@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,251 @@
1
+ import { Meta, Canvas, Story, Controls } from '@storybook/blocks';
2
+ import * as InputStories from './ds-input.stories';
3
+
4
+ <Meta of={InputStories} />
5
+
6
+ # Input
7
+
8
+ A comprehensive text input component for forms with label, help text, validation states, and customizable slots.
9
+
10
+ ## Overview
11
+
12
+ The `ds-input` component provides a fully-featured form input with support for:
13
+ - Labels with optional info buttons
14
+ - Help text and validation messages (error/success)
15
+ - Multiple input types (text, email, password, number)
16
+ - Prefix/suffix content via slots
17
+ - Custom actions (icons, buttons) inside the field
18
+ - Password visibility toggle
19
+ - Number steppers
20
+ - Clearable input (× button on focus)
21
+ - All interaction states (enabled, hover, focus, disabled, read-only, error)
22
+
23
+ <Canvas of={InputStories.Default} />
24
+
25
+ ## Props
26
+
27
+ <Controls />
28
+
29
+ ## States
30
+
31
+ ### Default
32
+ Basic input with label and helper text.
33
+
34
+ <Canvas of={InputStories.Default} />
35
+
36
+ ### Disabled
37
+ Input cannot be interacted with.
38
+
39
+ <Canvas of={InputStories.Disabled} />
40
+
41
+ ### Read Only
42
+ Value cannot be changed but is readable.
43
+
44
+ <Canvas of={InputStories.ReadOnly} />
45
+
46
+ ### Error State
47
+ Shows error message with icon and error styling.
48
+
49
+ <Canvas of={InputStories.Error} />
50
+
51
+ ### Success State
52
+ Shows success message with success styling.
53
+
54
+ <Canvas of={InputStories.Success} />
55
+
56
+ ## Features
57
+
58
+ ### With Info Button
59
+ Label can include an info button for additional context. The button is positioned outside the label element for accessibility.
60
+
61
+ <Canvas of={InputStories.WithInfoButton} />
62
+
63
+ ### Clearable Input
64
+ When `clearable` is enabled, a clear button (×) appears when the input is focused and has a value. Users can also press ESC to clear.
65
+
66
+ <Canvas of={InputStories.Clearable} />
67
+
68
+ ### Password with Toggle
69
+ Password inputs can show a visibility toggle button.
70
+
71
+ <Canvas of={InputStories.PasswordInput} />
72
+
73
+ ### Number with Steppers
74
+ Number inputs automatically display increment/decrement buttons.
75
+
76
+ <Canvas of={InputStories.NumberInput} />
77
+
78
+ ### With Prefix
79
+ Add text or content before the input field.
80
+
81
+ <Canvas of={InputStories.WithPrefix} />
82
+
83
+ ### With Suffix
84
+ Add text or content after the input field.
85
+
86
+ <Canvas of={InputStories.WithSuffix} />
87
+
88
+ ### With Action Slot
89
+ Add an icon or button inside the field (right side).
90
+
91
+ <Canvas of={InputStories.WithAction} />
92
+
93
+ ### Prefix and Suffix Combined
94
+ Use both prefix and suffix together.
95
+
96
+ <Canvas of={InputStories.WithPrefixAndSuffix} />
97
+
98
+ ## Input Types
99
+
100
+ ### Text
101
+ <Canvas of={InputStories.TextInput} />
102
+
103
+ ### Email
104
+ <Canvas of={InputStories.EmailInput} />
105
+
106
+ ### Password
107
+ <Canvas of={InputStories.PasswordInput} />
108
+
109
+ ### Number
110
+ <Canvas of={InputStories.NumberInput} />
111
+
112
+ ### Decimal Numbers
113
+ <Canvas of={InputStories.DecimalInput} />
114
+
115
+ ## Usage
116
+
117
+ ### Basic Usage
118
+
119
+ ```html
120
+ <ds-input
121
+ label="Email"
122
+ placeholder="your@email.com"
123
+ helper="We'll never share your email"
124
+ ></ds-input>
125
+ ```
126
+
127
+ ### With Validation
128
+
129
+ ```html
130
+ <ds-input
131
+ label="Email"
132
+ value="invalid@"
133
+ validation-status="error"
134
+ validation-message="Please enter a valid email address"
135
+ ></ds-input>
136
+
137
+ <ds-input
138
+ label="Username"
139
+ value="john_doe"
140
+ validation-status="success"
141
+ validation-message="Username is available"
142
+ ></ds-input>
143
+ ```
144
+
145
+ ### With Clearable
146
+
147
+ ```html
148
+ <ds-input
149
+ label="Search"
150
+ placeholder="Type to search..."
151
+ clearable
152
+ ></ds-input>
153
+ ```
154
+
155
+ ### With Slots
156
+
157
+ ```html
158
+ <ds-input label="Website" placeholder="example.com">
159
+ <span slot="prefix">https://</span>
160
+ </ds-input>
161
+
162
+ <ds-input label="Price" type="number" value="99.99">
163
+ <span slot="prefix">$</span>
164
+ <span slot="suffix">USD</span>
165
+ </ds-input>
166
+
167
+ <ds-input label="Search" placeholder="Type to search..." clearable>
168
+ <ds-icon-button
169
+ slot="action"
170
+ icon="search"
171
+ variant="action"
172
+ size="s"
173
+ ></ds-icon-button>
174
+ </ds-input>
175
+ ```
176
+
177
+ ### Password with Toggle
178
+
179
+ ```html
180
+ <ds-input
181
+ label="Password"
182
+ type="password"
183
+ show-password-toggle
184
+ helper="Min. 8 characters"
185
+ ></ds-input>
186
+ ```
187
+
188
+ ### Number Input
189
+
190
+ ```html
191
+ <ds-input
192
+ label="Quantity"
193
+ type="number"
194
+ value="1"
195
+ step="1"
196
+ ></ds-input>
197
+
198
+ <ds-input
199
+ label="Price"
200
+ type="number"
201
+ value="10.00"
202
+ step="0.5"
203
+ ></ds-input>
204
+ ```
205
+
206
+ ## Keyboard Shortcuts
207
+
208
+ | Key | Action |
209
+ |-----|--------|
210
+ | `Tab` | Navigate between input and action buttons |
211
+ | `ESC` | Clear input value (when `clearable` is enabled) |
212
+ | `Ctrl/Cmd + A` | Select all text (native behavior) |
213
+ | `Enter/Space` | Activate focused button (password toggle, clear, etc.) |
214
+
215
+ ## Events
216
+
217
+ | Event | Detail | Description |
218
+ |-------|--------|-------------|
219
+ | `input` | `{ value }` | Fired when input value changes |
220
+ | `change` | `{ value }` | Fired when input loses focus |
221
+ | `info-click` | `{ info }` | Fired when info button is clicked |
222
+
223
+ ## Accessibility
224
+
225
+ ### ARIA Attributes
226
+ - `aria-invalid="true"` when validation-status is "error"
227
+ - `aria-required="true"` when required
228
+ - `aria-describedby` links to helper text or validation message
229
+ - Icon buttons have descriptive `aria-label` attributes
230
+
231
+ ### Keyboard Navigation
232
+ - All interactive elements (input, info button, action buttons) are keyboard accessible
233
+ - Tab order follows visual layout: info button → input → action buttons
234
+ - Clear button only appears on focus to reduce tab stops
235
+
236
+ ### Screen Reader Support
237
+ - Validation messages are announced when they change
238
+ - Icon button labels clearly describe their purpose ("Clear input", "Show password", etc.)
239
+ - Error icon provides visual indication for sighted users while ARIA attributes convey state to screen readers
240
+
241
+ ### Focus Management
242
+ - Clear focus indicators on all interactive elements
243
+ - Selecting all text on focus helps users quickly replace content
244
+ - Clear button automatically refocuses input after clearing
245
+
246
+ ### Best Practices
247
+ - Always provide a descriptive label
248
+ - Use helper text to provide additional context
249
+ - For password fields, use `show-password-toggle` for better UX
250
+ - Use validation-status and validation-message for form feedback
251
+ - The clearable feature works best for search and filter inputs
@@ -0,0 +1,298 @@
1
+ import '../ds-icon/ds-icon.js';
2
+ import '../ds-icon-button/ds-icon-button.js';
3
+ import './ds-input.js';
4
+
5
+ export default {
6
+ title: 'Components/Input',
7
+ component: 'ds-input',
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ label: { control: 'text' },
11
+ info: { control: 'text' },
12
+ placeholder: { control: 'text' },
13
+ value: { control: 'text' },
14
+ type: {
15
+ control: 'select',
16
+ options: ['text', 'email', 'password', 'number']
17
+ },
18
+ helper: { control: 'text' },
19
+ validationStatus: {
20
+ control: 'select',
21
+ options: [undefined, 'error', 'success']
22
+ },
23
+ validationMessage: { control: 'text' },
24
+ disabled: { control: 'boolean' },
25
+ readonly: { control: 'boolean' },
26
+ required: { control: 'boolean' },
27
+ showPasswordToggle: { control: 'boolean' },
28
+ clearable: { control: 'boolean' },
29
+ step: { control: 'number' },
30
+ min: { control: 'number' },
31
+ max: { control: 'number' },
32
+ clamp: { control: 'boolean' },
33
+ width: { control: 'text' },
34
+ textAlign: {
35
+ control: 'select',
36
+ options: ['left', 'center', 'right']
37
+ },
38
+ autocomplete: { control: 'text' },
39
+ labelPosition: {
40
+ control: 'select',
41
+ options: ['top', 'inline-start']
42
+ },
43
+ labelWidth: { control: 'text' }
44
+ }
45
+ };
46
+
47
+ // Helper to convert camelCase to kebab-case
48
+ const toKebabCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
49
+
50
+ const Template = (args) => {
51
+ const input = document.createElement('ds-input');
52
+
53
+ Object.keys(args).forEach(key => {
54
+ if (args[key] !== undefined && args[key] !== null && args[key] !== '') {
55
+ const attrName = toKebabCase(key);
56
+ if (attrName === 'min' || attrName === 'max' || attrName === 'step') {
57
+ // Numeric props need to be passed as properties/attributes carefully
58
+ input.setAttribute(attrName, args[key]);
59
+ } else if (typeof args[key] === 'boolean') {
60
+ if (args[key]) input.setAttribute(attrName, '');
61
+ } else {
62
+ input.setAttribute(attrName, args[key]);
63
+ }
64
+ }
65
+ });
66
+
67
+ return input;
68
+ };
69
+
70
+ const TemplateWithSlots = (args, slots = {}) => {
71
+ const wrapper = document.createElement('div');
72
+
73
+ let slotsHTML = '';
74
+ if (slots.prefix) slotsHTML += `<span slot="prefix">${slots.prefix}</span>`;
75
+ if (slots.suffix) slotsHTML += `<span slot="suffix">${slots.suffix}</span>`;
76
+ if (slots.action) slotsHTML += slots.action;
77
+
78
+ const attrs = Object.keys(args)
79
+ .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '')
80
+ .map(key => {
81
+ const attrName = toKebabCase(key);
82
+ if (typeof args[key] === 'boolean') {
83
+ return args[key] ? attrName : '';
84
+ }
85
+ return `${attrName}="${args[key]}"`;
86
+ })
87
+ .join(' ');
88
+
89
+ wrapper.innerHTML = `<ds-input ${attrs}>${slotsHTML}</ds-input>`;
90
+ return wrapper.firstElementChild;
91
+ };
92
+
93
+ // ============================================================================
94
+ // BASIC EXAMPLES
95
+ // ============================================================================
96
+
97
+ export const Default = Template.bind({});
98
+ Default.args = {
99
+ label: 'Email',
100
+ placeholder: 'Enter your email',
101
+ helper: 'We\'ll never share your email',
102
+ autocomplete: 'email'
103
+ };
104
+
105
+ export const InlineLabel = Template.bind({});
106
+ InlineLabel.args = {
107
+ label: 'Username',
108
+ labelPosition: 'inline-start',
109
+ labelWidth: '120px',
110
+ placeholder: 'Enter username',
111
+ helper: 'Label is aligned to the left'
112
+ };
113
+
114
+ export const WithInfoButton = Template.bind({});
115
+ WithInfoButton.args = {
116
+ label: 'API Key',
117
+ info: 'You can find your API key in account settings',
118
+ placeholder: 'Enter API key'
119
+ };
120
+
121
+
122
+ export const LongLabelWithInfo = Template.bind({});
123
+ LongLabelWithInfo.args = {
124
+ label: 'This is a very long label that will eventually wrap to the next line in a narrow container',
125
+ info: 'Information about this very long label',
126
+ placeholder: 'Enter text here'
127
+ };
128
+ LongLabelWithInfo.decorators = [
129
+ (Story) => {
130
+ const wrapper = document.createElement('div');
131
+ wrapper.style.maxWidth = '200px';
132
+ wrapper.appendChild(Story());
133
+ return wrapper;
134
+ }
135
+ ];
136
+
137
+ // ============================================================================
138
+ // STATES
139
+ // ============================================================================
140
+
141
+ export const Disabled = Template.bind({});
142
+ Disabled.args = {
143
+ label: 'Email',
144
+ value: 'user@example.com',
145
+ disabled: true
146
+ };
147
+
148
+ export const ReadOnly = Template.bind({});
149
+ ReadOnly.args = {
150
+ label: 'Username',
151
+ value: 'john_doe',
152
+ readonly: true,
153
+ helper: 'Username cannot be changed'
154
+ };
155
+
156
+ export const Error = Template.bind({});
157
+ Error.args = {
158
+ label: 'Email',
159
+ value: 'invalid@',
160
+ validationStatus: 'error',
161
+ validationMessage: 'Please enter a valid email address'
162
+ };
163
+
164
+ export const Success = Template.bind({});
165
+ Success.args = {
166
+ label: 'Email',
167
+ value: 'user@example.com',
168
+ validationStatus: 'success',
169
+ validationMessage: 'Email is available'
170
+ };
171
+
172
+ // ============================================================================
173
+ // INPUT TYPES
174
+ // ============================================================================
175
+
176
+ export const TextInput = Template.bind({});
177
+ TextInput.args = {
178
+ label: 'Full Name',
179
+ type: 'text',
180
+ placeholder: 'John Doe',
181
+ required: true,
182
+ autocomplete: 'name'
183
+ };
184
+
185
+ export const EmailInput = Template.bind({});
186
+ EmailInput.args = {
187
+ label: 'Email Address',
188
+ type: 'email',
189
+ placeholder: 'you@example.com',
190
+ helper: 'Enter a valid email',
191
+ autocomplete: 'email'
192
+ };
193
+
194
+ export const PasswordInput = Template.bind({});
195
+ PasswordInput.args = {
196
+ label: 'Password',
197
+ type: 'password',
198
+ placeholder: 'Enter password',
199
+ showPasswordToggle: true,
200
+ helper: 'Min. 8 characters',
201
+ autocomplete: 'current-password'
202
+ };
203
+
204
+ export const NumberInput = Template.bind({});
205
+ NumberInput.args = {
206
+ label: 'Age',
207
+ type: 'number',
208
+ value: '18',
209
+ min: 0,
210
+ max: 120,
211
+ step: 1,
212
+ clamp: true,
213
+ helper: 'Enter value between 0-120 (auto-clamps on blur)'
214
+ };
215
+
216
+ export const DecimalInput = Template.bind({});
217
+ DecimalInput.args = {
218
+ label: 'Price',
219
+ type: 'number',
220
+ value: '10.00',
221
+ step: 0.5,
222
+ min: 0,
223
+ helper: 'Increments by 0.50'
224
+ };
225
+
226
+ export const Clearable = Template.bind({});
227
+ Clearable.args = {
228
+ label: 'Search',
229
+ placeholder: 'Type to search...',
230
+ clearable: true,
231
+ value: 'example query',
232
+ helper: 'Click × to clear or press ESC'
233
+ };
234
+
235
+ // ============================================================================
236
+ // WITH SLOTS
237
+ // ============================================================================
238
+
239
+ export const WithPrefix = (args) => {
240
+ return TemplateWithSlots(
241
+ {
242
+ ...args,
243
+ label: args.label || 'Website',
244
+ placeholder: args.placeholder || 'example.com'
245
+ },
246
+ { prefix: 'https://' }
247
+ );
248
+ };
249
+
250
+ export const WithSuffix = (args) => {
251
+ return TemplateWithSlots(
252
+ {
253
+ ...args,
254
+ label: args.label || 'Domain',
255
+ placeholder: args.placeholder || 'mysite'
256
+ },
257
+ { suffix: '.com' }
258
+ );
259
+ };
260
+
261
+ export const WithPrefixAndSuffix = (args) => {
262
+ return TemplateWithSlots(
263
+ {
264
+ ...args,
265
+ label: args.label || 'Price',
266
+ type: args.type || 'number',
267
+ value: args.value || '99.99',
268
+ step: args.step || 0.01
269
+ },
270
+ { prefix: '$', suffix: 'USD' }
271
+ );
272
+ };
273
+
274
+ export const WithAction = (args) => {
275
+ const actionHTML = '<ds-icon-button slot="action" icon="search" variant="action" size="s" aria-label="Search"></ds-icon-button>';
276
+ return TemplateWithSlots(
277
+ {
278
+ ...args,
279
+ label: args.label || 'Search',
280
+ placeholder: args.placeholder || 'Search...',
281
+ clearable: args.clearable !== undefined ? args.clearable : true,
282
+ value: args.value || 'test query'
283
+ },
284
+ { action: actionHTML }
285
+ );
286
+ };
287
+
288
+ export const CustomWidth = Template.bind({});
289
+ CustomWidth.args = {
290
+ label: 'Compact Input',
291
+ width: '120px',
292
+ textAlign: 'center',
293
+ placeholder: '000',
294
+ type: 'number',
295
+ helper: 'Centered text, fixed width'
296
+ };
297
+
298
+