@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,523 @@
1
+ <!-- sds-header — ported from kdls-design-system v2.5.1 (0f3edf8) components/Header/Header.tsx.
2
+
3
+ The bar across the top of a product: a logo, navigation, notifications, a product
4
+ switcher and a profile menu, collapsing to a single bar with a fullscreen menu on a
5
+ phone.
6
+
7
+ THE DIVERGENCE THAT DEFINES THIS PORT. KDLS's header renders <KenloLogo product={...} />
8
+ and nothing else can go there. SDS carries no Kenlo logo, by decision — so `product` is
9
+ gone and the logo is a slot the consumer fills:
10
+
11
+ <sds-header>
12
+ <@logo><img src="/logo.svg" alt="Kenlo"/></@logo>
13
+ </sds-header>
14
+
15
+ Which is what a design system for three different public products needed anyway: Site,
16
+ Landing Page and Blog do not share one mark, and a header that can only draw one is a
17
+ header two of them cannot use. `aria-label` on the products button lost the word Kenlo
18
+ for the same reason.
19
+
20
+ Other divergences:
21
+
22
+ It closes on Escape, not only on a click outside. KDLS binds mousedown on the document
23
+ and nothing else, so a dropdown opened from the keyboard cannot be closed from it.
24
+
25
+ Each panel is labelled by the button that opened it, and each button reports
26
+ aria-expanded and aria-haspopup. KDLS sets aria-expanded on the avatar alone, so the
27
+ notification and product buttons announce nothing about the panels they control.
28
+
29
+ The notification list is a <ul> and unread items say so in words. KDLS marks them with
30
+ a colour and a class, which is nothing at all to a screen reader — and "3 unread" is
31
+ the one thing a notifications button exists to tell you.
32
+
33
+ The mobile breakpoint is the header's own width, matching sds-table's reasoning: a
34
+ header inside a narrow shell is narrow on any screen. -->
35
+
36
+ import { isNarrow } from "../../foundations/breakpoints.js";
37
+ import faqIcon from "../../icons/generated/fluent-question-circle-regular.marko";
38
+ import bellIcon from "../../icons/generated/fluent-alert-regular.marko";
39
+ import gridIcon from "../../icons/generated/fluent-grid-regular.marko";
40
+ import dismissIcon from "../../icons/generated/fluent-dismiss-regular.marko";
41
+ import navIcon from "../../icons/generated/fluent-navigation-regular.marko";
42
+
43
+ class {
44
+ onCreate() {
45
+ this.state = { panel: "none", overlay: "none", narrow: false, measured: false };
46
+ }
47
+
48
+ onMount() {
49
+ const root = this.getEl("root");
50
+
51
+ this.onPointerDown = (event) => {
52
+ if (this.state.panel === "none") return;
53
+ if (root?.contains(event.target)) return;
54
+ this.state.panel = "none";
55
+ };
56
+ this.onKeyDown = (event) => {
57
+ if (event.key !== "Escape") return;
58
+ // KDLS binds mousedown and nothing else: a dropdown opened from the keyboard has no
59
+ // way to be closed from it.
60
+ if (this.state.panel !== "none") this.state.panel = "none";
61
+ if (this.state.overlay !== "none") this.state.overlay = "none";
62
+ };
63
+ document.addEventListener("mousedown", this.onPointerDown);
64
+ document.addEventListener("keydown", this.onKeyDown);
65
+
66
+ if (!root) return;
67
+ const measure = (width) => {
68
+ this.state.narrow = isNarrow(width);
69
+ this.state.measured = true;
70
+ };
71
+ measure(root.getBoundingClientRect().width);
72
+ if (typeof ResizeObserver === "undefined") return;
73
+ this.observer = new ResizeObserver(([entry]) => measure(entry.contentRect.width));
74
+ this.observer.observe(root);
75
+ }
76
+
77
+ onDestroy() {
78
+ document.removeEventListener("mousedown", this.onPointerDown);
79
+ document.removeEventListener("keydown", this.onKeyDown);
80
+ this.observer?.disconnect();
81
+ }
82
+
83
+ /** On a phone every panel is a fullscreen overlay instead of a dropdown. */
84
+ openPanel(which) {
85
+ if (this.state.narrow) {
86
+ this.state.overlay = which;
87
+ return;
88
+ }
89
+ this.state.panel = this.state.panel === which ? "none" : which;
90
+ }
91
+
92
+ closeOverlay() { this.state.overlay = "none"; }
93
+ }
94
+
95
+ $ {
96
+ const {
97
+ navItems = [],
98
+ activeNav,
99
+ notifications = [],
100
+ onClearNotifications,
101
+ products = [],
102
+ user,
103
+ profileMenuItems = [],
104
+ onLogout,
105
+ logoutLabel = "Sair",
106
+ onFAQs,
107
+ showFAQs = true,
108
+ showNotifications = true,
109
+ label = "Cabeçalho",
110
+ class: extraClass = "",
111
+ } = input;
112
+
113
+ const renderable = (value) => (value && value.renderBody) || value;
114
+ const narrow = state.narrow;
115
+ // Until the measurement lands, render both and let the stylesheet choose; afterwards,
116
+ // render only the one that was chosen. Rendering both is what makes the no-JavaScript
117
+ // case work at all, and it lasts one paint.
118
+ const hideWideNav = state.measured && narrow;
119
+ const hideNarrowNav = state.measured && !narrow;
120
+ const panel = state.panel;
121
+ const showProductsButton = products.length > 1;
122
+ const unread = notifications.filter((n) => !n.read).length;
123
+ const initials = user?.initials ?? user?.name?.slice(0, 2).toUpperCase();
124
+ }
125
+
126
+ <!-- `data-measured` is what lets the stylesheet know whether to trust `data-narrow`.
127
+
128
+ Before it is set — the server's render, and the first paint in the browser — neither
129
+ layout has been chosen yet: the header measures its OWN width and cannot do that until
130
+ it exists. The server used to resolve that by always sending the desktop row of links.
131
+ On a phone that is a row of links that does not fit, replaced a moment later; with no
132
+ JavaScript at all it is a row of links that never gets replaced, on a public site.
133
+
134
+ So both are rendered and the stylesheet picks by viewport until the measurement lands.
135
+ A media query is the best guess available without JS and is right on a plain page; the
136
+ measured answer then wins, which is what makes a header inside a narrow drawer narrow
137
+ on a wide screen. -->
138
+ <header
139
+ key="root"
140
+ class=`sds-header ${extraClass}`.trim()
141
+ aria-label=label
142
+ data-measured=(state.measured ? "true" : null)
143
+ data-narrow=(state.measured ? String(narrow) : null)
144
+ >
145
+ <!-- A slot, not a logo. SDS carries no Kenlo mark, and the three public products do not
146
+ share one anyway. -->
147
+ <if(input.logo)>
148
+ <div class="sds-header__logo">
149
+ <${renderable(input.logo)}/>
150
+ </div>
151
+ </if>
152
+
153
+ <if(input.contextSelector)>
154
+ <div class="sds-header__context">
155
+ <${renderable(input.contextSelector)}/>
156
+ </div>
157
+ </if>
158
+
159
+ <!-- A consumer that needs more than `navItems` can describe — a menu with a search box
160
+ in it, say — supplies the whole nav instead. `navItems` still drives the narrow
161
+ overlay, so the slot replaces what is drawn, not what is navigable. -->
162
+ <if(!hideWideNav && input.nav)>
163
+ <div class="sds-header__nav">
164
+ <${renderable(input.nav)}/>
165
+ </div>
166
+ </if>
167
+ <else-if(!hideWideNav && navItems.length)>
168
+ <div class="sds-header__nav">
169
+ <sds-nav orientation="horizontal">
170
+ <for|item| of=navItems>
171
+ <if(item.children)>
172
+ <@item custom>
173
+ <sds-menu
174
+ label=item.label
175
+ triggerActive=(activeNav === item.label)
176
+ items=item.children.map((child) => ({
177
+ label: child.label,
178
+ href: child.href,
179
+ onClick: child.onClick,
180
+ }))
181
+ />
182
+ </@item>
183
+ </if>
184
+ <else>
185
+ <@item href=item.href active=(activeNav === item.label) onClick=item.onClick>
186
+ ${item.label}
187
+ <if(item.isNew)>
188
+ <span class="sds-header__nav-badge">Novo</span>
189
+ </if>
190
+ </@item>
191
+ </else>
192
+ </for>
193
+ </sds-nav>
194
+ </div>
195
+ </else-if>
196
+
197
+ <!-- Nothing to act on, nothing to render: without a user, notifications or products
198
+ this bar is an empty flex item that still takes part in the layout. -->
199
+ <!-- `rightSlot` belongs in this condition: it was left out, so a header given a slot and
200
+ nothing else — no user, no FAQs, no notifications — dropped the slot on the floor
201
+ and rendered as if it had never been passed. -->
202
+ // `!hideNarrowNav && navItems.length` rather than `narrow && …`: before the header has
203
+ // measured itself the narrow button has to be in the DOM for the stylesheet to choose
204
+ // it, so this bar has to exist to hold it.
205
+ <if(user || input.rightSlot || (!hideNarrowNav && navItems.length) || showNotifications && notifications.length || showProductsButton || onFAQs)>
206
+ <div class="sds-header__actions">
207
+ <!-- On a narrow header the row of links is not rendered, so something has to open
208
+ it. KDLS reaches the navigation through the avatar's overlay, which works only
209
+ because every KDLS product has a signed-in user — with no user there was no
210
+ avatar, no overlay and no way to reach the navigation at all. Found by putting
211
+ this header on the portal, where the visitor is nobody. -->
212
+ <if(!hideNarrowNav && navItems.length)>
213
+ <sds-icon-button
214
+ class="sds-header__nav-btn"
215
+ variant="neutral"
216
+ aria-label="Abrir navegação"
217
+ aria-haspopup="dialog"
218
+ aria-expanded=(state.overlay === "profile" ? "true" : "false")
219
+ onClick=() => component.openPanel("profile")
220
+ >
221
+ <@icon><${navIcon}/></@icon>
222
+ </sds-icon-button>
223
+ </if>
224
+
225
+ <if(!narrow && showFAQs)>
226
+ <sds-icon-button aria-label="FAQs" class="sds-header__faqs" onClick=onFAQs>
227
+ <@icon><${faqIcon}/></@icon>
228
+ </sds-icon-button>
229
+ </if>
230
+
231
+ <if(showNotifications)>
232
+ <div class="sds-header__action-wrapper">
233
+ <sds-icon-button
234
+ class="sds-header__icon-btn"
235
+ aria-label=(unread ? `Notificações, ${unread} não lidas` : "Notificações")
236
+ aria-haspopup="dialog"
237
+ aria-expanded=(panel === "notifications" ? "true" : "false")
238
+ onClick=() => component.openPanel("notifications")
239
+ >
240
+ <@icon><${bellIcon}/></@icon>
241
+ </sds-icon-button>
242
+
243
+ <if(!narrow && panel === "notifications")>
244
+ <div class="sds-header__notifications-panel" role="dialog" aria-label="Notificações">
245
+ <div class="sds-header__notifications-header">
246
+ <span class="sds-header__notifications-title">Notificações</span>
247
+ <button type="button" class="sds-header__notifications-clear" on-click(onClearNotifications)>
248
+ Limpar
249
+ </button>
250
+ </div>
251
+ <ul class="sds-header__notifications-list sds-scroll sds-scroll--thin">
252
+ <if(!notifications.length)>
253
+ <li>
254
+ <sds-text as="div" variant="bodySm" class="sds-header__notifications-empty">
255
+ Sem notificações
256
+ </sds-text>
257
+ </li>
258
+ </if>
259
+ <for|item| of=notifications>
260
+ <li
261
+ key=item.id
262
+ class=[
263
+ "sds-header__notification-item",
264
+ !item.read ? "sds-header__notification-item--unread" : "",
265
+ ].filter(Boolean).join(" ")
266
+ >
267
+ <div class="sds-header__notification-content">
268
+ <p class="sds-header__notification-title">
269
+ ${item.title}
270
+ <!-- KDLS marks unread with a colour and a class, which says nothing
271
+ to a screen reader. -->
272
+ <if(!item.read)>
273
+ <span class="sds-visually-hidden">(não lida)</span>
274
+ </if>
275
+ </p>
276
+ <if(item.description)>
277
+ <sds-text as="p" variant="labelRegular" class="sds-header__notification-desc">
278
+ ${item.description}
279
+ </sds-text>
280
+ </if>
281
+ </div>
282
+ <span class="sds-header__notification-time">${item.time}</span>
283
+ </li>
284
+ </for>
285
+ </ul>
286
+ </div>
287
+ </if>
288
+ </div>
289
+ </if>
290
+
291
+ <if(showProductsButton)>
292
+ <div class="sds-header__action-wrapper">
293
+ <sds-icon-button
294
+ class="sds-header__icon-btn"
295
+ aria-label="Produtos"
296
+ aria-haspopup="menu"
297
+ aria-expanded=(panel === "products" ? "true" : "false")
298
+ onClick=() => component.openPanel("products")
299
+ >
300
+ <@icon><${gridIcon}/></@icon>
301
+ </sds-icon-button>
302
+
303
+ <if(!narrow && panel === "products")>
304
+ <div class="sds-header__products-panel" role="menu" aria-label="Produtos">
305
+ <for|product| of=products>
306
+ <a
307
+ key=product.label
308
+ href=product.href
309
+ role="menuitem"
310
+ aria-current=(product.active ? "true" : null)
311
+ class=[
312
+ "sds-header__products-item",
313
+ product.active ? "sds-header__products-item--active" : "",
314
+ ].filter(Boolean).join(" ")
315
+ on-click(product.onClick)
316
+ >
317
+ <if(product.icon)>
318
+ <sds-icon><${renderable(product.icon)}/></sds-icon>
319
+ </if>
320
+ <sds-text as="span" variant="bodyMd">${product.label}</sds-text>
321
+ </a>
322
+ </for>
323
+ </div>
324
+ </if>
325
+ </div>
326
+ </if>
327
+
328
+ <if(input.rightSlot)>
329
+ <${renderable(input.rightSlot)}/>
330
+ </if>
331
+
332
+ <!-- No user, no avatar. KDLS is for products you sign in to, so it renders the button
333
+ unconditionally and an absent user shows up as an avatar with a "?" in it. SDS
334
+ serves Site, Landing Page and Blog, where a visitor is usually nobody — and a
335
+ profile menu for nobody is a control that cannot do anything. -->
336
+ <if(user)>
337
+ <div class="sds-header__action-wrapper">
338
+ <button
339
+ type="button"
340
+ class="sds-header__avatar-btn"
341
+ aria-label="Menu do perfil"
342
+ aria-haspopup="menu"
343
+ aria-expanded=(panel === "profile" ? "true" : "false")
344
+ on-click("openPanel", "profile")
345
+ >
346
+ <sds-avatar src=user?.avatarSrc initials=initials size="sm"/>
347
+ </button>
348
+
349
+ <if(!narrow && panel === "profile")>
350
+ <div class="sds-header__profile-panel" role="menu" aria-label="Menu do perfil">
351
+ <macro|{ }| name="profileInfo">
352
+ <div class="sds-header__profile-header">
353
+ <sds-avatar src=user?.avatarSrc initials=initials size="sm"/>
354
+ <div>
355
+ <p class="sds-header__profile-name">${user?.name}</p>
356
+ <if(user?.code)>
357
+ <p class="sds-header__profile-code">${user.code}</p>
358
+ </if>
359
+ <if(user?.company)>
360
+ <sds-text as="p" variant="labelRegular" class="sds-header__profile-company">
361
+ ${user.company}
362
+ </sds-text>
363
+ </if>
364
+ </div>
365
+ </div>
366
+ </macro>
367
+
368
+ <profileInfo/>
369
+
370
+ <if(profileMenuItems.length || onLogout)>
371
+ <div class="sds-header__profile-menu">
372
+ <for|item| of=profileMenuItems>
373
+ <a key=item.label href=item.href role="menuitem"
374
+ class="sds-header__profile-item" on-click(item.onClick)>
375
+ ${item.label}
376
+ </a>
377
+ </for>
378
+ <if(onLogout)>
379
+ <if(profileMenuItems.length)>
380
+ <hr class="sds-header__profile-divider"/>
381
+ </if>
382
+ <button type="button" role="menuitem"
383
+ class="sds-header__profile-item sds-header__profile-item--danger"
384
+ on-click(onLogout)>
385
+ ${logoutLabel}
386
+ </button>
387
+ </if>
388
+ </div>
389
+ </if>
390
+ </div>
391
+ </if>
392
+ </div>
393
+ </if>
394
+ </div>
395
+ </if>
396
+
397
+ <!-- On a phone every panel is the same fullscreen overlay, which is KDLS's shape: a
398
+ dropdown anchored to a 40px button has nowhere to open on an 812px screen. -->
399
+ <if(narrow && state.overlay !== "none")>
400
+ <div class="sds-header__mobile-overlay" role="dialog" aria-modal="true" aria-label=label>
401
+ <sds-icon-button class="sds-header__mobile-overlay-dismiss" variant="neutral"
402
+ aria-label="Fechar" onClick=() => component.closeOverlay()>
403
+ <@icon><${dismissIcon}/></@icon>
404
+ </sds-icon-button>
405
+
406
+ <div class="sds-header__mobile-overlay-body sds-scroll sds-scroll--thin">
407
+ <if(state.overlay === "notifications")>
408
+ <ul class="sds-header__notifications-list">
409
+ <if(!notifications.length)>
410
+ <li>
411
+ <sds-text as="div" variant="bodySm" class="sds-header__notifications-empty">
412
+ Sem notificações
413
+ </sds-text>
414
+ </li>
415
+ </if>
416
+ <for|item| of=notifications>
417
+ <li key=item.id class=[
418
+ "sds-header__notification-item",
419
+ !item.read ? "sds-header__notification-item--unread" : "",
420
+ ].filter(Boolean).join(" ")>
421
+ <div class="sds-header__notification-content">
422
+ <p class="sds-header__notification-title">
423
+ ${item.title}
424
+ <if(!item.read)>
425
+ <span class="sds-visually-hidden">(não lida)</span>
426
+ </if>
427
+ </p>
428
+ <if(item.description)>
429
+ <sds-text as="p" variant="labelRegular" class="sds-header__notification-desc">
430
+ ${item.description}
431
+ </sds-text>
432
+ </if>
433
+ </div>
434
+ <span class="sds-header__notification-time">${item.time}</span>
435
+ </li>
436
+ </for>
437
+ </ul>
438
+ </if>
439
+
440
+ <else-if(state.overlay === "products")>
441
+ <for|product| of=products>
442
+ <a key=product.label href=product.href class=[
443
+ "sds-header__products-item",
444
+ product.active ? "sds-header__products-item--active" : "",
445
+ ].filter(Boolean).join(" ") on-click(product.onClick)>
446
+ <if(product.icon)>
447
+ <sds-icon><${renderable(product.icon)}/></sds-icon>
448
+ </if>
449
+ <sds-text as="span" variant="bodyMd">${product.label}</sds-text>
450
+ </a>
451
+ </for>
452
+ </else-if>
453
+
454
+ <else>
455
+ <div class="sds-header__mobile-menu">
456
+ <if(user)>
457
+ <div class="sds-header__mobile-menu-block">
458
+ <div class="sds-header__profile-header">
459
+ <sds-avatar src=user?.avatarSrc initials=initials size="sm"/>
460
+ <div>
461
+ <p class="sds-header__profile-name">${user?.name}</p>
462
+ <if(user?.company)>
463
+ <sds-text as="p" variant="labelRegular" class="sds-header__profile-company">
464
+ ${user.company}
465
+ </sds-text>
466
+ </if>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ </if>
471
+
472
+ <for|item| of=profileMenuItems>
473
+ <hr class="sds-header__mobile-divider"/>
474
+ <div class="sds-header__mobile-menu-block">
475
+ <a href=item.href class="sds-header__mobile-row" on-click(item.onClick)>
476
+ <sds-text as="span" variant="bodyMd">${item.label}</sds-text>
477
+ </a>
478
+ </div>
479
+ </for>
480
+
481
+ <for|item| of=navItems>
482
+ <hr class="sds-header__mobile-divider"/>
483
+ <div class="sds-header__mobile-menu-block">
484
+ <if(item.children)>
485
+ <span class="sds-header__mobile-menu-title">${item.label}</span>
486
+ <for|child| of=item.children>
487
+ <a href=child.href class="sds-header__mobile-row" on-click(child.onClick)>
488
+ <sds-text as="span" variant="bodyMd">${child.label}</sds-text>
489
+ </a>
490
+ </for>
491
+ </if>
492
+ <else>
493
+ <a href=item.href class="sds-header__mobile-row" on-click(item.onClick)>
494
+ <sds-text as="span" variant="bodyMd">${item.label}</sds-text>
495
+ </a>
496
+ </else>
497
+ </div>
498
+ </for>
499
+
500
+ <if(showFAQs)>
501
+ <hr class="sds-header__mobile-divider"/>
502
+ <div class="sds-header__mobile-menu-block">
503
+ <button type="button" class="sds-header__mobile-row" on-click(onFAQs)>
504
+ <sds-text as="span" variant="bodyMd">FAQs</sds-text>
505
+ </button>
506
+ </div>
507
+ </if>
508
+
509
+ <if(onLogout)>
510
+ <hr class="sds-header__mobile-divider"/>
511
+ <div class="sds-header__mobile-menu-block">
512
+ <button type="button" class="sds-header__mobile-row sds-header__mobile-row--danger"
513
+ on-click(onLogout)>
514
+ <sds-text as="span" variant="bodyMd">${logoutLabel}</sds-text>
515
+ </button>
516
+ </div>
517
+ </if>
518
+ </div>
519
+ </else>
520
+ </div>
521
+ </div>
522
+ </if>
523
+ </header>
@@ -0,0 +1,44 @@
1
+ /* SDS icon wrapper.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Icon/Icon.css.
3
+
4
+ Divergence: the .kdls-icon--lya rule is removed. It filled glyphs with
5
+ url(#kdls-icon-lya-gradient), a <linearGradient> whose third stop read
6
+ --color-tertiary-50 — a family SDS does not carry. A gradient reaches the icon
7
+ through applyCustomGradient and --color-primary-* now, like everything else. */
8
+
9
+ .sds-icon {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ flex-shrink: 0;
14
+ }
15
+
16
+ /* The generated glyphs carry width="24" height="24" as presentation attributes;
17
+ these rules win over them, which is how one 24px source serves all three sizes. */
18
+ .sds-icon--xs,
19
+ .sds-icon--xs > svg {
20
+ width: var(--icon-size-xs);
21
+ height: var(--icon-size-xs);
22
+ font-size: var(--icon-size-xs);
23
+ }
24
+
25
+ .sds-icon--sm,
26
+ .sds-icon--sm > svg {
27
+ width: var(--icon-size-sm);
28
+ height: var(--icon-size-sm);
29
+ font-size: var(--icon-size-sm);
30
+ }
31
+
32
+ .sds-icon--md,
33
+ .sds-icon--md > svg {
34
+ width: var(--icon-size-md);
35
+ height: var(--icon-size-md);
36
+ font-size: var(--icon-size-md);
37
+ }
38
+
39
+ .sds-icon--lg,
40
+ .sds-icon--lg > svg {
41
+ width: var(--icon-size-lg);
42
+ height: var(--icon-size-lg);
43
+ font-size: var(--icon-size-lg);
44
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Colour families an <sds-icon> accepts. Each resolves to that family's -50 step.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Icon/Icon.tsx (IconColor).
5
+ * Divergence: `secondary`, `tertiary` and `lya` are gone — the first two are Kenlo
6
+ * brand families SDS does not carry, and `lya` was not a family at all but a special
7
+ * case that swapped the solid fill for a gradient. That capability now belongs to
8
+ * applyCustomGradient, which fills --color-primary-* for every component at once.
9
+ */
10
+ export const ICON_COLORS = [
11
+ 'neutral', 'primary',
12
+ 'success', 'alert', 'error',
13
+ 'leaf', 'sand', 'wood', 'clay', 'flower', 'dusk', 'sky', 'sea', 'lake',
14
+ ];
15
+
16
+ export const ICON_SIZES = ['xs', 'sm', 'md', 'lg'];
17
+
18
+ export function assertIconColor(color) {
19
+ if (color === undefined || color === null) return null;
20
+ if (!ICON_COLORS.includes(color)) {
21
+ throw new Error(`Unknown icon color: "${color}". Allowed: ${ICON_COLORS.join(', ')}`);
22
+ }
23
+ return color;
24
+ }
25
+
26
+ export function assertIconSize(size) {
27
+ if (!ICON_SIZES.includes(size)) {
28
+ throw new Error(`Unknown icon size: "${size}". Allowed: ${ICON_SIZES.join(', ')}`);
29
+ }
30
+ return size;
31
+ }
@@ -0,0 +1,56 @@
1
+ <!-- sds-icon — ported from kdls-design-system v2.5.1 (0f3edf8) components/Icon/Icon.tsx.
2
+
3
+ Divergences:
4
+ - the glyph arrives as tag content, not as an `icon` prop holding an already
5
+ instantiated element. React can pass <SearchRegular /> as a value; Marko passes
6
+ markup as a body. The consumer still imports only the glyphs it uses, so the
7
+ tree-shaking property that made the KDLS shape worth keeping is preserved.
8
+ - color="lya" is gone, along with the inline <linearGradient> it rendered. Its
9
+ third stop read --color-tertiary-50, a family SDS does not carry. A gradient
10
+ reaches the icon through applyCustomGradient and --color-primary-* now.
11
+ - an unknown colour or size throws instead of rendering an unstyled glyph.
12
+ - no state: an icon is markup.
13
+ - KDLS's Icon takes an onClick. SDS's does not, and this is not a small point:
14
+ `on-click` bound to a template value makes Marko serialise the component, and
15
+ Icon is on every button, every menu row and every field. Measured on a page of
16
+ fifty: 1,485 bytes of payload, 190 after Brotli, for icons that were never
17
+ clicked. A clickable <span> was also unreachable by keyboard and unnamed to a
18
+ screen reader — so the case it served belongs to sds-icon-button, which has
19
+ focus, Enter and Space and a required label. -->
20
+ import { rehyphenate } from "../../foundations/attrs.js";
21
+ import { assertIconColor, assertIconSize } from "./icon-colors.js";
22
+
23
+ $ {
24
+ const { size = "md", color, class: extraClass = "" } = input;
25
+ assertIconSize(size);
26
+ assertIconColor(color);
27
+ if (input.onClick) {
28
+ throw new Error("sds-icon takes no onClick — a clickable icon is <sds-icon-button>, which is focusable and named.");
29
+ }
30
+ const classes = ["sds-icon", `sds-icon--${size}`, extraClass].filter(Boolean).join(" ");
31
+
32
+ // The `-solid` companion, not the slot. `--color-primary-50` may hold a gradient — that
33
+ // is the whole point of the slot — and a gradient is an <image>: `color` does not accept
34
+ // one, so the declaration is dropped and the glyph silently falls back to whatever it
35
+ // inherited. Every other place that paints with the slot was moved to `-solid` in the
36
+ // gradient sweep; this one was built by string interpolation and the sweep did not see
37
+ // it. Only the primary family has a companion; the rest are plain colours already.
38
+ const paint = color === "primary" ? "--color-primary-50-solid" : `--color-${color}-50`;
39
+
40
+ // Whatever else the caller wrote, forwarded — `aria-label`, `role`, `aria-hidden`,
41
+ // `style`, every `data-*`. The icon used to read them into `input` and drop them, so a
42
+ // decorative icon could not be hidden from a screen reader and a meaningful one could
43
+ // not be named. KDLS forwards its rest props; the port listed four and lost the rest.
44
+ const OWN = new Set(["size", "color", "class", "style", "renderBody", "onClick"]);
45
+ const forwarded = rehyphenate(Object.fromEntries(
46
+ Object.entries(input).filter(([key]) => !OWN.has(key)),
47
+ ));
48
+
49
+ // The caller's own inline style comes last, so it can override the family colour.
50
+ const style = [color ? `color: var(${paint})` : "", input.style ?? ""]
51
+ .filter(Boolean).join("; ") || null;
52
+ }
53
+
54
+ <span ...forwarded class=classes style=style>
55
+ <${input.renderBody}/>
56
+ </span>