Procházet zdrojové kódy

chore(insights-hub): ensured links continue working on mobile

benduran před 1 týdnem
rodič
revize
ce4346598e

+ 48 - 25
apps/insights/src/components/Root/search-button.tsx

@@ -14,13 +14,13 @@ import {
   Virtualizer,
 } from "@pythnetwork/component-library/Virtualizer";
 import type { Button as UnstyledButton } from "@pythnetwork/component-library/unstyled/Button";
+import type { ListBoxItemProps } from "@pythnetwork/component-library/unstyled/ListBox";
 import {
   ListBox,
   ListBoxItem,
 } from "@pythnetwork/component-library/unstyled/ListBox";
 import { useDrawer } from "@pythnetwork/component-library/useDrawer";
 import { useLogger } from "@pythnetwork/component-library/useLogger";
-import { useDetectBrowserInfo } from '@pythnetwork/react-hooks/use-detect-browser-info';
 import { matchSorter } from "match-sorter";
 import type { ReactNode } from "react";
 import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -58,9 +58,9 @@ type ResolvedSearchButtonProps = {
     averageScore?: number | undefined;
     cluster: Cluster;
   } & (
-      | { name: string; icon: ReactNode }
-      | { name?: undefined; icon?: undefined }
-    ))[];
+    | { name: string; icon: ReactNode }
+    | { name?: undefined; icon?: undefined }
+  ))[];
 };
 
 const ResolvedSearchButton = (props: ResolvedSearchButtonProps) => {
@@ -136,9 +136,9 @@ const SearchDialogContents = ({
   /** hooks */
   const drawer = useDrawer();
   const logger = useLogger();
-  const browserInfo = useDetectBrowserInfo();
 
   /** refs */
+  const closeDrawerDebounceRef = useRef<NodeJS.Timeout | undefined>(undefined);
   const openTabModifierActiveRef = useRef(false);
   const middleMousePressedRef = useRef(false);
 
@@ -146,12 +146,49 @@ const SearchDialogContents = ({
   const [search, setSearch] = useState("");
   const [type, setType] = useState<ResultType | "">("");
 
+  /** callbacks */
   const closeDrawer = useCallback(() => {
-    drawer.close().catch((error: unknown) => {
-      logger.error(error);
-    });
+    if (closeDrawerDebounceRef.current) {
+      clearTimeout(closeDrawerDebounceRef.current);
+      closeDrawerDebounceRef.current = undefined;
+    }
+
+    // we debounce the drawer closure because, if we don't,
+    // mobile browsers (at least on iOS) may squash the native <a />
+    // click, resulting in no price feed loading for the user
+    closeDrawerDebounceRef.current = setTimeout(() => {
+      drawer.close().catch((error: unknown) => {
+        logger.error(error);
+      });
+    }, 250);
   }, [drawer, logger]);
+  const onLinkPointerDown = useCallback<
+    NonNullable<ListBoxItemProps<never>["onPointerDown"]>
+  >((e) => {
+    const { button, ctrlKey, metaKey } = e;
+
+    middleMousePressedRef.current = button === 1;
+
+    // on press is too abstracted and doesn't give us the native event
+    // for determining if the user clicked their middle mouse button,
+    // so we need to use the native onClick directly
+    middleMousePressedRef.current = button === 1;
+    openTabModifierActiveRef.current = metaKey || ctrlKey;
+  }, []);
+  const onLinkPointerUp = useCallback<
+    NonNullable<ListBoxItemProps<never>["onPointerUp"]>
+  >(() => {
+    const userWantsNewTab =
+      middleMousePressedRef.current || openTabModifierActiveRef.current;
+
+    // // they want a new tab, the search popover stays open
+    if (!userWantsNewTab) closeDrawer();
+
+    middleMousePressedRef.current = false;
+    openTabModifierActiveRef.current = false;
+  }, [closeDrawer]);
 
+  /** memos */
   const results = useMemo(() => {
     const filteredFeeds = matchSorter(feeds, search, {
       keys: ["displaySymbol", "symbol", "description", "priceAccount"],
@@ -178,6 +215,7 @@ const SearchDialogContents = ({
     }
     return [...filteredFeeds, ...filteredPublishers];
   }, [feeds, publishers, search, type]);
+
   return (
     <div className={styles.searchDialogContents}>
       <div className={styles.searchBar}>
@@ -247,23 +285,8 @@ const SearchDialogContents = ({
                     : `/publishers/${ClusterToName[result.cluster]}/${encodeURIComponent(result.publisherKey)}`
                 }
                 data-is-first={result.id === results[0]?.id ? "" : undefined}
-                onPointerDown={e => {
-                  middleMousePressedRef.current = e.button === 1;
-                  // on press is too abstracted and doesn't give us the native event
-                  // for determining if the user clicked their middle mouse button,
-                  // so we need to use the native onClick directly
-                  middleMousePressedRef.current = e.button === 1;
-                  openTabModifierActiveRef.current = browserInfo?.isMacOS ? e.metaKey : e.ctrlKey;
-                }}
-                onPointerUp={() => {
-                  const userWantsNewTab = middleMousePressedRef.current || openTabModifierActiveRef.current;
-
-                  // they want a new tab, the search popover stays open
-                  if (!userWantsNewTab) closeDrawer();
-
-                  middleMousePressedRef.current = false;
-                  openTabModifierActiveRef.current = false;
-                }}
+                onPointerDown={onLinkPointerDown}
+                onPointerUp={onLinkPointerUp}
               >
                 <div className={styles.smallScreen}>
                   {result.type === ResultType.PriceFeed ? (

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

@@ -7,7 +7,7 @@ import { usePrefetch } from "../../use-prefetch.js";
 
 export { ListBox, ListBoxSection } from "react-aria-components";
 
-type ListBoxItemProps<T extends object> = ComponentProps<
+export type ListBoxItemProps<T extends object> = ComponentProps<
   typeof BaseListBoxItem<T>
 > & {
   prefetch?: Parameters<typeof usePrefetch>[0]["prefetch"];

+ 0 - 9
packages/react-hooks/package.json

@@ -21,7 +21,6 @@
     "eslint": "catalog:"
   },
   "dependencies": {
-    "ua-parser-js": "catalog:",
     "nuqs": "catalog:",
     "react": "catalog:",
     "react-dom": "catalog:"
@@ -44,14 +43,6 @@
       "types": "./dist/nuqs.d.ts",
       "default": "./dist/nuqs.mjs"
     },
-    "./use-detect-browser-info": {
-      "types": "./dist/use-detect-browser-info.d.ts",
-      "default": "./dist/use-detect-browser-info.mjs"
-    },
-    "./use-previous": {
-      "types": "./dist/use-previous.d.ts",
-      "default": "./dist/use-previous.mjs"
-    },
     "./package.json": "./package.json"
   }
 }

+ 0 - 81
packages/react-hooks/src/use-detect-browser-info.ts

@@ -1,81 +0,0 @@
-import { useEffect, useMemo, useRef, useState } from "react";
-import { UAParser } from "ua-parser-js";
-
-import { usePrevious } from "./use-previous.js";
-
-function safeGetUserAgent() {
-  // this guards against this blowing up in SSR
-  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-  return globalThis.window?.navigator?.userAgent ?? "";
-}
-
-type UseDetectBrowserInfoOpts = Partial<{
-  /**
-   * how often to check and see if the user agent has updated
-   */
-  checkInterval: number;
-}>;
-
-const DEFAULT_CHECK_INTERVAL = 1000; // one secondrt
-
-/**
- * returns relevant information about the user's browser, OS and Arch,
- * using the super popular ua-parser-js library:
- * npm i ua-parser-js
- */
-export function useDetectBrowserInfo(opts?: UseDetectBrowserInfoOpts) {
-  /** props */
-  const { checkInterval = DEFAULT_CHECK_INTERVAL } = opts ?? {};
-
-  /** state */
-  const [userAgent, setUserAgent] = useState(() => safeGetUserAgent());
-
-  /** hooks */
-  const prevUserAgent = usePrevious(userAgent);
-
-  /** refs */
-  const prevUserAgentRef = useRef(prevUserAgent);
-
-  /** memos */
-  const details = useMemo(
-    () => (userAgent ? UAParser(userAgent) : undefined),
-    [userAgent],
-  );
-
-  /** effects */
-  useEffect(() => {
-    prevUserAgentRef.current = prevUserAgent;
-  });
-
-  useEffect(() => {
-    // in case somebody is spoofing their user agent using
-    // some type of browser extension, we check the user agent periodically
-    // to see if it's changed, and if it has, we update what we have
-    const userAgentCheckInterval = setInterval(() => {
-      const ua = safeGetUserAgent();
-
-      if (ua !== prevUserAgentRef.current) {
-        setUserAgent(ua);
-      }
-    }, checkInterval);
-
-    return () => {
-      clearInterval(userAgentCheckInterval);
-    };
-  }, [checkInterval]);
-
-  return useMemo(() => {
-    if (!details) return;
-
-    const lowerOsName = details.os.name?.toLowerCase() ?? "";
-    const isMacOS = lowerOsName === "macos";
-    const isWindows = lowerOsName === "windows" || lowerOsName === "win";
-
-    return {
-      ...details,
-      isLinux: !isMacOS && !isWindows,
-      isMacOS,
-      isWindows,
-    };
-  }, []);
-}

+ 0 - 18
packages/react-hooks/src/use-previous.ts

@@ -1,18 +0,0 @@
-import { useEffect, useRef } from "react";
-
-/**
- * returns the n-1 value provided to it.
- * useful for comparing a current component
- * state value relative to a previous state value
- */
-export function usePrevious<T>(val: T): T | undefined {
-  /** refs */
-  const prevRef = useRef<T>(undefined);
-
-  /** effects */
-  useEffect(() => {
-    prevRef.current = val;
-  });
-
-  return prevRef.current;
-}

+ 0 - 31
pnpm-lock.yaml

@@ -318,9 +318,6 @@ catalogs:
     typescript:
       specifier: ^5.9.3
       version: 5.9.3
-    ua-parser-js:
-      specifier: ^2.0.6
-      version: 2.0.6
     vercel:
       specifier: ^41.4.1
       version: 41.4.1
@@ -2222,9 +2219,6 @@ importers:
       react-dom:
         specifier: 'catalog:'
         version: 19.1.0(react@19.1.0)
-      ua-parser-js:
-        specifier: 'catalog:'
-        version: 2.0.6
     devDependencies:
       '@cprussin/eslint-config':
         specifier: 'catalog:'
@@ -13482,9 +13476,6 @@ packages:
   detect-browser@5.3.0:
     resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==}
 
-  detect-europe-js@0.1.2:
-    resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
-
   detect-indent@7.0.2:
     resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==}
     engines: {node: '>=12.20'}
@@ -15943,9 +15934,6 @@ packages:
     resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
     engines: {node: '>= 0.4'}
 
-  is-standalone-pwa@0.1.1:
-    resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
-
   is-stream@2.0.1:
     resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
     engines: {node: '>=8'}
@@ -20715,17 +20703,10 @@ packages:
   u3@0.1.1:
     resolution: {integrity: sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==}
 
-  ua-is-frozen@0.1.2:
-    resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==}
-
   ua-parser-js@1.0.40:
     resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==}
     hasBin: true
 
-  ua-parser-js@2.0.6:
-    resolution: {integrity: sha512-EmaxXfltJaDW75SokrY4/lXMrVyXomE/0FpIIqP2Ctic93gK7rlme55Cwkz8l3YZ6gqf94fCU7AnIkidd/KXPg==}
-    hasBin: true
-
   uc.micro@2.1.0:
     resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
 
@@ -39479,8 +39460,6 @@ snapshots:
 
   detect-browser@5.3.0: {}
 
-  detect-europe-js@0.1.2: {}
-
   detect-indent@7.0.2: {}
 
   detect-libc@1.0.3:
@@ -43223,8 +43202,6 @@ snapshots:
     dependencies:
       call-bound: 1.0.4
 
-  is-standalone-pwa@0.1.1: {}
-
   is-stream@2.0.1: {}
 
   is-stream@4.0.1: {}
@@ -50122,16 +50099,8 @@ snapshots:
 
   u3@0.1.1: {}
 
-  ua-is-frozen@0.1.2: {}
-
   ua-parser-js@1.0.40: {}
 
-  ua-parser-js@2.0.6:
-    dependencies:
-      detect-europe-js: 0.1.2
-      is-standalone-pwa: 0.1.1
-      ua-is-frozen: 0.1.2
-
   uc.micro@2.1.0: {}
 
   ufo@1.5.4: {}

+ 0 - 1
pnpm-workspace.yaml

@@ -161,7 +161,6 @@ catalog:
   typedoc: ^0.26.8
   typescript: ^5.9.3
   turbo: ^2.5.8
-  ua-parser-js: ^2.0.6
   vercel: ^41.4.1
   viem: ^2.37.13
   wagmi: ^2.14.16