@momentumworld/ui-react 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/chunk-2HQPCV2L.js +895 -0
  2. package/dist/chunk-2HQPCV2L.js.map +1 -0
  3. package/dist/form.d.ts +175 -0
  4. package/dist/form.js +356 -0
  5. package/dist/form.js.map +1 -0
  6. package/dist/index.d.ts +2162 -0
  7. package/dist/index.js +11640 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/input-JCZs61MO.d.ts +24 -0
  10. package/package.json +90 -0
  11. package/src/components/form/checkbox-group-field.tsx +101 -0
  12. package/src/components/form/date-field.tsx +79 -0
  13. package/src/components/form/date-range-field.tsx +106 -0
  14. package/src/components/form/form-context.ts +10 -0
  15. package/src/components/form/form.tsx +54 -0
  16. package/src/components/form/number-field.tsx +69 -0
  17. package/src/components/form/select-field.tsx +76 -0
  18. package/src/components/form/submit-button.tsx +28 -0
  19. package/src/components/form/text-field.tsx +107 -0
  20. package/src/components/layout/dashboard-header.tsx +54 -0
  21. package/src/components/layout/dashboard-panel.tsx +34 -0
  22. package/src/components/layout/dashboard-topbar.tsx +221 -0
  23. package/src/components/theme-provider.tsx +403 -0
  24. package/src/components/ui/accordion.tsx +69 -0
  25. package/src/components/ui/alert-dialog.tsx +169 -0
  26. package/src/components/ui/alert.tsx +80 -0
  27. package/src/components/ui/animated-counter.tsx +109 -0
  28. package/src/components/ui/animated-theme-toggler.tsx +265 -0
  29. package/src/components/ui/app-store-buttons.tsx +182 -0
  30. package/src/components/ui/aspect-ratio.tsx +23 -0
  31. package/src/components/ui/aurora-orb.tsx +163 -0
  32. package/src/components/ui/aurora-text.tsx +76 -0
  33. package/src/components/ui/autocomplete.tsx +296 -0
  34. package/src/components/ui/avatar-group.tsx +150 -0
  35. package/src/components/ui/avatar.tsx +285 -0
  36. package/src/components/ui/badge-group.tsx +160 -0
  37. package/src/components/ui/badge.tsx +172 -0
  38. package/src/components/ui/breadcrumb.tsx +112 -0
  39. package/src/components/ui/button.tsx +77 -0
  40. package/src/components/ui/calendar.tsx +137 -0
  41. package/src/components/ui/card.tsx +244 -0
  42. package/src/components/ui/carousel.tsx +258 -0
  43. package/src/components/ui/chart.tsx +379 -0
  44. package/src/components/ui/chat-divider.tsx +67 -0
  45. package/src/components/ui/chat.tsx +494 -0
  46. package/src/components/ui/checkbox-group.tsx +16 -0
  47. package/src/components/ui/checkbox.tsx +82 -0
  48. package/src/components/ui/collapsible.tsx +45 -0
  49. package/src/components/ui/color-picker.tsx +389 -0
  50. package/src/components/ui/combobox.tsx +411 -0
  51. package/src/components/ui/command.tsx +264 -0
  52. package/src/components/ui/confetti.tsx +196 -0
  53. package/src/components/ui/context-menu.tsx +271 -0
  54. package/src/components/ui/context-meter.tsx +134 -0
  55. package/src/components/ui/credit-card.tsx +214 -0
  56. package/src/components/ui/data-table-paged.tsx +163 -0
  57. package/src/components/ui/date-picker.tsx +154 -0
  58. package/src/components/ui/dialog.tsx +196 -0
  59. package/src/components/ui/drawer.tsx +135 -0
  60. package/src/components/ui/empty.tsx +127 -0
  61. package/src/components/ui/fab.tsx +45 -0
  62. package/src/components/ui/featured-icon.tsx +149 -0
  63. package/src/components/ui/field.tsx +88 -0
  64. package/src/components/ui/fieldset.tsx +29 -0
  65. package/src/components/ui/form.tsx +17 -0
  66. package/src/components/ui/frame.tsx +82 -0
  67. package/src/components/ui/generic-empty.tsx +142 -0
  68. package/src/components/ui/group.tsx +97 -0
  69. package/src/components/ui/help-tip.tsx +94 -0
  70. package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
  71. package/src/components/ui/input-group.tsx +102 -0
  72. package/src/components/ui/input-otp.tsx +96 -0
  73. package/src/components/ui/input.tsx +66 -0
  74. package/src/components/ui/item.tsx +198 -0
  75. package/src/components/ui/kbd.tsx +30 -0
  76. package/src/components/ui/label.tsx +28 -0
  77. package/src/components/ui/markdown.tsx +188 -0
  78. package/src/components/ui/menu.tsx +312 -0
  79. package/src/components/ui/menubar.tsx +93 -0
  80. package/src/components/ui/meter.tsx +67 -0
  81. package/src/components/ui/multi-select.tsx +308 -0
  82. package/src/components/ui/navigation-menu.tsx +143 -0
  83. package/src/components/ui/number-field.tsx +160 -0
  84. package/src/components/ui/pagination-controls.tsx +74 -0
  85. package/src/components/ui/pagination.tsx +149 -0
  86. package/src/components/ui/pipeline.tsx +102 -0
  87. package/src/components/ui/popover.tsx +119 -0
  88. package/src/components/ui/preview-card.tsx +55 -0
  89. package/src/components/ui/progress.tsx +289 -0
  90. package/src/components/ui/qr-code.tsx +150 -0
  91. package/src/components/ui/radio-group.tsx +103 -0
  92. package/src/components/ui/resizable.tsx +56 -0
  93. package/src/components/ui/scroll-area.tsx +90 -0
  94. package/src/components/ui/scroller.tsx +38 -0
  95. package/src/components/ui/section-header.tsx +118 -0
  96. package/src/components/ui/segmented-control.tsx +83 -0
  97. package/src/components/ui/select.tsx +181 -0
  98. package/src/components/ui/separator.tsx +23 -0
  99. package/src/components/ui/sheet.tsx +224 -0
  100. package/src/components/ui/sidebar.tsx +774 -0
  101. package/src/components/ui/skeleton.tsx +16 -0
  102. package/src/components/ui/slider.tsx +108 -0
  103. package/src/components/ui/smooth-scroll.tsx +206 -0
  104. package/src/components/ui/social-button.tsx +247 -0
  105. package/src/components/ui/spinner-on-demand.tsx +32 -0
  106. package/src/components/ui/spinner.tsx +18 -0
  107. package/src/components/ui/stat.tsx +187 -0
  108. package/src/components/ui/stepper.tsx +167 -0
  109. package/src/components/ui/switch.tsx +56 -0
  110. package/src/components/ui/table.tsx +126 -0
  111. package/src/components/ui/tabs.tsx +90 -0
  112. package/src/components/ui/tag.tsx +242 -0
  113. package/src/components/ui/target-countdown.tsx +46 -0
  114. package/src/components/ui/text-editor.tsx +313 -0
  115. package/src/components/ui/textarea.tsx +51 -0
  116. package/src/components/ui/thinking-orb.tsx +6 -0
  117. package/src/components/ui/time-picker.tsx +308 -0
  118. package/src/components/ui/timeline.tsx +116 -0
  119. package/src/components/ui/toast.tsx +268 -0
  120. package/src/components/ui/toggle-group.tsx +101 -0
  121. package/src/components/ui/toggle.tsx +45 -0
  122. package/src/components/ui/toolbar.tsx +89 -0
  123. package/src/components/ui/tooltip.tsx +102 -0
  124. package/src/components/ui/vertical-scroll-fader.tsx +250 -0
  125. package/src/components/ui/video-player.tsx +275 -0
  126. package/src/components/upload/avatar-upload-base.tsx +131 -0
  127. package/src/components/upload/image-upload-base.tsx +112 -0
  128. package/src/form.ts +17 -0
  129. package/src/index.ts +143 -0
  130. package/src/lib/hooks/use-callback-ref.ts +15 -0
  131. package/src/lib/hooks/use-first-render.ts +11 -0
  132. package/src/lib/hooks/use-hover.ts +53 -0
  133. package/src/lib/hooks/use-is-tab-active.ts +17 -0
  134. package/src/lib/hooks/use-media-query.ts +164 -0
  135. package/src/lib/utils/css.ts +6 -0
  136. package/src/styles.css +329 -0
  137. package/src/types/helpers.ts +24 -0
  138. package/src/types/react.d.ts +7 -0
@@ -0,0 +1,258 @@
1
+ "use client";
2
+
3
+ import useEmblaCarousel, {
4
+ type UseEmblaCarouselType,
5
+ } from "embla-carousel-react";
6
+ import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
7
+ import * as React from "react";
8
+
9
+ import { cn } from "../../lib/utils/css";
10
+ import { Button } from "./button";
11
+
12
+ type CarouselApi = UseEmblaCarouselType[1];
13
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
14
+ type CarouselOptions = UseCarouselParameters[0];
15
+ type CarouselPlugin = UseCarouselParameters[1];
16
+
17
+ type CarouselProps = {
18
+ opts?: CarouselOptions;
19
+ plugins?: CarouselPlugin;
20
+ orientation?: "horizontal" | "vertical";
21
+ setApi?: (api: CarouselApi) => void;
22
+ /** Fades the leading/trailing edges of the viewport for a soft overflow. */
23
+ fade?: boolean;
24
+ };
25
+
26
+ type CarouselContextProps = {
27
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
28
+ api: ReturnType<typeof useEmblaCarousel>[1];
29
+ scrollPrev: () => void;
30
+ scrollNext: () => void;
31
+ canScrollPrev: boolean;
32
+ canScrollNext: boolean;
33
+ } & CarouselProps;
34
+
35
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null);
36
+
37
+ function useCarousel() {
38
+ const context = React.useContext(CarouselContext);
39
+
40
+ if (!context) {
41
+ throw new Error("useCarousel must be used within a <Carousel />");
42
+ }
43
+
44
+ return context;
45
+ }
46
+
47
+ function Carousel({
48
+ orientation = "horizontal",
49
+ opts,
50
+ setApi,
51
+ plugins,
52
+ fade = false,
53
+ className,
54
+ children,
55
+ ...props
56
+ }: React.ComponentProps<"div"> & CarouselProps) {
57
+ const [carouselRef, api] = useEmblaCarousel(
58
+ {
59
+ ...opts,
60
+ axis: orientation === "horizontal" ? "x" : "y",
61
+ },
62
+ plugins,
63
+ );
64
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
65
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
66
+
67
+ const onSelect = React.useCallback((api: CarouselApi) => {
68
+ if (!api) return;
69
+ setCanScrollPrev(api.canScrollPrev());
70
+ setCanScrollNext(api.canScrollNext());
71
+ }, []);
72
+
73
+ const scrollPrev = React.useCallback(() => {
74
+ api?.scrollPrev();
75
+ }, [api]);
76
+
77
+ const scrollNext = React.useCallback(() => {
78
+ api?.scrollNext();
79
+ }, [api]);
80
+
81
+ const handleKeyDown = React.useCallback(
82
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
83
+ if (event.key === "ArrowLeft") {
84
+ event.preventDefault();
85
+ scrollPrev();
86
+ } else if (event.key === "ArrowRight") {
87
+ event.preventDefault();
88
+ scrollNext();
89
+ }
90
+ },
91
+ [scrollPrev, scrollNext],
92
+ );
93
+
94
+ React.useEffect(() => {
95
+ if (!api || !setApi) return;
96
+ setApi(api);
97
+ }, [api, setApi]);
98
+
99
+ React.useEffect(() => {
100
+ if (!api) return;
101
+ onSelect(api);
102
+ api.on("reInit", onSelect);
103
+ api.on("select", onSelect);
104
+
105
+ return () => {
106
+ api?.off("select", onSelect);
107
+ };
108
+ }, [api, onSelect]);
109
+
110
+ return (
111
+ <CarouselContext.Provider
112
+ value={{
113
+ carouselRef,
114
+ api: api,
115
+ opts,
116
+ orientation:
117
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
118
+ scrollPrev,
119
+ scrollNext,
120
+ canScrollPrev,
121
+ canScrollNext,
122
+ fade,
123
+ }}
124
+ >
125
+ {/* biome-ignore lint/a11y/useSemanticElements: carousel region with keyboard support */}
126
+ <div
127
+ aria-roledescription="carousel"
128
+ className={cn("relative", className)}
129
+ data-slot="carousel"
130
+ onKeyDownCapture={handleKeyDown}
131
+ role="region"
132
+ {...props}
133
+ >
134
+ {children}
135
+ </div>
136
+ </CarouselContext.Provider>
137
+ );
138
+ }
139
+
140
+ function CarouselContent({
141
+ className,
142
+ ...props
143
+ }: React.ComponentProps<"div">) {
144
+ const { carouselRef, orientation, fade } = useCarousel();
145
+
146
+ const maskImage = fade
147
+ ? orientation === "horizontal"
148
+ ? "linear-gradient(to right, transparent 0, #000 var(--carousel-fade), #000 calc(100% - var(--carousel-fade)), transparent 100%)"
149
+ : "linear-gradient(to bottom, transparent 0, #000 var(--carousel-fade), #000 calc(100% - var(--carousel-fade)), transparent 100%)"
150
+ : undefined;
151
+
152
+ return (
153
+ <div
154
+ className="overflow-hidden [--carousel-fade:--spacing(8)]"
155
+ data-slot="carousel-content"
156
+ ref={carouselRef}
157
+ style={maskImage ? { maskImage, WebkitMaskImage: maskImage } : undefined}
158
+ >
159
+ <div
160
+ className={cn(
161
+ "flex",
162
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
163
+ className,
164
+ )}
165
+ {...props}
166
+ />
167
+ </div>
168
+ );
169
+ }
170
+
171
+ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
172
+ const { orientation } = useCarousel();
173
+
174
+ return (
175
+ // biome-ignore lint/a11y/useSemanticElements: embla slide
176
+ <div
177
+ aria-roledescription="slide"
178
+ className={cn(
179
+ "min-w-0 shrink-0 grow-0 basis-full",
180
+ orientation === "horizontal" ? "pl-4" : "pt-4",
181
+ className,
182
+ )}
183
+ data-slot="carousel-item"
184
+ role="group"
185
+ {...props}
186
+ />
187
+ );
188
+ }
189
+
190
+ function CarouselPrevious({
191
+ className,
192
+ variant = "outline",
193
+ size = "icon",
194
+ ...props
195
+ }: React.ComponentProps<typeof Button>) {
196
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
197
+
198
+ return (
199
+ <Button
200
+ className={cn(
201
+ "absolute size-8 rounded-full before:rounded-full",
202
+ orientation === "horizontal"
203
+ ? "-left-12 -translate-y-1/2 top-1/2"
204
+ : "-top-12 -translate-x-1/2 left-1/2 rotate-90",
205
+ className,
206
+ )}
207
+ data-slot="carousel-previous"
208
+ disabled={!canScrollPrev}
209
+ onClick={scrollPrev}
210
+ size={size}
211
+ variant={variant}
212
+ {...props}
213
+ >
214
+ <ArrowLeftIcon />
215
+ <span className="sr-only">Previous slide</span>
216
+ </Button>
217
+ );
218
+ }
219
+
220
+ function CarouselNext({
221
+ className,
222
+ variant = "outline",
223
+ size = "icon",
224
+ ...props
225
+ }: React.ComponentProps<typeof Button>) {
226
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
227
+
228
+ return (
229
+ <Button
230
+ className={cn(
231
+ "absolute size-8 rounded-full before:rounded-full",
232
+ orientation === "horizontal"
233
+ ? "-right-12 -translate-y-1/2 top-1/2"
234
+ : "-bottom-12 -translate-x-1/2 left-1/2 rotate-90",
235
+ className,
236
+ )}
237
+ data-slot="carousel-next"
238
+ disabled={!canScrollNext}
239
+ onClick={scrollNext}
240
+ size={size}
241
+ variant={variant}
242
+ {...props}
243
+ >
244
+ <ArrowRightIcon />
245
+ <span className="sr-only">Next slide</span>
246
+ </Button>
247
+ );
248
+ }
249
+
250
+ export {
251
+ type CarouselApi,
252
+ Carousel,
253
+ CarouselContent,
254
+ CarouselItem,
255
+ CarouselPrevious,
256
+ CarouselNext,
257
+ useCarousel,
258
+ };
@@ -0,0 +1,379 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as RechartsPrimitive from "recharts";
5
+
6
+ import { cn } from "../../lib/utils/css";
7
+
8
+ // Theme selectors follow the design system's `data-theme` attribute.
9
+ const THEMES = { light: "", dark: '[data-theme="dark"]' } as const;
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode;
14
+ icon?: React.ComponentType;
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ );
19
+ };
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig;
23
+ };
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null);
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext);
29
+
30
+ if (!context) {
31
+ throw new Error("useChart must be used within a <ChartContainer />");
32
+ }
33
+
34
+ return context;
35
+ }
36
+
37
+ function ChartContainer({
38
+ id,
39
+ className,
40
+ children,
41
+ config,
42
+ ...props
43
+ }: React.ComponentProps<"div"> & {
44
+ config: ChartConfig;
45
+ children: React.ComponentProps<
46
+ typeof RechartsPrimitive.ResponsiveContainer
47
+ >["children"];
48
+ }) {
49
+ const uniqueId = React.useId();
50
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
51
+
52
+ return (
53
+ <ChartContext.Provider value={{ config }}>
54
+ <div
55
+ className={cn(
56
+ "flex aspect-video justify-center overflow-hidden text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
57
+ className,
58
+ )}
59
+ data-chart={chartId}
60
+ data-slot="chart"
61
+ {...props}
62
+ >
63
+ <ChartStyle config={config} id={chartId} />
64
+ <RechartsPrimitive.ResponsiveContainer>
65
+ {children}
66
+ </RechartsPrimitive.ResponsiveContainer>
67
+ </div>
68
+ </ChartContext.Provider>
69
+ );
70
+ }
71
+
72
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
73
+ const colorConfig = Object.entries(config).filter(
74
+ ([, itemConfig]) => itemConfig.theme || itemConfig.color,
75
+ );
76
+
77
+ if (!colorConfig.length) {
78
+ return null;
79
+ }
80
+
81
+ return (
82
+ <style
83
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: CSS variables for chart series colors
84
+ dangerouslySetInnerHTML={{
85
+ __html: Object.entries(THEMES)
86
+ .map(
87
+ ([theme, prefix]) => `
88
+ ${prefix} [data-chart=${id}] {
89
+ ${colorConfig
90
+ .map(([key, itemConfig]) => {
91
+ const color =
92
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
93
+ itemConfig.color;
94
+ return color ? ` --color-${key}: ${color};` : null;
95
+ })
96
+ .filter(Boolean)
97
+ .join("\n")}
98
+ }
99
+ `,
100
+ )
101
+ .join("\n"),
102
+ }}
103
+ />
104
+ );
105
+ };
106
+
107
+ const ChartTooltip = RechartsPrimitive.Tooltip;
108
+
109
+ type TooltipPayloadItem = {
110
+ dataKey?: string | number;
111
+ name?: string | number;
112
+ value?: number | string;
113
+ color?: string;
114
+ fill?: string;
115
+ payload?: Record<string, unknown> & { fill?: string };
116
+ };
117
+
118
+ function ChartTooltipContent({
119
+ active,
120
+ payload,
121
+ className,
122
+ indicator = "dot",
123
+ hideLabel = false,
124
+ hideIndicator = false,
125
+ label,
126
+ labelFormatter,
127
+ labelClassName,
128
+ formatter,
129
+ color,
130
+ nameKey,
131
+ labelKey,
132
+ }: React.ComponentProps<"div"> & {
133
+ active?: boolean;
134
+ payload?: TooltipPayloadItem[];
135
+ indicator?: "line" | "dot" | "dashed";
136
+ hideLabel?: boolean;
137
+ hideIndicator?: boolean;
138
+ label?: unknown;
139
+ labelFormatter?: (
140
+ value: React.ReactNode,
141
+ payload: TooltipPayloadItem[],
142
+ ) => React.ReactNode;
143
+ labelClassName?: string;
144
+ formatter?: (
145
+ value: number | string,
146
+ name: string | number,
147
+ item: TooltipPayloadItem,
148
+ index: number,
149
+ payload: TooltipPayloadItem["payload"],
150
+ ) => React.ReactNode;
151
+ color?: string;
152
+ nameKey?: string;
153
+ labelKey?: string;
154
+ }) {
155
+ const { config } = useChart();
156
+
157
+ const tooltipLabel = React.useMemo(() => {
158
+ if (hideLabel || !payload?.length) {
159
+ return null;
160
+ }
161
+
162
+ const [item] = payload;
163
+ if (!item) {
164
+ return null;
165
+ }
166
+ const key = `${labelKey || item.dataKey || item.name || "value"}`;
167
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
168
+ const value =
169
+ !labelKey && typeof label === "string"
170
+ ? config[label]?.label || label
171
+ : itemConfig?.label;
172
+
173
+ if (labelFormatter) {
174
+ return (
175
+ <div className={cn("font-medium", labelClassName)}>
176
+ {labelFormatter(value, payload)}
177
+ </div>
178
+ );
179
+ }
180
+
181
+ if (!value) {
182
+ return null;
183
+ }
184
+
185
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>;
186
+ }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
187
+
188
+ if (!active || !payload?.length) {
189
+ return null;
190
+ }
191
+
192
+ const nestLabel = payload.length === 1 && indicator !== "dot";
193
+
194
+ return (
195
+ <div
196
+ className={cn(
197
+ "grid min-w-32 items-start gap-1.5 rounded-lg border bg-popover px-2.5 py-1.5 text-popover-foreground text-xs shadow-lg/5",
198
+ className,
199
+ )}
200
+ >
201
+ {!nestLabel ? tooltipLabel : null}
202
+ <div className="grid gap-1.5">
203
+ {payload.map((item, index) => {
204
+ const key = `${nameKey || item.name || item.dataKey || "value"}`;
205
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
206
+ const indicatorColor = color || item.payload?.fill || item.color;
207
+
208
+ return (
209
+ <div
210
+ className={cn(
211
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5 [&>svg]:text-muted-foreground",
212
+ indicator === "dot" && "items-center",
213
+ )}
214
+ key={item.dataKey ?? index}
215
+ >
216
+ {formatter && item?.value !== undefined && item.name ? (
217
+ formatter(item.value, item.name, item, index, item.payload)
218
+ ) : (
219
+ <>
220
+ {itemConfig?.icon ? (
221
+ <itemConfig.icon />
222
+ ) : (
223
+ !hideIndicator && (
224
+ <div
225
+ className={cn(
226
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
227
+ {
228
+ "h-2.5 w-2.5": indicator === "dot",
229
+ "w-1": indicator === "line",
230
+ "w-0 border-[1.5px] border-dashed bg-transparent":
231
+ indicator === "dashed",
232
+ "my-0.5": nestLabel && indicator === "dashed",
233
+ },
234
+ )}
235
+ style={
236
+ {
237
+ "--color-bg": indicatorColor,
238
+ "--color-border": indicatorColor,
239
+ } as React.CSSProperties
240
+ }
241
+ />
242
+ )
243
+ )}
244
+ <div
245
+ className={cn(
246
+ "flex flex-1 justify-between leading-none",
247
+ nestLabel ? "items-end" : "items-center",
248
+ )}
249
+ >
250
+ <div className="grid gap-1.5">
251
+ {nestLabel ? tooltipLabel : null}
252
+ <span className="text-muted-foreground">
253
+ {itemConfig?.label || item.name}
254
+ </span>
255
+ </div>
256
+ {item.value !== undefined && (
257
+ <span className="font-medium font-mono text-foreground tabular-nums">
258
+ {typeof item.value === "number"
259
+ ? item.value.toLocaleString()
260
+ : item.value}
261
+ </span>
262
+ )}
263
+ </div>
264
+ </>
265
+ )}
266
+ </div>
267
+ );
268
+ })}
269
+ </div>
270
+ </div>
271
+ );
272
+ }
273
+
274
+ const ChartLegend = RechartsPrimitive.Legend;
275
+
276
+ type LegendPayloadItem = {
277
+ value?: string | number;
278
+ dataKey?: string | number;
279
+ color?: string;
280
+ };
281
+
282
+ function ChartLegendContent({
283
+ className,
284
+ hideIcon = false,
285
+ payload,
286
+ verticalAlign = "bottom",
287
+ nameKey,
288
+ }: React.ComponentProps<"div"> & {
289
+ hideIcon?: boolean;
290
+ payload?: LegendPayloadItem[];
291
+ verticalAlign?: "top" | "bottom" | "middle";
292
+ nameKey?: string;
293
+ }) {
294
+ const { config } = useChart();
295
+
296
+ if (!payload?.length) {
297
+ return null;
298
+ }
299
+
300
+ return (
301
+ <div
302
+ className={cn(
303
+ "flex items-center justify-center gap-4",
304
+ verticalAlign === "top" ? "pb-3" : "pt-3",
305
+ className,
306
+ )}
307
+ >
308
+ {payload.map((item, index) => {
309
+ const key = `${nameKey || item.dataKey || "value"}`;
310
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
311
+
312
+ return (
313
+ <div
314
+ className="flex items-center gap-1.5 [&>svg]:size-3 [&>svg]:text-muted-foreground"
315
+ key={item.value ?? index}
316
+ >
317
+ {itemConfig?.icon && !hideIcon ? (
318
+ <itemConfig.icon />
319
+ ) : (
320
+ <div
321
+ className="h-2 w-2 shrink-0 rounded-[2px]"
322
+ style={{
323
+ backgroundColor: item.color,
324
+ }}
325
+ />
326
+ )}
327
+ {itemConfig?.label}
328
+ </div>
329
+ );
330
+ })}
331
+ </div>
332
+ );
333
+ }
334
+
335
+ // Helper to extract item config from a payload.
336
+ function getPayloadConfigFromPayload(
337
+ config: ChartConfig,
338
+ payload: unknown,
339
+ key: string,
340
+ ) {
341
+ if (typeof payload !== "object" || payload === null) {
342
+ return undefined;
343
+ }
344
+
345
+ const payloadPayload =
346
+ "payload" in payload &&
347
+ typeof payload.payload === "object" &&
348
+ payload.payload !== null
349
+ ? payload.payload
350
+ : undefined;
351
+
352
+ let configLabelKey: string = key;
353
+
354
+ if (
355
+ key in payload &&
356
+ typeof (payload as Record<string, unknown>)[key] === "string"
357
+ ) {
358
+ configLabelKey = (payload as Record<string, unknown>)[key] as string;
359
+ } else if (
360
+ payloadPayload &&
361
+ key in payloadPayload &&
362
+ typeof (payloadPayload as Record<string, unknown>)[key] === "string"
363
+ ) {
364
+ configLabelKey = (payloadPayload as Record<string, unknown>)[key] as string;
365
+ }
366
+
367
+ return configLabelKey in config
368
+ ? config[configLabelKey]
369
+ : config[key as keyof typeof config];
370
+ }
371
+
372
+ export {
373
+ ChartContainer,
374
+ ChartTooltip,
375
+ ChartTooltipContent,
376
+ ChartLegend,
377
+ ChartLegendContent,
378
+ ChartStyle,
379
+ };
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../../lib/utils/css";
6
+
7
+ type ChatDividerVariant = "default" | "summary" | "unread";
8
+
9
+ interface ChatDividerProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ /** Centered label, e.g. "New messages", "You're caught up", "Earlier messages summarized". */
11
+ label?: React.ReactNode;
12
+ /** Optional leading icon (e.g. a summary or sparkle glyph). */
13
+ icon?: React.ReactNode;
14
+ /**
15
+ * - `default` — neutral hairline with a muted label.
16
+ * - `summary` — marks where a rolling summary folds older history.
17
+ * - `unread` — the "new messages" baseline; accented so it stands out.
18
+ */
19
+ variant?: ChatDividerVariant;
20
+ }
21
+
22
+ const LABEL_TONE: Record<ChatDividerVariant, string> = {
23
+ default: "text-muted-foreground",
24
+ summary: "text-muted-foreground",
25
+ unread: "text-primary",
26
+ };
27
+
28
+ const LINE_TONE: Record<ChatDividerVariant, string> = {
29
+ default: "bg-border",
30
+ summary: "bg-border",
31
+ unread: "bg-primary/40",
32
+ };
33
+
34
+ /**
35
+ * A centered, labeled divider for the message stream: the point a rolling summary
36
+ * folds history (`summary`), the unread baseline (`unread`), or a plain date/section
37
+ * break (`default`). Pairs with the backend `AgentSummaryCheckpoint` and each
38
+ * participant's `joined_seq`/`last_read_seq`.
39
+ */
40
+ function ChatDivider({ label, icon, variant = "default", className, ...props }: ChatDividerProps) {
41
+ return (
42
+ <div
43
+ className={cn("flex w-full items-center gap-3 py-1 select-none", className)}
44
+ data-slot="chat-divider"
45
+ data-variant={variant}
46
+ role="separator"
47
+ {...props}
48
+ >
49
+ <span className={cn("h-px flex-1", LINE_TONE[variant])} />
50
+ {label != null ? (
51
+ <span
52
+ className={cn(
53
+ "inline-flex items-center gap-1.5 font-medium text-xs whitespace-nowrap",
54
+ LABEL_TONE[variant],
55
+ )}
56
+ >
57
+ {icon ? <span className="inline-flex shrink-0 items-center">{icon}</span> : null}
58
+ {label}
59
+ </span>
60
+ ) : null}
61
+ <span className={cn("h-px flex-1", LINE_TONE[variant])} />
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export { ChatDivider };
67
+ export type { ChatDividerProps, ChatDividerVariant };