@autional/ui 0.1.0-rc.53 → 0.1.0-rc.55
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.55",
|
|
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.18"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
88
|
"typecheck": "tsc --noEmit",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 头像/图标位统一显示件。
|
|
5
|
+
*
|
|
6
|
+
* 承载两类资源:用户头像、应用/租户 logo。核心约束只有一条 ——
|
|
7
|
+
* **URL 打不开时必须回落**,而不是把浏览器的碎图标直接落屏:
|
|
8
|
+
* - 无 `src` → 直接渲染回落(文本首字母或自定义节点);
|
|
9
|
+
* - `<img>` 触发 `onError`(404、DNS 失败、ORB 拦截等)→ 切到回落;
|
|
10
|
+
* - `src` 变化 → 重置失败标记(换头像后重新尝试)。
|
|
11
|
+
*
|
|
12
|
+
* 2026-10-28:此前四处 `<img src={avatarUrl}>` 都没有 onError,
|
|
13
|
+
* 一条死链(`avatars.autional.tianv.mobi/admin.png`)会在多个门户显示碎图。
|
|
14
|
+
*/
|
|
15
|
+
export interface AvatarProps {
|
|
16
|
+
/** 图片地址;空值或加载失败时回落 */
|
|
17
|
+
src?: string | null;
|
|
18
|
+
/** 回落文本:原样显示(通常已由调用方取好首字母) */
|
|
19
|
+
fallbackText?: string | null;
|
|
20
|
+
/** 自定义回落节点,优先于 fallbackText */
|
|
21
|
+
fallback?: React.ReactNode;
|
|
22
|
+
/** 像素尺寸,默认 32 */
|
|
23
|
+
size?: number;
|
|
24
|
+
/** 形状,默认圆形 */
|
|
25
|
+
shape?: 'circle' | 'square';
|
|
26
|
+
/** 图片替代文本;空串表示装饰性图片(同时标记 aria-hidden) */
|
|
27
|
+
alt?: string;
|
|
28
|
+
/** 根节点/图片追加类名(回落背景与文字色由调用方给) */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** 仅图片追加类名 */
|
|
31
|
+
imgClassName?: string;
|
|
32
|
+
/** 图片 referrer 策略;第三方图片(OAuth 客户端图标、租户 Logo)应传 no-referrer */
|
|
33
|
+
referrerPolicy?: React.HTMLAttributeReferrerPolicy;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Avatar({
|
|
37
|
+
src,
|
|
38
|
+
fallbackText,
|
|
39
|
+
fallback,
|
|
40
|
+
size = 32,
|
|
41
|
+
shape = 'circle',
|
|
42
|
+
alt = '',
|
|
43
|
+
className = '',
|
|
44
|
+
imgClassName = '',
|
|
45
|
+
referrerPolicy,
|
|
46
|
+
}: AvatarProps) {
|
|
47
|
+
const [broken, setBroken] = useState(false);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setBroken(false);
|
|
51
|
+
}, [src]);
|
|
52
|
+
|
|
53
|
+
const radius = shape === 'circle' ? 'rounded-full' : 'rounded-md';
|
|
54
|
+
const deco = alt ? undefined : true;
|
|
55
|
+
|
|
56
|
+
if (src && !broken) {
|
|
57
|
+
return (
|
|
58
|
+
<img
|
|
59
|
+
src={src}
|
|
60
|
+
alt={alt}
|
|
61
|
+
aria-hidden={deco}
|
|
62
|
+
width={size}
|
|
63
|
+
height={size}
|
|
64
|
+
loading="lazy"
|
|
65
|
+
decoding="async"
|
|
66
|
+
referrerPolicy={referrerPolicy}
|
|
67
|
+
onError={() => setBroken(true)}
|
|
68
|
+
className={`shrink-0 object-cover ${radius} ${imgClassName} ${className}`}
|
|
69
|
+
style={{ width: size, height: size }}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const content = fallback ?? (fallbackText ? fallbackText[0]!.toUpperCase() : '?');
|
|
75
|
+
return (
|
|
76
|
+
<span
|
|
77
|
+
aria-hidden={deco}
|
|
78
|
+
className={`flex shrink-0 items-center justify-center overflow-hidden font-medium ${radius} ${className}`}
|
|
79
|
+
style={{ width: size, height: size, fontSize: Math.max(10, Math.round(size * 0.4)) }}
|
|
80
|
+
>
|
|
81
|
+
{content}
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default Avatar;
|
package/src/atoms/index.ts
CHANGED
package/src/i18n/resources.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const uiI18nResources: Record<'zh-CN' | 'en-US', Record<string, string>>
|
|
|
23
23
|
'portal.names.developer': '开发者门户',
|
|
24
24
|
'portal.names.landing': '门户首页',
|
|
25
25
|
'portal.names.brand': '品牌站',
|
|
26
|
+
'portal.names.secret': '密钥中心',
|
|
26
27
|
// 整页占位(控制台的 PageLoading / PageError)。文案与 admin 站点里原有的
|
|
27
28
|
// pageStatus.* 三键**逐字一致** —— 合并只许改实现,不许顺手改用户看到的字。
|
|
28
29
|
'modal.close': '关闭',
|
|
@@ -57,6 +58,7 @@ export const uiI18nResources: Record<'zh-CN' | 'en-US', Record<string, string>>
|
|
|
57
58
|
'portal.names.developer': 'Developer Portal',
|
|
58
59
|
'portal.names.landing': 'Home',
|
|
59
60
|
'portal.names.brand': 'Brand Site',
|
|
61
|
+
'portal.names.secret': 'Secret Center',
|
|
60
62
|
'modal.close': 'Close',
|
|
61
63
|
'pageStatus.loading': 'Loading...',
|
|
62
64
|
'pageStatus.loadError': 'Data loading failed',
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Code2,
|
|
7
7
|
Globe,
|
|
8
8
|
KeyRound,
|
|
9
|
+
KeySquare,
|
|
9
10
|
LayoutGrid,
|
|
10
11
|
LogIn,
|
|
11
12
|
Settings,
|
|
@@ -53,6 +54,7 @@ const DEFAULT_PORTAL_ICONS: Record<string, React.ComponentType<{ className?: str
|
|
|
53
54
|
platform: Settings,
|
|
54
55
|
developer: Code2,
|
|
55
56
|
landing: Globe,
|
|
57
|
+
secret: KeySquare,
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
export function PortalSwitcher({
|
|
@@ -2,6 +2,7 @@ import type React from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { ChevronDown, LogOut, Settings, UserRound } from 'lucide-react';
|
|
4
4
|
import { useMenuButton } from '../internal/use-menu-button';
|
|
5
|
+
import { Avatar } from '../atoms/Avatar';
|
|
5
6
|
|
|
6
7
|
export interface UserMenuUserInfo {
|
|
7
8
|
displayName?: string | null;
|
|
@@ -93,13 +94,12 @@ export function UserMenu({
|
|
|
93
94
|
aria-label={text.userMenu}
|
|
94
95
|
className={`flex items-center gap-2 rounded-md p-1.5 transition-colors hover:bg-[var(--color-bg-muted)] ${className}`}
|
|
95
96
|
>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)}
|
|
97
|
+
<Avatar
|
|
98
|
+
src={user?.avatarUrl}
|
|
99
|
+
fallbackText={initials}
|
|
100
|
+
size={32}
|
|
101
|
+
className="bg-[var(--color-brand)] text-xs font-medium text-[var(--color-text-inverse)]"
|
|
102
|
+
/>
|
|
103
103
|
<span
|
|
104
104
|
className={`max-w-[10rem] truncate text-sm text-[var(--color-text-primary)] ${nameClassName}`}
|
|
105
105
|
>
|