@lyeve-labs/ui-kit 0.44.0 → 0.46.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/dist/components/AccountMenu.svelte +4 -1
- package/dist/components/AppShell.svelte +42 -10
- package/dist/components/AppShell.svelte.d.ts +8 -0
- package/dist/components/AuthShell.svelte +9 -2
- package/dist/components/Badge.svelte +7 -6
- package/dist/components/Button.svelte +29 -5
- package/dist/components/Card.svelte +9 -4
- package/dist/components/Drawer.svelte +1 -1
- package/dist/components/EmptyState.svelte +9 -3
- package/dist/components/Modal.svelte +1 -1
- package/dist/components/PageHeader.svelte +10 -4
- package/dist/components/Pagination.svelte +9 -22
- package/dist/components/PluginCart.svelte +32 -10
- package/dist/components/PluginCart.svelte.d.ts +8 -0
- package/dist/components/Progress.svelte +17 -3
- package/dist/components/SegmentedControl.svelte +2 -2
- package/dist/components/SidebarNav.svelte +14 -1
- package/dist/components/SortableList.svelte +1 -1
- package/dist/components/Stat.svelte +13 -3
- package/dist/components/Table.svelte +20 -9
- package/dist/components/Tabs.svelte +77 -10
- package/dist/components/Toaster.svelte +1 -1
- package/dist/components/Tooltip.svelte +1 -1
- package/dist/components/dialog/Dialog.svelte +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/field.js +12 -1
- package/dist/internal/layout.d.ts +29 -4
- package/dist/internal/layout.js +37 -5
- package/dist/internal/panel.d.ts +4 -1
- package/dist/internal/panel.js +4 -1
- package/dist/internal/scroll-edges.d.ts +9 -8
- package/dist/internal/scroll-edges.js +9 -8
- package/dist/styles/theme.css +203 -0
- package/package.json +1 -1
- package/src/lib/styles/theme.css +203 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* use this one instead.
|
|
9
9
|
*/
|
|
10
10
|
import type { StatusTone } from '../internal/tone.js';
|
|
11
|
+
import { CARD_SURFACE } from '../internal/layout.js';
|
|
11
12
|
|
|
12
13
|
type Trend = 'up' | 'down' | 'flat';
|
|
13
14
|
type Accent = 'brand' | 'violet' | 'success' | 'neutral';
|
|
@@ -74,8 +75,8 @@
|
|
|
74
75
|
|
|
75
76
|
const pads: Record<Size, string> = { sm: 'p-4', md: 'p-5' };
|
|
76
77
|
const values: Record<Size, string> = {
|
|
77
|
-
sm: 'mt-1 text-lg font-semibold',
|
|
78
|
-
md: 'mt-2 text-2xl font-bold',
|
|
78
|
+
sm: 'mt-1 text-lg font-semibold tracking-tight',
|
|
79
|
+
md: 'mt-2 text-2xl font-bold tracking-tight',
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
const valueColor = $derived(tone ? tones[tone] : accents[accent]);
|
|
@@ -84,7 +85,16 @@
|
|
|
84
85
|
const trendMark = { up: '↑', down: '↓', flat: '→' } as const;
|
|
85
86
|
</script>
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
<!-- A brand to violet line along the top edge, so a row of figures reads as a
|
|
89
|
+
set. It is a mark and carries nothing a reader needs, so where gradients
|
|
90
|
+
are not drawn it falls back to solid brand or to nothing at all. It is
|
|
91
|
+
mirrored under right-to-left, so cyan stays at the start edge.
|
|
92
|
+
overflow-hidden lets the card's corners trim it. -->
|
|
93
|
+
<div
|
|
94
|
+
class="{CARD_SURFACE} relative overflow-hidden before:absolute before:inset-x-0 before:top-0 before:h-0.5 before:bg-brand before:bg-accent rtl:before:-scale-x-100 {pads[
|
|
95
|
+
size
|
|
96
|
+
]} {klass}"
|
|
97
|
+
>
|
|
88
98
|
<p class="text-xs font-medium uppercase tracking-wide text-faint">{label}</p>
|
|
89
99
|
<p class="{values[size]} {valueColor} {mono ? 'font-mono tabular-nums' : ''}">{value}</p>
|
|
90
100
|
<div class="mt-1 flex items-center gap-2">
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
SCROLL_EDGE_START,
|
|
18
18
|
scrollEdges,
|
|
19
19
|
} from '../internal/scroll-edges.js';
|
|
20
|
+
import { CARD_SURFACE } from '../internal/layout.js';
|
|
20
21
|
|
|
21
22
|
interface Props {
|
|
22
23
|
striped?: boolean;
|
|
@@ -71,12 +72,13 @@
|
|
|
71
72
|
* start cell in either direction and the two agree in left-to-right text.
|
|
72
73
|
*/
|
|
73
74
|
const HOVER_RING = [
|
|
74
|
-
'[&_tbody_tr:hover]:bg-
|
|
75
|
+
'[&_tbody_tr:hover]:bg-tint',
|
|
75
76
|
'[&_tbody_tr:hover>td]:shadow-[inset_0_1px_var(--color-brand),inset_0_-1px_var(--color-brand)]',
|
|
76
77
|
'[&_tbody_tr:hover>td:first-child]:shadow-[inset_1px_1px_var(--color-brand),inset_1px_-1px_var(--color-brand)]',
|
|
77
78
|
'[&_tbody_tr:hover>td:last-child]:shadow-[inset_-1px_1px_var(--color-brand),inset_-1px_-1px_var(--color-brand)]',
|
|
78
79
|
'[&_tbody_tr:hover>td:first-child:last-child]:shadow-[inset_1px_1px_var(--color-brand),inset_-1px_-1px_var(--color-brand)]',
|
|
79
80
|
'[&_tbody_tr]:transition-colors',
|
|
81
|
+
'[&_tbody_tr>td]:transition-shadow',
|
|
80
82
|
].join(' ');
|
|
81
83
|
|
|
82
84
|
let {
|
|
@@ -239,10 +241,15 @@
|
|
|
239
241
|
|
|
240
242
|
<!-- overflow-hidden on the frame, not only on the scroller: the hovered row's
|
|
241
243
|
ring is square and the frame is not, and the last row's corners drew past
|
|
242
|
-
the frame's rounded ones.
|
|
244
|
+
the frame's rounded ones.
|
|
245
|
+
|
|
246
|
+
The frame is a card: the same surface, radius and shadows, so a table and
|
|
247
|
+
the cards above it sit at one depth. It paints its own surface rather than
|
|
248
|
+
showing the page through, which the shadow under it needs to read as a
|
|
249
|
+
raised sheet. -->
|
|
243
250
|
<div
|
|
244
251
|
data-testid="table-frame"
|
|
245
|
-
class="relative w-full overflow-hidden
|
|
252
|
+
class="relative w-full overflow-hidden {CARD_SURFACE} {cls}"
|
|
246
253
|
>
|
|
247
254
|
<!--
|
|
248
255
|
data-print: the box exists to clip. On paper there is no viewport to clip
|
|
@@ -255,7 +262,7 @@
|
|
|
255
262
|
onfocusin={reveal}
|
|
256
263
|
data-print="unclip"
|
|
257
264
|
data-testid="table-scroll"
|
|
258
|
-
class="w-full overflow-x-auto rounded-
|
|
265
|
+
class="w-full overflow-x-auto rounded-card"
|
|
259
266
|
{...scrollAttrs}
|
|
260
267
|
>
|
|
261
268
|
<!--
|
|
@@ -273,21 +280,25 @@
|
|
|
273
280
|
which is why the lines moved down to the cells they were already drawn
|
|
274
281
|
between. With border-spacing at zero the result is the same line in the
|
|
275
282
|
same place.
|
|
283
|
+
|
|
284
|
+
The head row and a hovered row take the brand tint. It is one opaque
|
|
285
|
+
color, so the contrast suite measures the head's labels and a row's text
|
|
286
|
+
on it, and a hovered row is told apart by more than its ring.
|
|
276
287
|
-->
|
|
277
288
|
<table
|
|
278
289
|
style="--cell-truncate: {truncateAt}"
|
|
279
290
|
class="w-full border-separate border-spacing-0 text-sm text-start
|
|
280
291
|
{fixed ? 'table-fixed' : ''}
|
|
281
292
|
{CELL_FIT[cell]} {CELL_NAMED} {COLUMN_ALIGN} {HEAD_CONTROLS}
|
|
282
|
-
[&_thead_th]:border-b [&_thead_th]:border-line [&_thead]:bg-
|
|
293
|
+
[&_thead_th]:border-b [&_thead_th]:border-line [&_thead]:bg-tint
|
|
283
294
|
[&_thead_th]:px-4 [&_thead_th]:py-3 [&_thead_th]:text-xs
|
|
284
295
|
[&_thead_th]:font-medium [&_thead_th]:text-faint
|
|
285
296
|
[&_thead_th]:uppercase [&_thead_th]:tracking-wider [&_thead_th]:whitespace-nowrap
|
|
286
297
|
[&_tbody_td]:px-4 [&_tbody_td]:py-3 [&_tbody_td]:text-fg [&_tbody_td]:align-middle
|
|
287
298
|
[&_tbody_td]:border-b [&_tbody_td]:border-line [&_tbody_tr:last-child_td]:border-0
|
|
288
299
|
{striped ? '[&_tbody_tr:nth-child(even)]:bg-surface-2/40' : ''}
|
|
289
|
-
[&_tbody_tr:last-child>td:first-child]:rounded-es-[calc(var(--radius-
|
|
290
|
-
[&_tbody_tr:last-child>td:last-child]:rounded-ee-[calc(var(--radius-
|
|
300
|
+
[&_tbody_tr:last-child>td:first-child]:rounded-es-[calc(var(--radius-card)-1px)]
|
|
301
|
+
[&_tbody_tr:last-child>td:last-child]:rounded-ee-[calc(var(--radius-card)-1px)]
|
|
291
302
|
{hoverable ? HOVER_RING : ''}"
|
|
292
303
|
>
|
|
293
304
|
{@render children()}
|
|
@@ -305,7 +316,7 @@
|
|
|
305
316
|
aria-hidden="true"
|
|
306
317
|
data-print="hide"
|
|
307
318
|
data-testid="table-more-before"
|
|
308
|
-
class="{SCROLL_EDGE} {SCROLL_EDGE_START} rounded-s-
|
|
319
|
+
class="{SCROLL_EDGE} {SCROLL_EDGE_START} rounded-s-card"
|
|
309
320
|
></div>
|
|
310
321
|
{/if}
|
|
311
322
|
{#if moreAfter}
|
|
@@ -313,7 +324,7 @@
|
|
|
313
324
|
aria-hidden="true"
|
|
314
325
|
data-print="hide"
|
|
315
326
|
data-testid="table-more-after"
|
|
316
|
-
class="{SCROLL_EDGE} {SCROLL_EDGE_END} rounded-e-
|
|
327
|
+
class="{SCROLL_EDGE} {SCROLL_EDGE_END} rounded-e-card"
|
|
317
328
|
></div>
|
|
318
329
|
{/if}
|
|
319
330
|
</div>
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
*
|
|
30
30
|
* The scrollbar is not drawn. A strip of tabs with a bar under it reads as
|
|
31
31
|
* a second rule under the real one. The fades carry the same message, the
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* arrow keys reach every tab, and the active tab is scrolled into view when
|
|
33
|
+
* it changes.
|
|
34
34
|
*/
|
|
35
35
|
let scroller: HTMLElement | undefined = $state();
|
|
36
36
|
let moreBefore = $state(false);
|
|
@@ -62,20 +62,81 @@
|
|
|
62
62
|
* page down to it as soon as it mounted. A test DOM lays nothing out, so
|
|
63
63
|
* every box is empty and nothing moves.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
function reveal(tab: HTMLElement) {
|
|
66
66
|
const el = scroller;
|
|
67
|
-
const id = active;
|
|
68
67
|
if (!el) return;
|
|
69
|
-
const tab = [...el.querySelectorAll<HTMLElement>('[role="tab"]')].find(
|
|
70
|
-
(t) => t.dataset.tab === id,
|
|
71
|
-
);
|
|
72
|
-
if (!tab) return;
|
|
73
68
|
const box = el.getBoundingClientRect();
|
|
74
69
|
const at = tab.getBoundingClientRect();
|
|
75
70
|
if (at.left < box.left) el.scrollLeft -= box.left - at.left;
|
|
76
71
|
else if (at.right > box.right) el.scrollLeft += at.right - box.right;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function tabAt(index: number): HTMLElement | undefined {
|
|
75
|
+
return scroller?.querySelectorAll<HTMLElement>('[role="tab"]')[index];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const activeIndex = $derived(items.findIndex((t) => t.id === active));
|
|
79
|
+
|
|
80
|
+
$effect(() => {
|
|
81
|
+
const tab = activeIndex === -1 ? undefined : tabAt(activeIndex);
|
|
82
|
+
if (tab) reveal(tab);
|
|
77
83
|
});
|
|
78
84
|
|
|
85
|
+
/*
|
|
86
|
+
* The strip is one stop in the tab order. A strip of five tabs cost five
|
|
87
|
+
* presses of Tab to step over, and the arrows did nothing.
|
|
88
|
+
*
|
|
89
|
+
* Activation is manual: Left, Right, Home and End move focus and the stop
|
|
90
|
+
* with it, and Enter, Space or a click chooses. A caller may navigate to
|
|
91
|
+
* another page when a tab is chosen, and a strip that chose on every arrow
|
|
92
|
+
* would then load a page per key press and take the reader off the strip.
|
|
93
|
+
*
|
|
94
|
+
* While focus is inside the strip the stop follows it, by key or by
|
|
95
|
+
* pointer. When focus leaves, the stop goes back to the selected tab. A
|
|
96
|
+
* selection matching no item would leave every tab at -1 and drop the
|
|
97
|
+
* strip out of the tab order, so the first tab holds it then.
|
|
98
|
+
*/
|
|
99
|
+
let roving: number | null = $state(null);
|
|
100
|
+
const tabStop = $derived(roving ?? (activeIndex === -1 ? 0 : activeIndex));
|
|
101
|
+
|
|
102
|
+
function moveTo(index: number) {
|
|
103
|
+
const tab = tabAt(index);
|
|
104
|
+
if (!tab) return;
|
|
105
|
+
// The strip is in view when a key reaches it, and focus() would scroll
|
|
106
|
+
// every box above the tab, the page among them.
|
|
107
|
+
tab.focus({ preventScroll: true });
|
|
108
|
+
reveal(tab);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function onTabKeydown(e: KeyboardEvent, index: number) {
|
|
112
|
+
const last = items.length - 1;
|
|
113
|
+
let next: number;
|
|
114
|
+
switch (e.key) {
|
|
115
|
+
case 'ArrowRight':
|
|
116
|
+
next = index === last ? 0 : index + 1;
|
|
117
|
+
break;
|
|
118
|
+
case 'ArrowLeft':
|
|
119
|
+
next = index === 0 ? last : index - 1;
|
|
120
|
+
break;
|
|
121
|
+
case 'Home':
|
|
122
|
+
next = 0;
|
|
123
|
+
break;
|
|
124
|
+
case 'End':
|
|
125
|
+
next = last;
|
|
126
|
+
break;
|
|
127
|
+
default:
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// The arrows scroll the strip and the page, and Home and End jump the page.
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
moveTo(next);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function onStripFocusOut(e: FocusEvent) {
|
|
136
|
+
if (scroller && e.relatedTarget instanceof Node && scroller.contains(e.relatedTarget)) return;
|
|
137
|
+
roving = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
/*
|
|
80
141
|
* The focus ring is inset: the tabs sit in a box that clips to scroll, and
|
|
81
142
|
* the global outline sits 2px outside its element, so on the strip's top
|
|
@@ -92,20 +153,26 @@
|
|
|
92
153
|
<div
|
|
93
154
|
bind:this={scroller}
|
|
94
155
|
onscroll={measure}
|
|
156
|
+
onfocusout={onStripFocusOut}
|
|
95
157
|
role="tablist"
|
|
96
158
|
data-testid="tabs-scroll"
|
|
97
159
|
class="flex gap-0.5 overflow-x-auto border-b border-line [scrollbar-width:none]"
|
|
98
160
|
>
|
|
99
|
-
|
|
161
|
+
<!-- The active indicator is the cyan to violet accent, with solid brand
|
|
162
|
+
under it for wherever gradients are not drawn. -->
|
|
163
|
+
{#each items as tab, i (tab.id)}
|
|
100
164
|
<button
|
|
101
165
|
type="button"
|
|
102
166
|
role="tab"
|
|
103
167
|
data-tab={tab.id}
|
|
104
168
|
aria-selected={active === tab.id}
|
|
169
|
+
tabindex={i === tabStop ? 0 : -1}
|
|
105
170
|
onclick={() => onchange(tab.id)}
|
|
171
|
+
onfocus={() => (roving = i)}
|
|
172
|
+
onkeydown={(e) => onTabKeydown(e, i)}
|
|
106
173
|
class="{TAB}
|
|
107
174
|
{active === tab.id
|
|
108
|
-
? 'text-fg after:absolute after:bottom-0 after:inset-x-0 after:h-0.5 after:bg-brand after:rounded-t'
|
|
175
|
+
? 'text-fg after:absolute after:bottom-0 after:inset-x-0 after:h-0.5 after:bg-brand after:bg-accent after:rounded-t rtl:after:-scale-x-100'
|
|
109
176
|
: 'text-faint hover:text-fg'}"
|
|
110
177
|
>
|
|
111
178
|
{tab.label}
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
transition:motion.toast|global
|
|
31
31
|
animate:motion.reorder
|
|
32
32
|
class="pointer-events-auto flex items-start gap-3 overflow-hidden rounded-lg border border-line
|
|
33
|
-
bg-surface ps-0 pe-3 py-3 shadow-
|
|
33
|
+
bg-surface ps-0 pe-3 py-3 shadow-popover inset-shadow-highlight"
|
|
34
34
|
>
|
|
35
35
|
<span class="w-1 self-stretch shrink-0 {styles[statusTone(t.tone)].bar}"></span>
|
|
36
36
|
<span
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
aria-hidden={describe ? undefined : 'true'}
|
|
139
139
|
style="top: {anchor.top}px; left: {anchor.left}px"
|
|
140
140
|
class="fixed {pos[position]} z-tooltip w-max max-w-[min(16rem,calc(100vw-2rem))]
|
|
141
|
-
rounded-lg border border-line bg-surface-2 px-2.5 py-1.5 text-xs text-fg shadow-
|
|
141
|
+
rounded-lg border border-line bg-surface-2 px-2.5 py-1.5 text-xs text-fg shadow-popover inset-shadow-highlight
|
|
142
142
|
transition {visible
|
|
143
143
|
? 'duration-base ease-enter'
|
|
144
144
|
: 'invisible scale-[0.96] opacity-0 duration-fast ease-exit'}"
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
use:overlay
|
|
111
111
|
transition:motion.dialog|global
|
|
112
112
|
class="relative w-full {sizeClass(entry.options.size ?? 'md')} mx-4
|
|
113
|
-
bg-surface border border-line rounded-
|
|
113
|
+
bg-surface bg-card border border-line rounded-card shadow-overlay inset-shadow-highlight
|
|
114
114
|
transition-transform duration-slow
|
|
115
115
|
focus:outline-none focus-visible:ring-2 focus-visible:ring-brand"
|
|
116
116
|
style="transform: scale({1 - offset * 0.03}) translateY({offset * 16}px);
|
package/dist/index.d.ts
CHANGED
|
@@ -113,4 +113,4 @@ export { cn, type ClassValue } from './utils/cn.js';
|
|
|
113
113
|
export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, watchThemePreference, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
|
|
114
114
|
export * as motion from './motion.js';
|
|
115
115
|
export type { Rung as MotionRung, Curve as MotionCurve } from './motion.js';
|
|
116
|
-
export declare const VERSION = "0.
|
|
116
|
+
export declare const VERSION = "0.46.0";
|
package/dist/index.js
CHANGED
|
@@ -122,4 +122,4 @@ export * as motion from './motion.js';
|
|
|
122
122
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
123
123
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
124
124
|
// this line. The build and the test suite fail when the two disagree.
|
|
125
|
-
export const VERSION = '0.
|
|
125
|
+
export const VERSION = '0.46.0';
|
package/dist/internal/field.js
CHANGED
|
@@ -40,6 +40,15 @@ export const FIELD_LABEL = 'text-sm font-medium text-fg';
|
|
|
40
40
|
export const FIELD_HINT = 'text-xs text-faint';
|
|
41
41
|
/** Error text below a control. Replaces the hint rather than stacking with it. */
|
|
42
42
|
export const FIELD_ERROR = 'text-xs text-danger';
|
|
43
|
+
/**
|
|
44
|
+
* The depth every field shares: an inner shadow along the top edge that sinks
|
|
45
|
+
* it into the surface, and the cyan glow around the focus ring. Both are
|
|
46
|
+
* shadows, so the field keeps its height and its border, and the ring stays
|
|
47
|
+
* the indicator that clears 3:1. The glow appears with the ring rather than
|
|
48
|
+
* easing in, because a focus indicator that fades is late for the key press
|
|
49
|
+
* that moved it.
|
|
50
|
+
*/
|
|
51
|
+
const CONTROL_DEPTH = 'inset-shadow-well focus-visible:shadow-glow';
|
|
43
52
|
/**
|
|
44
53
|
* Everything a single-line control needs except its border color.
|
|
45
54
|
*
|
|
@@ -58,11 +67,13 @@ export const FIELD_ERROR = 'text-xs text-danger';
|
|
|
58
67
|
*/
|
|
59
68
|
export const CONTROL_BASE = 'w-full h-control rounded-lg bg-surface-2 border px-3 text-sm text-fg ' +
|
|
60
69
|
'placeholder:text-faint transition-colors outline-none ' +
|
|
70
|
+
`${CONTROL_DEPTH} ` +
|
|
61
71
|
'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand ' +
|
|
62
72
|
'disabled:opacity-50 disabled:cursor-not-allowed';
|
|
63
73
|
/** As CONTROL_BASE, for controls whose height is driven by their content. */
|
|
64
74
|
export const CONTROL_MULTILINE = 'w-full min-h-control rounded-lg bg-surface-2 border px-3 py-2 text-sm text-fg ' +
|
|
65
75
|
'placeholder:text-faint transition-colors outline-none ' +
|
|
76
|
+
`${CONTROL_DEPTH} ` +
|
|
66
77
|
'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand ' +
|
|
67
78
|
'disabled:opacity-50 disabled:cursor-not-allowed';
|
|
68
79
|
/**
|
|
@@ -111,7 +122,7 @@ export function describedBy(id, error, hint) {
|
|
|
111
122
|
* held the caret.
|
|
112
123
|
*/
|
|
113
124
|
export const CONTROL_SEGMENTED = 'inline-flex h-control items-center gap-0.5 rounded-lg bg-surface-2 border px-3 ' +
|
|
114
|
-
'text-sm text-fg transition-colors';
|
|
125
|
+
'text-sm text-fg transition-colors inset-shadow-well has-[:focus-visible]:shadow-glow';
|
|
115
126
|
/** As controlBorder, for a wrapper that is only ever focused through a child. */
|
|
116
127
|
export function segmentedBorder(error) {
|
|
117
128
|
return error
|
|
@@ -91,8 +91,15 @@ export declare const PAGE_STACK = "flex flex-col gap-section";
|
|
|
91
91
|
* children flush to the border. `overflow-hidden` is deliberately absent: the
|
|
92
92
|
* focus ring sits 2px outside the element it belongs to, so a clipping surface
|
|
93
93
|
* crops the ring of every button inside it down to whichever edge fits.
|
|
94
|
+
*
|
|
95
|
+
* The depth is a few percent of light at the top, a 1px light along the top
|
|
96
|
+
* edge and a soft drop under the card. The light is a translucent background
|
|
97
|
+
* image laid over the background color, so a caller who passes its own bg-*
|
|
98
|
+
* keeps that color under it. The rest are shadows, so the card is the same
|
|
99
|
+
* size with or without them and the 1px border stays the boundary a forced
|
|
100
|
+
* colors scheme keeps.
|
|
94
101
|
*/
|
|
95
|
-
export declare const CARD_SURFACE = "bg-surface border border-line rounded-
|
|
102
|
+
export declare const CARD_SURFACE = "bg-surface bg-card border border-line rounded-card shadow-card inset-shadow-highlight";
|
|
96
103
|
/**
|
|
97
104
|
* The card surface in the danger tone, for a group of destructive actions.
|
|
98
105
|
*
|
|
@@ -101,7 +108,7 @@ export declare const CARD_SURFACE = "bg-surface border border-line rounded-xl";
|
|
|
101
108
|
* percent keeps the controls of a form inside it on a ground they were drawn
|
|
102
109
|
* for. The contrast suite reads the share from this string.
|
|
103
110
|
*/
|
|
104
|
-
export declare const PANEL_DANGER_SURFACE = "bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-
|
|
111
|
+
export declare const PANEL_DANGER_SURFACE = "bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-card shadow-card inset-shadow-highlight";
|
|
105
112
|
/**
|
|
106
113
|
* Card padding by name.
|
|
107
114
|
*
|
|
@@ -181,8 +188,20 @@ export declare function sectionHeading(level: 2 | 3, variant?: SectionVariant):
|
|
|
181
188
|
*
|
|
182
189
|
* `border-e` and not `border-r`: the shell is the one place a right-to-left
|
|
183
190
|
* locale flips, and a physical border leaves the rule on the wrong edge.
|
|
191
|
+
*
|
|
192
|
+
* The sidebar runs from the surface at the top a step toward ink at the foot,
|
|
193
|
+
* and its edge is a rule that fades in and out at its ends, drawn over a
|
|
194
|
+
* transparent border. The border keeps the column's width, and a forced
|
|
195
|
+
* colors scheme, which draws no gradient, paints that border in the system
|
|
196
|
+
* text color, so the edge is still there.
|
|
184
197
|
*/
|
|
185
|
-
export declare const APP_SIDEBAR
|
|
198
|
+
export declare const APP_SIDEBAR: string;
|
|
199
|
+
/**
|
|
200
|
+
* The faded edge of the sidebar, a 1px line laid over its transparent
|
|
201
|
+
* border. It sets a width of its own, so it stays out of APP_SIDEBAR, which
|
|
202
|
+
* holds no width at all.
|
|
203
|
+
*/
|
|
204
|
+
export declare const APP_SIDEBAR_EDGE = "after:pointer-events-none after:absolute after:inset-y-0 after:-end-px after:w-px after:bg-rule-y";
|
|
186
205
|
/**
|
|
187
206
|
* The sidebar's two widths. Expanded is the 224px column every authed screen
|
|
188
207
|
* had. The rail is the 56px icon column the theme had named and nothing used:
|
|
@@ -201,8 +220,14 @@ export declare const APP_SIDEBAR_RAIL = "w-nav-rail";
|
|
|
201
220
|
/**
|
|
202
221
|
* The header bar. Its gutter is the page gutter, so the section name in the
|
|
203
222
|
* header sits directly above the page title under it rather than 8px off.
|
|
223
|
+
*
|
|
224
|
+
* The surface is translucent, so the light at the top of the canvas carries
|
|
225
|
+
* up under it and the header reads as part of the page rather than a lid on
|
|
226
|
+
* it. Its rule fades out toward the end edge and stays solid where it meets
|
|
227
|
+
* the brand row's, so the two read as one line across the top. The rule sits
|
|
228
|
+
* over a transparent border, for the same reason as the sidebar's.
|
|
204
229
|
*/
|
|
205
|
-
export declare const APP_HEADER
|
|
230
|
+
export declare const APP_HEADER: string;
|
|
206
231
|
/**
|
|
207
232
|
* The sidebar's brand row. Exactly as tall as the header beside it: the two
|
|
208
233
|
* meet at the top left corner and a 4px disagreement there reads as a seam.
|
package/dist/internal/layout.js
CHANGED
|
@@ -102,8 +102,15 @@ export const PAGE_STACK = 'flex flex-col gap-section';
|
|
|
102
102
|
* children flush to the border. `overflow-hidden` is deliberately absent: the
|
|
103
103
|
* focus ring sits 2px outside the element it belongs to, so a clipping surface
|
|
104
104
|
* crops the ring of every button inside it down to whichever edge fits.
|
|
105
|
+
*
|
|
106
|
+
* The depth is a few percent of light at the top, a 1px light along the top
|
|
107
|
+
* edge and a soft drop under the card. The light is a translucent background
|
|
108
|
+
* image laid over the background color, so a caller who passes its own bg-*
|
|
109
|
+
* keeps that color under it. The rest are shadows, so the card is the same
|
|
110
|
+
* size with or without them and the 1px border stays the boundary a forced
|
|
111
|
+
* colors scheme keeps.
|
|
105
112
|
*/
|
|
106
|
-
export const CARD_SURFACE = 'bg-surface border border-line rounded-
|
|
113
|
+
export const CARD_SURFACE = 'bg-surface bg-card border border-line rounded-card shadow-card inset-shadow-highlight';
|
|
107
114
|
/**
|
|
108
115
|
* The card surface in the danger tone, for a group of destructive actions.
|
|
109
116
|
*
|
|
@@ -112,7 +119,7 @@ export const CARD_SURFACE = 'bg-surface border border-line rounded-xl';
|
|
|
112
119
|
* percent keeps the controls of a form inside it on a ground they were drawn
|
|
113
120
|
* for. The contrast suite reads the share from this string.
|
|
114
121
|
*/
|
|
115
|
-
export const PANEL_DANGER_SURFACE = 'bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-
|
|
122
|
+
export const PANEL_DANGER_SURFACE = 'bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-card shadow-card inset-shadow-highlight';
|
|
116
123
|
/**
|
|
117
124
|
* Card padding by name.
|
|
118
125
|
*
|
|
@@ -188,8 +195,12 @@ export const MODAL_PAD = 'px-card py-card-sm';
|
|
|
188
195
|
export function sectionHeading(level, variant = 'default') {
|
|
189
196
|
// The eyebrow reads the same at both levels. It is a label for the band under
|
|
190
197
|
// it rather than a title, so it does not take the level's size at all.
|
|
198
|
+
//
|
|
199
|
+
// It is muted and semibold rather than faint and medium. A band label in the
|
|
200
|
+
// faintest gray read as metadata beside the stronger page title, so it
|
|
201
|
+
// stepped up once in weight and once in color.
|
|
191
202
|
if (variant === 'eyebrow')
|
|
192
|
-
return 'text-xs font-
|
|
203
|
+
return 'text-xs font-semibold uppercase tracking-wider text-muted';
|
|
193
204
|
// Level 2 is the brand ramp's H3, which the guideline describes as the card
|
|
194
205
|
// and group head this is. Its leading and tracking come with it: a --text-*
|
|
195
206
|
// token sets font-size and nothing else, so the ratio has to be asked for by
|
|
@@ -215,8 +226,21 @@ export function sectionHeading(level, variant = 'default') {
|
|
|
215
226
|
*
|
|
216
227
|
* `border-e` and not `border-r`: the shell is the one place a right-to-left
|
|
217
228
|
* locale flips, and a physical border leaves the rule on the wrong edge.
|
|
229
|
+
*
|
|
230
|
+
* The sidebar runs from the surface at the top a step toward ink at the foot,
|
|
231
|
+
* and its edge is a rule that fades in and out at its ends, drawn over a
|
|
232
|
+
* transparent border. The border keeps the column's width, and a forced
|
|
233
|
+
* colors scheme, which draws no gradient, paints that border in the system
|
|
234
|
+
* text color, so the edge is still there.
|
|
218
235
|
*/
|
|
219
|
-
export const APP_SIDEBAR = 'h-full shrink-0 flex-col border-e border-
|
|
236
|
+
export const APP_SIDEBAR = 'relative h-full shrink-0 flex-col border-e border-transparent bg-surface bg-sidebar ' +
|
|
237
|
+
'transition-[width] duration-base ease-move';
|
|
238
|
+
/**
|
|
239
|
+
* The faded edge of the sidebar, a 1px line laid over its transparent
|
|
240
|
+
* border. It sets a width of its own, so it stays out of APP_SIDEBAR, which
|
|
241
|
+
* holds no width at all.
|
|
242
|
+
*/
|
|
243
|
+
export const APP_SIDEBAR_EDGE = 'after:pointer-events-none after:absolute after:inset-y-0 after:-end-px after:w-px after:bg-rule-y';
|
|
220
244
|
/**
|
|
221
245
|
* The sidebar's two widths. Expanded is the 224px column every authed screen
|
|
222
246
|
* had. The rail is the 56px icon column the theme had named and nothing used:
|
|
@@ -235,8 +259,16 @@ export const APP_SIDEBAR_RAIL = 'w-nav-rail';
|
|
|
235
259
|
/**
|
|
236
260
|
* The header bar. Its gutter is the page gutter, so the section name in the
|
|
237
261
|
* header sits directly above the page title under it rather than 8px off.
|
|
262
|
+
*
|
|
263
|
+
* The surface is translucent, so the light at the top of the canvas carries
|
|
264
|
+
* up under it and the header reads as part of the page rather than a lid on
|
|
265
|
+
* it. Its rule fades out toward the end edge and stays solid where it meets
|
|
266
|
+
* the brand row's, so the two read as one line across the top. The rule sits
|
|
267
|
+
* over a transparent border, for the same reason as the sidebar's.
|
|
238
268
|
*/
|
|
239
|
-
export const APP_HEADER = 'flex h-header shrink-0 items-center gap-2 border-b border-
|
|
269
|
+
export const APP_HEADER = 'relative flex h-header shrink-0 items-center gap-2 border-b border-transparent bg-surface/70 px-page-x ' +
|
|
270
|
+
'after:pointer-events-none after:absolute after:inset-x-0 after:-bottom-px after:h-px after:bg-rule-end ' +
|
|
271
|
+
'rtl:after:-scale-x-100';
|
|
240
272
|
/**
|
|
241
273
|
* The sidebar's brand row. Exactly as tall as the header beside it: the two
|
|
242
274
|
* meet at the top left corner and a 4px disagreement there reads as a seam.
|
package/dist/internal/panel.d.ts
CHANGED
|
@@ -52,8 +52,11 @@
|
|
|
52
52
|
*
|
|
53
53
|
* No vertical offset and no side. Both belong to placePanel, which measures
|
|
54
54
|
* the room and puts the panel above the anchor when there is none below.
|
|
55
|
+
*
|
|
56
|
+
* shadow-popover is the rung above a card, so a menu opened from a card reads
|
|
57
|
+
* as lifted off it rather than as part of it.
|
|
55
58
|
*/
|
|
56
|
-
export declare const PANEL_SURFACE = "absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-
|
|
59
|
+
export declare const PANEL_SURFACE = "absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-popover inset-shadow-highlight";
|
|
57
60
|
/**
|
|
58
61
|
* The scrolling region inside it.
|
|
59
62
|
*
|
package/dist/internal/panel.js
CHANGED
|
@@ -53,8 +53,11 @@ import { TOUCH_GROW } from './touch.js';
|
|
|
53
53
|
*
|
|
54
54
|
* No vertical offset and no side. Both belong to placePanel, which measures
|
|
55
55
|
* the room and puts the panel above the anchor when there is none below.
|
|
56
|
+
*
|
|
57
|
+
* shadow-popover is the rung above a card, so a menu opened from a card reads
|
|
58
|
+
* as lifted off it rather than as part of it.
|
|
56
59
|
*/
|
|
57
|
-
export const PANEL_SURFACE = 'absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-
|
|
60
|
+
export const PANEL_SURFACE = 'absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-popover inset-shadow-highlight';
|
|
58
61
|
/**
|
|
59
62
|
* The scrolling region inside it.
|
|
60
63
|
*
|
|
@@ -20,14 +20,15 @@ export interface ScrollMetrics {
|
|
|
20
20
|
}
|
|
21
21
|
export declare function scrollEdges(el: ScrollMetrics): ScrollEdges;
|
|
22
22
|
/**
|
|
23
|
-
* The fade itself: a
|
|
24
|
-
* does not scroll away with the content it describes.
|
|
25
|
-
*
|
|
26
|
-
* a
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* The fade itself: a wash of the foreground color on the edge, outside the
|
|
24
|
+
* scroll box so it does not scroll away with the content it describes. The
|
|
25
|
+
* foreground token inverts with the theme, so the fade darkens a light card
|
|
26
|
+
* and lightens a dark one. A black fade had nothing to darken on a dark card
|
|
27
|
+
* and read as no fade at all. No surface token is the color to fade to,
|
|
28
|
+
* because the box sits on the page in one place and inside a card in
|
|
29
|
+
* another. The direction is logical, so a right-to-left page fades the other
|
|
29
30
|
* way.
|
|
30
31
|
*/
|
|
31
32
|
export declare const SCROLL_EDGE = "pointer-events-none absolute inset-y-px w-6";
|
|
32
|
-
export declare const SCROLL_EDGE_START = "start-0 bg-linear-to-r rtl:bg-linear-to-l from-
|
|
33
|
-
export declare const SCROLL_EDGE_END = "end-0 bg-linear-to-l rtl:bg-linear-to-r from-
|
|
33
|
+
export declare const SCROLL_EDGE_START = "start-0 bg-linear-to-r rtl:bg-linear-to-l from-fg/25 to-transparent";
|
|
34
|
+
export declare const SCROLL_EDGE_END = "end-0 bg-linear-to-l rtl:bg-linear-to-r from-fg/25 to-transparent";
|
|
@@ -20,14 +20,15 @@ export function scrollEdges(el) {
|
|
|
20
20
|
return { before: traveled > 1, after: traveled < slack - 1 };
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* The fade itself: a
|
|
24
|
-
* does not scroll away with the content it describes.
|
|
25
|
-
*
|
|
26
|
-
* a
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* The fade itself: a wash of the foreground color on the edge, outside the
|
|
24
|
+
* scroll box so it does not scroll away with the content it describes. The
|
|
25
|
+
* foreground token inverts with the theme, so the fade darkens a light card
|
|
26
|
+
* and lightens a dark one. A black fade had nothing to darken on a dark card
|
|
27
|
+
* and read as no fade at all. No surface token is the color to fade to,
|
|
28
|
+
* because the box sits on the page in one place and inside a card in
|
|
29
|
+
* another. The direction is logical, so a right-to-left page fades the other
|
|
29
30
|
* way.
|
|
30
31
|
*/
|
|
31
32
|
export const SCROLL_EDGE = 'pointer-events-none absolute inset-y-px w-6';
|
|
32
|
-
export const SCROLL_EDGE_START = 'start-0 bg-linear-to-r rtl:bg-linear-to-l from-
|
|
33
|
-
export const SCROLL_EDGE_END = 'end-0 bg-linear-to-l rtl:bg-linear-to-r from-
|
|
33
|
+
export const SCROLL_EDGE_START = 'start-0 bg-linear-to-r rtl:bg-linear-to-l from-fg/25 to-transparent';
|
|
34
|
+
export const SCROLL_EDGE_END = 'end-0 bg-linear-to-l rtl:bg-linear-to-r from-fg/25 to-transparent';
|