@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,399 @@
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/ds-icon.js';
5
+ import '../ds-icon-button/ds-icon-button.js';
6
+ import '../ds-tooltip/ds-tooltip.js';
7
+
8
+ /**
9
+ * Slider component for selecting numeric values within a range.
10
+ *
11
+ * @element ds-slider
12
+ *
13
+ * @prop {number} value - Current value
14
+ * @prop {number} min - Minimum value
15
+ * @prop {number} max - Maximum value
16
+ * @prop {number} step - Step increment
17
+ * @prop {boolean} disabled - Disabled state
18
+ * @prop {string} label - Field label
19
+ * @prop {string} info - Tooltip text for info button
20
+ * @prop {string} labelPosition - Label position: 'top' | 'inline-start' (default: 'top')
21
+ * @prop {string} labelWidth - Fixed label width for inline layout (e.g., '100px')
22
+ * @prop {string} helpText - Help text below slider
23
+ * @prop {string} errorText - Error text (when validationStatus="error")
24
+ * @prop {string} validationStatus - Validation state
25
+ *
26
+ * @fires input - Fired during drag
27
+ * @fires change - Fired on drag end
28
+ * @fires info-click - Fired when info button is clicked
29
+ */
30
+ export class DsSlider extends FieldMessageMixin(FieldLabelMixin(LitElement)) {
31
+ static properties = {
32
+ value: { type: Number, reflect: true },
33
+ min: { type: Number },
34
+ max: { type: Number },
35
+ step: { type: Number },
36
+ disabled: { type: Boolean, reflect: true },
37
+ label: { type: String },
38
+ info: { type: String },
39
+ labelPosition: { type: String, attribute: 'label-position' },
40
+ labelWidth: { type: String, attribute: 'label-width' },
41
+ helpText: { type: String, attribute: 'help-text' },
42
+ errorText: { type: String, attribute: 'error-text' },
43
+ validationStatus: { type: String, attribute: 'validation-status', reflect: true },
44
+ _isDragging: { type: Boolean, state: true },
45
+ _showTooltip: { type: Boolean, state: true }
46
+ };
47
+
48
+ static styles = [
49
+ fieldLabelStyles,
50
+ fieldMessageStyles,
51
+ css`
52
+ :host {
53
+ display: block;
54
+ font-family: var(--ds-font-family-content);
55
+ outline: none;
56
+ }
57
+
58
+ .slider-container {
59
+ position: relative;
60
+ height: var(--ds-size-32, 32px);
61
+ display: flex;
62
+ align-items: center;
63
+ cursor: pointer;
64
+ }
65
+
66
+ :host([disabled]) .slider-container {
67
+ cursor: not-allowed;
68
+ }
69
+
70
+ /* Track - centered with 2px gap from thumb edges */
71
+ .track {
72
+ position: absolute;
73
+ width: 100%;
74
+ height: 4px;
75
+ border-radius: var(--ds-radius-container, 0px);
76
+ overflow: hidden;
77
+ display: flex;
78
+ top: 50%;
79
+ transform: translateY(-50%);
80
+ }
81
+
82
+ .track-filled {
83
+ height: 100%;
84
+ background-color: var(--ds-color-border-brand);
85
+ transition: width 0.05s ease-out;
86
+ }
87
+
88
+ .track-unfilled {
89
+ height: 100%;
90
+ background-color: var(--ds-color-border-default);
91
+ flex: 1;
92
+ }
93
+
94
+ :host([disabled]) .track-filled {
95
+ background-color: var(--ds-color-border-default);
96
+ }
97
+
98
+ /* Thumb - with 2px visual gap from track via border */
99
+ .thumb {
100
+ /* Position relative since it's inside the absolute wrapper */
101
+ position: relative;
102
+ width: var(--ds-size-24);
103
+ height: var(--ds-size-24);
104
+ border-radius: 999px;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ cursor: grab;
109
+ /* 2px background gap around icon to separate from track */
110
+ background-color: var(--ds-color-bg-default);
111
+ }
112
+
113
+ .thumb::before {
114
+ content: '';
115
+ position: absolute;
116
+ width: calc(100% - 4px); /* 20px to leave 2px gap */
117
+ height: calc(100% - 4px);
118
+ border-radius: 999px;
119
+ background-color: var(--ds-color-bg-default);
120
+ z-index: -1;
121
+ }
122
+
123
+ .thumb:active {
124
+ cursor: grabbing;
125
+ }
126
+
127
+ .thumb ds-icon {
128
+ color: var(--ds-color-icon-brand);
129
+ display: flex;
130
+ }
131
+
132
+ :host([disabled]) .thumb {
133
+ cursor: not-allowed;
134
+ }
135
+
136
+ :host([disabled]) .thumb ds-icon {
137
+ color: var(--ds-color-icon-disabled);
138
+ }
139
+
140
+ /* Focus state on thumb - triggered by input focus */
141
+ .thumb:focus-visible,
142
+ input[type="range"]:focus-visible ~ .slider-visuals .thumb {
143
+ outline: 2px solid var(--ds-color-border-focus);
144
+ outline-offset: 0;
145
+ }
146
+
147
+ /* Hidden range input for accessibility */
148
+ input[type="range"] {
149
+ position: absolute;
150
+ width: 100%;
151
+ height: 100%;
152
+ opacity: 0;
153
+ cursor: pointer;
154
+ margin: 0;
155
+ z-index: 2;
156
+ }
157
+ /* Visuals wrapper - contains track and thumb layer */
158
+ .slider-visuals {
159
+ position: absolute;
160
+ top: 50%;
161
+ left: 0;
162
+ right: 0;
163
+ transform: translateY(-50%);
164
+ height: var(--ds-size-24, 24px);
165
+ pointer-events: none;
166
+ }
167
+
168
+ /* Thumb track - inset to keep thumb within container bounds */
169
+ .thumb-track {
170
+ position: absolute;
171
+ top: 0;
172
+ left: var(--ds-size-12, 12px);
173
+ right: var(--ds-size-12, 12px);
174
+ height: 100%;
175
+ }
176
+
177
+ /* Tooltip positioning wrapper */
178
+ .thumb-positioner {
179
+ position: absolute;
180
+ top: 50%;
181
+ transform: translate(-50%, -50%);
182
+ transition: left 0.05s ease-out;
183
+ z-index: 1;
184
+ }
185
+
186
+ /* ds-tooltip styling override for slider */
187
+ ds-tooltip::part(tooltip) {
188
+ padding: var(--ds-space-xs) var(--ds-space-sm);
189
+ }
190
+ `];
191
+
192
+ constructor() {
193
+ super();
194
+ this.value = 0;
195
+ this.min = 0;
196
+ this.max = 100;
197
+ this.step = 1;
198
+ this.disabled = false;
199
+ this.label = '';
200
+ this.info = '';
201
+ this.labelPosition = 'top';
202
+ this.labelWidth = '';
203
+ this.helpText = '';
204
+ this.errorText = '';
205
+ this._isDragging = false;
206
+ this._showTooltip = false;
207
+ }
208
+
209
+ get _percentage() {
210
+ const range = this.max - this.min;
211
+ if (range <= 0) return 0;
212
+ return ((this.value - this.min) / range) * 100;
213
+ }
214
+
215
+ _handleInput(e) {
216
+ if (this.disabled) return;
217
+
218
+ const newValue = parseFloat(e.target.value);
219
+ this.value = newValue;
220
+ this._showTooltip = true;
221
+
222
+ // Update tooltip content and ensure it stays visible
223
+ this._updateTooltipContent();
224
+ this._showValueTooltip();
225
+
226
+ this.dispatchEvent(new CustomEvent('input', {
227
+ detail: { value: this.value },
228
+ bubbles: true,
229
+ composed: true
230
+ }));
231
+ }
232
+
233
+ _handleChange(e) {
234
+ if (this.disabled) return;
235
+
236
+ // Only hide tooltip if not focused (keyboard interaction keeps it visible)
237
+ const input = this.shadowRoot?.querySelector('input[type="range"]');
238
+ const isFocused = this.shadowRoot?.activeElement === input;
239
+
240
+ if (!isFocused && !this._isDragging) {
241
+ this._showTooltip = false;
242
+ this._hideValueTooltip();
243
+ }
244
+
245
+ this.dispatchEvent(new CustomEvent('change', {
246
+ detail: { value: this.value },
247
+ bubbles: true,
248
+ composed: true
249
+ }));
250
+ }
251
+
252
+ _handleMouseDown() {
253
+ if (this.disabled) return;
254
+ this._isDragging = true;
255
+ this._showTooltip = true;
256
+ this._showValueTooltip();
257
+ }
258
+
259
+ _handleMouseUp() {
260
+ this._isDragging = false;
261
+ this._showTooltip = false;
262
+ this._hideValueTooltip();
263
+ }
264
+
265
+ _handleFocus() {
266
+ this._showTooltip = true;
267
+ this._showValueTooltip();
268
+ }
269
+
270
+ _handleBlur() {
271
+ if (!this._isDragging) {
272
+ this._showTooltip = false;
273
+ this._hideValueTooltip();
274
+ }
275
+ }
276
+
277
+ _showValueTooltip() {
278
+ const tooltip = this.shadowRoot?.querySelector('ds-tooltip.value-tooltip');
279
+ if (tooltip) {
280
+ tooltip._open = true;
281
+ }
282
+ }
283
+
284
+ _hideValueTooltip() {
285
+ const tooltip = this.shadowRoot?.querySelector('ds-tooltip.value-tooltip');
286
+ if (tooltip) {
287
+ tooltip._open = false;
288
+ }
289
+ }
290
+
291
+ _updateTooltipContent() {
292
+ const tooltip = this.shadowRoot?.querySelector('ds-tooltip.value-tooltip');
293
+ if (tooltip) {
294
+ tooltip.content = String(this.value);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Focuses the slider input
300
+ * @public
301
+ */
302
+ focus() {
303
+ const input = this.shadowRoot?.querySelector('input[type="range"]');
304
+ input?.focus();
305
+ }
306
+
307
+ render() {
308
+ const percentage = this._percentage;
309
+ const isInline = this.labelPosition === 'inline-start';
310
+ const wrapperClass = isInline ? 'field-wrapper inline-label' : 'field-wrapper';
311
+ const labelStyle = isInline && this.labelWidth ? `width: ${this.labelWidth}` : '';
312
+
313
+ const sliderContent = html`
314
+ <div class="slider-container">
315
+ <input
316
+ type="range"
317
+ .value="${this.value}"
318
+ min="${this.min}"
319
+ max="${this.max}"
320
+ step="${this.step}"
321
+ ?disabled="${this.disabled}"
322
+ aria-label="${this.label || 'Slider'}"
323
+ @input="${this._handleInput}"
324
+ @change="${this._handleChange}"
325
+ @mousedown="${this._handleMouseDown}"
326
+ @mouseup="${this._handleMouseUp}"
327
+ @touchstart="${this._handleMouseDown}"
328
+ @touchend="${this._handleMouseUp}"
329
+ @focus="${this._handleFocus}"
330
+ @blur="${this._handleBlur}"
331
+ >
332
+
333
+ <div class="slider-visuals">
334
+ <div class="track">
335
+ <div class="track-filled" style="width: ${percentage}%;"></div>
336
+ <div class="track-unfilled"></div>
337
+ </div>
338
+
339
+ <div class="thumb-track">
340
+ <div class="thumb-positioner" style="left: ${percentage}%;">
341
+ <ds-tooltip class="value-tooltip" content="${this.value}" placement="top" manual>
342
+ <div class="thumb" tabindex="-1">
343
+ <ds-icon name="circle-filled" size="sm"></ds-icon>
344
+ </div>
345
+ </ds-tooltip>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ </div>
350
+
351
+ ${this.validationStatus === 'error'
352
+ ? this.renderFieldMessage('', this.errorText)
353
+ : this.renderFieldMessage(this.helpText, '')}
354
+ `;
355
+
356
+ if (isInline) {
357
+ return html`
358
+ <div class="${wrapperClass}">
359
+ <div class="label-row" part="label-row" style="${labelStyle}">
360
+ <label>${this.label}</label>
361
+ ${this.info ? html`
362
+ <ds-tooltip content="${this.info}" placement="top">
363
+ <ds-icon-button
364
+ icon="info"
365
+ variant="action"
366
+ size="s"
367
+ aria-label="${this.info}"
368
+ @click=${this._handleInfoClick}
369
+ ></ds-icon-button>
370
+ </ds-tooltip>
371
+ ` : ''}
372
+ </div>
373
+ <div class="field-content">
374
+ ${sliderContent}
375
+ </div>
376
+ </div>
377
+ `;
378
+ }
379
+
380
+ return html`
381
+ ${this.renderFieldLabel(this.label, this.info)}
382
+ ${sliderContent}
383
+ `;
384
+ }
385
+
386
+ _handleInfoClick(e) {
387
+ e.preventDefault();
388
+ e.stopPropagation();
389
+ this.dispatchEvent(new CustomEvent('info-click', {
390
+ bubbles: true,
391
+ composed: true,
392
+ detail: { info: this.info }
393
+ }));
394
+ }
395
+ }
396
+
397
+ if (!customElements.get('ds-slider')) {
398
+ customElements.define('ds-slider', DsSlider);
399
+ }
@@ -0,0 +1,107 @@
1
+ import './ds-slider.js';
2
+
3
+ export default {
4
+ title: 'Components/Slider',
5
+ component: 'ds-slider',
6
+ tags: ['autodocs'],
7
+ argTypes: {
8
+ value: { control: { type: 'number', min: 0, max: 100 } },
9
+ min: { control: 'number' },
10
+ max: { control: 'number' },
11
+ step: { control: 'number' },
12
+ disabled: { control: 'boolean' },
13
+ label: { control: 'text' },
14
+ info: { control: 'text' },
15
+ labelPosition: {
16
+ control: 'select',
17
+ options: ['top', 'inline-start']
18
+ },
19
+ labelWidth: { control: 'text' },
20
+ helpText: { control: 'text' },
21
+ errorText: { control: 'text' },
22
+ validationStatus: {
23
+ control: 'select',
24
+ options: [undefined, 'error']
25
+ }
26
+ }
27
+ };
28
+
29
+ const Template = (args) => {
30
+ const el = document.createElement('ds-slider');
31
+ Object.keys(args).forEach(key => {
32
+ const attrName = key.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
33
+ if (args[key] !== undefined && args[key] !== null) {
34
+ if (typeof args[key] === 'boolean') {
35
+ if (args[key]) el.setAttribute(attrName, '');
36
+ else el.removeAttribute(attrName);
37
+ } else {
38
+ el.setAttribute(attrName, args[key]);
39
+ }
40
+ }
41
+ });
42
+ return el;
43
+ };
44
+
45
+ export const Default = Template.bind({});
46
+ Default.args = {
47
+ value: 50,
48
+ min: 0,
49
+ max: 100
50
+ };
51
+
52
+ export const WithLabel = Template.bind({});
53
+ WithLabel.args = {
54
+ value: 75,
55
+ label: 'Volume',
56
+ helpText: 'Ajusta o volume de som'
57
+ };
58
+
59
+ export const WithStep = Template.bind({});
60
+ WithStep.args = {
61
+ value: 50,
62
+ min: 0,
63
+ max: 100,
64
+ step: 10,
65
+ label: 'Brightness',
66
+ helpText: 'Valores em incrementos de 10'
67
+ };
68
+
69
+ export const CustomRange = Template.bind({});
70
+ CustomRange.args = {
71
+ value: 25,
72
+ min: 0,
73
+ max: 50,
74
+ label: 'Temperatura',
75
+ helpText: 'De 0 a 50 graus'
76
+ };
77
+
78
+ export const Disabled = Template.bind({});
79
+ Disabled.args = {
80
+ value: 30,
81
+ disabled: true,
82
+ label: 'Slider desativado'
83
+ };
84
+
85
+ export const WithError = Template.bind({});
86
+ WithError.args = {
87
+ value: 10,
88
+ label: 'Prioridade',
89
+ errorText: 'O valor mínimo é 20',
90
+ validationStatus: 'error'
91
+ };
92
+
93
+ export const WithInfo = Template.bind({});
94
+ WithInfo.args = {
95
+ value: 50,
96
+ label: 'Qualidade',
97
+ info: 'Quanto maior, melhor a qualidade mas maior o tamanho do ficheiro'
98
+ };
99
+
100
+ export const InlineLabel = Template.bind({});
101
+ InlineLabel.args = {
102
+ value: 50,
103
+ label: 'Volume',
104
+ labelPosition: 'inline-start',
105
+ labelWidth: '100px',
106
+ helpText: 'Label alinhada à esquerda'
107
+ };