@w5-ui/svelte 0.2.1 → 0.3.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 +116 -4
- package/package.json +2 -2
- package/src/lib/components/Accordion.svelte +1 -1
- package/src/lib/components/AccordionItem.svelte +6 -7
- package/src/lib/components/ActionMenu.svelte +17 -9
- package/src/lib/components/ActivityFeed.svelte +20 -14
- package/src/lib/components/Alert.svelte +22 -4
- package/src/lib/components/AppLayout.svelte +34 -0
- package/src/lib/components/Avatar.svelte +28 -11
- package/src/lib/components/AvatarGroup.svelte +21 -4
- package/src/lib/components/Badge.svelte +22 -6
- package/src/lib/components/Banner.svelte +36 -7
- package/src/lib/components/Body.svelte +29 -0
- package/src/lib/components/BodyLg.svelte +26 -0
- package/src/lib/components/Breadcrumb.svelte +19 -12
- package/src/lib/components/Button.svelte +30 -1
- package/src/lib/components/CIDRInput.svelte +17 -9
- package/src/lib/components/Callout.svelte +29 -8
- package/src/lib/components/Caption.svelte +26 -0
- package/src/lib/components/Card.svelte +11 -3
- package/src/lib/components/CardMore.svelte +20 -0
- package/src/lib/components/CardTitle.svelte +20 -0
- package/src/lib/components/Carousel.svelte +27 -20
- package/src/lib/components/Checkbox.svelte +24 -3
- package/src/lib/components/Code.svelte +24 -0
- package/src/lib/components/CodeBlock.svelte +8 -7
- package/src/lib/components/ColorPicker.svelte +13 -13
- package/src/lib/components/ColumnToggle.svelte +10 -9
- package/src/lib/components/Combobox.svelte +31 -17
- package/src/lib/components/CommandPalette.svelte +42 -26
- package/src/lib/components/ConfirmDialog.svelte +21 -15
- package/src/lib/components/ContextMenu.svelte +12 -12
- package/src/lib/components/ContextualHelp.svelte +11 -6
- package/src/lib/components/CopyButton.svelte +2 -1
- package/src/lib/components/DatePicker.svelte +27 -23
- package/src/lib/components/DateRangePicker.svelte +46 -39
- package/src/lib/components/Delta.svelte +25 -0
- package/src/lib/components/Display1.svelte +29 -0
- package/src/lib/components/Display2.svelte +29 -0
- package/src/lib/components/Drawer.svelte +6 -5
- package/src/lib/components/DurationInput.svelte +16 -8
- package/src/lib/components/EmptyState.svelte +16 -7
- package/src/lib/components/ExpandableRow.svelte +8 -15
- package/src/lib/components/Eyebrow.svelte +25 -0
- package/src/lib/components/Field.svelte +7 -7
- package/src/lib/components/FieldStack.svelte +18 -0
- package/src/lib/components/FileUpload.svelte +8 -10
- package/src/lib/components/FilterBuilder.svelte +52 -28
- package/src/lib/components/FormActions.svelte +18 -0
- package/src/lib/components/FormRow.svelte +17 -0
- package/src/lib/components/Grid.svelte +17 -0
- package/src/lib/components/GroupedList.svelte +35 -21
- package/src/lib/components/H1.svelte +29 -0
- package/src/lib/components/H2.svelte +29 -0
- package/src/lib/components/H3.svelte +29 -0
- package/src/lib/components/H4.svelte +29 -0
- package/src/lib/components/HealthBar.svelte +6 -3
- package/src/lib/components/HoverCard.svelte +16 -5
- package/src/lib/components/IPInput.svelte +14 -8
- package/src/lib/components/IconButton.svelte +1 -2
- package/src/lib/components/InlineEdit.svelte +25 -14
- package/src/lib/components/Input.svelte +13 -4
- package/src/lib/components/InputGroup.svelte +14 -3
- package/src/lib/components/JsonViewer.svelte +7 -2
- package/src/lib/components/JsonViewerNode.svelte +38 -32
- package/src/lib/components/KVTable.svelte +10 -4
- package/src/lib/components/KanbanBoard.svelte +29 -27
- package/src/lib/components/Kbd.svelte +8 -5
- package/src/lib/components/Label.svelte +28 -0
- package/src/lib/components/LogViewer.svelte +24 -20
- package/src/lib/components/MACInput.svelte +14 -8
- package/src/lib/components/Mac.svelte +23 -0
- package/src/lib/components/Menubar.svelte +30 -13
- package/src/lib/components/Modal.svelte +15 -6
- package/src/lib/components/Mono.svelte +28 -0
- package/src/lib/components/MultiSelect.svelte +43 -23
- package/src/lib/components/Muted.svelte +24 -0
- package/src/lib/components/NameCell.svelte +35 -0
- package/src/lib/components/NotifDot.svelte +23 -0
- package/src/lib/components/NotificationPanel.svelte +44 -31
- package/src/lib/components/NumberInput.svelte +13 -12
- package/src/lib/components/OTPInput.svelte +14 -8
- package/src/lib/components/PageHeader.svelte +25 -0
- package/src/lib/components/Pagination.svelte +16 -12
- package/src/lib/components/PasswordInput.svelte +8 -7
- package/src/lib/components/Pill.svelte +34 -6
- package/src/lib/components/Popover.svelte +91 -16
- package/src/lib/components/ProgressBar.svelte +10 -7
- package/src/lib/components/RadioGroup.svelte +22 -7
- package/src/lib/components/RangeSlider.svelte +17 -12
- package/src/lib/components/RankedList.svelte +12 -18
- package/src/lib/components/ResizablePanel.svelte +25 -14
- package/src/lib/components/RowToggle.svelte +6 -4
- package/src/lib/components/SearchBox.svelte +6 -2
- package/src/lib/components/SegmentedControl.svelte +16 -2
- package/src/lib/components/Select.svelte +20 -8
- package/src/lib/components/SelectionToolbar.svelte +13 -9
- package/src/lib/components/Sidebar.svelte +32 -11
- package/src/lib/components/Signal.svelte +3 -2
- package/src/lib/components/Skeleton.svelte +21 -9
- package/src/lib/components/SkipLink.svelte +5 -2
- package/src/lib/components/Slider.svelte +16 -9
- package/src/lib/components/SortHeader.svelte +13 -4
- package/src/lib/components/SortableList.svelte +17 -18
- package/src/lib/components/Sparkline.svelte +20 -14
- package/src/lib/components/Spinner.svelte +16 -1
- package/src/lib/components/SplitButton.svelte +27 -8
- package/src/lib/components/Spoiler.svelte +8 -6
- package/src/lib/components/SrOnly.svelte +23 -0
- package/src/lib/components/StackedProgress.svelte +17 -9
- package/src/lib/components/StarRating.svelte +35 -10
- package/src/lib/components/Stat.svelte +19 -5
- package/src/lib/components/StatNumber.svelte +22 -0
- package/src/lib/components/StatusIndicator.svelte +9 -2
- package/src/lib/components/StatusRing.svelte +36 -0
- package/src/lib/components/Stepper.svelte +30 -8
- package/src/lib/components/Submeta.svelte +19 -0
- package/src/lib/components/SwitchPortGrid.svelte +20 -7
- package/src/lib/components/TablePager.svelte +22 -0
- package/src/lib/components/TableShell.svelte +19 -0
- package/src/lib/components/Tabs.svelte +16 -5
- package/src/lib/components/Tag.svelte +5 -3
- package/src/lib/components/TagInput.svelte +9 -10
- package/src/lib/components/Textarea.svelte +7 -1
- package/src/lib/components/TimePicker.svelte +14 -8
- package/src/lib/components/TimeRange.svelte +20 -22
- package/src/lib/components/Timeline.svelte +20 -9
- package/src/lib/components/Toast.svelte +24 -5
- package/src/lib/components/Toggle.svelte +10 -4
- package/src/lib/components/ToggleGroup.svelte +15 -5
- package/src/lib/components/Tooltip.svelte +20 -5
- package/src/lib/components/Topbar.svelte +108 -27
- package/src/lib/components/TopbarActions.svelte +52 -0
- package/src/lib/components/TransferList.svelte +30 -22
- package/src/lib/components/TreeItem.svelte +11 -13
- package/src/lib/components/Unit.svelte +22 -0
- package/src/lib/components/VirtualList.svelte +12 -9
- package/src/lib/index.ts +35 -0
|
@@ -22,14 +22,16 @@
|
|
|
22
22
|
label = 'Resize panels',
|
|
23
23
|
style = '',
|
|
24
24
|
first,
|
|
25
|
-
second
|
|
25
|
+
second,
|
|
26
26
|
}: Props = $props();
|
|
27
27
|
|
|
28
28
|
// svelte-ignore state_referenced_locally
|
|
29
29
|
let size = $state(defaultSize);
|
|
30
30
|
let container = $state<HTMLDivElement | undefined>(undefined);
|
|
31
31
|
|
|
32
|
-
function clamp(v: number) {
|
|
32
|
+
function clamp(v: number) {
|
|
33
|
+
return Math.min(max, Math.max(min, v));
|
|
34
|
+
}
|
|
33
35
|
|
|
34
36
|
function onMouseDown(e: MouseEvent) {
|
|
35
37
|
e.preventDefault();
|
|
@@ -37,9 +39,10 @@
|
|
|
37
39
|
function onMove(me: MouseEvent) {
|
|
38
40
|
if (!container) return;
|
|
39
41
|
const rect = container.getBoundingClientRect();
|
|
40
|
-
const pct =
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const pct =
|
|
43
|
+
orientation === 'vertical'
|
|
44
|
+
? ((me.clientX - rect.left) / rect.width) * 100
|
|
45
|
+
: ((me.clientY - rect.top) / rect.height) * 100;
|
|
43
46
|
size = clamp(Math.round(pct));
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -65,29 +68,37 @@
|
|
|
65
68
|
if (e.key === 'End') { e.preventDefault(); size = max; }
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
let firstStyle = $derived(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
let firstStyle = $derived(
|
|
72
|
+
orientation === 'vertical'
|
|
73
|
+
? `flex: 0 0 ${size}%; min-width: 0;`
|
|
74
|
+
: `flex: 0 0 ${size}%; min-height: 0;`,
|
|
75
|
+
);
|
|
71
76
|
</script>
|
|
72
77
|
|
|
73
|
-
<div
|
|
74
|
-
|
|
78
|
+
<div
|
|
79
|
+
bind:this={container}
|
|
80
|
+
data-orientation={orientation}
|
|
81
|
+
class="flex h-full w-full overflow-hidden data-[orientation=horizontal]:flex-col"
|
|
82
|
+
{style}
|
|
83
|
+
>
|
|
84
|
+
<div class="overflow-hidden" style={firstStyle}>
|
|
75
85
|
{@render first?.()}
|
|
76
86
|
</div>
|
|
77
87
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
78
88
|
<div
|
|
79
89
|
role="separator"
|
|
80
|
-
tabindex=
|
|
90
|
+
tabindex={0}
|
|
81
91
|
aria-orientation={orientation}
|
|
82
92
|
aria-valuenow={size}
|
|
83
93
|
aria-valuemin={min}
|
|
84
94
|
aria-valuemax={max}
|
|
85
95
|
aria-label={label}
|
|
86
|
-
|
|
96
|
+
data-orientation={orientation}
|
|
97
|
+
class="shrink-0 cursor-col-resize bg-row-active hover:bg-brand-05/40 data-[orientation=horizontal]:cursor-row-resize data-[orientation=vertical]:w-1 data-[orientation=horizontal]:h-1 focus-visible:bg-brand-05 focus-visible:outline-none"
|
|
87
98
|
onmousedown={onMouseDown}
|
|
88
99
|
onkeydown={onKeyDown}
|
|
89
|
-
></div>
|
|
90
|
-
<div class="
|
|
100
|
+
></div>
|
|
101
|
+
<div class="min-w-0 flex-1 overflow-hidden">
|
|
91
102
|
{@render second?.()}
|
|
92
103
|
</div>
|
|
93
104
|
</div>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import Toggle from './Toggle.svelte';
|
|
7
|
+
|
|
7
8
|
interface Props {
|
|
8
9
|
title: string;
|
|
9
10
|
description: string;
|
|
@@ -17,13 +18,14 @@
|
|
|
17
18
|
on = $bindable(false),
|
|
18
19
|
ontoggle,
|
|
19
20
|
}: Props = $props();
|
|
21
|
+
|
|
20
22
|
const descId = `dash-ui-rowtoggle-${++counter}`;
|
|
21
23
|
</script>
|
|
22
24
|
|
|
23
|
-
<div class="
|
|
24
|
-
<div class="
|
|
25
|
-
<div class="
|
|
26
|
-
<div class="
|
|
25
|
+
<div class="flex items-center gap-3.5 border-b border-border-1 py-2.5">
|
|
26
|
+
<div class="flex-1">
|
|
27
|
+
<div class="text-13 font-medium text-text-1">{title}</div>
|
|
28
|
+
<div class="mt-0.5 text-11 text-text-4" id={descId}>{description}</div>
|
|
27
29
|
</div>
|
|
28
30
|
<Toggle bind:on ariaLabel={title} ariaDescribedBy={descId} {ontoggle} />
|
|
29
31
|
</div>
|
|
@@ -18,8 +18,11 @@
|
|
|
18
18
|
}: Props = $props();
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
|
-
<div
|
|
22
|
-
|
|
21
|
+
<div
|
|
22
|
+
class="flex h-[30px] w-60 items-center gap-1.5 rounded-md border border-border-1 bg-bg-1 px-2.5 focus-within:border-brand-05"
|
|
23
|
+
role="search"
|
|
24
|
+
>
|
|
25
|
+
<span aria-hidden="true" class="inline-flex">
|
|
23
26
|
<SearchIcon size={14} style="color:#6E7079" />
|
|
24
27
|
</span>
|
|
25
28
|
<input
|
|
@@ -29,5 +32,6 @@
|
|
|
29
32
|
bind:value
|
|
30
33
|
{oninput}
|
|
31
34
|
{onchange}
|
|
35
|
+
class="min-w-0 flex-1 border-0 bg-transparent text-13 text-text-1 outline-none placeholder:text-text-4"
|
|
32
36
|
/>
|
|
33
37
|
</div>
|
|
@@ -36,9 +36,23 @@
|
|
|
36
36
|
root?.querySelector<HTMLButtonElement>('button[tabindex="0"]')?.focus();
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
const BTN_BASE =
|
|
41
|
+
'flex-shrink-0 grow-0 inline-flex h-7 cursor-pointer select-none items-center justify-center whitespace-nowrap rounded-md border-0 bg-transparent px-3 text-12 font-medium leading-none text-text-3 transition-[background-color,color] duration-100 hover:bg-row-hover hover:text-text-1 disabled:cursor-not-allowed disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-brand-05';
|
|
42
|
+
// Active button uses the elevated `--depthBg-3` so the active pill
|
|
43
|
+
// is legible on both motifs (a darker step on dark, a softer grey on light).
|
|
44
|
+
const BTN_ACTIVE =
|
|
45
|
+
'bg-bg-3 text-text-1 shadow-[0_1px_3px_rgba(0,0,0,0.18)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.4)]';
|
|
39
46
|
</script>
|
|
40
47
|
|
|
41
|
-
<div
|
|
48
|
+
<div
|
|
49
|
+
bind:this={root}
|
|
50
|
+
role="radiogroup"
|
|
51
|
+
aria-label={label}
|
|
52
|
+
class="inline-flex gap-px rounded-lg bg-bg-2 p-0.5"
|
|
53
|
+
tabindex={-1}
|
|
54
|
+
onkeydown={handleKeyDown}
|
|
55
|
+
>
|
|
42
56
|
{#each options as opt (opt.value)}
|
|
43
57
|
<button
|
|
44
58
|
type="button"
|
|
@@ -46,7 +60,7 @@
|
|
|
46
60
|
aria-checked={value === opt.value}
|
|
47
61
|
disabled={opt.disabled}
|
|
48
62
|
tabindex={value === opt.value ? 0 : -1}
|
|
49
|
-
class="
|
|
63
|
+
class="{BTN_BASE} {value === opt.value ? BTN_ACTIVE : ''}"
|
|
50
64
|
onclick={() => { value = opt.value; }}
|
|
51
65
|
>
|
|
52
66
|
{opt.label}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
let counter = 0;
|
|
3
|
-
export interface SelectOption {
|
|
3
|
+
export interface SelectOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
4
7
|
</script>
|
|
5
8
|
|
|
6
9
|
<script lang="ts">
|
|
@@ -27,7 +30,6 @@
|
|
|
27
30
|
class: className = '',
|
|
28
31
|
onchange,
|
|
29
32
|
}: Props = $props();
|
|
30
|
-
|
|
31
33
|
|
|
32
34
|
const uid = `dash-ui-sel-${++counter}`;
|
|
33
35
|
let triggerId = $derived(id ?? uid);
|
|
@@ -83,7 +85,7 @@
|
|
|
83
85
|
onDestroy(() => document.removeEventListener('mousedown', handleOutside));
|
|
84
86
|
</script>
|
|
85
87
|
|
|
86
|
-
<div class="
|
|
88
|
+
<div class="relative inline-block {className}" bind:this={wrapperEl}>
|
|
87
89
|
{#if label}<label for={triggerId} class="sr-only">{label}</label>{/if}
|
|
88
90
|
<button
|
|
89
91
|
bind:this={triggerEl}
|
|
@@ -94,24 +96,34 @@
|
|
|
94
96
|
aria-controls={listboxId}
|
|
95
97
|
aria-label={label}
|
|
96
98
|
{disabled}
|
|
97
|
-
|
|
99
|
+
type="button"
|
|
100
|
+
class="inline-flex h-[34px] cursor-pointer select-none items-center gap-1.5 whitespace-nowrap rounded-md border border-border-2 bg-bg-page pl-3 pr-2.5 text-13 leading-none text-text-1 transition-colors duration-100 hover:border-border-3 aria-expanded:border-brand-05 aria-expanded:shadow-[0_0_0_2px_rgba(0,111,255,0.2)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05 disabled:cursor-not-allowed disabled:opacity-40"
|
|
98
101
|
onclick={toggle}
|
|
99
102
|
onkeydown={onKeyDown}
|
|
100
|
-
type="button"
|
|
101
103
|
>
|
|
102
104
|
<span>{displayLabel}</span>
|
|
103
|
-
<svg
|
|
105
|
+
<svg
|
|
106
|
+
viewBox="0 0 16 16"
|
|
107
|
+
aria-hidden="true"
|
|
108
|
+
focusable="false"
|
|
109
|
+
class="h-3.5 w-3.5 shrink-0 text-text-4 transition-transform duration-100 motion-reduce:transition-none {open ? 'rotate-180' : ''}"
|
|
110
|
+
>
|
|
104
111
|
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
105
112
|
</svg>
|
|
106
113
|
</button>
|
|
107
114
|
{#if open}
|
|
108
|
-
<ul
|
|
115
|
+
<ul
|
|
116
|
+
id={listboxId}
|
|
117
|
+
role="listbox"
|
|
118
|
+
aria-label={label}
|
|
119
|
+
class="absolute left-0 top-[calc(100%+4px)] z-[9000] m-0 min-w-full list-none rounded-lg border border-border-3 bg-bg-2 p-1 shadow-[0_8px_24px_rgba(0,0,0,0.5)]"
|
|
120
|
+
>
|
|
109
121
|
{#each options as opt, idx (opt.value)}
|
|
110
122
|
<li
|
|
111
123
|
role="option"
|
|
112
124
|
aria-selected={opt.value === value}
|
|
113
125
|
data-active={idx === activeIdx ? 'true' : undefined}
|
|
114
|
-
class="
|
|
126
|
+
class="flex cursor-pointer items-center rounded-[5px] px-2.5 py-1.5 text-13 text-text-2 hover:bg-row-active hover:text-text-1 data-[active=true]:bg-row-active data-[active=true]:text-text-1 aria-selected:font-medium aria-selected:text-brand-05"
|
|
115
127
|
onmousedown={(e) => { e.preventDefault(); (() => pick(opt.value))(); }}
|
|
116
128
|
onmouseenter={() => { activeIdx = idx; }}
|
|
117
129
|
>{opt.label}</li>
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
<script lang="ts">
|
|
10
10
|
import CloseIcon from '../icons/CloseIcon.svelte';
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
interface Props {
|
|
14
13
|
count: number;
|
|
15
14
|
actions?: SelectionToolbarAction[];
|
|
@@ -23,20 +22,25 @@
|
|
|
23
22
|
actions = [],
|
|
24
23
|
onClear,
|
|
25
24
|
ariaLabel = 'Selection actions',
|
|
26
|
-
children
|
|
25
|
+
children,
|
|
27
26
|
}: Props = $props();
|
|
28
27
|
</script>
|
|
29
28
|
|
|
30
29
|
{#if count > 0}
|
|
31
|
-
<div
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
<div
|
|
31
|
+
role="toolbar"
|
|
32
|
+
aria-label={ariaLabel}
|
|
33
|
+
class="flex items-center gap-3 rounded-lg border border-brand-05/25 bg-brand-05/[0.08] px-4 py-2 text-13 text-text-2"
|
|
34
|
+
>
|
|
35
|
+
<span class="whitespace-nowrap" aria-live="polite" aria-atomic="true">
|
|
36
|
+
<strong class="text-text-1">{count}</strong> selected
|
|
34
37
|
</span>
|
|
35
|
-
<div class="
|
|
38
|
+
<div class="flex flex-1 items-center gap-2">
|
|
36
39
|
{#each actions as action}
|
|
37
40
|
<button
|
|
38
41
|
type="button"
|
|
39
|
-
|
|
42
|
+
data-danger={action.variant === 'danger' ? 'true' : undefined}
|
|
43
|
+
class="inline-flex h-7 cursor-pointer items-center rounded border border-border-2 bg-row-hover px-2.5 text-12 font-medium text-text-2 transition-colors duration-100 hover:border-border-3 hover:bg-row-active hover:text-text-1 data-[danger=true]:border-status-danger/30 data-[danger=true]:text-status-danger data-[danger=true]:hover:bg-status-danger/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
40
44
|
onclick={action.onClick}
|
|
41
45
|
>
|
|
42
46
|
{action.label}
|
|
@@ -46,9 +50,9 @@
|
|
|
46
50
|
</div>
|
|
47
51
|
<button
|
|
48
52
|
type="button"
|
|
49
|
-
class="sel-toolbar__clear icon-btn"
|
|
50
|
-
onclick={onClear}
|
|
51
53
|
aria-label="Clear selection"
|
|
54
|
+
class="inline-flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded border-0 bg-transparent text-text-4 hover:bg-row-active hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
55
|
+
onclick={onClear}
|
|
52
56
|
>
|
|
53
57
|
<CloseIcon />
|
|
54
58
|
</button>
|
|
@@ -14,34 +14,55 @@
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<script lang="ts">
|
|
17
|
-
|
|
18
17
|
interface Props {
|
|
19
18
|
sections?: SidebarSectionDef[];
|
|
20
19
|
activeId: string;
|
|
21
20
|
onchange?: (payload: string) => void;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
let { sections = [], activeId = $bindable(),
|
|
25
|
-
onchange,
|
|
26
|
-
}: Props = $props();
|
|
23
|
+
let { sections = [], activeId = $bindable(), onchange }: Props = $props();
|
|
27
24
|
</script>
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
<!--
|
|
27
|
+
Motif-aware chrome: bg via `var(--bg-page)`, border via `var(--border-1)`,
|
|
28
|
+
text via the standard `text-1/2/3/4` ramp. Active row uses `--row-active`
|
|
29
|
+
(brand-blue tint) so it stays visible in both motifs.
|
|
30
|
+
-->
|
|
31
|
+
<nav
|
|
32
|
+
class="overflow-auto border-r border-border-1 bg-bg-page py-2"
|
|
33
|
+
aria-label="Primary"
|
|
34
|
+
>
|
|
30
35
|
{#each sections as sec (sec.title)}
|
|
31
|
-
<h2
|
|
32
|
-
|
|
36
|
+
<h2
|
|
37
|
+
class="m-0 px-4 pb-1.5 pt-3.5 text-[10px] font-semibold uppercase leading-[1.4] tracking-[0.06em] text-text-4"
|
|
38
|
+
>{sec.title}</h2>
|
|
39
|
+
<ul class="m-0 flex list-none flex-col gap-px px-2">
|
|
33
40
|
{#each sec.items as it (it.id)}
|
|
34
41
|
<li>
|
|
35
42
|
<button
|
|
36
43
|
type="button"
|
|
37
|
-
class="
|
|
44
|
+
class="flex w-full cursor-pointer select-none items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left font-[inherit] text-13 leading-[18px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05
|
|
45
|
+
{it.id === activeId
|
|
46
|
+
? '[&_.sb-ico]:text-brand-05 bg-row-active text-text-1'
|
|
47
|
+
: 'bg-transparent text-text-2 hover:bg-row-hover hover:text-text-1'}"
|
|
38
48
|
aria-current={it.id === activeId ? 'page' : undefined}
|
|
39
49
|
onclick={() => { activeId = it.id; onchange?.(it.id); }}
|
|
40
50
|
>
|
|
41
|
-
<span class="sb-ico"
|
|
51
|
+
<span class="sb-ico shrink-0 text-text-3 [&_svg]:h-4 [&_svg]:w-4"
|
|
52
|
+
><it.icon /></span>
|
|
42
53
|
{it.label}
|
|
43
|
-
{#if it.count !== undefined}
|
|
44
|
-
|
|
54
|
+
{#if it.count !== undefined}
|
|
55
|
+
<span
|
|
56
|
+
class="ml-auto text-[11px] tabular-nums {it.id === activeId
|
|
57
|
+
? 'text-text-3'
|
|
58
|
+
: 'text-text-4'}"
|
|
59
|
+
>{it.count}</span>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if it.pill !== undefined}
|
|
62
|
+
<span
|
|
63
|
+
class="ml-auto rounded-full bg-brand-05 px-1.5 py-px text-[10px] font-semibold text-white"
|
|
64
|
+
>{it.pill}<span class="sr-only"> alert{it.pill !== 1 ? 's' : ''}</span></span>
|
|
65
|
+
{/if}
|
|
45
66
|
</button>
|
|
46
67
|
</li>
|
|
47
68
|
{/each}
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<span
|
|
11
|
-
|
|
11
|
+
data-weak={weak ? 'true' : undefined}
|
|
12
|
+
class="inline-flex h-3 items-end gap-px [&>i]:block [&>i]:w-0.5 [&>i]:rounded-[0.5px] [&>i]:bg-status-success data-[weak=true]:[&>i:nth-child(3)]:bg-bg-3 data-[weak=true]:[&>i:nth-child(4)]:bg-bg-3"
|
|
12
13
|
role="img"
|
|
13
14
|
aria-label={label ?? (weak ? 'Signal: weak' : 'Signal: strong')}
|
|
14
15
|
>
|
|
15
|
-
<i></i><i></i><i></i><i></i>
|
|
16
|
+
<i class="h-[3px]"></i><i class="h-1.5"></i><i class="h-[9px]"></i><i class="h-3"></i>
|
|
16
17
|
</span>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
2
|
interface Props {
|
|
4
3
|
variant?: 'text' | 'title' | 'stat' | 'circle' | 'block';
|
|
5
4
|
width?: string | number | undefined;
|
|
@@ -13,20 +12,33 @@
|
|
|
13
12
|
width = undefined,
|
|
14
13
|
height = undefined,
|
|
15
14
|
class: className = '',
|
|
16
|
-
ariaLabel = 'Loading'
|
|
15
|
+
ariaLabel = 'Loading',
|
|
17
16
|
}: Props = $props();
|
|
18
17
|
|
|
19
|
-
let variantClass = $derived(variant === 'block' ? '' : variant);
|
|
20
18
|
let w = $derived(width ?? (variant === 'circle' ? height : undefined));
|
|
21
|
-
let
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
let inlineStyle = $derived(
|
|
20
|
+
[
|
|
21
|
+
w !== undefined ? `width:${typeof w === 'number' ? w + 'px' : w}` : '',
|
|
22
|
+
height !== undefined ? `height:${typeof height === 'number' ? height + 'px' : height}` : '',
|
|
23
|
+
]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(';'),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Pre-composed variant strings so Tailwind's static scanner picks them up.
|
|
29
|
+
// Mirrors the legacy `.skeleton.text/.title/.stat/.circle` ramp.
|
|
30
|
+
const VARIANT: Record<NonNullable<Props['variant']>, string> = {
|
|
31
|
+
text: 'h-3 rounded-[3px]',
|
|
32
|
+
title: 'h-[18px] rounded',
|
|
33
|
+
stat: 'h-7 rounded',
|
|
34
|
+
circle: 'rounded-full',
|
|
35
|
+
block: 'rounded',
|
|
36
|
+
};
|
|
25
37
|
</script>
|
|
26
38
|
|
|
27
39
|
<span
|
|
28
|
-
class="
|
|
29
|
-
{
|
|
40
|
+
class="block animate-shimmer bg-[length:200%_100%] bg-[linear-gradient(90deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.1)_50%,rgba(255,255,255,0.04)_100%)] motion-reduce:animate-none {VARIANT[variant]} {className}"
|
|
41
|
+
style={inlineStyle}
|
|
30
42
|
role="status"
|
|
31
43
|
aria-label={ariaLabel}
|
|
32
44
|
aria-busy="true"
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
let { href = '#main-content', class: className = '', children }: Props = $props();
|
|
9
|
-
|
|
10
9
|
</script>
|
|
11
10
|
|
|
12
|
-
<a
|
|
11
|
+
<a
|
|
12
|
+
class="absolute left-0 top-0 z-[100] -translate-y-full rounded-br-md bg-brand-05 px-3 py-2 text-13 font-semibold text-white no-underline focus:translate-y-0 focus:outline-2 focus:-outline-offset-4 focus:outline-white
|
|
13
|
+
{className}"
|
|
14
|
+
{href}
|
|
15
|
+
>
|
|
13
16
|
{#if children}{@render children()}{:else}Skip to main content{/if}
|
|
14
17
|
</a>
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
2
|
interface Props {
|
|
5
3
|
label?: string | undefined;
|
|
6
4
|
value?: number;
|
|
@@ -34,27 +32,36 @@
|
|
|
34
32
|
value = Number((e.target as HTMLInputElement).value);
|
|
35
33
|
onchange?.(value);
|
|
36
34
|
}
|
|
35
|
+
|
|
36
|
+
// The native <input type="range"> track gets a gradient-fill background
|
|
37
|
+
// composed inline (so the fill % is reactive). Thumb chrome lives in
|
|
38
|
+
// arbitrary-value pseudo-element utilities — Tailwind v4 handles them
|
|
39
|
+
// statically.
|
|
40
|
+
const TRACK_CLS =
|
|
41
|
+
'h-1 w-full cursor-pointer appearance-none rounded-sm outline-none transition-opacity duration-100 disabled:cursor-not-allowed disabled:opacity-40 ' +
|
|
42
|
+
'[&::-webkit-slider-thumb]:[-webkit-appearance:none] [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-brand-05 [&::-webkit-slider-thumb]:bg-white ' +
|
|
43
|
+
'[&::-moz-range-thumb]:h-3.5 [&::-moz-range-thumb]:w-3.5 [&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-brand-05 [&::-moz-range-thumb]:bg-white';
|
|
37
44
|
</script>
|
|
38
45
|
|
|
39
|
-
<div class="
|
|
46
|
+
<div class="flex flex-col gap-1.5">
|
|
40
47
|
{#if label}
|
|
41
|
-
<div class="
|
|
42
|
-
<label for={inputId} class="
|
|
43
|
-
<span class="
|
|
44
|
-
{value}{#if suffix}<span class="
|
|
48
|
+
<div class="flex items-center justify-between gap-2">
|
|
49
|
+
<label for={inputId} class="text-12 text-text-4">{label}</label>
|
|
50
|
+
<span class="flex items-baseline gap-px text-12 font-medium text-text-1 tabular-nums" aria-live="polite">
|
|
51
|
+
{value}{#if suffix}<span class="ml-px text-11 text-text-4">{suffix}</span>{/if}
|
|
45
52
|
</span>
|
|
46
53
|
</div>
|
|
47
54
|
{/if}
|
|
48
55
|
<input
|
|
49
56
|
{id}
|
|
50
57
|
type="range"
|
|
51
|
-
class=
|
|
58
|
+
class={TRACK_CLS}
|
|
52
59
|
{min}
|
|
53
60
|
{max}
|
|
54
61
|
{step}
|
|
55
62
|
{value}
|
|
56
63
|
{disabled}
|
|
57
|
-
style=
|
|
64
|
+
style={`background: linear-gradient(to right, var(--brand-05) 0%, var(--brand-05) ${pct}%, rgba(255,255,255,0.12) ${pct}%, rgba(255,255,255,0.12) 100%);`}
|
|
58
65
|
aria-label={label}
|
|
59
66
|
aria-valuetext={suffix ? `${value} ${suffix}` : undefined}
|
|
60
67
|
oninput={handleChange}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
2
|
interface Props {
|
|
4
3
|
sortKey: string;
|
|
5
4
|
activeKey?: string | null;
|
|
@@ -15,14 +14,24 @@
|
|
|
15
14
|
children,
|
|
16
15
|
onsort,
|
|
17
16
|
}: Props = $props();
|
|
17
|
+
|
|
18
18
|
let active = $derived(activeKey === sortKey);
|
|
19
|
-
let ariaSort = $derived(
|
|
19
|
+
let ariaSort = $derived(
|
|
20
|
+
(active ? (dir === 'asc' ? 'ascending' : 'descending') : 'none') as
|
|
21
|
+
| 'ascending'
|
|
22
|
+
| 'descending'
|
|
23
|
+
| 'none',
|
|
24
|
+
);
|
|
20
25
|
</script>
|
|
21
26
|
|
|
22
27
|
<th scope="col" aria-sort={ariaSort}>
|
|
23
|
-
<button
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
class="inline-flex w-full cursor-pointer items-center gap-1.5 whitespace-nowrap border-0 bg-transparent px-3 py-2 font-inherit text-inherit hover:text-text-2 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-brand-05"
|
|
31
|
+
onclick={() => onsort?.(sortKey)}
|
|
32
|
+
>
|
|
24
33
|
{@render children?.()}
|
|
25
|
-
<svg class="
|
|
34
|
+
<svg class="h-[11px] w-2 shrink-0" viewBox="0 0 8 11" aria-hidden="true" fill="currentColor">
|
|
26
35
|
<path d="M4 0L7 4H1Z" opacity={active && dir === 'asc' ? 1 : 0.3} />
|
|
27
36
|
<path d="M4 11L1 7H7Z" opacity={active && dir === 'desc' ? 1 : 0.3} />
|
|
28
37
|
</svg>
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<script lang="ts">
|
|
10
|
-
|
|
11
10
|
interface Props {
|
|
12
11
|
items?: SortableItem[];
|
|
13
12
|
onChange?: (items: SortableItem[]) => void;
|
|
@@ -22,8 +21,8 @@
|
|
|
22
21
|
|
|
23
22
|
function reorder(fromId: string, toId: string) {
|
|
24
23
|
if (fromId === toId) return;
|
|
25
|
-
const from = items.findIndex(i => i.id === fromId);
|
|
26
|
-
const to = items.findIndex(i => i.id === toId);
|
|
24
|
+
const from = items.findIndex((i) => i.id === fromId);
|
|
25
|
+
const to = items.findIndex((i) => i.id === toId);
|
|
27
26
|
const arr = [...items];
|
|
28
27
|
const [item] = arr.splice(from, 1);
|
|
29
28
|
arr.splice(to, 0, item);
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
function move(id: string, dir: -1 | 1) {
|
|
35
|
-
const idx = items.findIndex(i => i.id === id);
|
|
34
|
+
const idx = items.findIndex((i) => i.id === id);
|
|
36
35
|
const next = idx + dir;
|
|
37
36
|
if (next < 0 || next >= items.length) return;
|
|
38
37
|
const arr = [...items];
|
|
@@ -53,25 +52,25 @@
|
|
|
53
52
|
}
|
|
54
53
|
</script>
|
|
55
54
|
|
|
56
|
-
<ol class="
|
|
55
|
+
<ol class="m-0 flex list-none flex-col gap-1 p-0" aria-label={ariaLabel}>
|
|
57
56
|
{#each items as item, idx (item.id)}
|
|
58
57
|
<li
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class:is-drag-over={dragOver === item.id}
|
|
58
|
+
data-grabbed={grabbed === item.id ? 'true' : undefined}
|
|
59
|
+
data-dragover={dragOver === item.id ? 'true' : undefined}
|
|
62
60
|
draggable="true"
|
|
63
|
-
|
|
61
|
+
class="flex items-center gap-2 rounded-md border border-border-1 bg-divider px-2 py-1.5 transition-colors duration-100 data-[grabbed=true]:border-brand-05 data-[grabbed=true]:bg-brand-05/[0.08] data-[dragover=true]:border-brand-05"
|
|
62
|
+
ondragstart={(e) => { e.dataTransfer?.setData('text/plain', item.id); if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move'; dragSrc = item.id; }}
|
|
64
63
|
ondragend={() => { dragSrc = null; dragOver = null; }}
|
|
65
|
-
ondragover={e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; dragOver = item.id; }}
|
|
64
|
+
ondragover={(e) => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; dragOver = item.id; }}
|
|
66
65
|
ondragleave={() => { dragOver = null; }}
|
|
67
|
-
ondrop={e => { e.preventDefault(); if (dragSrc) reorder(dragSrc, item.id); dragSrc = null; dragOver = null; }}
|
|
66
|
+
ondrop={(e) => { e.preventDefault(); if (dragSrc) reorder(dragSrc, item.id); dragSrc = null; dragOver = null; }}
|
|
68
67
|
>
|
|
69
68
|
<button
|
|
70
69
|
type="button"
|
|
71
|
-
|
|
72
|
-
aria-label="{grabbed === item.id ? 'Release' : 'Grab'} {item.label}. Use arrow keys to reorder."
|
|
70
|
+
aria-label={`${grabbed === item.id ? 'Release' : 'Grab'} ${item.label}. Use arrow keys to reorder.`}
|
|
73
71
|
aria-pressed={grabbed === item.id}
|
|
74
|
-
|
|
72
|
+
class="flex h-6 w-6 cursor-grab items-center justify-center rounded border-0 bg-transparent p-0 text-text-4 hover:bg-row-hover hover:text-text-1 active:cursor-grabbing focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
73
|
+
onkeydown={(e) => handleKeyDown(e, item.id)}
|
|
75
74
|
>
|
|
76
75
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true" focusable="false">
|
|
77
76
|
<rect x="3" y="2" width="2" height="2" rx="1" />
|
|
@@ -82,13 +81,13 @@
|
|
|
82
81
|
<rect x="9" y="10" width="2" height="2" rx="1" />
|
|
83
82
|
</svg>
|
|
84
83
|
</button>
|
|
85
|
-
<span class="
|
|
86
|
-
<span class="
|
|
84
|
+
<span class="flex min-w-0 flex-1 flex-col">
|
|
85
|
+
<span class="truncate text-13 text-text-1">{item.label}</span>
|
|
87
86
|
{#if item.meta}
|
|
88
|
-
<span class="
|
|
87
|
+
<span class="truncate text-11 text-text-4">{item.meta}</span>
|
|
89
88
|
{/if}
|
|
90
89
|
</span>
|
|
91
|
-
<span class="
|
|
90
|
+
<span class="shrink-0 text-11 text-text-4 tabular-nums" aria-hidden="true">{idx + 1}</span>
|
|
92
91
|
</li>
|
|
93
92
|
{/each}
|
|
94
93
|
</ol>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
bars = 36,
|
|
11
11
|
active = false,
|
|
12
12
|
seed = 1,
|
|
13
|
-
ariaLabel = 'Activity sparkline'
|
|
13
|
+
ariaLabel = 'Activity sparkline',
|
|
14
14
|
}: Props = $props();
|
|
15
15
|
|
|
16
16
|
function rng(s0: number) {
|
|
@@ -21,21 +21,27 @@
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
let heights = $derived(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
let heights = $derived(
|
|
25
|
+
(() => {
|
|
26
|
+
const r = rng(seed);
|
|
27
|
+
const out: number[] = [];
|
|
28
|
+
let prev = 12;
|
|
29
|
+
for (let i = 0; i < bars; i++) {
|
|
30
|
+
const v = Math.max(4, Math.min(40, prev + (r() * 10 - 5)));
|
|
31
|
+
prev = v;
|
|
32
|
+
out.push(v);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
})(),
|
|
36
|
+
);
|
|
35
37
|
</script>
|
|
36
38
|
|
|
37
|
-
<div class="
|
|
39
|
+
<div class="flex h-12 items-end gap-px" role="img" aria-label={ariaLabel}>
|
|
38
40
|
{#each heights as h, i}
|
|
39
|
-
<div
|
|
41
|
+
<div
|
|
42
|
+
class="flex-1 rounded-t-[1px] bg-gradient-to-b from-brand-06 to-brand-04"
|
|
43
|
+
aria-hidden="true"
|
|
44
|
+
style="height:{h}px;opacity:{active ? 0.4 + i / (bars * 1.4) : 0.5 + i / (bars * 1.6)}"
|
|
45
|
+
></div>
|
|
40
46
|
{/each}
|
|
41
47
|
</div>
|