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",
"next": "^15.5.6",
"next-themes": "^0.4.6",
"overlayscrollbars": "^2.12.0",
"overlayscrollbars-react": "^0.5.6",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-hook-form": "^7.42.1",

22
pnpm-lock.yaml generated
View File

@@ -44,6 +44,12 @@ importers:
next-themes:
specifier: ^0.4.6
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:
specifier: 19.2.0
version: 19.2.0
@@ -3328,6 +3334,15 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
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:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
@@ -7363,6 +7378,13 @@ snapshots:
type-check: 0.4.0
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:
dependencies:
get-intrinsic: 1.3.0

View File

@@ -2,7 +2,8 @@ import type { FunctionComponent, ReactNode } from "react";
import React from "react";
import { useBoolean } from "usehooks-ts";
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 = {
children?: ReactNode;
@@ -125,7 +126,16 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
)}
<Box p="4">
{showRaw ? (
<ScrollArea type="auto" scrollbars="both" style={{ maxHeight: "40rem" }}>
<OverlayScrollbarsComponent
defer
options={{
scrollbars: {
autoHide: "leave",
autoHideDelay: 1300,
},
}}
style={{ maxHeight: "40rem" }}
>
<Code
variant="ghost"
size="2"
@@ -137,7 +147,7 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
>
{JSON.stringify(data, null, 4)}
</Code>
</ScrollArea>
</OverlayScrollbarsComponent>
) : (
children
)}

View File

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

View File

@@ -10,6 +10,7 @@ import { ThemeToggle } from "@/components/ThemeToggle";
import { Maybe } from "true-myth";
import { Flex, Container, Section, Text, Link, IconButton } from "@radix-ui/themes";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
const Index: NextPage = () => {
const { error, setTarget, setTargetType, submit, currentType } = useLookup();
@@ -38,78 +39,89 @@ const Index: NextPage = () => {
content="xevion, rdap, whois, rdap, domain name, dns, ip address"
/>
</Head>
<Flex
asChild
justify="between"
align="center"
px="5"
py="4"
style={{
borderBottom: "1px solid var(--gray-a5)",
<OverlayScrollbarsComponent
defer
options={{
scrollbars: {
autoHide: "leave",
autoHideDelay: 1300,
},
}}
style={{ height: "100vh" }}
>
<nav>
<Text size="5" weight="medium">
<Link href="https://github.com/Xevion/rdap" color="gray" highContrast>
rdap
</Link>
<Link href="https://xevion.dev" color="gray">
.xevion.dev
</Link>
</Text>
<Flex gap="4" align="center">
<IconButton
asChild
size="3"
variant="ghost"
aria-label="View on GitHub"
title="View on GitHub"
>
<a
href="https://github.com/Xevion/rdap"
target="_blank"
rel="noopener noreferrer"
<Flex
asChild
justify="between"
align="center"
px="5"
py="4"
style={{
borderBottom: "1px solid var(--gray-a5)",
}}
>
<nav>
<Text size="5" weight="medium">
<Link href="https://github.com/Xevion/rdap" color="gray" highContrast>
rdap
</Link>
<Link href="https://xevion.dev" color="gray">
.xevion.dev
</Link>
</Text>
<Flex gap="4" align="center">
<IconButton
asChild
size="3"
variant="ghost"
aria-label="View on GitHub"
title="View on GitHub"
>
<GitHubLogoIcon width="22" height="22" />
</a>
</IconButton>
<ThemeToggle />
</Flex>
</nav>
</Flex>
<Container size="3" px="5">
<Section size="2">
<LookupInput
isLoading={isLoading}
detectedType={currentType}
onChange={({ target, targetType }) => {
setTarget(target);
setTargetType(targetType);
}}
onSubmit={async function (props) {
try {
setLoading(true);
setResponse(await submit(props));
setLoading(false);
} catch (e) {
console.error(e);
setResponse(Maybe.nothing());
setLoading(false);
}
}}
/>
{error != null ? (
<ErrorCard
title="An error occurred while performing a lookup."
description={error}
className="mb-2"
<a
href="https://github.com/Xevion/rdap"
target="_blank"
rel="noopener noreferrer"
>
<GitHubLogoIcon width="22" height="22" />
</a>
</IconButton>
<ThemeToggle />
</Flex>
</nav>
</Flex>
<Container size="3" px="5">
<Section size="2">
<LookupInput
isLoading={isLoading}
detectedType={currentType}
onChange={({ target, targetType }) => {
setTarget(target);
setTargetType(targetType);
}}
onSubmit={async function (props) {
try {
setLoading(true);
setResponse(await submit(props));
setLoading(false);
} catch (e) {
console.error(e);
setResponse(Maybe.nothing());
setLoading(false);
}
}}
/>
) : null}
{response.isJust ? (
<Generic url={response.value.url} data={response.value.data} />
) : null}
</Section>
</Container>
{error != null ? (
<ErrorCard
title="An error occurred while performing a lookup."
description={error}
className="mb-2"
/>
) : null}
{response.isJust ? (
<Generic url={response.value.url} data={response.value.data} />
) : null}
</Section>
</Container>
</OverlayScrollbarsComponent>
</>
);
};

View File

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