Skip to content

Commit 7cde46f

Browse files
committed
sticky tab bars, synchronized tab containers
1 parent 32fe376 commit 7cde46f

File tree

3 files changed

+101
-42
lines changed

3 files changed

+101
-42
lines changed

advocacy_docs/playground/1/tabs.mdx

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,12 @@
22
title: Tabs Tester
33
---
44

5+
import { Link } from "gatsby"
56

6-
<TabContainer>
7-
<Tab title="Tab One">
8-
9-
## Tab One Content
10-
11-
Here is where there is [markdown](https://www.markdownguide.org/cheat-sheet/) content.
12-
13-
</Tab>
14-
<Tab title="Tab Two" defaultSelected="true">
15-
16-
## Tab Two Content
17-
18-
![Image](https://placehold.co/600x400)
19-
20-
</Tab>
21-
<Tab title="Tab Three">
22-
23-
## Tab Three Content
24-
25-
```jsx
26-
<TabContainer>
27-
<Tab title="Hello!">
28-
29-
Hello Tabs
30-
31-
</Tab>
32-
</TabContainer>
33-
```
34-
35-
</Tab>
36-
</TabContainer>
377

388
Tabs like these are created using JSX components in Markdown:
399

40-
<TabContainer>
10+
<TabContainer syncKey="language">
4111
<Tab title="Markdown">
4212

4313
**Markdown** content gets separated from the wrapping tab
@@ -137,4 +107,63 @@ Column *Three*
137107
</tr>
138108
</tbody></table>
139109
</Tab>
110+
</TabContainer>
111+
112+
It's also possible to set a default tab:
113+
114+
<TabContainer syncKey="language">
115+
<Tab title="Tab One">
116+
117+
## Tab One Content
118+
119+
Here is where there is [markdown](https://www.markdownguide.org/cheat-sheet/) content.
120+
121+
</Tab>
122+
<Tab title="Tab Two">
123+
124+
## Tab Two Content
125+
126+
![Image](https://placehold.co/600x400)
127+
128+
</Tab>
129+
<Tab title="JSX" defaultSelected="true">
130+
131+
```jsx
132+
<TabContainer>
133+
<Tab title="JSX" defaultSelected="true">
134+
135+
Hello Tabs
136+
137+
</Tab>
138+
</TabContainer>
139+
```
140+
141+
</Tab>
142+
</TabContainer>
143+
144+
Finally, you can sync tabs between containers by setting the `syncKey` property.
145+
Can be any string, tab selection will be synchronized between all containers with the same key.
146+
147+
Note: no validation is done here - you can use the same key for groups with dissimilar tabs.
148+
This might be appropriate in some situations (e.g. tabs for OS selection where one group doesn't apply to one or more OSes).
149+
...But it can also get weird, as demonstrated here (middle tab group has two dissimilar tabs, but all three containers have the same `syncKey` set).
150+
151+
<TabContainer syncKey="language">
152+
<Tab title="Markdown">
153+
154+
[A link to this page](./)
155+
156+
```markdown
157+
[A link to this page](tabs)
158+
```
159+
160+
</Tab>
161+
<Tab title="JSX">
162+
<p><Link to="./">A link to this page</Link></p>
163+
<pre><code>&lt;Link to="tabs">A link to this page&lt;/Link></code></pre>
164+
</Tab>
165+
<Tab title="HTML">
166+
<p><a href="./">A link to this page</a></p>
167+
<pre><code>&lt;a href="tabs">A link to this page&lt;/a></code></pre>
168+
</Tab>
140169
</TabContainer>

src/components/tab-container.js

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import React, {
33
cloneElement,
44
useState,
55
useId,
6-
useCallback,
6+
useEffect,
77
} from "react";
88

9-
export default function TabContainer({ className, children }) {
9+
export default function TabContainer({ className, syncKey, children }) {
1010
const id = useId();
1111

12-
const tabs = [];
1312
let implicitSelected = "";
13+
let tabs = [];
1414

1515
children = Children.map(children, (child) => {
1616
if (child && child.props && child.props.mdxType === "Tab") {
@@ -21,7 +21,7 @@ export default function TabContainer({ className, children }) {
2121
}
2222
const tabIndex = tabs.length + 1;
2323
const tabId = id + "tab" + tabIndex;
24-
tabs.push({ id: tabId, title, defaultSelected });
24+
tabs.push({ id: tabId, title });
2525
if (defaultSelected || tabIndex === 1) implicitSelected = title;
2626
return cloneElement(child, {
2727
id: tabId + "panel",
@@ -32,13 +32,34 @@ export default function TabContainer({ className, children }) {
3232
return child;
3333
});
3434
const [selected, setSelected] = useState(implicitSelected);
35+
const _tabs = JSON.stringify(tabs);
3536

36-
const onTabSelect = useCallback(
37-
(event) => {
38-
setSelected(event.target.value);
39-
},
40-
[setSelected],
41-
);
37+
useEffect(() => {
38+
if (syncKey) {
39+
const updateSelected = () => {
40+
const newSelected = sessionStorage.getItem("selectedTab-" + syncKey);
41+
if (
42+
newSelected &&
43+
newSelected !== selected &&
44+
JSON.parse(_tabs).some((tab) => tab.title === newSelected)
45+
) {
46+
setSelected(newSelected);
47+
}
48+
};
49+
50+
updateSelected();
51+
window.addEventListener("tabSelected", updateSelected);
52+
return () => {
53+
window.removeEventListener("tabSelected", updateSelected);
54+
};
55+
}
56+
}, [syncKey, selected, _tabs]);
57+
58+
const onTabSelect = (event) => {
59+
setSelected(event.target.value);
60+
sessionStorage.setItem("selectedTab-" + syncKey, event.target.value);
61+
window.dispatchEvent(new Event("tabSelected"));
62+
};
4263

4364
// refer to https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
4465
// warnings disabled for jsx-a11y as they make no sense: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-to-interactive-role.md

src/styles/_docs.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,22 @@ label.link-label {
6666
/* Scrollable contents if viewport is shorter than content. */
6767
}
6868

69+
/* headers have a position: relative style added by gatsby-remark-auto-headers
70+
- this is only needed when the icon is positioned absolutely, which we don't do */
71+
h1, h2, h3, h4, h5, h6 {
72+
position: static !important;
73+
}
74+
6975
.tabs {
7076

7177
.tabs__list {
7278
display: flex;
7379
flex-direction: row;
7480
margin: 0;
7581
padding: 0;
82+
position: sticky;
83+
top: 0;
84+
background-color: var(--bs-body-bg);
7685
}
7786

7887
>.tab-content>.tabs__content {

0 commit comments

Comments
 (0)