PaneTabs
The switch between two panes in one swipe: Chat | Inbox in the app, Chat | Preview in setup. The white thumb rides the swipe itself, bound to the panes' scroll; a tap slides to that pane.
Large
The default, in the app's header on a phone: 176px wide, 32px tall (36 to a finger), its words at text-sm. Swipe the panes or tap a tab.
Chat
Inbox
import { PaneTabs } from "@/components/shared/pane-tabs";
const panes = useRef<HTMLDivElement>(null);
<PaneTabs
panes={panes}
tabs={[
{ id: "chat", label: "Chat" },
{ id: "app", label: "Inbox" },
]}
/>
<div ref={panes} className="flex snap-x snap-mandatory overflow-x-auto">
<div id="pane-chat" className="w-full shrink-0 snap-start">…</div>
<div id="pane-app" className="w-full shrink-0 snap-start">…</div>
</div>Small
Narrower (160px) and quieter (text-xs), for inside a card or a header that's already busy. Still 32px to a finger.
Chat
Inbox
<PaneTabs panes={panes} tabs={tabs} size="sm" />As a filter
Without panes, a choice of two the parent holds (value, onSelect), the thumb sliding to it on the motion scale (duration-fast, ease-smooth-out). The Inbox's All | Needs you, under its title, filling the list (fill).
const [filter, setFilter] = useState<0 | 1>(0);
<PaneTabs
size="sm"
fill
value={filter}
onSelect={setFilter}
tabs={[
{ id: "all", label: "All" },
{ id: "needs", label: "Needs you", shows: "Needs you · 4" },
]}
/>Guidelines
- Two panes only, side by side in one scroller: each pane is the element with id
pane-<tab id>. - A tab can show what's in its pane instead of its name (
shows), as the app's tab names the page in view.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| panes | RefObject<HTMLDivElement> | — | The scroller holding the two panes. |
| tabs | [PaneTab, PaneTab] | — | Each pane's id and label; shows, to name what's in it instead. |
| value · onSelect | 0 | 1 · (index) => void | — | Instead of panes: a filter of two, its chosen tab held by the parent. |
| fill | boolean | false | Spans its container (under a list's title) instead of its own width. |
| size | "lg" | "sm" | "lg" | lg under the app's header; sm inside a card or a busy header. |