feat: add OverlayScrollbars for improved scrolling UI

Replace native ScrollArea with OverlayScrollbars library in
AbstractCard raw view and main page layout. Provides consistent,
customizable scrollbars with auto-hide behavior across the
application.
This commit is contained in:
2025-10-23 10:18:52 -05:00
parent d1b27a734a
commit a2a83e9593
6 changed files with 119 additions and 71 deletions

View File

@@ -35,6 +35,8 @@
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"next": "^15.5.6", "next": "^15.5.6",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"overlayscrollbars": "^2.12.0",
"overlayscrollbars-react": "^0.5.6",
"react": "19.2.0", "react": "19.2.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"react-hook-form": "^7.42.1", "react-hook-form": "^7.42.1",

22
pnpm-lock.yaml generated
View File

@@ -44,6 +44,12 @@ importers:
next-themes: next-themes:
specifier: ^0.4.6 specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
overlayscrollbars:
specifier: ^2.12.0
version: 2.12.0
overlayscrollbars-react:
specifier: ^0.5.6
version: 0.5.6(overlayscrollbars@2.12.0)(react@19.2.0)
react: react:
specifier: 19.2.0 specifier: 19.2.0
version: 19.2.0 version: 19.2.0
@@ -3328,6 +3334,15 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
overlayscrollbars-react@0.5.6:
resolution: {integrity: sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==}
peerDependencies:
overlayscrollbars: ^2.0.0
react: '>=16.8.0'
overlayscrollbars@2.12.0:
resolution: {integrity: sha512-mWJ5MOkcZ/ljHwfLw8+bN0V9ziGCoNoqULcp994j5DTGNQvnkWKWkA7rnO29Kyew5AoHxUnJ4Ndqfcl0HSQjXg==}
own-keys@1.0.1: own-keys@1.0.1:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -7363,6 +7378,13 @@ snapshots:
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.5 word-wrap: 1.2.5
overlayscrollbars-react@0.5.6(overlayscrollbars@2.12.0)(react@19.2.0):
dependencies:
overlayscrollbars: 2.12.0
react: 19.2.0
overlayscrollbars@2.12.0: {}
own-keys@1.0.1: own-keys@1.0.1:
dependencies: dependencies:
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0

View File

@@ -2,7 +2,8 @@ import type { FunctionComponent, ReactNode } from "react";
import React from "react"; import React from "react";
import { useBoolean } from "usehooks-ts"; import { useBoolean } from "usehooks-ts";
import { Link2Icon, CodeIcon, DownloadIcon, ClipboardIcon } from "@radix-ui/react-icons"; import { Link2Icon, CodeIcon, DownloadIcon, ClipboardIcon } from "@radix-ui/react-icons";
import { Card, Flex, Box, IconButton, Code, ScrollArea, Tooltip } from "@radix-ui/themes"; import { Card, Flex, Box, IconButton, Code, Tooltip } from "@radix-ui/themes";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
type AbstractCardProps = { type AbstractCardProps = {
children?: ReactNode; children?: ReactNode;
@@ -125,7 +126,16 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
)} )}
<Box p="4"> <Box p="4">
{showRaw ? ( {showRaw ? (
<ScrollArea type="auto" scrollbars="both" style={{ maxHeight: "40rem" }}> <OverlayScrollbarsComponent
defer
options={{
scrollbars: {
autoHide: "leave",
autoHideDelay: 1300,
},
}}
style={{ maxHeight: "40rem" }}
>
<Code <Code
variant="ghost" variant="ghost"
size="2" size="2"
@@ -137,7 +147,7 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
> >
{JSON.stringify(data, null, 4)} {JSON.stringify(data, null, 4)}
</Code> </Code>
</ScrollArea> </OverlayScrollbarsComponent>
) : ( ) : (
children children
)} )}

View File

@@ -5,6 +5,7 @@ import { type AppType } from "next/dist/shared/lib/utils";
import "@fontsource-variable/inter"; import "@fontsource-variable/inter";
import "@fontsource/ibm-plex-mono/400.css"; import "@fontsource/ibm-plex-mono/400.css";
import "@radix-ui/themes/styles.css"; import "@radix-ui/themes/styles.css";
import "overlayscrollbars/overlayscrollbars.css";
import "@/styles/globals.css"; import "@/styles/globals.css";
import { DateFormatProvider } from "@/contexts/DateFormatContext"; import { DateFormatProvider } from "@/contexts/DateFormatContext";

View File

@@ -10,6 +10,7 @@ import { ThemeToggle } from "@/components/ThemeToggle";
import { Maybe } from "true-myth"; import { Maybe } from "true-myth";
import { Flex, Container, Section, Text, Link, IconButton } from "@radix-ui/themes"; import { Flex, Container, Section, Text, Link, IconButton } from "@radix-ui/themes";
import { GitHubLogoIcon } from "@radix-ui/react-icons"; import { GitHubLogoIcon } from "@radix-ui/react-icons";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
const Index: NextPage = () => { const Index: NextPage = () => {
const { error, setTarget, setTargetType, submit, currentType } = useLookup(); const { error, setTarget, setTargetType, submit, currentType } = useLookup();
@@ -38,6 +39,16 @@ const Index: NextPage = () => {
content="xevion, rdap, whois, rdap, domain name, dns, ip address" content="xevion, rdap, whois, rdap, domain name, dns, ip address"
/> />
</Head> </Head>
<OverlayScrollbarsComponent
defer
options={{
scrollbars: {
autoHide: "leave",
autoHideDelay: 1300,
},
}}
style={{ height: "100vh" }}
>
<Flex <Flex
asChild asChild
justify="between" justify="between"
@@ -110,6 +121,7 @@ const Index: NextPage = () => {
) : null} ) : null}
</Section> </Section>
</Container> </Container>
</OverlayScrollbarsComponent>
</> </>
); );
}; };

View File

@@ -157,6 +157,7 @@ const LookupInput: FunctionComponent<LookupInputProps> = ({
size="3" size="3"
placeholder={placeholders[selected]} placeholder={placeholders[selected]}
disabled={isLoading} disabled={isLoading}
autoFocus
onFocus={() => { onFocus={() => {
isFocusedRef.current = true; isFocusedRef.current = true;
}} }}