Adopt the shared ScrollArea for app-wide scrolling

This commit is contained in:
Steven RYDELL
2026-07-29 07:20:43 +02:00
parent bd16e64e51
commit c15cb22be4
6 changed files with 45 additions and 26 deletions
+7 -4
View File
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { ArrowLeft, ArrowRight, Check, Copy, Loader2, Rocket } from 'lucide-react'; import { ArrowLeft, ArrowRight, Check, Copy, Loader2, Rocket } from 'lucide-react';
import { useSchemaStore } from '@/stores/schemaStore'; import { useSchemaStore } from '@/stores/schemaStore';
@@ -403,13 +404,15 @@ export function BootstrapWizard() {
function WizardShell({ children }: { children: React.ReactNode }) { function WizardShell({ children }: { children: React.ReactNode }) {
return ( return (
<div className="flex min-h-screen flex-col bg-content-background"> <div className="flex h-screen flex-col bg-content-background">
<header className="flex items-center px-6 py-4 border-b bg-background"> <header className="flex items-center px-6 py-4 border-b bg-background">
<DefaultLogo /> <DefaultLogo />
</header> </header>
<main className="flex-1 overflow-auto p-6"> <ScrollArea role="main" className="flex-1">
<div className="mx-auto max-w-3xl">{children}</div> <div className="p-6">
</main> <div className="mx-auto max-w-3xl">{children}</div>
</div>
</ScrollArea>
</div> </div>
); );
} }
+5 -2
View File
@@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
@@ -1831,7 +1832,8 @@ function EnumMultiSelect({ enumName, items, onChange, readOnly, schema, minItems
className="h-8" className="h-8"
/> />
</div> </div>
<div className="max-h-60 overflow-y-auto p-2 space-y-1"> <ScrollArea viewportClassName="max-h-60">
<div className="p-2 space-y-1">
{filtered.length === 0 && ( {filtered.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-2">{t('field.noMatches', 'No matches')}</p> <p className="text-sm text-muted-foreground text-center py-2">{t('field.noMatches', 'No matches')}</p>
)} )}
@@ -1859,7 +1861,8 @@ function EnumMultiSelect({ enumName, items, onChange, readOnly, schema, minItems
</Label> </Label>
</div> </div>
))} ))}
</div> </div>
</ScrollArea>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
+4 -3
View File
@@ -12,6 +12,7 @@ import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useUIStore } from '@/stores/uiStore'; import { useUIStore } from '@/stores/uiStore';
import { useAccountStore } from '@/stores/accountStore'; import { useAccountStore } from '@/stores/accountStore';
@@ -342,8 +343,8 @@ export function Sidebar() {
onClick={() => setSidebarOpen(false)} onClick={() => setSidebarOpen(false)}
/> />
<aside className="fixed top-14 left-0 bottom-0 z-30 flex w-64 flex-col border-r bg-background"> <aside className="fixed top-14 left-0 bottom-0 z-30 flex w-64 flex-col border-r bg-background">
<div className="flex-1 overflow-y-auto py-2 [scrollbar-width:thin]"> <ScrollArea className="flex-1">
<nav ref={navRef} className="flex flex-col gap-0.5 px-2"> <nav ref={navRef} className="flex flex-col gap-0.5 px-2 py-2">
{layout.items.map((item) => ( {layout.items.map((item) => (
<SidebarTopItem <SidebarTopItem
key={'link' in item ? item.link.viewName : item.container.name} key={'link' in item ? item.link.viewName : item.container.name}
@@ -356,7 +357,7 @@ export function Sidebar() {
/> />
))} ))}
</nav> </nav>
</div> </ScrollArea>
{layouts.length > 1 && ( {layouts.length > 1 && (
<TooltipProvider> <TooltipProvider>
+4 -5
View File
@@ -11,6 +11,7 @@ import { Search } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Dialog, DialogContent } from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
const Command = React.forwardRef< const Command = React.forwardRef<
React.ComponentRef<typeof CommandPrimitive>, React.ComponentRef<typeof CommandPrimitive>,
@@ -61,11 +62,9 @@ const CommandList = React.forwardRef<
React.ComponentRef<typeof CommandPrimitive.List>, React.ComponentRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.List <ScrollArea viewportClassName="max-h-[300px]">
ref={ref} <CommandPrimitive.List ref={ref} className={cn('overflow-x-hidden', className)} {...props} />
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)} </ScrollArea>
{...props}
/>
)); ));
CommandList.displayName = CommandPrimitive.List.displayName; CommandList.displayName = CommandPrimitive.List.displayName;
+10 -3
View File
@@ -11,10 +11,17 @@ import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef< const ScrollArea = React.forwardRef<
React.ComponentRef<typeof ScrollAreaPrimitive.Root>, React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & {
>(({ className, children, ...props }, ref) => ( // Height caps (max-h-*) must be applied to the Viewport — the actual
// scroller — because a percentage height resolves to auto when the
// Root's height is content-driven, which would break scrolling.
viewportClassName?: string;
}
>(({ className, viewportClassName, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root ref={ref} className={cn('relative overflow-hidden', className)} {...props}> <ScrollAreaPrimitive.Root ref={ref} className={cn('relative overflow-hidden', className)} {...props}>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport> <ScrollAreaPrimitive.Viewport className={cn('h-full w-full rounded-[inherit]', viewportClassName)}>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
+15 -9
View File
@@ -16,6 +16,7 @@ import { setLocale } from '@/i18n';
import { TopBar } from '@/components/layout/TopBar'; import { TopBar } from '@/components/layout/TopBar';
import { Sidebar } from '@/components/layout/Sidebar'; import { Sidebar } from '@/components/layout/Sidebar';
import { MainContent } from '@/components/layout/MainContent'; import { MainContent } from '@/components/layout/MainContent';
import { ScrollArea } from '@/components/ui/scroll-area';
import { ErrorBoundary } from '@/components/layout/ErrorBoundary'; import { ErrorBoundary } from '@/components/layout/ErrorBoundary';
import { LoadingFallback } from '@/components/common/LoadingFallback'; import { LoadingFallback } from '@/components/common/LoadingFallback';
import { import {
@@ -249,20 +250,25 @@ export default function AdminPanel() {
); );
} }
// The shell is viewport-height so the main area scrolls internally through
// the shared ScrollArea instead of scrolling the whole window.
return ( return (
<div className="flex min-h-screen flex-col"> <div className="flex h-screen flex-col">
<TopBar /> <TopBar />
<div className="flex flex-1"> <div className="flex min-h-0 flex-1">
<Sidebar /> <Sidebar />
<main <ScrollArea
className={`flex-1 overflow-auto bg-content-background p-6 transition-[margin] ${sidebarOpen ? 'md:ml-64' : ''}`} role="main"
className={`flex-1 bg-content-background transition-[margin] ${sidebarOpen ? 'md:ml-64' : ''}`}
> >
<div className="mx-auto w-full max-w-7xl"> <div className="p-6">
<ErrorBoundary> <div className="mx-auto w-full max-w-7xl">
<MainContent viewName={viewName} id={id} section={section} /> <ErrorBoundary>
</ErrorBoundary> <MainContent viewName={viewName} id={id} section={section} />
</ErrorBoundary>
</div>
</div> </div>
</main> </ScrollArea>
</div> </div>
</div> </div>
); );