@dxos/react-ui-tabs 0.8.4-main.fffef41 → 0.9.0
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/LICENSE +102 -5
- package/dist/lib/browser/index.mjs +147 -195
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +147 -195
- 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 +65 -12
- package/dist/types/src/Tabs.d.ts.map +1 -1
- package/dist/types/src/Tabs.stories.d.ts +17 -4
- package/dist/types/src/Tabs.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -23
- package/src/Tabs.stories.tsx +48 -41
- package/src/Tabs.tsx +184 -144
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-tabs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Components for facilitating a Tabs pattern.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
-
"
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
|
-
"sideEffects":
|
|
13
|
+
"sideEffects": false,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
@@ -17,41 +21,37 @@
|
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"types": "dist/types/src/index.d.ts",
|
|
20
|
-
"typesVersions": {
|
|
21
|
-
"*": {}
|
|
22
|
-
},
|
|
23
24
|
"files": [
|
|
24
25
|
"dist",
|
|
25
26
|
"src"
|
|
26
27
|
],
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@fluentui/react-tabster": "
|
|
29
|
-
"@preact-signals/safe-react": "^0.9.0",
|
|
29
|
+
"@fluentui/react-tabster": "9.26.11",
|
|
30
30
|
"@radix-ui/primitive": "1.1.1",
|
|
31
31
|
"@radix-ui/react-context": "1.1.1",
|
|
32
32
|
"@radix-ui/react-primitive": "2.0.2",
|
|
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.0",
|
|
37
|
+
"@dxos/util": "0.9.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/react": "~19.2.
|
|
41
|
-
"@types/react-dom": "~19.2.
|
|
42
|
-
"react": "~19.2.
|
|
43
|
-
"react-dom": "~19.2.
|
|
44
|
-
"vite": "
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/storybook-utils": "0.
|
|
40
|
+
"@types/react": "~19.2.7",
|
|
41
|
+
"@types/react-dom": "~19.2.3",
|
|
42
|
+
"react": "~19.2.3",
|
|
43
|
+
"react-dom": "~19.2.3",
|
|
44
|
+
"vite": "^8.0.16",
|
|
45
|
+
"@dxos/react-ui": "0.9.0",
|
|
46
|
+
"@dxos/random": "0.9.0",
|
|
47
|
+
"@dxos/ui-theme": "0.9.0",
|
|
48
|
+
"@dxos/storybook-utils": "0.9.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"react": "
|
|
52
|
-
"react-dom": "
|
|
53
|
-
"@dxos/react-ui": "0.
|
|
54
|
-
"@dxos/
|
|
51
|
+
"react": "~19.2.3",
|
|
52
|
+
"react-dom": "~19.2.3",
|
|
53
|
+
"@dxos/react-ui": "0.9.0",
|
|
54
|
+
"@dxos/ui-theme": "0.9.0"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|
package/src/Tabs.stories.tsx
CHANGED
|
@@ -5,66 +5,73 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { random } from '@dxos/random';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
|
-
import { Tabs
|
|
12
|
+
import { Tabs, TabsRootProps } from './Tabs';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
random.seed(1234);
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const DefaultStory = ({ orientation }: TabsRootProps) => {
|
|
17
17
|
return (
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
})}
|
|
43
|
-
</NaturalTabs.Viewport>
|
|
44
|
-
</NaturalTabs.Root>
|
|
45
|
-
</Dialog.Content>
|
|
46
|
-
</Dialog.Overlay>
|
|
47
|
-
</Dialog.Root>
|
|
18
|
+
<Tabs.Root orientation={orientation} defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
|
|
19
|
+
<Tabs.Viewport
|
|
20
|
+
classNames={mx(
|
|
21
|
+
'w-full overflow-hidden grid',
|
|
22
|
+
orientation === 'vertical' && 'grid-cols-[minmax(min-content,1fr)_3fr]',
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<Tabs.Tablist>
|
|
26
|
+
{Object.entries(content).map(([id, { title }]) => (
|
|
27
|
+
<Tabs.Tab key={id} value={id}>
|
|
28
|
+
{title}
|
|
29
|
+
</Tabs.Tab>
|
|
30
|
+
))}
|
|
31
|
+
</Tabs.Tablist>
|
|
32
|
+
<div className='dx-container'>
|
|
33
|
+
{Object.entries(content).map(([id, { panel }]) => (
|
|
34
|
+
<Tabs.Panel key={id} value={id}>
|
|
35
|
+
<p className='px-1'>{panel}</p>
|
|
36
|
+
</Tabs.Panel>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
</Tabs.Viewport>
|
|
40
|
+
</Tabs.Root>
|
|
48
41
|
);
|
|
49
42
|
};
|
|
50
43
|
|
|
51
44
|
const content = [...Array(24)].reduce((acc: { [key: string]: { title: string; panel: string } }, _, index) => {
|
|
52
45
|
acc[`t${index}`] = {
|
|
53
|
-
title:
|
|
54
|
-
panel:
|
|
46
|
+
title: random.commerce.productName(),
|
|
47
|
+
panel: random.lorem.paragraphs(5),
|
|
55
48
|
};
|
|
56
49
|
return acc;
|
|
57
50
|
}, {});
|
|
58
51
|
|
|
59
52
|
const meta = {
|
|
60
53
|
title: 'ui/react-ui-tabs/Tabs',
|
|
61
|
-
component:
|
|
54
|
+
component: Tabs.Root,
|
|
62
55
|
render: DefaultStory,
|
|
63
|
-
decorators: [withTheme],
|
|
56
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
57
|
+
parameters: {
|
|
58
|
+
layout: 'fullscreen',
|
|
59
|
+
},
|
|
64
60
|
} satisfies Meta<typeof DefaultStory>;
|
|
65
61
|
|
|
66
62
|
export default meta;
|
|
67
63
|
|
|
68
64
|
type Story = StoryObj<typeof meta>;
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
// TODO(burdon): Scrolling.
|
|
67
|
+
export const Horizontal: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
orientation: 'horizontal',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const Vertical: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
orientation: 'vertical',
|
|
76
|
+
},
|
|
77
|
+
};
|
package/src/Tabs.tsx
CHANGED
|
@@ -6,157 +6,164 @@ import { useArrowNavigationGroup, useFocusFinders, useFocusableGroup } from '@fl
|
|
|
6
6
|
import { createContext } from '@radix-ui/react-context';
|
|
7
7
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
8
8
|
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
9
|
-
import React, {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
import React, { type ComponentPropsWithoutRef, type MouseEvent, forwardRef, useCallback, useLayoutEffect } from 'react';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
Button,
|
|
13
|
+
type ButtonProps,
|
|
14
|
+
IconButton,
|
|
15
|
+
type IconButtonProps,
|
|
16
|
+
type ThemedClassName,
|
|
17
|
+
useForwardedRef,
|
|
18
|
+
} from '@dxos/react-ui';
|
|
19
19
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
20
|
-
import {
|
|
20
|
+
import { mx } from '@dxos/ui-theme';
|
|
21
|
+
|
|
22
|
+
// TODO(burdon): Move to @dxos/react-ui.
|
|
21
23
|
|
|
22
24
|
type TabsActivePart = 'list' | 'panel';
|
|
23
25
|
|
|
24
26
|
const TABS_NAME = 'Tabs';
|
|
25
27
|
|
|
28
|
+
//
|
|
29
|
+
// Context
|
|
30
|
+
//
|
|
31
|
+
|
|
26
32
|
type TabsContextValue = {
|
|
27
33
|
activePart: TabsActivePart;
|
|
28
34
|
setActivePart: (nextActivePart: TabsActivePart) => void;
|
|
29
35
|
attendableId?: string;
|
|
30
|
-
verticalVariant?: 'stateful' | 'stateless';
|
|
31
36
|
} & Pick<TabsPrimitive.TabsProps, 'orientation' | 'value'>;
|
|
32
37
|
|
|
33
38
|
const [TabsContextProvider, useTabsContext] = createContext<TabsContextValue>(TABS_NAME, {
|
|
39
|
+
orientation: 'vertical',
|
|
34
40
|
activePart: 'list',
|
|
35
41
|
setActivePart: () => {},
|
|
36
|
-
orientation: 'vertical',
|
|
37
42
|
});
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
onActivePartChange: (nextActivePart: TabsActivePart) => void;
|
|
43
|
-
defaultActivePart: TabsActivePart;
|
|
44
|
-
}>;
|
|
45
|
-
|
|
46
|
-
const TabsRoot = ({
|
|
47
|
-
children,
|
|
48
|
-
classNames,
|
|
49
|
-
activePart: propsActivePart,
|
|
50
|
-
onActivePartChange,
|
|
51
|
-
defaultActivePart,
|
|
52
|
-
value: propsValue,
|
|
53
|
-
onValueChange,
|
|
54
|
-
defaultValue,
|
|
55
|
-
orientation = 'vertical',
|
|
56
|
-
activationMode = 'manual',
|
|
57
|
-
verticalVariant = 'stateful',
|
|
58
|
-
attendableId,
|
|
59
|
-
...props
|
|
60
|
-
}: TabsRootProps) => {
|
|
61
|
-
// TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
|
|
62
|
-
const _1 = useArrowNavigationGroup();
|
|
63
|
-
const _2 = useFocusableGroup();
|
|
64
|
-
const [activePart = 'list', setActivePart] = useControllableState({
|
|
65
|
-
prop: propsActivePart,
|
|
66
|
-
onChange: onActivePartChange,
|
|
67
|
-
defaultProp: defaultActivePart,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const [value, setValue] = useControllableState({
|
|
71
|
-
prop: propsValue,
|
|
72
|
-
onChange: onValueChange,
|
|
73
|
-
defaultProp: defaultValue,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const handleValueChange = useCallback(
|
|
77
|
-
(nextValue: string) => {
|
|
78
|
-
setActivePart('panel');
|
|
79
|
-
setValue(nextValue);
|
|
80
|
-
},
|
|
81
|
-
[value],
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const { findFirstFocusable } = useFocusFinders();
|
|
85
|
-
const tabsRoot = useRef<HTMLDivElement | null>(null);
|
|
44
|
+
//
|
|
45
|
+
// Root
|
|
46
|
+
//
|
|
86
47
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
48
|
+
type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
|
|
49
|
+
Partial<
|
|
50
|
+
Pick<TabsContextValue, 'activePart' | 'attendableId'> & {
|
|
51
|
+
onActivePartChange: (nextActivePart: TabsActivePart) => void;
|
|
52
|
+
defaultActivePart: TabsActivePart;
|
|
90
53
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
54
|
+
>;
|
|
55
|
+
|
|
56
|
+
const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
|
|
57
|
+
(
|
|
58
|
+
{
|
|
59
|
+
children,
|
|
60
|
+
classNames,
|
|
61
|
+
activePart: propsActivePart,
|
|
62
|
+
onActivePartChange,
|
|
63
|
+
defaultActivePart,
|
|
64
|
+
value: propsValue,
|
|
65
|
+
onValueChange,
|
|
66
|
+
defaultValue,
|
|
67
|
+
orientation = 'vertical',
|
|
68
|
+
activationMode = 'manual',
|
|
69
|
+
attendableId,
|
|
70
|
+
...props
|
|
71
|
+
},
|
|
72
|
+
forwardedRef,
|
|
73
|
+
) => {
|
|
74
|
+
const tabsRoot = useForwardedRef(forwardedRef);
|
|
75
|
+
|
|
76
|
+
// TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
|
|
77
|
+
const _1 = useArrowNavigationGroup();
|
|
78
|
+
const _2 = useFocusableGroup();
|
|
79
|
+
const [activePart = 'list', setActivePart] = useControllableState({
|
|
80
|
+
prop: propsActivePart,
|
|
81
|
+
onChange: onActivePartChange,
|
|
82
|
+
defaultProp: defaultActivePart,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const [value, setValue] = useControllableState({
|
|
86
|
+
prop: propsValue,
|
|
87
|
+
onChange: onValueChange,
|
|
88
|
+
defaultProp: defaultValue,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const handleValueChange = useCallback(
|
|
92
|
+
(nextValue: string) => {
|
|
93
|
+
setActivePart('panel');
|
|
94
|
+
setValue(nextValue);
|
|
95
|
+
},
|
|
96
|
+
[value],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const { findFirstFocusable } = useFocusFinders();
|
|
100
|
+
|
|
101
|
+
useLayoutEffect(() => {
|
|
102
|
+
if (tabsRoot.current) {
|
|
103
|
+
findFirstFocusable(tabsRoot.current)?.focus();
|
|
104
|
+
}
|
|
105
|
+
}, [activePart]);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<TabsContextProvider
|
|
105
109
|
orientation={orientation}
|
|
106
|
-
{
|
|
110
|
+
activePart={activePart}
|
|
111
|
+
setActivePart={setActivePart}
|
|
107
112
|
value={value}
|
|
108
|
-
|
|
109
|
-
className={mx(
|
|
110
|
-
'overflow-hidden',
|
|
111
|
-
orientation === 'vertical' &&
|
|
112
|
-
verticalVariant === 'stateful' &&
|
|
113
|
-
'[&[data-active=list]_[role=tabpanel]]:invisible @md:[&[data-active=list]_[role=tabpanel]]:visible',
|
|
114
|
-
classNames,
|
|
115
|
-
)}
|
|
116
|
-
ref={tabsRoot}
|
|
113
|
+
attendableId={attendableId}
|
|
117
114
|
>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
115
|
+
<TabsPrimitive.Root
|
|
116
|
+
{...props}
|
|
117
|
+
className={mx('overflow-hidden', classNames)}
|
|
118
|
+
orientation={orientation}
|
|
119
|
+
activationMode={activationMode}
|
|
120
|
+
data-active={activePart}
|
|
121
|
+
value={value}
|
|
122
|
+
onValueChange={handleValueChange}
|
|
123
|
+
ref={tabsRoot}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</TabsPrimitive.Root>
|
|
127
|
+
</TabsContextProvider>
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
TabsRoot.displayName = 'Tabs.Root';
|
|
133
|
+
|
|
134
|
+
//
|
|
135
|
+
// Viewport
|
|
136
|
+
//
|
|
123
137
|
|
|
124
138
|
type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
|
|
125
139
|
|
|
126
140
|
const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
|
|
127
|
-
const {
|
|
141
|
+
const { activePart } = useTabsContext('TabsViewport');
|
|
128
142
|
return (
|
|
129
|
-
<div
|
|
130
|
-
role='none'
|
|
131
|
-
{...props}
|
|
132
|
-
data-active={activePart}
|
|
133
|
-
className={mx(
|
|
134
|
-
orientation === 'vertical' &&
|
|
135
|
-
verticalVariant === 'stateful' && [
|
|
136
|
-
'grid is-[200%] grid-cols-2 data-[active=panel]:mis-[-100%]',
|
|
137
|
-
'@md:is-auto @md:data-[active=panel]:mis-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
|
|
138
|
-
],
|
|
139
|
-
classNames,
|
|
140
|
-
)}
|
|
141
|
-
>
|
|
143
|
+
<div {...props} data-active={activePart} className={mx(classNames)}>
|
|
142
144
|
{children}
|
|
143
145
|
</div>
|
|
144
146
|
);
|
|
145
147
|
};
|
|
146
148
|
|
|
149
|
+
TabsViewport.displayName = 'Tabs.Viewport';
|
|
150
|
+
|
|
151
|
+
//
|
|
152
|
+
// Tablist
|
|
153
|
+
//
|
|
154
|
+
|
|
147
155
|
type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
|
|
148
156
|
|
|
149
157
|
const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
|
|
150
|
-
const { orientation
|
|
158
|
+
const { orientation } = useTabsContext('TabsTablist');
|
|
151
159
|
return (
|
|
152
160
|
<TabsPrimitive.List
|
|
153
161
|
{...props}
|
|
154
162
|
data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
|
|
155
163
|
className={mx(
|
|
156
|
-
'max-
|
|
157
|
-
//
|
|
158
|
-
orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto
|
|
159
|
-
orientation === 'vertical' && verticalVariant === 'stateful' && 'place-self-start p-1',
|
|
164
|
+
'max-h-full w-full',
|
|
165
|
+
// TODO(burdon): Should be embeddable inside Toolbar (if horizontal).
|
|
166
|
+
orientation === 'vertical' ? 'overflow-y-auto' : 'flex p-1 gap-1 items-stretch justify-start overflow-x-auto',
|
|
160
167
|
classNames,
|
|
161
168
|
)}
|
|
162
169
|
>
|
|
@@ -165,6 +172,12 @@ const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
|
|
|
165
172
|
);
|
|
166
173
|
};
|
|
167
174
|
|
|
175
|
+
TabsTablist.displayName = 'Tabs.Tablist';
|
|
176
|
+
|
|
177
|
+
//
|
|
178
|
+
// BackButton
|
|
179
|
+
//
|
|
180
|
+
|
|
168
181
|
const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
|
|
169
182
|
const { setActivePart } = useTabsContext('TabsBackButton');
|
|
170
183
|
const handleClick = useCallback(
|
|
@@ -172,21 +185,31 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
|
|
|
172
185
|
setActivePart('list');
|
|
173
186
|
return onClick?.(event);
|
|
174
187
|
},
|
|
175
|
-
[
|
|
188
|
+
[setActivePart, onClick],
|
|
176
189
|
);
|
|
177
190
|
|
|
178
|
-
return <Button {...props} classNames={['
|
|
191
|
+
return <Button {...props} classNames={['@md:hidden text-start', classNames]} onClick={handleClick} />;
|
|
179
192
|
};
|
|
180
193
|
|
|
194
|
+
TabsBackButton.displayName = 'Tabs.BackButton';
|
|
195
|
+
|
|
196
|
+
//
|
|
197
|
+
// TabGroupHeading
|
|
198
|
+
//
|
|
199
|
+
|
|
181
200
|
type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
|
|
182
201
|
|
|
183
|
-
const TabsTabGroupHeading = ({ children, classNames, ...props }:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
202
|
+
const TabsTabGroupHeading = ({ children, classNames, ...props }: TabsTabGroupHeadingProps) => (
|
|
203
|
+
<h2 {...props} className={mx('my-1 px-2 text-sm text-un-accent', classNames)}>
|
|
204
|
+
{children}
|
|
205
|
+
</h2>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
TabsTabGroupHeading.displayName = 'Tabs.TabGroupHeading';
|
|
209
|
+
|
|
210
|
+
//
|
|
211
|
+
// Tab
|
|
212
|
+
//
|
|
190
213
|
|
|
191
214
|
type TabsTabProps = ButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
|
|
192
215
|
|
|
@@ -206,17 +229,16 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
|
|
|
206
229
|
return (
|
|
207
230
|
<TabsPrimitive.Trigger value={value} asChild>
|
|
208
231
|
<Button
|
|
209
|
-
|
|
232
|
+
{...props}
|
|
210
233
|
variant={
|
|
211
234
|
orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
|
|
212
235
|
}
|
|
213
|
-
{...props}
|
|
214
|
-
onClick={handleClick}
|
|
215
236
|
classNames={[
|
|
216
|
-
orientation === 'vertical' && 'block justify-start text-start
|
|
217
|
-
orientation === 'vertical' &&
|
|
237
|
+
orientation === 'vertical' && 'block justify-start text-start w-full',
|
|
238
|
+
orientation === 'vertical' && 'dx-selected',
|
|
218
239
|
classNames,
|
|
219
240
|
]}
|
|
241
|
+
onClick={handleClick}
|
|
220
242
|
>
|
|
221
243
|
{children}
|
|
222
244
|
</Button>
|
|
@@ -224,6 +246,12 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
|
|
|
224
246
|
);
|
|
225
247
|
};
|
|
226
248
|
|
|
249
|
+
TabsTab.displayName = 'Tabs.Tab';
|
|
250
|
+
|
|
251
|
+
//
|
|
252
|
+
// IconTab
|
|
253
|
+
//
|
|
254
|
+
|
|
227
255
|
type TabsIconTabProps = IconButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
|
|
228
256
|
|
|
229
257
|
const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps) => {
|
|
@@ -242,37 +270,49 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
|
|
|
242
270
|
return (
|
|
243
271
|
<TabsPrimitive.Trigger value={value} asChild>
|
|
244
272
|
<IconButton
|
|
245
|
-
|
|
273
|
+
{...props}
|
|
246
274
|
variant={
|
|
247
275
|
orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
|
|
248
276
|
}
|
|
249
|
-
{...props}
|
|
250
|
-
onClick={handleClick}
|
|
251
277
|
classNames={[
|
|
252
|
-
orientation === 'vertical' && 'justify-start text-start
|
|
253
|
-
orientation === 'vertical' &&
|
|
278
|
+
orientation === 'vertical' && 'justify-start text-start w-full',
|
|
279
|
+
orientation === 'vertical' && 'dx-selected',
|
|
254
280
|
classNames,
|
|
255
281
|
]}
|
|
282
|
+
onClick={handleClick}
|
|
256
283
|
/>
|
|
257
284
|
</TabsPrimitive.Trigger>
|
|
258
285
|
);
|
|
259
286
|
};
|
|
260
287
|
|
|
261
|
-
|
|
288
|
+
TabsIconTab.displayName = 'Tabs.IconTab';
|
|
262
289
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
290
|
+
//
|
|
291
|
+
// Panel
|
|
292
|
+
//
|
|
293
|
+
// Do NOT wrap TabsPanel children in React.Activity.
|
|
294
|
+
// Radix TabsPrimitive.Content already unmounts inactive panels (no forceMount) — inactive tab
|
|
295
|
+
// content is not in the DOM and effects do not run, which is the desired behaviour.
|
|
296
|
+
// React.Activity (experimental in React 19) is a reconciler-level symbol that deactivates its
|
|
297
|
+
// subtree when mode='hidden'. It was redundant here and prevented initial render of active panels.
|
|
298
|
+
//
|
|
299
|
+
|
|
300
|
+
type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
|
|
301
|
+
|
|
302
|
+
const TabsPanel = ({ classNames, children, ...props }: TabsPanelProps) => (
|
|
303
|
+
<TabsPrimitive.Content {...props} className={mx('p-0! dx-focus-ring-inset-over-all', classNames)}>
|
|
304
|
+
{children}
|
|
305
|
+
</TabsPrimitive.Content>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
TabsPanel.displayName = 'Tabs.Panel';
|
|
273
309
|
|
|
274
310
|
type TabsTabPrimitiveProps = TabsPrimitive.TabsTriggerProps;
|
|
275
311
|
|
|
312
|
+
//
|
|
313
|
+
// Tabs
|
|
314
|
+
//
|
|
315
|
+
|
|
276
316
|
export const Tabs = {
|
|
277
317
|
Root: TabsRoot,
|
|
278
318
|
Tablist: TabsTablist,
|
|
@@ -280,9 +320,9 @@ export const Tabs = {
|
|
|
280
320
|
IconTab: TabsIconTab,
|
|
281
321
|
TabPrimitive: TabsPrimitive.Trigger,
|
|
282
322
|
TabGroupHeading: TabsTabGroupHeading,
|
|
283
|
-
Tabpanel: TabsTabpanel,
|
|
284
|
-
BackButton: TabsBackButton,
|
|
285
323
|
Viewport: TabsViewport,
|
|
324
|
+
Panel: TabsPanel,
|
|
325
|
+
BackButton: TabsBackButton,
|
|
286
326
|
};
|
|
287
327
|
|
|
288
328
|
export type {
|
|
@@ -292,6 +332,6 @@ export type {
|
|
|
292
332
|
TabsTabProps,
|
|
293
333
|
TabsTabPrimitiveProps,
|
|
294
334
|
TabsTabGroupHeadingProps,
|
|
295
|
-
TabsTabpanelProps,
|
|
296
335
|
TabsViewportProps,
|
|
336
|
+
TabsPanelProps,
|
|
297
337
|
};
|