@lyeve-labs/ui-kit 0.45.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 +1 -1
- package/dist/components/AppShell.svelte +9 -5
- 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/PluginCart.svelte +21 -7
- 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 +19 -9
- package/dist/components/Tabs.svelte +3 -1
- 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/styles/theme.css +203 -0
- package/package.json +1 -1
- package/src/lib/styles/theme.css +203 -0
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
rule are the card treatment, because lifted off the header it is a
|
|
121
121
|
surface of its own. -->
|
|
122
122
|
<div
|
|
123
|
-
class="absolute end-0 top-full z-dropdown mt-2 flex w-64 origin-top-right flex-col gap-1 rounded-xl border border-line bg-surface p-2 shadow-
|
|
123
|
+
class="absolute end-0 top-full z-dropdown mt-2 flex w-64 origin-top-right flex-col gap-1 rounded-xl border border-line bg-surface p-2 shadow-popover inset-shadow-highlight animate-popover-in"
|
|
124
124
|
>
|
|
125
125
|
<div class="border-b border-line px-2 pb-2">
|
|
126
126
|
<p class="truncate text-sm font-medium text-fg">{name}</p>
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
APP_HEADER,
|
|
52
52
|
APP_SIDEBAR,
|
|
53
53
|
APP_SIDEBAR_BAND,
|
|
54
|
+
APP_SIDEBAR_EDGE,
|
|
54
55
|
APP_SIDEBAR_RAIL,
|
|
55
56
|
APP_SIDEBAR_WIDE,
|
|
56
57
|
} from '../internal/layout.js';
|
|
@@ -277,7 +278,7 @@
|
|
|
277
278
|
<div
|
|
278
279
|
data-print="hide"
|
|
279
280
|
transition:motion.drawer|global={{ side: startSide() }}
|
|
280
|
-
class="fixed inset-y-0 start-0 z-drawer flex shadow-
|
|
281
|
+
class="fixed inset-y-0 start-0 z-drawer flex shadow-overlay"
|
|
281
282
|
role="dialog"
|
|
282
283
|
aria-modal="true"
|
|
283
284
|
aria-label={drawerLabel}
|
|
@@ -304,7 +305,7 @@
|
|
|
304
305
|
>
|
|
305
306
|
<div
|
|
306
307
|
class="absolute inset-y-0 start-0 z-dropdown flex transition-shadow {railOpen
|
|
307
|
-
? 'shadow-
|
|
308
|
+
? 'shadow-popover'
|
|
308
309
|
: ''}"
|
|
309
310
|
>
|
|
310
311
|
{@render sidebar(isMobile, railIcons)}
|
|
@@ -316,7 +317,10 @@
|
|
|
316
317
|
</div>
|
|
317
318
|
{/if}
|
|
318
319
|
|
|
319
|
-
|
|
320
|
+
<!-- The column carries the ink and the light pooled at its top, so the
|
|
321
|
+
translucent header and the page under it share one canvas. The main
|
|
322
|
+
region paints nothing of its own for that reason. -->
|
|
323
|
+
<div class="flex min-w-0 flex-1 flex-col bg-ink bg-canvas">
|
|
320
324
|
{#if !barHidden}
|
|
321
325
|
<header data-print="hide" class={APP_HEADER}>
|
|
322
326
|
<div class="flex min-w-0 flex-1 items-center gap-2">
|
|
@@ -376,7 +380,7 @@
|
|
|
376
380
|
id="content"
|
|
377
381
|
tabindex="-1"
|
|
378
382
|
data-print="unclip"
|
|
379
|
-
class="relative min-w-0 flex-1 overflow-auto
|
|
383
|
+
class="relative min-w-0 flex-1 overflow-auto outline-none"
|
|
380
384
|
>
|
|
381
385
|
{@render children()}
|
|
382
386
|
</main>
|
|
@@ -391,7 +395,7 @@
|
|
|
391
395
|
aria-label={sidebarLabel}
|
|
392
396
|
data-print="hide"
|
|
393
397
|
data-testid="app-sidebar"
|
|
394
|
-
class="{APP_SIDEBAR} {rail ? APP_SIDEBAR_RAIL : APP_SIDEBAR_WIDE} flex"
|
|
398
|
+
class="{APP_SIDEBAR} {APP_SIDEBAR_EDGE} {rail ? APP_SIDEBAR_RAIL : APP_SIDEBAR_WIDE} flex"
|
|
395
399
|
inert={hidden}
|
|
396
400
|
aria-hidden={hidden ? 'true' : undefined}
|
|
397
401
|
>
|
|
@@ -59,8 +59,15 @@
|
|
|
59
59
|
lockup, heading and card, and no two agreed: three heading sizes, a lockup
|
|
60
60
|
on some pages, a theme control on others, and one class that named no
|
|
61
61
|
token at all. Stated once here, the way AppShell states the signed-in frame.
|
|
62
|
+
|
|
63
|
+
The ground is the hero wash, ink into a tenth of violet, because this page
|
|
64
|
+
is the first one a reader sees. bg-ink stays under it for wherever the wash
|
|
65
|
+
is not drawn.
|
|
62
66
|
-->
|
|
63
|
-
<main
|
|
67
|
+
<main
|
|
68
|
+
class={cn('flex min-h-screen items-center justify-center bg-ink bg-hero px-4 py-10', klass)}
|
|
69
|
+
{style}
|
|
70
|
+
>
|
|
64
71
|
<div class="w-full {widths[width]}">
|
|
65
72
|
{#if actions}
|
|
66
73
|
<div class="mb-4 flex justify-end">{@render actions()}</div>
|
|
@@ -86,7 +93,7 @@
|
|
|
86
93
|
{:else}
|
|
87
94
|
<Logo size="lg" class="mb-5" />
|
|
88
95
|
{/if}
|
|
89
|
-
<h1 class="text-2xl font-
|
|
96
|
+
<h1 class="text-2xl font-semibold tracking-h2 text-fg">{title}</h1>
|
|
90
97
|
{#if description}
|
|
91
98
|
<p class="mt-2 text-muted">{description}</p>
|
|
92
99
|
{/if}
|
|
@@ -23,13 +23,14 @@
|
|
|
23
23
|
danger: 'bg-danger/10 text-danger border-danger/20',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
/** The dot carries a soft halo of its own tone. A ring is a shadow, so the dot and the label beside it do not move. */
|
|
26
27
|
const dotColor: Record<AccentTone, string> = {
|
|
27
|
-
neutral: 'bg-muted',
|
|
28
|
-
brand: 'bg-brand',
|
|
29
|
-
violet: 'bg-violet',
|
|
30
|
-
success: 'bg-success',
|
|
31
|
-
warn: 'bg-warn',
|
|
32
|
-
danger: 'bg-danger',
|
|
28
|
+
neutral: 'bg-muted ring-2 ring-muted/20',
|
|
29
|
+
brand: 'bg-brand ring-2 ring-brand/25',
|
|
30
|
+
violet: 'bg-violet ring-2 ring-violet/25',
|
|
31
|
+
success: 'bg-success ring-2 ring-success/25',
|
|
32
|
+
warn: 'bg-warn ring-2 ring-warn/25',
|
|
33
|
+
danger: 'bg-danger ring-2 ring-danger/25',
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
const sizes: Record<Size, string> = {
|
|
@@ -82,15 +82,38 @@
|
|
|
82
82
|
* below brand-light on the dark ramp and below it again on the light one, and
|
|
83
83
|
* the filtered variants darken rather than brighten, so a press does not read
|
|
84
84
|
* as one gesture in one theme and its opposite in the other.
|
|
85
|
+
*
|
|
86
|
+
* The filled and secondary variants are raised: a bright edge along the top
|
|
87
|
+
* and a short drop under them. The primary action alone glows, softly at
|
|
88
|
+
* rest and more under the pointer or held, so the one thing a surface wants
|
|
89
|
+
* done is the one thing that answers with light. Its fill is lifted, white
|
|
90
|
+
* falling off from the top over the brand color, and the hover and held
|
|
91
|
+
* colors change under the lift. The glow is a shadow, so it never moves the
|
|
92
|
+
* button or its neighbors, and the color change carries the state on its
|
|
93
|
+
* own where shadows and gradients are not drawn. A disabled primary drops
|
|
94
|
+
* its glow, which would otherwise invite the click it refuses.
|
|
95
|
+
*
|
|
96
|
+
* A filled button's boundary is its fill, and a forced colors scheme
|
|
97
|
+
* replaces the fill with the canvas, so the button read as loose text.
|
|
98
|
+
* There it draws an outline in the button text color, inset by its own
|
|
99
|
+
* width. An outline takes no space, so nothing moves, and the focus
|
|
100
|
+
* outline in theme.css replaces it while the button has focus.
|
|
85
101
|
*/
|
|
102
|
+
const FILLED_EDGE =
|
|
103
|
+
'forced-colors:outline-solid forced-colors:outline-1 forced-colors:-outline-offset-1 ' +
|
|
104
|
+
'forced-colors:outline-[color:ButtonText] forced-colors:disabled:outline-[color:GrayText]';
|
|
105
|
+
|
|
86
106
|
const variants: Record<Variant, string> = {
|
|
87
107
|
primary:
|
|
88
|
-
'bg-brand text-ink hover:bg-brand-light active:bg-brand-deep shadow-
|
|
108
|
+
'bg-brand bg-lift text-ink hover:bg-brand-light active:bg-brand-deep inset-shadow-bevel ' +
|
|
109
|
+
'shadow-primary hover:shadow-primary-hover active:shadow-primary-hover ' +
|
|
110
|
+
`disabled:shadow-raised aria-disabled:shadow-raised ${FILLED_EDGE}`,
|
|
89
111
|
violet:
|
|
90
|
-
|
|
112
|
+
`bg-violet text-ink hover:brightness-110 active:brightness-90 inset-shadow-bevel shadow-raised ${FILLED_EDGE}`,
|
|
91
113
|
secondary:
|
|
92
|
-
'bg-surface-2 text-fg border border-line-strong hover:bg-line active:bg-line-strong/30',
|
|
93
|
-
danger:
|
|
114
|
+
'bg-surface-2 text-fg border border-line-strong hover:bg-line active:bg-line-strong/30 inset-shadow-highlight shadow-raised',
|
|
115
|
+
danger:
|
|
116
|
+
`bg-danger text-ink hover:brightness-110 active:brightness-90 inset-shadow-bevel shadow-raised ${FILLED_EDGE}`,
|
|
94
117
|
ghost: 'text-muted hover:bg-surface-2 hover:text-fg active:bg-line active:text-fg',
|
|
95
118
|
outline:
|
|
96
119
|
'border border-line-strong text-fg hover:border-brand hover:text-brand active:bg-brand/10',
|
|
@@ -125,7 +148,8 @@
|
|
|
125
148
|
* button its icon plus 16px.
|
|
126
149
|
*/
|
|
127
150
|
const cls = $derived(
|
|
128
|
-
`inline-flex items-center justify-center font-medium whitespace-nowrap
|
|
151
|
+
`inline-flex items-center justify-center font-medium whitespace-nowrap ` +
|
|
152
|
+
`transition-[color,background-color,border-color,box-shadow] focus-visible:shadow-glow ` +
|
|
129
153
|
`disabled:opacity-50 disabled:cursor-not-allowed select-none ${TOUCH_GROW} ` +
|
|
130
154
|
`${full ? 'w-full' : ''} ${variants[variant]} ${sizes[size]} ${klass}`,
|
|
131
155
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Component, Snippet } from 'svelte';
|
|
3
|
-
import { sectionHeading } from '../internal/layout.js';
|
|
3
|
+
import { CARD_SURFACE, sectionHeading } from '../internal/layout.js';
|
|
4
4
|
|
|
5
5
|
type Pad = 'none' | 'sm' | 'md' | 'lg';
|
|
6
6
|
|
|
@@ -81,11 +81,16 @@
|
|
|
81
81
|
<!-- tabindex is only set alongside role="button" (when onclick is provided),
|
|
82
82
|
so the element is interactive. The compiler can't narrow the dynamic role. -->
|
|
83
83
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
84
|
+
<!-- The surface is the layout contract's, so a Card and a bordered Panel on
|
|
85
|
+
one page agree. A hover card, and any clickable one, takes a cyan border
|
|
86
|
+
and the focus glow under the pointer, so it answers the way a button does.
|
|
87
|
+
A clickable card glows on focus too: its own shadow would otherwise hide
|
|
88
|
+
the global focus glow. -->
|
|
84
89
|
<div
|
|
85
90
|
data-print="keep"
|
|
86
|
-
class="
|
|
87
|
-
? 'hover:border-
|
|
88
|
-
: ''} {onclick ? 'cursor-pointer' : ''} {klass}"
|
|
91
|
+
class="{CARD_SURFACE} overflow-hidden transition-[border-color,box-shadow] {hover || onclick
|
|
92
|
+
? 'hover:border-brand/50 hover:shadow-glow'
|
|
93
|
+
: ''} {onclick ? 'cursor-pointer focus-visible:shadow-glow' : ''} {klass}"
|
|
89
94
|
role={onclick ? 'button' : undefined}
|
|
90
95
|
tabindex={onclick ? 0 : undefined}
|
|
91
96
|
{onclick}
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
<div
|
|
116
116
|
use:overlay
|
|
117
117
|
transition:motion.drawer|global={{ side: edge() }}
|
|
118
|
-
class="relative flex h-full w-full flex-col {OVERLAY_WIDTH[rung]} bg-surface shadow-
|
|
118
|
+
class="relative flex h-full w-full flex-col {OVERLAY_WIDTH[rung]} bg-surface shadow-overlay
|
|
119
119
|
{side === 'right' ? 'border-s' : 'border-e'} border-line"
|
|
120
120
|
role="dialog"
|
|
121
121
|
aria-modal="true"
|
|
@@ -29,19 +29,25 @@
|
|
|
29
29
|
}: Props = $props();
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
|
+
<!-- The hero wash: the page's ink into a tenth of violet, under a solid 1px
|
|
33
|
+
border. It was a dashed outline on a translucent surface, which read as a
|
|
34
|
+
slot left unfilled rather than as a place something will appear. The
|
|
35
|
+
medallion is a raised control in miniature, and its icon takes the brand
|
|
36
|
+
color. The contrast suite measures every text token against both ends of
|
|
37
|
+
the wash. -->
|
|
32
38
|
<div
|
|
33
|
-
class="flex flex-col items-center justify-center text-center rounded-
|
|
39
|
+
class="flex flex-col items-center justify-center text-center rounded-card border border-line bg-ink bg-hero inset-shadow-highlight px-6 py-12 {klass}"
|
|
34
40
|
>
|
|
35
41
|
{#if iconSnippet}
|
|
36
42
|
<div
|
|
37
|
-
class="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-surface-2 text-
|
|
43
|
+
class="mb-4 flex h-12 w-12 items-center justify-center rounded-full border border-line bg-surface-2 text-brand inset-shadow-highlight shadow-raised"
|
|
38
44
|
aria-hidden="true"
|
|
39
45
|
>
|
|
40
46
|
{@render iconSnippet()}
|
|
41
47
|
</div>
|
|
42
48
|
{:else if icon}
|
|
43
49
|
<div
|
|
44
|
-
class="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-surface-2 text-2xl"
|
|
50
|
+
class="mb-4 flex h-12 w-12 items-center justify-center rounded-full border border-line bg-surface-2 text-2xl inset-shadow-highlight shadow-raised"
|
|
45
51
|
aria-hidden="true"
|
|
46
52
|
>
|
|
47
53
|
{icon}
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
use:overlay
|
|
88
88
|
transition:motion.dialog|global
|
|
89
89
|
class="relative flex max-h-[calc(100dvh-2rem)] w-full {OVERLAY_WIDTH[rung]} flex-col
|
|
90
|
-
overflow-hidden rounded-
|
|
90
|
+
overflow-hidden rounded-card border border-line bg-surface bg-card shadow-overlay inset-shadow-highlight"
|
|
91
91
|
role="dialog"
|
|
92
92
|
aria-modal="true"
|
|
93
93
|
aria-labelledby={title ? headingId : undefined}
|
|
@@ -55,9 +55,15 @@
|
|
|
55
55
|
a 390px screen is the ramp applied rather than adopted. 32px is the step
|
|
56
56
|
the ramp does hold and the step this was already reaching for.
|
|
57
57
|
|
|
58
|
-
The weight
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
The weight is H2's 600 with H2's -0.02em tracking, on a 38px line
|
|
59
|
+
(leading-title). A 700 title at 32px read heavy beside the semibold
|
|
60
|
+
card headings under it, and the tighter tracking is what makes a large
|
|
61
|
+
title read as set rather than typed.
|
|
62
|
+
|
|
63
|
+
Below the sm breakpoint it drops to the console's 24/32 step, which
|
|
64
|
+
keeps a long title to fewer lines on a phone. A media query is settled
|
|
65
|
+
before the first paint, so the title never changes size while the
|
|
66
|
+
reader watches.
|
|
61
67
|
|
|
62
68
|
Each size token names its own leading and tracking. A `text-*` token
|
|
63
69
|
built from `--text-*` alone sets font-size and leaves line-height to
|
|
@@ -71,7 +77,7 @@
|
|
|
71
77
|
data-testid="page-title"
|
|
72
78
|
class="[overflow-wrap:anywhere] {compact
|
|
73
79
|
? 'text-sm font-semibold text-fg'
|
|
74
|
-
: 'text-
|
|
80
|
+
: 'text-2xl tracking-h2 font-semibold text-fg sm:text-h2 sm:leading-title'}"
|
|
75
81
|
>
|
|
76
82
|
{title}
|
|
77
83
|
</h1>
|
|
@@ -281,9 +281,22 @@
|
|
|
281
281
|
{/if}
|
|
282
282
|
|
|
283
283
|
{#if shown.length === 0}
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
<div
|
|
285
|
+
class="flex flex-col items-center gap-3 rounded-xl border border-dashed border-line p-8 text-center text-sm text-muted"
|
|
286
|
+
>
|
|
287
|
+
<p>No capabilities match these filters.</p>
|
|
288
|
+
<Button
|
|
289
|
+
type="button"
|
|
290
|
+
size="sm"
|
|
291
|
+
variant="secondary"
|
|
292
|
+
onclick={() => {
|
|
293
|
+
query = '';
|
|
294
|
+
category = 'all';
|
|
295
|
+
}}
|
|
296
|
+
>
|
|
297
|
+
Clear filters
|
|
298
|
+
</Button>
|
|
299
|
+
</div>
|
|
287
300
|
{:else}
|
|
288
301
|
<div class="grid gap-3 sm:grid-cols-2 2xl:grid-cols-3">
|
|
289
302
|
{#each shown as plugin (plugin.sku)}
|
|
@@ -368,7 +381,7 @@
|
|
|
368
381
|
<div class="sticky bottom-3 lg:hidden" transition:motion.toast|global>
|
|
369
382
|
<a
|
|
370
383
|
href="#{summaryId}"
|
|
371
|
-
class="flex items-center justify-between gap-3 rounded-xl border border-brand bg-surface px-4 py-3 text-sm shadow-
|
|
384
|
+
class="flex items-center justify-between gap-3 rounded-xl border border-brand bg-surface px-4 py-3 text-sm shadow-popover inset-shadow-highlight transition-colors hover:bg-surface-2"
|
|
372
385
|
>
|
|
373
386
|
<span class="text-fg">
|
|
374
387
|
<span class="font-semibold">{count}</span> picked{#if total !== null}{' '}<span
|
|
@@ -384,7 +397,7 @@
|
|
|
384
397
|
|
|
385
398
|
<aside
|
|
386
399
|
id={summaryId}
|
|
387
|
-
class="scroll-mt-20 self-start rounded-
|
|
400
|
+
class="scroll-mt-20 self-start rounded-card border border-line bg-surface bg-card p-5 shadow-card inset-shadow-highlight lg:sticky"
|
|
388
401
|
style:top={stickyTop}
|
|
389
402
|
aria-label={heading}
|
|
390
403
|
>
|
|
@@ -415,7 +428,8 @@
|
|
|
415
428
|
|
|
416
429
|
{#if count === 0}
|
|
417
430
|
<p class="py-6 text-center text-sm text-muted">
|
|
418
|
-
|
|
431
|
+
No capabilities picked yet. Without any, you stay on the free tier, which needs no
|
|
432
|
+
checkout.
|
|
419
433
|
</p>
|
|
420
434
|
{:else}
|
|
421
435
|
<ul class="mb-4 max-h-56 space-y-1 overflow-y-auto pe-1">
|
|
@@ -482,7 +496,7 @@
|
|
|
482
496
|
{/if}
|
|
483
497
|
{#if ladder.next}
|
|
484
498
|
<p class="mb-3 text-xs text-muted">
|
|
485
|
-
{ladder.next.more} more
|
|
499
|
+
Add {ladder.next.more} more for a {ladder.next.discount_pct}% discount.
|
|
486
500
|
</p>
|
|
487
501
|
{/if}
|
|
488
502
|
|
|
@@ -30,9 +30,21 @@
|
|
|
30
30
|
: 0,
|
|
31
31
|
);
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The brand fill runs cyan into violet, the accent the kit keeps for a mark
|
|
35
|
+
* a few pixels wide, and both ends clear 3:1 against the track in both
|
|
36
|
+
* palettes. bg-brand stays under it for paper, which draws no gradient. It
|
|
37
|
+
* runs from the start edge, so under right-to-left the fill is mirrored and
|
|
38
|
+
* its cyan end still sits where the bar starts.
|
|
39
|
+
*
|
|
40
|
+
* A forced colors scheme replaces every background color with the canvas,
|
|
41
|
+
* which erased the track and the fill alike. There the track draws a border
|
|
42
|
+
* and the fill opts out of the forced palette to paint in the system text
|
|
43
|
+
* color, so the value is still there to read.
|
|
44
|
+
*/
|
|
33
45
|
const tones: Record<AccentTone, string> = {
|
|
34
46
|
neutral: 'bg-muted',
|
|
35
|
-
brand: 'bg-brand',
|
|
47
|
+
brand: 'bg-brand bg-accent',
|
|
36
48
|
violet: 'bg-violet',
|
|
37
49
|
success: 'bg-success',
|
|
38
50
|
warn: 'bg-warn',
|
|
@@ -54,7 +66,8 @@
|
|
|
54
66
|
</div>
|
|
55
67
|
{/if}
|
|
56
68
|
<div
|
|
57
|
-
class="w-full overflow-hidden rounded-full bg-surface-2 {sizes[size]}
|
|
69
|
+
class="w-full overflow-hidden rounded-full bg-surface-2 inset-shadow-well {sizes[size]}
|
|
70
|
+
forced-colors:border forced-colors:border-[color:CanvasText]"
|
|
58
71
|
role="progressbar"
|
|
59
72
|
aria-valuenow={value}
|
|
60
73
|
aria-valuemin={0}
|
|
@@ -62,7 +75,8 @@
|
|
|
62
75
|
aria-label={label}
|
|
63
76
|
>
|
|
64
77
|
<div
|
|
65
|
-
class="h-full rounded-full transition-[width] duration-progress
|
|
78
|
+
class="h-full rounded-full transition-[width] duration-progress rtl:-scale-x-100
|
|
79
|
+
forced-colors:forced-color-adjust-none forced-colors:bg-[color:CanvasText]
|
|
66
80
|
{tones[tone]}
|
|
67
81
|
{animated ? 'animate-pulse' : ''}"
|
|
68
82
|
style="width: {pct}%"
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
onfocusout={linked ? onGroupFocusOut : undefined}
|
|
266
266
|
class="inline-flex items-stretch rounded-lg border {error
|
|
267
267
|
? 'border-danger coarse:outline-danger'
|
|
268
|
-
: 'border-line-strong coarse:outline-line-strong'} bg-surface-2 p-0.5 coarse:border-0 coarse:p-0.75 coarse:outline coarse:outline-1 coarse:-outline-offset-1 {SIZES[
|
|
268
|
+
: 'border-line-strong coarse:outline-line-strong'} bg-surface-2 inset-shadow-well p-0.5 coarse:border-0 coarse:p-0.75 coarse:outline coarse:outline-1 coarse:-outline-offset-1 {SIZES[
|
|
269
269
|
size
|
|
270
270
|
].row} {disabled ? 'opacity-50' : ''}"
|
|
271
271
|
>
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
`${HIT_AREA} inline-flex coarse:min-w-control items-center justify-center rounded-md outline-none transition-colors focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand disabled:cursor-not-allowed ` +
|
|
277
277
|
SIZES[size].segment +
|
|
278
278
|
(selected
|
|
279
|
-
? ' bg-surface text-brand shadow-
|
|
279
|
+
? ' bg-surface text-brand shadow-raised inset-shadow-highlight ring-1 ring-inset ring-brand/40'
|
|
280
280
|
: ' text-muted hover:text-fg active:bg-surface active:text-fg')}
|
|
281
281
|
{#snippet content()}
|
|
282
282
|
{#if Icon}
|
|
@@ -83,10 +83,23 @@
|
|
|
83
83
|
* inside `@media (hover: hover)` and never matches a finger. A sidebar is
|
|
84
84
|
* mostly used on a phone as a drawer, where the tap that closed it was the
|
|
85
85
|
* one gesture in the component with nothing on screen behind it.
|
|
86
|
+
*
|
|
87
|
+
* The current row's mark is a short cyan to violet bar laid over its start
|
|
88
|
+
* border, inset from the rounded corners, rather than the border itself: a
|
|
89
|
+
* gradient border cannot follow a radius. The border goes transparent under
|
|
90
|
+
* it and returns as the system text color in a forced colors scheme, which
|
|
91
|
+
* draws neither the bar nor the row's fill, so the row keeps a mark there.
|
|
92
|
+
* The row's fill is the brand tint and the bar carries a few pixels of
|
|
93
|
+
* glow, so the row reads as the place you are rather than one more entry.
|
|
86
94
|
*/
|
|
87
95
|
function rowPaint(state: 'current' | 'ancestor' | 'rest'): string {
|
|
88
96
|
if (state === 'current')
|
|
89
|
-
return
|
|
97
|
+
return (
|
|
98
|
+
'relative border-transparent forced-colors:border-[CanvasText] bg-tint ' +
|
|
99
|
+
'inset-shadow-highlight font-medium text-brand hover:bg-line active:bg-line ' +
|
|
100
|
+
'after:absolute after:inset-y-1.5 after:-start-0.5 after:w-0.5 after:rounded-full ' +
|
|
101
|
+
'after:bg-brand after:bg-accent-y after:shadow-mark'
|
|
102
|
+
);
|
|
90
103
|
if (state === 'ancestor') return 'border-brand text-fg hover:bg-surface-2 active:bg-line';
|
|
91
104
|
return 'border-transparent text-muted hover:bg-surface-2 hover:text-fg active:bg-line active:text-fg';
|
|
92
105
|
}
|
|
@@ -447,7 +447,7 @@
|
|
|
447
447
|
const mark = line?.index === index ? LINE[line.edge as 'before' | 'after'] : null;
|
|
448
448
|
return cn(
|
|
449
449
|
'relative flex items-center gap-1 rounded-lg',
|
|
450
|
-
drag?.id === id && 'z-dropdown bg-surface shadow-
|
|
450
|
+
drag?.id === id && 'z-dropdown bg-surface shadow-popover',
|
|
451
451
|
settling === id && 'transition-[translate] duration-base ease-move',
|
|
452
452
|
mark && `${mark.shape} ${line?.refused ? mark.refused : mark.ok}`,
|
|
453
453
|
);
|
|
@@ -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,7 +72,7 @@
|
|
|
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)]',
|
|
@@ -240,10 +241,15 @@
|
|
|
240
241
|
|
|
241
242
|
<!-- overflow-hidden on the frame, not only on the scroller: the hovered row's
|
|
242
243
|
ring is square and the frame is not, and the last row's corners drew past
|
|
243
|
-
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. -->
|
|
244
250
|
<div
|
|
245
251
|
data-testid="table-frame"
|
|
246
|
-
class="relative w-full overflow-hidden
|
|
252
|
+
class="relative w-full overflow-hidden {CARD_SURFACE} {cls}"
|
|
247
253
|
>
|
|
248
254
|
<!--
|
|
249
255
|
data-print: the box exists to clip. On paper there is no viewport to clip
|
|
@@ -256,7 +262,7 @@
|
|
|
256
262
|
onfocusin={reveal}
|
|
257
263
|
data-print="unclip"
|
|
258
264
|
data-testid="table-scroll"
|
|
259
|
-
class="w-full overflow-x-auto rounded-
|
|
265
|
+
class="w-full overflow-x-auto rounded-card"
|
|
260
266
|
{...scrollAttrs}
|
|
261
267
|
>
|
|
262
268
|
<!--
|
|
@@ -274,21 +280,25 @@
|
|
|
274
280
|
which is why the lines moved down to the cells they were already drawn
|
|
275
281
|
between. With border-spacing at zero the result is the same line in the
|
|
276
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.
|
|
277
287
|
-->
|
|
278
288
|
<table
|
|
279
289
|
style="--cell-truncate: {truncateAt}"
|
|
280
290
|
class="w-full border-separate border-spacing-0 text-sm text-start
|
|
281
291
|
{fixed ? 'table-fixed' : ''}
|
|
282
292
|
{CELL_FIT[cell]} {CELL_NAMED} {COLUMN_ALIGN} {HEAD_CONTROLS}
|
|
283
|
-
[&_thead_th]:border-b [&_thead_th]:border-line [&_thead]:bg-
|
|
293
|
+
[&_thead_th]:border-b [&_thead_th]:border-line [&_thead]:bg-tint
|
|
284
294
|
[&_thead_th]:px-4 [&_thead_th]:py-3 [&_thead_th]:text-xs
|
|
285
295
|
[&_thead_th]:font-medium [&_thead_th]:text-faint
|
|
286
296
|
[&_thead_th]:uppercase [&_thead_th]:tracking-wider [&_thead_th]:whitespace-nowrap
|
|
287
297
|
[&_tbody_td]:px-4 [&_tbody_td]:py-3 [&_tbody_td]:text-fg [&_tbody_td]:align-middle
|
|
288
298
|
[&_tbody_td]:border-b [&_tbody_td]:border-line [&_tbody_tr:last-child_td]:border-0
|
|
289
299
|
{striped ? '[&_tbody_tr:nth-child(even)]:bg-surface-2/40' : ''}
|
|
290
|
-
[&_tbody_tr:last-child>td:first-child]:rounded-es-[calc(var(--radius-
|
|
291
|
-
[&_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)]
|
|
292
302
|
{hoverable ? HOVER_RING : ''}"
|
|
293
303
|
>
|
|
294
304
|
{@render children()}
|
|
@@ -306,7 +316,7 @@
|
|
|
306
316
|
aria-hidden="true"
|
|
307
317
|
data-print="hide"
|
|
308
318
|
data-testid="table-more-before"
|
|
309
|
-
class="{SCROLL_EDGE} {SCROLL_EDGE_START} rounded-s-
|
|
319
|
+
class="{SCROLL_EDGE} {SCROLL_EDGE_START} rounded-s-card"
|
|
310
320
|
></div>
|
|
311
321
|
{/if}
|
|
312
322
|
{#if moreAfter}
|
|
@@ -314,7 +324,7 @@
|
|
|
314
324
|
aria-hidden="true"
|
|
315
325
|
data-print="hide"
|
|
316
326
|
data-testid="table-more-after"
|
|
317
|
-
class="{SCROLL_EDGE} {SCROLL_EDGE_END} rounded-e-
|
|
327
|
+
class="{SCROLL_EDGE} {SCROLL_EDGE_END} rounded-e-card"
|
|
318
328
|
></div>
|
|
319
329
|
{/if}
|
|
320
330
|
</div>
|
|
@@ -158,6 +158,8 @@
|
|
|
158
158
|
data-testid="tabs-scroll"
|
|
159
159
|
class="flex gap-0.5 overflow-x-auto border-b border-line [scrollbar-width:none]"
|
|
160
160
|
>
|
|
161
|
+
<!-- The active indicator is the cyan to violet accent, with solid brand
|
|
162
|
+
under it for wherever gradients are not drawn. -->
|
|
161
163
|
{#each items as tab, i (tab.id)}
|
|
162
164
|
<button
|
|
163
165
|
type="button"
|
|
@@ -170,7 +172,7 @@
|
|
|
170
172
|
onkeydown={(e) => onTabKeydown(e, i)}
|
|
171
173
|
class="{TAB}
|
|
172
174
|
{active === tab.id
|
|
173
|
-
? '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'
|
|
174
176
|
: 'text-faint hover:text-fg'}"
|
|
175
177
|
>
|
|
176
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';
|