@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,141 @@
1
+ <script lang="ts">
2
+ export interface KanbanCard {
3
+ id: string;
4
+ title: string;
5
+ subtitle?: string;
6
+ meta?: string;
7
+ }
8
+
9
+ export interface KanbanColumn {
10
+ id: string;
11
+ title: string;
12
+ color?: string;
13
+ cards: KanbanCard[];
14
+ }
15
+
16
+ export let columns: KanbanColumn[];
17
+ export let onCardMove: ((cardId: string, fromColId: string, toColId: string) => void) | undefined = undefined;
18
+ export let ariaLabel: string = 'Kanban board';
19
+
20
+ let announce = '';
21
+ let grabbed: { cardId: string; colId: string } | null = null;
22
+ let dragSrc: { cardId: string; colId: string } | null = null;
23
+ let dragOverCol: string | null = null;
24
+ let cardRefs: Record<string, HTMLButtonElement> = {};
25
+
26
+ const announceId = `kanban-announce-${Math.random().toString(36).slice(2, 9)}`;
27
+
28
+ function say(msg: string) { announce = msg; }
29
+
30
+ function colOf(cardId: string): KanbanColumn | undefined {
31
+ return columns.find(c => c.cards.some(k => k.id === cardId));
32
+ }
33
+
34
+ function handleKeyDown(e: KeyboardEvent, card: KanbanCard, col: KanbanColumn) {
35
+ if (e.key === ' ' || e.key === 'Enter') {
36
+ e.preventDefault();
37
+ if (grabbed?.cardId === card.id) {
38
+ grabbed = null;
39
+ say(`Dropped ${card.title} in ${col.title}.`);
40
+ } else {
41
+ grabbed = { cardId: card.id, colId: col.id };
42
+ say(`Picked up ${card.title} from ${col.title}. Use left/right arrows to move between columns, Space to drop, Escape to cancel.`);
43
+ }
44
+ } else if (e.key === 'Escape' && grabbed?.cardId === card.id) {
45
+ e.preventDefault();
46
+ grabbed = null;
47
+ say('Move cancelled.');
48
+ } else if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && grabbed?.cardId === card.id) {
49
+ e.preventDefault();
50
+ const idx = columns.findIndex(c => c.id === grabbed!.colId);
51
+ const nextIdx = e.key === 'ArrowLeft' ? idx - 1 : idx + 1;
52
+ if (nextIdx < 0 || nextIdx >= columns.length) return;
53
+ const nextCol = columns[nextIdx];
54
+ onCardMove?.(card.id, grabbed!.colId, nextCol.id);
55
+ grabbed = { cardId: card.id, colId: nextCol.id };
56
+ say(`Moved ${card.title} to ${nextCol.title}.`);
57
+ setTimeout(() => { cardRefs[card.id]?.focus(); }, 0);
58
+ }
59
+ }
60
+
61
+ function handleDragStart(e: DragEvent, cardId: string, colId: string) {
62
+ if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move';
63
+ dragSrc = { cardId, colId };
64
+ }
65
+
66
+ function handleDragOver(e: DragEvent, colId: string) {
67
+ e.preventDefault();
68
+ if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
69
+ dragOverCol = colId;
70
+ }
71
+
72
+ function handleDrop(e: DragEvent, toColId: string) {
73
+ e.preventDefault();
74
+ if (!dragSrc || dragSrc.colId === toColId) { dragSrc = null; dragOverCol = null; return; }
75
+ const card = colOf(dragSrc.cardId)?.cards.find(c => c.id === dragSrc!.cardId);
76
+ const toCol = columns.find(c => c.id === toColId);
77
+ onCardMove?.(dragSrc.cardId, dragSrc.colId, toColId);
78
+ if (card && toCol) say(`Moved ${card.title} to ${toCol.title}.`);
79
+ dragSrc = null;
80
+ dragOverCol = null;
81
+ }
82
+ </script>
83
+
84
+ <div class="kanban" role="region" aria-label={ariaLabel}>
85
+ <span
86
+ id={announceId}
87
+ role="status"
88
+ aria-live="assertive"
89
+ aria-atomic="true"
90
+ class="sr-only"
91
+ >{announce}</span>
92
+
93
+ <div class="kanban-board" role="list">
94
+ {#each columns as col (col.id)}
95
+ <div
96
+ class="kanban-col"
97
+ class:kanban-col--over={dragOverCol === col.id}
98
+ role="listitem"
99
+ on:dragover={e => handleDragOver(e, col.id)}
100
+ on:dragleave={() => { dragOverCol = null; }}
101
+ on:drop={e => handleDrop(e, col.id)}
102
+ >
103
+ <div class="kanban-col-header">
104
+ {#if col.color}
105
+ <span class="kanban-col-dot" style="background:{col.color}" aria-hidden="true" />
106
+ {/if}
107
+ <span class="kanban-col-title">{col.title}</span>
108
+ <span class="kanban-col-count" aria-label="{col.cards.length} card{col.cards.length !== 1 ? 's' : ''}">
109
+ {col.cards.length}
110
+ </span>
111
+ </div>
112
+
113
+ <ul class="kanban-cards" aria-label="{col.title} cards">
114
+ {#each col.cards as card (card.id)}
115
+ {@const isGrabbed = grabbed?.cardId === card.id}
116
+ <li class="kanban-card-item" class:is-grabbed={isGrabbed}>
117
+ <button
118
+ bind:this={cardRefs[card.id]}
119
+ type="button"
120
+ class="kanban-card"
121
+ draggable={true}
122
+ aria-pressed={isGrabbed}
123
+ 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.'}"
124
+ on:keydown={e => handleKeyDown(e, card, col)}
125
+ on:dragstart={e => handleDragStart(e, card.id, col.id)}
126
+ on:dragend={() => { dragSrc = null; dragOverCol = null; }}
127
+ >
128
+ <span class="kanban-card-title">{card.title}</span>
129
+ {#if card.subtitle}<span class="kanban-card-sub">{card.subtitle}</span>{/if}
130
+ {#if card.meta}<span class="kanban-card-meta">{card.meta}</span>{/if}
131
+ </button>
132
+ </li>
133
+ {/each}
134
+ {#if col.cards.length === 0}
135
+ <li class="kanban-empty" role="note" aria-label="{col.title} is empty">Empty</li>
136
+ {/if}
137
+ </ul>
138
+ </div>
139
+ {/each}
140
+ </div>
141
+ </div>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ export let keys: string | string[];
3
+ let className = '';
4
+ export { className as class };
5
+
6
+ $: parts = typeof keys === 'string' ? keys.split('+') : keys;
7
+ </script>
8
+
9
+ <kbd class="kbd {className}">
10
+ {#each parts as key, i}
11
+ <span class="kbd__segment">
12
+ {#if i > 0}<span class="kbd__plus" aria-hidden="true">+</span>{/if}
13
+ <kbd class="kbd__key">{key}</kbd>
14
+ </span>
15
+ {/each}
16
+ </kbd>
@@ -0,0 +1,67 @@
1
+ <script lang="ts">
2
+ export interface LineChartSeries {
3
+ label: string;
4
+ color: string;
5
+ values: number[];
6
+ }
7
+
8
+ export let series: LineChartSeries[] = [];
9
+ export let labels: string[] = [];
10
+ export let height: number = 160;
11
+ export let ariaLabel: string = 'Line chart';
12
+
13
+ const VW = 400;
14
+ const PAD = { t: 12, r: 8, b: 28, l: 8 };
15
+
16
+ $: allValues = series.flatMap((s) => s.values);
17
+ $: minV = allValues.length ? Math.min(...allValues) : 0;
18
+ $: maxV = allValues.length ? Math.max(...allValues) : 1;
19
+ $: range = maxV - minV || 1;
20
+ $: chartW = VW - PAD.l - PAD.r;
21
+ $: chartH = height - PAD.t - PAD.b;
22
+ $: n = series[0]?.values.length ?? 0;
23
+
24
+ function toX(i: number): number {
25
+ return PAD.l + (n > 1 ? (i / (n - 1)) * chartW : chartW / 2);
26
+ }
27
+ function toY(v: number): number {
28
+ return PAD.t + chartH - ((v - minV) / range) * chartH;
29
+ }
30
+
31
+ $: gridLines = [0, 0.25, 0.5, 0.75, 1].map((f) => PAD.t + (1 - f) * chartH);
32
+
33
+ $: paths = series.map((s) => {
34
+ const pts = s.values.map((v, i) => `${toX(i).toFixed(1)},${toY(v).toFixed(1)}`);
35
+ return { color: s.color, label: s.label, d: `M ${pts.join(' L ')}` };
36
+ });
37
+
38
+ $: areas = series.map((s) => {
39
+ const pts = s.values.map((v, i) => `${toX(i).toFixed(1)},${toY(v).toFixed(1)}`);
40
+ const bot = (PAD.t + chartH).toFixed(1);
41
+ return {
42
+ color: s.color,
43
+ label: s.label,
44
+ d: `M ${toX(0).toFixed(1)},${bot} L ${pts.join(' L ')} L ${toX(n - 1).toFixed(1)},${bot} Z`,
45
+ };
46
+ });
47
+ </script>
48
+
49
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
50
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
51
+ {#each gridLines as y}
52
+ <line x1={PAD.l} y1={y} x2={VW - PAD.r} y2={y} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
53
+ {/each}
54
+ {#each areas as s (s.label)}
55
+ <path d={s.d} fill={s.color} fill-opacity="0.1" />
56
+ {/each}
57
+ {#each paths as s (s.label)}
58
+ <path d={s.d} fill="none" stroke={s.color} stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />
59
+ {/each}
60
+ {#if labels.length > 0}
61
+ {#each labels as lbl, i}
62
+ {@const x = PAD.l + (labels.length > 1 ? (i / (labels.length - 1)) * chartW : chartW / 2)}
63
+ <text x={x} y={height - 4} text-anchor="middle" fill="#6E7079" font-size="9">{lbl}</text>
64
+ {/each}
65
+ {/if}
66
+ </svg>
67
+ </div>
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ import { afterUpdate } from 'svelte';
3
+
4
+ export interface LogEntry {
5
+ id?: string;
6
+ timestamp: string;
7
+ level: 'info' | 'warn' | 'error' | 'debug';
8
+ category?: string;
9
+ event: string;
10
+ subject?: string;
11
+ detail?: string;
12
+ }
13
+
14
+ export let entries: LogEntry[] = [];
15
+ export let height: number = 360;
16
+ export let defaultFollow: boolean = true;
17
+ export let ariaLabel: string = 'Log entries';
18
+ export let className: string = '';
19
+
20
+ let follow = defaultFollow;
21
+ let bodyEl: HTMLDivElement;
22
+
23
+ const SEV_DOT: Record<string, string> = {
24
+ error: '#F03A3A',
25
+ warn: '#F5A623',
26
+ info: '#006FFF',
27
+ debug: '#4A4B53',
28
+ };
29
+
30
+ afterUpdate(() => {
31
+ if (follow && bodyEl) bodyEl.scrollTop = bodyEl.scrollHeight;
32
+ });
33
+ </script>
34
+
35
+ <div class="lv {className}">
36
+ <div class="lv__toolbar">
37
+ <span class="lv__count">{entries.length} entries</span>
38
+ <button
39
+ type="button"
40
+ class="lv__follow{follow ? ' lv__follow--on' : ''}"
41
+ on:click={() => { follow = !follow; }}
42
+ aria-pressed={follow}
43
+ >Follow</button>
44
+ </div>
45
+ <div
46
+ bind:this={bodyEl}
47
+ role="log"
48
+ aria-live="polite"
49
+ aria-label={ariaLabel}
50
+ class="lv__body"
51
+ style="height:{height}px;"
52
+ >
53
+ <table class="lv__table">
54
+ <caption class="sr-only">{ariaLabel}</caption>
55
+ <thead>
56
+ <tr>
57
+ <th scope="col">Severity</th>
58
+ <th scope="col">Source</th>
59
+ <th scope="col">Time</th>
60
+ <th scope="col">Event</th>
61
+ <th scope="col">Subject</th>
62
+ <th scope="col">Detail</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+ {#each entries as e, i (e.id ?? i)}
67
+ <tr>
68
+ <td>
69
+ <span class="lv__sev lv__sev--{e.level}">
70
+ <span class="lv__dot" style="background:{SEV_DOT[e.level] ?? SEV_DOT.info};" aria-hidden="true"></span>
71
+ {e.level === 'error' ? 'crit' : e.level}
72
+ </span>
73
+ </td>
74
+ <td class="lv__cat">{e.category ?? ''}</td>
75
+ <td class="lv__time">{e.timestamp}</td>
76
+ <td class="lv__event">{e.event}</td>
77
+ <td class="lv__subject">{e.subject ?? ''}</td>
78
+ <td class="lv__detail">{e.detail ?? ''}</td>
79
+ </tr>
80
+ {/each}
81
+ </tbody>
82
+ </table>
83
+ </div>
84
+ </div>
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ export interface LollipopItem {
3
+ label: string;
4
+ value: number;
5
+ color?: string;
6
+ }
7
+
8
+ export let items: LollipopItem[] = [];
9
+ export let unit: string = '';
10
+ export let color: string = '#006FFF';
11
+ export let ariaLabel: string = 'Lollipop chart';
12
+
13
+ const VW = 340;
14
+ const LABEL_W = 110;
15
+ const VAL_W = 36;
16
+ const PAD_R = 4;
17
+ const TRACK_W = VW - LABEL_W - VAL_W - PAD_R;
18
+ const DOT_R = 5;
19
+ const ROW_H = 26;
20
+ const PAD_T = 6;
21
+ const AXIS_H = 18;
22
+ const TICKS = 4;
23
+
24
+ $: maxVal = Math.max(...items.map((it) => it.value), 1);
25
+ $: svgH = PAD_T + items.length * ROW_H + AXIS_H;
26
+ $: ticks = Array.from({ length: TICKS + 1 }, (_, i) => Math.round((i / TICKS) * maxVal));
27
+
28
+ function tx(v: number): number {
29
+ return LABEL_W + (v / maxVal) * TRACK_W;
30
+ }
31
+ </script>
32
+
33
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
34
+ <svg
35
+ viewBox="0 0 {VW} {svgH}"
36
+ style="width:100%;height:auto;display:block;"
37
+ aria-hidden="true"
38
+ focusable="false"
39
+ >
40
+ {#each ticks as t, i (i)}
41
+ <g>
42
+ <line
43
+ x1={tx(t)} y1={PAD_T}
44
+ x2={tx(t)} y2={PAD_T + items.length * ROW_H}
45
+ stroke="rgba(255,255,255,0.06)" stroke-width="1"
46
+ />
47
+ <text
48
+ x={tx(t)} y={PAD_T + items.length * ROW_H + 13}
49
+ fill="#6E7079" font-size="9" text-anchor="middle" font-family="inherit"
50
+ >{t}{unit}</text>
51
+ </g>
52
+ {/each}
53
+
54
+ {#each items as item, i (item.label)}
55
+ {@const cy = PAD_T + i * ROW_H + ROW_H / 2}
56
+ {@const cx = tx(item.value)}
57
+ {@const c = item.color ?? color}
58
+ <g>
59
+ <text
60
+ x={LABEL_W - 6} y={cy + 4}
61
+ fill="#A4A7B5" font-size="10" text-anchor="end" font-family="inherit"
62
+ >{item.label}</text>
63
+
64
+ <line
65
+ x1={LABEL_W} y1={cy}
66
+ x2={cx} y2={cy}
67
+ stroke={c} stroke-width="1.5" opacity="0.5"
68
+ />
69
+
70
+ <circle cx={cx} cy={cy} r={DOT_R} fill={c} />
71
+
72
+ <text
73
+ x={cx + DOT_R + 4} y={cy + 4}
74
+ fill="#A4A7B5" font-size="9" font-family="inherit"
75
+ >{item.value}{unit}</text>
76
+ </g>
77
+ {/each}
78
+ </svg>
79
+ </div>
@@ -0,0 +1,98 @@
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:00:00: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-mac-${++counter}`;
16
+
17
+ const HEX_RE = /^[0-9a-fA-F]{0,2}$/;
18
+ const FULL_MAC_RE = /^([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}$/;
19
+
20
+ function parseMAC(mac: string): [string, string, string, string, string, string] {
21
+ const parts = mac.replace(/-/g, ':').split(':');
22
+ return [
23
+ parts[0] ?? '00', parts[1] ?? '00', parts[2] ?? '00',
24
+ parts[3] ?? '00', parts[4] ?? '00', parts[5] ?? '00',
25
+ ];
26
+ }
27
+
28
+ let pairs: [string, string, string, string, string, string] = parseMAC(value);
29
+ let inputs: (HTMLInputElement | null)[] = [null, null, null, null, null, null];
30
+
31
+ function focusAt(i: number) {
32
+ const el = inputs[i];
33
+ if (el) { el.focus(); el.select(); }
34
+ }
35
+
36
+ function commitPair(i: number, raw: string) {
37
+ if (!HEX_RE.test(raw)) return;
38
+ pairs[i] = raw.toUpperCase();
39
+ pairs = pairs as [string, string, string, string, string, string];
40
+ value = pairs.join(':');
41
+ dispatch('change', value);
42
+ if (raw.length === 2 && i < 5) setTimeout(() => focusAt(i + 1), 0);
43
+ }
44
+
45
+ function handleKey(i: number, e: KeyboardEvent) {
46
+ const el = inputs[i]!;
47
+ if (e.key === ':' || e.key === '-') {
48
+ e.preventDefault();
49
+ if (i < 5) focusAt(i + 1);
50
+ } else if (e.key === 'ArrowRight' && el.selectionStart === el.value.length) {
51
+ if (i < 5) { e.preventDefault(); focusAt(i + 1); }
52
+ } else if (e.key === 'ArrowLeft' && el.selectionStart === 0) {
53
+ if (i > 0) { e.preventDefault(); focusAt(i - 1); }
54
+ } else if (e.key === 'Backspace' && el.value === '' && i > 0) {
55
+ e.preventDefault(); focusAt(i - 1);
56
+ }
57
+ }
58
+
59
+ function handlePaste(e: ClipboardEvent) {
60
+ const text = e.clipboardData?.getData('text').trim() ?? '';
61
+ if (FULL_MAC_RE.test(text)) {
62
+ e.preventDefault();
63
+ const normalized = text.replace(/-/g, ':').replace(/:/g, '').match(/.{1,2}/g)?.join(':') ?? text;
64
+ pairs = normalized.split(':').map((p) => p.toUpperCase()) as [string, string, string, string, string, string];
65
+ value = pairs.join(':');
66
+ dispatch('change', value);
67
+ setTimeout(() => focusAt(5), 0);
68
+ }
69
+ }
70
+ </script>
71
+
72
+ <div class="mac-input-wrapper {className}">
73
+ {#if label}
74
+ <label for="{uid}-0" class="mac-input__label">{label}</label>
75
+ {/if}
76
+ <div class="mac-input{disabled ? ' mac-input--disabled' : ''}" role="group" aria-label={label ?? 'MAC address'}>
77
+ {#each pairs as pair, i}
78
+ <input
79
+ bind:this={inputs[i]}
80
+ id={i === 0 ? `${uid}-0` : undefined}
81
+ type="text"
82
+ inputmode="text"
83
+ aria-label="Byte {i + 1} of 6"
84
+ value={pair}
85
+ {disabled}
86
+ maxlength={2}
87
+ class="mac-input__pair"
88
+ on:input={(e) => commitPair(i, e.currentTarget.value)}
89
+ on:keydown={(e) => handleKey(i, e)}
90
+ on:paste={handlePaste}
91
+ on:focus={(e) => e.currentTarget.select()}
92
+ />
93
+ {#if i < 5}
94
+ <span class="mac-input__colon" aria-hidden="true">:</span>
95
+ {/if}
96
+ {/each}
97
+ </div>
98
+ </div>
@@ -0,0 +1,108 @@
1
+ <script lang="ts">
2
+ export interface MarimekkoSegment {
3
+ label: string;
4
+ value: number;
5
+ color?: string;
6
+ }
7
+
8
+ export interface MarimekkoColumn {
9
+ label: string;
10
+ segments: MarimekkoSegment[];
11
+ }
12
+
13
+ export let columns: MarimekkoColumn[];
14
+ export let height = 160;
15
+ export let ariaLabel = 'Marimekko chart';
16
+
17
+ const PALETTE = ['#006FFF', '#00C875', '#F5A623', '#A78BFA', '#FF7B7B', '#00C8C8', '#FB923C'];
18
+ const VW = 340;
19
+ const LABEL_H = 22;
20
+ const GAP = 2;
21
+
22
+ interface Cell { x: number; y: number; w: number; h: number; color: string; label: string; }
23
+ interface Header { x: number; w: number; label: string; }
24
+
25
+ $: layout = (() => {
26
+ const colTotals = columns.map(col => col.segments.reduce((s, seg) => s + seg.value, 0));
27
+ const grandTotal = colTotals.reduce((s, t) => s + t, 0);
28
+ if (grandTotal === 0) return { cells: [] as Cell[], headers: [] as Header[] };
29
+
30
+ const chartH = height - LABEL_H;
31
+ const cells: Cell[] = [];
32
+ const headers: Header[] = [];
33
+
34
+ let cx = 0;
35
+ columns.forEach((col, ci) => {
36
+ const colTotal = colTotals[ci];
37
+ const colW = (colTotal / grandTotal) * VW;
38
+ headers.push({ x: cx, w: colW, label: col.label });
39
+
40
+ let sy = LABEL_H;
41
+ col.segments.forEach((seg, si) => {
42
+ const segH = colTotal > 0 ? (seg.value / colTotal) * chartH : 0;
43
+ cells.push({
44
+ x: cx, y: sy, w: colW, h: segH,
45
+ color: seg.color ?? PALETTE[si % PALETTE.length],
46
+ label: seg.label,
47
+ });
48
+ sy += segH;
49
+ });
50
+ cx += colW;
51
+ });
52
+ return { cells, headers };
53
+ })();
54
+ </script>
55
+
56
+ {#if layout.cells.length > 0}
57
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
58
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
59
+ {#each layout.headers as h, i (i)}
60
+ {#if h.w > GAP * 2}
61
+ <text
62
+ x={h.x + h.w / 2}
63
+ y={LABEL_H - 6}
64
+ text-anchor="middle"
65
+ font-size={Math.min(10, h.w / 6)}
66
+ fill="#A4A7B5"
67
+ font-family="inherit"
68
+ >{h.label}</text>
69
+ {/if}
70
+ {/each}
71
+ {#each layout.cells as cell, i (i)}
72
+ {#if cell.w > GAP * 2 && cell.h > GAP * 2}
73
+ <g>
74
+ <rect
75
+ x={cell.x + GAP / 2}
76
+ y={cell.y + GAP / 2}
77
+ width={Math.max(0, cell.w - GAP)}
78
+ height={Math.max(0, cell.h - GAP)}
79
+ fill={cell.color}
80
+ fill-opacity={0.8}
81
+ rx={2}
82
+ />
83
+ {#if cell.w > 36 && cell.h > 14}
84
+ <text
85
+ x={cell.x + cell.w / 2}
86
+ y={cell.y + cell.h / 2 + 4}
87
+ text-anchor="middle"
88
+ font-size={Math.min(9, (cell.w - GAP) / 8, cell.h - 6)}
89
+ fill="#fff"
90
+ font-family="inherit"
91
+ >{cell.label}</text>
92
+ {/if}
93
+ </g>
94
+ {/if}
95
+ {/each}
96
+ {#each layout.headers.slice(0, -1) as h, i (i)}
97
+ <line
98
+ x1={h.x + h.w}
99
+ y1={LABEL_H}
100
+ x2={h.x + h.w}
101
+ y2={height}
102
+ stroke="rgba(255,255,255,0.12)"
103
+ stroke-width={1}
104
+ />
105
+ {/each}
106
+ </svg>
107
+ </div>
108
+ {/if}
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ export let rows: string[] = [];
3
+ export let cols: string[] = [];
4
+ export let values: number[][] = [];
5
+ export let unit = '';
6
+ export let ariaLabel = 'Matrix chart';
7
+
8
+ const LEFT = 80;
9
+ const TOP = 60;
10
+ const CELL = 38;
11
+
12
+ function cellColor(t: number): string {
13
+ const alpha = 0.07 + t * 0.88;
14
+ return `rgba(0,111,255,${alpha.toFixed(2)})`;
15
+ }
16
+
17
+ function fmt(n: number): string {
18
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
19
+ return String(n);
20
+ }
21
+
22
+ $: allVals = values.flat();
23
+ $: max = Math.max(...allVals, 1);
24
+ $: W = LEFT + cols.length * CELL;
25
+ $: H = TOP + rows.length * CELL;
26
+ </script>
27
+
28
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
29
+ <svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
30
+ {#each cols as col, ci}
31
+ {@const cx = LEFT + ci * CELL + CELL / 2}
32
+ <text x={cx} y={TOP - 8} fill="#A4A7B5" font-size="10" text-anchor="end" font-family="inherit" transform="rotate(-40, {cx}, {TOP - 8})">{col}</text>
33
+ {/each}
34
+
35
+ {#each rows as row, ri}
36
+ {@const cy = TOP + ri * CELL + CELL / 2}
37
+ <text x={LEFT - 8} y={cy + 4} fill="#A4A7B5" font-size="10" text-anchor="end" font-family="inherit">{row}</text>
38
+ {/each}
39
+
40
+ {#each rows as _, ri}
41
+ {#each cols as _, ci}
42
+ {@const v = values[ri]?.[ci] ?? 0}
43
+ {@const t = v / max}
44
+ {@const x = LEFT + ci * CELL}
45
+ {@const y = TOP + ri * CELL}
46
+ {@const cx = x + CELL / 2}
47
+ {@const cy = y + CELL / 2}
48
+ <rect x={x + 1} y={y + 1} width={CELL - 2} height={CELL - 2} rx="3" fill={cellColor(t)} />
49
+ {#if v > 0}
50
+ <text x={cx} y={cy + 4} fill={t > 0.5 ? '#fff' : '#A4A7B5'} font-size="9" text-anchor="middle" font-family="inherit">{fmt(v)}{unit}</text>
51
+ {/if}
52
+ {/each}
53
+ {/each}
54
+ </svg>
55
+ </div>