Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions src/components/TableOfContents/ItemsList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { ChakraProps, List, ListItem } from "@chakra-ui/react"

import type { ToCItem } from "@/lib/types"

import ToCLink from "@/components/TableOfContents/TableOfContentsLink"

export type ItemsListProps = ChakraProps & {
export type ItemsListProps = {
items: Array<ToCItem>
depth: number
maxDepth: number
Expand All @@ -25,19 +23,19 @@ const ItemsList = ({
{items.map((item, index) => {
const { title, items } = item
return (
<ListItem key={index} m={0} {...rest}>
<li key={index} className="mb-2 last:mb-0" {...rest}>
<ToCLink depth={depth} item={item} activeHash={activeHash} />
{items && (
<List key={title} fontSize="sm" ps="2" m="0" mt="2" spacing="2">
<ul key={title} className="m-0 mt-2 list-none gap-2 ps-2 text-sm">
<ItemsList
items={items}
depth={depth + 1}
maxDepth={maxDepth}
activeHash={activeHash}
/>
</List>
</ul>
)}
</ListItem>
</li>
)
})}
</>
Expand Down
59 changes: 59 additions & 0 deletions src/components/TableOfContents/ItemsListMobile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Fragment } from "react"

import type { ToCItem } from "@/lib/types"

import { cn } from "@/lib/utils/cn"

import { DropdownMenuItem } from "../ui/dropdown-menu"
import { BaseLink } from "../ui/Link"

export type ItemsListProps = {
items: Array<ToCItem>
depth: number
maxDepth: number
activeHash?: string
className?: string
}

const ItemsListMobile = ({
items,
depth,
maxDepth,
activeHash,
...rest
}: ItemsListProps) => {
if (depth > maxDepth) return null

return (
<>
{items.map((item, index) => {
const { items, title, url } = item
return (
<Fragment key={title}>
<DropdownMenuItem key={index} {...rest} asChild>
<BaseLink
className="text-body no-underline focus-visible:outline-0"
href={url}
>
{title}
</BaseLink>
</DropdownMenuItem>
{items && (
<ItemsListMobile
className={cn("ps-4", depth > 1 && "ps-8")}
items={items}
depth={depth + 1}
maxDepth={maxDepth}
activeHash={activeHash}
/>
)}
</Fragment>
)
})}
</>
)
}

ItemsListMobile.displayName = "TocItemsListMobile"

export default ItemsListMobile
65 changes: 16 additions & 49 deletions src/components/TableOfContents/TableOfContentsLink.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { cssVar, SystemStyleObject } from "@chakra-ui/react"

import type { ToCItem } from "@/lib/types"

import { BaseLink } from "@/components/Link"
import { cn } from "@/lib/utils/cn"

import { BaseLink } from "../ui/Link"

export type TableOfContentsLinkProps = {
depth: number
Expand All @@ -16,57 +16,24 @@ const Link = ({
activeHash,
}: TableOfContentsLinkProps) => {
const isActive = activeHash === url
const isNested = depth === 2

const classList: Array<string> = []
isActive && classList.push("active")
isNested && classList.push("nested")
const classes = classList.join(" ")

const $dotBg = cssVar("dot-bg")

const hoverOrActiveStyle: SystemStyleObject = {
color: $dotBg.reference,
_after: {
content: `""`,
backgroundColor: "background.base",
border: "1px",
borderColor: $dotBg.reference,
borderRadius: "50%",
boxSize: 2,
position: "absolute",
// 16px is the initial list padding
// 8px is the padding for each nested list
// 4px is half of the width of the dot
// 1px for the border
"inset-inline-start": `calc(-16px - 8px * ${depth} - 4px - 1px)`,
top: "50%",
mt: -1,
},
}

return (
<BaseLink
href={url}
className={classes}
textDecoration="none"
display="inline-block"
position="relative"
color="body.medium"
width={{ base: "100%", lg: "auto" }}
_hover={{
...hoverOrActiveStyle,
}}
p="2"
ps="0"
sx={{
[$dotBg.variable]: "var(--eth-colors-primary-hover)",
"&.active": {
[$dotBg.variable]: "var(--eth-colors-primary-visited)",
...hoverOrActiveStyle,
},
}}
className={cn(
"group relative inline-block w-full p-2 ps-0 text-body-medium no-underline lg:w-auto",
isActive && "visited"
)}
>
<div
className={cn(
"absolute top-1/2 -mt-1 hidden h-2 w-2 rounded-full border border-primary-hover bg-background group-hover:inline-block",
isActive && "inline-block"
)}
style={{
insetInlineStart: `calc(-16px - 8px * ${depth} - 4px - 1px)`,
}}
/>
{title}
</BaseLink>
)
Expand Down
88 changes: 32 additions & 56 deletions src/components/TableOfContents/TableOfContentsMobile.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import React from "react"
import { useTranslation } from "next-i18next"
import { MdExpandMore } from "react-icons/md"
import {
Box,
chakra,
Fade,
Flex,
Icon,
List,
useDisclosure,
useToken,
} from "@chakra-ui/react"

import type { ToCItem } from "@/lib/types"

import { outerListProps } from "@/lib/utils/toc"
import { Button } from "../ui/buttons/Button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "../ui/dropdown-menu"

import ItemsList from "./ItemsList"
import ItemsListMobile from "./ItemsListMobile"

export type TableOfContentsMobileProps = {
items?: Array<ToCItem>
Expand All @@ -25,58 +20,39 @@ export type TableOfContentsMobileProps = {

const Mobile = ({ items, maxDepth }: TableOfContentsMobileProps) => {
const { t } = useTranslation("common")
// TODO: Replace with direct token implementation after UI migration is completed
const lgBp = useToken("breakpoints", "lg")

const { getButtonProps, getDisclosureProps, isOpen } = useDisclosure({
defaultIsOpen: false,
})
if (!items) {
return null
}

return (
<Box
hideFrom={lgBp}
as="aside"
background="background.base"
border="1px"
borderColor="border"
borderRadius="4px"
py={2}
px={4}
>
<Flex
color="text200"
cursor="pointer"
alignItems="center"
justify="space-between"
{...getButtonProps()}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
isSecondary
variant="outline"
className="flex w-full justify-between lg:hidden"
>
<span className="flex-1 text-center">{t("on-this-page")}</span>
<MdExpandMore />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
sideOffset={8}
className="w-[var(--radix-dropdown-menu-trigger-width)]"
// prevents focus from moving to the trigger after closing
onCloseAutoFocus={(e) => {
e.preventDefault()
}}
>
<chakra.span flex={1} fontWeight={500}>
{t("on-this-page")}
</chakra.span>
<Icon
as={MdExpandMore}
transform={isOpen ? "rotate(0)" : "rotate(-90deg)"}
boxSize={6}
transition="transform .4s"
<ItemsListMobile
items={items}
depth={0}
maxDepth={maxDepth ? maxDepth : 1}
/>
</Flex>
<Fade
in={isOpen}
{...getDisclosureProps()}
transition={{ enter: { duration: 0.6 } }}
>
<List {...outerListProps}>
<ItemsList
items={items}
depth={0}
maxDepth={maxDepth ? maxDepth : 1}
/>
</List>
</Fade>
</Box>
</DropdownMenuContent>
</DropdownMenu>
)
}

Expand Down
56 changes: 17 additions & 39 deletions src/components/TableOfContents/index.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import { useTranslation } from "next-i18next"
import { FaGithub } from "react-icons/fa"
import {
Box,
BoxProps,
calc,
Flex,
Icon,
List,
useToken,
} from "@chakra-ui/react"

import type { ToCItem } from "@/lib/types"

import { ButtonLink } from "@/components/Buttons"
import ItemsList from "@/components/TableOfContents/ItemsList"
import Mobile from "@/components/TableOfContents/TableOfContentsMobile"

import { outerListProps } from "@/lib/utils/toc"
import { cn } from "@/lib/utils/cn"

import { ButtonLink } from "../ui/buttons/Button"

import { useActiveHash } from "@/hooks/useActiveHash"

export type TableOfContentsProps = BoxProps & {
export type TableOfContentsProps = {
items: Array<ToCItem>
maxDepth?: number
editPath?: string
hideEditButton?: boolean
isMobile?: boolean
className?: string
}

const TableOfContents = ({
Expand All @@ -34,11 +27,10 @@ const TableOfContents = ({
editPath,
hideEditButton = false,
isMobile = false,
className,
...rest
}: TableOfContentsProps) => {
const { t } = useTranslation("common")
// TODO: Replace with direct token implementation after UI migration is completed
const lgBp = useToken("breakpoints", "lg")

const titleIds: Array<string> = []

Expand All @@ -65,43 +57,29 @@ const TableOfContents = ({
}

return (
<Flex
direction="column"
align="start"
gap={4}
hideBelow={lgBp}
as="aside"
position="sticky"
top="19" // Account for navbar
p={4}
pe={0}
maxW="25%"
minW={48}
height={calc.subtract("100vh", "80px")}
overflowY="auto"
<aside
className={cn(
"sticky top-19 hidden h-[calc(100vh-80px)] min-w-48 max-w-[25%] flex-col items-start gap-4 overflow-y-auto p-4 pe-0 lg:flex",
Comment thread
pettinarip marked this conversation as resolved.
className
)}
{...rest}
>
{!hideEditButton && editPath && (
<ButtonLink
leftIcon={<Icon as={FaGithub} />}
href={editPath}
variant="outline"
>
<ButtonLink href={editPath} variant="outline">
<FaGithub />
{t("edit-page")}
</ButtonLink>
)}
<Box textTransform="uppercase" color="body.medium">
{t("on-this-page")}
</Box>
<List m={0} spacing="2" {...outerListProps}>
<div className="uppercase text-body-medium">{t("on-this-page")}</div>
<ul className="m-0 mb-2 mt-2 list-none gap-2 border-s border-s-body-medium ps-4 pt-0 text-sm">
<ItemsList
items={items}
depth={0}
maxDepth={maxDepth ? maxDepth : 1}
activeHash={activeHash}
/>
</List>
</Flex>
</ul>
</aside>
)
}

Expand Down
Loading