@heyramzi/ui 6.1.0 → 8.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 +32 -40
- package/README.md +10 -4
- package/package.json +31 -29
- package/scripts/sync-shadcn.mjs +29 -16
- package/scripts/sync-transitions.mjs +11 -11
- package/src/ai/ai-button/AiButton.svelte +86 -0
- package/src/ai/ai-button/index.ts +4 -0
- package/src/ai/confirmation/Confirmation.svelte +8 -8
- package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
- package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
- package/src/ai/generation-progress/index.ts +7 -0
- package/src/ai/index.ts +16 -24
- package/src/ai/prompt-input/PromptInput.svelte +19 -1
- package/src/ai/prompt-input/PromptInputSubmit.svelte +9 -3
- package/src/ai/reasoning/Reasoning.svelte +11 -8
- package/src/ambient.d.ts +4 -0
- package/src/animations/README.md +17 -17
- package/src/animations/index.css +3 -14
- package/src/animations/upstream-coverage.test.ts +1 -1
- package/src/components/copy/clipboard.test.ts +206 -0
- package/src/components/copy/clipboard.ts +49 -0
- package/src/components/copy/copied.svelte.ts +39 -0
- package/src/components/copy/copy-button.svelte +175 -0
- package/src/components/copy/copy-button.test.ts +110 -0
- package/src/components/copy/index.ts +3 -0
- package/src/components/data-table/data-table.test.ts +134 -0
- package/src/components/data-table/data-table.ts +77 -0
- package/src/components/data-table/index.ts +20 -0
- package/src/components/data-table/infinite-scroll.svelte +69 -0
- package/src/components/data-table/pagination.svelte +62 -0
- package/src/components/data-table/row-arrow.svelte +28 -0
- package/src/components/data-table/row-context-menu.svelte +106 -0
- package/src/components/data-table/row-context-menu.test.ts +18 -0
- package/src/components/data-table/sortable-head.svelte +56 -0
- package/src/components/data-table/table-skeleton.svelte +83 -0
- package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
- package/src/components/hold-to-confirm/index.ts +1 -0
- package/src/components/icon/icon.svelte +23 -0
- package/src/components/icon/index.ts +1 -0
- package/src/components/language-flag/index.ts +1 -0
- package/src/components/language-flag/language-flag.svelte +60 -0
- package/src/components/list-page/detail-empty.svelte +50 -0
- package/src/components/list-page/detail-header.svelte +107 -0
- package/src/components/list-page/detail-panel.svelte +90 -0
- package/src/components/list-page/detail-row.svelte +36 -0
- package/src/components/list-page/detail-stack.svelte +33 -0
- package/src/components/list-page/empty-state.svelte +91 -0
- package/src/components/list-page/filter-item.svelte +49 -0
- package/src/components/list-page/filter-popover.svelte +88 -0
- package/src/components/list-page/filter-section.ts +12 -0
- package/src/components/list-page/grouped-list.svelte +116 -0
- package/src/components/list-page/index.ts +28 -0
- package/src/components/list-page/list-page-shell.svelte +195 -0
- package/src/components/list-page/list-table.ts +19 -0
- package/src/components/list-page/master-detail.svelte +206 -0
- package/src/components/list-page/master-detail.test.ts +37 -0
- package/src/components/list-page/notice-bar.svelte +68 -0
- package/src/components/list-page/prev-next.svelte +104 -0
- package/src/components/list-page/queue-rail.ts +25 -0
- package/src/components/list-page/rail-body.svelte +29 -0
- package/src/components/list-page/rail-empty.svelte +51 -0
- package/src/components/list-page/rail-head.svelte +49 -0
- package/src/components/list-page/rail-row.svelte +163 -0
- package/src/components/list-page/section-host.ts +24 -0
- package/src/components/list-page/section-label.svelte +39 -0
- package/src/components/list-page/step.test.ts +21 -0
- package/src/components/list-page/step.ts +10 -0
- package/src/components/list-page/toolbar-search.svelte +63 -0
- package/src/components/list-page/toolbar-trigger.ts +23 -0
- package/src/components/list-page/types.ts +1 -0
- package/src/components/locale-switch/index.ts +1 -0
- package/src/components/locale-switch/locale-switch.svelte +59 -0
- package/src/components/mark/index.ts +1 -0
- package/src/components/mark/mark.svelte +70 -0
- package/src/components/open-in/index.ts +6 -0
- package/src/components/open-in/open-in.svelte +106 -0
- package/src/components/picker/index.ts +4 -0
- package/src/components/picker/mark-picker.svelte +84 -0
- package/src/components/picker/picker.svelte +143 -0
- package/src/components/picker/trigger.ts +42 -0
- package/src/components/picker/types.ts +26 -0
- package/src/components/segmented/animated-tabs.svelte +175 -0
- package/src/components/segmented/collapse.test.ts +49 -0
- package/src/components/segmented/collapse.ts +39 -0
- package/src/components/segmented/index.ts +41 -0
- package/src/components/segmented/section-runs.svelte +79 -0
- package/src/components/segmented/segment.svelte +106 -0
- package/src/components/segmented/segmented-filter.svelte +125 -0
- package/src/components/segmented/segmented-nav.svelte +82 -0
- package/src/components/segmented/segmented-overflow.svelte +85 -0
- package/src/components/segmented/segmented-track.svelte +181 -0
- package/src/components/segmented/segmented.test.ts +20 -0
- package/src/components/segmented/segmented.ts +80 -0
- package/src/components/star-rating-input/index.ts +9 -0
- package/src/components/star-rating-input/star-rating-input.svelte +256 -0
- package/src/components/svg/index.ts +1 -0
- package/src/components/svg/svg.svelte +9 -0
- package/src/effects/border-beam/BorderBeam.svelte +1 -1
- package/src/effects/image-comparison/ImageComparison.css +42 -8
- package/src/effects/image-comparison/ImageComparison.svelte +138 -21
- package/src/effects/image-comparison/compare.test.ts +17 -0
- package/src/effects/image-comparison/compare.ts +5 -0
- package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
- package/src/effects/magic-card/MagicCard.css +47 -0
- package/src/effects/magic-card/MagicCard.svelte +57 -0
- package/src/effects/marquee/Marquee.css +27 -1
- package/src/effects/marquee/Marquee.svelte +67 -3
- package/src/effects/reveal/index.ts +2 -1
- package/src/effects/reveal/observe.test.ts +60 -0
- package/src/effects/reveal/observe.ts +33 -1
- package/src/effects/reveal/types.ts +7 -0
- package/src/effects/theme-toggle/ThemeToggle.css +199 -6
- package/src/effects/theme-toggle/ThemeToggle.svelte +51 -26
- package/src/gallery.test.ts +52 -0
- package/src/index.ts +5 -1
- package/src/lib/panel.ts +7 -1
- package/src/lib/sliding-pill.ts +68 -0
- package/src/lib/theme-transition.ts +41 -13
- package/src/lib/tone.ts +48 -0
- package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
- package/src/shadcn/alert/alert.svelte +3 -1
- package/src/shadcn/badge/badge.svelte +8 -7
- package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
- package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
- package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
- package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
- package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
- package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
- package/src/shadcn/calendar/calendar.svelte +6 -0
- package/src/shadcn/code-block/code-block-actions.svelte +23 -0
- package/src/shadcn/code-block/code-block-content.svelte +55 -0
- package/src/shadcn/code-block/code-block-copy-button.svelte +10 -0
- package/src/shadcn/code-block/code-block-footer.svelte +20 -0
- package/src/shadcn/code-block/code-block-header.svelte +23 -0
- package/src/shadcn/code-block/code-block-line.svelte +71 -0
- package/src/shadcn/code-block/code-block-title.svelte +23 -0
- package/src/shadcn/code-block/code-block.svelte +47 -0
- package/src/shadcn/code-block/index.ts +42 -0
- package/src/shadcn/command/index.ts +3 -0
- package/src/shadcn/field/index.ts +3 -0
- package/src/shadcn/field/simple-field.svelte +71 -0
- package/src/shadcn/field/simple-field.test.ts +47 -0
- package/src/shadcn/hooks/highlighter.ts +132 -0
- package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
- package/src/shadcn/kbd/index.ts +10 -0
- package/src/shadcn/kbd/kbd-group.svelte +20 -0
- package/src/shadcn/kbd/kbd.svelte +23 -0
- package/src/shadcn/message-scroller/index.ts +28 -0
- package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
- package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
- package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
- package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
- package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
- package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
- package/src/shadcn/radio-group/radio-group-item.svelte +3 -2
- package/src/shadcn/sheet/sheet-content.svelte +1 -0
- package/src/shadcn/sheet/sheet.css +48 -0
- package/src/shadcn/sonner/sonner.svelte +7 -10
- package/src/shadcn/tabs/tabs-list.svelte +13 -2
- package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
- package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
- package/src/shadcn/trace/index.ts +29 -0
- package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
- package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
- package/src/shadcn/trace/trace-collapsible.svelte +20 -0
- package/src/shadcn/trace/trace-description.svelte +20 -0
- package/src/shadcn/trace/trace-icon.svelte +24 -0
- package/src/shadcn/trace/trace-line.svelte +31 -0
- package/src/shadcn/trace/trace-title.svelte +20 -0
- package/src/shadcn/trace/trace.svelte +23 -0
- package/src/ai/composer/Composer.css +0 -108
- package/src/ai/composer/Composer.svelte +0 -111
- package/src/ai/composer/index.ts +0 -10
- package/src/ai/conversation/Conversation.svelte +0 -28
- package/src/ai/conversation/ConversationContent.svelte +0 -35
- package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
- package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
- package/src/ai/conversation/index.ts +0 -33
- package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
- package/src/animations/accordion.css +0 -58
- package/src/animations/icon-swap.css +0 -43
- package/src/animations/notification-badge.css +0 -81
- package/src/animations/panel.css +0 -86
- package/src/animations/reasoning-stream.css +0 -49
- package/src/animations/skeleton-reveal.css +0 -97
- package/src/animations/streaming-text.css +0 -31
- package/src/animations/tabs-sliding.css +0 -83
- package/src/animations/texts-reveal.css +0 -70
- package/src/animations/tooltip.css +0 -72
- package/src/animations/view-transition-reveal.ts +0 -74
- package/src/charts/animated-value.css +0 -7
- package/src/charts/animated-value.svelte +0 -110
- package/src/charts/area-chart.css +0 -29
- package/src/charts/area-chart.svelte +0 -195
- package/src/charts/bar-chart.css +0 -119
- package/src/charts/bar-chart.svelte +0 -269
- package/src/charts/charts.css +0 -201
- package/src/charts/crosshair.svelte +0 -35
- package/src/charts/donut-chart.css +0 -56
- package/src/charts/donut-chart.svelte +0 -158
- package/src/charts/edge-fade.svelte +0 -43
- package/src/charts/edge-fade.ts +0 -16
- package/src/charts/figure.css +0 -100
- package/src/charts/figure.svelte +0 -122
- package/src/charts/gauge-chart.css +0 -94
- package/src/charts/gauge-chart.svelte +0 -303
- package/src/charts/hover-band.test.ts +0 -30
- package/src/charts/hover-band.ts +0 -21
- package/src/charts/hover-focus.svelte +0 -85
- package/src/charts/index.ts +0 -30
- package/src/charts/legend.css +0 -131
- package/src/charts/legend.svelte +0 -112
- package/src/charts/line-chart.css +0 -26
- package/src/charts/line-chart.svelte +0 -256
- package/src/charts/motion.ts +0 -19
- package/src/charts/number-chart.css +0 -168
- package/src/charts/number-chart.svelte +0 -177
- package/src/charts/primitives/chart-container.svelte +0 -57
- package/src/charts/primitives/chart-style.svelte +0 -36
- package/src/charts/primitives/chart-tooltip.svelte +0 -161
- package/src/charts/primitives/chart-utils.ts +0 -54
- package/src/charts/primitives/index.ts +0 -11
- package/src/charts/reveal.svelte +0 -61
- package/src/charts/ring-chart.css +0 -96
- package/src/charts/ring-chart.svelte +0 -158
- package/src/charts/sparkline.css +0 -45
- package/src/charts/sparkline.svelte +0 -140
- package/src/charts/stat-card.css +0 -103
- package/src/charts/stat-card.svelte +0 -267
- package/src/charts/time-series-chart.css +0 -28
- package/src/charts/time-series-chart.svelte +0 -179
- package/src/charts/trend-pill.css +0 -38
- package/src/charts/trend-pill.svelte +0 -48
- package/src/lib/hooks/is-mobile.svelte.ts +0 -11
- package/src/lib/utils.ts +0 -45
- package/src/showcase/AiSection.svelte +0 -157
- package/src/showcase/AnimationsSection.svelte +0 -1658
- package/src/showcase/ChartsSection.svelte +0 -458
- package/src/showcase/EffectsSection.svelte +0 -152
- package/src/showcase/ShadcnSection.svelte +0 -27
- package/src/showcase/ShowcaseImport.svelte +0 -126
- package/src/showcase/ShowcaseList.svelte +0 -132
- package/src/showcase/ShowcaseNav.svelte +0 -247
- package/src/showcase/ShowcaseSection.svelte +0 -112
- package/src/showcase/ShowcaseShell.svelte +0 -55
- package/src/showcase/ShowcaseStage.svelte +0 -342
- package/src/showcase/ShowcaseVariant.svelte +0 -109
- package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
- package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
- package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
- package/src/showcase/check.svg +0 -1
- package/src/showcase/chevron-arms.svg +0 -2
- package/src/showcase/chevron-down.svg +0 -1
- package/src/showcase/copy.svg +0 -2
- package/src/showcase/demos/ai/confirmation.svelte +0 -59
- package/src/showcase/demos/ai/conversation.svelte +0 -59
- package/src/showcase/demos/ai/loading-state.svelte +0 -19
- package/src/showcase/demos/ai/prompt-input.svelte +0 -57
- package/src/showcase/demos/ai/reasoning.svelte +0 -19
- package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
- package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
- package/src/showcase/demos/effects/animated-beam.svelte +0 -50
- package/src/showcase/demos/effects/animated-group.svelte +0 -47
- package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
- package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
- package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
- package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
- package/src/showcase/demos/effects/image-comparison.svelte +0 -25
- package/src/showcase/demos/effects/image-reveal.svelte +0 -49
- package/src/showcase/demos/effects/in-view.svelte +0 -41
- package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
- package/src/showcase/demos/effects/magnetic.svelte +0 -31
- package/src/showcase/demos/effects/marquee.svelte +0 -33
- package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
- package/src/showcase/demos/effects/spotlight.svelte +0 -40
- package/src/showcase/demos/effects/text-effect.svelte +0 -55
- package/src/showcase/demos/shadcn/accordion.svelte +0 -45
- package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
- package/src/showcase/demos/shadcn/alert.svelte +0 -30
- package/src/showcase/demos/shadcn/attachment.svelte +0 -71
- package/src/showcase/demos/shadcn/avatar.svelte +0 -30
- package/src/showcase/demos/shadcn/badge.svelte +0 -26
- package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
- package/src/showcase/demos/shadcn/bubble.svelte +0 -36
- package/src/showcase/demos/shadcn/button.svelte +0 -11
- package/src/showcase/demos/shadcn/calendar.svelte +0 -8
- package/src/showcase/demos/shadcn/card.svelte +0 -39
- package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
- package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
- package/src/showcase/demos/shadcn/command.svelte +0 -48
- package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
- package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
- package/src/showcase/demos/shadcn/demos.test.ts +0 -20
- package/src/showcase/demos/shadcn/dialog.svelte +0 -38
- package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
- package/src/showcase/demos/shadcn/empty.svelte +0 -29
- package/src/showcase/demos/shadcn/field.svelte +0 -115
- package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
- package/src/showcase/demos/shadcn/index.ts +0 -358
- package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
- package/src/showcase/demos/shadcn/input.svelte +0 -5
- package/src/showcase/demos/shadcn/label.svelte +0 -11
- package/src/showcase/demos/shadcn/marker.svelte +0 -30
- package/src/showcase/demos/shadcn/message.svelte +0 -75
- package/src/showcase/demos/shadcn/popover.svelte +0 -36
- package/src/showcase/demos/shadcn/progress.svelte +0 -13
- package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
- package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
- package/src/showcase/demos/shadcn/select.svelte +0 -29
- package/src/showcase/demos/shadcn/separator.svelte +0 -12
- package/src/showcase/demos/shadcn/sheet.svelte +0 -48
- package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
- package/src/showcase/demos/shadcn/slider.svelte +0 -7
- package/src/showcase/demos/shadcn/sonner.svelte +0 -18
- package/src/showcase/demos/shadcn/spinner.svelte +0 -11
- package/src/showcase/demos/shadcn/switch.svelte +0 -9
- package/src/showcase/demos/shadcn/table.svelte +0 -76
- package/src/showcase/demos/shadcn/tabs.svelte +0 -62
- package/src/showcase/demos/shadcn/textarea.svelte +0 -5
- package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
- package/src/showcase/demos/shadcn/toggle.svelte +0 -14
- package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
- package/src/showcase/heart.svg +0 -1
- package/src/showcase/index.ts +0 -38
- package/src/showcase/kit.test.ts +0 -72
- package/src/showcase/kit.ts +0 -17
- package/src/showcase/plus.svg +0 -1
- package/src/showcase/showcase-shell.css +0 -91
- package/src/showcase/stacked-section.css +0 -15
- package/src/showcase/success-check.svg +0 -1
- package/src/showcase/types.ts +0 -29
- package/src/svg-raw.d.ts +0 -7
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
-
// =========================
|
|
3
|
-
// IMPORTS
|
|
4
|
-
// =========================
|
|
5
|
-
import "./stat-card.css";
|
|
6
|
-
import { Area, Bars, Chart, Highlight, LinearGradient, Spline, Svg, Tooltip } from "layerchart";
|
|
7
|
-
import { scaleBand } from "d3-scale";
|
|
8
|
-
import { curveBasis, curveCardinal } from "d3-shape";
|
|
9
|
-
import type { Snippet } from "svelte";
|
|
10
|
-
import Crosshair from "./crosshair.svelte";
|
|
11
|
-
import EdgeFade from "./edge-fade.svelte";
|
|
12
|
-
import Reveal from "./reveal.svelte";
|
|
13
|
-
import { resolveEdgeFade } from "./edge-fade.js";
|
|
14
|
-
import Figure from "./figure.svelte";
|
|
15
|
-
import GaugeChart from "./gauge-chart.svelte";
|
|
16
|
-
|
|
17
|
-
// =========================
|
|
18
|
-
// TYPES
|
|
19
|
-
// =========================
|
|
20
|
-
type Variant = "line" | "area" | "bar" | "gauge";
|
|
21
|
-
type Trend = "up" | "down" | "neutral";
|
|
22
|
-
|
|
23
|
-
type Props = {
|
|
24
|
-
/** Series for the line/area/bar variants. Unused by the gauge variant. */
|
|
25
|
-
data?: TData[];
|
|
26
|
-
xKey?: keyof TData & string;
|
|
27
|
-
yKey?: keyof TData & string;
|
|
28
|
-
/** Chart kind. "gauge" puts a radial gauge to the right of the figure. */
|
|
29
|
-
variant?: Variant;
|
|
30
|
-
/** Arc fill 0-100 for variant="gauge". Falls back to `value` when numeric. */
|
|
31
|
-
gaugeValue?: number;
|
|
32
|
-
/** Label under the gauge percentage. */
|
|
33
|
-
gaugeLabel?: string;
|
|
34
|
-
/** Metric name shown top-left. Omit (or set `figureOnly`) for a bare chart. */
|
|
35
|
-
label?: string;
|
|
36
|
-
/** Headline number; counts up on mount. */
|
|
37
|
-
value?: number;
|
|
38
|
-
/** Percentage change. Its sign drives the trend color/arrow unless `trend` is set. */
|
|
39
|
-
delta?: number;
|
|
40
|
-
/** Force the trend direction independent of `delta`'s sign. */
|
|
41
|
-
trend?: Trend;
|
|
42
|
-
/** Caption under the value, e.g. "Avg" or "vs last month". */
|
|
43
|
-
caption?: string;
|
|
44
|
-
/** Render only the edge-to-edge chart, no header/value/caption. */
|
|
45
|
-
figureOnly?: boolean;
|
|
46
|
-
color?: string;
|
|
47
|
-
/** Chart height in px. */
|
|
48
|
-
height?: number;
|
|
49
|
-
smooth?: boolean;
|
|
50
|
-
/**
|
|
51
|
-
* Softens the left and right ends of the line and the area fill instead of cutting them off at the card edge.
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
/*
|
|
55
|
-
* `true` fades 8% of the width per side, a number sets the fraction (0-0.45), `false` keeps the hard edges.
|
|
56
|
-
*/
|
|
57
|
-
edgeFade?: boolean | number;
|
|
58
|
-
/** Format for the headline value. */
|
|
59
|
-
formatValue?: (v: number) => string;
|
|
60
|
-
/** Format for the x value shown as the tooltip header (e.g. a date/period). */
|
|
61
|
-
formatX?: (v: unknown) => string;
|
|
62
|
-
/** Leading icon next to the label. */
|
|
63
|
-
icon?: Snippet;
|
|
64
|
-
class?: string;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// =========================
|
|
68
|
-
// PROPS
|
|
69
|
-
// =========================
|
|
70
|
-
let {
|
|
71
|
-
data = [],
|
|
72
|
-
xKey,
|
|
73
|
-
yKey,
|
|
74
|
-
variant = "area",
|
|
75
|
-
gaugeValue,
|
|
76
|
-
gaugeLabel,
|
|
77
|
-
label,
|
|
78
|
-
value,
|
|
79
|
-
delta,
|
|
80
|
-
trend,
|
|
81
|
-
caption,
|
|
82
|
-
figureOnly = false,
|
|
83
|
-
color = "var(--chart-1)",
|
|
84
|
-
height = 150,
|
|
85
|
-
smooth = true,
|
|
86
|
-
edgeFade = true,
|
|
87
|
-
formatValue = (v) => v.toLocaleString(),
|
|
88
|
-
formatX = (v) => String(v),
|
|
89
|
-
icon,
|
|
90
|
-
class: className,
|
|
91
|
-
}: Props = $props();
|
|
92
|
-
|
|
93
|
-
// =========================
|
|
94
|
-
// DERIVED
|
|
95
|
-
// =========================
|
|
96
|
-
const uid = `hr-stat-${Math.random().toString(36).slice(2, 8)}`;
|
|
97
|
-
// WHY: match bklit's stat-card curves per variant. The area card uses cardinal
|
|
98
|
-
|
|
99
|
-
// at 0.65 tension; the line card uses curveBasis (a smoother, approximating spline) so the line flows instead of kinking through every point.
|
|
100
|
-
const areaCurve = $derived(smooth ? curveCardinal.tension(0.65) : undefined);
|
|
101
|
-
const lineCurve = $derived(smooth ? curveBasis : undefined);
|
|
102
|
-
const yMax = $derived(yKey ? Math.max(0, ...data.map((d) => Number(d[yKey] ?? 0))) : 0);
|
|
103
|
-
const arcValue = $derived(gaugeValue ?? (typeof value === "number" ? value : 0));
|
|
104
|
-
// WHY: one prop drives both ends. The stroke ramp is half the fill's so the
|
|
105
|
-
|
|
106
|
-
// line still spans nearly the full width (8% fill fade -> the 4% stroke fade this card has always used) instead of reading as side padding.
|
|
107
|
-
const fade = $derived(resolveEdgeFade(edgeFade));
|
|
108
|
-
const strokePct = $derived(Math.round(fade * 50 * 1000) / 1000);
|
|
109
|
-
const strokeStop = $derived(`${strokePct}%`);
|
|
110
|
-
const strokeEnd = $derived(`${100 - strokePct}%`);
|
|
111
|
-
const fadeMask = $derived(fade > 0 ? `url(#${uid}-fade)` : undefined);
|
|
112
|
-
</script>
|
|
113
|
-
|
|
114
|
-
<!-- =========================== -->
|
|
115
|
-
<!-- STAT CARD -->
|
|
116
|
-
<!-- =========================== -->
|
|
117
|
-
<div
|
|
118
|
-
class="hr-stat-card {className ?? ''}"
|
|
119
|
-
class:hr-stat-figure-only={figureOnly}
|
|
120
|
-
class:hr-stat-has-gauge={variant === "gauge"}
|
|
121
|
-
>
|
|
122
|
-
{#if variant === "gauge"}
|
|
123
|
-
<div class="hr-stat-gauge-row">
|
|
124
|
-
{#if label != null || value != null || caption != null}
|
|
125
|
-
<Figure
|
|
126
|
-
{icon}
|
|
127
|
-
title={label}
|
|
128
|
-
{value}
|
|
129
|
-
{delta}
|
|
130
|
-
{trend}
|
|
131
|
-
{caption}
|
|
132
|
-
size="lg"
|
|
133
|
-
format={formatValue}
|
|
134
|
-
class="hr-stat-gauge-figure"
|
|
135
|
-
/>
|
|
136
|
-
{/if}
|
|
137
|
-
<GaugeChart
|
|
138
|
-
value={arcValue}
|
|
139
|
-
{color}
|
|
140
|
-
size={116}
|
|
141
|
-
minWidth={0}
|
|
142
|
-
suffix="%"
|
|
143
|
-
showValue
|
|
144
|
-
defaultLabel={gaugeLabel ?? ""}
|
|
145
|
-
ariaLabel={label}
|
|
146
|
-
/>
|
|
147
|
-
</div>
|
|
148
|
-
{:else}
|
|
149
|
-
{#if !figureOnly && (label != null || value != null || caption != null)}
|
|
150
|
-
<Figure
|
|
151
|
-
{icon}
|
|
152
|
-
title={label}
|
|
153
|
-
{value}
|
|
154
|
-
{delta}
|
|
155
|
-
{trend}
|
|
156
|
-
{caption}
|
|
157
|
-
size="lg"
|
|
158
|
-
format={formatValue}
|
|
159
|
-
/>
|
|
160
|
-
{/if}
|
|
161
|
-
|
|
162
|
-
<div
|
|
163
|
-
class="hr-stat-chart"
|
|
164
|
-
style:height="{height}px"
|
|
165
|
-
role="img"
|
|
166
|
-
aria-label="{label ?? 'Value'} trend"
|
|
167
|
-
>
|
|
168
|
-
{#if data.length > 0 && xKey && yKey}
|
|
169
|
-
{#if variant === "bar"}
|
|
170
|
-
<Chart
|
|
171
|
-
{data}
|
|
172
|
-
x={xKey}
|
|
173
|
-
xScale={scaleBand().padding(0.28)}
|
|
174
|
-
y={yKey}
|
|
175
|
-
yDomain={[0, yMax || 1]}
|
|
176
|
-
padding={{ top: 6, bottom: 0, left: 0, right: 0 }}
|
|
177
|
-
tooltipContext={{ mode: "band" }}
|
|
178
|
-
>
|
|
179
|
-
<Svg>
|
|
180
|
-
<Bars rounded="top" radius={2} style="fill: {color}" class="hr-stat-bars" />
|
|
181
|
-
<Highlight
|
|
182
|
-
bar={{ rounded: "top", radius: 2, fill: color, class: "hr-stat-bar-active" }}
|
|
183
|
-
/>
|
|
184
|
-
</Svg>
|
|
185
|
-
<Tooltip.Root>
|
|
186
|
-
{#snippet children({ data: d })}
|
|
187
|
-
<Tooltip.Header value={xKey ? formatX(d[xKey]) : ""} />
|
|
188
|
-
<Tooltip.List>
|
|
189
|
-
<Tooltip.Item label={String(yKey)} value={formatValue(Number(d[yKey]))} />
|
|
190
|
-
</Tooltip.List>
|
|
191
|
-
{/snippet}
|
|
192
|
-
</Tooltip.Root>
|
|
193
|
-
</Chart>
|
|
194
|
-
{:else}
|
|
195
|
-
<Chart
|
|
196
|
-
{data}
|
|
197
|
-
x={xKey}
|
|
198
|
-
y={yKey}
|
|
199
|
-
padding={{ top: 8, bottom: 0, left: 0, right: 0 }}
|
|
200
|
-
tooltipContext={{ mode: "quadtree-x" }}
|
|
201
|
-
>
|
|
202
|
-
<Svg>
|
|
203
|
-
<!-- WHY: bklit fades the stroke to transparent at both ends via a
|
|
204
|
-
horizontal gradient applied as the stroke paint; shared by the area
|
|
205
|
-
and line variants. -->
|
|
206
|
-
<LinearGradient id="{uid}-stroke">
|
|
207
|
-
{#snippet stopsContent()}
|
|
208
|
-
<stop offset="0%" stop-color={color} stop-opacity={fade > 0 ? 0 : 1} />
|
|
209
|
-
<stop offset={strokeStop} stop-color={color} stop-opacity="1" />
|
|
210
|
-
<stop offset={strokeEnd} stop-color={color} stop-opacity="1" />
|
|
211
|
-
<stop offset="100%" stop-color={color} stop-opacity={fade > 0 ? 0 : 1} />
|
|
212
|
-
{/snippet}
|
|
213
|
-
</LinearGradient>
|
|
214
|
-
{#if fade > 0}
|
|
215
|
-
<EdgeFade id="{uid}-fade" amount={fade} />
|
|
216
|
-
{/if}
|
|
217
|
-
<Crosshair id="{uid}-cross" />
|
|
218
|
-
<Reveal bleed={3}>
|
|
219
|
-
{#if variant === "area"}
|
|
220
|
-
<LinearGradient id={uid} vertical>
|
|
221
|
-
{#snippet stopsContent()}
|
|
222
|
-
<stop offset="0%" stop-color={color} stop-opacity="0.45" />
|
|
223
|
-
<stop offset="100%" stop-color={color} stop-opacity="0" />
|
|
224
|
-
{/snippet}
|
|
225
|
-
</LinearGradient>
|
|
226
|
-
<Area
|
|
227
|
-
y1={yKey}
|
|
228
|
-
curve={areaCurve}
|
|
229
|
-
fill="url(#{uid})"
|
|
230
|
-
mask={fadeMask}
|
|
231
|
-
line={false}
|
|
232
|
-
/>
|
|
233
|
-
{/if}
|
|
234
|
-
<Spline
|
|
235
|
-
y={yKey}
|
|
236
|
-
curve={variant === "line" ? lineCurve : areaCurve}
|
|
237
|
-
stroke="url(#{uid}-stroke)"
|
|
238
|
-
fill="none"
|
|
239
|
-
stroke-width={variant === "line" ? 2.5 : 2}
|
|
240
|
-
stroke-linecap="round"
|
|
241
|
-
class="hr-stat-spline"
|
|
242
|
-
/>
|
|
243
|
-
</Reveal>
|
|
244
|
-
<!-- WHY: the crosshair line (not just the point dot) makes hover targeting legible on the edge-to-edge chart; quadtree-x snaps to the nearest -->
|
|
245
|
-
<!-- point by cursor x (bisect-x mis-tracks a categorical x-axis), but without -->
|
|
246
|
-
<!-- the line the selected point is easy to miss. Matches AreaChart's highlight. points fill is forced to the series color (else it -->
|
|
247
|
-
<!-- falls back to layerchart's blue --color-primary); motion="none" drops the spring so the dot tracks the cursor instantly instead of lagging behind. -->
|
|
248
|
-
<Highlight
|
|
249
|
-
points={{ fill: color }}
|
|
250
|
-
lines={{ style: `stroke: url(#${uid}-cross)` }}
|
|
251
|
-
motion="none"
|
|
252
|
-
/>
|
|
253
|
-
</Svg>
|
|
254
|
-
<Tooltip.Root>
|
|
255
|
-
{#snippet children({ data: d })}
|
|
256
|
-
<Tooltip.Header value={xKey ? formatX(d[xKey]) : ""} />
|
|
257
|
-
<Tooltip.List>
|
|
258
|
-
<Tooltip.Item label={String(yKey)} value={formatValue(Number(d[yKey]))} />
|
|
259
|
-
</Tooltip.List>
|
|
260
|
-
{/snippet}
|
|
261
|
-
</Tooltip.Root>
|
|
262
|
-
</Chart>
|
|
263
|
-
{/if}
|
|
264
|
-
{/if}
|
|
265
|
-
</div>
|
|
266
|
-
{/if}
|
|
267
|
-
</div>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/* Styles for TimeSeriesChart. Shipped as a plain stylesheet (imported by the
|
|
2
|
-
component) instead of a Svelte `<style>` block: a `<style>` block becomes a
|
|
3
|
-
`?type=style&lang.css` virtual module that Tailwind v4's Vite plugin mis-parses
|
|
4
|
-
when this package is consumed as source from node_modules (tailwindlabs/
|
|
5
|
-
tailwindcss#20000). Selectors are namespaced under `.hr-ts-chart`. */
|
|
6
|
-
|
|
7
|
-
.hr-ts-chart {
|
|
8
|
-
width: 100%;
|
|
9
|
-
overflow: hidden;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.hr-ts-chart .empty {
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
height: 100%;
|
|
17
|
-
border: 1px dashed var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
|
|
18
|
-
border-radius: 0.5rem;
|
|
19
|
-
}
|
|
20
|
-
.hr-ts-chart .empty p {
|
|
21
|
-
font-size: 0.875rem;
|
|
22
|
-
color: var(--chart-label, oklch(0.5 0.01 286));
|
|
23
|
-
margin: 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.hr-ts-chart .hr-ts-grid {
|
|
27
|
-
stroke: var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
|
|
28
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
// =========================
|
|
3
|
-
// IMPORTS
|
|
4
|
-
// =========================
|
|
5
|
-
import "./time-series-chart.css";
|
|
6
|
-
import { scaleTime } from "d3-scale";
|
|
7
|
-
import { curveMonotoneX } from "d3-shape";
|
|
8
|
-
import {
|
|
9
|
-
Area,
|
|
10
|
-
Axis,
|
|
11
|
-
Chart,
|
|
12
|
-
Grid,
|
|
13
|
-
Highlight,
|
|
14
|
-
LinearGradient,
|
|
15
|
-
Spline,
|
|
16
|
-
Svg,
|
|
17
|
-
Tooltip,
|
|
18
|
-
} from "layerchart";
|
|
19
|
-
import Crosshair from "./crosshair.svelte";
|
|
20
|
-
import EdgeFade from "./edge-fade.svelte";
|
|
21
|
-
import { resolveEdgeFade } from "./edge-fade.js";
|
|
22
|
-
import { DOMAIN_MOTION } from "./motion.js";
|
|
23
|
-
import Reveal from "./reveal.svelte";
|
|
24
|
-
|
|
25
|
-
// =========================
|
|
26
|
-
// TYPES
|
|
27
|
-
// =========================
|
|
28
|
-
interface DataPoint {
|
|
29
|
-
date: string;
|
|
30
|
-
value: number;
|
|
31
|
-
value2?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface Props {
|
|
35
|
-
data: DataPoint[];
|
|
36
|
-
label?: string;
|
|
37
|
-
label2?: string;
|
|
38
|
-
color?: string;
|
|
39
|
-
color2?: string;
|
|
40
|
-
formatValue?: (v: number) => string;
|
|
41
|
-
formatValue2?: (v: number) => string;
|
|
42
|
-
height?: number;
|
|
43
|
-
/** Clamp Y-axis to 95th percentile so outlier spikes don't flatten normal data */
|
|
44
|
-
softMax?: boolean;
|
|
45
|
-
/** Y-axis floor. Raise above 0 to frame variation around a baseline. */
|
|
46
|
-
yMin?: number;
|
|
47
|
-
/**
|
|
48
|
-
* Softens the left and right ends of the filled area instead of cutting them off at the plot edge.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/*
|
|
52
|
-
* `true` fades 8% of the width per side, a number sets the fraction (0-0.45), `false` keeps the hard edges.
|
|
53
|
-
*/
|
|
54
|
-
edgeFade?: boolean | number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// =========================
|
|
58
|
-
// PROPS
|
|
59
|
-
// =========================
|
|
60
|
-
let {
|
|
61
|
-
data,
|
|
62
|
-
label = "Value",
|
|
63
|
-
label2,
|
|
64
|
-
color = "var(--chart-1)",
|
|
65
|
-
color2 = "var(--muted-foreground)",
|
|
66
|
-
formatValue = (v) => v.toLocaleString(),
|
|
67
|
-
formatValue2,
|
|
68
|
-
height = 280,
|
|
69
|
-
softMax = false,
|
|
70
|
-
yMin = 0,
|
|
71
|
-
edgeFade = true,
|
|
72
|
-
}: Props = $props();
|
|
73
|
-
|
|
74
|
-
// =========================
|
|
75
|
-
// DERIVED
|
|
76
|
-
// =========================
|
|
77
|
-
const gradientId = $derived(
|
|
78
|
-
`time-series-gradient-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
79
|
-
);
|
|
80
|
-
const fade = $derived(resolveEdgeFade(edgeFade));
|
|
81
|
-
const fadeMask = $derived(fade > 0 ? `url(#${gradientId}-fade)` : undefined);
|
|
82
|
-
|
|
83
|
-
let chartData = $derived(
|
|
84
|
-
data.map((d) => ({
|
|
85
|
-
...d,
|
|
86
|
-
dateObj: new Date(d.date),
|
|
87
|
-
})),
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
let yMax = $derived.by(() => {
|
|
91
|
-
const allValues = [
|
|
92
|
-
...data.map((d) => d.value),
|
|
93
|
-
...data.filter((d) => d.value2 != null).map((d) => d.value2!),
|
|
94
|
-
];
|
|
95
|
-
const absMax = Math.max(...allValues);
|
|
96
|
-
|
|
97
|
-
if (!softMax || allValues.length < 10) return absMax;
|
|
98
|
-
|
|
99
|
-
const sorted = allValues.filter((v) => v > 0).toSorted((a, b) => a - b);
|
|
100
|
-
if (sorted.length === 0) return absMax;
|
|
101
|
-
|
|
102
|
-
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
103
|
-
// Only clamp if the max is significantly larger than p95 (2x+)
|
|
104
|
-
if (absMax > p95 * 2) return p95 * 1.3;
|
|
105
|
-
return absMax;
|
|
106
|
-
});
|
|
107
|
-
</script>
|
|
108
|
-
|
|
109
|
-
<!-- =========================== -->
|
|
110
|
-
<!-- TIME SERIES CHART -->
|
|
111
|
-
<!-- =========================== -->
|
|
112
|
-
<div class="hr-ts-chart" style="height: {height}px">
|
|
113
|
-
{#if chartData.length === 0}
|
|
114
|
-
<div class="empty"><p>No historical data yet</p></div>
|
|
115
|
-
{:else}
|
|
116
|
-
<Chart
|
|
117
|
-
data={chartData}
|
|
118
|
-
x="dateObj"
|
|
119
|
-
xScale={scaleTime()}
|
|
120
|
-
y="value"
|
|
121
|
-
yDomain={[yMin, yMax]}
|
|
122
|
-
yNice
|
|
123
|
-
padding={{ left: 56, bottom: 36, top: 12, right: 16 }}
|
|
124
|
-
tooltipContext={{ mode: "bisect-x" }}
|
|
125
|
-
motion={DOMAIN_MOTION}
|
|
126
|
-
>
|
|
127
|
-
<Svg>
|
|
128
|
-
<Axis placement="left" format={(v) => formatValue(v)} ticks={6} />
|
|
129
|
-
<Axis
|
|
130
|
-
placement="bottom"
|
|
131
|
-
format={(d) => {
|
|
132
|
-
const date = d instanceof Date ? d : new Date(d);
|
|
133
|
-
return date.toLocaleDateString("en", { month: "short", day: "numeric" });
|
|
134
|
-
}}
|
|
135
|
-
/>
|
|
136
|
-
<Grid y class="hr-ts-grid" />
|
|
137
|
-
<LinearGradient id={gradientId} vertical>
|
|
138
|
-
{#snippet stopsContent()}
|
|
139
|
-
<stop offset="0%" stop-color={color} stop-opacity="0.35" />
|
|
140
|
-
<stop offset="100%" stop-color={color} stop-opacity="0.02" />
|
|
141
|
-
{/snippet}
|
|
142
|
-
</LinearGradient>
|
|
143
|
-
{#if fade > 0}
|
|
144
|
-
<EdgeFade id="{gradientId}-fade" amount={fade} />
|
|
145
|
-
{/if}
|
|
146
|
-
<Crosshair id="{gradientId}-cross" />
|
|
147
|
-
<Reveal>
|
|
148
|
-
<Area curve={curveMonotoneX} fill="url(#{gradientId})" mask={fadeMask} />
|
|
149
|
-
<Spline curve={curveMonotoneX} style="stroke: {color}; fill: none" stroke-width="1.5" />
|
|
150
|
-
{#if label2}
|
|
151
|
-
<Spline
|
|
152
|
-
y="value2"
|
|
153
|
-
curve={curveMonotoneX}
|
|
154
|
-
style="stroke: {color2}; stroke-dasharray: 4 4; stroke-width: 1; fill: none"
|
|
155
|
-
/>
|
|
156
|
-
{/if}
|
|
157
|
-
</Reveal>
|
|
158
|
-
<Highlight points lines={{ style: `stroke: url(#${gradientId}-cross)` }} />
|
|
159
|
-
</Svg>
|
|
160
|
-
<Tooltip.Root>
|
|
161
|
-
{#snippet children({ data: d })}
|
|
162
|
-
<Tooltip.Header
|
|
163
|
-
value={d.dateObj.toLocaleDateString("en", {
|
|
164
|
-
month: "short",
|
|
165
|
-
day: "numeric",
|
|
166
|
-
year: "numeric",
|
|
167
|
-
})}
|
|
168
|
-
/>
|
|
169
|
-
<Tooltip.List>
|
|
170
|
-
<Tooltip.Item {label} value={formatValue(d.value)} />
|
|
171
|
-
{#if label2 && d.value2 !== undefined}
|
|
172
|
-
<Tooltip.Item label={label2} value={(formatValue2 ?? formatValue)(d.value2)} />
|
|
173
|
-
{/if}
|
|
174
|
-
</Tooltip.List>
|
|
175
|
-
{/snippet}
|
|
176
|
-
</Tooltip.Root>
|
|
177
|
-
</Chart>
|
|
178
|
-
{/if}
|
|
179
|
-
</div>
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/* Styles for TrendPill. Plain stylesheet (see area-chart.css for why). All
|
|
2
|
-
selectors are already namespaced under `.hr-trend`. */
|
|
3
|
-
|
|
4
|
-
.hr-trend {
|
|
5
|
-
display: inline-flex;
|
|
6
|
-
/* WHY no shrink: the delta is the row's point, and it is only three characters wide. */
|
|
7
|
-
flex: 0 0 auto;
|
|
8
|
-
align-items: center;
|
|
9
|
-
gap: 0.1875rem;
|
|
10
|
-
padding: 0.1875rem 0.5rem;
|
|
11
|
-
border-radius: 999px;
|
|
12
|
-
border: 1px solid color-mix(in srgb, var(--stat-trend, currentColor) 22%, transparent);
|
|
13
|
-
background: color-mix(in srgb, var(--stat-trend, currentColor) 10%, transparent);
|
|
14
|
-
/* WHY the mix: a trend colour is picked to read as a mark, and at 12px on its
|
|
15
|
-
own 10% wash the raw hue measured 3.5:1. Mixing toward --foreground darkens
|
|
16
|
-
it on a light page and lightens it on a dark one, so one rule serves both
|
|
17
|
-
themes and the package needs to know nothing about which is on. */
|
|
18
|
-
color: color-mix(
|
|
19
|
-
in oklab,
|
|
20
|
-
var(--stat-trend, var(--muted-foreground, oklch(0.5 0.01 286))) 72%,
|
|
21
|
-
var(--foreground, oklch(0.2 0.01 286))
|
|
22
|
-
);
|
|
23
|
-
font-size: 0.75rem;
|
|
24
|
-
font-weight: 600;
|
|
25
|
-
font-feature-settings: "tnum";
|
|
26
|
-
line-height: 1.2;
|
|
27
|
-
white-space: nowrap;
|
|
28
|
-
}
|
|
29
|
-
.hr-trend[data-trend="up"] {
|
|
30
|
-
--stat-trend: var(--stat-up, oklch(0.6 0.13 163));
|
|
31
|
-
}
|
|
32
|
-
.hr-trend[data-trend="down"] {
|
|
33
|
-
--stat-trend: var(--stat-down, oklch(0.58 0.22 25));
|
|
34
|
-
}
|
|
35
|
-
.hr-trend-arrow {
|
|
36
|
-
font-size: 0.8125rem;
|
|
37
|
-
line-height: 1;
|
|
38
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
// =========================
|
|
3
|
-
// IMPORTS
|
|
4
|
-
// =========================
|
|
5
|
-
import "./trend-pill.css";
|
|
6
|
-
import { cn } from "@heyramzi/utils";
|
|
7
|
-
|
|
8
|
-
// =========================
|
|
9
|
-
// TYPES
|
|
10
|
-
// =========================
|
|
11
|
-
type Trend = "up" | "down" | "neutral";
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
/** Percentage change. Its sign drives the color/arrow unless `trend` is set. */
|
|
15
|
-
delta: number;
|
|
16
|
-
/** Force the direction independent of `delta`'s sign. */
|
|
17
|
-
trend?: Trend;
|
|
18
|
-
/** Format the delta text. */
|
|
19
|
-
format?: (v: number) => string;
|
|
20
|
-
class?: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// =========================
|
|
24
|
-
// PROPS
|
|
25
|
-
// =========================
|
|
26
|
-
let {
|
|
27
|
-
delta,
|
|
28
|
-
trend,
|
|
29
|
-
format = (v) =>
|
|
30
|
-
`${v > 0 ? "+" : ""}${v.toLocaleString(undefined, { maximumFractionDigits: 1 })}%`,
|
|
31
|
-
class: className,
|
|
32
|
-
}: Props = $props();
|
|
33
|
-
|
|
34
|
-
// =========================
|
|
35
|
-
// DERIVED
|
|
36
|
-
// =========================
|
|
37
|
-
const resolved = $derived<Trend>(trend ?? (delta === 0 ? "neutral" : delta > 0 ? "up" : "down"));
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<!-- =========================== -->
|
|
41
|
-
<!-- TREND PILL -->
|
|
42
|
-
<!-- =========================== -->
|
|
43
|
-
<span class={cn("hr-trend", className)} data-trend={resolved}>
|
|
44
|
-
{#if resolved !== "neutral"}
|
|
45
|
-
<span class="hr-trend-arrow" aria-hidden="true">{resolved === "up" ? "↑" : "↓"}</span>
|
|
46
|
-
{/if}
|
|
47
|
-
{format(delta)}
|
|
48
|
-
</span>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { MediaQuery } from "svelte/reactivity";
|
|
2
|
-
|
|
3
|
-
// WHY 1024, not 768: this picks rail or drawer, and the question is width left
|
|
4
|
-
// for content. A 256px rail on an 834px iPad leaves 578px, which is too narrow.
|
|
5
|
-
const DEFAULT_MOBILE_BREAKPOINT = 1024;
|
|
6
|
-
|
|
7
|
-
export class IsMobile extends MediaQuery {
|
|
8
|
-
constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) {
|
|
9
|
-
super(`max-width: ${breakpoint - 1}px`);
|
|
10
|
-
}
|
|
11
|
-
}
|
package/src/lib/utils.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from "clsx";
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
/** Merge conditional class names and de-duplicate conflicting Tailwind utilities. */
|
|
5
|
-
export function cn(...inputs: ClassValue[]) {
|
|
6
|
-
return twMerge(clsx(inputs));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
|
10
|
-
ref?: U | null;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type WithoutChildren<T> = T extends { children?: unknown } ? Omit<T, "children"> : T;
|
|
14
|
-
|
|
15
|
-
export type WithoutChild<T> = T extends { child?: unknown } ? Omit<T, "child"> : T;
|
|
16
|
-
|
|
17
|
-
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|
18
|
-
|
|
19
|
-
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
20
|
-
|
|
21
|
-
export function Ok<T>(value: T): Result<T, never> {
|
|
22
|
-
return { ok: true, value };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function Err<E>(error: E): Result<never, E> {
|
|
26
|
-
return { ok: false, error };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Run a function and return a result instead of throwing. */
|
|
30
|
-
export function tryCatch<T>(fn: () => T): Result<T> {
|
|
31
|
-
try {
|
|
32
|
-
return { ok: true, value: fn() } as Result<T>;
|
|
33
|
-
} catch (error) {
|
|
34
|
-
return { ok: false, error: error as Error };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Await a function and return a result instead of rejecting. */
|
|
39
|
-
export async function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T>> {
|
|
40
|
-
try {
|
|
41
|
-
return { ok: true, value: await fn() } as Result<T>;
|
|
42
|
-
} catch (error) {
|
|
43
|
-
return { ok: false, error: error as Error };
|
|
44
|
-
}
|
|
45
|
-
}
|