@autional/ui 0.1.0-rc.42 → 0.1.0-rc.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autional/ui",
3
- "version": "0.1.0-rc.42",
3
+ "version": "0.1.0-rc.44",
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.10"
85
+ "@autional/tokens": "0.1.0-rc.12"
86
86
  },
87
87
  "scripts": {
88
88
  "typecheck": "tsc --noEmit",
@@ -1,16 +1,49 @@
1
1
  import * as React from 'react';
2
2
  import { fieldControlClass, fieldControlHeight } from '../internal/field-styles';
3
3
 
4
- export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
4
+ // 注:`prefix` 与 HTML 自带的 `prefix` 属性同名(后者在 <input> 上没有意义,TS 会报
5
+ // 「ReactNode 不能赋给 string」)。所以这里显式 Omit 掉它再重定义 —— 保住最自然的调用名。
6
+ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
5
7
  error?: string;
8
+ /**
9
+ * 前缀槽:图标或极短的单位符号(如货币符号)。
10
+ *
11
+ * 为什么要有它(第 59 轮):舰队里 5 个站在手写同一个模式 ——
12
+ * 一个 `relative` 容器 + 绝对定位的图标 + **算出来的**左内边距(`pl-9` = 12 起点 + 16 图标 + 8 间隙 = 36px)。
13
+ * 那是同一个几何被抄了 6 遍,而且抄出来的值不在档位上。
14
+ * 这里用 **flex 排布**而不是绝对定位:图标占真实布局空间,于是
15
+ * **不需要任何魔法内边距**,也不挑图标尺寸。
16
+ *
17
+ * 图标请守图标纪律:默认 `size="1em"`,显式字号时 `size={N}`。
18
+ */
19
+ prefix?: React.ReactNode;
20
+ /** 后缀槽:清除按钮、单位后缀一类。同样占真实布局空间。 */
21
+ suffix?: React.ReactNode;
6
22
  }
7
23
 
8
24
  // 外观来自 ../internal/field-styles —— 与 @autional/ui/rhf 的绑定控件同一份,不各自维护。
25
+ //
26
+ // 两种渲染形态(**无槽时逐字节不变**,既有消费方零影响):
27
+ // · 无 prefix/suffix:只有一个 <input>,外框在它自己身上(原样);
28
+ // · 有槽:外框交给 wrapper,<input> 变透明并占满剩余宽度 —— 高度/圆角由调用方
29
+ // 原有的 className 决定(舰队里搜索框有三种几何:py-2 / py-2.5 / py-3.5 rounded-full),
30
+ // 所以这一层**不替调用方决定尺寸**。
9
31
  export const Input = React.forwardRef<HTMLInputElement, InputProps>(
10
- ({ className = '', error, ...props }, ref) => {
32
+ ({ className = '', error, prefix, suffix, ...props }, ref) => {
33
+ const hasSlot = prefix != null || suffix != null;
11
34
  return (
12
35
  <div className="w-full">
13
- <input ref={ref} className={fieldControlClass(!!error, fieldControlHeight + ' ' + className)} {...props} />
36
+ {hasSlot ? (
37
+ <div className={fieldControlClass(!!error, 'flex items-center gap-2 ' + className)}>
38
+ {prefix != null && (
39
+ <span className="shrink-0 text-[var(--color-text-muted)]" aria-hidden="true">{prefix}</span>
40
+ )}
41
+ <input ref={ref} className="min-w-0 flex-1 bg-transparent p-0 text-inherit outline-none placeholder:text-[var(--color-text-muted)]" {...props} />
42
+ {suffix != null && <span className="shrink-0">{suffix}</span>}
43
+ </div>
44
+ ) : (
45
+ <input ref={ref} className={fieldControlClass(!!error, fieldControlHeight + ' ' + className)} {...props} />
46
+ )}
14
47
  {error && <p className="mt-1 text-xs text-[var(--color-danger)]">{error}</p>}
15
48
  </div>
16
49
  );
@@ -34,7 +34,7 @@ export const Toggle = React.memo(function Toggle({
34
34
  } ${s.track} ${className}`}
35
35
  >
36
36
  <span
37
- className={`inline-block rounded-full bg-white shadow-sm transition-transform ${
37
+ className={`inline-block rounded-full bg-white shadow-soft transition-transform ${
38
38
  checked ? s.translate : 'translate-x-0.5'
39
39
  } ${s.circle}`}
40
40
  />
@@ -62,7 +62,7 @@ export const ConfirmDialog = React.memo(function ConfirmDialog({
62
62
  return (
63
63
  <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
64
64
  <div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={onCancel} />
65
- <div className="relative w-full max-w-md rounded-xl border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] p-6 shadow-lg">
65
+ <div className="relative w-full max-w-md rounded-xl border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] p-6 shadow-brand">
66
66
  <button
67
67
  onClick={onCancel}
68
68
  className="absolute right-4 top-4 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
@@ -106,7 +106,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
106
106
  {this.state.error?.message || T.unknown}
107
107
  </p>
108
108
  {this.state.error?.stack && (
109
- <pre className="mt-4 max-h-60 max-w-2xl overflow-auto rounded bg-neutral-100 p-3 text-left text-xs text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400">
109
+ <pre className="mt-4 max-h-60 max-w-2xl overflow-auto rounded-xs bg-neutral-100 p-3 text-left text-xs text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400">
110
110
  {this.state.error.stack}
111
111
  </pre>
112
112
  )}
@@ -138,7 +138,7 @@ export const Modal = React.memo(function Modal({
138
138
  aria-labelledby={title ? titleId : undefined}
139
139
  aria-describedby={title && description ? descriptionId : undefined}
140
140
  tabIndex={-1}
141
- className={`relative w-full ${maxWidthClasses[maxWidth]} rounded-xl border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] shadow-lg outline-none ${className}`}
141
+ className={`relative w-full ${maxWidthClasses[maxWidth]} rounded-xl border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] shadow-brand outline-none ${className}`}
142
142
  >
143
143
  {title && (
144
144
  <div className="flex items-start justify-between gap-4 border-b border-[var(--color-border-subtle)] px-6 py-4">
@@ -26,7 +26,7 @@ export const PageContainer = React.memo(function PageContainer({
26
26
  className={`flex min-h-screen items-center justify-center px-4 ${padding ? 'py-8' : ''} ${className}`}
27
27
  >
28
28
  <div
29
- className={`w-full ${maxWidthClasses[maxWidth]} space-y-6 rounded-2xl bg-[var(--color-bg-surface)] p-8 shadow-lg`}
29
+ className={`w-full ${maxWidthClasses[maxWidth]} space-y-6 rounded-lg bg-[var(--color-bg-surface)] p-8 shadow-brand`}
30
30
  >
31
31
  {children}
32
32
  </div>
@@ -96,7 +96,7 @@ export function PortalSwitcher({
96
96
  <div
97
97
  {...menuProps}
98
98
  ref={menuRef}
99
- className={`absolute top-full z-50 mt-2 w-56 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-bg-elevated)] py-1 shadow-lg ${
99
+ className={`absolute top-full z-50 mt-2 w-56 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-bg-elevated)] py-1 shadow-brand ${
100
100
  align === 'left' ? 'left-0' : 'right-0'
101
101
  } ${menuClassName}`}
102
102
  >
@@ -65,7 +65,7 @@ export const Result = React.memo(function Result({
65
65
  // 徽标:白底卡片上用同色浅底,整块浅底上退回白色面 —— 两种都保证徽标里的图标仍压在同色浅底上。
66
66
  const badgeCls = tinted ? 'bg-[var(--color-bg-surface)]' : SOFT_TEXT[variant];
67
67
  return (
68
- <div className={`flex flex-col items-center rounded-lg border p-8 text-center shadow-sm ${surfaceCls} ${className}`}>
68
+ <div className={`flex flex-col items-center rounded-lg border p-8 text-center shadow-card ${surfaceCls} ${className}`}>
69
69
  {glyph ? (
70
70
  <span className={`flex h-16 w-16 items-center justify-center rounded-full ${badgeCls} ${tinted ? textTone(variant) : ''}`}>
71
71
  {glyph}
@@ -34,7 +34,7 @@ export const SectionCard = React.memo(function SectionCard({
34
34
  }: SectionCardProps) {
35
35
  return (
36
36
  <div
37
- className={`rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] shadow-sm ${paddingMap[padding]} ${className}`}
37
+ className={`rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-bg-surface)] shadow-card ${paddingMap[padding]} ${className}`}
38
38
  >
39
39
  {title && (
40
40
  <h2 className="mb-4 text-base font-semibold text-[var(--color-text-primary)]">{title}</h2>
@@ -87,13 +87,13 @@ export function ToastProvider({ children }: { children: ReactNode }) {
87
87
  return (
88
88
  <div
89
89
  key={toast.id}
90
- className={`pointer-events-auto flex items-center gap-2 rounded-xl px-4 py-2.5 text-sm font-medium shadow-lg ${STYLES[toast.type]}`}
90
+ className={`pointer-events-auto flex items-center gap-2 rounded-xl px-4 py-2.5 text-sm font-medium shadow-brand ${STYLES[toast.type]}`}
91
91
  >
92
92
  <Icon className="h-4 w-4 shrink-0" />
93
93
  <span className="flex-1">{toast.message}</span>
94
94
  <button
95
95
  onClick={() => removeToast(toast.id)}
96
- className="ml-1 shrink-0 rounded p-0.5 opacity-70 hover:bg-white/20 hover:opacity-100"
96
+ className="ml-1 shrink-0 rounded-xs p-0.5 opacity-70 hover:bg-white/20 hover:opacity-100"
97
97
  >
98
98
  <X className="h-3.5 w-3.5" />
99
99
  </button>
@@ -111,7 +111,7 @@ export function UserMenu({
111
111
  <div
112
112
  {...menuProps}
113
113
  ref={menuRef}
114
- className={`absolute top-full z-50 mt-2 w-56 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-bg-elevated)] py-1 shadow-lg ${
114
+ className={`absolute top-full z-50 mt-2 w-56 rounded-lg border border-[var(--color-border-subtle)] bg-[var(--color-bg-elevated)] py-1 shadow-brand ${
115
115
  align === 'left' ? 'left-0' : 'right-0'
116
116
  } ${menuClassName}`}
117
117
  >