Give split-layout pages independent scroll regions
Chat and Settings escaped the content padding with a hardcoded calc(100vh - 44px) height and negative margins, while Files and Social let the whole page scroll, so secondary panes (channel list, settings nav, file tree) scrolled away. Add a .content.flush modifier the Shell applies to the chat, settings, files and social routes, plus a reusable .pane-scroll region, and rework those pages to fill the content area with each pane scrolling on its own. Removes the magic-number coupling to the topbar height.
This commit is contained in:
1 parent
5f70480907
commit
91145fd923
7 files changed
+56
-31
No files matched your search
@@ -196,7 +196,7 @@ export function ChatPage({ snapshot, actions }: PageProps) {
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="split-main" style={{ height: "calc(100vh - 44px)", margin: "-28px -32px -40px" }}>
|
||||
<div className="split-main">
|
||||
<ChatSidebar
|
||||
channels={channels}
|
||||
directs={directs}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function FilesPage({ snapshot, actions }: PageProps) {
|
||||
const totalBytes = visibleFiles.reduce((sum, file) => sum + file.sizeBytes, 0);
|
||||
|
||||
return (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "minmax(160px, 200px) minmax(0, 1fr)", gap: 24 }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "minmax(160px, 200px) minmax(0, 1fr)", gap: 24, height: "100%", minHeight: 0, padding: "28px 32px 0" }}>
|
||||
<FilesTree
|
||||
files={files}
|
||||
pinnedFiles={pinnedFiles}
|
||||
@@ -56,7 +56,7 @@ export function FilesPage({ snapshot, actions }: PageProps) {
|
||||
onChange={setSection}
|
||||
/>
|
||||
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ minWidth: 0, display: "flex", flexDirection: "column", minHeight: 0 }}>
|
||||
<TopbarActions>
|
||||
<Button tone="ghost"><Icon name="filter" size={13} /></Button>
|
||||
<Segmented<ViewMode>
|
||||
@@ -76,6 +76,7 @@ export function FilesPage({ snapshot, actions }: PageProps) {
|
||||
{visibleFiles.length} {visibleFiles.length === 1 ? "file" : "files"} · {formatBytes(totalBytes)}
|
||||
</p>
|
||||
|
||||
<div className="pane-scroll" style={{ paddingBottom: 40 }}>
|
||||
{pinnedFiles.length > 0 && section.kind === "all" && (
|
||||
<>
|
||||
<div className="section-label" style={{ marginBottom: 10 }}>Pinned</div>
|
||||
@@ -115,6 +116,7 @@ export function FilesPage({ snapshot, actions }: PageProps) {
|
||||
onTogglePin={(file) => void actions.run(() => window.agentCompany.setFilePinned({ orgId, fileId: file.id, pinned: !file.pinned }))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -137,7 +139,7 @@ function FilesTree({
|
||||
}) {
|
||||
const recent = files.filter((file) => isRecent(file.createdAt));
|
||||
return (
|
||||
<aside>
|
||||
<aside style={{ overflowY: "auto", minHeight: 0, paddingBottom: 40 }}>
|
||||
<div className="section-label">Library</div>
|
||||
<TreeItem
|
||||
icon="folder"
|
||||
|
||||
@@ -110,14 +110,16 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
|
||||
const rollbackSnapshots = useMemo(() => snapshot.rollbackSnapshots.filter((item) => item.orgId === orgId), [snapshot.rollbackSnapshots, orgId]);
|
||||
|
||||
return (
|
||||
<div className="split-main" style={{ height: "calc(100vh - 44px)", margin: "-28px -32px -40px" }}>
|
||||
<div className="split-main">
|
||||
<aside className="secondary-pane">
|
||||
<div style={{ fontSize: 13, fontWeight: 600, padding: "4px 8px 14px" }}>Settings</div>
|
||||
<NavGroup items={WORKSPACE_GROUP} active={tab} onChange={setTab} />
|
||||
<NavGroupLabel>Agents</NavGroupLabel>
|
||||
<NavGroup items={AGENTS_GROUP} active={tab} onChange={setTab} />
|
||||
<NavGroupLabel>Account</NavGroupLabel>
|
||||
<NavGroup items={ACCOUNT_GROUP} active={tab} onChange={setTab} />
|
||||
<div className="pane-scroll">
|
||||
<NavGroup items={WORKSPACE_GROUP} active={tab} onChange={setTab} />
|
||||
<NavGroupLabel>Agents</NavGroupLabel>
|
||||
<NavGroup items={AGENTS_GROUP} active={tab} onChange={setTab} />
|
||||
<NavGroupLabel>Account</NavGroupLabel>
|
||||
<NavGroup items={ACCOUNT_GROUP} active={tab} onChange={setTab} />
|
||||
</div>
|
||||
</aside>
|
||||
<div className="main" style={{ flex: 1, minWidth: 0 }}>
|
||||
<div className="content">
|
||||
|
||||
@@ -76,7 +76,7 @@ export function SocialPage({ snapshot, actions }: PageProps) {
|
||||
void actions.run(() => window.agentCompany.setScheduledPostState({ orgId, scheduledPostId: post.id, state, employeeId: employee.id }));
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page" style={{ display: "flex", flexDirection: "column", minHeight: 0, height: "100%", padding: "28px 32px 0" }}>
|
||||
<TopbarActions>
|
||||
<Button onClick={() => setConnectOpen(true)}><Icon name="plus" size={13} /> Connect account</Button>
|
||||
<Button tone="primary" onClick={() => setDraftOpen(true)} disabled={accounts.length === 0}>
|
||||
@@ -89,6 +89,7 @@ export function SocialPage({ snapshot, actions }: PageProps) {
|
||||
{operatorName ? ` · managed by ${operatorName}` : ""}
|
||||
</p>
|
||||
|
||||
<div className="pane-scroll" style={{ paddingBottom: 40 }}>
|
||||
{accounts.length > 0 && (
|
||||
<>
|
||||
<div className="section-label" style={{ marginBottom: 10 }}>Accounts</div>
|
||||
@@ -148,6 +149,7 @@ export function SocialPage({ snapshot, actions }: PageProps) {
|
||||
|
||||
<SocialCalendar posts={scheduledPosts} accounts={accounts} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog title="Connect social account" open={connectOpen} submitLabel="Connect" onClose={() => setConnectOpen(false)} onSubmit={connect}>
|
||||
<Field label="Platform">
|
||||
|
||||
@@ -26,33 +26,35 @@ export function ChatSidebar({
|
||||
<div style={{ fontSize: 13, fontWeight: 600 }}>Messages</div>
|
||||
<IconButton icon="plus" label="New space" onClick={onCreateSpace} />
|
||||
</div>
|
||||
<NavGroup label="Channels">
|
||||
{channels.length === 0 ? (
|
||||
<Empty>No channels.</Empty>
|
||||
) : channels.map((space) => (
|
||||
<ChannelItem
|
||||
key={space.id}
|
||||
space={space}
|
||||
active={space.id === activeSpaceId}
|
||||
count={countMessagesIn(space.id, messages)}
|
||||
onClick={() => onSelect(space.id)}
|
||||
/>
|
||||
))}
|
||||
</NavGroup>
|
||||
{directs.length > 0 && (
|
||||
<NavGroup label="Direct">
|
||||
{directs.map((space) => (
|
||||
<DirectItem
|
||||
<div className="pane-scroll">
|
||||
<NavGroup label="Channels">
|
||||
{channels.length === 0 ? (
|
||||
<Empty>No channels.</Empty>
|
||||
) : channels.map((space) => (
|
||||
<ChannelItem
|
||||
key={space.id}
|
||||
space={space}
|
||||
agents={agents}
|
||||
active={space.id === activeSpaceId}
|
||||
count={countMessagesIn(space.id, messages)}
|
||||
onClick={() => onSelect(space.id)}
|
||||
/>
|
||||
))}
|
||||
</NavGroup>
|
||||
)}
|
||||
{directs.length > 0 && (
|
||||
<NavGroup label="Direct">
|
||||
{directs.map((space) => (
|
||||
<DirectItem
|
||||
key={space.id}
|
||||
space={space}
|
||||
agents={agents}
|
||||
active={space.id === activeSpaceId}
|
||||
count={countMessagesIn(space.id, messages)}
|
||||
onClick={() => onSelect(space.id)}
|
||||
/>
|
||||
))}
|
||||
</NavGroup>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ interface NavSpec {
|
||||
icon: IconName;
|
||||
}
|
||||
|
||||
/** Routes whose pages own a full-height, multi-pane layout with their own
|
||||
* internal scroll regions, so the content area must not scroll as one block. */
|
||||
const FLUSH_ROUTES = new Set<RouteId>(["chat", "settings", "files", "social"]);
|
||||
|
||||
const primaryNav: NavSpec[] = [
|
||||
{ id: "home", label: "Home", icon: "home" },
|
||||
{ id: "team", label: "Team", icon: "team" },
|
||||
@@ -132,7 +136,7 @@ export function Shell({
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
<div className="content">
|
||||
<div className={FLUSH_ROUTES.has(route) ? "content flush" : "content"}>
|
||||
<TopbarSlotContext.Provider value={actionsSlot}>{children}</TopbarSlotContext.Provider>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -298,6 +298,19 @@ a { color: inherit; }
|
||||
overflow: auto;
|
||||
padding: 28px 32px 40px;
|
||||
}
|
||||
/* Split-layout routes fill the content area and let their panes scroll
|
||||
independently instead of scrolling the whole page. */
|
||||
.content.flush {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
.pane-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.page { max-width: 1100px; }
|
||||
.page-wide { width: 100%; }
|
||||
.page-h1 {
|
||||
|
||||
Reference in new issue
Block a user