From bb773c6fadd11d574de4db994e3d43a32d6f1f00 Mon Sep 17 00:00:00 2001 From: Fabrice Francois Date: Fri, 8 Sep 2023 08:58:44 -0400 Subject: [PATCH] Feature: APP-2445 - ActionItemAddress (#24) --- CHANGELOG.md | 4 + .../actionItems/actionItemAddress.stories.tsx | 65 +++++++ .../actionItems/actionItemAddress.tsx | 176 ++++++++++++++++++ src/components/actionItems/index.ts | 1 + src/components/avatar/avatar.tsx | 40 ++-- src/components/index.ts | 1 + 6 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 src/components/actionItems/actionItemAddress.stories.tsx create mode 100644 src/components/actionItems/actionItemAddress.tsx create mode 100644 src/components/actionItems/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a344bb9..1f14a5d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `ActionItemAddress` component + ## [0.2.13] - 2023-08-31 ### Fixed diff --git a/src/components/actionItems/actionItemAddress.stories.tsx b/src/components/actionItems/actionItemAddress.stories.tsx new file mode 100644 index 000000000..4294a89be --- /dev/null +++ b/src/components/actionItems/actionItemAddress.stories.tsx @@ -0,0 +1,65 @@ +// Button.stories.ts|tsx + +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { IconCopy, IconGovernance, IconLinkExternal } from '../icons'; +import { ListItemAction } from '../listItem'; +import { ActionItemAddress, TAG_WALLET_ID_VARIANTS } from './actionItemAddress'; + +const meta: Meta = { + component: ActionItemAddress, + title: 'Components/ActionItems/Address', + tags: ['autodocs'], + argTypes: { + addressOrEns: { control: { type: 'text' } }, + avatar: { control: { type: 'text' } }, + tagLabel: { control: { type: 'text' } }, + walletId: { + options: TAG_WALLET_ID_VARIANTS, + control: { type: 'radio' }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: ({ ...args }) => , + args: { + addressOrEns: '0x0000000000000000000000000000000000000000', + tagLabel: 'You', + delegations: 2, + delegationLabel: 'Delegations', + supplyPercentage: 10, + tokenAmount: '1000', + tokenSymbol: 'ANT', + menuOptions: [ + { + component: ( + } bgWhite /> + ), + }, + { + component: ( + } + bgWhite + /> + ), + }, + { + component: ( + } + bgWhite + /> + ), + }, + ], + }, +}; diff --git a/src/components/actionItems/actionItemAddress.tsx b/src/components/actionItems/actionItemAddress.tsx new file mode 100644 index 000000000..26ea535ca --- /dev/null +++ b/src/components/actionItems/actionItemAddress.tsx @@ -0,0 +1,176 @@ +import React from 'react'; +import styled from 'styled-components'; + +import { useScreen } from '../../hooks'; +import { shortenAddress } from '../../utils'; +import { Avatar } from '../avatar'; +import { ButtonIcon } from '../button'; +import { Dropdown, type ListItemProps } from '../dropdown'; +import { IconLinkExternal, IconMenuVertical } from '../icons'; +import { Tag } from '../tag'; + +/** + * Type declarations for `ActionItemAddressProps`. + */ +export type ActionItemAddressProps = { + /** Wallet address or ENS domain name. */ + addressOrEns: string; + + /** Optional ENS avatar URL. If not provided and the wallet address is valid, + * it will be used to generate a Blockies avatar. + */ + avatar?: string; + + /** Label for the delegation amount. */ + delegationLabel: string; + + /** Number of delegations. */ + delegations: number; + + /** List of dropdown menu options. */ + menuOptions: ListItemProps[]; + + /** Optional click handler for the "view on-chain" action. */ + onViewOnChainClick?: React.MouseEventHandler; + + /** Percentage of supply. */ + supplyPercentage: number; + + /** Optional label for the wallet tag. */ + tagLabel?: string; + + /** Number of tokens delegated. */ + tokenAmount: string; + + /** Symbol of the token delegated. */ + tokenSymbol: string; + + /** ID variant for the wallet, which can be 'delegate' or 'you'. */ + walletId?: TagWalletIdProps['variant']; +}; + +/** + * `ActionItemAddress` component: Displays an address item with associated actions. + * @param props - Component properties following `ActionItemAddressProps` type. + * @returns JSX Element. + */ +export const ActionItemAddress: React.FC = (props) => { + const { + addressOrEns, + avatar, + delegationLabel, + delegations, + menuOptions, + onViewOnChainClick, + supplyPercentage, + tagLabel, + tokenAmount, + tokenSymbol, + walletId, + } = props; + + const { isDesktop } = useScreen(); + + return ( + + + + + + {shortenAddress(addressOrEns)} + {walletId && tagLabel && ( + + )} + + + + {tokenAmount} + {tokenSymbol} + {supplyPercentage}% + + + {delegations > 0 && ( + <> + {delegations} + {delegationLabel} + + )} + + + + + {isDesktop && ( + } + size="small" + bgWhite + onClick={onViewOnChainClick} + /> + )} + + } size="small" bgWhite />} + /> + + + ); +}; + +export const TAG_WALLET_ID_VARIANTS = ['delegate', 'you'] as const; +type TagWalletIdVariant = (typeof TAG_WALLET_ID_VARIANTS)[number]; + +/** + * Type declarations for `TagWalletIdProps`. + */ +type TagWalletIdProps = { + /** Optional CSS classes to apply to the tag. */ + className?: string; + /** Label to display on the tag. */ + label: string; + /** Variant of the tag which affects its color. Can be 'delegate' or 'you'. */ + variant: TagWalletIdVariant; +}; + +/** + * `TagWalletId` component: Displays a styled tag based on the provided variant. + * @param props - Component properties following `TagWalletIdProps` type. + * @returns JSX Element. + */ +const TagWalletId: React.FC = ({ className, label, variant }) => { + const colorScheme = variant === 'you' ? 'neutral' : 'info'; + return ; +}; + +const Container = styled.div.attrs({ + className: + 'bg-ui-0 flex py-2 items-center px-1.5 desktop:pr-2 desktop:pl-3 space-x-2 w-full border-b border-b-ui-100', +})``; + +const ContentWrapper = styled.div.attrs({ className: 'desktop:flex flex-1 space-y-0.5' })``; + +const Content = styled.div.attrs({ + className: 'flex desktop:flex-1 space-x-1.5 font-semibold text-ui-600 ft-text-sm', +})``; + +const InfoWrapper = styled.div.attrs({ + className: 'flex desktop:flex-1 space-x-0.25', +})``; + +const InfoLabel = styled.span.attrs({ className: 'font-normal text-ui-500' })``; + +const Wallet = styled.div.attrs({ className: 'flex desktop:flex-1 items-center' })``; + +const AddressOrEns = styled.div.attrs({ className: 'font-semibold text-ui-800 ft-text-base' })``; + +const ButtonGroup = styled.div.attrs({ className: 'flex gap-x-1.5' })``; diff --git a/src/components/actionItems/index.ts b/src/components/actionItems/index.ts new file mode 100644 index 000000000..e962e4f44 --- /dev/null +++ b/src/components/actionItems/index.ts @@ -0,0 +1 @@ +export * from './actionItemAddress'; diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx index 5edcbb6f6..2212f135e 100644 --- a/src/components/avatar/avatar.tsx +++ b/src/components/avatar/avatar.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Blockies from 'react-blockies'; import styled from 'styled-components'; + import { IsAddress } from '../../utils/addresses'; export type AvatarProps = { @@ -23,15 +24,30 @@ const sizes: SizesType = { /** Simple Avatar*/ export const Avatar: React.FC = ({ mode = 'circle', size = 'default', src }) => { - return ( - - {IsAddress(src) ? ( - - ) : ( - - )} - - ); + const [error, setError] = useState(false); + + useEffect(() => { + setError(false); + }, [src]); + + if (!error) { + return ( + + {IsAddress(src) || src?.endsWith('.eth') ? ( + + ) : ( + { + setError(true); + }} + /> + )} + + ); + } + + return ; }; type StyledAvatarProps = Pick; @@ -46,6 +62,8 @@ const AvatarContainer = styled.div.attrs(({ size, mode }: StyledContainerProps) const className = `overflow-hidden bg-ui-100 ${sizes[size].sizes} ${mode === 'circle' ? 'rounded-full' : 'rounded-2xl'} - `; + `; return { className }; })``; + +const FallbackAvatar = styled.div.attrs({ className: 'w-3 h-3 rounded-full bg-gradient-to-tl from-ui-900' })``; diff --git a/src/components/index.ts b/src/components/index.ts index cb650a28a..8df4bc030 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,3 +1,4 @@ +export * from './actionItems'; export * from './alerts'; export * from './avatar'; export * from './backdrop';