@alsocoder/apna-modal
v0.1.2
Published
A flexible React modal and drawer component with stacked z-index, confirmation presets, and drag-to-dismiss drawers.
Maintainers
Readme
@alsocoder/apna-modal
React modal and drawer components with stacked z-index, confirmation presets, imperative API, and nested overlay support for @alsocoder/apna-select and @alsocoder/apna-date-picker.
Features
- Modal (Radix Dialog) and drawer (vaul) variants
- Four drawer sides: top, bottom, left, right
- Drag-to-dismiss on top/bottom drawers
- Stacked modals with automatic z-index via
ApnaModalProvider ApnaConfirmModalpreset for confirmationsuseApnaModal()imperativeopen()/confirm()API- Compound parts:
ApnaModalHeader,ApnaModalBody,ApnaModalFooter - Nested overlay contract — DatePicker and Select work inside modals
- Vanilla CSS theming via CSS variables
Installation
npm install @alsocoder/apna-modalPeer dependencies:
npm install react react-domFor DatePicker / Select inside modals, use matching versions of the sibling packages:
npm install @alsocoder/apna-date-picker @alsocoder/apna-selectSetup
import {
ApnaModalProvider,
ApnaModal,
ApnaModalHeader,
ApnaModalBody,
ApnaModalFooter,
} from "@alsocoder/apna-modal"
import "@alsocoder/apna-modal/styles.css"Wrap your app (or layout) with the provider — required for stacked z-index and nested popover CSS vars:
<ApnaModalProvider baseZIndex={500} zIndexStep={1} maxStack={10}>
{children}
</ApnaModalProvider>Basic modal
import { useState } from "react"
import {
ApnaModal,
ApnaModalHeader,
ApnaModalBody,
ApnaModalFooter,
} from "@alsocoder/apna-modal"
export function EditProfileModal() {
const [open, setOpen] = useState(false)
return (
<>
<button type="button" onClick={() => setOpen(true)}>Edit</button>
<ApnaModal open={open} onOpenChange={setOpen} size="md">
<ApnaModalHeader
title="Edit profile"
description="Update your public information."
onClose={() => setOpen(false)}
/>
<ApnaModalBody>
<p>Modal content here.</p>
</ApnaModalBody>
<ApnaModalFooter>
<button type="button" onClick={() => setOpen(false)}>Cancel</button>
<button type="button" onClick={() => setOpen(false)}>Save</button>
</ApnaModalFooter>
</ApnaModal>
</>
)
}Drawer
<ApnaModal
open={open}
onOpenChange={setOpen}
variant="drawer"
side="bottom"
draggable
>
<ApnaModalHeader title="Bottom drawer" onClose={() => setOpen(false)} />
<ApnaModalBody>Drawer content</ApnaModalBody>
</ApnaModal>Confirmation modal
import { ApnaConfirmModal } from "@alsocoder/apna-modal"
<ApnaConfirmModal
open={open}
onOpenChange={setOpen}
title="Delete item?"
description="This action cannot be undone."
confirmLabel="Delete"
variant="destructive"
onConfirm={() => deleteItem()}
/>Imperative API
import { useApnaModal } from "@alsocoder/apna-modal"
function Demo() {
const modal = useApnaModal()
return (
<button
type="button"
onClick={() =>
modal.open({
title: "Imperative modal",
content: <p>Rendered by the provider host.</p>,
})
}
>
Open
</button>
)
}DatePicker / Select inside modal
Use @alsocoder/apna-date-picker ≥ 0.1.2 and @alsocoder/apna-select ≥ 0.2.2 with ApnaModalProvider. Import their styles as usual:
import { ApnaDatePicker } from "@alsocoder/apna-date-picker"
import { ApnaSelect } from "@alsocoder/apna-select"
import "@alsocoder/apna-date-picker/styles.css"
import "@alsocoder/apna-select/styles.css"
<ApnaModal open={open} onOpenChange={setOpen}>
<ApnaModalBody>
<ApnaDatePicker value={date} onValueChange={setDate} />
<ApnaSelect options={options} value={status} onValueChange={setStatus} />
</ApnaModalBody>
</ApnaModal>The provider sets body CSS variables consumed by sibling packages:
--apna-overlay-nested-z-popover--apna-overlay-nested-z-select
Exports
ApnaModal,ApnaConfirmModal,ApnaModalProviderApnaModalHeader,ApnaModalBody,ApnaModalFooter,ApnaModalDragHandleuseApnaModaldefaultClassNames,mergeClassNamespreventDismissOnNestedOverlay,isInsideNestedOverlay,APNA_NESTED_OVERLAY_SELECTOR- Nested z-index constants:
NESTED_Z_POPOVER_VAR,NESTED_Z_SELECT_VAR, offsets
Changelog
v0.1.0
- Initial release
- Modal (Radix Dialog) and drawer (vaul) variants
ApnaModalProviderstacked z-index with nested overlay CSS varsApnaConfirmModalanduseApnaModal()imperative API- Compound header/body/footer parts
- Nested overlay dismiss prevention for portaled Select / DatePicker
Development
npm install
npm run build
cd playground && npm install && npm run devLicense
MIT
