Layout

Resizable.

Panels split by draggable handles, for editors and dashboards. Built on react-resizable-panels.

MonitorLeft
TerminalRight

Installation#

With the CLI

$npx lanterncn add resizable

This also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/resizable. See Installation if your project is not set up yet.

Manually

  1. Install the dependencies

    npm install react-resizable-panels lucide-react
  2. Copy the source into your project

    components/ui/resizable.tsx
    "use client";
    
    import * as React from "react";
    import { GripVerticalIcon } from "lucide-react";
    import * as ResizablePrimitive from "react-resizable-panels";
    
    import { cn } from "@/lib/utils";
    
    function ResizablePanelGroup({ className, ...props }: ResizablePrimitive.GroupProps) {
      return (
        <ResizablePrimitive.Group data-slot="resizable-panel-group" className={cn("h-full w-full", className)} {...props} />
      );
    }
    
    function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
      return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
    }
    
    function ResizableHandle({
      withHandle,
      className,
      ...props
    }: ResizablePrimitive.SeparatorProps & { withHandle?: boolean }) {
      return (
        <ResizablePrimitive.Separator
          data-slot="resizable-handle"
          className={cn(
            // A 1px line with a wider invisible hit area. In a vertical group the separator reports aria-orientation="horizontal".
            "group/handle relative flex w-px items-center justify-center bg-border transition-colors outline-none",
            "after:absolute after:inset-y-0 after:left-1/2 after:w-2 after:-translate-x-1/2",
            "hover:bg-primary/70 focus-visible:bg-primary data-[separator=active]:bg-primary data-[separator=focus]:bg-primary data-[separator=hover]:bg-primary/70",
            "aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full",
            "aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-2 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2",
            "[&[aria-orientation=horizontal]>div]:rotate-90",
            className,
          )}
          {...props}
        >
          {withHandle && (
            <div className="z-10 flex h-5 w-3 items-center justify-center rounded-sm border border-input bg-secondary text-muted-foreground transition-colors group-hover/handle:border-primary group-hover/handle:text-primary group-focus-visible/handle:border-primary group-focus-visible/handle:text-primary group-data-[separator=active]/handle:border-primary group-data-[separator=active]/handle:text-primary">
              <GripVerticalIcon className="size-2.5" />
            </div>
          )}
        </ResizablePrimitive.Separator>
      );
    }
    
    export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
  3. Update the import paths to match your project

    The source imports cn from @/lib/utils.

Usage#

import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/components/ui/resizable";
<ResizablePanelGroup orientation="horizontal">
  <ResizablePanel defaultSize="40%">Monitor</ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize="60%">Terminal</ResizablePanel>
</ResizablePanelGroup>

This uses react-resizable-panels v4. The group takes orientation (not direction). Sizes given as numbers are pixels; use strings like "40%" for percentages.

Handles are keyboard accessible: focus one with Tab and use the arrow keys. Double-click a handle to reset its panel.

Examples#

Vertical

Guestbook
Entries

Nested editor

Files
  • startup.lua
  • hub.lua
  • guestbook.lua
  • mine.lua
hub.lua
1local modem = peripheral.find("modem")
2local hub = require("hub")
3
4while true do
5 local id, msg = rednet.receive("lantern")
6 hub.handle(id, msg)
7end
Console
$ hub
Listening on lantern-main