@manohub/ui 0.6.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 (169) hide show
  1. package/README.md +141 -0
  2. package/dist/components/avatar/index.d.ts +283 -0
  3. package/dist/components/avatar/index.js +167 -0
  4. package/dist/components/badge/index.d.ts +132 -0
  5. package/dist/components/badge/index.js +99 -0
  6. package/dist/components/button/index.d.ts +578 -0
  7. package/dist/components/button/index.js +191 -0
  8. package/dist/components/capsule/index.d.ts +161 -0
  9. package/dist/components/capsule/index.js +101 -0
  10. package/dist/components/card/index.d.ts +456 -0
  11. package/dist/components/card/index.js +231 -0
  12. package/dist/components/checkbox/index.d.ts +331 -0
  13. package/dist/components/checkbox/index.js +165 -0
  14. package/dist/components/code/index.d.ts +134 -0
  15. package/dist/components/code/index.js +117 -0
  16. package/dist/components/code-editor/index.d.ts +163 -0
  17. package/dist/components/code-editor/index.js +98 -0
  18. package/dist/components/collapse/index.d.ts +125 -0
  19. package/dist/components/collapse/index.js +95 -0
  20. package/dist/components/control-size.d.ts +8 -0
  21. package/dist/components/dialog/index.d.ts +224 -0
  22. package/dist/components/dialog/index.js +194 -0
  23. package/dist/components/divider/index.d.ts +64 -0
  24. package/dist/components/divider/index.js +62 -0
  25. package/dist/components/drawer/index.d.ts +226 -0
  26. package/dist/components/drawer/index.js +190 -0
  27. package/dist/components/filter/index.d.ts +391 -0
  28. package/dist/components/filter/index.js +203 -0
  29. package/dist/components/form/index.d.ts +704 -0
  30. package/dist/components/form/index.js +329 -0
  31. package/dist/components/input/index.d.ts +980 -0
  32. package/dist/components/input/index.js +334 -0
  33. package/dist/components/layout/index.d.ts +162 -0
  34. package/dist/components/layout/index.js +100 -0
  35. package/dist/components/list-view/index.d.ts +563 -0
  36. package/dist/components/list-view/index.js +221 -0
  37. package/dist/components/loading/index.d.ts +77 -0
  38. package/dist/components/loading/index.js +40 -0
  39. package/dist/components/nav/index.d.ts +293 -0
  40. package/dist/components/nav/index.js +164 -0
  41. package/dist/components/notice/index.d.ts +137 -0
  42. package/dist/components/notice/index.js +101 -0
  43. package/dist/components/notification/index.d.ts +118 -0
  44. package/dist/components/notification/index.js +99 -0
  45. package/dist/components/page/index.d.ts +276 -0
  46. package/dist/components/page/index.js +351 -0
  47. package/dist/components/page/split-resize.d.ts +42 -0
  48. package/dist/components/page/split-resize.js +36 -0
  49. package/dist/components/pagination/index.d.ts +171 -0
  50. package/dist/components/pagination/index.js +226 -0
  51. package/dist/components/panel/index.d.ts +401 -0
  52. package/dist/components/panel/index.js +193 -0
  53. package/dist/components/part-padding.d.ts +27 -0
  54. package/dist/components/part-padding.js +23 -0
  55. package/dist/components/query-state/index.d.ts +207 -0
  56. package/dist/components/query-state/index.js +181 -0
  57. package/dist/components/radio/index.d.ts +473 -0
  58. package/dist/components/radio/index.js +250 -0
  59. package/dist/components/search/index.d.ts +167 -0
  60. package/dist/components/search/index.js +105 -0
  61. package/dist/components/select/index.d.ts +252 -0
  62. package/dist/components/select/index.js +281 -0
  63. package/dist/components/skeleton/index.d.ts +88 -0
  64. package/dist/components/skeleton/index.js +56 -0
  65. package/dist/components/steps/index.d.ts +199 -0
  66. package/dist/components/steps/index.js +117 -0
  67. package/dist/components/switch/index.d.ts +150 -0
  68. package/dist/components/switch/index.js +103 -0
  69. package/dist/components/table/index.d.ts +351 -0
  70. package/dist/components/table/index.js +286 -0
  71. package/dist/components/tabs/index.d.ts +179 -0
  72. package/dist/components/tabs/index.js +153 -0
  73. package/dist/components/tabset/index.d.ts +105 -0
  74. package/dist/components/tabset/index.js +68 -0
  75. package/dist/components/tag/index.d.ts +203 -0
  76. package/dist/components/tag/index.js +144 -0
  77. package/dist/components/toast/index.d.ts +271 -0
  78. package/dist/components/toast/index.js +116 -0
  79. package/dist/components/tooltip/index.d.ts +126 -0
  80. package/dist/components/tooltip/index.js +97 -0
  81. package/dist/components/tree/index.d.ts +317 -0
  82. package/dist/components/tree/index.js +251 -0
  83. package/dist/components/upload/index.d.ts +384 -0
  84. package/dist/components/upload/index.js +172 -0
  85. package/dist/index.d.ts +63 -0
  86. package/dist/index.js +102 -0
  87. package/dist/services/base.d.ts +21 -0
  88. package/dist/services/base.js +42 -0
  89. package/dist/services/dialog.d.ts +41 -0
  90. package/dist/services/dialog.js +83 -0
  91. package/dist/services/index.d.ts +12 -0
  92. package/dist/services/loading.d.ts +21 -0
  93. package/dist/services/loading.js +41 -0
  94. package/dist/services/toast.d.ts +40 -0
  95. package/dist/services/toast.js +74 -0
  96. package/dist/styles/components/avatar.css +128 -0
  97. package/dist/styles/components/avatar.tokens.css +24 -0
  98. package/dist/styles/components/badge.css +237 -0
  99. package/dist/styles/components/button.css +454 -0
  100. package/dist/styles/components/button.tokens.css +56 -0
  101. package/dist/styles/components/capsule.css +120 -0
  102. package/dist/styles/components/capsule.tokens.css +27 -0
  103. package/dist/styles/components/card.css +192 -0
  104. package/dist/styles/components/checkbox.css +145 -0
  105. package/dist/styles/components/checkbox.tokens.css +26 -0
  106. package/dist/styles/components/code-editor.css +91 -0
  107. package/dist/styles/components/code-editor.tokens.css +24 -0
  108. package/dist/styles/components/code.css +153 -0
  109. package/dist/styles/components/code.tokens.css +29 -0
  110. package/dist/styles/components/collapse.css +62 -0
  111. package/dist/styles/components/collapse.tokens.css +20 -0
  112. package/dist/styles/components/dialog.css +106 -0
  113. package/dist/styles/components/dialog.tokens.css +24 -0
  114. package/dist/styles/components/divider.css +71 -0
  115. package/dist/styles/components/divider.tokens.css +21 -0
  116. package/dist/styles/components/drawer.css +143 -0
  117. package/dist/styles/components/drawer.tokens.css +22 -0
  118. package/dist/styles/components/filter.css +41 -0
  119. package/dist/styles/components/filter.tokens.css +14 -0
  120. package/dist/styles/components/form.css +220 -0
  121. package/dist/styles/components/form.tokens.css +15 -0
  122. package/dist/styles/components/input.css +445 -0
  123. package/dist/styles/components/input.tokens.css +41 -0
  124. package/dist/styles/components/layout.css +198 -0
  125. package/dist/styles/components/list-view.css +244 -0
  126. package/dist/styles/components/loading.css +76 -0
  127. package/dist/styles/components/loading.tokens.css +23 -0
  128. package/dist/styles/components/nav.css +48 -0
  129. package/dist/styles/components/notice.css +153 -0
  130. package/dist/styles/components/notice.tokens.css +17 -0
  131. package/dist/styles/components/notification.css +159 -0
  132. package/dist/styles/components/notification.tokens.css +37 -0
  133. package/dist/styles/components/page.css +473 -0
  134. package/dist/styles/components/pagination.css +297 -0
  135. package/dist/styles/components/pagination.tokens.css +32 -0
  136. package/dist/styles/components/panel.css +77 -0
  137. package/dist/styles/components/query-state.css +88 -0
  138. package/dist/styles/components/radio.css +271 -0
  139. package/dist/styles/components/radio.tokens.css +26 -0
  140. package/dist/styles/components/search.css +30 -0
  141. package/dist/styles/components/select.css +242 -0
  142. package/dist/styles/components/select.tokens.css +33 -0
  143. package/dist/styles/components/skeleton.css +60 -0
  144. package/dist/styles/components/skeleton.tokens.css +27 -0
  145. package/dist/styles/components/steps.css +157 -0
  146. package/dist/styles/components/steps.tokens.css +42 -0
  147. package/dist/styles/components/switch.css +165 -0
  148. package/dist/styles/components/switch.tokens.css +26 -0
  149. package/dist/styles/components/table.css +439 -0
  150. package/dist/styles/components/table.tokens.css +41 -0
  151. package/dist/styles/components/tabs.css +234 -0
  152. package/dist/styles/components/tabs.tokens.css +30 -0
  153. package/dist/styles/components/tag.css +200 -0
  154. package/dist/styles/components/tag.tokens.css +30 -0
  155. package/dist/styles/components/toast.css +150 -0
  156. package/dist/styles/components/toast.tokens.css +26 -0
  157. package/dist/styles/components/tooltip.css +89 -0
  158. package/dist/styles/components/tooltip.tokens.css +25 -0
  159. package/dist/styles/components/tree.css +139 -0
  160. package/dist/styles/components/tree.tokens.css +20 -0
  161. package/dist/styles/components/upload.css +150 -0
  162. package/dist/styles/components/upload.tokens.css +27 -0
  163. package/dist/styles/index.css +149 -0
  164. package/dist/styles/operations/collapse.css +84 -0
  165. package/dist/styles/operations/popup.css +189 -0
  166. package/dist/styles/operations/tabset.css +57 -0
  167. package/dist/utils/state-illustration.d.ts +31 -0
  168. package/dist/utils/state-illustration.js +156 -0
  169. package/package.json +62 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Form + Form.Item(自建表单:label 列 + 控件列 + 错误 / 说明)。
3
+ *
4
+ * 依据:`@aihub/theme` 的 `components/field.css`(标签色与列宽 / 必填星标 / 说明与报错)与
5
+ * 设计稿《MH后台-0911》的表单(`02.1` 高级筛选区:标签 13px、必填星号 `#ff1922`、
6
+ * 标签与控件间距 10)。**必填星号取 `--ui-required-mark`(专用色),不复用 `--ui-error`**。
7
+ */
8
+
9
+ /* ===== Form(自建表单:label + 控件 + 错误 + 说明)=====
10
+ 沿 farris 表单的既有约定:label **右对齐**、必填星号在 label 文本**左侧**。
11
+ 与 farris 动态表单的两处有意差异:① 控件撑满剩余宽度(按需用 controlWidth 收敛),
12
+ 不受 farris「字段 max-width 26.625rem」约束;② 错误与说明走**文档流**排在控件下方
13
+ (farris 用绝对定位的浮层提示条,在弹窗里会压住下一行控件)。
14
+
15
+ label 列宽只由变量驱动:Form / Form.Item 落 `--mh-form-label-width`,默认取令牌,
16
+ 故换宽窄不需要在消费方写 CSS。 */
17
+
18
+ /* 行距取 8px 档:Form.Item 自己已经是一个完整行(label 与控件等高 + 错误/说明按文档流往下排),
19
+ 再用 16px 行距会把表单拉得很散 —— 尤其是步骤⑤那种「只读文本行」的摘要,行与行之间会显得发虚。
20
+ 要更松的排版属于消费方场景,改这里之前先确认不是想把「行距」当成区块间距在用。 */
21
+
22
+ .mh-form {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--ui-space-2);
26
+ }
27
+
28
+ /* 两列(Form columns=2):`auto-fit` + 列最小宽度 ⇒ 容器够宽才并排、窄了**自动回落一列**
29
+ (不需要媒体查询,也不需要容器查询)。`align-items: start` 让同一行的两个字段各自顶部对齐,
30
+ 不被隔壁那个「错误 + 多行说明」撑高的格子拉扯。 */
31
+
32
+ .mh-form--two-column {
33
+ display: grid;
34
+ /* 上限 2 列 + 最小列宽:track 下限取「最小列宽」与「按 2 列等分」的较大者
35
+ —— 否则宽表单里 auto-fit 会一路排到 4 列(实测 1400px 得 4 列),
36
+ 而 `columns={2}` 承诺的是"最多 2 列、窄了回落"(与 Layout.Row 同一公式)。 */
37
+ grid-template-columns: repeat(
38
+ auto-fit,
39
+ minmax(
40
+ max(var(--ui-form-column-min), calc((100% - var(--ui-space-2)) / 2)),
41
+ 1fr
42
+ )
43
+ );
44
+ align-items: start;
45
+ }
46
+
47
+ /* 跨满整行:说明行 / 卡片组 / 表格 / 文本域这类整块内容(单列时 grid-column 无效,无副作用) */
48
+
49
+ .mh-form-item--full {
50
+ grid-column: 1 / -1;
51
+ }
52
+
53
+ .mh-form-item {
54
+ /* label 列宽:Item 行内下发 --mh-form-label-width;未传时取令牌 */
55
+ --mh-form-label-width: var(--ui-form-label-width);
56
+ display: flex;
57
+ align-items: flex-start;
58
+ }
59
+
60
+ /* label 列:固定宽 + **右对齐**(与 farris 的动态表单一致,也是水平表单的通行默认)。
61
+ min-height 取**控件真实高度**(--ui-form-control-height 由桥接层按 farris 控件规则给出):
62
+ label 盒与控件盒等高 ⇒ 两侧文字各自居中后落在同一条中线上。
63
+ 曾经写的是 32px 令牌,而 farris 控件实际 ≈24px(rem,随根字号)⇒ label 明显偏低,已修。 */
64
+
65
+ .mh-form-item__label {
66
+ flex: 0 0 auto;
67
+ width: var(--mh-form-label-width);
68
+ box-sizing: border-box;
69
+ min-height: var(--ui-form-control-height, auto);
70
+ padding-right: var(--ui-space-3);
71
+ display: flex;
72
+ align-items: center;
73
+ /* 右对齐:label 文本贴着控件列,短标签也不会在列里留下大段空隙(与 farris 动态表单一致) */
74
+ justify-content: flex-end;
75
+ gap: 2px;
76
+ font-size: var(--ui-font-body);
77
+ line-height: var(--ui-line-body);
78
+ color: var(--ui-base-content);
79
+ }
80
+
81
+ .mh-form-item__required {
82
+ flex: 0 0 auto;
83
+ /* **不是** `--ui-error`:星号表达「这里有个必填要求」,不是「这里出错了」。
84
+ 两者放同一枚令牌时,改错误色会顺带改掉全站的必填星号(反之亦然)。
85
+ 设计实证:`02.1 > 高级筛选 > 1..4` 的 `*` = `#ff1922`(比错误色 `#f23030` 更艳)。 */
86
+ color: var(--ui-required-mark);
87
+ }
88
+
89
+ /* label 对齐:基类默认右对齐(贴控件列,`labelAlign` 缺省档);
90
+ `labelAlign="left"` 时挂这个修饰类改成左对齐 —— 只给「非默认值」挂类,避免两段声明争胜出顺序。
91
+ 位置必须在基类规则之后:`.mh-form-item__label {` 是它的子串,测试取规则体时按首次出现命中。 */
92
+
93
+ .mh-form-item--label-left .mh-form-item__label {
94
+ justify-content: flex-start;
95
+ }
96
+
97
+ /* 只读展示行(`Form.Item` 的 `text` 形态,或显式 `readonly`)的 label 降一档为**次要色**:
98
+ 值与字段名同取正文色时,摘要 / 详情读起来是一串并列文本,分不清哪个是字段名
99
+ (原型 .sum-group td.k / .field-item .k 用的正是次要色)。值侧不变,仍是正文色。
100
+ `muted` 而非 `subtle`:空值占位「—」已经是 subtle,label 再用同色会让空行整行同调。
101
+ 位置同样必须在基类 `.mh-form-item__label {` 之后(spec 的 ruleBody 按首次出现取规则体)。 */
102
+
103
+ .mh-form-item--readonly .mh-form-item__label {
104
+ color: var(--ui-base-content-muted);
105
+ }
106
+
107
+ /* label 文本过长时省略而不是换行(换行会把控件列挤错位) */
108
+
109
+ .mh-form-item__label-text {
110
+ min-width: 0;
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ white-space: nowrap;
114
+ }
115
+
116
+ .mh-form-item__control {
117
+ flex: 1;
118
+ min-width: 0;
119
+ }
120
+
121
+ /* 只读文本行(Form.Item 的 text 形态:摘要 / 详情):正文色与行高,
122
+ 长文案(SQL where、列说明)按词折行;`pre-wrap` 保留换行符(多行过滤条件回显)。 */
123
+
124
+ .mh-form-item__text {
125
+ /* 与 label 同一口径:桥接层不给 `--ui-form-control-height` 时退回 auto(不给回退会让整条声明作废) */
126
+ min-height: var(--ui-form-control-height, auto);
127
+ display: flex;
128
+ align-items: center;
129
+ font-size: var(--ui-font-body);
130
+ line-height: var(--ui-line-body);
131
+ color: var(--ui-base-content);
132
+ white-space: pre-wrap;
133
+ word-break: break-word;
134
+ }
135
+
136
+ /* 空值占位(组件统一给 `—`):压成次要色,避免它被当成真实取值 */
137
+
138
+ .mh-form-item__text[data-empty='true'] {
139
+ color: var(--ui-base-content-subtle);
140
+ }
141
+
142
+ .mh-form-item__error {
143
+ margin-top: var(--ui-space-1);
144
+ font-size: var(--ui-font-caption);
145
+ line-height: var(--ui-line-caption);
146
+ color: var(--ui-error);
147
+ }
148
+
149
+ .mh-form-item__hint {
150
+ margin-top: var(--ui-space-1);
151
+ font-size: var(--ui-font-caption);
152
+ line-height: var(--ui-line-caption);
153
+ color: var(--ui-base-content-subtle);
154
+ }
155
+
156
+ /* ===== 纵向排布(`layout="vertical"`:label 在控件上方) =====
157
+ * 窄容器(抽屉 / 移动端)与「字段名很长」的场景用纵向 —— 横向时 label 列宽会把控件压得很窄。 */
158
+ .mh-form--vertical .mh-form-item {
159
+ flex-direction: column;
160
+ align-items: stretch;
161
+ }
162
+ .mh-form--vertical .mh-form-item__label {
163
+ width: auto;
164
+ min-width: 0;
165
+ justify-content: flex-start;
166
+ padding-right: 0;
167
+ padding-bottom: var(--ui-space-1);
168
+ text-align: left;
169
+ }
170
+
171
+ /* ===== 表单头(`Form.Header`:单头,成员归位到最前)=====
172
+ 只做**标题排版**与**右端操作位**,不承担区块分组(那是 `Panel` 的职责)。
173
+ 三样(title / extra / description)都没有时组件整块不渲染 —— 空盒子同样占一档
174
+ `Form` 的 gap,会凭空多出间距。 */
175
+
176
+ .mh-form__header {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: var(--ui-space-1);
180
+ }
181
+
182
+ /* 标题行:14px/600 —— 与字段 label(14px/400)拉开「这是标题」的字重差;
183
+ 有意不打到 16px(`--ui-font-heading` 是弹窗标题那一档)。
184
+ 行高由这一行**最高的孩子**决定:筛选条里是两颗 32px 按钮 ⇒ 这一行就是 32px(与设计稿一致)。 */
185
+
186
+ .mh-form__title {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: var(--ui-space-2);
190
+ font-size: var(--ui-font-body);
191
+ font-weight: var(--ui-weight-semibold);
192
+ line-height: var(--ui-line-body);
193
+ color: var(--ui-base-content);
194
+ }
195
+
196
+ /* 标题文本允许被挤窄(右端操作位不参与收缩);长标题**换行**而不是截断
197
+ —— 截断会让这一块读不出是什么。 */
198
+
199
+ .mh-form__title-text {
200
+ min-width: 0;
201
+ }
202
+
203
+ /* 右端操作位(`extra` 槽):贴右由 `margin-left: auto` 表达,不用 `justify-content: space-between`
204
+ (那条依赖「这一行只有两个子节点」,标题换行或将来再加子节点时就不再贴右);
205
+ `flex: none` 让操作位自身不被标题挤变形(与卡片便捷头同一手法)。 */
206
+
207
+ .mh-form__title-extra {
208
+ margin-left: auto;
209
+ display: flex;
210
+ align-items: center;
211
+ gap: var(--ui-space-2);
212
+ flex: none;
213
+ }
214
+
215
+ .mh-form__description {
216
+ margin: 0;
217
+ font-size: var(--ui-font-caption);
218
+ line-height: var(--ui-line-caption);
219
+ color: var(--ui-base-content-muted);
220
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 组件几何令牌:label 列宽 120(与设计稿「标签 + 一行控件」的表单同名宽,需要不同宽度时在表单上
3
+ * 覆盖 —— 组件只下发 --mh-form-label-width)+ 两列表单的列最小宽度(320 = 「label 120 + 一个还能
4
+ * 用的输入框」的下限,容器放不下两列时自动回落一列)。
5
+ *
6
+ * ## 抽出说明
7
+ * 本文件从面文件里抽出:这些是**基础值**,锚在 `[data-manohub-ui]`(跨包唯一作用域锚,
8
+ * 由入口层无条件写在应用容器上)—— 任何落在容器里的分件都取得到值,不再依赖
9
+ * 「声明在某个组件根类上」这条脆弱假设。
10
+ * ⚠️ `.mh-<件>--<档>` 这类**档位改写**留在面文件的修饰符块里(拆开会让档位失效)。
11
+ */
12
+ [data-manohub-ui] {
13
+ --ui-form-label-width: 120px;
14
+ --ui-form-column-min: 320px;
15
+ }
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Input / Textarea —— 输入框族(三档尺寸 + 单行 + 多行 + 展示态 + 前后缀 + 清除/眼睛 + 前置后置标签)。
3
+ *
4
+ * 出处:设计稿《MH后台-0911》的搜索框(`0:3945`:高 32 / 圆角 6 / 内距 12 / 图标 16 + 距 8 /
5
+ * 占位 `#999999` / 边框 `#e6e6e6`)与 `@aihub/theme` 的 `src/components/input.css`
6
+ * (控件高 32 / 内距 12 / 圆角 6 / 边框 base-300;聚焦 = 主色描边 + 主色环;禁用 = base-200 底 + base-300 边 + subtle 字;
7
+ * 多行最小高 44 + 右下计数;前置后置标签嵌在同一控件内,标签底 Ne11、高 24)。
8
+ *
9
+ * **一处有意差异**:规范把控件最大宽钉在 300px(300px),本仓的控件宽度**由表单列宽驱动**
10
+ * (`Form.Item` 的控件列 `flex: 1`),故这里 `width: 100%`、不设上限 —— 与 form.css 里
11
+ * 「不受 farris 字段 max-width 约束」的既有结论同口径。需要定宽的消费方在容器上收敛。
12
+ * **第二处有意差异(图标档)**:设计注记的「图标 16」按**整库口径**改为**常规图标档**
13
+ * `--ui-input-affix-size = var(--ui-font-icon)`(14);前后缀 / 眼睛与全库其它常规图标同径。
14
+ * 清除圆点内的叉(10)不属于常规图标,仍按设计取小字形。
15
+ *
16
+ * **表壳是 flex 行**(`__wrap`):前置 → 控件 → 清除 → 眼睛 → 后缀 → 计数依次排列,
17
+ * 间隔统一吃 `--ui-input-affix-gap`。壳内控件 `flex: 1 1 auto + min-width: 0`,
18
+ * 故任何附加位出现/消失时输入区自动伸缩,**不需要为每个附加组合各写一条让位规则**
19
+ * (绝对定位 + padding 让位的旧法在「前后缀 + 清除 + 眼睛」同时出现时必然要两两枚举)。
20
+ *
21
+ * **高度三档全部吃主题令牌**(`--ui-control-height-sm` / `-md` / `-lg`,default 24 / 32 / 40),
22
+ * 换主题即换控件尺寸;档位只覆盖令牌,几何规则只有一份。
23
+ *
24
+ * **触发器形态(`as="button"`)**:控件元素可以是 `<button>`(`Select` 一类复合件靠它复用整件的
25
+ * 框 / 内距 / 清除位 / 右侧图标位,同时保留 `<button>` 才有的原生语义,如 `popovertarget`)。
26
+ * 面层只需两条 —— `.mh-input--trigger` 只归零 `<button>` 的 UA 残留,
27
+ * `.mh-input__wrap > .mh-input--trigger::after` 是**整箱热区**(见该节),
28
+ * 两条都**不重写框、不重写状态**:框与状态本来就挂在 `.mh-input` 与表壳上,与元素类型无关。
29
+ *
30
+ * **聚焦环不新造阴影令牌**:规范的聚焦阴影槽位映射为本仓的「主色弱底」令牌
31
+ * `--ui-primary-weak`(同一用途:主色低透明度环)。
32
+ *
33
+ * 状态三源等价:原生 `:hover` / `:focus` / `:disabled` / `[aria-invalid]` ⟷ 组件下发 `data-state`
34
+ * ⟷ 静态档 `.is-*`,同名面共用一份声明。
35
+ *
36
+ * 禁写:本件不消费 farris 类;跨件令牌一律进 `theme/`。
37
+ */
38
+
39
+ /* 控件本体:**裸用(不带壳)时由控件自己画框** —— Select 的搜索行、静态页写法都靠这一条。
40
+ * 带壳 / 进组时,后面「壳」「组」两节会把框逐层收归外层(见各节注释)。 */
41
+ .mh-input {
42
+ width: 100%;
43
+ height: var(--ui-input-height);
44
+ padding: 0 var(--ui-input-padding-x);
45
+ border: 1px solid var(--ui-input-line);
46
+ border-radius: var(--ui-input-radius);
47
+ background: var(--ui-base-100);
48
+ font-family: inherit;
49
+ font-size: var(--ui-input-font-size);
50
+ color: var(--ui-base-content);
51
+ outline: none;
52
+ transition-property: border-color, box-shadow, background-color;
53
+ transition-duration: 0.15s;
54
+ transition-timing-function: ease;
55
+ }
56
+ .mh-input::placeholder {
57
+ color: var(--ui-base-content-subtle);
58
+ }
59
+ .mh-input:hover,
60
+ .mh-input.is-hover {
61
+ border-color: var(--ui-input-line-hover);
62
+ }
63
+ .mh-input:focus,
64
+ .mh-input.is-focus {
65
+ border-color: var(--ui-input-line-hover);
66
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
67
+ }
68
+ /* 校验态:走 `aria-invalid`(本仓协议),静态档 `.is-error` 供规格冻结与读图 */
69
+ .mh-input[aria-invalid='true'],
70
+ .mh-input.is-error {
71
+ border-color: var(--ui-error);
72
+ }
73
+ .mh-input.is-error:focus,
74
+ .mh-input.is-error.is-focus {
75
+ box-shadow: 0 0 0 2px var(--ui-error-weak);
76
+ }
77
+ /* 警告态(尚未阻断提交,但需要用户注意) */
78
+ .mh-input.is-warning {
79
+ border-color: var(--ui-warning);
80
+ }
81
+ .mh-input.is-warning:focus,
82
+ .mh-input.is-warning.is-focus {
83
+ box-shadow: 0 0 0 2px var(--ui-warning-weak);
84
+ }
85
+ .mh-input:disabled,
86
+ .mh-input.is-disabled {
87
+ background: var(--ui-base-200); /* Ne10 */
88
+ border-color: var(--ui-base-300); /* Ne05 */
89
+ color: var(--ui-base-content-subtle); /* Te07 */
90
+ cursor: not-allowed;
91
+ }
92
+
93
+ /* ===== 尺寸档 ===== */
94
+ .mh-input--sm {
95
+ height: var(--ui-input-height-sm);
96
+ padding: 0 var(--ui-input-padding-x-sm);
97
+ font-size: var(--ui-input-font-size-sm);
98
+ }
99
+ .mh-input--lg {
100
+ height: var(--ui-input-height-lg);
101
+ padding: 0 var(--ui-input-padding-x-lg);
102
+ font-size: var(--ui-input-font-size-lg);
103
+ }
104
+
105
+ /* ===== 触发器形态(`as="button"`:控件是 `<button>`,框与状态仍归表壳) =====
106
+ *
107
+ * 复用面(框 / 内距 / 附加位 / 三态)**不需要为按钮重写一行**:那些规则要么挂在 `.mh-input` 上
108
+ * (本身与元素类型无关),要么挂在表壳上(`:has(> .mh-input…)` 对 button 同样命中)。
109
+ * 这里只补**作者源没覆盖、会被 `<button>` 的原生 UA 样式漏进来的四项**(实测 Chrome 的 html.css:
110
+ * `button { align-items: flex-start; text-align: center; appearance: auto; font: 400 13.3333px Arial }`):
111
+ *
112
+ * - `align-items: center` —— 控件是 flex 行,UA 的 `flex-start` 会把值 / 标签顶到上沿;
113
+ * - `text-align: inherit` —— UA 的 `center` 会**继承**给值文本;
114
+ * - `font-weight` / `line-height: inherit` —— UA 的 `font` 简写会把这两项从继承链上摘下来,
115
+ * 而作者源只写了 `font-family` 与 `font-size`(对比:`<input>` 的 UA 不设这两项,故输入框形态天然一致);
116
+ * - `appearance: none` —— 防仍画原生按钮皮肤的引擎(Safari 一类)。
117
+ *
118
+ * 禁写:本块不碰框 / 色 / 尺寸 / 内距 —— 那些归 `.mh-input` 与表壳,重复声明就是又开一份会漂的规格。 */
119
+ .mh-input--trigger {
120
+ appearance: none;
121
+ align-items: center;
122
+ font-weight: inherit;
123
+ line-height: inherit;
124
+ text-align: inherit;
125
+ /* 命中被下面的覆盖层重定向到本控件后,整箱(含内距与附加位)都取这一条光标;
126
+ 禁用态由 `.mh-input:disabled { cursor: not-allowed }`(0-2-0)压过(本条约 0-1-0)。 */
127
+ cursor: pointer;
128
+ }
129
+
130
+ /* ===== 触发器形态 · 整箱热区(命中测试重定向,纯 CSS) =====
131
+ *
132
+ * 目的:让**整件**(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮。
133
+ * 触发器形态的附加位(如右侧箭头)是控件的**兄弟**而不是子孙,点它们本来永远到不了按钮
134
+ * (实测:只有按钮本体那一格能开面板)。
135
+ *
136
+ * 做法:给控件挂一张**透明的绝对定位覆盖层**。伪元素**不产生独立事件目标** —— 命中它即命中
137
+ * 它的宿主元素,而 `inset: 0` 的包含块是最近的定位祖先,也就是**表壳**(`.mh-input__wrap`
138
+ * 自带 `position: relative`,见「壳」一节)。于是整个箱子的像素点都归控件:
139
+ * `popovertarget` 的原生激活、`:active`、mousedown 聚焦一律照旧,**不需要任何 JS 代理**
140
+ * (JS 代理会丢掉 `:active` 与按下聚焦的原生时序)。
141
+ *
142
+ * 层叠:覆盖层是定位元素 → 盖住静态的兄弟(`.mh-input__suffix` 里的箭头等)→ 正是所需;
143
+ * **清除位必须自己升到覆盖层之上**(它是唯一不该被热区截走的兄弟),故 `.mh-input__clear` 带
144
+ * `position: relative`,并在 spec 里有断言锁住 —— 漏了点清除会变成开面板。
145
+ * 消费方若在 `prefix` / `suffix` 里放**可点元素**,同样要自行 `position: relative`。
146
+ *
147
+ * 必须写成「表壳 > 控件」:裸写 `.mh-input--trigger::after` 时,无壳场景的包含块会落到页面级
148
+ * 定位祖先上 —— 那块透明层会铺满一大片并吞掉别人的点击。 */
149
+ .mh-input__wrap > .mh-input--trigger::after {
150
+ content: '';
151
+ position: absolute;
152
+ inset: 0;
153
+ }
154
+
155
+ /* ===== 无边框档(表格内编辑 / 行内编辑:没有框也没有底,靠悬停给反馈) ===== */
156
+ .mh-input--borderless {
157
+ border-color: transparent;
158
+ background: transparent;
159
+ border-radius: 0;
160
+ }
161
+ .mh-input--borderless:hover,
162
+ .mh-input--borderless.is-hover {
163
+ border-color: transparent;
164
+ background: var(--ui-base-200);
165
+ }
166
+ .mh-input--borderless:focus,
167
+ .mh-input--borderless.is-focus {
168
+ border-color: var(--ui-input-line-hover);
169
+ background: var(--ui-base-100);
170
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
171
+ }
172
+
173
+ /* ===== 展示态(下划线,只读值回显) ===== */
174
+ .mh-input--display {
175
+ display: inline-flex;
176
+ align-items: center;
177
+ height: var(--ui-input-height);
178
+ padding: 0;
179
+ border: none;
180
+ border-bottom: 1px solid var(--ui-base-300);
181
+ border-radius: 0;
182
+ background: transparent;
183
+ font-size: var(--ui-input-font-size);
184
+ color: var(--ui-base-content);
185
+ white-space: pre-wrap;
186
+ word-break: break-word;
187
+ }
188
+
189
+ /* ===== 多行(右下计数同容器) ===== */
190
+ .mh-input--textarea {
191
+ min-height: var(--ui-input-textarea-min-height);
192
+ height: auto;
193
+ padding: var(--ui-space-2) var(--ui-input-padding-x);
194
+ line-height: var(--ui-line-body);
195
+ resize: vertical;
196
+ }
197
+ .mh-input--textarea.mh-input--sm {
198
+ min-height: calc(var(--ui-input-textarea-min-height) - 4px);
199
+ }
200
+ .mh-input--textarea.mh-input--lg {
201
+ min-height: calc(var(--ui-input-textarea-min-height) + 8px);
202
+ }
203
+ /* 计数在位时给多行留出底部空间,避免遮住最后一行 */
204
+ .mh-input__wrap:has(> .mh-input__counter) > .mh-input--textarea {
205
+ padding-bottom: calc(var(--ui-space-2) + var(--ui-input-counter-size) + 2px);
206
+ }
207
+
208
+ /* ===== 壳(附加位的排布容器,**整件的框主**) =====
209
+ *
210
+ * 框画在壳上、不画在控件上:壳是一个 flex 行,前缀 / 清除 / 眼睛 / 后缀都在**控件之外**,
211
+ * 框挂在控件上时附加位就落在框外 —— 带眼睛的密码框实测:壳 220px、带框的控件只到 196px,
212
+ * 眼睛图标飘在框外。壳定高 + 控件 `height: 100%`,整件仍是可点区域。
213
+ * **裸 `.mh-input`(不带壳)仍由控件自己画框**(Select 的搜索行就靠它),见上一节。
214
+ */
215
+ .mh-input__wrap {
216
+ position: relative;
217
+ display: inline-flex;
218
+ align-items: center;
219
+ gap: var(--ui-input-affix-gap);
220
+ width: 100%;
221
+ height: var(--ui-input-height);
222
+ padding: 0 var(--ui-input-padding-x);
223
+ border: 1px solid var(--ui-input-line);
224
+ border-radius: var(--ui-input-radius);
225
+ background: var(--ui-base-100);
226
+ transition-property: border-color, box-shadow, background-color;
227
+ transition-duration: 0.15s;
228
+ transition-timing-function: ease;
229
+ }
230
+ .mh-input__wrap:has(> .mh-input--sm) {
231
+ height: var(--ui-input-height-sm);
232
+ }
233
+ .mh-input__wrap:has(> .mh-input--lg) {
234
+ height: var(--ui-input-height-lg);
235
+ }
236
+ /* 多行:高归内容(`resize` 要能改高),框仍归壳 */
237
+ .mh-input__wrap:has(> .mh-input--textarea) {
238
+ height: auto;
239
+ padding: var(--ui-space-2) var(--ui-input-padding-x);
240
+ }
241
+ /* 壳内控件占满剩余宽度,且允许被压缩(min-width:0);附加位增减时输入区自动伸缩 */
242
+ .mh-input__wrap > .mh-input {
243
+ flex: 1 1 auto;
244
+ min-width: 0;
245
+ /* 框 / 圆角 / 底色 / 环一并退掉:整件只留壳那一道(与组内同一套口径) */
246
+ border: none;
247
+ border-radius: 0;
248
+ padding: 0;
249
+ background: transparent;
250
+ }
251
+ /* 高度只给单行;多行的 `min-height` 与 `resize` 归控件自己 */
252
+ .mh-input__wrap > .mh-input:not(.mh-input--textarea) {
253
+ height: 100%;
254
+ }
255
+ .mh-input__wrap > .mh-input--textarea {
256
+ width: 100%;
257
+ display: block;
258
+ }
259
+ .mh-input__wrap > .mh-input:focus,
260
+ .mh-input__wrap > .mh-input.is-focus {
261
+ box-shadow: none;
262
+ }
263
+ /* 悬停:反馈画在壳上(壳铺满整件,附加位区域也一起响应) */
264
+ .mh-input__wrap:hover {
265
+ border-color: var(--ui-input-line-hover);
266
+ }
267
+ /* 聚焦:环画在壳上 —— 壳是框主,环自然跟着整件走 */
268
+ .mh-input__wrap:focus-within,
269
+ .mh-input__wrap:has(> .mh-input.is-focus) {
270
+ border-color: var(--ui-input-line-hover);
271
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
272
+ }
273
+ /* 校验态:色同上提到壳(error 走 `aria-invalid` 协议,另给静态档 `.is-error`) */
274
+ .mh-input__wrap:has(> .mh-input[aria-invalid='true']),
275
+ .mh-input__wrap:has(> .mh-input.is-error) {
276
+ border-color: var(--ui-error);
277
+ }
278
+ .mh-input__wrap:has(> .mh-input[aria-invalid='true']:focus),
279
+ .mh-input__wrap:has(> .mh-input.is-error:focus),
280
+ .mh-input__wrap:has(> .mh-input.is-error.is-focus) {
281
+ border-color: var(--ui-error);
282
+ box-shadow: 0 0 0 2px var(--ui-error-weak);
283
+ }
284
+ .mh-input__wrap:has(> .mh-input.is-warning) {
285
+ border-color: var(--ui-warning);
286
+ }
287
+ .mh-input__wrap:has(> .mh-input.is-warning:focus),
288
+ .mh-input__wrap:has(> .mh-input.is-warning.is-focus) {
289
+ border-color: var(--ui-warning);
290
+ box-shadow: 0 0 0 2px var(--ui-warning-weak);
291
+ }
292
+ /* 禁用:底与边归壳(字色仍在控件上,见 `.mh-input:disabled`) */
293
+ .mh-input__wrap:has(> .mh-input:disabled),
294
+ .mh-input__wrap:has(> .mh-input.is-disabled) {
295
+ background: var(--ui-base-200);
296
+ border-color: var(--ui-base-300);
297
+ }
298
+ /* 无边框档(表格内编辑 / 行内编辑):框与底全退,悬停给浅底、聚焦才显出框 */
299
+ .mh-input__wrap:has(> .mh-input--borderless) {
300
+ border-color: transparent;
301
+ border-radius: 0;
302
+ background: transparent;
303
+ }
304
+ .mh-input__wrap:has(> .mh-input--borderless):hover {
305
+ border-color: transparent;
306
+ background: var(--ui-base-200);
307
+ }
308
+ .mh-input__wrap:has(> .mh-input--borderless):focus-within {
309
+ border-color: var(--ui-input-line-hover);
310
+ background: var(--ui-base-100);
311
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
312
+ }
313
+
314
+ /* ===== 前缀 / 后缀插槽(纯装饰:图标、单位、域名;无底色、不进 Tab 序列) ===== */
315
+ .mh-input__prefix,
316
+ .mh-input__suffix {
317
+ flex: none;
318
+ display: inline-flex;
319
+ align-items: center;
320
+ color: var(--ui-base-content-subtle);
321
+ }
322
+ /* 槽内图标(消费方直接放 <Icon> 时也给到统一尺寸) */
323
+ .mh-input__prefix > svg,
324
+ .mh-input__suffix > svg {
325
+ flex: none;
326
+ width: var(--ui-input-affix-size);
327
+ height: var(--ui-input-affix-size);
328
+ }
329
+
330
+ /* ===== 清除(有内容才由组件渲染;圆形 + close 字形) ===== */
331
+ .mh-input__clear {
332
+ flex: none;
333
+ display: inline-flex;
334
+ align-items: center;
335
+ justify-content: center;
336
+ /* `position: relative` 是**触发器形态热区**的必要条件:热区覆盖层是定位元素、会盖住静态兄弟,
337
+ 清除位必须自己也是定位元素才能凭 DOM 顺序压在它之上 —— 否则点清除会被热区截走变成开面板。 */
338
+ position: relative;
339
+ width: var(--ui-input-icon-size);
340
+ height: var(--ui-input-icon-size);
341
+ padding: 0;
342
+ border: none;
343
+ border-radius: var(--ui-radius-full);
344
+ background: var(--ui-base-300); /* 规范 Te10 圆点底 */
345
+ color: var(--ui-base-100);
346
+ cursor: pointer;
347
+ }
348
+ .mh-input__clear:hover,
349
+ .mh-input__clear:active {
350
+ background: var(--ui-base-400); /* 规范 Te08 */
351
+ }
352
+ .mh-input__clear-icon {
353
+ flex: none;
354
+ }
355
+
356
+ /* ===== 密码可见性切换(右端图标位;明文态转主色,状态一眼可辨) ===== */
357
+ .mh-input__eye {
358
+ flex: none;
359
+ display: inline-flex;
360
+ align-items: center;
361
+ justify-content: center;
362
+ width: var(--ui-input-affix-size);
363
+ height: var(--ui-input-affix-size);
364
+ padding: 0;
365
+ border: none;
366
+ background: transparent;
367
+ color: var(--ui-base-content-subtle);
368
+ cursor: pointer;
369
+ }
370
+ .mh-input__eye:hover {
371
+ color: var(--ui-primary);
372
+ }
373
+ /* 明文显示中:图标转主色(同一个 `eye` 字形,靠状态色表达"当前是明文") */
374
+ .mh-input__eye.is-on {
375
+ color: var(--ui-primary);
376
+ }
377
+ .mh-input__eye-icon {
378
+ flex: none;
379
+ width: var(--ui-input-affix-size);
380
+ height: var(--ui-input-affix-size);
381
+ }
382
+
383
+ /* ===== 字数计数 =====
384
+ * 单行:壳内的普通成员(跟在后缀之后,不遮输入区);
385
+ * 多行:脱流钉在右下角(`textarea` 与它同容器,见上方的底部留白规则)。 */
386
+ .mh-input__counter {
387
+ flex: none;
388
+ font-size: var(--ui-input-counter-size);
389
+ line-height: var(--ui-line-caption);
390
+ color: var(--ui-base-content-subtle);
391
+ }
392
+ .mh-input__wrap:has(> .mh-input--textarea) > .mh-input__counter {
393
+ position: absolute;
394
+ right: var(--ui-input-padding-x);
395
+ bottom: var(--ui-space-1);
396
+ }
397
+
398
+ /* 附加位里下拉件的面板皮肤(与操作层并列组合:`class="mh-popup mh-input__menu"`) */
399
+ .mh-input__menu {
400
+ width: 96px;
401
+ margin-top: var(--ui-space-1);
402
+ padding: var(--ui-space-1) 0;
403
+ border-radius: var(--ui-input-radius);
404
+ background: var(--ui-base-100);
405
+ box-shadow: var(--ui-shadow-popover); /* 锚定浮层(下拉菜单),不是居中浮层 */
406
+ }
407
+ .mh-input__menu-item {
408
+ height: 30px;
409
+ padding: 0 var(--ui-space-3);
410
+ font-size: var(--ui-input-font-size);
411
+ line-height: 30px;
412
+ color: var(--ui-base-content);
413
+ cursor: pointer;
414
+ }
415
+ .mh-input__menu-item:hover,
416
+ .mh-input__menu-item.is-hover {
417
+ background: var(--ui-base-200);
418
+ }
419
+
420
+ /* ===== 输入说明(说明位 + 气泡皮肤) ===== */
421
+ .mh-input__help {
422
+ display: inline-flex;
423
+ align-items: center;
424
+ justify-content: center;
425
+ flex: none;
426
+ width: 14px;
427
+ height: 14px;
428
+ margin-left: var(--ui-space-1);
429
+ border: 1px solid var(--ui-base-content-muted);
430
+ border-radius: var(--ui-radius-full);
431
+ color: var(--ui-base-content-muted);
432
+ font-size: var(--ui-input-counter-size);
433
+ line-height: 1;
434
+ cursor: help;
435
+ }
436
+ /* 气泡:深底浅字(规范 #303c53 底 + 反白字),出血内距按规范 8/24 */
437
+ .mh-input__help-tip {
438
+ padding: var(--ui-space-2) var(--ui-space-6);
439
+ border-radius: var(--ui-input-radius);
440
+ background: var(--ui-base-content);
441
+ color: var(--ui-base-100);
442
+ font-size: var(--ui-input-font-size);
443
+ line-height: var(--ui-line-body);
444
+ box-shadow: var(--ui-shadow-popover);
445
+ }