@dxos/react-ui-tabs 0.8.4-main.f9ba587 → 0.8.4-main.fcc0d83b33
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 +149 -164
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +149 -164
- 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 +13 -7
- package/dist/types/src/Tabs.d.ts.map +1 -1
- package/dist/types/src/Tabs.stories.d.ts +21 -6
- package/dist/types/src/Tabs.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -22
- package/src/Tabs.stories.tsx +57 -45
- package/src/Tabs.tsx +154 -118
package/package.json
CHANGED
|
@@ -1,56 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-tabs",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.fcc0d83b33",
|
|
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
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
|
-
"sideEffects":
|
|
13
|
+
"sideEffects": false,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
13
18
|
"types": "./dist/types/src/index.d.ts",
|
|
14
19
|
"browser": "./dist/lib/browser/index.mjs",
|
|
15
20
|
"node": "./dist/lib/node-esm/index.mjs"
|
|
16
21
|
}
|
|
17
22
|
},
|
|
18
23
|
"types": "dist/types/src/index.d.ts",
|
|
19
|
-
"typesVersions": {
|
|
20
|
-
"*": {}
|
|
21
|
-
},
|
|
22
24
|
"files": [
|
|
23
25
|
"dist",
|
|
24
26
|
"src"
|
|
25
27
|
],
|
|
26
28
|
"dependencies": {
|
|
27
|
-
"@fluentui/react-tabster": "
|
|
28
|
-
"@preact-signals/safe-react": "^0.9.0",
|
|
29
|
+
"@fluentui/react-tabster": "9.26.11",
|
|
29
30
|
"@radix-ui/primitive": "1.1.1",
|
|
30
31
|
"@radix-ui/react-context": "1.1.1",
|
|
31
32
|
"@radix-ui/react-primitive": "2.0.2",
|
|
32
33
|
"@radix-ui/react-slot": "1.1.2",
|
|
33
34
|
"@radix-ui/react-tabs": "1.1.3",
|
|
34
35
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
35
|
-
"@dxos/react-ui-attention": "0.8.4-main.
|
|
36
|
-
"@dxos/util": "0.8.4-main.
|
|
36
|
+
"@dxos/react-ui-attention": "0.8.4-main.fcc0d83b33",
|
|
37
|
+
"@dxos/util": "0.8.4-main.fcc0d83b33"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@types/react": "~
|
|
40
|
-
"@types/react-dom": "~
|
|
41
|
-
"react": "~
|
|
42
|
-
"react-dom": "~
|
|
43
|
-
"vite": "
|
|
44
|
-
"@dxos/random": "0.8.4-main.
|
|
45
|
-
"@dxos/react-ui
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
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.10",
|
|
45
|
+
"@dxos/random": "0.8.4-main.fcc0d83b33",
|
|
46
|
+
"@dxos/react-ui": "0.8.4-main.fcc0d83b33",
|
|
47
|
+
"@dxos/ui-theme": "0.8.4-main.fcc0d83b33",
|
|
48
|
+
"@dxos/storybook-utils": "0.8.4-main.fcc0d83b33"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
|
-
"react": "~
|
|
51
|
-
"react-dom": "~
|
|
52
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
53
|
-
"@dxos/
|
|
51
|
+
"react": "~19.2.3",
|
|
52
|
+
"react-dom": "~19.2.3",
|
|
53
|
+
"@dxos/react-ui": "0.8.4-main.fcc0d83b33",
|
|
54
|
+
"@dxos/ui-theme": "0.8.4-main.fcc0d83b33"
|
|
54
55
|
},
|
|
55
56
|
"publishConfig": {
|
|
56
57
|
"access": "public"
|
package/src/Tabs.stories.tsx
CHANGED
|
@@ -2,64 +2,76 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { random } from '@dxos/random';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
10
11
|
|
|
11
|
-
import { Tabs
|
|
12
|
+
import { Tabs, TabsRootProps } from './Tabs';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
random.seed(1234);
|
|
15
|
+
|
|
16
|
+
const DefaultStory = ({ orientation }: TabsRootProps) => {
|
|
17
|
+
return (
|
|
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>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
14
43
|
|
|
15
44
|
const content = [...Array(24)].reduce((acc: { [key: string]: { title: string; panel: string } }, _, index) => {
|
|
16
45
|
acc[`t${index}`] = {
|
|
17
|
-
title:
|
|
18
|
-
panel:
|
|
46
|
+
title: random.commerce.productName(),
|
|
47
|
+
panel: random.lorem.paragraphs(5),
|
|
19
48
|
};
|
|
20
49
|
return acc;
|
|
21
50
|
}, {});
|
|
22
51
|
|
|
23
|
-
|
|
52
|
+
const meta = {
|
|
24
53
|
title: 'ui/react-ui-tabs/Tabs',
|
|
25
|
-
component:
|
|
26
|
-
|
|
27
|
-
|
|
54
|
+
component: Tabs.Root,
|
|
55
|
+
render: DefaultStory,
|
|
56
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
57
|
+
parameters: {
|
|
58
|
+
layout: 'fullscreen',
|
|
59
|
+
},
|
|
60
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
61
|
+
|
|
62
|
+
export default meta;
|
|
63
|
+
|
|
64
|
+
type Story = StoryObj<typeof meta>;
|
|
65
|
+
|
|
66
|
+
// TODO(burdon): Scrolling.
|
|
67
|
+
export const Horizontal: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
orientation: 'horizontal',
|
|
70
|
+
},
|
|
28
71
|
};
|
|
29
72
|
|
|
30
|
-
export const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<Dialog.Root open>
|
|
34
|
-
<Dialog.Overlay blockAlign='start'>
|
|
35
|
-
<Dialog.Content classNames='is-[calc(100dvw-4rem)] !max-is-full'>
|
|
36
|
-
<NaturalTabs.Root orientation='vertical' defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
|
|
37
|
-
<NaturalTabs.Viewport>
|
|
38
|
-
<NaturalTabs.Tablist>
|
|
39
|
-
{Object.entries(content).map(([id, { title }]) => {
|
|
40
|
-
return (
|
|
41
|
-
<NaturalTabs.Tab key={id} value={id}>
|
|
42
|
-
{title}
|
|
43
|
-
</NaturalTabs.Tab>
|
|
44
|
-
);
|
|
45
|
-
})}
|
|
46
|
-
</NaturalTabs.Tablist>
|
|
47
|
-
{Object.entries(content).map(([id, { panel }]) => {
|
|
48
|
-
return (
|
|
49
|
-
<NaturalTabs.Tabpanel key={id} value={id} classNames='m-1'>
|
|
50
|
-
<NaturalTabs.BackButton density='fine'>
|
|
51
|
-
<Icon icon='ph--arrow-left--bold' size={4} />
|
|
52
|
-
<span>Back to tab list</span>
|
|
53
|
-
</NaturalTabs.BackButton>
|
|
54
|
-
<p className='pli-1'>{panel}</p>
|
|
55
|
-
</NaturalTabs.Tabpanel>
|
|
56
|
-
);
|
|
57
|
-
})}
|
|
58
|
-
</NaturalTabs.Viewport>
|
|
59
|
-
</NaturalTabs.Root>
|
|
60
|
-
</Dialog.Content>
|
|
61
|
-
</Dialog.Overlay>
|
|
62
|
-
</Dialog.Root>
|
|
63
|
-
);
|
|
73
|
+
export const Vertical: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
orientation: 'vertical',
|
|
64
76
|
},
|
|
65
77
|
};
|
package/src/Tabs.tsx
CHANGED
|
@@ -2,15 +2,29 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useArrowNavigationGroup, useFocusFinders, useFocusableGroup } from '@fluentui/react-tabster';
|
|
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, {
|
|
9
|
+
import React, {
|
|
10
|
+
Activity,
|
|
11
|
+
type ComponentPropsWithoutRef,
|
|
12
|
+
type MouseEvent,
|
|
13
|
+
forwardRef,
|
|
14
|
+
useCallback,
|
|
15
|
+
useLayoutEffect,
|
|
16
|
+
} from 'react';
|
|
10
17
|
|
|
11
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
Button,
|
|
20
|
+
type ButtonProps,
|
|
21
|
+
IconButton,
|
|
22
|
+
type IconButtonProps,
|
|
23
|
+
type ThemedClassName,
|
|
24
|
+
useForwardedRef,
|
|
25
|
+
} from '@dxos/react-ui';
|
|
12
26
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
13
|
-
import {
|
|
27
|
+
import { mx } from '@dxos/ui-theme';
|
|
14
28
|
|
|
15
29
|
type TabsActivePart = 'list' | 'panel';
|
|
16
30
|
|
|
@@ -20,118 +34,105 @@ type TabsContextValue = {
|
|
|
20
34
|
activePart: TabsActivePart;
|
|
21
35
|
setActivePart: (nextActivePart: TabsActivePart) => void;
|
|
22
36
|
attendableId?: string;
|
|
23
|
-
verticalVariant?: 'stateful' | 'stateless';
|
|
24
37
|
} & Pick<TabsPrimitive.TabsProps, 'orientation' | 'value'>;
|
|
25
38
|
|
|
26
39
|
const [TabsContextProvider, useTabsContext] = createContext<TabsContextValue>(TABS_NAME, {
|
|
40
|
+
orientation: 'vertical',
|
|
27
41
|
activePart: 'list',
|
|
28
42
|
setActivePart: () => {},
|
|
29
|
-
orientation: 'vertical',
|
|
30
43
|
});
|
|
31
44
|
|
|
32
45
|
type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
|
|
33
|
-
Partial<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const _1 = useArrowNavigationGroup();
|
|
56
|
-
const _2 = useFocusableGroup();
|
|
57
|
-
const [activePart = 'list', setActivePart] = useControllableState({
|
|
58
|
-
prop: propsActivePart,
|
|
59
|
-
onChange: onActivePartChange,
|
|
60
|
-
defaultProp: defaultActivePart,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const [value, setValue] = useControllableState({
|
|
64
|
-
prop: propsValue,
|
|
65
|
-
onChange: onValueChange,
|
|
66
|
-
defaultProp: defaultValue,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const handleValueChange = useCallback(
|
|
70
|
-
(nextValue: string) => {
|
|
71
|
-
setActivePart('panel');
|
|
72
|
-
setValue(nextValue);
|
|
46
|
+
Partial<
|
|
47
|
+
Pick<TabsContextValue, 'activePart' | 'attendableId'> & {
|
|
48
|
+
onActivePartChange: (nextActivePart: TabsActivePart) => void;
|
|
49
|
+
defaultActivePart: TabsActivePart;
|
|
50
|
+
}
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
|
|
54
|
+
(
|
|
55
|
+
{
|
|
56
|
+
children,
|
|
57
|
+
classNames,
|
|
58
|
+
activePart: propsActivePart,
|
|
59
|
+
onActivePartChange,
|
|
60
|
+
defaultActivePart,
|
|
61
|
+
value: propsValue,
|
|
62
|
+
onValueChange,
|
|
63
|
+
defaultValue,
|
|
64
|
+
orientation = 'vertical',
|
|
65
|
+
activationMode = 'manual',
|
|
66
|
+
attendableId,
|
|
67
|
+
...props
|
|
73
68
|
},
|
|
74
|
-
|
|
75
|
-
)
|
|
69
|
+
forwardedRef,
|
|
70
|
+
) => {
|
|
71
|
+
// const tabsRoot = useRef<HTMLDivElement | null>(null);
|
|
72
|
+
const tabsRoot = useForwardedRef(forwardedRef);
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
// TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
|
|
75
|
+
const _1 = useArrowNavigationGroup();
|
|
76
|
+
const _2 = useFocusableGroup();
|
|
77
|
+
const [activePart = 'list', setActivePart] = useControllableState({
|
|
78
|
+
prop: propsActivePart,
|
|
79
|
+
onChange: onActivePartChange,
|
|
80
|
+
defaultProp: defaultActivePart,
|
|
81
|
+
});
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const [value, setValue] = useControllableState({
|
|
84
|
+
prop: propsValue,
|
|
85
|
+
onChange: onValueChange,
|
|
86
|
+
defaultProp: defaultValue,
|
|
87
|
+
});
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
value
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
const handleValueChange = useCallback(
|
|
90
|
+
(nextValue: string) => {
|
|
91
|
+
setActivePart('panel');
|
|
92
|
+
setValue(nextValue);
|
|
93
|
+
},
|
|
94
|
+
[value],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const { findFirstFocusable } = useFocusFinders();
|
|
98
|
+
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
if (tabsRoot.current) {
|
|
101
|
+
findFirstFocusable(tabsRoot.current)?.focus();
|
|
102
|
+
}
|
|
103
|
+
}, [activePart]);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<TabsContextProvider
|
|
98
107
|
orientation={orientation}
|
|
99
|
-
{
|
|
108
|
+
activePart={activePart}
|
|
109
|
+
setActivePart={setActivePart}
|
|
100
110
|
value={value}
|
|
101
|
-
|
|
102
|
-
className={mx(
|
|
103
|
-
'overflow-hidden',
|
|
104
|
-
orientation === 'vertical' &&
|
|
105
|
-
verticalVariant === 'stateful' &&
|
|
106
|
-
'[&[data-active=list]_[role=tabpanel]]:invisible @md:[&[data-active=list]_[role=tabpanel]]:visible',
|
|
107
|
-
classNames,
|
|
108
|
-
)}
|
|
109
|
-
ref={tabsRoot}
|
|
111
|
+
attendableId={attendableId}
|
|
110
112
|
>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
113
|
+
<TabsPrimitive.Root
|
|
114
|
+
{...props}
|
|
115
|
+
className={mx('overflow-hidden', classNames)}
|
|
116
|
+
orientation={orientation}
|
|
117
|
+
activationMode={activationMode}
|
|
118
|
+
data-active={activePart}
|
|
119
|
+
value={value}
|
|
120
|
+
onValueChange={handleValueChange}
|
|
121
|
+
ref={tabsRoot}
|
|
122
|
+
>
|
|
123
|
+
{children}
|
|
124
|
+
</TabsPrimitive.Root>
|
|
125
|
+
</TabsContextProvider>
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
116
129
|
|
|
117
130
|
type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
|
|
118
131
|
|
|
119
132
|
const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
|
|
120
|
-
const {
|
|
133
|
+
const { activePart } = useTabsContext('TabsViewport');
|
|
121
134
|
return (
|
|
122
|
-
<div
|
|
123
|
-
role='none'
|
|
124
|
-
{...props}
|
|
125
|
-
data-active={activePart}
|
|
126
|
-
className={mx(
|
|
127
|
-
orientation === 'vertical' &&
|
|
128
|
-
verticalVariant === 'stateful' && [
|
|
129
|
-
'grid is-[200%] grid-cols-2 data-[active=panel]:mis-[-100%]',
|
|
130
|
-
'@md:is-auto @md:data-[active=panel]:mis-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
|
|
131
|
-
],
|
|
132
|
-
classNames,
|
|
133
|
-
)}
|
|
134
|
-
>
|
|
135
|
+
<div role='none' {...props} data-active={activePart} className={mx(classNames)}>
|
|
135
136
|
{children}
|
|
136
137
|
</div>
|
|
137
138
|
);
|
|
@@ -140,15 +141,15 @@ const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) =>
|
|
|
140
141
|
type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
|
|
141
142
|
|
|
142
143
|
const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
|
|
143
|
-
const { orientation
|
|
144
|
+
const { orientation } = useTabsContext('TabsTablist');
|
|
144
145
|
return (
|
|
145
146
|
<TabsPrimitive.List
|
|
146
147
|
{...props}
|
|
148
|
+
data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
|
|
147
149
|
className={mx(
|
|
148
|
-
'max-
|
|
149
|
-
//
|
|
150
|
-
orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto
|
|
151
|
-
orientation === 'vertical' && verticalVariant === 'stateful' && 'place-self-start p-1',
|
|
150
|
+
'max-h-full w-full',
|
|
151
|
+
// TODO(burdon): Should be embeddable inside Toolbar (if horizontal).
|
|
152
|
+
orientation === 'vertical' ? 'overflow-y-auto' : 'flex p-1 gap-1 items-stretch justify-start overflow-x-auto',
|
|
152
153
|
classNames,
|
|
153
154
|
)}
|
|
154
155
|
>
|
|
@@ -164,17 +165,17 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
|
|
|
164
165
|
setActivePart('list');
|
|
165
166
|
return onClick?.(event);
|
|
166
167
|
},
|
|
167
|
-
[
|
|
168
|
+
[setActivePart, onClick],
|
|
168
169
|
);
|
|
169
170
|
|
|
170
|
-
return <Button {...props} classNames={['
|
|
171
|
+
return <Button {...props} classNames={['@md:hidden text-start', classNames]} onClick={handleClick} />;
|
|
171
172
|
};
|
|
172
173
|
|
|
173
174
|
type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
|
|
174
175
|
|
|
175
176
|
const TabsTabGroupHeading = ({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) => {
|
|
176
177
|
return (
|
|
177
|
-
<h2 {...props} className={mx('
|
|
178
|
+
<h2 {...props} className={mx('my-1 px-2 text-sm text-un-accent', classNames)}>
|
|
178
179
|
{children}
|
|
179
180
|
</h2>
|
|
180
181
|
);
|
|
@@ -185,6 +186,7 @@ type TabsTabProps = ButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
|
|
|
185
186
|
const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProps) => {
|
|
186
187
|
const { setActivePart, orientation, value: contextValue, attendableId } = useTabsContext('TabsTab');
|
|
187
188
|
const { hasAttention } = useAttention(attendableId);
|
|
189
|
+
|
|
188
190
|
const handleClick = useCallback(
|
|
189
191
|
// NOTE: This handler is only called if the tab is *already active*.
|
|
190
192
|
(event: MouseEvent<HTMLButtonElement>) => {
|
|
@@ -197,19 +199,16 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
|
|
|
197
199
|
return (
|
|
198
200
|
<TabsPrimitive.Trigger value={value} asChild>
|
|
199
201
|
<Button
|
|
200
|
-
|
|
202
|
+
{...props}
|
|
201
203
|
variant={
|
|
202
204
|
orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
|
|
203
205
|
}
|
|
204
|
-
{...props}
|
|
205
|
-
onClick={handleClick}
|
|
206
206
|
classNames={[
|
|
207
|
-
'
|
|
208
|
-
orientation === 'vertical' && '
|
|
209
|
-
orientation === 'vertical' && ghostSelectedContainerMd,
|
|
210
|
-
ghostHover,
|
|
207
|
+
orientation === 'vertical' && 'block justify-start text-start w-full',
|
|
208
|
+
orientation === 'vertical' && 'dx-selected',
|
|
211
209
|
classNames,
|
|
212
210
|
]}
|
|
211
|
+
onClick={handleClick}
|
|
213
212
|
>
|
|
214
213
|
{children}
|
|
215
214
|
</Button>
|
|
@@ -217,13 +216,49 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
|
|
|
217
216
|
);
|
|
218
217
|
};
|
|
219
218
|
|
|
220
|
-
type
|
|
219
|
+
type TabsIconTabProps = IconButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
|
|
220
|
+
|
|
221
|
+
const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps) => {
|
|
222
|
+
const { setActivePart, orientation, value: contextValue, attendableId } = useTabsContext('TabsTab');
|
|
223
|
+
const { hasAttention } = useAttention(attendableId);
|
|
224
|
+
|
|
225
|
+
// NOTE: This handler is only called if the tab is *already active*.
|
|
226
|
+
const handleClick = useCallback(
|
|
227
|
+
(event: MouseEvent<HTMLButtonElement>) => {
|
|
228
|
+
setActivePart('panel');
|
|
229
|
+
onClick?.(event);
|
|
230
|
+
},
|
|
231
|
+
[setActivePart, onClick],
|
|
232
|
+
);
|
|
221
233
|
|
|
222
|
-
const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
|
|
223
234
|
return (
|
|
224
|
-
<TabsPrimitive.
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
<TabsPrimitive.Trigger value={value} asChild>
|
|
236
|
+
<IconButton
|
|
237
|
+
{...props}
|
|
238
|
+
variant={
|
|
239
|
+
orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
|
|
240
|
+
}
|
|
241
|
+
classNames={[
|
|
242
|
+
orientation === 'vertical' && 'justify-start text-start w-full',
|
|
243
|
+
orientation === 'vertical' && 'dx-selected',
|
|
244
|
+
classNames,
|
|
245
|
+
]}
|
|
246
|
+
onClick={handleClick}
|
|
247
|
+
/>
|
|
248
|
+
</TabsPrimitive.Trigger>
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
|
|
253
|
+
|
|
254
|
+
const TabsPanel = ({ classNames, children, ...props }: TabsPanelProps) => {
|
|
255
|
+
const { value: contextValue } = useTabsContext('TabsTab');
|
|
256
|
+
return (
|
|
257
|
+
<Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
|
|
258
|
+
<TabsPrimitive.Content {...props} className={mx('p-0! dx-focus-ring-inset-over-all', classNames)}>
|
|
259
|
+
{children}
|
|
260
|
+
</TabsPrimitive.Content>
|
|
261
|
+
</Activity>
|
|
227
262
|
);
|
|
228
263
|
};
|
|
229
264
|
|
|
@@ -233,9 +268,10 @@ export const Tabs = {
|
|
|
233
268
|
Root: TabsRoot,
|
|
234
269
|
Tablist: TabsTablist,
|
|
235
270
|
Tab: TabsTab,
|
|
271
|
+
IconTab: TabsIconTab,
|
|
236
272
|
TabPrimitive: TabsPrimitive.Trigger,
|
|
237
273
|
TabGroupHeading: TabsTabGroupHeading,
|
|
238
|
-
|
|
274
|
+
Panel: TabsPanel,
|
|
239
275
|
BackButton: TabsBackButton,
|
|
240
276
|
Viewport: TabsViewport,
|
|
241
277
|
};
|
|
@@ -247,6 +283,6 @@ export type {
|
|
|
247
283
|
TabsTabProps,
|
|
248
284
|
TabsTabPrimitiveProps,
|
|
249
285
|
TabsTabGroupHeadingProps,
|
|
250
|
-
|
|
286
|
+
TabsPanelProps,
|
|
251
287
|
TabsViewportProps,
|
|
252
288
|
};
|