@tulipfarm/sprout 0.3.0 → 0.4.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/README.md +45 -40
- package/dist/components/accordion.d.ts +2 -2
- package/dist/components/accordion.js +9 -9
- package/dist/components/agent-fanout.d.ts +2 -2
- package/dist/components/agent-fanout.js +65 -87
- package/dist/components/alert-dialog.d.ts +6 -6
- package/dist/components/alert-dialog.js +20 -20
- package/dist/components/alert.d.ts +6 -6
- package/dist/components/alert.js +13 -13
- package/dist/components/aspect-ratio.d.ts +2 -2
- package/dist/components/aspect-ratio.js +2 -2
- package/dist/components/attachment.d.ts +17 -17
- package/dist/components/attachment.js +82 -115
- package/dist/components/avatar.d.ts +10 -10
- package/dist/components/avatar.js +14 -14
- package/dist/components/badge.d.ts +4 -4
- package/dist/components/badge.js +18 -18
- package/dist/components/board.d.ts +107 -0
- package/dist/components/board.js +640 -0
- package/dist/components/brand-icon.d.ts +6 -6
- package/dist/components/brand-icon.js +55 -63
- package/dist/components/breadcrumb.d.ts +11 -11
- package/dist/components/breadcrumb.js +16 -18
- package/dist/components/button-group.d.ts +6 -6
- package/dist/components/button-group.js +14 -14
- package/dist/components/button.d.ts +11 -11
- package/dist/components/button.js +46 -56
- package/dist/components/calendar.d.ts +5 -5
- package/dist/components/calendar.js +44 -53
- package/dist/components/canvas.d.ts +4 -4
- package/dist/components/canvas.js +39 -46
- package/dist/components/card.d.ts +6 -6
- package/dist/components/card.js +17 -17
- package/dist/components/carousel.d.ts +7 -7
- package/dist/components/carousel.js +57 -75
- package/dist/components/chart.d.ts +5 -112
- package/dist/components/chart.js +5 -172
- package/dist/components/chat-confirmation.d.ts +13 -11
- package/dist/components/chat-confirmation.js +32 -38
- package/dist/components/chat-input.d.ts +4 -4
- package/dist/components/chat-input.js +74 -81
- package/dist/components/chat-plan.d.ts +2 -2
- package/dist/components/chat-plan.js +15 -20
- package/dist/components/chat-sources.d.ts +8 -8
- package/dist/components/chat-sources.js +18 -27
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/checkbox.js +9 -9
- package/dist/components/choice-list.d.ts +2 -2
- package/dist/components/choice-list.js +16 -18
- package/dist/components/code-block.d.ts +5 -5
- package/dist/components/code-block.js +26 -26
- package/dist/components/collapsible.d.ts +1 -1
- package/dist/components/collapsible.js +7 -11
- package/dist/components/combobox.d.ts +3 -3
- package/dist/components/combobox.js +24 -24
- package/dist/components/command.d.ts +13 -13
- package/dist/components/command.js +61 -73
- package/dist/components/context-menu.d.ts +5 -5
- package/dist/components/context-menu.js +22 -22
- package/dist/components/dialog.d.ts +6 -6
- package/dist/components/dialog.js +23 -28
- package/dist/components/direction.d.ts +1 -1
- package/dist/components/direction.js +1 -1
- package/dist/components/dropdown-menu.d.ts +5 -5
- package/dist/components/dropdown-menu.js +20 -20
- package/dist/components/field.d.ts +11 -11
- package/dist/components/field.js +41 -61
- package/dist/components/file-explorer.d.ts +2 -2
- package/dist/components/file-explorer.js +89 -105
- package/dist/components/file-viewer.d.ts +10 -10
- package/dist/components/file-viewer.js +112 -140
- package/dist/components/form.d.ts +7 -7
- package/dist/components/form.js +24 -28
- package/dist/components/glass-banner.d.ts +3 -3
- package/dist/components/glass-banner.js +21 -24
- package/dist/components/gradient-banner.d.ts +5 -5
- package/dist/components/gradient-banner.js +13 -22
- package/dist/components/hover-card.d.ts +4 -4
- package/dist/components/hover-card.js +6 -6
- package/dist/components/input-group.d.ts +11 -11
- package/dist/components/input-group.js +22 -36
- package/dist/components/input-otp.d.ts +10 -10
- package/dist/components/input-otp.js +13 -15
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +7 -7
- package/dist/components/kbd.d.ts +3 -3
- package/dist/components/kbd.js +5 -5
- package/dist/components/label.d.ts +2 -2
- package/dist/components/label.js +2 -2
- package/dist/components/mention-list.d.ts +3 -3
- package/dist/components/mention-list.js +10 -10
- package/dist/components/menubar.d.ts +6 -6
- package/dist/components/menubar.js +22 -22
- package/dist/components/message-actions.d.ts +2 -2
- package/dist/components/message-actions.js +29 -31
- package/dist/components/message-response.d.ts +3 -3
- package/dist/components/message-response.js +18 -18
- package/dist/components/message-scroller.d.ts +12 -12
- package/dist/components/message-scroller.js +44 -56
- package/dist/components/message.d.ts +11 -11
- package/dist/components/message.js +25 -25
- package/dist/components/navigation-menu.d.ts +2 -2
- package/dist/components/navigation-menu.js +15 -15
- package/dist/components/notice-banner.d.ts +2 -2
- package/dist/components/notice-banner.js +2 -2
- package/dist/components/page-content.d.ts +2 -2
- package/dist/components/page-content.js +2 -2
- package/dist/components/page-header.d.ts +1 -1
- package/dist/components/page-header.js +16 -16
- package/dist/components/pagination.d.ts +9 -9
- package/dist/components/pagination.js +17 -17
- package/dist/components/plan-outline.d.ts +2 -2
- package/dist/components/plan-outline.js +43 -60
- package/dist/components/popover.d.ts +6 -6
- package/dist/components/popover.js +15 -15
- package/dist/components/presence-cursor.d.ts +2 -2
- package/dist/components/presence-cursor.js +3 -3
- package/dist/components/progress.d.ts +1 -1
- package/dist/components/progress.js +8 -8
- package/dist/components/prompt-list.d.ts +3 -3
- package/dist/components/prompt-list.js +6 -6
- package/dist/components/questionnaire.d.ts +19 -19
- package/dist/components/questionnaire.js +183 -242
- package/dist/components/radio-group.d.ts +2 -2
- package/dist/components/radio-group.js +6 -6
- package/dist/components/reasoning-effort.d.ts +3 -3
- package/dist/components/reasoning-effort.js +9 -12
- package/dist/components/record-list.d.ts +66 -0
- package/dist/components/record-list.js +151 -0
- package/dist/components/resizable.d.ts +5 -5
- package/dist/components/resizable.js +76 -102
- package/dist/components/result-card.d.ts +10 -5
- package/dist/components/result-card.js +42 -53
- package/dist/components/result-notice.d.ts +18 -0
- package/dist/components/result-notice.js +29 -0
- package/dist/components/review-card.d.ts +6 -6
- package/dist/components/review-card.js +18 -18
- package/dist/components/rich-text-editor.d.ts +2 -2
- package/dist/components/rich-text-editor.js +56 -72
- package/dist/components/schema-diagram.d.ts +3 -3
- package/dist/components/schema-diagram.js +39 -53
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/scroll-area.js +10 -14
- package/dist/components/search-dialog.d.ts +6 -6
- package/dist/components/search-dialog.js +16 -16
- package/dist/components/search-field.d.ts +1 -1
- package/dist/components/search-field.js +11 -11
- package/dist/components/select.d.ts +3 -3
- package/dist/components/select.js +20 -20
- package/dist/components/selection-slot.d.ts +2 -2
- package/dist/components/selection-slot.js +6 -9
- package/dist/components/separator.d.ts +15 -15
- package/dist/components/separator.js +22 -27
- package/dist/components/settings-list.d.ts +3 -3
- package/dist/components/settings-list.js +5 -5
- package/dist/components/sheet.d.ts +6 -6
- package/dist/components/sheet.js +27 -27
- package/dist/components/sidebar.d.ts +22 -22
- package/dist/components/sidebar.js +57 -58
- package/dist/components/skeleton.d.ts +5 -5
- package/dist/components/skeleton.js +14 -21
- package/dist/components/slider.d.ts +2 -2
- package/dist/components/slider.js +6 -6
- package/dist/components/spinner.d.ts +5 -5
- package/dist/components/spinner.js +22 -26
- package/dist/components/sql-viewer.d.ts +14 -14
- package/dist/components/sql-viewer.js +99 -128
- package/dist/components/state-panel-samples.d.ts +2 -2
- package/dist/components/state-panel-samples.js +209 -245
- package/dist/components/state-panel.d.ts +2 -35
- package/dist/components/state-panel.js +2 -29
- package/dist/components/state-sample.d.ts +7 -7
- package/dist/components/state-sample.js +18 -31
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/switch.js +7 -7
- package/dist/components/table-nuqs.d.ts +2 -2
- package/dist/components/table-nuqs.js +11 -12
- package/dist/components/table.d.ts +4 -4
- package/dist/components/table.js +166 -235
- package/dist/components/tabs.d.ts +2 -2
- package/dist/components/tabs.js +12 -12
- package/dist/components/textarea.d.ts +2 -2
- package/dist/components/textarea.js +6 -8
- package/dist/components/timeline.d.ts +3 -0
- package/dist/components/timeline.js +3 -0
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/toast.js +19 -25
- package/dist/components/toggle-group.d.ts +2 -2
- package/dist/components/toggle-group.js +18 -20
- package/dist/components/toggle.d.ts +2 -2
- package/dist/components/toggle.js +8 -8
- package/dist/components/tool-activity.d.ts +1 -1
- package/dist/components/tool-activity.js +83 -110
- package/dist/components/toolbar-status.d.ts +2 -2
- package/dist/components/toolbar-status.js +7 -7
- package/dist/components/tooltip.d.ts +5 -5
- package/dist/components/tooltip.js +12 -11
- package/dist/components/typed-text.d.ts +2 -2
- package/dist/components/typed-text.js +10 -12
- package/dist/components/view-switcher.d.ts +51 -0
- package/dist/components/view-switcher.js +114 -0
- package/dist/index.d.ts +98 -93
- package/dist/index.js +98 -93
- package/dist/internal/chart-kit.d.ts +142 -0
- package/dist/internal/chart-kit.js +200 -0
- package/dist/internal/charts/bars.d.ts +61 -0
- package/dist/internal/charts/bars.js +86 -0
- package/dist/internal/charts/lines.d.ts +67 -0
- package/dist/internal/charts/lines.js +70 -0
- package/dist/internal/charts/more.d.ts +77 -0
- package/dist/internal/charts/more.js +68 -0
- package/dist/internal/charts/parts.d.ts +54 -0
- package/dist/internal/charts/parts.js +82 -0
- package/dist/internal/charts/timeline-dates.d.ts +38 -0
- package/dist/internal/charts/timeline-dates.js +113 -0
- package/dist/internal/charts/timeline-window.d.ts +31 -0
- package/dist/internal/charts/timeline-window.js +26 -0
- package/dist/internal/charts/timeline.d.ts +72 -0
- package/dist/internal/charts/timeline.js +287 -0
- package/dist/internal/classes.d.ts +1 -1
- package/dist/internal/classes.js +7 -9
- package/dist/internal/code-tokens.d.ts +2 -2
- package/dist/internal/code-tokens.js +12 -12
- package/dist/internal/fieldset.d.ts +1 -1
- package/dist/internal/fieldset.js +6 -6
- package/dist/internal/file-kind.d.ts +1 -1
- package/dist/internal/file-kind.js +43 -51
- package/dist/internal/flow.d.ts +4 -4
- package/dist/internal/flow.js +20 -23
- package/dist/internal/languages.js +999 -999
- package/dist/internal/message-scroller.d.ts +3 -3
- package/dist/internal/message-scroller.js +64 -112
- package/dist/internal/pressable.d.ts +1 -1
- package/dist/internal/pressable.js +7 -7
- package/dist/internal/state-panel.d.ts +35 -0
- package/dist/internal/state-panel.js +22 -0
- package/dist/internal/syntax.d.ts +2 -2
- package/dist/internal/syntax.js +36 -47
- package/dist/styles/accordion.css +3 -5
- package/dist/styles/agent-fanout.css +64 -60
- package/dist/styles/alert-dialog.css +12 -26
- package/dist/styles/alert.css +10 -15
- package/dist/styles/attachment.css +51 -93
- package/dist/styles/avatar.css +17 -24
- package/dist/styles/badge.css +11 -36
- package/dist/styles/board.css +571 -0
- package/dist/styles/brand-icon.css +7 -8
- package/dist/styles/breadcrumb.css +4 -7
- package/dist/styles/button-group.css +8 -14
- package/dist/styles/button.css +22 -27
- package/dist/styles/calendar.css +24 -36
- package/dist/styles/canvas.css +2 -3
- package/dist/styles/card.css +7 -23
- package/dist/styles/carousel.css +11 -12
- package/dist/styles/chart.css +215 -31
- package/dist/styles/chat-input.css +15 -39
- package/dist/styles/chat-plan.css +3 -4
- package/dist/styles/chat-sources.css +3 -6
- package/dist/styles/checkbox.css +11 -25
- package/dist/styles/choice-list.css +17 -31
- package/dist/styles/code-block.css +11 -25
- package/dist/styles/collapsible.css +10 -15
- package/dist/styles/combobox.css +11 -15
- package/dist/styles/command.css +5 -8
- package/dist/styles/context-menu.css +2 -2
- package/dist/styles/dialog.css +5 -16
- package/dist/styles/dropdown-menu.css +2 -2
- package/dist/styles/field.css +15 -21
- package/dist/styles/file-explorer.css +19 -33
- package/dist/styles/file-viewer.css +13 -17
- package/dist/styles/form.css +3 -3
- package/dist/styles/glass-banner.css +12 -47
- package/dist/styles/gradient-banner.css +17 -35
- package/dist/styles/hover-card.css +9 -15
- package/dist/styles/index.css +116 -108
- package/dist/styles/input-group.css +25 -41
- package/dist/styles/input-otp.css +5 -6
- package/dist/styles/input.css +5 -5
- package/dist/styles/kbd.css +1 -1
- package/dist/styles/label.css +9 -30
- package/dist/styles/mention-list.css +6 -8
- package/dist/styles/menubar.css +5 -6
- package/dist/styles/message-actions.css +1 -1
- package/dist/styles/message-response.css +3 -5
- package/dist/styles/message-scroller.css +14 -21
- package/dist/styles/message.css +23 -60
- package/dist/styles/navigation-menu.css +14 -29
- package/dist/styles/notice-banner.css +1 -2
- package/dist/styles/page-header.css +7 -10
- package/dist/styles/pagination.css +1 -4
- package/dist/styles/plan-outline.css +20 -37
- package/dist/styles/popover.css +10 -19
- package/dist/styles/presence-cursor.css +1 -2
- package/dist/styles/progress.css +2 -4
- package/dist/styles/prompt-list.css +6 -17
- package/dist/styles/questionnaire.css +16 -40
- package/dist/styles/radio-group.css +12 -23
- package/dist/styles/reasoning-effort.css +8 -15
- package/dist/styles/record-list.css +243 -0
- package/dist/styles/resizable.css +10 -11
- package/dist/styles/result-card.css +17 -3
- package/dist/styles/result-notice.css +80 -0
- package/dist/styles/review-card.css +2 -3
- package/dist/styles/rich-text-editor.css +1 -2
- package/dist/styles/schema-diagram.css +12 -17
- package/dist/styles/scroll-area.css +8 -10
- package/dist/styles/search-dialog.css +1 -5
- package/dist/styles/search-field.css +1 -2
- package/dist/styles/select.css +7 -7
- package/dist/styles/selection-slot.css +1 -2
- package/dist/styles/separator.css +7 -8
- package/dist/styles/settings-list.css +1 -2
- package/dist/styles/sheet.css +40 -73
- package/dist/styles/sidebar.css +17 -21
- package/dist/styles/skeleton.css +9 -11
- package/dist/styles/slider.css +11 -21
- package/dist/styles/spinner.css +2 -2
- package/dist/styles/sql-viewer.css +29 -29
- package/dist/styles/state-panel.css +8 -9
- package/dist/styles/state-sample.css +20 -33
- package/dist/styles/switch.css +11 -18
- package/dist/styles/table.css +51 -77
- package/dist/styles/tabs.css +12 -16
- package/dist/styles/textarea.css +3 -3
- package/dist/styles/themes/chatgpt.css +11 -23
- package/dist/styles/themes/claude.css +12 -24
- package/dist/styles/themes/cloudflare.css +11 -23
- package/dist/styles/themes/discord.css +11 -23
- package/dist/styles/themes/github.css +11 -23
- package/dist/styles/themes/grok.css +11 -23
- package/dist/styles/themes/linear.css +12 -25
- package/dist/styles/themes/neutral.css +11 -23
- package/dist/styles/themes/notion.css +11 -23
- package/dist/styles/themes/perplexity.css +11 -23
- package/dist/styles/themes/raycast.css +11 -23
- package/dist/styles/themes/spotify.css +11 -23
- package/dist/styles/themes/stripe.css +11 -23
- package/dist/styles/themes/supabase.css +11 -23
- package/dist/styles/themes/tulipfarm.css +9 -10
- package/dist/styles/themes/vercel.css +12 -25
- package/dist/styles/timeline.css +276 -0
- package/dist/styles/toast.css +13 -20
- package/dist/styles/toggle-group.css +17 -23
- package/dist/styles/toggle.css +8 -8
- package/dist/styles/tool-activity.css +7 -13
- package/dist/styles/toolbar-status.css +2 -7
- package/dist/styles/tooltip.css +15 -21
- package/dist/styles/view-switcher.css +279 -0
- package/package.json +21 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type ComponentProps, type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import { ResponsiveContainer, Tooltip, type BarShapeProps, type DefaultLegendContentProps, type DefaultTooltipContentProps, type TooltipValueType } from 'recharts';
|
|
3
|
+
/** Theme names a `ChartConfig` entry can color, and the selector each one uses. */
|
|
4
|
+
declare const THEMES: {
|
|
5
|
+
readonly light: "";
|
|
6
|
+
readonly dark: ".dark";
|
|
7
|
+
};
|
|
8
|
+
export declare const chartIndicatorNames: readonly ["dot", "line", "dashed"];
|
|
9
|
+
export type ChartIndicator = (typeof chartIndicatorNames)[number];
|
|
10
|
+
export type ChartConfig = Record<string, {
|
|
11
|
+
label?: ReactNode;
|
|
12
|
+
icon?: ComponentType;
|
|
13
|
+
} & ({
|
|
14
|
+
color?: string;
|
|
15
|
+
theme?: never;
|
|
16
|
+
} | {
|
|
17
|
+
color?: never;
|
|
18
|
+
theme: Record<keyof typeof THEMES, string>;
|
|
19
|
+
})>;
|
|
20
|
+
/**
|
|
21
|
+
* The CSS variable a `ChartContainer` sets for one config key. Pass it as a
|
|
22
|
+
* series `fill` or `stroke`, for example `fill={chartColor("tasks")}`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function chartColor(key: string): string;
|
|
25
|
+
export type ChartContainerProps = ComponentProps<'div'> & {
|
|
26
|
+
config: ChartConfig;
|
|
27
|
+
children: ComponentProps<typeof ResponsiveContainer>['children'];
|
|
28
|
+
/** Size used before the container is measured, and on the server. */
|
|
29
|
+
initialDimension?: {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Sizes a Recharts chart to its box and turns `config` into series colors
|
|
36
|
+
* and labels for the tooltip and legend. The box is 16:9 unless `className`
|
|
37
|
+
* or `style` gives it a height.
|
|
38
|
+
*/
|
|
39
|
+
export declare function ChartContainer({ id, className, children, config, initialDimension, ...props }: ChartContainerProps): import("react").JSX.Element;
|
|
40
|
+
/**
|
|
41
|
+
* Writes `--sprout-chart-color-<key>` for every colored config entry, with a
|
|
42
|
+
* dark value for entries that pass `theme`. `ChartContainer` renders it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ChartStyle({ id, config }: {
|
|
45
|
+
id: string;
|
|
46
|
+
config: ChartConfig;
|
|
47
|
+
}): import("react").JSX.Element | null;
|
|
48
|
+
export type ChartTooltipProps = ComponentProps<typeof Tooltip>;
|
|
49
|
+
/**
|
|
50
|
+
* Recharts' `Tooltip`, moving in 160ms. Pass `content={<ChartTooltipContent />}`.
|
|
51
|
+
* It stays still when reduced motion is on.
|
|
52
|
+
*/
|
|
53
|
+
export declare function ChartTooltip(props: ChartTooltipProps): import("react").JSX.Element;
|
|
54
|
+
type TooltipNameType = number | string;
|
|
55
|
+
export type ChartTooltipContentProps = ComponentProps<typeof Tooltip> & ComponentProps<'div'> & {
|
|
56
|
+
hideLabel?: boolean;
|
|
57
|
+
hideIndicator?: boolean;
|
|
58
|
+
indicator?: ChartIndicator;
|
|
59
|
+
nameKey?: string;
|
|
60
|
+
labelKey?: string;
|
|
61
|
+
/** Formats each row's number, for example as currency. */
|
|
62
|
+
valueFormatter?: ValueFormatter;
|
|
63
|
+
} & Omit<DefaultTooltipContentProps<TooltipValueType, TooltipNameType>, 'accessibilityLayer'>;
|
|
64
|
+
/**
|
|
65
|
+
* The tooltip card: a label, then one row per series with its indicator,
|
|
66
|
+
* config label, and value. Recharts passes `active`, `payload`, and `label`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function ChartTooltipContent({ active, payload, className, indicator, hideLabel, hideIndicator, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, valueFormatter, }: ChartTooltipContentProps): import("react").JSX.Element | null;
|
|
69
|
+
/** Recharts' `Legend`. Pass `content={<ChartLegendContent />}`. */
|
|
70
|
+
export declare const ChartLegend: import("react").MemoExoticComponent<(outsideProps: import("recharts").LegendProps) => React.ReactPortal | null>;
|
|
71
|
+
export type ChartLegendContentProps = ComponentProps<'div'> & {
|
|
72
|
+
hideIcon?: boolean;
|
|
73
|
+
nameKey?: string;
|
|
74
|
+
} & DefaultLegendContentProps;
|
|
75
|
+
/** A centered row of swatches and config labels, one per series. */
|
|
76
|
+
export declare function ChartLegendContent({ className, hideIcon, payload, verticalAlign, nameKey, }: ChartLegendContentProps): import("react").JSX.Element | null;
|
|
77
|
+
/** One series: its data key, legend and tooltip label, and color. */
|
|
78
|
+
export type ChartSeries = {
|
|
79
|
+
key: string;
|
|
80
|
+
label: string;
|
|
81
|
+
color: string;
|
|
82
|
+
};
|
|
83
|
+
export type UsageSeries = ChartSeries;
|
|
84
|
+
/** One data row: a category or label key plus a number per series key. */
|
|
85
|
+
export type ChartRow = Record<string, number | string | null | undefined>;
|
|
86
|
+
/** Turns a number into the text an axis tick or tooltip shows. */
|
|
87
|
+
export type ValueFormatter = (value: number) => string;
|
|
88
|
+
export declare const formatNumber: ValueFormatter;
|
|
89
|
+
/** The five chart colors, in order. */
|
|
90
|
+
export declare const chartPalette: string[];
|
|
91
|
+
/** The color for parts past the fifth, folded into one "N more" part. */
|
|
92
|
+
export declare const chartOtherColor = "var(--sprout-muted)";
|
|
93
|
+
/** The palette color for a part, or the muted color past the fifth. */
|
|
94
|
+
export declare const partColor: (index: number) => string;
|
|
95
|
+
/**
|
|
96
|
+
* One of `steps` shades of `color`, mixed with the surface so it reads in
|
|
97
|
+
* light and dark. Step 1 is lightest; the last step is `color` itself.
|
|
98
|
+
*/
|
|
99
|
+
export declare const chartShade: (color: string, step: number, steps?: number) => string;
|
|
100
|
+
export type ChartPart = {
|
|
101
|
+
name: string;
|
|
102
|
+
value: number;
|
|
103
|
+
fill: string;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Sorts rows by `value`, keeps the top `limit` as colored parts, and folds the
|
|
107
|
+
* rest into one muted "N more" part.
|
|
108
|
+
*/
|
|
109
|
+
export declare function foldParts(rows: ChartRow[], category: string, value: string, limit?: number): ChartPart[];
|
|
110
|
+
/** A config entry per part, so the tooltip and legend show part names. */
|
|
111
|
+
export declare const partConfig: (parts: ChartPart[]) => ChartConfig;
|
|
112
|
+
/** Shared props for every ready chart. */
|
|
113
|
+
export interface ReadyChartProps {
|
|
114
|
+
/** Names the chart for screen readers. */
|
|
115
|
+
title?: string;
|
|
116
|
+
/** Formats axis ticks and tooltip values, for example as currency. */
|
|
117
|
+
valueFormatter?: ValueFormatter;
|
|
118
|
+
className?: string;
|
|
119
|
+
}
|
|
120
|
+
export declare const seriesConfig: (series: ChartSeries[]) => ChartConfig;
|
|
121
|
+
export declare const tooltipCursor: {
|
|
122
|
+
fill: string;
|
|
123
|
+
};
|
|
124
|
+
/** Axis props every ready chart shares: no tick marks, no axis line. */
|
|
125
|
+
export declare const quietAxis: {
|
|
126
|
+
readonly tickLine: false;
|
|
127
|
+
readonly axisLine: false;
|
|
128
|
+
};
|
|
129
|
+
/** How series pile up: side by side, stacked, or stacked to 100%. */
|
|
130
|
+
export type ChartStack = 'none' | 'stacked' | 'percent';
|
|
131
|
+
export declare const stackOffset: (stack: ChartStack) => "expand" | undefined;
|
|
132
|
+
export declare const formatPercent: ValueFormatter;
|
|
133
|
+
/** A short day label, like "Oct 17", for a `YYYY-MM-DD` string or a time. */
|
|
134
|
+
export declare const dayLabel: (value: unknown, locale?: string) => string;
|
|
135
|
+
/**
|
|
136
|
+
* Legend content listing every config entry, for charts whose marks are not
|
|
137
|
+
* one series each. Pass it as `<ChartLegend content={configLegend(config)} />`.
|
|
138
|
+
*/
|
|
139
|
+
export declare const configLegend: (config: ChartConfig) => () => import("react").JSX.Element;
|
|
140
|
+
/** A bar shape that takes its color from the row's `fill`. */
|
|
141
|
+
export declare const rowFillShape: (props: BarShapeProps) => import("react").JSX.Element;
|
|
142
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useId, useMemo, } from 'react';
|
|
4
|
+
import { Legend, Rectangle, ResponsiveContainer, Tooltip, } from 'recharts';
|
|
5
|
+
import { cn } from './classes.js';
|
|
6
|
+
/** Theme names a `ChartConfig` entry can color, and the selector each one uses. */
|
|
7
|
+
const THEMES = { light: '', dark: '.dark' };
|
|
8
|
+
const INITIAL_DIMENSION = { width: 320, height: 200 };
|
|
9
|
+
export const chartIndicatorNames = ['dot', 'line', 'dashed'];
|
|
10
|
+
const cssKey = (key) => key.replace(/[^\w-]/g, '_');
|
|
11
|
+
/**
|
|
12
|
+
* The CSS variable a `ChartContainer` sets for one config key. Pass it as a
|
|
13
|
+
* series `fill` or `stroke`, for example `fill={chartColor("tasks")}`.
|
|
14
|
+
*/
|
|
15
|
+
export function chartColor(key) {
|
|
16
|
+
return `var(--sprout-chart-color-${cssKey(key)})`;
|
|
17
|
+
}
|
|
18
|
+
const ChartContext = createContext(null);
|
|
19
|
+
function useChart() {
|
|
20
|
+
const context = useContext(ChartContext);
|
|
21
|
+
if (!context)
|
|
22
|
+
throw new Error('useChart must be used within a <ChartContainer />');
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sizes a Recharts chart to its box and turns `config` into series colors
|
|
27
|
+
* and labels for the tooltip and legend. The box is 16:9 unless `className`
|
|
28
|
+
* or `style` gives it a height.
|
|
29
|
+
*/
|
|
30
|
+
export function ChartContainer({ id, className, children, config, initialDimension = INITIAL_DIMENSION, ...props }) {
|
|
31
|
+
const uniqueId = useId();
|
|
32
|
+
const chartId = `chart-${cssKey(id ?? uniqueId)}`;
|
|
33
|
+
const value = useMemo(() => ({ config }), [config]);
|
|
34
|
+
return (_jsx(ChartContext.Provider, { value: value, children: _jsxs("div", { "data-slot": "chart", "data-chart": chartId, className: cn('sprout-chart', className), ...props, children: [_jsx(ChartStyle, { id: chartId, config: config }), _jsx(ResponsiveContainer, { initialDimension: initialDimension, children: children })] }) }));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Writes `--sprout-chart-color-<key>` for every colored config entry, with a
|
|
38
|
+
* dark value for entries that pass `theme`. `ChartContainer` renders it.
|
|
39
|
+
*/
|
|
40
|
+
export function ChartStyle({ id, config }) {
|
|
41
|
+
const colorConfig = Object.entries(config).filter(([, item]) => item.theme ?? item.color);
|
|
42
|
+
if (!colorConfig.length)
|
|
43
|
+
return null;
|
|
44
|
+
const rules = (theme) => colorConfig
|
|
45
|
+
.map(([key, item]) => {
|
|
46
|
+
const color = item.theme?.[theme] ?? item.color;
|
|
47
|
+
return color ? ` --sprout-chart-color-${cssKey(key)}: ${color};` : null;
|
|
48
|
+
})
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.join('\n');
|
|
51
|
+
const scope = `[data-chart="${cssKey(id)}"]`;
|
|
52
|
+
const themed = colorConfig.some(([, item]) => item.theme);
|
|
53
|
+
const css = [
|
|
54
|
+
`${scope} {\n${rules('light')}\n}`,
|
|
55
|
+
themed ? `${THEMES.dark} ${scope} {\n${rules('dark')}\n}` : '',
|
|
56
|
+
themed
|
|
57
|
+
? `@media (prefers-color-scheme: dark) {\n:root:not(.light):not(.dark) ${scope} {\n${rules('dark')}\n}\n}`
|
|
58
|
+
: '',
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join('\n');
|
|
62
|
+
return _jsx("style", { dangerouslySetInnerHTML: { __html: css } });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Recharts' `Tooltip`, moving in 160ms. Pass `content={<ChartTooltipContent />}`.
|
|
66
|
+
* It stays still when reduced motion is on.
|
|
67
|
+
*/
|
|
68
|
+
export function ChartTooltip(props) {
|
|
69
|
+
return _jsx(Tooltip, { animationDuration: 160, ...props });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The tooltip card: a label, then one row per series with its indicator,
|
|
73
|
+
* config label, and value. Recharts passes `active`, `payload`, and `label`.
|
|
74
|
+
*/
|
|
75
|
+
export function ChartTooltipContent({ active, payload, className, indicator = 'dot', hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, valueFormatter = formatNumber, }) {
|
|
76
|
+
const { config } = useChart();
|
|
77
|
+
const tooltipLabel = useMemo(() => {
|
|
78
|
+
if (hideLabel || !payload?.length)
|
|
79
|
+
return null;
|
|
80
|
+
const [item] = payload;
|
|
81
|
+
const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;
|
|
82
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
83
|
+
const value = !labelKey && typeof label === 'string' ? (config[label]?.label ?? label) : itemConfig?.label;
|
|
84
|
+
if (labelFormatter)
|
|
85
|
+
return _jsx("div", { className: cn('sprout-chart-tooltip-label', labelClassName), children: labelFormatter(value, payload) });
|
|
86
|
+
if (!value)
|
|
87
|
+
return null;
|
|
88
|
+
return _jsx("div", { className: cn('sprout-chart-tooltip-label', labelClassName), children: value });
|
|
89
|
+
}, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
|
|
90
|
+
if (!active || !payload?.length)
|
|
91
|
+
return null;
|
|
92
|
+
const nestLabel = payload.length === 1 && indicator !== 'dot';
|
|
93
|
+
return (_jsxs("div", { "data-slot": "chart-tooltip", "data-indicator": indicator, className: cn('sprout-chart-tooltip', className), children: [!nestLabel ? tooltipLabel : null, _jsx("div", { className: "sprout-chart-tooltip-items", children: payload
|
|
94
|
+
.filter(item => item.type !== 'none')
|
|
95
|
+
.map((item, index) => {
|
|
96
|
+
const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;
|
|
97
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
98
|
+
const indicatorColor = color ?? item.payload?.fill ?? item.color;
|
|
99
|
+
return (_jsx("div", { "data-slot": "chart-tooltip-item", className: "sprout-chart-tooltip-item", children: formatter && item?.value !== undefined && item.name ? (formatter(item.value, item.name, item, index, item.payload)) : (_jsxs(_Fragment, { children: [itemConfig?.icon ? (_jsx(itemConfig.icon, {})) : (!hideIndicator && (_jsx("span", { "aria-hidden": "true", "data-slot": "chart-tooltip-indicator", className: "sprout-chart-tooltip-indicator", style: {
|
|
100
|
+
'--sprout-chart-indicator': indicatorColor,
|
|
101
|
+
} }))), _jsxs("div", { className: "sprout-chart-tooltip-row", "data-nested": nestLabel || undefined, children: [_jsxs("div", { className: "sprout-chart-tooltip-name", children: [nestLabel ? tooltipLabel : null, _jsx("span", { children: itemConfig?.label ?? item.name })] }), item.value != null && (_jsx("span", { className: "sprout-chart-tooltip-value", children: typeof item.value === 'number' ? valueFormatter(item.value) : String(item.value) }))] })] })) }, index));
|
|
102
|
+
}) })] }));
|
|
103
|
+
}
|
|
104
|
+
/** Recharts' `Legend`. Pass `content={<ChartLegendContent />}`. */
|
|
105
|
+
export const ChartLegend = Legend;
|
|
106
|
+
/** A centered row of swatches and config labels, one per series. */
|
|
107
|
+
export function ChartLegendContent({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey, }) {
|
|
108
|
+
const { config } = useChart();
|
|
109
|
+
if (!payload?.length)
|
|
110
|
+
return null;
|
|
111
|
+
return (_jsx("div", { "data-slot": "chart-legend", "data-align": verticalAlign === 'top' ? 'top' : 'bottom', className: cn('sprout-chart-legend', className), children: payload
|
|
112
|
+
.filter(item => item.type !== 'none')
|
|
113
|
+
.map((item, index) => {
|
|
114
|
+
const key = `${nameKey ?? item.dataKey ?? 'value'}`;
|
|
115
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
116
|
+
return (_jsxs("div", { "data-slot": "chart-legend-item", className: "sprout-chart-legend-item", children: [itemConfig?.icon && !hideIcon ? (_jsx(itemConfig.icon, {})) : (_jsx("span", { "aria-hidden": "true", className: "sprout-chart-legend-swatch", style: { backgroundColor: item.color } })), itemConfig?.label] }, index));
|
|
117
|
+
}) }));
|
|
118
|
+
}
|
|
119
|
+
function getPayloadConfigFromPayload(config, payload, key) {
|
|
120
|
+
if (typeof payload !== 'object' || payload === null)
|
|
121
|
+
return undefined;
|
|
122
|
+
const inner = 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null
|
|
123
|
+
? payload.payload
|
|
124
|
+
: undefined;
|
|
125
|
+
const outer = payload;
|
|
126
|
+
let configLabelKey = key;
|
|
127
|
+
if (typeof outer[key] === 'string')
|
|
128
|
+
configLabelKey = outer[key];
|
|
129
|
+
else if (inner && typeof inner[key] === 'string')
|
|
130
|
+
configLabelKey = inner[key];
|
|
131
|
+
return configLabelKey in config ? config[configLabelKey] : config[key];
|
|
132
|
+
}
|
|
133
|
+
export const formatNumber = value => value.toLocaleString();
|
|
134
|
+
/** The five chart colors, in order. */
|
|
135
|
+
export const chartPalette = [1, 2, 3, 4, 5].map(step => `var(--sprout-chart-${step})`);
|
|
136
|
+
/** The color for parts past the fifth, folded into one "N more" part. */
|
|
137
|
+
export const chartOtherColor = 'var(--sprout-muted)';
|
|
138
|
+
/** The palette color for a part, or the muted color past the fifth. */
|
|
139
|
+
export const partColor = (index) => chartPalette[index] ?? chartOtherColor;
|
|
140
|
+
/**
|
|
141
|
+
* One of `steps` shades of `color`, mixed with the surface so it reads in
|
|
142
|
+
* light and dark. Step 1 is lightest; the last step is `color` itself.
|
|
143
|
+
*/
|
|
144
|
+
export const chartShade = (color, step, steps = 5) => `color-mix(in oklab, ${color} ${Math.round((step / steps) * 100)}%, var(--sprout-surface))`;
|
|
145
|
+
/**
|
|
146
|
+
* Sorts rows by `value`, keeps the top `limit` as colored parts, and folds the
|
|
147
|
+
* rest into one muted "N more" part.
|
|
148
|
+
*/
|
|
149
|
+
export function foldParts(rows, category, value, limit = chartPalette.length) {
|
|
150
|
+
const parts = rows
|
|
151
|
+
.map(row => ({
|
|
152
|
+
name: String(row[category] ?? ''),
|
|
153
|
+
value: Number(row[value]) || 0,
|
|
154
|
+
}))
|
|
155
|
+
.filter(part => part.value > 0)
|
|
156
|
+
.sort((a, b) => b.value - a.value);
|
|
157
|
+
const kept = parts.slice(0, limit);
|
|
158
|
+
const rest = parts.slice(kept.length);
|
|
159
|
+
const colored = kept.map((part, index) => ({
|
|
160
|
+
...part,
|
|
161
|
+
fill: partColor(index),
|
|
162
|
+
}));
|
|
163
|
+
if (!rest.length)
|
|
164
|
+
return colored;
|
|
165
|
+
return [
|
|
166
|
+
...colored,
|
|
167
|
+
{
|
|
168
|
+
name: `${rest.length} more`,
|
|
169
|
+
value: rest.reduce((sum, part) => sum + part.value, 0),
|
|
170
|
+
fill: chartOtherColor,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
/** A config entry per part, so the tooltip and legend show part names. */
|
|
175
|
+
export const partConfig = (parts) => Object.fromEntries(parts.map(part => [part.name, { label: part.name, color: part.fill }]));
|
|
176
|
+
export const seriesConfig = (series) => Object.fromEntries(series.map(item => [item.key, { label: item.label, color: item.color }]));
|
|
177
|
+
export const tooltipCursor = { fill: 'var(--sprout-hover)' };
|
|
178
|
+
/** Axis props every ready chart shares: no tick marks, no axis line. */
|
|
179
|
+
export const quietAxis = { tickLine: false, axisLine: false };
|
|
180
|
+
export const stackOffset = (stack) => (stack === 'percent' ? 'expand' : undefined);
|
|
181
|
+
export const formatPercent = value => `${Math.round(value * 100)}%`;
|
|
182
|
+
/** A short day label, like "Oct 17", for a `YYYY-MM-DD` string or a time. */
|
|
183
|
+
export const dayLabel = (value, locale) => new Date(typeof value === 'number' || value instanceof Date ? value : `${String(value)}T00:00:00Z`).toLocaleDateString(locale, {
|
|
184
|
+
month: 'short',
|
|
185
|
+
day: 'numeric',
|
|
186
|
+
timeZone: 'UTC',
|
|
187
|
+
});
|
|
188
|
+
/**
|
|
189
|
+
* Legend content listing every config entry, for charts whose marks are not
|
|
190
|
+
* one series each. Pass it as `<ChartLegend content={configLegend(config)} />`.
|
|
191
|
+
*/
|
|
192
|
+
export const configLegend = (config) => function ConfigLegend() {
|
|
193
|
+
return (_jsx(ChartLegendContent, { payload: Object.keys(config).map(key => ({
|
|
194
|
+
value: key,
|
|
195
|
+
dataKey: key,
|
|
196
|
+
color: chartColor(key),
|
|
197
|
+
})) }));
|
|
198
|
+
};
|
|
199
|
+
/** A bar shape that takes its color from the row's `fill`. */
|
|
200
|
+
export const rowFillShape = (props) => (_jsx(Rectangle, { ...props, fill: props.payload.fill }));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ChartRow, type ChartSeries, type ChartStack, type ReadyChartProps, type UsageSeries } from '../chart-kit.js';
|
|
2
|
+
export type UsageDay = {
|
|
3
|
+
date: string;
|
|
4
|
+
} & Record<string, number | string | null>;
|
|
5
|
+
export interface UsageChartProps {
|
|
6
|
+
/** One row per UTC day (`YYYY-MM-DD`), keyed by series. */
|
|
7
|
+
days: UsageDay[];
|
|
8
|
+
series: UsageSeries[];
|
|
9
|
+
/** Names the chart for screen readers. */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Locale for day labels. Defaults to the reader's. */
|
|
12
|
+
locale?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Stacked daily bars, 256px tall. Days are rows keyed by series; a missing or
|
|
17
|
+
* null value keeps that series out of the day's tooltip. The top series gets
|
|
18
|
+
* rounded corners.
|
|
19
|
+
*/
|
|
20
|
+
export declare function UsageChart({ days, series, title, locale, className }: UsageChartProps): import("react").JSX.Element;
|
|
21
|
+
export interface CategoryChartProps extends ReadyChartProps {
|
|
22
|
+
/** One row per category, keyed by `category` and by each series key. */
|
|
23
|
+
rows: ChartRow[];
|
|
24
|
+
/** The row key whose value names each group of bars. */
|
|
25
|
+
category: string;
|
|
26
|
+
series: ChartSeries[];
|
|
27
|
+
/** Bars grow sideways, one row per category. Good for long names. */
|
|
28
|
+
horizontal?: boolean;
|
|
29
|
+
/** `stacked` piles series in one bar; `percent` stacks them to 100%. */
|
|
30
|
+
stack?: ChartStack;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Bars per category with a legend in series order, 288px tall. Negative
|
|
34
|
+
* values hang below a zero line, for profit and loss.
|
|
35
|
+
*/
|
|
36
|
+
export declare function CategoryChart({ rows, category, series, horizontal, stack, title, valueFormatter, className, }: CategoryChartProps): import("react").JSX.Element;
|
|
37
|
+
export interface WaterfallChartProps extends ReadyChartProps {
|
|
38
|
+
/** One row per step. `value` is the change that step adds or takes away. */
|
|
39
|
+
rows: ChartRow[];
|
|
40
|
+
/** The row key whose value names each step. */
|
|
41
|
+
category: string;
|
|
42
|
+
/** The row key holding each step's change. */
|
|
43
|
+
value: string;
|
|
44
|
+
/**
|
|
45
|
+
* Steps drawn as a full bar from zero, like a start or end balance. A total
|
|
46
|
+
* with a value starts the running total there; one without shows the
|
|
47
|
+
* running total so far.
|
|
48
|
+
*/
|
|
49
|
+
totals?: string[];
|
|
50
|
+
/** Legend labels for rises, falls, and totals. */
|
|
51
|
+
labels?: {
|
|
52
|
+
increase?: string;
|
|
53
|
+
decrease?: string;
|
|
54
|
+
total?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A running total, step by step: green bars rise, red bars fall, and blue
|
|
59
|
+
* totals stand on zero. 288px tall.
|
|
60
|
+
*/
|
|
61
|
+
export declare function WaterfallChart({ rows, category, value, totals, labels, title, valueFormatter, className, }: WaterfallChartProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { Bar, BarChart, CartesianGrid, ReferenceLine, XAxis, YAxis } from 'recharts';
|
|
5
|
+
import { cn } from '../classes.js';
|
|
6
|
+
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, chartColor, configLegend, dayLabel, formatNumber, formatPercent, quietAxis, rowFillShape, seriesConfig, stackOffset, tooltipCursor, } from '../chart-kit.js';
|
|
7
|
+
/**
|
|
8
|
+
* Stacked daily bars, 256px tall. Days are rows keyed by series; a missing or
|
|
9
|
+
* null value keeps that series out of the day's tooltip. The top series gets
|
|
10
|
+
* rounded corners.
|
|
11
|
+
*/
|
|
12
|
+
export function UsageChart({ days, series, title = 'Usage by day', locale, className }) {
|
|
13
|
+
const config = useMemo(() => seriesConfig(series), [series]);
|
|
14
|
+
const usageDay = (date) => dayLabel(date, locale);
|
|
15
|
+
return (_jsx(ChartContainer, { "data-variant": "usage", className: cn('sprout-chart-usage', className), config: config, children: _jsxs(BarChart, { data: days, accessibilityLayer: true, title: title, barCategoryGap: "20%", children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "date", tickFormatter: usageDay, ...quietAxis, minTickGap: 24 }), _jsx(YAxis, { width: 40, ...quietAxis, allowDecimals: false }), _jsx(ChartTooltip, { cursor: tooltipCursor, content: _jsx(ChartTooltipContent, { labelFormatter: usageDay }) }), series.map((item, index) => (_jsx(Bar, { dataKey: item.key, stackId: "usage", fill: chartColor(item.key), radius: index === series.length - 1 ? [3, 3, 0, 0] : 0, isAnimationActive: false }, item.key)))] }) }));
|
|
16
|
+
}
|
|
17
|
+
const hasNegative = (rows, series) => rows.some(row => series.some(item => Number(row[item.key]) < 0));
|
|
18
|
+
/**
|
|
19
|
+
* Bars per category with a legend in series order, 288px tall. Negative
|
|
20
|
+
* values hang below a zero line, for profit and loss.
|
|
21
|
+
*/
|
|
22
|
+
export function CategoryChart({ rows, category, series, horizontal = false, stack = 'none', title, valueFormatter = formatNumber, className, }) {
|
|
23
|
+
const config = useMemo(() => seriesConfig(series), [series]);
|
|
24
|
+
const name = title ?? `${series.map(item => item.label).join(', ')} by ${category}`;
|
|
25
|
+
const stacked = stack !== 'none';
|
|
26
|
+
const end = horizontal ? [0, 3, 3, 0] : [3, 3, 0, 0];
|
|
27
|
+
const tick = stack === 'percent' ? formatPercent : valueFormatter;
|
|
28
|
+
const categoryAxis = { dataKey: category, ...quietAxis };
|
|
29
|
+
const valueAxis = { ...quietAxis, tickFormatter: tick };
|
|
30
|
+
return (_jsx(ChartContainer, { "data-variant": "category", className: cn('sprout-chart-category', className), config: config, children: _jsxs(BarChart, { data: rows, accessibilityLayer: true, title: name, layout: horizontal ? 'vertical' : 'horizontal', stackOffset: stackOffset(stack), barCategoryGap: "24%", children: [_jsx(CartesianGrid, { vertical: horizontal, horizontal: !horizontal }), horizontal ? (_jsxs(_Fragment, { children: [_jsx(XAxis, { type: "number", ...valueAxis }), _jsx(YAxis, { type: "category", width: "auto", ...categoryAxis, interval: 0 })] })) : (_jsxs(_Fragment, { children: [_jsx(XAxis, { ...categoryAxis, minTickGap: 16 }), _jsx(YAxis, { width: "auto", ...valueAxis, allowDecimals: stack === 'percent' })] })), hasNegative(rows, series) ? (_jsx(ReferenceLine, { ...(horizontal ? { x: 0 } : { y: 0 }), stroke: "var(--sprout-border-strong)" })) : null, _jsx(ChartTooltip, { cursor: tooltipCursor, content: _jsx(ChartTooltipContent, { valueFormatter: valueFormatter }) }), _jsx(ChartLegend, { itemSorter: null, content: _jsx(ChartLegendContent, {}) }), series.map((item, index) => (_jsx(Bar, { dataKey: item.key, stackId: stacked ? 'category' : undefined, fill: chartColor(item.key), radius: !stacked || index === series.length - 1 ? end : 0, isAnimationActive: false }, item.key)))] }) }));
|
|
31
|
+
}
|
|
32
|
+
const waterfallColors = {
|
|
33
|
+
increase: 'var(--sprout-green)',
|
|
34
|
+
decrease: 'var(--sprout-red)',
|
|
35
|
+
total: 'var(--sprout-blue)',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A running total, step by step: green bars rise, red bars fall, and blue
|
|
39
|
+
* totals stand on zero. 288px tall.
|
|
40
|
+
*/
|
|
41
|
+
export function WaterfallChart({ rows, category, value, totals = [], labels, title = 'Running total by step', valueFormatter = formatNumber, className, }) {
|
|
42
|
+
const steps = useMemo(() => {
|
|
43
|
+
let running = 0;
|
|
44
|
+
return rows.map(row => {
|
|
45
|
+
const name = String(row[category] ?? '');
|
|
46
|
+
const raw = row[value];
|
|
47
|
+
const change = Number(raw) || 0;
|
|
48
|
+
if (totals.includes(name)) {
|
|
49
|
+
if (raw != null && raw !== '')
|
|
50
|
+
running = change;
|
|
51
|
+
return {
|
|
52
|
+
name,
|
|
53
|
+
kind: 'total',
|
|
54
|
+
range: [0, running],
|
|
55
|
+
amount: running,
|
|
56
|
+
fill: waterfallColors.total,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const from = running;
|
|
60
|
+
running += change;
|
|
61
|
+
const kind = change < 0 ? 'decrease' : 'increase';
|
|
62
|
+
return {
|
|
63
|
+
name,
|
|
64
|
+
kind,
|
|
65
|
+
range: [from, running],
|
|
66
|
+
amount: change,
|
|
67
|
+
fill: waterfallColors[kind],
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}, [rows, category, value, totals]);
|
|
71
|
+
const config = useMemo(() => ({
|
|
72
|
+
increase: {
|
|
73
|
+
label: labels?.increase ?? 'Increase',
|
|
74
|
+
color: waterfallColors.increase,
|
|
75
|
+
},
|
|
76
|
+
decrease: {
|
|
77
|
+
label: labels?.decrease ?? 'Decrease',
|
|
78
|
+
color: waterfallColors.decrease,
|
|
79
|
+
},
|
|
80
|
+
total: { label: labels?.total ?? 'Total', color: waterfallColors.total },
|
|
81
|
+
}), [labels]);
|
|
82
|
+
return (_jsx(ChartContainer, { "data-variant": "waterfall", className: cn('sprout-chart-waterfall', className), config: config, children: _jsxs(BarChart, { data: steps, accessibilityLayer: true, title: title, barCategoryGap: "16%", children: [_jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: "name", ...quietAxis, minTickGap: 8 }), _jsx(YAxis, { width: "auto", ...quietAxis, tickFormatter: valueFormatter }), _jsx(ReferenceLine, { y: 0, stroke: "var(--sprout-border-strong)" }), _jsx(ChartLegend, { content: configLegend(config) }), _jsx(ChartTooltip, { cursor: tooltipCursor, content: props => (_jsx(ChartTooltipContent, { active: props.active, label: props.label, nameKey: "kind", valueFormatter: valueFormatter, payload: props.payload?.map(item => ({
|
|
83
|
+
...item,
|
|
84
|
+
value: item.payload.amount,
|
|
85
|
+
})) })) }), _jsx(Bar, { dataKey: "range", radius: 3, shape: rowFillShape, isAnimationActive: false })] }) }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ChartRow, type ChartSeries, type ChartStack, type ReadyChartProps, type ValueFormatter } from '../chart-kit.js';
|
|
2
|
+
export type TrendCurve = 'smooth' | 'linear' | 'step';
|
|
3
|
+
/** A series for `ComboChart`: drawn as bars or a line, on the left or right axis. */
|
|
4
|
+
export type ComboSeries = ChartSeries & {
|
|
5
|
+
kind?: 'bar' | 'line';
|
|
6
|
+
axis?: 'left' | 'right';
|
|
7
|
+
};
|
|
8
|
+
interface TrendBaseProps extends ReadyChartProps {
|
|
9
|
+
/** One row per point on the x axis, keyed by `category` and each series. */
|
|
10
|
+
rows: ChartRow[];
|
|
11
|
+
/** The row key for the x axis, such as a date or month. */
|
|
12
|
+
category: string;
|
|
13
|
+
series: ChartSeries[];
|
|
14
|
+
/** `smooth` curves, `linear` straight segments, or `step` stairs. */
|
|
15
|
+
curve?: TrendCurve;
|
|
16
|
+
/** Draws a dot at every point. */
|
|
17
|
+
dots?: boolean;
|
|
18
|
+
/** Formats x axis ticks and the tooltip label, for example dates. */
|
|
19
|
+
categoryFormatter?: (value: unknown) => string;
|
|
20
|
+
}
|
|
21
|
+
export type TrendChartProps = TrendBaseProps;
|
|
22
|
+
/** Lines over time, one per series, 288px tall. A legend shows past one series. */
|
|
23
|
+
export declare function TrendChart({ curve, dots, ...props }: TrendChartProps): import("react").JSX.Element;
|
|
24
|
+
export interface AreaTrendChartProps extends TrendBaseProps {
|
|
25
|
+
/** `stacked` piles series; `percent` stacks them to 100%. */
|
|
26
|
+
stack?: ChartStack;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Filled areas over time with a soft gradient, 288px tall. Stack them to show
|
|
30
|
+
* a total, or to 100% to show shares.
|
|
31
|
+
*/
|
|
32
|
+
export declare function AreaTrendChart({ curve, dots, stack, ...props }: AreaTrendChartProps): import("react").JSX.Element;
|
|
33
|
+
export interface ComboChartProps extends ReadyChartProps {
|
|
34
|
+
/** One row per category, keyed by `category` and each series. */
|
|
35
|
+
rows: ChartRow[];
|
|
36
|
+
category: string;
|
|
37
|
+
/** Bars by default. Set `kind: "line"` and `axis: "right"` for a rate. */
|
|
38
|
+
series: ComboSeries[];
|
|
39
|
+
/** Formats the right axis and its series in the tooltip, such as percent. */
|
|
40
|
+
rightValueFormatter?: ValueFormatter;
|
|
41
|
+
categoryFormatter?: (value: unknown) => string;
|
|
42
|
+
/** Stacks the bar series. */
|
|
43
|
+
stacked?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Bars and lines on one chart, like revenue with its growth rate, 288px tall.
|
|
47
|
+
* Series on `axis: "right"` get their own scale.
|
|
48
|
+
*/
|
|
49
|
+
export declare function ComboChart({ stacked, ...props }: ComboChartProps): import("react").JSX.Element;
|
|
50
|
+
export interface SparklineProps {
|
|
51
|
+
/** The points, oldest first. */
|
|
52
|
+
values: number[];
|
|
53
|
+
/** Names the trend for screen readers, such as "MRR, last 12 weeks". */
|
|
54
|
+
title: string;
|
|
55
|
+
/** Any CSS color. Defaults to `--sprout-chart-1`. */
|
|
56
|
+
color?: string;
|
|
57
|
+
/** Fills under the line. */
|
|
58
|
+
area?: boolean;
|
|
59
|
+
valueFormatter?: ValueFormatter;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A tiny trend line for a stat tile, 40px tall and as wide as its box. No
|
|
64
|
+
* axes; hover shows the value.
|
|
65
|
+
*/
|
|
66
|
+
export declare function Sparkline({ values, title, color, area, valueFormatter, className, }: SparklineProps): import("react").JSX.Element;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useMemo } from 'react';
|
|
4
|
+
import { Area, Bar, CartesianGrid, ComposedChart, Line, XAxis, YAxis } from 'recharts';
|
|
5
|
+
import { cn } from '../classes.js';
|
|
6
|
+
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, chartColor, formatNumber, formatPercent, quietAxis, seriesConfig, stackOffset, tooltipCursor, } from '../chart-kit.js';
|
|
7
|
+
const curveType = {
|
|
8
|
+
smooth: 'monotone',
|
|
9
|
+
linear: 'linear',
|
|
10
|
+
step: 'step',
|
|
11
|
+
};
|
|
12
|
+
function SeriesPlot({ variant, rows, category, series, mark, curve, dots, stack, title, valueFormatter = formatNumber, rightValueFormatter = valueFormatter, categoryFormatter, className, }) {
|
|
13
|
+
const config = useMemo(() => seriesConfig(series), [series]);
|
|
14
|
+
const gradient = `sprout-area-${useId().replace(/:/g, '')}`;
|
|
15
|
+
const stacked = stack !== 'none';
|
|
16
|
+
const right = series.some(item => item.axis === 'right');
|
|
17
|
+
const anyBar = series.some(item => mark(item) === 'bar');
|
|
18
|
+
const tick = stack === 'percent' ? formatPercent : valueFormatter;
|
|
19
|
+
const format = (key) => series.find(item => item.key === key)?.axis === 'right' ? rightValueFormatter : valueFormatter;
|
|
20
|
+
const name = title ?? `${series.map(item => item.label).join(', ')} by ${category}`;
|
|
21
|
+
const type = curveType[curve];
|
|
22
|
+
return (_jsx(ChartContainer, { "data-variant": variant, className: cn(`sprout-chart-${variant}`, className), config: config, children: _jsxs(ComposedChart, { data: rows, accessibilityLayer: true, title: name, stackOffset: stackOffset(stack), barCategoryGap: "24%", margin: { top: 4, right: right ? 0 : 12, bottom: 0, left: 0 }, children: [_jsx("defs", { children: series.map(item => (_jsxs("linearGradient", { id: `${gradient}-${item.key}`, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "5%", stopColor: chartColor(item.key), stopOpacity: 0.4 }), _jsx("stop", { offset: "95%", stopColor: chartColor(item.key), stopOpacity: 0.04 })] }, item.key))) }), _jsx(CartesianGrid, { vertical: false }), _jsx(XAxis, { dataKey: category, tickFormatter: categoryFormatter, ...quietAxis, minTickGap: 16 }), _jsx(YAxis, { yAxisId: "left", width: "auto", tickFormatter: tick, ...quietAxis }), right ? (_jsx(YAxis, { yAxisId: "right", orientation: "right", width: "auto", tickFormatter: rightValueFormatter, ...quietAxis })) : null, _jsx(ChartTooltip, { cursor: anyBar ? tooltipCursor : true, content: props => (_jsx(ChartTooltipContent, { active: props.active, label: props.label, indicator: anyBar ? 'dot' : 'line', labelFormatter: categoryFormatter
|
|
23
|
+
? (_, payload) => categoryFormatter(payload[0]?.payload?.[category])
|
|
24
|
+
: undefined, payload: props.payload?.map(item => ({
|
|
25
|
+
...item,
|
|
26
|
+
value: typeof item.value === 'number' ? format(String(item.dataKey))(item.value) : item.value,
|
|
27
|
+
})) })) }), series.length > 1 ? (_jsx(ChartLegend, { verticalAlign: "top", itemSorter: null, content: _jsx(ChartLegendContent, {}) })) : null, series.map((item, index) => {
|
|
28
|
+
const color = chartColor(item.key);
|
|
29
|
+
const shared = {
|
|
30
|
+
dataKey: item.key,
|
|
31
|
+
yAxisId: item.axis ?? 'left',
|
|
32
|
+
stackId: stacked ? 'series' : undefined,
|
|
33
|
+
isAnimationActive: false,
|
|
34
|
+
};
|
|
35
|
+
const kind = mark(item);
|
|
36
|
+
if (kind === 'bar')
|
|
37
|
+
return (_jsx(Bar, { ...shared, fill: color, radius: !stacked || index === series.length - 1 ? [3, 3, 0, 0] : 0 }, item.key));
|
|
38
|
+
if (kind === 'area')
|
|
39
|
+
return (_jsx(Area, { ...shared, type: type, stroke: color, strokeWidth: 2, fill: `url(#${gradient}-${item.key})`, dot: dots ? { r: 3, fill: color } : false, activeDot: { r: 4 } }, item.key));
|
|
40
|
+
return (_jsx(Line, { ...shared, type: type, stroke: color, strokeWidth: 2, dot: dots ? { r: 3, fill: color } : false, activeDot: { r: 4 } }, item.key));
|
|
41
|
+
})] }) }));
|
|
42
|
+
}
|
|
43
|
+
/** Lines over time, one per series, 288px tall. A legend shows past one series. */
|
|
44
|
+
export function TrendChart({ curve = 'smooth', dots = false, ...props }) {
|
|
45
|
+
return _jsx(SeriesPlot, { variant: "trend", mark: () => 'line', curve: curve, dots: dots, stack: "none", ...props });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Filled areas over time with a soft gradient, 288px tall. Stack them to show
|
|
49
|
+
* a total, or to 100% to show shares.
|
|
50
|
+
*/
|
|
51
|
+
export function AreaTrendChart({ curve = 'smooth', dots = false, stack = 'none', ...props }) {
|
|
52
|
+
return _jsx(SeriesPlot, { variant: "area", mark: () => 'area', curve: curve, dots: dots, stack: stack, ...props });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Bars and lines on one chart, like revenue with its growth rate, 288px tall.
|
|
56
|
+
* Series on `axis: "right"` get their own scale.
|
|
57
|
+
*/
|
|
58
|
+
export function ComboChart({ stacked = false, ...props }) {
|
|
59
|
+
return (_jsx(SeriesPlot, { variant: "combo", mark: item => item.kind ?? 'bar', curve: "smooth", dots: true, stack: stacked ? 'stacked' : 'none', ...props }));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A tiny trend line for a stat tile, 40px tall and as wide as its box. No
|
|
63
|
+
* axes; hover shows the value.
|
|
64
|
+
*/
|
|
65
|
+
export function Sparkline({ values, title, color = 'var(--sprout-chart-1)', area = false, valueFormatter = formatNumber, className, }) {
|
|
66
|
+
const rows = useMemo(() => values.map((value, index) => ({ index, value })), [values]);
|
|
67
|
+
const config = useMemo(() => ({ value: { label: title, color } }), [title, color]);
|
|
68
|
+
const gradient = `sprout-spark-${useId().replace(/:/g, '')}`;
|
|
69
|
+
return (_jsx(ChartContainer, { "data-variant": "sparkline", className: cn('sprout-chart-sparkline', className), config: config, initialDimension: { width: 120, height: 40 }, children: _jsxs(ComposedChart, { data: rows, accessibilityLayer: true, title: title, margin: { top: 4, right: 4, bottom: 4, left: 4 }, children: [_jsx("defs", { children: _jsxs("linearGradient", { id: gradient, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "5%", stopColor: chartColor('value'), stopOpacity: 0.3 }), _jsx("stop", { offset: "95%", stopColor: chartColor('value'), stopOpacity: 0 })] }) }), _jsx(YAxis, { hide: true, domain: ['dataMin', 'dataMax'] }), _jsx(ChartTooltip, { cursor: false, content: _jsx(ChartTooltipContent, { hideLabel: true, indicator: "line", valueFormatter: valueFormatter }) }), _jsx(Area, { dataKey: "value", type: "monotone", stroke: chartColor('value'), strokeWidth: 1.5, fill: area ? `url(#${gradient})` : 'none', dot: false, activeDot: { r: 3 }, isAnimationActive: false })] }) }));
|
|
70
|
+
}
|