epic-modals
v1.0.6
Published
A stunning modal library with macOS-style animations, drag, resize, and dock. Supports Svelte 5, React, and vanilla JS.
Maintainers
Readme
epic-modals
A stunning modal library with macOS-style animations, drag, resize, and dock. Supports Svelte 5, React, and vanilla JavaScript.
Installation
npm install epic-modalsQuick Start
React
import { ModalProvider, Modal, Dock, useModal } from 'epic-modals/react';
import 'epic-modals/styles';
// Modal IDs can be symbols (recommended) or strings
const MODAL_ID = Symbol('my-modal');
function App() {
return (
<ModalProvider>
<MyComponent />
<Dock />
</ModalProvider>
);
}
function MyComponent() {
const { open, close } = useModal(MODAL_ID);
return (
<>
<button onClick={(e) => open(e.currentTarget)}>Open</button>
<Modal id={MODAL_ID} title="Hello World" maxWidth="480px"
footer={<button onClick={close}>Close</button>}
>
<p>Your content here!</p>
</Modal>
</>
);
}Svelte 5
<script lang="ts">
import { ModalProvider, Modal, Dock, openModal, closeModal } from 'epic-modals/svelte';
import 'epic-modals/styles';
// Modal IDs can be symbols (recommended) or strings
const MODAL_ID = Symbol('my-modal');
</script>
<ModalProvider>
<button onclick={(e) => openModal(MODAL_ID, e.currentTarget)}>Open</button>
<Modal id={MODAL_ID} title="Hello World" maxWidth="480px">
<p>Your content here!</p>
{#snippet footer()}
<button onclick={() => closeModal(MODAL_ID)}>Close</button>
{/snippet}
</Modal>
<Dock />
</ModalProvider>Vanilla JavaScript
import { init, createModal, openModal, closeModal } from 'epic-modals/vanilla';
import 'epic-modals/styles';
// Modal IDs can be symbols (recommended) or strings
const MODAL_ID = Symbol('my-modal');
// Initialize library (creates backdrop + dock automatically)
init();
createModal({
id: MODAL_ID,
title: 'Hello World',
content: '<p>Your content here!</p>',
footer: '<button id="close-btn">Close</button>',
maxWidth: '480px',
});
document.querySelector('#close-btn').onclick = () => closeModal(MODAL_ID);
document.querySelector('#open-btn').onclick = (e) => {
openModal(MODAL_ID, e.currentTarget);
};Features
- Dock - Minimized modals appear in a customizable dock
- Minimize/Restore - Smooth genie animation to/from dock
- Resize - 8-direction resize handles
- Drag - Drag modals by header
- Parent-Child - Modals can spawn child modals
- Attention Effect - Shake animation to grab user attention
- Focus Trap - Tab key stays within modal
- macOS Style - Traffic light buttons (close, minimize, maximize)
- Theming - Light/dark themes + full CSS variable customization
Hooks & Functions
React
import { useModal, useModals } from 'epic-modals/react';
const MODAL_ID = Symbol('my-modal');
function MyComponent() {
const {
open, // (element) => void - Open modal
close, // () => void - Close modal
minimize, // () => void - Minimize to dock
restore, // () => void - Restore from dock
shake, // () => void - Trigger attention effect
bringToFront, // () => void - Focus modal (z-index)
isOpen, // boolean
isMinimized, // boolean
isRegistered, // boolean
} = useModal(MODAL_ID);
return (
<button onClick={(e) => open(e.currentTarget)}>Open</button>
);
}Svelte
<script lang="ts">
import { useModal } from 'epic-modals/svelte';
const MODAL_ID = Symbol('my-modal');
const modal = useModal(MODAL_ID);
// modal.open(element) - Open modal
// modal.close() - Close modal
// modal.minimize() - Minimize to dock
// modal.restore() - Restore from dock
// modal.shake() - Trigger attention effect
// modal.bringToFront() - Focus modal (z-index)
// modal.isOpen() - Check if open
// modal.isRegistered() - Check if registered
</script>
<button onclick={(e) => modal.open(e.currentTarget)}>Open</button>Vanilla
import {
openModal, closeModal, minimizeModal, restoreModal,
triggerAttention, bringToFront, isModalOpen
} from 'epic-modals/vanilla';
const MODAL_ID = Symbol('my-modal');
openModal(MODAL_ID, buttonElement);
closeModal(MODAL_ID);
minimizeModal(MODAL_ID);
restoreModal(MODAL_ID);
triggerAttention(MODAL_ID); // Shake effect
bringToFront(MODAL_ID);
isModalOpen(MODAL_ID); // Returns booleanParent-Child Modals
// React
import { useModal } from 'epic-modals/react';
const PARENT_ID = Symbol('parent');
const CHILD_ID = Symbol('child');
function Parent() {
const parent = useModal(PARENT_ID);
const child = useModal(CHILD_ID);
return (
<Modal id={PARENT_ID} title="Parent">
<button onClick={(e) => child.openAsChild(PARENT_ID, e.currentTarget)}>
Open Child
</button>
</Modal>
);
}<!-- Svelte -->
<script>
import { openChildModal } from 'epic-modals/svelte';
const PARENT_ID = Symbol('parent');
const CHILD_ID = Symbol('child');
</script>
<Modal id={PARENT_ID} title="Parent">
<button onclick={(e) => openChildModal(CHILD_ID, PARENT_ID, e.currentTarget)}>
Open Child
</button>
</Modal>
<Modal id={CHILD_ID} title="Child">
<p>I'm a child modal!</p>
</Modal>Configuration
<ModalProvider config={{
features: {
dock: true, // Enable dock for minimized modals
minimize: true, // Enable minimize button
resize: true, // Enable resize handles
drag: true, // Enable drag to move
focusTrap: true, // Enable focus trapping
animations: true, // Enable animations
backdrop: true, // Enable backdrop overlay
},
dock: {
position: 'bottom', // 'left' | 'right' | 'bottom'
labelMode: 'hidden', // 'hidden' | 'beside' | 'below'
},
animations: {
open: 400,
close: 250,
minimize: 500,
restore: 400,
},
appearance: {
headerLayout: 'macos', // 'macos' | 'windows' | 'none'
},
}}>Theming
/* Import base styles */
@import 'epic-modals/styles';
/* Optional: Import a theme */
@import 'epic-modals/styles/themes/light';
@import 'epic-modals/styles/themes/dark';Override CSS variables for custom theming:
:root {
--modal-bg: #ffffff;
--modal-border-radius: 12px;
--modal-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
--modal-title-color: #1a1a1a;
/* Traffic lights */
--modal-btn-close: #ff5f57;
--modal-btn-minimize: #febc2e;
--modal-btn-maximize: #28c840;
/* Dock */
--modal-dock-bg: linear-gradient(to bottom, #f8f9fa, #f0f1f3);
}License
Elastic License 2.0 - Free for commercial use, but you cannot offer it as a hosted service or redistribute it as your own product.
