ai-design-system 0.1.97 → 0.1.99

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.
@@ -20,12 +20,14 @@ import type { FileDownloadResult } from '@/components/composites/FileTreeExplore
20
20
 
21
21
 
22
22
  export interface SessionHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
23
+ title?: string;
23
24
  sessions?: ChatSessionInfo[];
24
25
  activeSessionId?: string | null;
25
26
  onNewSession?: () => void;
26
27
  onCloseSession?: (id: string) => void;
27
28
  onSelectSession?: (id: string) => void;
28
29
  onDownloadSession?: () => Promise<FileDownloadResult | undefined>;
30
+ showActions?: boolean;
29
31
  }
30
32
 
31
33
  /**
@@ -35,7 +37,7 @@ export interface SessionHeaderProps extends React.HTMLAttributes<HTMLDivElement>
35
37
  * and a dropdown history of past sessions.
36
38
  */
37
39
  export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps>(
38
- ({ sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, className, ...props }, ref) => {
40
+ ({ title, sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, showActions = true, className, ...props }, ref) => {
39
41
  const activeSession = sessions?.find(s => s.id === activeSessionId);
40
42
  const downloadRef = React.useRef<HTMLAnchorElement>(null);
41
43
 
@@ -53,6 +55,10 @@ export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps
53
55
  URL.revokeObjectURL(url);
54
56
  }, [onDownloadSession]);
55
57
 
58
+ const displayTitle = title || (activeSessionId
59
+ ? (activeSession?.title || 'Untitled Session')
60
+ : 'New Session');
61
+
56
62
  return (
57
63
  <div
58
64
  ref={ref}
@@ -61,48 +67,48 @@ export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps
61
67
  >
62
68
  <div className="flex items-center space-x-2 overflow-hidden">
63
69
  <span className="text-sm font-medium truncate">
64
- {activeSessionId
65
- ? (activeSession?.title || 'Untitled Session')
66
- : 'New Session'}
70
+ {displayTitle}
67
71
  </span>
68
72
  </div>
69
- <div className="flex items-center space-x-1 flex-none">
70
- {onDownloadSession && (
71
- <Button variant="ghost" size="icon" onClick={handleDownloadClick} className="h-8 w-8" title="Download Chat History">
72
- <Icon name="download" className="h-4 w-4" />
73
- </Button>
74
- )}
75
- <Button variant="ghost" size="icon" onClick={onNewSession} className="h-8 w-8" title="New Session">
76
- <Icon name="plus" className="h-4 w-4" />
77
- </Button>
78
-
79
- <DropdownMenu>
80
- <DropdownMenuTrigger asChild>
81
- <Button variant="ghost" size="icon" className="h-8 w-8">
82
- <Icon name="clock" className="h-4 w-4" />
73
+ {showActions && (
74
+ <div className="flex items-center space-x-1 flex-none">
75
+ {onDownloadSession && (
76
+ <Button variant="ghost" size="icon" onClick={handleDownloadClick} className="h-8 w-8" title="Download Chat History">
77
+ <Icon name="download" className="h-4 w-4" />
83
78
  </Button>
84
- </DropdownMenuTrigger>
85
- <DropdownMenuContent align="end" className="w-64 max-h-80 overflow-y-auto">
86
- <DropdownMenuLabel>Chat History</DropdownMenuLabel>
87
- <DropdownMenuSeparator />
88
- {sessions && sessions.length > 0 ? (
89
- sessions.map((session) => (
90
- <DropdownMenuItem
91
- key={session.id}
92
- onClick={() => onSelectSession?.(session.id)}
93
- className="flex flex-col items-start py-2 cursor-pointer"
94
- >
95
- <span className="text-sm font-medium truncate w-full">{session.title || 'Untitled Session'}</span>
96
- <span className="text-xs text-muted-foreground">{new Date(session.created_at).toLocaleString()}</span>
97
- </DropdownMenuItem>
98
- ))
99
- ) : (
100
- <div className="p-4 text-sm text-center text-muted-foreground">No previous sessions</div>
101
- )}
102
- </DropdownMenuContent>
103
- </DropdownMenu>
104
- <a ref={downloadRef} style={{ display: 'none' }} />
105
- </div>
79
+ )}
80
+ <Button variant="ghost" size="icon" onClick={onNewSession} className="h-8 w-8" title="New Session">
81
+ <Icon name="plus" className="h-4 w-4" />
82
+ </Button>
83
+
84
+ <DropdownMenu>
85
+ <DropdownMenuTrigger asChild>
86
+ <Button variant="ghost" size="icon" className="h-8 w-8">
87
+ <Icon name="clock" className="h-4 w-4" />
88
+ </Button>
89
+ </DropdownMenuTrigger>
90
+ <DropdownMenuContent align="end" className="w-64 max-h-80 overflow-y-auto">
91
+ <DropdownMenuLabel>Chat History</DropdownMenuLabel>
92
+ <DropdownMenuSeparator />
93
+ {sessions && sessions.length > 0 ? (
94
+ sessions.map((session) => (
95
+ <DropdownMenuItem
96
+ key={session.id}
97
+ onClick={() => onSelectSession?.(session.id)}
98
+ className="flex flex-col items-start py-2 cursor-pointer"
99
+ >
100
+ <span className="text-sm font-medium truncate w-full">{session.title || 'Untitled Session'}</span>
101
+ <span className="text-xs text-muted-foreground">{new Date(session.created_at).toLocaleString()}</span>
102
+ </DropdownMenuItem>
103
+ ))
104
+ ) : (
105
+ <div className="p-4 text-sm text-center text-muted-foreground">No previous sessions</div>
106
+ )}
107
+ </DropdownMenuContent>
108
+ </DropdownMenu>
109
+ <a ref={downloadRef} style={{ display: 'none' }} />
110
+ </div>
111
+ )}
106
112
  </div>
107
113
  );
108
114
  }
@@ -49,6 +49,8 @@ function getStoryStatusStyle(status?: StoryItem["status"]) {
49
49
 
50
50
  export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
51
51
  ({ stories, activeStoryId, onStoryClick, defaultIcon = "clapperboard", className, ...props }, ref) => {
52
+ const hasActiveStory = Boolean(activeStoryId);
53
+
52
54
  return (
53
55
  <div
54
56
  ref={ref}
@@ -68,16 +70,19 @@ export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
68
70
  key={story.id}
69
71
  variant="ghost"
70
72
  onClick={() => onStoryClick(story)}
71
- 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"
73
+ className={cn(
74
+ "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 transition-all duration-200",
75
+ hasActiveStory && (isActive ? "opacity-100 scale-105" : "opacity-35 hover:opacity-60")
76
+ )}
72
77
  data-testid={`btn-story-${story.id}`}
73
78
  aria-label={`Open story: ${story.title}`}
74
79
  >
75
80
  <div className="relative w-9 h-9 shrink-0 aspect-square rounded-full transition-transform group-hover:scale-105">
76
81
  <div
77
82
  className={cn(
78
- "w-full h-full rounded-full flex items-center justify-center border transition-colors overflow-hidden select-none",
83
+ "w-full h-full rounded-full flex items-center justify-center border transition-all overflow-hidden select-none",
79
84
  isActive
80
- ? "border-primary bg-muted/60"
85
+ ? "border-primary bg-muted/60 ring-2 ring-primary/60 shadow-lg shadow-primary/40"
81
86
  : "border-border/50 bg-muted/40"
82
87
  )}
83
88
  >
@@ -411,4 +411,26 @@ export const WithImageAndAudioAttachments: Story = {
411
411
  },
412
412
  };
413
413
 
414
+ /**
415
+ * Sheet Presentation - Floating bottom-sheet modal overlay
416
+ */
417
+ export const SheetPresentation: Story = {
418
+ args: {
419
+ variant: "sheet",
420
+ messages: inputStateMessages,
421
+ sessionTitle: "Video Generation Session",
422
+ placeholder: "Ask a question or describe a task...",
423
+ onSubmit: (msg: PromptInputMessage) => console.log("Submitted:", msg),
424
+ onClose: () => alert("Close sheet triggered"),
425
+ },
426
+ parameters: {
427
+ docs: {
428
+ description: {
429
+ story:
430
+ "Floating bottom-sheet panel presentation with frosted backdrop blur overlay, elevated surface, and grab handle.",
431
+ },
432
+ },
433
+ },
434
+ };
435
+
414
436
 
@@ -204,6 +204,27 @@ export interface ChatPanelProps {
204
204
  * @default true
205
205
  */
206
206
  showHeader?: boolean;
207
+ /**
208
+ * Title override for the session header
209
+ */
210
+ sessionTitle?: string;
211
+ /**
212
+ * Whether to show action buttons in the session header
213
+ * @default true
214
+ */
215
+ showSessionActions?: boolean;
216
+ /**
217
+ * Presentation variant for ChatPanel:
218
+ * - "default": standard full flex-1 panel (default)
219
+ * - "sheet": elevated floating bottom-sheet panel with frosted backdrop blur overlay,
220
+ * rounded surface, shadow, grab handle, and dismiss backdrop click.
221
+ * @default "default"
222
+ */
223
+ variant?: "default" | "sheet";
224
+ /**
225
+ * Callback fired when clicking the backdrop overlay or dismissing the sheet (when variant="sheet")
226
+ */
227
+ onClose?: () => void;
207
228
  }
208
229
 
209
230
  /**
@@ -249,6 +270,10 @@ export const ChatPanel = React.memo<ChatPanelProps>(
249
270
  onTaskClick,
250
271
  enableSpeech = true,
251
272
  showHeader = true,
273
+ sessionTitle,
274
+ showSessionActions = true,
275
+ variant = "default",
276
+ onClose,
252
277
  }) => {
253
278
  // File change queue state
254
279
  const [fileChangeState, setFileChangeState] = React.useState<
@@ -400,11 +425,13 @@ export const ChatPanel = React.memo<ChatPanelProps>(
400
425
  );
401
426
  }, [loading, messages]);
402
427
 
403
- return (
404
- <div className={`relative flex h-full flex-col ${className || ""}`}>
428
+ const chatContent = (
429
+ <div className={cn("relative flex h-full flex-col", variant !== "sheet" && className)}>
405
430
  {/* Chat Session Header */}
406
431
  {showHeader && (
407
432
  <SessionHeader
433
+ title={sessionTitle}
434
+ showActions={showSessionActions}
408
435
  sessions={sessions}
409
436
  activeSessionId={activeSessionId}
410
437
  onNewSession={onNewSession}
@@ -457,6 +484,40 @@ export const ChatPanel = React.memo<ChatPanelProps>(
457
484
  </div>
458
485
  </div>
459
486
  );
487
+
488
+ if (variant === "sheet") {
489
+ return (
490
+ <div
491
+ className="absolute inset-0 z-50 flex flex-col justify-end backdrop-blur-md bg-black/60"
492
+ data-testid="story-chat-head-overlay"
493
+ >
494
+ <div
495
+ className="absolute inset-0 cursor-pointer"
496
+ onClick={onClose}
497
+ data-testid="story-chat-head-backdrop"
498
+ aria-label="Close sheet"
499
+ />
500
+ <div
501
+ className={cn(
502
+ "relative z-10 flex flex-col bg-card rounded-2xl border border-border shadow-2xl h-[85%] mx-4 mb-4 overflow-hidden",
503
+ className
504
+ )}
505
+ data-testid="story-chat-head-popup"
506
+ >
507
+ <div
508
+ className="w-9 h-1 rounded-full bg-muted-foreground/30 mx-auto mt-2.5 mb-1 shrink-0"
509
+ data-testid="story-sheet-handle"
510
+ aria-hidden="true"
511
+ />
512
+ <div className="flex-1 flex flex-col min-h-0">
513
+ {chatContent}
514
+ </div>
515
+ </div>
516
+ </div>
517
+ );
518
+ }
519
+
520
+ return chatContent;
460
521
  }
461
522
  );
462
523
 
package/dist/index.cjs CHANGED
@@ -7786,7 +7786,7 @@ var ApprovalCard = React3__namespace.memo(
7786
7786
  ApprovalCard.displayName = "ApprovalCard";
7787
7787
  var SessionHeader = React3__namespace.forwardRef(
7788
7788
  (_a, ref) => {
7789
- var _b = _a, { sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, className } = _b, props = __objRest(_b, ["sessions", "activeSessionId", "onNewSession", "onCloseSession", "onSelectSession", "onDownloadSession", "className"]);
7789
+ var _b = _a, { title, sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, showActions = true, className } = _b, props = __objRest(_b, ["title", "sessions", "activeSessionId", "onNewSession", "onCloseSession", "onSelectSession", "onDownloadSession", "showActions", "className"]);
7790
7790
  const activeSession = sessions == null ? void 0 : sessions.find((s) => s.id === activeSessionId);
7791
7791
  const downloadRef = React3__namespace.useRef(null);
7792
7792
  const handleDownloadClick = React3__namespace.useCallback(async () => {
@@ -7802,6 +7802,7 @@ var SessionHeader = React3__namespace.forwardRef(
7802
7802
  }
7803
7803
  URL.revokeObjectURL(url);
7804
7804
  }, [onDownloadSession]);
7805
+ const displayTitle = title || (activeSessionId ? (activeSession == null ? void 0 : activeSession.title) || "Untitled Session" : "New Session");
7805
7806
  return /* @__PURE__ */ jsxRuntime.jsxs(
7806
7807
  "div",
7807
7808
  __spreadProps(__spreadValues({
@@ -7809,8 +7810,8 @@ var SessionHeader = React3__namespace.forwardRef(
7809
7810
  className: `flex flex-none items-center justify-between px-4 py-2 border-b border-border bg-background ${className || ""}`
7810
7811
  }, props), {
7811
7812
  children: [
7812
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center space-x-2 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: activeSessionId ? (activeSession == null ? void 0 : activeSession.title) || "Untitled Session" : "New Session" }) }),
7813
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-1 flex-none", children: [
7813
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center space-x-2 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: displayTitle }) }),
7814
+ showActions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-1 flex-none", children: [
7814
7815
  onDownloadSession && /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "ghost", size: "icon", onClick: handleDownloadClick, className: "h-8 w-8", title: "Download Chat History", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "download", className: "h-4 w-4" }) }),
7815
7816
  /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "ghost", size: "icon", onClick: onNewSession, className: "h-8 w-8", title: "New Session", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "h-4 w-4" }) }),
7816
7817
  /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu2, { children: [
@@ -8039,7 +8040,11 @@ var ChatPanel = React3__namespace.memo(
8039
8040
  onTaskStopAll,
8040
8041
  onTaskClick,
8041
8042
  enableSpeech = true,
8042
- showHeader = true
8043
+ showHeader = true,
8044
+ sessionTitle,
8045
+ showSessionActions = true,
8046
+ variant = "default",
8047
+ onClose
8043
8048
  }) => {
8044
8049
  const [fileChangeState, setFileChangeState] = React3__namespace.useState("approval-requested");
8045
8050
  const [approval, setApproval] = React3__namespace.useState({});
@@ -8157,10 +8162,12 @@ var ChatPanel = React3__namespace.memo(
8157
8162
  }
8158
8163
  );
8159
8164
  }, [loading, messages]);
8160
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative flex h-full flex-col ${className || ""}`, children: [
8165
+ const chatContent = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative flex h-full flex-col", variant !== "sheet" && className), children: [
8161
8166
  showHeader && /* @__PURE__ */ jsxRuntime.jsx(
8162
8167
  SessionHeader,
8163
8168
  {
8169
+ title: sessionTitle,
8170
+ showActions: showSessionActions,
8164
8171
  sessions,
8165
8172
  activeSessionId,
8166
8173
  onNewSession,
@@ -8216,6 +8223,48 @@ var ChatPanel = React3__namespace.memo(
8216
8223
  )
8217
8224
  ] })
8218
8225
  ] });
8226
+ if (variant === "sheet") {
8227
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8228
+ "div",
8229
+ {
8230
+ className: "absolute inset-0 z-50 flex flex-col justify-end backdrop-blur-md bg-black/60",
8231
+ "data-testid": "story-chat-head-overlay",
8232
+ children: [
8233
+ /* @__PURE__ */ jsxRuntime.jsx(
8234
+ "div",
8235
+ {
8236
+ className: "absolute inset-0 cursor-pointer",
8237
+ onClick: onClose,
8238
+ "data-testid": "story-chat-head-backdrop",
8239
+ "aria-label": "Close sheet"
8240
+ }
8241
+ ),
8242
+ /* @__PURE__ */ jsxRuntime.jsxs(
8243
+ "div",
8244
+ {
8245
+ className: cn(
8246
+ "relative z-10 flex flex-col bg-card rounded-2xl border border-border shadow-2xl h-[85%] mx-4 mb-4 overflow-hidden",
8247
+ className
8248
+ ),
8249
+ "data-testid": "story-chat-head-popup",
8250
+ children: [
8251
+ /* @__PURE__ */ jsxRuntime.jsx(
8252
+ "div",
8253
+ {
8254
+ className: "w-9 h-1 rounded-full bg-muted-foreground/30 mx-auto mt-2.5 mb-1 shrink-0",
8255
+ "data-testid": "story-sheet-handle",
8256
+ "aria-hidden": "true"
8257
+ }
8258
+ ),
8259
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex flex-col min-h-0", children: chatContent })
8260
+ ]
8261
+ }
8262
+ )
8263
+ ]
8264
+ }
8265
+ );
8266
+ }
8267
+ return chatContent;
8219
8268
  }
8220
8269
  );
8221
8270
  ChatPanel.displayName = "ChatPanel";
@@ -14416,6 +14465,7 @@ function getStoryStatusStyle(status) {
14416
14465
  var StoriesBar = React3__namespace.forwardRef(
14417
14466
  (_a, ref) => {
14418
14467
  var _b = _a, { stories, activeStoryId, onStoryClick, defaultIcon = "clapperboard", className } = _b, props = __objRest(_b, ["stories", "activeStoryId", "onStoryClick", "defaultIcon", "className"]);
14468
+ const hasActiveStory = Boolean(activeStoryId);
14419
14469
  return /* @__PURE__ */ jsxRuntime.jsx(
14420
14470
  "div",
14421
14471
  __spreadProps(__spreadValues({
@@ -14433,7 +14483,10 @@ var StoriesBar = React3__namespace.forwardRef(
14433
14483
  {
14434
14484
  variant: "ghost",
14435
14485
  onClick: () => onStoryClick(story),
14436
- 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",
14486
+ className: cn(
14487
+ "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 transition-all duration-200",
14488
+ hasActiveStory && (isActive ? "opacity-100 scale-105" : "opacity-35 hover:opacity-60")
14489
+ ),
14437
14490
  "data-testid": `btn-story-${story.id}`,
14438
14491
  "aria-label": `Open story: ${story.title}`,
14439
14492
  children: [
@@ -14442,8 +14495,8 @@ var StoriesBar = React3__namespace.forwardRef(
14442
14495
  "div",
14443
14496
  {
14444
14497
  className: cn(
14445
- "w-full h-full rounded-full flex items-center justify-center border transition-colors overflow-hidden select-none",
14446
- isActive ? "border-primary bg-muted/60" : "border-border/50 bg-muted/40"
14498
+ "w-full h-full rounded-full flex items-center justify-center border transition-all overflow-hidden select-none",
14499
+ isActive ? "border-primary bg-muted/60 ring-2 ring-primary/60 shadow-lg shadow-primary/40" : "border-border/50 bg-muted/40"
14447
14500
  ),
14448
14501
  children: story.thumbnailUrl ? /* @__PURE__ */ jsxRuntime.jsx(
14449
14502
  "img",