diff --git a/public/static/identicon_logos/nouns.svg b/public/static/identicon_logos/nouns.svg index 773511f200..8f14010f97 100644 --- a/public/static/identicon_logos/nouns.svg +++ b/public/static/identicon_logos/nouns.svg @@ -1,6 +1,6 @@ - + @@ -13,7 +13,7 @@ - + @@ -29,17 +29,17 @@ - + - + - - + + - + @@ -54,11 +54,11 @@ - + - + - + diff --git a/ui/address/contract/ContractDetails.tsx b/ui/address/contract/ContractDetails.tsx index 24ab461f99..29fa7fb691 100644 --- a/ui/address/contract/ContractDetails.tsx +++ b/ui/address/contract/ContractDetails.tsx @@ -24,6 +24,7 @@ import ContractDetailsInfo from './info/ContractDetailsInfo'; import useContractDetailsTabs from './useContractDetailsTabs'; const TAB_LIST_PROPS = { flexWrap: 'wrap', rowGap: 2 }; +const LEFT_SLOT_PROPS = { w: { base: '100%', lg: 'auto' } }; type Props = { addressHash: string; @@ -117,6 +118,7 @@ const ContractDetails = ({ addressHash, channel, mainContractQuery }: Props) => size="sm" leftSlot={ addressSelector } tabListProps={ TAB_LIST_PROPS } + leftSlotProps={ LEFT_SLOT_PROPS } /> ) : ( <> diff --git a/ui/address/contract/ContractSourceAddressSelector.tsx b/ui/address/contract/ContractSourceAddressSelector.tsx index 31fd271f42..0bb39c6f20 100644 --- a/ui/address/contract/ContractSourceAddressSelector.tsx +++ b/ui/address/contract/ContractSourceAddressSelector.tsx @@ -1,4 +1,4 @@ -import { chakra, Flex, Select, Skeleton } from '@chakra-ui/react'; +import { chakra, Flex, Skeleton } from '@chakra-ui/react'; import React from 'react'; import { route } from 'nextjs-routes'; @@ -6,6 +6,7 @@ import { route } from 'nextjs-routes'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import LinkNewTab from 'ui/shared/links/LinkNewTab'; +import Select from 'ui/shared/select/Select'; export interface Item { address: string; @@ -23,13 +24,17 @@ interface Props { const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, items, isLoading, label }: Props) => { - const handleItemSelect = React.useCallback((event: React.ChangeEvent) => { - const nextOption = items.find(({ address }) => address === event.target.value); + const handleItemSelect = React.useCallback((value: string) => { + const nextOption = items.find(({ address }) => address === value); if (nextOption) { onItemSelect(nextOption); } }, [ items, onItemSelect ]); + const options = React.useMemo(() => { + return items.map(({ address, name }) => ({ label: name || address, value: address })); + }, [ items ]); + if (isLoading) { return ; } @@ -53,19 +58,14 @@ const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, { label } + /> { const data = '0xE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A280E2A3A4E2A1B6E2A0BFE2A0BFE2A0B7E2A3B6E2A384E2A080E2A080E2A080E2A080E2A0800AE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3B0E2A1BFE2A081E2A080E2A080E2A280E2A380E2A180E2A099E2A3B7E2A180E2A080E2A080E2A0800AE2A080E2A080E2A080E2A180E2A080E2A080E2A080E2A080E2A080E2A2A0E2A3BFE2A081E2A080E2A080E2A080E2A098E2A0BFE2A083E2A080E2A2B8E2A3BFE2A3BFE2A3BFE2A3BF0AE2A080E2A3A0E2A1BFE2A09BE2A2B7E2A3A6E2A180E2A080E2A080E2A088E2A3BFE2A184E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3B8E2A3BFE2A3BFE2A3BFE2A09F0AE2A2B0E2A1BFE2A081E2A080E2A080E2A099E2A2BFE2A3A6E2A3A4E2A3A4E2A3BCE2A3BFE2A384E2A080E2A080E2A080E2A080E2A080E2A2B4E2A19FE2A09BE2A08BE2A081E2A0800AE2A3BFE2A087E2A080E2A080E2A080E2A080E2A080E2A089E2A089E2A089E2A089E2A089E2A081E2A080E2A080E2A080E2A080E2A080E2A088E2A3BFE2A180E2A080E2A080E2A0800AE2A3BFE2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A2B9E2A187E2A080E2A080E2A0800AE2A3BFE2A186E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A3BCE2A187E2A080E2A080E2A0800AE2A0B8E2A3B7E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A2A0E2A1BFE2A080E2A080E2A080E2A0800AE2A080E2A0B9E2A3B7E2A3A4E2A380E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A080E2A380E2A3B0E2A1BFE2A081E2A080E2A080E2A080E2A0800AE2A080E2A080E2A080E2A089E2A099E2A09BE2A0BFE2A0B6E2A3B6E2A3B6E2A3B6E2A3B6E2A3B6E2A0B6E2A0BFE2A09FE2A09BE2A089E2A080E2A080E2A080E2A080E2A080E2A080'; const component = await render(); await expect(component).toHaveScreenshot(); - await component.locator('select').selectOption('UTF-8'); + await component.getByRole('button', { name: 'Raw' }).click(); + await component.getByText('UTF-8').click(); await expect(component).toHaveScreenshot(); }); @@ -21,7 +22,8 @@ test('image', async({ render }) => { const data = '0x89504E470D0A1A0A0000000D494844520000003C0000003C0403000000C8D2C4410000000467414D410000B18F0BFC6105000000017352474200AECE1CE900000027504C54454C69712B6CB02A6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB02B6CB0F4205A540000000C74524E5300ED2F788CD91B99475C09B969CFA99D0000004F7A5458745261772070726F66696C65207479706520697074630000789CE3CA2C2849E6520003230B2E630B1323134B9314031320448034C3640323B35420CBD8D4C8C4CCC41CC407CB8048A04A2E0028950EE32A226D1F0000000970485973000084DF000084DF0195C81C33000000F24944415438CB636000018E983367CE482780D90CDA40F6991D0C4820152472A60ACCE6DA03629F4E40929E03961602B39964C09C0624691B24690E88F48461215D03160903B3D962C01C07842C2758C341A80643B0B40484C3646C6C5C78E6E016171723A8E215262EEE31670E161B1B7731304C05AB155EC08002C0D172E6F80206884DBB50651938CF4003FE0CBA4390E3C56064482F53525252C329CD562A2828283A0197340B22AAB0494332C311FCD2C747A547A58996C69998D8F12745B68DA0846C85331B2CEAE8E8681A81D91F8B348C4605D0527B02A4283FA88026CD05163EAAC0900ED21EC9800EC0C2110C002BBA9FE999B920330000000049454E44AE426082'; const component = await render(); await expect(component).toHaveScreenshot(); - await component.locator('select').selectOption('Base64'); + await component.getByRole('button', { name: 'Image' }).click(); + await component.getByText('Base64').click(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/blob/BlobData.tsx b/ui/blob/BlobData.tsx index eed81127dc..cfa9a31615 100644 --- a/ui/blob/BlobData.tsx +++ b/ui/blob/BlobData.tsx @@ -1,4 +1,4 @@ -import { Flex, GridItem, Select, Skeleton, Button } from '@chakra-ui/react'; +import { Flex, GridItem, Skeleton, Button } from '@chakra-ui/react'; import React from 'react'; import * as blobUtils from 'lib/blob'; @@ -10,12 +10,18 @@ import hexToBytes from 'lib/hexToBytes'; import hexToUtf8 from 'lib/hexToUtf8'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; +import Select from 'ui/shared/select/Select'; import BlobDataImage from './BlobDataImage'; -const FORMATS = [ 'Image', 'Raw', 'UTF-8', 'Base64' ] as const; +const FORMATS = [ + { label: 'Image', value: 'Image' as const }, + { label: 'Raw', value: 'Raw' as const }, + { label: 'UTF-8', value: 'UTF-8' as const }, + { label: 'Base64', value: 'Base64' as const }, +]; -type Format = typeof FORMATS[number]; +type Format = typeof FORMATS[number]['value']; interface Props { data: string; @@ -34,7 +40,7 @@ const BlobData = ({ data, isLoading, hash }: Props) => { }, [ data, isLoading ]); const isImage = guessedType?.mime?.startsWith('image/'); - const formats = isImage ? FORMATS : FORMATS.filter((format) => format !== 'Image'); + const formats = isImage ? FORMATS : FORMATS.filter((format) => format.value !== 'Image'); React.useEffect(() => { if (isImage) { @@ -42,10 +48,6 @@ const BlobData = ({ data, isLoading, hash }: Props) => { } }, [ isImage ]); - const handleSelectChange = React.useCallback((event: React.ChangeEvent) => { - setFormat(event.target.value as Format); - }, []); - const handleDownloadButtonClick = React.useCallback(() => { const fileBlob = (() => { switch (format) { @@ -104,16 +106,13 @@ const BlobData = ({ data, isLoading, hash }: Props) => { + options={ formats } + name="format" + defaultValue={ format } + onChange={ setFormat } + isLoading={ isLoading } + w="95px" + /> ); + const resolutionOptions = React.useMemo(() => { + const resolutions = lineQuery.data?.info?.resolutions || []; + return STATS_RESOLUTIONS + .filter((resolution) => resolutions.includes(resolution.id)) + .map((resolution) => ({ value: resolution.id, label: resolution.title })); + }, [ lineQuery.data?.info?.resolutions ]); + return ( <> { withTextAd /> - + { !isMobile && Period } @@ -187,11 +196,13 @@ const Chart = () => { { isMobile ? 'Res.' : 'Resolution' } - ) } diff --git a/ui/pages/__screenshots__/Chart.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png b/ui/pages/__screenshots__/Chart.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png index a8df3c7865..eb6884c0a8 100644 Binary files a/ui/pages/__screenshots__/Chart.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png and b/ui/pages/__screenshots__/Chart.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png differ diff --git a/ui/pages/__screenshots__/Chart.pw.tsx_default_base-view-dark-mode-mobile-1.png b/ui/pages/__screenshots__/Chart.pw.tsx_default_base-view-dark-mode-mobile-1.png index 0bd465f09f..4f0d0954b5 100644 Binary files a/ui/pages/__screenshots__/Chart.pw.tsx_default_base-view-dark-mode-mobile-1.png and b/ui/pages/__screenshots__/Chart.pw.tsx_default_base-view-dark-mode-mobile-1.png differ diff --git a/ui/pages/__screenshots__/Chart.pw.tsx_mobile_base-view-dark-mode-mobile-1.png b/ui/pages/__screenshots__/Chart.pw.tsx_mobile_base-view-dark-mode-mobile-1.png index 53431659d3..031e777d49 100644 Binary files a/ui/pages/__screenshots__/Chart.pw.tsx_mobile_base-view-dark-mode-mobile-1.png and b/ui/pages/__screenshots__/Chart.pw.tsx_mobile_base-view-dark-mode-mobile-1.png differ diff --git a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-1.png b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-1.png index 3fedeaa647..f1ac7bc9dc 100644 Binary files a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-1.png and b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-1.png differ diff --git a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-3.png b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-3.png index 92e03a6d7f..5159364a17 100644 Binary files a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-3.png and b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-3.png differ diff --git a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-failed-1.png b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-failed-1.png index 6b3f05a658..9954fd029b 100644 Binary files a/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-failed-1.png and b/ui/pages/__screenshots__/TokenInstance.pw.tsx_default_metadata-update-failed-1.png differ diff --git a/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png index 2220c6f755..2aa4b1d620 100644 Binary files a/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png and b/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png differ diff --git a/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png index 76f9175d64..ad580e51bd 100644 Binary files a/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/shared/RawInputData.tsx b/ui/shared/RawInputData.tsx index 26713e24de..3a21afb16c 100644 --- a/ui/shared/RawInputData.tsx +++ b/ui/shared/RawInputData.tsx @@ -1,11 +1,15 @@ -import { Select, Skeleton } from '@chakra-ui/react'; import React from 'react'; import hexToUtf8 from 'lib/hexToUtf8'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; +import Select from 'ui/shared/select/Select'; -export type DataType = 'Hex' | 'UTF-8'; -const OPTIONS: Array = [ 'Hex', 'UTF-8' ]; +const OPTIONS = [ + { label: 'Hex', value: 'Hex' as const }, + { label: 'UTF-8', value: 'UTF-8' as const }, +]; + +export type DataType = (typeof OPTIONS)[number]['value']; interface Props { hex: string; @@ -18,17 +22,17 @@ interface Props { const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex', isLoading, minHeight }: Props) => { const [ selectedDataType, setSelectedDataType ] = React.useState(defaultDataType); - const handleSelectChange = React.useCallback((event: React.ChangeEvent) => { - setSelectedDataType(event.target.value as DataType); - }, []); - const rightSlot = ( <> - - - + ); }; diff --git a/ui/shared/chart/ChartResolutionSelect.tsx b/ui/shared/chart/ChartResolutionSelect.tsx deleted file mode 100644 index aec9e166cd..0000000000 --- a/ui/shared/chart/ChartResolutionSelect.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Skeleton } from '@chakra-ui/react'; -import React from 'react'; - -import type { Resolution } from '@blockscout/stats-types'; - -import { STATS_RESOLUTIONS } from 'ui/stats/constants'; -import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu'; - -type Props = { - resolution: Resolution; - resolutions: Array; - onResolutionChange: (resolution: Resolution) => void; - isLoading?: boolean; -}; - -const ChartResolutionSelect = ({ resolution, resolutions, onResolutionChange, isLoading }: Props) => { - return ( - - resolutions.includes(r.id)) } - selectedId={ resolution } - onSelect={ onResolutionChange } - /> - - ); -}; - -export default React.memo(ChartResolutionSelect); diff --git a/ui/shared/filters/PopoverFilterRadio.tsx b/ui/shared/filters/PopoverFilterRadio.tsx index e29734f9b3..c1e818a17c 100644 --- a/ui/shared/filters/PopoverFilterRadio.tsx +++ b/ui/shared/filters/PopoverFilterRadio.tsx @@ -1,61 +1,12 @@ -import { - PopoverTrigger, - PopoverContent, - PopoverBody, - useDisclosure, - useRadio, - Box, - useRadioGroup, - useColorModeValue, -} from '@chakra-ui/react'; import React from 'react'; -import Popover from 'ui/shared/chakra/Popover'; -import FilterButton from 'ui/shared/filters/FilterButton'; -import IconSvg from 'ui/shared/IconSvg'; - -// OPTION -export interface TOption { - value: string; - label: string; -} - -type OptionProps = ReturnType['getRadioProps']>; - -const Option = (props: OptionProps) => { - const { getInputProps, getRadioProps } = useRadio(props); - - const input = getInputProps(); - const checkbox = getRadioProps(); - const bgColorHover = useColorModeValue('blue.50', 'whiteAlpha.100'); - - return ( - - - - { props.children } - - { props.isChecked && } - - ); -}; - -// FILTER +import type { SelectOption } from 'ui/shared/select/types'; +import FilterButton from 'ui/shared/filters/FilterButton'; +import Select from 'ui/shared/select/Select'; interface Props { name: string; - options: Array; + options: Array; hasActiveFilter: boolean; defaultValue?: string; isLoading?: boolean; @@ -63,39 +14,22 @@ interface Props { } 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 ( - - + ); }; diff --git a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-default-data-type-1.png b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-default-data-type-1.png index 3a4d1b5baa..569ee36589 100644 Binary files a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-default-data-type-1.png and b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-default-data-type-1.png differ diff --git a/ui/shared/select/Select.tsx b/ui/shared/select/Select.tsx new file mode 100644 index 0000000000..8ac82a6512 --- /dev/null +++ b/ui/shared/select/Select.tsx @@ -0,0 +1,65 @@ +import { PopoverTrigger, chakra, useDisclosure, useRadioGroup } from '@chakra-ui/react'; +import React from 'react'; + +import type { SelectOption } from './types'; + +import Popover from 'ui/shared/chakra/Popover'; + +import SelectButton from './SelectButton'; +import SelectContent from './SelectContent'; + +interface InjectedProps { + isOpen: boolean; + onToggle: () => void; + value: Value; +} + +export interface Props { + className?: string; + isLoading?: boolean; + options: Array>; + name: string; + defaultValue?: Value; + onChange: (value: Value) => void; + children?: (props: InjectedProps) => React.ReactNode; +} + +const Select = ({ className, isLoading, options, name, defaultValue, onChange, children }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const handleChange = React.useCallback((value: Value) => { + onChange(value); + onClose(); + }, [ onChange, onClose ]); + + const { value, getRootProps, getRadioProps, setValue } = useRadioGroup({ + name, + defaultValue, + onChange: handleChange, + }); + + React.useEffect(() => { + if (defaultValue) { + setValue(defaultValue); + } + }, [ defaultValue, setValue ]); + + return ( + + + { children?.({ isOpen, onToggle, value: value as Value }) || ( + option.value === value)?.label || String(value) } + /> + ) } + + + + ); +}; + +export default React.memo(chakra(Select)); diff --git a/ui/shared/select/SelectButton.tsx b/ui/shared/select/SelectButton.tsx new file mode 100644 index 0000000000..0bcbf202c5 --- /dev/null +++ b/ui/shared/select/SelectButton.tsx @@ -0,0 +1,46 @@ +import { Box, Button, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + className?: string; + onClick: () => void; + isOpen: boolean; + isLoading?: boolean; + label: string; +} + +const SelectButton = ({ className, onClick, isOpen, isLoading, label }: Props, ref: React.Ref) => { + + if (isLoading) { + return ; + } + + return ( + + ); +}; + +export default React.forwardRef(SelectButton); diff --git a/ui/shared/select/SelectContent.tsx b/ui/shared/select/SelectContent.tsx new file mode 100644 index 0000000000..2241a8454d --- /dev/null +++ b/ui/shared/select/SelectContent.tsx @@ -0,0 +1,36 @@ +import type { useRadioGroup } from '@chakra-ui/react'; +import { PopoverBody, PopoverContent } from '@chakra-ui/react'; +import React from 'react'; + +import type { SelectOption as TSelectOption } from './types'; + +import SelectOption from './SelectOption'; + +interface Props { + options: Array; + getRootProps: ReturnType['getRootProps']; + getRadioProps: ReturnType['getRadioProps']; + value: string | number; +} + +const SelectContent = ({ options, getRootProps, getRadioProps, value }: Props) => { + + const root = getRootProps(); + + return ( + + + { options.map((option) => { + const radio = getRadioProps({ value: option.value }); + return ( + + { option.label } + + ); + }) } + + + ); +}; + +export default React.memo(SelectContent); diff --git a/ui/shared/sort/Option.tsx b/ui/shared/select/SelectOption.tsx similarity index 53% rename from ui/shared/sort/Option.tsx rename to ui/shared/select/SelectOption.tsx index c24eaada0b..c8fe1ba962 100644 --- a/ui/shared/sort/Option.tsx +++ b/ui/shared/select/SelectOption.tsx @@ -1,21 +1,14 @@ -import { - useRadio, - Box, - useColorModeValue, -} from '@chakra-ui/react'; -import type { useRadioGroup } from '@chakra-ui/react'; +import type { UseRadioProps } from '@chakra-ui/react'; +import { Box, useRadio, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; -import IconSvg from 'ui/shared/IconSvg'; +import IconSvg from '../IconSvg'; -export interface TOption { - id: Sort | undefined; - title: string; +interface Props extends UseRadioProps { + children: React.ReactNode; } -type OptionProps = ReturnType['getRadioProps']>; - -const Option = (props: OptionProps) => { +const SelectOption = (props: Props) => { const { getInputProps, getRadioProps } = useRadio(props); const input = getInputProps(); @@ -35,13 +28,13 @@ const Option = (props: OptionProps) => { bgColor: bgColorHover, }} > + { props.isChecked ? : } { props.children } - { props.isChecked && } ); }; -export default Option; +export default React.memo(SelectOption); diff --git a/ui/shared/select/types.ts b/ui/shared/select/types.ts new file mode 100644 index 0000000000..7ec98a9086 --- /dev/null +++ b/ui/shared/select/types.ts @@ -0,0 +1,4 @@ +export interface SelectOption { + value: Value | undefined; + label: string; +} diff --git a/ui/shared/sort/Sort.tsx b/ui/shared/sort/Sort.tsx index 76f22c9f86..fb0aba87a6 100644 --- a/ui/shared/sort/Sort.tsx +++ b/ui/shared/sort/Sort.tsx @@ -1,70 +1,38 @@ -import { - PopoverTrigger, - PopoverContent, - PopoverBody, - useDisclosure, - useRadioGroup, - chakra, -} from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; +import type { SelectOption } from 'ui/shared/select/types'; + import useIsMobile from 'lib/hooks/useIsMobile'; -import Popover from 'ui/shared/chakra/Popover'; +import Select, { type Props as SelectProps } from 'ui/shared/select/Select'; import SortButtonDesktop from './ButtonDesktop'; import SortButtonMobile from './ButtonMobile'; -import Option from './Option'; -import type { TOption } from './Option'; -interface Props { - name: string; - options: Array>; - defaultValue?: Sort; - isLoading?: boolean; - onChange: (value: Sort | undefined) => void; -} +type Props = Omit, 'children'>; const Sort = ({ name, options, isLoading, onChange, defaultValue }: Props) => { const isMobile = useIsMobile(false); - const { isOpen, onToggle, onClose } = useDisclosure(); - - const handleChange = (value: Sort) => { - onChange(value); - onClose(); - }; - - const { value, getRootProps, getRadioProps } = useRadioGroup({ - name, - defaultValue, - onChange: handleChange, - }); - - const root = getRootProps(); return ( - - - { isMobile ? ( - - ) : ( - - { options.find((option: TOption) => option.id === value || (!option.id && !value))?.title } - - ) } - - - - { options.map((option, index) => { - const radio = getRadioProps({ value: option.id }); - return ( - - ); - }) } - - - + ); }; diff --git a/ui/shared/sort/getSortValueFromQuery.ts b/ui/shared/sort/getSortValueFromQuery.ts index efb07981ce..4bb9fc4a78 100644 --- a/ui/shared/sort/getSortValueFromQuery.ts +++ b/ui/shared/sort/getSortValueFromQuery.ts @@ -1,14 +1,14 @@ -import type { Query } from 'nextjs-routes'; +import type { SelectOption } from 'ui/shared/select/types'; -import type { TOption } from 'ui/shared/sort/Option'; +import type { Query } from 'nextjs-routes'; -export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { +export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { if (!query.sort || !query.order) { return undefined; } const str = query.sort + '-' + query.order; - if (sortOptions.map(option => option.id).includes(str as SortValue)) { + if (sortOptions.map(option => option.value).includes(str as SortValue)) { return str as SortValue; } } diff --git a/ui/stats/StatsDropdownMenu.tsx b/ui/stats/StatsDropdownMenu.tsx deleted file mode 100644 index 8ef82ea9f1..0000000000 --- a/ui/stats/StatsDropdownMenu.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Box, Button, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, chakra } from '@chakra-ui/react'; -import React, { useCallback } from 'react'; - -import Menu from 'ui/shared/chakra/Menu'; -import IconSvg from 'ui/shared/IconSvg'; - -type Props = { - items: ReadonlyArray<{ id: T; title: string }>; - selectedId: T; - onSelect: (id: T) => void; -}; - -export function StatsDropdownMenu({ items, selectedId, onSelect }: Props) { - const selectedCategory = items.find(category => category.id === selectedId); - - const handleSelection = useCallback((id: string | Array) => { - const selectedId = Array.isArray(id) ? id[0] : id; - onSelect(selectedId as T); - }, [ onSelect ]); - - return ( - - - - - { selectedCategory?.title } - - - - - - - - { items.map((item) => ( - - { item.title } - - )) } - - - - ); -} - -export default StatsDropdownMenu; diff --git a/ui/stats/StatsFilters.tsx b/ui/stats/StatsFilters.tsx index f5b9dc08d9..5721877b13 100644 --- a/ui/stats/StatsFilters.tsx +++ b/ui/stats/StatsFilters.tsx @@ -1,4 +1,4 @@ -import { Grid, GridItem, Skeleton } from '@chakra-ui/react'; +import { Grid, GridItem } from '@chakra-ui/react'; import React from 'react'; import type * as stats from '@blockscout/stats-types'; @@ -6,8 +6,7 @@ import type { StatsIntervalIds } from 'types/client/stats'; import ChartIntervalSelect from 'ui/shared/chart/ChartIntervalSelect'; import FilterInput from 'ui/shared/filters/FilterInput'; - -import StatsDropdownMenu from './StatsDropdownMenu'; +import Select from 'ui/shared/select/Select'; type Props = { sections?: Array; @@ -30,10 +29,13 @@ const StatsFilters = ({ isLoading, initialFilterValue, }: Props) => { - const sectionsList = [ { - id: 'all', - title: 'All stats', - }, ... (sections || []) ]; + + const options = React.useMemo(() => { + return [ + { value: 'all', label: 'All stats' }, + ...(sections || []).map((section) => ({ value: section.id, label: section.title })), + ]; + }, [ sections ]); return ( - { isLoading ? : ( - - ) } + - - - +