@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,298 @@
1
+ /* SDS Nav.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Nav/Nav.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+ *//* ============================================================
5
+ SDS Nav
6
+ Navigation container — horizontal (top bar) or vertical (sidebar)
7
+ Items reuse sds-menu__item styles
8
+ ============================================================ */
9
+
10
+ /* ── Root ── */
11
+ .sds-nav {
12
+ display: flex;
13
+ box-sizing: border-box;
14
+ }
15
+ .sds-nav--horizontal {
16
+ flex-direction: row;
17
+ align-items: center;
18
+ gap: var(--spacing-4);
19
+ /* Divergence: it wraps rather than pushing the page out. Five items at 375px measured
20
+ 427px wide, which is a horizontal scrollbar under the whole document — and the items
21
+ that did not fit were unreachable either way. KDLS lets it overflow.
22
+
23
+ It wraps rather than scrolls, and that was measured too. `overflow-x: auto` was the
24
+ first fix and it broke every menu a nav item holds: CSS computes `overflow-y` to
25
+ `auto` as soon as `overflow-x` is not `visible`, so a 71px nav clipped a 332px menu
26
+ panel to nothing. Wrapping costs height on a crowded row and hides nothing. */
27
+ min-width: 0;
28
+ max-width: 100%;
29
+ flex-wrap: wrap;
30
+ }
31
+ .sds-nav--vertical {
32
+ flex-direction: column;
33
+ align-items: stretch;
34
+ gap: 0;
35
+ width: 100%;
36
+ }
37
+ /* ── Header / Footer slots ── */
38
+ .sds-nav__header {
39
+ flex-shrink: 0;
40
+ padding: var(--spacing-3) var(--spacing-4);
41
+ border-bottom: 1px solid var(--border-static-color);
42
+ }
43
+ .sds-nav__footer {
44
+ flex-shrink: 0;
45
+ margin-top: auto;
46
+ padding: var(--spacing-3) var(--spacing-4);
47
+ border-top: 1px solid var(--border-static-color);
48
+ }
49
+ /* ── Item ── */
50
+ .sds-nav__item {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: var(--spacing-3);
54
+ padding: var(--spacing-2) var(--spacing-4);
55
+ min-height: 40px;
56
+ cursor: pointer;
57
+ color: var(--color-neutral-80);
58
+ font-family: var(--font-family-base);
59
+ font-size: var(--font-size-body-md);
60
+ font-weight: var(--font-weight-regular);
61
+ line-height: var(--lh-body-md);
62
+ border-radius: 11px; /* soft, the install default — schemes at the end */
63
+ border: none;
64
+ background: transparent;
65
+ text-align: left;
66
+ text-decoration: none;
67
+ transition: background-color 120ms ease, color 120ms ease;
68
+ user-select: none;
69
+ box-sizing: border-box;
70
+ white-space: nowrap;
71
+ }
72
+ .sds-nav--vertical .sds-nav__item {
73
+ width: 100%;
74
+ margin: 0 var(--spacing-1);
75
+ width: calc(100% - var(--spacing-2));
76
+ }
77
+ /* `:where(a, button)` rather than `a, button`.
78
+ These are scoped to the interactive elements on purpose — an item that is neither a link
79
+ nor a button is not hoverable and should not pretend to be. But naming the element
80
+ raised the specificity to (0,3,1), which beats every intent rule at (0,3,0) no matter
81
+ what order they are in: an item with `intent="error"` got the generic grey hover instead
82
+ of its red one, while its text colour — declared only on the intent rule — did change.
83
+ Half the style applied, which is worse than none.
84
+ `:where()` contributes nothing to specificity, so the scoping survives and the intent
85
+ rules below win on source order, which is what they were written to do. */
86
+ :where(a, button).sds-nav__item:hover:not(.sds-nav__item--disabled) {
87
+ background-color: var(--color-opacity-05);
88
+ }
89
+ :where(a, button).sds-nav__item:focus-visible {
90
+ outline: none;
91
+ background-color: var(--color-opacity-08);
92
+ box-shadow: var(--focus-ring-inset);
93
+ }
94
+ :where(a, button).sds-nav__item:active {
95
+ background-color: var(--color-opacity-10);
96
+ }
97
+ .sds-nav__item--active {
98
+ color: var(--color-neutral-90);
99
+ font-weight: var(--font-weight-bold);
100
+ }
101
+ .sds-nav__item--active:hover:not(.sds-nav__item--disabled) {
102
+ background-color: var(--color-opacity-05);
103
+ }
104
+ .sds-nav__item--disabled {
105
+ opacity: 0.4;
106
+ cursor: not-allowed;
107
+ }
108
+ /* ── Intent items ── */
109
+ .sds-nav__item--primary:hover:not(.sds-nav__item--disabled),
110
+ .sds-nav__item--primary:focus-visible {
111
+ background: var(--color-primary-10);
112
+ color: var(--color-primary-90-solid);
113
+ }
114
+ .sds-nav__item--primary:active {
115
+ background: var(--color-primary-30);
116
+ color: var(--color-primary-90-solid);
117
+ }
118
+ .sds-nav__item--error:hover:not(.sds-nav__item--disabled),
119
+ .sds-nav__item--error:focus-visible {
120
+ background-color: var(--color-error-10);
121
+ color: var(--color-error-10-on);
122
+ }
123
+ .sds-nav__item--error:active {
124
+ background-color: var(--color-error-30);
125
+ color: var(--color-error-30-on);
126
+ }
127
+ .sds-nav__item--success:hover:not(.sds-nav__item--disabled),
128
+ .sds-nav__item--success:focus-visible {
129
+ background-color: var(--color-success-10);
130
+ color: var(--color-success-10-on);
131
+ }
132
+ .sds-nav__item--success:active {
133
+ background-color: var(--color-success-30);
134
+ color: var(--color-success-30-on);
135
+ }
136
+ .sds-nav__item--alert:hover:not(.sds-nav__item--disabled),
137
+ .sds-nav__item--alert:focus-visible {
138
+ background-color: var(--color-alert-10);
139
+ color: var(--color-alert-10-on);
140
+ }
141
+ .sds-nav__item--alert:active {
142
+ background-color: var(--color-alert-30);
143
+ color: var(--color-alert-30-on);
144
+ }
145
+ /* ── Item slots ── */
146
+ .sds-nav__item-icon {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ flex-shrink: 0;
150
+ color: inherit;
151
+ }
152
+ .sds-nav__item-label {
153
+ flex: 1;
154
+ white-space: nowrap;
155
+ }
156
+ .sds-nav__item-tooltip-icon {
157
+ display: inline-flex;
158
+ align-items: center;
159
+ flex-shrink: 0;
160
+ }
161
+ /* ── Separator ── */
162
+ .sds-nav__separator {
163
+ height: 1px;
164
+ background: var(--color-neutral-20);
165
+ margin: var(--spacing-1) var(--spacing-3);
166
+ flex-shrink: 0;
167
+ }
168
+ .sds-nav--horizontal .sds-nav__separator {
169
+ width: 1px;
170
+ height: 24px;
171
+ margin: 0 var(--spacing-2);
172
+ }
173
+ /* ── Dark mode ── */
174
+ [data-theme="dark"] .sds-nav__header {
175
+ border-bottom-color: var(--border-interactive-color);
176
+ }
177
+ [data-theme="dark"] .sds-nav__footer {
178
+ border-top-color: var(--border-interactive-color);
179
+ }
180
+ [data-theme="dark"] .sds-nav__separator {
181
+ background: var(--border-interactive-color);
182
+ }
183
+
184
+ /* ── List semantics ────────────────────────────────────────────────────────────
185
+ SDS renders the nav as a real <ul> of <li>, which KDLS did not: it laid the items
186
+ out as bare elements. The list is the correct structure — a screen reader announces
187
+ "list, 5 items" and gives the visitor a way to move through them — and these rules
188
+ are what make it look the way KDLS's did. */
189
+ /* The gap between ITEMS lives here, not on the nav.
190
+ KDLS has no list element: its items are direct children of the nav, so the nav's own
191
+ 16px gap separated them. Here they sit inside a <ul>, so that gap fell between the
192
+ header, the list and the footer instead, and the list supplied 4px of its own —
193
+ measured 4px between two adjacent items where KDLS has 16. Vertical was worse: KDLS
194
+ stacks its rows flush and here every row gained 4px it never had. */
195
+ .sds-nav__list {
196
+ list-style: none;
197
+ margin: 0;
198
+ padding: 0;
199
+ display: flex;
200
+ gap: var(--spacing-4);
201
+ }
202
+
203
+ /* The items live on the list, so the list is what has to wrap — putting it on .sds-nav
204
+ changed nothing, and five items still measured 448px inside a 375px window. */
205
+ .sds-nav--horizontal .sds-nav__list {
206
+ flex-wrap: wrap;
207
+ min-width: 0;
208
+ /* And the items keep their own height. A flex container stretches its children by
209
+ default, so inside a 56px header the <li> grew to 56 and pulled the row with it — a
210
+ 40px control with a 56px hover box around it. KDLS has no list element here at all;
211
+ the <ul>/<li> is this port's addition, for the "list, 5 items" a screen reader
212
+ announces, and it must not change what anything looks like. */
213
+ align-items: center;
214
+ }
215
+
216
+ .sds-nav--horizontal .sds-nav__list-item {
217
+ align-items: center;
218
+ }
219
+
220
+ .sds-nav--vertical .sds-nav__list {
221
+ flex-direction: column;
222
+ /* Flush, as in KDLS: a vertical nav is a list of rows and a gap between them reads as a
223
+ gap between groups. The rows already have their own padding. */
224
+ gap: 0;
225
+ }
226
+
227
+ .sds-nav__list-item {
228
+ display: flex;
229
+ min-width: 0;
230
+ }
231
+
232
+ .sds-nav--vertical .sds-nav__list-item {
233
+ width: 100%;
234
+ }
235
+
236
+ /* The separator is a real <hr>, which KDLS draws as a div — so the element's own border has
237
+ to go before it can be a line. The colour reads --border-static-color rather than
238
+ --color-neutral-20: the same value today, but this is a border and that is the token for
239
+ one, so it follows if the border ever stops being neutral-20.
240
+
241
+ The horizontal inset stays KDLS's: the base rule insets a vertical nav's line by 12px,
242
+ and the horizontal variant overrides it anyway. */
243
+ .sds-nav__separator {
244
+ border: 0;
245
+ align-self: stretch;
246
+ background: var(--border-static-color);
247
+ margin: var(--spacing-1) var(--spacing-3);
248
+ width: 1px;
249
+ }
250
+
251
+ .sds-nav--vertical .sds-nav__separator {
252
+ width: auto;
253
+ height: 1px;
254
+ margin: var(--spacing-1) 0;
255
+ align-self: auto;
256
+ flex: 1;
257
+ }
258
+
259
+ /* Native chrome reset.
260
+ SDS renders these rows as real <button> and <a> elements, which KDLS did not — it used
261
+ bare <div>s with click handlers. A button brings keyboard activation, focus and the
262
+ disabled state from the browser, and these lines are the whole price: without them the
263
+ browser's own button border and background show through the design.
264
+
265
+ `width: 100%` was in here and does not belong: a width is layout, not chrome, and the
266
+ browser supplies no width to reset. KDLS sets it only on a vertical nav, where the row
267
+ should fill the column. Incondicional, it stretched every horizontal item to the width
268
+ of its slot, so the hover box was far wider than the label inside it. The same shape of
269
+ mistake as the ColorPicker's reset erasing a border the design had asked for — a chrome
270
+ reset names the properties the design is silent about, and nothing else. */
271
+ .sds-nav__item {
272
+ appearance: none;
273
+ border: 0;
274
+ background: none;
275
+ text-align: left;
276
+ text-decoration: none;
277
+ }
278
+
279
+ /* ── Corner radius: the 40px family ───────────────────────────────────────────
280
+ A row in a menu, a select or a dropdown is 40px tall, which makes it the same control
281
+ as the Pill and the navButton — so it takes the same four values. Rafael, pointing at
282
+ the theme bar's own menu: these are reactive, but not in the right way; they should be
283
+ 40px like the navButton and have the radius resolved here.
284
+
285
+ height 40px flat 0 soft 11px medium 15px rounded 20px (a capsule)
286
+ as a fraction 0 0.275 0.375 0.5
287
+
288
+ `--radius-sm` was what they had, and it tops out at 16px — so `rounded` drew a rounded
289
+ rectangle where every other 40px control in the system drew a capsule. See Pill.css for
290
+ the arithmetic; the numbers are a fraction of the control's own height, which is what
291
+ keeps the family reading as one thing.
292
+
293
+ The bare value is `soft`'s, because an install that never picks a scheme has no
294
+ attribute at all and falls through to it. */
295
+ [data-radius="flat"] .sds-nav__item { border-radius: 0; }
296
+ [data-radius="soft"] .sds-nav__item { border-radius: 11px; }
297
+ [data-radius="medium"] .sds-nav__item { border-radius: 15px; }
298
+ [data-radius="rounded"] .sds-nav__item { border-radius: var(--radius-full); }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The Nav vocabulary.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Nav/Nav.tsx.
5
+ * `intent` is shared with Menu — the two use the same set, and a nav item that deletes
6
+ * something should look like a menu item that deletes something.
7
+ *
8
+ * Divergence: `primary` is the slot, as it was. The badge a nav item can carry used
9
+ * variant="primary" in KDLS, which no longer exists as a Badge colour; it is
10
+ * `scheme-color` here, which is the same thing under its surviving name.
11
+ */
12
+ export const NAV_ORIENTATIONS = ['horizontal', 'vertical'];
13
+ export const NAV_INTENTS = ['primary', 'neutral', 'error', 'success', 'alert'];
14
+
15
+ function assertOneOf(label, value, allowed) {
16
+ if (!allowed.includes(value)) {
17
+ throw new Error(`Unknown nav ${label}: "${value}". Allowed: ${allowed.join(', ')}`);
18
+ }
19
+ return value;
20
+ }
21
+
22
+ export const assertOrientation = (v) => assertOneOf('orientation', v, NAV_ORIENTATIONS);
23
+ export const assertIntent = (v) => (v === undefined ? v : assertOneOf('intent', v, NAV_INTENTS));
@@ -0,0 +1,164 @@
1
+ <!-- sds-nav — ported from kdls-design-system v2.5.1 (0f3edf8) components/Nav/Nav.tsx.
2
+
3
+ Items arrive as attribute tags, not as nested children:
4
+
5
+ <sds-nav orientation="vertical">
6
+ <@item active href="/">Início</@item>
7
+ <@item separator/>
8
+ <@item icon=searchIcon badge="3">Buscar</@item>
9
+ </sds-nav>
10
+
11
+ A separator is an item that draws a line instead of a label, rather than a tag of its
12
+ own. Marko groups attribute tags by name, so <@item> and <@separator> would arrive as two
13
+ separate arrays and the order between them would be lost — every separator would sink to
14
+ the bottom of the list. One tag keeps the order the author wrote.
15
+
16
+ KDLS passed orientation down through a React context that Menu also read. Marko 5 has no
17
+ context, so the parent takes its items as data and renders them itself — see
18
+ `.claude/component-rules.md` §2c, where this is decided once for every compound component
19
+ rather than reinvented per component.
20
+
21
+ Each item is an <a> when it has an href and a <button> otherwise, which is what decides
22
+ whether it lands in the browser's link list or its button list.
23
+
24
+ `tooltip`, `tooltipIcon` and `tooltipPosition` are here now that Tooltip is. An item can
25
+ explain itself two ways, as in KDLS: the label itself becomes the trigger, or a separate
26
+ info affordance sits beside it. Tooltip's own touch question is still open and is recorded
27
+ there, not repeated here. -->
28
+
29
+ import { assertOrientation, assertIntent } from "./nav-variants.js";
30
+
31
+ class {
32
+ handleItemClick(item, event) {
33
+ if (item.disabled) {
34
+ event.preventDefault();
35
+ return;
36
+ }
37
+ item.onClick?.(event);
38
+ }
39
+ }
40
+
41
+ $ {
42
+ const { orientation = "horizontal", header, footer, class: extraClass = "" } = input;
43
+ assertOrientation(orientation);
44
+
45
+ const renderable = (value) => (value && value.renderBody) || value;
46
+
47
+ // Attribute tags arrive as a single object when used once and an array when repeated.
48
+ const asList = (value) => (value === undefined ? [] : [].concat(value));
49
+ const items = asList(input.item);
50
+
51
+ for (const item of items) assertIntent(item.intent);
52
+
53
+ // Marko camel-cases a hyphenated attribute on a custom tag, so both spellings arrive
54
+ // and a consumer should not have to know which.
55
+ const ariaLabel = input["aria-label"] ?? input.ariaLabel ?? null;
56
+
57
+ const classes = ["sds-nav", `sds-nav--${orientation}`, extraClass].filter(Boolean).join(" ");
58
+
59
+ const classFor = (item) => [
60
+ "sds-nav__item",
61
+ item.active ? "sds-nav__item--active" : "",
62
+ item.disabled ? "sds-nav__item--disabled" : "",
63
+ item.intent ? `sds-nav__item--${item.intent}` : "",
64
+ item.class ?? "",
65
+ ].filter(Boolean).join(" ");
66
+ }
67
+
68
+ <nav class=classes aria-label=ariaLabel>
69
+ <if(header)>
70
+ <div class="sds-nav__header"><${renderable(header)}/></div>
71
+ </if>
72
+
73
+ <ul class="sds-nav__list">
74
+ <for|entry| of=items>
75
+ <li class="sds-nav__list-item">
76
+ <if(entry.separator)>
77
+ <hr class="sds-nav__separator"/>
78
+ </if>
79
+ <else>
80
+ <macro|{ }| name="content">
81
+ <if(entry.icon)>
82
+ <span class="sds-nav__item-icon" aria-hidden="true">
83
+ <sds-icon><${renderable(entry.icon)}/></sds-icon>
84
+ </span>
85
+ </if>
86
+ <if(entry.tooltip && !entry.tooltipIcon)>
87
+ <sds-tooltip content=entry.tooltip placement=(entry.tooltipPosition ?? "top")>
88
+ <span class="sds-nav__item-label"><${entry.renderBody}/></span>
89
+ </sds-tooltip>
90
+ </if>
91
+ <else>
92
+ <span class="sds-nav__item-label"><${entry.renderBody}/></span>
93
+ </else>
94
+ <if(entry.tooltip && entry.tooltipIcon)>
95
+ <span class="sds-nav__item-tooltip-icon">
96
+ <sds-tooltip content=entry.tooltip placement=(entry.tooltipPosition ?? "top")/>
97
+ </span>
98
+ </if>
99
+ <if(entry.badge)>
100
+ <sds-badge label=entry.badge variant="scheme-color" appearance="tone-50" size="sm"/>
101
+ </if>
102
+ </macro>
103
+
104
+ <if(entry.href)>
105
+ <a
106
+ class=classFor(entry)
107
+ href=(entry.disabled ? null : entry.href)
108
+ target=(entry.targetBlank ? "_blank" : null)
109
+ rel=(entry.targetBlank ? "noopener noreferrer" : null)
110
+ aria-disabled=(entry.disabled ? "true" : null)
111
+ aria-current=(entry.active ? "page" : null)
112
+ on-click("handleItemClick", entry)
113
+ >
114
+ <content/>
115
+ </a>
116
+ </if>
117
+ <else-if(entry.onClick)>
118
+ <button
119
+ type="button"
120
+ class=classFor(entry)
121
+ disabled=(entry.disabled || null)
122
+ aria-current=(entry.active ? "page" : null)
123
+ on-click("handleItemClick", entry)
124
+ >
125
+ <content/>
126
+ </button>
127
+ </else-if>
128
+ <else-if(entry.custom)>
129
+ <!-- The item is already a control — a Menu, most often. KDLS does not wrap
130
+ those at all: its Header renders <Menu> as a direct child of <Nav>, with
131
+ no Nav.Item around it. Wrapping added a second box with its own padding,
132
+ so a 40px trigger sat inside a 56px row and the hover area was bigger
133
+ than the thing you were pointing at.
134
+
135
+ The <li> stays, because the list semantics are this port's addition and
136
+ must not change what anything looks like: it carries no padding of its
137
+ own, so it is transparent to layout. -->
138
+ <content/>
139
+ </else-if>
140
+ <else>
141
+ <!-- Divergence: an item that neither navigates nor acts is not a control.
142
+ KDLS makes every item without an href a <button>, and that button is
143
+ empty of behaviour — nothing to click, but a tab stop, a hover state and
144
+ a focus ring all the same. Worse where the item holds a control of its
145
+ own: the Header puts an sds-menu in one, so a <button> ended up inside a
146
+ <button>, which is invalid HTML the browser silently rearranges, and it
147
+ drew a second hover area around the first. Rafael saw the outer one. -->
148
+ <div
149
+ class=classFor(entry)
150
+ aria-current=(entry.active ? "page" : null)
151
+ aria-disabled=(entry.disabled ? "true" : null)
152
+ >
153
+ <content/>
154
+ </div>
155
+ </else>
156
+ </else>
157
+ </li>
158
+ </for>
159
+ </ul>
160
+
161
+ <if(footer)>
162
+ <div class="sds-nav__footer"><${renderable(footer)}/></div>
163
+ </if>
164
+ </nav>
@@ -0,0 +1,78 @@
1
+ /* SDS Notification.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Notification/Notification.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-notification--primary
7
+ */.sds-notification {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ font-family: var(--font-family-base);
12
+ font-weight: var(--font-weight-semibold);
13
+ white-space: nowrap;
14
+ user-select: none;
15
+ flex-shrink: 0;
16
+ }
17
+ /* ── Fill: primary ── */
18
+ .sds-notification--primary {
19
+ background: var(--color-primary-50);
20
+ color: var(--color-on-primary);
21
+ }
22
+ /* ── Fill: neutral ── */
23
+ .sds-notification--neutral {
24
+ background-color: var(--color-neutral-80);
25
+ color: var(--color-white);
26
+ }
27
+ /* ── Fill: error ── */
28
+ .sds-notification--error {
29
+ background-color: var(--color-error-50);
30
+ color: var(--color-white);
31
+ }
32
+ /* ── Fill: alert ── */
33
+ .sds-notification--alert {
34
+ background-color: var(--color-alert-50);
35
+ color: var(--color-white);
36
+ }
37
+ /* ── Fill: success ── */
38
+ .sds-notification--success {
39
+ background-color: var(--color-success-50);
40
+ color: var(--color-white);
41
+ }
42
+ /* ── Dot (size 8) ── */
43
+ .sds-notification--dot {
44
+ width: 8px;
45
+ height: 8px;
46
+ border-radius: var(--radius-full);
47
+ }
48
+ /* ── Size 16 ── */
49
+ .sds-notification--16 {
50
+ height: 16px;
51
+ min-width: 16px;
52
+ padding: 0 var(--spacing-1);
53
+ border-radius: var(--radius-full);
54
+ font-size: 10px;
55
+ line-height: var(--lh-label-sm);
56
+ }
57
+ /* ── Size 20 ── */
58
+ .sds-notification--20 {
59
+ height: 20px;
60
+ min-width: 20px;
61
+ padding: 0 5px;
62
+ border-radius: var(--radius-full);
63
+ font-size: 11px;
64
+ line-height: var(--lh-body-sm);
65
+ }
66
+ /* ── Size 24 ── */
67
+ .sds-notification--24 {
68
+ height: 24px;
69
+ min-width: 24px;
70
+ padding: 0 6px;
71
+ border-radius: var(--radius-full);
72
+ font-size: 12px;
73
+ line-height: var(--lh-body-sm);
74
+ }
75
+ /* ── Variant: square (fixed radius proportional to size) ── */
76
+ .sds-notification--16.sds-notification--square { border-radius: 4px; }
77
+ .sds-notification--20.sds-notification--square { border-radius: 6px; }
78
+ .sds-notification--24.sds-notification--square { border-radius: 8px; }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The Notification vocabulary.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Notification/Notification.tsx.
5
+ * Divergence: none. `primary` was already the slot.
6
+ */
7
+ export const NOTIFICATION_SIZES = [8, 16, 20, 24];
8
+ export const NOTIFICATION_VARIANTS = ['primary', 'neutral', 'error', 'alert', 'success'];
9
+ export const NOTIFICATION_SHAPES = ['circle', 'square'];
10
+
11
+ function assertOneOf(label, value, allowed) {
12
+ if (!allowed.includes(value)) {
13
+ throw new Error(`Unknown notification ${label}: "${value}". Allowed: ${allowed.join(', ')}`);
14
+ }
15
+ return value;
16
+ }
17
+
18
+ export const assertNotificationSize = (v) => assertOneOf('size', v, NOTIFICATION_SIZES);
19
+ export const assertNotificationVariant = (v) => assertOneOf('variant', v, NOTIFICATION_VARIANTS);
20
+ export const assertNotificationShape = (v) => assertOneOf('shape', v, NOTIFICATION_SHAPES);
21
+
22
+ /**
23
+ * What the badge reads out. Undefined when there is no count — a dot carries no number,
24
+ * and a badge that shows nothing should not announce anything either.
25
+ */
26
+ export function notificationLabel({ count, max = 99, showPlus = false }) {
27
+ if (count === undefined) return '';
28
+ if (count > max) return `+${max}`;
29
+ return showPlus ? `+${count}` : String(count);
30
+ }
@@ -0,0 +1,55 @@
1
+ <!-- sds-notification — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/Notification/Notification.tsx.
3
+
4
+ A count badge. At size 8 it is a bare dot with no number, and is hidden from assistive
5
+ tech: the thing it marks carries the meaning, and "notification" announced next to it
6
+ says nothing a screen reader user can act on.
7
+
8
+ Divergences: the [data-color-scheme="lya"] override on the primary variant is gone with
9
+ the scheme; primary reads the slot, as it already did. Validation throws on an unknown
10
+ size, variant or shape rather than rendering an unstyled badge. -->
11
+
12
+ import { assertNotificationSize, assertNotificationVariant, assertNotificationShape,
13
+ notificationLabel } from "./notification-variants.js";
14
+
15
+ $ {
16
+ const {
17
+ size = 20,
18
+ variant = "primary",
19
+ shape = "circle",
20
+ count,
21
+ max = 99,
22
+ showPlus = false,
23
+ showZero = false,
24
+ class: extraClass = "",
25
+ } = input;
26
+
27
+ assertNotificationSize(size);
28
+ assertNotificationVariant(variant);
29
+ assertNotificationShape(shape);
30
+
31
+ // At zero the badge disappears unless the caller says otherwise — a "0" badge is
32
+ // usually noise, but a counter that must show it exists is a real case.
33
+ const hidden = size !== 8 && count === 0 && !showZero;
34
+
35
+ const label = notificationLabel({ count, max, showPlus });
36
+
37
+ const classes = [
38
+ "sds-notification",
39
+ size === 8 ? "sds-notification--dot" : `sds-notification--${size}`,
40
+ `sds-notification--${variant}`,
41
+ shape === "square" && size !== 8 ? "sds-notification--square" : "",
42
+ extraClass,
43
+ ].filter(Boolean).join(" ");
44
+ }
45
+
46
+ <if(!hidden)>
47
+ <if(size === 8)>
48
+ <span class=classes aria-hidden="true"/>
49
+ </if>
50
+ <else>
51
+ <span class=classes aria-label=(count !== undefined ? `${label} notificações` : null)>
52
+ ${label}
53
+ </span>
54
+ </else>
55
+ </if>