import { useLoaderData, useLocation } from '@tanstack/react-router'
import { type ReactNode, useEffect } from 'react'
import { cn } from './cn.ts'
import { Logo } from './ui/Logo.tsx'
import { SidebarToggle } from './ui/SidebarToggle.tsx'
import { useSidebar } from './useSidebar.ts'
interface SettingsProps {
/** Links to each section. */
nav?: ReactNode
children?: ReactNode
}
interface ChatsProps {
/** The chat list. */
nav?: ReactNode
/** The page's frame while its code loads, its nav a skeleton of what will fill it. */
frame?: boolean
children?: ReactNode
}
/*
* The sidebar, after Linear's: dense one-line rows of small text on the frame. On a narrow screen it
* is a drawer over the page, closed until opened, which closes again once a link in it is followed.
*/
/** The narrow screen's drawer, closed on each navigation. */
function useDrawer() {
const sidebar = useSidebar()
const pathname = useLocation({ select: (location) => location.pathname })
const { closeDrawer } = sidebar
// biome-ignore lint/correctness/useExhaustiveDependencies: closing follows each navigation.
useEffect(() => closeDrawer(), [pathname])
return sidebar
}
interface BackdropProps {
open: boolean
onClose: () => void
}
/** The dimmed page behind an open drawer, which closes it when pressed. */
function Backdrop(props: BackdropProps) {
if (!props.open) return null
return (
)
}
/**
* Settings and admin, whose nav sits inside the panel beside the section. The panel fills the
* window, with the nav at its start and the section centered in the rest, so the nav stays put from
* section to section. The nav sits as far from the window's edge as the chat list does, with the
* chat's gutter on the other side.
*/
export function SettingsSidebarLayout(props: SettingsProps) {
const drawer = useDrawer()
return (
{/* On a narrow screen the toggle opens the page, a gutter from the panel's corner like
the section under it, and stays there as the page scrolls, since a sticky element
keeps clear of its scroll container's padding. */}
{/* A section pads its own foot, so its Save bar can stick to the panel's very bottom. */}
{props.children}
)
}
/** The chats, whose sidebar is a column beside the panel that collapses off the window's edge. */
export function ChatsLayout(props: ChatsProps) {
const drawer = useDrawer()
return (
// Whether the sidebar is collapsed is an attribute on the root, set before the first paint.
{/* A size container, so the conversation's header can reach the panel's edges, and the block
its absolutely placed parts are placed in, so none of them can stretch the page past the
panel's scroll. */}
{props.children}
)
}
const sidebarClass = cn(
'flex flex-col gap-0.5 overscroll-contain bg-chrome px-sidebar-pad pt-sidebar-pad pb-3 text-ui',
// On a narrow screen, a drawer over the page that stays visible until it has slid away.
'motion-reduce:transition-none max-wide:in-data-drawer-open:visible max-wide:invisible max-wide:fixed max-wide:inset-y-0 max-wide:start-0 max-wide:z-20 max-wide:w-drawer max-wide:-translate-x-full max-wide:in-data-drawer-open:translate-x-0 max-wide:shadow-float max-wide:transition-[translate,visibility] max-wide:duration-(--dur-slide) max-wide:ease-slide rtl:max-wide:in-data-drawer-open:translate-x-0 rtl:max-wide:translate-x-full',
)
/**
* The toggle in the panel, for when the sidebar is hidden: collapsed on a wide screen, or a closed
* drawer on a narrow one. On a wide screen it fades in once the sidebar, with its own toggle, has
* mostly slid away, and fades out at once as the sidebar comes back.
*/
function MainBar() {
return (
)
}
/** The app's logo, named for screen readers by the app's name. */
function AppLogo() {
const name = useLoaderData({ from: '__root__', select: (data) => data.appName })
return
}