@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,93 @@
1
+ <!-- sds-toast — ported from kdls-design-system v2.5.1 (0f3edf8) components/Toast/Toast.tsx.
2
+
3
+ One message on an inverted surface: dark in the light theme, light in the dark one.
4
+ It knows nothing about timing or stacking — that is sds-toaster and the store behind
5
+ it, which is what lets toast(...) be called from a catch block that is nowhere near a
6
+ component.
7
+
8
+ `leaving` is the exit animation, set by the toaster. It is in the public attribute
9
+ surface because KDLS put it there, marked internal for the same reason: nothing else
10
+ should set it.
11
+
12
+ Divergence: none in behaviour. The four hover and active overlays were rgba literals
13
+ in KDLS's stylesheet, which is what --color-opacity-inverse-* now holds — the regular
14
+ opacity family follows the theme and is backwards on a surface that runs against it. -->
15
+
16
+ import { assertPlacement } from "../Floating/floating-position.js";
17
+ import { assertToastFeedback, TOAST_FEEDBACK } from "./toast-variants.js";
18
+ import dismissIcon from "../../icons/generated/fluent-dismiss-regular.marko";
19
+ import successIcon from "../../icons/generated/fluent-checkmark-circle-filled.marko";
20
+ import alertIcon from "../../icons/generated/fluent-warning-filled.marko";
21
+ import errorIcon from "../../icons/generated/fluent-dismiss-circle-filled.marko";
22
+
23
+ $ {
24
+ const {
25
+ message,
26
+ feedback = "neutral",
27
+ actionLabel,
28
+ onAction,
29
+ actionDisabled = false,
30
+ actionDisabledTooltip,
31
+ actionDisabledTooltipPosition = "top",
32
+ showDismiss = true,
33
+ onDismiss,
34
+ leaving = false,
35
+ dismissLabel = "Fechar",
36
+ class: extraClass = "",
37
+ } = input;
38
+
39
+ assertPlacement(actionDisabledTooltipPosition);
40
+
41
+ if (!message) throw new Error("sds-toast needs a `message`.");
42
+ assertToastFeedback(feedback);
43
+
44
+ // neutral carries no icon at all: a message with nothing to report should not borrow a
45
+ // symbol that says something happened.
46
+ const icons = { neutral: null, success: successIcon, alert: alertIcon, error: errorIcon };
47
+ const icon = icons[feedback];
48
+
49
+ const classes = [
50
+ "sds-toast",
51
+ `sds-toast--${feedback}`,
52
+ leaving ? "sds-toast--leaving" : "",
53
+ extraClass,
54
+ ].filter(Boolean).join(" ");
55
+ }
56
+
57
+ <div
58
+ class=classes
59
+ role="status"
60
+ aria-live=(feedback === "error" ? "assertive" : "polite")
61
+ >
62
+ <if(icon)>
63
+ <span class="sds-toast__icon" aria-hidden="true">
64
+ <sds-icon color=feedback><${icon}/></sds-icon>
65
+ </span>
66
+ </if>
67
+
68
+ <span class="sds-toast__message">${message}</span>
69
+
70
+ <if(actionLabel)>
71
+ <sds-button
72
+ variant="link"
73
+ label=actionLabel
74
+ onClick=onAction
75
+ disabled=actionDisabled
76
+ disabledTooltip=actionDisabledTooltip
77
+ disabledTooltipPosition=actionDisabledTooltipPosition
78
+ class="sds-toast__action"
79
+ />
80
+ </if>
81
+
82
+ <if(showDismiss)>
83
+ <sds-icon-button
84
+ variant="neutral"
85
+ size="sm"
86
+ class="sds-toast__dismiss"
87
+ aria-label=dismissLabel
88
+ onClick=onDismiss
89
+ >
90
+ <@icon><${dismissIcon}/></@icon>
91
+ </sds-icon-button>
92
+ </if>
93
+ </div>
@@ -0,0 +1,70 @@
1
+ <!-- sds-toaster — ported from kdls-design-system v2.5.1 (0f3edf8) components/Toast/Toaster.tsx.
2
+
3
+ Mount once at the root of the page. It subscribes to the store and renders whatever
4
+ toast(...) has fired, stacked in the corner. No attributes: the position is fixed and
5
+ the timing belongs to the store.
6
+
7
+ Hovering the stack pauses every timer. A toast that vanishes while someone is reaching
8
+ for its action has wasted their time, and KDLS gets this right — it is carried as is.
9
+
10
+ Divergences:
11
+
12
+ No portal. KDLS renders through createPortal into <body>; Marko 5 has none, and a node
13
+ moved there returns on the next re-render (measured — see components/Floating).
14
+ The container is position:fixed, so it covers the viewport from wherever it sits, and
15
+ the only thing lost is escaping an ancestor with a transform. Mounted at the root of
16
+ the page, as it is meant to be, there is no such ancestor.
17
+
18
+ It renders nothing at all until the store has something, rather than an empty
19
+ container: an empty <div role="region" aria-label="Notificações"> is a landmark a
20
+ screen reader offers to navigate to and finds nothing inside. -->
21
+
22
+ import { subscribe, dismissToast, pauseAll, resumeAll } from "./toast-store.js";
23
+
24
+ class {
25
+ onCreate() {
26
+ this.state = { items: [] };
27
+ }
28
+
29
+ onMount() {
30
+ // The store calls back immediately with what it already holds, so a toaster mounted
31
+ // after a toast was fired still shows it.
32
+ this.unsubscribe = subscribe((items) => { this.state.items = items; });
33
+ }
34
+
35
+ onDestroy() {
36
+ this.unsubscribe?.();
37
+ }
38
+
39
+ pause() { pauseAll(); }
40
+ resume() { resumeAll(); }
41
+ dismiss(id) { dismissToast(id); }
42
+ }
43
+
44
+ <if(state.items.length)>
45
+ <div
46
+ class="sds-toaster"
47
+ role="region"
48
+ aria-label="Notificações"
49
+ on-mouseenter("pause")
50
+ on-mouseleave("resume")
51
+ >
52
+ <!-- `key` on each toast, so a dismissal removes the right node rather than shifting
53
+ the list up underneath the animation. -->
54
+ <for|item| of=state.items>
55
+ <sds-toast
56
+ key=item.id
57
+ message=item.message
58
+ feedback=item.feedback
59
+ actionLabel=item.actionLabel
60
+ onAction=item.onAction
61
+ actionDisabled=item.actionDisabled
62
+ actionDisabledTooltip=item.actionDisabledTooltip
63
+ actionDisabledTooltipPosition=item.actionDisabledTooltipPosition
64
+ showDismiss=item.showDismiss
65
+ leaving=item.leaving
66
+ onDismiss=() => component.dismiss(item.id)
67
+ />
68
+ </for>
69
+ </div>
70
+ </if>
@@ -0,0 +1,169 @@
1
+ /* SDS Toggle.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Toggle/Toggle.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+
5
+ Rules removed with the brand families they targeted:
6
+ [data-color-scheme="lya"] .sds-toggle__item--active
7
+ [data-color-scheme="lya"] .sds-toggle__item--active:hover:not(:disabled)
8
+ [data-color-scheme="lya"] .sds-toggle:hover:not(.sds-toggle--disabled)
9
+ *//* ── Toggle — Segmented control ──────────────────────────────────
10
+ Container: gray track with pill radius
11
+ Items: no individual borders; active = blue filled pill
12
+ ─────────────────────────────────────────────────────────────── */
13
+
14
+ .sds-toggle {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ gap: var(--spacing-0-5);
18
+ height: 40px;
19
+ padding: var(--spacing-1);
20
+ background: var(--component-bg-color);
21
+ border: 1px solid var(--border-interactive-color);
22
+ /* soft, the install default — see the note by the scheme overrides below */
23
+ border-radius: 11px;
24
+ transition: border-color 120ms ease, background-color 120ms ease;
25
+ }
26
+ .sds-toggle--fill { width: 100%; }
27
+ .sds-toggle--fill .sds-toggle__item { flex: 1; }
28
+ .sds-toggle:hover:not(.sds-toggle--disabled) {
29
+ border-color: var(--color-primary-50-solid);
30
+ }
31
+ .sds-toggle--disabled {
32
+ background: var(--color-neutral-20);
33
+ border-color: var(--border-static-color);
34
+ cursor: not-allowed;
35
+ }
36
+ .sds-toggle--disabled .sds-toggle__item {
37
+ color: var(--color-neutral-40);
38
+ }
39
+ [data-theme="dark"] .sds-toggle--disabled {
40
+ background: var(--color-neutral-30);
41
+ border-color: var(--border-interactive-color);
42
+ }
43
+ [data-theme="dark"] .sds-toggle--disabled .sds-toggle__item--active {
44
+ background: var(--color-neutral-40);
45
+ color: var(--component-bg-color);
46
+ }
47
+ /* ── Item (default = 40px track height) ────────────────────────── */
48
+ .sds-toggle__item {
49
+ display: inline-flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ gap: 6px;
53
+ height: 32px;
54
+ padding: 0 var(--spacing-3);
55
+ border: none;
56
+ background: transparent;
57
+ /* soft, the install default — see the note by the scheme overrides below */
58
+ border-radius: 9px;
59
+ font-family: var(--font-family-base);
60
+ font-size: var(--font-size-body-md); /* 16px */
61
+ font-weight: var(--font-weight-regular);
62
+ color: var(--color-neutral-80);
63
+ cursor: pointer;
64
+ transition: background 120ms ease, color 120ms ease;
65
+ white-space: nowrap;
66
+ user-select: none;
67
+ outline: none;
68
+ }
69
+ .sds-toggle__item:hover:not(:disabled) {
70
+ background: var(--color-neutral-10);
71
+ color: var(--color-neutral-10-on);
72
+ }
73
+ /* ── Active item ────────────────────────────────────────────────── */
74
+ .sds-toggle__item--active {
75
+ background: var(--color-primary-50);
76
+ color: var(--color-on-primary);
77
+ }
78
+ .sds-toggle__item--active:hover:not(:disabled) {
79
+ background: var(--color-primary-60);
80
+ color: var(--color-on-primary);
81
+ box-shadow: none;
82
+ }
83
+ /* ── Disabled individual item (item.disabled in an enabled group) ── */
84
+ .sds-toggle__item:disabled {
85
+ cursor: not-allowed;
86
+ color: var(--color-neutral-40);
87
+ }
88
+ /* ── Active item in disabled state — neutral instead of primary ─── */
89
+ .sds-toggle--disabled .sds-toggle__item--active {
90
+ background: var(--color-neutral-40);
91
+ color: var(--color-white);
92
+ }
93
+ /* ── Focus ring ─────────────────────────────────────────────────── */
94
+ .sds-toggle__item:focus-visible {
95
+ outline: none;
96
+ box-shadow: var(--focus-ring);
97
+ }
98
+ /* ── Icon slot ──────────────────────────────────────────────────── */
99
+ .sds-toggle__icon {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ flex-shrink: 0;
104
+ font-size: 24px;
105
+ }
106
+ /* ── Icon-only: square padding ──────────────────────────────────── */
107
+ .sds-toggle--icon-only .sds-toggle__item {
108
+ padding: 0 var(--spacing-2);
109
+ min-width: 32px;
110
+ }
111
+ /* ── Corner Radius Scheme overrides ──
112
+ Carried from KDLS unchanged. The numbers are a proportion of each element's own height,
113
+ which is what holds the container and the item to the same shape at every tier:
114
+
115
+ container 40px soft 11px (0.275) medium 15px (0.375)
116
+ item 32px soft 9px (0.28) medium 12px (0.375)
117
+
118
+ See Pill.css — the container is the same 40px as a pill and takes the same numbers, and
119
+ the note there explains why the base values below are soft's rather than a capsule's:
120
+ an install with no data-radius attribute gets the default scheme, and SDS's default is
121
+ soft. `rounded` is explicit for the same reason — it can no longer fall through. */
122
+ [data-radius="flat"] .sds-toggle { border-radius: 0; }
123
+ [data-radius="flat"] .sds-toggle__item { border-radius: 0; }
124
+ [data-radius="soft"] .sds-toggle { border-radius: 11px; }
125
+ [data-radius="soft"] .sds-toggle__item { border-radius: 9px; }
126
+ [data-radius="medium"] .sds-toggle { border-radius: 15px; }
127
+ [data-radius="rounded"] .sds-toggle { border-radius: var(--radius-full); }
128
+ /* Item is 32px tall — 16px would clamp to a full pill on its own, erasing the softened look */
129
+ [data-radius="medium"] .sds-toggle__item { border-radius: 12px; }
130
+ [data-radius="rounded"] .sds-toggle__item { border-radius: var(--radius-full); }
131
+ /* ── Dark mode ──────────────────────────────────────────────────── */
132
+ /* ── The native radios ─────────────────────────────────────────────────────────
133
+ A segmented control is a radio group. KDLS built it from divs with a click handler, so
134
+ arrow keys did nothing, the value never reached a form, and a screen reader read three
135
+ unrelated words instead of "Mapa, 2 de 3".
136
+
137
+ The inputs are hidden with opacity, not display:none — a hidden input cannot be focused,
138
+ and the whole point of using radios is the keyboard. */
139
+ /* A fieldset needs its browser chrome reset before it can be laid out — but only the
140
+ chrome. `padding: 0` here also erased the 4px inset the base rule gives the track, and
141
+ the segments ran into the container's own border. */
142
+ .sds-toggle {
143
+ border: 1px solid var(--border-interactive-color);
144
+ margin: 0;
145
+ min-inline-size: 0;
146
+ }
147
+
148
+ .sds-toggle__legend {
149
+ position: absolute;
150
+ width: 1px;
151
+ height: 1px;
152
+ overflow: hidden;
153
+ clip-path: inset(50%);
154
+ white-space: nowrap;
155
+ }
156
+
157
+ .sds-toggle__input {
158
+ position: absolute;
159
+ width: 1px;
160
+ height: 1px;
161
+ margin: 0;
162
+ opacity: 0;
163
+ pointer-events: none;
164
+ }
165
+
166
+ .sds-toggle__item:has(.sds-toggle__input:focus-visible) {
167
+ box-shadow: var(--focus-ring);
168
+ }
169
+
@@ -0,0 +1,93 @@
1
+ <!-- sds-toggle — ported from kdls-design-system v2.5.1 (0f3edf8) components/Toggle/Toggle.tsx.
2
+
3
+ A segmented control: several options, one chosen. That is a radio group, and it is built
4
+ as one — an <input type="radio"> per segment inside a <fieldset>.
5
+
6
+ KDLS built it from divs with useState and a click handler. The radios give what that could
7
+ not: arrow keys move between the segments, the browser enforces one-of-N, the value goes
8
+ into a form on its own, and a screen reader announces "Mapa, 2 de 3" instead of reading
9
+ three unrelated words. None of it needs JavaScript, so the component holds no state.
10
+
11
+ The fieldset needs a name. KDLS had none — a segmented control with no legend is three
12
+ buttons a screen reader cannot connect to each other. -->
13
+
14
+ class {
15
+ /** The radios are native, so the change event already bubbles — this names it. */
16
+ handleChange(event) {
17
+ if (event.target.type !== "radio") return;
18
+ this.input.onChange?.(event.target.value);
19
+ }
20
+ }
21
+
22
+ $ {
23
+ const {
24
+ items = [],
25
+ // `component.id`, not a random string. Marko gives every component an id that is the
26
+ // same on the server and in the browser; a random one drawn inside the render block is
27
+ // a different value on each side, so every `for` and `aria-*` pointing at it resolved
28
+ // to nothing after hydration — and changed again on every re-render.
29
+ name = `sds-toggle-${component.id}`,
30
+ value,
31
+ defaultValue,
32
+ label,
33
+ width = "hug",
34
+ disabled = false,
35
+ class: extraClass = "",
36
+ } = input;
37
+
38
+ if (items.length < 2) {
39
+ throw new Error("sds-toggle needs at least two items — one option is not a choice.");
40
+ }
41
+ if (!label) {
42
+ throw new Error("sds-toggle needs a `label` naming what is being chosen.");
43
+ }
44
+ if (width !== "hug" && width !== "fill") {
45
+ throw new Error(`Unknown toggle width: "${width}". Allowed: hug, fill`);
46
+ }
47
+
48
+ const renderable = (v) => (v && v.renderBody) || v;
49
+ const selected = value ?? defaultValue ?? items[0].value;
50
+ const iconOnly = items.every((item) => !item.label);
51
+
52
+ const classes = [
53
+ "sds-toggle",
54
+ `sds-toggle--${width}`,
55
+ disabled ? "sds-toggle--disabled" : "",
56
+ iconOnly ? "sds-toggle--icon-only" : "",
57
+ extraClass,
58
+ ].filter(Boolean).join(" ");
59
+ }
60
+
61
+ <fieldset class=classes on-change("handleChange")>
62
+ <legend class="sds-toggle__legend">${label}</legend>
63
+
64
+ <for|item| of=items>
65
+ <label class=`sds-toggle__item ${item.value === selected ? "sds-toggle__item--active" : ""}`.trim()>
66
+ <input
67
+ class="sds-toggle__input"
68
+ type="radio"
69
+ name=name
70
+ value=item.value
71
+ checked=(item.value === selected || null)
72
+ disabled=((disabled || item.disabled) || null)
73
+ />
74
+ <if(item.iconLeft)>
75
+ <span class="sds-toggle__icon" aria-hidden="true">
76
+ <sds-icon size="sm"><${renderable(item.iconLeft)}/></sds-icon>
77
+ </span>
78
+ </if>
79
+ <if(item.label)>
80
+ ${item.label}
81
+ </if>
82
+ <else>
83
+ <!-- Icon-only segments still need a name; the label is visually hidden. -->
84
+ <span class="sds-visually-hidden">${item.ariaLabel ?? item.value}</span>
85
+ </else>
86
+ <if(item.iconRight)>
87
+ <span class="sds-toggle__icon" aria-hidden="true">
88
+ <sds-icon size="sm"><${renderable(item.iconRight)}/></sds-icon>
89
+ </span>
90
+ </if>
91
+ </label>
92
+ </for>
93
+ </fieldset>
@@ -0,0 +1,115 @@
1
+ /* SDS Tooltip.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Tooltip/Tooltip.css.
3
+
4
+ The arrow is keyed off the panel's data-placement rather than a class built from the
5
+ placement name, for the same reason as Popover: KDLS spells the placement twice, once in
6
+ JavaScript and once in CSS, and the two can drift. Here sds-floating writes the placement
7
+ it actually settled on and the rules read that. */
8
+
9
+ .sds-tooltip-wrapper {
10
+ position: relative;
11
+ display: inline-flex;
12
+ align-items: center;
13
+ cursor: help;
14
+ }
15
+
16
+ .sds-tooltip {
17
+ width: max-content;
18
+ max-width: 240px;
19
+ padding: var(--spacing-1-5) var(--spacing-2-5);
20
+ background: var(--color-neutral-90);
21
+ color: var(--color-neutral-90-on);
22
+ border-radius: var(--radius-sm);
23
+ font-family: var(--font-family-base);
24
+ font-size: var(--font-size-label-sm);
25
+ line-height: var(--lh-label-sm);
26
+ /* The tooltip must never be the thing the pointer is over: a tooltip that can be hovered
27
+ keeps itself open and blocks what is underneath it. */
28
+ pointer-events: none;
29
+ white-space: normal;
30
+ animation: sds-tooltip-fade-in 120ms ease;
31
+ }
32
+
33
+ @keyframes sds-tooltip-fade-in {
34
+ from { opacity: 0; }
35
+ to { opacity: 1; }
36
+ }
37
+
38
+ /* ── Arrow ──
39
+ A CSS triangle: a zero-size box with one border side coloured. Which side depends on
40
+ where the panel ended up, which is what data-placement carries. */
41
+ .sds-tooltip::before {
42
+ content: '';
43
+ position: absolute;
44
+ width: 0;
45
+ height: 0;
46
+ border-style: solid;
47
+ }
48
+
49
+ /* Panel above the trigger → the arrow points down, from the panel's bottom edge. */
50
+ [data-placement^="top"] .sds-tooltip::before {
51
+ bottom: -6px;
52
+ border-width: 6px 6px 0 6px;
53
+ border-color: var(--color-neutral-90) transparent transparent transparent;
54
+ }
55
+
56
+ /* Panel below → points up. */
57
+ [data-placement^="bottom"] .sds-tooltip::before {
58
+ top: -6px;
59
+ border-width: 0 6px 6px 6px;
60
+ border-color: transparent transparent var(--color-neutral-90) transparent;
61
+ }
62
+
63
+ /* Panel to the left → points right. */
64
+ [data-placement^="left"] .sds-tooltip::before {
65
+ right: -6px;
66
+ border-width: 6px 0 6px 6px;
67
+ border-color: transparent transparent transparent var(--color-neutral-90);
68
+ }
69
+
70
+ /* Panel to the right → points left. */
71
+ [data-placement^="right"] .sds-tooltip::before {
72
+ left: -6px;
73
+ border-width: 6px 6px 6px 0;
74
+ border-color: transparent var(--color-neutral-90) transparent transparent;
75
+ }
76
+
77
+ /* Where along that edge the arrow sits. Exact matches, so the centred case — the one with
78
+ no corner word — does not also catch the cornered ones. */
79
+ [data-placement="top"] .sds-tooltip::before,
80
+ [data-placement="bottom"] .sds-tooltip::before { left: 50%; transform: translateX(-50%); }
81
+
82
+ [data-placement="topLeft"] .sds-tooltip::before,
83
+ [data-placement="bottomLeft"] .sds-tooltip::before { left: var(--spacing-2); }
84
+
85
+ [data-placement="topRight"] .sds-tooltip::before,
86
+ [data-placement="bottomRight"] .sds-tooltip::before { right: var(--spacing-2); }
87
+
88
+ [data-placement="left"] .sds-tooltip::before,
89
+ [data-placement="right"] .sds-tooltip::before { top: 50%; transform: translateY(-50%); }
90
+
91
+ [data-placement="leftTop"] .sds-tooltip::before,
92
+ [data-placement="rightTop"] .sds-tooltip::before { top: var(--spacing-2); }
93
+
94
+ [data-placement="leftBottom"] .sds-tooltip::before,
95
+ [data-placement="rightBottom"] .sds-tooltip::before { bottom: var(--spacing-2); }
96
+
97
+ /* The default trigger: the design system's own info affordance. KDLS renders a bare Icon
98
+ here, which no keyboard can reach — so a tooltip with no custom trigger was mouse-only.
99
+ This is a real button with the browser's chrome taken off. */
100
+ .sds-tooltip-wrapper__trigger {
101
+ appearance: none;
102
+ border: 0;
103
+ background: none;
104
+ padding: 0;
105
+ display: inline-flex;
106
+ align-items: center;
107
+ color: var(--color-neutral-60);
108
+ cursor: help;
109
+ border-radius: var(--radius-xs);
110
+ }
111
+
112
+ .sds-tooltip-wrapper__trigger:focus-visible {
113
+ outline: var(--focus-ring-width) solid var(--focus-stroke-color);
114
+ outline-offset: 2px;
115
+ }
@@ -0,0 +1,77 @@
1
+ <!-- sds-tooltip — ported from kdls-design-system v2.5.1 (0f3edf8) components/Tooltip/Tooltip.tsx.
2
+
3
+ A short label that appears on hover or focus. It is sds-floating with a dark panel and
4
+ an arrow, opened by hover and focus together — KDLS wrote the same show/hide, the same
5
+ 100ms delays and the same positioning maths inline, before Floating existed to hold it.
6
+
7
+ Three divergences, all of them defects rather than choices:
8
+
9
+ KDLS's default trigger is a bare <Icon> — a <span> with no tabindex. A tooltip with no
10
+ custom trigger is therefore unreachable by keyboard, which is most of the tooltips in a
11
+ product. Here it is a real <button>.
12
+
13
+ The panel carries role="tooltip" and nothing points at it, so a screen reader never
14
+ reads it: role="tooltip" describes what an element is, it does not announce it. The
15
+ trigger now carries aria-describedby, and the text is also in a permanently rendered
16
+ visually-hidden node so it is in the accessibility tree whether or not the panel is
17
+ open. When the trigger is the caller's own markup, put aria-describedby on the
18
+ focusable thing inside it — the id is `${id}-text`, and the component exposes `id`
19
+ for exactly that.
20
+
21
+ Touch is still open, and it is not a small gap: there is no hover on a phone, so a
22
+ tooltip whose only triggers are hover and focus does not exist for 62% of this
23
+ product's traffic. Tapping the default trigger does open it, because a tap focuses a
24
+ button — but a custom trigger that is not focusable stays silent. The real answer is
25
+ probably a press-and-hold or a bottom sheet, and that is Rafael's decision, recorded
26
+ here rather than guessed. -->
27
+
28
+ import { assertPlacement } from "../Floating/floating-position.js";
29
+ import infoIcon from "../../icons/generated/fluent-info-regular.marko";
30
+
31
+ $ {
32
+ const {
33
+ content,
34
+ placement = "top",
35
+ // `component.id`, not a random string. Marko gives every component an id that is the
36
+ // same on the server and in the browser; a random one drawn inside the render block is
37
+ // a different value on each side, so every `for` and `aria-*` pointing at it resolved
38
+ // to nothing after hydration — and changed again on every re-render.
39
+ id = `sds-tooltip-${component.id}`,
40
+ class: extraClass = "",
41
+ } = input;
42
+
43
+ if (!content) throw new Error("sds-tooltip needs `content` — the text it shows.");
44
+ assertPlacement(placement);
45
+
46
+ const textId = `${id}-text`;
47
+ const GAP = 8;
48
+ }
49
+
50
+ <span class=`sds-tooltip-wrapper ${extraClass}`.trim()>
51
+ <sds-floating
52
+ placement=placement
53
+ trigger=["hover", "focus"]
54
+ gap=GAP
55
+ zIndex="var(--z-notice)"
56
+ mouseEnterDelay=100
57
+ mouseLeaveDelay=100
58
+ >
59
+ <@content>
60
+ <span class="sds-tooltip" role="tooltip">${content}</span>
61
+ </@content>
62
+
63
+ <if(input.renderBody)>
64
+ <${input.renderBody}/>
65
+ </if>
66
+ <else>
67
+ <button type="button" class="sds-tooltip-wrapper__trigger" aria-describedby=textId>
68
+ <sds-icon size="sm"><${infoIcon}/></sds-icon>
69
+ </button>
70
+ </else>
71
+ </sds-floating>
72
+
73
+ <!-- The description lives here, always, not in the panel: a node that appears at the
74
+ same moment it is referenced is one a screen reader has already read past. The panel
75
+ is the visual half of the same text. -->
76
+ <span class="sds-visually-hidden" id=textId>${content}</span>
77
+ </span>