@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
|
@@ -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)}
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
<script lang="ts">
|
|
44
44
|
import { onMount, onDestroy } from 'svelte';
|
|
45
|
+
import Button from './Button.svelte';
|
|
45
46
|
|
|
46
47
|
interface Props {
|
|
47
48
|
value?: DateRange;
|
|
@@ -50,7 +51,10 @@
|
|
|
50
51
|
onchange?: (payload: DateRange) => void;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
let {
|
|
54
|
+
let {
|
|
55
|
+
value = { start: null, end: null },
|
|
56
|
+
placeholder = 'Select date range',
|
|
57
|
+
disabled = false,
|
|
54
58
|
onchange,
|
|
55
59
|
}: Props = $props();
|
|
56
60
|
const uid = `dash-ui-drp-${++counter}`;
|
|
@@ -62,7 +66,7 @@
|
|
|
62
66
|
let hoverDate: Date | null = $state(null);
|
|
63
67
|
let viewYear = $state(today.getFullYear());
|
|
64
68
|
let viewMonth = $state(today.getMonth());
|
|
65
|
-
let triggerEl = $state<
|
|
69
|
+
let triggerEl = $state<HTMLDivElement | undefined>(undefined);
|
|
66
70
|
let dlgEl = $state<HTMLDivElement | undefined>(undefined);
|
|
67
71
|
|
|
68
72
|
let rightMonth = $derived(viewMonth === 11 ? 0 : viewMonth + 1);
|
|
@@ -90,7 +94,6 @@
|
|
|
90
94
|
hoverDate = null;
|
|
91
95
|
onchange?.({ start: s, end: e });
|
|
92
96
|
open = false;
|
|
93
|
-
triggerEl?.focus();
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -128,55 +131,59 @@
|
|
|
128
131
|
|
|
129
132
|
onMount(() => document.addEventListener('mousedown', handleOutside));
|
|
130
133
|
onDestroy(() => document.removeEventListener('mousedown', handleOutside));
|
|
134
|
+
|
|
135
|
+
function dayCls(outside: boolean, isTodayDay: boolean, start: boolean, end: boolean, inRange: boolean) {
|
|
136
|
+
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';
|
|
137
|
+
if (outside) cls += ' text-text-4';
|
|
138
|
+
else cls += ' text-text-2';
|
|
139
|
+
if (isTodayDay && !start && !end) cls += ' ring-1 ring-inset ring-brand-05';
|
|
140
|
+
if (start || end) cls += ' bg-brand-05 text-white hover:bg-brand-06';
|
|
141
|
+
else if (inRange) cls += ' bg-brand-05/20 text-text-1';
|
|
142
|
+
return cls;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const NAV_BTN =
|
|
146
|
+
'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';
|
|
131
147
|
</script>
|
|
132
148
|
|
|
133
|
-
<div class="
|
|
134
|
-
<
|
|
135
|
-
bind:this={triggerEl}
|
|
136
|
-
type="button"
|
|
137
|
-
class="btn dp-trigger"
|
|
138
|
-
aria-haspopup="dialog"
|
|
139
|
-
aria-expanded={open}
|
|
140
|
-
aria-controls={open ? dlgId : undefined}
|
|
141
|
-
{disabled}
|
|
142
|
-
onclick={() => { if (open) { open = false; picking = null; } else open = true; }}
|
|
143
|
-
>
|
|
149
|
+
<div class="relative inline-block" bind:this={triggerEl}>
|
|
150
|
+
<Button variant="ghost" aria-haspopup="dialog" aria-expanded={open} aria-controls={open ? dlgId : undefined} disabled={disabled} onclick={() => { if (open) { open = false; picking = null; } else open = true; }}>
|
|
144
151
|
<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">
|
|
145
152
|
<rect x="1" y="2" width="12" height="11" rx="1.5" />
|
|
146
153
|
<path d="M1 6h12M4 1v2M10 1v2" />
|
|
147
154
|
</svg>
|
|
148
155
|
{formatRange() ?? placeholder}
|
|
149
|
-
</
|
|
156
|
+
</Button>
|
|
150
157
|
|
|
151
158
|
{#if open}
|
|
152
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions
|
|
159
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
153
160
|
<div
|
|
154
161
|
id={dlgId}
|
|
155
162
|
bind:this={dlgEl}
|
|
156
163
|
role="dialog"
|
|
157
164
|
aria-modal="true"
|
|
158
165
|
aria-label="Select date range"
|
|
159
|
-
tabindex=
|
|
160
|
-
class="
|
|
166
|
+
tabindex={-1}
|
|
167
|
+
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)]"
|
|
161
168
|
onkeydown={(e) => {
|
|
162
|
-
if (e.key === 'Escape') { e.preventDefault(); open = false; picking = null;
|
|
169
|
+
if (e.key === 'Escape') { e.preventDefault(); open = false; picking = null; }
|
|
163
170
|
}}
|
|
164
171
|
>
|
|
165
|
-
<div class="
|
|
172
|
+
<div class="flex gap-3">
|
|
166
173
|
<!-- Left calendar -->
|
|
167
|
-
<div
|
|
168
|
-
<div class="
|
|
169
|
-
<button type="button" class=
|
|
174
|
+
<div>
|
|
175
|
+
<div class="flex items-center justify-between px-1 py-1">
|
|
176
|
+
<button type="button" class={NAV_BTN} onclick={prevMonth} aria-label="Previous month">
|
|
170
177
|
<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">
|
|
171
178
|
<path d="M7 1L2 6l5 5" />
|
|
172
179
|
</svg>
|
|
173
180
|
</button>
|
|
174
|
-
<span class="
|
|
175
|
-
<span
|
|
181
|
+
<span class="text-13 font-medium text-text-1" aria-live="polite">{MONTHS[viewMonth]} {viewYear}</span>
|
|
182
|
+
<span class="inline-block w-7" aria-hidden="true"></span>
|
|
176
183
|
</div>
|
|
177
|
-
<table role="grid" class="
|
|
184
|
+
<table role="grid" class="mt-1 border-collapse" aria-label={`${MONTHS[viewMonth]} ${viewYear}`}>
|
|
178
185
|
<thead>
|
|
179
|
-
<tr>{#each DAYS as d}<th scope="col" abbr={d}>{d}</th>{/each}</tr>
|
|
186
|
+
<tr>{#each DAYS as d}<th scope="col" abbr={d} class="p-0 text-center text-11 font-semibold uppercase tracking-[0.05em] text-text-4">{d}</th>{/each}</tr>
|
|
180
187
|
</thead>
|
|
181
188
|
<tbody>
|
|
182
189
|
{#each Array(6) as _, row}
|
|
@@ -189,10 +196,10 @@
|
|
|
189
196
|
{@const start = isStart(day)}
|
|
190
197
|
{@const end = isEnd(day)}
|
|
191
198
|
{@const inRange = isInRange(day)}
|
|
192
|
-
<td role="gridcell" aria-selected={start || end}>
|
|
199
|
+
<td role="gridcell" aria-selected={start || end} class="p-0">
|
|
193
200
|
<button
|
|
194
201
|
type="button"
|
|
195
|
-
class=
|
|
202
|
+
class={dayCls(outside, isTodayDay, start, end, inRange)}
|
|
196
203
|
aria-label={day.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}
|
|
197
204
|
onclick={() => handleDayClick(day)}
|
|
198
205
|
onmouseenter={() => { if (picking) hoverDate = day; }}
|
|
@@ -206,19 +213,19 @@
|
|
|
206
213
|
</table>
|
|
207
214
|
</div>
|
|
208
215
|
<!-- Right calendar -->
|
|
209
|
-
<div
|
|
210
|
-
<div class="
|
|
211
|
-
<span
|
|
212
|
-
<span class="
|
|
213
|
-
<button type="button" class=
|
|
216
|
+
<div>
|
|
217
|
+
<div class="flex items-center justify-between px-1 py-1">
|
|
218
|
+
<span class="inline-block w-7" aria-hidden="true"></span>
|
|
219
|
+
<span class="text-13 font-medium text-text-1" aria-live="polite">{MONTHS[rightMonth]} {rightYear}</span>
|
|
220
|
+
<button type="button" class={NAV_BTN} onclick={nextMonth} aria-label="Next month">
|
|
214
221
|
<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">
|
|
215
222
|
<path d="M1 1l5 5-5 5" />
|
|
216
223
|
</svg>
|
|
217
224
|
</button>
|
|
218
225
|
</div>
|
|
219
|
-
<table role="grid" class="
|
|
226
|
+
<table role="grid" class="mt-1 border-collapse" aria-label={`${MONTHS[rightMonth]} ${rightYear}`}>
|
|
220
227
|
<thead>
|
|
221
|
-
<tr>{#each DAYS as d}<th scope="col" abbr={d}>{d}</th>{/each}</tr>
|
|
228
|
+
<tr>{#each DAYS as d}<th scope="col" abbr={d} class="p-0 text-center text-11 font-semibold uppercase tracking-[0.05em] text-text-4">{d}</th>{/each}</tr>
|
|
222
229
|
</thead>
|
|
223
230
|
<tbody>
|
|
224
231
|
{#each Array(6) as _, row}
|
|
@@ -231,10 +238,10 @@
|
|
|
231
238
|
{@const start = isStart(day)}
|
|
232
239
|
{@const end = isEnd(day)}
|
|
233
240
|
{@const inRange = isInRange(day)}
|
|
234
|
-
<td role="gridcell" aria-selected={start || end}>
|
|
241
|
+
<td role="gridcell" aria-selected={start || end} class="p-0">
|
|
235
242
|
<button
|
|
236
243
|
type="button"
|
|
237
|
-
class=
|
|
244
|
+
class={dayCls(outside, isTodayDay, start, end, inRange)}
|
|
238
245
|
aria-label={day.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}
|
|
239
246
|
onclick={() => handleDayClick(day)}
|
|
240
247
|
onmouseenter={() => { if (picking) hoverDate = day; }}
|
|
@@ -249,7 +256,7 @@
|
|
|
249
256
|
</div>
|
|
250
257
|
</div>
|
|
251
258
|
{#if picking}
|
|
252
|
-
<p class="
|
|
259
|
+
<p class="mt-2 px-1 text-12 text-status-info" aria-live="polite">Pick the end date</p>
|
|
253
260
|
{/if}
|
|
254
261
|
</div>
|
|
255
262
|
{/if}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
dir: 'up' | 'down' | 'flat';
|
|
4
|
+
class?: string;
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { dir, class: className = '', children }: Props = $props();
|
|
9
|
+
|
|
10
|
+
let deltaClass = $derived(dir === 'flat' ? '' : `delta-${dir}`);
|
|
11
|
+
let merged = $derived(`${deltaClass} ${className}`.trim());
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<!--
|
|
15
|
+
Trend indicator span used inside <Submeta> or any inline copy where a delta
|
|
16
|
+
needs to be colour-coded (success when up, danger when down, neutral when
|
|
17
|
+
flat). Emits a visually-hidden "Trend: " prefix so screen readers announce
|
|
18
|
+
the direction independent of any arrow glyph supplied by the caller. Typed
|
|
19
|
+
equivalent of `<span class="delta-up | delta-down">` from
|
|
20
|
+
@w5-ui/tokens/dashboard.css.
|
|
21
|
+
-->
|
|
22
|
+
<span class="sr-only">Trend: </span>
|
|
23
|
+
<span class={merged || undefined}>
|
|
24
|
+
{@render children?.()}
|
|
25
|
+
</span>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let { class: className = '', children }: Props = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
Display-1 - the 64 px / bold / --lh-tight / --tracking-display
|
|
12
|
+
(-0.02em) rhythm reserved for onboarding splashes and marketing
|
|
13
|
+
heroes (the top of the type scale, one step above the 48 px
|
|
14
|
+
<Display2>). Renders <h1 class="dash-ui-display-1"> so the
|
|
15
|
+
.dash-ui-display-1 rule in @w5-ui/tokens/tokens.css pins the canonical
|
|
16
|
+
size, weight, line-height, letter-spacing, and --text-1 colour. Stops
|
|
17
|
+
userland reaching for an ad-hoc <h1 style="font-size: 64px"> that
|
|
18
|
+
almost always forgets --lh-tight (1.1) and the negative tracking,
|
|
19
|
+
which is the documented "headings break awkwardly across two lines"
|
|
20
|
+
drift in Foundations / Type. Typed equivalent of
|
|
21
|
+
<h1 class="dash-ui-display-1"> (the userland-facing semantic class
|
|
22
|
+
documented in Foundations / Type). Use for onboarding splashes or
|
|
23
|
+
large marketing heroes. In-app pages should reach for <PageHeader>
|
|
24
|
+
(page title) and <H2> (section heads); never substitute <Display1>
|
|
25
|
+
for an in-app screen - its 64 px rhythm dwarfs dashboard chrome.
|
|
26
|
+
-->
|
|
27
|
+
<h1 class="dash-ui-display-1 {className}">
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</h1>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let { class: className = '', children }: Props = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
Display-2 - the 48 px / bold / --lh-tight / --tracking-display
|
|
12
|
+
(-0.02em) rhythm used for login and large marketing surfaces (the
|
|
13
|
+
step below the 64 px Display-1 hero). Renders
|
|
14
|
+
<h1 class="dash-ui-display-2"> so the .dash-ui-display-2 rule in
|
|
15
|
+
@w5-ui/tokens/tokens.css pins the canonical size, weight, line-
|
|
16
|
+
height, letter-spacing, and --text-1 colour. Stops userland reaching
|
|
17
|
+
for an ad-hoc <h1 style="font-size: 48px"> that almost always
|
|
18
|
+
forgets --lh-tight (1.1) and the negative tracking, which is the
|
|
19
|
+
documented "headings break awkwardly across two lines" drift in
|
|
20
|
+
Foundations / Type. Typed equivalent of <h1 class="dash-ui-display-2">
|
|
21
|
+
(the userland-facing semantic class documented in Foundations /
|
|
22
|
+
Type). Use for login-page titles or large marketing heroes one step
|
|
23
|
+
down from <Display1>. In-app pages should reach for <PageHeader>
|
|
24
|
+
(page title) and <H2> (section heads); never substitute <Display2>
|
|
25
|
+
for an in-app screen - its 48 px rhythm dominates dashboard chrome.
|
|
26
|
+
-->
|
|
27
|
+
<h1 class="dash-ui-display-2 {className}">
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</h1>
|
|
@@ -44,24 +44,25 @@
|
|
|
44
44
|
<svelte:window onkeydown={onKeydown} />
|
|
45
45
|
|
|
46
46
|
<div
|
|
47
|
-
class="
|
|
47
|
+
class="fixed inset-0 z-[60] bg-black/40 {open ? 'block' : 'hidden'}"
|
|
48
48
|
onclick={() => (open = false)}
|
|
49
49
|
aria-hidden="true"
|
|
50
50
|
role="presentation"
|
|
51
51
|
></div>
|
|
52
52
|
<div
|
|
53
53
|
bind:this={panelEl}
|
|
54
|
-
class="
|
|
54
|
+
class="fixed right-0 top-0 z-[61] flex h-full w-[380px] max-w-[90vw] flex-col border-l border-border-2 bg-bg-1 transition-transform duration-200 ease-out [box-shadow:-24px_0_64px_rgba(0,0,0,0.6)]
|
|
55
|
+
{open ? 'translate-x-0' : 'translate-x-full'}"
|
|
55
56
|
role="dialog"
|
|
56
57
|
aria-modal="true"
|
|
57
58
|
aria-labelledby={titleId}
|
|
58
59
|
tabindex="-1"
|
|
59
60
|
>
|
|
60
|
-
<div class="
|
|
61
|
-
<h2 id={titleId}>{title}</h2>
|
|
61
|
+
<div class="flex shrink-0 items-center justify-between border-b border-border-1 px-5 py-4">
|
|
62
|
+
<h2 id={titleId} class="m-0 text-16 font-semibold">{title}</h2>
|
|
62
63
|
<IconButton title="Close" onclick={() => (open = false)}>
|
|
63
64
|
<CloseIcon />
|
|
64
65
|
</IconButton>
|
|
65
66
|
</div>
|
|
66
|
-
<div class="
|
|
67
|
+
<div class="flex-1 overflow-y-auto px-5 py-4">{@render children?.()}</div>
|
|
67
68
|
</div>
|
|
@@ -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?: number;
|
|
@@ -39,7 +37,9 @@
|
|
|
39
37
|
|
|
40
38
|
let [hours, minutes, seconds] = $derived(toHMS(value));
|
|
41
39
|
|
|
42
|
-
let inputs: [HTMLInputElement | null, HTMLInputElement | null, HTMLInputElement | null] = $state([
|
|
40
|
+
let inputs: [HTMLInputElement | null, HTMLInputElement | null, HTMLInputElement | null] = $state([
|
|
41
|
+
null, null, null,
|
|
42
|
+
]);
|
|
43
43
|
|
|
44
44
|
function focusAt(i: number) {
|
|
45
45
|
const el = inputs[i];
|
|
@@ -91,13 +91,21 @@
|
|
|
91
91
|
|
|
92
92
|
let fieldVals = $derived([hours, minutes, seconds]);
|
|
93
93
|
let fieldMaxes = $derived([maxHours, 59, 59]);
|
|
94
|
+
|
|
95
|
+
const FIELD_CLS =
|
|
96
|
+
'w-9 border-0 bg-transparent p-0 text-center font-mono text-13 text-text-1 outline-none';
|
|
94
97
|
</script>
|
|
95
98
|
|
|
96
|
-
<div class=
|
|
99
|
+
<div class={className}>
|
|
97
100
|
{#if label}
|
|
98
|
-
<label for="{uid}-h" class="
|
|
101
|
+
<label for="{uid}-h" class="text-12 text-text-4">{label}</label>
|
|
99
102
|
{/if}
|
|
100
|
-
<div
|
|
103
|
+
<div
|
|
104
|
+
role="group"
|
|
105
|
+
aria-label={label ?? 'Duration'}
|
|
106
|
+
data-disabled={disabled ? 'true' : undefined}
|
|
107
|
+
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"
|
|
108
|
+
>
|
|
101
109
|
{#each FIELDS as { idx, ariaLabel }, i}
|
|
102
110
|
<input
|
|
103
111
|
bind:this={inputs[idx]}
|
|
@@ -114,13 +122,13 @@
|
|
|
114
122
|
value={pad(fieldVals[idx])}
|
|
115
123
|
{disabled}
|
|
116
124
|
maxlength={2}
|
|
117
|
-
class=
|
|
125
|
+
class={FIELD_CLS}
|
|
118
126
|
oninput={(e) => handleInput(idx, e)}
|
|
119
127
|
onkeydown={(e) => handleKey(idx, e)}
|
|
120
128
|
onfocus={(e) => e.currentTarget.select()}
|
|
121
129
|
/>
|
|
122
130
|
{#if i < 2}
|
|
123
|
-
<span class="
|
|
131
|
+
<span aria-hidden="true" class="select-none text-13 leading-none text-text-4">:</span>
|
|
124
132
|
{/if}
|
|
125
133
|
{/each}
|
|
126
134
|
</div>
|
|
@@ -10,22 +10,31 @@
|
|
|
10
10
|
title,
|
|
11
11
|
description = undefined,
|
|
12
12
|
class: className = '',
|
|
13
|
-
action
|
|
13
|
+
action,
|
|
14
14
|
}: Props = $props();
|
|
15
|
-
|
|
16
15
|
</script>
|
|
17
16
|
|
|
18
|
-
<div
|
|
19
|
-
|
|
17
|
+
<div
|
|
18
|
+
role="status"
|
|
19
|
+
aria-live="polite"
|
|
20
|
+
class="flex flex-col items-center justify-center gap-2.5 px-6 py-12 text-center {className}"
|
|
21
|
+
>
|
|
22
|
+
<svg
|
|
23
|
+
class="h-12 w-12 shrink-0 text-border-3"
|
|
24
|
+
viewBox="0 0 48 48"
|
|
25
|
+
fill="none"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
focusable="false"
|
|
28
|
+
>
|
|
20
29
|
<rect x="6" y="11" width="36" height="28" rx="4" stroke="currentColor" stroke-width="1.5"/>
|
|
21
30
|
<path d="M6 30h10l3 5h10l3-5h10" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
|
22
31
|
<path d="M15 20h18M15 25h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity=".4"/>
|
|
23
32
|
</svg>
|
|
24
|
-
<p class="
|
|
33
|
+
<p class="m-0 text-14 font-medium text-text-3">{title}</p>
|
|
25
34
|
{#if description}
|
|
26
|
-
<p class="
|
|
35
|
+
<p class="m-0 max-w-[280px] text-12 leading-[1.5] text-text-4">{description}</p>
|
|
27
36
|
{/if}
|
|
28
37
|
{#if action}
|
|
29
|
-
<div class="
|
|
38
|
+
<div class="mt-1.5">{@render action?.()}</div>
|
|
30
39
|
{/if}
|
|
31
40
|
</div>
|
|
@@ -6,35 +6,29 @@
|
|
|
6
6
|
detail?: import('svelte').Snippet;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
let {
|
|
10
|
-
colSpan,
|
|
11
|
-
defaultExpanded = false,
|
|
12
|
-
row,
|
|
13
|
-
detail
|
|
14
|
-
}: Props = $props();
|
|
9
|
+
let { colSpan, defaultExpanded = false, row, detail }: Props = $props();
|
|
15
10
|
|
|
16
11
|
// svelte-ignore state_referenced_locally
|
|
17
12
|
let open = $state(defaultExpanded);
|
|
18
13
|
const detailId = `exp-${Math.random().toString(36).slice(2, 9)}`;
|
|
19
14
|
</script>
|
|
20
15
|
|
|
21
|
-
<tbody
|
|
22
|
-
<tr
|
|
23
|
-
<td class="
|
|
16
|
+
<tbody>
|
|
17
|
+
<tr>
|
|
18
|
+
<td class="w-8 px-1">
|
|
24
19
|
<button
|
|
25
20
|
type="button"
|
|
26
|
-
class="exp-toggle"
|
|
27
21
|
aria-expanded={open}
|
|
28
22
|
aria-controls={detailId}
|
|
29
23
|
aria-label={open ? 'Collapse row' : 'Expand row'}
|
|
24
|
+
class="inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-text-4 transition-colors duration-100 hover:text-text-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
30
25
|
onclick={() => (open = !open)}
|
|
31
26
|
>
|
|
32
27
|
<svg
|
|
33
|
-
class="exp-chevron"
|
|
34
|
-
class:exp-chevron--open={open}
|
|
35
28
|
viewBox="0 0 10 6"
|
|
36
29
|
fill="none"
|
|
37
30
|
aria-hidden="true"
|
|
31
|
+
class="h-1.5 w-2.5 transition-transform duration-100 motion-reduce:transition-none {open ? 'rotate-180' : ''}"
|
|
38
32
|
>
|
|
39
33
|
<path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
40
34
|
</svg>
|
|
@@ -44,11 +38,10 @@
|
|
|
44
38
|
</tr>
|
|
45
39
|
<tr
|
|
46
40
|
id={detailId}
|
|
47
|
-
class="exp-detail"
|
|
48
|
-
class:exp-detail--open={open}
|
|
49
41
|
aria-hidden={!open}
|
|
42
|
+
class="{open ? 'table-row' : 'hidden'}"
|
|
50
43
|
>
|
|
51
|
-
<td colspan={colSpan + 1} class="
|
|
44
|
+
<td colspan={colSpan + 1} class="border-b border-border-1 bg-divider pb-3 pl-9 pr-3 pt-2">
|
|
52
45
|
{@render detail?.()}
|
|
53
46
|
</td>
|
|
54
47
|
</tr>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
class?: string;
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let { class: className = '', children }: Props = $props();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
Uppercase, tracked overline used as a section eyebrow / category tag
|
|
12
|
+
above a heading or beside a primary label. Renders
|
|
13
|
+
<span class="dash-ui-eyebrow"> so the .dash-ui-eyebrow rule in
|
|
14
|
+
@w5-ui/tokens/css applies the canonical 11 px / semibold weight,
|
|
15
|
+
0.08em letter-spacing, uppercase text-transform and the muted
|
|
16
|
+
--text-3 colour. Stops userland reaching for a one-off
|
|
17
|
+
text-transform: uppercase + letter-spacing + font-size: 11px trio
|
|
18
|
+
on every overline. Typed equivalent of
|
|
19
|
+
<span class="dash-ui-eyebrow"> (the contrast pair is AA against
|
|
20
|
+
every motif background, verified for --text-3 over
|
|
21
|
+
--depthBg-0/--depthBg-1/--depthBg-2 in both dark and light motifs).
|
|
22
|
+
-->
|
|
23
|
+
<span class="dash-ui-eyebrow {className}">
|
|
24
|
+
{@render children?.()}
|
|
25
|
+
</span>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
error = undefined,
|
|
21
21
|
required = false,
|
|
22
22
|
hint = undefined,
|
|
23
|
-
children
|
|
23
|
+
children,
|
|
24
24
|
}: Props = $props();
|
|
25
25
|
// svelte-ignore state_referenced_locally
|
|
26
26
|
const inputId = id ?? `dash-ui-field-${++counter}`;
|
|
@@ -29,18 +29,18 @@
|
|
|
29
29
|
let describedBy = $derived([hintId, errorId].filter(Boolean).join(' ') || undefined);
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
|
-
<div class="
|
|
33
|
-
<label for={inputId}>
|
|
34
|
-
{label}{#if required}<span class="
|
|
32
|
+
<div class="mb-3 flex flex-col gap-1.5">
|
|
33
|
+
<label for={inputId} class="text-12 font-medium text-text-3">
|
|
34
|
+
{label}{#if required}<span class="text-status-danger" aria-hidden="true"> *</span>{/if}
|
|
35
35
|
</label>
|
|
36
36
|
{#if hint}
|
|
37
|
-
<div class="
|
|
37
|
+
<div class="text-12 text-text-3" id={hintId}>{hint}</div>
|
|
38
38
|
{/if}
|
|
39
39
|
{#if children}
|
|
40
40
|
{@render children?.()}
|
|
41
41
|
{:else}
|
|
42
42
|
<input
|
|
43
|
-
class="
|
|
43
|
+
class="h-[34px] w-full rounded-md border border-border-2 bg-bg-page px-3 text-13 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)]"
|
|
44
44
|
id={inputId}
|
|
45
45
|
bind:value
|
|
46
46
|
{required}
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
/>
|
|
50
50
|
{/if}
|
|
51
51
|
{#if error}
|
|
52
|
-
<div class="
|
|
52
|
+
<div class="text-12 text-status-danger" id={errorId} role="alert">{error}</div>
|
|
53
53
|
{/if}
|
|
54
54
|
</div>
|