Skip to content

Commit 8e357a9

Browse files
authored
Add hide low TVL filter (#1815)
* Add low tvl filter * Add title to input * Reduce gap * Fix mobile * Change variable name
1 parent 8cb9b6a commit 8e357a9

File tree

4 files changed

+166
-49
lines changed

4 files changed

+166
-49
lines changed

apps/hyperdrive-trading/src/ui/base/components/MultiSelect.tsx

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,42 @@ import {
66
} from "@heroicons/react/20/solid";
77
import classNames from "classnames";
88
import Fuse from "fuse.js";
9-
import { ReactElement, ReactNode, useMemo, useRef, useState } from "react";
9+
import {
10+
PropsWithChildren,
11+
ReactElement,
12+
ReactNode,
13+
useMemo,
14+
useRef,
15+
useState,
16+
} from "react";
17+
import { OneOf } from "viem";
1018

11-
export interface MultiSelectProps<T extends OptionValue> {
19+
export type MultiSelectProps<T extends OptionValue> = {
1220
selected: T[];
13-
/**
14-
* The value to show inside the button, representing the selected value, e.g.,
15-
* `"4 selected"`.
16-
*/
17-
displayValue: ReactNode;
1821
options: MultiSelectOption<T>[];
1922
onChange: (selected: T[]) => void;
20-
/**
21-
* The value to use for the button's `title` attribute.
22-
*/
23-
title?: string;
2423
className?: string;
2524
searchEnabled?: boolean;
26-
}
25+
} & OneOf<
26+
| {
27+
// The button that will open the dropdown
28+
button: ReactNode;
29+
}
30+
| {
31+
/**
32+
* The value to show inside the button, representing the selected value, e.g.,
33+
* `"4 selected"`.
34+
*/
35+
displayValue: ReactNode;
36+
/**
37+
* The value to use for the button's `title` attribute.
38+
*/
39+
title?: string;
40+
}
41+
>;
2742

2843
export function MultiSelect<T extends OptionValue>({
44+
button,
2945
displayValue,
3046
selected,
3147
options,
@@ -54,15 +70,12 @@ export function MultiSelect<T extends OptionValue>({
5470

5571
return (
5672
<div className={classNames("daisy-dropdown", className)}>
57-
<div
58-
tabIndex={0}
59-
role="button"
60-
title={title}
61-
className="daisy-btn daisy-btn-outline daisy-btn-sm flex items-center justify-center border-gray-600"
62-
>
63-
{displayValue}
64-
<ChevronDownIcon className="size-5" />
65-
</div>
73+
{button || (
74+
<MultiSelectButton title={title}>
75+
{displayValue}
76+
<ChevronDownIcon className="hidden size-5 sm:block" />
77+
</MultiSelectButton>
78+
)}
6679
<div
6780
tabIndex={0}
6881
className="daisy-menu daisy-dropdown-content z-[1] mt-1 gap-2 rounded-lg border border-base-200 bg-base-100 p-2 shadow"
@@ -167,6 +180,29 @@ export function MultiSelect<T extends OptionValue>({
167180
);
168181
}
169182

183+
export function MultiSelectButton({
184+
title,
185+
className,
186+
children,
187+
}: PropsWithChildren<{
188+
title?: string;
189+
className?: string;
190+
}>): ReactNode {
191+
return (
192+
<div
193+
tabIndex={0}
194+
role="button"
195+
title={title}
196+
className={classNames(
197+
"daisy-btn daisy-btn-outline daisy-btn-sm flex items-center justify-center border-gray-600 py-1",
198+
className,
199+
)}
200+
>
201+
{children}
202+
</div>
203+
);
204+
}
205+
170206
export type MultiSelectOption<T extends OptionValue> = {
171207
value: T;
172208
/**

apps/hyperdrive-trading/src/ui/markets/PoolsList/PoolsList.tsx

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { parseFixed } from "@delvtech/fixed-point-wasm";
12
import {
23
AdjustmentsHorizontalIcon,
34
BarsArrowDownIcon,
5+
ChevronDownIcon,
46
} from "@heroicons/react/20/solid";
57
import { ArrowUpIcon } from "@heroicons/react/24/outline";
68
import { useNavigate, useSearch } from "@tanstack/react-router";
@@ -9,19 +11,28 @@ import { ReactElement, ReactNode } from "react";
911
import { Fade } from "react-awesome-reveal";
1012
import { useAppConfigForConnectedChain } from "src/ui/appconfig/useAppConfigForConnectedChain";
1113
import LoadingState from "src/ui/base/components/LoadingState";
12-
import { MultiSelect } from "src/ui/base/components/MultiSelect";
14+
import {
15+
MultiSelect,
16+
MultiSelectButton,
17+
} from "src/ui/base/components/MultiSelect";
1318
import { NonIdealState } from "src/ui/base/components/NonIdealState";
1419
import { Well } from "src/ui/base/components/Well/Well";
1520
import { LANDING_ROUTE } from "src/ui/landing/routes";
1621
import { PoolRow } from "src/ui/markets/PoolRow/PoolRow";
1722
import { sortOptions, usePoolsList } from "src/ui/markets/hooks/usePoolsList";
1823
import { useAccount } from "wagmi";
24+
1925
export function PoolsList(): ReactElement {
2026
const { address: account } = useAccount();
2127
const appConfig = useAppConfigForConnectedChain();
22-
const { chains: selectedChains, assets: selectedAssets } = useSearch({
28+
const {
29+
chains: selectedChains,
30+
assets: selectedAssets,
31+
hideLowTvl = true,
32+
} = useSearch({
2333
from: LANDING_ROUTE,
2434
});
35+
const lowTvlThreshold = parseFixed(10_000);
2536
const {
2637
filters,
2738
status,
@@ -32,6 +43,10 @@ export function PoolsList(): ReactElement {
3243
} = usePoolsList({
3344
selectedChains,
3445
selectedAssets,
46+
hideLowTvl: {
47+
enabled: hideLowTvl,
48+
threshold: lowTvlThreshold,
49+
},
3550
});
3651

3752
const navigate = useNavigate({ from: LANDING_ROUTE });
@@ -60,14 +75,26 @@ export function PoolsList(): ReactElement {
6075
) : pools ? (
6176
<>
6277
{/* List controls */}
63-
<div className="relative z-20 flex items-center justify-between gap-10">
78+
<div className="relative z-20 flex items-stretch justify-between gap-2">
6479
{/* Filters */}
65-
<div className="flex items-center gap-2">
66-
<AdjustmentsHorizontalIcon className="size-5 sm:mr-1" />
80+
<div className="flex items-stretch gap-2">
81+
<AdjustmentsHorizontalIcon className="hidden size-5 sm:mr-1 sm:block" />
6782
{/* Chain filter */}
6883
{filters && filters.chains.length > 1 && (
6984
<MultiSelect
70-
title="Filter by chain"
85+
button={
86+
<MultiSelectButton
87+
title="Filter by chain"
88+
className="h-full py-2 sm:py-0"
89+
>
90+
{selectedChains?.length === 1
91+
? appConfig.chains[selectedChains[0]].name
92+
: `${
93+
selectedChains?.length || filters?.chains.length
94+
} chains`}
95+
<ChevronDownIcon className="hidden size-5 sm:block" />
96+
</MultiSelectButton>
97+
}
7198
selected={selectedChains || []}
7299
onChange={(chains) => {
73100
window.plausible("filterChange", {
@@ -86,13 +113,6 @@ export function PoolsList(): ReactElement {
86113
},
87114
});
88115
}}
89-
displayValue={
90-
selectedChains?.length === 1
91-
? appConfig.chains[selectedChains[0]].name
92-
: `${
93-
selectedChains?.length || filters?.chains.length
94-
} chains`
95-
}
96116
searchEnabled
97117
options={filters.chains.map(({ chain, count }) => {
98118
return {
@@ -111,7 +131,19 @@ export function PoolsList(): ReactElement {
111131
{/* Assets filter */}
112132
{filters && filters.assets.length > 1 && (
113133
<MultiSelect
114-
title="Filter by deposit asset"
134+
button={
135+
<MultiSelectButton
136+
title="Filter by deposit asset"
137+
className="h-full py-2 sm:py-0"
138+
>
139+
{selectedAssets?.length === 1
140+
? selectedAssets[0]
141+
: `${
142+
selectedAssets?.length || filters.assets.length
143+
} assets`}
144+
<ChevronDownIcon className="hidden size-5 sm:block" />
145+
</MultiSelectButton>
146+
}
115147
selected={selectedAssets || []}
116148
onChange={(assets) => {
117149
window.plausible("filterChange", {
@@ -130,13 +162,6 @@ export function PoolsList(): ReactElement {
130162
},
131163
});
132164
}}
133-
displayValue={
134-
selectedAssets?.length === 1
135-
? selectedAssets[0]
136-
: `${
137-
selectedAssets?.length || filters.assets.length
138-
} assets`
139-
}
140165
searchEnabled
141166
options={filters.assets.map(({ asset, count }) => {
142167
return {
@@ -151,6 +176,39 @@ export function PoolsList(): ReactElement {
151176
/>
152177
)}
153178

179+
{/* Hide low TVL filter */}
180+
<span className="daisy-badge flex h-auto items-center self-stretch border-gray-600 py-2 sm:py-1">
181+
<label className="flex h-full cursor-pointer flex-col content-center items-center gap-2 sm:flex-row">
182+
<span className="daisy-label-text text-nowrap text-center">
183+
Hide low TVL
184+
</span>
185+
<input
186+
type="checkbox"
187+
title={`Hide pools with less than ${lowTvlThreshold.format()} in TVL`}
188+
className="daisy-toggle daisy-toggle-sm"
189+
defaultChecked={hideLowTvl}
190+
onChange={(e) => {
191+
window.plausible("filterChange", {
192+
props: {
193+
name: "Hide low TVL",
194+
value: String(e.target.checked),
195+
connectedWallet: account,
196+
},
197+
});
198+
navigate({
199+
search: (current) => {
200+
return {
201+
...current,
202+
hideLowTvl: e.target.checked,
203+
};
204+
},
205+
});
206+
}}
207+
/>
208+
</label>
209+
</span>
210+
211+
{/* Matching pools count */}
154212
<span className="daisy-badge hidden h-auto items-center self-stretch text-neutral-content sm:flex">
155213
{pools.length}
156214
{pools.length === 1 ? " pool" : " pools"}
@@ -164,7 +222,7 @@ export function PoolsList(): ReactElement {
164222
role="button"
165223
title="Sort by"
166224
className={classNames(
167-
"daisy-btn daisy-btn-outline daisy-btn-sm flex items-center justify-center border-gray-600",
225+
"daisy-btn daisy-btn-outline daisy-btn-sm flex h-full items-center justify-center border-gray-600 py-2 sm:py-0",
168226
{
169227
"daisy-btn-disabled": !isSortingEnabled,
170228
},

apps/hyperdrive-trading/src/ui/markets/hooks/usePoolsList.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fixed } from "@delvtech/fixed-point-wasm";
1+
import { fixed, FixedPoint } from "@delvtech/fixed-point-wasm";
22
import {
33
AnyReward,
44
AppConfig,
@@ -48,9 +48,14 @@ type SortOption = (typeof sortOptions)[number];
4848
export function usePoolsList({
4949
selectedAssets,
5050
selectedChains,
51+
hideLowTvl,
5152
}: {
5253
selectedAssets: string[] | undefined;
5354
selectedChains: number[] | undefined;
55+
hideLowTvl?: {
56+
enabled: boolean | undefined;
57+
threshold: FixedPoint;
58+
};
5459
}): {
5560
pools: (HyperdriveConfig & { rewardsAmount?: AnyReward[] })[] | undefined;
5661
filters: PoolListFilters | undefined;
@@ -76,10 +81,11 @@ export function usePoolsList({
7681
const chainId = useChainId();
7782
const isTestnet = isTestnetChain(chainId);
7883

79-
// Sorting is disabled any time we're fetching data. This is because sorting
80-
// requires fetching a significant amount of data, and we want the List to load
81-
// as fast as possible. Instead, the individual PoolRow components are
82-
// responsible for fetching the specific data they need.
84+
// Sorting and filtering based on async data is disabled any time we're
85+
// fetching data. This is because sorting requires fetching a significant
86+
// amount of data, and we want the List to load as fast as possible. Instead,
87+
// the individual PoolRow components are responsible for fetching the specific
88+
// data they need.
8389
const [sortOption, setSortOption] = useState<SortOption | undefined>();
8490
const isFetching = useIsFetching({
8591
// don't treat stale queries as fetching, since we have data we can show
@@ -90,6 +96,9 @@ export function usePoolsList({
9096
pools: selectedPools,
9197
enabled: isSortingEnabled,
9298
sortOption,
99+
filters: {
100+
tvl: hideLowTvl?.enabled ? hideLowTvl?.threshold : undefined,
101+
},
93102
});
94103

95104
return {
@@ -101,14 +110,19 @@ export function usePoolsList({
101110
isSortingEnabled,
102111
};
103112
}
113+
104114
function useSortedPools({
105115
pools,
106116
enabled,
107117
sortOption,
118+
filters,
108119
}: {
109120
pools: HyperdriveConfig[] | undefined;
110121
enabled: boolean;
111122
sortOption: SortOption | undefined;
123+
filters: {
124+
tvl?: FixedPoint;
125+
};
112126
}) {
113127
const appConfig = useAppConfigForConnectedChain();
114128
const queryEnabled = !!pools && enabled;
@@ -161,7 +175,15 @@ function useSortedPools({
161175
}
162176
: undefined,
163177
select: (poolsWithData) => {
164-
return poolsWithData
178+
const filteredPools = !!filters.tvl
179+
? poolsWithData.filter((pool) => {
180+
const tvl = pool.tvl.fiat
181+
? fixed(pool.tvl.fiat)
182+
: fixed(pool.tvl.base, pool.hyperdrive.decimals);
183+
return tvl.gte(filters.tvl!);
184+
})
185+
: poolsWithData;
186+
return filteredPools
165187
.toSorted((a, b) => {
166188
switch (sortOption) {
167189
case "Chain":

apps/hyperdrive-trading/src/ui/routes/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export const Route = createFileRoute(LANDING_ROUTE)({
1313
validateSearch: z.object({
1414
chains: z.array(z.number()).optional(),
1515
assets: z.array(z.string()).optional(),
16+
hideLowTvl: z.boolean().optional(),
1617
}),
1718
});

0 commit comments

Comments
 (0)