top-layer
Top Layer is a Library for working with native HTML dialogs and upper layers (f.e. notifications) without unnecessary application rerenders.
The library allows you to manage dialogs and custom elements in the browser Top Layer. This layer is supported by all modern browsers. It includes a Dialogs API and a Toasts API.
Tip
The native Toasts API doesn't behave correctly on top of the Dialogs API; use an upper layer for this instead.
Installation
npm i top-layer
Configuration
Wrap your application in the provider:
import { TopLayerProvider } from "top-layer";
import { ShareDialog } from "@src/components/share-dialog";
import { FullViewDialog } from "@src/components/full-view-dialog";
import { ToastLayer } from "@src/components/toast-layer";
const RootLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<TopLayerProvider
dialogs={[
{ dialog: ShareDialog, id: "share" },
{ dialog: FullViewDialog, id: "full-view" },
]}
upperLayers={[{ layer: ToastLayer, id: "toast" }]}
>
{children}
</TopLayerProvider>
);
export default RootLayout;
Note
TopLayerProvidernever causes rerenders, so it is recommended to use it over the entire application.
Usage
The library provides an interface for working with dialogs and upper layers like toasts. You can use them together or separately, in any order.
Dialogs
This is not a UI library; you create the dialog-content components and register them with the provider. The provider renders native dialog elements for you (and only it).
Example of a basic dialog-content component:
"use client";
import { useDialogAction, useDialogData } from "top-layer/dialogs/hooks";
export const AlertDialog: React.FC = () => {
const { close } = useDialogAction();
const message = useDialogData<string>();
return (
<div className="relative m-auto p-16 bg-slate-900 rounded-2xl w-full max-w-5xl z-10">
<p>{message}</p>
<button onClick={() => close()}>Close</button>
</div>
);
};
Register it in the provider and optionally pass props to the underlying html dialog:
<TopLayerProvider
dialogs={[
{
dialog: AlertDialog,
id: "alert",
props: { id: "alert-dialog" },
},
]}
/>
Open the dialog from anywhere:
import { useDialogAction } from "top-layer/dialogs/hooks";
// ...
const { open } = useDialogAction({ id: "alert" });
// ...
<button onClick={() => open({ data: "Base Alert Dialog" })}>
Open Alert Dialog
</button>;
Note
If you need to control dialogs outside React (e.g., Redux-Saga), use the standalone functions
openDialogandcloseDialog.
Upper Layers
Upper layers are React components rendered above the app root and inside each dialog (so they can appear above dialog content). Examples include toasts, global spinners, and banners.
Example of base upper-layer component:
"use client";
import {
useUpperLayerData,
useUpperLayerStore,
} from "top-layer/upper-layers/hooks";
type ToastData =
| { message: string; type: "success" | "warning" | "error" | "neutral" }
| undefined;
export const ToastLayer: React.FC = () => {
const data = useUpperLayerData<ToastData>();
const { reset } = useUpperLayerStore();
if (!data) return null;
return (
<div className={`toast toast_${data.type}`} onClick={() => reset()}>
{" "}
{data.message}{" "}
</div>
);
};
Update layer data from anywhere in React:
import { useUpperLayerStore } from "top-layer/upper-layers/hooks";
// ...
const { update } = useUpperLayerStore({ id: "toast" });
// ...
<button onClick={() => update({ data: { message: "Saved", type: "success" } })}>
Show Toast
</button>;
Or from outside React using utilities:
import {
updateUpperLayerData,
resetUpperLayerData,
} from "top-layer/upper-layers/utils";
updateUpperLayerData("toast", { message: "Failed", type: "error" });
// ...
resetUpperLayerData("toast");
API
TopLayerProvider
Main package provider. It must be added above the part of the application where you plan to use dialogs and upper layers.
Tip
TopLayerProvider never causes re-renders, so it is recommended to install it over the entire application.
import { TopLayerProvider } from "top-layer";
import { ShareDialog } from "@src/components/share-dialog";
import { FullViewDialog } from "@src/components/full-view-dialog";
import { ToastLayer } from "@src/components/toast-layer";
const RootLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<TopLayerProvider
dialogs={[
{ dialog: ShareDialog, id: "share" },
{ dialog: FullViewDialog, id: "full-view" },
]}
upperLayers={[{ layer: ToastLayer, id: "toast" }]}
>
{children}
</TopLayerProvider>
);
export default RootLayout;
Props
dialogs - Array of dialogs with keys:
dialog- dialog-content component;id- id of the dialog to control it from anywhere;props- props passed to the underlying nativedialogelement (optional);defaultData- initial data available via hooks (optional).
upperLayers - Array of upper layers with keys:
layer- upper-layer component;id- id of the upper layer to control it from anywhere;defaultData- initial data available via hooks (optional).
Dialogs API
Hooks for dialog control and state inside registered dialog content.
useDialogAction
import { useDialogAction } from "top-layer/dialogs/hooks";
// ...
const { open, close } = useDialogAction({ id: "modal" });
// ...
<button onClick={() => open({ data: { title: "Some modal title" } })}>Show Modal</button>
// ...
<button onClick={() => close()}>Close Modal</button>
Arguments
id - dialog id (optional if used inside the dialog-content component, where it is provided by context).
Returns
open - open dialog. Arguments: { data?: unknown; id?: string }.
close - close dialog. Arguments: { data?: unknown; id?: string }.
useDialogData
Hook to read reactive data passed to a dialog.
import { useDialogData } from "top-layer/dialogs/hooks";
const data = useDialogData<{ title: string }>();
Arguments
id - dialog id (optional if used inside the dialog-content component, where it is provided by context).
openDialog
Standalone function to open dialogs outside React.
import { openDialog } from "top-layer/dialogs/utils";
openDialog("alert", { title: "Some Alert Message" });
closeDialog
Standalone function to close dialogs outside React.
import { closeDialog } from "top-layer/dialogs/utils";
closeDialog("modal");
Upper Layers API
Hooks and utilities to manage arbitrary upper-layer components.
useUpperLayerStore
import { useUpperLayerStore } from "top-layer/upper-layers/hooks";
const { update, reset, get, subscribe, unsubscribe } = useUpperLayerStore({
id: "toast",
});
Arguments
id - layer id (optional if used inside the layer-content component, where it is provided by context).
useUpperLayerData
import { useUpperLayerData } from "top-layer/upper-layers/hooks";
const data = useUpperLayerData<{ message: string; type: string }>();
Arguments
id - layer id (optional if used inside the layer-content component, where it is provided by context).
updateUpperLayerData / resetUpperLayerData
Standalone functions to update or reset upper-layer data outside React.
import {
updateUpperLayerData,
resetUpperLayerData,
} from "top-layer/upper-layers/utils";
updateUpperLayerData("toast", { message: "Saved", type: "success" });
resetUpperLayerData("toast");
Additional
Please consider giving a star if you like it, this motivates the author to continue working on this and other solutions ❤️
Create issues with wishes, ideas, difficulties, etc. All of them will definitely be considered and thought over.