useDisclosure
Open and closed state for an overlay.
The controlled-or-uncontrolled pattern Dialog, BottomSheet and Select
implement internally, lifted out for when the overlay has to be driven from
somewhere else — closing a sheet once a request settles, or opening one from
another screen.
Usage
import { useDisclosure, BottomSheet, Button, Input } from 'panelui-native';
function ShareSheet() {
const sheet = useDisclosure();
return (
<>
<Button onPress={sheet.open}>Share</Button>
<BottomSheet {...sheet.props}>
<BottomSheet.Content>
<Input placeholder="https://panelui.dev/p/xK2f9" />
<Button onPress={sheet.close}>Done</Button>
</BottomSheet.Content>
</BottomSheet>
</>
);
}props spreads straight onto any PanelUI overlay — it is { open, onOpenChange }. Requests equal to the accepted state are ignored, so calling open() twice does not emit a duplicate change.
Controlled requests remain owner-driven until the open prop accepts them; external prop resets do not call onOpenChange. If ownership changes from controlled to uncontrolled, the hook retains the last accepted controlled value rather than restoring its old default.
Examples
Closing once a request settles
The reason to lift the state out at all — the overlay cannot close itself from inside an async handler.
const dialog = useDisclosure();
const [saving, setSaving] = useState(false);
async function save() {
setSaving(true);
try {
await api.save(draft);
dialog.close();
toast({ variant: 'success', title: 'Saved' });
} finally {
setSaving(false);
}
}
<Dialog {...dialog.props}>
<Dialog.Content>
<Dialog.Title>Edit name</Dialog.Title>
<Input defaultValue={project.name} />
<Dialog.Footer>
<Button variant="ghost" onPress={dialog.close}>Cancel</Button>
<Button loading={saving} onPress={save}>Save</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>;Controlled
Pass open to drive it from state you already own — a router param, a wizard
step, a query result.
const sheet = useDisclosure({
open: step === 'share',
onOpenChange: handleChange,
});Reacting to the state elsewhere
const menu = useDisclosure();
<>
<Button onPress={menu.toggle}>
{menu.isOpen ? 'Hide filters' : 'Show filters'}
</Button>
<BottomSheet {...menu.props}>
<BottomSheet.Content>{/* … */}</BottomSheet.Content>
</BottomSheet>
</>;Several overlays on one screen
Each gets its own instance — there is no shared registry to collide over.
const share = useDisclosure();
const confirmDelete = useDisclosure();
<BottomSheet {...share.props}>{/* … */}</BottomSheet>
<Dialog {...confirmDelete.props}>{/* … */}</Dialog>API Reference
Options
| Option | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | false | Initial state when uncontrolled. |
open | boolean | — | Controlled state. Providing it makes the hook controlled. |
onOpenChange | (open: boolean) => void | — | Called on every change, controlled or not. |
Returns
| Value | Type | Description |
|---|---|---|
isOpen | boolean | Current state. |
open | () => void | Opens. |
close | () => void | Closes. |
toggle | () => void | Flips. |
props | { open, onOpenChange } | Spread onto an overlay component. |