@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,146 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-accordion.js';
3
+ import './ds-accordion-item.js';
4
+
5
+ describe('ds-accordion', () => {
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 slots', async () => {
18
+ container.innerHTML = `
19
+ <ds-accordion>
20
+ <ds-accordion-item heading="Item 1">Content 1</ds-accordion-item>
21
+ <ds-accordion-item heading="Item 2">Content 2</ds-accordion-item>
22
+ </ds-accordion>
23
+ `;
24
+ const el = container.querySelector('ds-accordion');
25
+ await new Promise(resolve => setTimeout(resolve, 50)); // wait for render
26
+
27
+ expect(el).toBeTruthy();
28
+ const items = el.querySelectorAll('ds-accordion-item');
29
+ expect(items.length).toBe(2);
30
+ });
31
+
32
+ it('allows only one item open by default', async () => {
33
+ container.innerHTML = `
34
+ <ds-accordion>
35
+ <ds-accordion-item heading="Item 1" open>Content 1</ds-accordion-item>
36
+ <ds-accordion-item heading="Item 2">Content 2</ds-accordion-item>
37
+ </ds-accordion>
38
+ `;
39
+ const el = container.querySelector('ds-accordion');
40
+ await new Promise(resolve => setTimeout(resolve, 50));
41
+
42
+ const items = el.querySelectorAll('ds-accordion-item');
43
+ expect(items[0].open).toBe(true);
44
+ expect(items[1].open).toBe(false);
45
+
46
+ // Click header of second item
47
+ const header2 = items[1].shadowRoot.querySelector('.header');
48
+ header2.click();
49
+ await new Promise(resolve => setTimeout(resolve, 50));
50
+
51
+ expect(items[0].open).toBe(false);
52
+ expect(items[1].open).toBe(true);
53
+ });
54
+
55
+ it('allows multiple items open when multiple is true', async () => {
56
+ container.innerHTML = `
57
+ <ds-accordion multiple>
58
+ <ds-accordion-item heading="Item 1" open>Content 1</ds-accordion-item>
59
+ <ds-accordion-item heading="Item 2">Content 2</ds-accordion-item>
60
+ </ds-accordion>
61
+ `;
62
+ const el = container.querySelector('ds-accordion');
63
+ await new Promise(resolve => setTimeout(resolve, 50));
64
+
65
+ const items = el.querySelectorAll('ds-accordion-item');
66
+
67
+ const header2 = items[1].shadowRoot.querySelector('.header');
68
+ header2.click();
69
+ await new Promise(resolve => setTimeout(resolve, 50));
70
+
71
+ expect(items[0].open).toBe(true);
72
+ expect(items[1].open).toBe(true);
73
+ });
74
+ });
75
+
76
+ describe('ds-accordion-item', () => {
77
+ let container;
78
+
79
+ beforeEach(() => {
80
+ container = document.createElement('div');
81
+ document.body.appendChild(container);
82
+ });
83
+
84
+ afterEach(() => {
85
+ container.remove();
86
+ });
87
+
88
+ it('toggles open state on click', async () => {
89
+ container.innerHTML = '<ds-accordion-item heading="Test">Content</ds-accordion-item>';
90
+ const el = container.querySelector('ds-accordion-item');
91
+ await new Promise(resolve => setTimeout(resolve, 50));
92
+
93
+ const header = el.shadowRoot.querySelector('.header');
94
+
95
+ expect(el.open).toBe(false);
96
+
97
+ header.click();
98
+ await new Promise(resolve => setTimeout(resolve, 50));
99
+ expect(el.open).toBe(true);
100
+
101
+ header.click();
102
+ await new Promise(resolve => setTimeout(resolve, 50));
103
+ expect(el.open).toBe(false);
104
+ });
105
+
106
+ it('reflects chevron-position attribute', async () => {
107
+ container.innerHTML = '<ds-accordion-item heading="Test" chevron-position="left">Content</ds-accordion-item>';
108
+ const el = container.querySelector('ds-accordion-item');
109
+ await new Promise(resolve => setTimeout(resolve, 50));
110
+
111
+ expect(el.getAttribute('chevron-position')).toBe('left');
112
+ const header = el.shadowRoot.querySelector('.header');
113
+ expect(header.classList.contains('chevron-left')).toBe(true);
114
+ });
115
+
116
+ it('does not toggle when disabled', async () => {
117
+ container.innerHTML = '<ds-accordion-item heading="Test" disabled>Content</ds-accordion-item>';
118
+ const el = container.querySelector('ds-accordion-item');
119
+ await new Promise(resolve => setTimeout(resolve, 50));
120
+
121
+ const header = el.shadowRoot.querySelector('.header');
122
+
123
+ header.click();
124
+ await new Promise(resolve => setTimeout(resolve, 50));
125
+ expect(el.open).toBe(false);
126
+ });
127
+
128
+ // Test for custom events isn't easy with basic DOM unless we adhere to vitest mocking or just listen
129
+ it('dispatches ds-accordion-item-toggle event', async () => {
130
+ container.innerHTML = '<ds-accordion-item heading="Test">Content</ds-accordion-item>';
131
+ const el = container.querySelector('ds-accordion-item');
132
+ await new Promise(resolve => setTimeout(resolve, 50));
133
+
134
+ let eventDetail = null;
135
+ el.addEventListener('ds-accordion-item-toggle', (e) => {
136
+ eventDetail = e.detail;
137
+ });
138
+
139
+ const header = el.shadowRoot.querySelector('.header');
140
+ header.click();
141
+ await new Promise(resolve => setTimeout(resolve, 50));
142
+
143
+ expect(eventDetail).not.toBeNull();
144
+ expect(eventDetail.open).toBe(true);
145
+ });
146
+ });
@@ -0,0 +1,2 @@
1
+ export * from './ds-accordion.js';
2
+ export * from './ds-accordion-item.js';
@@ -0,0 +1,116 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ /**
4
+ * Action Bar component for the Design System
5
+ *
6
+ * @element ds-action-bar
7
+ *
8
+ * @prop {string} variant - 'pattern' | 'page' (default: 'pattern')
9
+ * @prop {boolean} divider - Whether to show top divider (border). Only applies to 'pattern' usually, but can apply to page.
10
+ *
11
+ * @slot start - Left aligned content
12
+ * @slot end - Right aligned content (default)
13
+ */
14
+ export class DsActionBar extends LitElement {
15
+ static properties = {
16
+ variant: { type: String, reflect: true },
17
+ divider: { type: Boolean, reflect: true }
18
+ };
19
+
20
+ static styles = css`
21
+ :host {
22
+ display: block;
23
+ width: 100%;
24
+ box-sizing: border-box;
25
+ background: var(--ds-color-bg-surface, #ffffff); /* Default background */
26
+ }
27
+
28
+ .action-bar {
29
+ display: flex;
30
+ justify-content: space-between;
31
+ align-items: center;
32
+ width: 100%;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ /*
37
+ * VARIANTS
38
+ */
39
+
40
+ /* Pattern (Default)
41
+ Padding: 16px vertical (16 top, 16 bottom), 16px horizontal
42
+ Gap: 16px
43
+ */
44
+ :host([variant="pattern"]) .action-bar,
45
+ :host(:not([variant])) .action-bar {
46
+ padding: 16px 16px;
47
+ gap: 16px;
48
+ }
49
+
50
+ /* Page
51
+ Padding: 24px vertical (24 top, 24 bottom), 32px horizontal
52
+ Gap: 32px
53
+ Elevation: shadow token
54
+ */
55
+ :host([variant="page"]) {
56
+ box-shadow: var(--ds-elevation-low, 0 2px 4px rgba(0,0,0,0.1)); /* Use proper token if available */
57
+ }
58
+
59
+ :host([variant="page"]) .action-bar {
60
+ padding: 24px 32px;
61
+ gap: 32px;
62
+ }
63
+
64
+ /*
65
+ * DIVIDER LOGIC
66
+ * Using absolute position to avoid affecting layout/padding.
67
+ */
68
+
69
+ :host([divider]) {
70
+ position: relative;
71
+ }
72
+
73
+ :host([divider])::before {
74
+ content: '';
75
+ position: absolute;
76
+ top: 0;
77
+ left: 0;
78
+ right: 0;
79
+ height: 2px;
80
+ background-color: var(--ds-color-border, #e0e0e0);
81
+ }
82
+
83
+ /* Remove old padding adjustments */
84
+
85
+ /* Slot utility styling */
86
+ .slot-start,
87
+ .slot-end {
88
+ display: flex;
89
+ align-items: center;
90
+ }
91
+
92
+ /* Ensure content in slots can flex if needed, but usually they are static groups */
93
+ `;
94
+
95
+ constructor() {
96
+ super();
97
+ this.variant = 'pattern';
98
+ this.divider = false;
99
+ }
100
+
101
+ render() {
102
+ return html`
103
+ <div class="action-bar">
104
+ <div class="slot-start">
105
+ <slot name="start"></slot>
106
+ </div>
107
+ <div class="slot-end">
108
+ <slot name="end"></slot>
109
+ <slot></slot> <!-- Default slot goes to end -->
110
+ </div>
111
+ </div>
112
+ `;
113
+ }
114
+ }
115
+
116
+ customElements.define('ds-action-bar', DsActionBar);
@@ -0,0 +1,86 @@
1
+ import { html } from 'lit';
2
+ import './ds-action-bar';
3
+ import '../ds-button/ds-button';
4
+ import '../ds-button-group/ds-button-group';
5
+
6
+ export default {
7
+ title: 'Components/Action Bar',
8
+ component: 'ds-action-bar',
9
+ argTypes: {
10
+ variant: {
11
+ control: { type: 'select' },
12
+ options: ['pattern', 'page'],
13
+ description: 'Variant of the Action Bar',
14
+ },
15
+ divider: {
16
+ control: { type: 'boolean' },
17
+ description: 'Show top border divider',
18
+ }
19
+ },
20
+ parameters: {
21
+ layout: 'fullscreen',
22
+ }
23
+ };
24
+
25
+ const Template = ({ variant, divider }) => html`
26
+ <div style="background: #f4f4f4; padding: 50px;">
27
+ <!-- Container to simulate context -->
28
+ <div style="background: white; border: 1px dashed #ccc;">
29
+ <div style="padding: 20px;">Content above action bar...</div>
30
+
31
+ <ds-action-bar variant=${variant} ?divider=${divider}>
32
+ <div slot="start" style="font-family: sans-serif; font-size: 14px; color: #666;">
33
+ Step 1 of 3
34
+ </div>
35
+
36
+ <ds-button-group align="end">
37
+ <ds-button variant="primary">Confirm</ds-button>
38
+ <ds-button variant="secondary">Cancel</ds-button>
39
+ </ds-button-group>
40
+ </ds-action-bar>
41
+ </div>
42
+ </div>
43
+ `;
44
+
45
+ export const Pattern = Template.bind({});
46
+ Pattern.args = {
47
+ variant: 'pattern',
48
+ divider: false,
49
+ };
50
+
51
+ export const PatternWithDivider = Template.bind({});
52
+ PatternWithDivider.args = {
53
+ variant: 'pattern',
54
+ divider: true,
55
+ };
56
+
57
+ // Page Sticky Footer Example
58
+ export const Page = () => html`
59
+ <div style="background: #f4f4f4; min-height: 100vh; display: flex; flex-direction: column;">
60
+ <!-- Content Area -->
61
+ <div style="flex: 1; padding: 40px; max-width: 800px; margin: 0 auto; width: 100%; box-sizing: border-box;">
62
+ <h1>Page Title</h1>
63
+ <p>Scroll down to see the sticky action bar...</p>
64
+ ${Array(5).fill(html`<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>`).map(p => p)}
65
+ </div>
66
+
67
+ <!-- Sticky Bottom Action Bar -->
68
+ <div style="position: sticky; bottom: 0; width: 100%; z-index: 100;">
69
+ <ds-action-bar variant="page" divider>
70
+ <div slot="start" style="font-family: sans-serif; font-size: 14px; color: #666;">
71
+ Last saved: Just now
72
+ </div>
73
+
74
+ <ds-button-group align="end">
75
+ <ds-button variant="primary">Save Changes</ds-button>
76
+ <ds-button variant="secondary">Cancel</ds-button>
77
+ </ds-button-group>
78
+ </ds-action-bar>
79
+ </div>
80
+ </div>
81
+ `;
82
+ Page.parameters = {
83
+ docs: {
84
+ story: 'Demonstrates a full-page sticky footer action bar.',
85
+ }
86
+ };
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-action-bar.js';
3
+ import '../ds-button/ds-button.js';
4
+ import '../ds-button-group/ds-button-group.js';
5
+
6
+ describe('ds-action-bar', () => {
7
+ let container;
8
+
9
+ beforeEach(() => {
10
+ container = document.createElement('div');
11
+ document.body.appendChild(container);
12
+ });
13
+
14
+ afterEach(() => {
15
+ container.remove();
16
+ });
17
+
18
+ it('renders with default values (pattern)', async () => {
19
+ container.innerHTML = `
20
+ <ds-action-bar>
21
+ <ds-button>Action</ds-button>
22
+ </ds-action-bar>
23
+ `;
24
+ const el = container.querySelector('ds-action-bar');
25
+ await new Promise(resolve => setTimeout(resolve, 100));
26
+
27
+ expect(el).toBeTruthy();
28
+ expect(el.variant).toBe('pattern');
29
+ expect(el.divider).toBe(false);
30
+ });
31
+
32
+ it('renders page variant', async () => {
33
+ container.innerHTML = '<ds-action-bar variant="page"></ds-action-bar>';
34
+ const el = container.querySelector('ds-action-bar');
35
+ await new Promise(resolve => setTimeout(resolve, 100));
36
+
37
+ expect(el.getAttribute('variant')).toBe('page');
38
+ });
39
+
40
+ it('renders divider', async () => {
41
+ container.innerHTML = '<ds-action-bar divider></ds-action-bar>';
42
+ const el = container.querySelector('ds-action-bar');
43
+ await new Promise(resolve => setTimeout(resolve, 100));
44
+
45
+ expect(el.hasAttribute('divider')).toBe(true);
46
+ });
47
+
48
+ it('renders slots correctly', async () => {
49
+ container.innerHTML = `
50
+ <ds-action-bar>
51
+ <div slot="start" id="start-content">Start</div>
52
+ <ds-button id="end-content">End</ds-button>
53
+ </ds-action-bar>
54
+ `;
55
+ const el = container.querySelector('ds-action-bar');
56
+ await new Promise(resolve => setTimeout(resolve, 100));
57
+
58
+ const start = el.querySelector('#start-content');
59
+ const end = el.querySelector('#end-content');
60
+ expect(start).toBeTruthy();
61
+ expect(end).toBeTruthy();
62
+ expect(start.textContent).toBe('Start');
63
+ });
64
+ });
@@ -0,0 +1 @@
1
+ export * from './ds-action-bar';
@@ -0,0 +1,151 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './ds-alert.js';
4
+
5
+ describe('ds-alert 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 with default settings', async () => {
18
+ container.innerHTML = '<ds-alert>This is an informational message</ds-alert>';
19
+ await new Promise(resolve => setTimeout(resolve, 100));
20
+
21
+ const results = await axe.run(container, {
22
+ rules: {
23
+ 'color-contrast': { enabled: false },
24
+ 'button-name': { enabled: false },
25
+ 'nested-interactive': { enabled: false }
26
+ }
27
+ });
28
+ if (results.violations.length > 0) {
29
+ console.log('Violations:', JSON.stringify(results.violations, null, 2));
30
+ }
31
+ expect(results.violations).toHaveLength(0);
32
+ });
33
+
34
+ it('should pass axe accessibility checks for error status', async () => {
35
+ container.innerHTML = '<ds-alert status="error">Error occurred</ds-alert>';
36
+ await new Promise(resolve => setTimeout(resolve, 100));
37
+
38
+ const results = await axe.run(container, {
39
+ rules: {
40
+ 'color-contrast': { enabled: false },
41
+ 'button-name': { enabled: false },
42
+ 'nested-interactive': { enabled: false }
43
+ }
44
+ });
45
+ expect(results.violations).toHaveLength(0);
46
+ });
47
+
48
+ it('should pass axe accessibility checks for warning status', async () => {
49
+ container.innerHTML = '<ds-alert status="warning">Warning message</ds-alert>';
50
+ await new Promise(resolve => setTimeout(resolve, 100));
51
+
52
+ const results = await axe.run(container, {
53
+ rules: {
54
+ 'color-contrast': { enabled: false },
55
+ 'button-name': { enabled: false },
56
+ 'nested-interactive': { enabled: false }
57
+ }
58
+ });
59
+ expect(results.violations).toHaveLength(0);
60
+ });
61
+
62
+ it('should pass axe accessibility checks for success status', async () => {
63
+ container.innerHTML = '<ds-alert status="success">Success message</ds-alert>';
64
+ await new Promise(resolve => setTimeout(resolve, 100));
65
+
66
+ const results = await axe.run(container, {
67
+ rules: {
68
+ 'color-contrast': { enabled: false },
69
+ 'button-name': { enabled: false },
70
+ 'nested-interactive': { enabled: false }
71
+ }
72
+ });
73
+ expect(results.violations).toHaveLength(0);
74
+ });
75
+
76
+ it('should pass axe accessibility checks with title slot', async () => {
77
+ container.innerHTML = `
78
+ <ds-alert status="info">
79
+ <strong slot="title">Alert Title</strong>
80
+ This is the message content
81
+ </ds-alert>
82
+ `;
83
+ await new Promise(resolve => setTimeout(resolve, 100));
84
+
85
+ const results = await axe.run(container, {
86
+ rules: {
87
+ 'color-contrast': { enabled: false },
88
+ 'button-name': { enabled: false },
89
+ 'nested-interactive': { enabled: false }
90
+ }
91
+ });
92
+ expect(results.violations).toHaveLength(0);
93
+ });
94
+
95
+ it('should pass axe accessibility checks with actions slot', async () => {
96
+ container.innerHTML = `
97
+ <ds-alert status="warning">
98
+ Warning message
99
+ <div slot="actions">
100
+ <button>Action 1</button>
101
+ <button>Action 2</button>
102
+ </div>
103
+ </ds-alert>
104
+ `;
105
+ await new Promise(resolve => setTimeout(resolve, 100));
106
+
107
+ const results = await axe.run(container, {
108
+ rules: {
109
+ 'color-contrast': { enabled: false },
110
+ 'button-name': { enabled: false },
111
+ 'nested-interactive': { enabled: false }
112
+ }
113
+ });
114
+ expect(results.violations).toHaveLength(0);
115
+ });
116
+
117
+ it('should pass axe accessibility checks when dismissible', async () => {
118
+ container.innerHTML = '<ds-alert dismissible>Dismissible alert</ds-alert>';
119
+ await new Promise(resolve => setTimeout(resolve, 100));
120
+
121
+ const results = await axe.run(container, {
122
+ rules: {
123
+ 'color-contrast': { enabled: false },
124
+ 'button-name': { enabled: false },
125
+ 'nested-interactive': { enabled: false }
126
+ }
127
+ });
128
+ expect(results.violations).toHaveLength(0);
129
+ });
130
+
131
+ it('should have proper ARIA role', async () => {
132
+ container.innerHTML = '<ds-alert status="error">Error message</ds-alert>';
133
+ const element = container.querySelector('ds-alert');
134
+ await new Promise(resolve => setTimeout(resolve, 100));
135
+
136
+ const alertContainer = element.shadowRoot.querySelector('.alert');
137
+ expect(alertContainer.getAttribute('role')).toBe('alert');
138
+ });
139
+
140
+ it('should have accessible dismiss button', async () => {
141
+ container.innerHTML = '<ds-alert dismissible>Message</ds-alert>';
142
+ const element = container.querySelector('ds-alert');
143
+ await new Promise(resolve => setTimeout(resolve, 100));
144
+
145
+ const dismissButton = element.shadowRoot.querySelector('ds-icon-button');
146
+ expect(dismissButton).toBeTruthy();
147
+ expect(dismissButton.getAttribute('icon')).toBe('close');
148
+ expect(dismissButton.getAttribute('size')).toBe('m');
149
+ expect(dismissButton.getAttribute('aria-label')).toBe('Fechar alerta');
150
+ });
151
+ });