sdxc

Type to search, or start from one of these:

Sheet

An edge-docked panel that renders through Dialog.

Your cart

3 items · billed yearly

Pro planAnnual · 5 seats$480
Extra monitors20 monitors$96
Priority supportBusiness hours$240
Total$816
View code
<Button commandfor="preview-cart" command="show-modal">
	Open cart
	<Badge variant="secondary">{lines.length}</Badge>
	<Keyboard>⌘B</Keyboard>
</Button>

<Sheet
	id="preview-cart"
	side="right"
	aria-labelledby="preview-cart-title"
	// A docked column fills the edge it is docked to; a dialog's own block size
	// hugs its content, so the panel states the full height it wants.
	mix={[hotkey("mod+b"), bs("100%")]}
>
	<Sheet.Header>
		<Sheet.Title id="preview-cart-title">Your cart</Sheet.Title>
		<Sheet.Description>
			{lines.length} {lines.length === 1 ? "item" : "items"} · billed yearly
		</Sheet.Description>
	</Sheet.Header>

	<div mix={[vstack({ gap: 3, align: "stretch" }), is("100%")]}>
		{lines.length === 0 ? (
			<Empty>
				<Empty.Title>Your cart is empty</Empty.Title>
				<Empty.Description>Put the sample lines back to look again.</Empty.Description>
				<Empty.Action>
					<Button
						size="sm"
						variant="outline"
						mix={[on<HTMLButtonElement, "click">("click", restore)]}
					>
						Restore the cart
					</Button>
				</Empty.Action>
			</Empty>
		) : (
			lines.map((line) => (
				<div key={line.id} mix={[vstack({ gap: 3, align: "stretch" })]}>
					<div mix={[hstack({ gap: 3, align: "center", justify: "between" })]}>
						<span mix={[vstack({ gap: 0, align: "start" }), css({ flexGrow: "1" })]}>
							<span mix={[text("sm"), weight("medium")]}>{line.name}</span>
							<Text>{line.detail}</Text>
						</span>
						<span mix={[text("sm"), weight("medium")]}>${line.price}</span>
						<Button
							variant="ghost"
							size="sm"
							aria-label={`Remove ${line.name}`}
							mix={[on<HTMLButtonElement, "click">("click", () => remove(line.id))]}
						>
							<Trash2Icon />
						</Button>
					</div>
					<Separator />
				</div>
			))
		)}

		<div mix={[hstack({ gap: 2, align: "center", justify: "between" })]}>
			<Text>Total</Text>
			<span mix={[text("lg"), weight("semibold")]}>${total}</span>
		</div>
	</div>

	<Sheet.Footer>
		<Button commandfor="preview-cart" command="close" variant="outline">
			Keep shopping
		</Button>
		<Button commandfor="preview-cart" command="close" disabled={lines.length === 0}>
			Check out
		</Button>
	</Sheet.Footer>
	<Sheet.Close commandfor="preview-cart" aria-label="Close the cart" />
</Sheet>

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

npm add @sdxc/ui
import "@sdxc/ui/theme.css";

Usage

import { Sheet } from "@sdxc/ui";

Renders the panel through Dialog: fixed, pinned to whichever inline edge side names with the far edge released to auto, and slid via @starting-style/allow-discrete so the close animates too.

Composition

The parts the component publishes, each a static property of the host.

  • Sheet
  • Sheet.HeaderRenders Sheet.HeaderProps.children as the panel's header slot: a tightly-stacked column of Sheet.Title and Sheet.Description, start-aligned at every width rather than centering like Dialog.Header.
  • Sheet.DescriptionRenders Sheet.DescriptionProps.children as the panel's supporting copy, in a native <p> set to the panel's own foreground color rather than Dialog.Description's more muted one.
  • Sheet.FooterRenders Sheet.FooterProps.children as the panel's action row: a single end-aligned row pinned to the block-end edge at every width, unlike Dialog.Footer's column that only becomes a row as its container grows.

Examples

<Sheet id="filters" side="left" aria-labelledby="filters-title">
	<Sheet.Header>
		<Sheet.Title id="filters-title">{t("filters.title")}</Sheet.Title>
	</Sheet.Header>
</Sheet>

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

PropTypeDescription
side?"left" | "right"Physical side of the viewport to dock to. Defaults to DEFAULT_SIDE.

Also accepts everything in Dialog.Props.

Sheet.Header

Also accepts everything in Dialog.HeaderProps.

Sheet.Description

Also accepts everything in Dialog.DescriptionProps.

Also accepts everything in Dialog.FooterProps.

Types

NameValuesWhat it decides
Side"left" | "right"Physical side of the viewport the panel docks to and slides in from, fixed rather than reading-direction-relative so it keeps docking to the same edge under any dir value, matching the safe-area padding's edge.