Skip to content
This repository was archived by the owner on Mar 2, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ const config: StorybookConfig = {
'../src/ui/hooks/useNetworkHook.mock.ts'
),
'@/ui/hooks/use-wallet$': path.resolve(__dirname, '../src/ui/hooks/use-wallet.mock.ts'),
'@/ui/hooks/useCoinHook$': path.resolve(__dirname, '../src/ui/hooks/useCoinHook.mock.ts'),
'@/ui/hooks/useCoinHook': path.resolve(__dirname, '../src/ui/hooks/useCoinHook.mock.ts'),
'@/ui/hooks/useContactHook$': path.resolve(
__dirname,
'../src/ui/hooks/useContactHook.mock.ts'
),
'@/ui/hooks/useContactHook': path.resolve(
__dirname,
'../src/ui/hooks/useContactHook.mock.ts'
),
'@/ui/hooks/useTransferListHook': path.resolve(
__dirname,
'../src/ui/hooks/useTransferListHook.mock.ts'
),

// Other aliases
'@': path.resolve(__dirname, '../src'),
Expand Down
157 changes: 114 additions & 43 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,128 @@ import themeOptions from '../src/ui/style/LLTheme'; // Import your theme options

import '../src/ui/style/fonts.css';

// Add this mock
if (typeof global.chrome === 'undefined' || typeof global.chrome.i18n === 'undefined') {
global.chrome = {
i18n: {
getMessage: (messageName: string, substitutions?: unknown) => {
const entry = messages[messageName as keyof typeof messages];
let msg = entry ? entry.message : messageName;
if (substitutions) {
if (Array.isArray(substitutions)) {
// Replace $1$, $2$, ... with array values
substitutions.forEach((val, idx) => {
msg = msg.replace(new RegExp(`\\$[^$]+\\$`), String(val));
});
return msg;
}
if (typeof substitutions === 'object') {
// Replace $key$ with object values
Object.entries(substitutions).forEach(([key, val]) => {
msg = msg.replace(new RegExp(`\\$${key}\\$`, 'g'), String(val));
});
return msg;
}
// Enhanced Chrome API mock for Storybook
const createChromeMock = () => ({
i18n: {
getMessage: (messageName: string, substitutions?: unknown) => {
const entry = messages[messageName as keyof typeof messages];
let msg = entry ? entry.message : messageName;
if (substitutions) {
if (Array.isArray(substitutions)) {
// Replace $1$, $2$, ... with array values
substitutions.forEach((val, idx) => {
msg = msg.replace(new RegExp(`\\$[^$]+\\$`), String(val));
});
return msg;
}
return msg;
},
if (typeof substitutions === 'object') {
// Replace $key$ with object values
Object.entries(substitutions).forEach(([key, val]) => {
msg = msg.replace(new RegExp(`\\$${key}\\$`, 'g'), String(val));
});
return msg;
}
}
return msg;
},
},
runtime: {
id: 'mock-extension-id',
onMessage: {
addListener: () => {},
removeListener: () => {},
},
sendMessage: () => Promise.resolve({}),
onConnect: {
addListener: () => {},
removeListener: () => {},
},
storage: {
onChanged: {
connect: () => ({
name: 'mock-port',
postMessage: () => {},
onMessage: {
addListener: () => {},
removeListener: () => {},
},
local: {
get: () => Promise.resolve({}),
set: () => Promise.resolve(),
remove: () => Promise.resolve(),
clear: () => Promise.resolve(),
onDisconnect: {
addListener: () => {},
removeListener: () => {},
},
session: {
get: () => Promise.resolve({}),
set: () => Promise.resolve(),
remove: () => Promise.resolve(),
clear: () => Promise.resolve(),
disconnect: () => {},
}),
},
tabs: {
query: () => Promise.resolve([]),
sendMessage: () => Promise.resolve({}),
update: () => Promise.resolve({}),
create: () => Promise.resolve({ id: 1, url: 'https://example.com' }),
remove: () => Promise.resolve(),
},
storage: {
onChanged: {
addListener: () => {},
removeListener: () => {},
},
local: {
get: () => Promise.resolve({}),
set: () => Promise.resolve(),
remove: () => Promise.resolve(),
clear: () => Promise.resolve(),
},
session: {
get: () => Promise.resolve({}),
set: () => Promise.resolve(),
remove: () => Promise.resolve(),
clear: () => Promise.resolve(),
},
sync: {
get: () => Promise.resolve({}),
set: () => Promise.resolve(),
remove: () => Promise.resolve(),
clear: () => Promise.resolve(),
},
},
});

// Always ensure chrome is available with all required APIs
if (typeof global.chrome === 'undefined') {
global.chrome = createChromeMock() as unknown as typeof chrome;
} else {
// If chrome already exists, ensure all required APIs are available
const mock = createChromeMock();
global.chrome = {
...global.chrome,
...mock,
runtime: {
...global.chrome.runtime,
...mock.runtime,
},
tabs: {
...global.chrome.tabs,
...mock.tabs,
},
} as unknown as typeof chrome;
}

// Also ensure chrome is available on window object
if (typeof window !== 'undefined') {
if (typeof window.chrome === 'undefined') {
window.chrome = createChromeMock() as unknown as typeof chrome;
} else {
const mock = createChromeMock();
window.chrome = {
...window.chrome,
...mock,
runtime: {
...window.chrome.runtime,
...mock.runtime,
},
sync: {
get: () => Promise.resolve({}),
set: () => Promise.resolve(),
remove: () => Promise.resolve(),
clear: () => Promise.resolve(),
tabs: {
...window.chrome.tabs,
...mock.tabs,
},
},
} as unknown as typeof chrome; // Use 'as any' to simplify mocking complex global objects
} as unknown as typeof chrome;
}
}

const theme = createTheme(themeOptions); // Create a theme instance
Expand Down
3 changes: 3 additions & 0 deletions src/ui/assets/svg/arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions src/ui/components/LLHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ interface LLHeaderProps {
title: string | JSX.Element;
help: boolean | JSX.Element;
goBackLink?: string; // Optional link
onBackClick?: () => void; // Optional custom back button handler
right?: React.ReactNode;
}

export const LLHeader = (props: LLHeaderProps) => {
// const { label, ...inherentProps } = props;
const navigate = useNavigate();
const handleGoBack = () => {
if (props.goBackLink) {
if (props.onBackClick) {
props.onBackClick();
} else if (props.goBackLink) {
navigate(props.goBackLink);
} else {
// Fall back to browser history
Expand All @@ -39,7 +42,14 @@ export const LLHeader = (props: LLHeaderProps) => {
</IconButton>
</Grid>
<Grid size={10}>
<Typography variant="h1" align="center" py="14px" fontWeight="bold" fontSize="16px">
<Typography
variant="h1"
align="center"
py="14px"
fontWeight="bold"
fontSize="16px"
sx={{ textTransform: 'capitalize' }}
>
{props.title}
</Typography>
</Grid>
Expand Down
98 changes: 91 additions & 7 deletions src/ui/components/TokenLists/TokenItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import VerifiedIcon from '@/ui/assets/svg/verfied-check.svg';
import IconCheckmark from '@/ui/components/iconfont/IconCheckmark';
import IconPlus from '@/ui/components/iconfont/IconPlus';
import { CurrencyValue } from '@/ui/components/TokenLists/CurrencyValue';
import { TokenBalance } from '@/ui/components/TokenLists/TokenBalance';
import { useCurrency } from '@/ui/hooks/preference-hooks';

import TokenAvatar from './TokenAvatar';
Expand All @@ -38,6 +39,14 @@ type TokenItemProps = {
tokenFilter?: TokenFilter;
updateTokenFilter?: (tokenFilter: TokenFilter) => void;
showSwitch?: boolean;
// Custom styling props
backgroundColor?: string;
fontSize?: string | number;
selected?: boolean;
customSx?: any;
// Display options
showBalance?: boolean;
showPrice?: boolean;
};

const TokenItem: React.FC<TokenItemProps> = ({
Expand All @@ -48,6 +57,12 @@ const TokenItem: React.FC<TokenItemProps> = ({
tokenFilter = undefined,
updateTokenFilter,
showSwitch = false,
backgroundColor = '#000000',
fontSize = '14px',
selected = false,
customSx = {},
showBalance = false,
showPrice = false,
}) => {
const currency = useCurrency();
const handleClick = () => {
Expand Down Expand Up @@ -78,12 +93,17 @@ const TokenItem: React.FC<TokenItemProps> = ({
mx: '8px',
py: '4px',
my: '8px',
backgroundColor: '#000000',
backgroundColor: selected ? '#2A2A2A' : backgroundColor,
borderRadius: '12px',
border: '1px solid #2A2A2A',
border: selected ? '1px solid #4A4A4A' : '1px solid #2A2A2A',
'&:hover': {
backgroundColor: selected ? '#2A2A2A' : '#1A1A1A',
},
...customSx,
}}
onClick={showSwitch ? undefined : handleClick}
disableRipple={showSwitch}
selected={selected}
>
<CustomListItem
disablePadding
Expand All @@ -95,6 +115,46 @@ const TokenItem: React.FC<TokenItemProps> = ({
onChange={handleSwitchChange}
edge="end"
/>
) : showBalance ? (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: '2px',
}}
>
<Typography
variant="body2"
sx={{
fontSize: typeof fontSize === 'number' ? `${fontSize * 0.9}px` : fontSize,
fontWeight: 600,
color: 'text.primary',
}}
>
<TokenBalance
value={token.balance || '0'}
decimals={token.decimals || 18}
displayDecimals={2}
/>{' '}
{token.symbol.toUpperCase()}
</Typography>
{token.total && parseFloat(token.total) > 0 && (
<Typography
variant="caption"
sx={{
fontSize: typeof fontSize === 'number' ? `${fontSize * 0.7}px` : fontSize,
color: 'text.secondary',
}}
>
<CurrencyValue
value={token.total}
currencyCode={currency?.code ?? ''}
currencySymbol={currency?.symbol ?? ''}
/>
</Typography>
)}
</Box>
) : (
<IconButton edge="end" aria-label="delete" onClick={handleClick}>
{isLoading ? (
Expand All @@ -120,13 +180,12 @@ const TokenItem: React.FC<TokenItemProps> = ({
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 1,
WebkitBoxOrient: 'vertical',
whiteSpace: 'nowrap',
maxWidth: '210px',
fontSize: fontSize,
}}
>
{token.name}
{showPrice ? token.symbol.toUpperCase() : token.name}
</Typography>
{token.isVerified && (
<img
Expand All @@ -151,8 +210,33 @@ const TokenItem: React.FC<TokenItemProps> = ({
currencyCode={currency?.code ?? ''}
currencySymbol={currency?.symbol ?? ''}
/>
) : showPrice ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
{token.price && parseFloat(token.price) > 0 && (
<Typography
variant="caption"
sx={{
fontSize: typeof fontSize === 'number' ? `${fontSize * 0.7}px` : fontSize,
color: 'text.secondary',
}}
>
<CurrencyValue
value={token.price}
currencyCode={currency?.code ?? ''}
currencySymbol={currency?.symbol ?? ''}
/>
</Typography>
)}
</Box>
) : (
token.symbol.toUpperCase()
<Typography
variant="body2"
sx={{
fontSize: typeof fontSize === 'number' ? `${fontSize * 0.8}px` : fontSize,
}}
>
{token.symbol.toUpperCase()}
</Typography>
)
}
/>
Expand Down
Loading
Loading