Skip to content

Commit 5a876ec

Browse files
authored
Keep app chrome visible in settings
Generated-By: PostHog Code Task-Id: 5579dbc2-d5dd-440f-8fb5-3cbd00ed0957
1 parent 34cf1cb commit 5a876ec

3 files changed

Lines changed: 94 additions & 147 deletions

File tree

packages/ui/src/features/settings/components/SettingsPanel.tsx

Lines changed: 40 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,23 @@ import {
1515
Palette,
1616
Plugs,
1717
Robot,
18-
SignOut,
1918
SlackLogo,
2019
Terminal,
2120
TrafficSignal,
2221
TreeStructure,
2322
Wrench,
2423
} from "@phosphor-icons/react";
2524
import { BILLING_FLAG } from "@posthog/shared";
26-
import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
27-
import { useAuthStateValue } from "@posthog/ui/features/auth/store";
28-
import { useLogoutMutation } from "@posthog/ui/features/auth/useAuthMutations";
29-
import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser";
30-
import { getUserInitials } from "@posthog/ui/features/auth/userInitials";
3125
import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag";
3226
import { SettingsPageContent } from "@posthog/ui/features/settings/components/SettingsPageContent";
3327
import { closeSettings } from "@posthog/ui/features/settings/hooks/useOpenSettings";
3428
import { useSettingsPageStore } from "@posthog/ui/features/settings/stores/settingsPageStore";
3529
import type { SettingsCategory } from "@posthog/ui/features/settings/types";
30+
import { ProjectSwitcher } from "@posthog/ui/features/sidebar/components/ProjectSwitcher";
3631
import { useSpendAnalysisEnabled } from "@posthog/ui/features/usage/useSpendAnalysisEnabled";
3732
import * as nav from "@posthog/ui/router/navigationBridge";
3833
import { useHostCapabilities } from "@posthog/ui/shell/useHostCapabilities";
39-
import { Avatar, Flex, ScrollArea, Text } from "@radix-ui/themes";
34+
import { Box, ScrollArea, Text } from "@radix-ui/themes";
4035
import type { ReactNode } from "react";
4136
import { useHotkeys } from "react-hotkeys-hook";
4237

@@ -170,27 +165,24 @@ export interface SettingsPanelProps {
170165
onClose?: () => void;
171166
/** Override the category-change handler. Defaults to router navigation. */
172167
onCategoryChange?: (category: SettingsCategory) => void;
168+
/** Whether to show the settings navigation sidebar. */
169+
sidebarOpen?: boolean;
173170
}
174171

175172
export function SettingsPanel({
176173
activeCategory: activeCategoryProp,
177174
onClose,
178175
onCategoryChange,
176+
sidebarOpen = true,
179177
}: SettingsPanelProps = {}) {
180178
const formMode = useSettingsPageStore((s) => s.formMode);
181179
const activeCategory = activeCategoryProp ?? "general";
182180
const close = onClose ?? closeSettings;
183181
const setCategory =
184182
onCategoryChange ??
185183
((cat: SettingsCategory) => nav.navigateToSettings(cat, { replace: true }));
186-
const isAuthenticated = useAuthStateValue(
187-
(state) => state.status === "authenticated",
188-
);
189-
const client = useOptionalAuthenticatedClient();
190-
const { data: user } = useCurrentUser({ client });
191184
const billingEnabled = useFeatureFlag(BILLING_FLAG);
192185
const { localWorkspaces } = useHostCapabilities();
193-
const logoutMutation = useLogoutMutation();
194186

195187
const spendAnalysisEnabled = useSpendAnalysisEnabled();
196188
const hiddenCategories = getHiddenSettingsCategories({
@@ -226,83 +218,52 @@ export function SettingsPanel({
226218
(item) => item.id === activeSidebarCategory,
227219
)?.icon;
228220

229-
const initials = getUserInitials(user);
230-
231221
return (
232222
<div
233223
className="flex h-full w-full bg-(--color-background)"
234224
data-page="settings"
235225
>
236-
<div className="flex h-full w-[256px] shrink-0 flex-col border-gray-6 border-r">
237-
<div className="drag h-[36px] shrink-0 border-b border-b-(--gray-6)" />
238-
239-
{isAuthenticated && user && (
240-
<Flex
241-
align="center"
242-
gap="3"
243-
px="3"
244-
py="3"
245-
className="border-b border-b-(--gray-5)"
246-
>
247-
<Avatar size="2" fallback={initials} radius="full" color="amber" />
248-
<Flex direction="column" className="min-w-0">
249-
<Text truncate className="font-medium text-sm">
250-
{user.email}
251-
</Text>
252-
</Flex>
253-
</Flex>
254-
)}
255-
256-
<button
257-
type="button"
258-
className="mt-2 flex cursor-pointer items-center gap-2 border-0 bg-transparent px-3 py-2 text-left text-[13px] text-gray-11 transition-colors hover:bg-gray-3"
259-
onClick={close}
260-
>
261-
<ArrowLeft size={14} />
262-
<span>Back to app</span>
263-
</button>
264-
265-
<ScrollArea className="flex-1">
266-
<div className="flex flex-col gap-3 py-2">
267-
{sidebarGroups.map((group) => (
268-
<div key={group.label}>
269-
<Text className="px-3 pb-1 font-medium text-[10px] text-gray-9 uppercase tracking-wider">
270-
{group.label}
271-
</Text>
272-
{group.items.map((item) => {
273-
const isActive = activeSidebarCategory === item.id;
274-
return (
275-
<SidebarNavItem
276-
key={item.id}
277-
item={item}
278-
isActive={isActive}
279-
onClick={() => setCategory(item.id)}
280-
/>
281-
);
282-
})}
283-
</div>
284-
))}
285-
</div>
286-
</ScrollArea>
287-
288-
{isAuthenticated && (
226+
{sidebarOpen && (
227+
<div className="flex h-full w-[256px] shrink-0 flex-col border-gray-6 border-r">
289228
<button
290229
type="button"
291-
disabled={logoutMutation.isPending}
292-
className="flex cursor-pointer items-center gap-2 border-0 border-gray-5 border-t bg-transparent px-3 py-2.5 text-left font-mono text-[12px] text-gray-9 transition-colors hover:bg-gray-3 hover:text-gray-11 disabled:pointer-events-none disabled:opacity-50"
293-
onClick={() => {
294-
close();
295-
logoutMutation.mutate();
296-
}}
230+
className="mt-2 flex cursor-pointer items-center gap-2 border-0 bg-transparent px-3 py-2 text-left text-[13px] text-gray-11 transition-colors hover:bg-gray-3"
231+
onClick={close}
297232
>
298-
<SignOut size={14} />
299-
<span>Sign out</span>
233+
<ArrowLeft size={14} />
234+
<span>Back to app</span>
300235
</button>
301-
)}
302-
</div>
236+
237+
<ScrollArea className="flex-1">
238+
<div className="flex flex-col gap-3 py-2">
239+
{sidebarGroups.map((group) => (
240+
<div key={group.label}>
241+
<Text className="px-3 pb-1 font-medium text-[10px] text-gray-9 uppercase tracking-wider">
242+
{group.label}
243+
</Text>
244+
{group.items.map((item) => {
245+
const isActive = activeSidebarCategory === item.id;
246+
return (
247+
<SidebarNavItem
248+
key={item.id}
249+
item={item}
250+
isActive={isActive}
251+
onClick={() => setCategory(item.id)}
252+
/>
253+
);
254+
})}
255+
</div>
256+
))}
257+
</div>
258+
</ScrollArea>
259+
260+
<Box className="shrink-0 px-2 pb-2">
261+
<ProjectSwitcher />
262+
</Box>
263+
</div>
264+
)}
303265

304266
<div className="relative flex flex-1 flex-col overflow-hidden">
305-
<div className="drag h-[36px] shrink-0 border-b border-b-(--gray-6)" />
306267
<div className="relative flex flex-1 justify-center overflow-hidden">
307268
<svg
308269
aria-hidden="true"

packages/ui/src/router/routes/__root.tsx

Lines changed: 51 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,6 @@ function RootLayout() {
275275
}
276276
}, [flagsLoaded, homeTabEnabled, view.type]);
277277

278-
// Settings is a full-page route — drop the app chrome (header/sidebar/
279-
// space-switcher) so the panel occupies the full window.
280278
const isSettingsRoute = useRouterState({
281279
select: (s) => s.matches.some((m) => m.routeId.startsWith("/settings")),
282280
});
@@ -311,31 +309,6 @@ function RootLayout() {
311309
const activeTabBlank = useActiveTabIsBlank();
312310
const showBlankTab = onChannelsIndex && activeTabBlank;
313311

314-
if (isSettingsRoute) {
315-
return (
316-
<Flex direction="column" height="100%">
317-
<ConnectivityBanner />
318-
<Outlet />
319-
<CommandMenu open={commandMenuOpen} onOpenChange={setCommandMenuOpen} />
320-
<GlobalFilePicker />
321-
<KeyboardShortcutsSheet
322-
open={shortcutsSheetOpen}
323-
onOpenChange={(open) => (open ? null : closeShortcutsSheet())}
324-
/>
325-
<GlobalEventHandlers
326-
onToggleCommandMenu={toggleCommandMenu}
327-
onToggleShortcutsSheet={toggleShortcutsSheet}
328-
/>
329-
{billingEnabled && <UsageLimitModal />}
330-
<UsageBillingAnnouncementModal />
331-
<UpdateAvailableModal />
332-
<WhatsNewModal />
333-
<RemoteBranchCheckoutDialog />
334-
<ExistingWorktreeDialog />
335-
</Flex>
336-
);
337-
}
338-
339312
return (
340313
// DnD scope for the tab strip's drag-to-reorder (pill sortables live in
341314
// the title bar; the provider must sit above them).
@@ -365,12 +338,17 @@ function RootLayout() {
365338
// over- or under-shoots; the env var fallback covers hosts
366339
// without Window Controls Overlay.
367340
paddingLeft: isMac ? "env(titlebar-area-x, 78px)" : "78px",
368-
width: sidebarOpen ? channelsSidebarWidth : undefined,
341+
width: sidebarOpen
342+
? isSettingsRoute
343+
? "256px"
344+
: channelsSidebarWidth
345+
: undefined,
369346
// Same curve/duration as ResizableSidebar's SLIDE_EASING so the
370347
// title bar tracks the sidebar edge.
371-
transition: sidebarIsResizing
372-
? "none"
373-
: "width 0.2s cubic-bezier(0, 0, 0.2, 1)",
348+
transition:
349+
isSettingsRoute || sidebarIsResizing
350+
? "none"
351+
: "width 0.2s cubic-bezier(0, 0, 0.2, 1)",
374352
}}
375353
>
376354
<Flex align="center" gap="2" className="no-drag">
@@ -383,7 +361,7 @@ function RootLayout() {
383361
aria-label="Toggle sidebar"
384362
onClick={handleToggleSidebar}
385363
onMouseEnter={() => {
386-
if (!sidebarOpen) beginSidebarPeek();
364+
if (!isSettingsRoute && !sidebarOpen) beginSidebarPeek();
387365
}}
388366
>
389367
{sidebarOpen ? (
@@ -444,42 +422,48 @@ function RootLayout() {
444422
)}
445423
</Flex>
446424
<ConnectivityBanner />
447-
<Flex flexGrow="1" overflow="hidden" className="relative">
448-
{/* Scrim under the peeked nav: dims the content while the overlay is
449-
out. Purely visual (pointer-transparent) and paired with the
450-
panel's slide — same 200ms ease-out — so they read as one unit. */}
451-
{!sidebarOpen && (
452-
<Box
453-
aria-hidden
454-
// The radix preset replaces Tailwind's palette, so plain
455-
// `bg-black/*` doesn't exist — use the radix black-alpha scale
456-
// (--black-a2 = 10%, --black-a5 = 30%).
457-
className={`pointer-events-none absolute inset-0 z-40 bg-blackA-2 transition-opacity duration-200 ease-out motion-reduce:transition-none dark:bg-blackA-5 ${
458-
sidebarPeek ? "opacity-100" : "opacity-0"
459-
}`}
460-
/>
461-
)}
462-
<ChannelsSidebar />
463-
{/* Content sits in a bordered, rounded card inset from the window
464-
edges — the framed pane from the design. */}
465-
<Box flexGrow="1" className="overflow-hidden">
466-
<Box
467-
className={`h-full overflow-hidden border-border border-t border-l bg-background ${
468-
sidebarOpen ? "rounded-tl-sm" : ""
469-
}`}
470-
>
471-
<Flex direction="column" height="100%">
472-
{/* The /website space renders its own header (WebsiteLayout);
473-
everywhere else the shared header carries the view title
474-
and, on a task, its action row. */}
475-
{!onWebsitePath && <ContentHeader />}
476-
<Box flexGrow="1" overflow="hidden">
477-
{showBlankTab ? <BlankTabView /> : <Outlet />}
478-
</Box>
479-
</Flex>
480-
</Box>
425+
{isSettingsRoute ? (
426+
<Box flexGrow="1" className="overflow-hidden bg-background">
427+
<Outlet />
481428
</Box>
482-
</Flex>
429+
) : (
430+
<Flex flexGrow="1" overflow="hidden" className="relative">
431+
{/* Scrim under the peeked nav: dims the content while the overlay is
432+
out. Purely visual (pointer-transparent) and paired with the
433+
panel's slide — same 200ms ease-out — so they read as one unit. */}
434+
{!sidebarOpen && (
435+
<Box
436+
aria-hidden
437+
// The radix preset replaces Tailwind's palette, so plain
438+
// `bg-black/*` doesn't exist — use the radix black-alpha scale
439+
// (--black-a2 = 10%, --black-a5 = 30%).
440+
className={`pointer-events-none absolute inset-0 z-40 bg-blackA-2 transition-opacity duration-200 ease-out motion-reduce:transition-none dark:bg-blackA-5 ${
441+
sidebarPeek ? "opacity-100" : "opacity-0"
442+
}`}
443+
/>
444+
)}
445+
<ChannelsSidebar />
446+
{/* Content sits in a bordered, rounded card inset from the window
447+
edges — the framed pane from the design. */}
448+
<Box flexGrow="1" className="overflow-hidden">
449+
<Box
450+
className={`h-full overflow-hidden border-border border-t border-l bg-background ${
451+
sidebarOpen ? "rounded-tl-sm" : ""
452+
}`}
453+
>
454+
<Flex direction="column" height="100%">
455+
{/* The /website space renders its own header (WebsiteLayout);
456+
everywhere else the shared header carries the view title
457+
and, on a task, its action row. */}
458+
{!onWebsitePath && <ContentHeader />}
459+
<Box flexGrow="1" overflow="hidden">
460+
{showBlankTab ? <BlankTabView /> : <Outlet />}
461+
</Box>
462+
</Flex>
463+
</Box>
464+
</Box>
465+
</Flex>
466+
)}
483467
<CommandMenu open={commandMenuOpen} onOpenChange={setCommandMenuOpen} />
484468
<GlobalFilePicker />
485469
<KeyboardShortcutsSheet

packages/ui/src/router/routes/settings/$category.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SettingsPanel } from "@posthog/ui/features/settings/components/SettingsPanel";
22
import { useSettingsPageStore } from "@posthog/ui/features/settings/stores/settingsPageStore";
33
import { isSettingsCategory } from "@posthog/ui/features/settings/types";
4+
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
45
import { createFileRoute } from "@tanstack/react-router";
56
import { useEffect } from "react";
67

@@ -11,6 +12,7 @@ export const Route = createFileRoute("/settings/$category")({
1112
function SettingsRoute() {
1213
const { category } = Route.useParams();
1314
const cat = isSettingsCategory(category) ? category : "general";
15+
const sidebarOpen = useSidebarStore((state) => state.open);
1416

1517
// Reset transient state when leaving the route entirely. Switching between
1618
// categories (e.g. general → environments) does not unmount this component,
@@ -19,5 +21,5 @@ function SettingsRoute() {
1921
return () => useSettingsPageStore.getState().reset();
2022
}, []);
2123

22-
return <SettingsPanel activeCategory={cat} />;
24+
return <SettingsPanel activeCategory={cat} sidebarOpen={sidebarOpen} />;
2325
}

0 commit comments

Comments
 (0)