@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
|
@@ -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}
|
|
@@ -8,12 +8,7 @@
|
|
|
8
8
|
placeholder?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
let {
|
|
12
|
-
value,
|
|
13
|
-
onconfirm,
|
|
14
|
-
label,
|
|
15
|
-
placeholder = ''
|
|
16
|
-
}: Props = $props();
|
|
11
|
+
let { value, onconfirm, label, placeholder = '' }: Props = $props();
|
|
17
12
|
|
|
18
13
|
let editing = $state(false);
|
|
19
14
|
// svelte-ignore state_referenced_locally
|
|
@@ -50,30 +45,46 @@
|
|
|
50
45
|
</script>
|
|
51
46
|
|
|
52
47
|
{#if editing}
|
|
53
|
-
<span class="
|
|
48
|
+
<span class="inline-flex items-center gap-1">
|
|
54
49
|
<input
|
|
55
50
|
bind:this={inputEl}
|
|
56
|
-
class="
|
|
57
|
-
aria-label=
|
|
51
|
+
class="min-w-[80px] max-w-[200px] rounded-[5px] border border-brand-05/50 bg-row-active px-[7px] py-0.5 text-13 leading-none text-text-1 outline-none focus:border-brand-05 focus:bg-brand-05/[0.08]"
|
|
52
|
+
aria-label={`Edit ${label}`}
|
|
58
53
|
bind:value={draft}
|
|
59
54
|
{placeholder}
|
|
60
55
|
onkeydown={handleKeydown}
|
|
61
56
|
/>
|
|
62
|
-
<button
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
aria-label="Save"
|
|
60
|
+
class="inline-flex cursor-pointer items-center rounded border-0 bg-transparent px-1 py-0.5 text-status-success transition-[color,background-color] duration-100 hover:bg-status-success/15 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
61
|
+
onclick={confirm}
|
|
62
|
+
>
|
|
63
63
|
<svg viewBox="0 0 14 14" width="12" height="12" fill="none" aria-hidden="true" focusable="false">
|
|
64
64
|
<path d="M2.5 7.5l3.5 3.5L12 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
65
65
|
</svg>
|
|
66
66
|
</button>
|
|
67
|
-
<button
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
aria-label="Cancel"
|
|
70
|
+
class="inline-flex cursor-pointer items-center rounded border-0 bg-transparent px-1 py-0.5 text-text-4 transition-[color,background-color] duration-100 hover:bg-row-active hover:text-text-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
71
|
+
onclick={cancel}
|
|
72
|
+
>
|
|
68
73
|
<svg viewBox="0 0 14 14" width="12" height="12" fill="none" aria-hidden="true" focusable="false">
|
|
69
74
|
<path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
70
75
|
</svg>
|
|
71
76
|
</button>
|
|
72
77
|
</span>
|
|
73
78
|
{:else}
|
|
74
|
-
<span class="ie">
|
|
75
|
-
<span class="
|
|
76
|
-
<button
|
|
79
|
+
<span class="group/ie inline-flex items-center gap-1">
|
|
80
|
+
<span class="text-13 text-text-1">{value}</span>
|
|
81
|
+
<button
|
|
82
|
+
bind:this={editBtnEl}
|
|
83
|
+
type="button"
|
|
84
|
+
aria-label={`Edit ${label}`}
|
|
85
|
+
class="inline-flex cursor-pointer items-center rounded border-0 bg-transparent px-1 py-0.5 text-text-4 opacity-0 transition-[opacity,color] duration-100 hover:text-text-2 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05 group-hover/ie:opacity-100"
|
|
86
|
+
onclick={startEdit}
|
|
87
|
+
>
|
|
77
88
|
<svg viewBox="0 0 14 14" width="12" height="12" fill="none" aria-hidden="true" focusable="false">
|
|
78
89
|
<path d="M9.5 2l2.5 2.5-8 8H1.5v-2.5l8-8z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
79
90
|
</svg>
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
// Shared utility-class string for native input elements. Matches the
|
|
3
|
+
// dashboard.css `.input` rule exactly so Field + Input render identically
|
|
4
|
+
// and old markup with `class="input"` keeps working through the
|
|
5
|
+
// unmigrated tail. Update both sites in lockstep if it ever moves.
|
|
6
|
+
export const INPUT_CLASSES =
|
|
7
|
+
"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)]";
|
|
8
|
+
</script>
|
|
9
|
+
|
|
1
10
|
<script lang="ts">
|
|
2
11
|
interface Props {
|
|
3
12
|
value?: string;
|
|
@@ -26,7 +35,7 @@
|
|
|
26
35
|
|
|
27
36
|
{#if type === 'text'}
|
|
28
37
|
<input
|
|
29
|
-
class="
|
|
38
|
+
class="{INPUT_CLASSES} {className}"
|
|
30
39
|
type="text"
|
|
31
40
|
{placeholder}
|
|
32
41
|
bind:value
|
|
@@ -38,7 +47,7 @@
|
|
|
38
47
|
/>
|
|
39
48
|
{:else if type === 'password'}
|
|
40
49
|
<input
|
|
41
|
-
class="
|
|
50
|
+
class="{INPUT_CLASSES} {className}"
|
|
42
51
|
type="password"
|
|
43
52
|
{placeholder}
|
|
44
53
|
bind:value
|
|
@@ -50,7 +59,7 @@
|
|
|
50
59
|
/>
|
|
51
60
|
{:else if type === 'number'}
|
|
52
61
|
<input
|
|
53
|
-
class="
|
|
62
|
+
class="{INPUT_CLASSES} {className}"
|
|
54
63
|
type="number"
|
|
55
64
|
{placeholder}
|
|
56
65
|
bind:value
|
|
@@ -62,7 +71,7 @@
|
|
|
62
71
|
/>
|
|
63
72
|
{:else}
|
|
64
73
|
<input
|
|
65
|
-
class="
|
|
74
|
+
class="{INPUT_CLASSES} {className}"
|
|
66
75
|
{type}
|
|
67
76
|
{placeholder}
|
|
68
77
|
{value}
|
|
@@ -8,12 +8,23 @@
|
|
|
8
8
|
let { prefix = undefined, suffix = undefined, children }: Props = $props();
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
<!--
|
|
12
|
+
Input group: prefix / suffix addons flank a slot-rendered `<Input>` and
|
|
13
|
+
trim/round its corners. The `[&_input]:rounded-none` selector targets the
|
|
14
|
+
child input regardless of its own utility classes. Addons inherit the
|
|
15
|
+
same height as the migrated Input (34px).
|
|
16
|
+
-->
|
|
17
|
+
<div
|
|
18
|
+
class="flex items-stretch
|
|
19
|
+
[&_input]:flex-1 [&_input]:min-w-0 [&_input]:rounded-none
|
|
20
|
+
{prefix === undefined ? '[&_input]:rounded-l-md' : ''}
|
|
21
|
+
{suffix === undefined ? '[&_input]:rounded-r-md' : ''}"
|
|
22
|
+
>
|
|
12
23
|
{#if prefix !== undefined}
|
|
13
|
-
<span class="
|
|
24
|
+
<span class="flex shrink-0 select-none items-center whitespace-nowrap rounded-l-md border border-r-0 border-border-2 bg-row-active px-2.5 text-13 text-text-4">{prefix}</span>
|
|
14
25
|
{/if}
|
|
15
26
|
{@render children?.()}
|
|
16
27
|
{#if suffix !== undefined}
|
|
17
|
-
<span class="
|
|
28
|
+
<span class="flex shrink-0 select-none items-center whitespace-nowrap rounded-r-md border border-l-0 border-border-2 bg-row-active px-2.5 text-13 text-text-4">{suffix}</span>
|
|
18
29
|
{/if}
|
|
19
30
|
</div>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import JsonViewerNode from './JsonViewerNode.svelte';
|
|
7
7
|
import type { JsVal } from './JsonViewerNode.svelte';
|
|
8
|
+
|
|
8
9
|
interface Props {
|
|
9
10
|
data: unknown;
|
|
10
11
|
label?: string;
|
|
@@ -16,11 +17,15 @@
|
|
|
16
17
|
data,
|
|
17
18
|
label = 'JSON viewer',
|
|
18
19
|
defaultExpanded = true,
|
|
19
|
-
maxDepth = 3
|
|
20
|
+
maxDepth = 3,
|
|
20
21
|
}: Props = $props();
|
|
21
22
|
let root = $derived(data as JsVal);
|
|
22
23
|
</script>
|
|
23
24
|
|
|
24
|
-
<div
|
|
25
|
+
<div
|
|
26
|
+
class="overflow-auto rounded-md border border-border-1 bg-bg-page py-2 font-mono text-12 leading-[1.8]"
|
|
27
|
+
role="tree"
|
|
28
|
+
aria-label={label}
|
|
29
|
+
>
|
|
25
30
|
<JsonViewerNode v={root} depth={0} {maxDepth} startOpen={defaultExpanded} />
|
|
26
31
|
</div>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import JsonViewerNode from './JsonViewerNode.svelte';
|
|
7
|
+
|
|
7
8
|
interface Props {
|
|
8
9
|
v: JsVal;
|
|
9
10
|
k?: string | undefined;
|
|
@@ -12,64 +13,69 @@
|
|
|
12
13
|
startOpen?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
let {
|
|
16
|
-
v,
|
|
17
|
-
k = undefined,
|
|
18
|
-
depth = 0,
|
|
19
|
-
maxDepth = 3,
|
|
20
|
-
startOpen = true
|
|
21
|
-
}: Props = $props();
|
|
16
|
+
let { v, k = undefined, depth = 0, maxDepth = 3, startOpen = true }: Props = $props();
|
|
22
17
|
|
|
23
18
|
let isArr = $derived(Array.isArray(v));
|
|
24
19
|
let isObj = $derived(v !== null && typeof v === 'object');
|
|
25
20
|
let expandable = $derived(isArr || isObj);
|
|
26
21
|
|
|
27
|
-
// `open` is initialised from `startOpen`/`depth`/`maxDepth` props but is
|
|
28
|
-
// intentionally a one-shot snapshot at construction time — afterwards
|
|
29
|
-
// the user controls it via the toggle.
|
|
30
22
|
// svelte-ignore state_referenced_locally
|
|
31
23
|
let open = $state(startOpen && depth < maxDepth);
|
|
32
24
|
|
|
33
|
-
let entries = $derived(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
let entries = $derived(
|
|
26
|
+
isArr
|
|
27
|
+
? (v as JsVal[]).map((item: JsVal, i: number) => ({ ek: String(i), ev: item, arrItem: true }))
|
|
28
|
+
: isObj
|
|
29
|
+
? Object.entries(v as { [key: string]: JsVal }).map(([ek, ev]) => ({ ek, ev, arrItem: false }))
|
|
30
|
+
: [],
|
|
31
|
+
);
|
|
38
32
|
let count = $derived(entries.length);
|
|
39
33
|
let ob = $derived(isArr ? '[' : '{');
|
|
40
34
|
let cb = $derived(isArr ? ']' : '}');
|
|
41
35
|
let indent = $derived(depth * 14);
|
|
36
|
+
|
|
37
|
+
// Syntax-highlight palette is motif-aware: dark uses the familiar
|
|
38
|
+
// VS Code dark-plus accents; light flips to a calmer, slightly
|
|
39
|
+
// higher-contrast set tuned for #ffffff page bg.
|
|
40
|
+
const KEY = 'text-[#9cdcfe] dark:text-[#9cdcfe] light:text-[#0451a5]';
|
|
41
|
+
const COLON = 'text-text-4';
|
|
42
|
+
const NULL = 'text-text-4 italic';
|
|
43
|
+
const STR = 'text-[#ce9178] dark:text-[#ce9178] light:text-[#a31515]';
|
|
44
|
+
const NUM = 'text-[#b5cea8] dark:text-[#b5cea8] light:text-[#098658]';
|
|
45
|
+
const BOOL = 'text-[#569cd6] dark:text-[#569cd6] light:text-[#0000ff]';
|
|
46
|
+
const BRACKET = 'text-text-2';
|
|
47
|
+
const COUNT = 'text-text-4 tabular-nums px-1';
|
|
42
48
|
</script>
|
|
43
49
|
|
|
44
50
|
{#if !expandable}
|
|
45
|
-
<div role="treeitem" aria-selected="false" class="
|
|
46
|
-
{#if k != null}<span class=
|
|
47
|
-
{#if v === null}<span class=
|
|
48
|
-
{:else if typeof v === 'string'}<span class=
|
|
49
|
-
{:else if typeof v === 'number'}<span class=
|
|
50
|
-
{:else}<span class=
|
|
51
|
+
<div role="treeitem" aria-selected="false" class="px-3 py-px" style:padding-left="{indent + 12}px">
|
|
52
|
+
{#if k != null}<span class={KEY}>"{k}"</span><span class={COLON}>: </span>{/if}
|
|
53
|
+
{#if v === null}<span class={NULL}>null</span>
|
|
54
|
+
{:else if typeof v === 'string'}<span class={STR}>"{v}"</span>
|
|
55
|
+
{:else if typeof v === 'number'}<span class={NUM}>{v}</span>
|
|
56
|
+
{:else}<span class={BOOL}>{String(v)}</span>{/if}
|
|
51
57
|
</div>
|
|
52
58
|
{:else}
|
|
53
|
-
<div role="treeitem" aria-selected="false" aria-expanded={open} class="
|
|
59
|
+
<div role="treeitem" aria-selected="false" aria-expanded={open} class="block">
|
|
54
60
|
<button
|
|
55
61
|
type="button"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
aria-label="{open ? 'Collapse' : 'Expand'} {k ?? (isArr ? 'array' : 'object')}"
|
|
62
|
+
aria-label={`${open ? 'Collapse' : 'Expand'} ${k ?? (isArr ? 'array' : 'object')}`}
|
|
63
|
+
class="flex w-full cursor-pointer items-baseline border-0 bg-transparent px-3 py-px text-left font-mono text-12 leading-[1.8] hover:bg-divider focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-brand-05"
|
|
59
64
|
style:padding-left="{indent}px"
|
|
65
|
+
onclick={() => (open = !open)}
|
|
60
66
|
>
|
|
61
|
-
<span class="
|
|
62
|
-
{#if k != null}<span class=
|
|
63
|
-
<span class=
|
|
64
|
-
{#if !open}<span class=
|
|
67
|
+
<span class="mr-1 inline-block w-3 text-text-4" aria-hidden="true">{open ? '▾' : '▸'}</span>
|
|
68
|
+
{#if k != null}<span class={KEY}>"{k}"</span><span class={COLON}>: </span>{/if}
|
|
69
|
+
<span class={BRACKET}>{ob}</span>
|
|
70
|
+
{#if !open}<span class={COUNT}> {count} </span><span class={BRACKET}>{cb}</span>{/if}
|
|
65
71
|
</button>
|
|
66
72
|
{#if open}
|
|
67
|
-
<div role="group" class="
|
|
73
|
+
<div role="group" class="block">
|
|
68
74
|
{#each entries as { ek, ev, arrItem } (ek)}
|
|
69
75
|
<JsonViewerNode v={ev} k={arrItem ? undefined : ek} depth={depth + 1} {maxDepth} {startOpen} />
|
|
70
76
|
{/each}
|
|
71
|
-
<div class="
|
|
72
|
-
<span class=
|
|
77
|
+
<div class="px-3 py-px" style:padding-left="{indent + 12}px">
|
|
78
|
+
<span class={BRACKET}>{cb}</span>
|
|
73
79
|
</div>
|
|
74
80
|
</div>
|
|
75
81
|
{/if}
|
|
@@ -7,15 +7,21 @@
|
|
|
7
7
|
let { rows = [], caption = undefined }: Props = $props();
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
|
-
<table class="
|
|
10
|
+
<table class="w-full border-collapse text-13">
|
|
11
11
|
{#if caption}
|
|
12
12
|
<caption class="sr-only">{caption}</caption>
|
|
13
13
|
{/if}
|
|
14
14
|
<tbody>
|
|
15
15
|
{#each rows as { label, value, valueColor }}
|
|
16
|
-
<tr class="
|
|
17
|
-
<th
|
|
18
|
-
|
|
16
|
+
<tr class="border-b border-border-1 last:border-b-0">
|
|
17
|
+
<th
|
|
18
|
+
scope="row"
|
|
19
|
+
class="w-2/5 whitespace-nowrap py-1.5 pr-3 text-left align-top font-medium text-text-3"
|
|
20
|
+
>{label}</th>
|
|
21
|
+
<td
|
|
22
|
+
class="py-1.5 align-top text-text-1 break-words"
|
|
23
|
+
style={valueColor ? `color:${valueColor}` : undefined}
|
|
24
|
+
>{value}</td>
|
|
19
25
|
</tr>
|
|
20
26
|
{/each}
|
|
21
27
|
</tbody>
|
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<script lang="ts">
|
|
17
|
-
|
|
18
|
-
|
|
19
17
|
interface Props {
|
|
20
18
|
columns: KanbanColumn[];
|
|
21
19
|
onCardMove?: ((cardId: string, fromColId: string, toColId: string) => void) | undefined;
|
|
@@ -35,7 +33,7 @@
|
|
|
35
33
|
function say(msg: string) { announce = msg; }
|
|
36
34
|
|
|
37
35
|
function colOf(cardId: string): KanbanColumn | undefined {
|
|
38
|
-
return columns.find(c => c.cards.some(k => k.id === cardId));
|
|
36
|
+
return columns.find((c) => c.cards.some((k) => k.id === cardId));
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
function handleKeyDown(e: KeyboardEvent, card: KanbanCard, col: KanbanColumn) {
|
|
@@ -54,7 +52,7 @@
|
|
|
54
52
|
say('Move cancelled.');
|
|
55
53
|
} else if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && grabbed?.cardId === card.id) {
|
|
56
54
|
e.preventDefault();
|
|
57
|
-
const idx = columns.findIndex(c => c.id === grabbed!.colId);
|
|
55
|
+
const idx = columns.findIndex((c) => c.id === grabbed!.colId);
|
|
58
56
|
const nextIdx = e.key === 'ArrowLeft' ? idx - 1 : idx + 1;
|
|
59
57
|
if (nextIdx < 0 || nextIdx >= columns.length) return;
|
|
60
58
|
const nextCol = columns[nextIdx];
|
|
@@ -79,8 +77,8 @@
|
|
|
79
77
|
function handleDrop(e: DragEvent, toColId: string) {
|
|
80
78
|
e.preventDefault();
|
|
81
79
|
if (!dragSrc || dragSrc.colId === toColId) { dragSrc = null; dragOverCol = null; return; }
|
|
82
|
-
const card = colOf(dragSrc.cardId)?.cards.find(c => c.id === dragSrc!.cardId);
|
|
83
|
-
const toCol = columns.find(c => c.id === toColId);
|
|
80
|
+
const card = colOf(dragSrc.cardId)?.cards.find((c) => c.id === dragSrc!.cardId);
|
|
81
|
+
const toCol = columns.find((c) => c.id === toColId);
|
|
84
82
|
onCardMove?.(dragSrc.cardId, dragSrc.colId, toColId);
|
|
85
83
|
if (card && toCol) say(`Moved ${card.title} to ${toCol.title}.`);
|
|
86
84
|
dragSrc = null;
|
|
@@ -88,7 +86,7 @@
|
|
|
88
86
|
}
|
|
89
87
|
</script>
|
|
90
88
|
|
|
91
|
-
<div
|
|
89
|
+
<div role="region" aria-label={ariaLabel}>
|
|
92
90
|
<span
|
|
93
91
|
id={announceId}
|
|
94
92
|
role="status"
|
|
@@ -97,49 +95,53 @@
|
|
|
97
95
|
class="sr-only"
|
|
98
96
|
>{announce}</span>
|
|
99
97
|
|
|
100
|
-
<div class="
|
|
98
|
+
<div class="flex gap-3 overflow-x-auto" role="list">
|
|
101
99
|
{#each columns as col (col.id)}
|
|
102
100
|
<div
|
|
103
|
-
|
|
104
|
-
class:kanban-col--over={dragOverCol === col.id}
|
|
101
|
+
data-over={dragOverCol === col.id ? 'true' : undefined}
|
|
105
102
|
role="listitem"
|
|
106
|
-
|
|
103
|
+
class="flex min-w-[240px] flex-1 flex-col gap-2 rounded-lg border border-border-1 bg-divider p-2 data-[over=true]:border-brand-05/40 data-[over=true]:bg-brand-05/[0.04]"
|
|
104
|
+
ondragover={(e) => handleDragOver(e, col.id)}
|
|
107
105
|
ondragleave={() => { dragOverCol = null; }}
|
|
108
|
-
ondrop={e => handleDrop(e, col.id)}
|
|
106
|
+
ondrop={(e) => handleDrop(e, col.id)}
|
|
109
107
|
>
|
|
110
|
-
<div class="
|
|
108
|
+
<div class="flex items-center gap-2 px-1 pt-0.5">
|
|
111
109
|
{#if col.color}
|
|
112
|
-
<span class="
|
|
110
|
+
<span class="inline-block h-2 w-2 shrink-0 rounded-full" style="background:{col.color}" aria-hidden="true"></span>
|
|
113
111
|
{/if}
|
|
114
|
-
<span class="
|
|
115
|
-
<span
|
|
112
|
+
<span class="flex-1 text-12 font-semibold uppercase tracking-[0.05em] text-text-3">{col.title}</span>
|
|
113
|
+
<span
|
|
114
|
+
class="rounded bg-row-active px-1.5 py-0.5 text-11 text-text-4 tabular-nums"
|
|
115
|
+
aria-label={`${col.cards.length} card${col.cards.length !== 1 ? 's' : ''}`}
|
|
116
|
+
>
|
|
116
117
|
{col.cards.length}
|
|
117
118
|
</span>
|
|
118
119
|
</div>
|
|
119
120
|
|
|
120
|
-
<ul class="
|
|
121
|
-
{#each col.cards as card (card.id)}
|
|
121
|
+
<ul class="m-0 flex list-none flex-col gap-1.5 p-0" aria-label={`${col.title} cards`}>
|
|
122
|
+
{#each col.cards as card (col.id + '/' + card.id)}
|
|
122
123
|
{@const isGrabbed = grabbed?.cardId === card.id}
|
|
123
|
-
<li
|
|
124
|
+
<li>
|
|
124
125
|
<button
|
|
125
126
|
bind:this={cardRefs[card.id]}
|
|
126
127
|
type="button"
|
|
127
|
-
class="kanban-card"
|
|
128
128
|
draggable={true}
|
|
129
129
|
aria-pressed={isGrabbed}
|
|
130
|
-
aria-label=
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
aria-label={`${card.title}${card.subtitle ? `, ${card.subtitle}` : ''}${card.meta ? `, ${card.meta}` : ''}. ${isGrabbed ? 'Moving. Use arrow keys to change column, Space to drop, Escape to cancel.' : 'Press Space to move.'}`}
|
|
131
|
+
data-grabbed={isGrabbed ? 'true' : undefined}
|
|
132
|
+
class="flex w-full cursor-grab flex-col items-start gap-0.5 rounded-md border border-border-1 bg-bg-1 p-2 text-left active:cursor-grabbing hover:border-border-3 data-[grabbed=true]:border-brand-05 data-[grabbed=true]:bg-brand-05/[0.08] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
133
|
+
onkeydown={(e) => handleKeyDown(e, card, col)}
|
|
134
|
+
ondragstart={(e) => handleDragStart(e, card.id, col.id)}
|
|
133
135
|
ondragend={() => { dragSrc = null; dragOverCol = null; }}
|
|
134
136
|
>
|
|
135
|
-
<span class="
|
|
136
|
-
{#if card.subtitle}<span class="
|
|
137
|
-
{#if card.meta}<span class="
|
|
137
|
+
<span class="text-13 font-medium text-text-1">{card.title}</span>
|
|
138
|
+
{#if card.subtitle}<span class="text-12 text-text-3">{card.subtitle}</span>{/if}
|
|
139
|
+
{#if card.meta}<span class="text-11 text-text-4">{card.meta}</span>{/if}
|
|
138
140
|
</button>
|
|
139
141
|
</li>
|
|
140
142
|
{/each}
|
|
141
143
|
{#if col.cards.length === 0}
|
|
142
|
-
<li class="
|
|
144
|
+
<li class="rounded-md border border-dashed border-border-1 p-3 text-center text-11 text-text-4" role="note" aria-label={`${col.title} is empty`}>Empty</li>
|
|
143
145
|
{/if}
|
|
144
146
|
</ul>
|
|
145
147
|
</div>
|
|
@@ -5,16 +5,19 @@
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
let { keys, class: className = '' }: Props = $props();
|
|
8
|
-
|
|
9
8
|
|
|
10
9
|
let parts = $derived(typeof keys === 'string' ? keys.split('+') : keys);
|
|
11
10
|
</script>
|
|
12
11
|
|
|
13
|
-
<kbd class="
|
|
12
|
+
<kbd class="inline-flex shrink-0 items-center gap-px font-mono text-[11px] leading-none {className}">
|
|
14
13
|
{#each parts as key, i}
|
|
15
|
-
<span class="
|
|
16
|
-
{#if i > 0}
|
|
17
|
-
|
|
14
|
+
<span class="inline-flex items-center gap-0.5">
|
|
15
|
+
{#if i > 0}
|
|
16
|
+
<span class="px-px font-sans text-[10px] leading-none text-text-4" aria-hidden="true">+</span>
|
|
17
|
+
{/if}
|
|
18
|
+
<kbd
|
|
19
|
+
class="inline-flex min-w-[18px] items-center justify-center rounded border border-b-2 border-border-3 bg-row-active px-1.5 py-0.5 font-[inherit] text-text-3 [box-shadow:0_1px_0_rgba(0,0,0,0.4)]"
|
|
20
|
+
>{key}</kbd>
|
|
18
21
|
</span>
|
|
19
22
|
{/each}
|
|
20
23
|
</kbd>
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
Small categorical label - the line above a KPI number ("Active devices"),
|
|
12
|
+
the cell heading on a hand-rolled form row, the tag on a definition list
|
|
13
|
+
term, anywhere a 13 px / medium-weight tag belongs without the full form
|
|
14
|
+
field machinery. Renders <span class="dash-ui-label"> so the
|
|
15
|
+
.dash-ui-label rule in @w5-ui/tokens/tokens.css applies the canonical
|
|
16
|
+
13 px / 500 weight, 1.25 line-height, and --text-2 colour. Stops
|
|
17
|
+
userland reaching for a one-off font-size: 13px; font-weight: 500; duo
|
|
18
|
+
on every KPI label or hand-rolled form caption. Typed equivalent of
|
|
19
|
+
<span class="dash-ui-label"> (the userland-facing semantic class
|
|
20
|
+
documented in Foundations / Type). For real form fields prefer
|
|
21
|
+
<Field label="..."> - it wires htmlFor, required, hint, and error
|
|
22
|
+
semantics for you. For KPI tiles prefer the <Stat> label slot. Reach
|
|
23
|
+
for <Label> only when hand-rolling a tile or row where the higher-level
|
|
24
|
+
component doesn't fit.
|
|
25
|
+
-->
|
|
26
|
+
<span class="dash-ui-label {className}">
|
|
27
|
+
{@render children?.()}
|
|
28
|
+
</span>
|
|
@@ -37,24 +37,28 @@
|
|
|
37
37
|
info: '#006FFF',
|
|
38
38
|
debug: '#4A4B53',
|
|
39
39
|
};
|
|
40
|
+
const SEV_TXT: Record<string, string> = {
|
|
41
|
+
error: 'text-status-danger',
|
|
42
|
+
warn: 'text-status-warning',
|
|
43
|
+
info: 'text-brand-05',
|
|
44
|
+
debug: 'text-text-4',
|
|
45
|
+
};
|
|
40
46
|
|
|
41
|
-
// Replaces `afterUpdate`: `$effect` runs after every DOM commit triggered
|
|
42
|
-
// by reactive dependencies. Reading `entries` ensures we re-scroll on
|
|
43
|
-
// every new batch.
|
|
44
47
|
$effect(() => {
|
|
45
48
|
void entries.length;
|
|
46
49
|
if (follow && bodyEl) bodyEl.scrollTop = bodyEl.scrollHeight;
|
|
47
50
|
});
|
|
48
51
|
</script>
|
|
49
52
|
|
|
50
|
-
<div class="
|
|
51
|
-
<div class="
|
|
52
|
-
<span class="
|
|
53
|
+
<div class="flex flex-col gap-1 rounded-lg border border-border-1 bg-bg-page {className}">
|
|
54
|
+
<div class="flex items-center justify-between border-b border-border-1 px-3 py-1.5 text-12 text-text-4">
|
|
55
|
+
<span class="tabular-nums">{entries.length} entries</span>
|
|
53
56
|
<button
|
|
54
57
|
type="button"
|
|
55
|
-
|
|
56
|
-
onclick={() => { follow = !follow; }}
|
|
58
|
+
data-on={follow ? 'true' : undefined}
|
|
57
59
|
aria-pressed={follow}
|
|
60
|
+
class="inline-flex h-6 cursor-pointer items-center rounded border border-border-2 bg-transparent px-2 text-12 text-text-3 transition-colors duration-100 hover:bg-row-hover hover:text-text-1 data-[on=true]:border-brand-05 data-[on=true]:bg-brand-05/[0.10] data-[on=true]:text-brand-05 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
61
|
+
onclick={() => { follow = !follow; }}
|
|
58
62
|
>Follow</button>
|
|
59
63
|
</div>
|
|
60
64
|
<div
|
|
@@ -62,12 +66,12 @@
|
|
|
62
66
|
role="log"
|
|
63
67
|
aria-live="polite"
|
|
64
68
|
aria-label={ariaLabel}
|
|
65
|
-
class="
|
|
69
|
+
class="overflow-auto"
|
|
66
70
|
style="height:{height}px;"
|
|
67
71
|
>
|
|
68
|
-
<table class="
|
|
72
|
+
<table class="w-full border-collapse font-mono text-12">
|
|
69
73
|
<caption class="sr-only">{ariaLabel}</caption>
|
|
70
|
-
<thead>
|
|
74
|
+
<thead class="sr-only">
|
|
71
75
|
<tr>
|
|
72
76
|
<th scope="col">Severity</th>
|
|
73
77
|
<th scope="col">Source</th>
|
|
@@ -79,18 +83,18 @@
|
|
|
79
83
|
</thead>
|
|
80
84
|
<tbody>
|
|
81
85
|
{#each entries as e, i (e.id ?? i)}
|
|
82
|
-
<tr>
|
|
83
|
-
<td>
|
|
84
|
-
<span class="
|
|
85
|
-
<span class="
|
|
86
|
+
<tr class="border-b border-divider last:border-b-0 hover:bg-divider">
|
|
87
|
+
<td class="whitespace-nowrap py-1 pl-3 pr-2">
|
|
88
|
+
<span class="inline-flex items-center gap-1 {SEV_TXT[e.level] ?? SEV_TXT.info}">
|
|
89
|
+
<span class="inline-block h-1.5 w-1.5 rounded-full" style="background:{SEV_DOT[e.level] ?? SEV_DOT.info};" aria-hidden="true"></span>
|
|
86
90
|
{e.level === 'error' ? 'crit' : e.level}
|
|
87
91
|
</span>
|
|
88
92
|
</td>
|
|
89
|
-
<td class="
|
|
90
|
-
<td class="
|
|
91
|
-
<td class="
|
|
92
|
-
<td class="
|
|
93
|
-
<td class="
|
|
93
|
+
<td class="whitespace-nowrap px-2 py-1 text-text-4">{e.category ?? ''}</td>
|
|
94
|
+
<td class="whitespace-nowrap px-2 py-1 text-text-4 tabular-nums">{e.timestamp}</td>
|
|
95
|
+
<td class="whitespace-nowrap px-2 py-1 text-text-2">{e.event}</td>
|
|
96
|
+
<td class="whitespace-nowrap px-2 py-1 text-text-2">{e.subject ?? ''}</td>
|
|
97
|
+
<td class="px-2 py-1 pr-3 text-text-3">{e.detail ?? ''}</td>
|
|
94
98
|
</tr>
|
|
95
99
|
{/each}
|
|
96
100
|
</tbody>
|
|
@@ -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,31 +73,39 @@
|
|
|
75
73
|
setTimeout(() => focusAt(5), 0);
|
|
76
74
|
}
|
|
77
75
|
}
|
|
76
|
+
|
|
77
|
+
const PAIR_CLS =
|
|
78
|
+
'w-6 border-0 bg-transparent p-0 text-center font-mono text-13 uppercase text-text-1 outline-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 ?? 'MAC 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 pairs as pair, i}
|
|
86
92
|
<input
|
|
87
93
|
bind:this={inputs[i]}
|
|
88
94
|
id={i === 0 ? `${uid}-0` : undefined}
|
|
89
95
|
type="text"
|
|
90
96
|
inputmode="text"
|
|
91
|
-
aria-label=
|
|
97
|
+
aria-label={`Byte ${i + 1} of 6`}
|
|
92
98
|
value={pair}
|
|
93
99
|
{disabled}
|
|
94
100
|
maxlength={2}
|
|
95
|
-
class=
|
|
101
|
+
class={PAIR_CLS}
|
|
96
102
|
oninput={(e) => commitPair(i, e.currentTarget.value)}
|
|
97
103
|
onkeydown={(e) => handleKey(i, e)}
|
|
98
104
|
onpaste={handlePaste}
|
|
99
105
|
onfocus={(e) => e.currentTarget.select()}
|
|
100
106
|
/>
|
|
101
107
|
{#if i < 5}
|
|
102
|
-
<span class="
|
|
108
|
+
<span aria-hidden="true" class="select-none text-13 leading-none text-text-4">:</span>
|
|
103
109
|
{/if}
|
|
104
110
|
{/each}
|
|
105
111
|
</div>
|