@autional/ui 0.1.0-rc.47 → 0.1.0-rc.48
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.
|
|
3
|
+
"version": "0.1.0-rc.48",
|
|
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.16"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
88
|
"typecheck": "tsc --noEmit",
|
|
@@ -69,7 +69,7 @@ export const AppShell = React.memo(function AppShell({
|
|
|
69
69
|
{/* 移动端遮罩:点它关闭抽屉。桌面端不渲染(lg:hidden)。 */}
|
|
70
70
|
{mobileOpen && (
|
|
71
71
|
<div
|
|
72
|
-
className="fixed inset-0 z-40 bg-
|
|
72
|
+
className="fixed inset-0 z-40 bg-scrim/30 lg:hidden"
|
|
73
73
|
onClick={onMobileClose}
|
|
74
74
|
aria-hidden="true"
|
|
75
75
|
/>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
import { AlertTriangle, X } from 'lucide-react';
|
|
3
4
|
|
|
4
5
|
interface ConfirmDialogProps {
|
|
@@ -59,9 +60,11 @@ export const ConfirmDialog = React.memo(function ConfirmDialog({
|
|
|
59
60
|
|
|
60
61
|
const style = variantStyles[variant];
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
// 与 Modal 同因同解(第 63 轮补·四):portal 到 body,否则「有 backdrop-filter 的祖先」
|
|
64
|
+
// 会成为 fixed 后代的包含块,遮罩只覆盖那一小块。
|
|
65
|
+
const content = (
|
|
63
66
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
64
|
-
<div className="absolute inset-0 bg-
|
|
67
|
+
<div className="absolute inset-0 bg-scrim/50 backdrop-blur-sm" onClick={onCancel} />
|
|
65
68
|
<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
69
|
<button
|
|
67
70
|
onClick={onCancel}
|
|
@@ -98,4 +101,7 @@ export const ConfirmDialog = React.memo(function ConfirmDialog({
|
|
|
98
101
|
</div>
|
|
99
102
|
</div>
|
|
100
103
|
);
|
|
104
|
+
|
|
105
|
+
if (typeof document === 'undefined') return content;
|
|
106
|
+
return createPortal(content, document.body);
|
|
101
107
|
});
|
package/src/molecules/Modal.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useId, useRef } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
import { X } from 'lucide-react';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
5
|
import { UI_I18N_NS, uiText } from '../i18n';
|
|
@@ -128,8 +129,14 @@ export const Modal = React.memo(function Modal({
|
|
|
128
129
|
|
|
129
130
|
if (!open) return null;
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
// ⚠ 必须 portal 到 document.body(第 63 轮补·四实测):
|
|
133
|
+
// `position: fixed` 的包含块**不是视口**,而是最近的「有 transform / filter / backdrop-filter /
|
|
134
|
+
// will-change / contain」的祖先。站点的顶栏内层写了 `backdrop-blur-xl`,于是**在顶栏里打开的浮层**
|
|
135
|
+
// 的 inset-0 只覆盖顶栏那一小块 —— 实测 www.autional.cn 的搜索面板:遮罩是 976x135 的一条,
|
|
136
|
+
// 而不是整屏(用户的原话是「弹出一个黑色遮罩,怪怪的」)。
|
|
137
|
+
// 浮层属于顶层,不属于触发它的那棵子树;portal 是这条规则的结构性实现。
|
|
138
|
+
const content = (
|
|
139
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-scrim/50 p-4">
|
|
133
140
|
<div className="absolute inset-0" onClick={onClose} />
|
|
134
141
|
<div
|
|
135
142
|
ref={panelRef}
|
|
@@ -175,4 +182,8 @@ export const Modal = React.memo(function Modal({
|
|
|
175
182
|
</div>
|
|
176
183
|
</div>
|
|
177
184
|
);
|
|
185
|
+
|
|
186
|
+
// SSR / 测试环境没有 document 时退回就地渲染(portal 的目标不存在)
|
|
187
|
+
if (typeof document === 'undefined') return content;
|
|
188
|
+
return createPortal(content, document.body);
|
|
178
189
|
});
|
package/src/molecules/Toast.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
|
|
11
|
+
import { SOFT_BORDER, SOFT_TEXT } from '../internal/semantic-styles';
|
|
11
12
|
|
|
12
13
|
export type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
13
14
|
|
|
@@ -42,11 +43,18 @@ const ICONS: Record<ToastType, typeof CheckCircle> = {
|
|
|
42
43
|
info: Info,
|
|
43
44
|
};
|
|
44
45
|
|
|
46
|
+
// 第 63 轮补·四:改用与 Alert / StatusBadge **同一份**语义配对(internal/semantic-styles.ts 的 SOFT_TEXT)。
|
|
47
|
+
// 此前是「实底 + 白字」,实测对白字的对比度 ≈ success 2.6 / error 3.4 / warning 2.2 : 1 —— 三档都不达 AA 4.5:1,
|
|
48
|
+
// 而 Toast 不在任何对比度目标页里,所以这条判据从未扫到它(「不是判据错了,是判据没扫到」)。
|
|
49
|
+
// 换成 -soft / -text 配对后,对比度是设计系统建这一份配对时就验过的:
|
|
50
|
+
// success 5.51 / warning 4.85 / danger 4.65 / info 6.70 —— 与 Alert 同一个来源,不再有第二套。
|
|
51
|
+
// 底 + 字 + 边框三件都取自那一份配对(Alert / StatusBadge 同源):软底若不带边框,
|
|
52
|
+
// 贴在白卡上分不出边界 —— Alert 早就是这么做的,Toast 直接跟它对齐。
|
|
45
53
|
const STYLES: Record<ToastType, string> = {
|
|
46
|
-
success:
|
|
47
|
-
error:
|
|
48
|
-
warning:
|
|
49
|
-
info: '
|
|
54
|
+
success: SOFT_TEXT.success + ' ' + SOFT_BORDER.success,
|
|
55
|
+
error: SOFT_TEXT.danger + ' ' + SOFT_BORDER.danger,
|
|
56
|
+
warning: SOFT_TEXT.warning + ' ' + SOFT_BORDER.warning,
|
|
57
|
+
info: SOFT_TEXT.info + ' ' + SOFT_BORDER.info,
|
|
50
58
|
};
|
|
51
59
|
|
|
52
60
|
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
@@ -87,13 +95,13 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
|
|
87
95
|
return (
|
|
88
96
|
<div
|
|
89
97
|
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-
|
|
98
|
+
className={`pointer-events-auto flex items-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-medium shadow-card ${STYLES[toast.type]}`}
|
|
91
99
|
>
|
|
92
100
|
<Icon className="h-4 w-4 shrink-0" />
|
|
93
101
|
<span className="flex-1">{toast.message}</span>
|
|
94
102
|
<button
|
|
95
103
|
onClick={() => removeToast(toast.id)}
|
|
96
|
-
className="ml-1 shrink-0 rounded-xs p-0.5 opacity-70 hover:
|
|
104
|
+
className="ml-1 shrink-0 rounded-xs p-0.5 opacity-70 hover:opacity-100"
|
|
97
105
|
>
|
|
98
106
|
<X className="h-3.5 w-3.5" />
|
|
99
107
|
</button>
|