@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,16 @@
1
+ /**
2
+ * The Skeleton vocabulary.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Skeleton/Skeleton.tsx, where
5
+ * Button, Input and Image were sub-components of Skeleton. They are sibling tags here:
6
+ * they share nothing but a stylesheet, so nesting them under one parent would be a
7
+ * namespace rather than a relationship.
8
+ */
9
+ export const SKELETON_SIZES = ['sm', 'md', 'lg'];
10
+
11
+ export function assertSkeletonSize(size) {
12
+ if (!SKELETON_SIZES.includes(size)) {
13
+ throw new Error(`Unknown skeleton size: "${size}". Allowed: ${SKELETON_SIZES.join(', ')}`);
14
+ }
15
+ return size;
16
+ }
@@ -0,0 +1,53 @@
1
+ <!-- sds-skeleton — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/Skeleton/Skeleton.tsx.
3
+
4
+ A placeholder for content that has not arrived. `loading=false` renders the real content
5
+ instead, so a caller writes the swap once rather than branching around it.
6
+
7
+ It is aria-hidden and carries aria-busy on the region: a screen reader should hear
8
+ "loading", not a description of grey rectangles. KDLS left it unannounced entirely, which
9
+ means a screen reader user hears nothing at all while waiting.
10
+
11
+ No state: it is told whether it is loading. -->
12
+
13
+ import { assertSkeletonSize } from "./skeleton-variants.js";
14
+
15
+ $ {
16
+ const {
17
+ loading = true,
18
+ rows = 3,
19
+ title = true,
20
+ avatar = false,
21
+ active = true,
22
+ size = "md",
23
+ label = "Carregando",
24
+ class: extraClass = "",
25
+ } = input;
26
+
27
+ assertSkeletonSize(size);
28
+
29
+ const classes = [
30
+ "sds-skeleton",
31
+ active ? "sds-skeleton--active" : "",
32
+ extraClass,
33
+ ].filter(Boolean).join(" ");
34
+ }
35
+
36
+ <if(!loading)>
37
+ <${input.renderBody}/>
38
+ </if>
39
+ <else>
40
+ <div class=classes role="status" aria-busy="true" aria-label=label>
41
+ <if(avatar)>
42
+ <span class=`sds-skeleton__avatar sds-skeleton__avatar--${size}` aria-hidden="true"/>
43
+ </if>
44
+ <div class="sds-skeleton__content" aria-hidden="true">
45
+ <if(title)>
46
+ <span class="sds-skeleton__line sds-skeleton__line--title"/>
47
+ </if>
48
+ <for|_, index| of=Array.from({ length: rows })>
49
+ <span class=`sds-skeleton__line ${index === rows - 1 ? "sds-skeleton__line--last" : ""}`.trim()/>
50
+ </for>
51
+ </div>
52
+ </div>
53
+ </else>
@@ -0,0 +1,173 @@
1
+ /* SDS Spinner.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Spinner/Spinner.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-spinner--solid .sds-spinner__arc
7
+ *//* ────────────────────────────────────────────────────────────
8
+ Spinner — SVG stroke-dasharray spinner
9
+ Spinning: arc grows then shrinks via stroke-dasharray + SVG rotation
10
+ Done: full circle (primary-50) + check draws via stroke-dashoffset
11
+ No dark block needed — all tokens auto-invert or are pivot (50)
12
+ ──────────────────────────────────────────────────────────── */
13
+
14
+ /* ── Size tokens ── */
15
+ .sds-spinner--sm { --sds-spinner-size: 24px; }
16
+ .sds-spinner--md { --sds-spinner-size: 40px; }
17
+ .sds-spinner--lg { --sds-spinner-size: 56px; }
18
+ /* ── Fullscreen overlay ── */
19
+ /* Fixed backdrop covering the viewport, spinner centered — same overlay treatment as Modal */
20
+ .sds-spinner-fullscreen {
21
+ position: fixed;
22
+ inset: 0;
23
+ z-index: var(--z-notice);
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ background: var(--color-opacity-50);
28
+ animation: sds-spinner-overlay-in 120ms ease;
29
+ }
30
+ [data-theme="dark"] .sds-spinner-fullscreen {
31
+ background: var(--color-scrim);
32
+ }
33
+ @keyframes sds-spinner-overlay-in {
34
+ from { opacity: 0; }
35
+ to { opacity: 1; }
36
+ }
37
+ /* ── Base wrapper ── */
38
+ .sds-spinner {
39
+ position: relative;
40
+ display: inline-flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ width: var(--sds-spinner-size);
44
+ height: var(--sds-spinner-size);
45
+ flex-shrink: 0;
46
+ }
47
+ /* ── SVG container ── */
48
+ .sds-spinner__svg {
49
+ display: block;
50
+ overflow: visible;
51
+ transform-origin: center center;
52
+ }
53
+ /* ── Track ring ── */
54
+ .sds-spinner__track {
55
+ fill: none;
56
+ stroke: var(--color-neutral-20);
57
+ }
58
+ /* ── Arc — base ── */
59
+ .sds-spinner__arc {
60
+ fill: none;
61
+ }
62
+ /* Solid variant color */
63
+ .sds-spinner--solid .sds-spinner__arc {
64
+ stroke: var(--color-primary-50-solid);
65
+ }
66
+ /* ── SPINNING STATE ── */
67
+
68
+ /* SVG rotates continuously — moves the arc around the circle */
69
+ .sds-spinner__svg {
70
+ animation: sds-spinner-rotate 1.4s linear infinite;
71
+ }
72
+ /* Arc grows from sliver → 75% → shrinks back to sliver ("worm") */
73
+ .sds-spinner__arc {
74
+ stroke-dasharray: 1px, var(--c);
75
+ stroke-dashoffset: 0;
76
+ animation: sds-spinner-dash 1.4s ease-in-out infinite;
77
+ }
78
+ /* Gradient variant: wave color cycles continuously scheme→secondary→tertiary */
79
+ /* ── DONE STATE ── */
80
+
81
+ /* Pause (not cancel) spinning when done — prevents snap-to-0 rotation glitch */
82
+ .sds-spinner--done .sds-spinner__svg {
83
+ animation-play-state: paused;
84
+ }
85
+ .sds-spinner--done .sds-spinner__arc {
86
+ animation-play-state: paused;
87
+ }
88
+ /* Done SVG overlay — absolutely positioned on top of the spinning SVG */
89
+ .sds-spinner__done-svg {
90
+ position: absolute;
91
+ top: 0;
92
+ left: 0;
93
+ display: block;
94
+ overflow: visible;
95
+ }
96
+ /* Fill circle — covers full stroke area (r + sw/2 radius set in TSX) */
97
+ .sds-spinner__done-fill {
98
+ fill: var(--color-primary-50-solid);
99
+ }
100
+ /* Mask hole — shrinks interior to zero; ease-out = starts fast, no noticeable pause */
101
+ .sds-spinner__fill-hole {
102
+ transform-box: fill-box;
103
+ transform-origin: 50% 50%;
104
+ animation: sds-fill-hole-shrink 420ms ease-out forwards;
105
+ }
106
+ /* ── Checkmark — draws itself after fill completes ── */
107
+ .sds-spinner__check {
108
+ fill: none;
109
+ stroke: var(--color-white);
110
+ stroke-dasharray: var(--check-len);
111
+ stroke-dashoffset: var(--check-len);
112
+ animation: sds-check-draw 300ms ease-out 420ms forwards;
113
+ }
114
+ /* ── Animations ── */
115
+
116
+ @keyframes sds-spinner-rotate {
117
+ to { transform: rotate(360deg); }
118
+ }
119
+ @keyframes sds-fill-hole-shrink {
120
+ from { transform: scale(1); }
121
+ to { transform: scale(0); }
122
+ }
123
+ /* Phase 1: arc grows from 1px to 75% of circumference
124
+ Phase 2: arc shrinks back to 1% while dashoffset advances full circle
125
+ Combined with SVG rotation → "traveling worm" effect */
126
+ @keyframes sds-spinner-dash {
127
+ 0% {
128
+ stroke-dasharray: 1px, var(--c);
129
+ stroke-dashoffset: 0;
130
+ }
131
+ 50% {
132
+ stroke-dasharray: calc(var(--c) * 0.75), var(--c);
133
+ stroke-dashoffset: calc(var(--c) * -0.25);
134
+ }
135
+ 100% {
136
+ stroke-dasharray: 1px, var(--c);
137
+ stroke-dashoffset: calc(var(--c) * -1);
138
+ }
139
+ }
140
+ @keyframes sds-check-draw {
141
+ to { stroke-dashoffset: 0; }
142
+ }
143
+
144
+ /* ── Variants ──────────────────────────────────────────────────────────────────
145
+ KDLS shipped two: `solid`, which paints the arc with the scheme colour, and
146
+ `gradient`, whose whole definition was to cycle scheme -> secondary -> tertiary. Those
147
+ two families do not exist in SDS, and there is nothing left of that variant once they
148
+ go — so the variant is gone and the `variant` attribute with it. The arc reads the
149
+ primary slot, which is what `solid` always did.
150
+
151
+ The Lya keyframe (an arc cycling between two hardcoded hexes, #77A6E0 and #BF71E5,
152
+ neither of which was ever a token) is removed for the same reason. */
153
+
154
+ /* The way out, in plain sight. It was visually hidden until focused — the skip-link
155
+ shape — and that means a control whose metrics change when it takes focus, which is the
156
+ one thing tests/no-reflow-on-interaction.test.js exists to forbid. It is also the wrong
157
+ answer here: a blocking overlay with a hidden exit is a blocking overlay. */
158
+ .sds-spinner-fullscreen__dismiss {
159
+ margin-top: var(--spacing-4);
160
+ padding: var(--spacing-2) var(--spacing-4);
161
+ border: 0;
162
+ border-radius: var(--radius-sm);
163
+ background: var(--component-bg-color);
164
+ color: var(--body-color);
165
+ font-family: var(--font-family-base);
166
+ font-size: var(--font-size-body-md);
167
+ cursor: pointer;
168
+ }
169
+
170
+ /* The overlay stacks its spinner over the way out. */
171
+ .sds-spinner-fullscreen {
172
+ flex-direction: column;
173
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The Spinner's SVG geometry, per size.
3
+ *
4
+ * Ported verbatim from kdls-design-system v2.5.1 (0f3edf8) components/Spinner/Spinner.tsx.
5
+ * These are drawing constants, not design tokens: they describe one shape at three sizes,
6
+ * and changing them changes the drawing rather than the theme.
7
+ *
8
+ * px the box, in CSS pixels
9
+ * r the circle radius
10
+ * sw the arc stroke width
11
+ * checkSw the checkmark stroke width
12
+ * checkLen the approximate path length, which drives the draw-on animation
13
+ * check the checkmark path, scaled 0.75 from centre so it sits inside the circle
14
+ */
15
+ export const SPINNER_SIZES = ['sm', 'md', 'lg'];
16
+
17
+ export const SPINNER_GEOMETRY = {
18
+ sm: { px: 24, r: 9, sw: 2, checkSw: 2, checkLen: 14, check: 'M7.5 12L10.5 15.5L16.5 9' },
19
+ md: { px: 40, r: 16, sw: 3, checkSw: 3, checkLen: 22, check: 'M12.5 20L17.75 25.5L27.5 15' },
20
+ lg: { px: 56, r: 23, sw: 4, checkSw: 4, checkLen: 31, check: 'M17.5 28L24.5 35.5L38.5 20.5' },
21
+ };
22
+
23
+ export function assertSpinnerSize(size) {
24
+ if (!SPINNER_SIZES.includes(size)) {
25
+ throw new Error(`Unknown spinner size: "${size}". Allowed: ${SPINNER_SIZES.join(', ')}`);
26
+ }
27
+ return size;
28
+ }
29
+
30
+ export function geometryFor(size) {
31
+ const g = SPINNER_GEOMETRY[assertSpinnerSize(size)];
32
+ return {
33
+ ...g,
34
+ cx: g.px / 2,
35
+ circumference: +(2 * Math.PI * g.r).toFixed(2),
36
+ holeR: g.r - g.sw / 2,
37
+ fillR: g.r + g.sw / 2,
38
+ };
39
+ }
@@ -0,0 +1,97 @@
1
+ <!-- sds-spinner-overlay — the fullscreen half of the Spinner, and the only half that has
2
+ state.
3
+
4
+ The Spinner is markup and must stay markup: a listing skeleton puts fifty of them on a
5
+ page, and Marko decides serialisation per template at compile time, so a single
6
+ `on-click` anywhere in spinner.marko would be paid by every one of those fifty.
7
+
8
+ An overlay is the opposite kind of thing. There is one, it covers the viewport, and it
9
+ needs a way out — a spinner that finishes behind a backdrop nobody can dismiss locks
10
+ the page. So the state lives here, in the template that is rendered once.
11
+
12
+ It closes on a click and, once `done`, on its own after `dismissAfter` milliseconds.
13
+ Both are reported through `onDismiss`; a caller that controls `isOpen` decides whether
14
+ the close actually happens, and one that does not gets it for free. -->
15
+
16
+ import { assertSpinnerSize } from "./spinner-geometry.js";
17
+
18
+ class {
19
+ onCreate() {
20
+ this.state = { closed: false };
21
+ }
22
+
23
+ onInput(input) {
24
+ // Re-opening after a dismissal has to clear the flag, or the overlay stays gone.
25
+ if (input.isOpen && this.state.closed) this.state.closed = false;
26
+ }
27
+
28
+ onMount() {
29
+ this.arm();
30
+ }
31
+
32
+ onUpdate() {
33
+ this.arm();
34
+ }
35
+
36
+ onDestroy() {
37
+ clearTimeout(this.timer);
38
+ }
39
+
40
+ /** The automatic close, armed only once the spinner has something to say it is done. */
41
+ arm() {
42
+ clearTimeout(this.timer);
43
+ if (!this.input.done || !this.isOpen) return;
44
+ this.timer = setTimeout(() => this.dismiss(), this.input.dismissAfter ?? 3000);
45
+ }
46
+
47
+ get isOpen() {
48
+ return this.input.isOpen !== undefined ? this.input.isOpen : !this.state.closed;
49
+ }
50
+
51
+ dismiss() {
52
+ clearTimeout(this.timer);
53
+ this.input.onDismiss?.();
54
+ if (this.input.isOpen === undefined) this.state.closed = true;
55
+ }
56
+
57
+ /** A click on the backdrop, not on the spinner sitting in the middle of it. */
58
+ closeFromBackdrop(event, element) {
59
+ if (event.target !== element) return;
60
+ this.dismiss();
61
+ }
62
+
63
+ closeOnEscape(event) {
64
+ if (event.key !== "Escape") return;
65
+ event.preventDefault();
66
+ this.dismiss();
67
+ }
68
+ }
69
+
70
+ $ {
71
+ const { size = "md", done = false, label, dismissLabel = "Fechar" } = input;
72
+
73
+ assertSpinnerSize(size);
74
+ }
75
+
76
+ <if(component.isOpen)>
77
+ <div
78
+ class="sds-spinner-fullscreen"
79
+ role="alertdialog"
80
+ aria-label=(label ?? (done ? "Concluído" : "Carregando"))
81
+ on-click("closeFromBackdrop")
82
+ on-keydown("closeOnEscape")
83
+ >
84
+ <sds-spinner size=size done=done label=label/>
85
+
86
+ <!-- The way out, in plain sight. Hiding it until focused would mean a control whose
87
+ metrics change when it takes focus, and a blocking overlay whose exit is
88
+ invisible is a blocking overlay. -->
89
+ <button
90
+ type="button"
91
+ class="sds-spinner-fullscreen__dismiss"
92
+ on-click("dismiss")
93
+ >
94
+ ${dismissLabel}
95
+ </button>
96
+ </div>
97
+ </if>
@@ -0,0 +1,88 @@
1
+ <!-- sds-spinner — ported from kdls-design-system v2.5.1 (0f3edf8) components/Spinner/Spinner.tsx.
2
+
3
+ Divergences:
4
+
5
+ No `variant`. KDLS had two — `solid`, which paints the arc with the scheme colour, and
6
+ `gradient`, whose entire definition was to cycle scheme to secondary to tertiary. Those
7
+ families do not exist in SDS and nothing is left of that variant once they go, so the
8
+ variant went with them and the attribute with it. The arc reads the primary slot, which
9
+ is what `solid` always did.
10
+
11
+ No `onComplete` callback, and therefore no state. KDLS attached a native `animationend`
12
+ listener to the checkmark path to report completion. The event is already the API: a
13
+ caller that needs to know listens for `animationend` on `.sds-spinner__check` itself,
14
+ and the component stays markup.
15
+
16
+ `fullscreen` moved out, to sds-spinner-overlay. It renders in place rather than through a
17
+ portal — the overlay is position:fixed, so it covers the viewport from wherever it sits in
18
+ the tree, and the portal was solving a React-specific problem that does not exist here.
19
+ What it also needs is a way out, and a way out is a click handler: Marko decides
20
+ serialisation per template at compile time, so one handler here would be paid by every
21
+ spinner on the page, and a listing skeleton renders fifty. The overlay is rendered once,
22
+ so that is where the state belongs. -->
23
+
24
+ import { assertSpinnerSize } from "./spinner-geometry.js";
25
+ import { geometryFor } from "./spinner-geometry.js";
26
+
27
+ $ {
28
+ const {
29
+ size = "md",
30
+ done = false,
31
+ label = done ? "Concluído" : "Carregando",
32
+ // A spinner inside a control that already reports its own busy state must not announce
33
+ // itself: a button's accessible name comes from its contents, so a spinner with
34
+ // role="status" and aria-label="Carregando" renames the button to "Carregando
35
+ // Publicar" for the whole time it is working. aria-busy on the control is what says it
36
+ // is busy; this is just the picture of it. KDLS has the same defect and no way to opt
37
+ // out — the spinner is announced inside every one of its loading buttons.
38
+ decorative = false,
39
+ class: extraClass = "",
40
+ } = input;
41
+
42
+ assertSpinnerSize(size);
43
+ const g = geometryFor(size);
44
+
45
+ const classes = [
46
+ "sds-spinner",
47
+ "sds-spinner--solid",
48
+ `sds-spinner--${size}`,
49
+ done ? "sds-spinner--done" : "",
50
+ extraClass,
51
+ ].filter(Boolean).join(" ");
52
+
53
+ // --c drives the arc dash animation, --check-len the checkmark draw-on.
54
+ const style = `--c: ${g.circumference}px; --check-len: ${g.checkLen}px`;
55
+ }
56
+
57
+ <macro|{ }| name="spinner">
58
+ <div
59
+ class=classes
60
+ role=(decorative ? null : "status")
61
+ aria-label=(decorative ? null : label)
62
+ aria-hidden=(decorative ? "true" : null)
63
+ style=style
64
+ >
65
+ <svg class="sds-spinner__svg" viewBox=`0 0 ${g.px} ${g.px}` width=g.px height=g.px fill="none" aria-hidden="true">
66
+ <circle class="sds-spinner__track" cx=g.cx cy=g.cx r=g.r stroke-width=g.sw/>
67
+ <circle class="sds-spinner__arc" cx=g.cx cy=g.cx r=g.r stroke-width=g.sw stroke-linecap="round"/>
68
+ </svg>
69
+
70
+ <if(done)>
71
+ <svg class="sds-spinner__done-svg" viewBox=`0 0 ${g.px} ${g.px}` width=g.px height=g.px fill="none" aria-hidden="true">
72
+ <defs>
73
+ <mask id=`sds-spinner-fill-mask-${size}`>
74
+ <!-- The hole covers the interior only, so the fill reaches the arc at t=0.
75
+ White and black here are mask channels, not colour: the mask reads
76
+ luminance and the values are never painted. -->
77
+ <rect x="0" y="0" width=g.px height=g.px fill="white"/>
78
+ <circle class="sds-spinner__fill-hole" cx=g.cx cy=g.cx r=g.holeR fill="black"/>
79
+ </mask>
80
+ </defs>
81
+ <circle class="sds-spinner__done-fill" cx=g.cx cy=g.cx r=g.fillR mask=`url(#sds-spinner-fill-mask-${size})`/>
82
+ <path class="sds-spinner__check" d=g.check stroke-width=g.checkSw stroke-linecap="round" stroke-linejoin="round"/>
83
+ </svg>
84
+ </if>
85
+ </div>
86
+ </macro>
87
+
88
+ <spinner/>
@@ -0,0 +1,90 @@
1
+ /* SDS StatCard.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/StatCard/StatCard.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+ *//* ── StatCard ───────────────────────────────────────────────────────────
5
+ KPI / big-number card. Typically arranged in a grid row at the top of a page.
6
+ BEM root: .sds-stat-card
7
+ ─────────────────────────────────────────────────────────────────── */
8
+
9
+ /* A <dl>: a number with a name is a description list. Its default styling has to be taken
10
+ back — see the note on .sds-card__info-pair, same element, same trap. */
11
+ /* The column is turned around: the DOM reads `<dt>` then `<dd>`, which is the only valid
12
+ order for a description list, and the card draws the number above its name. Nothing in
13
+ here is interactive, so there is no focus order to fall out of step with. */
14
+ .sds-stat-card {
15
+ background: var(--component-bg-color);
16
+ border-radius: var(--radius-md);
17
+ padding: var(--spacing-6);
18
+ display: flex;
19
+ flex-direction: column-reverse;
20
+ gap: var(--spacing-1);
21
+ margin: 0;
22
+ }
23
+
24
+ .sds-stat-card dt,
25
+ .sds-stat-card dd {
26
+ margin: 0;
27
+ }
28
+ .sds-stat-card--hug {
29
+ width: fit-content;
30
+ min-width: 200px;
31
+ max-width: 240px;
32
+ }
33
+ .sds-stat-card--fill {
34
+ width: 100%;
35
+ }
36
+ .sds-stat-card__top {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: var(--spacing-2);
40
+ }
41
+ /* 2 steps weaker than __value's --title-color, by design (not a bug) — neutral-70
42
+ auto-inverts the same way --title-color does, so the 2-step gap holds in both themes
43
+ without a dark override: light neutral-90→70 is 2 steps darker-to-lighter, and dark
44
+ mirrors that same gap from the inverted (white) end. */
45
+ .sds-stat-card__icon {
46
+ display: inline-flex;
47
+ font-size: 24px;
48
+ flex-shrink: 0;
49
+ color: var(--color-neutral-70);
50
+ }
51
+ .sds-stat-card__value {
52
+ font-family: var(--font-family-base);
53
+ font-size: 24px;
54
+ font-weight: var(--font-weight-bold);
55
+ line-height: 32px;
56
+ letter-spacing: -0.05em;
57
+ color: var(--title-color);
58
+ }
59
+ .sds-stat-card .sds-stat-card__label {
60
+ font-weight: var(--font-weight-medium);
61
+ }
62
+ /* ── Group wrapper — flex-wrap: cards (hug) hug the text and wrap onto the line ── */
63
+ .sds-stat-card-group {
64
+ display: flex;
65
+ flex-wrap: wrap;
66
+ gap: var(--spacing-4);
67
+ }
68
+ /* Mobile: 2 per row; drops to 1 when 2 don't fit (auto-fit + smaller min).
69
+ The cards fill the column, ignoring the 200–240 hug clamp.
70
+
71
+ Divergence: min() around the track minimum. A bare minmax(150px, 1fr) never goes under
72
+ 150, so a group placed in a column narrower than that is wider than its column — and
73
+ the page, not the group, is what scrolls. */
74
+ @media (max-width: 799px) {
75
+ .sds-stat-card-group {
76
+ display: grid;
77
+ grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
78
+ }
79
+ .sds-stat-card-group .sds-stat-card {
80
+ width: auto;
81
+ min-width: 0;
82
+ max-width: none;
83
+ }
84
+ }
85
+
86
+ /* Same rule the Card, the Accordion and the AttachmentList follow: a container that draws
87
+ its own surface needs the stroke only where that surface matches the ground behind it. */
88
+ .sds-stat-card--bordered {
89
+ border: 1px solid var(--border-static-color);
90
+ }
@@ -0,0 +1,5 @@
1
+ <!-- sds-stat-card-group — ported from kdls-design-system v2.5.1 (0f3edf8), StatCardGroup. -->
2
+ $ const { class: extraClass = "" } = input;
3
+ <div class=`sds-stat-card-group ${extraClass}`.trim()>
4
+ <${input.renderBody}/>
5
+ </div>
@@ -0,0 +1,57 @@
1
+ <!-- sds-stat-card — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/StatCard/StatCard.tsx.
3
+
4
+ A number and what it counts. The value and its label are a description list rather than
5
+ two spans: "2.560 imóveis ativos" should reach a screen reader as one fact, which is the
6
+ same correction the Card's InfoPair needed. KDLS used loose spans. -->
7
+
8
+ $ {
9
+ const {
10
+ value,
11
+ label,
12
+ icon,
13
+ width = "hug",
14
+ // A bordered container separates itself from a ground of its own colour; on a
15
+ // different ground the stroke is one line of noise. Same rule the Card, the Accordion
16
+ // and the AttachmentList follow.
17
+ bordered = false,
18
+ class: extraClass = "",
19
+ } = input;
20
+
21
+ if (value === undefined) throw new Error("sds-stat-card needs a `value`.");
22
+ if (!label) throw new Error("sds-stat-card needs a `label` — a number with no name is not a statistic.");
23
+ if (width !== "hug" && width !== "fill") {
24
+ throw new Error(`Unknown stat card width: "${width}". Allowed: hug, fill`);
25
+ }
26
+
27
+ const renderable = (v) => (v && v.renderBody) || v;
28
+ }
29
+
30
+ <!-- A `<dl>`, with the term first — which is what makes it valid.
31
+
32
+ The value is drawn ABOVE its label, and the port wrote the DOM in that order: `<dd>`
33
+ then `<dt>`. `<dl>` requires the term first, so the pairing a screen reader relies on
34
+ was broken by the very markup chosen to express it. The reading order is term then
35
+ description now, and the stylesheet turns the column around so the number still sits
36
+ on top. Nothing here is interactive, so visual order and focus order cannot disagree.
37
+
38
+ The list stays rather than going back to KDLS's loose spans: "2560" and "Imóveis
39
+ ativos" as two unrelated strings is what the `<dl>` was for. -->
40
+ <dl class=[
41
+ "sds-stat-card",
42
+ `sds-stat-card--${width}`,
43
+ bordered ? "sds-stat-card--bordered" : "",
44
+ extraClass,
45
+ ].filter(Boolean).join(" ")>
46
+ <dt>
47
+ <sds-text as="span" variant="bodyMd" class="sds-stat-card__label">${label}</sds-text>
48
+ </dt>
49
+ <dd class="sds-stat-card__top">
50
+ <if(icon)>
51
+ <span class="sds-stat-card__icon" aria-hidden="true">
52
+ <sds-icon><${renderable(icon)}/></sds-icon>
53
+ </span>
54
+ </if>
55
+ <span class="sds-stat-card__value">${value}</span>
56
+ </dd>
57
+ </dl>