docs: make code blocks collapsible (#7606)

* added collapsible code feature

* fixed side shadow

* fix build errors

* change design

* make code blocks collapsible
This commit is contained in:
Shahed Nasser
2024-06-05 10:28:41 +03:00
committed by GitHub
parent dc087bf310
commit 4a6327e497
44 changed files with 815 additions and 446 deletions
+2
View File
@@ -1,3 +1,5 @@
export * from "./use-collapsible"
export * from "./use-collapsible-code-lines"
export * from "./use-copy"
export * from "./use-current-learning-path"
export * from "./use-is-browser"
@@ -0,0 +1,136 @@
"use client"
import {
LineInputProps,
LineOutputProps,
Token,
TokenInputProps,
TokenOutputProps,
} from "prism-react-renderer"
import React, { useCallback, useMemo } from "react"
import { CodeBlockCollapsibleLines } from "../../components/CodeBlock/Collapsible/Lines"
import { useCollapsible } from "../use-collapsible"
export type HighlightProps = {
getLineProps: (input: LineInputProps) => LineOutputProps
getTokenProps: (input: TokenInputProps) => TokenOutputProps
}
export type CollapsibleCodeLines = {
collapsibleLinesStr?: string
getLines: (
token: Token[][],
highlightProps: HighlightProps,
lineNumberOffset?: number
) => React.ReactNode
}
export type CollapsedCodeLinesPosition = "start" | "end"
export const useCollapsibleCodeLines = ({
collapsibleLinesStr,
getLines,
}: CollapsibleCodeLines) => {
const collapsedRange:
| {
start: number
end: number
}
| undefined = useMemo(() => {
if (!collapsibleLinesStr) {
return
}
const splitCollapsedLines = collapsibleLinesStr
.split("-")
.map((lineNumber) => parseInt(lineNumber))
if (
splitCollapsedLines.length !== 2 ||
(splitCollapsedLines[0] !== 1 && splitCollapsedLines[1] < 2)
) {
return
}
return {
start: splitCollapsedLines[0],
end: splitCollapsedLines[1],
}
}, [collapsibleLinesStr])
const type: CollapsedCodeLinesPosition | undefined = useMemo(() => {
if (!collapsedRange) {
return undefined
}
return collapsedRange.start === 1 ? "start" : "end"
}, [collapsedRange])
const collapsibleHookResult = useCollapsible({
unmountOnExit: false,
translateEnabled: false,
heightAnimation: true,
})
const getCollapsedLinesElm = useCallback(
({
tokens,
highlightProps,
}: {
tokens: Token[][]
highlightProps: HighlightProps
}) => {
if (!collapsedRange || !type) {
return <></>
}
const startIndex =
type === "start" ? collapsedRange.start - 1 : collapsedRange.start
const lines = tokens.slice(
startIndex,
Math.min(collapsedRange.end, tokens.length)
)
return (
<CodeBlockCollapsibleLines {...collapsibleHookResult} type={type}>
{getLines(lines, highlightProps, startIndex)}
</CodeBlockCollapsibleLines>
)
},
[collapsedRange, collapsibleHookResult]
)
const getNonCollapsedLinesElm = useCallback(
({
tokens,
highlightProps,
}: {
tokens: Token[][]
highlightProps: HighlightProps
}) => {
if (!collapsedRange) {
return getLines(tokens, highlightProps)
}
const isCollapseBeginning = collapsedRange.start === 1
const lines = tokens.slice(
isCollapseBeginning ? collapsedRange.end : 0,
isCollapseBeginning ? undefined : collapsedRange.start
)
return getLines(
lines,
highlightProps,
isCollapseBeginning ? collapsedRange.end : 0
)
},
[collapsedRange, collapsibleHookResult]
)
return {
getCollapsedLinesElm,
getNonCollapsedLinesElm,
type,
...collapsibleHookResult,
}
}
@@ -0,0 +1,85 @@
"use client"
import React, { useState } from "react"
import { CSSTransition } from "react-transition-group"
export type CollapsibleProps = {
initialValue?: boolean
heightAnimation?: boolean
translateEnabled?: boolean
onClose?: () => void
unmountOnExit?: boolean
}
export type CollapsibleReturn = {
getCollapsibleElms: (children: React.ReactNode) => React.JSX.Element
collapsed: boolean
setCollapsed: React.Dispatch<React.SetStateAction<boolean>>
}
export const useCollapsible = ({
initialValue = true,
heightAnimation = false,
translateEnabled = true,
onClose,
unmountOnExit = true,
}: CollapsibleProps): CollapsibleReturn => {
const [collapsed, setCollapsed] = useState(initialValue)
const getCollapsibleElms = (children: React.ReactNode) => (
<CSSTransition
unmountOnExit={unmountOnExit}
in={!collapsed}
timeout={150}
onEnter={(node: HTMLElement) => {
if (heightAnimation) {
node.classList.add("transition-[height]")
node.style.height = `0px`
} else {
node.classList.add("!mb-docs_2", "!mt-0")
if (translateEnabled) {
node.classList.add("translate-y-docs_1", "transition-transform")
}
}
}}
onEntering={(node: HTMLElement) => {
if (heightAnimation) {
node.style.height = `${node.scrollHeight}px`
}
}}
onEntered={(node: HTMLElement) => {
if (heightAnimation) {
node.style.height = `auto`
}
}}
onExit={(node: HTMLElement) => {
if (heightAnimation) {
node.style.height = `${node.scrollHeight}px`
} else {
if (translateEnabled) {
node.classList.add("transition-transform", "!-translate-y-docs_1")
}
setTimeout(() => {
onClose?.()
}, 100)
}
}}
onExiting={(node: HTMLElement) => {
if (heightAnimation) {
node.style.height = `0px`
setTimeout(() => {
onClose?.()
}, 100)
}
}}
>
{children}
</CSSTransition>
)
return {
getCollapsibleElms,
collapsed,
setCollapsed,
}
}