Skip to component content
Navigation078 / 155

NavigationSplitView

Adapt an independently scrolling split view to the space available.

Example

Field guide

In progress
Source
NavigationSplitView.tsxtsx
import { useState } from "react";
import { Badge, Button, Card, Grid, NavigationSplitView, Picker, Text, Toggle, ViewFrame, VStack, type NavigationPane } from "@/ohmygui";

const projects = ["Field guide", "Planting calendar", "Garden journal"];
const panes = ["sidebar", "content", "detail"].map((value) => ({ value, label: value[0].toUpperCase() + value.slice(1) }));

export default function Example() {
  const [selected, setSelected] = useState(projects[0]);
  const [collapsed, setCollapsed] = useState(false);
  const [mobilePane, setMobilePane] = useState<NavigationPane>("content");
  const [breakpoint, setBreakpoint] = useState("760");
  const [motion, setMotion] = useState(true);

  return (
    <VStack gap="sm" align="stretch">
      <Grid columns="auto" minColumnWidth={190} gap="sm">
        <Picker label="Compact at" value={breakpoint} onChange={(event) => setBreakpoint(event.currentTarget.value)} items={["600", "760", "960"].map((value) => ({ value, label: `${value} px` }))} />
        <Picker label="Mobile pane" value={mobilePane} onChange={(event) => setMobilePane(event.currentTarget.value as NavigationPane)} items={panes} />
        <Toggle label="Sidebar collapsed" checked={collapsed} onChange={(event) => setCollapsed(event.currentTarget.checked)} />
        <Toggle label="Pane entrance motion" checked={motion} onChange={(event) => setMotion(event.currentTarget.checked)} />
      </Grid>
      <Text variant="metadata" color="muted" role="status">Selected project: {selected} · mobile pane: {mobilePane}</Text>
      <ViewFrame height={360}>
        <NavigationSplitView
          viewport={false} mobileBreakpoint={Number(breakpoint)} mobilePane={mobilePane}
          onMobilePaneChange={setMobilePane} paneAnimation={motion} detailTransitionKey={selected}
          collapsed={collapsed} onCollapsedChange={setCollapsed} sidebarWidth="12rem" contentWidth="15rem"
          paneLabels={{ sidebar: "Workspace", content: "Projects", detail: "Project details" }}
          sidebar={<VStack gap="sm"><Text variant="eyebrow">STUDIO</Text><Button variant="ghost" onClick={() => setMobilePane("content")}>All projects</Button><Button variant="ghost" onClick={() => setMobilePane("detail")}>Current project</Button></VStack>}
          content={<VStack gap="xs" align="stretch">{projects.map((project) => <Button key={project} variant={selected === project ? "secondary" : "ghost"} aria-pressed={selected === project} onClick={() => setSelected(project)}>{project}</Button>)}</VStack>}
          detail={<Card header={<Text as="h3" variant="title">{selected}</Text>}><Badge variant="success" tone="soft">In progress</Badge></Card>}
        />
      </ViewFrame>
    </VStack>
  );
}