@shapesos/clay 0.14.0 → 0.16.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 +14 -0
- package/dist/ai-elements.cjs +112 -7
- package/dist/ai-elements.cjs.map +1 -1
- package/dist/ai-elements.d.cts +30 -2
- package/dist/ai-elements.d.ts +30 -2
- package/dist/ai-elements.js +1 -1
- package/dist/artifacts.cjs +85 -34
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.js +2 -2
- package/dist/blocks.cjs +85 -34
- package/dist/blocks.cjs.map +1 -1
- package/dist/blocks.css +1 -1
- package/dist/blocks.js +3 -3
- package/dist/chart.cjs +85 -34
- package/dist/chart.cjs.map +1 -1
- package/dist/chart.d.cts +12 -10
- package/dist/chart.d.ts +12 -10
- package/dist/chart.js +1 -1
- package/dist/chat.cjs +85 -34
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.js +4 -4
- package/dist/{chunk-XFJ6XMJZ.js → chunk-2YCBDSPK.js} +2 -2
- package/dist/{chunk-BR5S37SC.js → chunk-4VE6ZXXW.js} +113 -8
- package/dist/chunk-4VE6ZXXW.js.map +1 -0
- package/dist/{chunk-3THOTQO3.js → chunk-GAZRZZRF.js} +3 -3
- package/dist/{chunk-YZB6YXKK.js → chunk-U3IXCSV6.js} +86 -35
- package/dist/chunk-U3IXCSV6.js.map +1 -0
- package/dist/{chunk-MDAWYZBI.js → chunk-UZKNSAAD.js} +2 -2
- package/dist/index.cjs +197 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -5
- package/package.json +1 -1
- package/dist/chunk-BR5S37SC.js.map +0 -1
- package/dist/chunk-YZB6YXKK.js.map +0 -1
- /package/dist/{chunk-XFJ6XMJZ.js.map → chunk-2YCBDSPK.js.map} +0 -0
- /package/dist/{chunk-3THOTQO3.js.map → chunk-GAZRZZRF.js.map} +0 -0
- /package/dist/{chunk-MDAWYZBI.js.map → chunk-UZKNSAAD.js.map} +0 -0
package/dist/chat.js
CHANGED
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
copyMessageText,
|
|
6
6
|
useChatContext,
|
|
7
7
|
useCopyToClipboard
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-GAZRZZRF.js";
|
|
9
|
+
import "./chunk-UZKNSAAD.js";
|
|
10
10
|
import "./chunk-NMKKU2UG.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-2YCBDSPK.js";
|
|
12
12
|
import "./chunk-PYDVANWI.js";
|
|
13
13
|
import "./chunk-LXYCT4YO.js";
|
|
14
14
|
import "./chunk-OLJIJYB5.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-U3IXCSV6.js";
|
|
16
16
|
import "./chunk-C77QMQNT.js";
|
|
17
17
|
import "./chunk-UXT2H3FM.js";
|
|
18
18
|
import "./chunk-JF3P66JF.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
BarChart,
|
|
9
9
|
LineChart,
|
|
10
10
|
PieChart
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-U3IXCSV6.js";
|
|
12
12
|
import {
|
|
13
13
|
Table,
|
|
14
14
|
TableBody,
|
|
@@ -1001,4 +1001,4 @@ export {
|
|
|
1001
1001
|
* See the LICENSE file in the root directory of this source tree.
|
|
1002
1002
|
*)
|
|
1003
1003
|
*/
|
|
1004
|
-
//# sourceMappingURL=chunk-
|
|
1004
|
+
//# sourceMappingURL=chunk-2YCBDSPK.js.map
|
|
@@ -96,43 +96,148 @@ var PromptInputSubmit = ({
|
|
|
96
96
|
|
|
97
97
|
// src/components/ai-elements/mouse-grid/mouse-grid.tsx
|
|
98
98
|
import { useEffect, useRef } from "react";
|
|
99
|
+
|
|
100
|
+
// src/utils/prefers-reduced-motion.ts
|
|
101
|
+
function prefersReducedMotion() {
|
|
102
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/components/ai-elements/mouse-grid/mouse-grid.tsx
|
|
99
106
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
100
|
-
function MouseGrid({ glowColor } = {}) {
|
|
107
|
+
function MouseGrid({ glowColor, colorCycleMs = 4e3, autoDrift = false } = {}) {
|
|
101
108
|
const layerRef = useRef(null);
|
|
109
|
+
const glowRef = useRef(null);
|
|
102
110
|
useEffect(() => {
|
|
111
|
+
const driftEnabled = autoDrift && !prefersReducedMotion();
|
|
103
112
|
let raf = 0;
|
|
113
|
+
let driftRaf = 0;
|
|
114
|
+
let lastDriftTime = 0;
|
|
104
115
|
let clientX = 0;
|
|
105
116
|
let clientY = 0;
|
|
117
|
+
let pointerInside = false;
|
|
118
|
+
let centerX = 0;
|
|
119
|
+
let centerY = 0;
|
|
120
|
+
let hasCenter = false;
|
|
121
|
+
function setCenter(x, y) {
|
|
122
|
+
const layer = layerRef.current;
|
|
123
|
+
if (!layer) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
centerX = x;
|
|
127
|
+
centerY = y;
|
|
128
|
+
hasCenter = true;
|
|
129
|
+
layer.style.setProperty("--vibe-mx", `${x}px`);
|
|
130
|
+
layer.style.setProperty("--vibe-my", `${y}px`);
|
|
131
|
+
}
|
|
106
132
|
function flush() {
|
|
107
133
|
raf = 0;
|
|
108
134
|
const layer = layerRef.current;
|
|
109
135
|
if (!layer) {
|
|
110
136
|
return;
|
|
111
137
|
}
|
|
138
|
+
if (driftEnabled && !pointerInside) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
112
141
|
const rect = layer.getBoundingClientRect();
|
|
113
|
-
|
|
114
|
-
layer.style.setProperty("--vibe-my", `${clientY - rect.top}px`);
|
|
142
|
+
setCenter(clientX - rect.left, clientY - rect.top);
|
|
115
143
|
}
|
|
116
144
|
function onMove(event) {
|
|
117
145
|
clientX = event.clientX;
|
|
118
146
|
clientY = event.clientY;
|
|
147
|
+
const layer = layerRef.current;
|
|
148
|
+
if (layer) {
|
|
149
|
+
const rect = layer.getBoundingClientRect();
|
|
150
|
+
pointerInside = clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
|
|
151
|
+
}
|
|
152
|
+
if (pointerInside) {
|
|
153
|
+
stopDrift();
|
|
154
|
+
} else {
|
|
155
|
+
startDrift();
|
|
156
|
+
}
|
|
119
157
|
if (raf) {
|
|
120
158
|
return;
|
|
121
159
|
}
|
|
122
160
|
raf = requestAnimationFrame(flush);
|
|
123
161
|
}
|
|
162
|
+
function onPointerLeave() {
|
|
163
|
+
pointerInside = false;
|
|
164
|
+
startDrift();
|
|
165
|
+
}
|
|
166
|
+
function drift(time) {
|
|
167
|
+
const delta = lastDriftTime ? time - lastDriftTime : 16.67;
|
|
168
|
+
lastDriftTime = time;
|
|
169
|
+
driftRaf = requestAnimationFrame(drift);
|
|
170
|
+
const layer = layerRef.current;
|
|
171
|
+
if (!layer) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const { width, height } = layer.getBoundingClientRect();
|
|
175
|
+
if (!hasCenter) {
|
|
176
|
+
centerX = width / 2;
|
|
177
|
+
centerY = height / 2;
|
|
178
|
+
}
|
|
179
|
+
const targetX = width * (0.5 + 0.32 * Math.sin(time * 37e-5) + 0.12 * Math.sin(time * 91e-5));
|
|
180
|
+
const targetY = height * (0.5 + 0.32 * Math.cos(time * 41e-5) + 0.12 * Math.sin(time * 11e-4));
|
|
181
|
+
const alpha = 1 - Math.exp(-25e-4 * delta);
|
|
182
|
+
setCenter(centerX + (targetX - centerX) * alpha, centerY + (targetY - centerY) * alpha);
|
|
183
|
+
}
|
|
184
|
+
function startDrift() {
|
|
185
|
+
if (!driftEnabled || driftRaf) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
lastDriftTime = 0;
|
|
189
|
+
driftRaf = requestAnimationFrame(drift);
|
|
190
|
+
}
|
|
191
|
+
function stopDrift() {
|
|
192
|
+
if (driftRaf) {
|
|
193
|
+
cancelAnimationFrame(driftRaf);
|
|
194
|
+
driftRaf = 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
124
197
|
window.addEventListener("mousemove", onMove, { passive: true });
|
|
198
|
+
document.documentElement.addEventListener("mouseleave", onPointerLeave);
|
|
199
|
+
startDrift();
|
|
125
200
|
return () => {
|
|
126
201
|
window.removeEventListener("mousemove", onMove);
|
|
202
|
+
document.documentElement.removeEventListener("mouseleave", onPointerLeave);
|
|
127
203
|
if (raf) {
|
|
128
204
|
cancelAnimationFrame(raf);
|
|
129
205
|
}
|
|
206
|
+
stopDrift();
|
|
130
207
|
};
|
|
131
|
-
}, []);
|
|
132
|
-
const
|
|
208
|
+
}, [autoDrift]);
|
|
209
|
+
const colorList = Array.isArray(glowColor) ? glowColor : glowColor != null ? [glowColor] : [];
|
|
210
|
+
const glowColorsKey = JSON.stringify(colorList);
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const glow = glowRef.current;
|
|
213
|
+
if (!glow) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const colors = JSON.parse(glowColorsKey).filter(Boolean);
|
|
217
|
+
const staticColor = colors[0];
|
|
218
|
+
if (staticColor) {
|
|
219
|
+
glow.style.setProperty("--vibe-glow-color", staticColor);
|
|
220
|
+
}
|
|
221
|
+
const clearColor = () => glow.style.removeProperty("--vibe-glow-color");
|
|
222
|
+
if (colors.length < 2 || prefersReducedMotion()) {
|
|
223
|
+
return staticColor ? clearColor : void 0;
|
|
224
|
+
}
|
|
225
|
+
let index = 0;
|
|
226
|
+
const raf = requestAnimationFrame(() => glow.style.setProperty("--vibe-glow-cycle", `${colorCycleMs}ms`));
|
|
227
|
+
const timer = setInterval(() => {
|
|
228
|
+
index = (index + 1) % colors.length;
|
|
229
|
+
glow.style.setProperty("--vibe-glow-color", colors[index]);
|
|
230
|
+
}, colorCycleMs);
|
|
231
|
+
return () => {
|
|
232
|
+
cancelAnimationFrame(raf);
|
|
233
|
+
clearInterval(timer);
|
|
234
|
+
glow.style.removeProperty("--vibe-glow-cycle");
|
|
235
|
+
clearColor();
|
|
236
|
+
};
|
|
237
|
+
}, [glowColorsKey, colorCycleMs]);
|
|
133
238
|
return /* @__PURE__ */ jsxs("div", { ref: layerRef, "aria-hidden": "true", className: "vibe-mouse-grid-layer", children: [
|
|
134
239
|
/* @__PURE__ */ jsx2("div", { className: "vibe-mouse-grid" }),
|
|
135
|
-
/* @__PURE__ */ jsx2("div", { className: "vibe-mouse-glow"
|
|
240
|
+
/* @__PURE__ */ jsx2("div", { ref: glowRef, className: "vibe-mouse-glow" })
|
|
136
241
|
] });
|
|
137
242
|
}
|
|
138
243
|
|
|
@@ -148,7 +253,7 @@ var Suggestion = ({ suggestion, onClick, className, children, ...props }) => {
|
|
|
148
253
|
type: "button",
|
|
149
254
|
onClick: handleClick,
|
|
150
255
|
className: cn(
|
|
151
|
-
"cursor-pointer whitespace-nowrap rounded-
|
|
256
|
+
"cursor-pointer whitespace-nowrap rounded-[12px] border border-brown-30 bg-white px-4 py-2 text-sm text-brown-100 transition-colors hover:bg-brown-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brown-40",
|
|
152
257
|
className
|
|
153
258
|
),
|
|
154
259
|
...props,
|
|
@@ -168,4 +273,4 @@ export {
|
|
|
168
273
|
Suggestions,
|
|
169
274
|
Suggestion
|
|
170
275
|
};
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
276
|
+
//# sourceMappingURL=chunk-4VE6ZXXW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ai-elements/prompt-input/prompt-input.tsx","../src/components/ai-elements/prompt-input/types.ts","../src/components/ai-elements/mouse-grid/mouse-grid.tsx","../src/utils/prefers-reduced-motion.ts","../src/components/ai-elements/suggestion/suggestion.tsx"],"sourcesContent":["import { forwardRef, useCallback } from \"react\";\nimport type { FormEventHandler, KeyboardEventHandler, MouseEventHandler } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/button/button\";\nimport { BUTTON_INTENT, BUTTON_SIZE } from \"@/components/button/constants\";\nimport { TextArea } from \"@/components/text-area/text-area\";\nimport { CHAT_STATUS } from \"./types\";\nimport type {\n PromptInputFooterProps,\n PromptInputProps,\n PromptInputSubmitProps,\n PromptInputTextareaProps,\n PromptInputToolsProps,\n} from \"./types\";\n\n/* ------------------------------------------------------------------------------------------------\n * Originally vendored from `@vercel/ai-elements`'s `prompt-input` registry entry. Slimmed hard to\n * the creation-surface essentials a consumer composes into a prompt-launcher hero: `PromptInput`\n * (form wrapper), `PromptInputTextarea`, `PromptInputFooter`, `PromptInputTools`, `PromptInputSubmit`.\n *\n * Everything else from the upstream entry is intentionally dropped: the action menu\n * (`PromptInputActionMenu*`), attachment / screenshot actions, the attachments strip, the\n * provider/controller contexts, and the `Select` / `HoverCard` / `Command` / `Tab` families. That\n * also sheds the upstream `nanoid` (id-per-attachment) dependency.\n *\n * Rewiring to clay's single-sourced primitives let this primitive ship with ZERO new runtime\n * dependencies — diverging from the original shared spec, which assumed the kept set would still\n * lean on `@base-ui/react`, `ai`, and `lucide-react`:\n * - upstream `InputGroupButton` (submit) → clay `Button` (filled, `type=\"button\"`, consumer label).\n * - upstream `InputGroupTextarea` → clay `TextArea` (auto-resize, transparent chrome).\n * - `lucide-react` icons → clay's already-bundled `@tabler/icons-react`.\n * - `ai`'s `ChatStatus` → a local ready/error status (see `./types`).\n *\n * Because clay's `Button` always renders `type=\"button\"`, native form submit isn't available;\n * both the Enter-key handler and the submit button trigger `form.requestSubmit()` explicitly. And\n * because clay's `TextArea` doesn't forward a `name` attribute, `PromptInput` reads the textarea's\n * value straight off the DOM at submit time rather than via `FormData` — keeping the form wrapper\n * decoupled from whoever controls the textarea's value.\n *\n * Types live in `./types`.\n * ------------------------------------------------------------------------------------------------ */\n\n/**\n * Form wrapper for a prompt composer. Renders a bordered, rounded surface; stacks its children\n * (textarea + footer) vertically. Reads the nested textarea's value on submit and forwards it to\n * `onSubmit`. Empty / whitespace-only submissions, and all submissions while `disabled`, are\n * ignored.\n */\nexport const PromptInput = ({ className, onSubmit, disabled, children, ...props }: PromptInputProps) => {\n const handleSubmit: FormEventHandler<HTMLFormElement> = useCallback(\n (event) => {\n event.preventDefault();\n if (disabled) {\n return;\n }\n const textarea = event.currentTarget.querySelector(\"textarea\");\n const text = textarea?.value ?? \"\";\n if (!text.trim()) {\n return;\n }\n onSubmit({ text }, event);\n },\n [disabled, onSubmit]\n );\n\n return (\n <form\n className={cn(\n \"flex w-full flex-col overflow-hidden rounded-xl border bg-background shadow-lg shadow-foreground/5\",\n className\n )}\n onSubmit={handleSubmit}\n {...props}\n >\n {children}\n </form>\n );\n};\n\n/**\n * Auto-resizing textarea for the prompt composer. Wraps clay's `TextArea` and adds Enter-to-submit\n * (Shift+Enter inserts a newline; submission is suppressed while an IME composition is active). The\n * consumer's `onKeyDown` runs first and can `preventDefault()` to opt out of the built-in Enter\n * handling.\n *\n * Horizontal padding lives on the textarea itself (not the wrapper) so its overflow scrollbar sits\n * flush against the wrapper's right edge; the wrapper only owns the vertical padding.\n */\nexport const PromptInputTextarea = forwardRef<HTMLTextAreaElement, PromptInputTextareaProps>(\n function PromptInputTextarea({ className, onKeyDown, placeholder = \"What would you like to know?\", ...props }, ref) {\n const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(\n (event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented) {\n return;\n }\n if (event.key === \"Enter\" && !event.shiftKey && !event.nativeEvent.isComposing) {\n event.preventDefault();\n event.currentTarget.form?.requestSubmit();\n }\n },\n [onKeyDown]\n );\n\n return (\n <div className={cn(\"py-3\", className)}>\n <TextArea ref={ref} className=\"px-4\" onKeyDown={handleKeyDown} placeholder={placeholder} {...props} />\n </div>\n );\n }\n);\n\n/**\n * Footer row beneath the textarea — left tools and the right-aligned submit button. Horizontal\n * padding matches {@link PromptInputTextarea}'s (`px-4`) so the tools/submit align with the\n * textarea's text edges.\n */\nexport const PromptInputFooter = ({ className, ...props }: PromptInputFooterProps) => (\n <div className={cn(\"flex items-center justify-between gap-1 px-4 pb-3\", className)} {...props} />\n);\n\n/** Left-aligned cluster inside {@link PromptInputFooter} (e.g. the submit hint). */\nexport const PromptInputTools = ({ className, ...props }: PromptInputToolsProps) => (\n <div className={cn(\"flex min-w-0 items-center gap-1\", className)} {...props} />\n);\n\n/**\n * Submit button for the prompt composer — a filled clay `Button` whose text is the consumer's\n * `label` (pass a localized string for i18n). Always submits the form via `form.requestSubmit()` on\n * click (clay buttons render `type=\"button\"`); the consumer disables it while a generation is in\n * flight. `status` drives the color: `error` renders the error (red) intent, otherwise primary.\n */\nexport const PromptInputSubmit = ({\n label = \"Submit\",\n status = CHAT_STATUS.READY,\n disabled,\n className,\n}: PromptInputSubmitProps) => {\n const handleClick = useCallback<MouseEventHandler<HTMLButtonElement>>((event) => {\n event.currentTarget.form?.requestSubmit();\n }, []);\n\n return (\n <Button\n intent={status === CHAT_STATUS.ERROR ? BUTTON_INTENT.ERROR : BUTTON_INTENT.PRIMARY}\n size={BUTTON_SIZE.XS}\n className={className}\n disabled={disabled}\n onClick={handleClick}\n >\n {label}\n </Button>\n );\n};\n","import type { FormEvent, HTMLAttributes } from \"react\";\n\nimport type { TextAreaProps } from \"@/components/text-area/types\";\n\n/**\n * Submission status the submit button reflects. The composer has no cancellation and no\n * intermediate \"generating\" state of its own — the consumer disables the button while busy — so\n * only the idle and error states are modeled here.\n */\nexport const CHAT_STATUS = {\n /** Idle / ready to submit. */\n READY: \"ready\",\n /** The last submission errored — the button shows a retry affordance. */\n ERROR: \"error\",\n} as const;\n\n/** Submission status the submit button reflects. */\nexport type ChatStatus = (typeof CHAT_STATUS)[keyof typeof CHAT_STATUS];\n\n/** Payload emitted by {@link PromptInput} on submit. v1 carries only text (attachments are cut). */\nexport interface PromptInputMessage {\n /** The current textarea value at submit time. */\n text: string;\n}\n\n/** Props for the {@link PromptInput} form wrapper. */\nexport type PromptInputProps = Omit<HTMLAttributes<HTMLFormElement>, \"onSubmit\"> & {\n /**\n * Fired when the form is submitted (Enter key or the submit button) with non-empty text.\n * Receives the captured message and the underlying form event.\n */\n onSubmit: (message: PromptInputMessage, event: FormEvent<HTMLFormElement>) => void;\n /**\n * Blocks submission across every trigger (Enter key, submit button, programmatic) when `true` —\n * e.g. while a generation is in flight. The single gate that keeps the Enter path in sync with a\n * disabled submit button. @default false\n */\n disabled?: boolean;\n};\n\n/** Props for {@link PromptInputTextarea} — clay `TextArea` props; `className` styles the wrapper. */\nexport type PromptInputTextareaProps = TextAreaProps;\n\n/** Props for {@link PromptInputFooter}. */\nexport type PromptInputFooterProps = HTMLAttributes<HTMLDivElement>;\n\n/** Props for {@link PromptInputTools}. */\nexport type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;\n\n/** Props for {@link PromptInputSubmit}. */\nexport interface PromptInputSubmitProps {\n /** Button label — pass a localized string (i18n) from the consumer. @default \"Submit\" */\n label?: string;\n /** Drives the button color: `error` renders the red error intent, otherwise primary. @default \"ready\" */\n status?: ChatStatus;\n /** Whether the button is non-interactive (e.g. empty input or a generation in flight). */\n disabled?: boolean;\n /** Additional CSS class name. */\n className?: string;\n}\n","import { useEffect, useRef } from \"react\";\n\nimport { prefersReducedMotion } from \"@/utils/prefers-reduced-motion\";\n\nimport type { MouseGridProps } from \"./types\";\n\n/**\n * Absolutely-positioned grid backdrop, confined to its nearest positioned ancestor (give the\n * parent `position: relative` + `overflow: hidden` to clip it). The grid is revealed only near the\n * cursor via a radial mask whose center is driven by the `--vibe-mx` / `--vibe-my` CSS vars,\n * updated by a rAF-throttled `mousemove` listener. Renders behind all content (`z-0`) and never\n * blocks pointer events.\n *\n * Cursor coordinates are translated from viewport space (`clientX` / `clientY`) into layer-local\n * space (minus the layer's `getBoundingClientRect()` origin) and written onto the layer element\n * itself — not the document root — so the glow tracks correctly even though the layer no longer\n * fills the viewport. The rect read happens inside the rAF so it batches with paint.\n *\n * Pure `useEffect` + `requestAnimationFrame`, no animation library. The `.vibe-mouse-grid-layer` /\n * `.vibe-mouse-grid` / `.vibe-mouse-glow` styles ship in `@shapesos/clay/blocks/styles.css`.\n *\n * With `autoDrift`, a second rAF loop wanders the glow center along a slow sum-of-sines path\n * whenever the cursor is outside the layer; it yields the moment the cursor re-enters (the\n * mousemove handler owns the vars then) and resumes on exit. Suppressed under `prefers-reduced-motion`.\n *\n * When `glowColor` is an array of 2+ colors, a `setInterval` cycles the `--vibe-glow-color` var\n * through the list every `colorCycleMs`; the `.vibe-mouse-glow` `background-color` transition\n * (duration = `--vibe-glow-cycle`) crossfades between them. Also suppressed under\n * `prefers-reduced-motion` (first color applied flat).\n */\nexport function MouseGrid({ glowColor, colorCycleMs = 4000, autoDrift = false }: MouseGridProps = {}) {\n const layerRef = useRef<HTMLDivElement>(null);\n const glowRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n // Drift only runs when `autoDrift` is on AND motion isn't reduced. When it's off, the cursor is\n // tracked normally even outside the layer — so reduced-motion users don't get a frozen glow.\n const driftEnabled = autoDrift && !prefersReducedMotion();\n\n let raf = 0;\n let driftRaf = 0;\n let lastDriftTime = 0;\n let clientX = 0;\n let clientY = 0;\n let pointerInside = false;\n // Last applied glow center (layer-local px). Tracked across both modes so drift can resume from\n // wherever the cursor left off instead of snapping to the wander path's current value.\n let centerX = 0;\n let centerY = 0;\n let hasCenter = false;\n\n function setCenter(x: number, y: number) {\n const layer = layerRef.current;\n if (!layer) {\n return;\n }\n centerX = x;\n centerY = y;\n hasCenter = true;\n layer.style.setProperty(\"--vibe-mx\", `${x}px`);\n layer.style.setProperty(\"--vibe-my\", `${y}px`);\n }\n function flush() {\n raf = 0;\n const layer = layerRef.current;\n if (!layer) {\n return;\n }\n // While drift is active and the cursor is outside the layer, the drift loop owns the center —\n // don't let the (out-of-bounds) cursor position fight it. Otherwise always track the cursor.\n if (driftEnabled && !pointerInside) {\n return;\n }\n const rect = layer.getBoundingClientRect();\n setCenter(clientX - rect.left, clientY - rect.top);\n }\n function onMove(event: MouseEvent) {\n clientX = event.clientX;\n clientY = event.clientY;\n const layer = layerRef.current;\n if (layer) {\n const rect = layer.getBoundingClientRect();\n pointerInside =\n clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n // Run the drift loop only when it has work to do (cursor outside) — pause it while inside.\n if (pointerInside) {\n stopDrift();\n } else {\n startDrift();\n }\n if (raf) {\n return;\n }\n raf = requestAnimationFrame(flush);\n }\n // The cursor can leave the layer without another `mousemove` ever firing — e.g. when the layer\n // fills the viewport (or Storybook's preview iframe) and the pointer exits it entirely. Reset\n // the flag on document exit so drift resumes instead of freezing at the last in-bounds frame.\n function onPointerLeave() {\n pointerInside = false;\n startDrift();\n }\n function drift(time: number) {\n // Frame-rate-independent ease: convert elapsed ms into a per-frame factor so the glow reaches\n // the wander target in the same wall-clock time at 60 Hz and 120 Hz (a fixed factor would be\n // ~2× snappier on high-refresh displays). First frame falls back to a ~60 Hz delta.\n const delta = lastDriftTime ? time - lastDriftTime : 16.67;\n lastDriftTime = time;\n driftRaf = requestAnimationFrame(drift);\n const layer = layerRef.current;\n if (!layer) {\n return;\n }\n const { width, height } = layer.getBoundingClientRect();\n if (!hasCenter) {\n centerX = width / 2;\n centerY = height / 2;\n }\n // Organic wander: two sines per axis, slow + offset, kept inside ~[6%, 94%] of the bounds.\n const targetX = width * (0.5 + 0.32 * Math.sin(time * 0.00037) + 0.12 * Math.sin(time * 0.00091));\n const targetY = height * (0.5 + 0.32 * Math.cos(time * 0.00041) + 0.12 * Math.sin(time * 0.0011));\n // ~0.04 per frame at 60 Hz, but resolution-stable. Lets drift resume continuously from the\n // cursor's exit point with no jump; the target moves far slower, so amplitude is intact.\n const alpha = 1 - Math.exp(-0.0025 * delta);\n setCenter(centerX + (targetX - centerX) * alpha, centerY + (targetY - centerY) * alpha);\n }\n function startDrift() {\n if (!driftEnabled || driftRaf) {\n return;\n }\n lastDriftTime = 0;\n driftRaf = requestAnimationFrame(drift);\n }\n function stopDrift() {\n if (driftRaf) {\n cancelAnimationFrame(driftRaf);\n driftRaf = 0;\n }\n }\n\n window.addEventListener(\"mousemove\", onMove, { passive: true });\n document.documentElement.addEventListener(\"mouseleave\", onPointerLeave);\n // No pointer has been seen yet → idle, so start drifting immediately when enabled.\n startDrift();\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n document.documentElement.removeEventListener(\"mouseleave\", onPointerLeave);\n if (raf) {\n cancelAnimationFrame(raf);\n }\n stopDrift();\n };\n }, [autoDrift]);\n\n // Normalize the single `glowColor` prop (a CSS color, a list of colors, or nothing) to an array,\n // then serialize it: an inline array literal is a fresh ref each render, so keying the effect on\n // the serialized content means only an actual color change re-runs it — not unrelated re-renders.\n const colorList = Array.isArray(glowColor) ? glowColor : glowColor != null ? [glowColor] : [];\n const glowColorsKey = JSON.stringify(colorList);\n\n // Single owner of `--vibe-glow-color`: applies the first/only color flat, and crossfades through\n // the list when there are 2+. Driving it from one effect (rather than a cycle vs. a React inline\n // style) lets teardown reliably remove the var so a stale cycled color can't stick when cycling\n // stops — it reverts to the CSS default instead.\n useEffect(() => {\n const glow = glowRef.current;\n if (!glow) {\n return;\n }\n const colors = (JSON.parse(glowColorsKey) as string[]).filter(Boolean);\n const staticColor = colors[0];\n if (staticColor) {\n // Apply the first/static color flat (cycle duration still 0ms) so it doesn't fade in.\n glow.style.setProperty(\"--vibe-glow-color\", staticColor);\n }\n const clearColor = () => glow.style.removeProperty(\"--vibe-glow-color\");\n if (colors.length < 2 || prefersReducedMotion()) {\n return staticColor ? clearColor : undefined;\n }\n // Enable the crossfade next frame, then advance the color on each interval. The transition\n // duration equals the interval, so the blob is always smoothly in transit between colors.\n let index = 0;\n const raf = requestAnimationFrame(() => glow.style.setProperty(\"--vibe-glow-cycle\", `${colorCycleMs}ms`));\n const timer = setInterval(() => {\n index = (index + 1) % colors.length;\n glow.style.setProperty(\"--vibe-glow-color\", colors[index]);\n }, colorCycleMs);\n return () => {\n cancelAnimationFrame(raf);\n clearInterval(timer);\n glow.style.removeProperty(\"--vibe-glow-cycle\");\n clearColor();\n };\n }, [glowColorsKey, colorCycleMs]);\n\n return (\n <div ref={layerRef} aria-hidden=\"true\" className=\"vibe-mouse-grid-layer\">\n <div className=\"vibe-mouse-grid\" />\n <div ref={glowRef} className=\"vibe-mouse-glow\" />\n </div>\n );\n}\n","/**\n * Whether the user has requested reduced motion via the OS / browser\n * (`(prefers-reduced-motion: reduce)`). SSR-safe — use it to gate any optional animation so it\n * doesn't run for users who've opted out.\n *\n * @returns `true` when reduced motion is requested; `false` otherwise (including non-browser /\n * `matchMedia`-less environments, where there's no preference to honor).\n */\nexport function prefersReducedMotion(): boolean {\n return typeof window !== \"undefined\" && typeof window.matchMedia === \"function\"\n ? window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n : false;\n}\n","import { useCallback } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { SuggestionProps, SuggestionsProps } from \"./types\";\n\n/* ------------------------------------------------------------------------------------------------\n * Vendored + slimmed from `@vercel/ai-elements`'s `suggestion` registry entry. Upstream wraps the\n * pills in a shadcn `ScrollArea` and renders each as a shadcn `Button variant=\"outline\"`; clay has\n * neither, so this ships a dependency-free row (`flex flex-wrap`) of Tailwind-styled pills using\n * clay's named color tokens. Engine note: this is vendored ai-elements chrome, so it's Tailwind —\n * clay's `Button` is styled-components with fixed radii and can't produce this 12px pill.\n * ------------------------------------------------------------------------------------------------ */\n\n/**\n * A centered, wrapping row of {@link Suggestion} pills — e.g. starter prompts beneath a composer.\n * Compose `Suggestion` children inside.\n */\nexport const Suggestions = ({ className, ...props }: SuggestionsProps) => (\n <div className={cn(\"flex flex-wrap items-center justify-center gap-3\", className)} {...props} />\n);\n\n/**\n * A single tappable suggestion pill. Renders `children` if provided, otherwise the `suggestion`\n * text, and calls `onClick(suggestion)` when clicked.\n */\nexport const Suggestion = ({ suggestion, onClick, className, children, ...props }: SuggestionProps) => {\n const handleClick = useCallback(() => onClick?.(suggestion), [onClick, suggestion]);\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n className={cn(\n \"cursor-pointer whitespace-nowrap rounded-[12px] border border-brown-30 bg-white px-4 py-2 text-sm text-brown-100 transition-colors hover:bg-brown-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brown-40\",\n className\n )}\n {...props}\n >\n {children ?? suggestion}\n </button>\n );\n};\n"],"mappings":"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","names":["PromptInputTextarea","jsx","useCallback","jsx","useCallback"]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Block,
|
|
3
3
|
BlockServices
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UZKNSAAD.js";
|
|
5
5
|
import {
|
|
6
6
|
IconCheck,
|
|
7
7
|
IconCopy,
|
|
8
8
|
IconThumbDown,
|
|
9
9
|
IconThumbUp
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2YCBDSPK.js";
|
|
11
11
|
import {
|
|
12
12
|
IconButton
|
|
13
13
|
} from "./chunk-PYDVANWI.js";
|
|
@@ -220,4 +220,4 @@ export {
|
|
|
220
220
|
MESSAGE_ROLE,
|
|
221
221
|
ChatMessage
|
|
222
222
|
};
|
|
223
|
-
//# sourceMappingURL=chunk-
|
|
223
|
+
//# sourceMappingURL=chunk-GAZRZZRF.js.map
|
|
@@ -98,6 +98,11 @@ var CHART_PALETTE = [
|
|
|
98
98
|
];
|
|
99
99
|
var OTHERS_SLICE_COLOR = colors["brown-60"];
|
|
100
100
|
var DEFAULT_OTHERS_CATEGORY_LABELS = ["others", "other"];
|
|
101
|
+
var NONE_CATEGORY_VALUES = ["", "(none)", "none", "null", "undefined", "n/a", "-"];
|
|
102
|
+
function isNoneCategory(value) {
|
|
103
|
+
if (value === null || value === void 0) return true;
|
|
104
|
+
return NONE_CATEGORY_VALUES.includes(String(value).trim().toLowerCase());
|
|
105
|
+
}
|
|
101
106
|
function isOthersCategory(value, labels) {
|
|
102
107
|
if (value === null || value === void 0) return false;
|
|
103
108
|
if (labels.length === 0) return false;
|
|
@@ -171,6 +176,13 @@ function truncateLabel(value, maxChars) {
|
|
|
171
176
|
if (s.length <= maxChars) return s;
|
|
172
177
|
return s.slice(0, Math.max(1, maxChars - 1)).trimEnd() + "\u2026";
|
|
173
178
|
}
|
|
179
|
+
var compactNumberFormat = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
|
|
180
|
+
function compactNumberTickFormatter(value) {
|
|
181
|
+
if (value === null || value === void 0) return "";
|
|
182
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
183
|
+
if (!Number.isFinite(n)) return String(value);
|
|
184
|
+
return compactNumberFormat.format(n);
|
|
185
|
+
}
|
|
174
186
|
function evenlyDistributedVisibleIndices(n, target) {
|
|
175
187
|
if (n <= target) {
|
|
176
188
|
return new Set(Array.from({ length: n }, (_, i) => i));
|
|
@@ -196,6 +208,11 @@ function defaultLineChartTickFormatter(categoryCount, visibleIndices) {
|
|
|
196
208
|
|
|
197
209
|
// src/components/chart/bar-chart/bar-chart.tsx
|
|
198
210
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
211
|
+
var STACK_TOTAL_KEY = "__stackTotal";
|
|
212
|
+
var VALUE_LABEL_HEADROOM = 20;
|
|
213
|
+
function sumSeries(row, keys) {
|
|
214
|
+
return keys.reduce((sum, key) => sum + (Number(row[key]) || 0), 0);
|
|
215
|
+
}
|
|
199
216
|
function BarChart({
|
|
200
217
|
data,
|
|
201
218
|
xKey,
|
|
@@ -221,15 +238,24 @@ function BarChart({
|
|
|
221
238
|
[data, seriesKeys]
|
|
222
239
|
);
|
|
223
240
|
const resolvedShowLegend = showLegend ?? series.length > 1;
|
|
224
|
-
const resolvedShowValueLabels = showValueLabels ??
|
|
241
|
+
const resolvedShowValueLabels = showValueLabels ?? true;
|
|
242
|
+
const plotData = useMemo(() => {
|
|
243
|
+
if (!stacked || !resolvedShowValueLabels) return coercedData;
|
|
244
|
+
return coercedData.map((row) => ({ ...row, [STACK_TOTAL_KEY]: sumSeries(row, seriesKeys) }));
|
|
245
|
+
}, [coercedData, stacked, resolvedShowValueLabels, seriesKeys]);
|
|
225
246
|
const resolvedAngle = xAxisAngle ?? (coercedData.length >= 8 ? -30 : 0);
|
|
226
247
|
const resolvedTickFormatter = useMemo(
|
|
227
248
|
() => xAxisTickFormatter ?? defaultBarTickFormatter(coercedData.length),
|
|
228
249
|
[xAxisTickFormatter, coercedData.length]
|
|
229
250
|
);
|
|
230
251
|
const angledLabelPad = resolvedAngle === 0 ? 0 : Math.min(Math.abs(resolvedAngle) * 0.7, 40);
|
|
231
|
-
const
|
|
232
|
-
|
|
252
|
+
const valueLabelHeadroom = resolvedShowValueLabels ? VALUE_LABEL_HEADROOM : 0;
|
|
253
|
+
const chartMargin = {
|
|
254
|
+
...CHART_MARGIN,
|
|
255
|
+
top: CHART_MARGIN.top + valueLabelHeadroom,
|
|
256
|
+
bottom: CHART_MARGIN.bottom + angledLabelPad
|
|
257
|
+
};
|
|
258
|
+
return /* @__PURE__ */ jsx4(ChartContainer, { height, width, className, children: /* @__PURE__ */ jsxs3(RechartsBarChart, { data: plotData, margin: chartMargin, barCategoryGap: "22%", children: [
|
|
233
259
|
showGrid ? /* @__PURE__ */ jsx4(CartesianGrid, { ...CHART_GRID_PROPS }) : null,
|
|
234
260
|
showXAxis ? /* @__PURE__ */ jsx4(
|
|
235
261
|
XAxis,
|
|
@@ -244,7 +270,7 @@ function BarChart({
|
|
|
244
270
|
tickFormatter: resolvedTickFormatter
|
|
245
271
|
}
|
|
246
272
|
) : null,
|
|
247
|
-
showYAxis ? /* @__PURE__ */ jsx4(YAxis, { ...CHART_AXIS_PROPS, width: 40 }) : null,
|
|
273
|
+
showYAxis ? /* @__PURE__ */ jsx4(YAxis, { ...CHART_AXIS_PROPS, width: 40, tickFormatter: compactNumberTickFormatter }) : null,
|
|
248
274
|
showTooltip ? /* @__PURE__ */ jsx4(Tooltip, { cursor: { fill: BAR_TOOLTIP_CURSOR_FILL }, content: /* @__PURE__ */ jsx4(ChartTooltipContent, {}) }) : null,
|
|
249
275
|
resolvedShowLegend ? /* @__PURE__ */ jsx4(
|
|
250
276
|
Legend,
|
|
@@ -256,18 +282,23 @@ function BarChart({
|
|
|
256
282
|
content: /* @__PURE__ */ jsx4(ChartLegendContent, { iconType: "square" })
|
|
257
283
|
}
|
|
258
284
|
) : null,
|
|
259
|
-
series.map((s, i) =>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
285
|
+
series.map((s, i) => {
|
|
286
|
+
const isTopSegment = i === series.length - 1;
|
|
287
|
+
const labelDataKey = stacked ? STACK_TOTAL_KEY : s.key;
|
|
288
|
+
const showLabel = resolvedShowValueLabels && (!stacked || isTopSegment);
|
|
289
|
+
return /* @__PURE__ */ jsx4(
|
|
290
|
+
Bar,
|
|
291
|
+
{
|
|
292
|
+
dataKey: s.key,
|
|
293
|
+
name: s.label,
|
|
294
|
+
fill: colorForSeriesIndex(i, palette),
|
|
295
|
+
stackId: stacked ? "default" : s.key,
|
|
296
|
+
radius: barRadius(stacked, i, series.length),
|
|
297
|
+
children: showLabel ? /* @__PURE__ */ jsx4(LabelList, { dataKey: labelDataKey, position: "top", style: CHART_VALUE_LABEL_STYLE }) : null
|
|
298
|
+
},
|
|
299
|
+
s.key
|
|
300
|
+
);
|
|
301
|
+
})
|
|
271
302
|
] }) });
|
|
272
303
|
}
|
|
273
304
|
function barRadius(stacked, seriesIndex, seriesCount) {
|
|
@@ -340,7 +371,7 @@ function LineChart({
|
|
|
340
371
|
tickFormatter: resolvedTickFormatter
|
|
341
372
|
}
|
|
342
373
|
) : null,
|
|
343
|
-
showYAxis ? /* @__PURE__ */ jsx5(YAxis2, { ...CHART_AXIS_PROPS, width: 40 }) : null,
|
|
374
|
+
showYAxis ? /* @__PURE__ */ jsx5(YAxis2, { ...CHART_AXIS_PROPS, width: 40, tickFormatter: compactNumberTickFormatter }) : null,
|
|
344
375
|
showTooltip ? /* @__PURE__ */ jsx5(Tooltip2, { content: /* @__PURE__ */ jsx5(ChartTooltipContent, {}) }) : null,
|
|
345
376
|
resolvedShowLegend ? /* @__PURE__ */ jsx5(
|
|
346
377
|
Legend2,
|
|
@@ -375,6 +406,42 @@ function LineChart({
|
|
|
375
406
|
// src/components/chart/pie-chart/pie-chart.tsx
|
|
376
407
|
import { useMemo as useMemo3 } from "react";
|
|
377
408
|
import { Cell, Legend as Legend3, Pie, PieChart as RechartsPieChart, Tooltip as Tooltip3 } from "recharts";
|
|
409
|
+
|
|
410
|
+
// src/components/chart/pie-chart/utils.ts
|
|
411
|
+
function mergedOthersLabel(leftoverRows, categoryKey, othersCategoryLabels) {
|
|
412
|
+
const explicit = leftoverRows.find((row) => isOthersCategory(row[categoryKey], othersCategoryLabels));
|
|
413
|
+
if (explicit) return String(explicit[categoryKey]);
|
|
414
|
+
const fallback = othersCategoryLabels[0] ?? "Other";
|
|
415
|
+
return fallback.charAt(0).toUpperCase() + fallback.slice(1);
|
|
416
|
+
}
|
|
417
|
+
function buildPieSlices(data, categoryKey, valueKey, othersCategoryLabels, palette) {
|
|
418
|
+
const namedRows = [];
|
|
419
|
+
const namedColors = [];
|
|
420
|
+
const leftoverRows = [];
|
|
421
|
+
for (const row of data) {
|
|
422
|
+
const isLeftover = othersCategoryLabels.length > 0 && (isNoneCategory(row[categoryKey]) || isOthersCategory(row[categoryKey], othersCategoryLabels));
|
|
423
|
+
if (isLeftover) {
|
|
424
|
+
leftoverRows.push(row);
|
|
425
|
+
} else {
|
|
426
|
+
namedRows.push(row);
|
|
427
|
+
namedColors.push(colorForSeriesIndex(namedRows.length - 1, palette));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (leftoverRows.length === 0) {
|
|
431
|
+
return { orderedData: namedRows, sliceColors: namedColors };
|
|
432
|
+
}
|
|
433
|
+
const total = leftoverRows.reduce((sum, row) => sum + (Number(row[valueKey]) || 0), 0);
|
|
434
|
+
const mergedRow = {
|
|
435
|
+
[categoryKey]: mergedOthersLabel(leftoverRows, categoryKey, othersCategoryLabels),
|
|
436
|
+
[valueKey]: total
|
|
437
|
+
};
|
|
438
|
+
return {
|
|
439
|
+
orderedData: [...namedRows, mergedRow],
|
|
440
|
+
sliceColors: [...namedColors, OTHERS_SLICE_COLOR]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/components/chart/pie-chart/pie-chart.tsx
|
|
378
445
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
379
446
|
function PieChart({
|
|
380
447
|
data,
|
|
@@ -393,23 +460,7 @@ function PieChart({
|
|
|
393
460
|
}) {
|
|
394
461
|
const { orderedData, sliceColors } = useMemo3(() => {
|
|
395
462
|
const coerced = coerceNumericColumns(data, [valueKey]);
|
|
396
|
-
|
|
397
|
-
const namedColors = [];
|
|
398
|
-
const othersRows = [];
|
|
399
|
-
const othersColors = [];
|
|
400
|
-
for (const row of coerced) {
|
|
401
|
-
if (isOthersCategory(row[categoryKey], othersCategoryLabels)) {
|
|
402
|
-
othersRows.push(row);
|
|
403
|
-
othersColors.push(OTHERS_SLICE_COLOR);
|
|
404
|
-
} else {
|
|
405
|
-
namedRows.push(row);
|
|
406
|
-
namedColors.push(colorForSeriesIndex(namedRows.length - 1, palette));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return {
|
|
410
|
-
orderedData: [...namedRows, ...othersRows],
|
|
411
|
-
sliceColors: [...namedColors, ...othersColors]
|
|
412
|
-
};
|
|
463
|
+
return buildPieSlices(coerced, categoryKey, valueKey, othersCategoryLabels, palette);
|
|
413
464
|
}, [data, valueKey, categoryKey, othersCategoryLabels, palette]);
|
|
414
465
|
return /* @__PURE__ */ jsx6(ChartContainer, { height, width, className, children: /* @__PURE__ */ jsxs5(RechartsPieChart, { margin: PIE_CHART_MARGIN, children: [
|
|
415
466
|
showTooltip ? /* @__PURE__ */ jsx6(Tooltip3, { content: /* @__PURE__ */ jsx6(ChartTooltipContent, {}) }) : null,
|
|
@@ -474,4 +525,4 @@ export {
|
|
|
474
525
|
LineChart,
|
|
475
526
|
PieChart
|
|
476
527
|
};
|
|
477
|
-
//# sourceMappingURL=chunk-
|
|
528
|
+
//# sourceMappingURL=chunk-U3IXCSV6.js.map
|