phanpy/src/components/modal.jsx
2024-11-21 21:46:50 +08:00

163 lines
4.5 KiB
JavaScript

import './modal.css';
import { createPortal } from 'preact/compat';
import { useEffect, useLayoutEffect, useRef } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import store from '../utils/store';
import useCloseWatcher from '../utils/useCloseWatcher';
const $modalContainer = document.getElementById('modal-container');
function getBackdropThemeColor() {
return getComputedStyle(document.documentElement).getPropertyValue(
'--backdrop-theme-color',
);
}
function Modal({ children, onClose, onClick, class: className, minimized }) {
if (!children) return null;
const modalRef = useRef();
useEffect(() => {
let timer = setTimeout(() => {
const focusElement = modalRef.current?.querySelector('[tabindex="-1"]');
if (focusElement) {
focusElement.focus();
}
}, 100);
return () => clearTimeout(timer);
}, []);
const supportsCloseWatcher = window.CloseWatcher;
const escRef = useHotkeys(
'esc',
() => {
setTimeout(() => {
onClose?.();
}, 0);
},
{
enabled: !supportsCloseWatcher && !!onClose,
// Using keyup and setTimeout above
// This will run "later" to prevent clash with esc handlers from other components
keydown: false,
keyup: true,
},
[onClose],
);
useCloseWatcher(onClose, [onClose]);
useEffect(() => {
const $deckContainers = document.querySelectorAll('.deck-container');
if (minimized) {
// Similar to focusDeck in focus-deck.jsx
// Focus last deck
const page = $deckContainers[$deckContainers.length - 1]; // last one
if (page && page.tabIndex === -1) {
page.focus();
}
} else {
if (children) {
$deckContainers.forEach(($deckContainer) => {
$deckContainer.setAttribute('inert', '');
});
} else {
$deckContainers.forEach(($deckContainer) => {
$deckContainer.removeAttribute('inert');
});
}
}
return () => {
$deckContainers.forEach(($deckContainer) => {
$deckContainer.removeAttribute('inert');
});
};
}, [children, minimized]);
const $meta = useRef();
const metaColor = useRef();
useLayoutEffect(() => {
if (children && !minimized) {
const theme = store.local.get('theme');
if (theme) {
const backdropColor = getBackdropThemeColor();
console.log({ backdropColor });
$meta.current = document.querySelector(
`meta[name="theme-color"][data-theme-setting="manual"]`,
);
if ($meta.current) {
metaColor.current = $meta.current.content;
$meta.current.content = backdropColor;
}
} else {
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)')
.matches
? 'dark'
: 'light';
const backdropColor = getBackdropThemeColor();
console.log({ backdropColor });
$meta.current = document.querySelector(
`meta[name="theme-color"][media*="${colorScheme}"]`,
);
if ($meta.current) {
metaColor.current = $meta.current.content;
$meta.current.content = backdropColor;
}
}
} else {
// Reset meta color
if ($meta.current && metaColor.current) {
$meta.current.content = metaColor.current;
}
}
return () => {
// Reset meta color
if ($meta.current && metaColor.current) {
$meta.current.content = metaColor.current;
}
};
}, [children, minimized]);
const Modal = (
<div
ref={(node) => {
modalRef.current = node;
escRef(node?.querySelector?.('[tabindex="-1"]') || node);
}}
className={className}
onClick={(e) => {
onClick?.(e);
if (e.target === e.currentTarget) {
onClose?.(e);
}
}}
tabIndex={minimized ? 0 : '-1'}
inert={minimized}
onFocus={(e) => {
try {
if (e.target === e.currentTarget) {
const focusElement =
modalRef.current?.querySelector('[tabindex="-1"]');
const isFocusable =
!!focusElement &&
getComputedStyle(focusElement)?.pointerEvents !== 'none';
if (focusElement && isFocusable) {
focusElement.focus();
}
}
} catch (err) {
console.error(err);
}
}}
>
{children}
</div>
);
return createPortal(Modal, $modalContainer);
// return createPortal(children, $modalContainer);
}
export default Modal;