@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,288 @@
1
+ import { html } from 'lit';
2
+ import './ds-stepper-item.js';
3
+ import '../ds-stepper/ds-stepper.js';
4
+ import '../ds-icon/ds-icon.js';
5
+
6
+ export default {
7
+ title: 'Components/Stepper Item',
8
+ component: 'ds-stepper-item',
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component: 'Individual step within a `ds-stepper`. Supports `completed`, `current`, `disabled`, and `error` states. Normally used inside `ds-stepper`, but can be configured standalone for demonstration.'
13
+ }
14
+ }
15
+ },
16
+ argTypes: {
17
+ completed: { control: 'boolean', description: 'Whether this step is completed' },
18
+ current: { control: 'boolean', description: 'Whether this is the current active step' },
19
+ disabled: { control: 'boolean', description: 'Whether this step is disabled' },
20
+ error: { control: 'boolean', description: 'Whether this step has an error state' }
21
+ }
22
+ };
23
+
24
+ // Helper to manually set internal properties that are normally set by ds-stepper
25
+ const configureItem = (el, config = {}) => {
26
+ if (!el) return;
27
+ requestAnimationFrame(() => {
28
+ el._direction = config.direction || 'horizontal';
29
+ el._position = config.position || 'middle';
30
+ el._step = config.step || 1;
31
+ el._totalSteps = config.totalSteps || 4;
32
+ el._linear = config.linear || false;
33
+ el._prevCompleted = config.prevCompleted || false;
34
+ el._prevError = config.prevError || false;
35
+ });
36
+ };
37
+
38
+ // ===== ALL STATES =====
39
+ export const AllStates = {
40
+ render: () => html`
41
+ <style>
42
+ .states-grid {
43
+ display: grid;
44
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
45
+ gap: 32px;
46
+ padding: 24px;
47
+ }
48
+ .state-card {
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: 8px;
52
+ align-items: center;
53
+ }
54
+ .state-label {
55
+ font: var(--ds-typo-content-caption-bold);
56
+ color: var(--ds-color-text-secondary);
57
+ text-transform: uppercase;
58
+ letter-spacing: 0.05em;
59
+ }
60
+ </style>
61
+ <div class="states-grid">
62
+ <div class="state-card">
63
+ <span class="state-label">Default (Pending)</span>
64
+ <ds-stepper-item
65
+ id="state-default"
66
+ @click=${(e) => configureItem(e.target, { step: 3, position: 'middle' })}
67
+ >Pending Step</ds-stepper-item>
68
+ </div>
69
+
70
+ <div class="state-card">
71
+ <span class="state-label">Current</span>
72
+ <ds-stepper-item
73
+ current
74
+ id="state-current"
75
+ >Current Step</ds-stepper-item>
76
+ </div>
77
+
78
+ <div class="state-card">
79
+ <span class="state-label">Completed</span>
80
+ <ds-stepper-item
81
+ completed
82
+ id="state-completed"
83
+ >Completed Step</ds-stepper-item>
84
+ </div>
85
+
86
+ <div class="state-card">
87
+ <span class="state-label">Error</span>
88
+ <ds-stepper-item
89
+ error
90
+ id="state-error"
91
+ >Error Step</ds-stepper-item>
92
+ </div>
93
+
94
+ <div class="state-card">
95
+ <span class="state-label">Disabled</span>
96
+ <ds-stepper-item
97
+ disabled
98
+ id="state-disabled"
99
+ >Disabled Step</ds-stepper-item>
100
+ </div>
101
+
102
+ <div class="state-card">
103
+ <span class="state-label">Completed + Error</span>
104
+ <ds-stepper-item
105
+ completed
106
+ error
107
+ id="state-completed-error"
108
+ >Completed Error</ds-stepper-item>
109
+ </div>
110
+ </div>
111
+
112
+ <script type="module">
113
+ requestAnimationFrame(() => {
114
+ const items = {
115
+ 'state-default': { step: 3, position: 'middle' },
116
+ 'state-current': { step: 2, position: 'middle' },
117
+ 'state-completed': { step: 1, position: 'first' },
118
+ 'state-error': { step: 3, position: 'middle' },
119
+ 'state-disabled': { step: 4, position: 'last' },
120
+ 'state-completed-error': { step: 2, position: 'middle' }
121
+ };
122
+ Object.entries(items).forEach(([id, config]) => {
123
+ const el = document.getElementById(id);
124
+ if (el) {
125
+ el._direction = 'horizontal';
126
+ el._position = config.position;
127
+ el._step = config.step;
128
+ el._totalSteps = 4;
129
+ el._linear = false;
130
+ el._prevCompleted = false;
131
+ el._prevError = false;
132
+ }
133
+ });
134
+ });
135
+ </script>
136
+ `,
137
+ parameters: {
138
+ docs: {
139
+ description: {
140
+ story: 'Visual catalogue of all individual states for `ds-stepper-item`: Default (pending), Current, Completed, Error, Disabled, and combined Completed+Error.'
141
+ }
142
+ }
143
+ }
144
+ };
145
+
146
+ // ===== WITHIN STEPPER CONTEXT =====
147
+ export const InStepperContext = {
148
+ render: () => html`
149
+ <style>
150
+ .context-section {
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: 32px;
154
+ padding: 24px;
155
+ }
156
+ .context-label {
157
+ font: var(--ds-typo-content-body-bold);
158
+ color: var(--ds-color-text-default);
159
+ margin-bottom: 8px;
160
+ }
161
+ .context-desc {
162
+ font: var(--ds-typo-content-caption-regular);
163
+ color: var(--ds-color-text-secondary);
164
+ margin-bottom: 16px;
165
+ }
166
+ </style>
167
+ <div class="context-section">
168
+ <div>
169
+ <p class="context-label">Horizontal — Step 2 active, step 1 completed</p>
170
+ <p class="context-desc">Shows how items relate to each other with connector lines.</p>
171
+ <ds-stepper active-step="1">
172
+ <ds-stepper-item>Account Setup</ds-stepper-item>
173
+ <ds-stepper-item>Personal Info</ds-stepper-item>
174
+ <ds-stepper-item>Preferences</ds-stepper-item>
175
+ <ds-stepper-item>Confirmation</ds-stepper-item>
176
+ </ds-stepper>
177
+ </div>
178
+
179
+ <div>
180
+ <p class="context-label">With Error on Step 2</p>
181
+ <p class="context-desc">Step 2 reports an error. Notice the red connector and indicator.</p>
182
+ <ds-stepper active-step="2">
183
+ <ds-stepper-item>Account Setup</ds-stepper-item>
184
+ <ds-stepper-item error>Personal Info</ds-stepper-item>
185
+ <ds-stepper-item>Preferences</ds-stepper-item>
186
+ <ds-stepper-item>Confirmation</ds-stepper-item>
187
+ </ds-stepper>
188
+ </div>
189
+
190
+ <div>
191
+ <p class="context-label">With Disabled Step</p>
192
+ <p class="context-desc">Step 3 is disabled — no pointer events, dimmed styling.</p>
193
+ <ds-stepper active-step="1">
194
+ <ds-stepper-item>Account Setup</ds-stepper-item>
195
+ <ds-stepper-item>Personal Info</ds-stepper-item>
196
+ <ds-stepper-item disabled>Restricted</ds-stepper-item>
197
+ <ds-stepper-item>Confirmation</ds-stepper-item>
198
+ </ds-stepper>
199
+ </div>
200
+
201
+ <div>
202
+ <p class="context-label">Linear Mode — Only completed/current steps are clickable</p>
203
+ <p class="context-desc">Steps 3 and 4 are locked (not clickable) until previous steps are completed.</p>
204
+ <ds-stepper linear active-step="1">
205
+ <ds-stepper-item>Account Setup</ds-stepper-item>
206
+ <ds-stepper-item>Personal Info</ds-stepper-item>
207
+ <ds-stepper-item>Preferences</ds-stepper-item>
208
+ <ds-stepper-item>Confirmation</ds-stepper-item>
209
+ </ds-stepper>
210
+ </div>
211
+ </div>
212
+ `,
213
+ parameters: {
214
+ docs: {
215
+ description: {
216
+ story: 'Shows `ds-stepper-item` within its parent `ds-stepper` context, demonstrating how items interact with connector lines, error states, disabled items, and linear mode.'
217
+ }
218
+ }
219
+ }
220
+ };
221
+
222
+ // ===== VERTICAL ITEM STATES =====
223
+ export const VerticalStates = {
224
+ render: () => html`
225
+ <style>
226
+ .vertical-section {
227
+ display: flex;
228
+ gap: 48px;
229
+ padding: 24px;
230
+ }
231
+ .vertical-card {
232
+ display: flex;
233
+ flex-direction: column;
234
+ gap: 8px;
235
+ }
236
+ .v-label {
237
+ font: var(--ds-typo-content-caption-bold);
238
+ color: var(--ds-color-text-secondary);
239
+ text-transform: uppercase;
240
+ }
241
+ </style>
242
+ <div class="vertical-section">
243
+ <div class="vertical-card">
244
+ <span class="v-label">Normal Flow</span>
245
+ <ds-stepper direction="vertical" active-step="1">
246
+ <ds-stepper-item>Account</ds-stepper-item>
247
+ <ds-stepper-item>Details</ds-stepper-item>
248
+ <ds-stepper-item>Review</ds-stepper-item>
249
+ </ds-stepper>
250
+ </div>
251
+
252
+ <div class="vertical-card">
253
+ <span class="v-label">With Error</span>
254
+ <ds-stepper direction="vertical" active-step="2">
255
+ <ds-stepper-item>Account</ds-stepper-item>
256
+ <ds-stepper-item error>Details</ds-stepper-item>
257
+ <ds-stepper-item>Review</ds-stepper-item>
258
+ </ds-stepper>
259
+ </div>
260
+
261
+ <div class="vertical-card">
262
+ <span class="v-label">With Content Slot</span>
263
+ <ds-stepper direction="vertical" active-step="1">
264
+ <ds-stepper-item>
265
+ Account Setup
266
+ <div slot="content" style="font: var(--ds-typo-content-caption-regular); color: var(--ds-color-text-secondary); padding: 8px 0;">
267
+ Fill in your account credentials to get started.
268
+ </div>
269
+ </ds-stepper-item>
270
+ <ds-stepper-item>
271
+ Personal Details
272
+ <div slot="content" style="font: var(--ds-typo-content-caption-regular); color: var(--ds-color-text-secondary); padding: 8px 0;">
273
+ Provide your personal information.
274
+ </div>
275
+ </ds-stepper-item>
276
+ <ds-stepper-item>Review</ds-stepper-item>
277
+ </ds-stepper>
278
+ </div>
279
+ </div>
280
+ `,
281
+ parameters: {
282
+ docs: {
283
+ description: {
284
+ story: 'Vertical layout variations showing individual item states, error handling, and the content slot for expandable step descriptions.'
285
+ }
286
+ }
287
+ }
288
+ };
@@ -0,0 +1,348 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { FieldMessageMixin, fieldMessageStyles } from '../mixins/field-message.mixin.js';
3
+ import { FieldLabelMixin, fieldLabelStyles } from '../mixins/field-label.mixin.js';
4
+ import '../ds-icon-button/ds-icon-button.js';
5
+ import '../ds-tooltip/ds-tooltip.js';
6
+
7
+ /**
8
+ * Toggle Switch component for boolean settings.
9
+ *
10
+ * @element ds-switch
11
+ *
12
+ * @prop {boolean} checked - Whether the switch is checked
13
+ * @prop {string} label - Block-level field label (above switch)
14
+ * @prop {string} info - Info tooltip text for info button
15
+ * @prop {string} labelPosition - Label position: 'top' | 'inline-start' (default: 'top')
16
+ * @prop {string} labelWidth - Fixed label width for inline layout (e.g., '100px')
17
+ * @prop {string} statusLabel - Inline state description next to track
18
+ * @prop {string} helpText - Help text below switch
19
+ * @prop {string} errorText - Error text (shown only when validationStatus="error")
20
+ * @prop {string} name - Form name
21
+ * @prop {string} value - Form value (default: 'on')
22
+ * @prop {boolean} disabled - Disabled state
23
+ * @prop {boolean} required - Required field
24
+ * @prop {boolean} standalone - Removes padding for tight layouts
25
+ *
26
+ * @fires change - Fired when state changes
27
+ * @fires info-click - Fired when info button is clicked
28
+ */
29
+ export class DsSwitch extends FieldMessageMixin(FieldLabelMixin(LitElement)) {
30
+ static properties = {
31
+ checked: { type: Boolean, reflect: true },
32
+ label: { type: String },
33
+ info: { type: String },
34
+ labelPosition: { type: String, attribute: 'label-position' },
35
+ labelWidth: { type: String, attribute: 'label-width' },
36
+ statusLabel: { type: String, attribute: 'status-label' },
37
+ helpText: { type: String, attribute: 'help-text' },
38
+ errorText: { type: String, attribute: 'error-text' },
39
+ name: { type: String },
40
+ value: { type: String },
41
+ disabled: { type: Boolean, reflect: true },
42
+ required: { type: Boolean, reflect: true },
43
+ validationStatus: { type: String, attribute: 'validation-status', reflect: true },
44
+ standalone: { type: Boolean, reflect: true }
45
+ };
46
+
47
+ static styles = [
48
+ fieldLabelStyles,
49
+ fieldMessageStyles,
50
+ css`
51
+ :host {
52
+ display: inline-block;
53
+ outline: none;
54
+ font-family: var(--ds-font-family-content);
55
+ }
56
+
57
+ .switch-row {
58
+ display: flex;
59
+ align-items: center;
60
+ cursor: pointer;
61
+ padding: var(--ds-size-6) var(--ds-space-sm);
62
+ min-height: var(--ds-size-32);
63
+ box-sizing: border-box;
64
+ transition: background-color 0.2s;
65
+ position: relative;
66
+ }
67
+
68
+ :host([standalone]) .switch-row {
69
+ padding: 0;
70
+ min-height: 0;
71
+ }
72
+
73
+ /* Hover effect on the whole row */
74
+ :host(:not([disabled])) .switch-row:hover {
75
+ background-color: var(--ds-color-bg-hover);
76
+ }
77
+
78
+ /* Focus state - border without offset */
79
+ .switch-row:has(input:focus-visible) {
80
+ outline: 2px solid var(--ds-color-border-focus);
81
+ outline-offset: 0;
82
+ }
83
+
84
+ /* Control Container - 20px height to match label line-height */
85
+ .control-container {
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ height: var(--ds-size-20);
90
+ flex-shrink: 0;
91
+ }
92
+
93
+ /* Hidden Native Input */
94
+ input {
95
+ position: absolute;
96
+ opacity: 0;
97
+ width: 100%;
98
+ height: 100%;
99
+ top: 0;
100
+ left: 0;
101
+ margin: 0;
102
+ cursor: inherit;
103
+ }
104
+
105
+ /* The Track - 32px x 16px */
106
+ .track {
107
+ width: var(--ds-size-32);
108
+ height: var(--ds-size-16);
109
+ border-radius: var(--ds-size-8);
110
+ position: relative;
111
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
112
+ box-sizing: border-box;
113
+ }
114
+
115
+ /* Unchecked Track: 2px border icon-default */
116
+ :host(:not([checked])) .track {
117
+ border: 2px solid var(--ds-color-icon-default);
118
+ background-color: transparent;
119
+ }
120
+
121
+ /* Checked Track: Solid brand */
122
+ :host([checked]) .track {
123
+ background-color: var(--ds-color-bg-brand);
124
+ border: 2px solid var(--ds-color-bg-brand);
125
+ }
126
+
127
+ /* The Thumb */
128
+ .thumb {
129
+ border-radius: 50%;
130
+ position: absolute;
131
+ top: 50%;
132
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
133
+ box-shadow: none; /* Flat design */
134
+ }
135
+
136
+ /* Unchecked Thumb: 10px x 10px, color icon-default */
137
+ :host(:not([checked])) .thumb {
138
+ width: var(--ds-size-10);
139
+ height: var(--ds-size-10);
140
+ background-color: var(--ds-color-icon-default);
141
+ left: 1px;
142
+ transform: translateY(-50%);
143
+ }
144
+
145
+ /* Checked Thumb: 12px x 12px, color bg-default */
146
+ :host([checked]) .thumb {
147
+ width: var(--ds-size-12);
148
+ height: var(--ds-size-12);
149
+ background-color: var(--ds-color-bg-default);
150
+ left: 16px;
151
+ transform: translate(0, -50%);
152
+ }
153
+
154
+ /* Status Label (inline next to track) */
155
+ .status-label {
156
+ margin-inline-start: var(--ds-space-sm);
157
+ font: var(--ds-typo-content-body-regular);
158
+ color: var(--ds-color-text-default);
159
+ user-select: none;
160
+ }
161
+
162
+ /* ---------------------------------------------------------
163
+ Error State (validation-status="error")
164
+ --------------------------------------------------------- */
165
+
166
+ /* Unchecked Error */
167
+ :host([validation-status="error"]:not([checked])) .track {
168
+ border-color: var(--ds-color-icon-error);
169
+ }
170
+
171
+ :host([validation-status="error"]:not([checked])) .thumb {
172
+ background-color: var(--ds-color-icon-error);
173
+ }
174
+
175
+ /* Checked Error */
176
+ :host([validation-status="error"][checked]) .track {
177
+ background-color: var(--ds-color-bg-error);
178
+ border-color: var(--ds-color-bg-error);
179
+ }
180
+
181
+ /* ---------------------------------------------------------
182
+ Disabled State
183
+ --------------------------------------------------------- */
184
+ :host([disabled]) .switch-row {
185
+ cursor: not-allowed;
186
+ }
187
+
188
+ /* Unchecked Disabled: No background on track, icon-disabled border/thumb */
189
+ :host([disabled]:not([checked])) .track {
190
+ background-color: transparent;
191
+ border-color: var(--ds-color-icon-disabled);
192
+ }
193
+
194
+ :host([disabled]:not([checked])) .thumb {
195
+ background-color: var(--ds-color-icon-disabled);
196
+ }
197
+
198
+ /* Checked Disabled: icon-disabled track fill, white thumb */
199
+ :host([disabled][checked]) .track {
200
+ background-color: var(--ds-color-icon-disabled);
201
+ border-color: var(--ds-color-icon-disabled);
202
+ }
203
+
204
+ :host([disabled][checked]) .thumb {
205
+ background-color: var(--ds-color-bg-default);
206
+ }
207
+
208
+ :host([disabled]) .status-label {
209
+ color: var(--ds-color-text-disabled);
210
+ }
211
+ `];
212
+
213
+ constructor() {
214
+ super();
215
+ this.checked = false;
216
+ this.label = '';
217
+ this.info = '';
218
+ this.labelPosition = 'top';
219
+ this.labelWidth = '';
220
+ this.statusLabel = '';
221
+ this.helpText = '';
222
+ this.errorText = '';
223
+ this.name = '';
224
+ this.value = 'on';
225
+ this.disabled = false;
226
+ this.required = false;
227
+ this.standalone = false;
228
+ this.validationStatus = undefined;
229
+ }
230
+
231
+ _handleClick(e) {
232
+ if (this.disabled) return;
233
+
234
+ // We prevent default because we handle the state via this.checked
235
+ // The native checkbox will still update but we control it
236
+ e.preventDefault();
237
+ this.checked = !this.checked;
238
+ this._dispatchChange();
239
+ }
240
+
241
+ _dispatchChange() {
242
+ this.dispatchEvent(new CustomEvent('change', {
243
+ detail: {
244
+ checked: this.checked,
245
+ value: this.value
246
+ },
247
+ bubbles: true,
248
+ composed: true
249
+ }));
250
+ }
251
+
252
+ _getAriaLabel() {
253
+ return this.getAttribute('aria-label') || this.statusLabel || this.label || '';
254
+ }
255
+
256
+ /**
257
+ * Toggles the switch state programmatically
258
+ * @public
259
+ */
260
+ toggle() {
261
+ if (this.disabled) return;
262
+ this.checked = !this.checked;
263
+ this._dispatchChange();
264
+ }
265
+
266
+ /**
267
+ * Focuses the switch input
268
+ * @public
269
+ */
270
+ focus() {
271
+ const input = this.shadowRoot?.querySelector('input');
272
+ input?.focus();
273
+ }
274
+
275
+ render() {
276
+ const isInline = this.labelPosition === 'inline-start';
277
+ const wrapperClass = isInline ? 'field-wrapper inline-label' : '';
278
+ const labelStyle = isInline && this.labelWidth ? `width: ${this.labelWidth}` : '';
279
+
280
+ const switchContent = html`
281
+ <div class="switch-row" @click="${this._handleClick}">
282
+ <input
283
+ type="checkbox"
284
+ role="switch"
285
+ .checked="${this.checked}"
286
+ ?disabled="${this.disabled}"
287
+ ?required="${this.required}"
288
+ name="${this.name}"
289
+ value="${this.value}"
290
+ aria-label="${this._getAriaLabel()}"
291
+ aria-checked="${this.checked}"
292
+ >
293
+
294
+ <div class="control-container">
295
+ <div class="track">
296
+ <div class="thumb"></div>
297
+ </div>
298
+ </div>
299
+
300
+ ${this.statusLabel ? html`<span class="status-label">${this.statusLabel}</span>` : ''}
301
+ </div>
302
+ ${this.validationStatus === 'error'
303
+ ? this.renderFieldMessage('', this.errorText)
304
+ : this.renderFieldMessage(this.helpText, '')}
305
+ `;
306
+
307
+ if (isInline) {
308
+ return html`
309
+ <div class="${wrapperClass}">
310
+ <div class="label-row" part="label-row" style="${labelStyle}">
311
+ <label>${this.label}</label>
312
+ ${this.info ? html`
313
+ <ds-tooltip content="${this.info}" placement="top">
314
+ <ds-icon-button
315
+ icon="info"
316
+ variant="action"
317
+ size="s"
318
+ aria-label="${this.info}"
319
+ @click=${this._handleInfoClick}
320
+ ></ds-icon-button>
321
+ </ds-tooltip>
322
+ ` : ''}
323
+ </div>
324
+ <div class="field-content">
325
+ ${switchContent}
326
+ </div>
327
+ </div>
328
+ `;
329
+ }
330
+
331
+ return html`
332
+ ${this.renderFieldLabel(this.label, this.info)}
333
+ ${switchContent}
334
+ `;
335
+ }
336
+
337
+ _handleInfoClick(e) {
338
+ e.preventDefault();
339
+ e.stopPropagation();
340
+ this.dispatchEvent(new CustomEvent('info-click', {
341
+ bubbles: true,
342
+ composed: true,
343
+ detail: { info: this.info }
344
+ }));
345
+ }
346
+ }
347
+
348
+ customElements.define('ds-switch', DsSwitch);