@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,102 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ export interface SplitButtonItem { id: string; label: string; disabled?: boolean; }
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import { createEventDispatcher, onMount, onDestroy } from 'svelte';
8
+
9
+ export let label: string;
10
+ export let variant: 'primary' | 'ghost' | 'danger' = 'ghost';
11
+ export let disabled: boolean = false;
12
+ export let items: SplitButtonItem[] = [];
13
+
14
+ const dispatch = createEventDispatcher<{ primary: void; action: string }>();
15
+ const uid = `dash-ui-spbtn-${++counter}`;
16
+ const menuId = `${uid}-menu`;
17
+
18
+ let open = false;
19
+ let activeIdx = 0;
20
+ let caretEl: HTMLButtonElement;
21
+ let wrapperEl: HTMLDivElement;
22
+
23
+ function toggleMenu() {
24
+ if (!open) activeIdx = 0;
25
+ open = !open;
26
+ }
27
+
28
+ function activate(id: string) {
29
+ dispatch('action', id);
30
+ open = false;
31
+ caretEl?.focus();
32
+ }
33
+
34
+ function onKeyDown(e: KeyboardEvent) {
35
+ if (!open) {
36
+ if (['ArrowDown', 'Enter', ' '].includes(e.key)) {
37
+ e.preventDefault();
38
+ activeIdx = 0;
39
+ open = true;
40
+ }
41
+ return;
42
+ }
43
+ if (e.key === 'Escape') { e.preventDefault(); open = false; caretEl?.focus(); }
44
+ else if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = Math.min(activeIdx + 1, items.length - 1); }
45
+ else if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = Math.max(activeIdx - 1, 0); }
46
+ else if (e.key === 'Home') { e.preventDefault(); activeIdx = 0; }
47
+ else if (e.key === 'End') { e.preventDefault(); activeIdx = items.length - 1; }
48
+ else if (e.key === 'Enter' || e.key === ' ') {
49
+ e.preventDefault();
50
+ const item = items[activeIdx];
51
+ if (item && !item.disabled) activate(item.id);
52
+ }
53
+ else if (e.key === 'Tab') { open = false; }
54
+ }
55
+
56
+ function handleOutside(e: MouseEvent) {
57
+ if (open && !wrapperEl?.contains(e.target as Node)) open = false;
58
+ }
59
+
60
+ onMount(() => document.addEventListener('mousedown', handleOutside));
61
+ onDestroy(() => document.removeEventListener('mousedown', handleOutside));
62
+ </script>
63
+
64
+ <div class="split-btn" bind:this={wrapperEl}>
65
+ <button
66
+ type="button"
67
+ class="btn btn-{variant} split-btn-primary"
68
+ {disabled}
69
+ on:click={() => dispatch('primary')}
70
+ >{label}</button>
71
+ <button
72
+ bind:this={caretEl}
73
+ type="button"
74
+ class="btn btn-{variant} split-btn-caret"
75
+ {disabled}
76
+ aria-label="{label} options"
77
+ aria-haspopup="menu"
78
+ aria-expanded={open}
79
+ aria-controls={open ? menuId : undefined}
80
+ on:click={toggleMenu}
81
+ on:keydown={onKeyDown}
82
+ >
83
+ <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
84
+ <path d="M1 3 L5 7 L9 3" />
85
+ </svg>
86
+ </button>
87
+ {#if open}
88
+ <ul id={menuId} role="menu" aria-label="{label} options" class="action-menu">
89
+ {#each items as item, idx (item.id)}
90
+ <li
91
+ role="menuitem"
92
+ tabindex="-1"
93
+ aria-disabled={item.disabled}
94
+ data-active={idx === activeIdx ? 'true' : undefined}
95
+ class="action-menu-item"
96
+ on:mouseenter={() => { activeIdx = idx; }}
97
+ on:mousedown|preventDefault={() => { if (!item.disabled) activate(item.id); }}
98
+ >{item.label}</li>
99
+ {/each}
100
+ </ul>
101
+ {/if}
102
+ </div>
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ export let maxHeight: number = 80;
5
+ export let showLabel: string = 'Show more';
6
+ export let hideLabel: string = 'Show less';
7
+ let klass: string = '';
8
+ export { klass as class };
9
+
10
+ let expanded = false;
11
+ let clipped = false;
12
+ let bodyEl: HTMLDivElement;
13
+
14
+ onMount(() => {
15
+ if (bodyEl) clipped = bodyEl.scrollHeight > maxHeight;
16
+ });
17
+ </script>
18
+
19
+ <div class="spoiler {klass}">
20
+ <div class="spoiler-outer" style="position:relative">
21
+ <div
22
+ bind:this={bodyEl}
23
+ style={!expanded && clipped ? `max-height:${maxHeight}px;overflow:hidden` : undefined}
24
+ >
25
+ <slot />
26
+ </div>
27
+ {#if !expanded && clipped}
28
+ <div class="spoiler-fade" aria-hidden="true" />
29
+ {/if}
30
+ </div>
31
+ {#if clipped}
32
+ <button
33
+ type="button"
34
+ class="spoiler-toggle"
35
+ aria-expanded={expanded}
36
+ on:click={() => (expanded = !expanded)}
37
+ >
38
+ {expanded ? hideLabel : showLabel}
39
+ </button>
40
+ {/if}
41
+ </div>
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ export interface StackedBarSeries {
3
+ label: string;
4
+ color: string;
5
+ values: number[];
6
+ }
7
+
8
+ export let series: StackedBarSeries[] = [];
9
+ export let labels: string[] = [];
10
+ export let height = 180;
11
+ export let normalized = false;
12
+ export let ariaLabel = 'Stacked bar chart';
13
+
14
+ const VW = 400;
15
+ const PAD = { t: 10, r: 8, b: 28, l: 36 };
16
+
17
+ $: n = series.length && series[0].values.length ? series[0].values.length : 0;
18
+ $: chartW = VW - PAD.l - PAD.r;
19
+ $: chartH = height - PAD.t - PAD.b;
20
+ $: bot = PAD.t + chartH;
21
+ $: barW = n > 0 ? (chartW / n) * 0.72 : 0;
22
+ $: gap = n > 0 ? (chartW / n) * 0.28 : 0;
23
+
24
+ $: totals = Array.from({ length: n }, (_, i) =>
25
+ series.reduce((s, ser) => s + (ser.values[i] ?? 0), 0)
26
+ );
27
+
28
+ $: maxTotal = normalized ? 1 : (totals.length ? Math.max(...totals) : 1) || 1;
29
+
30
+ $: stacks = Array.from({ length: n }, (_, gi) => {
31
+ let y = bot;
32
+ const total = totals[gi] || 1;
33
+ return series.map((ser) => {
34
+ const raw = ser.values[gi] ?? 0;
35
+ const v = normalized ? raw / total : raw;
36
+ const bh = (v / maxTotal) * chartH;
37
+ const rect = {
38
+ x: PAD.l + gi * (barW + gap) + gap / 2,
39
+ y: y - bh,
40
+ w: barW,
41
+ h: bh,
42
+ color: ser.color,
43
+ key: `${gi}-${ser.label}`,
44
+ };
45
+ y -= bh;
46
+ return rect;
47
+ });
48
+ });
49
+
50
+ $: gridFracs = [0.25, 0.5, 0.75, 1];
51
+ $: gridLines = gridFracs.map((f) => ({
52
+ y: PAD.t + (1 - f) * chartH,
53
+ label: normalized ? `${Math.round(f * 100)}%` : String(Math.round(f * maxTotal)),
54
+ }));
55
+
56
+ $: xLabels = Array.from({ length: n }, (_, i) => ({
57
+ x: PAD.l + i * (barW + gap) + gap / 2 + barW / 2,
58
+ text: labels[i] ?? String(i),
59
+ }));
60
+ </script>
61
+
62
+ {#if n > 0}
63
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
64
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
65
+ {#each gridLines as g, i (i)}
66
+ <line x1={PAD.l} y1={g.y} x2={VW - PAD.r} y2={g.y} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
67
+ <text x={PAD.l - 4} y={g.y + 3} fill="#6E7079" font-size="8" text-anchor="end" font-family="inherit">{g.label}</text>
68
+ {/each}
69
+
70
+ {#each stacks as rects, gi (gi)}
71
+ {#each rects as r (r.key)}
72
+ {#if r.h > 0}
73
+ <rect x={r.x} y={r.y} width={r.w} height={r.h} fill={r.color} fill-opacity="0.9" />
74
+ {/if}
75
+ {/each}
76
+ {/each}
77
+
78
+ {#each xLabels as l, i (i)}
79
+ <text x={l.x} y={height - 6} fill="#6E7079" font-size="8" text-anchor="middle" font-family="inherit">{l.text}</text>
80
+ {/each}
81
+ </svg>
82
+ </div>
83
+ {/if}
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ export interface StackedProgressSegment {
3
+ label: string;
4
+ value: number;
5
+ color: string;
6
+ }
7
+ export let segments: StackedProgressSegment[];
8
+ export let total: number | undefined = undefined;
9
+ export let ariaLabel: string | undefined = undefined;
10
+ export let showLegend: boolean = true;
11
+ $: sum = total ?? segments.reduce((a, s) => a + s.value, 0);
12
+ $: text = ariaLabel ?? segments.map((s) => `${s.label} ${Math.round((s.value / sum) * 100)}%`).join(', ');
13
+ </script>
14
+
15
+ <div class="sp" role="img" aria-label={text}>
16
+ <div class="sp-bar" aria-hidden="true">
17
+ {#each segments as { label, value, color }}
18
+ <div class="sp-seg" style="width:{((value / sum) * 100).toFixed(2)}%;background:{color}" />
19
+ {/each}
20
+ </div>
21
+ {#if showLegend}
22
+ <div class="sp-legend" aria-hidden="true">
23
+ {#each segments as { label, value, color }}
24
+ <span class="sp-item">
25
+ <span class="sp-dot" style="background:{color}" />
26
+ {label}
27
+ <span class="sp-pct">{Math.round((value / sum) * 100)}%</span>
28
+ </span>
29
+ {/each}
30
+ </div>
31
+ {/if}
32
+ </div>
@@ -0,0 +1,64 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ export let label: string;
7
+ export let value: number = 0;
8
+ export let max: number = 5;
9
+ export let readOnly: boolean = false;
10
+ export let size: 'sm' | 'md' = 'md';
11
+ export let onChange: ((v: number) => void) | undefined = undefined;
12
+
13
+ const gid = `dash-ui-sr-${++counter}`;
14
+ let hovered = 0;
15
+
16
+ function pick(star: number) {
17
+ value = star;
18
+ onChange?.(star);
19
+ }
20
+ </script>
21
+
22
+ {#if readOnly}
23
+ <span
24
+ class="star-rating{size === 'sm' ? ' star-rating--sm' : ''} star-rating--readonly"
25
+ role="img"
26
+ aria-label="{value} out of {max} stars"
27
+ >
28
+ {#each Array(max) as _, i}
29
+ <span class="star-icon{i < value ? ' star-icon--on' : ''}" aria-hidden="true">
30
+ <svg viewBox="0 0 24 24" fill={i < value ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="1.5" aria-hidden="true" focusable="false">
31
+ <polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
32
+ </svg>
33
+ </span>
34
+ {/each}
35
+ </span>
36
+ {:else}
37
+ <fieldset class="star-rating{size === 'sm' ? ' star-rating--sm' : ''}">
38
+ <legend class="sr-only">{label}</legend>
39
+ {#each Array(max) as _, i}
40
+ {@const star = i + 1}
41
+ {@const on = star <= (hovered || value)}
42
+ <label
43
+ for="{gid}-{star}"
44
+ class="star-label{on ? ' star-label--on' : ''}"
45
+ on:mouseenter={() => (hovered = star)}
46
+ on:mouseleave={() => (hovered = 0)}
47
+ >
48
+ <input
49
+ type="radio"
50
+ id="{gid}-{star}"
51
+ name={gid}
52
+ value={String(star)}
53
+ checked={value === star}
54
+ class="star-radio"
55
+ aria-label="{star} out of {max}"
56
+ on:change={() => pick(star)}
57
+ />
58
+ <svg viewBox="0 0 24 24" fill={on ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="1.5" aria-hidden="true" focusable="false">
59
+ <polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
60
+ </svg>
61
+ </label>
62
+ {/each}
63
+ </fieldset>
64
+ {/if}
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import Card from './Card.svelte';
3
+ export let label: string;
4
+ export let value: string = '';
5
+ export let unit: string | undefined = undefined;
6
+ export let sub: string | undefined = undefined;
7
+ export let delta: string | undefined = undefined;
8
+ export let deltaDir: 'up' | 'down' | 'neutral' = 'neutral';
9
+ export let span: number | undefined = undefined;
10
+ export let color: string | undefined = undefined;
11
+ $: deltaClass = deltaDir === 'up' ? 'delta-up' : deltaDir === 'down' ? 'delta-down' : '';
12
+ </script>
13
+
14
+ <Card {span}>
15
+ <h3>{label}</h3>
16
+ <div class="stat" style={color ? `color:${color};` : ''}>
17
+ <slot name="value">{value}</slot>{#if unit}<span class="unit">{unit}</span>{/if}
18
+ </div>
19
+ {#if sub || delta}
20
+ <div class="submeta">
21
+ {#if sub}{sub}{/if}{#if sub && delta} · {/if}{#if delta}<span class="sr-only">Trend: </span><span class={deltaClass}>{delta}</span>{/if}
22
+ </div>
23
+ {/if}
24
+ </Card>
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ export let color: string;
3
+ export let text: string;
4
+ export let textColor: string | undefined = undefined;
5
+ </script>
6
+
7
+ <span style="display:inline-flex;align-items:center;gap:6px;color:{textColor ?? color};font-size:12px;">
8
+ <span aria-hidden="true" style="width:6px;height:6px;border-radius:50%;background:{color};"></span>
9
+ {text}
10
+ </span>
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ export interface StepChartSeries {
3
+ label: string;
4
+ color: string;
5
+ values: number[];
6
+ }
7
+
8
+ export let series: StepChartSeries[] = [];
9
+ export let labels: string[] = [];
10
+ export let height: number = 160;
11
+ export let ariaLabel: string = 'Step 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
+ function stepPath(s: StepChartSeries): string {
32
+ if (!s.values.length) return '';
33
+ let d = `M ${toX(0).toFixed(1)},${toY(s.values[0]).toFixed(1)}`;
34
+ for (let i = 1; i < s.values.length; i++) {
35
+ d += ` H ${toX(i).toFixed(1)} V ${toY(s.values[i]).toFixed(1)}`;
36
+ }
37
+ return d;
38
+ }
39
+
40
+ function stepArea(s: StepChartSeries): string {
41
+ if (!s.values.length) return '';
42
+ const bot = (PAD.t + chartH).toFixed(1);
43
+ let d = `M ${toX(0).toFixed(1)},${bot} V ${toY(s.values[0]).toFixed(1)}`;
44
+ for (let i = 1; i < s.values.length; i++) {
45
+ d += ` H ${toX(i).toFixed(1)} V ${toY(s.values[i]).toFixed(1)}`;
46
+ }
47
+ d += ` H ${toX(s.values.length - 1).toFixed(1)} V ${bot} Z`;
48
+ return d;
49
+ }
50
+
51
+ $: gridLines = [0, 0.25, 0.5, 0.75, 1].map((f) => PAD.t + (1 - f) * chartH);
52
+ $: paths = series.map((s) => ({ color: s.color, label: s.label, d: stepPath(s) }));
53
+ $: areas = series.map((s) => ({ color: s.color, label: s.label, d: stepArea(s) }));
54
+ </script>
55
+
56
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
57
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
58
+ {#each gridLines as y}
59
+ <line x1={PAD.l} y1={y} x2={VW - PAD.r} y2={y} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
60
+ {/each}
61
+ {#each areas as s (s.label)}
62
+ <path d={s.d} fill={s.color} fill-opacity="0.1" />
63
+ {/each}
64
+ {#each paths as s (s.label)}
65
+ <path d={s.d} fill="none" stroke={s.color} stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />
66
+ {/each}
67
+ {#if labels.length > 0}
68
+ {#each labels as lbl, i}
69
+ {@const x = PAD.l + (labels.length > 1 ? (i / (labels.length - 1)) * chartW : chartW / 2)}
70
+ <text x={x} y={height - 4} text-anchor="middle" fill="#6E7079" font-size="9" font-family="inherit">{lbl}</text>
71
+ {/each}
72
+ {/if}
73
+ </svg>
74
+ </div>
@@ -0,0 +1,40 @@
1
+ <script context="module" lang="ts">
2
+ export interface StepperStep {
3
+ id: string;
4
+ label: string;
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ export let steps: StepperStep[];
10
+ export let active: string;
11
+ let className = '';
12
+ export { className as class };
13
+
14
+ $: activeIdx = steps.findIndex(s => s.id === active);
15
+ </script>
16
+
17
+ <nav aria-label="Progress" class="stepper {className}">
18
+ <ol class="stepper__list">
19
+ {#each steps as step, idx}
20
+ {@const isActive = step.id === active}
21
+ {@const isDone = idx < activeIdx}
22
+ {@const state = isActive ? 'active' : isDone ? 'done' : 'upcoming'}
23
+ <li class="stepper__item stepper__item--{state}">
24
+ <span class="stepper__num" aria-hidden="true">
25
+ {#if isDone}
26
+ <svg viewBox="0 0 12 12" width="12" height="12" fill="none">
27
+ <path d="M2 6l3 3 5-5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
28
+ </svg>
29
+ {:else}
30
+ {idx + 1}
31
+ {/if}
32
+ </span>
33
+ {#if isDone}<span class="sr-only">completed: </span>{/if}
34
+ <span class="stepper__label" aria-current={isActive ? 'step' : undefined}>
35
+ {step.label}
36
+ </span>
37
+ </li>
38
+ {/each}
39
+ </ol>
40
+ </nav>
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ export interface StreamSeries {
3
+ label: string;
4
+ values: number[];
5
+ color?: string;
6
+ }
7
+
8
+ export let labels: string[] = [];
9
+ export let series: StreamSeries[] = [];
10
+ export let ariaLabel: string = 'Stream graph';
11
+
12
+ const VW = 380;
13
+ const VH = 120;
14
+ const PAD_L = 8;
15
+ const PAD_R = 8;
16
+ const PAD_T = 8;
17
+ const PAD_B = 20;
18
+ const COLORS = ['#006FFF', '#00C8C8', '#F5A623', '#7FB6FF', '#A878F5', '#F56342'];
19
+
20
+ $: n = labels.length;
21
+ $: TRACK_W = VW - PAD_L - PAD_R;
22
+ $: TRACK_H = VH - PAD_T - PAD_B;
23
+
24
+ $: totals = Array.from({ length: n }, (_, i) =>
25
+ series.reduce((sum, s) => sum + (s.values[i] ?? 0), 0)
26
+ );
27
+ $: maxTotal = Math.max(...totals, 1);
28
+
29
+ function xOf(i: number): number {
30
+ return n > 1 ? PAD_L + (i / (n - 1)) * TRACK_W : PAD_L + TRACK_W / 2;
31
+ }
32
+
33
+ $: layers = series.map((s, si) => {
34
+ const tops: number[] = [];
35
+ const bots: number[] = [];
36
+ for (let i = 0; i < n; i++) {
37
+ const total = totals[i];
38
+ const baseline = (maxTotal - total) / 2;
39
+ let bottom = baseline;
40
+ for (let k = 0; k < si; k++) bottom += series[k].values[i] ?? 0;
41
+ const top = bottom + (s.values[i] ?? 0);
42
+ bots[i] = PAD_T + (bottom / maxTotal) * TRACK_H;
43
+ tops[i] = PAD_T + (top / maxTotal) * TRACK_H;
44
+ }
45
+ return { tops, bots, color: s.color ?? COLORS[si % COLORS.length] };
46
+ });
47
+
48
+ function buildArea(tops: number[], bots: number[]): string {
49
+ const fwd = tops.map((y, i) => {
50
+ const x = xOf(i);
51
+ if (i === 0) return `M${x},${y}`;
52
+ const px = xOf(i - 1);
53
+ const cpX = (px + x) / 2;
54
+ return `C${cpX},${tops[i - 1]} ${cpX},${y} ${x},${y}`;
55
+ });
56
+ const bwd = [...bots].reverse().map((y, ri) => {
57
+ const i = bots.length - 1 - ri;
58
+ const x = xOf(i);
59
+ if (ri === 0) return `L${x},${y}`;
60
+ const nx = xOf(i + 1);
61
+ const cpX = (nx + x) / 2;
62
+ return `C${cpX},${bots[i + 1]} ${cpX},${y} ${x},${y}`;
63
+ });
64
+ return [...fwd, ...bwd, 'Z'].join(' ');
65
+ }
66
+ </script>
67
+
68
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
69
+ <svg
70
+ viewBox="0 0 {VW} {VH}"
71
+ style="width:100%;height:auto;display:block;"
72
+ aria-hidden="true"
73
+ focusable="false"
74
+ >
75
+ {#each layers as layer, si (si)}
76
+ <path
77
+ d={buildArea(layer.tops, layer.bots)}
78
+ fill={layer.color}
79
+ opacity="0.82"
80
+ />
81
+ {/each}
82
+ {#each labels as label, i (i)}
83
+ <text
84
+ x={xOf(i)}
85
+ y={VH - 5}
86
+ fill="#6E7079"
87
+ font-size="9"
88
+ text-anchor={i === 0 ? 'start' : i === n - 1 ? 'end' : 'middle'}
89
+ font-family="inherit"
90
+ >{label}</text>
91
+ {/each}
92
+ </svg>
93
+ </div>
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ export let data: { label: string; value: number }[] = [];
3
+ export let height: number = 48;
4
+ export let colorLow: string = '#0A2840';
5
+ export let colorHigh: string = '#FF4040';
6
+ export let ariaLabel: string = 'Stripe chart';
7
+
8
+ function hexToRgb(hex: string): [number, number, number] {
9
+ const h = hex.replace('#', '');
10
+ const n = parseInt(h, 16);
11
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
12
+ }
13
+ function lerp(a: number, b: number, t: number) {
14
+ return Math.round(a + (b - a) * t);
15
+ }
16
+ function interpColor(low: string, high: string, t: number): string {
17
+ const [r1, g1, b1] = hexToRgb(low);
18
+ const [r2, g2, b2] = hexToRgb(high);
19
+ return `rgb(${lerp(r1, r2, t)},${lerp(g1, g2, t)},${lerp(b1, b2, t)})`;
20
+ }
21
+
22
+ $: vals = data.map((d) => d.value);
23
+ $: min = vals.length ? Math.min(...vals) : 0;
24
+ $: max = vals.length ? Math.max(...vals) : 1;
25
+ $: range = (max - min) || 1;
26
+ $: sw = 400 / (data.length || 1);
27
+ $: stripes = data.map((d, i) => ({
28
+ x: i * sw,
29
+ w: sw,
30
+ color: interpColor(colorLow, colorHigh, (d.value - min) / range),
31
+ label: d.label,
32
+ value: d.value,
33
+ }));
34
+ </script>
35
+
36
+ <div role="img" aria-label={ariaLabel} style="line-height:0;">
37
+ <svg
38
+ viewBox="0 0 400 {height}"
39
+ width="100%"
40
+ height={height}
41
+ aria-hidden="true"
42
+ focusable="false"
43
+ preserveAspectRatio="none"
44
+ >
45
+ {#each stripes as s, i (i)}
46
+ <g>
47
+ <rect x={s.x} y={0} width={s.w} height={height} fill={s.color} />
48
+ <title>{s.label}: {s.value}</title>
49
+ </g>
50
+ {/each}
51
+ </svg>
52
+ </div>