diff --git a/.gitignore b/.gitignore index 20bf28cb0..4e9403161 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /dist-web /dist-dweb /tmp +/.tmp /out-tsc /release /gh-pages diff --git a/TDD.md b/TDD.md index 873effe83..244fe03f8 100644 --- a/TDD.md +++ b/TDD.md @@ -1122,7 +1122,7 @@ export function WalletCard({ wallet }: WalletCardProps) { {wallet.name} {/* 宽容器时显示完整地址 */} -

+

{shortenAddress(wallet.address)} {wallet.address}

@@ -1133,7 +1133,7 @@ export function WalletCard({ wallet }: WalletCardProps) {

{formatBalance(wallet.balance)}

-

+

≈ ${wallet.fiatValue}

@@ -3100,7 +3100,7 @@ export function AuthorizeCard({ />

{appInfo.name}

-

{appInfo.home}

+

{appInfo.home}

diff --git a/e2e/__screenshots__/Desktop-Chrome/01-home-empty.png b/e2e/__screenshots__/Desktop-Chrome/01-home-empty.png index 2b27a3a99..2e9779a30 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/01-home-empty.png and b/e2e/__screenshots__/Desktop-Chrome/01-home-empty.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/07-verify-step.png b/e2e/__screenshots__/Desktop-Chrome/07-verify-step.png index 7df43a5d3..a8dc54c51 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/07-verify-step.png and b/e2e/__screenshots__/Desktop-Chrome/07-verify-step.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/authorize-address-page.png b/e2e/__screenshots__/Desktop-Chrome/authorize-address-page.png index 32cda1a2b..126f9c66f 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/authorize-address-page.png and b/e2e/__screenshots__/Desktop-Chrome/authorize-address-page.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/authorize-wallet-selector-main.png b/e2e/__screenshots__/Desktop-Chrome/authorize-wallet-selector-main.png index 32cda1a2b..279deb33d 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/authorize-wallet-selector-main.png and b/e2e/__screenshots__/Desktop-Chrome/authorize-wallet-selector-main.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/home-with-wallet.png b/e2e/__screenshots__/Desktop-Chrome/home-with-wallet.png index 85879d52c..69407d07a 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/home-with-wallet.png and b/e2e/__screenshots__/Desktop-Chrome/home-with-wallet.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-01-key-type.png b/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-01-key-type.png index dde481007..a3e10b4cf 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-01-key-type.png and b/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-01-key-type.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-02-preview.png b/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-02-preview.png index c055f1389..771a4701c 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-02-preview.png and b/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-dark-02-preview.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-rtl-01-preview.png b/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-rtl-01-preview.png index ac1125ba9..28d2fca0d 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-rtl-01-preview.png and b/e2e/__screenshots__/Desktop-Chrome/recover-arbitrary-rtl-01-preview.png differ diff --git a/e2e/__screenshots__/Desktop-Chrome/staking-overview.png b/e2e/__screenshots__/Desktop-Chrome/staking-overview.png index ad9064032..e5fe71303 100644 Binary files a/e2e/__screenshots__/Desktop-Chrome/staking-overview.png and b/e2e/__screenshots__/Desktop-Chrome/staking-overview.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/01-home-empty.png b/e2e/__screenshots__/Mobile-Chrome/01-home-empty.png index 7653d8510..1ec8ef08e 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/01-home-empty.png and b/e2e/__screenshots__/Mobile-Chrome/01-home-empty.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/07-verify-step.png b/e2e/__screenshots__/Mobile-Chrome/07-verify-step.png index 88f59bf4a..063e93e53 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/07-verify-step.png and b/e2e/__screenshots__/Mobile-Chrome/07-verify-step.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/authorize-address-page.png b/e2e/__screenshots__/Mobile-Chrome/authorize-address-page.png index f3ea528fa..2e4e640b5 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/authorize-address-page.png and b/e2e/__screenshots__/Mobile-Chrome/authorize-address-page.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/authorize-chain-selector-network.png b/e2e/__screenshots__/Mobile-Chrome/authorize-chain-selector-network.png index 706f4d040..57da844cd 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/authorize-chain-selector-network.png and b/e2e/__screenshots__/Mobile-Chrome/authorize-chain-selector-network.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/authorize-signature-transfer.png b/e2e/__screenshots__/Mobile-Chrome/authorize-signature-transfer.png index 6ddc3b25f..8832c9658 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/authorize-signature-transfer.png and b/e2e/__screenshots__/Mobile-Chrome/authorize-signature-transfer.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/authorize-wallet-selector-main.png b/e2e/__screenshots__/Mobile-Chrome/authorize-wallet-selector-main.png index f3ea528fa..f92bfd6c2 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/authorize-wallet-selector-main.png and b/e2e/__screenshots__/Mobile-Chrome/authorize-wallet-selector-main.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/home-with-wallet.png b/e2e/__screenshots__/Mobile-Chrome/home-with-wallet.png index 22abe4a74..ccd51d9b2 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/home-with-wallet.png and b/e2e/__screenshots__/Mobile-Chrome/home-with-wallet.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/migration-complete.png b/e2e/__screenshots__/Mobile-Chrome/migration-complete.png index 66fd4fd89..451596f88 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/migration-complete.png and b/e2e/__screenshots__/Mobile-Chrome/migration-complete.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/migration-progress.png b/e2e/__screenshots__/Mobile-Chrome/migration-progress.png index 66fd4fd89..451596f88 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/migration-progress.png and b/e2e/__screenshots__/Mobile-Chrome/migration-progress.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-01-key-type.png b/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-01-key-type.png index 323fb8d3d..6abc59b97 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-01-key-type.png and b/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-01-key-type.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-02-preview.png b/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-02-preview.png index 7f5188a7d..1195838d5 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-02-preview.png and b/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-dark-02-preview.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-rtl-01-preview.png b/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-rtl-01-preview.png index 17aeb2e07..6dd711b2b 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-rtl-01-preview.png and b/e2e/__screenshots__/Mobile-Chrome/recover-arbitrary-rtl-01-preview.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/settings-main.png b/e2e/__screenshots__/Mobile-Chrome/settings-main.png index aaf63ccce..37b3ee078 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/settings-main.png and b/e2e/__screenshots__/Mobile-Chrome/settings-main.png differ diff --git a/e2e/__screenshots__/Mobile-Chrome/staking-overview.png b/e2e/__screenshots__/Mobile-Chrome/staking-overview.png index c8097921c..a8137d0a9 100644 Binary files a/e2e/__screenshots__/Mobile-Chrome/staking-overview.png and b/e2e/__screenshots__/Mobile-Chrome/staking-overview.png differ diff --git a/package.json b/package.json index 643659c54..a39530c28 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "clsx": "^2.1.1", "i18next": "^25.7.1", "jsqr": "^1.4.0", - "lucide-react": "^0.556.0", "qrcode.react": "^4.2.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74eeae422..6e29a83b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,9 +74,6 @@ importers: jsqr: specifier: ^1.4.0 version: 1.4.0 - lucide-react: - specifier: ^0.556.0 - version: 0.556.0(react@19.2.1) qrcode.react: specifier: ^4.2.0 version: 4.2.0(react@19.2.1) @@ -3167,11 +3164,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.556.0: - resolution: {integrity: sha512-iOb8dRk7kLaYBZhR2VlV1CeJGxChBgUthpSP8wom9jfj79qovgG6qcSdiy6vkoREKPnbUYzJsCn4o4PtG3Iy+A==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -7344,10 +7336,6 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.556.0(react@19.2.1): - dependencies: - react: 19.2.1 - lz-string@1.5.0: {} magic-string@0.30.21: diff --git a/src/App.tsx b/src/App.tsx index a5df3258a..0b423e2f6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,10 @@ export default function App() { return ( -
+
-

BFM Pay

-

钱包应用开发中...

+

BFM Pay

+

钱包应用开发中...

- ) + ); } diff --git a/src/components/common/alert.test.tsx b/src/components/common/alert.test.tsx index 33d559c18..24535bd17 100644 --- a/src/components/common/alert.test.tsx +++ b/src/components/common/alert.test.tsx @@ -25,7 +25,7 @@ describe('Alert', () => { it('applies info variant by default', () => { const { container } = render(Info alert) - expect(container.querySelector('[role="alert"]')).toHaveClass('bg-blue-500/10') + expect(container.querySelector('[role="alert"]')).toHaveClass('bg-info/10') }) it('applies error variant styles', () => { @@ -35,12 +35,12 @@ describe('Alert', () => { it('applies success variant styles', () => { const { container } = render(Success alert) - expect(container.querySelector('[role="alert"]')).toHaveClass('bg-green-500/10') + expect(container.querySelector('[role="alert"]')).toHaveClass('bg-success/10') }) it('applies warning variant styles', () => { const { container } = render(Warning alert) - expect(container.querySelector('[role="alert"]')).toHaveClass('bg-yellow-500/10') + expect(container.querySelector('[role="alert"]')).toHaveClass('bg-warning/10') }) it('applies custom className', () => { diff --git a/src/components/common/alert.tsx b/src/components/common/alert.tsx index 5e0cea675..d7f7c5777 100644 --- a/src/components/common/alert.tsx +++ b/src/components/common/alert.tsx @@ -1,5 +1,10 @@ import { cn } from '@/lib/utils'; -import { AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-react'; +import { + IconAlertCircle as AlertCircle, + IconCircleCheck as CheckCircle, + IconInfoCircle as Info, + IconAlertTriangle as AlertTriangle, +} from '@tabler/icons-react'; import type { ReactNode } from 'react'; type AlertVariant = 'info' | 'success' | 'warning' | 'error'; @@ -14,15 +19,15 @@ interface AlertProps { const variantConfig = { info: { icon: Info, - styles: 'bg-blue-500/10 text-blue-700 dark:text-blue-300', + styles: 'bg-info/10 text-info', }, success: { icon: CheckCircle, - styles: 'bg-green-500/10 text-green-700 dark:text-green-300', + styles: 'bg-success/10 text-success', }, warning: { icon: AlertTriangle, - styles: 'bg-yellow-500/10 text-yellow-700 dark:text-yellow-300', + styles: 'bg-warning/10 text-warning', }, error: { icon: AlertCircle, diff --git a/src/components/common/amount-display.stories.tsx b/src/components/common/amount-display.stories.tsx index dcfe23290..5fae3dd61 100644 --- a/src/components/common/amount-display.stories.tsx +++ b/src/components/common/amount-display.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { AmountDisplay, AmountWithFiat } from './amount-display' +import type { Meta, StoryObj } from '@storybook/react'; +import { AmountDisplay, AmountWithFiat } from './amount-display'; const meta: Meta = { title: 'Common/AmountDisplay', @@ -11,29 +11,39 @@ const meta: Meta = { size: { control: 'select', options: ['xs', 'sm', 'md', 'lg', 'xl'] }, weight: { control: 'select', options: ['normal', 'medium', 'semibold', 'bold'] }, }, -} +}; -export default meta -type Story = StoryObj +export default meta; +type Story = StoryObj; export const Default: Story = { args: { value: 1234.5678, symbol: 'USDT', }, -} +}; export const Sizes: Story = { render: () => (
-
-
-
-
-
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
), -} +}; export const WithSign: Story = { render: () => ( @@ -42,21 +52,30 @@ export const WithSign: Story = {
-

sign="always" + color="auto"

+

sign="always" + color="auto"

), -} +}; export const Compact: Story = { render: () => (
-
(1,234)
-
(12,345)
-
(1,234,567)
-
(1,234,567,890)
+
+ (1,234) +
+
+ (12,345) +
+
+ (1,234,567) +
+
+ {' '} + (1,234,567,890) +
), -} +}; export const Hidden: Story = { args: { @@ -64,7 +83,7 @@ export const Hidden: Story = { symbol: 'USDT', hidden: true, }, -} +}; export const MonoFont: Story = { args: { @@ -72,65 +91,64 @@ export const MonoFont: Story = { mono: true, weight: 'semibold', }, -} +}; export const SmallNumbers: Story = { render: () => (
-
(0.1)
-
(0.01)
-
(0.001)
-
(0.0001)
-
(0.00001)
+
+ (0.1) +
+
+ (0.01) +
+
+ (0.001) +
+
+ (0.0001) +
+
+ (0.00001) +
), -} +}; // AmountWithFiat stories export const WithFiat: Story = { render: () => (
-

Vertical (default)

- +

Vertical (default)

+
-

Horizontal

- +

Horizontal

+
), -} +}; export const PriceChange: Story = { render: () => (
- BTC: + BTC: - % + %
- ETH: + ETH: - % + %
), -} +}; export const TokenList: Story = { render: () => ( @@ -142,14 +160,9 @@ export const TokenList: Story = { ].map((token) => (
{token.symbol} - +
))} ), -} +}; diff --git a/src/components/common/amount-display.tsx b/src/components/common/amount-display.tsx index 975d01249..4411400f9 100644 --- a/src/components/common/amount-display.tsx +++ b/src/components/common/amount-display.tsx @@ -1,67 +1,67 @@ -import { cn } from '@/lib/utils' +import { cn } from '@/lib/utils'; -type AmountSign = 'auto' | 'always' | 'never' -type AmountColor = 'auto' | 'default' | 'positive' | 'negative' +type AmountSign = 'auto' | 'always' | 'never'; +type AmountColor = 'auto' | 'default' | 'positive' | 'negative'; interface AmountDisplayProps { - value: string | number - symbol?: string | undefined - decimals?: number | undefined + value: string | number; + symbol?: string | undefined; + decimals?: number | undefined; /** 是否显示正负号 */ - sign?: AmountSign | undefined + sign?: AmountSign | undefined; /** 颜色模式:auto 根据正负自动,default 不变色 */ - color?: AmountColor | undefined + color?: AmountColor | undefined; /** 是否使用紧凑模式(1K, 1M) */ - compact?: boolean | undefined + compact?: boolean | undefined; /** 隐藏金额(隐私模式) */ - hidden?: boolean | undefined + hidden?: boolean | undefined; /** 尺寸 */ - size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | undefined + size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | undefined; /** 字重 */ - weight?: 'normal' | 'medium' | 'semibold' | 'bold' | undefined + weight?: 'normal' | 'medium' | 'semibold' | 'bold' | undefined; /** 是否等宽字体 */ - mono?: boolean | undefined - className?: string | undefined + mono?: boolean | undefined; + className?: string | undefined; } // 格式化数字 function formatAmount( value: string | number, decimals: number, - compact: boolean + compact: boolean, ): { formatted: string; isNegative: boolean; isZero: boolean } { - const num = typeof value === 'string' ? parseFloat(value) : value - + const num = typeof value === 'string' ? parseFloat(value) : value; + if (isNaN(num)) { - return { formatted: '0', isNegative: false, isZero: true } + return { formatted: '0', isNegative: false, isZero: true }; } - - const isNegative = num < 0 - const isZero = num === 0 - const absNum = Math.abs(num) - - let formatted: string - + + const isNegative = num < 0; + const isZero = num === 0; + const absNum = Math.abs(num); + + let formatted: string; + if (isZero) { - formatted = '0' + formatted = '0'; } else if (absNum < 0.0001) { - formatted = '< 0.0001' + formatted = '< 0.0001'; } else if (compact && absNum >= 1_000_000_000) { - formatted = (absNum / 1_000_000_000).toFixed(2).replace(/\.?0+$/, '') + 'B' + formatted = (absNum / 1_000_000_000).toFixed(2).replace(/\.?0+$/, '') + 'B'; } else if (compact && absNum >= 1_000_000) { - formatted = (absNum / 1_000_000).toFixed(2).replace(/\.?0+$/, '') + 'M' + formatted = (absNum / 1_000_000).toFixed(2).replace(/\.?0+$/, '') + 'M'; } else if (compact && absNum >= 1_000) { - formatted = (absNum / 1_000).toFixed(2).replace(/\.?0+$/, '') + 'K' + formatted = (absNum / 1_000).toFixed(2).replace(/\.?0+$/, '') + 'K'; } else if (absNum >= 1_000) { - formatted = absNum.toLocaleString('en-US', { + formatted = absNum.toLocaleString('en-US', { minimumFractionDigits: 0, - maximumFractionDigits: decimals - }) + maximumFractionDigits: decimals, + }); } else { - formatted = absNum.toFixed(decimals).replace(/\.?0+$/, '') + formatted = absNum.toFixed(decimals).replace(/\.?0+$/, ''); } - - return { formatted, isNegative, isZero } + + return { formatted, isNegative, isZero }; } const sizeClasses = { @@ -70,14 +70,14 @@ const sizeClasses = { md: 'text-base', lg: 'text-lg', xl: 'text-xl', -} +}; const weightClasses = { normal: 'font-normal', medium: 'font-medium', semibold: 'font-semibold', bold: 'font-bold', -} +}; export function AmountDisplay({ value, @@ -96,54 +96,47 @@ export function AmountDisplay({ return ( •••••• - {symbol && {symbol}} + {symbol && {symbol}} - ) + ); } - const { formatted, isNegative, isZero } = formatAmount(value, decimals, compact) - + const { formatted, isNegative, isZero } = formatAmount(value, decimals, compact); + // 计算符号 - let signChar = '' + let signChar = ''; if (sign === 'always' && !isZero) { - signChar = isNegative ? '-' : '+' + signChar = isNegative ? '-' : '+'; } else if (sign === 'auto' && isNegative) { - signChar = '-' + signChar = '-'; } - + // 计算颜色 - let colorClass = '' + let colorClass = ''; if (color === 'auto' && !isZero) { - colorClass = isNegative ? 'text-destructive' : 'text-secondary' + colorClass = isNegative ? 'text-destructive' : 'text-secondary'; } else if (color === 'positive') { - colorClass = 'text-secondary' + colorClass = 'text-secondary'; } else if (color === 'negative') { - colorClass = 'text-destructive' + colorClass = 'text-destructive'; } return ( - - {signChar}{formatted} - {symbol && {symbol}} + + {signChar} + {formatted} + {symbol && {symbol}} - ) + ); } // 带法币价值的复合显示 interface AmountWithFiatProps extends AmountDisplayProps { - fiatValue?: string | number | undefined - fiatSymbol?: string | undefined - fiatDecimals?: number | undefined + fiatValue?: string | number | undefined; + fiatSymbol?: string | undefined; + fiatDecimals?: number | undefined; /** 布局方向 */ - layout?: 'vertical' | 'horizontal' | undefined + layout?: 'vertical' | 'horizontal' | undefined; } export function AmountWithFiat({ @@ -154,33 +147,33 @@ export function AmountWithFiat({ className, ...amountProps }: AmountWithFiatProps) { - const fiatFormatted = fiatValue !== undefined - ? formatAmount(fiatValue, fiatDecimals, false).formatted - : null + const fiatFormatted = fiatValue !== undefined ? formatAmount(fiatValue, fiatDecimals, false).formatted : null; if (layout === 'horizontal') { return ( {fiatFormatted && ( - - ≈ {fiatSymbol}{fiatFormatted} + + ≈ {fiatSymbol} + {fiatFormatted} )} - ) + ); } return (
{fiatFormatted && ( -

- ≈ {fiatSymbol}{fiatFormatted} +

+ ≈ {fiatSymbol} + {fiatFormatted}

)}
- ) + ); } -export { formatAmount } +export { formatAmount }; diff --git a/src/components/common/empty-state.tsx b/src/components/common/empty-state.tsx index 2bede8dcc..577470540 100644 --- a/src/components/common/empty-state.tsx +++ b/src/components/common/empty-state.tsx @@ -1,32 +1,20 @@ -import { cn } from '@/lib/utils' +import { cn } from '@/lib/utils'; interface EmptyStateProps { - icon?: React.ReactNode | undefined - title: string - description?: string | undefined - action?: React.ReactNode | undefined - className?: string | undefined + icon?: React.ReactNode | undefined; + title: string; + description?: string | undefined; + action?: React.ReactNode | undefined; + className?: string | undefined; } -export function EmptyState({ - icon, - title, - description, - action, - className, -}: EmptyStateProps) { +export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) { return ( -
- {icon && ( -
- {icon} -
- )} -

{title}

- {description && ( -

{description}

- )} +
+ {icon &&
{icon}
} +

{title}

+ {description &&

{description}

} {action &&
{action}
}
- ) + ); } diff --git a/src/components/common/gradient-button.stories.tsx b/src/components/common/gradient-button.stories.tsx index 0b603e456..81bac9a05 100644 --- a/src/components/common/gradient-button.stories.tsx +++ b/src/components/common/gradient-button.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { GradientButton } from './gradient-button' +import type { Meta, StoryObj } from '@storybook/react'; +import { GradientButton } from './gradient-button'; const meta: Meta = { title: 'Common/GradientButton', @@ -24,16 +24,16 @@ const meta: Meta = { control: 'boolean', }, }, -} +}; -export default meta -type Story = StoryObj +export default meta; +type Story = StoryObj; export const Default: Story = { args: { children: '确认转账', }, -} +}; export const AllVariants: Story = { render: () => ( @@ -44,43 +44,43 @@ export const AllVariants: Story = { Mint
), -} +}; export const AllSizes: Story = { render: () => ( -
+
Small Medium (默认) Large
), -} +}; export const Loading: Story = { args: { children: '处理中...', loading: true, }, -} +}; export const Disabled: Story = { args: { children: '不可用', disabled: true, }, -} +}; export const FullWidth: Story = { args: { children: '全宽按钮', fullWidth: true, }, -} +}; export const ResponsiveContainer: Story = { render: () => (
-

拖拽容器边缘或使用工具栏切换容器尺寸,观察按钮响应式变化

+

拖拽容器边缘或使用工具栏切换容器尺寸,观察按钮响应式变化

响应式按钮
), @@ -91,4 +91,4 @@ export const ResponsiveContainer: Story = { }, }, }, -} +}; diff --git a/src/components/common/gradient-button.tsx b/src/components/common/gradient-button.tsx index b0eed7b05..82696e946 100644 --- a/src/components/common/gradient-button.tsx +++ b/src/components/common/gradient-button.tsx @@ -1,7 +1,8 @@ -import { forwardRef } from 'react' -import { Slot } from '@radix-ui/react-slot' -import { cva, type VariantProps } from 'class-variance-authority' -import { cn } from '@/lib/utils' +import { forwardRef } from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; +import { IconLoader } from '@tabler/icons-react'; const gradientButtonVariants = cva( 'inline-flex items-center justify-center gap-2 rounded-full text-white font-medium transition-opacity disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0', @@ -28,20 +29,19 @@ const gradientButtonVariants = cva( size: 'md', fullWidth: false, }, - } -) + }, +); export interface GradientButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean - loading?: boolean + extends React.ButtonHTMLAttributes, VariantProps { + asChild?: boolean; + loading?: boolean; } const GradientButton = forwardRef( ({ className, variant, size, fullWidth, asChild = false, loading, children, disabled, ...props }, ref) => { - const Comp = asChild ? Slot : 'button' - + const Comp = asChild ? Slot : 'button'; + return ( ( disabled={disabled || loading} {...props} > - {loading && ( - - )} + {loading && } {children} - ) - } -) -GradientButton.displayName = 'GradientButton' + ); + }, +); +GradientButton.displayName = 'GradientButton'; -export { GradientButton, gradientButtonVariants } +export { GradientButton, gradientButtonVariants }; diff --git a/src/components/common/icon-circle.test.tsx b/src/components/common/icon-circle.test.tsx index 590536f59..86dad15eb 100644 --- a/src/components/common/icon-circle.test.tsx +++ b/src/components/common/icon-circle.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/react'; import { IconCircle } from './icon-circle'; -import { ShieldCheck, Wallet, Settings } from 'lucide-react'; +import { IconShieldCheck as ShieldCheck, IconWallet as Wallet, IconSettings as Settings } from '@tabler/icons-react'; describe('IconCircle', () => { it('renders with icon', () => { diff --git a/src/components/common/icon-circle.tsx b/src/components/common/icon-circle.tsx index 80b8e7115..41f0672dd 100644 --- a/src/components/common/icon-circle.tsx +++ b/src/components/common/icon-circle.tsx @@ -1,11 +1,11 @@ import { cn } from '@/lib/utils'; -import type { LucideIcon } from 'lucide-react'; +import type { Icon } from '@tabler/icons-react'; type IconCircleVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'error'; type IconCircleSize = 'sm' | 'md' | 'lg'; interface IconCircleProps { - icon: LucideIcon; + icon: Icon; variant?: IconCircleVariant; size?: IconCircleSize; className?: string; diff --git a/src/components/common/loading-spinner.stories.tsx b/src/components/common/loading-spinner.stories.tsx index ea4d04343..0318c31e3 100644 --- a/src/components/common/loading-spinner.stories.tsx +++ b/src/components/common/loading-spinner.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { LoadingSpinner } from './loading-spinner' +import type { Meta, StoryObj } from '@storybook/react'; +import { LoadingSpinner } from './loading-spinner'; const meta: Meta = { title: 'Common/LoadingSpinner', @@ -14,37 +14,37 @@ const meta: Meta = { control: 'boolean', }, }, -} +}; -export default meta -type Story = StoryObj +export default meta; +type Story = StoryObj; -export const Default: Story = {} +export const Default: Story = {}; export const AllSizes: Story = { render: () => (
- Small + Small
- Medium + Medium
- Large + Large
), -} +}; export const InButton: Story = { render: () => ( - ), -} +}; diff --git a/src/components/common/step-indicator.tsx b/src/components/common/step-indicator.tsx index bbb4cd802..d8e4bff91 100644 --- a/src/components/common/step-indicator.tsx +++ b/src/components/common/step-indicator.tsx @@ -1,5 +1,5 @@ import { cn } from '@/lib/utils'; -import { Check } from 'lucide-react'; +import { IconCheck as Check } from '@tabler/icons-react'; interface Step { id: string; diff --git a/src/components/common/theme-demo.stories.tsx b/src/components/common/theme-demo.stories.tsx index f239eb14d..682d3586d 100644 --- a/src/components/common/theme-demo.stories.tsx +++ b/src/components/common/theme-demo.stories.tsx @@ -1,24 +1,24 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { useTranslation } from 'react-i18next' -import { GradientButton } from './gradient-button' -import { LoadingSpinner } from './loading-spinner' -import { EmptyState } from './empty-state' +import type { Meta, StoryObj } from '@storybook/react'; +import { useTranslation } from 'react-i18next'; +import { GradientButton } from './gradient-button'; +import { LoadingSpinner } from './loading-spinner'; +import { EmptyState } from './empty-state'; function ThemeDemoComponent() { - const { t, i18n } = useTranslation() - + const { t, i18n } = useTranslation(); + return ( -
+

{t('wallet.myWallet')}

-

{t('common.loading')}

+

{t('common.loading')}

- +
{t('common.loading')}...
- +
{t('wallet.transfer')} @@ -27,17 +27,17 @@ function ThemeDemoComponent() { {t('wallet.receive')}
- +

{t('transaction.send')}

-

{t('transaction.pending')}

+

{t('transaction.pending')}

- -
+ +
Current: {i18n.language} | Dir: {i18n.dir()}
- ) + ); } const meta: Meta = { @@ -63,16 +63,16 @@ const meta: Meta = { }, }, }, -} +}; -export default meta -type Story = StoryObj +export default meta; +type Story = StoryObj; -export const Default: Story = {} +export const Default: Story = {}; export const WithEmptyState: Story = { render: () => { - const { t } = useTranslation() + const { t } = useTranslation(); return ( } /> - ) + ); }, -} +}; export const TransactionStates: Story = { render: () => { - const { t } = useTranslation() + const { t } = useTranslation(); return (
{(['send', 'receive', 'swap', 'stake'] as const).map((type) => ( -
+
{t(`transaction.${type}`)} - {t('transaction.confirmed')} + {t('transaction.confirmed')}
))}
- ) + ); }, -} +}; export const SecurityLabels: Story = { render: () => { - const { t } = useTranslation() + const { t } = useTranslation(); return (
-
+

{t('security.password')}

-
+
{(['weak', 'medium', 'strong'] as const).map((level) => ( - {t(`security.strength.${level}`)} @@ -128,11 +127,11 @@ export const SecurityLabels: Story = { ))}
-
+

{t('security.mnemonic')}

-

{t('security.copyMnemonic')}

+

{t('security.copyMnemonic')}

- ) + ); }, -} +}; diff --git a/src/components/common/time-display.stories.tsx b/src/components/common/time-display.stories.tsx index b9ae9624e..f8a805210 100644 --- a/src/components/common/time-display.stories.tsx +++ b/src/components/common/time-display.stories.tsx @@ -1,5 +1,5 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { TimeDisplay } from './time-display' +import type { Meta, StoryObj } from '@storybook/react'; +import { TimeDisplay } from './time-display'; const meta: Meta = { title: 'Common/TimeDisplay', @@ -11,93 +11,93 @@ const meta: Meta = { options: ['relative', 'date', 'datetime', 'time'], }, }, -} +}; -export default meta -type Story = StoryObj +export default meta; +type Story = StoryObj; -const now = new Date() -const minutesAgo = new Date(now.getTime() - 5 * 60 * 1000) -const hoursAgo = new Date(now.getTime() - 3 * 60 * 60 * 1000) -const daysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) -const weeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000) +const now = new Date(); +const minutesAgo = new Date(now.getTime() - 5 * 60 * 1000); +const hoursAgo = new Date(now.getTime() - 3 * 60 * 60 * 1000); +const daysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000); +const weeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000); export const Default: Story = { args: { value: minutesAgo, }, -} +}; export const RelativeTime: Story = { render: () => (
- 刚刚: + 刚刚:
- 5分钟前: + 5分钟前:
- 3小时前: + 3小时前:
- 2天前: + 2天前:
- 2周前: + 2周前:
), -} +}; export const DateFormat: Story = { args: { value: now, format: 'date', }, -} +}; export const DateTimeFormat: Story = { args: { value: now, format: 'datetime', }, -} +}; export const TimeFormat: Story = { args: { value: now, format: 'time', }, -} +}; export const AllFormats: Story = { render: () => (
-

Relative (default)

+

Relative (default)

-

Date

+

Date

-

DateTime

+

DateTime

-

Time

+

Time

), -} +}; export const TransactionList: Story = { render: () => ( @@ -107,14 +107,14 @@ export const TransactionList: Story = { { type: '接收', amount: '+0.5 ETH', time: hoursAgo }, { type: '兑换', amount: '100 USDT → 0.05 ETH', time: daysAgo }, ].map((tx, i) => ( -
+

{tx.type}

-

{tx.amount}

+

{tx.amount}

- +
))}
), -} +}; diff --git a/src/components/common/time-display.tsx b/src/components/common/time-display.tsx index 10d7b01e2..90f5565ac 100644 --- a/src/components/common/time-display.tsx +++ b/src/components/common/time-display.tsx @@ -1,58 +1,54 @@ -import { cn } from '@/lib/utils' -import { useTranslation } from 'react-i18next' -import type { TFunction } from 'i18next' +import { cn } from '@/lib/utils'; +import { useTranslation } from 'react-i18next'; +import type { TFunction } from 'i18next'; -type TimeFormat = 'relative' | 'date' | 'datetime' | 'time' +type TimeFormat = 'relative' | 'date' | 'datetime' | 'time'; interface TimeDisplayProps { - value: Date | string | number - format?: TimeFormat - className?: string + value: Date | string | number; + format?: TimeFormat; + className?: string; } function toDate(value: Date | string | number): Date { - if (value instanceof Date) return value - if (typeof value === 'number') return new Date(value) - return new Date(value) + if (value instanceof Date) return value; + if (typeof value === 'number') return new Date(value); + return new Date(value); } // 获取当前语言的 locale function getLocale(lang: string): string { const localeMap: Record = { 'zh-CN': 'zh-CN', - 'en': 'en-US', - 'ar': 'ar-SA', - } - return localeMap[lang] || lang + en: 'en-US', + ar: 'ar-SA', + }; + return localeMap[lang] || lang; } -export function TimeDisplay({ - value, - format = 'relative', - className, -}: TimeDisplayProps) { - const { t, i18n } = useTranslation() - const date = toDate(value) - const locale = getLocale(i18n.language) - +export function TimeDisplay({ value, format = 'relative', className }: TimeDisplayProps) { + const { t, i18n } = useTranslation(); + const date = toDate(value); + const locale = getLocale(i18n.language); + // 无效日期 if (isNaN(date.getTime())) { - return -- + return --; } - let formatted: string - + let formatted: string; + switch (format) { case 'relative': - formatted = formatRelativeTime(date, t) - break + formatted = formatRelativeTime(date, t); + break; case 'date': formatted = date.toLocaleDateString(locale, { year: 'numeric', month: '2-digit', day: '2-digit', - }) - break + }); + break; case 'datetime': formatted = date.toLocaleString(locale, { year: 'numeric', @@ -60,14 +56,14 @@ export function TimeDisplay({ day: '2-digit', hour: '2-digit', minute: '2-digit', - }) - break + }); + break; case 'time': formatted = date.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', - }) - break + }); + break; } const titleText = date.toLocaleString(locale, { @@ -76,54 +72,41 @@ export function TimeDisplay({ day: '2-digit', hour: '2-digit', minute: '2-digit', - }) + }); return ( -