Skip to content

Commit

Permalink
feat: nft search (#1227)
Browse files Browse the repository at this point in the history
Co-authored-by: Christopher Howard <derhowiedesigns@gmail.com>
  • Loading branch information
DanielSinclair and derHowie authored Dec 20, 2023
1 parent 149afdc commit b918f06
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 39 deletions.
46 changes: 23 additions & 23 deletions src/design-system/components/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ export const Box = forwardRef(function Box(
},
ref,
) {
let hasBoxStyles = false;
const boxStyleOptions: BoxStyles = {};
const restProps: Record<string, unknown> = {};
let hasBoxStyles = false;
const boxStyleOptions: BoxStyles = {};
const restProps: Record<string, unknown> = {};

for (const key in props) {
if (boxStyles.properties.has(key as keyof BoxStyles)) {
Expand Down Expand Up @@ -109,26 +109,26 @@ export const Box = forwardRef(function Box(
].setColorContext) === 'light'
? themeClasses.lightTheme.lightContext
: themeClasses.lightTheme.darkContext,
(darkThemeBackgroundColor === 'accent'
? accentColorContext
: backgroundColors[darkThemeBackgroundColor][
darkThemeColorContext
].setColorContext) === 'light'
? themeClasses.darkTheme.lightContext
: themeClasses.darkTheme.darkContext,
]
: null,
className,
)}
data-is-modally-presented={isModal || undefined}
data-is-explainer-sheet={isExplainerSheet || undefined}
data-testid={testId}
// Since Box is a primitive component, it needs to spread props
// eslint-disable-next-line react/jsx-props-no-spreading
{...restProps}
tabIndex={tabIndex}
/>
);
(darkThemeBackgroundColor === 'accent'
? accentColorContext
: backgroundColors[darkThemeBackgroundColor][
darkThemeColorContext
].setColorContext) === 'light'
? themeClasses.darkTheme.lightContext
: themeClasses.darkTheme.darkContext,
]
: null,
className,
)}
data-is-modally-presented={isModal || undefined}
data-is-explainer-sheet={isExplainerSheet || undefined}
data-testid={testId}
// Since Box is a primitive component, it needs to spread props
// eslint-disable-next-line react/jsx-props-no-spreading
{...restProps}
tabIndex={tabIndex}
/>
);

return props.background ? (
<ColorContextProvider background={props.background}>
Expand Down
16 changes: 12 additions & 4 deletions src/entries/popup/components/CoinIcon/CoinIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ParsedUserAsset,
} from '~/core/types/assets';
import { ChainId } from '~/core/types/chains';
import { UniqueAsset } from '~/core/types/nfts';
import { SearchAsset } from '~/core/types/search';
import { AccentColorProvider, Box, Symbol } from '~/design-system';
import { BoxStyles } from '~/design-system/styles/core.css';
Expand Down Expand Up @@ -262,21 +263,28 @@ export const NFTIcon = ({
size,
badge = false,
}: {
asset: ParsedAsset;
asset: ParsedAsset | UniqueAsset;
size: keyof typeof nftRadiusBySize;
badge?: boolean;
}) => {
const chainId = asset.chainId;
const chainId = 'chainId' in asset ? asset.chainId : undefined;

return (
<Box position="relative" style={{ minWidth: size, height: size }}>
<ExternalImage
borderRadius={nftRadiusBySize[size]}
src={asset.icon_url}
src={
// eslint-disable-next-line no-nested-ternary
('icon_url' in asset
? asset.icon_url
: 'image_thumbnail_url' in asset
? asset.image_thumbnail_url
: '') || ''
}
height={size}
width={size}
/>
{badge && chainId !== ChainId.mainnet && (
{badge && chainId && chainId !== ChainId.mainnet && (
<Box position="absolute" bottom="0" style={{ zIndex: 2, left: '-6px' }}>
<ChainBadge chainId={chainId} shadow size="16" />
</Box>
Expand Down
11 changes: 11 additions & 0 deletions src/entries/popup/components/CommandK/CommandKList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { LIST_HEIGHT, MODAL_HEIGHT } from './CommandKModal';
import { TOOLBAR_HEIGHT } from './CommandKToolbar';
import {
COMMAND_ROW_HEIGHT,
NFTRow,
ShortcutRow,
TokenRow,
WalletRow,
} from './CommandRows';
import {
ENSOrAddressSearchItem,
NFTSearchItem,
SearchItem,
SearchItemType,
ShortcutSearchItem,
Expand Down Expand Up @@ -221,6 +223,15 @@ export const CommandKList = React.forwardRef<
selected={isSelected}
/>
);
} else if (command.type === SearchItemType.NFT) {
row = (
<NFTRow
command={command as NFTSearchItem}
handleExecuteCommand={handleExecuteCommand}
key={command.id}
selected={isSelected}
/>
);
}

return (
Expand Down
63 changes: 61 additions & 2 deletions src/entries/popup/components/CommandK/CommandRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { transitions } from '~/design-system/styles/designTokens';

import { Asterisks } from '../Asterisks/Asterisks';
import { CoinIcon } from '../CoinIcon/CoinIcon';
import { CoinIcon, NFTIcon } from '../CoinIcon/CoinIcon';
import { MenuItem } from '../Menu/MenuItem';
import { WalletAvatar } from '../WalletAvatar/WalletAvatar';

Expand All @@ -32,6 +32,7 @@ import {
} from './CommandKStyles.css';
import {
ENSOrAddressSearchItem,
NFTSearchItem,
SearchItem,
SearchItemType,
ShortcutSearchItem,
Expand Down Expand Up @@ -140,6 +141,62 @@ export const CommandRow = ({
);
};

type NFTRowProps = {
command: NFTSearchItem;
handleExecuteCommand: (command: SearchItem, e?: KeyboardEvent) => void;
selected: boolean;
};

export const NFTRow = ({
command,
handleExecuteCommand,
selected,
}: NFTRowProps) => {
const _NftIcon = React.useMemo(
() => <NFTIcon asset={command.nft} size={20} badge={false} />,
[command.nft],
);

const NFTBadge = React.useMemo(() => {
const tokenId = parseInt(command.nft?.id);
const hasTokenId = !isNaN(tokenId) && tokenId < 999999999;
return (
<Box
alignItems="center"
borderColor="separatorSecondary"
borderRadius="7px"
borderWidth="1px"
display="flex"
paddingHorizontal="4px"
style={{
height: 20,
whiteSpace: 'nowrap',
}}
>
<Text
align="center"
color="labelQuaternary"
size="12pt"
weight="semibold"
>
{hasTokenId ? `#${tokenId}` : 'NFT'}
</Text>
</Box>
);
}, [command.nft]);

return (
<CommandRow
command={command}
handleExecuteCommand={handleExecuteCommand}
name={command.name}
selected={selected}
LeftComponent={_NftIcon}
RightComponent={NFTBadge}
/>
);
};

type ShortcutRowProps = {
command: ShortcutSearchItem;
handleExecuteCommand: (command: SearchItem, e?: KeyboardEvent) => void;
Expand Down Expand Up @@ -203,7 +260,9 @@ export const ShortcutRow = ({

const shouldShowWalletName =
command.selectedWallet &&
(command.id === 'myTokens' || command.id === 'myQRCode');
(command.id === 'myTokens' ||
command.id === 'myNFTs' ||
command.id === 'myQRCode');

return (
<CommandRow
Expand Down
9 changes: 9 additions & 0 deletions src/entries/popup/components/CommandK/SearchItems.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Address } from 'wagmi';

import { ParsedUserAsset, ZerionAssetPrice } from '~/core/types/assets';
import { UniqueAsset } from '~/core/types/nfts';
import { SymbolName } from '~/design-system/styles/designTokens';

import { CommandKPage } from './pageConfig';

export enum SearchItemType {
ENSOrAddressResult,
NFT,
Shortcut,
Token,
Wallet,
Expand Down Expand Up @@ -41,6 +43,12 @@ export interface ENSOrAddressSearchItem extends BaseSearchItem {
type: SearchItemType.ENSOrAddressResult;
}

export interface NFTSearchItem extends BaseSearchItem {
nft: UniqueAsset;
selectedWalletAddress: Address;
type: SearchItemType.NFT;
}

export interface ShortcutSearchItem extends BaseSearchItem {
address?: Address;
hideWhenFullScreen?: boolean;
Expand Down Expand Up @@ -73,6 +81,7 @@ export interface WalletSearchItem extends BaseSearchItem {

export type SearchItem =
| ENSOrAddressSearchItem
| NFTSearchItem
| ShortcutSearchItem
| TokenSearchItem
| WalletSearchItem;
19 changes: 18 additions & 1 deletion src/entries/popup/components/CommandK/pageConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ export const PAGES: { [KEY: string]: Page } = {
listTitle: i18n.t('command_k.pages.add_wallet.section_title'),
searchPlaceholder: i18n.t('command_k.pages.add_wallet.search_placeholder'),
},
MY_NFTS: {
emptyLabel: i18n.t('command_k.pages.my_nfts.empty_label'),
listTitle: (command: SearchItem) =>
command.selectedWallet
? command.selectedWallet
: i18n.t('command_k.pages.my_nfts.section_title'),
searchPlaceholder: i18n.t('command_k.pages.my_nfts.search_placeholder'),
},
MY_TOKENS: {
emptyLabel: i18n.t('command_k.pages.my_tokens.empty_label'),
listTitle: (command: SearchItem) =>
Expand All @@ -29,6 +37,15 @@ export const PAGES: { [KEY: string]: Page } = {
listTitle: i18n.t('command_k.pages.my_wallets.section_title'),
searchPlaceholder: i18n.t('command_k.pages.my_wallets.search_placeholder'),
},
NFT_TOKEN_DETAIL: {
listTitle: (command: SearchItem) =>
command.type === SearchItemType.NFT
? command.name
: i18n.t('command_k.pages.my_nfts.section_title'),
searchPlaceholder: i18n.t(
'command_k.pages.nft_token_detail.search_placeholder',
),
},
TOKEN_DETAIL: {
listTitle: (command: SearchItem) =>
command.type === SearchItemType.Token
Expand All @@ -52,4 +69,4 @@ export const PAGES: { [KEY: string]: Page } = {
},
};

export type CommandKPage = typeof PAGES[keyof typeof PAGES];
export type CommandKPage = (typeof PAGES)[keyof typeof PAGES];
Loading

0 comments on commit b918f06

Please sign in to comment.