@xsolla/xui-b2b-drawer
v0.161.3
Published
A slide-in panel that opens from the right edge of the screen for B2B settings, multi-step flows, and auxiliary content. Supports an optional stepper sidebar for guided wizards.
Readme
Drawer
A slide-in panel that opens from the right edge of the screen for B2B settings, multi-step flows, and auxiliary content. Supports an optional stepper sidebar for guided wizards.
Installation
npm install @xsolla/xui-b2b-drawerImports
import {
Drawer,
useDrawer,
type DrawerProps,
type DrawerSize,
type DrawerFooterAlign,
type UseDrawerOptions,
type UseDrawerReturn,
} from "@xsolla/xui-b2b-drawer";Quick start
import { useState } from "react";
import { Drawer } from "@xsolla/xui-b2b-drawer";
import { Button } from "@xsolla/xui-button";
import { Typography } from "@xsolla/xui-typography";
function BasicDrawer() {
const [open, setOpen] = useState(false);
return (
<>
<Button
variant="primary"
tone="brand"
size="sm"
onPress={() => setOpen(true)}
>
Open drawer
</Button>
<Drawer
open={open}
onClose={() => setOpen(false)}
title="Settings"
footer={
<>
<Button
variant="secondary"
tone="mono"
size="sm"
onPress={() => setOpen(false)}
>
Cancel
</Button>
<Button
variant="primary"
tone="brand"
size="sm"
onPress={() => setOpen(false)}
>
Save
</Button>
</>
}
>
<Typography variant="bodyMd">Drawer body content goes here.</Typography>
</Drawer>
</>
);
}API Reference
<Drawer>
| Prop | Type | Default | Description |
| --------------------- | ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------- |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
| open | boolean | false | Whether the drawer is visible. |
| onClose | () => void | — | Called when the drawer should close (Escape, overlay click, close button). |
| size | DrawerSize | "md" | Width preset: sm 480px, md 620px, lg 1056px. |
| title | ReactNode | — | Title displayed in the header. |
| onBack | () => void | — | When provided, renders a back arrow button in the header. |
| headerAction | ReactNode | — | Element rendered between the title and the close button. |
| closeOnOverlayClick | boolean | true | Whether clicking the backdrop closes the drawer. |
| closeOnEscape | boolean | true | Whether pressing Escape closes the drawer. |
| footer | ReactNode | — | Footer content (typically action buttons). |
| footerAlign | DrawerFooterAlign | "right" | Alignment of footer content. |
| footerShadow | boolean | false | Show a drop shadow above the footer (useful with scrollable content). |
| footerFullWidth | boolean | true | Whether footer buttons stretch to full width. |
| stepper | ReactNode | — | Stepper sidebar rendered to the left of the content panel. |
| children | ReactNode | — | Required. Main scrollable body content. |
| initialFocusRef | RefObject<HTMLElement> | — | Ref to the element that should receive focus when the drawer opens. |
Inherits ThemeOverrideProps (themeMode, themeProductContext).
Deprecated props
| Prop | Replacement |
| ------------------------------ | ------------------ |
| isOpen | open |
| header ({ title, onBack }) | title + onBack |
| bottom | footer |
useDrawer(options?)
Convenience hook that owns local open/close state for a drawer.
function useDrawer(options?: UseDrawerOptions): UseDrawerReturn;
interface UseDrawerOptions {
onOpen?: () => void;
onClose?: () => void;
}
interface UseDrawerReturn {
isOpen: boolean;
open: () => void;
close: () => void;
}onOpen / onClose fire after the internal state flips — use them to coordinate analytics or external state.
Type exports
type DrawerSize = "sm" | "md" | "lg";
type DrawerFooterAlign = "center" | "right";Examples
Size variants
import { Drawer } from '@xsolla/xui-b2b-drawer';
<>
{/* sm = 480px, md = 620px (default), lg = 1056px */}
<Drawer open={open} onClose={onClose} title="Small" size="sm">{...}</Drawer>
<Drawer open={open} onClose={onClose} title="Medium" size="md">{...}</Drawer>
<Drawer open={open} onClose={onClose} title="Large" size="lg">{...}</Drawer>
</>;With back button
import { Drawer } from "@xsolla/xui-b2b-drawer";
<Drawer
open={open}
onClose={onClose}
title="Step details"
onBack={() => goToPreviousStep()}
>
{/* back arrow appears in the header */}
</Drawer>;With header action
import { Drawer } from "@xsolla/xui-b2b-drawer";
import { Button } from "@xsolla/xui-button";
<Drawer
open={open}
onClose={onClose}
title="User profile"
headerAction={
<Button variant="ghost" tone="brand" size="sm">
View history
</Button>
}
>
{/* action renders between the title and the close button */}
</Drawer>;useDrawer hook
import { Drawer, useDrawer } from "@xsolla/xui-b2b-drawer";
import { Button } from "@xsolla/xui-button";
import { Typography } from "@xsolla/xui-typography";
function HookExample() {
const drawer = useDrawer({ onOpen: () => console.log("opened") });
return (
<>
<Button variant="primary" tone="brand" size="sm" onPress={drawer.open}>
Open
</Button>
<Drawer open={drawer.isOpen} onClose={drawer.close} title="Hello">
<Typography variant="bodyMd">Driven by useDrawer.</Typography>
</Drawer>
</>
);
}With stepper sidebar
import { useState } from "react";
import { Drawer } from "@xsolla/xui-b2b-drawer";
import { Stepper } from "@xsolla/xui-b2b-stepper";
import { Button } from "@xsolla/xui-button";
import { Typography } from "@xsolla/xui-typography";
const STEPS = [
{ title: "Account", description: "Name and email" },
{ title: "Billing", description: "Payment method" },
{ title: "Confirm", description: "Review and submit" },
];
function StepperDrawer() {
const [open, setOpen] = useState(false);
const [active, setActive] = useState(0);
const steps = STEPS.map((s, i) => ({
...s,
state:
i < active
? ("complete" as const)
: i === active
? ("current" as const)
: ("incomplete" as const),
}));
const isLast = active === steps.length - 1;
return (
<>
<Button
variant="primary"
tone="brand"
size="sm"
onPress={() => {
setActive(0);
setOpen(true);
}}
>
Open wizard
</Button>
<Drawer
open={open}
onClose={() => setOpen(false)}
title={STEPS[active].title}
onBack={active > 0 ? () => setActive((a) => a - 1) : undefined}
stepper={
<Stepper
steps={steps}
direction="vertical"
surface
onClick={({ number }) => setActive(number - 1)}
/>
}
footer={
<>
<Button
variant="secondary"
tone="mono"
size="sm"
onPress={() => setOpen(false)}
>
Cancel
</Button>
<Button
variant="primary"
tone="brand"
size="sm"
onPress={() =>
isLast ? setOpen(false) : setActive((a) => a + 1)
}
>
{isLast ? "Submit" : "Next"}
</Button>
</>
}
>
<Typography variant="bodyMd">Content for step {active + 1}.</Typography>
</Drawer>
</>
);
}Scrollable content with footer shadow
import { Drawer } from "@xsolla/xui-b2b-drawer";
import { Button } from "@xsolla/xui-button";
import { Typography } from "@xsolla/xui-typography";
<Drawer
open={open}
onClose={onClose}
title="Long content"
footerShadow
footer={
<Button variant="primary" tone="brand" size="sm">
Done
</Button>
}
>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{Array.from({ length: 40 }, (_, i) => (
<Typography key={i} variant="bodyMd">
Item {i + 1}
</Typography>
))}
</div>
</Drawer>;Accessibility
- Drawer panel is
role="dialog"witharia-modal="true". aria-labelis set to thetitlewhen it is a string. If you pass a non-stringtitleand need a label, supply your own labelling via the surrounding markup or wrap the title text in a string node.- Focus is moved to
initialFocusRef(or the close button, or the first focusable element) on open, and restored to the previously active element after the exit animation completes. - Tab is trapped inside the drawer; Shift+Tab wraps to the last focusable element, Tab wraps back to the first.
- Escape closes the drawer unless
closeOnEscape={false}. - Close and back buttons in the header have accessible labels.
Behaviour
- Slides in from the right with an animated transition.
- Renders in a portal layer (z-index from
theme.sizing.drawer().zIndex) so it appears above page content. - When
stepperis provided, the panel widens to render the sidebar to the left of the content area inside the same surface.
