Primitives
Menu — collapsible sidebar
`collapsible` narrows the menu to its icons: names in tooltips, counts on the icons. `collapsedMode="hidden"` folds it away instead.
The button beside the title collapses it.
Gulshan Parking
sessions
Picked from the icons, a branch (Payments) opens the menu with it.
menu-collapsible.tsx
"use client";
import { Menu, MenuGroup, MenuItem, MenuSub, Segmented, type MenuLook } from "@waveinno/ui";
import { Car, DoorOpen, LayoutDashboard, Settings, Users, Wallet } from "lucide-react";
import * as React from "react";
const SIDEBAR_LOOKS: { value: MenuLook; label: string }[] = [
{ value: "soft", label: "soft" },
{ value: "raised", label: "raised" },
{ value: "gradient", label: "gradient" },
{ value: "dark", label: "dark" },
];
export default function CollapsibleDemo() {
const [look, setLook] = React.useState<MenuLook>("soft");
const [page, setPage] = React.useState("sessions");
const [collapsed, setCollapsed] = React.useState(false);
return (
<div className="space-y-3">
<div className="flex flex-wrap items-center gap-3">
<Segmented ariaLabel="Look" value={look} onChange={setLook} options={SIDEBAR_LOOKS} />
<span className="text-[12px] text-muted">{collapsed ? "Collapsed: hover an icon for its name." : "The button beside the title collapses it."}</span>
</div>
<div className="flex h-[460px] gap-3 overflow-hidden rounded-card border border-border bg-surface p-2">
<Menu
variant={look}
value={page}
onValueChange={setPage}
heading="Gulshan Parking"
collapsible
collapsed={collapsed}
onCollapsedChange={setCollapsed}
aria-label="Sidebar"
className="w-60"
>
<MenuGroup label="Workspace">
<MenuItem value="overview" icon={<LayoutDashboard />}>
Overview
</MenuItem>
<MenuItem value="sessions" icon={<Car />} count={12}>
Live sessions
</MenuItem>
<MenuItem value="gates" icon={<DoorOpen />}>
Gates
</MenuItem>
<MenuSub label="Payments" icon={<Wallet />}>
<MenuItem value="cash">Cash shifts</MenuItem>
<MenuItem value="bkash">bKash</MenuItem>
<MenuItem value="payouts">Payouts</MenuItem>
</MenuSub>
</MenuGroup>
<MenuGroup label="Account">
<MenuItem value="team" icon={<Users />}>
Team
</MenuItem>
<MenuItem value="settings" icon={<Settings />}>
Settings
</MenuItem>
</MenuGroup>
</Menu>
<div className="min-w-0 flex-1 rounded-[calc(10px*var(--radius-scale))] bg-canvas p-5">
<p className="ov">Gulshan Parking</p>
<p className="disp mt-1 text-[18px] font-semibold capitalize text-text">{page}</p>
<p className="mt-1 text-[13px] text-muted">Picked from the icons, a branch (Payments) opens the menu with it.</p>
</div>
</div>
</div>
);
}Import#
Everything comes from the package root. The example above uses:
your-component.tsx
import { Menu, MenuGroup, MenuItem, MenuSub, Segmented, type MenuLook } from "@waveinno/ui";Props#
Read from the source of this version, so they match the code exactly.
<Menu>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
variant | "soft" | "line" | "raised" | "gradient" | "dark" | "panel" | "rail" | "soft" | |
value | string | — | The chosen item's `value` (controlled). |
defaultValue | string | — | |
onValueChange | (value: string) => void | — | |
heading | React.ReactNode | — | On top of the list: a title, or in the rail, the product mark. |
collapsible | boolean | — | A collapse toggle in the heading row. |
collapsed | boolean | — | |
defaultCollapsed | boolean | false | |
onCollapsedChange | (collapsed: boolean) => void | — | |
collapsedMode | "icons" | "hidden" | "icons" | `icons`: narrow to the icons. `hidden`: fold away entirely (toggle it from outside). |
collapseShortcut | boolean | false | ⌘B / Ctrl+B toggles it. Off by default: AppShell already uses ⌘B for its panel. |
Also takes Omit<React.HTMLAttributes<HTMLElement>, "defaultValue" | "onChange">.
<MenuGroup>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
label | React.ReactNode | — | |
action | React.ReactNode | — | Something small on the label's right: a "+" button, a count. |
className | string | — | |
childrenrequired | React.ReactNode | — |
<MenuItem>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | |
description | React.ReactNode | — | Second line, muted: what is behind the item. Puts the icon in a tile. |
badge | React.ReactNode | — | |
count | number | string | — | A number on the right: unread, pending, items inside. |
shortcut | string | — | |
value | string | — | Chosen when it matches Menu's `value`; clicking makes it the value. |
active | boolean | — | Mark it chosen yourself, e.g. from the router's pathname. Overrides `value`. |
danger | boolean | — | |
asChild | boolean | — | Render your own element (a Next.js Link): `<MenuItem asChild><Link href="/x">Label</Link></MenuItem>`. |
Also takes Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "value">.
<MenuSub>
src/components/ui/menu.tsx| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | |
description | React.ReactNode | — | Second line, muted: what is behind the item. Puts the icon in a tile. |
badge | React.ReactNode | — | |
count | number | string | — | A number on the right: unread, pending, items inside. |
shortcut | string | — | |
labelrequired | React.ReactNode | — | |
defaultOpen | boolean | false | |
open | boolean | — | |
onOpenChange | (open: boolean) => void | — | |
disabled | boolean | — | |
className | string | — | |
childrenrequired | React.ReactNode | — |
<Segmented>
src/components/ui/segmented.tsx| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | T | — | |
onChangerequired | (v: T) => void | — | |
optionsrequired | SegmentedOption<T>[] | — | |
className | string | — | |
ariaLabel | string | — | |
variant | keyof typeof TRACK | "pill" | |
size | keyof typeof SIZES | "sm" | |
fullWidth | boolean | false | Stretch to the container, options sharing the width equally. |
Install#
If the project does not have the package yet, add it, then follow the setup for Next.js or Vite once.
$ npm install @waveinno/ui