@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,1343 +0,0 @@
1
- import Dashboard from '@mui/icons-material/Dashboard';
2
- import Home from '@mui/icons-material/Home';
3
- import MenuIcon from '@mui/icons-material/Menu';
4
- import Person from '@mui/icons-material/Person';
5
- import Settings from '@mui/icons-material/Settings';
6
- import {
7
- Box,
8
- Button,
9
- IconButton,
10
- List,
11
- ListItemButton,
12
- ListItemIcon,
13
- ListItemText,
14
- Stack,
15
- Typography,
16
- } from '@mui/material';
17
- import type { Meta, StoryObj } from '@storybook/react-vite';
18
- import { useState } from 'react';
19
- import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
20
-
21
- import { Drawer, DrawerContent,DrawerHeader } from './Drawer';
22
-
23
- const meta: Meta<typeof Drawer> = {
24
- title: 'Layout/Drawer/Tests',
25
- component: Drawer,
26
- parameters: {
27
- layout: 'fullscreen',
28
- chromatic: { disableSnapshot: false },
29
- },
30
- tags: ['autodocs', 'test', 'component:Drawer'],
31
- };
32
-
33
- export default meta;
34
- type Story = StoryObj<typeof meta>;
35
-
36
- // Basic Interaction Tests
37
- export const BasicInteraction: Story = {
38
- name: '🧪 Basic Interaction',
39
- render: () => {
40
- const BasicInteractionComponent = () => {
41
- const [open, setOpen] = useState(false);
42
- const [interactionState, setInteractionState] = useState('closed');
43
- const handleOpen = fn(() => {
44
- setOpen(true);
45
- setInteractionState('opened');
46
- });
47
- const handleClose = fn(() => {
48
- setOpen(false);
49
- setInteractionState('closed');
50
- });
51
-
52
- return (
53
- <Box sx={{ height: '100vh', position: 'relative' }}>
54
- <Button
55
- data-testid="open-drawer-button"
56
- onClick={handleOpen}
57
- sx={{ position: 'fixed', top: 16, left: 16, zIndex: 1300 }}
58
- >
59
- Open Drawer
60
- </Button>
61
- <Typography data-testid="interaction-state" sx={{ position: 'fixed', top: 60, left: 16 }}>
62
- State: {interactionState}
63
- </Typography>
64
- <Drawer open={open} onClose={handleClose} data-testid="drawer">
65
- <DrawerHeader onClose={handleClose}>
66
- <Typography variant="h6">Test Drawer</Typography>
67
- </DrawerHeader>
68
- <DrawerContent>
69
- <List>
70
- <ListItemButton data-testid="nav-item-home">
71
- <ListItemIcon>
72
- <Home />
73
- </ListItemIcon>
74
- <ListItemText primary="Home" />
75
- </ListItemButton>
76
- <ListItemButton data-testid="nav-item-settings">
77
- <ListItemIcon>
78
- <Settings />
79
- </ListItemIcon>
80
- <ListItemText primary="Settings" />
81
- </ListItemButton>
82
- </List>
83
- </DrawerContent>
84
- </Drawer>
85
- </Box>
86
- );
87
- };
88
-
89
- return <BasicInteractionComponent />;
90
- },
91
- play: async ({ canvasElement }) => {
92
- const canvas = within(canvasElement);
93
-
94
- // Test drawer opens
95
- const openButton = canvas.getByTestId('open-drawer-button');
96
- expect(openButton).toBeInTheDocument();
97
-
98
- // Verify initial state
99
- const stateIndicator = canvas.getByTestId('interaction-state');
100
- expect(stateIndicator).toHaveTextContent('State: closed');
101
-
102
- await userEvent.click(openButton);
103
-
104
- // Verify state change after opening
105
- await waitFor(() => {
106
- expect(stateIndicator).toHaveTextContent('State: opened');
107
- });
108
-
109
- // Test that MUI backdrop is rendered (drawer is open)
110
- await waitFor(() => {
111
- const backdrop = document.querySelector('.MuiBackdrop-root');
112
- expect(backdrop).toBeInTheDocument();
113
- });
114
-
115
- // Test ESC key closes drawer
116
- await userEvent.keyboard('{Escape}');
117
-
118
- await waitFor(() => {
119
- expect(stateIndicator).toHaveTextContent('State: closed');
120
- });
121
- },
122
- };
123
-
124
- // Keyboard Navigation Tests
125
- export const KeyboardNavigation: Story = {
126
- name: '⌨️ Keyboard Navigation',
127
- render: () => {
128
- const KeyboardNavigationComponent = () => {
129
- const [open, setOpen] = useState(false);
130
- const [keyboardState, setKeyboardState] = useState('ready');
131
- const handleOpen = fn(() => setOpen(true));
132
- const handleClose = fn(() => {
133
- setOpen(false);
134
- setKeyboardState('closed-by-escape');
135
- });
136
-
137
- return (
138
- <Box sx={{ height: '100vh', position: 'relative' }}>
139
- <Button
140
- data-testid="kbd-open-button"
141
- onClick={handleOpen}
142
- sx={{ position: 'fixed', top: 16, left: 16, zIndex: 1300 }}
143
- >
144
- Open for Keyboard Test
145
- </Button>
146
- <Typography data-testid="keyboard-state" sx={{ position: 'fixed', top: 60, left: 16 }}>
147
- Keyboard State: {keyboardState}
148
- </Typography>
149
- <Drawer open={open} onClose={handleClose} data-testid="keyboard-drawer">
150
- <DrawerHeader onClose={handleClose}>
151
- <Typography variant="h6">Keyboard Navigation</Typography>
152
- </DrawerHeader>
153
- <DrawerContent>
154
- <List>
155
- <ListItemButton data-testid="kbd-nav-home">
156
- <ListItemIcon>
157
- <Home />
158
- </ListItemIcon>
159
- <ListItemText primary="Home" />
160
- </ListItemButton>
161
- <ListItemButton data-testid="kbd-nav-dashboard">
162
- <ListItemIcon>
163
- <Dashboard />
164
- </ListItemIcon>
165
- <ListItemText primary="Dashboard" />
166
- </ListItemButton>
167
- <ListItemButton data-testid="kbd-nav-settings">
168
- <ListItemIcon>
169
- <Settings />
170
- </ListItemIcon>
171
- <ListItemText primary="Settings" />
172
- </ListItemButton>
173
- <ListItemButton data-testid="kbd-nav-profile">
174
- <ListItemIcon>
175
- <Person />
176
- </ListItemIcon>
177
- <ListItemText primary="Profile" />
178
- </ListItemButton>
179
- </List>
180
- </DrawerContent>
181
- </Drawer>
182
- </Box>
183
- );
184
- };
185
-
186
- return <KeyboardNavigationComponent />;
187
- },
188
- play: async ({ canvasElement }) => {
189
- const canvas = within(canvasElement);
190
-
191
- // Test opening drawer
192
- const openButton = canvas.getByTestId('kbd-open-button');
193
- expect(openButton).toBeInTheDocument();
194
-
195
- await userEvent.click(openButton);
196
-
197
- // Test that backdrop appears (drawer is open)
198
- await waitFor(() => {
199
- const backdrop = document.querySelector('.MuiBackdrop-root');
200
- expect(backdrop).toBeInTheDocument();
201
- });
202
-
203
- // Test Escape key closes drawer
204
- await userEvent.keyboard('{Escape}');
205
-
206
- await waitFor(() => {
207
- const stateIndicator = canvas.getByTestId('keyboard-state');
208
- expect(stateIndicator).toHaveTextContent('Keyboard State: closed-by-escape');
209
- });
210
-
211
- // Verify drawer is closed (no backdrop)
212
- await waitFor(() => {
213
- const backdrop = document.querySelector('.MuiBackdrop-root');
214
- expect(backdrop).not.toBeInTheDocument();
215
- });
216
- },
217
- };
218
-
219
- // Screen Reader Accessibility Tests
220
- export const ScreenReader: Story = {
221
- name: '🔊 Screen Reader',
222
- render: () => {
223
- const ScreenReaderComponent = () => {
224
- const [open, setOpen] = useState(true);
225
-
226
- return (
227
- <Box sx={{ height: '100vh', position: 'relative' }}>
228
- <Drawer
229
- open={open}
230
- onClose={() => setOpen(false)}
231
- data-testid="sr-drawer"
232
- aria-labelledby="drawer-title"
233
- role="navigation"
234
- >
235
- <DrawerHeader onClose={() => setOpen(false)}>
236
- <Typography id="drawer-title" variant="h6">
237
- Main Navigation
238
- </Typography>
239
- </DrawerHeader>
240
- <DrawerContent>
241
- <List role="menu" aria-labelledby="drawer-title">
242
- <ListItemButton role="menuitem" data-testid="sr-nav-home">
243
- <ListItemIcon aria-hidden="true">
244
- <Home />
245
- </ListItemIcon>
246
- <ListItemText primary="Home" />
247
- </ListItemButton>
248
- <ListItemButton role="menuitem" data-testid="sr-nav-dashboard">
249
- <ListItemIcon aria-hidden="true">
250
- <Dashboard />
251
- </ListItemIcon>
252
- <ListItemText primary="Dashboard" />
253
- </ListItemButton>
254
- <ListItemButton role="menuitem" data-testid="sr-nav-settings">
255
- <ListItemIcon aria-hidden="true">
256
- <Settings />
257
- </ListItemIcon>
258
- <ListItemText primary="Settings" />
259
- </ListItemButton>
260
- </List>
261
- </DrawerContent>
262
- </Drawer>
263
- </Box>
264
- );
265
- };
266
-
267
- return <ScreenReaderComponent />;
268
- },
269
- play: async () => {
270
- // Wait for drawer to be rendered (check for backdrop first, then content)
271
- await waitFor(
272
- () => {
273
- const backdrop = document.querySelector('.MuiBackdrop-root');
274
- expect(backdrop).toBeInTheDocument();
275
- },
276
- { timeout: 5000 },
277
- );
278
-
279
- await waitFor(
280
- () => {
281
- const title = document.querySelector('h6');
282
- expect(title).toBeInTheDocument();
283
- expect(title).toHaveTextContent('Main Navigation');
284
- },
285
- { timeout: 5000 },
286
- );
287
-
288
- // Test ARIA attributes on the drawer (look in document since MUI renders in portal)
289
- const drawer = document.querySelector('[data-testid="sr-drawer"]');
290
- expect(drawer).toBeInTheDocument();
291
- expect(drawer).toHaveAttribute('role', 'navigation');
292
- expect(drawer).toHaveAttribute('aria-labelledby', 'drawer-title');
293
-
294
- // Test drawer title
295
- const title = document.querySelector('#drawer-title');
296
- expect(title).toBeInTheDocument();
297
- expect(title).toHaveTextContent('Main Navigation');
298
-
299
- // Test navigation list
300
- const navList = document.querySelector('[role="menu"]');
301
- expect(navList).toBeInTheDocument();
302
- expect(navList).toHaveAttribute('aria-labelledby', 'drawer-title');
303
-
304
- // Test menu items
305
- const menuItems = document.querySelectorAll('[role="menuitem"]');
306
- expect(menuItems).toHaveLength(3);
307
-
308
- // Test icons are hidden from screen readers
309
- const icons = document.querySelectorAll('[aria-hidden="true"]');
310
- expect(icons.length).toBeGreaterThan(0);
311
-
312
- // Test close button accessibility
313
- const closeButton = document.querySelector('button[aria-label*="close" i]');
314
- expect(closeButton).toBeInTheDocument();
315
- },
316
- };
317
-
318
- // Focus Management Tests
319
- export const FocusManagement: Story = {
320
- name: '🎯 Focus Management',
321
- render: () => {
322
- const FocusManagementComponent = () => {
323
- const [open, setOpen] = useState(false);
324
-
325
- return (
326
- <Box sx={{ height: '100vh', position: 'relative' }}>
327
- <Button
328
- data-testid="focus-open-button"
329
- onClick={() => setOpen(true)}
330
- sx={{ position: 'fixed', top: 16, left: 16, zIndex: 1300 }}
331
- >
332
- Open Drawer
333
- </Button>
334
- <Drawer
335
- open={open}
336
- onClose={() => setOpen(false)}
337
- data-testid="focus-drawer"
338
- keepMounted={false}
339
- >
340
- <DrawerHeader onClose={() => setOpen(false)}>
341
- <Typography variant="h6">Focus Test</Typography>
342
- </DrawerHeader>
343
- <DrawerContent>
344
- <List>
345
- <ListItemButton data-testid="focus-first-item">
346
- <ListItemIcon>
347
- <Home />
348
- </ListItemIcon>
349
- <ListItemText primary="First Item" />
350
- </ListItemButton>
351
- <ListItemButton data-testid="focus-second-item">
352
- <ListItemIcon>
353
- <Dashboard />
354
- </ListItemIcon>
355
- <ListItemText primary="Second Item" />
356
- </ListItemButton>
357
- </List>
358
- </DrawerContent>
359
- </Drawer>
360
- <Box sx={{ p: 3, pt: 10 }}>
361
- <Button data-testid="focus-main-button">Main Content Button</Button>
362
- </Box>
363
- </Box>
364
- );
365
- };
366
-
367
- return <FocusManagementComponent />;
368
- },
369
- play: async ({ canvasElement }) => {
370
- const canvas = within(canvasElement);
371
-
372
- // Test initial focus on main content
373
- const mainButton = canvas.getByTestId('focus-main-button');
374
- const openButton = canvas.getByTestId('focus-open-button');
375
-
376
- // Click to focus rather than calling .focus(): the button carries no handler,
377
- // so a click only moves focus — and it is how a user would land there. This
378
- // is the starting point the "focus returns on close" check below relies on.
379
- await userEvent.click(mainButton);
380
- await waitFor(() => expect(document.activeElement).toBe(mainButton));
381
-
382
- // Open drawer
383
- await userEvent.click(openButton);
384
-
385
- await waitFor(() => {
386
- const drawerContent = document.querySelector('h6');
387
- expect(drawerContent).toBeInTheDocument();
388
- expect(drawerContent).toHaveTextContent('Focus Test');
389
- });
390
-
391
- // Test focus trap within drawer
392
- const firstItem = document.querySelector('[data-testid="focus-first-item"]');
393
- const secondItem = document.querySelector('[data-testid="focus-second-item"]');
394
- const closeButton = document.querySelector('button[aria-label*="close" i]');
395
-
396
- // Tab through drawer elements
397
- expect(firstItem).toBeInTheDocument();
398
- expect(secondItem).toBeInTheDocument();
399
- expect(closeButton).toBeInTheDocument();
400
-
401
- await userEvent.click(firstItem);
402
- await waitFor(() => expect(document.activeElement).toBe(firstItem));
403
-
404
- await userEvent.tab();
405
- await waitFor(() => expect(document.activeElement).toBe(secondItem));
406
-
407
- await userEvent.tab();
408
- await waitFor(() => expect(document.activeElement).toBe(closeButton));
409
-
410
- // Close drawer and verify focus returns
411
- await userEvent.click(closeButton);
412
-
413
- await waitFor(() => {
414
- expect(document.querySelector('h6')).not.toBeInTheDocument();
415
- });
416
- },
417
- };
418
-
419
- // Responsive Design Tests
420
- export const ResponsiveDesign: Story = {
421
- name: '📱 Responsive Design',
422
- render: () => {
423
- const ResponsiveComponent = () => {
424
- const [open, setOpen] = useState(true);
425
-
426
- return (
427
- <Box sx={{ height: '100vh', position: 'relative' }}>
428
- <Drawer
429
- open={open}
430
- onClose={() => setOpen(false)}
431
- data-testid="responsive-drawer"
432
- width={280}
433
- >
434
- <DrawerHeader onClose={() => setOpen(false)}>
435
- <Typography variant="h6">Responsive Drawer</Typography>
436
- </DrawerHeader>
437
- <DrawerContent>
438
- <Typography variant="body2" sx={{ p: 2 }}>
439
- This drawer should adapt to different screen sizes.
440
- </Typography>
441
- <List>
442
- <ListItemButton>
443
- <ListItemIcon>
444
- <Home />
445
- </ListItemIcon>
446
- <ListItemText primary="Home" />
447
- </ListItemButton>
448
- <ListItemButton>
449
- <ListItemIcon>
450
- <Settings />
451
- </ListItemIcon>
452
- <ListItemText primary="Settings" />
453
- </ListItemButton>
454
- </List>
455
- </DrawerContent>
456
- </Drawer>
457
- </Box>
458
- );
459
- };
460
-
461
- return <ResponsiveComponent />;
462
- },
463
- parameters: {
464
- viewport: {
465
- viewports: {
466
- mobile: { name: 'Mobile', styles: { width: '375px', height: '667px' } },
467
- tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
468
- desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } },
469
- },
470
- },
471
- },
472
- play: async () => {
473
- // Wait for drawer to be rendered
474
- await waitFor(
475
- () => {
476
- const backdrop = document.querySelector('.MuiBackdrop-root');
477
- expect(backdrop).toBeInTheDocument();
478
- },
479
- { timeout: 5000 },
480
- );
481
-
482
- // Test drawer renders properly (look in document since MUI renders in portal)
483
- const drawer = document.querySelector('[data-testid="responsive-drawer"]');
484
- expect(drawer).toBeInTheDocument();
485
- const drawerPaper = drawer.querySelector('.MuiDrawer-paper');
486
- expect(drawerPaper).toBeInTheDocument();
487
-
488
- // Test drawer content is accessible (look in document since it's in portal)
489
- await waitFor(() => {
490
- const title = document.querySelector('h6');
491
- expect(title).toBeInTheDocument();
492
- expect(title).toHaveTextContent('Responsive Drawer');
493
- });
494
-
495
- const description = Array.from(document.querySelectorAll('*')).find((el) =>
496
- el.textContent?.includes('This drawer should adapt to different screen sizes'),
497
- );
498
- expect(description).toBeInTheDocument();
499
-
500
- // Test navigation items are present
501
- const homeItem = Array.from(document.querySelectorAll('*')).find(
502
- (el) => el.textContent === 'Home',
503
- );
504
- const settingsItem = Array.from(document.querySelectorAll('*')).find(
505
- (el) => el.textContent === 'Settings',
506
- );
507
-
508
- expect(homeItem).toBeInTheDocument();
509
- expect(settingsItem).toBeInTheDocument();
510
- },
511
- };
512
-
513
- // Theme Variations Tests
514
- export const ThemeVariations: Story = {
515
- name: '🎨 Theme Variations',
516
- render: () => {
517
- const ThemeVariationsComponent = () => {
518
- const [openDrawers, setOpenDrawers] = useState({
519
- default: true,
520
- glass: false,
521
- });
522
-
523
- const toggleDrawer = (type: keyof typeof openDrawers) => {
524
- setOpenDrawers((prev) => ({ ...prev, [type]: !prev[type] }));
525
- };
526
-
527
- return (
528
- <Box sx={{ height: '100vh', position: 'relative' }}>
529
- <Stack direction="row" spacing={2} sx={{ p: 2, zIndex: 1300, position: 'relative' }}>
530
- <Button
531
- variant="outlined"
532
- onClick={() => toggleDrawer('glass')}
533
- data-testid="theme-glass-button"
534
- >
535
- Toggle Glass Theme
536
- </Button>
537
- </Stack>
538
-
539
- {/* Default Theme Drawer */}
540
- <Drawer
541
- open={openDrawers.default}
542
- onClose={() => toggleDrawer('default')}
543
- data-testid="theme-default-drawer"
544
- width={280}
545
- >
546
- <DrawerHeader onClose={() => toggleDrawer('default')}>
547
- <Typography variant="h6">Default Theme</Typography>
548
- </DrawerHeader>
549
- <DrawerContent>
550
- <Typography variant="body2" sx={{ p: 2 }}>
551
- This drawer uses the default theme styling.
552
- </Typography>
553
- <List>
554
- <ListItemButton>
555
- <ListItemIcon>
556
- <Home />
557
- </ListItemIcon>
558
- <ListItemText primary="Home" />
559
- </ListItemButton>
560
- </List>
561
- </DrawerContent>
562
- </Drawer>
563
-
564
- {/* Glass Theme Drawer */}
565
- <Drawer
566
- open={openDrawers.glass}
567
- onClose={() => toggleDrawer('glass')}
568
- data-testid="theme-glass-drawer"
569
- variant="glass"
570
- width={300}
571
- >
572
- <DrawerHeader onClose={() => toggleDrawer('glass')}>
573
- <Typography variant="h6">Glass Theme</Typography>
574
- </DrawerHeader>
575
- <DrawerContent>
576
- <Typography variant="body2" sx={{ p: 2 }}>
577
- This drawer uses glass morphism styling with backdrop blur.
578
- </Typography>
579
- <List>
580
- <ListItemButton>
581
- <ListItemIcon>
582
- <Settings />
583
- </ListItemIcon>
584
- <ListItemText primary="Settings" />
585
- </ListItemButton>
586
- </List>
587
- </DrawerContent>
588
- </Drawer>
589
- </Box>
590
- );
591
- };
592
-
593
- return <ThemeVariationsComponent />;
594
- },
595
- play: async ({ canvasElement }) => {
596
- const canvas = within(canvasElement);
597
-
598
- // Wait for default theme drawer to render
599
- await waitFor(
600
- () => {
601
- const backdrop = document.querySelector('.MuiBackdrop-root');
602
- expect(backdrop).toBeInTheDocument();
603
- },
604
- { timeout: 5000 },
605
- );
606
-
607
- // Test default theme drawer (look in document since MUI renders in portal)
608
- const defaultDrawer = document.querySelector('[data-testid="theme-default-drawer"]');
609
- expect(defaultDrawer).toBeInTheDocument();
610
-
611
- await waitFor(() => {
612
- const defaultTitle = document.querySelector('h6');
613
- expect(defaultTitle).toBeInTheDocument();
614
- expect(defaultTitle).toHaveTextContent('Default Theme');
615
- });
616
-
617
- // Test glass theme toggle
618
- const glassButton = canvas.getByTestId('theme-glass-button');
619
- await userEvent.click(glassButton);
620
-
621
- await waitFor(() => {
622
- const glassDrawer = document.querySelector('[data-testid="theme-glass-drawer"]');
623
- expect(glassDrawer).toBeInTheDocument();
624
- });
625
-
626
- await waitFor(() => {
627
- const glassDrawer = document.querySelector('[data-testid="theme-glass-drawer"]');
628
- expect(glassDrawer).toBeInTheDocument();
629
- const glassTitle = glassDrawer.querySelector('h6');
630
- expect(glassTitle).toBeInTheDocument();
631
- expect(glassTitle).toHaveTextContent('Glass Theme');
632
- });
633
-
634
- // Test glass drawer styling
635
- const glassDrawerElement = document.querySelector('[data-testid="theme-glass-drawer"]');
636
- const glassDrawerPaper = glassDrawerElement.querySelector('.MuiDrawer-paper');
637
- expect(glassDrawerPaper).toBeInTheDocument();
638
- },
639
- };
640
-
641
- // Visual States Tests
642
- export const VisualStates: Story = {
643
- name: '👁️ Visual States',
644
- render: () => {
645
- const VisualStatesComponent = () => {
646
- const [states, setStates] = useState({
647
- left: true,
648
- right: false,
649
- top: false,
650
- bottom: false,
651
- });
652
-
653
- const toggleState = (state: keyof typeof states) => {
654
- setStates((prev) => ({ ...prev, [state]: !prev[state] }));
655
- };
656
-
657
- return (
658
- <Box sx={{ height: '100vh', position: 'relative' }}>
659
- <Stack direction="row" spacing={1} sx={{ p: 2, zIndex: 1300, position: 'relative' }}>
660
- <Button
661
- size="small"
662
- onClick={() => toggleState('right')}
663
- data-testid="visual-right-btn"
664
- >
665
- Right
666
- </Button>
667
- <Button size="small" onClick={() => toggleState('top')} data-testid="visual-top-btn">
668
- Top
669
- </Button>
670
- <Button
671
- size="small"
672
- onClick={() => toggleState('bottom')}
673
- data-testid="visual-bottom-btn"
674
- >
675
- Bottom
676
- </Button>
677
- </Stack>
678
-
679
- {/* Left Drawer */}
680
- <Drawer
681
- open={states.left}
682
- onClose={() => toggleState('left')}
683
- variant="left"
684
- data-testid="visual-left-drawer"
685
- >
686
- <DrawerHeader onClose={() => toggleState('left')}>
687
- <Typography variant="h6">Left Drawer</Typography>
688
- </DrawerHeader>
689
- <DrawerContent>
690
- <Typography variant="body2">Left side content</Typography>
691
- </DrawerContent>
692
- </Drawer>
693
-
694
- {/* Right Drawer */}
695
- <Drawer
696
- open={states.right}
697
- onClose={() => toggleState('right')}
698
- variant="right"
699
- data-testid="visual-right-drawer"
700
- >
701
- <DrawerHeader onClose={() => toggleState('right')}>
702
- <Typography variant="h6">Right Drawer</Typography>
703
- </DrawerHeader>
704
- <DrawerContent>
705
- <Typography variant="body2">Right side content</Typography>
706
- </DrawerContent>
707
- </Drawer>
708
-
709
- {/* Top Drawer */}
710
- <Drawer
711
- open={states.top}
712
- onClose={() => toggleState('top')}
713
- variant="top"
714
- height={200}
715
- data-testid="visual-top-drawer"
716
- >
717
- <DrawerHeader onClose={() => toggleState('top')}>
718
- <Typography variant="h6">Top Drawer</Typography>
719
- </DrawerHeader>
720
- <DrawerContent>
721
- <Typography variant="body2">Top content</Typography>
722
- </DrawerContent>
723
- </Drawer>
724
-
725
- {/* Bottom Drawer */}
726
- <Drawer
727
- open={states.bottom}
728
- onClose={() => toggleState('bottom')}
729
- variant="bottom"
730
- height={200}
731
- data-testid="visual-bottom-drawer"
732
- >
733
- <DrawerHeader onClose={() => toggleState('bottom')}>
734
- <Typography variant="h6">Bottom Drawer</Typography>
735
- </DrawerHeader>
736
- <DrawerContent>
737
- <Typography variant="body2">Bottom content</Typography>
738
- </DrawerContent>
739
- </Drawer>
740
- </Box>
741
- );
742
- };
743
-
744
- return <VisualStatesComponent />;
745
- },
746
- play: async ({ canvasElement }) => {
747
- const canvas = within(canvasElement);
748
-
749
- // Wait for left drawer to render (initially open)
750
- await waitFor(
751
- () => {
752
- const backdrop = document.querySelector('.MuiBackdrop-root');
753
- expect(backdrop).toBeInTheDocument();
754
- },
755
- { timeout: 5000 },
756
- );
757
-
758
- // Test left drawer (initially open) - look in document since MUI renders in portal
759
- const leftDrawer = document.querySelector('[data-testid="visual-left-drawer"]');
760
- expect(leftDrawer).toBeInTheDocument();
761
-
762
- await waitFor(() => {
763
- const leftTitle = leftDrawer.querySelector('h6');
764
- expect(leftTitle).toBeInTheDocument();
765
- expect(leftTitle).toHaveTextContent('Left Drawer');
766
- });
767
-
768
- // Test right drawer toggle
769
- const rightButton = canvas.getByTestId('visual-right-btn');
770
- await userEvent.click(rightButton);
771
-
772
- await waitFor(() => {
773
- const rightDrawer = document.querySelector('[data-testid="visual-right-drawer"]');
774
- expect(rightDrawer).toBeInTheDocument();
775
- const rightTitle = rightDrawer.querySelector('h6');
776
- expect(rightTitle).toBeInTheDocument();
777
- expect(rightTitle).toHaveTextContent('Right Drawer');
778
- });
779
-
780
- // Test top drawer toggle
781
- const topButton = canvas.getByTestId('visual-top-btn');
782
- await userEvent.click(topButton);
783
-
784
- await waitFor(() => {
785
- const topDrawer = document.querySelector('[data-testid="visual-top-drawer"]');
786
- expect(topDrawer).toBeInTheDocument();
787
- const topTitle = topDrawer.querySelector('h6');
788
- expect(topTitle).toBeInTheDocument();
789
- expect(topTitle).toHaveTextContent('Top Drawer');
790
- });
791
-
792
- // Test bottom drawer toggle
793
- const bottomButton = canvas.getByTestId('visual-bottom-btn');
794
- await userEvent.click(bottomButton);
795
-
796
- await waitFor(() => {
797
- const bottomDrawer = document.querySelector('[data-testid="visual-bottom-drawer"]');
798
- expect(bottomDrawer).toBeInTheDocument();
799
- const bottomTitle = bottomDrawer.querySelector('h6');
800
- expect(bottomTitle).toBeInTheDocument();
801
- expect(bottomTitle).toHaveTextContent('Bottom Drawer');
802
- });
803
- },
804
- };
805
-
806
- // Performance Tests
807
- export const Performance: Story = {
808
- name: '⚡ Performance',
809
- render: () => {
810
- const PerformanceComponent = () => {
811
- const [open, setOpen] = useState(true);
812
- const [itemCount, setItemCount] = useState(50);
813
-
814
- // Generate large list of items
815
- const items = Array.from({ length: itemCount }, (_, i) => ({
816
- id: i,
817
- label: `Item ${i + 1}`,
818
- icon: i % 3 === 0 ? <Home /> : i % 3 === 1 ? <Settings /> : <Person />,
819
- }));
820
-
821
- return (
822
- <Box sx={{ height: '100vh', position: 'relative' }}>
823
- <Stack direction="row" spacing={2} sx={{ p: 2, zIndex: 1300, position: 'relative' }}>
824
- <Button size="small" onClick={() => setItemCount(100)} data-testid="perf-100-items">
825
- 100 Items
826
- </Button>
827
- <Button size="small" onClick={() => setItemCount(200)} data-testid="perf-200-items">
828
- 200 Items
829
- </Button>
830
- </Stack>
831
-
832
- <Drawer
833
- open={open}
834
- onClose={() => setOpen(false)}
835
- data-testid="performance-drawer"
836
- keepMounted={true}
837
- >
838
- <DrawerHeader onClose={() => setOpen(false)}>
839
- <Typography variant="h6">Performance Test ({itemCount} items)</Typography>
840
- </DrawerHeader>
841
- <DrawerContent>
842
- <List data-testid="performance-list">
843
- {items.map((item) => (
844
- <ListItemButton key={item.id} data-testid={`perf-item-${item.id}`}>
845
- <ListItemIcon>{item.icon}</ListItemIcon>
846
- <ListItemText primary={item.label} />
847
- </ListItemButton>
848
- ))}
849
- </List>
850
- </DrawerContent>
851
- </Drawer>
852
- </Box>
853
- );
854
- };
855
-
856
- return <PerformanceComponent />;
857
- },
858
- play: async ({ canvasElement }) => {
859
- const canvas = within(canvasElement);
860
-
861
- // Wait for performance drawer to render
862
- await waitFor(
863
- () => {
864
- const backdrop = document.querySelector('.MuiBackdrop-root');
865
- expect(backdrop).toBeInTheDocument();
866
- },
867
- { timeout: 5000 },
868
- );
869
-
870
- // Test initial render performance (look in document since MUI renders in portal)
871
- const performanceDrawer = document.querySelector('[data-testid="performance-drawer"]');
872
- expect(performanceDrawer).toBeInTheDocument();
873
-
874
- const performanceList = document.querySelector('[data-testid="performance-list"]');
875
- expect(performanceList).toBeInTheDocument();
876
-
877
- // Test that initial items are rendered
878
- const firstItem = document.querySelector('[data-testid="perf-item-0"]');
879
- const lastItem = document.querySelector('[data-testid="perf-item-49"]');
880
- expect(firstItem).toBeInTheDocument();
881
- expect(lastItem).toBeInTheDocument();
882
-
883
- // Test performance with more items
884
- const button100 = canvas.getByTestId('perf-100-items');
885
- await userEvent.click(button100);
886
-
887
- await waitFor(() => {
888
- const title = performanceDrawer.querySelector('h6');
889
- expect(title).toBeInTheDocument();
890
- expect(title).toHaveTextContent('Performance Test (100 items)');
891
- });
892
-
893
- // Verify new items are rendered
894
- await waitFor(() => {
895
- const item99 = document.querySelector('[data-testid="perf-item-99"]');
896
- expect(item99).toBeInTheDocument();
897
- });
898
-
899
- // Scrolling is not asserted here: setting scrollTop and reading it back only
900
- // confirms the DOM stored the value, and how far the list can actually scroll
901
- // depends on the runner's window height. The meaningful part of this story is
902
- // that all 100 items rendered, which is asserted above.
903
- },
904
- };
905
-
906
- // Edge Cases Tests
907
- export const EdgeCases: Story = {
908
- name: '🧩 Edge Cases',
909
- render: () => {
910
- const EdgeCasesComponent = () => {
911
- const [states, setStates] = useState({
912
- empty: true,
913
- noHeader: false,
914
- longContent: false,
915
- persistent: false,
916
- });
917
-
918
- const toggleState = (state: keyof typeof states) => {
919
- setStates((prev) => ({ ...prev, [state]: !prev[state] }));
920
- };
921
-
922
- const longText =
923
- 'This is a very long text content that should test how the drawer handles overflow content. '.repeat(
924
- 20,
925
- );
926
-
927
- return (
928
- <Box sx={{ height: '100vh', position: 'relative' }}>
929
- <Stack
930
- direction="row"
931
- spacing={1}
932
- sx={{ p: 1, zIndex: 1300, position: 'relative', flexWrap: 'wrap' }}
933
- >
934
- <Button
935
- size="small"
936
- onClick={() => toggleState('noHeader')}
937
- data-testid="edge-no-header"
938
- >
939
- No Header
940
- </Button>
941
- <Button
942
- size="small"
943
- onClick={() => toggleState('longContent')}
944
- data-testid="edge-long-content"
945
- >
946
- Long Content
947
- </Button>
948
- <Button
949
- size="small"
950
- onClick={() => toggleState('persistent')}
951
- data-testid="edge-persistent"
952
- >
953
- Persistent
954
- </Button>
955
- </Stack>
956
-
957
- {/* Empty Drawer */}
958
- <Drawer
959
- open={states.empty}
960
- onClose={() => toggleState('empty')}
961
- data-testid="edge-empty-drawer"
962
- >
963
- <DrawerHeader onClose={() => toggleState('empty')}>
964
- <Typography variant="h6">Empty Drawer</Typography>
965
- </DrawerHeader>
966
- <DrawerContent>{/* Intentionally empty content */}</DrawerContent>
967
- </Drawer>
968
-
969
- {/* No Header Drawer */}
970
- <Drawer
971
- open={states.noHeader}
972
- onClose={() => toggleState('noHeader')}
973
- data-testid="edge-no-header-drawer"
974
- >
975
- <DrawerContent>
976
- <Box sx={{ p: 2 }}>
977
- <Typography variant="h6">No Header</Typography>
978
- <Typography variant="body2">This drawer has no header component.</Typography>
979
- </Box>
980
- </DrawerContent>
981
- </Drawer>
982
-
983
- {/* Long Content Drawer */}
984
- <Drawer
985
- open={states.longContent}
986
- onClose={() => toggleState('longContent')}
987
- data-testid="edge-long-content-drawer"
988
- >
989
- <DrawerHeader onClose={() => toggleState('longContent')}>
990
- <Typography variant="h6">Long Content</Typography>
991
- </DrawerHeader>
992
- <DrawerContent>
993
- <Typography variant="body2" data-testid="edge-long-text">
994
- {longText}
995
- </Typography>
996
- </DrawerContent>
997
- </Drawer>
998
-
999
- {/* Persistent Drawer */}
1000
- <Drawer
1001
- open={states.persistent}
1002
- onClose={() => toggleState('persistent')}
1003
- data-testid="edge-persistent-drawer"
1004
- persistent={true}
1005
- >
1006
- <DrawerHeader>
1007
- <Typography variant="h6">Persistent Drawer</Typography>
1008
- </DrawerHeader>
1009
- <DrawerContent>
1010
- <Typography variant="body2">
1011
- This is a persistent drawer without backdrop click-to-close.
1012
- </Typography>
1013
- <Button
1014
- onClick={() => toggleState('persistent')}
1015
- data-testid="edge-persistent-close"
1016
- sx={{ mt: 2 }}
1017
- >
1018
- Close Manually
1019
- </Button>
1020
- </DrawerContent>
1021
- </Drawer>
1022
- </Box>
1023
- );
1024
- };
1025
-
1026
- return <EdgeCasesComponent />;
1027
- },
1028
- play: async ({ canvasElement }) => {
1029
- const canvas = within(canvasElement);
1030
-
1031
- // Wait for empty drawer to render
1032
- await waitFor(
1033
- () => {
1034
- const backdrop = document.querySelector('.MuiBackdrop-root');
1035
- expect(backdrop).toBeInTheDocument();
1036
- },
1037
- { timeout: 5000 },
1038
- );
1039
-
1040
- // Test empty drawer (look in document since MUI renders in portal)
1041
- const emptyDrawer = document.querySelector('[data-testid="edge-empty-drawer"]');
1042
- expect(emptyDrawer).toBeInTheDocument();
1043
-
1044
- await waitFor(() => {
1045
- const emptyTitle = emptyDrawer.querySelector('h6');
1046
- expect(emptyTitle).toBeInTheDocument();
1047
- expect(emptyTitle).toHaveTextContent('Empty Drawer');
1048
- });
1049
-
1050
- // Test no header drawer
1051
- const noHeaderButton = canvas.getByTestId('edge-no-header');
1052
- await userEvent.click(noHeaderButton);
1053
-
1054
- await waitFor(() => {
1055
- const noHeaderDrawer = document.querySelector('[data-testid="edge-no-header-drawer"]');
1056
- expect(noHeaderDrawer).toBeInTheDocument();
1057
- const noHeaderText = Array.from(document.querySelectorAll('*')).find(
1058
- (el) => el.textContent === 'No Header',
1059
- );
1060
- expect(noHeaderText).toBeInTheDocument();
1061
- });
1062
-
1063
- // Test long content drawer
1064
- const longContentButton = canvas.getByTestId('edge-long-content');
1065
- await userEvent.click(longContentButton);
1066
-
1067
- await waitFor(() => {
1068
- const longContentDrawer = document.querySelector('[data-testid="edge-long-content-drawer"]');
1069
- expect(longContentDrawer).toBeInTheDocument();
1070
- const longText = document.querySelector('[data-testid="edge-long-text"]');
1071
- expect(longText).toBeInTheDocument();
1072
- expect(longText.textContent?.length).toBeGreaterThan(100);
1073
- });
1074
-
1075
- // Test persistent drawer
1076
- const persistentButton = canvas.getByTestId('edge-persistent');
1077
- await userEvent.click(persistentButton);
1078
-
1079
- await waitFor(() => {
1080
- const persistentDrawer = document.querySelector('[data-testid="edge-persistent-drawer"]');
1081
- expect(persistentDrawer).toBeInTheDocument();
1082
- const persistentTitle = persistentDrawer.querySelector('h6');
1083
- expect(persistentTitle).toBeInTheDocument();
1084
- expect(persistentTitle).toHaveTextContent('Persistent Drawer');
1085
- });
1086
-
1087
- // Test manual close for persistent drawer
1088
- const manualCloseButton = canvas.getByTestId('edge-persistent-close');
1089
- await userEvent.click(manualCloseButton);
1090
-
1091
- await waitFor(() => {
1092
- const persistentDrawer = document.querySelector('[data-testid="edge-persistent-drawer"]');
1093
- // For persistent drawers, check if it's hidden rather than removed from DOM
1094
- if (persistentDrawer) {
1095
- const drawerPaper = persistentDrawer.querySelector('.MuiDrawer-paper');
1096
- const isHidden =
1097
- drawerPaper &&
1098
- (drawerPaper.style.visibility === 'hidden' ||
1099
- drawerPaper.style.transform.includes('translateX(-'));
1100
- expect(isHidden).toBe(true);
1101
- } else {
1102
- expect(persistentDrawer).not.toBeInTheDocument();
1103
- }
1104
- });
1105
- },
1106
- };
1107
-
1108
- // Integration Tests
1109
- export const Integration: Story = {
1110
- name: '🔗 Integration',
1111
- render: () => {
1112
- const IntegrationComponent = () => {
1113
- const [drawerOpen, setDrawerOpen] = useState(false);
1114
- const [selectedPage, setSelectedPage] = useState('home');
1115
- const [notifications, setNotifications] = useState(3);
1116
-
1117
- const handleNavigation = (page: string) => {
1118
- setSelectedPage(page);
1119
- if (page === 'notifications') {
1120
- setNotifications(0);
1121
- }
1122
- };
1123
-
1124
- return (
1125
- <Box sx={{ display: 'flex', height: '100vh' }}>
1126
- {/* Main Drawer */}
1127
- <Drawer
1128
- open={drawerOpen}
1129
- onClose={() => setDrawerOpen(false)}
1130
- data-testid="integration-main-drawer"
1131
- persistent={false}
1132
- >
1133
- <DrawerHeader onClose={() => setDrawerOpen(false)}>
1134
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
1135
- <Box
1136
- sx={{
1137
- width: 32,
1138
- height: 32,
1139
- borderRadius: '50%',
1140
- bgcolor: 'primary.main',
1141
- display: 'flex',
1142
- alignItems: 'center',
1143
- justifyContent: 'center',
1144
- color: 'white',
1145
- fontSize: '0.875rem',
1146
- fontWeight: 'bold',
1147
- }}
1148
- >
1149
- A
1150
- </Box>
1151
- <Typography variant="h6">App Name</Typography>
1152
- </Box>
1153
- </DrawerHeader>
1154
- <DrawerContent>
1155
- <List>
1156
- <ListItemButton
1157
- selected={selectedPage === 'home'}
1158
- onClick={() => handleNavigation('home')}
1159
- data-testid="integration-nav-home"
1160
- >
1161
- <ListItemIcon>
1162
- <Home />
1163
- </ListItemIcon>
1164
- <ListItemText primary="Home" />
1165
- </ListItemButton>
1166
- <ListItemButton
1167
- selected={selectedPage === 'dashboard'}
1168
- onClick={() => handleNavigation('dashboard')}
1169
- data-testid="integration-nav-dashboard"
1170
- >
1171
- <ListItemIcon>
1172
- <Dashboard />
1173
- </ListItemIcon>
1174
- <ListItemText primary="Dashboard" />
1175
- </ListItemButton>
1176
- <ListItemButton
1177
- selected={selectedPage === 'notifications'}
1178
- onClick={() => handleNavigation('notifications')}
1179
- data-testid="integration-nav-notifications"
1180
- >
1181
- <ListItemIcon>
1182
- <Box sx={{ position: 'relative' }}>
1183
- <Settings />
1184
- {notifications > 0 && (
1185
- <Box
1186
- sx={{
1187
- position: 'absolute',
1188
- top: -4,
1189
- right: -4,
1190
- width: 16,
1191
- height: 16,
1192
- borderRadius: '50%',
1193
- bgcolor: 'error.main',
1194
- color: 'white',
1195
- display: 'flex',
1196
- alignItems: 'center',
1197
- justifyContent: 'center',
1198
- fontSize: '0.75rem',
1199
- }}
1200
- >
1201
- {notifications}
1202
- </Box>
1203
- )}
1204
- </Box>
1205
- </ListItemIcon>
1206
- <ListItemText
1207
- primary="Notifications"
1208
- secondary={notifications > 0 ? `${notifications} new` : 'No new notifications'}
1209
- />
1210
- </ListItemButton>
1211
- </List>
1212
- </DrawerContent>
1213
- </Drawer>
1214
-
1215
- {/* Main Content Area */}
1216
- <Box component="main" sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
1217
- {/* App Bar */}
1218
- <Box
1219
- sx={{
1220
- p: 2,
1221
- borderBottom: 1,
1222
- borderColor: 'divider',
1223
- display: 'flex',
1224
- alignItems: 'center',
1225
- gap: 2,
1226
- }}
1227
- >
1228
- <IconButton
1229
- onClick={() => setDrawerOpen(!drawerOpen)}
1230
- data-testid="integration-menu-button"
1231
- >
1232
- <MenuIcon />
1233
- </IconButton>
1234
- <Typography variant="h6" sx={{ flexGrow: 1 }}>
1235
- {selectedPage.charAt(0).toUpperCase() + selectedPage.slice(1)}
1236
- </Typography>
1237
- {notifications > 0 && (
1238
- <Box
1239
- sx={{
1240
- bgcolor: 'error.main',
1241
- color: 'white',
1242
- borderRadius: 1,
1243
- px: 1,
1244
- py: 0.5,
1245
- fontSize: '0.75rem',
1246
- }}
1247
- >
1248
- {notifications} notifications
1249
- </Box>
1250
- )}
1251
- </Box>
1252
-
1253
- {/* Page Content */}
1254
- <Box sx={{ p: 3, flex: 1 }} data-testid="integration-main-content">
1255
- <Typography variant="h4" gutterBottom>
1256
- {selectedPage === 'home' && 'Welcome Home'}
1257
- {selectedPage === 'dashboard' && 'Dashboard Overview'}
1258
- {selectedPage === 'notifications' && 'Notification Center'}
1259
- </Typography>
1260
- <Typography variant="body1">
1261
- This is the {selectedPage} page content. The drawer integrates with the main
1262
- application navigation and state management.
1263
- </Typography>
1264
- {selectedPage === 'notifications' && (
1265
- <Typography variant="body2" sx={{ mt: 2, fontStyle: 'italic' }}>
1266
- All notifications have been marked as read.
1267
- </Typography>
1268
- )}
1269
- </Box>
1270
- </Box>
1271
- </Box>
1272
- );
1273
- };
1274
-
1275
- return <IntegrationComponent />;
1276
- },
1277
- play: async ({ canvasElement }) => {
1278
- const canvas = within(canvasElement);
1279
-
1280
- // Test initial state
1281
- expect(canvas.getByText('Welcome Home')).toBeInTheDocument();
1282
-
1283
- // Test drawer opening
1284
- const menuButton = canvas.getByTestId('integration-menu-button');
1285
- await userEvent.click(menuButton);
1286
-
1287
- await waitFor(() => {
1288
- const drawer = document.querySelector('[data-testid="integration-main-drawer"]');
1289
- expect(drawer).toBeInTheDocument();
1290
- const appName = Array.from(document.querySelectorAll('*')).find(
1291
- (el) => el.textContent === 'App Name',
1292
- );
1293
- expect(appName).toBeInTheDocument();
1294
- });
1295
-
1296
- // Test navigation to dashboard
1297
- const dashboardItem = document.querySelector('[data-testid="integration-nav-dashboard"]');
1298
- await userEvent.click(dashboardItem);
1299
-
1300
- await waitFor(() => {
1301
- const dashboardOverview = canvas.getByText('Dashboard Overview');
1302
- expect(dashboardOverview).toBeInTheDocument();
1303
- });
1304
-
1305
- // Test notification badge functionality
1306
- const notificationsItem = document.querySelector(
1307
- '[data-testid="integration-nav-notifications"]',
1308
- );
1309
-
1310
- // Should show notification badge
1311
- const notificationBadge = Array.from(document.querySelectorAll('*')).find(
1312
- (el) => el.textContent === '3',
1313
- );
1314
- expect(notificationBadge).toBeInTheDocument();
1315
-
1316
- await userEvent.click(notificationsItem);
1317
-
1318
- await waitFor(() => {
1319
- const notificationCenter = canvas.getByText('Notification Center');
1320
- expect(notificationCenter).toBeInTheDocument();
1321
- const notificationMessage = canvas.getByText('All notifications have been marked as read.');
1322
- expect(notificationMessage).toBeInTheDocument();
1323
- });
1324
-
1325
- // Notification badge should be cleared
1326
- await waitFor(() => {
1327
- const notificationText = canvas.queryByText('3 notifications');
1328
- expect(notificationText).not.toBeInTheDocument();
1329
- });
1330
-
1331
- // Test drawer closing
1332
- const closeButton = document.querySelector('button[aria-label*="close" i]');
1333
- await userEvent.click(closeButton);
1334
-
1335
- await waitFor(() => {
1336
- const appName = Array.from(document.querySelectorAll('*')).find(
1337
- (el) => el.textContent === 'App Name',
1338
- );
1339
- // appName should be undefined (not found) when drawer is closed
1340
- expect(appName).toBeUndefined();
1341
- });
1342
- },
1343
- };