Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6e4ffcd
feat: implement market list search
geositta Jan 16, 2026
8623e11
feat: layout changes to follow mobile design
geositta Jan 16, 2026
a814695
fix: use correct styles on max leverage label
geositta Jan 20, 2026
579edf3
feat: introduce stock/comm subfilter
geositta Jan 20, 2026
a14bf6e
feat: make perp search global
geositta Jan 20, 2026
6c613b8
fix: rm extra route
geositta Jan 20, 2026
cdbbc08
feat: use dropdown for perps sorting
geositta Jan 20, 2026
a8ec594
chore: rm duplicate sort market fn
geositta Jan 20, 2026
109b4b2
chore: add labels
geositta Jan 20, 2026
c84a73f
chore: add labels and tests
geositta Jan 20, 2026
8967662
chore: use ButtonBase in dropdown and search
geositta Jan 20, 2026
3f89c39
chore: rm polling and refresh
geositta Jan 20, 2026
e95ab3d
chore: button style fixes
geositta Jan 20, 2026
0203aa0
chore: replace div with Box in dropdown
geositta Jan 20, 2026
c3c06fc
chore: rm unused filter-chips component
geositta Jan 20, 2026
f92e982
chore: bug fixes, rm unused code
geositta Jan 21, 2026
2cb8ea9
chore: fix rebase typo
geositta Jan 21, 2026
1084704
chore: fix text color potential bug
geositta Jan 21, 2026
0f0158b
chore: refactor filterByType logic
geositta Jan 21, 2026
9c916c3
chore: fix whitespace potential bug
geositta Jan 21, 2026
74fb958
chore: rm unused market badge component
geositta Jan 21, 2026
9f3c496
fix: add types to tests
geositta Jan 21, 2026
6fccb18
fix: add tests to console baseline config
geositta Jan 21, 2026
1a2a3c4
Fix: a11y navigation for filter dropdown, use ButtonBase for back arrow
geositta Jan 21, 2026
ae225af
chore: rm unused hooks
geositta Jan 21, 2026
4e8ffb9
chore: update selected perp test
geositta Jan 21, 2026
be9f504
chore: add missing translations
geositta Jan 21, 2026
fd3b9de
chore: linting for jsdoc
geositta Jan 21, 2026
4b5551f
feat: use textfieldsearch component
geositta Jan 21, 2026
48a7008
chore: linting
geositta Jan 21, 2026
597b58e
fix: use ml-auto in dropdown
geositta Jan 21, 2026
da52964
chore: use existing getChangeColor util
geositta Jan 21, 2026
94ce943
chore: use feature flag for market-list, include test
geositta Jan 21, 2026
8a52a6f
feat: use DS skeleton
geositta Jan 22, 2026
e4edf37
Merge branch 'main' into perps/search-market-list
geositta Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: use dropdown for perps sorting
  • Loading branch information
geositta committed Jan 22, 2026
commit cdbbc085c085a3ede6d530854be45115e7c1451d
143 changes: 143 additions & 0 deletions ui/pages/perps/market-list/components/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { useState, useCallback, useRef, useEffect } from 'react';
import {
Box,
BoxFlexDirection,
Text,
TextVariant,
TextColor,
Icon,
IconName,
IconSize,
IconColor,
ButtonBase,
ButtonBaseSize,
} from '@metamask/design-system-react';

export type DropdownOption<T extends string> = {
id: T;
label: string;
};

export type DropdownProps<T extends string> = {
/** Available options */
options: DropdownOption<T>[];
/** Currently selected option ID */
selectedId: T;
/** Callback when selection changes */
onChange: (id: T) => void;
/** Test ID prefix for testing */
testId: string;
};

/**
* Reusable dropdown component styled like PickerNetwork
*
* @param props - Component props
* @param props.options - Available options to display
* @param props.selectedId - Currently selected option ID
* @param props.onChange - Callback when selection changes
* @param props.testId - Test ID prefix for testing
*/
export function Dropdown<T extends string>({
options,
selectedId,
onChange,
testId,
}: DropdownProps<T>) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

const selectedOption = options.find((opt) => opt.id === selectedId);

// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};

if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);

const handleToggle = useCallback(() => {
setIsOpen((prev) => !prev);
}, []);

const handleSelect = useCallback(
(option: DropdownOption<T>) => {
onChange(option.id);
setIsOpen(false);
},
[onChange],
);

return (
<div ref={dropdownRef} className="relative">
{/* Trigger button styled like PickerNetwork */}
<ButtonBase
size={ButtonBaseSize.Sm}
className="flex items-center justify-start gap-1 rounded-lg bg-background-muted px-3 py-2 hover:bg-hover active:opacity-70"
onClick={handleToggle}
data-testid={`${testId}-button`}
>
<Text variant={TextVariant.BodySm} color={TextColor.TextDefault}>
{selectedOption?.label ?? ''}
</Text>
<Icon
name={isOpen ? IconName.ArrowUp : IconName.ArrowDown}
size={IconSize.Xs}
color={IconColor.IconAlternative}
/>
</ButtonBase>

{/* Dropdown menu */}
{isOpen && (
<Box
className="absolute left-0 top-full z-10 mt-1 min-w-[120px] overflow-hidden rounded-lg border border-border-muted bg-background-default shadow-lg"
flexDirection={BoxFlexDirection.Column}
data-testid={`${testId}-menu`}
>
{options.map((option) => (
<button
key={option.id}
type="button"
onClick={() => handleSelect(option)}
className="flex w-full items-center justify-between px-3 py-2 text-left hover:bg-hover active:bg-pressed"
data-testid={`${testId}-option-${option.id}`}
>
<Text
variant={TextVariant.BodySm}
color={
option.id === selectedId
? TextColor.TextDefault
: TextColor.TextAlternative
}
>
{option.label}
</Text>
{option.id === selectedId && (
<Icon
name={IconName.Check}
size={IconSize.Sm}
color={IconColor.PrimaryDefault}
/>
)}
</button>
))}
</Box>
)}
</div>
);
}

export default Dropdown;
5 changes: 5 additions & 0 deletions ui/pages/perps/market-list/components/dropdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
Dropdown,
type DropdownOption,
type DropdownProps,
} from './dropdown';
133 changes: 18 additions & 115 deletions ui/pages/perps/market-list/components/filter-select/filter-select.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
import React, { useState, useCallback, useRef, useEffect } from 'react';
import {
Box,
BoxFlexDirection,
Text,
TextVariant,
TextColor,
Icon,
IconName,
IconSize,
IconColor,
} from '@metamask/design-system-react';
import React, { useMemo } from 'react';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
import { Dropdown, type DropdownOption } from '../dropdown';

export type MarketFilter = 'all' | 'crypto' | 'stocks';

export type FilterOption = {
id: MarketFilter;
labelKey: string;
};

export const FILTER_OPTIONS: FilterOption[] = [
{ id: 'all', labelKey: 'perpsFilterAll' },
{ id: 'crypto', labelKey: 'perpsFilterCrypto' },
{ id: 'stocks', labelKey: 'perpsFilterStocks' },
];

export type FilterSelectProps = {
/** Currently selected filter */
value: MarketFilter;
Expand All @@ -35,108 +14,32 @@ export type FilterSelectProps = {
/**
* FilterSelect component displays a dropdown for filtering markets by type
*
* @param options0 - Component props
* @param options0.value - Currently selected filter
* @param options0.onChange - Callback when filter changes
* @param props - Component props
* @param props.value - Currently selected filter
* @param props.onChange - Callback when filter changes
*/
export const FilterSelect: React.FC<FilterSelectProps> = ({
value,
onChange,
}) => {
const t = useI18nContext();
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

const selectedOption = FILTER_OPTIONS.find((option) => option.id === value);

// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};

if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);

const handleToggle = useCallback(() => {
setIsOpen((prev) => !prev);
}, []);

const handleOptionSelect = useCallback(
(option: FilterOption) => {
onChange(option.id);
setIsOpen(false);
},
[onChange],
const options: DropdownOption<MarketFilter>[] = useMemo(
() => [
{ id: 'all', label: t('perpsFilterAll') },
{ id: 'crypto', label: t('perpsFilterCrypto') },
{ id: 'stocks', label: t('perpsFilterStocks') },
],
[t],
);

return (
<div ref={dropdownRef} className="relative">
{/* Dropdown trigger button */}
<button
type="button"
onClick={handleToggle}
className="flex items-center gap-1 rounded-lg border border-border-muted bg-background-default px-3 py-2 hover:bg-hover active:bg-pressed"
data-testid="filter-select-button"
>
<Text variant={TextVariant.BodySm}>
{selectedOption ? t(selectedOption.labelKey) : ''}
</Text>
<Icon
name={isOpen ? IconName.ArrowUp : IconName.ArrowDown}
size={IconSize.Xs}
color={IconColor.IconAlternative}
/>
</button>

{/* Dropdown menu */}
{isOpen && (
<Box
className="absolute left-0 top-full z-10 mt-1 min-w-[120px] overflow-hidden rounded-lg border border-border-muted bg-background-default shadow-lg"
flexDirection={BoxFlexDirection.Column}
data-testid="filter-select-menu"
>
{FILTER_OPTIONS.map((option) => (
<button
key={option.id}
type="button"
onClick={() => handleOptionSelect(option)}
className="flex w-full items-center justify-between px-3 py-2 text-left hover:bg-hover active:bg-pressed"
data-testid={`filter-option-${option.id}`}
>
<Text
variant={TextVariant.BodySm}
color={
option.id === value
? TextColor.TextDefault
: TextColor.TextAlternative
}
>
{t(option.labelKey)}
</Text>
{option.id === value && (
<Icon
name={IconName.Check}
size={IconSize.Sm}
color={IconColor.PrimaryDefault}
/>
)}
</button>
))}
</Box>
)}
</div>
<Dropdown
options={options}
selectedId={value}
onChange={onChange}
testId="filter-select"
/>
);
};

Expand Down
Loading