ai-design-system 0.1.95 → 0.1.96
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/components/composites/StoriesBar/StoriesBar.stories.tsx +69 -0
- package/components/composites/StoriesBar/StoriesBar.tsx +136 -0
- package/components/composites/StoriesBar/index.ts +2 -0
- package/components/composites/TaskQueue/TaskQueue.tsx +8 -5
- package/components/composites/TaskQueue/interfaces.ts +4 -0
- package/components/composites/index.ts +5 -0
- package/components/features/ChatPanel/ChatPanel.tsx +16 -8
- package/components/index.ts +3 -0
- package/dist/index.cjs +130 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +45 -0
- package/dist/index.d.ts +221 -188
- package/dist/index.js +130 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { StoriesBar, type StoryItem } from "./StoriesBar";
|
|
3
|
+
import { fn } from "@storybook/test";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StoriesBar Composite Stories
|
|
7
|
+
*
|
|
8
|
+
* Displays circular story avatar bubbles with status indicators and titles.
|
|
9
|
+
*/
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Composites/StoriesBar",
|
|
12
|
+
component: StoriesBar,
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
parameters: { layout: "padded" },
|
|
15
|
+
args: {
|
|
16
|
+
onStoryClick: fn(),
|
|
17
|
+
},
|
|
18
|
+
} satisfies Meta<typeof StoriesBar>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
const mockStories: StoryItem[] = [
|
|
24
|
+
{
|
|
25
|
+
id: "story-1",
|
|
26
|
+
title: "Devin iOS",
|
|
27
|
+
status: "ready",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "story-2",
|
|
31
|
+
title: "Waypoint",
|
|
32
|
+
status: "ready",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "story-3",
|
|
36
|
+
title: "Design Sys",
|
|
37
|
+
status: "generating",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "story-4",
|
|
41
|
+
title: "Workspace",
|
|
42
|
+
status: "ready",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "story-5",
|
|
46
|
+
title: "ZeroOps",
|
|
47
|
+
status: "draft",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default usage with active story
|
|
53
|
+
*/
|
|
54
|
+
export const Default: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
activeStoryId: "story-1",
|
|
57
|
+
stories: mockStories,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Empty stories bar
|
|
63
|
+
*/
|
|
64
|
+
export const EmptyState: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
activeStoryId: null,
|
|
67
|
+
stories: [],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Button } from "@/components/primitives/Button";
|
|
5
|
+
import { Icon } from "@/components/primitives/Icon";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
export interface StoryItem {
|
|
9
|
+
id: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
title: string;
|
|
12
|
+
videoTitle?: string;
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
icon?: string;
|
|
15
|
+
time?: string;
|
|
16
|
+
status?: "ready" | "generating" | "draft";
|
|
17
|
+
thumbnailUrl?: string;
|
|
18
|
+
avatarColor?: string;
|
|
19
|
+
avatarInitials?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StoriesBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
stories: StoryItem[];
|
|
24
|
+
activeStoryId?: string | null;
|
|
25
|
+
onStoryClick: (story: StoryItem) => void;
|
|
26
|
+
defaultIcon?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getStoryStatusStyle(status?: StoryItem["status"]) {
|
|
30
|
+
switch (status) {
|
|
31
|
+
case "generating":
|
|
32
|
+
return {
|
|
33
|
+
iconColor: "text-amber-500",
|
|
34
|
+
ringClass: "ring-2 ring-amber-500 animate-pulse",
|
|
35
|
+
badgeBg: "bg-amber-500",
|
|
36
|
+
};
|
|
37
|
+
case "ready":
|
|
38
|
+
return {
|
|
39
|
+
iconColor: "text-emerald-500",
|
|
40
|
+
ringClass: "ring-1 ring-emerald-500/70 group-hover:ring-emerald-500",
|
|
41
|
+
badgeBg: "bg-emerald-500",
|
|
42
|
+
};
|
|
43
|
+
case "draft":
|
|
44
|
+
default:
|
|
45
|
+
return {
|
|
46
|
+
iconColor: "text-muted-foreground",
|
|
47
|
+
ringClass: "ring-1 ring-border group-hover:ring-primary/60",
|
|
48
|
+
badgeBg: "bg-muted-foreground",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
|
|
54
|
+
({ stories, activeStoryId, onStoryClick, defaultIcon = "clapperboard", className, ...props }, ref) => {
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn("w-full border-b shrink-0", className)}
|
|
59
|
+
data-testid="stories-bar"
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
<div className="flex items-center gap-3 overflow-x-auto no-scrollbar scroll-smooth">
|
|
63
|
+
{stories.map((story) => {
|
|
64
|
+
const isActive = story.id === activeStoryId;
|
|
65
|
+
const isGenerating = story.status === "generating";
|
|
66
|
+
const statusStyle = getStoryStatusStyle(story.status);
|
|
67
|
+
const iconName = story.icon || defaultIcon;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Button
|
|
71
|
+
key={story.id}
|
|
72
|
+
variant="ghost"
|
|
73
|
+
onClick={() => onStoryClick(story)}
|
|
74
|
+
className="flex flex-col items-center gap-1 h-auto p-0 hover:bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-lg cursor-pointer group shrink-0"
|
|
75
|
+
data-testid={`btn-story-${story.id}`}
|
|
76
|
+
aria-label={`Open story: ${story.title}`}
|
|
77
|
+
>
|
|
78
|
+
<div
|
|
79
|
+
className={cn(
|
|
80
|
+
"relative w-12 h-12 shrink-0 aspect-square rounded-full transition-transform group-hover:scale-105",
|
|
81
|
+
isActive
|
|
82
|
+
? "ring-2 ring-primary ring-offset-2 ring-offset-background"
|
|
83
|
+
: statusStyle.ringClass
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<div className="w-full h-full rounded-full flex items-center justify-center border shadow-sm overflow-hidden select-none">
|
|
87
|
+
{story.thumbnailUrl ? (
|
|
88
|
+
<img
|
|
89
|
+
src={story.thumbnailUrl}
|
|
90
|
+
alt={story.title}
|
|
91
|
+
className="w-full h-full object-cover"
|
|
92
|
+
/>
|
|
93
|
+
) : (
|
|
94
|
+
<Icon
|
|
95
|
+
name={iconName}
|
|
96
|
+
className={cn("w-5 h-5 transition-colors", statusStyle.iconColor)}
|
|
97
|
+
/>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{isGenerating ? (
|
|
102
|
+
<span className="absolute bottom-0 right-0 flex h-2.5 w-2.5">
|
|
103
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75" />
|
|
104
|
+
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-amber-500" />
|
|
105
|
+
</span>
|
|
106
|
+
) : (
|
|
107
|
+
<span
|
|
108
|
+
className={cn(
|
|
109
|
+
"absolute bottom-0 right-0 inline-flex rounded-full h-2.5 w-2.5 ring-2 ring-background",
|
|
110
|
+
statusStyle.badgeBg
|
|
111
|
+
)}
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<span
|
|
117
|
+
className={cn(
|
|
118
|
+
"text-xs truncate max-w-[58px] text-center",
|
|
119
|
+
isActive
|
|
120
|
+
? "font-semibold text-foreground"
|
|
121
|
+
: "text-muted-foreground group-hover:text-foreground"
|
|
122
|
+
)}
|
|
123
|
+
title={story.title}
|
|
124
|
+
>
|
|
125
|
+
{story.title}
|
|
126
|
+
</span>
|
|
127
|
+
</Button>
|
|
128
|
+
);
|
|
129
|
+
})}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
StoriesBar.displayName = "StoriesBar";
|
|
@@ -201,8 +201,11 @@ export const TaskQueue = React.memo<TaskQueueProps>(
|
|
|
201
201
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
|
202
202
|
{getStatusIcon(task.status)}
|
|
203
203
|
|
|
204
|
-
<QueueItemContent
|
|
205
|
-
|
|
204
|
+
<QueueItemContent
|
|
205
|
+
className="font-mono text-sm text-foreground select-all"
|
|
206
|
+
title={task.runId ? `Run ID: ${task.runId}` : undefined}
|
|
207
|
+
>
|
|
208
|
+
{task.title || task.command}
|
|
206
209
|
</QueueItemContent>
|
|
207
210
|
|
|
208
211
|
{task.duration && (
|
|
@@ -219,7 +222,7 @@ export const TaskQueue = React.memo<TaskQueueProps>(
|
|
|
219
222
|
e.stopPropagation();
|
|
220
223
|
onStop(task.id);
|
|
221
224
|
}}
|
|
222
|
-
aria-label={`Stop ${task.
|
|
225
|
+
aria-label={`Stop ${task.title || task.command}`}
|
|
223
226
|
title="Stop task"
|
|
224
227
|
className="size-6 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 opacity-100 group-hover:opacity-100"
|
|
225
228
|
>
|
|
@@ -233,7 +236,7 @@ export const TaskQueue = React.memo<TaskQueueProps>(
|
|
|
233
236
|
e.stopPropagation();
|
|
234
237
|
onCancel(task.id);
|
|
235
238
|
}}
|
|
236
|
-
aria-label={`Cancel ${task.
|
|
239
|
+
aria-label={`Cancel ${task.title || task.command}`}
|
|
237
240
|
title="Cancel task"
|
|
238
241
|
className="size-6 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 opacity-100 group-hover:opacity-100"
|
|
239
242
|
>
|
|
@@ -247,7 +250,7 @@ export const TaskQueue = React.memo<TaskQueueProps>(
|
|
|
247
250
|
e.stopPropagation();
|
|
248
251
|
onRetry(task.id);
|
|
249
252
|
}}
|
|
250
|
-
aria-label={`Retry ${task.
|
|
253
|
+
aria-label={`Retry ${task.title || task.command}`}
|
|
251
254
|
title="Retry task"
|
|
252
255
|
className="size-6 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 opacity-100 group-hover:opacity-100"
|
|
253
256
|
>
|
|
@@ -280,3 +280,8 @@ export type { MediaPreviewProps, MediaFileLike } from './MediaPreview'
|
|
|
280
280
|
// WorkspaceEmptyState Composite
|
|
281
281
|
export { WorkspaceEmptyState } from './WorkspaceEmptyState'
|
|
282
282
|
export type { WorkspaceEmptyStateProps } from './WorkspaceEmptyState'
|
|
283
|
+
|
|
284
|
+
// StoriesBar Composite
|
|
285
|
+
export { StoriesBar } from './StoriesBar'
|
|
286
|
+
export type { StoriesBarProps, StoryItem } from './StoriesBar'
|
|
287
|
+
|
|
@@ -199,6 +199,11 @@ export interface ChatPanelProps {
|
|
|
199
199
|
* @default true
|
|
200
200
|
*/
|
|
201
201
|
enableSpeech?: boolean;
|
|
202
|
+
/**
|
|
203
|
+
* Whether to display the session header at the top of the chat panel.
|
|
204
|
+
* @default true
|
|
205
|
+
*/
|
|
206
|
+
showHeader?: boolean;
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
/**
|
|
@@ -243,6 +248,7 @@ export const ChatPanel = React.memo<ChatPanelProps>(
|
|
|
243
248
|
onTaskStopAll,
|
|
244
249
|
onTaskClick,
|
|
245
250
|
enableSpeech = true,
|
|
251
|
+
showHeader = true,
|
|
246
252
|
}) => {
|
|
247
253
|
// File change queue state
|
|
248
254
|
const [fileChangeState, setFileChangeState] = React.useState<
|
|
@@ -397,14 +403,16 @@ export const ChatPanel = React.memo<ChatPanelProps>(
|
|
|
397
403
|
return (
|
|
398
404
|
<div className={`relative flex h-full flex-col ${className || ""}`}>
|
|
399
405
|
{/* Chat Session Header */}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
406
|
+
{showHeader && (
|
|
407
|
+
<SessionHeader
|
|
408
|
+
sessions={sessions}
|
|
409
|
+
activeSessionId={activeSessionId}
|
|
410
|
+
onNewSession={onNewSession}
|
|
411
|
+
onCloseSession={onCloseSession}
|
|
412
|
+
onSelectSession={onSelectSession}
|
|
413
|
+
onDownloadSession={onDownloadSession || (async () => exportMessagesToMarkdownFile(messages as ExportableMessage[]))}
|
|
414
|
+
/>
|
|
415
|
+
)}
|
|
408
416
|
|
|
409
417
|
<AIConversation
|
|
410
418
|
messages={messages}
|
package/components/index.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1487,6 +1487,16 @@ var defaultIcons = {
|
|
|
1487
1487
|
{ type: "circle", attrs: { cx: "9", cy: "9", r: "2" } },
|
|
1488
1488
|
{ type: "path", attrs: { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" } }
|
|
1489
1489
|
]
|
|
1490
|
+
},
|
|
1491
|
+
"clapperboard": {
|
|
1492
|
+
name: "clapperboard",
|
|
1493
|
+
viewBox: "0 0 24 24",
|
|
1494
|
+
elements: [
|
|
1495
|
+
{ type: "path", attrs: { d: "M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z" } },
|
|
1496
|
+
{ type: "path", attrs: { d: "m6.2 5.3 3.1 3.9" } },
|
|
1497
|
+
{ type: "path", attrs: { d: "m12.4 3.4 3.1 4" } },
|
|
1498
|
+
{ type: "path", attrs: { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" } }
|
|
1499
|
+
]
|
|
1490
1500
|
}
|
|
1491
1501
|
};
|
|
1492
1502
|
var IconRegistry = class {
|
|
@@ -7169,7 +7179,14 @@ var TaskQueue = React3__namespace.memo(
|
|
|
7169
7179
|
children: [
|
|
7170
7180
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 min-w-0 flex-1", children: [
|
|
7171
7181
|
getStatusIcon(task.status),
|
|
7172
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7182
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7183
|
+
QueueItemContent,
|
|
7184
|
+
{
|
|
7185
|
+
className: "font-mono text-sm text-foreground select-all",
|
|
7186
|
+
title: task.runId ? `Run ID: ${task.runId}` : void 0,
|
|
7187
|
+
children: task.title || task.command
|
|
7188
|
+
}
|
|
7189
|
+
),
|
|
7173
7190
|
task.duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground/60 font-sans ml-1 flex-shrink-0", children: task.duration })
|
|
7174
7191
|
] }),
|
|
7175
7192
|
/* @__PURE__ */ jsxRuntime.jsxs(QueueItemActions, { children: [
|
|
@@ -7180,7 +7197,7 @@ var TaskQueue = React3__namespace.memo(
|
|
|
7180
7197
|
e.stopPropagation();
|
|
7181
7198
|
onStop(task.id);
|
|
7182
7199
|
},
|
|
7183
|
-
"aria-label": `Stop ${task.
|
|
7200
|
+
"aria-label": `Stop ${task.title || task.command}`,
|
|
7184
7201
|
title: "Stop task",
|
|
7185
7202
|
className: "size-6 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 opacity-100 group-hover:opacity-100",
|
|
7186
7203
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "circle-stop", size: "sm" })
|
|
@@ -7193,7 +7210,7 @@ var TaskQueue = React3__namespace.memo(
|
|
|
7193
7210
|
e.stopPropagation();
|
|
7194
7211
|
onCancel(task.id);
|
|
7195
7212
|
},
|
|
7196
|
-
"aria-label": `Cancel ${task.
|
|
7213
|
+
"aria-label": `Cancel ${task.title || task.command}`,
|
|
7197
7214
|
title: "Cancel task",
|
|
7198
7215
|
className: "size-6 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 opacity-100 group-hover:opacity-100",
|
|
7199
7216
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", size: "sm" })
|
|
@@ -7206,7 +7223,7 @@ var TaskQueue = React3__namespace.memo(
|
|
|
7206
7223
|
e.stopPropagation();
|
|
7207
7224
|
onRetry(task.id);
|
|
7208
7225
|
},
|
|
7209
|
-
"aria-label": `Retry ${task.
|
|
7226
|
+
"aria-label": `Retry ${task.title || task.command}`,
|
|
7210
7227
|
title: "Retry task",
|
|
7211
7228
|
className: "size-6 rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 opacity-100 group-hover:opacity-100",
|
|
7212
7229
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "refresh-cw", size: "sm" })
|
|
@@ -8021,7 +8038,8 @@ var ChatPanel = React3__namespace.memo(
|
|
|
8021
8038
|
onTaskRetry,
|
|
8022
8039
|
onTaskStopAll,
|
|
8023
8040
|
onTaskClick,
|
|
8024
|
-
enableSpeech = true
|
|
8041
|
+
enableSpeech = true,
|
|
8042
|
+
showHeader = true
|
|
8025
8043
|
}) => {
|
|
8026
8044
|
const [fileChangeState, setFileChangeState] = React3__namespace.useState("approval-requested");
|
|
8027
8045
|
const [approval, setApproval] = React3__namespace.useState({});
|
|
@@ -8140,7 +8158,7 @@ var ChatPanel = React3__namespace.memo(
|
|
|
8140
8158
|
);
|
|
8141
8159
|
}, [loading, messages]);
|
|
8142
8160
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative flex h-full flex-col ${className || ""}`, children: [
|
|
8143
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8161
|
+
showHeader && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8144
8162
|
SessionHeader,
|
|
8145
8163
|
{
|
|
8146
8164
|
sessions,
|
|
@@ -14375,6 +14393,111 @@ var WorkspaceEmptyState = React3__namespace.memo(
|
|
|
14375
14393
|
}
|
|
14376
14394
|
);
|
|
14377
14395
|
WorkspaceEmptyState.displayName = "WorkspaceEmptyState";
|
|
14396
|
+
function getStoryStatusStyle(status) {
|
|
14397
|
+
switch (status) {
|
|
14398
|
+
case "generating":
|
|
14399
|
+
return {
|
|
14400
|
+
iconColor: "text-amber-500",
|
|
14401
|
+
ringClass: "ring-2 ring-amber-500 animate-pulse",
|
|
14402
|
+
badgeBg: "bg-amber-500"
|
|
14403
|
+
};
|
|
14404
|
+
case "ready":
|
|
14405
|
+
return {
|
|
14406
|
+
iconColor: "text-emerald-500",
|
|
14407
|
+
ringClass: "ring-1 ring-emerald-500/70 group-hover:ring-emerald-500",
|
|
14408
|
+
badgeBg: "bg-emerald-500"
|
|
14409
|
+
};
|
|
14410
|
+
case "draft":
|
|
14411
|
+
default:
|
|
14412
|
+
return {
|
|
14413
|
+
iconColor: "text-muted-foreground",
|
|
14414
|
+
ringClass: "ring-1 ring-border group-hover:ring-primary/60",
|
|
14415
|
+
badgeBg: "bg-muted-foreground"
|
|
14416
|
+
};
|
|
14417
|
+
}
|
|
14418
|
+
}
|
|
14419
|
+
var StoriesBar = React3__namespace.forwardRef(
|
|
14420
|
+
(_a, ref) => {
|
|
14421
|
+
var _b = _a, { stories, activeStoryId, onStoryClick, defaultIcon = "clapperboard", className } = _b, props = __objRest(_b, ["stories", "activeStoryId", "onStoryClick", "defaultIcon", "className"]);
|
|
14422
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14423
|
+
"div",
|
|
14424
|
+
__spreadProps(__spreadValues({
|
|
14425
|
+
ref,
|
|
14426
|
+
className: cn("w-full border-b shrink-0", className),
|
|
14427
|
+
"data-testid": "stories-bar"
|
|
14428
|
+
}, props), {
|
|
14429
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3 overflow-x-auto no-scrollbar scroll-smooth", children: stories.map((story) => {
|
|
14430
|
+
const isActive = story.id === activeStoryId;
|
|
14431
|
+
const isGenerating = story.status === "generating";
|
|
14432
|
+
const statusStyle = getStoryStatusStyle(story.status);
|
|
14433
|
+
const iconName = story.icon || defaultIcon;
|
|
14434
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14435
|
+
Button2,
|
|
14436
|
+
{
|
|
14437
|
+
variant: "ghost",
|
|
14438
|
+
onClick: () => onStoryClick(story),
|
|
14439
|
+
className: "flex flex-col items-center gap-1 h-auto p-0 hover:bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-lg cursor-pointer group shrink-0",
|
|
14440
|
+
"data-testid": `btn-story-${story.id}`,
|
|
14441
|
+
"aria-label": `Open story: ${story.title}`,
|
|
14442
|
+
children: [
|
|
14443
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
14444
|
+
"div",
|
|
14445
|
+
{
|
|
14446
|
+
className: cn(
|
|
14447
|
+
"relative w-12 h-12 shrink-0 aspect-square rounded-full transition-transform group-hover:scale-105",
|
|
14448
|
+
isActive ? "ring-2 ring-primary ring-offset-2 ring-offset-background" : statusStyle.ringClass
|
|
14449
|
+
),
|
|
14450
|
+
children: [
|
|
14451
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full rounded-full flex items-center justify-center border shadow-sm overflow-hidden select-none", children: story.thumbnailUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
14452
|
+
"img",
|
|
14453
|
+
{
|
|
14454
|
+
src: story.thumbnailUrl,
|
|
14455
|
+
alt: story.title,
|
|
14456
|
+
className: "w-full h-full object-cover"
|
|
14457
|
+
}
|
|
14458
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
14459
|
+
Icon,
|
|
14460
|
+
{
|
|
14461
|
+
name: iconName,
|
|
14462
|
+
className: cn("w-5 h-5 transition-colors", statusStyle.iconColor)
|
|
14463
|
+
}
|
|
14464
|
+
) }),
|
|
14465
|
+
isGenerating ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute bottom-0 right-0 flex h-2.5 w-2.5", children: [
|
|
14466
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75" }),
|
|
14467
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-amber-500" })
|
|
14468
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
14469
|
+
"span",
|
|
14470
|
+
{
|
|
14471
|
+
className: cn(
|
|
14472
|
+
"absolute bottom-0 right-0 inline-flex rounded-full h-2.5 w-2.5 ring-2 ring-background",
|
|
14473
|
+
statusStyle.badgeBg
|
|
14474
|
+
)
|
|
14475
|
+
}
|
|
14476
|
+
)
|
|
14477
|
+
]
|
|
14478
|
+
}
|
|
14479
|
+
),
|
|
14480
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14481
|
+
"span",
|
|
14482
|
+
{
|
|
14483
|
+
className: cn(
|
|
14484
|
+
"text-xs truncate max-w-[58px] text-center",
|
|
14485
|
+
isActive ? "font-semibold text-foreground" : "text-muted-foreground group-hover:text-foreground"
|
|
14486
|
+
),
|
|
14487
|
+
title: story.title,
|
|
14488
|
+
children: story.title
|
|
14489
|
+
}
|
|
14490
|
+
)
|
|
14491
|
+
]
|
|
14492
|
+
},
|
|
14493
|
+
story.id
|
|
14494
|
+
);
|
|
14495
|
+
}) })
|
|
14496
|
+
})
|
|
14497
|
+
);
|
|
14498
|
+
}
|
|
14499
|
+
);
|
|
14500
|
+
StoriesBar.displayName = "StoriesBar";
|
|
14378
14501
|
var SpecNavigator = React3__namespace.memo(
|
|
14379
14502
|
({ groups, selectedFileId, onFileSelect, className }) => {
|
|
14380
14503
|
var _a, _b;
|
|
@@ -17105,6 +17228,7 @@ exports.SectionLayout = SectionLayout;
|
|
|
17105
17228
|
exports.SessionHeader = SessionHeader;
|
|
17106
17229
|
exports.SpecNavigator = SpecNavigator;
|
|
17107
17230
|
exports.SpeechInput = SpeechInput;
|
|
17231
|
+
exports.StoriesBar = StoriesBar;
|
|
17108
17232
|
exports.TaskQueue = TaskQueue;
|
|
17109
17233
|
exports.TextEditor = TextEditor;
|
|
17110
17234
|
exports.WorkflowCanvas = WorkflowCanvas;
|