@manohub/ui 0.10.2 → 0.10.4

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 (54) hide show
  1. package/README.md +1 -1
  2. package/dist/components/badge/index.d.ts +46 -1
  3. package/dist/components/badge/index.js +42 -9
  4. package/dist/components/card/index.d.ts +1 -1
  5. package/dist/components/drawer/index.d.ts +1 -1
  6. package/dist/components/filter/index.d.ts +74 -6
  7. package/dist/components/filter/index.js +38 -10
  8. package/dist/components/form/index.d.ts +50 -1
  9. package/dist/components/form/index.js +20 -2
  10. package/dist/components/input/index.js +1 -0
  11. package/dist/components/nav/index.d.ts +27 -0
  12. package/dist/components/nav/index.js +12 -0
  13. package/dist/components/notice/index.d.ts +1 -1
  14. package/dist/components/notification/index.d.ts +2 -2
  15. package/dist/components/select/index.d.ts +9 -0
  16. package/dist/components/select/index.js +57 -12
  17. package/dist/components/select/select-list.d.ts +42 -8
  18. package/dist/components/select/select-list.js +36 -11
  19. package/dist/components/select/select-panel.d.ts +36 -8
  20. package/dist/components/select/select-panel.js +18 -4
  21. package/dist/components/select/select-shared.d.ts +51 -10
  22. package/dist/components/select/select-shared.js +12 -2
  23. package/dist/components/select/select-tree.d.ts +25 -3
  24. package/dist/components/select/select-tree.js +17 -6
  25. package/dist/components/select/select-trigger.d.ts +21 -0
  26. package/dist/components/select/select-trigger.js +15 -1
  27. package/dist/components/tabs/index.d.ts +22 -0
  28. package/dist/components/tabs/index.js +6 -0
  29. package/dist/components/tag/index.d.ts +32 -153
  30. package/dist/components/tag/index.js +102 -101
  31. package/dist/components/text/index.d.ts +1 -1
  32. package/dist/components/tooltip/index.d.ts +1 -1
  33. package/dist/components/tree/index.d.ts +65 -0
  34. package/dist/components/tree/index.js +29 -2
  35. package/dist/index.d.ts +2 -2
  36. package/dist/styles/components/avatar.css +2 -2
  37. package/dist/styles/components/badge.css +21 -1
  38. package/dist/styles/components/badge.tokens.css +24 -0
  39. package/dist/styles/components/button.css +7 -6
  40. package/dist/styles/components/button.tokens.css +5 -2
  41. package/dist/styles/components/capsule.css +8 -0
  42. package/dist/styles/components/divider.css +8 -4
  43. package/dist/styles/components/divider.tokens.css +2 -1
  44. package/dist/styles/components/form.css +51 -0
  45. package/dist/styles/components/notification.css +1 -1
  46. package/dist/styles/components/select.css +62 -39
  47. package/dist/styles/components/select.tokens.css +15 -8
  48. package/dist/styles/components/tag.css +77 -154
  49. package/dist/styles/components/tag.tokens.css +13 -22
  50. package/dist/styles/components/tree.css +37 -3
  51. package/dist/styles/components/tree.tokens.css +4 -3
  52. package/dist/styles/components/upload.tokens.css +1 -1
  53. package/dist/styles/index.css +2 -1
  54. package/package.json +2 -2
@@ -1,69 +1,34 @@
1
- import { defineComponent, computed, createVNode, mergeProps, Fragment, isVNode } from "vue";
1
+ import { defineComponent, computed, ref, watch, createVNode, mergeProps, Fragment } from "vue";
2
2
  import { Icon } from "@manohub/icon";
3
- function _isSlot(s) {
4
- return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
5
- }
3
+ import { Badge } from "../badge/index.js";
4
+ import { Input } from "../input/index.js";
6
5
  const Tag = /* @__PURE__ */ defineComponent({
7
6
  name: "Tag",
8
7
  inheritAttrs: false,
9
8
  props: {
10
9
  shape: {
11
10
  type: String,
12
- default: "regular"
11
+ default: "soft"
13
12
  },
14
13
  tone: {
15
14
  type: String,
16
- default: void 0
15
+ default: "default"
17
16
  },
17
+ /* 缺省 `md`(24)而不是跟随 `Badge` 的 `sm`(20):标签比徽标常用大一号 —— 它是列表项 / 卡片头
18
+ 里承载文字的那一枚,20 档留给「表格内标签」那种密集场景(显式写 `size="sm"`)。 */
18
19
  size: {
19
20
  type: String,
20
21
  default: "md"
21
22
  },
22
- lg: {
23
- type: Boolean,
24
- default: false
25
- },
26
- icon: {
27
- type: String,
28
- default: void 0
29
- },
30
- outline: {
31
- type: Boolean,
32
- default: false
33
- },
34
23
  closable: {
35
24
  type: Boolean,
36
25
  default: false
37
26
  },
38
- color: {
39
- type: String,
40
- default: void 0
41
- },
42
- selected: {
43
- type: Boolean,
44
- default: false
45
- },
46
- disabled: {
47
- type: Boolean,
48
- default: false
49
- },
50
- name: {
51
- type: String,
52
- default: void 0
53
- },
54
- value: {
55
- type: [String, Number],
56
- default: void 0
57
- },
58
- onClick: {
59
- type: Function,
60
- default: void 0
61
- },
62
- onChange: {
27
+ onClose: {
63
28
  type: Function,
64
29
  default: void 0
65
30
  },
66
- onClose: {
31
+ onAdd: {
67
32
  type: Function,
68
33
  default: void 0
69
34
  }
@@ -73,68 +38,104 @@ const Tag = /* @__PURE__ */ defineComponent({
73
38
  slots,
74
39
  attrs
75
40
  }) {
76
- const resolvedSize = computed(() => props.lg ? "lg" : props.size);
77
- const customColor = computed(() => props.color ? {
78
- "--ui-tag-bg": `color-mix(in srgb, ${props.color} 14%, white)`,
79
- color: props.color
80
- } : void 0);
81
- const classes = () => ["mh-tag", resolvedSize.value === "md" ? void 0 : `mh-tag--${resolvedSize.value}`, props.shape !== "regular" ? `mh-tag--${props.shape}` : void 0, props.shape === "status" && props.tone ? `mh-tag--${props.tone}` : void 0, props.outline ? "mh-tag--outline" : void 0, props.shape === "select" && props.selected ? "is-selected" : void 0, props.disabled ? "is-disabled" : void 0, attrs.class].filter(Boolean).join(" ");
82
- const content = () => createVNode(Fragment, null, [props.icon ? createVNode(Icon, {
83
- "name": props.icon,
84
- "class": "mh-tag__icon"
85
- }, null) : null, slots.default?.(), props.closable ? createVNode("button", {
86
- "type": "button",
87
- "class": "mh-tag__close",
88
- "aria-label": "关闭",
89
- "tabindex": -1,
90
- "onClick": (event) => {
91
- event.stopPropagation();
92
- event.preventDefault();
93
- props.onClose?.();
41
+ const isAdd = computed(() => props.shape === "add");
42
+ const baseShape = computed(() => props.shape === "add" ? "plain" : props.shape);
43
+ const editing = ref(false);
44
+ const draft = ref("");
45
+ const editorRef = ref(null);
46
+ const startEdit = () => {
47
+ draft.value = "";
48
+ editing.value = true;
49
+ };
50
+ const stopEdit = () => {
51
+ editing.value = false;
52
+ draft.value = "";
53
+ };
54
+ const submit = () => {
55
+ const name = draft.value.trim();
56
+ if (!name) return;
57
+ props.onAdd?.(name);
58
+ stopEdit();
59
+ };
60
+ watch(editing, (on) => {
61
+ if (!on) return;
62
+ editorRef.value?.querySelector("input")?.focus();
63
+ }, {
64
+ flush: "post"
65
+ });
66
+ const onEditorKeydown = (event) => {
67
+ if (event.key !== "Escape") return;
68
+ event.stopPropagation();
69
+ stopEdit();
70
+ };
71
+ const onEditorFocusout = (event) => {
72
+ const next = event.relatedTarget;
73
+ if (next && editorRef.value?.contains(next)) return;
74
+ stopEdit();
75
+ };
76
+ const forwardAttrs = computed(() => {
77
+ const rest = {};
78
+ for (const [key, value] of Object.entries(attrs)) {
79
+ if (key !== "class" && key !== "style") rest[key] = value;
94
80
  }
95
- }, [createVNode(Icon, {
96
- "name": "close"
97
- }, null)]) : null]);
81
+ if (isAdd.value) {
82
+ const consumer = rest.onClick;
83
+ rest.onClick = (event) => {
84
+ consumer?.(event);
85
+ startEdit();
86
+ };
87
+ }
88
+ return rest;
89
+ });
98
90
  return () => {
99
- let _slot;
100
- const style = {
101
- ...customColor.value,
102
- ...attrs.style
103
- };
104
- if (props.shape === "add") {
105
- return createVNode("button", {
91
+ if (isAdd.value && editing.value) {
92
+ return createVNode("span", {
93
+ "ref": editorRef,
94
+ "class": "mh-tag mh-tag--editing",
95
+ "onKeydown": onEditorKeydown,
96
+ "onFocusout": onEditorFocusout
97
+ }, [createVNode(Input, {
98
+ "class": "mh-tag__input",
99
+ "size": "sm",
100
+ "modelValue": draft.value,
101
+ "placeholder": "输入标签名",
102
+ "aria-label": "标签名",
103
+ "onChange": (value) => draft.value = value,
104
+ "onEnter": () => submit()
105
+ }, null), createVNode("button", {
106
106
  "type": "button",
107
- "class": classes(),
108
- "style": style,
109
- "onClick": props.onClick,
110
- "disabled": props.disabled
107
+ "class": "mh-tag__confirm",
108
+ "aria-label": "添加",
109
+ "tabindex": -1,
110
+ "onMousedown": (event) => event.preventDefault(),
111
+ "onClick": () => submit()
111
112
  }, [createVNode(Icon, {
112
- "name": "plus",
113
- "size": 14
114
- }, null), slots.default?.()]);
115
- }
116
- if (props.shape === "select") {
117
- return createVNode("label", {
118
- "class": classes(),
119
- "style": style
120
- }, [createVNode("input", {
121
- "type": "radio",
122
- "name": props.name,
123
- "value": props.value,
124
- "checked": props.selected,
125
- "disabled": props.disabled,
126
- "onChange": (event) => props.onChange?.(event.target.checked)
127
- }, null), content()]);
113
+ "name": "plus"
114
+ }, null)])]);
128
115
  }
129
- const Clickable = props.shape === "chip" ? "button" : "span";
130
- return createVNode(Clickable, mergeProps({
131
- "class": classes(),
132
- "style": style
133
- }, props.shape === "chip" ? {
134
- type: "button",
135
- onClick: props.onClick
136
- } : {}), _isSlot(_slot = content()) ? _slot : {
137
- default: () => [_slot]
116
+ const extraClass = attrs.class;
117
+ return createVNode(Badge, mergeProps(forwardAttrs.value, {
118
+ "as": isAdd.value ? "button" : "span",
119
+ "tone": props.tone,
120
+ "shape": baseShape.value,
121
+ "size": props.size,
122
+ "class": ["mh-tag", isAdd.value ? "mh-tag--add" : void 0, extraClass].filter(Boolean).join(" "),
123
+ "style": attrs.style
124
+ }), {
125
+ default: () => createVNode(Fragment, null, [isAdd.value ? createVNode(Icon, {
126
+ "name": "plus"
127
+ }, null) : null, slots.default?.(), props.closable && !isAdd.value ? createVNode("button", {
128
+ "type": "button",
129
+ "class": "mh-tag__close",
130
+ "aria-label": "关闭",
131
+ "tabindex": -1,
132
+ "onClick": (event) => {
133
+ event.stopPropagation();
134
+ props.onClose?.();
135
+ }
136
+ }, [createVNode(Icon, {
137
+ "name": "close"
138
+ }, null)]) : null])
138
139
  });
139
140
  };
140
141
  }
@@ -118,9 +118,9 @@ export declare const Text: import("vue").DefineComponent<import("vue").ExtractPr
118
118
  size: TextSize;
119
119
  weight: TextWeight;
120
120
  tone: TextTone;
121
+ as: TextTag;
121
122
  mono: boolean;
122
123
  truncate: number | boolean;
123
- as: TextTag;
124
124
  }, SlotsType<{
125
125
  default?: () => VNodeChild;
126
126
  }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -115,10 +115,10 @@ export declare const Tooltip: import("vue").DefineComponent<import("vue").Extrac
115
115
  title: string;
116
116
  align: TooltipAlign;
117
117
  action: string;
118
- onAction: () => void;
119
118
  side: TooltipSide;
120
119
  trigger: TooltipTrigger;
121
120
  guide: boolean;
121
+ onAction: () => void;
122
122
  }, SlotsType<{
123
123
  default?: () => VNodeChild;
124
124
  content?: () => VNodeChild;
@@ -43,12 +43,25 @@ export interface TreeNode {
43
43
  * 应用侧只给节点;操作容器自己做 `stopPropagation`,所以点「删除」不会顺带把这一行选中。
44
44
  * 导航件(`Nav`)因此不必重写一份行渲染,把数据与 `renderActions` 透传进来即可。
45
45
  *
46
+ * ## 图标出口(两处,都是「位」而不是「样式」)
47
+ *
48
+ * 行内的图标位只有两处,各自独立,缺省都不改变现有几何:
49
+ * - `renderCaret(node, expanded)` 替掉**箭头字形**(槽位 / 点击 / `data-expanded` 仍归组件);
50
+ * - `renderIcon(node)` 是标签**前**的节点图标槽(文件夹 / 文件一类),父子节点共用一位。
51
+ *
52
+ * 不在其中之列的(标题里的图标 + 文字)走 `renderLabel`:那属于标签**本体**。
53
+ *
46
54
  * @example 受控展开 + 计数徽标 + 行尾操作
47
55
  * <Tree nodes={treeNodes.value} rowKey="id" labelKey="name"
48
56
  * selected={selectedId.value} onSelect={(n) => select(n.id)}
49
57
  * expandedKeys={expandedKeys.value} onExpandChange={(k) => (expandedKeys.value = k)}
50
58
  * renderNode={(n) => <Badge>{countOfDomain(n.id)}</Badge>}
51
59
  * renderActions={(n) => <Button title="删除" onClick={() => removeDomain(n.id)} />} />
60
+ *
61
+ * @example 文件夹 / 文件图标 + 方向字形自表达(不靠旋转)
62
+ * <Tree nodes={tree.value} rowKey="id"
63
+ * renderIcon={(n) => <Icon name={hasChild(n) ? 'folder' : 'file-text'} />}
64
+ * renderCaret={(_n, expanded) => <Icon name={expanded ? 'chevron-down' : 'chevron-right'} />} />
52
65
  */
53
66
  export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
54
67
  nodes: {
@@ -92,6 +105,31 @@ export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPr
92
105
  type: PropType<(node: TreeNode) => unknown>;
93
106
  default: undefined;
94
107
  };
108
+ /**
109
+ * 展开 / 收起箭头的自定义渲染(拿到节点与**当前展开态**)。
110
+ *
111
+ * 缺省仍是 `chevron-down` 字形 + 面层收起态转 −90°(默认路径零变化)。
112
+ * 自定义时**朝向由消费方按 `expanded` 自选字形**(`chevron-right` / `chevron-down`)——
113
+ * 与包内「方位一律用字形、不靠旋转别的图标」的口径同源;**不要再给内容加
114
+ * `mh-tree__caret-icon` 类**(那是缺省字形的转向钩子,加上会被转成 −90°)。
115
+ * 尺寸不必自管:槽位与槽内字形都取全局图标档 `--ui-font-icon`。
116
+ */
117
+ renderCaret: {
118
+ type: PropType<(node: TreeNode, expanded: boolean) => unknown>;
119
+ default: undefined;
120
+ };
121
+ /**
122
+ * 标签**前**的节点图标槽(文件夹 / 文件一类)。
123
+ *
124
+ * 父节点与叶子节点**都由消费方按 `node` 判定**(组件不替它猜「是不是叶子」:同一个图标槽
125
+ * 在两种节点上都成立,判定依据是数据而不是渲染期形态)。缺省不渲染该槽,几何零变化。
126
+ * 叶子行的箭头占位仍保留,故图标列跨层级对齐。颜色不写死:跟随行色,
127
+ * 选中行的主色字与滑过态自动同步。
128
+ */
129
+ renderIcon: {
130
+ type: PropType<(node: TreeNode) => unknown>;
131
+ default: undefined;
132
+ };
95
133
  /** 节点附加内容(计数徽标等),渲染在标签之后 */
96
134
  renderNode: {
97
135
  type: PropType<(node: TreeNode) => unknown>;
@@ -225,6 +263,31 @@ export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPr
225
263
  type: PropType<(node: TreeNode) => unknown>;
226
264
  default: undefined;
227
265
  };
266
+ /**
267
+ * 展开 / 收起箭头的自定义渲染(拿到节点与**当前展开态**)。
268
+ *
269
+ * 缺省仍是 `chevron-down` 字形 + 面层收起态转 −90°(默认路径零变化)。
270
+ * 自定义时**朝向由消费方按 `expanded` 自选字形**(`chevron-right` / `chevron-down`)——
271
+ * 与包内「方位一律用字形、不靠旋转别的图标」的口径同源;**不要再给内容加
272
+ * `mh-tree__caret-icon` 类**(那是缺省字形的转向钩子,加上会被转成 −90°)。
273
+ * 尺寸不必自管:槽位与槽内字形都取全局图标档 `--ui-font-icon`。
274
+ */
275
+ renderCaret: {
276
+ type: PropType<(node: TreeNode, expanded: boolean) => unknown>;
277
+ default: undefined;
278
+ };
279
+ /**
280
+ * 标签**前**的节点图标槽(文件夹 / 文件一类)。
281
+ *
282
+ * 父节点与叶子节点**都由消费方按 `node` 判定**(组件不替它猜「是不是叶子」:同一个图标槽
283
+ * 在两种节点上都成立,判定依据是数据而不是渲染期形态)。缺省不渲染该槽,几何零变化。
284
+ * 叶子行的箭头占位仍保留,故图标列跨层级对齐。颜色不写死:跟随行色,
285
+ * 选中行的主色字与滑过态自动同步。
286
+ */
287
+ renderIcon: {
288
+ type: PropType<(node: TreeNode) => unknown>;
289
+ default: undefined;
290
+ };
228
291
  /** 节点附加内容(计数徽标等),渲染在标签之后 */
229
292
  renderNode: {
230
293
  type: PropType<(node: TreeNode) => unknown>;
@@ -331,6 +394,8 @@ export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPr
331
394
  labelKey: string;
332
395
  childrenKey: string;
333
396
  renderLabel: (node: TreeNode) => unknown;
397
+ renderCaret: (node: TreeNode, expanded: boolean) => unknown;
398
+ renderIcon: (node: TreeNode) => unknown;
334
399
  renderNode: (node: TreeNode) => unknown;
335
400
  renderActions: (node: TreeNode) => unknown;
336
401
  expandedKeys: string[];
@@ -46,6 +46,31 @@ const Tree = /* @__PURE__ */ defineComponent({
46
46
  type: Function,
47
47
  default: void 0
48
48
  },
49
+ /**
50
+ * 展开 / 收起箭头的自定义渲染(拿到节点与**当前展开态**)。
51
+ *
52
+ * 缺省仍是 `chevron-down` 字形 + 面层收起态转 −90°(默认路径零变化)。
53
+ * 自定义时**朝向由消费方按 `expanded` 自选字形**(`chevron-right` / `chevron-down`)——
54
+ * 与包内「方位一律用字形、不靠旋转别的图标」的口径同源;**不要再给内容加
55
+ * `mh-tree__caret-icon` 类**(那是缺省字形的转向钩子,加上会被转成 −90°)。
56
+ * 尺寸不必自管:槽位与槽内字形都取全局图标档 `--ui-font-icon`。
57
+ */
58
+ renderCaret: {
59
+ type: Function,
60
+ default: void 0
61
+ },
62
+ /**
63
+ * 标签**前**的节点图标槽(文件夹 / 文件一类)。
64
+ *
65
+ * 父节点与叶子节点**都由消费方按 `node` 判定**(组件不替它猜「是不是叶子」:同一个图标槽
66
+ * 在两种节点上都成立,判定依据是数据而不是渲染期形态)。缺省不渲染该槽,几何零变化。
67
+ * 叶子行的箭头占位仍保留,故图标列跨层级对齐。颜色不写死:跟随行色,
68
+ * 选中行的主色字与滑过态自动同步。
69
+ */
70
+ renderIcon: {
71
+ type: Function,
72
+ default: void 0
73
+ },
49
74
  /** 节点附加内容(计数徽标等),渲染在标签之后 */
50
75
  renderNode: {
51
76
  type: Function,
@@ -237,12 +262,14 @@ const Tree = /* @__PURE__ */ defineComponent({
237
262
  event.stopPropagation();
238
263
  toggleExpand(key);
239
264
  }
240
- }, [createVNode(Icon, {
265
+ }, [props.renderCaret ? props.renderCaret(node, expanded) : createVNode(Icon, {
241
266
  "name": "chevron-down",
242
267
  "class": "mh-tree__caret-icon"
243
268
  }, null)]) : createVNode("span", {
244
269
  "class": "mh-tree__caret mh-tree__caret--empty"
245
- }, null), createVNode("span", {
270
+ }, null), props.renderIcon ? createVNode("span", {
271
+ "class": "mh-tree__icon"
272
+ }, [props.renderIcon(node)]) : null, createVNode("span", {
246
273
  "class": "mh-tree__label"
247
274
  }, [props.renderLabel ? props.renderLabel(node) : String(node[props.labelKey] ?? "")]), props.renderNode ? createVNode("span", {
248
275
  "class": "mh-tree__extra"
package/dist/index.d.ts CHANGED
@@ -26,7 +26,7 @@ export { Layout, type LayoutGap, type LayoutAlign, type LayoutJustify, type Layo
26
26
  export { Avatar, type AvatarProps, type AvatarSize, type AvatarCompound } from './components/avatar';
27
27
  export { Skeleton, type SkeletonProps } from './components/skeleton';
28
28
  export { Radio, type RadioProps, type RadioCompound, type RadioCompoundType, type RadioCardItem, type RadioCardValue, } from './components/radio';
29
- export { Tag, type TagProps } from './components/tag';
29
+ export { Tag, type TagProps, type TagShape } from './components/tag';
30
30
  export { Text, type TextProps, type TextSize, type TextTone, type TextWeight, type TextTag, } from './components/text';
31
31
  export { Tree, type TreeNode } from './components/tree';
32
32
  export { ListView, type ListViewProps, type ListViewStatus, type ListViewStatusTone, type ListViewCardItemProps, type ListViewCompound, } from './components/list-view';
@@ -39,7 +39,7 @@ export { Switch } from './components/switch';
39
39
  export { Capsule, type CapsuleOption, type CapsuleShape } from './components/capsule';
40
40
  export { Form, type FormProps, type FormHeaderProps, type FormItemProps, type FormValues, type FormItemSlotScope, type FormItemAlign, type FormLabelAlign, type FormColumns, type FormPadding, type FormCompound, } from './components/form';
41
41
  export { Checkbox, type CheckboxOption, type CheckboxCompound, type CheckboxCompoundType, } from './components/checkbox';
42
- export { Filter, FilterItem, FILTER_ALL, normalizeFilterValue, type FilterValue, type FilterValues, type FilterProps, type FilterItemProps, type FilterItemSlotScope, type FilterCompound, } from './components/filter';
42
+ export { Filter, FilterItem, FILTER_ALL, normalizeFilterValue, type FilterValue, type FilterValues, type FilterProps, type FilterItemProps, type FilterItemSlotScope, type FilterTrigger, type FilterCompound, } from './components/filter';
43
43
  export { Notice, type NoticeTone } from './components/notice';
44
44
  export { Notification, type NotificationTone, } from './components/notification';
45
45
  export { Dialog, type DialogWidth } from './components/dialog';
@@ -64,9 +64,9 @@
64
64
  .mh-avatar--icon {
65
65
  color: var(--ui-base-100);
66
66
  }
67
- /* 占位:虚线框 + 说明字 */
67
+ /* 占位:虚线框 + 说明字(虚线线色取线型档,口径「实线 base-300 / 虚线 base-400」) */
68
68
  .mh-avatar--placeholder {
69
- border: 1px dashed var(--ui-base-300);
69
+ border: 1px dashed var(--ui-line-dashed);
70
70
  background: var(--ui-base-100);
71
71
  color: var(--ui-base-content-subtle);
72
72
  font-size: var(--ui-font-caption);
@@ -20,6 +20,10 @@
20
20
  box-sizing: border-box;
21
21
  border: 1px solid transparent;
22
22
  font-size: var(--ui-font-caption);
23
+ /* `as="button"`(可点形态,`Tag` 的「新增」用)时根是 `<button>`:UA 会把字形换成 Arial、
24
+ 文字居中、并给 padding/border/background —— 后三样本件已自带(`.mh-badge` 的框与各档内距),
25
+ 只有字族漏在外面,这里补上(对 `<span>` 根无副作用:span 本就继承)。 */
26
+ font-family: inherit;
23
27
  line-height: 1;
24
28
  white-space: nowrap;
25
29
  color: var(--mh-badge-fg);
@@ -30,7 +34,8 @@
30
34
  /* 尺寸档的圆角吃**体量档**(`xs` / `sm`),不吃容器档 `md` —— 容器档一改(卡片圆角调整)
31
35
  徽标会跟着变,那是两件无关的事。20 档有设计实证(表格内标签 r3),24 档无实证、按体量就近升一档。
32
36
  高度取**标记体量档**(全局令牌 `--ui-mark-height-sm/md`):与 `Tag` 的 20 / 24、`Select` 多选
33
- 标签片的 24 同源 —— 这三处以前各写一份字面量,改一次档要改三处(2026-09-30 收敛)。 */
37
+ 标签片的 24 同源 —— 这三处以前各写一份字面量,改一次档要改三处(2026-09-30 收敛)。
38
+ `Tag` 的基础圆角同日也归到这两档(改前它走控件档 6px):两族在 20 / 24 上同高同角。 */
34
39
  .mh-badge--sm {
35
40
  height: var(--ui-mark-height-sm);
36
41
  padding: 0 6px;
@@ -139,11 +144,23 @@
139
144
  --mh-badge-fg: var(--ui-error);
140
145
  }
141
146
 
147
+ /* ===== 前置点 / 状态点:**光学对齐**(下移 1px)=====
148
+ 两族的圆点都被 flex 居中在**盒中心**(实测偏心 0px),但**文字的墨迹中心在盒中心之下**:
149
+ 中文(`带点(填充)`)约 +0.5px、纯小写(`sm` / `md`)约 +2px、状态点那族(14px)约 +1px
150
+ —— 于是盒居中读起来是「点偏高」(2026-09-30 用户反馈,文档站 3× 放大逐档比对确认)。
151
+ 下移 1px 是这几种串的**折中**:中文偏下 0.5px、纯小写偏上 1px。
152
+ 没有再细分的办法 —— 小写字母的墨迹中心本就低于盒中心,任何单一常数都盖不住两种极端。
153
+ 位移走 `top`(绘制层):不参与 flex 布局,点仍被盒居中,只是看起来对着字。
154
+ ⚠️ 位移取值在**值区**(`badge.tokens.css` 的 `--ui-badge-dot-shift`):本件有两个互不嵌套的
155
+ 根类(`.mh-badge` / `.mh-badge--status`),声明在任一**根类**上都到不了另一个根。 */
156
+
142
157
  .mh-badge__dot {
143
158
  width: 8px;
144
159
  height: 8px;
145
160
  flex: none;
146
161
  border-radius: var(--ui-radius-full);
162
+ position: relative;
163
+ top: var(--ui-badge-dot-shift);
147
164
  }
148
165
 
149
166
  .mh-badge--dot-filled .mh-badge__dot {
@@ -233,6 +250,9 @@
233
250
  height: 8px;
234
251
  border-radius: var(--ui-radius-full);
235
252
  background: var(--mh-badge-dot);
253
+ /* 与前置点同一句光学对齐(位移取值见 `__dot` 一节的注释;令牌在 badge.tokens.css) */
254
+ position: relative;
255
+ top: var(--ui-badge-dot-shift);
236
256
  }
237
257
 
238
258
  /* `processing`(映射到 primary)加点呼吸:静态点读不出「正在进行」 */
@@ -0,0 +1,24 @@
1
+ /**
2
+ * badge —— 单件令牌(**基础值**)。
3
+ *
4
+ * 本件自己的几何 / 尺寸 / 色彩槽位;只服务本件,故不随主题进 `@manohub/theme`。
5
+ *
6
+ * ## 抽出说明
7
+ * 本文件从面文件里抽出(原先混写在 `badge.css` 顶部):这些是**基础值**,锚在
8
+ * `[data-manohub-ui]` —— 跨包唯一作用域锚,由入口层无条件写在应用容器上,因此**任何落在
9
+ * 容器里的分件都取得到值**,不再依赖「声明在某个组件根类上」这条脆弱假设
10
+ * (历史症状:跨件消费的 `--ui-*-group-*` 取不到值、组内间距变 0)。
11
+ *
12
+ * 本件为什么需要本文件:`Badge` 有**两个互不嵌套的根类** —— `.mh-badge`(内容徽标 / 数字角标)
13
+ * 与 `.mh-badge--status`(状态点 + 文字,它不带 `.mh-badge`)。前置点与状态点共用同一枚
14
+ * 光学对齐位移,声明在任何**根类**上都到不了另一个根(自定义属性只向下继承),故按本仓口径
15
+ * 落到容器锚上。
16
+ *
17
+ * ⚠️ `.mh-<件>--<档>` 这类**档位改写**留在面文件的修饰符块里 —— 它们靠「同一元素上两个
18
+ * 选择器改写同一个令牌」生效,搬到本文件会让档位失效。
19
+ * ⚠️ 新增组件要登记两处:本文件 + `src/styles/index.css`(见其文件头「五处齐备」)。
20
+ */
21
+ [data-manohub-ui] {
22
+ /* 前置点 / 状态点的**光学对齐**位移(数字见面文件 `__dot` 一节:盒居中读起来偏高)。 */
23
+ --ui-badge-dot-shift: 1px;
24
+ }
@@ -50,12 +50,12 @@
50
50
  transition-timing-function: ease;
51
51
  }
52
52
 
53
- /* 危险色修饰:换槽位即可,六型与各自 hover / active 一并跟随 */
53
+ /* 危险色修饰:换槽位即可,六型与各自 hover / active 一并跟随
54
+ (`--ui-button-accent-soft` 随无消费者的孤令牌一并撤除,2026-09-30) */
54
55
  .mh-button--danger {
55
56
  --ui-button-accent: var(--ui-error);
56
57
  --ui-button-accent-hover: color-mix(in srgb, var(--ui-error) 88%, black);
57
58
  --ui-button-accent-content: var(--ui-error-content);
58
- --ui-button-accent-soft: var(--ui-error-weak);
59
59
  }
60
60
 
61
61
  /* 键盘焦点环:画在按钮自身(与 radio / tag 同一句) */
@@ -166,12 +166,13 @@
166
166
  }
167
167
 
168
168
  /* ===== 虚线:区域内添加内容 =====
169
- * 虚线也是「框」→ 取 `--ui-base-300`(全库唯一的「线 / 框」色,出处 `@manohub/theme` 的
170
- * `default/colors.css` 头「三条口径」第 2 条;`base-400` 只服务勾选类小控件框体)。
171
- * 改前取的是 `base-400`("虚线要深一档才看得见"),2026-09-30 按库级口径收敛。 */
169
+ * 虚线取 `--ui-line-dashed`(= `base-400`,比实线深一档):同样粗细下虚线的墨迹只有实线的一半,
170
+ * 取实线色(`base-300`)会明显偏浅、整条框看着发虚。
171
+ * 这条口径 2026-09-30 一天内翻过一次:早些时候按「线框色只留一套」把虚线也收敛到 `base-300`,
172
+ * 当日晚按实机观感反转 —— 定稿见 `@manohub/theme` 的 `default/colors.css` 头「三条口径」第 2 条。 */
172
173
  .mh-button--dashed {
173
174
  background: var(--ui-base-100);
174
- border: 1px dashed var(--ui-base-300);
175
+ border: 1px dashed var(--ui-line-dashed);
175
176
  color: var(--ui-button-accent);
176
177
  }
177
178
  .mh-button--dashed:hover,
@@ -48,9 +48,12 @@
48
48
  等效「主色 65% 透明叠在页面上」,但**只作用于底** —— 字与环保持纯色(见「加载」节)。
49
49
  嫌淡嫌深都改这一处,不必去动任何一条色彩规则。 */
50
50
  --ui-button-loading-mix: 65%;
51
- /* 色彩槽位:危险色只换这四个变量(设计见文件头) */
51
+ /* 色彩槽位:危险色只换这三个变量(设计见文件头;`danger` 档在 button.css 里成组覆盖)。
52
+ ⚠️ `--ui-button-accent-soft` 已撤除(2026-09-30):本件**没有 soft 型**,这枚令牌从声明起就
53
+ 无人读取,而它取的槽位还是「选中态底」`--ui-primary-subtle`(危险色那半区取的却是浅底档
54
+ `-weak`)—— 无消费者不预置令牌;要浅底档时按档位惯例新开型,别复活它(负向锁见
55
+ `__tests__/contract-style-form.spec.ts` 的 Button 一组)。 */
52
56
  --ui-button-accent: var(--ui-primary);
53
57
  --ui-button-accent-hover: var(--ui-primary-hover);
54
58
  --ui-button-accent-content: var(--ui-primary-content);
55
- --ui-button-accent-soft: var(--ui-primary-subtle); /* 危险的浅底(次级实心档) */
56
59
  }
@@ -19,9 +19,17 @@
19
19
  *
20
20
  * **与 `Tabs` 的判据**:`Tabs` 是操作层的**骨架**(只给根、面板组与收起态,不带面),
21
21
  * 面可由本件或按钮族的分段形态担任;本件是**带自己轨道与选中胶囊的轻量选择控件**。
22
+ *
23
+ * **轨道按内容宽收缩,不被父级拉宽**(缺省档口径的第二半)—— `align-self: flex-start` 就是为这条而写:
24
+ * 父级若是**列向弹性**(典型是 `Tabs` 的根 `.mh-tabs`),交叉轴缺省 `stretch` 会把唯一的子项拉成通栏,
25
+ * 轨道底随之变成一条长胶囊(实测:内容 112px 的轨道被拉到 638px);`TabBar` 同样被拉满,但它规范里
26
+ * 第 1 条就是「不画轨线」,没有底色,肉眼看不出来。要**主动**撑满仍然走 `block` 档 —— 那条
27
+ * `width: 100%` 是显式宽度,`align-self` 管不着它(撑满与否只由 `block` 决定)。
22
28
  */
23
29
  .mh-capsule {
24
30
  display: inline-flex;
31
+ /* 缺省不吃父级交叉轴的拉伸(理由见上);`align-self` 只对弹性子项生效,独立使用无副作用 */
32
+ align-self: flex-start;
25
33
  align-items: center;
26
34
  gap: 0; /* 胶囊相邻无间隙 */
27
35
  min-height: var(--ui-capsule-height);
@@ -4,7 +4,9 @@
4
4
  * 出处:设计稿《MH后台-0911》内容区**用了 44 处** 1px 分隔线,取值统一:
5
5
  * - 水平:`0:2128 > 标题区 > 直线 2` = 1200×1(筛选区与页头带之间);
6
6
  * - 垂直:`0:3920 > 直线 2备份` = 1×708(树栏与主栏之间);
7
- * - 色:`#e6e6e6` = `--ui-base-300`(与页头下边线、表格线、卡片描边同源 —— 本包只有这一条「线」色)。
7
+ * - 色:`#e6e6e6` = `--ui-base-300`(与页头下边线、表格线、卡片描边同源 —— 实线只有这一条「线」色)。
8
+ * 虚线档另按**线型档**走 `--ui-line-dashed`(= `base-400`,见 `@manohub/theme` 的 `default/colors.css`
9
+ * 头「三条口径」第 2 条):同样粗细下虚线的墨迹只有实线的一半,取实线色会整条发虚。
8
10
  *
9
11
  * **为什么单独成件**:这条线在业务里被自绘了 44 次(`border-bottom: 1px solid #e6e6e6` 一类),
10
12
  * 于是「线色改一处漏一处」必然发生。收进一件后,改线色/改粗细只需要动本文件。
@@ -25,8 +27,10 @@
25
27
  width: 100%;
26
28
  border-top: 1px solid var(--ui-divider-line);
27
29
  }
30
+ /* 虚线档**连同线色一起换**:只写 `border-top-style: dashed` 会让虚线仍吃实线色 `base-300`,
31
+ 同一个分隔位上线型不同、深浅也不同(2026-09-30 实测发现 —— 当时只改了「带中段文字」那条路径)。 */
28
32
  .mh-divider--horizontal.mh-divider--dashed {
29
- border-top-style: dashed;
33
+ border-top: 1px dashed var(--ui-line-dashed);
30
34
  }
31
35
 
32
36
  /* 带中段文字:整条改成 flex 行,线由两根 `.mh-divider__line` 承担(左固定 24px、右吃剩余) */
@@ -51,7 +55,7 @@
51
55
  }
52
56
  .mh-divider--dashed .mh-divider__line {
53
57
  height: 0;
54
- border-top: 1px dashed var(--ui-divider-line);
58
+ border-top: 1px dashed var(--ui-line-dashed);
55
59
  }
56
60
  .mh-divider__label {
57
61
  flex: 0 0 auto;
@@ -67,5 +71,5 @@
67
71
  vertical-align: middle;
68
72
  }
69
73
  .mh-divider--vertical.mh-divider--dashed {
70
- border-left-style: dashed;
74
+ border-left: 1px dashed var(--ui-line-dashed);
71
75
  }
@@ -15,7 +15,8 @@
15
15
  */
16
16
  [data-manohub-ui] {
17
17
  /* 单件令牌 */
18
- --ui-divider-line: var(--ui-base-300);
18
+ --ui-divider-line: var(--ui-base-300); /* **实线**档的线色(与卡片描边、表格线同源)——
19
+ 虚线档不用它:虚线走线型档 `--ui-line-dashed`(`base-400`),在面文件的修饰符块里直接取 */
19
20
  --ui-divider-label-gap: var(--ui-space-3); /* 中段文字与两侧线的距离 12 */
20
21
  --ui-divider-label-color: var(--ui-base-content-subtle);
21
22
  }