@hyphen/hyphen-components 7.3.2 → 7.3.3

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 (220) hide show
  1. package/dist/css/utilities.css +1 -1
  2. package/dist/css/variables.css +18 -28
  3. package/dist/hyphen-components.cjs.development.js +5717 -5021
  4. package/dist/hyphen-components.cjs.development.js.map +1 -1
  5. package/dist/hyphen-components.cjs.production.min.js +18 -2
  6. package/dist/hyphen-components.cjs.production.min.js.map +1 -1
  7. package/dist/hyphen-components.esm.js +5618 -4846
  8. package/dist/hyphen-components.esm.js.map +1 -1
  9. package/dist/index.d.ts +2693 -57
  10. package/dist/index.js +0 -1
  11. package/package.json +18 -19
  12. package/src/components/Badge/Badge.module.scss +6 -0
  13. package/src/components/Badge/Badge.stories.tsx +1 -0
  14. package/src/components/Badge/Badge.test.tsx +3 -2
  15. package/src/components/Badge/Badge.tsx +5 -3
  16. package/src/components/Box/Box.tsx +5 -2
  17. package/src/components/Button/Button.module.scss +1 -1
  18. package/src/components/Button/Button.test.tsx +2 -2
  19. package/src/components/Calendar/Calendar.test.tsx +262 -0
  20. package/src/components/Card/Card.tsx +2 -0
  21. package/src/components/CheckboxInput/components/Checkbox.module.scss +1 -1
  22. package/src/components/CheckboxInput/components/Checkbox.tsx +2 -0
  23. package/src/components/Details/Details.module.scss +2 -2
  24. package/src/components/Details/Details.tsx +2 -0
  25. package/src/components/Drawer/Drawer.stories.tsx +1 -1
  26. package/src/components/Drawer/Drawer.test.tsx +494 -56
  27. package/src/components/Drawer/Drawer.tsx +7 -1
  28. package/src/components/DropdownMenu/DropdownMenu.test.tsx +532 -12
  29. package/src/components/FormControl/FormControl.tsx +2 -0
  30. package/src/components/Formik/Formik.stories.tsx +30 -7
  31. package/src/components/Formik/FormikSelectInput/FormikSelectInput.tsx +6 -5
  32. package/src/components/Formik/FormikToggleGroup/FormikToggleGroup.tsx +1 -1
  33. package/src/components/HelpText/HelpText.tsx +2 -0
  34. package/src/components/Icon/Icon.stories.tsx +1 -1
  35. package/src/components/Icon/Icon.tsx +2 -0
  36. package/src/components/Modal/Modal.test.tsx +630 -81
  37. package/src/components/Modal/Modal.tsx +2 -0
  38. package/src/components/Modal/components/ModalFooter/ModalFooter.test.tsx +2 -2
  39. package/src/components/Popover/Popover.tsx +2 -0
  40. package/src/components/RadioGroup/RadioInput/RadioInput.tsx +2 -0
  41. package/src/components/SelectInput/SelectInput.stories.tsx +22 -22
  42. package/src/components/SelectInput/SelectInput.tsx +13 -9
  43. package/src/components/SelectInputInset/SelectInputInset.tsx +2 -0
  44. package/src/components/Sidebar/Sidebar.module.scss +4 -0
  45. package/src/components/Sidebar/Sidebar.stories.tsx +8 -4
  46. package/src/components/Sidebar/Sidebar.test.tsx +7 -4
  47. package/src/components/Sidebar/Sidebar.tsx +7 -4
  48. package/src/components/Table/Table.stories.tsx +102 -52
  49. package/src/components/TextInput/TextInput.tsx +2 -0
  50. package/src/components/TextInputInset/TextInputInset.tsx +2 -0
  51. package/src/components/TextareaInputInset/TextareaInputInset.tsx +2 -0
  52. package/src/components/TimePickerNative/TimePickerNative.stories.tsx +0 -1
  53. package/src/components/Toast/Toast.store.ts +1 -1
  54. package/src/components/Toast/Toast.stories.tsx +3 -2
  55. package/src/components/Toast/Toast.test.tsx +8 -6
  56. package/src/components/Toggle/Toggle.tsx +2 -0
  57. package/src/components/ToggleGroup/ToggleGroup.tsx +2 -0
  58. package/src/docs/Colors.mdx +0 -13
  59. package/src/lib/getColumnKeys.ts +3 -3
  60. package/src/lib/mergeRefs.ts +1 -1
  61. package/src/lib/tokens.ts +4 -4
  62. package/dist/components/Alert/Alert.constants.d.ts +0 -8
  63. package/dist/components/Alert/Alert.d.ts +0 -42
  64. package/dist/components/Alert/Alert.stories.d.ts +0 -12
  65. package/dist/components/Alert/Alert.types.d.ts +0 -7
  66. package/dist/components/AspectRatio/AspectRatio.d.ts +0 -3
  67. package/dist/components/AspectRatio/AspectRatio.stories.d.ts +0 -6
  68. package/dist/components/Badge/Badge.d.ts +0 -24
  69. package/dist/components/Badge/Badge.stories.d.ts +0 -8
  70. package/dist/components/Box/Box.d.ts +0 -247
  71. package/dist/components/Box/Box.stories.d.ts +0 -46
  72. package/dist/components/Button/Button.constants.d.ts +0 -3
  73. package/dist/components/Button/Button.d.ts +0 -53
  74. package/dist/components/Button/Button.stories.d.ts +0 -16
  75. package/dist/components/Calendar/Calendar.d.ts +0 -7
  76. package/dist/components/Calendar/Calendar.stories.d.ts +0 -12
  77. package/dist/components/Card/Card.d.ts +0 -17
  78. package/dist/components/Card/Card.stories.d.ts +0 -8
  79. package/dist/components/Card/components/CardFooter/CardFooter.d.ts +0 -13
  80. package/dist/components/Card/components/CardHeader/CardHeader.d.ts +0 -13
  81. package/dist/components/Card/components/CardSection/CardSection.d.ts +0 -46
  82. package/dist/components/Card/components/index.d.ts +0 -3
  83. package/dist/components/CheckboxInput/CheckboxInput.d.ts +0 -72
  84. package/dist/components/CheckboxInput/CheckboxInput.stories.d.ts +0 -18
  85. package/dist/components/CheckboxInput/components/Checkbox.d.ts +0 -71
  86. package/dist/components/CheckboxInput/components/CheckboxIcon.d.ts +0 -27
  87. package/dist/components/Collapsible/Collapsible.d.ts +0 -5
  88. package/dist/components/Collapsible/Collapsible.stories.d.ts +0 -9
  89. package/dist/components/Details/Details.d.ts +0 -15
  90. package/dist/components/Details/Details.stories.d.ts +0 -6
  91. package/dist/components/Details/DetailsSummary.d.ts +0 -7
  92. package/dist/components/Drawer/Drawer.d.ts +0 -105
  93. package/dist/components/Drawer/Drawer.stories.d.ts +0 -62
  94. package/dist/components/DropdownMenu/DropdownMenu.d.ts +0 -25
  95. package/dist/components/DropdownMenu/DropdownMenu.stories.d.ts +0 -9
  96. package/dist/components/FormControl/FormControl.d.ts +0 -38
  97. package/dist/components/FormLabel/FormLabel.d.ts +0 -41
  98. package/dist/components/FormLabel/FormLabel.stories.d.ts +0 -6
  99. package/dist/components/Formik/Formik.stories.d.ts +0 -18
  100. package/dist/components/Formik/FormikCheckboxInput/FormikCheckboxInput.d.ts +0 -12
  101. package/dist/components/Formik/FormikRadioGroup/FormikRadioGroup.d.ts +0 -12
  102. package/dist/components/Formik/FormikSelectInput/FormikSelectInput.d.ts +0 -13
  103. package/dist/components/Formik/FormikSelectInputInset/FormikSelectInputInset.d.ts +0 -12
  104. package/dist/components/Formik/FormikSelectInputNative/FormikSelectInputNative.d.ts +0 -12
  105. package/dist/components/Formik/FormikSwitch/FormikSwitch.d.ts +0 -12
  106. package/dist/components/Formik/FormikTextInput/FormikTextInput.d.ts +0 -12
  107. package/dist/components/Formik/FormikTextInputInset/FormikTextInputInset.d.ts +0 -12
  108. package/dist/components/Formik/FormikTextareaInput/FormikTextareaInput.d.ts +0 -12
  109. package/dist/components/Formik/FormikTextareaInputInset/FormikTextareaInputInset.d.ts +0 -12
  110. package/dist/components/Formik/FormikTimePicker/FormikTimePicker.d.ts +0 -12
  111. package/dist/components/Formik/FormikTimePickerNative/FormikTimePickerNative.d.ts +0 -12
  112. package/dist/components/Formik/FormikToggleGroup/FormikToggleGroup.d.ts +0 -20
  113. package/dist/components/Formik/FormikToggleGroupMulti/FormikToggleGroupMulti.d.ts +0 -18
  114. package/dist/components/Heading/Heading.constants.d.ts +0 -10
  115. package/dist/components/Heading/Heading.d.ts +0 -35
  116. package/dist/components/Heading/Heading.stories.d.ts +0 -9
  117. package/dist/components/HelpText/HelpText.d.ts +0 -12
  118. package/dist/components/Icon/Icon.d.ts +0 -22
  119. package/dist/components/Icon/Icon.stories.d.ts +0 -10
  120. package/dist/components/InputValidationMessage/InputValidationMessage.d.ts +0 -9
  121. package/dist/components/Modal/Modal.d.ts +0 -83
  122. package/dist/components/Modal/Modal.stories.d.ts +0 -13
  123. package/dist/components/Modal/components/ModalBody/ModalBody.d.ts +0 -4
  124. package/dist/components/Modal/components/ModalFooter/ModalFooter.d.ts +0 -4
  125. package/dist/components/Modal/components/ModalHeader/ModalHeader.d.ts +0 -21
  126. package/dist/components/Modal/components/index.d.ts +0 -4
  127. package/dist/components/Pagination/Pagination.d.ts +0 -51
  128. package/dist/components/Pagination/Pagination.stories.d.ts +0 -8
  129. package/dist/components/Pagination/Pagination.utilities.d.ts +0 -10
  130. package/dist/components/Popover/Popover.d.ts +0 -8
  131. package/dist/components/Popover/Popover.stories.d.ts +0 -7
  132. package/dist/components/RadioGroup/RadioGroup.d.ts +0 -75
  133. package/dist/components/RadioGroup/RadioGroup.stories.d.ts +0 -16
  134. package/dist/components/RadioGroup/RadioInput/RadioInput.d.ts +0 -57
  135. package/dist/components/RadioGroup/RadioInput/RadioInputIcon.d.ts +0 -27
  136. package/dist/components/RangeInput/RangeInput.d.ts +0 -29
  137. package/dist/components/RangeInput/RangeInput.stories.d.ts +0 -7
  138. package/dist/components/ResponsiveProvider/ResponsiveProvider.d.ts +0 -17
  139. package/dist/components/ResponsiveProvider/ResponsiveProvider.stories.d.ts +0 -7
  140. package/dist/components/SelectInput/SelectInput.d.ts +0 -148
  141. package/dist/components/SelectInput/SelectInput.stories.d.ts +0 -24
  142. package/dist/components/SelectInputInset/SelectInputInset.d.ts +0 -92
  143. package/dist/components/SelectInputInset/SelectInputInset.stories.d.ts +0 -12
  144. package/dist/components/SelectInputNative/SelectInputNative.d.ts +0 -45
  145. package/dist/components/SelectInputNative/SelectInputNative.stories.d.ts +0 -19
  146. package/dist/components/Sidebar/Sidebar.d.ts +0 -57
  147. package/dist/components/Sidebar/Sidebar.stories.d.ts +0 -9
  148. package/dist/components/Spinner/Spinner.d.ts +0 -12
  149. package/dist/components/Spinner/Spinner.stories.d.ts +0 -8
  150. package/dist/components/Switch/Switch.d.ts +0 -64
  151. package/dist/components/Switch/Switch.stories.d.ts +0 -12
  152. package/dist/components/Table/Table.d.ts +0 -86
  153. package/dist/components/Table/Table.stories.d.ts +0 -31
  154. package/dist/components/Table/TableBody/TableBody.d.ts +0 -52
  155. package/dist/components/Table/TableBody/TableBodyCell/TableBodyCell.d.ts +0 -45
  156. package/dist/components/Table/TableHead/TableHead.d.ts +0 -46
  157. package/dist/components/Table/TableHead/TableHeaderCell/TableHeaderCell.d.ts +0 -65
  158. package/dist/components/Table/common/TableRow/TableRow.d.ts +0 -67
  159. package/dist/components/TextInput/TextInput.d.ts +0 -106
  160. package/dist/components/TextInput/TextInput.stories.d.ts +0 -19
  161. package/dist/components/TextInputInset/TextInputInset.d.ts +0 -102
  162. package/dist/components/TextInputInset/TextInputInset.stories.d.ts +0 -13
  163. package/dist/components/TextareaInput/TextareaInput.d.ts +0 -97
  164. package/dist/components/TextareaInput/TextareaInput.stories.d.ts +0 -23
  165. package/dist/components/TextareaInputInset/TextareaInputInset.d.ts +0 -105
  166. package/dist/components/TextareaInputInset/TextareaInputInset.stories.d.ts +0 -12
  167. package/dist/components/ThemeProvider/ThemeProvider.d.ts +0 -15
  168. package/dist/components/ThemeProvider/ThemeProvider.stories.d.ts +0 -6
  169. package/dist/components/TimePicker/TimePicker.d.ts +0 -35
  170. package/dist/components/TimePicker/TimePicker.stories.d.ts +0 -12
  171. package/dist/components/TimePickerNative/TimePickerNative.d.ts +0 -39
  172. package/dist/components/TimePickerNative/TimePickerNative.stories.d.ts +0 -11
  173. package/dist/components/Toast/Toast.store.d.ts +0 -36
  174. package/dist/components/Toast/Toast.stories.d.ts +0 -14
  175. package/dist/components/Toast/Toast.types.d.ts +0 -75
  176. package/dist/components/Toast/ToastContainer.d.ts +0 -43
  177. package/dist/components/Toast/ToastNotification.d.ts +0 -28
  178. package/dist/components/Toast/index.d.ts +0 -4
  179. package/dist/components/Toast/toast.d.ts +0 -20
  180. package/dist/components/Toast/useToasts.d.ts +0 -14
  181. package/dist/components/Toggle/Toggle.d.ts +0 -7
  182. package/dist/components/Toggle/Toggle.stories.d.ts +0 -11
  183. package/dist/components/ToggleGroup/ToggleGroup.d.ts +0 -19
  184. package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts +0 -12
  185. package/dist/components/Tooltip/Tooltip.d.ts +0 -8
  186. package/dist/components/Tooltip/Tooltip.stories.d.ts +0 -8
  187. package/dist/constants/keyCodes.d.ts +0 -2
  188. package/dist/css/index.css +0 -36
  189. package/dist/hooks/index.d.ts +0 -6
  190. package/dist/hooks/useBreakpoint/useBreakpoint.d.ts +0 -9
  191. package/dist/hooks/useBreakpoint/useBreakpoint.stories.d.ts +0 -6
  192. package/dist/hooks/useIsMobile/useIsMobile.d.ts +0 -1
  193. package/dist/hooks/useIsMobile/useIsMobile.stories.d.ts +0 -6
  194. package/dist/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.d.ts +0 -2
  195. package/dist/hooks/useOpenClose/useOpenClose.d.ts +0 -39
  196. package/dist/hooks/useOpenClose/useOpenClose.stories.d.ts +0 -6
  197. package/dist/hooks/useTheme/useTheme.d.ts +0 -5
  198. package/dist/hooks/useTheme/useTheme.stories.d.ts +0 -6
  199. package/dist/hooks/useWindowSize/useWindowSize.d.ts +0 -7
  200. package/dist/hooks/useWindowSize/useWindowSize.stories.d.ts +0 -6
  201. package/dist/lib/cssShorthandToClasses.d.ts +0 -4
  202. package/dist/lib/doesStringIncludeCssUnit.d.ts +0 -1
  203. package/dist/lib/generateResponsiveClasses.d.ts +0 -2
  204. package/dist/lib/getAutoCompleteValue.d.ts +0 -1
  205. package/dist/lib/getColumnKeys.d.ts +0 -3
  206. package/dist/lib/getDimensionCss.d.ts +0 -12
  207. package/dist/lib/getElementType.d.ts +0 -14
  208. package/dist/lib/getFlexCss.d.ts +0 -9
  209. package/dist/lib/index.d.ts +0 -15
  210. package/dist/lib/isFunction.d.ts +0 -3
  211. package/dist/lib/mergeRefs.d.ts +0 -2
  212. package/dist/lib/prefersReducedMotion.d.ts +0 -1
  213. package/dist/lib/react-children-utilities/filter.d.ts +0 -3
  214. package/dist/lib/react-children-utilities/index.d.ts +0 -1
  215. package/dist/lib/reactRouterClickHandler.d.ts +0 -12
  216. package/dist/lib/resolveValue.d.ts +0 -3
  217. package/dist/lib/tokens.d.ts +0 -22
  218. package/dist/modes.d.ts +0 -8
  219. package/dist/types/index.d.ts +0 -103
  220. package/dist/types/lib.types.d.ts +0 -3
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
3
4
  import {
4
5
  Drawer,
5
6
  DrawerTitle,
@@ -9,6 +10,7 @@ import {
9
10
  DrawerProps,
10
11
  DrawerProvider,
11
12
  DrawerTrigger,
13
+ useDrawer,
12
14
  } from './Drawer';
13
15
 
14
16
  const renderDrawer = (props: Partial<DrawerProps> = {}) => {
@@ -32,82 +34,143 @@ const renderDrawer = (props: Partial<DrawerProps> = {}) => {
32
34
  };
33
35
 
34
36
  describe('Drawer', () => {
35
- test('renders its children', () => {
36
- renderDrawer();
37
- fireEvent.click(screen.getByText('Open drawer'));
37
+ describe('Basic rendering', () => {
38
+ test('renders its children', () => {
39
+ renderDrawer();
40
+ fireEvent.click(screen.getByText('Open drawer'));
38
41
 
39
- expect(screen.getByText('Drawer Title')).toBeInTheDocument();
40
- });
42
+ expect(screen.getByText('Drawer Title')).toBeInTheDocument();
43
+ });
41
44
 
42
- test('it applies the aria label', () => {
43
- renderDrawer();
44
- fireEvent.click(screen.getByText('Open drawer'));
45
- expect(screen.getByLabelText('Test Drawer')).toBeInTheDocument();
46
- });
45
+ test('it applies the aria label', () => {
46
+ renderDrawer();
47
+ fireEvent.click(screen.getByText('Open drawer'));
48
+ expect(screen.getByLabelText('Test Drawer')).toBeInTheDocument();
49
+ });
50
+
51
+ test('it opens and closes based on isOpen prop', () => {
52
+ const { rerender } = renderDrawer();
53
+ expect(screen.queryByLabelText('Test Drawer')).toBe(null);
47
54
 
48
- test('it opens and closes based on isOpen prop', () => {
49
- const { rerender } = renderDrawer();
50
- expect(screen.queryByLabelText('Test Drawer')).toBe(null);
55
+ rerender(
56
+ <Drawer isOpen={true} ariaLabel="Test Drawer" onDismiss={() => null} />
57
+ );
58
+ expect(screen.getByLabelText('Test Drawer')).toBeInTheDocument();
59
+ });
60
+
61
+ test('renders DrawerContent with correct data attribute', () => {
62
+ renderDrawer();
63
+ fireEvent.click(screen.getByText('Open drawer'));
51
64
 
52
- rerender(
53
- <Drawer isOpen={true} ariaLabel="Test Drawer" onDismiss={() => null} />
54
- );
55
- expect(screen.getByLabelText('Test Drawer')).toBeInTheDocument();
65
+ expect(screen.getByTestId('drawer-content')).toBeInTheDocument();
66
+ });
56
67
  });
57
68
 
58
- // test('it calls onDismiss when close button is clicked', () => {
59
- // const mockedOnDismiss = jest.fn();
60
- // renderDrawer({
61
- // isOpen: true,
62
- // ariaLabel: 'Test Drawer',
63
- // onDismiss: mockedOnDismiss,
64
- // });
69
+ describe('Focus management', () => {
70
+ test('it traps focus within the drawer', async () => {
71
+ const user = userEvent.setup();
65
72
 
66
- // fireEvent.click(screen.getByLabelText('close'));
67
- // expect(mockedOnDismiss).toHaveBeenCalledTimes(1);
68
- // });
73
+ // Render with an outside button to verify focus cannot escape
74
+ render(
75
+ <>
76
+ <button data-testid="outside-button">Outside</button>
77
+ <Drawer isOpen ariaLabel="Focus Trap Drawer">
78
+ <DrawerCloseButton />
79
+ <DrawerContent>
80
+ <button data-testid="inside-button">Inside</button>
81
+ </DrawerContent>
82
+ </Drawer>
83
+ </>
84
+ );
69
85
 
70
- test('it traps focus within the drawer', () => {
71
- renderDrawer({ isOpen: true, ariaLabel: 'Test Drawer' });
72
- fireEvent.click(screen.getByText('Open drawer'));
86
+ const drawerContent = screen.getByTestId('drawer-content');
87
+ const insideButton = screen.getByTestId('inside-button');
73
88
 
74
- const closeButton = screen.getByLabelText('close');
75
- closeButton.focus();
76
- expect(closeButton).toHaveFocus();
77
- });
89
+ // Focus the inside button
90
+ insideButton.focus();
91
+ expect(insideButton).toHaveFocus();
92
+
93
+ // Tab multiple times - focus should remain trapped within the drawer
94
+ await user.tab();
95
+ expect(drawerContent.contains(document.activeElement)).toBe(true);
78
96
 
79
- test('it allows scrolling when dangerouslyBypassScrollLock is true', () => {
80
- renderDrawer({
81
- isOpen: true,
97
+ await user.tab();
98
+ expect(drawerContent.contains(document.activeElement)).toBe(true);
82
99
 
83
- dangerouslyBypassScrollLock: true,
100
+ await user.tab();
101
+ expect(drawerContent.contains(document.activeElement)).toBe(true);
84
102
  });
85
- fireEvent.click(screen.getByText('Open drawer'));
86
103
 
87
- expect(document.body).not.toHaveStyle('overflow: hidden');
88
- });
104
+ test('it does not trap focus when dangerouslyBypassFocusLock is true', async () => {
105
+ const user = userEvent.setup();
106
+
107
+ // Render with an outside button to verify focus can escape
108
+ render(
109
+ <>
110
+ <button data-testid="outside-button">Outside</button>
111
+ <Drawer
112
+ isOpen
113
+ ariaLabel="Bypass Focus Lock Drawer"
114
+ dangerouslyBypassFocusLock
115
+ >
116
+ <DrawerCloseButton />
117
+ <DrawerContent>
118
+ <button data-testid="inside-button">Inside</button>
119
+ </DrawerContent>
120
+ </Drawer>
121
+ </>
122
+ );
89
123
 
90
- test('it does not trap focus when dangerouslyBypassFocusLock is true', () => {
91
- renderDrawer({
92
- isOpen: true,
124
+ const outsideButton = screen.getByTestId('outside-button');
125
+ const insideButton = screen.getByTestId('inside-button');
93
126
 
94
- dangerouslyBypassFocusLock: true,
127
+ // Focus the inside button
128
+ insideButton.focus();
129
+ expect(insideButton).toHaveFocus();
130
+
131
+ // Tab - with focus lock bypassed, focus should eventually escape to outside
132
+ await user.tab();
133
+ await user.tab();
134
+ await user.tab();
135
+
136
+ // Focus should be able to reach the outside button (not trapped inside)
137
+ outsideButton.focus();
138
+ expect(outsideButton).toHaveFocus();
95
139
  });
96
- fireEvent.click(screen.getByText('Open drawer'));
140
+ });
141
+
142
+ describe('Scroll lock', () => {
143
+ test('it allows scrolling when dangerouslyBypassScrollLock is true', () => {
144
+ renderDrawer({
145
+ isOpen: true,
146
+ dangerouslyBypassScrollLock: true,
147
+ });
148
+ fireEvent.click(screen.getByText('Open drawer'));
97
149
 
98
- const closeButton = screen.getByLabelText('close');
99
- closeButton.focus();
100
- expect(closeButton).toHaveFocus();
150
+ expect(document.body).not.toHaveStyle('overflow: hidden');
151
+ });
101
152
  });
102
153
 
103
- test('it renders with custom width', () => {
104
- renderDrawer({
105
- isOpen: true,
106
- width: '500px',
154
+ describe('Width customization', () => {
155
+ test('it renders with custom width', () => {
156
+ renderDrawer({
157
+ isOpen: true,
158
+ width: '500px',
159
+ });
160
+ fireEvent.click(screen.getByText('Open drawer'));
161
+
162
+ expect(screen.getByRole('dialog')).toHaveStyle('--drawer-width: 500px');
107
163
  });
108
- fireEvent.click(screen.getByText('Open drawer'));
109
164
 
110
- expect(screen.getByRole('dialog')).toHaveStyle('--drawer-width: 500px');
165
+ test('it renders with token-based width', () => {
166
+ renderDrawer({
167
+ isOpen: true,
168
+ width: '300px',
169
+ });
170
+ fireEvent.click(screen.getByText('Open drawer'));
171
+
172
+ expect(screen.getByRole('dialog')).toHaveStyle('--drawer-width: 300px');
173
+ });
111
174
  });
112
175
 
113
176
  describe('Uncontrolled Drawer', () => {
@@ -122,6 +185,19 @@ describe('Drawer', () => {
122
185
  fireEvent.click(screen.getByLabelText('close'));
123
186
  expect(screen.queryByText('Uncontrolled Drawer')).toBe(null);
124
187
  });
188
+
189
+ test('defaultIsOpen starts the drawer open', () => {
190
+ render(
191
+ <DrawerProvider defaultIsOpen>
192
+ <DrawerTrigger>Toggle</DrawerTrigger>
193
+ <Drawer ariaLabel="Default Open Drawer">
194
+ <DrawerContent>Default Open Content</DrawerContent>
195
+ </Drawer>
196
+ </DrawerProvider>
197
+ );
198
+
199
+ expect(screen.getByText('Default Open Content')).toBeInTheDocument();
200
+ });
125
201
  });
126
202
 
127
203
  describe('Controlled Drawer', () => {
@@ -157,4 +233,366 @@ describe('Drawer', () => {
157
233
  expect(screen.queryByText('Controlled Drawer')).toBe(null);
158
234
  });
159
235
  });
236
+
237
+ describe('Placement', () => {
238
+ test('renders with left placement', () => {
239
+ render(
240
+ <Drawer isOpen ariaLabel="Left Drawer" placement="left">
241
+ <DrawerContent>Left Content</DrawerContent>
242
+ </Drawer>
243
+ );
244
+
245
+ expect(screen.getByText('Left Content')).toBeInTheDocument();
246
+ });
247
+
248
+ test('renders with right placement (default)', () => {
249
+ render(
250
+ <Drawer isOpen ariaLabel="Right Drawer">
251
+ <DrawerContent>Right Content</DrawerContent>
252
+ </Drawer>
253
+ );
254
+
255
+ expect(screen.getByText('Right Content')).toBeInTheDocument();
256
+ });
257
+
258
+ test('renders with top placement', () => {
259
+ render(
260
+ <Drawer isOpen ariaLabel="Top Drawer" placement="top">
261
+ <DrawerContent>Top Content</DrawerContent>
262
+ </Drawer>
263
+ );
264
+
265
+ expect(screen.getByText('Top Content')).toBeInTheDocument();
266
+ });
267
+
268
+ test('renders with bottom placement', () => {
269
+ render(
270
+ <Drawer isOpen ariaLabel="Bottom Drawer" placement="bottom">
271
+ <DrawerContent>Bottom Content</DrawerContent>
272
+ </Drawer>
273
+ );
274
+
275
+ expect(screen.getByText('Bottom Content')).toBeInTheDocument();
276
+ });
277
+ });
278
+
279
+ describe('DrawerProvider', () => {
280
+ test('provides context to children', () => {
281
+ render(
282
+ <DrawerProvider>
283
+ <DrawerTrigger>Trigger</DrawerTrigger>
284
+ <Drawer ariaLabel="Context Drawer">
285
+ <DrawerContent>Context Content</DrawerContent>
286
+ </Drawer>
287
+ </DrawerProvider>
288
+ );
289
+
290
+ fireEvent.click(screen.getByText('Trigger'));
291
+ expect(screen.getByText('Context Content')).toBeInTheDocument();
292
+ });
293
+
294
+ test('controlled DrawerProvider with open prop', () => {
295
+ const handleOpenChange = jest.fn();
296
+ render(
297
+ <DrawerProvider open={true} onOpenChange={handleOpenChange}>
298
+ <DrawerTrigger>Trigger</DrawerTrigger>
299
+ <Drawer ariaLabel="Controlled Provider Drawer">
300
+ <DrawerContent>Controlled Content</DrawerContent>
301
+ </Drawer>
302
+ </DrawerProvider>
303
+ );
304
+
305
+ expect(screen.getByText('Controlled Content')).toBeInTheDocument();
306
+ });
307
+
308
+ test('applies custom className to provider container', () => {
309
+ const { container } = render(
310
+ <DrawerProvider className="custom-container">
311
+ <DrawerTrigger>Trigger</DrawerTrigger>
312
+ <Drawer ariaLabel="Custom Class Drawer">
313
+ <DrawerContent>Content</DrawerContent>
314
+ </Drawer>
315
+ </DrawerProvider>
316
+ );
317
+
318
+ expect(container.querySelector('.custom-container')).toBeInTheDocument();
319
+ });
320
+
321
+ test('adds drawer-open class when open', () => {
322
+ const { container } = render(
323
+ <DrawerProvider defaultIsOpen>
324
+ <DrawerTrigger>Trigger</DrawerTrigger>
325
+ <Drawer ariaLabel="Open Drawer">
326
+ <DrawerContent>Content</DrawerContent>
327
+ </Drawer>
328
+ </DrawerProvider>
329
+ );
330
+
331
+ expect(container.querySelector('.drawer-open')).toBeInTheDocument();
332
+ });
333
+ });
334
+
335
+ describe('DrawerTrigger', () => {
336
+ test('sets correct ARIA attributes', () => {
337
+ render(
338
+ <DrawerProvider>
339
+ <DrawerTrigger>Open</DrawerTrigger>
340
+ <Drawer ariaLabel="ARIA Drawer">
341
+ <DrawerContent>Content</DrawerContent>
342
+ </Drawer>
343
+ </DrawerProvider>
344
+ );
345
+
346
+ const trigger = screen.getByText('Open');
347
+ expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');
348
+ expect(trigger).toHaveAttribute('data-drawer', 'trigger');
349
+ });
350
+
351
+ test('supports asChild prop', () => {
352
+ render(
353
+ <DrawerProvider>
354
+ <DrawerTrigger asChild>
355
+ <span role="button">Custom Trigger</span>
356
+ </DrawerTrigger>
357
+ <Drawer ariaLabel="AsChild Drawer">
358
+ <DrawerContent>Content</DrawerContent>
359
+ </Drawer>
360
+ </DrawerProvider>
361
+ );
362
+
363
+ fireEvent.click(screen.getByText('Custom Trigger'));
364
+ expect(screen.getByText('Content')).toBeInTheDocument();
365
+ });
366
+
367
+ test('calls custom onClick handler', () => {
368
+ const handleClick = jest.fn();
369
+ render(
370
+ <DrawerProvider>
371
+ <DrawerTrigger onClick={handleClick}>Click Me</DrawerTrigger>
372
+ <Drawer ariaLabel="Click Drawer">
373
+ <DrawerContent>Content</DrawerContent>
374
+ </Drawer>
375
+ </DrawerProvider>
376
+ );
377
+
378
+ fireEvent.click(screen.getByText('Click Me'));
379
+ expect(handleClick).toHaveBeenCalled();
380
+ });
381
+ });
382
+
383
+ describe('DrawerCloseButton', () => {
384
+ test('renders close button with correct aria-label', () => {
385
+ renderDrawer();
386
+ fireEvent.click(screen.getByText('Open drawer'));
387
+
388
+ expect(screen.getByLabelText('close')).toBeInTheDocument();
389
+ });
390
+
391
+ test('has data-drawer="close" attribute', () => {
392
+ renderDrawer();
393
+ fireEvent.click(screen.getByText('Open drawer'));
394
+
395
+ expect(screen.getByLabelText('close')).toHaveAttribute(
396
+ 'data-drawer',
397
+ 'close'
398
+ );
399
+ });
400
+
401
+ test('calls custom onClose in standalone mode', () => {
402
+ const handleClose = jest.fn();
403
+ render(
404
+ <Drawer isOpen ariaLabel="Close Test Drawer">
405
+ <DrawerHeader>
406
+ <DrawerCloseButton onClose={handleClose} />
407
+ </DrawerHeader>
408
+ </Drawer>
409
+ );
410
+
411
+ fireEvent.click(screen.getByLabelText('close'));
412
+ expect(handleClose).toHaveBeenCalled();
413
+ });
414
+ });
415
+
416
+ describe('DrawerHeader', () => {
417
+ test('renders with correct data attribute', () => {
418
+ render(
419
+ <Drawer isOpen ariaLabel="Header Test Drawer">
420
+ <DrawerHeader data-testid="header">Header Content</DrawerHeader>
421
+ </Drawer>
422
+ );
423
+
424
+ expect(screen.getByTestId('header')).toHaveAttribute(
425
+ 'data-drawer',
426
+ 'header'
427
+ );
428
+ });
429
+
430
+ test('applies custom className', () => {
431
+ render(
432
+ <Drawer isOpen ariaLabel="Header Class Drawer">
433
+ <DrawerHeader className="custom-header">Header</DrawerHeader>
434
+ </Drawer>
435
+ );
436
+
437
+ expect(
438
+ screen.getByText('Header').closest('[data-drawer="header"]')
439
+ ).toHaveClass('custom-header');
440
+ });
441
+ });
442
+
443
+ describe('DrawerTitle', () => {
444
+ test('renders with correct data attribute', () => {
445
+ render(
446
+ <Drawer isOpen ariaLabel="Title Test Drawer">
447
+ <DrawerHeader>
448
+ <DrawerTitle data-testid="title">Title</DrawerTitle>
449
+ </DrawerHeader>
450
+ </Drawer>
451
+ );
452
+
453
+ expect(screen.getByTestId('title')).toHaveAttribute(
454
+ 'data-drawer',
455
+ 'title'
456
+ );
457
+ });
458
+ });
459
+
460
+ describe('DrawerContent', () => {
461
+ test('renders with correct data attribute', () => {
462
+ render(
463
+ <Drawer isOpen ariaLabel="Content Test Drawer">
464
+ <DrawerContent data-testid="content">Content</DrawerContent>
465
+ </Drawer>
466
+ );
467
+
468
+ expect(screen.getByTestId('content')).toHaveAttribute(
469
+ 'data-drawer',
470
+ 'content'
471
+ );
472
+ });
473
+ });
474
+
475
+ describe('Overlay behavior', () => {
476
+ test('closeOnOverlayClick calls onDismiss when overlay is clicked', async () => {
477
+ const handleDismiss = jest.fn();
478
+ const { baseElement } = render(
479
+ <Drawer
480
+ isOpen
481
+ ariaLabel="Overlay Click Drawer"
482
+ onDismiss={handleDismiss}
483
+ >
484
+ <DrawerContent>Content</DrawerContent>
485
+ </Drawer>
486
+ );
487
+
488
+ expect(screen.getByText('Content')).toBeInTheDocument();
489
+
490
+ // Find the overlay element and click it
491
+ const overlay = baseElement.querySelector('.ReactModal__Overlay');
492
+ expect(overlay).toBeInTheDocument();
493
+ fireEvent.click(overlay!);
494
+
495
+ await waitFor(() => {
496
+ expect(handleDismiss).toHaveBeenCalledTimes(1);
497
+ });
498
+ });
499
+
500
+ test('closeOnOverlayClick=false prevents onDismiss when overlay is clicked', async () => {
501
+ const handleDismiss = jest.fn();
502
+ const { baseElement } = render(
503
+ <Drawer
504
+ isOpen
505
+ ariaLabel="Overlay Click Drawer"
506
+ onDismiss={handleDismiss}
507
+ closeOnOverlayClick={false}
508
+ >
509
+ <DrawerContent>Content</DrawerContent>
510
+ </Drawer>
511
+ );
512
+
513
+ expect(screen.getByText('Content')).toBeInTheDocument();
514
+
515
+ // Find the overlay element and click it
516
+ const overlay = baseElement.querySelector('.ReactModal__Overlay');
517
+ expect(overlay).toBeInTheDocument();
518
+ fireEvent.click(overlay!);
519
+
520
+ // onDismiss should NOT be called when closeOnOverlayClick is false
521
+ expect(handleDismiss).not.toHaveBeenCalled();
522
+ });
523
+ });
524
+
525
+ describe('Accessibility', () => {
526
+ test('applies ariaLabelledBy when provided', () => {
527
+ render(
528
+ <Drawer isOpen ariaLabelledBy="drawer-title">
529
+ <DrawerHeader>
530
+ <DrawerTitle id="drawer-title">Labelled Drawer</DrawerTitle>
531
+ </DrawerHeader>
532
+ <DrawerContent>Content</DrawerContent>
533
+ </Drawer>
534
+ );
535
+
536
+ const drawer = screen.getByTestId('drawer-content');
537
+ expect(drawer).toHaveAttribute('aria-labelledby', 'drawer-title');
538
+ });
539
+
540
+ test('allowPinchZoom prop is passed correctly', () => {
541
+ render(
542
+ <Drawer isOpen ariaLabel="Pinch Zoom Drawer" allowPinchZoom>
543
+ <DrawerContent>Pinch Content</DrawerContent>
544
+ </Drawer>
545
+ );
546
+
547
+ expect(screen.getByText('Pinch Content')).toBeInTheDocument();
548
+ });
549
+ });
550
+
551
+ describe('useDrawer hook', () => {
552
+ test('throws error when used outside DrawerProvider', () => {
553
+ const TestComponent = () => {
554
+ useDrawer();
555
+ return null;
556
+ };
557
+
558
+ expect(() => render(<TestComponent />)).toThrow(
559
+ 'useDrawer must be used within a DrawerProvider.'
560
+ );
561
+ });
562
+
563
+ test('provides open state and toggle function', () => {
564
+ const TestComponent = () => {
565
+ const { open, toggleDrawer } = useDrawer();
566
+ return (
567
+ <div>
568
+ <span data-testid="state">{open ? 'open' : 'closed'}</span>
569
+ <button onClick={toggleDrawer}>Toggle</button>
570
+ </div>
571
+ );
572
+ };
573
+
574
+ render(
575
+ <DrawerProvider>
576
+ <TestComponent />
577
+ </DrawerProvider>
578
+ );
579
+
580
+ expect(screen.getByTestId('state')).toHaveTextContent('closed');
581
+ fireEvent.click(screen.getByText('Toggle'));
582
+ expect(screen.getByTestId('state')).toHaveTextContent('open');
583
+ });
584
+ });
585
+
586
+ describe('Ref forwarding', () => {
587
+ test('forwards ref to DrawerProvider container', () => {
588
+ const ref = React.createRef<HTMLDivElement>();
589
+ render(
590
+ <DrawerProvider ref={ref}>
591
+ <DrawerTrigger>Trigger</DrawerTrigger>
592
+ </DrawerProvider>
593
+ );
594
+
595
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
596
+ });
597
+ });
160
598
  });
@@ -130,7 +130,7 @@ const DrawerTrigger = React.forwardRef<
130
130
  />
131
131
  );
132
132
  });
133
- DrawerTrigger.displayName = 'SidebarTrigger';
133
+ DrawerTrigger.displayName = 'DrawerTrigger';
134
134
 
135
135
  export type DrawerPlacementType = 'left' | 'right' | 'top' | 'bottom';
136
136
  export interface DrawerProps {
@@ -331,6 +331,8 @@ const Drawer: React.FC<DrawerProps> = forwardRef<HTMLDivElement, DrawerProps>(
331
331
  }
332
332
  );
333
333
 
334
+ Drawer.displayName = 'Drawer';
335
+
334
336
  const DrawerHeader = React.forwardRef<HTMLDivElement, BoxProps>(
335
337
  ({ className, ...props }, ref) => {
336
338
  return (
@@ -363,6 +365,8 @@ const DrawerTitle = React.forwardRef<HTMLDivElement, BoxProps>(
363
365
  }
364
366
  );
365
367
 
368
+ DrawerTitle.displayName = 'DrawerTitle';
369
+
366
370
  const DrawerCloseButton = forwardRef<
367
371
  HTMLButtonElement,
368
372
  ButtonProps & { onClose?: () => void }
@@ -417,6 +421,8 @@ const DrawerContent = React.forwardRef<HTMLDivElement, BoxProps>(
417
421
  }
418
422
  );
419
423
 
424
+ DrawerContent.displayName = 'DrawerContent';
425
+
420
426
  export {
421
427
  Drawer,
422
428
  DrawerContent,