@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,55 @@
1
+ import { html } from 'lit';
2
+ import './ds-tag-action.js';
3
+
4
+ export default {
5
+ title: 'Components/Tag Action',
6
+ parameters: {
7
+ layout: 'padded',
8
+ },
9
+ };
10
+
11
+ const ActionTemplate = (args) => html`
12
+ <ds-tag-action
13
+ label=${args.label}
14
+ icon=${args.icon}
15
+ size=${args.size}
16
+ color=${args.color}
17
+ ?selected=${args.selected}
18
+ ?disabled=${args.disabled}
19
+ @ds-selected-change=${(e) => console.log('Selection changed:', e.detail)}
20
+ ></ds-tag-action>
21
+ `;
22
+
23
+ export const Default = ActionTemplate.bind({});
24
+ Default.args = {
25
+ label: 'Action Tag',
26
+ icon: 'star',
27
+ size: 'm',
28
+ color: 'neutral',
29
+ selected: false,
30
+ disabled: false,
31
+ };
32
+ Default.argTypes = {
33
+ size: { control: { type: 'select' }, options: ['s', 'm'] },
34
+ color: { control: { type: 'select' }, options: ['neutral', 'info'] },
35
+ icon: { control: 'text' },
36
+ };
37
+
38
+ export const AllStates = () => html`
39
+ <div style="display: flex; gap: 16px; flex-direction: column;">
40
+ <!-- Neutral -->
41
+ <div style="display: flex; gap: 8px; align-items: center;">
42
+ <ds-tag-action label="Neutral M" size="m" color="neutral"></ds-tag-action>
43
+ <ds-tag-action label="Neutral S" size="s" color="neutral"></ds-tag-action>
44
+ <ds-tag-action label="Selected" size="m" color="neutral" selected></ds-tag-action>
45
+ <ds-tag-action label="Disabled" size="m" color="neutral" disabled></ds-tag-action>
46
+ </div>
47
+ <!-- Info -->
48
+ <div style="display: flex; gap: 8px; align-items: center;">
49
+ <ds-tag-action label="Info M" size="m" color="info"></ds-tag-action>
50
+ <ds-tag-action label="Info S" size="s" color="info"></ds-tag-action>
51
+ <ds-tag-action label="Selected" size="m" color="info" selected></ds-tag-action>
52
+ <ds-tag-action label="Disabled" size="m" color="info" disabled></ds-tag-action>
53
+ </div>
54
+ </div>
55
+ `;
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-tag-action.js';
3
+
4
+ describe('DsTagAction', () => {
5
+ let container;
6
+ beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); });
7
+ afterEach(() => { container.remove(); });
8
+
9
+ it('renders with default values', async () => {
10
+ container.innerHTML = '<ds-tag-action label="Test"></ds-tag-action>';
11
+ await new Promise(r => setTimeout(r, 0));
12
+ const el = container.querySelector('ds-tag-action');
13
+ expect(el.label).toBe('Test');
14
+ expect(el.shadowRoot.querySelector('span').textContent).toBe('Test');
15
+ expect(el.getAttribute('size')).toBe('m');
16
+ expect(el.getAttribute('color')).toBe('neutral');
17
+ });
18
+
19
+ it('renders with icon', async () => {
20
+ container.innerHTML = '<ds-tag-action label="Test" icon="star"></ds-tag-action>';
21
+ await new Promise(r => setTimeout(r, 0));
22
+ const el = container.querySelector('ds-tag-action');
23
+ const icon = el.shadowRoot.querySelector('ds-icon');
24
+ expect(icon).to.exist;
25
+ expect(icon.getAttribute('name')).toBe('star');
26
+ });
27
+
28
+ it('reflects selected state', async () => {
29
+ container.innerHTML = '<ds-tag-action label="Test" selected></ds-tag-action>';
30
+ await new Promise(r => setTimeout(r, 0));
31
+ const el = container.querySelector('ds-tag-action');
32
+ expect(el.hasAttribute('selected')).toBe(true);
33
+ const btn = el.shadowRoot.querySelector('button');
34
+ expect(btn.getAttribute('aria-pressed')).toBe('true');
35
+ });
36
+
37
+ it('reflects disabled state', async () => {
38
+ container.innerHTML = '<ds-tag-action label="Test" disabled></ds-tag-action>';
39
+ await new Promise(r => setTimeout(r, 0));
40
+ const el = container.querySelector('ds-tag-action');
41
+ const btn = el.shadowRoot.querySelector('button');
42
+ expect(btn.hasAttribute('disabled')).toBe(true);
43
+ });
44
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-tag-removable.js';
3
+
4
+ describe('DsTagRemovable A11y', () => {
5
+ let container;
6
+ beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); });
7
+ afterEach(() => { container.remove(); });
8
+
9
+ it('internal remove button has correct aria-label', async () => {
10
+ container.innerHTML = '<ds-tag-removable label="My Tag"></ds-tag-removable>';
11
+ await new Promise(r => setTimeout(r, 0));
12
+ const el = container.querySelector('ds-tag-removable');
13
+ const btn = el.shadowRoot.querySelector('ds-icon-button');
14
+ expect(btn.getAttribute('aria-label')).toBe('Remove My Tag');
15
+ });
16
+
17
+ it('internal button disabled state', async () => {
18
+ container.innerHTML = '<ds-tag-removable label="Disabled" disabled></ds-tag-removable>';
19
+ await new Promise(r => setTimeout(r, 0));
20
+ const el = container.querySelector('ds-tag-removable');
21
+ const btn = el.shadowRoot.querySelector('ds-icon-button');
22
+ expect(btn.hasAttribute('disabled')).toBe(true);
23
+ });
24
+ });
@@ -0,0 +1,146 @@
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
+ * @element ds-tag-removable
7
+ * @summary A square-edged tag with a remove action.
8
+ *
9
+ * @prop {string} label - Text label.
10
+ * @prop {string} icon - Optional icon name (left).
11
+ * @prop {boolean} disabled - Whether the tag (and remove action) is disabled.
12
+ * @prop {string} size - 's' (24px) | 'm' (32px). Default: 'm'.
13
+ * @prop {string} color - 'neutral' | 'info'. Default: 'neutral'.
14
+ *
15
+ * @fires ds-tag-remove - Dispatched when remove button is clicked.
16
+ */
17
+ export class DsTagRemovable extends LitElement {
18
+ static properties = {
19
+ label: { type: String },
20
+ icon: { type: String },
21
+ disabled: { type: Boolean, reflect: true },
22
+ size: { type: String, reflect: true },
23
+ color: { type: String, reflect: true },
24
+ };
25
+
26
+ static styles = css`
27
+ :host {
28
+ display: inline-flex;
29
+ vertical-align: middle;
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ .container {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ box-sizing: border-box;
37
+
38
+ border-radius: var(--ds-radius-container, 0px); /* Square/Sharp */
39
+
40
+ font: var(--ds-typo-content-body-regular);
41
+ color: var(--ds-color-text-default);
42
+ white-space: nowrap; /* Prevent breaking on resize */
43
+
44
+ /* Gap Logic:
45
+ Gap between Icon and Label is 4px.
46
+ Label to Action gap is 4px.
47
+ */
48
+ }
49
+
50
+ .content-wrapper {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: 4px; /* Icon to Label gap */
54
+ margin-right: 4px; /* Label to Action gap */
55
+ }
56
+
57
+ /*
58
+ * SIZES
59
+ */
60
+ /* Size M: 32px height */
61
+ :host([size="m"]) .container,
62
+ :host(:not([size])) .container {
63
+ height: 32px;
64
+ padding: 0 4px 0 var(--ds-space-sm);
65
+ }
66
+
67
+ /* Size S: 24px height */
68
+ :host([size="s"]) .container {
69
+ height: 24px;
70
+ padding: 0 4px 0 var(--ds-space-sm);
71
+ }
72
+
73
+ /*
74
+ * COLORS
75
+ */
76
+ /* NEUTRAL */
77
+ :host([color="neutral"]) .container,
78
+ :host(:not([color])) .container {
79
+ background-color: var(--ds-color-bg-neutral-subtle);
80
+ }
81
+
82
+ /* INFO */
83
+ :host([color="info"]) .container {
84
+ background-color: var(--ds-color-bg-brand-subtle);
85
+ }
86
+
87
+ /*
88
+ * DISABLED
89
+ */
90
+ :host([disabled]) {
91
+ /* Reset opacity strategy */
92
+ pointer-events: none;
93
+ }
94
+ :host([disabled]) .container {
95
+ background-color: var(--ds-color-bg-disabled);
96
+ color: var(--ds-color-text-disabled);
97
+ }
98
+
99
+ ds-icon {
100
+ /* User: "tamanho do ícone deve ser size S em ambos os casos" */
101
+ --size: var(--ds-icon-size-sm, 20px);
102
+ color: currentColor;
103
+ }
104
+ `;
105
+
106
+ constructor() {
107
+ super();
108
+ this.disabled = false;
109
+ this.size = 'm';
110
+ this.color = 'neutral';
111
+ this.label = '';
112
+ }
113
+
114
+ _handleRemove(e) {
115
+ e.stopPropagation(); // Don't bubble click if container had one (optional)
116
+ this.dispatchEvent(new CustomEvent('ds-tag-remove', {
117
+ bubbles: true,
118
+ composed: true
119
+ }));
120
+ }
121
+
122
+ render() {
123
+ return html`
124
+ <div class="container" part="container">
125
+ <div class="content-wrapper">
126
+ ${this.icon ? html`<ds-icon name=${this.icon} size="sm" part="icon"></ds-icon>` : ''}
127
+ <span>${this.label}</span>
128
+ </div>
129
+
130
+ <ds-icon-button
131
+ icon="close"
132
+ variant="action"
133
+ size="s"
134
+ ?disabled=${this.disabled}
135
+ aria-label="Remove ${this.label}"
136
+ @click=${this._handleRemove}
137
+ part="remove-button"
138
+ ></ds-icon-button>
139
+ </div>
140
+ `;
141
+ }
142
+ }
143
+
144
+ if (!customElements.get('ds-tag-removable')) {
145
+ customElements.define('ds-tag-removable', DsTagRemovable);
146
+ }
@@ -0,0 +1,52 @@
1
+ import { html } from 'lit';
2
+ import './ds-tag-removable.js';
3
+
4
+ export default {
5
+ title: 'Components/Tag Removable',
6
+ parameters: {
7
+ layout: 'padded',
8
+ },
9
+ };
10
+
11
+ const RemovableTemplate = (args) => html`
12
+ <ds-tag-removable
13
+ label=${args.label}
14
+ icon=${args.icon}
15
+ size=${args.size}
16
+ color=${args.color}
17
+ ?disabled=${args.disabled}
18
+ @ds-tag-remove=${() => console.log('Remove Clicked')}
19
+ ></ds-tag-removable>
20
+ `;
21
+
22
+ export const Default = RemovableTemplate.bind({});
23
+ Default.args = {
24
+ label: 'Removable Tag',
25
+ icon: 'star',
26
+ size: 'm',
27
+ color: 'neutral',
28
+ disabled: false,
29
+ };
30
+ Default.argTypes = {
31
+ size: { control: { type: 'select' }, options: ['s', 'm'] },
32
+ color: { control: { type: 'select' }, options: ['neutral', 'info'] },
33
+ icon: { control: 'text' },
34
+ };
35
+
36
+ export const AllStates = () => html`
37
+ <div style="display: flex; gap: 16px; flex-direction: column;">
38
+ <!-- Neutral -->
39
+ <div style="display: flex; gap: 8px; align-items: flex-start;">
40
+ <ds-tag-removable label="Neutral M" size="m" color="neutral"></ds-tag-removable>
41
+ <ds-tag-removable label="Neutral S" size="s" color="neutral"></ds-tag-removable>
42
+ <ds-tag-removable label="With Icon" icon="favorite" size="m" color="neutral"></ds-tag-removable>
43
+ <ds-tag-removable label="Disabled" size="m" color="neutral" disabled></ds-tag-removable>
44
+ </div>
45
+ <!-- Info -->
46
+ <div style="display: flex; gap: 8px; align-items: flex-start;">
47
+ <ds-tag-removable label="Info M" size="m" color="info"></ds-tag-removable>
48
+ <ds-tag-removable label="Info S" size="s" color="info"></ds-tag-removable>
49
+ <ds-tag-removable label="Disabled" size="m" color="info" disabled></ds-tag-removable>
50
+ </div>
51
+ </div>
52
+ `;
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import './ds-tag-removable.js';
3
+
4
+ describe('DsTagRemovable', () => {
5
+ let container;
6
+ beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); });
7
+ afterEach(() => { container.remove(); });
8
+
9
+ it('renders with default values', async () => {
10
+ container.innerHTML = '<ds-tag-removable label="Removable"></ds-tag-removable>';
11
+ await new Promise(r => setTimeout(r, 0));
12
+ const el = container.querySelector('ds-tag-removable');
13
+ expect(el.label).toBe('Removable');
14
+ expect(el.shadowRoot.querySelector('span').textContent).toBe('Removable');
15
+ });
16
+
17
+ it('dispatches ds-tag-remove event when close button is clicked', async () => {
18
+ container.innerHTML = '<ds-tag-removable label="Removable"></ds-tag-removable>';
19
+ await new Promise(r => setTimeout(r, 0));
20
+ const el = container.querySelector('ds-tag-removable');
21
+ const removeBtn = el.shadowRoot.querySelector('ds-icon-button');
22
+
23
+ const listener = vi.fn();
24
+ el.addEventListener('ds-tag-remove', listener);
25
+
26
+ removeBtn.click();
27
+ expect(listener).toHaveBeenCalled();
28
+ });
29
+
30
+ it('passes disabled state to icon button', async () => {
31
+ container.innerHTML = '<ds-tag-removable label="Removable" disabled></ds-tag-removable>';
32
+ await new Promise(r => setTimeout(r, 0));
33
+ const el = container.querySelector('ds-tag-removable');
34
+ const removeBtn = el.shadowRoot.querySelector('ds-icon-button');
35
+ expect(removeBtn.hasAttribute('disabled')).toBe(true);
36
+ });
37
+
38
+ it('renders size S structure', async () => {
39
+ container.innerHTML = '<ds-tag-removable label="Small" size="s"></ds-tag-removable>';
40
+ await new Promise(r => setTimeout(r, 0));
41
+ const el = container.querySelector('ds-tag-removable');
42
+ const removeBtn = el.shadowRoot.querySelector('ds-icon-button');
43
+ expect(removeBtn.getAttribute('size')).toBe('s');
44
+ expect(el.getAttribute('size')).toBe('s');
45
+ });
46
+ });
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './ds-tag-status.js';
4
+
5
+ describe('ds-tag-status 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 size S', async () => {
18
+ container.innerHTML = '<ds-tag-status status="success" label="Active"></ds-tag-status>';
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 with size XS', async () => {
31
+ container.innerHTML = '<ds-tag-status size="xs" status="warning" label="Pending"></ds-tag-status>';
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 for error status', async () => {
41
+ container.innerHTML = '<ds-tag-status status="error" label="Failed"></ds-tag-status>';
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 for info status', async () => {
51
+ container.innerHTML = '<ds-tag-status status="info" label="Information"></ds-tag-status>';
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 for neutral status', async () => {
61
+ container.innerHTML = '<ds-tag-status status="neutral" label="Draft"></ds-tag-status>';
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 without icon', async () => {
71
+ container.innerHTML = '<ds-tag-status hide-icon status="success" label="Complete"></ds-tag-status>';
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
+
80
+ it('should pass axe accessibility checks with all statuses', async () => {
81
+ const statuses = ['error', 'warning', 'success', 'info', 'neutral'];
82
+
83
+ for (const status of statuses) {
84
+ container.innerHTML = `<ds-tag-status status="${status}" label="Test ${status}"></ds-tag-status>`;
85
+ await new Promise(resolve => setTimeout(resolve, 100));
86
+
87
+ const results = await axe.run(container, {
88
+ rules: { 'color-contrast': { enabled: false } }
89
+ });
90
+ expect(results.violations).toHaveLength(0);
91
+ }
92
+ });
93
+ });
@@ -0,0 +1,164 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import '../ds-icon/ds-icon.js';
3
+
4
+ // Icon mapping constant
5
+ const STATUS_ICONS = {
6
+ error: 'cancel',
7
+ warning: 'warning',
8
+ success: 'check-circle',
9
+ info: 'info',
10
+ neutral: 'pending'
11
+ };
12
+
13
+ /**
14
+ * Tag status component for displaying status indicators
15
+ *
16
+ * @element ds-tag-status
17
+ *
18
+ * @prop {string} status - Status type: 'error' | 'warning' | 'success' | 'info' | 'neutral' (default: 'neutral')
19
+ * @prop {string} size - Size variant: 's' | 'xs' (default: 's')
20
+ * @prop {string} label - Label text to display
21
+ * @prop {boolean} hideIcon - Hide icon in size S (default: false)
22
+ * @prop {string} icon - Custom icon name (overrides status-based icon)
23
+ *
24
+ * @csspart container - The main container element
25
+ * @csspart icon - The icon element
26
+ * @csspart label - The label text element
27
+ */
28
+ export class DsTagStatus extends LitElement {
29
+ static properties = {
30
+ status: { type: String, reflect: true },
31
+ size: { type: String, reflect: true },
32
+ label: { type: String },
33
+ hideIcon: { type: Boolean, attribute: 'hide-icon', reflect: true },
34
+ icon: { type: String }
35
+ };
36
+
37
+ static styles = css`
38
+ :host {
39
+ display: inline-flex;
40
+ vertical-align: middle;
41
+ box-sizing: border-box;
42
+ }
43
+
44
+ .container {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ box-sizing: border-box;
48
+ border-radius: var(--ds-radius-container); /* 0px - sharp */
49
+ }
50
+
51
+ /* Size S (default): 24px height */
52
+ :host([size="s"]) .container,
53
+ :host(:not([size])) .container {
54
+ height: 24px;
55
+ padding: 2px var(--ds-space-sm); /* 2px vertical, 8px horizontal */
56
+ gap: var(--ds-space-xs); /* 4px gap between icon and label */
57
+ }
58
+
59
+ :host([size="s"]) .label,
60
+ :host(:not([size])) .label {
61
+ font: var(--ds-typo-content-body-regular);
62
+ }
63
+
64
+ /* Size XS: 20px height (caption text box height) */
65
+ :host([size="xs"]) .container {
66
+ height: 20px;
67
+ padding: 2px var(--ds-space-sm); /* 2px vertical, 8px horizontal */
68
+ }
69
+
70
+ :host([size="xs"]) .label {
71
+ font: var(--ds-typo-content-caption-regular);
72
+ }
73
+
74
+ /* Label styling */
75
+ .label {
76
+ color: var(--ds-color-text-default);
77
+ white-space: nowrap;
78
+ line-height: 1;
79
+ }
80
+
81
+ /* Icon styling */
82
+ ds-icon {
83
+ color: var(--ds-color-icon-default);
84
+ flex-shrink: 0;
85
+ }
86
+
87
+ /* Status: Error */
88
+ :host([status="error"]) .container {
89
+ background-color: var(--ds-color-bg-error-subtle);
90
+ }
91
+
92
+ /* Status: Warning */
93
+ :host([status="warning"]) .container {
94
+ background-color: var(--ds-color-bg-warning-subtle);
95
+ }
96
+
97
+ /* Status: Success */
98
+ :host([status="success"]) .container {
99
+ background-color: var(--ds-color-bg-success-subtle);
100
+ }
101
+
102
+ /* Status: Info */
103
+ :host([status="info"]) .container {
104
+ background-color: var(--ds-color-bg-info-subtle);
105
+ }
106
+
107
+ /* Status: Neutral (default) */
108
+ :host([status="neutral"]) .container,
109
+ :host(:not([status])) .container {
110
+ background-color: var(--ds-color-bg-neutral-subtle);
111
+ }
112
+ `;
113
+
114
+ constructor() {
115
+ super();
116
+ this.status = 'neutral';
117
+ this.size = 's';
118
+ this.label = '';
119
+ this.hideIcon = false;
120
+ this.icon = '';
121
+ }
122
+
123
+ /**
124
+ * Returns the icon name based on the status
125
+ * @returns {string} Icon name
126
+ */
127
+ _getIconName() {
128
+ // If custom icon is provided, use it
129
+ if (this.icon) {
130
+ return this.icon;
131
+ }
132
+
133
+ // Otherwise use status-based icon
134
+ return STATUS_ICONS[this.status] || STATUS_ICONS.neutral;
135
+ }
136
+
137
+ /**
138
+ * Determines if the icon should be shown
139
+ * @returns {boolean} Whether to show the icon
140
+ */
141
+ _shouldShowIcon() {
142
+ // Only show icon in size S, unless hideIcon is true
143
+ return this.size !== 'xs' && !this.hideIcon;
144
+ }
145
+
146
+ render() {
147
+ const showIcon = this._shouldShowIcon();
148
+
149
+ return html`
150
+ <div class="container" part="container">
151
+ ${showIcon ? html`
152
+ <ds-icon
153
+ name="${this._getIconName()}"
154
+ size="sm"
155
+ part="icon"
156
+ ></ds-icon>
157
+ ` : ''}
158
+ <span class="label" part="label">${this.label}</span>
159
+ </div>
160
+ `;
161
+ }
162
+ }
163
+
164
+ customElements.define('ds-tag-status', DsTagStatus);