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.
- package/dist/_genui-types.d.ts +2 -0
- package/dist/chat-item/index.d.ts +17 -1
- package/dist/chat-item/index.js +84 -31
- package/dist/chat-item/relative-time.d.ts +2 -0
- package/dist/chat-item/relative-time.js +22 -0
- package/dist/chat-item/style/index.css +56 -8
- package/dist/chat-list/index.d.ts +61 -0
- package/dist/chat-list/index.js +256 -0
- package/dist/chat-list/style/index.css +135 -0
- package/dist/chat-list/style/index.d.ts +2 -0
- package/dist/chat-list/style/index.js +2 -0
- package/dist/clarify-dock/index.d.ts +8 -2
- package/dist/clarify-dock/index.js +29 -11
- package/dist/clarify-dock/types.d.ts +10 -0
- package/dist/clarify-dock/types.js +12 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/preview-panel/CsvPreviewTable.d.ts +8 -0
- package/dist/preview-panel/CsvPreviewTable.js +49 -0
- package/dist/preview-panel/index.js +102 -2
- package/dist/preview-panel/parseCsv.d.ts +16 -0
- package/dist/preview-panel/parseCsv.js +103 -0
- package/dist/preview-panel/style/index.css +76 -8
- package/package.json +3 -2
package/dist/_genui-types.d.ts
CHANGED
|
@@ -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';
|
package/dist/chat-item/index.js
CHANGED
|
@@ -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
|
|
19
|
-
* 解决 CSS text-overflow 按字符边界截断后,省略号右侧留白把
|
|
20
|
-
* 量准时:截断标题宽 ≈ label − trailing − gap,整组仍铺满行,trailing 贴右缘。
|
|
19
|
+
* 有 trailing(客服 icon / status / 时间)时,用 DOM 实测把标题裁到可用宽度。
|
|
20
|
+
* 解决 CSS text-overflow 按字符边界截断后,省略号右侧留白把 trailing 顶开的问题。
|
|
21
21
|
*/
|
|
22
|
-
function useFittedTitle(title, enabled, labelRef, titleRef,
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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 时隐藏
|
|
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
|
|
204
|
-
|
|
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),
|
|
274
|
-
|
|
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
|
-
}))
|
|
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
|
-
})
|
|
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:
|
|
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,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-
|
|
13
|
-
|
|
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
|
-
/*
|
|
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
|
|
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
|
|
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:
|
|
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;
|