Commit 041f2130 authored by tom's avatar tom

AddressTxsFilter refactoring

parent ac0ac2b1
'use client'; 'use client';
import type { CollectionItem } from '@chakra-ui/react'; import type { CollectionItem } from '@chakra-ui/react';
import { Select as ChakraSelect, Portal } from '@chakra-ui/react'; import { Select as ChakraSelect, Portal, useSelect, useSelectContext } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -9,6 +9,11 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -9,6 +9,11 @@ import IconSvg from 'ui/shared/IconSvg';
import { CloseButton } from './close-button'; import { CloseButton } from './close-button';
import { Skeleton } from './skeleton'; import { Skeleton } from './skeleton';
export interface SelectOption<Value extends string = string> {
value: Value;
label: string;
}
export interface SelectControlProps extends ChakraSelect.ControlProps { export interface SelectControlProps extends ChakraSelect.ControlProps {
clearable?: boolean; clearable?: boolean;
noIndicator?: boolean; noIndicator?: boolean;
...@@ -20,11 +25,16 @@ export const SelectControl = React.forwardRef< ...@@ -20,11 +25,16 @@ export const SelectControl = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
SelectControlProps SelectControlProps
>(function SelectControl(props, ref) { >(function SelectControl(props, ref) {
const { children, clearable, noIndicator, triggerProps, loading, ...rest } = props; // NOTE: here defaultValue means the "default" option of the select, not its initial value
const { children, clearable, noIndicator, triggerProps, loading, defaultValue, ...rest } = props;
const context = useSelectContext();
const isDefaultValue = Array.isArray(defaultValue) ? context.value.every((item) => defaultValue.includes(item)) : context.value === defaultValue;
return ( return (
<Skeleton loading={ loading } asChild> <Skeleton loading={ loading } asChild>
<ChakraSelect.Control { ...rest } className="group"> <ChakraSelect.Control { ...rest } className="group">
<ChakraSelect.Trigger ref={ ref } { ...triggerProps }>{ children }</ChakraSelect.Trigger> <ChakraSelect.Trigger ref={ ref } { ...triggerProps } data-default-value={ isDefaultValue }>{ children }</ChakraSelect.Trigger>
{ (!noIndicator || clearable) && ( { (!noIndicator || clearable) && (
<ChakraSelect.IndicatorGroup> <ChakraSelect.IndicatorGroup>
{ clearable && <SelectClearTrigger/> } { clearable && <SelectClearTrigger/> }
......
...@@ -243,12 +243,12 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -243,12 +243,12 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
}, },
filter: { filter: {
fg: { fg: {
DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, DEFAULT: { value: '{colors.button.dropdown.fg}' },
selected: { value: { _light: '{colors.blue.600}', _dark: '{colors.gray.50}' } }, selected: { value: '{colors.button.dropdown.fg.selected}' },
}, },
border: { border: {
DEFAULT: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } }, DEFAULT: { value: '{colors.button.dropdown.border}' },
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.600}' } }, selected: { value: '{colors.button.dropdown.border.selected}' },
}, },
}, },
}, },
......
...@@ -8,7 +8,7 @@ export const recipe = defineSlotRecipe({ ...@@ -8,7 +8,7 @@ export const recipe = defineSlotRecipe({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '1.5', gap: '1.5',
width: 'full', width: 'fit-content',
}, },
trigger: { trigger: {
display: 'flex', display: 'flex',
...@@ -156,12 +156,14 @@ export const recipe = defineSlotRecipe({ ...@@ -156,12 +156,14 @@ export const recipe = defineSlotRecipe({
bgColor: 'select.trigger.filter.border.selected', bgColor: 'select.trigger.filter.border.selected',
borderColor: 'select.trigger.filter.border.selected', borderColor: 'select.trigger.filter.border.selected',
_expanded: { _expanded: {
color: 'link.primary.hover', color: 'select.trigger.filter.fg.selected',
borderColor: 'link.primary.hover', bgColor: 'select.trigger.filter.border.selected',
borderColor: 'select.trigger.filter.border.selected',
}, },
_hover: { _hover: {
color: 'select.trigger.filter.fg.selected', color: 'select.trigger.filter.fg.selected',
borderColor: 'select.trigger.filter.border.selected', borderColor: 'select.trigger.filter.border.selected',
bgColor: 'select.trigger.filter.border.selected',
}, },
_focusVisible: { _focusVisible: {
borderColor: 'link.primary.hover', borderColor: 'link.primary.hover',
...@@ -174,6 +176,23 @@ export const recipe = defineSlotRecipe({ ...@@ -174,6 +176,23 @@ export const recipe = defineSlotRecipe({
_hover: { _hover: {
color: 'link.primary.hover', color: 'link.primary.hover',
borderColor: 'link.primary.hover', borderColor: 'link.primary.hover',
bgColor: 'transparent',
},
},
// If the default value is selected, the styles should be the same as when the placeholder is shown
'&[data-default-value="true"]': {
color: 'select.trigger.filter.fg',
borderColor: 'select.trigger.filter.border',
bgColor: 'transparent',
_hover: {
color: 'link.primary.hover',
borderColor: 'link.primary.hover',
bgColor: 'transparent',
},
_expanded: {
color: 'inherit',
borderColor: 'inherit',
bgColor: 'inherit',
}, },
}, },
}, },
......
...@@ -66,7 +66,8 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = ...@@ -66,7 +66,8 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const currentAddress = getQueryParamString(router.query.hash); const currentAddress = getQueryParamString(router.query.hash);
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(getFilterValue(router.query.filter)); const initialFilterValue = getFilterValue(router.query.filter);
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(initialFilterValue);
const addressTxsQuery = useQueryWithPages({ const addressTxsQuery = useQueryWithPages({
resourceName: 'address_txs', resourceName: 'address_txs',
...@@ -165,15 +166,14 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = ...@@ -165,15 +166,14 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
return null; return null;
} }
// const filter = ( const filter = (
// <AddressTxsFilter <AddressTxsFilter
// defaultFilter={ filterValue } initialValue={ initialFilterValue }
// onFilterChange={ handleFilterChange } onFilterChange={ handleFilterChange }
// hasActiveFilter={ Boolean(filterValue) } hasActiveFilter={ Boolean(filterValue) }
// isLoading={ addressTxsQuery.pagination.isLoading } isLoading={ addressTxsQuery.pagination.isLoading }
// /> />
// ); );
const filter = null;
const csvExportLink = ( const csvExportLink = (
<AddressCsvExportLink <AddressCsvExportLink
......
import { createListCollection } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
...@@ -10,25 +11,26 @@ const OPTIONS = [ ...@@ -10,25 +11,26 @@ const OPTIONS = [
{ value: 'from', label: 'Outgoing transactions' }, { value: 'from', label: 'Outgoing transactions' },
{ value: 'to', label: 'Incoming transactions' }, { value: 'to', label: 'Incoming transactions' },
]; ];
const collection = createListCollection({ items: OPTIONS });
interface Props { interface Props {
hasActiveFilter: boolean; hasActiveFilter: boolean;
defaultFilter: AddressFromToFilter; initialValue: AddressFromToFilter;
onFilterChange: (nextValue: string | Array<string>) => void; onFilterChange: (nextValue: string | Array<string>) => void;
isLoading?: boolean; isLoading?: boolean;
} }
const AddressTxsFilter = ({ onFilterChange, defaultFilter, hasActiveFilter, isLoading }: Props) => { const AddressTxsFilter = ({ onFilterChange, initialValue, hasActiveFilter, isLoading }: Props) => {
const isInitialLoading = useIsInitialLoading(isLoading); const isInitialLoading = useIsInitialLoading(isLoading);
return ( return (
<PopoverFilterRadio <PopoverFilterRadio
name="txs_filter" name="txs_filter"
options={ OPTIONS } collection={ collection }
onChange={ onFilterChange } onChange={ onFilterChange }
hasActiveFilter={ hasActiveFilter } hasActiveFilter={ hasActiveFilter }
isLoading={ isInitialLoading } isLoading={ isInitialLoading }
defaultValue={ defaultFilter || OPTIONS[0].value } initialValue={ initialValue }
/> />
); );
}; };
......
import type { NextRouter } from 'next/router'; import type { NextRouter } from 'next/router';
import type { SelectOption } from 'ui/shared/select/types';
import config from 'configs/app'; import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import removeQueryParam from 'lib/router/removeQueryParam'; import removeQueryParam from 'lib/router/removeQueryParam';
import type { SelectOption } from 'toolkit/chakra/select';
const feature = config.features.marketplace; const feature = config.features.marketplace;
......
import type { EnsLookupSorting } from 'types/api/ens'; import type { EnsLookupSorting } from 'types/api/ens';
import type { SelectOption } from 'ui/shared/select/types'; import type { SelectOption } from 'toolkit/chakra/select';
import getNextSortValueShared from 'ui/shared/sort/getNextSortValue'; import getNextSortValueShared from 'ui/shared/sort/getNextSortValue';
......
...@@ -39,8 +39,10 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex', ...@@ -39,8 +39,10 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex',
variant="outline" variant="outline"
defaultValue={ [ defaultDataType ] } defaultValue={ [ defaultDataType ] }
onValueChange={ handleValueChange } onValueChange={ handleValueChange }
w="100px"
mr="auto"
> >
<SelectControl w="100px" mr="auto" loading={ isLoading }> <SelectControl loading={ isLoading }>
<SelectValueText placeholder="Select framework"/> <SelectValueText placeholder="Select framework"/>
</SelectControl> </SelectControl>
<SelectContent> <SelectContent>
......
...@@ -2,8 +2,8 @@ import type { TagProps } from '@chakra-ui/react'; ...@@ -2,8 +2,8 @@ import type { TagProps } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { StatsInterval, StatsIntervalIds } from 'types/client/stats'; import type { StatsInterval, StatsIntervalIds } from 'types/client/stats';
import type { SelectOption } from 'ui/shared/select/types';
import type { SelectOption } from 'toolkit/chakra/select';
import Skeleton from 'ui/shared/chakra/Skeleton'; import Skeleton from 'ui/shared/chakra/Skeleton';
import Select from 'ui/shared/select/Select'; import Select from 'ui/shared/select/Select';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect'; import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
......
import type { ListCollection } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SelectOption } from 'ui/shared/select/types'; import type { SelectOption } from 'toolkit/chakra/select';
import { SelectContent, SelectItem, SelectRoot, SelectControl } from 'toolkit/chakra/select';
import FilterButton from 'ui/shared/filters/FilterButton'; import FilterButton from 'ui/shared/filters/FilterButton';
import Select from 'ui/shared/select/Select';
interface Props { interface Props {
name: string; name: string;
options: Array<SelectOption>; collection: ListCollection<SelectOption>;
hasActiveFilter: boolean; hasActiveFilter: boolean;
defaultValue?: string; initialValue?: string;
isLoading?: boolean; isLoading?: boolean;
onChange: (nextValue: string) => void; onChange: (nextValue: string) => void;
} }
const PopoverFilterRadio = ({ name, hasActiveFilter, options, isLoading, onChange, defaultValue }: Props) => { const PopoverFilterRadio = ({ name, hasActiveFilter, collection, isLoading, onChange, initialValue }: Props) => {
const handleValueChange = React.useCallback(({ value }: { value: Array<string> }) => {
onChange(value[0]);
}, [ onChange ]);
return ( return (
<Select <SelectRoot
options={ options }
name={ name } name={ name }
defaultValue={ defaultValue } collection={ collection }
onChange={ onChange } defaultValue={ initialValue ? [ initialValue ] : [ collection.items[0].value ] }
onValueChange={ handleValueChange }
variant="filter"
> >
{ ({ isOpen, onToggle }) => ( <SelectControl
triggerProps={{ asChild: true, pr: 2 }}
noIndicator
defaultValue={ [ collection.items[0].value ] }
>
<FilterButton <FilterButton
isActive={ isOpen }
onClick={ onToggle }
appliedFiltersNum={ hasActiveFilter ? 1 : 0 } appliedFiltersNum={ hasActiveFilter ? 1 : 0 }
isLoading={ isLoading } isLoading={ isLoading }
/> />
) } </SelectControl>
</Select> <SelectContent>
{ collection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
); );
}; };
......
export interface SelectOption<Value extends string = string> {
value: Value;
label: string;
}
import type { SelectOption } from 'ui/shared/select/types';
import type { Query } from 'nextjs-routes'; import type { Query } from 'nextjs-routes';
import type { SelectOption } from 'toolkit/chakra/select';
export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<SelectOption<SortValue>>) { export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<SelectOption<SortValue>>) {
if (!query.sort || !query.order) { if (!query.sort || !query.order) {
return undefined; return undefined;
......
...@@ -3,6 +3,7 @@ import { noop } from 'es-toolkit'; ...@@ -3,6 +3,7 @@ import { noop } from 'es-toolkit';
import React from 'react'; import React from 'react';
import { SelectContent, SelectItem, SelectRoot, SelectControl, SelectValueText } from 'toolkit/chakra/select'; import { SelectContent, SelectItem, SelectRoot, SelectControl, SelectValueText } from 'toolkit/chakra/select';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
import Sort from 'ui/shared/sort/Sort'; import Sort from 'ui/shared/sort/Sort';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter'; import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import { SORT_OPTIONS } from 'ui/txs/useTxsSort'; import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
...@@ -22,6 +23,11 @@ const txSortingOptions = createListCollection({ ...@@ -22,6 +23,11 @@ const txSortingOptions = createListCollection({
}); });
const SelectShowcase = () => { const SelectShowcase = () => {
const [ hasActiveFilter, setHasActiveFilter ] = React.useState(false);
const handleFilterChange = React.useCallback((nextValue: string) => {
setHasActiveFilter(nextValue !== txSortingOptions.items[0].value);
}, []);
return ( return (
<Container value="select"> <Container value="select">
...@@ -68,12 +74,14 @@ const SelectShowcase = () => { ...@@ -68,12 +74,14 @@ const SelectShowcase = () => {
name="transactions_sorting" name="transactions_sorting"
defaultValue={ [ txSortingOptions.items[0].value ] } defaultValue={ [ txSortingOptions.items[0].value ] }
collection={ txSortingOptions } collection={ txSortingOptions }
w="fit-content"
/> />
<Sort <Sort
name="transactions_sorting" name="transactions_sorting"
defaultValue={ [ txSortingOptions.items[0].value ] } defaultValue={ [ txSortingOptions.items[0].value ] }
collection={ txSortingOptions } collection={ txSortingOptions }
isLoading isLoading
w="fit-content"
/> />
</Sample> </Sample>
</SamplesStack> </SamplesStack>
...@@ -86,6 +94,25 @@ const SelectShowcase = () => { ...@@ -86,6 +94,25 @@ const SelectShowcase = () => {
<TokenTransferFilter defaultTypeFilters={ [ ] } onTypeFilterChange={ noop } appliedFiltersNum={ 2 } isLoading/> <TokenTransferFilter defaultTypeFilters={ [ ] } onTypeFilterChange={ noop } appliedFiltersNum={ 2 } isLoading/>
</Sample> </Sample>
</SamplesStack> </SamplesStack>
<SectionSubHeader>Radio filter</SectionSubHeader>
<SamplesStack>
<Sample flexWrap="nowrap">
<PopoverFilterRadio
name="transactions_sorting"
collection={ txSortingOptions }
hasActiveFilter={ hasActiveFilter }
onChange={ handleFilterChange }
/>
<PopoverFilterRadio
name="transactions_sorting"
collection={ txSortingOptions }
hasActiveFilter
onChange={ noop }
isLoading
/>
</Sample>
</SamplesStack>
</Section> </Section>
</Container> </Container>
); );
......
import type { TokenType } from 'types/api/token'; import type { TokenType } from 'types/api/token';
import type { TokensSortingValue } from 'types/api/tokens'; import type { TokensSortingValue } from 'types/api/tokens';
import type { SelectOption } from 'ui/shared/select/types';
import config from 'configs/app'; import config from 'configs/app';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import { TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; import { TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import type { SelectOption } from 'toolkit/chakra/select';
export const SORT_OPTIONS: Array<SelectOption<TokensSortingValue>> = [ export const SORT_OPTIONS: Array<SelectOption<TokensSortingValue>> = [
{ label: 'Default', value: undefined }, { label: 'Default', value: undefined },
......
...@@ -2,10 +2,10 @@ import type { UseQueryResult } from '@tanstack/react-query'; ...@@ -2,10 +2,10 @@ import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { TransactionsSortingValue, TxsResponse } from 'types/api/transaction'; import type { TransactionsSortingValue, TxsResponse } from 'types/api/transaction';
import type { SelectOption } from 'ui/shared/select/types';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import type { SelectOption } from 'toolkit/chakra/select';
import sortTxs from './sortTxs'; import sortTxs from './sortTxs';
......
...@@ -2,7 +2,7 @@ import type { ...@@ -2,7 +2,7 @@ import type {
ValidatorsBlackfortSortingValue, ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField, ValidatorsBlackfortSortingField,
} from 'types/api/validators'; } from 'types/api/validators';
import type { SelectOption } from 'ui/shared/select/types'; import type { SelectOption } from 'toolkit/chakra/select';
export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<SelectOption<ValidatorsBlackfortSortingValue>> = [ export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<SelectOption<ValidatorsBlackfortSortingValue>> = [
{ label: 'Default', value: undefined }, { label: 'Default', value: undefined },
......
...@@ -2,7 +2,7 @@ import type { ...@@ -2,7 +2,7 @@ import type {
ValidatorsStabilitySortingValue, ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField, ValidatorsStabilitySortingField,
} from 'types/api/validators'; } from 'types/api/validators';
import type { SelectOption } from 'ui/shared/select/types'; import type { SelectOption } from 'toolkit/chakra/select';
export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<SelectOption<ValidatorsStabilitySortingValue>> = [ export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<SelectOption<ValidatorsStabilitySortingValue>> = [
{ label: 'Default', value: undefined }, { label: 'Default', value: undefined },
......
import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts'; import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts';
import type { SelectOption } from 'ui/shared/select/types'; import type { SelectOption } from 'toolkit/chakra/select';
export const SORT_OPTIONS: Array<SelectOption<VerifiedContractsSortingValue>> = [ export const SORT_OPTIONS: Array<SelectOption<VerifiedContractsSortingValue>> = [
{ label: 'Default', value: undefined }, { label: 'Default', value: undefined },
......
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