@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.
Files changed (37) hide show
  1. package/README.md +14 -0
  2. package/dist/ai-elements.cjs +112 -7
  3. package/dist/ai-elements.cjs.map +1 -1
  4. package/dist/ai-elements.d.cts +30 -2
  5. package/dist/ai-elements.d.ts +30 -2
  6. package/dist/ai-elements.js +1 -1
  7. package/dist/artifacts.cjs +85 -34
  8. package/dist/artifacts.cjs.map +1 -1
  9. package/dist/artifacts.js +2 -2
  10. package/dist/blocks.cjs +85 -34
  11. package/dist/blocks.cjs.map +1 -1
  12. package/dist/blocks.css +1 -1
  13. package/dist/blocks.js +3 -3
  14. package/dist/chart.cjs +85 -34
  15. package/dist/chart.cjs.map +1 -1
  16. package/dist/chart.d.cts +12 -10
  17. package/dist/chart.d.ts +12 -10
  18. package/dist/chart.js +1 -1
  19. package/dist/chat.cjs +85 -34
  20. package/dist/chat.cjs.map +1 -1
  21. package/dist/chat.js +4 -4
  22. package/dist/{chunk-XFJ6XMJZ.js → chunk-2YCBDSPK.js} +2 -2
  23. package/dist/{chunk-BR5S37SC.js → chunk-4VE6ZXXW.js} +113 -8
  24. package/dist/chunk-4VE6ZXXW.js.map +1 -0
  25. package/dist/{chunk-3THOTQO3.js → chunk-GAZRZZRF.js} +3 -3
  26. package/dist/{chunk-YZB6YXKK.js → chunk-U3IXCSV6.js} +86 -35
  27. package/dist/chunk-U3IXCSV6.js.map +1 -0
  28. package/dist/{chunk-MDAWYZBI.js → chunk-UZKNSAAD.js} +2 -2
  29. package/dist/index.cjs +197 -41
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +5 -5
  32. package/package.json +1 -1
  33. package/dist/chunk-BR5S37SC.js.map +0 -1
  34. package/dist/chunk-YZB6YXKK.js.map +0 -1
  35. /package/dist/{chunk-XFJ6XMJZ.js.map → chunk-2YCBDSPK.js.map} +0 -0
  36. /package/dist/{chunk-3THOTQO3.js.map → chunk-GAZRZZRF.js.map} +0 -0
  37. /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-3THOTQO3.js";
9
- import "./chunk-MDAWYZBI.js";
8
+ } from "./chunk-GAZRZZRF.js";
9
+ import "./chunk-UZKNSAAD.js";
10
10
  import "./chunk-NMKKU2UG.js";
11
- import "./chunk-XFJ6XMJZ.js";
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-YZB6YXKK.js";
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-YZB6YXKK.js";
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-XFJ6XMJZ.js.map
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
- layer.style.setProperty("--vibe-mx", `${clientX - rect.left}px`);
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 glowStyle = glowColor ? { "--vibe-glow-color": glowColor } : void 0;
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", style: glowStyle })
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-full 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",
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-BR5S37SC.js.map
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-MDAWYZBI.js";
4
+ } from "./chunk-UZKNSAAD.js";
5
5
  import {
6
6
  IconCheck,
7
7
  IconCopy,
8
8
  IconThumbDown,
9
9
  IconThumbUp
10
- } from "./chunk-XFJ6XMJZ.js";
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-3THOTQO3.js.map
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 ?? (!stacked && series.length === 1);
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 chartMargin = { ...CHART_MARGIN, bottom: CHART_MARGIN.bottom + angledLabelPad };
232
- return /* @__PURE__ */ jsx4(ChartContainer, { height, width, className, children: /* @__PURE__ */ jsxs3(RechartsBarChart, { data: coercedData, margin: chartMargin, barCategoryGap: "22%", children: [
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) => /* @__PURE__ */ jsx4(
260
- Bar,
261
- {
262
- dataKey: s.key,
263
- name: s.label,
264
- fill: colorForSeriesIndex(i, palette),
265
- stackId: stacked ? "default" : s.key,
266
- radius: barRadius(stacked, i, series.length),
267
- children: resolvedShowValueLabels ? /* @__PURE__ */ jsx4(LabelList, { dataKey: s.key, position: "top", style: CHART_VALUE_LABEL_STYLE }) : null
268
- },
269
- s.key
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
- const namedRows = [];
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-YZB6YXKK.js.map
528
+ //# sourceMappingURL=chunk-U3IXCSV6.js.map