@dxos/react-ui-tabs 0.8.4-main.937b3ca → 0.8.4-main.9be5663bfe

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.937b3ca",
3
+ "version": "0.8.4-main.9be5663bfe",
4
4
  "description": "Components for facilitating a Tabs pattern.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -36,25 +36,25 @@
36
36
  "@radix-ui/react-slot": "1.1.2",
37
37
  "@radix-ui/react-tabs": "1.1.3",
38
38
  "@radix-ui/react-use-controllable-state": "1.1.0",
39
- "@dxos/react-ui-attention": "0.8.4-main.937b3ca",
40
- "@dxos/util": "0.8.4-main.937b3ca"
39
+ "@dxos/react-ui-attention": "0.8.4-main.9be5663bfe",
40
+ "@dxos/util": "0.8.4-main.9be5663bfe"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/react": "~19.2.7",
44
44
  "@types/react-dom": "~19.2.3",
45
45
  "react": "~19.2.3",
46
46
  "react-dom": "~19.2.3",
47
- "vite": "7.1.9",
48
- "@dxos/random": "0.8.4-main.937b3ca",
49
- "@dxos/react-ui": "0.8.4-main.937b3ca",
50
- "@dxos/storybook-utils": "0.8.4-main.937b3ca",
51
- "@dxos/ui-theme": "0.8.4-main.937b3ca"
47
+ "vite": "^7.1.11",
48
+ "@dxos/react-ui": "0.8.4-main.9be5663bfe",
49
+ "@dxos/storybook-utils": "0.8.4-main.9be5663bfe",
50
+ "@dxos/random": "0.8.4-main.9be5663bfe",
51
+ "@dxos/ui-theme": "0.8.4-main.9be5663bfe"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "react": "~19.2.3",
55
55
  "react-dom": "~19.2.3",
56
- "@dxos/ui-theme": "0.8.4-main.937b3ca",
57
- "@dxos/react-ui": "0.8.4-main.937b3ca"
56
+ "@dxos/ui-theme": "0.8.4-main.9be5663bfe",
57
+ "@dxos/react-ui": "0.8.4-main.9be5663bfe"
58
58
  },
59
59
  "publishConfig": {
60
60
  "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}
@@ -138,21 +130,9 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
138
130
  type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
139
131
 
140
132
  const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
141
- const { orientation, activePart, verticalVariant } = useTabsContext('TabsViewport');
133
+ const { activePart } = useTabsContext('TabsViewport');
142
134
  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
- >
135
+ <div role='none' {...props} data-active={activePart} className={mx(classNames)}>
156
136
  {children}
157
137
  </div>
158
138
  );
@@ -161,16 +141,15 @@ const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) =>
161
141
  type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
162
142
 
163
143
  const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
164
- const { orientation, verticalVariant } = useTabsContext('TabsTablist');
144
+ const { orientation } = useTabsContext('TabsTablist');
165
145
  return (
166
146
  <TabsPrimitive.List
167
147
  {...props}
168
148
  data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
169
149
  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',
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',
174
153
  classNames,
175
154
  )}
176
155
  >
@@ -186,17 +165,17 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
186
165
  setActivePart('list');
187
166
  return onClick?.(event);
188
167
  },
189
- [onClick, setActivePart],
168
+ [setActivePart, onClick],
190
169
  );
191
170
 
192
- return <Button {...props} classNames={['is-full text-start @md:hidden mbe-2', classNames]} onClick={handleClick} />;
171
+ return <Button {...props} classNames={['@md:hidden text-start', classNames]} onClick={handleClick} />;
193
172
  };
194
173
 
195
174
  type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
196
175
 
197
176
  const TabsTabGroupHeading = ({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) => {
198
177
  return (
199
- <h2 {...props} className={mx('mlb-1 pli-2 text-sm text-unAccent', classNames)}>
178
+ <h2 {...props} className={mx('my-1 px-2 text-sm text-un-accent', classNames)}>
200
179
  {children}
201
180
  </h2>
202
181
  );
@@ -220,17 +199,16 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
220
199
  return (
221
200
  <TabsPrimitive.Trigger value={value} asChild>
222
201
  <Button
223
- density='fine'
202
+ {...props}
224
203
  variant={
225
204
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
226
205
  }
227
- {...props}
228
- onClick={handleClick}
229
206
  classNames={[
230
- orientation === 'vertical' && 'block justify-start text-start is-full',
231
- orientation === 'vertical' && ghostSelectedContainerMd,
207
+ orientation === 'vertical' && 'block justify-start text-start w-full',
208
+ orientation === 'vertical' && 'dx-selected',
232
209
  classNames,
233
210
  ]}
211
+ onClick={handleClick}
234
212
  >
235
213
  {children}
236
214
  </Button>
@@ -256,25 +234,25 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
256
234
  return (
257
235
  <TabsPrimitive.Trigger value={value} asChild>
258
236
  <IconButton
259
- density='fine'
237
+ {...props}
260
238
  variant={
261
239
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
262
240
  }
263
- {...props}
264
- onClick={handleClick}
265
241
  classNames={[
266
- orientation === 'vertical' && 'justify-start text-start is-full',
267
- orientation === 'vertical' && ghostSelectedContainerMd,
242
+ orientation === 'vertical' && 'justify-start text-start w-full',
243
+ orientation === 'vertical' && 'dx-selected',
268
244
  classNames,
269
245
  ]}
246
+ onClick={handleClick}
270
247
  />
271
248
  </TabsPrimitive.Trigger>
272
249
  );
273
250
  };
274
251
 
275
- type TabsTabpanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
252
+ type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
276
253
 
277
- const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
254
+ // TODO(burdon): Make slottable.
255
+ const TabsPanel = ({ classNames, children, ...props }: TabsPanelProps) => {
278
256
  const { value: contextValue } = useTabsContext('TabsTab');
279
257
  return (
280
258
  <Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
@@ -294,7 +272,7 @@ export const Tabs = {
294
272
  IconTab: TabsIconTab,
295
273
  TabPrimitive: TabsPrimitive.Trigger,
296
274
  TabGroupHeading: TabsTabGroupHeading,
297
- Tabpanel: TabsTabpanel,
275
+ Panel: TabsPanel,
298
276
  BackButton: TabsBackButton,
299
277
  Viewport: TabsViewport,
300
278
  };
@@ -306,6 +284,6 @@ export type {
306
284
  TabsTabProps,
307
285
  TabsTabPrimitiveProps,
308
286
  TabsTabGroupHeadingProps,
309
- TabsTabpanelProps,
287
+ TabsPanelProps,
310
288
  TabsViewportProps,
311
289
  };