ai-design-system 0.1.116 → 0.1.117

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.
@@ -17,10 +17,24 @@ export interface ChatSessionInfo {
17
17
  }
18
18
 
19
19
  import type { FileDownloadResult } from '@/components/composites/FileTreeExplorer';
20
+ import { formatDayLabel } from '@/lib/date';
20
21
 
21
22
 
22
23
  export interface SessionHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
23
24
  title?: string;
25
+ /**
26
+ * What a session is called.
27
+ * - "title": the session's own title (default).
28
+ * - "date": the day it was created, as "21 Aug 2025", in the reader's time
29
+ * zone. For a surface that keeps one session per day, where the day is
30
+ * what tells sessions apart and a first message is not.
31
+ * @default "title"
32
+ */
33
+ titleFormat?: 'title' | 'date';
34
+ /**
35
+ * IANA time zone for "date" titles. Defaults to the reader's own.
36
+ */
37
+ timeZone?: string;
24
38
  sessions?: ChatSessionInfo[];
25
39
  activeSessionId?: string | null;
26
40
  onNewSession?: () => void;
@@ -48,7 +62,7 @@ export interface SessionHeaderProps extends React.HTMLAttributes<HTMLDivElement>
48
62
  * and a dropdown history of past sessions.
49
63
  */
50
64
  export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps>(
51
- ({ title, sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, showActions = true, showNewSession = true, showDownloadSession = true, onOverview, showOverview = true, className, ...props }, ref) => {
65
+ ({ title, titleFormat = 'title', timeZone, sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, showActions = true, showNewSession = true, showDownloadSession = true, onOverview, showOverview = true, className, ...props }, ref) => {
52
66
  const activeSession = sessions?.find(s => s.id === activeSessionId);
53
67
  const downloadRef = React.useRef<HTMLAnchorElement>(null);
54
68
 
@@ -66,9 +80,16 @@ export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps
66
80
  URL.revokeObjectURL(url);
67
81
  }, [onDownloadSession]);
68
82
 
69
- const displayTitle = title || (activeSessionId
70
- ? (activeSession?.title || 'Untitled Session')
71
- : 'New Session');
83
+ const byDate = titleFormat === 'date';
84
+ const sessionLabel = (session: ChatSessionInfo | undefined): string => {
85
+ if (!byDate) return session?.title || 'Untitled Session';
86
+ // A session not yet in the list is the one being started now: today's.
87
+ return formatDayLabel(session?.created_at ?? new Date(), { timeZone }) || 'Untitled Session';
88
+ };
89
+
90
+ const displayTitle = title || (activeSessionId
91
+ ? sessionLabel(activeSession)
92
+ : byDate ? formatDayLabel(new Date(), { timeZone }) : 'New Session');
72
93
 
73
94
  return (
74
95
  <div
@@ -121,8 +142,11 @@ export const SessionHeader = React.forwardRef<HTMLDivElement, SessionHeaderProps
121
142
  onClick={() => onSelectSession?.(session.id)}
122
143
  className="flex flex-col items-start py-2 cursor-pointer"
123
144
  >
124
- <span className="text-sm font-medium truncate w-full">{session.title || 'Untitled Session'}</span>
125
- <span className="text-xs text-muted-foreground">{new Date(session.created_at).toLocaleString()}</span>
145
+ <span className="text-sm font-medium truncate w-full">{sessionLabel(session)}</span>
146
+ {/* The date is the title in "date" mode; repeating it underneath says nothing. */}
147
+ {!byDate && (
148
+ <span className="text-xs text-muted-foreground">{new Date(session.created_at).toLocaleString()}</span>
149
+ )}
126
150
  </DropdownMenuItem>
127
151
  ))
128
152
  ) : (
@@ -220,6 +220,17 @@ export interface ChatPanelProps {
220
220
  * Title override for the session header
221
221
  */
222
222
  sessionTitle?: string;
223
+ /**
224
+ * What sessions are called in the header and the history list: their own
225
+ * title ("title", default) or the day they were created ("date", shown as
226
+ * "21 Aug 2025" in the reader's time zone).
227
+ * @default "title"
228
+ */
229
+ sessionTitleFormat?: 'title' | 'date';
230
+ /**
231
+ * IANA time zone for "date" session titles. Defaults to the reader's own.
232
+ */
233
+ sessionTimeZone?: string;
223
234
  /**
224
235
  * Whether to show action buttons in the session header
225
236
  * @default true
@@ -304,6 +315,8 @@ export const ChatPanel = React.memo<ChatPanelProps>(
304
315
  enableSpeech = true,
305
316
  showHeader = true,
306
317
  sessionTitle,
318
+ sessionTitleFormat = 'title',
319
+ sessionTimeZone,
307
320
  showSessionActions = true,
308
321
  showNewSession = true,
309
322
  showDownloadSession = true,
@@ -468,6 +481,8 @@ export const ChatPanel = React.memo<ChatPanelProps>(
468
481
  {showHeader && (
469
482
  <SessionHeader
470
483
  title={sessionTitle}
484
+ titleFormat={sessionTitleFormat}
485
+ timeZone={sessionTimeZone}
471
486
  showActions={showSessionActions}
472
487
  showNewSession={showNewSession}
473
488
  showDownloadSession={showDownloadSession}
@@ -94,6 +94,9 @@ export type { ExternalToast, ToastT, ToasterProps } from 'sonner';
94
94
 
95
95
  // Utilities
96
96
  export { cn } from '@/lib/utils';
97
+ // Calendar days in the reader's time zone
98
+ export { formatDayLabel, dayKey, isSameDay, userTimeZone } from '@/lib/date';
99
+ export type { DayOptions } from '@/lib/date';
97
100
  export { ButtonSwitcher } from './composites';
98
101
  export type { ButtonSwitcherProps, ButtonSwitcherItem } from './composites';
99
102
 
package/dist/index.cjs CHANGED
@@ -8136,9 +8136,43 @@ var ApprovalCard = React3__namespace.memo(
8136
8136
  }
8137
8137
  );
8138
8138
  ApprovalCard.displayName = "ApprovalCard";
8139
+
8140
+ // lib/date.ts
8141
+ function userTimeZone() {
8142
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
8143
+ }
8144
+ function dayParts(value, options) {
8145
+ const date = value instanceof Date ? value : new Date(value);
8146
+ if (Number.isNaN(date.getTime())) return null;
8147
+ const zone = (options == null ? void 0 : options.timeZone) ? { timeZone: options.timeZone } : {};
8148
+ const named = new Intl.DateTimeFormat("en-US", __spreadValues({ day: "numeric", month: "short", year: "numeric" }, zone)).formatToParts(date);
8149
+ const numeric = new Intl.DateTimeFormat("en-US", __spreadValues({ month: "2-digit" }, zone)).formatToParts(date);
8150
+ const part = (parts, type) => {
8151
+ var _a, _b;
8152
+ return (_b = (_a = parts.find((p) => p.type === type)) == null ? void 0 : _a.value) != null ? _b : "";
8153
+ };
8154
+ return {
8155
+ day: part(named, "day"),
8156
+ month: part(named, "month"),
8157
+ monthNumber: part(numeric, "month"),
8158
+ year: part(named, "year")
8159
+ };
8160
+ }
8161
+ function formatDayLabel(value, options) {
8162
+ const parts = dayParts(value, options);
8163
+ return parts ? `${parts.day} ${parts.month} ${parts.year}` : "";
8164
+ }
8165
+ function dayKey(value, options) {
8166
+ const parts = dayParts(value, options);
8167
+ return parts ? `${parts.year}-${parts.monthNumber}-${parts.day.padStart(2, "0")}` : "";
8168
+ }
8169
+ function isSameDay(a, b, options) {
8170
+ const key = dayKey(a, options);
8171
+ return key !== "" && key === dayKey(b, options);
8172
+ }
8139
8173
  var SessionHeader = React3__namespace.forwardRef(
8140
8174
  (_a, ref) => {
8141
- var _b = _a, { title, sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, showActions = true, showNewSession = true, showDownloadSession = true, onOverview, showOverview = true, className } = _b, props = __objRest(_b, ["title", "sessions", "activeSessionId", "onNewSession", "onCloseSession", "onSelectSession", "onDownloadSession", "showActions", "showNewSession", "showDownloadSession", "onOverview", "showOverview", "className"]);
8175
+ var _b = _a, { title, titleFormat = "title", timeZone, sessions, activeSessionId, onNewSession, onCloseSession, onSelectSession, onDownloadSession, showActions = true, showNewSession = true, showDownloadSession = true, onOverview, showOverview = true, className } = _b, props = __objRest(_b, ["title", "titleFormat", "timeZone", "sessions", "activeSessionId", "onNewSession", "onCloseSession", "onSelectSession", "onDownloadSession", "showActions", "showNewSession", "showDownloadSession", "onOverview", "showOverview", "className"]);
8142
8176
  const activeSession = sessions == null ? void 0 : sessions.find((s) => s.id === activeSessionId);
8143
8177
  const downloadRef = React3__namespace.useRef(null);
8144
8178
  const handleDownloadClick = React3__namespace.useCallback(async () => {
@@ -8154,7 +8188,13 @@ var SessionHeader = React3__namespace.forwardRef(
8154
8188
  }
8155
8189
  URL.revokeObjectURL(url);
8156
8190
  }, [onDownloadSession]);
8157
- const displayTitle = title || (activeSessionId ? (activeSession == null ? void 0 : activeSession.title) || "Untitled Session" : "New Session");
8191
+ const byDate = titleFormat === "date";
8192
+ const sessionLabel = (session) => {
8193
+ var _a2;
8194
+ if (!byDate) return (session == null ? void 0 : session.title) || "Untitled Session";
8195
+ return formatDayLabel((_a2 = session == null ? void 0 : session.created_at) != null ? _a2 : /* @__PURE__ */ new Date(), { timeZone }) || "Untitled Session";
8196
+ };
8197
+ const displayTitle = title || (activeSessionId ? sessionLabel(activeSession) : byDate ? formatDayLabel(/* @__PURE__ */ new Date(), { timeZone }) : "New Session");
8158
8198
  return /* @__PURE__ */ jsxRuntime.jsxs(
8159
8199
  "div",
8160
8200
  __spreadProps(__spreadValues({
@@ -8188,8 +8228,8 @@ var SessionHeader = React3__namespace.forwardRef(
8188
8228
  onClick: () => onSelectSession == null ? void 0 : onSelectSession(session.id),
8189
8229
  className: "flex flex-col items-start py-2 cursor-pointer",
8190
8230
  children: [
8191
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate w-full", children: session.title || "Untitled Session" }),
8192
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: new Date(session.created_at).toLocaleString() })
8231
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate w-full", children: sessionLabel(session) }),
8232
+ !byDate && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: new Date(session.created_at).toLocaleString() })
8193
8233
  ]
8194
8234
  },
8195
8235
  session.id
@@ -8407,6 +8447,8 @@ var ChatPanel = React3__namespace.memo(
8407
8447
  enableSpeech = true,
8408
8448
  showHeader = true,
8409
8449
  sessionTitle,
8450
+ sessionTitleFormat = "title",
8451
+ sessionTimeZone,
8410
8452
  showSessionActions = true,
8411
8453
  showNewSession = true,
8412
8454
  showDownloadSession = true,
@@ -8536,6 +8578,8 @@ var ChatPanel = React3__namespace.memo(
8536
8578
  SessionHeader,
8537
8579
  {
8538
8580
  title: sessionTitle,
8581
+ titleFormat: sessionTitleFormat,
8582
+ timeZone: sessionTimeZone,
8539
8583
  showActions: showSessionActions,
8540
8584
  showNewSession,
8541
8585
  showDownloadSession,
@@ -18607,10 +18651,14 @@ exports.WorkflowCanvas = WorkflowCanvas;
18607
18651
  exports.WorkflowObservabilityFeature = WorkflowObservabilityFeature;
18608
18652
  exports.bmcToCanvas = bmcToCanvas;
18609
18653
  exports.cn = cn;
18654
+ exports.dayKey = dayKey;
18655
+ exports.formatDayLabel = formatDayLabel;
18610
18656
  exports.getLayoutedElements = getLayoutedElements;
18611
18657
  exports.getPreset = getPreset;
18658
+ exports.isSameDay = isSameDay;
18612
18659
  exports.useOptionalPromptInputController = useOptionalPromptInputController;
18613
18660
  exports.usePromptInputAttachments = usePromptInputAttachments;
18614
18661
  exports.usePromptInputController = usePromptInputController;
18662
+ exports.userTimeZone = userTimeZone;
18615
18663
  //# sourceMappingURL=index.cjs.map
18616
18664
  //# sourceMappingURL=index.cjs.map