{"_id":"@awtechs/fanta-modal","name":"@awtechs/fanta-modal","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@awtechs/fanta-modal","version":"0.1.0","type":"module","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc"},"peerDependencies":{"react":">=18","react-dom":">=18"},"devDependencies":{"@types/react":"^19.2.7","@types/react-dom":"^19.2.3","react":"^19.2.3","react-dom":"^19.2.3","typescript":"^5.9.3"},"_id":"@awtechs/fanta-modal@0.1.0","gitHead":"a0e0ba6430183c0abc5900c088afaee634fd865d","description":"A modern, accessible React modal library built with TypeScript. Provides a robust foundation for managing modal dialogs with support for stacked modals, focus management, keyboard interactions, and automatic DOM inertness handling.","_nodeVersion":"22.16.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-J66M9+2rYPh4WNYc2G9a6uenxuq7L1bsvKgvnAfyEVHM5L+FM63shZY4e1Gut9ehpMlISf3G0gsfl7k3rSWIDQ==","shasum":"b9c0b797313fdd9053f4a5cfae9c399772cbdda4","tarball":"https://registry.npmjs.org/@awtechs/fanta-modal/-/fanta-modal-0.1.0.tgz","fileCount":22,"unpackedSize":16677,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDAGBXz0TDipRuUfeENh/ZfQqh/meufxi1wQ1kzsd3BCQIgac68eEgCN4pmQbNzTqjA1w69U+xUuCNv11hfeTVYoMw="}]},"_npmUser":{"name":"awtechs","email":"eaglemike7@gmail.com"},"directories":{},"maintainers":[{"name":"awtechs","email":"eaglemike7@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/fanta-modal_0.1.0_1767538003982_0.9903959377493474"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-04T14:46:43.860Z","0.1.0":"2026-01-04T14:46:44.141Z","modified":"2026-01-04T14:46:44.523Z"},"maintainers":[{"name":"awtechs","email":"eaglemike7@gmail.com"}],"description":"A modern, accessible React modal library built with TypeScript. Provides a robust foundation for managing modal dialogs with support for stacked modals, focus management, keyboard interactions, and automatic DOM inertness handling.","readme":"# fanta-modal\r\n\r\nA modern, accessible React modal library built with TypeScript. Provides a robust foundation for managing modal dialogs with support for stacked modals, focus management, keyboard interactions, and automatic DOM inertness handling.\r\n\r\n## Features\r\n\r\n- **Context-based State Management** – Centralized modal stack with programmatic open/close control\r\n- **Stacked Modals** – Support for multiple modals displayed simultaneously with proper layering\r\n- **Focus Trap** – Automatic focus management that cycles through focusable elements\r\n- **Escape Key Handling** – Close modals with the Escape key (top modal only)\r\n- **Scroll Lock** – Prevent body scroll when any modal is open\r\n- **Inert Attribute** – Automatically make the app root inert when modals are active for accessibility\r\n- **Portal Rendering** – Render modals outside the component tree using React portals\r\n- **Controlled & Uncontrolled** – Choose between programmatic control or simple on/off state\r\n- **Fully Typed** – Complete TypeScript support with proper type inference\r\n- **Accessibility** – Built-in ARIA attributes and semantic HTML\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @awtechs/fanta-modal react react-dom\r\n```\r\n\r\n## Usage\r\n\r\n### Basic Setup\r\n\r\nWrap your app with the `ModalProvider` and include a modal root element:\r\n\r\n```tsx\r\nimport { ModalProvider, ModalRoot } from '@awtechs/fanta-modal';\r\n\r\nexport default function App() {\r\n  return (\r\n    <ModalProvider>\r\n      <div id=\"root\">\r\n        {/* Your app content */}\r\n      </div>\r\n      <div id=\"modal-root\" />\r\n    </ModalProvider>\r\n  );\r\n}\r\n```\r\n\r\n### Programmatic Modal Control\r\n\r\nUse the `useModal` hook to open, close, and manage modals:\r\n\r\n```tsx\r\nimport { useModal, ModalContent, ModalOverlay } from '@awtechs/fanta-modal';\r\n\r\nexport function MyComponent() {\r\n  const { open, closeTop } = useModal();\r\n\r\n  const handleOpenModal = () => {\r\n    open(\r\n      <ModalOverlay onClick={closeTop}>\r\n        <ModalContent label=\"Confirm Action\">\r\n          <p>Are you sure?</p>\r\n          <button onClick={closeTop}>Cancel</button>\r\n        </ModalContent>\r\n      </ModalOverlay>\r\n    );\r\n  };\r\n\r\n  return <button onClick={handleOpenModal}>Open Modal</button>;\r\n}\r\n```\r\n\r\n### Controlled Modal\r\n\r\nFor simpler use cases, use `ControlledModal` to manage modal visibility locally:\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport { ControlledModal, ModalContent } from '@awtechs/fanta-modal';\r\n\r\nexport function SimpleModal() {\r\n  const [isOpen, setIsOpen] = useState(false);\r\n\r\n  return (\r\n    <>\r\n      <button onClick={() => setIsOpen(true)}>Open</button>\r\n      <ControlledModal open={isOpen}>\r\n        <ModalContent label=\"Hello\">\r\n          <p>This is a modal!</p>\r\n          <button onClick={() => setIsOpen(false)}>Close</button>\r\n        </ModalContent>\r\n      </ControlledModal>\r\n    </>\r\n  );\r\n}\r\n```\r\n\r\n## API Reference\r\n\r\n### ModalProvider\r\n\r\nContext provider that manages the modal stack. Must wrap all components that use modal hooks.\r\n\r\n**Props:**\r\n- `children` (ReactNode) – App content to wrap\r\n\r\n### useModal()\r\n\r\nHook to access the modal context and control modals programmatically.\r\n\r\n**Returns:**\r\n```typescript\r\n{\r\n  stack: ModalInstance[];        // Array of open modals\r\n  open(content: ReactNode): string;  // Opens a modal, returns unique ID\r\n  close(id: string): void;       // Closes specific modal by ID\r\n  closeTop(): void;              // Closes the topmost modal\r\n}\r\n```\r\n\r\n**Example:**\r\n```tsx\r\nconst { open, close, closeTop, stack } = useModal();\r\n\r\nconst id = open(<p>Content</p>);\r\nclose(id);        // Close by ID\r\ncloseTop();       // Close the top modal\r\n```\r\n\r\n### ModalRoot\r\n\r\nComponent that renders the modal stack as portals. Must be placed after your main app root.\r\n\r\n**Props:**\r\n- `children` (ReactNode, optional) – Not required; renders portal content\r\n\r\n**Example:**\r\n```tsx\r\n<div id=\"root\">Your app</div>\r\n<ModalRoot />  {/* or */}\r\n<div id=\"modal-root\" />\r\n```\r\n\r\n### ModalContent\r\n\r\nAccessible dialog content wrapper with ARIA attributes.\r\n\r\n**Props:**\r\n- `children` (ReactNode) – Modal body content\r\n- `labelledBy` (string, optional) – ID of element that labels the modal\r\n- `describedBy` (string, optional) – ID of element that describes the modal\r\n- `label` (string, optional) – Accessible label for the modal\r\n- `...rest` (HTMLAttributes) – Standard div attributes (className, style, etc.)\r\n\r\n**Example:**\r\n```tsx\r\n<ModalContent label=\"Confirm\" className=\"modal-dialog\">\r\n  <p>Confirm this action</p>\r\n</ModalContent>\r\n```\r\n\r\n### ModalOverlay\r\n\r\nSimple overlay/backdrop component that typically wraps `ModalContent`.\r\n\r\n**Props:**\r\n- `children` (ReactNode) – Content to display (usually ModalContent)\r\n- `onClick` (function, optional) – Callback when overlay is clicked\r\n\r\n**Example:**\r\n```tsx\r\n<ModalOverlay onClick={closeTop}>\r\n  <ModalContent>Your content</ModalContent>\r\n</ModalOverlay>\r\n```\r\n\r\n### ControlledModal\r\n\r\nSimple controlled modal that renders to a portal based on an `open` prop.\r\n\r\n**Props:**\r\n- `open` (boolean) – Whether the modal is visible\r\n- `children` (ReactNode) – Modal content\r\n\r\n**Example:**\r\n```tsx\r\n<ControlledModal open={isOpen}>\r\n  <ModalContent>Controlled content</ModalContent>\r\n</ControlledModal>\r\n```\r\n\r\n### useFocusTrap(active: boolean)\r\n\r\nHook to trap focus within a modal element. Automatically cycles focus through focusable elements.\r\n\r\n**Parameters:**\r\n- `active` (boolean) – Whether focus trap is enabled\r\n\r\n**Returns:**\r\n- `RefObject<HTMLElement | null>` – Ref to attach to the container element\r\n\r\n**Example:**\r\n```tsx\r\nconst ref = useFocusTrap(isActive);\r\n<div ref={ref}>\r\n  <button>Focused element 1</button>\r\n  <button>Focused element 2</button>\r\n</div>\r\n```\r\n\r\n### EscapeEffect\r\n\r\nComponent that closes the top modal when Escape is pressed. Must be inside `ModalProvider`.\r\n\r\n**Example:**\r\n```tsx\r\n<ModalProvider>\r\n  <EscapeEffect />\r\n  {/* rest of app */}\r\n</ModalProvider>\r\n```\r\n\r\n### ScrollLockEffect\r\n\r\nComponent that locks body scroll when any modal is open. Must be inside `ModalProvider`.\r\n\r\n**Example:**\r\n```tsx\r\n<ModalProvider>\r\n  <ScrollLockEffect />\r\n  {/* rest of app */}\r\n</ModalProvider>\r\n```\r\n\r\n### InertEffect\r\n\r\nComponent that adds the `inert` attribute to the app root when modals are open (prevents background interaction). Must be inside `ModalProvider`.\r\n\r\n**Example:**\r\n```tsx\r\n<ModalProvider>\r\n  <InertEffect />\r\n  {/* rest of app */}\r\n</ModalProvider>\r\n```\r\n\r\n## Complete Example\r\n\r\n```tsx\r\nimport { useState } from 'react';\r\nimport {\r\n  ModalProvider,\r\n  ModalRoot,\r\n  useModal,\r\n  ModalContent,\r\n  ModalOverlay,\r\n  EscapeEffect,\r\n  ScrollLockEffect,\r\n  InertEffect,\r\n} from '@awtechs/fanta-modal';\r\n\r\nfunction App() {\r\n  return (\r\n    <ModalProvider>\r\n      <EscapeEffect />\r\n      <ScrollLockEffect />\r\n      <InertEffect />\r\n\r\n      <div id=\"root\">\r\n        <Main />\r\n      </div>\r\n\r\n      <div id=\"modal-root\" />\r\n      <ModalRoot />\r\n    </ModalProvider>\r\n  );\r\n}\r\n\r\nfunction Main() {\r\n  const { open, closeTop } = useModal();\r\n  const [count, setCount] = useState(0);\r\n\r\n  const showConfirm = () => {\r\n    open(\r\n      <ModalOverlay onClick={closeTop}>\r\n        <ModalContent label=\"Confirm\">\r\n          <h2>Increment Counter?</h2>\r\n          <p>Current count: {count}</p>\r\n          <div style={{ display: 'flex', gap: '1rem' }}>\r\n            <button onClick={() => { setCount(c => c + 1); closeTop(); }}>\r\n              Yes\r\n            </button>\r\n            <button onClick={closeTop}>No</button>\r\n          </div>\r\n        </ModalContent>\r\n      </ModalOverlay>\r\n    );\r\n  };\r\n\r\n  return (\r\n    <div>\r\n      <h1>Count: {count}</h1>\r\n      <button onClick={showConfirm}>Open Modal</button>\r\n    </div>\r\n  );\r\n}\r\n\r\nexport default App;\r\n```\r\n\r\n## Architecture\r\n\r\nThe library is organized into focused modules:\r\n\r\n- **context/** – `ModalProvider` and `useModal` hook for state management\r\n- **portal/** – `ModalRoot` for rendering modals as portals\r\n- **components/** – `ModalContent` and `ModalOverlay` UI components\r\n- **effects/** – `EscapeEffect`, `ScrollLockEffect`, `InertEffect` side-effect handlers\r\n- **focus/** – `useFocusTrap` for keyboard navigation and focus management\r\n- **controlled/** – `ControlledModal` for simple, local modal state\r\n\r\n## Accessibility\r\n\r\nThis library is built with accessibility in mind:\r\n\r\n- **ARIA Attributes** – `role=\"dialog\"`, `aria-modal=\"true\"`, `aria-labelledby`, `aria-describedby`, `aria-label`\r\n- **Focus Management** – `useFocusTrap` ensures keyboard navigation stays within modals\r\n- **Inert Content** – `InertEffect` makes background content non-interactive\r\n- **Keyboard Navigation** – Tab/Shift+Tab cycles through focusable elements; Escape closes modals\r\n- **Semantic HTML** – Proper heading hierarchy and button elements\r\n\r\n## Browser Support\r\n\r\n- Chrome/Edge 90+\r\n- Firefox 88+\r\n- Safari 14+\r\n\r\nRequires React 18+\r\n\r\n## License\r\n\r\nMIT\r\n\r\n## Contributing\r\n\r\nContributions are welcome. Please ensure TypeScript types are correct and components remain accessible.\r\n\r\n```\r\n\r\nBuild with:\r\n```bash\r\nnpm run build\r\n```\r\n\r\nOutputs to `dist/` with declaration files for full type support.\r\n","readmeFilename":"README.md","_rev":"1-c885aa7eb71dac8728c66cb352881d5e"}