@dhasdk/simple-ui 1.0.7 → 1.0.8

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 (227) hide show
  1. package/.babelrc +12 -0
  2. package/.storybook/main.ts +35 -0
  3. package/.storybook/preview.ts +4 -0
  4. package/BAKpostcss.config.jsBAK +15 -0
  5. package/BAKtailwind.config.mjsBAK +99 -0
  6. package/README.md +464 -16
  7. package/coverage/storybook/coverage-storybook.json +32411 -0
  8. package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
  9. package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
  10. package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
  11. package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
  12. package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
  13. package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
  14. package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
  15. package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
  16. package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
  17. package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
  18. package/coverage/storybook/lcov-report/List.tsx.html +364 -0
  19. package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
  20. package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
  21. package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
  22. package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
  23. package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
  24. package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
  25. package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
  26. package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
  27. package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
  28. package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
  29. package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
  30. package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
  31. package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
  32. package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
  33. package/coverage/storybook/lcov-report/base.css +224 -0
  34. package/coverage/storybook/lcov-report/block-navigation.js +87 -0
  35. package/coverage/storybook/lcov-report/favicon.png +0 -0
  36. package/coverage/storybook/lcov-report/index.html +476 -0
  37. package/coverage/storybook/lcov-report/prettify.css +1 -0
  38. package/coverage/storybook/lcov-report/prettify.js +2 -0
  39. package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
  40. package/coverage/storybook/lcov-report/sorter.js +196 -0
  41. package/coverage/storybook/lcov.info +2312 -0
  42. package/dist/README.md +1815 -0
  43. package/eslint.config.mjs +13 -0
  44. package/package.json +6 -7
  45. package/project.json +11 -0
  46. package/src/assets/img/Frame.svg +5 -0
  47. package/src/assets/img/backArrowRight.svg +10 -0
  48. package/src/assets/img/bc-separator.png +0 -0
  49. package/src/assets/img/calendar.png +0 -0
  50. package/src/assets/img/calendar.svg +4 -0
  51. package/src/assets/img/check.svg +5 -0
  52. package/src/assets/img/check_box.svg +10 -0
  53. package/src/assets/img/check_box_empty.svg +10 -0
  54. package/src/assets/img/check_box_fill.svg +10 -0
  55. package/src/assets/img/check_box_fill_empty.svg +10 -0
  56. package/src/assets/img/chevron-down-white.svg +2 -0
  57. package/src/assets/img/chevron-down.svg +2 -0
  58. package/src/assets/img/chevron-left.svg +1 -0
  59. package/src/assets/img/chevron-right-light.svg +4 -0
  60. package/src/assets/img/chevron-right.svg +3 -0
  61. package/src/assets/img/chevron-up-white.svg +1 -0
  62. package/src/assets/img/chevron-up.svg +1 -0
  63. package/src/assets/img/clock.svg +6 -0
  64. package/src/assets/img/close.svg +1 -0
  65. package/src/assets/img/close2.svg +6 -0
  66. package/src/assets/img/closeModal.svg +10 -0
  67. package/src/assets/img/close_icon_dark.svg +10 -0
  68. package/src/assets/img/close_small.svg +3 -0
  69. package/src/assets/img/emergency_home.svg +10 -0
  70. package/src/assets/img/first-aid-kit.svg +7 -0
  71. package/src/assets/img/heartbeat.svg +4 -0
  72. package/src/assets/img/home-gray.svg +3 -0
  73. package/src/assets/img/home.svg +3 -0
  74. package/src/assets/img/hospital.jpg +0 -0
  75. package/src/assets/img/indeterminate_check_box.svg +10 -0
  76. package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
  77. package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
  78. package/src/assets/img/info_24_ 2c6441.svg +3 -0
  79. package/src/assets/img/marker_check_by_default.svg +10 -0
  80. package/src/assets/img/marker_check_by_default_fill.svg +10 -0
  81. package/src/assets/img/minus-accordion.svg +5 -0
  82. package/src/assets/img/minus.svg +3 -0
  83. package/src/assets/img/open.svg +1 -0
  84. package/src/assets/img/pill-white.svg +7 -0
  85. package/src/assets/img/pill.svg +5 -0
  86. package/src/assets/img/plus-accordion.svg +5 -0
  87. package/src/assets/img/plus.svg +4 -0
  88. package/src/assets/img/prescription.svg +6 -0
  89. package/src/assets/img/search.svg +10 -0
  90. package/src/assets/img/search_icon_light.svg +10 -0
  91. package/src/assets/img/separator.svg +3 -0
  92. package/src/assets/img/stethoscope-white.svg +8 -0
  93. package/src/assets/img/stethoscope.svg +8 -0
  94. package/src/assets/img/thumb_up.svg +10 -0
  95. package/src/assets/img/vector.svg +3 -0
  96. package/src/assets/img/warning-badge-disabled.svg +11 -0
  97. package/src/assets/img/warning-badge-green.svg +11 -0
  98. package/src/assets/img/warning-badge-red.svg +11 -0
  99. package/src/assets/img/warning-badge-yellow.svg +11 -0
  100. package/src/assets/img/warning.svg +10 -0
  101. package/src/global.d.ts +13 -0
  102. package/{index.d.ts → src/index.ts} +13 -5
  103. package/src/lib/Accordian--Accordian.stories.tsx +312 -0
  104. package/src/lib/Accordion.spec.tsx +384 -0
  105. package/src/lib/Accordion.tsx +240 -0
  106. package/src/lib/AppointmentPicker.spec.tsx +138 -0
  107. package/src/lib/AppointmentPicker.tsx +97 -0
  108. package/src/lib/Badge--Badge.stories.tsx +60 -0
  109. package/src/lib/Badge.spec.tsx +70 -0
  110. package/src/lib/Badge.tsx +87 -0
  111. package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
  112. package/src/lib/Breadcrumbs.spec.tsx +218 -0
  113. package/src/lib/Breadcrumbs.tsx +219 -0
  114. package/src/lib/Button--Button.stories.tsx +220 -0
  115. package/src/lib/Button.spec.tsx +241 -0
  116. package/src/lib/Button.tsx +121 -0
  117. package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
  118. package/src/lib/ButtonGroup.spec.tsx +89 -0
  119. package/src/lib/ButtonGroup.tsx +107 -0
  120. package/src/lib/Card--Card.stories.tsx +113 -0
  121. package/src/lib/Card.spec.tsx +112 -0
  122. package/src/lib/Card.tsx +69 -0
  123. package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
  124. package/src/lib/CharacterCounter.spec.tsx +123 -0
  125. package/src/lib/CharacterCounter.tsx +56 -0
  126. package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
  127. package/src/lib/CheckBox.spec.tsx +412 -0
  128. package/src/lib/CheckBox.tsx +491 -0
  129. package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
  130. package/src/lib/DatePicker.spec.tsx +424 -0
  131. package/src/lib/DatePicker.tsx +247 -0
  132. package/src/lib/Input--Input.stories.tsx +449 -0
  133. package/src/lib/Input.spec.tsx +281 -0
  134. package/src/lib/Input.tsx +309 -0
  135. package/src/lib/List--List.stories.tsx +157 -0
  136. package/src/lib/List.spec.tsx +211 -0
  137. package/src/lib/List.tsx +93 -0
  138. package/src/lib/Modal--Modal.stories.tsx +454 -0
  139. package/src/lib/Modal.spec.tsx +202 -0
  140. package/src/lib/Modal.tsx +220 -0
  141. package/src/lib/Pill--Pill.stories.tsx +98 -0
  142. package/src/lib/Pill.spec.tsx +103 -0
  143. package/src/lib/Pill.tsx +91 -0
  144. package/src/lib/ProgressBar.spec.tsx +106 -0
  145. package/src/lib/ProgressBar.tsx +112 -0
  146. package/src/lib/RadioGroup.spec.tsx +84 -0
  147. package/src/lib/RadioGroup.tsx +74 -0
  148. package/src/lib/RadioIcon.tsx +13 -0
  149. package/src/lib/Search--Search.stories.tsx +67 -0
  150. package/src/lib/Search.spec.tsx +182 -0
  151. package/src/lib/Search.tsx +304 -0
  152. package/src/lib/SearchContent.tsx +51 -0
  153. package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
  154. package/src/lib/SectionHeader.spec.tsx +60 -0
  155. package/src/lib/SectionHeader.tsx +91 -0
  156. package/src/lib/Select--Select.stories.tsx +387 -0
  157. package/src/lib/Select.spec.tsx +493 -0
  158. package/src/lib/Select.tsx +311 -0
  159. package/src/lib/Shield--Shield.stories.tsx +196 -0
  160. package/src/lib/Shield.spec.tsx +275 -0
  161. package/src/lib/Shield.tsx +239 -0
  162. package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
  163. package/src/lib/SideBarNav.spec.tsx +178 -0
  164. package/src/lib/SideBarNav.tsx +135 -0
  165. package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
  166. package/src/lib/Skeleton.module.css +16 -0
  167. package/src/lib/Skeleton.spec.tsx +83 -0
  168. package/src/lib/Skeleton.tsx +103 -0
  169. package/src/lib/SkipLink.spec.tsx +76 -0
  170. package/src/lib/SkipLink.tsx +48 -0
  171. package/src/lib/Slider--Slider.stories.tsx +108 -0
  172. package/src/lib/Slider.module.css +109 -0
  173. package/src/lib/Slider.spec.tsx +67 -0
  174. package/src/lib/Slider.tsx +101 -0
  175. package/src/lib/Status--Status.stories.tsx +93 -0
  176. package/src/lib/Status.spec.tsx +118 -0
  177. package/src/lib/Status.tsx +79 -0
  178. package/src/lib/Tabs--Tabs.stories.tsx +294 -0
  179. package/src/lib/Tabs.spec.tsx +249 -0
  180. package/src/lib/Tabs.tsx +188 -0
  181. package/src/lib/Tester.spec.tsx +17 -0
  182. package/src/lib/Toggle--Toggle.stories.tsx +162 -0
  183. package/src/lib/Toggle.spec.tsx +122 -0
  184. package/src/lib/Toggle.tsx +96 -0
  185. package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
  186. package/src/lib/Tooltip.spec.tsx +307 -0
  187. package/src/lib/Tooltip.tsx +137 -0
  188. package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
  189. package/src/styles.css +190 -0
  190. package/tsconfig.json +25 -0
  191. package/tsconfig.lib.json +42 -0
  192. package/tsconfig.spec.json +29 -0
  193. package/tsconfig.storybook.json +36 -0
  194. package/vite.config.mts +87 -0
  195. package/vitest.setup.ts +12 -0
  196. package/index.css +0 -1
  197. package/index.js +0 -35
  198. package/index.mjs +0 -4981
  199. package/lib/Accordion.d.ts +0 -36
  200. package/lib/AppointmentPicker.d.ts +0 -21
  201. package/lib/Badge.d.ts +0 -11
  202. package/lib/Breadcrumbs.d.ts +0 -13
  203. package/lib/Button.d.ts +0 -15
  204. package/lib/ButtonGroup.d.ts +0 -8
  205. package/lib/Card.d.ts +0 -11
  206. package/lib/CharacterCounter.d.ts +0 -11
  207. package/lib/CheckBox.d.ts +0 -30
  208. package/lib/DatePicker.d.ts +0 -7
  209. package/lib/Input.d.ts +0 -16
  210. package/lib/List.d.ts +0 -22
  211. package/lib/Modal.d.ts +0 -18
  212. package/lib/Pill.d.ts +0 -13
  213. package/lib/ProgressBar.d.ts +0 -19
  214. package/lib/RadioGroup.d.ts +0 -15
  215. package/lib/Search.d.ts +0 -26
  216. package/lib/SearchContent.d.ts +0 -6
  217. package/lib/SectionHeader.d.ts +0 -18
  218. package/lib/Select.d.ts +0 -19
  219. package/lib/Shield.d.ts +0 -12
  220. package/lib/SideBarNav.d.ts +0 -21
  221. package/lib/Skeleton.d.ts +0 -15
  222. package/lib/SkipLink.d.ts +0 -22
  223. package/lib/Slider.d.ts +0 -14
  224. package/lib/Status.d.ts +0 -10
  225. package/lib/Tabs.d.ts +0 -23
  226. package/lib/Toggle.d.ts +0 -11
  227. package/lib/Tooltip.d.ts +0 -14
@@ -0,0 +1,454 @@
1
+ import { Meta, StoryContext } from '@storybook/react';
2
+ import { Modal, AccessibleModalProps } from './Modal';
3
+ import { Button } from './Button';
4
+ import { userEvent, within } from 'storybook/test';
5
+ import { expect } from 'storybook/test';
6
+ import { FC, useRef, useState } from 'react';
7
+
8
+
9
+ export default {
10
+ title: 'Components/Modal',
11
+ component: Modal,
12
+ parameters: {
13
+ layout: 'centered',
14
+ backgrounds: {
15
+ default: 'white',
16
+ values: [
17
+ { name: 'white', value: '#ffffff' },
18
+ { name: 'light', value: '#f0f0f0' },
19
+ ],
20
+ },
21
+ },
22
+ argTypes: {
23
+ isOpen: { control: 'boolean' },
24
+ title: { control: 'text' },
25
+ },
26
+ args: {
27
+ isOpen: true,
28
+ onClose: () => {console.log('modal closed');},
29
+ title: 'Storybook Modal',
30
+ },
31
+ } as Meta<typeof Modal>;
32
+
33
+ /*
34
+ interface AccessibleModalProps {
35
+ isOpen: boolean; // Controls modal visibility
36
+ onClose: () => void; // Close handler
37
+ title: string; // Modal title for screen readers
38
+ children: React.ReactNode; // Modal content
39
+ }
40
+ */
41
+
42
+ // Define a story using an alternate method that contains state values
43
+ export const DefaultModal = () => {
44
+ const [isOpen, setIsOpen] = useState(false);
45
+
46
+ return (
47
+ <div className="w-full h-full p-8 border-2 border-blue-500">
48
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
49
+ information or request input. Its primary benefits include:
50
+ </p>
51
+
52
+ <ul className="list-disc ms-5 mt-4">
53
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
54
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
55
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
56
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
57
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
58
+ </ul>
59
+
60
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
61
+
62
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal">
63
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
64
+ to taking user input, ideal for required quick tasks on a page.
65
+ </p>
66
+ </Modal>
67
+ </div>
68
+ );
69
+ }
70
+
71
+ // Define a story using an alternate method that contains state values
72
+ export const DefaultClickOutside = () => {
73
+ const [isOpen, setIsOpen] = useState(false);
74
+
75
+ return (
76
+ <div className="w-full h-full p-8 border-2 border-blue-500">
77
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
78
+ information or request input. Its primary benefits include:
79
+ </p>
80
+
81
+ <ul className="list-disc ms-5 mt-4">
82
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
83
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
84
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
85
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
86
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
87
+ </ul>
88
+
89
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
90
+
91
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal" clickOutsideCloses>
92
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
93
+ to taking user input, ideal for required quick tasks on a page.
94
+ </p>
95
+ </Modal>
96
+ </div>
97
+ );
98
+ }
99
+ // Play function for the DefaultClickOutside story
100
+ DefaultClickOutside.play = async ({ canvasElement }: StoryContext) => {
101
+ const canvas = within(canvasElement);
102
+
103
+ // Step 1: Open the modal by clicking the "Open Modal" button
104
+ const openButton = await canvas.getByRole('button', { name: 'Open Modal' });
105
+ await userEvent.click(openButton);
106
+
107
+ // Step 2: Assert that the modal dialog is now visible
108
+ const modal = await canvas.getByRole('dialog');
109
+ expect(modal).toBeInTheDocument();
110
+
111
+ // Step 3: Retrieve the modal backdrop and click on it
112
+ const backdrop = canvasElement.ownerDocument.getElementById('backdrop');
113
+ if (!backdrop) {
114
+ throw new Error('Backdrop element not found');
115
+ }
116
+ await userEvent.click(backdrop);
117
+
118
+ // Step 4: Assert that the modal is no longer visible
119
+ await expect(canvas.queryByRole('dialog', { name: 'Test Modal' })).not.toBeInTheDocument();
120
+ };
121
+
122
+
123
+ // Define a story using an alternate method that contains state values
124
+ export const CancelCloseButtonText = () => {
125
+ const [isOpen, setIsOpen] = useState(false);
126
+
127
+ return (
128
+ <div className="w-full h-full p-8 border-2 border-blue-500">
129
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
130
+ information or request input. Its primary benefits include:
131
+ </p>
132
+
133
+ <ul className="list-disc ms-5 mt-4">
134
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
135
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
136
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
137
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
138
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
139
+ </ul>
140
+
141
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
142
+
143
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal" closeButtonText='Cancel'>
144
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
145
+ to taking user input, ideal for required quick tasks on a page.
146
+ </p>
147
+ </Modal>
148
+ </div>
149
+ );
150
+ }
151
+
152
+ // Define a story using an alternate method that contains state values
153
+ export const WithContinueButton = () => {
154
+ const [isOpen, setIsOpen] = useState(false);
155
+ const [continueClicked, setContinueClicked] = useState(false);
156
+
157
+ return (
158
+ <div className="w-full h-full p-8 border-2 border-blue-500">
159
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
160
+ information or request input. Its primary benefits include:
161
+ </p>
162
+
163
+ <ul className="list-disc ms-5 mt-4">
164
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
165
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
166
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
167
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
168
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
169
+ </ul>
170
+
171
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
172
+
173
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal" variant='default'
174
+ continueButton continueButtonHandler={() => setContinueClicked(!continueClicked)}
175
+ clickOutsideCloses
176
+ continueButtonText='Display Continue'
177
+ >
178
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
179
+ to taking user input, ideal for required quick tasks on a page.
180
+ </p>
181
+ </Modal>
182
+ { continueClicked && <div className='mt-10 font-lg bg-gray-200 rounded-md border p-6 border-black text-center'>
183
+ Continue button has been clicked
184
+ </div>
185
+ }
186
+ </div>
187
+ );
188
+ }
189
+
190
+
191
+ // Define a story using an alternate method that contains state values
192
+ export const VariantDarker = () => {
193
+ const [isOpen, setIsOpen] = useState(false);
194
+ const [continueClicked, setContinueClicked] = useState(false);
195
+
196
+ return (
197
+ <div className="w-full h-full p-8 border-2 border-blue-500">
198
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
199
+ information or request input. Its primary benefits include:
200
+ </p>
201
+
202
+ <ul className="list-disc ms-5 mt-4">
203
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
204
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
205
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
206
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
207
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
208
+ </ul>
209
+
210
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
211
+
212
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal" variant='darker'
213
+ continueButton continueButtonHandler={() => setContinueClicked(!continueClicked)}
214
+ clickOutsideCloses
215
+ continueButtonText='Display Continue'
216
+ >
217
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
218
+ to taking user input, ideal for required quick tasks on a page.
219
+ </p>
220
+ </Modal>
221
+ { continueClicked && <div className='mt-10 font-lg bg-gray-200 rounded-md border p-6 border-black text-center'>
222
+ Continue button has been clicked
223
+ </div>
224
+ }
225
+ </div>
226
+ );
227
+ }
228
+
229
+ // Define a story using an alternate method that contains state values
230
+ export const VariantDark = () => {
231
+ const [isOpen, setIsOpen] = useState(false);
232
+ const [continueClicked, setContinueClicked] = useState(false);
233
+
234
+ return (
235
+ <div className="w-full h-full p-8 border-2 border-blue-500">
236
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
237
+ information or request input. Its primary benefits include:
238
+ </p>
239
+
240
+ <ul className="list-disc ms-5 mt-4">
241
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
242
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
243
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
244
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
245
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
246
+ </ul>
247
+
248
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
249
+
250
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal" variant='dark'
251
+ continueButton continueButtonHandler={() => setContinueClicked(!continueClicked)}
252
+ clickOutsideCloses
253
+ continueButtonText='Display Continue'
254
+ >
255
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
256
+ to taking user input, ideal for required quick tasks on a page.
257
+ </p>
258
+ </Modal>
259
+ { continueClicked && <div className='mt-10 font-lg bg-gray-200 rounded-md border p-6 border-black text-center'>
260
+ Continue button has been clicked
261
+ </div>
262
+ }
263
+ </div>
264
+ );
265
+
266
+ }
267
+
268
+
269
+ // Define a story using an alternate method that contains state values
270
+ export const VariantMedcard = () => {
271
+ const [isOpen, setIsOpen] = useState(false);
272
+ const [continueClicked, setContinueClicked] = useState(false);
273
+
274
+ return (
275
+ <div className="w-full h-full p-8 border-2 border-blue-500">
276
+ <p>A modal dialog is a UI component that temporarily interrupts the user’s workflow to display critical
277
+ information or request input. Its primary benefits include:
278
+ </p>
279
+
280
+ <ul className="list-disc ms-5 mt-4">
281
+ <li>Focus: By dimming the background, it ensures the user focuses solely on the content or action presented.</li>
282
+ <li>Context Preservation: Users can interact with the dialog without leaving the current page, keeping their workflow intact.</li>
283
+ <li>Efficiency: It’s ideal for quick tasks like confirmations, alerts, or forms, minimizing navigation.</li>
284
+ <li>User Guidance: Helps guide users through essential decisions or processes without distractions.</li>
285
+ <li>Overall, modals improve usability by keeping tasks concise and contextually relevant.</li>
286
+ </ul>
287
+
288
+ <Button onClick={() => setIsOpen(true)} className="mt-8" variant='default'>Open Modal</Button>
289
+
290
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Test Modal" variant='default'
291
+ continueButton continueButtonHandler={() => setContinueClicked(!continueClicked)}
292
+ clickOutsideCloses
293
+ continueButtonText='Display Continue'
294
+ >
295
+ <p>This is a Modal dialog that can be used for many varying purposes, from informational messages
296
+ to taking user input, ideal for required quick tasks on a page.
297
+ </p>
298
+ </Modal>
299
+ { continueClicked && <div className='mt-10 font-lg bg-gray-200 rounded-md border p-6 border-black text-center'>
300
+ Continue button has been clicked
301
+ </div>
302
+ }
303
+ </div>
304
+ );
305
+ }
306
+
307
+
308
+ export const TrapFocus = () => {
309
+ const [isOpen, setIsOpen] = useState(false);
310
+
311
+ return (
312
+ <div className="p-8">
313
+ <Button onClick={() => setIsOpen(true)}>Open Modal</Button>
314
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Focus Trap Modal">
315
+ <p>Press Tab and Shift+Tab to test focus trapping.</p>
316
+ </Modal>
317
+ </div>
318
+ );
319
+ };
320
+
321
+ // Play function for testing focus trapping
322
+ TrapFocus.play = async ({ canvasElement }: StoryContext) => {
323
+ const canvas = within(canvasElement);
324
+
325
+ // Open the modal
326
+ const openButton = canvas.getByRole('button', { name: 'Open Modal' });
327
+ await userEvent.click(openButton);
328
+
329
+ // Assert that the modal is open
330
+ const modal = await canvas.getByRole('dialog');
331
+ expect(modal).toBeInTheDocument();
332
+
333
+ // Get focusable elements
334
+ const closeX = canvas.getByLabelText('Close modal');
335
+ const closeButton = canvas.getByText('Close');
336
+
337
+ await userEvent.tab();
338
+ expect(closeX).toHaveFocus();
339
+
340
+ // Simulate Tab key press (focus should move forward)
341
+ await userEvent.tab();
342
+ expect(closeButton).toHaveFocus();
343
+
344
+ await userEvent.tab();
345
+ expect(closeX).toHaveFocus();
346
+
347
+ await userEvent.tab();
348
+ expect(closeButton).toHaveFocus();
349
+
350
+ await userEvent.tab();
351
+ expect(closeX).toHaveFocus();
352
+
353
+ // Simulate Shift + Tab key press (focus should move backward)
354
+ await userEvent.keyboard("{Shift>}{Tab}{/Shift}");
355
+ expect(closeButton).toHaveFocus();
356
+
357
+ await userEvent.keyboard("{Shift>}{Tab}{/Shift}");
358
+ expect(closeX).toHaveFocus();
359
+
360
+ await userEvent.keyboard("{Shift>}{Tab}{/Shift}");
361
+ expect(closeButton).toHaveFocus();
362
+
363
+ await userEvent.keyboard("{Shift>}{Tab}{/Shift}");
364
+ expect(closeX).toHaveFocus();
365
+
366
+ };
367
+
368
+
369
+ export const ContinueButtonHandler = () => {
370
+ const [isOpen, setIsOpen] = useState(false);
371
+ const [text, setText] = useState("initial text value");
372
+
373
+ return (
374
+ <div className="p-8">
375
+ <p aria-label="paragraph text">{text}</p>
376
+ <Button
377
+ onClick={() => setIsOpen(true)}
378
+ >Open Modal</Button>
379
+ <Modal
380
+ isOpen={isOpen}
381
+ onClose={() => setIsOpen(false)}
382
+ continueButton
383
+ continueButtonHandler={() => setText("text after continue button pressed")}
384
+ title="Focus Trap Modal">
385
+ <p>Press Tab and Shift+Tab to test focus trapping.</p>
386
+ </Modal>
387
+ </div>
388
+ );
389
+ };
390
+
391
+ // Play function for testing focus trapping
392
+ ContinueButtonHandler.play = async ({ canvasElement }: StoryContext) => {
393
+ const canvas = within(canvasElement);
394
+
395
+ // Open the modal
396
+ const openButton = canvas.getByRole('button', { name: 'Open Modal' });
397
+ await userEvent.click(openButton);
398
+
399
+ // Retrieve paragraph
400
+ const paragraph = canvas.getByLabelText('paragraph text');
401
+ expect(paragraph).toHaveTextContent('initial text value')
402
+
403
+ // Assert that the modal is open
404
+ const modal = await canvas.getByRole('dialog');
405
+ expect(modal).toBeInTheDocument();
406
+
407
+ // Click Continue button
408
+ const continueButton = canvas.getByText("Continue");
409
+ await userEvent.click(continueButton);
410
+
411
+ // Test that Continue button handler changed paragraph text
412
+ expect(paragraph).toHaveTextContent('text after continue button pressed')
413
+
414
+ };
415
+
416
+
417
+
418
+ export const EscapeKeyCloses = () => {
419
+ const [isOpen, setIsOpen] = useState(false);
420
+
421
+ return (
422
+ <div className="p-8">
423
+ <Button
424
+ onClick={() => setIsOpen(true)}
425
+ >Open Modal</Button>
426
+ <Modal
427
+ isOpen={isOpen}
428
+ onClose={() => setIsOpen(false)}
429
+ title="Escape Key Closes">
430
+ <p>Press Escape to test closing the modal via an Escape key press.</p>
431
+ </Modal>
432
+ </div>
433
+ );
434
+ };
435
+
436
+ // Play function for testing focus trapping
437
+ EscapeKeyCloses.play = async ({ canvasElement }: StoryContext) => {
438
+ const canvas = within(canvasElement);
439
+
440
+ // Open the modal
441
+ const openButton = canvas.getByRole('button', { name: 'Open Modal' });
442
+ await userEvent.click(openButton);
443
+
444
+ // Assert that the modal is open
445
+ const modal = await canvas.getByRole('dialog');
446
+ expect(modal).toBeInTheDocument();
447
+
448
+ // Press escape key
449
+ await userEvent.keyboard('{Escape}');
450
+
451
+ // Ensure Dialog is no long present
452
+ await expect(canvas.queryByRole('dialog')).not.toBeInTheDocument();
453
+
454
+ };
@@ -0,0 +1,202 @@
1
+ import { render, screen, fireEvent } from "@testing-library/react";
2
+ import { userEvent } from "@testing-library/user-event";
3
+ import { axe } from "vitest-axe";
4
+ import { describe, it, expect, vi } from "vitest";
5
+ import { Modal } from "./Modal";
6
+
7
+ describe("Modal Component", () => {
8
+ const handleClose = vi.fn();
9
+
10
+ it("renders when open", () => {
11
+ render(
12
+ <Modal isOpen={true} onClose={handleClose} title="Test Modal">
13
+ <p>Modal Content</p>
14
+ </Modal>
15
+ );
16
+
17
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
18
+ expect(screen.getByText("Modal Content")).toBeInTheDocument();
19
+ });
20
+
21
+ it("does not render when closed", () => {
22
+ render(
23
+ <Modal isOpen={false} onClose={handleClose} title="Test Modal">
24
+ <p>Modal Content</p>
25
+ </Modal>
26
+ );
27
+
28
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
29
+ });
30
+
31
+
32
+ it("utilizes Continue button and hanlder", () => {
33
+ const handleContinue = vi.fn();
34
+ render(
35
+ <Modal isOpen={true} onClose={handleClose} title="Test Modal" continueButton continueButtonHandler={handleContinue}>
36
+ <p>Modal Content</p>
37
+ </Modal>
38
+ );
39
+
40
+ // capture and click on Continue button
41
+ const continueButton = screen.getByText('Continue');
42
+ fireEvent.click(continueButton);
43
+
44
+ // verify Continue button handler was executed
45
+ expect(handleClose).toHaveBeenCalledTimes(1);
46
+ });
47
+
48
+
49
+ it("renders modal with a medium blur level and no close button, closes on Close button click", () => {
50
+ const handleClose = vi.fn();
51
+ render(
52
+ <Modal closeButton={false} blurLevel="md" isOpen={true} onClose={handleClose} title="Test Modal">
53
+ <p>Modal Content</p>
54
+ </Modal>
55
+ );
56
+
57
+ const closeButton = screen.getByLabelText("Close modal");
58
+ fireEvent.click(closeButton);
59
+
60
+ expect(handleClose).toHaveBeenCalledTimes(1);
61
+ });
62
+
63
+ it("click outside closes the modal", () => {
64
+ const handleClose = vi.fn();
65
+ render(
66
+ <Modal isOpen={true} onClose={handleClose} title="Test Modal" clickOutsideCloses>
67
+ <p>Modal Content</p>
68
+ </Modal>
69
+ );
70
+
71
+ // Simulate clicking outside the modal
72
+ fireEvent.mouseDown(document.body);
73
+
74
+ // Check if the onClose function was called
75
+ expect(handleClose).toHaveBeenCalledTimes(1);
76
+ });
77
+
78
+ it("closes on Escape key press", () => {
79
+ const handleClose = vi.fn();
80
+ render(
81
+ <Modal isOpen={true} onClose={handleClose} title="Test Modal">
82
+ <p>Modal Content</p>
83
+ </Modal>
84
+ );
85
+
86
+ fireEvent.keyDown(document, { key: "Escape", code: "Escape" });
87
+ expect(handleClose).toHaveBeenCalledTimes(1);
88
+ });
89
+ });
90
+
91
+ describe("Modal Accessibility Tests", () => {
92
+ const handleClose = vi.fn();
93
+
94
+ it("should have no accessibility violations", async () => {
95
+ const { container } = render(
96
+ <Modal isOpen={true} onClose={handleClose} title="Accessible Modal">
97
+ <p>Modal Content</p>
98
+ </Modal>
99
+ );
100
+
101
+ const results = await axe(container);
102
+ expect(results).toHaveNoViolations();
103
+ });
104
+
105
+ it("has a valid ARIA role and label", () => {
106
+ render(
107
+ <Modal isOpen={true} onClose={handleClose} title="Accessible Modal">
108
+ <p>Modal Content</p>
109
+ </Modal>
110
+ );
111
+
112
+ const dialog = screen.getByRole("dialog");
113
+
114
+ expect(dialog).toBeInTheDocument();
115
+ });
116
+
117
+ it("allows tabbing through interactive elements within the modal, closes via enter key press", async () => {
118
+ const handleClose = vi.fn();
119
+ render(
120
+ <Modal isOpen={true} onClose={handleClose} title="Test Modal">
121
+ <p>I am a Modal dialog! Behold my GREATNESS!</p>
122
+ </Modal>
123
+ );
124
+
125
+ const user = userEvent.setup();
126
+
127
+ // Get buttons inside the modal
128
+ const xButton = screen.getByLabelText("Close modal");
129
+ const closeButton = screen.getByText("Close");
130
+
131
+ // Initial focus should be on the first button
132
+ xButton.focus();
133
+ expect(document.activeElement).toBe(xButton);
134
+
135
+ // Press Tab to move to the second button
136
+ await user.tab();
137
+ expect(document.activeElement).toBe(closeButton);
138
+
139
+ // Press Tab to move to the second button
140
+ await user.tab();
141
+ expect(document.activeElement).toBe(xButton);
142
+
143
+ // Press Tab to move to the second button
144
+ await user.tab();
145
+ expect(document.activeElement).toBe(closeButton);
146
+
147
+ // Press Tab to move to the second button
148
+ await user.tab();
149
+ expect(document.activeElement).toBe(xButton);
150
+
151
+ // Press Tab to move to the second button
152
+ await user.tab();
153
+ expect(document.activeElement).toBe(closeButton);
154
+
155
+ // Simulate pressing Enter
156
+ await user.keyboard("{Enter}");
157
+
158
+ });
159
+
160
+ it("allows tabbing in reverse w/ shift key through interactive elements within the modal, closes via enter key press", async () => {
161
+ const handleClose = vi.fn();
162
+ render(
163
+ <Modal isOpen={true} onClose={handleClose} title="Test Modal">
164
+ <p>I am a Modal dialog! Behold my GREATNESS!</p>
165
+ </Modal>
166
+ );
167
+
168
+ const user = userEvent.setup();
169
+
170
+ // Get buttons inside the modal
171
+ const xButton = screen.getByLabelText("Close modal");
172
+ const closeButton = screen.getByText("Close");
173
+
174
+ // Initial focus should be on the first button
175
+ xButton.focus();
176
+ expect(document.activeElement).toBe(xButton);
177
+
178
+ // Press Tab to move to the second button
179
+ await user.tab();
180
+ expect(document.activeElement).toBe(closeButton);
181
+
182
+ // Press Tab to move to the second button
183
+ await user.keyboard("{Shift>}{Tab}{/Shift}");
184
+ expect(document.activeElement).toBe(xButton);
185
+
186
+ // Press Tab to move to the second button
187
+ await user.keyboard("{Shift>}{Tab}{/Shift}");
188
+ expect(document.activeElement).toBe(closeButton);
189
+
190
+ // Press Tab to move to the second button
191
+ await user.keyboard("{Shift>}{Tab}{/Shift}");
192
+ expect(document.activeElement).toBe(xButton);
193
+
194
+ // Press Tab to move to the second button
195
+ await user.keyboard("{Shift>}{Tab}{/Shift}");
196
+ expect(document.activeElement).toBe(closeButton);
197
+
198
+ // Simulate pressing Enter
199
+ await user.keyboard("{Enter}");
200
+
201
+ });
202
+ });