diff --git a/.changeset/witty-bananas-play.md b/.changeset/witty-bananas-play.md new file mode 100644 index 0000000000..663620ca06 --- /dev/null +++ b/.changeset/witty-bananas-play.md @@ -0,0 +1,5 @@ +--- +'@penumbra-zone/ui': patch +--- + +Update UI components: `ValueViewComponent`, `AssetIcon`, and `Popover` diff --git a/packages/ui/src/AssetIcon/DelegationTokenIcon.tsx b/packages/ui/src/AssetIcon/DelegationTokenIcon.tsx index db7a46a802..aa8bc12b08 100644 --- a/packages/ui/src/AssetIcon/DelegationTokenIcon.tsx +++ b/packages/ui/src/AssetIcon/DelegationTokenIcon.tsx @@ -9,8 +9,6 @@ const Svg = styled.svg.attrs({ })` display: block; border-radius: ${props => props.theme.borderRadius.full}; - width: 24px; - height: 24px; `; const getFirstEightCharactersOfValidatorId = (displayDenom = ''): [string, string] => { diff --git a/packages/ui/src/AssetIcon/UnbondingTokenIcon.tsx b/packages/ui/src/AssetIcon/UnbondingTokenIcon.tsx index e2a848e77c..a5f2e41982 100644 --- a/packages/ui/src/AssetIcon/UnbondingTokenIcon.tsx +++ b/packages/ui/src/AssetIcon/UnbondingTokenIcon.tsx @@ -9,8 +9,6 @@ const Svg = styled.svg.attrs({ })` display: block; border-radius: ${props => props.theme.borderRadius.full}; - width: 24px; - height: 24px; `; const getFirstEightCharactersOfValidatorId = (displayDenom = ''): [string, string] => { diff --git a/packages/ui/src/AssetIcon/index.stories.tsx b/packages/ui/src/AssetIcon/index.stories.tsx new file mode 100644 index 0000000000..aa0a66e713 --- /dev/null +++ b/packages/ui/src/AssetIcon/index.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AssetIcon } from '.'; +import { + PENUMBRA_METADATA, + DELEGATION_TOKEN_METADATA, + UNBONDING_TOKEN_METADATA, + UNKNOWN_TOKEN_METADATA, + PIZZA_METADATA, +} from '../utils/bufs'; + +const meta: Meta = { + component: AssetIcon, + tags: ['autodocs', '!dev'], + argTypes: { + metadata: { + options: ['Penumbra', 'Pizza', 'Delegation token', 'Unbonding token', 'Unknown asset'], + mapping: { + Penumbra: PENUMBRA_METADATA, + Pizza: PIZZA_METADATA, + 'Delegation token': DELEGATION_TOKEN_METADATA, + 'Unbonding token': UNBONDING_TOKEN_METADATA, + 'Unknown asset': UNKNOWN_TOKEN_METADATA, + }, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + size: 'md', + metadata: PENUMBRA_METADATA, + }, +}; diff --git a/packages/ui/src/AssetIcon/index.tsx b/packages/ui/src/AssetIcon/index.tsx index 1195538ed3..d4f87c27e4 100644 --- a/packages/ui/src/AssetIcon/index.tsx +++ b/packages/ui/src/AssetIcon/index.tsx @@ -1,52 +1,62 @@ +import { ReactNode } from 'react'; +import styled from 'styled-components'; import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; -import { Identicon } from '../Identicon'; -import { DelegationTokenIcon } from './DelegationTokenIcon'; import { getDisplay } from '@penumbra-zone/getters/metadata'; import { assetPatterns } from '@penumbra-zone/types/assets'; +import { Identicon } from '../Identicon'; +import { DelegationTokenIcon } from './DelegationTokenIcon'; import { UnbondingTokenIcon } from './UnbondingTokenIcon'; -import styled from 'styled-components'; -const BorderWrapper = styled.div` +type Size = 'lg' | 'md' | 'sm'; + +const sizeMap: Record = { + lg: 32, + md: 24, + sm: 16, +}; + +const BorderWrapper = styled.div<{ $size: Size }>` + width: ${props => sizeMap[props.$size]}px; + height: ${props => sizeMap[props.$size]}px; border-radius: ${props => props.theme.borderRadius.full}; - border: 1px solid ${props => props.theme.color.other.tonalStroke}; overflow: hidden; + + & > * { + width: 100%; + height: 100%; + } `; const IconImg = styled.img` display: block; - width: 24px; - height: 24px; `; -export interface AssetIcon { +export interface AssetIconProps { + size?: Size; metadata?: Metadata; } -export const AssetIcon = ({ metadata }: AssetIcon) => { +export const AssetIcon = ({ metadata, size = 'md' }: AssetIconProps) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- possibly empty string const icon = metadata?.images[0]?.png || metadata?.images[0]?.svg; const display = getDisplay.optional()(metadata); const isDelegationToken = display ? assetPatterns.delegationToken.matches(display) : false; const isUnbondingToken = display ? assetPatterns.unbondingToken.matches(display) : false; - return ( - - {/* eslint-disable-next-line no-nested-ternary -- readable ternary */} - {icon ? ( - - ) : // eslint-disable-next-line no-nested-ternary -- readable ternary - isDelegationToken ? ( - - ) : isUnbondingToken ? ( - /** - * @todo: Render a custom unbonding token for validators that have a - * logo -- e.g., with the validator ID superimposed over the validator - * logo. - */ - - ) : ( - - )} - - ); + let assetIcon: ReactNode; + if (icon) { + assetIcon = ; + } else if (isDelegationToken) { + assetIcon = ; + } else if (isUnbondingToken) { + /** + * @todo: Render a custom unbonding token for validators that have a + * logo -- e.g., with the validator ID superimposed over the validator logo. + */ + assetIcon = ; + } else { + assetIcon = ; + } + + return {assetIcon}; }; diff --git a/packages/ui/src/AssetIcon/shared.ts b/packages/ui/src/AssetIcon/shared.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/ui/src/ValueViewComponent/index.tsx b/packages/ui/src/ValueViewComponent/index.tsx index ed35b99882..1292f580c0 100644 --- a/packages/ui/src/ValueViewComponent/index.tsx +++ b/packages/ui/src/ValueViewComponent/index.tsx @@ -1,11 +1,12 @@ +import { ReactNode } from 'react'; +import styled from 'styled-components'; import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { getMetadata } from '@penumbra-zone/getters/value-view'; +import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view'; import { ConditionalWrap } from '../ConditionalWrap'; -import { Pill } from '../Pill'; +import { Pill, PillProps } from '../Pill'; import { Text } from '../Text'; -import styled from 'styled-components'; import { AssetIcon } from '../AssetIcon'; -import { getMetadata } from '@penumbra-zone/getters/value-view'; -import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view'; import { Density } from '../types/Density'; import { useDensity } from '../hooks/useDensity'; @@ -25,8 +26,9 @@ const AssetIconWrapper = styled.div` `; const PillMarginOffsets = styled.div<{ $density: Density }>` - margin-left: ${props => props.theme.spacing(props.$density === 'sparse' ? -2 : -1)}; - margin-right: ${props => props.theme.spacing(props.$density === 'sparse' ? -1 : 0)}; + margin-left: ${props => props.theme.spacing(-2)}; + margin-top: ${props => props.theme.spacing(props.$density === 'sparse' ? 0 : -1)}; + margin-bottom: ${props => props.theme.spacing(props.$density === 'sparse' ? 0 : -1)}; `; const Content = styled.div<{ $context: Context; $priority: 'primary' | 'secondary' }>` @@ -54,6 +56,14 @@ const SymbolWrapper = styled.div` white-space: nowrap; `; +const ValueText = ({ children, density }: { children: ReactNode; density: Density }) => { + if (density === 'sparse') { + return {children}; + } + + return {children}; +}; + export interface ValueViewComponentProps { valueView?: ValueView; /** @@ -67,7 +77,7 @@ export interface ValueViewComponentProps { * represents a secondary value, such as when it's an equivalent value of a * numeraire. */ - priority?: 'primary' | 'secondary'; + priority?: PillProps['priority']; } /** @@ -104,13 +114,13 @@ export const ValueViewComponent = ( > - + - {formattedAmount} + {formattedAmount} - {symbol} + {symbol} diff --git a/packages/ui/src/utils/bufs/metadata.ts b/packages/ui/src/utils/bufs/metadata.ts index cf9f3baf0f..17637628b1 100644 --- a/packages/ui/src/utils/bufs/metadata.ts +++ b/packages/ui/src/utils/bufs/metadata.ts @@ -73,3 +73,7 @@ export const PIZZA_METADATA = new Metadata({ display: 'pizza', denomUnits: [{ denom: 'upizza' }, { denom: 'pizza', exponent: 6 }], }); + +export const UNKNOWN_TOKEN_METADATA = new Metadata({ + penumbraAssetId: { inner: new Uint8Array([]) }, +}); diff --git a/packages/ui/src/utils/popover.ts b/packages/ui/src/utils/popover.ts index 174deaa044..e5ed4637d7 100644 --- a/packages/ui/src/utils/popover.ts +++ b/packages/ui/src/utils/popover.ts @@ -17,7 +17,7 @@ export const PopoverContent = styled.div` width: 240px; max-width: 320px; - padding: ${props => props.theme.spacing(3)} ${props => props.theme.spacing(2)}; + padding: ${props => props.theme.spacing(3)}; background: ${props => props.theme.color.other.dialogBackground}; border: 1px solid ${props => props.theme.color.other.tonalStroke};