@blocklet/ui-react 3.2.17 → 3.2.19

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 (39) hide show
  1. package/lib/Dashboard/app-shell/app-badge.d.ts +24 -0
  2. package/lib/Dashboard/app-shell/app-badge.js +48 -0
  3. package/lib/Dashboard/app-shell/app-header.d.ts +5 -0
  4. package/lib/Dashboard/app-shell/app-header.js +72 -0
  5. package/lib/Dashboard/app-shell/app-info-context.d.ts +42 -0
  6. package/lib/Dashboard/app-shell/app-info-context.js +83 -0
  7. package/lib/Dashboard/app-shell/badges/app-badge-default.d.ts +20 -0
  8. package/lib/Dashboard/app-shell/badges/app-badge-default.js +84 -0
  9. package/lib/Dashboard/app-shell/badges/app-badge-did.d.ts +5 -0
  10. package/lib/Dashboard/app-shell/badges/app-badge-did.js +16 -0
  11. package/lib/Dashboard/app-shell/badges/app-badge-state.d.ts +6 -0
  12. package/lib/Dashboard/app-shell/badges/app-badge-state.js +34 -0
  13. package/lib/Dashboard/app-shell/badges/app-badge-switch.d.ts +8 -0
  14. package/lib/Dashboard/app-shell/badges/app-badge-switch.js +66 -0
  15. package/lib/Dashboard/app-shell/badges/app-badge-version.d.ts +14 -0
  16. package/lib/Dashboard/app-shell/badges/app-badge-version.js +50 -0
  17. package/lib/Dashboard/app-shell/index.d.ts +4 -0
  18. package/lib/Dashboard/app-shell/index.js +9 -0
  19. package/lib/Dashboard/index.d.ts +12 -2
  20. package/lib/Dashboard/index.js +83 -63
  21. package/lib/Footer/internal-footer.js +11 -11
  22. package/lib/Footer/links.d.ts +5 -3
  23. package/lib/Footer/links.js +63 -61
  24. package/lib/utils.js +28 -28
  25. package/package.json +12 -6
  26. package/src/Dashboard/app-shell/app-badge.stories.tsx +64 -0
  27. package/src/Dashboard/app-shell/app-badge.tsx +94 -0
  28. package/src/Dashboard/app-shell/app-header.tsx +104 -0
  29. package/src/Dashboard/app-shell/app-info-context.tsx +182 -0
  30. package/src/Dashboard/app-shell/badges/app-badge-default.tsx +131 -0
  31. package/src/Dashboard/app-shell/badges/app-badge-did.tsx +28 -0
  32. package/src/Dashboard/app-shell/badges/app-badge-state.tsx +40 -0
  33. package/src/Dashboard/app-shell/badges/app-badge-switch.tsx +72 -0
  34. package/src/Dashboard/app-shell/badges/app-badge-version.tsx +60 -0
  35. package/src/Dashboard/app-shell/index.ts +5 -0
  36. package/src/Dashboard/index.jsx +17 -3
  37. package/src/Footer/internal-footer.jsx +1 -1
  38. package/src/Footer/links.jsx +11 -7
  39. package/src/utils.js +6 -3
@@ -0,0 +1,64 @@
1
+ import { Box, Stack, Typography } from '@mui/material';
2
+ import { useState } from 'react';
3
+ import { ThemeProvider } from '@arcblock/ux/lib/Theme';
4
+ import { ThemeModeToggle } from '@arcblock/ux/lib/Config';
5
+ import AppBadge from './app-badge';
6
+
7
+ export default {
8
+ title: 'Blocklet-UI-React/Dashboard',
9
+ component: AppBadge,
10
+ };
11
+
12
+ export function Badges() {
13
+ const [checked, setChecked] = useState(true);
14
+
15
+ return (
16
+ <ThemeProvider prefer="system">
17
+ <ThemeModeToggle />
18
+ <Box sx={{ p: 3, backgroundColor: 'background.default' }}>
19
+ <Stack spacing={2}>
20
+ <Typography variant="h6">Loading</Typography>
21
+ <Stack direction="row" spacing={2}>
22
+ <AppBadge label="Loading" value="Value" loading />
23
+ </Stack>
24
+ <Typography variant="h6">Default</Typography>
25
+ <Stack direction="row" spacing={2}>
26
+ <AppBadge label="Label" value="Value" />
27
+ <AppBadge icon="lucide:info" label="Label" value="Value" />
28
+ </Stack>
29
+ <Typography variant="h6">Number</Typography>
30
+ <Stack direction="row" spacing={2}>
31
+ <AppBadge variant="number" value={1234} />
32
+ <AppBadge variant="number" value={1234567} round={1} />
33
+ <AppBadge variant="number" label="Label" value={1234567890} round={2} />
34
+ </Stack>
35
+ <Typography variant="h6">Time</Typography>
36
+ <Stack direction="row" spacing={2}>
37
+ <AppBadge variant="time" value={new Date()} />
38
+ <AppBadge variant="time" label="Label" value={Date.now() - 3600000} />
39
+ </Stack>
40
+ <Typography variant="h6">State</Typography>
41
+ <Stack direction="row" spacing={2}>
42
+ <AppBadge variant="state" value="Running" color="success" />
43
+ <AppBadge variant="state" value="Stopped" color="error" />
44
+ <AppBadge variant="state" value="Warning" color="warning" />
45
+ </Stack>
46
+ <Typography variant="h6">Version</Typography>
47
+ <Stack direction="row" spacing={2}>
48
+ <AppBadge variant="version" value="1.0.0" color="info" />
49
+ <AppBadge variant="version" icon="lucide:package" value="2.3.4" color="primary" />
50
+ </Stack>
51
+ <Typography variant="h6">DID</Typography>
52
+ <Stack direction="row" spacing={2}>
53
+ <AppBadge variant="did" value="z1kFxaZ5hJXHhqJzXqJzXqJzXqJzXqJzXqJz" />
54
+ <AppBadge variant="did" value="0x1234567890123456789012345678901234567890" />
55
+ </Stack>
56
+ <Typography variant="h6">Switch</Typography>
57
+ <Stack direction="row" spacing={2}>
58
+ <AppBadge variant="switch" label="Toggle" value={checked} onChange={setChecked} />
59
+ </Stack>
60
+ </Stack>
61
+ </Box>
62
+ </ThemeProvider>
63
+ );
64
+ }
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import dayjs from 'dayjs';
3
+ import relativeTime from 'dayjs/plugin/relativeTime';
4
+ import localizedFormat from 'dayjs/plugin/localizedFormat';
5
+ import 'dayjs/locale/zh';
6
+ import 'dayjs/locale/zh-tw';
7
+ import 'dayjs/locale/ja';
8
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
9
+ import { AppBadgeDefault, AppBadgeDefaultProps } from './badges/app-badge-default';
10
+ import { AppBadgeSwitch, AppBadgeSwitchProps } from './badges/app-badge-switch';
11
+ import { AppBadgeVersion, AppBadgeVersionProps } from './badges/app-badge-version';
12
+ import { AppBadgeState, AppBadgeStateProps } from './badges/app-badge-state';
13
+ import { AppBadgeDID, AppBadgeDIDProps } from './badges/app-badge-did';
14
+
15
+ dayjs.extend(relativeTime);
16
+ dayjs.extend(localizedFormat);
17
+
18
+ export const formatNumber = (num: unknown, round: number = 0): string => {
19
+ if (typeof num !== 'number') return '';
20
+
21
+ const absNum = Math.abs(num);
22
+ let rounded;
23
+ let unit = '';
24
+
25
+ if (absNum >= 1e12) {
26
+ unit = 'T';
27
+ rounded = `${(num / 1e12).toFixed(round)}`; // 万亿
28
+ } else if (absNum >= 1e9) {
29
+ unit = 'B';
30
+ rounded = `${(num / 1e9).toFixed(round)}`; // 十亿
31
+ } else if (absNum >= 1e6) {
32
+ unit = 'M';
33
+ rounded = `${(num / 1e6).toFixed(round)}`; // 百万
34
+ } else if (absNum >= 1e3) {
35
+ unit = 'K';
36
+ rounded = `${(num / 1e3).toFixed(round)}`; // 千
37
+ } else {
38
+ rounded = num.toString();
39
+ }
40
+
41
+ // 去掉尾部的零
42
+ if (round > 0) {
43
+ rounded = rounded.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
44
+ }
45
+
46
+ return `${rounded}${unit}`;
47
+ };
48
+
49
+ export const formatTime = (value?: string | number | Date, locale: string = 'en'): string => {
50
+ if (!value) return '';
51
+
52
+ return dayjs(value).locale(locale).fromNow();
53
+ };
54
+
55
+ const BadgeVariants: Record<string, React.ComponentType<any>> = {
56
+ switch: AppBadgeSwitch,
57
+ version: AppBadgeVersion,
58
+ state: AppBadgeState,
59
+ did: AppBadgeDID,
60
+ };
61
+
62
+ export type AppBadgeProps =
63
+ | (AppBadgeDefaultProps & { variant?: 'default' })
64
+ | (AppBadgeDefaultProps & { variant: 'number' })
65
+ | (Omit<AppBadgeDefaultProps, 'value'> & { variant: 'time'; value?: string | number | Date })
66
+ | (AppBadgeSwitchProps & { variant: 'switch' })
67
+ | (AppBadgeVersionProps & { variant: 'version' })
68
+ | (AppBadgeStateProps & { variant: 'state' })
69
+ | (AppBadgeDIDProps & { variant: 'did' });
70
+
71
+ export default function AppBadge(props: AppBadgeProps) {
72
+ const { locale = 'en' } = useLocaleContext() || {};
73
+
74
+ if (props.variant === 'number') {
75
+ const { value, round = 0, ...rest } = props;
76
+ const val = formatNumber(Number(value), round);
77
+
78
+ return <AppBadgeDefault value={val} {...rest} />;
79
+ }
80
+ if (props.variant === 'time') {
81
+ const { value, ...rest } = props;
82
+ const val = formatTime(value, locale);
83
+
84
+ return <AppBadgeDefault value={val} {...rest} />;
85
+ }
86
+ if (props.variant) {
87
+ const Badge = BadgeVariants[props.variant];
88
+ if (Badge) {
89
+ return <Badge {...props} />;
90
+ }
91
+ }
92
+
93
+ return <AppBadgeDefault {...(props as AppBadgeDefaultProps)} />;
94
+ }
@@ -0,0 +1,104 @@
1
+ import { isValidElement, useEffect } from 'react';
2
+ import { Box, Stack, Typography, useMediaQuery, SxProps, Theme } from '@mui/material';
3
+ import Tabs from '@arcblock/ux/lib/Tabs';
4
+ import AppBadge from './app-badge';
5
+ import { useAppInfo } from './app-info-context';
6
+
7
+ function useMobile(): boolean {
8
+ const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
9
+
10
+ return isMobile;
11
+ }
12
+
13
+ interface DividerProps {
14
+ sx?: SxProps<Theme>;
15
+ }
16
+
17
+ function Divider({ sx = {} }: DividerProps) {
18
+ const isMobile = useMobile();
19
+
20
+ return (
21
+ <Box sx={{ mx: isMobile ? -2 : -3, borderBottom: '1px solid', borderColor: 'divider', mt: 1.5, mb: 1, ...sx }} />
22
+ );
23
+ }
24
+
25
+ interface AppHeaderProps {
26
+ onTabChange?: (tab: string) => void;
27
+ }
28
+
29
+ function AppHeader({ onTabChange = undefined }: AppHeaderProps) {
30
+ const isMobile = useMobile();
31
+ const {
32
+ inService,
33
+ navItem,
34
+ icon = undefined,
35
+ name = '',
36
+ description = undefined,
37
+ actions = undefined,
38
+ badges = [],
39
+ tabs = [],
40
+ currentTab = '',
41
+ updateAppInfo,
42
+ } = useAppInfo();
43
+
44
+ // 非 Service 应用,自动更新 name / description
45
+ useEffect(() => {
46
+ if (!inService) {
47
+ updateAppInfo({
48
+ name: navItem?.title || '',
49
+ description: navItem?.description || '',
50
+ });
51
+ }
52
+ }, [navItem?.title, navItem?.description, inService, updateAppInfo]);
53
+
54
+ if (!name) {
55
+ return null;
56
+ }
57
+
58
+ return (
59
+ <Box className="app-header" sx={{ mt: 3, mb: 3 }}>
60
+ {/* Basic Info */}
61
+ <Box
62
+ sx={{
63
+ display: 'flex',
64
+ alignItems: 'center',
65
+ gap: 1,
66
+ }}>
67
+ {icon && <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>{icon}</Box>}
68
+ <Stack sx={{ flexGrow: 1 }}>
69
+ <Typography variant="h1" sx={{ mb: 0.5 }}>
70
+ {name}
71
+ </Typography>
72
+ {description && (
73
+ <Typography
74
+ variant="body2"
75
+ color="text.secondary"
76
+ sx={{ lineHeight: 1.6, '& a': { color: 'primary.main' }, maxWidth: 980 }}>
77
+ {description}
78
+ </Typography>
79
+ )}
80
+ </Stack>
81
+ {!isMobile && actions && <Box sx={{ ml: 1 }}>{actions}</Box>}
82
+ </Box>
83
+ {/* Badges */}
84
+ {badges.length > 0 && (
85
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, alignItems: 'center', mt: 2 }}>
86
+ {badges.map((config, index) =>
87
+ isValidElement(config) ? config : <AppBadge key={config.label || index} {...config} />
88
+ )}
89
+ </Box>
90
+ )}
91
+ {isMobile && actions && <Box sx={{ mt: isMobile ? 2 : 0 }}>{actions}</Box>}
92
+ {/* Tabs */}
93
+ {tabs.length <= 1 && <Divider />}
94
+ {tabs.length > 1 && (
95
+ <>
96
+ <Tabs tabs={tabs} current={currentTab} onChange={onTabChange} scrollButtons="auto" sx={{ mt: 2.5 }} />
97
+ <Divider sx={{ mt: 0 }} />
98
+ </>
99
+ )}
100
+ </Box>
101
+ );
102
+ }
103
+
104
+ export default AppHeader;
@@ -0,0 +1,182 @@
1
+ import React, { createContext, useState, useContext, useMemo } from 'react';
2
+ import { useCreation, useMemoizedFn } from 'ahooks';
3
+ import { withoutTrailingSlash } from 'ufo';
4
+ import isPlainObject from 'lodash/isPlainObject';
5
+ import { WELLKNOWN_BLOCKLET_ADMIN_PATH } from '@abtnode/constant';
6
+ import { Locale } from '@arcblock/ux/lib/type';
7
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
8
+ import { AppBadgeProps } from './app-badge';
9
+
10
+ export type NavItem = {
11
+ id: string;
12
+ parent: string; // pid
13
+ title: string;
14
+ link: string;
15
+ description?: string;
16
+ };
17
+
18
+ export interface TabConfig {
19
+ value: string;
20
+ label?: string;
21
+ render?: React.ComponentType | React.ReactNode;
22
+ }
23
+
24
+ export interface AppInfo {
25
+ icon?: React.ReactNode;
26
+ name?: React.ReactNode;
27
+ description?: React.ReactNode;
28
+ badges?: Array<AppBadgeProps | React.ReactElement>;
29
+ actions?: React.ReactNode;
30
+ tabs?: TabConfig[];
31
+ currentTab?: string;
32
+ }
33
+
34
+ export interface AppInfoContextValue extends AppInfo {
35
+ inService: boolean;
36
+ currentTab: string;
37
+ TabComponent: React.ComponentType | React.ReactNode;
38
+ navItem?: NavItem;
39
+ updateAppInfo: (patch: Partial<AppInfo>) => void;
40
+ }
41
+
42
+ const AppInfoContext = createContext<AppInfoContextValue>({
43
+ inService: false,
44
+ currentTab: '',
45
+ TabComponent: null,
46
+ navItem: undefined,
47
+ updateAppInfo: () => {},
48
+ });
49
+
50
+ export const getI18nVal = (
51
+ obj: Record<string, string | Record<Locale, string>>,
52
+ key: string,
53
+ locale: Locale = 'en'
54
+ ) => {
55
+ const val = obj?.[key];
56
+
57
+ // @ts-ignore
58
+ return isPlainObject(val) ? val[locale] || val.en : val;
59
+ };
60
+
61
+ // 基于前缀匹配算法查找 NavItem
62
+ export const findNavItem = (items: NavItem[], targetLink: string = '', locale: Locale = 'en') => {
63
+ const targetParts = targetLink === '/' ? ['/'] : targetLink.split('/').filter(Boolean);
64
+
65
+ let result: NavItem | null = null;
66
+ let maxLen = 0;
67
+
68
+ items.forEach((item) => {
69
+ const currentLink = getI18nVal(item, 'link', locale) || '';
70
+ const currentParts = currentLink === '/' ? ['/'] : currentLink.split('/').filter(Boolean);
71
+
72
+ if (currentParts.length > targetParts.length) {
73
+ return;
74
+ }
75
+
76
+ let isMatch = true;
77
+ for (let i = 0; i < currentParts.length; i++) {
78
+ if (currentParts[i] !== targetParts[i]) {
79
+ isMatch = false;
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (isMatch && currentParts.length > maxLen) {
85
+ result = item;
86
+ maxLen = currentParts.length;
87
+ }
88
+ });
89
+
90
+ return result;
91
+ };
92
+
93
+ interface AppInfoProviderProps {
94
+ path?: string;
95
+ currentTab?: string;
96
+ meta?: object;
97
+ children?: React.ReactNode;
98
+ }
99
+ export function AppInfoProvider({
100
+ path = window?.location?.pathname || '',
101
+ currentTab = '',
102
+ meta = undefined,
103
+ children = null,
104
+ }: AppInfoProviderProps) {
105
+ const { locale = 'en' } = useLocaleContext() || {};
106
+ const targetLink = withoutTrailingSlash(path);
107
+ const inService = targetLink.startsWith(WELLKNOWN_BLOCKLET_ADMIN_PATH);
108
+ // 便于测试
109
+ const blockletMeta = useCreation(() => Object.assign({}, window.blocklet, meta), [meta]);
110
+
111
+ // 匹配 Dashboard NavItem
112
+ const navItem = useCreation(() => {
113
+ // @ts-ignore
114
+ const navigations = blockletMeta.navigation?.filter((v) =>
115
+ Array.isArray(v.section) ? v.section.includes('dashboard') : v.section === 'dashboard'
116
+ );
117
+ const items: NavItem[] = navigations?.flatMap((v: { items?: NavItem[] }) => v.items || []);
118
+ // 前缀匹配 navItem
119
+ const item = findNavItem(items, targetLink, locale);
120
+
121
+ if (!item) {
122
+ return undefined;
123
+ }
124
+
125
+ const parentItem = navigations.find((v: NavItem) => v.id === item.parent);
126
+ let title = getI18nVal(item, 'title', locale) || '';
127
+
128
+ if (!inService) {
129
+ // 应用带上父级标题
130
+ title = `${getI18nVal(parentItem, 'title', locale) || ''} - ${title}`;
131
+ }
132
+
133
+ return {
134
+ id: item.id,
135
+ parent: item.parent,
136
+ title,
137
+ link: getI18nVal(item, 'link', locale) || '',
138
+ description: getI18nVal(item, 'description', locale) || '',
139
+ } as NavItem;
140
+ }, [path, inService, locale, blockletMeta]);
141
+
142
+ const [appInfo, setAppInfo] = useState<AppInfo>({});
143
+
144
+ const updateAppInfo = useMemoizedFn((patch: Partial<AppInfo>) => {
145
+ setAppInfo((prev) => ({ ...prev, ...patch }));
146
+ });
147
+
148
+ const value = useMemo(
149
+ () => {
150
+ const { tabs = [] } = appInfo;
151
+ const currentTabConfig = tabs.find((v) => v.value === currentTab);
152
+
153
+ let ctab = currentTab;
154
+ let TabComponent = null;
155
+ // fallback 到第一个 tab
156
+ if (!currentTabConfig && tabs.length > 0) {
157
+ ctab = tabs[0].value;
158
+ TabComponent = tabs[0].render || null;
159
+ } else {
160
+ TabComponent = currentTabConfig?.render || null;
161
+ }
162
+
163
+ return {
164
+ ...appInfo,
165
+ inService,
166
+ navItem,
167
+ currentTab: ctab,
168
+ TabComponent,
169
+ updateAppInfo,
170
+ };
171
+ },
172
+ // eslint-disable-next-line react-hooks/exhaustive-deps
173
+ [appInfo, navItem, inService, currentTab, updateAppInfo]
174
+ );
175
+
176
+ return <AppInfoContext.Provider value={value}>{children}</AppInfoContext.Provider>;
177
+ }
178
+
179
+ export function useAppInfo(): AppInfoContextValue {
180
+ const context = useContext(AppInfoContext);
181
+ return context;
182
+ }
@@ -0,0 +1,131 @@
1
+ import { Box, Skeleton, styled, Typography } from '@mui/material';
2
+ import { Icon } from '@iconify/react';
3
+ import { TBox } from '@arcblock/ux/lib/MuiWrap';
4
+ import { Link } from 'react-router-dom';
5
+ import { BoxProps } from '@mui/material/Box';
6
+
7
+ export const isExternal = (to: string = ''): boolean => {
8
+ return to.startsWith('http:') || to.startsWith('https:');
9
+ };
10
+
11
+ export const isSameOrigin = (to: string): boolean => {
12
+ try {
13
+ const url = new URL(to);
14
+ return url.origin === window.location.origin;
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
19
+
20
+ export const StateIcon = styled('span')<{ color: string }>(({ theme, color }) => {
21
+ return {
22
+ position: 'relative',
23
+ display: 'inline-block',
24
+ width: theme.spacing(1),
25
+ height: theme.spacing(1),
26
+ borderRadius: '50%',
27
+ backgroundColor: color,
28
+ marginRight: theme.spacing(1),
29
+ };
30
+ });
31
+ export const StyledBadge = styled(Box)(({ theme }) => ({
32
+ position: 'relative',
33
+ display: 'inline-flex',
34
+ alignItems: 'center',
35
+ height: theme.spacing(3),
36
+ paddingLeft: theme.spacing(1.5),
37
+ paddingRight: theme.spacing(1.5),
38
+ border: '1px solid',
39
+ borderColor: theme.palette.divider,
40
+ borderRadius: (theme.shape.borderRadius as number) * 0.5,
41
+ fontSize: 12,
42
+ overflow: 'hidden',
43
+ '& .app-badge-label': {
44
+ display: 'inline-flex',
45
+ gap: theme.spacing(1),
46
+ alignItems: 'center',
47
+ lineHeight: theme.spacing(3),
48
+ marginLeft: theme.spacing(-1.5),
49
+ paddingLeft: theme.spacing(1.5),
50
+ marginRight: theme.spacing(1),
51
+ fontSize: 12,
52
+ backgroundColor: theme.palette.grey[100],
53
+ color: theme.palette.text.secondary,
54
+ '&::after': {
55
+ content: '""',
56
+ height: theme.spacing(3),
57
+ borderRight: '1px solid',
58
+ borderColor: theme.palette.divider,
59
+ backgroundColor: theme.palette.grey[100],
60
+ },
61
+ },
62
+ '& .app-badge-value': {
63
+ color: theme.palette.text.primary,
64
+ fontSize: 12,
65
+ },
66
+ })) as typeof TBox;
67
+
68
+ export interface BadgeContainerProps extends Omit<BoxProps, 'to'> {
69
+ loading?: boolean;
70
+ to?: string;
71
+ }
72
+
73
+ export function BadgeContainer({ loading = false, children = null, to = '', ...rest }: BadgeContainerProps) {
74
+ const container = (
75
+ <StyledBadge className="app-badge" component={to ? 'a' : 'div'} href={to} {...rest}>
76
+ {children}
77
+ </StyledBadge>
78
+ );
79
+
80
+ if (loading) {
81
+ return <Skeleton variant="rounded">{container}</Skeleton>;
82
+ }
83
+
84
+ if (to) {
85
+ if (isExternal(to)) {
86
+ const target = isSameOrigin(to) ? '_self' : '_blank';
87
+
88
+ // 外部链接跳转
89
+ return (
90
+ <StyledBadge className="app-badge" component="a" href={to} target={target} rel="noreferrer noopener" {...rest}>
91
+ {children}
92
+ </StyledBadge>
93
+ );
94
+ }
95
+
96
+ // 内部路由跳转
97
+ return (
98
+ <StyledBadge className="app-badge" component={Link} to={to} {...rest}>
99
+ {children}
100
+ </StyledBadge>
101
+ );
102
+ }
103
+
104
+ // 无跳转
105
+ return (
106
+ <StyledBadge className="app-badge" component="div" {...rest}>
107
+ {children}
108
+ </StyledBadge>
109
+ );
110
+ }
111
+
112
+ export interface AppBadgeDefaultProps extends BadgeContainerProps {
113
+ icon?: string;
114
+ label?: string;
115
+ value?: string | number;
116
+ round?: number;
117
+ }
118
+
119
+ export function AppBadgeDefault({ icon = '', label = '', value = '', loading = false, ...rest }: AppBadgeDefaultProps) {
120
+ return (
121
+ <BadgeContainer loading={loading} {...rest}>
122
+ {label && (
123
+ <Typography className="app-badge-label">
124
+ {icon && <Icon icon={icon} />}
125
+ {label}
126
+ </Typography>
127
+ )}
128
+ <Typography className="app-badge-value">{value}</Typography>
129
+ </BadgeContainer>
130
+ );
131
+ }
@@ -0,0 +1,28 @@
1
+ import { useCreation } from 'ahooks';
2
+ import { Typography } from '@mui/material';
3
+ import { isEthereumDid } from '@arcblock/ux/lib/Util';
4
+ import DidAddress from '@arcblock/ux/lib/Address';
5
+ import { AppBadgeDefaultProps, BadgeContainer } from './app-badge-default';
6
+
7
+ export interface AppBadgeDIDProps extends Omit<AppBadgeDefaultProps, 'value'> {
8
+ value?: string;
9
+ }
10
+
11
+ export function AppBadgeDID({ value = '', loading = false, ...rest }: AppBadgeDIDProps) {
12
+ const label = useCreation(() => {
13
+ const isEthDid = isEthereumDid(value);
14
+
15
+ return `DID:${isEthDid ? 'ETH' : 'ABT'}`;
16
+ }, [value]);
17
+
18
+ return (
19
+ <BadgeContainer loading={loading} {...rest}>
20
+ <Typography className="app-badge-label">{label}</Typography>
21
+ <Typography className="app-badge-value">
22
+ <DidAddress inline size={14} compact responsive={false}>
23
+ {value}
24
+ </DidAddress>
25
+ </Typography>
26
+ </BadgeContainer>
27
+ );
28
+ }
@@ -0,0 +1,40 @@
1
+ import { useTheme, alpha, Typography, styled } from '@mui/material';
2
+ import { AppBadgeDefaultProps, BadgeContainer } from './app-badge-default';
3
+ import { BadgeColor, getBgColor, getTextColor } from './app-badge-version';
4
+
5
+ const StateIcon = styled('span')<{ color: string }>(({ theme, color }) => {
6
+ return {
7
+ position: 'relative',
8
+ display: 'inline-block',
9
+ width: theme.spacing(1),
10
+ height: theme.spacing(1),
11
+ borderRadius: '50%',
12
+ backgroundColor: color,
13
+ marginRight: theme.spacing(1),
14
+ };
15
+ });
16
+
17
+ export interface AppBadgeStateProps extends AppBadgeDefaultProps {
18
+ color?: BadgeColor;
19
+ }
20
+
21
+ export function AppBadgeState({ value = '', color = 'primary', loading = false, ...rest }: AppBadgeStateProps) {
22
+ const theme = useTheme();
23
+ const txtcolor = getTextColor(theme, color);
24
+ const bgcolor = getBgColor(theme, color);
25
+
26
+ return (
27
+ <BadgeContainer
28
+ loading={loading}
29
+ sx={{
30
+ borderColor: alpha(txtcolor, 0.2),
31
+ bgcolor,
32
+ }}
33
+ {...rest}>
34
+ <StateIcon color={getTextColor(theme, color)} />
35
+ <Typography className="app-badge-value" style={{ color: txtcolor }}>
36
+ {value}
37
+ </Typography>
38
+ </BadgeContainer>
39
+ );
40
+ }