@dxos/react-ui-tabs 0.8.4-main.ef1bc66f44 → 0.8.4-main.effb148878

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-tabs",
3
- "version": "0.8.4-main.ef1bc66f44",
3
+ "version": "0.8.4-main.effb148878",
4
4
  "description": "Components for facilitating a Tabs pattern.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": false,
14
14
  "type": "module",
@@ -21,9 +21,6 @@
21
21
  }
22
22
  },
23
23
  "types": "dist/types/src/index.d.ts",
24
- "typesVersions": {
25
- "*": {}
26
- },
27
24
  "files": [
28
25
  "dist",
29
26
  "src"
@@ -36,25 +33,25 @@
36
33
  "@radix-ui/react-slot": "1.1.2",
37
34
  "@radix-ui/react-tabs": "1.1.3",
38
35
  "@radix-ui/react-use-controllable-state": "1.1.0",
39
- "@dxos/react-ui-attention": "0.8.4-main.ef1bc66f44",
40
- "@dxos/util": "0.8.4-main.ef1bc66f44"
36
+ "@dxos/react-ui-attention": "0.8.4-main.effb148878",
37
+ "@dxos/util": "0.8.4-main.effb148878"
41
38
  },
42
39
  "devDependencies": {
43
40
  "@types/react": "~19.2.7",
44
41
  "@types/react-dom": "~19.2.3",
45
42
  "react": "~19.2.3",
46
43
  "react-dom": "~19.2.3",
47
- "vite": "7.1.9",
48
- "@dxos/react-ui": "0.8.4-main.ef1bc66f44",
49
- "@dxos/storybook-utils": "0.8.4-main.ef1bc66f44",
50
- "@dxos/ui-theme": "0.8.4-main.ef1bc66f44",
51
- "@dxos/random": "0.8.4-main.ef1bc66f44"
44
+ "vite": "^8.0.14",
45
+ "@dxos/react-ui": "0.8.4-main.effb148878",
46
+ "@dxos/storybook-utils": "0.8.4-main.effb148878",
47
+ "@dxos/ui-theme": "0.8.4-main.effb148878",
48
+ "@dxos/random": "0.8.4-main.effb148878"
52
49
  },
53
50
  "peerDependencies": {
54
51
  "react": "~19.2.3",
55
52
  "react-dom": "~19.2.3",
56
- "@dxos/react-ui": "0.8.4-main.ef1bc66f44",
57
- "@dxos/ui-theme": "0.8.4-main.ef1bc66f44"
53
+ "@dxos/react-ui": "0.8.4-main.effb148878",
54
+ "@dxos/ui-theme": "0.8.4-main.effb148878"
58
55
  },
59
56
  "publishConfig": {
60
57
  "access": "public"
@@ -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 { faker } from '@dxos/random';
9
- import { Dialog, Icon } from '@dxos/react-ui';
10
- import { withTheme } from '@dxos/react-ui/testing';
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 as NaturalTabs } from './Tabs';
12
+ import { Tabs, TabsRootProps } from './Tabs';
13
13
 
14
- faker.seed(1234);
14
+ random.seed(1234);
15
15
 
16
- export const DefaultStory = () => {
16
+ const DefaultStory = ({ orientation }: TabsRootProps) => {
17
17
  return (
18
- <Dialog.Root open>
19
- <Dialog.Overlay blockAlign='start'>
20
- <Dialog.Content size='xl'>
21
- <NaturalTabs.Root orientation='vertical' defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
22
- <NaturalTabs.Viewport>
23
- <NaturalTabs.Tablist>
24
- {Object.entries(content).map(([id, { title }]) => {
25
- return (
26
- <NaturalTabs.Tab key={id} value={id}>
27
- {title}
28
- </NaturalTabs.Tab>
29
- );
30
- })}
31
- </NaturalTabs.Tablist>
32
- {Object.entries(content).map(([id, { panel }]) => {
33
- return (
34
- <NaturalTabs.Tabpanel key={id} value={id} classNames='m-1'>
35
- <NaturalTabs.BackButton density='fine'>
36
- <Icon icon='ph--arrow-left--bold' size={4} />
37
- <span>Back to tab list</span>
38
- </NaturalTabs.BackButton>
39
- <p className='pli-1'>{panel}</p>
40
- </NaturalTabs.Tabpanel>
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: faker.commerce.productName(),
54
- panel: faker.lorem.paragraphs(5),
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: NaturalTabs.Root,
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
- export const Default: Story = {};
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
@@ -24,7 +24,7 @@ import {
24
24
  useForwardedRef,
25
25
  } from '@dxos/react-ui';
26
26
  import { useAttention } from '@dxos/react-ui-attention';
27
- import { ghostSelectedContainerMd, mx } from '@dxos/ui-theme';
27
+ import { mx } from '@dxos/ui-theme';
28
28
 
29
29
  type TabsActivePart = 'list' | 'panel';
30
30
 
@@ -34,21 +34,21 @@ type TabsContextValue = {
34
34
  activePart: TabsActivePart;
35
35
  setActivePart: (nextActivePart: TabsActivePart) => void;
36
36
  attendableId?: string;
37
- verticalVariant?: 'stateful' | 'stateless';
38
37
  } & Pick<TabsPrimitive.TabsProps, 'orientation' | 'value'>;
39
38
 
40
39
  const [TabsContextProvider, useTabsContext] = createContext<TabsContextValue>(TABS_NAME, {
40
+ orientation: 'vertical',
41
41
  activePart: 'list',
42
42
  setActivePart: () => {},
43
- orientation: 'vertical',
44
43
  });
45
44
 
46
45
  type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
47
- Partial<Pick<TabsContextValue, 'activePart' | 'verticalVariant' | 'attendableId'>> &
48
- Partial<{
49
- onActivePartChange: (nextActivePart: TabsActivePart) => void;
50
- defaultActivePart: TabsActivePart;
51
- }>;
46
+ Partial<
47
+ Pick<TabsContextValue, 'activePart' | 'attendableId'> & {
48
+ onActivePartChange: (nextActivePart: TabsActivePart) => void;
49
+ defaultActivePart: TabsActivePart;
50
+ }
51
+ >;
52
52
 
53
53
  const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
54
54
  (
@@ -63,7 +63,6 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
63
63
  defaultValue,
64
64
  orientation = 'vertical',
65
65
  activationMode = 'manual',
66
- verticalVariant = 'stateful',
67
66
  attendableId,
68
67
  ...props
69
68
  },
@@ -110,22 +109,15 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
110
109
  setActivePart={setActivePart}
111
110
  value={value}
112
111
  attendableId={attendableId}
113
- verticalVariant={verticalVariant}
114
112
  >
115
113
  <TabsPrimitive.Root
114
+ {...props}
115
+ className={mx('overflow-hidden', classNames)}
116
+ orientation={orientation}
116
117
  activationMode={activationMode}
117
118
  data-active={activePart}
118
- orientation={orientation}
119
- {...props}
120
119
  value={value}
121
120
  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
121
  ref={tabsRoot}
130
122
  >
131
123
  {children}
@@ -135,76 +127,73 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
135
127
  },
136
128
  );
137
129
 
130
+ TabsRoot.displayName = 'Tabs.Root';
131
+
138
132
  type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
139
133
 
140
- const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
141
- const { orientation, activePart, verticalVariant } = useTabsContext('TabsViewport');
134
+ function TabsViewport({ classNames, children, ...props }: TabsViewportProps) {
135
+ const { activePart } = useTabsContext('TabsViewport');
142
136
  return (
143
- <div
144
- role='none'
145
- {...props}
146
- data-active={activePart}
147
- className={mx(
148
- orientation === 'vertical' &&
149
- verticalVariant === 'stateful' && [
150
- 'grid is-[200%] grid-cols-2 data-[active=panel]:mis-[-100%]',
151
- '@md:is-auto @md:data-[active=panel]:mis-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
152
- ],
153
- classNames,
154
- )}
155
- >
137
+ <div {...props} data-active={activePart} className={mx(classNames)}>
156
138
  {children}
157
139
  </div>
158
140
  );
159
- };
141
+ }
142
+
143
+ TabsViewport.displayName = 'Tabs.Viewport';
160
144
 
161
145
  type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
162
146
 
163
- const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
164
- const { orientation, verticalVariant } = useTabsContext('TabsTablist');
147
+ function TabsTablist({ children, classNames, ...props }: TabsTablistProps) {
148
+ const { orientation } = useTabsContext('TabsTablist');
165
149
  return (
166
150
  <TabsPrimitive.List
167
151
  {...props}
168
152
  data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
169
153
  className={mx(
170
- 'max-bs-full is-full',
171
- // NOTE: Padding should be common to Toolbar.
172
- orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto p-1 gap-1',
173
- orientation === 'vertical' && verticalVariant === 'stateful' && 'place-self-start p-1',
154
+ 'max-h-full w-full',
155
+ // TODO(burdon): Should be embeddable inside Toolbar (if horizontal).
156
+ orientation === 'vertical' ? 'overflow-y-auto' : 'flex p-1 gap-1 items-stretch justify-start overflow-x-auto',
174
157
  classNames,
175
158
  )}
176
159
  >
177
160
  {children}
178
161
  </TabsPrimitive.List>
179
162
  );
180
- };
163
+ }
164
+
165
+ TabsTablist.displayName = 'Tabs.Tablist';
181
166
 
182
- const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
167
+ function TabsBackButton({ onClick, classNames, ...props }: ButtonProps) {
183
168
  const { setActivePart } = useTabsContext('TabsBackButton');
184
169
  const handleClick = useCallback(
185
170
  (event: MouseEvent<HTMLButtonElement>) => {
186
171
  setActivePart('list');
187
172
  return onClick?.(event);
188
173
  },
189
- [onClick, setActivePart],
174
+ [setActivePart, onClick],
190
175
  );
191
176
 
192
- return <Button {...props} classNames={['is-full text-start @md:hidden mbe-2', classNames]} onClick={handleClick} />;
193
- };
177
+ return <Button {...props} classNames={['@md:hidden text-start', classNames]} onClick={handleClick} />;
178
+ }
179
+
180
+ TabsBackButton.displayName = 'Tabs.BackButton';
194
181
 
195
182
  type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
196
183
 
197
- const TabsTabGroupHeading = ({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) => {
184
+ function TabsTabGroupHeading({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) {
198
185
  return (
199
- <h2 {...props} className={mx('mlb-1 pli-2 text-sm text-unAccent', classNames)}>
186
+ <h2 {...props} className={mx('my-1 px-2 text-sm text-un-accent', classNames)}>
200
187
  {children}
201
188
  </h2>
202
189
  );
203
- };
190
+ }
191
+
192
+ TabsTabGroupHeading.displayName = 'Tabs.TabGroupHeading';
204
193
 
205
194
  type TabsTabProps = ButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
206
195
 
207
- const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProps) => {
196
+ function TabsTab({ value, classNames, children, onClick, ...props }: TabsTabProps) {
208
197
  const { setActivePart, orientation, value: contextValue, attendableId } = useTabsContext('TabsTab');
209
198
  const { hasAttention } = useAttention(attendableId);
210
199
 
@@ -220,27 +209,28 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
220
209
  return (
221
210
  <TabsPrimitive.Trigger value={value} asChild>
222
211
  <Button
223
- density='fine'
212
+ {...props}
224
213
  variant={
225
214
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
226
215
  }
227
- {...props}
228
- onClick={handleClick}
229
216
  classNames={[
230
- orientation === 'vertical' && 'block justify-start text-start is-full',
231
- orientation === 'vertical' && ghostSelectedContainerMd,
217
+ orientation === 'vertical' && 'block justify-start text-start w-full',
218
+ orientation === 'vertical' && 'dx-selected',
232
219
  classNames,
233
220
  ]}
221
+ onClick={handleClick}
234
222
  >
235
223
  {children}
236
224
  </Button>
237
225
  </TabsPrimitive.Trigger>
238
226
  );
239
- };
227
+ }
228
+
229
+ TabsTab.displayName = 'Tabs.Tab';
240
230
 
241
231
  type TabsIconTabProps = IconButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
242
232
 
243
- const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps) => {
233
+ function TabsIconTab({ value, classNames, onClick, ...props }: TabsIconTabProps) {
244
234
  const { setActivePart, orientation, value: contextValue, attendableId } = useTabsContext('TabsTab');
245
235
  const { hasAttention } = useAttention(attendableId);
246
236
 
@@ -256,34 +246,37 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
256
246
  return (
257
247
  <TabsPrimitive.Trigger value={value} asChild>
258
248
  <IconButton
259
- density='fine'
249
+ {...props}
260
250
  variant={
261
251
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
262
252
  }
263
- {...props}
264
- onClick={handleClick}
265
253
  classNames={[
266
- orientation === 'vertical' && 'justify-start text-start is-full',
267
- orientation === 'vertical' && ghostSelectedContainerMd,
254
+ orientation === 'vertical' && 'justify-start text-start w-full',
255
+ orientation === 'vertical' && 'dx-selected',
268
256
  classNames,
269
257
  ]}
258
+ onClick={handleClick}
270
259
  />
271
260
  </TabsPrimitive.Trigger>
272
261
  );
273
- };
262
+ }
263
+
264
+ TabsIconTab.displayName = 'Tabs.IconTab';
274
265
 
275
- type TabsTabpanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
266
+ type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
276
267
 
277
- const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
268
+ function TabsPanel({ classNames, children, ...props }: TabsPanelProps) {
278
269
  const { value: contextValue } = useTabsContext('TabsTab');
279
270
  return (
280
271
  <Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
281
- <TabsPrimitive.Content {...props} className={mx('dx-focus-ring-inset-over-all', classNames)}>
272
+ <TabsPrimitive.Content {...props} className={mx('p-0! dx-focus-ring-inset-over-all', classNames)}>
282
273
  {children}
283
274
  </TabsPrimitive.Content>
284
275
  </Activity>
285
276
  );
286
- };
277
+ }
278
+
279
+ TabsPanel.displayName = 'Tabs.Panel';
287
280
 
288
281
  type TabsTabPrimitiveProps = TabsPrimitive.TabsTriggerProps;
289
282
 
@@ -294,7 +287,7 @@ export const Tabs = {
294
287
  IconTab: TabsIconTab,
295
288
  TabPrimitive: TabsPrimitive.Trigger,
296
289
  TabGroupHeading: TabsTabGroupHeading,
297
- Tabpanel: TabsTabpanel,
290
+ Panel: TabsPanel,
298
291
  BackButton: TabsBackButton,
299
292
  Viewport: TabsViewport,
300
293
  };
@@ -306,6 +299,6 @@ export type {
306
299
  TabsTabProps,
307
300
  TabsTabPrimitiveProps,
308
301
  TabsTabGroupHeadingProps,
309
- TabsTabpanelProps,
302
+ TabsPanelProps,
310
303
  TabsViewportProps,
311
304
  };