Files
2026-07-29 16:44:20 -04:00

35 lines
12 KiB
JSON

{
"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
"name": "chart",
"type": "registry:ui",
"devDependencies": [
"layerchart@^2.0.0"
],
"files": [
{
"content": "<script lang=\"ts\">\n\timport { cn, type WithElementRef } from \"$UTILS$.js\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport ChartStyle from \"./chart-style.svelte\";\n\timport { setChartContext, type ChartConfig } from \"./chart-utils.js\";\n\n\tconst uid = $props.id();\n\n\tlet {\n\t\tref = $bindable(null),\n\t\tid = uid,\n\t\tclass: className,\n\t\tchildren,\n\t\tconfig,\n\t\t...restProps\n\t}: WithElementRef<HTMLAttributes<HTMLElement>> & {\n\t\tconfig: ChartConfig;\n\t} = $props();\n\n\tconst chartId = `chart-${id || uid.replace(/:/g, \"\")}`;\n\n\tsetChartContext({\n\t\tget config() {\n\t\t\treturn config;\n\t\t},\n\t});\n</script>\n\n<div\n\tbind:this={ref}\n\tdata-chart={chartId}\n\tdata-slot=\"chart\"\n\tclass={cn(\n\t\t\"flex aspect-video justify-center overflow-visible text-xs\",\n\t\t// Overrides\n\t\t//\n\t\t// Stroke around dots/marks when hovering\n\t\t\"[&_.lc-highlight-point]:stroke-transparent\",\n\t\t// override the default stroke color of lines\n\t\t\"[&_.lc-line]:stroke-border/50\",\n\n\t\t// by default, layerchart shows a line intersecting the point when hovering, this hides that\n\t\t\"[&_.lc-highlight-line]:stroke-0\",\n\n\t\t// by default, when you hover a point on a stacked series chart, it will drop the opacity\n\t\t// of the other series, this overrides that\n\t\t\"[&_.lc-area-path]:opacity-100 [&_.lc-highlight-line]:opacity-100 [&_.lc-highlight-point]:opacity-100 [&_.lc-spline-path]:opacity-100 [&_.lc-text]:text-xs [&_.lc-text-svg]:overflow-visible\",\n\n\t\t// We don't want the little tick lines between the axis labels and the chart, so we remove\n\t\t// the stroke. The alternative is to manually disable `tickMarks` on the x/y axis of every\n\t\t// chart.\n\t\t\"[&_.lc-axis-tick]:stroke-0\",\n\n\t\t// We don't want to display the rule on the x/y axis, as there is already going to be\n\t\t// a grid line there and rule ends up overlapping the marks because it is rendered after\n\t\t// the marks\n\t\t\"[&_.lc-rule-x-line:not(.lc-grid-x-rule)]:stroke-0 [&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0\",\n\t\t\"[&_.lc-grid-x-radial-line]:stroke-border [&_.lc-grid-x-radial-circle]:stroke-border\",\n\t\t\"[&_.lc-grid-y-radial-line]:stroke-border [&_.lc-grid-y-radial-circle]:stroke-border\",\n\n\t\t// Legend adjustments\n\t\t\"[&_.lc-legend-swatch-button]:items-center [&_.lc-legend-swatch-button]:gap-1.5\",\n\t\t\"[&_.lc-legend-swatch-group]:items-center [&_.lc-legend-swatch-group]:gap-4\",\n\t\t\"[&_.lc-legend-swatch]:size-2.5 [&_.lc-legend-swatch]:rounded-[2px]\",\n\n\t\t// Labels\n\t\t\"[&_.lc-labels-text:not([fill])]:fill-foreground [&_text]:stroke-transparent\",\n\n\t\t// Tick labels on th x/y axes\n\t\t\"[&_.lc-axis-tick-label]:fill-muted-foreground [&_.lc-axis-tick-label]:font-normal\",\n\t\t\"[&_.lc-tooltip-rects-g]:fill-transparent\",\n\t\t\"[&_.lc-layout-svg-g]:fill-transparent\",\n\t\t\"[&_.lc-root-container]:w-full\",\n\t\tclassName\n\t)}\n\t{...restProps}\n>\n\t<ChartStyle id={chartId} {config} />\n\t{@render children?.()}\n</div>\n",
"type": "registry:file",
"target": "chart/chart-container.svelte"
},
{
"content": "<script lang=\"ts\">\n\timport { THEMES, type ChartConfig } from \"./chart-utils.js\";\n\n\tlet { id, config }: { id: string; config: ChartConfig } = $props();\n\n\tconst colorConfig = $derived(\n\t\tconfig ? Object.entries(config).filter(([, config]) => config.theme || config.color) : null\n\t);\n\n\tconst themeContents = $derived.by(() => {\n\t\tif (!colorConfig || !colorConfig.length) return;\n\n\t\tconst themeContents = [];\n\t\tfor (const [_theme, prefix] of Object.entries(THEMES)) {\n\t\t\tlet content = `${prefix} [data-chart=${id}] {\\n`;\n\t\t\tconst color = colorConfig.map(([key, itemConfig]) => {\n\t\t\t\tconst theme = _theme as keyof typeof itemConfig.theme;\n\t\t\t\tconst color = itemConfig.theme?.[theme] || itemConfig.color;\n\t\t\t\treturn color ? `\\t--color-${key}: ${color};` : null;\n\t\t\t});\n\n\t\t\tcontent += color.join(\"\\n\") + \"\\n}\";\n\n\t\t\tthemeContents.push(content);\n\t\t}\n\n\t\treturn themeContents.join(\"\\n\");\n\t});\n</script>\n\n{#if themeContents}\n\t{#key id}\n\t\t<svelte:element this={\"style\"}>\n\t\t\t{themeContents}\n\t\t</svelte:element>\n\t{/key}\n{/if}\n",
"type": "registry:file",
"target": "chart/chart-style.svelte"
},
{
"content": "<script lang=\"ts\">\n\timport { cn, type WithElementRef, type WithoutChildren } from \"$UTILS$.js\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { getPayloadConfigFromPayload, useChart, type TooltipPayload } from \"./chart-utils.js\";\n\timport { getTooltipContext, Tooltip as TooltipPrimitive } from \"layerchart\";\n\timport type { Snippet } from \"svelte\";\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tfunction defaultFormatter(value: any, _payload: TooltipPayload[]) {\n\t\treturn `${value}`;\n\t}\n\n\tlet {\n\t\tref = $bindable(null),\n\t\tclass: className,\n\t\thideLabel = false,\n\t\tindicator = \"dot\",\n\t\thideIndicator = false,\n\t\tlabelKey,\n\t\tlabel,\n\t\tlabelFormatter = defaultFormatter,\n\t\tlabelClassName,\n\t\tformatter,\n\t\tnameKey,\n\t\tcolor,\n\t\t...restProps\n\t}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & {\n\t\thideLabel?: boolean;\n\t\tlabel?: string;\n\t\tindicator?: \"line\" | \"dot\" | \"dashed\";\n\t\tnameKey?: string;\n\t\tlabelKey?: string;\n\t\thideIndicator?: boolean;\n\t\tlabelClassName?: string;\n\t\tlabelFormatter?: // eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t((value: any, payload: TooltipPayload[]) => string | number | Snippet) | null;\n\t\tformatter?: Snippet<\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\tvalue: unknown;\n\t\t\t\t\tname: string;\n\t\t\t\t\titem: TooltipPayload;\n\t\t\t\t\tindex: number;\n\t\t\t\t\tpayload: TooltipPayload[];\n\t\t\t\t},\n\t\t\t]\n\t\t>;\n\t} = $props();\n\n\tconst chart = useChart();\n\tconst tooltipCtx = getTooltipContext();\n\n\tconst formattedLabel = $derived.by(() => {\n\t\tif (hideLabel || !tooltipCtx.payload?.length) return null;\n\n\t\tconst [item] = tooltipCtx.payload;\n\t\tconst key = labelKey ?? item?.label ?? item?.name ?? \"value\";\n\n\t\tconst itemConfig = getPayloadConfigFromPayload(chart.config, item, key);\n\n\t\tconst value =\n\t\t\t!labelKey && typeof label === \"string\"\n\t\t\t\t? (chart.config[label as keyof typeof chart.config]?.label ?? label)\n\t\t\t\t: (itemConfig?.label ?? item.label);\n\n\t\tif (value === undefined) return null;\n\t\tif (!labelFormatter) return value;\n\t\treturn labelFormatter(value, tooltipCtx.payload);\n\t});\n\n\tconst nestLabel = $derived(tooltipCtx.payload.length === 1 && indicator !== \"dot\");\n</script>\n\n{#snippet TooltipLabel()}\n\t{#if formattedLabel}\n\t\t<div class={cn(\"font-medium\", labelClassName)}>\n\t\t\t{#if typeof formattedLabel === \"function\"}\n\t\t\t\t{@render formattedLabel()}\n\t\t\t{:else}\n\t\t\t\t{formattedLabel}\n\t\t\t{/if}\n\t\t</div>\n\t{/if}\n{/snippet}\n\n<TooltipPrimitive.Root variant=\"none\">\n\t<div\n\t\tclass={cn(\n\t\t\t\"border-border/50 bg-background grid min-w-[9rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl\",\n\t\t\tclassName\n\t\t)}\n\t\t{...restProps}\n\t>\n\t\t{#if !nestLabel}\n\t\t\t{@render TooltipLabel()}\n\t\t{/if}\n\t\t<div class=\"grid gap-1.5\">\n\t\t\t{#each tooltipCtx.payload as item, i (item.key + i)}\n\t\t\t\t{@const key = `${nameKey || item.key || item.name || \"value\"}`}\n\t\t\t\t{@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)}\n\t\t\t\t{@const indicatorColor = color || item.payload?.color || item.color}\n\t\t\t\t<div\n\t\t\t\t\tclass={cn(\n\t\t\t\t\t\t\"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5\",\n\t\t\t\t\t\tindicator === \"dot\" && \"items-center\"\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{#if formatter && item.value !== undefined && item.name}\n\t\t\t\t\t\t{@render formatter({\n\t\t\t\t\t\t\tvalue: item.value,\n\t\t\t\t\t\t\tname: item.name,\n\t\t\t\t\t\t\titem,\n\t\t\t\t\t\t\tindex: i,\n\t\t\t\t\t\t\tpayload: tooltipCtx.payload,\n\t\t\t\t\t\t})}\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t{#if itemConfig?.icon}\n\t\t\t\t\t\t\t<itemConfig.icon />\n\t\t\t\t\t\t{:else if !hideIndicator}\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tstyle=\"--color-bg: {indicatorColor}; --color-border: {indicatorColor};\"\n\t\t\t\t\t\t\t\tclass={cn(\n\t\t\t\t\t\t\t\t\t\"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\",\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"size-2.5\": indicator === \"dot\",\n\t\t\t\t\t\t\t\t\t\t\"h-full w-1\": indicator === \"line\",\n\t\t\t\t\t\t\t\t\t\t\"w-0 border-[1.5px] border-dashed bg-transparent\":\n\t\t\t\t\t\t\t\t\t\t\tindicator === \"dashed\",\n\t\t\t\t\t\t\t\t\t\t\"my-0.5\": nestLabel && indicator === \"dashed\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t></div>\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass={cn(\n\t\t\t\t\t\t\t\t\"flex flex-1 shrink-0 justify-between leading-none\",\n\t\t\t\t\t\t\t\tnestLabel ? \"items-end\" : \"items-center\"\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div class=\"grid gap-1.5\">\n\t\t\t\t\t\t\t\t{#if nestLabel}\n\t\t\t\t\t\t\t\t\t{@render TooltipLabel()}\n\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t\t<span class=\"text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t{itemConfig?.label || item.name}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t{#if item.value !== undefined}\n\t\t\t\t\t\t\t\t<span class=\"text-foreground font-mono font-medium tabular-nums\">\n\t\t\t\t\t\t\t\t\t{item.value.toLocaleString()}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/each}\n\t\t</div>\n\t</div>\n</TooltipPrimitive.Root>\n",
"type": "registry:file",
"target": "chart/chart-tooltip.svelte"
},
{
"content": "import type { Tooltip } from \"layerchart\";\nimport { getContext, setContext, type Component, type ComponentProps, type Snippet } from \"svelte\";\n\nexport const THEMES = { light: \"\", dark: \".dark\" } as const;\n\nexport type ChartConfig = {\n\t[k in string]: {\n\t\tlabel?: string;\n\t\ticon?: Component;\n\t} & (\n\t\t| { color?: string; theme?: never }\n\t\t| { color?: never; theme: Record<keyof typeof THEMES, string> }\n\t);\n};\n\nexport type ExtractSnippetParams<T> = T extends Snippet<[infer P]> ? P : never;\n\nexport type TooltipPayload = ExtractSnippetParams<\n\tComponentProps<typeof Tooltip.Root>[\"children\"]\n>[\"payload\"][number];\n\n// Helper to extract item config from a payload.\nexport function getPayloadConfigFromPayload(\n\tconfig: ChartConfig,\n\tpayload: TooltipPayload,\n\tkey: string\n) {\n\tif (typeof payload !== \"object\" || payload === null) return undefined;\n\n\tconst payloadPayload =\n\t\t\"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n\t\t\t? payload.payload\n\t\t\t: undefined;\n\n\tlet configLabelKey: string = key;\n\n\tif (payload.key === key) {\n\t\tconfigLabelKey = payload.key;\n\t} else if (payload.name === key) {\n\t\tconfigLabelKey = payload.name;\n\t} else if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n\t\tconfigLabelKey = payload[key as keyof typeof payload] as string;\n\t} else if (\n\t\tpayloadPayload !== undefined &&\n\t\tkey in payloadPayload &&\n\t\ttypeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n\t) {\n\t\tconfigLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n\t}\n\n\treturn configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];\n}\n\ntype ChartContextValue = {\n\tconfig: ChartConfig;\n};\n\nconst chartContextKey = Symbol(\"chart-context\");\n\nexport function setChartContext(value: ChartContextValue) {\n\treturn setContext(chartContextKey, value);\n}\n\nexport function useChart() {\n\treturn getContext<ChartContextValue>(chartContextKey);\n}\n",
"type": "registry:file",
"target": "chart/chart-utils.ts"
},
{
"content": "import ChartContainer from \"./chart-container.svelte\";\nimport ChartTooltip from \"./chart-tooltip.svelte\";\n\nexport { getPayloadConfigFromPayload, type ChartConfig } from \"./chart-utils.js\";\n\nexport { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip };\n",
"type": "registry:file",
"target": "chart/index.ts"
}
]
}