@dxos/react-ui-tabs 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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.ae835ea",
3
+ "version": "0.8.4-main.bc674ce",
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.ae835ea",
37
- "@dxos/util": "0.8.4-main.ae835ea"
39
+ "@dxos/react-ui-attention": "0.8.4-main.bc674ce",
40
+ "@dxos/util": "0.8.4-main.bc674ce"
38
41
  },
39
42
  "devDependencies": {
40
- "@types/react": "~19.2.2",
41
- "@types/react-dom": "~19.2.2",
42
- "react": "~19.2.0",
43
- "react-dom": "~19.2.0",
43
+ "@types/react": "~19.2.7",
44
+ "@types/react-dom": "~19.2.3",
45
+ "react": "~19.2.3",
46
+ "react-dom": "~19.2.3",
44
47
  "vite": "7.1.9",
45
- "@dxos/random": "0.8.4-main.ae835ea",
46
- "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
47
- "@dxos/react-ui": "0.8.4-main.ae835ea",
48
- "@dxos/storybook-utils": "0.8.4-main.ae835ea"
48
+ "@dxos/react-ui": "0.8.4-main.bc674ce",
49
+ "@dxos/storybook-utils": "0.8.4-main.bc674ce",
50
+ "@dxos/random": "0.8.4-main.bc674ce",
51
+ "@dxos/ui-theme": "0.8.4-main.bc674ce"
49
52
  },
50
53
  "peerDependencies": {
51
- "react": "^19.0.0",
52
- "react-dom": "^19.0.0",
53
- "@dxos/react-ui": "0.8.4-main.ae835ea",
54
- "@dxos/react-ui-theme": "0.8.4-main.ae835ea"
54
+ "react": "~19.2.3",
55
+ "react-dom": "~19.2.3",
56
+ "@dxos/react-ui": "0.8.4-main.bc674ce",
57
+ "@dxos/ui-theme": "0.8.4-main.bc674ce"
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>
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
 
@@ -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