@w5-ui/svelte 0.1.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.
Files changed (195) hide show
  1. package/README.md +8 -0
  2. package/package.json +57 -0
  3. package/src/lib/components/Accordion.svelte +3 -0
  4. package/src/lib/components/AccordionItem.svelte +40 -0
  5. package/src/lib/components/ActionMenu.svelte +96 -0
  6. package/src/lib/components/ActivityFeed.svelte +60 -0
  7. package/src/lib/components/Alert.svelte +19 -0
  8. package/src/lib/components/AnnotatedTimeSeries.svelte +90 -0
  9. package/src/lib/components/ArcDiagram.svelte +74 -0
  10. package/src/lib/components/AreaChart.svelte +64 -0
  11. package/src/lib/components/Avatar.svelte +30 -0
  12. package/src/lib/components/AvatarGroup.svelte +35 -0
  13. package/src/lib/components/Badge.svelte +25 -0
  14. package/src/lib/components/Banner.svelte +37 -0
  15. package/src/lib/components/BarChart.svelte +52 -0
  16. package/src/lib/components/BeeswarmChart.svelte +88 -0
  17. package/src/lib/components/BoxPlot.svelte +66 -0
  18. package/src/lib/components/Breadcrumb.svelte +31 -0
  19. package/src/lib/components/BubbleChart.svelte +69 -0
  20. package/src/lib/components/BulletChart.svelte +70 -0
  21. package/src/lib/components/BumpChart.svelte +109 -0
  22. package/src/lib/components/Button.svelte +25 -0
  23. package/src/lib/components/CIDRInput.svelte +142 -0
  24. package/src/lib/components/CalendarHeatmap.svelte +86 -0
  25. package/src/lib/components/Callout.svelte +38 -0
  26. package/src/lib/components/CandlestickChart.svelte +66 -0
  27. package/src/lib/components/Card.svelte +11 -0
  28. package/src/lib/components/Carousel.svelte +64 -0
  29. package/src/lib/components/Checkbox.svelte +45 -0
  30. package/src/lib/components/ChordDiagram.svelte +90 -0
  31. package/src/lib/components/CirclePacking.svelte +119 -0
  32. package/src/lib/components/CodeBlock.svelte +49 -0
  33. package/src/lib/components/ColorPicker.svelte +91 -0
  34. package/src/lib/components/ColumnToggle.svelte +96 -0
  35. package/src/lib/components/Combobox.svelte +138 -0
  36. package/src/lib/components/CommandPalette.svelte +152 -0
  37. package/src/lib/components/ConfirmDialog.svelte +84 -0
  38. package/src/lib/components/ContextMenu.svelte +107 -0
  39. package/src/lib/components/ContextualHelp.svelte +73 -0
  40. package/src/lib/components/ContourPlot.svelte +164 -0
  41. package/src/lib/components/CopyButton.svelte +33 -0
  42. package/src/lib/components/CorrelationMatrix.svelte +90 -0
  43. package/src/lib/components/CountUp.svelte +49 -0
  44. package/src/lib/components/CumulativeDistribution.svelte +67 -0
  45. package/src/lib/components/DatePicker.svelte +201 -0
  46. package/src/lib/components/DateRangePicker.svelte +249 -0
  47. package/src/lib/components/Dendrogram.svelte +128 -0
  48. package/src/lib/components/Donut.svelte +49 -0
  49. package/src/lib/components/DotPlot.svelte +121 -0
  50. package/src/lib/components/Drawer.svelte +60 -0
  51. package/src/lib/components/DualAxisChart.svelte +80 -0
  52. package/src/lib/components/DumbbellChart.svelte +56 -0
  53. package/src/lib/components/DurationInput.svelte +118 -0
  54. package/src/lib/components/EmptyState.svelte +21 -0
  55. package/src/lib/components/ErrorBandChart.svelte +69 -0
  56. package/src/lib/components/ExpandableRow.svelte +42 -0
  57. package/src/lib/components/Field.svelte +40 -0
  58. package/src/lib/components/FileUpload.svelte +96 -0
  59. package/src/lib/components/FilterBuilder.svelte +160 -0
  60. package/src/lib/components/FlameGraph.svelte +88 -0
  61. package/src/lib/components/ForceGraph.svelte +104 -0
  62. package/src/lib/components/FunnelChart.svelte +50 -0
  63. package/src/lib/components/GanttChart.svelte +49 -0
  64. package/src/lib/components/Gauge.svelte +53 -0
  65. package/src/lib/components/GroupedList.svelte +87 -0
  66. package/src/lib/components/HealthBar.svelte +19 -0
  67. package/src/lib/components/HeatMap.svelte +64 -0
  68. package/src/lib/components/HexbinChart.svelte +100 -0
  69. package/src/lib/components/Histogram.svelte +93 -0
  70. package/src/lib/components/HorizonChart.svelte +71 -0
  71. package/src/lib/components/HoverCard.svelte +45 -0
  72. package/src/lib/components/IPInput.svelte +103 -0
  73. package/src/lib/components/IcicleChart.svelte +87 -0
  74. package/src/lib/components/IconButton.svelte +19 -0
  75. package/src/lib/components/InlineEdit.svelte +72 -0
  76. package/src/lib/components/Input.svelte +18 -0
  77. package/src/lib/components/InputGroup.svelte +14 -0
  78. package/src/lib/components/JsonViewer.svelte +15 -0
  79. package/src/lib/components/JsonViewerNode.svelte +59 -0
  80. package/src/lib/components/KVTable.svelte +18 -0
  81. package/src/lib/components/KanbanBoard.svelte +141 -0
  82. package/src/lib/components/Kbd.svelte +16 -0
  83. package/src/lib/components/LineChart.svelte +67 -0
  84. package/src/lib/components/LogViewer.svelte +84 -0
  85. package/src/lib/components/LollipopChart.svelte +79 -0
  86. package/src/lib/components/MACInput.svelte +98 -0
  87. package/src/lib/components/MarimekkoChart.svelte +108 -0
  88. package/src/lib/components/MatrixChart.svelte +55 -0
  89. package/src/lib/components/Menubar.svelte +131 -0
  90. package/src/lib/components/MirroredBarChart.svelte +120 -0
  91. package/src/lib/components/Modal.svelte +90 -0
  92. package/src/lib/components/MultiSelect.svelte +165 -0
  93. package/src/lib/components/NightingaleChart.svelte +63 -0
  94. package/src/lib/components/NotificationPanel.svelte +170 -0
  95. package/src/lib/components/NumberInput.svelte +93 -0
  96. package/src/lib/components/OTPInput.svelte +94 -0
  97. package/src/lib/components/Pagination.svelte +58 -0
  98. package/src/lib/components/ParallelCoordinates.svelte +98 -0
  99. package/src/lib/components/ParetoChart.svelte +88 -0
  100. package/src/lib/components/PasswordInput.svelte +62 -0
  101. package/src/lib/components/PieChart.svelte +91 -0
  102. package/src/lib/components/Pill.svelte +12 -0
  103. package/src/lib/components/PolarHeatmap.svelte +85 -0
  104. package/src/lib/components/Popover.svelte +89 -0
  105. package/src/lib/components/ProgressBar.svelte +29 -0
  106. package/src/lib/components/PunchCard.svelte +73 -0
  107. package/src/lib/components/QuadrantChart.svelte +122 -0
  108. package/src/lib/components/RadarChart.svelte +71 -0
  109. package/src/lib/components/RadialBarChart.svelte +76 -0
  110. package/src/lib/components/RadioGroup.svelte +48 -0
  111. package/src/lib/components/RangeSlider.svelte +68 -0
  112. package/src/lib/components/RankedList.svelte +49 -0
  113. package/src/lib/components/ResizablePanel.svelte +77 -0
  114. package/src/lib/components/RidgelinePlot.svelte +103 -0
  115. package/src/lib/components/RowToggle.svelte +19 -0
  116. package/src/lib/components/SankeyDiagram.svelte +100 -0
  117. package/src/lib/components/ScatterPlot.svelte +54 -0
  118. package/src/lib/components/SearchBox.svelte +11 -0
  119. package/src/lib/components/SegmentedControl.svelte +51 -0
  120. package/src/lib/components/Select.svelte +110 -0
  121. package/src/lib/components/SelectionToolbar.svelte +42 -0
  122. package/src/lib/components/Sidebar.svelte +44 -0
  123. package/src/lib/components/Signal.svelte +12 -0
  124. package/src/lib/components/Skeleton.svelte +23 -0
  125. package/src/lib/components/SkipLink.svelte +9 -0
  126. package/src/lib/components/Slider.svelte +48 -0
  127. package/src/lib/components/SlopeChart.svelte +91 -0
  128. package/src/lib/components/SortHeader.svelte +19 -0
  129. package/src/lib/components/SortableList.svelte +87 -0
  130. package/src/lib/components/Sparkline.svelte +32 -0
  131. package/src/lib/components/SparklineMatrix.svelte +84 -0
  132. package/src/lib/components/Spinner.svelte +6 -0
  133. package/src/lib/components/SplitButton.svelte +102 -0
  134. package/src/lib/components/Spoiler.svelte +41 -0
  135. package/src/lib/components/StackedBarChart.svelte +83 -0
  136. package/src/lib/components/StackedProgress.svelte +32 -0
  137. package/src/lib/components/StarRating.svelte +64 -0
  138. package/src/lib/components/Stat.svelte +24 -0
  139. package/src/lib/components/StatusIndicator.svelte +10 -0
  140. package/src/lib/components/StepChart.svelte +74 -0
  141. package/src/lib/components/Stepper.svelte +40 -0
  142. package/src/lib/components/StreamGraph.svelte +93 -0
  143. package/src/lib/components/StripeChart.svelte +52 -0
  144. package/src/lib/components/SunburstChart.svelte +80 -0
  145. package/src/lib/components/SwitchPortGrid.svelte +45 -0
  146. package/src/lib/components/TabPanel.svelte +17 -0
  147. package/src/lib/components/Tabs.svelte +45 -0
  148. package/src/lib/components/Tag.svelte +19 -0
  149. package/src/lib/components/TagInput.svelte +92 -0
  150. package/src/lib/components/Textarea.svelte +31 -0
  151. package/src/lib/components/ThresholdAreaChart.svelte +72 -0
  152. package/src/lib/components/TimePicker.svelte +116 -0
  153. package/src/lib/components/TimeRange.svelte +104 -0
  154. package/src/lib/components/Timeline.svelte +32 -0
  155. package/src/lib/components/Toast.svelte +52 -0
  156. package/src/lib/components/Toggle.svelte +23 -0
  157. package/src/lib/components/ToggleGroup.svelte +32 -0
  158. package/src/lib/components/Tooltip.svelte +16 -0
  159. package/src/lib/components/Topbar.svelte +63 -0
  160. package/src/lib/components/TransferList.svelte +184 -0
  161. package/src/lib/components/TreeItem.svelte +61 -0
  162. package/src/lib/components/TreeMap.svelte +90 -0
  163. package/src/lib/components/TreeView.svelte +130 -0
  164. package/src/lib/components/UptimeTimeline.svelte +86 -0
  165. package/src/lib/components/VennDiagram.svelte +91 -0
  166. package/src/lib/components/ViolinPlot.svelte +114 -0
  167. package/src/lib/components/VirtualList.svelte +38 -0
  168. package/src/lib/components/WaffleChart.svelte +68 -0
  169. package/src/lib/components/WaterfallChart.svelte +96 -0
  170. package/src/lib/components/WordCloud.svelte +103 -0
  171. package/src/lib/icons/AlarmIcon.svelte +5 -0
  172. package/src/lib/icons/BellIcon.svelte +6 -0
  173. package/src/lib/icons/CaretIcon.svelte +8 -0
  174. package/src/lib/icons/ClientsIcon.svelte +5 -0
  175. package/src/lib/icons/CloseIcon.svelte +6 -0
  176. package/src/lib/icons/DashboardIcon.svelte +7 -0
  177. package/src/lib/icons/DevicesIcon.svelte +5 -0
  178. package/src/lib/icons/DownloadIcon.svelte +6 -0
  179. package/src/lib/icons/HelpIcon.svelte +7 -0
  180. package/src/lib/icons/InfraIcon.svelte +7 -0
  181. package/src/lib/icons/IntegrationsIcon.svelte +5 -0
  182. package/src/lib/icons/LogsIcon.svelte +6 -0
  183. package/src/lib/icons/PlusIcon.svelte +6 -0
  184. package/src/lib/icons/PortsIcon.svelte +6 -0
  185. package/src/lib/icons/SearchIcon.svelte +10 -0
  186. package/src/lib/icons/SecurityIcon.svelte +4 -0
  187. package/src/lib/icons/SettingsIcon.svelte +5 -0
  188. package/src/lib/icons/TopologyIcon.svelte +7 -0
  189. package/src/lib/icons/UpdatesIcon.svelte +6 -0
  190. package/src/lib/icons/VpnIcon.svelte +5 -0
  191. package/src/lib/icons/WifiIcon.svelte +5 -0
  192. package/src/lib/icons/WirelessIcon.svelte +4 -0
  193. package/src/lib/icons.ts +22 -0
  194. package/src/lib/index.ts +275 -0
  195. package/src/lib/styles.css +2 -0
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ export interface SunburstNode {
3
+ label: string;
4
+ value?: number;
5
+ color?: string;
6
+ children?: SunburstNode[];
7
+ }
8
+
9
+ export let root: SunburstNode;
10
+ export let ariaLabel: string = 'Sunburst chart';
11
+
12
+ const PALETTE = ['#006FFF', '#00C875', '#A78BFA', '#F5A623', '#50B8E7', '#F04949'];
13
+ const CX = 160, CY = 160, R_HOLE = 48, R_MID = 97, R_OUTER = 142, GAP = 0.01;
14
+
15
+ function arcPath(r1: number, r2: number, a1: number, a2: number): string {
16
+ const lg = a2 - a1 > Math.PI ? 1 : 0;
17
+ const p = (r: number, a: number) => `${(CX + r * Math.cos(a)).toFixed(1)},${(CY + r * Math.sin(a)).toFixed(1)}`;
18
+ return `M${p(r2, a1)} A${r2},${r2} 0 ${lg},1 ${p(r2, a2)} L${p(r1, a2)} A${r1},${r1} 0 ${lg},0 ${p(r1, a1)} Z`;
19
+ }
20
+
21
+ type PathData = { d: string; fill: string };
22
+ type LabelData = { x: number; y: number; anchor: string; text: string };
23
+
24
+ $: ({ paths, labels, total } = (() => {
25
+ const items = root.children ?? [];
26
+ const total = items.reduce((s, c) => s + (c.value ?? c.children?.reduce((cs, x) => cs + (x.value ?? 0), 0) ?? 0), 0);
27
+ if (!total) return { paths: [] as PathData[], labels: [] as LabelData[], total: 0 };
28
+
29
+ const paths: PathData[] = [];
30
+ const labels: LabelData[] = [];
31
+ let a = -Math.PI / 2;
32
+
33
+ for (let i = 0; i < items.length; i++) {
34
+ const c = items[i];
35
+ const fill = c.color ?? PALETTE[i % PALETTE.length];
36
+ const cv = c.value ?? c.children?.reduce((s, x) => s + (x.value ?? 0), 0) ?? 0;
37
+ const span = (cv / total) * 2 * Math.PI;
38
+ const a2 = a + span;
39
+ const midA = (a + a2) / 2;
40
+
41
+ paths.push({ d: arcPath(R_MID + 2, R_OUTER, a + GAP, a2 - GAP), fill });
42
+
43
+ if (span > 0.22) {
44
+ const r = (R_MID + 2 + R_OUTER) / 2;
45
+ const anchor = Math.cos(midA) > 0.15 ? 'start' : Math.cos(midA) < -0.15 ? 'end' : 'middle';
46
+ labels.push({ x: CX + r * Math.cos(midA), y: CY + r * Math.sin(midA) + 4, anchor, text: c.label });
47
+ }
48
+
49
+ if (c.children?.length) {
50
+ let ca = a;
51
+ for (const child of c.children) {
52
+ const cs = ((child.value ?? 0) / cv) * span;
53
+ if (cs > GAP * 2) paths.push({ d: arcPath(R_HOLE, R_MID - 2, ca + GAP, ca + cs - GAP), fill: fill + '99' });
54
+ ca += cs;
55
+ }
56
+ } else {
57
+ paths.push({ d: arcPath(R_HOLE, R_MID - 2, a + GAP, a2 - GAP), fill: fill + '99' });
58
+ }
59
+
60
+ a = a2;
61
+ }
62
+
63
+ return { paths, labels, total };
64
+ })());
65
+
66
+ const fmt = (n: number) => n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
67
+ </script>
68
+
69
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
70
+ <svg viewBox="0 0 320 320" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
71
+ {#each paths as p, i (i)}
72
+ <path d={p.d} fill={p.fill} />
73
+ {/each}
74
+ {#each labels as l, i (i)}
75
+ <text x={l.x.toFixed(1)} y={l.y.toFixed(1)} text-anchor={l.anchor} fill="#fff" font-size="10" font-weight="600" font-family="inherit">{l.text}</text>
76
+ {/each}
77
+ <text x={CX} y={CY - 8} text-anchor="middle" fill="#fff" font-size="22" font-weight="700" font-family="inherit">{fmt(total)}</text>
78
+ <text x={CX} y={CY + 12} text-anchor="middle" fill="#6E7079" font-size="10" font-family="inherit">{root.label}</text>
79
+ </svg>
80
+ </div>
@@ -0,0 +1,45 @@
1
+ <script context="module" lang="ts">
2
+ export type SwitchPortStatus = 'up' | 'poe' | 'down';
3
+ export interface SwitchPort {
4
+ status: SwitchPortStatus;
5
+ speed?: string;
6
+ label?: string;
7
+ }
8
+ const STATUS_LABEL: Record<SwitchPortStatus, string> = {
9
+ up: 'Connected',
10
+ poe: 'PoE Active',
11
+ down: 'Down',
12
+ };
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import { createEventDispatcher } from 'svelte';
17
+ export let ports: SwitchPort[] = [];
18
+ export let columns: number = 12;
19
+ export let ariaLabel: string = 'Switch port panel';
20
+ const dispatch = createEventDispatcher<{ portclick: number }>();
21
+ </script>
22
+
23
+ <ul class="spg" role="list" aria-label={ariaLabel} style="grid-template-columns: repeat({columns}, 1fr);">
24
+ {#each ports as port, i}
25
+ {@const n = i + 1}
26
+ {@const desc = `Port ${n}: ${STATUS_LABEL[port.status]}${port.speed ? ` · ${port.speed}` : ''}${port.label ? ` · ${port.label}` : ''}`}
27
+ <li>
28
+ <button
29
+ type="button"
30
+ class="spg__btn"
31
+ data-status={port.status}
32
+ aria-label={desc}
33
+ on:click={() => dispatch('portclick', i)}
34
+ >
35
+ <span class="spg__num">{n}</span>
36
+ {#if port.speed}
37
+ <span class="spg__speed--{port.status}">{port.speed}</span>
38
+ {/if}
39
+ {#if port.status === 'poe'}
40
+ <span class="spg__poe" aria-hidden="true">PoE</span>
41
+ {/if}
42
+ </button>
43
+ </li>
44
+ {/each}
45
+ </ul>
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ export let id: string;
3
+ export let active: string;
4
+ $: isActive = id === active;
5
+ </script>
6
+
7
+ <div
8
+ role="tabpanel"
9
+ id="tabpanel-{id}"
10
+ aria-labelledby="tab-{id}"
11
+ hidden={!isActive}
12
+ tabindex="0"
13
+ >
14
+ {#if isActive}
15
+ <slot />
16
+ {/if}
17
+ </div>
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ export interface TabItem { id: string; label: string; badge?: string | number }
4
+ export let items: TabItem[] = [];
5
+ export let active: string;
6
+ export let ariaLabel: string | undefined = undefined;
7
+ const dispatch = createEventDispatcher<{ change: string }>();
8
+
9
+ let buttons: (HTMLButtonElement | null)[] = [];
10
+
11
+ function select(idx: number) {
12
+ const next = items[(idx + items.length) % items.length];
13
+ if (!next) return;
14
+ active = next.id;
15
+ dispatch('change', next.id);
16
+ buttons[(idx + items.length) % items.length]?.focus();
17
+ }
18
+
19
+ function onKey(e: KeyboardEvent, i: number) {
20
+ if (e.key === 'ArrowRight') { e.preventDefault(); select(i + 1); }
21
+ else if (e.key === 'ArrowLeft') { e.preventDefault(); select(i - 1); }
22
+ else if (e.key === 'Home') { e.preventDefault(); select(0); }
23
+ else if (e.key === 'End') { e.preventDefault(); select(items.length - 1); }
24
+ }
25
+ </script>
26
+
27
+ <div class="tabs" role="tablist" aria-label={ariaLabel}>
28
+ {#each items as t, i (t.id)}
29
+ <button
30
+ bind:this={buttons[i]}
31
+ type="button"
32
+ class="tab {t.id === active ? 'active' : ''}"
33
+ role="tab"
34
+ id="tab-{t.id}"
35
+ aria-selected={t.id === active}
36
+ aria-controls="tabpanel-{t.id}"
37
+ tabindex={t.id === active ? 0 : -1}
38
+ on:click={() => { active = t.id; dispatch('change', t.id); }}
39
+ on:keydown={(e) => onKey(e, i)}
40
+ >
41
+ {t.label}
42
+ {#if t.badge !== undefined}<span class="badge">{t.badge}</span>{/if}
43
+ </button>
44
+ {/each}
45
+ </div>
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ export let label: string;
3
+ let className = '';
4
+ export { className as class };
5
+ </script>
6
+
7
+ <span class="tag {className}">
8
+ <span class="tag__label">{label}</span>
9
+ <button
10
+ type="button"
11
+ class="tag__remove"
12
+ aria-label="Remove {label} filter"
13
+ on:click
14
+ >
15
+ <svg viewBox="0 0 10 10" width="10" height="10" fill="none" aria-hidden="true" focusable="false">
16
+ <path d="M2 2l6 6M8 2l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
17
+ </svg>
18
+ </button>
19
+ </span>
@@ -0,0 +1,92 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { createEventDispatcher } from 'svelte';
7
+
8
+ export let label: string | undefined = undefined;
9
+ export let value: string[] = [];
10
+ export let placeholder: string = 'Type and press Enter…';
11
+ export let disabled: boolean = false;
12
+ let klass = '';
13
+ export { klass as class };
14
+
15
+ const dispatch = createEventDispatcher<{ change: string[] }>();
16
+ const uid = `dash-ui-tag-input-${++counter}`;
17
+
18
+ let draft = '';
19
+ let inputEl: HTMLInputElement;
20
+
21
+ function addTag(raw: string) {
22
+ const trimmed = raw.trim();
23
+ if (!trimmed || value.includes(trimmed)) return;
24
+ value = [...value, trimmed];
25
+ draft = '';
26
+ dispatch('change', value);
27
+ }
28
+
29
+ function removeTag(index: number) {
30
+ value = value.filter((_, i) => i !== index);
31
+ dispatch('change', value);
32
+ }
33
+
34
+ function handleKeyDown(e: KeyboardEvent) {
35
+ if (e.key === 'Enter' || e.key === ',' || e.key === 'Tab') {
36
+ if (draft.trim()) {
37
+ e.preventDefault();
38
+ addTag(draft);
39
+ }
40
+ } else if (e.key === 'Backspace' && draft === '' && value.length > 0) {
41
+ removeTag(value.length - 1);
42
+ }
43
+ }
44
+
45
+ function handleBlur() {
46
+ if (draft.trim()) addTag(draft);
47
+ }
48
+ </script>
49
+
50
+ <div class="tag-input-wrapper {klass}">
51
+ {#if label}
52
+ <label for={uid} class="tag-input__label">{label}</label>
53
+ {/if}
54
+ <!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
55
+ <div
56
+ role="group"
57
+ aria-label={label ?? 'Tags'}
58
+ class="tag-input{disabled ? ' tag-input--disabled' : ''}"
59
+ on:click={() => inputEl?.focus()}
60
+ >
61
+ {#each value as tag, i}
62
+ <span class="tag">
63
+ <span class="tag__label">{tag}</span>
64
+ {#if !disabled}
65
+ <button
66
+ type="button"
67
+ class="tag__remove"
68
+ aria-label="Remove {tag}"
69
+ on:click|stopPropagation={() => removeTag(i)}
70
+ >
71
+ <svg viewBox="0 0 10 10" width="10" height="10" fill="none" aria-hidden="true" focusable="false">
72
+ <path d="M2 2l6 6M8 2l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
73
+ </svg>
74
+ </button>
75
+ {/if}
76
+ </span>
77
+ {/each}
78
+ <input
79
+ bind:this={inputEl}
80
+ id={uid}
81
+ type="text"
82
+ class="tag-input__field"
83
+ bind:value={draft}
84
+ {disabled}
85
+ placeholder={value.length === 0 ? placeholder : ''}
86
+ aria-label={label ?? 'Add tag'}
87
+ autocomplete="off"
88
+ on:keydown={handleKeyDown}
89
+ on:blur={handleBlur}
90
+ />
91
+ </div>
92
+ </div>
@@ -0,0 +1,31 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ export let value: string = '';
7
+ export let rows: number = 4;
8
+ export let placeholder: string | undefined = undefined;
9
+ export let disabled: boolean = false;
10
+ export let readonly: boolean = false;
11
+ export let id: string | undefined = undefined;
12
+ let className = '';
13
+ export { className as class };
14
+
15
+ const uid = `dash-ui-ta-${++counter}`;
16
+ $: inputId = id ?? uid;
17
+ </script>
18
+
19
+ <textarea
20
+ id={inputId}
21
+ class="input textarea {className}"
22
+ {rows}
23
+ {placeholder}
24
+ {disabled}
25
+ readonly={readonly}
26
+ bind:value
27
+ on:input
28
+ on:change
29
+ on:focus
30
+ on:blur
31
+ ></textarea>
@@ -0,0 +1,72 @@
1
+ <script context="module">
2
+ let _n = 0;
3
+ </script>
4
+ <script lang="ts">
5
+ const uid = `tac${_n++}`;
6
+
7
+ export let values: number[];
8
+ export let labels: string[] = [];
9
+ export let threshold: number;
10
+ export let thresholdLabel: string | undefined = undefined;
11
+ export let belowColor: string = '#00C875';
12
+ export let aboveColor: string = '#FF7B7B';
13
+ export let height: number = 160;
14
+ export let ariaLabel: string = 'Threshold area chart';
15
+
16
+ const VW = 400;
17
+ const PAD = { t: 16, r: 8, b: 28, l: 8 };
18
+
19
+ $: n = values.length;
20
+ $: chartW = VW - PAD.l - PAD.r;
21
+ $: chartH = height - PAD.t - PAD.b;
22
+ $: maxV = Math.max(...values, threshold, 1);
23
+ $: toX = (i: number) => PAD.l + (n > 1 ? (i / (n - 1)) * chartW : chartW / 2);
24
+ $: toY = (v: number) => PAD.t + chartH - (v / maxV) * chartH;
25
+ $: threshY = toY(threshold);
26
+ $: baseY = PAD.t + chartH;
27
+ $: pts = values.map((v, i) => `${toX(i).toFixed(1)},${toY(v).toFixed(1)}`);
28
+ $: d = `M ${pts.join(' L ')} L ${toX(n - 1).toFixed(1)},${baseY} L ${toX(0).toFixed(1)},${baseY} Z`;
29
+ $: tLabel = thresholdLabel ?? String(threshold);
30
+ $: gridLines = [0.25, 0.5, 0.75, 1].map((f) => PAD.t + (1 - f) * chartH);
31
+ </script>
32
+
33
+ <div role="img" aria-label={ariaLabel} style="width:100%">
34
+ <svg
35
+ viewBox="0 0 {VW} {height}"
36
+ style="width:100%;height:auto;display:block"
37
+ aria-hidden="true"
38
+ focusable="false"
39
+ >
40
+ <defs>
41
+ <clipPath id="{uid}b">
42
+ <rect x="0" y={threshY} width={VW} height={height - threshY} />
43
+ </clipPath>
44
+ <clipPath id="{uid}a">
45
+ <rect x="0" y="0" width={VW} height={threshY} />
46
+ </clipPath>
47
+ </defs>
48
+ {#each gridLines as y, i (i)}
49
+ <line x1={PAD.l} y1={y} x2={VW - PAD.r} y2={y} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
50
+ {/each}
51
+ <path {d} fill={belowColor} fill-opacity="0.7" clip-path="url(#{uid}b)" />
52
+ <path {d} fill={aboveColor} fill-opacity="0.7" clip-path="url(#{uid}a)" />
53
+ <line
54
+ x1={PAD.l} y1={threshY}
55
+ x2={VW - PAD.r} y2={threshY}
56
+ stroke={aboveColor}
57
+ stroke-width="1"
58
+ stroke-dasharray="4 3"
59
+ opacity="0.8"
60
+ />
61
+ <text
62
+ x={VW - PAD.r - 2} y={threshY - 4}
63
+ fill={aboveColor} font-size="9" text-anchor="end" font-family="inherit" opacity="0.9"
64
+ >{tLabel}</text>
65
+ {#each labels as lbl, i (i)}
66
+ <text
67
+ x={toX(i)} y={height - 4}
68
+ text-anchor="middle" fill="#6E7079" font-size="9" font-family="inherit"
69
+ >{lbl}</text>
70
+ {/each}
71
+ </svg>
72
+ </div>
@@ -0,0 +1,116 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { createEventDispatcher } from 'svelte';
7
+
8
+ export let label: string | undefined = undefined;
9
+ export let value: string = '00:00';
10
+ export let disabled: boolean = false;
11
+ let className = '';
12
+ export { className as class };
13
+
14
+ const dispatch = createEventDispatcher<{ change: string }>();
15
+ const uid = `dash-ui-tp-${++counter}`;
16
+
17
+ function pad(n: number): string {
18
+ return String(n).padStart(2, '0');
19
+ }
20
+
21
+ function parseTime(t: string): [number, number] {
22
+ const [h = '0', m = '0'] = t.split(':');
23
+ return [
24
+ Math.min(23, Math.max(0, parseInt(h, 10) || 0)),
25
+ Math.min(59, Math.max(0, parseInt(m, 10) || 0)),
26
+ ];
27
+ }
28
+
29
+ $: [hours, minutes] = parseTime(value);
30
+
31
+ let inputs: [HTMLInputElement | null, HTMLInputElement | null] = [null, null];
32
+
33
+ function commit(h: number, m: number) {
34
+ value = `${pad(h)}:${pad(m)}`;
35
+ dispatch('change', value);
36
+ }
37
+
38
+ function handleKey(field: 0 | 1, e: KeyboardEvent) {
39
+ const val = field === 0 ? hours : minutes;
40
+ const max = field === 0 ? 23 : 59;
41
+ if (e.key === 'ArrowUp') {
42
+ e.preventDefault();
43
+ const next = val >= max ? 0 : val + 1;
44
+ field === 0 ? commit(next, minutes) : commit(hours, next);
45
+ } else if (e.key === 'ArrowDown') {
46
+ e.preventDefault();
47
+ const next = val <= 0 ? max : val - 1;
48
+ field === 0 ? commit(next, minutes) : commit(hours, next);
49
+ } else if ((e.key === ':' || e.key === 'ArrowRight') && field === 0) {
50
+ e.preventDefault();
51
+ inputs[1]?.focus();
52
+ } else if (e.key === 'ArrowLeft' && field === 1) {
53
+ e.preventDefault();
54
+ inputs[0]?.focus();
55
+ }
56
+ }
57
+
58
+ function handleInput(field: 0 | 1, e: Event) {
59
+ const raw = (e.target as HTMLInputElement).value;
60
+ if (!/^\d{0,2}$/.test(raw)) return;
61
+ const n = parseInt(raw, 10);
62
+ if (isNaN(n)) return;
63
+ const max = field === 0 ? 23 : 59;
64
+ const clamped = Math.min(max, n);
65
+ field === 0 ? commit(clamped, minutes) : commit(hours, clamped);
66
+ if (raw.length === 2 && field === 0) setTimeout(() => inputs[1]?.focus(), 0);
67
+ }
68
+ </script>
69
+
70
+ <div class="time-picker-wrapper {className}">
71
+ {#if label}
72
+ <label for="{uid}-h" class="time-picker__label">{label}</label>
73
+ {/if}
74
+ <div class="time-picker{disabled ? ' time-picker--disabled' : ''}" role="group" aria-label={label ?? 'Time'}>
75
+ <input
76
+ bind:this={inputs[0]}
77
+ id="{uid}-h"
78
+ type="text"
79
+ inputmode="numeric"
80
+ pattern="[0-9]*"
81
+ role="spinbutton"
82
+ aria-label="Hours"
83
+ aria-valuenow={hours}
84
+ aria-valuemin={0}
85
+ aria-valuemax={23}
86
+ aria-valuetext={pad(hours)}
87
+ value={pad(hours)}
88
+ {disabled}
89
+ maxlength={2}
90
+ class="time-picker__field"
91
+ on:input={(e) => handleInput(0, e)}
92
+ on:keydown={(e) => handleKey(0, e)}
93
+ on:focus={(e) => e.currentTarget.select()}
94
+ />
95
+ <span class="time-picker__sep" aria-hidden="true">:</span>
96
+ <input
97
+ bind:this={inputs[1]}
98
+ type="text"
99
+ inputmode="numeric"
100
+ pattern="[0-9]*"
101
+ role="spinbutton"
102
+ aria-label="Minutes"
103
+ aria-valuenow={minutes}
104
+ aria-valuemin={0}
105
+ aria-valuemax={59}
106
+ aria-valuetext={pad(minutes)}
107
+ value={pad(minutes)}
108
+ {disabled}
109
+ maxlength={2}
110
+ class="time-picker__field"
111
+ on:input={(e) => handleInput(1, e)}
112
+ on:keydown={(e) => handleKey(1, e)}
113
+ on:focus={(e) => e.currentTarget.select()}
114
+ />
115
+ </div>
116
+ </div>
@@ -0,0 +1,104 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ export type TimeRangeId = '1h' | '6h' | '24h' | '7d' | '30d';
4
+ const PRESETS: { id: TimeRangeId; label: string; short: string }[] = [
5
+ { id: '1h', label: 'Last 1 hour', short: 'Last 1 h' },
6
+ { id: '6h', label: 'Last 6 hours', short: 'Last 6 h' },
7
+ { id: '24h', label: 'Last 24 hours', short: 'Last 24 h' },
8
+ { id: '7d', label: 'Last 7 days', short: 'Last 7 d' },
9
+ { id: '30d', label: 'Last 30 days', short: 'Last 30 d' },
10
+ ];
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import { createEventDispatcher, onMount, onDestroy } from 'svelte';
15
+
16
+ export let value: TimeRangeId = '1h';
17
+
18
+ const dispatch = createEventDispatcher<{ change: TimeRangeId }>();
19
+ const uid = `dash-ui-tr-${++counter}`;
20
+ const listId = `${uid}-list`;
21
+
22
+ let open = false;
23
+ let activeIdx = 0;
24
+ let triggerEl: HTMLButtonElement;
25
+ let wrapperEl: HTMLDivElement;
26
+
27
+ $: selected = PRESETS.find((p) => p.id === value) ?? PRESETS[0];
28
+
29
+ function toggle() {
30
+ if (!open) activeIdx = PRESETS.findIndex((p) => p.id === value);
31
+ open = !open;
32
+ }
33
+
34
+ function select(id: TimeRangeId) {
35
+ dispatch('change', id);
36
+ open = false;
37
+ triggerEl?.focus();
38
+ }
39
+
40
+ function onKeyDown(e: KeyboardEvent) {
41
+ if (!open) {
42
+ if (['ArrowDown', 'Enter', ' '].includes(e.key)) {
43
+ e.preventDefault();
44
+ activeIdx = PRESETS.findIndex((p) => p.id === value);
45
+ open = true;
46
+ }
47
+ return;
48
+ }
49
+ if (e.key === 'Escape') { e.preventDefault(); open = false; triggerEl?.focus(); }
50
+ else if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = Math.min(activeIdx + 1, PRESETS.length - 1); }
51
+ else if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = Math.max(activeIdx - 1, 0); }
52
+ else if (e.key === 'Home') { e.preventDefault(); activeIdx = 0; }
53
+ else if (e.key === 'End') { e.preventDefault(); activeIdx = PRESETS.length - 1; }
54
+ else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); select(PRESETS[activeIdx].id); }
55
+ else if (e.key === 'Tab') { open = false; }
56
+ }
57
+
58
+ function handleOutside(e: MouseEvent) {
59
+ if (open && !wrapperEl?.contains(e.target as Node)) open = false;
60
+ }
61
+
62
+ onMount(() => document.addEventListener('mousedown', handleOutside));
63
+ onDestroy(() => document.removeEventListener('mousedown', handleOutside));
64
+ </script>
65
+
66
+ <div class="time-range-root" bind:this={wrapperEl}>
67
+ <button
68
+ bind:this={triggerEl}
69
+ type="button"
70
+ class="btn time-range-trigger"
71
+ aria-haspopup="listbox"
72
+ aria-expanded={open}
73
+ aria-controls={open ? listId : undefined}
74
+ on:click={toggle}
75
+ on:keydown={onKeyDown}
76
+ >
77
+ {selected.short}
78
+ <svg class="time-range-chevron" width="10" height="6" viewBox="0 0 10 6" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
79
+ <path d="M1 1l4 4 4-4" />
80
+ </svg>
81
+ </button>
82
+ {#if open}
83
+ <ul id={listId} role="listbox" aria-label="Time range" class="time-range-list">
84
+ {#each PRESETS as p, idx (p.id)}
85
+ <li
86
+ role="option"
87
+ aria-selected={p.id === value}
88
+ tabindex="-1"
89
+ data-active={idx === activeIdx ? 'true' : undefined}
90
+ class="time-range-option"
91
+ on:mouseenter={() => { activeIdx = idx; }}
92
+ on:mousedown|preventDefault={() => select(p.id)}
93
+ >
94
+ {p.label}
95
+ {#if p.id === value}
96
+ <svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
97
+ <path d="M2 6l3 3 5-5" />
98
+ </svg>
99
+ {/if}
100
+ </li>
101
+ {/each}
102
+ </ul>
103
+ {/if}
104
+ </div>
@@ -0,0 +1,32 @@
1
+ <script context="module" lang="ts">
2
+ export interface TimelineEvent {
3
+ id: string;
4
+ title: string;
5
+ description?: string;
6
+ time: string;
7
+ variant?: 'info' | 'warn' | 'danger' | 'success' | 'neutral';
8
+ }
9
+ </script>
10
+
11
+ <script lang="ts">
12
+ export let events: TimelineEvent[];
13
+ let className = '';
14
+ export { className as class };
15
+ </script>
16
+
17
+ <ol class="timeline {className}" aria-label="Event timeline">
18
+ {#each events as e (e.id)}
19
+ <li class="timeline__item timeline__item--{e.variant ?? 'neutral'}">
20
+ <span class="timeline__dot" aria-hidden="true"></span>
21
+ <div class="timeline__content">
22
+ <div class="timeline__header">
23
+ <span class="timeline__title">{e.title}</span>
24
+ <time class="timeline__time">{e.time}</time>
25
+ </div>
26
+ {#if e.description}
27
+ <p class="timeline__desc">{e.description}</p>
28
+ {/if}
29
+ </div>
30
+ </li>
31
+ {/each}
32
+ </ol>