@lyeve-labs/ui-kit 0.12.1 → 0.13.1

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 (87) hide show
  1. package/dist/components/Autocomplete.svelte +191 -125
  2. package/dist/components/Autocomplete.svelte.d.ts +29 -8
  3. package/dist/components/Card.svelte +43 -2
  4. package/dist/components/Card.svelte.d.ts +24 -2
  5. package/dist/components/Checkbox.svelte +174 -63
  6. package/dist/components/Checkbox.svelte.d.ts +20 -3
  7. package/dist/components/CheckboxGroup.svelte +162 -0
  8. package/dist/components/CheckboxGroup.svelte.d.ts +51 -0
  9. package/dist/components/Collapsible.svelte +142 -0
  10. package/dist/components/Collapsible.svelte.d.ts +32 -0
  11. package/dist/components/CopyButton.svelte +126 -0
  12. package/dist/components/CopyButton.svelte.d.ts +14 -0
  13. package/dist/components/DatePicker.svelte +48 -6
  14. package/dist/components/DateTimePicker.svelte +337 -0
  15. package/dist/components/DateTimePicker.svelte.d.ts +26 -0
  16. package/dist/components/DescriptionList.svelte +78 -0
  17. package/dist/components/DescriptionList.svelte.d.ts +34 -0
  18. package/dist/components/Field.svelte +104 -0
  19. package/dist/components/Field.svelte.d.ts +46 -0
  20. package/dist/components/FileInput.svelte +5 -2
  21. package/dist/components/FormMessage.svelte +85 -0
  22. package/dist/components/FormMessage.svelte.d.ts +11 -0
  23. package/dist/components/Input.svelte +1 -1
  24. package/dist/components/Label.svelte +7 -1
  25. package/dist/components/Label.svelte.d.ts +6 -0
  26. package/dist/components/MultiSelect.svelte +199 -109
  27. package/dist/components/MultiSelect.svelte.d.ts +22 -9
  28. package/dist/components/NumberInput.svelte +8 -4
  29. package/dist/components/PageHeader.svelte +37 -4
  30. package/dist/components/PageHeader.svelte.d.ts +15 -0
  31. package/dist/components/PageShell.svelte +85 -0
  32. package/dist/components/PageShell.svelte.d.ts +38 -0
  33. package/dist/components/Pagination.svelte +43 -7
  34. package/dist/components/Panel.svelte +101 -0
  35. package/dist/components/Panel.svelte.d.ts +39 -0
  36. package/dist/components/PasswordInput.svelte +139 -0
  37. package/dist/components/PasswordInput.svelte.d.ts +29 -0
  38. package/dist/components/Radio.svelte +152 -35
  39. package/dist/components/Radio.svelte.d.ts +16 -1
  40. package/dist/components/RadioGroup.svelte +118 -71
  41. package/dist/components/RadioGroup.svelte.d.ts +39 -9
  42. package/dist/components/SectionHeading.svelte +39 -0
  43. package/dist/components/SectionHeading.svelte.d.ts +21 -0
  44. package/dist/components/SegmentedControl.svelte +194 -0
  45. package/dist/components/SegmentedControl.svelte.d.ts +55 -0
  46. package/dist/components/Select.svelte +471 -46
  47. package/dist/components/Select.svelte.d.ts +95 -6
  48. package/dist/components/SidebarNav.svelte +264 -0
  49. package/dist/components/SidebarNav.svelte.d.ts +17 -0
  50. package/dist/components/Stat.svelte +53 -2
  51. package/dist/components/Stat.svelte.d.ts +31 -0
  52. package/dist/components/Textarea.svelte +1 -1
  53. package/dist/components/TimePicker.svelte +480 -0
  54. package/dist/components/TimePicker.svelte.d.ts +23 -0
  55. package/dist/components/Toolbar.svelte +39 -0
  56. package/dist/components/Toolbar.svelte.d.ts +26 -0
  57. package/dist/components/TreeView.svelte +339 -0
  58. package/dist/components/TreeView.svelte.d.ts +37 -0
  59. package/dist/index.d.ts +25 -1
  60. package/dist/index.js +20 -1
  61. package/dist/internal/calendar.d.ts +119 -0
  62. package/dist/internal/calendar.js +225 -0
  63. package/dist/internal/choice.d.ts +136 -0
  64. package/dist/internal/choice.js +179 -0
  65. package/dist/internal/field.d.ts +39 -0
  66. package/dist/internal/field.js +48 -0
  67. package/dist/internal/filter.d.ts +80 -0
  68. package/dist/internal/filter.js +80 -0
  69. package/dist/internal/layout.d.ts +119 -0
  70. package/dist/internal/layout.js +132 -0
  71. package/dist/internal/listbox.svelte.d.ts +77 -0
  72. package/dist/internal/listbox.svelte.js +438 -0
  73. package/dist/internal/nav-expansion.svelte.d.ts +36 -0
  74. package/dist/internal/nav-expansion.svelte.js +144 -0
  75. package/dist/internal/nav-tree.d.ts +68 -0
  76. package/dist/internal/nav-tree.js +102 -0
  77. package/dist/internal/panel.d.ts +100 -0
  78. package/dist/internal/panel.js +109 -0
  79. package/dist/internal/rollup.d.ts +52 -0
  80. package/dist/internal/rollup.js +67 -0
  81. package/dist/internal/time.d.ts +103 -0
  82. package/dist/internal/time.js +166 -0
  83. package/dist/internal/tree.d.ts +86 -0
  84. package/dist/internal/tree.js +111 -0
  85. package/dist/styles/theme.css +18 -10
  86. package/package.json +4 -2
  87. package/src/lib/styles/theme.css +18 -10
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The arithmetic behind TimePicker's hour, minute and optional second segments.
3
+ *
4
+ * The value the field reads and writes is RFC 3339 partial-time with no
5
+ * fraction:
6
+ *
7
+ * unset ''
8
+ * seconds: false /^([01]\d|2[0-3]):([0-5]\d)$/ for example '09:30'
9
+ * seconds: true /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/ for example '09:30:15'
10
+ *
11
+ * 24-hour, zero padded, no offset, no fractional seconds, no '24:00', no ':60'.
12
+ *
13
+ * Every helper here is a step a component author gets wrong on the first
14
+ * attempt: hours that carry when the arrow key promised one segment, a range
15
+ * check that cannot express an overnight window, per-segment clamping that
16
+ * rewrites a legal time, and a modulo that turns midnight into hour zero of a
17
+ * clock with no hour zero.
18
+ *
19
+ * Not exported from the package entry point - this is an implementation detail.
20
+ */
21
+ /** Highest legal value plus one, per stepped segment. */
22
+ const SEGMENT_SIZE = {
23
+ hour: 24,
24
+ minute: 60,
25
+ second: 60,
26
+ };
27
+ const SECONDS_PER_MINUTE = 60;
28
+ const SECONDS_PER_HOUR = 3600;
29
+ /** The whole time as one number, so a comparison cannot compare the minute first. */
30
+ function secondsOf(p) {
31
+ return p.h * SECONDS_PER_HOUR + p.mi * SECONDS_PER_MINUTE + p.s;
32
+ }
33
+ /**
34
+ * The segments are fixed width and the third is optional, which is the whole
35
+ * grammar. Anchored at both ends so a trailing offset or a fraction fails.
36
+ */
37
+ const ISO_TIME = /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/;
38
+ /**
39
+ * Range-checks rather than wrapping: '24:00' and '00:60' return null, not a
40
+ * rolled-over value.
41
+ *
42
+ * Parsing through Date was the first attempt and it accepts both, because
43
+ * '1970-01-01T24:00' is the following midnight. A picker fed that displayed
44
+ * 00:00 for a string the server had already rejected. A single unpadded digit
45
+ * such as '9:30' is rejected for the same reason: the segments are fixed width,
46
+ * and accepting a short one lets a keystroke part way through an entry read as
47
+ * a committed value.
48
+ */
49
+ export function parseISOTime(s) {
50
+ if (typeof s !== 'string')
51
+ return null;
52
+ const m = ISO_TIME.exec(s);
53
+ if (!m)
54
+ return null;
55
+ const sec = m[3];
56
+ return { h: Number(m[1]), mi: Number(m[2]), s: sec ? Number(sec) : 0 };
57
+ }
58
+ /**
59
+ * Writes a time back in the one format the field accepts.
60
+ *
61
+ * `seconds` decides whether the third segment is written at all. A picker with
62
+ * no second segment that emits '09:30:00' round-trips a field the user cannot
63
+ * see and can never correct.
64
+ */
65
+ export function toISOTime(p, seconds) {
66
+ const base = `${pad2(p.h)}:${pad2(p.mi)}`;
67
+ return seconds ? `${base}:${pad2(p.s)}` : base;
68
+ }
69
+ /**
70
+ * Steps one segment without carrying into its neighbour. Stepping the minute
71
+ * past 59 wraps to 0 and leaves the hour alone, because a spinner that changes
72
+ * two fields at once is not what the arrow key promised.
73
+ *
74
+ * A step that does not divide its segment lands on the far end rather than
75
+ * carrying the remainder: the minute 59 with step 15 goes to 0, not to 14, and
76
+ * stepping down from 0 goes to 59, not to 45. The remainder is a value the user
77
+ * cannot predict from the key they pressed.
78
+ *
79
+ * `meridiem` has two values, so it ignores `step` and moves the hour by twelve
80
+ * once per unit of delta. An even delta lands back where it started.
81
+ */
82
+ export function stepSegment(p, segment, delta, step) {
83
+ if (segment === 'meridiem') {
84
+ const flips = Math.abs(Math.trunc(delta)) % 2;
85
+ return { ...p, h: (p.h + 12 * flips) % SEGMENT_SIZE.hour };
86
+ }
87
+ const size = SEGMENT_SIZE[segment];
88
+ const current = segment === 'hour' ? p.h : segment === 'minute' ? p.mi : p.s;
89
+ const next = current + delta * step;
90
+ const moved = next > size - 1 ? 0 : next < 0 ? size - 1 : next;
91
+ if (segment === 'hour')
92
+ return { ...p, h: moved };
93
+ if (segment === 'minute')
94
+ return { ...p, mi: moved };
95
+ return { ...p, s: moved };
96
+ }
97
+ /**
98
+ * Inclusive bounds. A range whose min is greater than its max wraps past
99
+ * midnight and is a legal way to say "overnight", so the check is a disjunction
100
+ * rather than a conjunction in that case.
101
+ *
102
+ * The conjunction alone makes 22:00 to 06:00 match nothing, and a night shift
103
+ * picker built on it rejected every time a user could enter.
104
+ */
105
+ export function withinTimeRange(p, min, max) {
106
+ const t = secondsOf(p);
107
+ if (min !== null && max !== null) {
108
+ const lo = secondsOf(min);
109
+ const hi = secondsOf(max);
110
+ return lo > hi ? t >= lo || t <= hi : t >= lo && t <= hi;
111
+ }
112
+ if (min !== null)
113
+ return t >= secondsOf(min);
114
+ if (max !== null)
115
+ return t <= secondsOf(max);
116
+ return true;
117
+ }
118
+ /**
119
+ * Clamps a whole time to the bounds. Clamping per segment is wrong: with min
120
+ * 09:30, the time 10:15 is legal and per-segment clamping would push its minute
121
+ * to 30.
122
+ *
123
+ * When the range wraps past midnight the excluded window is the gap between max
124
+ * and min, so a time inside it moves to whichever end is nearer around the
125
+ * clock. A tie moves back to max, because the user was on the earlier side of
126
+ * the window before the step that left it.
127
+ */
128
+ export function clampTime(p, min, max) {
129
+ if (withinTimeRange(p, min, max))
130
+ return p;
131
+ if (min !== null && max !== null && secondsOf(min) > secondsOf(max)) {
132
+ const t = secondsOf(p);
133
+ return t - secondsOf(max) <= secondsOf(min) - t ? max : min;
134
+ }
135
+ if (min !== null && secondsOf(p) < secondsOf(min))
136
+ return min;
137
+ if (max !== null && secondsOf(p) > secondsOf(max))
138
+ return max;
139
+ return p;
140
+ }
141
+ /**
142
+ * 24-hour to 12-hour display, returning the hour and the meridiem. Midnight is
143
+ * 12 AM and noon is 12 PM, which is where the naive modulo gets it wrong: it
144
+ * gives 0 for both, and a picker showing '0:00 AM' has invented an hour zero
145
+ * that no 12-hour clock has.
146
+ */
147
+ export function to12Hour(h) {
148
+ const wrapped = h % 12;
149
+ return { hour: wrapped === 0 ? 12 : wrapped, meridiem: h < 12 ? 'AM' : 'PM' };
150
+ }
151
+ /**
152
+ * 12-hour back to 24. 12 AM is hour 0 and 12 PM is hour 12; every other hour is
153
+ * itself in the morning and itself plus twelve in the afternoon.
154
+ */
155
+ export function from12Hour(hour, meridiem) {
156
+ const base = hour % 12;
157
+ return meridiem === 'PM' ? base + 12 : base;
158
+ }
159
+ /**
160
+ * Zero-pads a segment for display. A minute of 5 written straight into the
161
+ * value gives '09:5', which parseISOTime then rejects, so the field drops the
162
+ * time the user just picked and blanks itself.
163
+ */
164
+ export function pad2(n) {
165
+ return String(n).padStart(2, '0');
166
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The shape of a data tree and the arithmetic a checkable one needs.
3
+ *
4
+ * A data tree is not a navigation menu. A menu shows where you can go and one
5
+ * item is current; a tree shows a structure, and a branch in it summarises
6
+ * everything beneath it while a click on that branch writes every one of them.
7
+ * Those two jobs are the reason this module exists: the summary and the write
8
+ * have to be computed from the same list, or a branch reports a state its own
9
+ * click does not produce.
10
+ *
11
+ * The tri-state itself comes from rollup.ts rather than being written again
12
+ * here. That module already holds the case a fold gets wrong, and a second
13
+ * implementation of it would be a second chance to get it wrong.
14
+ *
15
+ * Not exported from the package entry point - this is an implementation detail.
16
+ */
17
+ import type { Component } from 'svelte';
18
+ import { type TriState } from './rollup.js';
19
+ /** Re-exported so a consumer of a branch's state does not have to know where the arithmetic lives. */
20
+ export type { TriState };
21
+ /** One node of a data tree. */
22
+ export interface TreeNode<T = unknown> {
23
+ /** Stable across renders. */
24
+ id: string;
25
+ label: string;
26
+ icon?: Component<{
27
+ size?: number;
28
+ class?: string;
29
+ }>;
30
+ children?: TreeNode<T>[];
31
+ /** A count or status beside the label. */
32
+ badge?: string | number;
33
+ disabled?: boolean;
34
+ /** Whatever the consumer needs on activation. */
35
+ data?: T;
36
+ }
37
+ /** One visible row, in the order a keyboard walks them. */
38
+ export interface TreeRow<T = unknown> {
39
+ node: TreeNode<T>;
40
+ depth: number;
41
+ parentId?: string;
42
+ }
43
+ /**
44
+ * Whether a node has anything to descend into.
45
+ *
46
+ * An empty `children` array is a leaf, not a branch that happens to be empty.
47
+ * A node that announces `aria-expanded` and then opens onto nothing tells a
48
+ * screen reader user there is more to read when there is not.
49
+ */
50
+ export declare function isBranch<T>(node: TreeNode<T>): boolean;
51
+ /** Every leaf under a node, excluding disabled ones, because a disabled leaf must be neither written nor counted. */
52
+ export declare function leavesOf<T>(node: TreeNode<T>): TreeNode<T>[];
53
+ /**
54
+ * The tri-state of a branch, from the checked set.
55
+ *
56
+ * A leaf is its own only leaf, so a caller can ask this of any node and get an
57
+ * answer that matches what a click on it will do. A branch whose leaves are all
58
+ * disabled has none to count and reports 'none': `leaves.every(isChecked)` is
59
+ * true over an empty array, which would draw that branch as fully checked and
60
+ * make the next click on it clear rows nobody chose.
61
+ */
62
+ export declare function branchState<T>(node: TreeNode<T>, checked: ReadonlySet<string>): TriState;
63
+ /**
64
+ * The ids to add and remove to set a whole subtree.
65
+ *
66
+ * Only leaf ids are ever returned. A branch's state is derived from its leaves
67
+ * on every render, so storing the branch as well gives the two a way to
68
+ * disagree, and the stored one always wins on the next paint.
69
+ *
70
+ * The direction is decided by counting the same list the write is filtered
71
+ * from, which is what keeps a partly disabled branch honest: the state that
72
+ * says 'fill' cannot have counted a leaf the fill will skip.
73
+ */
74
+ export declare function toggleSubtree<T>(node: TreeNode<T>, checked: ReadonlySet<string>): {
75
+ add: string[];
76
+ remove: string[];
77
+ };
78
+ /**
79
+ * The visible rows, in order, with the depth and parent each one needs.
80
+ *
81
+ * A collapsed branch contributes its own row and nothing below it. The arrow
82
+ * keys walk this list, so a row that is not in it cannot be reached by keyboard
83
+ * either, which is the only way the keyboard order and the painted order stay
84
+ * the same list.
85
+ */
86
+ export declare function flattenTree<T>(nodes: TreeNode<T>[], expanded: ReadonlySet<string>): TreeRow<T>[];
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The shape of a data tree and the arithmetic a checkable one needs.
3
+ *
4
+ * A data tree is not a navigation menu. A menu shows where you can go and one
5
+ * item is current; a tree shows a structure, and a branch in it summarises
6
+ * everything beneath it while a click on that branch writes every one of them.
7
+ * Those two jobs are the reason this module exists: the summary and the write
8
+ * have to be computed from the same list, or a branch reports a state its own
9
+ * click does not produce.
10
+ *
11
+ * The tri-state itself comes from rollup.ts rather than being written again
12
+ * here. That module already holds the case a fold gets wrong, and a second
13
+ * implementation of it would be a second chance to get it wrong.
14
+ *
15
+ * Not exported from the package entry point - this is an implementation detail.
16
+ */
17
+ import { nextState, rollUp, setSubtree } from './rollup.js';
18
+ /**
19
+ * Whether a node has anything to descend into.
20
+ *
21
+ * An empty `children` array is a leaf, not a branch that happens to be empty.
22
+ * A node that announces `aria-expanded` and then opens onto nothing tells a
23
+ * screen reader user there is more to read when there is not.
24
+ */
25
+ export function isBranch(node) {
26
+ return (node.children?.length ?? 0) > 0;
27
+ }
28
+ /**
29
+ * Every leaf under a node, disabled ones included, so the write and the count
30
+ * can be taken from one list.
31
+ *
32
+ * The walk stops at a disabled branch. Nothing in the subtree below it can be
33
+ * reached from any ancestor's control, so counting those leaves would describe
34
+ * rows the write skips.
35
+ */
36
+ function walkLeaves(node, out) {
37
+ if (!isBranch(node)) {
38
+ out.push(node);
39
+ return;
40
+ }
41
+ if (node.disabled)
42
+ return;
43
+ for (const child of node.children ?? [])
44
+ walkLeaves(child, out);
45
+ }
46
+ /** Every leaf under a node, excluding disabled ones, because a disabled leaf must be neither written nor counted. */
47
+ export function leavesOf(node) {
48
+ const reachable = [];
49
+ walkLeaves(node, reachable);
50
+ return reachable.filter((leaf) => !leaf.disabled);
51
+ }
52
+ /**
53
+ * The tri-state of a branch, from the checked set.
54
+ *
55
+ * A leaf is its own only leaf, so a caller can ask this of any node and get an
56
+ * answer that matches what a click on it will do. A branch whose leaves are all
57
+ * disabled has none to count and reports 'none': `leaves.every(isChecked)` is
58
+ * true over an empty array, which would draw that branch as fully checked and
59
+ * make the next click on it clear rows nobody chose.
60
+ */
61
+ export function branchState(node, checked) {
62
+ return rollUp(leavesOf(node), (leaf) => checked.has(leaf.id));
63
+ }
64
+ /**
65
+ * The ids to add and remove to set a whole subtree.
66
+ *
67
+ * Only leaf ids are ever returned. A branch's state is derived from its leaves
68
+ * on every render, so storing the branch as well gives the two a way to
69
+ * disagree, and the stored one always wins on the next paint.
70
+ *
71
+ * The direction is decided by counting the same list the write is filtered
72
+ * from, which is what keeps a partly disabled branch honest: the state that
73
+ * says 'fill' cannot have counted a leaf the fill will skip.
74
+ */
75
+ export function toggleSubtree(node, checked) {
76
+ const reachable = [];
77
+ walkLeaves(node, reachable);
78
+ const settable = (leaf) => !leaf.disabled;
79
+ const value = nextState(rollUp(reachable.filter(settable), (leaf) => checked.has(leaf.id)));
80
+ const add = [];
81
+ const remove = [];
82
+ for (const write of setSubtree(reachable, value, settable)) {
83
+ const held = checked.has(write.item.id);
84
+ if (write.value && !held)
85
+ add.push(write.item.id);
86
+ if (!write.value && held)
87
+ remove.push(write.item.id);
88
+ }
89
+ return { add, remove };
90
+ }
91
+ /**
92
+ * The visible rows, in order, with the depth and parent each one needs.
93
+ *
94
+ * A collapsed branch contributes its own row and nothing below it. The arrow
95
+ * keys walk this list, so a row that is not in it cannot be reached by keyboard
96
+ * either, which is the only way the keyboard order and the painted order stay
97
+ * the same list.
98
+ */
99
+ export function flattenTree(nodes, expanded) {
100
+ const rows = [];
101
+ const visit = (list, depth, parentId) => {
102
+ for (const node of list) {
103
+ rows.push(parentId === undefined ? { node, depth } : { node, depth, parentId });
104
+ if (isBranch(node) && expanded.has(node.id)) {
105
+ visit(node.children ?? [], depth + 1, node.id);
106
+ }
107
+ }
108
+ };
109
+ visit(nodes, 0);
110
+ return rows;
111
+ }
@@ -97,13 +97,13 @@
97
97
  * Caption 13px 1.50 0 400 Labels, metadata *
98
98
  * Mono 18px 1.50 0 500 Code, eyebrows *
99
99
  * */
100
- --font-size-display: 4rem; /* 64px */
101
- --font-size-h1: 2.75rem; /* 44px */
102
- --font-size-h2: 2rem; /* 32px */
103
- --font-size-h3: 1.375rem; /* 22px */
104
- --font-size-body: 1rem; /* 16px */
105
- --font-size-caption: 0.8125rem; /* 13px */
106
- --font-size-mono: 1.125rem; /* 18px */
100
+ --text-display: 4rem; /* 64px */
101
+ --text-h1: 2.75rem; /* 44px */
102
+ --text-h2: 2rem; /* 32px */
103
+ --text-h3: 1.375rem; /* 22px */
104
+ --text-body: 1rem; /* 16px */
105
+ --text-caption: 0.8125rem; /* 13px */
106
+ --text-mono: 1.125rem; /* 18px */
107
107
 
108
108
  --leading-display: 1.05;
109
109
  --leading-h1: 1.1;
@@ -122,16 +122,24 @@
122
122
  * Tailwind's default 0.25rem (4px) grid is unchanged. *
123
123
  * These tokens add named slots for common component spacing. */
124
124
  --spacing-page-x: 1.5rem; /* 24px - page horizontal gutter */
125
- --spacing-page-y: 2rem; /* 32px - page vertical gutter */
126
- --spacing-card: 1.5rem; /* 24px - card inner padding */
125
+ --spacing-page-y: 1.5rem; /* 24px - page vertical gutter. Equal to page-x because no page
126
+ * measured renders an asymmetric gutter; the two names stay
127
+ * separate so one axis can move without dragging the other. */
128
+ --spacing-card: 1.25rem; /* 20px - card inner padding. 24px matched none of the card
129
+ * surfaces in use; 20px is their measured mode and Card's own md. */
127
130
  --spacing-card-sm: 1rem; /* 16px - compact card padding */
128
- --spacing-section: 3rem; /* 48px - section gap */
131
+ --spacing-section: 2rem; /* 32px - section gap. 48px matched nothing anywhere; 32px is the
132
+ * gap the pages that set one already use, and PageHeader's own. */
129
133
  --spacing-input-x: 0.75rem; /* 12px - input horizontal padding */
130
134
  --spacing-input-y: 0.5rem; /* 8px - input vertical padding */
131
135
  /* 38px - the height of every single-line control. Stated once so an Input,
132
136
  * a Select and a NumberInput sitting in the same row line up. Derived from
133
137
  * the 13px/1.25 control text plus input-y padding and a 1px border. */
134
138
  --spacing-control: 2.375rem;
139
+ --spacing-panel-max: 15rem; /* 240px - the scroll cap on a floating option list */
140
+ --spacing-nav-indent: 0.875rem; /* 14px - one level of sidebar nesting */
141
+ --spacing-nav-rail: 3.5rem; /* 56px - the collapsed icon rail */
142
+ --spacing-sidebar: 14rem; /* 224px - the expanded sidebar */
135
143
  --spacing-stack: 1rem; /* 16px - default vertical stack gap */
136
144
  --spacing-inline: 0.5rem; /* 8px - default inline gap */
137
145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyeve-labs/ui-kit",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "description": "A clean, accessible, palette-aware Svelte 5 component library. The design system behind LyEve.",
5
5
  "license": "MIT",
6
6
  "author": "LyEve Labs <hello@lyeve.com>",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "type": "module",
25
25
  "sideEffects": false,
26
- "packageManager": "pnpm@10.33.4",
26
+ "packageManager": "pnpm@11.25.0",
27
27
  "engines": {
28
28
  "node": ">=20"
29
29
  },
@@ -37,6 +37,8 @@
37
37
  "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
38
38
  "test": "vitest run",
39
39
  "test:watch": "vitest",
40
+ "test:coverage": "vitest run --coverage",
41
+ "test:ct": "playwright test --config playwright-ct.config.ts",
40
42
  "format": "prettier --write .",
41
43
  "format:check": "prettier --check ."
42
44
  },
@@ -97,13 +97,13 @@
97
97
  * Caption 13px 1.50 0 400 Labels, metadata *
98
98
  * Mono 18px 1.50 0 500 Code, eyebrows *
99
99
  * */
100
- --font-size-display: 4rem; /* 64px */
101
- --font-size-h1: 2.75rem; /* 44px */
102
- --font-size-h2: 2rem; /* 32px */
103
- --font-size-h3: 1.375rem; /* 22px */
104
- --font-size-body: 1rem; /* 16px */
105
- --font-size-caption: 0.8125rem; /* 13px */
106
- --font-size-mono: 1.125rem; /* 18px */
100
+ --text-display: 4rem; /* 64px */
101
+ --text-h1: 2.75rem; /* 44px */
102
+ --text-h2: 2rem; /* 32px */
103
+ --text-h3: 1.375rem; /* 22px */
104
+ --text-body: 1rem; /* 16px */
105
+ --text-caption: 0.8125rem; /* 13px */
106
+ --text-mono: 1.125rem; /* 18px */
107
107
 
108
108
  --leading-display: 1.05;
109
109
  --leading-h1: 1.1;
@@ -122,16 +122,24 @@
122
122
  * Tailwind's default 0.25rem (4px) grid is unchanged. *
123
123
  * These tokens add named slots for common component spacing. */
124
124
  --spacing-page-x: 1.5rem; /* 24px - page horizontal gutter */
125
- --spacing-page-y: 2rem; /* 32px - page vertical gutter */
126
- --spacing-card: 1.5rem; /* 24px - card inner padding */
125
+ --spacing-page-y: 1.5rem; /* 24px - page vertical gutter. Equal to page-x because no page
126
+ * measured renders an asymmetric gutter; the two names stay
127
+ * separate so one axis can move without dragging the other. */
128
+ --spacing-card: 1.25rem; /* 20px - card inner padding. 24px matched none of the card
129
+ * surfaces in use; 20px is their measured mode and Card's own md. */
127
130
  --spacing-card-sm: 1rem; /* 16px - compact card padding */
128
- --spacing-section: 3rem; /* 48px - section gap */
131
+ --spacing-section: 2rem; /* 32px - section gap. 48px matched nothing anywhere; 32px is the
132
+ * gap the pages that set one already use, and PageHeader's own. */
129
133
  --spacing-input-x: 0.75rem; /* 12px - input horizontal padding */
130
134
  --spacing-input-y: 0.5rem; /* 8px - input vertical padding */
131
135
  /* 38px - the height of every single-line control. Stated once so an Input,
132
136
  * a Select and a NumberInput sitting in the same row line up. Derived from
133
137
  * the 13px/1.25 control text plus input-y padding and a 1px border. */
134
138
  --spacing-control: 2.375rem;
139
+ --spacing-panel-max: 15rem; /* 240px - the scroll cap on a floating option list */
140
+ --spacing-nav-indent: 0.875rem; /* 14px - one level of sidebar nesting */
141
+ --spacing-nav-rail: 3.5rem; /* 56px - the collapsed icon rail */
142
+ --spacing-sidebar: 14rem; /* 224px - the expanded sidebar */
135
143
  --spacing-stack: 1rem; /* 16px - default vertical stack gap */
136
144
  --spacing-inline: 0.5rem; /* 8px - default inline gap */
137
145
  }