@loidolt/theme-svelte 0.2.0 → 0.4.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 +21 -0
- package/dist/components/Accordion.svelte +100 -0
- package/dist/components/Accordion.svelte.d.ts +50 -0
- package/dist/components/Alert.svelte +6 -3
- package/dist/components/Alert.svelte.d.ts +3 -2
- package/dist/components/AlertDialog.svelte +130 -0
- package/dist/components/AlertDialog.svelte.d.ts +35 -0
- package/dist/components/AppShell.svelte +2 -1
- package/dist/components/AppShell.svelte.d.ts +1 -0
- package/dist/components/Badge.svelte +9 -2
- package/dist/components/Badge.svelte.d.ts +2 -1
- package/dist/components/Brand.svelte +29 -15
- package/dist/components/Brand.svelte.d.ts +8 -4
- package/dist/components/Breadcrumbs.svelte +53 -0
- package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
- package/dist/components/Button.svelte +43 -21
- package/dist/components/Button.svelte.d.ts +9 -4
- package/dist/components/Card.svelte +23 -5
- package/dist/components/Card.svelte.d.ts +11 -4
- package/dist/components/CodeBlock.svelte +55 -0
- package/dist/components/CodeBlock.svelte.d.ts +14 -0
- package/dist/components/CommentList.svelte +67 -0
- package/dist/components/CommentList.svelte.d.ts +53 -0
- package/dist/components/Dialog.svelte +25 -3
- package/dist/components/Dialog.svelte.d.ts +4 -1
- package/dist/components/Drawer.svelte +105 -0
- package/dist/components/Drawer.svelte.d.ts +31 -0
- package/dist/components/DropdownMenu.svelte +2 -2
- package/dist/components/DropdownMenu.svelte.d.ts +1 -1
- package/dist/components/EmptyState.svelte +40 -0
- package/dist/components/EmptyState.svelte.d.ts +19 -0
- package/dist/components/Fieldset.svelte +60 -0
- package/dist/components/Fieldset.svelte.d.ts +19 -0
- package/dist/components/FileInput.svelte +47 -0
- package/dist/components/FileInput.svelte.d.ts +15 -0
- package/dist/components/Filmstrip.svelte +72 -0
- package/dist/components/Filmstrip.svelte.d.ts +50 -0
- package/dist/components/FloatingBar.svelte +43 -0
- package/dist/components/FloatingBar.svelte.d.ts +16 -0
- package/dist/components/IconButton.svelte +1 -1
- package/dist/components/Input.svelte.d.ts +1 -1
- package/dist/components/ListRow.svelte +50 -0
- package/dist/components/ListRow.svelte.d.ts +18 -0
- package/dist/components/Marker.svelte +38 -0
- package/dist/components/Marker.svelte.d.ts +14 -0
- package/dist/components/NavMenu.svelte +6 -1
- package/dist/components/NavMenu.svelte.d.ts +2 -1
- package/dist/components/NumberField.svelte +12 -1
- package/dist/components/NumberField.svelte.d.ts +1 -1
- package/dist/components/Pagination.svelte +86 -0
- package/dist/components/Pagination.svelte.d.ts +22 -0
- package/dist/components/Popover.svelte +24 -3
- package/dist/components/Popover.svelte.d.ts +4 -1
- package/dist/components/RadioGroup.svelte.d.ts +2 -2
- package/dist/components/RecordStepper.svelte +58 -0
- package/dist/components/RecordStepper.svelte.d.ts +18 -0
- package/dist/components/SegmentedNav.svelte +53 -0
- package/dist/components/SegmentedNav.svelte.d.ts +22 -0
- package/dist/components/Select.svelte.d.ts +2 -2
- package/dist/components/Stat.svelte +41 -0
- package/dist/components/Stat.svelte.d.ts +17 -0
- package/dist/components/StatusDot.svelte +27 -0
- package/dist/components/StatusDot.svelte.d.ts +12 -0
- package/dist/components/SwatchGroup.svelte +69 -0
- package/dist/components/SwatchGroup.svelte.d.ts +44 -0
- package/dist/components/Switch.svelte +19 -0
- package/dist/components/Table.svelte +30 -3
- package/dist/components/Table.svelte.d.ts +13 -0
- package/dist/components/TableHeader.svelte +72 -0
- package/dist/components/TableHeader.svelte.d.ts +24 -0
- package/dist/components/Tabs.svelte +23 -5
- package/dist/components/Tabs.svelte.d.ts +4 -3
- package/dist/components/Textarea.svelte.d.ts +1 -1
- package/dist/components/ThemeToggle.svelte +133 -0
- package/dist/components/ThemeToggle.svelte.d.ts +20 -0
- package/dist/components/Thumbnail.svelte +51 -0
- package/dist/components/Thumbnail.svelte.d.ts +19 -0
- package/dist/components/ToggleGroup.svelte +107 -0
- package/dist/components/ToggleGroup.svelte.d.ts +46 -0
- package/dist/components/TooltipProvider.svelte +1 -0
- package/dist/components/Workspace.svelte +27 -6
- package/dist/components/Workspace.svelte.d.ts +12 -0
- package/dist/index.d.ts +29 -2
- package/dist/index.js +26 -1
- package/dist/media.svelte.d.ts +48 -0
- package/dist/media.svelte.js +69 -0
- package/dist/theme.svelte.d.ts +86 -0
- package/dist/theme.svelte.js +166 -0
- package/dist/toaster.svelte.d.ts +2 -0
- package/dist/toaster.svelte.js +51 -9
- package/dist/types.d.ts +21 -0
- package/package.json +10 -8
|
@@ -8,6 +8,19 @@
|
|
|
8
8
|
caption?: string;
|
|
9
9
|
/** Accessible name for the scroll region when there is no `caption`. */
|
|
10
10
|
regionLabel?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Pins the header row while the body scrolls. Needs a bounded scroll container — set
|
|
13
|
+
* `--ldt-table-height` on the wrapper, or the page scrolls instead and nothing sticks.
|
|
14
|
+
*/
|
|
15
|
+
sticky?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Rendered in place of the rows, as a single full-width cell. Pass it only when there is
|
|
18
|
+
* nothing to show — `empty={rows.length === 0 ? nothingYet : undefined}` — since the
|
|
19
|
+
* component cannot see inside `children` to count rows.
|
|
20
|
+
*/
|
|
21
|
+
empty?: Snippet;
|
|
22
|
+
/** Columns the empty cell spans. Defaults to the whole row. */
|
|
23
|
+
columns?: number;
|
|
11
24
|
/** `class` lands on the `<table>`; use `wrapperClass` for the scroll container. */
|
|
12
25
|
class?: string;
|
|
13
26
|
wrapperClass?: string;
|
|
@@ -19,6 +32,9 @@
|
|
|
19
32
|
let {
|
|
20
33
|
caption,
|
|
21
34
|
regionLabel = 'Data table',
|
|
35
|
+
sticky = false,
|
|
36
|
+
empty,
|
|
37
|
+
columns,
|
|
22
38
|
class: className,
|
|
23
39
|
wrapperClass,
|
|
24
40
|
children,
|
|
@@ -32,12 +48,23 @@
|
|
|
32
48
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
33
49
|
<div
|
|
34
50
|
bind:this={wrapperRef}
|
|
35
|
-
class={cx('ldt-table-wrap', wrapperClass)}
|
|
51
|
+
class={cx('ldt-table-wrap', sticky && 'ldt-table-wrap--sticky', wrapperClass)}
|
|
36
52
|
role="region"
|
|
37
53
|
tabindex="0"
|
|
38
54
|
aria-label={caption ?? regionLabel}
|
|
39
55
|
>
|
|
40
|
-
<table
|
|
41
|
-
|
|
56
|
+
<table
|
|
57
|
+
bind:this={ref}
|
|
58
|
+
class={cx('ldt-table', sticky && 'ldt-table--sticky', className)}
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
{#if caption}<caption class="ldt-sr-only">{caption}</caption
|
|
62
|
+
>{/if}{@render children()}{#if empty}<tbody class="ldt-table__empty">
|
|
63
|
+
<tr>
|
|
64
|
+
<!-- `colspan` defaults to the row-spanning maximum so the cell never leaves a gap
|
|
65
|
+
beside it, whatever the column count turns out to be. -->
|
|
66
|
+
<td colspan={columns ?? 1000}>{@render empty()}</td>
|
|
67
|
+
</tr>
|
|
68
|
+
</tbody>{/if}
|
|
42
69
|
</table>
|
|
43
70
|
</div>
|
|
@@ -5,6 +5,19 @@ interface Props extends HTMLTableAttributes {
|
|
|
5
5
|
caption?: string;
|
|
6
6
|
/** Accessible name for the scroll region when there is no `caption`. */
|
|
7
7
|
regionLabel?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Pins the header row while the body scrolls. Needs a bounded scroll container — set
|
|
10
|
+
* `--ldt-table-height` on the wrapper, or the page scrolls instead and nothing sticks.
|
|
11
|
+
*/
|
|
12
|
+
sticky?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Rendered in place of the rows, as a single full-width cell. Pass it only when there is
|
|
15
|
+
* nothing to show — `empty={rows.length === 0 ? nothingYet : undefined}` — since the
|
|
16
|
+
* component cannot see inside `children` to count rows.
|
|
17
|
+
*/
|
|
18
|
+
empty?: Snippet;
|
|
19
|
+
/** Columns the empty cell spans. Defaults to the whole row. */
|
|
20
|
+
columns?: number;
|
|
8
21
|
/** `class` lands on the `<table>`; use `wrapperClass` for the scroll container. */
|
|
9
22
|
class?: string;
|
|
10
23
|
wrapperClass?: string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLThAttributes } from 'svelte/elements';
|
|
4
|
+
import type { ColumnAlign, SortDirection } from '../types.js';
|
|
5
|
+
import { cx } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
// `align` is omitted from the base: the deprecated HTML attribute of that name takes
|
|
8
|
+
// physical values (`left`/`right`), and this one is logical.
|
|
9
|
+
interface Props extends Omit<HTMLThAttributes, 'children' | 'align'> {
|
|
10
|
+
/**
|
|
11
|
+
* Current sort state of this column. Drives `aria-sort` and the indicator; leave at `none`
|
|
12
|
+
* for every column that is not the sorted one, so screen readers report a single sort.
|
|
13
|
+
*/
|
|
14
|
+
sort?: SortDirection;
|
|
15
|
+
/**
|
|
16
|
+
* Makes the header a sort control. Called with the direction a press asks for: `ascending`
|
|
17
|
+
* for an unsorted or descending column, `descending` for an ascending one.
|
|
18
|
+
*/
|
|
19
|
+
onSort?: (direction: SortDirection) => void;
|
|
20
|
+
align?: ColumnAlign;
|
|
21
|
+
/** Accessible name of the sort button when the header's own text is not enough. */
|
|
22
|
+
sortLabel?: string;
|
|
23
|
+
class?: string;
|
|
24
|
+
children: Snippet;
|
|
25
|
+
ref?: HTMLTableCellElement | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
sort = 'none',
|
|
30
|
+
onSort,
|
|
31
|
+
align,
|
|
32
|
+
sortLabel,
|
|
33
|
+
class: className,
|
|
34
|
+
children,
|
|
35
|
+
ref = $bindable(null),
|
|
36
|
+
...rest
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
// Two-state cycle. A third "unsorted" step reads as a broken control far more often than it
|
|
40
|
+
// reads as a feature: the table has to be sorted *somehow*, so there is nothing to return to.
|
|
41
|
+
const next = $derived<SortDirection>(sort === 'ascending' ? 'descending' : 'ascending');
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<!--
|
|
45
|
+
A `<th>` that can sort. `aria-sort` lives on the cell (where the spec puts it) while the
|
|
46
|
+
press target is a real button inside it, so the column is reachable and announced by keyboard
|
|
47
|
+
without turning the whole cell into a click surface.
|
|
48
|
+
-->
|
|
49
|
+
<th
|
|
50
|
+
bind:this={ref}
|
|
51
|
+
scope="col"
|
|
52
|
+
class={cx('ldt-table__header', className)}
|
|
53
|
+
data-align={align}
|
|
54
|
+
aria-sort={onSort ? sort : undefined}
|
|
55
|
+
{...rest}
|
|
56
|
+
>
|
|
57
|
+
{#if onSort}
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
class="ldt-table__sort"
|
|
61
|
+
data-sort={sort}
|
|
62
|
+
aria-label={sortLabel}
|
|
63
|
+
onclick={() => onSort(next)}
|
|
64
|
+
>
|
|
65
|
+
{@render children()}
|
|
66
|
+
<!-- Decorative: `aria-sort` already carries the state to assistive tech. -->
|
|
67
|
+
<span class="ldt-table__sort-indicator" aria-hidden="true"></span>
|
|
68
|
+
</button>
|
|
69
|
+
{:else}
|
|
70
|
+
{@render children()}
|
|
71
|
+
{/if}
|
|
72
|
+
</th>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLThAttributes } from 'svelte/elements';
|
|
3
|
+
import type { ColumnAlign, SortDirection } from '../types.js';
|
|
4
|
+
interface Props extends Omit<HTMLThAttributes, 'children' | 'align'> {
|
|
5
|
+
/**
|
|
6
|
+
* Current sort state of this column. Drives `aria-sort` and the indicator; leave at `none`
|
|
7
|
+
* for every column that is not the sorted one, so screen readers report a single sort.
|
|
8
|
+
*/
|
|
9
|
+
sort?: SortDirection;
|
|
10
|
+
/**
|
|
11
|
+
* Makes the header a sort control. Called with the direction a press asks for: `ascending`
|
|
12
|
+
* for an unsorted or descending column, `descending` for an ascending one.
|
|
13
|
+
*/
|
|
14
|
+
onSort?: (direction: SortDirection) => void;
|
|
15
|
+
align?: ColumnAlign;
|
|
16
|
+
/** Accessible name of the sort button when the header's own text is not enough. */
|
|
17
|
+
sortLabel?: string;
|
|
18
|
+
class?: string;
|
|
19
|
+
children: Snippet;
|
|
20
|
+
ref?: HTMLTableCellElement | null;
|
|
21
|
+
}
|
|
22
|
+
declare const TableHeader: import("svelte").Component<Props, {}, "ref">;
|
|
23
|
+
type TableHeader = ReturnType<typeof TableHeader>;
|
|
24
|
+
export default TableHeader;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends string = string">
|
|
2
2
|
import { Tabs as TabsPrimitive } from 'bits-ui';
|
|
3
|
+
import type { TabsRootProps } from 'bits-ui';
|
|
3
4
|
import type { Snippet } from 'svelte';
|
|
4
5
|
import type { NavItem, Orientation } from '../types.js';
|
|
5
6
|
import { cx } from '../utils.js';
|
|
6
7
|
|
|
7
|
-
interface Props
|
|
8
|
+
interface Props extends Omit<
|
|
9
|
+
TabsRootProps,
|
|
10
|
+
'value' | 'onValueChange' | 'orientation' | 'children' | 'child' | 'class' | 'ref'
|
|
11
|
+
> {
|
|
8
12
|
/** Defaults to the first tab, so a panel is always rendered. */
|
|
9
13
|
value?: T;
|
|
10
14
|
tabs: NavItem<T>[];
|
|
@@ -20,8 +24,10 @@
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
let {
|
|
23
|
-
value = $bindable(),
|
|
24
27
|
tabs,
|
|
28
|
+
// Defaulted at destructure time (not in an effect) so SSR output and the first client
|
|
29
|
+
// frame already agree with what a parent binding observes.
|
|
30
|
+
value = $bindable(tabs[0]?.value),
|
|
25
31
|
label,
|
|
26
32
|
orientation = 'horizontal',
|
|
27
33
|
children,
|
|
@@ -30,13 +36,24 @@
|
|
|
30
36
|
contentClass,
|
|
31
37
|
onValueChange,
|
|
32
38
|
ref = $bindable(null),
|
|
39
|
+
...rest
|
|
33
40
|
}: Props = $props();
|
|
34
41
|
|
|
42
|
+
// Falls back to the first tab when `value` points at a tab that no longer exists,
|
|
43
|
+
// so removing the selected tab cannot blank every panel.
|
|
44
|
+
const active = $derived(
|
|
45
|
+
value !== undefined && tabs.some((tab) => tab.value === value) ? value : tabs[0]?.value
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Keep the public binding honest when a dynamic collection removes the selected tab. Effects
|
|
49
|
+
// do not run during SSR, so the server render remains side-effect free while the client and its
|
|
50
|
+
// parent converge on the fallback that is already being displayed.
|
|
35
51
|
$effect(() => {
|
|
36
|
-
if (
|
|
52
|
+
if (active !== undefined && value !== active) {
|
|
53
|
+
value = active;
|
|
54
|
+
onValueChange?.(active);
|
|
55
|
+
}
|
|
37
56
|
});
|
|
38
|
-
|
|
39
|
-
const active = $derived(value ?? tabs[0]?.value);
|
|
40
57
|
</script>
|
|
41
58
|
|
|
42
59
|
<TabsPrimitive.Root
|
|
@@ -48,6 +65,7 @@
|
|
|
48
65
|
}}
|
|
49
66
|
{orientation}
|
|
50
67
|
class={cx('ldt-tabs', className)}
|
|
68
|
+
{...rest}
|
|
51
69
|
>
|
|
52
70
|
<TabsPrimitive.List class={cx('ldt-tabs__list', listClass)} aria-label={label}
|
|
53
71
|
>{#each tabs as tab (tab.value)}<TabsPrimitive.Trigger
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { Tabs as TabsPrimitive } from 'bits-ui';
|
|
1
2
|
import type { Snippet } from 'svelte';
|
|
2
3
|
import type { NavItem, Orientation } from '../types.js';
|
|
3
4
|
declare function $$render<T extends string = string>(): {
|
|
4
|
-
props: {
|
|
5
|
+
props: Omit<TabsPrimitive.RootProps, "children" | "class" | "value" | "onValueChange" | "ref" | "child" | "orientation"> & {
|
|
5
6
|
/** Defaults to the first tab, so a panel is always rendered. */
|
|
6
7
|
value?: T;
|
|
7
8
|
tabs: NavItem<T>[];
|
|
@@ -18,7 +19,7 @@ declare function $$render<T extends string = string>(): {
|
|
|
18
19
|
ref?: HTMLElement | null;
|
|
19
20
|
};
|
|
20
21
|
exports: {};
|
|
21
|
-
bindings: "
|
|
22
|
+
bindings: "value" | "ref";
|
|
22
23
|
slots: {};
|
|
23
24
|
events: {};
|
|
24
25
|
};
|
|
@@ -26,7 +27,7 @@ declare class __sveltets_Render<T extends string = string> {
|
|
|
26
27
|
props(): ReturnType<typeof $$render<T>>['props'];
|
|
27
28
|
events(): ReturnType<typeof $$render<T>>['events'];
|
|
28
29
|
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
29
|
-
bindings(): "
|
|
30
|
+
bindings(): "value" | "ref";
|
|
30
31
|
exports(): {};
|
|
31
32
|
}
|
|
32
33
|
interface $$IsomorphicComponent {
|
|
@@ -6,6 +6,6 @@ interface Props extends Omit<HTMLTextareaAttributes, 'value'> {
|
|
|
6
6
|
class?: string;
|
|
7
7
|
ref?: HTMLTextAreaElement | null;
|
|
8
8
|
}
|
|
9
|
-
declare const Textarea: import("svelte").Component<Props, {}, "
|
|
9
|
+
declare const Textarea: import("svelte").Component<Props, {}, "value" | "ref">;
|
|
10
10
|
type Textarea = ReturnType<typeof Textarea>;
|
|
11
11
|
export default Textarea;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import type { ControlSize } from '../types.js';
|
|
4
|
+
import type { Theme, ThemePreference } from '../theme.svelte.js';
|
|
5
|
+
import { cx } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<HTMLButtonAttributes, 'children' | 'aria-label'> {
|
|
8
|
+
/** The store from `createTheme()`. */
|
|
9
|
+
theme: Theme;
|
|
10
|
+
/** Accessible name for the preference represented by the icon. */
|
|
11
|
+
label?: string;
|
|
12
|
+
lightLabel?: string;
|
|
13
|
+
darkLabel?: string;
|
|
14
|
+
systemLabel?: string;
|
|
15
|
+
/** Include "system" in the cycle. */
|
|
16
|
+
showSystem?: boolean;
|
|
17
|
+
size?: ControlSize;
|
|
18
|
+
class?: string;
|
|
19
|
+
ref?: HTMLButtonElement | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
theme,
|
|
24
|
+
label = 'Colour scheme',
|
|
25
|
+
lightLabel = 'Light',
|
|
26
|
+
darkLabel = 'Dark',
|
|
27
|
+
systemLabel = 'System',
|
|
28
|
+
showSystem = true,
|
|
29
|
+
size = 'sm',
|
|
30
|
+
class: className,
|
|
31
|
+
disabled = false,
|
|
32
|
+
type = 'button',
|
|
33
|
+
title,
|
|
34
|
+
onclick,
|
|
35
|
+
ref = $bindable(null),
|
|
36
|
+
...rest
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
const preferences = $derived<ThemePreference[]>(
|
|
40
|
+
showSystem ? ['light', 'dark', 'system'] : ['light', 'dark']
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// If the system option is hidden, represent the scheme that is actually showing and toggle
|
|
44
|
+
// away from it on the first press.
|
|
45
|
+
const activePreference = $derived<ThemePreference>(
|
|
46
|
+
!showSystem && theme.preference === 'system' ? theme.resolved : theme.preference
|
|
47
|
+
);
|
|
48
|
+
const currentIndex = $derived(preferences.indexOf(activePreference));
|
|
49
|
+
const nextPreference = $derived(preferences[(currentIndex + 1) % preferences.length]);
|
|
50
|
+
|
|
51
|
+
const preferenceLabel = (preference: ThemePreference) => {
|
|
52
|
+
if (preference === 'light') return lightLabel;
|
|
53
|
+
if (preference === 'dark') return darkLabel;
|
|
54
|
+
return systemLabel;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const currentLabel = $derived(preferenceLabel(activePreference));
|
|
58
|
+
const nextLabel = $derived(preferenceLabel(nextPreference));
|
|
59
|
+
const buttonLabel = $derived(`${label}: ${currentLabel}`);
|
|
60
|
+
const buttonTitle = $derived(title ?? `${buttonLabel} → ${nextLabel}`);
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<!-- A native button gives the three-state cycle standard keyboard behavior in one compact target. -->
|
|
64
|
+
<button
|
|
65
|
+
bind:this={ref}
|
|
66
|
+
{type}
|
|
67
|
+
{disabled}
|
|
68
|
+
aria-label={buttonLabel}
|
|
69
|
+
title={buttonTitle}
|
|
70
|
+
class={cx(
|
|
71
|
+
'ldt-button ldt-icon-button ldt-button--ghost',
|
|
72
|
+
size !== 'md' && `ldt-button--${size}`,
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
data-theme-preference={activePreference}
|
|
76
|
+
onclick={(event) => {
|
|
77
|
+
theme.preference = nextPreference;
|
|
78
|
+
onclick?.(event);
|
|
79
|
+
}}
|
|
80
|
+
{...rest}
|
|
81
|
+
>
|
|
82
|
+
{#if activePreference === 'light'}
|
|
83
|
+
<svg
|
|
84
|
+
data-theme-icon="light"
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
focusable="false"
|
|
87
|
+
width="18"
|
|
88
|
+
height="18"
|
|
89
|
+
viewBox="0 0 24 24"
|
|
90
|
+
fill="none"
|
|
91
|
+
stroke="currentColor"
|
|
92
|
+
stroke-width="1.8"
|
|
93
|
+
stroke-linecap="square"
|
|
94
|
+
>
|
|
95
|
+
<circle cx="12" cy="12" r="3.5" />
|
|
96
|
+
<path
|
|
97
|
+
d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.65 17.65l1.42 1.42M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.65 6.35l1.42-1.42"
|
|
98
|
+
/>
|
|
99
|
+
</svg>
|
|
100
|
+
{:else if activePreference === 'dark'}
|
|
101
|
+
<svg
|
|
102
|
+
data-theme-icon="dark"
|
|
103
|
+
aria-hidden="true"
|
|
104
|
+
focusable="false"
|
|
105
|
+
width="18"
|
|
106
|
+
height="18"
|
|
107
|
+
viewBox="0 0 24 24"
|
|
108
|
+
fill="none"
|
|
109
|
+
stroke="currentColor"
|
|
110
|
+
stroke-width="1.8"
|
|
111
|
+
stroke-linejoin="miter"
|
|
112
|
+
>
|
|
113
|
+
<path d="M20.4 15.4A8.5 8.5 0 0 1 8.6 3.6 8.5 8.5 0 1 0 20.4 15.4Z" />
|
|
114
|
+
</svg>
|
|
115
|
+
{:else}
|
|
116
|
+
<svg
|
|
117
|
+
data-theme-icon="system"
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
focusable="false"
|
|
120
|
+
width="18"
|
|
121
|
+
height="18"
|
|
122
|
+
viewBox="0 0 24 24"
|
|
123
|
+
fill="none"
|
|
124
|
+
stroke="currentColor"
|
|
125
|
+
stroke-width="1.8"
|
|
126
|
+
stroke-linecap="square"
|
|
127
|
+
stroke-linejoin="miter"
|
|
128
|
+
>
|
|
129
|
+
<rect x="3" y="4" width="18" height="13" />
|
|
130
|
+
<path d="M9 21h6M12 17v4" />
|
|
131
|
+
</svg>
|
|
132
|
+
{/if}
|
|
133
|
+
</button>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
2
|
+
import type { ControlSize } from '../types.js';
|
|
3
|
+
import type { Theme } from '../theme.svelte.js';
|
|
4
|
+
interface Props extends Omit<HTMLButtonAttributes, 'children' | 'aria-label'> {
|
|
5
|
+
/** The store from `createTheme()`. */
|
|
6
|
+
theme: Theme;
|
|
7
|
+
/** Accessible name for the preference represented by the icon. */
|
|
8
|
+
label?: string;
|
|
9
|
+
lightLabel?: string;
|
|
10
|
+
darkLabel?: string;
|
|
11
|
+
systemLabel?: string;
|
|
12
|
+
/** Include "system" in the cycle. */
|
|
13
|
+
showSystem?: boolean;
|
|
14
|
+
size?: ControlSize;
|
|
15
|
+
class?: string;
|
|
16
|
+
ref?: HTMLButtonElement | null;
|
|
17
|
+
}
|
|
18
|
+
declare const ThemeToggle: import("svelte").Component<Props, {}, "ref">;
|
|
19
|
+
type ThemeToggle = ReturnType<typeof ThemeToggle>;
|
|
20
|
+
export default ThemeToggle;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { cx } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
7
|
+
src?: string;
|
|
8
|
+
/** Required whenever `src` is set — decorative images should say so with `alt=""`. */
|
|
9
|
+
alt?: string;
|
|
10
|
+
/** CSS aspect-ratio value: `'4/3'`, `'1/1'`, or a number. */
|
|
11
|
+
ratio?: string | number;
|
|
12
|
+
fit?: 'contain' | 'cover';
|
|
13
|
+
/** `sunken` recesses into the page; `paper` stays white in every theme for artwork. */
|
|
14
|
+
surface?: 'sunken' | 'paper';
|
|
15
|
+
/** Alternative to `src` — inline SVG, canvas, anything that fills the frame. */
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
class?: string;
|
|
18
|
+
ref?: HTMLElement | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
src,
|
|
23
|
+
alt = '',
|
|
24
|
+
ratio = '4/3',
|
|
25
|
+
fit = 'contain',
|
|
26
|
+
surface = 'sunken',
|
|
27
|
+
children,
|
|
28
|
+
class: className,
|
|
29
|
+
ref = $bindable(null),
|
|
30
|
+
...rest
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<figure
|
|
35
|
+
bind:this={ref}
|
|
36
|
+
class={cx(
|
|
37
|
+
'ldt-thumbnail',
|
|
38
|
+
surface === 'paper' && 'ldt-thumbnail--paper',
|
|
39
|
+
fit === 'cover' && 'ldt-thumbnail--cover',
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
style={`--ldt-thumbnail-ratio: ${ratio};`}
|
|
43
|
+
{...rest}
|
|
44
|
+
>
|
|
45
|
+
{#if children}{@render children()}{:else if src}<img
|
|
46
|
+
class="ldt-thumbnail__image"
|
|
47
|
+
{src}
|
|
48
|
+
{alt}
|
|
49
|
+
loading="lazy"
|
|
50
|
+
/>{/if}
|
|
51
|
+
</figure>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
4
|
+
src?: string;
|
|
5
|
+
/** Required whenever `src` is set — decorative images should say so with `alt=""`. */
|
|
6
|
+
alt?: string;
|
|
7
|
+
/** CSS aspect-ratio value: `'4/3'`, `'1/1'`, or a number. */
|
|
8
|
+
ratio?: string | number;
|
|
9
|
+
fit?: 'contain' | 'cover';
|
|
10
|
+
/** `sunken` recesses into the page; `paper` stays white in every theme for artwork. */
|
|
11
|
+
surface?: 'sunken' | 'paper';
|
|
12
|
+
/** Alternative to `src` — inline SVG, canvas, anything that fills the frame. */
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
class?: string;
|
|
15
|
+
ref?: HTMLElement | null;
|
|
16
|
+
}
|
|
17
|
+
declare const Thumbnail: import("svelte").Component<Props, {}, "ref">;
|
|
18
|
+
type Thumbnail = ReturnType<typeof Thumbnail>;
|
|
19
|
+
export default Thumbnail;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script lang="ts" generics="T extends string = string">
|
|
2
|
+
import { ToggleGroup as ToggleGroupPrimitive } from 'bits-ui';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
+
import type { ControlSize, Option, Orientation } from '../types.js';
|
|
6
|
+
import { cx } from '../utils.js';
|
|
7
|
+
|
|
8
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
9
|
+
options: Option<T>[];
|
|
10
|
+
/** Narrowed from the DOM attribute: the Bits root this spreads onto rejects `null`. */
|
|
11
|
+
id?: string;
|
|
12
|
+
/** Lets more than one option be pressed at once. `value` is then an array. */
|
|
13
|
+
multiple?: boolean;
|
|
14
|
+
/** Pressed option, or options when `multiple`. Bindable. */
|
|
15
|
+
value?: T | T[];
|
|
16
|
+
/** Accessible name of the group. */
|
|
17
|
+
label?: string;
|
|
18
|
+
size?: ControlSize;
|
|
19
|
+
orientation?: Orientation;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Renders an option's contents — an icon, say — in place of its plain label. */
|
|
22
|
+
children?: Snippet<[Option<T>]>;
|
|
23
|
+
class?: string;
|
|
24
|
+
itemClass?: string;
|
|
25
|
+
onValueChange?: (value: T | T[]) => void;
|
|
26
|
+
ref?: HTMLDivElement | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
options,
|
|
31
|
+
multiple = false,
|
|
32
|
+
value = $bindable(),
|
|
33
|
+
label,
|
|
34
|
+
size = 'md',
|
|
35
|
+
orientation = 'horizontal',
|
|
36
|
+
disabled = false,
|
|
37
|
+
children,
|
|
38
|
+
class: className,
|
|
39
|
+
itemClass,
|
|
40
|
+
onValueChange,
|
|
41
|
+
ref = $bindable(null),
|
|
42
|
+
...rest
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
const classes = $derived(
|
|
46
|
+
cx('ldt-toggle-group', size !== 'md' && `ldt-toggle-group--${size}`, className)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// As with `Accordion`, only the Root's typing differs between the two modes.
|
|
50
|
+
const change = (next: string | string[]) => {
|
|
51
|
+
value = next as T | T[];
|
|
52
|
+
onValueChange?.(next as T | T[]);
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<!--
|
|
57
|
+
A segmented control: a small, fixed set of choices shown side by side rather than folded into a
|
|
58
|
+
Select. Single mode is a radio group in behaviour — one press replaces the last — so reach for
|
|
59
|
+
it when the options are worth showing at all times.
|
|
60
|
+
-->
|
|
61
|
+
{#snippet items()}
|
|
62
|
+
{#each options as option (option.value)}
|
|
63
|
+
<ToggleGroupPrimitive.Item
|
|
64
|
+
value={option.value}
|
|
65
|
+
disabled={option.disabled}
|
|
66
|
+
class={cx('ldt-toggle-group__item', itemClass)}
|
|
67
|
+
>{#if children}{@render children(option)}{:else}{option.label}{/if}</ToggleGroupPrimitive.Item
|
|
68
|
+
>
|
|
69
|
+
{/each}
|
|
70
|
+
{/snippet}
|
|
71
|
+
|
|
72
|
+
{#if multiple}
|
|
73
|
+
<ToggleGroupPrimitive.Root
|
|
74
|
+
bind:ref
|
|
75
|
+
type="multiple"
|
|
76
|
+
value={(value ?? []) as string[]}
|
|
77
|
+
onValueChange={change}
|
|
78
|
+
{orientation}
|
|
79
|
+
{disabled}
|
|
80
|
+
class={classes}
|
|
81
|
+
aria-label={label}
|
|
82
|
+
{...rest}
|
|
83
|
+
>
|
|
84
|
+
{@render items()}
|
|
85
|
+
</ToggleGroupPrimitive.Root>
|
|
86
|
+
{:else}
|
|
87
|
+
<!--
|
|
88
|
+
Rendered through `child` to correct the root's role. In single mode Bits marks the items
|
|
89
|
+
`role="radio"` but leaves the root at `role="group"`, and a radio must be owned by a
|
|
90
|
+
`radiogroup` — otherwise assistive tech reports loose radios with no set and no position.
|
|
91
|
+
-->
|
|
92
|
+
<ToggleGroupPrimitive.Root
|
|
93
|
+
bind:ref
|
|
94
|
+
type="single"
|
|
95
|
+
value={(value ?? '') as string}
|
|
96
|
+
onValueChange={change}
|
|
97
|
+
{orientation}
|
|
98
|
+
{disabled}
|
|
99
|
+
class={classes}
|
|
100
|
+
aria-label={label}
|
|
101
|
+
{...rest}
|
|
102
|
+
>
|
|
103
|
+
{#snippet child({ props })}
|
|
104
|
+
<div {...props} role="radiogroup">{@render items()}</div>
|
|
105
|
+
{/snippet}
|
|
106
|
+
</ToggleGroupPrimitive.Root>
|
|
107
|
+
{/if}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { ControlSize, Option, Orientation } from '../types.js';
|
|
4
|
+
declare function $$render<T extends string = string>(): {
|
|
5
|
+
props: Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
6
|
+
options: Option<T>[];
|
|
7
|
+
/** Narrowed from the DOM attribute: the Bits root this spreads onto rejects `null`. */
|
|
8
|
+
id?: string;
|
|
9
|
+
/** Lets more than one option be pressed at once. `value` is then an array. */
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
/** Pressed option, or options when `multiple`. Bindable. */
|
|
12
|
+
value?: T | T[];
|
|
13
|
+
/** Accessible name of the group. */
|
|
14
|
+
label?: string;
|
|
15
|
+
size?: ControlSize;
|
|
16
|
+
orientation?: Orientation;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** Renders an option's contents — an icon, say — in place of its plain label. */
|
|
19
|
+
children?: Snippet<[Option<T>]>;
|
|
20
|
+
class?: string;
|
|
21
|
+
itemClass?: string;
|
|
22
|
+
onValueChange?: (value: T | T[]) => void;
|
|
23
|
+
ref?: HTMLDivElement | null;
|
|
24
|
+
};
|
|
25
|
+
exports: {};
|
|
26
|
+
bindings: "value" | "ref";
|
|
27
|
+
slots: {};
|
|
28
|
+
events: {};
|
|
29
|
+
};
|
|
30
|
+
declare class __sveltets_Render<T extends string = string> {
|
|
31
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
32
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
33
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
34
|
+
bindings(): "value" | "ref";
|
|
35
|
+
exports(): {};
|
|
36
|
+
}
|
|
37
|
+
interface $$IsomorphicComponent {
|
|
38
|
+
new <T extends string = 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']>> & {
|
|
39
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
40
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
41
|
+
<T extends string = string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
42
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
43
|
+
}
|
|
44
|
+
declare const ToggleGroup: $$IsomorphicComponent;
|
|
45
|
+
type ToggleGroup<T extends string = string> = InstanceType<typeof ToggleGroup<T>>;
|
|
46
|
+
export default ToggleGroup;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
7
7
|
import { setContext, type Snippet } from 'svelte';
|
|
8
8
|
|
|
9
|
+
// Renderless: no element of its own, so there are no rest attributes to forward.
|
|
9
10
|
interface Props {
|
|
10
11
|
/** Delay before the first tooltip in the group opens. */
|
|
11
12
|
delayDuration?: number;
|