Replace global scrollbars with themed overlay scroll areas

This commit is contained in:
Mike Cao committed 2026-08-07 15:31:14 -07:00
1 parent 85c4c1863d
commit 8a05dfee58
7 files changed
+114 -78

No files matched your search

+3 -2
View File
@@ -13,6 +13,7 @@ import { SettingsNav } from '@/app/(main)/settings/SettingsNav';
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { OverlayScrollArea } from '@/components/common/OverlayScrollArea';
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
import {
Globe,
@@ -103,7 +104,7 @@ export function SideNav(props: any) {
<PanelButton />
</Row>
</Row>
<Column flexGrow="1" minHeight="0" style={{ overflowY: 'auto', overflowX: 'hidden' }}>
<OverlayScrollArea style={{ flexGrow: 1, minHeight: 0 }}>
{websiteId ? (
<WebsiteNav websiteId={websiteId} isCollapsed={isCollapsed} />
) : pathname.includes('/settings') ? (
@@ -147,7 +148,7 @@ export function SideNav(props: any) {
})}
</Column>
)}
</Column>
</OverlayScrollArea>
<Row
paddingTop="2"
width="100%"
@@ -1,5 +1,6 @@
import { Column, Grid, Row } from '@umami/react-zen';
import { WebsiteExpandedMenu } from '@/app/(main)/websites/[websiteId]/WebsiteExpandedMenu';
import { OverlayScrollArea } from '@/components/common/OverlayScrollArea';
import { useMessages, useNavigation } from '@/components/hooks';
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
import { MetricsExpandedTable } from '@/components/metrics/MetricsExpandedTable';
@@ -40,9 +41,12 @@ export function WebsiteExpandedView({
gap="6"
border="right"
paddingRight="3"
overflow="auto"
minHeight="0"
overflow="hidden"
>
<WebsiteExpandedMenu excludedIds={excludedIds} />
<OverlayScrollArea style={{ height: '100%' }}>
<WebsiteExpandedMenu excludedIds={excludedIds} />
</OverlayScrollArea>
</Column>
<Column id="metrics-expanded-table" overflow="hidden">
<MetricsExpandedTable title={title} type={view} websiteId={websiteId} onClose={onClose} />
-23
View File
@@ -18,29 +18,6 @@ html[style*="padding-right"] {
padding-right: 0 !important;
}
::-webkit-scrollbar {
width: 15px;
background: transparent;
}
::-webkit-scrollbar-track {
border: 7px solid rgba(0, 0, 0, 0);
background-color: var(--border-default);
background-clip: padding-box;
}
::-webkit-scrollbar-thumb {
border: 7px solid rgba(0, 0, 0, 0);
background-color: var(--text-disabled);
border-radius: var(--border-radius-full);
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
}
/* Fix autofill background color to match theme */
input:is(:-webkit-autofill, :autofill),
input:is(:-webkit-autofill, :autofill):hover,
+3 -3
View File
@@ -52,17 +52,17 @@ export function NavMenu({
};
return (
<Column gap overflowY="auto" justifyContent="space-between" position="sticky">
<Column gap justifyContent="space-between" position="sticky">
{title && (
<Row padding>
<Heading size="lg">{title}</Heading>
</Row>
)}
<Column gap="6" {...props}>
<Column gap="3" {...props}>
{items?.map(({ label, items }, index) => {
if (label) {
return (
<Column key={`${label}${index}`} gap="1" marginBottom="3" minHeight="40px">
<Column key={`${label}${index}`} gap="1" minHeight="40px">
<Row paddingX="3" marginTop="2">
<Text weight="bold">{label}</Text>
</Row>
@@ -0,0 +1,45 @@
.root {
--overlay-scrollbar-track: var(--gray-50);
--overlay-scrollbar-thumb: var(--gray-300);
--overlay-scrollbar-thumb-hover: var(--gray-400);
}
.root > [data-orientation="vertical"] {
width: 4px;
margin: 16px 2px;
background-color: var(--overlay-scrollbar-track);
}
.root > [data-orientation="horizontal"] {
height: 4px;
margin: 2px 16px;
background-color: var(--overlay-scrollbar-track);
}
.root > [data-orientation] > div {
position: relative;
background-color: var(--overlay-scrollbar-thumb);
}
.root > [data-orientation] > div:hover,
.root > [data-orientation] > div:active {
background-color: var(--overlay-scrollbar-thumb-hover);
}
.root > [data-orientation="vertical"] > div::before {
position: absolute;
inset: 0 -4px;
content: "";
}
.root > [data-orientation="horizontal"] > div::before {
position: absolute;
inset: -4px 0;
content: "";
}
:global([data-theme="dark"]) .root {
--overlay-scrollbar-track: var(--gray-800);
--overlay-scrollbar-thumb: var(--gray-600);
--overlay-scrollbar-thumb-hover: var(--gray-500);
}
@@ -0,0 +1,6 @@
import { cn, type ScrollAreaProps, ScrollArea as ZenScrollArea } from '@umami/react-zen';
import styles from './OverlayScrollArea.module.css';
export function OverlayScrollArea({ className, ...props }: ScrollAreaProps) {
return <ZenScrollArea className={cn(styles.root, className)} {...props} />;
}
+51 -48
View File
@@ -1,6 +1,7 @@
import { Button, Column, DataColumn, DataTable, Icon, Row, SearchField } from '@umami/react-zen';
import { type ReactNode, useState } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { OverlayScrollArea } from '@/components/common/OverlayScrollArea';
import { useMessages, useWebsiteExpandedMetricsQuery } from '@/components/hooks';
import { X } from '@/components/icons';
import { DownloadButton } from '@/components/input/DownloadButton';
@@ -76,55 +77,57 @@ export function MetricsExpandedTable({
height="100%"
loadingIcon="spinner"
>
<Column overflow="auto" minHeight="0" height="100%" paddingRight="3">
{items && (
<DataTable data={items}>
<DataColumn id="label" label={title} width="minmax(200px, 2fr)" align="start">
{row => (
<Row overflow="hidden">
<MetricLabel type={type} data={row} />
</Row>
)}
</DataColumn>
<DataColumn id="visitors" label={t(labels.visitors)} align="end" width="120px">
{row => row?.visitors?.toLocaleString()}
</DataColumn>
<DataColumn id="visits" label={t(labels.visits)} align="end" width="120px">
{row => row?.visits?.toLocaleString()}
</DataColumn>
<DataColumn id="pageviews" label={t(labels.views)} align="end" width="120px">
{row => row?.pageviews?.toLocaleString()}
</DataColumn>
{showBounceDuration && [
<DataColumn
key="bounceRate"
id="bounceRate"
label={t(labels.bounceRate)}
align="end"
width="120px"
>
{row => {
const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100;
return `${Math.round(+n)}%`;
}}
</DataColumn>,
<OverlayScrollArea orientation="both" style={{ height: '100%', minHeight: 0 }}>
<Column paddingRight="3">
{items && (
<DataTable data={items}>
<DataColumn id="label" label={title} width="minmax(200px, 2fr)" align="start">
{row => (
<Row overflow="hidden">
<MetricLabel type={type} data={row} />
</Row>
)}
</DataColumn>
<DataColumn id="visitors" label={t(labels.visitors)} align="end" width="120px">
{row => row?.visitors?.toLocaleString()}
</DataColumn>
<DataColumn id="visits" label={t(labels.visits)} align="end" width="120px">
{row => row?.visits?.toLocaleString()}
</DataColumn>
<DataColumn id="pageviews" label={t(labels.views)} align="end" width="120px">
{row => row?.pageviews?.toLocaleString()}
</DataColumn>
{showBounceDuration && [
<DataColumn
key="bounceRate"
id="bounceRate"
label={t(labels.bounceRate)}
align="end"
width="120px"
>
{row => {
const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100;
return `${Math.round(+n)}%`;
}}
</DataColumn>,
<DataColumn
key="visitDuration"
id="visitDuration"
label={t(labels.visitDuration)}
align="end"
width="120px"
>
{row => {
const n = row?.totaltime / row?.visits;
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
}}
</DataColumn>,
]}
</DataTable>
)}
</Column>
<DataColumn
key="visitDuration"
id="visitDuration"
label={t(labels.visitDuration)}
align="end"
width="120px"
>
{row => {
const n = row?.totaltime / row?.visits;
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
}}
</DataColumn>,
]}
</DataTable>
)}
</Column>
</OverlayScrollArea>
</LoadingPanel>
</>
);