@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.
- package/README.md +8 -0
- package/package.json +57 -0
- package/src/lib/components/Accordion.svelte +3 -0
- package/src/lib/components/AccordionItem.svelte +40 -0
- package/src/lib/components/ActionMenu.svelte +96 -0
- package/src/lib/components/ActivityFeed.svelte +60 -0
- package/src/lib/components/Alert.svelte +19 -0
- package/src/lib/components/AnnotatedTimeSeries.svelte +90 -0
- package/src/lib/components/ArcDiagram.svelte +74 -0
- package/src/lib/components/AreaChart.svelte +64 -0
- package/src/lib/components/Avatar.svelte +30 -0
- package/src/lib/components/AvatarGroup.svelte +35 -0
- package/src/lib/components/Badge.svelte +25 -0
- package/src/lib/components/Banner.svelte +37 -0
- package/src/lib/components/BarChart.svelte +52 -0
- package/src/lib/components/BeeswarmChart.svelte +88 -0
- package/src/lib/components/BoxPlot.svelte +66 -0
- package/src/lib/components/Breadcrumb.svelte +31 -0
- package/src/lib/components/BubbleChart.svelte +69 -0
- package/src/lib/components/BulletChart.svelte +70 -0
- package/src/lib/components/BumpChart.svelte +109 -0
- package/src/lib/components/Button.svelte +25 -0
- package/src/lib/components/CIDRInput.svelte +142 -0
- package/src/lib/components/CalendarHeatmap.svelte +86 -0
- package/src/lib/components/Callout.svelte +38 -0
- package/src/lib/components/CandlestickChart.svelte +66 -0
- package/src/lib/components/Card.svelte +11 -0
- package/src/lib/components/Carousel.svelte +64 -0
- package/src/lib/components/Checkbox.svelte +45 -0
- package/src/lib/components/ChordDiagram.svelte +90 -0
- package/src/lib/components/CirclePacking.svelte +119 -0
- package/src/lib/components/CodeBlock.svelte +49 -0
- package/src/lib/components/ColorPicker.svelte +91 -0
- package/src/lib/components/ColumnToggle.svelte +96 -0
- package/src/lib/components/Combobox.svelte +138 -0
- package/src/lib/components/CommandPalette.svelte +152 -0
- package/src/lib/components/ConfirmDialog.svelte +84 -0
- package/src/lib/components/ContextMenu.svelte +107 -0
- package/src/lib/components/ContextualHelp.svelte +73 -0
- package/src/lib/components/ContourPlot.svelte +164 -0
- package/src/lib/components/CopyButton.svelte +33 -0
- package/src/lib/components/CorrelationMatrix.svelte +90 -0
- package/src/lib/components/CountUp.svelte +49 -0
- package/src/lib/components/CumulativeDistribution.svelte +67 -0
- package/src/lib/components/DatePicker.svelte +201 -0
- package/src/lib/components/DateRangePicker.svelte +249 -0
- package/src/lib/components/Dendrogram.svelte +128 -0
- package/src/lib/components/Donut.svelte +49 -0
- package/src/lib/components/DotPlot.svelte +121 -0
- package/src/lib/components/Drawer.svelte +60 -0
- package/src/lib/components/DualAxisChart.svelte +80 -0
- package/src/lib/components/DumbbellChart.svelte +56 -0
- package/src/lib/components/DurationInput.svelte +118 -0
- package/src/lib/components/EmptyState.svelte +21 -0
- package/src/lib/components/ErrorBandChart.svelte +69 -0
- package/src/lib/components/ExpandableRow.svelte +42 -0
- package/src/lib/components/Field.svelte +40 -0
- package/src/lib/components/FileUpload.svelte +96 -0
- package/src/lib/components/FilterBuilder.svelte +160 -0
- package/src/lib/components/FlameGraph.svelte +88 -0
- package/src/lib/components/ForceGraph.svelte +104 -0
- package/src/lib/components/FunnelChart.svelte +50 -0
- package/src/lib/components/GanttChart.svelte +49 -0
- package/src/lib/components/Gauge.svelte +53 -0
- package/src/lib/components/GroupedList.svelte +87 -0
- package/src/lib/components/HealthBar.svelte +19 -0
- package/src/lib/components/HeatMap.svelte +64 -0
- package/src/lib/components/HexbinChart.svelte +100 -0
- package/src/lib/components/Histogram.svelte +93 -0
- package/src/lib/components/HorizonChart.svelte +71 -0
- package/src/lib/components/HoverCard.svelte +45 -0
- package/src/lib/components/IPInput.svelte +103 -0
- package/src/lib/components/IcicleChart.svelte +87 -0
- package/src/lib/components/IconButton.svelte +19 -0
- package/src/lib/components/InlineEdit.svelte +72 -0
- package/src/lib/components/Input.svelte +18 -0
- package/src/lib/components/InputGroup.svelte +14 -0
- package/src/lib/components/JsonViewer.svelte +15 -0
- package/src/lib/components/JsonViewerNode.svelte +59 -0
- package/src/lib/components/KVTable.svelte +18 -0
- package/src/lib/components/KanbanBoard.svelte +141 -0
- package/src/lib/components/Kbd.svelte +16 -0
- package/src/lib/components/LineChart.svelte +67 -0
- package/src/lib/components/LogViewer.svelte +84 -0
- package/src/lib/components/LollipopChart.svelte +79 -0
- package/src/lib/components/MACInput.svelte +98 -0
- package/src/lib/components/MarimekkoChart.svelte +108 -0
- package/src/lib/components/MatrixChart.svelte +55 -0
- package/src/lib/components/Menubar.svelte +131 -0
- package/src/lib/components/MirroredBarChart.svelte +120 -0
- package/src/lib/components/Modal.svelte +90 -0
- package/src/lib/components/MultiSelect.svelte +165 -0
- package/src/lib/components/NightingaleChart.svelte +63 -0
- package/src/lib/components/NotificationPanel.svelte +170 -0
- package/src/lib/components/NumberInput.svelte +93 -0
- package/src/lib/components/OTPInput.svelte +94 -0
- package/src/lib/components/Pagination.svelte +58 -0
- package/src/lib/components/ParallelCoordinates.svelte +98 -0
- package/src/lib/components/ParetoChart.svelte +88 -0
- package/src/lib/components/PasswordInput.svelte +62 -0
- package/src/lib/components/PieChart.svelte +91 -0
- package/src/lib/components/Pill.svelte +12 -0
- package/src/lib/components/PolarHeatmap.svelte +85 -0
- package/src/lib/components/Popover.svelte +89 -0
- package/src/lib/components/ProgressBar.svelte +29 -0
- package/src/lib/components/PunchCard.svelte +73 -0
- package/src/lib/components/QuadrantChart.svelte +122 -0
- package/src/lib/components/RadarChart.svelte +71 -0
- package/src/lib/components/RadialBarChart.svelte +76 -0
- package/src/lib/components/RadioGroup.svelte +48 -0
- package/src/lib/components/RangeSlider.svelte +68 -0
- package/src/lib/components/RankedList.svelte +49 -0
- package/src/lib/components/ResizablePanel.svelte +77 -0
- package/src/lib/components/RidgelinePlot.svelte +103 -0
- package/src/lib/components/RowToggle.svelte +19 -0
- package/src/lib/components/SankeyDiagram.svelte +100 -0
- package/src/lib/components/ScatterPlot.svelte +54 -0
- package/src/lib/components/SearchBox.svelte +11 -0
- package/src/lib/components/SegmentedControl.svelte +51 -0
- package/src/lib/components/Select.svelte +110 -0
- package/src/lib/components/SelectionToolbar.svelte +42 -0
- package/src/lib/components/Sidebar.svelte +44 -0
- package/src/lib/components/Signal.svelte +12 -0
- package/src/lib/components/Skeleton.svelte +23 -0
- package/src/lib/components/SkipLink.svelte +9 -0
- package/src/lib/components/Slider.svelte +48 -0
- package/src/lib/components/SlopeChart.svelte +91 -0
- package/src/lib/components/SortHeader.svelte +19 -0
- package/src/lib/components/SortableList.svelte +87 -0
- package/src/lib/components/Sparkline.svelte +32 -0
- package/src/lib/components/SparklineMatrix.svelte +84 -0
- package/src/lib/components/Spinner.svelte +6 -0
- package/src/lib/components/SplitButton.svelte +102 -0
- package/src/lib/components/Spoiler.svelte +41 -0
- package/src/lib/components/StackedBarChart.svelte +83 -0
- package/src/lib/components/StackedProgress.svelte +32 -0
- package/src/lib/components/StarRating.svelte +64 -0
- package/src/lib/components/Stat.svelte +24 -0
- package/src/lib/components/StatusIndicator.svelte +10 -0
- package/src/lib/components/StepChart.svelte +74 -0
- package/src/lib/components/Stepper.svelte +40 -0
- package/src/lib/components/StreamGraph.svelte +93 -0
- package/src/lib/components/StripeChart.svelte +52 -0
- package/src/lib/components/SunburstChart.svelte +80 -0
- package/src/lib/components/SwitchPortGrid.svelte +45 -0
- package/src/lib/components/TabPanel.svelte +17 -0
- package/src/lib/components/Tabs.svelte +45 -0
- package/src/lib/components/Tag.svelte +19 -0
- package/src/lib/components/TagInput.svelte +92 -0
- package/src/lib/components/Textarea.svelte +31 -0
- package/src/lib/components/ThresholdAreaChart.svelte +72 -0
- package/src/lib/components/TimePicker.svelte +116 -0
- package/src/lib/components/TimeRange.svelte +104 -0
- package/src/lib/components/Timeline.svelte +32 -0
- package/src/lib/components/Toast.svelte +52 -0
- package/src/lib/components/Toggle.svelte +23 -0
- package/src/lib/components/ToggleGroup.svelte +32 -0
- package/src/lib/components/Tooltip.svelte +16 -0
- package/src/lib/components/Topbar.svelte +63 -0
- package/src/lib/components/TransferList.svelte +184 -0
- package/src/lib/components/TreeItem.svelte +61 -0
- package/src/lib/components/TreeMap.svelte +90 -0
- package/src/lib/components/TreeView.svelte +130 -0
- package/src/lib/components/UptimeTimeline.svelte +86 -0
- package/src/lib/components/VennDiagram.svelte +91 -0
- package/src/lib/components/ViolinPlot.svelte +114 -0
- package/src/lib/components/VirtualList.svelte +38 -0
- package/src/lib/components/WaffleChart.svelte +68 -0
- package/src/lib/components/WaterfallChart.svelte +96 -0
- package/src/lib/components/WordCloud.svelte +103 -0
- package/src/lib/icons/AlarmIcon.svelte +5 -0
- package/src/lib/icons/BellIcon.svelte +6 -0
- package/src/lib/icons/CaretIcon.svelte +8 -0
- package/src/lib/icons/ClientsIcon.svelte +5 -0
- package/src/lib/icons/CloseIcon.svelte +6 -0
- package/src/lib/icons/DashboardIcon.svelte +7 -0
- package/src/lib/icons/DevicesIcon.svelte +5 -0
- package/src/lib/icons/DownloadIcon.svelte +6 -0
- package/src/lib/icons/HelpIcon.svelte +7 -0
- package/src/lib/icons/InfraIcon.svelte +7 -0
- package/src/lib/icons/IntegrationsIcon.svelte +5 -0
- package/src/lib/icons/LogsIcon.svelte +6 -0
- package/src/lib/icons/PlusIcon.svelte +6 -0
- package/src/lib/icons/PortsIcon.svelte +6 -0
- package/src/lib/icons/SearchIcon.svelte +10 -0
- package/src/lib/icons/SecurityIcon.svelte +4 -0
- package/src/lib/icons/SettingsIcon.svelte +5 -0
- package/src/lib/icons/TopologyIcon.svelte +7 -0
- package/src/lib/icons/UpdatesIcon.svelte +6 -0
- package/src/lib/icons/VpnIcon.svelte +5 -0
- package/src/lib/icons/WifiIcon.svelte +5 -0
- package/src/lib/icons/WirelessIcon.svelte +4 -0
- package/src/lib/icons.ts +22 -0
- package/src/lib/index.ts +275 -0
- package/src/lib/styles.css +2 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface CalendarDay {
|
|
3
|
+
date: string;
|
|
4
|
+
value: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export let data: CalendarDay[] = [];
|
|
8
|
+
export let maxValue: number | undefined = undefined;
|
|
9
|
+
export let ariaLabel: string = 'Calendar heatmap';
|
|
10
|
+
|
|
11
|
+
const CELL = 10;
|
|
12
|
+
const GAP = 2;
|
|
13
|
+
const STEP = CELL + GAP;
|
|
14
|
+
const WEEKS = 52;
|
|
15
|
+
const DAYS = 7;
|
|
16
|
+
const LEFT = 22;
|
|
17
|
+
const TOP = 16;
|
|
18
|
+
const COLORS = ['rgba(255,255,255,0.06)', '#0d2a5e', '#1a4da6', '#2979ff', '#5ba4ff'];
|
|
19
|
+
const DAY_LABELS = ['Mon', '', 'Wed', '', 'Fri', '', ''];
|
|
20
|
+
const W = LEFT + WEEKS * STEP;
|
|
21
|
+
const H = TOP + DAYS * STEP + 2;
|
|
22
|
+
|
|
23
|
+
function lvl(v: number, mx: number): number {
|
|
24
|
+
if (!v) return 0;
|
|
25
|
+
const r = v / mx;
|
|
26
|
+
return r < 0.25 ? 1 : r < 0.5 ? 2 : r < 0.75 ? 3 : 4;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function toDateStr(d: Date): string {
|
|
30
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$: ({ cells, monthLabels } = (() => {
|
|
34
|
+
const byDate = new Map(data.map((d) => [d.date, d.value]));
|
|
35
|
+
const mx = maxValue ?? Math.max(1, ...data.map((d) => d.value));
|
|
36
|
+
|
|
37
|
+
const today = new Date();
|
|
38
|
+
today.setHours(0, 0, 0, 0);
|
|
39
|
+
const start = new Date(today);
|
|
40
|
+
start.setDate(start.getDate() - WEEKS * 7 + 1);
|
|
41
|
+
start.setDate(start.getDate() - start.getDay());
|
|
42
|
+
|
|
43
|
+
const cells: { x: number; y: number; fill: string }[] = [];
|
|
44
|
+
const seenMonths = new Set<string>();
|
|
45
|
+
const monthLabels: { x: number; label: string }[] = [];
|
|
46
|
+
|
|
47
|
+
for (let w = 0; w < WEEKS; w++) {
|
|
48
|
+
for (let d = 0; d < DAYS; d++) {
|
|
49
|
+
const date = new Date(start);
|
|
50
|
+
date.setDate(start.getDate() + w * 7 + d);
|
|
51
|
+
const monthKey = `${date.getFullYear()}-${date.getMonth()}`;
|
|
52
|
+
if (!seenMonths.has(monthKey) && d === 0) {
|
|
53
|
+
seenMonths.add(monthKey);
|
|
54
|
+
monthLabels.push({ x: LEFT + w * STEP, label: date.toLocaleString('en', { month: 'short' }) });
|
|
55
|
+
}
|
|
56
|
+
const val = byDate.get(toDateStr(date)) ?? 0;
|
|
57
|
+
cells.push({ x: LEFT + w * STEP, y: TOP + d * STEP, fill: COLORS[lvl(val, mx)] });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { cells, monthLabels };
|
|
62
|
+
})());
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<div role="img" aria-label={ariaLabel} style="width:100%;overflow-x:auto;">
|
|
66
|
+
<svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
|
|
67
|
+
{#each DAY_LABELS as label, d}
|
|
68
|
+
{#if label}
|
|
69
|
+
<text
|
|
70
|
+
x={LEFT - 3}
|
|
71
|
+
y={TOP + d * STEP + CELL * 0.8}
|
|
72
|
+
text-anchor="end"
|
|
73
|
+
fill="#6E7079"
|
|
74
|
+
font-size="7"
|
|
75
|
+
font-family="inherit"
|
|
76
|
+
>{label}</text>
|
|
77
|
+
{/if}
|
|
78
|
+
{/each}
|
|
79
|
+
{#each monthLabels as m}
|
|
80
|
+
<text x={m.x} y={TOP - 3} fill="#6E7079" font-size="7" font-family="inherit">{m.label}</text>
|
|
81
|
+
{/each}
|
|
82
|
+
{#each cells as c, i (i)}
|
|
83
|
+
<rect x={c.x} y={c.y} width={CELL} height={CELL} rx="2" fill={c.fill} />
|
|
84
|
+
{/each}
|
|
85
|
+
</svg>
|
|
86
|
+
</div>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
export type CalloutVariant = 'info' | 'success' | 'warn' | 'danger' | 'tip';
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
export let variant: CalloutVariant = 'info';
|
|
7
|
+
export let title: string | undefined = undefined;
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<div class="callout callout--{variant}" role="note">
|
|
11
|
+
{#if variant === 'info'}
|
|
12
|
+
<svg class="callout-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
13
|
+
<path fill-rule="evenodd" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM7.25 5.5a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0ZM7 7.5h2v4H7v-4Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
{:else if variant === 'success'}
|
|
16
|
+
<svg class="callout-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
17
|
+
<path fill-rule="evenodd" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1Zm3.78 5.47-4.25 4.5a.75.75 0 0 1-1.06.03L4.72 9.22a.75.75 0 1 1 1.06-1.06l1.22 1.22 3.72-3.94a.75.75 0 1 1 1.06 1.03Z" />
|
|
18
|
+
</svg>
|
|
19
|
+
{:else if variant === 'warn'}
|
|
20
|
+
<svg class="callout-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
21
|
+
<path fill-rule="evenodd" d="M8.9 1.6a1 1 0 0 0-1.8 0L.6 13a1 1 0 0 0 .9 1.5h13a1 1 0 0 0 .9-1.5L8.9 1.6ZM7.25 6.5a.75.75 0 1 1 1.5 0v3a.75.75 0 0 1-1.5 0v-3Zm.75 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z" />
|
|
22
|
+
</svg>
|
|
23
|
+
{:else if variant === 'danger'}
|
|
24
|
+
<svg class="callout-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
25
|
+
<path fill-rule="evenodd" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM5.97 5.97a.75.75 0 0 1 1.06 0L8 6.94l.97-.97a.75.75 0 1 1 1.06 1.06L9.06 8l.97.97a.75.75 0 0 1-1.06 1.06L8 9.06l-.97.97a.75.75 0 0 1-1.06-1.06L6.94 8l-.97-.97a.75.75 0 0 1 0-1.06Z" />
|
|
26
|
+
</svg>
|
|
27
|
+
{:else}
|
|
28
|
+
<svg class="callout-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
29
|
+
<path d="M8 1a5 5 0 0 1 3 9.01V11H5v-.99A5 5 0 0 1 8 1Zm-1 12h2v1.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5V13Z" />
|
|
30
|
+
</svg>
|
|
31
|
+
{/if}
|
|
32
|
+
<div class="callout-body">
|
|
33
|
+
{#if title}
|
|
34
|
+
<div class="callout-title">{title}</div>
|
|
35
|
+
{/if}
|
|
36
|
+
<div><slot /></div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface CandlestickBar {
|
|
3
|
+
label: string;
|
|
4
|
+
open: number;
|
|
5
|
+
close: number;
|
|
6
|
+
high: number;
|
|
7
|
+
low: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export let bars: CandlestickBar[] = [];
|
|
11
|
+
export let yRange: [number, number] | undefined = undefined;
|
|
12
|
+
export let height = 180;
|
|
13
|
+
export let unit = '';
|
|
14
|
+
export let upColor = '#00C875';
|
|
15
|
+
export let downColor = '#FF7B7B';
|
|
16
|
+
export let ariaLabel = 'Candlestick chart';
|
|
17
|
+
|
|
18
|
+
const VW = 340;
|
|
19
|
+
const PAD_L = 28;
|
|
20
|
+
const PAD_R = 8;
|
|
21
|
+
const PAD_T = 8;
|
|
22
|
+
const PAD_B = 20;
|
|
23
|
+
const PLOT_W = VW - PAD_L - PAD_R;
|
|
24
|
+
|
|
25
|
+
$: n = bars.length;
|
|
26
|
+
$: PLOT_H = height - PAD_T - PAD_B;
|
|
27
|
+
$: allVals = bars.flatMap((b) => [b.low, b.high]);
|
|
28
|
+
$: minV = yRange ? yRange[0] : allVals.length ? Math.min(...allVals) : 0;
|
|
29
|
+
$: maxV = yRange ? yRange[1] : allVals.length ? Math.max(...allVals) : 1;
|
|
30
|
+
$: vRange = maxV - minV || 1;
|
|
31
|
+
$: yTicks = [minV, (minV + maxV) / 2, maxV];
|
|
32
|
+
$: candleW = Math.max(4, (PLOT_W / (n || 1)) * 0.5);
|
|
33
|
+
|
|
34
|
+
function tx(i: number): number {
|
|
35
|
+
return PAD_L + ((i + 0.5) / (n || 1)) * PLOT_W;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ty(v: number): number {
|
|
39
|
+
return PAD_T + (1 - (v - minV) / vRange) * PLOT_H;
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if bars.length > 0}
|
|
44
|
+
<div role="img" aria-label={ariaLabel} style="width:100%;">
|
|
45
|
+
<svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
|
|
46
|
+
{#each yTicks as v, i (i)}
|
|
47
|
+
<line x1={PAD_L} y1={ty(v)} x2={PAD_L + PLOT_W} y2={ty(v)} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
|
|
48
|
+
<text x={PAD_L - 4} y={ty(v) + 4} fill="#6E7079" font-size="8" text-anchor="end" font-family="inherit">{Math.round(v)}{unit}</text>
|
|
49
|
+
{/each}
|
|
50
|
+
|
|
51
|
+
{#each bars as b, i (i)}
|
|
52
|
+
{@const x = tx(i)}
|
|
53
|
+
{@const color = b.close >= b.open ? upColor : downColor}
|
|
54
|
+
{@const bodyTop = ty(Math.max(b.open, b.close))}
|
|
55
|
+
{@const bodyBot = ty(Math.min(b.open, b.close))}
|
|
56
|
+
{@const bodyH = Math.max(1, bodyBot - bodyTop)}
|
|
57
|
+
<line x1={x} y1={ty(b.high)} x2={x} y2={ty(b.low)} stroke={color} stroke-width="1" />
|
|
58
|
+
<rect x={x - candleW / 2} y={bodyTop} width={candleW} height={bodyH} fill={color} />
|
|
59
|
+
{/each}
|
|
60
|
+
|
|
61
|
+
{#each bars as b, i (i)}
|
|
62
|
+
<text x={tx(i)} y={height - 4} fill="#6E7079" font-size="9" text-anchor="middle" font-family="inherit">{b.label}</text>
|
|
63
|
+
{/each}
|
|
64
|
+
</svg>
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let span: number | undefined = undefined;
|
|
3
|
+
let className = '';
|
|
4
|
+
export { className as class };
|
|
5
|
+
export let style: string = '';
|
|
6
|
+
$: spanStyle = span !== undefined ? `grid-column: span ${span};` : '';
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div class="card {className}" style="{spanStyle} {style}">
|
|
10
|
+
<slot />
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let slides: Array<{ id: string; title: string; description?: string; color?: string }> =
|
|
3
|
+
[];
|
|
4
|
+
export let label: string;
|
|
5
|
+
let klass = '';
|
|
6
|
+
export { klass as class };
|
|
7
|
+
|
|
8
|
+
let index = 0;
|
|
9
|
+
|
|
10
|
+
function prev() {
|
|
11
|
+
index = (index - 1 + slides.length) % slides.length;
|
|
12
|
+
}
|
|
13
|
+
function next() {
|
|
14
|
+
index = (index + 1) % slides.length;
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<section
|
|
19
|
+
class="carousel {klass}"
|
|
20
|
+
aria-roledescription="carousel"
|
|
21
|
+
aria-label={label}
|
|
22
|
+
>
|
|
23
|
+
<div class="carousel-track" aria-live="polite" aria-atomic="false">
|
|
24
|
+
{#each slides as slide, i (slide.id)}
|
|
25
|
+
<div
|
|
26
|
+
role="group"
|
|
27
|
+
aria-roledescription="slide"
|
|
28
|
+
aria-label="{i + 1} of {slides.length}"
|
|
29
|
+
class="carousel-slide{i === index ? ' is-active' : ''}"
|
|
30
|
+
aria-hidden={i !== index ? true : undefined}
|
|
31
|
+
>
|
|
32
|
+
{#if slide.color}
|
|
33
|
+
<div class="carousel-slide-accent" style="background:{slide.color}" aria-hidden="true" />
|
|
34
|
+
{/if}
|
|
35
|
+
<div class="carousel-slide-body">
|
|
36
|
+
<div class="carousel-slide-title">{slide.title}</div>
|
|
37
|
+
{#if slide.description}
|
|
38
|
+
<div class="carousel-slide-desc">{slide.description}</div>
|
|
39
|
+
{/if}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
{/each}
|
|
43
|
+
</div>
|
|
44
|
+
<div class="carousel-controls">
|
|
45
|
+
<button type="button" class="carousel-btn" aria-label="Previous slide" on:click={prev}
|
|
46
|
+
>‹</button
|
|
47
|
+
>
|
|
48
|
+
<div role="tablist" aria-label="Choose slide to display" class="carousel-indicators">
|
|
49
|
+
{#each slides as slide, i (slide.id)}
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
role="tab"
|
|
53
|
+
aria-selected={i === index}
|
|
54
|
+
aria-label="Slide {i + 1}: {slide.title}"
|
|
55
|
+
class="carousel-dot{i === index ? ' is-active' : ''}"
|
|
56
|
+
on:click={() => (index = i)}
|
|
57
|
+
/>
|
|
58
|
+
{/each}
|
|
59
|
+
</div>
|
|
60
|
+
<button type="button" class="carousel-btn" aria-label="Next slide" on:click={next}
|
|
61
|
+
>›</button
|
|
62
|
+
>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
let counter = 0;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
export let checked: boolean = false;
|
|
7
|
+
export let indeterminate: boolean = false;
|
|
8
|
+
export let label: string | undefined = undefined;
|
|
9
|
+
export let id: string | undefined = undefined;
|
|
10
|
+
export let disabled: boolean = false;
|
|
11
|
+
|
|
12
|
+
const uid = `dash-ui-cb-${++counter}`;
|
|
13
|
+
$: inputId = id ?? uid;
|
|
14
|
+
|
|
15
|
+
let el: HTMLInputElement;
|
|
16
|
+
$: if (el) {
|
|
17
|
+
el.checked = checked;
|
|
18
|
+
el.indeterminate = indeterminate;
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if label}
|
|
23
|
+
<label class="checkbox-label" for={inputId}>
|
|
24
|
+
<input
|
|
25
|
+
bind:this={el}
|
|
26
|
+
type="checkbox"
|
|
27
|
+
id={inputId}
|
|
28
|
+
{disabled}
|
|
29
|
+
class="checkbox"
|
|
30
|
+
on:change
|
|
31
|
+
{...$$restProps}
|
|
32
|
+
/>
|
|
33
|
+
<span>{label}</span>
|
|
34
|
+
</label>
|
|
35
|
+
{:else}
|
|
36
|
+
<input
|
|
37
|
+
bind:this={el}
|
|
38
|
+
type="checkbox"
|
|
39
|
+
id={inputId}
|
|
40
|
+
{disabled}
|
|
41
|
+
class="checkbox"
|
|
42
|
+
on:change
|
|
43
|
+
{...$$restProps}
|
|
44
|
+
/>
|
|
45
|
+
{/if}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface ChordNode { label: string; color?: string; }
|
|
3
|
+
|
|
4
|
+
export let nodes: ChordNode[];
|
|
5
|
+
export let matrix: number[][];
|
|
6
|
+
export let ariaLabel: string = 'Chord diagram';
|
|
7
|
+
|
|
8
|
+
const PALETTE = ['#006FFF', '#00C875', '#A78BFA', '#F5A623', '#50B8E7', '#F04949'];
|
|
9
|
+
const CX = 160, CY = 160, R_IN = 108, R_ARC = 125, R_LBL = 138, PAD = 0.04;
|
|
10
|
+
|
|
11
|
+
function pt(r: number, a: number) {
|
|
12
|
+
return `${(CX + r * Math.cos(a)).toFixed(1)},${(CY + r * Math.sin(a)).toFixed(1)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function arcSlice(r1: number, r2: number, a1: number, a2: number) {
|
|
16
|
+
const lg = a2 - a1 > Math.PI ? 1 : 0;
|
|
17
|
+
return `M${pt(r2, a1)} A${r2},${r2} 0 ${lg},1 ${pt(r2, a2)} L${pt(r1, a2)} A${r1},${r1} 0 ${lg},0 ${pt(r1, a1)} Z`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ribbon(a1: number, a2: number, b1: number, b2: number) {
|
|
21
|
+
const la = +(a2 - a1 > Math.PI);
|
|
22
|
+
const lb = +(b2 - b1 > Math.PI);
|
|
23
|
+
return `M${pt(R_IN, a1)} A${R_IN},${R_IN} 0 ${la},1 ${pt(R_IN, a2)} Q${CX},${CY} ${pt(R_IN, b2)} A${R_IN},${R_IN} 0 ${lb},0 ${pt(R_IN, b1)} Q${CX},${CY} ${pt(R_IN, a1)}Z`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Arc = { d: string; fill: string };
|
|
27
|
+
type Chord = { d: string; fill: string };
|
|
28
|
+
type Label = { x: number; y: number; anchor: string; text: string };
|
|
29
|
+
|
|
30
|
+
$: ({ arcs, chords, labels } = (() => {
|
|
31
|
+
const n = nodes.length;
|
|
32
|
+
const totals = nodes.map((_, i) => matrix[i].reduce((s, v) => s + v, 0));
|
|
33
|
+
const grand = totals.reduce((s, v) => s + v, 0);
|
|
34
|
+
if (!grand) return { arcs: [] as Arc[], chords: [] as Chord[], labels: [] as Label[] };
|
|
35
|
+
|
|
36
|
+
const scale = (2 * Math.PI - PAD * n) / grand;
|
|
37
|
+
const nodeA: { start: number; end: number; offsets: number[] }[] = [];
|
|
38
|
+
let angle = -Math.PI / 2;
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < n; i++) {
|
|
41
|
+
const span = totals[i] * scale;
|
|
42
|
+
const offsets: number[] = [];
|
|
43
|
+
let off = angle;
|
|
44
|
+
for (let j = 0; j < n; j++) {
|
|
45
|
+
offsets.push(off);
|
|
46
|
+
off += matrix[i][j] * scale;
|
|
47
|
+
}
|
|
48
|
+
nodeA.push({ start: angle, end: angle + span, offsets });
|
|
49
|
+
angle += span + PAD;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const arcs: Arc[] = nodeA.map((a, i) => ({
|
|
53
|
+
d: arcSlice(R_IN + 2, R_ARC, a.start, a.end),
|
|
54
|
+
fill: nodes[i].color ?? PALETTE[i % PALETTE.length],
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
const chords: Chord[] = [];
|
|
58
|
+
for (let i = 0; i < n; i++) {
|
|
59
|
+
for (let j = i + 1; j < n; j++) {
|
|
60
|
+
const vij = matrix[i][j], vji = matrix[j][i];
|
|
61
|
+
if (!vij && !vji) continue;
|
|
62
|
+
const a1 = nodeA[i].offsets[j], a2 = a1 + vij * scale;
|
|
63
|
+
const b1 = nodeA[j].offsets[i], b2 = b1 + vji * scale;
|
|
64
|
+
chords.push({ d: ribbon(a1, a2, b1, b2), fill: (nodes[i].color ?? PALETTE[i % PALETTE.length]) + '55' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const labels: Label[] = nodeA.map((a, i) => {
|
|
69
|
+
const mid = (a.start + a.end) / 2;
|
|
70
|
+
const anchor = Math.cos(mid) > 0.2 ? 'start' : Math.cos(mid) < -0.2 ? 'end' : 'middle';
|
|
71
|
+
return { x: CX + R_LBL * Math.cos(mid), y: CY + R_LBL * Math.sin(mid) + 4, anchor, text: nodes[i].label };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return { arcs, chords, labels };
|
|
75
|
+
})());
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<div role="img" aria-label={ariaLabel} style="width:100%;">
|
|
79
|
+
<svg viewBox="0 0 320 320" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
|
|
80
|
+
{#each chords as c, i (i)}
|
|
81
|
+
<path d={c.d} fill={c.fill} />
|
|
82
|
+
{/each}
|
|
83
|
+
{#each arcs as a, i (i)}
|
|
84
|
+
<path d={a.d} fill={a.fill} />
|
|
85
|
+
{/each}
|
|
86
|
+
{#each labels as l, i (i)}
|
|
87
|
+
<text x={l.x.toFixed(1)} y={l.y.toFixed(1)} text-anchor={l.anchor} fill="#fff" font-size="10" font-weight="600" font-family="inherit">{l.text}</text>
|
|
88
|
+
{/each}
|
|
89
|
+
</svg>
|
|
90
|
+
</div>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface CirclePackItem {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
value: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export let items: CirclePackItem[];
|
|
10
|
+
export let height = 200;
|
|
11
|
+
export let ariaLabel = 'Circle packing chart';
|
|
12
|
+
|
|
13
|
+
const PALETTE = ['#006FFF', '#00C875', '#F5A623', '#A78BFA', '#FF7B7B', '#00C8C8', '#FB923C', '#4797FF'];
|
|
14
|
+
const VW = 360;
|
|
15
|
+
const MAX_R = 72;
|
|
16
|
+
const MIN_R = 10;
|
|
17
|
+
|
|
18
|
+
function pack(radii: number[]): { cx: number; cy: number }[] {
|
|
19
|
+
const pos: { cx: number; cy: number; r: number }[] = [];
|
|
20
|
+
for (let i = 0; i < radii.length; i++) {
|
|
21
|
+
const r = radii[i];
|
|
22
|
+
if (i === 0) { pos.push({ cx: 0, cy: 0, r }); continue; }
|
|
23
|
+
if (i === 1) { pos.push({ cx: pos[0].r + r, cy: 0, r }); continue; }
|
|
24
|
+
|
|
25
|
+
const candidates: { cx: number; cy: number }[] = [];
|
|
26
|
+
for (let a = 0; a < pos.length; a++) {
|
|
27
|
+
for (let b = a + 1; b < pos.length; b++) {
|
|
28
|
+
const pa = pos[a], pb = pos[b];
|
|
29
|
+
const da = pa.r + r, db = pb.r + r;
|
|
30
|
+
const dx = pb.cx - pa.cx, dy = pb.cy - pa.cy;
|
|
31
|
+
const dist = Math.hypot(dx, dy);
|
|
32
|
+
if (dist > da + db || dist < Math.abs(da - db)) continue;
|
|
33
|
+
const cosA = (da * da + dist * dist - db * db) / (2 * da * dist);
|
|
34
|
+
if (cosA < -1 || cosA > 1) continue;
|
|
35
|
+
const acos = Math.acos(Math.max(-1, Math.min(1, cosA)));
|
|
36
|
+
const base = Math.atan2(dy, dx);
|
|
37
|
+
for (const sign of [1, -1]) {
|
|
38
|
+
candidates.push({
|
|
39
|
+
cx: pa.cx + da * Math.cos(base + sign * acos),
|
|
40
|
+
cy: pa.cy + da * Math.sin(base + sign * acos),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const pa = pos[a];
|
|
45
|
+
const ang = Math.atan2(-pa.cy, -pa.cx);
|
|
46
|
+
candidates.push({
|
|
47
|
+
cx: pa.cx + (pa.r + r) * Math.cos(ang),
|
|
48
|
+
cy: pa.cy + (pa.r + r) * Math.sin(ang),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let best: { cx: number; cy: number } | null = null;
|
|
53
|
+
let bestDist = Infinity;
|
|
54
|
+
for (const c of candidates) {
|
|
55
|
+
const overlap = pos.some((p) => Math.hypot(c.cx - p.cx, c.cy - p.cy) < p.r + r - 0.4);
|
|
56
|
+
if (!overlap) {
|
|
57
|
+
const d = Math.hypot(c.cx, c.cy);
|
|
58
|
+
if (d < bestDist) { bestDist = d; best = c; }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const last = pos[pos.length - 1];
|
|
62
|
+
pos.push({ cx: best?.cx ?? last.cx + last.r + r + 2, cy: best?.cy ?? 0, r });
|
|
63
|
+
}
|
|
64
|
+
return pos.map(({ cx, cy }) => ({ cx, cy }));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
$: layout = (() => {
|
|
68
|
+
if (!items.length) return null;
|
|
69
|
+
const sorted = [...items].sort((a, b) => b.value - a.value);
|
|
70
|
+
const maxV = sorted[0].value;
|
|
71
|
+
const radii = sorted.map((it) => Math.max(MIN_R, Math.round(MAX_R * Math.sqrt(it.value / maxV))));
|
|
72
|
+
const positions = pack(radii);
|
|
73
|
+
|
|
74
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
75
|
+
positions.forEach((p, i) => {
|
|
76
|
+
minX = Math.min(minX, p.cx - radii[i]);
|
|
77
|
+
minY = Math.min(minY, p.cy - radii[i]);
|
|
78
|
+
maxX = Math.max(maxX, p.cx + radii[i]);
|
|
79
|
+
maxY = Math.max(maxY, p.cy + radii[i]);
|
|
80
|
+
});
|
|
81
|
+
const w = maxX - minX, h = maxY - minY;
|
|
82
|
+
const pad = 8;
|
|
83
|
+
const scale = Math.min((VW - pad * 2) / w, (height - pad * 2) / h);
|
|
84
|
+
const offX = (VW - w * scale) / 2 - minX * scale;
|
|
85
|
+
const offY = (height - h * scale) / 2 - minY * scale;
|
|
86
|
+
|
|
87
|
+
return sorted.map((item, i) => {
|
|
88
|
+
const r = radii[i] * scale;
|
|
89
|
+
const cx = positions[i].cx * scale + offX;
|
|
90
|
+
const cy = positions[i].cy * scale + offY;
|
|
91
|
+
const color = item.color ?? PALETTE[i % PALETTE.length];
|
|
92
|
+
return { id: item.id, label: item.label, value: item.value, cx, cy, r, color };
|
|
93
|
+
});
|
|
94
|
+
})();
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
{#if layout}
|
|
98
|
+
<div role="img" aria-label={ariaLabel} style="width:100%;">
|
|
99
|
+
<svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
|
|
100
|
+
{#each layout as c (c.id)}
|
|
101
|
+
<g>
|
|
102
|
+
<circle cx={c.cx} cy={c.cy} r={c.r} fill={c.color} fill-opacity={0.25} stroke={c.color} stroke-width={1.5} />
|
|
103
|
+
{#if c.r >= 18}
|
|
104
|
+
<text x={c.cx} y={c.cy - (c.r >= 26 ? 5 : 0)} text-anchor="middle" dominant-baseline="middle"
|
|
105
|
+
font-size={Math.min(11, c.r * 0.38)} fill="#E2E4ED" font-family="inherit" font-weight={500}>
|
|
106
|
+
{c.label}
|
|
107
|
+
</text>
|
|
108
|
+
{/if}
|
|
109
|
+
{#if c.r >= 26}
|
|
110
|
+
<text x={c.cx} y={c.cy + 10} text-anchor="middle" dominant-baseline="middle"
|
|
111
|
+
font-size={Math.min(9, c.r * 0.3)} fill={c.color} font-family="inherit">
|
|
112
|
+
{c.value}
|
|
113
|
+
</text>
|
|
114
|
+
{/if}
|
|
115
|
+
</g>
|
|
116
|
+
{/each}
|
|
117
|
+
</svg>
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy } from 'svelte';
|
|
3
|
+
export let code: string = '';
|
|
4
|
+
export let label: string = 'Code block';
|
|
5
|
+
export let language: string = '';
|
|
6
|
+
let className = '';
|
|
7
|
+
export { className as class };
|
|
8
|
+
|
|
9
|
+
let copied = false;
|
|
10
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
11
|
+
|
|
12
|
+
function handleCopy() {
|
|
13
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
14
|
+
copied = true;
|
|
15
|
+
if (timer) clearTimeout(timer);
|
|
16
|
+
timer = setTimeout(() => { copied = false; }, 2000);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onDestroy(() => { if (timer) clearTimeout(timer); });
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<div role="region" aria-label={label} class="code-block {className}">
|
|
24
|
+
<div class="code-block__header">
|
|
25
|
+
{#if language}
|
|
26
|
+
<span class="code-block__lang">{language}</span>
|
|
27
|
+
{:else}
|
|
28
|
+
<span></span>
|
|
29
|
+
{/if}
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class="copy-btn{copied ? ' copy-btn--done' : ''}"
|
|
33
|
+
aria-label={copied ? 'Copied!' : 'Copy code'}
|
|
34
|
+
on:click={handleCopy}
|
|
35
|
+
>
|
|
36
|
+
{#if copied}
|
|
37
|
+
<svg viewBox="0 0 14 14" width="14" height="14" fill="none" aria-hidden="true" focusable="false">
|
|
38
|
+
<path d="M2.5 7.5l3.5 3.5L12 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
39
|
+
</svg>
|
|
40
|
+
{:else}
|
|
41
|
+
<svg viewBox="0 0 14 14" width="14" height="14" fill="none" aria-hidden="true" focusable="false">
|
|
42
|
+
<rect x="4" y="1" width="9" height="10" rx="1.5" stroke="currentColor" stroke-width="1.5" />
|
|
43
|
+
<rect x="1" y="4" width="9" height="10" rx="1.5" stroke="currentColor" stroke-width="1.5" />
|
|
44
|
+
</svg>
|
|
45
|
+
{/if}
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
<pre class="code-block__pre"><code class="code-block__code">{code}</code></pre>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
let counter = 0;
|
|
3
|
+
export interface ColorSwatchDef {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
color: string;
|
|
7
|
+
}
|
|
8
|
+
export const COLOR_SWATCHES: ColorSwatchDef[] = [
|
|
9
|
+
{ value: 'blue', label: 'Blue', color: '#006FFF' },
|
|
10
|
+
{ value: 'green', label: 'Green', color: '#00B070' },
|
|
11
|
+
{ value: 'amber', label: 'Amber', color: '#F5A623' },
|
|
12
|
+
{ value: 'red', label: 'Red', color: '#F03A3A' },
|
|
13
|
+
{ value: 'purple', label: 'Purple', color: '#9B59B6' },
|
|
14
|
+
{ value: 'teal', label: 'Teal', color: '#1ABC9C' },
|
|
15
|
+
{ value: 'pink', label: 'Pink', color: '#E91E8C' },
|
|
16
|
+
{ value: 'orange', label: 'Orange', color: '#FF6B35' },
|
|
17
|
+
{ value: 'slate', label: 'Slate', color: '#6E7079' },
|
|
18
|
+
{ value: 'light', label: 'Light', color: '#E8E8EC' },
|
|
19
|
+
];
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
export let label: string = 'Colour';
|
|
24
|
+
export let srOnlyLabel: boolean = false;
|
|
25
|
+
export let value: string = COLOR_SWATCHES[0].value;
|
|
26
|
+
export let swatches: ColorSwatchDef[] = COLOR_SWATCHES;
|
|
27
|
+
export let disabled: boolean = false;
|
|
28
|
+
export let onChange: ((v: string) => void) | undefined = undefined;
|
|
29
|
+
|
|
30
|
+
const groupId = `dash-ui-cp-${++counter}`;
|
|
31
|
+
|
|
32
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
33
|
+
const cells = Array.from(
|
|
34
|
+
(e.currentTarget as HTMLElement).querySelectorAll<HTMLInputElement>('input[type="radio"]'),
|
|
35
|
+
);
|
|
36
|
+
if (!cells.length) return;
|
|
37
|
+
const idx = cells.findIndex((c) => c.value === value);
|
|
38
|
+
let next = idx;
|
|
39
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = (idx + 1) % cells.length;
|
|
40
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp')
|
|
41
|
+
next = (idx - 1 + cells.length) % cells.length;
|
|
42
|
+
else if (e.key === 'Home') next = 0;
|
|
43
|
+
else if (e.key === 'End') next = cells.length - 1;
|
|
44
|
+
else return;
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
cells[next].focus();
|
|
47
|
+
value = cells[next].value;
|
|
48
|
+
onChange?.(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$: hasMatch = swatches.some((s) => s.value === value);
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<div class="color-picker{disabled ? ' color-picker--disabled' : ''}">
|
|
55
|
+
<div id="{groupId}-label" class="color-picker__label{srOnlyLabel ? ' sr-only' : ''}"
|
|
56
|
+
>{label}</div
|
|
57
|
+
>
|
|
58
|
+
<div
|
|
59
|
+
role="radiogroup"
|
|
60
|
+
aria-labelledby="{groupId}-label"
|
|
61
|
+
class="color-picker__grid"
|
|
62
|
+
tabindex="-1"
|
|
63
|
+
on:keydown={handleKeyDown}
|
|
64
|
+
>
|
|
65
|
+
{#each swatches as sw, i}
|
|
66
|
+
{@const checked = sw.value === value}
|
|
67
|
+
{@const tabIdx = checked || (i === 0 && !hasMatch) ? 0 : -1}
|
|
68
|
+
<label class="color-picker__swatch" title={sw.label}>
|
|
69
|
+
<input
|
|
70
|
+
type="radio"
|
|
71
|
+
name={groupId}
|
|
72
|
+
value={sw.value}
|
|
73
|
+
{checked}
|
|
74
|
+
{disabled}
|
|
75
|
+
class="sr-only"
|
|
76
|
+
tabindex={tabIdx}
|
|
77
|
+
aria-label={sw.label}
|
|
78
|
+
on:change={() => {
|
|
79
|
+
value = sw.value;
|
|
80
|
+
onChange?.(value);
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
<span
|
|
84
|
+
class="color-picker__circle{checked ? ' color-picker__circle--selected' : ''}"
|
|
85
|
+
style="background:{sw.color}"
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
/>
|
|
88
|
+
</label>
|
|
89
|
+
{/each}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|