zebflow 0.9.0

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { useState } from "zeb/react";
import Tabs from "@/components/ui/tabs";
import TabsList from "@/components/ui/tabs-list";
import TabsTrigger from "@/components/ui/tabs-trigger";
import TabsContent from "@/components/ui/tabs-content";
import { StudioTabNav, StudioTabLink } from "@/components/ui/studio-tab-nav";
import Separator from "@/components/ui/separator";
import TreeView from "@/components/ui/tree-view";
import TreeItem from "@/components/ui/tree-item";
import HierarchyTree from "@/components/ui/hierarchy-tree";
import WebhookRouteTree from "@/components/ui/webhook-route-tree";
import RepoTree from "@/components/ui/repo-tree";
import RepoTreeRow from "@/components/ui/repo-tree-row";
import { SectionHeading, Entry } from "@/pages/dev/design-system/components/gallery";

const ROUTES = [
  { name: "login", title: "Login", webhook_method: "POST", webhook_path: "/auth/login", editor_href: "#login" },
  { name: "me", title: "Me", webhook_method: "GET", webhook_path: "/me", editor_href: "#me" },
  { name: "google-start", title: "Google start", webhook_method: "GET", webhook_path: "/auth/google/start", editor_href: "#gs" },
  { name: "google-callback", title: "Google callback", webhook_method: "GET", webhook_path: "/auth/google/callback", editor_href: "#gc" },
  { name: "admin", title: "Admin", webhook_method: "GET", webhook_path: "/auth/admin", editor_href: "#admin" },
];

const HIERARCHY = [
  {
    id: "pipelines", label: "pipelines", expanded: true,
    children: [
      { id: "auth", label: "auth", expanded: true, badge: "8", children: [
        { id: "login", label: "login.zf.json", active: true },
        { id: "me", label: "me.zf.json" },
      ] },
      { id: "test", label: "test", children: [{ id: "smoke", label: "smoke.zf.json" }] },
    ],
  },
  { id: "templates", label: "templates", children: [{ id: "pages", label: "pages" }] },
];

export default function NavigationSection({ owner, project }) {
  const [tab, setTab] = useState("runs");
  const [selected, setSelected] = useState("");

  return (
    <div>
      <SectionHeading title="Navigation" description="Ways to move around inside a page." />

      <Entry
        name="Tabs"
        file="tabs.tsx"
        description="Segmented tabs for switching content in place. State lives with the caller: `active` on each Trigger and Content."
        code={`<Tabs>
  <TabsList>
    <TabsTrigger label="Runs" active={tab === "runs"} onClick={() => setTab("runs")} />
    <TabsTrigger label="Settings" active={tab === "settings"} onClick={() => setTab("settings")} />
  </TabsList>
  <TabsContent active={tab === "runs"}>…</TabsContent>
  <TabsContent active={tab === "settings"}>…</TabsContent>
</Tabs>`}
      >
        <Tabs>
          <TabsList>
            <TabsTrigger label="Runs" active={tab === "runs"} onClick={() => setTab("runs")} />
            <TabsTrigger label="Settings" active={tab === "settings"} onClick={() => setTab("settings")} />
            <TabsTrigger label="Disabled" disabled />
          </TabsList>
          <TabsContent active={tab === "runs"}><p className="text-sm text-muted-foreground">12 runs, last one 4 minutes ago.</p></TabsContent>
          <TabsContent active={tab === "settings"}><p className="text-sm text-muted-foreground">Capture level: on-error.</p></TabsContent>
        </Tabs>
      </Entry>

      <Entry
        name="StudioTabNav"
        file="studio-tab-nav.tsx"
        description="The page-level tab bar under a studio header. Links, not buttons — each tab is a route."
        code={`<StudioTabNav>
  <StudioTabLink href="/…/pipelines/registry" active>Registry</StudioTabLink>
  <StudioTabLink href="/…/pipelines/webhooks">Webhooks</StudioTabLink>
</StudioTabNav>`}
        demoClassName="p-0 overflow-hidden"
      >
        <StudioTabNav>
          <StudioTabLink href="#" active>Registry</StudioTabLink>
          <StudioTabLink href="#">Webhooks</StudioTabLink>
          <StudioTabLink href="#">Schedules</StudioTabLink>
          <StudioTabLink href="#">Manual</StudioTabLink>
        </StudioTabNav>
      </Entry>

      <Entry
        name="Separator"
        file="separator.tsx"
        description="A hairline. `orientation` horizontal (default) | vertical."
        code={`<Separator />
<div className="flex h-6 items-center gap-3">a <Separator orientation="vertical" /> b</div>`}
      >
        <div className="grid gap-4 text-sm">
          <Separator />
          <div className="flex h-6 items-center gap-3">
            <span>registry</span>
            <Separator orientation="vertical" />
            <span>webhooks</span>
            <Separator orientation="vertical" />
            <span>schedules</span>
          </div>
        </div>
      </Entry>

      <Entry
        name="TreeView · TreeItem"
        file="tree-view.tsx"
        description="A <details>-based tree: works before hydration, expands natively. TreeItem: isFolder, expanded, href, active."
        code={`<TreeView>
  <TreeItem id="pipelines" label="pipelines" isFolder expanded>
    <TreeItem id="login" label="login.zf.json" href="#" active />
  </TreeItem>
</TreeView>`}
      >
        <TreeView>
          <TreeItem id="pipelines" label="pipelines" isFolder expanded>
            <TreeItem id="auth" label="auth" isFolder expanded>
              <TreeItem id="login" label="login.zf.json" href="#" active />
              <TreeItem id="me" label="me.zf.json" href="#" />
            </TreeItem>
            <TreeItem id="test" label="test" isFolder expanded={false}>
              <TreeItem id="smoke" label="smoke.zf.json" href="#" />
            </TreeItem>
          </TreeItem>
          <TreeItem id="templates" label="templates" isFolder expanded={false}>
            <TreeItem id="pages" label="pages" isFolder />
          </TreeItem>
        </TreeView>
      </Entry>

      <Entry
        name="HierarchyTree"
        file="hierarchy-tree.tsx"
        description="A data-driven tree: items of { id, label, icon?, badge?, href?, active?, expanded?, children? }. `defaultExpandedDepth` opens the first levels."
        code={`<HierarchyTree items={items} defaultExpandedDepth={2} emptyLabel="No pipelines." />`}
      >
        <HierarchyTree items={HIERARCHY} defaultExpandedDepth={2} emptyLabel="No pipelines." />
      </Entry>

      <Entry
        name="WebhookRouteTree"
        file="webhook-route-tree.tsx"
        description="Pipelines grouped by webhook path, one branch per segment, the method beside each leaf. Reads webhook_path, webhook_method, editor_href, title."
        code={`<WebhookRouteTree items={pipelineItems} />`}
      >
        <WebhookRouteTree items={ROUTES} />
      </Entry>

      <Entry
        name="RepoTreeRow"
        file="repo-tree-row.tsx"
        description="One row of the repository tree: kind icon, name, git status, a ⋯ context menu. RepoTree stacks these."
        code={`<RepoTreeRow item={{ kind: "folder", name: "pipelines", rel_path: "pipelines" }} depth={0} open />
<RepoTreeRow item={{ kind: "file", name: "login.zf.json", rel_path: "pipelines/auth/login.zf.json" }} depth={2} active status="M" />`}
      >
        <div className="max-w-sm">
          <RepoTreeRow item={{ kind: "root", name: "repo", rel_path: "" }} depth={0} open onActivate={() => {}} menu={[]} />
          <RepoTreeRow item={{ kind: "folder", name: "pipelines", rel_path: "pipelines" }} depth={1} open onActivate={() => {}} menu={[{ label: "New file" }]} />
          <RepoTreeRow item={{ kind: "folder", name: "auth", rel_path: "pipelines/auth" }} depth={2} open onActivate={() => {}} menu={[{ label: "New file" }]} />
          <RepoTreeRow item={{ kind: "file", name: "login.zf.json", rel_path: "pipelines/auth/login.zf.json" }} depth={3} active status="M" onActivate={() => {}} menu={[{ label: "Rename" }, { label: "Delete", variant: "destructive" }]} />
          <RepoTreeRow item={{ kind: "file", name: "me.zf.json", rel_path: "pipelines/auth/me.zf.json" }} depth={3} status="?" onActivate={() => {}} menu={[{ label: "Rename" }]} />
          <RepoTreeRow item={{ kind: "file", name: "me.tsx", rel_path: "templates/pages/me.tsx" }} depth={2} onActivate={() => {}} menu={[]} />
        </div>
      </Entry>

      <Entry
        name="RepoTree"
        file="repo-tree.tsx"
        description={project ? `Live against ${owner}/${project}: folders fetch on open, git status paints the rows.` : "Needs a project; the viewer has none yet."}
        code={`<RepoTree owner={owner} project={project} rootLabel={project} selected={selected} onSelect={(item) => setSelected(item.rel_path)} onAction={(action, item) => …} />`}
      >
        {project ? (
          <div className="grid gap-3">
            <div className="max-w-sm">
              <RepoTree owner={owner} project={project} rootLabel={project} selected={selected} onSelect={(item) => setSelected(String(item?.rel_path ?? ""))} onAction={() => {}} />
            </div>
            <span className="font-mono text-xs text-muted-foreground">selected: {selected || "(none)"}</span>
          </div>
        ) : (
          <p className="text-sm text-muted-foreground">No project to browse.</p>
        )}
      </Entry>
    </div>
  );
}