@dxos/react-ui-tabs 0.8.4-main.8360d9e660 → 0.8.4-main.8baae0fced

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.8360d9e660",
3
+ "version": "0.8.4-main.8baae0fced",
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.8360d9e660",
40
- "@dxos/util": "0.8.4-main.8360d9e660"
36
+ "@dxos/react-ui-attention": "0.8.4-main.8baae0fced",
37
+ "@dxos/util": "0.8.4-main.8baae0fced"
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.11",
48
- "@dxos/random": "0.8.4-main.8360d9e660",
49
- "@dxos/ui-theme": "0.8.4-main.8360d9e660",
50
- "@dxos/storybook-utils": "0.8.4-main.8360d9e660",
51
- "@dxos/react-ui": "0.8.4-main.8360d9e660"
44
+ "vite": "^8.0.13",
45
+ "@dxos/random": "0.8.4-main.8baae0fced",
46
+ "@dxos/storybook-utils": "0.8.4-main.8baae0fced",
47
+ "@dxos/react-ui": "0.8.4-main.8baae0fced",
48
+ "@dxos/ui-theme": "0.8.4-main.8baae0fced"
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.8360d9e660",
57
- "@dxos/ui-theme": "0.8.4-main.8360d9e660"
53
+ "@dxos/react-ui": "0.8.4-main.8baae0fced",
54
+ "@dxos/ui-theme": "0.8.4-main.8baae0fced"
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='px-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
@@ -34,23 +34,22 @@ 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
- // TODO(burdon): Reconcile padding with Toolbar.
54
53
  const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
55
54
  (
56
55
  {
@@ -64,7 +63,6 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
64
63
  defaultValue,
65
64
  orientation = 'vertical',
66
65
  activationMode = 'manual',
67
- verticalVariant = 'stateful',
68
66
  attendableId,
69
67
  ...props
70
68
  },
@@ -111,22 +109,15 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
111
109
  setActivePart={setActivePart}
112
110
  value={value}
113
111
  attendableId={attendableId}
114
- verticalVariant={verticalVariant}
115
112
  >
116
113
  <TabsPrimitive.Root
114
+ {...props}
115
+ className={mx('overflow-hidden', classNames)}
116
+ orientation={orientation}
117
117
  activationMode={activationMode}
118
118
  data-active={activePart}
119
- orientation={orientation}
120
- {...props}
121
119
  value={value}
122
120
  onValueChange={handleValueChange}
123
- className={mx(
124
- 'overflow-hidden',
125
- orientation === 'vertical' &&
126
- verticalVariant === 'stateful' &&
127
- '[&[data-active=list]_[role=tabpanel]]:invisible @md:[&[data-active=list]_[role=tabpanel]]:visible',
128
- classNames,
129
- )}
130
121
  ref={tabsRoot}
131
122
  >
132
123
  {children}
@@ -139,21 +130,9 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
139
130
  type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
140
131
 
141
132
  const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
142
- const { orientation, activePart, verticalVariant } = useTabsContext('TabsViewport');
133
+ const { activePart } = useTabsContext('TabsViewport');
143
134
  return (
144
- <div
145
- role='none'
146
- {...props}
147
- data-active={activePart}
148
- className={mx(
149
- orientation === 'vertical' &&
150
- verticalVariant === 'stateful' && [
151
- 'grid w-[200%] grid-cols-2 data-[active=panel]:ms-[-100%]',
152
- '@md:w-auto @md:data-[active=panel]:ms-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
153
- ],
154
- classNames,
155
- )}
156
- >
135
+ <div {...props} data-active={activePart} className={mx(classNames)}>
157
136
  {children}
158
137
  </div>
159
138
  );
@@ -161,18 +140,16 @@ const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) =>
161
140
 
162
141
  type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
163
142
 
164
- // TODO(burdon): Should have same geometry as Toolbar.
165
143
  const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
166
- const { orientation, verticalVariant } = useTabsContext('TabsTablist');
144
+ const { orientation } = useTabsContext('TabsTablist');
167
145
  return (
168
146
  <TabsPrimitive.List
169
147
  {...props}
170
148
  data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
171
149
  className={mx(
172
150
  'max-h-full w-full',
173
- // NOTE: Padding should be common to Toolbar.
174
- orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto p-1 gap-1',
175
- orientation === 'vertical' && verticalVariant === 'stateful' && 'place-self-start p-1',
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',
176
153
  classNames,
177
154
  )}
178
155
  >
@@ -188,10 +165,10 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
188
165
  setActivePart('list');
189
166
  return onClick?.(event);
190
167
  },
191
- [onClick, setActivePart],
168
+ [setActivePart, onClick],
192
169
  );
193
170
 
194
- return <Button {...props} classNames={['w-full text-start @md:hidden mb-2', classNames]} onClick={handleClick} />;
171
+ return <Button {...props} classNames={['@md:hidden text-start', classNames]} onClick={handleClick} />;
195
172
  };
196
173
 
197
174
  type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
@@ -222,17 +199,16 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
222
199
  return (
223
200
  <TabsPrimitive.Trigger value={value} asChild>
224
201
  <Button
225
- density='fine'
202
+ {...props}
226
203
  variant={
227
204
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
228
205
  }
229
- {...props}
230
- onClick={handleClick}
231
206
  classNames={[
232
207
  orientation === 'vertical' && 'block justify-start text-start w-full',
233
208
  orientation === 'vertical' && 'dx-selected',
234
209
  classNames,
235
210
  ]}
211
+ onClick={handleClick}
236
212
  >
237
213
  {children}
238
214
  </Button>
@@ -258,29 +234,28 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
258
234
  return (
259
235
  <TabsPrimitive.Trigger value={value} asChild>
260
236
  <IconButton
261
- density='fine'
237
+ {...props}
262
238
  variant={
263
239
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
264
240
  }
265
- {...props}
266
- onClick={handleClick}
267
241
  classNames={[
268
242
  orientation === 'vertical' && 'justify-start text-start w-full',
269
243
  orientation === 'vertical' && 'dx-selected',
270
244
  classNames,
271
245
  ]}
246
+ onClick={handleClick}
272
247
  />
273
248
  </TabsPrimitive.Trigger>
274
249
  );
275
250
  };
276
251
 
277
- type TabsTabpanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
252
+ type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
278
253
 
279
- const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
254
+ const TabsPanel = ({ classNames, children, ...props }: TabsPanelProps) => {
280
255
  const { value: contextValue } = useTabsContext('TabsTab');
281
256
  return (
282
257
  <Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
283
- <TabsPrimitive.Content {...props} className={mx('dx-focus-ring-inset-over-all', classNames)}>
258
+ <TabsPrimitive.Content {...props} className={mx('p-0! dx-focus-ring-inset-over-all', classNames)}>
284
259
  {children}
285
260
  </TabsPrimitive.Content>
286
261
  </Activity>
@@ -296,7 +271,7 @@ export const Tabs = {
296
271
  IconTab: TabsIconTab,
297
272
  TabPrimitive: TabsPrimitive.Trigger,
298
273
  TabGroupHeading: TabsTabGroupHeading,
299
- Tabpanel: TabsTabpanel,
274
+ Panel: TabsPanel,
300
275
  BackButton: TabsBackButton,
301
276
  Viewport: TabsViewport,
302
277
  };
@@ -308,6 +283,6 @@ export type {
308
283
  TabsTabProps,
309
284
  TabsTabPrimitiveProps,
310
285
  TabsTabGroupHeadingProps,
311
- TabsTabpanelProps,
286
+ TabsPanelProps,
312
287
  TabsViewportProps,
313
288
  };