@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,110 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ export interface SelectOption { value: string; label: string; }
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import { createEventDispatcher, onMount, onDestroy } from 'svelte';
8
+
9
+ export let options: SelectOption[] = [];
10
+ export let value: string | undefined = undefined;
11
+ export let label: string | undefined = undefined;
12
+ export let placeholder: string = 'Select…';
13
+ export let id: string | undefined = undefined;
14
+ export let disabled: boolean = false;
15
+ let className = '';
16
+ export { className as class };
17
+
18
+ const uid = `dash-ui-sel-${++counter}`;
19
+ $: triggerId = id ?? uid;
20
+ $: listboxId = `${triggerId}-lb`;
21
+
22
+ const dispatch = createEventDispatcher<{ change: string }>();
23
+
24
+ let open = false;
25
+ let activeIdx = -1;
26
+ let triggerEl: HTMLButtonElement;
27
+ let wrapperEl: HTMLDivElement;
28
+
29
+ $: selected = options.find((o) => o.value === value);
30
+ $: displayLabel = selected?.label ?? placeholder;
31
+
32
+ function toggle() {
33
+ if (disabled) return;
34
+ if (!open) {
35
+ const idx = options.findIndex((o) => o.value === value);
36
+ activeIdx = idx >= 0 ? idx : 0;
37
+ }
38
+ open = !open;
39
+ }
40
+
41
+ function pick(val: string) {
42
+ dispatch('change', val);
43
+ open = false;
44
+ triggerEl?.focus();
45
+ }
46
+
47
+ function onKeyDown(e: KeyboardEvent) {
48
+ if (disabled) return;
49
+ if (e.key === 'Escape') { open = false; triggerEl?.focus(); return; }
50
+ if (!open) {
51
+ if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
52
+ e.preventDefault();
53
+ const idx = options.findIndex((o) => o.value === value);
54
+ activeIdx = idx >= 0 ? idx : 0;
55
+ open = true;
56
+ }
57
+ return;
58
+ }
59
+ if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = Math.min(activeIdx + 1, options.length - 1); }
60
+ else if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = Math.max(activeIdx - 1, 0); }
61
+ else if (e.key === 'Home') { e.preventDefault(); activeIdx = 0; }
62
+ else if (e.key === 'End') { e.preventDefault(); activeIdx = options.length - 1; }
63
+ else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (activeIdx >= 0) pick(options[activeIdx].value); }
64
+ else if (e.key === 'Tab') { open = false; }
65
+ }
66
+
67
+ function handleOutside(e: MouseEvent) {
68
+ if (open && !wrapperEl?.contains(e.target as Node)) open = false;
69
+ }
70
+
71
+ onMount(() => document.addEventListener('mousedown', handleOutside));
72
+ onDestroy(() => document.removeEventListener('mousedown', handleOutside));
73
+ </script>
74
+
75
+ <div class="select-wrapper {className}" bind:this={wrapperEl}>
76
+ {#if label}<label for={triggerId} class="sr-only">{label}</label>{/if}
77
+ <button
78
+ bind:this={triggerEl}
79
+ id={triggerId}
80
+ role="combobox"
81
+ aria-haspopup="listbox"
82
+ aria-expanded={open}
83
+ aria-controls={listboxId}
84
+ aria-label={label}
85
+ {disabled}
86
+ class="select-trigger"
87
+ on:click={toggle}
88
+ on:keydown={onKeyDown}
89
+ type="button"
90
+ >
91
+ <span>{displayLabel}</span>
92
+ <svg class="select-chevron" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
93
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
94
+ </svg>
95
+ </button>
96
+ {#if open}
97
+ <ul id={listboxId} role="listbox" aria-label={label} class="select-listbox">
98
+ {#each options as opt, idx (opt.value)}
99
+ <li
100
+ role="option"
101
+ aria-selected={opt.value === value}
102
+ data-active={idx === activeIdx ? 'true' : undefined}
103
+ class="select-option"
104
+ on:mousedown|preventDefault={() => pick(opt.value)}
105
+ on:mouseenter={() => { activeIdx = idx; }}
106
+ >{opt.label}</li>
107
+ {/each}
108
+ </ul>
109
+ {/if}
110
+ </div>
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import CloseIcon from '../icons/CloseIcon.svelte';
3
+
4
+ export interface SelectionToolbarAction {
5
+ label: string;
6
+ onClick: () => void;
7
+ variant?: 'default' | 'danger';
8
+ }
9
+
10
+ export let count: number;
11
+ export let actions: SelectionToolbarAction[] = [];
12
+ export let onClear: () => void;
13
+ export let ariaLabel: string = 'Selection actions';
14
+ </script>
15
+
16
+ {#if count > 0}
17
+ <div class="sel-toolbar" role="toolbar" aria-label={ariaLabel}>
18
+ <span class="sel-toolbar__count" aria-live="polite" aria-atomic="true">
19
+ <strong>{count}</strong> selected
20
+ </span>
21
+ <div class="sel-toolbar__actions">
22
+ {#each actions as action}
23
+ <button
24
+ type="button"
25
+ class="sel-toolbar__btn{action.variant === 'danger' ? ' sel-toolbar__btn--danger' : ''}"
26
+ on:click={action.onClick}
27
+ >
28
+ {action.label}
29
+ </button>
30
+ {/each}
31
+ <slot />
32
+ </div>
33
+ <button
34
+ type="button"
35
+ class="sel-toolbar__clear icon-btn"
36
+ on:click={onClear}
37
+ aria-label="Clear selection"
38
+ >
39
+ <CloseIcon aria-hidden="true" />
40
+ </button>
41
+ </div>
42
+ {/if}
@@ -0,0 +1,44 @@
1
+ <script lang="ts" context="module">
2
+ import type { ComponentType } from 'svelte';
3
+ export interface SidebarItemDef {
4
+ id: string;
5
+ label: string;
6
+ icon: ComponentType;
7
+ count?: number;
8
+ pill?: number;
9
+ }
10
+ export interface SidebarSectionDef {
11
+ title: string;
12
+ items: SidebarItemDef[];
13
+ }
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import { createEventDispatcher } from 'svelte';
18
+ export let sections: SidebarSectionDef[] = [];
19
+ export let activeId: string;
20
+ const dispatch = createEventDispatcher<{ change: string }>();
21
+ </script>
22
+
23
+ <nav class="sidebar" aria-label="Primary">
24
+ {#each sections as sec (sec.title)}
25
+ <h2 class="sb-section">{sec.title}</h2>
26
+ <ul class="sb-list">
27
+ {#each sec.items as it (it.id)}
28
+ <li>
29
+ <button
30
+ type="button"
31
+ class="sb-item {it.id === activeId ? 'active' : ''}"
32
+ aria-current={it.id === activeId ? 'page' : undefined}
33
+ on:click={() => { activeId = it.id; dispatch('change', it.id); }}
34
+ >
35
+ <span class="sb-ico"><svelte:component this={it.icon} /></span>
36
+ {it.label}
37
+ {#if it.count !== undefined}<span class="sb-count">{it.count}</span>{/if}
38
+ {#if it.pill !== undefined}<span class="sb-pill">{it.pill}<span class="sr-only"> alert{it.pill !== 1 ? 's' : ''}</span></span>{/if}
39
+ </button>
40
+ </li>
41
+ {/each}
42
+ </ul>
43
+ {/each}
44
+ </nav>
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ export let weak: boolean = false;
3
+ export let label: string | undefined = undefined;
4
+ </script>
5
+
6
+ <span
7
+ class="signal {weak ? 'weak' : ''}"
8
+ role="img"
9
+ aria-label={label ?? (weak ? 'Signal: weak' : 'Signal: strong')}
10
+ >
11
+ <i></i><i></i><i></i><i></i>
12
+ </span>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ export let variant: 'text' | 'title' | 'stat' | 'circle' | 'block' = 'text';
3
+ export let width: string | number | undefined = undefined;
4
+ export let height: string | number | undefined = undefined;
5
+ let className = '';
6
+ export { className as class };
7
+ export let ariaLabel: string = 'Loading';
8
+
9
+ $: variantClass = variant === 'block' ? '' : variant;
10
+ $: w = width ?? (variant === 'circle' ? height : undefined);
11
+ $: style = [
12
+ w !== undefined ? `width:${typeof w === 'number' ? w + 'px' : w}` : '',
13
+ height !== undefined ? `height:${typeof height === 'number' ? height + 'px' : height}` : '',
14
+ ].filter(Boolean).join(';');
15
+ </script>
16
+
17
+ <span
18
+ class="skeleton {variantClass} {className}"
19
+ {style}
20
+ role="status"
21
+ aria-label={ariaLabel}
22
+ aria-busy="true"
23
+ ></span>
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ export let href: string = '#main-content';
3
+ let className = '';
4
+ export { className as class };
5
+ </script>
6
+
7
+ <a class="skip-link {className}" {href}>
8
+ <slot>Skip to main content</slot>
9
+ </a>
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+
4
+ export let label: string | undefined = undefined;
5
+ export let value: number = 0;
6
+ export let min: number = 0;
7
+ export let max: number = 100;
8
+ export let step: number = 1;
9
+ export let suffix: string | undefined = undefined;
10
+ export let disabled: boolean = false;
11
+ export let id: string | undefined = undefined;
12
+
13
+ let counter = 0;
14
+ const inputId = id ?? `slider-${counter++}`;
15
+ const dispatch = createEventDispatcher<{ change: number }>();
16
+
17
+ $: pct = max === min ? 0 : ((value - min) / (max - min)) * 100;
18
+
19
+ function handleChange(e: Event) {
20
+ value = Number((e.target as HTMLInputElement).value);
21
+ dispatch('change', value);
22
+ }
23
+ </script>
24
+
25
+ <div class="slider">
26
+ {#if label}
27
+ <div class="slider__header">
28
+ <label for={inputId} class="slider__label">{label}</label>
29
+ <span class="slider__value" aria-live="polite">
30
+ {value}{#if suffix}<span class="slider__suffix">{suffix}</span>{/if}
31
+ </span>
32
+ </div>
33
+ {/if}
34
+ <input
35
+ {id}
36
+ type="range"
37
+ class="slider__track"
38
+ {min}
39
+ {max}
40
+ {step}
41
+ {value}
42
+ {disabled}
43
+ style="--slider-fill:{pct}%"
44
+ aria-label={label}
45
+ aria-valuetext={suffix ? `${value} ${suffix}` : undefined}
46
+ on:input={handleChange}
47
+ />
48
+ </div>
@@ -0,0 +1,91 @@
1
+ <script lang="ts">
2
+ export interface SlopeItem {
3
+ label: string;
4
+ before: number;
5
+ after: number;
6
+ }
7
+
8
+ export let items: SlopeItem[] = [];
9
+ export let labelBefore: string = 'Before';
10
+ export let labelAfter: string = 'After';
11
+ export let unit: string = '';
12
+ export let ariaLabel: string = 'Slope chart';
13
+ export let positiveIsGood: boolean = false;
14
+
15
+ const VW = 340;
16
+ const COL_L = 108;
17
+ const COL_R = 232;
18
+ const PAD_T = 26;
19
+ const CHART_H = 192;
20
+ const PAD_B = 8;
21
+ const SVG_H = PAD_T + CHART_H + PAD_B;
22
+ const DOT_R = 4;
23
+ const LABEL_GAP = 15;
24
+
25
+ function nudge(ys: number[], gap: number): number[] {
26
+ const out = [...ys];
27
+ const ord = out.map((y, i) => ({ y, i })).sort((a, b) => a.y - b.y);
28
+ for (let k = 1; k < ord.length; k++) {
29
+ if (ord[k].y < ord[k - 1].y + gap) ord[k].y = ord[k - 1].y + gap;
30
+ }
31
+ for (let k = ord.length - 2; k >= 0; k--) {
32
+ if (ord[k].y > ord[k + 1].y - gap) ord[k].y = ord[k + 1].y - gap;
33
+ }
34
+ const res: number[] = new Array(ys.length);
35
+ ord.forEach(({ y, i }) => { res[i] = y; });
36
+ return res;
37
+ }
38
+
39
+ $: allVals = items.flatMap((it) => [it.before, it.after]);
40
+ $: minV = allVals.length ? Math.min(...allVals) : 0;
41
+ $: maxV = allVals.length ? Math.max(...allVals) : 1;
42
+ $: range = maxV - minV || 1;
43
+ $: ty = (v: number) => PAD_T + (1 - (v - minV) / range) * CHART_H;
44
+
45
+ $: leftDotY = items.map((it) => ty(it.before));
46
+ $: rightDotY = items.map((it) => ty(it.after));
47
+ $: leftLabelY = nudge(leftDotY, LABEL_GAP);
48
+ $: rightLabelY = nudge(rightDotY, LABEL_GAP);
49
+
50
+ $: colored = items.map((it, i) => {
51
+ const diff = it.after - it.before;
52
+ const c =
53
+ diff === 0 ? '#A4A7B5' : diff > 0 === positiveIsGood ? '#00C875' : '#FF7B7B';
54
+ return {
55
+ ...it,
56
+ c,
57
+ ly: leftDotY[i],
58
+ ry: rightDotY[i],
59
+ lly: leftLabelY[i],
60
+ rly: rightLabelY[i],
61
+ delta: it.after - it.before,
62
+ };
63
+ });
64
+ </script>
65
+
66
+ {#if items.length}
67
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
68
+ <svg viewBox="0 0 {VW} {SVG_H}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
69
+ <text x={COL_L} y="14" fill="#6E7079" font-size="10" text-anchor="middle" font-family="inherit">{labelBefore}</text>
70
+ <text x={COL_R} y="14" fill="#6E7079" font-size="10" text-anchor="middle" font-family="inherit">{labelAfter}</text>
71
+ <line x1={COL_L} y1={PAD_T} x2={COL_L} y2={PAD_T + CHART_H} stroke="rgba(255,255,255,0.07)" stroke-width="1" />
72
+ <line x1={COL_R} y1={PAD_T} x2={COL_R} y2={PAD_T + CHART_H} stroke="rgba(255,255,255,0.07)" stroke-width="1" />
73
+
74
+ {#each colored as it, i (i)}
75
+ <line x1={COL_L} y1={it.ly} x2={COL_R} y2={it.ry} stroke={it.c} stroke-width="1.5" opacity="0.65" />
76
+ {#if Math.abs(it.lly - it.ly) > 1}
77
+ <line x1={COL_L - 6} y1={it.ly} x2={COL_L - 6} y2={it.lly} stroke="rgba(255,255,255,0.12)" stroke-width="0.75" />
78
+ {/if}
79
+ {#if Math.abs(it.rly - it.ry) > 1}
80
+ <line x1={COL_R + 6} y1={it.ry} x2={COL_R + 6} y2={it.rly} stroke="rgba(255,255,255,0.12)" stroke-width="0.75" />
81
+ {/if}
82
+ <circle cx={COL_L} cy={it.ly} r={DOT_R} fill={it.c} />
83
+ <circle cx={COL_R} cy={it.ry} r={DOT_R} fill={it.c} />
84
+ <text x={COL_L - 10} y={it.lly + 4} fill="#A4A7B5" font-size="9" text-anchor="end" font-family="inherit">{it.label}</text>
85
+ <text x={COL_L - 10} y={it.lly + 15} fill="#6E7079" font-size="9" text-anchor="end" font-family="inherit" font-variant-numeric="tabular-nums">{it.before}{unit}</text>
86
+ <text x={COL_R + 10} y={it.rly + 4} fill="#A4A7B5" font-size="9" text-anchor="start" font-family="inherit" font-variant-numeric="tabular-nums">{it.after}{unit}</text>
87
+ <text x={COL_R + 10} y={it.rly + 15} fill={it.c} font-size="9" text-anchor="start" font-family="inherit" font-variant-numeric="tabular-nums">{it.delta > 0 ? '+' : ''}{it.delta}{unit}</text>
88
+ {/each}
89
+ </svg>
90
+ </div>
91
+ {/if}
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ export let sortKey: string;
4
+ export let activeKey: string | null = null;
5
+ export let dir: 'asc' | 'desc' = 'asc';
6
+ const dispatch = createEventDispatcher<{ sort: string }>();
7
+ $: active = activeKey === sortKey;
8
+ $: ariaSort = active ? (dir === 'asc' ? 'ascending' : 'descending') : 'none';
9
+ </script>
10
+
11
+ <th scope="col" aria-sort={ariaSort}>
12
+ <button type="button" class="sort-header-btn" on:click={() => dispatch('sort', sortKey)}>
13
+ <slot />
14
+ <svg class="sort-icon" viewBox="0 0 8 11" aria-hidden="true" fill="currentColor">
15
+ <path d="M4 0L7 4H1Z" opacity={active && dir === 'asc' ? 1 : 0.3} />
16
+ <path d="M4 11L1 7H7Z" opacity={active && dir === 'desc' ? 1 : 0.3} />
17
+ </svg>
18
+ </button>
19
+ </th>
@@ -0,0 +1,87 @@
1
+ <script lang="ts">
2
+ export interface SortableItem {
3
+ id: string;
4
+ label: string;
5
+ meta?: string;
6
+ }
7
+
8
+ export let items: SortableItem[] = [];
9
+ export let onChange: (items: SortableItem[]) => void = () => {};
10
+ export let ariaLabel: string | undefined = undefined;
11
+
12
+ let grabbed: string | null = null;
13
+ let dragSrc: string | null = null;
14
+ let dragOver: string | null = null;
15
+
16
+ function reorder(fromId: string, toId: string) {
17
+ if (fromId === toId) return;
18
+ const from = items.findIndex(i => i.id === fromId);
19
+ const to = items.findIndex(i => i.id === toId);
20
+ const arr = [...items];
21
+ const [item] = arr.splice(from, 1);
22
+ arr.splice(to, 0, item);
23
+ items = arr;
24
+ onChange(arr);
25
+ }
26
+
27
+ function move(id: string, dir: -1 | 1) {
28
+ const idx = items.findIndex(i => i.id === id);
29
+ const next = idx + dir;
30
+ if (next < 0 || next >= items.length) return;
31
+ const arr = [...items];
32
+ [arr[idx], arr[next]] = [arr[next], arr[idx]];
33
+ items = arr;
34
+ onChange(arr);
35
+ }
36
+
37
+ function handleKeyDown(e: KeyboardEvent, id: string) {
38
+ if (e.key === ' ' || e.key === 'Enter') {
39
+ e.preventDefault();
40
+ grabbed = grabbed === id ? null : id;
41
+ } else if (grabbed === id) {
42
+ if (e.key === 'ArrowUp') { e.preventDefault(); move(id, -1); }
43
+ else if (e.key === 'ArrowDown') { e.preventDefault(); move(id, 1); }
44
+ else if (e.key === 'Escape') { grabbed = null; }
45
+ }
46
+ }
47
+ </script>
48
+
49
+ <ol class="sortable-list" aria-label={ariaLabel}>
50
+ {#each items as item, idx (item.id)}
51
+ <li
52
+ class="sortable-list__item"
53
+ class:is-grabbed={grabbed === item.id}
54
+ class:is-drag-over={dragOver === item.id}
55
+ draggable="true"
56
+ on:dragstart={e => { e.dataTransfer?.setData('text/plain', item.id); if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move'; dragSrc = item.id; }}
57
+ on:dragend={() => { dragSrc = null; dragOver = null; }}
58
+ on:dragover={e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; dragOver = item.id; }}
59
+ on:dragleave={() => { dragOver = null; }}
60
+ on:drop={e => { e.preventDefault(); if (dragSrc) reorder(dragSrc, item.id); dragSrc = null; dragOver = null; }}
61
+ >
62
+ <button
63
+ type="button"
64
+ class="sortable-list__handle"
65
+ aria-label="{grabbed === item.id ? 'Release' : 'Grab'} {item.label}. Use arrow keys to reorder."
66
+ aria-pressed={grabbed === item.id}
67
+ on:keydown={e => handleKeyDown(e, item.id)}
68
+ >
69
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true" focusable="false">
70
+ <rect x="3" y="2" width="2" height="2" rx="1" />
71
+ <rect x="9" y="2" width="2" height="2" rx="1" />
72
+ <rect x="3" y="6" width="2" height="2" rx="1" />
73
+ <rect x="9" y="6" width="2" height="2" rx="1" />
74
+ <rect x="3" y="10" width="2" height="2" rx="1" />
75
+ <rect x="9" y="10" width="2" height="2" rx="1" />
76
+ </svg>
77
+ </button>
78
+ <span class="sortable-list__content">
79
+ <span class="sortable-list__label">{item.label}</span>
80
+ {#if item.meta}
81
+ <span class="sortable-list__meta">{item.meta}</span>
82
+ {/if}
83
+ </span>
84
+ <span class="sortable-list__index" aria-hidden="true">{idx + 1}</span>
85
+ </li>
86
+ {/each}
87
+ </ol>
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ export let bars: number = 36;
3
+ export let active: boolean = false;
4
+ export let seed: number = 1;
5
+ export let ariaLabel: string = 'Activity sparkline';
6
+
7
+ function rng(s0: number) {
8
+ let s = s0;
9
+ return () => {
10
+ s = (s * 9301 + 49297) % 233280;
11
+ return s / 233280;
12
+ };
13
+ }
14
+
15
+ $: heights = (() => {
16
+ const r = rng(seed);
17
+ const out: number[] = [];
18
+ let prev = 12;
19
+ for (let i = 0; i < bars; i++) {
20
+ const v = Math.max(4, Math.min(40, prev + (r() * 10 - 5)));
21
+ prev = v;
22
+ out.push(v);
23
+ }
24
+ return out;
25
+ })();
26
+ </script>
27
+
28
+ <div class="spark" role="img" aria-label={ariaLabel}>
29
+ {#each heights as h, i}
30
+ <div class="b" aria-hidden="true" style="height:{h}px;opacity:{active ? 0.4 + i / (bars * 1.4) : 0.5 + i / (bars * 1.6)}"></div>
31
+ {/each}
32
+ </div>
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ export interface SparklineMatrixRow {
3
+ label: string;
4
+ values: number[];
5
+ unit?: string;
6
+ color?: string;
7
+ }
8
+
9
+ export let rows: SparklineMatrixRow[] = [];
10
+ export let height: number = 160;
11
+ export let ariaLabel: string = 'Sparkline matrix';
12
+
13
+ const VW = 380;
14
+ const PAD_L = 76;
15
+ const PAD_R = 44;
16
+ const SPARK_PAD_X = 8;
17
+ const SPARK_PAD_Y = 5;
18
+ const PALETTE = ['#006FFF', '#00C875', '#7FB6FF', '#F5A623', '#FF7B7B', '#A78BFA'];
19
+
20
+ $: rowH = height / (rows.length || 1);
21
+ $: sparkX0 = PAD_L + SPARK_PAD_X;
22
+ $: sparkW = VW - PAD_L - PAD_R - SPARK_PAD_X * 2;
23
+ $: sparkH = rowH - SPARK_PAD_Y * 2;
24
+
25
+ $: computed = rows.map((row, i) => {
26
+ const y0 = i * rowH + SPARK_PAD_Y;
27
+ const color = row.color ?? PALETTE[i % PALETTE.length];
28
+ const min = Math.min(...row.values);
29
+ const max = Math.max(...row.values);
30
+ const range = max - min || 1;
31
+ const lastVal = row.values[row.values.length - 1];
32
+
33
+ const d =
34
+ row.values.length < 2
35
+ ? ''
36
+ : row.values
37
+ .map((v, j) => {
38
+ const x = sparkX0 + (j / (row.values.length - 1)) * sparkW;
39
+ const y = y0 + sparkH - ((v - min) / range) * sparkH;
40
+ return `${j === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`;
41
+ })
42
+ .join(' ');
43
+
44
+ const dotY = y0 + sparkH - ((lastVal - min) / range) * sparkH;
45
+ return { color, lastVal, d, dotX: sparkX0 + sparkW, dotY, midY: i * rowH + rowH / 2 };
46
+ });
47
+ </script>
48
+
49
+ {#if rows.length}
50
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
51
+ <svg
52
+ viewBox="0 0 {VW} {height}"
53
+ style="width:100%;height:auto;display:block;"
54
+ aria-hidden="true"
55
+ focusable="false"
56
+ >
57
+ {#each rows as row, i}
58
+ <g>
59
+ {#if i > 0}
60
+ <line
61
+ x1={0} y1={i * rowH} x2={VW} y2={i * rowH}
62
+ stroke="rgba(255,255,255,0.06)" stroke-width="1"
63
+ />
64
+ {/if}
65
+ <text
66
+ x={PAD_L - 6} y={computed[i].midY + 3.5} text-anchor="end"
67
+ fill="#6E7079" font-size="9" font-family="inherit"
68
+ >{row.label}</text>
69
+ {#if computed[i].d}
70
+ <path
71
+ d={computed[i].d} fill="none" stroke={computed[i].color} stroke-width="1.5"
72
+ stroke-linejoin="round" stroke-linecap="round"
73
+ />
74
+ {/if}
75
+ <circle cx={computed[i].dotX} cy={computed[i].dotY} r="2.5" fill={computed[i].color} />
76
+ <text
77
+ x={VW - PAD_R + 6} y={computed[i].midY + 3.5} text-anchor="start"
78
+ fill={computed[i].color} font-size="9" font-family="inherit"
79
+ >{Math.round(computed[i].lastVal)}{row.unit ?? ''}</text>
80
+ </g>
81
+ {/each}
82
+ </svg>
83
+ </div>
84
+ {/if}
@@ -0,0 +1,6 @@
1
+ <script lang="ts">
2
+ export let size: 'sm' | 'md' | 'lg' = 'md';
3
+ export let label: string = 'Loading';
4
+ </script>
5
+
6
+ <span role="status" aria-label={label} class="spinner {size}" />