# Examples
Common usage patterns for the `ZestResponsiveLayout` component.
---
## Table of Contents
- [Basic Layout](#basic-layout)
- [Custom Desktop Width](#custom-desktop-width)
- [Mobile Breakpoint Configuration](#mobile-breakpoint-configuration)
- [Animation Control](#animation-control)
- [Desktop Overlay Configuration](#desktop-overlay-configuration)
- [State Preservation with keepMounted](#state-preservation-with-keepmounted)
- [Side Pane Stacking (Nested Views)](#side-pane-stacking-nested-views)
- [Function Components with useSidePane](#function-components-with-usesidepane)
- [Class Components with withSidePane HOC](#class-components-with-withsidepane-hoc)
- [Class Components with SidePaneConsumer](#class-components-with-sidepaneconsumer)
- [Deep Nesting and Data Preservation](#deep-nesting-and-data-preservation)
- [Nesting with Form State Preservation](#nesting-with-form-state-preservation)
- [Custom Styling](#custom-styling)
---
## Basic Layout
A standard sidebar-and-content layout with automatic mobile responsiveness.
```tsx
import { ZestResponsiveLayout } from 'jattac.libs.web.zest-responsive-layout';
import { useState } from 'react';
const BasicApp = () => {
const [isOpen, setIsOpen] = useState(false);
return (
Home
Profile
,
onClose: () => setIsOpen(false)
}}
>
Main content goes here.
);
};
```
On viewports narrower than the configured breakpoint (default 768px), the side pane renders as a full-screen overlay that slides in from the right edge.
---
## Custom Desktop Width
```tsx
Fixed Width Sidebar
}}
>
Dynamic Content
```
The `sidePaneWidth` prop accepts any valid CSS length value, including `"30%"`, `"400px"`, or `"20vw"`.
---
## Mobile Breakpoint Configuration
```tsx
Responsive Menu
}}
>
Content
```
Use this to switch the layout to mobile mode at a higher or lower viewport width.
---
## Animation Control
```tsx
Sidebar
}}
>
Content
```
Disable the bounce animation for applications that require a more restrained visual style or need to conform to accessibility guidelines regarding reduced motion.
---
## Desktop Overlay Configuration
```tsx
Settings,
onClose: () => handleClose()
}}
>
Settings Dashboard
```
- `enableDesktopOverlay={false}` removes the dimming overlay, allowing the main content to remain fully interactive.
- `closeOnDesktopOverlayClick={false}` prevents the side pane from closing when the overlay area is clicked; users must use the close button.
---
## State Preservation with keepMounted
```tsx
,
onClose: () => setIsOpen(false)
}}
>
Main Content Area
```
When `keepMounted` is `true`, the side pane content remains in the DOM even while hidden. This is useful when the content includes forms, data grids, or other stateful components that would be expensive or disruptive to reinitialize on each open.
---
## Side Pane Stacking (Nested Views)
**Important:** Do not nest `ZestResponsiveLayout` components or place side panes inside other side panes. Doing so produces a cramped and unusable interface. Instead, use the side pane stack API.
The stack API allows an unlimited number of side panes to be pushed onto a stack. Only the topmost pane is visible at any time. When a pane is closed, the pane below it is revealed with its state fully preserved, including form inputs, scroll position, and all component state.
### Function Components with useSidePane
The `useSidePane()` hook is the primary API for function components. It returns `openSidePane`, `closeSidePane`, `stackLength`, and `stack`.
```tsx
import {
ZestResponsiveLayout,
SidePaneProvider,
useSidePane,
ISidePaneConfig
} from 'jattac.libs.web.zest-responsive-layout';
import { useState } from 'react';
const ItemList = () => {
const { openSidePane } = useSidePane();
const items = [
{ id: 1, name: "Project Alpha" },
{ id: 2, name: "Project Beta" }
];
return (
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
return (
,
onClose: () => setIsOpen(false)
}}
>
);
};
```
### Class Components with withSidePane HOC
Class components can use the `withSidePane` higher-order component, which injects `openSidePane`, `closeSidePane`, and `stackLength` as props.
```tsx
import {
ZestResponsiveLayout,
SidePaneProvider,
withSidePane,
WithSidePaneProps,
ISidePaneConfig
} from 'jattac.libs.web.zest-responsive-layout';
import React from 'react';
interface ProjectListProps extends WithSidePaneProps {}
class ProjectList extends React.Component {
private items = [
{ id: 1, name: "Project Alpha" },
{ id: 2, name: "Project Beta" },
{ id: 3, name: "Project Gamma" }
];
private handleView(item: { id: number; name: string }): void {
this.props.openSidePane({
title: item.name,
content:
});
}
render() {
return (
{this.items.map(item => (
))}
Current stack depth: {this.props.stackLength}
);
}
}
// Wrap with the HOC to inject side pane context props
const ProjectListWithSidePane = withSidePane(ProjectList);
const App = () => {
return (
}}
>
Main Content
);
};
```
### Class Components with SidePaneConsumer
An alternative for class components is the `SidePaneConsumer` render-prop component. This avoids the HOC wrapper and gives direct access to the full context value, including the raw stack array.
```tsx
import {
ZestResponsiveLayout,
SidePaneProvider,
SidePaneConsumer
} from 'jattac.libs.web.zest-responsive-layout';
import React from 'react';
interface TaskListState {
tasks: { id: number; title: string }[];
}
class TaskList extends React.Component<{}, TaskListState> {
constructor(props: {}) {
super(props);
this.state = {
tasks: [
{ id: 1, title: "Design review" },
{ id: 2, title: "API integration" },
{ id: 3, title: "Testing" }
]
};
}
render() {
return (
{({ openSidePane, closeSidePane, stackLength, stack }) => (
Tasks (stack depth: {stackLength})
{this.state.tasks.map(task => (
{task.title}
))}
{stackLength > 0 && (
Back
)}
)}
);
}
}
const App = () => {
return (
}}
>
Main Content
);
};
```
### Deep Nesting and Data Preservation
The stack API supports an arbitrary number of nested panes. Each level pushes a new entry onto the stack. When the user closes panes one by one, each previous pane is restored with its component state intact.
The following example demonstrates four levels of nesting with form data preservation:
```tsx
import {
ZestResponsiveLayout,
SidePaneProvider,
useSidePane
} from 'jattac.libs.web.zest-responsive-layout';
import { useState } from 'react';
// Level 1: Order list
const OrderList = () => {
const { openSidePane } = useSidePane();
return (
Orders
{[1001, 1002, 1003].map(id => (
Order #{id}
openSidePane({
title: `Order #${id}`,
content:
})}>
View
))}
);
};
// Level 2: Order detail with edit action
const OrderDetail = ({ orderId }: { orderId: number }) => {
const { openSidePane } = useSidePane();
return (
Order #{orderId}
Status: Pending
Items: 3 line items
openSidePane({
title: `Edit Order #${orderId}`,
content:
})}>
Edit Order
);
};
// Level 3: Order edit form with line-item drill-in
const OrderEdit = ({ orderId }: { orderId: number }) => {
const [notes, setNotes] = useState("");
const { openSidePane } = useSidePane();
return (
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
return (
,
onClose: () => setIsOpen(false)
}}
>
setIsOpen(true)}>Open Orders
);
};
```
**Navigation flow:**
1. User opens the Orders pane.
2. User clicks "View" on Order #1001, pushing `OrderDetail` onto the stack.
3. User clicks "Edit Order", pushing `OrderEdit` onto the stack.
4. User types notes in the textarea (state is tracked in `OrderEdit`).
5. User clicks "Edit Line Item" on "Widget A", pushing `LineItemEdit` onto the stack.
6. User changes quantity to 5.
7. User clicks the close button (x) four times, or the overlay four times if configured.
8. Each close pops one level: `LineItemEdit` closes, revealing `OrderEdit` with the notes textarea still containing the typed text.
9. Another close reveals `OrderDetail`. Another close reveals `OrderList`. Final close returns to the main application.
**Data preservation guarantees:**
- The notes textarea in `OrderEdit` retains its value across all intervening pushes and pops.
- The quantity input in `LineItemEdit` retains its value as long as the pane is on the stack.
- Any React state, refs, timers, or subscriptions within each pane continue to function normally across hide and show cycles.
- The only data that is reset is component state that is explicitly cleaned up by the consumer (e.g., in a `useEffect` cleanup function).
### Return Values (v2.5.0+)
When a side pane is closed, it can pass a result back to the pane that opened it. This enables patterns like "open an editor in a side pane and act on the result when done".
#### Promise-based (await)
The opener awaits the Promise returned by `openSidePane()`:
```tsx
import {
ZestResponsiveLayout,
SidePaneProvider,
useSidePane,
} from 'jattac.libs.web.zest-responsive-layout';
import { useState } from 'react';
const Dashboard = () => {
const { openSidePane } = useSidePane();
const [items, setItems] = useState(['Alpha', 'Beta', 'Gamma']);
const handleEdit = async (name: string) => {
const result = await openSidePane<{ newName: string }>({
title: `Rename ${name}`,
content: ,
});
if (result?.newName) {
setItems(prev => prev.map(i => i === name ? result.newName : i));
}
};
return (
setValue(e.target.value)} />
closeSidePane({ newName: value })}>Save closeSidePane()}>Cancel
);
};
```
**Flow:**
1. User clicks Rename on "Alpha".
2. `RenameForm` opens in a side pane with the current name pre-filled.
3. User types "Alphonso" and clicks Save.
4. `closeSidePane({ newName: "Alphonso" })` resolves the opener's Promise.
5. `Dashboard` receives `{ newName: "Alphonso" }` and updates the list.
6. If the user clicks Cancel (or the × button), `closeSidePane()` is called with no argument. The Promise resolves with `undefined`. The `result?.newName` check handles this gracefully — nothing happens.
#### Callback style via .then()
```tsx
openSidePane<{ saved: boolean }>({ title: "Edit", content: })
.then(result => { if (result?.saved) refreshList(); });
```
#### Broadcast subscription
Third-party or decoupled components can observe all close events:
```tsx
const AuditLogger = () => {
const { subscribe } = useSidePane();
useEffect(() => {
return subscribe(({ paneId, result }) => {
console.log(`[Audit] Pane ${paneId} closed with`, result);
});
}, [subscribe]);
return null;
};
```
The subscribe callback receives `{ paneId: string; result: unknown }`. The returned unsubscribe function should be called in `useEffect` cleanup.
#### Class components
```tsx
// withSidePane HOC
class MyComponent extends React.Component {
async handleOpen() {
const result = await this.props.openSidePane<{ saved: boolean }>({
content: ,
});
if (result?.saved) this.refresh();
}
}
// SidePaneConsumer
{({ openSidePane, subscribe }) => (
openSidePane({ content: }).then(r => {
if (r?.saved) refresh();
})}>
Open
)}
```
---
### Nesting with Form State Preservation
A common requirement is to have a form inside one side pane that remains filled in while the user drills into a reference data lookup in another side pane. The stack API handles this inherently.
```tsx
import {
ZestResponsiveLayout,
SidePaneProvider,
useSidePane
} from 'jattac.libs.web.zest-responsive-layout';
import { useState } from 'react';
// A form inside a side pane
const CustomerForm = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [country, setCountry] = useState("");
const { openSidePane } = useSidePane();
return (
{
onSelect();
closeSidePane();
}}>
Select This Country
);
};
const App = () => {
const [isOpen, setIsOpen] = useState(false);
return (
,
onClose: () => setIsOpen(false)
}}
>
setIsOpen(true)}>Open Customer Form
);
};
```
**Behavior:**
1. User opens the Customer form and types "John" as the name and "john@example.com" as the email.
2. User clicks "Lookup" to find a country, pushing `CountryLookup` onto the stack.
3. User types "Ken" in the search field within the lookup (internal state of `CountryLookup`).
4. User clicks "Details" on Kenya, pushing `CountryDetail` onto the stack.
5. User clicks "Select This Country".
6. `CountryDetail` and `CountryLookup` are popped from the stack (two close operations).
7. The `CustomerForm` is revealed with "John" and "john@example.com" still in the input fields, and "Kenya" now populated in the Country field.
---
## Custom Styling
```tsx
,
className: "my-custom-sidebar"
}}
>
```
The `className` and `style` props on the root container apply to the outermost layout wrapper. The `sidePane.className` and `sidePane.style` props apply directly to the side pane element. These same props are available on `ISidePaneConfig` for stacked panes via the context API.