Commit 041f2130 authored by tom's avatar tom

AddressTxsFilter refactoring

parent ac0ac2b1
'use client';
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 IconSvg from 'ui/shared/IconSvg';
......@@ -9,6 +9,11 @@ import IconSvg from 'ui/shared/IconSvg';
import { CloseButton } from './close-button';
import { Skeleton } from './skeleton';
export interface SelectOption<Value extends string = string> {
value: Value;
label: string;
}
export interface SelectControlProps extends ChakraSelect.ControlProps {
clearable?: boolean;
noIndicator?: boolean;
......@@ -20,11 +25,16 @@ export const SelectControl = React.forwardRef<
HTMLButtonElement,
SelectControlProps
>(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 (
<Skeleton loading={ loading } asChild>
<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) && (
<ChakraSelect.IndicatorGroup>
{ clearable && <SelectClearTrigger/> }
......
......@@ -243,12 +243,12 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
},
filter: {
fg: {
DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
selected: { value: { _light: '{colors.blue.600}', _dark: '{colors.gray.50}' } },
DEFAULT: { value: '{colors.button.dropdown.fg}' },
selected: { value: '{colors.button.dropdown.fg.selected}' },
},
border: {
DEFAULT: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } },
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.600}' } },
DEFAULT: { value: '{colors.button.dropdown.border}' },
selected: { value: '{colors.button.dropdown.border.selected}' },
},
},
},
......
......@@ -8,7 +8,7 @@ export const recipe = defineSlotRecipe({
display: 'flex',
flexDirection: 'column',
gap: '1.5',
width: 'full',
width: 'fit-content',
},
trigger: {
display: 'flex',
......@@ -156,12 +156,14 @@ export const recipe = defineSlotRecipe({
bgColor: 'select.trigger.filter.border.selected',
borderColor: 'select.trigger.filter.border.selected',
_expanded: {
color: 'link.primary.hover',
borderColor: 'link.primary.hover',
color: 'select.trigger.filter.fg.selected',
bgColor: 'select.trigger.filter.border.selected',
borderColor: 'select.trigger.filter.border.selected',
},
_hover: {
color: 'select.trigger.filter.fg.selected',
borderColor: 'select.trigger.filter.border.selected',
bgColor: 'select.trigger.filter.border.selected',
},
_focusVisible: {
borderColor: 'link.primary.hover',
......@@ -174,6 +176,23 @@ export const recipe = defineSlotRecipe({
_hover: {
color: '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 =
const isMobile = useIsMobile();
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({
resourceName: 'address_txs',
......@@ -165,15 +166,14 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender =
return null;
}
// const filter = (
// <AddressTxsFilter
// defaultFilter={ filterValue }
// onFilterChange={ handleFilterChange }
// hasActiveFilter={ Boolean(filterValue) }
// isLoading={ addressTxsQuery.pagination.isLoading }
// />
// );
const filter = null;
const filter = (
<AddressTxsFilter
initialValue={ initialFilterValue }
onFilterChange={ handleFilterChange }
hasActiveFilter={ Boolean(filterValue) }
isLoading={ addressTxsQuery.pagination.isLoading }
/>
);
const csvExportLink = (
<AddressCsvExportLink
......
import { createListCollection } from '@chakra-ui/react';
import React from 'react';
import type { AddressFromToFilter } from 'types/api/address';
......@@ -10,25 +11,26 @@ const OPTIONS = [
{ value: 'from', label: 'Outgoing transactions' },
{ value: 'to', label: 'Incoming transactions' },
];
const collection = createListCollection({ items: OPTIONS });
interface Props {
hasActiveFilter: boolean;
defaultFilter: AddressFromToFilter;
initialValue: AddressFromToFilter;
onFilterChange: (nextValue: string | Array<string>) => void;
isLoading?: boolean;
}
const AddressTxsFilter = ({ onFilterChange, defaultFilter, hasActiveFilter, isLoading }: Props) => {
const AddressTxsFilter = ({ onFilterChange, initialValue, hasActiveFilter, isLoading }: Props) => {
const isInitialLoading = useIsInitialLoading(isLoading);
return (
<PopoverFilterRadio
name="txs_filter"
options={ OPTIONS }
collection={ collection }
onChange={ onFilterChange }
hasActiveFilter={ hasActiveFilter }
isLoading={ isInitialLoading }
defaultValue={ defaultFilter || OPTIONS[0].value }
initialValue={ initialValue }
/>
);
};
......
import type { NextRouter } from 'next/router';
import type { SelectOption } from 'ui/shared/select/types';
import config from 'configs/app';
import getQueryParamString from 'lib/router/getQueryParamString';
import removeQueryParam from 'lib/router/removeQueryParam';
import type { SelectOption } from 'toolkit/chakra/select';
const feature = config.features.marketplace;
......
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';
......
......@@ -39,8 +39,10 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex',
variant="outline"
defaultValue={ [ defaultDataType ] }
onValueChange={ handleValueChange }
w="100px"
mr="auto"
>
<SelectControl w="100px" mr="auto" loading={ isLoading }>
<SelectControl loading={ isLoading }>
<SelectValueText placeholder="Select framework"/>
</SelectControl>
<SelectContent>
......
......@@ -2,8 +2,8 @@ import type { TagProps } 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 type { SelectOption } from 'toolkit/chakra/select';
import Skeleton from 'ui/shared/chakra/Skeleton';
import Select from 'ui/shared/select/Select';
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
......
import type { ListCollection } from '@chakra-ui/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 Select from 'ui/shared/select/Select';
interface Props {
name: string;
options: Array<SelectOption>;
collection: ListCollection<SelectOption>;
hasActiveFilter: boolean;
defaultValue?: string;
initialValue?: string;
isLoading?: boolean;
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 (
<Select
options={ options }
<SelectRoot
name={ name }
defaultValue={ defaultValue }
onChange={ onChange }
collection={ collection }
defaultValue={ initialValue ? [ initialValue ] : [ collection.items[0].value ] }
onValueChange={ handleValueChange }
variant="filter"
>
<SelectControl
triggerProps={{ asChild: true, pr: 2 }}
noIndicator
defaultValue={ [ collection.items[0].value ] }
>
{ ({ isOpen, onToggle }) => (
<FilterButton
isActive={ isOpen }
onClick={ onToggle }
appliedFiltersNum={ hasActiveFilter ? 1 : 0 }
isLoading={ isLoading }
/>
) }
</Select>
</SelectControl>
<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 { SelectOption } from 'toolkit/chakra/select';
export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<SelectOption<SortValue>>) {
if (!query.sort || !query.order) {
return undefined;
......
......@@ -3,6 +3,7 @@ import { noop } from 'es-toolkit';
import React from 'react';
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 TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
......@@ -22,6 +23,11 @@ const txSortingOptions = createListCollection({
});
const SelectShowcase = () => {
const [ hasActiveFilter, setHasActiveFilter ] = React.useState(false);
const handleFilterChange = React.useCallback((nextValue: string) => {
setHasActiveFilter(nextValue !== txSortingOptions.items[0].value);
}, []);
return (
<Container value="select">
......@@ -68,12 +74,14 @@ const SelectShowcase = () => {
name="transactions_sorting"
defaultValue={ [ txSortingOptions.items[0].value ] }
collection={ txSortingOptions }
w="fit-content"
/>
<Sort
name="transactions_sorting"
defaultValue={ [ txSortingOptions.items[0].value ] }
collection={ txSortingOptions }
isLoading
w="fit-content"
/>
</Sample>
</SamplesStack>
......@@ -86,6 +94,25 @@ const SelectShowcase = () => {
<TokenTransferFilter defaultTypeFilters={ [ ] } onTypeFilterChange={ noop } appliedFiltersNum={ 2 } isLoading/>
</Sample>
</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>
</Container>
);
......
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 { SelectOption } from 'toolkit/chakra/select';
export const SORT_OPTIONS: Array<SelectOption<TokensSortingValue>> = [
{ label: 'Default', value: undefined },
......
......@@ -2,10 +2,10 @@ 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 { SelectOption } from 'toolkit/chakra/select';
import sortTxs from './sortTxs';
......
......@@ -2,7 +2,7 @@ import type {
ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField,
} 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>> = [
{ label: 'Default', value: undefined },
......
......@@ -2,7 +2,7 @@ import type {
ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField,
} 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>> = [
{ label: 'Default', value: undefined },
......
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>> = [
{ 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