@dxos/react-ui-tabs 0.8.4-main.fffef41 → 0.9.0

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.fffef41",
3
+ "version": "0.9.0",
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
- "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -17,41 +21,37 @@
17
21
  }
18
22
  },
19
23
  "types": "dist/types/src/index.d.ts",
20
- "typesVersions": {
21
- "*": {}
22
- },
23
24
  "files": [
24
25
  "dist",
25
26
  "src"
26
27
  ],
27
28
  "dependencies": {
28
- "@fluentui/react-tabster": "^9.24.2",
29
- "@preact-signals/safe-react": "^0.9.0",
29
+ "@fluentui/react-tabster": "9.26.11",
30
30
  "@radix-ui/primitive": "1.1.1",
31
31
  "@radix-ui/react-context": "1.1.1",
32
32
  "@radix-ui/react-primitive": "2.0.2",
33
33
  "@radix-ui/react-slot": "1.1.2",
34
34
  "@radix-ui/react-tabs": "1.1.3",
35
35
  "@radix-ui/react-use-controllable-state": "1.1.0",
36
- "@dxos/react-ui-attention": "0.8.4-main.fffef41",
37
- "@dxos/util": "0.8.4-main.fffef41"
36
+ "@dxos/react-ui-attention": "0.9.0",
37
+ "@dxos/util": "0.9.0"
38
38
  },
39
39
  "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",
44
- "vite": "7.1.9",
45
- "@dxos/random": "0.8.4-main.fffef41",
46
- "@dxos/react-ui": "0.8.4-main.fffef41",
47
- "@dxos/react-ui-theme": "0.8.4-main.fffef41",
48
- "@dxos/storybook-utils": "0.8.4-main.fffef41"
40
+ "@types/react": "~19.2.7",
41
+ "@types/react-dom": "~19.2.3",
42
+ "react": "~19.2.3",
43
+ "react-dom": "~19.2.3",
44
+ "vite": "^8.0.16",
45
+ "@dxos/react-ui": "0.9.0",
46
+ "@dxos/random": "0.9.0",
47
+ "@dxos/ui-theme": "0.9.0",
48
+ "@dxos/storybook-utils": "0.9.0"
49
49
  },
50
50
  "peerDependencies": {
51
- "react": "^19.0.0",
52
- "react-dom": "^19.0.0",
53
- "@dxos/react-ui": "0.8.4-main.fffef41",
54
- "@dxos/react-ui-theme": "0.8.4-main.fffef41"
51
+ "react": "~19.2.3",
52
+ "react-dom": "~19.2.3",
53
+ "@dxos/react-ui": "0.9.0",
54
+ "@dxos/ui-theme": "0.9.0"
55
55
  },
56
56
  "publishConfig": {
57
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 classNames='is-[calc(100dvw-4rem)] !max-is-full'>
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
@@ -6,157 +6,164 @@ 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, {
10
- Activity,
11
- type ComponentPropsWithoutRef,
12
- type MouseEvent,
13
- useCallback,
14
- useLayoutEffect,
15
- useRef,
16
- } from 'react';
17
-
18
- import { Button, type ButtonProps, IconButton, type IconButtonProps, type ThemedClassName } from '@dxos/react-ui';
9
+ import React, { type ComponentPropsWithoutRef, type MouseEvent, forwardRef, useCallback, useLayoutEffect } from 'react';
10
+
11
+ import {
12
+ Button,
13
+ type ButtonProps,
14
+ IconButton,
15
+ type IconButtonProps,
16
+ type ThemedClassName,
17
+ useForwardedRef,
18
+ } from '@dxos/react-ui';
19
19
  import { useAttention } from '@dxos/react-ui-attention';
20
- import { ghostSelectedContainerMd, mx } from '@dxos/react-ui-theme';
20
+ import { mx } from '@dxos/ui-theme';
21
+
22
+ // TODO(burdon): Move to @dxos/react-ui.
21
23
 
22
24
  type TabsActivePart = 'list' | 'panel';
23
25
 
24
26
  const TABS_NAME = 'Tabs';
25
27
 
28
+ //
29
+ // Context
30
+ //
31
+
26
32
  type TabsContextValue = {
27
33
  activePart: TabsActivePart;
28
34
  setActivePart: (nextActivePart: TabsActivePart) => void;
29
35
  attendableId?: string;
30
- verticalVariant?: 'stateful' | 'stateless';
31
36
  } & Pick<TabsPrimitive.TabsProps, 'orientation' | 'value'>;
32
37
 
33
38
  const [TabsContextProvider, useTabsContext] = createContext<TabsContextValue>(TABS_NAME, {
39
+ orientation: 'vertical',
34
40
  activePart: 'list',
35
41
  setActivePart: () => {},
36
- orientation: 'vertical',
37
42
  });
38
43
 
39
- type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
40
- Partial<Pick<TabsContextValue, 'activePart' | 'verticalVariant' | 'attendableId'>> &
41
- Partial<{
42
- onActivePartChange: (nextActivePart: TabsActivePart) => void;
43
- defaultActivePart: TabsActivePart;
44
- }>;
45
-
46
- const TabsRoot = ({
47
- children,
48
- classNames,
49
- activePart: propsActivePart,
50
- onActivePartChange,
51
- defaultActivePart,
52
- value: propsValue,
53
- onValueChange,
54
- defaultValue,
55
- orientation = 'vertical',
56
- activationMode = 'manual',
57
- verticalVariant = 'stateful',
58
- attendableId,
59
- ...props
60
- }: TabsRootProps) => {
61
- // TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
62
- const _1 = useArrowNavigationGroup();
63
- const _2 = useFocusableGroup();
64
- const [activePart = 'list', setActivePart] = useControllableState({
65
- prop: propsActivePart,
66
- onChange: onActivePartChange,
67
- defaultProp: defaultActivePart,
68
- });
69
-
70
- const [value, setValue] = useControllableState({
71
- prop: propsValue,
72
- onChange: onValueChange,
73
- defaultProp: defaultValue,
74
- });
75
-
76
- const handleValueChange = useCallback(
77
- (nextValue: string) => {
78
- setActivePart('panel');
79
- setValue(nextValue);
80
- },
81
- [value],
82
- );
83
-
84
- const { findFirstFocusable } = useFocusFinders();
85
- const tabsRoot = useRef<HTMLDivElement | null>(null);
44
+ //
45
+ // Root
46
+ //
86
47
 
87
- useLayoutEffect(() => {
88
- if (tabsRoot.current) {
89
- findFirstFocusable(tabsRoot.current)?.focus();
48
+ type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
49
+ Partial<
50
+ Pick<TabsContextValue, 'activePart' | 'attendableId'> & {
51
+ onActivePartChange: (nextActivePart: TabsActivePart) => void;
52
+ defaultActivePart: TabsActivePart;
90
53
  }
91
- }, [activePart]);
92
-
93
- return (
94
- <TabsContextProvider
95
- orientation={orientation}
96
- activePart={activePart}
97
- setActivePart={setActivePart}
98
- value={value}
99
- attendableId={attendableId}
100
- verticalVariant={verticalVariant}
101
- >
102
- <TabsPrimitive.Root
103
- activationMode={activationMode}
104
- data-active={activePart}
54
+ >;
55
+
56
+ const TabsRoot = forwardRef<HTMLDivElement, TabsRootProps>(
57
+ (
58
+ {
59
+ children,
60
+ classNames,
61
+ activePart: propsActivePart,
62
+ onActivePartChange,
63
+ defaultActivePart,
64
+ value: propsValue,
65
+ onValueChange,
66
+ defaultValue,
67
+ orientation = 'vertical',
68
+ activationMode = 'manual',
69
+ attendableId,
70
+ ...props
71
+ },
72
+ forwardedRef,
73
+ ) => {
74
+ const tabsRoot = useForwardedRef(forwardedRef);
75
+
76
+ // TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
77
+ const _1 = useArrowNavigationGroup();
78
+ const _2 = useFocusableGroup();
79
+ const [activePart = 'list', setActivePart] = useControllableState({
80
+ prop: propsActivePart,
81
+ onChange: onActivePartChange,
82
+ defaultProp: defaultActivePart,
83
+ });
84
+
85
+ const [value, setValue] = useControllableState({
86
+ prop: propsValue,
87
+ onChange: onValueChange,
88
+ defaultProp: defaultValue,
89
+ });
90
+
91
+ const handleValueChange = useCallback(
92
+ (nextValue: string) => {
93
+ setActivePart('panel');
94
+ setValue(nextValue);
95
+ },
96
+ [value],
97
+ );
98
+
99
+ const { findFirstFocusable } = useFocusFinders();
100
+
101
+ useLayoutEffect(() => {
102
+ if (tabsRoot.current) {
103
+ findFirstFocusable(tabsRoot.current)?.focus();
104
+ }
105
+ }, [activePart]);
106
+
107
+ return (
108
+ <TabsContextProvider
105
109
  orientation={orientation}
106
- {...props}
110
+ activePart={activePart}
111
+ setActivePart={setActivePart}
107
112
  value={value}
108
- onValueChange={handleValueChange}
109
- className={mx(
110
- 'overflow-hidden',
111
- orientation === 'vertical' &&
112
- verticalVariant === 'stateful' &&
113
- '[&[data-active=list]_[role=tabpanel]]:invisible @md:[&[data-active=list]_[role=tabpanel]]:visible',
114
- classNames,
115
- )}
116
- ref={tabsRoot}
113
+ attendableId={attendableId}
117
114
  >
118
- {children}
119
- </TabsPrimitive.Root>
120
- </TabsContextProvider>
121
- );
122
- };
115
+ <TabsPrimitive.Root
116
+ {...props}
117
+ className={mx('overflow-hidden', classNames)}
118
+ orientation={orientation}
119
+ activationMode={activationMode}
120
+ data-active={activePart}
121
+ value={value}
122
+ onValueChange={handleValueChange}
123
+ ref={tabsRoot}
124
+ >
125
+ {children}
126
+ </TabsPrimitive.Root>
127
+ </TabsContextProvider>
128
+ );
129
+ },
130
+ );
131
+
132
+ TabsRoot.displayName = 'Tabs.Root';
133
+
134
+ //
135
+ // Viewport
136
+ //
123
137
 
124
138
  type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
125
139
 
126
140
  const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
127
- const { orientation, activePart, verticalVariant } = useTabsContext('TabsViewport');
141
+ const { activePart } = useTabsContext('TabsViewport');
128
142
  return (
129
- <div
130
- role='none'
131
- {...props}
132
- data-active={activePart}
133
- className={mx(
134
- orientation === 'vertical' &&
135
- verticalVariant === 'stateful' && [
136
- 'grid is-[200%] grid-cols-2 data-[active=panel]:mis-[-100%]',
137
- '@md:is-auto @md:data-[active=panel]:mis-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
138
- ],
139
- classNames,
140
- )}
141
- >
143
+ <div {...props} data-active={activePart} className={mx(classNames)}>
142
144
  {children}
143
145
  </div>
144
146
  );
145
147
  };
146
148
 
149
+ TabsViewport.displayName = 'Tabs.Viewport';
150
+
151
+ //
152
+ // Tablist
153
+ //
154
+
147
155
  type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
148
156
 
149
157
  const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
150
- const { orientation, verticalVariant } = useTabsContext('TabsTablist');
158
+ const { orientation } = useTabsContext('TabsTablist');
151
159
  return (
152
160
  <TabsPrimitive.List
153
161
  {...props}
154
162
  data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
155
163
  className={mx(
156
- 'max-bs-full is-full',
157
- // NOTE: Padding should be common to Toolbar.
158
- orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto p-1 gap-1',
159
- orientation === 'vertical' && verticalVariant === 'stateful' && 'place-self-start p-1',
164
+ 'max-h-full w-full',
165
+ // TODO(burdon): Should be embeddable inside Toolbar (if horizontal).
166
+ orientation === 'vertical' ? 'overflow-y-auto' : 'flex p-1 gap-1 items-stretch justify-start overflow-x-auto',
160
167
  classNames,
161
168
  )}
162
169
  >
@@ -165,6 +172,12 @@ const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
165
172
  );
166
173
  };
167
174
 
175
+ TabsTablist.displayName = 'Tabs.Tablist';
176
+
177
+ //
178
+ // BackButton
179
+ //
180
+
168
181
  const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
169
182
  const { setActivePart } = useTabsContext('TabsBackButton');
170
183
  const handleClick = useCallback(
@@ -172,21 +185,31 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
172
185
  setActivePart('list');
173
186
  return onClick?.(event);
174
187
  },
175
- [onClick, setActivePart],
188
+ [setActivePart, onClick],
176
189
  );
177
190
 
178
- return <Button {...props} classNames={['is-full text-start @md:hidden mbe-2', classNames]} onClick={handleClick} />;
191
+ return <Button {...props} classNames={['@md:hidden text-start', classNames]} onClick={handleClick} />;
179
192
  };
180
193
 
194
+ TabsBackButton.displayName = 'Tabs.BackButton';
195
+
196
+ //
197
+ // TabGroupHeading
198
+ //
199
+
181
200
  type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
182
201
 
183
- const TabsTabGroupHeading = ({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) => {
184
- return (
185
- <h2 {...props} className={mx('mlb-1 pli-2 text-sm text-unAccent', classNames)}>
186
- {children}
187
- </h2>
188
- );
189
- };
202
+ const TabsTabGroupHeading = ({ children, classNames, ...props }: TabsTabGroupHeadingProps) => (
203
+ <h2 {...props} className={mx('my-1 px-2 text-sm text-un-accent', classNames)}>
204
+ {children}
205
+ </h2>
206
+ );
207
+
208
+ TabsTabGroupHeading.displayName = 'Tabs.TabGroupHeading';
209
+
210
+ //
211
+ // Tab
212
+ //
190
213
 
191
214
  type TabsTabProps = ButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
192
215
 
@@ -206,17 +229,16 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
206
229
  return (
207
230
  <TabsPrimitive.Trigger value={value} asChild>
208
231
  <Button
209
- density='fine'
232
+ {...props}
210
233
  variant={
211
234
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
212
235
  }
213
- {...props}
214
- onClick={handleClick}
215
236
  classNames={[
216
- orientation === 'vertical' && 'block justify-start text-start is-full',
217
- orientation === 'vertical' && ghostSelectedContainerMd,
237
+ orientation === 'vertical' && 'block justify-start text-start w-full',
238
+ orientation === 'vertical' && 'dx-selected',
218
239
  classNames,
219
240
  ]}
241
+ onClick={handleClick}
220
242
  >
221
243
  {children}
222
244
  </Button>
@@ -224,6 +246,12 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
224
246
  );
225
247
  };
226
248
 
249
+ TabsTab.displayName = 'Tabs.Tab';
250
+
251
+ //
252
+ // IconTab
253
+ //
254
+
227
255
  type TabsIconTabProps = IconButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
228
256
 
229
257
  const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps) => {
@@ -242,37 +270,49 @@ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps)
242
270
  return (
243
271
  <TabsPrimitive.Trigger value={value} asChild>
244
272
  <IconButton
245
- density='fine'
273
+ {...props}
246
274
  variant={
247
275
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
248
276
  }
249
- {...props}
250
- onClick={handleClick}
251
277
  classNames={[
252
- orientation === 'vertical' && 'justify-start text-start is-full',
253
- orientation === 'vertical' && ghostSelectedContainerMd,
278
+ orientation === 'vertical' && 'justify-start text-start w-full',
279
+ orientation === 'vertical' && 'dx-selected',
254
280
  classNames,
255
281
  ]}
282
+ onClick={handleClick}
256
283
  />
257
284
  </TabsPrimitive.Trigger>
258
285
  );
259
286
  };
260
287
 
261
- type TabsTabpanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
288
+ TabsIconTab.displayName = 'Tabs.IconTab';
262
289
 
263
- const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
264
- const { value: contextValue } = useTabsContext('TabsTab');
265
- return (
266
- <Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
267
- <TabsPrimitive.Content {...props} className={mx('dx-focus-ring-inset-over-all', classNames)}>
268
- {children}
269
- </TabsPrimitive.Content>
270
- </Activity>
271
- );
272
- };
290
+ //
291
+ // Panel
292
+ //
293
+ // Do NOT wrap TabsPanel children in React.Activity.
294
+ // Radix TabsPrimitive.Content already unmounts inactive panels (no forceMount) — inactive tab
295
+ // content is not in the DOM and effects do not run, which is the desired behaviour.
296
+ // React.Activity (experimental in React 19) is a reconciler-level symbol that deactivates its
297
+ // subtree when mode='hidden'. It was redundant here and prevented initial render of active panels.
298
+ //
299
+
300
+ type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
301
+
302
+ const TabsPanel = ({ classNames, children, ...props }: TabsPanelProps) => (
303
+ <TabsPrimitive.Content {...props} className={mx('p-0! dx-focus-ring-inset-over-all', classNames)}>
304
+ {children}
305
+ </TabsPrimitive.Content>
306
+ );
307
+
308
+ TabsPanel.displayName = 'Tabs.Panel';
273
309
 
274
310
  type TabsTabPrimitiveProps = TabsPrimitive.TabsTriggerProps;
275
311
 
312
+ //
313
+ // Tabs
314
+ //
315
+
276
316
  export const Tabs = {
277
317
  Root: TabsRoot,
278
318
  Tablist: TabsTablist,
@@ -280,9 +320,9 @@ export const Tabs = {
280
320
  IconTab: TabsIconTab,
281
321
  TabPrimitive: TabsPrimitive.Trigger,
282
322
  TabGroupHeading: TabsTabGroupHeading,
283
- Tabpanel: TabsTabpanel,
284
- BackButton: TabsBackButton,
285
323
  Viewport: TabsViewport,
324
+ Panel: TabsPanel,
325
+ BackButton: TabsBackButton,
286
326
  };
287
327
 
288
328
  export type {
@@ -292,6 +332,6 @@ export type {
292
332
  TabsTabProps,
293
333
  TabsTabPrimitiveProps,
294
334
  TabsTabGroupHeadingProps,
295
- TabsTabpanelProps,
296
335
  TabsViewportProps,
336
+ TabsPanelProps,
297
337
  };