ai-design-system 0.1.96 → 0.1.98

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
  }
@@ -31,20 +31,17 @@ function getStoryStatusStyle(status?: StoryItem["status"]) {
31
31
  case "generating":
32
32
  return {
33
33
  iconColor: "text-amber-500",
34
- ringClass: "ring-2 ring-amber-500 animate-pulse",
35
34
  badgeBg: "bg-amber-500",
36
35
  };
37
36
  case "ready":
38
37
  return {
39
38
  iconColor: "text-emerald-500",
40
- ringClass: "ring-1 ring-emerald-500/70 group-hover:ring-emerald-500",
41
39
  badgeBg: "bg-emerald-500",
42
40
  };
43
41
  case "draft":
44
42
  default:
45
43
  return {
46
44
  iconColor: "text-muted-foreground",
47
- ringClass: "ring-1 ring-border group-hover:ring-primary/60",
48
45
  badgeBg: "bg-muted-foreground",
49
46
  };
50
47
  }
@@ -55,11 +52,11 @@ export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
55
52
  return (
56
53
  <div
57
54
  ref={ref}
58
- className={cn("w-full border-b shrink-0", className)}
55
+ className={cn("w-full border-b border-border bg-background shrink-0", className)}
59
56
  data-testid="stories-bar"
60
57
  {...props}
61
58
  >
62
- <div className="flex items-center gap-3 overflow-x-auto no-scrollbar scroll-smooth">
59
+ <div className="flex items-center gap-2.5 px-3 py-1.5 overflow-x-auto no-scrollbar scroll-smooth">
63
60
  {stories.map((story) => {
64
61
  const isActive = story.id === activeStoryId;
65
62
  const isGenerating = story.status === "generating";
@@ -75,15 +72,15 @@ export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
75
72
  data-testid={`btn-story-${story.id}`}
76
73
  aria-label={`Open story: ${story.title}`}
77
74
  >
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">
75
+ <div className="relative w-9 h-9 shrink-0 aspect-square rounded-full transition-transform group-hover:scale-105">
76
+ <div
77
+ className={cn(
78
+ "w-full h-full rounded-full flex items-center justify-center border transition-colors overflow-hidden select-none",
79
+ isActive
80
+ ? "border-primary bg-muted/60"
81
+ : "border-border/50 bg-muted/40"
82
+ )}
83
+ >
87
84
  {story.thumbnailUrl ? (
88
85
  <img
89
86
  src={story.thumbnailUrl}
@@ -93,20 +90,20 @@ export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
93
90
  ) : (
94
91
  <Icon
95
92
  name={iconName}
96
- className={cn("w-5 h-5 transition-colors", statusStyle.iconColor)}
93
+ className={cn("w-4 h-4 transition-colors", statusStyle.iconColor)}
97
94
  />
98
95
  )}
99
96
  </div>
100
97
 
101
98
  {isGenerating ? (
102
- <span className="absolute bottom-0 right-0 flex h-2.5 w-2.5">
99
+ <span className="absolute bottom-0 right-0 flex h-2 w-2">
103
100
  <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" />
101
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-amber-500" />
105
102
  </span>
106
103
  ) : (
107
104
  <span
108
105
  className={cn(
109
- "absolute bottom-0 right-0 inline-flex rounded-full h-2.5 w-2.5 ring-2 ring-background",
106
+ "absolute bottom-0 right-0 inline-flex rounded-full h-2 w-2 ring-1.5 ring-background",
110
107
  statusStyle.badgeBg
111
108
  )}
112
109
  />
@@ -115,7 +112,7 @@ export const StoriesBar = React.forwardRef<HTMLDivElement, StoriesBarProps>(
115
112
 
116
113
  <span
117
114
  className={cn(
118
- "text-xs truncate max-w-[58px] text-center",
115
+ "text-[10px] leading-tight truncate max-w-[56px] text-center",
119
116
  isActive
120
117
  ? "font-semibold text-foreground"
121
118
  : "text-muted-foreground group-hover:text-foreground"
@@ -204,6 +204,15 @@ 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;
207
216
  }
208
217
 
209
218
  /**
@@ -249,6 +258,8 @@ export const ChatPanel = React.memo<ChatPanelProps>(
249
258
  onTaskClick,
250
259
  enableSpeech = true,
251
260
  showHeader = true,
261
+ sessionTitle,
262
+ showSessionActions = true,
252
263
  }) => {
253
264
  // File change queue state
254
265
  const [fileChangeState, setFileChangeState] = React.useState<
@@ -405,6 +416,8 @@ export const ChatPanel = React.memo<ChatPanelProps>(
405
416
  {/* Chat Session Header */}
406
417
  {showHeader && (
407
418
  <SessionHeader
419
+ title={sessionTitle}
420
+ showActions={showSessionActions}
408
421
  sessions={sessions}
409
422
  activeSessionId={activeSessionId}
410
423
  onNewSession={onNewSession}
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,9 @@ 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
8043
8046
  }) => {
8044
8047
  const [fileChangeState, setFileChangeState] = React3__namespace.useState("approval-requested");
8045
8048
  const [approval, setApproval] = React3__namespace.useState({});
@@ -8161,6 +8164,8 @@ var ChatPanel = React3__namespace.memo(
8161
8164
  showHeader && /* @__PURE__ */ jsxRuntime.jsx(
8162
8165
  SessionHeader,
8163
8166
  {
8167
+ title: sessionTitle,
8168
+ showActions: showSessionActions,
8164
8169
  sessions,
8165
8170
  activeSessionId,
8166
8171
  onNewSession,
@@ -14398,20 +14403,17 @@ function getStoryStatusStyle(status) {
14398
14403
  case "generating":
14399
14404
  return {
14400
14405
  iconColor: "text-amber-500",
14401
- ringClass: "ring-2 ring-amber-500 animate-pulse",
14402
14406
  badgeBg: "bg-amber-500"
14403
14407
  };
14404
14408
  case "ready":
14405
14409
  return {
14406
14410
  iconColor: "text-emerald-500",
14407
- ringClass: "ring-1 ring-emerald-500/70 group-hover:ring-emerald-500",
14408
14411
  badgeBg: "bg-emerald-500"
14409
14412
  };
14410
14413
  case "draft":
14411
14414
  default:
14412
14415
  return {
14413
14416
  iconColor: "text-muted-foreground",
14414
- ringClass: "ring-1 ring-border group-hover:ring-primary/60",
14415
14417
  badgeBg: "bg-muted-foreground"
14416
14418
  };
14417
14419
  }
@@ -14423,10 +14425,10 @@ var StoriesBar = React3__namespace.forwardRef(
14423
14425
  "div",
14424
14426
  __spreadProps(__spreadValues({
14425
14427
  ref,
14426
- className: cn("w-full border-b shrink-0", className),
14428
+ className: cn("w-full border-b border-border bg-background shrink-0", className),
14427
14429
  "data-testid": "stories-bar"
14428
14430
  }, props), {
14429
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3 overflow-x-auto no-scrollbar scroll-smooth", children: stories.map((story) => {
14431
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2.5 px-3 py-1.5 overflow-x-auto no-scrollbar scroll-smooth", children: stories.map((story) => {
14430
14432
  const isActive = story.id === activeStoryId;
14431
14433
  const isGenerating = story.status === "generating";
14432
14434
  const statusStyle = getStoryStatusStyle(story.status);
@@ -14440,15 +14442,15 @@ var StoriesBar = React3__namespace.forwardRef(
14440
14442
  "data-testid": `btn-story-${story.id}`,
14441
14443
  "aria-label": `Open story: ${story.title}`,
14442
14444
  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(
14445
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-9 h-9 shrink-0 aspect-square rounded-full transition-transform group-hover:scale-105", children: [
14446
+ /* @__PURE__ */ jsxRuntime.jsx(
14447
+ "div",
14448
+ {
14449
+ className: cn(
14450
+ "w-full h-full rounded-full flex items-center justify-center border transition-colors overflow-hidden select-none",
14451
+ isActive ? "border-primary bg-muted/60" : "border-border/50 bg-muted/40"
14452
+ ),
14453
+ children: story.thumbnailUrl ? /* @__PURE__ */ jsxRuntime.jsx(
14452
14454
  "img",
14453
14455
  {
14454
14456
  src: story.thumbnailUrl,
@@ -14459,29 +14461,29 @@ var StoriesBar = React3__namespace.forwardRef(
14459
14461
  Icon,
14460
14462
  {
14461
14463
  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
- )
14464
+ className: cn("w-4 h-4 transition-colors", statusStyle.iconColor)
14475
14465
  }
14476
14466
  )
14477
- ]
14478
- }
14479
- ),
14467
+ }
14468
+ ),
14469
+ isGenerating ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "absolute bottom-0 right-0 flex h-2 w-2", children: [
14470
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-500 opacity-75" }),
14471
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-amber-500" })
14472
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
14473
+ "span",
14474
+ {
14475
+ className: cn(
14476
+ "absolute bottom-0 right-0 inline-flex rounded-full h-2 w-2 ring-1.5 ring-background",
14477
+ statusStyle.badgeBg
14478
+ )
14479
+ }
14480
+ )
14481
+ ] }),
14480
14482
  /* @__PURE__ */ jsxRuntime.jsx(
14481
14483
  "span",
14482
14484
  {
14483
14485
  className: cn(
14484
- "text-xs truncate max-w-[58px] text-center",
14486
+ "text-[10px] leading-tight truncate max-w-[56px] text-center",
14485
14487
  isActive ? "font-semibold text-foreground" : "text-muted-foreground group-hover:text-foreground"
14486
14488
  ),
14487
14489
  title: story.title,