@heyramzi/ui 6.1.0 → 7.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.
Files changed (98) hide show
  1. package/COMPONENTS.md +6 -25
  2. package/README.md +6 -4
  3. package/package.json +7 -15
  4. package/scripts/sync-shadcn.mjs +12 -3
  5. package/src/ai/index.ts +0 -18
  6. package/src/animations/README.md +3 -3
  7. package/src/lib/sliding-pill.ts +68 -0
  8. package/src/lib/theme-transition.ts +4 -2
  9. package/src/shadcn/code-block/code-block-actions.svelte +23 -0
  10. package/src/shadcn/code-block/code-block-content.svelte +55 -0
  11. package/src/shadcn/code-block/code-block-copy-button.svelte +59 -0
  12. package/src/shadcn/code-block/code-block-footer.svelte +20 -0
  13. package/src/shadcn/code-block/code-block-header.svelte +23 -0
  14. package/src/shadcn/code-block/code-block-line.svelte +71 -0
  15. package/src/shadcn/code-block/code-block-title.svelte +23 -0
  16. package/src/shadcn/code-block/code-block.svelte +47 -0
  17. package/src/shadcn/code-block/index.ts +42 -0
  18. package/src/shadcn/hooks/highlighter.ts +132 -0
  19. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
  20. package/src/shadcn/kbd/index.ts +10 -0
  21. package/src/shadcn/kbd/kbd-group.svelte +20 -0
  22. package/src/shadcn/kbd/kbd.svelte +23 -0
  23. package/src/shadcn/message-scroller/index.ts +28 -0
  24. package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
  25. package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
  26. package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
  27. package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
  28. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
  29. package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
  30. package/src/shadcn/tabs/tabs-list.svelte +13 -2
  31. package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
  32. package/src/shadcn/trace/index.ts +29 -0
  33. package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
  34. package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
  35. package/src/shadcn/trace/trace-collapsible.svelte +20 -0
  36. package/src/shadcn/trace/trace-description.svelte +20 -0
  37. package/src/shadcn/trace/trace-icon.svelte +24 -0
  38. package/src/shadcn/trace/trace-line.svelte +31 -0
  39. package/src/shadcn/trace/trace-title.svelte +20 -0
  40. package/src/shadcn/trace/trace.svelte +23 -0
  41. package/src/showcase/AiSection.svelte +0 -7
  42. package/src/showcase/demos/shadcn/code-block.svelte +80 -0
  43. package/src/showcase/demos/shadcn/index.ts +24 -0
  44. package/src/showcase/demos/shadcn/message-scroller.svelte +44 -0
  45. package/src/showcase/demos/shadcn/trace.svelte +51 -0
  46. package/src/showcase/kit.ts +1 -2
  47. package/src/ai/conversation/Conversation.svelte +0 -28
  48. package/src/ai/conversation/ConversationContent.svelte +0 -35
  49. package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
  50. package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
  51. package/src/ai/conversation/index.ts +0 -33
  52. package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
  53. package/src/charts/animated-value.css +0 -7
  54. package/src/charts/animated-value.svelte +0 -110
  55. package/src/charts/area-chart.css +0 -29
  56. package/src/charts/area-chart.svelte +0 -195
  57. package/src/charts/bar-chart.css +0 -119
  58. package/src/charts/bar-chart.svelte +0 -269
  59. package/src/charts/charts.css +0 -201
  60. package/src/charts/crosshair.svelte +0 -35
  61. package/src/charts/donut-chart.css +0 -56
  62. package/src/charts/donut-chart.svelte +0 -158
  63. package/src/charts/edge-fade.svelte +0 -43
  64. package/src/charts/edge-fade.ts +0 -16
  65. package/src/charts/figure.css +0 -100
  66. package/src/charts/figure.svelte +0 -122
  67. package/src/charts/gauge-chart.css +0 -94
  68. package/src/charts/gauge-chart.svelte +0 -303
  69. package/src/charts/hover-band.test.ts +0 -30
  70. package/src/charts/hover-band.ts +0 -21
  71. package/src/charts/hover-focus.svelte +0 -85
  72. package/src/charts/index.ts +0 -30
  73. package/src/charts/legend.css +0 -131
  74. package/src/charts/legend.svelte +0 -112
  75. package/src/charts/line-chart.css +0 -26
  76. package/src/charts/line-chart.svelte +0 -256
  77. package/src/charts/motion.ts +0 -19
  78. package/src/charts/number-chart.css +0 -168
  79. package/src/charts/number-chart.svelte +0 -177
  80. package/src/charts/primitives/chart-container.svelte +0 -57
  81. package/src/charts/primitives/chart-style.svelte +0 -36
  82. package/src/charts/primitives/chart-tooltip.svelte +0 -161
  83. package/src/charts/primitives/chart-utils.ts +0 -54
  84. package/src/charts/primitives/index.ts +0 -11
  85. package/src/charts/reveal.svelte +0 -61
  86. package/src/charts/ring-chart.css +0 -96
  87. package/src/charts/ring-chart.svelte +0 -158
  88. package/src/charts/sparkline.css +0 -45
  89. package/src/charts/sparkline.svelte +0 -140
  90. package/src/charts/stat-card.css +0 -103
  91. package/src/charts/stat-card.svelte +0 -267
  92. package/src/charts/time-series-chart.css +0 -28
  93. package/src/charts/time-series-chart.svelte +0 -179
  94. package/src/charts/trend-pill.css +0 -38
  95. package/src/charts/trend-pill.svelte +0 -48
  96. package/src/lib/hooks/is-mobile.svelte.ts +0 -11
  97. package/src/showcase/ChartsSection.svelte +0 -458
  98. package/src/showcase/demos/ai/conversation.svelte +0 -59
@@ -1,131 +0,0 @@
1
- import { setContext, getContext } from "svelte";
2
-
3
- const STICK_TO_BOTTOM_CONTEXT_KEY = Symbol("stick-to-bottom-context");
4
-
5
- const SCROLL_THRESHOLD_PX = 200;
6
-
7
- export class StickToBottomContext {
8
- #element: HTMLElement | null = $state(null);
9
- #isAtBottom = $state(true);
10
- #userHasScrolled = $state(false);
11
- #resizeObserver: ResizeObserver | null = null;
12
- #mutationObserver: MutationObserver | null = null;
13
- #intersectionObserver: IntersectionObserver | null = null;
14
- #sentinel: HTMLElement | null = null;
15
-
16
- isAtBottom = $derived(this.#isAtBottom);
17
-
18
- setElement(element: HTMLElement) {
19
- this.#cleanup();
20
- this.#element = element;
21
- if (element) this.#setupObservers();
22
- }
23
-
24
- scrollToBottom = (behavior: ScrollBehavior = "smooth") => {
25
- if (!this.#element) return;
26
- this.#userHasScrolled = false;
27
- this.#element.scrollTo({ top: this.#element.scrollHeight, behavior });
28
- };
29
-
30
- destroy() {
31
- this.#cleanup();
32
- this.#element = null;
33
- }
34
-
35
- #handleScroll = () => {
36
- if (!this.#element) return;
37
- const { scrollTop, scrollHeight, clientHeight } = this.#element;
38
- const isAtBottom = scrollTop + clientHeight >= scrollHeight - SCROLL_THRESHOLD_PX;
39
- this.#isAtBottom = isAtBottom;
40
- if (!isAtBottom) {
41
- this.#userHasScrolled = true;
42
- } else if (this.#userHasScrolled) {
43
- this.#userHasScrolled = false;
44
- }
45
- };
46
-
47
- #setupObservers() {
48
- if (!this.#element) return;
49
-
50
- this.#sentinel = document.createElement("div");
51
- this.#sentinel.style.height = "1px";
52
- this.#sentinel.style.width = "100%";
53
- this.#sentinel.style.pointerEvents = "none";
54
- this.#sentinel.style.opacity = "0";
55
- this.#sentinel.setAttribute("data-stick-to-bottom-sentinel", "");
56
- this.#element.appendChild(this.#sentinel);
57
-
58
- this.#intersectionObserver = new IntersectionObserver(
59
- (entries) => {
60
- const entry = entries[0];
61
- if (entry?.isIntersecting) {
62
- this.#isAtBottom = true;
63
- this.#userHasScrolled = false;
64
- }
65
- },
66
- { threshold: 0, root: this.#element },
67
- );
68
- this.#intersectionObserver.observe(this.#sentinel);
69
-
70
- this.#element.addEventListener("scroll", this.#handleScroll, { passive: true });
71
-
72
- this.#resizeObserver = new ResizeObserver(() => {
73
- this.#checkPosition();
74
- if (this.#isAtBottom && !this.#userHasScrolled) {
75
- this.scrollToBottom("auto");
76
- }
77
- });
78
- this.#resizeObserver.observe(this.#element);
79
-
80
- this.#mutationObserver = new MutationObserver(() => {
81
- requestAnimationFrame(() => {
82
- const shouldAutoScroll = this.#isAtBottom && !this.#userHasScrolled;
83
- this.#checkPosition();
84
- if (shouldAutoScroll) this.scrollToBottom("smooth");
85
- });
86
- });
87
- this.#mutationObserver.observe(this.#element, {
88
- childList: true,
89
- subtree: true,
90
- characterData: true,
91
- });
92
-
93
- this.#checkPosition();
94
- }
95
-
96
- #checkPosition() {
97
- if (!this.#element) return;
98
- const { scrollTop, scrollHeight, clientHeight } = this.#element;
99
- this.#isAtBottom = scrollTop + clientHeight >= scrollHeight - SCROLL_THRESHOLD_PX;
100
- }
101
-
102
- #cleanup() {
103
- this.#resizeObserver?.disconnect();
104
- this.#mutationObserver?.disconnect();
105
- this.#intersectionObserver?.disconnect();
106
- if (this.#element) {
107
- this.#element.removeEventListener("scroll", this.#handleScroll);
108
- }
109
- if (this.#sentinel && this.#element?.contains(this.#sentinel)) {
110
- this.#element.removeChild(this.#sentinel);
111
- }
112
- this.#resizeObserver = null;
113
- this.#mutationObserver = null;
114
- this.#intersectionObserver = null;
115
- this.#sentinel = null;
116
- }
117
- }
118
-
119
- export function setStickToBottomContext(): StickToBottomContext {
120
- const context = new StickToBottomContext();
121
- setContext(STICK_TO_BOTTOM_CONTEXT_KEY, context);
122
- return context;
123
- }
124
-
125
- export function getStickToBottomContext(): StickToBottomContext {
126
- const context = getContext<StickToBottomContext>(STICK_TO_BOTTOM_CONTEXT_KEY);
127
- if (!context) {
128
- throw new Error("Conversation parts must be used inside a <Conversation>.");
129
- }
130
- return context;
131
- }
@@ -1,7 +0,0 @@
1
- /* Styles for AnimatedValue. Plain stylesheet (see area-chart.css for why).
2
- `.hr-animated-value` is the component's own namespaced fallback for tabular
3
- figures (it also carries the `tabular-nums` utility class). */
4
-
5
- .hr-animated-value {
6
- font-variant-numeric: tabular-nums;
7
- }
@@ -1,110 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./animated-value.css";
6
- import { untrack } from "svelte";
7
- import { cn } from "@heyramzi/utils";
8
-
9
- // =========================
10
- // TYPES
11
- // =========================
12
- type Props = {
13
- /** Target number. The display counts up to it on mount and tweens on change. */
14
- value: number;
15
- /** Fixed decimal places when no `formatOptions` is given. */
16
- decimals?: number;
17
- /** Count-up duration in ms. */
18
- duration?: number;
19
- /** Custom formatter for the live number. Wins over `formatOptions`/`decimals`. */
20
- format?: (value: number) => string;
21
- /** Intl number formatting (currency, compact, percent, ...). Wins over `decimals`. */
22
- formatOptions?: Intl.NumberFormatOptions;
23
- /** BCP-47 locale for `formatOptions`. */
24
- locale?: string;
25
- /** Rendered before the number. */
26
- prefix?: string;
27
- /** Rendered after the number. */
28
- suffix?: string;
29
- class?: string;
30
- style?: string;
31
- };
32
-
33
- // =========================
34
- // PROPS
35
- // =========================
36
- let {
37
- value,
38
- decimals = 0,
39
- duration = 800,
40
- format,
41
- formatOptions,
42
- locale,
43
- prefix,
44
- suffix,
45
- class: className,
46
- style,
47
- }: Props = $props();
48
-
49
- // =========================
50
- // STATE
51
- // =========================
52
- let displayed = $state(0);
53
- let started = $state(false);
54
-
55
- // =========================
56
- // DERIVED
57
- // =========================
58
- const formatter = $derived(formatOptions ? new Intl.NumberFormat(locale, formatOptions) : null);
59
- const currentValue = $derived(started ? displayed : value);
60
- const text = $derived(
61
- format
62
- ? format(currentValue)
63
- : formatter
64
- ? formatter.format(currentValue)
65
- : currentValue.toFixed(decimals),
66
- );
67
-
68
- // ========================= EFFECTS ========================= WHY: $effect only runs in the browser, so SSR renders the final value (no flash).
69
-
70
- // `value` is the sole tracked dependency; `displayed` is read and written inside untrack() so the per-frame writes don't retrigger the effect (that self-dependency loops and Svelte bails, freezing the number).
71
-
72
- // The first run sweeps from 0; later value changes tween from the current display.
73
- $effect(() => {
74
- const target = value;
75
- let raf = 0;
76
- untrack(() => {
77
- const reduced =
78
- typeof window !== "undefined" &&
79
- window.matchMedia("(prefers-reduced-motion: reduce)").matches;
80
-
81
- const from = started ? displayed : 0;
82
- started = true;
83
-
84
- // WHY: snap (no tween) when motion is off, the delta is tiny, or the tab
85
-
86
- // is hidden. rAF is paused in background tabs, so animating there would leave the number stale until the tab is shown; snapping keeps it correct.
87
- if (reduced || document.hidden || Math.abs(target - from) < 0.005) {
88
- displayed = target;
89
- return;
90
- }
91
-
92
- const start = performance.now();
93
- const tick = (now: number) => {
94
- const t = Math.min((now - start) / duration, 1);
95
- const eased = 1 - (1 - t) ** 3;
96
- displayed = from + (target - from) * eased;
97
- if (t < 1) raf = requestAnimationFrame(tick);
98
- };
99
- raf = requestAnimationFrame(tick);
100
- });
101
- return () => cancelAnimationFrame(raf);
102
- });
103
- </script>
104
-
105
- <!-- =========================== -->
106
- <!-- MARKUP -->
107
- <!-- =========================== -->
108
- <span class={cn("hr-animated-value tabular-nums", className)} {style}
109
- >{prefix ?? ""}{text}{suffix ?? ""}</span
110
- >
@@ -1,29 +0,0 @@
1
- /* Styles for AreaChart. 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-area-chart`. */
6
-
7
- .hr-area-chart {
8
- width: 100%;
9
- display: flex;
10
- flex-direction: column;
11
- gap: 0.5rem;
12
- }
13
- .hr-area-chart .hr-chart-body {
14
- width: 100%;
15
- }
16
-
17
- .hr-area-chart .empty {
18
- display: flex;
19
- align-items: center;
20
- justify-content: center;
21
- height: 100%;
22
- border: 1px dashed var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
23
- border-radius: 0.5rem;
24
- }
25
- .hr-area-chart .empty p {
26
- font-size: 0.8125rem;
27
- color: var(--chart-label, oklch(0.5 0.01 286));
28
- margin: 0;
29
- }
@@ -1,195 +0,0 @@
1
- <script lang="ts" generics="TData extends Record<string, unknown>">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./area-chart.css";
6
- import { Area, Axis, Chart, Highlight, LinearGradient, Spline, Svg, Tooltip } from "layerchart";
7
- import { curveMonotoneX } from "d3-shape";
8
- import Crosshair from "./crosshair.svelte";
9
- import EdgeFade from "./edge-fade.svelte";
10
- import { resolveEdgeFade } from "./edge-fade.js";
11
- import Figure from "./figure.svelte";
12
- import HoverFocus from "./hover-focus.svelte";
13
- import { DOMAIN_MOTION, TOOLTIP_GAP, TOOLTIP_TOP } from "./motion.js";
14
- import Reveal from "./reveal.svelte";
15
-
16
- // =========================
17
- // TYPES
18
- // =========================
19
- type Series = {
20
- key: keyof TData & string;
21
- label?: string;
22
- color?: string;
23
- };
24
-
25
- type Props = {
26
- data: TData[];
27
- xKey: keyof TData & string;
28
- yKey?: keyof TData & string;
29
- series?: Series[];
30
- color?: string;
31
- height?: number;
32
- stacked?: boolean;
33
- smooth?: boolean;
34
- /**
35
- * Softens the left and right ends of the filled area instead of cutting them off at the plot edge.
36
- */
37
-
38
- /*
39
- * `true` fades 8% of the width per side, a number sets the fraction (0-0.45), `false` keeps the hard edges.
40
- */
41
- edgeFade?: boolean | number;
42
- formatValue?: (v: number) => string;
43
- formatX?: (v: unknown) => string;
44
- xTicks?: number;
45
- class?: string;
46
- };
47
-
48
- // =========================
49
- // PROPS
50
- // =========================
51
- let {
52
- data,
53
- xKey,
54
- yKey,
55
- series: seriesInput,
56
- color = "var(--chart-1)",
57
- height = 240,
58
- stacked = false,
59
- smooth = true,
60
- edgeFade = true,
61
- formatValue = (v) => v.toLocaleString(),
62
- formatX = (v) => String(v),
63
- xTicks = 6,
64
- class: className,
65
- }: Props = $props();
66
-
67
- // =========================
68
- // STATE
69
- // =========================
70
- let active = $state<TData | null>(null);
71
-
72
- // ========================= FUNCTIONS ========================= WHY: called from the tooltip header's value expression so it re-runs on every active-point change; an unread {@const} side effect only evaluates once.
73
-
74
- // The microtask defers the state write out of template evaluation.
75
- function trackActive(d: TData): string {
76
- queueMicrotask(() => {
77
- active = d;
78
- });
79
- return formatX(d[xKey]);
80
- }
81
-
82
- // =========================
83
- // DERIVED
84
- // =========================
85
- const uid = `hr-area-${Math.random().toString(36).slice(2, 8)}`;
86
- const series = $derived(
87
- seriesInput && seriesInput.length > 0
88
- ? seriesInput.map((s, i) => ({
89
- key: s.key,
90
- label: s.label ?? s.key,
91
- color: s.color ?? `var(--chart-${(i % 5) + 1})`,
92
- }))
93
- : yKey
94
- ? [{ key: yKey, label: yKey, color }]
95
- : [],
96
- );
97
- const yMax = $derived.by(() => {
98
- if (stacked) {
99
- return Math.max(0, ...data.map((d) => series.reduce((s, k) => s + Number(d[k.key] ?? 0), 0)));
100
- }
101
- return Math.max(0, ...data.flatMap((d) => series.map((s) => Number(d[s.key] ?? 0))));
102
- });
103
- const curve = $derived(smooth ? curveMonotoneX : undefined);
104
- const fade = $derived(resolveEdgeFade(edgeFade));
105
- const fadeMask = $derived(fade > 0 ? `url(#${uid}-fade)` : undefined);
106
- const fallback = $derived(data.length > 0 ? data[data.length - 1] : null);
107
- const display = $derived(active ?? fallback);
108
- const figureRows = $derived(
109
- display
110
- ? series.map((s) => ({ value: Number(display[s.key]), label: s.label, color: s.color }))
111
- : null,
112
- );
113
- </script>
114
-
115
- <!-- =========================== -->
116
- <!-- AREA CHART -->
117
- <!-- =========================== -->
118
- <div
119
- class="hr-area-chart {className ?? ''}"
120
- role="img"
121
- aria-label="Area chart"
122
- onpointerleave={() => (active = null)}
123
- >
124
- {#if figureRows && display}
125
- <Figure series={figureRows} eyebrow={formatX(display[xKey])} format={formatValue} size="md" />
126
- {:else}
127
- <Figure value="—" eyebrow="Latest" size="md" />
128
- {/if}
129
-
130
- <div class="hr-chart-body" style:height="{height}px">
131
- {#if data.length === 0}
132
- <div class="empty"><p>No data</p></div>
133
- {:else}
134
- <Chart
135
- {data}
136
- x={xKey}
137
- y={series[0]?.key}
138
- yDomain={[0, yMax || 1]}
139
- yNice
140
- padding={{ left: 48, bottom: 28, top: 8, right: 12 }}
141
- tooltipContext={{ mode: "quadtree-x" }}
142
- motion={DOMAIN_MOTION}
143
- >
144
- <Svg>
145
- <Axis placement="left" format={(v) => formatValue(Number(v))} ticks={4} />
146
- <Axis placement="bottom" format={(v) => formatX(v)} ticks={xTicks} />
147
- <g class="hr-area-grid">
148
- <Axis placement="left" grid rule={false} ticks={4} format={() => ""} />
149
- </g>
150
- {#if fade > 0}
151
- <EdgeFade id="{uid}-fade" amount={fade} />
152
- {/if}
153
- <Crosshair id="{uid}-cross" />
154
- <HoverFocus>
155
- <Reveal>
156
- {#each series as s, i (s.key)}
157
- <LinearGradient id="{uid}-{i}" vertical>
158
- {#snippet stopsContent()}
159
- <stop offset="0%" stop-color={s.color} stop-opacity="0.45" />
160
- <stop offset="100%" stop-color={s.color} stop-opacity="0.04" />
161
- {/snippet}
162
- </LinearGradient>
163
- <!-- WHY `y1` and not `y`: layerchart's Area has no `y` prop, so every
164
- series drew the first series' shape and the gradients stacked on
165
- one path. See `Area.shared.svelte.js`. -->
166
- <Area y1={s.key} {curve} fill="url(#{uid}-{i})" mask={fadeMask} />
167
- <Spline y={s.key} {curve} style="stroke: {s.color}; fill: none" stroke-width="2" />
168
- {/each}
169
- </Reveal>
170
- {#snippet segment()}
171
- {#each series as s (s.key)}
172
- <Spline y={s.key} {curve} style="stroke: {s.color}; fill: none" stroke-width="2" />
173
- {/each}
174
- {/snippet}
175
- </HoverFocus>
176
- <Highlight points lines={{ style: `stroke: url(#${uid}-cross)` }} />
177
- </Svg>
178
- <Tooltip.Root x="data" y={TOOLTIP_TOP} xOffset={TOOLTIP_GAP}>
179
- {#snippet children({ data: d })}
180
- <Tooltip.Header value={trackActive(d as TData)} />
181
- <Tooltip.List>
182
- {#each series as s (s.key)}
183
- <Tooltip.Item
184
- label={s.label}
185
- value={formatValue(Number(d[s.key]))}
186
- color={s.color}
187
- />
188
- {/each}
189
- </Tooltip.List>
190
- {/snippet}
191
- </Tooltip.Root>
192
- </Chart>
193
- {/if}
194
- </div>
195
- </div>
@@ -1,119 +0,0 @@
1
- /* Styles for BarChart. Plain stylesheet (see area-chart.css for why). Selectors
2
- are namespaced under `.hr-bar-chart`. */
3
-
4
- /* WHY: self-contained visually-hidden styling so the data table reaches
5
- screen readers without depending on the consumer's CSS framework. */
6
- .hr-sr-only {
7
- position: absolute;
8
- width: 1px;
9
- height: 1px;
10
- padding: 0;
11
- margin: -1px;
12
- overflow: hidden;
13
- clip: rect(0, 0, 0, 0);
14
- white-space: nowrap;
15
- border: 0;
16
- }
17
- .hr-bar-chart {
18
- width: 100%;
19
- display: flex;
20
- flex-direction: column;
21
- gap: 0.5rem;
22
- }
23
- .hr-bar-chart .hr-chart-body {
24
- width: 100%;
25
- }
26
-
27
- /* WHY: height="fill" grows the chart to its parent's height (e.g. a card that
28
- stretches to match a taller sibling) instead of the fixed body height. The
29
- parent must be a flex column that gives this a determinate height. */
30
- .hr-bar-chart.hr-bar-fill {
31
- flex: 1 1 auto;
32
- min-height: 0;
33
- }
34
- .hr-bar-chart.hr-bar-fill .hr-chart-body {
35
- flex: 1 1 auto;
36
- min-height: 0;
37
- }
38
-
39
- .hr-bar-chart .empty {
40
- display: flex;
41
- align-items: center;
42
- justify-content: center;
43
- height: 100%;
44
- border: 1px dashed var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
45
- border-radius: 0.5rem;
46
- }
47
- .hr-bar-chart .empty p {
48
- font-size: 0.8125rem;
49
- color: var(--chart-label, oklch(0.5 0.01 286));
50
- margin: 0;
51
- }
52
-
53
- /* WHY: Bars render as <path> elements, not <rect>. Selectors target the path's class directly. */
54
- .hr-bar-chart .hr-bar-bars {
55
- transform-box: fill-box;
56
- transform-origin: bottom;
57
- animation: hr-bar-grow var(--chart-motion-reveal, 1100ms)
58
- var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
59
- transition: opacity var(--chart-motion-fast, 150ms) ease-in-out;
60
- }
61
- .hr-bar-chart .hr-bar-bars:nth-of-type(2) {
62
- animation-delay: var(--chart-stagger, 30ms);
63
- }
64
- .hr-bar-chart .hr-bar-bars:nth-of-type(3) {
65
- animation-delay: calc(var(--chart-stagger, 30ms) * 2);
66
- }
67
- .hr-bar-chart .hr-bar-bars:nth-of-type(4) {
68
- animation-delay: calc(var(--chart-stagger, 30ms) * 3);
69
- }
70
- .hr-bar-chart .hr-bar-bars:nth-of-type(5) {
71
- animation-delay: calc(var(--chart-stagger, 30ms) * 4);
72
- }
73
- .hr-bar-chart .hr-bar-bars:nth-of-type(6) {
74
- animation-delay: calc(var(--chart-stagger, 30ms) * 5);
75
- }
76
- .hr-bar-chart .hr-bar-bars:nth-of-type(7) {
77
- animation-delay: calc(var(--chart-stagger, 30ms) * 6);
78
- }
79
- .hr-bar-chart .hr-bar-bars:nth-of-type(8) {
80
- animation-delay: calc(var(--chart-stagger, 30ms) * 7);
81
- }
82
- .hr-bar-chart .hr-bar-bars:nth-of-type(9) {
83
- animation-delay: calc(var(--chart-stagger, 30ms) * 8);
84
- }
85
- .hr-bar-chart .hr-bar-bars:nth-of-type(10) {
86
- animation-delay: calc(var(--chart-stagger, 30ms) * 9);
87
- }
88
- .hr-bar-chart .hr-bar-bars:nth-of-type(11) {
89
- animation-delay: calc(var(--chart-stagger, 30ms) * 10);
90
- }
91
- .hr-bar-chart .hr-bar-bars:nth-of-type(n + 12) {
92
- animation-delay: calc(var(--chart-stagger, 30ms) * 11);
93
- }
94
- @keyframes hr-bar-grow {
95
- from {
96
- transform: scaleY(0);
97
- }
98
- }
99
- .hr-bar-horizontal .hr-bar-bars {
100
- transform-origin: left;
101
- animation-name: hr-bar-grow-x;
102
- }
103
- @keyframes hr-bar-grow-x {
104
- from {
105
- transform: scaleX(0);
106
- }
107
- }
108
- .hr-bar-chart.hr-bar-has-active .hr-bar-bars {
109
- opacity: 0.3;
110
- }
111
- .hr-bar-chart .hr-bar-active {
112
- pointer-events: none;
113
- }
114
-
115
- @media (prefers-reduced-motion: reduce) {
116
- .hr-bar-chart .hr-bar-bars {
117
- animation: none;
118
- }
119
- }