@shapesos/clay 0.10.0 → 0.12.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 +60 -0
- package/dist/artifacts.cjs +1467 -0
- package/dist/artifacts.cjs.map +1 -0
- package/dist/artifacts.d.cts +89 -0
- package/dist/artifacts.d.ts +89 -0
- package/dist/artifacts.js +33 -0
- package/dist/blocks.cjs +1418 -68
- package/dist/blocks.cjs.map +1 -1
- package/dist/blocks.css +2 -0
- package/dist/blocks.d.cts +36 -11
- package/dist/blocks.d.ts +36 -11
- package/dist/blocks.js +18 -5
- package/dist/button.d.cts +2 -2
- package/dist/button.d.ts +2 -2
- package/dist/chart.cjs +594 -0
- package/dist/chart.cjs.map +1 -0
- package/dist/chart.d.cts +439 -0
- package/dist/chart.d.ts +439 -0
- package/dist/chart.js +32 -0
- package/dist/chart.js.map +1 -0
- package/dist/chat.cjs +1462 -312
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.d.cts +28 -10
- package/dist/chat.d.ts +28 -10
- package/dist/chat.js +10 -3
- package/dist/{chunk-R3BGPOAM.js → chunk-36CB624W.js} +53 -45
- package/dist/chunk-36CB624W.js.map +1 -0
- package/dist/chunk-4MZZH3WX.js +93 -0
- package/dist/chunk-4MZZH3WX.js.map +1 -0
- package/dist/chunk-AQEJRMRN.js +1 -0
- package/dist/chunk-AQEJRMRN.js.map +1 -0
- package/dist/{chunk-WS4IPADR.js → chunk-FFX3CAOX.js} +41 -109
- package/dist/chunk-FFX3CAOX.js.map +1 -0
- package/dist/chunk-JGMN6W72.js +12 -0
- package/dist/chunk-JGMN6W72.js.map +1 -0
- package/dist/{chunk-27GJUWVN.js → chunk-JJUIBBBU.js} +14 -8
- package/dist/chunk-JJUIBBBU.js.map +1 -0
- package/dist/{chunk-MLCRDVQ2.js → chunk-OBOXCBDL.js} +13 -5
- package/dist/chunk-OBOXCBDL.js.map +1 -0
- package/dist/chunk-P6GUNIAE.js +11 -0
- package/dist/chunk-P6GUNIAE.js.map +1 -0
- package/dist/chunk-QXGYMDIA.js +477 -0
- package/dist/chunk-QXGYMDIA.js.map +1 -0
- package/dist/chunk-Z5JWF24N.js +719 -0
- package/dist/chunk-Z5JWF24N.js.map +1 -0
- package/dist/icon.cjs +12 -4
- package/dist/icon.cjs.map +1 -1
- package/dist/icon.d.cts +19 -6
- package/dist/icon.d.ts +19 -6
- package/dist/icon.js +1 -1
- package/dist/index.cjs +1520 -332
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +58 -25
- package/dist/lottie.d.cts +2 -2
- package/dist/lottie.d.ts +2 -2
- package/dist/table.cjs +142 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +44 -0
- package/dist/table.d.ts +44 -0
- package/dist/table.js +25 -0
- package/dist/table.js.map +1 -0
- package/dist/text-area.d.cts +2 -2
- package/dist/text-area.d.ts +2 -2
- package/dist/types-B2aYk82c.d.cts +29 -0
- package/dist/types-B2aYk82c.d.ts +29 -0
- package/dist/types-C0BvwliI.d.cts +332 -0
- package/dist/types-C5bFH4v3.d.ts +332 -0
- package/dist/types-DCutaXjZ.d.cts +83 -0
- package/dist/types-uPfn67Dc.d.ts +83 -0
- package/dist/utils.cjs +14 -7
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +18 -1
- package/dist/utils.d.ts +18 -1
- package/dist/utils.js +6 -4
- package/package.json +41 -4
- package/dist/chunk-27GJUWVN.js.map +0 -1
- package/dist/chunk-MLCRDVQ2.js.map +0 -1
- package/dist/chunk-OKPNST44.js +0 -1
- package/dist/chunk-R3BGPOAM.js.map +0 -1
- package/dist/chunk-WS4IPADR.js.map +0 -1
- package/dist/types-Q9aqd9nq.d.cts +0 -34
- package/dist/types-Q9aqd9nq.d.ts +0 -34
- /package/dist/{chunk-OKPNST44.js.map → artifacts.js.map} +0 -0
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
markdownToPlainText
|
|
3
|
-
} from "./chunk-NMKKU2UG.js";
|
|
4
1
|
import {
|
|
5
2
|
Block,
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
3
|
+
BlockServices
|
|
4
|
+
} from "./chunk-36CB624W.js";
|
|
8
5
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
IconCheck,
|
|
7
|
+
IconCopy,
|
|
8
|
+
IconThumbDown,
|
|
9
|
+
IconThumbUp
|
|
10
|
+
} from "./chunk-Z5JWF24N.js";
|
|
11
11
|
import {
|
|
12
12
|
typographyMixin,
|
|
13
13
|
typographyTypes
|
|
14
14
|
} from "./chunk-SV24ONND.js";
|
|
15
|
+
import {
|
|
16
|
+
IconButton
|
|
17
|
+
} from "./chunk-OBOXCBDL.js";
|
|
15
18
|
import {
|
|
16
19
|
colors
|
|
17
20
|
} from "./chunk-JF3P66JF.js";
|
|
@@ -19,6 +22,23 @@ import {
|
|
|
19
22
|
// src/components/chat/chat-message/chat-message.tsx
|
|
20
23
|
import { useMemo } from "react";
|
|
21
24
|
|
|
25
|
+
// src/components/chat/hooks/copy-message-text.ts
|
|
26
|
+
function copyMessageText(message) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
for (const block of message.blocks) {
|
|
29
|
+
const service = BlockServices[block.type];
|
|
30
|
+
if (!service) continue;
|
|
31
|
+
if (!service.toClipboardText) continue;
|
|
32
|
+
const text = service.toClipboardText(block);
|
|
33
|
+
if (text) parts.push(text);
|
|
34
|
+
}
|
|
35
|
+
if (parts.length === 0) return message.fallbackText;
|
|
36
|
+
return parts.join("\n\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/components/chat/chat-message-actions/chat-message-actions.tsx
|
|
40
|
+
import { useCallback as useCallback2 } from "react";
|
|
41
|
+
|
|
22
42
|
// src/components/chat/chat-context/chat-context.ts
|
|
23
43
|
import { createContext, useContext } from "react";
|
|
24
44
|
var ChatContext = createContext(null);
|
|
@@ -30,81 +50,6 @@ function useChatContext() {
|
|
|
30
50
|
return context;
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
// src/components/chat/chat-message-actions/chat-message-actions.tsx
|
|
34
|
-
import { useCallback as useCallback2 } from "react";
|
|
35
|
-
|
|
36
|
-
// node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
|
|
37
|
-
import { forwardRef, createElement } from "react";
|
|
38
|
-
|
|
39
|
-
// node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs
|
|
40
|
-
var defaultAttributes = {
|
|
41
|
-
outline: {
|
|
42
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
43
|
-
width: 24,
|
|
44
|
-
height: 24,
|
|
45
|
-
viewBox: "0 0 24 24",
|
|
46
|
-
fill: "none",
|
|
47
|
-
stroke: "currentColor",
|
|
48
|
-
strokeWidth: 2,
|
|
49
|
-
strokeLinecap: "round",
|
|
50
|
-
strokeLinejoin: "round"
|
|
51
|
-
},
|
|
52
|
-
filled: {
|
|
53
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
54
|
-
width: 24,
|
|
55
|
-
height: 24,
|
|
56
|
-
viewBox: "0 0 24 24",
|
|
57
|
-
fill: "currentColor",
|
|
58
|
-
stroke: "none"
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs
|
|
63
|
-
var createReactComponent = (type, iconName, iconNamePascal, iconNode) => {
|
|
64
|
-
const Component = forwardRef(
|
|
65
|
-
({ color = "currentColor", size = 24, stroke = 2, title, className, children, ...rest }, ref) => createElement(
|
|
66
|
-
"svg",
|
|
67
|
-
{
|
|
68
|
-
ref,
|
|
69
|
-
...defaultAttributes[type],
|
|
70
|
-
width: size,
|
|
71
|
-
height: size,
|
|
72
|
-
className: [`tabler-icon`, `tabler-icon-${iconName}`, className].join(" "),
|
|
73
|
-
...type === "filled" ? {
|
|
74
|
-
fill: color
|
|
75
|
-
} : {
|
|
76
|
-
strokeWidth: stroke,
|
|
77
|
-
stroke: color
|
|
78
|
-
},
|
|
79
|
-
...rest
|
|
80
|
-
},
|
|
81
|
-
[
|
|
82
|
-
title && createElement("title", { key: "svg-title" }, title),
|
|
83
|
-
...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
|
|
84
|
-
...Array.isArray(children) ? children : [children]
|
|
85
|
-
]
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
Component.displayName = `${iconNamePascal}`;
|
|
89
|
-
return Component;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs
|
|
93
|
-
var __iconNode = [["path", { "d": "M5 12l5 5l10 -10", "key": "svg-0" }]];
|
|
94
|
-
var IconCheck = createReactComponent("outline", "check", "Check", __iconNode);
|
|
95
|
-
|
|
96
|
-
// node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs
|
|
97
|
-
var __iconNode2 = [["path", { "d": "M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666", "key": "svg-0" }], ["path", { "d": "M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1", "key": "svg-1" }]];
|
|
98
|
-
var IconCopy = createReactComponent("outline", "copy", "Copy", __iconNode2);
|
|
99
|
-
|
|
100
|
-
// node_modules/@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs
|
|
101
|
-
var __iconNode3 = [["path", { "d": "M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3", "key": "svg-0" }]];
|
|
102
|
-
var IconThumbDown = createReactComponent("outline", "thumb-down", "ThumbDown", __iconNode3);
|
|
103
|
-
|
|
104
|
-
// node_modules/@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs
|
|
105
|
-
var __iconNode4 = [["path", { "d": "M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3", "key": "svg-0" }]];
|
|
106
|
-
var IconThumbUp = createReactComponent("outline", "thumb-up", "ThumbUp", __iconNode4);
|
|
107
|
-
|
|
108
53
|
// src/components/chat/hooks/use-copy-to-clipboard.ts
|
|
109
54
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
110
55
|
|
|
@@ -127,7 +72,7 @@ function useCopyToClipboard() {
|
|
|
127
72
|
};
|
|
128
73
|
}, []);
|
|
129
74
|
const copy = useCallback((text) => {
|
|
130
|
-
copyToClipboard(
|
|
75
|
+
copyToClipboard(text, {
|
|
131
76
|
onSuccess: () => {
|
|
132
77
|
setIsCopied(true);
|
|
133
78
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
@@ -238,26 +183,28 @@ var MessageBubble = styled2.div`
|
|
|
238
183
|
padding: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "12px 16px" : "0"};
|
|
239
184
|
border-radius: ${({ $role }) => $role === MESSAGE_ROLE.USER ? "16px" : "0"};
|
|
240
185
|
max-width: 100%;
|
|
186
|
+
width: 100%;
|
|
241
187
|
min-width: 0;
|
|
188
|
+
/* Vertical rhythm between consecutive blocks — renderer-owned, not agent-emitted.
|
|
189
|
+
* Industry standard (Claude, ChatGPT, Vercel AI Elements): the chat renderer applies
|
|
190
|
+
* spacing via CSS; agents emit semantic blocks only. Value matches the MessageContainer
|
|
191
|
+
* gap. TODO: replace 12px with a clay spacing token when a spacing scale is added. */
|
|
192
|
+
display: flex;
|
|
193
|
+
flex-direction: column;
|
|
194
|
+
gap: 12px;
|
|
242
195
|
`;
|
|
243
196
|
|
|
244
197
|
// src/components/chat/chat-message/chat-message.tsx
|
|
245
198
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
246
199
|
function ChatMessage({ message }) {
|
|
247
|
-
const
|
|
248
|
-
const blockContextValue = useMemo(
|
|
249
|
-
() => ({
|
|
250
|
-
TableActions: ChatTableActions ? ({ tableIndex }) => /* @__PURE__ */ jsx2(ChatTableActions, { messageId: message.id, tableIndex }) : void 0
|
|
251
|
-
}),
|
|
252
|
-
[ChatTableActions, message.id]
|
|
253
|
-
);
|
|
200
|
+
const clipboardText = useMemo(() => copyMessageText(message), [message]);
|
|
254
201
|
return /* @__PURE__ */ jsx2(MessageRow, { $role: message.role, role: "article", "aria-label": `${message.role} message`, children: /* @__PURE__ */ jsxs2(MessageContainer, { $role: message.role, children: [
|
|
255
|
-
/* @__PURE__ */ jsx2(MessageBubble, { $role: message.role,
|
|
202
|
+
/* @__PURE__ */ jsx2(MessageBubble, { $role: message.role, "data-testid": "message-bubble", children: message.blocks.map((block, index) => /* @__PURE__ */ jsx2(Block, { block }, index)) }),
|
|
256
203
|
/* @__PURE__ */ jsx2(
|
|
257
204
|
ChatMessageActions,
|
|
258
205
|
{
|
|
259
206
|
messageId: message.id,
|
|
260
|
-
content:
|
|
207
|
+
content: clipboardText,
|
|
261
208
|
role: message.role,
|
|
262
209
|
isHelpful: message.isHelpful
|
|
263
210
|
}
|
|
@@ -266,26 +213,11 @@ function ChatMessage({ message }) {
|
|
|
266
213
|
}
|
|
267
214
|
|
|
268
215
|
export {
|
|
216
|
+
copyMessageText,
|
|
269
217
|
ChatContext,
|
|
270
218
|
useChatContext,
|
|
271
219
|
useCopyToClipboard,
|
|
272
220
|
MESSAGE_ROLE,
|
|
273
221
|
ChatMessage
|
|
274
222
|
};
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
@tabler/icons-react/dist/esm/defaultAttributes.mjs:
|
|
278
|
-
@tabler/icons-react/dist/esm/createReactComponent.mjs:
|
|
279
|
-
@tabler/icons-react/dist/esm/icons/IconCheck.mjs:
|
|
280
|
-
@tabler/icons-react/dist/esm/icons/IconCopy.mjs:
|
|
281
|
-
@tabler/icons-react/dist/esm/icons/IconThumbDown.mjs:
|
|
282
|
-
@tabler/icons-react/dist/esm/icons/IconThumbUp.mjs:
|
|
283
|
-
@tabler/icons-react/dist/esm/tabler-icons-react.mjs:
|
|
284
|
-
(**
|
|
285
|
-
* @license @tabler/icons-react v3.37.1 - MIT
|
|
286
|
-
*
|
|
287
|
-
* This source code is licensed under the MIT license.
|
|
288
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
289
|
-
*)
|
|
290
|
-
*/
|
|
291
|
-
//# sourceMappingURL=chunk-WS4IPADR.js.map
|
|
223
|
+
//# sourceMappingURL=chunk-FFX3CAOX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/chat/chat-message/chat-message.tsx","../src/components/chat/hooks/copy-message-text.ts","../src/components/chat/chat-message-actions/chat-message-actions.tsx","../src/components/chat/chat-context/chat-context.ts","../src/components/chat/hooks/use-copy-to-clipboard.ts","../src/utils/clipboard.ts","../src/components/chat/constants.ts","../src/components/chat/chat-message-actions/chat-message-actions-styles.ts","../src/components/chat/chat-message/chat-message-styles.ts"],"sourcesContent":["import { useMemo } from \"react\";\n\nimport { Block } from \"@/components/blocks/block\";\nimport { copyMessageText } from \"../hooks/copy-message-text\";\nimport type { ChatMessage as ChatMessageType } from \"../types\";\nimport { ChatMessageActions } from \"../chat-message-actions/chat-message-actions\";\nimport { MessageRow, MessageBubble, MessageContainer } from \"./chat-message-styles\";\n\ninterface ChatMessageProps {\n message: ChatMessageType;\n}\n\nexport function ChatMessage({ message }: ChatMessageProps) {\n // Aggregate per-block clipboard text so the copy button produces richer output than the\n // server-supplied `fallbackText` (which is typically \"[table: Title]\" for artifact blocks).\n // `useMemo` keeps the string stable across re-renders that don't change the blocks array.\n const clipboardText = useMemo(() => copyMessageText(message), [message]);\n\n return (\n <MessageRow $role={message.role} role=\"article\" aria-label={`${message.role} message`}>\n <MessageContainer $role={message.role}>\n <MessageBubble $role={message.role} data-testid=\"message-bubble\">\n {message.blocks.map((block, index) => (\n <Block key={index} block={block} />\n ))}\n </MessageBubble>\n <ChatMessageActions\n messageId={message.id}\n content={clipboardText}\n role={message.role}\n isHelpful={message.isHelpful}\n />\n </MessageContainer>\n </MessageRow>\n );\n}\n","import { BlockServices } from \"@/components/blocks/block-services\";\nimport type { ChatMessage } from \"../types\";\n\n/**\n * Aggregates the clipboard text for all blocks in a message.\n *\n * Iterates `message.blocks` in order, calls `BlockServices[block.type].toClipboardText(block)`\n * for each, and joins the results with `\"\\n\\n\"` so multi-block messages have a blank line between\n * each section (matching the visual gap rendered between blocks in the chat UI).\n *\n * Block-type-specific behaviour:\n * - `TEXT` blocks: markdown stripped to plain text via `markdownToPlainText`.\n * - `ARTIFACT_REF` blocks: delegates to the registered artifact component's `toClipboardText`\n * (e.g. the artifact title as plain text for table artifacts).\n * - Unknown block types: silently skipped (no registered service → no contribution to output).\n *\n * Falls back to `message.fallbackText` only when every block produces an empty string — this\n * guards against edge cases (empty message, all-unknown block types) while still preferring\n * the richer per-block text in the normal case.\n *\n * @param message The chat message whose blocks should be aggregated.\n * @returns A string suitable for writing to the system clipboard.\n */\nexport function copyMessageText(message: ChatMessage): string {\n const parts: string[] = [];\n\n for (const block of message.blocks) {\n const service = BlockServices[block.type];\n if (!service) continue;\n // `toClipboardText` is optional on the BlockService interface — external implementors may\n // omit it. Skip silently when absent (contributes \"\" to the aggregated output).\n if (!service.toClipboardText) continue;\n // The registry is keyed by BlockType and each service's toClipboardText is typed to its\n // concrete TBlock. The cast is safe because BlockServices[block.type] is always the matching\n // service for block.type — same soundness argument as the registry's own type erasure.\n const text = (service as { toClipboardText: (b: typeof block) => string }).toClipboardText(block);\n if (text) parts.push(text);\n }\n\n if (parts.length === 0) return message.fallbackText;\n return parts.join(\"\\n\\n\");\n}\n","import { useCallback } from \"react\";\nimport { IconCopy, IconCheck, IconThumbUp, IconThumbDown } from \"@tabler/icons-react\";\n\nimport { IconButton } from \"../../icon-button/icon-button\";\nimport { useChatContext } from \"../chat-context/chat-context\";\nimport { useCopyToClipboard } from \"../hooks/use-copy-to-clipboard\";\nimport { MESSAGE_ROLE } from \"../constants\";\nimport type { MessageRole } from \"../types\";\nimport { ActionsContainer, AnimatedAction } from \"./chat-message-actions-styles\";\n\ninterface ChatMessageActionsProps {\n className?: string;\n messageId: string;\n content: string;\n role: MessageRole;\n isHelpful?: boolean | null;\n}\n\nexport function ChatMessageActions({ className, messageId, content, role, isHelpful }: ChatMessageActionsProps) {\n const { onCopyMessage, onThumbUpClick, onThumbDownClick } = useChatContext();\n const { isCopied, copy } = useCopyToClipboard();\n\n const handleCopy = useCallback(() => {\n copy(content);\n onCopyMessage?.(messageId);\n }, [content, messageId, copy, onCopyMessage]);\n\n const handleThumbUp = useCallback(() => {\n onThumbUpClick?.(messageId, isHelpful === true ? null : true);\n }, [messageId, isHelpful, onThumbUpClick]);\n\n const handleThumbDown = useCallback(() => {\n onThumbDownClick?.(messageId, isHelpful === false ? null : false);\n }, [messageId, isHelpful, onThumbDownClick]);\n\n const isAssistant = role === MESSAGE_ROLE.ASSISTANT;\n const hasFeedback = isAssistant && (onThumbUpClick || onThumbDownClick);\n\n return (\n <ActionsContainer $role={role} className={className}>\n <IconButton\n icon={isCopied ? IconCheck : IconCopy}\n onClick={handleCopy}\n aria-label={isCopied ? \"Copied\" : \"Copy message\"}\n />\n\n {hasFeedback && (\n <AnimatedAction $visible={isHelpful !== false}>\n <IconButton\n icon={IconThumbUp}\n onClick={handleThumbUp}\n isSelected={isHelpful === true}\n aria-label=\"Good response\"\n />\n </AnimatedAction>\n )}\n\n {hasFeedback && (\n <AnimatedAction $visible={isHelpful !== true}>\n <IconButton\n icon={IconThumbDown}\n onClick={handleThumbDown}\n isSelected={isHelpful === false}\n aria-label=\"Bad response\"\n />\n </AnimatedAction>\n )}\n </ActionsContainer>\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { ChatContextValue } from \"../types\";\n\nexport const ChatContext = createContext<ChatContextValue | null>(null);\n\nexport function useChatContext(): ChatContextValue {\n const context = useContext(ChatContext);\n if (!context) {\n throw new Error(\"useChatContext must be used within a Chat.Root component\");\n }\n return context;\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\n\nimport { copyToClipboard } from \"@/utils/clipboard\";\n\nconst RESET_DELAY_MS = 2000;\n\ninterface UseCopyToClipboardResult {\n isCopied: boolean;\n copy: (text: string) => void;\n}\n\nexport function useCopyToClipboard(): UseCopyToClipboardResult {\n const [isCopied, setIsCopied] = useState(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n };\n }, []);\n\n // Write text verbatim — callers (copyMessageText + each BlockService.toClipboardText) are\n // responsible for converting their own format to clipboard-ready plain text.\n const copy = useCallback((text: string) => {\n copyToClipboard(text, {\n onSuccess: () => {\n setIsCopied(true);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n timeoutRef.current = setTimeout(() => {\n setIsCopied(false);\n timeoutRef.current = null;\n }, RESET_DELAY_MS);\n },\n });\n }, []);\n\n return { isCopied, copy };\n}\n","/** Options for the {@link copyToClipboard} function. */\ninterface CopyToClipboardOptions {\n /** Called after the text has been successfully written to the clipboard. */\n onSuccess?: () => void;\n /** Called if the clipboard write fails (e.g., permission denied). */\n onFailure?: (error: unknown) => void;\n}\n\n/**\n * Copies text to the system clipboard using the Clipboard API.\n * @param text - The string to copy.\n * @param options - Optional success/failure callbacks.\n */\nexport function copyToClipboard(text: string, options?: CopyToClipboardOptions): void {\n navigator.clipboard.writeText(text).then(\n () => options?.onSuccess?.(),\n (error) => options?.onFailure?.(error)\n );\n}\n","export const MESSAGE_ROLE = {\n USER: \"user\",\n ASSISTANT: \"assistant\",\n} as const;\n","import styled from \"styled-components\";\n\nimport { MESSAGE_ROLE } from \"../constants\";\nimport type { MessageRole } from \"../types\";\n\nconst FADE_DURATION_MS = 150;\nconst ANIMATE_DURATION_MS = 200;\n\nexport const ActionsContainer = styled.div<{ $role: MessageRole }>`\n display: flex;\n opacity: ${({ $role }) => ($role === MESSAGE_ROLE.ASSISTANT ? 1 : 0)};\n pointer-events: ${({ $role }) => ($role === MESSAGE_ROLE.ASSISTANT ? \"auto\" : \"none\")};\n transition: opacity ${FADE_DURATION_MS}ms ease;\n`;\n\nexport const AnimatedAction = styled.div<{ $visible: boolean }>`\n display: flex;\n overflow: hidden;\n max-width: ${({ $visible }) => ($visible ? \"40px\" : \"0\")};\n opacity: ${({ $visible }) => ($visible ? 1 : 0)};\n transition: max-width ${ANIMATE_DURATION_MS}ms ease, opacity ${ANIMATE_DURATION_MS}ms ease;\n`;\n","import styled from \"styled-components\";\n\nimport { colors } from \"../../../tokens/colors\";\nimport { typographyMixin, typographyTypes } from \"../../../tokens/typography\";\nimport { ActionsContainer } from \"../chat-message-actions/chat-message-actions-styles\";\nimport { MESSAGE_ROLE } from \"../constants\";\nimport type { MessageRole } from \"../types\";\n\nexport const MessageRow = styled.div<{ $role: MessageRole }>`\n display: flex;\n justify-content: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"flex-end\" : \"flex-start\")};\n\n &:hover ${ActionsContainer} {\n opacity: 1;\n pointer-events: auto;\n }\n`;\n\nexport const MessageContainer = styled.div<{ $role: MessageRole }>`\n display: flex;\n flex-direction: column;\n align-items: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"flex-end\" : \"flex-start\")};\n max-width: 90%;\n width: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"auto\" : \"100%\")};\n gap: 12px;\n`;\n\nexport const MessageBubble = styled.div<{ $role: MessageRole }>`\n ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};\n color: ${colors[\"brown-100\"]};\n background: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? colors.white : \"transparent\")};\n padding: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"12px 16px\" : \"0\")};\n border-radius: ${({ $role }) => ($role === MESSAGE_ROLE.USER ? \"16px\" : \"0\")};\n max-width: 100%;\n width: 100%;\n min-width: 0;\n /* Vertical rhythm between consecutive blocks — renderer-owned, not agent-emitted.\n * Industry standard (Claude, ChatGPT, Vercel AI Elements): the chat renderer applies\n * spacing via CSS; agents emit semantic blocks only. Value matches the MessageContainer\n * gap. TODO: replace 12px with a clay spacing token when a spacing scale is added. */\n display: flex;\n flex-direction: column;\n gap: 12px;\n`;\n"],"mappings":"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","names":["useCallback","useCallback","styled","styled","jsx","jsxs"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/components/blocks/block-context/block-context.tsx
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
var BlockContext = createContext(null);
|
|
4
|
+
function useBlockContext() {
|
|
5
|
+
return useContext(BlockContext) ?? {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
BlockContext,
|
|
10
|
+
useBlockContext
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=chunk-JGMN6W72.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/blocks/block-context/block-context.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\n\n/** Renderer-level concerns provided to descendants of any `Block`-rendering tree. */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- placeholder to preserve API while the surface area is scoped down\nexport interface BlockContextValue {}\n\nexport const BlockContext = createContext<BlockContextValue | null>(null);\n\n/** Returns the ambient block-rendering context. Returns an empty object outside a `<BlockContext.Provider>`. */\nexport function useBlockContext(): BlockContextValue {\n return useContext(BlockContext) ?? {};\n}\n"],"mappings":";AAAA,SAAS,eAAe,kBAAkB;AAMnC,IAAM,eAAe,cAAwC,IAAI;AAGjE,SAAS,kBAAqC;AACnD,SAAO,WAAW,YAAY,KAAK,CAAC;AACtC;","names":[]}
|
|
@@ -55,21 +55,27 @@ function parseMarkdownTable(markdown, tableIndex = 0) {
|
|
|
55
55
|
return null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// src/utils/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
// src/utils/escape-csv-field.ts
|
|
59
|
+
var FORMULA_TRIGGERS = /* @__PURE__ */ new Set(["=", "+", "@", " ", "\r"]);
|
|
60
|
+
function escapeCsvField(field) {
|
|
61
|
+
const needsNeutralise = field.length > 0 && FORMULA_TRIGGERS.has(field[0]);
|
|
62
|
+
const safeField = needsNeutralise ? ` ${field}` : field;
|
|
63
|
+
if (needsNeutralise || safeField.includes(",") || safeField.includes('"') || safeField.includes("\n") || safeField.includes("\r")) {
|
|
64
|
+
return `"${safeField.replace(/"/g, '""')}"`;
|
|
62
65
|
}
|
|
63
|
-
return
|
|
66
|
+
return safeField;
|
|
64
67
|
}
|
|
68
|
+
|
|
69
|
+
// src/utils/convert-table-to-csv.ts
|
|
65
70
|
function convertTableToCSV(table) {
|
|
66
|
-
const headerRow = table.columns.map(
|
|
67
|
-
const dataRows = table.rows.map((row) => row.map(
|
|
71
|
+
const headerRow = table.columns.map(escapeCsvField).join(",");
|
|
72
|
+
const dataRows = table.rows.map((row) => row.map(escapeCsvField).join(","));
|
|
68
73
|
return [headerRow, ...dataRows].join("\r\n");
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
export {
|
|
72
77
|
parseMarkdownTable,
|
|
78
|
+
escapeCsvField,
|
|
73
79
|
convertTableToCSV
|
|
74
80
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
81
|
+
//# sourceMappingURL=chunk-JJUIBBBU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/parse-markdown-table.ts","../src/utils/escape-csv-field.ts","../src/utils/convert-table-to-csv.ts"],"sourcesContent":["import { stripInlineMarks } from \"./markdown-to-plain-text\";\n\n/** Structured representation of a parsed markdown table. */\nexport interface MarkdownTable {\n /** Column header labels. */\n columns: string[];\n /** Data rows, each an array of cell values matching the column order. */\n rows: string[][];\n /** Optional title extracted from a `<!-- table-title: ... -->` comment above the table. */\n title?: string;\n}\n\n/**\n * Parses cells from a markdown table row, trimming whitespace and stripping leading/trailing pipes.\n * @param line - A single line of a markdown table.\n * @returns An array of trimmed cell values.\n */\nfunction parseCells(line: string): string[] {\n const trimmed = line.trim();\n const stripped = trimmed.startsWith(\"|\") ? trimmed.slice(1) : trimmed;\n const withoutTrailing = stripped.endsWith(\"|\") ? stripped.slice(0, -1) : stripped;\n return withoutTrailing.split(\"|\").map((cell) => cell.trim());\n}\n\n/**\n * Tests whether a line is a GFM table separator row (e.g. `| --- | --- |`).\n * @param line - A single line to test.\n * @returns `true` if the line matches the separator pattern.\n */\nfunction isSeparatorRow(line: string): boolean {\n const cells = parseCells(line);\n return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));\n}\n\n/**\n * Tests whether a line looks like a markdown table row (contains at least one pipe).\n * @param line - A single line to test.\n * @returns `true` if the line contains a pipe character.\n */\nfunction isTableRow(line: string): boolean {\n return line.includes(\"|\");\n}\n\nconst TABLE_TITLE_PREFIX = \"<!-- table-title:\";\nconst TABLE_TITLE_SUFFIX = \"-->\";\n\n/**\n * Extracts a table title from a `<!-- table-title: ... -->` HTML comment.\n * Uses string methods instead of regex to avoid ReDoS on untrusted input.\n * @param line - A single line to test.\n * @returns The title string, or `undefined` if the line is not a title comment.\n */\nfunction extractTableTitle(line: string): string | undefined {\n const trimmed = line.trim();\n if (!trimmed.startsWith(TABLE_TITLE_PREFIX) || !trimmed.endsWith(TABLE_TITLE_SUFFIX)) {\n return undefined;\n }\n return trimmed.slice(TABLE_TITLE_PREFIX.length, -TABLE_TITLE_SUFFIX.length).trim() || undefined;\n}\n\n/**\n * Extracts the Nth GFM markdown table from a string.\n * @param markdown - The markdown content to parse.\n * @param tableIndex - Zero-based index of the table to extract.\n * @returns The parsed table, or `null` if no table exists at the given index.\n */\nexport function parseMarkdownTable(markdown: string, tableIndex = 0): MarkdownTable | null {\n const lines = markdown.split(\"\\n\");\n let tableCount = 0;\n\n for (let i = 0; i < lines.length - 1; i++) {\n const headerLine = lines[i];\n const separatorLine = lines[i + 1];\n\n if (!isTableRow(headerLine) || !isSeparatorRow(separatorLine)) {\n continue;\n }\n\n if (tableCount === tableIndex) {\n const columns = parseCells(headerLine).map(stripInlineMarks);\n const rows: string[][] = [];\n const title = i > 0 ? extractTableTitle(lines[i - 1]) : undefined;\n\n for (let j = i + 2; j < lines.length; j++) {\n if (!isTableRow(lines[j]) || isSeparatorRow(lines[j])) {\n break;\n }\n const trimmedLine = lines[j].trim();\n if (trimmedLine === \"\") {\n break;\n }\n rows.push(parseCells(lines[j]).map(stripInlineMarks));\n }\n\n return { columns, rows, ...(title ? { title } : {}) };\n }\n\n tableCount++;\n }\n\n return null;\n}\n","/**\n * Characters that trigger spreadsheet-formula evaluation when present at the start of a CSV cell.\n * Excel, Google Sheets, and Numbers all treat a field beginning with one of these as a formula\n * (e.g. `=HYPERLINK(...)`, `+1+1`, `@SUM(...)`), so a field exfiltrated from user-controlled\n * data can run arbitrary expressions in the spreadsheet on open.\n *\n * Note: `-` is intentionally excluded. While `+` and `-` are listed in some CSV injection docs,\n * `-42` is a valid negative number and Excel/Sheets do NOT evaluate it as a formula. Including `-`\n * would corrupt all negative-number cells. The actual risk from `-` is extremely low in practice.\n */\nconst FORMULA_TRIGGERS = new Set([\"=\", \"+\", \"@\", \"\\t\", \"\\r\"]);\n\n/**\n * Escapes and optionally quotes a CSV field per RFC 4180, with OWASP-recommended formula-injection\n * neutralisation for fields beginning with a trigger character.\n *\n * - Fields starting with `=`, `+`, `@`, `\\t`, or `\\r` are prefixed with a tab so the spreadsheet\n * renders them as literal text instead of evaluating them as a formula. The tab itself disappears\n * in the rendered cell.\n * - Fields containing commas, double quotes, newlines, or a leading tab (from formula neutralisation)\n * are wrapped in double quotes per RFC 4180. The double-quote wrapping ensures the tab prefix is\n * preserved in parsers that would otherwise strip leading whitespace.\n * - Existing double quotes are escaped by doubling them.\n *\n * @param field - The raw field value.\n * @returns The escaped/quoted field string.\n */\nexport function escapeCsvField(field: string): string {\n const needsNeutralise = field.length > 0 && FORMULA_TRIGGERS.has(field[0]);\n const safeField = needsNeutralise ? `\\t${field}` : field;\n if (\n needsNeutralise ||\n safeField.includes(\",\") ||\n safeField.includes('\"') ||\n safeField.includes(\"\\n\") ||\n safeField.includes(\"\\r\")\n ) {\n return `\"${safeField.replace(/\"/g, '\"\"')}\"`;\n }\n return safeField;\n}\n","import { escapeCsvField } from \"./escape-csv-field\";\nimport type { MarkdownTable } from \"./parse-markdown-table\";\n\n/**\n * Converts a parsed markdown table to a CSV string (RFC 4180).\n * Fields containing commas, double quotes, or newlines are quoted.\n * @param table - The structured table data to convert.\n * @returns A CSV-formatted string with CRLF line endings.\n */\nexport function convertTableToCSV(table: MarkdownTable): string {\n const headerRow = table.columns.map(escapeCsvField).join(\",\");\n const dataRows = table.rows.map((row) => row.map(escapeCsvField).join(\",\"));\n return [headerRow, ...dataRows].join(\"\\r\\n\");\n}\n"],"mappings":";;;;;AAiBA,SAAS,WAAW,MAAwB;AAC1C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,WAAW,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AAC9D,QAAM,kBAAkB,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AACzE,SAAO,gBAAgB,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAC7D;AAOA,SAAS,eAAe,MAAuB;AAC7C,QAAM,QAAQ,WAAW,IAAI;AAC7B,SAAO,MAAM,SAAS,KAAK,MAAM,MAAM,CAAC,SAAS,cAAc,KAAK,IAAI,CAAC;AAC3E;AAOA,SAAS,WAAW,MAAuB;AACzC,SAAO,KAAK,SAAS,GAAG;AAC1B;AAEA,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAQ3B,SAAS,kBAAkB,MAAkC;AAC3D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAQ,WAAW,kBAAkB,KAAK,CAAC,QAAQ,SAAS,kBAAkB,GAAG;AACpF,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,MAAM,mBAAmB,QAAQ,CAAC,mBAAmB,MAAM,EAAE,KAAK,KAAK;AACxF;AAQO,SAAS,mBAAmB,UAAkB,aAAa,GAAyB;AACzF,QAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,MAAI,aAAa;AAEjB,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,aAAa,MAAM,CAAC;AAC1B,UAAM,gBAAgB,MAAM,IAAI,CAAC;AAEjC,QAAI,CAAC,WAAW,UAAU,KAAK,CAAC,eAAe,aAAa,GAAG;AAC7D;AAAA,IACF;AAEA,QAAI,eAAe,YAAY;AAC7B,YAAM,UAAU,WAAW,UAAU,EAAE,IAAI,gBAAgB;AAC3D,YAAM,OAAmB,CAAC;AAC1B,YAAM,QAAQ,IAAI,IAAI,kBAAkB,MAAM,IAAI,CAAC,CAAC,IAAI;AAExD,eAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,YAAI,CAAC,WAAW,MAAM,CAAC,CAAC,KAAK,eAAe,MAAM,CAAC,CAAC,GAAG;AACrD;AAAA,QACF;AACA,cAAM,cAAc,MAAM,CAAC,EAAE,KAAK;AAClC,YAAI,gBAAgB,IAAI;AACtB;AAAA,QACF;AACA,aAAK,KAAK,WAAW,MAAM,CAAC,CAAC,EAAE,IAAI,gBAAgB,CAAC;AAAA,MACtD;AAEA,aAAO,EAAE,SAAS,MAAM,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG;AAAA,IACtD;AAEA;AAAA,EACF;AAEA,SAAO;AACT;;;AC3FA,IAAM,mBAAmB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAM,IAAI,CAAC;AAiBrD,SAAS,eAAe,OAAuB;AACpD,QAAM,kBAAkB,MAAM,SAAS,KAAK,iBAAiB,IAAI,MAAM,CAAC,CAAC;AACzE,QAAM,YAAY,kBAAkB,IAAK,KAAK,KAAK;AACnD,MACE,mBACA,UAAU,SAAS,GAAG,KACtB,UAAU,SAAS,GAAG,KACtB,UAAU,SAAS,IAAI,KACvB,UAAU,SAAS,IAAI,GACvB;AACA,WAAO,IAAI,UAAU,QAAQ,MAAM,IAAI,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;;;AC/BO,SAAS,kBAAkB,OAA8B;AAC9D,QAAM,YAAY,MAAM,QAAQ,IAAI,cAAc,EAAE,KAAK,GAAG;AAC5D,QAAM,WAAW,MAAM,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,cAAc,EAAE,KAAK,GAAG,CAAC;AAC1E,SAAO,CAAC,WAAW,GAAG,QAAQ,EAAE,KAAK,MAAM;AAC7C;","names":[]}
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
colors
|
|
6
6
|
} from "./chunk-JF3P66JF.js";
|
|
7
7
|
|
|
8
|
+
// src/components/icon-button/icon-button.tsx
|
|
9
|
+
import { forwardRef } from "react";
|
|
10
|
+
|
|
8
11
|
// src/components/icon-button/icon-button-styles.ts
|
|
9
12
|
import styled from "styled-components";
|
|
10
13
|
var PADDING = {
|
|
@@ -54,7 +57,7 @@ var ICON_SIZE_BY_BUTTON_SIZE = {
|
|
|
54
57
|
small: 16,
|
|
55
58
|
medium: 20
|
|
56
59
|
};
|
|
57
|
-
|
|
60
|
+
var IconButton = forwardRef(function IconButton2({
|
|
58
61
|
icon,
|
|
59
62
|
size = "small",
|
|
60
63
|
variant = "short",
|
|
@@ -64,11 +67,14 @@ function IconButton({
|
|
|
64
67
|
className,
|
|
65
68
|
id,
|
|
66
69
|
as,
|
|
67
|
-
"aria-label": ariaLabel
|
|
68
|
-
|
|
70
|
+
"aria-label": ariaLabel,
|
|
71
|
+
...rest
|
|
72
|
+
}, ref) {
|
|
73
|
+
const isNativeButton = as === void 0 || as === "button";
|
|
69
74
|
return /* @__PURE__ */ jsx(
|
|
70
75
|
Button,
|
|
71
76
|
{
|
|
77
|
+
ref,
|
|
72
78
|
className,
|
|
73
79
|
id,
|
|
74
80
|
as,
|
|
@@ -78,12 +84,14 @@ function IconButton({
|
|
|
78
84
|
disabled,
|
|
79
85
|
onClick: disabled ? void 0 : onClick,
|
|
80
86
|
"aria-label": ariaLabel,
|
|
87
|
+
...isNativeButton ? { type: "button" } : {},
|
|
88
|
+
...rest,
|
|
81
89
|
children: /* @__PURE__ */ jsx(Icon, { icon, size: ICON_SIZE_BY_BUTTON_SIZE[size] })
|
|
82
90
|
}
|
|
83
91
|
);
|
|
84
|
-
}
|
|
92
|
+
});
|
|
85
93
|
|
|
86
94
|
export {
|
|
87
95
|
IconButton
|
|
88
96
|
};
|
|
89
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-OBOXCBDL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/icon-button/icon-button.tsx","../src/components/icon-button/icon-button-styles.ts"],"sourcesContent":["import { forwardRef } from \"react\";\n\nimport { Icon } from \"../icon/icon\";\nimport { Button } from \"./icon-button-styles\";\nimport type { IconButtonProps, IconButtonSize } from \"./types\";\n\nconst ICON_SIZE_BY_BUTTON_SIZE: Record<IconButtonSize, number> = {\n small: 16,\n medium: 20,\n};\n\n/** Ghost-style icon button. `forwardRef` + the rest-props spread so Radix `Slot`-based wrappers\n * (Tooltip, Popover, DropdownMenu) can attach refs and event handlers via `asChild`. */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n {\n icon,\n size = \"small\",\n variant = \"short\",\n isSelected = false,\n disabled = false,\n onClick,\n className,\n id,\n as,\n \"aria-label\": ariaLabel,\n ...rest\n },\n ref\n) {\n // Only set `type=\"button\"` when rendering as a native button. Anchors (`as=\"a\"`) treat the\n // `type` attribute as a content-type hint for the linked resource — `type=\"button\"` on an\n // anchor is semantically wrong and shows up as an a11y / HTML-validation noise warning.\n const isNativeButton = as === undefined || as === \"button\";\n return (\n <Button\n ref={ref}\n className={className}\n id={id}\n as={as}\n $size={size}\n $variant={variant}\n $isSelected={isSelected}\n disabled={disabled}\n onClick={disabled ? undefined : onClick}\n aria-label={ariaLabel}\n {...(isNativeButton ? { type: \"button\" as const } : {})}\n {...rest}\n >\n <Icon icon={icon} size={ICON_SIZE_BY_BUTTON_SIZE[size]} />\n </Button>\n );\n});\n","import styled from \"styled-components\";\n\nimport { colors } from \"../../tokens/colors\";\nimport type { IconButtonSize, IconButtonVariant } from \"./types\";\n\nconst PADDING: Record<IconButtonVariant, Record<IconButtonSize, string>> = {\n short: {\n small: \"4px\",\n medium: \"6px\",\n },\n long: {\n small: \"4px 6px\",\n medium: \"6px 10px\",\n },\n};\n\nconst BORDER_RADIUS: Record<IconButtonSize, number> = {\n small: 6,\n medium: 10,\n};\n\nexport const Button = styled.button<{\n $size: IconButtonSize;\n $variant: IconButtonVariant;\n $isSelected: boolean;\n}>`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: ${({ $size, $variant }) => PADDING[$variant][$size]};\n border: none;\n border-radius: ${({ $size }) => BORDER_RADIUS[$size]}px;\n background: ${({ $isSelected }) => ($isSelected ? colors[\"brown-40\"] : \"transparent\")};\n color: ${colors[\"brown-100\"]};\n cursor: pointer;\n transition: background-color 100ms ease;\n\n &:hover {\n background: ${({ $isSelected }) => ($isSelected ? colors[\"brown-40\"] : colors[\"brown-20\"])};\n }\n\n &:active {\n background: ${colors[\"brown-40\"]};\n }\n\n &:disabled {\n color: ${colors[\"brown-50\"]};\n background: transparent;\n cursor: not-allowed;\n }\n`;\n"],"mappings":";;;;;;;;AAAA,SAAS,kBAAkB;;;ACA3B,OAAO,YAAY;AAKnB,IAAM,UAAqE;AAAA,EACzE,OAAO;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAEA,IAAM,gBAAgD;AAAA,EACpD,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,IAAM,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA,aAQhB,CAAC,EAAE,OAAO,SAAS,MAAM,QAAQ,QAAQ,EAAE,KAAK,CAAC;AAAA;AAAA,mBAE3C,CAAC,EAAE,MAAM,MAAM,cAAc,KAAK,CAAC;AAAA,gBACtC,CAAC,EAAE,YAAY,MAAO,cAAc,OAAO,UAAU,IAAI,aAAc;AAAA,WAC5E,OAAO,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,kBAKZ,CAAC,EAAE,YAAY,MAAO,cAAc,OAAO,UAAU,IAAI,OAAO,UAAU,CAAE;AAAA;AAAA;AAAA;AAAA,kBAI5E,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA,aAIvB,OAAO,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;;;ADEzB;AA1CN,IAAM,2BAA2D;AAAA,EAC/D,OAAO;AAAA,EACP,QAAQ;AACV;AAIO,IAAM,aAAa,WAA+C,SAASA,YAChF;AAAA,EACE;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV,aAAa;AAAA,EACb,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,GAAG;AACL,GACA,KACA;AAIA,QAAM,iBAAiB,OAAO,UAAa,OAAO;AAClD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,MACb;AAAA,MACA,SAAS,WAAW,SAAY;AAAA,MAChC,cAAY;AAAA,MACX,GAAI,iBAAiB,EAAE,MAAM,SAAkB,IAAI,CAAC;AAAA,MACpD,GAAG;AAAA,MAEJ,8BAAC,QAAK,MAAY,MAAM,yBAAyB,IAAI,GAAG;AAAA;AAAA,EAC1D;AAEJ,CAAC;","names":["IconButton"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Combines class names using clsx and tailwind-merge — the standard shadcn/ui helper.\n * Tailwind classes that conflict (e.g. `p-2 p-4`) get merged so the last one wins.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAMjB,SAAS,MAAM,QAA8B;AAClD,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
|