Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 96fb083

Browse files
authoredDec 14, 2024··
[v4] select tab with active hash and scroll to right heading (#3803)
* aa * aa * prettify
1 parent f60a4a4 commit 96fb083

File tree

8 files changed

+89
-62
lines changed

8 files changed

+89
-62
lines changed
 

‎.changeset/neat-seas-beg.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nextra": patch
3+
---
4+
5+
select tab with active hash and scroll to right heading

‎packages/nextra-theme-blog/CHANGELOG.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,8 @@
355355
by
356356

357357
```js
358-
import "nextra-theme-docs/style.css"; // for docs theme
359-
import "nextra-theme-blog/style.css"; // for blog theme
358+
import 'nextra-theme-docs/style.css' // for docs theme
359+
import 'nextra-theme-blog/style.css' // for blog theme
360360
```
361361

362362
- Updated dependencies [2c8a8ab]
@@ -1408,19 +1408,19 @@
14081408
and `nextra-theme-docs`
14091409

14101410
```js
1411-
import { Card, Cards } from "nextra/components";
1411+
import { Card, Cards } from 'nextra/components'
14121412
```
14131413

14141414
```js
1415-
import { Tab, Tabs } from "nextra/components";
1415+
import { Tab, Tabs } from 'nextra/components'
14161416
```
14171417

14181418
```js
1419-
import { Steps } from "nextra/components";
1419+
import { Steps } from 'nextra/components'
14201420
```
14211421

14221422
```js
1423-
import { FileTree } from "nextra/components";
1423+
import { FileTree } from 'nextra/components'
14241424
```
14251425

14261426
### Patch Changes

‎packages/nextra-theme-docs/CHANGELOG.md

+18-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
### Minor Changes
66

7-
- 6d22c8f: export a `style-prefixed.css` file with Tailwind CSS v4 prefixed layers for projects using Tailwind CSS v3
7+
- 6d22c8f: export a `style-prefixed.css` file with Tailwind CSS v4 prefixed
8+
layers for projects using Tailwind CSS v3
89

910
- `@layer utilities` -> `@layer v4-utilities`
1011
- `@layer base` -> `@layer v4-base`
@@ -398,8 +399,8 @@
398399
by
399400

400401
```js
401-
import "nextra-theme-docs/style.css"; // for docs theme
402-
import "nextra-theme-blog/style.css"; // for blog theme
402+
import 'nextra-theme-docs/style.css' // for docs theme
403+
import 'nextra-theme-blog/style.css' // for blog theme
403404
```
404405

405406
- Updated dependencies [2c8a8ab]
@@ -1898,19 +1899,19 @@
18981899
and `nextra-theme-docs`
18991900

19001901
```js
1901-
import { Card, Cards } from "nextra/components";
1902+
import { Card, Cards } from 'nextra/components'
19021903
```
19031904

19041905
```js
1905-
import { Tab, Tabs } from "nextra/components";
1906+
import { Tab, Tabs } from 'nextra/components'
19061907
```
19071908

19081909
```js
1909-
import { Steps } from "nextra/components";
1910+
import { Steps } from 'nextra/components'
19101911
```
19111912

19121913
```js
1913-
import { FileTree } from "nextra/components";
1914+
import { FileTree } from 'nextra/components'
19141915
```
19151916

19161917
### Patch Changes
@@ -2486,14 +2487,14 @@
24862487
- 582ad96: feat: bump `rehype-pretty-code` version, support `showLineNumbers`
24872488
- da998e6: move react components to `components` folder and replace exports:
24882489
```ts
2489-
import Bleed from "nextra-theme-docs/bleed";
2490-
import Callout from "nextra-theme-docs/callout";
2491-
import Collapse from "nextra-theme-docs/collapse";
2492-
import { Tab, Tabs } from "nextra-theme-docs/tabs";
2490+
import Bleed from 'nextra-theme-docs/bleed'
2491+
import Callout from 'nextra-theme-docs/callout'
2492+
import Collapse from 'nextra-theme-docs/collapse'
2493+
import { Tab, Tabs } from 'nextra-theme-docs/tabs'
24932494
```
24942495
by
24952496
```ts
2496-
import { Bleed, Callout, Collapse, Tab, Tabs } from "nextra-theme-docs";
2497+
import { Bleed, Callout, Collapse, Tab, Tabs } from 'nextra-theme-docs'
24972498
```
24982499
- e6771ca: move `withLayout` logic directly in nextra loader
24992500
- 8ad9507: fix unable expanding folder items in sidebar
@@ -2813,14 +2814,14 @@
28132814
- 48e0ac2: export `useConfig` and `useTheme`
28142815
- da998e6: move react components to `components` folder and replace exports:
28152816
```ts
2816-
import Bleed from "nextra-theme-docs/bleed";
2817-
import Callout from "nextra-theme-docs/callout";
2818-
import Collapse from "nextra-theme-docs/collapse";
2819-
import { Tab, Tabs } from "nextra-theme-docs/tabs";
2817+
import Bleed from 'nextra-theme-docs/bleed'
2818+
import Callout from 'nextra-theme-docs/callout'
2819+
import Collapse from 'nextra-theme-docs/collapse'
2820+
import { Tab, Tabs } from 'nextra-theme-docs/tabs'
28202821
```
28212822
by
28222823
```ts
2823-
import { Bleed, Callout, Collapse, Tab, Tabs } from "nextra-theme-docs";
2824+
import { Bleed, Callout, Collapse, Tab, Tabs } from 'nextra-theme-docs'
28242825
```
28252826
- 43409ad: fix: mdx theme is missing
28262827

‎packages/nextra/CHANGELOG.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@
294294
by
295295

296296
```js
297-
import "nextra-theme-docs/style.css"; // for docs theme
298-
import "nextra-theme-blog/style.css"; // for blog theme
297+
import 'nextra-theme-docs/style.css' // for docs theme
298+
import 'nextra-theme-blog/style.css' // for blog theme
299299
```
300300

301301
## 4.0.0-app-router.8
@@ -1232,7 +1232,7 @@
12321232
while importing
12331233

12341234
```js
1235-
import filterRouteLocale from "nextra/filter-route-locale";
1235+
import filterRouteLocale from 'nextra/filter-route-locale'
12361236
```
12371237

12381238
- 4dd720ad: remove `font-weight: 500;` from styles of code blocks since it gives
@@ -1278,19 +1278,19 @@
12781278
and `nextra-theme-docs`
12791279

12801280
```js
1281-
import { Card, Cards } from "nextra/components";
1281+
import { Card, Cards } from 'nextra/components'
12821282
```
12831283

12841284
```js
1285-
import { Tab, Tabs } from "nextra/components";
1285+
import { Tab, Tabs } from 'nextra/components'
12861286
```
12871287

12881288
```js
1289-
import { Steps } from "nextra/components";
1289+
import { Steps } from 'nextra/components'
12901290
```
12911291

12921292
```js
1293-
import { FileTree } from "nextra/components";
1293+
import { FileTree } from 'nextra/components'
12941294
```
12951295

12961296
### Patch Changes

‎packages/nextra/src/client/components/tabs/index.client.tsx

+28-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import type {
1515
} from '@headlessui/react'
1616
import cn from 'clsx'
1717
import type { FC, ReactElement, ReactNode } from 'react'
18-
import { useEffect, useState } from 'react'
18+
import { useEffect, useRef, useState } from 'react'
19+
import { useHash } from './use-hash.js'
1920

2021
type TabItem = string | ReactElement
2122

@@ -47,13 +48,37 @@ export const Tabs: FC<
4748
tabClassName
4849
}) => {
4950
const [selectedIndex, setSelectedIndex] = useState(defaultIndex)
51+
const hash = useHash()
52+
const tabPanelsRef = useRef<HTMLDivElement>(null!)
5053

5154
useEffect(() => {
5255
if (_selectedIndex !== undefined) {
5356
setSelectedIndex(_selectedIndex)
5457
}
5558
}, [_selectedIndex])
5659

60+
useEffect(() => {
61+
if (!hash) return
62+
63+
const tabPanel = tabPanelsRef.current.querySelector(
64+
`[role=tabpanel]:has([id="${hash}"])`
65+
)
66+
if (!tabPanel) return
67+
68+
for (const [index, el] of Object.entries(tabPanelsRef.current.children)) {
69+
if (el === tabPanel) {
70+
setSelectedIndex(Number(index))
71+
// Execute on next tick after `selectedIndex` update
72+
setTimeout(() => {
73+
const link = tabPanel.querySelector<HTMLAnchorElement>(
74+
`a[href="#${hash}"]`
75+
)
76+
link?.click()
77+
}, 0)
78+
}
79+
}
80+
}, [hash])
81+
5782
useEffect(() => {
5883
if (!storageKey) {
5984
// Do not listen storage events if there is no storage key
@@ -116,7 +141,7 @@ export const Tabs: FC<
116141
const { selected, disabled, hover, focus } = args
117142
return cn(
118143
focus && 'x:nextra-focus x:ring-inset',
119-
'x:whitespace-nowrap',
144+
'x:whitespace-nowrap x:cursor-pointer',
120145
'x:rounded-t x:p-2 x:font-medium x:leading-5 x:transition-colors',
121146
'x:-mb-0.5 x:select-none x:border-b-2',
122147
selected
@@ -141,7 +166,7 @@ export const Tabs: FC<
141166
</HeadlessTab>
142167
))}
143168
</TabList>
144-
<TabPanels>{children}</TabPanels>
169+
<TabPanels ref={tabPanelsRef}>{children}</TabPanels>
145170
</TabGroup>
146171
)
147172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useEffect, useState } from 'react'
2+
3+
export function useHash() {
4+
const [hash, setHash] = useState('')
5+
6+
useEffect(() => {
7+
function handleHashChange() {
8+
setHash(location.hash.replace('#', ''))
9+
}
10+
handleHashChange()
11+
12+
window.addEventListener('hashchange', handleHashChange)
13+
return () => {
14+
window.removeEventListener('hashchange', handleHashChange)
15+
}
16+
}, [])
17+
18+
return hash
19+
}

‎packages/nextra/src/server/__tests__/compile.test.ts

-17
Original file line numberDiff line numberDiff line change
@@ -430,23 +430,6 @@ import Last from './three.mdx'
430430
}"
431431
`)
432432
})
433-
it('should not attach headings with parent Tab or Tabs.Tab', async () => {
434-
const rawJs = await compileMdx(
435-
`
436-
<Tab>
437-
## foo
438-
</Tab>
439-
440-
<Tabs.Tab>
441-
## bar
442-
## baz [#custom-id]
443-
</Tabs.Tab>
444-
`,
445-
{ mdxOptions }
446-
)
447-
expect(rawJs).toMatch('export const toc = useTOC()')
448-
expect(rawJs).not.toMatch('id=')
449-
})
450433
})
451434

452435
describe('Link', () => {

‎packages/nextra/src/server/remark-plugins/remark-headings.ts

+6-12
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ export const getFlattenedValue = (node: Parent): string =>
1717
)
1818
.join('')
1919

20-
const SKIP_FOR_PARENT_NAMES = new Set(['Tab', 'Tabs.Tab'])
21-
2220
export const remarkHeadings: Plugin<
2321
[{ exportName?: string; isRemoteContent?: boolean }],
2422
Root
@@ -39,23 +37,19 @@ export const remarkHeadings: Plugin<
3937
// verify .md/.mdx exports and attach named `toc` export
4038
'mdxjsEsm'
4139
],
42-
(node, _index, parent) => {
40+
(node, _index) => {
4341
if (node.type === 'heading') {
4442
if (node.depth === 1) {
4543
return
4644
}
4745

4846
node.data ||= {}
4947
const headingProps: HProperties = (node.data.hProperties ||= {})
50-
if (SKIP_FOR_PARENT_NAMES.has((parent as any).name)) {
51-
delete headingProps.id
52-
} else {
53-
const value = getFlattenedValue(node)
54-
const id = slugger.slug(headingProps.id || value)
55-
// Attach flattened/custom #id to heading node
56-
headingProps.id = id
57-
headings.push({ depth: node.depth, value, id })
58-
}
48+
const value = getFlattenedValue(node)
49+
const id = slugger.slug(headingProps.id || value)
50+
// Attach flattened/custom #id to heading node
51+
headingProps.id = id
52+
headings.push({ depth: node.depth, value, id })
5953
return
6054
}
6155

0 commit comments

Comments
 (0)
Please sign in to comment.