@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,78 @@
1
+ /* SDS Pagination.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Pagination/Pagination.css
3
+ by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
+ *//* ── Pagination ─────────────────────────────────────────────────────────
5
+ 3-column grid: info (left) | page controls (center) | jump/spacer (right).
6
+ BEM root: .sds-pagination
7
+ ─────────────────────────────────────────────────────────────────── */
8
+
9
+ .sds-pagination {
10
+ display: grid;
11
+ grid-template-columns: 1fr auto 1fr;
12
+ align-items: center;
13
+ gap: var(--spacing-4);
14
+ }
15
+ @media (max-width: 799px) {
16
+ .sds-pagination {
17
+ grid-template-columns: 1fr;
18
+ justify-items: center;
19
+ gap: var(--spacing-3);
20
+ }
21
+ .sds-pagination__spacer {
22
+ display: none;
23
+ }
24
+ }
25
+ /* ── Left info ("Showing [10 ▼] of 48 items") ── */
26
+ .sds-pagination__info {
27
+ display: flex;
28
+ align-items: center;
29
+ gap: var(--spacing-2);
30
+ }
31
+ .sds-pagination__info-text {
32
+ white-space: nowrap;
33
+ }
34
+ /* ── Center: prev button + pills + next button ── */
35
+ .sds-pagination__controls {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: var(--spacing-4); /* 16px between arrows and page group */
39
+ /* Divergence: the arrows stay put and the numbers scroll between them.
40
+ Ten pages at 44px need 500px before the arrows and the gaps, so on a phone this row
41
+ did not fit — measured at 375px, it reached 422 and took the whole page sideways
42
+ with it. KDLS has the same row and says nothing about what happens when it is too
43
+ wide. Scrolling the numbers keeps prev and next where the thumb expects them. */
44
+ max-width: 100%;
45
+ min-width: 0;
46
+ }
47
+ .sds-pagination__pages {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: var(--spacing-2); /* 8px between pills and ellipsis */
51
+ min-width: 0;
52
+ overflow-x: auto;
53
+ /* Room for the scrollbar so it does not sit on top of the pills. */
54
+ padding-bottom: var(--spacing-1);
55
+ }
56
+ .sds-pagination__controls > .sds-icon-button,
57
+ .sds-pagination__controls > button {
58
+ flex-shrink: 0;
59
+ }
60
+ .sds-pagination__controls .sds-pill {
61
+ width: 44px;
62
+ padding: 0;
63
+ justify-content: center;
64
+ }
65
+ /* ── Ellipsis between page groups ── */
66
+ .sds-pagination__ellipsis {
67
+ font-family: var(--font-family-base);
68
+ font-size: var(--font-size-body-sm);
69
+ color: var(--body-color);
70
+ padding: 0 var(--spacing-1);
71
+ display: inline-flex;
72
+ align-items: center;
73
+ height: 40px;
74
+ }
75
+ /* ── Right spacer — mirrors info width to keep controls centered ── */
76
+ .sds-pagination__spacer {
77
+ /* intentionally empty */
78
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Which page numbers a pagination control shows.
3
+ *
4
+ * Ported verbatim from kdls-design-system v2.5.1 (0f3edf8) components/Pagination/Pagination.tsx.
5
+ * Pulled out because it is pure and because the edge cases — the first pages, the last
6
+ * pages, a total small enough that no gap is needed — are exactly where an off-by-one
7
+ * hides and nobody notices until a customer cannot reach page 2.
8
+ */
9
+
10
+ /** Every page size the component will accept. */
11
+ export const PAGE_SIZES = [10, 20, 50, 500];
12
+
13
+ /**
14
+ * The sizes the selector offers, which is not the same list.
15
+ *
16
+ * 500 is accepted and never offered — KDLS marks it `@internal`, for a caller that sets it
17
+ * itself (its own icon catalogue does). Offering it put "500 itens por página" in front of
18
+ * a visitor as an ordinary choice, and a listing of five hundred property cards is a page
19
+ * that takes seconds to paint and cannot be scrolled back from.
20
+ */
21
+ export const PAGE_SIZE_OPTIONS = [10, 20, 50];
22
+ export const SIBLING_COUNTS = [1, 2, 3];
23
+
24
+ export const GAP = '…';
25
+
26
+ /** How many pages a total splits into. Always at least one, even with nothing to show. */
27
+ export const pageCount = (totalItems, pageSize) => Math.max(1, Math.ceil(totalItems / pageSize));
28
+
29
+ /**
30
+ * The first page, the last page, and `siblingCount` pages either side of the current one.
31
+ * A gap marker stands wherever the run is broken.
32
+ */
33
+ export function buildPages(page, totalPages, siblingCount = 1) {
34
+ const near = new Set(
35
+ [
36
+ 1,
37
+ totalPages,
38
+ ...Array.from({ length: siblingCount * 2 + 1 }, (_, i) => page - siblingCount + i),
39
+ ].filter((p) => p >= 1 && p <= totalPages),
40
+ );
41
+
42
+ const result = [];
43
+ let previous = null;
44
+
45
+ for (const p of [...near].sort((a, b) => a - b)) {
46
+ if (previous !== null && p - previous > 1) result.push(GAP);
47
+ result.push(p);
48
+ previous = p;
49
+ }
50
+
51
+ return result;
52
+ }
53
+
54
+ export function assertPageSize(size) {
55
+ if (!PAGE_SIZES.includes(size)) {
56
+ throw new Error(`Unknown page size: "${size}". Allowed: ${PAGE_SIZES.join(', ')}`);
57
+ }
58
+ return size;
59
+ }
60
+
61
+ export function assertSiblingCount(count) {
62
+ if (!SIBLING_COUNTS.includes(count)) {
63
+ throw new Error(`Unknown sibling count: "${count}". Allowed: ${SIBLING_COUNTS.join(', ')}`);
64
+ }
65
+ return count;
66
+ }
@@ -0,0 +1,141 @@
1
+ <!-- sds-pagination — ported from kdls-design-system v2.5.1 (0f3edf8)
2
+ components/Pagination/Pagination.tsx.
3
+
4
+ Which page numbers to show is in pagination-pages.js, tested on its own: the edges are
5
+ exactly where an off-by-one hides until a customer cannot reach page 2.
6
+
7
+ It hydrates, because the buttons have to navigate. It holds no state — the current page
8
+ comes from the caller, which is the thing that knows what it is showing.
9
+
10
+ Divergences:
11
+
12
+ It is a <nav> with an aria-label, and the page buttons carry aria-current. KDLS rendered
13
+ loose Pills, so a screen reader met a row of unrelated buttons with no way to tell which
14
+ page it was on.
15
+
16
+ The gap is aria-hidden. An ellipsis read aloud says nothing useful, and the pages either
17
+ side of it already say what is skipped. -->
18
+
19
+ import { buildPages, pageCount, GAP, PAGE_SIZES, PAGE_SIZE_OPTIONS, assertPageSize, assertSiblingCount }
20
+ from "./pagination-pages.js";
21
+
22
+ class {
23
+ go(page) {
24
+ const { currentPage, totalItems, pageSize = 20, disabled, onChange } = this.input;
25
+ if (disabled) return;
26
+ const total = pageCount(totalItems, pageSize);
27
+ if (page < 1 || page > total || page === currentPage) return;
28
+ onChange?.(page, pageSize);
29
+ }
30
+
31
+ previous() { this.go(this.input.currentPage - 1); }
32
+ next() { this.go(this.input.currentPage + 1); }
33
+
34
+ handleSizeChange(size) {
35
+ const next = Number(size);
36
+ // Changing the page size moves the visitor back to the first page: staying on page 9
37
+ // of a list that now has four is how a "no results" screen appears out of nowhere.
38
+ this.input.onChange?.(1, next);
39
+ }
40
+ }
41
+
42
+ $ {
43
+ const {
44
+ currentPage,
45
+ totalItems,
46
+ pageSize = 20,
47
+ siblingCount = 2,
48
+ showSizeChanger = false,
49
+ hideOnSinglePage = false,
50
+ disabled = false,
51
+ label = "Paginação",
52
+ class: extraClass = "",
53
+ } = input;
54
+
55
+ if (typeof currentPage !== "number") throw new Error("sds-pagination needs a numeric `currentPage`.");
56
+ if (typeof totalItems !== "number") throw new Error("sds-pagination needs a numeric `totalItems`.");
57
+ assertPageSize(pageSize);
58
+ assertSiblingCount(siblingCount);
59
+
60
+ const totalPages = pageCount(totalItems, pageSize);
61
+ // No items, no page buttons. `pageCount` clamps to 1 so that nothing divides by zero,
62
+ // and the clamp leaked into the buttons: an empty list rendered an active "1", telling
63
+ // the visitor they are on page 1 of a list that has nothing on any page. KDLS renders
64
+ // none, and the info text beside it already says there is nothing.
65
+ const pages = totalItems === 0 ? [] : buildPages(currentPage, totalPages, siblingCount);
66
+ const hidden = hideOnSinglePage && totalPages <= 1;
67
+
68
+ const first = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
69
+ const last = Math.min(currentPage * pageSize, totalItems);
70
+ }
71
+
72
+ <if(!hidden)>
73
+ <nav class=`sds-pagination ${extraClass}`.trim() aria-label=label>
74
+ <div class="sds-pagination__info">
75
+ <sds-text as="span" variant="labelRegular" class="sds-pagination__info-text">
76
+ ${first}–${last} de ${totalItems}
77
+ </sds-text>
78
+ <if(showSizeChanger)>
79
+ <sds-select
80
+ label="Itens por página"
81
+ trigger="navButton"
82
+ value=String(pageSize)
83
+ options=PAGE_SIZE_OPTIONS.map((size) => ({ label: String(size), value: String(size) }))
84
+ onChange=(size) => component.handleSizeChange(size)
85
+ disabled=disabled
86
+ // `clearable={false}`, because sds-select defaults it to true and a page size has
87
+ // no empty. Clearing reported `null`, which `Number(null)` turned into a page size
88
+ // of 0 — and `pageCount` divides by it. There is no state of this control that
89
+ // means "no size": every list has one.
90
+ clearable=false
91
+ />
92
+ </if>
93
+ </div>
94
+
95
+ // The controls come SECOND, and the spacer last.
96
+ //
97
+ // The grid is `1fr auto 1fr` and places children in order, so whatever is second takes
98
+ // the centre column. With the spacer there, the page buttons were pushed into the
99
+ // trailing 1fr and sat left-aligned inside it rather than centred under the list. KDLS
100
+ // emits info → controls → spacer for exactly this reason.
101
+ <div class="sds-pagination__controls">
102
+ <sds-icon-button
103
+ variant="neutral"
104
+ aria-label="Página anterior"
105
+ disabled=(disabled || currentPage <= 1)
106
+ onClick=() => component.previous()
107
+ >
108
+ <@icon><fluent-chevron-left-regular/></@icon>
109
+ </sds-icon-button>
110
+
111
+ <div class="sds-pagination__pages">
112
+ <for|entry| of=pages>
113
+ <if(entry === GAP)>
114
+ <span class="sds-pagination__ellipsis" aria-hidden="true">${GAP}</span>
115
+ </if>
116
+ <else>
117
+ <sds-pill
118
+ label=String(entry)
119
+ active=(entry === currentPage)
120
+ disabled=disabled
121
+ aria-label=`Página ${entry}`
122
+ aria-current=(entry === currentPage ? "page" : null)
123
+ onClick=() => component.go(entry)
124
+ />
125
+ </else>
126
+ </for>
127
+ </div>
128
+
129
+ <sds-icon-button
130
+ variant="neutral"
131
+ aria-label="Próxima página"
132
+ disabled=(disabled || currentPage >= totalPages)
133
+ onClick=() => component.next()
134
+ >
135
+ <@icon><fluent-chevron-right-regular/></@icon>
136
+ </sds-icon-button>
137
+ </div>
138
+
139
+ <div class="sds-pagination__spacer" aria-hidden="true"/>
140
+ </nav>
141
+ </if>
@@ -0,0 +1,193 @@
1
+ /* SDS Pill.
2
+ Ported from kdls-design-system v2.5.1 (0f3edf8) components/Pill/Pill.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-pill--off:hover:not(:disabled):not(.sds-pill--loading)
7
+ [data-color-scheme="lya"] .sds-pill--on
8
+ [data-color-scheme="lya"] .sds-pill--on:hover:not(:disabled):not(.sds-pill--loading)
9
+ [data-color-scheme="lya"] .sds-pill--on:active:not(:disabled):not(.sds-pill--loading)
10
+ *//* ============================================================
11
+ SDS Pill — Filter Chip
12
+ Height: 40px (equal to Toggle)
13
+ Status: off (outlined) | on (filled primary)
14
+ States: enabled | hover | pressed | focused | disabled
15
+ Icons: left icon | chevron | filter | notification badge
16
+ ============================================================ */
17
+
18
+ .sds-pill {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ gap: var(--spacing-2);
22
+ height: 40px;
23
+
24
+ padding: 0 var(--spacing-4);
25
+ /* soft, the install default — see the note by the scheme overrides below */
26
+ border-radius: 11px;
27
+ font-family: var(--font-family-base);
28
+ font-size: var(--font-size-body-md); /* 16px */
29
+ font-weight: var(--font-weight-medium);
30
+ line-height: var(--lh-body-md); /* 24px */
31
+ cursor: pointer;
32
+ white-space: nowrap;
33
+ border: 1px solid transparent;
34
+ transition:
35
+ background-color 120ms ease,
36
+ color 120ms ease,
37
+ border-color 120ms ease,
38
+ box-shadow 120ms ease;
39
+ position: relative;
40
+ outline: none;
41
+ -webkit-font-smoothing: antialiased;
42
+ user-select: none;
43
+ }
44
+ /* ──────────────────────────────────────────────
45
+ STATUS: OFF — outlined / ghost
46
+ ────────────────────────────────────────────── */
47
+ .sds-pill--off {
48
+ background-color: var(--component-bg-color);
49
+ color: var(--color-neutral-80);
50
+ border-color: var(--border-interactive-color);
51
+ }
52
+ .sds-pill--off:hover:not(:disabled):not(.sds-pill--loading) {
53
+ background-color: var(--component-bg-color);
54
+ border-color: var(--color-primary-50-solid);
55
+ }
56
+ .sds-pill--off:active:not(:disabled):not(.sds-pill--loading) {
57
+ background-color: var(--color-neutral-20);
58
+ border-color: var(--color-neutral-60);
59
+ color: var(--color-neutral-90);
60
+ }
61
+ .sds-pill--off:focus-visible {
62
+ box-shadow: var(--focus-ring);
63
+ }
64
+ /* ──────────────────────────────────────────────
65
+ STATUS: ON — filled primary
66
+ ────────────────────────────────────────────── */
67
+ .sds-pill--on {
68
+ background: var(--color-primary-50);
69
+ color: var(--color-on-primary);
70
+ border-color: var(--color-primary-50-solid);
71
+ }
72
+ .sds-pill--on:hover:not(:disabled):not(.sds-pill--loading) {
73
+ background: var(--color-primary-60);
74
+ border-color: var(--color-primary-60-solid);
75
+ }
76
+ .sds-pill--on:active:not(:disabled):not(.sds-pill--loading) {
77
+ background: var(--color-primary-70);
78
+ border-color: var(--color-primary-70-solid);
79
+ }
80
+ .sds-pill--on:focus-visible {
81
+ box-shadow: var(--focus-ring);
82
+ }
83
+ /* ──────────────────────────────────────────────
84
+ DISABLED
85
+ ────────────────────────────────────────────── */
86
+ .sds-pill:disabled,
87
+ .sds-pill[aria-disabled="true"] {
88
+ background-color: var(--color-neutral-20) !important;
89
+ border-color: var(--border-static-color) !important;
90
+ color: var(--color-neutral-40) !important;
91
+ cursor: not-allowed;
92
+ }
93
+ .sds-pill--loading {
94
+ cursor: wait;
95
+ }
96
+ .sds-pill--off:disabled,
97
+ .sds-pill--off[aria-disabled="true"] {
98
+ background-color: transparent !important;
99
+ }
100
+ [data-theme="dark"] .sds-pill:disabled,
101
+ [data-theme="dark"] .sds-pill[aria-disabled="true"] {
102
+ background-color: var(--color-neutral-30) !important;
103
+ border-color: var(--border-interactive-color) !important;
104
+ color: var(--color-neutral-40) !important;
105
+ }
106
+ [data-theme="dark"] .sds-pill--off:disabled,
107
+ [data-theme="dark"] .sds-pill--off[aria-disabled="true"] {
108
+ background-color: transparent !important;
109
+ }
110
+ /* ──────────────────────────────────────────────
111
+ INNER ELEMENTS
112
+ ────────────────────────────────────────────── */
113
+ .sds-pill__icon,
114
+ .sds-pill__icon-right {
115
+ display: inline-flex;
116
+ align-items: center;
117
+ font-size: 24px;
118
+ flex-shrink: 0;
119
+ }
120
+ .sds-pill__label {
121
+ text-align: left;
122
+ }
123
+ /* ── Width ── */
124
+ .sds-pill--fill { width: 100%; justify-content: flex-start; }
125
+ .sds-pill--fill .sds-pill__label { flex: 1; }
126
+ /* ── Clickable icon-right (dismiss) ── */
127
+ .sds-pill__icon-right--clickable {
128
+ cursor: pointer;
129
+ border-radius: var(--radius-sm);
130
+ transition: background-color 120ms ease;
131
+ padding: var(--spacing-0-5);
132
+ }
133
+ .sds-pill__icon-right--clickable:hover { background-color: var(--color-opacity-05); }
134
+ .sds-pill__icon-right--clickable:active { background-color: var(--color-opacity-10); }
135
+ /* ── Corner Radius Scheme overrides ──
136
+ Carried from KDLS unchanged, including the numbers, which are not arbitrary: they are a
137
+ proportion of the control's own height, and that is what keeps the four schemes reading
138
+ as one family rather than four unrelated shapes.
139
+
140
+ height 40px flat 0 soft 11px medium 15px rounded 20px (a capsule)
141
+ as a fraction 0 0.275 0.375 0.5
142
+
143
+ The same fractions give the Toggle's 32px item 9px and 12px — see Toggle.css. A radius
144
+ taken from --radius-md instead would be 16px and 24px here, and 24px on a 40px control
145
+ is past half its height, so the medium scheme would draw the same capsule as rounded and
146
+ two of the four schemes would collapse into one.
147
+
148
+ (Briefly changed to --radius-md - 2px on 2026-09-10 and changed back the same day:
149
+ Rafael pointed out that KDLS gets soft, medium and rounded right as they stand.)
150
+
151
+ The base value is `soft`, because `soft` is what an install without a data-radius
152
+ attribute gets.
153
+
154
+ KDLS's default scheme was `default`, Kenlo's own, and it lived unnamed in :root — so
155
+ KDLS's base rule here is `default`'s value and the four named schemes override it. SDS
156
+ removed `default` and promoted `soft`, and foundations/theme-server.js deliberately does
157
+ not stamp an attribute for the value that is already the default. The base rule was
158
+ therefore still answering for a scheme that no longer exists: with the install defaults
159
+ this control rendered as a capsule instead of 11px.
160
+
161
+ tokens.css already solves this the same way — :root carries soft's values and each
162
+ [data-radius] block overrides them. The named rules below stay for when the attribute is
163
+ stamped, and `rounded` is now explicit because it can no longer fall through to the base.
164
+ */
165
+ [data-radius="flat"] .sds-pill { border-radius: 0; }
166
+ /* Kept in parity with Toggle's outer container, which is the same 40px tall */
167
+ [data-radius="soft"] .sds-pill { border-radius: 11px; }
168
+ [data-radius="medium"] .sds-pill { border-radius: 15px; }
169
+ [data-radius="rounded"] .sds-pill { border-radius: var(--radius-full); }
170
+ /* ── Notification inside active Pill — inverted colors ── */
171
+ .sds-pill--on .sds-notification {
172
+ background-color: var(--color-on-primary);
173
+ color: var(--color-primary-50-solid);
174
+ }
175
+ /* ── Notification inside disabled Pill ── */
176
+ .sds-pill:disabled .sds-notification,
177
+ .sds-pill[aria-disabled="true"] .sds-notification {
178
+ background-color: var(--color-neutral-40);
179
+ color: var(--color-neutral-20);
180
+ }
181
+ [data-theme="dark"] .sds-pill:disabled .sds-notification,
182
+ [data-theme="dark"] .sds-pill[aria-disabled="true"] .sds-notification {
183
+ color: var(--color-neutral-30);
184
+ }
185
+ /* ──────────────────────────────────────────────
186
+ DARK MODE
187
+ ────────────────────────────────────────────── */
188
+ [data-theme="dark"] .sds-pill--off:hover:not(:disabled):not(.sds-pill--loading) {
189
+ background-color: transparent;
190
+ }
191
+ [data-theme="dark"] .sds-pill--off:active:not(:disabled):not(.sds-pill--loading) {
192
+ background-color: var(--color-neutral-30);
193
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The Pill's vocabulary.
3
+ *
4
+ * Ported from kdls-design-system v2.5.1 (0f3edf8) components/Pill/Pill.tsx.
5
+ *
6
+ * Only `width` is a closed set: everything else about a pill is a boolean or a label.
7
+ * It was read as `width === "fill" ? … : ""`, which silently treated every typo as hug.
8
+ */
9
+ export const PILL_WIDTHS = ['hug', 'fill'];
10
+
11
+ export function assertPillWidth(width) {
12
+ if (!PILL_WIDTHS.includes(width)) {
13
+ throw new Error(`Unknown pill width: "${width}". Allowed: ${PILL_WIDTHS.join(', ')}`);
14
+ }
15
+ return width;
16
+ }
@@ -0,0 +1,165 @@
1
+ <!-- sds-pill — ported from kdls-design-system v2.5.1 (0f3edf8) components/Pill/Pill.tsx.
2
+
3
+ A toggle shaped like a chip: a filter that is on or off. It is a real <button> with
4
+ aria-pressed, which is what tells a screen reader this is a toggle and not a link — and it
5
+ hydrates, for the same reason every button does.
6
+
7
+ A known limitation, carried over rather than fixed: when the right icon has its own
8
+ action it is a <span role="button">, not a real button. A <button> cannot contain another
9
+ <button> — that is invalid HTML — and the pill itself is the button. Making the icon
10
+ properly focusable means the pill stops being a <button> and becomes a container with two
11
+ of them inside, which changes its whole keyboard and ARIA story. That is a design decision,
12
+ not a port decision, so it is left as KDLS had it and recorded here.
13
+
14
+ Divergence: notification renders through sds-notification instead of being drawn inline. -->
15
+ import { assertPillWidth } from "./pill-variants.js";
16
+
17
+
18
+ class {
19
+ handleClick(event) {
20
+ const { disabled, isLoading, onClick } = this.input;
21
+ if (disabled || isLoading) {
22
+ event.preventDefault();
23
+ return;
24
+ }
25
+ onClick?.(event);
26
+ }
27
+
28
+ /**
29
+ * Enter and Space on the right icon.
30
+ *
31
+ * It carries `role="button"` and `tabindex="0"`, so the keyboard can reach it and a
32
+ * screen reader announces it as a button — and then nothing happened, because only
33
+ * `click` was wired. A control that takes focus, says it is a button and does nothing
34
+ * when you press it is worse than one you cannot reach: you are told it works.
35
+ *
36
+ * KDLS has this handler; the port dropped it. `stopPropagation` matters as much as the
37
+ * call: without it the key reaches the pill it sits inside and opens what you were
38
+ * trying to clear.
39
+ */
40
+ iconRightKey(event) {
41
+ if (event.key !== "Enter" && event.key !== " ") return;
42
+ event.preventDefault();
43
+ this.handleIconRight(event);
44
+ }
45
+
46
+ handleIconRight(event) {
47
+ event.stopPropagation();
48
+ const { disabled, isLoading, onClickIconRight } = this.input;
49
+ if (disabled || isLoading) {
50
+ event.preventDefault();
51
+ return;
52
+ }
53
+ onClickIconRight?.(event);
54
+ }
55
+ }
56
+
57
+ $ {
58
+ const {
59
+ label,
60
+ active = false,
61
+ width = "hug",
62
+ iconLeft,
63
+ iconRight,
64
+ onClickIconRight,
65
+ iconRightLabel = "Remover",
66
+ showNotification = false,
67
+ notificationCount,
68
+ isLoading = false,
69
+ disabled = false,
70
+ class: extraClass = "",
71
+ } = input;
72
+
73
+ assertPillWidth(width);
74
+
75
+ // Marko camelCases a hyphenated attribute written on a component tag, capitalising only
76
+ // the letter after the hyphen: aria-haspopup arrives as `ariaHaspopup`, not
77
+ // `ariaHasPopup`. Guessing that spelling by hand is how an attribute silently goes
78
+ // missing — the pill announced itself as a pressed switch for exactly that reason. So
79
+ // the rule is applied rather than remembered, and both forms are read: a template writes
80
+ // the first, a test or a loop passes the second.
81
+ const aria = (name) => input[`aria-${name}`]
82
+ ?? input[`aria${name[0].toUpperCase()}${name.slice(1)}`]
83
+ ?? null;
84
+
85
+ const ariaLabel = aria("label");
86
+ // A pill is a toggle by default, and aria-pressed is what says so. Two callers need it
87
+ // to be something else, and a control cannot be two things at once:
88
+ //
89
+ // aria-current a navigation marker — the page you are on, in Pagination
90
+ // aria-haspopup a disclosure — the trigger of a Select, which opens a listbox
91
+ //
92
+ // Either one takes over the announcement. A button claiming to be a pressed switch *and*
93
+ // the current page, or a pressed switch that also opens a menu, tells a screen reader two
94
+ // stories about itself and the visitor hears the wrong one.
95
+ const ariaCurrent = aria("current");
96
+ const ariaHasPopup = aria("haspopup");
97
+ const ariaExpanded = aria("expanded");
98
+ const ariaControls = aria("controls");
99
+ if (!label && !ariaLabel) {
100
+ throw new Error("sds-pill needs a `label`, or an `aria-label` when it shows only an icon.");
101
+ }
102
+
103
+ const renderable = (value) => (value && value.renderBody) || value;
104
+ const loading = isLoading && !disabled;
105
+
106
+ const classes = [
107
+ "sds-pill",
108
+ active ? "sds-pill--on" : "sds-pill--off",
109
+ disabled ? "sds-pill--disabled" : "",
110
+ loading ? "sds-pill--loading" : "",
111
+ width === "fill" ? "sds-pill--fill" : "",
112
+ extraClass,
113
+ ].filter(Boolean).join(" ");
114
+ }
115
+
116
+ <button
117
+ type="button"
118
+ class=classes
119
+ aria-pressed=(ariaCurrent || ariaHasPopup ? null : (active ? "true" : "false"))
120
+ aria-current=ariaCurrent
121
+ aria-haspopup=ariaHasPopup
122
+ aria-expanded=ariaExpanded
123
+ aria-controls=ariaControls
124
+ aria-busy=(loading ? "true" : null)
125
+ aria-label=ariaLabel
126
+ disabled=(disabled || null)
127
+ on-click("handleClick")
128
+ >
129
+ <if(loading)>
130
+ <sds-spinner size="sm" decorative/>
131
+ </if>
132
+ <else-if(iconLeft)>
133
+ <span class="sds-pill__icon" aria-hidden="true">
134
+ <sds-icon size="sm"><${renderable(iconLeft)}/></sds-icon>
135
+ </span>
136
+ </else-if>
137
+
138
+ <if(label)>
139
+ <span class="sds-pill__label">${label}</span>
140
+ </if>
141
+
142
+ <if(showNotification || notificationCount !== undefined)>
143
+ <!-- No wrapper: KDLS renders it as a direct flex child, and a wrapper with no rule
144
+ behind it is an unstyled block in the middle of a row. -->
145
+ <sds-notification count=notificationCount size=(notificationCount === undefined ? 8 : 16)/>
146
+ </if>
147
+
148
+ <if(iconRight && onClickIconRight)>
149
+ <span
150
+ class="sds-pill__icon-right sds-pill__icon-right--clickable"
151
+ role="button"
152
+ tabindex="0"
153
+ aria-label=iconRightLabel
154
+ on-click("handleIconRight")
155
+ on-keydown("iconRightKey")
156
+ >
157
+ <sds-icon size="sm"><${renderable(iconRight)}/></sds-icon>
158
+ </span>
159
+ </if>
160
+ <else-if(iconRight)>
161
+ <span class="sds-pill__icon-right" aria-hidden="true">
162
+ <sds-icon size="sm"><${renderable(iconRight)}/></sds-icon>
163
+ </span>
164
+ </else-if>
165
+ </button>