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,41 +48,46 @@ 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: {
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,
color: 'link_hovered',
borderColor: 'link_hovered',
bg,
span: {
color: 'link_hovered',
},
_disabled: {
color,
borderColor,
color: 'link_hovered',
borderColor: 'link_hovered',
},
p: {
color: activeColor,
},
// 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 }
<PopoverFilterRadio
name="account_history_filter"
options={ OPTIONS }
onChange={ onFilterChange }
hasActiveFilter={ hasActiveFilter }
isLoading={ isInitialLoading }
onClick={ onToggle }
appliedFiltersNum={ isActive ? 1 : 0 }
as="div"
defaultValue={ defaultFilter || OPTIONS[0].value }
/>
</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>
);
};
......
......@@ -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 }
<PopoverFilterRadio
name="txs_filter"
options={ OPTIONS }
onChange={ onFilterChange }
hasActiveFilter={ hasActiveFilter }
isLoading={ isInitialLoading }
onClick={ onToggle }
appliedFiltersNum={ isActive ? 1 : 0 }
as="div"
defaultValue={ defaultFilter || OPTIONS[0].value }
/>
</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>
);
};
......
......@@ -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 }
......
......@@ -106,11 +106,11 @@ const Tokens = () => {
const hasMultipleTabs = bridgedTokensFeature.isEnabled;
const filter = tab === 'bridged' ? (
<PopoverFilter isActive={ bridgeChains && bridgeChains.length > 0 } contentProps={{ maxW: '350px' }} appliedFiltersNum={ bridgeChains?.length }>
<PopoverFilter contentProps={{ maxW: '350px' }} appliedFiltersNum={ bridgeChains?.length }>
<TokensBridgedChainsFilter onChange={ handleBridgeChainsChange } defaultValue={ bridgeChains }/>
</PopoverFilter>
) : (
<PopoverFilter isActive={ tokenTypes && tokenTypes.length > 0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }>
<PopoverFilter contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }>
<TokenTypeFilter<TokenType> onChange={ handleTokenTypesChange } defaultValue={ tokenTypes } nftOnly={ false }/>
</PopoverFilter>
);
......
......@@ -83,7 +83,7 @@ const Validators = () => {
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const filterMenu = <ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } isActive={ Boolean(statusFilter) }/>;
const filterMenu = <ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>;
// const filterInput = (
// <FilterInput
......
......@@ -77,7 +77,13 @@ const VerifiedContracts = () => {
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const typeFilter = <VerifiedContractsFilter onChange={ handleTypeChange } defaultValue={ type } isActive={ Boolean(type) }/>;
const typeFilter = (
<VerifiedContractsFilter
onChange={ handleTypeChange }
defaultValue={ type }
hasActiveFilter={ Boolean(type) }
/>
);
const filterInput = (
<FilterInput
......
......@@ -62,6 +62,7 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => {
variant="outline"
colorScheme="gray"
onClick={ onToggle }
isActive={ isOpen }
aria-label="Verify in other explorers"
fontWeight={ 500 }
px={ 2 }
......
......@@ -119,6 +119,7 @@ const AdaptiveTabsList = (props: Props) => {
color: 'inherit',
},
}}
{ ...(index === props.activeTabIndex ? { 'data-selected': true } : {}) }
>
<Skeleton isLoaded={ !props.isLoading }>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
......
......@@ -22,20 +22,38 @@ const FilterButton = ({ isActive, isLoading, appliedFiltersNum, onClick, as }: P
return <Skeleton w={{ base: 9, lg: '78px' }} h={ 8 } borderRadius="base" flexShrink={ 0 }/>;
}
const num = (
<Circle
className="AppliedFiltersNum"
bg={ isActive ? 'link_hovered' : badgeBgColor }
size={ 5 }
color={ badgeColor }
>
{ appliedFiltersNum }
</Circle>
);
return (
<Button
ref={ ref }
rightIcon={ appliedFiltersNum ? <Circle bg={ badgeBgColor } size={ 5 } color={ badgeColor }>{ appliedFiltersNum }</Circle> : undefined }
rightIcon={ appliedFiltersNum ? num : undefined }
size="sm"
fontWeight="500"
variant="outline"
colorScheme="gray-dark"
colorScheme="gray"
onClick={ onClick }
isActive={ isActive }
data-selected={ Boolean(appliedFiltersNum) }
px={ 1.5 }
flexShrink={ 0 }
as={ as }
pointerEvents="all"
_hover={ isActive ? {
color: 'link_hovered',
'.AppliedFiltersNum': {
bg: 'link_hovered',
},
} : undefined }
>
{ FilterIcon }
<Box display={{ base: 'none', lg: 'block' }}>Filter</Box>
......
......@@ -12,20 +12,19 @@ import FilterButton from 'ui/shared/filters/FilterButton';
interface Props {
appliedFiltersNum?: number;
isActive?: boolean;
children: React.ReactNode;
contentProps?: PopoverContentProps;
isLoading?: boolean;
}
const PopoverFilter = ({ appliedFiltersNum, children, contentProps, isActive, isLoading }: Props) => {
const PopoverFilter = ({ appliedFiltersNum, children, contentProps, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<FilterButton
isActive={ isOpen || isActive || Number(appliedFiltersNum) > 0 }
isActive={ isOpen }
onClick={ onToggle }
appliedFiltersNum={ appliedFiltersNum }
isLoading={ isLoading }
......
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
useRadio,
Box,
useRadioGroup,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import FilterButton from 'ui/shared/filters/FilterButton';
import IconSvg from 'ui/shared/IconSvg';
// OPTION
export interface TOption {
value: string;
label: string;
}
type OptionProps = ReturnType<ReturnType<typeof useRadioGroup>['getRadioProps']>;
const Option = (props: OptionProps) => {
const { getInputProps, getRadioProps } = useRadio(props);
const input = getInputProps();
const checkbox = getRadioProps();
const bgColorHover = useColorModeValue('blue.50', 'whiteAlpha.100');
return (
<Box
as="label"
px={ 4 }
py={ 2 }
cursor="pointer"
display="flex"
columnGap={ 3 }
alignItems="center"
_hover={{
bgColor: bgColorHover,
}}
>
<input { ...input }/>
<Box { ...checkbox }>
{ props.children }
</Box>
{ props.isChecked && <IconSvg name="check" boxSize={ 4 }/> }
</Box>
);
};
// FILTER
interface Props {
name: string;
options: Array<TOption>;
hasActiveFilter: boolean;
defaultValue?: string;
isLoading?: boolean;
onChange: (nextValue: string) => void;
}
const PopoverFilterRadio = ({ name, hasActiveFilter, options, isLoading, onChange, defaultValue }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const { getRootProps, getRadioProps } = useRadioGroup({
name,
defaultValue,
onChange,
});
const root = getRootProps();
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<FilterButton
isActive={ isOpen }
onClick={ onToggle }
appliedFiltersNum={ hasActiveFilter ? 1 : 0 }
isLoading={ isLoading }
/>
</PopoverTrigger>
<PopoverContent w="fit-content" minW="150px">
<PopoverBody { ...root } py={ 2 } px={ 0 } display="flex" flexDir="column">
{ options.map((option) => {
const radio = getRadioProps({ value: option.value });
return (
<Option key={ option.value } { ...radio }>
{ option.label }
</Option>
);
}) }
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default React.memo(PopoverFilterRadio);
......@@ -74,7 +74,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash,
</Tooltip> */ }
<Skeleton isLoaded={ !isLoading } ml="auto" borderRadius="base">
<Tooltip label="Log index">
<Button variant="outline" colorScheme="gray" isActive size="sm" fontWeight={ 400 }>
<Button variant="outline" colorScheme="gray" data-selected="true" size="sm" fontWeight={ 400 }>
{ index }
</Button>
</Tooltip>
......
......@@ -62,7 +62,7 @@ const LogTopic = ({ hex, index, isLoading }: Props) => {
return (
<Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%">
<Skeleton isLoaded={ !isLoading } mr={ 3 } borderRadius="base">
<Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } w={ 6 }>
<Button variant="outline" colorScheme="gray" data-selected size="xs" fontWeight={ 400 } w={ 6 }>
{ index }
</Button>
</Skeleton>
......
......@@ -48,7 +48,7 @@ const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasPage
<Button
variant="outline"
size="sm"
isActive
data-selected={ true }
borderWidth="1px"
fontWeight={ 400 }
h={ 8 }
......
......@@ -12,10 +12,11 @@ interface Props {
onlyIcon?: boolean;
onClick?: () => void;
label?: string;
isActive: boolean;
}
const SolidityscanReportButton = (
{ score, isLoading, onlyIcon, onClick, label = 'Security score' }: Props,
{ score, isLoading, onlyIcon, onClick, label = 'Security score', isActive }: Props,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
const { scoreColor } = useScoreLevelAndColor(score);
......@@ -31,6 +32,7 @@ const SolidityscanReportButton = (
variant="outline"
colorScheme="gray"
onClick={ onClick }
isActive={ isActive }
aria-label="SolidityScan score"
fontWeight={ 500 }
px="6px"
......
......@@ -21,7 +21,7 @@ const SortButton = ({ onClick, isActive, className, isLoading }: Props) => {
aria-label="sort"
size="sm"
variant="outline"
colorScheme="gray-dark"
colorScheme="gray"
minWidth="36px"
onClick={ onClick }
isActive={ isActive }
......
......@@ -12,9 +12,16 @@ interface Props {
const NetworkMenuPopup = ({ items, tabs }: Props) => {
const selectedNetwork = items?.find(({ isActive }) => isActive);
const selectedTab = tabs.findIndex((tab) => selectedNetwork?.group === tab);
const defaultTab = tabs.findIndex((tab) => selectedNetwork?.group === tab);
const [ tabIndex, setTabIndex ] = React.useState(defaultTab > -1 ? defaultTab : 0);
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const handleTabChange = React.useCallback((index: number) => {
setTabIndex(index);
}, []);
const content = !items || items.length === 0 ? (
<>
<Flex alignItems="center">
......@@ -45,11 +52,16 @@ const NetworkMenuPopup = ({ items, tabs }: Props) => {
colorScheme="gray"
size="sm"
isLazy
defaultIndex={ selectedTab !== -1 ? selectedTab : undefined }
index={ tabIndex }
onChange={ handleTabChange }
>
{ tabs.length > 1 && (
<TabList columnGap={ 2 }>
{ tabs.map((tab) => <Tab key={ tab } textTransform="capitalize">{ tab }</Tab>) }
{ tabs.map((tab, index) => (
<Tab key={ tab } textTransform="capitalize" { ...(tabIndex === index ? { 'data-selected': 'true' } : {}) }>
{ tab }
</Tab>
)) }
</TabList>
) }
<TabPanels mt={ 3 }>
......
......@@ -186,12 +186,12 @@ test('scroll suggest to category', async({ render, page, mockApiResponse }) => {
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
test('recent keywords suggest +@mobile', async({ render, page }) => {
test('recent keywords suggest +@mobile', async({ render, page }, { project }) => {
await render(<SearchBar/>);
// eslint-disable-next-line max-len
await page.evaluate(() => window.localStorage.setItem('recent_search_keywords', '["10x2d311959270e0bbdc1fc7bc6dbd8ad645c4dd8d6aa32f5f89d54629a924f112b","0x1d311959270e0bbdc1fc7bc6dbd8ad645c4dd8d6aa32f5f89d54629a924f112b","usd","bob"]'));
await page.getByPlaceholder(/search/i).click();
await page.getByText('0x1d311959270e0bbdc1fc7bc6db').isVisible();
await page.getByText(project.name === 'mobile' ? '0x1d311959270e0bbdc1fc7bc6dbd...112b' : '0x1d311959270e0bbdc1fc7bc6dbd').isVisible();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
......
......@@ -123,7 +123,11 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
<Box position="sticky" top="0" width="100%" background={ bgColor } py={ 5 } my={ -5 } ref={ tabsRef }>
<Tabs variant="outline" colorScheme="gray" size="sm" index={ tabIndex }>
<TabList columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
{ resultCategories.map((cat, index) => <Tab key={ cat.id } onClick={ scrollToCategory(index) }>{ cat.title }</Tab>) }
{ resultCategories.map((cat, index) => (
<Tab key={ cat.id } onClick={ scrollToCategory(index) } { ...(tabIndex === index ? { 'data-selected': 'true' } : {}) }>
{ cat.title }
</Tab>
)) }
</TabList>
</Tabs>
</Box>
......
......@@ -45,7 +45,7 @@ const DeFiDropdown = () => {
<PopoverTrigger>
<Button
onClick={ onToggle }
bgColor={ isOpen ? 'blue.400' : undefined }
isActive={ isOpen }
{ ...buttonStyles }
>
<chakra.span display={{ base: 'none', lg: 'inline' }} mr={ 1 }>
......
import { Box, Button, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, Text } from '@chakra-ui/react';
import { Box, Button, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, chakra } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import IconSvg from 'ui/shared/IconSvg';
......@@ -32,13 +32,13 @@ export function StatsDropdownMenu<T extends string>({ items, selectedId, onSelec
display="flex"
alignItems="center"
>
<Text
<chakra.span
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
>
{ selectedCategory?.title }
</Text>
</chakra.span>
<IconSvg transform="rotate(-90deg)" ml="auto" name="arrows/east-mini" w={ 5 } h={ 5 }/>
</Box>
</MenuButton>
......
......@@ -27,7 +27,7 @@ const TokenProjectInfo = ({ data }: Props) => {
if (isMobile) {
return (
<>
<TriggerButton onClick={ onToggle }/>
<TriggerButton onClick={ onToggle } isActive={ isOpen }/>
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent>
<ModalCloseButton/>
......@@ -41,7 +41,7 @@ const TokenProjectInfo = ({ 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 }>
......
......@@ -5,9 +5,10 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props {
onClick: () => void;
isActive: boolean;
}
const TriggerButton = ({ onClick }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const TriggerButton = ({ onClick, isActive }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
return (
<Button
ref={ ref }
......@@ -15,6 +16,7 @@ const TriggerButton = ({ onClick }: Props, ref: React.ForwardedRef<HTMLButtonEle
variant="outline"
colorScheme="gray"
onClick={ onClick }
isActive={ isActive }
aria-label="Show project info"
fontWeight={ 500 }
lineHeight={ 6 }
......
import {
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import type { ValidatorsFilters } from 'types/api/validators';
import FilterButton from 'ui/shared/filters/FilterButton';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
const OPTIONS = [
{ value: 'all', label: 'All' },
{ value: 'active', label: 'Active' },
{ value: 'probation', label: 'Probation' },
{ value: 'inactive', label: 'Inactive' },
];
interface Props {
isActive: boolean;
hasActiveFilter: boolean;
defaultValue: ValidatorsFilters['state_filter'] | undefined;
onChange: (nextValue: string | Array<string>) => void;
}
const ValidatorsFilter = ({ onChange, defaultValue, isActive }: Props) => {
const { isOpen, onToggle } = useDisclosure();
const ValidatorsFilter = ({ onChange, defaultValue, hasActiveFilter }: Props) => {
return (
<Menu>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
appliedFiltersNum={ isActive ? 1 : 0 }
onClick={ onToggle }
as="div"
<PopoverFilterRadio
name="validators_filter"
options={ OPTIONS }
onChange={ onChange }
hasActiveFilter={ hasActiveFilter }
defaultValue={ defaultValue || OPTIONS[0].value }
/>
</MenuButton>
<MenuList zIndex="popover">
<MenuOptionGroup defaultValue={ defaultValue || 'all' } title="Status" type="radio" onChange={ onChange }>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="active">Active</MenuItemOption>
<MenuItemOption value="probation">Probation</MenuItemOption>
<MenuItemOption value="inactive">Inactive</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
);
};
......
import {
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import type { VerifiedContractsFilters } from 'types/api/contracts';
import FilterButton from 'ui/shared/filters/FilterButton';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
const OPTIONS = [
{ value: 'all', label: 'All' },
{ value: 'solidity', label: 'Solidity' },
{ value: 'vyper', label: 'Vyper' },
{ value: 'yul', label: 'Yul' },
];
interface Props {
isActive: boolean;
hasActiveFilter: boolean;
defaultValue: VerifiedContractsFilters['filter'] | undefined;
onChange: (nextValue: string | Array<string>) => void;
}
const VerifiedContractsFilter = ({ onChange, defaultValue, isActive }: Props) => {
const { isOpen, onToggle } = useDisclosure();
const VerifiedContractsFilter = ({ onChange, defaultValue, hasActiveFilter }: Props) => {
return (
<Menu>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
appliedFiltersNum={ isActive ? 1 : 0 }
onClick={ onToggle }
as="div"
<PopoverFilterRadio
name="verified_contracts_filter"
options={ OPTIONS }
onChange={ onChange }
hasActiveFilter={ hasActiveFilter }
defaultValue={ defaultValue || OPTIONS[0].value }
/>
</MenuButton>
<MenuList zIndex="popover">
<MenuOptionGroup defaultValue={ defaultValue || 'all' } title="Filter" type="radio" onChange={ onChange }>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="solidity">Solidity</MenuItemOption>
<MenuItemOption value="vyper">Vyper</MenuItemOption>
<MenuItemOption value="yul">Yul</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
);
};
......
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