react-imp
v0.3.3
Published
Easy to use imperative dialog callouts
Downloads
17
Readme
React Imp
Pluggable easy to use imperative dialogs/callouts.
- Accessible.
- Customizable.
- Lightweight.
- Easy to use.
Getting Started
Install
yarn add react-imp
Basic usage
import { Imp, confirm } from "react-imp";
export default function App() {
const handleDeleteItem = () => api.deleteItem();
return (
<div>
<Item
onDelete={() =>
confirm({
title: "Are you sure?",
message: "This action is irreversible. You can't go back!",
danger: true,
onConfirm: handleDeleteItem,
})
}
/>
<Imp />
</div>
);
}
Creating reusable guards
import { confirm } from "react-imp";
export const areYouSure = (cb: () => any) => () =>
confirm({
title: "Are you sure?",
message: "This action is irreversible. You can't go back!",
danger: true,
onConfirm: cb,
});
import { Imp } from "react-imp";
import { areYouSure } from "../imp-guards";
export default function App() {
const handleDeleteItem = () => api.deleteItem();
return (
<div>
<Item onDelete={areYouSure(handleDeleteItem)} />
<Imp />
</div>
);
}
Built-in callers
import { confirm, alert, custom } from "react-imp";
confirm
types
const confirm: (props: {
title?: string | undefined;
danger?: boolean | undefined;
message?: string | undefined;
onConfirm: () => any;
onCancel?: (() => any) | undefined;
onClose?: (() => any) | undefined;
}) => void;
example
confirm({
title: "Are you sure?",
message: "This action is irreversible. You can't go back!",
danger: true,
onConfirm: () => console.log("Confirmed"),
onCancel: () => console.log("Canceled"),
onClose: () => console.log("Closed"),
});
alert
import { alert } from "react-imp";
types
const alert: (props: {
title?: string | undefined;
message?: string | undefined;
onClose?: (() => any) | undefined;
}) => void;
example
alert({
title: "Success!",
message: "Your payment has been successfully processed. We have emailed your receipt.",
onClose: () => console.log("Closed"),
});
custom
types
const custom: (props: (item: Omit<CallerComponentProps, "props">) => React.ReactNode) => void;
example
custom((item) => (
<span>
<div>
Custom and <b>bold</b>
</div>
<button onClick={() => item.close()}>Close</button>
</span>
));
Create your callers
You can implement your own callers, with your own props and UI.
Below is an example of how you can implement your version of the alert and confirm.
import { createCaller } from "react-imp";
import {
Dialog,
DialogDismiss,
DialogTitle,
DialogActions,
DialogBody,
Button,
PrimaryButton,
DangerButton,
} from "react-imp/dialog";
export const alert = createCaller<{
title?: string;
message?: string;
onClose?: () => any;
}>((item) => (
<Dialog open={item.isOpen} onClose={item.handleClose(onClose)}>
<DialogDismiss />
{item.props.title && <DialogTitle>{item.props.title}</DialogTitle>}
{item.props.message && <DialogBody>{item.props.message}</DialogBody>}
<DialogActions>
<PrimaryButton onClick={item.handleClose(onClose)}>Ok</PrimaryButton>
</DialogActions>
</Dialog>
));
export const confirm = createCaller<{
title?: string;
message?: string;
danger?: boolean;
onConfirm?: () => any;
onCancel?: () => any;
onClose?: () => any;
}>((item) => (
<Dialog open={item.isOpen} onClose={item.handleClose(item.props.onClose)}>
<DialogDismiss />
{item.props.title && <DialogTitle>{item.props.title}</DialogTitle>}
{item.props.message && <DialogBody>{item.props.message}</DialogBody>}
<DialogActions>
<Button onClick={item.handleClose(item.props.onCancel)}>Cancel</Button>
{item.props.danger ? (
<DangerButton onClick={item.handleClose(item.props.onConfirm)}>Confirm</DangerButton>
) : (
<PrimaryButton onClick={item.handleClose(item.props.onConfirm)}>Confirm</PrimaryButton>
)}
</DialogActions>
</Dialog>
));
You can also separate the Component if you intend to build something reusable:
import { createCaller, CallerComponentProps } from "react-imp";
import { Dialog /*, ... */ } from "react-imp/dialog";
function AlertDialog(
item: CallerComponentProps<{
title?: string;
message?: string;
onClose?: () => any;
}>,
) {
return (
<Dialog open={item.isOpen} onClose={() => item.close()}>
<DialogTitle>{item.props.title}</DialogTitle>
<DialogContent>
<DialogContentText>{item.props.message}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={item.handleClose(item.props.onDisagree)}>Disagree</Button>
<Button onClick={item.handleClose(item.props.onAgree)} autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
);
}
// prop types inferred from AlertDialog!
export const alert = createCaller(AlertDialog);
// ^? const alert: (props: {
// title?: string | undefined;
// message?: string | undefined;
// onClose?: (() => any) | undefined;
// }) => void
Headless
If you are already using a UI library with a Dialog component, you can create your own callers using it instead of using ours.
By importing from react-imp/headless
you also reduce the bundle size by avoiding react-imp UI dependencies.
See below an example creating a caller with MUI.
import { createCaller } from "react-imp/headless";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
export const alert = createCaller<{
title: string;
message: string;
onClose?: () => any;
}>((item) => (
<Dialog open={item.isOpen} onClose={() => item.close()}>
{item.props.title && <DialogTitle>{item.props.title}</DialogTitle>}
{item.props.message && (
<DialogContent>
<DialogContentText>{item.props.message}</DialogContentText>
</DialogContent>
)}
<DialogActions>
<Button onClick={item.handleClose(item.props.onClose)}>Ok</Button>
</DialogActions>
</Dialog>
));
Custom render
You can customize the caller render if you want to.
The default render is (Component, props) => <Component {...props} />
import { Imp } from "react-imp";
export function App() {
return (
<>
<Imp
render={(Component, props) => (
<CustomWrapper>
<Component {...props} />
</CustomWrapper>
)}
/>
</>
);
}
Channels
You can create different channels with different renderers to your callers.
import { Imp, createCaller } from "react-imp";
function Toast(props) {}
function Dialog(props) {}
const toast = createCaller(Toast, { channel: "toasts" });
const confirm = createCaller(Dialog, { channel: "dialogs" });
export function App() {
return (
<>
<Imp channel="toasts" />
<Imp channel="dialogs" />
</>
);
}
Acknowledgement
Some inspirations for the project were:
- react-hot-toast: Inspired me by their API simplicity.
- Ariakit: Learned a lot from Ariakit to build the Dialog component and all its a11y concerns.
Author
© renatorib, Released under the MIT License.