Skip to content

Commit 2771dab

Browse files
diedu89ndom91
authored andcommitted
fix: code tabs behavior
- do not scroll to top when changing tabs - use components names as url param value to make slug parsing deterministic - update local storage value according url param - separate local storage state for all frameworks and base frameworks
1 parent 0651f2c commit 2771dab

File tree

1 file changed

+117
-23
lines changed

1 file changed

+117
-23
lines changed

docs/components/Code/index.tsx

Lines changed: 117 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface ChildrenProps {
99
}
1010

1111
const AUTHJS_TAB_KEY = "authjs.codeTab.framework"
12+
const AUTHJS_TAB_KEY_ALL = "authjs.codeTab.framework.all"
1213

1314
Code.Next = NextCode
1415
Code.NextClient = NextClientCode
@@ -34,15 +35,76 @@ const allFrameworks = {
3435
[ExpressCode.name]: "Express",
3536
}
3637

37-
/**
38-
* Replace all non-alphabetic characters with a hyphen
39-
*
40-
* @param url - URL to parse
41-
* @returns - A string parsed from the URL
42-
*/
43-
const parseParams = (url: string): string => {
44-
let parsedUrl = url.toLowerCase().replaceAll(/[^a-zA-z]+/g, "-")
45-
return parsedUrl.endsWith("-") ? parsedUrl.slice(0, -1) : parsedUrl
38+
const findFrameworkKey = (
39+
text: string,
40+
frameworks: Record<string, string>
41+
): string | null => {
42+
const entry = Object.entries(frameworks).find(([_, value]) => value === text)
43+
return entry ? entry[0] : null
44+
}
45+
46+
const getIndexFrameworkFromUrl = (
47+
url: string,
48+
frameworks: Record<string, string>
49+
): number | null => {
50+
const params = new URLSearchParams(url)
51+
const paramValue = params.get("framework")
52+
if (!paramValue) return null
53+
54+
const pascalCase = paramValue.replace(
55+
/(^|-)([a-z])/g,
56+
(_, _separator, letter) => letter.toUpperCase()
57+
)
58+
59+
const index = Object.keys(frameworks).findIndex((key) => key === pascalCase)
60+
return index === -1 ? null : index
61+
}
62+
63+
const getIndexFrameworkFromStorage = (
64+
frameworks: Record<string, string>,
65+
isAllFrameworks: boolean
66+
): number | null => {
67+
const storageKey = isAllFrameworks ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY
68+
const storedIndex = window.localStorage.getItem(storageKey)
69+
70+
if (!storedIndex) {
71+
return null
72+
}
73+
74+
return parseInt(storedIndex) % Object.keys(frameworks).length
75+
}
76+
77+
const updateFrameworkStorage = (
78+
frameworkKey: string,
79+
frameworks: Record<string, string>,
80+
isAllFrameworks: boolean
81+
): void => {
82+
const index = Object.keys(frameworks).findIndex((key) => key === frameworkKey)
83+
if (index === -1) return
84+
85+
const storageKey = isAllFrameworks ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY
86+
window.localStorage.setItem(storageKey, index.toString())
87+
88+
// Update other storage if framework exists in other object
89+
const otherFrameworksKeys = Object.keys(
90+
isAllFrameworks ? baseFrameworks : allFrameworks
91+
)
92+
const otherStorageKey = isAllFrameworks ? AUTHJS_TAB_KEY : AUTHJS_TAB_KEY_ALL
93+
94+
const existsInOther = otherFrameworksKeys.includes(frameworkKey)
95+
if (existsInOther) {
96+
const otherIndex = otherFrameworksKeys.findIndex(
97+
(key) => key === frameworkKey
98+
)
99+
window.localStorage.setItem(otherStorageKey, otherIndex.toString())
100+
// see https://github.com/shuding/nextra/blob/7ae958f02922e608151411042f658480b75164a6/packages/nextra/src/client/components/tabs/index.client.tsx#L106
101+
window.dispatchEvent(
102+
new StorageEvent("storage", {
103+
key: otherStorageKey,
104+
newValue: otherIndex.toString(),
105+
})
106+
)
107+
}
46108
}
47109

48110
export function Code({ children }: ChildrenProps) {
@@ -58,37 +120,69 @@ export function Code({ children }: ChildrenProps) {
58120

59121
const renderedFrameworks = withNextJsPages ? allFrameworks : baseFrameworks
60122

61-
const updateFrameworkStorage = (value: string): void => {
123+
const updateFrameworkInUrl = (frameworkKey: string): void => {
62124
const params = new URLSearchParams(searchParams?.toString())
63-
params.set("framework", value)
64-
router.push(`${router.pathname}?${params.toString()}`)
125+
const kebabCaseValue = frameworkKey
126+
.replace(/([a-z])([A-Z])/g, "$1-$2")
127+
.toLowerCase()
128+
params.set("framework", kebabCaseValue)
129+
130+
router.push(`${router.pathname}?${params.toString()}`, undefined, {
131+
scroll: false,
132+
})
65133
}
66134

67135
const handleClickFramework = (event: MouseEvent<HTMLDivElement>) => {
68136
if (!(event.target instanceof HTMLButtonElement)) return
69137
const { textContent } = event.target as unknown as HTMLDivElement
70-
updateFrameworkStorage(parseParams(textContent!))
138+
if (!textContent) return
139+
140+
const frameworkKey = findFrameworkKey(textContent, renderedFrameworks)
141+
if (frameworkKey) {
142+
updateFrameworkInUrl(frameworkKey)
143+
updateFrameworkStorage(frameworkKey, renderedFrameworks, withNextJsPages)
144+
145+
// Focus and scroll to maintain position when code blocks above are expanded
146+
const element = event.target as HTMLButtonElement
147+
const rect = element.getBoundingClientRect()
148+
requestAnimationFrame(() => {
149+
element.focus()
150+
window.scrollBy(0, element.getBoundingClientRect().top - rect.top)
151+
})
152+
}
71153
}
72154

73155
useEffect(() => {
74-
const length = Object.keys(renderedFrameworks).length
75-
const getFrameworkStorage = window.localStorage.getItem(AUTHJS_TAB_KEY)
76-
const indexFramework = parseInt(getFrameworkStorage ?? "0") % length
77-
if (!getFrameworkStorage) {
78-
window.localStorage.setItem(AUTHJS_TAB_KEY, "0")
79-
}
80-
updateFrameworkStorage(
81-
parseParams(Object.values(renderedFrameworks)[indexFramework])
156+
const indexFrameworkFromStorage = getIndexFrameworkFromStorage(
157+
renderedFrameworks,
158+
withNextJsPages
159+
)
160+
const indexFrameworkFromUrl = getIndexFrameworkFromUrl(
161+
router.asPath,
162+
renderedFrameworks
82163
)
83-
}, [router.pathname, renderedFrameworks])
164+
165+
if (indexFrameworkFromStorage === null) {
166+
updateFrameworkStorage(
167+
Object.keys(renderedFrameworks)[indexFrameworkFromUrl ?? 0],
168+
renderedFrameworks,
169+
withNextJsPages
170+
)
171+
}
172+
173+
if (!indexFrameworkFromUrl) {
174+
const index = indexFrameworkFromStorage ?? 0
175+
updateFrameworkInUrl(Object.keys(renderedFrameworks)[index])
176+
}
177+
}, [router.pathname, renderedFrameworks, withNextJsPages])
84178

85179
return (
86180
<div
87181
className="[&_div[role='tablist']]:!pb-0"
88182
onClick={handleClickFramework}
89183
>
90184
<Tabs
91-
storageKey={AUTHJS_TAB_KEY}
185+
storageKey={withNextJsPages ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY}
92186
items={Object.values(renderedFrameworks)}
93187
>
94188
{Object.keys(renderedFrameworks).map((f) => {

0 commit comments

Comments
 (0)