@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.
- package/dist/chunk-2HQPCV2L.js +895 -0
- package/dist/chunk-2HQPCV2L.js.map +1 -0
- package/dist/form.d.ts +175 -0
- package/dist/form.js +356 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +2162 -0
- package/dist/index.js +11640 -0
- package/dist/index.js.map +1 -0
- package/dist/input-JCZs61MO.d.ts +24 -0
- package/package.json +90 -0
- package/src/components/form/checkbox-group-field.tsx +101 -0
- package/src/components/form/date-field.tsx +79 -0
- package/src/components/form/date-range-field.tsx +106 -0
- package/src/components/form/form-context.ts +10 -0
- package/src/components/form/form.tsx +54 -0
- package/src/components/form/number-field.tsx +69 -0
- package/src/components/form/select-field.tsx +76 -0
- package/src/components/form/submit-button.tsx +28 -0
- package/src/components/form/text-field.tsx +107 -0
- package/src/components/layout/dashboard-header.tsx +54 -0
- package/src/components/layout/dashboard-panel.tsx +34 -0
- package/src/components/layout/dashboard-topbar.tsx +221 -0
- package/src/components/theme-provider.tsx +403 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert-dialog.tsx +169 -0
- package/src/components/ui/alert.tsx +80 -0
- package/src/components/ui/animated-counter.tsx +109 -0
- package/src/components/ui/animated-theme-toggler.tsx +265 -0
- package/src/components/ui/app-store-buttons.tsx +182 -0
- package/src/components/ui/aspect-ratio.tsx +23 -0
- package/src/components/ui/aurora-orb.tsx +163 -0
- package/src/components/ui/aurora-text.tsx +76 -0
- package/src/components/ui/autocomplete.tsx +296 -0
- package/src/components/ui/avatar-group.tsx +150 -0
- package/src/components/ui/avatar.tsx +285 -0
- package/src/components/ui/badge-group.tsx +160 -0
- package/src/components/ui/badge.tsx +172 -0
- package/src/components/ui/breadcrumb.tsx +112 -0
- package/src/components/ui/button.tsx +77 -0
- package/src/components/ui/calendar.tsx +137 -0
- package/src/components/ui/card.tsx +244 -0
- package/src/components/ui/carousel.tsx +258 -0
- package/src/components/ui/chart.tsx +379 -0
- package/src/components/ui/chat-divider.tsx +67 -0
- package/src/components/ui/chat.tsx +494 -0
- package/src/components/ui/checkbox-group.tsx +16 -0
- package/src/components/ui/checkbox.tsx +82 -0
- package/src/components/ui/collapsible.tsx +45 -0
- package/src/components/ui/color-picker.tsx +389 -0
- package/src/components/ui/combobox.tsx +411 -0
- package/src/components/ui/command.tsx +264 -0
- package/src/components/ui/confetti.tsx +196 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/context-meter.tsx +134 -0
- package/src/components/ui/credit-card.tsx +214 -0
- package/src/components/ui/data-table-paged.tsx +163 -0
- package/src/components/ui/date-picker.tsx +154 -0
- package/src/components/ui/dialog.tsx +196 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/empty.tsx +127 -0
- package/src/components/ui/fab.tsx +45 -0
- package/src/components/ui/featured-icon.tsx +149 -0
- package/src/components/ui/field.tsx +88 -0
- package/src/components/ui/fieldset.tsx +29 -0
- package/src/components/ui/form.tsx +17 -0
- package/src/components/ui/frame.tsx +82 -0
- package/src/components/ui/generic-empty.tsx +142 -0
- package/src/components/ui/group.tsx +97 -0
- package/src/components/ui/help-tip.tsx +94 -0
- package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
- package/src/components/ui/input-group.tsx +102 -0
- package/src/components/ui/input-otp.tsx +96 -0
- package/src/components/ui/input.tsx +66 -0
- package/src/components/ui/item.tsx +198 -0
- package/src/components/ui/kbd.tsx +30 -0
- package/src/components/ui/label.tsx +28 -0
- package/src/components/ui/markdown.tsx +188 -0
- package/src/components/ui/menu.tsx +312 -0
- package/src/components/ui/menubar.tsx +93 -0
- package/src/components/ui/meter.tsx +67 -0
- package/src/components/ui/multi-select.tsx +308 -0
- package/src/components/ui/navigation-menu.tsx +143 -0
- package/src/components/ui/number-field.tsx +160 -0
- package/src/components/ui/pagination-controls.tsx +74 -0
- package/src/components/ui/pagination.tsx +149 -0
- package/src/components/ui/pipeline.tsx +102 -0
- package/src/components/ui/popover.tsx +119 -0
- package/src/components/ui/preview-card.tsx +55 -0
- package/src/components/ui/progress.tsx +289 -0
- package/src/components/ui/qr-code.tsx +150 -0
- package/src/components/ui/radio-group.tsx +103 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +90 -0
- package/src/components/ui/scroller.tsx +38 -0
- package/src/components/ui/section-header.tsx +118 -0
- package/src/components/ui/segmented-control.tsx +83 -0
- package/src/components/ui/select.tsx +181 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +224 -0
- package/src/components/ui/sidebar.tsx +774 -0
- package/src/components/ui/skeleton.tsx +16 -0
- package/src/components/ui/slider.tsx +108 -0
- package/src/components/ui/smooth-scroll.tsx +206 -0
- package/src/components/ui/social-button.tsx +247 -0
- package/src/components/ui/spinner-on-demand.tsx +32 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/components/ui/stat.tsx +187 -0
- package/src/components/ui/stepper.tsx +167 -0
- package/src/components/ui/switch.tsx +56 -0
- package/src/components/ui/table.tsx +126 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/tag.tsx +242 -0
- package/src/components/ui/target-countdown.tsx +46 -0
- package/src/components/ui/text-editor.tsx +313 -0
- package/src/components/ui/textarea.tsx +51 -0
- package/src/components/ui/thinking-orb.tsx +6 -0
- package/src/components/ui/time-picker.tsx +308 -0
- package/src/components/ui/timeline.tsx +116 -0
- package/src/components/ui/toast.tsx +268 -0
- package/src/components/ui/toggle-group.tsx +101 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/toolbar.tsx +89 -0
- package/src/components/ui/tooltip.tsx +102 -0
- package/src/components/ui/vertical-scroll-fader.tsx +250 -0
- package/src/components/ui/video-player.tsx +275 -0
- package/src/components/upload/avatar-upload-base.tsx +131 -0
- package/src/components/upload/image-upload-base.tsx +112 -0
- package/src/form.ts +17 -0
- package/src/index.ts +143 -0
- package/src/lib/hooks/use-callback-ref.ts +15 -0
- package/src/lib/hooks/use-first-render.ts +11 -0
- package/src/lib/hooks/use-hover.ts +53 -0
- package/src/lib/hooks/use-is-tab-active.ts +17 -0
- package/src/lib/hooks/use-media-query.ts +164 -0
- package/src/lib/utils/css.ts +6 -0
- package/src/styles.css +329 -0
- package/src/types/helpers.ts +24 -0
- 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 };
|