@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,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
3
|
+
import Button from './Button.svelte';
|
|
4
|
+
import Popover from './Popover.svelte';
|
|
5
|
+
import OptionList from './OptionList.svelte';
|
|
6
|
+
import type { Option } from '../types.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
options = [],
|
|
10
|
+
loadOptions,
|
|
11
|
+
value = $bindable(),
|
|
12
|
+
label,
|
|
13
|
+
ariaLabel,
|
|
14
|
+
placeholder = 'Type to search…',
|
|
15
|
+
allowCustom = false,
|
|
16
|
+
disabled = false,
|
|
17
|
+
error,
|
|
18
|
+
debounce = 200
|
|
19
|
+
}: {
|
|
20
|
+
options?: Option[];
|
|
21
|
+
/** Async source (server search). Receives the query and an AbortSignal for stale requests. */
|
|
22
|
+
loadOptions?: (query: string, signal: AbortSignal) => Promise<Option[]>;
|
|
23
|
+
value?: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Accessible name when no visible `label` is shown. */
|
|
26
|
+
ariaLabel?: string;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
/** Accept free text as the value when nothing is picked. */
|
|
29
|
+
allowCustom?: boolean;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
error?: string;
|
|
32
|
+
debounce?: number;
|
|
33
|
+
} = $props();
|
|
34
|
+
|
|
35
|
+
const id = $props.id();
|
|
36
|
+
let open = $state(false);
|
|
37
|
+
let text = $state('');
|
|
38
|
+
let dirty = $state(false);
|
|
39
|
+
let active = $state(0);
|
|
40
|
+
let items = $state<Option[]>([]);
|
|
41
|
+
let loading = $state(false);
|
|
42
|
+
let input: HTMLInputElement;
|
|
43
|
+
const known = new SvelteMap<string, string>();
|
|
44
|
+
|
|
45
|
+
const labelOf = (v: string) => known.get(v) ?? v;
|
|
46
|
+
$effect(() => { for (const o of options) known.set(String(o.value), o.label); });
|
|
47
|
+
$effect(() => { if (!dirty) text = value ? labelOf(value) : ''; });
|
|
48
|
+
|
|
49
|
+
const shown = $derived(
|
|
50
|
+
loadOptions ? items : dirty ? options.filter((o) => o.label.toLowerCase().includes(text.trim().toLowerCase())) : options
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
54
|
+
let ctrl: AbortController | undefined;
|
|
55
|
+
/** A typed query has not produced results yet (debouncing or in flight): the list on screen is stale. */
|
|
56
|
+
let pending = $state(false);
|
|
57
|
+
$effect(() => () => { clearTimeout(timer); ctrl?.abort(); });
|
|
58
|
+
|
|
59
|
+
async function run(q: string) {
|
|
60
|
+
ctrl?.abort();
|
|
61
|
+
const c = (ctrl = new AbortController());
|
|
62
|
+
loading = true;
|
|
63
|
+
try {
|
|
64
|
+
const r = await loadOptions!(q, c.signal);
|
|
65
|
+
if (c.signal.aborted) return;
|
|
66
|
+
items = r;
|
|
67
|
+
for (const o of r) known.set(String(o.value), o.label);
|
|
68
|
+
} catch {
|
|
69
|
+
if (!c.signal.aborted) items = [];
|
|
70
|
+
} finally {
|
|
71
|
+
if (!c.signal.aborted) { loading = false; pending = false; }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function onInput() {
|
|
76
|
+
dirty = true; open = true; active = 0;
|
|
77
|
+
if (loadOptions) { pending = true; ctrl?.abort(); clearTimeout(timer); timer = setTimeout(() => run(text.trim()), debounce); }
|
|
78
|
+
}
|
|
79
|
+
function openList() {
|
|
80
|
+
if (disabled) return;
|
|
81
|
+
open = true;
|
|
82
|
+
if (loadOptions && !dirty) run('');
|
|
83
|
+
}
|
|
84
|
+
function choose(o: Option) {
|
|
85
|
+
value = String(o.value); known.set(value, o.label);
|
|
86
|
+
text = o.label; dirty = false; open = false;
|
|
87
|
+
if (loadOptions) { ctrl?.abort(); pending = false; items = []; } // the next open starts from the full list
|
|
88
|
+
}
|
|
89
|
+
function settle() {
|
|
90
|
+
if (dirty) {
|
|
91
|
+
const t = text.trim();
|
|
92
|
+
if (!t) value = undefined;
|
|
93
|
+
else if (allowCustom) value = t;
|
|
94
|
+
}
|
|
95
|
+
dirty = false;
|
|
96
|
+
text = value ? labelOf(value) : '';
|
|
97
|
+
}
|
|
98
|
+
function onKey(e: KeyboardEvent) {
|
|
99
|
+
const n = shown.length;
|
|
100
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); if (!open) openList(); else active = (active + 1) % Math.max(n, 1); }
|
|
101
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); if (!open) openList(); else active = (active - 1 + n) % Math.max(n, 1); }
|
|
102
|
+
else if (e.key === 'Enter') {
|
|
103
|
+
if (pending) { e.preventDefault(); if (allowCustom) settle(); return; } // don't pick from the previous query's list
|
|
104
|
+
if (open && shown[active]) { e.preventDefault(); choose(shown[active]); }
|
|
105
|
+
else if (allowCustom) settle();
|
|
106
|
+
} else if (e.key === 'Escape' && (open || dirty)) { open = false; dirty = false; text = value ? labelOf(value) : ''; }
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<div class="tr-field">
|
|
111
|
+
{#if label}<label class="tr-label" for={id}>{label}</label>{/if}
|
|
112
|
+
<div class="tr-anchor">
|
|
113
|
+
<div class="tr-control" data-trail={!!value && !disabled} data-invalid={!!error} data-disabled={disabled}>
|
|
114
|
+
<input
|
|
115
|
+
class="tr-bare" {id} aria-label={label ? undefined : ariaLabel} bind:this={input} bind:value={text} {placeholder} {disabled} autocomplete="off"
|
|
116
|
+
role="combobox" aria-autocomplete="list" aria-expanded={open} aria-controls="{id}-list"
|
|
117
|
+
aria-activedescendant={open && shown.length ? `${id}-list-opt-${active}` : undefined}
|
|
118
|
+
onclick={openList} oninput={onInput} onkeydown={onKey} onblur={settle}
|
|
119
|
+
/>
|
|
120
|
+
{#if value && !disabled}
|
|
121
|
+
<Button variant="minimal" intent="neutral" size="sm" icon="cross" aria-label="Clear" tabindex={-1}
|
|
122
|
+
onclick={() => { value = undefined; text = ''; dirty = false; input.focus(); }} />
|
|
123
|
+
{/if}
|
|
124
|
+
</div>
|
|
125
|
+
<Popover bind:open>
|
|
126
|
+
<OptionList id="{id}-list" options={shown} {active} selected={value ? [value] : []} query={dirty ? text.trim() : ''} {loading}
|
|
127
|
+
emptyText={allowCustom ? 'Press Enter to use this text' : 'No matches'} onchoose={choose} onhover={(i) => (active = i)} />
|
|
128
|
+
</Popover>
|
|
129
|
+
</div>
|
|
130
|
+
{#if error}<div class="tr-hint" data-error="true">{error}</div>{/if}
|
|
131
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Option } from '../types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
options?: Option[];
|
|
4
|
+
/** Async source (server search). Receives the query and an AbortSignal for stale requests. */
|
|
5
|
+
loadOptions?: (query: string, signal: AbortSignal) => Promise<Option[]>;
|
|
6
|
+
value?: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Accessible name when no visible `label` is shown. */
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
/** Accept free text as the value when nothing is picked. */
|
|
12
|
+
allowCustom?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
debounce?: number;
|
|
16
|
+
};
|
|
17
|
+
declare const Combobox: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
18
|
+
type Combobox = ReturnType<typeof Combobox>;
|
|
19
|
+
export default Combobox;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Menu from './Menu.svelte';
|
|
4
|
+
import type { MenuEntry } from '../types.js';
|
|
5
|
+
|
|
6
|
+
let { items, children, label = 'Context menu' }: { items: MenuEntry[]; children: Snippet; label?: string } = $props();
|
|
7
|
+
|
|
8
|
+
let at = $state<{ x: number; y: number } | null>(null);
|
|
9
|
+
let box = $state<HTMLDivElement>();
|
|
10
|
+
let host: HTMLDivElement;
|
|
11
|
+
let opener: HTMLElement | null = null;
|
|
12
|
+
|
|
13
|
+
function openAt(x: number, y: number) { opener = document.activeElement as HTMLElement | null; at = { x, y }; }
|
|
14
|
+
function onContext(e: MouseEvent) { e.preventDefault(); openAt(e.clientX, e.clientY); }
|
|
15
|
+
function onKey(e: KeyboardEvent) {
|
|
16
|
+
if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
const r = ((e.target as HTMLElement | null) ?? host).getBoundingClientRect();
|
|
19
|
+
openAt(r.left + 16, r.top + 16);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function close() { at = null; opener?.focus(); }
|
|
23
|
+
|
|
24
|
+
// Keep the menu inside the viewport once measured
|
|
25
|
+
$effect(() => {
|
|
26
|
+
if (!at || !box) return;
|
|
27
|
+
const r = box.getBoundingClientRect();
|
|
28
|
+
if (r.right > innerWidth - 4) box.style.left = `${Math.max(4, innerWidth - r.width - 4)}px`;
|
|
29
|
+
if (r.bottom > innerHeight - 4) box.style.top = `${Math.max(4, innerHeight - r.height - 4)}px`;
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<svelte:window
|
|
34
|
+
onpointerdown={(e) => at && !box?.contains(e.target as Node) && (at = null)}
|
|
35
|
+
onkeydown={(e) => e.key === 'Escape' && at && close()}
|
|
36
|
+
onblur={() => (at = null)}
|
|
37
|
+
onscroll={() => (at = null)}
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
41
|
+
<div class="cm" bind:this={host} tabindex="-1" role="presentation" oncontextmenu={onContext} onkeydown={onKey}>{@render children()}</div>
|
|
42
|
+
{#if at}
|
|
43
|
+
<div class="pop" bind:this={box} style:left="{at.x}px" style:top="{at.y}px">
|
|
44
|
+
<Menu {items} {label} autofocus onclose={close} />
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.cm { display: contents; }
|
|
50
|
+
.pop { position: fixed; z-index: 300; background: var(--tr-surface-container-low); border: 1px solid var(--tr-outline-variant); border-radius: var(--tr-radius-md); box-shadow: var(--tr-elevation-3); }
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { MenuEntry } from '../types.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
items: MenuEntry[];
|
|
5
|
+
children: Snippet;
|
|
6
|
+
label?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const ContextMenu: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
10
|
+
export default ContextMenu;
|