@sabrenski/spire-ui 0.0.1

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/dist/index.d.ts +4981 -0
  4. package/dist/spire-ui.css +1 -0
  5. package/dist/spire-ui.es.js +18403 -0
  6. package/dist/spire-ui.umd.js +45 -0
  7. package/package.json +83 -0
  8. package/src/components/Accordion/Accordion.test.ts +218 -0
  9. package/src/components/Accordion/AccordionContent.vue +112 -0
  10. package/src/components/Accordion/AccordionItem.vue +87 -0
  11. package/src/components/Accordion/AccordionRoot.vue +111 -0
  12. package/src/components/Accordion/AccordionTrigger.vue +125 -0
  13. package/src/components/Accordion/index.ts +11 -0
  14. package/src/components/Accordion/keys.ts +23 -0
  15. package/src/components/Avatar/Avatar.test.ts +181 -0
  16. package/src/components/Avatar/Avatar.vue +150 -0
  17. package/src/components/Avatar/index.ts +2 -0
  18. package/src/components/Badge/Badge.test.ts +141 -0
  19. package/src/components/Badge/Badge.vue +133 -0
  20. package/src/components/Badge/index.ts +2 -0
  21. package/src/components/BadgeContainer/BadgeContainer.test.ts +150 -0
  22. package/src/components/BadgeContainer/BadgeContainer.vue +90 -0
  23. package/src/components/BadgeContainer/index.ts +2 -0
  24. package/src/components/Breadcrumb/Breadcrumb.test.ts +342 -0
  25. package/src/components/Breadcrumb/BreadcrumbEllipsis.vue +96 -0
  26. package/src/components/Breadcrumb/BreadcrumbItem.vue +16 -0
  27. package/src/components/Breadcrumb/BreadcrumbLink.vue +67 -0
  28. package/src/components/Breadcrumb/BreadcrumbList.vue +20 -0
  29. package/src/components/Breadcrumb/BreadcrumbPage.vue +25 -0
  30. package/src/components/Breadcrumb/BreadcrumbRoot.vue +41 -0
  31. package/src/components/Breadcrumb/BreadcrumbSeparator.vue +63 -0
  32. package/src/components/Breadcrumb/index.ts +13 -0
  33. package/src/components/Breadcrumb/keys.ts +7 -0
  34. package/src/components/Button/Button.test.ts +231 -0
  35. package/src/components/Button/Button.vue +349 -0
  36. package/src/components/Button/index.ts +2 -0
  37. package/src/components/Callout/Callout.test.ts +260 -0
  38. package/src/components/Callout/Callout.vue +341 -0
  39. package/src/components/Callout/index.ts +2 -0
  40. package/src/components/Card/Card.test.ts +565 -0
  41. package/src/components/Card/Card.vue +209 -0
  42. package/src/components/Card/CardContent.vue +57 -0
  43. package/src/components/Card/CardFooter.vue +72 -0
  44. package/src/components/Card/CardHeader.vue +111 -0
  45. package/src/components/Card/CardImage.vue +124 -0
  46. package/src/components/Card/index.ts +14 -0
  47. package/src/components/Chart/BarChart.vue +208 -0
  48. package/src/components/Chart/BaseChart.vue +444 -0
  49. package/src/components/Chart/Chart.test.ts +359 -0
  50. package/src/components/Chart/DonutChart.vue +283 -0
  51. package/src/components/Chart/LineChart.vue +211 -0
  52. package/src/components/Chart/index.ts +20 -0
  53. package/src/components/Chart/useChartTheme.ts +192 -0
  54. package/src/components/Checkbox/Checkbox.test.ts +209 -0
  55. package/src/components/Checkbox/Checkbox.vue +285 -0
  56. package/src/components/Checkbox/index.ts +2 -0
  57. package/src/components/ChoiceChip/ChoiceChip.test.ts +142 -0
  58. package/src/components/ChoiceChip/ChoiceChip.vue +218 -0
  59. package/src/components/ChoiceChip/index.ts +2 -0
  60. package/src/components/ChoiceChipGroup/ChoiceChipGroup.test.ts +151 -0
  61. package/src/components/ChoiceChipGroup/ChoiceChipGroup.vue +70 -0
  62. package/src/components/ChoiceChipGroup/index.ts +2 -0
  63. package/src/components/ColorPicker/ColorArea.vue +159 -0
  64. package/src/components/ColorPicker/ColorPicker.test.ts +250 -0
  65. package/src/components/ColorPicker/ColorPicker.vue +339 -0
  66. package/src/components/ColorPicker/ColorSlider.vue +191 -0
  67. package/src/components/ColorPicker/index.ts +7 -0
  68. package/src/components/Combobox/Combobox.test.ts +891 -0
  69. package/src/components/Combobox/Combobox.vue +934 -0
  70. package/src/components/Combobox/index.ts +2 -0
  71. package/src/components/DataTable/DataTable.test.ts +1221 -0
  72. package/src/components/DataTable/DataTable.vue +1415 -0
  73. package/src/components/DataTable/index.ts +10 -0
  74. package/src/components/DatePicker/DatePicker.test.ts +625 -0
  75. package/src/components/DatePicker/DatePicker.vue +1586 -0
  76. package/src/components/DatePicker/index.ts +2 -0
  77. package/src/components/Drawer/Drawer.test.ts +336 -0
  78. package/src/components/Drawer/Drawer.vue +466 -0
  79. package/src/components/Drawer/index.ts +2 -0
  80. package/src/components/Dropdown/Dropdown.test.ts +607 -0
  81. package/src/components/Dropdown/Dropdown.vue +807 -0
  82. package/src/components/Dropdown/DropdownItem.vue +227 -0
  83. package/src/components/Dropdown/DropdownSeparator.vue +14 -0
  84. package/src/components/Dropdown/DropdownSub.vue +104 -0
  85. package/src/components/Dropdown/DropdownSubContent.vue +187 -0
  86. package/src/components/Dropdown/DropdownSubTrigger.vue +151 -0
  87. package/src/components/Dropdown/index.ts +14 -0
  88. package/src/components/EmptyState/EmptyState.test.ts +180 -0
  89. package/src/components/EmptyState/EmptyState.vue +137 -0
  90. package/src/components/EmptyState/index.ts +2 -0
  91. package/src/components/FileUpload/FileUpload.test.ts +1151 -0
  92. package/src/components/FileUpload/FileUpload.vue +1042 -0
  93. package/src/components/FileUpload/index.ts +2 -0
  94. package/src/components/Heading/Heading.test.ts +107 -0
  95. package/src/components/Heading/Heading.vue +67 -0
  96. package/src/components/Heading/index.ts +2 -0
  97. package/src/components/Icon/Icon.test.ts +157 -0
  98. package/src/components/Icon/Icon.vue +86 -0
  99. package/src/components/Icon/index.ts +2 -0
  100. package/src/components/Input/Input.test.ts +273 -0
  101. package/src/components/Input/Input.vue +388 -0
  102. package/src/components/Input/index.ts +2 -0
  103. package/src/components/Layout/Container.vue +67 -0
  104. package/src/components/Layout/Grid.vue +159 -0
  105. package/src/components/Layout/GridItem.vue +154 -0
  106. package/src/components/Layout/Layout.test.ts +202 -0
  107. package/src/components/Layout/Stack.vue +128 -0
  108. package/src/components/Layout/index.ts +9 -0
  109. package/src/components/Layout/keys.ts +7 -0
  110. package/src/components/Modal/Modal.test.ts +311 -0
  111. package/src/components/Modal/Modal.vue +336 -0
  112. package/src/components/Modal/index.ts +2 -0
  113. package/src/components/Pagination/Pagination.test.ts +303 -0
  114. package/src/components/Pagination/Pagination.vue +212 -0
  115. package/src/components/Pagination/index.ts +3 -0
  116. package/src/components/Pagination/utils.ts +86 -0
  117. package/src/components/Popover/Popover.test.ts +285 -0
  118. package/src/components/Popover/Popover.vue +441 -0
  119. package/src/components/Popover/index.ts +2 -0
  120. package/src/components/Progress/Progress.test.ts +361 -0
  121. package/src/components/Progress/Progress.vue +363 -0
  122. package/src/components/Progress/index.ts +7 -0
  123. package/src/components/Radio/Radio.test.ts +216 -0
  124. package/src/components/Radio/Radio.vue +214 -0
  125. package/src/components/Radio/index.ts +2 -0
  126. package/src/components/Rating/Rating.test.ts +319 -0
  127. package/src/components/Rating/Rating.vue +247 -0
  128. package/src/components/Rating/index.ts +2 -0
  129. package/src/components/SegmentedControl/SegmentedControl.test.ts +292 -0
  130. package/src/components/SegmentedControl/SegmentedControl.vue +288 -0
  131. package/src/components/SegmentedControl/index.ts +2 -0
  132. package/src/components/Select/Select.test.ts +589 -0
  133. package/src/components/Select/Select.vue +666 -0
  134. package/src/components/Select/index.ts +2 -0
  135. package/src/components/Sidebar/Sidebar.test.ts +301 -0
  136. package/src/components/Sidebar/SidebarGroup.vue +103 -0
  137. package/src/components/Sidebar/SidebarItem.vue +196 -0
  138. package/src/components/Sidebar/SidebarLayout.vue +42 -0
  139. package/src/components/Sidebar/SidebarRoot.vue +122 -0
  140. package/src/components/Sidebar/index.ts +11 -0
  141. package/src/components/Sidebar/keys.ts +14 -0
  142. package/src/components/Skeleton/Skeleton.test.ts +130 -0
  143. package/src/components/Skeleton/Skeleton.vue +104 -0
  144. package/src/components/Skeleton/index.ts +2 -0
  145. package/src/components/Slider/Slider.test.ts +416 -0
  146. package/src/components/Slider/Slider.vue +435 -0
  147. package/src/components/Slider/index.ts +2 -0
  148. package/src/components/Slider/utils.ts +91 -0
  149. package/src/components/Spinner/Spinner.test.ts +79 -0
  150. package/src/components/Spinner/Spinner.vue +159 -0
  151. package/src/components/Spinner/index.ts +2 -0
  152. package/src/components/SpireProvider/SpireProvider.vue +71 -0
  153. package/src/components/SpireProvider/index.ts +11 -0
  154. package/src/components/Stepper/Stepper.test.ts +221 -0
  155. package/src/components/Stepper/StepperContent.vue +51 -0
  156. package/src/components/Stepper/StepperItem.vue +89 -0
  157. package/src/components/Stepper/StepperRoot.vue +101 -0
  158. package/src/components/Stepper/StepperSeparator.vue +52 -0
  159. package/src/components/Stepper/StepperTrigger.vue +144 -0
  160. package/src/components/Stepper/index.ts +11 -0
  161. package/src/components/Stepper/keys.ts +27 -0
  162. package/src/components/Switch/Switch.test.ts +214 -0
  163. package/src/components/Switch/Switch.vue +235 -0
  164. package/src/components/Switch/index.ts +2 -0
  165. package/src/components/Tabs/Tabs.test.ts +363 -0
  166. package/src/components/Tabs/Tabs.vue +318 -0
  167. package/src/components/Tabs/index.ts +2 -0
  168. package/src/components/Text/Text.test.ts +154 -0
  169. package/src/components/Text/Text.vue +100 -0
  170. package/src/components/Text/index.ts +2 -0
  171. package/src/components/Textarea/Textarea.test.ts +432 -0
  172. package/src/components/Textarea/Textarea.vue +411 -0
  173. package/src/components/Textarea/index.ts +2 -0
  174. package/src/components/TimePicker/TimePicker.test.ts +352 -0
  175. package/src/components/TimePicker/TimePicker.vue +569 -0
  176. package/src/components/TimePicker/index.ts +2 -0
  177. package/src/components/Timeline/Timeline.test.ts +193 -0
  178. package/src/components/Timeline/Timeline.vue +111 -0
  179. package/src/components/Timeline/TimelineItem.vue +167 -0
  180. package/src/components/Timeline/index.ts +13 -0
  181. package/src/components/Timeline/keys.ts +21 -0
  182. package/src/components/Toast/ToastItem.test.ts +289 -0
  183. package/src/components/Toast/ToastItem.vue +370 -0
  184. package/src/components/Toast/ToastProvider.test.ts +158 -0
  185. package/src/components/Toast/ToastProvider.vue +181 -0
  186. package/src/components/Toast/index.ts +83 -0
  187. package/src/components/Toast/toastState.test.ts +165 -0
  188. package/src/components/Toast/toastState.ts +161 -0
  189. package/src/components/ToggleButton/ToggleButton.test.ts +166 -0
  190. package/src/components/ToggleButton/ToggleButton.vue +197 -0
  191. package/src/components/ToggleButton/index.ts +2 -0
  192. package/src/components/ToggleGroup/ToggleGroup.test.ts +181 -0
  193. package/src/components/ToggleGroup/ToggleGroup.vue +130 -0
  194. package/src/components/ToggleGroup/index.ts +2 -0
  195. package/src/components/Tooltip/Tooltip.test.ts +238 -0
  196. package/src/components/Tooltip/Tooltip.vue +217 -0
  197. package/src/components/Tooltip/index.ts +2 -0
  198. package/src/components/TreeView/TreeView.test.ts +357 -0
  199. package/src/components/TreeView/TreeView.vue +251 -0
  200. package/src/components/TreeView/TreeViewItem.vue +288 -0
  201. package/src/components/TreeView/index.ts +11 -0
  202. package/src/components/TreeView/keys.ts +35 -0
  203. package/src/composables/index.ts +12 -0
  204. package/src/composables/useClickOutside.ts +36 -0
  205. package/src/composables/useClipboard.ts +35 -0
  206. package/src/composables/useEventListener.ts +48 -0
  207. package/src/composables/useFocusTrap.ts +58 -0
  208. package/src/composables/useHoverReveal.ts +98 -0
  209. package/src/composables/useId.ts +10 -0
  210. package/src/composables/useMagnetic.ts +171 -0
  211. package/src/composables/useRelativePosition.ts +127 -0
  212. package/src/composables/useRipple.ts +146 -0
  213. package/src/composables/useScrollLock.ts +25 -0
  214. package/src/composables/useSpireConfig.ts +27 -0
  215. package/src/composables/useStagger.ts +224 -0
  216. package/src/config/icons.test.ts +115 -0
  217. package/src/config/icons.ts +170 -0
  218. package/src/index.ts +361 -0
  219. package/src/styles/depth.css +129 -0
  220. package/src/styles/effects.css +169 -0
  221. package/src/styles/fallback.css +152 -0
  222. package/src/styles/main.css +25 -0
  223. package/src/styles/mood.css +211 -0
  224. package/src/styles/motion.css +159 -0
  225. package/src/styles/reset.css +97 -0
  226. package/src/styles/theme.css +708 -0
  227. package/src/styles/tokens.css +183 -0
  228. package/src/utils/.gitkeep +0 -0
  229. package/src/utils/color.ts +277 -0
  230. package/src/utils/date.test.ts +522 -0
  231. package/src/utils/date.ts +380 -0
  232. package/src/utils/index.ts +23 -0
  233. package/src/utils/object.test.ts +80 -0
  234. package/src/utils/object.ts +25 -0
  235. package/src/utils/string.test.ts +64 -0
  236. package/src/utils/string.ts +32 -0
  237. package/src/utils/time.ts +156 -0
@@ -0,0 +1,2 @@
1
+ export { default as DatePicker } from './DatePicker.vue'
2
+ export type { DatePickerProps, ViewMode } from './DatePicker.vue'
@@ -0,0 +1,336 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { mount, config } from '@vue/test-utils'
3
+ import { nextTick, Teleport } from 'vue'
4
+ import Drawer from './Drawer.vue'
5
+
6
+ config.global.stubs = {
7
+ teleport: true
8
+ }
9
+
10
+ describe('Drawer', () => {
11
+ beforeEach(() => {
12
+ document.body.style.overflow = ''
13
+ })
14
+
15
+ afterEach(() => {
16
+ document.body.style.overflow = ''
17
+ config.global.stubs = { teleport: true }
18
+ })
19
+
20
+ describe('Rendering', () => {
21
+ it('does not render when modelValue is false', () => {
22
+ const wrapper = mount(Drawer, {
23
+ props: { modelValue: false }
24
+ })
25
+ expect(wrapper.find('.ui-drawer').exists()).toBe(false)
26
+ })
27
+
28
+ it('renders when modelValue is true', () => {
29
+ const wrapper = mount(Drawer, {
30
+ props: { modelValue: true }
31
+ })
32
+ expect(wrapper.find('.ui-drawer').exists()).toBe(true)
33
+ })
34
+
35
+ it('renders slot content in body', () => {
36
+ const wrapper = mount(Drawer, {
37
+ props: { modelValue: true },
38
+ slots: {
39
+ default: '<p>Drawer content</p>'
40
+ }
41
+ })
42
+ expect(wrapper.find('.ui-drawer__body').html()).toContain('Drawer content')
43
+ })
44
+
45
+ it('renders title when provided', () => {
46
+ const wrapper = mount(Drawer, {
47
+ props: { modelValue: true, title: 'My Drawer' }
48
+ })
49
+ expect(wrapper.find('.ui-drawer__title').text()).toBe('My Drawer')
50
+ })
51
+
52
+ it('does not render header when no title or showClose', () => {
53
+ const wrapper = mount(Drawer, {
54
+ props: { modelValue: true, showClose: false }
55
+ })
56
+ expect(wrapper.find('.ui-drawer__header').exists()).toBe(false)
57
+ })
58
+
59
+ it('renders header slot instead of title', () => {
60
+ const wrapper = mount(Drawer, {
61
+ props: { modelValue: true },
62
+ slots: {
63
+ header: '<span class="custom-header">Custom Header</span>'
64
+ }
65
+ })
66
+ expect(wrapper.find('.custom-header').exists()).toBe(true)
67
+ })
68
+
69
+ it('renders footer slot when provided', () => {
70
+ const wrapper = mount(Drawer, {
71
+ props: { modelValue: true },
72
+ slots: {
73
+ footer: '<button>Save</button>'
74
+ }
75
+ })
76
+ expect(wrapper.find('.ui-drawer__footer').exists()).toBe(true)
77
+ expect(wrapper.find('.ui-drawer__footer button').exists()).toBe(true)
78
+ })
79
+
80
+ it('does not render footer when no slot provided', () => {
81
+ const wrapper = mount(Drawer, {
82
+ props: { modelValue: true }
83
+ })
84
+ expect(wrapper.find('.ui-drawer__footer').exists()).toBe(false)
85
+ })
86
+ })
87
+
88
+ describe('Variants', () => {
89
+ it('applies default variant class', () => {
90
+ const wrapper = mount(Drawer, {
91
+ props: { modelValue: true, variant: 'default' }
92
+ })
93
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--default')
94
+ })
95
+
96
+ it('applies floating variant class', () => {
97
+ const wrapper = mount(Drawer, {
98
+ props: { modelValue: true, variant: 'floating' }
99
+ })
100
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--floating')
101
+ })
102
+ })
103
+
104
+ describe('Placements', () => {
105
+ it('applies right placement by default', () => {
106
+ const wrapper = mount(Drawer, {
107
+ props: { modelValue: true }
108
+ })
109
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--right')
110
+ })
111
+
112
+ it('applies left placement', () => {
113
+ const wrapper = mount(Drawer, {
114
+ props: { modelValue: true, placement: 'left' }
115
+ })
116
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--left')
117
+ })
118
+
119
+ it('applies bottom placement', () => {
120
+ const wrapper = mount(Drawer, {
121
+ props: { modelValue: true, placement: 'bottom' }
122
+ })
123
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--bottom')
124
+ })
125
+ })
126
+
127
+ describe('Sizes', () => {
128
+ it('applies sm size class', () => {
129
+ const wrapper = mount(Drawer, {
130
+ props: { modelValue: true, size: 'sm' }
131
+ })
132
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--sm')
133
+ })
134
+
135
+ it('applies md size class by default', () => {
136
+ const wrapper = mount(Drawer, {
137
+ props: { modelValue: true }
138
+ })
139
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--md')
140
+ })
141
+
142
+ it('applies lg size class', () => {
143
+ const wrapper = mount(Drawer, {
144
+ props: { modelValue: true, size: 'lg' }
145
+ })
146
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--lg')
147
+ })
148
+
149
+ it('applies xl size class', () => {
150
+ const wrapper = mount(Drawer, {
151
+ props: { modelValue: true, size: 'xl' }
152
+ })
153
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--xl')
154
+ })
155
+
156
+ it('applies full size class', () => {
157
+ const wrapper = mount(Drawer, {
158
+ props: { modelValue: true, size: 'full' }
159
+ })
160
+ expect(wrapper.find('.ui-drawer__panel').classes()).toContain('ui-drawer__panel--full')
161
+ })
162
+ })
163
+
164
+ describe('Close button', () => {
165
+ it('renders close button when showClose is true', () => {
166
+ const wrapper = mount(Drawer, {
167
+ props: { modelValue: true, showClose: true }
168
+ })
169
+ expect(wrapper.find('.ui-drawer__close').exists()).toBe(true)
170
+ })
171
+
172
+ it('does not render close button when showClose is false', () => {
173
+ const wrapper = mount(Drawer, {
174
+ props: { modelValue: true, showClose: false, title: 'Test' }
175
+ })
176
+ expect(wrapper.find('.ui-drawer__close').exists()).toBe(false)
177
+ })
178
+
179
+ it('emits update:modelValue false when close button clicked', async () => {
180
+ const wrapper = mount(Drawer, {
181
+ props: { modelValue: true }
182
+ })
183
+
184
+ await wrapper.find('.ui-drawer__close').trigger('click')
185
+
186
+ expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
187
+ })
188
+
189
+ it('close button has aria-label', () => {
190
+ const wrapper = mount(Drawer, {
191
+ props: { modelValue: true }
192
+ })
193
+ expect(wrapper.find('.ui-drawer__close').attributes('aria-label')).toBe('Close drawer')
194
+ })
195
+ })
196
+
197
+ describe('Backdrop click', () => {
198
+ it('emits update:modelValue false when backdrop clicked', async () => {
199
+ const wrapper = mount(Drawer, {
200
+ props: { modelValue: true }
201
+ })
202
+
203
+ await wrapper.find('.ui-drawer__backdrop').trigger('click')
204
+
205
+ expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
206
+ })
207
+
208
+ it('does not close on backdrop click when maskClosable is false', async () => {
209
+ const wrapper = mount(Drawer, {
210
+ props: { modelValue: true, maskClosable: false }
211
+ })
212
+
213
+ await wrapper.find('.ui-drawer__backdrop').trigger('click')
214
+
215
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
216
+ })
217
+ })
218
+
219
+ describe('Escape key', () => {
220
+ it('emits update:modelValue false on Escape', async () => {
221
+ const wrapper = mount(Drawer, {
222
+ props: { modelValue: true }
223
+ })
224
+
225
+ await wrapper.find('.ui-drawer').trigger('keydown', { key: 'Escape' })
226
+
227
+ expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
228
+ })
229
+
230
+ it('does not close on Escape when closeOnEscape is false', async () => {
231
+ const wrapper = mount(Drawer, {
232
+ props: { modelValue: true, closeOnEscape: false }
233
+ })
234
+
235
+ await wrapper.find('.ui-drawer').trigger('keydown', { key: 'Escape' })
236
+
237
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
238
+ })
239
+ })
240
+
241
+ describe('Events', () => {
242
+ it('emits open event when opened', async () => {
243
+ const wrapper = mount(Drawer, {
244
+ props: { modelValue: false }
245
+ })
246
+
247
+ await wrapper.setProps({ modelValue: true })
248
+ await nextTick()
249
+
250
+ expect(wrapper.emitted('open')).toHaveLength(1)
251
+ })
252
+
253
+ it('emits close event when closed', async () => {
254
+ const wrapper = mount(Drawer, {
255
+ props: { modelValue: true }
256
+ })
257
+
258
+ await wrapper.find('.ui-drawer__close').trigger('click')
259
+
260
+ expect(wrapper.emitted('close')).toHaveLength(1)
261
+ })
262
+ })
263
+
264
+ describe('Scroll lock', () => {
265
+ it('locks body scroll when opened', async () => {
266
+ const wrapper = mount(Drawer, {
267
+ props: { modelValue: false }
268
+ })
269
+
270
+ await wrapper.setProps({ modelValue: true })
271
+ await nextTick()
272
+
273
+ expect(document.body.style.overflow).toBe('hidden')
274
+ })
275
+
276
+ it('unlocks body scroll when closed', async () => {
277
+ const wrapper = mount(Drawer, {
278
+ props: { modelValue: true }
279
+ })
280
+
281
+ await wrapper.setProps({ modelValue: false })
282
+ await nextTick()
283
+
284
+ expect(document.body.style.overflow).toBe('')
285
+ })
286
+ })
287
+
288
+ describe('Accessibility', () => {
289
+ it('has role dialog', () => {
290
+ const wrapper = mount(Drawer, {
291
+ props: { modelValue: true }
292
+ })
293
+ expect(wrapper.find('.ui-drawer__panel').attributes('role')).toBe('dialog')
294
+ })
295
+
296
+ it('has aria-modal true', () => {
297
+ const wrapper = mount(Drawer, {
298
+ props: { modelValue: true }
299
+ })
300
+ expect(wrapper.find('.ui-drawer__panel').attributes('aria-modal')).toBe('true')
301
+ })
302
+
303
+ it('has aria-labelledby when title provided', () => {
304
+ const wrapper = mount(Drawer, {
305
+ props: { modelValue: true, title: 'Test Title' }
306
+ })
307
+ const panel = wrapper.find('.ui-drawer__panel')
308
+ const labelledby = panel.attributes('aria-labelledby')
309
+ expect(labelledby).toBeTruthy()
310
+
311
+ const title = wrapper.find('.ui-drawer__title')
312
+ expect(title.attributes('id')).toBe(labelledby)
313
+ })
314
+
315
+ it('close button icon is aria-hidden', () => {
316
+ const wrapper = mount(Drawer, {
317
+ props: { modelValue: true }
318
+ })
319
+ expect(wrapper.find('.ui-drawer__close svg').attributes('aria-hidden')).toBe('true')
320
+ })
321
+ })
322
+
323
+ describe('Exposed methods', () => {
324
+ it('exposes close method', async () => {
325
+ const wrapper = mount(Drawer, {
326
+ props: { modelValue: true }
327
+ })
328
+
329
+ const vm = wrapper.vm as unknown as { close: () => void }
330
+ vm.close()
331
+ await nextTick()
332
+
333
+ expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
334
+ })
335
+ })
336
+ })