@autional/ui 0.1.0-rc.43 → 0.1.0-rc.45
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/package.json +2 -2
- package/src/atoms/Input.tsx +49 -5
- package/src/internal/field-styles.ts +40 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autional/ui",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.45",
|
|
4
4
|
"description": "Autional shared React component library (atoms + molecules) — the canonical home of the component layer",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"react-i18next": "17.0.7",
|
|
83
83
|
"typescript": "5.8.3",
|
|
84
84
|
"vitest": "3.2.0",
|
|
85
|
-
"@autional/tokens": "0.1.0-rc.
|
|
85
|
+
"@autional/tokens": "0.1.0-rc.13"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
88
|
"typecheck": "tsc --noEmit",
|
package/src/atoms/Input.tsx
CHANGED
|
@@ -1,19 +1,63 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { fieldControlClass, fieldControlHeight } from '../internal/field-styles';
|
|
2
|
+
import { fieldControlClass, fieldControlClassFor, fieldControlHeight, type FieldShape, type FieldSize } from '../internal/field-styles';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// 注:`prefix` 与 `size` 都与 HTML 自带的同名属性冲突(`prefix?: string`、`size?: number`,
|
|
5
|
+
// 而这里要的是 ReactNode 与 'sm'|'md'|'lg')。两处都是 **tsc 当场报出来的** ——
|
|
6
|
+
// 所以显式 Omit 掉再重定义,保住最自然的调用名,而不是改成 `inputSize` 这种妥协名。
|
|
7
|
+
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix' | 'size'> {
|
|
5
8
|
error?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 前缀槽:图标或极短的单位符号(如货币符号)。
|
|
11
|
+
*
|
|
12
|
+
* 为什么要有它(第 59 轮):舰队里 5 个站在手写同一个模式 ——
|
|
13
|
+
* 一个 `relative` 容器 + 绝对定位的图标 + **算出来的**左内边距(`pl-9` = 12 起点 + 16 图标 + 8 间隙 = 36px)。
|
|
14
|
+
* 那是同一个几何被抄了 6 遍,而且抄出来的值不在档位上。
|
|
15
|
+
* 这里用 **flex 排布**而不是绝对定位:图标占真实布局空间,于是
|
|
16
|
+
* **不需要任何魔法内边距**,也不挑图标尺寸。
|
|
17
|
+
*
|
|
18
|
+
* 图标请守图标纪律:默认 `size="1em"`,显式字号时 `size={N}`。
|
|
19
|
+
*/
|
|
20
|
+
prefix?: React.ReactNode;
|
|
21
|
+
/** 后缀槽:清除按钮、单位后缀一类。同样占真实布局空间。 */
|
|
22
|
+
suffix?: React.ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* 尺寸档(第 61 轮):sm=h-9 / md=h-10(默认)/ lg=h-14。
|
|
25
|
+
* 舰队里手写的搜索框有三种几何,这个参数就是为它们准备的 —— **只影响有槽形态**,
|
|
26
|
+
* 无槽形态仍走默认高度(既有消费方零影响)。
|
|
27
|
+
*/
|
|
28
|
+
size?: FieldSize;
|
|
29
|
+
/** 形状档:default=rounded-md / pill=rounded-full(大圆角搜索框)。同样只影响有槽形态。 */
|
|
30
|
+
shape?: FieldShape;
|
|
6
31
|
}
|
|
7
32
|
|
|
8
33
|
// 外观来自 ../internal/field-styles —— 与 @autional/ui/rhf 的绑定控件同一份,不各自维护。
|
|
34
|
+
//
|
|
35
|
+
// 两种渲染形态(**无槽时逐字节不变**,既有消费方零影响):
|
|
36
|
+
// · 无 prefix/suffix:只有一个 <input>,外框在它自己身上(原样);
|
|
37
|
+
// · 有槽:外框交给 wrapper,<input> 变透明并占满剩余宽度 —— 高度/圆角由 `size` / `shape`
|
|
38
|
+
// 两轴决定(第 61 轮补:舰队里搜索框有三种几何 py-2 / py-2.5 / py-3.5 + rounded-full,
|
|
39
|
+
// 组件层若只有一种,站点就只能各自写材质 —— 那就是这条维度存在的原因)。
|
|
40
|
+
// ⚠️ 调用方**不要**再传高度/内边距/圆角:同一批属性只能有一个来源,否则胜负由
|
|
41
|
+
// Tailwind 的生成顺序决定,而那是无声的。
|
|
9
42
|
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
10
|
-
({ className = '', error, ...props }, ref) => {
|
|
43
|
+
({ className = '', error, prefix, suffix, size = 'md', shape = 'default', ...props }, ref) => {
|
|
44
|
+
const hasSlot = prefix != null || suffix != null;
|
|
11
45
|
return (
|
|
12
46
|
<div className="w-full">
|
|
13
|
-
|
|
47
|
+
{hasSlot ? (
|
|
48
|
+
<div className={fieldControlClassFor(size, shape, !!error, 'items-center gap-2 ' + className)}>
|
|
49
|
+
{prefix != null && (
|
|
50
|
+
<span className="shrink-0 text-[var(--color-text-muted)]" aria-hidden="true">{prefix}</span>
|
|
51
|
+
)}
|
|
52
|
+
<input ref={ref} className="min-w-0 flex-1 bg-transparent p-0 text-inherit outline-none placeholder:text-[var(--color-text-muted)]" {...props} />
|
|
53
|
+
{suffix != null && <span className="shrink-0">{suffix}</span>}
|
|
54
|
+
</div>
|
|
55
|
+
) : (
|
|
56
|
+
<input ref={ref} className={fieldControlClass(!!error, fieldControlHeight + ' ' + className)} {...props} />
|
|
57
|
+
)}
|
|
14
58
|
{error && <p className="mt-1 text-xs text-[var(--color-danger)]">{error}</p>}
|
|
15
59
|
</div>
|
|
16
60
|
);
|
|
17
61
|
},
|
|
18
62
|
);
|
|
19
|
-
Input.displayName = 'Input';
|
|
63
|
+
Input.displayName = 'Input';
|
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
// 表单控件的外观只有一份 —— Input atom 与 @autional/ui/rhf 的绑定控件都用它。
|
|
2
2
|
// 各自复制一份类名串的后果不是「看起来一样」,而是「改了一处、另一处没变」:
|
|
3
3
|
// 那正是这一层要消灭的东西(与 antd 组件级令牌同理,只是这里是一段 Tailwind 类)。
|
|
4
|
+
//
|
|
5
|
+
// 第 61 轮加了尺寸与形状两轴 —— 起因是舰队里手写的搜索框有**三种几何**
|
|
6
|
+
// (py-2 / py-2.5 / py-3.5 + rounded-full),而组件层只有一种,于是站点只能各自写材质。
|
|
7
|
+
// 关键纪律:**高度/内边距/圆角只由一个地方发出来**(这里),调用方的 className 不得与它冲突 ——
|
|
8
|
+
// 否则胜负由 Tailwind 的生成顺序决定,那是无声的。
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
// ⚠️ 拆 BASE 的时候差点丢掉两个状态样式(focus-visible 环、disabled)——
|
|
11
|
+
// 「重写一个常量的整行」时最容易漏掉的就是**行尾那几个**。它们与尺寸/形状不冲突,
|
|
12
|
+
// 所以留在 STRUCT 里,一处不少。
|
|
13
|
+
const STRUCT =
|
|
14
|
+
'flex w-full border bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] ' +
|
|
15
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)] focus-visible:ring-offset-2 ' +
|
|
16
|
+
'disabled:cursor-not-allowed disabled:opacity-50';
|
|
7
17
|
|
|
8
|
-
/**
|
|
18
|
+
/** 尺寸档:高度是**定值**(不是 padding 撑出来的)—— 同一行里的控件要对齐,定值才可预测。 */
|
|
19
|
+
export type FieldSize = 'sm' | 'md' | 'lg';
|
|
20
|
+
const SIZE: Record<FieldSize, string> = {
|
|
21
|
+
sm: 'h-9 px-3 text-sm',
|
|
22
|
+
md: 'h-10 px-3 text-sm',
|
|
23
|
+
lg: 'h-14 px-4 text-base',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** 形状档。pill 是给「搜索框」一类大圆角控件用的,不是给表单控件的默认。 */
|
|
27
|
+
export type FieldShape = 'default' | 'pill';
|
|
28
|
+
const SHAPE: Record<FieldShape, string> = {
|
|
29
|
+
default: 'rounded-md',
|
|
30
|
+
pill: 'rounded-full',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** 单行控件的默认高度。textarea 不给(它要能长高)。 */
|
|
9
34
|
export const fieldControlHeight = 'h-10';
|
|
10
35
|
|
|
11
|
-
export function
|
|
36
|
+
export function fieldControlClassFor(
|
|
37
|
+
size: FieldSize = 'md',
|
|
38
|
+
shape: FieldShape = 'default',
|
|
39
|
+
invalid?: boolean,
|
|
40
|
+
extra = '',
|
|
41
|
+
): string {
|
|
12
42
|
const border = invalid ? 'border-[var(--color-danger)]' : 'border-[var(--color-border-subtle)]';
|
|
13
|
-
return `${
|
|
43
|
+
return `${STRUCT} ${SIZE[size]} ${SHAPE[shape]} ${border} ${extra}`.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 旧签名(默认 md + default)—— **逐字节不变**,既有消费方零影响。 */
|
|
47
|
+
export function fieldControlClass(invalid?: boolean, extra = ''): string {
|
|
48
|
+
return fieldControlClassFor('md', 'default', invalid, extra);
|
|
14
49
|
}
|