@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,83 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import './ds-avatar.js';
3
+
4
+ describe('ds-avatar', () => {
5
+ let container;
6
+
7
+ beforeEach(() => {
8
+ container = document.createElement('div');
9
+ document.body.appendChild(container);
10
+ });
11
+
12
+ afterEach(() => {
13
+ container.remove();
14
+ vi.restoreAllMocks();
15
+ });
16
+
17
+ it('should render default icon variant', async () => {
18
+ container.innerHTML = '<ds-avatar></ds-avatar>';
19
+ const el = container.querySelector('ds-avatar');
20
+ await new Promise(resolve => setTimeout(resolve, 0));
21
+
22
+ const icon = el.shadowRoot.querySelector('ds-icon');
23
+ expect(icon).toBeTruthy();
24
+ expect(icon.getAttribute('name')).toBe('account_circle');
25
+ });
26
+
27
+ it('should render image when src is provided', async () => {
28
+ container.innerHTML = '<ds-avatar src="image.jpg" alt="User Image"></ds-avatar>';
29
+ const el = container.querySelector('ds-avatar');
30
+ await new Promise(resolve => setTimeout(resolve, 0));
31
+
32
+ const img = el.shadowRoot.querySelector('img');
33
+ expect(img).toBeTruthy();
34
+ expect(img.getAttribute('src')).toBe('image.jpg');
35
+ expect(img.getAttribute('alt')).toBe('User Image');
36
+ });
37
+
38
+ it('should render initials when src is missing but initials are provided', async () => {
39
+ container.innerHTML = '<ds-avatar initials="MS"></ds-avatar>';
40
+ const el = container.querySelector('ds-avatar');
41
+ await new Promise(resolve => setTimeout(resolve, 0));
42
+
43
+ const initials = el.shadowRoot.querySelector('.avatar__initials');
44
+ expect(initials).toBeTruthy();
45
+ expect(initials.textContent).toBe('MS');
46
+ });
47
+
48
+ it('should render custom icon when no src/initials', async () => {
49
+ container.innerHTML = '<ds-avatar icon="star"></ds-avatar>';
50
+ const el = container.querySelector('ds-avatar');
51
+ await new Promise(resolve => setTimeout(resolve, 0));
52
+
53
+ const icon = el.shadowRoot.querySelector('ds-icon');
54
+ expect(icon.getAttribute('name')).toBe('star');
55
+ });
56
+
57
+ it('should apply size attribute', async () => {
58
+ container.innerHTML = '<ds-avatar size="sm"></ds-avatar>';
59
+ const el = container.querySelector('ds-avatar');
60
+ await new Promise(resolve => setTimeout(resolve, 0));
61
+
62
+ expect(el.size).toBe('sm');
63
+ expect(el.getAttribute('size')).toBe('sm');
64
+ });
65
+
66
+ it('should handle clickable state and emit click event', async () => {
67
+ const clickSpy = vi.fn();
68
+ container.innerHTML = '<ds-avatar clickable></ds-avatar>';
69
+ const el = container.querySelector('ds-avatar');
70
+ await new Promise(resolve => setTimeout(resolve, 0));
71
+
72
+ el.addEventListener('click', clickSpy);
73
+
74
+ // Setup internal click if needed, but since it's on host, we can simulate click on host
75
+ el.click();
76
+ expect(clickSpy).toHaveBeenCalled();
77
+
78
+ // Keyboard interaction
79
+ const event = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true });
80
+ el.dispatchEvent(event);
81
+ expect(clickSpy).toHaveBeenCalledTimes(2);
82
+ });
83
+ });
@@ -0,0 +1 @@
1
+ export { DsAvatar } from './ds-avatar.js';
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './ds-avatar-extended.js';
4
+
5
+ describe('ds-avatar-extended 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 be accessible', async () => {
18
+ container.innerHTML = `
19
+ <ds-avatar-extended
20
+ name="Miguel Silva"
21
+ additional-text="Developer"
22
+ initials="MS">
23
+ </ds-avatar-extended>`;
24
+ await new Promise(resolve => setTimeout(resolve, 0));
25
+
26
+ const results = await axe.run(container);
27
+ expect(results.violations).toHaveLength(0);
28
+ });
29
+ });
@@ -0,0 +1,108 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import '../ds-avatar/ds-avatar.js';
3
+
4
+ /**
5
+ * Extended Avatar component with name and additional text.
6
+ *
7
+ * @element ds-avatar-extended
8
+ *
9
+ * @prop {string} size - Avatar size: 'sm' | 'md' | 'lg' (default: 'md')
10
+ * @prop {string} src - Avatar image source
11
+ * @prop {string} alt - Avatar alt text
12
+ * @prop {string} initials - Avatar initials (overrides auto-generated)
13
+ * @prop {string} icon - Avatar icon
14
+ * @prop {string} backgroundColor - Avatar custom background color
15
+ * @prop {boolean} clickable - Whether the avatar is clickable
16
+ *
17
+ * @prop {string} name - Primary text (Body Bold - updated to Caption Bold)
18
+ * @prop {string} additionalText - Body Regular text (updated to Caption Regular)
19
+ * @prop {string} textPosition - 'left' | 'right' (default: 'right')
20
+ */
21
+ export class DsAvatarExtended extends LitElement {
22
+ static properties = {
23
+ // Avatar Props (Pass-through)
24
+ size: { type: String },
25
+ src: { type: String },
26
+ alt: { type: String },
27
+ initials: { type: String },
28
+ icon: { type: String },
29
+ backgroundColor: { type: String, attribute: 'background-color' },
30
+ clickable: { type: Boolean },
31
+
32
+ // Extended Props
33
+ name: { type: String },
34
+ additionalText: { type: String, attribute: 'additional-text' },
35
+ textPosition: { type: String, attribute: 'text-position' }
36
+ };
37
+
38
+ static styles = css`
39
+ :host {
40
+ display: inline-flex;
41
+ align-items: center;
42
+ gap: var(--ds-space-sm);
43
+ color: var(--ds-color-text-default);
44
+ }
45
+
46
+ :host([text-position="left"]) {
47
+ flex-direction: row-reverse;
48
+ text-align: right;
49
+ }
50
+
51
+ .info {
52
+ display: flex;
53
+ flex-direction: column;
54
+ justify-content: center;
55
+ }
56
+
57
+ .name {
58
+ font: var(--ds-typo-content-caption-bold);
59
+ color: var(--ds-color-text-default);
60
+ }
61
+
62
+ .additional-text {
63
+ font: var(--ds-typo-content-caption-regular);
64
+ color: var(--ds-color-text-default);
65
+ }
66
+ `;
67
+
68
+ constructor() {
69
+ super();
70
+ this.size = 'md';
71
+ this.textPosition = 'right';
72
+ }
73
+
74
+ _getInitials(name) {
75
+ if (!name) return '';
76
+ const parts = name.trim().split(/\s+/);
77
+ if (parts.length === 1) {
78
+ return parts[0].substring(0, 2).toUpperCase();
79
+ }
80
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
81
+ }
82
+
83
+ render() {
84
+ // Determine initials: explicit prop > auto-generated from name
85
+ const displayInitials = this.initials || this._getInitials(this.name);
86
+
87
+ return html`
88
+ <ds-avatar
89
+ .size=${this.size}
90
+ .src=${this.src}
91
+ .alt=${this.alt}
92
+ .initials=${displayInitials}
93
+ .icon=${this.icon}
94
+ .backgroundColor=${this.backgroundColor}
95
+ .clickable=${this.clickable}
96
+ ></ds-avatar>
97
+
98
+ <div class="info">
99
+ <span class="name">${this.name}</span>
100
+ ${this.additionalText
101
+ ? html`<span class="additional-text">${this.additionalText}</span>`
102
+ : ''}
103
+ </div>
104
+ `;
105
+ }
106
+ }
107
+
108
+ customElements.define('ds-avatar-extended', DsAvatarExtended);
@@ -0,0 +1,93 @@
1
+ import './ds-avatar-extended.js';
2
+
3
+ export default {
4
+ title: 'Components/Avatar Extended',
5
+ component: 'ds-avatar-extended',
6
+ argTypes: {
7
+ size: {
8
+ control: 'select',
9
+ options: ['sm', 'md', 'lg'],
10
+ },
11
+ name: { control: 'text' },
12
+ additionalText: { control: 'text' },
13
+ textPosition: {
14
+ control: 'select',
15
+ options: ['left', 'right']
16
+ },
17
+ src: { control: 'text' },
18
+ initials: { control: 'text' },
19
+ icon: { control: 'text' },
20
+ clickable: { control: 'boolean' }
21
+ },
22
+ };
23
+
24
+ const createExtended = ({ size, name, additionalText, textPosition, src, initials, icon, clickable }) => {
25
+ const el = document.createElement('ds-avatar-extended');
26
+
27
+ if (size) el.setAttribute('size', size);
28
+ if (name) el.setAttribute('name', name);
29
+ if (additionalText) el.setAttribute('additional-text', additionalText);
30
+ if (textPosition) el.setAttribute('text-position', textPosition);
31
+
32
+ if (src) el.setAttribute('src', src);
33
+ if (initials) el.setAttribute('initials', initials);
34
+ if (icon) el.setAttribute('icon', icon);
35
+ if (clickable) el.setAttribute('clickable', '');
36
+
37
+ return el;
38
+ };
39
+
40
+ export const Default = {
41
+ args: {
42
+ name: 'Miguel Silva',
43
+ additionalText: 'Software Engineer',
44
+ src: '/avatar-default.png',
45
+ size: 'md'
46
+ },
47
+ render: createExtended
48
+ };
49
+
50
+ export const AutoInitials = {
51
+ args: {
52
+ name: 'John Doe',
53
+ additionalText: 'Product Manager',
54
+ size: 'md'
55
+ },
56
+ render: createExtended
57
+ };
58
+
59
+ export const TextLeft = {
60
+ args: {
61
+ name: 'Jane Smith',
62
+ additionalText: 'Designer',
63
+ src: '/avatar-default.png',
64
+ textPosition: 'left'
65
+ },
66
+ render: createExtended
67
+ };
68
+
69
+ export const NameOnly = {
70
+ args: {
71
+ name: 'Guest User',
72
+ icon: 'account_circle',
73
+ },
74
+ render: createExtended
75
+ };
76
+
77
+ export const Sizes = {
78
+ render: () => {
79
+ const container = document.createElement('div');
80
+ container.style.cssText = 'display: flex; flex-direction: column; gap: 20px;';
81
+
82
+ ['sm', 'md', 'lg'].forEach(size => {
83
+ container.appendChild(createExtended({
84
+ size,
85
+ name: `Size ${size}`,
86
+ additionalText: 'Description',
87
+ // Auto initials will work here
88
+ }));
89
+ });
90
+
91
+ return container;
92
+ }
93
+ };
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-avatar-extended.js';
3
+
4
+ describe('ds-avatar-extended', () => {
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 avatar and text', async () => {
17
+ container.innerHTML = `
18
+ <ds-avatar-extended
19
+ name="Miguel Silva"
20
+ additional-text="Developer"
21
+ initials="MS">
22
+ </ds-avatar-extended>`;
23
+ const el = container.querySelector('ds-avatar-extended');
24
+ await new Promise(resolve => setTimeout(resolve, 0));
25
+
26
+ const avatar = el.shadowRoot.querySelector('ds-avatar');
27
+ expect(avatar).toBeTruthy();
28
+ expect(avatar.initials).toBe('MS');
29
+
30
+ const name = el.shadowRoot.querySelector('.name');
31
+ expect(name.textContent).toBe('Miguel Silva');
32
+
33
+ const additional = el.shadowRoot.querySelector('.additional-text');
34
+ expect(additional.textContent).toBe('Developer');
35
+ });
36
+
37
+ it('should auto-generate initials from name', async () => {
38
+ container.innerHTML = '<ds-avatar-extended name="Jane Doe"></ds-avatar-extended>';
39
+ const el = container.querySelector('ds-avatar-extended');
40
+ await new Promise(resolve => setTimeout(resolve, 0));
41
+
42
+ const avatar = el.shadowRoot.querySelector('ds-avatar');
43
+ expect(avatar.initials).toBe('JD');
44
+ });
45
+
46
+ it('should handle text position left', async () => {
47
+ container.innerHTML = `
48
+ <ds-avatar-extended
49
+ name="Miguel Silva"
50
+ text-position="left">
51
+ </ds-avatar-extended>`;
52
+ const el = container.querySelector('ds-avatar-extended');
53
+ await new Promise(resolve => setTimeout(resolve, 0));
54
+
55
+ expect(el.getAttribute('text-position')).toBe('left');
56
+ });
57
+
58
+ it('should pass clickable prop to avatar', async () => {
59
+ container.innerHTML = '<ds-avatar-extended clickable></ds-avatar-extended>';
60
+ const el = container.querySelector('ds-avatar-extended');
61
+ await new Promise(resolve => setTimeout(resolve, 0));
62
+
63
+ const avatar = el.shadowRoot.querySelector('ds-avatar');
64
+ expect(avatar.clickable).toBe(true);
65
+ });
66
+ });
@@ -0,0 +1 @@
1
+ export { DsAvatarExtended } from './ds-avatar-extended.js';
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './ds-banner.js';
4
+
5
+ describe('ds-banner 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-banner message="Accessible banner message"></ds-banner>';
19
+ const element = container.querySelector('ds-banner');
20
+ await element.updateComplete;
21
+
22
+ const results = await axe.run(element);
23
+ expect(results.violations.length).toBe(0);
24
+ });
25
+
26
+ it('should pass axe checks with different statuses', async () => {
27
+ const statuses = ['error', 'warning', 'success', 'info'];
28
+
29
+ for (const status of statuses) {
30
+ container.innerHTML = `<ds-banner status="${status}" message="Message for ${status}"></ds-banner>`;
31
+ const element = container.querySelector('ds-banner');
32
+ await element.updateComplete;
33
+
34
+ const results = await axe.run(element);
35
+ expect(results.violations.length).toBe(0);
36
+ }
37
+ });
38
+
39
+ it('should have accessible dismiss button', async () => {
40
+ container.innerHTML = '<ds-banner dismissible message="Dismissible banner"></ds-banner>';
41
+ const element = container.querySelector('ds-banner');
42
+ await element.updateComplete;
43
+
44
+ const dismissButton = element.shadowRoot.querySelector('ds-icon-button');
45
+ expect(dismissButton).toBeTruthy();
46
+ expect(dismissButton.getAttribute('aria-label')).toBe('Fechar banner');
47
+
48
+ const results = await axe.run(element);
49
+ expect(results.violations.length).toBe(0);
50
+ });
51
+ });
@@ -0,0 +1,233 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import '../ds-icon/ds-icon.js';
3
+ import '../ds-icon-button/ds-icon-button.js';
4
+
5
+ /**
6
+ * Banner component for system-wide feedback
7
+ *
8
+ * @element ds-banner
9
+ *
10
+ * @prop {string} status - Status type: 'error' | 'warning' | 'success' | 'info' (default: 'info')
11
+ * @prop {string} message - Message text
12
+ * @prop {boolean} dismissible - Show/hide close button (default: false)
13
+ * @prop {boolean} fixed - Whether the banner is fixed at the top of the viewport
14
+ * @prop {boolean} hideIcon - Hide the status icon
15
+ *
16
+ * @slot - Default slot for message content (if message prop is not used)
17
+ * @slot actions - Optional slot for action buttons
18
+ *
19
+ * @fires ds-dismiss - Dispatched when user clicks the close button
20
+ *
21
+ * @csspart banner - The main banner container
22
+ * @csspart content-container - The centered container limiting width
23
+ * @csspart icon - The status icon element
24
+ * @csspart message - The message text container
25
+ * @csspart actions - The actions slot container
26
+ * @csspart close-button - The close button
27
+ */
28
+ export class DsBanner extends LitElement {
29
+ static properties = {
30
+ status: { type: String, reflect: true },
31
+ title: { type: String },
32
+ message: { type: String },
33
+ dismissible: { type: Boolean, reflect: true },
34
+ fixed: { type: Boolean, reflect: true },
35
+ hideIcon: { type: Boolean, attribute: 'hide-icon', reflect: true },
36
+ _hasActions: { type: Boolean, state: true }
37
+ };
38
+
39
+ static styles = css`
40
+ :host {
41
+ display: block;
42
+ width: 100%;
43
+ box-sizing: border-box;
44
+ z-index: var(--ds-zindex-message, 70);
45
+ }
46
+
47
+ /* Fixed positioning variant */
48
+ :host([fixed]) {
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ right: 0;
53
+ }
54
+
55
+ .banner {
56
+ display: flex;
57
+ justify-content: center;
58
+ width: 100%;
59
+ min-height: 40px; /* Ensure 40px minimum height */
60
+ padding: 10px 8px; /* Matching ds-alert padding */
61
+ box-sizing: border-box;
62
+ }
63
+
64
+ /* Inner container to center content and constrain max-width of message */
65
+ .content-container {
66
+ display: flex;
67
+ align-items: flex-start; /* Align valid for multi-line */
68
+ width: 100%;
69
+ max-width: 100%; /* Allow taking full width if needed, but message will constrain */
70
+ gap: var(--ds-space-sm); /* 8px gap elements */
71
+ }
72
+
73
+ /* Side containers matching ds-alert strategy for alignment */
74
+ .icon-container,
75
+ .dismiss-container {
76
+ flex-shrink: 0;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ height: 20px; /* text line-height matching */
81
+ }
82
+
83
+ .icon {
84
+ color: var(--ds-color-icon-default);
85
+ }
86
+
87
+ /* Message Area */
88
+ .message-area {
89
+ flex: 1;
90
+ display: flex;
91
+ flex-direction: column;
92
+ min-width: 0;
93
+ color: var(--ds-color-text-default);
94
+ font: var(--ds-typo-content-body-regular);
95
+ }
96
+
97
+ /* Title styling */
98
+ .title {
99
+ font: var(--ds-typo-content-body-bold);
100
+ margin-bottom: var(--ds-space-xs); /* 4px gap to message */
101
+ max-width: 800px;
102
+ width: 100%;
103
+ }
104
+
105
+ /* Constrain the message text specifically to 800px as requested */
106
+ .message-text {
107
+ max-width: 800px;
108
+ width: 100%;
109
+ }
110
+
111
+ /* Actions slot wrapper */
112
+ .actions {
113
+ display: flex;
114
+ gap: var(--ds-space-sm);
115
+ flex-wrap: wrap;
116
+ margin-top: var(--ds-space-sm);
117
+ }
118
+
119
+ .actions[hidden] {
120
+ display: none;
121
+ }
122
+
123
+ /* Dismiss button container needs to be pushed to the right if we want it fully responsive,
124
+ but in this flex layout, flex:1 on message area handles it. */
125
+ .dismiss-container {
126
+ margin-left: auto; /* Push to the far right if content doesn't fill */
127
+ }
128
+
129
+ /* Status Styles - Background Colors */
130
+ :host([status="error"]) .banner {
131
+ background-color: var(--ds-color-bg-error-subtle);
132
+ }
133
+
134
+ :host([status="warning"]) .banner {
135
+ background-color: var(--ds-color-bg-warning-subtle);
136
+ }
137
+
138
+ :host([status="success"]) .banner {
139
+ background-color: var(--ds-color-bg-success-subtle);
140
+ }
141
+
142
+ :host([status="info"]) .banner,
143
+ :host(:not([status])) .banner {
144
+ background-color: var(--ds-color-bg-info-subtle);
145
+ }
146
+ `;
147
+
148
+ constructor() {
149
+ super();
150
+ this.status = 'info';
151
+ this.title = '';
152
+ this.message = '';
153
+ this.dismissible = false;
154
+ this.fixed = false;
155
+ this.hideIcon = false;
156
+ this._hasActions = false;
157
+ }
158
+
159
+ /**
160
+ * Returns the icon name based on the status
161
+ */
162
+ _getIconName() {
163
+ const iconMap = {
164
+ error: 'cancel',
165
+ warning: 'warning',
166
+ success: 'check-circle',
167
+ info: 'info'
168
+ };
169
+ return iconMap[this.status] || iconMap.info;
170
+ }
171
+
172
+ _handleDismiss() {
173
+ this.dispatchEvent(new CustomEvent('ds-dismiss', {
174
+ bubbles: true,
175
+ composed: true
176
+ }));
177
+ }
178
+
179
+ _handleSlotChange(e) {
180
+ const assignedNodes = e.target.assignedNodes({ flatten: true });
181
+ this._hasActions = assignedNodes.length > 0;
182
+ }
183
+
184
+ render() {
185
+ return html`
186
+ <div class="banner" part="banner" role="status">
187
+ <div class="content-container" part="content-container">
188
+
189
+ ${!this.hideIcon ? html`
190
+ <div class="icon-container" part="icon-container">
191
+ <ds-icon
192
+ class="icon"
193
+ name="${this._getIconName()}"
194
+ size="sm"
195
+ part="icon"
196
+ ></ds-icon>
197
+ </div>
198
+ ` : ''}
199
+
200
+ <div class="message-area" part="message-area">
201
+ ${this.title ? html`<div class="title" part="title">${this.title}</div>` : ''}
202
+
203
+ <div class="message-text" part="message">
204
+ ${this.message}
205
+ <slot></slot>
206
+ </div>
207
+
208
+ <div class="actions" ?hidden="${!this._hasActions}" part="actions">
209
+ <slot name="actions" @slotchange="${this._handleSlotChange}"></slot>
210
+ </div>
211
+ </div>
212
+
213
+ ${this.dismissible ? html`
214
+ <div class="dismiss-container" part="dismiss-container">
215
+ <ds-icon-button
216
+ class="close-button"
217
+ icon="close"
218
+ variant="action"
219
+ size="m"
220
+ aria-label="Fechar banner"
221
+ part="close-button"
222
+ @click="${this._handleDismiss}"
223
+ ></ds-icon-button>
224
+ </div>
225
+ ` : ''}
226
+
227
+ </div>
228
+ </div>
229
+ `;
230
+ }
231
+ }
232
+
233
+ customElements.define('ds-banner', DsBanner);