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 @@
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 = (
<>
-
-
-
+
{ rightSlotProp }
>
);
diff --git a/ui/shared/chart/ChartIntervalSelect.tsx b/ui/shared/chart/ChartIntervalSelect.tsx
index b9387bfc8a..dcb9b0a394 100644
--- a/ui/shared/chart/ChartIntervalSelect.tsx
+++ b/ui/shared/chart/ChartIntervalSelect.tsx
@@ -3,15 +3,16 @@ import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
+import type { SelectOption } from 'ui/shared/select/types';
+import Select from 'ui/shared/select/Select';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
import { STATS_INTERVALS } from 'ui/stats/constants';
-import StatsDropdownMenu from 'ui/stats/StatsDropdownMenu';
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
- id: id,
- title: STATS_INTERVALS[id as StatsIntervalIds].title,
-})) as Array;
+ value: id,
+ label: STATS_INTERVALS[id as StatsIntervalIds].title,
+})) as Array;
const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
@@ -31,13 +32,16 @@ const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading, selectTagS
items={ intervalListShort } onChange={ onIntervalChange } value={ interval } tagSize={ selectTagSize }/>
-
-
-
+
>
);
};
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 (
-
-
+
-
-
- { options.map((option) => {
- const radio = getRadioProps({ value: option.value });
- 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 (
-
- );
-}
-
-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 ? : (
-
- ) }
+
{
test('raw view', async({ render }) => {
const component = await render();
- await component.locator('select').selectOption('JSON');
+ await component.getByRole('button', { name: 'Table' }).click();
+ await component.getByText('JSON').click();
await expect(component).toHaveScreenshot();
});
diff --git a/ui/tokenInstance/TokenInstanceMetadata.tsx b/ui/tokenInstance/TokenInstanceMetadata.tsx
index 19c1856222..d9841ef134 100644
--- a/ui/tokenInstance/TokenInstanceMetadata.tsx
+++ b/ui/tokenInstance/TokenInstanceMetadata.tsx
@@ -1,4 +1,4 @@
-import { Alert, Box, Flex, Select, chakra } from '@chakra-ui/react';
+import { Alert, Box, Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
@@ -6,11 +6,17 @@ import type { TokenInstance } from 'types/api/token';
import ContentLoader from 'ui/shared/ContentLoader';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
+import Select from 'ui/shared/select/Select';
import { useMetadataUpdateContext } from './contexts/metadataUpdate';
import MetadataAccordion from './metadata/MetadataAccordion';
-type Format = 'JSON' | 'Table';
+const OPTIONS = [
+ { label: 'Table', value: 'Table' as const },
+ { label: 'JSON', value: 'JSON' as const },
+];
+
+type Format = (typeof OPTIONS)[number]['value'];
interface Props {
data: TokenInstance['metadata'] | undefined;
@@ -22,10 +28,6 @@ const TokenInstanceMetadata = ({ data, isPlaceholderData }: Props) => {
const { status: refetchStatus } = useMetadataUpdateContext() || {};
- const handleSelectChange = React.useCallback((event: React.ChangeEvent) => {
- setFormat(event.target.value as Format);
- }, []);
-
if (isPlaceholderData || refetchStatus === 'WAITING_FOR_RESPONSE') {
return ;
}
@@ -48,10 +50,14 @@ const TokenInstanceMetadata = ({ data, isPlaceholderData }: Props) => {
) }
Metadata
-
+
{ format === 'JSON' && }
{ content }
diff --git a/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_base-view-mobile-1.png b/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_base-view-mobile-1.png
index f4487e0236..4d1588c598 100644
Binary files a/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_base-view-mobile-1.png and b/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_raw-view-1.png b/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_raw-view-1.png
index 4f318cc8d8..5840b2454e 100644
Binary files a/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_raw-view-1.png and b/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_default_raw-view-1.png differ
diff --git a/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_mobile_base-view-mobile-1.png b/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_mobile_base-view-mobile-1.png
index 1b1576afe3..51ed0e6ed4 100644
Binary files a/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_mobile_base-view-mobile-1.png and b/ui/tokenInstance/__screenshots__/TokenInstanceMetadata.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/tokens/utils.ts b/ui/tokens/utils.ts
index 1069626b63..a420453023 100644
--- a/ui/tokens/utils.ts
+++ b/ui/tokens/utils.ts
@@ -1,19 +1,19 @@
import type { TokenType } from 'types/api/token';
import type { TokensSortingValue } from 'types/api/tokens';
+import type { SelectOption } from 'ui/shared/select/types';
import config from 'configs/app';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import { TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
-import type { TOption } from 'ui/shared/sort/Option';
-export const SORT_OPTIONS: Array> = [
- { title: 'Default', id: undefined },
- { title: 'Price ascending', id: 'fiat_value-asc' },
- { title: 'Price descending', id: 'fiat_value-desc' },
- { title: 'Holders ascending', id: 'holder_count-asc' },
- { title: 'Holders descending', id: 'holder_count-desc' },
- { title: 'On-chain market cap ascending', id: 'circulating_market_cap-asc' },
- { title: 'On-chain market cap descending', id: 'circulating_market_cap-desc' },
+export const SORT_OPTIONS: Array> = [
+ { label: 'Default', value: undefined },
+ { label: 'Price ascending', value: 'fiat_value-asc' },
+ { label: 'Price descending', value: 'fiat_value-desc' },
+ { label: 'Holders ascending', value: 'holder_count-asc' },
+ { label: 'Holders descending', value: 'holder_count-desc' },
+ { label: 'On-chain market cap ascending', value: 'circulating_market_cap-asc' },
+ { label: 'On-chain market cap descending', value: 'circulating_market_cap-desc' },
];
export const getTokenFilterValue = (getFilterValuesFromQuery).bind(null, TOKEN_TYPE_IDS);
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png
index 9c3dfaa912..038e6e0ac9 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png
index 285687964a..d47d50239b 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png
index e8a1904be3..9999b5a6ec 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png
index 678c5ea350..734064b4b2 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png
index 4744c0f0b7..29113001c5 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png differ
diff --git a/ui/txs/useTxsSort.tsx b/ui/txs/useTxsSort.tsx
index 16ee6a8271..b3a77e5514 100644
--- a/ui/txs/useTxsSort.tsx
+++ b/ui/txs/useTxsSort.tsx
@@ -2,19 +2,19 @@ import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { TransactionsSortingValue, TxsResponse } from 'types/api/transaction';
+import type { SelectOption } from 'ui/shared/select/types';
import type { ResourceError } from 'lib/api/resources';
import * as cookies from 'lib/cookies';
-import type { TOption } from 'ui/shared/sort/Option';
import sortTxs from './sortTxs';
-export const SORT_OPTIONS: Array> = [
- { title: 'Default', id: undefined },
- { title: 'Value ascending', id: 'value-asc' },
- { title: 'Value descending', id: 'value-desc' },
- { title: 'Fee ascending', id: 'fee-asc' },
- { title: 'Fee descending', id: 'fee-desc' },
+export const SORT_OPTIONS: Array> = [
+ { label: 'Default', value: undefined },
+ { label: 'Value ascending', value: 'value-asc' },
+ { label: 'Value descending', value: 'value-desc' },
+ { label: 'Fee ascending', value: 'fee-asc' },
+ { label: 'Fee descending', value: 'fee-desc' },
];
type SortingValue = TransactionsSortingValue | undefined;
diff --git a/ui/validatorsBlackfort/utils.ts b/ui/validatorsBlackfort/utils.ts
index a146199674..5a4f4550e2 100644
--- a/ui/validatorsBlackfort/utils.ts
+++ b/ui/validatorsBlackfort/utils.ts
@@ -2,13 +2,12 @@ import type {
ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField,
} from 'types/api/validators';
+import type { SelectOption } from 'ui/shared/select/types';
-import type { TOption } from 'ui/shared/sort/Option';
-
-export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array> = [
- { title: 'Default', id: undefined },
- { title: 'Address descending', id: 'address_hash-desc' },
- { title: 'Address ascending', id: 'address_hash-asc' },
+export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array> = [
+ { label: 'Default', value: undefined },
+ { label: 'Address descending', value: 'address_hash-desc' },
+ { label: 'Address ascending', value: 'address_hash-asc' },
];
export const VALIDATORS_BLACKFORT_SORT_SEQUENCE: Record> = {
diff --git a/ui/validatorsStability/utils.ts b/ui/validatorsStability/utils.ts
index d3e37f8bf0..ee62c7ac83 100644
--- a/ui/validatorsStability/utils.ts
+++ b/ui/validatorsStability/utils.ts
@@ -2,15 +2,14 @@ import type {
ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField,
} from 'types/api/validators';
+import type { SelectOption } from 'ui/shared/select/types';
-import type { TOption } from 'ui/shared/sort/Option';
-
-export const VALIDATORS_STABILITY_SORT_OPTIONS: Array> = [
- { title: 'Default', id: undefined },
- { title: 'Status descending', id: 'state-desc' },
- { title: 'Status ascending', id: 'state-asc' },
- { title: 'Blocks validated descending', id: 'blocks_validated-desc' },
- { title: 'Blocks validated ascending', id: 'blocks_validated-asc' },
+export const VALIDATORS_STABILITY_SORT_OPTIONS: Array> = [
+ { label: 'Default', value: undefined },
+ { label: 'Status descending', value: 'state-desc' },
+ { label: 'Status ascending', value: 'state-asc' },
+ { label: 'Blocks validated descending', value: 'blocks_validated-desc' },
+ { label: 'Blocks validated ascending', value: 'blocks_validated-asc' },
];
export const VALIDATORS_STABILITY_SORT_SEQUENCE: Record> = {
diff --git a/ui/verifiedContracts/utils.ts b/ui/verifiedContracts/utils.ts
index 3f6a13bf65..c8bb5a5e35 100644
--- a/ui/verifiedContracts/utils.ts
+++ b/ui/verifiedContracts/utils.ts
@@ -1,13 +1,12 @@
import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts';
+import type { SelectOption } from 'ui/shared/select/types';
-import type { TOption } from 'ui/shared/sort/Option';
-
-export const SORT_OPTIONS: Array> = [
- { title: 'Default', id: undefined },
- { title: 'Balance descending', id: 'balance-desc' },
- { title: 'Balance ascending', id: 'balance-asc' },
- { title: 'Txs count descending', id: 'txs_count-desc' },
- { title: 'Txs count ascending', id: 'txs_count-asc' },
+export const SORT_OPTIONS: Array> = [
+ { label: 'Default', value: undefined },
+ { label: 'Balance descending', value: 'balance-desc' },
+ { label: 'Balance ascending', value: 'balance-asc' },
+ { label: 'Txs count descending', value: 'txs_count-desc' },
+ { label: 'Txs count ascending', value: 'txs_count-asc' },
];
export const SORT_SEQUENCE: Record> = {