@dxos/react-ui-tabs 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112
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/lib/browser/index.mjs +42 -16
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +42 -16
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/Tabs.d.ts +19 -15
- package/dist/types/src/Tabs.d.ts.map +1 -1
- package/dist/types/src/Tabs.stories.d.ts +7 -4
- package/dist/types/src/Tabs.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Tabs.stories.tsx +1 -1
- package/src/Tabs.tsx +56 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-tabs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1-main.c7dcc2e112",
|
|
4
4
|
"description": "Components for facilitating a Tabs pattern.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"@radix-ui/react-slot": "1.1.2",
|
|
34
34
|
"@radix-ui/react-tabs": "1.1.3",
|
|
35
35
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
36
|
-
"@dxos/react-ui-attention": "0.
|
|
37
|
-
"@dxos/util": "0.
|
|
36
|
+
"@dxos/react-ui-attention": "0.9.1-main.c7dcc2e112",
|
|
37
|
+
"@dxos/util": "0.9.1-main.c7dcc2e112"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/react": "~19.2.7",
|
|
@@ -42,16 +42,16 @@
|
|
|
42
42
|
"react": "~19.2.3",
|
|
43
43
|
"react-dom": "~19.2.3",
|
|
44
44
|
"vite": "^8.0.16",
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/storybook-utils": "0.
|
|
47
|
-
"@dxos/ui
|
|
48
|
-
"@dxos/
|
|
45
|
+
"@dxos/random": "0.9.1-main.c7dcc2e112",
|
|
46
|
+
"@dxos/storybook-utils": "0.9.1-main.c7dcc2e112",
|
|
47
|
+
"@dxos/react-ui": "0.9.1-main.c7dcc2e112",
|
|
48
|
+
"@dxos/ui-theme": "0.9.1-main.c7dcc2e112"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "~19.2.3",
|
|
52
52
|
"react-dom": "~19.2.3",
|
|
53
|
-
"@dxos/react-ui": "0.
|
|
54
|
-
"@dxos/ui-theme": "0.
|
|
53
|
+
"@dxos/react-ui": "0.9.1-main.c7dcc2e112",
|
|
54
|
+
"@dxos/ui-theme": "0.9.1-main.c7dcc2e112"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|
package/src/Tabs.stories.tsx
CHANGED
|
@@ -15,7 +15,7 @@ random.seed(1234);
|
|
|
15
15
|
|
|
16
16
|
const DefaultStory = ({ orientation }: TabsRootProps) => {
|
|
17
17
|
return (
|
|
18
|
-
<Tabs.Root orientation={orientation} defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
|
|
18
|
+
<Tabs.Root asChild orientation={orientation} defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
|
|
19
19
|
<Tabs.Viewport
|
|
20
20
|
classNames={mx(
|
|
21
21
|
'w-full overflow-hidden grid',
|
package/src/Tabs.tsx
CHANGED
|
@@ -4,22 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
import { useArrowNavigationGroup, useFocusFinders, useFocusableGroup } from '@fluentui/react-tabster';
|
|
6
6
|
import { createContext } from '@radix-ui/react-context';
|
|
7
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
7
8
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
8
9
|
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
9
|
-
import React, { type ComponentPropsWithoutRef, type MouseEvent,
|
|
10
|
+
import React, { type ComponentPropsWithoutRef, type MouseEvent, useCallback, useLayoutEffect } from 'react';
|
|
10
11
|
|
|
11
12
|
import {
|
|
12
13
|
Button,
|
|
13
14
|
type ButtonProps,
|
|
14
15
|
IconButton,
|
|
15
16
|
type IconButtonProps,
|
|
17
|
+
type SlottableProps,
|
|
16
18
|
type ThemedClassName,
|
|
19
|
+
composableProps,
|
|
20
|
+
slottable,
|
|
17
21
|
useForwardedRef,
|
|
18
22
|
} from '@dxos/react-ui';
|
|
19
23
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
20
24
|
import { mx } from '@dxos/ui-theme';
|
|
21
25
|
|
|
22
|
-
// TODO(burdon):
|
|
26
|
+
// TODO(burdon): Rewrite this; there are too many hacks/quirks.
|
|
23
27
|
|
|
24
28
|
type TabsActivePart = 'list' | 'panel';
|
|
25
29
|
|
|
@@ -45,19 +49,22 @@ const [TabsContextProvider, useTabsContext] = createContext<TabsContextValue>(TA
|
|
|
45
49
|
// Root
|
|
46
50
|
//
|
|
47
51
|
|
|
48
|
-
type
|
|
52
|
+
type TabsRootCustomProps = TabsPrimitive.TabsProps &
|
|
49
53
|
Partial<
|
|
50
54
|
Pick<TabsContextValue, 'activePart' | 'attendableId'> & {
|
|
51
55
|
onActivePartChange: (nextActivePart: TabsActivePart) => void;
|
|
52
56
|
defaultActivePart: TabsActivePart;
|
|
57
|
+
/** Skip master-detail focus moves (e.g. when a child form owns initial focus). */
|
|
58
|
+
suppressRegionFocus?: boolean;
|
|
53
59
|
}
|
|
54
60
|
>;
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
type TabsRootProps = SlottableProps<TabsRootCustomProps>;
|
|
63
|
+
|
|
64
|
+
const TabsRoot = slottable<HTMLDivElement, TabsRootCustomProps>(
|
|
57
65
|
(
|
|
58
66
|
{
|
|
59
67
|
children,
|
|
60
|
-
classNames,
|
|
61
68
|
activePart: propsActivePart,
|
|
62
69
|
onActivePartChange,
|
|
63
70
|
defaultActivePart,
|
|
@@ -67,6 +74,8 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
|
|
|
67
74
|
orientation = 'vertical',
|
|
68
75
|
activationMode = 'manual',
|
|
69
76
|
attendableId,
|
|
77
|
+
suppressRegionFocus = false,
|
|
78
|
+
asChild,
|
|
70
79
|
...props
|
|
71
80
|
},
|
|
72
81
|
forwardedRef,
|
|
@@ -74,8 +83,8 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
|
|
|
74
83
|
const tabsRoot = useForwardedRef(forwardedRef);
|
|
75
84
|
|
|
76
85
|
// TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
useArrowNavigationGroup();
|
|
87
|
+
useFocusableGroup();
|
|
79
88
|
const [activePart = 'list', setActivePart] = useControllableState({
|
|
80
89
|
prop: propsActivePart,
|
|
81
90
|
onChange: onActivePartChange,
|
|
@@ -96,13 +105,36 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
|
|
|
96
105
|
[value],
|
|
97
106
|
);
|
|
98
107
|
|
|
99
|
-
const { findFirstFocusable } = useFocusFinders();
|
|
108
|
+
const { findFirstFocusable, findNextFocusable } = useFocusFinders();
|
|
100
109
|
|
|
101
110
|
useLayoutEffect(() => {
|
|
102
|
-
if (
|
|
103
|
-
|
|
111
|
+
if (suppressRegionFocus) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const root = tabsRoot.current;
|
|
116
|
+
if (!root) {
|
|
117
|
+
return;
|
|
104
118
|
}
|
|
105
|
-
|
|
119
|
+
|
|
120
|
+
if (activePart === 'list') {
|
|
121
|
+
const tablist = root.querySelector<HTMLElement>('[role="tablist"]');
|
|
122
|
+
findFirstFocusable(tablist)?.focus();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const panel = root.querySelector<HTMLElement>('[role="tabpanel"][data-state="active"]');
|
|
127
|
+
if (!panel) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Radix marks the active panel focusable for roving tabindex; skip it so content receives focus.
|
|
132
|
+
let target = findFirstFocusable(panel);
|
|
133
|
+
if (target === panel) {
|
|
134
|
+
target = findNextFocusable(panel, { container: panel }) ?? undefined;
|
|
135
|
+
}
|
|
136
|
+
target?.focus();
|
|
137
|
+
}, [activePart, value, findFirstFocusable, findNextFocusable, suppressRegionFocus]);
|
|
106
138
|
|
|
107
139
|
return (
|
|
108
140
|
<TabsContextProvider
|
|
@@ -113,8 +145,8 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
|
|
|
113
145
|
attendableId={attendableId}
|
|
114
146
|
>
|
|
115
147
|
<TabsPrimitive.Root
|
|
116
|
-
{...props}
|
|
117
|
-
|
|
148
|
+
{...composableProps<HTMLDivElement>(props)}
|
|
149
|
+
asChild={asChild}
|
|
118
150
|
orientation={orientation}
|
|
119
151
|
activationMode={activationMode}
|
|
120
152
|
data-active={activePart}
|
|
@@ -135,16 +167,22 @@ TabsRoot.displayName = 'Tabs.Root';
|
|
|
135
167
|
// Viewport
|
|
136
168
|
//
|
|
137
169
|
|
|
138
|
-
type TabsViewportProps =
|
|
170
|
+
type TabsViewportProps = SlottableProps<
|
|
171
|
+
Omit<ComponentPropsWithoutRef<'div'>, 'className' | 'style' | 'children' | 'role'>
|
|
172
|
+
>;
|
|
139
173
|
|
|
140
|
-
const TabsViewport =
|
|
174
|
+
const TabsViewport = slottable<
|
|
175
|
+
HTMLDivElement,
|
|
176
|
+
Omit<ComponentPropsWithoutRef<'div'>, 'className' | 'style' | 'children' | 'role'>
|
|
177
|
+
>(({ children, asChild, ...props }, forwardedRef) => {
|
|
141
178
|
const { activePart } = useTabsContext('TabsViewport');
|
|
179
|
+
const Comp = asChild ? Slot : 'div';
|
|
142
180
|
return (
|
|
143
|
-
<
|
|
181
|
+
<Comp {...composableProps<HTMLDivElement>(props)} data-active={activePart} ref={forwardedRef}>
|
|
144
182
|
{children}
|
|
145
|
-
</
|
|
183
|
+
</Comp>
|
|
146
184
|
);
|
|
147
|
-
};
|
|
185
|
+
});
|
|
148
186
|
|
|
149
187
|
TabsViewport.displayName = 'Tabs.Viewport';
|
|
150
188
|
|