Skip to content

Commit 9f595ae

Browse files
authored
Merge pull request #3187 from pyth-network/bduran/UI-324-insights-hub-search-popover
feat(insights-hub): made dialog stick around if new tab is opening
2 parents a85b88d + ce43465 commit 9f595ae

File tree

6 files changed

+65
-6
lines changed

6 files changed

+65
-6
lines changed

apps/insights/src/components/Root/search-button.tsx

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Virtualizer,
1515
} from "@pythnetwork/component-library/Virtualizer";
1616
import type { Button as UnstyledButton } from "@pythnetwork/component-library/unstyled/Button";
17+
import type { ListBoxItemProps } from "@pythnetwork/component-library/unstyled/ListBox";
1718
import {
1819
ListBox,
1920
ListBoxItem,
@@ -22,7 +23,7 @@ import { useDrawer } from "@pythnetwork/component-library/useDrawer";
2223
import { useLogger } from "@pythnetwork/component-library/useLogger";
2324
import { matchSorter } from "match-sorter";
2425
import type { ReactNode } from "react";
25-
import { useCallback, useEffect, useMemo, useState } from "react";
26+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2627

2728
import { Cluster, ClusterToName } from "../../services/pyth";
2829
import { AssetClassBadge } from "../AssetClassBadge";
@@ -132,16 +133,62 @@ const SearchDialogContents = ({
132133
feeds,
133134
publishers,
134135
}: SearchDialogContentsProps) => {
136+
/** hooks */
135137
const drawer = useDrawer();
136138
const logger = useLogger();
139+
140+
/** refs */
141+
const closeDrawerDebounceRef = useRef<NodeJS.Timeout | undefined>(undefined);
142+
const openTabModifierActiveRef = useRef(false);
143+
const middleMousePressedRef = useRef(false);
144+
145+
/** state */
137146
const [search, setSearch] = useState("");
138147
const [type, setType] = useState<ResultType | "">("");
148+
149+
/** callbacks */
139150
const closeDrawer = useCallback(() => {
140-
drawer.close().catch((error: unknown) => {
141-
logger.error(error);
142-
});
151+
if (closeDrawerDebounceRef.current) {
152+
clearTimeout(closeDrawerDebounceRef.current);
153+
closeDrawerDebounceRef.current = undefined;
154+
}
155+
156+
// we debounce the drawer closure because, if we don't,
157+
// mobile browsers (at least on iOS) may squash the native <a />
158+
// click, resulting in no price feed loading for the user
159+
closeDrawerDebounceRef.current = setTimeout(() => {
160+
drawer.close().catch((error: unknown) => {
161+
logger.error(error);
162+
});
163+
}, 250);
143164
}, [drawer, logger]);
165+
const onLinkPointerDown = useCallback<
166+
NonNullable<ListBoxItemProps<never>["onPointerDown"]>
167+
>((e) => {
168+
const { button, ctrlKey, metaKey } = e;
144169

170+
middleMousePressedRef.current = button === 1;
171+
172+
// on press is too abstracted and doesn't give us the native event
173+
// for determining if the user clicked their middle mouse button,
174+
// so we need to use the native onClick directly
175+
middleMousePressedRef.current = button === 1;
176+
openTabModifierActiveRef.current = metaKey || ctrlKey;
177+
}, []);
178+
const onLinkPointerUp = useCallback<
179+
NonNullable<ListBoxItemProps<never>["onPointerUp"]>
180+
>(() => {
181+
const userWantsNewTab =
182+
middleMousePressedRef.current || openTabModifierActiveRef.current;
183+
184+
// // they want a new tab, the search popover stays open
185+
if (!userWantsNewTab) closeDrawer();
186+
187+
middleMousePressedRef.current = false;
188+
openTabModifierActiveRef.current = false;
189+
}, [closeDrawer]);
190+
191+
/** memos */
145192
const results = useMemo(() => {
146193
const filteredFeeds = matchSorter(feeds, search, {
147194
keys: ["displaySymbol", "symbol", "description", "priceAccount"],
@@ -168,6 +215,7 @@ const SearchDialogContents = ({
168215
}
169216
return [...filteredFeeds, ...filteredPublishers];
170217
}, [feeds, publishers, search, type]);
218+
171219
return (
172220
<div className={styles.searchDialogContents}>
173221
<div className={styles.searchBar}>
@@ -231,13 +279,14 @@ const SearchDialogContents = ({
231279
: (result.name ?? result.publisherKey)
232280
}
233281
className={styles.item ?? ""}
234-
onAction={closeDrawer}
235282
href={
236283
result.type === ResultType.PriceFeed
237284
? `/price-feeds/${encodeURIComponent(result.symbol)}`
238285
: `/publishers/${ClusterToName[result.cluster]}/${encodeURIComponent(result.publisherKey)}`
239286
}
240287
data-is-first={result.id === results[0]?.id ? "" : undefined}
288+
onPointerDown={onLinkPointerDown}
289+
onPointerUp={onLinkPointerUp}
241290
>
242291
<div className={styles.smallScreen}>
243292
{result.type === ResultType.PriceFeed ? (

packages/component-library/src/unstyled/ListBox/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { usePrefetch } from "../../use-prefetch.js";
77

88
export { ListBox, ListBoxSection } from "react-aria-components";
99

10-
type ListBoxItemProps<T extends object> = ComponentProps<
10+
export type ListBoxItemProps<T extends object> = ComponentProps<
1111
typeof BaseListBoxItem<T>
1212
> & {
1313
prefetch?: Parameters<typeof usePrefetch>[0]["prefetch"];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineJestConfigForNextJs } from "@pythnetwork/jest-config/define-next-config";
2+
3+
export default defineJestConfigForNextJs();

packages/react-hooks/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
},
1515
"devDependencies": {
1616
"@cprussin/eslint-config": "catalog:",
17+
"@pythnetwork/jest-config": "workspace:",
1718
"@cprussin/tsconfig": "catalog:",
1819
"@types/react": "catalog:",
1920
"@types/react-dom": "catalog:",

packages/react-hooks/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"extends": "@cprussin/tsconfig/base.json",
3+
"compilerOptions": {
4+
"lib": ["DOM", "ESNext"]
5+
},
36
"exclude": ["node_modules", "dist"]
47
}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)