Commit e0259db8 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Update styles of outlined buttons and more (#1978)

* selected state for Button

* tests

* fix menu filter button hover issue

* update screenshots

* try to fix SearchBar recent keywords test
parent cf1caf7f
import { Button } from '@chakra-ui/react';
import { Box, Button, Flex } from '@chakra-ui/react';
import React from 'react';
import { test, expect } from 'playwright/lib';
test.use({ viewport: { width: 150, height: 350 } });
[
{ variant: 'solid' },
{ variant: 'solid', colorScheme: 'gray', withDarkMode: true },
{ variant: 'outline', colorScheme: 'gray', withDarkMode: true },
{ variant: 'outline', colorScheme: 'gray-dark', withDarkMode: true },
{ variant: 'outline', colorScheme: 'blue', withDarkMode: true },
{ variant: 'simple', withDarkMode: true },
{ variant: 'ghost', withDarkMode: true },
{ variant: 'subtle' },
{ variant: 'subtle', colorScheme: 'gray', withDarkMode: true },
].forEach(({ variant, colorScheme, withDarkMode }) => {
{ variant: 'solid', states: [ 'default', 'disabled', 'hovered', 'active' ] },
{ variant: 'outline', colorScheme: 'gray', withDarkMode: true, states: [ 'default', 'disabled', 'hovered', 'active', 'selected' ] },
{ variant: 'outline', colorScheme: 'blue', withDarkMode: true, states: [ 'default', 'disabled', 'hovered', 'active', 'selected' ] },
{ variant: 'simple', withDarkMode: true, states: [ 'default', 'hovered' ] },
{ variant: 'ghost', withDarkMode: true, states: [ 'default', 'hovered', 'active' ] },
{ variant: 'subtle', states: [ 'default', 'hovered' ] },
{ variant: 'subtle', colorScheme: 'gray', states: [ 'default', 'hovered' ], withDarkMode: true },
].forEach(({ variant, colorScheme, withDarkMode, states }) => {
test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => {
test('base', async({ render }) => {
const component = await render(<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>);
await expect(component.locator('button')).toHaveScreenshot();
});
test('disabled', async({ render }) => {
const component = await render(<Button variant={ variant } colorScheme={ colorScheme } isDisabled>Click me</Button>);
await expect(component.locator('button')).toHaveScreenshot();
});
test('hovered', async({ render }) => {
const component = await render(<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>);
await component.getByText(/click/i).hover();
await expect(component.locator('button')).toHaveScreenshot();
});
test('', async({ render }) => {
const component = await render(
<Flex p={ 2 } flexDir="column" rowGap={ 3 }>
{ states?.map((state) => {
switch (state) {
case 'default': {
return (
<Box>
<Box color="text_secondary" fontSize="sm">Default:</Box>
<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>
</Box>
);
}
case 'disabled': {
return (
<Box>
<Box color="text_secondary" fontSize="sm">Disabled:</Box>
<Button variant={ variant } colorScheme={ colorScheme } isDisabled>Click me</Button>
</Box>
);
}
case 'active': {
return (
<Box>
<Box color="text_secondary" fontSize="sm">Active:</Box>
<Button variant={ variant } colorScheme={ colorScheme } isActive>Click me</Button>
</Box>
);
}
case 'hovered': {
return (
<Box>
<Box color="text_secondary" fontSize="sm">Hovered:</Box>
<Button variant={ variant } colorScheme={ colorScheme }>Hover me</Button>
</Box>
);
}
case 'selected': {
return (
<Box>
<Box color="text_secondary" fontSize="sm">Selected:</Box>
<Button variant={ variant } colorScheme={ colorScheme } data-selected="true">Click me</Button>
</Box>
);
}
test('active', async({ render }) => {
const component = await render(<Button variant={ variant } colorScheme={ colorScheme } isActive>Click me</Button>);
await expect(component.locator('button')).toHaveScreenshot();
default: {
return null;
}
}
}) }
</Flex>,
);
await component.getByText('Hover me').hover();
await expect(component).toHaveScreenshot();
});
});
});
......@@ -5,25 +5,10 @@ import { runIfFn } from '@chakra-ui/utils';
const variantSolid = defineStyle((props) => {
const { colorScheme: c } = props;
if (c === 'gray') {
const bg = mode(`gray.100`, `whiteAlpha.200`)(props);
return {
bg,
_hover: {
bg: mode(`gray.200`, `whiteAlpha.300`)(props),
_disabled: {
bg,
},
},
_active: { bg: mode(`gray.300`, `whiteAlpha.400`)(props) },
};
}
const bg = `${ c }.600`;
const color = 'white';
const hoverBg = `${ c }.400`;
const activeBg = `${ c }.700`;
const activeBg = hoverBg;
return {
bg,
......@@ -37,6 +22,8 @@ const variantSolid = defineStyle((props) => {
_disabled: {
opacity: 0.2,
},
// According to design there is no "active" or "pressed" state
// It is simply should be the same as the "hover" state
_active: { bg: activeBg },
fontWeight: 600,
};
......@@ -45,22 +32,15 @@ const variantSolid = defineStyle((props) => {
const variantOutline = defineStyle((props) => {
const { colorScheme: c } = props;
const isGrayTheme = c === 'gray' || c === 'gray-dark';
const isGrayTheme = c === 'gray';
const bg = 'transparent';
const color = isGrayTheme ? mode('blackAlpha.800', 'whiteAlpha.800')(props) : mode(`${ c }.600`, `${ c }.300`)(props);
const borderColor = isGrayTheme ? mode('gray.200', 'gray.600')(props) : mode(`${ c }.600`, `${ c }.300`)(props);
const activeBg = isGrayTheme ? mode('blue.50', 'gray.600')(props) : mode(`${ c }.50`, 'gray.600')(props);
const activeColor = (() => {
if (c === 'gray') {
return mode('blue.600', 'gray.50')(props);
}
if (c === 'gray-dark') {
return mode('blue.600', 'gray.50')(props);
}
if (c === 'blue') {
return mode('blue.600', 'gray.50')(props);
}
return 'blue.600';
})();
const selectedBg = isGrayTheme ? mode('blue.50', 'gray.600')(props) : mode(`${ c }.50`, 'gray.600')(props);
const selectedColor = mode('blue.600', 'gray.50')(props);
return {
color,
......@@ -68,42 +48,47 @@ const variantOutline = defineStyle((props) => {
borderWidth: props.borderWidth || '2px',
borderStyle: 'solid',
borderColor,
bg: 'transparent',
bg,
_hover: {
color: 'link_hovered',
borderColor: 'link_hovered',
bg: 'transparent',
_active: {
bg: props.isActive ? activeBg : 'transparent',
borderColor: props.isActive ? activeBg : 'link_hovered',
color: props.isActive ? activeColor : 'link_hovered',
p: {
color: 'link_hovered',
},
bg,
span: {
color: 'link_hovered',
},
_disabled: {
color,
borderColor,
},
p: {
color: 'link_hovered',
},
},
_disabled: {
opacity: 0.2,
},
// According to design there is no "active" or "pressed" state
// It is simply should be the same as the "hover" state
_active: {
bg: activeBg,
borderColor: activeBg,
color: activeColor,
_disabled: {
color,
borderColor,
color: 'link_hovered',
borderColor: 'link_hovered',
bg,
span: {
color: 'link_hovered',
},
p: {
color: activeColor,
_disabled: {
color: 'link_hovered',
borderColor: 'link_hovered',
},
},
// We have a special state for this button variant that serves as a popover trigger.
// When any items (filters) are selected in the popover, the button should change its background and text color.
// The last CSS selector is for redefining styles for the TabList component.
[`
&[data-selected=true],
&[data-selected=true][aria-selected=true]
`]: {
bg: selectedBg,
color: selectedColor,
borderColor: selectedBg,
},
};
});
......
......@@ -61,7 +61,7 @@ const AddressAccountHistory = ({ scrollRef, shouldRender = true }: Props) => {
<AccountHistoryFilter
defaultFilter={ filterValue }
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
hasActiveFilter={ Boolean(filterValue) }
isLoading={ pagination.isLoading }
/>
......
import {
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import type { NovesHistoryFilterValue } from 'types/api/noves';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import FilterButton from 'ui/shared/filters/FilterButton';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
const OPTIONS = [
{ value: 'all', label: 'All' },
{ value: 'received', label: 'Received from' },
{ value: 'sent', label: 'Sent to' },
];
interface Props {
isActive: boolean;
hasActiveFilter: boolean;
defaultFilter: NovesHistoryFilterValue;
onFilterChange: (nextValue: string | Array<string>) => void;
isLoading?: boolean;
}
const AccountHistoryFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }: Props) => {
const { isOpen, onToggle } = useDisclosure();
const AccountHistoryFilter = ({ onFilterChange, defaultFilter, hasActiveFilter, isLoading }: Props) => {
const isInitialLoading = useIsInitialLoading(isLoading);
const onCloseMenu = React.useCallback(() => {
if (isOpen) {
onToggle();
}
}, [ isOpen, onToggle ]);
return (
<Menu isOpen={ isOpen } onClose={ onCloseMenu }>
<MenuButton onClick={ onToggle }>
<FilterButton
isActive={ isOpen || isActive }
isLoading={ isInitialLoading }
onClick={ onToggle }
appliedFiltersNum={ isActive ? 1 : 0 }
as="div"
/>
</MenuButton>
<MenuList zIndex={ 2 }>
<MenuOptionGroup defaultValue={ defaultFilter || 'all' } type="radio" onChange={ onFilterChange }>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="received">Received from</MenuItemOption>
<MenuItemOption value="sent">Sent to</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
<PopoverFilterRadio
name="account_history_filter"
options={ OPTIONS }
onChange={ onFilterChange }
hasActiveFilter={ hasActiveFilter }
isLoading={ isInitialLoading }
defaultValue={ defaultFilter || OPTIONS[0].value }
/>
);
};
......
......@@ -82,7 +82,7 @@ const AddressInternalTxs = ({ scrollRef, shouldRender = true }: Props) => {
<AddressTxsFilter
defaultFilter={ filterValue }
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
hasActiveFilter={ Boolean(filterValue) }
isLoading={ pagination.isLoading }
/>
<AddressCsvExportLink
......
......@@ -111,7 +111,7 @@ const AddressTokens = ({ shouldRender = true }: Props) => {
}
const nftTypeFilter = (
<PopoverFilter isActive={ tokenTypes && tokenTypes.length > 0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }>
<PopoverFilter contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }>
<TokenTypeFilter<NFTTokenType> nftOnly onChange={ handleTokenTypesChange } defaultValue={ tokenTypes }/>
</PopoverFilter>
);
......
......@@ -167,7 +167,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
<AddressTxsFilter
defaultFilter={ filterValue }
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
hasActiveFilter={ Boolean(filterValue) }
isLoading={ addressTxsQuery.pagination.isLoading }
/>
);
......
import {
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import type { AddressFromToFilter } from 'types/api/address';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import FilterButton from 'ui/shared/filters/FilterButton';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
const OPTIONS = [
{ value: 'all', label: 'All' },
{ value: 'from', label: 'Outgoing transactions' },
{ value: 'to', label: 'Incoming transactions' },
];
interface Props {
isActive: boolean;
hasActiveFilter: boolean;
defaultFilter: AddressFromToFilter;
onFilterChange: (nextValue: string | Array<string>) => void;
isLoading?: boolean;
}
const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }: Props) => {
const { isOpen, onToggle } = useDisclosure();
const AddressTxsFilter = ({ onFilterChange, defaultFilter, hasActiveFilter, isLoading }: Props) => {
const isInitialLoading = useIsInitialLoading(isLoading);
return (
<Menu>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
isLoading={ isInitialLoading }
onClick={ onToggle }
appliedFiltersNum={ isActive ? 1 : 0 }
as="div"
/>
</MenuButton>
<MenuList zIndex={ 2 }>
<MenuOptionGroup defaultValue={ defaultFilter || 'all' } type="radio" onChange={ onFilterChange }>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="from">Outgoing transactions</MenuItemOption>
<MenuItemOption value="to">Incoming transactions</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
<PopoverFilterRadio
name="txs_filter"
options={ OPTIONS }
onChange={ onFilterChange }
hasActiveFilter={ hasActiveFilter }
isLoading={ isInitialLoading }
defaultValue={ defaultFilter || OPTIONS[0].value }
/>
);
};
......
......@@ -44,6 +44,7 @@ const SolidityscanReport = ({ hash }: Props) => {
score={ score }
isLoading={ isPlaceholderData }
onClick={ onToggle }
isActive={ isOpen }
/>
</PopoverTrigger>
<PopoverContent w={{ base: '100vw', lg: '328px' }}>
......
......@@ -76,6 +76,7 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => {
ml={ 1 }
p={ 0 }
onClick={ onToggle }
isActive={ isOpen }
isDisabled={ isDisabled }
>
<IconSvg
......
......@@ -61,6 +61,7 @@ const ContractCodeIde = ({ className, hash, isLoading }: Props) => {
variant="outline"
colorScheme="gray"
onClick={ onToggle }
isActive={ isOpen }
aria-label="Open source code in IDE"
fontWeight={ 500 }
px={ 2 }
......
......@@ -65,6 +65,7 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
variant="outline"
colorScheme="gray"
onClick={ onToggle }
isActive={ isOpen }
fontWeight={ 600 }
px={ 2 }
aria-label="View external libraries"
......
......@@ -111,6 +111,7 @@ const AddressEnsDomains = ({ addressHash, mainDomainName }: Props) => {
variant="outline"
colorScheme="gray"
onClick={ onToggle }
isActive={ isOpen }
aria-label="Address domains"
fontWeight={ 500 }
px={ 2 }
......
import { Box, Button, Skeleton, Text, useColorModeValue } from '@chakra-ui/react';
import { Box, Button, Skeleton, chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { FormattedData } from './types';
......@@ -39,20 +39,21 @@ const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: Rea
variant="outline"
colorScheme="gray"
onClick={ handleClick }
isActive={ isOpen }
aria-label="Token select"
>
<IconSvg name="tokens" boxSize={ 4 } mr={ 2 }/>
<Text fontWeight={ 600 }>{ prefix }{ num }</Text>
<Text
<chakra.span fontWeight={ 600 }>{ prefix }{ num }</chakra.span>
<chakra.span
whiteSpace="pre"
variant="secondary"
color="text_secondary"
fontWeight={ 400 }
maxW={{ base: 'calc(100vw - 230px)', lg: '500px' }}
overflow="hidden"
textOverflow="ellipsis"
>
{ space }({ prefix }${ usd.toFormat(2) })
</Text>
</chakra.span>
<IconSvg name="arrows/east-mini" transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 3 }/>
</Button>
{ isLoading && !isOpen && <Skeleton h="100%" w="100%" position="absolute" top={ 0 } left={ 0 } bgColor={ skeletonBgColor } borderRadius="base"/> }
......
......@@ -51,6 +51,7 @@ const AppSecurityReport = ({ id, securityReport, showContractList, isLoading, on
score={ securityScore }
isLoading={ isLoading }
onClick={ handleButtonClick }
isActive={ isOpen }
onlyIcon={ onlyIcon }
label="The security score is based on analysis of a DApp's smart contracts."
/>
......
......@@ -40,6 +40,7 @@ const ContractSecurityReport = ({ securityReport }: Props) => {
<SolidityscanReportButton
score={ parseFloat(securityScore) }
onClick={ handleClick }
isActive={ isOpen }
/>
</PopoverTrigger>
<PopoverContent w={{ base: '100vw', lg: '328px' }}>
......
......@@ -36,7 +36,7 @@ const MarketplaceAppInfo = ({ data }: Props) => {
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<TriggerButton onClick={ onToggle }/>
<TriggerButton onClick={ onToggle } isActive={ isOpen }/>
</PopoverTrigger>
<PopoverContent w="500px">
<PopoverBody px={ 6 } py={ 5 }>
......
......@@ -6,9 +6,10 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props {
onClick: () => void;
onlyIcon?: boolean;
isActive?: boolean;
}
const TriggerButton = ({ onClick, onlyIcon }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const TriggerButton = ({ onClick, onlyIcon, isActive }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
return (
<Button
ref={ ref }
......@@ -16,6 +17,7 @@ const TriggerButton = ({ onClick, onlyIcon }: Props, ref: React.ForwardedRef<HTM
variant="outline"
colorScheme="gray"
onClick={ onClick }
isActive={ isActive }
aria-label="Show project info"
fontWeight={ 500 }
px={ onlyIcon ? 1 : 2 }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment