@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,1324 +0,0 @@
1
- import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
2
- import ChevronRightIcon from '@mui/icons-material/ChevronRight';
3
- import DashboardIcon from '@mui/icons-material/Dashboard';
4
- import HomeIcon from '@mui/icons-material/Home';
5
- import InboxIcon from '@mui/icons-material/Inbox';
6
- import MenuIcon from '@mui/icons-material/Menu';
7
- import PersonIcon from '@mui/icons-material/Person';
8
- import SendIcon from '@mui/icons-material/Send';
9
- import SettingsIcon from '@mui/icons-material/Settings';
10
- import StarIcon from '@mui/icons-material/Star';
11
- import {
12
- Avatar,
13
- Box,
14
- Button,
15
- IconButton,
16
- List,
17
- ListItemButton,
18
- ListItemIcon,
19
- ListItemText,
20
- Typography,
21
- } from '@mui/material';
22
- import type { Meta, StoryObj } from '@storybook/react-vite';
23
- import React, { useState } from 'react';
24
- import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
25
-
26
- import { Sidebar, SidebarContent, SidebarFooter,SidebarHeader } from './Sidebar';
27
-
28
- const meta: Meta<typeof Sidebar> = {
29
- title: 'Layout/Sidebar/Tests',
30
- component: Sidebar,
31
- parameters: {
32
- layout: 'fullscreen',
33
- chromatic: { disableSnapshot: false },
34
- docs: {
35
- description: {
36
- component:
37
- 'Comprehensive test suite for the Sidebar component covering interaction, accessibility, visual states, and performance.',
38
- },
39
- },
40
- },
41
- tags: ['autodocs', 'test', 'component:Sidebar'],
42
- argTypes: {
43
- variant: {
44
- control: { type: 'select' },
45
- options: ['fixed', 'collapsible', 'floating', 'glass'],
46
- },
47
- open: {
48
- control: { type: 'boolean' },
49
- },
50
- width: {
51
- control: { type: 'number' },
52
- },
53
- collapsedWidth: {
54
- control: { type: 'number' },
55
- },
56
- position: {
57
- control: { type: 'select' },
58
- options: ['left', 'right'],
59
- },
60
- },
61
- };
62
- export default meta;
63
- export type Story = StoryObj<typeof meta>;
64
-
65
- const navigationItems = [
66
- { icon: <HomeIcon />, label: 'Home', 'data-testid': 'nav-home' },
67
- { icon: <DashboardIcon />, label: 'Dashboard', 'data-testid': 'nav-dashboard' },
68
- { icon: <InboxIcon />, label: 'Inbox', 'data-testid': 'nav-inbox' },
69
- { icon: <StarIcon />, label: 'Favorites', 'data-testid': 'nav-favorites' },
70
- { icon: <SendIcon />, label: 'Sent', 'data-testid': 'nav-sent' },
71
- ];
72
-
73
- // Basic Interaction Tests
74
- const BasicInteractionComponent = () => {
75
- const [open, setOpen] = useState(true);
76
- const mockOnToggle = fn();
77
-
78
- return (
79
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
80
- <Sidebar variant="fixed" open={open} onToggle={mockOnToggle} data-testid="sidebar">
81
- <SidebarHeader data-testid="sidebar-header">
82
- <Typography variant="h6">Test Sidebar</Typography>
83
- </SidebarHeader>
84
-
85
- <SidebarContent data-testid="sidebar-content">
86
- <List>
87
- {navigationItems.map((item, index) => (
88
- <ListItemButton key={index} data-testid={item['data-testid']}>
89
- <ListItemIcon>{item.icon}</ListItemIcon>
90
- <ListItemText primary={item.label} />
91
- </ListItemButton>
92
- ))}
93
- </List>
94
- </SidebarContent>
95
-
96
- <SidebarFooter data-testid="sidebar-footer">
97
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
98
- <Avatar sx={{ width: 32, height: 32 }} data-testid="user-avatar">
99
- U
100
- </Avatar>
101
- <Typography variant="body2">Test User</Typography>
102
- </Box>
103
- </SidebarFooter>
104
- </Sidebar>
105
-
106
- <Box sx={{ flex: 1, p: 3 }}>
107
- <Typography variant="h5" data-testid="main-content">
108
- Main Content
109
- </Typography>
110
- <Button onClick={() => setOpen(!open)} data-testid="toggle-button">
111
- Toggle Sidebar
112
- </Button>
113
- </Box>
114
- </Box>
115
- );
116
- };
117
-
118
- export const BasicInteraction: Story = {
119
- render: () => <BasicInteractionComponent />,
120
- play: async ({ canvasElement }) => {
121
- const canvas = within(canvasElement);
122
-
123
- // Test basic rendering
124
- await expect(canvas.getByTestId('sidebar')).toBeInTheDocument();
125
- await expect(canvas.getByTestId('sidebar-header')).toBeInTheDocument();
126
- await expect(canvas.getByTestId('sidebar-content')).toBeInTheDocument();
127
- await expect(canvas.getByTestId('sidebar-footer')).toBeInTheDocument();
128
- await expect(canvas.getByTestId('main-content')).toBeInTheDocument();
129
-
130
- // Test navigation items are rendered
131
- await expect(canvas.getByTestId('nav-home')).toBeInTheDocument();
132
- await expect(canvas.getByTestId('nav-dashboard')).toBeInTheDocument();
133
- await expect(canvas.getByTestId('nav-inbox')).toBeInTheDocument();
134
- await expect(canvas.getByTestId('nav-favorites')).toBeInTheDocument();
135
- await expect(canvas.getByTestId('nav-sent')).toBeInTheDocument();
136
-
137
- // Test user avatar is rendered
138
- await expect(canvas.getByTestId('user-avatar')).toBeInTheDocument();
139
-
140
- // Test sidebar has expected width
141
- const sidebar = canvas.getByTestId('sidebar');
142
- const sidebarStyles = window.getComputedStyle(sidebar);
143
- await expect(sidebarStyles.width).toBe('280px');
144
- },
145
- };
146
-
147
- // Form Interaction Test
148
- const FormInteractionComponent = () => {
149
- const [selectedItem, setSelectedItem] = useState('');
150
-
151
- return (
152
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
153
- <Sidebar variant="fixed" data-testid="form-sidebar">
154
- <SidebarHeader>
155
- <Typography variant="h6">Form Sidebar</Typography>
156
- </SidebarHeader>
157
-
158
- <SidebarContent>
159
- <Box sx={{ p: 2 }}>
160
- <Typography variant="body1" gutterBottom>
161
- Selected: {selectedItem}
162
- </Typography>
163
- <List>
164
- {navigationItems.map((item, index) => (
165
- <ListItemButton
166
- key={index}
167
- data-testid={`form-${item['data-testid']}`}
168
- onClick={() => setSelectedItem(item.label)}
169
- selected={selectedItem === item.label}
170
- >
171
- <ListItemIcon>{item.icon}</ListItemIcon>
172
- <ListItemText primary={item.label} />
173
- </ListItemButton>
174
- ))}
175
- </List>
176
- </Box>
177
- </SidebarContent>
178
- </Sidebar>
179
-
180
- <Box sx={{ flex: 1, p: 3 }}>
181
- <Typography variant="h5">Form Content</Typography>
182
- <Typography data-testid="selected-display">
183
- Current selection: {selectedItem || 'None'}
184
- </Typography>
185
- </Box>
186
- </Box>
187
- );
188
- };
189
-
190
- export const FormInteraction: Story = {
191
- render: () => <FormInteractionComponent />,
192
- play: async ({ canvasElement }) => {
193
- const canvas = within(canvasElement);
194
-
195
- // Initially no item selected
196
- await expect(canvas.getByTestId('selected-display')).toHaveTextContent(
197
- 'Current selection: None',
198
- );
199
-
200
- // Click on Home item
201
- await userEvent.click(canvas.getByTestId('form-nav-home'));
202
- await waitFor(() => {
203
- expect(canvas.getByTestId('selected-display')).toHaveTextContent('Current selection: Home');
204
- });
205
-
206
- // Click on Dashboard item
207
- await userEvent.click(canvas.getByTestId('form-nav-dashboard'));
208
- await waitFor(() => {
209
- expect(canvas.getByTestId('selected-display')).toHaveTextContent(
210
- 'Current selection: Dashboard',
211
- );
212
- });
213
-
214
- // Click on Inbox item
215
- await userEvent.click(canvas.getByTestId('form-nav-inbox'));
216
- await waitFor(() => {
217
- expect(canvas.getByTestId('selected-display')).toHaveTextContent('Current selection: Inbox');
218
- });
219
- },
220
- };
221
-
222
- // Keyboard Navigation Test
223
- export const KeyboardNavigation: Story = {
224
- render: () => (
225
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
226
- <Sidebar variant="fixed" data-testid="keyboard-sidebar">
227
- <SidebarHeader>
228
- <Typography variant="h6">Keyboard Navigation</Typography>
229
- </SidebarHeader>
230
-
231
- <SidebarContent>
232
- <List>
233
- {navigationItems.map((item, index) => (
234
- <ListItemButton
235
- key={index}
236
- data-testid={`keyboard-${item['data-testid']}`}
237
- tabIndex={0}
238
- role="button"
239
- aria-label={`Navigate to ${item.label}`}
240
- >
241
- <ListItemIcon>{item.icon}</ListItemIcon>
242
- <ListItemText primary={item.label} />
243
- </ListItemButton>
244
- ))}
245
- </List>
246
- </SidebarContent>
247
-
248
- <SidebarFooter>
249
- <Button data-testid="footer-button" tabIndex={0}>
250
- Settings
251
- </Button>
252
- </SidebarFooter>
253
- </Sidebar>
254
-
255
- <Box sx={{ flex: 1, p: 3 }}>
256
- <Typography variant="h5">Main Content</Typography>
257
- <Button data-testid="main-button" tabIndex={0}>
258
- Main Action
259
- </Button>
260
- </Box>
261
- </Box>
262
- ),
263
- play: async ({ canvasElement }) => {
264
- const canvas = within(canvasElement);
265
-
266
- // Test Tab navigation through sidebar items. The first nav item is the
267
- // first focusable node in the story, so tabbing in from the body reaches it
268
- // — the old .focus() call asserted only that focus() works.
269
- const firstNavItem = canvas.getByTestId('keyboard-nav-home');
270
- await userEvent.tab();
271
- await waitFor(() => expect(firstNavItem).toHaveFocus());
272
-
273
- // Enter key activation, checked here while focus is already on the item
274
- // rather than by re-focusing it at the end of the journey.
275
- await userEvent.keyboard('{Enter}');
276
- await expect(firstNavItem).toBeInTheDocument();
277
-
278
- // Tab to next item
279
- await userEvent.tab();
280
- await waitFor(() => expect(canvas.getByTestId('keyboard-nav-dashboard')).toHaveFocus());
281
-
282
- // Tab to next item
283
- await userEvent.tab();
284
- await waitFor(() => expect(canvas.getByTestId('keyboard-nav-inbox')).toHaveFocus());
285
-
286
- // Tab to footer button
287
- await userEvent.tab();
288
- await userEvent.tab();
289
- await userEvent.tab();
290
- await waitFor(() => expect(canvas.getByTestId('footer-button')).toHaveFocus());
291
-
292
- // Tab to main content
293
- await userEvent.tab();
294
- await waitFor(() => expect(canvas.getByTestId('main-button')).toHaveFocus());
295
- },
296
- };
297
-
298
- // Screen Reader Accessibility Test
299
- export const ScreenReader: Story = {
300
- render: () => (
301
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
302
- <Sidebar
303
- variant="fixed"
304
- data-testid="screen-reader-sidebar"
305
- role="navigation"
306
- aria-label="Main navigation sidebar"
307
- >
308
- <SidebarHeader>
309
- <Typography variant="h6" role="banner">
310
- Screen Reader Test
311
- </Typography>
312
- </SidebarHeader>
313
-
314
- <SidebarContent>
315
- <nav role="navigation" aria-label="Primary navigation">
316
- <List role="menu">
317
- {navigationItems.map((item, index) => (
318
- <ListItemButton
319
- key={index}
320
- data-testid={`sr-${item['data-testid']}`}
321
- role="menuitem"
322
- aria-label={`Navigate to ${item.label} section`}
323
- tabIndex={0}
324
- >
325
- <ListItemIcon aria-hidden="true">{item.icon}</ListItemIcon>
326
- <ListItemText primary={item.label} />
327
- </ListItemButton>
328
- ))}
329
- </List>
330
- </nav>
331
- </SidebarContent>
332
-
333
- <SidebarFooter>
334
- <Box
335
- role="contentinfo"
336
- aria-label="User information"
337
- sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
338
- >
339
- <Avatar sx={{ width: 32, height: 32 }} aria-label="User avatar" data-testid="sr-avatar">
340
- U
341
- </Avatar>
342
- <Box>
343
- <Typography variant="body2">Screen Reader User</Typography>
344
- <Typography variant="caption" color="text.secondary">
345
- sr-user@example.com
346
- </Typography>
347
- </Box>
348
- </Box>
349
- </SidebarFooter>
350
- </Sidebar>
351
-
352
- <main role="main" aria-label="Main content area" style={{ flex: 1, padding: '24px' }}>
353
- <Typography variant="h5">Main Content Area</Typography>
354
- <Typography variant="body1">This area contains the main application content.</Typography>
355
- </main>
356
- </Box>
357
- ),
358
- play: async ({ canvasElement }) => {
359
- const canvas = within(canvasElement);
360
-
361
- // Test ARIA attributes
362
- const sidebar = canvas.getByTestId('screen-reader-sidebar');
363
- await expect(sidebar).toHaveAttribute('role', 'navigation');
364
- await expect(sidebar).toHaveAttribute('aria-label', 'Main navigation sidebar');
365
-
366
- // Test navigation structure
367
- const navigation = canvas.getByRole('navigation', { name: 'Primary navigation' });
368
- await expect(navigation).toBeInTheDocument();
369
-
370
- const menu = canvas.getByRole('menu');
371
- await expect(menu).toBeInTheDocument();
372
-
373
- // Test menu items have proper ARIA labels
374
- const homeMenuItem = canvas.getByTestId('sr-nav-home');
375
- await expect(homeMenuItem).toHaveAttribute('role', 'menuitem');
376
- await expect(homeMenuItem).toHaveAttribute('aria-label', 'Navigate to Home section');
377
-
378
- const dashboardMenuItem = canvas.getByTestId('sr-nav-dashboard');
379
- await expect(dashboardMenuItem).toHaveAttribute('aria-label', 'Navigate to Dashboard section');
380
-
381
- // Test avatar accessibility
382
- const avatar = canvas.getByTestId('sr-avatar');
383
- await expect(avatar).toHaveAttribute('aria-label', 'User avatar');
384
-
385
- // Test main content area
386
- const main = canvas.getByRole('main');
387
- await expect(main).toHaveAttribute('aria-label', 'Main content area');
388
-
389
- // Test contentinfo role
390
- const userInfo = canvas.getByRole('contentinfo');
391
- await expect(userInfo).toHaveAttribute('aria-label', 'User information');
392
- },
393
- };
394
-
395
- // Focus Management Test
396
- const FocusManagementComponent = () => {
397
- const [sidebarVisible, setSidebarVisible] = useState(true);
398
-
399
- return (
400
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
401
- {sidebarVisible && (
402
- <Sidebar variant="fixed" data-testid="focus-sidebar">
403
- <SidebarHeader>
404
- <Typography variant="h6">Focus Management</Typography>
405
- <Button
406
- data-testid="hide-sidebar-button"
407
- onClick={() => setSidebarVisible(false)}
408
- size="small"
409
- >
410
- Hide Sidebar
411
- </Button>
412
- </SidebarHeader>
413
-
414
- <SidebarContent>
415
- <List>
416
- {navigationItems.slice(0, 3).map((item, index) => (
417
- <ListItemButton
418
- key={index}
419
- data-testid={`focus-${item['data-testid']}`}
420
- tabIndex={0}
421
- >
422
- <ListItemIcon>{item.icon}</ListItemIcon>
423
- <ListItemText primary={item.label} />
424
- </ListItemButton>
425
- ))}
426
- </List>
427
- </SidebarContent>
428
-
429
- <SidebarFooter>
430
- <Button data-testid="sidebar-action" tabIndex={0}>
431
- Sidebar Action
432
- </Button>
433
- </SidebarFooter>
434
- </Sidebar>
435
- )}
436
-
437
- <Box sx={{ flex: 1, p: 3 }}>
438
- <Typography variant="h5">Focus Management Test</Typography>
439
- <Button
440
- data-testid="show-sidebar-button"
441
- onClick={() => setSidebarVisible(true)}
442
- style={{ display: sidebarVisible ? 'none' : 'block' }}
443
- >
444
- Show Sidebar
445
- </Button>
446
- <Button data-testid="main-focus-button" tabIndex={0}>
447
- Main Button
448
- </Button>
449
- </Box>
450
- </Box>
451
- );
452
- };
453
-
454
- export const FocusManagement: Story = {
455
- render: () => <FocusManagementComponent />,
456
- play: async ({ canvasElement }) => {
457
- const canvas = within(canvasElement);
458
-
459
- // Test initial focus on first sidebar item
460
- const firstItem = canvas.getByTestId('focus-nav-home');
461
- await userEvent.tab();
462
- await waitFor(() => expect(firstItem).toHaveFocus());
463
-
464
- // Test focus moves through sidebar elements
465
- await userEvent.tab();
466
- await waitFor(() => expect(canvas.getByTestId('focus-nav-dashboard')).toHaveFocus());
467
-
468
- await userEvent.tab();
469
- await waitFor(() => expect(canvas.getByTestId('focus-nav-inbox')).toHaveFocus());
470
-
471
- await userEvent.tab();
472
- await waitFor(() => expect(canvas.getByTestId('sidebar-action')).toHaveFocus());
473
-
474
- // Test focus moves to main content
475
- await userEvent.tab();
476
- await waitFor(() => expect(canvas.getByTestId('main-focus-button')).toHaveFocus());
477
-
478
- // Test hiding sidebar
479
- const hideButton = canvas.getByTestId('hide-sidebar-button');
480
- await userEvent.click(hideButton);
481
-
482
- // Verify sidebar is hidden
483
- await waitFor(() => {
484
- expect(canvas.queryByTestId('focus-sidebar')).not.toBeInTheDocument();
485
- });
486
-
487
- // Test showing sidebar again
488
- const showButton = canvas.getByTestId('show-sidebar-button');
489
- await userEvent.click(showButton);
490
-
491
- // Verify sidebar is visible again
492
- await waitFor(() => {
493
- expect(canvas.getByTestId('focus-sidebar')).toBeInTheDocument();
494
- });
495
- },
496
- };
497
-
498
- // Responsive Design Test
499
- export const ResponsiveDesign: Story = {
500
- parameters: {
501
- viewport: {
502
- viewports: {
503
- mobile: { name: 'Mobile', styles: { width: '375px', height: '667px' } },
504
- tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
505
- desktop: { name: 'Desktop', styles: { width: '1200px', height: '800px' } },
506
- },
507
- },
508
- },
509
- render: () => (
510
- <Box sx={{ display: 'flex', height: '100vh', bgcolor: 'grey.50' }}>
511
- <Sidebar variant="fixed" width={280} data-testid="responsive-sidebar">
512
- <SidebarHeader>
513
- <Typography variant="h6" noWrap>
514
- Responsive Sidebar
515
- </Typography>
516
- </SidebarHeader>
517
-
518
- <SidebarContent>
519
- <List>
520
- {navigationItems.map((item, index) => (
521
- <ListItemButton key={index} data-testid={`resp-${item['data-testid']}`}>
522
- <ListItemIcon>{item.icon}</ListItemIcon>
523
- <ListItemText primary={item.label} primaryTypographyProps={{ noWrap: true }} />
524
- </ListItemButton>
525
- ))}
526
- </List>
527
- </SidebarContent>
528
-
529
- <SidebarFooter>
530
- <Typography variant="caption" noWrap data-testid="responsive-footer">
531
- Responsive Footer
532
- </Typography>
533
- </SidebarFooter>
534
- </Sidebar>
535
-
536
- <Box
537
- sx={{
538
- flex: 1,
539
- p: { xs: 1, sm: 2, md: 3 },
540
- overflow: 'auto',
541
- }}
542
- >
543
- <Typography variant="h5" gutterBottom>
544
- Responsive Content
545
- </Typography>
546
- <Typography variant="body1" data-testid="responsive-content">
547
- This content adapts to different screen sizes. The sidebar maintains its functionality
548
- across all viewport sizes.
549
- </Typography>
550
- </Box>
551
- </Box>
552
- ),
553
- play: async ({ canvasElement }) => {
554
- const canvas = within(canvasElement);
555
-
556
- // Test sidebar is present
557
- const sidebar = canvas.getByTestId('responsive-sidebar');
558
- await expect(sidebar).toBeInTheDocument();
559
-
560
- // Test sidebar maintains structure
561
- await expect(canvas.getByTestId('resp-nav-home')).toBeInTheDocument();
562
- await expect(canvas.getByTestId('resp-nav-dashboard')).toBeInTheDocument();
563
- await expect(canvas.getByTestId('responsive-footer')).toBeInTheDocument();
564
- await expect(canvas.getByTestId('responsive-content')).toBeInTheDocument();
565
-
566
- // Test sidebar width
567
- const sidebarStyles = window.getComputedStyle(sidebar);
568
- await expect(sidebarStyles.width).toBe('280px');
569
-
570
- // Test overflow handling
571
- await expect(sidebarStyles.overflow).toBe('hidden');
572
- // Test that height is set (could be 100vh or computed pixels)
573
- await expect(parseInt(sidebarStyles.height) > 0).toBe(true);
574
- },
575
- };
576
-
577
- // Theme Variations Test
578
- const ThemeVariationsComponent = () => {
579
- const [variant, setVariant] = useState<'fixed' | 'glass' | 'floating'>('fixed');
580
-
581
- const backgroundStyles =
582
- variant === 'glass'
583
- ? { background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }
584
- : { bgcolor: 'grey.100' };
585
-
586
- return (
587
- <Box sx={{ ...backgroundStyles, height: '500px', position: 'relative' }}>
588
- <Box sx={{ display: 'flex', height: '100%' }}>
589
- <Sidebar variant={variant} data-testid="theme-sidebar">
590
- <SidebarHeader>
591
- <Typography variant="h6" sx={{ color: variant === 'glass' ? 'white' : 'inherit' }}>
592
- {variant.charAt(0).toUpperCase() + variant.slice(1)} Theme
593
- </Typography>
594
- </SidebarHeader>
595
-
596
- <SidebarContent>
597
- <List>
598
- {navigationItems.slice(0, 3).map((item, index) => (
599
- <ListItemButton
600
- key={index}
601
- data-testid={`theme-${item['data-testid']}`}
602
- sx={{ color: variant === 'glass' ? 'white' : 'inherit' }}
603
- >
604
- <ListItemIcon sx={{ color: variant === 'glass' ? 'white' : 'inherit' }}>
605
- {item.icon}
606
- </ListItemIcon>
607
- <ListItemText primary={item.label} />
608
- </ListItemButton>
609
- ))}
610
- </List>
611
- </SidebarContent>
612
-
613
- <SidebarFooter>
614
- <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
615
- {(['fixed', 'glass', 'floating'] as const).map((themeVariant) => (
616
- <Button
617
- key={themeVariant}
618
- size="small"
619
- variant={variant === themeVariant ? 'contained' : 'outlined'}
620
- onClick={() => setVariant(themeVariant)}
621
- data-testid={`theme-${themeVariant}-button`}
622
- sx={{
623
- color: variant === 'glass' ? 'white' : 'inherit',
624
- borderColor: variant === 'glass' ? 'white' : 'inherit',
625
- }}
626
- >
627
- {themeVariant}
628
- </Button>
629
- ))}
630
- </Box>
631
- </SidebarFooter>
632
- </Sidebar>
633
-
634
- <Box sx={{ flex: 1, p: 3, color: variant === 'glass' ? 'white' : 'inherit' }}>
635
- <Typography variant="h5" gutterBottom data-testid="theme-title">
636
- {variant.charAt(0).toUpperCase() + variant.slice(1)} Variant
637
- </Typography>
638
- <Typography variant="body1" data-testid="theme-description">
639
- Current theme variant: {variant}
640
- </Typography>
641
- </Box>
642
- </Box>
643
- </Box>
644
- );
645
- };
646
-
647
- export const ThemeVariations: Story = {
648
- render: () => <ThemeVariationsComponent />,
649
- play: async ({ canvasElement }) => {
650
- const canvas = within(canvasElement);
651
-
652
- // Test initial fixed theme
653
- await expect(canvas.getByTestId('theme-title')).toHaveTextContent('Fixed Variant');
654
- await expect(canvas.getByTestId('theme-description')).toHaveTextContent(
655
- 'Current theme variant: fixed',
656
- );
657
-
658
- // Test switching to glass theme
659
- await userEvent.click(canvas.getByTestId('theme-glass-button'));
660
- await waitFor(() => {
661
- expect(canvas.getByTestId('theme-title')).toHaveTextContent('Glass Variant');
662
- expect(canvas.getByTestId('theme-description')).toHaveTextContent(
663
- 'Current theme variant: glass',
664
- );
665
- });
666
-
667
- // Test switching to floating theme
668
- await userEvent.click(canvas.getByTestId('theme-floating-button'));
669
- await waitFor(() => {
670
- expect(canvas.getByTestId('theme-title')).toHaveTextContent('Floating Variant');
671
- expect(canvas.getByTestId('theme-description')).toHaveTextContent(
672
- 'Current theme variant: floating',
673
- );
674
- });
675
-
676
- // Test switching back to fixed theme
677
- await userEvent.click(canvas.getByTestId('theme-fixed-button'));
678
- await waitFor(() => {
679
- expect(canvas.getByTestId('theme-title')).toHaveTextContent('Fixed Variant');
680
- expect(canvas.getByTestId('theme-description')).toHaveTextContent(
681
- 'Current theme variant: fixed',
682
- );
683
- });
684
-
685
- // Verify sidebar maintains structure across theme changes
686
- await expect(canvas.getByTestId('theme-sidebar')).toBeInTheDocument();
687
- await expect(canvas.getByTestId('theme-nav-home')).toBeInTheDocument();
688
- await expect(canvas.getByTestId('theme-nav-dashboard')).toBeInTheDocument();
689
- await expect(canvas.getByTestId('theme-nav-inbox')).toBeInTheDocument();
690
- },
691
- };
692
-
693
- // Visual States Test
694
- const VisualStatesComponent = () => {
695
- const [collapsed, setCollapsed] = useState(false);
696
-
697
- return (
698
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
699
- <Sidebar
700
- variant="collapsible"
701
- open={!collapsed}
702
- width={280}
703
- collapsedWidth={64}
704
- data-testid="visual-states-sidebar"
705
- >
706
- <SidebarHeader>
707
- <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
708
- <Typography variant="h6" sx={{ display: collapsed ? 'none' : 'block' }}>
709
- Visual States
710
- </Typography>
711
- <IconButton
712
- onClick={() => setCollapsed(!collapsed)}
713
- data-testid="collapse-toggle"
714
- size="small"
715
- >
716
- {collapsed ? <ChevronRightIcon /> : <ChevronLeftIcon />}
717
- </IconButton>
718
- </Box>
719
- </SidebarHeader>
720
-
721
- <SidebarContent>
722
- <List>
723
- {navigationItems.map((item, index) => (
724
- <ListItemButton
725
- key={index}
726
- data-testid={`visual-${item['data-testid']}`}
727
- sx={{
728
- '&:hover': {
729
- backgroundColor: 'action.hover',
730
- },
731
- '&:focus': {
732
- backgroundColor: 'action.selected',
733
- },
734
- }}
735
- >
736
- <ListItemIcon
737
- sx={{
738
- minWidth: collapsed ? 40 : 56,
739
- justifyContent: 'center',
740
- }}
741
- >
742
- {item.icon}
743
- </ListItemIcon>
744
- {!collapsed && <ListItemText primary={item.label} />}
745
- </ListItemButton>
746
- ))}
747
- </List>
748
- </SidebarContent>
749
-
750
- <SidebarFooter>
751
- {!collapsed ? (
752
- <Typography variant="caption" data-testid="expanded-footer">
753
- Expanded Footer
754
- </Typography>
755
- ) : (
756
- <Box sx={{ display: 'flex', justifyContent: 'center' }}>
757
- <Avatar sx={{ width: 24, height: 24 }} data-testid="collapsed-avatar">
758
- U
759
- </Avatar>
760
- </Box>
761
- )}
762
- </SidebarFooter>
763
- </Sidebar>
764
-
765
- <Box sx={{ flex: 1, p: 3 }}>
766
- <Typography variant="h5" gutterBottom>
767
- Visual States Test
768
- </Typography>
769
- <Typography variant="body1" data-testid="collapse-status">
770
- Sidebar is {collapsed ? 'collapsed' : 'expanded'}
771
- </Typography>
772
- <Typography variant="body2" color="text.secondary">
773
- Toggle the sidebar to see different visual states and transitions.
774
- </Typography>
775
- </Box>
776
- </Box>
777
- );
778
- };
779
-
780
- export const VisualStates: Story = {
781
- render: () => <VisualStatesComponent />,
782
- play: async ({ canvasElement }) => {
783
- const canvas = within(canvasElement);
784
-
785
- // Test initial expanded state
786
- const sidebar = canvas.getByTestId('visual-states-sidebar');
787
- await expect(sidebar).toBeInTheDocument();
788
- await expect(canvas.getByTestId('collapse-status')).toHaveTextContent('Sidebar is expanded');
789
- await expect(canvas.getByTestId('expanded-footer')).toBeInTheDocument();
790
-
791
- // Test sidebar width when expanded
792
- await expect(window.getComputedStyle(sidebar).width).toBe('280px');
793
-
794
- // Test collapsing
795
- const collapseToggle = canvas.getByTestId('collapse-toggle');
796
- await userEvent.click(collapseToggle);
797
-
798
- await waitFor(() => {
799
- expect(canvas.getByTestId('collapse-status')).toHaveTextContent('Sidebar is collapsed');
800
- });
801
-
802
- // Test sidebar width when collapsed. Read the computed style inside the
803
- // waitFor rather than reassigning an outer variable from it — the callback
804
- // reruns, so the shared binding held whichever value the last retry left.
805
- await waitFor(() => {
806
- expect(window.getComputedStyle(sidebar).width).toBe('64px');
807
- });
808
-
809
- // Test collapsed avatar is visible
810
- await expect(canvas.getByTestId('collapsed-avatar')).toBeInTheDocument();
811
-
812
- // Test expanding again
813
- await userEvent.click(collapseToggle);
814
-
815
- await waitFor(() => {
816
- expect(canvas.getByTestId('collapse-status')).toHaveTextContent('Sidebar is expanded');
817
- });
818
-
819
- // Test navigation items are still clickable and accessible
820
- await expect(canvas.getByTestId('visual-nav-home')).toBeInTheDocument();
821
- await expect(canvas.getByTestId('visual-nav-dashboard')).toBeInTheDocument();
822
-
823
- // Test that navigation items remain interactive during visual state transitions
824
- const homeItem = canvas.getByTestId('visual-nav-home');
825
-
826
- // Verify item is clickable
827
- await userEvent.click(homeItem);
828
-
829
- // Verify item is still present and enabled after click
830
- await expect(homeItem).toBeInTheDocument();
831
- await expect(homeItem).toBeEnabled();
832
- },
833
- };
834
-
835
- // Performance Test
836
- const PerformanceComponent = () => {
837
- const [itemCount, setItemCount] = useState(50);
838
- const [renderTime, setRenderTime] = useState<number>(0);
839
-
840
- const generateItems = (count: number) => Array.from({ length: count }, (_, index) => ({
841
- icon: <HomeIcon />,
842
- label: `Item ${index + 1}`,
843
- 'data-testid': `perf-item-${index}`,
844
- }));
845
-
846
- const handleRender = () => {
847
- const start = window.performance.now();
848
- // Force re-render
849
- setItemCount((prev) => prev + 0);
850
- const end = window.performance.now();
851
- setRenderTime(end - start);
852
- };
853
-
854
- const items = generateItems(itemCount);
855
-
856
- return (
857
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50' }}>
858
- <Sidebar variant="fixed" data-testid="performance-sidebar">
859
- <SidebarHeader>
860
- <Typography variant="h6">Performance Test</Typography>
861
- <Typography variant="caption" data-testid="render-time">
862
- Render time: {renderTime.toFixed(2)}ms
863
- </Typography>
864
- </SidebarHeader>
865
-
866
- <SidebarContent>
867
- <List data-testid="performance-list">
868
- {items.map((item, index) => (
869
- <ListItemButton key={index} data-testid={item['data-testid']}>
870
- <ListItemIcon>{item.icon}</ListItemIcon>
871
- <ListItemText primary={item.label} />
872
- </ListItemButton>
873
- ))}
874
- </List>
875
- </SidebarContent>
876
-
877
- <SidebarFooter>
878
- <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
879
- <Button size="small" onClick={() => setItemCount(25)} data-testid="perf-25-items">
880
- 25 Items
881
- </Button>
882
- <Button size="small" onClick={() => setItemCount(50)} data-testid="perf-50-items">
883
- 50 Items
884
- </Button>
885
- <Button size="small" onClick={() => setItemCount(100)} data-testid="perf-100-items">
886
- 100 Items
887
- </Button>
888
- <Button size="small" onClick={handleRender} data-testid="measure-render">
889
- Measure
890
- </Button>
891
- </Box>
892
- </SidebarFooter>
893
- </Sidebar>
894
-
895
- <Box sx={{ flex: 1, p: 3 }}>
896
- <Typography variant="h5" gutterBottom>
897
- Performance Metrics
898
- </Typography>
899
- <Typography variant="body1" data-testid="item-count">
900
- Current items: {itemCount}
901
- </Typography>
902
- <Typography variant="body2" color="text.secondary">
903
- Test sidebar performance with different numbers of items.
904
- </Typography>
905
- </Box>
906
- </Box>
907
- );
908
- };
909
-
910
- export const Performance: Story = {
911
- render: () => <PerformanceComponent />,
912
- play: async ({ canvasElement }) => {
913
- const canvas = within(canvasElement);
914
-
915
- // Test initial state
916
- await expect(canvas.getByTestId('performance-sidebar')).toBeInTheDocument();
917
- await expect(canvas.getByTestId('item-count')).toHaveTextContent('Current items: 50');
918
-
919
- // Test items are rendered
920
- await expect(canvas.getByTestId('perf-item-0')).toBeInTheDocument();
921
- await expect(canvas.getByTestId('perf-item-49')).toBeInTheDocument();
922
-
923
- // Test performance with different item counts
924
- await userEvent.click(canvas.getByTestId('perf-25-items'));
925
- await waitFor(() => {
926
- expect(canvas.getByTestId('item-count')).toHaveTextContent('Current items: 25');
927
- });
928
- await expect(canvas.getByTestId('perf-item-0')).toBeInTheDocument();
929
- await expect(canvas.getByTestId('perf-item-24')).toBeInTheDocument();
930
-
931
- await userEvent.click(canvas.getByTestId('perf-100-items'));
932
- await waitFor(() => {
933
- expect(canvas.getByTestId('item-count')).toHaveTextContent('Current items: 100');
934
- });
935
- await expect(canvas.getByTestId('perf-item-0')).toBeInTheDocument();
936
- await expect(canvas.getByTestId('perf-item-99')).toBeInTheDocument();
937
-
938
- // Test render time measurement
939
- await userEvent.click(canvas.getByTestId('measure-render'));
940
- const renderTimeElement = canvas.getByTestId('render-time');
941
- await expect(renderTimeElement).toBeInTheDocument();
942
-
943
- // Verify list maintains scroll functionality
944
- const performanceList = canvas.getByTestId('performance-list');
945
- await expect(performanceList).toBeInTheDocument();
946
- },
947
- };
948
-
949
- // Edge Cases Test
950
- const EdgeCasesComponent = () => {
951
- const [contentType, setContentType] = useState<'normal' | 'empty' | 'overflow' | 'special-chars'>(
952
- 'normal',
953
- );
954
-
955
- const getContent = () => {
956
- switch (contentType) {
957
- case 'empty':
958
- return [];
959
- case 'overflow':
960
- return Array.from({ length: 100 }, (_, i) => ({
961
- icon: <HomeIcon />,
962
- label: `Very long navigation item name that might overflow the container ${i + 1}`,
963
- 'data-testid': `overflow-item-${i}`,
964
- }));
965
- case 'special-chars':
966
- return [
967
- { icon: <HomeIcon />, label: '🏠 Home with Emoji', 'data-testid': 'emoji-item' },
968
- {
969
- icon: <DashboardIcon />,
970
- label: 'Spëçíàl Çhäracters',
971
- 'data-testid': 'special-chars-item',
972
- },
973
- { icon: <SettingsIcon />, label: '中文字符', 'data-testid': 'chinese-item' },
974
- { icon: <PersonIcon />, label: 'العربية', 'data-testid': 'arabic-item' },
975
- { icon: <InboxIcon />, label: 'Русский', 'data-testid': 'russian-item' },
976
- ];
977
- default:
978
- return navigationItems;
979
- }
980
- };
981
-
982
- const content = getContent();
983
-
984
- return (
985
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50' }}>
986
- <Sidebar variant="fixed" data-testid="edge-cases-sidebar">
987
- <SidebarHeader>
988
- <Typography variant="h6" noWrap>
989
- Edge Cases Test
990
- </Typography>
991
- <Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 1 }}>
992
- {(['normal', 'empty', 'overflow', 'special-chars'] as const).map((type) => (
993
- <Button
994
- key={type}
995
- size="small"
996
- variant={contentType === type ? 'contained' : 'outlined'}
997
- onClick={() => setContentType(type)}
998
- data-testid={`edge-${type}-button`}
999
- sx={{ fontSize: '0.7rem', minWidth: 'auto' }}
1000
- >
1001
- {type}
1002
- </Button>
1003
- ))}
1004
- </Box>
1005
- </SidebarHeader>
1006
-
1007
- <SidebarContent data-testid="edge-cases-content">
1008
- {content.length === 0 ? (
1009
- <Box sx={{ p: 2, textAlign: 'center' }}>
1010
- <Typography variant="body2" color="text.secondary" data-testid="empty-state">
1011
- No navigation items available
1012
- </Typography>
1013
- </Box>
1014
- ) : (
1015
- <List>
1016
- {content.map((item, index) => (
1017
- <ListItemButton key={index} data-testid={item['data-testid']}>
1018
- <ListItemIcon>{item.icon}</ListItemIcon>
1019
- <ListItemText
1020
- primary={item.label}
1021
- primaryTypographyProps={{
1022
- noWrap: contentType === 'overflow',
1023
- title: item.label,
1024
- }}
1025
- />
1026
- </ListItemButton>
1027
- ))}
1028
- </List>
1029
- )}
1030
- </SidebarContent>
1031
-
1032
- <SidebarFooter>
1033
- <Typography variant="caption" data-testid="content-type-display">
1034
- Current: {contentType} ({content.length} items)
1035
- </Typography>
1036
- </SidebarFooter>
1037
- </Sidebar>
1038
-
1039
- <Box sx={{ flex: 1, p: 3 }}>
1040
- <Typography variant="h5" gutterBottom>
1041
- Edge Cases
1042
- </Typography>
1043
- <Typography variant="body1" data-testid="edge-description">
1044
- Testing sidebar with {contentType} content
1045
- </Typography>
1046
- </Box>
1047
- </Box>
1048
- );
1049
- };
1050
-
1051
- export const EdgeCases: Story = {
1052
- render: () => <EdgeCasesComponent />,
1053
- play: async ({ canvasElement }) => {
1054
- const canvas = within(canvasElement);
1055
-
1056
- // Test normal state
1057
- await expect(canvas.getByTestId('edge-cases-sidebar')).toBeInTheDocument();
1058
- await expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
1059
- 'Current: normal (5 items)',
1060
- );
1061
- await expect(canvas.getByTestId('edge-description')).toHaveTextContent(
1062
- 'Testing sidebar with normal content',
1063
- );
1064
-
1065
- // Test empty state
1066
- await userEvent.click(canvas.getByTestId('edge-empty-button'));
1067
- await waitFor(() => {
1068
- expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
1069
- 'Current: empty (0 items)',
1070
- );
1071
- expect(canvas.getByTestId('empty-state')).toHaveTextContent('No navigation items available');
1072
- });
1073
-
1074
- // Test overflow state
1075
- await userEvent.click(canvas.getByTestId('edge-overflow-button'));
1076
- await waitFor(() => {
1077
- expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
1078
- 'Current: overflow (100 items)',
1079
- );
1080
- });
1081
- await expect(canvas.getByTestId('overflow-item-0')).toBeInTheDocument();
1082
- await expect(canvas.getByTestId('overflow-item-99')).toBeInTheDocument();
1083
-
1084
- // Test special characters
1085
- await userEvent.click(canvas.getByTestId('edge-special-chars-button'));
1086
- await waitFor(() => {
1087
- expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
1088
- 'Current: special-chars (5 items)',
1089
- );
1090
- });
1091
- await expect(canvas.getByTestId('emoji-item')).toHaveTextContent('🏠 Home with Emoji');
1092
- await expect(canvas.getByTestId('special-chars-item')).toHaveTextContent('Spëçíàl Çhäracters');
1093
- await expect(canvas.getByTestId('chinese-item')).toHaveTextContent('中文字符');
1094
- await expect(canvas.getByTestId('arabic-item')).toHaveTextContent('العربية');
1095
- await expect(canvas.getByTestId('russian-item')).toHaveTextContent('Русский');
1096
-
1097
- // Test back to normal
1098
- await userEvent.click(canvas.getByTestId('edge-normal-button'));
1099
- await waitFor(() => {
1100
- expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
1101
- 'Current: normal (5 items)',
1102
- );
1103
- });
1104
- },
1105
- };
1106
-
1107
- // Integration Test
1108
- const IntegrationComponent = () => {
1109
- const [activeSection, setActiveSection] = useState('home');
1110
- const [sidebarOpen, setSidebarOpen] = useState(true);
1111
- const [notifications, setNotifications] = useState(3);
1112
-
1113
- const integrationItems = [
1114
- {
1115
- id: 'home',
1116
- icon: <HomeIcon />,
1117
- label: 'Home',
1118
- 'data-testid': 'integration-home',
1119
- badge: null,
1120
- },
1121
- {
1122
- id: 'dashboard',
1123
- icon: <DashboardIcon />,
1124
- label: 'Dashboard',
1125
- 'data-testid': 'integration-dashboard',
1126
- badge: null,
1127
- },
1128
- {
1129
- id: 'inbox',
1130
- icon: <InboxIcon />,
1131
- label: 'Messages',
1132
- 'data-testid': 'integration-inbox',
1133
- badge: notifications > 0 ? notifications : null,
1134
- },
1135
- {
1136
- id: 'settings',
1137
- icon: <SettingsIcon />,
1138
- label: 'Settings',
1139
- 'data-testid': 'integration-settings',
1140
- badge: null,
1141
- },
1142
- ];
1143
-
1144
- return (
1145
- <Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50' }}>
1146
- <Sidebar
1147
- variant="collapsible"
1148
- open={sidebarOpen}
1149
- width={280}
1150
- collapsedWidth={64}
1151
- data-testid="integration-sidebar"
1152
- >
1153
- <SidebarHeader>
1154
- <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
1155
- <Typography variant="h6" sx={{ display: sidebarOpen ? 'block' : 'none' }}>
1156
- Integration Test
1157
- </Typography>
1158
- <IconButton
1159
- onClick={() => setSidebarOpen(!sidebarOpen)}
1160
- data-testid="integration-toggle"
1161
- size="small"
1162
- >
1163
- {sidebarOpen ? <ChevronLeftIcon /> : <MenuIcon />}
1164
- </IconButton>
1165
- </Box>
1166
- </SidebarHeader>
1167
-
1168
- <SidebarContent>
1169
- <List>
1170
- {integrationItems.map((item) => (
1171
- <ListItemButton
1172
- key={item.id}
1173
- data-testid={item['data-testid']}
1174
- selected={activeSection === item.id}
1175
- onClick={() => setActiveSection(item.id)}
1176
- >
1177
- <ListItemIcon
1178
- sx={{
1179
- minWidth: sidebarOpen ? 56 : 40,
1180
- justifyContent: 'center',
1181
- }}
1182
- >
1183
- <Box sx={{ position: 'relative' }}>
1184
- {item.icon}
1185
- {item.badge && (
1186
- <Box
1187
- sx={{
1188
- position: 'absolute',
1189
- top: -8,
1190
- right: -8,
1191
- backgroundColor: 'error.main',
1192
- color: 'white',
1193
- borderRadius: '50%',
1194
- width: 16,
1195
- height: 16,
1196
- display: 'flex',
1197
- alignItems: 'center',
1198
- justifyContent: 'center',
1199
- fontSize: '0.7rem',
1200
- }}
1201
- data-testid={`badge-${item.id}`}
1202
- >
1203
- {item.badge}
1204
- </Box>
1205
- )}
1206
- </Box>
1207
- </ListItemIcon>
1208
- {sidebarOpen && <ListItemText primary={item.label} />}
1209
- </ListItemButton>
1210
- ))}
1211
- </List>
1212
- </SidebarContent>
1213
-
1214
- <SidebarFooter>
1215
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
1216
- <Avatar sx={{ width: 32, height: 32 }}>I</Avatar>
1217
- {sidebarOpen && (
1218
- <Box>
1219
- <Typography variant="body2">Integration User</Typography>
1220
- <Button
1221
- size="small"
1222
- onClick={() => setNotifications(0)}
1223
- data-testid="clear-notifications"
1224
- disabled={notifications === 0}
1225
- >
1226
- Clear ({notifications})
1227
- </Button>
1228
- </Box>
1229
- )}
1230
- </Box>
1231
- </SidebarFooter>
1232
- </Sidebar>
1233
-
1234
- <Box sx={{ flex: 1, p: 3 }}>
1235
- <Typography variant="h5" gutterBottom data-testid="integration-title">
1236
- {integrationItems.find((item) => item.id === activeSection)?.label} Section
1237
- </Typography>
1238
- <Typography variant="body1" data-testid="integration-content">
1239
- Active section: {activeSection}
1240
- </Typography>
1241
- <Typography variant="body2" color="text.secondary">
1242
- Sidebar is {sidebarOpen ? 'expanded' : 'collapsed'}
1243
- </Typography>
1244
- <Button
1245
- variant="outlined"
1246
- onClick={() => setNotifications((prev) => prev + 1)}
1247
- data-testid="add-notification"
1248
- sx={{ mt: 2 }}
1249
- >
1250
- Add Notification
1251
- </Button>
1252
- </Box>
1253
- </Box>
1254
- );
1255
- };
1256
-
1257
- export const Integration: Story = {
1258
- render: () => <IntegrationComponent />,
1259
- play: async ({ canvasElement }) => {
1260
- const canvas = within(canvasElement);
1261
-
1262
- // Test initial state
1263
- await expect(canvas.getByTestId('integration-sidebar')).toBeInTheDocument();
1264
- await expect(canvas.getByTestId('integration-title')).toHaveTextContent('Home Section');
1265
- await expect(canvas.getByTestId('integration-content')).toHaveTextContent(
1266
- 'Active section: home',
1267
- );
1268
-
1269
- // Test notification badge
1270
- await expect(canvas.getByTestId('badge-inbox')).toHaveTextContent('3');
1271
-
1272
- // Test navigation
1273
- await userEvent.click(canvas.getByTestId('integration-dashboard'));
1274
- await waitFor(() => {
1275
- expect(canvas.getByTestId('integration-title')).toHaveTextContent('Dashboard Section');
1276
- expect(canvas.getByTestId('integration-content')).toHaveTextContent(
1277
- 'Active section: dashboard',
1278
- );
1279
- });
1280
-
1281
- await userEvent.click(canvas.getByTestId('integration-inbox'));
1282
- await waitFor(() => {
1283
- expect(canvas.getByTestId('integration-title')).toHaveTextContent('Messages Section');
1284
- expect(canvas.getByTestId('integration-content')).toHaveTextContent('Active section: inbox');
1285
- });
1286
-
1287
- // Test adding notifications
1288
- await userEvent.click(canvas.getByTestId('add-notification'));
1289
- await waitFor(() => {
1290
- expect(canvas.getByTestId('badge-inbox')).toHaveTextContent('4');
1291
- });
1292
-
1293
- // Test clearing notifications
1294
- await userEvent.click(canvas.getByTestId('clear-notifications'));
1295
- await waitFor(() => {
1296
- expect(canvas.queryByTestId('badge-inbox')).not.toBeInTheDocument();
1297
- });
1298
-
1299
- // Test sidebar collapse
1300
- await userEvent.click(canvas.getByTestId('integration-toggle'));
1301
- await waitFor(() => {
1302
- const sidebar = canvas.getByTestId('integration-sidebar');
1303
- const sidebarStyles = window.getComputedStyle(sidebar);
1304
- expect(sidebarStyles.width).toBe('64px');
1305
- });
1306
-
1307
- // Test sidebar expand
1308
- await userEvent.click(canvas.getByTestId('integration-toggle'));
1309
- await waitFor(() => {
1310
- const sidebar = canvas.getByTestId('integration-sidebar');
1311
- const sidebarStyles = window.getComputedStyle(sidebar);
1312
- expect(sidebarStyles.width).toBe('280px');
1313
- });
1314
-
1315
- // Test settings navigation
1316
- await userEvent.click(canvas.getByTestId('integration-settings'));
1317
- await waitFor(() => {
1318
- expect(canvas.getByTestId('integration-title')).toHaveTextContent('Settings Section');
1319
- expect(canvas.getByTestId('integration-content')).toHaveTextContent(
1320
- 'Active section: settings',
1321
- );
1322
- });
1323
- },
1324
- };