@12-apps/ui 1.5.0 → 1.5.2

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.
Files changed (229) hide show
  1. package/package.json +17 -9
  2. package/src/button/index.ts +1 -0
  3. package/src/social-login-button/index.ts +1 -0
  4. package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
  5. package/src/user-avatar/index.ts +1 -0
  6. package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
  7. package/eslint.config.js +0 -89
  8. package/src/__tests__/data-grid.test.tsx +0 -125
  9. package/src/__tests__/package-wiring.test.ts +0 -41
  10. package/src/__tests__/setup.ts +0 -1
  11. package/src/__tests__/social-login-button.test.tsx +0 -186
  12. package/src/__tests__/user-avatar.test.tsx +0 -195
  13. package/src/__tests__/viewport.test.ts +0 -50
  14. package/src/components/command/__tests__/command.test.tsx +0 -112
  15. package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
  16. package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
  17. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
  18. package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -763
  19. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
  20. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
  21. package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
  22. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
  23. package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
  24. package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
  25. package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
  26. package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -511
  27. package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
  28. package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -701
  29. package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
  30. package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
  31. package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
  32. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
  33. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
  34. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
  35. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
  36. package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
  37. package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
  38. package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
  39. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
  40. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
  41. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
  42. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
  43. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
  44. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
  45. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
  46. package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
  47. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
  48. package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
  49. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
  50. package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
  51. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
  52. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
  53. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
  54. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
  55. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
  56. package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
  57. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
  58. package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
  59. package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
  60. package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
  61. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
  62. package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
  63. package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
  64. package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
  65. package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
  66. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
  67. package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
  68. package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
  69. package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1105
  70. package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
  71. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
  72. package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
  73. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
  74. package/src/components/data-display/Table/Table.stories.tsx +0 -803
  75. package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
  76. package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
  77. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
  78. package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
  79. package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
  80. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
  81. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
  82. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
  83. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
  84. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
  85. package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
  86. package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -964
  87. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
  88. package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
  89. package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
  90. package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
  91. package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
  92. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
  93. package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -973
  94. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
  95. package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
  96. package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
  97. package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
  98. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
  99. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
  100. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
  101. package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
  102. package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
  103. package/src/components/form/Button/Button.stories.tsx +0 -365
  104. package/src/components/form/Button/Button.test.stories.tsx +0 -607
  105. package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
  106. package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
  107. package/src/components/form/CepField/CepField.stories.tsx +0 -138
  108. package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
  109. package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
  110. package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
  111. package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
  112. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
  113. package/src/components/form/Command/Command.stories.tsx +0 -580
  114. package/src/components/form/Command/Command.test.stories.tsx +0 -215
  115. package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
  116. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
  117. package/src/components/form/Form/Form.stories.tsx +0 -687
  118. package/src/components/form/Form/Form.test.stories.tsx +0 -601
  119. package/src/components/form/Input/Input.stories.tsx +0 -260
  120. package/src/components/form/Input/Input.test.stories.tsx +0 -1191
  121. package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
  122. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
  123. package/src/components/form/Label/Label.stories.tsx +0 -473
  124. package/src/components/form/Label/Label.test.stories.tsx +0 -915
  125. package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
  126. package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
  127. package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
  128. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
  129. package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
  130. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
  131. package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
  132. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
  133. package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
  134. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
  135. package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
  136. package/src/components/form/Select/Select.stories.tsx +0 -364
  137. package/src/components/form/Select/Select.test.stories.tsx +0 -985
  138. package/src/components/form/Slider/Slider.stories.tsx +0 -418
  139. package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
  140. package/src/components/form/Switch/Switch.stories.tsx +0 -697
  141. package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
  142. package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
  143. package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
  144. package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
  145. package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
  146. package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
  147. package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -915
  148. package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
  149. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
  150. package/src/components/form/total-form/total-form.test.tsx +0 -214
  151. package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
  152. package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
  153. package/src/components/layout/Card/Card.stories.tsx +0 -591
  154. package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
  155. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
  156. package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
  157. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
  158. package/src/components/layout/Container/Container.stories.tsx +0 -557
  159. package/src/components/layout/Container/Container.test.stories.tsx +0 -745
  160. package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
  161. package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
  162. package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
  163. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
  164. package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
  165. package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
  166. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
  167. package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
  168. package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
  169. package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
  170. package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -754
  171. package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
  172. package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
  173. package/src/components/layout/Separator/Separator.stories.tsx +0 -563
  174. package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
  175. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
  176. package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
  177. package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
  178. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
  179. package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
  180. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
  181. package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
  182. package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -474
  183. package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
  184. package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
  185. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
  186. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
  187. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
  188. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
  189. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
  190. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
  191. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
  192. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
  193. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
  194. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
  195. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
  196. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
  197. package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
  198. package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
  199. package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
  200. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
  201. package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
  202. package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
  203. package/src/components/typography/Code/Code.stories.tsx +0 -686
  204. package/src/components/typography/Code/Code.test.stories.tsx +0 -703
  205. package/src/components/typography/Heading/Heading.stories.tsx +0 -451
  206. package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
  207. package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
  208. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
  209. package/src/components/typography/Text/Text.stories.tsx +0 -359
  210. package/src/components/typography/Text/Text.test.stories.tsx +0 -759
  211. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
  212. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
  213. package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
  214. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
  215. package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
  216. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
  217. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
  218. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
  219. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
  220. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
  221. package/src/components/utility/Portal/Portal.stories.tsx +0 -340
  222. package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
  223. package/src/components/utility/Transition/Transition.stories.tsx +0 -493
  224. package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
  225. package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
  226. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
  227. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
  228. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
  229. /package/src/{button.tsx → button/button.tsx} +0 -0
@@ -1,1225 +0,0 @@
1
- import Dashboard from '@mui/icons-material/Dashboard';
2
- import Email from '@mui/icons-material/Email';
3
- import Home from '@mui/icons-material/Home';
4
- import Notifications from '@mui/icons-material/Notifications';
5
- import Person from '@mui/icons-material/Person';
6
- import Security from '@mui/icons-material/Security';
7
- import Settings from '@mui/icons-material/Settings';
8
- import { Box, Typography } from '@mui/material';
9
- import type { Meta, StoryObj } from '@storybook/react-vite';
10
- import { useState } from 'react';
11
- import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
12
-
13
- import { Tabs } from './Tabs';
14
- import type { TabItem, TabsProps } from './Tabs.types';
15
-
16
- const meta: Meta<typeof Tabs> = {
17
- title: 'Navigation/Tabs/Tests',
18
- component: Tabs,
19
- parameters: {
20
- layout: 'centered',
21
- chromatic: { disableSnapshot: false },
22
- },
23
- tags: ['autodocs', 'test', 'component:Tabs'],
24
- };
25
-
26
- export default meta;
27
- type Story = StoryObj<typeof meta>;
28
-
29
- // Test wrapper component
30
- interface TabsTestWrapperProps extends Partial<TabsProps> {
31
- onChangeCallback?: typeof fn;
32
- onTabCloseCallback?: typeof fn;
33
- onFocusCallback?: typeof fn;
34
- onBlurCallback?: typeof fn;
35
- initialValue?: string;
36
- }
37
-
38
- const TabsTestWrapper = ({
39
- onChangeCallback = fn(),
40
- onTabCloseCallback = fn(),
41
- onFocusCallback = fn(),
42
- onBlurCallback = fn(),
43
- initialValue = 'tab1',
44
- ...props
45
- }: TabsTestWrapperProps) => {
46
- const [value, setValue] = useState(initialValue);
47
-
48
- const handleChange = (event: React.SyntheticEvent, tabId: string) => {
49
- setValue(tabId);
50
- onChangeCallback(event, tabId);
51
- };
52
-
53
- const handleTabClose = (tabId: string) => {
54
- onTabCloseCallback(tabId);
55
- };
56
-
57
- return (
58
- <Box sx={{ width: 600, minHeight: 300 }}>
59
- <Tabs
60
- {...props}
61
- value={value}
62
- onChange={handleChange}
63
- onTabClose={handleTabClose}
64
- onFocus={onFocusCallback}
65
- onBlur={onBlurCallback}
66
- />
67
- </Box>
68
- );
69
- };
70
-
71
- // Basic tab items for testing
72
- const basicTestItems: TabItem[] = [
73
- {
74
- id: 'tab1',
75
- label: 'Tab 1',
76
- content: (
77
- <Box sx={{ p: 2 }}>
78
- <Typography>Content for Tab 1</Typography>
79
- </Box>
80
- ),
81
- },
82
- {
83
- id: 'tab2',
84
- label: 'Tab 2',
85
- content: (
86
- <Box sx={{ p: 2 }}>
87
- <Typography>Content for Tab 2</Typography>
88
- </Box>
89
- ),
90
- },
91
- {
92
- id: 'tab3',
93
- label: 'Tab 3',
94
- content: (
95
- <Box sx={{ p: 2 }}>
96
- <Typography>Content for Tab 3</Typography>
97
- </Box>
98
- ),
99
- },
100
- ];
101
-
102
- const itemsWithIcons: TabItem[] = [
103
- {
104
- id: 'home',
105
- label: 'Home',
106
- icon: <Home />,
107
- content: (
108
- <Box sx={{ p: 2 }}>
109
- <Typography>Home content</Typography>
110
- </Box>
111
- ),
112
- },
113
- {
114
- id: 'settings',
115
- label: 'Settings',
116
- icon: <Settings />,
117
- content: (
118
- <Box sx={{ p: 2 }}>
119
- <Typography>Settings content</Typography>
120
- </Box>
121
- ),
122
- },
123
- {
124
- id: 'profile',
125
- label: 'Profile',
126
- icon: <Person />,
127
- content: (
128
- <Box sx={{ p: 2 }}>
129
- <Typography>Profile content</Typography>
130
- </Box>
131
- ),
132
- },
133
- ];
134
-
135
- // 1. Basic Interaction Test
136
- export const BasicInteraction: Story = {
137
- name: '🧪 Basic Interaction Test',
138
- args: {
139
- items: basicTestItems,
140
- onChangeCallback: fn(),
141
- onTabCloseCallback: fn(),
142
- onFocusCallback: fn(),
143
- onBlurCallback: fn(),
144
- },
145
- render: (args) => <TabsTestWrapper {...args} />,
146
- parameters: {
147
- test: {
148
- dangerouslyIgnoreUnhandledErrors: true,
149
- },
150
- },
151
- play: async ({ canvasElement, step }) => {
152
- const canvas = within(canvasElement);
153
-
154
- await step('Initial render verification', async () => {
155
- // Check tabs are rendered
156
- const tab1 = canvas.getByRole('tab', { name: /Tab 1/i });
157
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
158
- const tab3 = canvas.getByRole('tab', { name: /Tab 3/i });
159
-
160
- await expect(tab1).toBeInTheDocument();
161
- await expect(tab2).toBeInTheDocument();
162
- await expect(tab3).toBeInTheDocument();
163
-
164
- // Check first tab is selected
165
- await expect(tab1).toHaveAttribute('aria-selected', 'true');
166
- await expect(tab2).toHaveAttribute('aria-selected', 'false');
167
-
168
- // Check first panel is visible
169
- const panel1 = canvas.getByRole('tabpanel', { name: /Tab 1/i });
170
- await expect(panel1).toBeInTheDocument();
171
- await expect(panel1).toHaveTextContent('Content for Tab 1');
172
- });
173
-
174
- await step('Tab click interaction', async () => {
175
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
176
- await userEvent.click(tab2);
177
-
178
- // Check tab2 is now selected
179
- await expect(tab2).toHaveAttribute('aria-selected', 'true');
180
-
181
- // Check panel2 is now visible
182
- const panel2 = canvas.getByRole('tabpanel', { name: /Tab 2/i });
183
- await expect(panel2).toBeInTheDocument();
184
- await expect(panel2).toHaveTextContent('Content for Tab 2');
185
- });
186
-
187
- await step('Tab click on already selected tab', async () => {
188
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
189
- await userEvent.click(tab2);
190
-
191
- // Should still be selected
192
- await expect(tab2).toHaveAttribute('aria-selected', 'true');
193
- });
194
- },
195
- };
196
-
197
- // 2. Keyboard Navigation Test
198
- export const KeyboardNavigation: Story = {
199
- name: '⌨️ Keyboard Navigation Test',
200
- args: {
201
- items: basicTestItems,
202
- onChangeCallback: fn(),
203
- onTabCloseCallback: fn(),
204
- onFocusCallback: fn(),
205
- onBlurCallback: fn(),
206
- },
207
- render: (args) => <TabsTestWrapper {...args} />,
208
- parameters: {
209
- a11y: {
210
- element: '#storybook-root',
211
- config: {
212
- rules: [
213
- { id: 'color-contrast', enabled: true },
214
- { id: 'aria-required-attr', enabled: true },
215
- { id: 'aria-roles', enabled: true },
216
- { id: 'aria-valid-attr-value', enabled: true },
217
- ],
218
- },
219
- },
220
- },
221
- play: async ({ canvasElement, step }) => {
222
- const canvas = within(canvasElement);
223
-
224
- await step('Tab navigation with arrow keys', async () => {
225
- const tab1 = canvas.getByRole('tab', { name: /Tab 1/i });
226
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
227
- const tab3 = canvas.getByRole('tab', { name: /Tab 3/i });
228
-
229
- // A tab list is a single tab stop (roving tabindex), so tabbing in from
230
- // the body lands on the selected tab — the first one here.
231
- await userEvent.tab();
232
- await waitFor(() => expect(tab1).toHaveFocus());
233
-
234
- // Navigate right with arrow key
235
- await userEvent.keyboard('{ArrowRight}');
236
- await waitFor(() => expect(tab2).toHaveFocus());
237
-
238
- // Navigate right again
239
- await userEvent.keyboard('{ArrowRight}');
240
- await waitFor(() => expect(tab3).toHaveFocus());
241
-
242
- // Navigate left
243
- await userEvent.keyboard('{ArrowLeft}');
244
- await waitFor(() => expect(tab2).toHaveFocus());
245
- });
246
-
247
- await step('Tab activation with Enter key', async () => {
248
- const tab3 = canvas.getByRole('tab', { name: /Tab 3/i });
249
-
250
- // Focus is on tab 2 from the previous step; arrow right to reach tab 3.
251
- await userEvent.keyboard('{ArrowRight}');
252
- await waitFor(() => expect(tab3).toHaveFocus());
253
-
254
- // Activate with Enter
255
- await userEvent.keyboard('{Enter}');
256
-
257
- // Check tab3 is selected
258
- await expect(tab3).toHaveAttribute('aria-selected', 'true');
259
-
260
- // Check panel3 is visible
261
- const panel3 = canvas.getByRole('tabpanel', { name: /Tab 3/i });
262
- await expect(panel3).toBeInTheDocument();
263
- await expect(panel3).toHaveTextContent('Content for Tab 3');
264
- });
265
-
266
- await step('Tab navigation with Tab key', async () => {
267
- const tab3 = canvas.getByRole('tab', { name: /Tab 3/i });
268
-
269
- // Focus is still on tab 3, activated in the previous step.
270
- await waitFor(() => expect(tab3).toHaveFocus());
271
-
272
- // Tab to content. Because the list is one tab stop, Tab has to leave the
273
- // list entirely rather than step to the next tab.
274
- await userEvent.tab();
275
- await waitFor(() => expect(tab3).not.toHaveFocus());
276
- });
277
- },
278
- };
279
-
280
- // 3. Closable Tabs Test
281
- export const ClosableTabsTest: Story = {
282
- name: '❌ Closable Tabs Test',
283
- args: {
284
- onChange: fn(),
285
- onTabClose: fn(),
286
- onFocus: fn(),
287
- onBlur: fn(),
288
- items: [
289
- {
290
- id: 'tab1',
291
- label: 'Tab 1',
292
- closable: true,
293
- content: (
294
- <Box sx={{ p: 2 }}>
295
- <Typography>Content 1</Typography>
296
- </Box>
297
- ),
298
- },
299
- {
300
- id: 'tab2',
301
- label: 'Tab 2',
302
- closable: true,
303
- content: (
304
- <Box sx={{ p: 2 }}>
305
- <Typography>Content 2</Typography>
306
- </Box>
307
- ),
308
- },
309
- {
310
- id: 'tab3',
311
- label: 'Tab 3',
312
- closable: false,
313
- content: (
314
- <Box sx={{ p: 2 }}>
315
- <Typography>Content 3</Typography>
316
- </Box>
317
- ),
318
- },
319
- ],
320
- },
321
- render: (args) => {
322
- const ClosableTabsWrapper = () => {
323
- const [tabs, setTabs] = useState(args.items);
324
- const [value, setValue] = useState('tab1');
325
-
326
- const handleChange = (event: React.SyntheticEvent, tabId: string) => {
327
- setValue(tabId);
328
- args.onChange(event, tabId);
329
- };
330
-
331
- const handleTabClose = (tabId: string) => {
332
- const newTabs = tabs.filter((tab) => tab.id !== tabId);
333
- setTabs(newTabs);
334
- if (value === tabId && newTabs.length > 0) {
335
- setValue(newTabs[0].id);
336
- }
337
- args.onTabClose(tabId);
338
- };
339
-
340
- return (
341
- <Box sx={{ width: 600, minHeight: 300 }}>
342
- <Tabs
343
- {...args}
344
- items={tabs}
345
- value={value}
346
- onChange={handleChange}
347
- onTabClose={handleTabClose}
348
- onFocus={args.onFocus}
349
- onBlur={args.onBlur}
350
- />
351
- </Box>
352
- );
353
- };
354
-
355
- return <ClosableTabsWrapper />;
356
- },
357
- play: async ({ canvasElement, step }) => {
358
- const canvas = within(canvasElement);
359
-
360
- await step('Verify close buttons are rendered', async () => {
361
- // Check close buttons for closable tabs
362
- const closeButtons = canvas.getAllByRole('button', { name: 'Close tab' });
363
- // Should have 2 close buttons (tab1 and tab2)
364
- await expect(closeButtons).toHaveLength(2);
365
- });
366
-
367
- await step('Close a tab', async () => {
368
- // Get the first close button
369
- const closeButtons = canvas.getAllByRole('button', { name: 'Close tab' });
370
- const firstCloseButton = closeButtons[0];
371
-
372
- // Click close button
373
- await userEvent.click(firstCloseButton);
374
-
375
- // Wait for tab to be removed
376
- await waitFor(() => {
377
- const tabs = canvas.getAllByRole('tab');
378
- expect(tabs).toHaveLength(2);
379
- });
380
- });
381
-
382
- await step('Verify non-closable tab has no close button', async () => {
383
- const tab3 = canvas.getByRole('tab', { name: /Tab 3/i });
384
-
385
- // Should not have a close button within Tab 3
386
- await waitFor(() =>
387
- expect(within(tab3).queryByRole('button', { name: 'Close tab' })).not.toBeInTheDocument(),
388
- );
389
- });
390
- },
391
- };
392
-
393
- // 4. Badge Test
394
- export const BadgeTest: Story = {
395
- name: '🔴 Badge Test',
396
- args: {
397
- items: [
398
- {
399
- id: 'messages',
400
- label: 'Messages',
401
- icon: <Email />,
402
- badge: 5,
403
- content: (
404
- <Box sx={{ p: 2 }}>
405
- <Typography>5 new messages</Typography>
406
- </Box>
407
- ),
408
- },
409
- {
410
- id: 'notifications',
411
- label: 'Notifications',
412
- icon: <Notifications />,
413
- badge: 99,
414
- content: (
415
- <Box sx={{ p: 2 }}>
416
- <Typography>99+ notifications</Typography>
417
- </Box>
418
- ),
419
- },
420
- {
421
- id: 'dashboard',
422
- label: 'Dashboard',
423
- icon: <Dashboard />,
424
- content: (
425
- <Box sx={{ p: 2 }}>
426
- <Typography>Dashboard content</Typography>
427
- </Box>
428
- ),
429
- },
430
- ],
431
- onChangeCallback: fn(),
432
- onTabCloseCallback: fn(),
433
- onFocusCallback: fn(),
434
- onBlurCallback: fn(),
435
- },
436
- render: (args) => <TabsTestWrapper {...args} initialValue="messages" />,
437
- play: async ({ canvasElement, step }) => {
438
- const canvas = within(canvasElement);
439
-
440
- await step('Verify badges are rendered', async () => {
441
- // Check for badge elements
442
- const badges = canvas.getAllByText(/^(5|99)$/);
443
- await expect(badges).toHaveLength(2);
444
-
445
- // Verify badge content
446
- await expect(canvas.getByText('5')).toBeInTheDocument();
447
- await expect(canvas.getByText('99')).toBeInTheDocument();
448
- });
449
-
450
- await step('Verify tab without badge', async () => {
451
- const dashboardTab = canvas.getByRole('tab', { name: /Dashboard/i });
452
- await expect(dashboardTab).toBeInTheDocument();
453
-
454
- // Dashboard tab should not contain any badge numbers
455
- const dashboardContainer = dashboardTab.closest('[role="tab"]');
456
- const badgeInDashboard = dashboardContainer
457
- ? within(dashboardContainer).queryByText(/^(5|99)$/)
458
- : null;
459
- await expect(badgeInDashboard).toBeNull();
460
- });
461
- },
462
- };
463
-
464
- // 5. Disabled Tabs Test
465
- export const DisabledTabsTest: Story = {
466
- name: '🚫 Disabled Tabs Test',
467
- args: {
468
- items: [
469
- {
470
- id: 'tab1',
471
- label: 'Active Tab',
472
- content: (
473
- <Box sx={{ p: 2 }}>
474
- <Typography>Active content</Typography>
475
- </Box>
476
- ),
477
- },
478
- {
479
- id: 'tab2',
480
- label: 'Disabled Tab',
481
- disabled: true,
482
- content: (
483
- <Box sx={{ p: 2 }}>
484
- <Typography>This should not be visible</Typography>
485
- </Box>
486
- ),
487
- },
488
- {
489
- id: 'tab3',
490
- label: 'Another Active Tab',
491
- content: (
492
- <Box sx={{ p: 2 }}>
493
- <Typography>Another active content</Typography>
494
- </Box>
495
- ),
496
- },
497
- ],
498
- },
499
- render: (args) => <TabsTestWrapper {...args} />,
500
- play: async ({ canvasElement, step }) => {
501
- const canvas = within(canvasElement);
502
-
503
- await step('Verify disabled tab appearance', async () => {
504
- const disabledTab = canvas.getByRole('tab', { name: /Disabled Tab/i });
505
- await expect(disabledTab).toBeInTheDocument();
506
- await expect(disabledTab).toHaveAttribute('aria-disabled', 'true');
507
- });
508
-
509
- await step('Attempt to click disabled tab', async () => {
510
- const disabledTab = canvas.getByRole('tab', { name: /Disabled Tab/i });
511
-
512
- // Try to click but expect it to fail gracefully due to pointer-events: none
513
- try {
514
- await userEvent.click(disabledTab, { skipPointerEventsCheck: true });
515
- } catch {
516
- // Expected to fail due to pointer-events: none
517
- }
518
-
519
- // Should not be selected
520
- await expect(disabledTab).toHaveAttribute('aria-selected', 'false');
521
-
522
- // First tab should still be selected
523
- const firstTab = canvas.getByRole('tab', { name: 'Active Tab' });
524
- await expect(firstTab).toHaveAttribute('aria-selected', 'true');
525
- });
526
-
527
- await step('Verify disabled content is not shown', async () => {
528
- // The disabled tab's content should not be visible
529
- await waitFor(() =>
530
- expect(canvas.queryByText('This should not be visible')).not.toBeInTheDocument(),
531
- );
532
- });
533
- },
534
- };
535
-
536
- // 6. Variant Test
537
- export const VariantTest: Story = {
538
- name: '🎨 Variant Test',
539
- args: {
540
- items: itemsWithIcons,
541
- },
542
- render: () => {
543
- const variants = ['default', 'pills', 'underline', 'enclosed'] as const;
544
-
545
- return (
546
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4, width: 800 }}>
547
- {variants.map((variant) => (
548
- <Box key={variant}>
549
- <Typography variant="h6" gutterBottom>
550
- {variant}
551
- </Typography>
552
- <TabsTestWrapper items={itemsWithIcons} variant={variant} initialValue="home" />
553
- </Box>
554
- ))}
555
- </Box>
556
- );
557
- },
558
- play: async ({ canvasElement, step }) => {
559
- const canvas = within(canvasElement);
560
-
561
- await step('Verify all variants are rendered', async () => {
562
- // Check that all variant labels are present
563
- await expect(canvas.getByText('default')).toBeInTheDocument();
564
- await expect(canvas.getByText('pills')).toBeInTheDocument();
565
- await expect(canvas.getByText('underline')).toBeInTheDocument();
566
- await expect(canvas.getByText('enclosed')).toBeInTheDocument();
567
-
568
- // Check that tabs are rendered for each variant
569
- const allTabs = canvas.getAllByRole('tab');
570
- // 3 tabs × 4 variants = 12 tabs
571
- await expect(allTabs).toHaveLength(12);
572
- });
573
- },
574
- };
575
-
576
- // 7. Size Variation Test
577
- export const SizeVariationTest: Story = {
578
- name: '📏 Size Variation Test',
579
- args: {
580
- items: basicTestItems,
581
- onChange: fn(),
582
- onTabClose: fn(),
583
- onFocus: fn(),
584
- onBlur: fn(),
585
- },
586
- render: (args) => {
587
- const sizes = ['sm', 'md', 'lg'] as const;
588
-
589
- return (
590
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, width: 600 }}>
591
- {sizes.map((size) => (
592
- <Box key={size}>
593
- <Typography variant="subtitle2" gutterBottom>
594
- Size: {size}
595
- </Typography>
596
- <TabsTestWrapper
597
- items={basicTestItems}
598
- size={size}
599
- variant="default"
600
- initialValue="tab1"
601
- onChangeCallback={args.onChange}
602
- onTabCloseCallback={args.onTabClose}
603
- onFocusCallback={args.onFocus}
604
- onBlurCallback={args.onBlur}
605
- />
606
- </Box>
607
- ))}
608
- </Box>
609
- );
610
- },
611
- parameters: {
612
- test: {
613
- dangerouslyIgnoreUnhandledErrors: true,
614
- },
615
- },
616
- play: async ({ canvasElement, step }) => {
617
- const canvas = within(canvasElement);
618
-
619
- await step('Verify all sizes are rendered', async () => {
620
- // Check size labels
621
- await expect(canvas.getByText('Size: sm')).toBeInTheDocument();
622
- await expect(canvas.getByText('Size: md')).toBeInTheDocument();
623
- await expect(canvas.getByText('Size: lg')).toBeInTheDocument();
624
-
625
- // Check that tabs exist for each size
626
- const allTabs = canvas.getAllByRole('tab');
627
- // 3 tabs × 3 sizes = 9 tabs
628
- await expect(allTabs).toHaveLength(9);
629
- });
630
- },
631
- };
632
-
633
- // 8. Scrollable Tabs Test
634
- export const ScrollableTabsTest: Story = {
635
- name: '📜 Scrollable Tabs Test',
636
- args: {
637
- scrollable: true,
638
- items: [
639
- ...basicTestItems,
640
- {
641
- id: 'tab4',
642
- label: 'Tab 4',
643
- content: (
644
- <Box sx={{ p: 2 }}>
645
- <Typography>Content 4</Typography>
646
- </Box>
647
- ),
648
- },
649
- {
650
- id: 'tab5',
651
- label: 'Tab 5',
652
- content: (
653
- <Box sx={{ p: 2 }}>
654
- <Typography>Content 5</Typography>
655
- </Box>
656
- ),
657
- },
658
- {
659
- id: 'tab6',
660
- label: 'Tab 6',
661
- content: (
662
- <Box sx={{ p: 2 }}>
663
- <Typography>Content 6</Typography>
664
- </Box>
665
- ),
666
- },
667
- {
668
- id: 'tab7',
669
- label: 'Tab 7',
670
- content: (
671
- <Box sx={{ p: 2 }}>
672
- <Typography>Content 7</Typography>
673
- </Box>
674
- ),
675
- },
676
- {
677
- id: 'tab8',
678
- label: 'Tab 8',
679
- content: (
680
- <Box sx={{ p: 2 }}>
681
- <Typography>Content 8</Typography>
682
- </Box>
683
- ),
684
- },
685
- ],
686
- },
687
- render: (args) => (
688
- <Box sx={{ width: 400 }}>
689
- <TabsTestWrapper {...args} />
690
- </Box>
691
- ),
692
- play: async ({ canvasElement, step }) => {
693
- const canvas = within(canvasElement);
694
-
695
- await step('Verify scrollable tabs are rendered', async () => {
696
- // Check that all tabs are rendered
697
- const allTabs = canvas.getAllByRole('tab');
698
- await expect(allTabs).toHaveLength(8);
699
- });
700
-
701
- await step('Check for scroll buttons', async () => {
702
- // MUI adds scroll buttons when tabs overflow
703
- // Note: Scroll buttons might not appear if all tabs fit
704
- const tabsContainer = canvas.getByRole('tablist');
705
- await expect(tabsContainer).toBeInTheDocument();
706
- });
707
- },
708
- };
709
-
710
- // 9. Animation Test
711
- export const AnimationTest: Story = {
712
- name: '🎬 Animation Test',
713
- args: {
714
- items: basicTestItems,
715
- animateContent: true,
716
- animationDuration: 300,
717
- },
718
- render: (args) => <TabsTestWrapper {...args} />,
719
- play: async ({ canvasElement, step }) => {
720
- const canvas = within(canvasElement);
721
-
722
- await step('Switch tabs with animation', async () => {
723
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
724
-
725
- // Click tab2
726
- await userEvent.click(tab2);
727
-
728
- // Wait for animation to complete
729
- await waitFor(
730
- () => {
731
- const panel2 = canvas.getByRole('tabpanel', { name: /Tab 2/i });
732
- expect(panel2).toBeInTheDocument();
733
- expect(panel2).toHaveTextContent('Content for Tab 2');
734
- },
735
- { timeout: 500 },
736
- );
737
- });
738
-
739
- await step('Switch back with animation', async () => {
740
- const tab1 = canvas.getByRole('tab', { name: /Tab 1/i });
741
-
742
- // Click tab1
743
- await userEvent.click(tab1);
744
-
745
- // Wait for animation
746
- await waitFor(
747
- () => {
748
- const panel1 = canvas.getByRole('tabpanel', { name: /Tab 1/i });
749
- expect(panel1).toBeInTheDocument();
750
- expect(panel1).toHaveTextContent('Content for Tab 1');
751
- },
752
- { timeout: 500 },
753
- );
754
- });
755
- },
756
- };
757
-
758
- // 10. Persist Content Test
759
- export const PersistContentTest: Story = {
760
- name: '💾 Persist Content Test',
761
- args: {
762
- items: [
763
- {
764
- id: 'form',
765
- label: 'Form',
766
- content: (
767
- <Box sx={{ p: 2 }}>
768
- <input type="text" data-testid="form-input" placeholder="Enter text" />
769
- </Box>
770
- ),
771
- },
772
- {
773
- id: 'settings',
774
- label: 'Settings',
775
- content: (
776
- <Box sx={{ p: 2 }}>
777
- <Typography>Settings content</Typography>
778
- </Box>
779
- ),
780
- },
781
- ],
782
- persistContent: true,
783
- },
784
- render: (args) => <TabsTestWrapper {...args} initialValue="form" />,
785
- play: async ({ canvasElement, step }) => {
786
- const canvas = within(canvasElement);
787
-
788
- await step('Enter text in form', async () => {
789
- const input = canvas.getByTestId('form-input');
790
- await userEvent.type(input, 'Test value');
791
- await expect(input).toHaveValue('Test value');
792
- });
793
-
794
- await step('Switch to another tab', async () => {
795
- const settingsTab = canvas.getByRole('tab', { name: /Settings/i });
796
- await userEvent.click(settingsTab);
797
-
798
- // Settings panel should be visible
799
- const settingsPanel = canvas.getByRole('tabpanel', { name: /Settings/i });
800
- await expect(settingsPanel).toBeInTheDocument();
801
- });
802
-
803
- await step('Switch back and verify form persisted', async () => {
804
- const formTab = canvas.getByRole('tab', { name: /Form/i });
805
- await userEvent.click(formTab);
806
-
807
- // Check that input value is preserved
808
- const input = canvas.getByTestId('form-input');
809
- await expect(input).toHaveValue('Test value');
810
- });
811
- },
812
- };
813
-
814
- // 11. Loading State Test
815
- export const LoadingStateTest: Story = {
816
- name: '⏳ Loading State Test',
817
- args: {
818
- items: basicTestItems,
819
- loading: true,
820
- },
821
- render: (args) => <TabsTestWrapper {...args} />,
822
- play: async ({ canvasElement, step }) => {
823
- const canvas = within(canvasElement);
824
-
825
- await step('Verify loading indicator is shown', async () => {
826
- // Check for loading spinner (CircularProgress)
827
- const loadingIndicator = canvas.getByRole('progressbar');
828
- await expect(loadingIndicator).toBeInTheDocument();
829
- });
830
-
831
- await step('Verify content is not shown while loading', async () => {
832
- // Content should not be visible
833
- await waitFor(() =>
834
- expect(canvas.queryByText('Content for Tab 1')).not.toBeInTheDocument(),
835
- );
836
- });
837
- },
838
- };
839
-
840
- // 12. Accessibility Test
841
- export const AccessibilityTest: Story = {
842
- name: '♿ Accessibility Test',
843
- args: {
844
- items: itemsWithIcons,
845
- },
846
- render: (args) => <TabsTestWrapper {...args} initialValue="home" />,
847
- parameters: {
848
- a11y: {
849
- element: '#storybook-root',
850
- config: {
851
- rules: [
852
- { id: 'color-contrast', enabled: true },
853
- { id: 'aria-required-attr', enabled: true },
854
- { id: 'aria-roles', enabled: true },
855
- { id: 'aria-valid-attr-value', enabled: true },
856
- { id: 'button-name', enabled: true },
857
- { id: 'tabindex', enabled: true },
858
- ],
859
- },
860
- },
861
- },
862
- play: async ({ canvasElement, step }) => {
863
- const canvas = within(canvasElement);
864
-
865
- await step('Verify ARIA attributes', async () => {
866
- // Check tablist role
867
- const tablist = canvas.getByRole('tablist');
868
- await expect(tablist).toBeInTheDocument();
869
-
870
- // Check tab roles and ARIA attributes
871
- const tabs = canvas.getAllByRole('tab');
872
- for (const tab of tabs) {
873
- await expect(tab).toHaveAttribute('role', 'tab');
874
- await expect(tab).toHaveAttribute('aria-controls');
875
- await expect(tab).toHaveAttribute('id');
876
- }
877
-
878
- // Check tabpanel role and ARIA attributes
879
- const panel = canvas.getByRole('tabpanel');
880
- await expect(panel).toHaveAttribute('role', 'tabpanel');
881
- await expect(panel).toHaveAttribute('aria-labelledby');
882
- await expect(panel).toHaveAttribute('id');
883
- });
884
-
885
- await step('Verify keyboard accessibility', async () => {
886
- const firstTab = canvas.getByRole('tab', { name: /Home/i });
887
-
888
- // Tab in from the body — the selected tab is the list's single tab stop.
889
- await userEvent.tab();
890
- await waitFor(() => expect(firstTab).toHaveFocus());
891
-
892
- // Tab key should move focus out of tablist
893
- await userEvent.tab();
894
- await waitFor(() => expect(firstTab).not.toHaveFocus());
895
-
896
- // Shift+Tab should return focus to tablist
897
- await userEvent.tab({ shift: true });
898
- await waitFor(() => expect(firstTab).toHaveFocus());
899
- });
900
- },
901
- };
902
-
903
- // 13. Color Theme Test
904
- export const ColorThemeTest: Story = {
905
- name: '🎨 Color Theme Test',
906
- args: {
907
- items: basicTestItems,
908
- },
909
- render: () => (
910
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, width: 600 }}>
911
- <Box>
912
- <Typography variant="subtitle2" gutterBottom>
913
- Primary Color
914
- </Typography>
915
- <TabsTestWrapper items={basicTestItems} color="primary" variant="default" />
916
- </Box>
917
- <Box>
918
- <Typography variant="subtitle2" gutterBottom>
919
- Secondary Color
920
- </Typography>
921
- <TabsTestWrapper items={basicTestItems} color="secondary" variant="default" />
922
- </Box>
923
- </Box>
924
- ),
925
- play: async ({ canvasElement, step }) => {
926
- const canvas = within(canvasElement);
927
-
928
- await step('Verify both color themes are rendered', async () => {
929
- await expect(canvas.getByText('Primary Color')).toBeInTheDocument();
930
- await expect(canvas.getByText('Secondary Color')).toBeInTheDocument();
931
-
932
- // Check that tabs are rendered for both themes
933
- const allTabs = canvas.getAllByRole('tab');
934
- await expect(allTabs).toHaveLength(6); // 3 tabs × 2 themes
935
- });
936
- },
937
- };
938
-
939
- // 14. Full Width Test
940
- export const FullWidthTest: Story = {
941
- name: '↔️ Full Width Test',
942
- args: {
943
- items: basicTestItems.slice(0, 2),
944
- fullWidth: true,
945
- variant: 'default',
946
- },
947
- render: (args) => (
948
- <Box sx={{ width: 800, border: '1px dashed #ccc', p: 2 }}>
949
- <TabsTestWrapper {...args} />
950
- </Box>
951
- ),
952
- play: async ({ canvasElement, step }) => {
953
- const canvas = within(canvasElement);
954
-
955
- await step('Verify tabs stretch full width', async () => {
956
- const tablist = canvas.getByRole('tablist');
957
- await expect(tablist).toBeInTheDocument();
958
-
959
- // Tabs should be present
960
- const tabs = canvas.getAllByRole('tab');
961
- await expect(tabs).toHaveLength(2);
962
- });
963
- },
964
- };
965
-
966
- // 15. Centered Tabs Test
967
- export const CenteredTabsTest: Story = {
968
- name: '🎯 Centered Tabs Test',
969
- args: {
970
- items: basicTestItems.slice(0, 2),
971
- centered: true,
972
- variant: 'default',
973
- },
974
- render: (args) => (
975
- <Box sx={{ width: 800, border: '1px dashed #ccc', p: 2 }}>
976
- <TabsTestWrapper {...args} />
977
- </Box>
978
- ),
979
- play: async ({ canvasElement, step }) => {
980
- const canvas = within(canvasElement);
981
-
982
- await step('Verify tabs are centered', async () => {
983
- const tablist = canvas.getByRole('tablist');
984
- await expect(tablist).toBeInTheDocument();
985
-
986
- // Tabs should be present
987
- const tabs = canvas.getAllByRole('tab');
988
- await expect(tabs).toHaveLength(2);
989
- });
990
- },
991
- };
992
-
993
- // 16. Edge Cases Test
994
- export const EdgeCasesTest: Story = {
995
- name: '🔧 Edge Cases Test',
996
- args: {
997
- items: [
998
- {
999
- id: 'empty',
1000
- label: '',
1001
- content: <Box sx={{ p: 2 }}></Box>,
1002
- },
1003
- {
1004
- id: 'long',
1005
- label: 'This is a very long tab label that might overflow the container',
1006
- content: (
1007
- <Box sx={{ p: 2 }}>
1008
- <Typography>Long label content</Typography>
1009
- </Box>
1010
- ),
1011
- },
1012
- {
1013
- id: 'special',
1014
- label: 'Special @#$% Characters!',
1015
- content: (
1016
- <Box sx={{ p: 2 }}>
1017
- <Typography>Special characters content</Typography>
1018
- </Box>
1019
- ),
1020
- },
1021
- ],
1022
- },
1023
- render: (args) => (
1024
- <Box sx={{ width: 400 }}>
1025
- <TabsTestWrapper {...args} initialValue="long" />
1026
- </Box>
1027
- ),
1028
- play: async ({ canvasElement, step }) => {
1029
- const canvas = within(canvasElement);
1030
-
1031
- await step('Verify edge case tabs are rendered', async () => {
1032
- const tabs = canvas.getAllByRole('tab');
1033
- await expect(tabs).toHaveLength(3);
1034
-
1035
- // Check special characters tab
1036
- const specialTab = canvas.getByRole('tab', { name: /Special.*Characters/i });
1037
- await expect(specialTab).toBeInTheDocument();
1038
-
1039
- // Handle empty label
1040
- const emptyTab = tabs[0];
1041
- await expect(emptyTab).toBeInTheDocument();
1042
- // Even with empty label, tab should be clickable
1043
- await userEvent.click(emptyTab);
1044
- await expect(emptyTab).toHaveAttribute('aria-selected', 'true');
1045
- });
1046
- },
1047
- };
1048
-
1049
- // 17. Custom Indicator Color Test
1050
- export const CustomIndicatorColorTest: Story = {
1051
- name: '🖌️ Custom Indicator Color Test',
1052
- args: {
1053
- items: basicTestItems,
1054
- variant: 'default',
1055
- indicatorColor: '#ff6b6b',
1056
- },
1057
- render: (args) => <TabsTestWrapper {...args} />,
1058
- play: async ({ canvasElement, step }) => {
1059
- const canvas = within(canvasElement);
1060
-
1061
- await step('Verify tabs with custom indicator are rendered', async () => {
1062
- const tabs = canvas.getAllByRole('tab');
1063
- await expect(tabs).toHaveLength(3);
1064
-
1065
- // Click to switch tabs and verify indicator moves
1066
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
1067
- await userEvent.click(tab2);
1068
- await expect(tab2).toHaveAttribute('aria-selected', 'true');
1069
- });
1070
- },
1071
- };
1072
-
1073
- // 18. Dividers Test
1074
- export const DividersTest: Story = {
1075
- name: '│ Dividers Test',
1076
- args: {
1077
- items: basicTestItems,
1078
- showDividers: true,
1079
- variant: 'underline',
1080
- },
1081
- render: (args) => <TabsTestWrapper {...args} />,
1082
- play: async ({ canvasElement, step }) => {
1083
- const canvas = within(canvasElement);
1084
-
1085
- await step('Verify tabs with dividers are rendered', async () => {
1086
- const tabs = canvas.getAllByRole('tab');
1087
- await expect(tabs).toHaveLength(3);
1088
-
1089
- // Tabs should be functional
1090
- const tab2 = canvas.getByRole('tab', { name: /Tab 2/i });
1091
- await userEvent.click(tab2);
1092
- await expect(tab2).toHaveAttribute('aria-selected', 'true');
1093
- });
1094
- },
1095
- };
1096
-
1097
- // 19. Comprehensive Integration Test
1098
- export const IntegrationTest: Story = {
1099
- name: '🔗 Integration Test',
1100
- args: {
1101
- variant: 'pills',
1102
- size: 'md',
1103
- color: 'primary',
1104
- animateContent: true,
1105
- onChange: fn(),
1106
- onTabClose: fn(),
1107
- onFocus: fn(),
1108
- onBlur: fn(),
1109
- items: [
1110
- {
1111
- id: 'home',
1112
- label: 'Home',
1113
- icon: <Home />,
1114
- badge: 2,
1115
- content: (
1116
- <Box sx={{ p: 2 }}>
1117
- <Typography>Home with notifications</Typography>
1118
- </Box>
1119
- ),
1120
- },
1121
- {
1122
- id: 'security',
1123
- label: 'Security',
1124
- icon: <Security />,
1125
- disabled: true,
1126
- content: (
1127
- <Box sx={{ p: 2 }}>
1128
- <Typography>Security (disabled)</Typography>
1129
- </Box>
1130
- ),
1131
- },
1132
- {
1133
- id: 'settings',
1134
- label: 'Settings',
1135
- icon: <Settings />,
1136
- closable: true,
1137
- content: (
1138
- <Box sx={{ p: 2 }}>
1139
- <Typography>Closable settings</Typography>
1140
- </Box>
1141
- ),
1142
- },
1143
- ],
1144
- },
1145
- render: (args) => {
1146
- const IntegrationWrapper = () => {
1147
- const [items, setItems] = useState(args.items);
1148
- const [value, setValue] = useState('home');
1149
-
1150
- const handleChange = (event: React.SyntheticEvent, tabId: string) => {
1151
- setValue(tabId);
1152
- args.onChange(event, tabId);
1153
- };
1154
-
1155
- const handleTabClose = (tabId: string) => {
1156
- const newItems = items.filter((item) => item.id !== tabId);
1157
- setItems(newItems);
1158
- if (value === tabId && newItems.length > 0) {
1159
- setValue(newItems[0].id);
1160
- }
1161
- args.onTabClose(tabId);
1162
- };
1163
-
1164
- return (
1165
- <Box sx={{ width: 600, minHeight: 300 }}>
1166
- <Tabs
1167
- {...args}
1168
- items={items}
1169
- value={value}
1170
- onChange={handleChange}
1171
- onTabClose={handleTabClose}
1172
- onFocus={args.onFocus}
1173
- onBlur={args.onBlur}
1174
- />
1175
- </Box>
1176
- );
1177
- };
1178
-
1179
- return <IntegrationWrapper />;
1180
- },
1181
- play: async ({ canvasElement, step }) => {
1182
- const canvas = within(canvasElement);
1183
-
1184
- await step('Verify all features work together', async () => {
1185
- // Check badge
1186
- await expect(canvas.getByText('2')).toBeInTheDocument();
1187
-
1188
- // Check disabled tab
1189
- const securityTab = canvas.getByRole('tab', { name: /Security/i });
1190
- await expect(securityTab).toHaveAttribute('aria-disabled', 'true');
1191
-
1192
- // Check closable tab has close button
1193
- const closeButtons = canvas.getAllByRole('button', { name: 'Close tab' });
1194
- await expect(closeButtons.length).toBeGreaterThan(0);
1195
- });
1196
-
1197
- await step('Test tab switching with animation', async () => {
1198
- const settingsTab = canvas.getByRole('tab', { name: /Settings/i });
1199
- await userEvent.click(settingsTab);
1200
-
1201
- // Wait for animation
1202
- await waitFor(() => {
1203
- const panel = canvas.getByRole('tabpanel');
1204
- expect(panel).toHaveTextContent('Closable settings');
1205
- });
1206
- });
1207
-
1208
- await step('Close the settings tab', async () => {
1209
- const closeButtons = canvas.getAllByRole('button', { name: 'Close tab' });
1210
- const settingsCloseButton = closeButtons[0];
1211
-
1212
- await userEvent.click(settingsCloseButton);
1213
-
1214
- // Wait for tab to be removed
1215
- await waitFor(() => {
1216
- const settingsTab = canvas.queryByRole('tab', { name: /Settings/i });
1217
- expect(settingsTab).not.toBeInTheDocument();
1218
- });
1219
-
1220
- // Should switch to home tab
1221
- const homeTab = canvas.getByRole('tab', { name: /Home/i });
1222
- await expect(homeTab).toHaveAttribute('aria-selected', 'true');
1223
- });
1224
- },
1225
- };