@nuvia-ui/components 4.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 (230) hide show
  1. package/package.json +27 -0
  2. package/src/ds-accordion/ds-accordion-item.js +288 -0
  3. package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
  4. package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
  5. package/src/ds-accordion/ds-accordion.js +68 -0
  6. package/src/ds-accordion/ds-accordion.stories.js +118 -0
  7. package/src/ds-accordion/ds-accordion.test.js +146 -0
  8. package/src/ds-accordion/index.js +2 -0
  9. package/src/ds-action-bar/ds-action-bar.js +116 -0
  10. package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
  11. package/src/ds-action-bar/ds-action-bar.test.js +64 -0
  12. package/src/ds-action-bar/index.js +1 -0
  13. package/src/ds-alert/ds-alert.a11y.test.js +151 -0
  14. package/src/ds-alert/ds-alert.js +223 -0
  15. package/src/ds-alert/ds-alert.mdx +142 -0
  16. package/src/ds-alert/ds-alert.stories.js +166 -0
  17. package/src/ds-alert/ds-alert.test.js +256 -0
  18. package/src/ds-alert/index.js +1 -0
  19. package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
  20. package/src/ds-avatar/ds-avatar.js +216 -0
  21. package/src/ds-avatar/ds-avatar.stories.js +120 -0
  22. package/src/ds-avatar/ds-avatar.test.js +83 -0
  23. package/src/ds-avatar/index.js +1 -0
  24. package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
  25. package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
  26. package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
  27. package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
  28. package/src/ds-avatar-extended/index.js +1 -0
  29. package/src/ds-banner/ds-banner.a11y.test.js +51 -0
  30. package/src/ds-banner/ds-banner.js +233 -0
  31. package/src/ds-banner/ds-banner.stories.js +185 -0
  32. package/src/ds-banner/ds-banner.test.js +116 -0
  33. package/src/ds-banner/index.js +1 -0
  34. package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
  35. package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
  36. package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
  37. package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
  38. package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
  39. package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
  40. package/src/ds-button/ds-button.a11y.test.js +49 -0
  41. package/src/ds-button/ds-button.js +205 -0
  42. package/src/ds-button/ds-button.mdx +141 -0
  43. package/src/ds-button/ds-button.stories.js +152 -0
  44. package/src/ds-button/ds-button.test.js +62 -0
  45. package/src/ds-button/index.js +1 -0
  46. package/src/ds-button-group/ds-button-group.js +82 -0
  47. package/src/ds-button-group/ds-button-group.mdx +39 -0
  48. package/src/ds-button-group/ds-button-group.stories.js +47 -0
  49. package/src/ds-button-group/ds-button-group.test.js +47 -0
  50. package/src/ds-button-group/index.js +1 -0
  51. package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
  52. package/src/ds-checkbox/ds-checkbox.js +271 -0
  53. package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
  54. package/src/ds-checkbox/ds-checkbox.test.js +191 -0
  55. package/src/ds-checkbox/index.js +1 -0
  56. package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
  57. package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
  58. package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
  59. package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
  60. package/src/ds-checkbox-group/index.js +1 -0
  61. package/src/ds-dialog/ds-dialog.js +466 -0
  62. package/src/ds-dialog/ds-dialog.stories.js +274 -0
  63. package/src/ds-dialog/ds-dialog.test.js +441 -0
  64. package/src/ds-dialog/index.js +1 -0
  65. package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
  66. package/src/ds-dropdown/ds-dropdown.js +891 -0
  67. package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
  68. package/src/ds-dropdown/ds-dropdown.test.js +268 -0
  69. package/src/ds-dropdown/index.js +1 -0
  70. package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
  71. package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
  72. package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
  73. package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
  74. package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
  75. package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
  76. package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
  77. package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
  78. package/src/ds-file-uploader/ds-file-uploader.js +442 -0
  79. package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
  80. package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
  81. package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
  82. package/src/ds-header/ds-header.a11y.test.js +38 -0
  83. package/src/ds-header/ds-header.js +149 -0
  84. package/src/ds-header/ds-header.stories.js +63 -0
  85. package/src/ds-header/ds-header.test.js +52 -0
  86. package/src/ds-header/index.js +1 -0
  87. package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
  88. package/src/ds-header-nav/ds-header-nav.js +114 -0
  89. package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
  90. package/src/ds-header-nav/ds-header-nav.test.js +93 -0
  91. package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
  92. package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
  93. package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
  94. package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
  95. package/src/ds-icon/ds-icon.a11y.test.js +49 -0
  96. package/src/ds-icon/ds-icon.js +75 -0
  97. package/src/ds-icon/ds-icon.mdx +36 -0
  98. package/src/ds-icon/ds-icon.stories.js +88 -0
  99. package/src/ds-icon/ds-icon.test.js +97 -0
  100. package/src/ds-icon/index.js +1 -0
  101. package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
  102. package/src/ds-icon-button/ds-icon-button.js +224 -0
  103. package/src/ds-icon-button/ds-icon-button.mdx +131 -0
  104. package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
  105. package/src/ds-icon-button/ds-icon-button.test.js +90 -0
  106. package/src/ds-icon-button/index.js +1 -0
  107. package/src/ds-input/ds-input.a11y.test.js +145 -0
  108. package/src/ds-input/ds-input.js +645 -0
  109. package/src/ds-input/ds-input.mdx +251 -0
  110. package/src/ds-input/ds-input.stories.js +298 -0
  111. package/src/ds-input/ds-input.test.js +792 -0
  112. package/src/ds-input/index.js +1 -0
  113. package/src/ds-link/ds-link.js +111 -0
  114. package/src/ds-link/ds-link.stories.js +56 -0
  115. package/src/ds-link/ds-link.test.js +74 -0
  116. package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
  117. package/src/ds-list-item/ds-list-item.js +292 -0
  118. package/src/ds-list-item/ds-list-item.stories.js +101 -0
  119. package/src/ds-list-item/ds-list-item.test.js +63 -0
  120. package/src/ds-menu/ds-menu.js +30 -0
  121. package/src/ds-menu/ds-menu.stories.js +120 -0
  122. package/src/ds-menu/ds-menu.test.js +123 -0
  123. package/src/ds-menu-group/ds-menu-group.js +101 -0
  124. package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
  125. package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
  126. package/src/ds-nav-item/ds-nav-item.js +307 -0
  127. package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
  128. package/src/ds-nav-item/ds-nav-item.test.js +169 -0
  129. package/src/ds-nav-item/index.js +1 -0
  130. package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
  131. package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
  132. package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
  133. package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
  134. package/src/ds-nav-vertical/index.js +1 -0
  135. package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
  136. package/src/ds-pagination/ds-pagination.js +232 -0
  137. package/src/ds-pagination/ds-pagination.stories.js +63 -0
  138. package/src/ds-pagination/ds-pagination.test.js +141 -0
  139. package/src/ds-pagination/index.js +1 -0
  140. package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
  141. package/src/ds-progress-bar/ds-progress-bar.js +81 -0
  142. package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
  143. package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
  144. package/src/ds-radio/ds-radio.a11y.test.js +69 -0
  145. package/src/ds-radio/ds-radio.js +240 -0
  146. package/src/ds-radio/ds-radio.stories.js +102 -0
  147. package/src/ds-radio/ds-radio.test.js +114 -0
  148. package/src/ds-radio/index.js +1 -0
  149. package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
  150. package/src/ds-radio-group/ds-radio-group.js +257 -0
  151. package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
  152. package/src/ds-radio-group/ds-radio-group.test.js +194 -0
  153. package/src/ds-radio-group/index.js +1 -0
  154. package/src/ds-rich-list/ds-rich-list.js +246 -0
  155. package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
  156. package/src/ds-rich-list/ds-rich-list.test.js +293 -0
  157. package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
  158. package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
  159. package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
  160. package/src/ds-slider/ds-slider.js +399 -0
  161. package/src/ds-slider/ds-slider.stories.js +107 -0
  162. package/src/ds-slider/ds-slider.test.js +308 -0
  163. package/src/ds-spinner/ds-spinner.js +173 -0
  164. package/src/ds-spinner/ds-spinner.stories.js +52 -0
  165. package/src/ds-spinner/ds-spinner.test.js +50 -0
  166. package/src/ds-status-border/ds-status-border.js +88 -0
  167. package/src/ds-status-border/ds-status-border.stories.js +242 -0
  168. package/src/ds-status-border/ds-status-border.test.js +168 -0
  169. package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
  170. package/src/ds-stepper/ds-stepper.js +207 -0
  171. package/src/ds-stepper/ds-stepper.stories.js +530 -0
  172. package/src/ds-stepper/ds-stepper.test.js +311 -0
  173. package/src/ds-stepper-item/ds-stepper-item.js +485 -0
  174. package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
  175. package/src/ds-switch/ds-switch.js +348 -0
  176. package/src/ds-switch/ds-switch.stories.js +145 -0
  177. package/src/ds-switch/ds-switch.test.js +226 -0
  178. package/src/ds-switch/index.js +1 -0
  179. package/src/ds-tab-item/ds-tab-item.js +341 -0
  180. package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
  181. package/src/ds-tabs/ds-tab-panel.js +48 -0
  182. package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
  183. package/src/ds-tabs/ds-tabs.js +180 -0
  184. package/src/ds-tabs/ds-tabs.stories.js +152 -0
  185. package/src/ds-tabs/ds-tabs.test.js +306 -0
  186. package/src/ds-tabs/index.js +3 -0
  187. package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
  188. package/src/ds-tag-action/ds-tag-action.js +185 -0
  189. package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
  190. package/src/ds-tag-action/ds-tag-action.test.js +44 -0
  191. package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
  192. package/src/ds-tag-removable/ds-tag-removable.js +146 -0
  193. package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
  194. package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
  195. package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
  196. package/src/ds-tag-status/ds-tag-status.js +164 -0
  197. package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
  198. package/src/ds-tag-status/ds-tag-status.test.js +140 -0
  199. package/src/ds-tag-status/index.js +1 -0
  200. package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
  201. package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
  202. package/src/ds-textarea/ds-textarea.js +505 -0
  203. package/src/ds-textarea/ds-textarea.stories.js +335 -0
  204. package/src/ds-textarea/ds-textarea.test.js +218 -0
  205. package/src/ds-textarea/index.js +1 -0
  206. package/src/ds-thumbnail/ds-thumbnail.js +207 -0
  207. package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
  208. package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
  209. package/src/ds-toast/ds-toast-provider.js +110 -0
  210. package/src/ds-toast/ds-toast.a11y.test.js +34 -0
  211. package/src/ds-toast/ds-toast.js +243 -0
  212. package/src/ds-toast/ds-toast.stories.js +143 -0
  213. package/src/ds-toast/ds-toast.test.js +93 -0
  214. package/src/ds-toast/index.js +2 -0
  215. package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
  216. package/src/ds-tooltip/ds-tooltip.js +217 -0
  217. package/src/ds-tooltip/ds-tooltip.mdx +75 -0
  218. package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
  219. package/src/ds-tooltip/ds-tooltip.test.js +191 -0
  220. package/src/ds-tooltip/index.js +1 -0
  221. package/src/ds-tooltip/positioner.js +117 -0
  222. package/src/index.js +50 -0
  223. package/src/mixins/field-label.mixin.js +113 -0
  224. package/src/mixins/field-message.mixin.js +66 -0
  225. package/src/token-provider/index.js +1 -0
  226. package/src/token-provider/token-provider.a11y.test.js +44 -0
  227. package/src/token-provider/token-provider.js +85 -0
  228. package/src/token-provider/token-provider.stories.js +105 -0
  229. package/src/token-provider/token-provider.test.js +134 -0
  230. package/src/utils/number-input.utils.js +42 -0
@@ -0,0 +1,259 @@
1
+ import { html, nothing } from 'lit';
2
+ import './ds-dropdown.js';
3
+ import '../ds-list-item/ds-list-item.js';
4
+
5
+ export default {
6
+ title: 'Components/Dropdown',
7
+ component: 'ds-dropdown',
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ label: { control: 'text' },
11
+ info: { control: 'text' },
12
+ placeholder: { control: 'text' },
13
+ value: { control: 'text' },
14
+ helper: { control: 'text' },
15
+ validationStatus: {
16
+ control: 'select',
17
+ options: ['', 'error', 'success']
18
+ },
19
+ validationMessage: { control: 'text' },
20
+ disabled: { control: 'boolean' },
21
+ required: { control: 'boolean' },
22
+ clearable: { control: 'boolean' },
23
+ multiple: { control: 'boolean' },
24
+ selectAll: { control: 'boolean' },
25
+ labelPosition: {
26
+ control: 'select',
27
+ options: ['top', 'inline-start']
28
+ },
29
+ labelWidth: { control: 'text' }
30
+ },
31
+ args: {
32
+ label: 'Country',
33
+ info: '',
34
+ placeholder: 'Select a country...',
35
+ value: '',
36
+ helper: '',
37
+ validationStatus: '',
38
+ validationMessage: '',
39
+ disabled: false,
40
+ required: false,
41
+ clearable: false,
42
+ multiple: false,
43
+ selectAll: false,
44
+ labelPosition: 'top',
45
+ labelWidth: ''
46
+ },
47
+ decorators: [
48
+ (story) => html`
49
+ <div style="min-height: 400px; padding: 20px;">
50
+ ${story()}
51
+ </div>
52
+ `
53
+ ]
54
+ };
55
+
56
+ const Template = (args) => html`
57
+ <ds-dropdown
58
+ label="${args.label}"
59
+ info="${args.info || nothing}"
60
+ placeholder="${args.placeholder}"
61
+ value="${args.value}"
62
+ helper="${args.helper || nothing}"
63
+ validation-status="${args.validationStatus || nothing}"
64
+ validation-message="${args.validationMessage || nothing}"
65
+ ?disabled="${args.disabled}"
66
+ ?required="${args.required}"
67
+ ?clearable="${args.clearable}"
68
+ ?multiple="${args.multiple}"
69
+ ?select-all="${args.selectAll}"
70
+ label-position="${args.labelPosition || nothing}"
71
+ label-width="${args.labelWidth || nothing}"
72
+ >
73
+ <ds-list-item value="pt" label="Portugal"></ds-list-item>
74
+ <ds-list-item value="es" label="Spain"></ds-list-item>
75
+ <ds-list-item value="fr" label="France"></ds-list-item>
76
+ <ds-list-item value="de" label="Germany"></ds-list-item>
77
+ <ds-list-item value="it" label="Italy"></ds-list-item>
78
+ </ds-dropdown>
79
+ `;
80
+
81
+ export const Default = {
82
+ render: Template
83
+ };
84
+
85
+ export const InlineLabel = {
86
+ render: Template,
87
+ args: {
88
+ label: 'Select Region',
89
+ labelPosition: 'inline-start',
90
+ labelWidth: '150px',
91
+ placeholder: 'Select a region...'
92
+ }
93
+ };
94
+
95
+ export const MultiSelect = {
96
+ render: Template,
97
+ args: {
98
+ label: 'Countries (Multi)',
99
+ multiple: true,
100
+ placeholder: 'Select countries...'
101
+ },
102
+ parameters: {
103
+ docs: {
104
+ description: {
105
+ story: 'Use `multiple` prop to allow selecting multiple options. The component automatically switches to checkbox variants.'
106
+ }
107
+ }
108
+ }
109
+ };
110
+
111
+ export const SelectAll = {
112
+ render: Template,
113
+ args: {
114
+ label: 'Countries (Select All)',
115
+ multiple: true,
116
+ selectAll: true,
117
+ placeholder: 'Select countries...'
118
+ },
119
+ parameters: {
120
+ docs: {
121
+ description: {
122
+ story: 'Use `select-all` prop in combination with `multiple` to verify easy bulk selection.'
123
+ }
124
+ }
125
+ }
126
+ };
127
+
128
+ export const WithValue = {
129
+ args: {
130
+ label: 'Language',
131
+ value: 'en',
132
+ placeholder: 'Select language...'
133
+ },
134
+ render: (args) => html`
135
+ <ds-dropdown
136
+ label="${args.label}"
137
+ value="${args.value}"
138
+ placeholder="${args.placeholder}"
139
+ helper="${args.helper || nothing}"
140
+ validation-status="${args.validationStatus || nothing}"
141
+ validation-message="${args.validationMessage || nothing}"
142
+ ?disabled="${args.disabled}"
143
+ ?required="${args.required}"
144
+ >
145
+ <ds-list-item value="pt" label="Português"></ds-list-item>
146
+ <ds-list-item value="en" label="English"></ds-list-item>
147
+ <ds-list-item value="es" label="Español"></ds-list-item>
148
+ <ds-list-item value="fr" label="Français"></ds-list-item>
149
+ </ds-dropdown>
150
+ `
151
+ };
152
+
153
+ export const WithError = {
154
+ args: {
155
+ label: 'Priority',
156
+ validationStatus: 'error',
157
+ validationMessage: 'Please select a priority',
158
+ required: true
159
+ },
160
+ render: (args) => html`
161
+ <ds-dropdown
162
+ label="${args.label}"
163
+ validation-status="${args.validationStatus}"
164
+ validation-message="${args.validationMessage}"
165
+ helper="${args.helper || nothing}"
166
+ ?required="${args.required}"
167
+ ?disabled="${args.disabled}"
168
+ >
169
+ <ds-list-item value="low" label="Low"></ds-list-item>
170
+ <ds-list-item value="medium" label="Medium"></ds-list-item>
171
+ <ds-list-item value="high" label="High"></ds-list-item>
172
+ </ds-dropdown>
173
+ `
174
+ };
175
+
176
+ export const Disabled = {
177
+ args: {
178
+ label: 'Status',
179
+ value: 'active',
180
+ disabled: true
181
+ },
182
+ render: (args) => html`
183
+ <ds-dropdown
184
+ label="${args.label}"
185
+ value="${args.value}"
186
+ helper="${args.helper || nothing}"
187
+ ?disabled="${args.disabled}"
188
+ >
189
+ <ds-list-item value="active" label="Active"></ds-list-item>
190
+ <ds-list-item value="inactive" label="Inactive"></ds-list-item>
191
+ </ds-dropdown>
192
+ `
193
+ };
194
+
195
+ export const LargeMultiSelect = {
196
+ args: {
197
+ label: 'Large Selection (60 items)',
198
+ multiple: true,
199
+ selectAll: true,
200
+ placeholder: 'Scroll to see all...'
201
+ },
202
+ render: (args) => html`
203
+ <ds-dropdown
204
+ label="${args.label}"
205
+ info="${args.info || nothing}"
206
+ placeholder="${args.placeholder}"
207
+ value="${args.value}"
208
+ helper="${args.helper || nothing}"
209
+ validation-status="${args.validationStatus || nothing}"
210
+ validation-message="${args.validationMessage || nothing}"
211
+ ?disabled="${args.disabled}"
212
+ ?required="${args.required}"
213
+ ?clearable="${args.clearable}"
214
+ ?multiple="${args.multiple}"
215
+ ?select-all="${args.selectAll}"
216
+ >
217
+ ${Array.from({ length: 60 }).map((_, i) => html`
218
+ <ds-list-item value="item-${i}" label="Option ${i + 1}"></ds-list-item>
219
+ `)}
220
+ </ds-dropdown>
221
+ `
222
+ };
223
+
224
+ export const LargeGrouped = {
225
+ args: {
226
+ label: 'Grouped Large Selection',
227
+ multiple: true,
228
+ selectAll: true,
229
+ placeholder: 'Grouped options...'
230
+ },
231
+ render: (args) => html`
232
+ <ds-dropdown
233
+ label="${args.label}"
234
+ info="${args.info || nothing}"
235
+ placeholder="${args.placeholder}"
236
+ value="${args.value}"
237
+ helper="${args.helper || nothing}"
238
+ validation-status="${args.validationStatus || nothing}"
239
+ validation-message="${args.validationMessage || nothing}"
240
+ ?disabled="${args.disabled}"
241
+ ?required="${args.required}"
242
+ ?clearable="${args.clearable}"
243
+ ?multiple="${args.multiple}"
244
+ ?select-all="${args.selectAll}"
245
+ >
246
+ <ds-list-item variant="section" label="Primary Options"></ds-list-item>
247
+ ${Array.from({ length: 25 }).map((_, i) => html`
248
+ <ds-list-item value="opt-a-${i}" label="Primary ${i + 1}"></ds-list-item>
249
+ `)}
250
+
251
+ <ds-list-item variant="section" label="Secondary Options"></ds-list-item>
252
+ ${Array.from({ length: 25 }).map((_, i) => html`
253
+ <ds-list-item value="opt-b-${i}" label="Secondary ${i + 1}"></ds-list-item>
254
+ `)}
255
+ </ds-dropdown>
256
+ `
257
+ };
258
+
259
+
@@ -0,0 +1,268 @@
1
+
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3
+ import './ds-dropdown.js';
4
+
5
+ describe('ds-dropdown', () => {
6
+ let container;
7
+
8
+ beforeEach(() => {
9
+ container = document.createElement('div');
10
+ document.body.appendChild(container);
11
+ });
12
+
13
+ afterEach(() => {
14
+ container.remove();
15
+ });
16
+
17
+ it('renders with default values', async () => {
18
+ container.innerHTML = '<ds-dropdown></ds-dropdown>';
19
+ const element = container.querySelector('ds-dropdown');
20
+ await new Promise(resolve => setTimeout(resolve, 50));
21
+
22
+ expect(element.label).toBe('');
23
+ expect(element.value).toBe('');
24
+ expect(element.placeholder).toBe('Select...'); // Changed default placeholder
25
+ expect(element.disabled).toBe(false);
26
+ });
27
+
28
+ it('renders label when provided', async () => {
29
+ container.innerHTML = '<ds-dropdown label="Country"></ds-dropdown>';
30
+ const element = container.querySelector('ds-dropdown');
31
+ await new Promise(resolve => setTimeout(resolve, 50));
32
+
33
+ const label = element.shadowRoot.querySelector('label');
34
+ expect(label.textContent).toBe('Country');
35
+ });
36
+
37
+ it('renders placeholder when no value selected', async () => {
38
+ container.innerHTML = '<ds-dropdown placeholder="Choose..."></ds-dropdown>';
39
+ const element = container.querySelector('ds-dropdown');
40
+ await new Promise(resolve => setTimeout(resolve, 50));
41
+
42
+ const valueText = element.shadowRoot.querySelector('.value-text.placeholder');
43
+ expect(valueText.textContent.trim()).toBe('Choose...');
44
+ });
45
+
46
+ it('displays selected value from ds-list-item', async () => {
47
+ container.innerHTML = `
48
+ <ds-dropdown>
49
+ <ds-list-item value="pt" label="Portugal"></ds-list-item>
50
+ <ds-list-item value="es" label="Spain" selected></ds-list-item>
51
+ </ds-dropdown>
52
+ `;
53
+ const element = container.querySelector('ds-dropdown');
54
+ await new Promise(resolve => setTimeout(resolve, 100)); // Wait for slotchange and sync
55
+
56
+ expect(element.value).toBe('es');
57
+ const valueText = element.shadowRoot.querySelector('.value-text');
58
+ expect(valueText.textContent.trim()).toBe('Spain');
59
+ });
60
+
61
+ it('dispatches change event on selection', async () => {
62
+ container.innerHTML = `
63
+ <ds-dropdown>
64
+ <ds-list-item value="pt" label="Portugal"></ds-list-item>
65
+ <ds-list-item value="es" label="Spain"></ds-list-item>
66
+ </ds-dropdown>
67
+ `;
68
+ const element = container.querySelector('ds-dropdown');
69
+ await new Promise(resolve => setTimeout(resolve, 100));
70
+
71
+ const changeSpy = vi.fn();
72
+ element.addEventListener('change', changeSpy);
73
+
74
+ // Simulate clicking an item
75
+ const items = element.shadowRoot.querySelector('slot').assignedElements({ flatten: true });
76
+ const itemToSelect = items.find(el => el.getAttribute('value') === 'pt');
77
+
78
+ // Create a mock event bubbling up from ds-list-item -> ds-menu-group -> dropdown
79
+ // But since logic is in ds-menu-group, we might need to simulate click on item which propagates to group
80
+
81
+ // To properly test integration, we should click the item.
82
+ itemToSelect.click();
83
+ await new Promise(resolve => setTimeout(resolve, 50));
84
+
85
+ expect(changeSpy).toHaveBeenCalledTimes(1);
86
+ expect(changeSpy.mock.calls[0][0].detail.value).toBe('pt');
87
+ expect(element.value).toBe('pt');
88
+ });
89
+
90
+ it('disables trigger when disabled', async () => {
91
+ container.innerHTML = '<ds-dropdown disabled></ds-dropdown>';
92
+ const element = container.querySelector('ds-dropdown');
93
+ await new Promise(resolve => setTimeout(resolve, 50));
94
+
95
+ const trigger = element.shadowRoot.querySelector('.trigger');
96
+ expect(trigger.getAttribute('tabindex')).toBe('-1');
97
+ expect(trigger.classList.contains('disabled')).toBeFalsy(); // Trigger style depends on :host([disabled])
98
+ });
99
+
100
+ it('shows clear button when clearable and has value', async () => {
101
+ container.innerHTML = `
102
+ <ds-dropdown clearable>
103
+ <ds-list-item value="pt" label="Portugal" selected></ds-list-item>
104
+ </ds-dropdown>
105
+ `;
106
+ const element = container.querySelector('ds-dropdown');
107
+ await new Promise(resolve => setTimeout(resolve, 100));
108
+
109
+ // Should be shown because we have a value
110
+ const clearButton = element.shadowRoot.querySelector('ds-icon-button[icon="close"]');
111
+ expect(clearButton).toBeTruthy();
112
+ });
113
+
114
+ it('clears value when clear button is clicked', async () => {
115
+ container.innerHTML = `
116
+ <ds-dropdown clearable>
117
+ <ds-list-item value="pt" label="Portugal" selected></ds-list-item>
118
+ </ds-dropdown>
119
+ `;
120
+ const element = container.querySelector('ds-dropdown');
121
+ await new Promise(resolve => setTimeout(resolve, 100));
122
+
123
+ const clearButton = element.shadowRoot.querySelector('ds-icon-button[icon="close"]');
124
+ clearButton.click();
125
+ await new Promise(resolve => setTimeout(resolve, 50));
126
+
127
+ expect(element.value).toBe('');
128
+ const valueText = element.shadowRoot.querySelector('.value-text.placeholder');
129
+ expect(valueText).toBeTruthy();
130
+
131
+ // Validate that the item is no longer selected
132
+ const items = element.shadowRoot.querySelector('slot').assignedElements({ flatten: true });
133
+ expect(items[0].hasAttribute('selected')).toBe(false);
134
+ });
135
+
136
+ it('shows indeterminate state on select all when partially selected', async () => {
137
+ container.innerHTML = `
138
+ <ds-dropdown multiple select-all>
139
+ <ds-list-item value="1" label="Option 1"></ds-list-item>
140
+ <ds-list-item value="2" label="Option 2"></ds-list-item>
141
+ </ds-dropdown>
142
+ `;
143
+ const element = container.querySelector('ds-dropdown');
144
+ await new Promise(resolve => setTimeout(resolve, 100));
145
+
146
+ // Select one item
147
+ element.value = "1";
148
+ await element.updateComplete;
149
+ await new Promise(resolve => setTimeout(resolve, 50));
150
+
151
+ // Open to check visual state
152
+ const trigger = element.shadowRoot.querySelector('.trigger');
153
+ trigger.click();
154
+ await new Promise(resolve => setTimeout(resolve, 50));
155
+
156
+ // Find Select All item
157
+ const selectAllItem = element.shadowRoot.querySelector('ds-list-item[label="Select All"]');
158
+ expect(selectAllItem).toBeTruthy();
159
+ expect(selectAllItem.hasAttribute('indeterminate')).toBe(true);
160
+ expect(selectAllItem.hasAttribute('selected')).toBe(false);
161
+ });
162
+
163
+
164
+ it('initializes with provided value', async () => {
165
+ container.innerHTML = `
166
+ <ds-dropdown value="es">
167
+ <ds-list-item value="pt" label="Portugal"></ds-list-item>
168
+ <ds-list-item value="es" label="Spain"></ds-list-item>
169
+ </ds-dropdown>
170
+ `;
171
+ const element = container.querySelector('ds-dropdown');
172
+ await new Promise(resolve => setTimeout(resolve, 100)); // Wait for slotchange and sync logic
173
+
174
+ const valueText = element.shadowRoot.querySelector('.value-text');
175
+ expect(valueText.textContent.trim()).toBe('Spain');
176
+
177
+ // Verify item selected state
178
+ const items = element.shadowRoot.querySelector('slot').assignedElements({ flatten: true });
179
+ expect(items[1].hasAttribute('selected')).toBe(true);
180
+ });
181
+
182
+ it('updates display value when item selected via click', async () => {
183
+ container.innerHTML = `
184
+ <ds-dropdown>
185
+ <ds-list-item value="pt" label="Portugal"></ds-list-item>
186
+ <ds-list-item value="es" label="Spain"></ds-list-item>
187
+ </ds-dropdown>
188
+ `;
189
+ const element = container.querySelector('ds-dropdown');
190
+ await new Promise(resolve => setTimeout(resolve, 100));
191
+
192
+ const items = element.shadowRoot.querySelector('slot').assignedElements({ flatten: true });
193
+ items[0].click(); // Select Portugal
194
+ await new Promise(resolve => setTimeout(resolve, 50));
195
+
196
+ expect(element.value).toBe('pt');
197
+ const valueText = element.shadowRoot.querySelector('.value-text');
198
+ expect(valueText.textContent.trim()).toBe('Portugal');
199
+ expect(valueText.classList.contains('placeholder')).toBe(false);
200
+ });
201
+
202
+
203
+ it('renders helper text', async () => {
204
+ container.innerHTML = '<ds-dropdown helper="Select your country"></ds-dropdown>';
205
+ const element = container.querySelector('ds-dropdown');
206
+ await new Promise(resolve => setTimeout(resolve, 50));
207
+
208
+ // Helper just uses base class, no specific modifier unless error
209
+ const message = element.shadowRoot.querySelector('.field-message');
210
+ expect(message.textContent.trim()).toBe('Select your country');
211
+ });
212
+
213
+ it('renders error message and icon', async () => {
214
+ container.innerHTML = `
215
+ <ds-dropdown
216
+ validation-status="error"
217
+ validation-message="This field is required"
218
+ ></ds-dropdown>
219
+ `;
220
+ const element = container.querySelector('ds-dropdown');
221
+ await new Promise(resolve => setTimeout(resolve, 50));
222
+
223
+ // Error adds modifier class
224
+ const errorMessage = element.shadowRoot.querySelector('.field-message--error');
225
+ expect(errorMessage).toBeTruthy();
226
+ expect(errorMessage.textContent.trim()).toBe('This field is required');
227
+
228
+ const errorIcon = errorMessage.querySelector('ds-icon[name="warning"]');
229
+ expect(errorIcon).toBeTruthy();
230
+ });
231
+
232
+ it('opens menu on trigger click', async () => {
233
+ container.innerHTML = '<ds-dropdown><ds-list-item label="A"></ds-list-item></ds-dropdown>';
234
+ const element = container.querySelector('ds-dropdown');
235
+ await new Promise(resolve => setTimeout(resolve, 50));
236
+
237
+ const trigger = element.shadowRoot.querySelector('.trigger');
238
+ trigger.click();
239
+ await new Promise(resolve => setTimeout(resolve, 50));
240
+
241
+ expect(element.open).toBe(true);
242
+ });
243
+
244
+ it('opens menu with ArrowDown key on trigger', async () => {
245
+ container.innerHTML = '<ds-dropdown><ds-list-item label="A"></ds-list-item></ds-dropdown>';
246
+ const element = container.querySelector('ds-dropdown');
247
+ await new Promise(resolve => setTimeout(resolve, 50));
248
+
249
+ const trigger = element.shadowRoot.querySelector('.trigger');
250
+ trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
251
+ await new Promise(resolve => setTimeout(resolve, 50));
252
+
253
+ expect(element.open).toBe(true);
254
+ });
255
+
256
+ it('Closes menu with Escape key within menu interaction', async () => {
257
+ container.innerHTML = '<ds-dropdown open><ds-list-item label="A"></ds-list-item></ds-dropdown>';
258
+ const element = container.querySelector('ds-dropdown');
259
+ await new Promise(resolve => setTimeout(resolve, 50));
260
+
261
+ const trigger = element.shadowRoot.querySelector('.trigger');
262
+ // In Virtual Focus, the trigger handles key events
263
+ trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }));
264
+ await new Promise(resolve => setTimeout(resolve, 50));
265
+
266
+ expect(element.open).toBe(false);
267
+ });
268
+ });
@@ -0,0 +1 @@
1
+ export { DsDropdown } from './ds-dropdown.js';
@@ -0,0 +1,55 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ /**
4
+ * Logical grouping of dropdown items with optional label.
5
+ *
6
+ * @element ds-dropdown-group
7
+ * @prop {string} label - Group label text
8
+ * @slot - Default slot for ds-list-item elements
9
+ */
10
+ export class DsDropdownGroup extends LitElement {
11
+ static properties = {
12
+ label: { type: String }
13
+ };
14
+
15
+ static styles = css`
16
+ :host {
17
+ display: block;
18
+ }
19
+
20
+ .group-label {
21
+ font: var(--ds-typo-content-body-bold);
22
+ color: var(--ds-color-text-secondary);
23
+ padding: 8px 16px 4px;
24
+ display: block;
25
+ }
26
+
27
+ .group-items {
28
+ display: flex;
29
+ flex-direction: column;
30
+ }
31
+
32
+ /* Divider between groups */
33
+ :host(:not(:first-child)) {
34
+ margin-top: 8px;
35
+ padding-top: 8px;
36
+ border-top: 1px solid var(--ds-color-border-default);
37
+ }
38
+ `;
39
+
40
+ constructor() {
41
+ super();
42
+ this.label = '';
43
+ }
44
+
45
+ render() {
46
+ return html`
47
+ ${this.label ? html`<span class="group-label">${this.label}</span>` : ''}
48
+ <div class="group-items" role="group" aria-label="${this.label || 'Item group'}">
49
+ <slot></slot>
50
+ </div>
51
+ `;
52
+ }
53
+ }
54
+
55
+ customElements.define('ds-dropdown-group', DsDropdownGroup);
@@ -0,0 +1,34 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ /**
4
+ * specialized panel for dropdown popups.
5
+ * Handles scrolling, containment, and visual styling (shadow, radius).
6
+ *
7
+ * @element ds-dropdown-panel
8
+ * @slot - Content (Items, Headers)
9
+ */
10
+ export class DsDropdownPanel extends LitElement {
11
+ static styles = css`
12
+ :host {
13
+ display: flex;
14
+ flex-direction: column;
15
+ background-color: var(--ds-color-bg-default);
16
+ border-radius: var(--ds-radius-container);
17
+ box-shadow: var(--ds-elevation-floating);
18
+ max-height: 300px; /* Default constraint */
19
+ overflow: hidden; /* Changed from auto - zones handle scroll */
20
+ padding: 0; /* Zones handle their own padding */
21
+ box-sizing: border-box;
22
+
23
+ /* Scrollbar styling */
24
+ scrollbar-width: thin;
25
+ scrollbar-color: var(--ds-color-border-strong) transparent;
26
+ }
27
+ `;
28
+
29
+ render() {
30
+ return html`<slot></slot>`;
31
+ }
32
+ }
33
+
34
+ customElements.define('ds-dropdown-panel', DsDropdownPanel);
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './ds-file-uploaded.js';
4
+
5
+ describe('DsFileUploaded A11y', () => {
6
+ let container;
7
+
8
+ beforeEach(() => {
9
+ container = document.createElement('div');
10
+ document.body.appendChild(container);
11
+ });
12
+
13
+ afterEach(() => {
14
+ container.remove();
15
+ });
16
+
17
+ it('default state passes axe a11y checks', async () => {
18
+ container.innerHTML = '<div role="list"><ds-file-uploaded filename="report.pdf" size="2 MB"></ds-file-uploaded></div>';
19
+ await new Promise(resolve => setTimeout(resolve, 0));
20
+
21
+ const results = await axe.run(container);
22
+ expect(results.violations).toHaveLength(0);
23
+ });
24
+
25
+ it('loading state passes axe a11y checks', async () => {
26
+ container.innerHTML = '<div role="list"><ds-file-uploaded filename="report.pdf" size="2 MB" loading progress="50"></ds-file-uploaded></div>';
27
+ await new Promise(resolve => setTimeout(resolve, 0));
28
+
29
+ const results = await axe.run(container);
30
+ expect(results.violations).toHaveLength(0);
31
+ });
32
+
33
+ it('error state passes axe a11y checks', async () => {
34
+ container.innerHTML = '<div role="list"><ds-file-uploaded filename="report.pdf" size="2 MB" error error-message="Upload failed"></ds-file-uploaded></div>';
35
+ await new Promise(resolve => setTimeout(resolve, 0));
36
+
37
+ const results = await axe.run(container);
38
+ expect(results.violations).toHaveLength(0);
39
+ });
40
+ });