@shapesos/clay 0.18.0 → 0.19.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 (60) hide show
  1. package/dist/artifacts.cjs +151 -130
  2. package/dist/artifacts.cjs.map +1 -1
  3. package/dist/artifacts.js +3 -2
  4. package/dist/badge.cjs +70 -0
  5. package/dist/badge.cjs.map +1 -0
  6. package/dist/badge.d.cts +28 -0
  7. package/dist/badge.d.ts +28 -0
  8. package/dist/badge.js +9 -0
  9. package/dist/badge.js.map +1 -0
  10. package/dist/blocks.cjs +434 -176
  11. package/dist/blocks.cjs.map +1 -1
  12. package/dist/blocks.css +1 -1
  13. package/dist/blocks.d.cts +14 -3
  14. package/dist/blocks.d.ts +14 -3
  15. package/dist/blocks.js +17 -5
  16. package/dist/chat.cjs +439 -231
  17. package/dist/chat.cjs.map +1 -1
  18. package/dist/chat.d.cts +1 -1
  19. package/dist/chat.d.ts +1 -1
  20. package/dist/chat.js +10 -5
  21. package/dist/{chunk-JGMN6W72.js → chunk-2X4KUXYP.js} +1 -1
  22. package/dist/chunk-CSTCTO3B.js +161 -0
  23. package/dist/chunk-CSTCTO3B.js.map +1 -0
  24. package/dist/{chunk-QR4VBGK4.js → chunk-DC5KYT2G.js} +65 -198
  25. package/dist/chunk-DC5KYT2G.js.map +1 -0
  26. package/dist/chunk-EIAWZXE4.js +69 -0
  27. package/dist/chunk-EIAWZXE4.js.map +1 -0
  28. package/dist/chunk-EXRNGIQ2.js +41 -0
  29. package/dist/chunk-EXRNGIQ2.js.map +1 -0
  30. package/dist/{chunk-CATIRDZP.js → chunk-FCSYVF4Z.js} +10 -44
  31. package/dist/chunk-FCSYVF4Z.js.map +1 -0
  32. package/dist/{chunk-4MZZH3WX.js → chunk-HU5W7CGE.js} +37 -2
  33. package/dist/chunk-HU5W7CGE.js.map +1 -0
  34. package/dist/chunk-JMHE3Q5K.js +406 -0
  35. package/dist/chunk-JMHE3Q5K.js.map +1 -0
  36. package/dist/copy-button.cjs +439 -0
  37. package/dist/copy-button.cjs.map +1 -0
  38. package/dist/copy-button.d.cts +20 -0
  39. package/dist/copy-button.d.ts +20 -0
  40. package/dist/copy-button.js +12 -0
  41. package/dist/copy-button.js.map +1 -0
  42. package/dist/index.cjs +515 -289
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +4 -2
  45. package/dist/index.d.ts +4 -2
  46. package/dist/index.js +26 -9
  47. package/dist/table.cjs +40 -0
  48. package/dist/table.cjs.map +1 -1
  49. package/dist/table.d.cts +37 -2
  50. package/dist/table.d.ts +37 -2
  51. package/dist/table.js +11 -1
  52. package/dist/{types-BDYPVP2N.d.cts → types-Bhg0_ES8.d.cts} +74 -2
  53. package/dist/{types-BMwa4bPW.d.ts → types-orOQcI2z.d.ts} +74 -2
  54. package/package.json +11 -1
  55. package/dist/chunk-4MZZH3WX.js.map +0 -1
  56. package/dist/chunk-7MY4X4YL.js +0 -250
  57. package/dist/chunk-7MY4X4YL.js.map +0 -1
  58. package/dist/chunk-CATIRDZP.js.map +0 -1
  59. package/dist/chunk-QR4VBGK4.js.map +0 -1
  60. /package/dist/{chunk-JGMN6W72.js.map → chunk-2X4KUXYP.js.map} +0 -0
@@ -0,0 +1,41 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-P6GUNIAE.js";
4
+
5
+ // src/components/badge/badge.tsx
6
+ import { jsx } from "react/jsx-runtime";
7
+ var BASE = "inline-flex items-center whitespace-nowrap rounded-full";
8
+ var SIZES = {
9
+ // `sm` is a uniform scale-down of `md` (text 10/12 of caption): padding and line-height keep
10
+ // md's proportions (caption is 12px text on a 16px line = 1.333 ratio) so the pill shape matches.
11
+ md: "px-2.5 py-0.5 text-geist-label-caption-medium",
12
+ sm: "px-2 py-0.5 text-[11px]/[1.333] font-medium"
13
+ };
14
+ var SUBTLE = {
15
+ neutral: "bg-brown-20 text-brown-70",
16
+ warning: "bg-orange-50 text-orange-600",
17
+ violet: "bg-violet-50 text-violet-600"
18
+ };
19
+ var OUTLINE = {
20
+ neutral: "border border-brown-30 bg-white text-brown-100",
21
+ warning: "border border-orange-200 bg-white text-orange-600",
22
+ violet: "border border-violet-400 bg-white text-violet-600"
23
+ };
24
+ var VARIANTS = {
25
+ subtle: SUBTLE,
26
+ outline: OUTLINE
27
+ };
28
+ function Badge({
29
+ variant = "subtle",
30
+ tone = "neutral",
31
+ size = "md",
32
+ className,
33
+ children
34
+ }) {
35
+ return /* @__PURE__ */ jsx("span", { className: cn(BASE, SIZES[size], VARIANTS[variant][tone], className), children });
36
+ }
37
+
38
+ export {
39
+ Badge
40
+ };
41
+ //# sourceMappingURL=chunk-EXRNGIQ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/badge/badge.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Fill treatment of the badge. */\nexport type BadgeVariant = \"subtle\" | \"outline\";\n\n/** Semantic color of the badge. */\nexport type BadgeTone = \"neutral\" | \"warning\" | \"violet\";\n\n/** Overall size — `md` (default) or a more compact `sm` for eyebrow-style tags. */\nexport type BadgeSize = \"sm\" | \"md\";\n\nexport interface BadgeProps {\n /** Fill treatment. `subtle` = tinted background, `outline` = bordered on white. Default `subtle`. */\n variant?: BadgeVariant;\n /** Semantic color. `neutral` = brown, `warning` = orange, `violet` = violet. Default `neutral`. */\n tone?: BadgeTone;\n /** Size. `md` (12px text) or `sm` (10px text, tighter padding). Default `md`. */\n size?: BadgeSize;\n /** Extra classes merged after the variant/tone classes (last-wins via tailwind-merge). */\n className?: string;\n /** Badge label content. */\n children: ReactNode;\n}\n\nconst BASE = \"inline-flex items-center whitespace-nowrap rounded-full\";\n\n/** Padding + text size per size. Kept as one class each so there is a single font-size utility (deterministic override). */\nconst SIZES: Record<BadgeSize, string> = {\n // `sm` is a uniform scale-down of `md` (text 10/12 of caption): padding and line-height keep\n // md's proportions (caption is 12px text on a 16px line = 1.333 ratio) so the pill shape matches.\n md: \"px-2.5 py-0.5 text-geist-label-caption-medium\",\n sm: \"px-2 py-0.5 text-[11px]/[1.333] font-medium\",\n};\n\n/** Tinted background — low-emphasis tag. */\nconst SUBTLE: Record<BadgeTone, string> = {\n neutral: \"bg-brown-20 text-brown-70\",\n warning: \"bg-orange-50 text-orange-600\",\n violet: \"bg-violet-50 text-violet-600\",\n};\n\n/** Bordered on white — reads as a discrete chip. */\nconst OUTLINE: Record<BadgeTone, string> = {\n neutral: \"border border-brown-30 bg-white text-brown-100\",\n warning: \"border border-orange-200 bg-white text-orange-600\",\n violet: \"border border-violet-400 bg-white text-violet-600\",\n};\n\nconst VARIANTS: Record<BadgeVariant, Record<BadgeTone, string>> = {\n subtle: SUBTLE,\n outline: OUTLINE,\n};\n\n/**\n * Small pill for labels, counts, and status tags — recipient chips, \"+N\" overflow, \"Email\n * (fallback)\" tags, \"Labs\". Presentational only; on clay tokens. Compose the label at the call\n * site.\n */\nexport function Badge({\n variant = \"subtle\",\n tone = \"neutral\",\n size = \"md\",\n className,\n children,\n}: BadgeProps): ReactNode {\n return <span className={cn(BASE, SIZES[size], VARIANTS[variant][tone], className)}>{children}</span>;\n}\n"],"mappings":";;;;;AAmES;AAzCT,IAAM,OAAO;AAGb,IAAM,QAAmC;AAAA;AAAA;AAAA,EAGvC,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,IAAM,SAAoC;AAAA,EACxC,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AACV;AAGA,IAAM,UAAqC;AAAA,EACzC,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AACV;AAEA,IAAM,WAA4D;AAAA,EAChE,QAAQ;AAAA,EACR,SAAS;AACX;AAOO,SAAS,MAAM;AAAA,EACpB,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP;AAAA,EACA;AACF,GAA0B;AACxB,SAAO,oBAAC,UAAK,WAAW,GAAG,MAAM,MAAM,IAAI,GAAG,SAAS,OAAO,EAAE,IAAI,GAAG,SAAS,GAAI,UAAS;AAC/F;","names":[]}
@@ -1,13 +1,16 @@
1
1
  import {
2
2
  Block,
3
3
  BlockServices
4
- } from "./chunk-7MY4X4YL.js";
4
+ } from "./chunk-JMHE3Q5K.js";
5
+ import {
6
+ useCopyToClipboard
7
+ } from "./chunk-EIAWZXE4.js";
5
8
  import {
6
9
  IconCheck,
7
10
  IconCopy,
8
11
  IconThumbDown,
9
12
  IconThumbUp
10
- } from "./chunk-QR4VBGK4.js";
13
+ } from "./chunk-CSTCTO3B.js";
11
14
  import {
12
15
  IconButton
13
16
  } from "./chunk-E36VI2HA.js";
@@ -37,7 +40,7 @@ function copyMessageText(message) {
37
40
  }
38
41
 
39
42
  // src/components/chat/chat-message-actions/chat-message-actions.tsx
40
- import { useCallback as useCallback2 } from "react";
43
+ import { useCallback } from "react";
41
44
 
42
45
  // src/components/chat/chat-context/chat-context.ts
43
46
  import { createContext, useContext } from "react";
@@ -50,42 +53,6 @@ function useChatContext() {
50
53
  return context;
51
54
  }
52
55
 
53
- // src/components/chat/hooks/use-copy-to-clipboard.ts
54
- import { useState, useCallback, useRef, useEffect } from "react";
55
-
56
- // src/utils/clipboard.ts
57
- function copyToClipboard(text, options) {
58
- navigator.clipboard.writeText(text).then(
59
- () => options?.onSuccess?.(),
60
- (error) => options?.onFailure?.(error)
61
- );
62
- }
63
-
64
- // src/components/chat/hooks/use-copy-to-clipboard.ts
65
- var RESET_DELAY_MS = 2e3;
66
- function useCopyToClipboard() {
67
- const [isCopied, setIsCopied] = useState(false);
68
- const timeoutRef = useRef(null);
69
- useEffect(() => {
70
- return () => {
71
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
72
- };
73
- }, []);
74
- const copy = useCallback((text) => {
75
- copyToClipboard(text, {
76
- onSuccess: () => {
77
- setIsCopied(true);
78
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
79
- timeoutRef.current = setTimeout(() => {
80
- setIsCopied(false);
81
- timeoutRef.current = null;
82
- }, RESET_DELAY_MS);
83
- }
84
- });
85
- }, []);
86
- return { isCopied, copy };
87
- }
88
-
89
56
  // src/components/chat/constants.ts
90
57
  var MESSAGE_ROLE = {
91
58
  USER: "user",
@@ -115,14 +82,14 @@ import { jsx, jsxs } from "react/jsx-runtime";
115
82
  function ChatMessageActions({ className, messageId, content, role, isHelpful }) {
116
83
  const { onCopyMessage, onThumbUpClick, onThumbDownClick } = useChatContext();
117
84
  const { isCopied, copy } = useCopyToClipboard();
118
- const handleCopy = useCallback2(() => {
85
+ const handleCopy = useCallback(() => {
119
86
  copy(content);
120
87
  onCopyMessage?.(messageId);
121
88
  }, [content, messageId, copy, onCopyMessage]);
122
- const handleThumbUp = useCallback2(() => {
89
+ const handleThumbUp = useCallback(() => {
123
90
  onThumbUpClick?.(messageId, isHelpful === true ? null : true);
124
91
  }, [messageId, isHelpful, onThumbUpClick]);
125
- const handleThumbDown = useCallback2(() => {
92
+ const handleThumbDown = useCallback(() => {
126
93
  onThumbDownClick?.(messageId, isHelpful === false ? null : false);
127
94
  }, [messageId, isHelpful, onThumbDownClick]);
128
95
  const isAssistant = role === MESSAGE_ROLE.ASSISTANT;
@@ -216,8 +183,7 @@ export {
216
183
  copyMessageText,
217
184
  ChatContext,
218
185
  useChatContext,
219
- useCopyToClipboard,
220
186
  MESSAGE_ROLE,
221
187
  ChatMessage
222
188
  };
223
- //# sourceMappingURL=chunk-CATIRDZP.js.map
189
+ //# sourceMappingURL=chunk-FCSYVF4Z.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/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","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":["styled","styled","jsx","jsxs"]}
@@ -79,6 +79,36 @@ function useScrollShadow() {
79
79
  return { targetRef, isScrolled };
80
80
  }
81
81
 
82
+ // src/components/table/data-table.tsx
83
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
84
+ var DATA_TABLE_HEAD_CLASS = "h-auto py-2 px-4 text-xs font-medium text-foreground whitespace-nowrap text-left";
85
+ var DATA_TABLE_CELL_CLASS = "p-4 text-sm font-normal text-foreground align-top min-w-[150px] text-left";
86
+ var DATA_TABLE_ROW_CLASS = "border-b-brown-30 bg-background/70 hover:bg-secondary";
87
+ var DATA_TABLE_SCROLL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.01), 0 6px 12px -2px rgb(0 0 0 / 0.03)";
88
+ function DataTable({ header, children, maxBodyHeight, className }) {
89
+ const { targetRef, isScrolled } = useScrollShadow();
90
+ return /* @__PURE__ */ jsx2(
91
+ "div",
92
+ {
93
+ ref: targetRef,
94
+ className: cn("overscroll-none", className),
95
+ style: maxBodyHeight !== void 0 ? { maxHeight: maxBodyHeight, overflowY: "auto" } : void 0,
96
+ children: /* @__PURE__ */ jsxs(Table, { containerClassName: "overflow-visible", children: [
97
+ /* @__PURE__ */ jsx2(
98
+ TableHeader,
99
+ {
100
+ "data-scrolled": isScrolled ? "true" : void 0,
101
+ className: "sticky top-0 z-10 bg-background/70 backdrop-blur-lg transition-shadow",
102
+ style: isScrolled ? { boxShadow: DATA_TABLE_SCROLL_SHADOW } : void 0,
103
+ children: header
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx2(TableBody, { children })
107
+ ] })
108
+ }
109
+ );
110
+ }
111
+
82
112
  export {
83
113
  Table,
84
114
  TableHeader,
@@ -88,6 +118,11 @@ export {
88
118
  TableHead,
89
119
  TableCell,
90
120
  TableCaption,
91
- useScrollShadow
121
+ useScrollShadow,
122
+ DATA_TABLE_HEAD_CLASS,
123
+ DATA_TABLE_CELL_CLASS,
124
+ DATA_TABLE_ROW_CLASS,
125
+ DATA_TABLE_SCROLL_SHADOW,
126
+ DataTable
92
127
  };
93
- //# sourceMappingURL=chunk-4MZZH3WX.js.map
128
+ //# sourceMappingURL=chunk-HU5W7CGE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ui/table.tsx","../src/components/table/use-scroll-shadow.ts","../src/components/table/data-table.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Extension of shadcn's `Table` registry component: exposes `containerClassName` so callers can\n * override the wrapping `<div>`'s default `overflow-auto`. We need this for sticky-header support\n * (sticky uses the nearest overflow ancestor as its containing block — when the actual scroll\n * container is one level out, the inner overflow-auto needs to be neutralised with\n * `overflow-visible` so sticky aligns with the real scrolling parent). */\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes<HTMLTableElement> & { containerClassName?: string }\n>(({ className, containerClassName, ...props }, ref) => (\n <div className={cn(\"relative w-full overflow-auto\", containerClassName)}>\n <table ref={ref} className={cn(\"w-full caption-bottom text-sm\", className)} {...props} />\n </div>\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n);\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => (\n <tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n )\n);\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => (\n <tfoot ref={ref} className={cn(\"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\", className)} {...props} />\n )\n);\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(\n ({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\", className)}\n {...props}\n />\n )\n);\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(\n ({ className, ...props }, ref) => (\n <th\n ref={ref}\n className={cn(\n \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n className\n )}\n {...props}\n />\n )\n);\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(\n ({ className, ...props }, ref) => (\n <td ref={ref} className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)} {...props} />\n )\n);\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(\n ({ className, ...props }, ref) => (\n <caption ref={ref} className={cn(\"mt-4 text-sm text-muted-foreground\", className)} {...props} />\n )\n);\nTableCaption.displayName = \"TableCaption\";\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n","import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/**\n * Tracks whether a scroll container has scrolled past its top edge, for sticky-header shadow effects.\n *\n * Returns `targetRef` (attach to the scroll container itself or any element inside it) and\n * `isScrolled` (true once the container's `scrollTop > 0`). On mount the hook walks up the DOM\n * from the target to find the nearest overflow ancestor (or uses the target itself if it already\n * has `overflowY: auto | scroll`), then listens to its `scroll` event.\n *\n * Used to surface a subtle \"scroll lift\" shadow under sticky headers so users know there is\n * content hidden above.\n *\n * **Why a scroll listener and not a sentinel + IntersectionObserver:** an earlier version used a\n * 1px sentinel as the first child of the scroll container. The sentinel's height pushed the thead\n * down by one pixel in normal flow, and as soon as the user scrolled the thead snapped up to its\n * sticky position — a visible 1px jitter. A scroll listener has no layout footprint.\n *\n * `passive: true` so the listener doesn't block native scroll smoothness; we never call\n * `preventDefault`.\n */\nexport function useScrollShadow(): {\n targetRef: RefObject<HTMLDivElement | null>;\n isScrolled: boolean;\n} {\n const targetRef = useRef<HTMLDivElement>(null);\n const [isScrolled, setIsScrolled] = useState(false);\n\n useEffect(() => {\n const target = targetRef.current;\n if (!target) return;\n\n let scrollContainer: HTMLElement | null = target;\n while (scrollContainer) {\n const overflowY = getComputedStyle(scrollContainer).overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") break;\n scrollContainer = scrollContainer.parentElement;\n }\n if (!scrollContainer) return;\n\n setIsScrolled(scrollContainer.scrollTop > 0);\n\n const handleScroll = () => {\n if (!scrollContainer) return;\n setIsScrolled(scrollContainer.scrollTop > 0);\n };\n scrollContainer.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => scrollContainer.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n return { targetRef, isScrolled };\n}\n","import type { ReactNode } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Table, TableBody, TableHeader } from \"@/components/ui/table\";\nimport { useScrollShadow } from \"./use-scroll-shadow\";\n\n/**\n * Header cell class — compact, left-aligned, non-wrapping. Apply to each `<TableHead>` composed at\n * the call site so every clay data table shares one header treatment.\n */\nexport const DATA_TABLE_HEAD_CLASS = \"h-auto py-2 px-4 text-xs font-medium text-foreground whitespace-nowrap text-left\";\n\n/** Body cell class — roomy padding, top-aligned, left-aligned, min width so columns don't collapse. */\nexport const DATA_TABLE_CELL_CLASS = \"p-4 text-sm font-normal text-foreground align-top min-w-[150px] text-left\";\n\n/**\n * Data-row class matching the frosted-glass card scheme: `border-b-brown-30` overrides shadcn's\n * default `--color-border` (brown-40) with the lighter brown-30; hover lifts to opaque `bg-secondary`.\n */\nexport const DATA_TABLE_ROW_CLASS = \"border-b-brown-30 bg-background/70 hover:bg-secondary\";\n\n/** Layered drop-shadow for the scroll-lift effect on the sticky header. */\nexport const DATA_TABLE_SCROLL_SHADOW = \"0 1px 2px rgb(0 0 0 / 0.01), 0 6px 12px -2px rgb(0 0 0 / 0.03)\";\n\nexport interface DataTableProps {\n /**\n * Header row content — typically a single `<TableRow>` of `<TableHead>` cells (use\n * `DATA_TABLE_HEAD_CLASS`). Rendered inside the sticky, frosted `<TableHeader>` shell.\n */\n header: ReactNode;\n /** Body rows composed at the call site (`<TableRow>`/`<TableCell>`). */\n children: ReactNode;\n /** Max body height (px) before vertical scroll engages. Omit for no cap (grows to content). */\n maxBodyHeight?: number;\n /** Extra classes for the scroll container. */\n className?: string;\n}\n\n/**\n * Shared styled-table shell — the scroll container, sticky frosted (`bg-background/70\n * backdrop-blur-lg`) header, and scroll-lift shadow (via {@link useScrollShadow}) that give clay\n * data tables (table artifact, notification-draft recipients) one consistent look. Callers compose\n * the header cells and body rows at the call site; this is only the shell.\n */\nexport function DataTable({ header, children, maxBodyHeight, className }: DataTableProps): ReactNode {\n const { targetRef, isScrolled } = useScrollShadow();\n return (\n <div\n ref={targetRef}\n className={cn(\"overscroll-none\", className)}\n style={maxBodyHeight !== undefined ? { maxHeight: maxBodyHeight, overflowY: \"auto\" } : undefined}\n >\n <Table containerClassName=\"overflow-visible\">\n <TableHeader\n data-scrolled={isScrolled ? \"true\" : undefined}\n className=\"sticky top-0 z-10 bg-background/70 backdrop-blur-lg transition-shadow\"\n style={isScrolled ? { boxShadow: DATA_TABLE_SCROLL_SHADOW } : undefined}\n >\n {header}\n </TableHeader>\n <TableBody>{children}</TableBody>\n </Table>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx"]}
@@ -0,0 +1,406 @@
1
+ import {
2
+ CopyButton
3
+ } from "./chunk-EIAWZXE4.js";
4
+ import {
5
+ markdownToPlainText
6
+ } from "./chunk-NMKKU2UG.js";
7
+ import {
8
+ Badge
9
+ } from "./chunk-EXRNGIQ2.js";
10
+ import {
11
+ ArtifactServices,
12
+ artifactToClipboardText
13
+ } from "./chunk-DC5KYT2G.js";
14
+ import {
15
+ IconChevronDown,
16
+ IconFlask2Filled
17
+ } from "./chunk-CSTCTO3B.js";
18
+ import {
19
+ Icon
20
+ } from "./chunk-OLJIJYB5.js";
21
+ import {
22
+ typographyMixin,
23
+ typographyTypes
24
+ } from "./chunk-UXT2H3FM.js";
25
+ import {
26
+ colors
27
+ } from "./chunk-SN3ORQSJ.js";
28
+ import {
29
+ DATA_TABLE_CELL_CLASS,
30
+ DATA_TABLE_HEAD_CLASS,
31
+ DATA_TABLE_ROW_CLASS,
32
+ DataTable,
33
+ TableCell,
34
+ TableHead,
35
+ TableRow
36
+ } from "./chunk-HU5W7CGE.js";
37
+
38
+ // src/components/blocks/types.ts
39
+ var blockTypes = {
40
+ TEXT: "TEXT",
41
+ ARTIFACT_REF: "ARTIFACT_REF",
42
+ NOTIFICATION_DRAFT: "NOTIFICATION_DRAFT"
43
+ };
44
+
45
+ // src/components/blocks/artifact-ref-block/artifact-ref-block.tsx
46
+ import { jsx } from "react/jsx-runtime";
47
+ function ArtifactRefBlock({ block }) {
48
+ const { artifact, labels, callbacks } = block.payload;
49
+ const entry = ArtifactServices[artifact.type];
50
+ if (!entry) return null;
51
+ const Component = entry.Component;
52
+ return /* @__PURE__ */ jsx(Component, { artifact, labels, callbacks });
53
+ }
54
+
55
+ // src/components/blocks/block-services/artifact-ref-block-service.ts
56
+ var ArtifactRefBlockService = {
57
+ type: blockTypes.ARTIFACT_REF,
58
+ Component: ArtifactRefBlock,
59
+ /**
60
+ * Delegates to the registered artifact component's `toClipboardText`. Falls back to
61
+ * `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type
62
+ * has no registered component, so copy never silently produces an empty string.
63
+ */
64
+ toClipboardText: (block) => artifactToClipboardText(block.payload.artifact)
65
+ };
66
+
67
+ // src/components/blocks/notification-draft-block/types.ts
68
+ var notificationChannels = {
69
+ /** Branded email — renders a From/subject header. */
70
+ EMAIL: "email",
71
+ /** Slack DM — renders as a chat bubble, no subject. */
72
+ SLACK: "slack",
73
+ /** Microsoft Teams DM — renders as a chat bubble, no subject. */
74
+ TEAMS: "teams"
75
+ };
76
+ var notificationEmailTypes = {
77
+ /** Recipient's work email. */
78
+ WORK: "work",
79
+ /** Recipient's personal email. */
80
+ PERSONAL: "personal"
81
+ };
82
+
83
+ // src/components/blocks/notification-draft-block/notification-draft-block.tsx
84
+ import { useState } from "react";
85
+
86
+ // src/components/blocks/markdown/markdown.tsx
87
+ import ReactMarkdown from "react-markdown";
88
+ import remarkBreaks from "remark-breaks";
89
+ import remarkGfm from "remark-gfm";
90
+
91
+ // src/components/blocks/scrollable-table/scrollable-table-styles.ts
92
+ import styled from "styled-components";
93
+ var TableContainer = styled.div`
94
+ position: relative;
95
+ margin-block: 8px 16px;
96
+ `;
97
+ var TableScroll = styled.div`
98
+ width: 100%;
99
+ overflow-x: auto;
100
+ overflow-y: hidden;
101
+ `;
102
+
103
+ // src/components/blocks/scrollable-table/scrollable-table.tsx
104
+ import { jsx as jsx2 } from "react/jsx-runtime";
105
+ function ScrollableTable({ node: _node, ...props }) {
106
+ return /* @__PURE__ */ jsx2(TableContainer, { children: /* @__PURE__ */ jsx2(TableScroll, { children: /* @__PURE__ */ jsx2("table", { ...props }) }) });
107
+ }
108
+
109
+ // src/components/blocks/text-block/sanitize.ts
110
+ function sanitize(text) {
111
+ return text.split("\n").filter((line) => {
112
+ const t = line.trim();
113
+ return !(t.startsWith("<!-- table-title:") && t.endsWith("-->"));
114
+ }).join("\n");
115
+ }
116
+
117
+ // src/components/blocks/text-block/text-block-styles.ts
118
+ import styled2 from "styled-components";
119
+ var TextBlockWrapper = styled2.div`
120
+ ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};
121
+ color: ${colors["brown-100"]};
122
+ word-break: break-word;
123
+
124
+ & h1 {
125
+ ${typographyMixin(typographyTypes.GEIST_HEADING_S_BOLD)};
126
+ margin-block: 20px;
127
+ }
128
+
129
+ & h2 {
130
+ ${typographyMixin(typographyTypes.GEIST_BODY_L_SEMI_BOLD)};
131
+ margin-block: 12px;
132
+ }
133
+
134
+ & h3,
135
+ & h4,
136
+ & h5,
137
+ & h6 {
138
+ ${typographyMixin(typographyTypes.GEIST_BODY_M_SEMI_BOLD)};
139
+ margin-block: 8px;
140
+ }
141
+
142
+ & strong {
143
+ ${typographyMixin(typographyTypes.GEIST_BODY_S_SEMI_BOLD)};
144
+ }
145
+
146
+ & p {
147
+ margin-block: 0px;
148
+ }
149
+
150
+ & p + p {
151
+ margin-block: 8px 0px;
152
+ }
153
+
154
+ & ul,
155
+ & ol {
156
+ margin-block: 16px;
157
+ padding-left: 20px;
158
+ }
159
+
160
+ & li + li {
161
+ margin-block: 4px;
162
+ }
163
+
164
+ & ul {
165
+ list-style-type: disc;
166
+ }
167
+
168
+ & ul ul {
169
+ list-style-type: circle;
170
+ }
171
+
172
+ & ul ul ul {
173
+ list-style-type: square;
174
+ }
175
+
176
+ & ol {
177
+ list-style-type: decimal;
178
+ }
179
+
180
+ & ol ol {
181
+ list-style-type: lower-alpha;
182
+ }
183
+
184
+ & ol ol ol {
185
+ list-style-type: lower-roman;
186
+ }
187
+
188
+ & a {
189
+ color: inherit;
190
+ text-decoration-line: underline;
191
+ text-decoration-style: dotted;
192
+ text-decoration-thickness: auto;
193
+ text-underline-offset: auto;
194
+ text-underline-position: from-font;
195
+ transition: color 75ms ease-in-out;
196
+
197
+ &:hover {
198
+ color: ${colors["blue-600"]};
199
+ }
200
+ }
201
+
202
+ & hr {
203
+ border: none;
204
+ border-top: 1px solid ${colors["brown-40"]};
205
+ margin-block: 20px;
206
+ }
207
+
208
+ & table {
209
+ margin: 0;
210
+ width: 100%;
211
+ border-collapse: collapse;
212
+ }
213
+
214
+ & th,
215
+ & td {
216
+ border: none;
217
+ border-bottom: 1px solid ${colors["brown-40"]};
218
+ text-align: left;
219
+ }
220
+
221
+ & th {
222
+ ${typographyMixin(typographyTypes.GEIST_LABEL_CAPTION_MEDIUM)};
223
+ padding: 8px;
224
+ }
225
+
226
+ & td {
227
+ ${typographyMixin(typographyTypes.GEIST_BODY_XS_MEDIUM)};
228
+ padding: 16px 8px;
229
+ min-width: 150px;
230
+ width: 1%;
231
+ }
232
+
233
+ & td strong {
234
+ font-weight: inherit;
235
+ font-size: inherit;
236
+ line-height: inherit;
237
+ }
238
+
239
+ & blockquote {
240
+ margin-block: 4px;
241
+ margin-inline: 0px;
242
+ padding-left: 16px;
243
+ border-left: 4px solid ${colors["brown-40"]};
244
+ color: ${colors["brown-80"]};
245
+ }
246
+ `;
247
+
248
+ // src/components/blocks/markdown/markdown.tsx
249
+ import { jsx as jsx3 } from "react/jsx-runtime";
250
+ var markdownComponents = {
251
+ table: ((tableProps) => /* @__PURE__ */ jsx3(ScrollableTable, { ...tableProps }))
252
+ };
253
+ function Markdown({ children }) {
254
+ return /* @__PURE__ */ jsx3(TextBlockWrapper, { children: /* @__PURE__ */ jsx3(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: markdownComponents, children: sanitize(children) }) });
255
+ }
256
+
257
+ // src/components/blocks/notification-draft-block/to-text.ts
258
+ function notificationDraftToText(payload) {
259
+ const { channel, subject, body } = payload;
260
+ const text = markdownToPlainText(body);
261
+ return channel === notificationChannels.EMAIL && subject ? `${subject}
262
+
263
+ ${text}` : text;
264
+ }
265
+
266
+ // src/components/blocks/notification-draft-block/notification-draft-block.tsx
267
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
268
+ var CHIP_LIMIT = 3;
269
+ var RECIPIENTS_MAX_HEIGHT = 320;
270
+ function chipLabel(recipient) {
271
+ return recipient.name ?? recipient.email ?? recipient.employeeId;
272
+ }
273
+ function channelCellText(recipient) {
274
+ return recipient.fallback ? `${recipient.channelLabel} (fallback)` : recipient.channelLabel;
275
+ }
276
+ function FieldLabel({ children }) {
277
+ return /* @__PURE__ */ jsx4("span", { className: "text-geist-label-caption-medium tracking-wide text-brown-60", children });
278
+ }
279
+ function RecipientChips({ recipients }) {
280
+ const shown = recipients.slice(0, CHIP_LIMIT);
281
+ const overflow = recipients.length - shown.length;
282
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
283
+ shown.map((recipient) => /* @__PURE__ */ jsx4(Badge, { variant: "outline", children: chipLabel(recipient) }, recipient.employeeId)),
284
+ overflow > 0 && /* @__PURE__ */ jsx4(Badge, { variant: "subtle", children: `+${overflow}` })
285
+ ] });
286
+ }
287
+ function RecipientsTable({ recipients }) {
288
+ return /* @__PURE__ */ jsx4(
289
+ DataTable,
290
+ {
291
+ maxBodyHeight: RECIPIENTS_MAX_HEIGHT,
292
+ header: /* @__PURE__ */ jsxs(TableRow, { className: "hover:bg-transparent", children: [
293
+ /* @__PURE__ */ jsx4(TableHead, { scope: "col", className: DATA_TABLE_HEAD_CLASS, children: "Name" }),
294
+ /* @__PURE__ */ jsx4(TableHead, { scope: "col", className: DATA_TABLE_HEAD_CLASS, children: "Email" }),
295
+ /* @__PURE__ */ jsx4(TableHead, { scope: "col", className: DATA_TABLE_HEAD_CLASS, children: "Channel" })
296
+ ] }),
297
+ children: recipients.map((recipient) => /* @__PURE__ */ jsxs(TableRow, { className: DATA_TABLE_ROW_CLASS, children: [
298
+ /* @__PURE__ */ jsx4(TableCell, { className: DATA_TABLE_CELL_CLASS, children: recipient.name ?? "\u2014" }),
299
+ /* @__PURE__ */ jsx4(TableCell, { className: DATA_TABLE_CELL_CLASS, children: recipient.email ?? "\u2014" }),
300
+ /* @__PURE__ */ jsx4(TableCell, { className: DATA_TABLE_CELL_CLASS, children: /* @__PURE__ */ jsx4(Badge, { tone: recipient.fallback ? "warning" : "neutral", variant: "subtle", children: channelCellText(recipient) }) })
301
+ ] }, recipient.employeeId))
302
+ }
303
+ );
304
+ }
305
+ function NotificationDraftBlock({ block }) {
306
+ const { channel, subject, body, recipients } = block.payload;
307
+ const [detailsOpen, setDetailsOpen] = useState(false);
308
+ const showSubject = channel === notificationChannels.EMAIL && subject;
309
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-2xl border border-brown-30 bg-white", children: [
310
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 border-b border-brown-30 px-4 py-3", children: [
311
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
312
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
313
+ /* @__PURE__ */ jsx4("span", { className: "text-geist-body-s-semi-bold text-brown-100", children: "Notification draft" }),
314
+ /* @__PURE__ */ jsxs(Badge, { tone: "violet", variant: "subtle", size: "sm", className: "gap-1 py-1", children: [
315
+ /* @__PURE__ */ jsx4(Icon, { icon: IconFlask2Filled, size: 12 }),
316
+ "Labs"
317
+ ] })
318
+ ] }),
319
+ /* @__PURE__ */ jsx4(CopyButton, { text: notificationDraftToText(block.payload), copyLabel: "Copy draft", copiedLabel: "Copied" })
320
+ ] }),
321
+ showSubject && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
322
+ /* @__PURE__ */ jsx4(FieldLabel, { children: "Title" }),
323
+ /* @__PURE__ */ jsx4("p", { className: "text-geist-body-s-medium text-brown-100", children: subject })
324
+ ] }),
325
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
326
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
327
+ /* @__PURE__ */ jsx4(FieldLabel, { children: `Recipients (${recipients.length})` }),
328
+ /* @__PURE__ */ jsxs(
329
+ "button",
330
+ {
331
+ type: "button",
332
+ "aria-expanded": detailsOpen,
333
+ onClick: () => setDetailsOpen((open) => !open),
334
+ className: "inline-flex items-center gap-1 text-geist-label-caption-medium text-brown-70 transition-colors hover:text-brown-100",
335
+ children: [
336
+ "Details",
337
+ /* @__PURE__ */ jsx4(Icon, { icon: IconChevronDown, size: 14, className: detailsOpen ? "rotate-180" : void 0 })
338
+ ]
339
+ }
340
+ )
341
+ ] }),
342
+ detailsOpen ? /* @__PURE__ */ jsx4(RecipientsTable, { recipients }) : /* @__PURE__ */ jsx4(RecipientChips, { recipients })
343
+ ] })
344
+ ] }),
345
+ /* @__PURE__ */ jsx4("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx4(Markdown, { children: body }) })
346
+ ] });
347
+ }
348
+
349
+ // src/components/blocks/block-services/notification-draft-block-service.ts
350
+ var NotificationDraftBlockService = {
351
+ type: blockTypes.NOTIFICATION_DRAFT,
352
+ Component: NotificationDraftBlock,
353
+ /**
354
+ * Plain-text clipboard form of the drafted notification — shared with the in-block copy button
355
+ * via {@link notificationDraftToText} so both copy the same text.
356
+ */
357
+ toClipboardText: (block) => notificationDraftToText(block.payload)
358
+ };
359
+
360
+ // src/components/blocks/text-block/text-block.tsx
361
+ import { jsx as jsx5 } from "react/jsx-runtime";
362
+ function TextBlock({ block }) {
363
+ return /* @__PURE__ */ jsx5(Markdown, { children: block.payload.text });
364
+ }
365
+
366
+ // src/components/blocks/block-services/text-block-service.ts
367
+ var TextBlockService = {
368
+ type: blockTypes.TEXT,
369
+ Component: TextBlock,
370
+ /**
371
+ * Converts the block's markdown source to plain text so the final clipboard value is clean
372
+ * when pasted into tools that don't render markdown. Each block type owns its own clipboard
373
+ * conversion so `useCopyToClipboard` can write text verbatim without a second pass.
374
+ */
375
+ toClipboardText: (block) => markdownToPlainText(block.payload.text)
376
+ };
377
+
378
+ // src/components/blocks/block-services/index.ts
379
+ var BlockServices = {
380
+ [blockTypes.TEXT]: TextBlockService,
381
+ [blockTypes.ARTIFACT_REF]: ArtifactRefBlockService,
382
+ [blockTypes.NOTIFICATION_DRAFT]: NotificationDraftBlockService
383
+ };
384
+
385
+ // src/components/blocks/block/block.tsx
386
+ import { jsx as jsx6 } from "react/jsx-runtime";
387
+ function Block({ block }) {
388
+ const service = BlockServices[block.type];
389
+ if (!service) return null;
390
+ const { Component } = service;
391
+ return /* @__PURE__ */ jsx6(Component, { block });
392
+ }
393
+
394
+ export {
395
+ blockTypes,
396
+ ArtifactRefBlock,
397
+ ArtifactRefBlockService,
398
+ notificationChannels,
399
+ notificationEmailTypes,
400
+ NotificationDraftBlock,
401
+ NotificationDraftBlockService,
402
+ TextBlockService,
403
+ BlockServices,
404
+ Block
405
+ };
406
+ //# sourceMappingURL=chunk-JMHE3Q5K.js.map