@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,152 @@
1
+ import './ds-button.js';
2
+ import '../ds-icon/ds-icon.js';
3
+
4
+
5
+ export default {
6
+ title: 'Components/Button',
7
+ component: 'ds-button',
8
+ argTypes: {
9
+ variant: {
10
+ control: 'select',
11
+ options: ['primary', 'secondary', 'outline', 'action', 'tertiary'],
12
+ },
13
+ disabled: { control: 'boolean' },
14
+ label: { control: 'text' },
15
+ iconStart: { control: 'text' },
16
+ iconEnd: { control: 'text' }
17
+ },
18
+ };
19
+
20
+ const createButton = ({ variant, disabled, label, iconStart, iconEnd }) => {
21
+ const btn = document.createElement('ds-button');
22
+
23
+ if (variant) btn.setAttribute('variant', variant);
24
+ if (disabled) btn.setAttribute('disabled', ''); // Boolean attribute
25
+
26
+ if (iconStart) {
27
+ const icon = document.createElement('ds-icon');
28
+ icon.setAttribute('slot', 'icon-start');
29
+ icon.setAttribute('name', iconStart);
30
+ icon.setAttribute('size', 'sm');
31
+ btn.appendChild(icon);
32
+ }
33
+
34
+ if (label) {
35
+ btn.appendChild(document.createTextNode(label));
36
+ }
37
+
38
+ if (iconEnd) {
39
+ const icon = document.createElement('ds-icon');
40
+ icon.setAttribute('slot', 'icon-end');
41
+ icon.setAttribute('name', iconEnd);
42
+ icon.setAttribute('size', 'sm');
43
+ btn.appendChild(icon);
44
+ }
45
+
46
+ return btn;
47
+ };
48
+
49
+ export const Primary = {
50
+ args: {
51
+ variant: 'primary',
52
+ label: 'Save Changes',
53
+ },
54
+ render: createButton
55
+ };
56
+
57
+ export const Secondary = {
58
+ args: {
59
+ variant: 'secondary',
60
+ label: 'Cancel',
61
+ },
62
+ render: createButton
63
+ };
64
+
65
+ export const Outline = {
66
+ args: {
67
+ variant: 'outline',
68
+ label: 'Learn More',
69
+ },
70
+ render: createButton
71
+ };
72
+
73
+ export const Action = {
74
+ args: {
75
+ variant: 'action',
76
+ label: 'Edit',
77
+ },
78
+ render: createButton
79
+ };
80
+
81
+ export const Tertiary = {
82
+ args: {
83
+ variant: 'tertiary',
84
+ label: 'Skip',
85
+ },
86
+ render: createButton
87
+ };
88
+
89
+ export const WithIconStart = {
90
+ args: {
91
+ variant: 'primary',
92
+ label: 'Icon Start',
93
+ iconStart: 'star',
94
+ disabled: false
95
+ },
96
+ render: createButton
97
+ };
98
+
99
+ export const WithIconEnd = {
100
+ args: {
101
+ variant: 'primary',
102
+ label: 'Icon End',
103
+ iconEnd: 'arrow-forward',
104
+ },
105
+ render: createButton
106
+ };
107
+
108
+ export const WithBothIcons = {
109
+ args: {
110
+ variant: 'primary',
111
+ label: 'Both Icons',
112
+ iconStart: 'check',
113
+ iconEnd: 'close',
114
+ },
115
+ render: createButton
116
+ };
117
+
118
+ export const Disabled = {
119
+ args: {
120
+ variant: 'primary',
121
+ label: 'Disabled',
122
+ disabled: true,
123
+ },
124
+ render: createButton
125
+ };
126
+
127
+ export const AllVariants = {
128
+ render: () => {
129
+ const container = document.createElement('div');
130
+ container.style.cssText = 'display: flex; flex-direction: column; gap: 20px;';
131
+
132
+ // Row 1: Enabled
133
+ const row1 = document.createElement('div');
134
+ row1.style.cssText = 'display: flex; gap: 16px; flex-wrap: wrap; align-items: center;';
135
+
136
+ ['primary', 'secondary', 'outline', 'action', 'tertiary'].forEach(variant => {
137
+ row1.appendChild(createButton({ variant, label: variant.charAt(0).toUpperCase() + variant.slice(1) }));
138
+ });
139
+ container.appendChild(row1);
140
+
141
+ // Row 2: Disabled
142
+ const row2 = document.createElement('div');
143
+ row2.style.cssText = 'display: flex; gap: 16px; flex-wrap: wrap; align-items: center;';
144
+
145
+ ['primary', 'secondary', 'outline', 'action', 'tertiary'].forEach(variant => {
146
+ row2.appendChild(createButton({ variant, label: variant.charAt(0).toUpperCase() + variant.slice(1), disabled: true }));
147
+ });
148
+ container.appendChild(row2);
149
+
150
+ return container;
151
+ }
152
+ };
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-button.js';
3
+
4
+ describe('ds-button', () => {
5
+ let container;
6
+
7
+ beforeEach(() => {
8
+ container = document.createElement('div');
9
+ document.body.appendChild(container);
10
+ });
11
+
12
+ afterEach(() => {
13
+ container.remove();
14
+ });
15
+
16
+ it('should render default primary variant', async () => {
17
+ container.innerHTML = '<ds-button>Click me</ds-button>';
18
+ const el = container.querySelector('ds-button');
19
+ await new Promise(resolve => setTimeout(resolve, 100));
20
+
21
+ const button = el.shadowRoot.querySelector('button');
22
+ expect(button).toBeTruthy();
23
+ expect(el.variant).toBe('primary');
24
+ });
25
+
26
+ it('should reflect variant attribute', async () => {
27
+ container.innerHTML = '<ds-button variant="secondary">Secondary</ds-button>';
28
+ const el = container.querySelector('ds-button');
29
+ await new Promise(resolve => setTimeout(resolve, 100));
30
+
31
+ expect(el.variant).toBe('secondary');
32
+ expect(el.getAttribute('variant')).toBe('secondary');
33
+ });
34
+
35
+ it('should render disabled state', async () => {
36
+ container.innerHTML = '<ds-button disabled>Disabled</ds-button>';
37
+ const el = container.querySelector('ds-button');
38
+ await new Promise(resolve => setTimeout(resolve, 100));
39
+
40
+ const button = el.shadowRoot.querySelector('button');
41
+ expect(button.disabled).toBe(true);
42
+ expect(el.hasAttribute('disabled')).toBe(true);
43
+ });
44
+
45
+ it('should render slots correctly', async () => {
46
+ container.innerHTML = `
47
+ <ds-button>
48
+ <div slot="icon-start">Start</div>
49
+ Label
50
+ <div slot="icon-end">End</div>
51
+ </ds-button>
52
+ `;
53
+ const el = container.querySelector('ds-button');
54
+ await new Promise(resolve => setTimeout(resolve, 100));
55
+
56
+ const slots = el.shadowRoot.querySelectorAll('slot');
57
+ expect(slots.length).toBe(3);
58
+ expect(slots[0].name).toBe('icon-start');
59
+ expect(slots[1].name).toBe('');
60
+ expect(slots[2].name).toBe('icon-end');
61
+ });
62
+ });
@@ -0,0 +1 @@
1
+ export { DsButton } from './ds-button.js';
@@ -0,0 +1,82 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ /**
4
+ * Button component for the Design System
5
+ *
6
+ * @element ds-button-group
7
+ *
8
+ * @prop {string} align - Layout/Alignment of buttons: 'start' | 'end' | 'stack' (default: 'start')
9
+ * 'start': Left aligned
10
+ * 'end': Right aligned (reversed visual order)
11
+ * 'stack': Vertical stack, full width
12
+ *
13
+ * @slot - Button content
14
+ */
15
+ export class DsButtonGroup extends LitElement {
16
+ static properties = {
17
+ align: { type: String, reflect: true }
18
+ };
19
+
20
+ static styles = css`
21
+ :host {
22
+ display: flex;
23
+ width: 100%;
24
+ }
25
+
26
+ /* Base Layout */
27
+ .button-group {
28
+ display: flex;
29
+ flex-wrap: wrap;
30
+ width: 100%;
31
+ gap: var(--ds-space-sm, 8px);
32
+ }
33
+
34
+ /*
35
+ * Alignment / Layout Variants
36
+ */
37
+
38
+ /* Start (Default) */
39
+ :host([align="start"]) .button-group {
40
+ flex-direction: row;
41
+ justify-content: flex-start;
42
+ align-items: center;
43
+ }
44
+
45
+ /* End (Visual Inversion for Dialogs) */
46
+ :host([align="end"]) .button-group {
47
+ flex-direction: row-reverse;
48
+ justify-content: flex-start;
49
+ align-items: center;
50
+ }
51
+
52
+ /* Stack (Vertical, Full Width) */
53
+ :host([align="stack"]) .button-group {
54
+ flex-direction: column;
55
+ align-items: stretch;
56
+ }
57
+
58
+ /* Ensure buttons take full width when stacked */
59
+ :host([align="stack"]) ::slotted(ds-button) {
60
+ width: 100%;
61
+ display: flex; /* Ensure ds-button behaves as a flex container if it isn't already, or fills width */
62
+ }
63
+
64
+ /* Specific fix for ds-button internal styling if needed */
65
+ /* If ds-button host is inline-block, width:100% works but we might need to ensure its internal button is also 100% */
66
+ `;
67
+
68
+ constructor() {
69
+ super();
70
+ this.align = 'start';
71
+ }
72
+
73
+ render() {
74
+ return html`
75
+ <div class="button-group">
76
+ <slot></slot>
77
+ </div>
78
+ `;
79
+ }
80
+ }
81
+
82
+ customElements.define('ds-button-group', DsButtonGroup);
@@ -0,0 +1,39 @@
1
+ import { Meta, Story, Canvas, Controls } from '@storybook/blocks';
2
+ import { DsButtonGroup } from './ds-button-group';
3
+ import * as stories from './ds-button-group.stories';
4
+
5
+ <Meta title="Components/Button Group" component={DsButtonGroup} />
6
+
7
+ # Button Group
8
+
9
+ The `ds-button-group` component is used to group related buttons together. It supports horizontal and vertical stacking, custom spacing, and alignment options.
10
+
11
+ ## Usage
12
+
13
+ ```html
14
+ <ds-button-group>
15
+ <ds-button>Primary</ds-button>
16
+ <ds-button variant="secondary">Secondary</ds-button>
17
+ </ds-button-group>
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ ### Default (Left Aligned)
23
+ <Canvas>
24
+ <Story name="Default" story={stories.Default} />
25
+ </Canvas>
26
+
27
+ ### Right Aligned (Dialog Footer)
28
+ <Canvas>
29
+ <Story name="RightAligned" story={stories.RightAligned} />
30
+ </Canvas>
31
+
32
+ ### Vertical Stack
33
+ <Canvas>
34
+ <Story name="VerticalStack" story={stories.Stacked} />
35
+ </Canvas>
36
+
37
+ ## API
38
+
39
+ <Controls />
@@ -0,0 +1,47 @@
1
+ import { html } from 'lit';
2
+ import './ds-button-group';
3
+ import '../ds-button/ds-button';
4
+
5
+ export default {
6
+ title: 'Components/Button Group',
7
+ component: 'ds-button-group',
8
+ argTypes: {
9
+ align: {
10
+ control: { type: 'select' },
11
+ options: ['start', 'end', 'stack'],
12
+ description: 'Layout of the buttons. "end" reverses visual order. "stack" is vertical full-width.',
13
+ },
14
+ },
15
+ };
16
+
17
+ const Template = ({ align }) => html`
18
+ <div style="max-width: 300px;">
19
+ <ds-button-group align=${align}>
20
+ <ds-button variant="primary">Primary</ds-button>
21
+ <ds-button variant="secondary">Secondary</ds-button>
22
+ <ds-button variant="action">Action</ds-button>
23
+ </ds-button-group>
24
+ </div>
25
+ `;
26
+
27
+ export const Default = Template.bind({});
28
+ Default.args = {
29
+ align: 'start',
30
+ };
31
+
32
+ export const RightAligned = Template.bind({});
33
+ RightAligned.args = {
34
+ align: 'end',
35
+ };
36
+ RightAligned.parameters = {
37
+ docs: {
38
+ description: {
39
+ story: 'Visual Order is reversed [Action] [Secondary] [Primary] relative to DOM.',
40
+ },
41
+ },
42
+ };
43
+
44
+ export const Stacked = Template.bind({});
45
+ Stacked.args = {
46
+ align: 'stack',
47
+ };
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-button-group.js';
3
+ import '../ds-button/ds-button.js';
4
+
5
+ describe('ds-button-group', () => {
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 = `
19
+ <ds-button-group>
20
+ <ds-button>Button 1</ds-button>
21
+ <ds-button>Button 2</ds-button>
22
+ </ds-button-group>
23
+ `;
24
+ const el = container.querySelector('ds-button-group');
25
+ await new Promise(resolve => setTimeout(resolve, 100));
26
+
27
+ expect(el).toBeTruthy();
28
+ expect(el.align).toBe('start');
29
+ });
30
+
31
+ it('applies correct alignment attribute', async () => {
32
+ container.innerHTML = '<ds-button-group align="end"></ds-button-group>';
33
+ const el = container.querySelector('ds-button-group');
34
+ await new Promise(resolve => setTimeout(resolve, 100));
35
+
36
+ expect(el.getAttribute('align')).toBe('end');
37
+ expect(el.align).toBe('end');
38
+ });
39
+
40
+ it('applies stack alignment', async () => {
41
+ container.innerHTML = '<ds-button-group align="stack"></ds-button-group>';
42
+ const el = container.querySelector('ds-button-group');
43
+ await new Promise(resolve => setTimeout(resolve, 100));
44
+
45
+ expect(el.getAttribute('align')).toBe('stack');
46
+ });
47
+ });
@@ -0,0 +1 @@
1
+ export * from './ds-button-group';
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './ds-checkbox.js';
4
+
5
+ describe('ds-checkbox 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('should pass axe accessibility checks in default state', async () => {
18
+ container.innerHTML = '<ds-checkbox label="Accept terms"></ds-checkbox>';
19
+ await new Promise(resolve => setTimeout(resolve, 100));
20
+
21
+ const results = await axe.run(container, {
22
+ rules: { 'color-contrast': { enabled: false } }
23
+ });
24
+ if (results.violations.length > 0) {
25
+ console.log('Violations:', JSON.stringify(results.violations, null, 2));
26
+ }
27
+ expect(results.violations).toHaveLength(0);
28
+ });
29
+
30
+ it('should pass axe accessibility checks when checked', async () => {
31
+ container.innerHTML = '<ds-checkbox label="I agree" checked></ds-checkbox>';
32
+ await new Promise(resolve => setTimeout(resolve, 100));
33
+
34
+ const results = await axe.run(container, {
35
+ rules: { 'color-contrast': { enabled: false } }
36
+ });
37
+ expect(results.violations).toHaveLength(0);
38
+ });
39
+
40
+ it('should pass axe accessibility checks when indeterminate', async () => {
41
+ container.innerHTML = '<ds-checkbox label="Select all" indeterminate></ds-checkbox>';
42
+ await new Promise(resolve => setTimeout(resolve, 100));
43
+
44
+ const results = await axe.run(container, {
45
+ rules: { 'color-contrast': { enabled: false } }
46
+ });
47
+ expect(results.violations).toHaveLength(0);
48
+ });
49
+
50
+ it('should pass axe accessibility checks when disabled', async () => {
51
+ container.innerHTML = '<ds-checkbox label="Disabled option" disabled></ds-checkbox>';
52
+ await new Promise(resolve => setTimeout(resolve, 100));
53
+
54
+ const results = await axe.run(container, {
55
+ rules: { 'color-contrast': { enabled: false } }
56
+ });
57
+ expect(results.violations).toHaveLength(0);
58
+ });
59
+
60
+ it('should pass axe accessibility checks in error state', async () => {
61
+ container.innerHTML = '<ds-checkbox label="Required field" validation-status="error"></ds-checkbox>';
62
+ await new Promise(resolve => setTimeout(resolve, 100));
63
+
64
+ const results = await axe.run(container, {
65
+ rules: { 'color-contrast': { enabled: false } }
66
+ });
67
+ expect(results.violations).toHaveLength(0);
68
+ });
69
+
70
+ it('should pass axe accessibility checks with required attribute', async () => {
71
+ container.innerHTML = '<ds-checkbox label="Required checkbox" required></ds-checkbox>';
72
+ await new Promise(resolve => setTimeout(resolve, 100));
73
+
74
+ const results = await axe.run(container, {
75
+ rules: { 'color-contrast': { enabled: false } }
76
+ });
77
+ expect(results.violations).toHaveLength(0);
78
+ });
79
+ });