@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,391 @@
1
+ /* SDS Card.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Card/Card.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+ *//* ── Card ──────────────────────────────────────────────────────────────
5
+ Surface container with header, body, footer and section sub-parts.
6
+ BEM root: .sds-card
7
+ ─────────────────────────────────────────────────────────────────── */
8
+
9
+ .sds-card {
10
+ position: relative; /* the hit area is positioned against this */
11
+ background: var(--component-bg-color);
12
+ border-radius: var(--radius-md);
13
+ border: 1px solid transparent;
14
+ overflow: hidden;
15
+ transition: box-shadow 120ms ease;
16
+ }
17
+ .sds-card--bordered {
18
+ border-color: var(--border-static-color);
19
+ }
20
+ .sds-card--bordered.sds-card--hoverable {
21
+ border-color: var(--border-interactive-color);
22
+ }
23
+ .sds-card--hoverable {
24
+ cursor: pointer;
25
+ outline: none;
26
+ }
27
+ .sds-card--hoverable:hover {
28
+ box-shadow: var(--shadow-drop-md);
29
+ }
30
+ .sds-card--hoverable:active {
31
+ box-shadow: var(--shadow-drop-sm);
32
+ }
33
+ .sds-card--hoverable:focus-visible {
34
+ box-shadow: var(--focus-ring);
35
+ }
36
+ /* ── Cover — media/image slot, no padding, bleeds to the card's own radius.
37
+ Default: top, full width, aspect-ratio fixed at 16/9 (overridden by coverHeight).
38
+ horizontal: left column, fixed width, fills the row's height — aspect-ratio has
39
+ no effect there since both dimensions are already explicit. ── */
40
+ .sds-card__cover {
41
+ position: relative;
42
+ line-height: 0;
43
+ }
44
+ .sds-card__cover--16-9 {
45
+ aspect-ratio: 16 / 9;
46
+ }
47
+ .sds-card__cover img {
48
+ display: block;
49
+ width: 100%;
50
+ height: 100%;
51
+ object-fit: cover;
52
+ }
53
+ /* Slot positioned at the top-left corner of the cover — badges, tags.
54
+ Works in both modes because it only depends on the cover's position:relative. */
55
+ .sds-card__cover-badges {
56
+ position: absolute;
57
+ top: var(--spacing-4);
58
+ left: var(--spacing-4);
59
+ right: var(--spacing-4);
60
+ display: flex;
61
+ flex-wrap: wrap;
62
+ align-items: center;
63
+ gap: var(--spacing-2);
64
+ }
65
+ .sds-card__content {
66
+ min-width: 0;
67
+ }
68
+ /* ── Horizontal — cover on the left, content column on the right.
69
+ Width always fixed (296px by default, or coverWidth's value).
70
+ Height always follows the card (align-items: stretch). ── */
71
+ .sds-card--horizontal {
72
+ display: flex;
73
+ align-items: stretch;
74
+ }
75
+ .sds-card--horizontal .sds-card__cover {
76
+ width: 296px;
77
+ flex-shrink: 0;
78
+ }
79
+ .sds-card--horizontal .sds-card__content {
80
+ display: flex;
81
+ flex-direction: column;
82
+ flex: 1;
83
+ }
84
+ /* ── Header — column: top row (title/left/right/actions) then subtitle, full-width, own line ── */
85
+ .sds-card__header {
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: var(--spacing-0-5);
89
+ padding: var(--spacing-6);
90
+ border-bottom: 1px solid var(--border-static-color);
91
+ }
92
+ /* size="sm" — smaller padding (16px), gap doesn't change */
93
+ .sds-card--sm .sds-card__header {
94
+ padding: var(--spacing-4);
95
+ }
96
+ /* bordered=false — no divider. Only zeroes the bottom padding when there's something after
97
+ (:not(:last-child)) — alone (Card's only child), keeps full padding,
98
+ otherwise the header gets cut off at the bottom with no breathing room */
99
+ .sds-card__header--borderless {
100
+ border-bottom: none;
101
+ }
102
+ .sds-card__header--borderless:not(:last-child) {
103
+ padding-bottom: 0;
104
+ }
105
+ /* When header is a direct child of the canvas/page — no padding or border */
106
+ .sds-datatable > .sds-card__header {
107
+ padding: 0;
108
+ border-bottom: none;
109
+ }
110
+ .sds-card__header-top {
111
+ display: flex;
112
+ align-items: flex-start;
113
+ justify-content: space-between;
114
+ flex-wrap: wrap;
115
+ gap: var(--spacing-4);
116
+ }
117
+ .sds-card__header-left-col {
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: var(--spacing-0-5);
121
+ flex: 1;
122
+ min-width: 0;
123
+ }
124
+ .sds-card__header-title-row {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: var(--spacing-4);
128
+ min-width: 0;
129
+ }
130
+ .sds-card__header-right {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: var(--spacing-4);
134
+ flex-wrap: wrap;
135
+ }
136
+ /* ── Body ── */
137
+ .sds-card__body {
138
+ padding: var(--spacing-6);
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: var(--spacing-4);
142
+ }
143
+ .sds-card--sm .sds-card__body {
144
+ padding: var(--spacing-4);
145
+ }
146
+ /* ── Footer ── */
147
+ .sds-card__footer {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: var(--spacing-4);
151
+ padding: var(--spacing-4) var(--spacing-6);
152
+ border-top: 1px solid var(--border-static-color);
153
+ }
154
+ .sds-card--sm .sds-card__footer {
155
+ padding: var(--spacing-4);
156
+ }
157
+ .sds-card__footer--right {
158
+ flex-direction: row;
159
+ align-items: center;
160
+ justify-content: flex-end;
161
+ gap: var(--spacing-2);
162
+ }
163
+ /* ── Section ── */
164
+ .sds-card__section {
165
+ border-top: 1px solid var(--border-static-color);
166
+ padding: var(--spacing-6);
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: var(--spacing-4);
170
+ }
171
+ .sds-card--sm .sds-card__section {
172
+ padding: var(--spacing-4);
173
+ }
174
+ /* bordered=false — no divider. Section's border is on top (border-top),
175
+ the opposite of Header — only zeroes the TOP padding when there's something before
176
+ (:not(:first-child)) — alone/first child of Card, keeps full padding,
177
+ otherwise the section gets cut off at the top with no breathing room */
178
+ .sds-card__section--borderless {
179
+ border-top: none;
180
+ }
181
+ .sds-card__section--borderless:not(:first-child) {
182
+ padding-top: 0;
183
+ }
184
+ /* ── Info row (label/value pairs) — default 2 columns, columns prop overrides ── */
185
+ .sds-card__info-row {
186
+ display: grid;
187
+ grid-template-columns: 1fr 1fr;
188
+ gap: var(--spacing-4);
189
+ }
190
+ .sds-card__info-row--3 {
191
+ grid-template-columns: repeat(3, 1fr);
192
+ }
193
+ .sds-card__info-row--4 {
194
+ grid-template-columns: repeat(4, 1fr);
195
+ }
196
+ /* A <dl>, which KDLS draws as a <div> — a label beside a value is a description list, and
197
+ saying so is what makes a screen reader pair them. What came with the element is its
198
+ default styling: `dl` carries a 1em block margin and `dd` a 40px inline indent, so the
199
+ pairs sat apart from each other and the value sat pushed off to the right. None of that
200
+ is the design; it has to be taken back explicitly. */
201
+ .sds-card__info-pair {
202
+ display: flex;
203
+ flex-direction: column;
204
+ gap: var(--spacing-1);
205
+ margin: 0;
206
+ text-align: start;
207
+ }
208
+
209
+ .sds-card__info-pair dt,
210
+ .sds-card__info-pair dd {
211
+ margin: 0;
212
+ }
213
+ .sds-card__info-value {
214
+ font-family: var(--font-family-base);
215
+ font-size: var(--font-size-body-md);
216
+ font-weight: var(--font-weight-medium);
217
+ color: var(--body-color);
218
+ }
219
+ /* ── Collapsible — hides children by default, reveals via its own trigger ── */
220
+ .sds-card__collapsible {
221
+ display: flex;
222
+ flex-direction: column;
223
+ gap: 0;
224
+ }
225
+ /* Only creates spacing between content and trigger when open — closed, the
226
+ content area has zero height and this gap would add to Card.Body's external gap,
227
+ making the space before the button inconsistent between the two states. */
228
+ .sds-card__collapsible:has(.sds-card__collapsible-body-wrap--open) {
229
+ gap: var(--spacing-4);
230
+ }
231
+ .sds-card__collapsible-body-wrap {
232
+ display: grid;
233
+ grid-template-rows: 0fr;
234
+ transition: grid-template-rows 200ms ease;
235
+ }
236
+ .sds-card__collapsible-body-wrap--open {
237
+ grid-template-rows: 1fr;
238
+ }
239
+ .sds-card__collapsible-body-clip {
240
+ overflow: hidden;
241
+ min-height: 0;
242
+ }
243
+ .sds-card__collapsible-body {
244
+ display: flex;
245
+ flex-direction: column;
246
+ gap: var(--spacing-4);
247
+ }
248
+ /* The trigger reads as the link button KDLS builds it from.
249
+
250
+ It is a <summary>, not a <button>, and that stays: <details> gives the keyboard handling
251
+ and the no-JavaScript fallback for free, which a button and a piece of state do not. But
252
+ it was inheriting the card's body text — no colour of its own, no hover, no active, and
253
+ the browser's default outline on focus instead of the system's ring. So the one control
254
+ in the card that opens something looked like a caption. */
255
+ .sds-card__collapsible-trigger {
256
+ display: flex;
257
+ justify-content: center;
258
+ align-items: center;
259
+ gap: var(--spacing-2);
260
+ height: 24px;
261
+ font-size: var(--font-size-title-sm);
262
+ line-height: var(--lh-title-sm);
263
+ color: var(--color-primary-50-solid);
264
+ }
265
+ .sds-card__collapsible-trigger:hover {
266
+ color: var(--color-primary-60-solid);
267
+ text-decoration: underline;
268
+ text-underline-offset: 3px;
269
+ }
270
+ .sds-card__collapsible-trigger:active {
271
+ color: var(--color-primary-70-solid);
272
+ }
273
+ .sds-card__collapsible-trigger:focus-visible {
274
+ outline: none;
275
+ box-shadow: var(--focus-ring);
276
+ border-radius: var(--radius-sm);
277
+ }
278
+ .sds-card__collapsible-trigger--right {
279
+ justify-content: flex-end;
280
+ }
281
+
282
+ .sds-card__collapsible:has(.sds-card__collapsible-body-wrap--open) .sds-card__collapsible-trigger .sds-button__icon--right {
283
+ transform: translateY(1px);
284
+ }
285
+ /* ── Loading skeleton — replaces children entirely while loading is true.
286
+ Composed of the real Card.Header and Card.Body, each with a Skeleton
287
+ inside — they inherit padding/border/size automatically, no CSS of their own. ── */
288
+
289
+ /* ── Accordion modifier ──────────────────────────────────────────────
290
+ When a Card is used as a borderless accordion container.
291
+ ─────────────────────────────────────────────────────────────────── */
292
+ .sds-card--accordion {
293
+ border: none;
294
+ }
295
+ .sds-card--accordion .sds-accordion__header {
296
+ border-bottom: 1px solid var(--border-static-color);
297
+ min-height: auto;
298
+ }
299
+ .sds-card--accordion .sds-accordion__body {
300
+ padding: 0;
301
+ }
302
+ .sds-card--accordion .sds-accordion__body .sds-card__body {
303
+ padding: var(--spacing-4) var(--spacing-6) var(--spacing-6);
304
+ }
305
+ /* borderless=true — no stroke on header or footer */
306
+ .sds-card--accordion.sds-accordion--borderless .sds-accordion__header {
307
+ border-bottom: none;
308
+ }
309
+ .sds-card--accordion.sds-accordion--borderless .sds-card__footer {
310
+ border-top: none;
311
+ }
312
+
313
+ /* ── Collapsible ───────────────────────────────────────────────────────────────
314
+ Built on <details>, like sds-accordion, so keyboard handling and the no-JavaScript
315
+ fallback come from the browser. The label swaps with the open state, which is a pair of
316
+ spans and a rule rather than a piece of JavaScript.
317
+
318
+ --closing wins over [open] so the rows collapse while the element is still open;
319
+ without it the browser hides the content first and the transition has nothing to move. */
320
+ details.sds-card__collapsible > summary {
321
+ list-style: none;
322
+ cursor: pointer;
323
+ }
324
+
325
+ details.sds-card__collapsible > summary::-webkit-details-marker,
326
+ details.sds-card__collapsible > summary::marker {
327
+ display: none;
328
+ content: "";
329
+ }
330
+
331
+ .sds-card__collapsible-label--hide,
332
+ details.sds-card__collapsible[open] .sds-card__collapsible-label--show {
333
+ display: none;
334
+ }
335
+
336
+ details.sds-card__collapsible[open] .sds-card__collapsible-label--hide {
337
+ display: inline;
338
+ }
339
+
340
+ details.sds-card__collapsible[open] .sds-card__collapsible-chevron {
341
+ transform: rotate(180deg);
342
+ }
343
+
344
+ .sds-card__collapsible-chevron {
345
+ display: inline-flex;
346
+ transition: transform 200ms ease;
347
+ }
348
+
349
+ details.sds-card__collapsible[open]:not(.sds-card__collapsible--closing)
350
+ > .sds-card__collapsible-body-wrap {
351
+ grid-template-rows: 1fr;
352
+ }
353
+
354
+ details.sds-card__collapsible.sds-card__collapsible--closing > .sds-card__collapsible-body-wrap {
355
+ grid-template-rows: 0fr;
356
+ }
357
+
358
+ /* ── The click target of a clickable card ──────────────────────────────────────
359
+ A box over the whole card, beside the content rather than around it — see the
360
+ reasoning in card.marko. It carries no UA button chrome: SDS ships no element reset,
361
+ so every component that renders a <button> resets its own.
362
+
363
+ The stacking is the whole trick. The hit area sits above the card's own content, so a
364
+ click anywhere on the card reaches it; anything interactive inside the card sits above
365
+ the hit area, so a control that has its own answer still gets the click. */
366
+ .sds-card__hit {
367
+ position: absolute;
368
+ inset: 0;
369
+ z-index: 1;
370
+ appearance: none;
371
+ border: none;
372
+ padding: 0;
373
+ margin: 0;
374
+ background: transparent;
375
+ font: inherit;
376
+ color: inherit;
377
+ cursor: pointer;
378
+ }
379
+
380
+ .sds-card__hit:focus-visible {
381
+ /* The system's ring, never the browser's: this is a real <button>, so without the reset
382
+ Chrome draws its own outline around the whole card on top of the inset ring. */
383
+ outline: none;
384
+ border-radius: inherit;
385
+ box-shadow: var(--focus-ring-inset);
386
+ }
387
+
388
+ .sds-card:has(.sds-card__hit) :is(a, button, input, select, textarea, summary, [tabindex]):not(.sds-card__hit) {
389
+ position: relative;
390
+ z-index: 2;
391
+ }
@@ -0,0 +1,5 @@
1
+ <!-- sds-card-body — ported from kdls-design-system v2.5.1 (0f3edf8), Card.Body. -->
2
+ $ const { class: extraClass = "" } = input;
3
+ <div class=`sds-card__body ${extraClass}`.trim()>
4
+ <${input.renderBody}/>
5
+ </div>
@@ -0,0 +1,94 @@
1
+ <!-- sds-card-collapsible — ported from kdls-design-system v2.5.1 (0f3edf8),
2
+ Card.Collapsible.
3
+
4
+ The one piece of the Card that hydrates, and the reason the Card itself does not: KDLS
5
+ held this state on the Card, so every card paid for it whether it had a collapsible
6
+ section or not. A property listing renders twenty. The state moved here, with the thing
7
+ that uses it.
8
+
9
+ Same shape as sds-accordion — <details> underneath for keyboard, ARIA and a no-JavaScript
10
+ fallback, with the close intercepted so the transition has something to animate. -->
11
+
12
+ import { assertAlign } from "./card-variants.js";
13
+
14
+ class {
15
+ onCreate() {
16
+ this.state = { closing: false };
17
+ }
18
+
19
+ handleSummaryClick(event) {
20
+ const details = this.getEl("root");
21
+ if (!details.open) return;
22
+ event.preventDefault();
23
+ this.state.closing = true;
24
+ }
25
+
26
+ handleTransitionEnd(event) {
27
+ if (event.propertyName !== "grid-template-rows" || !this.state.closing) return;
28
+ this.getEl("root").open = false;
29
+ this.state.closing = false;
30
+ this.input.onOpenChange?.(false);
31
+ }
32
+
33
+ handleToggle(event) {
34
+ if (this.state.closing) return;
35
+ this.input.onOpenChange?.(event.target.open);
36
+ }
37
+ }
38
+
39
+ $ {
40
+ const {
41
+ showLabel = "Exibir mais",
42
+ hideLabel = "Exibir menos",
43
+ // `center`, as in KDLS. With no default the trigger fell to the right, so every
44
+ // collapsible that did not say otherwise was aligned the opposite way from the one
45
+ // the design system ships.
46
+ align = "center",
47
+ defaultOpen = false,
48
+ class: extraClass = "",
49
+ } = input;
50
+
51
+ assertAlign(align);
52
+
53
+ const classes = [
54
+ "sds-card__collapsible",
55
+ state.closing ? "sds-card__collapsible--closing" : "",
56
+ extraClass,
57
+ ].filter(Boolean).join(" ");
58
+
59
+ // Centre is the base rule now, so the modifier is the other one. Same two alignments.
60
+ const triggerClass = [
61
+ "sds-card__collapsible-trigger",
62
+ align === "right" ? "sds-card__collapsible-trigger--right" : "",
63
+ ].filter(Boolean).join(" ");
64
+ }
65
+
66
+ <details
67
+ key="root"
68
+ class=classes
69
+ open=(defaultOpen || null)
70
+ on-transitionend("handleTransitionEnd")
71
+ on-toggle("handleToggle")
72
+ >
73
+ <summary class=triggerClass on-click("handleSummaryClick")>
74
+ <span class="sds-card__collapsible-label--show">${showLabel}</span>
75
+ <span class="sds-card__collapsible-label--hide">${hideLabel}</span>
76
+ <!-- The chevron can be turned off for a card whose header already says it opens some
77
+ other way — KDLS's showIcon, which defaults to on. -->
78
+ <if(input.showIcon !== false)>
79
+ <span class="sds-card__collapsible-chevron" aria-hidden="true">
80
+ // 24px, the icon default and KDLS's. It was `sm` (16px), which next to a
81
+ // title-sm label reads as a different control from every other link button.
82
+ <sds-icon><fluent-chevron-down-regular/></sds-icon>
83
+ </span>
84
+ </if>
85
+ </summary>
86
+
87
+ <div class="sds-card__collapsible-body-wrap">
88
+ <div class="sds-card__collapsible-body-clip">
89
+ <div class="sds-card__collapsible-body">
90
+ <${input.renderBody}/>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </details>
@@ -0,0 +1,12 @@
1
+ <!-- sds-card-footer — ported from kdls-design-system v2.5.1 (0f3edf8), Card.Footer. -->
2
+ import { assertAlign } from "./card-variants.js";
3
+
4
+ $ {
5
+ const { align, class: extraClass = "" } = input;
6
+ assertAlign(align);
7
+ const classes = ["sds-card__footer", align === "right" ? "sds-card__footer--right" : "", extraClass]
8
+ .filter(Boolean).join(" ");
9
+ }
10
+ <div class=classes>
11
+ <${input.renderBody}/>
12
+ </div>
@@ -0,0 +1,30 @@
1
+ <!-- sds-card-header — ported from kdls-design-system v2.5.1 (0f3edf8), where it was
2
+ Card.Header. Title and subtitle render through sds-text, so the card's typography comes
3
+ from the token scale rather than from rules of its own. -->
4
+
5
+ $ {
6
+ const { title, subtitle, left, right, bordered = true, class: extraClass = "" } = input;
7
+ const renderable = (value) => (value && value.renderBody) || value;
8
+ const classes = ["sds-card__header", bordered ? "" : "sds-card__header--borderless", extraClass]
9
+ .filter(Boolean).join(" ");
10
+ }
11
+
12
+ <div class=classes>
13
+ <div class="sds-card__header-top">
14
+ <div class="sds-card__header-left-col">
15
+ <div class="sds-card__header-title-row">
16
+ <if(left)><${renderable(left)}/></if>
17
+ <if(title)>
18
+ <sds-text as="h3" variant="titleSm">${title}</sds-text>
19
+ </if>
20
+ </div>
21
+ <if(subtitle)>
22
+ <sds-text as="p" variant="bodySm">${subtitle}</sds-text>
23
+ </if>
24
+ </div>
25
+ <if(right)>
26
+ <div class="sds-card__header-right"><${renderable(right)}/></div>
27
+ </if>
28
+ </div>
29
+ <${input.renderBody}/>
30
+ </div>
@@ -0,0 +1,17 @@
1
+ <!-- sds-card-info-pair — ported from kdls-design-system v2.5.1 (0f3edf8), Card.InfoPair.
2
+
3
+ A <dl> rather than two divs: a label and its value are a description list, which is what
4
+ lets a screen reader read "Quartos, 3" as one fact instead of two loose strings. KDLS used
5
+ plain divs, so the pairing existed only visually. -->
6
+ $ {
7
+ const { label, value, class: extraClass = "" } = input;
8
+ if (!label) throw new Error("sds-card-info-pair needs a `label`.");
9
+ const renderable = (v) => (v && v.renderBody) || v;
10
+ }
11
+ <dl class=`sds-card__info-pair ${extraClass}`.trim()>
12
+ <dt><sds-text as="span" variant="labelRegular">${label}</sds-text></dt>
13
+ <dd class="sds-card__info-value">
14
+ <if(value !== undefined)>${value}</if>
15
+ <else><${input.renderBody}/></else>
16
+ </dd>
17
+ </dl>
@@ -0,0 +1,12 @@
1
+ <!-- sds-card-info-row — ported from kdls-design-system v2.5.1 (0f3edf8), Card.InfoRow.
2
+ A grid of label/value pairs — the "3 quartos · 2 vagas · 128 m²" row of a property card. -->
3
+ import { assertColumns } from "./card-variants.js";
4
+ $ {
5
+ const { columns = 3, class: extraClass = "" } = input;
6
+ assertColumns(columns);
7
+ const classes = ["sds-card__info-row", `sds-card__info-row--${columns}`, extraClass]
8
+ .filter(Boolean).join(" ");
9
+ }
10
+ <div class=classes>
11
+ <${input.renderBody}/>
12
+ </div>
@@ -0,0 +1,14 @@
1
+ <!-- sds-card-section — ported from kdls-design-system v2.5.1 (0f3edf8), Card.Section.
2
+ A titled block inside a card. The title is an h4 rather than a styled div, so a card with
3
+ several sections gives a screen reader an outline to move through. -->
4
+ $ {
5
+ const { title, bordered = true, class: extraClass = "" } = input;
6
+ const classes = ["sds-card__section", bordered ? "" : "sds-card__section--borderless", extraClass]
7
+ .filter(Boolean).join(" ");
8
+ }
9
+ <section class=classes>
10
+ <if(title)>
11
+ <sds-text as="h4" variant="labelSemiBold">${title}</sds-text>
12
+ </if>
13
+ <${input.renderBody}/>
14
+ </section>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The Card vocabulary.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Card/Card.tsx, where Header,
5
+ * Body, Footer, Section, InfoRow, InfoPair and Collapsible were sub-components. They are
6
+ * sibling tags here and nest as ordinary children rather than attribute tags: the card
7
+ * has nothing to tell them, so there is nothing for it to intercept. Attribute tags are
8
+ * for a parent that must reach its children — see component-rules.md §2c.
9
+ */
10
+ export const CARD_SIZES = ['sm', 'md'];
11
+ export const INFO_ROW_COLUMNS = [2, 3, 4];
12
+ export const ALIGNMENTS = ['right', 'center'];
13
+
14
+ function assertOneOf(label, value, allowed) {
15
+ if (!allowed.includes(value)) {
16
+ throw new Error(`Unknown card ${label}: "${value}". Allowed: ${allowed.join(', ')}`);
17
+ }
18
+ return value;
19
+ }
20
+
21
+ export const assertCardSize = (v) => assertOneOf('size', v, CARD_SIZES);
22
+ // `align` is optional in both places that take it, so undefined passes through.
23
+ export const assertAlign = (v) => (v === undefined ? v : assertOneOf('align', v, ALIGNMENTS));
24
+ export const assertColumns = (v) => assertOneOf('columns', v, INFO_ROW_COLUMNS);