Merge pull request #209 from the-programmers-hangout/feat-resources-collapsible-languages

feat(resources): make languages collapsible
This commit is contained in:
Jean-Philippe Sirois
2020-01-18 16:05:55 -05:00
committed by GitHub
6 changed files with 164 additions and 65 deletions
+11 -3
View File
@@ -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
View File
@@ -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,
+67 -33
View File
@@ -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>
)
}
+63 -25
View File
@@ -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};
`
+4
View File
@@ -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

+4
View File
@@ -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