From 18de087745207f688c736a75b0358bac1847325c Mon Sep 17 00:00:00 2001 From: AtelyPham Date: Thu, 3 Aug 2023 18:21:09 +0700 Subject: [PATCH 1/7] refactor: the TokenSelector component --- libs/icons/src/TokenIcon.tsx | 22 ++++++++++--- libs/icons/src/hooks/useDynamicSVGImport.tsx | 8 +++-- libs/icons/src/tokens/placeholder.svg | 3 ++ libs/icons/src/types.ts | 2 +- .../src/components/ListCard/AssetListItem.tsx | 20 ++++++++---- .../src/components/ListCard/TokenListCard.tsx | 5 ++- .../src/components/ListCard/types.ts | 5 --- .../TokenSelector/TokenSelector.tsx | 32 ++++++------------- .../src/components/TokenSelector/index.ts | 5 +++ .../src/components/TokenSelector/types.ts | 9 ++---- .../src/components/index.ts | 5 +-- .../src/stories/molecules/Card.stories.jsx | 10 ++---- .../molecules/TokenSelector.stories.jsx | 19 ----------- .../molecules/TokenSelector.stories.tsx | 18 +++++++++++ 14 files changed, 83 insertions(+), 80 deletions(-) create mode 100644 libs/icons/src/tokens/placeholder.svg delete mode 100644 libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.jsx create mode 100644 libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx diff --git a/libs/icons/src/TokenIcon.tsx b/libs/icons/src/TokenIcon.tsx index e29457a91f..95e9fe6ee1 100644 --- a/libs/icons/src/TokenIcon.tsx +++ b/libs/icons/src/TokenIcon.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import React, { cloneElement, useMemo } from 'react'; +import React, { MouseEventHandler, cloneElement, useMemo } from 'react'; import { Spinner } from './Spinner'; import { useDynamicSVGImport } from './hooks/useDynamicSVGImport'; @@ -27,12 +27,24 @@ export const TokenIcon: React.FC = ( }); const className = useMemo( - () => twMerge(cx({ 'cursor-copy': Boolean(onClick) }), classNameProp), + () => + twMerge( + cx({ + 'cursor-copy': Boolean(onClick), + [cx( + 'fill-mono-60 stroke-mono-60', + 'dark:fill-mono-140 dark:stroke-mono-140' + )]: typeof name === 'undefined', // Style for placeholder + }), + classNameProp + ), [classNameProp] ); // Prevent infinite loop when the passed onClick not use useCallback - const onClickRef = React.useRef(onClick); + const onClickRef = React.useRef< + MouseEventHandler | undefined + >(onClick); if (error) { return {error.message}; @@ -53,12 +65,12 @@ export const TokenIcon: React.FC = ( }; return isActive ? ( -
+
{cloneElement(svgElement, props)}
) : ( - cloneElement(svgElement, props) + cloneElement(svgElement, { ...props, onClick: onClickRef.current }) ); } diff --git a/libs/icons/src/hooks/useDynamicSVGImport.tsx b/libs/icons/src/hooks/useDynamicSVGImport.tsx index d1a80842fd..a43c74a796 100644 --- a/libs/icons/src/hooks/useDynamicSVGImport.tsx +++ b/libs/icons/src/hooks/useDynamicSVGImport.tsx @@ -32,7 +32,7 @@ export interface DynamicSVGImportOptions { * @returns `error`, `loading` and `SvgIcon` in an object */ export function useDynamicSVGImport( - name: string, + name?: string, options: DynamicSVGImportOptions = {} ) { const [importedIcon, setImportedIcon] = useState< @@ -43,7 +43,11 @@ export function useDynamicSVGImport( const { onCompleted, onError } = options; - const _name = useMemo(() => name.trim().toLowerCase(), [name]); + const _name = useMemo( + () => + typeof name === 'string' ? name.trim().toLowerCase() : 'placeholder', + [name] + ); const type = useMemo(() => options.type ?? 'token', [options]); useEffect(() => { diff --git a/libs/icons/src/tokens/placeholder.svg b/libs/icons/src/tokens/placeholder.svg new file mode 100644 index 0000000000..22b2ca594a --- /dev/null +++ b/libs/icons/src/tokens/placeholder.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/libs/icons/src/types.ts b/libs/icons/src/types.ts index 0efdc917f9..4b0992cdaa 100644 --- a/libs/icons/src/types.ts +++ b/libs/icons/src/types.ts @@ -26,5 +26,5 @@ export interface TokenIconBase /** * The symbol for the cryptocurrency to get the icon */ - name: string; + name?: string; } diff --git a/libs/webb-ui-components/src/components/ListCard/AssetListItem.tsx b/libs/webb-ui-components/src/components/ListCard/AssetListItem.tsx index 32baecf28f..d96b6eb89a 100644 --- a/libs/webb-ui-components/src/components/ListCard/AssetListItem.tsx +++ b/libs/webb-ui-components/src/components/ListCard/AssetListItem.tsx @@ -1,7 +1,13 @@ import { TokenIcon } from '@webb-tools/icons'; import { Typography } from '../../typography'; import { getRoundedAmountString } from '../../utils'; -import { ComponentProps, forwardRef, useMemo, useRef, useState } from 'react'; +import React, { + ComponentProps, + forwardRef, + useMemo, + useRef, + useState, +} from 'react'; import { ListItem } from './ListItem'; import { AssetType } from './types'; import { Button } from '../buttons'; @@ -11,21 +17,21 @@ export const AssetListItem = forwardRef< AssetType & ComponentProps >( ( - { balance, name, symbol, onTokenClick, isTokenAddedToMetamask, ...props }, + { balance, name, symbol, isTokenAddedToMetamask, onClick, ...props }, ref ) => { - const onTokenClickRef = useRef(onTokenClick); + const onTokenClickRef = useRef(onClick); const [isHovered, setIsHovered] = useState(false); const handleTokenIconClick = useMemo(() => { - if (typeof onTokenClick === 'function') { - return (event: any) => { + if (typeof onTokenClickRef.current === 'function') { + return (event: React.MouseEvent) => { event.stopPropagation(); - onTokenClickRef.current?.(symbol); + onTokenClickRef.current?.(event as React.MouseEvent); }; } - }, [onTokenClick, symbol]); + }, []); return ( ( [onChange, setAsset, unavailableTokens] ); - const { filteredPopular, filteredSelect, filteredUnavailable } = useMemo( + const { filteredPopular, filteredSelect } = useMemo( () => ({ filteredPopular: getFilterList(popularTokens), filteredSelect: getFilterList(selectTokens), @@ -93,7 +93,6 @@ export const TokenListCard = forwardRef( onItemChange(current)} - onTokenClick={current.onTokenClick} > {current.symbol} diff --git a/libs/webb-ui-components/src/components/ListCard/types.ts b/libs/webb-ui-components/src/components/ListCard/types.ts index 527db67357..a906ebb6db 100644 --- a/libs/webb-ui-components/src/components/ListCard/types.ts +++ b/libs/webb-ui-components/src/components/ListCard/types.ts @@ -64,11 +64,6 @@ export type AssetType = { */ balance?: number; - /** - * The callback when user clicks on the token - */ - onTokenClick?: (symbol: string) => void; - /** * Check if the token is added to metamask */ diff --git a/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx b/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx index 72806698ea..241dc14235 100644 --- a/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx +++ b/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx @@ -1,26 +1,12 @@ import { TokenIcon } from '@webb-tools/icons'; import cx from 'classnames'; -import { MouseEvent, forwardRef, useMemo, useRef } from 'react'; +import { forwardRef, useMemo } from 'react'; import { twMerge } from 'tailwind-merge'; import { TokenSelectorProps } from './types'; -export const TokenSelector = forwardRef( - ( - { children, className, disabled, isActive, onTokenClick, ...props }, - ref - ) => { - const onTokenClickRef = useRef(onTokenClick); - - const handleTokenIconClick = useMemo(() => { - if (typeof onTokenClick === 'function') { - return (event: MouseEvent) => { - event.stopPropagation(); - onTokenClickRef.current?.(children); - }; - } - }, []); - +const TokenSelector = forwardRef( + ({ children, className, disabled, isActive, ...props }, ref) => { const mergedClsx = useMemo( () => twMerge( @@ -44,14 +30,14 @@ export const TokenSelector = forwardRef( return ( ); } ); + +export default TokenSelector; diff --git a/libs/webb-ui-components/src/components/TokenSelector/index.ts b/libs/webb-ui-components/src/components/TokenSelector/index.ts index f54d29af98..8f4c08f560 100644 --- a/libs/webb-ui-components/src/components/TokenSelector/index.ts +++ b/libs/webb-ui-components/src/components/TokenSelector/index.ts @@ -1 +1,6 @@ +import TokenSelector from './TokenSelector'; + export * from './TokenSelector'; +export { default as TokenSelector } from './TokenSelector'; + +export default TokenSelector; diff --git a/libs/webb-ui-components/src/components/TokenSelector/types.ts b/libs/webb-ui-components/src/components/TokenSelector/types.ts index 20814286d9..9b0d4e5f48 100644 --- a/libs/webb-ui-components/src/components/TokenSelector/types.ts +++ b/libs/webb-ui-components/src/components/TokenSelector/types.ts @@ -5,16 +5,13 @@ export interface TokenSelectorProps PropsOf<'button'> { /** * The chidren must be a token symbol (e.g. eth, dot, ...) + * for rendering the token icon and displaying. + * If not provided, the component will display the placeholder */ - children: string; + children?: string; /** * If `true`, the component will display as disable state */ isActive?: boolean; - - /** - * The callback when user clicks on the token - */ - onTokenClick?: (symbol: string) => void; } diff --git a/libs/webb-ui-components/src/components/index.ts b/libs/webb-ui-components/src/components/index.ts index 48a495d6f1..d68fe6b29f 100644 --- a/libs/webb-ui-components/src/components/index.ts +++ b/libs/webb-ui-components/src/components/index.ts @@ -7,7 +7,6 @@ export * from './Banner'; export * from './BottomDialog'; export * from './Breadcrumbs'; export * from './BridgeInputs'; -export * from './buttons'; export * from './Card'; export * from './CardTable'; export * from './ChainChip'; @@ -25,7 +24,7 @@ export * from './Drawer'; export * from './Dropdown'; export * from './DropdownMenu'; export * from './ErrorFallback'; -export { default as third } from './FeeDetails'; +export { default as FeeDetails } from './FeeDetails'; export * from './FileUploads'; export * from './Filter'; export * from './Footer'; @@ -70,6 +69,7 @@ export { default as ToggleCard } from './ToggleCard'; export * from './TokenPair'; export * from './TokenPairIcons'; export * from './TokenSelector'; +export { default as TokenSelector } from './TokenSelector'; export * from './TokenWithAmount'; export * from './TokensRing'; export * from './Tooltip'; @@ -77,3 +77,4 @@ export * from './WalletConnectionCard'; export * from './WebsiteCommunity'; export * from './WebsiteFooter'; export * from './WebsiteNewsLetterForm'; +export * from './buttons'; diff --git a/libs/webb-ui-components/src/stories/molecules/Card.stories.jsx b/libs/webb-ui-components/src/stories/molecules/Card.stories.jsx index 5a39c01a04..62ea973d5c 100644 --- a/libs/webb-ui-components/src/stories/molecules/Card.stories.jsx +++ b/libs/webb-ui-components/src/stories/molecules/Card.stories.jsx @@ -1,10 +1,6 @@ -import React from 'react'; - -import { - Card, - TitleWithInfo, - TokenSelector, -} from '@webb-tools/webb-ui-components/components'; +import { Card } from '../../components/Card'; +import { TitleWithInfo } from '../../components/TitleWithInfo'; +import TokenSelector from '../../components/TokenSelector'; // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export export default { diff --git a/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.jsx b/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.jsx deleted file mode 100644 index cd4f2368c4..0000000000 --- a/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import { TokenSelector } from '@webb-tools/webb-ui-components/components'; - -// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export -export default { - title: 'Design System/Molecules/TokenSelector', - component: TokenSelector, - // More on argTypes: https://storybook.js.org/docs/react/api/argtypes -}; - -// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Template = (args) => ETH; - -export const Primary = Template.bind({}); -// More on args: https://storybook.js.org/docs/react/writing-stories/args -Primary.args = { - isActive: false, -}; diff --git a/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx b/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx new file mode 100644 index 0000000000..1c2e13fe85 --- /dev/null +++ b/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import TokenSelector from '../../components/TokenSelector'; + +const meta: Meta = { + title: 'Design System/V2 (WIP)/Molecules/TokenSelector', + component: TokenSelector, +}; + +// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default meta; + +type Story = StoryObj; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +export const Default: Story = { + render: () => , +}; From 087bc7473e1f094b329b2454403dfdbbbd3a2ab1 Mon Sep 17 00:00:00 2001 From: AtelyPham Date: Thu, 3 Aug 2023 18:45:59 +0700 Subject: [PATCH 2/7] feat: apply new styles --- .../TokenSelector/TokenSelector.tsx | 46 +++++++++++++------ .../src/components/TokenSelector/types.ts | 7 ++- .../molecules/TokenSelector.stories.tsx | 10 ++++ 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx b/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx index 241dc14235..2063450edb 100644 --- a/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx +++ b/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx @@ -1,40 +1,56 @@ -import { TokenIcon } from '@webb-tools/icons'; +import { ChevronDown, TokenIcon } from '@webb-tools/icons'; import cx from 'classnames'; import { forwardRef, useMemo } from 'react'; import { twMerge } from 'tailwind-merge'; - +import { Typography } from '../../typography/Typography'; import { TokenSelectorProps } from './types'; const TokenSelector = forwardRef( - ({ children, className, disabled, isActive, ...props }, ref) => { + ({ children, className, isDisabled, isActive, ...props }, ref) => { const mergedClsx = useMemo( () => twMerge( cx( - 'px-3 py-2 flex items-center space-x-2 max-w-fit', - 'bg-mono-0 dark:bg-mono-160', - 'border rounded-lg border-mono-60 dark:border-mono-120', - 'text-mono-120 dark:text-mono-0', - 'hover:bg-mono-20 dark:hover:bg-mono-120', - 'disabled:opacity-50 disabled:pointer-events-none dark:disabled:border-mono-60' + 'group px-4 py-2 rounded-lg', + 'flex items-center gap-2 max-w-fit', + 'bg-[#E2E5EB]/30 dark:bg-mono-160', + 'border border-transparent', + 'enabled:hover:border-mono-60', + 'dark:enabled:hover:border-mono-140', + 'disabled:bg-[#E2E5EB]/20 dark:disabled:bg-[#3A3E53]/70' ), className ), [className] ); - const isDisabled = useMemo( - () => isActive || disabled, - [disabled, isActive] + const disabled = useMemo( + () => isActive || isDisabled, + [isDisabled, isActive] ); return ( - ); } diff --git a/libs/webb-ui-components/src/components/TokenSelector/types.ts b/libs/webb-ui-components/src/components/TokenSelector/types.ts index 9b0d4e5f48..e0aa1ee86b 100644 --- a/libs/webb-ui-components/src/components/TokenSelector/types.ts +++ b/libs/webb-ui-components/src/components/TokenSelector/types.ts @@ -2,7 +2,7 @@ import { IWebbComponentBase, PropsOf } from '../../types'; export interface TokenSelectorProps extends IWebbComponentBase, - PropsOf<'button'> { + Omit, 'disabled'> { /** * The chidren must be a token symbol (e.g. eth, dot, ...) * for rendering the token icon and displaying. @@ -14,4 +14,9 @@ export interface TokenSelectorProps * If `true`, the component will display as disable state */ isActive?: boolean; + + /** + * If `true`, the component will display as disable state + */ + isDisabled?: boolean; } diff --git a/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx b/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx index 1c2e13fe85..5258696287 100644 --- a/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx +++ b/libs/webb-ui-components/src/stories/molecules/TokenSelector.stories.tsx @@ -16,3 +16,13 @@ type Story = StoryObj; export const Default: Story = { render: () => , }; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +export const Disabled: Story = { + render: () => , +}; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +export const WithToken: Story = { + render: () => , +}; From 2fbff1641b7710b87e5eef3814f44b621ef922d6 Mon Sep 17 00:00:00 2001 From: AtelyPham Date: Thu, 3 Aug 2023 19:35:28 +0700 Subject: [PATCH 3/7] feat: add shielded token type --- libs/icons/src/ShieldedAssetIcon.tsx | 200 ++++++++++++++++++ libs/icons/src/index.ts | 1 + .../TokenSelector/TokenSelector.tsx | 23 +- .../src/components/TokenSelector/types.ts | 8 + .../molecules/TokenSelector.stories.tsx | 11 +- 5 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 libs/icons/src/ShieldedAssetIcon.tsx diff --git a/libs/icons/src/ShieldedAssetIcon.tsx b/libs/icons/src/ShieldedAssetIcon.tsx new file mode 100644 index 0000000000..7ff9ad1a05 --- /dev/null +++ b/libs/icons/src/ShieldedAssetIcon.tsx @@ -0,0 +1,200 @@ +import { twMerge } from 'tailwind-merge'; +import cx from 'classnames'; +import { createIcon } from './create-icon'; +import { IconBase } from './types'; + +const getSizeProps = (size: IconBase['size']) => { + switch (size) { + case 'lg': + return { + width: 20, + height: 25, + viewBox: '0 0 20 25', + }; + + case 'xl': + return { + width: 40, + height: 49, + viewBox: '0 0 40 49', + }; + + default: + return { + width: 14, + height: 17, + viewBox: '0 0 14 17', + }; + } +}; + +interface ShieldedAssetIconProps extends IconBase { + /** + * Whether to display the placeholder icon or not + */ + displayPlaceholder?: boolean; +} + +const ShieldedAssetIcon = ({ + displayPlaceholder, + ...props +}: ShieldedAssetIconProps) => { + return createIcon({ + ...getSizeProps(props.size), + ...props, + className: twMerge( + displayPlaceholder + ? 'fill-mono-60 stroke-mono-60 dark:fill-mono-140 dark:stroke-mono-140' + : '!fill-none !stroke-none', + props.className + ), + path: ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ), + displayName: 'ShieldedAssetIcon', + }); +}; + +export default ShieldedAssetIcon; diff --git a/libs/icons/src/index.ts b/libs/icons/src/index.ts index adca5d5284..d978901670 100644 --- a/libs/icons/src/index.ts +++ b/libs/icons/src/index.ts @@ -76,6 +76,7 @@ export * from './Search'; export * from './SendPlanLineIcon'; export * from './ShieldKeyholeIcon'; export * from './ShieldedAssetDark'; +export { default as ShieldedAssetIcon } from './ShieldedAssetIcon'; export * from './ShieldedAssetLight'; export * from './ShuffleLine'; export * from './SosLineIcon'; diff --git a/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx b/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx index 2063450edb..b3884423b7 100644 --- a/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx +++ b/libs/webb-ui-components/src/components/TokenSelector/TokenSelector.tsx @@ -1,4 +1,4 @@ -import { ChevronDown, TokenIcon } from '@webb-tools/icons'; +import { ChevronDown, ShieldedAssetIcon, TokenIcon } from '@webb-tools/icons'; import cx from 'classnames'; import { forwardRef, useMemo } from 'react'; import { twMerge } from 'tailwind-merge'; @@ -6,7 +6,17 @@ import { Typography } from '../../typography/Typography'; import { TokenSelectorProps } from './types'; const TokenSelector = forwardRef( - ({ children, className, isDisabled, isActive, ...props }, ref) => { + ( + { + children, + className, + isDisabled, + isActive, + tokenType = 'unshielded', + ...props + }, + ref + ) => { const mergedClsx = useMemo( () => twMerge( @@ -31,7 +41,14 @@ const TokenSelector = forwardRef( return (