@morlay/dsh-client-ui-primitives 0.0.7 → 0.0.8
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/README.md +29 -5
- package/dist/client.d.mts +186 -11201
- package/dist/client.mjs +981 -1827
- package/package.json +5 -7
- package/src/client/controls/Bubble.module.css +17 -0
- package/src/client/controls/Bubble.tsx +17 -0
- package/src/client/controls/Button.module.css +10 -0
- package/src/client/controls/Button.tsx +29 -0
- package/src/client/controls/DisclosureRow.module.css +22 -0
- package/src/client/controls/DisclosureRow.tsx +20 -0
- package/src/client/controls/IconButton.module.css +50 -0
- package/src/client/controls/IconButton.tsx +45 -0
- package/src/client/controls/ModelRouteList.module.css +100 -0
- package/src/client/controls/ModelRouteList.tsx +118 -0
- package/src/client/controls/MultilineField.module.css +26 -0
- package/src/client/controls/MultilineField.tsx +19 -0
- package/src/client/controls/SearchInput.module.css +11 -0
- package/src/client/controls/SearchInput.tsx +22 -0
- package/src/client/controls/SearchSelect.module.css +34 -0
- package/src/client/controls/SearchSelect.tsx +138 -0
- package/src/client/controls/SeatButton.module.css +53 -0
- package/src/client/controls/SeatButton.tsx +40 -0
- package/src/client/controls/Spinner.module.css +18 -0
- package/src/client/controls/Spinner.tsx +14 -0
- package/src/client/controls/Tabs.module.css +48 -0
- package/src/client/controls/Tabs.tsx +57 -0
- package/src/client/controls/TagInput.module.css +105 -0
- package/src/client/controls/TagInput.tsx +152 -0
- package/src/client/controls/TimeLabel.module.css +16 -0
- package/src/client/controls/TimeLabel.tsx +15 -0
- package/src/client/controls/VisuallyHidden.module.css +9 -0
- package/src/client/controls/VisuallyHidden.tsx +16 -0
- package/src/client/controls/classes.ts +7 -0
- package/src/client/controls/index.ts +30 -0
- package/src/client/controls/tags.ts +24 -0
- package/src/client/index.ts +10 -2
- package/src/client/layout/Row.tsx +86 -0
- package/src/client/layout/Stack.tsx +79 -0
- package/src/client/layout/Text.tsx +54 -0
- package/src/client/layout/index.ts +16 -0
- package/src/client/layout/layout.module.css +217 -0
- package/src/client/layout/scales.ts +91 -0
- package/src/client/schema-form/Editor.tsx +48 -65
- package/src/client/schema-form/SchemaForm.tsx +4 -4
- package/src/client/schema-form/controller.ts +5 -2
- package/src/client/schema-form/geometry.ts +3 -0
- package/src/client/schema-form/schema-form.module.css +335 -0
- package/src/client/schema-form/value.tsx +10 -9
- package/src/client/settings-form/SettingsFieldRow.module.css +93 -0
- package/src/client/settings-form/SettingsFieldRow.tsx +103 -0
- package/src/client/settings-form/index.ts +5 -18
- package/src/css-modules.d.ts +6 -0
- package/src/client/schema-form/styles.ts +0 -321
- package/src/client/settings-form/SettingsForm.styles.ts +0 -58
- package/src/client/settings-form/SettingsForm.tsx +0 -68
- package/src/client/settings-form/fields.styles.ts +0 -158
- package/src/client/settings-form/fields.tsx +0 -184
- package/src/client/settings-form/form-model.ts +0 -339
- package/src/client/styling/css.ts +0 -27
- package/src/client/styling/focus.ts +0 -9
- package/src/client/styling/index.ts +0 -6
- package/src/client/styling/styled.tsx +0 -54
- package/src/client/styling/styling.ts +0 -142
- package/src/client/styling/token.ts +0 -275
- package/src/client/styling/toolkit.ts +0 -37
- package/src/client/theme.generated.ts +0 -846
- package/src/client/theme.ts +0 -5
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// 拼 class 名:CSS Modules 的映射查表给 `string | undefined`(`exactOptionalPropertyTypes` 下没命中的键是
|
|
2
|
+
// undefined),一个没命中的类名不该在 DOM 上留一个空串。
|
|
3
|
+
export function classes(...names: readonly (string | false | null | undefined)[]): string {
|
|
4
|
+
return names
|
|
5
|
+
.filter((name): name is string => typeof name === "string" && name !== "")
|
|
6
|
+
.join(" ");
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// 设置面通用控件:按钮、图标按钮、选择器、标签输入、多行文本。
|
|
2
|
+
//
|
|
3
|
+
// 官方那套基础组件(`Menu` / `Modal` / `Switch` / …)由本包统一 re-export(见 `../index.ts`),业务包只从这里取控件。
|
|
4
|
+
// 两类"带候选"的控件共用同一套「搜索 + 候选」交互:
|
|
5
|
+
// - **multi-input**(`TagInput`):多值 + 自由输入,聚焦即整体下拉候选、输入即过滤,候选只是加速;
|
|
6
|
+
// - **searchable**(`SearchSelect`):单值,从候选里搜着选。
|
|
7
|
+
|
|
8
|
+
export { Bubble, type BubbleProps } from "./Bubble.tsx";
|
|
9
|
+
export { Button, type ButtonProps, type ButtonTone } from "./Button.tsx";
|
|
10
|
+
export { DisclosureRow, type DisclosureRowProps } from "./DisclosureRow.tsx";
|
|
11
|
+
export { SearchInput, type SearchInputProps, type SearchInputWidth } from "./SearchInput.tsx";
|
|
12
|
+
export { SeatButton, type SeatButtonProps } from "./SeatButton.tsx";
|
|
13
|
+
export { Spinner, type SpinnerProps } from "./Spinner.tsx";
|
|
14
|
+
export { Tabs, type TabItem, type TabsProps } from "./Tabs.tsx";
|
|
15
|
+
export { TimeLabel, type TimeLabelProps } from "./TimeLabel.tsx";
|
|
16
|
+
export { VisuallyHidden, type VisuallyHiddenProps } from "./VisuallyHidden.tsx";
|
|
17
|
+
export { IconButton, type IconButtonProps } from "./IconButton.tsx";
|
|
18
|
+
export {
|
|
19
|
+
ModelRouteList,
|
|
20
|
+
type ModelRouteCandidate,
|
|
21
|
+
type ModelRouteListProps,
|
|
22
|
+
} from "./ModelRouteList.tsx";
|
|
23
|
+
export { MultilineField, type MultilineFieldProps } from "./MultilineField.tsx";
|
|
24
|
+
export {
|
|
25
|
+
SearchSelect,
|
|
26
|
+
type SearchSelectOption,
|
|
27
|
+
type SearchSelectProps,
|
|
28
|
+
} from "./SearchSelect.tsx";
|
|
29
|
+
export { TagInput, type TagInputProps } from "./TagInput.tsx";
|
|
30
|
+
export { mergeTags, parseTagList } from "./tags.ts";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// 名单类字段的文本解析:把粘贴或输入的一串名字折成标签列表。
|
|
2
|
+
|
|
3
|
+
// 分隔符:中英逗号、分号、制表符与换行;去空白、去重、保序。
|
|
4
|
+
export function parseTagList(text: string): string[] {
|
|
5
|
+
const seen = new Set<string>();
|
|
6
|
+
const names: string[] = [];
|
|
7
|
+
for (const raw of text.split(/[,,;;\t\n\r]+/u)) {
|
|
8
|
+
const name = raw.trim();
|
|
9
|
+
if (name === "" || seen.has(name)) continue;
|
|
10
|
+
seen.add(name);
|
|
11
|
+
names.push(name);
|
|
12
|
+
}
|
|
13
|
+
return names;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 把新名字并进现有名单:去重、保序(现有的在前),返回新数组(一次替换就是一次编辑动作)。
|
|
17
|
+
export function mergeTags(current: readonly string[], incoming: readonly string[]): string[] {
|
|
18
|
+
const merged = [...current];
|
|
19
|
+
for (const name of incoming) {
|
|
20
|
+
if (name === "" || merged.includes(name)) continue;
|
|
21
|
+
merged.push(name);
|
|
22
|
+
}
|
|
23
|
+
return merged;
|
|
24
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
2
|
import * as schemaForm from "./schema-form/index.ts";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// 官方那套基础组件(`Button` / `Menu` / `Input` / `Tag` / `Modal` / `Switch` / `SegmentedControl` /
|
|
5
|
+
// `SettingsForm` 与两个字段控件 / `DisclosureRow` / 图标 …)统一从这里转出:业务包只向本包取控件,不必各自去认
|
|
6
|
+
// 官方包名。官方那份**直接用**(同形同名、样式本来就是上游的 CSS Modules),本包只补官方没有的那几件。
|
|
7
|
+
export * from "@deepseek-ai/dsh-client-ui-primitives";
|
|
8
|
+
|
|
6
9
|
export * from "./markdown-labels.ts";
|
|
10
|
+
export * from "./layout/index.ts";
|
|
7
11
|
export * from "./settings-form/index.ts";
|
|
12
|
+
export * from "./controls/index.ts";
|
|
13
|
+
// `Button` 与 `DisclosureRow` 是本包包装版(官方那份 + 文字不换行 / 危险档 / 两条固定的布局类):
|
|
14
|
+
// 两个星号导出之间必须显式点名,否则判成歧义。
|
|
15
|
+
export { Button, DisclosureRow, type ButtonProps, type ButtonTone, type DisclosureRowProps } from "./controls/index.ts";
|
|
8
16
|
// 按行 schema 自动生成的行配置页(原 `@morlay/dsh-client-ui-schema-form/client`,2026-09-28 合并进来):
|
|
9
17
|
// 两者都是"对话 UI / 设置页的基础面",拆成两行会各自注册一遍、还要求每个 bundle 都插齐两行。
|
|
10
18
|
// 只**具名导出**它那一面的门面:`export *` 会与 settings-form 的样式 / token 重名。
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// `Row`:横向行 + 间距档。标题行的「左列文本 + 右侧控件」(`justify="between"`)、按钮排、标签排都用它。
|
|
2
|
+
|
|
3
|
+
import { forwardRef, type ElementType, type HTMLAttributes, type ReactNode } from "react";
|
|
4
|
+
import { classes } from "../controls/classes.ts";
|
|
5
|
+
import css from "./layout.module.css";
|
|
6
|
+
import {
|
|
7
|
+
alignClass,
|
|
8
|
+
gapClass,
|
|
9
|
+
justifyClass,
|
|
10
|
+
padClass,
|
|
11
|
+
type LayoutAlign,
|
|
12
|
+
type LayoutGap,
|
|
13
|
+
type LayoutJustify,
|
|
14
|
+
type LayoutPad,
|
|
15
|
+
} from "./scales.ts";
|
|
16
|
+
|
|
17
|
+
export interface RowProps extends HTMLAttributes<HTMLDivElement> {
|
|
18
|
+
// 渲染成哪个元素(行内摘要用 `span`)。
|
|
19
|
+
as?: ElementType;
|
|
20
|
+
// 子项之间的横向间距。
|
|
21
|
+
gap?: LayoutGap;
|
|
22
|
+
// 交叉轴对齐;缺省 `stretch`。
|
|
23
|
+
align?: LayoutAlign;
|
|
24
|
+
// 主轴分布;缺省从起点排。
|
|
25
|
+
justify?: LayoutJustify;
|
|
26
|
+
// 放不下就换行。
|
|
27
|
+
wrap?: boolean;
|
|
28
|
+
// 撑开:吃掉剩下的宽度并允许收缩。
|
|
29
|
+
grow?: boolean;
|
|
30
|
+
// 卡片底座(边框 + 圆角 + 底色)。
|
|
31
|
+
boxed?: boolean;
|
|
32
|
+
// 不被压缩(页头、动作排这类固定项)。
|
|
33
|
+
fixed?: boolean;
|
|
34
|
+
// 窄列宽度(对话消息那一列)。
|
|
35
|
+
narrow?: boolean;
|
|
36
|
+
// 贴右:把这一项推到行尾。
|
|
37
|
+
push?: boolean;
|
|
38
|
+
// 列表(配 `as="ul"`):去掉列表标记与内距。
|
|
39
|
+
plain?: boolean;
|
|
40
|
+
// 内距档;缺省不留内距。
|
|
41
|
+
pad?: LayoutPad;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const Row = forwardRef<HTMLElement, RowProps>(function Row({
|
|
45
|
+
as: Element = "div",
|
|
46
|
+
gap,
|
|
47
|
+
align,
|
|
48
|
+
justify,
|
|
49
|
+
wrap = false,
|
|
50
|
+
grow = false,
|
|
51
|
+
boxed = false,
|
|
52
|
+
fixed = false,
|
|
53
|
+
narrow = false,
|
|
54
|
+
push = false,
|
|
55
|
+
plain = false,
|
|
56
|
+
pad,
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
...rest
|
|
60
|
+
}, ref): ReactNode {
|
|
61
|
+
return (
|
|
62
|
+
<Element
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={classes(
|
|
65
|
+
css.row,
|
|
66
|
+
gapClass(css, gap),
|
|
67
|
+
alignClass(css, align),
|
|
68
|
+
justifyClass(css, justify),
|
|
69
|
+
padClass(css, pad),
|
|
70
|
+
wrap ? css.wrap : undefined,
|
|
71
|
+
grow ? css.grow : undefined,
|
|
72
|
+
boxed ? css.boxed : undefined,
|
|
73
|
+
fixed ? css.fixed : undefined,
|
|
74
|
+
narrow ? css.narrow : undefined,
|
|
75
|
+
push ? css.push : undefined,
|
|
76
|
+
boxed ? css.boxed : undefined,
|
|
77
|
+
fixed ? css.fixed : undefined,
|
|
78
|
+
plain ? css.plain : undefined,
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
{...rest}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</Element>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// `Stack`:纵向列 + 间距档。设置面上绝大多数区块都是它(页根、分组、字段列),
|
|
2
|
+
// 间距只从档位里选,页面不写数值。
|
|
3
|
+
|
|
4
|
+
import { forwardRef, type ElementType, type HTMLAttributes, type ReactNode } from "react";
|
|
5
|
+
import { classes } from "../controls/classes.ts";
|
|
6
|
+
import css from "./layout.module.css";
|
|
7
|
+
import {
|
|
8
|
+
alignClass,
|
|
9
|
+
gapClass,
|
|
10
|
+
padClass,
|
|
11
|
+
type LayoutAlign,
|
|
12
|
+
type LayoutGap,
|
|
13
|
+
type LayoutPad,
|
|
14
|
+
} from "./scales.ts";
|
|
15
|
+
|
|
16
|
+
export interface StackProps extends HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
// 渲染成哪个元素(区块用 `section`、列表用 `ul`、行内用 `span`)。
|
|
18
|
+
as?: ElementType;
|
|
19
|
+
// 子项之间的纵向间距。
|
|
20
|
+
gap?: LayoutGap;
|
|
21
|
+
// 交叉轴对齐;缺省 `stretch`(子项撑满整列)。
|
|
22
|
+
align?: LayoutAlign;
|
|
23
|
+
// 内距档;缺省不留内距。
|
|
24
|
+
pad?: LayoutPad;
|
|
25
|
+
// 撑开:吃掉剩下的宽度并允许收缩。
|
|
26
|
+
grow?: boolean;
|
|
27
|
+
// 卡片底座(边框 + 圆角 + 底色)。
|
|
28
|
+
boxed?: boolean;
|
|
29
|
+
// 不被压缩(页头、动作排这类固定项)。
|
|
30
|
+
fixed?: boolean;
|
|
31
|
+
// 窄列宽度(对话消息那一列)。
|
|
32
|
+
narrow?: boolean;
|
|
33
|
+
// 占满:页面根(宽高撑满、自己不滚)。
|
|
34
|
+
fill?: boolean;
|
|
35
|
+
// 滚动区:吃掉剩下的高度,内容长了它自己滚。
|
|
36
|
+
scroll?: boolean;
|
|
37
|
+
// 列表(配 `as="ul"`):去掉列表标记与内距。
|
|
38
|
+
plain?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Stack = forwardRef<HTMLElement, StackProps>(function Stack({
|
|
42
|
+
as: Element = "div",
|
|
43
|
+
gap,
|
|
44
|
+
align,
|
|
45
|
+
pad,
|
|
46
|
+
grow = false,
|
|
47
|
+
boxed = false,
|
|
48
|
+
fixed = false,
|
|
49
|
+
narrow = false,
|
|
50
|
+
fill = false,
|
|
51
|
+
scroll = false,
|
|
52
|
+
plain = false,
|
|
53
|
+
className,
|
|
54
|
+
children,
|
|
55
|
+
...rest
|
|
56
|
+
}, ref): ReactNode {
|
|
57
|
+
return (
|
|
58
|
+
<Element
|
|
59
|
+
ref={ref}
|
|
60
|
+
className={classes(
|
|
61
|
+
css.stack,
|
|
62
|
+
gapClass(css, gap),
|
|
63
|
+
alignClass(css, align),
|
|
64
|
+
padClass(css, pad),
|
|
65
|
+
grow ? css.grow : undefined,
|
|
66
|
+
boxed ? css.boxed : undefined,
|
|
67
|
+
fixed ? css.fixed : undefined,
|
|
68
|
+
narrow ? css.narrow : undefined,
|
|
69
|
+
fill ? css.fill : undefined,
|
|
70
|
+
scroll ? css.scroll : undefined,
|
|
71
|
+
plain ? css.plain : undefined,
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
{...rest}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</Element>
|
|
78
|
+
);
|
|
79
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// `Text`:文本档位(字号 / 颜色 / 字重 / 等宽 / 截断)。设置面上的标题、说明、摘要、诊断行都从这里选档,
|
|
2
|
+
// 页面不写字号与颜色。
|
|
3
|
+
|
|
4
|
+
import type { ElementType, HTMLAttributes, ReactNode } from "react";
|
|
5
|
+
import { classes } from "../controls/classes.ts";
|
|
6
|
+
import css from "./layout.module.css";
|
|
7
|
+
import { sizeClass, toneClass, weightClass, type TextSize, type TextTone, type TextWeight } from "./scales.ts";
|
|
8
|
+
|
|
9
|
+
export interface TextProps extends Omit<HTMLAttributes<HTMLElement>, "children" | "color"> {
|
|
10
|
+
// 渲染成哪个元素(标题用 `h4`、说明用 `p`、行内摘要用 `span`)。
|
|
11
|
+
as?: ElementType;
|
|
12
|
+
// 字号档;缺省继承。
|
|
13
|
+
size?: TextSize;
|
|
14
|
+
// 颜色档;缺省继承。
|
|
15
|
+
tone?: TextTone;
|
|
16
|
+
// 字重档(区块标题那一档)。
|
|
17
|
+
weight?: TextWeight;
|
|
18
|
+
// 等宽(诊断行、配置值那类文本)。
|
|
19
|
+
mono?: boolean;
|
|
20
|
+
// 一行放不下就省略号截断。
|
|
21
|
+
truncate?: boolean;
|
|
22
|
+
// 数字等宽(计数、分页这类会跳动的数值)。
|
|
23
|
+
tabular?: boolean;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Text({
|
|
28
|
+
as: Element = "span",
|
|
29
|
+
size,
|
|
30
|
+
tone,
|
|
31
|
+
weight,
|
|
32
|
+
mono = false,
|
|
33
|
+
truncate = false,
|
|
34
|
+
tabular = false,
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
}: TextProps): ReactNode {
|
|
38
|
+
return (
|
|
39
|
+
<Element
|
|
40
|
+
className={classes(
|
|
41
|
+
css.text,
|
|
42
|
+
sizeClass(css, size),
|
|
43
|
+
toneClass(css, tone),
|
|
44
|
+
weightClass(css, weight),
|
|
45
|
+
mono ? css.mono : undefined,
|
|
46
|
+
truncate ? css.truncate : undefined,
|
|
47
|
+
tabular ? css.tabular : undefined,
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</Element>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// 布局原语:业务页面只摆结构与选档,不写样式。
|
|
2
|
+
//
|
|
3
|
+
// 通用原语(`Stack` / `Row` / `Text` / `Panel`)覆盖设置面的绝大多数排法;成型的那几件(字段行
|
|
4
|
+
// `SettingsFieldRow`、标签输入 `TagInput` 等)住在 `../controls` 与 `../settings-form`。
|
|
5
|
+
|
|
6
|
+
export { Row, type RowProps } from "./Row.tsx";
|
|
7
|
+
export { Stack, type StackProps } from "./Stack.tsx";
|
|
8
|
+
export { Text, type TextProps } from "./Text.tsx";
|
|
9
|
+
export type {
|
|
10
|
+
LayoutAlign,
|
|
11
|
+
LayoutGap,
|
|
12
|
+
LayoutJustify,
|
|
13
|
+
LayoutPad,
|
|
14
|
+
TextSize,
|
|
15
|
+
TextTone,
|
|
16
|
+
} from "./scales.ts";
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/* 布局原语共用的档位表:几何只有一套(间距 / 内距 / 文本档位),四个原语都从这份里取类名。
|
|
2
|
+
数值照官方设置面的尺度:2/4/6/8/12/14/16 的间距档、11/12/13/16 的字号档。 */
|
|
3
|
+
|
|
4
|
+
/* 纵向列与横向行的底座。 */
|
|
5
|
+
.stack {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
min-width: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.row {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: row;
|
|
14
|
+
align-items: center;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* 间距档。 */
|
|
19
|
+
.gap0 {
|
|
20
|
+
gap: 0;
|
|
21
|
+
}
|
|
22
|
+
.gap2 {
|
|
23
|
+
gap: 2px;
|
|
24
|
+
}
|
|
25
|
+
.gap4 {
|
|
26
|
+
gap: 4px;
|
|
27
|
+
}
|
|
28
|
+
.gap6 {
|
|
29
|
+
gap: 6px;
|
|
30
|
+
}
|
|
31
|
+
.gap8 {
|
|
32
|
+
gap: 8px;
|
|
33
|
+
}
|
|
34
|
+
.gap10 {
|
|
35
|
+
gap: 10px;
|
|
36
|
+
}
|
|
37
|
+
.gap12 {
|
|
38
|
+
gap: 12px;
|
|
39
|
+
}
|
|
40
|
+
.gap14 {
|
|
41
|
+
gap: 14px;
|
|
42
|
+
}
|
|
43
|
+
.gap16 {
|
|
44
|
+
gap: 16px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* 交叉轴对齐:不写就是 `stretch`(列里子项撑满,行里子项等高)。 */
|
|
48
|
+
.alignStart {
|
|
49
|
+
align-items: flex-start;
|
|
50
|
+
}
|
|
51
|
+
.alignCenter {
|
|
52
|
+
align-items: center;
|
|
53
|
+
}
|
|
54
|
+
.alignEnd {
|
|
55
|
+
align-items: flex-end;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* 主轴分布:`between` 是「左列文本 + 右侧控件」那一行,`end` 是分页那种贴右。 */
|
|
59
|
+
.justifyBetween {
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
}
|
|
62
|
+
.justifyEnd {
|
|
63
|
+
justify-content: flex-end;
|
|
64
|
+
}
|
|
65
|
+
.justifyCenter {
|
|
66
|
+
justify-content: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* 一行放不下就换行(标签与按钮排)。 */
|
|
70
|
+
.wrap {
|
|
71
|
+
flex-wrap: wrap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* 撑开:吃掉剩下的宽度并允许收缩(长文本因此不会把整行撑出去)。 */
|
|
75
|
+
.grow {
|
|
76
|
+
flex: 1 1 auto;
|
|
77
|
+
min-width: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* 贴右:把这一项推到行尾。 */
|
|
81
|
+
.push {
|
|
82
|
+
margin-left: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* 占满:整块页面的根(宽高都撑满,自己不滚——滚动交给里面的 `scroll` 那一层)。 */
|
|
86
|
+
.fill {
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
width: 100%;
|
|
89
|
+
height: 100%;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* 滚动区:吃掉剩下的高度,内容长了它自己滚。 */
|
|
94
|
+
.scroll {
|
|
95
|
+
flex: 1 1 auto;
|
|
96
|
+
min-height: 0;
|
|
97
|
+
overflow-y: auto;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* 列表(`as="ul"` 时):去掉列表标记与内距。 */
|
|
101
|
+
.plain {
|
|
102
|
+
margin: 0;
|
|
103
|
+
padding: 0;
|
|
104
|
+
list-style: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* 内距档:`card` 是模式卡片那档,`body` 是卡片内容那档,`page` / `row` / `blocking` 见注释。
|
|
108
|
+
上下必须对称——不对称的内距会让里面的行看着不在卡片中线(旧实现里的 4/8 与 8/4 就是这么偏的)。 */
|
|
109
|
+
.padNone {
|
|
110
|
+
padding: 0;
|
|
111
|
+
}
|
|
112
|
+
.padCard {
|
|
113
|
+
padding: 8px 12px;
|
|
114
|
+
}
|
|
115
|
+
.padBody {
|
|
116
|
+
padding: 8px 0;
|
|
117
|
+
}
|
|
118
|
+
/* 页面根那一档。 */
|
|
119
|
+
.padPage {
|
|
120
|
+
padding: 24px 28px;
|
|
121
|
+
}
|
|
122
|
+
/* 列表里的一行卡片。 */
|
|
123
|
+
.padRow {
|
|
124
|
+
padding: 10px 12px;
|
|
125
|
+
}
|
|
126
|
+
/* 居中的提示块(加载中 / 阻塞提示)。 */
|
|
127
|
+
.padBlocking {
|
|
128
|
+
padding: 32px 40px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* 文本档:字号 / 行高 / 颜色 / 字重 / 等宽 / 截断 / 等宽数字。 */
|
|
132
|
+
.text {
|
|
133
|
+
margin: 0;
|
|
134
|
+
}
|
|
135
|
+
.sizeXs {
|
|
136
|
+
font-size: 11px;
|
|
137
|
+
line-height: 1.5;
|
|
138
|
+
}
|
|
139
|
+
.sizeSm {
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
line-height: 18px;
|
|
142
|
+
}
|
|
143
|
+
.sizeMd {
|
|
144
|
+
font-size: 13px;
|
|
145
|
+
line-height: 20px;
|
|
146
|
+
}
|
|
147
|
+
.sizeBase {
|
|
148
|
+
font-size: 14px;
|
|
149
|
+
line-height: 24px;
|
|
150
|
+
}
|
|
151
|
+
.sizeLg {
|
|
152
|
+
font-size: 16px;
|
|
153
|
+
line-height: 24px;
|
|
154
|
+
}
|
|
155
|
+
.tonePrimary {
|
|
156
|
+
color: var(--dsw-alias-label-primary);
|
|
157
|
+
}
|
|
158
|
+
.toneSecondary {
|
|
159
|
+
color: var(--dsw-alias-label-secondary);
|
|
160
|
+
}
|
|
161
|
+
.toneTertiary {
|
|
162
|
+
color: var(--dsw-alias-label-tertiary);
|
|
163
|
+
}
|
|
164
|
+
/* 失败提示那一档:官方主题里有就用它,没有就退回主文本色。 */
|
|
165
|
+
.toneDanger {
|
|
166
|
+
color: var(--dsw-alias-label-error, var(--dsw-alias-label-primary));
|
|
167
|
+
}
|
|
168
|
+
.weightMedium {
|
|
169
|
+
font-weight: 500;
|
|
170
|
+
}
|
|
171
|
+
.strong {
|
|
172
|
+
font-weight: 600;
|
|
173
|
+
}
|
|
174
|
+
.mono {
|
|
175
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
176
|
+
word-break: break-all;
|
|
177
|
+
}
|
|
178
|
+
.truncate {
|
|
179
|
+
overflow: hidden;
|
|
180
|
+
text-overflow: ellipsis;
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
}
|
|
183
|
+
.tabular {
|
|
184
|
+
font-variant-numeric: tabular-nums;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* 卡片底座:边框 + 圆角 + 底色(列表里的一行、用量块)。内距由 `pad` 档给。 */
|
|
188
|
+
.boxed {
|
|
189
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
190
|
+
border-radius: 10px;
|
|
191
|
+
background: var(--dsw-alias-bg-base);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* 不被压缩:页头、动作排这类固定项(内容变长时它们不该被压成 min-content)。 */
|
|
195
|
+
.fixed {
|
|
196
|
+
flex: none;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* 两轴不同间距的一排(统计项:列间距 20,换行后的行间距 6)。 */
|
|
200
|
+
.gapWide {
|
|
201
|
+
column-gap: 20px;
|
|
202
|
+
row-gap: 6px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* 窄列:对话消息那一列。上游以宽度轴的比例表达 Figma 那个 525px 上限(525/748 ≈ 0.702),
|
|
206
|
+
748px 的兜底覆盖"挂在会话列之外"的挂载(此时轴变量不在)。 */
|
|
207
|
+
.narrow {
|
|
208
|
+
min-width: 0;
|
|
209
|
+
max-width: min(calc(var(--dsh-chat-content-width, 748px) * 0.702), 82%);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* 搜索框那一档宽度(列表与设置面的搜索行)。 */
|
|
213
|
+
.wSearch {
|
|
214
|
+
width: 360px;
|
|
215
|
+
max-width: 100%;
|
|
216
|
+
flex: none;
|
|
217
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// 布局原语的档位:几何只有这一套,四个原语与消费方都从这里取,页面不写数值。
|
|
2
|
+
|
|
3
|
+
// 间距档(px):2/4/6/8 是行内与字段内的尺度,12/14/16 是区块之间的尺度。
|
|
4
|
+
export type LayoutGap = 0 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | "wide";
|
|
5
|
+
|
|
6
|
+
// 交叉轴对齐:`stretch`(缺省)让列里子项撑满、行里子项等高。
|
|
7
|
+
export type LayoutAlign = "stretch" | "start" | "center" | "end";
|
|
8
|
+
|
|
9
|
+
// 主轴分布:`between` 用于"左列文本 + 右侧控件"那一行,`end` 用于贴右(分页)。
|
|
10
|
+
export type LayoutJustify = "start" | "center" | "between" | "end";
|
|
11
|
+
|
|
12
|
+
// 内距档:`card` = 模式卡片那档,`body` = 卡片内容那档,`page` / `row` / `blocking` 见样式表。
|
|
13
|
+
export type LayoutPad = "none" | "card" | "body" | "page" | "row" | "blocking";
|
|
14
|
+
|
|
15
|
+
// 文本字号档:11 / 12 / 13 / 16px。
|
|
16
|
+
export type TextSize = "xs" | "sm" | "md" | "base" | "lg";
|
|
17
|
+
|
|
18
|
+
// 文本颜色档。
|
|
19
|
+
export type TextTone = "primary" | "secondary" | "tertiary" | "danger";
|
|
20
|
+
|
|
21
|
+
// 文本字重档。
|
|
22
|
+
export type TextWeight = "medium" | "strong";
|
|
23
|
+
|
|
24
|
+
// 档位 → 类名。CSS Modules 的映射查表可能给 undefined(exactOptionalPropertyTypes),交给 `classes` 过滤。
|
|
25
|
+
export function gapClass(
|
|
26
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
27
|
+
gap?: LayoutGap,
|
|
28
|
+
): string | undefined {
|
|
29
|
+
if (gap === undefined) return undefined;
|
|
30
|
+
// `wide` 是两轴不同间距的那一档(见样式表的 `.gapWide`)。
|
|
31
|
+
return gap === "wide" ? css.gapWide : css[`gap${gap}`];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function alignClass(
|
|
35
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
36
|
+
align?: LayoutAlign,
|
|
37
|
+
): string | undefined {
|
|
38
|
+
if (align === undefined || align === "stretch") return undefined;
|
|
39
|
+
if (align === "start") return css.alignStart;
|
|
40
|
+
return align === "center" ? css.alignCenter : css.alignEnd;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function justifyClass(
|
|
44
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
45
|
+
justify?: LayoutJustify,
|
|
46
|
+
): string | undefined {
|
|
47
|
+
if (justify === undefined) return undefined;
|
|
48
|
+
if (justify === "between") return css.justifyBetween;
|
|
49
|
+
if (justify === "end") return css.justifyEnd;
|
|
50
|
+
return justify === "center" ? css.justifyCenter : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function padClass(
|
|
54
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
55
|
+
pad?: LayoutPad,
|
|
56
|
+
): string | undefined {
|
|
57
|
+
if (pad === undefined || pad === "none") return undefined;
|
|
58
|
+
if (pad === "card") return css.padCard;
|
|
59
|
+
if (pad === "body") return css.padBody;
|
|
60
|
+
if (pad === "page") return css.padPage;
|
|
61
|
+
return pad === "row" ? css.padRow : css.padBlocking;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sizeClass(
|
|
65
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
66
|
+
size?: TextSize,
|
|
67
|
+
): string | undefined {
|
|
68
|
+
if (size === undefined) return undefined;
|
|
69
|
+
if (size === "xs") return css.sizeXs;
|
|
70
|
+
if (size === "sm") return css.sizeSm;
|
|
71
|
+
if (size === "md") return css.sizeMd;
|
|
72
|
+
return size === "base" ? css.sizeBase : css.sizeLg;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function toneClass(
|
|
76
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
77
|
+
tone?: TextTone,
|
|
78
|
+
): string | undefined {
|
|
79
|
+
if (tone === undefined) return undefined;
|
|
80
|
+
if (tone === "primary") return css.tonePrimary;
|
|
81
|
+
if (tone === "secondary") return css.toneSecondary;
|
|
82
|
+
return tone === "tertiary" ? css.toneTertiary : css.toneDanger;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function weightClass(
|
|
86
|
+
css: Readonly<Record<string, string | undefined>>,
|
|
87
|
+
weight?: TextWeight,
|
|
88
|
+
): string | undefined {
|
|
89
|
+
if (weight === undefined) return undefined;
|
|
90
|
+
return weight === "medium" ? css.weightMedium : css.strong;
|
|
91
|
+
}
|