Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
e72108f
feat: add market list client sort util with missing-value sink
erikzou Jul 18, 2026
2c305ef
feat: open all columns for market list client sort
erikzou Jul 18, 2026
b41ccd4
feat: enable full-column header sort on trending tab
erikzou Jul 18, 2026
3961680
fix: avoid nested ternary in client sort key resolution
erikzou Jul 18, 2026
47822a2
feat: hide injected BTC row behind handoff switch
erikzou Jul 18, 2026
8f84d49
feat: add showMarketListRedesign dev flag
erikzou Jul 18, 2026
8abbc00
feat: add market list filter state model and local adapter
erikzou Jul 18, 2026
a021c62
feat: add market filter chips bar with tier popover
erikzou Jul 18, 2026
9b88832
feat: add market filters popover with tier rows
erikzou Jul 18, 2026
8a4ca91
fix: wrap filters badge count in Badge.Text and slugify audit testIDs
erikzou Jul 18, 2026
4d91cd8
feat: wire filter chips demo into desktop trending tab
erikzou Jul 18, 2026
20237a8
feat: add redesigned trending columns with reason tag placeholder
erikzou Jul 18, 2026
3e19ea9
fix: gate market list redesign to trending tab only
erikzou Jul 18, 2026
51f5506
feat: add dex filter chip analytics
erikzou Jul 18, 2026
e5844d7
fix: use toSorted in market list client sort
erikzou Jul 18, 2026
42f9031
fix: constrain market filters popover height and wrap tier rows
erikzou Jul 18, 2026
3568aaa
fix: reduce filters popover scroll area for short windows
erikzou Jul 18, 2026
e21e10a
fix: keep BTC-hidden trending list from re-opening pagination
erikzou Jul 18, 2026
8ccb7b1
fix: restrict banner detail client sort to change24h column
erikzou Jul 18, 2026
64b5c00
feat: align market list redesign UI with Figma specs
erikzou Jul 19, 2026
9e185a7
fix: stabilize filter chips bar height and unify tier pill style
erikzou Jul 19, 2026
d917517
feat: rework market filter tiers into dimension buckets
erikzou Jul 19, 2026
8e1e91f
feat: align filters popover tier grid and scrolling tabs
erikzou Jul 19, 2026
eea97e1
fix: uniform fixed-width tier pills and drop columnless filter dimens…
erikzou Jul 19, 2026
6a4c522
feat: fit four tier pills per row and unit-suffixed row titles
erikzou Jul 19, 2026
9200b54
feat: replace filters popover with grouped modal
erikzou Jul 19, 2026
b87b71d
feat: refine redesign table header and first column metrics
erikzou Jul 19, 2026
e9b7bbd
fix: use size token for header sort icon and restore price sorting
erikzou Jul 19, 2026
f8cca38
fix: set redesign row height to 68px and pad sticky header
erikzou Jul 19, 2026
b65ae51
fix: narrow redesign name column to 208px
erikzou Jul 19, 2026
f505bf1
feat: add copy affordance to redesign address cell
erikzou Jul 19, 2026
1ad60f4
fix: recolor address copy target on hover instead of fading
erikzou Jul 19, 2026
a4b927f
feat: lay out filter rows with left labels and metrics/audit groups
erikzou Jul 19, 2026
7d0edd0
feat: restyle filters modal to match design spec
erikzou Jul 19, 2026
2c0b11f
refactor: order filter rows by stock then flow metrics
erikzou Jul 19, 2026
04c5a98
fix: drop non-reactive time frame row from filters modal
erikzou Jul 19, 2026
f222811
fix: restore time frame row with draft-applied selection
erikzou Jul 19, 2026
63b82d9
fix: keep filter row labels on a single line
erikzou Jul 19, 2026
402e1f9
fix: name audit group after the metric its rows measure
erikzou Jul 19, 2026
9632a16
feat: unify chip and header sort state, add chip tooltips
erikzou Jul 20, 2026
ed64b49
feat: rebuild P2-9 chips on the frozen 2026-07-21 spec
erikzou Jul 20, 2026
8a38f52
feat: no-jitter hover remove on condition chips, trim tier rows
erikzou Jul 21, 2026
8ac7b30
feat: condition chip close-button hover recolor, reuse GradientMask
erikzou Jul 21, 2026
6a8e534
fix: opaque chip fade plateau, inert sort-chip body
erikzou Jul 21, 2026
1357592
feat: single-row tier popover, sort chip dropdown with disabled asc
erikzou Jul 21, 2026
23c8097
fix: widen tier popover so three floor labels fit one row
erikzou Jul 21, 2026
96872be
style: tier popover title to bodySmMedium
erikzou Jul 21, 2026
26712e0
feat: fade-slide transition on chip <-> pill swap
erikzou Jul 21, 2026
ee59b15
style: condition-chip and filter-label text to text/text
erikzou Jul 21, 2026
96325f7
style: Filters badge to info color
erikzou Jul 21, 2026
7cb238d
feat: move trending filters into the bordered sub-category toolbar
erikzou Jul 23, 2026
641b7c4
style: rebalance toolbar spacing 8px up
erikzou Jul 23, 2026
3ddc3d9
refactor: one shared toolbar frame across all four Market tabs
erikzou Jul 23, 2026
732997b
style: trending toolbar buttons take the category pill shape
erikzou Jul 23, 2026
86f774b
fix: play the chip transition on expand, not on load
erikzou Jul 23, 2026
4f5cf86
chore: hide the mock reason tag for this delivery
erikzou Jul 27, 2026
4ae5b46
chore: drop two dev-note captions from the Filters modal
erikzou Jul 27, 2026
50d3e20
feat: add the Filters entry to the mobile toolbar
erikzou Jul 27, 2026
c0e4edc
Merge remote-tracking branch 'upstream/x' into feat/market-list-sort-…
erikzou Aug 14, 2026
bb7da24
fix: carry the Filters entry into the native mobile layout
erikzou Aug 14, 2026
3eef0cf
feat: hide the holdings audit filter group for this wave
erikzou Aug 14, 2026
87dc202
feat: pass market filter conditions through to the hot-token API
erikzou Aug 14, 2026
fdb4edf
fix: drop the unsupported tabPressAnimationEnabled prop
erikzou Aug 14, 2026
df9c397
test: lock token age as the only client-side filter dimension
erikzou Aug 14, 2026
14b17ae
feat: share the redesigned list visuals and client sort across market…
erikzou Aug 14, 2026
60c0b39
feat: let every banner detail column sort on the client
erikzou Aug 14, 2026
e319f5e
fix: keep ascending selectable for a hand-driven sort
erikzou Aug 14, 2026
a6cba4a
feat: localize the market filter panel, chips and header tooltips
erikzou Aug 14, 2026
44a4d84
fix: fit the token age tiers in the filter pill
erikzou Aug 15, 2026
5c5c819
fix: align the perps mobile rows with the spot lists
erikzou Aug 15, 2026
48d8d9d
fix: name the market volume columns by the window they report
erikzou Aug 15, 2026
062315b
fix: give the perps desktop table the shared row geometry
erikzou Aug 15, 2026
1564eba
Merge remote-tracking branch 'upstream/x' into feat/market-list-sort-…
erikzou Aug 16, 2026
d6cac55
fix: restore tabPressAnimationEnabled now that Tabs declares it
erikzou Aug 16, 2026
7641a1e
refactor: ship the market list redesign without the dev flag
erikzou Aug 16, 2026
7259124
fix: address review findings on market sort, filters and analytics
erikzou Aug 16, 2026
e6c3df5
chore: pull translations after token age wording fix
erikzou Aug 16, 2026
3f79d1d
docs: record the measured pagination behavior behind client sort
erikzou Aug 16, 2026
0068eae
refactor: gate the market filters entry on the list that consumes it
erikzou Aug 16, 2026
adc8101
revert: stop hiding the injected BTC row in trending
erikzou Aug 16, 2026
49df56e
fix: keep the sort on an unchanged Confirm and stop double-firing hea…
erikzou Aug 16, 2026
b5e2b14
Merge remote-tracking branch 'upstream/x' into feat/market-list-sort-…
erikzou Aug 16, 2026
bc30da4
fix: drop watchlist header sorting and sort banner perps
erikzou Aug 17, 2026
91c3679
fix: keep the sort on an unchanged filter and anchor presets to their…
erikzou Aug 17, 2026
b5007aa
style: wrap the preset time-range guard
erikzou Aug 17, 2026
eda5ecd
feat: apply quick filter chips on the user's selected time frame
erikzou Aug 17, 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface IHeaderColumnProps<T> {
selectedColumnName: string;
onChangeSelectedName: (columnName: string) => void;
onHeaderRow?: ITableProps<T>['onHeaderRow'];
// Controlled mode: the order comes from the parent and no internal sort
// state is kept, so external sort actions stay reflected in the arrows.
isSortControlled?: boolean;
controlledOrder?: ETableSortType;
}

function HeaderColumn<T>({
Expand All @@ -23,6 +27,8 @@ function HeaderColumn<T>({
onHeaderRow,
selectedColumnName,
onChangeSelectedName,
isSortControlled,
controlledOrder,
}: IHeaderColumnProps<T>) {
const {
title,
Expand All @@ -39,19 +45,36 @@ function HeaderColumn<T>({
events?.initialSortOrder,
);

let currentSortOrder: ETableSortType | undefined;
if (isSortControlled) {
currentSortOrder = controlledOrder;
} else if (dataIndex === selectedColumnName) {
currentSortOrder = sortOrder;
}

useEffect(() => {
if (isSortControlled) {
return;
}
if (selectedColumnName !== dataIndex) {
setSortOrder(undefined);
}
}, [dataIndex, selectedColumnName]);
}, [dataIndex, selectedColumnName, isSortControlled]);

const handleColumnPress = useCallback(() => {
events?.onPress?.();
if (!enableSortType) {
return;
}
const disabledSorts = events?.disableSort || [];
const order = getNextSortOrder(sortOrder, disabledSorts);
const order = getNextSortOrder(currentSortOrder, disabledSorts);

// Controlled mode owns no local state: just report the next order and let
// the parent's re-render drive the arrow.
if (isSortControlled) {
events?.onSortTypeChange?.(order);
return;
}

// When resetting to undefined, clear the selected column to allow default sorting
if (order === undefined) {
Expand All @@ -68,12 +91,17 @@ function HeaderColumn<T>({
setTimeout(() => {
events?.onSortTypeChange?.(order);
});
}, [dataIndex, enableSortType, events, onChangeSelectedName, sortOrder]);
}, [
dataIndex,
enableSortType,
events,
onChangeSelectedName,
currentSortOrder,
isSortControlled,
]);

const cursor = enableSortType ? 'pointer' : undefined;
const showSortIcon = enableSortType && !renderTitle;
const currentSortOrder =
dataIndex === selectedColumnName ? sortOrder : undefined;

const { renderSortIcon: renderInlineSortIcon } = useSortIcon({
showSortIcon: enableSortType && !!renderTitle,
Expand All @@ -91,7 +119,12 @@ function HeaderColumn<T>({

let titleContent;
if (renderTitle) {
titleContent = renderTitle(renderInlineSortIcon());
titleContent = renderTitle(renderInlineSortIcon(), {
order: currentSortOrder,
onSortPress: enableSortType
? (handleColumnPress as () => void)
: undefined,
});
} else if (typeof title === 'string') {
titleContent = (
<SizableText
Expand Down
32 changes: 29 additions & 3 deletions packages/components/src/composite/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,15 @@ function TableHeaderRow<T>({
onHeaderRow,
rowProps,
headerRowProps,
controlledSort,
}: {
columns: ITableProps<T>['columns'];
onHeaderRow?: ITableProps<T>['onHeaderRow'];
rowProps?: ITableProps<T>['rowProps'];
headerRowProps?: ITableProps<T>['headerRowProps'];
controlledSort?: ITableProps<T>['controlledSort'];
}) {
const isSortControlled = controlledSort !== undefined;
const initialSelectedColumn = useMemo(() => {
if (!onHeaderRow) return '';
for (let i = 0; i < columns.length; i += 1) {
Expand All @@ -328,6 +331,9 @@ function TableHeaderRow<T>({
const [selectedColumnName, setSelectedColumnName] = useState(
initialSelectedColumn,
);
const activeColumnName = isSortControlled
? (controlledSort?.dataIndex ?? '')
: selectedColumnName;
return (
<XStack
{...(rowProps as IXStackProps)}
Expand All @@ -337,11 +343,17 @@ function TableHeaderRow<T>({
column ? (
<MemoHeaderColumn
key={column.dataIndex}
selectedColumnName={selectedColumnName}
selectedColumnName={activeColumnName}
onChangeSelectedName={setSelectedColumnName}
column={column as any}
index={index}
onHeaderRow={onHeaderRow}
isSortControlled={isSortControlled}
controlledOrder={
controlledSort?.dataIndex === column.dataIndex
? controlledSort.order
: undefined
}
/>
) : null,
)}
Expand All @@ -358,6 +370,7 @@ function BasicTable<T>({
TableFooterComponent,
TableEmptyComponent,
onHeaderRow,
controlledSort,
onRow,
rowProps,
keyExtractor,
Expand Down Expand Up @@ -445,9 +458,17 @@ function BasicTable<T>({
rowProps={rowProps}
headerRowProps={headerRowProps}
onHeaderRow={onHeaderRow}
controlledSort={controlledSort}
/>
) : null,
[columns, headerRowProps, onHeaderRow, rowProps, showHeader],
[
columns,
headerRowProps,
onHeaderRow,
controlledSort,
rowProps,
showHeader,
],
);

const renderPlaceholder = useCallback(
Expand Down Expand Up @@ -810,5 +831,10 @@ export const Table = withStaticProperties(BasicTable, {
});

// Export types
export type { ITableProps, ITableColumn } from './types';
export type {
ITableProps,
ITableColumn,
ITableColumnSortContext,
ITableControlledSort,
} from './types';
export { ETableSortType } from './types';
23 changes: 22 additions & 1 deletion packages/components/src/composite/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,30 @@ export enum ETableSortType {
DESC = 'desc',
}

// Sort state handed to renderTitle so a custom header can draw its own sort
// affordance and make the whole title (text + icon) the click target.
export interface ITableColumnSortContext {
order?: ETableSortType;
onSortPress?: () => void;
}

// Externally-owned sort state. `null` means "nothing sorted". Passing this
// switches the header from its internal per-column state to controlled mode,
// which is what lets an outside control (e.g. a sort shortcut chip) and the
// header arrows stay on one source of truth.
export type ITableControlledSort = {
dataIndex: string;
order: ETableSortType;
} | null;

export interface ITableColumn<T> {
title: ReactNode;
// When provided, renders the title with an inline sort icon instead of
// the default external sort icon rendered by Column.
renderTitle?: (sortIcon: ReactNode) => ReactNode;
renderTitle?: (
sortIcon: ReactNode,
sortContext: ITableColumnSortContext,
) => ReactNode;
dataIndex: string;
titleProps?: ISizableTextProps;
columnProps?: Omit<IStackProps, 'onPress' | 'onLongPress'>;
Expand Down Expand Up @@ -55,6 +74,8 @@ export interface ITableProps<T> {
onDragBegin?: ISortableListViewProps<T>['onDragBegin'];
onDragEnd?: ISortableListViewProps<T>['onDragEnd'];
keyExtractor: (item: T, index: number) => string;
// Opt into controlled header sort; omit to keep the internal state behavior.
controlledSort?: ITableControlledSort;
onHeaderRow?: (
column: ITableColumn<T>,
index: number,
Expand Down
7 changes: 7 additions & 0 deletions packages/kit-bg/src/services/ServiceMarketV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ type IMarketTokenListRequestParams = {
type?: string;
category?: string;
timeFrame?: string;
// hot-token v6 filter params (marketCapMin, liquidityMin, volumeMin, ...),
// already flattened by buildHotTokenFilterParams on the caller side.
filterParams?: Record<string, number>;
};

type INormalizedMarketTokenListRequestParams = IMarketTokenListRequestParams & {
Expand Down Expand Up @@ -184,6 +187,7 @@ class ServiceMarketV2 extends ServiceBase {
type,
category,
timeFrame,
filterParams,
}: INormalizedMarketTokenListRequestParams) {
const client = await this.getClient(EServiceEndpointEnum.Utility);
const response = await client.get<{
Expand All @@ -202,6 +206,7 @@ class ServiceMarketV2 extends ServiceBase {
type,
category,
timeFrame,
...filterParams,
currency: 'usd',
},
});
Expand Down Expand Up @@ -341,6 +346,7 @@ class ServiceMarketV2 extends ServiceBase {
type,
category,
timeFrame,
filterParams,
}: IMarketTokenListRequestParams,
options?: IFetchMarketTokenListOptions,
) {
Expand All @@ -355,6 +361,7 @@ class ServiceMarketV2 extends ServiceBase {
type,
category,
timeFrame,
filterParams,
});
if (options?.forceRemote) {
return this._fetchMarketTokenListFromApi(normalizedParams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export function BannerDetailListColumnHeader({
<SizableText color="$textSubdued" size="$bodySmMedium" py="$2">
{`${intl.formatMessage({
id: ETranslations.global_name,
})} / ${intl.formatMessage({
id: ETranslations.dexmarket_turnover,
})} / 24h ${intl.formatMessage({
id: ETranslations.perp_token_selector_volume,
})}`}
</SizableText>
</XStack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type IMarketPerpsToken,
mapServerToken,
} from '../MarketHomeV2/components/MarketPerpsList/hooks/useMarketPerpsTokenList';
import { usePerpsClientSort } from '../MarketHomeV2/components/MarketPerpsList/hooks/usePerpsClientSort';
import { usePerpsColumns } from '../MarketHomeV2/components/MarketPerpsList/hooks/usePerpsColumns';

import { BannerDetailTokenFlatList } from './BannerDetailTokenFlatList';
Expand Down Expand Up @@ -98,6 +99,14 @@ export function PerpsTokenListSection({
[tokens],
);

// Same header sorting the standalone perps list uses, so the two perps
// tables behave identically wherever they appear.
const {
sortedTokens,
onHeaderRow: handleHeaderRow,
controlledSort,
} = usePerpsClientSort({ tokens });

const showSkeleton = Boolean(isLoading) && tokens.length === 0;

const handleMobileItemPress = useCallback(
Expand Down Expand Up @@ -155,7 +164,9 @@ export function PerpsTokenListSection({
<Table<IMarketPerpsToken>
stickyHeader
columns={perpsColumns}
dataSource={tokens}
dataSource={sortedTokens}
onHeaderRow={handleHeaderRow}
controlledSort={controlledSort}
keyExtractor={(item) => item.name}
estimatedItemSize="$14"
extraData={tokens.length}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import backgroundApiProxy from '@onekeyhq/kit/src/background/instance/background
import { usePromiseResult } from '@onekeyhq/kit/src/hooks/usePromiseResult';
import { useMarketBannerListSortAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms';

import { MARKET_CLIENT_SORT_FIELD_MAP } from '../../MarketHomeV2/components/MarketTokenList/utils/marketListClientSort';
import {
getNetworkLogoUri,
transformApiItemToToken,
Expand All @@ -12,12 +13,13 @@ import {
import type { IMarketTokenListResult } from '../../MarketHomeV2/components/MarketTokenList/MarketTokenListBase';

const BANNER_DETAIL_CHANGE_SORT_BY = 'change24h';
type IBannerDetailSortBy = typeof BANNER_DETAIL_CHANGE_SORT_BY;

function isBannerDetailSortBy(
sortBy: string | undefined,
): sortBy is IBannerDetailSortBy {
return sortBy === BANNER_DETAIL_CHANGE_SORT_BY;
// Any column the shared client sort can express is accepted, so the desktop
// table's headers all sort here like they do on the home lists. The mobile
// change toggle still reads `changeSortType` below, which narrows back to the
// change column on its own.
function isBannerDetailSortBy(sortBy: string | undefined): sortBy is string {
return Boolean(sortBy && MARKET_CLIENT_SORT_FIELD_MAP[sortBy]);
}

type IUseMarketBannerDetailParams = {
Expand Down Expand Up @@ -89,7 +91,7 @@ export function useMarketBannerDetail({
);

const toggleSort = useCallback(
(sortBy: IBannerDetailSortBy) => {
(sortBy: string) => {
const activeSortType =
sortRef.current.sortBy === sortBy
? sortRef.current.sortType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,11 @@ const MarketTokenSelectorList = memo(
size="$bodySm"
color="$textSubdued"
>
{intl.formatMessage({ id: ETranslations.dexmarket_turnover })}
{/* The selector requests a fixed 1h window (MarketTokenSelector
passes timeRange="1h"), so the header names that window. */}
{`1h ${intl.formatMessage({
id: ETranslations.perp_token_selector_volume,
})}`}
</SizableText>
</XStack>

Expand Down
Loading