@lyeve-labs/ui-kit 0.41.1 → 0.42.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.
|
@@ -152,6 +152,7 @@
|
|
|
152
152
|
class="{cls} {inert ? 'pointer-events-none opacity-50' : ''}"
|
|
153
153
|
aria-disabled={inert ? 'true' : undefined}
|
|
154
154
|
tabindex={inert ? -1 : undefined}
|
|
155
|
+
onclick={inert ? undefined : onclick}
|
|
155
156
|
{...rest}
|
|
156
157
|
>
|
|
157
158
|
{#if loading}<Spinner size={spinnerSize} />{/if}
|
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
/** The period that quote was priced for. A monthly quote says nothing about a year. */
|
|
55
55
|
quotePeriod?: BillingPeriod | null;
|
|
56
56
|
period?: BillingPeriod;
|
|
57
|
+
/**
|
|
58
|
+
* What the total is charged per, when it differs from `period`. A
|
|
59
|
+
* one-time payment is quoted monthly and charged once, so its host passes
|
|
60
|
+
* `'once'` and the total reads "$6.00 once" rather than "$6.00/mo".
|
|
61
|
+
*/
|
|
62
|
+
per?: BillingPeriod | 'once';
|
|
57
63
|
/** True while a fresh price is in flight. */
|
|
58
64
|
quoting?: boolean;
|
|
59
65
|
/** The count the bill will carry, when the host knows better than the cart. */
|
|
@@ -69,6 +75,21 @@
|
|
|
69
75
|
actions?: Snippet<[{ skus: string[]; total: number | null }]>;
|
|
70
76
|
/** Anything the host wants between the total and the actions. */
|
|
71
77
|
notice?: Snippet;
|
|
78
|
+
/**
|
|
79
|
+
* The host's own summary, drawn in place of everything inside the aside:
|
|
80
|
+
* the title line, `error`, the list, the ladder, the total, `notice` and
|
|
81
|
+
* `actions`. For a host that prices more than the selection, such as a
|
|
82
|
+
* plan change or a discount code. The aside keeps its id, its stickiness
|
|
83
|
+
* and the bar that jumps to it on a narrow screen. `remove` takes one
|
|
84
|
+
* selected capability out and reports it through `onchange`, as unticking
|
|
85
|
+
* its card does. A capability that is not selected is left alone. `total`
|
|
86
|
+
* is null both while a price is in flight and before one is asked for, so
|
|
87
|
+
* a host that tells the two apart tracks its own request, as it does for
|
|
88
|
+
* `quoting`.
|
|
89
|
+
*/
|
|
90
|
+
summary?: Snippet<[{ skus: string[]; total: number | null; remove: (sku: string) => void }]>;
|
|
91
|
+
/** The panel's name: the aside's accessible name, also printed atop the default summary. */
|
|
92
|
+
heading?: string;
|
|
72
93
|
/**
|
|
73
94
|
* How far below the top of the window the summary sticks on a wide
|
|
74
95
|
* screen, as a CSS length. A page with a sticky header passes its height,
|
|
@@ -102,6 +123,7 @@
|
|
|
102
123
|
quote = null,
|
|
103
124
|
quotePeriod = null,
|
|
104
125
|
period = 'monthly',
|
|
126
|
+
per,
|
|
105
127
|
quoting = false,
|
|
106
128
|
billedSkus = null,
|
|
107
129
|
currency = 'USD',
|
|
@@ -111,6 +133,8 @@
|
|
|
111
133
|
onchange,
|
|
112
134
|
actions,
|
|
113
135
|
notice,
|
|
136
|
+
summary,
|
|
137
|
+
heading = 'Your selection',
|
|
114
138
|
stickyTop = '1rem',
|
|
115
139
|
class: klass = '',
|
|
116
140
|
}: Props = $props();
|
|
@@ -176,7 +200,10 @@
|
|
|
176
200
|
),
|
|
177
201
|
);
|
|
178
202
|
|
|
179
|
-
const perLabel = $derived(
|
|
203
|
+
const perLabel = $derived.by(() => {
|
|
204
|
+
const charged = per ?? period;
|
|
205
|
+
return charged === 'once' ? ' once' : charged === 'annual' ? '/yr' : '/mo';
|
|
206
|
+
});
|
|
180
207
|
|
|
181
208
|
// The volume ladder drawn as a meter: the rungs that take something off,
|
|
182
209
|
// placed along the count of the last one.
|
|
@@ -301,7 +328,7 @@
|
|
|
301
328
|
>{plugin.name}</span
|
|
302
329
|
>
|
|
303
330
|
</span>
|
|
304
|
-
<span id="{id}-price" class="shrink-0
|
|
331
|
+
<span id="{id}-price" class="shrink-0 tabular-nums text-sm font-semibold text-fg">
|
|
305
332
|
{formatCents(plugin.price_cents, currency)}<span
|
|
306
333
|
class="text-xs font-normal text-muted">/mo</span
|
|
307
334
|
>
|
|
@@ -333,13 +360,12 @@
|
|
|
333
360
|
class="flex items-center justify-between gap-3 rounded-xl border border-brand bg-surface px-4 py-3 text-sm shadow-lg transition-colors hover:bg-surface-2"
|
|
334
361
|
>
|
|
335
362
|
<span class="text-fg">
|
|
336
|
-
<span class="font-semibold">{count}</span> picked{#if total !== null}<span
|
|
337
|
-
class="text-muted"
|
|
363
|
+
<span class="font-semibold">{count}</span> picked{#if total !== null}{' '}<span
|
|
364
|
+
class="text-muted">·</span
|
|
338
365
|
>
|
|
339
|
-
|
|
340
|
-
</span><span class="font-mono">{formatCents(total, currency)}{perLabel}</span>{/if}
|
|
366
|
+
<span class="tabular-nums">{formatCents(total, currency)}</span>{/if}
|
|
341
367
|
</span>
|
|
342
|
-
<span class="font-semibold text-brand">Review</span>
|
|
368
|
+
<span class="font-semibold text-brand">Review order</span>
|
|
343
369
|
</a>
|
|
344
370
|
</div>
|
|
345
371
|
{/if}
|
|
@@ -349,129 +375,141 @@
|
|
|
349
375
|
id={summaryId}
|
|
350
376
|
class="scroll-mt-20 self-start rounded-xl border border-line bg-surface p-5 lg:sticky"
|
|
351
377
|
style:top={stickyTop}
|
|
352
|
-
aria-label=
|
|
378
|
+
aria-label={heading}
|
|
353
379
|
>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
380
|
+
{#if summary}
|
|
381
|
+
{@render summary({
|
|
382
|
+
skus: [...selected],
|
|
383
|
+
total,
|
|
384
|
+
remove: (sku) => {
|
|
385
|
+
if (selected.has(sku)) toggle(sku);
|
|
386
|
+
},
|
|
387
|
+
})}
|
|
388
|
+
{:else}
|
|
389
|
+
<div class="mb-4 flex items-center justify-between gap-2">
|
|
390
|
+
<p class="text-xs font-semibold uppercase tracking-widest text-faint">{heading}</p>
|
|
391
|
+
{#if count > 0}
|
|
392
|
+
<Badge tone="brand" size="sm">
|
|
393
|
+
{count}
|
|
394
|
+
{count === 1 ? 'capability' : 'capabilities'}
|
|
395
|
+
</Badge>
|
|
396
|
+
{/if}
|
|
397
|
+
</div>
|
|
363
398
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
399
|
+
{#if error}
|
|
400
|
+
<p class="mb-3 rounded-lg border border-danger/30 bg-danger/10 p-3 text-xs text-danger">
|
|
401
|
+
{error}
|
|
402
|
+
</p>
|
|
403
|
+
{/if}
|
|
369
404
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
405
|
+
{#if count === 0}
|
|
406
|
+
<p class="py-6 text-center text-sm text-muted">
|
|
407
|
+
Nothing picked yet. With none of these you are on the free tier, which needs no checkout.
|
|
408
|
+
</p>
|
|
409
|
+
{:else}
|
|
410
|
+
<ul class="mb-4 max-h-56 space-y-1 overflow-y-auto pe-1">
|
|
411
|
+
{#each [...selected] as sku (sku)}
|
|
412
|
+
{@const p = bySku.get(sku)}
|
|
413
|
+
<li class="flex items-center justify-between gap-2 text-xs">
|
|
414
|
+
<span class="truncate text-fg">{p?.name ?? sku}</span>
|
|
415
|
+
<span class="flex items-center gap-1">
|
|
416
|
+
<span class="tabular-nums text-muted"
|
|
417
|
+
>{formatCents(p?.price_cents ?? 0, currency)}</span
|
|
418
|
+
>
|
|
419
|
+
<Button
|
|
420
|
+
type="button"
|
|
421
|
+
variant="ghost"
|
|
422
|
+
size="sm"
|
|
423
|
+
onclick={() => toggle(sku)}
|
|
424
|
+
aria-label={`Take ${p?.name ?? sku} out of your selection`}
|
|
425
|
+
>
|
|
426
|
+
<X size={12} />
|
|
427
|
+
</Button>
|
|
428
|
+
</span>
|
|
429
|
+
</li>
|
|
430
|
+
{/each}
|
|
431
|
+
</ul>
|
|
395
432
|
|
|
396
|
-
|
|
397
|
-
|
|
433
|
+
{#if rungs.length > 0}
|
|
434
|
+
<!-- The ladder as a meter, so "two more" is something a reader can
|
|
398
435
|
see the distance to. -->
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<span
|
|
402
|
-
class="absolute inset-y-0 start-0 rounded-full bg-brand transition-[width] duration-progress"
|
|
403
|
-
style:width="{ladderFill}%"
|
|
404
|
-
></span>
|
|
405
|
-
{#each rungs as r (r.plugins)}
|
|
436
|
+
<div class="mb-3" aria-hidden="true">
|
|
437
|
+
<div class="relative h-1.5 rounded-full bg-surface-2">
|
|
406
438
|
<span
|
|
407
|
-
class=
|
|
408
|
-
|
|
409
|
-
ladder.count >= r.plugins
|
|
410
|
-
? 'border-brand bg-brand'
|
|
411
|
-
: 'border-line-strong bg-surface',
|
|
412
|
-
)}
|
|
413
|
-
style:left="{(r.plugins / ladderTop) * 100}%"
|
|
439
|
+
class="absolute inset-y-0 start-0 rounded-full bg-brand transition-[width] duration-progress"
|
|
440
|
+
style:width="{ladderFill}%"
|
|
414
441
|
></span>
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
442
|
+
{#each rungs as r (r.plugins)}
|
|
443
|
+
<span
|
|
444
|
+
class={cn(
|
|
445
|
+
'absolute top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 transition-colors',
|
|
446
|
+
ladder.count >= r.plugins
|
|
447
|
+
? 'border-brand bg-brand'
|
|
448
|
+
: 'border-line-strong bg-surface',
|
|
449
|
+
)}
|
|
450
|
+
style:left="{(r.plugins / ladderTop) * 100}%"
|
|
451
|
+
></span>
|
|
452
|
+
{/each}
|
|
453
|
+
</div>
|
|
454
|
+
<div class="relative mt-2 h-4 text-[10px] text-muted">
|
|
455
|
+
{#each rungs as r (r.plugins)}
|
|
456
|
+
<span
|
|
457
|
+
class="absolute -translate-x-1/2 whitespace-nowrap tabular-nums"
|
|
458
|
+
style:left="{Math.min((r.plugins / ladderTop) * 100, 92)}%"
|
|
459
|
+
>
|
|
460
|
+
{r.plugins}+ · {r.discount_pct}%
|
|
461
|
+
</span>
|
|
462
|
+
{/each}
|
|
463
|
+
</div>
|
|
426
464
|
</div>
|
|
427
|
-
|
|
428
|
-
{/if}
|
|
429
|
-
|
|
430
|
-
{#if ladder.reachedPct > 0}
|
|
431
|
-
<p class="mb-2 text-xs text-success">
|
|
432
|
-
Volume discount {ladder.reachedPct}% applied.
|
|
433
|
-
</p>
|
|
434
|
-
{/if}
|
|
435
|
-
{#if ladder.next}
|
|
436
|
-
<p class="mb-3 text-xs text-muted">
|
|
437
|
-
{ladder.next.more} more and the discount is {ladder.next.discount_pct}%.
|
|
438
|
-
</p>
|
|
439
|
-
{/if}
|
|
465
|
+
{/if}
|
|
440
466
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
{#if quoting}
|
|
445
|
-
<Spinner size={14} />
|
|
446
|
-
Pricing your selection
|
|
447
|
-
{:else}
|
|
448
|
-
Waiting for a price.
|
|
449
|
-
{/if}
|
|
467
|
+
{#if ladder.reachedPct > 0}
|
|
468
|
+
<p class="mb-2 text-xs text-success">
|
|
469
|
+
Volume discount {ladder.reachedPct}% applied.
|
|
450
470
|
</p>
|
|
451
|
-
{
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
{formatCents(total, currency)}<span class="text-sm font-normal text-muted"
|
|
456
|
-
>{perLabel}</span
|
|
457
|
-
>
|
|
458
|
-
</span>
|
|
471
|
+
{/if}
|
|
472
|
+
{#if ladder.next}
|
|
473
|
+
<p class="mb-3 text-xs text-muted">
|
|
474
|
+
{ladder.next.more} more and the discount is {ladder.next.discount_pct}%.
|
|
459
475
|
</p>
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
476
|
+
{/if}
|
|
477
|
+
|
|
478
|
+
<div class="mb-4 border-t border-line pt-3">
|
|
479
|
+
{#if total === null}
|
|
480
|
+
<p class="flex items-center gap-2 text-sm text-muted">
|
|
481
|
+
{#if quoting}
|
|
482
|
+
<Spinner size={14} />
|
|
483
|
+
Pricing your selection
|
|
484
|
+
{:else}
|
|
485
|
+
Waiting for a price.
|
|
486
|
+
{/if}
|
|
463
487
|
</p>
|
|
464
|
-
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
488
|
+
{:else}
|
|
489
|
+
<p class="flex items-baseline justify-between gap-2">
|
|
490
|
+
<span class="text-sm text-muted">Total</span>
|
|
491
|
+
<span class="tabular-nums text-xl font-semibold text-fg">
|
|
492
|
+
{formatCents(total, currency)}<span class="text-sm font-normal text-muted"
|
|
493
|
+
>{perLabel}</span
|
|
494
|
+
>
|
|
495
|
+
</span>
|
|
468
496
|
</p>
|
|
497
|
+
{#if quoted && quoted.volume_discount_cents > 0}
|
|
498
|
+
<p class="mt-1 text-end text-xs text-muted">
|
|
499
|
+
{formatCents(quoted.volume_discount_cents, currency)} off the list price.
|
|
500
|
+
</p>
|
|
501
|
+
{/if}
|
|
502
|
+
{#if quoted && quoted.community_discount_cents > 0}
|
|
503
|
+
<p class="mt-1 text-end text-xs text-success">
|
|
504
|
+
Community discount {quoted.community_discount_pct}% applied.
|
|
505
|
+
</p>
|
|
506
|
+
{/if}
|
|
469
507
|
{/if}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
{/if}
|
|
508
|
+
</div>
|
|
509
|
+
{/if}
|
|
473
510
|
|
|
474
|
-
|
|
475
|
-
|
|
511
|
+
{#if notice}{@render notice()}{/if}
|
|
512
|
+
{#if actions}{@render actions({ skus: [...selected], total })}{/if}
|
|
513
|
+
{/if}
|
|
476
514
|
</aside>
|
|
477
515
|
</div>
|
|
@@ -20,6 +20,12 @@ interface Props {
|
|
|
20
20
|
/** The period that quote was priced for. A monthly quote says nothing about a year. */
|
|
21
21
|
quotePeriod?: BillingPeriod | null;
|
|
22
22
|
period?: BillingPeriod;
|
|
23
|
+
/**
|
|
24
|
+
* What the total is charged per, when it differs from `period`. A
|
|
25
|
+
* one-time payment is quoted monthly and charged once, so its host passes
|
|
26
|
+
* `'once'` and the total reads "$6.00 once" rather than "$6.00/mo".
|
|
27
|
+
*/
|
|
28
|
+
per?: BillingPeriod | 'once';
|
|
23
29
|
/** True while a fresh price is in flight. */
|
|
24
30
|
quoting?: boolean;
|
|
25
31
|
/** The count the bill will carry, when the host knows better than the cart. */
|
|
@@ -38,6 +44,25 @@ interface Props {
|
|
|
38
44
|
}]>;
|
|
39
45
|
/** Anything the host wants between the total and the actions. */
|
|
40
46
|
notice?: Snippet;
|
|
47
|
+
/**
|
|
48
|
+
* The host's own summary, drawn in place of everything inside the aside:
|
|
49
|
+
* the title line, `error`, the list, the ladder, the total, `notice` and
|
|
50
|
+
* `actions`. For a host that prices more than the selection, such as a
|
|
51
|
+
* plan change or a discount code. The aside keeps its id, its stickiness
|
|
52
|
+
* and the bar that jumps to it on a narrow screen. `remove` takes one
|
|
53
|
+
* selected capability out and reports it through `onchange`, as unticking
|
|
54
|
+
* its card does. A capability that is not selected is left alone. `total`
|
|
55
|
+
* is null both while a price is in flight and before one is asked for, so
|
|
56
|
+
* a host that tells the two apart tracks its own request, as it does for
|
|
57
|
+
* `quoting`.
|
|
58
|
+
*/
|
|
59
|
+
summary?: Snippet<[{
|
|
60
|
+
skus: string[];
|
|
61
|
+
total: number | null;
|
|
62
|
+
remove: (sku: string) => void;
|
|
63
|
+
}]>;
|
|
64
|
+
/** The panel's name: the aside's accessible name, also printed atop the default summary. */
|
|
65
|
+
heading?: string;
|
|
41
66
|
/**
|
|
42
67
|
* How far below the top of the window the summary sticks on a wide
|
|
43
68
|
* screen, as a CSS length. A page with a sticky header passes its height,
|
package/dist/index.d.ts
CHANGED
|
@@ -107,4 +107,4 @@ export { cn, type ClassValue } from './utils/cn.js';
|
|
|
107
107
|
export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, watchThemePreference, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
|
|
108
108
|
export * as motion from './motion.js';
|
|
109
109
|
export type { Rung as MotionRung, Curve as MotionCurve } from './motion.js';
|
|
110
|
-
export declare const VERSION = "0.
|
|
110
|
+
export declare const VERSION = "0.42.1";
|
package/dist/index.js
CHANGED
|
@@ -119,4 +119,4 @@ export * as motion from './motion.js';
|
|
|
119
119
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
120
120
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
121
121
|
// this line. The build and the test suite fail when the two disagree.
|
|
122
|
-
export const VERSION = '0.
|
|
122
|
+
export const VERSION = '0.42.1';
|
package/package.json
CHANGED