@lyeve-labs/ui-kit 0.34.0 → 0.35.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/README.md
CHANGED
|
@@ -28,7 +28,7 @@ No config file, no theme provider, no setup ceremony.
|
|
|
28
28
|
|
|
29
29
|
## What's in the box
|
|
30
30
|
|
|
31
|
-
- **
|
|
31
|
+
- **72 components:** buttons, inputs, modals, drawers, tabs, tables, toasts, the works.
|
|
32
32
|
- **Two themes:** Soft Dark (default) and Soft Light, switched by a single `data-theme` attribute on `<html>`.
|
|
33
33
|
- **One CSS file:** `@lyeve-labs/ui-kit/styles.css` declares every token. The rest is just Tailwind.
|
|
34
34
|
- **Svelte 5 native:** built on runes and snippets, fully typed end-to-end.
|
|
@@ -37,13 +37,13 @@ No config file, no theme provider, no setup ceremony.
|
|
|
37
37
|
## Component list
|
|
38
38
|
|
|
39
39
|
<details>
|
|
40
|
-
<summary>
|
|
40
|
+
<summary>72 components, organized by purpose</summary>
|
|
41
41
|
|
|
42
42
|
**Layout and structure**
|
|
43
43
|
Card, Panel, AppShell, AuthShell, PageShell, PageHeader, SectionHeading, Divider, Accordion, AccordionItem, Collapsible, Table, DescriptionList, Toolbar, TreeView
|
|
44
44
|
|
|
45
45
|
**Forms and inputs**
|
|
46
|
-
Button, ButtonGroup, Input, PasswordInput, CopyField, Textarea, NumberInput, SearchInput, FileInput, Label, Field, FormMessage, SegmentedControl, Select, MultiSelect, Autocomplete, DatePicker, TimePicker, DateTimePicker, Checkbox, CheckboxGroup, Radio, RadioGroup, Toggle
|
|
46
|
+
Button, ButtonGroup, Input, PasswordInput, CopyField, Textarea, NumberInput, SearchInput, FileInput, Label, Field, FormMessage, SegmentedControl, FilterChips, Select, MultiSelect, Autocomplete, DatePicker, TimePicker, DateTimePicker, Checkbox, CheckboxGroup, Radio, RadioGroup, Toggle
|
|
47
47
|
|
|
48
48
|
**Navigation**
|
|
49
49
|
Breadcrumb, Tabs, Pagination, StepIndicator, Dropdown, SidebarNav, AccountMenu
|
|
@@ -253,7 +253,7 @@ This repo is a single-purpose component library. Nothing but `src/lib/`.
|
|
|
253
253
|
```
|
|
254
254
|
src/
|
|
255
255
|
└── lib/ # → published as @lyeve-labs/ui-kit
|
|
256
|
-
├── components/ #
|
|
256
|
+
├── components/ # 72 .svelte files
|
|
257
257
|
├── stores/ # toast.svelte.ts
|
|
258
258
|
├── styles/ # theme.css (the one stylesheet)
|
|
259
259
|
├── utils/ # cn.ts, theme.ts
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/** One chip. */
|
|
5
|
+
export interface FilterChip<T extends string = string> {
|
|
6
|
+
/** The chosen value, and the key the chip is rendered under. */
|
|
7
|
+
value: T;
|
|
8
|
+
/** What the reader sees. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** How many rows this chip leaves on screen, drawn small beside the label. */
|
|
11
|
+
count?: number;
|
|
12
|
+
/** A lucide component before the label. */
|
|
13
|
+
icon?: Component<{ size?: number; class?: string }>;
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script lang="ts" generics="T extends string">
|
|
18
|
+
/**
|
|
19
|
+
* One filter out of many: a row of chips that wraps.
|
|
20
|
+
*
|
|
21
|
+
* A SegmentedControl holds two to four short options in one strip. A
|
|
22
|
+
* catalog filtered by a dozen categories does not fit one: the strip either
|
|
23
|
+
* scrolls, hiding categories past the edge, or squeezes each label onto two
|
|
24
|
+
* lines inside its segment. Chips wrap instead, so every category is in
|
|
25
|
+
* sight at every width. One is always pressed.
|
|
26
|
+
*/
|
|
27
|
+
import { cn } from '../utils/cn.js';
|
|
28
|
+
import { HIT_AREA } from '../internal/touch.js';
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
/** The pressed chip's value. */
|
|
32
|
+
value: T;
|
|
33
|
+
options: FilterChip<T>[];
|
|
34
|
+
/** Names the group for assistive technology. The chips name themselves. */
|
|
35
|
+
label: string;
|
|
36
|
+
/** Read after the count, so "3" is heard as "3 on sale". */
|
|
37
|
+
countLabel?: string;
|
|
38
|
+
class?: string;
|
|
39
|
+
onchange?: (value: T) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
value = $bindable(),
|
|
44
|
+
options,
|
|
45
|
+
label,
|
|
46
|
+
countLabel = '',
|
|
47
|
+
class: klass = '',
|
|
48
|
+
onchange = undefined,
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
function pick(next: T): void {
|
|
52
|
+
if (next === value) return;
|
|
53
|
+
value = next;
|
|
54
|
+
onchange?.(next);
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div class={cn('flex flex-wrap gap-1.5', klass)} role="group" aria-label={label}>
|
|
59
|
+
{#each options as chip (chip.value)}
|
|
60
|
+
{@const pressed = chip.value === value}
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
aria-pressed={pressed}
|
|
64
|
+
class={cn(
|
|
65
|
+
HIT_AREA,
|
|
66
|
+
'inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors',
|
|
67
|
+
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand',
|
|
68
|
+
pressed
|
|
69
|
+
? 'border-brand bg-brand/10 text-fg'
|
|
70
|
+
: 'border-line text-muted hover:border-line-strong hover:text-fg',
|
|
71
|
+
)}
|
|
72
|
+
onclick={() => pick(chip.value)}
|
|
73
|
+
>
|
|
74
|
+
{#if chip.icon}
|
|
75
|
+
{@const Icon = chip.icon}
|
|
76
|
+
<Icon size={12} class="shrink-0" />
|
|
77
|
+
{/if}
|
|
78
|
+
{chip.label}
|
|
79
|
+
{#if chip.count !== undefined}
|
|
80
|
+
<span class="font-mono text-[10px] text-faint"
|
|
81
|
+
>{chip.count}{#if countLabel}<span class="sr-only"> {countLabel}</span>{/if}</span
|
|
82
|
+
>
|
|
83
|
+
{/if}
|
|
84
|
+
</button>
|
|
85
|
+
{/each}
|
|
86
|
+
</div>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
/** One chip. */
|
|
3
|
+
export interface FilterChip<T extends string = string> {
|
|
4
|
+
/** The chosen value, and the key the chip is rendered under. */
|
|
5
|
+
value: T;
|
|
6
|
+
/** What the reader sees. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** How many rows this chip leaves on screen, drawn small beside the label. */
|
|
9
|
+
count?: number;
|
|
10
|
+
/** A lucide component before the label. */
|
|
11
|
+
icon?: Component<{
|
|
12
|
+
size?: number;
|
|
13
|
+
class?: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
declare function $$render<T extends string>(): {
|
|
17
|
+
props: {
|
|
18
|
+
/** The pressed chip's value. */
|
|
19
|
+
value: T;
|
|
20
|
+
options: FilterChip<T>[];
|
|
21
|
+
/** Names the group for assistive technology. The chips name themselves. */
|
|
22
|
+
label: string;
|
|
23
|
+
/** Read after the count, so "3" is heard as "3 on sale". */
|
|
24
|
+
countLabel?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
onchange?: (value: T) => void;
|
|
27
|
+
};
|
|
28
|
+
exports: {};
|
|
29
|
+
bindings: "value";
|
|
30
|
+
slots: {};
|
|
31
|
+
events: {};
|
|
32
|
+
};
|
|
33
|
+
declare class __sveltets_Render<T extends string> {
|
|
34
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
35
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
36
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
37
|
+
bindings(): "value";
|
|
38
|
+
exports(): {};
|
|
39
|
+
}
|
|
40
|
+
interface $$IsomorphicComponent {
|
|
41
|
+
new <T extends string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
42
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
43
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
44
|
+
<T extends string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
45
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
46
|
+
}
|
|
47
|
+
declare const FilterChips: $$IsomorphicComponent;
|
|
48
|
+
type FilterChips<T extends string> = InstanceType<typeof FilterChips<T>>;
|
|
49
|
+
export default FilterChips;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { X } from '@lucide/svelte';
|
|
16
16
|
import Badge from './Badge.svelte';
|
|
17
17
|
import Button from './Button.svelte';
|
|
18
|
+
import FilterChips from './FilterChips.svelte';
|
|
18
19
|
import SearchInput from './SearchInput.svelte';
|
|
19
20
|
import Spinner from './Spinner.svelte';
|
|
20
21
|
import * as motion from '../motion.js';
|
|
@@ -161,7 +162,11 @@
|
|
|
161
162
|
);
|
|
162
163
|
|
|
163
164
|
const total = $derived(
|
|
164
|
-
quoted
|
|
165
|
+
quoted
|
|
166
|
+
? period === 'annual'
|
|
167
|
+
? (quoted.annual_total_cents ?? null)
|
|
168
|
+
: quoted.total_cents
|
|
169
|
+
: null,
|
|
165
170
|
);
|
|
166
171
|
|
|
167
172
|
const ladder = $derived(
|
|
@@ -229,26 +234,12 @@
|
|
|
229
234
|
{#if categories.length > 2}
|
|
230
235
|
<!-- Chips that wrap rather than a strip that scrolls: every category is
|
|
231
236
|
in sight at every width, and none hides past the edge. -->
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
HIT_AREA,
|
|
239
|
-
'inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors',
|
|
240
|
-
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand',
|
|
241
|
-
category === c.value
|
|
242
|
-
? 'border-brand bg-brand/10 text-fg'
|
|
243
|
-
: 'border-line text-muted hover:border-line-strong hover:text-fg',
|
|
244
|
-
)}
|
|
245
|
-
onclick={() => (category = c.value)}
|
|
246
|
-
>
|
|
247
|
-
{c.label}
|
|
248
|
-
<span class="font-mono text-[10px] text-faint">{c.count}<span class="sr-only"> on sale</span></span>
|
|
249
|
-
</button>
|
|
250
|
-
{/each}
|
|
251
|
-
</div>
|
|
237
|
+
<FilterChips
|
|
238
|
+
label="Category"
|
|
239
|
+
countLabel="on sale"
|
|
240
|
+
options={categories}
|
|
241
|
+
bind:value={category}
|
|
242
|
+
/>
|
|
252
243
|
{/if}
|
|
253
244
|
|
|
254
245
|
{#if shown.length === 0}
|
|
@@ -278,7 +269,9 @@
|
|
|
278
269
|
aria-describedby="{id}-price {id}-blurb{tagged ? ` ${id}-tags` : ''}"
|
|
279
270
|
onchange={() => toggle(plugin.sku)}
|
|
280
271
|
/>
|
|
281
|
-
<span
|
|
272
|
+
<span
|
|
273
|
+
class={cn(choiceWrap('card', on, owned), CHOICE_FOCUS, 'h-full flex-col gap-2 p-4')}
|
|
274
|
+
>
|
|
282
275
|
<span class="flex w-full items-start justify-between gap-3">
|
|
283
276
|
<span class="flex min-w-0 items-start gap-2.5">
|
|
284
277
|
<span
|
|
@@ -289,15 +282,29 @@
|
|
|
289
282
|
aria-hidden="true"
|
|
290
283
|
>
|
|
291
284
|
{#if on}
|
|
292
|
-
<svg
|
|
285
|
+
<svg
|
|
286
|
+
viewBox="0 0 10 8"
|
|
287
|
+
class="size-2.5"
|
|
288
|
+
fill="none"
|
|
289
|
+
stroke="currentColor"
|
|
290
|
+
stroke-width="1.5"
|
|
291
|
+
stroke-linecap="round"
|
|
292
|
+
stroke-linejoin="round"><path d={CHOICE_MARK.check} /></svg
|
|
293
|
+
>
|
|
293
294
|
{/if}
|
|
294
295
|
</span>
|
|
295
296
|
<!-- A one-word name longer than the space beside the price breaks
|
|
296
297
|
with a hyphen rather than running under it. -->
|
|
297
|
-
<span
|
|
298
|
+
<span
|
|
299
|
+
id="{id}-name"
|
|
300
|
+
class="min-w-0 text-sm font-semibold leading-snug text-fg hyphens-auto [overflow-wrap:anywhere]"
|
|
301
|
+
>{plugin.name}</span
|
|
302
|
+
>
|
|
298
303
|
</span>
|
|
299
304
|
<span id="{id}-price" class="shrink-0 font-mono text-sm font-semibold text-fg">
|
|
300
|
-
{formatCents(plugin.price_cents, currency)}<span
|
|
305
|
+
{formatCents(plugin.price_cents, currency)}<span
|
|
306
|
+
class="text-xs font-normal text-muted">/mo</span
|
|
307
|
+
>
|
|
301
308
|
</span>
|
|
302
309
|
</span>
|
|
303
310
|
<span id="{id}-blurb" class="text-xs leading-relaxed text-muted">
|
|
@@ -305,7 +312,8 @@
|
|
|
305
312
|
</span>
|
|
306
313
|
{#if tagged}
|
|
307
314
|
<span id="{id}-tags" class="mt-auto flex flex-wrap gap-1.5 pt-1">
|
|
308
|
-
{#if plugin.category}<Badge tone="neutral" size="sm">{plugin.category}</Badge
|
|
315
|
+
{#if plugin.category}<Badge tone="neutral" size="sm">{plugin.category}</Badge
|
|
316
|
+
>{/if}
|
|
309
317
|
{#if blurb.beta}<Badge tone="violet" size="sm">Beta</Badge>{/if}
|
|
310
318
|
{#if owned}<Badge tone="success" size="sm">Owned</Badge>{/if}
|
|
311
319
|
</span>
|
|
@@ -325,8 +333,11 @@
|
|
|
325
333
|
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"
|
|
326
334
|
>
|
|
327
335
|
<span class="text-fg">
|
|
328
|
-
<span class="font-semibold">{count}</span> picked{#if total !== null}<span
|
|
329
|
-
|
|
336
|
+
<span class="font-semibold">{count}</span> picked{#if total !== null}<span
|
|
337
|
+
class="text-muted"
|
|
338
|
+
>
|
|
339
|
+
·
|
|
340
|
+
</span><span class="font-mono">{formatCents(total, currency)}{perLabel}</span>{/if}
|
|
330
341
|
</span>
|
|
331
342
|
<span class="font-semibold text-brand">Review</span>
|
|
332
343
|
</a>
|
|
@@ -395,7 +406,9 @@
|
|
|
395
406
|
<span
|
|
396
407
|
class={cn(
|
|
397
408
|
'absolute top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 transition-colors',
|
|
398
|
-
ladder.count >= r.plugins
|
|
409
|
+
ladder.count >= r.plugins
|
|
410
|
+
? 'border-brand bg-brand'
|
|
411
|
+
: 'border-line-strong bg-surface',
|
|
399
412
|
)}
|
|
400
413
|
style:left="{(r.plugins / ladderTop) * 100}%"
|
|
401
414
|
></span>
|
|
@@ -403,7 +416,10 @@
|
|
|
403
416
|
</div>
|
|
404
417
|
<div class="relative mt-2 h-4 text-[10px] text-muted">
|
|
405
418
|
{#each rungs as r (r.plugins)}
|
|
406
|
-
<span
|
|
419
|
+
<span
|
|
420
|
+
class="absolute -translate-x-1/2 whitespace-nowrap font-mono"
|
|
421
|
+
style:left="{Math.min((r.plugins / ladderTop) * 100, 92)}%"
|
|
422
|
+
>
|
|
407
423
|
{r.plugins}+ · {r.discount_pct}%
|
|
408
424
|
</span>
|
|
409
425
|
{/each}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export { default as Label } from './components/Label.svelte';
|
|
|
37
37
|
export { default as Field } from './components/Field.svelte';
|
|
38
38
|
export { default as FormMessage } from './components/FormMessage.svelte';
|
|
39
39
|
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
40
|
+
export { default as FilterChips } from './components/FilterChips.svelte';
|
|
41
|
+
export type { FilterChip } from './components/FilterChips.svelte';
|
|
40
42
|
export { default as Select } from './components/Select.svelte';
|
|
41
43
|
export { default as MultiSelect } from './components/MultiSelect.svelte';
|
|
42
44
|
export { default as Autocomplete } from './components/Autocomplete.svelte';
|
|
@@ -98,4 +100,4 @@ export { cn, type ClassValue } from './utils/cn.js';
|
|
|
98
100
|
export { getTheme, getThemePreference, nextThemePreference, resolveTheme, setTheme, setThemePreference, systemTheme, themeBootScript, watchThemePreference, toggleTheme, watchSystemTheme, THEME_PREFERENCES, type Theme, type ThemePreference, } from './utils/theme.js';
|
|
99
101
|
export * as motion from './motion.js';
|
|
100
102
|
export type { Rung as MotionRung, Curve as MotionCurve } from './motion.js';
|
|
101
|
-
export declare const VERSION = "0.
|
|
103
|
+
export declare const VERSION = "0.35.0";
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ export { default as Label } from './components/Label.svelte';
|
|
|
38
38
|
export { default as Field } from './components/Field.svelte';
|
|
39
39
|
export { default as FormMessage } from './components/FormMessage.svelte';
|
|
40
40
|
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
41
|
+
export { default as FilterChips } from './components/FilterChips.svelte';
|
|
41
42
|
export { default as Select } from './components/Select.svelte';
|
|
42
43
|
export { default as MultiSelect } from './components/MultiSelect.svelte';
|
|
43
44
|
export { default as Autocomplete } from './components/Autocomplete.svelte';
|
|
@@ -107,4 +108,4 @@ export * as motion from './motion.js';
|
|
|
107
108
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
108
109
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
109
110
|
// this line. The build and the test suite fail when the two disagree.
|
|
110
|
-
export const VERSION = '0.
|
|
111
|
+
export const VERSION = '0.35.0';
|
package/package.json
CHANGED