@epilot/spark-ui 1.0.0-alpha.3 → 1.0.0-alpha.5
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/dist/accent-colors-Ch2J0EEY.js +13 -0
- package/dist/accent-colors-Ch2J0EEY.js.map +1 -0
- package/dist/accent-colors-CjHCf8fH.cjs +18 -0
- package/dist/accent-colors-CjHCf8fH.cjs.map +1 -0
- package/dist/components/accordion.cjs +1 -1
- package/dist/components/accordion.d.ts +1 -1
- package/dist/components/accordion.js +1 -1
- package/dist/components/attachment.d.cts +1 -1
- package/dist/components/attachment.d.ts +1 -1
- package/dist/components/badge.cjs +12 -7
- package/dist/components/badge.cjs.map +1 -1
- package/dist/components/badge.d.cts +5 -1
- package/dist/components/badge.d.ts +5 -1
- package/dist/components/badge.js +12 -7
- package/dist/components/badge.js.map +1 -1
- package/dist/components/bar-chart.cjs +60 -56
- package/dist/components/bar-chart.cjs.map +1 -1
- package/dist/components/bar-chart.d.cts +7 -2
- package/dist/components/bar-chart.d.ts +7 -2
- package/dist/components/bar-chart.js +60 -56
- package/dist/components/bar-chart.js.map +1 -1
- package/dist/components/battery-progress-row.cjs +10 -16
- package/dist/components/battery-progress-row.cjs.map +1 -1
- package/dist/components/battery-progress-row.d.cts +1 -1
- package/dist/components/battery-progress-row.d.ts +2 -2
- package/dist/components/battery-progress-row.js +10 -16
- package/dist/components/battery-progress-row.js.map +1 -1
- package/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/button.cjs +5 -2
- package/dist/components/button.cjs.map +1 -1
- package/dist/components/button.d.cts +16 -7
- package/dist/components/button.d.ts +16 -7
- package/dist/components/button.js +5 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/callout.d.cts +1 -1
- package/dist/components/callout.d.ts +1 -1
- package/dist/components/card.d.cts +1 -1
- package/dist/components/card.d.ts +1 -1
- package/dist/components/chart.cjs +1 -1
- package/dist/components/chart.cjs.map +1 -1
- package/dist/components/chart.js +1 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.cjs +30 -23
- package/dist/components/checkbox.cjs.map +1 -1
- package/dist/components/checkbox.d.cts +5 -1
- package/dist/components/checkbox.d.ts +5 -1
- package/dist/components/checkbox.js +30 -23
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/circular-gauge.d.ts +1 -1
- package/dist/components/day-of-week-picker.cjs +2 -2
- package/dist/components/day-of-week-picker.cjs.map +1 -1
- package/dist/components/day-of-week-picker.js +2 -2
- package/dist/components/day-of-week-picker.js.map +1 -1
- package/dist/components/device-card.d.ts +1 -1
- package/dist/components/donut-chart.cjs +3 -3
- package/dist/components/donut-chart.cjs.map +1 -1
- package/dist/components/donut-chart.js +3 -3
- package/dist/components/donut-chart.js.map +1 -1
- package/dist/components/energy-bar-chart.cjs +2 -1
- package/dist/components/energy-bar-chart.cjs.map +1 -1
- package/dist/components/energy-bar-chart.d.ts +1 -1
- package/dist/components/energy-bar-chart.js +2 -1
- package/dist/components/energy-bar-chart.js.map +1 -1
- package/dist/components/energy-breakdown.cjs +1 -1
- package/dist/components/energy-breakdown.cjs.map +1 -1
- package/dist/components/energy-breakdown.js +1 -1
- package/dist/components/energy-breakdown.js.map +1 -1
- package/dist/components/energy-history-chart.cjs +14 -6
- package/dist/components/energy-history-chart.cjs.map +1 -1
- package/dist/components/energy-history-chart.d.cts +11 -3
- package/dist/components/energy-history-chart.d.ts +11 -3
- package/dist/components/energy-history-chart.js +14 -6
- package/dist/components/energy-history-chart.js.map +1 -1
- package/dist/components/feed-in-hero.d.ts +1 -1
- package/dist/components/field.cjs +1 -1
- package/dist/components/field.d.cts +1 -1
- package/dist/components/field.d.ts +1 -1
- package/dist/components/field.js +1 -1
- package/dist/components/info-block.d.cts +1 -1
- package/dist/components/info-block.d.ts +1 -1
- package/dist/components/input-group.cjs +1 -1
- package/dist/components/input-group.js +1 -1
- package/dist/components/input-otp.cjs +1 -1
- package/dist/components/input-otp.js +1 -1
- package/dist/components/input.cjs +1 -1
- package/dist/components/input.d.cts +1 -1
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/line-chart.cjs +61 -19
- package/dist/components/line-chart.cjs.map +1 -1
- package/dist/components/line-chart.d.cts +7 -0
- package/dist/components/line-chart.d.ts +7 -0
- package/dist/components/line-chart.js +61 -19
- package/dist/components/line-chart.js.map +1 -1
- package/dist/components/metering-row.d.ts +1 -1
- package/dist/components/money-saved-block.cjs +1 -1
- package/dist/components/money-saved-block.cjs.map +1 -1
- package/dist/components/money-saved-block.js +1 -1
- package/dist/components/money-saved-block.js.map +1 -1
- package/dist/components/number-flow.d.ts +1 -1
- package/dist/components/power-history-chart.cjs +23 -44
- package/dist/components/power-history-chart.cjs.map +1 -1
- package/dist/components/power-history-chart.d.cts +10 -1
- package/dist/components/power-history-chart.d.ts +10 -1
- package/dist/components/power-history-chart.js +24 -45
- package/dist/components/power-history-chart.js.map +1 -1
- package/dist/components/power-history.cjs +1 -1
- package/dist/components/power-history.cjs.map +1 -1
- package/dist/components/power-history.js +1 -1
- package/dist/components/power-history.js.map +1 -1
- package/dist/components/power-network.cjs +36 -11
- package/dist/components/power-network.cjs.map +1 -1
- package/dist/components/power-network.js +36 -11
- package/dist/components/power-network.js.map +1 -1
- package/dist/components/progress.cjs +1 -1
- package/dist/components/progress.cjs.map +1 -1
- package/dist/components/progress.js +1 -1
- package/dist/components/progress.js.map +1 -1
- package/dist/components/radio-card-group.cjs +26 -1
- package/dist/components/radio-card-group.cjs.map +1 -1
- package/dist/components/radio-card-group.js +26 -1
- package/dist/components/radio-card-group.js.map +1 -1
- package/dist/components/radio.cjs +11 -4
- package/dist/components/radio.cjs.map +1 -1
- package/dist/components/radio.d.cts +15 -3
- package/dist/components/radio.d.ts +15 -3
- package/dist/components/radio.js +11 -4
- package/dist/components/radio.js.map +1 -1
- package/dist/components/sankey-chart.cjs +12 -45
- package/dist/components/sankey-chart.cjs.map +1 -1
- package/dist/components/sankey-chart.d.cts +8 -0
- package/dist/components/sankey-chart.d.ts +9 -1
- package/dist/components/sankey-chart.js +12 -45
- package/dist/components/sankey-chart.js.map +1 -1
- package/dist/components/schedule-list.cjs +3 -3
- package/dist/components/schedule-list.cjs.map +1 -1
- package/dist/components/schedule-list.js +3 -3
- package/dist/components/schedule-list.js.map +1 -1
- package/dist/components/segmented-control.cjs +44 -0
- package/dist/components/segmented-control.cjs.map +1 -0
- package/dist/components/segmented-control.d.cts +33 -0
- package/dist/components/segmented-control.d.ts +33 -0
- package/dist/components/segmented-control.js +40 -0
- package/dist/components/segmented-control.js.map +1 -0
- package/dist/components/select.cjs +1 -1
- package/dist/components/select.d.cts +1 -1
- package/dist/components/select.d.ts +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/separator.cjs +1 -1
- package/dist/components/separator.cjs.map +1 -1
- package/dist/components/separator.js +1 -1
- package/dist/components/separator.js.map +1 -1
- package/dist/components/series-legend.cjs +1 -1
- package/dist/components/series-legend.cjs.map +1 -1
- package/dist/components/series-legend.js +1 -1
- package/dist/components/series-legend.js.map +1 -1
- package/dist/components/slider.cjs +6 -19
- package/dist/components/slider.cjs.map +1 -1
- package/dist/components/slider.d.cts +0 -13
- package/dist/components/slider.d.ts +0 -13
- package/dist/components/slider.js +6 -19
- package/dist/components/slider.js.map +1 -1
- package/dist/components/solar-forecast-chart.cjs +4 -3
- package/dist/components/solar-forecast-chart.cjs.map +1 -1
- package/dist/components/solar-forecast-chart.d.cts +4 -1
- package/dist/components/solar-forecast-chart.d.ts +4 -1
- package/dist/components/solar-forecast-chart.js +4 -3
- package/dist/components/solar-forecast-chart.js.map +1 -1
- package/dist/components/stepper.cjs +2 -2
- package/dist/components/stepper.cjs.map +1 -1
- package/dist/components/stepper.js +2 -2
- package/dist/components/stepper.js.map +1 -1
- package/dist/components/switch.cjs +8 -3
- package/dist/components/switch.cjs.map +1 -1
- package/dist/components/switch.d.cts +9 -4
- package/dist/components/switch.d.ts +9 -4
- package/dist/components/switch.js +8 -3
- package/dist/components/switch.js.map +1 -1
- package/dist/components/tabs.cjs +4 -4
- package/dist/components/tabs.cjs.map +1 -1
- package/dist/components/tabs.js +4 -4
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/theme.cjs +1 -1
- package/dist/components/theme.d.cts +1 -1
- package/dist/components/theme.d.ts +1 -1
- package/dist/components/theme.js +1 -1
- package/dist/components/time-picker.cjs +1 -1
- package/dist/components/time-picker.cjs.map +1 -1
- package/dist/components/time-picker.js +1 -1
- package/dist/components/time-picker.js.map +1 -1
- package/dist/components/tooltip.cjs +1 -1
- package/dist/components/tooltip.cjs.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/hooks/use-reduced-motion.cjs +22 -0
- package/dist/hooks/use-reduced-motion.cjs.map +1 -0
- package/dist/hooks/use-reduced-motion.d.cts +7 -0
- package/dist/hooks/use-reduced-motion.d.ts +7 -0
- package/dist/hooks/use-reduced-motion.js +20 -0
- package/dist/hooks/use-reduced-motion.js.map +1 -0
- package/dist/{input-box-variants-Dr1K7uYE.js → input-box-variants-DcajYXvo.js} +1 -1
- package/dist/{input-box-variants-Dr1K7uYE.js.map → input-box-variants-DcajYXvo.js.map} +1 -1
- package/dist/{input-box-variants-BMDdwvqg.cjs → input-box-variants-ZCtgs-1S.cjs} +1 -1
- package/dist/{input-box-variants-BMDdwvqg.cjs.map → input-box-variants-ZCtgs-1S.cjs.map} +1 -1
- package/dist/{number-flow-DSbucQ_K.d.ts → number-flow-D9zsxH0-.d.ts} +1 -1
- package/dist/styles/index.css +39 -0
- package/dist/styles/spark-default.css +123 -40
- package/dist/styles/theme.css +78 -0
- package/dist/{theme-B3WtiByA.js → theme-8PDO9E0S.js} +2 -2
- package/dist/theme-8PDO9E0S.js.map +1 -0
- package/dist/{theme-C9_OSe0S.cjs → theme-ClRJhexF.cjs} +2 -2
- package/dist/theme-ClRJhexF.cjs.map +1 -0
- package/dist/{theme-types-DoROIXpi.d.ts → theme-types-BDdoRxXa.d.ts} +2 -3
- package/dist/{theme-types-r20EJfEC.d.cts → theme-types-BwSJKdsB.d.cts} +2 -3
- package/dist/{tones-DyCkL71h.d.ts → tones-PXJGlSpe.d.ts} +1 -1
- package/dist/utils/build-sankey-nodes.cjs +16 -3
- package/dist/utils/build-sankey-nodes.cjs.map +1 -1
- package/dist/utils/build-sankey-nodes.js +16 -3
- package/dist/utils/build-sankey-nodes.js.map +1 -1
- package/dist/utils/chart-scale.cjs +43 -0
- package/dist/utils/chart-scale.cjs.map +1 -0
- package/dist/utils/chart-scale.d.cts +22 -0
- package/dist/utils/chart-scale.d.ts +22 -0
- package/dist/utils/chart-scale.js +41 -0
- package/dist/utils/chart-scale.js.map +1 -0
- package/package.json +1 -1
- package/dist/theme-B3WtiByA.js.map +0 -1
- package/dist/theme-C9_OSe0S.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-history-chart.js","names":["valueFormat"],"sources":["../../src/components/power-history-chart.tsx"],"sourcesContent":["import { curveMonotoneX } from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { scaleLinear } from '@visx/scale'\nimport { Area, LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { cn } from '../utils/cn'\nimport {\n integerFormat,\n oneDecimalFormat,\n twoDecimalFormat as valueFormat,\n} from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\nexport interface PowerHistoryPoint {\n /** Epoch milliseconds. */\n t: number\n solar: number\n /** Signed: positive = import, negative = feed-in. */\n grid: number\n /** Signed: positive = discharge, negative = charge. */\n battery: number\n consumption: number\n}\n\nexport interface PowerHistoryChartProps {\n data: PowerHistoryPoint[]\n /** Axis/tooltip unit, e.g. \"kW\". */\n unit: string\n formatX: (t: number) => string\n formatValue?: (value: number) => string\n labels: {\n solar: string\n grid: string\n battery: string\n consumption: string\n }\n /** Epoch ms to mark with the current-time dashed line (omit to hide). */\n highlightAt?: number\n title?: string\n className?: string\n height?: number\n}\n\nconst AREA_KEYS = ['solar', 'grid', 'battery'] as const\n\n// All toggleable series, in legend order. The areas (solar/grid/battery) plus\n// the consumption overlay line.\nconst SERIES_KEYS = ['solar', 'grid', 'battery', 'consumption'] as const\ntype SeriesKey = (typeof SERIES_KEYS)[number]\n\nconst SERIES_COLORS = {\n solar: 'var(--orange-a9)',\n grid: 'var(--blue-9)',\n battery: 'var(--green-a9)',\n consumption: 'var(--gray-12)',\n} as const\n\nconst PADDING_TOP = 20\nconst PADDING_BOTTOM = 28\nconst PADDING_RIGHT = 8\nconst Y_AXIS_GUTTER = 34\nconst X_TICK_COUNT = 6\nconst DEFAULT_HEIGHT = 260\n\nconst tBisector = bisector<PowerHistoryPoint, number>((d) => d.t)\n\nfunction niceStep(rough: number) {\n const normalized = rough === 0 ? 1 : rough\n const magnitude = Math.pow(10, Math.floor(Math.log10(normalized)))\n const steps = [1, 2, 2.5, 5, 10]\n return (\n (steps.find((value) => value * magnitude >= normalized) ?? 10) * magnitude\n )\n}\n\n// Nice y ticks that always include zero, supporting negative values (feed-in /\n// battery charging dip below the baseline).\nfunction buildAreaYTicks(min: number, max: number) {\n const step = niceStep((max - min || 1) / 4)\n const lo = Math.floor(min / step) * step\n const hi = Math.ceil(max / step) * step\n const ticks: number[] = []\n for (let value = lo; value <= hi + step * 0.001; value += step) {\n ticks.push(Number(value.toFixed(6)))\n }\n return { ticks, lo, hi }\n}\n\nfunction formatYTick(value: number) {\n return Math.abs(value) >= 10\n ? integerFormat.format(value)\n : oneDecimalFormat.format(value)\n}\n\n/**\n * Diverging \"power sources\" area chart over a continuous time axis: solar\n * (orange), grid (blue) and battery (green) areas around a zero baseline, with\n * consumption overlaid as a line. Negative values (grid feed-in, battery\n * charging) dip below zero. For instantaneous power (day view). A legend at the\n * top toggles each series on/off; the y-axis rescales to the visible ones. visx.\n */\nexport function PowerHistoryChart({\n data,\n unit,\n formatX,\n formatValue,\n labels,\n highlightAt,\n title,\n className,\n height = DEFAULT_HEIGHT,\n}: PowerHistoryChartProps) {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const isHidden = useCallback(\n (key: SeriesKey) => hiddenSeries.has(key),\n [hiddenSeries],\n )\n\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<PowerHistoryPoint>()\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const fmtValue = (value: number) =>\n formatValue\n ? formatValue(value)\n : `${valueFormat.format(value)}${NARROW_NBSP}${unit}`\n\n const tMin = data.length ? (data[0]?.t ?? 0) : 0\n const tMax = data.length ? (data[data.length - 1]?.t ?? 0) : 0\n\n const { yMin, yMax } = useMemo(() => {\n let min = 0\n let max = 0\n for (const d of data) {\n if (!hiddenSeries.has('solar')) max = Math.max(max, d.solar)\n if (!hiddenSeries.has('consumption')) max = Math.max(max, d.consumption)\n if (!hiddenSeries.has('grid')) {\n max = Math.max(max, d.grid)\n min = Math.min(min, d.grid)\n }\n if (!hiddenSeries.has('battery')) {\n max = Math.max(max, d.battery)\n min = Math.min(min, d.battery)\n }\n }\n return { yMin: min, yMax: max }\n }, [data, hiddenSeries])\n\n const {\n ticks: yTicks,\n lo,\n hi,\n } = useMemo(() => buildAreaYTicks(yMin, yMax), [yMin, yMax])\n\n const plotRight = Math.max(Y_AXIS_GUTTER, width - PADDING_RIGHT)\n\n const xScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [tMin, tMax === tMin ? tMin + 1 : tMax],\n range: [Y_AXIS_GUTTER, plotRight],\n }),\n [tMin, tMax, plotRight],\n )\n\n const yScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [lo, hi === lo ? lo + 1 : hi],\n range: [height - PADDING_BOTTOM, PADDING_TOP],\n }),\n [lo, hi, height],\n )\n\n const xTicks = useMemo(() => {\n if (tMax <= tMin) return [tMin]\n return Array.from(\n { length: X_TICK_COUNT },\n (_, i) => tMin + (i / (X_TICK_COUNT - 1)) * (tMax - tMin),\n )\n }, [tMin, tMax])\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point || data.length === 0) return\n\n const xMs = xScale.invert(point.x)\n const index = tBisector.left(data, xMs, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return\n\n const d = d1 && xMs - d0.t > d1.t - xMs ? d1 : d0\n showTooltip({\n tooltipData: d,\n tooltipLeft: xScale(d.t),\n tooltipTop: PADDING_TOP,\n })\n },\n [data, showTooltip, xScale],\n )\n\n const legendItems: SeriesLegendItem[] = SERIES_KEYS.map((key) => ({\n id: key,\n label: labels[key],\n color: SERIES_COLORS[key],\n }))\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? SERIES_KEYS.filter((key) => !isHidden(key)).map((key) => ({\n key,\n label: labels[key],\n color: SERIES_COLORS[key],\n value: fmtValue(tooltipData[key]),\n }))\n : []\n\n const showHighlight =\n highlightAt !== undefined && highlightAt >= tMin && highlightAt <= tMax\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? PADDING_TOP,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n return (\n <div className={cn('flex w-full flex-col gap-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n <span className=\"px-1 text-xs text-gray-light\">{unit}</span>\n <div ref={containerRef} className=\"relative w-full\" style={{ height }}>\n {width > 0 && (\n <svg width={width} height={height}>\n <defs>\n {AREA_KEYS.map((key) => (\n <linearGradient\n key={key}\n id={`power-history-fill-${key}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.04}\n />\n </linearGradient>\n ))}\n </defs>\n\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--gray-a4)\"\n strokeWidth={1}\n />\n <text\n x={Y_AXIS_GUTTER - 7}\n y={y + 3}\n textAnchor=\"end\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n >\n {formatYTick(tick)}\n </text>\n </g>\n )\n })}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <Area<PowerHistoryPoint>\n key={`area-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y0={yScale(0)}\n y1={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n fill={`url(#power-history-fill-${key})`}\n stroke=\"transparent\"\n />\n ))}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <LinePath<PowerHistoryPoint>\n key={`line-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS[key]}\n strokeWidth={1.5}\n fill=\"none\"\n />\n ))}\n\n {!isHidden('consumption') && (\n <LinePath<PowerHistoryPoint>\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d.consumption)}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS.consumption}\n strokeWidth={1.5}\n fill=\"none\"\n />\n )}\n\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={yScale(0)}\n y2={yScale(0)}\n stroke=\"var(--slate-a6)\"\n strokeWidth={1}\n />\n\n {showHighlight && (\n <line\n x1={xScale(highlightAt)}\n x2={xScale(highlightAt)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--spark-a9)\"\n strokeWidth={1.5}\n strokeDasharray=\"5 4\"\n pointerEvents=\"none\"\n />\n )}\n\n {xTicks.map((tick) => (\n <text\n key={`x-tick-${tick}`}\n x={xScale(tick)}\n y={height - PADDING_BOTTOM + 16}\n textAnchor=\"middle\"\n fill=\"var(--gray-a11)\"\n fontSize={10}\n >\n {formatX(tick)}\n </text>\n ))}\n\n {tooltipOpen && tooltipData && (\n <>\n <line\n x1={xScale(tooltipData.t)}\n x2={xScale(tooltipData.t)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--slate-a6)\"\n strokeDasharray=\"4 4\"\n pointerEvents=\"none\"\n />\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <circle\n key={`dot-${key}`}\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData[key])}\n r={3.5}\n fill={SERIES_COLORS[key]}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n ))}\n {!isHidden('consumption') && (\n <circle\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData.consumption)}\n r={3.5}\n fill={SERIES_COLORS.consumption}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n )}\n </>\n )}\n\n <rect\n x={Y_AXIS_GUTTER}\n y={PADDING_TOP}\n width={Math.max(0, plotRight - Y_AXIS_GUTTER)}\n height={Math.max(0, height - PADDING_BOTTOM - PADDING_TOP)}\n fill=\"transparent\"\n onMouseMove={handleTooltip}\n onMouseLeave={hideTooltip}\n onTouchMove={handleTooltip}\n onTouchEnd={hideTooltip}\n />\n </svg>\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity: tooltipOpen && tooltipData ? 1 : 0,\n transition:\n 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={formatX(tooltipData.t)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"power-history-chart.js","names":["valueFormat"],"sources":["../../src/components/power-history-chart.tsx"],"sourcesContent":["import { curveMonotoneX } from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { scaleLinear } from '@visx/scale'\nimport { Area, LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useReducedMotion } from '../hooks/use-reduced-motion'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { buildYAxisScale, formatYAxisTick } from '../utils/chart-scale'\nimport { cn } from '../utils/cn'\nimport { twoDecimalFormat as valueFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\nexport interface PowerHistoryPoint {\n /** Epoch milliseconds. */\n t: number\n solar: number\n /** Signed: positive = import, negative = feed-in. */\n grid: number\n /** Signed: positive = discharge, negative = charge. */\n battery: number\n consumption: number\n}\n\nexport interface PowerHistoryChartProps {\n data: PowerHistoryPoint[]\n /** Axis/tooltip unit, e.g. \"kW\". */\n unit: string\n formatX: (t: number) => string\n /** Tooltip time formatter. Defaults to `formatX`; use to keep the tooltip's\n * full clock time when the axis ticks are compact (hour-only). */\n formatTooltipX?: (t: number) => string\n formatValue?: (value: number) => string\n labels: {\n solar: string\n grid: string\n battery: string\n consumption: string\n }\n /** Series to drop entirely (no legend entry, area, or tooltip row). */\n omitSeries?: readonly PowerHistorySeriesKey[]\n /** Epoch ms to mark with the current-time dashed line (omit to hide). */\n highlightAt?: number\n title?: string\n className?: string\n height?: number\n}\n\nconst AREA_KEYS = ['solar', 'grid', 'battery'] as const\n\n// All toggleable series, in legend order. The areas (solar/grid/battery) plus\n// the consumption overlay line.\nconst SERIES_KEYS = ['solar', 'grid', 'battery', 'consumption'] as const\nexport type PowerHistorySeriesKey = (typeof SERIES_KEYS)[number]\ntype SeriesKey = PowerHistorySeriesKey\n\nconst SERIES_COLORS = {\n solar: 'var(--orange-a9)',\n grid: 'var(--blue-9)',\n battery: 'var(--green-a9)',\n consumption: 'var(--neutral-12)',\n} as const\n\nconst PADDING_TOP = 20\nconst PADDING_BOTTOM = 28\nconst PADDING_RIGHT = 8\nconst Y_AXIS_GUTTER = 34\nconst X_TICK_COUNT = 6\nconst DEFAULT_HEIGHT = 260\n\nconst tBisector = bisector<PowerHistoryPoint, number>((d) => d.t)\n\n/**\n * Diverging \"power sources\" area chart over a continuous time axis: solar\n * (orange), grid (blue) and battery (green) areas around a zero baseline, with\n * consumption overlaid as a line. Negative values (grid feed-in, battery\n * charging) dip below zero. For instantaneous power (day view). A legend at the\n * top toggles each series on/off; the y-axis rescales to the visible ones. visx.\n */\nexport function PowerHistoryChart({\n data,\n unit,\n formatX,\n formatTooltipX,\n formatValue,\n labels,\n omitSeries,\n highlightAt,\n title,\n className,\n height = DEFAULT_HEIGHT,\n}: PowerHistoryChartProps) {\n const fmtTooltipX = formatTooltipX ?? formatX\n const containerRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const [width, setWidth] = useState(0)\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n const omitted = useMemo(() => new Set<SeriesKey>(omitSeries), [omitSeries])\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const isHidden = useCallback(\n (key: SeriesKey) => omitted.has(key) || hiddenSeries.has(key),\n [hiddenSeries, omitted],\n )\n\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<PowerHistoryPoint>()\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const fmtValue = (value: number) =>\n formatValue\n ? formatValue(value)\n : `${valueFormat.format(value)}${NARROW_NBSP}${unit}`\n\n const tMin = data.length ? (data[0]?.t ?? 0) : 0\n const tMax = data.length ? (data[data.length - 1]?.t ?? 0) : 0\n\n const { yMin, yMax } = useMemo(() => {\n let min = 0\n let max = 0\n for (const d of data) {\n if (!isHidden('solar')) max = Math.max(max, d.solar)\n if (!isHidden('consumption')) max = Math.max(max, d.consumption)\n if (!isHidden('grid')) {\n max = Math.max(max, d.grid)\n min = Math.min(min, d.grid)\n }\n if (!isHidden('battery')) {\n max = Math.max(max, d.battery)\n min = Math.min(min, d.battery)\n }\n }\n return { yMin: min, yMax: max }\n }, [data, isHidden])\n\n const {\n ticks: yTicks,\n min: lo,\n max: hi,\n } = useMemo(() => buildYAxisScale(yMin, yMax), [yMin, yMax])\n\n const plotRight = Math.max(Y_AXIS_GUTTER, width - PADDING_RIGHT)\n\n const xScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [tMin, tMax === tMin ? tMin + 1 : tMax],\n range: [Y_AXIS_GUTTER, plotRight],\n }),\n [tMin, tMax, plotRight],\n )\n\n const yScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [lo, hi === lo ? lo + 1 : hi],\n range: [height - PADDING_BOTTOM, PADDING_TOP],\n }),\n [lo, hi, height],\n )\n\n const xTicks = useMemo(() => {\n if (tMax <= tMin) return [tMin]\n return Array.from(\n { length: X_TICK_COUNT },\n (_, i) => tMin + (i / (X_TICK_COUNT - 1)) * (tMax - tMin),\n )\n }, [tMin, tMax])\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point || data.length === 0) return\n\n const xMs = xScale.invert(point.x)\n const index = tBisector.left(data, xMs, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return\n\n const d = d1 && xMs - d0.t > d1.t - xMs ? d1 : d0\n showTooltip({\n tooltipData: d,\n tooltipLeft: xScale(d.t),\n tooltipTop: PADDING_TOP,\n })\n },\n [data, showTooltip, xScale],\n )\n\n const legendItems: SeriesLegendItem[] = SERIES_KEYS.filter(\n (key) => !omitted.has(key),\n ).map((key) => ({\n id: key,\n label: labels[key],\n color: SERIES_COLORS[key],\n }))\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? SERIES_KEYS.filter((key) => !isHidden(key)).map((key) => ({\n key,\n label: labels[key],\n color: SERIES_COLORS[key],\n value: fmtValue(tooltipData[key]),\n }))\n : []\n\n const showHighlight =\n highlightAt !== undefined && highlightAt >= tMin && highlightAt <= tMax\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? PADDING_TOP,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n return (\n <div className={cn('flex w-full flex-col gap-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n <span className=\"px-1 text-xs text-gray-light\">{unit}</span>\n <div ref={containerRef} className=\"relative w-full\" style={{ height }}>\n {width > 0 && (\n <svg width={width} height={height}>\n <defs>\n {AREA_KEYS.map((key) => (\n <linearGradient\n key={key}\n id={`power-history-fill-${key}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.04}\n />\n </linearGradient>\n ))}\n </defs>\n\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--neutral-a4)\"\n strokeWidth={1}\n />\n <text\n x={Y_AXIS_GUTTER - 7}\n y={y + 3}\n textAnchor=\"end\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n >\n {formatYAxisTick(tick)}\n </text>\n </g>\n )\n })}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <Area<PowerHistoryPoint>\n key={`area-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y0={yScale(0)}\n y1={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n fill={`url(#power-history-fill-${key})`}\n stroke=\"transparent\"\n />\n ))}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <LinePath<PowerHistoryPoint>\n key={`line-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS[key]}\n strokeWidth={1.5}\n fill=\"none\"\n />\n ))}\n\n {!isHidden('consumption') && (\n <LinePath<PowerHistoryPoint>\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d.consumption)}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS.consumption}\n strokeWidth={1.5}\n fill=\"none\"\n />\n )}\n\n <line\n x1={Y_AXIS_GUTTER}\n x2={plotRight}\n y1={yScale(0)}\n y2={yScale(0)}\n stroke=\"var(--neutral-a6)\"\n strokeWidth={1}\n />\n\n {showHighlight && (\n <line\n x1={xScale(highlightAt)}\n x2={xScale(highlightAt)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--spark-a9)\"\n strokeWidth={1.5}\n strokeDasharray=\"5 4\"\n pointerEvents=\"none\"\n />\n )}\n\n {xTicks.map((tick) => (\n <text\n key={`x-tick-${tick}`}\n x={xScale(tick)}\n y={height - PADDING_BOTTOM + 16}\n textAnchor=\"middle\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n >\n {formatX(tick)}\n </text>\n ))}\n\n {tooltipOpen && tooltipData && (\n <>\n <line\n x1={xScale(tooltipData.t)}\n x2={xScale(tooltipData.t)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--neutral-a6)\"\n strokeDasharray=\"4 4\"\n pointerEvents=\"none\"\n />\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <circle\n key={`dot-${key}`}\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData[key])}\n r={3.5}\n fill={SERIES_COLORS[key]}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n ))}\n {!isHidden('consumption') && (\n <circle\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData.consumption)}\n r={3.5}\n fill={SERIES_COLORS.consumption}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n )}\n </>\n )}\n\n <rect\n x={Y_AXIS_GUTTER}\n y={PADDING_TOP}\n width={Math.max(0, plotRight - Y_AXIS_GUTTER)}\n height={Math.max(0, height - PADDING_BOTTOM - PADDING_TOP)}\n fill=\"transparent\"\n onMouseMove={handleTooltip}\n onMouseLeave={hideTooltip}\n onTouchMove={handleTooltip}\n onTouchEnd={hideTooltip}\n />\n </svg>\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity: tooltipOpen && tooltipData ? 1 : 0,\n transition: reducedMotion\n ? 'opacity 140ms ease-out'\n : 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={fmtTooltipX(tooltipData.t)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-history.cjs","names":["useContainerWidth","SectionTitle","SeriesLegend","LineChart","kwFormat"],"sources":["../../src/components/power-history.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport { useContainerWidth } from '../hooks/use-container-width'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { oneDecimalFormat as kwFormat } from '../utils/format'\nimport { LineChart } from './line-chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\ninterface PowerHistorySeries {\n id: string\n label: string\n /** CSS color value (e.g. `var(--orange-a9)`). */\n color: string\n /** Tailwind stroke class applied to the line (e.g. `stroke-orange-a9`). */\n strokeClass: string\n data: { date: Date; value: number }[]\n}\n\n/** Visual config (colors/classes) per series, independent of labels. */\nconst SERIES_STYLE: ReadonlyArray<\n Pick<PowerHistorySeries, 'id' | 'color' | 'strokeClass'>\n> = [\n {\n id: 'production',\n color: 'var(--orange-a9)',\n strokeClass: 'stroke-orange-a9',\n },\n {\n id: 'feedin',\n color: 'var(--green-a9)',\n strokeClass: 'stroke-green-a9',\n },\n {\n id: 'grid',\n color: 'var(--blue-11)',\n strokeClass: 'stroke-blue-11',\n },\n {\n id: 'consumption',\n color: 'var(--
|
|
1
|
+
{"version":3,"file":"power-history.cjs","names":["useContainerWidth","SectionTitle","SeriesLegend","LineChart","kwFormat"],"sources":["../../src/components/power-history.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport { useContainerWidth } from '../hooks/use-container-width'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { oneDecimalFormat as kwFormat } from '../utils/format'\nimport { LineChart } from './line-chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\ninterface PowerHistorySeries {\n id: string\n label: string\n /** CSS color value (e.g. `var(--orange-a9)`). */\n color: string\n /** Tailwind stroke class applied to the line (e.g. `stroke-orange-a9`). */\n strokeClass: string\n data: { date: Date; value: number }[]\n}\n\n/** Visual config (colors/classes) per series, independent of labels. */\nconst SERIES_STYLE: ReadonlyArray<\n Pick<PowerHistorySeries, 'id' | 'color' | 'strokeClass'>\n> = [\n {\n id: 'production',\n color: 'var(--orange-a9)',\n strokeClass: 'stroke-orange-a9',\n },\n {\n id: 'feedin',\n color: 'var(--green-a9)',\n strokeClass: 'stroke-green-a9',\n },\n {\n id: 'grid',\n color: 'var(--blue-11)',\n strokeClass: 'stroke-blue-11',\n },\n {\n id: 'consumption',\n color: 'var(--neutral-a7)',\n strokeClass: 'stroke-gray-a7',\n },\n]\n\ninterface PowerHistorySeriesLabels {\n production?: React.ReactNode\n feedIn?: React.ReactNode\n grid?: React.ReactNode\n consumption?: React.ReactNode\n}\n\nexport default function PowerHistory({\n solar,\n consumption,\n feedIn,\n grid,\n formatDate,\n title = 'Power history',\n seriesLabels,\n}: {\n solar: { date: Date; value: number }[]\n consumption: { date: Date; value: number }[]\n feedIn: { date: Date; value: number }[]\n grid: { date: Date; value: number }[]\n formatDate: (date: Date) => string\n /** Override the section title. Defaults to `\"Power history\"`. */\n title?: React.ReactNode\n /** Override the legend labels for each series. Each defaults to its English name. */\n seriesLabels?: PowerHistorySeriesLabels\n}) {\n const [chartContainerRef, chartWidth] = useContainerWidth<HTMLDivElement>()\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const dataById = useMemo(\n () => ({\n production: solar,\n feedin: feedIn,\n grid,\n consumption,\n }),\n [consumption, feedIn, grid, solar],\n )\n\n const labelById = useMemo<Record<string, React.ReactNode>>(\n () => ({\n production: seriesLabels?.production ?? 'Production',\n feedin: seriesLabels?.feedIn ?? 'Feed in',\n grid: seriesLabels?.grid ?? 'Grid',\n consumption: seriesLabels?.consumption ?? 'Consumption',\n }),\n [seriesLabels],\n )\n\n const legendItems: SeriesLegendItem[] = useMemo(\n () =>\n SERIES_STYLE.map((entry) => ({\n id: entry.id,\n label: labelById[entry.id] as string,\n color: entry.color,\n })),\n [labelById],\n )\n\n const visibleSeries = useMemo(\n () =>\n SERIES_STYLE.filter((entry) => !hiddenSeries.has(entry.id)).map(\n (entry) => ({\n id: entry.id,\n data: dataById[entry.id as keyof typeof dataById],\n curve: 'monotone' as const,\n className: entry.strokeClass,\n }),\n ),\n [dataById, hiddenSeries],\n )\n\n const dataEmpty =\n solar.every((s) => s.value === 0) &&\n consumption.every((c) => c.value === 0) &&\n feedIn.every((f) => f.value === 0) &&\n grid.every((g) => g.value === 0)\n\n if (dataEmpty) return null\n\n return (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-center justify-between\">\n <SectionTitle>{title}</SectionTitle>\n </div>\n\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n\n <div\n ref={chartContainerRef}\n className=\"w-full\"\n style={{ minHeight: 200 }}\n >\n {chartWidth > 0 && visibleSeries.length > 0 && (\n <LineChart\n width={chartWidth}\n height={200}\n margin={{ top: 8, right: 8, bottom: 28, left: 8 }}\n series={visibleSeries}\n showGrid\n showTooltip\n xTickCount={5}\n yTickCount={4}\n formatDate={formatDate}\n formatValue={(v) => `${kwFormat.format(v / 1000)}${NARROW_NBSP}kW`}\n />\n )}\n </div>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAM,eAEF;CACF;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACF;AASD,SAAwB,aAAa,EACnC,OACA,aACA,QACA,MACA,YACA,QAAQ,iBACR,gBAWC;CACD,MAAM,CAAC,mBAAmB,cAAcA,kCAAAA,mBAAmC;CAC3E,MAAM,CAAC,cAAc,oBAAA,GAAA,MAAA,gCACb,IAAI,KAAK,CAChB;CAED,MAAM,gBAAA,GAAA,MAAA,cAA4B,OAAe;AAC/C,mBAAiB,SAAS;GACxB,MAAM,OAAO,IAAI,IAAI,KAAK;AAC1B,OAAI,KAAK,IAAI,GAAG,CACd,MAAK,OAAO,GAAG;OAEf,MAAK,IAAI,GAAG;AAEd,UAAO;IACP;IACD,EAAE,CAAC;CAEN,MAAM,YAAA,GAAA,MAAA,gBACG;EACL,YAAY;EACZ,QAAQ;EACR;EACA;EACD,GACD;EAAC;EAAa;EAAQ;EAAM;EAAM,CACnC;CAED,MAAM,aAAA,GAAA,MAAA,gBACG;EACL,YAAY,cAAc,cAAc;EACxC,QAAQ,cAAc,UAAU;EAChC,MAAM,cAAc,QAAQ;EAC5B,aAAa,cAAc,eAAe;EAC3C,GACD,CAAC,aAAa,CACf;CAED,MAAM,eAAA,GAAA,MAAA,eAEF,aAAa,KAAK,WAAW;EAC3B,IAAI,MAAM;EACV,OAAO,UAAU,MAAM;EACvB,OAAO,MAAM;EACd,EAAE,EACL,CAAC,UAAU,CACZ;CAED,MAAM,iBAAA,GAAA,MAAA,eAEF,aAAa,QAAQ,UAAU,CAAC,aAAa,IAAI,MAAM,GAAG,CAAC,CAAC,KACzD,WAAW;EACV,IAAI,MAAM;EACV,MAAM,SAAS,MAAM;EACrB,OAAO;EACP,WAAW,MAAM;EAClB,EACF,EACH,CAAC,UAAU,aAAa,CACzB;AAQD,KALE,MAAM,OAAO,MAAM,EAAE,UAAU,EAAE,IACjC,YAAY,OAAO,MAAM,EAAE,UAAU,EAAE,IACvC,OAAO,OAAO,MAAM,EAAE,UAAU,EAAE,IAClC,KAAK,OAAO,MAAM,EAAE,UAAU,EAAE,CAEnB,QAAO;AAEtB,QACE,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,WAAU;YAAf;GACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,WAAU;cACb,iBAAA,GAAA,kBAAA,KAACC,8BAAAA,cAAD,EAAA,UAAe,OAAqB,CAAA;IAChC,CAAA;GAEN,iBAAA,GAAA,kBAAA,KAACC,iCAAAA,cAAD;IACE,OAAO;IACP,WAAW;IACX,UAAU;IACV,CAAA;GAEF,iBAAA,GAAA,kBAAA,KAAC,OAAD;IACE,KAAK;IACL,WAAU;IACV,OAAO,EAAE,WAAW,KAAK;cAExB,aAAa,KAAK,cAAc,SAAS,KACxC,iBAAA,GAAA,kBAAA,KAACC,8BAAAA,WAAD;KACE,OAAO;KACP,QAAQ;KACR,QAAQ;MAAE,KAAK;MAAG,OAAO;MAAG,QAAQ;MAAI,MAAM;MAAG;KACjD,QAAQ;KACR,UAAA;KACA,aAAA;KACA,YAAY;KACZ,YAAY;KACA;KACZ,cAAc,MAAM,GAAGC,qBAAAA,iBAAS,OAAO,IAAI,IAAK;KAChD,CAAA;IAEA,CAAA;GACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-history.js","names":["kwFormat"],"sources":["../../src/components/power-history.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport { useContainerWidth } from '../hooks/use-container-width'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { oneDecimalFormat as kwFormat } from '../utils/format'\nimport { LineChart } from './line-chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\ninterface PowerHistorySeries {\n id: string\n label: string\n /** CSS color value (e.g. `var(--orange-a9)`). */\n color: string\n /** Tailwind stroke class applied to the line (e.g. `stroke-orange-a9`). */\n strokeClass: string\n data: { date: Date; value: number }[]\n}\n\n/** Visual config (colors/classes) per series, independent of labels. */\nconst SERIES_STYLE: ReadonlyArray<\n Pick<PowerHistorySeries, 'id' | 'color' | 'strokeClass'>\n> = [\n {\n id: 'production',\n color: 'var(--orange-a9)',\n strokeClass: 'stroke-orange-a9',\n },\n {\n id: 'feedin',\n color: 'var(--green-a9)',\n strokeClass: 'stroke-green-a9',\n },\n {\n id: 'grid',\n color: 'var(--blue-11)',\n strokeClass: 'stroke-blue-11',\n },\n {\n id: 'consumption',\n color: 'var(--
|
|
1
|
+
{"version":3,"file":"power-history.js","names":["kwFormat"],"sources":["../../src/components/power-history.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport { useContainerWidth } from '../hooks/use-container-width'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { oneDecimalFormat as kwFormat } from '../utils/format'\nimport { LineChart } from './line-chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\ninterface PowerHistorySeries {\n id: string\n label: string\n /** CSS color value (e.g. `var(--orange-a9)`). */\n color: string\n /** Tailwind stroke class applied to the line (e.g. `stroke-orange-a9`). */\n strokeClass: string\n data: { date: Date; value: number }[]\n}\n\n/** Visual config (colors/classes) per series, independent of labels. */\nconst SERIES_STYLE: ReadonlyArray<\n Pick<PowerHistorySeries, 'id' | 'color' | 'strokeClass'>\n> = [\n {\n id: 'production',\n color: 'var(--orange-a9)',\n strokeClass: 'stroke-orange-a9',\n },\n {\n id: 'feedin',\n color: 'var(--green-a9)',\n strokeClass: 'stroke-green-a9',\n },\n {\n id: 'grid',\n color: 'var(--blue-11)',\n strokeClass: 'stroke-blue-11',\n },\n {\n id: 'consumption',\n color: 'var(--neutral-a7)',\n strokeClass: 'stroke-gray-a7',\n },\n]\n\ninterface PowerHistorySeriesLabels {\n production?: React.ReactNode\n feedIn?: React.ReactNode\n grid?: React.ReactNode\n consumption?: React.ReactNode\n}\n\nexport default function PowerHistory({\n solar,\n consumption,\n feedIn,\n grid,\n formatDate,\n title = 'Power history',\n seriesLabels,\n}: {\n solar: { date: Date; value: number }[]\n consumption: { date: Date; value: number }[]\n feedIn: { date: Date; value: number }[]\n grid: { date: Date; value: number }[]\n formatDate: (date: Date) => string\n /** Override the section title. Defaults to `\"Power history\"`. */\n title?: React.ReactNode\n /** Override the legend labels for each series. Each defaults to its English name. */\n seriesLabels?: PowerHistorySeriesLabels\n}) {\n const [chartContainerRef, chartWidth] = useContainerWidth<HTMLDivElement>()\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const dataById = useMemo(\n () => ({\n production: solar,\n feedin: feedIn,\n grid,\n consumption,\n }),\n [consumption, feedIn, grid, solar],\n )\n\n const labelById = useMemo<Record<string, React.ReactNode>>(\n () => ({\n production: seriesLabels?.production ?? 'Production',\n feedin: seriesLabels?.feedIn ?? 'Feed in',\n grid: seriesLabels?.grid ?? 'Grid',\n consumption: seriesLabels?.consumption ?? 'Consumption',\n }),\n [seriesLabels],\n )\n\n const legendItems: SeriesLegendItem[] = useMemo(\n () =>\n SERIES_STYLE.map((entry) => ({\n id: entry.id,\n label: labelById[entry.id] as string,\n color: entry.color,\n })),\n [labelById],\n )\n\n const visibleSeries = useMemo(\n () =>\n SERIES_STYLE.filter((entry) => !hiddenSeries.has(entry.id)).map(\n (entry) => ({\n id: entry.id,\n data: dataById[entry.id as keyof typeof dataById],\n curve: 'monotone' as const,\n className: entry.strokeClass,\n }),\n ),\n [dataById, hiddenSeries],\n )\n\n const dataEmpty =\n solar.every((s) => s.value === 0) &&\n consumption.every((c) => c.value === 0) &&\n feedIn.every((f) => f.value === 0) &&\n grid.every((g) => g.value === 0)\n\n if (dataEmpty) return null\n\n return (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-center justify-between\">\n <SectionTitle>{title}</SectionTitle>\n </div>\n\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n\n <div\n ref={chartContainerRef}\n className=\"w-full\"\n style={{ minHeight: 200 }}\n >\n {chartWidth > 0 && visibleSeries.length > 0 && (\n <LineChart\n width={chartWidth}\n height={200}\n margin={{ top: 8, right: 8, bottom: 28, left: 8 }}\n series={visibleSeries}\n showGrid\n showTooltip\n xTickCount={5}\n yTickCount={4}\n formatDate={formatDate}\n formatValue={(v) => `${kwFormat.format(v / 1000)}${NARROW_NBSP}kW`}\n />\n )}\n </div>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;AAmBA,MAAM,eAEF;CACF;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACD;EACE,IAAI;EACJ,OAAO;EACP,aAAa;EACd;CACF;AASD,SAAwB,aAAa,EACnC,OACA,aACA,QACA,MACA,YACA,QAAQ,iBACR,gBAWC;CACD,MAAM,CAAC,mBAAmB,cAAc,mBAAmC;CAC3E,MAAM,CAAC,cAAc,mBAAmB,+BAChC,IAAI,KAAK,CAChB;CAED,MAAM,eAAe,aAAa,OAAe;AAC/C,mBAAiB,SAAS;GACxB,MAAM,OAAO,IAAI,IAAI,KAAK;AAC1B,OAAI,KAAK,IAAI,GAAG,CACd,MAAK,OAAO,GAAG;OAEf,MAAK,IAAI,GAAG;AAEd,UAAO;IACP;IACD,EAAE,CAAC;CAEN,MAAM,WAAW,eACR;EACL,YAAY;EACZ,QAAQ;EACR;EACA;EACD,GACD;EAAC;EAAa;EAAQ;EAAM;EAAM,CACnC;CAED,MAAM,YAAY,eACT;EACL,YAAY,cAAc,cAAc;EACxC,QAAQ,cAAc,UAAU;EAChC,MAAM,cAAc,QAAQ;EAC5B,aAAa,cAAc,eAAe;EAC3C,GACD,CAAC,aAAa,CACf;CAED,MAAM,cAAkC,cAEpC,aAAa,KAAK,WAAW;EAC3B,IAAI,MAAM;EACV,OAAO,UAAU,MAAM;EACvB,OAAO,MAAM;EACd,EAAE,EACL,CAAC,UAAU,CACZ;CAED,MAAM,gBAAgB,cAElB,aAAa,QAAQ,UAAU,CAAC,aAAa,IAAI,MAAM,GAAG,CAAC,CAAC,KACzD,WAAW;EACV,IAAI,MAAM;EACV,MAAM,SAAS,MAAM;EACrB,OAAO;EACP,WAAW,MAAM;EAClB,EACF,EACH,CAAC,UAAU,aAAa,CACzB;AAQD,KALE,MAAM,OAAO,MAAM,EAAE,UAAU,EAAE,IACjC,YAAY,OAAO,MAAM,EAAE,UAAU,EAAE,IACvC,OAAO,OAAO,MAAM,EAAE,UAAU,EAAE,IAClC,KAAK,OAAO,MAAM,EAAE,UAAU,EAAE,CAEnB,QAAO;AAEtB,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,cAAD,EAAA,UAAe,OAAqB,CAAA;IAChC,CAAA;GAEN,oBAAC,cAAD;IACE,OAAO;IACP,WAAW;IACX,UAAU;IACV,CAAA;GAEF,oBAAC,OAAD;IACE,KAAK;IACL,WAAU;IACV,OAAO,EAAE,WAAW,KAAK;cAExB,aAAa,KAAK,cAAc,SAAS,KACxC,oBAAC,WAAD;KACE,OAAO;KACP,QAAQ;KACR,QAAQ;MAAE,KAAK;MAAG,OAAO;MAAG,QAAQ;MAAI,MAAM;MAAG;KACjD,QAAQ;KACR,UAAA;KACA,aAAA;KACA,YAAY;KACZ,YAAY;KACA;KACZ,cAAc,MAAM,GAAGA,iBAAS,OAAO,IAAI,IAAK;KAChD,CAAA;IAEA,CAAA;GACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
require("../chunk-aJGBMT86.cjs");
|
|
4
|
+
const require_hooks_use_reduced_motion = require("../hooks/use-reduced-motion.cjs");
|
|
4
5
|
const require_icons_material_symbol = require("../icons/material-symbol.cjs");
|
|
5
6
|
const require_components_typography = require("./typography.cjs");
|
|
6
7
|
const require_components_number_flow = require("./number-flow.cjs");
|
|
@@ -26,14 +27,14 @@ const SOURCE_VARIANTS = {
|
|
|
26
27
|
initialDashGap: 7
|
|
27
28
|
},
|
|
28
29
|
neutral: {
|
|
29
|
-
activeClassName: "border-
|
|
30
|
-
flowStroke: "var(--
|
|
30
|
+
activeClassName: "border-gray-a9 bg-gray-a1 shadow-gray-a3",
|
|
31
|
+
flowStroke: "var(--neutral-a9)",
|
|
31
32
|
initialDashGap: 9
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
const DEVICE_FLOW_STROKE = "var(--
|
|
35
|
+
const DEVICE_FLOW_STROKE = "var(--neutral-a9)";
|
|
35
36
|
const DEVICE_INITIAL_DASH_GAP = 9;
|
|
36
|
-
const SKELETON_STROKE = "var(--
|
|
37
|
+
const SKELETON_STROKE = "var(--neutral-a6)";
|
|
37
38
|
const STROKE_WIDTH = 2;
|
|
38
39
|
const STRAIGHT_TOLERANCE_PX = 3;
|
|
39
40
|
const SOURCE_HOME_OFFSET_STEP_PX = 10;
|
|
@@ -81,6 +82,7 @@ function connectorPathsEqual(a, b) {
|
|
|
81
82
|
}
|
|
82
83
|
function PowerNetworkDiagram({ title, loading = false, className, children }) {
|
|
83
84
|
const containerRef = (0, react.useRef)(null);
|
|
85
|
+
const reducedMotion = require_hooks_use_reduced_motion.useReducedMotion();
|
|
84
86
|
const [nodes, setNodes] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
85
87
|
const [connectorPaths, setConnectorPaths] = (0, react.useState)([]);
|
|
86
88
|
const connectorPathsRef = (0, react.useRef)([]);
|
|
@@ -192,7 +194,7 @@ function PowerNetworkDiagram({ title, loading = false, className, children }) {
|
|
|
192
194
|
targetFlowVisualsRef.current = targets;
|
|
193
195
|
}, [nodes, totalSourceWatts]);
|
|
194
196
|
(0, react.useEffect)(() => {
|
|
195
|
-
if (loading) return;
|
|
197
|
+
if (loading || reducedMotion) return;
|
|
196
198
|
let frameId = 0;
|
|
197
199
|
let lastFrameTime = performance.now();
|
|
198
200
|
const animate = (now) => {
|
|
@@ -225,7 +227,23 @@ function PowerNetworkDiagram({ title, loading = false, className, children }) {
|
|
|
225
227
|
};
|
|
226
228
|
frameId = window.requestAnimationFrame(animate);
|
|
227
229
|
return () => window.cancelAnimationFrame(frameId);
|
|
228
|
-
}, [loading]);
|
|
230
|
+
}, [loading, reducedMotion]);
|
|
231
|
+
(0, react.useEffect)(() => {
|
|
232
|
+
if (loading || !reducedMotion) return;
|
|
233
|
+
for (const connectorPath of connectorPathsRef.current) {
|
|
234
|
+
const target = targetFlowVisualsRef.current[connectorPath.id];
|
|
235
|
+
const pathElement = flowPathRefs.current[connectorPath.id];
|
|
236
|
+
if (!target || !pathElement) continue;
|
|
237
|
+
pathElement.setAttribute("stroke-dasharray", `${FLOW_DASH_SOLID} ${target.gap.toFixed(3)}`);
|
|
238
|
+
pathElement.setAttribute("stroke-dashoffset", "0");
|
|
239
|
+
pathElement.setAttribute("opacity", target.opacity.toFixed(3));
|
|
240
|
+
}
|
|
241
|
+
}, [
|
|
242
|
+
loading,
|
|
243
|
+
reducedMotion,
|
|
244
|
+
nodes,
|
|
245
|
+
totalSourceWatts
|
|
246
|
+
]);
|
|
229
247
|
const contextValue = (0, react.useMemo)(() => ({
|
|
230
248
|
loading,
|
|
231
249
|
register,
|
|
@@ -259,7 +277,7 @@ function PowerNetworkDiagram({ title, loading = false, className, children }) {
|
|
|
259
277
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
260
278
|
d: path.d,
|
|
261
279
|
fill: "none",
|
|
262
|
-
stroke: "var(--
|
|
280
|
+
stroke: "var(--neutral-a1)",
|
|
263
281
|
strokeWidth: STROKE_WIDTH,
|
|
264
282
|
strokeLinecap: "round"
|
|
265
283
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
@@ -273,7 +291,8 @@ function PowerNetworkDiagram({ title, loading = false, className, children }) {
|
|
|
273
291
|
},
|
|
274
292
|
strokeDasharray: `${FLOW_DASH_SOLID} ${initialDashGap}`,
|
|
275
293
|
strokeDashoffset: 0,
|
|
276
|
-
opacity: 0
|
|
294
|
+
opacity: 0,
|
|
295
|
+
style: reducedMotion ? { transition: "opacity 300ms ease" } : void 0
|
|
277
296
|
})] }, path.id);
|
|
278
297
|
})
|
|
279
298
|
}),
|
|
@@ -394,7 +413,7 @@ function PowerDevice({ id: idProp, watts, icon, className }) {
|
|
|
394
413
|
className: (0, cnfast.cn)("flex flex-col items-center justify-center gap-1", className),
|
|
395
414
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
396
415
|
ref: setElement,
|
|
397
|
-
className: "flex aspect-square items-center justify-center rounded-circle border border-gray bg-
|
|
416
|
+
className: "flex aspect-square items-center justify-center rounded-circle border border-gray bg-gray-a1 p-4",
|
|
398
417
|
children: icon
|
|
399
418
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_number_flow, {
|
|
400
419
|
value: watts,
|
|
@@ -447,6 +466,7 @@ function PowerNetworkHome({ watts, autarky, className }) {
|
|
|
447
466
|
});
|
|
448
467
|
}
|
|
449
468
|
function HomeSection({ anchorRef, autarky, className, watts }) {
|
|
469
|
+
const reducedMotion = require_hooks_use_reduced_motion.useReducedMotion();
|
|
450
470
|
const targetAutarky = autarky == null ? 0 : Math.max(0, Math.min(100, autarky * 100));
|
|
451
471
|
const [animatedAutarky, setAnimatedAutarky] = (0, react.useState)(targetAutarky);
|
|
452
472
|
const animatedAutarkyRef = (0, react.useRef)(animatedAutarky);
|
|
@@ -459,6 +479,11 @@ function HomeSection({ anchorRef, autarky, className, watts }) {
|
|
|
459
479
|
animatedAutarkyRef.current = animatedAutarky;
|
|
460
480
|
}, [animatedAutarky]);
|
|
461
481
|
(0, react.useEffect)(() => {
|
|
482
|
+
if (reducedMotion) {
|
|
483
|
+
animatedAutarkyRef.current = targetAutarky;
|
|
484
|
+
setAnimatedAutarky(targetAutarky);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
462
487
|
let frameId = 0;
|
|
463
488
|
let lastTime = performance.now();
|
|
464
489
|
const animate = (now) => {
|
|
@@ -476,7 +501,7 @@ function HomeSection({ anchorRef, autarky, className, watts }) {
|
|
|
476
501
|
};
|
|
477
502
|
frameId = window.requestAnimationFrame(animate);
|
|
478
503
|
return () => window.cancelAnimationFrame(frameId);
|
|
479
|
-
}, [targetAutarky]);
|
|
504
|
+
}, [targetAutarky, reducedMotion]);
|
|
480
505
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
481
506
|
ref: anchorRef,
|
|
482
507
|
className: (0, cnfast.cn)("relative mx-auto size-[78px]", className),
|
|
@@ -488,7 +513,7 @@ function HomeSection({ anchorRef, autarky, className, watts }) {
|
|
|
488
513
|
cy: nodeSize / 2,
|
|
489
514
|
r: radius,
|
|
490
515
|
fill: "none",
|
|
491
|
-
stroke: "var(--
|
|
516
|
+
stroke: "var(--neutral-a5)",
|
|
492
517
|
strokeWidth: ringStrokeWidth
|
|
493
518
|
}), progressLength > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
494
519
|
cx: nodeSize / 2,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"power-network.cjs","names":["SectionTitle","NumberFlow","Skeleton","MaterialSymbol"],"sources":["../../src/components/power-network.tsx"],"sourcesContent":["import {\n createContext,\n type ReactElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { MaterialSymbol } from '../icons/material-symbol'\nimport { cn } from '../utils/cn'\nimport NumberFlow from './number-flow'\nimport { Skeleton } from './skeleton'\nimport { SectionTitle } from './typography'\n\n// --- Variants -----------------------------------------------------------\n\nexport type PowerSourceVariant = 'grid' | 'solar' | 'battery' | 'neutral'\n\ninterface SourceVariantStyles {\n activeClassName: string\n flowStroke: string\n initialDashGap: number\n}\n\nconst SOURCE_VARIANTS: Record<PowerSourceVariant, SourceVariantStyles> = {\n grid: {\n activeClassName: 'border-blue-a9 bg-blue-a1 shadow-blue-a4',\n flowStroke: 'var(--blue-11)',\n initialDashGap: 10,\n },\n solar: {\n activeClassName: 'border-orange-a9 bg-orange-a1 shadow-orange-a3',\n flowStroke: 'var(--orange-a9)',\n initialDashGap: 5,\n },\n battery: {\n activeClassName: 'border-green-a9 bg-green-a1 shadow-green-a3',\n flowStroke: 'var(--green-a9)',\n initialDashGap: 7,\n },\n neutral: {\n activeClassName: 'border-slate-a9 bg-slate-a1 shadow-slate-a3',\n flowStroke: 'var(--slate-a9)',\n initialDashGap: 9,\n },\n}\n\nconst DEVICE_FLOW_STROKE = 'var(--slate-a9)'\nconst DEVICE_INITIAL_DASH_GAP = 9\n\nconst SKELETON_STROKE = 'var(--gray-a6)'\nconst STROKE_WIDTH = 2\nconst STRAIGHT_TOLERANCE_PX = 3\nconst SOURCE_HOME_OFFSET_STEP_PX = 10\nconst DEVICE_HOME_OFFSET_STEP_PX = 10\n\n// --- Flow animation parameters ------------------------------------------\n\nconst FLOW_DASH_SOLID = 1\nconst FLOW_GAP_MAX = 14\nconst FLOW_GAP_MIN = 5\nconst FLOW_SPEED_MIN = 10\nconst FLOW_SPEED_MAX = 56\nconst FLOW_SMOOTHING = 5.5\n\ninterface FlowVisual {\n gap: number\n speed: number\n opacity: number\n}\n\ninterface FlowAnimationState extends FlowVisual {\n offset: number\n}\n\n// --- Context ------------------------------------------------------------\n\ntype NodeKind = 'source' | 'device' | 'home'\n\ninterface RegisteredNode {\n id: string\n kind: NodeKind\n variant?: PowerSourceVariant\n watts: number\n element: HTMLDivElement | null\n}\n\ninterface PowerNetworkContextValue {\n loading: boolean\n register: (node: RegisteredNode) => void\n unregister: (id: string) => void\n totalSourceWatts: number\n}\n\nconst PowerNetworkContext = createContext<PowerNetworkContextValue | null>(null)\n\nfunction usePowerNetwork(): PowerNetworkContextValue {\n const ctx = useContext(PowerNetworkContext)\n if (!ctx) {\n throw new Error(\n 'Power network sub-components must be used inside <PowerNetworkDiagram>.',\n )\n }\n return ctx\n}\n\n// --- Geometry helpers ---------------------------------------------------\n\ninterface Point {\n x: number\n y: number\n}\n\nfunction anchorTop(rect: DOMRect, containerRect: DOMRect): Point {\n return {\n x: rect.left + rect.width / 2 - containerRect.left,\n y: rect.top - containerRect.top,\n }\n}\n\nfunction anchorBottom(rect: DOMRect, containerRect: DOMRect): Point {\n return {\n x: rect.left + rect.width / 2 - containerRect.left,\n y: rect.bottom - containerRect.top,\n }\n}\n\nfunction buildAdaptivePath(from: Point, to: Point): string {\n if (Math.abs(from.x - to.x) < STRAIGHT_TOLERANCE_PX) {\n return `M ${from.x} ${from.y} L ${to.x} ${to.y}`\n }\n const dy = to.y - from.y\n const c1y = from.y + dy * 0.56\n const c2y = to.y - dy * 0.58\n return `M ${from.x} ${from.y} C ${from.x} ${c1y} ${to.x} ${c2y} ${to.x} ${to.y}`\n}\n\n// --- PowerNetworkDiagram ------------------------------------------------\n\nexport interface PowerNetworkDiagramProps {\n title?: string\n loading?: boolean\n className?: string\n children?: ReactNode\n}\n\ninterface ConnectorPath {\n id: string\n d: string\n kind: NodeKind\n variant?: PowerSourceVariant\n reversed: boolean\n}\n\nfunction connectorPathsEqual(a: ConnectorPath[], b: ConnectorPath[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n const ap = a[i]\n const bp = b[i]\n if (!ap || !bp) return false\n if (\n ap.id !== bp.id ||\n ap.d !== bp.d ||\n ap.variant !== bp.variant ||\n ap.kind !== bp.kind ||\n ap.reversed !== bp.reversed\n ) {\n return false\n }\n }\n return true\n}\n\nexport function PowerNetworkDiagram({\n title,\n loading = false,\n className,\n children,\n}: PowerNetworkDiagramProps): ReactElement {\n const containerRef = useRef<HTMLDivElement>(null)\n const [nodes, setNodes] = useState<Map<string, RegisteredNode>>(\n () => new Map(),\n )\n const [connectorPaths, setConnectorPaths] = useState<ConnectorPath[]>([])\n const connectorPathsRef = useRef<ConnectorPath[]>([])\n const flowPathRefs = useRef<Record<string, SVGPathElement | null>>({})\n const currentFlowVisualsRef = useRef<Record<string, FlowAnimationState>>({})\n const targetFlowVisualsRef = useRef<Record<string, FlowVisual>>({})\n\n const register = useCallback((node: RegisteredNode) => {\n setNodes((prev) => {\n const existing = prev.get(node.id)\n if (\n existing &&\n existing.kind === node.kind &&\n existing.variant === node.variant &&\n existing.watts === node.watts &&\n existing.element === node.element\n ) {\n return prev\n }\n const next = new Map(prev)\n next.set(node.id, node)\n return next\n })\n }, [])\n\n const unregister = useCallback((id: string) => {\n setNodes((prev) => {\n if (!prev.has(id)) return prev\n const next = new Map(prev)\n next.delete(id)\n return next\n })\n }, [])\n\n const totalSourceWatts = useMemo(() => {\n let total = 0\n for (const node of nodes.values()) {\n if (node.kind === 'source') total += node.watts\n }\n return total\n }, [nodes])\n\n useEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n let homeNode: RegisteredNode | undefined\n const sources: RegisteredNode[] = []\n const devices: RegisteredNode[] = []\n for (const node of nodes.values()) {\n if (node.kind === 'home') {\n homeNode = node\n } else if (node.element) {\n if (node.kind === 'source') sources.push(node)\n else devices.push(node)\n }\n }\n\n const updatePaths = () => {\n if (!homeNode || !homeNode.element) {\n setConnectorPaths((prev) => (prev.length === 0 ? prev : []))\n return\n }\n const containerRect = container.getBoundingClientRect()\n const homeRect = homeNode.element.getBoundingClientRect()\n const homeTop = anchorTop(homeRect, containerRect)\n const homeBottom = anchorBottom(homeRect, containerRect)\n const next: ConnectorPath[] = []\n\n const sourceCount = sources.length\n const deviceCount = devices.length\n\n sources.forEach((source, index) => {\n if (!source.element) return\n const from = anchorBottom(\n source.element.getBoundingClientRect(),\n containerRect,\n )\n const offsetX =\n SOURCE_HOME_OFFSET_STEP_PX * (index - (sourceCount - 1) / 2)\n const to = { x: homeTop.x + offsetX, y: homeTop.y }\n next.push({\n id: source.id,\n d: buildAdaptivePath(from, to),\n kind: 'source',\n variant: source.variant,\n reversed: source.watts < 0,\n })\n })\n\n devices.forEach((device, index) => {\n if (!device.element) return\n const to = anchorTop(\n device.element.getBoundingClientRect(),\n containerRect,\n )\n const offsetX =\n DEVICE_HOME_OFFSET_STEP_PX * (index - (deviceCount - 1) / 2)\n const from = { x: homeBottom.x + offsetX, y: homeBottom.y }\n next.push({\n id: device.id,\n d: buildAdaptivePath(from, to),\n kind: 'device',\n reversed: false,\n })\n })\n\n setConnectorPaths((prev) =>\n connectorPathsEqual(prev, next) ? prev : next,\n )\n }\n\n updatePaths()\n\n const resizeObserver = new ResizeObserver(updatePaths)\n resizeObserver.observe(container)\n for (const node of nodes.values()) {\n if (node.element) resizeObserver.observe(node.element)\n }\n window.addEventListener('resize', updatePaths)\n\n return () => {\n resizeObserver.disconnect()\n window.removeEventListener('resize', updatePaths)\n }\n }, [nodes])\n\n useEffect(() => {\n connectorPathsRef.current = connectorPaths\n }, [connectorPaths])\n\n useEffect(() => {\n const targets: Record<string, FlowVisual> = {}\n const homeWatts = totalSourceWatts\n for (const node of nodes.values()) {\n if (node.kind === 'home') continue\n const watts = node.watts\n const relative =\n homeWatts > 0\n ? Math.max(0, Math.min(1, Math.abs(watts) / homeWatts))\n : 0\n targets[node.id] = {\n gap: FLOW_GAP_MAX - (FLOW_GAP_MAX - FLOW_GAP_MIN) * relative,\n speed: FLOW_SPEED_MIN + (FLOW_SPEED_MAX - FLOW_SPEED_MIN) * relative,\n opacity: Math.abs(watts) > 0 ? 1 : 0,\n }\n }\n targetFlowVisualsRef.current = targets\n }, [nodes, totalSourceWatts])\n\n useEffect(() => {\n if (loading) return\n let frameId = 0\n let lastFrameTime = performance.now()\n\n const animate = (now: number) => {\n const deltaSeconds = Math.max(0.001, (now - lastFrameTime) / 1000)\n lastFrameTime = now\n const smoothing = 1 - Math.exp(-FLOW_SMOOTHING * deltaSeconds)\n\n for (const connectorPath of connectorPathsRef.current) {\n const id = connectorPath.id\n const target = targetFlowVisualsRef.current[id]\n const pathElement = flowPathRefs.current[id]\n if (!target || !pathElement) continue\n\n const current = currentFlowVisualsRef.current[id] ?? {\n gap: target.gap,\n offset: 0,\n speed: target.speed,\n opacity: target.opacity,\n }\n\n current.gap += (target.gap - current.gap) * smoothing\n current.speed += (target.speed - current.speed) * smoothing\n current.opacity += (target.opacity - current.opacity) * smoothing\n\n const dashCycle = FLOW_DASH_SOLID + Math.max(0.001, current.gap)\n const direction = connectorPath.reversed ? 1 : -1\n const nextOffset =\n current.offset + direction * current.speed * deltaSeconds\n const wrappedOffset = ((nextOffset % dashCycle) + dashCycle) % dashCycle\n current.offset = wrappedOffset - dashCycle\n\n pathElement.setAttribute(\n 'stroke-dasharray',\n `${FLOW_DASH_SOLID} ${current.gap.toFixed(3)}`,\n )\n pathElement.setAttribute('stroke-dashoffset', current.offset.toFixed(3))\n pathElement.setAttribute('opacity', current.opacity.toFixed(3))\n\n currentFlowVisualsRef.current[id] = current\n }\n\n frameId = window.requestAnimationFrame(animate)\n }\n\n frameId = window.requestAnimationFrame(animate)\n return () => window.cancelAnimationFrame(frameId)\n }, [loading])\n\n const contextValue = useMemo<PowerNetworkContextValue>(\n () => ({ loading, register, unregister, totalSourceWatts }),\n [loading, register, unregister, totalSourceWatts],\n )\n\n return (\n <PowerNetworkContext.Provider value={contextValue}>\n <div\n ref={containerRef}\n className={cn('@container relative flex flex-col', className)}\n >\n {title && <SectionTitle>{title}</SectionTitle>}\n {connectorPaths.length > 0 && (\n <svg className=\"pointer-events-none absolute inset-0 size-full\">\n {connectorPaths.map((path) => {\n if (loading) {\n return (\n <path\n key={path.id}\n d={path.d}\n fill=\"none\"\n stroke={SKELETON_STROKE}\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n />\n )\n }\n\n const flowStroke =\n path.kind === 'source' && path.variant\n ? SOURCE_VARIANTS[path.variant].flowStroke\n : DEVICE_FLOW_STROKE\n const initialDashGap =\n path.kind === 'source' && path.variant\n ? SOURCE_VARIANTS[path.variant].initialDashGap\n : DEVICE_INITIAL_DASH_GAP\n\n return (\n <g key={path.id}>\n <path\n d={path.d}\n fill=\"none\"\n stroke=\"var(--gray-a1)\"\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n />\n <path\n d={path.d}\n fill=\"none\"\n stroke={flowStroke}\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n ref={(el) => {\n flowPathRefs.current[path.id] = el\n }}\n strokeDasharray={`${FLOW_DASH_SOLID} ${initialDashGap}`}\n strokeDashoffset={0}\n opacity={0}\n />\n </g>\n )\n })}\n </svg>\n )}\n <div className=\"flex flex-col gap-y-14\">{children}</div>\n </div>\n </PowerNetworkContext.Provider>\n )\n}\n\n// --- Row containers -----------------------------------------------------\n\nexport interface PowerNetworkRowProps {\n className?: string\n children?: ReactNode\n}\n\nexport function PowerNetworkSources({\n className,\n children,\n}: PowerNetworkRowProps): ReactElement {\n return (\n <div\n className={cn(\n 'flex items-center justify-center gap-[clamp(1.5rem,10cqw,4rem)]',\n className,\n )}\n >\n {children}\n </div>\n )\n}\n\nexport function PowerNetworkDevices({\n className,\n children,\n}: PowerNetworkRowProps): ReactElement {\n return (\n <div\n className={cn(\n 'flex items-center justify-center gap-[clamp(1.5rem,10cqw,4rem)]',\n className,\n )}\n >\n {children}\n </div>\n )\n}\n\n// --- PowerSource --------------------------------------------------------\n\nexport interface PowerSourceProps {\n id?: string\n watts: number\n icon: ReactNode\n variant?: PowerSourceVariant\n className?: string\n /**\n * Optional 0–100 progress (e.g. battery SoC). When set, a thin colored arc\n * is drawn around the icon circle as a non-intrusive indicator.\n */\n progress?: number\n}\n\nexport function PowerSource({\n id: idProp,\n watts,\n icon,\n variant = 'neutral',\n className,\n progress,\n}: PowerSourceProps): ReactElement {\n const autoId = useId()\n const id = idProp ?? autoId\n const { register, unregister, loading } = usePowerNetwork()\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (!element) return\n register({ id, kind: 'source', variant, watts, element })\n }, [element, register, id, variant, watts])\n\n useEffect(() => {\n return () => unregister(id)\n }, [unregister, id])\n\n if (loading) {\n return <SourceSkeleton anchorRef={setElement} />\n }\n\n const isActive = watts !== 0\n const variantStyles = SOURCE_VARIANTS[variant]\n const clampedProgress =\n progress == null ? null : Math.max(0, Math.min(100, progress))\n const showRing = clampedProgress != null\n const RING_RADIUS = 48\n const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS\n const ringDash =\n clampedProgress != null ? (clampedProgress / 100) * RING_CIRCUMFERENCE : 0\n\n return (\n <div\n className={cn(\n 'flex flex-col items-center justify-center gap-1 transition-opacity duration-500',\n !isActive && 'opacity-45',\n className,\n )}\n >\n <NumberFlow\n value={Math.abs(watts)}\n suffix=\"W\"\n className=\"mt-1 text-sm leading-none font-medium\"\n />\n <div\n ref={setElement}\n className={cn(\n 'relative flex aspect-square items-center justify-center rounded-circle border p-4 shadow-sm',\n isActive\n ? variantStyles.activeClassName\n : 'border-gray bg-gray-a3 shadow-gray-a4 saturate-0',\n )}\n >\n {showRing && (\n <svg\n className=\"pointer-events-none absolute inset-0 size-full -rotate-90\"\n viewBox=\"0 0 100 100\"\n aria-hidden=\"true\"\n >\n <circle\n cx=\"50\"\n cy=\"50\"\n r={RING_RADIUS}\n fill=\"none\"\n stroke={variantStyles.flowStroke}\n strokeWidth=\"4\"\n strokeLinecap=\"round\"\n strokeDasharray={`${ringDash} ${RING_CIRCUMFERENCE}`}\n opacity={isActive ? 1 : 0.5}\n />\n </svg>\n )}\n {icon}\n </div>\n </div>\n )\n}\n\nfunction SourceSkeleton({\n anchorRef,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n}): ReactElement {\n return (\n <div className=\"flex flex-col items-center justify-center gap-1\">\n <Skeleton className=\"h-3.5 w-10\" />\n <div\n ref={anchorRef}\n className=\"flex aspect-square animate-pulse items-center justify-center rounded-circle border border-gray bg-base-gray-soft p-4 motion-reduce:animate-none\"\n >\n <div className=\"size-7\" />\n </div>\n </div>\n )\n}\n\n// --- PowerDevice --------------------------------------------------------\n\nexport interface PowerDeviceProps {\n id?: string\n watts: number\n icon: ReactNode\n className?: string\n}\n\nexport function PowerDevice({\n id: idProp,\n watts,\n icon,\n className,\n}: PowerDeviceProps): ReactElement {\n const autoId = useId()\n const id = idProp ?? autoId\n const { register, unregister, loading } = usePowerNetwork()\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (!element) return\n register({ id, kind: 'device', watts, element })\n }, [element, register, id, watts])\n\n useEffect(() => {\n return () => unregister(id)\n }, [unregister, id])\n\n if (loading) {\n return <DeviceSkeleton anchorRef={setElement} />\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col items-center justify-center gap-1',\n className,\n )}\n >\n <div\n ref={setElement}\n className=\"flex aspect-square items-center justify-center rounded-circle border border-gray bg-slate-a1 p-4\"\n >\n {icon}\n </div>\n <NumberFlow\n value={watts}\n suffix=\"W\"\n className=\"text-sm font-medium tabular-nums\"\n />\n </div>\n )\n}\n\nfunction DeviceSkeleton({\n anchorRef,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n}): ReactElement {\n return (\n <div className=\"flex flex-col items-center justify-center gap-1\">\n <div\n ref={anchorRef}\n className=\"flex aspect-square animate-pulse items-center justify-center rounded-circle border border-gray bg-base-gray-soft p-4 motion-reduce:animate-none\"\n >\n <div className=\"size-5\" />\n </div>\n <Skeleton className=\"h-3.5 w-10\" />\n </div>\n )\n}\n\n// --- PowerNetworkHome ---------------------------------------------------\n\nexport interface PowerNetworkHomeProps {\n /** Override total power. Defaults to the sum of registered source watts. */\n watts?: number\n /** Self-sufficiency ratio in 0..1. Renders a green ring when provided. */\n autarky?: number\n className?: string\n}\n\nexport function PowerNetworkHome({\n watts,\n autarky,\n className,\n}: PowerNetworkHomeProps): ReactElement {\n const autoId = useId()\n const { register, unregister, loading, totalSourceWatts } = usePowerNetwork()\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n const effectiveWatts = watts ?? totalSourceWatts\n\n useEffect(() => {\n if (!element) return\n register({ id: autoId, kind: 'home', watts: effectiveWatts, element })\n }, [element, register, autoId, effectiveWatts])\n\n useEffect(() => {\n return () => unregister(autoId)\n }, [unregister, autoId])\n\n if (loading) {\n return <HomeSkeleton anchorRef={setElement} className={className} />\n }\n\n return (\n <HomeSection\n anchorRef={setElement}\n autarky={autarky ?? null}\n watts={effectiveWatts}\n className={className}\n />\n )\n}\n\nfunction HomeSection({\n anchorRef,\n autarky,\n className,\n watts,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n autarky: number | null\n className?: string\n watts: number\n}): ReactElement {\n const targetAutarky =\n autarky == null ? 0 : Math.max(0, Math.min(100, autarky * 100))\n const [animatedAutarky, setAnimatedAutarky] = useState(targetAutarky)\n const animatedAutarkyRef = useRef(animatedAutarky)\n const nodeSize = 78\n const ringStrokeWidth = 2\n const radius = (nodeSize - ringStrokeWidth) / 2\n const circumference = 2 * Math.PI * radius\n const progressLength = (animatedAutarky / 100) * circumference\n\n useEffect(() => {\n animatedAutarkyRef.current = animatedAutarky\n }, [animatedAutarky])\n\n useEffect(() => {\n let frameId = 0\n let lastTime = performance.now()\n\n const animate = (now: number) => {\n const deltaSeconds = Math.max(0.001, (now - lastTime) / 1000)\n lastTime = now\n const smoothing = 1 - Math.exp(-7 * deltaSeconds)\n const next =\n animatedAutarkyRef.current +\n (targetAutarky - animatedAutarkyRef.current) * smoothing\n\n animatedAutarkyRef.current = next\n setAnimatedAutarky(next)\n\n if (Math.abs(targetAutarky - next) > 0.05) {\n frameId = window.requestAnimationFrame(animate)\n } else {\n animatedAutarkyRef.current = targetAutarky\n setAnimatedAutarky(targetAutarky)\n }\n }\n\n frameId = window.requestAnimationFrame(animate)\n return () => window.cancelAnimationFrame(frameId)\n }, [targetAutarky])\n\n return (\n <div\n ref={anchorRef}\n className={cn('relative mx-auto size-[78px]', className)}\n >\n {autarky != null && (\n <svg\n className=\"absolute inset-0 size-full -rotate-90\"\n viewBox={`0 0 ${nodeSize} ${nodeSize}`}\n >\n <circle\n cx={nodeSize / 2}\n cy={nodeSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"var(--gray-a5)\"\n strokeWidth={ringStrokeWidth}\n />\n {progressLength > 0 && (\n <circle\n cx={nodeSize / 2}\n cy={nodeSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"var(--green-a9)\"\n strokeLinecap=\"round\"\n strokeWidth={ringStrokeWidth}\n strokeDasharray={`${progressLength} ${circumference}`}\n />\n )}\n </svg>\n )}\n <div className=\"absolute inset-0 flex flex-col items-center justify-center rounded-circle border border-gray-a5 shadow-sm shadow-gray-a1\">\n <MaterialSymbol className=\"text-[14px]\" name=\"home\" />\n <NumberFlow\n value={watts}\n suffix=\"W\"\n className=\"mt-1 text-sm leading-none font-medium\"\n />\n </div>\n </div>\n )\n}\n\nfunction HomeSkeleton({\n anchorRef,\n className,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n className?: string\n}): ReactElement {\n return (\n <div\n ref={anchorRef}\n className={cn(\n 'mx-auto size-[78px] animate-pulse rounded-circle border border-gray motion-reduce:animate-none',\n className,\n )}\n />\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"power-network.cjs","names":["useReducedMotion","SectionTitle","NumberFlow","Skeleton","MaterialSymbol"],"sources":["../../src/components/power-network.tsx"],"sourcesContent":["import {\n createContext,\n type ReactElement,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { useReducedMotion } from '../hooks/use-reduced-motion'\nimport { MaterialSymbol } from '../icons/material-symbol'\nimport { cn } from '../utils/cn'\nimport NumberFlow from './number-flow'\nimport { Skeleton } from './skeleton'\nimport { SectionTitle } from './typography'\n\n// --- Variants -----------------------------------------------------------\n\nexport type PowerSourceVariant = 'grid' | 'solar' | 'battery' | 'neutral'\n\ninterface SourceVariantStyles {\n activeClassName: string\n flowStroke: string\n initialDashGap: number\n}\n\nconst SOURCE_VARIANTS: Record<PowerSourceVariant, SourceVariantStyles> = {\n grid: {\n activeClassName: 'border-blue-a9 bg-blue-a1 shadow-blue-a4',\n flowStroke: 'var(--blue-11)',\n initialDashGap: 10,\n },\n solar: {\n activeClassName: 'border-orange-a9 bg-orange-a1 shadow-orange-a3',\n flowStroke: 'var(--orange-a9)',\n initialDashGap: 5,\n },\n battery: {\n activeClassName: 'border-green-a9 bg-green-a1 shadow-green-a3',\n flowStroke: 'var(--green-a9)',\n initialDashGap: 7,\n },\n neutral: {\n activeClassName: 'border-gray-a9 bg-gray-a1 shadow-gray-a3',\n flowStroke: 'var(--neutral-a9)',\n initialDashGap: 9,\n },\n}\n\nconst DEVICE_FLOW_STROKE = 'var(--neutral-a9)'\nconst DEVICE_INITIAL_DASH_GAP = 9\n\nconst SKELETON_STROKE = 'var(--neutral-a6)'\nconst STROKE_WIDTH = 2\nconst STRAIGHT_TOLERANCE_PX = 3\nconst SOURCE_HOME_OFFSET_STEP_PX = 10\nconst DEVICE_HOME_OFFSET_STEP_PX = 10\n\n// --- Flow animation parameters ------------------------------------------\n\nconst FLOW_DASH_SOLID = 1\nconst FLOW_GAP_MAX = 14\nconst FLOW_GAP_MIN = 5\nconst FLOW_SPEED_MIN = 10\nconst FLOW_SPEED_MAX = 56\nconst FLOW_SMOOTHING = 5.5\n\ninterface FlowVisual {\n gap: number\n speed: number\n opacity: number\n}\n\ninterface FlowAnimationState extends FlowVisual {\n offset: number\n}\n\n// --- Context ------------------------------------------------------------\n\ntype NodeKind = 'source' | 'device' | 'home'\n\ninterface RegisteredNode {\n id: string\n kind: NodeKind\n variant?: PowerSourceVariant\n watts: number\n element: HTMLDivElement | null\n}\n\ninterface PowerNetworkContextValue {\n loading: boolean\n register: (node: RegisteredNode) => void\n unregister: (id: string) => void\n totalSourceWatts: number\n}\n\nconst PowerNetworkContext = createContext<PowerNetworkContextValue | null>(null)\n\nfunction usePowerNetwork(): PowerNetworkContextValue {\n const ctx = useContext(PowerNetworkContext)\n if (!ctx) {\n throw new Error(\n 'Power network sub-components must be used inside <PowerNetworkDiagram>.',\n )\n }\n return ctx\n}\n\n// --- Geometry helpers ---------------------------------------------------\n\ninterface Point {\n x: number\n y: number\n}\n\nfunction anchorTop(rect: DOMRect, containerRect: DOMRect): Point {\n return {\n x: rect.left + rect.width / 2 - containerRect.left,\n y: rect.top - containerRect.top,\n }\n}\n\nfunction anchorBottom(rect: DOMRect, containerRect: DOMRect): Point {\n return {\n x: rect.left + rect.width / 2 - containerRect.left,\n y: rect.bottom - containerRect.top,\n }\n}\n\nfunction buildAdaptivePath(from: Point, to: Point): string {\n if (Math.abs(from.x - to.x) < STRAIGHT_TOLERANCE_PX) {\n return `M ${from.x} ${from.y} L ${to.x} ${to.y}`\n }\n const dy = to.y - from.y\n const c1y = from.y + dy * 0.56\n const c2y = to.y - dy * 0.58\n return `M ${from.x} ${from.y} C ${from.x} ${c1y} ${to.x} ${c2y} ${to.x} ${to.y}`\n}\n\n// --- PowerNetworkDiagram ------------------------------------------------\n\nexport interface PowerNetworkDiagramProps {\n title?: string\n loading?: boolean\n className?: string\n children?: ReactNode\n}\n\ninterface ConnectorPath {\n id: string\n d: string\n kind: NodeKind\n variant?: PowerSourceVariant\n reversed: boolean\n}\n\nfunction connectorPathsEqual(a: ConnectorPath[], b: ConnectorPath[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n const ap = a[i]\n const bp = b[i]\n if (!ap || !bp) return false\n if (\n ap.id !== bp.id ||\n ap.d !== bp.d ||\n ap.variant !== bp.variant ||\n ap.kind !== bp.kind ||\n ap.reversed !== bp.reversed\n ) {\n return false\n }\n }\n return true\n}\n\nexport function PowerNetworkDiagram({\n title,\n loading = false,\n className,\n children,\n}: PowerNetworkDiagramProps): ReactElement {\n const containerRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const [nodes, setNodes] = useState<Map<string, RegisteredNode>>(\n () => new Map(),\n )\n const [connectorPaths, setConnectorPaths] = useState<ConnectorPath[]>([])\n const connectorPathsRef = useRef<ConnectorPath[]>([])\n const flowPathRefs = useRef<Record<string, SVGPathElement | null>>({})\n const currentFlowVisualsRef = useRef<Record<string, FlowAnimationState>>({})\n const targetFlowVisualsRef = useRef<Record<string, FlowVisual>>({})\n\n const register = useCallback((node: RegisteredNode) => {\n setNodes((prev) => {\n const existing = prev.get(node.id)\n if (\n existing &&\n existing.kind === node.kind &&\n existing.variant === node.variant &&\n existing.watts === node.watts &&\n existing.element === node.element\n ) {\n return prev\n }\n const next = new Map(prev)\n next.set(node.id, node)\n return next\n })\n }, [])\n\n const unregister = useCallback((id: string) => {\n setNodes((prev) => {\n if (!prev.has(id)) return prev\n const next = new Map(prev)\n next.delete(id)\n return next\n })\n }, [])\n\n const totalSourceWatts = useMemo(() => {\n let total = 0\n for (const node of nodes.values()) {\n if (node.kind === 'source') total += node.watts\n }\n return total\n }, [nodes])\n\n useEffect(() => {\n const container = containerRef.current\n if (!container) return\n\n let homeNode: RegisteredNode | undefined\n const sources: RegisteredNode[] = []\n const devices: RegisteredNode[] = []\n for (const node of nodes.values()) {\n if (node.kind === 'home') {\n homeNode = node\n } else if (node.element) {\n if (node.kind === 'source') sources.push(node)\n else devices.push(node)\n }\n }\n\n const updatePaths = () => {\n if (!homeNode || !homeNode.element) {\n setConnectorPaths((prev) => (prev.length === 0 ? prev : []))\n return\n }\n const containerRect = container.getBoundingClientRect()\n const homeRect = homeNode.element.getBoundingClientRect()\n const homeTop = anchorTop(homeRect, containerRect)\n const homeBottom = anchorBottom(homeRect, containerRect)\n const next: ConnectorPath[] = []\n\n const sourceCount = sources.length\n const deviceCount = devices.length\n\n sources.forEach((source, index) => {\n if (!source.element) return\n const from = anchorBottom(\n source.element.getBoundingClientRect(),\n containerRect,\n )\n const offsetX =\n SOURCE_HOME_OFFSET_STEP_PX * (index - (sourceCount - 1) / 2)\n const to = { x: homeTop.x + offsetX, y: homeTop.y }\n next.push({\n id: source.id,\n d: buildAdaptivePath(from, to),\n kind: 'source',\n variant: source.variant,\n reversed: source.watts < 0,\n })\n })\n\n devices.forEach((device, index) => {\n if (!device.element) return\n const to = anchorTop(\n device.element.getBoundingClientRect(),\n containerRect,\n )\n const offsetX =\n DEVICE_HOME_OFFSET_STEP_PX * (index - (deviceCount - 1) / 2)\n const from = { x: homeBottom.x + offsetX, y: homeBottom.y }\n next.push({\n id: device.id,\n d: buildAdaptivePath(from, to),\n kind: 'device',\n reversed: false,\n })\n })\n\n setConnectorPaths((prev) =>\n connectorPathsEqual(prev, next) ? prev : next,\n )\n }\n\n updatePaths()\n\n const resizeObserver = new ResizeObserver(updatePaths)\n resizeObserver.observe(container)\n for (const node of nodes.values()) {\n if (node.element) resizeObserver.observe(node.element)\n }\n window.addEventListener('resize', updatePaths)\n\n return () => {\n resizeObserver.disconnect()\n window.removeEventListener('resize', updatePaths)\n }\n }, [nodes])\n\n useEffect(() => {\n connectorPathsRef.current = connectorPaths\n }, [connectorPaths])\n\n useEffect(() => {\n const targets: Record<string, FlowVisual> = {}\n const homeWatts = totalSourceWatts\n for (const node of nodes.values()) {\n if (node.kind === 'home') continue\n const watts = node.watts\n const relative =\n homeWatts > 0\n ? Math.max(0, Math.min(1, Math.abs(watts) / homeWatts))\n : 0\n targets[node.id] = {\n gap: FLOW_GAP_MAX - (FLOW_GAP_MAX - FLOW_GAP_MIN) * relative,\n speed: FLOW_SPEED_MIN + (FLOW_SPEED_MAX - FLOW_SPEED_MIN) * relative,\n opacity: Math.abs(watts) > 0 ? 1 : 0,\n }\n }\n targetFlowVisualsRef.current = targets\n }, [nodes, totalSourceWatts])\n\n useEffect(() => {\n if (loading || reducedMotion) return\n let frameId = 0\n let lastFrameTime = performance.now()\n\n const animate = (now: number) => {\n const deltaSeconds = Math.max(0.001, (now - lastFrameTime) / 1000)\n lastFrameTime = now\n const smoothing = 1 - Math.exp(-FLOW_SMOOTHING * deltaSeconds)\n\n for (const connectorPath of connectorPathsRef.current) {\n const id = connectorPath.id\n const target = targetFlowVisualsRef.current[id]\n const pathElement = flowPathRefs.current[id]\n if (!target || !pathElement) continue\n\n const current = currentFlowVisualsRef.current[id] ?? {\n gap: target.gap,\n offset: 0,\n speed: target.speed,\n opacity: target.opacity,\n }\n\n current.gap += (target.gap - current.gap) * smoothing\n current.speed += (target.speed - current.speed) * smoothing\n current.opacity += (target.opacity - current.opacity) * smoothing\n\n const dashCycle = FLOW_DASH_SOLID + Math.max(0.001, current.gap)\n const direction = connectorPath.reversed ? 1 : -1\n const nextOffset =\n current.offset + direction * current.speed * deltaSeconds\n const wrappedOffset = ((nextOffset % dashCycle) + dashCycle) % dashCycle\n current.offset = wrappedOffset - dashCycle\n\n pathElement.setAttribute(\n 'stroke-dasharray',\n `${FLOW_DASH_SOLID} ${current.gap.toFixed(3)}`,\n )\n pathElement.setAttribute('stroke-dashoffset', current.offset.toFixed(3))\n pathElement.setAttribute('opacity', current.opacity.toFixed(3))\n\n currentFlowVisualsRef.current[id] = current\n }\n\n frameId = window.requestAnimationFrame(animate)\n }\n\n frameId = window.requestAnimationFrame(animate)\n return () => window.cancelAnimationFrame(frameId)\n }, [loading, reducedMotion])\n\n // Reduced motion: freeze the marching dashes but keep the dash gap tracking\n // live magnitude, and let opacity fade in/out via the CSS transition on each\n // flow path (see the flow <path> style below).\n useEffect(() => {\n if (loading || !reducedMotion) return\n for (const connectorPath of connectorPathsRef.current) {\n const target = targetFlowVisualsRef.current[connectorPath.id]\n const pathElement = flowPathRefs.current[connectorPath.id]\n if (!target || !pathElement) continue\n pathElement.setAttribute(\n 'stroke-dasharray',\n `${FLOW_DASH_SOLID} ${target.gap.toFixed(3)}`,\n )\n pathElement.setAttribute('stroke-dashoffset', '0')\n pathElement.setAttribute('opacity', target.opacity.toFixed(3))\n }\n }, [loading, reducedMotion, nodes, totalSourceWatts])\n\n const contextValue = useMemo<PowerNetworkContextValue>(\n () => ({ loading, register, unregister, totalSourceWatts }),\n [loading, register, unregister, totalSourceWatts],\n )\n\n return (\n <PowerNetworkContext.Provider value={contextValue}>\n <div\n ref={containerRef}\n className={cn('@container relative flex flex-col', className)}\n >\n {title && <SectionTitle>{title}</SectionTitle>}\n {connectorPaths.length > 0 && (\n <svg className=\"pointer-events-none absolute inset-0 size-full\">\n {connectorPaths.map((path) => {\n if (loading) {\n return (\n <path\n key={path.id}\n d={path.d}\n fill=\"none\"\n stroke={SKELETON_STROKE}\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n />\n )\n }\n\n const flowStroke =\n path.kind === 'source' && path.variant\n ? SOURCE_VARIANTS[path.variant].flowStroke\n : DEVICE_FLOW_STROKE\n const initialDashGap =\n path.kind === 'source' && path.variant\n ? SOURCE_VARIANTS[path.variant].initialDashGap\n : DEVICE_INITIAL_DASH_GAP\n\n return (\n <g key={path.id}>\n <path\n d={path.d}\n fill=\"none\"\n stroke=\"var(--neutral-a1)\"\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n />\n <path\n d={path.d}\n fill=\"none\"\n stroke={flowStroke}\n strokeWidth={STROKE_WIDTH}\n strokeLinecap=\"round\"\n ref={(el) => {\n flowPathRefs.current[path.id] = el\n }}\n strokeDasharray={`${FLOW_DASH_SOLID} ${initialDashGap}`}\n strokeDashoffset={0}\n opacity={0}\n style={\n reducedMotion\n ? { transition: 'opacity 300ms ease' }\n : undefined\n }\n />\n </g>\n )\n })}\n </svg>\n )}\n <div className=\"flex flex-col gap-y-14\">{children}</div>\n </div>\n </PowerNetworkContext.Provider>\n )\n}\n\n// --- Row containers -----------------------------------------------------\n\nexport interface PowerNetworkRowProps {\n className?: string\n children?: ReactNode\n}\n\nexport function PowerNetworkSources({\n className,\n children,\n}: PowerNetworkRowProps): ReactElement {\n return (\n <div\n className={cn(\n 'flex items-center justify-center gap-[clamp(1.5rem,10cqw,4rem)]',\n className,\n )}\n >\n {children}\n </div>\n )\n}\n\nexport function PowerNetworkDevices({\n className,\n children,\n}: PowerNetworkRowProps): ReactElement {\n return (\n <div\n className={cn(\n 'flex items-center justify-center gap-[clamp(1.5rem,10cqw,4rem)]',\n className,\n )}\n >\n {children}\n </div>\n )\n}\n\n// --- PowerSource --------------------------------------------------------\n\nexport interface PowerSourceProps {\n id?: string\n watts: number\n icon: ReactNode\n variant?: PowerSourceVariant\n className?: string\n /**\n * Optional 0–100 progress (e.g. battery SoC). When set, a thin colored arc\n * is drawn around the icon circle as a non-intrusive indicator.\n */\n progress?: number\n}\n\nexport function PowerSource({\n id: idProp,\n watts,\n icon,\n variant = 'neutral',\n className,\n progress,\n}: PowerSourceProps): ReactElement {\n const autoId = useId()\n const id = idProp ?? autoId\n const { register, unregister, loading } = usePowerNetwork()\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (!element) return\n register({ id, kind: 'source', variant, watts, element })\n }, [element, register, id, variant, watts])\n\n useEffect(() => {\n return () => unregister(id)\n }, [unregister, id])\n\n if (loading) {\n return <SourceSkeleton anchorRef={setElement} />\n }\n\n const isActive = watts !== 0\n const variantStyles = SOURCE_VARIANTS[variant]\n const clampedProgress =\n progress == null ? null : Math.max(0, Math.min(100, progress))\n const showRing = clampedProgress != null\n const RING_RADIUS = 48\n const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS\n const ringDash =\n clampedProgress != null ? (clampedProgress / 100) * RING_CIRCUMFERENCE : 0\n\n return (\n <div\n className={cn(\n 'flex flex-col items-center justify-center gap-1 transition-opacity duration-500',\n !isActive && 'opacity-45',\n className,\n )}\n >\n <NumberFlow\n value={Math.abs(watts)}\n suffix=\"W\"\n className=\"mt-1 text-sm leading-none font-medium\"\n />\n <div\n ref={setElement}\n className={cn(\n 'relative flex aspect-square items-center justify-center rounded-circle border p-4 shadow-sm',\n isActive\n ? variantStyles.activeClassName\n : 'border-gray bg-gray-a3 shadow-gray-a4 saturate-0',\n )}\n >\n {showRing && (\n <svg\n className=\"pointer-events-none absolute inset-0 size-full -rotate-90\"\n viewBox=\"0 0 100 100\"\n aria-hidden=\"true\"\n >\n <circle\n cx=\"50\"\n cy=\"50\"\n r={RING_RADIUS}\n fill=\"none\"\n stroke={variantStyles.flowStroke}\n strokeWidth=\"4\"\n strokeLinecap=\"round\"\n strokeDasharray={`${ringDash} ${RING_CIRCUMFERENCE}`}\n opacity={isActive ? 1 : 0.5}\n />\n </svg>\n )}\n {icon}\n </div>\n </div>\n )\n}\n\nfunction SourceSkeleton({\n anchorRef,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n}): ReactElement {\n return (\n <div className=\"flex flex-col items-center justify-center gap-1\">\n <Skeleton className=\"h-3.5 w-10\" />\n <div\n ref={anchorRef}\n className=\"flex aspect-square animate-pulse items-center justify-center rounded-circle border border-gray bg-base-gray-soft p-4 motion-reduce:animate-none\"\n >\n <div className=\"size-7\" />\n </div>\n </div>\n )\n}\n\n// --- PowerDevice --------------------------------------------------------\n\nexport interface PowerDeviceProps {\n id?: string\n watts: number\n icon: ReactNode\n className?: string\n}\n\nexport function PowerDevice({\n id: idProp,\n watts,\n icon,\n className,\n}: PowerDeviceProps): ReactElement {\n const autoId = useId()\n const id = idProp ?? autoId\n const { register, unregister, loading } = usePowerNetwork()\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (!element) return\n register({ id, kind: 'device', watts, element })\n }, [element, register, id, watts])\n\n useEffect(() => {\n return () => unregister(id)\n }, [unregister, id])\n\n if (loading) {\n return <DeviceSkeleton anchorRef={setElement} />\n }\n\n return (\n <div\n className={cn(\n 'flex flex-col items-center justify-center gap-1',\n className,\n )}\n >\n <div\n ref={setElement}\n className=\"flex aspect-square items-center justify-center rounded-circle border border-gray bg-gray-a1 p-4\"\n >\n {icon}\n </div>\n <NumberFlow\n value={watts}\n suffix=\"W\"\n className=\"text-sm font-medium tabular-nums\"\n />\n </div>\n )\n}\n\nfunction DeviceSkeleton({\n anchorRef,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n}): ReactElement {\n return (\n <div className=\"flex flex-col items-center justify-center gap-1\">\n <div\n ref={anchorRef}\n className=\"flex aspect-square animate-pulse items-center justify-center rounded-circle border border-gray bg-base-gray-soft p-4 motion-reduce:animate-none\"\n >\n <div className=\"size-5\" />\n </div>\n <Skeleton className=\"h-3.5 w-10\" />\n </div>\n )\n}\n\n// --- PowerNetworkHome ---------------------------------------------------\n\nexport interface PowerNetworkHomeProps {\n /** Override total power. Defaults to the sum of registered source watts. */\n watts?: number\n /** Self-sufficiency ratio in 0..1. Renders a green ring when provided. */\n autarky?: number\n className?: string\n}\n\nexport function PowerNetworkHome({\n watts,\n autarky,\n className,\n}: PowerNetworkHomeProps): ReactElement {\n const autoId = useId()\n const { register, unregister, loading, totalSourceWatts } = usePowerNetwork()\n const [element, setElement] = useState<HTMLDivElement | null>(null)\n const effectiveWatts = watts ?? totalSourceWatts\n\n useEffect(() => {\n if (!element) return\n register({ id: autoId, kind: 'home', watts: effectiveWatts, element })\n }, [element, register, autoId, effectiveWatts])\n\n useEffect(() => {\n return () => unregister(autoId)\n }, [unregister, autoId])\n\n if (loading) {\n return <HomeSkeleton anchorRef={setElement} className={className} />\n }\n\n return (\n <HomeSection\n anchorRef={setElement}\n autarky={autarky ?? null}\n watts={effectiveWatts}\n className={className}\n />\n )\n}\n\nfunction HomeSection({\n anchorRef,\n autarky,\n className,\n watts,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n autarky: number | null\n className?: string\n watts: number\n}): ReactElement {\n const reducedMotion = useReducedMotion()\n const targetAutarky =\n autarky == null ? 0 : Math.max(0, Math.min(100, autarky * 100))\n const [animatedAutarky, setAnimatedAutarky] = useState(targetAutarky)\n const animatedAutarkyRef = useRef(animatedAutarky)\n const nodeSize = 78\n const ringStrokeWidth = 2\n const radius = (nodeSize - ringStrokeWidth) / 2\n const circumference = 2 * Math.PI * radius\n const progressLength = (animatedAutarky / 100) * circumference\n\n useEffect(() => {\n animatedAutarkyRef.current = animatedAutarky\n }, [animatedAutarky])\n\n useEffect(() => {\n if (reducedMotion) {\n animatedAutarkyRef.current = targetAutarky\n setAnimatedAutarky(targetAutarky)\n return\n }\n\n let frameId = 0\n let lastTime = performance.now()\n\n const animate = (now: number) => {\n const deltaSeconds = Math.max(0.001, (now - lastTime) / 1000)\n lastTime = now\n const smoothing = 1 - Math.exp(-7 * deltaSeconds)\n const next =\n animatedAutarkyRef.current +\n (targetAutarky - animatedAutarkyRef.current) * smoothing\n\n animatedAutarkyRef.current = next\n setAnimatedAutarky(next)\n\n if (Math.abs(targetAutarky - next) > 0.05) {\n frameId = window.requestAnimationFrame(animate)\n } else {\n animatedAutarkyRef.current = targetAutarky\n setAnimatedAutarky(targetAutarky)\n }\n }\n\n frameId = window.requestAnimationFrame(animate)\n return () => window.cancelAnimationFrame(frameId)\n }, [targetAutarky, reducedMotion])\n\n return (\n <div\n ref={anchorRef}\n className={cn('relative mx-auto size-[78px]', className)}\n >\n {autarky != null && (\n <svg\n className=\"absolute inset-0 size-full -rotate-90\"\n viewBox={`0 0 ${nodeSize} ${nodeSize}`}\n >\n <circle\n cx={nodeSize / 2}\n cy={nodeSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"var(--neutral-a5)\"\n strokeWidth={ringStrokeWidth}\n />\n {progressLength > 0 && (\n <circle\n cx={nodeSize / 2}\n cy={nodeSize / 2}\n r={radius}\n fill=\"none\"\n stroke=\"var(--green-a9)\"\n strokeLinecap=\"round\"\n strokeWidth={ringStrokeWidth}\n strokeDasharray={`${progressLength} ${circumference}`}\n />\n )}\n </svg>\n )}\n <div className=\"absolute inset-0 flex flex-col items-center justify-center rounded-circle border border-gray-a5 shadow-sm shadow-gray-a1\">\n <MaterialSymbol className=\"text-[14px]\" name=\"home\" />\n <NumberFlow\n value={watts}\n suffix=\"W\"\n className=\"mt-1 text-sm leading-none font-medium\"\n />\n </div>\n </div>\n )\n}\n\nfunction HomeSkeleton({\n anchorRef,\n className,\n}: {\n anchorRef: (element: HTMLDivElement | null) => void\n className?: string\n}): ReactElement {\n return (\n <div\n ref={anchorRef}\n className={cn(\n 'mx-auto size-[78px] animate-pulse rounded-circle border border-gray motion-reduce:animate-none',\n className,\n )}\n />\n )\n}\n"],"mappings":"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"}
|