@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,121 @@
1
+ /* SDS Popover.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Popover/Popover.css.
3
+
4
+ One divergence, and it fixes an upstream bug. KDLS builds the arrow's modifier from the
5
+ placement — camelToKebab('bottomLeft') gives `bottom-left` — but its stylesheet defines
6
+ `--bottom-start` and `--bottom-end`. The eight cornered placements therefore match no
7
+ rule at all: the arrow keeps the base rule only, so it sits unpositioned at the panel's
8
+ top-left corner with all four borders on. Only the four centred placements work.
9
+
10
+ SDS writes no modifier on the arrow. The rules key off the panel's data-placement, which
11
+ sds-floating already sets to the placement it actually settled on after the flip. There
12
+ is no second spelling to drift from the first. */
13
+
14
+ /* ── Panel ── */
15
+ .sds-popover__panel {
16
+ position: relative;
17
+ min-width: 120px;
18
+ max-width: 320px;
19
+ background: var(--component-bg-color);
20
+ border: 1px solid var(--border-static-color);
21
+ border-radius: var(--radius-md);
22
+ box-shadow: var(--shadow-drop-md);
23
+ /* Stays visible so the arrow, which hangs outside the box, is not clipped off. */
24
+ overflow: visible;
25
+ }
26
+
27
+ .sds-popover__panel--fill-width {
28
+ max-width: none;
29
+ width: 100%;
30
+ }
31
+
32
+ /* ── Title ── */
33
+ .sds-popover__title {
34
+ padding: var(--spacing-4) var(--spacing-4) var(--spacing-2);
35
+ font-family: var(--font-family-base);
36
+ font-size: var(--font-size-body-md);
37
+ font-weight: var(--font-weight-semibold);
38
+ color: var(--color-neutral-90);
39
+ }
40
+
41
+ /* ── Content ── */
42
+ .sds-popover__content {
43
+ padding: var(--spacing-4);
44
+ font-family: var(--font-family-base);
45
+ font-size: var(--font-size-body-md);
46
+ font-weight: var(--font-weight-regular);
47
+ color: var(--color-neutral-80);
48
+ line-height: var(--lh-body-md);
49
+ }
50
+
51
+ /* ── Arrow ──
52
+ A square rotated 45°, with the two borders that fall against the panel face removed so
53
+ only the point is outlined. Which two depends on the side the panel ended up on, which
54
+ is what data-placement carries.
55
+
56
+ `^=` matches the side and every alignment of it in one rule: the rotation and the edge
57
+ depend on the side alone. The alignment only moves the arrow along that edge, which is
58
+ the second block. */
59
+ .sds-popover__arrow {
60
+ position: absolute;
61
+ width: 8px;
62
+ height: 8px;
63
+ background: var(--component-bg-color);
64
+ border: 1px solid var(--border-static-color);
65
+ transform: rotate(45deg);
66
+ pointer-events: none;
67
+ }
68
+
69
+ /* Panel below the trigger → the arrow points up, from the panel's top edge. */
70
+ [data-placement^="bottom"] .sds-popover__arrow {
71
+ top: -5px;
72
+ border-right: none;
73
+ border-bottom: none;
74
+ }
75
+
76
+ /* Panel above → points down. */
77
+ [data-placement^="top"] .sds-popover__arrow {
78
+ bottom: -5px;
79
+ border-left: none;
80
+ border-top: none;
81
+ }
82
+
83
+ /* Panel to the right → points left. */
84
+ [data-placement^="right"] .sds-popover__arrow {
85
+ left: -5px;
86
+ border-right: none;
87
+ border-top: none;
88
+ }
89
+
90
+ /* Panel to the left → points right. */
91
+ [data-placement^="left"] .sds-popover__arrow {
92
+ right: -5px;
93
+ border-left: none;
94
+ border-bottom: none;
95
+ }
96
+
97
+ /* Where along that edge the arrow sits. Exact matches, so `bottom` does not also catch
98
+ `bottomLeft` — the centred case is the one with no corner word. */
99
+ [data-placement="bottom"] .sds-popover__arrow,
100
+ [data-placement="top"] .sds-popover__arrow { left: 50%; margin-left: -4px; }
101
+
102
+ [data-placement="bottomLeft"] .sds-popover__arrow,
103
+ [data-placement="topLeft"] .sds-popover__arrow { left: var(--spacing-3); }
104
+
105
+ [data-placement="bottomRight"] .sds-popover__arrow,
106
+ [data-placement="topRight"] .sds-popover__arrow { right: var(--spacing-3); }
107
+
108
+ [data-placement="right"] .sds-popover__arrow,
109
+ [data-placement="left"] .sds-popover__arrow { top: 50%; margin-top: -4px; }
110
+
111
+ [data-placement="rightTop"] .sds-popover__arrow,
112
+ [data-placement="leftTop"] .sds-popover__arrow { top: var(--spacing-3); }
113
+
114
+ [data-placement="rightBottom"] .sds-popover__arrow,
115
+ [data-placement="leftBottom"] .sds-popover__arrow { bottom: var(--spacing-3); }
116
+
117
+ /* ── Dark mode ── */
118
+ [data-theme="dark"] .sds-popover__panel,
119
+ [data-theme="dark"] .sds-popover__arrow {
120
+ border-color: var(--border-interactive-color);
121
+ }
@@ -0,0 +1,96 @@
1
+ <!-- sds-popover — ported from kdls-design-system v2.5.1 (0f3edf8) components/Popover/Popover.tsx.
2
+
3
+ sds-floating with a panel drawn on it: a card, an optional title, and an arrow pointing
4
+ back at what opened it. All the positioning, flipping and trigger behaviour is the
5
+ floating component's; this is the skin.
6
+
7
+ It carries no class block. Every piece of state belongs to sds-floating, so a popover
8
+ serialises nothing of its own — the cost is the one floating pays, once.
9
+
10
+ Divergence: the arrow has no modifier class. KDLS builds one from the placement and
11
+ its stylesheet spells the same thing differently, so the eight cornered placements
12
+ match no rule and the arrow lands unpositioned in the panel's corner. Here the rules
13
+ key off the panel's data-placement, which is written by the component that decided it.
14
+ See Popover.css.
15
+
16
+ KDLS's panelClassName, titleClassName and contentClassName are one `panelClass` here.
17
+ Marko has no className, the title and the content are one slot each, and a caller who
18
+ needs to reach either one can target .sds-popover__title and .sds-popover__content
19
+ under their own panelClass — which is what those three attributes were for.
20
+ destroyOnClose is not here for the reason given in sds-floating: it is always true.
21
+
22
+ The gap opens from 4px to 10px when there is an arrow, because the arrow needs
23
+ somewhere to be — carried from KDLS, which had the same two constants.
24
+
25
+ Written as a consumer writes it: the body is the trigger, <@content> is the panel.
26
+ That is sds-floating's shape, unchanged, because a wrapper that renames its slots
27
+ makes the reader learn two vocabularies for one idea.
28
+
29
+ KDLS also wrapped the whole thing in a .kdls-popover element that only repeats what
30
+ .kdls-floating already declares. SDS does not: sds-floating is the wrapper, and a
31
+ class with no rule behind it is the failure this project keeps catching. -->
32
+
33
+ import { assertPlacement, assertTrigger } from "../Floating/floating-position.js";
34
+
35
+ $ {
36
+ const {
37
+ title,
38
+ placement = "top",
39
+ trigger,
40
+ arrow = true,
41
+ matchTriggerWidth = false,
42
+ defaultOpen,
43
+ open,
44
+ onOpenChange,
45
+ zIndex,
46
+ class: extraClass = "",
47
+ panelClass = "",
48
+ } = input;
49
+
50
+ assertPlacement(placement);
51
+ if (trigger !== undefined) assertTrigger(trigger);
52
+
53
+ const GAP = 4;
54
+ const ARROW_GAP = 10;
55
+
56
+ const renderable = (value) => (value && value.renderBody) || value;
57
+
58
+ const panelClasses = [
59
+ "sds-popover__panel",
60
+ matchTriggerWidth ? "sds-popover__panel--fill-width" : "",
61
+ panelClass,
62
+ ].filter(Boolean).join(" ");
63
+ }
64
+
65
+ <sds-floating
66
+ placement=placement
67
+ gap=(arrow ? ARROW_GAP : GAP)
68
+ trigger=trigger
69
+ matchTriggerWidth=matchTriggerWidth
70
+ defaultOpen=defaultOpen
71
+ open=open
72
+ onOpenChange=onOpenChange
73
+ zIndex=zIndex
74
+ class=extraClass
75
+ >
76
+ <@content>
77
+ <div class=panelClasses>
78
+ <if(arrow)>
79
+ <div class="sds-popover__arrow"/>
80
+ </if>
81
+ <if(title)>
82
+ <div class="sds-popover__title">
83
+ <!-- A title arrives as text or as markup. `<${value}/>` with a string renders an
84
+ *element named by that string*, so the two cases cannot share a branch. -->
85
+ <if(typeof title === "string")>${title}</if>
86
+ <else><${renderable(title)}/></else>
87
+ </div>
88
+ </if>
89
+ <div class="sds-popover__content">
90
+ <${input.content}/>
91
+ </div>
92
+ </div>
93
+ </@content>
94
+
95
+ <${input.renderBody}/>
96
+ </sds-floating>
@@ -0,0 +1,230 @@
1
+ /* SDS Radio.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Radio/Radio.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-radio--selected .sds-radio__circle
7
+ [data-color-scheme="lya"] .sds-radio--selected .sds-radio__circle::after
8
+ [data-color-scheme="lya"] .sds-radio--md.sds-radio--selected .sds-radio__circle::after
9
+ [data-color-scheme="lya"] .sds-radio--selected:active:not(.sds-radio--disabled) .sds-radio__circle
10
+ [data-color-scheme="lya"] .sds-radio--selected:focus-visible .sds-radio__circle
11
+ [data-color-scheme="lya"] .sds-radio:active:not(.sds-radio--disabled):not(.sds-radio--selected) .sds-radio__circle
12
+ */.sds-radio {
13
+ display: inline-flex;
14
+ /* A column: the hint is a sibling of the clickable row, not part of it. As a row with
15
+ `align-items: center` the hint sat BESIDE the label and read as a second label. */
16
+ flex-direction: column;
17
+ align-items: flex-start;
18
+ gap: var(--spacing-1);
19
+ cursor: pointer;
20
+ user-select: none;
21
+ font-family: var(--font-family-base);
22
+ outline: none;
23
+ }
24
+ .sds-radio--disabled {
25
+ cursor: not-allowed;
26
+ }
27
+ /* Circle */
28
+ .sds-radio__circle {
29
+ flex-shrink: 0;
30
+ display: inline-flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ border-radius: 50%;
34
+ border: 1px solid var(--border-interactive-color);
35
+ background: var(--component-bg-color);
36
+ transition: border-color 200ms ease, box-shadow 200ms ease;
37
+ }
38
+ /* Size: sm — 24px row height */
39
+ .sds-radio--sm {
40
+ min-height: 24px;
41
+ --radio-circle-size: 24px;
42
+ }
43
+ .sds-radio--sm .sds-radio__circle {
44
+ width: 24px;
45
+ height: 24px;
46
+ }
47
+ /* Size: md — 32px row height */
48
+ .sds-radio--md {
49
+ min-height: 32px;
50
+ --radio-circle-size: 32px;
51
+ }
52
+ .sds-radio--md .sds-radio__circle {
53
+ width: 32px;
54
+ height: 32px;
55
+ }
56
+ /* ── Selected: the input says so, not a class ─────────────────────────────────
57
+ `--selected` came from the `checked` prop, which is fixed at render. The component
58
+ holds no state and does not hydrate, so clicking a radio marked the native input — the
59
+ browser does that — and nothing on screen changed. The `:checked` pseudo-class is the
60
+ browser's own answer to the same question, and it is right without any JavaScript.
61
+
62
+ The class is kept alongside it: a caller that controls `checked` still gets the visual
63
+ on the first render, before the input is touched.
64
+
65
+ The dot is painted rather than drawn with an inset box-shadow. A shadow takes a <color>
66
+ and nothing else, so the mark could never carry a custom gradient — and an inset ring
67
+ also left the middle of the circle showing the page through it. */
68
+ .sds-radio:hover:not(.sds-radio--disabled):not(.sds-radio--selected):not(:has(:checked)) .sds-radio__circle,
69
+ .sds-radio:active:not(.sds-radio--disabled):not(.sds-radio--selected):not(:has(:checked)) .sds-radio__circle {
70
+ border-color: var(--color-primary-50-solid);
71
+ }
72
+
73
+ .sds-radio--selected .sds-radio__circle,
74
+ .sds-radio__input:checked ~ .sds-radio__circle {
75
+ border-color: var(--color-primary-50-solid);
76
+ }
77
+
78
+ .sds-radio--selected:hover:not(.sds-radio--disabled) .sds-radio__circle,
79
+ .sds-radio--selected:active:not(.sds-radio--disabled) .sds-radio__circle,
80
+ .sds-radio:hover:not(.sds-radio--disabled) .sds-radio__input:checked ~ .sds-radio__circle,
81
+ .sds-radio:active:not(.sds-radio--disabled) .sds-radio__input:checked ~ .sds-radio__circle {
82
+ border-color: var(--color-primary-60-solid);
83
+ }
84
+
85
+ /* The dot. Zero-sized and transparent until chosen, so it animates into place. */
86
+ .sds-radio__dot {
87
+ width: 0;
88
+ height: 0;
89
+ border-radius: 50%;
90
+ background: var(--color-primary-50);
91
+ transition: width 150ms ease, height 150ms ease;
92
+ }
93
+
94
+ .sds-radio--selected .sds-radio__dot,
95
+ .sds-radio__input:checked ~ .sds-radio__circle .sds-radio__dot {
96
+ width: 10px;
97
+ height: 10px;
98
+ }
99
+
100
+ .sds-radio--md.sds-radio--selected .sds-radio__dot,
101
+ .sds-radio--md .sds-radio__input:checked ~ .sds-radio__circle .sds-radio__dot {
102
+ width: 14px;
103
+ height: 14px;
104
+ }
105
+
106
+ .sds-radio:hover:not(.sds-radio--disabled) .sds-radio__input:checked ~ .sds-radio__circle .sds-radio__dot,
107
+ .sds-radio--selected:hover:not(.sds-radio--disabled) .sds-radio__dot {
108
+ background: var(--color-primary-60);
109
+ }
110
+
111
+ /* ── Feedback (validation) — circle border when not selected ── */
112
+ .sds-radio--feedback-error:not(.sds-radio--selected) .sds-radio__circle { border-color: var(--color-error-50); }
113
+ .sds-radio--feedback-alert:not(.sds-radio--selected) .sds-radio__circle { border-color: var(--color-alert-50); }
114
+ .sds-radio--feedback-success:not(.sds-radio--selected) .sds-radio__circle { border-color: var(--color-success-50); }
115
+ /* ── Disabled ── */
116
+ .sds-radio--disabled .sds-radio__circle {
117
+ background: var(--color-neutral-20);
118
+ border-color: var(--border-interactive-color);
119
+ }
120
+ .sds-radio--selected.sds-radio--disabled .sds-radio__circle {
121
+ border-color: var(--color-neutral-40);
122
+ }
123
+ .sds-radio--disabled .sds-radio__dot {
124
+ background: var(--color-neutral-40);
125
+ }
126
+ .sds-radio--disabled .sds-radio__label {
127
+ color: var(--color-neutral-40);
128
+ }
129
+ /* Focus ring */
130
+ .sds-radio:focus-visible .sds-radio__circle {
131
+ border-color: var(--color-primary-50-solid);
132
+ box-shadow: var(--focus-ring);
133
+ }
134
+ /* The focus ring is the same whether or not the radio is chosen: the dot draws the
135
+ choice, so the shadow is free to be only the ring. */
136
+ .sds-radio__input:focus-visible ~ .sds-radio__circle {
137
+ border-color: var(--color-primary-50-solid);
138
+ box-shadow: var(--focus-ring);
139
+ }
140
+ /* Container */
141
+ .sds-radio-container {
142
+ display: flex;
143
+ align-items: center;
144
+ height: 56px;
145
+ padding: 0 var(--spacing-4);
146
+ border: 1px solid var(--border-interactive-color);
147
+ border-radius: var(--radius-md);
148
+ background: var(--component-bg-color);
149
+ }
150
+ .sds-radio-container--disabled {
151
+ border-color: var(--border-static-color);
152
+ }
153
+ [data-theme="dark"] .sds-radio-container {
154
+ border-color: var(--color-neutral-60);
155
+ }
156
+ /* Wrapper — vertical stack with tight gap */
157
+ .sds-radio-wrapper {
158
+ display: flex;
159
+ flex-direction: column;
160
+ align-items: flex-start;
161
+ gap: var(--spacing-1);
162
+ }
163
+ /* Width modifier */
164
+ .sds-radio-wrapper--fill { width: 100%; }
165
+ .sds-radio-wrapper--fill > .sds-radio-container { width: 100%; }
166
+ /* Content column (label + hint stacked, no-container case) */
167
+ /* Divergence: the clickable row is `__control`, and `__content` is gone.
168
+
169
+ KDLS puts the box and the label straight inside the root and uses `__content` for a
170
+ different job — it appears only when there is a hint, and stacks the label above it.
171
+ This port moved the hint out of the label (a hint is not part of the thing you click),
172
+ so the element that needed a name was the row, and it inherited the KDLS name along
173
+ with the KDLS rule: `flex-direction: column`. The box sat on top of its own label in
174
+ every checkbox and radio in the product. Rafael found it on the portal. */
175
+ .sds-radio__control {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: var(--spacing-2);
179
+ }
180
+ /* Secondary to the label, and lined up with it rather than with the circle. The template
181
+ passed `color="inherit"`, so the hint took the page's ink and competed with the label
182
+ it exists to explain. */
183
+ .sds-radio__hint {
184
+ padding-left: calc(var(--radio-circle-size, 24px) + var(--spacing-2));
185
+ color: var(--color-neutral-60);
186
+ }
187
+ .sds-radio__hint.sds-radio__hint--success { color: var(--color-success-50); }
188
+ .sds-radio__hint.sds-radio__hint--alert { color: var(--color-alert-50); }
189
+ .sds-radio__hint.sds-radio__hint--error { color: var(--color-error-50); }
190
+ .sds-radio--disabled .sds-radio__hint,
191
+ .sds-radio__hint.sds-radio__hint--disabled { color: var(--color-neutral-40); }
192
+ .sds-radio-container + .sds-radio__hint {
193
+ padding-left: var(--spacing-4);
194
+ }
195
+ [data-theme="dark"] .sds-radio-container--disabled {
196
+ border-color: var(--border-interactive-color);
197
+ }
198
+
199
+ /* ── The native input ──────────────────────────────────────────────────────────
200
+ SDS renders a real <input type="radio">. KDLS drew a <div> with <span>s and no input at
201
+ all, which means a KDLS radio group has no keyboard navigation between options, no form
202
+ participation, and nothing a screen reader can hear as a group.
203
+
204
+ Hidden with opacity rather than display:none, for the same reason as the Checkbox: a
205
+ hidden input cannot be focused, and an unfocusable radio cannot be reached at all. */
206
+ .sds-radio__input {
207
+ position: absolute;
208
+ width: 1px;
209
+ height: 1px;
210
+ margin: 0;
211
+ opacity: 0;
212
+ pointer-events: none;
213
+ }
214
+
215
+ .sds-radio__input:checked ~ .sds-radio__circle {
216
+ border-color: var(--color-primary-50-solid);
217
+ }
218
+
219
+ .sds-radio__input:checked ~ .sds-radio__circle .sds-radio__dot {
220
+ transform: scale(1);
221
+ }
222
+
223
+ .sds-radio__input:focus-visible ~ .sds-radio__circle {
224
+ box-shadow: var(--focus-ring);
225
+ }
226
+
227
+ .sds-radio__dot {
228
+ transform: scale(0);
229
+ transition: transform 120ms ease;
230
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The Radio vocabulary.
3
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Radio/Radio.tsx.
4
+ */
5
+ export const RADIO_SIZES = ['sm', 'md'];
6
+ export const RADIO_FEEDBACKS = ['none', 'success', 'alert', 'error'];
7
+
8
+ function assertOneOf(label, value, allowed) {
9
+ if (!allowed.includes(value)) {
10
+ throw new Error(`Unknown radio ${label}: "${value}". Allowed: ${allowed.join(', ')}`);
11
+ }
12
+ return value;
13
+ }
14
+
15
+ export const assertRadioSize = (v) => assertOneOf('size', v, RADIO_SIZES);
16
+ export const assertRadioFeedback = (v) => assertOneOf('feedback', v, RADIO_FEEDBACKS);
@@ -0,0 +1,93 @@
1
+ <!-- sds-radio — ported from kdls-design-system v2.5.1 (0f3edf8) components/Radio/Radio.tsx.
2
+
3
+ A real <input type="radio">, which KDLS's was not: it drew a <div> with <span>s and no
4
+ input at all, so a radio group had no keyboard navigation, no form participation and no
5
+ grouping a screen reader could hear.
6
+
7
+ The native element brings all of that, and `name` is what makes a set of them one group —
8
+ arrow keys move between them, only one can be chosen, and the browser enforces it. With
9
+ appearance:none the circle on screen is still entirely ours.
10
+
11
+ No state, no onChange listener: the native change event bubbles, so a form or a
12
+ RadioGroup listens once on its container. -->
13
+
14
+ import { assertRadioSize, assertRadioFeedback } from "./radio-variants.js";
15
+
16
+ $ {
17
+ const {
18
+ value,
19
+ label,
20
+ hint,
21
+ name,
22
+ size = "sm",
23
+ checked = false,
24
+ disabled = false,
25
+ required = false,
26
+ feedback = "none",
27
+ // `component.id`, not a random string. Marko gives every component an id that is the
28
+ // same on the server and in the browser; a random one drawn inside the render block is
29
+ // a different value on each side, so every `for` and `aria-*` pointing at it resolved
30
+ // to nothing after hydration — and changed again on every re-render.
31
+ id = `sds-radio-${component.id}`,
32
+ class: extraClass = "",
33
+ } = input;
34
+
35
+ if (value === undefined) throw new Error("sds-radio needs a `value`.");
36
+ if (!label) throw new Error("sds-radio needs a `label`.");
37
+ assertRadioSize(size);
38
+ assertRadioFeedback(feedback);
39
+
40
+ const hintId = hint ? `${id}-hint` : null;
41
+
42
+ const classes = [
43
+ "sds-radio",
44
+ `sds-radio--${size}`,
45
+ checked ? "sds-radio--selected" : "",
46
+ disabled ? "sds-radio--disabled" : "",
47
+ feedback !== "none" ? `sds-radio--feedback-${feedback}` : "",
48
+ extraClass,
49
+ ].filter(Boolean).join(" ");
50
+ }
51
+
52
+ <div class=classes>
53
+ <label class="sds-radio__control" for=id>
54
+ <input
55
+ class="sds-radio__input"
56
+ type="radio"
57
+ id=id
58
+ name=name
59
+ value=value
60
+ checked=(checked || null)
61
+ disabled=(disabled || null)
62
+ required=(required || null)
63
+ aria-describedby=hintId
64
+ // Announced, not only painted.
65
+ aria-invalid=(feedback === "error" ? "true" : null)
66
+ />
67
+
68
+ <span class="sds-radio__circle" aria-hidden="true">
69
+ <span class="sds-radio__dot"/>
70
+ </span>
71
+
72
+ <sds-text as="span" variant="bodyMd" color="inherit" class="sds-radio__label">
73
+ ${label}
74
+ </sds-text>
75
+ </label>
76
+
77
+ <if(hint)>
78
+ // The feedback modifier, which was never written — the hint took the page's ink and an
79
+ // error message read like an ordinary note. Same fault as the Checkbox and the Input.
80
+ <sds-text
81
+ as="span"
82
+ id=hintId
83
+ variant="labelRegular"
84
+ // No `color="inherit"`: that writes `sds-text--inherit`, which reads `color: inherit`
85
+ // and beats the hint's own rule on equal specificity by coming later in the sheet.
86
+ // The hint's colour is the hint's to decide — secondary by default, and the feedback
87
+ // colour when there is one.
88
+ class=`sds-radio__hint sds-radio__hint--${disabled ? "disabled" : feedback}`
89
+ >
90
+ ${hint}
91
+ </sds-text>
92
+ </if>
93
+ </div>
@@ -0,0 +1,32 @@
1
+ /* SDS RadioGroup.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/RadioGroup/RadioGroup.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+ *//* ============================================================
5
+ SDS RadioGroup
6
+ ============================================================ */
7
+
8
+ /* ── Field (label + options + hint stacked) ── */
9
+ .sds-radio-group {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: var(--spacing-4); /* 16px from the label to the radios */
13
+ }
14
+ /* KDLS styled a __label element and an __options column. Neither exists here: the group
15
+ is a <fieldset>, its <legend> names it, and the fieldset itself is the column. */
16
+ /* ── Hint (help / error) ── */
17
+ .sds-radio-group__hint.sds-radio-group__hint--success { color: var(--color-success-50); }
18
+ .sds-radio-group__hint.sds-radio-group__hint--alert { color: var(--color-alert-50); }
19
+ .sds-radio-group__hint.sds-radio-group__hint--error { color: var(--color-error-50); }
20
+ .sds-radio-group__hint.sds-radio-group__hint--disabled { color: var(--color-neutral-40); }
21
+
22
+ /* A fieldset needs its browser chrome reset before it can be laid out. */
23
+ .sds-radio-group {
24
+ border: 0;
25
+ margin: 0;
26
+ padding: 0;
27
+ min-inline-size: 0;
28
+ }
29
+
30
+ /* The legend is hidden with .sds-visually-hidden, from the foundations — hidden, not
31
+ removed: it is what a screen reader announces when focus enters the group, and without
32
+ it the options are loose radios with no shared name. */
@@ -0,0 +1,93 @@
1
+ <!-- sds-radio-group — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/RadioGroup/RadioGroup.tsx.
3
+
4
+ A <fieldset> of native radios. KDLS held the selected value in useState and passed it to
5
+ each child through context; none of that is needed once the radios are real — the browser
6
+ owns which one is chosen, `name` makes them one group, and the change event bubbles to
7
+ whoever wants it.
8
+
9
+ So this hydrates only if a caller passes onChange, and even then it holds no state: it
10
+ reports the chosen value and lets the browser keep it. -->
11
+
12
+ import { assertRadioSize, assertRadioFeedback } from "../Radio/radio-variants.js";
13
+
14
+ class {
15
+ handleChange(event) {
16
+ if (event.target.type !== "radio") return;
17
+ this.input.onChange?.(event.target.value);
18
+ }
19
+ }
20
+
21
+ $ {
22
+ const {
23
+ label,
24
+ hint,
25
+ options = [],
26
+ // `component.id`, not a random string. Marko gives every component an id that is the
27
+ // same on the server and in the browser; a random one drawn inside the render block is
28
+ // a different value on each side, so every `for` and `aria-*` pointing at it resolved
29
+ // to nothing after hydration — and changed again on every re-render.
30
+ name = `sds-radio-group-${component.id}`,
31
+ value,
32
+ defaultValue,
33
+ size = "sm",
34
+ feedback = "none",
35
+ required = false,
36
+ disabled = false,
37
+ class: extraClass = "",
38
+ } = input;
39
+
40
+ if (!label) throw new Error("sds-radio-group needs a `label` naming what is being chosen.");
41
+ if (!options.length) throw new Error("sds-radio-group needs `options`.");
42
+ assertRadioSize(size);
43
+ assertRadioFeedback(feedback);
44
+
45
+ const selected = value ?? defaultValue;
46
+ const hintId = hint ? `${name}-hint` : null;
47
+
48
+ const classes = ["sds-radio-group", extraClass].filter(Boolean).join(" ");
49
+
50
+ // The hint is where the group's own state shows: the legend is hidden and each radio
51
+ // draws its own feedback. Disabled wins over feedback — a greyed-out group that still
52
+ // shows a red hint is telling the visitor to fix something it will not let them touch.
53
+ const hintClass = [
54
+ "sds-radio-group__hint",
55
+ disabled ? "sds-radio-group__hint--disabled" : "",
56
+ !disabled && feedback !== "none" ? `sds-radio-group__hint--${feedback}` : "",
57
+ ].filter(Boolean).join(" ");
58
+ }
59
+
60
+ <fieldset
61
+ class=classes
62
+ aria-describedby=hintId
63
+ aria-required=(required ? "true" : null)
64
+ // The group is the thing in error, not any one option: a screen reader on the fieldset
65
+ // was told the value was required and never that it was wrong.
66
+ aria-invalid=(feedback === "error" ? "true" : null)
67
+ on-change("handleChange")
68
+ >
69
+ <!-- The asterisk is inside the legend, which is the group's accessible name, so a screen
70
+ reader reads "Tipo de imóvel, obrigatório" rather than announcing a symbol on its
71
+ own. KDLS hangs it off a visible label with a ::after; there is no visible label
72
+ here, so it is said in words. -->
73
+ <legend class="sds-visually-hidden">${label}<if(required)> (obrigatório)</if></legend>
74
+
75
+ <for|option| of=options>
76
+ <sds-radio
77
+ name=name
78
+ value=option.value
79
+ label=option.label
80
+ hint=option.hint
81
+ size=size
82
+ feedback=feedback
83
+ checked=(option.value === selected)
84
+ disabled=(disabled || option.disabled)
85
+ />
86
+ </for>
87
+
88
+ <if(hint)>
89
+ <sds-text as="span" id=hintId variant="labelRegular" color="inherit" class=hintClass>
90
+ ${hint}
91
+ </sds-text>
92
+ </if>
93
+ </fieldset>