fix: standardize direct page scroll viewports

This commit is contained in:
2026-07-28 22:50:11 +02:00
parent 324c26da78
commit 53e947935a
6 changed files with 67 additions and 32 deletions

View File

@@ -0,0 +1,23 @@
import type { HTMLAttributes, ReactNode } from "react";
export type PageScrollViewportProps = Omit<
HTMLAttributes<HTMLDivElement>,
"children"
> & {
children: ReactNode;
};
export default function PageScrollViewport({
children,
className = "",
...props
}: PageScrollViewportProps) {
return (
<div
{...props}
className={["page-scroll-viewport", className].filter(Boolean).join(" ")}
>
{children}
</div>
);
}

View File

@@ -6,6 +6,7 @@ import { dispatchPlatformViewChanged } from "../platform/views";
import { useGuardedNavigate } from "./UnsavedChangesGuard"; import { useGuardedNavigate } from "./UnsavedChangesGuard";
import Button from "./Button"; import Button from "./Button";
import Card from "./Card"; import Card from "./Card";
import PageScrollViewport from "./PageScrollViewport";
export default function ViewSurfaceRouteBoundary({ export default function ViewSurfaceRouteBoundary({
surfaceId, surfaceId,
@@ -33,26 +34,28 @@ export default function ViewSurfaceRouteBoundary({
} }
return ( return (
<div className="content-pad"> <PageScrollViewport>
<Card title="Outside the current view"> <div className="content-pad">
<div className="empty-state"> <Card title="Outside the current view">
<EyeOff size={24} aria-hidden="true" /> <div className="empty-state">
<p> <EyeOff size={24} aria-hidden="true" />
This area is available to your account, but hidden by <p>
{projection?.activeViewName This area is available to your account, but hidden by
? ` the ${projection.activeViewName} view` {projection?.activeViewName
: " the current view"}. ? ` the ${projection.activeViewName} view`
</p> : " the current view"}.
<div className="button-row"> </p>
{!projection?.locked && runtime && ( <div className="button-row">
<Button variant="primary" onClick={() => void exitView()}> {!projection?.locked && runtime && (
Exit view <Button variant="primary" onClick={() => void exitView()}>
</Button> Exit view
)} </Button>
<Button onClick={() => navigate(fallbackPath)}>Back to view</Button> )}
<Button onClick={() => navigate(fallbackPath)}>Back to view</Button>
</div>
</div> </div>
</div> </Card>
</Card> </div>
</div> </PageScrollViewport>
); );
} }

View File

@@ -1,15 +1,18 @@
import Card from "../components/Card"; import Card from "../components/Card";
import PageScrollViewport from "../components/PageScrollViewport";
export default function PlaceholderPage({ title }: {title: string;}) { export default function PlaceholderPage({ title }: {title: string;}) {
return ( return (
<div className="content-pad"> <PageScrollViewport>
<div className="page-heading"> <div className="content-pad">
<h1>{title}</h1> <div className="page-heading">
<p>i18n:govoplan-core.this_module_is_prepared_but_not_implemented_yet.6e6449ee</p> <h1>{title}</h1>
<p>i18n:govoplan-core.this_module_is_prepared_but_not_implemented_yet.6e6449ee</p>
</div>
<Card>
<p className="muted">i18n:govoplan-core.next_passes_will_add_functionality_here.c13caade</p>
</Card>
</div> </div>
<Card> </PageScrollViewport>);
<p className="muted">i18n:govoplan-core.next_passes_will_add_functionality_here.c13caade</p>
</Card>
</div>);
} }

View File

@@ -1,5 +1,6 @@
import Card from "../../components/Card"; import Card from "../../components/Card";
import MetricCard from "../../components/MetricCard"; import MetricCard from "../../components/MetricCard";
import PageScrollViewport from "../../components/PageScrollViewport";
import PageTitle from "../../components/PageTitle"; import PageTitle from "../../components/PageTitle";
import { usePlatformModules } from "../../platform/ModuleContext"; import { usePlatformModules } from "../../platform/ModuleContext";
@@ -7,7 +8,8 @@ export default function DashboardPage() {
const modules = usePlatformModules(); const modules = usePlatformModules();
return ( return (
<div className="content-pad workspace-data-page core-dashboard-page"> <PageScrollViewport className="core-dashboard-page">
<div className="content-pad workspace-data-page">
<div className="page-heading split workspace-heading"> <div className="page-heading split workspace-heading">
<div> <div>
<PageTitle>i18n:govoplan-core.dashboard.d87f47b4</PageTitle> <PageTitle>i18n:govoplan-core.dashboard.d87f47b4</PageTitle>
@@ -37,7 +39,8 @@ export default function DashboardPage() {
<p className="muted">This minimal core home is only shown while no dashboard WebUI module is available. Feature modules own their pages and can expose dashboard widgets once the dashboard module is installed.</p> <p className="muted">This minimal core home is only shown while no dashboard WebUI module is available. Feature modules own their pages and can expose dashboard widgets once the dashboard module is installed.</p>
</Card> </Card>
</div> </div>
</div>); </div>
</PageScrollViewport>);
} }

View File

@@ -95,6 +95,8 @@ export { default as MetricCard } from "./components/MetricCard";
export { default as MessageDisplayPanel } from "./components/MessageDisplayPanel"; export { default as MessageDisplayPanel } from "./components/MessageDisplayPanel";
export type { MessageDisplayAttachment, MessageDisplayField } from "./components/MessageDisplayPanel"; export type { MessageDisplayAttachment, MessageDisplayField } from "./components/MessageDisplayPanel";
export { default as PageTitle } from "./components/PageTitle"; export { default as PageTitle } from "./components/PageTitle";
export { default as PageScrollViewport } from "./components/PageScrollViewport";
export type { PageScrollViewportProps } from "./components/PageScrollViewport";
export { default as PasswordField } from "./components/PasswordField"; export { default as PasswordField } from "./components/PasswordField";
export { default as PeoplePicker } from "./components/people/PeoplePicker"; export { default as PeoplePicker } from "./components/people/PeoplePicker";
export type { PeoplePickerProps } from "./components/people/PeoplePicker"; export type { PeoplePickerProps } from "./components/people/PeoplePicker";

View File

@@ -68,8 +68,10 @@
.section-link.active { border-left: 3px solid var(--accent); font-weight: 700; } .section-link.active { border-left: 3px solid var(--accent); font-weight: 700; }
.section-link.subtle { font-size: 13px; } .section-link.subtle { font-size: 13px; }
.workspace-content { min-width: 0; max-width: 100%; min-height: 0; overflow: auto; } .workspace-content { min-width: 0; max-width: 100%; min-height: 0; overflow: auto; }
.page-scroll-viewport { width: 100%; height: 100%; min-width: 0; min-height: 0; overflow: auto; }
.ui-no-sticky-section-sidebars .app-content { overflow: auto; } .ui-no-sticky-section-sidebars .app-content { overflow: auto; }
.ui-no-sticky-section-sidebars .workspace { height: auto; min-height: 100%; } .ui-no-sticky-section-sidebars .workspace { height: auto; min-height: 100%; }
.ui-no-sticky-section-sidebars .page-scroll-viewport { height: auto; min-height: 100%; overflow: visible; }
.ui-no-sticky-section-sidebars .section-sidebar { .ui-no-sticky-section-sidebars .section-sidebar {
height: max-content; height: max-content;
max-height: none; max-height: none;
@@ -83,7 +85,6 @@
.page-heading.split { display: flex; align-items: center; justify-content: space-between; } .page-heading.split { display: flex; align-items: center; justify-content: space-between; }
.module-entry-page { max-width: none; } .module-entry-page { max-width: none; }
.workspace-data-page .card { margin-bottom: 18px; } .workspace-data-page .card { margin-bottom: 18px; }
.core-dashboard-page { height: 100%; overflow: auto; }
.page-heading.workspace-heading { .page-heading.workspace-heading {
position: sticky; position: sticky;
top: 0; top: 0;