@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,197 @@
1
+ import './ds-rich-list-item.js';
2
+ import '../ds-tag-status/ds-tag-status.js';
3
+ import '../ds-button-group/ds-button-group.js';
4
+ import '../ds-button/ds-button.js';
5
+ import '../ds-icon-button/ds-icon-button.js';
6
+ import '../ds-avatar/ds-avatar.js';
7
+
8
+ export default {
9
+ title: 'Components/Rich List Item',
10
+ component: 'ds-rich-list-item',
11
+ argTypes: {
12
+ selected: { control: 'boolean' },
13
+ disabled: { control: 'boolean' },
14
+ clickable: { control: 'boolean' },
15
+ href: { control: 'text' },
16
+ value: { control: 'text' },
17
+ multiline: { control: 'boolean' },
18
+ // Slots as controls for playground
19
+ mediaContent: { control: 'text', description: 'Content for media slot' },
20
+ titleContent: { control: 'text', description: 'Content for title slot' },
21
+ descriptionContent: { control: 'text', description: 'Content for description slot' },
22
+ customContent: { control: 'text', description: 'Content for custom slot' },
23
+ },
24
+ parameters: {
25
+ actions: {
26
+ handles: ['rich-list-item-click', 'change'],
27
+ },
28
+ }
29
+ };
30
+
31
+ /* ─── Helper: Create Item with Props & Slots ─── */
32
+ const createItem = (args) => {
33
+ const item = document.createElement('ds-rich-list-item');
34
+
35
+ if (args.selected) item.setAttribute('selected', '');
36
+ if (args.disabled) item.setAttribute('disabled', '');
37
+ if (args.clickable) item.setAttribute('clickable', '');
38
+ if (args.href) item.setAttribute('href', args.href);
39
+ if (args.value) item.setAttribute('value', args.value);
40
+ if (args.multiline) item.setAttribute('multiline', '');
41
+
42
+ // Media
43
+ if (args.mediaContent) {
44
+ // Use the new media prop!
45
+ // For storyboard simplicity, if it's "placeholder", use a data URI
46
+ if (args.mediaContent === 'placeholder') {
47
+ item.setAttribute('media', 'data:image/svg+xml;charset=UTF-8,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%2240%22 height%3D%2240%22 viewBox%3D%220 0 40 40%22%3E%3Crect width%3D%2240%22 height%3D%2240%22 fill%3D%22%23e0e0e0%22%2F%3E%3Ctext x%3D%2250%25%22 y%3D%2250%25%22 dominant-baseline%3D%22middle%22 text-anchor%3D%22middle%22 font-family%3D%22sans-serif%22 font-size%3D%2210%22 fill%3D%22%23757575%22%3EIMG%3C%2Ftext%3E%3C%2Fsvg%3E');
48
+ item.setAttribute('media-alt', 'Placeholder Image');
49
+ } else {
50
+ item.setAttribute('media', args.mediaContent);
51
+ }
52
+ }
53
+
54
+ // Title
55
+ const title = document.createElement('span');
56
+ title.setAttribute('slot', 'title');
57
+ title.textContent = args.titleContent || 'Item Title';
58
+ item.appendChild(title);
59
+
60
+ // Description
61
+ const desc = document.createElement('span');
62
+ desc.setAttribute('slot', 'description');
63
+ desc.textContent = args.descriptionContent || 'Item description goes here';
64
+ item.appendChild(desc);
65
+
66
+ // Custom
67
+ if (args.customContent) {
68
+ const custom = document.createElement('span');
69
+ custom.setAttribute('slot', 'custom');
70
+ custom.textContent = args.customContent; // Start simplest
71
+ // If complex HTML string passed in args (not supported by default control text),
72
+ // normally we'd parse it, but for playground simple text is fine.
73
+ // For specific stories below, we append elements manually.
74
+ custom.style.font = 'var(--ds-typo-content-body-bold)';
75
+ custom.style.color = 'var(--ds-color-text-default)';
76
+ item.appendChild(custom);
77
+ }
78
+
79
+ // Actions — only add when explicitly requested
80
+ if (args.actions) {
81
+ const btnGroup = document.createElement('ds-button-group');
82
+ btnGroup.setAttribute('slot', 'action-group');
83
+ const btn = document.createElement('ds-icon-button');
84
+ btn.setAttribute('icon', 'more_vert');
85
+ btn.setAttribute('variant', 'ghost');
86
+ btnGroup.appendChild(btn);
87
+ item.appendChild(btnGroup);
88
+ }
89
+
90
+ return item;
91
+ };
92
+
93
+ /* ─── Stories ─── */
94
+
95
+ export const Playground = {
96
+ args: {
97
+ titleContent: 'Playground Item',
98
+ descriptionContent: 'Tweak my props in the controls panel',
99
+ mediaContent: 'placeholder',
100
+ customContent: '€120.00',
101
+ actions: false,
102
+ clickable: true,
103
+ selected: false,
104
+ disabled: false,
105
+ multiline: false,
106
+ value: 'item-1'
107
+ },
108
+ render: (args) => createItem(args)
109
+ };
110
+
111
+ export const Default = {
112
+ render: () => createItem({
113
+ titleContent: 'Standard Item',
114
+ descriptionContent: 'Basic configuration with media and actions',
115
+ mediaContent: 'placeholder',
116
+ actions: true
117
+ })
118
+ };
119
+
120
+ export const WithoutMedia = {
121
+ render: () => createItem({
122
+ titleContent: 'No Media Item',
123
+ descriptionContent: 'Notice how text aligns to the left',
124
+ mediaContent: ''
125
+ })
126
+ };
127
+
128
+ export const WithCustomContent = {
129
+ render: () => {
130
+ const item = createItem({
131
+ titleContent: 'Custom Slot Usage',
132
+ descriptionContent: 'Showing a status tag in the custom slot',
133
+ mediaContent: 'placeholder',
134
+ actions: true
135
+ });
136
+
137
+ // Replace simple custom slot from createItem with a real tag
138
+ const oldCustom = item.querySelector('[slot="custom"]');
139
+ if (oldCustom) oldCustom.remove();
140
+
141
+ const tag = document.createElement('ds-tag-status');
142
+ tag.setAttribute('slot', 'custom');
143
+ tag.setAttribute('label', 'Active');
144
+ tag.setAttribute('variant', 'success');
145
+ item.appendChild(tag);
146
+
147
+ return item;
148
+ }
149
+ };
150
+
151
+ export const Clickable = {
152
+ render: () => createItem({
153
+ titleContent: 'Clickable Item',
154
+ descriptionContent: 'Hover me to see the interaction state',
155
+ mediaContent: 'placeholder',
156
+ clickable: true,
157
+ value: 'clickable-item'
158
+ })
159
+ };
160
+
161
+ export const Link = {
162
+ render: () => createItem({
163
+ titleContent: 'Link Item',
164
+ descriptionContent: 'This item is an anchor tag link',
165
+ mediaContent: 'placeholder',
166
+ href: '#'
167
+ })
168
+ };
169
+
170
+ export const Selected = {
171
+ render: () => createItem({
172
+ titleContent: 'Selected Item',
173
+ descriptionContent: 'Pre-selected state styling',
174
+ mediaContent: 'placeholder',
175
+ selected: true,
176
+ clickable: true // Usually selectable items are interactive
177
+ })
178
+ };
179
+
180
+ export const Disabled = {
181
+ render: () => createItem({
182
+ titleContent: 'Disabled Item',
183
+ descriptionContent: 'Opacity reduced, no interaction allowed',
184
+ mediaContent: 'placeholder',
185
+ disabled: true,
186
+ clickable: true // Should not be interactive despite this
187
+ })
188
+ };
189
+
190
+ export const Multiline = {
191
+ render: () => createItem({
192
+ titleContent: 'Multiline Item with Long Description',
193
+ descriptionContent: 'This description is intentionally long to demonstrate the automatic height adjustment when the multiline property is enabled. The text will wrap to the next line instead of being truncated with an ellipsis.',
194
+ mediaContent: 'placeholder',
195
+ multiline: true
196
+ })
197
+ };
@@ -0,0 +1,434 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-rich-list-item.js';
3
+
4
+ describe('ds-rich-list-item', () => {
5
+ let container;
6
+
7
+ const tick = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms));
8
+
9
+ beforeEach(() => {
10
+ container = document.createElement('div');
11
+ document.body.appendChild(container);
12
+ });
13
+
14
+ afterEach(() => {
15
+ container.remove();
16
+ });
17
+
18
+ // ─── Rendering ───
19
+
20
+ it('renders with default values', async () => {
21
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Title</span></ds-rich-list-item>';
22
+ const el = container.querySelector('ds-rich-list-item');
23
+ await tick();
24
+
25
+ expect(el.selected).toBe(false);
26
+ expect(el.disabled).toBe(false);
27
+ expect(el.clickable).toBe(false);
28
+ expect(el.href).toBe('');
29
+ expect(el.value).toBe('');
30
+ expect(el.multiline).toBe(false);
31
+ });
32
+
33
+ it('renders title and description slots', async () => {
34
+ container.innerHTML = `
35
+ <ds-rich-list-item>
36
+ <span slot="title">Title</span>
37
+ <span slot="description">Description</span>
38
+ </ds-rich-list-item>
39
+ `;
40
+ const el = container.querySelector('ds-rich-list-item');
41
+ await tick();
42
+
43
+ const titleSlot = el.shadowRoot.querySelector('slot[name="title"]');
44
+ const descSlot = el.shadowRoot.querySelector('slot[name="description"]');
45
+ expect(titleSlot).toBeTruthy();
46
+ expect(descSlot).toBeTruthy();
47
+ expect(titleSlot.assignedElements().length).toBe(1);
48
+ expect(descSlot.assignedElements().length).toBe(1);
49
+ });
50
+
51
+ it('renders media via prop', async () => {
52
+ container.innerHTML = '<ds-rich-list-item media="test.jpg" media-alt="Test"><span slot="title">Title</span></ds-rich-list-item>';
53
+ const el = container.querySelector('ds-rich-list-item');
54
+ await tick();
55
+
56
+ expect(el.hasAttribute('has-media')).toBe(true);
57
+ const thumbnail = el.shadowRoot.querySelector('ds-thumbnail');
58
+ expect(thumbnail).toBeTruthy();
59
+ expect(thumbnail.getAttribute('src')).toBe('test.jpg');
60
+ expect(thumbnail.getAttribute('alt')).toBe('Test');
61
+ expect(thumbnail.getAttribute('fit')).toBe('cover');
62
+ });
63
+
64
+ it('renders media via slot', async () => {
65
+ container.innerHTML = '<ds-rich-list-item><img slot="media" src="slot.jpg" alt="Slot"><span slot="title">Title</span></ds-rich-list-item>';
66
+ const el = container.querySelector('ds-rich-list-item');
67
+ await tick();
68
+
69
+ expect(el.hasAttribute('has-media')).toBe(true);
70
+ const mediaSlot = el.shadowRoot.querySelector('slot[name="media"]');
71
+ const assigned = mediaSlot.assignedElements();
72
+ expect(assigned.length).toBe(1);
73
+ expect(assigned[0].src).toContain('slot.jpg');
74
+ });
75
+
76
+ it('hides media container when no media', async () => {
77
+ container.innerHTML = '<ds-rich-list-item><span slot="title">No media</span></ds-rich-list-item>';
78
+ const el = container.querySelector('ds-rich-list-item');
79
+ await tick();
80
+
81
+ expect(el.hasAttribute('has-media')).toBe(false);
82
+ const mediaContainer = el.shadowRoot.querySelector('.media-container');
83
+ const style = getComputedStyle(mediaContainer);
84
+ expect(style.display).toBe('none');
85
+ });
86
+
87
+ it('hides action-group container when empty', async () => {
88
+ container.innerHTML = '<ds-rich-list-item><span slot="title">No actions</span></ds-rich-list-item>';
89
+ const el = container.querySelector('ds-rich-list-item');
90
+ await tick();
91
+
92
+ expect(el.hasAttribute('has-action-group')).toBe(false);
93
+ const actionContainer = el.shadowRoot.querySelector('.action-group-container');
94
+ const style = getComputedStyle(actionContainer);
95
+ expect(style.display).toBe('none');
96
+ });
97
+
98
+ it('ignores empty wrapper elements in action-group slot', async () => {
99
+ // Simple empty span
100
+ container.innerHTML = '<ds-rich-list-item><span slot="action-group"> </span></ds-rich-list-item>';
101
+ const el = container.querySelector('ds-rich-list-item');
102
+ await tick();
103
+ expect(el.hasAttribute('has-action-group')).toBe(false);
104
+
105
+ // Nested empty divs
106
+ el.innerHTML = '<div slot="action-group"><span></span></div>';
107
+ await tick();
108
+ expect(el.hasAttribute('has-action-group')).toBe(false);
109
+ });
110
+
111
+ it('detects content inside wrapper elements', async () => {
112
+ container.innerHTML = '<ds-rich-list-item><span slot="action-group"><button>Click</button></span></ds-rich-list-item>';
113
+ const el = container.querySelector('ds-rich-list-item');
114
+ await tick();
115
+
116
+ expect(el.hasAttribute('has-action-group')).toBe(true);
117
+ });
118
+
119
+ it('detects text content inside wrapper elements', async () => {
120
+ container.innerHTML = '<ds-rich-list-item><span slot="action-group">Text</span></ds-rich-list-item>';
121
+ const el = container.querySelector('ds-rich-list-item');
122
+ await tick();
123
+
124
+ expect(el.hasAttribute('has-action-group')).toBe(true);
125
+ });
126
+
127
+ it('dispatches structure change event on content update', async () => {
128
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Test</span></ds-rich-list-item>';
129
+ const el = container.querySelector('ds-rich-list-item');
130
+ await tick();
131
+
132
+ let eventFired = false;
133
+ el.addEventListener('rich-list-item-structure-change', () => { eventFired = true; });
134
+
135
+ // Add action group content
136
+ const span = document.createElement('span');
137
+ span.slot = 'action-group';
138
+ span.textContent = 'Action';
139
+ el.appendChild(span);
140
+
141
+ await tick();
142
+ expect(eventFired).toBe(true);
143
+ expect(el.hasAttribute('has-action-group')).toBe(true);
144
+ });
145
+
146
+ it('hides custom container when empty', async () => {
147
+ container.innerHTML = '<ds-rich-list-item><span slot="title">No custom</span></ds-rich-list-item>';
148
+ const el = container.querySelector('ds-rich-list-item');
149
+ await tick();
150
+
151
+ expect(el.hasAttribute('has-custom')).toBe(false);
152
+ const customContainer = el.shadowRoot.querySelector('.custom-container');
153
+ const style = getComputedStyle(customContainer);
154
+ expect(style.display).toBe('none');
155
+ });
156
+
157
+ // ─── Properties ───
158
+
159
+ it('reflects selected attribute', async () => {
160
+ container.innerHTML = '<ds-rich-list-item selected><span slot="title">Title</span></ds-rich-list-item>';
161
+ const el = container.querySelector('ds-rich-list-item');
162
+ await tick();
163
+
164
+ expect(el.selected).toBe(true);
165
+ expect(el.hasAttribute('selected')).toBe(true);
166
+ });
167
+
168
+ it('reflects disabled attribute', async () => {
169
+ container.innerHTML = '<ds-rich-list-item disabled><span slot="title">Title</span></ds-rich-list-item>';
170
+ const el = container.querySelector('ds-rich-list-item');
171
+ await tick();
172
+
173
+ expect(el.disabled).toBe(true);
174
+ expect(el.hasAttribute('disabled')).toBe(true);
175
+ });
176
+
177
+ it('reflects clickable attribute', async () => {
178
+ container.innerHTML = '<ds-rich-list-item clickable><span slot="title">Title</span></ds-rich-list-item>';
179
+ const el = container.querySelector('ds-rich-list-item');
180
+ await tick();
181
+
182
+ expect(el.clickable).toBe(true);
183
+ expect(el.hasAttribute('interactive')).toBe(true);
184
+ });
185
+
186
+ it('reflects multiline attribute', async () => {
187
+ container.innerHTML = '<ds-rich-list-item multiline><span slot="title">Title</span></ds-rich-list-item>';
188
+ const el = container.querySelector('ds-rich-list-item');
189
+ await tick();
190
+
191
+ expect(el.multiline).toBe(true);
192
+ expect(el.hasAttribute('multiline')).toBe(true);
193
+ });
194
+
195
+ // ─── ARIA ───
196
+
197
+ it('sets role=listitem by default', async () => {
198
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Title</span></ds-rich-list-item>';
199
+ const el = container.querySelector('ds-rich-list-item');
200
+ await tick();
201
+
202
+ expect(el.getAttribute('role')).toBe('listitem');
203
+ });
204
+
205
+ it('sets role=option when selectable', async () => {
206
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Title</span></ds-rich-list-item>';
207
+ const el = container.querySelector('ds-rich-list-item');
208
+ await tick();
209
+
210
+ el._selectable = 'single';
211
+ await tick();
212
+
213
+ expect(el.getAttribute('role')).toBe('option');
214
+ expect(el.getAttribute('aria-selected')).toBe('false');
215
+ });
216
+
217
+ it('sets aria-disabled when disabled', async () => {
218
+ container.innerHTML = '<ds-rich-list-item disabled><span slot="title">Title</span></ds-rich-list-item>';
219
+ const el = container.querySelector('ds-rich-list-item');
220
+ await tick();
221
+
222
+ expect(el.getAttribute('aria-disabled')).toBe('true');
223
+ });
224
+
225
+ // ─── Interactions ───
226
+
227
+ it('dispatches rich-list-item-click when clickable', async () => {
228
+ container.innerHTML = '<ds-rich-list-item clickable value="clicked"><span slot="title">Click me</span></ds-rich-list-item>';
229
+ const el = container.querySelector('ds-rich-list-item');
230
+ await tick();
231
+
232
+ let detail = null;
233
+ el.addEventListener('rich-list-item-click', (e) => { detail = e.detail; });
234
+
235
+ const itemDiv = el.shadowRoot.querySelector('.item');
236
+ itemDiv.click();
237
+ await tick();
238
+
239
+ expect(detail).not.toBeNull();
240
+ expect(detail.value).toBe('clicked');
241
+ });
242
+
243
+ it('does not fire click event when disabled', async () => {
244
+ container.innerHTML = '<ds-rich-list-item clickable disabled value="nope"><span slot="title">Disabled</span></ds-rich-list-item>';
245
+ const el = container.querySelector('ds-rich-list-item');
246
+ await tick();
247
+
248
+ let fired = false;
249
+ el.addEventListener('rich-list-item-click', () => { fired = true; });
250
+
251
+ const itemDiv = el.shadowRoot.querySelector('.item');
252
+ itemDiv.click();
253
+ await tick();
254
+
255
+ expect(fired).toBe(false);
256
+ });
257
+
258
+ it('toggles selection in multiple mode', async () => {
259
+ container.innerHTML = '<ds-rich-list-item value="item1"><span slot="title">Multi</span></ds-rich-list-item>';
260
+ const el = container.querySelector('ds-rich-list-item');
261
+ await tick();
262
+
263
+ el._selectable = 'multiple';
264
+ await tick();
265
+
266
+ const itemDiv = el.shadowRoot.querySelector('.item');
267
+
268
+ // Click to select
269
+ itemDiv.click();
270
+ await tick();
271
+ expect(el.selected).toBe(true);
272
+
273
+ // Click again to deselect
274
+ itemDiv.click();
275
+ await tick();
276
+ expect(el.selected).toBe(false);
277
+ });
278
+
279
+ it('selects but does not deselect in single mode', async () => {
280
+ container.innerHTML = '<ds-rich-list-item value="item1"><span slot="title">Single</span></ds-rich-list-item>';
281
+ const el = container.querySelector('ds-rich-list-item');
282
+ await tick();
283
+
284
+ el._selectable = 'single';
285
+ await tick();
286
+
287
+ const itemDiv = el.shadowRoot.querySelector('.item');
288
+
289
+ // Click to select
290
+ itemDiv.click();
291
+ await tick();
292
+ expect(el.selected).toBe(true);
293
+
294
+ // Click again — should remain selected
295
+ itemDiv.click();
296
+ await tick();
297
+ expect(el.selected).toBe(true);
298
+ });
299
+
300
+ it('dispatches change event on selection', async () => {
301
+ container.innerHTML = '<ds-rich-list-item value="myval"><span slot="title">Title</span></ds-rich-list-item>';
302
+ const el = container.querySelector('ds-rich-list-item');
303
+ await tick();
304
+
305
+ el._selectable = 'multiple';
306
+ await tick();
307
+
308
+ let detail = null;
309
+ el.addEventListener('change', (e) => { detail = e.detail; });
310
+
311
+ const itemDiv = el.shadowRoot.querySelector('.item');
312
+ itemDiv.click();
313
+ await tick();
314
+
315
+ expect(detail).not.toBeNull();
316
+ expect(detail.selected).toBe(true);
317
+ expect(detail.value).toBe('myval');
318
+ });
319
+
320
+ it('renders href link when provided', async () => {
321
+ container.innerHTML = '<ds-rich-list-item href="https://example.com"><span slot="title">Link</span></ds-rich-list-item>';
322
+ const el = container.querySelector('ds-rich-list-item');
323
+ await tick();
324
+
325
+ const link = el.shadowRoot.querySelector('a.item-link');
326
+ expect(link).toBeTruthy();
327
+ expect(link.getAttribute('href')).toBe('https://example.com');
328
+ expect(link.getAttribute('aria-hidden')).toBe('true');
329
+ });
330
+
331
+ it('does not render href link when not provided', async () => {
332
+ container.innerHTML = '<ds-rich-list-item><span slot="title">No link</span></ds-rich-list-item>';
333
+ const el = container.querySelector('ds-rich-list-item');
334
+ await tick();
335
+
336
+ const link = el.shadowRoot.querySelector('a.item-link');
337
+ expect(link).toBeNull();
338
+ });
339
+
340
+ // ─── Selection Controls ───
341
+
342
+ it('renders checkbox in multiple selection mode', async () => {
343
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Check me</span></ds-rich-list-item>';
344
+ const el = container.querySelector('ds-rich-list-item');
345
+ await tick();
346
+
347
+ el._selectable = 'multiple';
348
+ await tick();
349
+
350
+ const checkbox = el.shadowRoot.querySelector('ds-checkbox');
351
+ expect(checkbox).toBeTruthy();
352
+ });
353
+
354
+ it('renders radio in single selection mode', async () => {
355
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Pick one</span></ds-rich-list-item>';
356
+ const el = container.querySelector('ds-rich-list-item');
357
+ await tick();
358
+
359
+ el._selectable = 'single';
360
+ await tick();
361
+
362
+ const radio = el.shadowRoot.querySelector('ds-radio');
363
+ expect(radio).toBeTruthy();
364
+ });
365
+
366
+ it('renders no control in none selection mode', async () => {
367
+ container.innerHTML = '<ds-rich-list-item><span slot="title">Just text</span></ds-rich-list-item>';
368
+ const el = container.querySelector('ds-rich-list-item');
369
+ await tick();
370
+
371
+ const checkbox = el.shadowRoot.querySelector('ds-checkbox');
372
+ const radio = el.shadowRoot.querySelector('ds-radio');
373
+ expect(checkbox).toBeNull();
374
+ expect(radio).toBeNull();
375
+ });
376
+
377
+ // ─── Keyboard ───
378
+
379
+ it('activates on Enter key', async () => {
380
+ container.innerHTML = '<ds-rich-list-item clickable value="enter"><span slot="title">Enter</span></ds-rich-list-item>';
381
+ const el = container.querySelector('ds-rich-list-item');
382
+ await tick();
383
+
384
+ let detail = null;
385
+ el.addEventListener('rich-list-item-click', (e) => { detail = e.detail; });
386
+
387
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
388
+ await tick();
389
+
390
+ expect(detail).not.toBeNull();
391
+ expect(detail.value).toBe('enter');
392
+ });
393
+
394
+ it('activates on Space key', async () => {
395
+ container.innerHTML = '<ds-rich-list-item clickable value="space"><span slot="title">Space</span></ds-rich-list-item>';
396
+ const el = container.querySelector('ds-rich-list-item');
397
+ await tick();
398
+
399
+ let detail = null;
400
+ el.addEventListener('rich-list-item-click', (e) => { detail = e.detail; });
401
+
402
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }));
403
+ await tick();
404
+
405
+ expect(detail).not.toBeNull();
406
+ expect(detail.value).toBe('space');
407
+ });
408
+
409
+ // ─── Interactive State ───
410
+
411
+ it('sets interactive attribute for clickable items', async () => {
412
+ container.innerHTML = '<ds-rich-list-item clickable><span slot="title">Click</span></ds-rich-list-item>';
413
+ const el = container.querySelector('ds-rich-list-item');
414
+ await tick();
415
+
416
+ expect(el.hasAttribute('interactive')).toBe(true);
417
+ });
418
+
419
+ it('sets interactive attribute for href items', async () => {
420
+ container.innerHTML = '<ds-rich-list-item href="https://example.com"><span slot="title">Link</span></ds-rich-list-item>';
421
+ const el = container.querySelector('ds-rich-list-item');
422
+ await tick();
423
+
424
+ expect(el.hasAttribute('interactive')).toBe(true);
425
+ });
426
+
427
+ it('does not set interactive attribute when disabled', async () => {
428
+ container.innerHTML = '<ds-rich-list-item clickable disabled><span slot="title">Disabled</span></ds-rich-list-item>';
429
+ const el = container.querySelector('ds-rich-list-item');
430
+ await tick();
431
+
432
+ expect(el.hasAttribute('interactive')).toBe(false);
433
+ });
434
+ });