@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
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const TEXT_OPS = ['contains', 'not contains', 'equals', 'not equals', 'starts with'];
|
|
17
|
-
const NUM_OPS
|
|
18
|
-
const SEL_OPS
|
|
17
|
+
const NUM_OPS = ['equals', 'not equals', 'greater than', 'less than', 'at least', 'at most'];
|
|
18
|
+
const SEL_OPS = ['is', 'is not'];
|
|
19
19
|
|
|
20
20
|
function opsFor(type?: string) {
|
|
21
21
|
if (type === 'number') return NUM_OPS;
|
|
22
22
|
if (type === 'select') return SEL_OPS;
|
|
23
23
|
return TEXT_OPS;
|
|
24
24
|
}
|
|
25
|
-
function defaultOp(type?: string) {
|
|
25
|
+
function defaultOp(type?: string) {
|
|
26
|
+
return opsFor(type)[0];
|
|
27
|
+
}
|
|
26
28
|
</script>
|
|
27
29
|
|
|
28
30
|
<script lang="ts">
|
|
@@ -43,7 +45,6 @@
|
|
|
43
45
|
}: Props = $props();
|
|
44
46
|
|
|
45
47
|
const uid = `dash-ui-fb-${++counter}`;
|
|
46
|
-
|
|
47
48
|
const CONJUNCTIONS = ['and', 'or'] as const;
|
|
48
49
|
|
|
49
50
|
function emit(rules: FilterRule[], conj: 'and' | 'or' = conjunction) {
|
|
@@ -61,56 +62,77 @@
|
|
|
61
62
|
|
|
62
63
|
function setField(id: string, key: string) {
|
|
63
64
|
const f = fields.find((x) => x.key === key);
|
|
64
|
-
emit(value.map((r) => r.id === id ? { ...r, field: key, op: defaultOp(f?.type), value: '' } : r));
|
|
65
|
+
emit(value.map((r) => (r.id === id ? { ...r, field: key, op: defaultOp(f?.type), value: '' } : r)));
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
function setOp(id: string, op: string) {
|
|
68
|
-
emit(value.map((r) => r.id === id ? { ...r, op } : r));
|
|
69
|
+
emit(value.map((r) => (r.id === id ? { ...r, op } : r)));
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
function setValue(id: string, v: string) {
|
|
72
|
-
emit(value.map((r) => r.id === id ? { ...r, value: v } : r));
|
|
73
|
+
emit(value.map((r) => (r.id === id ? { ...r, value: v } : r)));
|
|
73
74
|
}
|
|
75
|
+
|
|
76
|
+
// Field chrome: select chevron uses a data-URL bg so the chrome stays in
|
|
77
|
+
// class strings rather than scoped CSS.
|
|
78
|
+
const SELECT_CHEVRON =
|
|
79
|
+
'background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'10\' height=\'6\' viewBox=\'0 0 10 6\'%3E%3Cpath d=\'M1 1l4 4 4-4\' stroke=\'%236E7079\' stroke-width=\'1.5\' fill=\'none\' stroke-linecap=\'round\'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;';
|
|
80
|
+
|
|
81
|
+
const SELECT_CLS =
|
|
82
|
+
'h-[30px] cursor-pointer appearance-none rounded border border-border-2 bg-row-active px-2 pr-6 text-13 leading-none text-text-1 outline-none transition-colors duration-100 hover:border-border-3 focus:border-brand-05';
|
|
83
|
+
const INPUT_CLS =
|
|
84
|
+
'h-[30px] min-w-[140px] flex-1 rounded border border-border-2 bg-row-active px-2 text-13 leading-none text-text-1 outline-none transition-colors duration-100 placeholder:text-text-4 hover:border-border-3 focus:border-brand-05';
|
|
74
85
|
</script>
|
|
75
86
|
|
|
76
|
-
<div class="
|
|
77
|
-
<div class="
|
|
78
|
-
<span class="
|
|
87
|
+
<div class="flex flex-col gap-1.5" role="group" aria-label={ariaLabel}>
|
|
88
|
+
<div class="flex min-h-8 flex-wrap items-center gap-2">
|
|
89
|
+
<span class="mr-1 text-12 font-semibold uppercase tracking-[0.05em] text-text-3">Filters</span>
|
|
79
90
|
{#if value.length > 0}
|
|
80
|
-
<span class="
|
|
81
|
-
<div class="
|
|
91
|
+
<span class="text-13 text-text-4">Match</span>
|
|
92
|
+
<div class="flex overflow-hidden rounded border border-border-2" role="group" aria-label="Conjunction">
|
|
82
93
|
{#each CONJUNCTIONS as c}
|
|
83
94
|
<button
|
|
84
95
|
type="button"
|
|
85
|
-
class="fb-conj-btn{conjunction === c ? ' is-active' : ''}"
|
|
86
96
|
aria-pressed={conjunction === c}
|
|
97
|
+
class="cursor-pointer border-0 bg-transparent px-2.5 py-0.5 text-11 font-bold uppercase tracking-[0.06em] text-text-4 hover:bg-row-active hover:text-text-1 aria-pressed:bg-brand-05/[0.18] aria-pressed:text-status-info focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-brand-05"
|
|
87
98
|
onclick={() => emit(value, c)}
|
|
88
99
|
>{c.toUpperCase()}</button>
|
|
89
100
|
{/each}
|
|
90
101
|
</div>
|
|
91
|
-
<span class="
|
|
102
|
+
<span class="text-13 text-text-4">conditions</span>
|
|
92
103
|
{/if}
|
|
93
|
-
<button
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
aria-label="Add filter rule"
|
|
107
|
+
class="ml-auto cursor-pointer rounded border border-dashed border-border-3 bg-transparent px-2.5 py-1 text-12 leading-none text-brand-05 hover:border-brand-05 hover:bg-brand-05/[0.08] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
108
|
+
onclick={addRule}
|
|
109
|
+
>+ Add rule</button>
|
|
94
110
|
{#if value.length > 0}
|
|
95
|
-
<button
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
aria-label="Clear all filters"
|
|
114
|
+
class="cursor-pointer border-0 bg-transparent px-1.5 py-1 text-12 leading-none text-text-4 hover:text-text-1 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
115
|
+
onclick={() => emit([])}
|
|
116
|
+
>Clear</button>
|
|
96
117
|
{/if}
|
|
97
118
|
</div>
|
|
98
119
|
{#if value.length > 0}
|
|
99
|
-
<ul class="
|
|
120
|
+
<ul class="m-0 flex list-none flex-col gap-1 p-0" aria-label="Filter rules">
|
|
100
121
|
{#each value as rule, i}
|
|
101
122
|
{@const f = fields.find((x) => x.key === rule.field)}
|
|
102
123
|
{@const ops = opsFor(f?.type)}
|
|
103
124
|
{@const fieldId = `${uid}-f${i}`}
|
|
104
125
|
{@const opId = `${uid}-op${i}`}
|
|
105
126
|
{@const valId = `${uid}-v${i}`}
|
|
106
|
-
<li class="
|
|
127
|
+
<li class="flex items-center gap-1.5">
|
|
107
128
|
<label class="sr-only" for={fieldId}>Field {i + 1}</label>
|
|
108
129
|
<select
|
|
109
130
|
id={fieldId}
|
|
110
|
-
class=
|
|
131
|
+
class={SELECT_CLS}
|
|
132
|
+
style={SELECT_CHEVRON}
|
|
111
133
|
value={rule.field}
|
|
112
134
|
onchange={(e) => setField(rule.id, e.currentTarget.value)}
|
|
113
|
-
aria-label=
|
|
135
|
+
aria-label={`Rule ${i + 1} field`}
|
|
114
136
|
>
|
|
115
137
|
{#each fields as x}
|
|
116
138
|
<option value={x.key}>{x.label}</option>
|
|
@@ -119,10 +141,11 @@
|
|
|
119
141
|
<label class="sr-only" for={opId}>Operator {i + 1}</label>
|
|
120
142
|
<select
|
|
121
143
|
id={opId}
|
|
122
|
-
class=
|
|
144
|
+
class={SELECT_CLS}
|
|
145
|
+
style={SELECT_CHEVRON}
|
|
123
146
|
value={rule.op}
|
|
124
147
|
onchange={(e) => setOp(rule.id, e.currentTarget.value)}
|
|
125
|
-
aria-label=
|
|
148
|
+
aria-label={`Rule ${i + 1} operator`}
|
|
126
149
|
>
|
|
127
150
|
{#each ops as op}
|
|
128
151
|
<option value={op}>{op}</option>
|
|
@@ -132,10 +155,11 @@
|
|
|
132
155
|
{#if f?.type === 'select'}
|
|
133
156
|
<select
|
|
134
157
|
id={valId}
|
|
135
|
-
class=
|
|
158
|
+
class={SELECT_CLS}
|
|
159
|
+
style={SELECT_CHEVRON}
|
|
136
160
|
value={rule.value}
|
|
137
161
|
onchange={(e) => setValue(rule.id, e.currentTarget.value)}
|
|
138
|
-
aria-label=
|
|
162
|
+
aria-label={`Rule ${i + 1} value`}
|
|
139
163
|
>
|
|
140
164
|
<option value="">Any</option>
|
|
141
165
|
{#each f.options ?? [] as o}
|
|
@@ -145,19 +169,19 @@
|
|
|
145
169
|
{:else}
|
|
146
170
|
<input
|
|
147
171
|
id={valId}
|
|
148
|
-
class=
|
|
172
|
+
class={INPUT_CLS}
|
|
149
173
|
type={f?.type === 'number' ? 'number' : 'text'}
|
|
150
174
|
value={rule.value}
|
|
151
175
|
placeholder="Value…"
|
|
152
176
|
oninput={(e) => setValue(rule.id, e.currentTarget.value)}
|
|
153
|
-
aria-label=
|
|
177
|
+
aria-label={`Rule ${i + 1} value`}
|
|
154
178
|
/>
|
|
155
179
|
{/if}
|
|
156
180
|
<button
|
|
157
181
|
type="button"
|
|
158
|
-
|
|
182
|
+
aria-label={`Remove rule ${i + 1}`}
|
|
183
|
+
class="flex h-[26px] w-[26px] shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent text-text-4 hover:bg-status-danger/10 hover:text-status-danger focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
159
184
|
onclick={() => removeRule(rule.id)}
|
|
160
|
-
aria-label="Remove rule {i + 1}"
|
|
161
185
|
>
|
|
162
186
|
<svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true" fill="currentColor">
|
|
163
187
|
<path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
Right-aligned action row closing a form <Card> or modal body
|
|
12
|
+
(display:flex; justify-content:flex-end; gap:8px; margin-top:16px).
|
|
13
|
+
Typed equivalent of `<div class="form-actions">` from
|
|
14
|
+
@w5-ui/tokens/dashboard.css.
|
|
15
|
+
-->
|
|
16
|
+
<div class="form-actions {className}">
|
|
17
|
+
{@render children?.()}
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
Two-column form grid (1fr 1fr, 14px gap, 6px top offset) that every Settings
|
|
12
|
+
card uses. Typed equivalent of `<div class="form-row">` from
|
|
13
|
+
@w5-ui/tokens/dashboard.css. Use inside wider <Card> tiles for dense editors.
|
|
14
|
+
-->
|
|
15
|
+
<div class="form-row {className}">
|
|
16
|
+
{@render children?.()}
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
The canonical 12-column page grid (repeat(12, 1fr), 12px gap, 16px 24px
|
|
12
|
+
padding, reserves room for the bottom panel). Typed equivalent of
|
|
13
|
+
`<div class="grid">` from @w5-ui/tokens/dashboard.css.
|
|
14
|
+
-->
|
|
15
|
+
<div class="grid {className}">
|
|
16
|
+
{@render children?.()}
|
|
17
|
+
</div>
|
|
@@ -34,57 +34,71 @@
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const uid = Math.random().toString(36).slice(2, 9);
|
|
37
|
-
// Initial open state mirrors each group's `defaultOpen` once; afterwards
|
|
38
|
-
// the user toggles each entry independently.
|
|
39
37
|
// svelte-ignore state_referenced_locally
|
|
40
38
|
let open: boolean[] = $state(groups.map((g) => g.defaultOpen !== false));
|
|
41
39
|
|
|
42
|
-
function toggle(i: number) {
|
|
40
|
+
function toggle(i: number) {
|
|
41
|
+
open[i] = !open[i];
|
|
42
|
+
open = [...open];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const HEADER_CLS =
|
|
46
|
+
'flex w-full items-center gap-2 rounded-md border border-border-1 bg-divider px-3 py-2 text-13 text-text-1';
|
|
47
|
+
const HEADER_BTN =
|
|
48
|
+
HEADER_CLS + ' cursor-pointer hover:bg-row-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05';
|
|
43
49
|
</script>
|
|
44
50
|
|
|
45
|
-
<div class="
|
|
51
|
+
<div class="flex flex-col gap-1.5" aria-label={ariaLabel}>
|
|
46
52
|
{#each groups as group, gi}
|
|
47
53
|
{@const isOpen = open[gi]}
|
|
48
54
|
{@const panelId = `${uid}-gl-${gi}`}
|
|
49
|
-
<div class="
|
|
55
|
+
<div class="flex flex-col gap-0.5">
|
|
50
56
|
{#if collapsible}
|
|
51
57
|
<button
|
|
52
58
|
type="button"
|
|
53
|
-
class="gl-header"
|
|
54
59
|
aria-expanded={isOpen}
|
|
55
60
|
aria-controls={panelId}
|
|
61
|
+
class={HEADER_BTN}
|
|
56
62
|
onclick={() => toggle(gi)}
|
|
57
63
|
>
|
|
58
|
-
{#if group.color}
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
{#if group.color}
|
|
65
|
+
<span class="inline-block h-2 w-2 shrink-0 rounded-full" style="background:{group.color}" aria-hidden="true"></span>
|
|
66
|
+
{/if}
|
|
67
|
+
<span class="flex-1 text-left font-medium">{group.label}</span>
|
|
68
|
+
<span class="rounded bg-row-active px-1.5 py-0.5 text-11 text-text-3 tabular-nums">{group.items.length}</span>
|
|
61
69
|
<svg
|
|
62
|
-
class="gl-chevron{isOpen ? ' gl-chevron--open' : ''}"
|
|
63
70
|
width="12" height="12" viewBox="0 0 12 12"
|
|
64
71
|
aria-hidden="true" focusable="false"
|
|
72
|
+
class="text-text-4 transition-transform duration-100 motion-reduce:transition-none {isOpen ? 'rotate-180' : ''}"
|
|
65
73
|
>
|
|
66
74
|
<path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
67
75
|
</svg>
|
|
68
76
|
</button>
|
|
69
77
|
{:else}
|
|
70
|
-
<div class=
|
|
71
|
-
{#if group.color}
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
<div class={HEADER_CLS}>
|
|
79
|
+
{#if group.color}
|
|
80
|
+
<span class="inline-block h-2 w-2 shrink-0 rounded-full" style="background:{group.color}" aria-hidden="true"></span>
|
|
81
|
+
{/if}
|
|
82
|
+
<span class="flex-1 text-left font-medium">{group.label}</span>
|
|
83
|
+
<span class="rounded bg-row-active px-1.5 py-0.5 text-11 text-text-3 tabular-nums">{group.items.length}</span>
|
|
74
84
|
</div>
|
|
75
85
|
{/if}
|
|
76
86
|
{#if !collapsible || isOpen}
|
|
77
|
-
<ul id={panelId} class="
|
|
87
|
+
<ul id={panelId} class="m-0 flex list-none flex-col gap-0.5 p-0 pl-4" role="list">
|
|
78
88
|
{#each group.items as item}
|
|
79
|
-
<li class="
|
|
89
|
+
<li class="flex items-center gap-2 rounded px-2 py-1.5 text-13 hover:bg-divider">
|
|
80
90
|
{#if item.status}
|
|
81
|
-
<span
|
|
91
|
+
<span
|
|
92
|
+
class="inline-block h-2 w-2 shrink-0 rounded-full"
|
|
93
|
+
style="background:{STATUS_COLORS[item.status]}"
|
|
94
|
+
aria-label={item.status}
|
|
95
|
+
></span>
|
|
82
96
|
{/if}
|
|
83
|
-
<div class="
|
|
84
|
-
<div class="
|
|
85
|
-
{#if item.sublabel}<div class="
|
|
97
|
+
<div class="min-w-0 flex-1">
|
|
98
|
+
<div class="truncate text-text-2">{item.label}</div>
|
|
99
|
+
{#if item.sublabel}<div class="truncate text-11 text-text-4">{item.sublabel}</div>{/if}
|
|
86
100
|
</div>
|
|
87
|
-
{#if item.meta}<span class="
|
|
101
|
+
{#if item.meta}<span class="shrink-0 text-11 text-text-4 tabular-nums">{item.meta}</span>{/if}
|
|
88
102
|
</li>
|
|
89
103
|
{/each}
|
|
90
104
|
</ul>
|
|
@@ -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
|
+
Page title - the 38 px / bold / --lh-snug rhythm with
|
|
12
|
+
--tracking-display (-0.02em) used for the top-of-page heading on
|
|
13
|
+
marketing surfaces (login, onboarding splash, top-level landing).
|
|
14
|
+
Renders <h1 class="dash-ui-h1"> so the .dash-ui-h1 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 a one-off <h1 style="font-size: 36px"> (or worse, leaving an
|
|
18
|
+
unstyled browser default <h1> at 32 px / 700 / 1.2 with no negative
|
|
19
|
+
tracking, which is the documented "headings break awkwardly across
|
|
20
|
+
two lines" drift fixed by --tracking-display). Typed equivalent of
|
|
21
|
+
<h1 class="dash-ui-h1"> (the userland-facing semantic class
|
|
22
|
+
documented in Foundations / Type). Use <H1> for marketing-grade page
|
|
23
|
+
titles. In-app pages should reach for <PageHeader> (which already
|
|
24
|
+
styles the page title) and use <H2> for section heads, <H3> for sub-
|
|
25
|
+
sections, <H4> for card titles.
|
|
26
|
+
-->
|
|
27
|
+
<h1 class="dash-ui-h1 {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
|
+
Page section heading - the 30 px / semibold / --lh-snug rhythm used
|
|
12
|
+
for top-of-section labels on a dashboard page (above a grid of cards,
|
|
13
|
+
above a table, above a settings group). Renders
|
|
14
|
+
<h2 class="dash-ui-h2"> so the .dash-ui-h2 rule in
|
|
15
|
+
@w5-ui/tokens/tokens.css pins the canonical size, weight, line-height,
|
|
16
|
+
and --text-1 colour. Stops userland reaching for a one-off
|
|
17
|
+
<h2 style="font-size: 30px; font-weight: 600"> (or worse, leaving an
|
|
18
|
+
unstyled browser default <h2> at ~24 px with 700 weight and 1.2 line-
|
|
19
|
+
height, which is the documented "section headings feel cramped on one
|
|
20
|
+
line" drift). Typed equivalent of <h2 class="dash-ui-h2"> (the
|
|
21
|
+
userland-facing semantic class documented in Foundations / Type). Use
|
|
22
|
+
<H2> for the page section head ("Devices", "Wi-Fi", "Settings")
|
|
23
|
+
sitting above a content block. For card titles prefer <H4> (or just
|
|
24
|
+
<Card title="..."> which injects .dash-ui-h4 for you); for full page
|
|
25
|
+
titles in marketing pages prefer <H1>.
|
|
26
|
+
-->
|
|
27
|
+
<h2 class="dash-ui-h2 {className}">
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</h2>
|
|
@@ -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
|
+
Sub-section heading - the 24 px / semibold / --lh-snug rhythm used
|
|
12
|
+
for sub-section labels on a dashboard page (a heading inside a card
|
|
13
|
+
region, a settings sub-group, a divider above a secondary table).
|
|
14
|
+
Renders <h3 class="dash-ui-h3"> so the .dash-ui-h3 rule in
|
|
15
|
+
@w5-ui/tokens/tokens.css pins the canonical size, weight, line-height,
|
|
16
|
+
and --text-1 colour. Stops userland reaching for a one-off
|
|
17
|
+
<h3 style="font-size: 24px; font-weight: 600"> (or worse, leaving an
|
|
18
|
+
unstyled browser default <h3> at ~18.7 px with 700 weight and 1.2
|
|
19
|
+
line-height, which is the documented "sub-section headings drift away
|
|
20
|
+
from the reference" case). Typed equivalent of
|
|
21
|
+
<h3 class="dash-ui-h3"> (the userland-facing semantic class
|
|
22
|
+
documented in Foundations / Type). Use <H3> for a sub-section head
|
|
23
|
+
sitting below an <H2> page section head. For card titles prefer <H4>
|
|
24
|
+
(or just <Card title="..."> which injects .dash-ui-h4 for you); for
|
|
25
|
+
page section heads prefer <H2>.
|
|
26
|
+
-->
|
|
27
|
+
<h3 class="dash-ui-h3 {className}">
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</h3>
|
|
@@ -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
|
+
Card / tile title - the 18 px / semibold / --lh-snug rhythm used for
|
|
12
|
+
the heading that sits inside a card, a stat tile, or a settings
|
|
13
|
+
sub-panel. Renders <h4 class="dash-ui-h4"> so the .dash-ui-h4 rule in
|
|
14
|
+
@w5-ui/tokens/tokens.css pins the canonical size, weight, line-height,
|
|
15
|
+
and --text-1 colour. Stops userland reaching for a one-off
|
|
16
|
+
<h3 style="font-size: 20px"> or text-xl (the documented "card title
|
|
17
|
+
size doesn't match the reference" case), and stops them leaving an
|
|
18
|
+
unstyled browser default <h4> at ~13.3 px / 700 / 1.2 which reads as
|
|
19
|
+
caption text instead of a title. Typed equivalent of
|
|
20
|
+
<h4 class="dash-ui-h4"> (the userland-facing semantic class documented
|
|
21
|
+
in Foundations / Type). Use <H4> when you build a card-like tile by
|
|
22
|
+
hand and need the canonical 18 / 600 title. If you reach for
|
|
23
|
+
<Card title="..."> you get the same class injected for you - prefer
|
|
24
|
+
that. For page section heads prefer <H2>; for sub-section heads
|
|
25
|
+
prefer <H3>.
|
|
26
|
+
-->
|
|
27
|
+
<h4 class="dash-ui-h4 {className}">
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</h4>
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
value,
|
|
11
11
|
fillStyle = '',
|
|
12
12
|
label = undefined,
|
|
13
|
-
valueText = undefined
|
|
13
|
+
valueText = undefined,
|
|
14
14
|
}: Props = $props();
|
|
15
15
|
let clamped = $derived(Math.max(0, Math.min(100, value)));
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<div
|
|
19
|
-
class="
|
|
19
|
+
class="h-1 overflow-hidden rounded-sm bg-row-active"
|
|
20
20
|
role="progressbar"
|
|
21
21
|
aria-valuenow={Math.round(clamped)}
|
|
22
22
|
aria-valuemin={0}
|
|
@@ -24,5 +24,8 @@
|
|
|
24
24
|
aria-label={label}
|
|
25
25
|
aria-valuetext={valueText}
|
|
26
26
|
>
|
|
27
|
-
<div
|
|
27
|
+
<div
|
|
28
|
+
class="h-full bg-gradient-to-r from-brand-05 to-brand-06"
|
|
29
|
+
style="width:{clamped}%; {fillStyle}"
|
|
30
|
+
></div>
|
|
28
31
|
</div>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import { onDestroy } from 'svelte';
|
|
7
|
+
|
|
7
8
|
interface Props {
|
|
8
9
|
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
9
10
|
delay?: number;
|
|
@@ -17,9 +18,8 @@
|
|
|
17
18
|
delay = 300,
|
|
18
19
|
class: className = '',
|
|
19
20
|
trigger,
|
|
20
|
-
content
|
|
21
|
+
content,
|
|
21
22
|
}: Props = $props();
|
|
22
|
-
|
|
23
23
|
|
|
24
24
|
const id = `dash-ui-hovercard-${++counter}`;
|
|
25
25
|
let open = $state(false);
|
|
@@ -36,21 +36,32 @@
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
onDestroy(() => { if (timer) clearTimeout(timer); });
|
|
39
|
+
|
|
40
|
+
const PLACEMENT: Record<NonNullable<Props['placement']>, string> = {
|
|
41
|
+
top: 'bottom-[calc(100%+6px)] left-0',
|
|
42
|
+
bottom: 'top-[calc(100%+6px)] left-0',
|
|
43
|
+
left: 'right-[calc(100%+6px)] top-0',
|
|
44
|
+
right: 'left-[calc(100%+6px)] top-0',
|
|
45
|
+
};
|
|
39
46
|
</script>
|
|
40
47
|
|
|
41
48
|
<div
|
|
42
49
|
role="group"
|
|
43
|
-
class="
|
|
50
|
+
class="relative block {className}"
|
|
44
51
|
onmouseenter={show}
|
|
45
52
|
onmouseleave={hide}
|
|
46
53
|
onfocusin={show}
|
|
47
54
|
onfocusout={hide}
|
|
48
55
|
>
|
|
49
|
-
<div class="
|
|
56
|
+
<div class="block" aria-describedby={open ? id : undefined}>
|
|
50
57
|
{@render trigger?.()}
|
|
51
58
|
</div>
|
|
52
59
|
{#if open}
|
|
53
|
-
<div
|
|
60
|
+
<div
|
|
61
|
+
{id}
|
|
62
|
+
role="tooltip"
|
|
63
|
+
class="absolute z-[9998] min-w-[200px] max-w-[280px] rounded-[10px] border border-border-3 bg-bg-2 p-3 text-12 shadow-[0_8px_32px_rgba(0,0,0,0.5)] {PLACEMENT[placement]}"
|
|
64
|
+
>
|
|
54
65
|
{@render content?.()}
|
|
55
66
|
</div>
|
|
56
67
|
{/if}
|
|
@@ -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;
|
|
@@ -75,13 +73,21 @@
|
|
|
75
73
|
setTimeout(() => focusAt(3), 0);
|
|
76
74
|
}
|
|
77
75
|
}
|
|
76
|
+
|
|
77
|
+
const OCTET_CLS =
|
|
78
|
+
'w-9 border-0 bg-transparent p-0 text-center font-mono text-13 text-text-1 outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none';
|
|
78
79
|
</script>
|
|
79
80
|
|
|
80
|
-
<div class=
|
|
81
|
+
<div class={className}>
|
|
81
82
|
{#if label}
|
|
82
|
-
<label for="{uid}-0" class="
|
|
83
|
+
<label for="{uid}-0" class="text-12 text-text-4">{label}</label>
|
|
83
84
|
{/if}
|
|
84
|
-
<div
|
|
85
|
+
<div
|
|
86
|
+
role="group"
|
|
87
|
+
aria-label={label ?? 'IP address'}
|
|
88
|
+
data-disabled={disabled ? 'true' : undefined}
|
|
89
|
+
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"
|
|
90
|
+
>
|
|
85
91
|
{#each octets as oct, i}
|
|
86
92
|
<input
|
|
87
93
|
bind:this={inputs[i]}
|
|
@@ -90,21 +96,21 @@
|
|
|
90
96
|
inputmode="numeric"
|
|
91
97
|
pattern="[0-9]*"
|
|
92
98
|
role="spinbutton"
|
|
93
|
-
aria-label=
|
|
99
|
+
aria-label={`Octet ${i + 1} of 4`}
|
|
94
100
|
aria-valuenow={oct === '' ? 0 : Number(oct)}
|
|
95
101
|
aria-valuemin={0}
|
|
96
102
|
aria-valuemax={255}
|
|
97
103
|
value={oct}
|
|
98
104
|
{disabled}
|
|
99
105
|
maxlength={3}
|
|
100
|
-
class=
|
|
106
|
+
class={OCTET_CLS}
|
|
101
107
|
oninput={(e) => commitOctet(i, e.currentTarget.value)}
|
|
102
108
|
onkeydown={(e) => handleKey(i, e)}
|
|
103
109
|
onpaste={handlePaste}
|
|
104
110
|
onfocus={(e) => e.currentTarget.select()}
|
|
105
111
|
/>
|
|
106
112
|
{#if i < 3}
|
|
107
|
-
<span class="
|
|
113
|
+
<span aria-hidden="true" class="select-none text-13 leading-none text-text-4">.</span>
|
|
108
114
|
{/if}
|
|
109
115
|
{/each}
|
|
110
116
|
</div>
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
[key: string]: unknown;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// svelte-ignore state_referenced_locally
|
|
15
14
|
// svelte-ignore state_referenced_locally
|
|
16
15
|
let {
|
|
17
16
|
title = undefined,
|
|
@@ -26,7 +25,7 @@
|
|
|
26
25
|
|
|
27
26
|
<button
|
|
28
27
|
{type}
|
|
29
|
-
class="
|
|
28
|
+
class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-3 hover:bg-row-hover hover:text-text-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05 {className}"
|
|
30
29
|
{title}
|
|
31
30
|
{style}
|
|
32
31
|
{...rest}
|