forked from defunkt/toes
109 lines
2.4 KiB
TypeScript
109 lines
2.4 KiB
TypeScript
import type { Child } from 'hono/jsx'
|
||
import { define } from '@because/forge'
|
||
import { theme } from '../themes'
|
||
|
||
let modalTitle: string | null = null
|
||
let modalContent: (() => Child) | null = null
|
||
let renderFn: (() => void) | null = null
|
||
|
||
export const initModal = (render: () => void) => {
|
||
renderFn = render
|
||
}
|
||
|
||
export const openModal = (title: string, content: () => Child) => {
|
||
modalTitle = title
|
||
modalContent = content
|
||
renderFn?.()
|
||
requestAnimationFrame(() => {
|
||
document.querySelector<HTMLInputElement>('[data-modal-body] input')?.focus()
|
||
})
|
||
}
|
||
|
||
export const closeModal = () => {
|
||
modalTitle = null
|
||
modalContent = null
|
||
renderFn?.()
|
||
}
|
||
|
||
export const rerenderModal = () => {
|
||
renderFn?.()
|
||
}
|
||
|
||
// ESC key handler
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && modalContent) {
|
||
closeModal()
|
||
}
|
||
})
|
||
|
||
const ModalBackdrop = define('ModalBackdrop', {
|
||
position: 'fixed',
|
||
inset: 0,
|
||
background: 'rgba(0, 0, 0, 0.5)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
zIndex: 1000,
|
||
})
|
||
|
||
const ModalBox = define('ModalBox', {
|
||
background: theme('colors-bg'),
|
||
borderRadius: theme('radius-md'),
|
||
border: `1px solid ${theme('colors-border')}`,
|
||
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.2)',
|
||
maxWidth: 500,
|
||
width: '90%',
|
||
maxHeight: '80vh',
|
||
overflow: 'auto',
|
||
})
|
||
|
||
const ModalHeader = define('ModalHeader', {
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
padding: '16px 20px',
|
||
borderBottom: `1px solid ${theme('colors-border')}`,
|
||
})
|
||
|
||
const ModalTitle = define('ModalTitle', {
|
||
fontSize: 16,
|
||
fontWeight: 600,
|
||
margin: 0,
|
||
})
|
||
|
||
const ModalCloseButton = define('ModalCloseButton', {
|
||
base: 'button',
|
||
background: 'none',
|
||
border: 'none',
|
||
cursor: 'pointer',
|
||
padding: 4,
|
||
fontSize: 18,
|
||
color: theme('colors-textMuted'),
|
||
lineHeight: 1,
|
||
selectors: {
|
||
'&:hover': { color: theme('colors-text') },
|
||
},
|
||
})
|
||
|
||
const ModalBody = define('ModalBody', {
|
||
padding: 20,
|
||
})
|
||
|
||
export const Modal = () => {
|
||
if (!modalContent) return null
|
||
|
||
return (
|
||
<ModalBackdrop onClick={closeModal}>
|
||
<ModalBox onClick={(e: MouseEvent) => e.stopPropagation()}>
|
||
<ModalHeader>
|
||
<ModalTitle>{modalTitle}</ModalTitle>
|
||
<ModalCloseButton onClick={closeModal}>×</ModalCloseButton>
|
||
</ModalHeader>
|
||
<ModalBody data-modal-body>
|
||
{modalContent()}
|
||
</ModalBody>
|
||
</ModalBox>
|
||
</ModalBackdrop>
|
||
)
|
||
}
|