@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.
- package/README.md +129 -0
- package/components/Accordion/Accordion.css +186 -0
- package/components/Accordion/accordion-variants.js +17 -0
- package/components/Accordion/accordion.marko +264 -0
- package/components/AccordionGroup/AccordionGroup.css +35 -0
- package/components/AccordionGroup/accordion-group.marko +17 -0
- package/components/Alert/Alert.css +104 -0
- package/components/Alert/alert-variants.js +35 -0
- package/components/Alert/alert.marko +94 -0
- package/components/AttachmentList/AttachmentList.css +92 -0
- package/components/AttachmentList/attachment-list.marko +51 -0
- package/components/AttachmentList/attachment-row.marko +167 -0
- package/components/Avatar/Avatar.css +110 -0
- package/components/Avatar/avatar-variants.js +12 -0
- package/components/Avatar/avatar.marko +71 -0
- package/components/Badge/Badge.css +381 -0
- package/components/Badge/badge-dismissible.marko +143 -0
- package/components/Badge/badge-variants.js +35 -0
- package/components/Badge/badge.marko +134 -0
- package/components/BottomSheet/BottomSheet.css +300 -0
- package/components/BottomSheet/bottom-sheet.marko +340 -0
- package/components/BulkActionBar/BulkActionBar.css +88 -0
- package/components/BulkActionBar/bulk-action-bar.marko +186 -0
- package/components/Button/Button.css +340 -0
- package/components/Button/button-variants.js +25 -0
- package/components/Button/button.marko +139 -0
- package/components/Card/Card.css +391 -0
- package/components/Card/card-body.marko +5 -0
- package/components/Card/card-collapsible.marko +94 -0
- package/components/Card/card-footer.marko +12 -0
- package/components/Card/card-header.marko +30 -0
- package/components/Card/card-info-pair.marko +17 -0
- package/components/Card/card-info-row.marko +12 -0
- package/components/Card/card-section.marko +14 -0
- package/components/Card/card-variants.js +24 -0
- package/components/Card/card.marko +124 -0
- package/components/Checkbox/Checkbox.css +249 -0
- package/components/Checkbox/checkbox-variants.js +21 -0
- package/components/Checkbox/checkbox.marko +128 -0
- package/components/ColorPicker/ColorPicker.css +253 -0
- package/components/ColorPicker/color-maths.js +104 -0
- package/components/ColorPicker/color-picker.marko +474 -0
- package/components/DatePicker/DatePicker.css +429 -0
- package/components/DatePicker/date-logic.js +225 -0
- package/components/DatePicker/date-picker.marko +492 -0
- package/components/Drawer/Drawer.css +174 -0
- package/components/Drawer/drawer.marko +262 -0
- package/components/Floating/Floating.css +28 -0
- package/components/Floating/floating-position.js +251 -0
- package/components/Floating/floating.marko +291 -0
- package/components/Form/Form.css +15 -0
- package/components/Form/form-context.js +69 -0
- package/components/Form/form.marko +73 -0
- package/components/GradientPicker/GradientPicker.css +179 -0
- package/components/GradientPicker/gradient-picker.marko +398 -0
- package/components/Grid/Grid.css +161 -0
- package/components/Grid/col.marko +36 -0
- package/components/Grid/grid-variants.js +43 -0
- package/components/Grid/grid.marko +25 -0
- package/components/Header/Header.css +457 -0
- package/components/Header/header.marko +523 -0
- package/components/Icon/Icon.css +44 -0
- package/components/Icon/icon-colors.js +31 -0
- package/components/Icon/icon.marko +56 -0
- package/components/IconButton/IconButton.css +85 -0
- package/components/IconButton/icon-button-variants.js +18 -0
- package/components/IconButton/icon-button.marko +103 -0
- package/components/Incrementer/Incrementer.css +94 -0
- package/components/Incrementer/incrementer.marko +70 -0
- package/components/Input/Input.css +547 -0
- package/components/Input/input-countries.js +109 -0
- package/components/Input/input-masks.js +218 -0
- package/components/Input/input-types.js +194 -0
- package/components/Input/input.marko +530 -0
- package/components/Menu/Menu.css +651 -0
- package/components/Menu/menu-variants.js +89 -0
- package/components/Menu/menu.marko +560 -0
- package/components/Modal/Modal.css +160 -0
- package/components/Modal/modal.marko +296 -0
- package/components/Nav/Nav.css +298 -0
- package/components/Nav/nav-variants.js +23 -0
- package/components/Nav/nav.marko +164 -0
- package/components/Notification/Notification.css +78 -0
- package/components/Notification/notification-variants.js +30 -0
- package/components/Notification/notification.marko +55 -0
- package/components/Pagination/Pagination.css +78 -0
- package/components/Pagination/pagination-pages.js +66 -0
- package/components/Pagination/pagination.marko +141 -0
- package/components/Pill/Pill.css +193 -0
- package/components/Pill/pill-variants.js +16 -0
- package/components/Pill/pill.marko +165 -0
- package/components/Popover/Popover.css +121 -0
- package/components/Popover/popover.marko +96 -0
- package/components/Radio/Radio.css +230 -0
- package/components/Radio/radio-variants.js +16 -0
- package/components/Radio/radio.marko +93 -0
- package/components/RadioGroup/RadioGroup.css +32 -0
- package/components/RadioGroup/radio-group.marko +93 -0
- package/components/SearchBar/SearchBar.css +181 -0
- package/components/SearchBar/search-bar-variants.js +24 -0
- package/components/SearchBar/search-bar.marko +157 -0
- package/components/Select/Select.css +427 -0
- package/components/Select/select-options.js +163 -0
- package/components/Select/select.marko +449 -0
- package/components/Separator/Separator.css +22 -0
- package/components/Separator/separator.marko +14 -0
- package/components/Skeleton/Skeleton.css +100 -0
- package/components/Skeleton/skeleton-avatar.marko +28 -0
- package/components/Skeleton/skeleton-button.marko +22 -0
- package/components/Skeleton/skeleton-image.marko +20 -0
- package/components/Skeleton/skeleton-input.marko +22 -0
- package/components/Skeleton/skeleton-variants.js +16 -0
- package/components/Skeleton/skeleton.marko +53 -0
- package/components/Spinner/Spinner.css +173 -0
- package/components/Spinner/spinner-geometry.js +39 -0
- package/components/Spinner/spinner-overlay.marko +97 -0
- package/components/Spinner/spinner.marko +88 -0
- package/components/StatCard/StatCard.css +90 -0
- package/components/StatCard/stat-card-group.marko +5 -0
- package/components/StatCard/stat-card.marko +57 -0
- package/components/Switch/Switch.css +209 -0
- package/components/Switch/switch.marko +99 -0
- package/components/Table/Table.css +270 -0
- package/components/Table/table-cell.marko +28 -0
- package/components/Table/table-header.marko +27 -0
- package/components/Table/table-logic.js +129 -0
- package/components/Table/table.marko +372 -0
- package/components/Text/Text.css +79 -0
- package/components/Text/text-variants.js +25 -0
- package/components/Text/text.marko +48 -0
- package/components/TextEditor/TextEditor.css +78 -0
- package/components/TextEditor/safe-url.js +50 -0
- package/components/TextEditor/text-editor-commands.js +22 -0
- package/components/TextEditor/text-editor.marko +253 -0
- package/components/Toast/Toast.css +88 -0
- package/components/Toast/toast-api.js +19 -0
- package/components/Toast/toast-store.js +118 -0
- package/components/Toast/toast-variants.js +16 -0
- package/components/Toast/toast.marko +93 -0
- package/components/Toast/toaster.marko +70 -0
- package/components/Toggle/Toggle.css +169 -0
- package/components/Toggle/toggle.marko +93 -0
- package/components/Tooltip/Tooltip.css +115 -0
- package/components/Tooltip/tooltip.marko +77 -0
- package/components/Uploader/Uploader.css +180 -0
- package/components/Uploader/crop-geometry.js +161 -0
- package/components/Uploader/uploader.marko +269 -0
- package/foundations/Scrollbar/Scrollbar.css +26 -0
- package/foundations/attrs.js +27 -0
- package/foundations/breakpoints.js +27 -0
- package/foundations/contrast.js +74 -0
- package/foundations/custom-scheme.js +222 -0
- package/foundations/overlay.js +108 -0
- package/foundations/styles.css +284 -0
- package/foundations/text.js +22 -0
- package/foundations/theme-server.js +81 -0
- package/foundations/theme.js +42 -0
- package/foundations/tokens.css +1656 -0
- package/icons/custom/custom-video-horizontal-filled.marko +6 -0
- package/icons/custom/custom-video-horizontal-regular.marko +6 -0
- package/icons/custom/custom-video-tour-3d-filled.marko +7 -0
- package/icons/custom/custom-video-tour-3d-regular.marko +9 -0
- package/icons/custom/custom-video-vertical-filled.marko +6 -0
- package/icons/custom/custom-video-vertical-regular.marko +6 -0
- package/icons/generated/fluent-add-regular.marko +3 -0
- package/icons/generated/fluent-alert-regular.marko +3 -0
- package/icons/generated/fluent-apps-regular.marko +3 -0
- package/icons/generated/fluent-arrow-download-regular.marko +3 -0
- package/icons/generated/fluent-arrow-left-regular.marko +3 -0
- package/icons/generated/fluent-arrow-right-regular.marko +3 -0
- package/icons/generated/fluent-arrow-rotate-clockwise-regular.marko +3 -0
- package/icons/generated/fluent-arrow-rotate-counterclockwise-regular.marko +3 -0
- package/icons/generated/fluent-border-all-regular.marko +3 -0
- package/icons/generated/fluent-calendar-regular.marko +3 -0
- package/icons/generated/fluent-checkmark-circle-filled.marko +3 -0
- package/icons/generated/fluent-checkmark-regular.marko +3 -0
- package/icons/generated/fluent-chevron-down-regular.marko +3 -0
- package/icons/generated/fluent-chevron-left-regular.marko +3 -0
- package/icons/generated/fluent-chevron-right-regular.marko +3 -0
- package/icons/generated/fluent-chevron-up-regular.marko +3 -0
- package/icons/generated/fluent-circle-half-fill-regular.marko +3 -0
- package/icons/generated/fluent-clock-regular.marko +3 -0
- package/icons/generated/fluent-color-regular.marko +3 -0
- package/icons/generated/fluent-crop-regular.marko +3 -0
- package/icons/generated/fluent-dark-theme-regular.marko +3 -0
- package/icons/generated/fluent-delete-regular.marko +3 -0
- package/icons/generated/fluent-desktop-regular.marko +3 -0
- package/icons/generated/fluent-dismiss-circle-filled.marko +3 -0
- package/icons/generated/fluent-dismiss-regular.marko +3 -0
- package/icons/generated/fluent-document-pdf-regular.marko +3 -0
- package/icons/generated/fluent-document-regular.marko +3 -0
- package/icons/generated/fluent-eye-off-regular.marko +3 -0
- package/icons/generated/fluent-eye-regular.marko +3 -0
- package/icons/generated/fluent-eyedropper-regular.marko +3 -0
- package/icons/generated/fluent-grid-regular.marko +3 -0
- package/icons/generated/fluent-history-regular.marko +3 -0
- package/icons/generated/fluent-home-regular.marko +3 -0
- package/icons/generated/fluent-image-regular.marko +3 -0
- package/icons/generated/fluent-info-regular.marko +3 -0
- package/icons/generated/fluent-link-regular.marko +3 -0
- package/icons/generated/fluent-more-horizontal-regular.marko +3 -0
- package/icons/generated/fluent-navigation-regular.marko +3 -0
- package/icons/generated/fluent-paint-brush-regular.marko +3 -0
- package/icons/generated/fluent-question-circle-regular.marko +3 -0
- package/icons/generated/fluent-search-regular.marko +3 -0
- package/icons/generated/fluent-settings-regular.marko +3 -0
- package/icons/generated/fluent-square-hint-regular.marko +3 -0
- package/icons/generated/fluent-subtract-regular.marko +3 -0
- package/icons/generated/fluent-text-align-center-regular.marko +3 -0
- package/icons/generated/fluent-text-align-left-regular.marko +3 -0
- package/icons/generated/fluent-text-align-right-regular.marko +3 -0
- package/icons/generated/fluent-text-bold-regular.marko +3 -0
- package/icons/generated/fluent-text-bullet-list-regular.marko +3 -0
- package/icons/generated/fluent-text-font-regular.marko +3 -0
- package/icons/generated/fluent-text-font-size-regular.marko +3 -0
- package/icons/generated/fluent-text-italic-regular.marko +3 -0
- package/icons/generated/fluent-text-number-list-ltr-regular.marko +3 -0
- package/icons/generated/fluent-video-regular.marko +3 -0
- package/icons/generated/fluent-warning-filled.marko +3 -0
- package/icons/generated/fluent-weather-moon-regular.marko +3 -0
- package/icons/generated/fluent-weather-sunny-regular.marko +3 -0
- package/icons/generated/fluent-zoom-in-regular.marko +3 -0
- package/icons/generated/fluent-zoom-out-regular.marko +3 -0
- package/icons/manifest.json +61 -0
- package/icons/social/social-facebook-color.marko +7 -0
- package/icons/social/social-facebook.marko +6 -0
- package/icons/social/social-google-color.marko +20 -0
- package/icons/social/social-google.marko +6 -0
- package/icons/social/social-instagram-color.marko +16 -0
- package/icons/social/social-instagram.marko +6 -0
- package/icons/social/social-linkedin-color.marko +8 -0
- package/icons/social/social-linkedin.marko +8 -0
- package/icons/social/social-messenger-color.marko +15 -0
- package/icons/social/social-messenger.marko +6 -0
- package/icons/social/social-pinterest-color.marko +6 -0
- package/icons/social/social-pinterest.marko +6 -0
- package/icons/social/social-telegram-color.marko +7 -0
- package/icons/social/social-telegram.marko +6 -0
- package/icons/social/social-whatsapp-color.marko +8 -0
- package/icons/social/social-whatsapp.marko +7 -0
- package/icons/social/social-x-color.marko +7 -0
- package/icons/social/social-x.marko +6 -0
- package/icons/social/social-youtube-color.marko +7 -0
- package/icons/social/social-youtube.marko +6 -0
- package/marko.json +203 -0
- package/package.json +55 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contrast maths and the label-colour rule, shared by the token generator and its tests.
|
|
3
|
+
*
|
|
4
|
+
* The rule, in one sentence: a label reads the step of its own family that gives the most
|
|
5
|
+
* contrast against the surface it sits on, and only falls back to white or black when the
|
|
6
|
+
* family itself cannot reach WCAG AA.
|
|
7
|
+
*
|
|
8
|
+
* Why this is generated rather than written by hand: KDLS wrote these values by eye, one
|
|
9
|
+
* declaration per family per tone per theme, and got a number of them wrong in a way that
|
|
10
|
+
* is invisible until someone squints at a badge. Deriving them from the token values means
|
|
11
|
+
* the palette can change and the labels follow.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** WCAG 2.1 minimum for normal-size text. Badge labels are 12–16px, so this is the bar. */
|
|
15
|
+
export const AA_NORMAL = 4.5;
|
|
16
|
+
/** WCAG 2.1 minimum for large text — the floor we accept when the family cannot do better. */
|
|
17
|
+
export const AA_LARGE = 3;
|
|
18
|
+
|
|
19
|
+
export const STEPS = [10, 20, 30, 40, 50, 60, 70, 80, 90];
|
|
20
|
+
|
|
21
|
+
export function relativeLuminance(hex) {
|
|
22
|
+
const n = parseInt(hex.replace(/^#/, '').slice(0, 6), 16);
|
|
23
|
+
const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((c) => {
|
|
24
|
+
const v = c / 255;
|
|
25
|
+
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
26
|
+
});
|
|
27
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function contrastRatio(a, b) {
|
|
31
|
+
const [hi, lo] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x);
|
|
32
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const WHITE = '#FFFFFF';
|
|
36
|
+
const BLACK = '#000000';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A colour is never written as a literal in this design system. The fallback pair has
|
|
40
|
+
* tokens of its own, and callers emit those — the hex here exists only so the contrast
|
|
41
|
+
* maths has something to compute against.
|
|
42
|
+
*/
|
|
43
|
+
export const FALLBACK_TOKENS = {
|
|
44
|
+
[WHITE]: 'var(--color-white)',
|
|
45
|
+
[BLACK]: 'var(--color-black)',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Picks the label colour for one surface.
|
|
50
|
+
*
|
|
51
|
+
* `family` is a map of step -> hex for the family the surface belongs to. The best step of
|
|
52
|
+
* that family wins whenever it clears AA. Otherwise white or black takes over — whichever
|
|
53
|
+
* reads better — because an unreadable label that happens to be on-palette is still an
|
|
54
|
+
* unreadable label.
|
|
55
|
+
*
|
|
56
|
+
* Returns { step, hex, ratio, source } where source is 'family' or 'fallback'.
|
|
57
|
+
*/
|
|
58
|
+
export function pickLabelColour(background, family, { threshold = AA_NORMAL } = {}) {
|
|
59
|
+
const candidates = STEPS
|
|
60
|
+
.map((step) => ({ step, hex: family[step], ratio: contrastRatio(background, family[step]) }))
|
|
61
|
+
.sort((a, b) => b.ratio - a.ratio);
|
|
62
|
+
|
|
63
|
+
const best = candidates[0];
|
|
64
|
+
if (best.ratio >= threshold) return { ...best, source: 'family' };
|
|
65
|
+
|
|
66
|
+
const fallback = [WHITE, BLACK]
|
|
67
|
+
.map((hex) => ({ step: null, hex, ratio: contrastRatio(background, hex) }))
|
|
68
|
+
.sort((a, b) => b.ratio - a.ratio)[0];
|
|
69
|
+
|
|
70
|
+
// Only take the fallback if it is actually better — on a mid-tone it sometimes is not.
|
|
71
|
+
return fallback.ratio > best.ratio
|
|
72
|
+
? { ...fallback, source: 'fallback' }
|
|
73
|
+
: { ...best, source: 'family' };
|
|
74
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom colour and custom gradient — the two ways a site fills the primary slot.
|
|
3
|
+
*
|
|
4
|
+
* Ported from kdls-design-system v2.5.1 (0f3edf8) components/ColorScheme/ColorScheme.ts.
|
|
5
|
+
* The tone table, the HSL conversions and the contrast pick are carried verbatim: their
|
|
6
|
+
* output is what the design team already approved, and changing the ramp would silently
|
|
7
|
+
* restyle every consumer.
|
|
8
|
+
*
|
|
9
|
+
* Divergences, all deliberate:
|
|
10
|
+
* - `applyCustomScheme` is renamed `applyCustomColor`, for symmetry with its new twin;
|
|
11
|
+
* - `applyCustomGradient` is new. It is the mechanism that replaces the Lya scheme:
|
|
12
|
+
* the gradient machinery lives on, the Kenlo brand does not. It is deliberately not
|
|
13
|
+
* surfaced — no preset [data-color-scheme] points at it and the portal exposes no
|
|
14
|
+
* control. Plumbing installed, tap closed;
|
|
15
|
+
* - an invalid hex throws. KDLS returned silently, which left the page mysteriously
|
|
16
|
+
* unthemed after a typo;
|
|
17
|
+
* - the React `useCustomScheme` hook is not ported. SDS is not React.
|
|
18
|
+
*
|
|
19
|
+
* Both paths write to the same --color-primary-10..90 slots, which is why no component
|
|
20
|
+
* needs to know which one is active: a gradient in that slot is exactly what the Lya
|
|
21
|
+
* scheme used to put there, and the components already know how to paint it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { pickLabelColour, FALLBACK_TOKENS } from './contrast.js';
|
|
25
|
+
|
|
26
|
+
const STYLE_ID = 'sds-custom-scheme';
|
|
27
|
+
const TONES = [10, 20, 30, 40, 50, 60, 70, 80, 90];
|
|
28
|
+
/** The tones a component uses as a surface, and therefore needs a label colour for. */
|
|
29
|
+
const SURFACE_TONES = [10, 20, 30, 40, 50];
|
|
30
|
+
const HEX = /^#?[0-9a-f]{6,8}$/i;
|
|
31
|
+
|
|
32
|
+
function assertHex(hex) {
|
|
33
|
+
if (typeof hex !== 'string' || !HEX.test(hex.trim())) {
|
|
34
|
+
throw new Error(`Invalid hex color: ${hex}`);
|
|
35
|
+
}
|
|
36
|
+
return hex.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hexToHsl(hex) {
|
|
40
|
+
const clean = hex.replace(/^#/, '').slice(0, 6);
|
|
41
|
+
const n = parseInt(clean, 16);
|
|
42
|
+
const r = ((n >> 16) & 255) / 255;
|
|
43
|
+
const g = ((n >> 8) & 255) / 255;
|
|
44
|
+
const b = (n & 255) / 255;
|
|
45
|
+
const max = Math.max(r, g, b);
|
|
46
|
+
const min = Math.min(r, g, b);
|
|
47
|
+
const l = (max + min) / 2;
|
|
48
|
+
const d = max - min;
|
|
49
|
+
const s = d === 0 ? 0 : l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
50
|
+
let h = 0;
|
|
51
|
+
if (d !== 0) {
|
|
52
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
53
|
+
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
54
|
+
else h = ((r - g) / d + 4) / 6;
|
|
55
|
+
}
|
|
56
|
+
return [h, s, l];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hslToHex(h, s, l) {
|
|
60
|
+
if (s === 0) {
|
|
61
|
+
const v = Math.round(l * 255).toString(16).padStart(2, '0');
|
|
62
|
+
return `#${v}${v}${v}`.toUpperCase();
|
|
63
|
+
}
|
|
64
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
65
|
+
const p = 2 * l - q;
|
|
66
|
+
const hue = (t) => {
|
|
67
|
+
if (t < 0) t += 1;
|
|
68
|
+
if (t > 1) t -= 1;
|
|
69
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
70
|
+
if (t < 1 / 2) return q;
|
|
71
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
72
|
+
return p;
|
|
73
|
+
};
|
|
74
|
+
const toHex = (x) => Math.round(hue(x) * 255).toString(16).padStart(2, '0');
|
|
75
|
+
return `#${toHex(h + 1 / 3)}${toHex(h)}${toHex(h - 1 / 3)}`.toUpperCase();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The lightness and saturation transform for one tone. Carried verbatim from KDLS.
|
|
82
|
+
* Both the solid and the gradient path go through here, so the ramp can only be
|
|
83
|
+
* changed in one place.
|
|
84
|
+
*/
|
|
85
|
+
function toneTransform(h, s, l, tone) {
|
|
86
|
+
const table = {
|
|
87
|
+
10: [lerp(l, 0.95, 0.82), lerp(s, 0.25, 0.35)],
|
|
88
|
+
20: [lerp(l, 0.95, 0.63), s],
|
|
89
|
+
30: [lerp(l, 0.95, 0.43), s],
|
|
90
|
+
40: [lerp(l, 0.95, 0.22), s],
|
|
91
|
+
50: [l, s],
|
|
92
|
+
60: [lerp(l, 0.05, 0.20), s],
|
|
93
|
+
70: [lerp(l, 0.05, 0.40), Math.min(s * 1.04, 1)],
|
|
94
|
+
80: [lerp(l, 0.05, 0.60), Math.min(s * 1.07, 1)],
|
|
95
|
+
90: [lerp(l, 0.05, 0.80), Math.min(s * 1.10, 1)],
|
|
96
|
+
};
|
|
97
|
+
const [toneL, toneS] = table[tone];
|
|
98
|
+
return hslToHex(h, toneS, toneL);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Nine solid tones from one hex. */
|
|
102
|
+
export function generatePalette(hex) {
|
|
103
|
+
const [h, s, l] = hexToHsl(assertHex(hex));
|
|
104
|
+
return TONES.reduce((acc, tone) => {
|
|
105
|
+
acc[tone] = toneTransform(h, s, l, tone);
|
|
106
|
+
return acc;
|
|
107
|
+
}, {});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Nine gradients from two or more stops. Each tone applies that tone's transform to
|
|
112
|
+
* every stop, so the ramp behaves like the solid palette does — tone 10 is the pale
|
|
113
|
+
* end of the whole gradient, tone 90 the deep end.
|
|
114
|
+
*/
|
|
115
|
+
export function generateGradientPalette(hexes) {
|
|
116
|
+
if (!Array.isArray(hexes) || hexes.length < 2) {
|
|
117
|
+
throw new Error('generateGradientPalette needs at least two stops');
|
|
118
|
+
}
|
|
119
|
+
const parsed = hexes.map((hex) => hexToHsl(assertHex(hex)));
|
|
120
|
+
|
|
121
|
+
return TONES.reduce((acc, tone) => {
|
|
122
|
+
const stops = parsed.map(([h, s, l]) => toneTransform(h, s, l, tone));
|
|
123
|
+
acc[tone] = `linear-gradient(90deg, ${stops.join(', ')})`;
|
|
124
|
+
return acc;
|
|
125
|
+
}, {});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** WCAG relative luminance, deciding whether text on the colour is white or black. */
|
|
129
|
+
/** The token for the contrast pick, so a literal never reaches the stylesheet. */
|
|
130
|
+
export function onPrimaryToken(hex) {
|
|
131
|
+
return FALLBACK_TOKENS[onPrimaryColor(hex)];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function onPrimaryColor(hex) {
|
|
135
|
+
const clean = assertHex(hex).replace(/^#/, '').slice(0, 6);
|
|
136
|
+
const n = parseInt(clean, 16);
|
|
137
|
+
const toLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
138
|
+
const r = toLinear(((n >> 16) & 255) / 255);
|
|
139
|
+
const g = toLinear(((n >> 8) & 255) / 255);
|
|
140
|
+
const b = toLinear((n & 255) / 255);
|
|
141
|
+
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
142
|
+
return 1.05 / (lum + 0.05) >= (lum + 0.05) / 0.05 ? '#FFFFFF' : '#000000';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function invertPalette(light) {
|
|
146
|
+
return {
|
|
147
|
+
10: light[90], 20: light[80], 30: light[70], 40: light[60], 50: light[50],
|
|
148
|
+
60: light[40], 70: light[30], 80: light[20], 90: light[10],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A gradient is only valid where a surface is painted. `color`, `box-shadow` and
|
|
154
|
+
* `border-color` reject one, so the ramp is written twice: the slot carries whatever
|
|
155
|
+
* was generated, and the -solid companion carries a value those properties accept.
|
|
156
|
+
* For a solid ramp the two are identical; for a gradient the companion is the stop
|
|
157
|
+
* named by `solidOf`.
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* The label colour for each tone, by the same rule the generated tokens follow: the step
|
|
161
|
+
* of this very ramp that reads best on that surface, falling back to white or black only
|
|
162
|
+
* where the ramp cannot reach AA.
|
|
163
|
+
*
|
|
164
|
+
* This is what makes a custom colour as accessible as a built-in palette. Without it, a
|
|
165
|
+
* site that themes itself pale would get a pale label on a pale surface, and nothing in
|
|
166
|
+
* the design system would notice.
|
|
167
|
+
*/
|
|
168
|
+
function labelVars(palette, solidOf) {
|
|
169
|
+
const solids = TONES.reduce((acc, t) => { acc[t] = solidOf(palette[t]); return acc; }, {});
|
|
170
|
+
return SURFACE_TONES
|
|
171
|
+
.map((t) => {
|
|
172
|
+
const pick = pickLabelColour(solids[t], solids);
|
|
173
|
+
// A step of the ramp is injected as its own value; the fallback pair is emitted as
|
|
174
|
+
// a token, so no literal colour is ever written into the page.
|
|
175
|
+
return `--color-primary-${t}-on:${FALLBACK_TOKENS[pick.hex] ?? pick.hex};`;
|
|
176
|
+
})
|
|
177
|
+
.join('');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildCss(light, dark, onColor, solidOf) {
|
|
181
|
+
const vars = (palette) => TONES
|
|
182
|
+
.map((t) => `--color-primary-${t}:${palette[t]};--color-primary-${t}-solid:${solidOf(palette[t])};`)
|
|
183
|
+
.join('') + labelVars(palette, solidOf);
|
|
184
|
+
return `:root{${vars(light)}--color-on-primary:${onColor};}`
|
|
185
|
+
+ `[data-theme="dark"]{${vars(dark)}--color-on-primary:${onColor};}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The first stop of a gradient — the edge a left-aligned label starts against. */
|
|
189
|
+
export function firstStopOf(value) {
|
|
190
|
+
const match = /#[0-9A-F]{6}/i.exec(value);
|
|
191
|
+
if (!match) throw new Error(`No colour stop found in: ${value}`);
|
|
192
|
+
return match[0];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** One style element, reused. A second call replaces the first — the two never stack. */
|
|
196
|
+
function inject(light, onColor, solidOf) {
|
|
197
|
+
let el = document.getElementById(STYLE_ID);
|
|
198
|
+
if (!el) {
|
|
199
|
+
el = document.createElement('style');
|
|
200
|
+
el.id = STYLE_ID;
|
|
201
|
+
document.head.appendChild(el);
|
|
202
|
+
}
|
|
203
|
+
el.textContent = buildCss(light, invertPalette(light), onColor, solidOf);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Fills the primary slot with a solid ramp generated from one hex. */
|
|
207
|
+
export function applyCustomColor(hex) {
|
|
208
|
+
inject(generatePalette(hex), onPrimaryToken(hex), (value) => value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Fills the primary slot with a gradient ramp generated from two or more stops.
|
|
213
|
+
* Text contrast is decided by the first stop — the gradient's own left edge, which is
|
|
214
|
+
* where a label starts.
|
|
215
|
+
*/
|
|
216
|
+
export function applyCustomGradient(hexes) {
|
|
217
|
+
inject(generateGradientPalette(hexes), onPrimaryToken(hexes[0]), firstStopOf);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function clearCustomScheme() {
|
|
221
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
222
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every overlay has to do, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Modal, Drawer and BottomSheet are the same behaviour with different geometry: they cover
|
|
5
|
+
* the page, they take the keyboard while they are open, and they give it back afterwards.
|
|
6
|
+
* KDLS writes the scroll lock and the Escape handler separately in each of the three, and
|
|
7
|
+
* none of them manages focus at all — so a keyboard user can Tab straight out of an open
|
|
8
|
+
* modal and carry on in the page behind it, with no way to know where they are.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here touches the DOM until `open` is called, so it is safe to construct on the
|
|
11
|
+
* server.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Everything that can hold focus, minus the things that only look like they can. */
|
|
15
|
+
const FOCUSABLE = [
|
|
16
|
+
'a[href]',
|
|
17
|
+
'button:not([disabled])',
|
|
18
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
19
|
+
'select:not([disabled])',
|
|
20
|
+
'textarea:not([disabled])',
|
|
21
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
22
|
+
].join(',');
|
|
23
|
+
|
|
24
|
+
export function focusableWithin(root) {
|
|
25
|
+
if (!root) return [];
|
|
26
|
+
return [...root.querySelectorAll(FOCUSABLE)].filter((el) => {
|
|
27
|
+
// offsetParent is null for display:none, and for position:fixed — which an overlay's
|
|
28
|
+
// own panel is — so the fixed case is checked by size instead.
|
|
29
|
+
if (el.hidden || el.getAttribute('aria-hidden') === 'true') return false;
|
|
30
|
+
const box = el.getBoundingClientRect();
|
|
31
|
+
return box.width > 0 || box.height > 0;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The scroll lock, counted.
|
|
37
|
+
*
|
|
38
|
+
* Two overlays open at once — a modal that opens a drawer — and the first one to close
|
|
39
|
+
* would hand the page back its scrollbar while the second is still covering it. So the
|
|
40
|
+
* locks are counted and the page is only released when the last one lets go.
|
|
41
|
+
*/
|
|
42
|
+
let lockCount = 0;
|
|
43
|
+
let previousOverflow = '';
|
|
44
|
+
|
|
45
|
+
export function lockScroll(doc = globalThis.document) {
|
|
46
|
+
if (!doc) return;
|
|
47
|
+
if (lockCount === 0) {
|
|
48
|
+
previousOverflow = doc.body.style.overflow;
|
|
49
|
+
doc.body.style.overflow = 'hidden';
|
|
50
|
+
}
|
|
51
|
+
lockCount += 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function unlockScroll(doc = globalThis.document) {
|
|
55
|
+
if (!doc || lockCount === 0) return;
|
|
56
|
+
lockCount -= 1;
|
|
57
|
+
if (lockCount === 0) doc.body.style.overflow = previousOverflow;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Only for tests: the counter is module state and survives between them. */
|
|
61
|
+
export function resetScrollLock(doc = globalThis.document) {
|
|
62
|
+
lockCount = 0;
|
|
63
|
+
if (doc) doc.body.style.overflow = '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Keeps Tab inside `panel` while it is open, and puts focus back where it came from.
|
|
68
|
+
*
|
|
69
|
+
* Returns a release function. Calling it twice is harmless — an overlay that is destroyed
|
|
70
|
+
* while closing would otherwise restore focus to an element that has since moved on.
|
|
71
|
+
*/
|
|
72
|
+
export function trapFocus(panel, { doc = globalThis.document, initial } = {}) {
|
|
73
|
+
if (!panel || !doc) return () => {};
|
|
74
|
+
|
|
75
|
+
const returnTo = doc.activeElement;
|
|
76
|
+
let released = false;
|
|
77
|
+
|
|
78
|
+
const first = initial ?? focusableWithin(panel)[0] ?? panel;
|
|
79
|
+
// A panel with nothing focusable in it still has to take the keyboard, or Tab lands in
|
|
80
|
+
// the page behind. tabindex="-1" makes it focusable without putting it in the tab order.
|
|
81
|
+
if (first === panel && !panel.hasAttribute('tabindex')) panel.setAttribute('tabindex', '-1');
|
|
82
|
+
first.focus?.();
|
|
83
|
+
|
|
84
|
+
const onKeyDown = (event) => {
|
|
85
|
+
if (event.key !== 'Tab') return;
|
|
86
|
+
const stops = focusableWithin(panel);
|
|
87
|
+
if (!stops.length) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const edge = event.shiftKey ? stops[0] : stops[stops.length - 1];
|
|
92
|
+
if (doc.activeElement === edge || !panel.contains(doc.activeElement)) {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
(event.shiftKey ? stops[stops.length - 1] : stops[0]).focus();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
doc.addEventListener('keydown', onKeyDown, true);
|
|
99
|
+
|
|
100
|
+
return () => {
|
|
101
|
+
if (released) return;
|
|
102
|
+
released = true;
|
|
103
|
+
doc.removeEventListener('keydown', onKeyDown, true);
|
|
104
|
+
// Only if it is still there: the thing that opened the overlay may have been removed
|
|
105
|
+
// by what the overlay did.
|
|
106
|
+
if (returnTo && doc.contains(returnTo)) returnTo.focus?.();
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
SDS — Spacing utilities (opt-in)
|
|
3
|
+
Carried from kdls-design-system v2.5.1 (0f3edf8) with the kdls- to sds- rename.
|
|
4
|
+
|
|
5
|
+
Import: @kenlo/sds-design-system/foundations/styles.css
|
|
6
|
+
Brings the tokens and the scrollbar utility along (self-contained),
|
|
7
|
+
so it works standalone, no need to use components.
|
|
8
|
+
|
|
9
|
+
Classes with the SAME names as Tailwind (p-4, gap-2, mx-auto…),
|
|
10
|
+
with the value coming from the tokens. Do NOT load alongside Tailwind —
|
|
11
|
+
pick one: this file OR the preset (tailwind-preset).
|
|
12
|
+
|
|
13
|
+
GENERATED by scripts/gen-styles.mjs (npm run gen:styles) — do not hand-edit.
|
|
14
|
+
============================================================ */
|
|
15
|
+
|
|
16
|
+
@import './tokens.css';
|
|
17
|
+
@import './Scrollbar/Scrollbar.css';
|
|
18
|
+
|
|
19
|
+
/* ── Padding ── */
|
|
20
|
+
.p-0\.5 { padding: var(--spacing-0-5); }
|
|
21
|
+
.p-1 { padding: var(--spacing-1); }
|
|
22
|
+
.p-2 { padding: var(--spacing-2); }
|
|
23
|
+
.p-3 { padding: var(--spacing-3); }
|
|
24
|
+
.p-4 { padding: var(--spacing-4); }
|
|
25
|
+
.p-5 { padding: var(--spacing-5); }
|
|
26
|
+
.p-6 { padding: var(--spacing-6); }
|
|
27
|
+
.p-7 { padding: var(--spacing-7); }
|
|
28
|
+
.p-8 { padding: var(--spacing-8); }
|
|
29
|
+
.p-9 { padding: var(--spacing-9); }
|
|
30
|
+
.p-10 { padding: var(--spacing-10); }
|
|
31
|
+
.p-12 { padding: var(--spacing-12); }
|
|
32
|
+
.p-14 { padding: var(--spacing-14); }
|
|
33
|
+
.p-16 { padding: var(--spacing-16); }
|
|
34
|
+
.px-0\.5 { padding-left: var(--spacing-0-5); padding-right: var(--spacing-0-5); }
|
|
35
|
+
.px-1 { padding-left: var(--spacing-1); padding-right: var(--spacing-1); }
|
|
36
|
+
.px-2 { padding-left: var(--spacing-2); padding-right: var(--spacing-2); }
|
|
37
|
+
.px-3 { padding-left: var(--spacing-3); padding-right: var(--spacing-3); }
|
|
38
|
+
.px-4 { padding-left: var(--spacing-4); padding-right: var(--spacing-4); }
|
|
39
|
+
.px-5 { padding-left: var(--spacing-5); padding-right: var(--spacing-5); }
|
|
40
|
+
.px-6 { padding-left: var(--spacing-6); padding-right: var(--spacing-6); }
|
|
41
|
+
.px-7 { padding-left: var(--spacing-7); padding-right: var(--spacing-7); }
|
|
42
|
+
.px-8 { padding-left: var(--spacing-8); padding-right: var(--spacing-8); }
|
|
43
|
+
.px-9 { padding-left: var(--spacing-9); padding-right: var(--spacing-9); }
|
|
44
|
+
.px-10 { padding-left: var(--spacing-10); padding-right: var(--spacing-10); }
|
|
45
|
+
.px-12 { padding-left: var(--spacing-12); padding-right: var(--spacing-12); }
|
|
46
|
+
.px-14 { padding-left: var(--spacing-14); padding-right: var(--spacing-14); }
|
|
47
|
+
.px-16 { padding-left: var(--spacing-16); padding-right: var(--spacing-16); }
|
|
48
|
+
.py-0\.5 { padding-top: var(--spacing-0-5); padding-bottom: var(--spacing-0-5); }
|
|
49
|
+
.py-1 { padding-top: var(--spacing-1); padding-bottom: var(--spacing-1); }
|
|
50
|
+
.py-2 { padding-top: var(--spacing-2); padding-bottom: var(--spacing-2); }
|
|
51
|
+
.py-3 { padding-top: var(--spacing-3); padding-bottom: var(--spacing-3); }
|
|
52
|
+
.py-4 { padding-top: var(--spacing-4); padding-bottom: var(--spacing-4); }
|
|
53
|
+
.py-5 { padding-top: var(--spacing-5); padding-bottom: var(--spacing-5); }
|
|
54
|
+
.py-6 { padding-top: var(--spacing-6); padding-bottom: var(--spacing-6); }
|
|
55
|
+
.py-7 { padding-top: var(--spacing-7); padding-bottom: var(--spacing-7); }
|
|
56
|
+
.py-8 { padding-top: var(--spacing-8); padding-bottom: var(--spacing-8); }
|
|
57
|
+
.py-9 { padding-top: var(--spacing-9); padding-bottom: var(--spacing-9); }
|
|
58
|
+
.py-10 { padding-top: var(--spacing-10); padding-bottom: var(--spacing-10); }
|
|
59
|
+
.py-12 { padding-top: var(--spacing-12); padding-bottom: var(--spacing-12); }
|
|
60
|
+
.py-14 { padding-top: var(--spacing-14); padding-bottom: var(--spacing-14); }
|
|
61
|
+
.py-16 { padding-top: var(--spacing-16); padding-bottom: var(--spacing-16); }
|
|
62
|
+
.pt-0\.5 { padding-top: var(--spacing-0-5); }
|
|
63
|
+
.pt-1 { padding-top: var(--spacing-1); }
|
|
64
|
+
.pt-2 { padding-top: var(--spacing-2); }
|
|
65
|
+
.pt-3 { padding-top: var(--spacing-3); }
|
|
66
|
+
.pt-4 { padding-top: var(--spacing-4); }
|
|
67
|
+
.pt-5 { padding-top: var(--spacing-5); }
|
|
68
|
+
.pt-6 { padding-top: var(--spacing-6); }
|
|
69
|
+
.pt-7 { padding-top: var(--spacing-7); }
|
|
70
|
+
.pt-8 { padding-top: var(--spacing-8); }
|
|
71
|
+
.pt-9 { padding-top: var(--spacing-9); }
|
|
72
|
+
.pt-10 { padding-top: var(--spacing-10); }
|
|
73
|
+
.pt-12 { padding-top: var(--spacing-12); }
|
|
74
|
+
.pt-14 { padding-top: var(--spacing-14); }
|
|
75
|
+
.pt-16 { padding-top: var(--spacing-16); }
|
|
76
|
+
.pr-0\.5 { padding-right: var(--spacing-0-5); }
|
|
77
|
+
.pr-1 { padding-right: var(--spacing-1); }
|
|
78
|
+
.pr-2 { padding-right: var(--spacing-2); }
|
|
79
|
+
.pr-3 { padding-right: var(--spacing-3); }
|
|
80
|
+
.pr-4 { padding-right: var(--spacing-4); }
|
|
81
|
+
.pr-5 { padding-right: var(--spacing-5); }
|
|
82
|
+
.pr-6 { padding-right: var(--spacing-6); }
|
|
83
|
+
.pr-7 { padding-right: var(--spacing-7); }
|
|
84
|
+
.pr-8 { padding-right: var(--spacing-8); }
|
|
85
|
+
.pr-9 { padding-right: var(--spacing-9); }
|
|
86
|
+
.pr-10 { padding-right: var(--spacing-10); }
|
|
87
|
+
.pr-12 { padding-right: var(--spacing-12); }
|
|
88
|
+
.pr-14 { padding-right: var(--spacing-14); }
|
|
89
|
+
.pr-16 { padding-right: var(--spacing-16); }
|
|
90
|
+
.pb-0\.5 { padding-bottom: var(--spacing-0-5); }
|
|
91
|
+
.pb-1 { padding-bottom: var(--spacing-1); }
|
|
92
|
+
.pb-2 { padding-bottom: var(--spacing-2); }
|
|
93
|
+
.pb-3 { padding-bottom: var(--spacing-3); }
|
|
94
|
+
.pb-4 { padding-bottom: var(--spacing-4); }
|
|
95
|
+
.pb-5 { padding-bottom: var(--spacing-5); }
|
|
96
|
+
.pb-6 { padding-bottom: var(--spacing-6); }
|
|
97
|
+
.pb-7 { padding-bottom: var(--spacing-7); }
|
|
98
|
+
.pb-8 { padding-bottom: var(--spacing-8); }
|
|
99
|
+
.pb-9 { padding-bottom: var(--spacing-9); }
|
|
100
|
+
.pb-10 { padding-bottom: var(--spacing-10); }
|
|
101
|
+
.pb-12 { padding-bottom: var(--spacing-12); }
|
|
102
|
+
.pb-14 { padding-bottom: var(--spacing-14); }
|
|
103
|
+
.pb-16 { padding-bottom: var(--spacing-16); }
|
|
104
|
+
.pl-0\.5 { padding-left: var(--spacing-0-5); }
|
|
105
|
+
.pl-1 { padding-left: var(--spacing-1); }
|
|
106
|
+
.pl-2 { padding-left: var(--spacing-2); }
|
|
107
|
+
.pl-3 { padding-left: var(--spacing-3); }
|
|
108
|
+
.pl-4 { padding-left: var(--spacing-4); }
|
|
109
|
+
.pl-5 { padding-left: var(--spacing-5); }
|
|
110
|
+
.pl-6 { padding-left: var(--spacing-6); }
|
|
111
|
+
.pl-7 { padding-left: var(--spacing-7); }
|
|
112
|
+
.pl-8 { padding-left: var(--spacing-8); }
|
|
113
|
+
.pl-9 { padding-left: var(--spacing-9); }
|
|
114
|
+
.pl-10 { padding-left: var(--spacing-10); }
|
|
115
|
+
.pl-12 { padding-left: var(--spacing-12); }
|
|
116
|
+
.pl-14 { padding-left: var(--spacing-14); }
|
|
117
|
+
.pl-16 { padding-left: var(--spacing-16); }
|
|
118
|
+
|
|
119
|
+
/* ── Margin ── */
|
|
120
|
+
.m-0\.5 { margin: var(--spacing-0-5); }
|
|
121
|
+
.m-1 { margin: var(--spacing-1); }
|
|
122
|
+
.m-2 { margin: var(--spacing-2); }
|
|
123
|
+
.m-3 { margin: var(--spacing-3); }
|
|
124
|
+
.m-4 { margin: var(--spacing-4); }
|
|
125
|
+
.m-5 { margin: var(--spacing-5); }
|
|
126
|
+
.m-6 { margin: var(--spacing-6); }
|
|
127
|
+
.m-7 { margin: var(--spacing-7); }
|
|
128
|
+
.m-8 { margin: var(--spacing-8); }
|
|
129
|
+
.m-9 { margin: var(--spacing-9); }
|
|
130
|
+
.m-10 { margin: var(--spacing-10); }
|
|
131
|
+
.m-12 { margin: var(--spacing-12); }
|
|
132
|
+
.m-14 { margin: var(--spacing-14); }
|
|
133
|
+
.m-16 { margin: var(--spacing-16); }
|
|
134
|
+
.mx-0\.5 { margin-left: var(--spacing-0-5); margin-right: var(--spacing-0-5); }
|
|
135
|
+
.mx-1 { margin-left: var(--spacing-1); margin-right: var(--spacing-1); }
|
|
136
|
+
.mx-2 { margin-left: var(--spacing-2); margin-right: var(--spacing-2); }
|
|
137
|
+
.mx-3 { margin-left: var(--spacing-3); margin-right: var(--spacing-3); }
|
|
138
|
+
.mx-4 { margin-left: var(--spacing-4); margin-right: var(--spacing-4); }
|
|
139
|
+
.mx-5 { margin-left: var(--spacing-5); margin-right: var(--spacing-5); }
|
|
140
|
+
.mx-6 { margin-left: var(--spacing-6); margin-right: var(--spacing-6); }
|
|
141
|
+
.mx-7 { margin-left: var(--spacing-7); margin-right: var(--spacing-7); }
|
|
142
|
+
.mx-8 { margin-left: var(--spacing-8); margin-right: var(--spacing-8); }
|
|
143
|
+
.mx-9 { margin-left: var(--spacing-9); margin-right: var(--spacing-9); }
|
|
144
|
+
.mx-10 { margin-left: var(--spacing-10); margin-right: var(--spacing-10); }
|
|
145
|
+
.mx-12 { margin-left: var(--spacing-12); margin-right: var(--spacing-12); }
|
|
146
|
+
.mx-14 { margin-left: var(--spacing-14); margin-right: var(--spacing-14); }
|
|
147
|
+
.mx-16 { margin-left: var(--spacing-16); margin-right: var(--spacing-16); }
|
|
148
|
+
.my-0\.5 { margin-top: var(--spacing-0-5); margin-bottom: var(--spacing-0-5); }
|
|
149
|
+
.my-1 { margin-top: var(--spacing-1); margin-bottom: var(--spacing-1); }
|
|
150
|
+
.my-2 { margin-top: var(--spacing-2); margin-bottom: var(--spacing-2); }
|
|
151
|
+
.my-3 { margin-top: var(--spacing-3); margin-bottom: var(--spacing-3); }
|
|
152
|
+
.my-4 { margin-top: var(--spacing-4); margin-bottom: var(--spacing-4); }
|
|
153
|
+
.my-5 { margin-top: var(--spacing-5); margin-bottom: var(--spacing-5); }
|
|
154
|
+
.my-6 { margin-top: var(--spacing-6); margin-bottom: var(--spacing-6); }
|
|
155
|
+
.my-7 { margin-top: var(--spacing-7); margin-bottom: var(--spacing-7); }
|
|
156
|
+
.my-8 { margin-top: var(--spacing-8); margin-bottom: var(--spacing-8); }
|
|
157
|
+
.my-9 { margin-top: var(--spacing-9); margin-bottom: var(--spacing-9); }
|
|
158
|
+
.my-10 { margin-top: var(--spacing-10); margin-bottom: var(--spacing-10); }
|
|
159
|
+
.my-12 { margin-top: var(--spacing-12); margin-bottom: var(--spacing-12); }
|
|
160
|
+
.my-14 { margin-top: var(--spacing-14); margin-bottom: var(--spacing-14); }
|
|
161
|
+
.my-16 { margin-top: var(--spacing-16); margin-bottom: var(--spacing-16); }
|
|
162
|
+
.mt-0\.5 { margin-top: var(--spacing-0-5); }
|
|
163
|
+
.mt-1 { margin-top: var(--spacing-1); }
|
|
164
|
+
.mt-2 { margin-top: var(--spacing-2); }
|
|
165
|
+
.mt-3 { margin-top: var(--spacing-3); }
|
|
166
|
+
.mt-4 { margin-top: var(--spacing-4); }
|
|
167
|
+
.mt-5 { margin-top: var(--spacing-5); }
|
|
168
|
+
.mt-6 { margin-top: var(--spacing-6); }
|
|
169
|
+
.mt-7 { margin-top: var(--spacing-7); }
|
|
170
|
+
.mt-8 { margin-top: var(--spacing-8); }
|
|
171
|
+
.mt-9 { margin-top: var(--spacing-9); }
|
|
172
|
+
.mt-10 { margin-top: var(--spacing-10); }
|
|
173
|
+
.mt-12 { margin-top: var(--spacing-12); }
|
|
174
|
+
.mt-14 { margin-top: var(--spacing-14); }
|
|
175
|
+
.mt-16 { margin-top: var(--spacing-16); }
|
|
176
|
+
.mr-0\.5 { margin-right: var(--spacing-0-5); }
|
|
177
|
+
.mr-1 { margin-right: var(--spacing-1); }
|
|
178
|
+
.mr-2 { margin-right: var(--spacing-2); }
|
|
179
|
+
.mr-3 { margin-right: var(--spacing-3); }
|
|
180
|
+
.mr-4 { margin-right: var(--spacing-4); }
|
|
181
|
+
.mr-5 { margin-right: var(--spacing-5); }
|
|
182
|
+
.mr-6 { margin-right: var(--spacing-6); }
|
|
183
|
+
.mr-7 { margin-right: var(--spacing-7); }
|
|
184
|
+
.mr-8 { margin-right: var(--spacing-8); }
|
|
185
|
+
.mr-9 { margin-right: var(--spacing-9); }
|
|
186
|
+
.mr-10 { margin-right: var(--spacing-10); }
|
|
187
|
+
.mr-12 { margin-right: var(--spacing-12); }
|
|
188
|
+
.mr-14 { margin-right: var(--spacing-14); }
|
|
189
|
+
.mr-16 { margin-right: var(--spacing-16); }
|
|
190
|
+
.mb-0\.5 { margin-bottom: var(--spacing-0-5); }
|
|
191
|
+
.mb-1 { margin-bottom: var(--spacing-1); }
|
|
192
|
+
.mb-2 { margin-bottom: var(--spacing-2); }
|
|
193
|
+
.mb-3 { margin-bottom: var(--spacing-3); }
|
|
194
|
+
.mb-4 { margin-bottom: var(--spacing-4); }
|
|
195
|
+
.mb-5 { margin-bottom: var(--spacing-5); }
|
|
196
|
+
.mb-6 { margin-bottom: var(--spacing-6); }
|
|
197
|
+
.mb-7 { margin-bottom: var(--spacing-7); }
|
|
198
|
+
.mb-8 { margin-bottom: var(--spacing-8); }
|
|
199
|
+
.mb-9 { margin-bottom: var(--spacing-9); }
|
|
200
|
+
.mb-10 { margin-bottom: var(--spacing-10); }
|
|
201
|
+
.mb-12 { margin-bottom: var(--spacing-12); }
|
|
202
|
+
.mb-14 { margin-bottom: var(--spacing-14); }
|
|
203
|
+
.mb-16 { margin-bottom: var(--spacing-16); }
|
|
204
|
+
.ml-0\.5 { margin-left: var(--spacing-0-5); }
|
|
205
|
+
.ml-1 { margin-left: var(--spacing-1); }
|
|
206
|
+
.ml-2 { margin-left: var(--spacing-2); }
|
|
207
|
+
.ml-3 { margin-left: var(--spacing-3); }
|
|
208
|
+
.ml-4 { margin-left: var(--spacing-4); }
|
|
209
|
+
.ml-5 { margin-left: var(--spacing-5); }
|
|
210
|
+
.ml-6 { margin-left: var(--spacing-6); }
|
|
211
|
+
.ml-7 { margin-left: var(--spacing-7); }
|
|
212
|
+
.ml-8 { margin-left: var(--spacing-8); }
|
|
213
|
+
.ml-9 { margin-left: var(--spacing-9); }
|
|
214
|
+
.ml-10 { margin-left: var(--spacing-10); }
|
|
215
|
+
.ml-12 { margin-left: var(--spacing-12); }
|
|
216
|
+
.ml-14 { margin-left: var(--spacing-14); }
|
|
217
|
+
.ml-16 { margin-left: var(--spacing-16); }
|
|
218
|
+
|
|
219
|
+
/* ── Gap ── */
|
|
220
|
+
.gap-0\.5 { gap: var(--spacing-0-5); }
|
|
221
|
+
.gap-1 { gap: var(--spacing-1); }
|
|
222
|
+
.gap-2 { gap: var(--spacing-2); }
|
|
223
|
+
.gap-3 { gap: var(--spacing-3); }
|
|
224
|
+
.gap-4 { gap: var(--spacing-4); }
|
|
225
|
+
.gap-5 { gap: var(--spacing-5); }
|
|
226
|
+
.gap-6 { gap: var(--spacing-6); }
|
|
227
|
+
.gap-7 { gap: var(--spacing-7); }
|
|
228
|
+
.gap-8 { gap: var(--spacing-8); }
|
|
229
|
+
.gap-9 { gap: var(--spacing-9); }
|
|
230
|
+
.gap-10 { gap: var(--spacing-10); }
|
|
231
|
+
.gap-12 { gap: var(--spacing-12); }
|
|
232
|
+
.gap-14 { gap: var(--spacing-14); }
|
|
233
|
+
.gap-16 { gap: var(--spacing-16); }
|
|
234
|
+
.gap-x-0\.5 { column-gap: var(--spacing-0-5); }
|
|
235
|
+
.gap-x-1 { column-gap: var(--spacing-1); }
|
|
236
|
+
.gap-x-2 { column-gap: var(--spacing-2); }
|
|
237
|
+
.gap-x-3 { column-gap: var(--spacing-3); }
|
|
238
|
+
.gap-x-4 { column-gap: var(--spacing-4); }
|
|
239
|
+
.gap-x-5 { column-gap: var(--spacing-5); }
|
|
240
|
+
.gap-x-6 { column-gap: var(--spacing-6); }
|
|
241
|
+
.gap-x-7 { column-gap: var(--spacing-7); }
|
|
242
|
+
.gap-x-8 { column-gap: var(--spacing-8); }
|
|
243
|
+
.gap-x-9 { column-gap: var(--spacing-9); }
|
|
244
|
+
.gap-x-10 { column-gap: var(--spacing-10); }
|
|
245
|
+
.gap-x-12 { column-gap: var(--spacing-12); }
|
|
246
|
+
.gap-x-14 { column-gap: var(--spacing-14); }
|
|
247
|
+
.gap-x-16 { column-gap: var(--spacing-16); }
|
|
248
|
+
.gap-y-0\.5 { row-gap: var(--spacing-0-5); }
|
|
249
|
+
.gap-y-1 { row-gap: var(--spacing-1); }
|
|
250
|
+
.gap-y-2 { row-gap: var(--spacing-2); }
|
|
251
|
+
.gap-y-3 { row-gap: var(--spacing-3); }
|
|
252
|
+
.gap-y-4 { row-gap: var(--spacing-4); }
|
|
253
|
+
.gap-y-5 { row-gap: var(--spacing-5); }
|
|
254
|
+
.gap-y-6 { row-gap: var(--spacing-6); }
|
|
255
|
+
.gap-y-7 { row-gap: var(--spacing-7); }
|
|
256
|
+
.gap-y-8 { row-gap: var(--spacing-8); }
|
|
257
|
+
.gap-y-9 { row-gap: var(--spacing-9); }
|
|
258
|
+
.gap-y-10 { row-gap: var(--spacing-10); }
|
|
259
|
+
.gap-y-12 { row-gap: var(--spacing-12); }
|
|
260
|
+
.gap-y-14 { row-gap: var(--spacing-14); }
|
|
261
|
+
.gap-y-16 { row-gap: var(--spacing-16); }
|
|
262
|
+
|
|
263
|
+
/* ── Margin auto ── */
|
|
264
|
+
.m-auto { margin: auto; }
|
|
265
|
+
.mx-auto { margin-left: auto; margin-right: auto; }
|
|
266
|
+
.my-auto { margin-top: auto; margin-bottom: auto; }
|
|
267
|
+
.mt-auto { margin-top: auto; }
|
|
268
|
+
.mr-auto { margin-right: auto; }
|
|
269
|
+
.mb-auto { margin-bottom: auto; }
|
|
270
|
+
.ml-auto { margin-left: auto; }
|
|
271
|
+
|
|
272
|
+
/* ── Visually hidden ──
|
|
273
|
+
Text only a screen reader reads. Not `display: none` and not `visibility: hidden`:
|
|
274
|
+
both of those remove the element from the accessibility tree as well, which is the
|
|
275
|
+
opposite of the point. A control whose visible content is its *value* — a select
|
|
276
|
+
trigger showing "20" — needs its name supplied this way. */
|
|
277
|
+
.sds-visually-hidden {
|
|
278
|
+
position: absolute;
|
|
279
|
+
width: 1px;
|
|
280
|
+
height: 1px;
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
clip-path: inset(50%);
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
}
|