@charcuterie/ui 4.18.0 → 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Tabs/PanelTabs.d.ts.map +1 -1
- package/dist/Tabs/PanelTabs.js +6 -1
- package/dist/Tabs/PanelTabs.js.map +1 -1
- package/dist/Tabs/RoutedTabs.d.ts.map +1 -1
- package/dist/Tabs/RoutedTabs.js +3 -1
- package/dist/Tabs/RoutedTabs.js.map +1 -1
- package/dist/Tabs/useRevealCurrentTab.d.ts +4 -0
- package/dist/Tabs/useRevealCurrentTab.d.ts.map +1 -0
- package/dist/Tabs/useRevealCurrentTab.js +85 -0
- package/dist/Tabs/useRevealCurrentTab.js.map +1 -0
- package/package.json +1 -1
- package/src/Tabs/PanelTabs.tsx +9 -2
- package/src/Tabs/RoutedTabs.tsx +6 -0
- package/src/Tabs/Tabs.mdx +8 -0
- package/src/Tabs/Tabs.stories.tsx +73 -0
- package/src/Tabs/Tabs.test.tsx +85 -0
- package/src/Tabs/useRevealCurrentTab.ts +93 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanelTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"PanelTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAiBhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,eAAO,MAAM,SAAS,GAAI,2EAQvB,cAAc,KAAG,SAoLnB,CAAA"}
|
package/dist/Tabs/PanelTabs.js
CHANGED
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef } from "react";
|
|
|
4
4
|
import { toClassName } from "../toClassName.js";
|
|
5
5
|
import { TabTrigger } from "./TabTrigger.js";
|
|
6
6
|
import { toTabListClass } from "./tabStyles.js";
|
|
7
|
+
import { useRevealCurrentTab } from "./useRevealCurrentTab.js";
|
|
7
8
|
const ORIENTATION_KEYS = {
|
|
8
9
|
horizontal: {
|
|
9
10
|
next: "ArrowRight",
|
|
@@ -95,6 +96,7 @@ export const PanelTabs = ({ activation = "automatic", activeKey, className, labe
|
|
|
95
96
|
* `selectTabIndex` itself and got fixed there.
|
|
96
97
|
*/
|
|
97
98
|
const shownKey = selection.selectedValue ?? selection.pendingValue;
|
|
99
|
+
const revealRef = useRevealCurrentTab(shownKey, orientation);
|
|
98
100
|
const selectTab = useCallback((key) => {
|
|
99
101
|
// Both, because a pointer press moves focus *and* chooses.
|
|
100
102
|
// Only selecting would leave `automatic`'s effect below to
|
|
@@ -146,7 +148,10 @@ export const PanelTabs = ({ activation = "automatic", activeKey, className, labe
|
|
|
146
148
|
keyEvent.preventDefault();
|
|
147
149
|
select(activeValue);
|
|
148
150
|
}
|
|
149
|
-
}, ref:
|
|
151
|
+
}, ref: (element) => {
|
|
152
|
+
tablistRef.current = element;
|
|
153
|
+
revealRef.current = element;
|
|
154
|
+
}, role: "tablist", children: tabs.map((tab, index) => (_jsx(TabTrigger, { id: `${baseId}-tab-${index}`, isSelected: shownKey === tab.key, onSelect: selectTab, panelId: `${baseId}-panel-${index}`, registerFocus: focus.register, orientation: orientation, registerSelection: selection.register, tab: tab,
|
|
150
155
|
// The roving-tabindex rule read from the core rather
|
|
151
156
|
// than restated here: exactly one member is in the tab
|
|
152
157
|
// order, so Tab enters and leaves the bar once while
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanelTabs.js","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"PanelTabs.js","sourceRoot":"","sources":["../../src/Tabs/PanelTabs.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoutedTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"RoutedTabs.d.ts","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAI/C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,GAAI,sDAMxB,aAAa,KAAG,SA8BlB,CAAA"}
|
package/dist/Tabs/RoutedTabs.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getNavItemKey, resolveActiveKey, } from "../Nav/navItems.js";
|
|
|
3
3
|
import { toClassName } from "../toClassName.js";
|
|
4
4
|
import { TabLink } from "./TabLink.js";
|
|
5
5
|
import { toTabListClass } from "./tabStyles.js";
|
|
6
|
+
import { useRevealCurrentTab } from "./useRevealCurrentTab.js";
|
|
6
7
|
/**
|
|
7
8
|
* The routed bar: a named `<nav>` of real links wearing the tab
|
|
8
9
|
* paint.
|
|
@@ -20,7 +21,8 @@ import { toTabListClass } from "./tabStyles.js";
|
|
|
20
21
|
*/
|
|
21
22
|
export const RoutedTabs = ({ activeHref, className, label, orientation = "horizontal", tabs, }) => {
|
|
22
23
|
const activeKey = resolveActiveKey(tabs, activeHref);
|
|
23
|
-
|
|
24
|
+
const containerRef = useRevealCurrentTab(activeKey, orientation);
|
|
25
|
+
return (_jsx("nav", { ref: containerRef, "aria-label": label, className: toClassName(toTabListClass(orientation), className), children: tabs.map((tab) => {
|
|
24
26
|
const key = getNavItemKey(tab);
|
|
25
27
|
return (_jsx(TabLink, { isCurrent: key === activeKey, item: tab, orientation: orientation }, key));
|
|
26
28
|
}) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoutedTabs.js","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":";AAEA,OAAO,EACL,aAAa,EACb,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAe,CAAA;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"RoutedTabs.js","sourceRoot":"","sources":["../../src/Tabs/RoutedTabs.tsx"],"names":[],"mappings":";AAEA,OAAO,EACL,aAAa,EACb,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAe,CAAA;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,UAAU,EACV,SAAS,EACT,KAAK,EACL,WAAW,GAAG,YAAY,EAC1B,IAAI,GACU,EAAa,EAAE;IAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACpD,MAAM,YAAY,GAAG,mBAAmB,CACtC,SAAS,EACT,WAAW,CACZ,CAAA;IAED,OAAO,CACL,cACE,GAAG,EAAE,YAAY,gBACL,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,cAAc,CAAC,WAAW,CAAC,EAC3B,SAAS,CACV,YAEA,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;YAE9B,OAAO,CACL,KAAC,OAAO,IACN,SAAS,EAAE,GAAG,KAAK,SAAS,EAC5B,IAAI,EAAE,GAAG,EAET,WAAW,EAAE,WAAW,IADnB,GAAG,CAER,CACH,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TabsOrientation } from "./tabItems.ts";
|
|
2
|
+
/** Reveal within the tab bar only; never scroll an ancestor or move focus. */
|
|
3
|
+
export declare function useRevealCurrentTab(currentKey: string | null, orientation: TabsOrientation): import("react").RefObject<HTMLElement | null>;
|
|
4
|
+
//# sourceMappingURL=useRevealCurrentTab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRevealCurrentTab.d.ts","sourceRoot":"","sources":["../../src/Tabs/useRevealCurrentTab.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,8EAA8E;AAC9E,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,WAAW,EAAE,eAAe,iDAsF7B"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react";
|
|
2
|
+
/** Reveal within the tab bar only; never scroll an ancestor or move focus. */
|
|
3
|
+
export function useRevealCurrentTab(currentKey, orientation) {
|
|
4
|
+
const containerRef = useRef(null);
|
|
5
|
+
useLayoutEffect(() => {
|
|
6
|
+
const container = containerRef.current;
|
|
7
|
+
if (!container || currentKey === null)
|
|
8
|
+
return;
|
|
9
|
+
let isActive = true;
|
|
10
|
+
const reveal = () => {
|
|
11
|
+
if (!isActive)
|
|
12
|
+
return;
|
|
13
|
+
// In manual panel mode, the focused tab can differ from the selected tab.
|
|
14
|
+
const focused = container.ownerDocument.activeElement;
|
|
15
|
+
const current = focused instanceof HTMLElement &&
|
|
16
|
+
container.contains(focused) &&
|
|
17
|
+
focused.matches('a, [role="tab"]')
|
|
18
|
+
? focused
|
|
19
|
+
: container.querySelector('[aria-current="page"], [aria-selected="true"]');
|
|
20
|
+
if (!current)
|
|
21
|
+
return;
|
|
22
|
+
const viewport = container.getBoundingClientRect();
|
|
23
|
+
const target = current.getBoundingClientRect();
|
|
24
|
+
const start = orientation === "horizontal"
|
|
25
|
+
? target.left
|
|
26
|
+
: target.top;
|
|
27
|
+
const end = orientation === "horizontal"
|
|
28
|
+
? target.right
|
|
29
|
+
: target.bottom;
|
|
30
|
+
const viewStart = orientation === "horizontal"
|
|
31
|
+
? viewport.left
|
|
32
|
+
: viewport.top;
|
|
33
|
+
const viewEnd = orientation === "horizontal"
|
|
34
|
+
? viewport.right
|
|
35
|
+
: viewport.bottom;
|
|
36
|
+
const hasOversize = end - start > viewEnd - viewStart;
|
|
37
|
+
const isRtl = orientation === "horizontal" &&
|
|
38
|
+
getComputedStyle(container).direction === "rtl";
|
|
39
|
+
const delta = hasOversize
|
|
40
|
+
? isRtl
|
|
41
|
+
? end - viewEnd
|
|
42
|
+
: start - viewStart
|
|
43
|
+
: start < viewStart
|
|
44
|
+
? start - viewStart
|
|
45
|
+
: end > viewEnd
|
|
46
|
+
? end - viewEnd
|
|
47
|
+
: 0;
|
|
48
|
+
if (orientation === "horizontal")
|
|
49
|
+
container.scrollLeft += delta;
|
|
50
|
+
else
|
|
51
|
+
container.scrollTop += delta;
|
|
52
|
+
};
|
|
53
|
+
const frame = { id: 0 };
|
|
54
|
+
const scheduleReveal = () => {
|
|
55
|
+
cancelAnimationFrame(frame.id);
|
|
56
|
+
frame.id = requestAnimationFrame(reveal);
|
|
57
|
+
};
|
|
58
|
+
const sizes = new ResizeObserver(scheduleReveal);
|
|
59
|
+
const observeItems = () => {
|
|
60
|
+
sizes.disconnect();
|
|
61
|
+
sizes.observe(container);
|
|
62
|
+
for (const child of container.children)
|
|
63
|
+
sizes.observe(child);
|
|
64
|
+
reveal();
|
|
65
|
+
};
|
|
66
|
+
const contents = new MutationObserver(observeItems);
|
|
67
|
+
contents.observe(container, {
|
|
68
|
+
childList: true,
|
|
69
|
+
subtree: true,
|
|
70
|
+
characterData: true,
|
|
71
|
+
});
|
|
72
|
+
container.addEventListener("focusin", reveal);
|
|
73
|
+
observeItems();
|
|
74
|
+
void container.ownerDocument.fonts.ready.then(reveal);
|
|
75
|
+
return () => {
|
|
76
|
+
isActive = false;
|
|
77
|
+
cancelAnimationFrame(frame.id);
|
|
78
|
+
sizes.disconnect();
|
|
79
|
+
contents.disconnect();
|
|
80
|
+
container.removeEventListener("focusin", reveal);
|
|
81
|
+
};
|
|
82
|
+
}, [currentKey, orientation]);
|
|
83
|
+
return containerRef;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=useRevealCurrentTab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRevealCurrentTab.js","sourceRoot":"","sources":["../../src/Tabs/useRevealCurrentTab.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Tabs/PanelTabs.tsx
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
} from "@charcuterie/logic"
|
|
7
7
|
import type { ReactNode } from "react"
|
|
8
8
|
import { useCallback, useEffect, useRef } from "react"
|
|
9
|
-
|
|
10
9
|
import { toClassName } from "../toClassName.ts"
|
|
11
10
|
import type { TabsPanelProps } from "./Tabs.tsx"
|
|
12
11
|
import { TabTrigger } from "./TabTrigger.tsx"
|
|
13
12
|
import type { TabsOrientation } from "./tabItems.ts"
|
|
14
13
|
import { toTabListClass } from "./tabStyles.ts"
|
|
14
|
+
import { useRevealCurrentTab } from "./useRevealCurrentTab.ts"
|
|
15
15
|
|
|
16
16
|
const ORIENTATION_KEYS: Record<
|
|
17
17
|
TabsOrientation,
|
|
@@ -128,6 +128,10 @@ export const PanelTabs = ({
|
|
|
128
128
|
*/
|
|
129
129
|
const shownKey =
|
|
130
130
|
selection.selectedValue ?? selection.pendingValue
|
|
131
|
+
const revealRef = useRevealCurrentTab(
|
|
132
|
+
shownKey,
|
|
133
|
+
orientation,
|
|
134
|
+
)
|
|
131
135
|
|
|
132
136
|
const selectTab = useCallback(
|
|
133
137
|
(key: string) => {
|
|
@@ -214,7 +218,10 @@ export const PanelTabs = ({
|
|
|
214
218
|
select(activeValue)
|
|
215
219
|
}
|
|
216
220
|
}}
|
|
217
|
-
ref={
|
|
221
|
+
ref={(element) => {
|
|
222
|
+
tablistRef.current = element
|
|
223
|
+
revealRef.current = element
|
|
224
|
+
}}
|
|
218
225
|
role="tablist"
|
|
219
226
|
>
|
|
220
227
|
{tabs.map((tab, index) => (
|
package/src/Tabs/RoutedTabs.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { toClassName } from "../toClassName.ts"
|
|
|
8
8
|
import { TabLink } from "./TabLink.tsx"
|
|
9
9
|
import type { TabsLinkProps } from "./Tabs.tsx"
|
|
10
10
|
import { toTabListClass } from "./tabStyles.ts"
|
|
11
|
+
import { useRevealCurrentTab } from "./useRevealCurrentTab.ts"
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* The routed bar: a named `<nav>` of real links wearing the tab
|
|
@@ -32,9 +33,14 @@ export const RoutedTabs = ({
|
|
|
32
33
|
tabs,
|
|
33
34
|
}: TabsLinkProps): ReactNode => {
|
|
34
35
|
const activeKey = resolveActiveKey(tabs, activeHref)
|
|
36
|
+
const containerRef = useRevealCurrentTab(
|
|
37
|
+
activeKey,
|
|
38
|
+
orientation,
|
|
39
|
+
)
|
|
35
40
|
|
|
36
41
|
return (
|
|
37
42
|
<nav
|
|
43
|
+
ref={containerRef}
|
|
38
44
|
aria-label={label}
|
|
39
45
|
className={toClassName(
|
|
40
46
|
toTabListClass(orientation),
|
package/src/Tabs/Tabs.mdx
CHANGED
|
@@ -194,3 +194,11 @@ bar below and the underline stays put until Enter: focus has moved to a tab that
|
|
|
194
194
|
selected, which is a state a three-kind model cannot represent at all.
|
|
195
195
|
|
|
196
196
|
<Canvas of={TabsStories.Manual} />
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
## Current tabs in a narrow bar
|
|
200
|
+
|
|
201
|
+
An overflowing bar keeps its current tab in view on first render, route changes,
|
|
202
|
+
and reflow. It scrolls only itself, without moving focus or the surrounding page.
|
|
203
|
+
Manual panel activation keeps the focused tab visible even while another panel
|
|
204
|
+
remains selected. A reader can still scroll to other tabs.
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { playgroundParameters } from "@charcuterie/storybook-config/story-parameters"
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
3
|
|
|
4
|
+
import { useState } from "react"
|
|
4
5
|
import { toStoryChoice } from "../argTypes.storyHelpers.ts"
|
|
5
6
|
import { Badge } from "../Badge/Badge.tsx"
|
|
7
|
+
import { Button } from "../Button/Button.tsx"
|
|
6
8
|
import {
|
|
7
9
|
ContainerBoard,
|
|
8
10
|
StoryCell,
|
|
9
11
|
StoryGrid,
|
|
10
12
|
} from "../board.storyHelpers.tsx"
|
|
13
|
+
import { Field } from "../Field/Field.tsx"
|
|
11
14
|
import { ProgressBar } from "../ProgressBar/ProgressBar.tsx"
|
|
12
15
|
import { Tabs } from "./Tabs.tsx"
|
|
13
16
|
import type { TabItem, TabLinkItem } from "./tabItems.ts"
|
|
@@ -319,3 +322,73 @@ export const RoutedAllStates: Story = {
|
|
|
319
322
|
/>
|
|
320
323
|
),
|
|
321
324
|
}
|
|
325
|
+
|
|
326
|
+
const OVERFLOW_ROUTES: TabLinkItem[] = [
|
|
327
|
+
{ href: "/sections/activity", label: "Recent activity" },
|
|
328
|
+
{ href: "/sections/files", label: "Saved files" },
|
|
329
|
+
{ href: "/sections/builds", label: "Prepared builds" },
|
|
330
|
+
{ href: "/sections/reports", label: "Reports" },
|
|
331
|
+
]
|
|
332
|
+
export const CurrentTabVisible: Story = {
|
|
333
|
+
args: { label: "Sections", tabs: BAY_TABS },
|
|
334
|
+
render: () => {
|
|
335
|
+
const [href, setHref] = useState("/sections/reports")
|
|
336
|
+
const [inlineSize, setInlineSize] = useState(240)
|
|
337
|
+
return (
|
|
338
|
+
<div className="flex flex-col gap-4">
|
|
339
|
+
<Field label="Available width">
|
|
340
|
+
<input
|
|
341
|
+
type="number"
|
|
342
|
+
min={100}
|
|
343
|
+
max={500}
|
|
344
|
+
value={inlineSize}
|
|
345
|
+
onChange={(event) =>
|
|
346
|
+
setInlineSize(
|
|
347
|
+
Number(event.currentTarget.value),
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
/>
|
|
351
|
+
</Field>
|
|
352
|
+
<div className="flex flex-wrap gap-2">
|
|
353
|
+
<Button
|
|
354
|
+
onClick={() => setHref("/sections/activity")}
|
|
355
|
+
>
|
|
356
|
+
Show activity
|
|
357
|
+
</Button>
|
|
358
|
+
<Button
|
|
359
|
+
onClick={() => setHref("/sections/reports")}
|
|
360
|
+
>
|
|
361
|
+
Show reports
|
|
362
|
+
</Button>
|
|
363
|
+
</div>
|
|
364
|
+
<div style={{ inlineSize, maxInlineSize: "100%" }}>
|
|
365
|
+
<Tabs
|
|
366
|
+
label="Current routed section"
|
|
367
|
+
activeHref={href}
|
|
368
|
+
tabs={OVERFLOW_ROUTES}
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
371
|
+
<div
|
|
372
|
+
dir="rtl"
|
|
373
|
+
style={{ inlineSize: 240, maxInlineSize: "100%" }}
|
|
374
|
+
>
|
|
375
|
+
<Tabs
|
|
376
|
+
label="Current RTL section"
|
|
377
|
+
activeHref="/sections/reports"
|
|
378
|
+
tabs={OVERFLOW_ROUTES}
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
<div
|
|
382
|
+
style={{ inlineSize: 150, maxInlineSize: "100%" }}
|
|
383
|
+
>
|
|
384
|
+
<Tabs
|
|
385
|
+
label="Current manual panel"
|
|
386
|
+
activation="manual"
|
|
387
|
+
activeKey="disc"
|
|
388
|
+
tabs={BAY_TABS}
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
},
|
|
394
|
+
}
|
package/src/Tabs/Tabs.test.tsx
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
Routed,
|
|
17
17
|
RoutedAllStates,
|
|
18
18
|
RoutedAllVariants,
|
|
19
|
+
CurrentTabVisible,
|
|
19
20
|
} = composeStories(stories)
|
|
20
21
|
|
|
21
22
|
test("selecting a tab wires the panel back to it", async () => {
|
|
@@ -450,3 +451,87 @@ test("a routed tab and a panel tab are painted identically", async () => {
|
|
|
450
451
|
)
|
|
451
452
|
}
|
|
452
453
|
})
|
|
454
|
+
|
|
455
|
+
test("current routed and panel tabs remain visible without moving focus", async () => {
|
|
456
|
+
const { canvas } = await mountStory(CurrentTabVisible)
|
|
457
|
+
const assertVisible = async (
|
|
458
|
+
bar: HTMLElement,
|
|
459
|
+
selector: string,
|
|
460
|
+
) => {
|
|
461
|
+
await waitFor(() => {
|
|
462
|
+
const selected =
|
|
463
|
+
bar.querySelector<HTMLElement>(selector)
|
|
464
|
+
expect(selected).not.toBeNull()
|
|
465
|
+
const item = selected?.getBoundingClientRect()
|
|
466
|
+
const bounds = bar.getBoundingClientRect()
|
|
467
|
+
expect(
|
|
468
|
+
item?.left ?? -Infinity,
|
|
469
|
+
).toBeGreaterThanOrEqual(bounds.left - 1)
|
|
470
|
+
expect(item?.right ?? Infinity).toBeLessThanOrEqual(
|
|
471
|
+
bounds.right + 1,
|
|
472
|
+
)
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
const routed = expectAgentDrivable(canvas, {
|
|
476
|
+
role: "navigation",
|
|
477
|
+
name: "Current routed section",
|
|
478
|
+
})
|
|
479
|
+
const rtl = expectAgentDrivable(canvas, {
|
|
480
|
+
role: "navigation",
|
|
481
|
+
name: "Current RTL section",
|
|
482
|
+
})
|
|
483
|
+
const panel = expectAgentDrivable(canvas, {
|
|
484
|
+
role: "tablist",
|
|
485
|
+
name: "Current manual panel",
|
|
486
|
+
})
|
|
487
|
+
await assertVisible(routed, '[aria-current="page"]')
|
|
488
|
+
await assertVisible(rtl, '[aria-current="page"]')
|
|
489
|
+
await assertVisible(panel, '[aria-selected="true"]')
|
|
490
|
+
const width = expectAgentDrivable(canvas, {
|
|
491
|
+
role: "spinbutton",
|
|
492
|
+
name: "Available width",
|
|
493
|
+
})
|
|
494
|
+
await userEvent.clear(width)
|
|
495
|
+
await userEvent.type(width, "160")
|
|
496
|
+
await assertVisible(routed, '[aria-current="page"]')
|
|
497
|
+
await expect(width).toHaveFocus()
|
|
498
|
+
const activity = expectAgentDrivable(canvas, {
|
|
499
|
+
role: "button",
|
|
500
|
+
name: "Show activity",
|
|
501
|
+
})
|
|
502
|
+
await userEvent.click(activity)
|
|
503
|
+
await assertVisible(routed, '[aria-current="page"]')
|
|
504
|
+
await expect(activity).toHaveFocus()
|
|
505
|
+
const reports = expectAgentDrivable(canvas, {
|
|
506
|
+
role: "button",
|
|
507
|
+
name: "Show reports",
|
|
508
|
+
})
|
|
509
|
+
await userEvent.click(reports)
|
|
510
|
+
await assertVisible(routed, '[aria-current="page"]')
|
|
511
|
+
await expect(reports).toHaveFocus()
|
|
512
|
+
await userEvent.clear(width)
|
|
513
|
+
await userEvent.type(width, "80")
|
|
514
|
+
await userEvent.click(activity)
|
|
515
|
+
await waitFor(() => {
|
|
516
|
+
const bounds = routed.getBoundingClientRect()
|
|
517
|
+
const selected = routed
|
|
518
|
+
.querySelector<HTMLElement>('[aria-current="page"]')
|
|
519
|
+
?.getBoundingClientRect()
|
|
520
|
+
expect(
|
|
521
|
+
selected?.left ?? -Infinity,
|
|
522
|
+
).toBeGreaterThanOrEqual(bounds.left - 1)
|
|
523
|
+
expect(Math.abs(routed.scrollLeft)).toBeLessThanOrEqual(
|
|
524
|
+
1,
|
|
525
|
+
)
|
|
526
|
+
})
|
|
527
|
+
const disc = panel.querySelector<HTMLElement>(
|
|
528
|
+
'[aria-selected="true"]',
|
|
529
|
+
)
|
|
530
|
+
disc?.focus()
|
|
531
|
+
await userEvent.keyboard("{ArrowLeft}")
|
|
532
|
+
await assertVisible(panel, ":focus")
|
|
533
|
+
await expect(disc).toHaveAttribute(
|
|
534
|
+
"aria-selected",
|
|
535
|
+
"true",
|
|
536
|
+
)
|
|
537
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react"
|
|
2
|
+
import type { TabsOrientation } from "./tabItems.ts"
|
|
3
|
+
|
|
4
|
+
/** Reveal within the tab bar only; never scroll an ancestor or move focus. */
|
|
5
|
+
export function useRevealCurrentTab(
|
|
6
|
+
currentKey: string | null,
|
|
7
|
+
orientation: TabsOrientation,
|
|
8
|
+
) {
|
|
9
|
+
const containerRef = useRef<HTMLElement>(null)
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
const container = containerRef.current
|
|
12
|
+
if (!container || currentKey === null) return
|
|
13
|
+
let isActive = true
|
|
14
|
+
const reveal = () => {
|
|
15
|
+
if (!isActive) return
|
|
16
|
+
// In manual panel mode, the focused tab can differ from the selected tab.
|
|
17
|
+
const focused = container.ownerDocument.activeElement
|
|
18
|
+
const current =
|
|
19
|
+
focused instanceof HTMLElement &&
|
|
20
|
+
container.contains(focused) &&
|
|
21
|
+
focused.matches('a, [role="tab"]')
|
|
22
|
+
? focused
|
|
23
|
+
: container.querySelector<HTMLElement>(
|
|
24
|
+
'[aria-current="page"], [aria-selected="true"]',
|
|
25
|
+
)
|
|
26
|
+
if (!current) return
|
|
27
|
+
const viewport = container.getBoundingClientRect()
|
|
28
|
+
const target = current.getBoundingClientRect()
|
|
29
|
+
const start =
|
|
30
|
+
orientation === "horizontal"
|
|
31
|
+
? target.left
|
|
32
|
+
: target.top
|
|
33
|
+
const end =
|
|
34
|
+
orientation === "horizontal"
|
|
35
|
+
? target.right
|
|
36
|
+
: target.bottom
|
|
37
|
+
const viewStart =
|
|
38
|
+
orientation === "horizontal"
|
|
39
|
+
? viewport.left
|
|
40
|
+
: viewport.top
|
|
41
|
+
const viewEnd =
|
|
42
|
+
orientation === "horizontal"
|
|
43
|
+
? viewport.right
|
|
44
|
+
: viewport.bottom
|
|
45
|
+
const hasOversize = end - start > viewEnd - viewStart
|
|
46
|
+
const isRtl =
|
|
47
|
+
orientation === "horizontal" &&
|
|
48
|
+
getComputedStyle(container).direction === "rtl"
|
|
49
|
+
const delta = hasOversize
|
|
50
|
+
? isRtl
|
|
51
|
+
? end - viewEnd
|
|
52
|
+
: start - viewStart
|
|
53
|
+
: start < viewStart
|
|
54
|
+
? start - viewStart
|
|
55
|
+
: end > viewEnd
|
|
56
|
+
? end - viewEnd
|
|
57
|
+
: 0
|
|
58
|
+
if (orientation === "horizontal")
|
|
59
|
+
container.scrollLeft += delta
|
|
60
|
+
else container.scrollTop += delta
|
|
61
|
+
}
|
|
62
|
+
const frame = { id: 0 }
|
|
63
|
+
const scheduleReveal = () => {
|
|
64
|
+
cancelAnimationFrame(frame.id)
|
|
65
|
+
frame.id = requestAnimationFrame(reveal)
|
|
66
|
+
}
|
|
67
|
+
const sizes = new ResizeObserver(scheduleReveal)
|
|
68
|
+
const observeItems = () => {
|
|
69
|
+
sizes.disconnect()
|
|
70
|
+
sizes.observe(container)
|
|
71
|
+
for (const child of container.children)
|
|
72
|
+
sizes.observe(child)
|
|
73
|
+
reveal()
|
|
74
|
+
}
|
|
75
|
+
const contents = new MutationObserver(observeItems)
|
|
76
|
+
contents.observe(container, {
|
|
77
|
+
childList: true,
|
|
78
|
+
subtree: true,
|
|
79
|
+
characterData: true,
|
|
80
|
+
})
|
|
81
|
+
container.addEventListener("focusin", reveal)
|
|
82
|
+
observeItems()
|
|
83
|
+
void container.ownerDocument.fonts.ready.then(reveal)
|
|
84
|
+
return () => {
|
|
85
|
+
isActive = false
|
|
86
|
+
cancelAnimationFrame(frame.id)
|
|
87
|
+
sizes.disconnect()
|
|
88
|
+
contents.disconnect()
|
|
89
|
+
container.removeEventListener("focusin", reveal)
|
|
90
|
+
}
|
|
91
|
+
}, [currentKey, orientation])
|
|
92
|
+
return containerRef
|
|
93
|
+
}
|