@ruckc/truss-ui 0.0.0-stage → 1.0.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/LICENSE +24 -0
- package/README.md +91 -2
- package/dist/color.d.ts +21 -0
- package/dist/color.js +32 -0
- package/dist/components/Accordion.svelte +70 -0
- package/dist/components/Accordion.svelte.d.ts +15 -0
- package/dist/components/Alert.svelte +46 -0
- package/dist/components/Alert.svelte.d.ts +15 -0
- package/dist/components/Avatar.svelte +32 -0
- package/dist/components/Avatar.svelte.d.ts +9 -0
- package/dist/components/AvatarGroup.svelte +20 -0
- package/dist/components/AvatarGroup.svelte.d.ts +11 -0
- package/dist/components/Badge.svelte +27 -0
- package/dist/components/Badge.svelte.d.ts +9 -0
- package/dist/components/Breadcrumbs.svelte +38 -0
- package/dist/components/Breadcrumbs.svelte.d.ts +12 -0
- package/dist/components/Button.svelte +119 -0
- package/dist/components/Button.svelte.d.ts +16 -0
- package/dist/components/ButtonGroup.svelte +23 -0
- package/dist/components/ButtonGroup.svelte.d.ts +9 -0
- package/dist/components/Calendar.svelte +166 -0
- package/dist/components/Calendar.svelte.d.ts +19 -0
- package/dist/components/Callout.svelte +50 -0
- package/dist/components/Callout.svelte.d.ts +14 -0
- package/dist/components/Card.svelte +50 -0
- package/dist/components/Card.svelte.d.ts +15 -0
- package/dist/components/Checkbox.svelte +37 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/ColorPicker.svelte +164 -0
- package/dist/components/ColorPicker.svelte.d.ts +14 -0
- package/dist/components/Combobox.svelte +131 -0
- package/dist/components/Combobox.svelte.d.ts +19 -0
- package/dist/components/ContextMenu.svelte +51 -0
- package/dist/components/ContextMenu.svelte.d.ts +10 -0
- package/dist/components/DataTable.svelte +657 -0
- package/dist/components/DataTable.svelte.d.ts +76 -0
- package/dist/components/DatePicker.svelte +70 -0
- package/dist/components/DatePicker.svelte.d.ts +15 -0
- package/dist/components/DateRangePicker.svelte +92 -0
- package/dist/components/DateRangePicker.svelte.d.ts +15 -0
- package/dist/components/DateTimePicker.svelte +62 -0
- package/dist/components/DateTimePicker.svelte.d.ts +19 -0
- package/dist/components/DateTimeRangePicker.svelte +167 -0
- package/dist/components/DateTimeRangePicker.svelte.d.ts +22 -0
- package/dist/components/Dialog.svelte +70 -0
- package/dist/components/Dialog.svelte.d.ts +14 -0
- package/dist/components/Divider.svelte +16 -0
- package/dist/components/Divider.svelte.d.ts +8 -0
- package/dist/components/Drawer.svelte +64 -0
- package/dist/components/Drawer.svelte.d.ts +13 -0
- package/dist/components/DurationInput.svelte +112 -0
- package/dist/components/DurationInput.svelte.d.ts +19 -0
- package/dist/components/EditableText.svelte +73 -0
- package/dist/components/EditableText.svelte.d.ts +14 -0
- package/dist/components/EmptyState.svelte +30 -0
- package/dist/components/EmptyState.svelte.d.ts +12 -0
- package/dist/components/FileUpload.svelte +143 -0
- package/dist/components/FileUpload.svelte.d.ts +18 -0
- package/dist/components/FormGroup.svelte +27 -0
- package/dist/components/FormGroup.svelte.d.ts +13 -0
- package/dist/components/Input.svelte +62 -0
- package/dist/components/Input.svelte.d.ts +15 -0
- package/dist/components/InputAddon.svelte +21 -0
- package/dist/components/InputAddon.svelte.d.ts +9 -0
- package/dist/components/InputGroup.svelte +70 -0
- package/dist/components/InputGroup.svelte.d.ts +16 -0
- package/dist/components/Menu.svelte +95 -0
- package/dist/components/Menu.svelte.d.ts +15 -0
- package/dist/components/MenuButton.svelte +45 -0
- package/dist/components/MenuButton.svelte.d.ts +16 -0
- package/dist/components/MultiSelect.svelte +129 -0
- package/dist/components/MultiSelect.svelte.d.ts +18 -0
- package/dist/components/Navbar.svelte +17 -0
- package/dist/components/Navbar.svelte.d.ts +10 -0
- package/dist/components/NumberInput.svelte +66 -0
- package/dist/components/NumberInput.svelte.d.ts +16 -0
- package/dist/components/OptionList.svelte +74 -0
- package/dist/components/OptionList.svelte.d.ts +15 -0
- package/dist/components/Popover.svelte +87 -0
- package/dist/components/Popover.svelte.d.ts +14 -0
- package/dist/components/ProgressBar.svelte +35 -0
- package/dist/components/ProgressBar.svelte.d.ts +10 -0
- package/dist/components/RadioGroup.svelte +39 -0
- package/dist/components/RadioGroup.svelte.d.ts +14 -0
- package/dist/components/Section.svelte +56 -0
- package/dist/components/Section.svelte.d.ts +15 -0
- package/dist/components/SegmentedControl.svelte +56 -0
- package/dist/components/SegmentedControl.svelte.d.ts +17 -0
- package/dist/components/Select.svelte +204 -0
- package/dist/components/Select.svelte.d.ts +41 -0
- package/dist/components/Shell.svelte +143 -0
- package/dist/components/Shell.svelte.d.ts +36 -0
- package/dist/components/Sidebar.svelte +84 -0
- package/dist/components/Sidebar.svelte.d.ts +24 -0
- package/dist/components/Skeleton.svelte +25 -0
- package/dist/components/Skeleton.svelte.d.ts +9 -0
- package/dist/components/Slider.svelte +121 -0
- package/dist/components/Slider.svelte.d.ts +16 -0
- package/dist/components/Spinner.svelte +30 -0
- package/dist/components/Spinner.svelte.d.ts +9 -0
- package/dist/components/SplitButton.svelte +113 -0
- package/dist/components/SplitButton.svelte.d.ts +14 -0
- package/dist/components/SplitPane.svelte +106 -0
- package/dist/components/SplitPane.svelte.d.ts +16 -0
- package/dist/components/Stepper.svelte +73 -0
- package/dist/components/Stepper.svelte.d.ts +26 -0
- package/dist/components/Switch.svelte +21 -0
- package/dist/components/Switch.svelte.d.ts +10 -0
- package/dist/components/Tabs.svelte +74 -0
- package/dist/components/Tabs.svelte.d.ts +18 -0
- package/dist/components/Tag.svelte +49 -0
- package/dist/components/Tag.svelte.d.ts +16 -0
- package/dist/components/TagInput.svelte +72 -0
- package/dist/components/TagInput.svelte.d.ts +17 -0
- package/dist/components/Textarea.svelte +37 -0
- package/dist/components/Textarea.svelte.d.ts +11 -0
- package/dist/components/TimeList.svelte +76 -0
- package/dist/components/TimeList.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +79 -0
- package/dist/components/TimePicker.svelte.d.ts +15 -0
- package/dist/components/TimeRangePicker.svelte +44 -0
- package/dist/components/TimeRangePicker.svelte.d.ts +17 -0
- package/dist/components/Timeline.svelte +38 -0
- package/dist/components/Timeline.svelte.d.ts +17 -0
- package/dist/components/Toaster.svelte +35 -0
- package/dist/components/Toaster.svelte.d.ts +18 -0
- package/dist/components/Tooltip.svelte +66 -0
- package/dist/components/Tooltip.svelte.d.ts +11 -0
- package/dist/components/Tree.svelte +147 -0
- package/dist/components/Tree.svelte.d.ts +16 -0
- package/dist/date.d.ts +19 -0
- package/dist/date.js +43 -0
- package/dist/duration.d.ts +11 -0
- package/dist/duration.js +44 -0
- package/dist/fonts/LICENSE-Inter.txt +93 -0
- package/dist/fonts/LICENSE-JetBrainsMono.txt +93 -0
- package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- package/dist/icons/Icon.svelte +30 -0
- package/dist/icons/Icon.svelte.d.ts +9 -0
- package/dist/icons/paths.d.ts +39 -0
- package/dist/icons/paths.js +43 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +65 -0
- package/dist/styles/base.css +71 -0
- package/dist/styles/fonts.css +21 -0
- package/dist/styles/index.css +3 -0
- package/dist/styles/tokens.css +143 -0
- package/dist/time.d.ts +7 -0
- package/dist/time.js +30 -0
- package/dist/toast.svelte.d.ts +22 -0
- package/dist/toast.svelte.js +19 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +1 -0
- package/package.json +76 -5
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { Column, ColumnFilters, SortState } from '../types.js';
|
|
3
|
+
declare function $$render<T extends Record<string, any>>(): {
|
|
4
|
+
props: {
|
|
5
|
+
columns: Column<T>[];
|
|
6
|
+
rows: T[];
|
|
7
|
+
rowKey: (row: T) => string;
|
|
8
|
+
/** Provide to render a tree-grid; first column gets indentation + expander. */
|
|
9
|
+
getChildren?: (row: T) => T[] | undefined;
|
|
10
|
+
/** Custom cell renderer, e.g. status badges. Fall back to the raw value. */
|
|
11
|
+
cell?: Snippet<[row: T, column: Column<T>]>;
|
|
12
|
+
/** Active (highlighted) row id. */
|
|
13
|
+
selected?: string;
|
|
14
|
+
/** Show checkbox column; `checked` holds the checked row ids. */
|
|
15
|
+
selectable?: boolean;
|
|
16
|
+
checked?: string[];
|
|
17
|
+
/** Show a column visibility menu. */
|
|
18
|
+
columnMenu?: boolean;
|
|
19
|
+
toolbar?: Snippet;
|
|
20
|
+
caption: string;
|
|
21
|
+
maxHeight?: string;
|
|
22
|
+
/** Page the rows with a floating pager. Turn off for one continuous (optionally virtual) list. */
|
|
23
|
+
pagination?: boolean;
|
|
24
|
+
pageSize?: number;
|
|
25
|
+
/** Render only the rows in view (fixed `rowHeight`). Needs a bounded `maxHeight`. Use with `pagination={false}`. */
|
|
26
|
+
virtual?: boolean;
|
|
27
|
+
/** px; every row is exactly this tall when `virtual`. */
|
|
28
|
+
rowHeight?: number;
|
|
29
|
+
/** Extra rows rendered above/below the viewport. */
|
|
30
|
+
overscan?: number;
|
|
31
|
+
/** Infinite scroll: more rows exist beyond `rows`. */
|
|
32
|
+
hasMore?: boolean;
|
|
33
|
+
/** Infinite scroll: a fetch is in flight (shows a spinner row). */
|
|
34
|
+
loading?: boolean;
|
|
35
|
+
/** Request more when within this many rows of the bottom. */
|
|
36
|
+
loadThreshold?: number;
|
|
37
|
+
/** Called near the end of the list. Append to `rows` (or flip `hasMore` off); it is not called again until `rows.length` changes. */
|
|
38
|
+
onloadmore?: () => void | Promise<void>;
|
|
39
|
+
/** Show a filter row; columns opt in with `filter`. */
|
|
40
|
+
/**
|
|
41
|
+
* Column filtering UI (columns opt in with `filter`): `'row'` (or `true`) adds a filter row under the header;
|
|
42
|
+
* `'menu'` has no row — filters live in each column heading's menu (funnel button, right-click, or Shift+F10);
|
|
43
|
+
* `'both'` offers both. `false` turns filtering off.
|
|
44
|
+
*/
|
|
45
|
+
filterable?: boolean | "row" | "menu" | "both";
|
|
46
|
+
/** Active filters by column key: text → string, select → string[], number → { min, max }. */
|
|
47
|
+
filters?: ColumnFilters;
|
|
48
|
+
sort?: SortState;
|
|
49
|
+
/** Your data source filters and sorts: the table only reports `filters`/`sort` (bind them) and shows `rows` as-is. */
|
|
50
|
+
remote?: boolean;
|
|
51
|
+
/** ms to debounce typed filters (default 120 client, 300 remote). */
|
|
52
|
+
filterDelay?: number;
|
|
53
|
+
empty?: string;
|
|
54
|
+
};
|
|
55
|
+
exports: {};
|
|
56
|
+
bindings: "checked" | "sort" | "selected" | "filters";
|
|
57
|
+
slots: {};
|
|
58
|
+
events: {};
|
|
59
|
+
};
|
|
60
|
+
declare class __sveltets_Render<T extends Record<string, any>> {
|
|
61
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
62
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
63
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
64
|
+
bindings(): "checked" | "sort" | "selected" | "filters";
|
|
65
|
+
exports(): {};
|
|
66
|
+
}
|
|
67
|
+
interface $$IsomorphicComponent {
|
|
68
|
+
new <T extends Record<string, any>>(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']>> & {
|
|
69
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
70
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
71
|
+
<T extends Record<string, any>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
72
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
73
|
+
}
|
|
74
|
+
declare const DataTable: $$IsomorphicComponent;
|
|
75
|
+
type DataTable<T extends Record<string, any>> = InstanceType<typeof DataTable<T>>;
|
|
76
|
+
export default DataTable;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Button from './Button.svelte';
|
|
3
|
+
import Calendar from './Calendar.svelte';
|
|
4
|
+
import Popover from './Popover.svelte';
|
|
5
|
+
import { parseDateInput } from '../date.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
value = $bindable(),
|
|
9
|
+
label,
|
|
10
|
+
ariaLabel,
|
|
11
|
+
min,
|
|
12
|
+
max,
|
|
13
|
+
placeholder = 'YYYY-MM-DD',
|
|
14
|
+
disabled = false,
|
|
15
|
+
error,
|
|
16
|
+
weekStart = 0,
|
|
17
|
+
locale = 'en-US'
|
|
18
|
+
}: {
|
|
19
|
+
value?: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
min?: string;
|
|
23
|
+
max?: string;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
error?: string;
|
|
27
|
+
weekStart?: number;
|
|
28
|
+
locale?: string;
|
|
29
|
+
} = $props();
|
|
30
|
+
|
|
31
|
+
const id = $props.id();
|
|
32
|
+
let open = $state(false);
|
|
33
|
+
let text = $state('');
|
|
34
|
+
let bad = $state(false);
|
|
35
|
+
let input: HTMLInputElement;
|
|
36
|
+
|
|
37
|
+
$effect(() => { text = value ?? ''; bad = false; });
|
|
38
|
+
|
|
39
|
+
function commit() {
|
|
40
|
+
const t = text.trim();
|
|
41
|
+
if (!t) { value = undefined; bad = false; return; }
|
|
42
|
+
const iso = parseDateInput(t);
|
|
43
|
+
if (!iso || (min && iso < min) || (max && iso > max)) { bad = true; return; }
|
|
44
|
+
bad = false;
|
|
45
|
+
value = iso;
|
|
46
|
+
text = iso;
|
|
47
|
+
}
|
|
48
|
+
const invalid = $derived(!!error || bad);
|
|
49
|
+
const msg = $derived(error?.trim() || (bad ? `Enter a date as YYYY-MM-DD${min && max ? `, ${min} to ${max}` : min ? `, on or after ${min}` : max ? `, on or before ${max}` : ''}` : undefined));
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class="tr-field">
|
|
53
|
+
{#if label}<label class="tr-label" for={id}>{label}</label>{/if}
|
|
54
|
+
<div class="tr-anchor">
|
|
55
|
+
<div class="tr-control" data-trail="true" data-invalid={invalid} data-disabled={disabled}>
|
|
56
|
+
<input
|
|
57
|
+
class="tr-bare" {id} bind:this={input} bind:value={text} {placeholder} {disabled}
|
|
58
|
+
autocomplete="off" inputmode="numeric" aria-haspopup="dialog" aria-expanded={open}
|
|
59
|
+
aria-label={label ? undefined : ariaLabel} aria-invalid={invalid || undefined} aria-describedby={msg ? `${id}-msg` : undefined}
|
|
60
|
+
onblur={commit}
|
|
61
|
+
onkeydown={(e) => { if (e.key === 'Enter') commit(); else if (e.key === 'ArrowDown' && e.altKey) open = true; }}
|
|
62
|
+
/>
|
|
63
|
+
<Button variant="minimal" intent="neutral" size="sm" icon="calendar" aria-label="Choose date" {disabled} onclick={() => (open = !open)} />
|
|
64
|
+
</div>
|
|
65
|
+
<Popover bind:open onclose={(r) => r && input.focus()}>
|
|
66
|
+
<Calendar bind:value {min} {max} {weekStart} {locale} onpick={() => { open = false; input.focus(); }} />
|
|
67
|
+
</Popover>
|
|
68
|
+
</div>
|
|
69
|
+
{#if msg}<div class="tr-hint" id="{id}-msg" data-error="true">{msg}</div>{/if}
|
|
70
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value?: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
ariaLabel?: string;
|
|
5
|
+
min?: string;
|
|
6
|
+
max?: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
weekStart?: number;
|
|
11
|
+
locale?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const DatePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
14
|
+
type DatePicker = ReturnType<typeof DatePicker>;
|
|
15
|
+
export default DatePicker;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Button from './Button.svelte';
|
|
3
|
+
import Calendar from './Calendar.svelte';
|
|
4
|
+
import Popover from './Popover.svelte';
|
|
5
|
+
import Icon from '../icons/Icon.svelte';
|
|
6
|
+
import { addDays, parseDateInput, parseISO, toISO } from '../date.js';
|
|
7
|
+
import type { DateRange } from '../types.js';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
value = $bindable({}),
|
|
11
|
+
label,
|
|
12
|
+
min,
|
|
13
|
+
max,
|
|
14
|
+
presets = true,
|
|
15
|
+
disabled = false,
|
|
16
|
+
error,
|
|
17
|
+
weekStart = 0,
|
|
18
|
+
locale = 'en-US'
|
|
19
|
+
}: {
|
|
20
|
+
value?: DateRange;
|
|
21
|
+
label?: string;
|
|
22
|
+
min?: string;
|
|
23
|
+
max?: string;
|
|
24
|
+
presets?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
error?: string;
|
|
27
|
+
weekStart?: number;
|
|
28
|
+
locale?: string;
|
|
29
|
+
} = $props();
|
|
30
|
+
|
|
31
|
+
const id = $props.id();
|
|
32
|
+
let open = $state(false);
|
|
33
|
+
let a = $state('');
|
|
34
|
+
let b = $state('');
|
|
35
|
+
let bad = $state(false);
|
|
36
|
+
let startInput: HTMLInputElement;
|
|
37
|
+
|
|
38
|
+
$effect(() => { a = value.start ?? ''; b = value.end ?? ''; bad = false; });
|
|
39
|
+
|
|
40
|
+
function commit() {
|
|
41
|
+
const s = a.trim() ? parseDateInput(a) : undefined;
|
|
42
|
+
const e = b.trim() ? parseDateInput(b) : undefined;
|
|
43
|
+
const invalid = (a.trim() && !s) || (b.trim() && !e) || (s && e && s > e) || [s, e].some((x) => x && ((min && x < min) || (max && x > max)));
|
|
44
|
+
bad = !!invalid;
|
|
45
|
+
if (!invalid) value = { start: s ?? undefined, end: e ?? undefined };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const now = new Date();
|
|
49
|
+
const iso = (d: Date) => toISO(d);
|
|
50
|
+
const list = [
|
|
51
|
+
{ label: 'Today', r: () => ({ start: iso(now), end: iso(now) }) },
|
|
52
|
+
{ label: 'Last 7 days', r: () => ({ start: iso(addDays(now, -6)), end: iso(now) }) },
|
|
53
|
+
{ label: 'Last 30 days', r: () => ({ start: iso(addDays(now, -29)), end: iso(now) }) },
|
|
54
|
+
{ label: 'This month', r: () => ({ start: iso(new Date(now.getFullYear(), now.getMonth(), 1)), end: iso(new Date(now.getFullYear(), now.getMonth() + 1, 0)) }) },
|
|
55
|
+
{ label: 'Last month', r: () => ({ start: iso(new Date(now.getFullYear(), now.getMonth() - 1, 1)), end: iso(new Date(now.getFullYear(), now.getMonth(), 0)) }) }
|
|
56
|
+
];
|
|
57
|
+
const msg = $derived(error ?? (bad ? 'Enter a valid range (start ≤ end) as YYYY-MM-DD' : undefined));
|
|
58
|
+
void parseISO;
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<div class="tr-field">
|
|
62
|
+
{#if label}<span class="tr-label" id="{id}-label">{label}</span>{/if}
|
|
63
|
+
<div class="tr-anchor">
|
|
64
|
+
<div class="tr-control" data-trail="true" data-invalid={!!msg} data-disabled={disabled} role="group" aria-labelledby={label ? `${id}-label` : undefined}>
|
|
65
|
+
<input class="tr-bare" bind:this={startInput} bind:value={a} placeholder="Start" aria-label="Start date" {disabled} autocomplete="off" onblur={commit} onkeydown={(e) => e.key === 'Enter' && commit()} />
|
|
66
|
+
<Icon name="chevron-right" />
|
|
67
|
+
<input class="tr-bare" bind:value={b} placeholder="End" aria-label="End date" {disabled} autocomplete="off" onblur={commit} onkeydown={(e) => e.key === 'Enter' && commit()} />
|
|
68
|
+
<Button variant="minimal" intent="neutral" size="sm" icon="calendar" aria-label="Choose date range" {disabled} onclick={() => (open = !open)} />
|
|
69
|
+
</div>
|
|
70
|
+
<Popover bind:open onclose={(r) => r && startInput.focus()}>
|
|
71
|
+
<div class="panel">
|
|
72
|
+
{#if presets}
|
|
73
|
+
<ul class="presets" aria-label="Presets">
|
|
74
|
+
{#each list as p}
|
|
75
|
+
<li><button type="button" onclick={() => { value = p.r(); open = false; }}>{p.label}</button></li>
|
|
76
|
+
{/each}
|
|
77
|
+
</ul>
|
|
78
|
+
{/if}
|
|
79
|
+
<Calendar mode="range" bind:range={value} {min} {max} {weekStart} {locale} onpick={() => (open = false)} />
|
|
80
|
+
</div>
|
|
81
|
+
</Popover>
|
|
82
|
+
</div>
|
|
83
|
+
{#if msg}<div class="tr-hint" data-error="true">{msg}</div>{/if}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<style>
|
|
87
|
+
.panel { display: flex; }
|
|
88
|
+
.presets { list-style: none; margin: 0; padding: 8px 4px 8px 8px; border-right: 1px solid var(--tr-outline-variant); }
|
|
89
|
+
.presets button { display: block; width: 100%; height: var(--tr-control-h); padding: 0 10px; border: 0; border-radius: var(--tr-radius-sm); background: none; color: var(--tr-on-surface); font: inherit; text-align: left; white-space: nowrap; cursor: pointer; }
|
|
90
|
+
.presets button:hover { background: var(--tr-surface-container-highest); }
|
|
91
|
+
.presets button:focus-visible { outline: none; box-shadow: var(--tr-focus-ring); }
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DateRange } from '../types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
value?: DateRange;
|
|
4
|
+
label?: string;
|
|
5
|
+
min?: string;
|
|
6
|
+
max?: string;
|
|
7
|
+
presets?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
weekStart?: number;
|
|
11
|
+
locale?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const DateRangePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
14
|
+
type DateRangePicker = ReturnType<typeof DateRangePicker>;
|
|
15
|
+
export default DateRangePicker;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import DatePicker from './DatePicker.svelte';
|
|
3
|
+
import TimePicker from './TimePicker.svelte';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
value = $bindable(),
|
|
7
|
+
label,
|
|
8
|
+
ariaLabelPrefix,
|
|
9
|
+
min,
|
|
10
|
+
max,
|
|
11
|
+
hour12 = false,
|
|
12
|
+
step = 5,
|
|
13
|
+
disabled = false,
|
|
14
|
+
error,
|
|
15
|
+
weekStart = 0,
|
|
16
|
+
locale = 'en-US'
|
|
17
|
+
}: {
|
|
18
|
+
/** Local date-time "YYYY-MM-DDTHH:MM". */
|
|
19
|
+
value?: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
/** Accessible name prefix for the inner date/time fields (defaults to `label`). */
|
|
22
|
+
ariaLabelPrefix?: string;
|
|
23
|
+
/** Date bounds "YYYY-MM-DD". */
|
|
24
|
+
min?: string;
|
|
25
|
+
max?: string;
|
|
26
|
+
hour12?: boolean;
|
|
27
|
+
step?: number;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
error?: string;
|
|
30
|
+
weekStart?: number;
|
|
31
|
+
locale?: string;
|
|
32
|
+
} = $props();
|
|
33
|
+
|
|
34
|
+
const id = $props.id();
|
|
35
|
+
const prefix = $derived(ariaLabelPrefix ?? label ?? 'Date and time');
|
|
36
|
+
const date = $derived(value?.slice(0, 10));
|
|
37
|
+
const time = $derived(value?.slice(11, 16) || undefined);
|
|
38
|
+
let pendingTime = $state<string>();
|
|
39
|
+
|
|
40
|
+
function setDate(d?: string) {
|
|
41
|
+
if (!d) { value = undefined; return; }
|
|
42
|
+
value = `${d}T${time ?? pendingTime ?? '00:00'}`;
|
|
43
|
+
pendingTime = undefined;
|
|
44
|
+
}
|
|
45
|
+
function setTime(t?: string) {
|
|
46
|
+
if (date) value = `${date}T${t ?? '00:00'}`;
|
|
47
|
+
else pendingTime = t;
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div class="tr-field">
|
|
52
|
+
{#if label}<span class="tr-label" id="{id}-label">{label}</span>{/if}
|
|
53
|
+
<div class="dt" role="group" aria-labelledby={label ? `${id}-label` : undefined}>
|
|
54
|
+
<DatePicker ariaLabel="{prefix}: date" {min} {max} {disabled} {weekStart} {locale} error={error ? ' ' : undefined} bind:value={() => date, setDate} />
|
|
55
|
+
<TimePicker ariaLabel="{prefix}: time" {hour12} {step} {disabled} error={error ? ' ' : undefined} bind:value={() => time ?? pendingTime, setTime} />
|
|
56
|
+
</div>
|
|
57
|
+
{#if error}<div class="tr-hint" data-error="true">{error}</div>{/if}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.dt { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 8px; align-items: start; }
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** Local date-time "YYYY-MM-DDTHH:MM". */
|
|
3
|
+
value?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
/** Accessible name prefix for the inner date/time fields (defaults to `label`). */
|
|
6
|
+
ariaLabelPrefix?: string;
|
|
7
|
+
/** Date bounds "YYYY-MM-DD". */
|
|
8
|
+
min?: string;
|
|
9
|
+
max?: string;
|
|
10
|
+
hour12?: boolean;
|
|
11
|
+
step?: number;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
weekStart?: number;
|
|
15
|
+
locale?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const DateTimePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
18
|
+
type DateTimePicker = ReturnType<typeof DateTimePicker>;
|
|
19
|
+
export default DateTimePicker;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Button from './Button.svelte';
|
|
3
|
+
import Calendar from './Calendar.svelte';
|
|
4
|
+
import Popover from './Popover.svelte';
|
|
5
|
+
import TimeList from './TimeList.svelte';
|
|
6
|
+
import Icon from '../icons/Icon.svelte';
|
|
7
|
+
import { addDays, joinDateTime, pad, parseDateInput, splitDateTime, toISO } from '../date.js';
|
|
8
|
+
import { formatTime, parseTime } from '../time.js';
|
|
9
|
+
import type { DateRange } from '../types.js';
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
value = $bindable({}),
|
|
13
|
+
label,
|
|
14
|
+
min,
|
|
15
|
+
max,
|
|
16
|
+
presets = true,
|
|
17
|
+
hour12 = false,
|
|
18
|
+
step = 5,
|
|
19
|
+
disabled = false,
|
|
20
|
+
error,
|
|
21
|
+
weekStart = 0,
|
|
22
|
+
locale = 'en-US'
|
|
23
|
+
}: {
|
|
24
|
+
/** Local date-times "YYYY-MM-DDTHH:MM". */
|
|
25
|
+
value?: { start?: string; end?: string };
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Date bounds "YYYY-MM-DD". */
|
|
28
|
+
min?: string;
|
|
29
|
+
max?: string;
|
|
30
|
+
/** Quick ranges (last hour, today, last 7 days…) in the popover. */
|
|
31
|
+
presets?: boolean;
|
|
32
|
+
hour12?: boolean;
|
|
33
|
+
step?: number;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
weekStart?: number;
|
|
37
|
+
locale?: string;
|
|
38
|
+
} = $props();
|
|
39
|
+
|
|
40
|
+
const id = $props.id();
|
|
41
|
+
let open = $state(false);
|
|
42
|
+
let a = $state('');
|
|
43
|
+
let b = $state('');
|
|
44
|
+
let bad = $state(false);
|
|
45
|
+
let startInput: HTMLInputElement;
|
|
46
|
+
|
|
47
|
+
// staged edits inside the popover; committed with Apply
|
|
48
|
+
let ds = $state<string>(), de = $state<string>(), ts = $state<string>(), te = $state<string>();
|
|
49
|
+
|
|
50
|
+
const fmt = (v?: string) => {
|
|
51
|
+
if (!v) return '';
|
|
52
|
+
const { date, time } = splitDateTime(v);
|
|
53
|
+
return time ? `${date} ${formatTime(time, hour12)}` : (date ?? '');
|
|
54
|
+
};
|
|
55
|
+
$effect(() => { a = fmt(value.start); b = fmt(value.end); bad = false; });
|
|
56
|
+
|
|
57
|
+
/** "2026-03-01 14:30", "2026-03-01T14:30", "2026-03-01 2:30 pm", or a bare date. */
|
|
58
|
+
function parseDT(text: string, fallbackTime: string): string | null {
|
|
59
|
+
const t = text.trim();
|
|
60
|
+
if (!t) return null;
|
|
61
|
+
const m = /^(\S+?)(?:[ T]+(.+))?$/.exec(t);
|
|
62
|
+
if (!m) return null;
|
|
63
|
+
const d = parseDateInput(m[1]);
|
|
64
|
+
const tm = m[2] ? parseTime(m[2]) : fallbackTime;
|
|
65
|
+
return d && tm ? joinDateTime(d, tm) : null;
|
|
66
|
+
}
|
|
67
|
+
const outOfBounds = (v: string) => (!!min && v.slice(0, 10) < min) || (!!max && v.slice(0, 10) > max);
|
|
68
|
+
|
|
69
|
+
function commitText() {
|
|
70
|
+
const s = a.trim() ? parseDT(a, '00:00') : undefined;
|
|
71
|
+
const e = b.trim() ? parseDT(b, '23:59') : undefined;
|
|
72
|
+
const invalid = (a.trim() && !s) || (b.trim() && !e) || (s && e && e <= s) || [s, e].some((x) => x && outOfBounds(x));
|
|
73
|
+
bad = !!invalid;
|
|
74
|
+
if (!invalid) value = { start: s ?? undefined, end: e ?? undefined };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function openPopover() {
|
|
78
|
+
({ date: ds, time: ts } = splitDateTime(value.start));
|
|
79
|
+
({ date: de, time: te } = splitDateTime(value.end));
|
|
80
|
+
open = true;
|
|
81
|
+
}
|
|
82
|
+
const toggle = () => (open ? (open = false) : openPopover());
|
|
83
|
+
|
|
84
|
+
const draftStart = $derived(ds && ts ? joinDateTime(ds, ts) : undefined);
|
|
85
|
+
const draftEnd = $derived(de && te ? joinDateTime(de, te) : undefined);
|
|
86
|
+
const draftBad = $derived(!!draftStart && !!draftEnd && draftEnd <= draftStart);
|
|
87
|
+
const canApply = $derived(!!draftStart && !!draftEnd && !draftBad);
|
|
88
|
+
|
|
89
|
+
function onRange(r: DateRange) {
|
|
90
|
+
ds = r.start; de = r.end;
|
|
91
|
+
if (r.start && !ts) ts = '00:00';
|
|
92
|
+
if (r.end && !te) te = '23:59';
|
|
93
|
+
}
|
|
94
|
+
function apply() {
|
|
95
|
+
if (!canApply) return;
|
|
96
|
+
value = { start: draftStart, end: draftEnd };
|
|
97
|
+
open = false;
|
|
98
|
+
startInput?.focus();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const now = () => new Date();
|
|
102
|
+
const dt = (d: Date) => `${toISO(d)}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
103
|
+
const list = [
|
|
104
|
+
{ label: 'Last hour', r: () => { const n = now(); return { start: dt(new Date(n.getTime() - 3600e3)), end: dt(n) }; } },
|
|
105
|
+
{ label: 'Last 24 hours', r: () => { const n = now(); return { start: dt(new Date(n.getTime() - 86400e3)), end: dt(n) }; } },
|
|
106
|
+
{ label: 'Today', r: () => ({ start: `${toISO(now())}T00:00`, end: `${toISO(now())}T23:59` }) },
|
|
107
|
+
{ label: 'Yesterday', r: () => { const y = toISO(addDays(now(), -1)); return { start: `${y}T00:00`, end: `${y}T23:59` }; } },
|
|
108
|
+
{ label: 'Last 7 days', r: () => ({ start: `${toISO(addDays(now(), -6))}T00:00`, end: `${toISO(now())}T23:59` }) },
|
|
109
|
+
{ label: 'Last 30 days', r: () => ({ start: `${toISO(addDays(now(), -29))}T00:00`, end: `${toISO(now())}T23:59` }) }
|
|
110
|
+
];
|
|
111
|
+
const msg = $derived(error?.trim() || (bad ? 'Enter a valid range (end after start), e.g. 2026-03-01 09:00' : undefined));
|
|
112
|
+
const invalid = $derived(!!error || bad);
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<div class="tr-field">
|
|
116
|
+
{#if label}<span class="tr-label" id="{id}-label">{label}</span>{/if}
|
|
117
|
+
<div class="tr-anchor">
|
|
118
|
+
<div class="tr-control" data-trail="true" data-invalid={invalid} data-disabled={disabled} role="group" aria-labelledby={label ? `${id}-label` : undefined}>
|
|
119
|
+
<input class="tr-bare" bind:this={startInput} bind:value={a} placeholder={hour12 ? 'Start YYYY-MM-DD h:mm AM' : 'Start YYYY-MM-DD HH:MM'} aria-label="Start date and time" {disabled} autocomplete="off" aria-invalid={invalid || undefined}
|
|
120
|
+
onblur={commitText} onkeydown={(e) => e.key === 'Enter' && commitText()} />
|
|
121
|
+
<Icon name="chevron-right" />
|
|
122
|
+
<input class="tr-bare" bind:value={b} placeholder={hour12 ? 'End YYYY-MM-DD h:mm AM' : 'End YYYY-MM-DD HH:MM'} aria-label="End date and time" {disabled} autocomplete="off" aria-invalid={invalid || undefined}
|
|
123
|
+
onblur={commitText} onkeydown={(e) => e.key === 'Enter' && commitText()} />
|
|
124
|
+
<Button variant="minimal" intent="neutral" size="sm" icon="calendar" aria-label="Choose date and time range" aria-haspopup="dialog" aria-expanded={open} {disabled} onclick={toggle} />
|
|
125
|
+
</div>
|
|
126
|
+
<Popover bind:open onclose={(r) => r && startInput.focus()}>
|
|
127
|
+
<div class="panel" role="dialog" aria-label="Choose date and time range">
|
|
128
|
+
<div class="main">
|
|
129
|
+
{#if presets}
|
|
130
|
+
<ul class="presets" aria-label="Presets">
|
|
131
|
+
{#each list as p}
|
|
132
|
+
<li><button type="button" onclick={() => { value = p.r(); open = false; }}>{p.label}</button></li>
|
|
133
|
+
{/each}
|
|
134
|
+
</ul>
|
|
135
|
+
{/if}
|
|
136
|
+
<Calendar mode="range" range={{ start: ds, end: de }} onrange={onRange} {min} {max} {weekStart} {locale} />
|
|
137
|
+
<div class="times">
|
|
138
|
+
<div class="tcol"><div class="th">Start time</div><TimeList bind:value={ts} {hour12} {step} label="Start time" /></div>
|
|
139
|
+
<div class="tcol"><div class="th">End time</div><TimeList bind:value={te} {hour12} {step} label="End time" /></div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="foot">
|
|
143
|
+
<span class="sum" data-bad={draftBad}>
|
|
144
|
+
{#if draftBad}End must be after start{:else if draftStart || draftEnd}{fmt(draftStart) || '…'} → {fmt(draftEnd) || '…'}{:else}Pick a start and end date{/if}
|
|
145
|
+
</span>
|
|
146
|
+
<Button variant="minimal" intent="neutral" size="sm" onclick={() => (open = false)}>Cancel</Button>
|
|
147
|
+
<Button size="sm" disabled={!canApply} onclick={apply}>Apply</Button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</Popover>
|
|
151
|
+
</div>
|
|
152
|
+
{#if msg}<div class="tr-hint" data-error="true">{msg}</div>{/if}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<style>
|
|
156
|
+
.panel { display: flex; flex-direction: column; }
|
|
157
|
+
.main { display: flex; }
|
|
158
|
+
.presets { list-style: none; margin: 0; padding: 8px 4px 8px 8px; border-right: 1px solid var(--tr-outline-variant); }
|
|
159
|
+
.presets button { display: block; width: 100%; height: var(--tr-control-h); padding: 0 10px; border: 0; border-radius: var(--tr-radius-sm); background: none; color: var(--tr-on-surface); font: inherit; text-align: left; white-space: nowrap; cursor: pointer; }
|
|
160
|
+
.presets button:hover { background: var(--tr-surface-container-highest); }
|
|
161
|
+
.presets button:focus-visible { outline: none; box-shadow: var(--tr-focus-ring); }
|
|
162
|
+
.times { display: flex; gap: 10px; padding: 8px 10px 8px 4px; border-left: 1px solid var(--tr-outline-variant); }
|
|
163
|
+
.th { margin-bottom: 4px; font-size: var(--tr-text-xs); font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--tr-on-surface-variant); }
|
|
164
|
+
.foot { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-top: 1px solid var(--tr-outline-variant); }
|
|
165
|
+
.sum { flex: 1; font: var(--tr-text-sm) var(--tr-font-mono); color: var(--tr-on-surface-variant); }
|
|
166
|
+
.sum[data-bad='true'] { color: var(--tr-danger); font-family: var(--tr-font-sans); font-weight: 600; }
|
|
167
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** Local date-times "YYYY-MM-DDTHH:MM". */
|
|
3
|
+
value?: {
|
|
4
|
+
start?: string;
|
|
5
|
+
end?: string;
|
|
6
|
+
};
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Date bounds "YYYY-MM-DD". */
|
|
9
|
+
min?: string;
|
|
10
|
+
max?: string;
|
|
11
|
+
/** Quick ranges (last hour, today, last 7 days…) in the popover. */
|
|
12
|
+
presets?: boolean;
|
|
13
|
+
hour12?: boolean;
|
|
14
|
+
step?: number;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
weekStart?: number;
|
|
18
|
+
locale?: string;
|
|
19
|
+
};
|
|
20
|
+
declare const DateTimeRangePicker: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
21
|
+
type DateTimeRangePicker = ReturnType<typeof DateTimeRangePicker>;
|
|
22
|
+
export default DateTimeRangePicker;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Button from './Button.svelte';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
open = $bindable(false),
|
|
7
|
+
title,
|
|
8
|
+
alert = false,
|
|
9
|
+
size = 'md',
|
|
10
|
+
children,
|
|
11
|
+
footer,
|
|
12
|
+
onclose
|
|
13
|
+
}: {
|
|
14
|
+
open?: boolean;
|
|
15
|
+
title: string;
|
|
16
|
+
/** Use role=alertdialog (confirmations that need a response). */
|
|
17
|
+
alert?: boolean;
|
|
18
|
+
size?: 'sm' | 'md' | 'lg';
|
|
19
|
+
children: Snippet;
|
|
20
|
+
footer?: Snippet;
|
|
21
|
+
onclose?: () => void;
|
|
22
|
+
} = $props();
|
|
23
|
+
|
|
24
|
+
let el: HTMLDialogElement;
|
|
25
|
+
const uid = $props.id();
|
|
26
|
+
|
|
27
|
+
$effect(() => {
|
|
28
|
+
if (open && !el.open) el.showModal();
|
|
29
|
+
else if (!open && el.open) el.close();
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<dialog
|
|
34
|
+
bind:this={el}
|
|
35
|
+
class="tr-dialog"
|
|
36
|
+
role={alert ? 'alertdialog' : undefined}
|
|
37
|
+
data-size={size}
|
|
38
|
+
aria-labelledby="{uid}-title"
|
|
39
|
+
onclose={() => { open = false; onclose?.(); }}
|
|
40
|
+
onclick={(e) => e.target === el && (open = false)}
|
|
41
|
+
>
|
|
42
|
+
<div class="frame">
|
|
43
|
+
<header>
|
|
44
|
+
<h2 id="{uid}-title">{title}</h2>
|
|
45
|
+
<Button variant="minimal" intent="neutral" size="sm" icon="cross" aria-label="Close dialog" onclick={() => (open = false)} />
|
|
46
|
+
</header>
|
|
47
|
+
<div class="body">{@render children()}</div>
|
|
48
|
+
{#if footer}<footer>{@render footer()}</footer>{/if}
|
|
49
|
+
</div>
|
|
50
|
+
</dialog>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.tr-dialog {
|
|
54
|
+
width: min(var(--w, 480px), calc(100vw - 32px)); max-height: calc(100vh - 64px);
|
|
55
|
+
padding: 0; border: 1px solid var(--tr-outline-variant); border-radius: var(--tr-radius-lg);
|
|
56
|
+
background: var(--tr-surface-container-low); color: var(--tr-on-surface);
|
|
57
|
+
box-shadow: var(--tr-elevation-4); overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
.tr-dialog[data-size='sm'] { --w: 360px; }
|
|
60
|
+
.tr-dialog[data-size='lg'] { --w: 720px; }
|
|
61
|
+
.tr-dialog[open] { animation: pop var(--tr-dur) var(--tr-ease); }
|
|
62
|
+
.tr-dialog::backdrop { background: var(--tr-scrim); backdrop-filter: blur(4px); }
|
|
63
|
+
.frame { display: flex; flex-direction: column; max-height: calc(100vh - 64px); }
|
|
64
|
+
/* Blueprint-style hard divisions between header / body / footer */
|
|
65
|
+
header { display: flex; align-items: center; justify-content: space-between; gap: 8px; height: 44px; padding: 0 8px 0 16px; background: var(--tr-surface-container); border-bottom: 1px solid var(--tr-outline-variant); }
|
|
66
|
+
h2 { margin: 0; font-size: var(--tr-text-lg); font-weight: 600; }
|
|
67
|
+
.body { padding: 16px; overflow: auto; }
|
|
68
|
+
footer { display: flex; justify-content: flex-end; gap: 8px; padding: 10px 16px; background: var(--tr-surface-container); border-top: 1px solid var(--tr-outline-variant); }
|
|
69
|
+
@keyframes pop { from { opacity: 0; transform: translateY(8px) scale(0.98); } }
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
title: string;
|
|
5
|
+
/** Use role=alertdialog (confirmations that need a response). */
|
|
6
|
+
alert?: boolean;
|
|
7
|
+
size?: 'sm' | 'md' | 'lg';
|
|
8
|
+
children: Snippet;
|
|
9
|
+
footer?: Snippet;
|
|
10
|
+
onclose?: () => void;
|
|
11
|
+
};
|
|
12
|
+
declare const Dialog: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
13
|
+
type Dialog = ReturnType<typeof Dialog>;
|
|
14
|
+
export default Dialog;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
let { vertical = false, children }: { vertical?: boolean; children?: Snippet } = $props();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<div class="dv" role="separator" aria-orientation={vertical ? 'vertical' : 'horizontal'} data-vertical={vertical} data-label={!!children}>
|
|
7
|
+
{#if children}<span>{@render children()}</span>{/if}
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
.dv { display: flex; align-items: center; gap: 10px; color: var(--tr-on-surface-variant); font-size: var(--tr-text-sm); font-weight: 600; }
|
|
12
|
+
.dv::before, .dv::after { content: ''; flex: 1; height: 1px; background: var(--tr-outline-variant); }
|
|
13
|
+
.dv:not([data-label='true'])::after { display: none; }
|
|
14
|
+
.dv[data-vertical='true'] { display: inline-block; align-self: stretch; width: 1px; min-height: 1em; margin: 0 6px; background: var(--tr-outline-variant); }
|
|
15
|
+
.dv[data-vertical='true']::before, .dv[data-vertical='true']::after { display: none; }
|
|
16
|
+
</style>
|