@epilot/spark-ui 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/accordion.cjs +7 -7
- package/dist/components/accordion.cjs.map +1 -1
- package/dist/components/accordion.d.ts +1 -1
- package/dist/components/accordion.js +1 -1
- package/dist/components/alert-dialog.cjs +10 -10
- package/dist/components/alert-dialog.cjs.map +1 -1
- package/dist/components/alert-dialog.js +1 -1
- package/dist/components/attachment.cjs +10 -10
- package/dist/components/attachment.cjs.map +1 -1
- package/dist/components/attachment.d.cts +2 -2
- package/dist/components/attachment.d.ts +2 -2
- package/dist/components/autocomplete.cjs +13 -13
- package/dist/components/autocomplete.cjs.map +1 -1
- package/dist/components/autocomplete.js +1 -1
- package/dist/components/avatar.cjs +2 -2
- package/dist/components/avatar.cjs.map +1 -1
- package/dist/components/avatar.js +1 -1
- package/dist/components/badge.cjs +2 -2
- package/dist/components/badge.cjs.map +1 -1
- package/dist/components/badge.js +1 -1
- package/dist/components/bar-chart-with-total.d.cts +1 -1
- package/dist/components/bar-chart-with-total.d.ts +1 -1
- package/dist/components/bar-chart.cjs +4 -4
- package/dist/components/bar-chart.cjs.map +1 -1
- package/dist/components/bar-chart.js +2 -2
- package/dist/components/box.cjs +2 -2
- package/dist/components/box.cjs.map +1 -1
- package/dist/components/box.js +1 -1
- package/dist/components/breadcrumbs.cjs +7 -7
- package/dist/components/breadcrumbs.cjs.map +1 -1
- package/dist/components/bubble.cjs +5 -5
- package/dist/components/bubble.cjs.map +1 -1
- package/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/button-group.cjs +5 -5
- package/dist/components/button-group.cjs.map +1 -1
- package/dist/components/button-group.js +1 -1
- package/dist/components/button.cjs +2 -2
- package/dist/components/button.cjs.map +1 -1
- package/dist/components/button.d.cts +2 -2
- package/dist/components/button.d.ts +2 -2
- package/dist/components/button.js +1 -1
- package/dist/components/calendar.cjs +9 -9
- package/dist/components/calendar.cjs.map +1 -1
- package/dist/components/calendar.js +1 -1
- package/dist/components/callout.cjs +8 -8
- package/dist/components/callout.cjs.map +1 -1
- package/dist/components/callout.d.cts +1 -1
- package/dist/components/callout.d.ts +2 -2
- package/dist/components/callout.js +1 -1
- package/dist/components/card.cjs +16 -16
- package/dist/components/card.cjs.map +1 -1
- package/dist/components/card.js +2 -2
- package/dist/components/card.js.map +1 -1
- package/dist/components/chart.cjs +2 -2
- package/dist/components/chart.cjs.map +1 -1
- package/dist/components/checkbox.cjs +2 -2
- package/dist/components/checkbox.cjs.map +1 -1
- package/dist/components/checkbox.js +1 -1
- package/dist/components/circular-gauge.cjs +3 -3
- package/dist/components/circular-gauge.cjs.map +1 -1
- package/dist/components/day-of-week-picker.cjs +4 -4
- package/dist/components/day-of-week-picker.cjs.map +1 -1
- package/dist/components/dialog.cjs +9 -9
- package/dist/components/dialog.cjs.map +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/drawer.cjs +8 -8
- package/dist/components/drawer.cjs.map +1 -1
- package/dist/components/dropdown-menu.cjs +11 -11
- package/dist/components/dropdown-menu.cjs.map +1 -1
- package/dist/components/dropdown-menu.js +1 -1
- package/dist/components/empty-state.d.ts +1 -1
- package/dist/components/energy-breakdown.cjs +80 -0
- package/dist/components/energy-breakdown.cjs.map +1 -0
- package/dist/components/energy-breakdown.d.cts +24 -0
- package/dist/components/energy-breakdown.d.ts +24 -0
- package/dist/components/energy-breakdown.js +79 -0
- package/dist/components/energy-breakdown.js.map +1 -0
- package/dist/components/energy-history-chart.cjs +63 -0
- package/dist/components/energy-history-chart.cjs.map +1 -0
- package/dist/components/energy-history-chart.d.cts +64 -0
- package/dist/components/energy-history-chart.d.ts +64 -0
- package/dist/components/energy-history-chart.js +62 -0
- package/dist/components/energy-history-chart.js.map +1 -0
- package/dist/components/entity-card.cjs +11 -11
- package/dist/components/entity-card.cjs.map +1 -1
- package/dist/components/entity-card.d.ts +1 -1
- package/dist/components/field.cjs +15 -15
- package/dist/components/field.cjs.map +1 -1
- package/dist/components/hero-metric.cjs +4 -4
- package/dist/components/hero-metric.cjs.map +1 -1
- package/dist/components/icon.cjs +2 -2
- package/dist/components/icon.cjs.map +1 -1
- package/dist/components/icon.js +1 -1
- package/dist/components/info-block.cjs +9 -9
- package/dist/components/info-block.cjs.map +1 -1
- package/dist/components/info-block.d.cts +1 -1
- package/dist/components/info-block.d.ts +1 -1
- package/dist/components/input-date.cjs +3 -3
- package/dist/components/input-date.cjs.map +1 -1
- package/dist/components/input-date.js +1 -1
- package/dist/components/input-group.cjs +5 -5
- package/dist/components/input-group.cjs.map +1 -1
- package/dist/components/input-otp.cjs +3 -3
- package/dist/components/input-otp.cjs.map +1 -1
- package/dist/components/input-otp.js +1 -1
- package/dist/components/input.cjs +3 -3
- package/dist/components/input.cjs.map +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/label.cjs +2 -2
- package/dist/components/label.cjs.map +1 -1
- package/dist/components/layout.cjs +2 -2
- package/dist/components/layout.cjs.map +1 -1
- package/dist/components/line-chart.cjs +7 -7
- package/dist/components/line-chart.cjs.map +1 -1
- package/dist/components/line-chart.js +4 -4
- package/dist/components/list.cjs +10 -10
- package/dist/components/list.cjs.map +1 -1
- package/dist/components/list.js +1 -1
- package/dist/components/list.js.map +1 -1
- package/dist/components/marker.cjs +4 -4
- package/dist/components/marker.cjs.map +1 -1
- package/dist/components/marker.d.cts +1 -1
- package/dist/components/marker.d.ts +1 -1
- package/dist/components/message-scroller.cjs +8 -8
- package/dist/components/message-scroller.cjs.map +1 -1
- package/dist/components/message-scroller.js +4 -4
- package/dist/components/message-scroller.js.map +1 -1
- package/dist/components/message.cjs +7 -7
- package/dist/components/message.cjs.map +1 -1
- package/dist/components/number-flow.d.cts +2 -12
- package/dist/components/number-flow.d.ts +2 -12
- package/dist/components/page-header.cjs +6 -6
- package/dist/components/page-header.cjs.map +1 -1
- package/dist/components/page-header.d.cts +1 -1
- package/dist/components/page-header.d.ts +1 -1
- package/dist/components/page-header.js +1 -1
- package/dist/components/period-tabs.cjs +6 -6
- package/dist/components/period-tabs.cjs.map +1 -1
- package/dist/components/pill.cjs +3 -3
- package/dist/components/pill.cjs.map +1 -1
- package/dist/components/pill.js +2 -2
- package/dist/components/popover.cjs +2 -2
- package/dist/components/popover.cjs.map +1 -1
- package/dist/components/power-history-chart.cjs +498 -0
- package/dist/components/power-history-chart.cjs.map +1 -0
- package/dist/components/power-history-chart.d.cts +71 -0
- package/dist/components/power-history-chart.d.ts +71 -0
- package/dist/components/power-history-chart.js +496 -0
- package/dist/components/power-history-chart.js.map +1 -0
- package/dist/components/power-network.cjs +559 -0
- package/dist/components/power-network.cjs.map +1 -0
- package/dist/components/power-network.d.cts +80 -0
- package/dist/components/power-network.d.ts +80 -0
- package/dist/components/power-network.js +553 -0
- package/dist/components/power-network.js.map +1 -0
- package/dist/components/progress.cjs +2 -2
- package/dist/components/progress.cjs.map +1 -1
- package/dist/components/radio-card-group.cjs +3 -3
- package/dist/components/radio-card-group.cjs.map +1 -1
- package/dist/components/radio.cjs +3 -3
- package/dist/components/radio.cjs.map +1 -1
- package/dist/components/radio.js +1 -1
- package/dist/components/sankey-chart.cjs +13 -3
- package/dist/components/sankey-chart.cjs.map +1 -1
- package/dist/components/sankey-chart.d.cts +3 -2
- package/dist/components/sankey-chart.d.ts +3 -2
- package/dist/components/sankey-chart.js +11 -2
- package/dist/components/sankey-chart.js.map +1 -1
- package/dist/components/schedule-editor.cjs +9 -9
- package/dist/components/schedule-editor.cjs.map +1 -1
- package/dist/components/schedule-list.cjs +11 -11
- package/dist/components/schedule-list.cjs.map +1 -1
- package/dist/components/schedule-list.js +1 -1
- package/dist/components/scroll-area.cjs +3 -3
- package/dist/components/scroll-area.cjs.map +1 -1
- package/dist/components/segmented-control.cjs +4 -4
- package/dist/components/segmented-control.cjs.map +1 -1
- package/dist/components/select.cjs +13 -13
- package/dist/components/select.cjs.map +1 -1
- package/dist/components/select.js +2 -2
- package/dist/components/separator.cjs +3 -3
- package/dist/components/separator.cjs.map +1 -1
- package/dist/components/series-legend.cjs +4 -4
- package/dist/components/series-legend.cjs.map +1 -1
- package/dist/components/sheet.cjs +9 -9
- package/dist/components/sheet.cjs.map +1 -1
- package/dist/components/sheet.js +1 -1
- package/dist/components/skeleton.cjs +3 -3
- package/dist/components/skeleton.cjs.map +1 -1
- package/dist/components/slider.cjs +11 -11
- package/dist/components/slider.cjs.map +1 -1
- package/dist/components/solar-forecast-chart.cjs +108 -0
- package/dist/components/solar-forecast-chart.cjs.map +1 -0
- package/dist/components/solar-forecast-chart.d.cts +77 -0
- package/dist/components/solar-forecast-chart.d.ts +77 -0
- package/dist/components/solar-forecast-chart.js +103 -0
- package/dist/components/solar-forecast-chart.js.map +1 -0
- package/dist/components/sortable-list.cjs +3 -3
- package/dist/components/sortable-list.cjs.map +1 -1
- package/dist/components/spinner.cjs +2 -2
- package/dist/components/spinner.cjs.map +1 -1
- package/dist/components/stat-card.cjs +8 -8
- package/dist/components/stat-card.cjs.map +1 -1
- package/dist/components/stepper.cjs +3 -3
- package/dist/components/stepper.cjs.map +1 -1
- package/dist/components/stepper.js +1 -1
- package/dist/components/switch.cjs +3 -3
- package/dist/components/switch.cjs.map +1 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/table.cjs +9 -9
- package/dist/components/table.cjs.map +1 -1
- package/dist/components/table.js +1 -1
- package/dist/components/tabs.cjs +8 -8
- package/dist/components/tabs.cjs.map +1 -1
- package/dist/components/tabs.js +1 -1
- package/dist/components/textarea.cjs +3 -3
- package/dist/components/textarea.cjs.map +1 -1
- package/dist/components/textarea.js +1 -1
- package/dist/components/theme.cjs +2 -2
- package/dist/components/theme.cjs.map +1 -1
- package/dist/components/theme.js +1 -1
- package/dist/components/thumbnail.cjs +3 -3
- package/dist/components/thumbnail.cjs.map +1 -1
- package/dist/components/time-picker.cjs +3 -3
- package/dist/components/time-picker.cjs.map +1 -1
- package/dist/components/toast.cjs +8 -8
- package/dist/components/toast.cjs.map +1 -1
- package/dist/components/toast.js +1 -1
- package/dist/components/tooltip.cjs +4 -4
- package/dist/components/tooltip.cjs.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/typography.cjs +6 -6
- package/dist/components/typography.cjs.map +1 -1
- package/dist/components/wizard-layout.cjs +8 -8
- package/dist/components/wizard-layout.cjs.map +1 -1
- package/dist/lib/input-box-variants.cjs +9 -9
- package/dist/lib/input-box-variants.cjs.map +1 -1
- package/dist/number-flow-Bch81ixZ.d.cts +14 -0
- package/dist/number-flow-Bch81ixZ.d.ts +14 -0
- package/dist/styles/spark-default.css +11 -0
- package/dist/utils/build-sankey-nodes.cjs +72 -0
- package/dist/utils/build-sankey-nodes.cjs.map +1 -0
- package/dist/utils/build-sankey-nodes.d.cts +27 -0
- package/dist/utils/build-sankey-nodes.d.ts +27 -0
- package/dist/utils/build-sankey-nodes.js +71 -0
- package/dist/utils/build-sankey-nodes.js.map +1 -0
- package/dist/utils/cn.cjs +2 -2
- package/dist/utils/cn.d.cts +1 -1
- package/dist/utils/cn.d.ts +1 -1
- package/dist/utils/cn.js +1 -1
- package/dist/utils/format.cjs +13 -0
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.d.cts +7 -1
- package/dist/utils/format.d.ts +7 -1
- package/dist/utils/format.js +12 -1
- package/dist/utils/format.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line-chart.cjs","names":["curveMonotoneX","curveStep","curveStepAfter","curveStepBefore","curveLinear","localeNumberFormat","formatYAxisTick","React","useChartZoom","useReducedMotion","buildYAxisScale","useAnimatedChartDomain","buildTimeTicks","useMeasuredSize","clampTooltipPosition","Group","LinePath","ChartZoomResetButton","ChartTooltipCard"],"sources":["../../src/components/line-chart.tsx"],"sourcesContent":["import {\n curveLinear,\n curveMonotoneX,\n curveStep,\n curveStepAfter,\n curveStepBefore,\n} from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { Group } from '@visx/group'\nimport { scaleLinear, scaleTime } from '@visx/scale'\nimport { LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector, extent } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useMemo } from 'react'\nimport { useAnimatedChartDomain, useChartZoom } from '../hooks/use-chart-zoom'\nimport { useReducedMotion } from '../hooks/use-reduced-motion'\nimport { buildYAxisScale, formatYAxisTick } from '../utils/chart-scale'\nimport { buildTimeTicks } from '../utils/chart-time-ticks'\nimport type { TimeDomain } from '../utils/chart-zoom'\nimport { cn } from '../utils/cn'\nimport { localeNumberFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n ChartZoomResetButton,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\n\nexport interface LineChartDatum {\n date: Date\n value: number\n}\n\nexport type LineChartCurve =\n | 'monotone'\n | 'step'\n | 'step-after'\n | 'step-before'\n | 'linear'\n\nexport interface LineChartSeries {\n id: string\n data: LineChartDatum[]\n /** Label shown in the hover tooltip. Defaults to `id`. */\n label?: React.ReactNode\n className?: string\n /**\n * CSS color for the tooltip swatch and hover dot (e.g. `var(--orange-9)`).\n * Defaults to the palette color for the series index. Set this whenever\n * `className` overrides the stroke so the tooltip stays in sync.\n */\n color?: string\n /** Curve interpolation type. Default: inherits from LineChartProps.curve */\n curve?: LineChartCurve\n /** Render as a dashed line */\n dashed?: boolean\n /** Custom dash pattern (e.g. \"8,4\" or \"4,2,1,2\"). Only applies when dashed is true. */\n dashPattern?: string\n}\n\nexport interface LineChartProps {\n series: LineChartSeries[]\n width: number\n height: number\n margin?: { top: number; right: number; bottom: number; left: number }\n /** Default curve for all series. Default: 'monotone' */\n curve?: LineChartCurve\n showGrid?: boolean\n showTooltip?: boolean\n className?: string\n xTickCount?: number\n yTickCount?: number\n /** Fixes the y-axis to an explicit `[min, max]`. Defaults to the data extent. */\n yDomain?: [number, number]\n /** Formats the x-axis tick labels. */\n formatDate?: (date: Date) => string\n /** Formats the tooltip title. Defaults to `formatDate`. */\n formatTooltipDate?: (date: Date) => string\n formatValue?: (value: number) => string\n /**\n * Unit shown once at the top of the y-axis (e.g. \"kW\"). When set, y-axis tick\n * labels render as plain numbers (matching BarChart / PowerHistoryChart)\n * instead of repeating `formatValue` on every tick. `formatValue` still feeds\n * the tooltip. Omit to keep the per-tick `formatValue` labels.\n */\n yAxisUnit?: string\n /** Overrides the y-axis tick formatter used when `yAxisUnit` is set. Defaults to the shared compact formatter. */\n formatYTick?: (value: number) => string\n /**\n * Vertical reference markers positioned on the time axis (e.g. sunrise /\n * sunset). Each renders a dashed line across the plot with an optional label.\n */\n referenceLines?: Array<{ key: string; date: Date; label?: React.ReactNode }>\n /**\n * HTML content pinned to the top of the plot at a given time (e.g. an hourly\n * weather icon). Positioned by the same time scale as the series, so it lines\n * up with the curve below it.\n */\n topContent?: Array<{ key: string; date: Date; content: React.ReactNode }>\n zoomable?: boolean\n zoomResetLabel?: string\n zoomDomain?: [Date, Date] | null\n onZoomDomainChange?: (domain: [Date, Date] | null) => void\n}\n\nconst DEFAULT_MARGIN = { top: 16, right: 16, bottom: 40, left: 48 }\nconst DETAILED_X_AXIS_THRESHOLD = 6 * 60 * 60 * 1000\n\nconst CURVE_MAP = {\n monotone: curveMonotoneX,\n step: curveStep,\n 'step-after': curveStepAfter,\n 'step-before': curveStepBefore,\n linear: curveLinear,\n} as const\n\nconst resolveCurve = (type: LineChartCurve) => CURVE_MAP[type]\n\nconst dateBisector = bisector<LineChartDatum, Date>((d) => d.date)\nconst bisectDate = (data: LineChartDatum[], x: Date, lo?: number) =>\n dateBisector.left(data, x, lo)\n\n/** Whether `date` falls within a series' own x-range (data is sorted ascending). */\nfunction withinDomain(data: LineChartDatum[], date: Date): boolean {\n const first = data[0]\n const last = data[data.length - 1]\n if (!first || !last) return false\n const t = date.valueOf()\n return t >= first.date.valueOf() && t <= last.date.valueOf()\n}\n\n/**\n * y-domain padded 10% beyond the data extent. The baseline is pinned to 0 so\n * all-positive series render from the axis up; a series that dips below 0\n * (signed grid / battery power) extends the domain downward to keep the zero\n * crossing visible.\n */\nfunction computeYDomain(data: LineChartDatum[]): [number, number] {\n let min = 0\n let max = 0\n for (const d of data) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n if (min === max) return [min, min + 1]\n return [min * 1.1, max * 1.1]\n}\n\nfunction nearestDatum(\n data: LineChartDatum[],\n date: Date,\n): LineChartDatum | undefined {\n if (data.length === 0) return undefined\n const index = bisectDate(data, date, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return d1\n if (!d1) return d0\n return date.valueOf() - d0.date.valueOf() > d1.date.valueOf() - date.valueOf()\n ? d1\n : d0\n}\n\nconst defaultFormatDate = (date: Date) =>\n date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n\nconst defaultValueFormat = localeNumberFormat()\nconst defaultFormatValue = (value: number) => defaultValueFormat.format(value)\n\nconst LINE_COLORS = [\n 'stroke-spark-9',\n 'stroke-green-9',\n 'stroke-blue-9',\n 'stroke-orange-9',\n 'stroke-red-9',\n]\n\nconst DOT_COLORS = [\n 'fill-spark-9',\n 'fill-green-9',\n 'fill-blue-9',\n 'fill-orange-9',\n 'fill-red-9',\n]\n\n// CSS-var colours matching LINE_COLORS, used for the tooltip legend dots.\nconst LEGEND_COLORS = [\n 'var(--spark-9)',\n 'var(--green-9)',\n 'var(--blue-9)',\n 'var(--orange-9)',\n 'var(--red-9)',\n]\n\nfunction toTimeDomain(dates: [Date, Date]): TimeDomain {\n return [dates[0].valueOf(), dates[1].valueOf()]\n}\n\nfunction fromTimeDomain(domain: TimeDomain): [Date, Date] {\n return [new Date(domain[0]), new Date(domain[1])]\n}\n\nfunction touchDistance(a: React.Touch, b: React.Touch): number {\n return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY)\n}\n\nfunction touchMidpoint(\n a: React.Touch,\n b: React.Touch,\n): {\n clientX: number\n clientY: number\n} {\n return {\n clientX: (a.clientX + b.clientX) / 2,\n clientY: (a.clientY + b.clientY) / 2,\n }\n}\n\nconst LineChart: React.FC<LineChartProps> = ({\n series,\n width,\n height,\n margin = DEFAULT_MARGIN,\n curve: defaultCurve = 'monotone',\n showGrid = true,\n showTooltip: enableTooltip = true,\n className,\n xTickCount = 5,\n yTickCount = 4,\n formatDate = defaultFormatDate,\n formatTooltipDate,\n formatValue = defaultFormatValue,\n yAxisUnit,\n formatYTick = formatYAxisTick,\n yDomain,\n referenceLines,\n topContent,\n zoomable = false,\n zoomResetLabel,\n zoomDomain: zoomDomainProp,\n onZoomDomainChange,\n}) => {\n const plotClipId = React.useId()\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<{ date: Date }>()\n\n const innerWidth = Math.max(0, width - margin.left - margin.right)\n const innerHeight = Math.max(0, height - margin.top - margin.bottom)\n\n const allData = useMemo(() => series.flatMap((s) => s.data), [series])\n\n const fullDomain = useMemo((): TimeDomain | null => {\n const ext = extent(allData, (d) => d.date.valueOf())\n if (ext[0] == null || ext[1] == null) return null\n return [ext[0], ext[1]]\n }, [allData])\n\n const controlledZoomDomain =\n zoomDomainProp === undefined\n ? undefined\n : zoomDomainProp == null\n ? null\n : toTimeDomain(zoomDomainProp)\n\n const handleZoomDomainChange = useCallback(\n (domain: TimeDomain | null) => {\n onZoomDomainChange?.(domain ? fromTimeDomain(domain) : null)\n },\n [onZoomDomainChange],\n )\n\n const zoom = useChartZoom({\n enabled: zoomable,\n fullDomain,\n zoomDomain: controlledZoomDomain,\n onZoomDomainChange: handleZoomDomainChange,\n })\n\n const xDomain = useMemo((): [Date, Date] => {\n if (zoom.zoomWindow) return fromTimeDomain(zoom.zoomWindow)\n const ext = extent(allData, (d) => d.date) as\n | [Date, Date]\n | [undefined, undefined]\n if (ext[0] && ext[1]) return [ext[0], ext[1]]\n const now = new Date()\n return [now, now]\n }, [allData, zoom.zoomWindow])\n\n const xScale = useMemo(\n () =>\n scaleTime({\n range: [0, innerWidth],\n domain: xDomain,\n }),\n [innerWidth, xDomain],\n )\n\n const visibleData = useMemo(() => {\n const [start, end] = xDomain\n const t0 = start.valueOf()\n const t1 = end.valueOf()\n return allData.filter((d) => {\n const t = d.date.valueOf()\n return t >= t0 && t <= t1\n })\n }, [allData, xDomain])\n\n const reducedMotion = useReducedMotion()\n\n // With a unit, the y-axis is generated by the shared scale util (nice ticks\n // + rounded bounds) so it matches BarChart / PowerHistoryChart exactly.\n const yAxisScale = useMemo(() => {\n if (yAxisUnit === undefined) return null\n let min = 0\n let max = 0\n for (const d of visibleData) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n return buildYAxisScale(min, max)\n }, [visibleData, yAxisUnit])\n\n const targetYDomain = useMemo((): TimeDomain => {\n if (yDomain) return yDomain\n if (yAxisScale) return [yAxisScale.min, yAxisScale.max]\n return scaleLinear({\n domain: computeYDomain(visibleData),\n nice: true,\n }).domain() as [number, number]\n }, [visibleData, yAxisScale, yDomain])\n\n const displayedYDomain = useAnimatedChartDomain(targetYDomain, {\n animateChanges: zoom.isPinching,\n reducedMotion,\n })\n\n const yScale = useMemo(\n () =>\n scaleLinear({\n range: [innerHeight, 0],\n domain: [displayedYDomain[0], displayedYDomain[1]],\n }),\n [displayedYDomain, innerHeight],\n )\n\n const xTicks = zoom.isZoomed\n ? buildTimeTicks(\n xDomain[0].valueOf(),\n xDomain[1].valueOf(),\n xTickCount,\n ).map((tick) => new Date(tick))\n : xScale.ticks(xTickCount)\n const yTicks = yAxisScale ? yAxisScale.ticks : yScale.ticks(yTickCount)\n const formatAxisDate =\n zoom.isZoomed &&\n xDomain[1].valueOf() - xDomain[0].valueOf() <= DETAILED_X_AXIS_THRESHOLD\n ? (formatTooltipDate ?? formatDate)\n : formatDate\n\n const domainXFromEvent = useCallback(\n (\n event:\n | React.MouseEvent<SVGRectElement>\n | React.PointerEvent<SVGRectElement>\n | React.TouchEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point) return null\n return xScale.invert(point.x - margin.left).valueOf()\n },\n [margin.left, xScale],\n )\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n if (!enableTooltip || zoom.isBrushing || zoom.isPanning) return\n\n const point = localPoint(event)\n if (!point) return\n\n const x0 = xScale.invert(point.x - margin.left)\n\n // Snap to the data point closest to the cursor across all series, so the\n // cursor line + tooltip line up with real values.\n let anchorDate: Date | null = null\n let bestDistance = Infinity\n for (const s of series) {\n const d = nearestDatum(s.data, x0)\n if (!d) continue\n const distance = Math.abs(d.date.valueOf() - x0.valueOf())\n if (distance < bestDistance) {\n bestDistance = distance\n anchorDate = d.date\n }\n }\n\n if (!anchorDate) return\n\n showTooltip({\n tooltipData: { date: anchorDate },\n tooltipLeft: xScale(anchorDate) + margin.left,\n tooltipTop: margin.top,\n })\n },\n [\n enableTooltip,\n margin,\n series,\n showTooltip,\n xScale,\n zoom.isBrushing,\n zoom.isPanning,\n ],\n )\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? series.flatMap((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return []\n return [\n {\n key: s.id,\n label: s.label ?? s.id,\n color:\n s.color ?? (LEGEND_COLORS[i % LEGEND_COLORS.length] as string),\n value: formatValue(d.value),\n marker: s.dashed ? ('dashed-line' as const) : ('dot' as const),\n },\n ]\n })\n : []\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? margin.top,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n if (width < 10 || height < 10) return null\n\n return (\n <div className={cn('relative', className)}>\n <svg className=\"block\" height={height} width={width}>\n <defs>\n <clipPath id={plotClipId}>\n <rect height={innerHeight} width={innerWidth} x={0} y={0} />\n </clipPath>\n </defs>\n <Group left={margin.left} top={margin.top}>\n {/* Grid lines */}\n {showGrid &&\n yTicks.map((tick) => (\n <line\n className=\"stroke-gray-a4\"\n key={`grid-y-${tick}`}\n strokeDasharray=\"4,4\"\n x1={0}\n x2={innerWidth}\n y1={yScale(tick)}\n y2={yScale(tick)}\n />\n ))}\n\n {/* X axis */}\n <line\n className=\"stroke-gray-a6\"\n x1={0}\n x2={innerWidth}\n y1={innerHeight}\n y2={innerHeight}\n />\n\n {/* Reference markers (e.g. sunrise / sunset) */}\n {referenceLines?.map((ref) => {\n const x = xScale(ref.date)\n if (!Number.isFinite(x) || x < 0 || x > innerWidth) return null\n // Sit the label beside the line, on whichever side keeps it in\n // frame: right of a left-half marker, left of a right-half one.\n const anchorEnd = x > innerWidth / 2\n return (\n <g key={ref.key} pointerEvents=\"none\">\n <line\n className=\"stroke-orange-8\"\n strokeDasharray=\"3,3\"\n x1={x}\n x2={x}\n y1={0}\n y2={innerHeight}\n />\n {ref.label != null && (\n <text\n className=\"fill-orange-11 text-[10px]\"\n dy=\"0.9em\"\n textAnchor={anchorEnd ? 'end' : 'start'}\n x={x + (anchorEnd ? -4 : 4)}\n y={0}\n >\n {ref.label}\n </text>\n )}\n </g>\n )\n })}\n\n {/* X axis labels */}\n {xTicks.map((tick, index) => (\n <text\n dy=\"1em\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n key={`x-${tick.valueOf()}`}\n textAnchor={\n index === 0\n ? 'start'\n : index === xTicks.length - 1\n ? 'end'\n : 'middle'\n }\n x={xScale(tick)}\n y={innerHeight + 8}\n >\n {formatAxisDate(tick)}\n </text>\n ))}\n\n {/* Y axis. With `yAxisUnit`, matches BarChart: a vertical axis line,\n short tick marks, plain numeric ticks, and the unit as a bold\n top-left header (top tick hidden). Without it, ticks repeat\n `formatValue` and no axis line/marks are drawn. */}\n {yAxisUnit !== undefined && (\n <line\n stroke=\"var(--neutral-a6)\"\n strokeWidth={1}\n x1={0}\n x2={0}\n y1={0}\n y2={innerHeight}\n />\n )}\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-${tick}`}>\n {yAxisUnit !== undefined && (\n <line\n stroke=\"var(--neutral-a8)\"\n strokeWidth={1}\n x1={-4}\n x2={0}\n y1={y}\n y2={y}\n />\n )}\n <text\n dy=\"0.32em\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n textAnchor=\"end\"\n x={yAxisUnit !== undefined ? -7 : -4}\n y={y}\n >\n {yAxisUnit !== undefined\n ? formatYTick(tick)\n : formatValue(tick)}\n </text>\n </g>\n )\n })}\n {yAxisUnit !== undefined && (\n <text\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n fontWeight={600}\n textAnchor=\"start\"\n x={-margin.left}\n y={-6}\n >\n {yAxisUnit}\n </text>\n )}\n\n <g clipPath={`url(#${plotClipId})`} data-testid=\"chart-plot-clip\">\n {series.map((s, i) => (\n <LinePath\n className={cn(LINE_COLORS[i % LINE_COLORS.length], s.className)}\n curve={resolveCurve(s.curve ?? defaultCurve)}\n data={s.data}\n fill=\"none\"\n key={s.id}\n strokeDasharray={\n s.dashed ? (s.dashPattern ?? '6,4') : undefined\n }\n strokeWidth={2}\n x={(d) => xScale(d.date)}\n y={(d) => yScale(d.value)}\n />\n ))}\n </g>\n\n {zoom.brushBand && (\n <rect\n data-testid=\"chart-brush-band\"\n fill=\"var(--spark-a4)\"\n height={innerHeight}\n pointerEvents=\"none\"\n width={Math.abs(\n xScale(new Date(zoom.brushBand[1])) -\n xScale(new Date(zoom.brushBand[0])),\n )}\n x={Math.min(\n xScale(new Date(zoom.brushBand[0])),\n xScale(new Date(zoom.brushBand[1])),\n )}\n y={0}\n />\n )}\n\n {(enableTooltip || zoomable) && (\n <rect\n fill=\"transparent\"\n height={innerHeight}\n style={zoomable ? { touchAction: 'pan-y' } : undefined}\n width={innerWidth}\n x={0}\n y={0}\n onMouseLeave={() => {\n if (!zoom.isBrushing) hideTooltip()\n }}\n onMouseMove={(event) => {\n if (zoom.isBrushing) {\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerMove(domainX)\n return\n }\n handleTooltip(event)\n }}\n onPointerDown={(event) => {\n if (!zoomable || event.pointerType === 'touch') return\n if (event.button !== 0) return\n const domainX = domainXFromEvent(event)\n if (domainX == null) return\n event.currentTarget.setPointerCapture?.(event.pointerId)\n hideTooltip()\n zoom.onPointerDown(domainX)\n }}\n onPointerMove={(event) => {\n if (!zoomable) return\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerMove(domainX)\n }}\n onPointerUp={(event) => {\n if (!zoomable) return\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerUp(domainX)\n else zoom.onPointerCancel()\n }}\n onPointerCancel={() => {\n zoom.onPointerCancel()\n }}\n onDoubleClick={() => {\n if (zoomable) zoom.onDoubleClick()\n }}\n onTouchStart={(event) => {\n if (!zoomable) return\n if (event.touches.length === 2) {\n const [a, b] = [event.touches[0], event.touches[1]]\n if (!a || !b) return\n const point = localPoint(\n event.currentTarget,\n touchMidpoint(a, b) as MouseEvent,\n )\n if (!point) return\n const midpoint = Math.min(\n 1,\n Math.max(0, (point.x - margin.left) / innerWidth),\n )\n zoom.onPinchStart(touchDistance(a, b), midpoint)\n hideTooltip()\n return\n }\n if (event.touches.length === 1 && zoom.isZoomed) {\n const point = localPoint(event)\n if (!point) return\n zoom.onPanStart({\n x: (point.x - margin.left) / innerWidth,\n y: (point.y - margin.top) / innerHeight,\n })\n hideTooltip()\n }\n }}\n onTouchMove={(event) => {\n if (zoomable && event.touches.length === 2) {\n const [a, b] = [event.touches[0], event.touches[1]]\n if (!a || !b) return\n const point = localPoint(\n event.currentTarget,\n touchMidpoint(a, b) as MouseEvent,\n )\n if (!point) return\n const midpoint = Math.min(\n 1,\n Math.max(0, (point.x - margin.left) / innerWidth),\n )\n zoom.onPinchMove(touchDistance(a, b), midpoint)\n return\n }\n if (event.touches.length === 1 && zoom.isZoomed) {\n const point = localPoint(event)\n if (!point) return\n zoom.onPanMove({\n x: (point.x - margin.left) / innerWidth,\n y: (point.y - margin.top) / innerHeight,\n })\n return\n }\n if (event.touches.length === 1) handleTooltip(event)\n }}\n onTouchEnd={(event) => {\n if (zoomable && event.touches.length < 2) zoom.onPinchEnd()\n if (event.touches.length === 0) {\n if (zoomable) zoom.onPanEnd()\n if (zoomable) zoom.onTap()\n hideTooltip()\n }\n }}\n onTouchCancel={() => {\n zoom.onPinchEnd()\n zoom.onPanEnd()\n }}\n />\n )}\n\n {/* Cursor line + a dot per series */}\n {tooltipOpen &&\n tooltipData &&\n !zoom.isBrushing &&\n !zoom.isPanning && (\n <>\n <line\n className=\"stroke-gray-a6\"\n pointerEvents=\"none\"\n strokeDasharray=\"4,4\"\n x1={xScale(tooltipData.date)}\n x2={xScale(tooltipData.date)}\n y1={0}\n y2={innerHeight}\n />\n {series.map((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return null\n return (\n <circle\n key={s.id}\n className={\n s.color\n ? undefined\n : cn(DOT_COLORS[i % DOT_COLORS.length])\n }\n cx={xScale(d.date)}\n cy={yScale(d.value)}\n fill={s.color}\n pointerEvents=\"none\"\n r={4}\n stroke=\"white\"\n strokeWidth={2}\n />\n )\n })}\n </>\n )}\n </Group>\n </svg>\n\n {topContent && topContent.length > 0 && (\n <div\n className=\"pointer-events-none absolute overflow-hidden\"\n style={{\n height: margin.top,\n left: margin.left,\n top: 0,\n width: innerWidth,\n }}\n >\n {topContent.map((item) => {\n const x = xScale(item.date)\n if (!Number.isFinite(x) || x < 0 || x > innerWidth) return null\n return (\n <div\n key={item.key}\n className=\"absolute -translate-x-1/2\"\n style={{ left: x, top: 0 }}\n >\n {item.content}\n </div>\n )\n })}\n </div>\n )}\n\n {zoom.isZoomed && zoomResetLabel && (\n <ChartZoomResetButton label={zoomResetLabel} onReset={zoom.resetZoom} />\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:\n tooltipOpen && tooltipData && !zoom.isPanning && !zoom.isBrushing\n ? 1\n : 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={(formatTooltipDate ?? formatDate)(tooltipData.date)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n )\n}\n\nexport { LineChart }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"line-chart.cjs","names":["curveMonotoneX","curveStep","curveStepAfter","curveStepBefore","curveLinear","localeNumberFormat","formatYAxisTick","React","useChartZoom","useReducedMotion","buildYAxisScale","useAnimatedChartDomain","buildTimeTicks","useMeasuredSize","clampTooltipPosition","Group","LinePath","ChartZoomResetButton","ChartTooltipCard"],"sources":["../../src/components/line-chart.tsx"],"sourcesContent":["import {\n curveLinear,\n curveMonotoneX,\n curveStep,\n curveStepAfter,\n curveStepBefore,\n} from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { Group } from '@visx/group'\nimport { scaleLinear, scaleTime } from '@visx/scale'\nimport { LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector, extent } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useMemo } from 'react'\nimport { useAnimatedChartDomain, useChartZoom } from '../hooks/use-chart-zoom'\nimport { useReducedMotion } from '../hooks/use-reduced-motion'\nimport { buildYAxisScale, formatYAxisTick } from '../utils/chart-scale'\nimport { buildTimeTicks } from '../utils/chart-time-ticks'\nimport type { TimeDomain } from '../utils/chart-zoom'\nimport { cn } from '../utils/cn'\nimport { localeNumberFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n ChartZoomResetButton,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\n\nexport interface LineChartDatum {\n date: Date\n value: number\n}\n\nexport type LineChartCurve =\n | 'monotone'\n | 'step'\n | 'step-after'\n | 'step-before'\n | 'linear'\n\nexport interface LineChartSeries {\n id: string\n data: LineChartDatum[]\n /** Label shown in the hover tooltip. Defaults to `id`. */\n label?: React.ReactNode\n className?: string\n /**\n * CSS color for the tooltip swatch and hover dot (e.g. `var(--orange-9)`).\n * Defaults to the palette color for the series index. Set this whenever\n * `className` overrides the stroke so the tooltip stays in sync.\n */\n color?: string\n /** Curve interpolation type. Default: inherits from LineChartProps.curve */\n curve?: LineChartCurve\n /** Render as a dashed line */\n dashed?: boolean\n /** Custom dash pattern (e.g. \"8,4\" or \"4,2,1,2\"). Only applies when dashed is true. */\n dashPattern?: string\n}\n\nexport interface LineChartProps {\n series: LineChartSeries[]\n width: number\n height: number\n margin?: { top: number; right: number; bottom: number; left: number }\n /** Default curve for all series. Default: 'monotone' */\n curve?: LineChartCurve\n showGrid?: boolean\n showTooltip?: boolean\n className?: string\n xTickCount?: number\n yTickCount?: number\n /** Fixes the y-axis to an explicit `[min, max]`. Defaults to the data extent. */\n yDomain?: [number, number]\n /** Formats the x-axis tick labels. */\n formatDate?: (date: Date) => string\n /** Formats the tooltip title. Defaults to `formatDate`. */\n formatTooltipDate?: (date: Date) => string\n formatValue?: (value: number) => string\n /**\n * Unit shown once at the top of the y-axis (e.g. \"kW\"). When set, y-axis tick\n * labels render as plain numbers (matching BarChart / PowerHistoryChart)\n * instead of repeating `formatValue` on every tick. `formatValue` still feeds\n * the tooltip. Omit to keep the per-tick `formatValue` labels.\n */\n yAxisUnit?: string\n /** Overrides the y-axis tick formatter used when `yAxisUnit` is set. Defaults to the shared compact formatter. */\n formatYTick?: (value: number) => string\n /**\n * Vertical reference markers positioned on the time axis (e.g. sunrise /\n * sunset). Each renders a dashed line across the plot with an optional label.\n */\n referenceLines?: Array<{ key: string; date: Date; label?: React.ReactNode }>\n /**\n * HTML content pinned to the top of the plot at a given time (e.g. an hourly\n * weather icon). Positioned by the same time scale as the series, so it lines\n * up with the curve below it.\n */\n topContent?: Array<{ key: string; date: Date; content: React.ReactNode }>\n zoomable?: boolean\n zoomResetLabel?: string\n zoomDomain?: [Date, Date] | null\n onZoomDomainChange?: (domain: [Date, Date] | null) => void\n}\n\nconst DEFAULT_MARGIN = { top: 16, right: 16, bottom: 40, left: 48 }\nconst DETAILED_X_AXIS_THRESHOLD = 6 * 60 * 60 * 1000\n\nconst CURVE_MAP = {\n monotone: curveMonotoneX,\n step: curveStep,\n 'step-after': curveStepAfter,\n 'step-before': curveStepBefore,\n linear: curveLinear,\n} as const\n\nconst resolveCurve = (type: LineChartCurve) => CURVE_MAP[type]\n\nconst dateBisector = bisector<LineChartDatum, Date>((d) => d.date)\nconst bisectDate = (data: LineChartDatum[], x: Date, lo?: number) =>\n dateBisector.left(data, x, lo)\n\n/** Whether `date` falls within a series' own x-range (data is sorted ascending). */\nfunction withinDomain(data: LineChartDatum[], date: Date): boolean {\n const first = data[0]\n const last = data[data.length - 1]\n if (!first || !last) return false\n const t = date.valueOf()\n return t >= first.date.valueOf() && t <= last.date.valueOf()\n}\n\n/**\n * y-domain padded 10% beyond the data extent. The baseline is pinned to 0 so\n * all-positive series render from the axis up; a series that dips below 0\n * (signed grid / battery power) extends the domain downward to keep the zero\n * crossing visible.\n */\nfunction computeYDomain(data: LineChartDatum[]): [number, number] {\n let min = 0\n let max = 0\n for (const d of data) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n if (min === max) return [min, min + 1]\n return [min * 1.1, max * 1.1]\n}\n\nfunction nearestDatum(\n data: LineChartDatum[],\n date: Date,\n): LineChartDatum | undefined {\n if (data.length === 0) return undefined\n const index = bisectDate(data, date, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return d1\n if (!d1) return d0\n return date.valueOf() - d0.date.valueOf() > d1.date.valueOf() - date.valueOf()\n ? d1\n : d0\n}\n\nconst defaultFormatDate = (date: Date) =>\n date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n\nconst defaultValueFormat = localeNumberFormat()\nconst defaultFormatValue = (value: number) => defaultValueFormat.format(value)\n\nconst LINE_COLORS = [\n 'stroke-spark-9',\n 'stroke-green-9',\n 'stroke-blue-9',\n 'stroke-orange-9',\n 'stroke-red-9',\n]\n\nconst DOT_COLORS = [\n 'fill-spark-9',\n 'fill-green-9',\n 'fill-blue-9',\n 'fill-orange-9',\n 'fill-red-9',\n]\n\n// CSS-var colours matching LINE_COLORS, used for the tooltip legend dots.\nconst LEGEND_COLORS = [\n 'var(--spark-9)',\n 'var(--green-9)',\n 'var(--blue-9)',\n 'var(--orange-9)',\n 'var(--red-9)',\n]\n\nfunction toTimeDomain(dates: [Date, Date]): TimeDomain {\n return [dates[0].valueOf(), dates[1].valueOf()]\n}\n\nfunction fromTimeDomain(domain: TimeDomain): [Date, Date] {\n return [new Date(domain[0]), new Date(domain[1])]\n}\n\nfunction touchDistance(a: React.Touch, b: React.Touch): number {\n return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY)\n}\n\nfunction touchMidpoint(\n a: React.Touch,\n b: React.Touch,\n): {\n clientX: number\n clientY: number\n} {\n return {\n clientX: (a.clientX + b.clientX) / 2,\n clientY: (a.clientY + b.clientY) / 2,\n }\n}\n\nconst LineChart: React.FC<LineChartProps> = ({\n series,\n width,\n height,\n margin = DEFAULT_MARGIN,\n curve: defaultCurve = 'monotone',\n showGrid = true,\n showTooltip: enableTooltip = true,\n className,\n xTickCount = 5,\n yTickCount = 4,\n formatDate = defaultFormatDate,\n formatTooltipDate,\n formatValue = defaultFormatValue,\n yAxisUnit,\n formatYTick = formatYAxisTick,\n yDomain,\n referenceLines,\n topContent,\n zoomable = false,\n zoomResetLabel,\n zoomDomain: zoomDomainProp,\n onZoomDomainChange,\n}) => {\n const plotClipId = React.useId()\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<{ date: Date }>()\n\n const innerWidth = Math.max(0, width - margin.left - margin.right)\n const innerHeight = Math.max(0, height - margin.top - margin.bottom)\n\n const allData = useMemo(() => series.flatMap((s) => s.data), [series])\n\n const fullDomain = useMemo((): TimeDomain | null => {\n const ext = extent(allData, (d) => d.date.valueOf())\n if (ext[0] == null || ext[1] == null) return null\n return [ext[0], ext[1]]\n }, [allData])\n\n const controlledZoomDomain =\n zoomDomainProp === undefined\n ? undefined\n : zoomDomainProp == null\n ? null\n : toTimeDomain(zoomDomainProp)\n\n const handleZoomDomainChange = useCallback(\n (domain: TimeDomain | null) => {\n onZoomDomainChange?.(domain ? fromTimeDomain(domain) : null)\n },\n [onZoomDomainChange],\n )\n\n const zoom = useChartZoom({\n enabled: zoomable,\n fullDomain,\n zoomDomain: controlledZoomDomain,\n onZoomDomainChange: handleZoomDomainChange,\n })\n\n const xDomain = useMemo((): [Date, Date] => {\n if (zoom.zoomWindow) return fromTimeDomain(zoom.zoomWindow)\n const ext = extent(allData, (d) => d.date) as\n | [Date, Date]\n | [undefined, undefined]\n if (ext[0] && ext[1]) return [ext[0], ext[1]]\n const now = new Date()\n return [now, now]\n }, [allData, zoom.zoomWindow])\n\n const xScale = useMemo(\n () =>\n scaleTime({\n range: [0, innerWidth],\n domain: xDomain,\n }),\n [innerWidth, xDomain],\n )\n\n const visibleData = useMemo(() => {\n const [start, end] = xDomain\n const t0 = start.valueOf()\n const t1 = end.valueOf()\n return allData.filter((d) => {\n const t = d.date.valueOf()\n return t >= t0 && t <= t1\n })\n }, [allData, xDomain])\n\n const reducedMotion = useReducedMotion()\n\n // With a unit, the y-axis is generated by the shared scale util (nice ticks\n // + rounded bounds) so it matches BarChart / PowerHistoryChart exactly.\n const yAxisScale = useMemo(() => {\n if (yAxisUnit === undefined) return null\n let min = 0\n let max = 0\n for (const d of visibleData) {\n if (d.value < min) min = d.value\n if (d.value > max) max = d.value\n }\n return buildYAxisScale(min, max)\n }, [visibleData, yAxisUnit])\n\n const targetYDomain = useMemo((): TimeDomain => {\n if (yDomain) return yDomain\n if (yAxisScale) return [yAxisScale.min, yAxisScale.max]\n return scaleLinear({\n domain: computeYDomain(visibleData),\n nice: true,\n }).domain() as [number, number]\n }, [visibleData, yAxisScale, yDomain])\n\n const displayedYDomain = useAnimatedChartDomain(targetYDomain, {\n animateChanges: zoom.isPinching,\n reducedMotion,\n })\n\n const yScale = useMemo(\n () =>\n scaleLinear({\n range: [innerHeight, 0],\n domain: [displayedYDomain[0], displayedYDomain[1]],\n }),\n [displayedYDomain, innerHeight],\n )\n\n const xTicks = zoom.isZoomed\n ? buildTimeTicks(\n xDomain[0].valueOf(),\n xDomain[1].valueOf(),\n xTickCount,\n ).map((tick) => new Date(tick))\n : xScale.ticks(xTickCount)\n const yTicks = yAxisScale ? yAxisScale.ticks : yScale.ticks(yTickCount)\n const formatAxisDate =\n zoom.isZoomed &&\n xDomain[1].valueOf() - xDomain[0].valueOf() <= DETAILED_X_AXIS_THRESHOLD\n ? (formatTooltipDate ?? formatDate)\n : formatDate\n\n const domainXFromEvent = useCallback(\n (\n event:\n | React.MouseEvent<SVGRectElement>\n | React.PointerEvent<SVGRectElement>\n | React.TouchEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point) return null\n return xScale.invert(point.x - margin.left).valueOf()\n },\n [margin.left, xScale],\n )\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n if (!enableTooltip || zoom.isBrushing || zoom.isPanning) return\n\n const point = localPoint(event)\n if (!point) return\n\n const x0 = xScale.invert(point.x - margin.left)\n\n // Snap to the data point closest to the cursor across all series, so the\n // cursor line + tooltip line up with real values.\n let anchorDate: Date | null = null\n let bestDistance = Infinity\n for (const s of series) {\n const d = nearestDatum(s.data, x0)\n if (!d) continue\n const distance = Math.abs(d.date.valueOf() - x0.valueOf())\n if (distance < bestDistance) {\n bestDistance = distance\n anchorDate = d.date\n }\n }\n\n if (!anchorDate) return\n\n showTooltip({\n tooltipData: { date: anchorDate },\n tooltipLeft: xScale(anchorDate) + margin.left,\n tooltipTop: margin.top,\n })\n },\n [\n enableTooltip,\n margin,\n series,\n showTooltip,\n xScale,\n zoom.isBrushing,\n zoom.isPanning,\n ],\n )\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? series.flatMap((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return []\n return [\n {\n key: s.id,\n label: s.label ?? s.id,\n color:\n s.color ?? (LEGEND_COLORS[i % LEGEND_COLORS.length] as string),\n value: formatValue(d.value),\n marker: s.dashed ? ('dashed-line' as const) : ('dot' as const),\n },\n ]\n })\n : []\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? margin.top,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n if (width < 10 || height < 10) return null\n\n return (\n <div className={cn('relative', className)}>\n <svg className=\"block\" height={height} width={width}>\n <defs>\n <clipPath id={plotClipId}>\n <rect height={innerHeight} width={innerWidth} x={0} y={0} />\n </clipPath>\n </defs>\n <Group left={margin.left} top={margin.top}>\n {/* Grid lines */}\n {showGrid &&\n yTicks.map((tick) => (\n <line\n className=\"stroke-gray-a4\"\n key={`grid-y-${tick}`}\n strokeDasharray=\"4,4\"\n x1={0}\n x2={innerWidth}\n y1={yScale(tick)}\n y2={yScale(tick)}\n />\n ))}\n\n {/* X axis */}\n <line\n className=\"stroke-gray-a6\"\n x1={0}\n x2={innerWidth}\n y1={innerHeight}\n y2={innerHeight}\n />\n\n {/* Reference markers (e.g. sunrise / sunset) */}\n {referenceLines?.map((ref) => {\n const x = xScale(ref.date)\n if (!Number.isFinite(x) || x < 0 || x > innerWidth) return null\n // Sit the label beside the line, on whichever side keeps it in\n // frame: right of a left-half marker, left of a right-half one.\n const anchorEnd = x > innerWidth / 2\n return (\n <g key={ref.key} pointerEvents=\"none\">\n <line\n className=\"stroke-orange-8\"\n strokeDasharray=\"3,3\"\n x1={x}\n x2={x}\n y1={0}\n y2={innerHeight}\n />\n {ref.label != null && (\n <text\n className=\"fill-orange-11 text-[10px]\"\n dy=\"0.9em\"\n textAnchor={anchorEnd ? 'end' : 'start'}\n x={x + (anchorEnd ? -4 : 4)}\n y={0}\n >\n {ref.label}\n </text>\n )}\n </g>\n )\n })}\n\n {/* X axis labels */}\n {xTicks.map((tick, index) => (\n <text\n dy=\"1em\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n key={`x-${tick.valueOf()}`}\n textAnchor={\n index === 0\n ? 'start'\n : index === xTicks.length - 1\n ? 'end'\n : 'middle'\n }\n x={xScale(tick)}\n y={innerHeight + 8}\n >\n {formatAxisDate(tick)}\n </text>\n ))}\n\n {/* Y axis. With `yAxisUnit`, matches BarChart: a vertical axis line,\n short tick marks, plain numeric ticks, and the unit as a bold\n top-left header (top tick hidden). Without it, ticks repeat\n `formatValue` and no axis line/marks are drawn. */}\n {yAxisUnit !== undefined && (\n <line\n stroke=\"var(--neutral-a6)\"\n strokeWidth={1}\n x1={0}\n x2={0}\n y1={0}\n y2={innerHeight}\n />\n )}\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-${tick}`}>\n {yAxisUnit !== undefined && (\n <line\n stroke=\"var(--neutral-a8)\"\n strokeWidth={1}\n x1={-4}\n x2={0}\n y1={y}\n y2={y}\n />\n )}\n <text\n dy=\"0.32em\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n textAnchor=\"end\"\n x={yAxisUnit !== undefined ? -7 : -4}\n y={y}\n >\n {yAxisUnit !== undefined\n ? formatYTick(tick)\n : formatValue(tick)}\n </text>\n </g>\n )\n })}\n {yAxisUnit !== undefined && (\n <text\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n fontWeight={600}\n textAnchor=\"start\"\n x={-margin.left}\n y={-6}\n >\n {yAxisUnit}\n </text>\n )}\n\n <g clipPath={`url(#${plotClipId})`} data-testid=\"chart-plot-clip\">\n {series.map((s, i) => (\n <LinePath\n className={cn(LINE_COLORS[i % LINE_COLORS.length], s.className)}\n curve={resolveCurve(s.curve ?? defaultCurve)}\n data={s.data}\n fill=\"none\"\n key={s.id}\n strokeDasharray={\n s.dashed ? (s.dashPattern ?? '6,4') : undefined\n }\n strokeWidth={2}\n x={(d) => xScale(d.date)}\n y={(d) => yScale(d.value)}\n />\n ))}\n </g>\n\n {zoom.brushBand && (\n <rect\n data-testid=\"chart-brush-band\"\n fill=\"var(--spark-a4)\"\n height={innerHeight}\n pointerEvents=\"none\"\n width={Math.abs(\n xScale(new Date(zoom.brushBand[1])) -\n xScale(new Date(zoom.brushBand[0])),\n )}\n x={Math.min(\n xScale(new Date(zoom.brushBand[0])),\n xScale(new Date(zoom.brushBand[1])),\n )}\n y={0}\n />\n )}\n\n {(enableTooltip || zoomable) && (\n <rect\n fill=\"transparent\"\n height={innerHeight}\n style={zoomable ? { touchAction: 'pan-y' } : undefined}\n width={innerWidth}\n x={0}\n y={0}\n onMouseLeave={() => {\n if (!zoom.isBrushing) hideTooltip()\n }}\n onMouseMove={(event) => {\n if (zoom.isBrushing) {\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerMove(domainX)\n return\n }\n handleTooltip(event)\n }}\n onPointerDown={(event) => {\n if (!zoomable || event.pointerType === 'touch') return\n if (event.button !== 0) return\n const domainX = domainXFromEvent(event)\n if (domainX == null) return\n event.currentTarget.setPointerCapture?.(event.pointerId)\n hideTooltip()\n zoom.onPointerDown(domainX)\n }}\n onPointerMove={(event) => {\n if (!zoomable) return\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerMove(domainX)\n }}\n onPointerUp={(event) => {\n if (!zoomable) return\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerUp(domainX)\n else zoom.onPointerCancel()\n }}\n onPointerCancel={() => {\n zoom.onPointerCancel()\n }}\n onDoubleClick={() => {\n if (zoomable) zoom.onDoubleClick()\n }}\n onTouchStart={(event) => {\n if (!zoomable) return\n if (event.touches.length === 2) {\n const [a, b] = [event.touches[0], event.touches[1]]\n if (!a || !b) return\n const point = localPoint(\n event.currentTarget,\n touchMidpoint(a, b) as MouseEvent,\n )\n if (!point) return\n const midpoint = Math.min(\n 1,\n Math.max(0, (point.x - margin.left) / innerWidth),\n )\n zoom.onPinchStart(touchDistance(a, b), midpoint)\n hideTooltip()\n return\n }\n if (event.touches.length === 1 && zoom.isZoomed) {\n const point = localPoint(event)\n if (!point) return\n zoom.onPanStart({\n x: (point.x - margin.left) / innerWidth,\n y: (point.y - margin.top) / innerHeight,\n })\n hideTooltip()\n }\n }}\n onTouchMove={(event) => {\n if (zoomable && event.touches.length === 2) {\n const [a, b] = [event.touches[0], event.touches[1]]\n if (!a || !b) return\n const point = localPoint(\n event.currentTarget,\n touchMidpoint(a, b) as MouseEvent,\n )\n if (!point) return\n const midpoint = Math.min(\n 1,\n Math.max(0, (point.x - margin.left) / innerWidth),\n )\n zoom.onPinchMove(touchDistance(a, b), midpoint)\n return\n }\n if (event.touches.length === 1 && zoom.isZoomed) {\n const point = localPoint(event)\n if (!point) return\n zoom.onPanMove({\n x: (point.x - margin.left) / innerWidth,\n y: (point.y - margin.top) / innerHeight,\n })\n return\n }\n if (event.touches.length === 1) handleTooltip(event)\n }}\n onTouchEnd={(event) => {\n if (zoomable && event.touches.length < 2) zoom.onPinchEnd()\n if (event.touches.length === 0) {\n if (zoomable) zoom.onPanEnd()\n if (zoomable) zoom.onTap()\n hideTooltip()\n }\n }}\n onTouchCancel={() => {\n zoom.onPinchEnd()\n zoom.onPanEnd()\n }}\n />\n )}\n\n {/* Cursor line + a dot per series */}\n {tooltipOpen &&\n tooltipData &&\n !zoom.isBrushing &&\n !zoom.isPanning && (\n <>\n <line\n className=\"stroke-gray-a6\"\n pointerEvents=\"none\"\n strokeDasharray=\"4,4\"\n x1={xScale(tooltipData.date)}\n x2={xScale(tooltipData.date)}\n y1={0}\n y2={innerHeight}\n />\n {series.map((s, i) => {\n const d = nearestDatum(s.data, tooltipData.date)\n if (!d || !withinDomain(s.data, tooltipData.date)) return null\n return (\n <circle\n key={s.id}\n className={\n s.color\n ? undefined\n : cn(DOT_COLORS[i % DOT_COLORS.length])\n }\n cx={xScale(d.date)}\n cy={yScale(d.value)}\n fill={s.color}\n pointerEvents=\"none\"\n r={4}\n stroke=\"white\"\n strokeWidth={2}\n />\n )\n })}\n </>\n )}\n </Group>\n </svg>\n\n {topContent && topContent.length > 0 && (\n <div\n className=\"pointer-events-none absolute overflow-hidden\"\n style={{\n height: margin.top,\n left: margin.left,\n top: 0,\n width: innerWidth,\n }}\n >\n {topContent.map((item) => {\n const x = xScale(item.date)\n if (!Number.isFinite(x) || x < 0 || x > innerWidth) return null\n return (\n <div\n key={item.key}\n className=\"absolute -translate-x-1/2\"\n style={{ left: x, top: 0 }}\n >\n {item.content}\n </div>\n )\n })}\n </div>\n )}\n\n {zoom.isZoomed && zoomResetLabel && (\n <ChartZoomResetButton label={zoomResetLabel} onReset={zoom.resetZoom} />\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:\n tooltipOpen && tooltipData && !zoom.isPanning && !zoom.isBrushing\n ? 1\n : 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={(formatTooltipDate ?? formatDate)(tooltipData.date)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n )\n}\n\nexport { LineChart }\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useAnimatedChartDomain, useChartZoom } from "../hooks/use-chart-zoom.js";
|
|
3
|
+
import { useReducedMotion } from "../hooks/use-reduced-motion.js";
|
|
4
|
+
import { cn } from "../utils/cn.js";
|
|
2
5
|
import { localeNumberFormat } from "../utils/format.js";
|
|
3
6
|
import { buildYAxisScale, formatYAxisTick } from "../utils/chart-scale.js";
|
|
4
|
-
import { buildTimeTicks } from "../utils/chart-time-ticks.js";
|
|
5
|
-
import { cn } from "../utils/cn.js";
|
|
6
|
-
import { useReducedMotion } from "../hooks/use-reduced-motion.js";
|
|
7
7
|
import { ChartTooltipCard, ChartZoomResetButton, clampTooltipPosition, useMeasuredSize } from "./chart.js";
|
|
8
|
-
import {
|
|
8
|
+
import { buildTimeTicks } from "../utils/chart-time-ticks.js";
|
|
9
9
|
import * as React$1 from "react";
|
|
10
10
|
import { useCallback, useMemo } from "react";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
package/dist/components/list.cjs
CHANGED
|
@@ -6,7 +6,7 @@ let react = require("react");
|
|
|
6
6
|
react = require_chunk.__toESM(react);
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
8
|
let _base_ui_react_use_render = require("@base-ui/react/use-render");
|
|
9
|
-
let
|
|
9
|
+
let cn = require("cn");
|
|
10
10
|
//#region src/components/list.tsx
|
|
11
11
|
const radiusRowClasses = {
|
|
12
12
|
none: "group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none",
|
|
@@ -27,7 +27,7 @@ function List({ className, variant = "soft", color = "neutral", radius = "lg", g
|
|
|
27
27
|
"data-variant": variant,
|
|
28
28
|
"data-look": color === "accent" ? `${variant}-accent` : variant,
|
|
29
29
|
"data-list-radius": radius,
|
|
30
|
-
className: (0,
|
|
30
|
+
className: (0, cn.cn)("group/list m-0 list-none p-0", "data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]", "data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]", "data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]", className),
|
|
31
31
|
style: {
|
|
32
32
|
"--list-gap-steps": gap,
|
|
33
33
|
...variant === "solid" && {
|
|
@@ -53,7 +53,7 @@ function ListItem({ className, children, recent, selected, skeleton, render, ...
|
|
|
53
53
|
props: {
|
|
54
54
|
"data-slot": "list-item-row",
|
|
55
55
|
"aria-hidden": skeleton ? true : void 0,
|
|
56
|
-
className: (0,
|
|
56
|
+
className: (0, cn.cn)("flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline", hoistLeading ? "min-w-0 flex-1 pr-4" : "w-full rounded-[inherit] px-4", !hoistLeading && !skeleton && selected && "bg-spark-a3", interactive && [
|
|
57
57
|
"group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none",
|
|
58
58
|
"disabled:pointer-events-none disabled:opacity-50",
|
|
59
59
|
hoistLeading ? "focus-visible:outline-hidden" : ["hover:bg-spark-a3 focus-visible:bg-spark-a3", "focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden"]
|
|
@@ -63,7 +63,7 @@ function ListItem({ className, children, recent, selected, skeleton, render, ...
|
|
|
63
63
|
});
|
|
64
64
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
65
65
|
"data-slot": "list-item",
|
|
66
|
-
className: (0,
|
|
66
|
+
className: (0, cn.cn)("relative has-focus-visible:z-10", hoistLeading && [
|
|
67
67
|
"flex min-w-0 items-center gap-4 pl-4",
|
|
68
68
|
!skeleton && selected && "bg-spark-a3",
|
|
69
69
|
interactive && [
|
|
@@ -95,7 +95,7 @@ function ListItemSkeleton() {
|
|
|
95
95
|
function ListItemLeading({ className, interactive: _interactive, ...props }) {
|
|
96
96
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
97
97
|
"data-slot": "list-item-leading",
|
|
98
|
-
className: (0,
|
|
98
|
+
className: (0, cn.cn)("flex shrink-0 items-center", className),
|
|
99
99
|
...props
|
|
100
100
|
});
|
|
101
101
|
}
|
|
@@ -105,7 +105,7 @@ function ListItemContent({ className, align = "start", ...props }) {
|
|
|
105
105
|
value: columnCtx,
|
|
106
106
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
107
107
|
"data-slot": "list-item-content",
|
|
108
|
-
className: (0,
|
|
108
|
+
className: (0, cn.cn)("flex min-w-0 flex-1 flex-col gap-1", align === "end" ? "items-end text-right" : "items-start text-left", className),
|
|
109
109
|
...props
|
|
110
110
|
})
|
|
111
111
|
});
|
|
@@ -113,7 +113,7 @@ function ListItemContent({ className, align = "start", ...props }) {
|
|
|
113
113
|
function ListItemPreline({ className, ...props }) {
|
|
114
114
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
115
115
|
"data-slot": "list-item-preline",
|
|
116
|
-
className: (0,
|
|
116
|
+
className: (0, cn.cn)("block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))", className),
|
|
117
117
|
...props
|
|
118
118
|
});
|
|
119
119
|
}
|
|
@@ -123,7 +123,7 @@ function ListItemTitle({ className, children, ...props }) {
|
|
|
123
123
|
const showDot = recent && !isEnd;
|
|
124
124
|
const title = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
125
125
|
"data-slot": "list-item-title",
|
|
126
|
-
className: (0,
|
|
126
|
+
className: (0, cn.cn)(showDot ? "min-w-0 truncate" : "block w-full truncate", "text-base text-(--list-item-fg,var(--neutral-12))", recent ? "font-bold" : "font-medium", className),
|
|
127
127
|
...props,
|
|
128
128
|
children
|
|
129
129
|
});
|
|
@@ -140,14 +140,14 @@ function ListItemTitle({ className, children, ...props }) {
|
|
|
140
140
|
function ListItemSubline({ className, ...props }) {
|
|
141
141
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
142
142
|
"data-slot": "list-item-subline",
|
|
143
|
-
className: (0,
|
|
143
|
+
className: (0, cn.cn)("block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))", className),
|
|
144
144
|
...props
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
function ListItemTrailing({ className, ...props }) {
|
|
148
148
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
149
149
|
"data-slot": "list-item-trailing",
|
|
150
|
-
className: (0,
|
|
150
|
+
className: (0, cn.cn)("flex shrink-0 items-center text-gray-11", "transition-colors duration-150 motion-reduce:transition-none", "group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11", className),
|
|
151
151
|
...props
|
|
152
152
|
});
|
|
153
153
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.cjs","names":["React","Skeleton"],"sources":["../../src/components/list.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render'\nimport * as React from 'react'\nimport type { ContainerRadius } from '../lib/container-chrome'\nimport type { SparkCardColor, SparkCardVariant } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Skeleton } from './skeleton'\n\ntype ListVariant = SparkCardVariant\ntype ListRadius = ContainerRadius\n\n// Rounds only the first/last row of a run, regardless of variant — harmless on ghost\n// (nothing painted to clip). Keyed on `data-list-radius` alone: unlike fill (below),\n// rounding doesn't depend on variant/color, so no compound group-data selector is needed.\n// Named `list-radius`, not `radius`: the global Roundness knob (Theme) also writes a\n// bare `data-radius` via an unscoped `[data-radius=...]` selector in spark-default.css —\n// `none`/`full` are spelled the same in both scales, so a plain `data-radius` here would\n// collide and let List silently override the app-wide knob for its whole subtree.\nconst radiusRowClasses: Record<ListRadius, string> = {\n none: 'group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none',\n sm: 'group-data-[list-radius=sm]/list:first:rounded-t-sm group-data-[list-radius=sm]/list:last:rounded-b-sm',\n md: 'group-data-[list-radius=md]/list:first:rounded-t-md group-data-[list-radius=md]/list:last:rounded-b-md',\n lg: 'group-data-[list-radius=lg]/list:first:rounded-t-lg group-data-[list-radius=lg]/list:last:rounded-b-lg',\n xl: 'group-data-[list-radius=xl]/list:first:rounded-t-xl group-data-[list-radius=xl]/list:last:rounded-b-xl',\n '2xl':\n 'group-data-[list-radius=2xl]/list:first:rounded-t-2xl group-data-[list-radius=2xl]/list:last:rounded-b-2xl',\n '3xl':\n 'group-data-[list-radius=3xl]/list:first:rounded-t-3xl group-data-[list-radius=3xl]/list:last:rounded-b-3xl',\n full: 'group-data-[list-radius=full]/list:first:rounded-t-full group-data-[list-radius=full]/list:last:rounded-b-full',\n}\n\n// ListItem publishes `recent` so ListItemTitle can render the inline recency dot; the\n// column context lets the title show it only on the primary (start) column, never the\n// value column of a two-column row.\nconst ListItemContext = React.createContext<{ recent: boolean }>({\n recent: false,\n})\nconst ListItemColumnContext = React.createContext<{ isEnd: boolean }>({\n isEnd: false,\n})\n\ninterface ListProps extends React.ComponentProps<'ul'> {\n /** A prop, not the ambient `data-style` knob — that would cascade into row content. */\n variant?: ListVariant\n /** Chrome palette. `neutral` (default) tracks the app neutral; `accent` tints with the brand accent. */\n color?: SparkCardColor\n /** Corner radius, as a named step. Unset → `lg`, today's fixed behaviour. */\n radius?: ListRadius\n /** How many `--spark-layout-gap` units apart each row sits (`soft`/`solid`/`ghost` only). */\n gap?: number\n}\n\nfunction List({\n className,\n variant = 'soft',\n color = 'neutral',\n radius = 'lg',\n gap = 0.5,\n style,\n ...props\n}: ListProps) {\n // Fill/border-colour needs BOTH axes at once (e.g. \"soft, tinted with accent\"), which a\n // single group-data selector can't express and chaining two on the same named group\n // can't either (that generates a descendant match between two *different* ancestors,\n // not one ancestor bearing both attributes). Resolving the pair to one value up front\n // sidesteps it entirely — see the `data-look` selectors on ListItem below.\n const look = color === 'accent' ? `${variant}-accent` : variant\n return (\n <ul\n // role=list keeps list semantics even under list-style:none (Safari drops them).\n role=\"list\"\n data-slot=\"list\"\n // group/list + data-variant drive every item's per-variant chrome (below).\n data-variant={variant}\n data-look={look}\n data-list-radius={radius}\n className={cn(\n 'group/list m-0 list-none p-0',\n // soft/solid = separate rows → stack with a small gap (the gap IS the divider).\n // ghost shares that same gap for spacing but no fill/border (see ListItem's\n // chrome block, which has no ghost selector). surface/outline glue rows into one\n // panel with no gap at all.\n 'data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n className,\n )}\n style={\n {\n '--list-gap-steps': gap,\n // solid's fill inverts far enough that the row's own ink must invert with it\n // (unlike soft/surface/outline's translucent tints, where the default ink\n // stays legible regardless of colour) — matches Card's own solid recipe,\n // where fg and fg-muted both collapse to the one contrast-guaranteed step.\n ...(variant === 'solid' && {\n '--list-item-fg':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n '--list-item-fg-muted':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ninterface ListItemProps extends React.ComponentProps<'li'> {\n /** The \"recently changed\" accent dot. */\n recent?: boolean\n /** Persistent accent-tint (e.g. the active document). */\n selected?: boolean\n /** Loading placeholder (Figma `skeleton=true`). */\n skeleton?: boolean\n /**\n * Base UI polymorphism for the inner row. Pass `<a href/>` or `<button/>` to make\n * the row interactive (accent-on-hover/focus, cursor, focus ring). Omit → static row.\n */\n render?: useRender.RenderProp\n}\n\nfunction ListItem({\n className,\n children,\n recent,\n selected,\n skeleton,\n render,\n ...props\n}: ListItemProps) {\n // Skeleton rows are decorative placeholders — never render as a real <a>/<button>. An\n // aria-hidden anchor would still be focusable (WCAG 4.1.2), so skeleton forces a plain,\n // non-interactive <div> regardless of `render`.\n const interactive = !skeleton && render != null\n\n const recentCtx = React.useMemo(() => ({ recent: recent ?? false }), [recent])\n\n // Only a ListItemLeading actually marked `interactive` (a Checkbox/Radio/Button) needs\n // to move outside `render`'s element, and only when `render` is literally an <a>/\n // <button> — nesting a focusable control inside another interactive element is invalid\n // HTML and double-fires clicks. A `render` of anything else (a `<Card/>`, say) has no\n // such conflict: it isn't natively interactive itself, so a control inside it is valid,\n // unduplicated HTML, and hoisting would only pull the control outside whatever that\n // element paints (its fill, border, rounded corner) for no reason. A plain icon\n // (interactive unset) is the same story from the other side — either way, Leading stays\n // inside the row by default, so the whole visual row remains one click/tap target.\n const rendersAnchorOrButton =\n React.isValidElement(render) &&\n (render.type === 'a' || render.type === 'button')\n const childArray = skeleton ? undefined : React.Children.toArray(children)\n const leadingChild = childArray?.find(\n (child): child is React.ReactElement<ListItemLeadingProps> =>\n React.isValidElement(child) && child.type === ListItemLeading,\n )\n const hoistLeading =\n rendersAnchorOrButton && Boolean(leadingChild?.props.interactive)\n const leading = hoistLeading ? leadingChild : undefined\n\n const row = useRender({\n defaultTagName: 'div',\n render: skeleton ? undefined : render,\n props: {\n 'data-slot': 'list-item-row',\n // skeleton is a decorative placeholder → keep it out of the a11y tree.\n 'aria-hidden': skeleton ? true : undefined,\n className: cn(\n // row layout from Figma 2217-1667; min-h is a HARD 44px floor (WCAG 2.5.5),\n // py/gap/text still scale past it via the spacing/scaling knobs. no-underline\n // resets the default <a> decoration when the row renders as a link.\n 'flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline',\n hoistLeading ? 'min-w-0 flex-1 pr-4' : 'w-full rounded-[inherit] px-4',\n // not hoisted → this row IS the whole visual item (no outer <li> chrome), so\n // selection tint lives here, same as a row with no leading slot at all.\n !hoistLeading && !skeleton && selected && 'bg-spark-a3',\n interactive && [\n 'group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n hoistLeading\n ? // the custom focus ring is drawn on the outer <li> (has-focus-visible,\n // below) so it wraps the leading control too — but the browser's own\n // default outline still targets whichever element actually has focus,\n // which is this one, so it has to be suppressed here specifically.\n 'focus-visible:outline-hidden'\n : [\n 'hover:bg-spark-a3 focus-visible:bg-spark-a3',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n ],\n ],\n ),\n children: skeleton ? (\n <ListItemSkeleton />\n ) : hoistLeading ? (\n childArray?.filter((child) => child !== leadingChild)\n ) : (\n children\n ),\n },\n })\n\n return (\n <li\n data-slot=\"list-item\"\n className={cn(\n 'relative has-focus-visible:z-10',\n hoistLeading && [\n 'flex min-w-0 items-center gap-4 pl-4',\n !skeleton && selected && 'bg-spark-a3',\n interactive && [\n // neutral at rest → subtle accent tint on hover/focus (the affordance goes\n // accent too, in ListItemTrailing; title + leading stay neutral). The click\n // target itself is `row`, so focus-visible (unlike hover) has to be read off\n // a descendant via has() — it never lands on this <li> directly.\n 'cursor-pointer',\n 'hover:bg-spark-a3 has-focus-visible:bg-spark-a3',\n 'has-focus-visible:ring-2 has-focus-visible:ring-offset-2',\n ],\n ],\n // soft/solid rows sit in a gapped stack, not a glued panel, but still read as one\n // rounded group: only the first/last row round the outer corners. surface/\n // outline glue rows into one bordered panel instead — border SIDES are variant-\n // only (no colour axis), so they stay on data-variant; fill and border COLOUR\n // depend on variant *and* colour together, so those live on data-look instead\n // (see the comment on `look` in List, above).\n 'group-data-[variant=surface]/list:border-x group-data-[variant=surface]/list:border-t group-data-[variant=surface]/list:last:border-b',\n 'group-data-[variant=outline]/list:border-x group-data-[variant=outline]/list:border-t group-data-[variant=outline]/list:last:border-b',\n 'group-data-[look=soft]/list:bg-gray-a2 dark:group-data-[look=soft]/list:bg-gray-a3',\n // arbitrary-var form, not the bg-spark-a3 utility: identical colour, but avoids a\n // literal substring collision with the unconditional selection/hover tint below,\n // which a couple of tests distinguish with a plain className.includes check.\n 'group-data-[look=soft-accent]/list:bg-(--spark-a3)',\n 'group-data-[look=surface]/list:border-gray-a6 group-data-[look=surface]/list:bg-gray-2',\n 'group-data-[look=surface-accent]/list:border-spark-a6 group-data-[look=surface-accent]/list:bg-(--spark-surface)',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n 'group-data-[look=solid]/list:bg-(--neutral-1)',\n 'group-data-[look=solid-accent]/list:bg-spark-9',\n Object.values(radiusRowClasses),\n className,\n )}\n {...props}\n >\n <ListItemContext.Provider value={recentCtx}>\n {leading}\n {row}\n </ListItemContext.Provider>\n </li>\n )\n}\n\nfunction ListItemSkeleton() {\n // Compose the shared Skeleton primitive so the placeholder matches every other skeleton\n // (token + radius + animation + motion-reduce).\n return (\n <>\n <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Skeleton className=\"h-3.5 w-1/3\" />\n <Skeleton className=\"h-3.5 w-2/3\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col items-end gap-2\">\n <Skeleton className=\"h-3.5 w-1/4\" />\n <Skeleton className=\"h-3.5 w-1/2\" />\n </div>\n </>\n )\n}\n\ninterface ListItemLeadingProps extends React.ComponentProps<'div'> {\n /**\n * Set when this slot holds a focusable control (Checkbox, Radio, Button). ListItem\n * then hoists it outside `render`'s element — nesting a real control inside the row's\n * own <a>/<button> is invalid HTML and double-fires clicks. Leave unset for decorative\n * content (an icon): it stays inside the row, so the whole visual row is one\n * click/tap target, same as a row with no leading slot at all.\n */\n interactive?: boolean\n}\n\nfunction ListItemLeading({\n className,\n interactive: _interactive,\n ...props\n}: ListItemLeadingProps) {\n return (\n <div\n data-slot=\"list-item-leading\"\n className={cn('flex shrink-0 items-center', className)}\n {...props}\n />\n )\n}\n\ninterface ListItemContentProps extends React.ComponentProps<'div'> {\n /** `end` right-aligns the column (the value column). */\n align?: 'start' | 'end'\n}\n\nfunction ListItemContent({\n className,\n align = 'start',\n ...props\n}: ListItemContentProps) {\n const columnCtx = React.useMemo(() => ({ isEnd: align === 'end' }), [align])\n return (\n <ListItemColumnContext.Provider value={columnCtx}>\n <div\n data-slot=\"list-item-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-1',\n align === 'end' ? 'items-end text-right' : 'items-start text-left',\n className,\n )}\n {...props}\n />\n </ListItemColumnContext.Provider>\n )\n}\n\nfunction ListItemPreline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-preline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTitle({\n className,\n children,\n ...props\n}: React.ComponentProps<'span'>) {\n const { recent } = React.useContext(ListItemContext)\n const { isEnd } = React.useContext(ListItemColumnContext)\n // recency dot sits inline, immediately left of the title (Figma 1310-977) — only on the\n // primary (start) column, so a two-column value row's value never carries it.\n const showDot = recent && !isEnd\n\n // className/{...props} always land on THIS span (the one that truncates), so a caller's\n // override (e.g. `title`, `line-clamp-2 whitespace-normal`) behaves identically whether\n // or not the dot renders — only the wrapping structure differs below.\n const title = (\n <span\n data-slot=\"list-item-title\"\n className={cn(\n showDot ? 'min-w-0 truncate' : 'block w-full truncate',\n 'text-base font-bold text-(--list-item-fg,var(--neutral-12))',\n className,\n )}\n {...props}\n >\n {children}\n </span>\n )\n\n if (!showDot) {\n // No dot: stay a plain block so the column's text alignment (e.g. an end column's\n // text-right) governs the title directly.\n return title\n }\n\n return (\n <span className=\"flex w-full items-center gap-2\">\n <span\n aria-hidden=\"true\"\n data-slot=\"list-item-indicator\"\n className=\"size-2 shrink-0 rounded-full bg-spark-9\"\n />\n {title}\n </span>\n )\n}\n\nfunction ListItemSubline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-subline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTrailing({\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"list-item-trailing\"\n className={cn(\n 'flex shrink-0 items-center text-gray-11',\n // interactive rows tint the affordance accent on hover/focus (group/row is set on\n // the interactive inner row; absent on static rows, so this simply never fires).\n // Relies on the child reading currentColor (Spark icons do) — the colour-\n // independent Icon component hardcodes its own ink and won't pick this up; use\n // a Spark icon here, not Icon.\n 'transition-colors duration-150 motion-reduce:transition-none',\n 'group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n List,\n ListItem,\n ListItemContent,\n ListItemLeading,\n ListItemPreline,\n ListItemSubline,\n ListItemTitle,\n ListItemTrailing,\n}\nexport type {\n ListProps,\n ListItemProps,\n ListItemContentProps,\n ListItemLeadingProps,\n ListVariant,\n ListRadius,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"list.cjs","names":["React","Skeleton"],"sources":["../../src/components/list.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render'\nimport * as React from 'react'\nimport type { ContainerRadius } from '../lib/container-chrome'\nimport type { SparkCardColor, SparkCardVariant } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Skeleton } from './skeleton'\n\ntype ListVariant = SparkCardVariant\ntype ListRadius = ContainerRadius\n\n// Rounds only the first/last row of a run, regardless of variant — harmless on ghost\n// (nothing painted to clip). Keyed on `data-list-radius` alone: unlike fill (below),\n// rounding doesn't depend on variant/color, so no compound group-data selector is needed.\n// Named `list-radius`, not `radius`: the global Roundness knob (Theme) also writes a\n// bare `data-radius` via an unscoped `[data-radius=...]` selector in spark-default.css —\n// `none`/`full` are spelled the same in both scales, so a plain `data-radius` here would\n// collide and let List silently override the app-wide knob for its whole subtree.\nconst radiusRowClasses: Record<ListRadius, string> = {\n none: 'group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none',\n sm: 'group-data-[list-radius=sm]/list:first:rounded-t-sm group-data-[list-radius=sm]/list:last:rounded-b-sm',\n md: 'group-data-[list-radius=md]/list:first:rounded-t-md group-data-[list-radius=md]/list:last:rounded-b-md',\n lg: 'group-data-[list-radius=lg]/list:first:rounded-t-lg group-data-[list-radius=lg]/list:last:rounded-b-lg',\n xl: 'group-data-[list-radius=xl]/list:first:rounded-t-xl group-data-[list-radius=xl]/list:last:rounded-b-xl',\n '2xl':\n 'group-data-[list-radius=2xl]/list:first:rounded-t-2xl group-data-[list-radius=2xl]/list:last:rounded-b-2xl',\n '3xl':\n 'group-data-[list-radius=3xl]/list:first:rounded-t-3xl group-data-[list-radius=3xl]/list:last:rounded-b-3xl',\n full: 'group-data-[list-radius=full]/list:first:rounded-t-full group-data-[list-radius=full]/list:last:rounded-b-full',\n}\n\n// ListItem publishes `recent` so ListItemTitle can render the inline recency dot; the\n// column context lets the title show it only on the primary (start) column, never the\n// value column of a two-column row.\nconst ListItemContext = React.createContext<{ recent: boolean }>({\n recent: false,\n})\nconst ListItemColumnContext = React.createContext<{ isEnd: boolean }>({\n isEnd: false,\n})\n\ninterface ListProps extends React.ComponentProps<'ul'> {\n /** A prop, not the ambient `data-style` knob — that would cascade into row content. */\n variant?: ListVariant\n /** Chrome palette. `neutral` (default) tracks the app neutral; `accent` tints with the brand accent. */\n color?: SparkCardColor\n /** Corner radius, as a named step. Unset → `lg`, today's fixed behaviour. */\n radius?: ListRadius\n /** How many `--spark-layout-gap` units apart each row sits (`soft`/`solid`/`ghost` only). */\n gap?: number\n}\n\nfunction List({\n className,\n variant = 'soft',\n color = 'neutral',\n radius = 'lg',\n gap = 0.5,\n style,\n ...props\n}: ListProps) {\n // Fill/border-colour needs BOTH axes at once (e.g. \"soft, tinted with accent\"), which a\n // single group-data selector can't express and chaining two on the same named group\n // can't either (that generates a descendant match between two *different* ancestors,\n // not one ancestor bearing both attributes). Resolving the pair to one value up front\n // sidesteps it entirely — see the `data-look` selectors on ListItem below.\n const look = color === 'accent' ? `${variant}-accent` : variant\n return (\n <ul\n // role=list keeps list semantics even under list-style:none (Safari drops them).\n role=\"list\"\n data-slot=\"list\"\n // group/list + data-variant drive every item's per-variant chrome (below).\n data-variant={variant}\n data-look={look}\n data-list-radius={radius}\n className={cn(\n 'group/list m-0 list-none p-0',\n // soft/solid = separate rows → stack with a small gap (the gap IS the divider).\n // ghost shares that same gap for spacing but no fill/border (see ListItem's\n // chrome block, which has no ghost selector). surface/outline glue rows into one\n // panel with no gap at all.\n 'data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n className,\n )}\n style={\n {\n '--list-gap-steps': gap,\n // solid's fill inverts far enough that the row's own ink must invert with it\n // (unlike soft/surface/outline's translucent tints, where the default ink\n // stays legible regardless of colour) — matches Card's own solid recipe,\n // where fg and fg-muted both collapse to the one contrast-guaranteed step.\n ...(variant === 'solid' && {\n '--list-item-fg':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n '--list-item-fg-muted':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ninterface ListItemProps extends React.ComponentProps<'li'> {\n /** The \"recently changed\" accent dot. */\n recent?: boolean\n /** Persistent accent-tint (e.g. the active document). */\n selected?: boolean\n /** Loading placeholder (Figma `skeleton=true`). */\n skeleton?: boolean\n /**\n * Base UI polymorphism for the inner row. Pass `<a href/>` or `<button/>` to make\n * the row interactive (accent-on-hover/focus, cursor, focus ring). Omit → static row.\n */\n render?: useRender.RenderProp\n}\n\nfunction ListItem({\n className,\n children,\n recent,\n selected,\n skeleton,\n render,\n ...props\n}: ListItemProps) {\n // Skeleton rows are decorative placeholders — never render as a real <a>/<button>. An\n // aria-hidden anchor would still be focusable (WCAG 4.1.2), so skeleton forces a plain,\n // non-interactive <div> regardless of `render`.\n const interactive = !skeleton && render != null\n\n const recentCtx = React.useMemo(() => ({ recent: recent ?? false }), [recent])\n\n // Only a ListItemLeading actually marked `interactive` (a Checkbox/Radio/Button) needs\n // to move outside `render`'s element, and only when `render` is literally an <a>/\n // <button> — nesting a focusable control inside another interactive element is invalid\n // HTML and double-fires clicks. A `render` of anything else (a `<Card/>`, say) has no\n // such conflict: it isn't natively interactive itself, so a control inside it is valid,\n // unduplicated HTML, and hoisting would only pull the control outside whatever that\n // element paints (its fill, border, rounded corner) for no reason. A plain icon\n // (interactive unset) is the same story from the other side — either way, Leading stays\n // inside the row by default, so the whole visual row remains one click/tap target.\n const rendersAnchorOrButton =\n React.isValidElement(render) &&\n (render.type === 'a' || render.type === 'button')\n const childArray = skeleton ? undefined : React.Children.toArray(children)\n const leadingChild = childArray?.find(\n (child): child is React.ReactElement<ListItemLeadingProps> =>\n React.isValidElement(child) && child.type === ListItemLeading,\n )\n const hoistLeading =\n rendersAnchorOrButton && Boolean(leadingChild?.props.interactive)\n const leading = hoistLeading ? leadingChild : undefined\n\n const row = useRender({\n defaultTagName: 'div',\n render: skeleton ? undefined : render,\n props: {\n 'data-slot': 'list-item-row',\n // skeleton is a decorative placeholder → keep it out of the a11y tree.\n 'aria-hidden': skeleton ? true : undefined,\n className: cn(\n // row layout from Figma 2217-1667; min-h is a HARD 44px floor (WCAG 2.5.5),\n // py/gap/text still scale past it via the spacing/scaling knobs. no-underline\n // resets the default <a> decoration when the row renders as a link.\n 'flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline',\n hoistLeading ? 'min-w-0 flex-1 pr-4' : 'w-full rounded-[inherit] px-4',\n // not hoisted → this row IS the whole visual item (no outer <li> chrome), so\n // selection tint lives here, same as a row with no leading slot at all.\n !hoistLeading && !skeleton && selected && 'bg-spark-a3',\n interactive && [\n 'group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n hoistLeading\n ? // the custom focus ring is drawn on the outer <li> (has-focus-visible,\n // below) so it wraps the leading control too — but the browser's own\n // default outline still targets whichever element actually has focus,\n // which is this one, so it has to be suppressed here specifically.\n 'focus-visible:outline-hidden'\n : [\n 'hover:bg-spark-a3 focus-visible:bg-spark-a3',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n ],\n ],\n ),\n children: skeleton ? (\n <ListItemSkeleton />\n ) : hoistLeading ? (\n childArray?.filter((child) => child !== leadingChild)\n ) : (\n children\n ),\n },\n })\n\n return (\n <li\n data-slot=\"list-item\"\n className={cn(\n 'relative has-focus-visible:z-10',\n hoistLeading && [\n 'flex min-w-0 items-center gap-4 pl-4',\n !skeleton && selected && 'bg-spark-a3',\n interactive && [\n // neutral at rest → subtle accent tint on hover/focus (the affordance goes\n // accent too, in ListItemTrailing; title + leading stay neutral). The click\n // target itself is `row`, so focus-visible (unlike hover) has to be read off\n // a descendant via has() — it never lands on this <li> directly.\n 'cursor-pointer',\n 'hover:bg-spark-a3 has-focus-visible:bg-spark-a3',\n 'has-focus-visible:ring-2 has-focus-visible:ring-offset-2',\n ],\n ],\n // soft/solid rows sit in a gapped stack, not a glued panel, but still read as one\n // rounded group: only the first/last row round the outer corners. surface/\n // outline glue rows into one bordered panel instead — border SIDES are variant-\n // only (no colour axis), so they stay on data-variant; fill and border COLOUR\n // depend on variant *and* colour together, so those live on data-look instead\n // (see the comment on `look` in List, above).\n 'group-data-[variant=surface]/list:border-x group-data-[variant=surface]/list:border-t group-data-[variant=surface]/list:last:border-b',\n 'group-data-[variant=outline]/list:border-x group-data-[variant=outline]/list:border-t group-data-[variant=outline]/list:last:border-b',\n 'group-data-[look=soft]/list:bg-gray-a2 dark:group-data-[look=soft]/list:bg-gray-a3',\n // arbitrary-var form, not the bg-spark-a3 utility: identical colour, but avoids a\n // literal substring collision with the unconditional selection/hover tint below,\n // which a couple of tests distinguish with a plain className.includes check.\n 'group-data-[look=soft-accent]/list:bg-(--spark-a3)',\n 'group-data-[look=surface]/list:border-gray-a6 group-data-[look=surface]/list:bg-gray-2',\n 'group-data-[look=surface-accent]/list:border-spark-a6 group-data-[look=surface-accent]/list:bg-(--spark-surface)',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n 'group-data-[look=solid]/list:bg-(--neutral-1)',\n 'group-data-[look=solid-accent]/list:bg-spark-9',\n Object.values(radiusRowClasses),\n className,\n )}\n {...props}\n >\n <ListItemContext.Provider value={recentCtx}>\n {leading}\n {row}\n </ListItemContext.Provider>\n </li>\n )\n}\n\nfunction ListItemSkeleton() {\n // Compose the shared Skeleton primitive so the placeholder matches every other skeleton\n // (token + radius + animation + motion-reduce).\n return (\n <>\n <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Skeleton className=\"h-3.5 w-1/3\" />\n <Skeleton className=\"h-3.5 w-2/3\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col items-end gap-2\">\n <Skeleton className=\"h-3.5 w-1/4\" />\n <Skeleton className=\"h-3.5 w-1/2\" />\n </div>\n </>\n )\n}\n\ninterface ListItemLeadingProps extends React.ComponentProps<'div'> {\n /**\n * Set when this slot holds a focusable control (Checkbox, Radio, Button). ListItem\n * then hoists it outside `render`'s element — nesting a real control inside the row's\n * own <a>/<button> is invalid HTML and double-fires clicks. Leave unset for decorative\n * content (an icon): it stays inside the row, so the whole visual row is one\n * click/tap target, same as a row with no leading slot at all.\n */\n interactive?: boolean\n}\n\nfunction ListItemLeading({\n className,\n interactive: _interactive,\n ...props\n}: ListItemLeadingProps) {\n return (\n <div\n data-slot=\"list-item-leading\"\n className={cn('flex shrink-0 items-center', className)}\n {...props}\n />\n )\n}\n\ninterface ListItemContentProps extends React.ComponentProps<'div'> {\n /** `end` right-aligns the column (the value column). */\n align?: 'start' | 'end'\n}\n\nfunction ListItemContent({\n className,\n align = 'start',\n ...props\n}: ListItemContentProps) {\n const columnCtx = React.useMemo(() => ({ isEnd: align === 'end' }), [align])\n return (\n <ListItemColumnContext.Provider value={columnCtx}>\n <div\n data-slot=\"list-item-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-1',\n align === 'end' ? 'items-end text-right' : 'items-start text-left',\n className,\n )}\n {...props}\n />\n </ListItemColumnContext.Provider>\n )\n}\n\nfunction ListItemPreline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-preline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTitle({\n className,\n children,\n ...props\n}: React.ComponentProps<'span'>) {\n const { recent } = React.useContext(ListItemContext)\n const { isEnd } = React.useContext(ListItemColumnContext)\n // recency dot sits inline, immediately left of the title (Figma 1310-977) — only on the\n // primary (start) column, so a two-column value row's value never carries it.\n const showDot = recent && !isEnd\n\n // className/{...props} always land on THIS span (the one that truncates), so a caller's\n // override (e.g. `title`, `line-clamp-2 whitespace-normal`) behaves identically whether\n // or not the dot renders — only the wrapping structure differs below.\n const title = (\n <span\n data-slot=\"list-item-title\"\n className={cn(\n showDot ? 'min-w-0 truncate' : 'block w-full truncate',\n 'text-base text-(--list-item-fg,var(--neutral-12))',\n recent ? 'font-bold' : 'font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </span>\n )\n\n if (!showDot) {\n // No dot: stay a plain block so the column's text alignment (e.g. an end column's\n // text-right) governs the title directly.\n return title\n }\n\n return (\n <span className=\"flex w-full items-center gap-2\">\n <span\n aria-hidden=\"true\"\n data-slot=\"list-item-indicator\"\n className=\"size-2 shrink-0 rounded-full bg-spark-9\"\n />\n {title}\n </span>\n )\n}\n\nfunction ListItemSubline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-subline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTrailing({\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"list-item-trailing\"\n className={cn(\n 'flex shrink-0 items-center text-gray-11',\n // interactive rows tint the affordance accent on hover/focus (group/row is set on\n // the interactive inner row; absent on static rows, so this simply never fires).\n // Relies on the child reading currentColor (Spark icons do) — the colour-\n // independent Icon component hardcodes its own ink and won't pick this up; use\n // a Spark icon here, not Icon.\n 'transition-colors duration-150 motion-reduce:transition-none',\n 'group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n List,\n ListItem,\n ListItemContent,\n ListItemLeading,\n ListItemPreline,\n ListItemSubline,\n ListItemTitle,\n ListItemTrailing,\n}\nexport type {\n ListProps,\n ListItemProps,\n ListItemContentProps,\n ListItemLeadingProps,\n ListVariant,\n ListRadius,\n}\n"],"mappings":"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"}
|
package/dist/components/list.js
CHANGED
|
@@ -120,7 +120,7 @@ function ListItemTitle({ className, children, ...props }) {
|
|
|
120
120
|
const showDot = recent && !isEnd;
|
|
121
121
|
const title = /* @__PURE__ */ jsx("span", {
|
|
122
122
|
"data-slot": "list-item-title",
|
|
123
|
-
className: cn(showDot ? "min-w-0 truncate" : "block w-full truncate", "text-base
|
|
123
|
+
className: cn(showDot ? "min-w-0 truncate" : "block w-full truncate", "text-base text-(--list-item-fg,var(--neutral-12))", recent ? "font-bold" : "font-medium", className),
|
|
124
124
|
...props,
|
|
125
125
|
children
|
|
126
126
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","names":["React"],"sources":["../../src/components/list.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render'\nimport * as React from 'react'\nimport type { ContainerRadius } from '../lib/container-chrome'\nimport type { SparkCardColor, SparkCardVariant } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Skeleton } from './skeleton'\n\ntype ListVariant = SparkCardVariant\ntype ListRadius = ContainerRadius\n\n// Rounds only the first/last row of a run, regardless of variant — harmless on ghost\n// (nothing painted to clip). Keyed on `data-list-radius` alone: unlike fill (below),\n// rounding doesn't depend on variant/color, so no compound group-data selector is needed.\n// Named `list-radius`, not `radius`: the global Roundness knob (Theme) also writes a\n// bare `data-radius` via an unscoped `[data-radius=...]` selector in spark-default.css —\n// `none`/`full` are spelled the same in both scales, so a plain `data-radius` here would\n// collide and let List silently override the app-wide knob for its whole subtree.\nconst radiusRowClasses: Record<ListRadius, string> = {\n none: 'group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none',\n sm: 'group-data-[list-radius=sm]/list:first:rounded-t-sm group-data-[list-radius=sm]/list:last:rounded-b-sm',\n md: 'group-data-[list-radius=md]/list:first:rounded-t-md group-data-[list-radius=md]/list:last:rounded-b-md',\n lg: 'group-data-[list-radius=lg]/list:first:rounded-t-lg group-data-[list-radius=lg]/list:last:rounded-b-lg',\n xl: 'group-data-[list-radius=xl]/list:first:rounded-t-xl group-data-[list-radius=xl]/list:last:rounded-b-xl',\n '2xl':\n 'group-data-[list-radius=2xl]/list:first:rounded-t-2xl group-data-[list-radius=2xl]/list:last:rounded-b-2xl',\n '3xl':\n 'group-data-[list-radius=3xl]/list:first:rounded-t-3xl group-data-[list-radius=3xl]/list:last:rounded-b-3xl',\n full: 'group-data-[list-radius=full]/list:first:rounded-t-full group-data-[list-radius=full]/list:last:rounded-b-full',\n}\n\n// ListItem publishes `recent` so ListItemTitle can render the inline recency dot; the\n// column context lets the title show it only on the primary (start) column, never the\n// value column of a two-column row.\nconst ListItemContext = React.createContext<{ recent: boolean }>({\n recent: false,\n})\nconst ListItemColumnContext = React.createContext<{ isEnd: boolean }>({\n isEnd: false,\n})\n\ninterface ListProps extends React.ComponentProps<'ul'> {\n /** A prop, not the ambient `data-style` knob — that would cascade into row content. */\n variant?: ListVariant\n /** Chrome palette. `neutral` (default) tracks the app neutral; `accent` tints with the brand accent. */\n color?: SparkCardColor\n /** Corner radius, as a named step. Unset → `lg`, today's fixed behaviour. */\n radius?: ListRadius\n /** How many `--spark-layout-gap` units apart each row sits (`soft`/`solid`/`ghost` only). */\n gap?: number\n}\n\nfunction List({\n className,\n variant = 'soft',\n color = 'neutral',\n radius = 'lg',\n gap = 0.5,\n style,\n ...props\n}: ListProps) {\n // Fill/border-colour needs BOTH axes at once (e.g. \"soft, tinted with accent\"), which a\n // single group-data selector can't express and chaining two on the same named group\n // can't either (that generates a descendant match between two *different* ancestors,\n // not one ancestor bearing both attributes). Resolving the pair to one value up front\n // sidesteps it entirely — see the `data-look` selectors on ListItem below.\n const look = color === 'accent' ? `${variant}-accent` : variant\n return (\n <ul\n // role=list keeps list semantics even under list-style:none (Safari drops them).\n role=\"list\"\n data-slot=\"list\"\n // group/list + data-variant drive every item's per-variant chrome (below).\n data-variant={variant}\n data-look={look}\n data-list-radius={radius}\n className={cn(\n 'group/list m-0 list-none p-0',\n // soft/solid = separate rows → stack with a small gap (the gap IS the divider).\n // ghost shares that same gap for spacing but no fill/border (see ListItem's\n // chrome block, which has no ghost selector). surface/outline glue rows into one\n // panel with no gap at all.\n 'data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n className,\n )}\n style={\n {\n '--list-gap-steps': gap,\n // solid's fill inverts far enough that the row's own ink must invert with it\n // (unlike soft/surface/outline's translucent tints, where the default ink\n // stays legible regardless of colour) — matches Card's own solid recipe,\n // where fg and fg-muted both collapse to the one contrast-guaranteed step.\n ...(variant === 'solid' && {\n '--list-item-fg':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n '--list-item-fg-muted':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ninterface ListItemProps extends React.ComponentProps<'li'> {\n /** The \"recently changed\" accent dot. */\n recent?: boolean\n /** Persistent accent-tint (e.g. the active document). */\n selected?: boolean\n /** Loading placeholder (Figma `skeleton=true`). */\n skeleton?: boolean\n /**\n * Base UI polymorphism for the inner row. Pass `<a href/>` or `<button/>` to make\n * the row interactive (accent-on-hover/focus, cursor, focus ring). Omit → static row.\n */\n render?: useRender.RenderProp\n}\n\nfunction ListItem({\n className,\n children,\n recent,\n selected,\n skeleton,\n render,\n ...props\n}: ListItemProps) {\n // Skeleton rows are decorative placeholders — never render as a real <a>/<button>. An\n // aria-hidden anchor would still be focusable (WCAG 4.1.2), so skeleton forces a plain,\n // non-interactive <div> regardless of `render`.\n const interactive = !skeleton && render != null\n\n const recentCtx = React.useMemo(() => ({ recent: recent ?? false }), [recent])\n\n // Only a ListItemLeading actually marked `interactive` (a Checkbox/Radio/Button) needs\n // to move outside `render`'s element, and only when `render` is literally an <a>/\n // <button> — nesting a focusable control inside another interactive element is invalid\n // HTML and double-fires clicks. A `render` of anything else (a `<Card/>`, say) has no\n // such conflict: it isn't natively interactive itself, so a control inside it is valid,\n // unduplicated HTML, and hoisting would only pull the control outside whatever that\n // element paints (its fill, border, rounded corner) for no reason. A plain icon\n // (interactive unset) is the same story from the other side — either way, Leading stays\n // inside the row by default, so the whole visual row remains one click/tap target.\n const rendersAnchorOrButton =\n React.isValidElement(render) &&\n (render.type === 'a' || render.type === 'button')\n const childArray = skeleton ? undefined : React.Children.toArray(children)\n const leadingChild = childArray?.find(\n (child): child is React.ReactElement<ListItemLeadingProps> =>\n React.isValidElement(child) && child.type === ListItemLeading,\n )\n const hoistLeading =\n rendersAnchorOrButton && Boolean(leadingChild?.props.interactive)\n const leading = hoistLeading ? leadingChild : undefined\n\n const row = useRender({\n defaultTagName: 'div',\n render: skeleton ? undefined : render,\n props: {\n 'data-slot': 'list-item-row',\n // skeleton is a decorative placeholder → keep it out of the a11y tree.\n 'aria-hidden': skeleton ? true : undefined,\n className: cn(\n // row layout from Figma 2217-1667; min-h is a HARD 44px floor (WCAG 2.5.5),\n // py/gap/text still scale past it via the spacing/scaling knobs. no-underline\n // resets the default <a> decoration when the row renders as a link.\n 'flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline',\n hoistLeading ? 'min-w-0 flex-1 pr-4' : 'w-full rounded-[inherit] px-4',\n // not hoisted → this row IS the whole visual item (no outer <li> chrome), so\n // selection tint lives here, same as a row with no leading slot at all.\n !hoistLeading && !skeleton && selected && 'bg-spark-a3',\n interactive && [\n 'group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n hoistLeading\n ? // the custom focus ring is drawn on the outer <li> (has-focus-visible,\n // below) so it wraps the leading control too — but the browser's own\n // default outline still targets whichever element actually has focus,\n // which is this one, so it has to be suppressed here specifically.\n 'focus-visible:outline-hidden'\n : [\n 'hover:bg-spark-a3 focus-visible:bg-spark-a3',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n ],\n ],\n ),\n children: skeleton ? (\n <ListItemSkeleton />\n ) : hoistLeading ? (\n childArray?.filter((child) => child !== leadingChild)\n ) : (\n children\n ),\n },\n })\n\n return (\n <li\n data-slot=\"list-item\"\n className={cn(\n 'relative has-focus-visible:z-10',\n hoistLeading && [\n 'flex min-w-0 items-center gap-4 pl-4',\n !skeleton && selected && 'bg-spark-a3',\n interactive && [\n // neutral at rest → subtle accent tint on hover/focus (the affordance goes\n // accent too, in ListItemTrailing; title + leading stay neutral). The click\n // target itself is `row`, so focus-visible (unlike hover) has to be read off\n // a descendant via has() — it never lands on this <li> directly.\n 'cursor-pointer',\n 'hover:bg-spark-a3 has-focus-visible:bg-spark-a3',\n 'has-focus-visible:ring-2 has-focus-visible:ring-offset-2',\n ],\n ],\n // soft/solid rows sit in a gapped stack, not a glued panel, but still read as one\n // rounded group: only the first/last row round the outer corners. surface/\n // outline glue rows into one bordered panel instead — border SIDES are variant-\n // only (no colour axis), so they stay on data-variant; fill and border COLOUR\n // depend on variant *and* colour together, so those live on data-look instead\n // (see the comment on `look` in List, above).\n 'group-data-[variant=surface]/list:border-x group-data-[variant=surface]/list:border-t group-data-[variant=surface]/list:last:border-b',\n 'group-data-[variant=outline]/list:border-x group-data-[variant=outline]/list:border-t group-data-[variant=outline]/list:last:border-b',\n 'group-data-[look=soft]/list:bg-gray-a2 dark:group-data-[look=soft]/list:bg-gray-a3',\n // arbitrary-var form, not the bg-spark-a3 utility: identical colour, but avoids a\n // literal substring collision with the unconditional selection/hover tint below,\n // which a couple of tests distinguish with a plain className.includes check.\n 'group-data-[look=soft-accent]/list:bg-(--spark-a3)',\n 'group-data-[look=surface]/list:border-gray-a6 group-data-[look=surface]/list:bg-gray-2',\n 'group-data-[look=surface-accent]/list:border-spark-a6 group-data-[look=surface-accent]/list:bg-(--spark-surface)',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n 'group-data-[look=solid]/list:bg-(--neutral-1)',\n 'group-data-[look=solid-accent]/list:bg-spark-9',\n Object.values(radiusRowClasses),\n className,\n )}\n {...props}\n >\n <ListItemContext.Provider value={recentCtx}>\n {leading}\n {row}\n </ListItemContext.Provider>\n </li>\n )\n}\n\nfunction ListItemSkeleton() {\n // Compose the shared Skeleton primitive so the placeholder matches every other skeleton\n // (token + radius + animation + motion-reduce).\n return (\n <>\n <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Skeleton className=\"h-3.5 w-1/3\" />\n <Skeleton className=\"h-3.5 w-2/3\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col items-end gap-2\">\n <Skeleton className=\"h-3.5 w-1/4\" />\n <Skeleton className=\"h-3.5 w-1/2\" />\n </div>\n </>\n )\n}\n\ninterface ListItemLeadingProps extends React.ComponentProps<'div'> {\n /**\n * Set when this slot holds a focusable control (Checkbox, Radio, Button). ListItem\n * then hoists it outside `render`'s element — nesting a real control inside the row's\n * own <a>/<button> is invalid HTML and double-fires clicks. Leave unset for decorative\n * content (an icon): it stays inside the row, so the whole visual row is one\n * click/tap target, same as a row with no leading slot at all.\n */\n interactive?: boolean\n}\n\nfunction ListItemLeading({\n className,\n interactive: _interactive,\n ...props\n}: ListItemLeadingProps) {\n return (\n <div\n data-slot=\"list-item-leading\"\n className={cn('flex shrink-0 items-center', className)}\n {...props}\n />\n )\n}\n\ninterface ListItemContentProps extends React.ComponentProps<'div'> {\n /** `end` right-aligns the column (the value column). */\n align?: 'start' | 'end'\n}\n\nfunction ListItemContent({\n className,\n align = 'start',\n ...props\n}: ListItemContentProps) {\n const columnCtx = React.useMemo(() => ({ isEnd: align === 'end' }), [align])\n return (\n <ListItemColumnContext.Provider value={columnCtx}>\n <div\n data-slot=\"list-item-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-1',\n align === 'end' ? 'items-end text-right' : 'items-start text-left',\n className,\n )}\n {...props}\n />\n </ListItemColumnContext.Provider>\n )\n}\n\nfunction ListItemPreline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-preline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTitle({\n className,\n children,\n ...props\n}: React.ComponentProps<'span'>) {\n const { recent } = React.useContext(ListItemContext)\n const { isEnd } = React.useContext(ListItemColumnContext)\n // recency dot sits inline, immediately left of the title (Figma 1310-977) — only on the\n // primary (start) column, so a two-column value row's value never carries it.\n const showDot = recent && !isEnd\n\n // className/{...props} always land on THIS span (the one that truncates), so a caller's\n // override (e.g. `title`, `line-clamp-2 whitespace-normal`) behaves identically whether\n // or not the dot renders — only the wrapping structure differs below.\n const title = (\n <span\n data-slot=\"list-item-title\"\n className={cn(\n showDot ? 'min-w-0 truncate' : 'block w-full truncate',\n 'text-base font-bold text-(--list-item-fg,var(--neutral-12))',\n className,\n )}\n {...props}\n >\n {children}\n </span>\n )\n\n if (!showDot) {\n // No dot: stay a plain block so the column's text alignment (e.g. an end column's\n // text-right) governs the title directly.\n return title\n }\n\n return (\n <span className=\"flex w-full items-center gap-2\">\n <span\n aria-hidden=\"true\"\n data-slot=\"list-item-indicator\"\n className=\"size-2 shrink-0 rounded-full bg-spark-9\"\n />\n {title}\n </span>\n )\n}\n\nfunction ListItemSubline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-subline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTrailing({\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"list-item-trailing\"\n className={cn(\n 'flex shrink-0 items-center text-gray-11',\n // interactive rows tint the affordance accent on hover/focus (group/row is set on\n // the interactive inner row; absent on static rows, so this simply never fires).\n // Relies on the child reading currentColor (Spark icons do) — the colour-\n // independent Icon component hardcodes its own ink and won't pick this up; use\n // a Spark icon here, not Icon.\n 'transition-colors duration-150 motion-reduce:transition-none',\n 'group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n List,\n ListItem,\n ListItemContent,\n ListItemLeading,\n ListItemPreline,\n ListItemSubline,\n ListItemTitle,\n ListItemTrailing,\n}\nexport type {\n ListProps,\n ListItemProps,\n ListItemContentProps,\n ListItemLeadingProps,\n ListVariant,\n ListRadius,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"list.js","names":["React"],"sources":["../../src/components/list.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render'\nimport * as React from 'react'\nimport type { ContainerRadius } from '../lib/container-chrome'\nimport type { SparkCardColor, SparkCardVariant } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Skeleton } from './skeleton'\n\ntype ListVariant = SparkCardVariant\ntype ListRadius = ContainerRadius\n\n// Rounds only the first/last row of a run, regardless of variant — harmless on ghost\n// (nothing painted to clip). Keyed on `data-list-radius` alone: unlike fill (below),\n// rounding doesn't depend on variant/color, so no compound group-data selector is needed.\n// Named `list-radius`, not `radius`: the global Roundness knob (Theme) also writes a\n// bare `data-radius` via an unscoped `[data-radius=...]` selector in spark-default.css —\n// `none`/`full` are spelled the same in both scales, so a plain `data-radius` here would\n// collide and let List silently override the app-wide knob for its whole subtree.\nconst radiusRowClasses: Record<ListRadius, string> = {\n none: 'group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none',\n sm: 'group-data-[list-radius=sm]/list:first:rounded-t-sm group-data-[list-radius=sm]/list:last:rounded-b-sm',\n md: 'group-data-[list-radius=md]/list:first:rounded-t-md group-data-[list-radius=md]/list:last:rounded-b-md',\n lg: 'group-data-[list-radius=lg]/list:first:rounded-t-lg group-data-[list-radius=lg]/list:last:rounded-b-lg',\n xl: 'group-data-[list-radius=xl]/list:first:rounded-t-xl group-data-[list-radius=xl]/list:last:rounded-b-xl',\n '2xl':\n 'group-data-[list-radius=2xl]/list:first:rounded-t-2xl group-data-[list-radius=2xl]/list:last:rounded-b-2xl',\n '3xl':\n 'group-data-[list-radius=3xl]/list:first:rounded-t-3xl group-data-[list-radius=3xl]/list:last:rounded-b-3xl',\n full: 'group-data-[list-radius=full]/list:first:rounded-t-full group-data-[list-radius=full]/list:last:rounded-b-full',\n}\n\n// ListItem publishes `recent` so ListItemTitle can render the inline recency dot; the\n// column context lets the title show it only on the primary (start) column, never the\n// value column of a two-column row.\nconst ListItemContext = React.createContext<{ recent: boolean }>({\n recent: false,\n})\nconst ListItemColumnContext = React.createContext<{ isEnd: boolean }>({\n isEnd: false,\n})\n\ninterface ListProps extends React.ComponentProps<'ul'> {\n /** A prop, not the ambient `data-style` knob — that would cascade into row content. */\n variant?: ListVariant\n /** Chrome palette. `neutral` (default) tracks the app neutral; `accent` tints with the brand accent. */\n color?: SparkCardColor\n /** Corner radius, as a named step. Unset → `lg`, today's fixed behaviour. */\n radius?: ListRadius\n /** How many `--spark-layout-gap` units apart each row sits (`soft`/`solid`/`ghost` only). */\n gap?: number\n}\n\nfunction List({\n className,\n variant = 'soft',\n color = 'neutral',\n radius = 'lg',\n gap = 0.5,\n style,\n ...props\n}: ListProps) {\n // Fill/border-colour needs BOTH axes at once (e.g. \"soft, tinted with accent\"), which a\n // single group-data selector can't express and chaining two on the same named group\n // can't either (that generates a descendant match between two *different* ancestors,\n // not one ancestor bearing both attributes). Resolving the pair to one value up front\n // sidesteps it entirely — see the `data-look` selectors on ListItem below.\n const look = color === 'accent' ? `${variant}-accent` : variant\n return (\n <ul\n // role=list keeps list semantics even under list-style:none (Safari drops them).\n role=\"list\"\n data-slot=\"list\"\n // group/list + data-variant drive every item's per-variant chrome (below).\n data-variant={variant}\n data-look={look}\n data-list-radius={radius}\n className={cn(\n 'group/list m-0 list-none p-0',\n // soft/solid = separate rows → stack with a small gap (the gap IS the divider).\n // ghost shares that same gap for spacing but no fill/border (see ListItem's\n // chrome block, which has no ghost selector). surface/outline glue rows into one\n // panel with no gap at all.\n 'data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n className,\n )}\n style={\n {\n '--list-gap-steps': gap,\n // solid's fill inverts far enough that the row's own ink must invert with it\n // (unlike soft/surface/outline's translucent tints, where the default ink\n // stays legible regardless of colour) — matches Card's own solid recipe,\n // where fg and fg-muted both collapse to the one contrast-guaranteed step.\n ...(variant === 'solid' && {\n '--list-item-fg':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n '--list-item-fg-muted':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ninterface ListItemProps extends React.ComponentProps<'li'> {\n /** The \"recently changed\" accent dot. */\n recent?: boolean\n /** Persistent accent-tint (e.g. the active document). */\n selected?: boolean\n /** Loading placeholder (Figma `skeleton=true`). */\n skeleton?: boolean\n /**\n * Base UI polymorphism for the inner row. Pass `<a href/>` or `<button/>` to make\n * the row interactive (accent-on-hover/focus, cursor, focus ring). Omit → static row.\n */\n render?: useRender.RenderProp\n}\n\nfunction ListItem({\n className,\n children,\n recent,\n selected,\n skeleton,\n render,\n ...props\n}: ListItemProps) {\n // Skeleton rows are decorative placeholders — never render as a real <a>/<button>. An\n // aria-hidden anchor would still be focusable (WCAG 4.1.2), so skeleton forces a plain,\n // non-interactive <div> regardless of `render`.\n const interactive = !skeleton && render != null\n\n const recentCtx = React.useMemo(() => ({ recent: recent ?? false }), [recent])\n\n // Only a ListItemLeading actually marked `interactive` (a Checkbox/Radio/Button) needs\n // to move outside `render`'s element, and only when `render` is literally an <a>/\n // <button> — nesting a focusable control inside another interactive element is invalid\n // HTML and double-fires clicks. A `render` of anything else (a `<Card/>`, say) has no\n // such conflict: it isn't natively interactive itself, so a control inside it is valid,\n // unduplicated HTML, and hoisting would only pull the control outside whatever that\n // element paints (its fill, border, rounded corner) for no reason. A plain icon\n // (interactive unset) is the same story from the other side — either way, Leading stays\n // inside the row by default, so the whole visual row remains one click/tap target.\n const rendersAnchorOrButton =\n React.isValidElement(render) &&\n (render.type === 'a' || render.type === 'button')\n const childArray = skeleton ? undefined : React.Children.toArray(children)\n const leadingChild = childArray?.find(\n (child): child is React.ReactElement<ListItemLeadingProps> =>\n React.isValidElement(child) && child.type === ListItemLeading,\n )\n const hoistLeading =\n rendersAnchorOrButton && Boolean(leadingChild?.props.interactive)\n const leading = hoistLeading ? leadingChild : undefined\n\n const row = useRender({\n defaultTagName: 'div',\n render: skeleton ? undefined : render,\n props: {\n 'data-slot': 'list-item-row',\n // skeleton is a decorative placeholder → keep it out of the a11y tree.\n 'aria-hidden': skeleton ? true : undefined,\n className: cn(\n // row layout from Figma 2217-1667; min-h is a HARD 44px floor (WCAG 2.5.5),\n // py/gap/text still scale past it via the spacing/scaling knobs. no-underline\n // resets the default <a> decoration when the row renders as a link.\n 'flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline',\n hoistLeading ? 'min-w-0 flex-1 pr-4' : 'w-full rounded-[inherit] px-4',\n // not hoisted → this row IS the whole visual item (no outer <li> chrome), so\n // selection tint lives here, same as a row with no leading slot at all.\n !hoistLeading && !skeleton && selected && 'bg-spark-a3',\n interactive && [\n 'group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n hoistLeading\n ? // the custom focus ring is drawn on the outer <li> (has-focus-visible,\n // below) so it wraps the leading control too — but the browser's own\n // default outline still targets whichever element actually has focus,\n // which is this one, so it has to be suppressed here specifically.\n 'focus-visible:outline-hidden'\n : [\n 'hover:bg-spark-a3 focus-visible:bg-spark-a3',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n ],\n ],\n ),\n children: skeleton ? (\n <ListItemSkeleton />\n ) : hoistLeading ? (\n childArray?.filter((child) => child !== leadingChild)\n ) : (\n children\n ),\n },\n })\n\n return (\n <li\n data-slot=\"list-item\"\n className={cn(\n 'relative has-focus-visible:z-10',\n hoistLeading && [\n 'flex min-w-0 items-center gap-4 pl-4',\n !skeleton && selected && 'bg-spark-a3',\n interactive && [\n // neutral at rest → subtle accent tint on hover/focus (the affordance goes\n // accent too, in ListItemTrailing; title + leading stay neutral). The click\n // target itself is `row`, so focus-visible (unlike hover) has to be read off\n // a descendant via has() — it never lands on this <li> directly.\n 'cursor-pointer',\n 'hover:bg-spark-a3 has-focus-visible:bg-spark-a3',\n 'has-focus-visible:ring-2 has-focus-visible:ring-offset-2',\n ],\n ],\n // soft/solid rows sit in a gapped stack, not a glued panel, but still read as one\n // rounded group: only the first/last row round the outer corners. surface/\n // outline glue rows into one bordered panel instead — border SIDES are variant-\n // only (no colour axis), so they stay on data-variant; fill and border COLOUR\n // depend on variant *and* colour together, so those live on data-look instead\n // (see the comment on `look` in List, above).\n 'group-data-[variant=surface]/list:border-x group-data-[variant=surface]/list:border-t group-data-[variant=surface]/list:last:border-b',\n 'group-data-[variant=outline]/list:border-x group-data-[variant=outline]/list:border-t group-data-[variant=outline]/list:last:border-b',\n 'group-data-[look=soft]/list:bg-gray-a2 dark:group-data-[look=soft]/list:bg-gray-a3',\n // arbitrary-var form, not the bg-spark-a3 utility: identical colour, but avoids a\n // literal substring collision with the unconditional selection/hover tint below,\n // which a couple of tests distinguish with a plain className.includes check.\n 'group-data-[look=soft-accent]/list:bg-(--spark-a3)',\n 'group-data-[look=surface]/list:border-gray-a6 group-data-[look=surface]/list:bg-gray-2',\n 'group-data-[look=surface-accent]/list:border-spark-a6 group-data-[look=surface-accent]/list:bg-(--spark-surface)',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n 'group-data-[look=solid]/list:bg-(--neutral-1)',\n 'group-data-[look=solid-accent]/list:bg-spark-9',\n Object.values(radiusRowClasses),\n className,\n )}\n {...props}\n >\n <ListItemContext.Provider value={recentCtx}>\n {leading}\n {row}\n </ListItemContext.Provider>\n </li>\n )\n}\n\nfunction ListItemSkeleton() {\n // Compose the shared Skeleton primitive so the placeholder matches every other skeleton\n // (token + radius + animation + motion-reduce).\n return (\n <>\n <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Skeleton className=\"h-3.5 w-1/3\" />\n <Skeleton className=\"h-3.5 w-2/3\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col items-end gap-2\">\n <Skeleton className=\"h-3.5 w-1/4\" />\n <Skeleton className=\"h-3.5 w-1/2\" />\n </div>\n </>\n )\n}\n\ninterface ListItemLeadingProps extends React.ComponentProps<'div'> {\n /**\n * Set when this slot holds a focusable control (Checkbox, Radio, Button). ListItem\n * then hoists it outside `render`'s element — nesting a real control inside the row's\n * own <a>/<button> is invalid HTML and double-fires clicks. Leave unset for decorative\n * content (an icon): it stays inside the row, so the whole visual row is one\n * click/tap target, same as a row with no leading slot at all.\n */\n interactive?: boolean\n}\n\nfunction ListItemLeading({\n className,\n interactive: _interactive,\n ...props\n}: ListItemLeadingProps) {\n return (\n <div\n data-slot=\"list-item-leading\"\n className={cn('flex shrink-0 items-center', className)}\n {...props}\n />\n )\n}\n\ninterface ListItemContentProps extends React.ComponentProps<'div'> {\n /** `end` right-aligns the column (the value column). */\n align?: 'start' | 'end'\n}\n\nfunction ListItemContent({\n className,\n align = 'start',\n ...props\n}: ListItemContentProps) {\n const columnCtx = React.useMemo(() => ({ isEnd: align === 'end' }), [align])\n return (\n <ListItemColumnContext.Provider value={columnCtx}>\n <div\n data-slot=\"list-item-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-1',\n align === 'end' ? 'items-end text-right' : 'items-start text-left',\n className,\n )}\n {...props}\n />\n </ListItemColumnContext.Provider>\n )\n}\n\nfunction ListItemPreline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-preline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTitle({\n className,\n children,\n ...props\n}: React.ComponentProps<'span'>) {\n const { recent } = React.useContext(ListItemContext)\n const { isEnd } = React.useContext(ListItemColumnContext)\n // recency dot sits inline, immediately left of the title (Figma 1310-977) — only on the\n // primary (start) column, so a two-column value row's value never carries it.\n const showDot = recent && !isEnd\n\n // className/{...props} always land on THIS span (the one that truncates), so a caller's\n // override (e.g. `title`, `line-clamp-2 whitespace-normal`) behaves identically whether\n // or not the dot renders — only the wrapping structure differs below.\n const title = (\n <span\n data-slot=\"list-item-title\"\n className={cn(\n showDot ? 'min-w-0 truncate' : 'block w-full truncate',\n 'text-base text-(--list-item-fg,var(--neutral-12))',\n recent ? 'font-bold' : 'font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </span>\n )\n\n if (!showDot) {\n // No dot: stay a plain block so the column's text alignment (e.g. an end column's\n // text-right) governs the title directly.\n return title\n }\n\n return (\n <span className=\"flex w-full items-center gap-2\">\n <span\n aria-hidden=\"true\"\n data-slot=\"list-item-indicator\"\n className=\"size-2 shrink-0 rounded-full bg-spark-9\"\n />\n {title}\n </span>\n )\n}\n\nfunction ListItemSubline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-subline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTrailing({\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"list-item-trailing\"\n className={cn(\n 'flex shrink-0 items-center text-gray-11',\n // interactive rows tint the affordance accent on hover/focus (group/row is set on\n // the interactive inner row; absent on static rows, so this simply never fires).\n // Relies on the child reading currentColor (Spark icons do) — the colour-\n // independent Icon component hardcodes its own ink and won't pick this up; use\n // a Spark icon here, not Icon.\n 'transition-colors duration-150 motion-reduce:transition-none',\n 'group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n List,\n ListItem,\n ListItemContent,\n ListItemLeading,\n ListItemPreline,\n ListItemSubline,\n ListItemTitle,\n ListItemTrailing,\n}\nexport type {\n ListProps,\n ListItemProps,\n ListItemContentProps,\n ListItemLeadingProps,\n ListVariant,\n ListRadius,\n}\n"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ let react_jsx_runtime = require("react/jsx-runtime");
|
|
|
7
7
|
let class_variance_authority = require("class-variance-authority");
|
|
8
8
|
let _base_ui_react_merge_props = require("@base-ui/react/merge-props");
|
|
9
9
|
let _base_ui_react_use_render = require("@base-ui/react/use-render");
|
|
10
|
-
let
|
|
10
|
+
let cn = require("cn");
|
|
11
11
|
//#region src/components/marker.tsx
|
|
12
12
|
const markerVariants = (0, class_variance_authority.cva)("group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray", { variants: { variant: {
|
|
13
13
|
default: "[&:is(button,a)]:touch-target",
|
|
@@ -21,7 +21,7 @@ function Marker({ className, variant = "default", render, ...props }) {
|
|
|
21
21
|
props: (0, _base_ui_react_merge_props.mergeProps)({
|
|
22
22
|
"data-slot": "marker",
|
|
23
23
|
"data-variant": variant,
|
|
24
|
-
className: (0,
|
|
24
|
+
className: (0, cn.cn)(markerVariants({
|
|
25
25
|
variant,
|
|
26
26
|
className
|
|
27
27
|
}))
|
|
@@ -32,14 +32,14 @@ function MarkerIcon({ className, ...props }) {
|
|
|
32
32
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
33
33
|
"data-slot": "marker-icon",
|
|
34
34
|
"aria-hidden": "true",
|
|
35
|
-
className: (0,
|
|
35
|
+
className: (0, cn.cn)("size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
36
36
|
...props
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
function MarkerContent({ className, ...props }) {
|
|
40
40
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
41
41
|
"data-slot": "marker-content",
|
|
42
|
-
className: (0,
|
|
42
|
+
className: (0, cn.cn)("min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-gray", className),
|
|
43
43
|
...props
|
|
44
44
|
});
|
|
45
45
|
}
|