@lyeve-labs/ui-kit 0.41.1 → 0.42.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/PluginCart.svelte +138 -109
- package/dist/components/PluginCart.svelte.d.ts +19 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -69,6 +69,21 @@
|
|
|
69
69
|
actions?: Snippet<[{ skus: string[]; total: number | null }]>;
|
|
70
70
|
/** Anything the host wants between the total and the actions. */
|
|
71
71
|
notice?: Snippet;
|
|
72
|
+
/**
|
|
73
|
+
* The host's own summary, drawn in place of everything inside the aside:
|
|
74
|
+
* the title line, `error`, the list, the ladder, the total, `notice` and
|
|
75
|
+
* `actions`. For a host that prices more than the selection, such as a
|
|
76
|
+
* plan change or a discount code. The aside keeps its id, its stickiness
|
|
77
|
+
* and the bar that jumps to it on a narrow screen. `remove` takes one
|
|
78
|
+
* selected capability out and reports it through `onchange`, as unticking
|
|
79
|
+
* its card does. A capability that is not selected is left alone. `total`
|
|
80
|
+
* is null both while a price is in flight and before one is asked for, so
|
|
81
|
+
* a host that tells the two apart tracks its own request, as it does for
|
|
82
|
+
* `quoting`.
|
|
83
|
+
*/
|
|
84
|
+
summary?: Snippet<[{ skus: string[]; total: number | null; remove: (sku: string) => void }]>;
|
|
85
|
+
/** The panel's name: the aside's accessible name, also printed atop the default summary. */
|
|
86
|
+
heading?: string;
|
|
72
87
|
/**
|
|
73
88
|
* How far below the top of the window the summary sticks on a wide
|
|
74
89
|
* screen, as a CSS length. A page with a sticky header passes its height,
|
|
@@ -111,6 +126,8 @@
|
|
|
111
126
|
onchange,
|
|
112
127
|
actions,
|
|
113
128
|
notice,
|
|
129
|
+
summary,
|
|
130
|
+
heading = 'Your selection',
|
|
114
131
|
stickyTop = '1rem',
|
|
115
132
|
class: klass = '',
|
|
116
133
|
}: Props = $props();
|
|
@@ -349,129 +366,141 @@
|
|
|
349
366
|
id={summaryId}
|
|
350
367
|
class="scroll-mt-20 self-start rounded-xl border border-line bg-surface p-5 lg:sticky"
|
|
351
368
|
style:top={stickyTop}
|
|
352
|
-
aria-label=
|
|
369
|
+
aria-label={heading}
|
|
353
370
|
>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
371
|
+
{#if summary}
|
|
372
|
+
{@render summary({
|
|
373
|
+
skus: [...selected],
|
|
374
|
+
total,
|
|
375
|
+
remove: (sku) => {
|
|
376
|
+
if (selected.has(sku)) toggle(sku);
|
|
377
|
+
},
|
|
378
|
+
})}
|
|
379
|
+
{:else}
|
|
380
|
+
<div class="mb-4 flex items-center justify-between gap-2">
|
|
381
|
+
<p class="text-xs font-semibold uppercase tracking-widest text-faint">{heading}</p>
|
|
382
|
+
{#if count > 0}
|
|
383
|
+
<Badge tone="brand" size="sm">
|
|
384
|
+
{count}
|
|
385
|
+
{count === 1 ? 'capability' : 'capabilities'}
|
|
386
|
+
</Badge>
|
|
387
|
+
{/if}
|
|
388
|
+
</div>
|
|
363
389
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
390
|
+
{#if error}
|
|
391
|
+
<p class="mb-3 rounded-lg border border-danger/30 bg-danger/10 p-3 text-xs text-danger">
|
|
392
|
+
{error}
|
|
393
|
+
</p>
|
|
394
|
+
{/if}
|
|
369
395
|
|
|
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
|
-
|
|
396
|
+
{#if count === 0}
|
|
397
|
+
<p class="py-6 text-center text-sm text-muted">
|
|
398
|
+
Nothing picked yet. With none of these you are on the free tier, which needs no checkout.
|
|
399
|
+
</p>
|
|
400
|
+
{:else}
|
|
401
|
+
<ul class="mb-4 max-h-56 space-y-1 overflow-y-auto pe-1">
|
|
402
|
+
{#each [...selected] as sku (sku)}
|
|
403
|
+
{@const p = bySku.get(sku)}
|
|
404
|
+
<li class="flex items-center justify-between gap-2 text-xs">
|
|
405
|
+
<span class="truncate text-fg">{p?.name ?? sku}</span>
|
|
406
|
+
<span class="flex items-center gap-1">
|
|
407
|
+
<span class="font-mono text-muted"
|
|
408
|
+
>{formatCents(p?.price_cents ?? 0, currency)}</span
|
|
409
|
+
>
|
|
410
|
+
<Button
|
|
411
|
+
type="button"
|
|
412
|
+
variant="ghost"
|
|
413
|
+
size="sm"
|
|
414
|
+
onclick={() => toggle(sku)}
|
|
415
|
+
aria-label={`Take ${p?.name ?? sku} out of your selection`}
|
|
416
|
+
>
|
|
417
|
+
<X size={12} />
|
|
418
|
+
</Button>
|
|
419
|
+
</span>
|
|
420
|
+
</li>
|
|
421
|
+
{/each}
|
|
422
|
+
</ul>
|
|
395
423
|
|
|
396
|
-
|
|
397
|
-
|
|
424
|
+
{#if rungs.length > 0}
|
|
425
|
+
<!-- The ladder as a meter, so "two more" is something a reader can
|
|
398
426
|
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)}
|
|
427
|
+
<div class="mb-3" aria-hidden="true">
|
|
428
|
+
<div class="relative h-1.5 rounded-full bg-surface-2">
|
|
406
429
|
<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}%"
|
|
430
|
+
class="absolute inset-y-0 start-0 rounded-full bg-brand transition-[width] duration-progress"
|
|
431
|
+
style:width="{ladderFill}%"
|
|
414
432
|
></span>
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
433
|
+
{#each rungs as r (r.plugins)}
|
|
434
|
+
<span
|
|
435
|
+
class={cn(
|
|
436
|
+
'absolute top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 transition-colors',
|
|
437
|
+
ladder.count >= r.plugins
|
|
438
|
+
? 'border-brand bg-brand'
|
|
439
|
+
: 'border-line-strong bg-surface',
|
|
440
|
+
)}
|
|
441
|
+
style:left="{(r.plugins / ladderTop) * 100}%"
|
|
442
|
+
></span>
|
|
443
|
+
{/each}
|
|
444
|
+
</div>
|
|
445
|
+
<div class="relative mt-2 h-4 text-[10px] text-muted">
|
|
446
|
+
{#each rungs as r (r.plugins)}
|
|
447
|
+
<span
|
|
448
|
+
class="absolute -translate-x-1/2 whitespace-nowrap font-mono"
|
|
449
|
+
style:left="{Math.min((r.plugins / ladderTop) * 100, 92)}%"
|
|
450
|
+
>
|
|
451
|
+
{r.plugins}+ · {r.discount_pct}%
|
|
452
|
+
</span>
|
|
453
|
+
{/each}
|
|
454
|
+
</div>
|
|
426
455
|
</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}
|
|
456
|
+
{/if}
|
|
440
457
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
{#if quoting}
|
|
445
|
-
<Spinner size={14} />
|
|
446
|
-
Pricing your selection
|
|
447
|
-
{:else}
|
|
448
|
-
Waiting for a price.
|
|
449
|
-
{/if}
|
|
458
|
+
{#if ladder.reachedPct > 0}
|
|
459
|
+
<p class="mb-2 text-xs text-success">
|
|
460
|
+
Volume discount {ladder.reachedPct}% applied.
|
|
450
461
|
</p>
|
|
451
|
-
{
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
{formatCents(total, currency)}<span class="text-sm font-normal text-muted"
|
|
456
|
-
>{perLabel}</span
|
|
457
|
-
>
|
|
458
|
-
</span>
|
|
462
|
+
{/if}
|
|
463
|
+
{#if ladder.next}
|
|
464
|
+
<p class="mb-3 text-xs text-muted">
|
|
465
|
+
{ladder.next.more} more and the discount is {ladder.next.discount_pct}%.
|
|
459
466
|
</p>
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
467
|
+
{/if}
|
|
468
|
+
|
|
469
|
+
<div class="mb-4 border-t border-line pt-3">
|
|
470
|
+
{#if total === null}
|
|
471
|
+
<p class="flex items-center gap-2 text-sm text-muted">
|
|
472
|
+
{#if quoting}
|
|
473
|
+
<Spinner size={14} />
|
|
474
|
+
Pricing your selection
|
|
475
|
+
{:else}
|
|
476
|
+
Waiting for a price.
|
|
477
|
+
{/if}
|
|
463
478
|
</p>
|
|
464
|
-
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
479
|
+
{:else}
|
|
480
|
+
<p class="flex items-baseline justify-between gap-2">
|
|
481
|
+
<span class="text-sm text-muted">Total</span>
|
|
482
|
+
<span class="font-mono text-xl font-semibold text-fg">
|
|
483
|
+
{formatCents(total, currency)}<span class="text-sm font-normal text-muted"
|
|
484
|
+
>{perLabel}</span
|
|
485
|
+
>
|
|
486
|
+
</span>
|
|
468
487
|
</p>
|
|
488
|
+
{#if quoted && quoted.volume_discount_cents > 0}
|
|
489
|
+
<p class="mt-1 text-end text-xs text-muted">
|
|
490
|
+
{formatCents(quoted.volume_discount_cents, currency)} off the list price.
|
|
491
|
+
</p>
|
|
492
|
+
{/if}
|
|
493
|
+
{#if quoted && quoted.community_discount_cents > 0}
|
|
494
|
+
<p class="mt-1 text-end text-xs text-success">
|
|
495
|
+
Community discount {quoted.community_discount_pct}% applied.
|
|
496
|
+
</p>
|
|
497
|
+
{/if}
|
|
469
498
|
{/if}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
{/if}
|
|
499
|
+
</div>
|
|
500
|
+
{/if}
|
|
473
501
|
|
|
474
|
-
|
|
475
|
-
|
|
502
|
+
{#if notice}{@render notice()}{/if}
|
|
503
|
+
{#if actions}{@render actions({ skus: [...selected], total })}{/if}
|
|
504
|
+
{/if}
|
|
476
505
|
</aside>
|
|
477
506
|
</div>
|
|
@@ -38,6 +38,25 @@ interface Props {
|
|
|
38
38
|
}]>;
|
|
39
39
|
/** Anything the host wants between the total and the actions. */
|
|
40
40
|
notice?: Snippet;
|
|
41
|
+
/**
|
|
42
|
+
* The host's own summary, drawn in place of everything inside the aside:
|
|
43
|
+
* the title line, `error`, the list, the ladder, the total, `notice` and
|
|
44
|
+
* `actions`. For a host that prices more than the selection, such as a
|
|
45
|
+
* plan change or a discount code. The aside keeps its id, its stickiness
|
|
46
|
+
* and the bar that jumps to it on a narrow screen. `remove` takes one
|
|
47
|
+
* selected capability out and reports it through `onchange`, as unticking
|
|
48
|
+
* its card does. A capability that is not selected is left alone. `total`
|
|
49
|
+
* is null both while a price is in flight and before one is asked for, so
|
|
50
|
+
* a host that tells the two apart tracks its own request, as it does for
|
|
51
|
+
* `quoting`.
|
|
52
|
+
*/
|
|
53
|
+
summary?: Snippet<[{
|
|
54
|
+
skus: string[];
|
|
55
|
+
total: number | null;
|
|
56
|
+
remove: (sku: string) => void;
|
|
57
|
+
}]>;
|
|
58
|
+
/** The panel's name: the aside's accessible name, also printed atop the default summary. */
|
|
59
|
+
heading?: string;
|
|
41
60
|
/**
|
|
42
61
|
* How far below the top of the window the summary sticks on a wide
|
|
43
62
|
* 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.0";
|
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.0';
|
package/package.json
CHANGED