@w5-ui/svelte 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -4
- package/package.json +2 -2
- package/src/lib/actions/portal.ts +55 -0
- 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 +101 -17
- 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 +161 -28
- 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 +37 -0
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
let { label, class: className = '', onclick }: Props = $props();
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
|
-
<span
|
|
12
|
-
|
|
11
|
+
<span
|
|
12
|
+
class="inline-flex h-6 items-center gap-1 whitespace-nowrap rounded-full border border-brand-05/25 bg-brand-05/14 pl-2.5 pr-1 text-12 font-medium text-status-info {className}"
|
|
13
|
+
>
|
|
14
|
+
<span class="leading-none">{label}</span>
|
|
13
15
|
<button
|
|
14
16
|
type="button"
|
|
15
|
-
class="
|
|
17
|
+
class="inline-flex h-[18px] w-[18px] shrink-0 cursor-pointer items-center justify-center rounded-full border-0 bg-transparent p-0 text-status-info transition-[background,color] duration-100 hover:bg-brand-05/25 hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
16
18
|
aria-label="Remove {label} filter"
|
|
17
19
|
{onclick}
|
|
18
20
|
>
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
|
-
|
|
7
|
-
|
|
8
6
|
interface Props {
|
|
9
7
|
label?: string | undefined;
|
|
10
8
|
value?: string[];
|
|
@@ -56,26 +54,27 @@
|
|
|
56
54
|
}
|
|
57
55
|
</script>
|
|
58
56
|
|
|
59
|
-
<div class=
|
|
57
|
+
<div class={klass}>
|
|
60
58
|
{#if label}
|
|
61
|
-
<label for={uid} class="
|
|
59
|
+
<label for={uid} class="text-12 text-text-4">{label}</label>
|
|
62
60
|
{/if}
|
|
63
61
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
64
62
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions - group container focuses input on click -->
|
|
65
63
|
<div
|
|
66
64
|
role="group"
|
|
67
65
|
aria-label={label ?? 'Tags'}
|
|
68
|
-
|
|
66
|
+
data-disabled={disabled ? 'true' : undefined}
|
|
67
|
+
class="flex min-h-[34px] cursor-text flex-wrap items-center gap-1 rounded-md border border-border-2 bg-bg-page py-[3px] pl-2 pr-2 transition-colors duration-100 hover:border-border-3 focus-within:border-brand-05 focus-within:shadow-[0_0_0_2px_rgba(0,111,255,0.2)] data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-40"
|
|
69
68
|
onclick={() => inputEl?.focus()}
|
|
70
69
|
>
|
|
71
70
|
{#each value as tag, i}
|
|
72
|
-
<span class="
|
|
73
|
-
<span class="
|
|
71
|
+
<span class="inline-flex h-6 items-center gap-1 whitespace-nowrap rounded-full border border-brand-05/25 bg-brand-05/[0.14] pl-2.5 pr-1 text-12 font-medium text-status-info">
|
|
72
|
+
<span class="leading-none">{tag}</span>
|
|
74
73
|
{#if !disabled}
|
|
75
74
|
<button
|
|
76
75
|
type="button"
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
aria-label={`Remove ${tag}`}
|
|
77
|
+
class="inline-flex h-[18px] w-[18px] shrink-0 cursor-pointer items-center justify-center rounded-full border-0 bg-transparent p-0 text-status-info transition-colors duration-100 hover:bg-brand-05/25 hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-05"
|
|
79
78
|
onclick={(e) => { e.stopPropagation(); (() => removeTag(i))(); }}
|
|
80
79
|
>
|
|
81
80
|
<svg viewBox="0 0 10 10" width="10" height="10" fill="none" aria-hidden="true" focusable="false">
|
|
@@ -89,12 +88,12 @@
|
|
|
89
88
|
bind:this={inputEl}
|
|
90
89
|
id={uid}
|
|
91
90
|
type="text"
|
|
92
|
-
class="tag-input__field"
|
|
93
91
|
bind:value={draft}
|
|
94
92
|
{disabled}
|
|
95
93
|
placeholder={value.length === 0 ? placeholder : ''}
|
|
96
94
|
aria-label={label ?? 'Add tag'}
|
|
97
95
|
autocomplete="off"
|
|
96
|
+
class="h-6 min-w-[80px] flex-1 border-0 bg-transparent px-1 text-13 leading-none text-text-1 outline-none placeholder:text-text-4"
|
|
98
97
|
onkeydown={handleKeyDown}
|
|
99
98
|
onblur={handleBlur}
|
|
100
99
|
/>
|
|
@@ -37,9 +37,15 @@
|
|
|
37
37
|
let inputId = $derived(id ?? uid);
|
|
38
38
|
</script>
|
|
39
39
|
|
|
40
|
+
<!--
|
|
41
|
+
Textarea reuses the dashboard.css/Input layout (.input + .textarea sit
|
|
42
|
+
together; the `auto` height + min-height: 80px + resize: vertical + 8px
|
|
43
|
+
vertical padding + 1.5 line-height overrides come from .textarea). All
|
|
44
|
+
inline now.
|
|
45
|
+
-->
|
|
40
46
|
<textarea
|
|
41
47
|
id={inputId}
|
|
42
|
-
class="
|
|
48
|
+
class="block min-h-20 w-full resize-y rounded-md border border-border-2 bg-bg-page px-3 py-2 font-[inherit] text-13 leading-[1.5] text-text-1 outline-none transition-[border-color] duration-100 focus:border-brand-05 focus:[box-shadow:0_0_0_2px_rgba(0,111,255,0.2)] aria-[invalid='true']:border-status-danger aria-[invalid='true']:focus:[box-shadow:0_0_0_2px_rgba(240,58,58,0.12)] {className}"
|
|
43
49
|
{style}
|
|
44
50
|
{rows}
|
|
45
51
|
{placeholder}
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
|
-
|
|
7
|
-
|
|
8
6
|
interface Props {
|
|
9
7
|
label?: string | undefined;
|
|
10
8
|
value?: string;
|
|
@@ -73,13 +71,21 @@
|
|
|
73
71
|
field === 0 ? commit(clamped, minutes) : commit(hours, clamped);
|
|
74
72
|
if (raw.length === 2 && field === 0) setTimeout(() => inputs[1]?.focus(), 0);
|
|
75
73
|
}
|
|
74
|
+
|
|
75
|
+
const FIELD_CLS =
|
|
76
|
+
'w-9 border-0 bg-transparent p-0 text-center font-mono text-13 text-text-1 outline-none';
|
|
76
77
|
</script>
|
|
77
78
|
|
|
78
|
-
<div class=
|
|
79
|
+
<div class={className}>
|
|
79
80
|
{#if label}
|
|
80
|
-
<label for="{uid}-h" class="
|
|
81
|
+
<label for="{uid}-h" class="text-12 text-text-4">{label}</label>
|
|
81
82
|
{/if}
|
|
82
|
-
<div
|
|
83
|
+
<div
|
|
84
|
+
role="group"
|
|
85
|
+
aria-label={label ?? 'Time'}
|
|
86
|
+
data-disabled={disabled ? 'true' : undefined}
|
|
87
|
+
class="inline-flex h-[34px] items-center gap-0.5 rounded-md border border-border-2 bg-bg-page px-2 transition-colors duration-100 focus-within:border-brand-05 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-40"
|
|
88
|
+
>
|
|
83
89
|
<input
|
|
84
90
|
bind:this={inputs[0]}
|
|
85
91
|
id="{uid}-h"
|
|
@@ -95,12 +101,12 @@
|
|
|
95
101
|
value={pad(hours)}
|
|
96
102
|
{disabled}
|
|
97
103
|
maxlength={2}
|
|
98
|
-
class=
|
|
104
|
+
class={FIELD_CLS}
|
|
99
105
|
oninput={(e) => handleInput(0, e)}
|
|
100
106
|
onkeydown={(e) => handleKey(0, e)}
|
|
101
107
|
onfocus={(e) => e.currentTarget.select()}
|
|
102
108
|
/>
|
|
103
|
-
<span class="
|
|
109
|
+
<span aria-hidden="true" class="select-none text-13 leading-none text-text-4">:</span>
|
|
104
110
|
<input
|
|
105
111
|
bind:this={inputs[1]}
|
|
106
112
|
type="text"
|
|
@@ -115,7 +121,7 @@
|
|
|
115
121
|
value={pad(minutes)}
|
|
116
122
|
{disabled}
|
|
117
123
|
maxlength={2}
|
|
118
|
-
class=
|
|
124
|
+
class={FIELD_CLS}
|
|
119
125
|
oninput={(e) => handleInput(1, e)}
|
|
120
126
|
onkeydown={(e) => handleKey(1, e)}
|
|
121
127
|
onfocus={(e) => e.currentTarget.select()}
|
|
@@ -12,21 +12,19 @@
|
|
|
12
12
|
|
|
13
13
|
<script lang="ts">
|
|
14
14
|
import { onMount, onDestroy } from 'svelte';
|
|
15
|
+
import Button from './Button.svelte';
|
|
15
16
|
|
|
16
17
|
interface Props {
|
|
17
18
|
value?: TimeRangeId;
|
|
18
19
|
onchange?: (payload: TimeRangeId) => void;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
let { value = '1h',
|
|
22
|
-
onchange,
|
|
23
|
-
}: Props = $props();
|
|
22
|
+
let { value = '1h', onchange }: Props = $props();
|
|
24
23
|
const uid = `dash-ui-tr-${++counter}`;
|
|
25
24
|
const listId = `${uid}-list`;
|
|
26
25
|
|
|
27
26
|
let open = $state(false);
|
|
28
27
|
let activeIdx = $state(0);
|
|
29
|
-
let triggerEl = $state<HTMLButtonElement | undefined>(undefined);
|
|
30
28
|
let wrapperEl = $state<HTMLDivElement | undefined>(undefined);
|
|
31
29
|
|
|
32
30
|
let selected = $derived(PRESETS.find((p) => p.id === value) ?? PRESETS[0]);
|
|
@@ -39,7 +37,6 @@
|
|
|
39
37
|
function select(id: TimeRangeId) {
|
|
40
38
|
onchange?.(id);
|
|
41
39
|
open = false;
|
|
42
|
-
triggerEl?.focus();
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
function onKeyDown(e: KeyboardEvent) {
|
|
@@ -51,7 +48,7 @@
|
|
|
51
48
|
}
|
|
52
49
|
return;
|
|
53
50
|
}
|
|
54
|
-
if (e.key === 'Escape') { e.preventDefault(); open = false;
|
|
51
|
+
if (e.key === 'Escape') { e.preventDefault(); open = false; }
|
|
55
52
|
else if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = Math.min(activeIdx + 1, PRESETS.length - 1); }
|
|
56
53
|
else if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = Math.max(activeIdx - 1, 0); }
|
|
57
54
|
else if (e.key === 'Home') { e.preventDefault(); activeIdx = 0; }
|
|
@@ -68,31 +65,32 @@
|
|
|
68
65
|
onDestroy(() => document.removeEventListener('mousedown', handleOutside));
|
|
69
66
|
</script>
|
|
70
67
|
|
|
71
|
-
<div
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
aria-controls={open ? listId : undefined}
|
|
79
|
-
onclick={toggle}
|
|
80
|
-
onkeydown={onKeyDown}
|
|
81
|
-
>
|
|
68
|
+
<div
|
|
69
|
+
class="relative inline-block"
|
|
70
|
+
bind:this={wrapperEl}
|
|
71
|
+
onkeydown={onKeyDown}
|
|
72
|
+
role="presentation"
|
|
73
|
+
>
|
|
74
|
+
<Button variant="ghost" aria-haspopup="listbox" aria-expanded={open} aria-controls={open ? listId : undefined} onclick={toggle}>
|
|
82
75
|
{selected.short}
|
|
83
|
-
<svg
|
|
76
|
+
<svg width="10" height="6" viewBox="0 0 10 6" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 transition-transform duration-100 motion-reduce:transition-none {open ? 'rotate-180' : ''}">
|
|
84
77
|
<path d="M1 1l4 4 4-4" />
|
|
85
78
|
</svg>
|
|
86
|
-
</
|
|
79
|
+
</Button>
|
|
87
80
|
{#if open}
|
|
88
|
-
<ul
|
|
81
|
+
<ul
|
|
82
|
+
id={listId}
|
|
83
|
+
role="listbox"
|
|
84
|
+
aria-label="Time range"
|
|
85
|
+
class="absolute right-0 top-[calc(100%+4px)] z-[9100] m-0 min-w-[152px] list-none rounded-lg border border-border-3 bg-bg-2 p-1 shadow-[0_8px_24px_rgba(0,0,0,0.5)]"
|
|
86
|
+
>
|
|
89
87
|
{#each PRESETS as p, idx (p.id)}
|
|
90
88
|
<li
|
|
91
89
|
role="option"
|
|
92
90
|
aria-selected={p.id === value}
|
|
93
|
-
tabindex=
|
|
91
|
+
tabindex={-1}
|
|
94
92
|
data-active={idx === activeIdx ? 'true' : undefined}
|
|
95
|
-
class="
|
|
93
|
+
class="flex cursor-pointer items-center justify-between gap-2 whitespace-nowrap rounded-[5px] px-2.5 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 aria-selected:text-status-info"
|
|
96
94
|
onmouseenter={() => { activeIdx = idx; }}
|
|
97
95
|
onmousedown={(e) => { e.preventDefault(); (() => select(p.id))(); }}
|
|
98
96
|
>
|
|
@@ -15,20 +15,31 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
let { events, class: className = '' }: Props = $props();
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
type V = NonNullable<TimelineEvent['variant']>;
|
|
20
|
+
const VARIANT: Record<V, string> = {
|
|
21
|
+
info: 'bg-brand-05',
|
|
22
|
+
success: 'bg-status-success',
|
|
23
|
+
warn: 'bg-status-warning',
|
|
24
|
+
danger: 'bg-status-danger',
|
|
25
|
+
neutral: 'bg-status-neutral',
|
|
26
|
+
};
|
|
19
27
|
</script>
|
|
20
28
|
|
|
21
|
-
<ol class="
|
|
29
|
+
<ol class="relative m-0 list-none p-0 {className}" aria-label="Event timeline">
|
|
22
30
|
{#each events as e (e.id)}
|
|
23
|
-
<li class="
|
|
24
|
-
<span
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
<li class="relative flex gap-3 pb-3 pl-4 last:pb-0 [&:not(:last-child)]:before:absolute [&:not(:last-child)]:before:bottom-0 [&:not(:last-child)]:before:left-[5px] [&:not(:last-child)]:before:top-4 [&:not(:last-child)]:before:w-px [&:not(:last-child)]:before:bg-row-active [&:not(:last-child)]:before:content-['']">
|
|
32
|
+
<span
|
|
33
|
+
class="absolute left-0 top-1.5 inline-block h-2.5 w-2.5 shrink-0 rounded-full {VARIANT[e.variant ?? 'neutral']}"
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
></span>
|
|
36
|
+
<div class="min-w-0 flex-1">
|
|
37
|
+
<div class="flex items-baseline justify-between gap-2">
|
|
38
|
+
<span class="font-medium text-text-1">{e.title}</span>
|
|
39
|
+
<time class="shrink-0 text-11 text-text-4 tabular-nums">{e.time}</time>
|
|
29
40
|
</div>
|
|
30
41
|
{#if e.description}
|
|
31
|
-
<p class="
|
|
42
|
+
<p class="m-0 mt-0.5 text-12 text-text-3">{e.description}</p>
|
|
32
43
|
{/if}
|
|
33
44
|
</div>
|
|
34
45
|
</li>
|
|
@@ -30,16 +30,35 @@
|
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
<script lang="ts">
|
|
33
|
-
// store
|
|
33
|
+
// store + dismiss live in module scope; template uses $store
|
|
34
|
+
// Pre-composed variant classes for Tailwind's static scanner.
|
|
35
|
+
// Backgrounds at 16% (matches dashboard.css), left border solid, text
|
|
36
|
+
// colour the legibility-tuned literal hue.
|
|
37
|
+
const TOAST: Record<ToastVariant, string> = {
|
|
38
|
+
success: 'bg-status-success/16 border-l-status-success text-status-success',
|
|
39
|
+
warn: 'bg-status-warning/16 border-l-status-warning text-status-warning',
|
|
40
|
+
danger: 'bg-status-danger/16 border-l-status-danger text-status-danger',
|
|
41
|
+
info: 'bg-status-info/16 border-l-brand-05 text-status-info',
|
|
42
|
+
};
|
|
34
43
|
</script>
|
|
35
44
|
|
|
36
|
-
<div
|
|
45
|
+
<div
|
|
46
|
+
class="pointer-events-none fixed bottom-6 right-6 z-[9900] flex min-w-[240px] max-w-[360px] flex-col gap-2"
|
|
47
|
+
role="log"
|
|
48
|
+
aria-label="Notifications"
|
|
49
|
+
aria-live="polite"
|
|
50
|
+
aria-atomic="false"
|
|
51
|
+
>
|
|
37
52
|
{#each $store as item (item.id)}
|
|
38
|
-
<div
|
|
39
|
-
|
|
53
|
+
<div
|
|
54
|
+
class="pointer-events-auto flex items-center gap-2.5 rounded-lg border-l-[3px] px-3.5 py-2.5 text-13 leading-[1.4] [animation:toast-in_150ms_ease] [box-shadow:0_4px_16px_rgba(0,0,0,0.5)]
|
|
55
|
+
{TOAST[item.variant]}"
|
|
56
|
+
role={item.variant === 'danger' ? 'alert' : 'status'}
|
|
57
|
+
>
|
|
58
|
+
<span class="flex-1">{item.message}</span>
|
|
40
59
|
<button
|
|
41
60
|
type="button"
|
|
42
|
-
class="
|
|
61
|
+
class="ml-auto inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent text-current hover:bg-row-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
43
62
|
onclick={() => dismiss(item.id)}
|
|
44
63
|
aria-label="Dismiss notification"
|
|
45
64
|
>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
2
|
interface Props {
|
|
4
3
|
on?: boolean;
|
|
5
4
|
ariaLabel?: string | undefined;
|
|
@@ -7,7 +6,10 @@
|
|
|
7
6
|
ontoggle?: (payload: boolean) => void;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
let {
|
|
9
|
+
let {
|
|
10
|
+
on = $bindable(false),
|
|
11
|
+
ariaLabel = undefined,
|
|
12
|
+
ariaDescribedBy = undefined,
|
|
11
13
|
ontoggle,
|
|
12
14
|
}: Props = $props();
|
|
13
15
|
function flip() {
|
|
@@ -22,8 +24,12 @@
|
|
|
22
24
|
aria-checked={on}
|
|
23
25
|
aria-label={ariaLabel}
|
|
24
26
|
aria-describedby={ariaDescribedBy}
|
|
25
|
-
class="
|
|
27
|
+
class="relative inline-block h-[18px] w-8 shrink-0 cursor-pointer appearance-none rounded-full border-0 p-0 font-[inherit] text-[inherit] transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05
|
|
28
|
+
{on ? 'bg-brand-05' : 'bg-text-4'}"
|
|
26
29
|
onclick={flip}
|
|
27
30
|
>
|
|
28
|
-
<span
|
|
31
|
+
<span
|
|
32
|
+
class="absolute top-0.5 h-3.5 w-3.5 rounded-full bg-white transition-[left] duration-150
|
|
33
|
+
{on ? 'left-4' : 'left-0.5'}"
|
|
34
|
+
></span>
|
|
29
35
|
</button>
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
2
|
interface Props {
|
|
5
3
|
options?: { value: string; label: string; disabled?: boolean }[];
|
|
6
4
|
value?: string[];
|
|
@@ -18,20 +16,32 @@
|
|
|
18
16
|
class: klass = '',
|
|
19
17
|
onchange,
|
|
20
18
|
}: Props = $props();
|
|
19
|
+
|
|
21
20
|
function toggle(v: string) {
|
|
22
21
|
const next = value.includes(v) ? value.filter((x) => x !== v) : [...value, v];
|
|
23
22
|
value = next;
|
|
24
23
|
onchange?.(next);
|
|
25
24
|
}
|
|
25
|
+
|
|
26
|
+
// Pre-composed size strings — Tailwind's static scanner must see literals.
|
|
27
|
+
const SIZE: Record<NonNullable<Props['size']>, string> = {
|
|
28
|
+
sm: 'h-6 px-[9px] text-11',
|
|
29
|
+
md: 'h-7 px-3 text-12',
|
|
30
|
+
};
|
|
26
31
|
</script>
|
|
27
32
|
|
|
28
|
-
<div
|
|
33
|
+
<div
|
|
34
|
+
role="group"
|
|
35
|
+
aria-label={ariaLabel}
|
|
36
|
+
class="inline-flex flex-wrap gap-px rounded-lg bg-row-hover p-0.5 {klass}"
|
|
37
|
+
>
|
|
29
38
|
{#each options as opt}
|
|
39
|
+
{@const on = value.includes(opt.value)}
|
|
30
40
|
<button
|
|
31
41
|
type="button"
|
|
32
|
-
aria-pressed={
|
|
42
|
+
aria-pressed={on}
|
|
33
43
|
disabled={opt.disabled}
|
|
34
|
-
class="
|
|
44
|
+
class="flex-shrink-0 grow-0 inline-flex cursor-pointer select-none items-center justify-center whitespace-nowrap rounded-md border-0 bg-transparent 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 {SIZE[size]} {on ? '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)]' : ''}"
|
|
35
45
|
onclick={() => toggle(opt.value)}
|
|
36
46
|
>
|
|
37
47
|
{opt.label}
|
|
@@ -14,13 +14,28 @@
|
|
|
14
14
|
label,
|
|
15
15
|
placement = 'top',
|
|
16
16
|
class: className = '',
|
|
17
|
-
children
|
|
17
|
+
children,
|
|
18
18
|
}: Props = $props();
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
const id = `dash-ui-tooltip-${++counter}`;
|
|
21
|
+
|
|
22
|
+
// Pre-composed placement strings so Tailwind's scanner picks each up.
|
|
23
|
+
// The wrapper carries `group` so the content shows on group-hover or
|
|
24
|
+
// group-focus-within (replaces dashboard.css's `:hover .tooltip-content`).
|
|
25
|
+
const PLACEMENT: Record<NonNullable<Props['placement']>, string> = {
|
|
26
|
+
top: 'bottom-[calc(100%+6px)] left-1/2 -translate-x-1/2',
|
|
27
|
+
bottom: 'top-[calc(100%+6px)] left-1/2 -translate-x-1/2',
|
|
28
|
+
left: 'right-[calc(100%+6px)] top-1/2 -translate-y-1/2',
|
|
29
|
+
right: 'left-[calc(100%+6px)] top-1/2 -translate-y-1/2',
|
|
30
|
+
};
|
|
21
31
|
</script>
|
|
22
32
|
|
|
23
|
-
<span class="
|
|
24
|
-
<span class="
|
|
25
|
-
<span
|
|
33
|
+
<span class="group relative inline-flex {className}">
|
|
34
|
+
<span class="inline-flex" aria-describedby={id}>{@render children?.()}</span>
|
|
35
|
+
<span
|
|
36
|
+
{id}
|
|
37
|
+
role="tooltip"
|
|
38
|
+
class="pointer-events-none absolute z-[9999] whitespace-nowrap rounded border border-border-1 bg-bg-2 px-2 py-1 text-[11px] leading-[1.3] text-text-2 opacity-0 transition-opacity duration-100 group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none
|
|
39
|
+
{PLACEMENT[placement]}"
|
|
40
|
+
>{label}</span>
|
|
26
41
|
</span>
|