From 51fae2567833419f5ba9ef26adf04efd43795e0d Mon Sep 17 00:00:00 2001 From: Alunara <> Date: Sat, 22 Feb 2025 01:06:06 +0100 Subject: [PATCH 01/30] styling: add missing reactive border color to clickable chips --- packages/curve-ui-kit/src/themes/chip/mui-chip.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/curve-ui-kit/src/themes/chip/mui-chip.ts b/packages/curve-ui-kit/src/themes/chip/mui-chip.ts index 33d396001..7c1c68240 100644 --- a/packages/curve-ui-kit/src/themes/chip/mui-chip.ts +++ b/packages/curve-ui-kit/src/themes/chip/mui-chip.ts @@ -107,6 +107,7 @@ export const defineMuiChip = ( props: { clickable: true }, style: { borderRadius: Chips.BorderRadius.Clickable, + borderColor: Chips.Default.Stroke, cursor: 'pointer', '&:has(.MuiChip-icon)': { ...handleBreakpoints({ paddingInline: Spacing.sm }) }, '&:hover': { From 536017b96fcca3453b6c50df5df2a265a68b4961 Mon Sep 17 00:00:00 2001 From: Alunara <> Date: Sat, 22 Feb 2025 01:06:40 +0100 Subject: [PATCH 02/30] styling: change color of divider according to design system --- packages/curve-ui-kit/src/themes/components.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/curve-ui-kit/src/themes/components.ts b/packages/curve-ui-kit/src/themes/components.ts index 2959f93f9..b26d0d79c 100644 --- a/packages/curve-ui-kit/src/themes/components.ts +++ b/packages/curve-ui-kit/src/themes/components.ts @@ -46,6 +46,13 @@ export const createComponents = (design: DesignSystem, typography: TypographyOpt paper: { maxHeight: '100dvh', [basicMuiTheme.breakpoints.down('tablet')]: { margin: 0 } }, }, }, + MuiDivider: { + styleOverrides: { + root: { + borderColor: design.Layer[2].Outline, + }, + }, + }, MuiFormControlLabel: { styleOverrides: { root: { margin: '0' }, // by default there is a negative margin 🤦 From ce0c9c6c2851b1318e30e5c3adb06dd97348a773 Mon Sep 17 00:00:00 2001 From: Alunara <> Date: Wed, 26 Feb 2025 17:03:52 +0100 Subject: [PATCH 03/30] styling: remove focus outline on lists --- packages/curve-ui-kit/src/themes/components.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/curve-ui-kit/src/themes/components.ts b/packages/curve-ui-kit/src/themes/components.ts index b26d0d79c..94be0ddf4 100644 --- a/packages/curve-ui-kit/src/themes/components.ts +++ b/packages/curve-ui-kit/src/themes/components.ts @@ -104,6 +104,15 @@ export const createComponents = (design: DesignSystem, typography: TypographyOpt }, }, }, + MuiList: { + styleOverrides: { + root: { + '&:focus': { + outline: 'none', + }, + }, + }, + }, MuiMenuItem: defineMuiMenuItem(design), MuiSelect: defineMuiSelect(design, typography), MuiSlider: { From 129e292ee911df357437034311258d36c455ea45 Mon Sep 17 00:00:00 2001 From: Alunara <> Date: Wed, 26 Feb 2025 21:05:50 +0100 Subject: [PATCH 04/30] styling: fix card header being aligned to bottom --- packages/curve-ui-kit/src/themes/card-header/mui-card-header.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/curve-ui-kit/src/themes/card-header/mui-card-header.ts b/packages/curve-ui-kit/src/themes/card-header/mui-card-header.ts index 701b458ab..18eb18832 100644 --- a/packages/curve-ui-kit/src/themes/card-header/mui-card-header.ts +++ b/packages/curve-ui-kit/src/themes/card-header/mui-card-header.ts @@ -12,7 +12,6 @@ export const defineMuiCardHeader = ( styleOverrides: { root: { padding: `${Spacing.lg.desktop} ${Spacing.md.desktop} ${Spacing.sm.desktop}`, - alignItems: 'flex-end', borderBottom: `1px solid ${design.Layer[3].Outline}`, minHeight: `calc(${ButtonSize.lg} + 1px)`, // 1px to account for border variants: [ From 9671b4954142ae8cd80868556474c12f0624f6e7 Mon Sep 17 00:00:00 2001 From: Alunara <> Date: Thu, 6 Feb 2025 01:12:32 +0100 Subject: [PATCH 05/30] feat: add token select feature --- .../select-token/SelectToken.stories.tsx | 196 ++++++++++++++++++ .../src/features/select-token/index.ts | 2 + .../src/features/select-token/types.ts | 8 + .../select-token/ui/TokenSelectButton.tsx | 95 +++++++++ .../select-token/ui/TokenSelector.tsx | 66 ++++++ .../select-token/ui/modal/FavoriteTokens.tsx | 40 ++++ .../select-token/ui/modal/TokenList.tsx | 157 ++++++++++++++ .../select-token/ui/modal/TokenOption.tsx | 61 ++++++ .../ui/modal/TokenSelectorModal.tsx | 58 ++++++ .../select-token/ui/modal/TokenSettings.tsx | 45 ++++ .../switch-chain/ui/ChainSwitcher.tsx | 4 +- .../user-profile/settings/Settings.tsx | 2 + .../hide-small-pools/HideSmallPools.tsx | 22 ++ .../settings/hide-small-pools/index.ts | 1 + .../src/features/user-profile/store.ts | 4 + .../src/shared/ui/ModalDialog.tsx | 6 +- .../ui/ModalSettingsButton.tsx} | 2 +- .../curve-ui-kit/src/shared/ui/TokenIcon.tsx | 3 +- .../src/themes/design/1_sizes_spaces.ts | 1 + 19 files changed, 767 insertions(+), 6 deletions(-) create mode 100644 packages/curve-ui-kit/src/features/select-token/SelectToken.stories.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/index.ts create mode 100644 packages/curve-ui-kit/src/features/select-token/types.ts create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/TokenSelectButton.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/TokenSelector.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/modal/FavoriteTokens.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/modal/TokenList.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/modal/TokenOption.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/modal/TokenSelectorModal.tsx create mode 100644 packages/curve-ui-kit/src/features/select-token/ui/modal/TokenSettings.tsx create mode 100644 packages/curve-ui-kit/src/features/user-profile/settings/hide-small-pools/HideSmallPools.tsx create mode 100644 packages/curve-ui-kit/src/features/user-profile/settings/hide-small-pools/index.ts rename packages/curve-ui-kit/src/{features/switch-chain/ui/SettingsButton.tsx => shared/ui/ModalSettingsButton.tsx} (82%) diff --git a/packages/curve-ui-kit/src/features/select-token/SelectToken.stories.tsx b/packages/curve-ui-kit/src/features/select-token/SelectToken.stories.tsx new file mode 100644 index 000000000..5c5fa12e2 --- /dev/null +++ b/packages/curve-ui-kit/src/features/select-token/SelectToken.stories.tsx @@ -0,0 +1,196 @@ +import { useState } from 'react' +import type { Meta, StoryObj } from '@storybook/react' +import { Button, Stack, Typography } from '@mui/material' +import { TokenSelector } from './' +import type { TokenOption } from './types' + +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' + +const { Spacing } = SizesAndSpaces + +const defaultTokens: TokenOption[] = [ + { + chain: 'ethereum', + address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + symbol: 'ETH', + label: 'Ethereum', + }, + { + chain: 'ethereum', + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + symbol: 'USDC', + label: 'Circle Dollar', + }, + { + chain: 'ethereum', + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + symbol: 'USDT', + label: 'Tether Dollar', + }, + { + chain: 'ethereum', + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + symbol: 'DAI', + label: 'Maker DAI', + }, +] + +const defaultBalances = { + [defaultTokens[0].address]: '32', + [defaultTokens[1].address]: '1000.00', + [defaultTokens[3].address]: '2000.00', +} + +const defaultTokenPrices = { + [defaultTokens[0].address]: 2600, + [defaultTokens[1].address]: 0.996, + [defaultTokens[2].address]: 1.01, +} + +const defaultFavorites = [defaultTokens[0], defaultTokens[1]] + +const defaultDisabledTokens = [defaultTokens[2].address] + +const TokenSelectorComponent = ({ + selectedToken: selectedTokenInit, + ...props +}: React.ComponentProps) => { + const [selectedToken, setSelectedToken] = useState(selectedTokenInit) + + return +} + +const meta: Meta = { + title: 'UI Kit/Features/TokenSelector', + component: TokenSelectorComponent, + args: { + selectedToken: defaultTokens[0], + tokens: defaultTokens, + favorites: defaultFavorites, + balances: defaultBalances, + tokenPrices: defaultTokenPrices, + disabled: false, + showSearch: true, + showSettings: true, + compact: false, + error: '', + disabledTokens: defaultDisabledTokens, + onToken: (token) => console.log('Selected token:', token), + }, + argTypes: { + tokens: { + control: 'object', + description: 'Array of token options to display in selector', + }, + favorites: { + control: 'object', + description: 'Array of favorite token options to display in selector', + }, + balances: { + control: 'object', + description: 'Record of token balances by address', + }, + tokenPrices: { + control: 'object', + description: 'Record of token prices in USD by address', + }, + disabled: { + control: 'boolean', + description: 'Disables the token selector button and modal', + }, + showSearch: { + control: 'boolean', + description: 'Shows search input in token selector modal', + }, + showSettings: { + control: 'boolean', + description: 'Shows settings button in token selector modal footer', + }, + compact: { + control: 'boolean', + description: 'Renders the modal in a compact size', + }, + selectedToken: { + control: 'object', + description: 'Currently selected token', + }, + error: { + control: 'text', + description: 'Custom error message to display in the token selector modal', + }, + disabledTokens: { + control: 'object', + description: 'Array of token addresses that should be disabled in the selector', + }, + customOptions: { + control: false, + description: 'Adds extra custom options to the modal, below the favorites', + }, + onToken: { + action: 'token selected', + description: 'Callback when a token is selected', + }, + }, +} + +type Story = StoryObj + +export const Default: Story = { + parameters: { + docs: { + description: { + component: 'TokenSelector allows selecting a token from a list with search, favorites and balances', + story: 'Default view showing token selector button that opens modal', + }, + }, + }, +} + +export const NoSelectedToken: Story = { + args: { + selectedToken: undefined, + disabled: true, + }, + parameters: { + docs: { + description: { + story: 'Token selector with no token selected initially', + }, + }, + }, +} + +export const WithError: Story = { + args: { + error: 'Failed to load tokens. Please try again later.', + }, + parameters: { + docs: { + description: { + story: 'Token selector displaying an error message', + }, + }, + }, +} + +export const WithCustomOptions: Story = { + args: { + customOptions: ( + + Custom Options + + + ), + }, + parameters: { + docs: { + description: { + story: 'Token selector with custom options displayed below favorites', + }, + }, + }, +} + +export default meta diff --git a/packages/curve-ui-kit/src/features/select-token/index.ts b/packages/curve-ui-kit/src/features/select-token/index.ts new file mode 100644 index 000000000..5f1acf4f0 --- /dev/null +++ b/packages/curve-ui-kit/src/features/select-token/index.ts @@ -0,0 +1,2 @@ +export { TokenSelector } from './ui/TokenSelector' +export * from './types' diff --git a/packages/curve-ui-kit/src/features/select-token/types.ts b/packages/curve-ui-kit/src/features/select-token/types.ts new file mode 100644 index 000000000..197e60098 --- /dev/null +++ b/packages/curve-ui-kit/src/features/select-token/types.ts @@ -0,0 +1,8 @@ +import { Address } from '@ui-kit/utils' + +export type TokenOption = { + chain: string + address: Address + symbol: string + label: string +} diff --git a/packages/curve-ui-kit/src/features/select-token/ui/TokenSelectButton.tsx b/packages/curve-ui-kit/src/features/select-token/ui/TokenSelectButton.tsx new file mode 100644 index 000000000..ad0892e29 --- /dev/null +++ b/packages/curve-ui-kit/src/features/select-token/ui/TokenSelectButton.tsx @@ -0,0 +1,95 @@ +import CircularProgress from '@mui/material/CircularProgress' +import Select from '@mui/material/Select' +import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' +import type { SxProps } from '@mui/system' + +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { TokenIcon } from '@ui-kit/shared/ui/TokenIcon' + +const { Spacing, ButtonSize, MinWidth } = SizesAndSpaces + +import type { TokenOption } from '../types' + +const ButtonContent = ({ token, disabled }: { token: TokenOption; disabled: boolean }) => ( + + + {token.symbol} + +) + +const Spinner = () => ( + theme.palette.text.secondary, + }} + /> +) + +export type TokenSelectButtonCallbacks = { + onClick: () => void +} + +export type TokenSelectButtonProps = { + token?: TokenOption + disabled: boolean +} + +export type Props = TokenSelectButtonProps & + TokenSelectButtonCallbacks & { + sx?: SxProps + } + +/** The token selector is Select but acts like a button, so it's a bit unique */ +export const TokenSelectButton = ({ token, disabled, onClick, sx }: Props) => ( +