zebflow 0.11.13

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { useState } from "zeb/react";
import { Button, buttonVariants } from "zeb/ui/button";
import { Popover, PopoverTrigger, PopoverContent } from "zeb/ui/popover";
import { Tooltip, TooltipTrigger, TooltipContent } from "zeb/ui/tooltip";
import { HoverCard, HoverCardTrigger, HoverCardContent } from "zeb/ui/hover-card";
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuItem,
  DropdownMenuShortcut,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
} from "zeb/ui/dropdown-menu";
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from "zeb/ui/context-menu";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectLabel, SelectItem } from "zeb/ui/select";
import { toast, Toaster } from "zeb/ui/sonner";
import { SectionHeading, Entry } from "@/pages/dev/design-system/components/gallery";

/** zeb/ui — overlays, part 2: anchored panels, the two menus, Select, Sonner. */

function PopoverDemo() {
  return (
    <Popover>
      <PopoverTrigger className={buttonVariants({ variant: "outline" })}>Open popover</PopoverTrigger>
      <PopoverContent>
        <p className="text-sm font-medium text-foreground">Dimensions</p>
        <p className="mt-1 text-sm text-muted-foreground">Set the width and height for the layer.</p>
      </PopoverContent>
    </Popover>
  );
}

function TooltipDemo() {
  return (
    <Tooltip delayDuration={200}>
      <TooltipTrigger>
        <button type="button" className={buttonVariants({ variant: "outline" })}>
          Hover or focus me
        </button>
      </TooltipTrigger>
      <TooltipContent>Add to library</TooltipContent>
    </Tooltip>
  );
}

function HoverCardDemo() {
  return (
    <HoverCard>
      <HoverCardTrigger>
        <a href="#" className="text-sm font-medium text-primary underline-offset-4 hover:underline">
          @zebflow
        </a>
      </HoverCardTrigger>
      <HoverCardContent>
        <p className="text-sm font-semibold text-foreground">Zebflow</p>
        <p className="mt-1 text-sm text-muted-foreground">The platform this whole gallery renders on. Joined 2019.</p>
      </HoverCardContent>
    </HoverCard>
  );
}

function DropdownMenuDemo() {
  const [bookmarked, setBookmarked] = useState(true);
  const [account, setAccount] = useState("personal");
  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline" })}>Options</DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>My account</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem>
          Profile
          <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem>Billing</DropdownMenuItem>
        <DropdownMenuCheckboxItem checked={bookmarked} onCheckedChange={setBookmarked}>
          Bookmarked
        </DropdownMenuCheckboxItem>
        <DropdownMenuSeparator />
        <DropdownMenuRadioGroup>
          <DropdownMenuRadioItem checked={account === "personal"} onClick={() => setAccount("personal")}>
            Personal
          </DropdownMenuRadioItem>
          <DropdownMenuRadioItem checked={account === "team"} onClick={() => setAccount("team")}>
            Team
          </DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive">Delete account</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

function ContextMenuDemo() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="flex h-28 w-full items-center justify-center rounded-md border border-dashed border-border text-sm text-muted-foreground">
        Right click here
      </ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem>Back</ContextMenuItem>
        <ContextMenuItem>Forward</ContextMenuItem>
        <ContextMenuItem>Reload</ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="destructive">Delete</ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  );
}

const FRUITS = { apple: "Apple", banana: "Banana", blueberry: "Blueberry", grapes: "Grapes" };

function SelectDemo() {
  const [value, setValue] = useState("blueberry");
  return (
    <div className="w-56">
      <Select value={value} onValueChange={setValue}>
        <SelectTrigger>
          <SelectValue placeholder="Select a fruit" />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectLabel>Fruits</SelectLabel>
            {Object.keys(FRUITS).map((key) => (
              <SelectItem key={key} value={key}>
                {FRUITS[key]}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  );
}

function SonnerDemo() {
  return (
    <div className="flex flex-wrap gap-3">
      <Button variant="outline" onClick={() => toast("Event has been created")}>
        Default
      </Button>
      <Button variant="outline" onClick={() => toast.success("Saved successfully")}>
        Success
      </Button>
      <Button variant="outline" onClick={() => toast.error("Something went wrong")}>
        Error
      </Button>
      <Button variant="outline" onClick={() => toast.warning("Check your input")}>
        Warning
      </Button>
      <Button variant="outline" onClick={() => toast.info("A new version is available")}>
        Info
      </Button>
      <Toaster />
    </div>
  );
}

export default function ZebUiOverlays2Section() {
  return (
    <div>
      <SectionHeading
        title="zeb/ui · Overlays (2/2)"
        description="Anchored panels, the two menus, Select, and Sonner. Part 1 (zeb-ui-overlays.tsx) has the hooks and the modal-panel family."
      />

      <Entry
        name="Popover"
        file="zeb/ui/popover"
        description="Opens on click; closes on outside click, focus leaving both trigger and panel, or Escape. Anchored with useAnchoredPosition (side/align/sideOffset live on PopoverContent), which flips side when it would run off-screen and portals to document.body."
        code={`<Popover>
  <PopoverTrigger>Open popover</PopoverTrigger>
  <PopoverContent>
    <p className="font-medium">Dimensions</p>
    <p className="text-muted-foreground">Set the width and height.</p>
  </PopoverContent>
</Popover>`}
      >
        <PopoverDemo />
      </Entry>

      <Entry
        name="Tooltip"
        file="zeb/ui/tooltip"
        description="Opens on hover or focus after delayDuration; Escape dismisses it. Hiding is delayed and cancelled while the pointer is over the panel, so you can move the pointer onto the tooltip without it closing first. No TooltipProvider — delayDuration is a prop on each Tooltip instead."
        code={`<Tooltip delayDuration={200}>
  <TooltipTrigger><button>Hover or focus me</button></TooltipTrigger>
  <TooltipContent>Add to library</TooltipContent>
</Tooltip>`}
      >
        <TooltipDemo />
      </Entry>

      <Entry
        name="HoverCard"
        file="zeb/ui/hover-card"
        description="Opens 700ms after the pointer enters the trigger or the card itself; closes 150ms after it leaves both."
        code={`<HoverCard>
  <HoverCardTrigger><a href="/zebflow">@zebflow</a></HoverCardTrigger>
  <HoverCardContent>
    <p className="font-semibold">Zebflow</p>
    <p className="text-muted-foreground">Joined 2019.</p>
  </HoverCardContent>
</HoverCard>`}
      >
        <HoverCardDemo />
      </Entry>

      <Entry
        name="DropdownMenu"
        file="zeb/ui/dropdown-menu"
        description="Arrow keys move real DOM focus between items; Enter/Space activates; Tab closes the menu and moves on to the next tab stop (without trapping focus inside it); Escape and outside click close it too. A checkbox item stays open and toggles on click instead of dismissing."
        code={`<DropdownMenu>
  <DropdownMenuTrigger>Options</DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuLabel>My account</DropdownMenuLabel>
    <DropdownMenuSeparator />
    <DropdownMenuCheckboxItem checked={bookmarked} onCheckedChange={setBookmarked}>Bookmarked</DropdownMenuCheckboxItem>
    <DropdownMenuItem variant="destructive">Delete account</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>`}
      >
        <DropdownMenuDemo />
      </Entry>

      <Entry
        name="ContextMenu"
        file="zeb/ui/context-menu"
        description="Opens at the pointer on a right-click (contextmenu), not anchored to the trigger element. Same item types and keyboard support as DropdownMenu."
        code={`<ContextMenu>
  <ContextMenuTrigger>Right click here</ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem>Back</ContextMenuItem>
    <ContextMenuItem variant="destructive">Delete</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>`}
      >
        <ContextMenuDemo />
      </Entry>

      <Entry
        name="Select"
        file="zeb/ui/select"
        description="A listbox (role=listbox/option), not the native element — upstream's is too. Select owns the selected value through context (controlled with value/onValueChange, uncontrolled with defaultValue) the same way RadioGroup injects checked, so SelectItem only needs its own value. On open, focus moves to the selected option (else the first); ArrowUp/ArrowDown on the trigger opens the listbox too."
        code={`<Select value={value} onValueChange={setValue}>
  <SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      <SelectItem value="blueberry">Blueberry</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>`}
      >
        <SelectDemo />
      </Entry>

      <Entry
        name="Sonner"
        file="zeb/ui/sonner"
        description="No npm sonner underneath — toast() and Toaster talk through a module-level subscriber list instead of props. Mount exactly one Toaster per page."
        code={`import { toast, Toaster } from "zeb/ui/sonner";

<Button onClick={() => toast.success("Saved successfully")}>Save</Button>
<Toaster /> {/* once, near the root */}`}
      >
        <SonnerDemo />
      </Entry>
    </div>
  );
}