@dxos/react-ui-tabs 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae

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/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-tabs",
3
- "version": "0.8.4-main.c4373fc",
3
+ "version": "0.8.4-main.c85a9c8dae",
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": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -25,33 +29,32 @@
25
29
  "src"
26
30
  ],
27
31
  "dependencies": {
28
- "@fluentui/react-tabster": "^9.24.2",
29
- "@preact-signals/safe-react": "^0.9.0",
32
+ "@fluentui/react-tabster": "9.26.11",
30
33
  "@radix-ui/primitive": "1.1.1",
31
34
  "@radix-ui/react-context": "1.1.1",
32
35
  "@radix-ui/react-primitive": "2.0.2",
33
36
  "@radix-ui/react-slot": "1.1.2",
34
37
  "@radix-ui/react-tabs": "1.1.3",
35
38
  "@radix-ui/react-use-controllable-state": "1.1.0",
36
- "@dxos/react-ui-attention": "0.8.4-main.c4373fc",
37
- "@dxos/util": "0.8.4-main.c4373fc"
39
+ "@dxos/react-ui-attention": "0.8.4-main.c85a9c8dae",
40
+ "@dxos/util": "0.8.4-main.c85a9c8dae"
38
41
  },
39
42
  "devDependencies": {
40
- "@types/react": "~19.2.2",
41
- "@types/react-dom": "~19.2.1",
42
- "react": "~19.2.0",
43
- "react-dom": "~19.2.0",
44
- "vite": "7.1.9",
45
- "@dxos/random": "0.8.4-main.c4373fc",
46
- "@dxos/react-ui": "0.8.4-main.c4373fc",
47
- "@dxos/react-ui-theme": "0.8.4-main.c4373fc",
48
- "@dxos/storybook-utils": "0.8.4-main.c4373fc"
43
+ "@types/react": "~19.2.7",
44
+ "@types/react-dom": "~19.2.3",
45
+ "react": "~19.2.3",
46
+ "react-dom": "~19.2.3",
47
+ "vite": "^7.1.11",
48
+ "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
49
+ "@dxos/storybook-utils": "0.8.4-main.c85a9c8dae",
50
+ "@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
51
+ "@dxos/random": "0.8.4-main.c85a9c8dae"
49
52
  },
50
53
  "peerDependencies": {
51
- "react": "^19.0.0",
52
- "react-dom": "^19.0.0",
53
- "@dxos/react-ui": "0.8.4-main.c4373fc",
54
- "@dxos/react-ui-theme": "0.8.4-main.c4373fc"
54
+ "react": "~19.2.3",
55
+ "react-dom": "~19.2.3",
56
+ "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
57
+ "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
55
58
  },
56
59
  "publishConfig": {
57
60
  "access": "public"
@@ -17,7 +17,7 @@ export const DefaultStory = () => {
17
17
  return (
18
18
  <Dialog.Root open>
19
19
  <Dialog.Overlay blockAlign='start'>
20
- <Dialog.Content classNames='is-[calc(100dvw-4rem)] !max-is-full'>
20
+ <Dialog.Content size='xl'>
21
21
  <NaturalTabs.Root orientation='vertical' defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
22
22
  <NaturalTabs.Viewport>
23
23
  <NaturalTabs.Tablist>
@@ -36,7 +36,7 @@ export const DefaultStory = () => {
36
36
  <Icon icon='ph--arrow-left--bold' size={4} />
37
37
  <span>Back to tab list</span>
38
38
  </NaturalTabs.BackButton>
39
- <p className='pli-1'>{panel}</p>
39
+ <p className='px-1'>{panel}</p>
40
40
  </NaturalTabs.Tabpanel>
41
41
  );
42
42
  })}
@@ -60,7 +60,7 @@ const meta = {
60
60
  title: 'ui/react-ui-tabs/Tabs',
61
61
  component: NaturalTabs.Root,
62
62
  render: DefaultStory,
63
- decorators: [withTheme],
63
+ decorators: [withTheme()],
64
64
  } satisfies Meta<typeof DefaultStory>;
65
65
 
66
66
  export default meta;
package/src/Tabs.tsx CHANGED
@@ -6,11 +6,25 @@ 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, { type ComponentPropsWithoutRef, type MouseEvent, useCallback, useLayoutEffect, useRef } from 'react';
10
-
11
- import { Button, type ButtonProps, IconButton, type IconButtonProps, type ThemedClassName } from '@dxos/react-ui';
9
+ import React, {
10
+ Activity,
11
+ type ComponentPropsWithoutRef,
12
+ type MouseEvent,
13
+ forwardRef,
14
+ useCallback,
15
+ useLayoutEffect,
16
+ } from 'react';
17
+
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 { ghostSelectedContainerMd, mx } from '@dxos/react-ui-theme';
27
+ import { ghostSelectedContainerMd, mx } from '@dxos/ui-theme';
14
28
 
15
29
  type TabsActivePart = 'list' | 'panel';
16
30
 
@@ -36,83 +50,90 @@ type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
36
50
  defaultActivePart: TabsActivePart;
37
51
  }>;
38
52
 
39
- const TabsRoot = ({
40
- children,
41
- classNames,
42
- activePart: propsActivePart,
43
- onActivePartChange,
44
- defaultActivePart,
45
- value: propsValue,
46
- onValueChange,
47
- defaultValue,
48
- orientation = 'vertical',
49
- activationMode = 'manual',
50
- verticalVariant = 'stateful',
51
- attendableId,
52
- ...props
53
- }: TabsRootProps) => {
54
- // TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
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);
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
+ verticalVariant = 'stateful',
67
+ attendableId,
68
+ ...props
73
69
  },
74
- [value],
75
- );
76
-
77
- const { findFirstFocusable } = useFocusFinders();
78
- const tabsRoot = useRef<HTMLDivElement | null>(null);
79
-
80
- useLayoutEffect(() => {
81
- if (tabsRoot.current) {
82
- findFirstFocusable(tabsRoot.current)?.focus();
83
- }
84
- }, [activePart]);
85
-
86
- return (
87
- <TabsContextProvider
88
- orientation={orientation}
89
- activePart={activePart}
90
- setActivePart={setActivePart}
91
- value={value}
92
- attendableId={attendableId}
93
- verticalVariant={verticalVariant}
94
- >
95
- <TabsPrimitive.Root
96
- activationMode={activationMode}
97
- data-active={activePart}
70
+ forwardedRef,
71
+ ) => {
72
+ // const tabsRoot = useRef<HTMLDivElement | null>(null);
73
+ const tabsRoot = useForwardedRef(forwardedRef);
74
+
75
+ // TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
76
+ const _1 = useArrowNavigationGroup();
77
+ const _2 = useFocusableGroup();
78
+ const [activePart = 'list', setActivePart] = useControllableState({
79
+ prop: propsActivePart,
80
+ onChange: onActivePartChange,
81
+ defaultProp: defaultActivePart,
82
+ });
83
+
84
+ const [value, setValue] = useControllableState({
85
+ prop: propsValue,
86
+ onChange: onValueChange,
87
+ defaultProp: defaultValue,
88
+ });
89
+
90
+ const handleValueChange = useCallback(
91
+ (nextValue: string) => {
92
+ setActivePart('panel');
93
+ setValue(nextValue);
94
+ },
95
+ [value],
96
+ );
97
+
98
+ const { findFirstFocusable } = useFocusFinders();
99
+
100
+ useLayoutEffect(() => {
101
+ if (tabsRoot.current) {
102
+ findFirstFocusable(tabsRoot.current)?.focus();
103
+ }
104
+ }, [activePart]);
105
+
106
+ return (
107
+ <TabsContextProvider
98
108
  orientation={orientation}
99
- {...props}
109
+ activePart={activePart}
110
+ setActivePart={setActivePart}
100
111
  value={value}
101
- onValueChange={handleValueChange}
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}
112
+ attendableId={attendableId}
113
+ verticalVariant={verticalVariant}
110
114
  >
111
- {children}
112
- </TabsPrimitive.Root>
113
- </TabsContextProvider>
114
- );
115
- };
115
+ <TabsPrimitive.Root
116
+ activationMode={activationMode}
117
+ data-active={activePart}
118
+ orientation={orientation}
119
+ {...props}
120
+ value={value}
121
+ onValueChange={handleValueChange}
122
+ className={mx(
123
+ 'overflow-hidden',
124
+ orientation === 'vertical' &&
125
+ verticalVariant === 'stateful' &&
126
+ '[&[data-active=list]_[role=tabpanel]]:invisible @md:[&[data-active=list]_[role=tabpanel]]:visible',
127
+ classNames,
128
+ )}
129
+ ref={tabsRoot}
130
+ >
131
+ {children}
132
+ </TabsPrimitive.Root>
133
+ </TabsContextProvider>
134
+ );
135
+ },
136
+ );
116
137
 
117
138
  type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
118
139
 
@@ -126,8 +147,8 @@ const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) =>
126
147
  className={mx(
127
148
  orientation === 'vertical' &&
128
149
  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',
150
+ 'grid w-[200%] grid-cols-2 data-[active=panel]:ms-[-100%]',
151
+ '@md:w-auto @md:data-[active=panel]:ms-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
131
152
  ],
132
153
  classNames,
133
154
  )}
@@ -146,7 +167,7 @@ const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
146
167
  {...props}
147
168
  data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
148
169
  className={mx(
149
- 'max-bs-full is-full',
170
+ 'max-h-full w-full',
150
171
  // NOTE: Padding should be common to Toolbar.
151
172
  orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto p-1 gap-1',
152
173
  orientation === 'vertical' && verticalVariant === 'stateful' && 'place-self-start p-1',
@@ -168,14 +189,14 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
168
189
  [onClick, setActivePart],
169
190
  );
170
191
 
171
- return <Button {...props} classNames={['is-full text-start @md:hidden mbe-2', classNames]} onClick={handleClick} />;
192
+ return <Button {...props} classNames={['w-full text-start @md:hidden mb-2', classNames]} onClick={handleClick} />;
172
193
  };
173
194
 
174
195
  type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
175
196
 
176
197
  const TabsTabGroupHeading = ({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) => {
177
198
  return (
178
- <h2 {...props} className={mx('mlb-1 pli-2 text-sm text-unAccent', classNames)}>
199
+ <h2 {...props} className={mx('my-1 px-2 text-sm text-un-accent', classNames)}>
179
200
  {children}
180
201
  </h2>
181
202
  );
@@ -206,7 +227,7 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
206
227
  {...props}
207
228
  onClick={handleClick}
208
229
  classNames={[
209
- orientation === 'vertical' && 'block justify-start text-start is-full',
230
+ orientation === 'vertical' && 'block justify-start text-start w-full',
210
231
  orientation === 'vertical' && ghostSelectedContainerMd,
211
232
  classNames,
212
233
  ]}
@@ -242,7 +263,7 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
242
263
  {...props}
243
264
  onClick={handleClick}
244
265
  classNames={[
245
- orientation === 'vertical' && 'justify-start text-start is-full',
266
+ orientation === 'vertical' && 'justify-start text-start w-full',
246
267
  orientation === 'vertical' && ghostSelectedContainerMd,
247
268
  classNames,
248
269
  ]}
@@ -254,10 +275,13 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
254
275
  type TabsTabpanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
255
276
 
256
277
  const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
278
+ const { value: contextValue } = useTabsContext('TabsTab');
257
279
  return (
258
- <TabsPrimitive.Content {...props} className={mx('dx-focus-ring-inset-over-all', classNames)}>
259
- {children}
260
- </TabsPrimitive.Content>
280
+ <Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
281
+ <TabsPrimitive.Content {...props} className={mx('dx-focus-ring-inset-over-all', classNames)}>
282
+ {children}
283
+ </TabsPrimitive.Content>
284
+ </Activity>
261
285
  );
262
286
  };
263
287