@octohirono/stitch-design-system 0.2.0 → 0.3.0

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.
Files changed (46) hide show
  1. package/dist/components/ChatFile/ChatFile.d.ts +34 -0
  2. package/dist/components/ChatFile/ChatFile.js +71 -0
  3. package/dist/components/ChatFile/chat-file.module.js +12 -0
  4. package/dist/components/ChatFile/index.d.ts +2 -0
  5. package/dist/components/ChatImage/ChatImage.d.ts +31 -0
  6. package/dist/components/ChatImage/ChatImage.js +88 -0
  7. package/dist/components/ChatImage/chat-image.module.js +12 -0
  8. package/dist/components/ChatImage/index.d.ts +2 -0
  9. package/dist/components/ChatInput/ChatInput.d.ts +77 -0
  10. package/dist/components/ChatInput/ChatInput.js +88 -0
  11. package/dist/components/ChatInput/chat-input.module.js +11 -0
  12. package/dist/components/ChatInput/index.d.ts +2 -0
  13. package/dist/components/ChatList/ChatList.d.ts +58 -0
  14. package/dist/components/ChatList/ChatList.js +49 -0
  15. package/dist/components/ChatList/chat-list.module.js +7 -0
  16. package/dist/components/ChatList/index.d.ts +2 -0
  17. package/dist/components/ChatMessage/ChatMessage.d.ts +52 -0
  18. package/dist/components/ChatMessage/ChatMessage.js +71 -0
  19. package/dist/components/ChatMessage/chat-message.module.js +21 -0
  20. package/dist/components/ChatMessage/index.d.ts +2 -0
  21. package/dist/components/ChatMessageActions/ChatMessageActions.d.ts +40 -0
  22. package/dist/components/ChatMessageActions/ChatMessageActions.js +34 -0
  23. package/dist/components/ChatMessageActions/chat-message-actions.module.js +7 -0
  24. package/dist/components/ChatMessageActions/index.d.ts +2 -0
  25. package/dist/components/ChatVoice/ChatVoice.d.ts +39 -0
  26. package/dist/components/ChatVoice/ChatVoice.js +44 -0
  27. package/dist/components/ChatVoice/chat-voice.module.js +12 -0
  28. package/dist/components/ChatVoice/index.d.ts +2 -0
  29. package/dist/components/ContextMenu/ContextMenu.js +41 -34
  30. package/dist/components/ContextMenu/context-menu.module.js +10 -9
  31. package/dist/components/DropdownMenu/DropdownMenu.js +45 -38
  32. package/dist/components/DropdownMenu/dropdown-menu.module.js +10 -9
  33. package/dist/components/Icon/Icon.d.ts +20 -0
  34. package/dist/components/Icon/Icon.js +35 -2
  35. package/dist/components/Image/Image.js +1 -1
  36. package/dist/components/Image/image.module.js +14 -14
  37. package/dist/components/Select/Select.js +87 -83
  38. package/dist/components/Select/select.module.js +10 -9
  39. package/dist/components/TypingIndicator/TypingIndicator.d.ts +24 -0
  40. package/dist/components/TypingIndicator/TypingIndicator.js +29 -0
  41. package/dist/components/TypingIndicator/index.d.ts +2 -0
  42. package/dist/components/TypingIndicator/typing-indicator.module.js +8 -0
  43. package/dist/index.d.ts +16 -0
  44. package/dist/index.js +36 -28
  45. package/dist/style.css +873 -411
  46. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ /**
3
+ * 文件附件卡:复用 `<Card>` 承载——类型图标(`<Icon name="file">`)+ 文件名 + 大小 /
4
+ * 副文本;作为 `<ChatMessage>` 的 `content` 塞入(sent / received 两侧皆可)。点击 / 下载
5
+ * 均为**回调交 app**(组件不导航、不发起下载)。对外 props 取 Ant Design X `Attachments`
6
+ * 附件项最近惯例(`name` / `size` / `description`)+ Ant v5 回调命名。
7
+ *
8
+ * 跨-prop 注意事项:
9
+ * - **下载键是文件卡固有部件、常驻**(文件默认可下载):右侧复用 `<Button type="text">`,点击走
10
+ * `onDownload` 回调、由 app 落地下载。
11
+ * - **两个独立可聚焦控件、互不嵌套**:给 `onClick` 时文件名区升格为整行透明按钮(打开),与下载
12
+ * 键平级,无按钮套按钮。
13
+ * - **`size` 是字节数**,组件内格式化为人类可读(`1.2 MB`);`description` 为副文本(如类型),
14
+ * 与已格式化的大小同排、`·` 分隔。
15
+ * - **图标走 `<Icon>`**:类型图标 `<Icon name="file">`(装饰性)、下载 `<Icon name="download">`
16
+ * (其按钮带可访问名 `下载 {name}`),非 emoji / 裸 `<svg>`。
17
+ */
18
+ export interface ChatFileProps {
19
+ /** 文件名(必填) */
20
+ name: string;
21
+ /** 文件大小(字节数);组件内格式化为人类可读 */
22
+ size?: number;
23
+ /** 副文本(如文件类型说明),与大小同排 `·` 分隔 */
24
+ description?: React.ReactNode;
25
+ /** 点击文件卡的回调(打开 / 预览交 app);给出时文件名区可聚焦、可键盘触发 */
26
+ onClick?: () => void;
27
+ /** 点击下载键的回调(下载交 app);下载键常驻,点击走此回调 */
28
+ onDownload?: () => void;
29
+ /** 自定义类名(挂到根 Card) */
30
+ className?: string;
31
+ /** 行内样式(挂到根 Card) */
32
+ style?: React.CSSProperties;
33
+ }
34
+ export declare const ChatFile: React.FC<ChatFileProps>;
@@ -0,0 +1,71 @@
1
+ import { Button as e } from "../Button/Button.js";
2
+ import { Icon as t } from "../Icon/Icon.js";
3
+ import { Card as n } from "../Card/Card.js";
4
+ import r from "./chat-file.module.js";
5
+ import "react";
6
+ import i from "clsx";
7
+ import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
8
+ //#region packages/react/src/components/ChatFile/ChatFile.tsx
9
+ var c = [
10
+ "B",
11
+ "KB",
12
+ "MB",
13
+ "GB",
14
+ "TB"
15
+ ];
16
+ function l(e) {
17
+ if (!Number.isFinite(e) || e < 0) return "";
18
+ if (e < 1) return "0 B";
19
+ let t = Math.min(c.length - 1, Math.floor(Math.log(e) / Math.log(1024))), n = e / 1024 ** t;
20
+ return `${t === 0 ? String(Math.round(n)) : n.toFixed(1)} ${c[t]}`;
21
+ }
22
+ var u = ({ name: c, size: u, description: d, onClick: f, onDownload: p, className: m, style: h }) => {
23
+ let g = typeof u == "number" ? l(u) : "", _ = /* @__PURE__ */ s("span", {
24
+ className: r.meta,
25
+ children: [
26
+ g && /* @__PURE__ */ o("span", { children: g }),
27
+ g && d != null && /* @__PURE__ */ o("span", {
28
+ "aria-hidden": "true",
29
+ children: " · "
30
+ }),
31
+ d != null && /* @__PURE__ */ o("span", { children: d })
32
+ ]
33
+ }), v = /* @__PURE__ */ s(a, { children: [/* @__PURE__ */ o(t, {
34
+ name: "file",
35
+ className: r.type,
36
+ size: "1.5em"
37
+ }), /* @__PURE__ */ s("span", {
38
+ className: r.text,
39
+ children: [/* @__PURE__ */ o("span", {
40
+ className: r.name,
41
+ children: c
42
+ }), (g || d != null) && _]
43
+ })] });
44
+ return /* @__PURE__ */ s(n, {
45
+ variant: "outlined",
46
+ className: i(r.file, m),
47
+ style: h,
48
+ children: [f ? /* @__PURE__ */ o("button", {
49
+ type: "button",
50
+ className: r.trigger,
51
+ onClick: f,
52
+ "aria-label": `打开文件 ${c}`,
53
+ children: v
54
+ }) : /* @__PURE__ */ o("div", {
55
+ className: r.trigger,
56
+ children: v
57
+ }), /* @__PURE__ */ o(e, {
58
+ type: "text",
59
+ className: r.download,
60
+ onClick: p,
61
+ "aria-label": `下载 ${c}`,
62
+ children: /* @__PURE__ */ o(t, {
63
+ name: "download",
64
+ size: "1.25em"
65
+ })
66
+ })]
67
+ });
68
+ };
69
+ u.displayName = "ChatFile";
70
+ //#endregion
71
+ export { u as ChatFile };
@@ -0,0 +1,12 @@
1
+ //#region packages/react/src/components/ChatFile/chat-file.module.less
2
+ var e = "_file_1tjaq_1", t = "_trigger_1tjaq_8", n = "_type_1tjaq_29", r = "_text_1tjaq_33", i = "_name_1tjaq_39", a = "_meta_1tjaq_47", o = "_download_1tjaq_51", s = {
3
+ file: e,
4
+ trigger: t,
5
+ type: n,
6
+ text: r,
7
+ name: i,
8
+ meta: a,
9
+ download: o
10
+ };
11
+ //#endregion
12
+ export { s as default, o as download, e as file, a as meta, i as name, r as text, t as trigger, n as type };
@@ -0,0 +1,2 @@
1
+ export { ChatFile } from './ChatFile';
2
+ export type { ChatFileProps } from './ChatFile';
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ /**
3
+ * 图片消息(气泡内缩略图 + 大图查看器):气泡里显缩略图(复用通用 `<Image>`、`preview` 关闭),
4
+ * 点击开大图——**大图仍复用通用 `<Image>`(它自带的相框即「框」)**,本组件只做一层遮罩 + 把下载 /
5
+ * 关闭工具键**叠在这个框上**。作为 `<ChatMessage>` 的 `content` 塞入(sent / received 两侧皆可)。
6
+ * `src` / `alt` / `width` / `height` 照搬 antd `Image`;`width` 即缩略图宽。
7
+ *
8
+ * 跨-prop 注意事项:
9
+ * - **缩略图与大图都复用 `<Image preview={false}>`**(不手写裸 `<img>`、不重造 Image 的相框 / 圆角 /
10
+ * 加载淡入 / 错误占位);本组件只加聊天专属的遮罩 + 框上工具键(下载 / 关闭),`<Image>` 一行未改。
11
+ * - **块级排布**:根为块级元素,故 `<ChatMessage>` 的时间 / 回执落在缩略图**下方**(同文件卡)。
12
+ * - **大图查看器 a11y**:Portal 遮罩 + `role="dialog"`;打开焦点落关闭键、Tab 在下载 / 关闭间循环、
13
+ * ESC 关闭、关闭后焦点归还缩略图。下载走原生 `<a download href={src}>`(存下当前图,不解码不发
14
+ * 请求),工具键图标走 `<Icon>`,均带可访问名。
15
+ * - `width` 控缩略图尺寸;点击后大图按视口放大(原始尺寸)。
16
+ */
17
+ export interface ChatImageProps {
18
+ /** 图片地址(必填,照搬 antd Image) */
19
+ src: string;
20
+ /** 图片替代文本(无障碍);留空表示装饰性图片 */
21
+ alt?: string;
22
+ /** 缩略图宽度(数字按 px,字符串原样) */
23
+ width?: number | string;
24
+ /** 缩略图高度(数字按 px,字符串原样) */
25
+ height?: number | string;
26
+ /** 自定义类名(挂到根) */
27
+ className?: string;
28
+ /** 行内样式(挂到根) */
29
+ style?: React.CSSProperties;
30
+ }
31
+ export declare const ChatImage: React.FC<ChatImageProps>;
@@ -0,0 +1,88 @@
1
+ import { Icon as e } from "../Icon/Icon.js";
2
+ import { Image as t } from "../Image/Image.js";
3
+ import n from "./chat-image.module.js";
4
+ import { useEffect as r, useRef as i, useState as a } from "react";
5
+ import o from "clsx";
6
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
+ import { createPortal as l } from "react-dom";
8
+ //#region packages/react/src/components/ChatImage/ChatImage.tsx
9
+ var u = ({ src: u, alt: d = "", width: f, height: p, className: m, style: h }) => {
10
+ let [g, _] = a(!1), v = i(null), y = i(null), b = i(null);
11
+ return r(() => {
12
+ if (!g) return;
13
+ v.current?.focus();
14
+ let e = (e) => {
15
+ if (e.key === "Escape") _(!1);
16
+ else if (e.key === "Tab") {
17
+ e.preventDefault();
18
+ let t = [y.current, v.current].filter((e) => e != null);
19
+ if (t.length === 0) return;
20
+ let n = t.indexOf(document.activeElement);
21
+ t[e.shiftKey ? n <= 0 ? t.length - 1 : n - 1 : n >= t.length - 1 ? 0 : n + 1]?.focus();
22
+ }
23
+ };
24
+ return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
25
+ }, [g]), r(() => {
26
+ g || (b.current?.focus(), b.current = null);
27
+ }, [g]), /* @__PURE__ */ c("span", {
28
+ className: o(n.frame, m),
29
+ style: h,
30
+ children: [/* @__PURE__ */ s("button", {
31
+ type: "button",
32
+ className: n.thumb,
33
+ onClick: () => {
34
+ b.current = document.activeElement, _(!0);
35
+ },
36
+ "aria-label": d ? `查看大图 ${d}` : "查看大图",
37
+ children: /* @__PURE__ */ s(t, {
38
+ src: u,
39
+ alt: d,
40
+ width: f,
41
+ height: p,
42
+ preview: !1
43
+ })
44
+ }), l(g ? /* @__PURE__ */ s("div", {
45
+ className: n.mask,
46
+ onClick: () => _(!1),
47
+ children: /* @__PURE__ */ c("div", {
48
+ className: n.dialog,
49
+ role: "dialog",
50
+ "aria-modal": "true",
51
+ "aria-label": d ? `查看图片:${d}` : "图片预览",
52
+ onClick: (e) => e.stopPropagation(),
53
+ children: [/* @__PURE__ */ s(t, {
54
+ src: u,
55
+ alt: d,
56
+ preview: !1,
57
+ className: n.full
58
+ }), /* @__PURE__ */ c("div", {
59
+ className: n.tools,
60
+ children: [/* @__PURE__ */ s("a", {
61
+ ref: y,
62
+ className: n.toolBtn,
63
+ href: u,
64
+ download: !0,
65
+ "aria-label": d ? `下载图片 ${d}` : "下载图片",
66
+ children: /* @__PURE__ */ s(e, {
67
+ name: "download",
68
+ size: 14
69
+ })
70
+ }), /* @__PURE__ */ s("button", {
71
+ type: "button",
72
+ ref: v,
73
+ className: n.toolBtn,
74
+ "aria-label": "关闭大图",
75
+ onClick: () => _(!1),
76
+ children: /* @__PURE__ */ s(e, {
77
+ name: "close",
78
+ size: 14
79
+ })
80
+ })]
81
+ })]
82
+ })
83
+ }) : null, document.body)]
84
+ });
85
+ };
86
+ u.displayName = "ChatImage";
87
+ //#endregion
88
+ export { u as ChatImage };
@@ -0,0 +1,12 @@
1
+ //#region packages/react/src/components/ChatImage/chat-image.module.less
2
+ var e = "_frame_1b4n8_1", t = "_thumb_1b4n8_6", n = "_mask_1b4n8_18", r = "_dialog_1b4n8_27", i = "_full_1b4n8_32", a = "_tools_1b4n8_36", o = "_toolBtn_1b4n8_44", s = {
3
+ frame: e,
4
+ thumb: t,
5
+ mask: n,
6
+ dialog: r,
7
+ full: i,
8
+ tools: a,
9
+ toolBtn: o
10
+ };
11
+ //#endregion
12
+ export { s as default, r as dialog, e as frame, i as full, n as mask, t as thumb, o as toolBtn, a as tools };
@@ -0,0 +1,2 @@
1
+ export { ChatImage } from './ChatImage';
2
+ export type { ChatImageProps } from './ChatImage';
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ export type ChatInputSubmitType = 'enter' | 'shiftEnter';
3
+ /** autoSize 精细档:限制自增高的最小 / 最大行数(照搬 antd Input.TextArea / Sender) */
4
+ export interface ChatInputAutoSize {
5
+ /** 最小行数(低于此仍占此高) */
6
+ minRows?: number;
7
+ /** 最大行数(超过后内部滚动、不再增高) */
8
+ maxRows?: number;
9
+ }
10
+ /**
11
+ * 聊天输入器:多行 textarea **自增高**(`autoSize`)+ 受控/非受控 `value` + `submitType`
12
+ * 决定发送键位(Enter 发送 / Shift+Enter 换行)+ `onSubmit` 发送回调 + `prefix` / `actions`
13
+ * 插槽(附件按钮等钩子由 app 填)。对外 props 照搬 antd `Input`(`value` / `onChange` /
14
+ * `disabled` / `placeholder` / `autoSize`)+ 聊天专属补 Ant Design X `Sender`
15
+ * (`onSubmit` / `loading` / `submitType` / `prefix` / `actions`)。
16
+ *
17
+ * 跨-prop 注意事项:
18
+ * - **受控/非受控双模式**:给 `value` 由父管,只给 `defaultValue` 组件自管;非受控时发送后
19
+ * 自动清空,受控时清空由父在 `onSubmit` 里做(组件不擅自改父的值)。
20
+ * - **`autoSize` 自增高**:`true`(默认)随内容行数增高;传 `{ minRows, maxRows }` 卡上下界,
21
+ * 超 `maxRows` 内部滚动。`false` 关自增(固定单行高、可手动拉伸由浏览器定)。
22
+ * - **`submitType` 定发送键位**:`enter`(默认)→ Enter 发送、Shift+Enter 换行;`shiftEnter`
23
+ * → Shift+Enter 发送、Enter 换行。输入法组词中(IME composing)的 Enter 一律只上屏、不发送。
24
+ * - **`onSubmit` 只在有内容时触发**:纯空白不发送;`disabled` / `loading` 时不发送。发送键是
25
+ * 原生 `<button>`,图标走 `<Icon name="send">`(禁 emoji / Unicode / 裸 svg),`sendAriaLabel`
26
+ * 定其可访问名。
27
+ * - **IM 无停止态**:`loading` 只置发送键忙 / 禁发,**不**把发送键换成「停止」键(那是 AI 生成
28
+ * 语境;IM 是人对人、无生成可停)。
29
+ * - **`prefix` / `actions` 传你自己的元素**(附件按钮走 `<Icon>` 或 `<Button>`)——**Do NOT** 传
30
+ * emoji / Unicode 符号 / 裸 `<svg>`。附件的选择 / 上传逻辑由 app 实现,组件只留插槽。
31
+ * - **可及名**:其余原生 textarea 属性(`aria-label` / `id` / `name` / `maxLength` …)经 `...rest`
32
+ * 透传到内部 `<textarea>`——给它可访问名(配 `<label htmlFor>` 或 `aria-label`)。
33
+ */
34
+ export interface ChatInputProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'defaultValue' | 'onChange' | 'onSubmit' | 'onKeyDown' | 'rows' | 'prefix'> {
35
+ /** 输入值(受控);不传由组件自管(非受控,配 `defaultValue`) */
36
+ value?: string;
37
+ /** 默认值(非受控初始值) */
38
+ defaultValue?: string;
39
+ /** 值变化回调(照搬 antd Input) */
40
+ onChange?: React.ChangeEventHandler<HTMLTextAreaElement>;
41
+ /** 发送回调(照搬 Ant Design X Sender):参数为当前消息文本 */
42
+ onSubmit?: (message: string) => void;
43
+ /** 占位符(照搬 antd Input) */
44
+ placeholder?: string;
45
+ /** 禁用(照搬 antd Input) */
46
+ disabled?: boolean;
47
+ /**
48
+ * 发送中(照搬 Sender):置发送键忙 / 禁发;**IM 无停止态**,不换成「停止」键
49
+ * @default false
50
+ */
51
+ loading?: boolean;
52
+ /**
53
+ * 多行自增高(照搬 antd Input.TextArea / Sender):`true` 随内容增高,`{ minRows, maxRows }`
54
+ * 卡上下界,`false` 关自增
55
+ * @default true
56
+ */
57
+ autoSize?: boolean | ChatInputAutoSize;
58
+ /**
59
+ * 发送键位(照搬 Sender):`enter` = Enter 发送 / Shift+Enter 换行;`shiftEnter` 反之
60
+ * @default 'enter'
61
+ */
62
+ submitType?: ChatInputSubmitType;
63
+ /** 前缀插槽(附件按钮等钩子,照搬 Sender);附件逻辑由 app */
64
+ prefix?: React.ReactNode;
65
+ /** 动作区插槽(照搬 Sender):覆盖默认发送键旁的自定义动作 */
66
+ actions?: React.ReactNode;
67
+ /**
68
+ * 发送键的无障碍标签(纯图标按钮必备可访问名)
69
+ * @default '发送'
70
+ */
71
+ sendAriaLabel?: string;
72
+ /** 自定义类名(挂到根容器) */
73
+ className?: string;
74
+ /** 行内样式(挂到根容器) */
75
+ style?: React.CSSProperties;
76
+ }
77
+ export declare const ChatInput: React.FC<ChatInputProps>;
@@ -0,0 +1,88 @@
1
+ import { Icon as e } from "../Icon/Icon.js";
2
+ import t from "./chat-input.module.js";
3
+ import { useCallback as n, useLayoutEffect as r, useRef as i, useState as a } from "react";
4
+ import o from "clsx";
5
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
+ //#region packages/react/src/components/ChatInput/ChatInput.tsx
7
+ var l = 1, u = ({ value: u, defaultValue: d, onChange: f, onSubmit: p, placeholder: m, disabled: h = !1, loading: g = !1, autoSize: _ = !0, submitType: v = "enter", prefix: y, actions: b, sendAriaLabel: x = "发送", className: S, style: C, ...w }) => {
8
+ let [T, E] = a(d ?? ""), D = u !== void 0, O = D ? u : T, k = i(null), A = n(() => {
9
+ let e = k.current;
10
+ if (e == null || _ === !1) return;
11
+ let t = _ === !0 ? {} : _, n = parseFloat(window.getComputedStyle(e).lineHeight) || 0;
12
+ e.style.height = "auto";
13
+ let r = e.scrollHeight;
14
+ if (n > 0) {
15
+ let i = t.minRows ?? l;
16
+ if (r = Math.max(r, i * n), t.maxRows != null) {
17
+ let i = t.maxRows * n;
18
+ e.style.overflowY = r > i ? "auto" : "hidden", r = Math.min(r, i);
19
+ } else e.style.overflowY = "hidden";
20
+ }
21
+ e.style.height = `${r}px`;
22
+ }, [_]);
23
+ r(() => {
24
+ A();
25
+ }, [O, A]), r(() => {
26
+ let e = k.current;
27
+ if (e == null || _ === !1 || typeof ResizeObserver > "u") return;
28
+ let t = -1, n = new ResizeObserver((e) => {
29
+ let n = e[0].contentRect.width;
30
+ n !== t && (t = n, A());
31
+ });
32
+ return n.observe(e), () => n.disconnect();
33
+ }, [_, A]);
34
+ let j = n((e) => {
35
+ D || E(e.target.value), f?.(e);
36
+ }, [D, f]), M = n(() => {
37
+ if (h || g) return;
38
+ let e = O;
39
+ e.trim() !== "" && (p?.(e), D || E(""));
40
+ }, [
41
+ h,
42
+ g,
43
+ O,
44
+ p,
45
+ D
46
+ ]), N = n((e) => {
47
+ e.key === "Enter" && (e.nativeEvent.isComposing || (v === "enter" ? !e.shiftKey : e.shiftKey) && (e.preventDefault(), M()));
48
+ }, [v, M]), P = !h && !g && O.trim() !== "";
49
+ return /* @__PURE__ */ c("div", {
50
+ className: o(t.input, h && t["is-disabled"], S),
51
+ style: C,
52
+ children: [
53
+ y != null && /* @__PURE__ */ s("div", {
54
+ className: t.prefix,
55
+ children: y
56
+ }),
57
+ /* @__PURE__ */ s("textarea", {
58
+ ref: k,
59
+ className: t.textarea,
60
+ value: O,
61
+ placeholder: m,
62
+ disabled: h,
63
+ rows: 1,
64
+ onChange: j,
65
+ onKeyDown: N,
66
+ ...w
67
+ }),
68
+ /* @__PURE__ */ c("div", {
69
+ className: t.actions,
70
+ children: [b, /* @__PURE__ */ s("button", {
71
+ type: "button",
72
+ className: t.send,
73
+ onClick: M,
74
+ disabled: !P,
75
+ "aria-label": x,
76
+ "aria-busy": g || void 0,
77
+ children: /* @__PURE__ */ s(e, {
78
+ name: "send",
79
+ size: 20
80
+ })
81
+ })]
82
+ })
83
+ ]
84
+ });
85
+ };
86
+ u.displayName = "ChatInput";
87
+ //#endregion
88
+ export { u as ChatInput };
@@ -0,0 +1,11 @@
1
+ //#region packages/react/src/components/ChatInput/chat-input.module.less
2
+ var e = "_input_1o9t1_1", t = "_prefix_1o9t1_27", n = "_textarea_1o9t1_32", r = "_actions_1o9t1_56", i = "_send_1o9t1_62", a = {
3
+ input: e,
4
+ "is-disabled": "_is-disabled_1o9t1_15",
5
+ prefix: t,
6
+ textarea: n,
7
+ actions: r,
8
+ send: i
9
+ };
10
+ //#endregion
11
+ export { r as actions, a as default, e as input, t as prefix, i as send, n as textarea };
@@ -0,0 +1,2 @@
1
+ export { ChatInput } from './ChatInput';
2
+ export type { ChatInputProps, ChatInputSubmitType, ChatInputAutoSize, } from './ChatInput';
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import type { ChatMessageProps, ChatMessageVariant } from '../ChatMessage';
3
+ /** 单条消息数据 = 一条 `ChatMessage` 的 props + 稳定 `id`(React key + append 判定) */
4
+ export interface ChatListItem extends ChatMessageProps {
5
+ /** 唯一标识:作 React key,并驱动「新消息 append」判定 */
6
+ id: string | number;
7
+ }
8
+ /**
9
+ * 消息流容器:**数据驱动**——吃 `items`(每项 = 一条 `ChatMessage` 的 props + `id`),内部
10
+ * 渲染 `ChatMessage`(`ChatMessage` 仍可单独使用,列表场景走 `items`)。负责连发分组编排、
11
+ * 贴底滚动与向上加载历史的接缝。对外 props 照搬 Ant Design X `Bubble.List`(`items` / `roles` /
12
+ * `autoScroll`),加载更多接缝取 Ant `on*` 惯例。
13
+ *
14
+ * 跨-prop 注意事项:
15
+ * - **数据驱动、组件不算业务**:消息内容 / 时间 / 头像 / 已读态全由 `items` 每项自带(就是
16
+ * `ChatMessageProps`);组件只按数组顺序渲染,不排序、不去重、不算时间。
17
+ * - **`roles` 灌角色默认**:按 `variant` 给同类消息灌共享默认(如 received 统一头像),`item`
18
+ * 显式字段优先覆盖 `roles` 默认(照 Ant X `roles`)。
19
+ * - **连发分组自动编排**:相邻同 `variant`(非 system)的续条自动置 `ChatMessage` 的 `grouped`
20
+ * ——头像只在组内首条显示、组内间距收紧(**不合并内容,仍是多个独立气泡**)。`system` 项
21
+ * (如日期分隔)断组。`item` 显式传 `grouped` 时以其为准。
22
+ * - **日期分隔 = `system` 项**:日期分隔线作为一条 `variant="system"` 的 `item` 承载居中灰条,
23
+ * **标签文字(今天 / 昨天 / 某月某日)由 app 放进该项的 `content`,组件不算日期**。
24
+ * - **`autoScroll` 贴底 + 上滚暂停**:`items` 追加时若用户仍在底部则自动滚到底;用户手动上滚
25
+ * 离底即暂停自动贴底、滚回底部恢复。`autoScroll={false}` 整体关闭。
26
+ * - **`onReachTop` 只发信号、组件不 fetch**:滚到顶发一次 `onReachTop`,去哪取历史、怎么
27
+ * prepend 全交 app;`loadingMore` 由 app 控制,为真时顶部显示转圈(复用 `<Loading>`)。组件
28
+ * 不拥有传输(接缝原则同 `ChatMessage`)。
29
+ * - **根节点 `role="log"` + 键盘可聚焦**:消息流作实时日志区(`aria-live="polite"` + 增量播报),
30
+ * 新消息由辅助技术增量读出;容器 `tabIndex={0}` 使纯键盘用户 Tab 进本区后用方向键 /
31
+ * PageUp/Down 翻历史(滚到顶即触 `onReachTop`),聚焦环走 `:focus-visible`(WCAG 2.1.1)。
32
+ */
33
+ export interface ChatListProps {
34
+ /** 消息数据(数据驱动,内部渲染 `ChatMessage`);每项 = `ChatMessageProps` + `id` */
35
+ items: ChatListItem[];
36
+ /** 按 `variant` 的角色默认配置(照 Ant X `roles`);合并进同 variant 的每项,item 显式值优先 */
37
+ roles?: Partial<Record<ChatMessageVariant, Partial<ChatMessageProps>>>;
38
+ /**
39
+ * 新消息自动贴底(照 Ant X `autoScroll`):用户手动上滚时暂停、滚回底部恢复
40
+ * @default true
41
+ */
42
+ autoScroll?: boolean;
43
+ /**
44
+ * 滚到顶时触发(加载更多历史):**组件不 fetch**,去哪拿数据、拿到怎么 prepend 全交 app;
45
+ * 组件只发信号并等 app 回灌 `items`(接缝原则同 `ChatMessage` 不拥有传输)
46
+ */
47
+ onReachTop?: () => void;
48
+ /**
49
+ * 顶部加载中(**由 app 控制**,配合 `onReachTop`):为真在顶部显示转圈(复用 `<Loading>`)
50
+ * @default false
51
+ */
52
+ loadingMore?: boolean;
53
+ /** 自定义类名(挂到根容器) */
54
+ className?: string;
55
+ /** 行内样式(挂到根容器) */
56
+ style?: React.CSSProperties;
57
+ }
58
+ export declare const ChatList: React.FC<ChatListProps>;
@@ -0,0 +1,49 @@
1
+ import { ChatMessage as e } from "../ChatMessage/ChatMessage.js";
2
+ import { Loading as t } from "../Loading/Loading.js";
3
+ import n from "./chat-list.module.js";
4
+ import { useCallback as r, useLayoutEffect as i, useRef as a } from "react";
5
+ import o from "clsx";
6
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
+ //#region packages/react/src/components/ChatList/ChatList.tsx
8
+ var l = 48, u = ({ items: u, roles: d, onReachTop: f, autoScroll: p = !0, loadingMore: m = !1, className: h, style: g }) => {
9
+ let _ = a(null), v = a(!1), y = a(!0), b = r(() => {
10
+ let e = _.current;
11
+ if (e == null) return;
12
+ let t = e.scrollHeight - e.clientHeight > l && e.scrollTop <= l;
13
+ t && !v.current && !m && f?.(), v.current = t;
14
+ let n = e.scrollHeight - e.clientHeight - e.scrollTop;
15
+ y.current = n <= l;
16
+ }, [f, m]);
17
+ return i(() => {
18
+ let e = _.current;
19
+ e == null || !p || !y.current || (e.scrollTop = e.scrollHeight);
20
+ }, [u, p]), /* @__PURE__ */ c("div", {
21
+ className: o(n.list, h),
22
+ style: g,
23
+ ref: _,
24
+ onScroll: b,
25
+ role: "log",
26
+ "aria-live": "polite",
27
+ "aria-relevant": "additions",
28
+ tabIndex: 0,
29
+ children: [m && /* @__PURE__ */ s("div", {
30
+ className: n["top-loader"],
31
+ children: /* @__PURE__ */ s(t, {
32
+ spinning: !0,
33
+ size: "small"
34
+ })
35
+ }), u.map((t, n) => {
36
+ let { id: r, ...i } = t, a = d?.[i.variant ?? "received"], o = a ? {
37
+ ...a,
38
+ ...i
39
+ } : i, c = u[n - 1], l = o.grouped ?? (c != null && c.variant === o.variant && o.variant !== "system");
40
+ return /* @__PURE__ */ s(e, {
41
+ ...o,
42
+ grouped: l
43
+ }, r);
44
+ })]
45
+ });
46
+ };
47
+ u.displayName = "ChatList";
48
+ //#endregion
49
+ export { u as ChatList };
@@ -0,0 +1,7 @@
1
+ //#region packages/react/src/components/ChatList/chat-list.module.less
2
+ var e = "_list_1xrm9_1", t = {
3
+ list: e,
4
+ "top-loader": "_top-loader_1xrm9_13"
5
+ };
6
+ //#endregion
7
+ export { t as default, e as list };
@@ -0,0 +1,2 @@
1
+ export { ChatList } from './ChatList';
2
+ export type { ChatListProps, ChatListItem } from './ChatList';
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ export type ChatMessageVariant = 'sent' | 'received' | 'system';
3
+ export type ChatMessageStatus = 'sent' | 'delivered' | 'read';
4
+ /**
5
+ * 单条聊天气泡:三态 `variant`(sent 右 / received 左 / system 居中灰条)+ `content`
6
+ * 消息体(纯文本或 ReactNode)+ `avatar`(复用 `<Avatar>`)+ `time` 时间元信息 +
7
+ * `status` 已读回执(仅 sent 侧)+ `grouped` 连发分组样式钩子。对外 props 照搬 Ant
8
+ * Design X `Bubble`(`content`/`variant`/`avatar`),已读回执 `status` 取 Ant 命名惯例。
9
+ *
10
+ * 跨-prop 注意事项:
11
+ * - **`variant` 驱动 placement**:sent 靠右、received 靠左、system 居中——不另立
12
+ * `placement` prop(三态已定方位)。system 态无头像、无 status。
13
+ * - **`content` 优先,`children` 兜底**:两者皆可传消息体,`content` 存在时取 `content`。
14
+ * - **纯 emoji 自动大图无气泡**:`content`(或 `children`)为纯 emoji 字符串(≤3 个、无其它
15
+ * 可见字符)时自动渲成大图、去掉气泡底色与内边距(照即时通讯惯例),不需额外 prop。
16
+ * - **`status` 仅 sent 侧出现**:sent → 单勾、delivered → 双勾(均 `--stitch-text-muted`)、
17
+ * read → 双勾 `--stitch-info`;勾走 `<Icon name="check">`(禁 emoji / Unicode `✓` / 裸
18
+ * svg),回执整体挂 `role="img"` + 可访问名(已发送 / 已送达 / 已读),内层 Icon 纯装饰。
19
+ * - **`grouped` 只出样式钩子**:为真时隐藏头像、收紧下间距(相邻同 variant 连发),由上层
20
+ * 编排决定何时置真,组件本身不判断相邻。头像列占位随「该条是否带 `avatar`」:带头像的续条
21
+ * (grouped 隐藏头像但保留列,同串气泡左缘对齐成一列,群聊);无头像的消息不占列、气泡贴边
22
+ * (1:1 私聊首尾条都无头像 → 整串贴边对齐)。要对齐成列 = 给每条都灌头像(列表走 `roles` /
23
+ * 每项 `avatar`),不靠「续条自动占空列」。
24
+ */
25
+ export interface ChatMessageProps {
26
+ /**
27
+ * 气泡三态:sent(本方,右)/ received(对方,左)/ system(系统居中灰条)
28
+ * @default 'received'
29
+ */
30
+ variant?: ChatMessageVariant;
31
+ /** 消息内容(纯文本或 ReactNode);纯 emoji 字符串自动大图无气泡。与 children 二选一(优先 content) */
32
+ content?: React.ReactNode;
33
+ /** 消息内容(content 的兜底写法) */
34
+ children?: React.ReactNode;
35
+ /** 头像(复用 `<Avatar>`);system 态不显示 */
36
+ avatar?: React.ReactNode;
37
+ /** 时间 / 元信息(如发送时间) */
38
+ time?: React.ReactNode;
39
+ /** 已读回执:sent 单勾 / delivered 双勾 / read 双勾高亮;仅 sent 侧渲染 */
40
+ status?: ChatMessageStatus;
41
+ /**
42
+ * 连发分组样式钩子:为真时隐藏头像但保留头像列缩进(气泡对齐成列)、收紧下间距
43
+ * (相邻同 variant 时由上层置真)
44
+ * @default false
45
+ */
46
+ grouped?: boolean;
47
+ /** 自定义类名(挂到根容器) */
48
+ className?: string;
49
+ /** 行内样式(挂到根容器) */
50
+ style?: React.CSSProperties;
51
+ }
52
+ export declare const ChatMessage: React.FC<ChatMessageProps>;