` props can be passed to this component.
-
### `useModal`
| prop | type | default value | description |
diff --git a/src/lib/components/index.tsx b/src/lib/components/index.tsx
index 4ff5a79..d6183f0 100644
--- a/src/lib/components/index.tsx
+++ b/src/lib/components/index.tsx
@@ -1,3 +1,2 @@
export * from "./modal"
-export * from "./modal-backdrop"
export * from "./modal-content"
diff --git a/src/lib/components/modal-backdrop/index.tsx b/src/lib/components/modal-backdrop/index.tsx
deleted file mode 100644
index e77e22d..0000000
--- a/src/lib/components/modal-backdrop/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export { ModalBackdrop } from "./modal-backdrop"
-export type { ModalBackdropProps } from "./modal-backdrop"
diff --git a/src/lib/components/modal-backdrop/modal-backdrop.module.scss b/src/lib/components/modal-backdrop/modal-backdrop.module.scss
deleted file mode 100644
index 085853f..0000000
--- a/src/lib/components/modal-backdrop/modal-backdrop.module.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.modalBackdrop {
- background-color: rgb(0 0 0 / 80%);
- inset: 0;
- position: absolute;
-}
diff --git a/src/lib/components/modal-backdrop/modal-backdrop.tsx b/src/lib/components/modal-backdrop/modal-backdrop.tsx
deleted file mode 100644
index 6f7b43d..0000000
--- a/src/lib/components/modal-backdrop/modal-backdrop.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { classnames } from "../../../utils/classnames"
-
-import styles from "./modal-backdrop.module.scss"
-
-export interface ModalBackdropProps {
- className?: string
- onClick?: () => void
-}
-
-export function ModalBackdrop({ className, ...props }: ModalBackdropProps) {
- return
-}
diff --git a/src/lib/components/modal-content/modal-content.module.scss b/src/lib/components/modal-content/modal-content.module.scss
index 8f0e694..f1029fc 100644
--- a/src/lib/components/modal-content/modal-content.module.scss
+++ b/src/lib/components/modal-content/modal-content.module.scss
@@ -1,5 +1,4 @@
.modalContent {
max-height: 100vh;
- position: absolute;
width: 100%;
}
diff --git a/src/lib/components/modal-inner/index.tsx b/src/lib/components/modal-inner/index.tsx
deleted file mode 100644
index 8234e7b..0000000
--- a/src/lib/components/modal-inner/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export { ModalInner } from "./modal-inner"
-export type { ModalInnerProps } from "./modal-inner"
diff --git a/src/lib/components/modal-inner/modal-inner.module.scss b/src/lib/components/modal-inner/modal-inner.module.scss
deleted file mode 100644
index 7ddfff6..0000000
--- a/src/lib/components/modal-inner/modal-inner.module.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-.modalInner {
- align-items: center;
- background: transparent;
- border: none;
- color: inherit;
- display: flex;
- height: 100%;
- justify-content: center;
- max-height: 100%;
- max-width: 100%;
- padding: 0;
- width: 100%;
-}
-
-.modalInner::backdrop {
- background: rgba(0,0,0,0.5);
-}
diff --git a/src/lib/components/modal-inner/modal-inner.tsx b/src/lib/components/modal-inner/modal-inner.tsx
deleted file mode 100644
index 997ebe7..0000000
--- a/src/lib/components/modal-inner/modal-inner.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { useEffect, useRef } from "react"
-
-import { classnames } from "../../../utils/classnames"
-
-import styles from "./modal-inner.module.scss"
-
-export interface ModalInnerProps extends React.HTMLAttributes
{
- /**
- * The content of the modal.
- */
- children?: React.ReactNode
- /**
- * The className of the modal.
- */
- className?: string
- /**
- * A ref to the native dialog element.
- */
- dialogRef?: React.RefObject
- /**
- * Called when the cancel event fires (e.g. user presses Escape).
- * The default browser close behaviour is always prevented so React state stays in control.
- */
- onCancel?: React.ReactEventHandler
-}
-
-export function ModalInner({
- children,
- className,
- dialogRef: forwardedDialogRef,
- onCancel,
- ...props
-}: ModalInnerProps) {
- const internalDialogRef = useRef(null)
- const dialogRef = forwardedDialogRef ?? internalDialogRef
-
- useEffect(() => {
- if (dialogRef.current && !dialogRef.current.open) dialogRef.current.showModal()
- }, [dialogRef])
-
- return (
-
- )
-}
diff --git a/src/lib/components/modal/modal.module.scss b/src/lib/components/modal/modal.module.scss
new file mode 100644
index 0000000..3dcf666
--- /dev/null
+++ b/src/lib/components/modal/modal.module.scss
@@ -0,0 +1,26 @@
+.modal {
+ align-items: center;
+ background: transparent;
+ border: none;
+ color: inherit;
+ justify-content: center;
+ margin: auto;
+ height: 100vh;
+ max-height: 100vh;
+ max-width: 100vw;
+ padding: 0;
+ position: fixed;
+ width: 100vw;
+}
+
+.modal:not([open]) {
+ display: none;
+}
+
+.modal[open] {
+ display: flex;
+}
+
+.modal::backdrop {
+ background: rgb(0 0 0 / 80%);
+}
diff --git a/src/lib/components/modal/modal.tsx b/src/lib/components/modal/modal.tsx
index 5169701..217e405 100644
--- a/src/lib/components/modal/modal.tsx
+++ b/src/lib/components/modal/modal.tsx
@@ -1,23 +1,55 @@
"use client"
+import { useEffect, useRef } from "react"
import ReactDOM from "react-dom"
-import { ModalInner } from "../modal-inner"
-import type { ModalInnerProps } from "../modal-inner"
+import { classnames } from "../../../utils/classnames"
-export interface ModalProps extends ModalInnerProps {
+import styles from "./modal.module.scss"
+
+export interface ModalProps extends React.DialogHTMLAttributes {
/**
* The modal will be appended to the passed element instead of being rendered in place
* @defaultValue defaults inPlace
**/
renderTo: HTMLElement
+ /**
+ * A ref to the native dialog element.
+ */
+ dialogRef?: { readonly current: HTMLDialogElement | null }
}
-export function Modal({ renderTo, ...props }: ModalProps) {
- const modalContent =
+export function Modal({
+ renderTo,
+ dialogRef: forwardedDialogRef,
+ className,
+ onCancel,
+ children,
+ ...props
+}: ModalProps) {
+ const internalDialogRef = useRef(null)
+ const dialogRef = forwardedDialogRef ?? internalDialogRef
+
+ useEffect(() => {
+ if (dialogRef.current && !dialogRef.current.open) dialogRef.current.showModal()
+ }, [dialogRef])
+
+ const modal = (
+
+ )
if (renderTo) {
- return ReactDOM.createPortal(modalContent, renderTo)
+ return ReactDOM.createPortal(modal, renderTo)
}
- return modalContent
+ return modal
}
diff --git a/src/lib/hooks/use-modal.ts b/src/lib/hooks/use-modal.ts
index 0dd0cb6..1741f4a 100644
--- a/src/lib/hooks/use-modal.ts
+++ b/src/lib/hooks/use-modal.ts
@@ -22,20 +22,9 @@ export function useModal(props: UseModalOptions = {}) {
const dialogRef = useRef(null)
const handleClose = useCallback(() => {
- let current: ModalStates | undefined
+ if (state === ModalStates.CLOSED) return
- setState((cur) => {
- // get the current state from the hook to avoid
- // an extra dependecy and stay within the render loop
- current = cur
-
- if (cur !== ModalStates.CLOSED) return ModalStates.CLOSED
- return cur
- })
-
- // If the modal is already closed, don't do anything
- // we don't want focus back on the trigger
- if (current === ModalStates.CLOSED || !current) return
+ setState(ModalStates.CLOSED)
if (hash && window && window.location.hash === `#${hash}`) {
window.location.hash = ""
@@ -45,31 +34,18 @@ export function useModal(props: UseModalOptions = {}) {
if (dialogRef.current?.open) dialogRef.current.close()
if (triggerRef && triggerRef.current) triggerRef.current.focus()
- }, [triggerRef, hash])
+ }, [triggerRef, hash, state])
const handleOpen = useCallback(() => {
- let current: ModalStates | undefined
-
- setState((cur) => {
- if (cur !== ModalStates.OPEN) return ModalStates.OPEN
-
- // get the current state from the hook to avoid
- // an extra dependecy and stay within the render loop
- current = cur
- return cur
- })
-
- if (current === ModalStates.OPEN) {
- if (dialogRef.current && !dialogRef.current.open) dialogRef.current.showModal()
- return
- }
+ const wasOpen = state === ModalStates.OPEN
+ if (!wasOpen) setState(ModalStates.OPEN)
if (dialogRef.current && !dialogRef.current.open) dialogRef.current.showModal()
- if (hash && window && window.location.hash !== `#${hash}`) {
+ if (!wasOpen && hash && window.location.hash !== `#${hash}`) {
window.location.hash = `#${hash}`
}
- }, [hash])
+ }, [hash, state])
const toggle = useCallback(() => {
// check all states in case user tried to close the modal in a transition
diff --git a/src/lib/styles.d.ts b/src/lib/styles.d.ts
new file mode 100644
index 0000000..a92b265
--- /dev/null
+++ b/src/lib/styles.d.ts
@@ -0,0 +1,4 @@
+declare module "*.module.scss" {
+ const styles: Record
+ export default styles
+}
diff --git a/src/main.module.css b/src/main.module.css
index 4651be0..7b138cb 100644
--- a/src/main.module.css
+++ b/src/main.module.css
@@ -1,17 +1,7 @@
-.CustomModalOverlay {
+.CustomModalOverlay::backdrop {
background-color: rgb(125 125 125 / 80%);
}
-.CustomModalClose {
- border-radius: 0.25rem;
- background-color: black;
- color: white;
- font-size: 1rem;
- border: none;
- cursor: pointer;
- padding: 0.5em 1em;
-}
-
.CustomModalContent {
max-width: 50vw;
background-color: white;
@@ -31,7 +21,3 @@
--ease: cubic-bezier(0.77, 0, 0.1, 1.39);
--scale: 1;
}
-
-.CustomModalAbsolute {
- position: absolute;
-}
diff --git a/src/main.tsx b/src/main.tsx
index 17dd241..23e9a03 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,8 +1,7 @@
import React, { useRef, useEffect } from "react"
import { createRoot } from "react-dom/client"
-import { Modal, ModalContent, useModal, ModalBackdrop } from "./lib"
-import { classnames } from "./utils/classnames"
+import { Modal, ModalContent, useModal } from "./lib"
import styles from "./main.module.css"
@@ -22,10 +21,14 @@ function CustomModal() {
{isOpen && modalRootRef.current && (
-
-
-
-