@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,185 @@
1
+ import { html } from 'lit';
2
+ import './ds-banner.js';
3
+ import '../ds-button/ds-button.js';
4
+
5
+ export default {
6
+ title: 'Components/Banner',
7
+ component: 'ds-banner',
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ status: {
11
+ control: 'select',
12
+ options: ['error', 'warning', 'success', 'info'],
13
+ description: 'Status type that determines background color and icon',
14
+ table: {
15
+ defaultValue: { summary: 'info' }
16
+ }
17
+ },
18
+ title: {
19
+ control: 'text',
20
+ description: 'Optional title text'
21
+ },
22
+ message: {
23
+ control: 'text',
24
+ description: 'Message text'
25
+ },
26
+ dismissible: {
27
+ control: 'boolean',
28
+ description: 'Show/hide close button'
29
+ },
30
+ fixed: {
31
+ control: 'boolean',
32
+ description: 'Fix banner to the top of the viewport'
33
+ },
34
+ hideIcon: {
35
+ control: 'boolean',
36
+ description: 'Hide status icon'
37
+ }
38
+ }
39
+ };
40
+
41
+ const Template = (args) => html`
42
+ <ds-banner
43
+ .status=${args.status}
44
+ .title=${args.title}
45
+ .message=${args.message}
46
+ ?dismissible=${args.dismissible}
47
+ ?fixed=${args.fixed}
48
+ ?hide-icon=${args.hideIcon}
49
+ >
50
+ ${args.slotContent ? html`${args.slotContent}` : ''}
51
+ </ds-banner>
52
+ `;
53
+
54
+ /**
55
+ * Default banner with info status
56
+ */
57
+ export const Default = {
58
+ args: {
59
+ status: 'info',
60
+ message: 'System update scheduled for tonight at 2 AM.',
61
+ dismissible: false,
62
+ fixed: false,
63
+ hideIcon: false
64
+ },
65
+ render: (args) => Template(args)
66
+ };
67
+
68
+ /**
69
+ * Banner with title property
70
+ */
71
+ export const WithTitle = {
72
+ args: {
73
+ status: 'warning',
74
+ title: 'Maintenance Alert',
75
+ message: 'The system will be down for maintenance from 2 AM to 4 AM UTC.',
76
+ dismissible: true,
77
+ fixed: false,
78
+ hideIcon: false
79
+ },
80
+ render: (args) => Template(args)
81
+ };
82
+
83
+ /**
84
+ * Success banner with dismiss action
85
+ */
86
+ export const Success = {
87
+ args: {
88
+ status: 'success',
89
+ message: 'Your profile settings have been successfully updated.',
90
+ dismissible: true,
91
+ fixed: false,
92
+ hideIcon: false
93
+ },
94
+ render: (args) => Template(args)
95
+ };
96
+
97
+ /**
98
+ * Warning banner
99
+ */
100
+ export const Warning = {
101
+ args: {
102
+ status: 'warning',
103
+ message: 'Your subscription is about to expire. Please renew to avoid interruption.',
104
+ dismissible: true,
105
+ fixed: false,
106
+ hideIcon: false
107
+ },
108
+ render: (args) => Template(args)
109
+ };
110
+
111
+ /**
112
+ * Error banner
113
+ */
114
+ export const Error = {
115
+ args: {
116
+ status: 'error',
117
+ message: 'Failed to connect to the database. Please try again later.',
118
+ dismissible: true,
119
+ fixed: false,
120
+ hideIcon: false
121
+ },
122
+ render: (args) => Template(args)
123
+ };
124
+
125
+ /**
126
+ * Banner with custom actions
127
+ */
128
+ export const WithActions = {
129
+ args: {
130
+ status: 'info',
131
+ message: 'A new version of the application is available.',
132
+ dismissible: true
133
+ },
134
+ render: (args) => html`
135
+ <ds-banner
136
+ .status=${args.status}
137
+ .title=${args.title}
138
+ .message=${args.message}
139
+ ?dismissible=${args.dismissible}
140
+ ?fixed=${args.fixed}
141
+ ?hide-icon=${args.hideIcon}
142
+ >
143
+ <div slot="actions" style="display: flex; gap: 8px;">
144
+ <ds-button variant="action">Update Now</ds-button>
145
+ <ds-button variant="action">Dismiss</ds-button>
146
+ </div>
147
+ </ds-banner>
148
+ `
149
+ };
150
+
151
+ /**
152
+ * Fixed banner at the top of the page.
153
+ * (View in Canvas mode to see it stick to the top)
154
+ */
155
+ export const FixedTop = {
156
+ args: {
157
+ status: 'warning',
158
+ message: 'This banner is fixed to the top of the viewport. Scroll down to see it stick.',
159
+ fixed: true,
160
+ dismissible: true
161
+ },
162
+ render: (args) => html`
163
+ <div style="min-height: 200px; transform: scale(1);">
164
+ ${Template(args)}
165
+ <div style="padding: 60px 20px;">
166
+ <p>Scroll content...</p>
167
+ <p>Scroll content...</p>
168
+ <p>Scroll content...</p>
169
+ </div>
170
+ </div>
171
+ `
172
+ };
173
+
174
+ /**
175
+ * Demonstrating the 800px max-width constraint on the message text.
176
+ * The banner background spans full width, but the text wraps at 800px.
177
+ */
178
+ export const LongMessage = {
179
+ args: {
180
+ status: 'info',
181
+ message: 'This is a very long message that demonstrates the 800px max-width constraint for readability. Even though the banner background extends to the full width of the viewport, this text content will wrap once it exceeds 800 pixels. This ensures that lines of text do not become too long to read comfortably on wide screens, adhering to accessibility best practices.',
182
+ dismissible: true
183
+ },
184
+ render: (args) => Template(args)
185
+ };
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import './ds-banner.js';
3
+
4
+ describe('ds-banner', () => {
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('renders with default values', async () => {
17
+ container.innerHTML = '<ds-banner message="Default message"></ds-banner>';
18
+ const element = container.querySelector('ds-banner');
19
+ await element.updateComplete;
20
+
21
+ expect(element.status).toBe('info');
22
+ expect(element.title).toBe('');
23
+ expect(element.message).toBe('Default message');
24
+ expect(element.dismissible).toBe(false);
25
+ expect(element.fixed).toBe(false);
26
+
27
+ const banner = element.shadowRoot.querySelector('.banner');
28
+ expect(banner).toBeTruthy();
29
+ });
30
+
31
+ it('renders title when provided', async () => {
32
+ container.innerHTML = '<ds-banner title="Test Title" message="Test Message"></ds-banner>';
33
+ const element = container.querySelector('ds-banner');
34
+ await element.updateComplete;
35
+
36
+ expect(element.title).toBe('Test Title');
37
+ const titleEl = element.shadowRoot.querySelector('.title');
38
+ expect(titleEl).toBeTruthy();
39
+ expect(titleEl.textContent).toBe('Test Title');
40
+ });
41
+
42
+ it('reflects fixed attribute', async () => {
43
+ container.innerHTML = '<ds-banner fixed></ds-banner>';
44
+ const element = container.querySelector('ds-banner');
45
+ await element.updateComplete;
46
+
47
+ expect(element.hasAttribute('fixed')).toBe(true);
48
+ expect(element.fixed).toBe(true);
49
+ });
50
+
51
+ it('renders correct icon for each status', async () => {
52
+ const statuses = {
53
+ error: 'cancel',
54
+ warning: 'warning',
55
+ success: 'check-circle',
56
+ info: 'info'
57
+ };
58
+
59
+ for (const [status, iconName] of Object.entries(statuses)) {
60
+ container.innerHTML = `<ds-banner status="${status}"></ds-banner>`;
61
+ const element = container.querySelector('ds-banner');
62
+ await element.updateComplete;
63
+
64
+ const icon = element.shadowRoot.querySelector('ds-icon');
65
+ expect(icon.getAttribute('name')).toBe(iconName);
66
+ }
67
+ });
68
+
69
+ it('hides icon when hideIcon is true', async () => {
70
+ container.innerHTML = '<ds-banner hide-icon></ds-banner>';
71
+ const element = container.querySelector('ds-banner');
72
+ await element.updateComplete;
73
+
74
+ const icon = element.shadowRoot.querySelector('ds-icon');
75
+ expect(icon).toBeNull();
76
+ });
77
+
78
+ it('shows close button only when dismissible', async () => {
79
+ container.innerHTML = '<ds-banner></ds-banner>';
80
+ const element = container.querySelector('ds-banner');
81
+ await element.updateComplete;
82
+
83
+ expect(element.shadowRoot.querySelector('ds-icon-button')).toBeNull();
84
+
85
+ element.dismissible = true;
86
+ await element.updateComplete;
87
+
88
+ expect(element.shadowRoot.querySelector('ds-icon-button')).toBeTruthy();
89
+ });
90
+
91
+ it('dispatches ds-dismiss event when close button is clicked', async () => {
92
+ const spy = vi.fn();
93
+ container.innerHTML = '<ds-banner dismissible></ds-banner>';
94
+ const element = container.querySelector('ds-banner');
95
+ element.addEventListener('ds-dismiss', spy);
96
+ await element.updateComplete;
97
+
98
+ const button = element.shadowRoot.querySelector('ds-icon-button');
99
+ button.click();
100
+
101
+ expect(spy).toHaveBeenCalledTimes(1);
102
+ });
103
+
104
+ it('renders actions slot when content is provided', async () => {
105
+ container.innerHTML = `
106
+ <ds-banner>
107
+ <div slot="actions">Action</div>
108
+ </ds-banner>
109
+ `;
110
+ const element = container.querySelector('ds-banner');
111
+ await element.updateComplete;
112
+
113
+ const actionsSlot = element.shadowRoot.querySelector('slot[name="actions"]');
114
+ expect(actionsSlot).toBeTruthy();
115
+ });
116
+ });
@@ -0,0 +1 @@
1
+ export { DsBanner } from './ds-banner.js';
@@ -0,0 +1,135 @@
1
+ import { LitElement, html, css, nothing } from 'lit';
2
+ import '../ds-link/ds-link.js';
3
+ import '../ds-icon/ds-icon.js';
4
+
5
+ /**
6
+ * @element ds-breadcrumb-item
7
+ * @summary A single item within a breadcrumb navigation, representing a page or a collapsed set of pages.
8
+ *
9
+ * @prop {string} href - URL for the link. If omitted, and not current/collapsed, simple text is rendered (though typically breadcrumbs are links).
10
+ * @prop {string} label - Text to display.
11
+ * @prop {boolean} current - Whether this item represents the current page. Renders as bold text, not a link.
12
+ * @prop {boolean} collapsed - Whether this item represents a collapsed section (renders ". . .").
13
+ * @prop {boolean} last - If true, hides the separator icon.
14
+ */
15
+ export class DsBreadcrumbItem extends LitElement {
16
+ static properties = {
17
+ href: { type: String },
18
+ label: { type: String },
19
+ icon: { type: String },
20
+ current: { type: Boolean, reflect: true },
21
+ collapsed: { type: Boolean, reflect: true },
22
+ };
23
+
24
+ static styles = css`
25
+ :host {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: var(--ds-space-sm, 8px);
29
+ color: var(--ds-color-text-default);
30
+ }
31
+
32
+ /* Accessibility Role */
33
+ :host {
34
+ role: 'listitem';
35
+ }
36
+
37
+ /* Current Item (Static Text) */
38
+ span.current {
39
+ font: var(--ds-typo-content-body-bold);
40
+ color: var(--ds-color-text-default);
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: var(--ds-space-xs, 4px);
44
+ }
45
+
46
+ /* Separator Icon */
47
+ ds-icon.separator {
48
+ color: var(--ds-color-icon-default);
49
+ display: var(--ds-breadcrumb-separator-display, inline-flex);
50
+ }
51
+
52
+ /* Strictly hide separator if current */
53
+ :host([current]) ds-icon.separator {
54
+ display: none !important;
55
+ }
56
+
57
+ /* Collapsed Action Button */
58
+ button.collapsed-action {
59
+ appearance: none;
60
+ background: none;
61
+ border: none;
62
+ padding: 0;
63
+ margin: 0;
64
+ font: inherit;
65
+ color: var(--ds-color-text-link, #005cc5); /* Fallback color */
66
+ cursor: pointer;
67
+ text-decoration: underline;
68
+ display: inline-flex;
69
+ align-items: center;
70
+ min-width: 1.5em; /* Ensure clickable area */
71
+ justify-content: center;
72
+ }
73
+
74
+ button.collapsed-action:hover {
75
+ color: var(--ds-color-text-link-hover);
76
+ }
77
+
78
+ button.collapsed-action:focus-visible {
79
+ outline: 2px solid var(--ds-color-border-focus);
80
+ border-radius: var(--ds-radius-container, 0px);
81
+ }
82
+ `;
83
+
84
+ constructor() {
85
+ super();
86
+ this.current = false;
87
+ this.collapsed = false;
88
+ }
89
+
90
+ _handleExpand() {
91
+ this.dispatchEvent(new CustomEvent('ds-expand', {
92
+ bubbles: true,
93
+ composed: true
94
+ }));
95
+ }
96
+
97
+ render() {
98
+ // 1. Current Page (Static Text, Bold)
99
+ if (this.current) {
100
+ return html`
101
+ <span class="current" aria-current="page">
102
+ ${this.icon ? html`<ds-icon name="${this.icon}" size="sm"></ds-icon>` : nothing}
103
+ ${this.label}
104
+ </span>
105
+ `;
106
+ }
107
+
108
+ // 2. Collapsed State (Action Button)
109
+ if (this.collapsed) {
110
+ return html`
111
+ <button
112
+ type="button"
113
+ class="collapsed-action"
114
+ aria-label="Show hidden breadcrumbs"
115
+ @click=${this._handleExpand}
116
+ >
117
+ . . .
118
+ </button>
119
+ <ds-icon class="separator" name="chevron-right" size="xs" aria-hidden="true"></ds-icon>
120
+ `;
121
+ }
122
+
123
+ // 3. Standard Link
124
+ return html`
125
+ <ds-link href=${this.href} .inline=${false} icon-start=${this.icon}>
126
+ ${this.label}
127
+ </ds-link>
128
+ <ds-icon class="separator" name="chevron-right" size="xs" aria-hidden="true"></ds-icon>
129
+ `;
130
+ }
131
+ }
132
+
133
+ if (!customElements.get('ds-breadcrumb-item')) {
134
+ customElements.define('ds-breadcrumb-item', DsBreadcrumbItem);
135
+ }
@@ -0,0 +1,49 @@
1
+ import { html } from 'lit';
2
+ import './ds-breadcrumb-item.js';
3
+
4
+ export default {
5
+ title: 'Components/Breadcrumb Item',
6
+ component: 'ds-breadcrumb-item',
7
+ argTypes: {
8
+ label: { control: 'text' },
9
+ href: { control: 'text' },
10
+ current: { control: 'boolean' },
11
+ collapsed: { control: 'boolean' },
12
+ },
13
+ };
14
+
15
+ const Template = (args) => html`
16
+ <ds-breadcrumb-item
17
+ label="${args.label || ''}"
18
+ href="${args.href || ''}"
19
+ icon="${args.icon || ''}"
20
+ ?current=${args.current}
21
+ ?collapsed=${args.collapsed}
22
+ ></ds-breadcrumb-item>
23
+ `;
24
+
25
+ export const Default = Template.bind({});
26
+ Default.args = {
27
+ label: 'Home',
28
+ href: '/',
29
+ };
30
+
31
+ export const Current = Template.bind({});
32
+ Current.args = {
33
+ label: 'Current Page',
34
+ current: true,
35
+ };
36
+
37
+ export const Collapsed = Template.bind({});
38
+ Collapsed.args = {
39
+ href: '#',
40
+ collapsed: true,
41
+ label: 'Expand',
42
+ };
43
+
44
+ export const WithIcon = Template.bind({});
45
+ WithIcon.args = {
46
+ label: 'Home',
47
+ href: '/',
48
+ icon: 'home',
49
+ };
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-breadcrumb-item.js';
3
+
4
+ describe('ds-breadcrumb-item', () => {
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('renders label and separator by default', async () => {
17
+ container.innerHTML = '<ds-breadcrumb-item label="Home" href="/home"></ds-breadcrumb-item>';
18
+ const el = container.querySelector('ds-breadcrumb-item');
19
+ await new Promise(r => setTimeout(r, 50));
20
+
21
+ const link = el.shadowRoot.querySelector('ds-link');
22
+ expect(link).toBeTruthy();
23
+ expect(link.textContent.trim()).toBe('Home');
24
+
25
+ const icon = el.shadowRoot.querySelector('ds-icon[name="chevron-right"]');
26
+ expect(icon).toBeTruthy();
27
+ expect(icon.getAttribute('aria-hidden')).toBe('true');
28
+ });
29
+
30
+ it('renders current page as text without separator', async () => {
31
+ container.innerHTML = '<ds-breadcrumb-item label="Current" current></ds-breadcrumb-item>';
32
+ const el = container.querySelector('ds-breadcrumb-item');
33
+ await new Promise(r => setTimeout(r, 50));
34
+
35
+ const span = el.shadowRoot.querySelector('span.current');
36
+ expect(span).toBeTruthy();
37
+ expect(span.getAttribute('aria-current')).toBe('page');
38
+ expect(span.textContent.trim()).toBe('Current');
39
+
40
+ const icon = el.shadowRoot.querySelector('ds-icon');
41
+ expect(icon).toBeNull();
42
+ });
43
+
44
+ it('renders collapsed state with ellipses', async () => {
45
+ container.innerHTML = '<ds-breadcrumb-item collapsed href="#"></ds-breadcrumb-item>';
46
+ const el = container.querySelector('ds-breadcrumb-item');
47
+ await new Promise(r => setTimeout(r, 50));
48
+
49
+ const button = el.shadowRoot.querySelector('button.collapsed-action');
50
+ expect(button.textContent.trim()).toBe('. . .');
51
+
52
+ const icon = el.shadowRoot.querySelector('ds-icon[name="chevron-right"]');
53
+ expect(icon).toBeTruthy();
54
+ });
55
+ });