@heyramzi/ui 6.0.1 → 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 (114) hide show
  1. package/COMPONENTS.md +7 -26
  2. package/README.md +12 -5
  3. package/package.json +14 -16
  4. package/scripts/sync-shadcn.mjs +12 -3
  5. package/src/agents/agent-avatar.svelte +639 -0
  6. package/src/agents/agent-cast.svelte +173 -0
  7. package/src/agents/demo.svelte +78 -0
  8. package/src/agents/index.ts +11 -0
  9. package/src/agents/types.ts +72 -0
  10. package/{LICENSE → src/agents/vendor/LICENSE} +1 -1
  11. package/src/agents/vendor/color.ts +155 -0
  12. package/src/agents/vendor/draw.ts +840 -0
  13. package/src/agents/vendor/engine.ts +845 -0
  14. package/src/agents/vendor/mesh.ts +838 -0
  15. package/src/agents/vendor/plastic.ts +4958 -0
  16. package/src/agents/vendor/presets.ts +103 -0
  17. package/src/agents/vendor/shapes.ts +41 -0
  18. package/src/agents/vendor/ticker.ts +67 -0
  19. package/src/agents/vendor/types.ts +261 -0
  20. package/src/agents/vendor/wear.ts +1699 -0
  21. package/src/ai/index.ts +0 -18
  22. package/src/animations/README.md +3 -3
  23. package/src/lib/sliding-pill.ts +68 -0
  24. package/src/lib/theme-transition.ts +4 -2
  25. package/src/shadcn/code-block/code-block-actions.svelte +23 -0
  26. package/src/shadcn/code-block/code-block-content.svelte +55 -0
  27. package/src/shadcn/code-block/code-block-copy-button.svelte +59 -0
  28. package/src/shadcn/code-block/code-block-footer.svelte +20 -0
  29. package/src/shadcn/code-block/code-block-header.svelte +23 -0
  30. package/src/shadcn/code-block/code-block-line.svelte +71 -0
  31. package/src/shadcn/code-block/code-block-title.svelte +23 -0
  32. package/src/shadcn/code-block/code-block.svelte +47 -0
  33. package/src/shadcn/code-block/index.ts +42 -0
  34. package/src/shadcn/hooks/highlighter.ts +132 -0
  35. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
  36. package/src/shadcn/kbd/index.ts +10 -0
  37. package/src/shadcn/kbd/kbd-group.svelte +20 -0
  38. package/src/shadcn/kbd/kbd.svelte +23 -0
  39. package/src/shadcn/message-scroller/index.ts +28 -0
  40. package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
  41. package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
  42. package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
  43. package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
  44. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
  45. package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
  46. package/src/shadcn/tabs/tabs-list.svelte +13 -2
  47. package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
  48. package/src/shadcn/trace/index.ts +29 -0
  49. package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
  50. package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
  51. package/src/shadcn/trace/trace-collapsible.svelte +20 -0
  52. package/src/shadcn/trace/trace-description.svelte +20 -0
  53. package/src/shadcn/trace/trace-icon.svelte +24 -0
  54. package/src/shadcn/trace/trace-line.svelte +31 -0
  55. package/src/shadcn/trace/trace-title.svelte +20 -0
  56. package/src/shadcn/trace/trace.svelte +23 -0
  57. package/src/showcase/AiSection.svelte +0 -7
  58. package/src/showcase/demos/shadcn/code-block.svelte +80 -0
  59. package/src/showcase/demos/shadcn/index.ts +24 -0
  60. package/src/showcase/demos/shadcn/message-scroller.svelte +44 -0
  61. package/src/showcase/demos/shadcn/trace.svelte +51 -0
  62. package/src/showcase/kit.ts +1 -2
  63. package/src/ai/conversation/Conversation.svelte +0 -28
  64. package/src/ai/conversation/ConversationContent.svelte +0 -35
  65. package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
  66. package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
  67. package/src/ai/conversation/index.ts +0 -33
  68. package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
  69. package/src/charts/animated-value.css +0 -7
  70. package/src/charts/animated-value.svelte +0 -110
  71. package/src/charts/area-chart.css +0 -29
  72. package/src/charts/area-chart.svelte +0 -195
  73. package/src/charts/bar-chart.css +0 -119
  74. package/src/charts/bar-chart.svelte +0 -269
  75. package/src/charts/charts.css +0 -201
  76. package/src/charts/crosshair.svelte +0 -35
  77. package/src/charts/donut-chart.css +0 -56
  78. package/src/charts/donut-chart.svelte +0 -158
  79. package/src/charts/edge-fade.svelte +0 -43
  80. package/src/charts/edge-fade.ts +0 -16
  81. package/src/charts/figure.css +0 -100
  82. package/src/charts/figure.svelte +0 -122
  83. package/src/charts/gauge-chart.css +0 -94
  84. package/src/charts/gauge-chart.svelte +0 -303
  85. package/src/charts/hover-band.test.ts +0 -30
  86. package/src/charts/hover-band.ts +0 -21
  87. package/src/charts/hover-focus.svelte +0 -85
  88. package/src/charts/index.ts +0 -30
  89. package/src/charts/legend.css +0 -131
  90. package/src/charts/legend.svelte +0 -112
  91. package/src/charts/line-chart.css +0 -26
  92. package/src/charts/line-chart.svelte +0 -256
  93. package/src/charts/motion.ts +0 -19
  94. package/src/charts/number-chart.css +0 -168
  95. package/src/charts/number-chart.svelte +0 -177
  96. package/src/charts/primitives/chart-container.svelte +0 -57
  97. package/src/charts/primitives/chart-style.svelte +0 -36
  98. package/src/charts/primitives/chart-tooltip.svelte +0 -161
  99. package/src/charts/primitives/chart-utils.ts +0 -54
  100. package/src/charts/primitives/index.ts +0 -11
  101. package/src/charts/reveal.svelte +0 -61
  102. package/src/charts/ring-chart.css +0 -96
  103. package/src/charts/ring-chart.svelte +0 -158
  104. package/src/charts/sparkline.css +0 -45
  105. package/src/charts/sparkline.svelte +0 -140
  106. package/src/charts/stat-card.css +0 -103
  107. package/src/charts/stat-card.svelte +0 -267
  108. package/src/charts/time-series-chart.css +0 -28
  109. package/src/charts/time-series-chart.svelte +0 -179
  110. package/src/charts/trend-pill.css +0 -38
  111. package/src/charts/trend-pill.svelte +0 -48
  112. package/src/lib/hooks/is-mobile.svelte.ts +0 -11
  113. package/src/showcase/ChartsSection.svelte +0 -458
  114. package/src/showcase/demos/ai/conversation.svelte +0 -59
@@ -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
- }