@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,124 @@
1
+ <!-- sds-card — ported from kdls-design-system v2.5.1 (0f3edf8) components/Card/Card.tsx.
2
+
3
+ The shell. Header, Body, Footer, Section, InfoRow, InfoPair and Collapsible are sibling
4
+ tags that nest inside it as ordinary children: the card has nothing to tell them, so
5
+ there is nothing for it to intercept.
6
+
7
+ No state of its own. KDLS's Card held useState for the collapsible section — that state
8
+ moved with it, into sds-card-collapsible, so a card with no collapsible section costs
9
+ nothing. A property listing renders twenty of these.
10
+
11
+ It is a <button> when it has a click handler, rather than a div with a handler and no
12
+ keyboard path. -->
13
+
14
+ import { assertCardSize } from "./card-variants.js";
15
+
16
+ $ {
17
+ const {
18
+ cover,
19
+ coverWidth,
20
+ coverHeight,
21
+ coverBadges,
22
+ horizontal = false,
23
+ size = "md",
24
+ bordered = true,
25
+ hoverable = false,
26
+ onClick,
27
+ clickLabel,
28
+ loading = false,
29
+ loadingRows = 3,
30
+ loadingTitleRows = 2,
31
+ class: extraClass = "",
32
+ } = input;
33
+
34
+ assertCardSize(size);
35
+
36
+ const renderable = (value) => (value && value.renderBody) || value;
37
+ const interactive = Boolean(onClick);
38
+
39
+ // The hit area has no text of its own — it is an empty box over the card — so it cannot
40
+ // borrow a name from its contents the way a normal button does. A control announced as
41
+ // just "button" tells a screen-reader user nothing about which card they are on, and in
42
+ // a listing of twenty cards that is twenty identical buttons. Same rule the icon button
43
+ // already applies.
44
+ if (interactive && !clickLabel) {
45
+ throw new Error("sds-card needs a `clickLabel` when it takes an `onClick` — the click target covers the card and has no text to be named by.");
46
+ }
47
+
48
+ const classes = [
49
+ "sds-card",
50
+ size === "sm" ? "sds-card--sm" : "",
51
+ horizontal ? "sds-card--horizontal" : "",
52
+ bordered ? "sds-card--bordered" : "",
53
+ hoverable || interactive ? "sds-card--hoverable" : "",
54
+ extraClass,
55
+ ].filter(Boolean).join(" ");
56
+
57
+ // Each dimension belongs to the orientation that can honour it, which is the gate KDLS
58
+ // applies and the port dropped. A horizontal card's cover is a fixed-width column beside
59
+ // the content; a vertical one's is a fixed-height band above it. Applying both meant a
60
+ // `coverHeight` written for the vertical case silently squashed the horizontal one.
61
+ const coverStyle = [
62
+ horizontal && coverWidth ? `width: ${coverWidth}px` : "",
63
+ !horizontal && coverHeight ? `height: ${coverHeight}px` : "",
64
+ ].filter(Boolean).join("; ") || null;
65
+ }
66
+
67
+ <macro|{ }| name="inner">
68
+ // `cover`, not `cover || loading`. A loading card with no cover drew a cover-shaped
69
+ // skeleton block, promising a picture that was never going to arrive — and once loaded
70
+ // the card jumped as that block disappeared. KDLS gates on the cover alone.
71
+ <if(cover)>
72
+ <div class="sds-card__cover" style=coverStyle>
73
+ <if(loading)>
74
+ <sds-skeleton-image fill/>
75
+ </if>
76
+ <else>
77
+ <${renderable(cover)}/>
78
+ </else>
79
+ <if(coverBadges && !loading)>
80
+ <div class="sds-card__cover-badges"><${renderable(coverBadges)}/></div>
81
+ </if>
82
+ </div>
83
+ </if>
84
+
85
+ <div class="sds-card__content">
86
+ <if(loading)>
87
+ // `loadingTitleRows` is a count, and it was being spent as a yes/no: 1, 2 and 10 all
88
+ // drew the same single title line, and the default of 2 lost its second row. The
89
+ // first row is the title; whatever remains are lines under it.
90
+ <sds-skeleton
91
+ title=(loadingTitleRows >= 1)
92
+ rows=(Math.max(0, loadingTitleRows - 1) + loadingRows)
93
+ label="Carregando"
94
+ />
95
+ </if>
96
+ <else>
97
+ <${input.renderBody}/>
98
+ </else>
99
+ </div>
100
+ </macro>
101
+
102
+ <div class=classes aria-busy=(loading ? "true" : null)>
103
+ <if(interactive)>
104
+ // The click target covers the card and sits BESIDE the content, never around it.
105
+ //
106
+ // It used to be a <button> wrapping everything, which is invalid HTML the moment the
107
+ // card holds a button or a link of its own — and a card in a listing almost always
108
+ // does. In the browser it does not merely warn: the parser closes the outer button at
109
+ // the inner one and the inner control becomes the card's SIBLING. Rendered on the
110
+ // server and read back, a card with one "Ver imóvel" button came out as an empty card
111
+ // followed by a loose button. Proven in card.test.js.
112
+ //
113
+ // So the card is a plain <div> again and this covers it instead. The content is
114
+ // painted above the hit area, and interactive children above that, so clicking the
115
+ // card works everywhere except on a control that had its own answer.
116
+ <button
117
+ type="button"
118
+ class="sds-card__hit"
119
+ aria-label=clickLabel
120
+ on-click(onClick)
121
+ />
122
+ </if>
123
+ <inner/>
124
+ </div>
@@ -0,0 +1,249 @@
1
+ /* SDS Checkbox.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Checkbox/Checkbox.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-checkbox--indeterminate .sds-checkbox__box
7
+ [data-color-scheme="lya"] .sds-checkbox--indeterminate:active:not(.sds-checkbox--disabled) .sds-checkbox__box
8
+ [data-color-scheme="lya"] .sds-checkbox:active:not(.sds-checkbox--disabled):not(.sds-checkbox--checked):not(.sds-checkbox--indeterminate) .sds-checkbox__box
9
+ */.sds-checkbox {
10
+ display: inline-flex;
11
+ /* A column, because the hint is a sibling of the clickable row rather than part of it —
12
+ the row is `__control` and it is still a row. This was `align-items: center` on a flex
13
+ row, so a hint sat BESIDE the label on the same line, reading as a second label. */
14
+ flex-direction: column;
15
+ align-items: flex-start;
16
+ gap: var(--spacing-1);
17
+ cursor: pointer;
18
+ user-select: none;
19
+ font-family: var(--font-family-base);
20
+ outline: none;
21
+ }
22
+ .sds-checkbox--disabled {
23
+ cursor: not-allowed;
24
+ }
25
+ /* Box */
26
+ .sds-checkbox__box {
27
+ flex-shrink: 0;
28
+ display: inline-flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ border: 1px solid var(--border-interactive-color);
32
+ border-radius: var(--radius-xs);
33
+ transition: background 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
34
+ background: var(--component-bg-color);
35
+ }
36
+ /* Size: small — 24px row height */
37
+ .sds-checkbox--sm {
38
+ min-height: 24px;
39
+ --checkbox-box-size: 24px;
40
+ }
41
+ .sds-checkbox--sm .sds-checkbox__box {
42
+ width: 24px;
43
+ height: 24px;
44
+ }
45
+ /* Size: medium — 32px row height */
46
+ .sds-checkbox--md {
47
+ min-height: 32px;
48
+ --checkbox-box-size: 32px;
49
+ }
50
+ .sds-checkbox--md .sds-checkbox__box {
51
+ width: 32px;
52
+ height: 32px;
53
+ }
54
+ /* ── Checked: the input says so, not a class ──────────────────────────────────
55
+ `--checked` comes from the `checked` prop, which is fixed at render. The component
56
+ holds no state and does not hydrate, so clicking marked the native input — the browser
57
+ does that — and the box did not fill. `:checked` is the browser's own answer, and it is
58
+ right with no JavaScript at all.
59
+
60
+ The class stays alongside it, so a caller that controls `checked` gets the visual on
61
+ the first render, before the input is touched. */
62
+ /* Unchecked states */
63
+ .sds-checkbox:hover:not(.sds-checkbox--disabled):not(.sds-checkbox--checked):not(.sds-checkbox--indeterminate):not(:has(:checked)) .sds-checkbox__box,
64
+ .sds-checkbox:active:not(.sds-checkbox--disabled):not(.sds-checkbox--checked):not(.sds-checkbox--indeterminate):not(:has(:checked)) .sds-checkbox__box {
65
+ border-color: var(--color-primary-50-solid);
66
+ }
67
+ /* Checked states */
68
+ .sds-checkbox--checked .sds-checkbox__box,
69
+ .sds-checkbox--indeterminate .sds-checkbox__box,
70
+ .sds-checkbox__input:checked ~ .sds-checkbox__box {
71
+ background: var(--color-primary-50);
72
+ border-color: var(--color-primary-50-solid);
73
+ color: var(--color-on-primary);
74
+ }
75
+ .sds-checkbox--checked:hover:not(.sds-checkbox--disabled) .sds-checkbox__box,
76
+ .sds-checkbox--indeterminate:hover:not(.sds-checkbox--disabled) .sds-checkbox__box,
77
+ .sds-checkbox:hover:not(.sds-checkbox--disabled) .sds-checkbox__input:checked ~ .sds-checkbox__box {
78
+ background: var(--color-primary-60);
79
+ border-color: var(--color-primary-60-solid);
80
+ }
81
+ .sds-checkbox--checked:active:not(.sds-checkbox--disabled) .sds-checkbox__box,
82
+ .sds-checkbox--indeterminate:active:not(.sds-checkbox--disabled) .sds-checkbox__box,
83
+ .sds-checkbox:active:not(.sds-checkbox--disabled) .sds-checkbox__input:checked ~ .sds-checkbox__box {
84
+ background: var(--color-primary-60);
85
+ border-color: var(--color-primary-60-solid);
86
+ }
87
+ /* ── Feedback (validation) — box border when unchecked and not indeterminate ── */
88
+ .sds-checkbox--feedback-error:not(.sds-checkbox--checked):not(.sds-checkbox--indeterminate) .sds-checkbox__box { border-color: var(--color-error-50); }
89
+ .sds-checkbox--feedback-alert:not(.sds-checkbox--checked):not(.sds-checkbox--indeterminate) .sds-checkbox__box { border-color: var(--color-alert-50); }
90
+ .sds-checkbox--feedback-success:not(.sds-checkbox--checked):not(.sds-checkbox--indeterminate) .sds-checkbox__box { border-color: var(--color-success-50); }
91
+ /* ── Disabled ── */
92
+ .sds-checkbox--disabled .sds-checkbox__box {
93
+ background: var(--color-neutral-20);
94
+ border-color: var(--border-interactive-color);
95
+ }
96
+ .sds-checkbox--checked.sds-checkbox--disabled .sds-checkbox__box,
97
+ .sds-checkbox--indeterminate.sds-checkbox--disabled .sds-checkbox__box {
98
+ background: var(--color-neutral-30);
99
+ border-color: var(--border-interactive-color);
100
+ }
101
+ .sds-checkbox--disabled .sds-checkbox__label {
102
+ color: var(--color-neutral-40);
103
+ }
104
+ .sds-checkbox__label--required::after {
105
+ content: ' *';
106
+ color: var(--color-error-50);
107
+ }
108
+ /* Focus ring */
109
+ .sds-checkbox:focus-visible .sds-checkbox__box {
110
+ border-color: var(--color-primary-50-solid);
111
+ box-shadow: var(--focus-ring);
112
+ }
113
+ /* Checkmark icon */
114
+ .sds-checkbox__icon {
115
+ width: 100%;
116
+ height: 100%;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ opacity: 0;
121
+ color: var(--color-on-primary);
122
+ transition: opacity 120ms ease;
123
+ }
124
+ .sds-checkbox--checked .sds-checkbox__icon,
125
+ .sds-checkbox--indeterminate .sds-checkbox__icon,
126
+ .sds-checkbox__input:checked ~ .sds-checkbox__box .sds-checkbox__icon {
127
+ opacity: 1;
128
+ }
129
+ /* Container */
130
+ .sds-checkbox-container {
131
+ display: flex;
132
+ align-items: center;
133
+ height: 56px;
134
+ padding: 0 var(--spacing-4);
135
+ border: 1px solid var(--border-interactive-color);
136
+ border-radius: var(--radius-md);
137
+ background: var(--component-bg-color);
138
+ }
139
+ .sds-checkbox-container--disabled {
140
+ border-color: var(--border-static-color);
141
+ }
142
+ [data-theme="dark"] .sds-checkbox-container {
143
+ border-color: var(--color-neutral-60);
144
+ }
145
+ /* Hint */
146
+ .sds-checkbox-wrapper {
147
+ display: flex;
148
+ flex-direction: column;
149
+ align-items: flex-start;
150
+ gap: var(--spacing-1);
151
+ }
152
+ /* Width modifier */
153
+ .sds-checkbox-wrapper--fill { width: 100%; }
154
+ .sds-checkbox-wrapper--fill > .sds-checkbox-container { width: 100%; }
155
+ /* Container variant stretches to fill available width */
156
+ .sds-checkbox-wrapper > .sds-checkbox-container {
157
+ align-self: stretch;
158
+ }
159
+ /* Label position: left — box moves to the right */
160
+ .sds-checkbox--label-left {
161
+ flex-direction: row-reverse;
162
+ }
163
+ .sds-checkbox-container .sds-checkbox--label-left {
164
+ width: 100%;
165
+ justify-content: space-between;
166
+ }
167
+ /* Content column (label + hint stacked, no-container case) */
168
+ /* Divergence: the clickable row is `__control`, and `__content` is gone.
169
+
170
+ KDLS puts the box and the label straight inside the root and uses `__content` for a
171
+ different job — it appears only when there is a hint, and stacks the label above it.
172
+ This port moved the hint out of the label (a hint is not part of the thing you click),
173
+ so the element that needed a name was the row, and it inherited the KDLS name along
174
+ with the KDLS rule: `flex-direction: column`. The box sat on top of its own label in
175
+ every checkbox and radio in the product. Rafael found it on the portal. */
176
+ .sds-checkbox__control {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: var(--spacing-2);
180
+ }
181
+ /* Hint.
182
+
183
+ Secondary by default: it explains the label, and when it reads in the same ink at the
184
+ same weight it competes with it instead. The template used to pass `color="inherit"`,
185
+ so it took the page's body colour and the feedback classes below never matched anything
186
+ — they were written at the port and have been dead since.
187
+
188
+ Indented past the box, so it lines up with the label rather than with the control. */
189
+ .sds-checkbox__hint {
190
+ padding-left: calc(var(--checkbox-box-size, 24px) + var(--spacing-2));
191
+ color: var(--color-neutral-60);
192
+ }
193
+ .sds-checkbox--label-left .sds-checkbox__hint {
194
+ padding-left: 0;
195
+ }
196
+ .sds-checkbox-container + .sds-checkbox__hint {
197
+ padding-left: var(--spacing-4);
198
+ }
199
+ .sds-checkbox__hint.sds-checkbox__hint--success { color: var(--color-success-50); }
200
+ .sds-checkbox__hint.sds-checkbox__hint--alert { color: var(--color-alert-50); }
201
+ .sds-checkbox__hint.sds-checkbox__hint--error { color: var(--color-error-50); }
202
+ .sds-checkbox__hint.sds-checkbox__hint--disabled {
203
+ color: var(--color-neutral-40);
204
+ }
205
+ [data-theme="dark"] .sds-checkbox-container--disabled {
206
+ border-color: var(--border-interactive-color);
207
+ }
208
+ /* ── The native input ──────────────────────────────────────────────────────────
209
+ SDS renders a real <input type="checkbox">, which KDLS did not: it drew a
210
+ <div role="checkbox"> and bolted a hidden <input> alongside so a form could still
211
+ submit the value. The native element brings keyboard activation, focus, the disabled
212
+ state, label association and form participation — all of which KDLS rebuilt by hand.
213
+
214
+ The input itself is visually hidden rather than display:none, because a hidden input
215
+ cannot be focused and an unfocusable checkbox cannot be reached by keyboard. The box
216
+ the visitor sees is .sds-checkbox__box, drawn beside it and driven by :checked. */
217
+ .sds-checkbox__input {
218
+ position: absolute;
219
+ width: 1px;
220
+ height: 1px;
221
+ margin: 0;
222
+ opacity: 0;
223
+ pointer-events: none;
224
+ }
225
+
226
+ .sds-checkbox__input:checked ~ .sds-checkbox__box,
227
+ .sds-checkbox--indeterminate .sds-checkbox__box {
228
+ background: var(--color-primary-50);
229
+ border-color: var(--color-primary-50-solid);
230
+ }
231
+
232
+ .sds-checkbox__input:focus-visible ~ .sds-checkbox__box {
233
+ box-shadow: var(--focus-ring);
234
+ }
235
+
236
+ .sds-checkbox__input:checked ~ .sds-checkbox__box .sds-checkbox__icon,
237
+ .sds-checkbox--indeterminate .sds-checkbox__box .sds-checkbox__icon {
238
+ opacity: 1;
239
+ }
240
+
241
+ .sds-checkbox__icon {
242
+ opacity: 0;
243
+ color: var(--color-on-primary);
244
+ display: inline-flex;
245
+ }
246
+
247
+ /* Indeterminate is drawn from a class rather than the :indeterminate pseudo-class.
248
+ That property can only be set from JavaScript, and this component does not hydrate —
249
+ with appearance:none the browser draws nothing here anyway, so the class is enough. */
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The Checkbox vocabulary.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Checkbox/Checkbox.tsx.
5
+ */
6
+ export const CHECKBOX_SIZES = ['sm', 'md'];
7
+ export const CHECKBOX_FEEDBACKS = ['none', 'success', 'alert', 'error'];
8
+ export const CHECKBOX_WIDTHS = ['hug', 'fill'];
9
+ export const CHECKBOX_LABEL_POSITIONS = ['right', 'left'];
10
+
11
+ function assertOneOf(label, value, allowed) {
12
+ if (!allowed.includes(value)) {
13
+ throw new Error(`Unknown checkbox ${label}: "${value}". Allowed: ${allowed.join(', ')}`);
14
+ }
15
+ return value;
16
+ }
17
+
18
+ export const assertCheckboxSize = (v) => assertOneOf('size', v, CHECKBOX_SIZES);
19
+ export const assertLabelPosition = (v) => assertOneOf('labelPosition', v, CHECKBOX_LABEL_POSITIONS);
20
+ export const assertFeedback = (v) => assertOneOf('feedback', v, CHECKBOX_FEEDBACKS);
21
+ export const assertWidth = (v) => assertOneOf('width', v, CHECKBOX_WIDTHS);
@@ -0,0 +1,128 @@
1
+ <!-- sds-checkbox — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/Checkbox/Checkbox.tsx.
3
+
4
+ A real <input type="checkbox">, which KDLS's was not: it drew a <div role="checkbox"> and
5
+ bolted a hidden <input> alongside so a form could still submit. The native element brings
6
+ keyboard activation, focus, the disabled state, label association and form participation —
7
+ everything KDLS rebuilt by hand — and with appearance:none the box on screen is still
8
+ entirely ours.
9
+
10
+ It does not hydrate, and takes no onChange. The native `change` event bubbles: a form, or
11
+ a Select rendering a list of these, listens once on its own container instead of every
12
+ checkbox carrying a listener. On a page with twenty filter checkboxes that is the
13
+ difference between one listener and twenty serialised components.
14
+
15
+ `indeterminate` is a class, not the DOM property. That property can only be set from
16
+ JavaScript, and since appearance:none means the browser draws nothing here, the class is
17
+ what the visual comes from anyway. -->
18
+
19
+ import { assertCheckboxSize, assertFeedback, assertWidth, assertLabelPosition } from "./checkbox-variants.js";
20
+
21
+ $ {
22
+ const {
23
+ label,
24
+ // A checkbox in a table row needs a name and no visible text — KDLS renders those with
25
+ // no label at all, so a screen reader announces "checkbox" and nothing about which row.
26
+ hideLabel = false,
27
+ size = "sm",
28
+ width = "hug",
29
+ labelPosition = "right",
30
+ checked = false,
31
+ indeterminate = false,
32
+ disabled = false,
33
+ required = false,
34
+ container = false,
35
+ feedback = "none",
36
+ hint,
37
+ name,
38
+ value,
39
+ // `component.id`, not a random string. Marko gives every component an id that is the
40
+ // same on the server and in the browser; a random one drawn inside the render block is
41
+ // a different value on each side, so every `for` and `aria-*` pointing at it resolved
42
+ // to nothing after hydration — and changed again on every re-render.
43
+ id = `sds-checkbox-${component.id}`,
44
+ class: extraClass = "",
45
+ } = input;
46
+
47
+ assertLabelPosition(labelPosition);
48
+
49
+ assertCheckboxSize(size);
50
+ assertFeedback(feedback);
51
+ assertWidth(width);
52
+
53
+ const hintId = hint ? `${id}-hint` : null;
54
+
55
+ const classes = [
56
+ "sds-checkbox",
57
+ `sds-checkbox--${size}`,
58
+ checked && !indeterminate ? "sds-checkbox--checked" : "",
59
+ indeterminate ? "sds-checkbox--indeterminate" : "",
60
+ disabled ? "sds-checkbox--disabled" : "",
61
+ container ? "sds-checkbox-container" : "",
62
+ width === "fill" ? "sds-checkbox-wrapper--fill" : "",
63
+ labelPosition === "left" ? "sds-checkbox--label-left" : "",
64
+ feedback !== "none" ? `sds-checkbox--feedback-${feedback}` : "",
65
+ extraClass,
66
+ ].filter(Boolean).join(" ");
67
+ }
68
+
69
+ <div class=classes>
70
+ <label class="sds-checkbox__control" for=id>
71
+ <input
72
+ class="sds-checkbox__input"
73
+ type="checkbox"
74
+ id=id
75
+ name=name
76
+ value=value
77
+ checked=(checked || null)
78
+ disabled=(disabled || null)
79
+ required=(required || null)
80
+ aria-checked=(indeterminate ? "mixed" : null)
81
+ aria-describedby=hintId
82
+ // Said to a screen reader, not only drawn. The box turned red and nothing announced
83
+ // that the field was wrong.
84
+ aria-invalid=(feedback === "error" ? "true" : null)
85
+ />
86
+
87
+ <span class="sds-checkbox__box" aria-hidden="true">
88
+ <span class="sds-checkbox__icon">
89
+ <if(indeterminate)>
90
+ <sds-icon size="xs"><fluent-subtract-regular/></sds-icon>
91
+ </if>
92
+ <else>
93
+ <sds-icon size="xs"><fluent-checkmark-regular/></sds-icon>
94
+ </else>
95
+ </span>
96
+ </span>
97
+
98
+ <if(label)>
99
+ <sds-text
100
+ as="span"
101
+ variant="bodyMd"
102
+ color="inherit"
103
+ class=(hideLabel ? "sds-visually-hidden" : "sds-checkbox__label")
104
+ >
105
+ ${label}
106
+ </sds-text>
107
+ </if>
108
+ </label>
109
+
110
+ <if(hint)>
111
+ // The feedback modifier is what colours it, and it was never written: the hint carried
112
+ // `color="inherit"` and took the page's ink, so an error message read like an ordinary
113
+ // note. The rules for `--error`, `--alert`, `--success` and `--disabled` have been in
114
+ // the stylesheet since the port with nothing to match. Same fault the Input had.
115
+ <sds-text
116
+ as="span"
117
+ id=hintId
118
+ variant="labelRegular"
119
+ // No `color="inherit"`: that writes `sds-text--inherit`, which reads `color: inherit`
120
+ // and beats the hint's own rule on equal specificity by coming later in the sheet.
121
+ // The hint's colour is the hint's to decide — secondary by default, and the feedback
122
+ // colour when there is one.
123
+ class=`sds-checkbox__hint sds-checkbox__hint--${disabled ? "disabled" : feedback}`
124
+ >
125
+ ${hint}
126
+ </sds-text>
127
+ </if>
128
+ </div>