[ @sdxc/ui ]
Drawer
A preset of Dialog docked flush against one physical edge of the viewport, sized to fill that edge, sliding into place from it on open and fading out with a short drift toward it on close.
View code
let lines: CartLine[] = CART_LINES.map((line) => ({ ...line }));
function changeQuantity(event: Event & { currentTarget: HTMLInputElement }) {
let line = lines.find((candidate) => candidate.id === event.currentTarget.dataset.line);
if (!line) return;
line.quantity = Math.max(1, Number(event.currentTarget.value) || 1);
void handle.update();
}
let totals = cartTotals(lines);
<Button commandfor="cart" command="show-modal">
<ShoppingCartIcon />
Cart · {totals.itemCount}
</Button>
<Drawer id="cart" placement="right" aria-labelledby="cart-title">
<Drawer.Header>
<Drawer.Title id="cart-title">Your cart</Drawer.Title>
<Drawer.Description>
{totals.freeShipping ? "Shipping is on us on this order." : "Spend $150 to get free shipping."}
</Drawer.Description>
</Drawer.Header>
<div
mix={[
vstack({ gap: 4, align: "stretch" }),
// The dock is as tall as the viewport, so everything between the header and the
// footer scrolls as one column — the checkout button never slides out of reach.
grow(),
minBs(0),
overflowY("auto"),
]}
>
{lines.map((line) => (
<Item key={line.id}>
<Item.Content>
<Item.Title>{line.name}</Item.Title>
<Item.Description>{line.variant}</Item.Description>
<div mix={[hstack({ gap: 2, align: "center", justify: "between" }), mbs(1)]}>
<NumberField.Group mix={[stepper()]}>
<NumberField.DecrementButton
command={NUMBER_FIELD_STEP_DOWN_COMMAND}
commandfor={`cart-quantity-${line.id}`}
aria-label={`One fewer ${line.name}`}
/>
<NumberField.Input
id={`cart-quantity-${line.id}`}
name={`quantity[${line.id}]`}
data-line={line.id}
min={1}
max={99}
value={String(line.quantity)}
aria-label={`Quantity of ${line.name}`}
mix={[on<HTMLInputElement, "input">("input", changeQuantity)]}
/>
<NumberField.IncrementButton
command={NUMBER_FIELD_STEP_UP_COMMAND}
commandfor={`cart-quantity-${line.id}`}
aria-label={`One more ${line.name}`}
/>
</NumberField.Group>
<span>{formatPrice(line.unitPrice * line.quantity)}</span>
</div>
</Item.Content>
</Item>
))}
<Separator />
<Text>Subtotal</Text>
<Text>{totals.subtotal}</Text>
<Text>Shipping</Text>
<Text>{totals.shipping}</Text>
<Text>Total</Text>
<Text>{totals.total}</Text>
</div>
<Drawer.Footer>
<Button commandfor="cart" command="close" variant="outline" color="neutral">
Keep shopping
</Button>
<Button commandfor="cart" command="close">Checkout · {totals.total}</Button>
</Drawer.Footer>
<Drawer.Close commandfor="cart" aria-label="Close the cart" />
</Drawer>Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/ui remiximport "@sdxc/ui/theme.css";Usage
import { Drawer } from "@sdxc/ui";Renders Dialog pinned to one edge of the viewport, animating a
transform off the native open attribute so @starting-style can
animate the close; a mix prop composes alongside this placement styling.
Examples
Bottom filters
View code
<Button commandfor="filters" command="show-modal" variant="outline">
<ListFilterIcon />
Filters
</Button>
<Drawer id="filters" placement="bottom" aria-labelledby="filters-title">
<Drawer.Header>
<Drawer.Title id="filters-title">Filter issues</Drawer.Title>
<Drawer.Description>Show only the issues that match every choice.</Drawer.Description>
</Drawer.Header>
<div mix={[vstack({ gap: 3, align: "start" })]}>
<Checkbox name="assigned" defaultChecked>Assigned to me</Checkbox>
<Checkbox name="open">Open only</Checkbox>
<Checkbox name="priority">High priority</Checkbox>
</div>
<Drawer.Footer>
<Button commandfor="filters" command="close">Apply filters</Button>
</Drawer.Footer>
</Drawer>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Description |
|---|---|---|
placement? | "top" | "right" | "bottom" | "left" | Physical edge of the viewport to dock against and slide in from. Defaults to DEFAULT_PLACEMENT. |
Also accepts everything in Dialog.Props.
Types
| Name | Values | What it decides |
|---|---|---|
Placement | "top" | "right" | "bottom" | "left" | Physical edge of the viewport the panel docks against and slides in from. |