@dxos/react-ui-tabs 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5

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,15 +1,20 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-tabs",
3
- "version": "0.8.4-main.f9ba587",
3
+ "version": "0.8.4-main.fcfe5033a5",
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
  ".": {
17
+ "source": "./src/index.ts",
13
18
  "types": "./dist/types/src/index.d.ts",
14
19
  "browser": "./dist/lib/browser/index.mjs",
15
20
  "node": "./dist/lib/node-esm/index.mjs"
@@ -24,33 +29,32 @@
24
29
  "src"
25
30
  ],
26
31
  "dependencies": {
27
- "@fluentui/react-tabster": "^9.24.2",
28
- "@preact-signals/safe-react": "^0.9.0",
32
+ "@fluentui/react-tabster": "9.26.11",
29
33
  "@radix-ui/primitive": "1.1.1",
30
34
  "@radix-ui/react-context": "1.1.1",
31
35
  "@radix-ui/react-primitive": "2.0.2",
32
36
  "@radix-ui/react-slot": "1.1.2",
33
37
  "@radix-ui/react-tabs": "1.1.3",
34
38
  "@radix-ui/react-use-controllable-state": "1.1.0",
35
- "@dxos/react-ui-attention": "0.8.4-main.f9ba587",
36
- "@dxos/util": "0.8.4-main.f9ba587"
39
+ "@dxos/react-ui-attention": "0.8.4-main.fcfe5033a5",
40
+ "@dxos/util": "0.8.4-main.fcfe5033a5"
37
41
  },
38
42
  "devDependencies": {
39
- "@types/react": "~18.2.0",
40
- "@types/react-dom": "~18.2.0",
41
- "react": "~18.2.0",
42
- "react-dom": "~18.2.0",
43
- "vite": "5.4.7",
44
- "@dxos/random": "0.8.4-main.f9ba587",
45
- "@dxos/react-ui-theme": "0.8.4-main.f9ba587",
46
- "@dxos/storybook-utils": "0.8.4-main.f9ba587",
47
- "@dxos/react-ui": "0.8.4-main.f9ba587"
43
+ "@types/react": "~19.2.7",
44
+ "@types/react-dom": "~19.2.3",
45
+ "react": "~19.2.3",
46
+ "react-dom": "~19.2.3",
47
+ "vite": "^7.1.11",
48
+ "@dxos/random": "0.8.4-main.fcfe5033a5",
49
+ "@dxos/react-ui": "0.8.4-main.fcfe5033a5",
50
+ "@dxos/ui-theme": "0.8.4-main.fcfe5033a5",
51
+ "@dxos/storybook-utils": "0.8.4-main.fcfe5033a5"
48
52
  },
49
53
  "peerDependencies": {
50
- "react": "~18.2.0",
51
- "react-dom": "~18.2.0",
52
- "@dxos/react-ui": "0.8.4-main.f9ba587",
53
- "@dxos/react-ui-theme": "0.8.4-main.f9ba587"
54
+ "react": "~19.2.3",
55
+ "react-dom": "~19.2.3",
56
+ "@dxos/ui-theme": "0.8.4-main.fcfe5033a5",
57
+ "@dxos/react-ui": "0.8.4-main.fcfe5033a5"
54
58
  },
55
59
  "publishConfig": {
56
60
  "access": "public"
@@ -2,64 +2,76 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
5
6
  import React from 'react';
6
7
 
7
- import { faker } from '@dxos/random';
8
- import { Dialog, Icon } from '@dxos/react-ui';
9
- import { withTheme } from '@dxos/storybook-utils';
8
+ import { random } from '@dxos/random';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+ import { mx } from '@dxos/ui-theme';
10
11
 
11
- import { Tabs as NaturalTabs } from './Tabs';
12
+ import { Tabs, TabsRootProps } from './Tabs';
12
13
 
13
- faker.seed(1234);
14
+ random.seed(1234);
15
+
16
+ const DefaultStory = ({ orientation }: TabsRootProps) => {
17
+ return (
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>
41
+ );
42
+ };
14
43
 
15
44
  const content = [...Array(24)].reduce((acc: { [key: string]: { title: string; panel: string } }, _, index) => {
16
45
  acc[`t${index}`] = {
17
- title: faker.commerce.productName(),
18
- panel: faker.lorem.paragraphs(5),
46
+ title: random.commerce.productName(),
47
+ panel: random.lorem.paragraphs(5),
19
48
  };
20
49
  return acc;
21
50
  }, {});
22
51
 
23
- export default {
52
+ const meta = {
24
53
  title: 'ui/react-ui-tabs/Tabs',
25
- component: NaturalTabs.Root,
26
- decorators: [withTheme],
27
- // parameters: { translations },
54
+ component: Tabs.Root,
55
+ render: DefaultStory,
56
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
57
+ parameters: {
58
+ layout: 'fullscreen',
59
+ },
60
+ } satisfies Meta<typeof DefaultStory>;
61
+
62
+ export default meta;
63
+
64
+ type Story = StoryObj<typeof meta>;
65
+
66
+ // TODO(burdon): Scrolling.
67
+ export const Horizontal: Story = {
68
+ args: {
69
+ orientation: 'horizontal',
70
+ },
28
71
  };
29
72
 
30
- export const Tabs = {
31
- render: () => {
32
- return (
33
- <Dialog.Root open>
34
- <Dialog.Overlay blockAlign='start'>
35
- <Dialog.Content classNames='is-[calc(100dvw-4rem)] !max-is-full'>
36
- <NaturalTabs.Root orientation='vertical' defaultValue={Object.keys(content)[3]} defaultActivePart='list'>
37
- <NaturalTabs.Viewport>
38
- <NaturalTabs.Tablist>
39
- {Object.entries(content).map(([id, { title }]) => {
40
- return (
41
- <NaturalTabs.Tab key={id} value={id}>
42
- {title}
43
- </NaturalTabs.Tab>
44
- );
45
- })}
46
- </NaturalTabs.Tablist>
47
- {Object.entries(content).map(([id, { panel }]) => {
48
- return (
49
- <NaturalTabs.Tabpanel key={id} value={id} classNames='m-1'>
50
- <NaturalTabs.BackButton density='fine'>
51
- <Icon icon='ph--arrow-left--bold' size={4} />
52
- <span>Back to tab list</span>
53
- </NaturalTabs.BackButton>
54
- <p className='pli-1'>{panel}</p>
55
- </NaturalTabs.Tabpanel>
56
- );
57
- })}
58
- </NaturalTabs.Viewport>
59
- </NaturalTabs.Root>
60
- </Dialog.Content>
61
- </Dialog.Overlay>
62
- </Dialog.Root>
63
- );
73
+ export const Vertical: Story = {
74
+ args: {
75
+ orientation: 'vertical',
64
76
  },
65
77
  };
package/src/Tabs.tsx CHANGED
@@ -2,15 +2,29 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useFocusFinders, useArrowNavigationGroup, useFocusableGroup } from '@fluentui/react-tabster';
5
+ import { useArrowNavigationGroup, useFocusFinders, useFocusableGroup } from '@fluentui/react-tabster';
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';
9
+ import React, {
10
+ Activity,
11
+ type ComponentPropsWithoutRef,
12
+ type MouseEvent,
13
+ forwardRef,
14
+ useCallback,
15
+ useLayoutEffect,
16
+ } from 'react';
10
17
 
11
- import { Button, type ButtonProps, type ThemedClassName } from '@dxos/react-ui';
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 { ghostHover, ghostSelectedContainerMd, mx } from '@dxos/react-ui-theme';
27
+ import { mx } from '@dxos/ui-theme';
14
28
 
15
29
  type TabsActivePart = 'list' | 'panel';
16
30
 
@@ -20,118 +34,105 @@ type TabsContextValue = {
20
34
  activePart: TabsActivePart;
21
35
  setActivePart: (nextActivePart: TabsActivePart) => void;
22
36
  attendableId?: string;
23
- verticalVariant?: 'stateful' | 'stateless';
24
37
  } & Pick<TabsPrimitive.TabsProps, 'orientation' | 'value'>;
25
38
 
26
39
  const [TabsContextProvider, useTabsContext] = createContext<TabsContextValue>(TABS_NAME, {
40
+ orientation: 'vertical',
27
41
  activePart: 'list',
28
42
  setActivePart: () => {},
29
- orientation: 'vertical',
30
43
  });
31
44
 
32
45
  type TabsRootProps = ThemedClassName<TabsPrimitive.TabsProps> &
33
- Partial<Pick<TabsContextValue, 'activePart' | 'verticalVariant' | 'attendableId'>> &
34
- Partial<{
35
- onActivePartChange: (nextActivePart: TabsActivePart) => void;
36
- defaultActivePart: TabsActivePart;
37
- }>;
38
-
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);
46
+ Partial<
47
+ Pick<TabsContextValue, 'activePart' | 'attendableId'> & {
48
+ onActivePartChange: (nextActivePart: TabsActivePart) => void;
49
+ defaultActivePart: TabsActivePart;
50
+ }
51
+ >;
52
+
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
+ attendableId,
67
+ ...props
73
68
  },
74
- [value],
75
- );
69
+ forwardedRef,
70
+ ) => {
71
+ // const tabsRoot = useRef<HTMLDivElement | null>(null);
72
+ const tabsRoot = useForwardedRef(forwardedRef);
76
73
 
77
- const { findFirstFocusable } = useFocusFinders();
78
- const tabsRoot = useRef<HTMLDivElement | null>(null);
74
+ // TODO(thure): Without these, we get Groupper/Mover `API used before initialization`, but why?
75
+ const _1 = useArrowNavigationGroup();
76
+ const _2 = useFocusableGroup();
77
+ const [activePart = 'list', setActivePart] = useControllableState({
78
+ prop: propsActivePart,
79
+ onChange: onActivePartChange,
80
+ defaultProp: defaultActivePart,
81
+ });
79
82
 
80
- useLayoutEffect(() => {
81
- if (tabsRoot.current) {
82
- findFirstFocusable(tabsRoot.current)?.focus();
83
- }
84
- }, [activePart]);
83
+ const [value, setValue] = useControllableState({
84
+ prop: propsValue,
85
+ onChange: onValueChange,
86
+ defaultProp: defaultValue,
87
+ });
85
88
 
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}
89
+ const handleValueChange = useCallback(
90
+ (nextValue: string) => {
91
+ setActivePart('panel');
92
+ setValue(nextValue);
93
+ },
94
+ [value],
95
+ );
96
+
97
+ const { findFirstFocusable } = useFocusFinders();
98
+
99
+ useLayoutEffect(() => {
100
+ if (tabsRoot.current) {
101
+ findFirstFocusable(tabsRoot.current)?.focus();
102
+ }
103
+ }, [activePart]);
104
+
105
+ return (
106
+ <TabsContextProvider
98
107
  orientation={orientation}
99
- {...props}
108
+ activePart={activePart}
109
+ setActivePart={setActivePart}
100
110
  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}
111
+ attendableId={attendableId}
110
112
  >
111
- {children}
112
- </TabsPrimitive.Root>
113
- </TabsContextProvider>
114
- );
115
- };
113
+ <TabsPrimitive.Root
114
+ {...props}
115
+ className={mx('overflow-hidden', classNames)}
116
+ orientation={orientation}
117
+ activationMode={activationMode}
118
+ data-active={activePart}
119
+ value={value}
120
+ onValueChange={handleValueChange}
121
+ ref={tabsRoot}
122
+ >
123
+ {children}
124
+ </TabsPrimitive.Root>
125
+ </TabsContextProvider>
126
+ );
127
+ },
128
+ );
116
129
 
117
130
  type TabsViewportProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
118
131
 
119
132
  const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) => {
120
- const { orientation, activePart, verticalVariant } = useTabsContext('TabsViewport');
133
+ const { activePart } = useTabsContext('TabsViewport');
121
134
  return (
122
- <div
123
- role='none'
124
- {...props}
125
- data-active={activePart}
126
- className={mx(
127
- orientation === 'vertical' &&
128
- verticalVariant === 'stateful' && [
129
- 'grid is-[200%] grid-cols-2 data-[active=panel]:mis-[-100%]',
130
- '@md:is-auto @md:data-[active=panel]:mis-0 @md:grid-cols-[minmax(min-content,1fr)_3fr] @md:gap-1',
131
- ],
132
- classNames,
133
- )}
134
- >
135
+ <div role='none' {...props} data-active={activePart} className={mx(classNames)}>
135
136
  {children}
136
137
  </div>
137
138
  );
@@ -140,15 +141,15 @@ const TabsViewport = ({ classNames, children, ...props }: TabsViewportProps) =>
140
141
  type TabsTablistProps = ThemedClassName<TabsPrimitive.TabsListProps>;
141
142
 
142
143
  const TabsTablist = ({ children, classNames, ...props }: TabsTablistProps) => {
143
- const { orientation, verticalVariant } = useTabsContext('TabsTablist');
144
+ const { orientation } = useTabsContext('TabsTablist');
144
145
  return (
145
146
  <TabsPrimitive.List
146
147
  {...props}
148
+ data-arrow-keys={orientation === 'vertical' ? 'up down' : 'left right'}
147
149
  className={mx(
148
- 'max-bs-full is-full',
149
- // NOTE: Padding should be common to Toolbar.
150
- orientation === 'vertical' ? 'overflow-y-auto' : 'flex items-stretch justify-start overflow-x-auto p-1 gap-1',
151
- 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',
152
153
  classNames,
153
154
  )}
154
155
  >
@@ -164,17 +165,17 @@ const TabsBackButton = ({ onClick, classNames, ...props }: ButtonProps) => {
164
165
  setActivePart('list');
165
166
  return onClick?.(event);
166
167
  },
167
- [onClick, setActivePart],
168
+ [setActivePart, onClick],
168
169
  );
169
170
 
170
- 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} />;
171
172
  };
172
173
 
173
174
  type TabsTabGroupHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'h2'>>;
174
175
 
175
176
  const TabsTabGroupHeading = ({ children, classNames, ...props }: ThemedClassName<TabsTabGroupHeadingProps>) => {
176
177
  return (
177
- <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)}>
178
179
  {children}
179
180
  </h2>
180
181
  );
@@ -185,6 +186,7 @@ type TabsTabProps = ButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
185
186
  const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProps) => {
186
187
  const { setActivePart, orientation, value: contextValue, attendableId } = useTabsContext('TabsTab');
187
188
  const { hasAttention } = useAttention(attendableId);
189
+
188
190
  const handleClick = useCallback(
189
191
  // NOTE: This handler is only called if the tab is *already active*.
190
192
  (event: MouseEvent<HTMLButtonElement>) => {
@@ -197,19 +199,16 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
197
199
  return (
198
200
  <TabsPrimitive.Trigger value={value} asChild>
199
201
  <Button
200
- density='fine'
202
+ {...props}
201
203
  variant={
202
204
  orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
203
205
  }
204
- {...props}
205
- onClick={handleClick}
206
206
  classNames={[
207
- 'pli-2 rounded-sm',
208
- orientation === 'vertical' && 'block justify-start text-start is-full',
209
- orientation === 'vertical' && ghostSelectedContainerMd,
210
- ghostHover,
207
+ orientation === 'vertical' && 'block justify-start text-start w-full',
208
+ orientation === 'vertical' && 'dx-selected',
211
209
  classNames,
212
210
  ]}
211
+ onClick={handleClick}
213
212
  >
214
213
  {children}
215
214
  </Button>
@@ -217,13 +216,50 @@ const TabsTab = ({ value, classNames, children, onClick, ...props }: TabsTabProp
217
216
  );
218
217
  };
219
218
 
220
- type TabsTabpanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
219
+ type TabsIconTabProps = IconButtonProps & Pick<TabsPrimitive.TabsTriggerProps, 'value'>;
220
+
221
+ const TabsIconTab = ({ value, classNames, onClick, ...props }: TabsIconTabProps) => {
222
+ const { setActivePart, orientation, value: contextValue, attendableId } = useTabsContext('TabsTab');
223
+ const { hasAttention } = useAttention(attendableId);
224
+
225
+ // NOTE: This handler is only called if the tab is *already active*.
226
+ const handleClick = useCallback(
227
+ (event: MouseEvent<HTMLButtonElement>) => {
228
+ setActivePart('panel');
229
+ onClick?.(event);
230
+ },
231
+ [setActivePart, onClick],
232
+ );
221
233
 
222
- const TabsTabpanel = ({ classNames, children, ...props }: TabsTabpanelProps) => {
223
234
  return (
224
- <TabsPrimitive.Content {...props} className={mx('dx-focus-ring-inset-over-all', classNames)}>
225
- {children}
226
- </TabsPrimitive.Content>
235
+ <TabsPrimitive.Trigger value={value} asChild>
236
+ <IconButton
237
+ {...props}
238
+ variant={
239
+ orientation === 'horizontal' && contextValue === value ? (hasAttention ? 'primary' : 'default') : 'ghost'
240
+ }
241
+ classNames={[
242
+ orientation === 'vertical' && 'justify-start text-start w-full',
243
+ orientation === 'vertical' && 'dx-selected',
244
+ classNames,
245
+ ]}
246
+ onClick={handleClick}
247
+ />
248
+ </TabsPrimitive.Trigger>
249
+ );
250
+ };
251
+
252
+ type TabsPanelProps = ThemedClassName<TabsPrimitive.TabsContentProps>;
253
+
254
+ // TODO(burdon): Make slottable.
255
+ const TabsPanel = ({ classNames, children, ...props }: TabsPanelProps) => {
256
+ const { value: contextValue } = useTabsContext('TabsTab');
257
+ return (
258
+ <Activity mode={contextValue === props.value ? 'visible' : 'hidden'}>
259
+ <TabsPrimitive.Content {...props} className={mx('p-0! dx-focus-ring-inset-over-all', classNames)}>
260
+ {children}
261
+ </TabsPrimitive.Content>
262
+ </Activity>
227
263
  );
228
264
  };
229
265
 
@@ -233,9 +269,10 @@ export const Tabs = {
233
269
  Root: TabsRoot,
234
270
  Tablist: TabsTablist,
235
271
  Tab: TabsTab,
272
+ IconTab: TabsIconTab,
236
273
  TabPrimitive: TabsPrimitive.Trigger,
237
274
  TabGroupHeading: TabsTabGroupHeading,
238
- Tabpanel: TabsTabpanel,
275
+ Panel: TabsPanel,
239
276
  BackButton: TabsBackButton,
240
277
  Viewport: TabsViewport,
241
278
  };
@@ -247,6 +284,6 @@ export type {
247
284
  TabsTabProps,
248
285
  TabsTabPrimitiveProps,
249
286
  TabsTabGroupHeadingProps,
250
- TabsTabpanelProps,
287
+ TabsPanelProps,
251
288
  TabsViewportProps,
252
289
  };