@tangle-network/ui 6.0.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/run.js CHANGED
@@ -1,19 +1,17 @@
1
- import "./chunk-LQS34IGP.js";
1
+ import "./chunk-47XH56SV.js";
2
2
  import {
3
3
  ToolCallFeed,
4
4
  parseToolEvent
5
- } from "./chunk-O6NUUCT2.js";
5
+ } from "./chunk-IWQZXL6A.js";
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-LASW7CYH.js";
9
+ } from "./chunk-QIRVZMQY.js";
10
10
  import {
11
11
  ExpandedToolDetail,
12
12
  InlineToolItem,
13
- LiveDuration,
14
- ToolCallGroup,
15
- ToolCallStep
16
- } from "./chunk-EOGJX2TU.js";
13
+ LiveDuration
14
+ } from "./chunk-RKQDBRTC.js";
17
15
  import "./chunk-ULDNFLIM.js";
18
16
  import "./chunk-AAUNOHVL.js";
19
17
  import "./chunk-52Y3FMFI.js";
@@ -30,7 +28,5 @@ export {
30
28
  LiveDuration,
31
29
  RunGroup,
32
30
  ToolCallFeed,
33
- ToolCallGroup,
34
- ToolCallStep,
35
31
  parseToolEvent
36
32
  };
@@ -4,7 +4,7 @@ import { R as Run, G as GroupedMessage } from './run-PfLmDAox.js';
4
4
  import { S as SessionMessage } from './message-BHWbxBtT.js';
5
5
  import { S as SessionPart } from './parts-dj7AcUg0.js';
6
6
  import { R as RegisterActiveSessionOptions, g as ActiveSessionTransportMode, c as ActiveSessionConnectionState } from './active-sessions-store-CeOmXgv5.js';
7
- import { F as FeedSegment } from './tool-call-feed-Bs3MyQMT.js';
7
+ import { F as FeedSegment } from './tool-call-feed-D9iofJgW.js';
8
8
  import 'nanostores';
9
9
  import 'react/jsx-runtime';
10
10
 
package/dist/sdk-hooks.js CHANGED
@@ -5,15 +5,15 @@ import {
5
5
  useSSEStream,
6
6
  useSdkSession,
7
7
  useToolCallStream
8
- } from "./chunk-PN3S2MTV.js";
8
+ } from "./chunk-DLSGUNRD.js";
9
9
  import "./chunk-OEX7NZE3.js";
10
10
  import {
11
11
  useAutoScroll,
12
12
  useRunCollapseState,
13
13
  useRunGroups
14
14
  } from "./chunk-AZWDI2JG.js";
15
- import "./chunk-O6NUUCT2.js";
16
- import "./chunk-EOGJX2TU.js";
15
+ import "./chunk-IWQZXL6A.js";
16
+ import "./chunk-RKQDBRTC.js";
17
17
  import "./chunk-ULDNFLIM.js";
18
18
  import "./chunk-AAUNOHVL.js";
19
19
  import "./chunk-ZRVH3WCA.js";
@@ -1,29 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
2
 
4
3
  type ToolCallType = "bash" | "read" | "write" | "edit" | "glob" | "grep" | "list" | "download" | "inspect" | "audit" | "unknown";
5
4
  type ToolCallStatus = "running" | "success" | "error";
6
- interface ToolCallStepProps {
7
- type: ToolCallType;
8
- label: string;
9
- status: ToolCallStatus;
10
- detail?: string;
11
- output?: string;
12
- /** Override syntax highlighting language; inferred from detail path if omitted */
13
- language?: string;
14
- duration?: number;
15
- className?: string;
16
- }
17
- declare function ToolCallStep({ type, label, status, detail, output, language, duration, className, }: ToolCallStepProps): react_jsx_runtime.JSX.Element;
18
- /**
19
- * ToolCallGroup — groups multiple tool calls under a heading.
20
- */
21
- interface ToolCallGroupProps {
22
- title?: string;
23
- children: ReactNode;
24
- className?: string;
25
- }
26
- declare function ToolCallGroup({ title, children, className }: ToolCallGroupProps): react_jsx_runtime.JSX.Element;
27
5
 
28
6
  interface ToolCallData {
29
7
  id: string;
@@ -65,4 +43,4 @@ declare function parseToolEvent(event: {
65
43
  data: Record<string, unknown>;
66
44
  }): ToolCallData | null;
67
45
 
68
- export { type FeedSegment as F, type ToolCallData as T, ToolCallFeed as a, type ToolCallFeedProps as b, ToolCallGroup as c, type ToolCallGroupProps as d, type ToolCallStatus as e, ToolCallStep as f, type ToolCallStepProps as g, type ToolCallType as h, parseToolEvent as p };
46
+ export { type FeedSegment as F, type ToolCallData as T, ToolCallFeed as a, type ToolCallFeedProps as b, type ToolCallStatus as c, type ToolCallType as d, parseToolEvent as p };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "6.0.0",
3
+ "version": "8.0.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -132,7 +132,7 @@
132
132
  "react": "^18 || ^19",
133
133
  "react-dom": "^18 || ^19",
134
134
  "react-router": "^7",
135
- "@tangle-network/brand": "^0.7.0"
135
+ "@tangle-network/brand": "^0.8.0"
136
136
  },
137
137
  "peerDependenciesMeta": {
138
138
  "@nanostores/react": {
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  memo,
3
3
  type ReactNode,
4
- useCallback,
5
4
  useMemo,
6
5
  useRef,
7
6
  } from "react";
@@ -20,7 +19,6 @@ import {
20
19
  AgentTimeline,
21
20
  type AgentTimelineItem,
22
21
  } from "./agent-timeline";
23
- import { ChatInput, type PendingFile } from "./chat-input";
24
22
  import { InlineThinkingItem } from "../run/inline-thinking-item";
25
23
  import { getToolDisplayMetadata } from "../utils/tool-display";
26
24
  import {
@@ -29,27 +27,21 @@ import {
29
27
  type OpenUIComponentNode,
30
28
  } from "../openui/openui-artifact-renderer";
31
29
 
30
+ /**
31
+ * Transcript-only container: message list + auto-scroll. Composers are
32
+ * composed BELOW this by the app (the canonical one is `AgentComposer` in
33
+ * `@tangle-network/sandbox-ui`) — this component never renders an input.
34
+ */
32
35
  export interface ChatContainerProps {
33
36
  messages: SessionMessage[];
34
37
  partMap: Record<string, SessionPart[]>;
35
38
  isStreaming: boolean;
36
- onSend?: (text: string) => void;
37
- onCancel?: () => void;
38
39
  branding?: AgentBranding;
39
- placeholder?: string;
40
40
  className?: string;
41
- /** Hide the input area (useful for read-only views). */
42
- hideInput?: boolean;
43
41
  /** Custom renderer for tool details. Return ReactNode to override, null to use default. */
44
42
  renderToolDetail?: CustomToolRenderer;
45
43
  /** Presentation mode for the session view. */
46
44
  presentation?: "runs" | "timeline";
47
- modelLabel?: string;
48
- onModelClick?: () => void;
49
- pendingFiles?: PendingFile[];
50
- onRemoveFile?: (id: string) => void;
51
- onAttach?: (files: FileList) => void;
52
- disabled?: boolean;
53
45
  /** Callback when an OpenUI action button is pressed within inline OpenUI blocks. */
54
46
  onOpenUIAction?: (action: OpenUIAction) => void;
55
47
  /** Enable rendering OpenUI schemas inline in the chat timeline. Defaults to true. */
@@ -355,7 +347,7 @@ function buildTimelineItems(
355
347
  }
356
348
 
357
349
  /**
358
- * Full chat container: message list + auto-scroll + input area.
350
+ * Chat transcript container: message list + auto-scroll.
359
351
  * Orchestrates useRunGroups, useRunCollapseState, and useAutoScroll.
360
352
  */
361
353
  export const ChatContainer = memo(
@@ -363,20 +355,10 @@ export const ChatContainer = memo(
363
355
  messages,
364
356
  partMap,
365
357
  isStreaming,
366
- onSend,
367
- onCancel,
368
358
  branding,
369
- placeholder = "Type a message...",
370
359
  className,
371
- hideInput = false,
372
360
  renderToolDetail,
373
361
  presentation = "runs",
374
- modelLabel,
375
- onModelClick,
376
- pendingFiles,
377
- onRemoveFile,
378
- onAttach,
379
- disabled = false,
380
362
  onOpenUIAction,
381
363
  enableOpenUI = true,
382
364
  renderRunActions,
@@ -404,13 +386,6 @@ export const ChatContainer = memo(
404
386
  [messages, partMap, isStreaming, onOpenUIAction, enableOpenUI],
405
387
  );
406
388
 
407
- const handleSend = useCallback(
408
- (text: string) => {
409
- onSend?.(text);
410
- },
411
- [onSend],
412
- );
413
-
414
389
  return (
415
390
  <div className={cn("flex h-full flex-col", className)}>
416
391
  {/* Message area */}
@@ -462,23 +437,6 @@ export const ChatContainer = memo(
462
437
  </button>
463
438
  </div>
464
439
  )}
465
-
466
- {/* Input area */}
467
- {!hideInput && onSend && (
468
- <ChatInput
469
- onSend={handleSend}
470
- onCancel={onCancel}
471
- isStreaming={isStreaming}
472
- placeholder={placeholder}
473
- modelLabel={modelLabel}
474
- onModelClick={onModelClick}
475
- pendingFiles={pendingFiles}
476
- onRemoveFile={onRemoveFile}
477
- onAttach={onAttach}
478
- disabled={disabled}
479
- className="shrink-0 border-t border-border bg-background"
480
- />
481
- )}
482
440
  </div>
483
441
  );
484
442
  },
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { ChatMessage } from './chat-message'
3
- import { ToolCallStep } from '../run/tool-call-step'
3
+ import { InlineToolItem } from '../run/inline-tool-item'
4
+ import type { ToolPart } from '../types/parts'
4
5
 
5
6
  const meta: Meta<typeof ChatMessage> = {
6
7
  title: 'Chat/ChatMessage',
@@ -26,6 +27,30 @@ type Story = StoryObj<typeof ChatMessage>
26
27
  const ts = (offsetMinutes = 0) =>
27
28
  new Date(Date.now() - offsetMinutes * 60 * 1000)
28
29
 
30
+ const readToolPart: ToolPart = {
31
+ type: 'tool',
32
+ id: 'chat-read',
33
+ tool: 'read',
34
+ state: {
35
+ status: 'completed',
36
+ input: { file_path: 'src/hooks/useFetchData.ts' },
37
+ output: `export function useFetchData<T>(url: string) {\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(true)\n // ...`,
38
+ time: { start: Date.now() - 1200, end: Date.now() - 1152 },
39
+ },
40
+ }
41
+
42
+ const searchToolPart: ToolPart = {
43
+ type: 'tool',
44
+ id: 'chat-search',
45
+ tool: 'grep',
46
+ state: {
47
+ status: 'completed',
48
+ input: { pattern: 'cache', path: 'src/hooks/useFetchData.ts' },
49
+ output: 'No cache layer found. Data fetched on every mount.',
50
+ time: { start: Date.now() - 900, end: Date.now() - 888 },
51
+ },
52
+ }
53
+
29
54
  export const UserMessage: Story = {
30
55
  args: {
31
56
  role: 'user',
@@ -117,20 +142,8 @@ export const AssistantWithToolCalls: Story = {
117
142
  content: 'Let me read the current implementation first.',
118
143
  toolCalls: (
119
144
  <div className="mt-3 space-y-2">
120
- <ToolCallStep
121
- type="read"
122
- label="Read src/hooks/useFetchData.ts"
123
- status="success"
124
- output={`export function useFetchData<T>(url: string) {\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(true)\n // ...`}
125
- duration={48}
126
- />
127
- <ToolCallStep
128
- type="grep"
129
- label="Search for cache references"
130
- status="success"
131
- output="No cache layer found. Data fetched on every mount."
132
- duration={12}
133
- />
145
+ <InlineToolItem part={readToolPart} groupPosition="first" />
146
+ <InlineToolItem part={searchToolPart} groupPosition="last" />
134
147
  </div>
135
148
  ),
136
149
  timestamp: ts(1),
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import { type ReactNode } from "react";
9
- import { User, Bot } from "lucide-react";
10
9
  import { cn } from "../lib/utils";
11
10
  import { Markdown } from "../markdown/markdown";
12
11
 
@@ -28,10 +27,6 @@ export interface ChatMessageProps {
28
27
  assistantLabel?: string;
29
28
  /** Hide the role label row entirely */
30
29
  hideRoleLabel?: boolean;
31
- /** Hide the avatar icon */
32
- hideAvatar?: boolean;
33
- /** Custom avatar element (replaces default User/Bot icon) */
34
- avatar?: ReactNode;
35
30
  }
36
31
 
37
32
  export function ChatMessage({
@@ -44,36 +39,28 @@ export function ChatMessage({
44
39
  userLabel = "You",
45
40
  assistantLabel = "Agent",
46
41
  hideRoleLabel,
47
- hideAvatar,
48
- avatar,
49
42
  }: ChatMessageProps) {
50
43
  const isUser = role === "user";
51
44
 
52
45
  return (
53
46
  <div
54
47
  className={cn(
55
- "flex gap-3",
56
- isUser ? "flex-row-reverse" : "flex-row",
48
+ "flex flex-col gap-1",
49
+ isUser ? "items-end" : "items-start",
57
50
  className,
58
51
  )}
59
52
  >
60
- {/* Avatar */}
61
- {!hideAvatar && (
62
- avatar ? (
63
- <div className="mt-0.5 shrink-0">{avatar}</div>
64
- ) : (
65
- <div
66
- className={cn(
67
- "mt-0.5 flex shrink-0 items-center justify-center rounded-[calc(var(--radius-md)+2px)] border",
68
- "h-[var(--avatar-size)] w-[var(--avatar-size)]",
69
- isUser
70
- ? "border-border bg-[var(--accent-surface-soft)] text-[var(--accent-text)]"
71
- : "border-border bg-muted text-[var(--brand-cool)]",
72
- )}
73
- >
74
- {isUser ? <User className="h-3.5 w-3.5" /> : <Bot className="h-3.5 w-3.5" />}
75
- </div>
76
- )
53
+ {!hideRoleLabel && (
54
+ <div className={cn("flex items-center gap-2 px-1", isUser && "flex-row-reverse")}>
55
+ <span className="font-medium text-foreground text-xs">
56
+ {isUser ? userLabel : assistantLabel}
57
+ </span>
58
+ {timestamp && (
59
+ <span className="text-muted-foreground text-xs">
60
+ {formatTime(timestamp)}
61
+ </span>
62
+ )}
63
+ </div>
77
64
  )}
78
65
 
79
66
  {/* Bubble */}
@@ -86,20 +73,6 @@ export function ChatMessage({
86
73
  : "border-border bg-card",
87
74
  )}
88
75
  >
89
- {/* Role label + timestamp */}
90
- {!hideRoleLabel && (
91
- <div className={cn("flex items-center gap-2", isUser && "flex-row-reverse")}>
92
- <span className="text-[var(--font-size-xs)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.14em)] text-foreground">
93
- {isUser ? userLabel : assistantLabel}
94
- </span>
95
- {timestamp && (
96
- <span className="text-[var(--font-size-xs)] text-muted-foreground">
97
- {formatTime(timestamp)}
98
- </span>
99
- )}
100
- </div>
101
- )}
102
-
103
76
  {/* Message body */}
104
77
  {isUser ? (
105
78
  <div className="whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground">
package/src/chat/index.ts CHANGED
@@ -2,7 +2,6 @@ export { ChatContainer, type ChatContainerProps } from "./chat-container";
2
2
  export { MessageList, type MessageListProps } from "./message-list";
3
3
  export { UserMessage, type UserMessageProps } from "./user-message";
4
4
  export { ChatMessage, type ChatMessageProps, type MessageRole } from "./chat-message";
5
- export { ChatInput, type ChatInputProps, type PendingFile } from "./chat-input";
6
5
  export { ThinkingIndicator, type ThinkingIndicatorProps } from "./thinking-indicator";
7
6
  export {
8
7
  AgentTimeline,
@@ -39,7 +39,7 @@ pnpm add @tangle/sandbox-ui
39
39
  Import the components you need:
40
40
 
41
41
  \`\`\`tsx
42
- import { ChatInput, DropZone, UploadProgress } from '@tangle/sandbox-ui'
42
+ import { AgentComposer, DropZone, UploadProgress } from '@tangle-network/sandbox-ui'
43
43
  \`\`\`
44
44
 
45
45
  > **Note:** Components assume a Tailwind CSS v4 setup with the design tokens
package/src/run/index.ts CHANGED
@@ -9,5 +9,8 @@ export {
9
9
  type ExpandedToolDetailProps,
10
10
  } from "./expanded-tool-detail";
11
11
  export { LiveDuration } from "./run-item-primitives";
12
- export { ToolCallStep, ToolCallGroup, type ToolCallStepProps, type ToolCallGroupProps, type ToolCallType, type ToolCallStatus } from "./tool-call-step";
12
+ // ToolCallStep/ToolCallGroup are internal adapters over InlineToolItem (used by
13
+ // AgentTimeline + ToolCallFeed); only their status/type vocabulary is public
14
+ // because ToolCallData references it.
15
+ export { type ToolCallType, type ToolCallStatus } from "./tool-call-step";
13
16
  export { ToolCallFeed, parseToolEvent, type ToolCallFeedProps, type ToolCallData, type FeedSegment } from "./tool-call-feed";
@@ -115,7 +115,7 @@ export const InlineToolItem = memo(
115
115
  className,
116
116
  )}
117
117
  >
118
- <div className="flex items-center gap-2 px-2.5 py-1.5">
118
+ <div className="flex items-center gap-2.5 px-3 py-2.5">
119
119
  <div className={cn(
120
120
  "shrink-0",
121
121
  isRunning && "text-primary",
@@ -151,13 +151,13 @@ export const InlineToolItem = memo(
151
151
  </span>
152
152
  ) : null}
153
153
  {isError ? (
154
- <span className="rounded-full border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-1.5 py-px text-[10px] font-semibold uppercase text-[var(--surface-danger-text)]">
155
- Failed
154
+ <span className="text-[10px] font-medium text-[var(--surface-danger-text)]">
155
+ failed
156
156
  </span>
157
157
  ) : null}
158
158
  {isRunning ? (
159
- <span className="rounded-full border border-[var(--border-accent)] bg-primary/10 px-1.5 py-px text-[10px] font-semibold uppercase text-primary">
160
- Running
159
+ <span className="text-[10px] font-medium text-primary">
160
+ running
161
161
  </span>
162
162
  ) : null}
163
163
  {open ? (
@@ -58,23 +58,17 @@ function AssistantShell({
58
58
  children: ReactNode;
59
59
  }) {
60
60
  return (
61
- <div className="flex gap-2.5">
62
- <div className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white">
63
- <Bot className="h-3.5 w-3.5" />
64
- </div>
65
-
66
- <div className={ASSISTANT_SHELL}>
67
- <div className="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-[var(--text-muted)]">
68
- <span>{branding.label}</span>
69
- {isStreaming ? (
70
- <span className="inline-flex items-center gap-1.5 text-[var(--text-muted)]">
71
- <Loader2 className="h-3 w-3 animate-spin" />
72
- Thinking
73
- </span>
74
- ) : null}
75
- </div>
76
- {children}
61
+ <div className="flex flex-col gap-1">
62
+ <div className="flex items-center gap-2 px-1 font-medium text-muted-foreground text-xs">
63
+ <span>{branding.label}</span>
64
+ {isStreaming ? (
65
+ <span className="inline-flex items-center gap-1.5">
66
+ <Loader2 className="h-3 w-3 animate-spin" />
67
+ Thinking
68
+ </span>
69
+ ) : null}
77
70
  </div>
71
+ <div className={ASSISTANT_SHELL}>{children}</div>
78
72
  </div>
79
73
  );
80
74
  }
@@ -411,15 +405,7 @@ export const RunGroup = memo(
411
405
  )}
412
406
  >
413
407
  <div className="flex items-center gap-2">
414
- <div
415
- className={cn(
416
- "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--brand-primary)] text-white",
417
- )}
418
- >
419
- <Bot className="h-3.5 w-3.5" />
420
- </div>
421
-
422
- <span className={cn("text-sm font-semibold", branding.textClass)}>
408
+ <span className={cn("font-semibold text-sm", branding.textClass)}>
423
409
  {branding.label}
424
410
  </span>
425
411
 
@@ -1,11 +1,9 @@
1
1
  /**
2
- * ToolCallStep — a single agent tool invocation as a collapsible activity step.
3
- *
4
- * Now a thin adapter over the canonical `InlineToolItem`: it maps the flat
5
- * `ToolCallData`-style props (label / status / detail / output / duration) onto
6
- * a `ToolPart` and delegates rendering, so the timeline feed (`AgentTimeline`,
7
- * `ToolCallFeed`) and the run group share ONE row implementation and one look.
8
- * The bespoke row markup is gone; only the prop adapter remains.
2
+ * ToolCallStep — internal adapter over the canonical `InlineToolItem` row.
3
+ * Maps flat `ToolCallData`-style props (label / status / detail / output /
4
+ * duration) onto a `ToolPart` so `AgentTimeline` and `ToolCallFeed` share the
5
+ * one row implementation. Not exported publicly; only the `ToolCallType` /
6
+ * `ToolCallStatus` vocabulary is (via `ToolCallData`).
9
7
  */
10
8
 
11
9
  import { type ReactNode } from "react";
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { InlineToolItem } from "../run/inline-tool-item";
3
+ import type { ToolPart } from "../types/parts";
4
+
5
+ const meta: Meta = {
6
+ title: "Foundations/Theme Showcase",
7
+ parameters: { layout: "fullscreen" },
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj;
11
+
12
+ const THEMES: { name: string; theme?: string }[] = [
13
+ { name: "Tangle (neutral)", theme: undefined }, // default :root dark
14
+ { name: "Tangle · light", theme: "tangle-light" },
15
+ { name: "Aubergine (bazaar)", theme: "aubergine" },
16
+ { name: "Aubergine · light", theme: "aubergine-light" },
17
+ { name: "Experimental green", theme: "arena" },
18
+ { name: "Experimental green · light", theme: "arena-light" },
19
+ ];
20
+
21
+ const NOW = Date.now();
22
+
23
+ const themeToolParts: ToolPart[] = [
24
+ {
25
+ type: "tool",
26
+ id: "theme-read",
27
+ tool: "read",
28
+ state: {
29
+ status: "completed",
30
+ input: { file_path: "src/batch-writer.ts" },
31
+ time: { start: NOW - 3100, end: NOW - 2500 },
32
+ },
33
+ },
34
+ {
35
+ type: "tool",
36
+ id: "theme-search",
37
+ tool: "grep",
38
+ state: {
39
+ status: "completed",
40
+ input: { pattern: "await sleep\\(" },
41
+ time: { start: NOW - 2200, end: NOW - 1800 },
42
+ },
43
+ },
44
+ {
45
+ type: "tool",
46
+ id: "theme-build",
47
+ tool: "bash",
48
+ state: {
49
+ status: "error",
50
+ input: { command: "pnpm test" },
51
+ error: "1 failing test",
52
+ time: { start: NOW - 1600, end: NOW - 400 },
53
+ },
54
+ },
55
+ ];
56
+
57
+ function Cell({ name, theme }: { name: string; theme?: string }) {
58
+ return (
59
+ <div
60
+ data-theme={theme}
61
+ className="flex flex-col gap-3 rounded-2xl border border-border bg-background p-5"
62
+ >
63
+ <div className="flex items-center justify-between">
64
+ <span className="font-semibold text-foreground text-sm">{name}</span>
65
+ <span className="font-medium text-muted-foreground text-xs">
66
+ {theme?.includes("light") ? "Light" : "Dark"}
67
+ </span>
68
+ </div>
69
+
70
+ <div className="space-y-1.5 rounded-xl border border-[var(--border-subtle)] bg-surface-container-high p-3">
71
+ <div className="flex items-center gap-2">
72
+ <span className="size-2 rounded-full bg-primary" />
73
+ <span className="font-semibold text-foreground text-xs">Agent</span>
74
+ <span className="text-[11px] text-muted-foreground">4 tools · 3s</span>
75
+ </div>
76
+ {themeToolParts.map((part, index) => (
77
+ <InlineToolItem
78
+ key={part.id}
79
+ part={part}
80
+ groupPosition={index === 0 ? "first" : index === themeToolParts.length - 1 ? "last" : "middle"}
81
+ />
82
+ ))}
83
+ </div>
84
+
85
+ <p className="text-muted-foreground text-xs leading-relaxed">
86
+ Body copy on the canvas. Surfaces separate by fill; the border is a quiet edge.
87
+ </p>
88
+
89
+ <div className="flex items-center gap-2">
90
+ <button
91
+ type="button"
92
+ className="rounded-lg bg-primary px-3 py-1.5 font-medium text-primary-foreground text-xs"
93
+ >
94
+ Primary
95
+ </button>
96
+ <button
97
+ type="button"
98
+ className="rounded-lg border border-border bg-muted px-3 py-1.5 font-medium text-foreground text-xs"
99
+ >
100
+ Secondary
101
+ </button>
102
+ </div>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ export const AllThemes: Story = {
108
+ render: () => (
109
+ <div className="min-h-screen bg-[#0a0a0c] p-6">
110
+ <div className="grid grid-cols-1 gap-5 md:grid-cols-2">
111
+ {THEMES.map((t) => (
112
+ <Cell key={t.name} name={t.name} theme={t.theme} />
113
+ ))}
114
+ </div>
115
+ </div>
116
+ ),
117
+ };