@heyramzi/ui 1.54.2 → 2.0.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/COMPONENTS.md +3 -2
- package/README.md +34 -9
- package/package.json +198 -6
- package/src/ai/generation-progress/GenerationProgress.svelte +12 -12
- package/src/ambient.d.ts +8 -0
- package/src/brand/brand.test.ts +67 -0
- package/src/brand/index.ts +137 -0
- package/src/canvas/README.md +236 -0
- package/src/canvas/card/card-frame.svelte +61 -0
- package/src/canvas/card/card.css +39 -0
- package/src/canvas/card/resize-handles.svelte +119 -0
- package/src/canvas/card/resize.ts +33 -0
- package/src/canvas/chrome/chrome-zoom.svelte +120 -0
- package/src/canvas/chrome/chrome.css +231 -0
- package/src/canvas/chrome/lock-badge.svelte +18 -0
- package/src/canvas/chrome/side-handles.svelte +30 -0
- package/src/canvas/chrome/size-chip.svelte +45 -0
- package/src/canvas/chrome/snap-guides.svelte +69 -0
- package/src/canvas/components/icons/ToolIcon.svelte +30 -0
- package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
- package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
- package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
- package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
- package/src/canvas/edges/bend-handle.svelte +76 -0
- package/src/canvas/edges/edge-gradient.svelte +30 -0
- package/src/canvas/edges/edge-path.test.ts +58 -0
- package/src/canvas/edges/edge-path.ts +293 -0
- package/src/canvas/edges/gradient-edge.svelte +43 -0
- package/src/canvas/editor/context-menu.test.ts +65 -0
- package/src/canvas/editor/context-menu.ts +48 -0
- package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
- package/src/canvas/editor/dispatch.svelte.ts +205 -0
- package/src/canvas/editor/editor-context.ts +62 -0
- package/src/canvas/editor/editor-mode.ts +67 -0
- package/src/canvas/editor/frame.ts +98 -0
- package/src/canvas/editor/hand-context.ts +15 -0
- package/src/canvas/editor/history-core.test.ts +239 -0
- package/src/canvas/editor/history-core.ts +247 -0
- package/src/canvas/editor/keymap.test.ts +161 -0
- package/src/canvas/editor/keymap.ts +151 -0
- package/src/canvas/editor/layout-actions.test.ts +156 -0
- package/src/canvas/editor/layout-actions.ts +219 -0
- package/src/canvas/editor/lock.test.ts +68 -0
- package/src/canvas/editor/lock.ts +87 -0
- package/src/canvas/editor/rebase.test.ts +208 -0
- package/src/canvas/editor/rebase.ts +272 -0
- package/src/canvas/editor/style.ts +184 -0
- package/src/canvas/editor/text-field-context.ts +41 -0
- package/src/canvas/editor/z-order.test.ts +75 -0
- package/src/canvas/editor/z-order.ts +87 -0
- package/src/canvas/headless/alignment.test.ts +59 -0
- package/src/canvas/headless/alignment.ts +23 -0
- package/src/canvas/headless/break-apart-find.ts +418 -0
- package/src/canvas/headless/break-apart-pieces.ts +104 -0
- package/src/canvas/headless/break-apart-plan.ts +439 -0
- package/src/canvas/headless/break-apart-style.ts +106 -0
- package/src/canvas/headless/break-apart.ts +244 -0
- package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
- package/src/canvas/headless/canvas-cleanup.ts +102 -0
- package/src/canvas/headless/edge-anchors.test.ts +70 -0
- package/src/canvas/headless/edge-anchors.ts +143 -0
- package/src/canvas/headless/edge-memo.test.ts +58 -0
- package/src/canvas/headless/edge-memo.ts +32 -0
- package/src/canvas/headless/edge-serialization.test.ts +55 -0
- package/src/canvas/headless/edge-serialization.ts +32 -0
- package/src/canvas/headless/figure-geometry.ts +293 -0
- package/src/canvas/headless/fit-viewport.test.ts +85 -0
- package/src/canvas/headless/fit-viewport.ts +137 -0
- package/src/canvas/headless/grid.test.ts +34 -0
- package/src/canvas/headless/grid.ts +94 -0
- package/src/canvas/headless/icon-node.test.ts +23 -0
- package/src/canvas/headless/icon-node.ts +66 -0
- package/src/canvas/headless/index.ts +9 -0
- package/src/canvas/headless/linkify.test.ts +23 -0
- package/src/canvas/headless/linkify.ts +25 -0
- package/src/canvas/headless/pen-marks.ts +133 -0
- package/src/canvas/headless/sketch.test.ts +99 -0
- package/src/canvas/headless/sketch.ts +302 -0
- package/src/canvas/headless/snap-guides.test.ts +61 -0
- package/src/canvas/headless/snap-guides.ts +146 -0
- package/src/canvas/headless/svg-figure.test.ts +49 -0
- package/src/canvas/headless/svg-figure.ts +176 -0
- package/src/canvas/headless/table-layout.test.ts +158 -0
- package/src/canvas/headless/table-layout.ts +309 -0
- package/src/canvas/index.ts +35 -0
- package/src/canvas/lod/lod-mode.svelte.ts +30 -0
- package/src/canvas/lod/lod-switch.svelte +140 -0
- package/src/canvas/lod/render-all.svelte.ts +52 -0
- package/src/canvas/ops/arrange.ts +108 -0
- package/src/canvas/ops/ops.test.ts +98 -0
- package/src/canvas/ops/ops.ts +347 -0
- package/src/canvas/pen/PenField.svelte +128 -0
- package/src/canvas/pen/PenField.test.ts +87 -0
- package/src/canvas/pen/PenOverlay.svelte +172 -0
- package/src/canvas/pen/ShapeNode.svelte +64 -0
- package/src/canvas/pen/TextNode.svelte +97 -0
- package/src/canvas/pen/colors.ts +28 -0
- package/src/canvas/pen/eraser.ts +46 -0
- package/src/canvas/pen/geometry.ts +148 -0
- package/src/canvas/pen/host-field.test.svelte +13 -0
- package/src/canvas/pen/index.ts +40 -0
- package/src/canvas/pen/ops.ts +16 -0
- package/src/canvas/pen/pen-session.ts +177 -0
- package/src/canvas/pen/pen.css +149 -0
- package/src/canvas/pen/plugin.ts +139 -0
- package/src/canvas/pen/shapes.ts +104 -0
- package/src/canvas/pen/stroke-path.ts +54 -0
- package/src/canvas/pen/types.ts +96 -0
- package/src/canvas/pen/ui.ts +26 -0
- package/src/canvas/plugin.test.ts +45 -0
- package/src/canvas/plugin.ts +159 -0
- package/src/canvas/screen/GlyphIcon.svelte +25 -0
- package/src/canvas/screen/PartView.svelte +176 -0
- package/src/canvas/screen/ScreenBody.svelte +142 -0
- package/src/canvas/screen/ScreenCard.svelte +32 -0
- package/src/canvas/screen/glyphs/button.svg +1 -0
- package/src/canvas/screen/glyphs/close.svg +1 -0
- package/src/canvas/screen/glyphs/down.svg +1 -0
- package/src/canvas/screen/glyphs/header.svg +1 -0
- package/src/canvas/screen/glyphs/hills.svg +1 -0
- package/src/canvas/screen/glyphs/image.svg +1 -0
- package/src/canvas/screen/glyphs/input.svg +1 -0
- package/src/canvas/screen/glyphs/list.svg +1 -0
- package/src/canvas/screen/glyphs/modal.svg +1 -0
- package/src/canvas/screen/glyphs/plus.svg +1 -0
- package/src/canvas/screen/glyphs/up.svg +1 -0
- package/src/canvas/screen/glyphs.ts +40 -0
- package/src/canvas/screen/labels.ts +41 -0
- package/src/canvas/screen/parts.ts +164 -0
- package/src/canvas/screen/plugin.ts +67 -0
- package/src/canvas/screen/screen.css +593 -0
- package/src/canvas/screen/types.ts +47 -0
- package/src/canvas/screen/ui.ts +13 -0
- package/src/canvas/test-utils.ts +18 -0
- package/src/canvas/types.ts +53 -0
- package/src/canvas/ui/CanvasToolbar.svelte +169 -0
- package/src/canvas/ui/FrameLayer.svelte +116 -0
- package/src/canvas/ui/PenLayer.svelte +39 -0
- package/src/canvas/ui/StylePanel.svelte +268 -0
- package/src/canvas/ui/canvas-grid.svelte +156 -0
- package/src/canvas/ui/canvas-ui.css +350 -0
- package/src/canvas/ui/forward-wheel.ts +29 -0
- package/src/canvas/ui/icons.ts +124 -0
- package/src/canvas/ui/place-panel.ts +80 -0
- package/src/canvas/ui/shortcut-tip.test.ts +16 -0
- package/src/canvas/ui/shortcut-tip.ts +17 -0
- package/src/canvas/ui/tools.ts +146 -0
- package/src/charts/README.md +104 -0
- package/src/charts/ambient.d.ts +4 -0
- package/src/charts/animated-value.css +7 -0
- package/src/charts/animated-value.svelte +110 -0
- package/src/charts/area-chart.css +29 -0
- package/src/charts/area-chart.svelte +204 -0
- package/src/charts/bar-chart.css +119 -0
- package/src/charts/bar-chart.svelte +275 -0
- package/src/charts/charts.css +201 -0
- package/src/charts/crosshair.svelte +35 -0
- package/src/charts/dashboard/chart.ts +5 -0
- package/src/charts/dashboard/dashboard-empty.svelte +55 -0
- package/src/charts/dashboard/dashboard-section.svelte +58 -0
- package/src/charts/dashboard/data-panel.svelte +81 -0
- package/src/charts/dashboard/data-sort-head.svelte +102 -0
- package/src/charts/dashboard/data-table.svelte +278 -0
- package/src/charts/dashboard/data-table.ts +26 -0
- package/src/charts/dashboard/index.ts +25 -0
- package/src/charts/dashboard/panel-grid.svelte +43 -0
- package/src/charts/dashboard/period-filter.svelte +53 -0
- package/src/charts/dashboard/rating-chip.svelte +35 -0
- package/src/charts/dashboard/stat-band.svelte +47 -0
- package/src/charts/dashboard/stat-tile.svelte +176 -0
- package/src/charts/dashboard/windows.ts +8 -0
- package/src/charts/donut-chart.css +56 -0
- package/src/charts/donut-chart.svelte +158 -0
- package/src/charts/edge-fade.svelte +43 -0
- package/src/charts/edge-fade.ts +16 -0
- package/src/charts/figure.css +100 -0
- package/src/charts/figure.svelte +122 -0
- package/src/charts/gauge-chart.css +94 -0
- package/src/charts/gauge-chart.svelte +302 -0
- package/src/charts/hover-band.test.ts +30 -0
- package/src/charts/hover-band.ts +21 -0
- package/src/charts/hover-focus.svelte +85 -0
- package/src/charts/index.ts +31 -0
- package/src/charts/legend.css +136 -0
- package/src/charts/legend.svelte +112 -0
- package/src/charts/line-chart.css +26 -0
- package/src/charts/line-chart.svelte +256 -0
- package/src/charts/motion.ts +19 -0
- package/src/charts/number-chart.css +179 -0
- package/src/charts/number-chart.svelte +187 -0
- package/src/charts/primitives/chart-container.svelte +57 -0
- package/src/charts/primitives/chart-style.svelte +36 -0
- package/src/charts/primitives/chart-tooltip.svelte +161 -0
- package/src/charts/primitives/chart-utils.ts +54 -0
- package/src/charts/primitives/index.ts +11 -0
- package/src/charts/reference-line.css +18 -0
- package/src/charts/reference-line.svelte +31 -0
- package/src/charts/reveal.svelte +61 -0
- package/src/charts/ring-chart.css +104 -0
- package/src/charts/ring-chart.svelte +166 -0
- package/src/charts/sparkline.css +45 -0
- package/src/charts/sparkline.svelte +141 -0
- package/src/charts/stat-card.css +105 -0
- package/src/charts/stat-card.svelte +267 -0
- package/src/charts/time-series-chart.css +28 -0
- package/src/charts/time-series-chart.svelte +179 -0
- package/src/charts/trend-pill.css +38 -0
- package/src/charts/trend-pill.svelte +48 -0
- package/src/components/copy/copy-button.svelte +1 -1
- package/src/components/docs/doc-page.svelte +1 -1
- package/src/components/docs/on-this-page.svelte +2 -2
- package/src/components/list-page/empty-state.svelte +1 -1
- package/src/components/list-page/filter-popover.svelte +1 -1
- package/src/components/list-page/section-label.svelte +1 -1
- package/src/components/segmented/segment.svelte +4 -4
- package/src/components/theme/theme-provider.svelte +6 -4
- package/src/components/toolbar/index.ts +5 -0
- package/src/components/toolbar/toolbar.svelte +168 -0
- package/src/components/toolbar/toolbar.test.ts +66 -0
- package/src/editor/README.md +58 -0
- package/src/editor/ambient.d.ts +1 -0
- package/src/editor/block-id.ts +77 -0
- package/src/editor/block-ops.test.ts +237 -0
- package/src/editor/block-ops.ts +157 -0
- package/src/editor/content.css +418 -0
- package/src/editor/editor-toolbar.svelte +303 -0
- package/src/editor/editor.svelte +462 -0
- package/src/editor/extensions.ts +145 -0
- package/src/editor/image-toolbar.svelte +123 -0
- package/src/editor/index.ts +17 -0
- package/src/editor/inline-editor.svelte +261 -0
- package/src/editor/markdown-escaping.test.ts +158 -0
- package/src/editor/markdown-marks.test.ts +89 -0
- package/src/editor/markdown-test-helpers.ts +13 -0
- package/src/editor/markdown-youtube.test.ts +93 -0
- package/src/editor/markdown.test.ts +250 -0
- package/src/editor/markdown.ts +511 -0
- package/src/editor/renderer.svelte +92 -0
- package/src/editor/serializer.test.ts +133 -0
- package/src/editor/serializer.ts +370 -0
- package/src/editor/slash-items.ts +115 -0
- package/src/editor/slash-menu.svelte +159 -0
- package/src/editor/youtube-url.ts +35 -0
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
- package/src/lib/result.ts +19 -0
- package/src/onboarding/README.md +120 -0
- package/src/onboarding/define-tours.test.ts +50 -0
- package/src/onboarding/define-tours.ts +31 -0
- package/src/onboarding/demo.svelte +86 -0
- package/src/onboarding/index.ts +12 -0
- package/src/onboarding/onboarding-provider.svelte +120 -0
- package/src/onboarding/onboarding.svelte.ts +24 -0
- package/src/onboarding/pick.test.ts +79 -0
- package/src/onboarding/pick.ts +36 -0
- package/src/onboarding/stores.test.ts +103 -0
- package/src/onboarding/stores.ts +58 -0
- package/src/onboarding/tour-card.svelte +70 -0
- package/src/onboarding/tour-runner.svelte +197 -0
- package/src/onboarding/types.ts +34 -0
- package/src/onboarding/wait.ts +29 -0
- package/src/shadcn/command/command-input.svelte +1 -1
- package/src/slides/Controls.svelte +138 -0
- package/src/slides/Deck.svelte +271 -0
- package/src/slides/Fragment.svelte +71 -0
- package/src/slides/Overview.svelte +113 -0
- package/src/slides/Presenter.svelte +292 -0
- package/src/slides/README.md +89 -0
- package/src/slides/Slide.svelte +179 -0
- package/src/slides/SlideList.svelte +19 -0
- package/src/slides/ambient.d.ts +1 -0
- package/src/slides/bridges.ts +69 -0
- package/src/slides/deck-controller.svelte.ts +292 -0
- package/src/slides/demo-slides.svelte +358 -0
- package/src/slides/demo.svelte +176 -0
- package/src/slides/export/capture.ts +113 -0
- package/src/slides/export/ffmpeg.ts +120 -0
- package/src/slides/export/index.ts +9 -0
- package/src/slides/export/launch.ts +10 -0
- package/src/slides/export/pdf.ts +152 -0
- package/src/slides/export/types.ts +48 -0
- package/src/slides/index.ts +19 -0
- package/src/slides/labels.ts +48 -0
- package/src/slides/layout.ts +51 -0
- package/src/slides/model.ts +47 -0
- package/src/slides/navigation.ts +100 -0
- package/src/slides/theme.css +205 -0
- package/src/slides/types.ts +86 -0
- package/src/tasks/README.md +312 -0
- package/src/tasks/ambient.d.ts +3 -0
- package/src/tasks/board-order.ts +137 -0
- package/src/tasks/dates.ts +31 -0
- package/src/tasks/define-statuses.ts +32 -0
- package/src/tasks/demo.svelte +389 -0
- package/src/tasks/detail/detail-demo.svelte +186 -0
- package/src/tasks/detail/detail-types.ts +54 -0
- package/src/tasks/detail/task-chip.svelte +28 -0
- package/src/tasks/detail/task-detail.svelte +315 -0
- package/src/tasks/detail/task-property.svelte +17 -0
- package/src/tasks/detail/task-thread.svelte +258 -0
- package/src/tasks/gantt/gantt-bar.svelte +243 -0
- package/src/tasks/gantt/gantt-grid.svelte +94 -0
- package/src/tasks/gantt/gantt-group-row.svelte +55 -0
- package/src/tasks/gantt/gantt-header.svelte +71 -0
- package/src/tasks/gantt/gantt-task-row.svelte +186 -0
- package/src/tasks/gantt/gantt.css +22 -0
- package/src/tasks/gantt/gantt.test.ts +77 -0
- package/src/tasks/gantt/logic.test.ts +163 -0
- package/src/tasks/gantt/logic.ts +248 -0
- package/src/tasks/inbox/inbox-card.svelte +283 -0
- package/src/tasks/inbox/inbox-draft.svelte +84 -0
- package/src/tasks/inbox/inbox-logic.ts +55 -0
- package/src/tasks/inbox/inbox-statuses.ts +48 -0
- package/src/tasks/inbox/inbox-trace.svelte +45 -0
- package/src/tasks/inbox/inbox-types.ts +108 -0
- package/src/tasks/inbox/inbox.svelte +203 -0
- package/src/tasks/inbox/index.ts +23 -0
- package/src/tasks/index.ts +85 -0
- package/src/tasks/kanban-board.svelte +561 -0
- package/src/tasks/kanban-column.svelte +117 -0
- package/src/tasks/kanban-drag.ts +38 -0
- package/src/tasks/kanban-types.ts +65 -0
- package/src/tasks/presets/sales.ts +16 -0
- package/src/tasks/record/demo-data.ts +286 -0
- package/src/tasks/record/field-section.svelte +53 -0
- package/src/tasks/record/fields.ts +142 -0
- package/src/tasks/record/format.ts +87 -0
- package/src/tasks/record/gutter.ts +2 -0
- package/src/tasks/record/index.ts +54 -0
- package/src/tasks/record/info-list.svelte +316 -0
- package/src/tasks/record/kinds.ts +42 -0
- package/src/tasks/record/logic.ts +324 -0
- package/src/tasks/record/next-up.svelte +70 -0
- package/src/tasks/record/project-list.svelte +133 -0
- package/src/tasks/record/record-demo.svelte +324 -0
- package/src/tasks/record/record-header.svelte +70 -0
- package/src/tasks/record/record-shell.svelte +138 -0
- package/src/tasks/record/record-stats.svelte +48 -0
- package/src/tasks/record/record-timeline.svelte +231 -0
- package/src/tasks/record/record.test.ts +350 -0
- package/src/tasks/record/steps-rail.svelte +474 -0
- package/src/tasks/record/types.ts +280 -0
- package/src/tasks/record/where-we-are.svelte +121 -0
- package/src/tasks/reorder.ts +19 -0
- package/src/tasks/side-panel-types.ts +16 -0
- package/src/tasks/side-panel.svelte +193 -0
- package/src/tasks/status-badge.svelte +63 -0
- package/src/tasks/status-dot.svelte +228 -0
- package/src/tasks/status-icon.svelte +30 -0
- package/src/tasks/status-types.ts +40 -0
- package/src/tasks/task-gantt.svelte +230 -0
- package/src/tasks/task-list.svelte +470 -0
- package/src/tasks/task-types.ts +33 -0
- package/src/tasks/time/duration.ts +69 -0
- package/src/tasks/time/time-chip.svelte +62 -0
- package/src/tasks/time/time-dot.svelte +58 -0
- package/src/tasks/time/time-ring.svelte +74 -0
- package/src/tasks/time/time-tracker.svelte +565 -0
- package/src/tasks/time/time-types.ts +74 -0
- package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
- package/src/tokens/base.css +5 -5
- package/src/tokens/palette.css +115 -120
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Styles for DonutChart. Plain stylesheet (see area-chart.css for why).
|
|
2
|
+
Selectors are namespaced under `.hr-donut-chart`. */
|
|
3
|
+
|
|
4
|
+
.hr-donut-chart {
|
|
5
|
+
width: 100%;
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.hr-donut-chart .empty {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
width: 100%;
|
|
15
|
+
border: 1px dashed var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
|
|
16
|
+
border-radius: 0.5rem;
|
|
17
|
+
}
|
|
18
|
+
.hr-donut-chart .empty p {
|
|
19
|
+
font-size: 0.8125rem;
|
|
20
|
+
color: var(--chart-label, oklch(0.5 0.01 286));
|
|
21
|
+
margin: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* The slice slides out a little slower than it slides back: a close is always the quicker. */
|
|
25
|
+
.hr-donut-chart .hr-donut-arc {
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition:
|
|
28
|
+
opacity var(--chart-motion-fast, 150ms) ease,
|
|
29
|
+
translate var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
|
|
30
|
+
}
|
|
31
|
+
.hr-donut-chart .hr-donut-arc[data-hovered] {
|
|
32
|
+
transition:
|
|
33
|
+
opacity var(--chart-motion-fast, 150ms) ease,
|
|
34
|
+
translate var(--duration-fast, 250ms) var(--ease-smooth-out);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.hr-donut-chart .hr-donut-center-value {
|
|
38
|
+
fill: var(--foreground, oklch(0.18 0.02 240));
|
|
39
|
+
font-size: 16px;
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
font-feature-settings: "tnum";
|
|
42
|
+
transition: opacity 180ms ease;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.hr-donut-chart .hr-donut-center-label {
|
|
46
|
+
fill: var(--chart-label, oklch(0.5 0.01 286));
|
|
47
|
+
font-size: 11px;
|
|
48
|
+
transition: opacity 180ms ease;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (prefers-reduced-motion: reduce) {
|
|
52
|
+
.hr-donut-chart .hr-donut-arc,
|
|
53
|
+
.hr-donut-chart .hr-donut-arc[data-hovered] {
|
|
54
|
+
transition: opacity 120ms ease;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import "./donut-chart.css";
|
|
6
|
+
import { Arc, Chart, Group, Pie, Svg, Text } from "layerchart";
|
|
7
|
+
import { scaleOrdinal } from "d3-scale";
|
|
8
|
+
import { expoInOut } from "svelte/easing";
|
|
9
|
+
import { prefersReducedMotion, Tween } from "svelte/motion";
|
|
10
|
+
import { REVEAL_MS } from "./motion.js";
|
|
11
|
+
|
|
12
|
+
// =========================
|
|
13
|
+
// TYPES
|
|
14
|
+
// =========================
|
|
15
|
+
interface Props {
|
|
16
|
+
data: TData[];
|
|
17
|
+
valueKey: keyof TData & string;
|
|
18
|
+
labelKey: keyof TData & string;
|
|
19
|
+
colorKey?: keyof TData & string;
|
|
20
|
+
height?: number;
|
|
21
|
+
innerRadius?: number;
|
|
22
|
+
outerRadius?: number;
|
|
23
|
+
/** Center label shown when nothing is hovered */
|
|
24
|
+
centerLabel?: string;
|
|
25
|
+
/** Format the center value */
|
|
26
|
+
formatValue?: (v: number) => string;
|
|
27
|
+
class?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// =========================
|
|
31
|
+
// PROPS
|
|
32
|
+
// =========================
|
|
33
|
+
let {
|
|
34
|
+
data,
|
|
35
|
+
valueKey,
|
|
36
|
+
labelKey,
|
|
37
|
+
colorKey,
|
|
38
|
+
height = 240,
|
|
39
|
+
innerRadius = 60,
|
|
40
|
+
outerRadius = 100,
|
|
41
|
+
centerLabel,
|
|
42
|
+
formatValue = (v) => v.toLocaleString(),
|
|
43
|
+
class: className,
|
|
44
|
+
}: Props = $props();
|
|
45
|
+
|
|
46
|
+
// =========================
|
|
47
|
+
// STATE
|
|
48
|
+
// =========================
|
|
49
|
+
let hovered = $state<string | null>(null);
|
|
50
|
+
// Milliseconds into the entrance; each slice sweeps on its own offset.
|
|
51
|
+
const clock = new Tween(0);
|
|
52
|
+
let swept = $state(false);
|
|
53
|
+
let started = false;
|
|
54
|
+
|
|
55
|
+
// =========================
|
|
56
|
+
// FUNCTIONS
|
|
57
|
+
// =========================
|
|
58
|
+
const SLICE_DELAY = 100;
|
|
59
|
+
const SLICE_STEP = 80;
|
|
60
|
+
const HOVER_OFFSET = 6;
|
|
61
|
+
|
|
62
|
+
function sweep(i: number): number {
|
|
63
|
+
if (swept) return 1;
|
|
64
|
+
const t = (clock.current - SLICE_DELAY - i * SLICE_STEP) / REVEAL_MS;
|
|
65
|
+
return expoInOut(Math.min(1, Math.max(0, t)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// =========================
|
|
69
|
+
// EFFECTS
|
|
70
|
+
// =========================
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (started || filtered.length === 0) return;
|
|
73
|
+
started = true;
|
|
74
|
+
const end = SLICE_DELAY + (filtered.length - 1) * SLICE_STEP + REVEAL_MS;
|
|
75
|
+
const duration = prefersReducedMotion.current ? 0 : end;
|
|
76
|
+
clock.set(end, { duration }).then(() => (swept = true));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// =========================
|
|
80
|
+
// DERIVED
|
|
81
|
+
// =========================
|
|
82
|
+
const filtered = $derived(data.filter((d) => Number(d[valueKey] ?? 0) > 0));
|
|
83
|
+
const total = $derived(filtered.reduce((sum, d) => sum + Number(d[valueKey] ?? 0), 0));
|
|
84
|
+
const idAccessor = $derived((d: TData) => String(d[labelKey] ?? ""));
|
|
85
|
+
const colorRange = $derived(colorKey ? filtered.map((d) => String(d[colorKey] ?? "")) : []);
|
|
86
|
+
const colorDomain = $derived(filtered.map(idAccessor));
|
|
87
|
+
const hoveredItem = $derived(filtered.find((d) => idAccessor(d) === hovered) ?? null);
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<!-- =========================== -->
|
|
91
|
+
<!-- DONUT CHART -->
|
|
92
|
+
<!-- =========================== -->
|
|
93
|
+
<div
|
|
94
|
+
class="hr-donut-chart {className ?? ''}"
|
|
95
|
+
style:height="{height}px"
|
|
96
|
+
role="img"
|
|
97
|
+
aria-label="{centerLabel ?? 'Donut chart'}: {formatValue(total)} total"
|
|
98
|
+
>
|
|
99
|
+
{#if filtered.length === 0}
|
|
100
|
+
<div class="empty">
|
|
101
|
+
<p>No data</p>
|
|
102
|
+
</div>
|
|
103
|
+
{:else}
|
|
104
|
+
<Chart
|
|
105
|
+
data={filtered}
|
|
106
|
+
x={valueKey}
|
|
107
|
+
c={colorKey ? idAccessor : undefined}
|
|
108
|
+
cScale={scaleOrdinal()}
|
|
109
|
+
cDomain={colorDomain}
|
|
110
|
+
cRange={colorRange}
|
|
111
|
+
>
|
|
112
|
+
<Svg>
|
|
113
|
+
<Group center>
|
|
114
|
+
<Pie {innerRadius} {outerRadius} cornerRadius={3} padAngle={0.02} sort={null}>
|
|
115
|
+
{#snippet children({ arcs })}
|
|
116
|
+
{#each arcs as arc, i (i)}
|
|
117
|
+
{@const id = idAccessor(arc.data as TData)}
|
|
118
|
+
{@const isHovered = hovered === id}
|
|
119
|
+
{@const dimmed = hovered !== null && !isHovered}
|
|
120
|
+
{@const mid = (arc.startAngle + arc.endAngle) / 2}
|
|
121
|
+
{@const dx = isHovered ? Math.sin(mid) * HOVER_OFFSET : 0}
|
|
122
|
+
{@const dy = isHovered ? -Math.cos(mid) * HOVER_OFFSET : 0}
|
|
123
|
+
<Arc
|
|
124
|
+
startAngle={arc.startAngle}
|
|
125
|
+
endAngle={arc.startAngle + (arc.endAngle - arc.startAngle) * sweep(i)}
|
|
126
|
+
padAngle={arc.padAngle}
|
|
127
|
+
{innerRadius}
|
|
128
|
+
{outerRadius}
|
|
129
|
+
cornerRadius={3}
|
|
130
|
+
fill={colorKey
|
|
131
|
+
? String((arc.data as TData)[colorKey])
|
|
132
|
+
: `var(--chart-${(i % 5) + 1})`}
|
|
133
|
+
class="hr-donut-arc"
|
|
134
|
+
data-hovered={isHovered ? "" : undefined}
|
|
135
|
+
style="opacity: {dimmed ? 0.4 : 1}; translate: {dx}px {dy}px"
|
|
136
|
+
onmouseenter={() => (hovered = id)}
|
|
137
|
+
onmouseleave={() => (hovered = null)}
|
|
138
|
+
/>
|
|
139
|
+
{/each}
|
|
140
|
+
{/snippet}
|
|
141
|
+
</Pie>
|
|
142
|
+
<Text
|
|
143
|
+
value={hoveredItem ? formatValue(Number(hoveredItem[valueKey])) : formatValue(total)}
|
|
144
|
+
textAnchor="middle"
|
|
145
|
+
dy={-4}
|
|
146
|
+
class="hr-donut-center-value"
|
|
147
|
+
/>
|
|
148
|
+
<Text
|
|
149
|
+
value={hoveredItem ? String(hoveredItem[labelKey]) : (centerLabel ?? "Total")}
|
|
150
|
+
textAnchor="middle"
|
|
151
|
+
dy={14}
|
|
152
|
+
class="hr-donut-center-label"
|
|
153
|
+
/>
|
|
154
|
+
</Group>
|
|
155
|
+
</Svg>
|
|
156
|
+
</Chart>
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import { EDGE_FADE_DEFAULT } from "./edge-fade.js";
|
|
6
|
+
|
|
7
|
+
// =========================
|
|
8
|
+
// TYPES
|
|
9
|
+
// =========================
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Mask id. Apply it to the shape with `mask="url(#id)"`. */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Fraction of the shape's width faded at each side, 0-0.45. */
|
|
14
|
+
amount?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// =========================
|
|
18
|
+
// PROPS
|
|
19
|
+
// =========================
|
|
20
|
+
let { id, amount = EDGE_FADE_DEFAULT }: Props = $props();
|
|
21
|
+
|
|
22
|
+
// =========================
|
|
23
|
+
// DERIVED
|
|
24
|
+
// =========================
|
|
25
|
+
const stop = $derived(Math.min(0.45, Math.max(0, amount)));
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<!-- =========================== -->
|
|
29
|
+
<!-- EDGE FADE -->
|
|
30
|
+
<!-- =========================== -->
|
|
31
|
+
<!-- WHY: the fill gradient of an area is vertical, so the horizontal fade cannot ride on it. A luminance mask in objectBoundingBox units needs no pixel -->
|
|
32
|
+
<!-- dimensions: the ramp is expressed as a fraction of the masked path's own box, so it follows the plot width through every resize. -->
|
|
33
|
+
<defs>
|
|
34
|
+
<linearGradient id="{id}-ramp" x1="0" y1="0" x2="1" y2="0">
|
|
35
|
+
<stop offset="0" stop-color="#fff" stop-opacity="0" />
|
|
36
|
+
<stop offset={stop} stop-color="#fff" stop-opacity="1" />
|
|
37
|
+
<stop offset={1 - stop} stop-color="#fff" stop-opacity="1" />
|
|
38
|
+
<stop offset="1" stop-color="#fff" stop-opacity="0" />
|
|
39
|
+
</linearGradient>
|
|
40
|
+
<mask {id} maskContentUnits="objectBoundingBox">
|
|
41
|
+
<rect x="0" y="0" width="1" height="1" fill="url(#{id}-ramp)" />
|
|
42
|
+
</mask>
|
|
43
|
+
</defs>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** `true` fades this much of the width at each side. */
|
|
2
|
+
export const EDGE_FADE_DEFAULT = 0.08;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Turns an `edgeFade` prop into the fraction of the width to fade at each side.
|
|
6
|
+
* `false` (or 0) means no mask is rendered at all.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveEdgeFade(
|
|
9
|
+
value: boolean | number | undefined,
|
|
10
|
+
fallback = EDGE_FADE_DEFAULT,
|
|
11
|
+
): number {
|
|
12
|
+
if (value === false) return 0;
|
|
13
|
+
if (value === true || value == null) return fallback;
|
|
14
|
+
if (!Number.isFinite(value)) return 0;
|
|
15
|
+
return Math.min(0.45, Math.max(0, value));
|
|
16
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* Styles for Figure. Plain stylesheet (see area-chart.css for why). All
|
|
2
|
+
selectors are already namespaced under `.hr-figure`. */
|
|
3
|
+
|
|
4
|
+
.hr-figure {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
gap: 0.1875rem;
|
|
8
|
+
min-width: 0;
|
|
9
|
+
}
|
|
10
|
+
.hr-figure[data-align="center"] {
|
|
11
|
+
align-items: center;
|
|
12
|
+
text-align: center;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* WHY the title truncates: title and pill share one narrow row (the gauge card
|
|
16
|
+
leaves it ~160px), and a shrunken title box let the word paint over the pill. */
|
|
17
|
+
.hr-figure-head {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
gap: 0.75rem;
|
|
22
|
+
}
|
|
23
|
+
.hr-figure-title {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 0.4375rem;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
font-size: 0.9375rem;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
color: var(--foreground, oklch(0.2 0.01 286));
|
|
31
|
+
letter-spacing: -0.005em;
|
|
32
|
+
}
|
|
33
|
+
.hr-figure-title-text {
|
|
34
|
+
min-width: 0;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
}
|
|
39
|
+
.hr-figure-icon {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.hr-figure-values {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-wrap: wrap;
|
|
48
|
+
gap: 0.75rem;
|
|
49
|
+
align-items: baseline;
|
|
50
|
+
}
|
|
51
|
+
.hr-figure-value-row {
|
|
52
|
+
display: inline-flex;
|
|
53
|
+
align-items: baseline;
|
|
54
|
+
gap: 0.375rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.hr-figure-value {
|
|
58
|
+
font-feature-settings: "tnum";
|
|
59
|
+
line-height: 1.1;
|
|
60
|
+
transition: color 180ms ease;
|
|
61
|
+
}
|
|
62
|
+
/* md: chart header (monospace, compact). */
|
|
63
|
+
.hr-figure[data-size="md"] .hr-figure-value {
|
|
64
|
+
font-size: 1.25rem;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
67
|
+
}
|
|
68
|
+
/* lg: KPI card headline. */
|
|
69
|
+
.hr-figure[data-size="lg"] .hr-figure-value {
|
|
70
|
+
font-size: 1.875rem;
|
|
71
|
+
font-weight: 700;
|
|
72
|
+
line-height: 1;
|
|
73
|
+
letter-spacing: -0.025em;
|
|
74
|
+
color: var(--foreground, oklch(0.2 0.01 286));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hr-figure-series-label {
|
|
78
|
+
font-size: 0.6875rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* The readable form of a series colour: see the note in figure.svelte. The wash
|
|
82
|
+
and the line keep the raw hue; only text is pulled toward the foreground. */
|
|
83
|
+
.hr-figure-value.is-series,
|
|
84
|
+
.hr-figure-series-label.is-series {
|
|
85
|
+
color: color-mix(
|
|
86
|
+
in oklab,
|
|
87
|
+
var(--hr-series, currentColor) 70%,
|
|
88
|
+
var(--foreground, oklch(0.2 0.01 286))
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
.hr-figure-caption {
|
|
92
|
+
font-size: 0.75rem;
|
|
93
|
+
color: var(--muted-foreground, oklch(0.5 0.01 286));
|
|
94
|
+
}
|
|
95
|
+
.hr-figure-eyebrow {
|
|
96
|
+
font-size: 0.6875rem;
|
|
97
|
+
color: var(--chart-label, var(--muted-foreground, oklch(0.5 0.01 286)));
|
|
98
|
+
text-transform: uppercase;
|
|
99
|
+
letter-spacing: 0.04em;
|
|
100
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// =========================
|
|
3
|
+
// IMPORTS
|
|
4
|
+
// =========================
|
|
5
|
+
import "./figure.css";
|
|
6
|
+
import { cn } from "../lib/utils.js";
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
import AnimatedValue from "./animated-value.svelte";
|
|
9
|
+
import TrendPill from "./trend-pill.svelte";
|
|
10
|
+
|
|
11
|
+
// =========================
|
|
12
|
+
// TYPES
|
|
13
|
+
// =========================
|
|
14
|
+
type Trend = "up" | "down" | "neutral";
|
|
15
|
+
type SeriesValue = { value: number; label?: string; color?: string };
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
/** Single metric value. Numbers count up via AnimatedValue. Ignored when `series` is set. */
|
|
19
|
+
value?: number | string;
|
|
20
|
+
/** Multiple value rows (chart headers with more than one series). */
|
|
21
|
+
series?: SeriesValue[];
|
|
22
|
+
/** Single-value color. */
|
|
23
|
+
color?: string;
|
|
24
|
+
/** Title above the value (KPI card). Pairs with `delta` for a trend pill. */
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Percentage change; renders a TrendPill beside the title. */
|
|
27
|
+
delta?: number;
|
|
28
|
+
trend?: Trend;
|
|
29
|
+
/** Sub-caption under the value. */
|
|
30
|
+
caption?: string;
|
|
31
|
+
/** Uppercase context label under the value (chart headers, e.g. "JUN"). */
|
|
32
|
+
eyebrow?: string;
|
|
33
|
+
/** Left header (md, monospace) vs centered overlay reuse later. */
|
|
34
|
+
align?: "start" | "center";
|
|
35
|
+
/** Value scale: md = chart header, lg = KPI card. */
|
|
36
|
+
size?: "md" | "lg";
|
|
37
|
+
format?: (v: number) => string;
|
|
38
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
39
|
+
locale?: string;
|
|
40
|
+
animate?: boolean;
|
|
41
|
+
/** Leading icon before the title. */
|
|
42
|
+
icon?: Snippet;
|
|
43
|
+
class?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// =========================
|
|
47
|
+
// PROPS
|
|
48
|
+
// =========================
|
|
49
|
+
let {
|
|
50
|
+
value,
|
|
51
|
+
series,
|
|
52
|
+
color,
|
|
53
|
+
title,
|
|
54
|
+
delta,
|
|
55
|
+
trend,
|
|
56
|
+
caption,
|
|
57
|
+
eyebrow,
|
|
58
|
+
align = "start",
|
|
59
|
+
size = "md",
|
|
60
|
+
format,
|
|
61
|
+
formatOptions,
|
|
62
|
+
locale,
|
|
63
|
+
animate = true,
|
|
64
|
+
icon,
|
|
65
|
+
class: className,
|
|
66
|
+
}: Props = $props();
|
|
67
|
+
|
|
68
|
+
// =========================
|
|
69
|
+
// DERIVED
|
|
70
|
+
// =========================
|
|
71
|
+
const rows = $derived(series && series.length > 0 ? series : null);
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<!-- =========================== -->
|
|
75
|
+
<!-- FIGURE -->
|
|
76
|
+
<!-- =========================== -->
|
|
77
|
+
<div class={cn("hr-figure", className)} data-align={align} data-size={size}>
|
|
78
|
+
{#if title}
|
|
79
|
+
<div class="hr-figure-head">
|
|
80
|
+
<span class="hr-figure-title">
|
|
81
|
+
{#if icon}<span class="hr-figure-icon" style:color>{@render icon()}</span>{/if}
|
|
82
|
+
<span class="hr-figure-title-text">{title}</span>
|
|
83
|
+
</span>
|
|
84
|
+
{#if delta != null}
|
|
85
|
+
<TrendPill {delta} {trend} />
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
88
|
+
{/if}
|
|
89
|
+
|
|
90
|
+
{#if rows}
|
|
91
|
+
<div class="hr-figure-values">
|
|
92
|
+
{#each rows as row, i (i)}
|
|
93
|
+
<!-- WHY the series colour goes in as a custom property rather than as `color`: a palette entry is picked to read as a line on a chart, which -->
|
|
94
|
+
<!-- is a 3:1 job, and the same value as 11px text measured 2.7:1. The stylesheet mixes it toward the page foreground, so the number still -->
|
|
95
|
+
<!-- names its series and the text still clears AA in either theme. -->
|
|
96
|
+
<span class="hr-figure-value-row" style:--hr-series={row.color}>
|
|
97
|
+
<span class="hr-figure-value is-series">
|
|
98
|
+
<AnimatedValue value={row.value} {format} {formatOptions} {locale} duration={450} />
|
|
99
|
+
</span>
|
|
100
|
+
{#if rows.length > 1 && row.label}
|
|
101
|
+
<span class="hr-figure-series-label is-series">{row.label}</span>
|
|
102
|
+
{/if}
|
|
103
|
+
</span>
|
|
104
|
+
{/each}
|
|
105
|
+
</div>
|
|
106
|
+
{:else if value != null}
|
|
107
|
+
<span class="hr-figure-value" style:color>
|
|
108
|
+
{#if typeof value === "number" && animate}
|
|
109
|
+
<AnimatedValue {value} {format} {formatOptions} {locale} duration={600} />
|
|
110
|
+
{:else}
|
|
111
|
+
{value}
|
|
112
|
+
{/if}
|
|
113
|
+
</span>
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
{#if caption}
|
|
117
|
+
<span class="hr-figure-caption">{caption}</span>
|
|
118
|
+
{/if}
|
|
119
|
+
{#if eyebrow}
|
|
120
|
+
<span class="hr-figure-eyebrow">{eyebrow}</span>
|
|
121
|
+
{/if}
|
|
122
|
+
</div>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* Styles for GaugeChart. Plain stylesheet (see area-chart.css for why).
|
|
2
|
+
Selectors are namespaced under `.hr-gauge` (the generic ones below would
|
|
3
|
+
otherwise collide with RingChart's identical `.value`/`.label`/`.center`/
|
|
4
|
+
`.fill`/`svg` names once they are no longer Svelte-scoped). */
|
|
5
|
+
|
|
6
|
+
.hr-gauge {
|
|
7
|
+
position: relative;
|
|
8
|
+
display: inline-grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
aspect-ratio: 1;
|
|
11
|
+
container-type: size;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.hr-gauge svg {
|
|
15
|
+
grid-area: 1 / 1;
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
overflow: visible;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.hr-gauge .center {
|
|
22
|
+
grid-area: 1 / 1;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
gap: 0.15cqmin;
|
|
28
|
+
/* WHY: keep the readout inside the inner ring so long values never collide
|
|
29
|
+
with the notches. */
|
|
30
|
+
max-width: 62cqmin;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.hr-gauge .value {
|
|
35
|
+
font-size: max(0.78rem, 11cqmin);
|
|
36
|
+
font-weight: 650;
|
|
37
|
+
line-height: 1;
|
|
38
|
+
letter-spacing: -0.02em;
|
|
39
|
+
font-variant-numeric: tabular-nums;
|
|
40
|
+
color: var(--foreground);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.hr-gauge .label {
|
|
44
|
+
margin-top: 1.5cqmin;
|
|
45
|
+
font-size: max(0.5rem, 5cqmin);
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
white-space: nowrap;
|
|
48
|
+
color: var(--muted-foreground);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* WHY: active paint comes from the fill attribute (flat or gradient url), so
|
|
52
|
+
no CSS fill here, which would override it. */
|
|
53
|
+
.hr-gauge .fill {
|
|
54
|
+
/* WHY: each active notch fades up in arc order; the per-notch stagger
|
|
55
|
+
sweeps the colour around the arc so the gauge reads as filling, instead
|
|
56
|
+
of every notch popping in from the centre at once. */
|
|
57
|
+
animation: hr-gauge-notch-in 0.3s var(--chart-ease-out, cubic-bezier(0.22, 1, 0.36, 1)) both;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@keyframes hr-gauge-notch-in {
|
|
61
|
+
from {
|
|
62
|
+
opacity: 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.hr-gauge .ring-track,
|
|
67
|
+
.hr-gauge .ring-fill {
|
|
68
|
+
fill: none;
|
|
69
|
+
stroke-linecap: round;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.hr-gauge .ring-fill {
|
|
73
|
+
stroke-dasharray: 100;
|
|
74
|
+
transition: stroke-dashoffset var(--chart-motion-slow, 1.1s)
|
|
75
|
+
var(--chart-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
|
|
76
|
+
animation: hr-gauge-ring-sweep var(--chart-motion-reveal, 1100ms)
|
|
77
|
+
var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@keyframes hr-gauge-ring-sweep {
|
|
81
|
+
from {
|
|
82
|
+
stroke-dashoffset: 100;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (prefers-reduced-motion: reduce) {
|
|
87
|
+
.hr-gauge .fill {
|
|
88
|
+
animation: none;
|
|
89
|
+
}
|
|
90
|
+
.hr-gauge .ring-fill {
|
|
91
|
+
transition: none;
|
|
92
|
+
animation: none;
|
|
93
|
+
}
|
|
94
|
+
}
|