@lyeve-labs/ui-kit 0.17.1 → 0.19.0
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 +27 -17
- package/dist/components/AccordionItem.svelte +2 -2
- package/dist/components/AccountMenu.svelte +1 -1
- package/dist/components/AppShell.svelte +22 -8
- package/dist/components/Autocomplete.svelte +13 -4
- package/dist/components/AvatarGroup.svelte +2 -2
- package/dist/components/Breadcrumb.svelte +1 -1
- package/dist/components/Button.svelte +34 -9
- package/dist/components/ButtonGroup.svelte +3 -3
- package/dist/components/Card.svelte +2 -1
- package/dist/components/Checkbox.svelte +1 -1
- package/dist/components/CheckboxGroup.svelte +1 -1
- package/dist/components/Collapsible.svelte +4 -4
- package/dist/components/CopyButton.svelte +1 -1
- package/dist/components/DatePicker.svelte +3 -3
- package/dist/components/DateTimePicker.svelte +1 -1
- package/dist/components/Drawer.svelte +7 -27
- package/dist/components/Dropdown.svelte +197 -11
- package/dist/components/Dropdown.svelte.d.ts +12 -0
- package/dist/components/Field.svelte +1 -1
- package/dist/components/Input.svelte +1 -1
- package/dist/components/Label.svelte +1 -1
- package/dist/components/Logo.svelte +77 -0
- package/dist/components/Logo.svelte.d.ts +35 -0
- package/dist/components/Modal.svelte +2 -15
- package/dist/components/MultiSelect.svelte +16 -7
- package/dist/components/NumberInput.svelte +3 -3
- package/dist/components/PageHeader.svelte +23 -1
- package/dist/components/Pagination.svelte +164 -65
- package/dist/components/Pagination.svelte.d.ts +39 -2
- package/dist/components/Panel.svelte +1 -1
- package/dist/components/PasswordInput.svelte +4 -4
- package/dist/components/Radio.svelte +1 -1
- package/dist/components/RadioGroup.svelte +1 -1
- package/dist/components/SearchInput.svelte +13 -4
- package/dist/components/SegmentedControl.svelte +3 -1
- package/dist/components/Select.svelte +6 -6
- package/dist/components/SidebarNav.svelte +20 -5
- package/dist/components/Table.svelte +256 -10
- package/dist/components/Table.svelte.d.ts +44 -0
- package/dist/components/Tabs.svelte +1 -1
- package/dist/components/Tag.svelte +1 -1
- package/dist/components/Textarea.svelte +1 -1
- package/dist/components/ThemeToggle.svelte +76 -13
- package/dist/components/ThemeToggle.svelte.d.ts +9 -0
- package/dist/components/TimePicker.svelte +2 -2
- package/dist/components/Toaster.svelte +3 -15
- package/dist/components/Toggle.svelte +10 -4
- package/dist/components/Tooltip.svelte +1 -1
- package/dist/components/TreeView.svelte +4 -4
- package/dist/components/dialog/Dialog.svelte +10 -5
- package/dist/components/dialog/dialog-manager.svelte.d.ts +20 -1
- package/dist/components/dialog/dialog-manager.svelte.js +13 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -2
- package/dist/internal/href.d.ts +13 -0
- package/dist/internal/href.js +27 -0
- package/dist/internal/layout.js +11 -2
- package/dist/internal/panel.d.ts +1 -1
- package/dist/internal/panel.js +2 -2
- package/dist/styles/theme.css +288 -0
- package/dist/utils/theme.d.ts +68 -3
- package/dist/utils/theme.js +118 -3
- package/package.json +20 -22
- package/src/lib/styles/theme.css +288 -0
|
@@ -40,13 +40,32 @@ export declare function dismissDialog(id?: string): void;
|
|
|
40
40
|
* Close all open dialogs, rejecting their promises.
|
|
41
41
|
*/
|
|
42
42
|
export declare function dismissAllDialogs(): void;
|
|
43
|
+
/** The labels a caller may override on a confirm dialog. */
|
|
44
|
+
export interface ConfirmOptions {
|
|
45
|
+
/**
|
|
46
|
+
* The verb on the accepting button. Defaults to `Confirm`.
|
|
47
|
+
*
|
|
48
|
+
* Name the action rather than accepting the default wherever the caller has
|
|
49
|
+
* a verb of its own: a button reading `Delete` says what happens, and one
|
|
50
|
+
* reading `Confirm` only says that something does. ConfirmDialog has always
|
|
51
|
+
* read this, but until it was reachable from here a caller had to drop to
|
|
52
|
+
* openDialog and setDialogMeta to reach it, so every call site took the
|
|
53
|
+
* generic label instead.
|
|
54
|
+
*/
|
|
55
|
+
confirmLabel?: string;
|
|
56
|
+
/** The verb on the dismissing button. Defaults to `Cancel`. */
|
|
57
|
+
cancelLabel?: string;
|
|
58
|
+
}
|
|
43
59
|
/**
|
|
44
60
|
* Convenience: confirm dialog.
|
|
45
61
|
* Returns `true` if user confirmed, `false` if cancelled.
|
|
46
62
|
*
|
|
47
63
|
* const ok = await confirm('Delete item?', 'This cannot be undone.');
|
|
64
|
+
* const ok = await confirm('Delete item?', 'This cannot be undone.', {
|
|
65
|
+
* confirmLabel: 'Delete',
|
|
66
|
+
* });
|
|
48
67
|
*/
|
|
49
|
-
export declare function confirm(title: string, message?: string): Promise<boolean>;
|
|
68
|
+
export declare function confirm(title: string, message?: string, options?: ConfirmOptions): Promise<boolean>;
|
|
50
69
|
/**
|
|
51
70
|
* Set metadata on a dialog entry after it's opened.
|
|
52
71
|
* Used by convenience wrappers to pass extra data without polluting DialogOptions.
|
|
@@ -136,11 +136,22 @@ export function dismissAllDialogs() {
|
|
|
136
136
|
* Returns `true` if user confirmed, `false` if cancelled.
|
|
137
137
|
*
|
|
138
138
|
* const ok = await confirm('Delete item?', 'This cannot be undone.');
|
|
139
|
+
* const ok = await confirm('Delete item?', 'This cannot be undone.', {
|
|
140
|
+
* confirmLabel: 'Delete',
|
|
141
|
+
* });
|
|
139
142
|
*/
|
|
140
|
-
export function confirm(title, message) {
|
|
143
|
+
export function confirm(title, message, options) {
|
|
141
144
|
const id = `confirm-${++idCounter}`;
|
|
142
145
|
const promise = openDialog({ id, size: 'sm', title });
|
|
143
|
-
setDialogMeta(id, {
|
|
146
|
+
setDialogMeta(id, {
|
|
147
|
+
confirmTitle: title,
|
|
148
|
+
confirmMessage: message ?? '',
|
|
149
|
+
// Undefined keys are dropped rather than written, because ConfirmDialog
|
|
150
|
+
// decides its default by type-checking the meta value: writing undefined
|
|
151
|
+
// would be indistinguishable from an override to the empty case.
|
|
152
|
+
...(options?.confirmLabel !== undefined ? { confirmLabel: options.confirmLabel } : {}),
|
|
153
|
+
...(options?.cancelLabel !== undefined ? { cancelLabel: options.cancelLabel } : {}),
|
|
154
|
+
});
|
|
144
155
|
// Cancelling a dialog dismisses it, and dismissal rejects. Callers write
|
|
145
156
|
// `if (await confirm(...))`, so a rejection on Cancel is an unhandled
|
|
146
157
|
// rejection on the ordinary path rather than an error anyone meant to
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { default as Table } from './components/Table.svelte';
|
|
|
20
20
|
export { default as DescriptionList } from './components/DescriptionList.svelte';
|
|
21
21
|
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
22
22
|
export { default as TreeView } from './components/TreeView.svelte';
|
|
23
|
+
export type { CellFit } from './components/Table.svelte';
|
|
23
24
|
export type { PageWidth, SectionVariant } from './internal/layout.js';
|
|
24
25
|
export type { TreeNode, TriState } from './internal/tree.js';
|
|
25
26
|
export { default as Button } from './components/Button.svelte';
|
|
@@ -75,6 +76,7 @@ export { default as Kbd } from './components/Kbd.svelte';
|
|
|
75
76
|
export { default as CopyButton } from './components/CopyButton.svelte';
|
|
76
77
|
export { default as Avatar } from './components/Avatar.svelte';
|
|
77
78
|
export { default as AvatarGroup } from './components/AvatarGroup.svelte';
|
|
79
|
+
export { default as Logo } from './components/Logo.svelte';
|
|
78
80
|
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
79
81
|
export { default as Toaster } from './components/Toaster.svelte';
|
|
80
82
|
export { toast } from './stores/toast.svelte.js';
|
|
@@ -83,7 +85,8 @@ export { default as DialogContainer } from './components/dialog/DialogContainer.
|
|
|
83
85
|
export { default as Dialog } from './components/dialog/Dialog.svelte';
|
|
84
86
|
export { default as ConfirmDialog } from './components/dialog/ConfirmDialog.svelte';
|
|
85
87
|
export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, setDialogMeta, getDialogStack, } from './components/dialog/dialog-manager.svelte.js';
|
|
88
|
+
export type { ConfirmOptions } from './components/dialog/dialog-manager.svelte.js';
|
|
86
89
|
export type { DialogOptions, DialogEntry, DialogSize } from './components/dialog/types.js';
|
|
87
90
|
export { cn, type ClassValue } from './utils/cn.js';
|
|
88
|
-
export { getTheme, setTheme,
|
|
89
|
-
export declare const VERSION = "0.
|
|
91
|
+
export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
|
|
92
|
+
export declare const VERSION = "0.19.0";
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,10 @@ export { default as CopyButton } from './components/CopyButton.svelte';
|
|
|
76
76
|
// ── Media ──────────────────────────────────────────────────────────────────
|
|
77
77
|
export { default as Avatar } from './components/Avatar.svelte';
|
|
78
78
|
export { default as AvatarGroup } from './components/AvatarGroup.svelte';
|
|
79
|
+
// The product mark. Two applications carried their own copy of the same lockup
|
|
80
|
+
// and the two had drifted apart in size, in weight and in how they answered the
|
|
81
|
+
// theme; the kit owns it now.
|
|
82
|
+
export { default as Logo } from './components/Logo.svelte';
|
|
79
83
|
// ── Theming & toasts ───────────────────────────────────────────────────────
|
|
80
84
|
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
81
85
|
export { default as Toaster } from './components/Toaster.svelte';
|
|
@@ -88,8 +92,8 @@ export { default as ConfirmDialog } from './components/dialog/ConfirmDialog.svel
|
|
|
88
92
|
export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, setDialogMeta, getDialogStack, } from './components/dialog/dialog-manager.svelte.js';
|
|
89
93
|
// ── Utilities ──────────────────────────────────────────────────────────────
|
|
90
94
|
export { cn } from './utils/cn.js';
|
|
91
|
-
export { getTheme, setTheme,
|
|
95
|
+
export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, toggleTheme, watchSystemTheme, THEME_PREFERENCES, } from './utils/theme.js';
|
|
92
96
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
93
97
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
94
98
|
// this line; the build and the test suite fail when the two disagree.
|
|
95
|
-
export const VERSION = '0.
|
|
99
|
+
export const VERSION = '0.19.0';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one rule the kit applies to an href it did not write itself.
|
|
3
|
+
*
|
|
4
|
+
* A component takes a URL from its caller, and a caller takes it from data, so
|
|
5
|
+
* `javascript:` and `data:` arrive as markup and leave as a script the page
|
|
6
|
+
* never wrote. Button carried this rule inline and Pagination now builds hrefs
|
|
7
|
+
* from a caller supplied function, and a rule kept in two places is a rule that
|
|
8
|
+
* gets fixed in one of them.
|
|
9
|
+
*
|
|
10
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
11
|
+
*/
|
|
12
|
+
/** The href as given, or nothing when its scheme is one no component will emit. */
|
|
13
|
+
export declare function safeHref(href: string | undefined | null): string | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one rule the kit applies to an href it did not write itself.
|
|
3
|
+
*
|
|
4
|
+
* A component takes a URL from its caller, and a caller takes it from data, so
|
|
5
|
+
* `javascript:` and `data:` arrive as markup and leave as a script the page
|
|
6
|
+
* never wrote. Button carried this rule inline and Pagination now builds hrefs
|
|
7
|
+
* from a caller supplied function, and a rule kept in two places is a rule that
|
|
8
|
+
* gets fixed in one of them.
|
|
9
|
+
*
|
|
10
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The characters a browser drops before it reads the scheme.
|
|
14
|
+
*
|
|
15
|
+
* The URL parser trims leading and trailing ASCII whitespace and strips tab,
|
|
16
|
+
* line feed and carriage return from anywhere in the value, so a tab written
|
|
17
|
+
* inside the word `javascript` still navigates while an anchored scheme test on
|
|
18
|
+
* the raw string sees an unknown scheme and allows it through.
|
|
19
|
+
*/
|
|
20
|
+
const IGNORED = /[\u0000-\u0020]/g;
|
|
21
|
+
const DENIED = /^(?:javascript|data|vbscript):/i;
|
|
22
|
+
/** The href as given, or nothing when its scheme is one no component will emit. */
|
|
23
|
+
export function safeHref(href) {
|
|
24
|
+
if (!href)
|
|
25
|
+
return undefined;
|
|
26
|
+
return DENIED.test(href.replace(IGNORED, '')) ? undefined : href;
|
|
27
|
+
}
|
package/dist/internal/layout.js
CHANGED
|
@@ -137,10 +137,19 @@ export function sectionHeading(level, variant = 'default') {
|
|
|
137
137
|
// it rather than a title, so it does not take the level's size at all.
|
|
138
138
|
if (variant === 'eyebrow')
|
|
139
139
|
return 'text-xs font-medium uppercase tracking-wide text-faint';
|
|
140
|
+
// Level 2 is the brand ramp's H3, which the guideline describes as the card
|
|
141
|
+
// and group head this is. Its leading and tracking come with it: a --text-*
|
|
142
|
+
// token sets font-size and nothing else, so the ratio has to be asked for by
|
|
143
|
+
// name or the heading inherits whatever line-height its parent had.
|
|
144
|
+
//
|
|
140
145
|
// Level 3 drops a size rather than a weight. Inside a card it sits under the
|
|
141
146
|
// card's own semibold title, and two semibold lines at the same size read as
|
|
142
|
-
// one heading broken in half.
|
|
143
|
-
|
|
147
|
+
// one heading broken in half. It stays at text-sm: the ramp holds nothing
|
|
148
|
+
// between 16px and 22px, and 16px is exactly the size of the card title it
|
|
149
|
+
// has to sit below.
|
|
150
|
+
return level === 2
|
|
151
|
+
? 'text-h3 leading-h3 tracking-h3 font-semibold text-fg'
|
|
152
|
+
: 'text-sm font-semibold text-fg';
|
|
144
153
|
}
|
|
145
154
|
/**
|
|
146
155
|
* The authed app frame, stated once for every app that has one.
|
package/dist/internal/panel.d.ts
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* needs a boundary that clears 3:1, which line does not. It carries the
|
|
41
41
|
* resting text colour so a row can override it with a single utility.
|
|
42
42
|
*/
|
|
43
|
-
export declare const PANEL_SURFACE = "absolute z-
|
|
43
|
+
export declare const PANEL_SURFACE = "absolute z-dropdown mt-1 rounded-xl border border-line-strong bg-surface text-fg shadow-2xl";
|
|
44
44
|
/**
|
|
45
45
|
* The scrolling region inside it.
|
|
46
46
|
*
|
package/dist/internal/panel.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* needs a boundary that clears 3:1, which line does not. It carries the
|
|
41
41
|
* resting text colour so a row can override it with a single utility.
|
|
42
42
|
*/
|
|
43
|
-
export const PANEL_SURFACE = 'absolute z-
|
|
43
|
+
export const PANEL_SURFACE = 'absolute z-dropdown mt-1 rounded-xl border border-line-strong bg-surface text-fg shadow-2xl';
|
|
44
44
|
/**
|
|
45
45
|
* The scrolling region inside it.
|
|
46
46
|
*
|
|
@@ -55,7 +55,7 @@ export const PANEL_LIST = 'max-h-panel-max overflow-y-auto overscroll-contain py
|
|
|
55
55
|
* No background and no text colour of its own: it inherits both from the
|
|
56
56
|
* surface, which leaves each state below a single utility to override.
|
|
57
57
|
*/
|
|
58
|
-
export const PANEL_OPTION = 'flex w-full items-center gap-2.5 px-3 py-2 text-
|
|
58
|
+
export const PANEL_OPTION = 'flex w-full items-center gap-2.5 px-3 py-2 text-start text-sm ' +
|
|
59
59
|
'transition-colors duration-150 outline-none hover:bg-surface-2 ' +
|
|
60
60
|
'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand';
|
|
61
61
|
/**
|
package/dist/styles/theme.css
CHANGED
|
@@ -79,6 +79,61 @@
|
|
|
79
79
|
--radius-xl: 16px;
|
|
80
80
|
--radius-2xl: 24px;
|
|
81
81
|
|
|
82
|
+
/* -- Elevation - the stacking order of everything that floats --- *
|
|
83
|
+
* Every floating surface in the kit carried a bare z-50, so which
|
|
84
|
+
* one won was decided by document order: a Dropdown opened after a
|
|
85
|
+
* Modal painted over it, and the same two in the other order did
|
|
86
|
+
* not. The layer a surface belongs to is a property of the surface,
|
|
87
|
+
* so it is named here once and each component asks for its layer by
|
|
88
|
+
* name.
|
|
89
|
+
*
|
|
90
|
+
* The ramp starts at 100 and leaves everything below it to the
|
|
91
|
+
* consuming app. The admin's own chrome sits at z-10 and z-20
|
|
92
|
+
* today, and a kit layer landing on either number would tie with
|
|
93
|
+
* it and resolve, again, by document order.
|
|
94
|
+
*
|
|
95
|
+
* The steps are 100 apart because one of these layers holds a
|
|
96
|
+
* stack rather than a surface: the dialog manager offsets each
|
|
97
|
+
* dialog above the one it opened over, and a ramp in steps of ten
|
|
98
|
+
* would put the eleventh dialog through the ceiling of its own
|
|
99
|
+
* layer and under the tooltip above it.
|
|
100
|
+
*
|
|
101
|
+
* tooltip sits above modal because a tooltip on a control inside a
|
|
102
|
+
* dialog is drawn while that dialog is the topmost surface. toast
|
|
103
|
+
* sits above tooltip because it reports the outcome of the action
|
|
104
|
+
* the tooltip described. skip-link is above everything: it is the
|
|
105
|
+
* first focusable thing in the document and nothing may cover it.
|
|
106
|
+
*
|
|
107
|
+
* No sticky rung is declared. Nothing in the kit is sticky or
|
|
108
|
+
* fixed below the dropdown layer, and a token no rule reads is the
|
|
109
|
+
* defect this scale was written to end. */
|
|
110
|
+
--z-index-dropdown: 100; /* a panel anchored to its trigger */
|
|
111
|
+
--z-index-overlay: 200; /* the scrim behind a drawer or modal */
|
|
112
|
+
--z-index-drawer: 300; /* a panel docked to a viewport edge */
|
|
113
|
+
--z-index-modal: 400; /* a centred dialog, and the stack */
|
|
114
|
+
--z-index-tooltip: 500; /* a label attached to a control */
|
|
115
|
+
--z-index-toast: 600; /* transient status */
|
|
116
|
+
--z-index-skip-link: 700; /* the first tab stop in the document */
|
|
117
|
+
|
|
118
|
+
/* -- Motion - how long a transition or an entrance runs -------- *
|
|
119
|
+
* Four durations shipped as literals inside the animate-[...]
|
|
120
|
+
* utility of the component that used them, so the only way to
|
|
121
|
+
* read the kit's motion was to grep four components. They are
|
|
122
|
+
* stated here at the values they already ran at.
|
|
123
|
+
*
|
|
124
|
+
* Tailwind builds no utility from a --duration-* token: there is
|
|
125
|
+
* no such theme namespace, and `duration-collapse` compiles to
|
|
126
|
+
* nothing at all. Each one is read through var() from an
|
|
127
|
+
* arbitrary utility instead, which is why they are listed in
|
|
128
|
+
* VAR_ONLY in theme-tokens.test.ts.
|
|
129
|
+
*
|
|
130
|
+
* The reduced-motion block at the foot of this file overrides
|
|
131
|
+
* every one of them and stays the authority on whether they run. */
|
|
132
|
+
--duration-modal-in: 120ms; /* Modal, the centred entrance */
|
|
133
|
+
--duration-toast-in: 140ms; /* Toaster, one toast arriving */
|
|
134
|
+
--duration-drawer-in: 150ms; /* Drawer, either edge */
|
|
135
|
+
--duration-collapse: 200ms; /* Accordion and Collapsible */
|
|
136
|
+
|
|
82
137
|
/* ── Typography · families ───────────────────────────────────── */
|
|
83
138
|
--font-sans:
|
|
84
139
|
'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
|
@@ -96,6 +151,8 @@
|
|
|
96
151
|
* Body 16px 1.55 0 400 Default body copy *
|
|
97
152
|
* Caption 13px 1.50 0 400 Labels, metadata *
|
|
98
153
|
* Mono 18px 1.50 0 500 Code, eyebrows *
|
|
154
|
+
* The ramp the guideline states, for marketing surfaces. *
|
|
155
|
+
* The scale the console renders is declared below it. *
|
|
99
156
|
* */
|
|
100
157
|
--text-display: 4rem; /* 64px */
|
|
101
158
|
--text-h1: 2.75rem; /* 44px */
|
|
@@ -118,6 +175,57 @@
|
|
|
118
175
|
--tracking-h2: -0.02em;
|
|
119
176
|
--tracking-h3: -0.015em;
|
|
120
177
|
|
|
178
|
+
/* -- Typography - console type scale --------------------------- *
|
|
179
|
+
* Token Size Line Where it renders *
|
|
180
|
+
* text-xs 12px 16px labels, table heads, metadata, hints*
|
|
181
|
+
* text-sm 14px 20px body copy and every control *
|
|
182
|
+
* text-base 16px 24px a card title, the large button *
|
|
183
|
+
* text-lg 18px 28px a dialog title, a small stat value *
|
|
184
|
+
* text-xl 20px 28px the large wordmark *
|
|
185
|
+
* text-2xl 24px 32px a stat value, an empty-state glyph *
|
|
186
|
+
*
|
|
187
|
+
* Two scales, on purpose, and this is the one the components
|
|
188
|
+
* are drawn in.
|
|
189
|
+
*
|
|
190
|
+
* The ramp above is the brand guideline's, and it is a marketing
|
|
191
|
+
* ramp: it runs 64 / 44 / 32 / 22 / 16 / 13, holds nothing
|
|
192
|
+
* between 16px and 22px, and has no step at 14px at all. A
|
|
193
|
+
* console's body copy is 14px and its labels are 12px, both
|
|
194
|
+
* below the ramp's floor, so adopting the ramp wholesale would
|
|
195
|
+
* have meant restyling every screen to fit a scale drawn for a
|
|
196
|
+
* landing page. The ramp keeps its steps for the surfaces it was
|
|
197
|
+
* drawn for, and the two overlap at exactly the two steps a
|
|
198
|
+
* console does reach: PageHeader takes H2 for the page title and
|
|
199
|
+
* SectionHeading takes H3 for a section head.
|
|
200
|
+
*
|
|
201
|
+
* These are Tailwind's own steps at Tailwind's own values,
|
|
202
|
+
* stated here rather than inherited silently. Nothing renders
|
|
203
|
+
* one pixel differently for it. What changes is that the scale
|
|
204
|
+
* the components are built from is now declared beside the one
|
|
205
|
+
* they are not, so a designer reading this file sees both, and
|
|
206
|
+
* moving console body copy to 15px is one line here instead of
|
|
207
|
+
* 49 class attributes. Restated rather than renamed: an app
|
|
208
|
+
* writing `text-sm` in its own markup and a component writing it
|
|
209
|
+
* in the kit's must mean the same size, and a private spelling
|
|
210
|
+
* of the same 14px would be a second name for one thing.
|
|
211
|
+
*
|
|
212
|
+
* A step is declared with its leading, because a --text-* token
|
|
213
|
+
* sets font-size and nothing else. Only the six steps the kit
|
|
214
|
+
* actually renders are declared; the larger ones a marketing
|
|
215
|
+
* page reaches for stay as Tailwind ships them. */
|
|
216
|
+
--text-xs: 0.75rem; /* 12px */
|
|
217
|
+
--text-xs--line-height: calc(1 / 0.75); /* 16px */
|
|
218
|
+
--text-sm: 0.875rem; /* 14px */
|
|
219
|
+
--text-sm--line-height: calc(1.25 / 0.875); /* 20px */
|
|
220
|
+
--text-base: 1rem; /* 16px */
|
|
221
|
+
--text-base--line-height: calc(1.5 / 1); /* 24px */
|
|
222
|
+
--text-lg: 1.125rem; /* 18px */
|
|
223
|
+
--text-lg--line-height: calc(1.75 / 1.125); /* 28px */
|
|
224
|
+
--text-xl: 1.25rem; /* 20px */
|
|
225
|
+
--text-xl--line-height: calc(1.75 / 1.25); /* 28px */
|
|
226
|
+
--text-2xl: 1.5rem; /* 24px */
|
|
227
|
+
--text-2xl--line-height: calc(2 / 1.5); /* 32px */
|
|
228
|
+
|
|
121
229
|
/* ── Spacing grid · 4px base (semantic shortcuts) ────────────── *
|
|
122
230
|
* Tailwind's default 0.25rem (4px) grid is unchanged. *
|
|
123
231
|
* These tokens add named slots for common component spacing. */
|
|
@@ -181,6 +289,65 @@ html[data-theme='light'] {
|
|
|
181
289
|
--color-danger: #ab1a1a;
|
|
182
290
|
}
|
|
183
291
|
|
|
292
|
+
/*
|
|
293
|
+
* The entrance keyframes, declared here and not in the components that play
|
|
294
|
+
* them.
|
|
295
|
+
*
|
|
296
|
+
* Modal, Drawer and Toaster each held their own `@keyframes` in a scoped
|
|
297
|
+
* `<style>` block and started it from a Tailwind `animate-[...]` class. Svelte
|
|
298
|
+
* renames a scoped keyframe to `svelte-<hash>-<name>` and rewrites only the
|
|
299
|
+
* references inside that same style block, so the class asked the browser for
|
|
300
|
+
* `modal-in` while the only rule declared was `svelte-ta60gp-modal-in`. An
|
|
301
|
+
* animation-name that resolves to nothing is not an error: the element simply
|
|
302
|
+
* appears, and all four entrances had been dead in every consumer.
|
|
303
|
+
*
|
|
304
|
+
* A keyframe named from a class has to be global, and the file every consumer
|
|
305
|
+
* already imports is the one place it can be global from.
|
|
306
|
+
*/
|
|
307
|
+
@keyframes modal-in {
|
|
308
|
+
from {
|
|
309
|
+
opacity: 0;
|
|
310
|
+
transform: translateY(6px) scale(0.98);
|
|
311
|
+
}
|
|
312
|
+
to {
|
|
313
|
+
opacity: 1;
|
|
314
|
+
transform: translateY(0) scale(1);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@keyframes drawer-in-right {
|
|
319
|
+
from {
|
|
320
|
+
opacity: 0;
|
|
321
|
+
transform: translateX(16px);
|
|
322
|
+
}
|
|
323
|
+
to {
|
|
324
|
+
opacity: 1;
|
|
325
|
+
transform: translateX(0);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@keyframes drawer-in-left {
|
|
330
|
+
from {
|
|
331
|
+
opacity: 0;
|
|
332
|
+
transform: translateX(-16px);
|
|
333
|
+
}
|
|
334
|
+
to {
|
|
335
|
+
opacity: 1;
|
|
336
|
+
transform: translateX(0);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@keyframes toast-in {
|
|
341
|
+
from {
|
|
342
|
+
opacity: 0;
|
|
343
|
+
transform: translateX(12px);
|
|
344
|
+
}
|
|
345
|
+
to {
|
|
346
|
+
opacity: 1;
|
|
347
|
+
transform: translateX(0);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
184
351
|
@layer base {
|
|
185
352
|
html {
|
|
186
353
|
background-color: var(--color-ink);
|
|
@@ -238,3 +405,124 @@ html[data-theme='light'] {
|
|
|
238
405
|
}
|
|
239
406
|
}
|
|
240
407
|
}
|
|
408
|
+
|
|
409
|
+
/*
|
|
410
|
+
* Print.
|
|
411
|
+
*
|
|
412
|
+
* The estate had no `@media print` rule anywhere, and three surfaces print:
|
|
413
|
+
* the portal prints an invoice, the ops console prints an audit log, the admin
|
|
414
|
+
* prints a subject-access export. All three printed the sidebar, the theme
|
|
415
|
+
* toggle and the nav, in a dark palette, and all three printed one screenful
|
|
416
|
+
* and stopped, because the shell is `h-screen` with the content column set to
|
|
417
|
+
* `overflow-auto` and paper has neither a viewport nor a scrollbar.
|
|
418
|
+
*
|
|
419
|
+
* These rules are deliberately outside `@layer base`. An unlayered declaration
|
|
420
|
+
* beats every layered one whatever its specificity, so a `bg-surface` utility
|
|
421
|
+
* on a card cannot put the dark surface back.
|
|
422
|
+
*
|
|
423
|
+
* Three markers, each stating what paper needs rather than what an element is:
|
|
424
|
+
*
|
|
425
|
+
* data-print="hide" chrome that exists to navigate. Paper cannot be
|
|
426
|
+
* navigated, so it is noise on every page.
|
|
427
|
+
* data-print="unclip" a box that clips on screen because the screen is a
|
|
428
|
+
* fixed height. On paper it must give up its height and
|
|
429
|
+
* its overflow or the content past the first page is
|
|
430
|
+
* never printed.
|
|
431
|
+
* data-print="keep" a block that reads as one thing and must not be split
|
|
432
|
+
* across a page break.
|
|
433
|
+
*/
|
|
434
|
+
@media print {
|
|
435
|
+
/*
|
|
436
|
+
* The palette, not the components.
|
|
437
|
+
*
|
|
438
|
+
* Every kit component paints from these tokens, so redefining them here
|
|
439
|
+
* turns the whole library monochrome without a single component knowing
|
|
440
|
+
* about paper. Browsers drop backgrounds when printing by default, which
|
|
441
|
+
* left light text on the white the printer supplies; naming white here means
|
|
442
|
+
* the two agree either way.
|
|
443
|
+
*
|
|
444
|
+
* The status hues collapse to black rather than to a print-safe tint. A
|
|
445
|
+
* colour that carries meaning on screen carries none through a mono printer,
|
|
446
|
+
* and a legible black label beats an unreadable coloured one.
|
|
447
|
+
*/
|
|
448
|
+
html,
|
|
449
|
+
html[data-theme='light'] {
|
|
450
|
+
--color-ink: #ffffff;
|
|
451
|
+
--color-surface: #ffffff;
|
|
452
|
+
--color-surface-2: #ffffff;
|
|
453
|
+
--color-line: #999999;
|
|
454
|
+
--color-line-strong: #666666;
|
|
455
|
+
--color-fg: #000000;
|
|
456
|
+
--color-muted: #000000;
|
|
457
|
+
--color-faint: #000000;
|
|
458
|
+
--color-brand: #000000;
|
|
459
|
+
--color-brand-light: #000000;
|
|
460
|
+
--color-brand-deep: #000000;
|
|
461
|
+
--color-violet: #000000;
|
|
462
|
+
--color-success: #000000;
|
|
463
|
+
--color-warn: #000000;
|
|
464
|
+
--color-danger: #000000;
|
|
465
|
+
color-scheme: light;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
html,
|
|
469
|
+
body {
|
|
470
|
+
background: #ffffff;
|
|
471
|
+
color: #000000;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
[data-print='hide'] {
|
|
475
|
+
display: none !important;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/*
|
|
479
|
+
* height as well as overflow. The shell is `h-screen`, so releasing the
|
|
480
|
+
* overflow alone still caps the printed document at one viewport.
|
|
481
|
+
*/
|
|
482
|
+
[data-print='unclip'] {
|
|
483
|
+
overflow: visible !important;
|
|
484
|
+
height: auto !important;
|
|
485
|
+
max-height: none !important;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
[data-print='keep'],
|
|
489
|
+
tr,
|
|
490
|
+
li {
|
|
491
|
+
break-inside: avoid;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* A table running past one page repeats its own column headings. */
|
|
495
|
+
thead {
|
|
496
|
+
display: table-header-group;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
tfoot {
|
|
500
|
+
display: table-footer-group;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/*
|
|
504
|
+
* A heading orphaned at the foot of a page belongs to the section on the
|
|
505
|
+
* next one.
|
|
506
|
+
*/
|
|
507
|
+
h1,
|
|
508
|
+
h2,
|
|
509
|
+
h3 {
|
|
510
|
+
break-after: avoid;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/*
|
|
514
|
+
* The target of a link, for the links a reader cannot follow from paper and
|
|
515
|
+
* could not guess.
|
|
516
|
+
*
|
|
517
|
+
* Absolute links only. An invoice full of "(/portal/invoices/8841)" after
|
|
518
|
+
* every internal link is noise, and the reader holding the paper is already
|
|
519
|
+
* inside that application. A caller whose absolute link is decorative, or
|
|
520
|
+
* whose text is the URL already, opts out per link.
|
|
521
|
+
*/
|
|
522
|
+
a[href^='http']:not([data-print='bare'])::after {
|
|
523
|
+
content: ' <' attr(href) '>';
|
|
524
|
+
font-size: 0.85em;
|
|
525
|
+
font-weight: normal;
|
|
526
|
+
word-break: break-all;
|
|
527
|
+
}
|
|
528
|
+
}
|
package/dist/utils/theme.d.ts
CHANGED
|
@@ -5,22 +5,87 @@
|
|
|
5
5
|
* The `<ThemeToggle />` component uses these under the hood; you can also
|
|
6
6
|
* call them directly when you need to set the theme from a layout, route
|
|
7
7
|
* loader, or an inline `<script>` that runs before paint.
|
|
8
|
+
*
|
|
9
|
+
* Two vocabularies, and the distinction is the whole point:
|
|
10
|
+
*
|
|
11
|
+
* Theme what is painted. `dark` or `light`, and it is what
|
|
12
|
+
* `data-theme` holds and what every palette rule keys off.
|
|
13
|
+
* ThemePreference what the reader asked for. `dark`, `light`, or `system`,
|
|
14
|
+
* which is a standing instruction to follow the operating
|
|
15
|
+
* system rather than a colour.
|
|
16
|
+
*
|
|
17
|
+
* The kit resolved a preference in exactly one place, the pre-paint script,
|
|
18
|
+
* and nothing else could read it back. A control could therefore offer two
|
|
19
|
+
* states and no third, so a reader who wanted the operating system's choice
|
|
20
|
+
* had to keep re-picking it by hand, and one console forked ThemeToggle rather
|
|
21
|
+
* than ship that. `system` is now a value the storage key holds and every
|
|
22
|
+
* helper here understands.
|
|
8
23
|
*/
|
|
24
|
+
/** What is painted. */
|
|
9
25
|
export type Theme = 'dark' | 'light';
|
|
26
|
+
/** What the reader asked for. `system` resolves against the OS at read time. */
|
|
27
|
+
export type ThemePreference = Theme | 'system';
|
|
28
|
+
/** The order a control cycles through, and the order a picker lists. */
|
|
29
|
+
export declare const THEME_PREFERENCES: readonly ThemePreference[];
|
|
10
30
|
/**
|
|
11
31
|
* Inline-script payload that should run *before* the first paint to avoid
|
|
12
32
|
* the flash-of-wrong-theme. Drop the returned string into a `<script>` tag
|
|
13
33
|
* in the `<head>` of your app shell.
|
|
14
34
|
*
|
|
15
35
|
* Picks the theme in this order:
|
|
16
|
-
* 1. `localStorage["lyeve-theme"]`
|
|
17
|
-
* 2. The user's OS preference (`prefers-color-scheme`)
|
|
18
|
-
*
|
|
36
|
+
* 1. `localStorage["lyeve-theme"]` when it holds `'light'` or `'dark'`
|
|
37
|
+
* 2. The user's OS preference (`prefers-color-scheme`), which is also what
|
|
38
|
+
* the stored value `'system'` asks for
|
|
39
|
+
* 3. Dark (the default brand palette), including when storage throws
|
|
19
40
|
*/
|
|
20
41
|
export declare function themeBootScript(): string;
|
|
42
|
+
/**
|
|
43
|
+
* What the operating system is asking for.
|
|
44
|
+
*
|
|
45
|
+
* Dark unless the OS says light, which is the same fallback the pre-paint
|
|
46
|
+
* script uses and the same one a browser without `matchMedia` gets.
|
|
47
|
+
*/
|
|
48
|
+
export declare function systemTheme(): Theme;
|
|
49
|
+
/** The theme a preference resolves to right now. */
|
|
50
|
+
export declare function resolveTheme(preference: ThemePreference): Theme;
|
|
51
|
+
/**
|
|
52
|
+
* What the reader asked for, from storage.
|
|
53
|
+
*
|
|
54
|
+
* `system` when nothing is stored, which is the default this kit resolves by
|
|
55
|
+
* and not a guess: an unanswered question is answered by the operating system,
|
|
56
|
+
* because the reader already answered it there.
|
|
57
|
+
*/
|
|
58
|
+
export declare function getThemePreference(): ThemePreference;
|
|
21
59
|
/** Read the current theme. SSR-safe; returns `'dark'` on the server. */
|
|
22
60
|
export declare function getTheme(): Theme;
|
|
61
|
+
/**
|
|
62
|
+
* Record a preference, apply what it resolves to, and return the applied theme.
|
|
63
|
+
*
|
|
64
|
+
* `system` is stored under its own name rather than by clearing the key. The
|
|
65
|
+
* two are indistinguishable on the next read otherwise, and they are not the
|
|
66
|
+
* same thing: one reader chose to follow the OS and the other has not chosen
|
|
67
|
+
* anything yet. Both resolve identically today; only one of them is a decision
|
|
68
|
+
* a later default may not override.
|
|
69
|
+
*/
|
|
70
|
+
export declare function setThemePreference(preference: ThemePreference): Theme;
|
|
23
71
|
/** Apply a theme and persist the choice to `localStorage`. */
|
|
24
72
|
export declare function setTheme(theme: Theme): void;
|
|
73
|
+
/**
|
|
74
|
+
* The next preference in `order`, wrapping at the end.
|
|
75
|
+
*
|
|
76
|
+
* Takes the list so a surface that offers only two states cycles through only
|
|
77
|
+
* those two. A preference that is not in the list starts the cycle at its
|
|
78
|
+
* first entry rather than falling out of it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function nextThemePreference(current: ThemePreference, order?: readonly ThemePreference[]): ThemePreference;
|
|
25
81
|
/** Flip between dark and light. Returns the new theme. */
|
|
26
82
|
export declare function toggleTheme(): Theme;
|
|
83
|
+
/**
|
|
84
|
+
* Call `onChange` whenever the operating system's choice changes. Returns the
|
|
85
|
+
* unsubscribe function, and a no-op one where there is no `matchMedia`.
|
|
86
|
+
*
|
|
87
|
+
* A page resolving `system` has to repaint when the OS flips at dusk. Without
|
|
88
|
+
* this the preference is honoured once, at load, and reads as ignored for the
|
|
89
|
+
* rest of the session.
|
|
90
|
+
*/
|
|
91
|
+
export declare function watchSystemTheme(onChange: (theme: Theme) => void): () => void;
|