one-design-next 0.0.61 → 0.0.62

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.
@@ -220,6 +220,8 @@ export interface Conversation {
220
220
  /** 对话类型。`'human'` 为人工客服会话;默认视为 AI。 */
221
221
  type?: 'ai' | 'human';
222
222
  status?: 'generating' | 'waiting' | 'unread' | 'error';
223
+ /** 是否置顶。排序由列表容器负责。 */
224
+ pinned?: boolean;
223
225
  }
224
226
  export interface MessageSnapshot {
225
227
  content: string;
@@ -9,8 +9,23 @@ export interface ChatItemProps {
9
9
  /** 对话类型。`'human'` 时标题后展示客服 icon;默认 `'ai'`。 */
10
10
  type?: ConversationType;
11
11
  status?: StatusIndicator;
12
+ /**
13
+ * 是否置顶。仅影响菜单文案(置顶 / 取消置顶)与 `data-odn-chat-item-pinned`。
14
+ * 不渲染行内置顶图标;分区/排序由 ChatList 负责。
15
+ */
16
+ pinned?: boolean;
17
+ /**
18
+ * 对话时间。展示为相对时间(刚刚 / N分钟前 / …),在行右侧;
19
+ * 与「…」同槽:hover / 选中常显 / 菜单打开时瞬时让位(无过渡)。
20
+ */
21
+ time?: number | string | Date;
12
22
  onClick?: (id: string) => void;
13
23
  onRename?: (id: string, newTitle: string) => void;
24
+ /**
25
+ * 置顶回调。传入时菜单展示「置顶 / 取消置顶」。
26
+ * 第二个参数为切换后的目标状态。
27
+ */
28
+ onPin?: (id: string, pinned: boolean) => void;
14
29
  onDelete?: (id: string) => void;
15
30
  actionMenuZIndex?: number | string;
16
31
  renaming?: boolean;
@@ -23,8 +38,9 @@ export interface ChatItemProps {
23
38
  defaultDeleteOpen?: boolean;
24
39
  onDeleteOpenChange?: (open: boolean) => void;
25
40
  }
26
- export declare function ChatItem({ id, title, active, type, status, onClick, onRename, onDelete, actionMenuZIndex, renaming: renamingProp, defaultRenaming, onRenamingChange, menuOpen: menuOpenProp, defaultMenuOpen, onMenuOpenChange, deleteOpen: deleteOpenProp, defaultDeleteOpen, onDeleteOpenChange, }: ChatItemProps): import("react").JSX.Element;
41
+ export declare function ChatItem({ id, title, active, type, status, pinned, time, onClick, onRename, onPin, onDelete, actionMenuZIndex, renaming: renamingProp, defaultRenaming, onRenamingChange, menuOpen: menuOpenProp, defaultMenuOpen, onMenuOpenChange, deleteOpen: deleteOpenProp, defaultDeleteOpen, onDeleteOpenChange, }: ChatItemProps): import("react").JSX.Element;
27
42
  export declare namespace ChatItem {
28
43
  var displayName: string;
29
44
  }
30
45
  export default ChatItem;
46
+ export { formatRelativeTime } from './relative-time';
@@ -1,10 +1,11 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
2
  import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
3
- import { useState, useRef, useEffect, useLayoutEffect } from 'react';
3
+ import { useState, useRef, useEffect, useLayoutEffect, useMemo } from 'react';
4
4
  import HoverFill from "../hover-fill";
5
5
  import Icon from "../icon";
6
6
  import Dialog from "../dialog";
7
7
  import Dropdown from "../dropdown";
8
+ import { formatRelativeTime } from "./relative-time";
8
9
  import "./style";
9
10
  var STATUS_ARIA_LABEL = {
10
11
  generating: '生成中',
@@ -15,11 +16,12 @@ var STATUS_ARIA_LABEL = {
15
16
  var ELLIPSIS = '…';
16
17
 
17
18
  /**
18
- * 有 trailing(客服 icon / status)时,用 DOM 实测把标题裁到可用宽度。
19
- * 解决 CSS text-overflow 按字符边界截断后,省略号右侧留白把 status 顶开的问题。
20
- * 量准时:截断标题宽 ≈ label − trailing − gap,整组仍铺满行,trailing 贴右缘。
19
+ * 有 trailing(客服 icon / status / 时间)时,用 DOM 实测把标题裁到可用宽度。
20
+ * 解决 CSS text-overflow 按字符边界截断后,省略号右侧留白把 trailing 顶开的问题。
21
21
  */
22
- function useFittedTitle(title, enabled, labelRef, titleRef, trailingRef) {
22
+ function useFittedTitle(title, enabled, labelRef, titleRef, /** 右对齐区(状态 / 时间),量宽预留 */
23
+ trailingRef, /** 紧跟标题的类型 icon,量宽预留 */
24
+ typeIconRef) {
23
25
  var _useState = useState(title),
24
26
  _useState2 = _slicedToArray(_useState, 2),
25
27
  fitted = _useState2[0],
@@ -52,15 +54,19 @@ function useFittedTitle(title, enabled, labelRef, titleRef, trailingRef) {
52
54
  return probe.getBoundingClientRect().width;
53
55
  };
54
56
  var fit = function fit() {
55
- var _trailing$getBounding;
57
+ var _trailing$getBounding, _typeIcon$getBounding;
56
58
  if (cancelled) return;
57
59
  syncProbeFont();
58
60
  if (!probe.isConnected) document.body.appendChild(probe);
61
+ var cs = getComputedStyle(label);
62
+ var gap = Number.parseFloat(cs.columnGap || cs.gap) || 0;
59
63
  var trailing = trailingRef.current;
60
- var gap = trailing ? Number.parseFloat(getComputedStyle(label).columnGap || getComputedStyle(label).gap) || 0 : 0;
64
+ var typeIcon = typeIconRef.current;
61
65
  var trailingWidth = (_trailing$getBounding = trailing === null || trailing === void 0 ? void 0 : trailing.getBoundingClientRect().width) !== null && _trailing$getBounding !== void 0 ? _trailing$getBounding : 0;
62
- // 用浮点宽度,避免 offsetWidth 取整导致少算 1px、过早截断
63
- var maxWidth = Math.max(0, label.getBoundingClientRect().width - trailingWidth - gap);
66
+ var typeWidth = (_typeIcon$getBounding = typeIcon === null || typeIcon === void 0 ? void 0 : typeIcon.getBoundingClientRect().width) !== null && _typeIcon$getBounding !== void 0 ? _typeIcon$getBounding : 0;
67
+ // 标题两侧可能各有一段 gap:title↔type、type/title↔trailing
68
+ var gapCount = (typeWidth > 0 ? 1 : 0) + (trailingWidth > 0 ? 1 : 0);
69
+ var maxWidth = Math.max(0, label.getBoundingClientRect().width - trailingWidth - typeWidth - gap * gapCount);
64
70
  if (measure(title) <= maxWidth + 0.5) {
65
71
  setFitted(title);
66
72
  return;
@@ -88,7 +94,8 @@ function useFittedTitle(title, enabled, labelRef, titleRef, trailingRef) {
88
94
  ro.observe(label);
89
95
  var trailing = trailingRef.current;
90
96
  if (trailing) ro.observe(trailing);
91
- // hover btn padding-right 变化会改变 label 宽度
97
+ var typeIcon = typeIconRef.current;
98
+ if (typeIcon) ro.observe(typeIcon);
92
99
  var btn = label.closest('[data-odn-chat-item-btn]');
93
100
  if (btn) ro.observe(btn);
94
101
  return function () {
@@ -96,7 +103,7 @@ function useFittedTitle(title, enabled, labelRef, titleRef, trailingRef) {
96
103
  ro.disconnect();
97
104
  probe.remove();
98
105
  };
99
- }, [title, enabled, labelRef, titleRef, trailingRef]);
106
+ }, [title, enabled, labelRef, titleRef, trailingRef, typeIconRef]);
100
107
  return enabled ? fitted : title;
101
108
  }
102
109
  function ChatItemStatus(_ref) {
@@ -161,8 +168,12 @@ export function ChatItem(_ref2) {
161
168
  _ref2$type = _ref2.type,
162
169
  type = _ref2$type === void 0 ? 'ai' : _ref2$type,
163
170
  status = _ref2.status,
171
+ _ref2$pinned = _ref2.pinned,
172
+ pinned = _ref2$pinned === void 0 ? false : _ref2$pinned,
173
+ time = _ref2.time,
164
174
  _onClick = _ref2.onClick,
165
175
  onRename = _ref2.onRename,
176
+ onPin = _ref2.onPin,
166
177
  onDelete = _ref2.onDelete,
167
178
  actionMenuZIndex = _ref2.actionMenuZIndex,
168
179
  renamingProp = _ref2.renaming,
@@ -193,15 +204,27 @@ export function ChatItem(_ref2) {
193
204
  _useControllableState6 = _slicedToArray(_useControllableState5, 2),
194
205
  menuOpen = _useControllableState6[0],
195
206
  setMenuOpen = _useControllableState6[1];
207
+ var _useState7 = useState(function () {
208
+ return Date.now();
209
+ }),
210
+ _useState8 = _slicedToArray(_useState7, 2),
211
+ now = _useState8[0],
212
+ setNow = _useState8[1];
196
213
  var inputRef = useRef(null);
197
214
  var labelRef = useRef(null);
198
215
  var titleRef = useRef(null);
216
+ var typeIconRef = useRef(null);
199
217
  var trailingRef = useRef(null);
200
- // active 时隐藏 waiting/unread/error;generating 仍展示(当前会话仍在生成)
201
- var showStatus = Boolean(status && (!active || status === 'generating'));
218
+ // active 时隐藏 unread/error;generating / waiting 仍展示(当前会话仍在进行)
219
+ var showStatus = Boolean(status && (!active || status === 'generating' || status === 'waiting'));
202
220
  var showTypeIcon = type === 'human';
203
- var fitTitle = showTypeIcon || showStatus;
204
- var displayTitle = useFittedTitle(title, fitTitle, labelRef, titleRef, trailingRef);
221
+ var timeLabel = useMemo(function () {
222
+ return time == null ? '' : formatRelativeTime(time, now);
223
+ }, [time, now]);
224
+ var showTime = Boolean(timeLabel);
225
+ var showTrailing = showStatus || showTime;
226
+ var fitTitle = showTypeIcon || showTrailing;
227
+ var displayTitle = useFittedTitle(title, fitTitle, labelRef, titleRef, trailingRef, typeIconRef);
205
228
  useEffect(function () {
206
229
  if (!renaming) setDraft(title);
207
230
  }, [title, renaming]);
@@ -214,6 +237,17 @@ export function ChatItem(_ref2) {
214
237
  el.scrollLeft = 0;
215
238
  }
216
239
  }, [renaming]);
240
+
241
+ // 相对时间每分钟刷新一次(有 time 时)
242
+ useEffect(function () {
243
+ if (time == null) return;
244
+ var id = window.setInterval(function () {
245
+ return setNow(Date.now());
246
+ }, 60000);
247
+ return function () {
248
+ return window.clearInterval(id);
249
+ };
250
+ }, [time]);
217
251
  var commitRename = function commitRename() {
218
252
  var _inputRef$current;
219
253
  var trimmed = draft.trim();
@@ -227,8 +261,22 @@ export function ChatItem(_ref2) {
227
261
  setDraft(title);
228
262
  setRenaming(false);
229
263
  };
264
+ var actionMenu = [].concat(_toConsumableArray(onRename ? [{
265
+ value: 'rename',
266
+ label: '修改名称',
267
+ icon: 'pencil-line'
268
+ }] : []), _toConsumableArray(onPin ? [{
269
+ value: 'pin',
270
+ label: pinned ? '取消置顶' : '置顶',
271
+ icon: 'arrow-up-to-line'
272
+ }] : []), _toConsumableArray(onDelete ? [{
273
+ value: 'delete',
274
+ label: '删除',
275
+ icon: 'trash-2'
276
+ }] : []));
230
277
  return /*#__PURE__*/React.createElement(HoverFill, {
231
278
  "data-odn-chat-item": true,
279
+ "data-odn-chat-item-pinned": pinned || undefined,
232
280
  bgClassName: "odn-chat-item-btn-bg",
233
281
  disabled: active || renaming
234
282
  }, /*#__PURE__*/React.createElement("div", {
@@ -251,11 +299,13 @@ export function ChatItem(_ref2) {
251
299
  },
252
300
  onBlur: commitRename,
253
301
  onKeyDown: function onKeyDown(e) {
302
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
254
303
  if (e.key === 'Enter') commitRename();
255
304
  if (e.key === 'Escape') cancelRename();
256
305
  },
257
306
  "data-odn-chat-item-rename-input": true
258
307
  }))) : /*#__PURE__*/React.createElement("button", {
308
+ type: "button",
259
309
  onClick: function onClick() {
260
310
  return _onClick === null || _onClick === void 0 ? void 0 : _onClick(id);
261
311
  },
@@ -270,39 +320,41 @@ export function ChatItem(_ref2) {
270
320
  "data-odn-chat-item-title-bold": active ? '' : undefined,
271
321
  "data-odn-chat-item-title-fitted": fitTitle ? '' : undefined,
272
322
  title: title
273
- }, displayTitle), (showTypeIcon || showStatus) && /*#__PURE__*/React.createElement("span", {
274
- "data-odn-chat-item-trailing": true,
275
- ref: trailingRef
276
- }, showTypeIcon && /*#__PURE__*/React.createElement("span", {
323
+ }, displayTitle), showTypeIcon ? /*#__PURE__*/React.createElement("span", {
324
+ ref: typeIconRef,
277
325
  "data-odn-chat-item-type-icon": true,
278
326
  "aria-label": "\u4EBA\u5DE5\u5BA2\u670D\u4F1A\u8BDD",
279
327
  role: "img"
280
328
  }, /*#__PURE__*/React.createElement(Icon, {
281
329
  name: "support-bold",
282
330
  size: 12
283
- })), showStatus && status && /*#__PURE__*/React.createElement(ChatItemStatus, {
331
+ })) : null, showTrailing ? /*#__PURE__*/React.createElement("span", {
332
+ "data-odn-chat-item-trailing": true,
333
+ ref: trailingRef
334
+ }, showStatus && status ? /*#__PURE__*/React.createElement(ChatItemStatus, {
284
335
  status: status
285
- })))), /*#__PURE__*/React.createElement(Dropdown, {
336
+ }) : null, showTime ? /*#__PURE__*/React.createElement("time", {
337
+ "data-odn-chat-item-time": true,
338
+ dateTime: typeof time === 'number' ? new Date(time).toISOString() : time instanceof Date ? time.toISOString() : String(time)
339
+ }, timeLabel) : null) : null)), /*#__PURE__*/React.createElement(Dropdown, {
286
340
  trigger: "click",
287
341
  placement: "bottomRight",
288
342
  zIndex: actionMenuZIndex,
289
343
  popupClassName: "odn-chat-item-action-menu",
290
344
  visible: menuOpen,
291
345
  onVisibleChange: setMenuOpen,
292
- menu: [].concat(_toConsumableArray(onRename ? [{
293
- value: 'rename',
294
- label: '修改名称',
295
- icon: 'edit'
296
- }] : []), _toConsumableArray(onDelete ? [{
297
- value: 'delete',
298
- label: '删除',
299
- icon: 'delete'
300
- }] : [])),
346
+ menu: actionMenu,
301
347
  onSelect: function onSelect(value) {
302
348
  if (value === 'rename') {
303
349
  setDraft(title);
304
350
  setRenaming(true);
305
351
  setMenuOpen(false);
352
+ return;
353
+ }
354
+ if (value === 'pin') {
355
+ onPin === null || onPin === void 0 || onPin(id, !pinned);
356
+ setMenuOpen(false);
357
+ return;
306
358
  }
307
359
  if (value === 'delete') {
308
360
  setDeleteOpen(true);
@@ -339,4 +391,5 @@ export function ChatItem(_ref2) {
339
391
  })));
340
392
  }
341
393
  ChatItem.displayName = 'ChatItem';
342
- export default ChatItem;
394
+ export default ChatItem;
395
+ export { formatRelativeTime } from "./relative-time";
@@ -0,0 +1,2 @@
1
+ /** 将时间点格式化为中文相对时间(刚刚 / N分钟前 / …)。 */
2
+ export declare function formatRelativeTime(input: number | string | Date, now?: number): string;
@@ -0,0 +1,22 @@
1
+ /** 将时间点格式化为中文相对时间(刚刚 / N分钟前 / …)。 */
2
+ export function formatRelativeTime(input) {
3
+ var now = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Date.now();
4
+ var t = typeof input === 'number' ? input : input instanceof Date ? input.getTime() : Date.parse(input);
5
+ if (!Number.isFinite(t)) return '';
6
+ var diff = Math.max(0, now - t);
7
+ var sec = Math.floor(diff / 1000);
8
+ if (sec < 60) return '刚刚';
9
+ var min = Math.floor(sec / 60);
10
+ if (min < 60) return "".concat(min, "\u5206\u949F\u524D");
11
+ var hr = Math.floor(min / 60);
12
+ if (hr < 24) return "".concat(hr, "\u5C0F\u65F6\u524D");
13
+ var day = Math.floor(hr / 24);
14
+ if (day === 1) return '昨天';
15
+ if (day < 7) return "".concat(day, "\u5929\u524D");
16
+ var d = new Date(t);
17
+ var n = new Date(now);
18
+ if (d.getFullYear() === n.getFullYear()) {
19
+ return "".concat(d.getMonth() + 1, "\u6708").concat(d.getDate(), "\u65E5");
20
+ }
21
+ return "".concat(d.getFullYear(), "/").concat(d.getMonth() + 1, "/").concat(d.getDate());
22
+ }
@@ -1,16 +1,34 @@
1
1
  @charset "UTF-8";
2
+ /*
3
+ * … 相对行边缘:上/下/右 inset 一致(约 6px)。
4
+ * 行高 ≈ 8+8+内容;24px 按钮垂直居中后上下各 ~6px → right 同为 6。
5
+ * 左侧 12px 是正文 padding,不是 … 的 inset。
6
+ * hover 腾位:6 + 24 + 6 = 36。
7
+ */
2
8
  [data-odn-chat-item]:hover [data-odn-chat-item-menu-trigger], [data-odn-chat-item]:has(:focus-visible) [data-odn-chat-item-menu-trigger] {
3
9
  opacity: 1;
4
10
  pointer-events: auto;
5
11
  }
6
- [data-odn-chat-item]:hover [data-odn-chat-item-btn], [data-odn-chat-item]:has(:focus-visible) [data-odn-chat-item-btn] {
12
+ [data-odn-chat-item]:hover [data-odn-chat-item-btn], [data-odn-chat-item]:has(:focus-visible) [data-odn-chat-item-btn], [data-odn-chat-item]:has([data-odn-chat-item-menu-trigger-open]) [data-odn-chat-item-btn] {
7
13
  padding-right: 36px;
8
14
  }
9
15
  [data-odn-chat-item] {
10
- /* 菜单打开时:保留让位空间,按钮保持激活视觉 */
16
+ /*
17
+ * 同槽让位:时间瞬时退出布局(无过渡,避免与 … 叠影)。
18
+ * 无 status 时收掉 trailing 左间距,避免标题与 … 之间空档。
19
+ */
11
20
  }
12
- [data-odn-chat-item]:has([data-odn-chat-item-menu-trigger-open]) [data-odn-chat-item-btn] {
13
- padding-right: 36px;
21
+ [data-odn-chat-item]:hover [data-odn-chat-item-time], [data-odn-chat-item]:has(:focus-visible) [data-odn-chat-item-time], [data-odn-chat-item]:has([data-odn-chat-item-menu-trigger-active]) [data-odn-chat-item-time], [data-odn-chat-item]:has([data-odn-chat-item-menu-trigger-open]) [data-odn-chat-item-time] {
22
+ opacity: 0;
23
+ width: 0;
24
+ min-width: 0;
25
+ max-width: 0;
26
+ margin: 0;
27
+ overflow: hidden;
28
+ pointer-events: none;
29
+ }
30
+ [data-odn-chat-item]:hover [data-odn-chat-item-trailing]:not(:has([data-odn-chat-item-status])), [data-odn-chat-item]:has(:focus-visible) [data-odn-chat-item-trailing]:not(:has([data-odn-chat-item-status])), [data-odn-chat-item]:has([data-odn-chat-item-menu-trigger-active]) [data-odn-chat-item-trailing]:not(:has([data-odn-chat-item-status])), [data-odn-chat-item]:has([data-odn-chat-item-menu-trigger-open]) [data-odn-chat-item-trailing]:not(:has([data-odn-chat-item-status])) {
31
+ padding-left: 0;
14
32
  }
15
33
 
16
34
  .odn-chat-item-btn-bg {
@@ -22,7 +40,7 @@
22
40
  display: block;
23
41
  }
24
42
 
25
- /* 重命名态:保持按钮尺寸不变(沿用 active/hover 的 padding-right),隐藏菜单按钮但不影响布局 */
43
+ /* 重命名态:保持按钮尺寸不变,隐藏菜单按钮 */
26
44
  [data-odn-chat-item-renaming] [data-odn-chat-item-btn] {
27
45
  padding-right: 36px;
28
46
  cursor: text;
@@ -105,7 +123,7 @@
105
123
  }
106
124
 
107
125
  [data-odn-chat-item-title] {
108
- /* 0 1 auto:短标题不撑开(icon 贴字);长标题可收缩省略,trailing 被顶到右缘 */
126
+ /* 0 1 auto:短标题不撑开,类型 icon 紧跟;长标题可收缩省略 */
109
127
  flex: 0 1 auto;
110
128
  min-width: 0;
111
129
  font-size: 14px;
@@ -114,6 +132,13 @@
114
132
  white-space: nowrap;
115
133
  }
116
134
 
135
+ [data-odn-chat-item-title-mark] {
136
+ padding: 0;
137
+ color: var(--odn-color-brand-6);
138
+ background: transparent;
139
+ font: inherit;
140
+ }
141
+
117
142
  /* JS 已裁到可用宽度:按内容收缩,省略号后无字符边界留白;量准时整组仍铺满行 */
118
143
  [data-odn-chat-item-title-fitted] {
119
144
  flex: 0 0 auto;
@@ -125,11 +150,33 @@
125
150
  font-weight: 500;
126
151
  }
127
152
 
153
+ /* 状态 / 时间:右对齐;类型 icon 在标题旁,不在此区 */
128
154
  [data-odn-chat-item-trailing] {
129
155
  flex-shrink: 0;
130
156
  display: inline-flex;
131
157
  align-items: center;
132
- gap: 6px;
158
+ /* 不用 gap:时间让位时用 margin 一并收掉,避免空档残留 */
159
+ gap: 0;
160
+ margin-left: auto;
161
+ padding-left: 12px;
162
+ }
163
+
164
+ [data-odn-chat-item-time] {
165
+ flex-shrink: 0;
166
+ max-width: 4.5em;
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ white-space: nowrap;
170
+ color: var(--odn-color-black-9);
171
+ font-size: 12px;
172
+ font-weight: 400;
173
+ line-height: 18px;
174
+ /* 让位无过渡:瞬时切换 */
175
+ }
176
+
177
+ /* status ↔ 时间 = 6px;让位时 margin 归零 */
178
+ [data-odn-chat-item-status] + [data-odn-chat-item-time] {
179
+ margin-left: 6px;
133
180
  }
134
181
 
135
182
  [data-odn-chat-item-type-icon] {
@@ -165,7 +212,7 @@
165
212
  [data-odn-chat-item-status-waiting] {
166
213
  width: auto;
167
214
  height: 16px;
168
- color: #fff;
215
+ color: var(--odn-color-white);
169
216
  font-size: 11px;
170
217
  font-weight: 600;
171
218
  line-height: 16px;
@@ -197,6 +244,7 @@
197
244
  padding: 0;
198
245
  opacity: 0;
199
246
  pointer-events: none;
247
+ /* opacity 瞬时切换(与时间同步);仅保留背景色过渡 */
200
248
  transition: background 0.15s, color 0.15s;
201
249
  }
202
250
  [data-odn-chat-item-menu-trigger]:hover {
@@ -0,0 +1,61 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type ChatItemProps } from '../chat-item';
3
+ import './style';
4
+ /** 按会话时间筛选:全部 / 今天 / 近 7 天 / 近 30 天。 */
5
+ export type ChatListFilterTime = 'all' | 'today' | '7d' | '30d';
6
+ /** 按会话类型筛选:全部 / AI / 人工客服。 */
7
+ export type ChatListFilterType = 'all' | 'ai' | 'human';
8
+ export type ChatListItem = Pick<ChatItemProps, 'id' | 'title' | 'active' | 'type' | 'status' | 'pinned' | 'time'>;
9
+ export interface ChatListProps {
10
+ items: ChatListItem[];
11
+ /** 列表标题。默认「对话」。 */
12
+ title?: ReactNode;
13
+ /**
14
+ * 置顶分区标题。视觉同组头,无交互。传 `null` 隐藏。默认「置顶」。
15
+ */
16
+ pinnedSectionTitle?: ReactNode | null;
17
+ /**
18
+ * 非置顶分区标题。仅当存在置顶会话时展示;无置顶时不渲染组名。
19
+ * 传 `null` 始终隐藏。默认「最近」。
20
+ */
21
+ recentSectionTitle?: ReactNode | null;
22
+ /**
23
+ * 受控:时间筛选。`'all'` | `'today'` | `'7d'` | `'30d'`。
24
+ */
25
+ filterTime?: ChatListFilterTime;
26
+ /** 非受控:初始时间筛选。默认 `'all'`。 */
27
+ defaultFilterTime?: ChatListFilterTime;
28
+ onFilterTimeChange?: (value: ChatListFilterTime) => void;
29
+ /**
30
+ * 受控:类型筛选。`'all'` | `'ai'` | `'human'`。
31
+ */
32
+ filterType?: ChatListFilterType;
33
+ /** 非受控:初始类型筛选。默认 `'all'`。 */
34
+ defaultFilterType?: ChatListFilterType;
35
+ onFilterTypeChange?: (value: ChatListFilterType) => void;
36
+ /** 受控:筛选选择器是否展开 */
37
+ filterOpen?: boolean;
38
+ defaultFilterOpen?: boolean;
39
+ onFilterOpenChange?: (open: boolean) => void;
40
+ /** 空列表时的占位 */
41
+ empty?: ReactNode;
42
+ actionMenuZIndex?: number | string;
43
+ /** 筛选下拉的 z-index */
44
+ filterMenuZIndex?: number | string;
45
+ className?: string;
46
+ onItemClick?: ChatItemProps['onClick'];
47
+ onRename?: ChatItemProps['onRename'];
48
+ onPin?: ChatItemProps['onPin'];
49
+ onDelete?: ChatItemProps['onDelete'];
50
+ /** 自定义行渲染。默认渲染 ChatItem。 */
51
+ renderItem?: (item: ChatListItem, node: ReactNode) => ReactNode;
52
+ }
53
+ /** 按时间筛选项判断会话是否命中。无有效 `time` 时仅在「全部」下保留。 */
54
+ export declare function matchChatListFilterTime(time: ChatListItem['time'], filter: ChatListFilterTime, now?: number): boolean;
55
+ /** 按类型筛选项判断会话是否命中。缺省 `type` 视为 `'ai'`。 */
56
+ export declare function matchChatListFilterType(type: ChatListItem['type'], filter: ChatListFilterType): boolean;
57
+ export declare function ChatList({ items, title, pinnedSectionTitle, recentSectionTitle, filterTime: filterTimeProp, defaultFilterTime, onFilterTimeChange, filterType: filterTypeProp, defaultFilterType, onFilterTypeChange, filterOpen: filterOpenProp, defaultFilterOpen, onFilterOpenChange, empty, actionMenuZIndex, filterMenuZIndex, className, onItemClick, onRename, onPin, onDelete, renderItem, }: ChatListProps): import("react").JSX.Element;
58
+ export declare namespace ChatList {
59
+ var displayName: string;
60
+ }
61
+ export default ChatList;