Create Dialog system
This commit is contained in:
parent
fdc7880faa
commit
b2f4329266
157
src/renderer/src/components/Dialog/index.css
Normal file
157
src/renderer/src/components/Dialog/index.css
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
.modalClassName {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
max-width: 20rem;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 0 0.5rem 0.25rem hsl(0 0% 0% / 10%);
|
||||||
|
}
|
||||||
|
.modalClassName::backdrop {
|
||||||
|
background: hsl(0 0% 0% / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
|
font-size: .75em;
|
||||||
|
position: absolute;
|
||||||
|
top: .25em;
|
||||||
|
right: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.Dialog {
|
||||||
|
padding: 0;
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
|
--dialog-margin-horizontal: 32px;
|
||||||
|
--dialog-margin-vertical: 24px;
|
||||||
|
--dialog-gap: 24px;
|
||||||
|
|
||||||
|
--modal-border: black;
|
||||||
|
--modal-background: rgb(25, 25, 25);
|
||||||
|
--text-default: white;
|
||||||
|
--modal-backdrop: hsl(0 0% 0% / 10%);
|
||||||
|
--space-xs: .8rem;
|
||||||
|
--space-md: 1rem;
|
||||||
|
--space-lg: 1.2rem;
|
||||||
|
--text-xl: 1.6rem;
|
||||||
|
--accent: blue;
|
||||||
|
--text-hover: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__element {
|
||||||
|
top: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(calc(-50% + 50px), -50%);
|
||||||
|
z-index: 8;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-top: var(--dialog-margin-vertical);
|
||||||
|
border: solid 1px var(--modal-border);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--modal-background);
|
||||||
|
color: var(--text-default);
|
||||||
|
opacity: 0;
|
||||||
|
/* transform: translateY(50px); */
|
||||||
|
transition:
|
||||||
|
opacity 500ms,
|
||||||
|
transform 500ms;
|
||||||
|
max-width: min(700px, 85vw);
|
||||||
|
max-height: 95vh;
|
||||||
|
|
||||||
|
/* remove padding top when there's a header element, it has its own padding */
|
||||||
|
&:has(.Dialog__header) {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.Dialog__element::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__element:popover-open,
|
||||||
|
.Dialog__element[open] {
|
||||||
|
opacity: 1;
|
||||||
|
/* transform: translateY(0); */
|
||||||
|
box-shadow: 0px 0px 0px 100vmax var(--modal-backdrop);
|
||||||
|
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__header {
|
||||||
|
display: flex;
|
||||||
|
z-index: 2;
|
||||||
|
padding-bottom: var(--dialog-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__headerTitle {
|
||||||
|
flex: 100% 1 1;
|
||||||
|
padding: var(--dialog-margin-vertical) 16px 0 var(--dialog-margin-horizontal);
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
margin: var(--space-lg) 0 0;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__Close {
|
||||||
|
padding: 0 var(--dialog-margin-horizontal) 0 0;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__Close,
|
||||||
|
.Dialog__header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
background: var(--modal-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__CloseButton {
|
||||||
|
border: none;
|
||||||
|
margin: calc(-1 * var(--space-xs));
|
||||||
|
padding: var(--space-xs);
|
||||||
|
border-radius: var(--space-xs);
|
||||||
|
background: none;
|
||||||
|
color: var(--text-default);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 250ms color;
|
||||||
|
position: absolute;
|
||||||
|
right: var(--space-lg);
|
||||||
|
top: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__CloseButton:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--accent) 0 0 0 2px inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__CloseButton:hover {
|
||||||
|
color: var(--text-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__CloseButton:active {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__CloseIcon {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__content {
|
||||||
|
padding: 0 var(--dialog-margin-horizontal) var(--dialog-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Dialog__footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: end;
|
||||||
|
padding: 0 var(--dialog-margin-horizontal) var(--dialog-margin-vertical)
|
||||||
|
var(--dialog-margin-horizontal);
|
||||||
|
}
|
||||||
109
src/renderer/src/components/Dialog/index.tsx
Normal file
109
src/renderer/src/components/Dialog/index.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { ReactNode, SyntheticEvent, KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
className?: string
|
||||||
|
children: ReactNode
|
||||||
|
showCloseButton: boolean
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Dialog = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = false,
|
||||||
|
onClose
|
||||||
|
}: DialogProps) => {
|
||||||
|
const dialogRef = useRef<HTMLDialogElement | null>(null)
|
||||||
|
const onCloseRef = useRef(onClose)
|
||||||
|
onCloseRef.current = onClose
|
||||||
|
const [focusOnClose, setFocusOnClose] = useState<HTMLElement | null>(null)
|
||||||
|
// const { disableDialogBackdropClose } = useContext(ContextProvider)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFocusOnClose(document.querySelector<HTMLElement>('*:focus'))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
onCloseRef.current()
|
||||||
|
if (focusOnClose) {
|
||||||
|
setTimeout(() => focusOnClose.focus(), 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dialog = dialogRef.current
|
||||||
|
console.log(dialog)
|
||||||
|
if (dialog) {
|
||||||
|
const cancel = () => {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
dialog.addEventListener('cancel', cancel)
|
||||||
|
|
||||||
|
// if (disableDialogBackdropClose) {
|
||||||
|
// dialog['showPopover']()
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// dialog.removeEventListener('cancel', cancel)
|
||||||
|
// dialog['hidePopover']()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
dialog.showModal()
|
||||||
|
console.log('dialog open')
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dialog.removeEventListener('cancel', cancel)
|
||||||
|
dialog.close()
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}, [dialogRef.current/*, disableDialogBackdropClose*/])
|
||||||
|
|
||||||
|
const onDialogClick = useCallback(
|
||||||
|
(e: SyntheticEvent) => {
|
||||||
|
if (e.target === dialogRef.current) {
|
||||||
|
const ev = e.nativeEvent as MouseEvent
|
||||||
|
const tg = e.target as HTMLElement
|
||||||
|
if (
|
||||||
|
ev.offsetX < 0 ||
|
||||||
|
ev.offsetX > tg.offsetWidth ||
|
||||||
|
ev.offsetY < 0 ||
|
||||||
|
ev.offsetY > tg.offsetHeight
|
||||||
|
) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClose]
|
||||||
|
)
|
||||||
|
|
||||||
|
const closeIfEsc = (event: KeyboardEvent<HTMLDialogElement>) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Dialog">
|
||||||
|
<dialog
|
||||||
|
className={`Dialog__element ${className}`}
|
||||||
|
ref={dialogRef}
|
||||||
|
onClick={onDialogClick}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore, this feature is new and not yet typed
|
||||||
|
popover="manual"
|
||||||
|
onKeyUp={closeIfEsc}
|
||||||
|
>
|
||||||
|
{showCloseButton && (
|
||||||
|
<div className="Dialog__Close">
|
||||||
|
<button className="Dialog__CloseButton" onClick={close}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in a new issue