@kenlo/sds-design-system 0.9.9

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 (246) hide show
  1. package/README.md +129 -0
  2. package/components/Accordion/Accordion.css +186 -0
  3. package/components/Accordion/accordion-variants.js +17 -0
  4. package/components/Accordion/accordion.marko +264 -0
  5. package/components/AccordionGroup/AccordionGroup.css +35 -0
  6. package/components/AccordionGroup/accordion-group.marko +17 -0
  7. package/components/Alert/Alert.css +104 -0
  8. package/components/Alert/alert-variants.js +35 -0
  9. package/components/Alert/alert.marko +94 -0
  10. package/components/AttachmentList/AttachmentList.css +92 -0
  11. package/components/AttachmentList/attachment-list.marko +51 -0
  12. package/components/AttachmentList/attachment-row.marko +167 -0
  13. package/components/Avatar/Avatar.css +110 -0
  14. package/components/Avatar/avatar-variants.js +12 -0
  15. package/components/Avatar/avatar.marko +71 -0
  16. package/components/Badge/Badge.css +381 -0
  17. package/components/Badge/badge-dismissible.marko +143 -0
  18. package/components/Badge/badge-variants.js +35 -0
  19. package/components/Badge/badge.marko +134 -0
  20. package/components/BottomSheet/BottomSheet.css +300 -0
  21. package/components/BottomSheet/bottom-sheet.marko +340 -0
  22. package/components/BulkActionBar/BulkActionBar.css +88 -0
  23. package/components/BulkActionBar/bulk-action-bar.marko +186 -0
  24. package/components/Button/Button.css +340 -0
  25. package/components/Button/button-variants.js +25 -0
  26. package/components/Button/button.marko +139 -0
  27. package/components/Card/Card.css +391 -0
  28. package/components/Card/card-body.marko +5 -0
  29. package/components/Card/card-collapsible.marko +94 -0
  30. package/components/Card/card-footer.marko +12 -0
  31. package/components/Card/card-header.marko +30 -0
  32. package/components/Card/card-info-pair.marko +17 -0
  33. package/components/Card/card-info-row.marko +12 -0
  34. package/components/Card/card-section.marko +14 -0
  35. package/components/Card/card-variants.js +24 -0
  36. package/components/Card/card.marko +124 -0
  37. package/components/Checkbox/Checkbox.css +249 -0
  38. package/components/Checkbox/checkbox-variants.js +21 -0
  39. package/components/Checkbox/checkbox.marko +128 -0
  40. package/components/ColorPicker/ColorPicker.css +253 -0
  41. package/components/ColorPicker/color-maths.js +104 -0
  42. package/components/ColorPicker/color-picker.marko +474 -0
  43. package/components/DatePicker/DatePicker.css +429 -0
  44. package/components/DatePicker/date-logic.js +225 -0
  45. package/components/DatePicker/date-picker.marko +492 -0
  46. package/components/Drawer/Drawer.css +174 -0
  47. package/components/Drawer/drawer.marko +262 -0
  48. package/components/Floating/Floating.css +28 -0
  49. package/components/Floating/floating-position.js +251 -0
  50. package/components/Floating/floating.marko +291 -0
  51. package/components/Form/Form.css +15 -0
  52. package/components/Form/form-context.js +69 -0
  53. package/components/Form/form.marko +73 -0
  54. package/components/GradientPicker/GradientPicker.css +179 -0
  55. package/components/GradientPicker/gradient-picker.marko +398 -0
  56. package/components/Grid/Grid.css +161 -0
  57. package/components/Grid/col.marko +36 -0
  58. package/components/Grid/grid-variants.js +43 -0
  59. package/components/Grid/grid.marko +25 -0
  60. package/components/Header/Header.css +457 -0
  61. package/components/Header/header.marko +523 -0
  62. package/components/Icon/Icon.css +44 -0
  63. package/components/Icon/icon-colors.js +31 -0
  64. package/components/Icon/icon.marko +56 -0
  65. package/components/IconButton/IconButton.css +85 -0
  66. package/components/IconButton/icon-button-variants.js +18 -0
  67. package/components/IconButton/icon-button.marko +103 -0
  68. package/components/Incrementer/Incrementer.css +94 -0
  69. package/components/Incrementer/incrementer.marko +70 -0
  70. package/components/Input/Input.css +547 -0
  71. package/components/Input/input-countries.js +109 -0
  72. package/components/Input/input-masks.js +218 -0
  73. package/components/Input/input-types.js +194 -0
  74. package/components/Input/input.marko +530 -0
  75. package/components/Menu/Menu.css +651 -0
  76. package/components/Menu/menu-variants.js +89 -0
  77. package/components/Menu/menu.marko +560 -0
  78. package/components/Modal/Modal.css +160 -0
  79. package/components/Modal/modal.marko +296 -0
  80. package/components/Nav/Nav.css +298 -0
  81. package/components/Nav/nav-variants.js +23 -0
  82. package/components/Nav/nav.marko +164 -0
  83. package/components/Notification/Notification.css +78 -0
  84. package/components/Notification/notification-variants.js +30 -0
  85. package/components/Notification/notification.marko +55 -0
  86. package/components/Pagination/Pagination.css +78 -0
  87. package/components/Pagination/pagination-pages.js +66 -0
  88. package/components/Pagination/pagination.marko +141 -0
  89. package/components/Pill/Pill.css +193 -0
  90. package/components/Pill/pill-variants.js +16 -0
  91. package/components/Pill/pill.marko +165 -0
  92. package/components/Popover/Popover.css +121 -0
  93. package/components/Popover/popover.marko +96 -0
  94. package/components/Radio/Radio.css +230 -0
  95. package/components/Radio/radio-variants.js +16 -0
  96. package/components/Radio/radio.marko +93 -0
  97. package/components/RadioGroup/RadioGroup.css +32 -0
  98. package/components/RadioGroup/radio-group.marko +93 -0
  99. package/components/SearchBar/SearchBar.css +181 -0
  100. package/components/SearchBar/search-bar-variants.js +24 -0
  101. package/components/SearchBar/search-bar.marko +157 -0
  102. package/components/Select/Select.css +427 -0
  103. package/components/Select/select-options.js +163 -0
  104. package/components/Select/select.marko +449 -0
  105. package/components/Separator/Separator.css +22 -0
  106. package/components/Separator/separator.marko +14 -0
  107. package/components/Skeleton/Skeleton.css +100 -0
  108. package/components/Skeleton/skeleton-avatar.marko +28 -0
  109. package/components/Skeleton/skeleton-button.marko +22 -0
  110. package/components/Skeleton/skeleton-image.marko +20 -0
  111. package/components/Skeleton/skeleton-input.marko +22 -0
  112. package/components/Skeleton/skeleton-variants.js +16 -0
  113. package/components/Skeleton/skeleton.marko +53 -0
  114. package/components/Spinner/Spinner.css +173 -0
  115. package/components/Spinner/spinner-geometry.js +39 -0
  116. package/components/Spinner/spinner-overlay.marko +97 -0
  117. package/components/Spinner/spinner.marko +88 -0
  118. package/components/StatCard/StatCard.css +90 -0
  119. package/components/StatCard/stat-card-group.marko +5 -0
  120. package/components/StatCard/stat-card.marko +57 -0
  121. package/components/Switch/Switch.css +209 -0
  122. package/components/Switch/switch.marko +99 -0
  123. package/components/Table/Table.css +270 -0
  124. package/components/Table/table-cell.marko +28 -0
  125. package/components/Table/table-header.marko +27 -0
  126. package/components/Table/table-logic.js +129 -0
  127. package/components/Table/table.marko +372 -0
  128. package/components/Text/Text.css +79 -0
  129. package/components/Text/text-variants.js +25 -0
  130. package/components/Text/text.marko +48 -0
  131. package/components/TextEditor/TextEditor.css +78 -0
  132. package/components/TextEditor/safe-url.js +50 -0
  133. package/components/TextEditor/text-editor-commands.js +22 -0
  134. package/components/TextEditor/text-editor.marko +253 -0
  135. package/components/Toast/Toast.css +88 -0
  136. package/components/Toast/toast-api.js +19 -0
  137. package/components/Toast/toast-store.js +118 -0
  138. package/components/Toast/toast-variants.js +16 -0
  139. package/components/Toast/toast.marko +93 -0
  140. package/components/Toast/toaster.marko +70 -0
  141. package/components/Toggle/Toggle.css +169 -0
  142. package/components/Toggle/toggle.marko +93 -0
  143. package/components/Tooltip/Tooltip.css +115 -0
  144. package/components/Tooltip/tooltip.marko +77 -0
  145. package/components/Uploader/Uploader.css +180 -0
  146. package/components/Uploader/crop-geometry.js +161 -0
  147. package/components/Uploader/uploader.marko +269 -0
  148. package/foundations/Scrollbar/Scrollbar.css +26 -0
  149. package/foundations/attrs.js +27 -0
  150. package/foundations/breakpoints.js +27 -0
  151. package/foundations/contrast.js +74 -0
  152. package/foundations/custom-scheme.js +222 -0
  153. package/foundations/overlay.js +108 -0
  154. package/foundations/styles.css +284 -0
  155. package/foundations/text.js +22 -0
  156. package/foundations/theme-server.js +81 -0
  157. package/foundations/theme.js +42 -0
  158. package/foundations/tokens.css +1656 -0
  159. package/icons/custom/custom-video-horizontal-filled.marko +6 -0
  160. package/icons/custom/custom-video-horizontal-regular.marko +6 -0
  161. package/icons/custom/custom-video-tour-3d-filled.marko +7 -0
  162. package/icons/custom/custom-video-tour-3d-regular.marko +9 -0
  163. package/icons/custom/custom-video-vertical-filled.marko +6 -0
  164. package/icons/custom/custom-video-vertical-regular.marko +6 -0
  165. package/icons/generated/fluent-add-regular.marko +3 -0
  166. package/icons/generated/fluent-alert-regular.marko +3 -0
  167. package/icons/generated/fluent-apps-regular.marko +3 -0
  168. package/icons/generated/fluent-arrow-download-regular.marko +3 -0
  169. package/icons/generated/fluent-arrow-left-regular.marko +3 -0
  170. package/icons/generated/fluent-arrow-right-regular.marko +3 -0
  171. package/icons/generated/fluent-arrow-rotate-clockwise-regular.marko +3 -0
  172. package/icons/generated/fluent-arrow-rotate-counterclockwise-regular.marko +3 -0
  173. package/icons/generated/fluent-border-all-regular.marko +3 -0
  174. package/icons/generated/fluent-calendar-regular.marko +3 -0
  175. package/icons/generated/fluent-checkmark-circle-filled.marko +3 -0
  176. package/icons/generated/fluent-checkmark-regular.marko +3 -0
  177. package/icons/generated/fluent-chevron-down-regular.marko +3 -0
  178. package/icons/generated/fluent-chevron-left-regular.marko +3 -0
  179. package/icons/generated/fluent-chevron-right-regular.marko +3 -0
  180. package/icons/generated/fluent-chevron-up-regular.marko +3 -0
  181. package/icons/generated/fluent-circle-half-fill-regular.marko +3 -0
  182. package/icons/generated/fluent-clock-regular.marko +3 -0
  183. package/icons/generated/fluent-color-regular.marko +3 -0
  184. package/icons/generated/fluent-crop-regular.marko +3 -0
  185. package/icons/generated/fluent-dark-theme-regular.marko +3 -0
  186. package/icons/generated/fluent-delete-regular.marko +3 -0
  187. package/icons/generated/fluent-desktop-regular.marko +3 -0
  188. package/icons/generated/fluent-dismiss-circle-filled.marko +3 -0
  189. package/icons/generated/fluent-dismiss-regular.marko +3 -0
  190. package/icons/generated/fluent-document-pdf-regular.marko +3 -0
  191. package/icons/generated/fluent-document-regular.marko +3 -0
  192. package/icons/generated/fluent-eye-off-regular.marko +3 -0
  193. package/icons/generated/fluent-eye-regular.marko +3 -0
  194. package/icons/generated/fluent-eyedropper-regular.marko +3 -0
  195. package/icons/generated/fluent-grid-regular.marko +3 -0
  196. package/icons/generated/fluent-history-regular.marko +3 -0
  197. package/icons/generated/fluent-home-regular.marko +3 -0
  198. package/icons/generated/fluent-image-regular.marko +3 -0
  199. package/icons/generated/fluent-info-regular.marko +3 -0
  200. package/icons/generated/fluent-link-regular.marko +3 -0
  201. package/icons/generated/fluent-more-horizontal-regular.marko +3 -0
  202. package/icons/generated/fluent-navigation-regular.marko +3 -0
  203. package/icons/generated/fluent-paint-brush-regular.marko +3 -0
  204. package/icons/generated/fluent-question-circle-regular.marko +3 -0
  205. package/icons/generated/fluent-search-regular.marko +3 -0
  206. package/icons/generated/fluent-settings-regular.marko +3 -0
  207. package/icons/generated/fluent-square-hint-regular.marko +3 -0
  208. package/icons/generated/fluent-subtract-regular.marko +3 -0
  209. package/icons/generated/fluent-text-align-center-regular.marko +3 -0
  210. package/icons/generated/fluent-text-align-left-regular.marko +3 -0
  211. package/icons/generated/fluent-text-align-right-regular.marko +3 -0
  212. package/icons/generated/fluent-text-bold-regular.marko +3 -0
  213. package/icons/generated/fluent-text-bullet-list-regular.marko +3 -0
  214. package/icons/generated/fluent-text-font-regular.marko +3 -0
  215. package/icons/generated/fluent-text-font-size-regular.marko +3 -0
  216. package/icons/generated/fluent-text-italic-regular.marko +3 -0
  217. package/icons/generated/fluent-text-number-list-ltr-regular.marko +3 -0
  218. package/icons/generated/fluent-video-regular.marko +3 -0
  219. package/icons/generated/fluent-warning-filled.marko +3 -0
  220. package/icons/generated/fluent-weather-moon-regular.marko +3 -0
  221. package/icons/generated/fluent-weather-sunny-regular.marko +3 -0
  222. package/icons/generated/fluent-zoom-in-regular.marko +3 -0
  223. package/icons/generated/fluent-zoom-out-regular.marko +3 -0
  224. package/icons/manifest.json +61 -0
  225. package/icons/social/social-facebook-color.marko +7 -0
  226. package/icons/social/social-facebook.marko +6 -0
  227. package/icons/social/social-google-color.marko +20 -0
  228. package/icons/social/social-google.marko +6 -0
  229. package/icons/social/social-instagram-color.marko +16 -0
  230. package/icons/social/social-instagram.marko +6 -0
  231. package/icons/social/social-linkedin-color.marko +8 -0
  232. package/icons/social/social-linkedin.marko +8 -0
  233. package/icons/social/social-messenger-color.marko +15 -0
  234. package/icons/social/social-messenger.marko +6 -0
  235. package/icons/social/social-pinterest-color.marko +6 -0
  236. package/icons/social/social-pinterest.marko +6 -0
  237. package/icons/social/social-telegram-color.marko +7 -0
  238. package/icons/social/social-telegram.marko +6 -0
  239. package/icons/social/social-whatsapp-color.marko +8 -0
  240. package/icons/social/social-whatsapp.marko +7 -0
  241. package/icons/social/social-x-color.marko +7 -0
  242. package/icons/social/social-x.marko +6 -0
  243. package/icons/social/social-youtube-color.marko +7 -0
  244. package/icons/social/social-youtube.marko +6 -0
  245. package/marko.json +203 -0
  246. package/package.json +55 -0
@@ -0,0 +1,398 @@
1
+ <!-- sds-gradient-picker — designed for SDS; KDLS has no counterpart.
2
+
3
+ KDLS's palette has no gradient to pick: its one gradient was the Lya brand value,
4
+ fixed, and chosen by selecting the `lya` colour scheme. SDS removed those values and
5
+ kept the machinery, so the gradient became something a site chooses — and choosing it
6
+ needs a control. This is that control, built to the design at
7
+ figma.com/design/HefwQxdw5h6wrhjzC9FHtf?node-id=11362-100508.
8
+
9
+ Two stops, side by side, each with a hue rail, a brightness rail and a hex field;
10
+ under them the gradient itself, at the size it is actually judged at; then Apply.
11
+
12
+ Two rails rather than the ColorPicker's saturation canvas, because that is what the
13
+ design asks for and because a gradient is read as two hues, not as two points in a
14
+ colour space. Saturation is still carried — a hex typed into the field keeps whatever
15
+ it has, and the rails move without flattening it — so nothing a caller sets is lost
16
+ to a control that cannot express it.
17
+
18
+ `onChange` fires on Apply, not on every drag: it repaints the whole site, and doing
19
+ that on each pixel of a drag is a strobe rather than a preview.
20
+
21
+ `overlay` takes the panel out of the flow entirely: a backdrop over the page with the
22
+ panel centred in it, instead of a panel anchored to the trigger. It is for one situation —
23
+ this control living inside another panel, where anchoring would put a panel on top of the
24
+ panel it came from — and for a phone, where there is no room beside a trigger to anchor
25
+ anything. Whatever opened this is told through `onOpenChange`, so it can get out of the
26
+ way.
27
+ -->
28
+
29
+ import { hsvToRgb, rgbToHex, hexToRgb, rgbToHsv, startingRgb } from "../ColorPicker/color-maths.js";
30
+
31
+ /**
32
+ * A stop remembers the hex it came in as, and gives it back untouched until it is moved.
33
+ *
34
+ * hex → HSV → hex does not round-trip: #BF71E5 comes back as #BE70E6, a whole point off
35
+ * on each channel. Harmless once, and not once — the portal applies, the value returns as
36
+ * the picker's input, and the next apply rounds it again. A colour nobody touched would
37
+ * walk. So `source` is the answer until an edit makes it stale.
38
+ */
39
+ static function readStop(value, fallback) {
40
+ const source = value ?? fallback;
41
+ const [r, g, b] = startingRgb(source);
42
+ const [h, s, v] = rgbToHsv(r, g, b);
43
+ return { h, s, v, source };
44
+ }
45
+
46
+ class {
47
+ onCreate() {
48
+ this.state = { open: false, stops: this.stopsFrom(undefined), hexDraft: [null, null] };
49
+ }
50
+
51
+ onInput(input) {
52
+ if (this.seeded) return;
53
+ this.state.stops = this.stopsFrom(input.value);
54
+ this.seeded = true;
55
+ }
56
+
57
+ stopsFrom(value) {
58
+ const [a, b] = Array.isArray(value) ? value : [];
59
+ return [readStop(a, "#77A6E0"), readStop(b, "#BF71E5")];
60
+ }
61
+
62
+ hexAt(index) {
63
+ const { h, s, v, source } = this.state.stops[index];
64
+ return source ?? rgbToHex(...hsvToRgb(h, s, v));
65
+ }
66
+
67
+ get gradient() {
68
+ return `linear-gradient(91deg, ${this.hexAt(0)} 0%, ${this.hexAt(1)} 100%)`;
69
+ }
70
+
71
+ toggle() {
72
+ if (this.input.disabled) return;
73
+ this.setOpen(!this.state.open);
74
+ }
75
+
76
+ // The backdrop dismisses, the panel does not: a drag that starts on a slider and ends
77
+ // outside it is still a drag. Escape is listened for on the document because the
78
+ // backdrop only receives key events while something inside it holds focus.
79
+ onMount() {
80
+ this.escape = (event) => this.closeOnEscape(event);
81
+ document.addEventListener("keydown", this.escape);
82
+ }
83
+
84
+ onDestroy() {
85
+ document.removeEventListener("keydown", this.escape);
86
+ }
87
+
88
+ closeFromBackdrop(event, element) {
89
+ if (event.target !== element) return;
90
+ this.setOpen(false);
91
+ }
92
+
93
+ closeOnEscape(event) {
94
+ if (event.key !== "Escape") return;
95
+ if (!this.input.overlay || !(this.input.open ?? this.state.open)) return;
96
+ event.preventDefault();
97
+ this.setOpen(false);
98
+ }
99
+
100
+ setOpen(next) {
101
+ // The Floating reports its own open/close too, so the disabled check belongs here and
102
+ // not only on the trigger's click: a disabled <button> swallows the click, but the
103
+ // Floating around it does not, and the panel opened anyway.
104
+ if (next && this.input.disabled) return;
105
+ if (this.input.open === undefined) this.state.open = next;
106
+ this.input.onOpenChange?.(next);
107
+ // Reopening starts from what is applied, not from where the last drag was left.
108
+ if (next) {
109
+ this.state.stops = this.stopsFrom(this.input.value);
110
+ this.state.hexDraft = [null, null];
111
+ }
112
+ }
113
+
114
+ setStop(index, part, amount) {
115
+ // Moving a rail is the edit that makes `source` stale.
116
+ const stops = this.state.stops.map((stop, i) =>
117
+ (i === index ? { ...stop, [part]: amount, source: null } : stop));
118
+ this.state.stops = stops;
119
+ this.state.hexDraft = this.state.hexDraft.map((draft, i) => (i === index ? null : draft));
120
+ }
121
+
122
+ /** Shared by both rails: a press anywhere on the rail moves the thumb there. */
123
+ track(element, read, event) {
124
+ if (!element) return;
125
+ const apply = (moveEvent) => {
126
+ const rect = element.getBoundingClientRect();
127
+ read(Math.max(0, Math.min(1, (moveEvent.clientX - rect.left) / rect.width)));
128
+ };
129
+ apply(event);
130
+ const move = (moveEvent) => apply(moveEvent);
131
+ const up = () => {
132
+ window.removeEventListener("mousemove", move);
133
+ window.removeEventListener("mouseup", up);
134
+ };
135
+ window.addEventListener("mousemove", move);
136
+ window.addEventListener("mouseup", up);
137
+ }
138
+
139
+ dragHue(index, event) {
140
+ this.track(this.getEl(`hue-${index}`), (x) => this.setStop(index, "h", Math.round(x * 360)), event);
141
+ }
142
+
143
+ dragBrightness(index, event) {
144
+ this.track(this.getEl(`bri-${index}`), (x) => this.setStop(index, "v", Math.round(x * 100)), event);
145
+ }
146
+
147
+ /** Arrows on a rail, Shift for ten at a time — the ColorPicker's rule, same reason. */
148
+ nudge(index, part, event) {
149
+ const direction = { ArrowLeft: -1, ArrowDown: -1, ArrowRight: 1, ArrowUp: 1 }[event.key];
150
+ if (direction === undefined) return;
151
+ event.preventDefault();
152
+
153
+ const max = part === "h" ? 360 : 100;
154
+ const step = event.shiftKey ? 10 : 1;
155
+ const next = this.state.stops[index][part] + direction * step;
156
+ this.setStop(index, part, Math.max(0, Math.min(max, next)));
157
+ }
158
+
159
+ typeHex(index, raw) {
160
+ const draft = this.state.hexDraft.map((value, i) => (i === index ? raw : value));
161
+ this.state.hexDraft = draft;
162
+
163
+ const rgb = hexToRgb(raw.startsWith("#") ? raw : `#${raw}`);
164
+ if (!rgb) return;
165
+
166
+ const [h, s, v] = rgbToHsv(...rgb);
167
+ // A typed hex is exact by definition, so it becomes the new source.
168
+ const source = rgbToHex(...rgb);
169
+ this.state.stops = this.state.stops.map((stop, i) => (i === index ? { h, s, v, source } : stop));
170
+ }
171
+
172
+ apply() {
173
+ this.input.onChange?.([this.hexAt(0), this.hexAt(1)]);
174
+ this.state.open = false;
175
+ }
176
+ }
177
+
178
+ $ {
179
+ const {
180
+ label,
181
+ width = "hug",
182
+ zIndex,
183
+ disabled = false,
184
+ applyLabel = "Aplicar",
185
+ overlay = false,
186
+ open: openInput,
187
+ showTrigger = true,
188
+ class: extraClass = "",
189
+ } = input;
190
+
191
+ if (!label) throw new Error("sds-gradient-picker needs a `label`.");
192
+ if (width !== "hug" && width !== "fill") {
193
+ throw new Error(`Unknown gradient picker width: "${width}". Allowed: hug, fill`);
194
+ }
195
+
196
+ // Controlled when the caller passes `open`. An overlay picker usually has to be: it is
197
+ // opened from somewhere that then gets out of the way, and a picker that owns its own
198
+ // open state dies with whatever it was rendered inside.
199
+ const open = openInput !== undefined ? openInput : state.open;
200
+ const stops = state.stops;
201
+ const gradient = component.gradient;
202
+
203
+ const shown = (index) => state.hexDraft[index] ?? component.hexAt(index).replace("#", "").toUpperCase();
204
+ // The brightness rail runs from black to this stop's hue at full strength, so its own
205
+ // colour says what moving it would do.
206
+ // Black to this stop's hue at full strength: the rail's own colour says what moving it
207
+ // does. `hsl(h 100% 0%)` rather than a literal black, for the same reason the hue rail
208
+ // is written in hue angles — this is the axis, not a palette value.
209
+ const briRail = (stop) =>
210
+ `linear-gradient(to right, hsl(${stop.h}, 100%, 0%) 0%, ${rgbToHex(...hsvToRgb(stop.h, stop.s, 100))} 100%)`;
211
+ }
212
+
213
+ <div class=[
214
+ "sds-gradient-picker",
215
+ showTrigger ? "" : "sds-gradient-picker--headless",
216
+ width === "fill" ? "sds-gradient-picker--fill" : "",
217
+ extraClass,
218
+ ].filter(Boolean).join(" ")>
219
+ <if(showTrigger)>
220
+ <sds-floating
221
+ open=open
222
+ onOpenChange=(next) => component.setOpen(next)
223
+ placement="bottomLeft"
224
+ zIndex=zIndex
225
+ >
226
+ <@content>
227
+ <if(!overlay)>
228
+ <div class="sds-gradient-picker__panel">
229
+ <div class="sds-gradient-picker__stops">
230
+ <for|stop, index| of=stops>
231
+ <if(index > 0)>
232
+ <div class="sds-gradient-picker__divider" aria-hidden="true"/>
233
+ </if>
234
+
235
+ <div class="sds-gradient-picker__stop">
236
+ <div
237
+ key=`hue-${index}`
238
+ class="sds-gradient-picker__rail sds-gradient-picker__rail--hue"
239
+ role="slider"
240
+ aria-label=`Matiz da parada ${index + 1}`
241
+ aria-valuemin="0"
242
+ aria-valuemax="360"
243
+ aria-valuenow=String(stop.h)
244
+ tabindex="0"
245
+ on-mousedown("dragHue", index)
246
+ on-keydown("nudge", index, "h")
247
+ >
248
+ <div class="sds-gradient-picker__thumb" style=`left:${(stop.h / 360) * 100}%`/>
249
+ </div>
250
+
251
+ <div
252
+ key=`bri-${index}`
253
+ class="sds-gradient-picker__rail"
254
+ style=`background:${briRail(stop)}`
255
+ role="slider"
256
+ aria-label=`Brilho da parada ${index + 1}`
257
+ aria-valuemin="0"
258
+ aria-valuemax="100"
259
+ aria-valuenow=String(stop.v)
260
+ tabindex="0"
261
+ on-mousedown("dragBrightness", index)
262
+ on-keydown("nudge", index, "v")
263
+ >
264
+ <div class="sds-gradient-picker__thumb" style=`left:${stop.v}%`/>
265
+ </div>
266
+
267
+ <sds-input
268
+ label="HEX"
269
+ width="fill"
270
+ value=shown(index)
271
+ onChange=(event, value) => component.typeHex(index, value)
272
+ />
273
+ </div>
274
+ </for>
275
+ </div>
276
+
277
+ <!-- The gradient as a band. A swatch the size of a chip cannot show where two
278
+ hues meet, which is the only thing being decided here; 40px can, and keeps
279
+ the panel short enough for the places that open it. -->
280
+ <div
281
+ class="sds-gradient-picker__preview"
282
+ style=`background:${gradient}`
283
+ role="img"
284
+ aria-label=`Prévia: ${component.hexAt(0)} a ${component.hexAt(1)}`
285
+ />
286
+
287
+ <!-- `lg` is 56px, which is the height the design gives this button. -->
288
+ <sds-button
289
+ variant="primary"
290
+ size="lg"
291
+ width="fill"
292
+ label=applyLabel
293
+ onClick=() => component.apply()
294
+ />
295
+ </div>
296
+ </if>
297
+ </@content>
298
+
299
+ <!-- A real button, with its own click: the Floating positions the panel, it does not
300
+ open it. The ColorPicker's trigger carries the same `on-click`, and leaving it off
301
+ here produced a control that looked identical and did nothing. -->
302
+ <button
303
+ type="button"
304
+ class="sds-gradient-picker__trigger"
305
+ disabled=(disabled || null)
306
+ aria-haspopup="dialog"
307
+ aria-expanded=(open ? "true" : "false")
308
+ on-click("toggle")
309
+ >
310
+ <span class="sds-gradient-picker__swatch" style=`background:${gradient}` aria-hidden="true"/>
311
+ <span class="sds-gradient-picker__field">
312
+ <sds-text as="span" variant="labelRegular">${label}</sds-text>
313
+ </span>
314
+ </button>
315
+ </sds-floating>
316
+ </if>
317
+
318
+ <!-- Centred, over everything, when `overlay` is on. Escape and the backdrop both close
319
+ it; the panel itself does not, so a drag that ends outside the panel is still a
320
+ drag rather than a dismissal. -->
321
+ <if(overlay && open)>
322
+ <div
323
+ class="sds-gradient-picker__backdrop"
324
+ style=(zIndex !== undefined ? `z-index:${zIndex}` : null)
325
+ on-click("closeFromBackdrop")
326
+ on-keydown("closeOnEscape")
327
+ >
328
+ <div class="sds-gradient-picker__panel">
329
+ <div class="sds-gradient-picker__stops">
330
+ <for|stop, index| of=stops>
331
+ <if(index > 0)>
332
+ <div class="sds-gradient-picker__divider" aria-hidden="true"/>
333
+ </if>
334
+
335
+ <div class="sds-gradient-picker__stop">
336
+ <div
337
+ key=`hue-${index}`
338
+ class="sds-gradient-picker__rail sds-gradient-picker__rail--hue"
339
+ role="slider"
340
+ aria-label=`Matiz da parada ${index + 1}`
341
+ aria-valuemin="0"
342
+ aria-valuemax="360"
343
+ aria-valuenow=String(stop.h)
344
+ tabindex="0"
345
+ on-mousedown("dragHue", index)
346
+ on-keydown("nudge", index, "h")
347
+ >
348
+ <div class="sds-gradient-picker__thumb" style=`left:${(stop.h / 360) * 100}%`/>
349
+ </div>
350
+
351
+ <div
352
+ key=`bri-${index}`
353
+ class="sds-gradient-picker__rail"
354
+ style=`background:${briRail(stop)}`
355
+ role="slider"
356
+ aria-label=`Brilho da parada ${index + 1}`
357
+ aria-valuemin="0"
358
+ aria-valuemax="100"
359
+ aria-valuenow=String(stop.v)
360
+ tabindex="0"
361
+ on-mousedown("dragBrightness", index)
362
+ on-keydown("nudge", index, "v")
363
+ >
364
+ <div class="sds-gradient-picker__thumb" style=`left:${stop.v}%`/>
365
+ </div>
366
+
367
+ <sds-input
368
+ label="HEX"
369
+ width="fill"
370
+ value=shown(index)
371
+ onChange=(event, value) => component.typeHex(index, value)
372
+ />
373
+ </div>
374
+ </for>
375
+ </div>
376
+
377
+ <!-- The gradient as a band. A swatch the size of a chip cannot show where two
378
+ hues meet, which is the only thing being decided here; 40px can, and keeps
379
+ the panel short enough for the places that open it. -->
380
+ <div
381
+ class="sds-gradient-picker__preview"
382
+ style=`background:${gradient}`
383
+ role="img"
384
+ aria-label=`Prévia: ${component.hexAt(0)} a ${component.hexAt(1)}`
385
+ />
386
+
387
+ <!-- `lg` is 56px, which is the height the design gives this button. -->
388
+ <sds-button
389
+ variant="primary"
390
+ size="lg"
391
+ width="fill"
392
+ label=applyLabel
393
+ onClick=() => component.apply()
394
+ />
395
+ </div>
396
+ </div>
397
+ </if>
398
+ </div>
@@ -0,0 +1,161 @@
1
+ /* SDS Grid.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Grid/Grid.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+ *//* ============================================================
5
+ GRID — sds-grid / sds-col
6
+ 12-column responsive grid system
7
+ xs: < 800px | sm: 800px+ | md: 1024px+ | lg: 1280px+ | xl: 1440px+
8
+ ============================================================ */
9
+
10
+ /* ── Grid container ── */
11
+ .sds-grid {
12
+ display: grid;
13
+ grid-template-columns: repeat(12, 1fr);
14
+ gap: var(--spacing-4);
15
+ width: 100%;
16
+ }
17
+ /* ── Column variants ── */
18
+ .sds-grid--cols-1 { grid-template-columns: repeat(1, 1fr); }
19
+ .sds-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
20
+ .sds-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
21
+ .sds-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
22
+ .sds-grid--cols-6 { grid-template-columns: repeat(6, 1fr); }
23
+ .sds-grid--cols-12 { grid-template-columns: repeat(12, 1fr); }
24
+ /* ── Gap variants (spacing scale) ── */
25
+ .sds-grid--gap-1 { gap: var(--spacing-1); }
26
+ .sds-grid--gap-2 { gap: var(--spacing-2); }
27
+ .sds-grid--gap-3 { gap: var(--spacing-3); }
28
+ .sds-grid--gap-4 { gap: var(--spacing-4); }
29
+ .sds-grid--gap-5 { gap: var(--spacing-5); }
30
+ .sds-grid--gap-6 { gap: var(--spacing-6); }
31
+ .sds-grid--gap-7 { gap: var(--spacing-7); }
32
+ .sds-grid--gap-8 { gap: var(--spacing-8); }
33
+ .sds-grid--gap-9 { gap: var(--spacing-9); }
34
+ .sds-grid--gap-10 { gap: var(--spacing-10); }
35
+ .sds-grid--gap-12 { gap: var(--spacing-12); }
36
+ .sds-grid--gap-14 { gap: var(--spacing-14); }
37
+ .sds-grid--gap-16 { gap: var(--spacing-16); }
38
+ /* ── Row gap variants ── */
39
+ .sds-grid--row-gap-1 { row-gap: var(--spacing-1); }
40
+ .sds-grid--row-gap-2 { row-gap: var(--spacing-2); }
41
+ .sds-grid--row-gap-3 { row-gap: var(--spacing-3); }
42
+ .sds-grid--row-gap-4 { row-gap: var(--spacing-4); }
43
+ .sds-grid--row-gap-5 { row-gap: var(--spacing-5); }
44
+ .sds-grid--row-gap-6 { row-gap: var(--spacing-6); }
45
+ .sds-grid--row-gap-7 { row-gap: var(--spacing-7); }
46
+ .sds-grid--row-gap-8 { row-gap: var(--spacing-8); }
47
+ .sds-grid--row-gap-9 { row-gap: var(--spacing-9); }
48
+ .sds-grid--row-gap-10 { row-gap: var(--spacing-10); }
49
+ .sds-grid--row-gap-12 { row-gap: var(--spacing-12); }
50
+ .sds-grid--row-gap-14 { row-gap: var(--spacing-14); }
51
+ .sds-grid--row-gap-16 { row-gap: var(--spacing-16); }
52
+ /* ── Col gap variants ── */
53
+ .sds-grid--col-gap-1 { column-gap: var(--spacing-1); }
54
+ .sds-grid--col-gap-2 { column-gap: var(--spacing-2); }
55
+ .sds-grid--col-gap-3 { column-gap: var(--spacing-3); }
56
+ .sds-grid--col-gap-4 { column-gap: var(--spacing-4); }
57
+ .sds-grid--col-gap-5 { column-gap: var(--spacing-5); }
58
+ .sds-grid--col-gap-6 { column-gap: var(--spacing-6); }
59
+ .sds-grid--col-gap-7 { column-gap: var(--spacing-7); }
60
+ .sds-grid--col-gap-8 { column-gap: var(--spacing-8); }
61
+ .sds-grid--col-gap-9 { column-gap: var(--spacing-9); }
62
+ .sds-grid--col-gap-10 { column-gap: var(--spacing-10); }
63
+ .sds-grid--col-gap-12 { column-gap: var(--spacing-12); }
64
+ .sds-grid--col-gap-14 { column-gap: var(--spacing-14); }
65
+ .sds-grid--col-gap-16 { column-gap: var(--spacing-16); }
66
+ /* ── Align variants ── */
67
+ .sds-grid--align-start { align-items: start; }
68
+ .sds-grid--align-center { align-items: center; }
69
+ .sds-grid--align-end { align-items: end; }
70
+ /* ── Col base ── */
71
+ .sds-col {
72
+ min-width: 0;
73
+ }
74
+ /* ── Span classes (base / xs default) ── */
75
+ .sds-col--span-1 { grid-column: span 1; }
76
+ .sds-col--span-2 { grid-column: span 2; }
77
+ .sds-col--span-3 { grid-column: span 3; }
78
+ .sds-col--span-4 { grid-column: span 4; }
79
+ .sds-col--span-5 { grid-column: span 5; }
80
+ .sds-col--span-6 { grid-column: span 6; }
81
+ .sds-col--span-7 { grid-column: span 7; }
82
+ .sds-col--span-8 { grid-column: span 8; }
83
+ .sds-col--span-9 { grid-column: span 9; }
84
+ .sds-col--span-10 { grid-column: span 10; }
85
+ .sds-col--span-11 { grid-column: span 11; }
86
+ .sds-col--span-12 { grid-column: span 12; }
87
+ /* ── xs — < 800px ── */
88
+ @media (max-width: 799px) {
89
+ .sds-col--xs-1 { grid-column: span 1; }
90
+ .sds-col--xs-2 { grid-column: span 2; }
91
+ .sds-col--xs-3 { grid-column: span 3; }
92
+ .sds-col--xs-4 { grid-column: span 4; }
93
+ .sds-col--xs-5 { grid-column: span 5; }
94
+ .sds-col--xs-6 { grid-column: span 6; }
95
+ .sds-col--xs-7 { grid-column: span 7; }
96
+ .sds-col--xs-8 { grid-column: span 8; }
97
+ .sds-col--xs-9 { grid-column: span 9; }
98
+ .sds-col--xs-10 { grid-column: span 10; }
99
+ .sds-col--xs-11 { grid-column: span 11; }
100
+ .sds-col--xs-12 { grid-column: span 12; }
101
+ }
102
+ /* ── sm — 800px+ ── */
103
+ @media (min-width: 800px) {
104
+ .sds-col--sm-1 { grid-column: span 1; }
105
+ .sds-col--sm-2 { grid-column: span 2; }
106
+ .sds-col--sm-3 { grid-column: span 3; }
107
+ .sds-col--sm-4 { grid-column: span 4; }
108
+ .sds-col--sm-5 { grid-column: span 5; }
109
+ .sds-col--sm-6 { grid-column: span 6; }
110
+ .sds-col--sm-7 { grid-column: span 7; }
111
+ .sds-col--sm-8 { grid-column: span 8; }
112
+ .sds-col--sm-9 { grid-column: span 9; }
113
+ .sds-col--sm-10 { grid-column: span 10; }
114
+ .sds-col--sm-11 { grid-column: span 11; }
115
+ .sds-col--sm-12 { grid-column: span 12; }
116
+ }
117
+ /* ── md — 1024px+ ── */
118
+ @media (min-width: 1024px) {
119
+ .sds-col--md-1 { grid-column: span 1; }
120
+ .sds-col--md-2 { grid-column: span 2; }
121
+ .sds-col--md-3 { grid-column: span 3; }
122
+ .sds-col--md-4 { grid-column: span 4; }
123
+ .sds-col--md-5 { grid-column: span 5; }
124
+ .sds-col--md-6 { grid-column: span 6; }
125
+ .sds-col--md-7 { grid-column: span 7; }
126
+ .sds-col--md-8 { grid-column: span 8; }
127
+ .sds-col--md-9 { grid-column: span 9; }
128
+ .sds-col--md-10 { grid-column: span 10; }
129
+ .sds-col--md-11 { grid-column: span 11; }
130
+ .sds-col--md-12 { grid-column: span 12; }
131
+ }
132
+ /* ── lg — 1280px+ ── */
133
+ @media (min-width: 1280px) {
134
+ .sds-col--lg-1 { grid-column: span 1; }
135
+ .sds-col--lg-2 { grid-column: span 2; }
136
+ .sds-col--lg-3 { grid-column: span 3; }
137
+ .sds-col--lg-4 { grid-column: span 4; }
138
+ .sds-col--lg-5 { grid-column: span 5; }
139
+ .sds-col--lg-6 { grid-column: span 6; }
140
+ .sds-col--lg-7 { grid-column: span 7; }
141
+ .sds-col--lg-8 { grid-column: span 8; }
142
+ .sds-col--lg-9 { grid-column: span 9; }
143
+ .sds-col--lg-10 { grid-column: span 10; }
144
+ .sds-col--lg-11 { grid-column: span 11; }
145
+ .sds-col--lg-12 { grid-column: span 12; }
146
+ }
147
+ /* ── xl — 1440px+ ── */
148
+ @media (min-width: 1440px) {
149
+ .sds-col--xl-1 { grid-column: span 1; }
150
+ .sds-col--xl-2 { grid-column: span 2; }
151
+ .sds-col--xl-3 { grid-column: span 3; }
152
+ .sds-col--xl-4 { grid-column: span 4; }
153
+ .sds-col--xl-5 { grid-column: span 5; }
154
+ .sds-col--xl-6 { grid-column: span 6; }
155
+ .sds-col--xl-7 { grid-column: span 7; }
156
+ .sds-col--xl-8 { grid-column: span 8; }
157
+ .sds-col--xl-9 { grid-column: span 9; }
158
+ .sds-col--xl-10 { grid-column: span 10; }
159
+ .sds-col--xl-11 { grid-column: span 11; }
160
+ .sds-col--xl-12 { grid-column: span 12; }
161
+ }
@@ -0,0 +1,36 @@
1
+ <!-- sds-col — ported from kdls-design-system v2.5.1 (0f3edf8) components/Grid/Grid.css.
2
+
3
+ A column inside sds-grid. `span` is its width at every size; xs, sm, md, lg and xl
4
+ override it from that breakpoint up — xl being 1440px and over.
5
+
6
+ The tag is named for the class the stylesheet already defines — .sds-col, not
7
+ .sds-grid__item. An earlier draft invented the latter and it matched nothing: a column
8
+ with no rule spans one track and the whole layout quietly collapses. -->
9
+
10
+ import { BREAKPOINTS } from "./grid-variants.js";
11
+
12
+ $ {
13
+ // `span = 12`, which is KDLS's default and the one this file's own header warns about:
14
+ // a column with no span rule falls to grid auto-placement and takes ONE track of twelve,
15
+ // so a <sds-col> written with no span at all — the obvious way to write "a full-width
16
+ // column" — came out a twelfth of the width.
17
+ //
18
+ // The five breakpoints are destructured rather than read as `input[bp]`. They were read
19
+ // dynamically, which works at runtime and is invisible to everything else: the portal
20
+ // builds its props table and its controls by parsing what a component destructures, so
21
+ // `xs` through `xl` appeared in neither. A prop nobody can see is a prop nobody uses.
22
+ const { span = 12, xs, sm, md, lg, xl, class: extraClass = "" } = input;
23
+ const breakpoints = { xs, sm, md, lg, xl };
24
+ const classes = [
25
+ "sds-col",
26
+ `sds-col--span-${span}`,
27
+ ...BREAKPOINTS
28
+ .filter((bp) => breakpoints[bp] !== undefined)
29
+ .map((bp) => `sds-col--${bp}-${breakpoints[bp]}`),
30
+ extraClass,
31
+ ].filter(Boolean).join(" ");
32
+ }
33
+
34
+ <div class=classes>
35
+ <${input.renderBody}/>
36
+ </div>
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The Grid vocabulary.
3
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Grid/Grid.tsx.
4
+ */
5
+ export const GRID_COLS = [1, 2, 3, 4, 6, 12];
6
+ export const GRID_ALIGNMENTS = ['start', 'center', 'end', 'stretch'];
7
+ export const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl'];
8
+
9
+ /**
10
+ * Where each breakpoint starts, in pixels of viewport width.
11
+ *
12
+ * `Grid.css` is where these are enforced — CSS cannot read a custom property inside a
13
+ * `@media` condition, so the numbers have to be written there as literals. This map is
14
+ * for everything that needs to *know* them without being CSS: the portal's breakpoint
15
+ * strip, and any code deciding what is on screen.
16
+ *
17
+ * `tests/grid-breakpoints.test.js` parses Grid.css and fails if the two disagree. That
18
+ * test exists because they already did: the portal's strip was drawn at 600/900/1200,
19
+ * numbers from nowhere, so between 600px and 799px it announced `sm` while the grid was
20
+ * still laying out `xs`.
21
+ */
22
+ export const BREAKPOINT_MIN_WIDTH = {
23
+ xs: 0,
24
+ sm: 800,
25
+ md: 1024,
26
+ lg: 1280,
27
+ xl: 1440,
28
+ };
29
+
30
+ export function assertCols(cols) {
31
+ if (!GRID_COLS.includes(cols)) {
32
+ throw new Error(`Unknown grid cols: "${cols}". Allowed: ${GRID_COLS.join(', ')}`);
33
+ }
34
+ return cols;
35
+ }
36
+
37
+ export function assertAlign(align) {
38
+ if (align === undefined) return align;
39
+ if (!GRID_ALIGNMENTS.includes(align)) {
40
+ throw new Error(`Unknown grid align: "${align}". Allowed: ${GRID_ALIGNMENTS.join(', ')}`);
41
+ }
42
+ return align;
43
+ }
@@ -0,0 +1,25 @@
1
+ <!-- sds-grid — ported from kdls-design-system v2.5.1 (0f3edf8) components/Grid/Grid.tsx.
2
+
3
+ Layout only, no semantics: a grid is not a list and should not claim to be one. -->
4
+
5
+ import { assertCols, assertAlign } from "./grid-variants.js";
6
+
7
+ $ {
8
+ const { cols = 12, gap, rowGap, colGap, align, class: extraClass = "" } = input;
9
+ assertCols(cols);
10
+ assertAlign(align);
11
+
12
+ const classes = [
13
+ "sds-grid",
14
+ `sds-grid--cols-${cols}`,
15
+ gap !== undefined ? `sds-grid--gap-${gap}` : "",
16
+ rowGap !== undefined ? `sds-grid--row-gap-${rowGap}` : "",
17
+ colGap !== undefined ? `sds-grid--col-gap-${colGap}` : "",
18
+ align ? `sds-grid--align-${align}` : "",
19
+ extraClass,
20
+ ].filter(Boolean).join(" ");
21
+ }
22
+
23
+ <div class=classes>
24
+ <${input.renderBody}/>
25
+ </div>