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 { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { IconLabel } from '@/components/common/IconLabel'; import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link'; import Link from '@/components/common/Link';
import { OverlayScrollArea } from '@/components/common/OverlayScrollArea';
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks'; import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
import { import {
Globe, Globe,
@@ -103,7 +104,7 @@ export function SideNav(props: any) {
<PanelButton /> <PanelButton />
</Row> </Row>
</Row> </Row>
<Column flexGrow="1" minHeight="0" style={{ overflowY: 'auto', overflowX: 'hidden' }}> <OverlayScrollArea style={{ flexGrow: 1, minHeight: 0 }}>
{websiteId ? ( {websiteId ? (
<WebsiteNav websiteId={websiteId} isCollapsed={isCollapsed} /> <WebsiteNav websiteId={websiteId} isCollapsed={isCollapsed} />
) : pathname.includes('/settings') ? ( ) : pathname.includes('/settings') ? (
@@ -147,7 +148,7 @@ export function SideNav(props: any) {
})} })}
</Column> </Column>
)} )}
</Column> </OverlayScrollArea>
<Row <Row
paddingTop="2" paddingTop="2"
width="100%" width="100%"
@@ -1,5 +1,6 @@
import { Column, Grid, Row } from '@umami/react-zen'; import { Column, Grid, Row } from '@umami/react-zen';
import { WebsiteExpandedMenu } from '@/app/(main)/websites/[websiteId]/WebsiteExpandedMenu'; import { WebsiteExpandedMenu } from '@/app/(main)/websites/[websiteId]/WebsiteExpandedMenu';
import { OverlayScrollArea } from '@/components/common/OverlayScrollArea';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { MobileMenuButton } from '@/components/input/MobileMenuButton'; import { MobileMenuButton } from '@/components/input/MobileMenuButton';
import { MetricsExpandedTable } from '@/components/metrics/MetricsExpandedTable'; import { MetricsExpandedTable } from '@/components/metrics/MetricsExpandedTable';
@@ -40,9 +41,12 @@ export function WebsiteExpandedView({
gap="6" gap="6"
border="right" border="right"
paddingRight="3" paddingRight="3"
overflow="auto" minHeight="0"
overflow="hidden"
> >
<WebsiteExpandedMenu excludedIds={excludedIds} /> <OverlayScrollArea style={{ height: '100%' }}>
<WebsiteExpandedMenu excludedIds={excludedIds} />
</OverlayScrollArea>
</Column> </Column>
<Column id="metrics-expanded-table" overflow="hidden"> <Column id="metrics-expanded-table" overflow="hidden">
<MetricsExpandedTable title={title} type={view} websiteId={websiteId} onClose={onClose} /> <MetricsExpandedTable title={title} type={view} websiteId={websiteId} onClose={onClose} />
-23
View File
@@ -18,29 +18,6 @@ html[style*="padding-right"] {
padding-right: 0 !important; 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 */ /* Fix autofill background color to match theme */
input:is(:-webkit-autofill, :autofill), input:is(:-webkit-autofill, :autofill),
input:is(:-webkit-autofill, :autofill):hover, input:is(:-webkit-autofill, :autofill):hover,
+3 -3
View File
@@ -52,17 +52,17 @@ export function NavMenu({
}; };
return ( return (
<Column gap overflowY="auto" justifyContent="space-between" position="sticky"> <Column gap justifyContent="space-between" position="sticky">
{title && ( {title && (
<Row padding> <Row padding>
<Heading size="lg">{title}</Heading> <Heading size="lg">{title}</Heading>
</Row> </Row>
)} )}
<Column gap="6" {...props}> <Column gap="3" {...props}>
{items?.map(({ label, items }, index) => { {items?.map(({ label, items }, index) => {
if (label) { if (label) {
return ( return (
<Column key={`${label}${index}`} gap="1" marginBottom="3" minHeight="40px"> <Column key={`${label}${index}`} gap="1" minHeight="40px">
<Row paddingX="3" marginTop="2"> <Row paddingX="3" marginTop="2">
<Text weight="bold">{label}</Text> <Text weight="bold">{label}</Text>
</Row> </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 { Button, Column, DataColumn, DataTable, Icon, Row, SearchField } from '@umami/react-zen';
import { type ReactNode, useState } from 'react'; import { type ReactNode, useState } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel'; import { LoadingPanel } from '@/components/common/LoadingPanel';
import { OverlayScrollArea } from '@/components/common/OverlayScrollArea';
import { useMessages, useWebsiteExpandedMetricsQuery } from '@/components/hooks'; import { useMessages, useWebsiteExpandedMetricsQuery } from '@/components/hooks';
import { X } from '@/components/icons'; import { X } from '@/components/icons';
import { DownloadButton } from '@/components/input/DownloadButton'; import { DownloadButton } from '@/components/input/DownloadButton';
@@ -76,55 +77,57 @@ export function MetricsExpandedTable({
height="100%" height="100%"
loadingIcon="spinner" loadingIcon="spinner"
> >
<Column overflow="auto" minHeight="0" height="100%" paddingRight="3"> <OverlayScrollArea orientation="both" style={{ height: '100%', minHeight: 0 }}>
{items && ( <Column paddingRight="3">
<DataTable data={items}> {items && (
<DataColumn id="label" label={title} width="minmax(200px, 2fr)" align="start"> <DataTable data={items}>
{row => ( <DataColumn id="label" label={title} width="minmax(200px, 2fr)" align="start">
<Row overflow="hidden"> {row => (
<MetricLabel type={type} data={row} /> <Row overflow="hidden">
</Row> <MetricLabel type={type} data={row} />
)} </Row>
</DataColumn> )}
<DataColumn id="visitors" label={t(labels.visitors)} align="end" width="120px"> </DataColumn>
{row => row?.visitors?.toLocaleString()} <DataColumn id="visitors" label={t(labels.visitors)} align="end" width="120px">
</DataColumn> {row => row?.visitors?.toLocaleString()}
<DataColumn id="visits" label={t(labels.visits)} align="end" width="120px"> </DataColumn>
{row => row?.visits?.toLocaleString()} <DataColumn id="visits" label={t(labels.visits)} align="end" width="120px">
</DataColumn> {row => row?.visits?.toLocaleString()}
<DataColumn id="pageviews" label={t(labels.views)} align="end" width="120px"> </DataColumn>
{row => row?.pageviews?.toLocaleString()} <DataColumn id="pageviews" label={t(labels.views)} align="end" width="120px">
</DataColumn> {row => row?.pageviews?.toLocaleString()}
{showBounceDuration && [ </DataColumn>
<DataColumn {showBounceDuration && [
key="bounceRate" <DataColumn
id="bounceRate" key="bounceRate"
label={t(labels.bounceRate)} id="bounceRate"
align="end" label={t(labels.bounceRate)}
width="120px" align="end"
> width="120px"
{row => { >
const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100; {row => {
return `${Math.round(+n)}%`; const n = (Math.min(row?.visits, row?.bounces) / row?.visits) * 100;
}} return `${Math.round(+n)}%`;
</DataColumn>, }}
</DataColumn>,
<DataColumn <DataColumn
key="visitDuration" key="visitDuration"
id="visitDuration" id="visitDuration"
label={t(labels.visitDuration)} label={t(labels.visitDuration)}
align="end" align="end"
width="120px" width="120px"
> >
{row => { {row => {
const n = row?.totaltime / row?.visits; const n = row?.totaltime / row?.visits;
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`; return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
}} }}
</DataColumn>, </DataColumn>,
]} ]}
</DataTable> </DataTable>
)} )}
</Column> </Column>
</OverlayScrollArea>
</LoadingPanel> </LoadingPanel>
</> </>
); );