@w5-ui/svelte 0.2.0 → 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 +4 -4
- 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
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
<script lang="ts">
|
|
10
10
|
import { onMount } from 'svelte';
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
interface Props {
|
|
14
13
|
columns?: ColumnDef[];
|
|
15
14
|
visible?: Set<string>;
|
|
@@ -56,14 +55,15 @@
|
|
|
56
55
|
});
|
|
57
56
|
</script>
|
|
58
57
|
|
|
59
|
-
<div class="
|
|
58
|
+
<div class="relative inline-block">
|
|
60
59
|
<button
|
|
61
60
|
bind:this={btnEl}
|
|
62
61
|
type="button"
|
|
63
|
-
|
|
62
|
+
data-open={open ? 'true' : undefined}
|
|
64
63
|
aria-haspopup="dialog"
|
|
65
64
|
aria-expanded={open}
|
|
66
65
|
aria-controls={panelId}
|
|
66
|
+
class="inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md border border-border-2 bg-transparent px-2.5 text-12 font-medium text-text-3 transition-colors duration-100 hover:bg-row-hover hover:text-text-1 data-[open=true]:border-brand-05 data-[open=true]:bg-brand-05/[0.10] data-[open=true]:text-brand-05 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
67
67
|
onclick={() => (open = !open)}
|
|
68
68
|
>
|
|
69
69
|
<svg viewBox="0 0 14 14" width="14" height="14" fill="none" aria-hidden="true" focusable="false">
|
|
@@ -77,18 +77,19 @@
|
|
|
77
77
|
<div
|
|
78
78
|
bind:this={panelEl}
|
|
79
79
|
id={panelId}
|
|
80
|
-
class="col-toggle__panel"
|
|
81
80
|
role="dialog"
|
|
82
81
|
aria-label="Toggle columns"
|
|
82
|
+
class="absolute right-0 top-[calc(100%+4px)] z-[9100] min-w-[200px] rounded-lg border border-border-3 bg-bg-2 p-1 shadow-[0_8px_24px_rgba(0,0,0,0.5)]"
|
|
83
83
|
>
|
|
84
|
-
<div class="
|
|
85
|
-
<ul class="
|
|
84
|
+
<div class="px-2.5 pb-1.5 pt-2 text-11 font-semibold uppercase tracking-[0.06em] text-text-4">Columns</div>
|
|
85
|
+
<ul class="m-0 list-none p-0" role="list">
|
|
86
86
|
{#each columns as col (col.key)}
|
|
87
|
-
<li
|
|
88
|
-
<label class="
|
|
87
|
+
<li>
|
|
88
|
+
<label class="flex cursor-pointer items-center gap-2 rounded-[5px] px-2.5 py-1.5 text-13 text-text-2 hover:bg-row-active hover:text-text-1 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50">
|
|
89
89
|
<input
|
|
90
90
|
type="checkbox"
|
|
91
|
-
class="
|
|
91
|
+
class="h-3.5 w-3.5 cursor-pointer appearance-none rounded-sm border-[1.5px] border-border-3 bg-bg-1 checked:border-brand-05 checked:bg-brand-05 disabled:cursor-not-allowed"
|
|
92
|
+
style="background-image: var(--check-img, none);"
|
|
92
93
|
checked={visible.has(col.key)}
|
|
93
94
|
disabled={col.required}
|
|
94
95
|
onchange={() => { if (!col.required) toggle(col.key); }}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
let counter = 0;
|
|
3
|
-
export interface ComboboxOption {
|
|
3
|
+
export interface ComboboxOption {
|
|
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-cb-${++counter}`;
|
|
33
35
|
let inputId = $derived(id ?? uid);
|
|
@@ -40,13 +42,15 @@
|
|
|
40
42
|
|
|
41
43
|
let selectedOption = $derived(options.find((o) => o.value === value));
|
|
42
44
|
|
|
43
|
-
let filtered = $derived(
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
let filtered = $derived(
|
|
46
|
+
query.trim() === ''
|
|
47
|
+
? options
|
|
48
|
+
: options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase())),
|
|
49
|
+
);
|
|
46
50
|
|
|
47
|
-
let activeOptionId = $derived(
|
|
48
|
-
? `${listboxId}-opt-${activeIdx}`
|
|
49
|
-
|
|
51
|
+
let activeOptionId = $derived(
|
|
52
|
+
activeIdx >= 0 && filtered[activeIdx] ? `${listboxId}-opt-${activeIdx}` : undefined,
|
|
53
|
+
);
|
|
50
54
|
|
|
51
55
|
let displayValue = $derived(open ? query : (selectedOption?.label ?? ''));
|
|
52
56
|
|
|
@@ -93,9 +97,9 @@
|
|
|
93
97
|
onDestroy(() => document.removeEventListener('mousedown', handleOutside));
|
|
94
98
|
</script>
|
|
95
99
|
|
|
96
|
-
<div class="
|
|
100
|
+
<div class="relative block {className}" bind:this={wrapperEl}>
|
|
97
101
|
{#if label}<label for={inputId} class="sr-only">{label}</label>{/if}
|
|
98
|
-
<div class="
|
|
102
|
+
<div class="relative flex items-center">
|
|
99
103
|
<input
|
|
100
104
|
bind:this={inputEl}
|
|
101
105
|
id={inputId}
|
|
@@ -113,24 +117,34 @@
|
|
|
113
117
|
oninput={onInputChange}
|
|
114
118
|
onfocus={openList}
|
|
115
119
|
onkeydown={onKeyDown}
|
|
116
|
-
class="
|
|
120
|
+
class="box-border h-[34px] w-full rounded-md border border-border-2 bg-bg-page py-0 pl-3 pr-9 text-13 leading-none text-text-1 transition-colors duration-100 hover:border-border-3 focus:border-brand-05 focus:outline-none focus:shadow-[0_0_0_2px_rgba(0,111,255,0.2)] disabled:cursor-not-allowed disabled:opacity-40"
|
|
117
121
|
/>
|
|
118
122
|
<button
|
|
119
123
|
type="button"
|
|
120
|
-
tabindex=
|
|
124
|
+
tabindex={-1}
|
|
121
125
|
aria-hidden="true"
|
|
122
|
-
class="
|
|
126
|
+
class="absolute right-2 flex cursor-pointer items-center border-0 bg-transparent p-0.5 leading-none text-text-4"
|
|
123
127
|
onclick={() => { if (open) { open = false; query = ''; } else { inputEl?.focus(); openList(); } }}
|
|
124
128
|
>
|
|
125
|
-
<svg
|
|
129
|
+
<svg
|
|
130
|
+
viewBox="0 0 16 16"
|
|
131
|
+
aria-hidden="true"
|
|
132
|
+
focusable="false"
|
|
133
|
+
class="h-3.5 w-3.5 transition-transform duration-100 motion-reduce:transition-none {open ? 'rotate-180' : ''}"
|
|
134
|
+
>
|
|
126
135
|
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
127
136
|
</svg>
|
|
128
137
|
</button>
|
|
129
138
|
</div>
|
|
130
139
|
{#if open}
|
|
131
|
-
<ul
|
|
140
|
+
<ul
|
|
141
|
+
id={listboxId}
|
|
142
|
+
role="listbox"
|
|
143
|
+
aria-label={label}
|
|
144
|
+
class="absolute left-0 top-[calc(100%+4px)] z-[9000] m-0 max-h-60 min-w-full list-none overflow-y-auto rounded-lg border border-border-3 bg-bg-2 p-1 shadow-[0_8px_24px_rgba(0,0,0,0.5)]"
|
|
145
|
+
>
|
|
132
146
|
{#if filtered.length === 0}
|
|
133
|
-
<li class="
|
|
147
|
+
<li class="list-none p-2.5 text-center text-13 text-text-4">No results</li>
|
|
134
148
|
{:else}
|
|
135
149
|
{#each filtered as opt, idx (opt.value)}
|
|
136
150
|
<li
|
|
@@ -138,7 +152,7 @@
|
|
|
138
152
|
role="option"
|
|
139
153
|
aria-selected={opt.value === value}
|
|
140
154
|
data-active={idx === activeIdx ? 'true' : undefined}
|
|
141
|
-
class="
|
|
155
|
+
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"
|
|
142
156
|
onmousedown={(e) => { e.preventDefault(); (() => pick(opt))(); }}
|
|
143
157
|
onmouseenter={() => { activeIdx = idx; }}
|
|
144
158
|
>{opt.label}</li>
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
onclose?: () => void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let {
|
|
22
|
+
let {
|
|
23
|
+
open = false,
|
|
24
|
+
items = [],
|
|
25
|
+
placeholder = 'Search pages and actions…',
|
|
23
26
|
onselect,
|
|
24
27
|
onclose,
|
|
25
28
|
}: Props = $props();
|
|
@@ -28,21 +31,24 @@
|
|
|
28
31
|
let inputEl = $state<HTMLInputElement | undefined>(undefined);
|
|
29
32
|
const listboxId = `cp-list-${Math.random().toString(36).slice(2)}`;
|
|
30
33
|
|
|
31
|
-
let filtered =
|
|
32
|
-
|
|
34
|
+
let filtered = $derived(
|
|
35
|
+
query.trim() === ''
|
|
33
36
|
? items
|
|
34
|
-
: items.filter((i) => i.label.toLowerCase().includes(query.toLowerCase()))
|
|
37
|
+
: items.filter((i) => i.label.toLowerCase().includes(query.toLowerCase())),
|
|
38
|
+
);
|
|
35
39
|
|
|
36
|
-
let grouped = $derived(
|
|
37
|
-
(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
let grouped = $derived(
|
|
41
|
+
filtered.reduce(
|
|
42
|
+
(acc, item) => {
|
|
43
|
+
const g = item.group ?? '';
|
|
44
|
+
const found = acc.find((a: { group: string }) => a.group === g);
|
|
45
|
+
if (found) found.items.push(item);
|
|
46
|
+
else acc.push({ group: g, items: [item] });
|
|
47
|
+
return acc;
|
|
48
|
+
},
|
|
49
|
+
[] as { group: string; items: CommandItem[] }[],
|
|
50
|
+
),
|
|
51
|
+
);
|
|
46
52
|
|
|
47
53
|
$effect(() => {
|
|
48
54
|
if (open) {
|
|
@@ -86,16 +92,21 @@
|
|
|
86
92
|
|
|
87
93
|
{#if open}
|
|
88
94
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
89
|
-
<div
|
|
95
|
+
<div
|
|
96
|
+
class="fixed inset-0 z-[9300] flex items-start justify-center bg-black/60 pt-[15vh] backdrop-blur-md"
|
|
97
|
+
role="presentation"
|
|
98
|
+
onclick={handleBackdrop}
|
|
99
|
+
onkeydown={handleKey}
|
|
100
|
+
>
|
|
90
101
|
<div
|
|
91
102
|
role="dialog"
|
|
92
103
|
aria-modal="true"
|
|
93
104
|
aria-label="Command palette"
|
|
94
|
-
class="
|
|
105
|
+
class="w-[520px] max-w-[90vw] overflow-hidden rounded-xl border border-border-3 bg-bg-1 shadow-[0_24px_64px_rgba(0,0,0,0.6)]"
|
|
95
106
|
>
|
|
96
|
-
<div class="
|
|
107
|
+
<div class="flex items-center gap-2.5 border-b border-border-1 px-4 py-3">
|
|
97
108
|
<svg
|
|
98
|
-
class="
|
|
109
|
+
class="shrink-0 text-text-4"
|
|
99
110
|
width="16"
|
|
100
111
|
height="16"
|
|
101
112
|
viewBox="0 0 16 16"
|
|
@@ -112,7 +123,6 @@
|
|
|
112
123
|
</svg>
|
|
113
124
|
<input
|
|
114
125
|
bind:this={inputEl}
|
|
115
|
-
class="cp-input"
|
|
116
126
|
type="text"
|
|
117
127
|
role="combobox"
|
|
118
128
|
aria-expanded={filtered.length > 0}
|
|
@@ -121,33 +131,39 @@
|
|
|
121
131
|
{placeholder}
|
|
122
132
|
bind:value={query}
|
|
123
133
|
onkeydown={handleKey}
|
|
134
|
+
class="min-w-0 flex-1 border-0 bg-transparent text-[15px] leading-none text-text-1 outline-none placeholder:text-text-4"
|
|
124
135
|
/>
|
|
125
|
-
<span class="
|
|
136
|
+
<span class="shrink-0 whitespace-nowrap rounded border border-border-2 bg-row-active px-1.5 py-0.5 text-[10px] text-text-4">Esc</span>
|
|
126
137
|
</div>
|
|
127
|
-
<ul
|
|
138
|
+
<ul
|
|
139
|
+
id={listboxId}
|
|
140
|
+
role="listbox"
|
|
141
|
+
aria-label="Results"
|
|
142
|
+
class="m-0 max-h-[360px] list-none overflow-y-auto p-1.5"
|
|
143
|
+
>
|
|
128
144
|
{#if filtered.length === 0}
|
|
129
|
-
<li class="
|
|
145
|
+
<li class="list-none p-6 text-center text-13 text-text-4">No results for “{query}”</li>
|
|
130
146
|
{:else}
|
|
131
147
|
{#each grouped as { group, items: groupItems }}
|
|
132
148
|
<li role="presentation">
|
|
133
149
|
{#if group}
|
|
134
|
-
<div class="
|
|
150
|
+
<div class="px-2.5 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.06em] text-text-4">{group}</div>
|
|
135
151
|
{/if}
|
|
136
152
|
{#each groupItems as item}
|
|
137
153
|
{@const idx = filtered.indexOf(item)}
|
|
138
154
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
139
155
|
<div
|
|
140
156
|
role="option"
|
|
141
|
-
tabindex=
|
|
157
|
+
tabindex={-1}
|
|
142
158
|
aria-selected={idx === activeIdx}
|
|
143
159
|
data-active={idx === activeIdx ? 'true' : undefined}
|
|
144
|
-
class="
|
|
160
|
+
class="flex cursor-pointer list-none items-center gap-2.5 rounded-md px-2.5 py-2 text-13 text-text-2 outline-none data-[active=true]:bg-brand-05/15 data-[active=true]:text-text-1"
|
|
145
161
|
onclick={() => commit(item.id)}
|
|
146
162
|
onmouseenter={() => (activeIdx = idx)}
|
|
147
163
|
>
|
|
148
164
|
{item.label}
|
|
149
165
|
{#if item.shortcut}
|
|
150
|
-
<span class="
|
|
166
|
+
<span class="ml-auto shrink-0" aria-label={item.shortcut.replace(/\+/g, ' then ')}>
|
|
151
167
|
<Kbd keys={item.shortcut} />
|
|
152
168
|
</span>
|
|
153
169
|
{/if}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte';
|
|
3
|
+
import Button from './Button.svelte';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
open?: boolean;
|
|
@@ -26,10 +27,17 @@
|
|
|
26
27
|
const FOCUSABLE =
|
|
27
28
|
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// Map confirm-variant to the migrated `Button` variants. `warning` doesn't
|
|
31
|
+
// exist on `Button`, so we mark it as primary + override colours inline.
|
|
32
|
+
const CONFIRM_VARIANT: Record<NonNullable<Props['variant']>, 'primary' | 'danger'> = {
|
|
33
|
+
danger: 'danger',
|
|
34
|
+
warning: 'primary',
|
|
35
|
+
info: 'primary',
|
|
36
|
+
};
|
|
37
|
+
const CONFIRM_EXTRA: Record<NonNullable<Props['variant']>, string> = {
|
|
38
|
+
danger: '!bg-status-danger !text-white hover:!bg-[#ff5e5e] !border-status-danger',
|
|
39
|
+
warning: '!bg-status-warning !text-neutral-00 hover:!bg-[#ffba42] !border-status-warning',
|
|
40
|
+
info: '',
|
|
33
41
|
};
|
|
34
42
|
|
|
35
43
|
let dialogEl = $state<HTMLDivElement | undefined>(undefined);
|
|
@@ -59,8 +67,6 @@
|
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
// Replaces the old `$: if (open) { ... }` + `afterUpdate` combo. `$effect`
|
|
63
|
-
// runs after the DOM commits, so `dialogEl` is bound before we query it.
|
|
64
70
|
$effect(() => {
|
|
65
71
|
if (open) {
|
|
66
72
|
prev = document.activeElement as HTMLElement | null;
|
|
@@ -86,7 +92,7 @@
|
|
|
86
92
|
</script>
|
|
87
93
|
|
|
88
94
|
<div
|
|
89
|
-
class="backdrop {open ? '
|
|
95
|
+
class="fixed inset-0 z-[60] items-center justify-center bg-black/55 backdrop-blur-lg {open ? 'flex' : 'hidden'}"
|
|
90
96
|
role="presentation"
|
|
91
97
|
onclick={(e) => {
|
|
92
98
|
if (e.target === e.currentTarget) onCancel();
|
|
@@ -94,22 +100,22 @@
|
|
|
94
100
|
>
|
|
95
101
|
<div
|
|
96
102
|
bind:this={dialogEl}
|
|
97
|
-
class="
|
|
103
|
+
class="w-[400px] max-w-[90vw] overflow-hidden rounded-xl border border-border-2 bg-bg-1 shadow-modal"
|
|
98
104
|
role="alertdialog"
|
|
99
105
|
aria-modal="true"
|
|
100
106
|
aria-labelledby="cd-title"
|
|
101
107
|
aria-describedby={description ? 'cd-desc' : undefined}
|
|
102
|
-
tabindex=
|
|
108
|
+
tabindex={-1}
|
|
103
109
|
>
|
|
104
|
-
<div class="
|
|
105
|
-
<h2 id="cd-title">{title}</h2>
|
|
110
|
+
<div class="flex items-center justify-between border-b border-border-1 px-5 py-4">
|
|
111
|
+
<h2 id="cd-title" class="m-0 text-16 font-semibold">{title}</h2>
|
|
106
112
|
</div>
|
|
107
113
|
{#if description}
|
|
108
|
-
<div id="cd-desc" class="
|
|
114
|
+
<div id="cd-desc" class="px-5 py-3.5 text-13 leading-[1.5] text-text-3">{description}</div>
|
|
109
115
|
{/if}
|
|
110
|
-
<div class="
|
|
111
|
-
<
|
|
112
|
-
<
|
|
116
|
+
<div class="flex justify-end gap-2 border-t border-border-1 bg-bg-0 px-5 py-3.5">
|
|
117
|
+
<Button variant="ghost" onclick={onCancel}>{cancelLabel}</Button>
|
|
118
|
+
<Button variant={CONFIRM_VARIANT[variant]} class={CONFIRM_EXTRA[variant]} onclick={onConfirm}>{confirmLabel}</Button>
|
|
113
119
|
</div>
|
|
114
120
|
</div>
|
|
115
121
|
</div>
|
|
@@ -33,10 +33,12 @@
|
|
|
33
33
|
let menuEl = $state<HTMLUListElement | undefined>(undefined);
|
|
34
34
|
let activeIdx = $state(0);
|
|
35
35
|
|
|
36
|
-
let actionItems = $derived(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
let actionItems = $derived(
|
|
37
|
+
items.reduce<ContextMenuItem[]>((acc, e) => {
|
|
38
|
+
if (!('separator' in e)) acc.push(e);
|
|
39
|
+
return acc;
|
|
40
|
+
}, []),
|
|
41
|
+
);
|
|
40
42
|
|
|
41
43
|
const menuW = 168;
|
|
42
44
|
let menuH = $derived(items.length * 30 + 8);
|
|
@@ -92,28 +94,26 @@
|
|
|
92
94
|
bind:this={menuEl}
|
|
93
95
|
role="menu"
|
|
94
96
|
aria-label={label}
|
|
95
|
-
tabindex=
|
|
96
|
-
class="
|
|
97
|
+
tabindex={-1}
|
|
98
|
+
class="fixed z-[9200] m-0 min-w-[168px] list-none rounded-lg border border-border-3 bg-bg-2 p-1 shadow-[0_8px_24px_rgba(0,0,0,0.5)] focus:outline-none"
|
|
97
99
|
style="left:{cx}px;top:{cy}px"
|
|
98
100
|
onkeydown={handleKeyDown}
|
|
99
101
|
>
|
|
100
102
|
{#each items as entry, i (i)}
|
|
101
103
|
{#if 'separator' in entry}
|
|
102
|
-
<li role="separator" class="
|
|
104
|
+
<li role="separator" class="my-1 h-px bg-row-active"></li>
|
|
103
105
|
{:else}
|
|
104
106
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
105
107
|
<li
|
|
106
108
|
role="menuitem"
|
|
107
|
-
tabindex=
|
|
109
|
+
tabindex={-1}
|
|
108
110
|
aria-disabled={entry.disabled || undefined}
|
|
109
111
|
data-active={actionIndex(entry) === activeIdx ? 'true' : undefined}
|
|
110
112
|
data-danger={entry.danger || undefined}
|
|
111
|
-
class="
|
|
113
|
+
class="flex cursor-pointer items-center whitespace-nowrap rounded-[5px] px-3 py-1.5 text-13 text-text-2 outline-none hover:bg-row-active hover:text-text-1 data-[active=true]:bg-row-active data-[active=true]:text-text-1 data-[danger=true]:text-status-danger data-[danger=true]:hover:bg-status-danger/10 aria-disabled:cursor-not-allowed aria-disabled:opacity-40"
|
|
112
114
|
onmouseenter={() => (activeIdx = actionIndex(entry))}
|
|
113
115
|
onmousedown={(e) => { e.preventDefault(); (() => { if (!entry.disabled) activate(entry.id); })(); }}
|
|
114
|
-
>
|
|
115
|
-
{entry.label}
|
|
116
|
-
</li>
|
|
116
|
+
>{entry.label}</li>
|
|
117
117
|
{/if}
|
|
118
118
|
{/each}
|
|
119
119
|
</ul>
|
|
@@ -50,16 +50,21 @@
|
|
|
50
50
|
window.removeEventListener('keydown', onKey);
|
|
51
51
|
document.removeEventListener('pointerdown', onPointer);
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
const PLACEMENT: Record<NonNullable<Props['placement']>, string> = {
|
|
55
|
+
top: 'bottom-[calc(100%+8px)] left-1/2 -translate-x-1/2',
|
|
56
|
+
bottom: 'top-[calc(100%+8px)] left-1/2 -translate-x-1/2',
|
|
57
|
+
};
|
|
53
58
|
</script>
|
|
54
59
|
|
|
55
|
-
<span class="
|
|
60
|
+
<span class="relative inline-flex items-center" bind:this={rootEl}>
|
|
56
61
|
<button
|
|
57
62
|
bind:this={triggerEl}
|
|
58
63
|
type="button"
|
|
59
|
-
class="ch-trigger"
|
|
60
64
|
aria-label="Help"
|
|
61
65
|
aria-haspopup="dialog"
|
|
62
66
|
aria-expanded={open}
|
|
67
|
+
class="inline-flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border-3 bg-transparent p-0 text-[10px] font-bold leading-none text-text-4 transition-[border-color,color] duration-100 hover:border-text-3 hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
63
68
|
onclick={toggle}
|
|
64
69
|
>?</button>
|
|
65
70
|
{#if open}
|
|
@@ -67,11 +72,11 @@
|
|
|
67
72
|
bind:this={panelEl}
|
|
68
73
|
role="dialog"
|
|
69
74
|
aria-labelledby={titleId}
|
|
70
|
-
class="
|
|
71
|
-
tabindex=
|
|
75
|
+
class="absolute z-[9200] min-w-[220px] max-w-[300px] rounded-[10px] border border-border-3 bg-bg-2 shadow-[0_8px_32px_rgba(0,0,0,0.5)] focus:outline-none {PLACEMENT[placement]}"
|
|
76
|
+
tabindex={-1}
|
|
72
77
|
>
|
|
73
|
-
<div id={titleId} class="
|
|
74
|
-
<div class="
|
|
78
|
+
<div id={titleId} class="border-b border-border-1 px-3.5 pb-2 pt-2.5 text-12 font-semibold text-text-2">{title}</div>
|
|
79
|
+
<div class="px-3.5 py-2.5 text-12 leading-[1.5] text-text-3">{body}</div>
|
|
75
80
|
</div>
|
|
76
81
|
{/if}
|
|
77
82
|
</span>
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
<button
|
|
22
22
|
type="button"
|
|
23
|
-
|
|
23
|
+
data-copied={copied ? 'true' : undefined}
|
|
24
24
|
aria-label={copied ? 'Copied!' : label}
|
|
25
|
+
class="inline-flex h-[22px] w-[22px] shrink-0 cursor-pointer items-center justify-center rounded border-0 bg-transparent text-text-4 transition-[color,background-color] duration-100 hover:bg-row-hover hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05 data-[copied=true]:text-status-success data-[copied=true]:hover:bg-status-success/10"
|
|
25
26
|
onclick={handleCopy}
|
|
26
27
|
>
|
|
27
28
|
{#if copied}
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
<script lang="ts">
|
|
39
39
|
import { onMount, onDestroy, tick } from 'svelte';
|
|
40
|
+
import Button from './Button.svelte';
|
|
40
41
|
|
|
41
42
|
interface Props {
|
|
42
43
|
value?: Date | null;
|
|
@@ -45,7 +46,10 @@
|
|
|
45
46
|
onchange?: (payload: Date) => void;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
let {
|
|
49
|
+
let {
|
|
50
|
+
value = null,
|
|
51
|
+
placeholder = 'Pick a date',
|
|
52
|
+
disabled = false,
|
|
49
53
|
onchange,
|
|
50
54
|
}: Props = $props();
|
|
51
55
|
const uid = `dash-ui-dp-${++counter}`;
|
|
@@ -58,7 +62,7 @@
|
|
|
58
62
|
// svelte-ignore state_referenced_locally
|
|
59
63
|
let viewMonth = $state(value?.getMonth() ?? today.getMonth());
|
|
60
64
|
let focusIdx = $state(0);
|
|
61
|
-
let triggerEl = $state<
|
|
65
|
+
let triggerEl = $state<HTMLElement | undefined>(undefined);
|
|
62
66
|
let calEl = $state<HTMLDivElement | undefined>(undefined);
|
|
63
67
|
let dayEls: (HTMLButtonElement | null)[] = $state(Array(42).fill(null));
|
|
64
68
|
|
|
@@ -125,25 +129,25 @@
|
|
|
125
129
|
|
|
126
130
|
onMount(() => document.addEventListener('mousedown', handleOutside));
|
|
127
131
|
onDestroy(() => document.removeEventListener('mousedown', handleOutside));
|
|
132
|
+
|
|
133
|
+
function dayCls(outside: boolean, isToday: boolean, isSelected: boolean) {
|
|
134
|
+
let cls = 'flex h-8 w-8 cursor-pointer items-center justify-center rounded border-0 bg-transparent text-12 leading-none transition-colors duration-100 hover:bg-row-active focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-brand-05';
|
|
135
|
+
if (outside) cls += ' text-text-4';
|
|
136
|
+
else cls += ' text-text-2';
|
|
137
|
+
if (isToday && !isSelected) cls += ' ring-1 ring-inset ring-brand-05';
|
|
138
|
+
if (isSelected) cls += ' bg-brand-05 text-white hover:bg-brand-06';
|
|
139
|
+
return cls;
|
|
140
|
+
}
|
|
128
141
|
</script>
|
|
129
142
|
|
|
130
|
-
<div class="
|
|
131
|
-
<
|
|
132
|
-
bind:this={triggerEl}
|
|
133
|
-
type="button"
|
|
134
|
-
class="btn dp-trigger"
|
|
135
|
-
aria-haspopup="dialog"
|
|
136
|
-
aria-expanded={open}
|
|
137
|
-
aria-controls={open ? calId : undefined}
|
|
138
|
-
{disabled}
|
|
139
|
-
onclick={() => open ? closeCal() : openCal()}
|
|
140
|
-
>
|
|
143
|
+
<div class="relative inline-block" bind:this={triggerEl}>
|
|
144
|
+
<Button variant="ghost" aria-haspopup="dialog" aria-expanded={open} aria-controls={open ? calId : undefined} disabled={disabled} onclick={() => open ? closeCal() : openCal()}>
|
|
141
145
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
|
142
146
|
<rect x="1" y="2" width="12" height="11" rx="1.5" />
|
|
143
147
|
<path d="M1 6h12M4 1v2M10 1v2" />
|
|
144
148
|
</svg>
|
|
145
149
|
{value ? formatDate(value) : placeholder}
|
|
146
|
-
</
|
|
150
|
+
</Button>
|
|
147
151
|
|
|
148
152
|
{#if open}
|
|
149
153
|
<div
|
|
@@ -152,27 +156,27 @@
|
|
|
152
156
|
role="dialog"
|
|
153
157
|
aria-modal="true"
|
|
154
158
|
aria-label={`Choose date, ${label}`}
|
|
155
|
-
class="
|
|
159
|
+
class="absolute left-0 top-[calc(100%+4px)] z-[9100] rounded-lg border border-border-3 bg-bg-2 p-2 shadow-[0_8px_24px_rgba(0,0,0,0.5)]"
|
|
156
160
|
>
|
|
157
|
-
<div class="
|
|
158
|
-
<button type="button" class="
|
|
161
|
+
<div class="flex items-center justify-between px-1 py-1">
|
|
162
|
+
<button type="button" class="inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded border-0 bg-transparent text-text-4 hover:bg-row-hover hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05" onclick={prevMonth} aria-label="Previous month">
|
|
159
163
|
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
|
160
164
|
<path d="M7 1L2 6l5 5" />
|
|
161
165
|
</svg>
|
|
162
166
|
</button>
|
|
163
|
-
<span class="
|
|
164
|
-
<button type="button" class="
|
|
167
|
+
<span class="text-13 font-medium text-text-1" aria-live="polite">{label}</span>
|
|
168
|
+
<button type="button" class="inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded border-0 bg-transparent text-text-4 hover:bg-row-hover hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05" onclick={nextMonth} aria-label="Next month">
|
|
165
169
|
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
|
166
170
|
<path d="M1 1l5 5-5 5" />
|
|
167
171
|
</svg>
|
|
168
172
|
</button>
|
|
169
173
|
</div>
|
|
170
174
|
|
|
171
|
-
<table role="grid" class="
|
|
175
|
+
<table role="grid" class="mt-1 border-collapse" aria-label={label}>
|
|
172
176
|
<thead>
|
|
173
177
|
<tr>
|
|
174
178
|
{#each DAYS as d}
|
|
175
|
-
<th scope="col" abbr={d}>{d}</th>
|
|
179
|
+
<th scope="col" abbr={d} class="p-0 text-center text-11 font-semibold uppercase tracking-[0.05em] text-text-4">{d}</th>
|
|
176
180
|
{/each}
|
|
177
181
|
</tr>
|
|
178
182
|
</thead>
|
|
@@ -185,12 +189,12 @@
|
|
|
185
189
|
{@const outside = day.getMonth() !== viewMonth}
|
|
186
190
|
{@const isToday = isSameDay(day, today)}
|
|
187
191
|
{@const isSelected = value ? isSameDay(day, value) : false}
|
|
188
|
-
<td role="gridcell" aria-selected={isSelected}>
|
|
192
|
+
<td role="gridcell" aria-selected={isSelected} class="p-0">
|
|
189
193
|
<button
|
|
190
194
|
bind:this={dayEls[idx]}
|
|
191
195
|
type="button"
|
|
192
196
|
tabindex={idx === focusIdx ? 0 : -1}
|
|
193
|
-
class=
|
|
197
|
+
class={dayCls(outside, isToday, isSelected)}
|
|
194
198
|
aria-label={day.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}
|
|
195
199
|
onkeydown={(e) => onGridKey(e, idx)}
|
|
196
200
|
onclick={() => select(day)}
|