Koala logo Design
No matches for “
↑↓ navigate open Esc close
Components Drawers

Drawers

Right-docked companion panels — the non-modal counterpart to the modal and side tray. Drawers open into a shared dock that squeezes the page rather than covering it: one drawer fills the dock, two split it, and any beyond that collapse to an icon rail. Below lg a drawer becomes a full-screen sheet with a scrim. One controller (koalaDrawerDock) and one shell (<koala-drawer-dock>) power every surface — the Portal comms rail (Joey + softphone) mounts the exact same component.

<koala-drawer>

A row opens a drawer by { kind, id }; the dock resolves its icon, title and content and docks it beside the feed. Open several — two share the width in full, more collapse to the icon rail (page with the chevrons or jump by icon; hover an icon to preview). Drag the seam to resize. Content persists when a drawer closes, so a stateful drawer (the softphone's live call) survives close/reopen.

7b Station Road

The feed keeps working while drawers are open beside it — open any row.

Tasks

3 open
Chase Searchflow High · due tomorrow
Order office copies Medium · due Friday
Approve the draft plan Low · no due date

Messages

2 unread
Mortgage offer issued Halifax · 4h ago
Quick question on completion J. Okafor · 1d ago
Keys ready for collection Bramley & Co · 2d ago

Documents

3 files
Draft contract pack PDF · 14 pages
Local authority search PDF · 8 pages
TA6 property information PDF · 6 pages
<div x-data="koalaDrawerDock({ iconRegistryId: 'dock-icons',
                                iconKinds: ['task','message','document','close','prev','next'],
                                title: d => catalogue[d.kind+':'+d.id].title,
                                meta:  d => catalogue[d.kind+':'+d.id].meta })"
     x-ref="row" class="flex">

    <div class="flex-1 min-w-0">
        @* the page — any row opens a companion drawer *@
        <tr x-on:click="openDrawer('task','searchflow')">…</tr>
    </div>

    <div x-ref="handle" class="ws-drawer-resize-handle"
         x-bind:style="drawers.length ? 'display:flex' : 'display:none'">
        <span class="ws-drawer-resize-grip"></span>
    </div>

    <koala-drawer-dock />  @* THE dock: columns, rail, paging, resize, preview *@

    <div x-ref="contentHolder" class="hidden">
        <div data-drawer-content="task:searchflow">…</div>  @* moved into the column; state persists *@
    </div>
</div>

Mounted once and opened from anywhere (like modals / side trays): call openDrawer(kind, id) from any row. The host supplies the icon registry, the row (x-ref="row"), the resize handle and the [data-drawer-content] nodes; the dock owns everything else.

3 behaviours

Width-sharing

Visible drawers share the dock exactly — one fills it, two split it evenly. Never a cropped column.

Overflow rail

Drawers that don't fit collapse to the bottom icon rail. Page with the chevrons or jump by icon; the track book-scrolls to the target.

Resize

Drag the seam to set the dock width; the page keeps pageMinWidth. Columns re-share as it widens or narrows.

3 states
Closed
Dock width 0 — the page has the full width. Content nodes wait, parked in the holder.
Open (desktop)
Docks at its width and squeezes the page beside it. No scrim — non-modal, you keep working the page.
Open (mobile < lg)
Full-screen sheet with a scrim; ESC / backdrop dismiss. Switch drawers from the bottom nav.
koalaDrawerDock(config)

The behaviour is configured once where the controller is declared on the row. Everything is optional except the icon registry.

Attribute Values Notes
iconRegistryId string Id of a hidden element holding one [data-drawer-icon="kind"] span per kind (server-rendered <koala-icon>). The dock captures the SVG at init.
iconKinds string[] Kinds to capture from the registry (include close, prev, next for the chrome).
title (drawer) => string Resolves a drawer's header title from its { kind, id }. AJAX-fed in the app.
meta (drawer) => string Resolves the small header sub-line (the mono reference / timestamp).
columnWidth number = 320 Minimum full width (px) of one visible column — decides how many fit before overflow.
maxAutoColumns number = 2 How many columns the dock auto-sizes to before you drag to widen it.
pageMinWidth number = 320 Width (px) the squeezed page must keep — caps how wide the dock can grow.

The base <koala-drawer> primitive (used directly for a single fixed drawer, and under the hood for the mobile sheet) takes:

Attribute Values Notes
flag string Alpine boolean controlling open/closed (e.g. $store.call.open). The parent scope must declare it.
width CSS length Docked width on desktop. Defaults to 24rem. Mobile is always a full-screen sheet.
aria-label string Accessible label when no title is set. Falls back to Panel.
3 helpers
Tag Provides Notes
<koala-drawer-dock> the whole dock The shared shell: the column window, icon rail, prev/next paging, resize seam wiring and hover preview. Paired with the koalaDrawerDock() controller. Drop it once inside the x-ref="row"; open drawers with openDrawer(kind, id).
<koala-drawer-header> flag (string) Title row with a close button, for a single base <koala-drawer> that needs a richer header (icon + title + mono reference). flag mirrors the parent so the close-X clears it.
<koala-drawer-content> class (string?) Scrollable body (flex-1 overflow-y-auto) for a base drawer. Optional — content that scrolls itself (the softphone panel) can be a direct child instead.
Do Use a drawer for a companion surface you keep open while working the page (Joey, the softphone) — it squeezes, so the page stays usable, and its state survives close/reopen.
Don't Don't hand-roll a second dock or drawer controller — there is exactly one (koalaDrawerDock + ). A parallel implementation is drift; check-design fails on inline dock/drawer factories.
Don't Don't use a drawer for a focused decision that must block the page — that's a modal (scrim, inert background) or, for editing one entity, a side tray.