mirror of
https://github.com/Stone-Red-Code/website.git
synced 2026-09-04 09:15:58 +02:00
feat(resources): make languages collapsible
This commit is contained in:
@@ -5,10 +5,17 @@ function appendSlashToPath(path: string): string {
|
||||
return path.endsWith("/") ? path : `${path}/`
|
||||
}
|
||||
|
||||
function getSecondLevel(path: string): string | void {
|
||||
const [, , secondLevel] = path.split("/")
|
||||
|
||||
return secondLevel
|
||||
}
|
||||
|
||||
export interface ILocationContextInterface {
|
||||
location: WindowLocation | void
|
||||
isHome: boolean
|
||||
isMatchingPath: (path: string) => boolean
|
||||
getSecondLevel: (path: string) => string | void
|
||||
}
|
||||
|
||||
interface ILocationProviderProps {
|
||||
@@ -19,9 +26,9 @@ export const LocationContext = React.createContext<ILocationContextInterface | n
|
||||
null
|
||||
)
|
||||
|
||||
export const LocationProvider: FC<
|
||||
PropsWithChildren<ILocationProviderProps>
|
||||
> = ({ children, location }) => {
|
||||
export const LocationProvider: FC<PropsWithChildren<
|
||||
ILocationProviderProps
|
||||
>> = ({ children, location }) => {
|
||||
const memoizedContextValue = useMemo(
|
||||
() => ({
|
||||
location,
|
||||
@@ -31,6 +38,7 @@ export const LocationProvider: FC<
|
||||
? appendSlashToPath(location.pathname).startsWith(path)
|
||||
: false
|
||||
},
|
||||
getSecondLevel,
|
||||
}),
|
||||
[location]
|
||||
)
|
||||
|
||||
+15
-4
@@ -1,8 +1,9 @@
|
||||
import React, { FC, useMemo, useState } from "react"
|
||||
import React, { FC, useEffect, useMemo, useState } from "react"
|
||||
import useLocation from "./hooks/useLocation"
|
||||
|
||||
export interface ISidebarContextInterface {
|
||||
current: number
|
||||
setCurrent: (index: number) => void
|
||||
current: string | null
|
||||
setCurrent: (key: string) => void
|
||||
openOnMobile: boolean
|
||||
setOpenOnMobile: (active: boolean) => void
|
||||
}
|
||||
@@ -12,9 +13,19 @@ export const SidebarContext = React.createContext<ISidebarContextInterface | nul
|
||||
)
|
||||
|
||||
export const SidebarProvider: FC = ({ children }) => {
|
||||
const [current, setCurrent] = useState(0)
|
||||
const { location, getSecondLevel } = useLocation()
|
||||
const [current, setCurrent] = useState<string | null>(null)
|
||||
const [openOnMobile, setOpenOnMobile] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (location && location.pathname) {
|
||||
const secondLevel = getSecondLevel(location.pathname)
|
||||
setCurrent(secondLevel || null)
|
||||
} else {
|
||||
setCurrent(null)
|
||||
}
|
||||
}, [location])
|
||||
|
||||
const memoizedContextValue = useMemo(
|
||||
() => ({
|
||||
current,
|
||||
|
||||
@@ -46,7 +46,6 @@ const childrenSort = sortWith<IFileOrFolder>([
|
||||
|
||||
function Tree({
|
||||
item,
|
||||
index,
|
||||
firstLevel,
|
||||
}: {
|
||||
item: IFileOrFolder
|
||||
@@ -74,8 +73,8 @@ function Tree({
|
||||
)
|
||||
}
|
||||
|
||||
if (firstLevel && index !== undefined) {
|
||||
return <FirstLevelFolder key={item.title} item={item} index={index} />
|
||||
if (firstLevel) {
|
||||
return <FirstLevelFolder key={item.title} item={item} />
|
||||
}
|
||||
|
||||
return <Folder key={item.title} item={item} />
|
||||
@@ -108,46 +107,81 @@ function Folder({ item }: { item: IFolder }) {
|
||||
)
|
||||
}
|
||||
|
||||
const FirstLevelFolder = memo(
|
||||
({ item, index }: { item: IFolder; index: number }) => {
|
||||
const { current, setCurrent } = useSidebar()
|
||||
const FirstLevelFolder = memo(({ item }: { item: IFolder }) => {
|
||||
const { setCurrent } = useSidebar()
|
||||
|
||||
useMatchingPath(item.path, () => {
|
||||
setCurrent(index)
|
||||
})
|
||||
useMatchingPath(item.path, () => {
|
||||
setCurrent(item.title)
|
||||
})
|
||||
|
||||
const collapsed = current !== index
|
||||
const sortedChildren = childrenSort(item.children)
|
||||
const sortedChildren = childrenSort(item.children)
|
||||
|
||||
return (
|
||||
<SC.TreeWrapper className="firstLevel" collapsed={collapsed}>
|
||||
<SC.FirstLabel
|
||||
className={!collapsed ? "active" : undefined}
|
||||
onClick={() => setCurrent(index)}
|
||||
>
|
||||
{humanize(item.title)}
|
||||
<SC.CollapseToggler />
|
||||
</SC.FirstLabel>
|
||||
<SC.Children>
|
||||
{sortedChildren.map(node => (
|
||||
<Tree item={node} />
|
||||
return (
|
||||
<SC.TreeWrapper className="firstLevel">
|
||||
<SC.FirstLabel>{humanize(item.title)}</SC.FirstLabel>
|
||||
<SC.Children>
|
||||
{sortedChildren.map(node => (
|
||||
<Tree item={node} />
|
||||
))}
|
||||
</SC.Children>
|
||||
</SC.TreeWrapper>
|
||||
)
|
||||
})
|
||||
|
||||
const AllLanguages: FC<{
|
||||
items: IFileOrFolder[]
|
||||
expanded: boolean
|
||||
setExpanded: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}> = ({ items, expanded, setExpanded }) => {
|
||||
const { current, setCurrent } = useSidebar()
|
||||
|
||||
return (
|
||||
<SC.ExpandLanguages>
|
||||
<SC.ExpandLanguagesHeader
|
||||
onClick={() => setExpanded(prevState => !prevState)}
|
||||
>
|
||||
Expand languages {expanded ? <SC.CollapseIcon /> : <SC.ExpandIcon />}
|
||||
</SC.ExpandLanguagesHeader>
|
||||
{expanded && (
|
||||
<SC.ExpandLanguagesList>
|
||||
{items.map(item => (
|
||||
<SC.Language
|
||||
className={current === item.title ? "active" : ""}
|
||||
onClick={() => {
|
||||
setCurrent(item.title)
|
||||
setExpanded(false)
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</SC.Language>
|
||||
))}
|
||||
</SC.Children>
|
||||
</SC.TreeWrapper>
|
||||
)
|
||||
}
|
||||
)
|
||||
</SC.ExpandLanguagesList>
|
||||
)}
|
||||
</SC.ExpandLanguages>
|
||||
)
|
||||
}
|
||||
|
||||
export const ResourcesSidebar: FC<HTMLAttributes<HTMLDivElement>> = props => {
|
||||
const [expandedLanguages, setExpandedLanguages] = useState(false)
|
||||
const resources = useStaticQuery<IAllResourcesQuery>(ALL_RESOURCES)
|
||||
const tree = useBuildTree(resources, "/resources")
|
||||
const sortedTree = sort((a, b) => a.title.localeCompare(b.title), tree)
|
||||
const languages = useBuildTree(resources, "/resources")
|
||||
const { current } = useSidebar()
|
||||
const sortedLanguages = sort(
|
||||
(a, b) => a.title.localeCompare(b.title),
|
||||
languages
|
||||
)
|
||||
|
||||
const currentLanguage =
|
||||
sortedLanguages.find(lang => lang.title === current) || sortedLanguages[0]
|
||||
|
||||
return (
|
||||
<Sidebar {...props}>
|
||||
{sortedTree.map((node, index) => (
|
||||
<Tree item={node} index={index} firstLevel={true} />
|
||||
))}
|
||||
<AllLanguages
|
||||
items={sortedLanguages}
|
||||
expanded={expandedLanguages}
|
||||
setExpanded={setExpandedLanguages}
|
||||
/>
|
||||
<Tree item={currentLanguage} firstLevel={true} />
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Link } from "gatsby"
|
||||
import { transparentize } from "polished"
|
||||
import styled from "styled-components"
|
||||
import styled, { css } from "styled-components"
|
||||
import ChevronUp from "../../icons/chevron-up.svg"
|
||||
import Collapse from "../../icons/collapse.svg"
|
||||
import Expand from "../../icons/expand.svg"
|
||||
|
||||
export const Children = styled.div`
|
||||
padding-left: 16px;
|
||||
@@ -10,6 +12,10 @@ export const Children = styled.div`
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`
|
||||
|
||||
export const Label = styled.div`
|
||||
@@ -44,7 +50,7 @@ export const FirstLabel = styled.div`
|
||||
box-sizing: border-box;
|
||||
padding: 12px 15px 12px 0;
|
||||
font-weight: 700;
|
||||
color: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||
color: ${props => props.theme.sidebar.foreground};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
@@ -55,30 +61,17 @@ export const FirstLabel = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${props => props.theme.sidebar.foreground};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-left: auto;
|
||||
transform: rotate(90deg);
|
||||
transition: transform 0.3s;
|
||||
|
||||
path {
|
||||
fill: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||
}
|
||||
}
|
||||
|
||||
&.active svg {
|
||||
transform: rotate(0);
|
||||
|
||||
path {
|
||||
fill: ${props => props.theme.sidebar.foreground};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const TreeWrapper = styled.div<{ collapsed: boolean }>`
|
||||
export const TreeWrapper = styled.div<{ collapsed?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
@@ -88,11 +81,6 @@ export const TreeWrapper = styled.div<{ collapsed: boolean }>`
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid
|
||||
${props => transparentize(0.8, props.theme.sidebar.foreground)};
|
||||
}
|
||||
|
||||
${Children} {
|
||||
display: ${props => (props.collapsed ? "none" : "flex")};
|
||||
}
|
||||
@@ -105,22 +93,64 @@ export const TreeWrapper = styled.div<{ collapsed: boolean }>`
|
||||
|
||||
export const CollapseToggler = styled(ChevronUp)``
|
||||
|
||||
export const PageLink = styled(Link)`
|
||||
export const CollapseIcon = styled(Collapse)`
|
||||
margin-left: auto;
|
||||
|
||||
path {
|
||||
fill: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||
}
|
||||
`
|
||||
|
||||
export const ExpandIcon = styled(Expand)`
|
||||
margin-left: auto;
|
||||
|
||||
path {
|
||||
fill: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||
}
|
||||
`
|
||||
|
||||
export const ExpandLanguages = styled.div`
|
||||
padding: 12px 15px 12px 0;
|
||||
|
||||
border-bottom: 1px solid
|
||||
${props => transparentize(0.8, props.theme.sidebar.foreground)};
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
export const ExpandLanguagesHeader = styled.div`
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||
font-weight: 700;
|
||||
`
|
||||
|
||||
export const ExpandLanguagesList = styled.div`
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const item = css`
|
||||
display: block;
|
||||
padding: 4px 0 0;
|
||||
margin-bottom: 4px;
|
||||
color: ${props => props.theme.sidebar.foreground};
|
||||
align-self: flex-start;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: ${props =>
|
||||
transparentize(0.7, props.theme.sidebar.foreground)};
|
||||
}
|
||||
|
||||
& + & {
|
||||
margin-top: 4px;
|
||||
& + &,
|
||||
${TreeWrapper} + & {
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@@ -132,3 +162,11 @@ export const PageLink = styled(Link)`
|
||||
border-color: ${props => transparentize(0.7, props.theme.sidebar.active)};
|
||||
}
|
||||
`
|
||||
|
||||
export const Language = styled.div`
|
||||
${item};
|
||||
`
|
||||
|
||||
export const PageLink = styled(Link)`
|
||||
${item};
|
||||
`
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="19" viewBox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.73261 11.2124L0.21417 17.7463C-0.0712273 18.033 -0.0712273 18.4975 0.21417 18.785C0.499568 19.0717 0.963128 19.0717 1.24853 18.785L7.24976 12.7693L13.251 18.7842C13.5364 19.071 14 19.071 14.2861 18.7842C14.5715 18.4975 14.5715 18.0323 14.2861 17.7456L7.76763 11.2117C7.48518 10.9293 7.01434 10.9293 6.73261 11.2124Z" fill="#6C6C6C"/>
|
||||
<path d="M7.76751 7.7876L14.286 1.25367C14.5713 0.966964 14.5713 0.502457 14.286 0.215028C14.0006 -0.0716761 13.537 -0.0716761 13.2516 0.215028L7.25036 6.23069L1.24913 0.215752C0.96373 -0.0709527 0.500169 -0.0709527 0.214049 0.215752C-0.0713482 0.502456 -0.0713482 0.967687 0.214049 1.25439L6.73249 7.78832C7.01494 8.07068 7.48578 8.07068 7.76751 7.7876Z" fill="#6C6C6C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 822 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="19" viewBox="0 0 15 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.2138 18.7876L14.7322 12.2537C15.0176 11.967 15.0176 11.5025 14.7322 11.215C14.4468 10.9283 13.9833 10.9283 13.6979 11.215L7.69665 17.2307L1.69542 11.2158C1.41002 10.929 0.946458 10.929 0.660338 11.2158C0.374941 11.5025 0.374941 11.9677 0.660338 12.2544L7.17878 18.7883C7.46123 19.0707 7.93207 19.0707 8.2138 18.7876Z" fill="#6C6C6C"/>
|
||||
<path d="M7.17878 0.212404L0.660337 6.74633C0.37494 7.03304 0.37494 7.49754 0.660337 7.78497C0.945735 8.07168 1.40929 8.07168 1.69469 7.78497L7.69593 1.76931L13.6972 7.78425C13.9826 8.07095 14.4461 8.07095 14.7322 7.78425C15.0176 7.49754 15.0176 7.03231 14.7322 6.74561L8.2138 0.21168C7.93135 -0.0706804 7.4605 -0.0706805 7.17878 0.212404Z" fill="#6C6C6C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 808 B |
Reference in New Issue
Block a user