@w5-ui/svelte 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -4
- package/package.json +2 -2
- package/src/lib/actions/portal.ts +55 -0
- package/src/lib/components/Accordion.svelte +1 -1
- package/src/lib/components/AccordionItem.svelte +6 -7
- package/src/lib/components/ActionMenu.svelte +17 -9
- package/src/lib/components/ActivityFeed.svelte +20 -14
- package/src/lib/components/Alert.svelte +22 -4
- package/src/lib/components/AppLayout.svelte +34 -0
- package/src/lib/components/Avatar.svelte +28 -11
- package/src/lib/components/AvatarGroup.svelte +21 -4
- package/src/lib/components/Badge.svelte +22 -6
- package/src/lib/components/Banner.svelte +36 -7
- package/src/lib/components/Body.svelte +29 -0
- package/src/lib/components/BodyLg.svelte +26 -0
- package/src/lib/components/Breadcrumb.svelte +19 -12
- package/src/lib/components/Button.svelte +30 -1
- package/src/lib/components/CIDRInput.svelte +17 -9
- package/src/lib/components/Callout.svelte +29 -8
- package/src/lib/components/Caption.svelte +26 -0
- package/src/lib/components/Card.svelte +11 -3
- package/src/lib/components/CardMore.svelte +20 -0
- package/src/lib/components/CardTitle.svelte +20 -0
- package/src/lib/components/Carousel.svelte +27 -20
- package/src/lib/components/Checkbox.svelte +24 -3
- package/src/lib/components/Code.svelte +24 -0
- package/src/lib/components/CodeBlock.svelte +8 -7
- package/src/lib/components/ColorPicker.svelte +13 -13
- package/src/lib/components/ColumnToggle.svelte +10 -9
- package/src/lib/components/Combobox.svelte +31 -17
- package/src/lib/components/CommandPalette.svelte +42 -26
- package/src/lib/components/ConfirmDialog.svelte +21 -15
- package/src/lib/components/ContextMenu.svelte +12 -12
- package/src/lib/components/ContextualHelp.svelte +11 -6
- package/src/lib/components/CopyButton.svelte +2 -1
- package/src/lib/components/DatePicker.svelte +27 -23
- package/src/lib/components/DateRangePicker.svelte +46 -39
- package/src/lib/components/Delta.svelte +25 -0
- package/src/lib/components/Display1.svelte +29 -0
- package/src/lib/components/Display2.svelte +29 -0
- package/src/lib/components/Drawer.svelte +6 -5
- package/src/lib/components/DurationInput.svelte +16 -8
- package/src/lib/components/EmptyState.svelte +16 -7
- package/src/lib/components/ExpandableRow.svelte +8 -15
- package/src/lib/components/Eyebrow.svelte +25 -0
- package/src/lib/components/Field.svelte +7 -7
- package/src/lib/components/FieldStack.svelte +18 -0
- package/src/lib/components/FileUpload.svelte +8 -10
- package/src/lib/components/FilterBuilder.svelte +52 -28
- package/src/lib/components/FormActions.svelte +18 -0
- package/src/lib/components/FormRow.svelte +17 -0
- package/src/lib/components/Grid.svelte +17 -0
- package/src/lib/components/GroupedList.svelte +35 -21
- package/src/lib/components/H1.svelte +29 -0
- package/src/lib/components/H2.svelte +29 -0
- package/src/lib/components/H3.svelte +29 -0
- package/src/lib/components/H4.svelte +29 -0
- package/src/lib/components/HealthBar.svelte +6 -3
- package/src/lib/components/HoverCard.svelte +16 -5
- package/src/lib/components/IPInput.svelte +14 -8
- package/src/lib/components/IconButton.svelte +1 -2
- package/src/lib/components/InlineEdit.svelte +25 -14
- package/src/lib/components/Input.svelte +13 -4
- package/src/lib/components/InputGroup.svelte +14 -3
- package/src/lib/components/JsonViewer.svelte +7 -2
- package/src/lib/components/JsonViewerNode.svelte +38 -32
- package/src/lib/components/KVTable.svelte +10 -4
- package/src/lib/components/KanbanBoard.svelte +29 -27
- package/src/lib/components/Kbd.svelte +8 -5
- package/src/lib/components/Label.svelte +28 -0
- package/src/lib/components/LogViewer.svelte +24 -20
- package/src/lib/components/MACInput.svelte +14 -8
- package/src/lib/components/Mac.svelte +23 -0
- package/src/lib/components/Menubar.svelte +30 -13
- package/src/lib/components/Modal.svelte +15 -6
- package/src/lib/components/Mono.svelte +28 -0
- package/src/lib/components/MultiSelect.svelte +43 -23
- package/src/lib/components/Muted.svelte +24 -0
- package/src/lib/components/NameCell.svelte +35 -0
- package/src/lib/components/NotifDot.svelte +23 -0
- package/src/lib/components/NotificationPanel.svelte +44 -31
- package/src/lib/components/NumberInput.svelte +13 -12
- package/src/lib/components/OTPInput.svelte +14 -8
- package/src/lib/components/PageHeader.svelte +25 -0
- package/src/lib/components/Pagination.svelte +16 -12
- package/src/lib/components/PasswordInput.svelte +8 -7
- package/src/lib/components/Pill.svelte +34 -6
- package/src/lib/components/Popover.svelte +101 -17
- package/src/lib/components/ProgressBar.svelte +10 -7
- package/src/lib/components/RadioGroup.svelte +22 -7
- package/src/lib/components/RangeSlider.svelte +17 -12
- package/src/lib/components/RankedList.svelte +12 -18
- package/src/lib/components/ResizablePanel.svelte +25 -14
- package/src/lib/components/RowToggle.svelte +6 -4
- package/src/lib/components/SearchBox.svelte +6 -2
- package/src/lib/components/SegmentedControl.svelte +16 -2
- package/src/lib/components/Select.svelte +20 -8
- package/src/lib/components/SelectionToolbar.svelte +13 -9
- package/src/lib/components/Sidebar.svelte +32 -11
- package/src/lib/components/Signal.svelte +3 -2
- package/src/lib/components/Skeleton.svelte +21 -9
- package/src/lib/components/SkipLink.svelte +5 -2
- package/src/lib/components/Slider.svelte +16 -9
- package/src/lib/components/SortHeader.svelte +13 -4
- package/src/lib/components/SortableList.svelte +17 -18
- package/src/lib/components/Sparkline.svelte +20 -14
- package/src/lib/components/Spinner.svelte +16 -1
- package/src/lib/components/SplitButton.svelte +27 -8
- package/src/lib/components/Spoiler.svelte +8 -6
- package/src/lib/components/SrOnly.svelte +23 -0
- package/src/lib/components/StackedProgress.svelte +17 -9
- package/src/lib/components/StarRating.svelte +35 -10
- package/src/lib/components/Stat.svelte +19 -5
- package/src/lib/components/StatNumber.svelte +22 -0
- package/src/lib/components/StatusIndicator.svelte +9 -2
- package/src/lib/components/StatusRing.svelte +36 -0
- package/src/lib/components/Stepper.svelte +30 -8
- package/src/lib/components/Submeta.svelte +19 -0
- package/src/lib/components/SwitchPortGrid.svelte +20 -7
- package/src/lib/components/TablePager.svelte +22 -0
- package/src/lib/components/TableShell.svelte +19 -0
- package/src/lib/components/Tabs.svelte +16 -5
- package/src/lib/components/Tag.svelte +5 -3
- package/src/lib/components/TagInput.svelte +9 -10
- package/src/lib/components/Textarea.svelte +7 -1
- package/src/lib/components/TimePicker.svelte +14 -8
- package/src/lib/components/TimeRange.svelte +20 -22
- package/src/lib/components/Timeline.svelte +20 -9
- package/src/lib/components/Toast.svelte +24 -5
- package/src/lib/components/Toggle.svelte +10 -4
- package/src/lib/components/ToggleGroup.svelte +15 -5
- package/src/lib/components/Tooltip.svelte +20 -5
- package/src/lib/components/Topbar.svelte +161 -28
- package/src/lib/components/TopbarActions.svelte +52 -0
- package/src/lib/components/TransferList.svelte +30 -22
- package/src/lib/components/TreeItem.svelte +11 -13
- package/src/lib/components/Unit.svelte +22 -0
- package/src/lib/components/VirtualList.svelte +12 -9
- package/src/lib/index.ts +37 -0
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
<script lang="ts">
|
|
10
10
|
import CloseIcon from '../icons/CloseIcon.svelte';
|
|
11
|
+
import IconButton from './IconButton.svelte';
|
|
12
|
+
|
|
11
13
|
interface Props {
|
|
12
14
|
variant?: BannerVariant;
|
|
13
15
|
title?: string | undefined;
|
|
@@ -21,28 +23,55 @@
|
|
|
21
23
|
title = undefined,
|
|
22
24
|
action = undefined,
|
|
23
25
|
onDismiss = undefined,
|
|
24
|
-
children
|
|
26
|
+
children,
|
|
25
27
|
}: Props = $props();
|
|
26
28
|
|
|
27
29
|
let isUrgent = $derived(variant === 'danger' || variant === 'warn');
|
|
30
|
+
|
|
31
|
+
// Pre-composed variant pairs (banner + title tint). Backgrounds at 10%,
|
|
32
|
+
// top border solid in the base colour, both matching dashboard.css.
|
|
33
|
+
const BANNER: Record<BannerVariant, string> = {
|
|
34
|
+
info: 'bg-status-info/10 border-t-brand-05 text-status-info',
|
|
35
|
+
success: 'bg-status-success/10 border-t-status-success text-status-success',
|
|
36
|
+
warn: 'bg-status-warning/10 border-t-status-warning text-status-warning',
|
|
37
|
+
danger: 'bg-status-danger/10 border-t-status-danger text-status-danger',
|
|
38
|
+
};
|
|
39
|
+
const TITLE: Record<BannerVariant, string> = {
|
|
40
|
+
info: 'text-status-info',
|
|
41
|
+
success: 'text-status-success',
|
|
42
|
+
warn: 'text-status-warning',
|
|
43
|
+
danger: 'text-status-danger',
|
|
44
|
+
};
|
|
28
45
|
</script>
|
|
29
46
|
|
|
30
47
|
<div
|
|
31
|
-
class="
|
|
48
|
+
class="box-border flex w-full items-center gap-2.5 border-t-[3px] px-4 py-2.5 text-13 leading-[1.4]
|
|
49
|
+
{BANNER[variant]}"
|
|
32
50
|
role={isUrgent ? 'alert' : 'status'}
|
|
33
51
|
aria-live={isUrgent ? 'assertive' : 'polite'}
|
|
34
52
|
aria-atomic="true"
|
|
35
53
|
>
|
|
36
|
-
<div class="
|
|
37
|
-
{#if title}
|
|
54
|
+
<div class="flex flex-1 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
55
|
+
{#if title}
|
|
56
|
+
<span class="mr-0.5 font-semibold {TITLE[variant]}">{title}</span>
|
|
57
|
+
{/if}
|
|
38
58
|
<span>{@render children?.()}</span>
|
|
39
59
|
</div>
|
|
40
60
|
{#if action}
|
|
41
|
-
<button
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
class="h-6 shrink-0 cursor-pointer rounded border border-current bg-transparent px-2.5 text-12 text-[inherit] opacity-85 hover:opacity-100"
|
|
64
|
+
onclick={action.onClick}
|
|
65
|
+
>{action.label}</button>
|
|
42
66
|
{/if}
|
|
43
67
|
{#if onDismiss}
|
|
44
|
-
<
|
|
68
|
+
<IconButton
|
|
69
|
+
title="Dismiss banner"
|
|
70
|
+
onclick={onDismiss}
|
|
71
|
+
class="ml-1 h-6 w-6 shrink-0"
|
|
72
|
+
aria-label="Dismiss banner"
|
|
73
|
+
>
|
|
45
74
|
<CloseIcon />
|
|
46
|
-
</
|
|
75
|
+
</IconButton>
|
|
47
76
|
{/if}
|
|
48
77
|
</div>
|
|
@@ -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
|
+
Default body paragraph - the 14 px / regular / 1.4 / --text-2 rhythm
|
|
12
|
+
that runs across table cells, paragraphs, and most user-readable copy
|
|
13
|
+
in the dashboard. Renders <p class="dash-ui-body"> so the
|
|
14
|
+
.dash-ui-body rule in @w5-ui/tokens/tokens.css pins the canonical
|
|
15
|
+
size, weight, line-height, and colour. Stops userland reaching for a
|
|
16
|
+
one-off font-size: 14px; line-height: 1.4; duo (or worse, leaving the
|
|
17
|
+
16 px browser default in place and getting the #1 documented spacing
|
|
18
|
+
drift: "page text looks too big vs the reference dashboard"). Typed
|
|
19
|
+
equivalent of <p class="dash-ui-body"> (the userland-facing semantic
|
|
20
|
+
class documented in Foundations / Type). Body copy is "almost always
|
|
21
|
+
inherited, almost never overridden" inside .card / .table-shell /
|
|
22
|
+
page chrome - reach for <Body> when wrapping standalone prose in
|
|
23
|
+
userland (empty states, onboarding paragraphs, info banners) or when
|
|
24
|
+
a parent stylesheet has reset the default size and you need to opt
|
|
25
|
+
back in explicitly. For 16 px settings descriptions prefer <BodyLg>.
|
|
26
|
+
-->
|
|
27
|
+
<p class="dash-ui-body {className}">
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
</p>
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
Longer-form prose paragraph - settings descriptions, onboarding copy,
|
|
12
|
+
empty-state explanations, anywhere the default 14 px body feels too
|
|
13
|
+
cramped for multi-sentence reading. Renders <p class="dash-ui-body-lg">
|
|
14
|
+
so the .dash-ui-body-lg rule in @w5-ui/tokens/tokens.css applies the
|
|
15
|
+
canonical 16 px / regular weight, 1.55 line-height, and --text-2
|
|
16
|
+
colour pair. Stops userland reaching for a one-off font-size: 16px;
|
|
17
|
+
line-height: 1.6; duo on every settings description. Typed equivalent
|
|
18
|
+
of <p class="dash-ui-body-lg"> (the userland-facing semantic class
|
|
19
|
+
documented in Foundations / Type). Reach for <BodyLg> when the cliff
|
|
20
|
+
between 1.25 (heading) and 1.4 (default body) feels too dense for a
|
|
21
|
+
2-3 sentence paragraph; keep the default .dash-ui-body (14 / 1.4) for
|
|
22
|
+
table cells, table footnotes, and single-line labels.
|
|
23
|
+
-->
|
|
24
|
+
<p class="dash-ui-body-lg {className}">
|
|
25
|
+
{@render children?.()}
|
|
26
|
+
</p>
|
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
-
export interface BreadcrumbItem { label: string; href?: string
|
|
2
|
+
export interface BreadcrumbItem { label: string; href?: string }
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
|
-
|
|
7
6
|
interface Props {
|
|
8
7
|
items?: BreadcrumbItem[];
|
|
9
8
|
class?: string;
|
|
10
9
|
onnavigate?: (payload: number) => void;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
let { items = [], class: className = '',
|
|
14
|
-
onnavigate,
|
|
15
|
-
}: Props = $props();
|
|
12
|
+
let { items = [], class: className = '', onnavigate }: Props = $props();
|
|
16
13
|
</script>
|
|
17
14
|
|
|
18
|
-
<nav aria-label="Breadcrumb" class=
|
|
19
|
-
<ol class="
|
|
15
|
+
<nav aria-label="Breadcrumb" class={className}>
|
|
16
|
+
<ol class="m-0 flex flex-wrap items-center gap-x-1.5 gap-y-0.5 p-0 text-12 [list-style:none]">
|
|
20
17
|
{#each items as item, i}
|
|
21
18
|
{@const isLast = i === items.length - 1}
|
|
22
|
-
<li class="
|
|
19
|
+
<li class="flex items-center gap-1.5">
|
|
23
20
|
{#if isLast}
|
|
24
|
-
<span
|
|
21
|
+
<span
|
|
22
|
+
aria-current="page"
|
|
23
|
+
class="font-medium text-text-2"
|
|
24
|
+
>{item.label}</span>
|
|
25
25
|
{:else if item.href}
|
|
26
|
-
<a
|
|
26
|
+
<a
|
|
27
|
+
href={item.href}
|
|
28
|
+
class="cursor-pointer text-text-4 no-underline transition-colors duration-100 hover:text-text-2 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
29
|
+
>{item.label}</a>
|
|
27
30
|
{:else}
|
|
28
|
-
<button
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
class="cursor-pointer border-0 bg-transparent p-0 font-[inherit] text-text-4 transition-colors duration-100 hover:text-text-2 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05"
|
|
34
|
+
onclick={() => onnavigate?.(i)}
|
|
35
|
+
>{item.label}</button>
|
|
29
36
|
{/if}
|
|
30
37
|
{#if !isLast}
|
|
31
|
-
<span aria-hidden="true"
|
|
38
|
+
<span class="select-none text-border-3" aria-hidden="true">/</span>
|
|
32
39
|
{/if}
|
|
33
40
|
</li>
|
|
34
41
|
{/each}
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
class?: string;
|
|
13
13
|
style?: string;
|
|
14
14
|
'aria-label'?: string | undefined;
|
|
15
|
+
/** ARIA popup trigger: `'dialog'` for popover-style buttons that open
|
|
16
|
+
* an inline calendar / picker; mirrors the React `<Button>` API and the
|
|
17
|
+
* actual prop usage in DatePicker / DateRangePicker / TimeRange. */
|
|
18
|
+
'aria-haspopup'?: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | boolean | undefined;
|
|
19
|
+
/** Expanded state for popup triggers. */
|
|
20
|
+
'aria-expanded'?: boolean | undefined;
|
|
21
|
+
/** ID of the controlled popup/dialog. */
|
|
22
|
+
'aria-controls'?: string | undefined;
|
|
15
23
|
onclick?: (event: MouseEvent) => void;
|
|
16
24
|
children?: Snippet;
|
|
17
25
|
}
|
|
@@ -26,17 +34,38 @@
|
|
|
26
34
|
class: className = '',
|
|
27
35
|
style = '',
|
|
28
36
|
'aria-label': ariaLabel = undefined,
|
|
37
|
+
'aria-haspopup': ariaHasPopup = undefined,
|
|
38
|
+
'aria-expanded': ariaExpanded = undefined,
|
|
39
|
+
'aria-controls': ariaControls = undefined,
|
|
29
40
|
onclick,
|
|
30
41
|
children,
|
|
31
42
|
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
// Pre-compose variant strings so Tailwind's static scanner picks them up.
|
|
45
|
+
// Motif-aware:
|
|
46
|
+
// primary: brand-blue, always white text (works against blue in both motifs).
|
|
47
|
+
// ghost: transparent surface with motif-aware text/border + hover.
|
|
48
|
+
// danger: semantic status-danger text + faded border.
|
|
49
|
+
const VARIANT: Record<NonNullable<Props['variant']>, string> = {
|
|
50
|
+
primary: 'bg-brand-05 text-white hover:bg-brand-06 border-transparent',
|
|
51
|
+
ghost:
|
|
52
|
+
'bg-transparent text-text-2 border-border-2 hover:bg-row-hover hover:text-text-1',
|
|
53
|
+
danger: 'bg-transparent text-status-danger border-status-danger/30',
|
|
54
|
+
};
|
|
32
55
|
</script>
|
|
33
56
|
|
|
34
57
|
<button
|
|
35
58
|
{type}
|
|
36
59
|
{title}
|
|
37
60
|
aria-label={ariaLabel}
|
|
61
|
+
aria-haspopup={ariaHasPopup}
|
|
62
|
+
aria-expanded={ariaExpanded}
|
|
63
|
+
aria-controls={ariaControls}
|
|
38
64
|
{onclick}
|
|
39
|
-
class="
|
|
65
|
+
class="inline-flex h-[30px] cursor-pointer items-center gap-1.5 whitespace-nowrap rounded-md border px-3 text-13 font-medium transition-all duration-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05 disabled:cursor-not-allowed disabled:opacity-55
|
|
66
|
+
{VARIANT[variant]}
|
|
67
|
+
{iconOnly ? 'w-[30px] justify-center p-0' : ''}
|
|
68
|
+
{className}"
|
|
40
69
|
{style}
|
|
41
70
|
disabled={disabled || loading}
|
|
42
71
|
aria-busy={loading ? true : undefined}
|
|
@@ -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;
|
|
@@ -97,13 +95,23 @@
|
|
|
97
95
|
setTimeout(() => focusAt(4), 0);
|
|
98
96
|
}
|
|
99
97
|
}
|
|
98
|
+
|
|
99
|
+
const OCTET_CLS =
|
|
100
|
+
'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';
|
|
101
|
+
const PREFIX_CLS =
|
|
102
|
+
'w-7 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';
|
|
100
103
|
</script>
|
|
101
104
|
|
|
102
|
-
<div class=
|
|
105
|
+
<div class={className}>
|
|
103
106
|
{#if label}
|
|
104
|
-
<label for="{uid}-0" class="
|
|
107
|
+
<label for="{uid}-0" class="text-12 text-text-4">{label}</label>
|
|
105
108
|
{/if}
|
|
106
|
-
<div
|
|
109
|
+
<div
|
|
110
|
+
role="group"
|
|
111
|
+
aria-label={label ?? 'CIDR'}
|
|
112
|
+
data-disabled={disabled ? 'true' : undefined}
|
|
113
|
+
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"
|
|
114
|
+
>
|
|
107
115
|
{#each [0, 1, 2, 3] as i (i)}
|
|
108
116
|
<input
|
|
109
117
|
bind:this={inputs[i]}
|
|
@@ -112,20 +120,20 @@
|
|
|
112
120
|
inputmode="numeric"
|
|
113
121
|
pattern="[0-9]*"
|
|
114
122
|
role="spinbutton"
|
|
115
|
-
aria-label=
|
|
123
|
+
aria-label={`Octet ${i + 1} of 4`}
|
|
116
124
|
aria-valuenow={fields[i] === '' ? 0 : Number(fields[i])}
|
|
117
125
|
aria-valuemin={0}
|
|
118
126
|
aria-valuemax={255}
|
|
119
127
|
value={fields[i]}
|
|
120
128
|
{disabled}
|
|
121
129
|
maxlength={3}
|
|
122
|
-
class=
|
|
130
|
+
class={OCTET_CLS}
|
|
123
131
|
oninput={(e) => commitOctet(i, e.currentTarget.value)}
|
|
124
132
|
onkeydown={(e) => handleKey(i, e)}
|
|
125
133
|
onpaste={handlePaste}
|
|
126
134
|
onfocus={(e) => e.currentTarget.select()}
|
|
127
135
|
/>
|
|
128
|
-
<span class="
|
|
136
|
+
<span aria-hidden="true" class="select-none text-13 leading-none text-text-4">{i < 3 ? '.' : '/'}</span>
|
|
129
137
|
{/each}
|
|
130
138
|
<input
|
|
131
139
|
bind:this={inputs[4]}
|
|
@@ -140,7 +148,7 @@
|
|
|
140
148
|
value={fields[4]}
|
|
141
149
|
{disabled}
|
|
142
150
|
maxlength={2}
|
|
143
|
-
class=
|
|
151
|
+
class={PREFIX_CLS}
|
|
144
152
|
oninput={(e) => commitPrefix(e.currentTarget.value)}
|
|
145
153
|
onkeydown={(e) => handleKey(4, e)}
|
|
146
154
|
onpaste={handlePaste}
|
|
@@ -10,33 +10,54 @@
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
let { variant = 'info', title = undefined, children }: Props = $props();
|
|
13
|
+
|
|
14
|
+
// Per-variant chrome ramp (bg + left border + body text colour) and a
|
|
15
|
+
// separate title colour. Pre-composed so Tailwind v4's static scanner
|
|
16
|
+
// picks them up.
|
|
17
|
+
const VARIANT: Record<CalloutVariant, string> = {
|
|
18
|
+
info: 'bg-brand-05/[0.08] border-l-brand-05 text-status-info',
|
|
19
|
+
success: 'bg-status-success/[0.08] border-l-status-success text-status-success',
|
|
20
|
+
warn: 'bg-status-warning/[0.08] border-l-status-warning text-status-warning',
|
|
21
|
+
danger: 'bg-status-danger/[0.08] border-l-status-danger text-status-danger',
|
|
22
|
+
tip: 'bg-brand-04/[0.08] border-l-brand-04 text-status-info',
|
|
23
|
+
};
|
|
24
|
+
const TITLE_COLOUR: Record<CalloutVariant, string> = {
|
|
25
|
+
info: 'text-status-info',
|
|
26
|
+
success: 'text-status-success',
|
|
27
|
+
warn: 'text-status-warning',
|
|
28
|
+
danger: 'text-status-danger',
|
|
29
|
+
tip: 'text-status-info',
|
|
30
|
+
};
|
|
13
31
|
</script>
|
|
14
32
|
|
|
15
|
-
<div
|
|
33
|
+
<div
|
|
34
|
+
role="note"
|
|
35
|
+
class="flex gap-3 rounded-lg border-l-[3px] px-4 py-3 text-13 leading-[1.5] {VARIANT[variant]}"
|
|
36
|
+
>
|
|
16
37
|
{#if variant === 'info'}
|
|
17
|
-
<svg class="
|
|
38
|
+
<svg class="mt-px h-4 w-4 shrink-0" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
18
39
|
<path fill-rule="evenodd" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM7.25 5.5a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0ZM7 7.5h2v4H7v-4Z" />
|
|
19
40
|
</svg>
|
|
20
41
|
{:else if variant === 'success'}
|
|
21
|
-
<svg class="
|
|
42
|
+
<svg class="mt-px h-4 w-4 shrink-0" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
22
43
|
<path fill-rule="evenodd" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1Zm3.78 5.47-4.25 4.5a.75.75 0 0 1-1.06.03L4.72 9.22a.75.75 0 1 1 1.06-1.06l1.22 1.22 3.72-3.94a.75.75 0 1 1 1.06 1.03Z" />
|
|
23
44
|
</svg>
|
|
24
45
|
{:else if variant === 'warn'}
|
|
25
|
-
<svg class="
|
|
46
|
+
<svg class="mt-px h-4 w-4 shrink-0" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
26
47
|
<path fill-rule="evenodd" d="M8.9 1.6a1 1 0 0 0-1.8 0L.6 13a1 1 0 0 0 .9 1.5h13a1 1 0 0 0 .9-1.5L8.9 1.6ZM7.25 6.5a.75.75 0 1 1 1.5 0v3a.75.75 0 0 1-1.5 0v-3Zm.75 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z" />
|
|
27
48
|
</svg>
|
|
28
49
|
{:else if variant === 'danger'}
|
|
29
|
-
<svg class="
|
|
50
|
+
<svg class="mt-px h-4 w-4 shrink-0" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
30
51
|
<path fill-rule="evenodd" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM5.97 5.97a.75.75 0 0 1 1.06 0L8 6.94l.97-.97a.75.75 0 1 1 1.06 1.06L9.06 8l.97.97a.75.75 0 0 1-1.06 1.06L8 9.06l-.97.97a.75.75 0 0 1-1.06-1.06L6.94 8l-.97-.97a.75.75 0 0 1 0-1.06Z" />
|
|
31
52
|
</svg>
|
|
32
53
|
{:else}
|
|
33
|
-
<svg class="
|
|
54
|
+
<svg class="mt-px h-4 w-4 shrink-0" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
34
55
|
<path d="M8 1a5 5 0 0 1 3 9.01V11H5v-.99A5 5 0 0 1 8 1Zm-1 12h2v1.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V13Z" />
|
|
35
56
|
</svg>
|
|
36
57
|
{/if}
|
|
37
|
-
<div class="
|
|
58
|
+
<div class="flex-1">
|
|
38
59
|
{#if title}
|
|
39
|
-
<div class="
|
|
60
|
+
<div class="mb-1 font-semibold {TITLE_COLOUR[variant]}">{title}</div>
|
|
40
61
|
{/if}
|
|
41
62
|
<div>{@render children?.()}</div>
|
|
42
63
|
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
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 helper / metadata text - "last updated 5 min ago", table footnote,
|
|
12
|
+
timestamp under a chart, byline beneath a section heading. Renders
|
|
13
|
+
<span class="dash-ui-caption"> so the .dash-ui-caption rule in
|
|
14
|
+
@w5-ui/tokens/tokens.css applies the canonical 12 px / regular weight,
|
|
15
|
+
1.25 line-height, and the muted --text-3 colour. Stops userland reaching
|
|
16
|
+
for a one-off font-size: 12px; color: #a4a7b5; trio on every caption.
|
|
17
|
+
Typed equivalent of <span class="dash-ui-caption"> (the userland-facing
|
|
18
|
+
semantic class documented in Foundations / Type). Use <Caption> for
|
|
19
|
+
non-essential metadata only: on --depthBg-3 (popovers, drawers) the
|
|
20
|
+
--text-3 contrast slides toward 4.0:1, so keep primary copy on
|
|
21
|
+
.dash-ui-body / --text-2. Pair with <Muted> when you want the same
|
|
22
|
+
colour at the default 14 px body size.
|
|
23
|
+
-->
|
|
24
|
+
<span class="dash-ui-caption {className}">
|
|
25
|
+
{@render children?.()}
|
|
26
|
+
</span>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
2
|
interface Props {
|
|
4
3
|
span?: number | undefined;
|
|
5
4
|
class?: string;
|
|
@@ -11,11 +10,20 @@
|
|
|
11
10
|
span = undefined,
|
|
12
11
|
class: className = '',
|
|
13
12
|
style = '',
|
|
14
|
-
children
|
|
13
|
+
children,
|
|
15
14
|
}: Props = $props();
|
|
16
15
|
let spanStyle = $derived(span !== undefined ? `grid-column: span ${span};` : '');
|
|
17
16
|
</script>
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
<!--
|
|
19
|
+
Motif-aware card surface: bg via `var(--depthBg-1)` (= `#141415` dark,
|
|
20
|
+
`#ffffff` light) and border via `var(--border-1)`. The `h3` child
|
|
21
|
+
styling lives in the consumer slot — Tailwind utilities at the call
|
|
22
|
+
site, not bundled here.
|
|
23
|
+
-->
|
|
24
|
+
<div
|
|
25
|
+
class="flex min-w-0 flex-col gap-2 rounded-[8px] border border-border-1 bg-bg-1 px-4 py-3.5 {className}"
|
|
26
|
+
style="{spanStyle} {style}"
|
|
27
|
+
>
|
|
20
28
|
{@render children?.()}
|
|
21
29
|
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
href?: string;
|
|
4
|
+
class?: string;
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { href = undefined, class: className = '', children }: Props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<!--
|
|
12
|
+
Trailing "View all" / "More →" affordance inside a <Card> title row. Renders
|
|
13
|
+
an <a class="more"> so the `.card h3 .more` rule in
|
|
14
|
+
@w5-ui/tokens/dashboard.css styles it (muted colour, pointer cursor) and the
|
|
15
|
+
parent <h3> flex pushes it to the right edge of the title. Pair with <Card>
|
|
16
|
+
and a plain <h3> for the canonical card title pattern.
|
|
17
|
+
-->
|
|
18
|
+
<a {href} class="more {className}">
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</a>
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
Title row inside a <Card> tile. Renders a plain <h3> so the `.card h3` rule
|
|
12
|
+
in @w5-ui/tokens/dashboard.css sets the canonical 12 px size, weight 500,
|
|
13
|
+
--text-3 (muted) colour, and flex layout (title on the left, any trailing
|
|
14
|
+
<Pill> / <CardMore> pushed to the right edge). Stops userland reaching for
|
|
15
|
+
an <h2> or a custom <div class="title"> that would skip the rule and read
|
|
16
|
+
broken inside the tile. Pair with <CardMore> for a "View all →" affordance.
|
|
17
|
+
-->
|
|
18
|
+
<h3 class={className}>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</h3>
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
let { slides = [], label, class: klass = '' }: Props = $props();
|
|
9
|
-
|
|
10
9
|
|
|
11
10
|
let index = $state(0);
|
|
12
11
|
|
|
@@ -19,49 +18,57 @@
|
|
|
19
18
|
</script>
|
|
20
19
|
|
|
21
20
|
<section
|
|
22
|
-
class="
|
|
21
|
+
class="relative overflow-hidden rounded-lg border border-border-1 bg-divider {klass}"
|
|
23
22
|
aria-roledescription="carousel"
|
|
24
23
|
aria-label={label}
|
|
25
24
|
>
|
|
26
|
-
<div class="
|
|
25
|
+
<div class="relative min-h-[140px]" aria-live="polite" aria-atomic="false">
|
|
27
26
|
{#each slides as slide, i (slide.id)}
|
|
28
27
|
<div
|
|
29
28
|
role="group"
|
|
30
29
|
aria-roledescription="slide"
|
|
31
|
-
aria-label=
|
|
32
|
-
|
|
30
|
+
aria-label={`${i + 1} of ${slides.length}`}
|
|
31
|
+
data-active={i === index ? 'true' : undefined}
|
|
32
|
+
class="absolute inset-0 flex items-stretch gap-3 p-4 opacity-0 transition-opacity duration-200 data-[active=true]:opacity-100 data-[active=true]:relative"
|
|
33
33
|
aria-hidden={i !== index ? true : undefined}
|
|
34
34
|
>
|
|
35
35
|
{#if slide.color}
|
|
36
|
-
<div class="
|
|
36
|
+
<div class="w-1 shrink-0 rounded" style="background:{slide.color}" aria-hidden="true"></div>
|
|
37
37
|
{/if}
|
|
38
|
-
<div class="
|
|
39
|
-
<div class="
|
|
38
|
+
<div class="min-w-0 flex-1">
|
|
39
|
+
<div class="text-15 font-semibold text-text-1">{slide.title}</div>
|
|
40
40
|
{#if slide.description}
|
|
41
|
-
<div class="
|
|
41
|
+
<div class="mt-1 text-13 text-text-3">{slide.description}</div>
|
|
42
42
|
{/if}
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
45
45
|
{/each}
|
|
46
46
|
</div>
|
|
47
|
-
<div class="
|
|
48
|
-
<button
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
<div class="flex items-center justify-between border-t border-border-1 bg-bg-page px-2 py-1.5">
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
aria-label="Previous slide"
|
|
51
|
+
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"
|
|
52
|
+
onclick={prev}
|
|
53
|
+
>‹</button>
|
|
54
|
+
<div role="tablist" aria-label="Choose slide to display" class="flex items-center gap-1.5">
|
|
52
55
|
{#each slides as slide, i (slide.id)}
|
|
53
56
|
<button
|
|
54
57
|
type="button"
|
|
55
58
|
role="tab"
|
|
56
59
|
aria-selected={i === index}
|
|
57
|
-
aria-label=
|
|
58
|
-
|
|
60
|
+
aria-label={`Slide ${i + 1}: ${slide.title}`}
|
|
61
|
+
data-active={i === index ? 'true' : undefined}
|
|
62
|
+
class="h-1.5 w-1.5 cursor-pointer rounded-full border-0 bg-border-3 transition-all duration-150 hover:bg-text-3 data-[active=true]:w-4 data-[active=true]:bg-brand-05"
|
|
59
63
|
onclick={() => (index = i)}
|
|
60
|
-
></button>
|
|
64
|
+
></button>
|
|
61
65
|
{/each}
|
|
62
66
|
</div>
|
|
63
|
-
<button
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
aria-label="Next slide"
|
|
70
|
+
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"
|
|
71
|
+
onclick={next}
|
|
72
|
+
>›</button>
|
|
66
73
|
</div>
|
|
67
74
|
</section>
|
|
@@ -38,16 +38,36 @@
|
|
|
38
38
|
el.indeterminate = indeterminate;
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
|
+
|
|
42
|
+
// The ✓ + indeterminate dash glyphs ship as inline SVG data URLs so the
|
|
43
|
+
// checkbox stays a single `<input type="checkbox">` (no wrapping span,
|
|
44
|
+
// native AT behaviour, native focus ring), with the marks drawn by
|
|
45
|
+
// `background-image`. Tailwind v4 supports arbitrary-value `bg-[url(...)]`
|
|
46
|
+
// but the SVG payload would have to live inline in a class string; we
|
|
47
|
+
// keep these as `var(...)` references via a tiny inline style so the
|
|
48
|
+
// class string stays scannable + the icons stay editable.
|
|
49
|
+
const CHECK_SVG = `url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3 8.5l3 3 6.5-6' stroke='%23fff' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E")`;
|
|
50
|
+
const DASH_SVG = `url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 8h8' stroke='%23fff' stroke-width='2' stroke-linecap='round' fill='none'/%3E%3C/svg%3E")`;
|
|
51
|
+
|
|
52
|
+
// Compose the background image conditionally for the input style.
|
|
53
|
+
let bgImage = $derived(indeterminate ? DASH_SVG : checked ? CHECK_SVG : 'none');
|
|
54
|
+
|
|
55
|
+
const INPUT_CLS =
|
|
56
|
+
'relative inline-block h-4 w-4 min-w-4 cursor-pointer appearance-none rounded-sm border-[1.5px] border-border-3 bg-bg-1 align-middle bg-[length:100%_100%] bg-center bg-no-repeat transition-[border-color,background-color] duration-100 hover:border-text-3 checked:border-brand-05 checked:bg-brand-05 indeterminate:border-brand-05 indeterminate:bg-brand-05 disabled:cursor-not-allowed disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-05';
|
|
41
57
|
</script>
|
|
42
58
|
|
|
43
59
|
{#if label}
|
|
44
|
-
<label
|
|
60
|
+
<label
|
|
61
|
+
class="inline-flex cursor-pointer select-none items-center gap-2 text-13 text-text-2 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-40"
|
|
62
|
+
for={inputId}
|
|
63
|
+
>
|
|
45
64
|
<input
|
|
46
65
|
bind:this={el}
|
|
47
66
|
type="checkbox"
|
|
48
67
|
id={inputId}
|
|
49
68
|
{disabled}
|
|
50
|
-
class=
|
|
69
|
+
class={INPUT_CLS}
|
|
70
|
+
style="background-image: {bgImage};"
|
|
51
71
|
{onchange}
|
|
52
72
|
{...rest}
|
|
53
73
|
/>
|
|
@@ -59,7 +79,8 @@
|
|
|
59
79
|
type="checkbox"
|
|
60
80
|
id={inputId}
|
|
61
81
|
{disabled}
|
|
62
|
-
class=
|
|
82
|
+
class={INPUT_CLS}
|
|
83
|
+
style="background-image: {bgImage};"
|
|
63
84
|
{onchange}
|
|
64
85
|
{...rest}
|
|
65
86
|
/>
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
Inline code snippet for key names, config paths, environment variables
|
|
12
|
+
and other short literal strings that read inside body copy. Renders a
|
|
13
|
+
<code class="dash-ui-code"> so the .dash-ui-code rule in
|
|
14
|
+
@w5-ui/tokens/css applies the canonical JetBrains Mono family, 12 px
|
|
15
|
+
size, muted text colour, and the inset --depthBg-2 chip background
|
|
16
|
+
with a 1 px border. Stops userland reaching for bare <code> (which
|
|
17
|
+
falls back to a serif monospace on #fff) or inline font-family /
|
|
18
|
+
background overrides on every snippet. Typed equivalent of
|
|
19
|
+
<code class="dash-ui-code"> (reach for <CodeBlock> when the snippet
|
|
20
|
+
wraps to multiple lines; this primitive is for the inline case).
|
|
21
|
+
-->
|
|
22
|
+
<code class="dash-ui-code {className}">
|
|
23
|
+
{@render children?.()}
|
|
24
|
+
</code>
|