@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,117 @@
1
+ import { computePosition, autoUpdate, flip, shift, offset, arrow, autoPlacement } from '@floating-ui/dom';
2
+
3
+ /**
4
+ * A Reactive Controller to handle floating-ui positioning
5
+ */
6
+ export class PositionerController {
7
+ constructor(host) {
8
+ this.host = host;
9
+ this.host.addController(this);
10
+ this._cleanup = null;
11
+ this._reference = null;
12
+ this._floating = null;
13
+ this._placement = 'top';
14
+ this._strategy = 'absolute';
15
+ }
16
+
17
+ _update() {
18
+ if (!this.host.isConnected || !this._reference || !this._floating) {
19
+ return;
20
+ }
21
+
22
+ this._stop();
23
+
24
+ this._cleanup = autoUpdate(
25
+ this._reference,
26
+ this._floating,
27
+ () => {
28
+ // Calculate the offset dynamically from token
29
+ const offsetAmount = this._offsetValue;
30
+
31
+ // Handle 'auto' placement
32
+ const isAuto = this._placement.startsWith('auto');
33
+
34
+ // Use placement as-is for standard values
35
+ const targetPlacement = this._placement;
36
+
37
+ const middleware = [
38
+ offset(offsetAmount),
39
+ shift({ padding: 8 })
40
+ ];
41
+
42
+ if (isAuto) {
43
+ middleware.push(autoPlacement());
44
+ } else {
45
+ middleware.push(flip());
46
+ }
47
+
48
+ computePosition(this._reference, this._floating, {
49
+ strategy: this._strategy,
50
+ placement: isAuto ? undefined : targetPlacement,
51
+ middleware,
52
+ }).then(({ x, y }) => {
53
+ Object.assign(this._floating.style, {
54
+ left: `${x}px`,
55
+ top: `${y}px`,
56
+ position: this._strategy // Ensure CSS position matches strategy
57
+ });
58
+ });
59
+ },
60
+ {
61
+ // Critical options for dynamic repositioning
62
+ ancestorScroll: true, // Reposition when ancestor scrolls
63
+ ancestorResize: true, // Reposition when ancestor resizes
64
+ elementResize: true, // Reposition when elements resize
65
+ layoutShift: true // Reposition on layout shifts
66
+ }
67
+ );
68
+ }
69
+
70
+ set target(referenceElement) {
71
+ this._reference = referenceElement;
72
+ this._update();
73
+ }
74
+
75
+ set floating(floatingElement) {
76
+ this._floating = floatingElement;
77
+ this._update();
78
+ }
79
+
80
+ set placement(value) {
81
+ this._placement = value;
82
+ this._update();
83
+ }
84
+
85
+ set strategy(value) {
86
+ this._strategy = value;
87
+ this._update();
88
+ }
89
+
90
+ hostConnected() {
91
+ this._update();
92
+ }
93
+
94
+ hostDisconnected() {
95
+ this._stop();
96
+ }
97
+
98
+ _stop() {
99
+ if (this._cleanup) {
100
+ this._cleanup();
101
+ this._cleanup = null;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Reads the --ds-space-sm token from the computed styles of the host
107
+ * to determine the offset value in pixels.
108
+ */
109
+ get _offsetValue() {
110
+ if (!this.host) return 8; // Fallback
111
+ const styles = getComputedStyle(this.host);
112
+ const tokenValue = styles.getPropertyValue('--ds-space-sm').trim();
113
+ // Parse '8px' -> 8
114
+ const pixels = parseFloat(tokenValue);
115
+ return isNaN(pixels) ? 8 : pixels;
116
+ }
117
+ }
package/src/index.js ADDED
@@ -0,0 +1,50 @@
1
+ // Export all components
2
+ export * from './ds-accordion/index.js';
3
+ export { DsTagAction } from './ds-tag-action/ds-tag-action.js';
4
+ export { DsTagRemovable } from './ds-tag-removable/ds-tag-removable.js';
5
+ export { DsTabItem } from './ds-tab-item/ds-tab-item.js';
6
+ export { DsTabs } from './ds-tabs/ds-tabs.js';
7
+ export { DsTabPanel } from './ds-tabs/ds-tab-panel.js';
8
+ export { DsIcon } from './ds-icon/index.js';
9
+ export { TokenProvider } from './token-provider/index.js';
10
+ export { DsButton } from './ds-button/index.js';
11
+ export { DsButtonGroup } from './ds-button-group/index.js';
12
+ export { DsActionBar } from './ds-action-bar/index.js';
13
+ export { DsIconButton } from './ds-icon-button/index.js';
14
+ export { DsInput } from './ds-input/index.js';
15
+ export { DsTextarea } from './ds-textarea/index.js';
16
+ export { DsDropdown } from './ds-dropdown/index.js';
17
+ export { DsCheckbox } from './ds-checkbox/index.js';
18
+ export { DsCheckboxGroup } from './ds-checkbox-group/index.js';
19
+ export { DsRadio } from './ds-radio/index.js';
20
+ export { DsRadioGroup } from './ds-radio-group/index.js';
21
+ export { DsTooltip } from './ds-tooltip/index.js';
22
+ export { DsTagStatus } from './ds-tag-status/index.js';
23
+ export { DsProgressBar } from './ds-progress-bar/ds-progress-bar.js';
24
+ export { DsSpinner } from './ds-spinner/ds-spinner.js';
25
+ export { DsLink } from './ds-link/ds-link.js';
26
+ export { DsNavItem } from './ds-nav-item/index.js';
27
+ export { DsNavVertical } from './ds-nav-vertical/index.js';
28
+ export { DsHeader } from './ds-header/index.js';
29
+ export { DsHeaderNav } from './ds-header-nav/ds-header-nav.js';
30
+ export { DsHeaderNavItem } from './ds-header-nav-item/ds-header-nav-item.js';
31
+ export { DsAvatar } from './ds-avatar/index.js';
32
+ export { DsAvatarExtended } from './ds-avatar-extended/index.js';
33
+ export { DsListItem } from './ds-list-item/ds-list-item.js';
34
+ export { DsMenu } from './ds-menu/ds-menu.js';
35
+ export { DsMenuGroup } from './ds-menu-group/ds-menu-group.js';
36
+ export { DsAlert } from './ds-alert/index.js';
37
+ export { DsBanner } from './ds-banner/index.js';
38
+ export { DsToast, DsToastProvider } from './ds-toast/index.js';
39
+ export { DsDialog } from './ds-dialog/index.js';
40
+ export { DsPagination } from './ds-pagination/index.js';
41
+ export { DsSwitch } from './ds-switch/index.js';
42
+ export { DsSlider } from './ds-slider/ds-slider.js';
43
+ export { DsStepper } from './ds-stepper/ds-stepper.js';
44
+ export { DsStepperItem } from './ds-stepper-item/ds-stepper-item.js';
45
+ export { DsRichList } from './ds-rich-list/ds-rich-list.js';
46
+ export { DsRichListItem } from './ds-rich-list-item/ds-rich-list-item.js';
47
+ export { DsThumbnail } from './ds-thumbnail/ds-thumbnail.js';
48
+ export { DsStatusBorder } from './ds-status-border/ds-status-border.js';
49
+ export { DsFileUploaded } from './ds-file-uploaded/ds-file-uploaded.js';
50
+ export { DsFileUploader } from './ds-file-uploader/ds-file-uploader.js';
@@ -0,0 +1,113 @@
1
+ import { html, css } from 'lit';
2
+ import '../ds-tooltip/ds-tooltip.js';
3
+ import '../ds-icon-button/ds-icon-button.js';
4
+
5
+ export const fieldLabelStyles = css`
6
+ .label-row {
7
+ display: flex;
8
+ align-items: flex-start; /* Align to top for text wrap edge case */
9
+ gap: var(--ds-space-xs);
10
+ }
11
+
12
+ .label-row label {
13
+ font: var(--ds-typo-content-body-bold);
14
+ color: var(--ds-color-text-default);
15
+ line-height: 24px; /* Match info button height for alignment */
16
+ cursor: pointer;
17
+ }
18
+
19
+ .label-row ds-icon-button {
20
+ flex-shrink: 0; /* Prevent button from shrinking */
21
+ }
22
+
23
+ /* Inline layout styles (used when labelPosition="inline-start") */
24
+ .field-wrapper.inline-label {
25
+ display: flex;
26
+ flex-direction: row;
27
+ align-items: flex-start;
28
+ gap: var(--ds-space-sm, 8px);
29
+ }
30
+
31
+ .field-wrapper.inline-label .label-row {
32
+ flex-shrink: 0;
33
+ padding-top: 4px; /* Align label text (24px) with 32px input: (32 - 24) / 2 = 4px */
34
+ }
35
+
36
+ .field-wrapper.inline-label .field-content {
37
+ flex: 1;
38
+ min-width: 0; /* Allow shrinking */
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: var(--ds-space-xs);
42
+ }
43
+ `;
44
+
45
+
46
+ /**
47
+ * Mixin for rendering field labels with optional info button
48
+ *
49
+ * @param {LitElement} superClass - The class to extend
50
+ * @returns {LitElement} Extended class with label rendering capabilities
51
+ */
52
+ export const FieldLabelMixin = (superClass) => class extends superClass {
53
+
54
+ /**
55
+ * Renders a field label with optional info button
56
+ * Info button is now OUTSIDE the label element for accessibility
57
+ *
58
+ * @param {string} label - Label text
59
+ * @param {string} info - Info tooltip text (shows info button if provided)
60
+ * @param {string} inputId - ID of the input to associate with label
61
+ * @returns {TemplateResult|string} Lit template or empty string
62
+ */
63
+ renderFieldLabel(label, info = '', inputId = '', labelId = '') {
64
+ if (!label) return '';
65
+
66
+ return html`
67
+ <div class="label-row" part="label-row">
68
+ <label id="${labelId}" for="${inputId}">${label}</label>
69
+ ${info ? html`
70
+ <ds-tooltip content="${info}" placement="top">
71
+ <ds-icon-button
72
+ icon="info"
73
+ variant="action"
74
+ size="s"
75
+ aria-label="${info}"
76
+ @click=${this._handleInfoClick}
77
+ ></ds-icon-button>
78
+ </ds-tooltip>
79
+ ` : ''}
80
+ </div>
81
+ `;
82
+ }
83
+
84
+ /**
85
+ * Handles info button click
86
+ * @private
87
+ */
88
+ _handleInfoClick(e) {
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ e.stopImmediatePropagation(); // Prevent any other handlers
92
+
93
+ // Prevent focus from moving to the field
94
+ const activeElement = document.activeElement;
95
+ if (activeElement && activeElement !== e.target) {
96
+ e.target.blur();
97
+ }
98
+
99
+ this.dispatchEvent(new CustomEvent('info-click', {
100
+ bubbles: true,
101
+ composed: true,
102
+ detail: { info: this.info }
103
+ }));
104
+ }
105
+
106
+ /**
107
+ * Returns CSS for field label styling
108
+ * @returns {CSSResult} CSS result
109
+ */
110
+ static get fieldLabelStyles() {
111
+ return fieldLabelStyles;
112
+ }
113
+ };
@@ -0,0 +1,66 @@
1
+ import { html, css } from 'lit';
2
+ import '../ds-icon/ds-icon.js';
3
+
4
+ export const fieldMessageStyles = css`
5
+ .field-message {
6
+ display: flex;
7
+ align-items: center;
8
+ gap: var(--ds-space-xs);
9
+ font: var(--ds-typo-content-body-regular);
10
+ color: var(--ds-color-text-secondary);
11
+ }
12
+
13
+ .field-message--error {
14
+ color: var(--ds-color-text-error);
15
+ }
16
+
17
+ .field-message--error .field-message__icon {
18
+ color: var(--ds-color-icon-error);
19
+ }
20
+
21
+ .field-message__icon {
22
+ flex-shrink: 0;
23
+ }
24
+
25
+ .field-message__text {
26
+ color: inherit;
27
+ }
28
+ `;
29
+
30
+ /**
31
+ * Mixin for rendering field messages (help text or error text)
32
+ *
33
+ * @param {LitElement} superClass - The class to extend
34
+ * @returns {LitElement} Extended class with message rendering capabilities
35
+ */
36
+ export const FieldMessageMixin = (superClass) => class extends superClass {
37
+
38
+ /**
39
+ * Renders a field message (help text or error text with icon)
40
+ *
41
+ * @param {string} helpText - Help text to display
42
+ * @param {string} errorText - Error text to display (takes precedence over helpText)
43
+ * @returns {TemplateResult|string} Lit template or empty string
44
+ */
45
+ renderFieldMessage(helpText = '', errorText = '') {
46
+ const hasError = !!errorText;
47
+ const message = hasError ? errorText : helpText;
48
+
49
+ if (!message) return '';
50
+
51
+ return html`
52
+ <div class="field-message ${hasError ? 'field-message--error' : ''}" part="message">
53
+ ${hasError ? html`<ds-icon name="warning" size="sm" class="field-message__icon"></ds-icon>` : ''}
54
+ <span class="field-message__text">${message}</span>
55
+ </div>
56
+ `;
57
+ }
58
+
59
+ /**
60
+ * Returns CSS for field message styling
61
+ * @returns {CSSResult} CSS result
62
+ */
63
+ static get fieldMessageStyles() {
64
+ return fieldMessageStyles;
65
+ }
66
+ };
@@ -0,0 +1 @@
1
+ export { TokenProvider } from './token-provider.js';
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import axe from 'axe-core';
3
+ import './token-provider.js';
4
+
5
+ describe('token-provider accessibility', () => {
6
+ let container;
7
+
8
+ beforeEach(() => {
9
+ container = document.createElement('div');
10
+ document.body.appendChild(container);
11
+ document.documentElement.removeAttribute('data-theme');
12
+ });
13
+
14
+ afterEach(() => {
15
+ container.remove();
16
+ document.documentElement.removeAttribute('data-theme');
17
+ });
18
+
19
+ it('should pass axe accessibility checks', async () => {
20
+ container.innerHTML = `
21
+ <token-provider theme="light">
22
+ <div>Content inside provider</div>
23
+ </token-provider>
24
+ `;
25
+
26
+ await new Promise(resolve => setTimeout(resolve, 100));
27
+
28
+ const results = await axe.run(container);
29
+ expect(results.violations).toHaveLength(0);
30
+ });
31
+
32
+ it('should not interfere with document accessibility', async () => {
33
+ container.innerHTML = '<token-provider theme="light"></token-provider>';
34
+
35
+ await new Promise(resolve => setTimeout(resolve, 100));
36
+
37
+ // token-provider should be invisible to screen readers
38
+ const provider = container.querySelector('token-provider');
39
+ expect(provider).toBeTruthy();
40
+
41
+ // Verify it doesn't add any problematic ARIA attributes
42
+ expect(provider.getAttribute('role')).toBeFalsy();
43
+ });
44
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Token Provider Web Component
3
+ * Manages theme context and injects design tokens
4
+ *
5
+ * Usage:
6
+ * <token-provider theme="light">
7
+ * <your-app></your-app>
8
+ * </token-provider>
9
+ */
10
+
11
+ export class TokenProvider extends HTMLElement {
12
+ static get observedAttributes() {
13
+ return ['theme'];
14
+ }
15
+
16
+ constructor() {
17
+ super();
18
+ this._theme = 'light';
19
+ }
20
+
21
+ connectedCallback() {
22
+ // Apply theme to document root for global token access
23
+ this._applyTheme();
24
+
25
+ // Dispatch ready event
26
+ this.dispatchEvent(new CustomEvent('tokens-ready', {
27
+ bubbles: true,
28
+ composed: true,
29
+ detail: { theme: this._theme }
30
+ }));
31
+ }
32
+
33
+ attributeChangedCallback(name, oldValue, newValue) {
34
+ if (name === 'theme' && oldValue !== newValue) {
35
+ this._theme = newValue || 'light';
36
+ this._applyTheme();
37
+
38
+ this.dispatchEvent(new CustomEvent('theme-change', {
39
+ bubbles: true,
40
+ composed: true,
41
+ detail: { theme: this._theme, previousTheme: oldValue }
42
+ }));
43
+ }
44
+ }
45
+
46
+ get theme() {
47
+ return this._theme;
48
+ }
49
+
50
+ set theme(value) {
51
+ this.setAttribute('theme', value);
52
+ }
53
+
54
+ _applyTheme() {
55
+ document.documentElement.setAttribute('data-theme', this._theme);
56
+ }
57
+
58
+ /**
59
+ * Toggle between light and dark themes
60
+ * @returns {string} The new theme
61
+ */
62
+ toggleTheme() {
63
+ const newTheme = this._theme === 'light' ? 'dark' : 'light';
64
+ this.theme = newTheme;
65
+ return newTheme;
66
+ }
67
+
68
+ /**
69
+ * Get a token value from CSS custom properties
70
+ * @param {string} tokenName - Token name without -- prefix
71
+ * @returns {string} The computed token value
72
+ */
73
+ getToken(tokenName) {
74
+ return getComputedStyle(document.documentElement)
75
+ .getPropertyValue(`--${tokenName}`)
76
+ .trim();
77
+ }
78
+ }
79
+
80
+ // Register the custom element
81
+ if (!customElements.get('token-provider')) {
82
+ customElements.define('token-provider', TokenProvider);
83
+ }
84
+
85
+ export default TokenProvider;
@@ -0,0 +1,105 @@
1
+ import './token-provider.js';
2
+
3
+ import { html } from 'lit';
4
+
5
+ export default {
6
+ title: 'Components/Token Provider',
7
+ component: 'token-provider',
8
+ argTypes: {
9
+ theme: {
10
+ control: 'radio',
11
+ options: ['light'],
12
+ description: 'Theme mode (dark coming soon)',
13
+ },
14
+ },
15
+ };
16
+
17
+ export const Default = {
18
+ args: {
19
+ theme: 'light',
20
+ },
21
+ render: ({ theme }) => {
22
+ const provider = document.createElement('token-provider');
23
+ provider.setAttribute('theme', theme);
24
+
25
+ const content = document.createElement('div');
26
+ content.style.cssText = `
27
+ padding: 2rem;
28
+ background: var(--ds-color-bg-secondary);
29
+ border: 1px solid var(--ds-color-border-default);
30
+ border-radius: 8px;
31
+ `;
32
+ content.innerHTML = `
33
+ <h2 style="font: var(--ds-typo-heading-lg); color: var(--ds-color-text-default); margin-bottom: 1rem;">
34
+ Sample Content
35
+ </h2>
36
+ <p style="font: var(--ds-typo-content-body-regular); color: var(--ds-color-text-default); margin-bottom: 1rem;">
37
+ This content is wrapped in a token-provider which manages theme context and injects design tokens.
38
+ </p>
39
+ <button style="
40
+ background: var(--ds-color-bg-brand);
41
+ color: var(--ds-color-text-inverse);
42
+ padding: var(--ds-space-sm) var(--ds-space-md);
43
+ border: none;
44
+ border-radius: var(--ds-radius-action);
45
+ font: var(--ds-typo-content-body-bold);
46
+ cursor: pointer;
47
+ ">
48
+ Brand Button
49
+ </button>
50
+ `;
51
+
52
+ provider.appendChild(content);
53
+ return provider;
54
+ },
55
+ };
56
+
57
+ export const Usage = {
58
+ render: () => {
59
+ const container = document.createElement('div');
60
+ container.innerHTML = `
61
+ <div style="font: var(--ds-typo-content-body-regular); color: var(--ds-color-text-default); max-width: 600px;">
62
+ <h2 style="font: var(--ds-typo-heading-lg); margin-bottom: 1rem;">
63
+ How to use Token Provider
64
+ </h2>
65
+
66
+ <h3 style="font: var(--ds-typo-heading-sm); margin-top: 2rem; margin-bottom: 0.5rem;">
67
+ HTML
68
+ </h3>
69
+ <pre style="
70
+ background: var(--ds-color-bg-secondary);
71
+ padding: var(--ds-space-md);
72
+ border-radius: 4px;
73
+ overflow-x: auto;
74
+ font-family: monospace;
75
+ font-size: 12px;
76
+ "><code>&lt;token-provider theme="light"&gt;
77
+ &lt;your-app&gt;&lt;/your-app&gt;
78
+ &lt;/token-provider&gt;</code></pre>
79
+
80
+ <h3 style="font: var(--ds-typo-heading-sm); margin-top: 2rem; margin-bottom: 0.5rem;">
81
+ JavaScript
82
+ </h3>
83
+ <pre style="
84
+ background: var(--ds-color-bg-secondary);
85
+ padding: var(--ds-space-md);
86
+ border-radius: 4px;
87
+ overflow-x: auto;
88
+ font-family: monospace;
89
+ font-size: 12px;
90
+ "><code>// Get token value
91
+ const provider = document.querySelector('token-provider');
92
+ const tokenValue = provider.getToken('ds-space-md');
93
+
94
+ // Toggle theme
95
+ provider.toggleTheme();
96
+
97
+ // Listen to theme changes
98
+ provider.addEventListener('theme-change', (e) => {
99
+ console.log('New theme:', e.detail.theme);
100
+ });</code></pre>
101
+ </div>
102
+ `;
103
+ return container;
104
+ },
105
+ };