Skip to content

Commit

Permalink
fix: 💄 fixed discrete transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
dobromiryor committed Aug 21, 2024
1 parent 73625fd commit aef478f
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 150 deletions.
206 changes: 102 additions & 104 deletions src/components/atoms/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,115 +13,113 @@ import { useModal } from "../../hooks/useModal";
import { Icon } from "./Icon";

interface ModalProps {
title: string;
children: ReactNode;
wrapperId?: string;
title: string;
children: ReactNode;
wrapperId?: string;
}

const createWrapperAndAppendToBody = (wrapperId: string) => {
const wrapperElement = document.createElement("div");
wrapperElement.setAttribute("id", wrapperId);
document.body.appendChild(wrapperElement);
return wrapperElement;
const wrapperElement = document.createElement("div");
wrapperElement.setAttribute("id", wrapperId);
document.body.appendChild(wrapperElement);
return wrapperElement;
};

export const Modal = ({
title,
children,
wrapperId = "portal-root",
title,
children,
wrapperId = "portal-root",
}: ModalProps) => {
const [wrapperElement, setWrapperElement] = useState<HTMLElement | null>(
null
);

const modalRef = useRef<HTMLDivElement>(null);

const [isOpen, setIsOpen] = useModal();

useLayoutEffect(() => {
let element = document.getElementById(wrapperId);
let systemCreated = false;

if (!element) {
systemCreated = true;
element = createWrapperAndAppendToBody(wrapperId);
}

setWrapperElement(element);

return () => {
if (systemCreated && element.parentNode) {
element.parentNode.removeChild(element);
}
};
}, [wrapperId]);

const handleDismiss = useCallback(() => setIsOpen(false), [setIsOpen]);
const handleStopPropagation = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
return false;
};

useEffect(() => {
if (isOpen) {
modalRef.current?.focus();
}
}, [isOpen]);

useEffect(() => {
if (isOpen) {
const handleKeypress = (e: KeyboardEvent) => {
if (e.key === "Escape") {
handleDismiss();
}
};

document.addEventListener("keyup", handleKeypress);

return () => document.removeEventListener("keyup", handleKeypress);
}
}, [handleDismiss, isOpen]);

if (wrapperElement === null) return null;

return createPortal(
<div
className={clsx(
"fixed top-0 left-0 w-full h-full bg-black/25 origin-center transition-all duration-300",
"[transition-behavior:allow-discrete] initial-opacity-0 initial-backdrop-blur-0 z-50",
isOpen
? "flex justify-center items-center opacity-100 backdrop-blur"
: "hidden opacity-0 backdrop-blur-0"
)}
onClick={handleDismiss}
>
<div
ref={modalRef}
role="dialog"
aria-modal={isOpen}
aria-labelledby={`modal-${title}`}
className={clsx(
"flex flex-col gap-4 w-full max-w-screen-sm max-h-screen p-4 m-4 rounded-xl bg-primary text-inverted-text dark:text-text origin-center transition-all duration-150 initial-scale-0 shadow-2xl",
isOpen ? "scale-100" : "scale-0"
)}
onClick={handleStopPropagation}
tabIndex={isOpen ? 0 : -1}
>
<div className="flex justify-between items-center">
<h1 id={`modal-${title}`} className="font-bold text-xl">
{title}
</h1>
<button
className="flex justify-center items-center w-6 h-6 "
aria-label="Close modal"
onClick={handleDismiss}
>
<Icon icon="close" size="24" aria-hidden />
</button>
</div>
{children}
</div>
</div>,
wrapperElement
);
const [wrapperElement, setWrapperElement] = useState<HTMLElement | null>(
null
);

const modalRef = useRef<HTMLDivElement>(null);

const [isOpen, setIsOpen] = useModal();

useLayoutEffect(() => {
let element = document.getElementById(wrapperId);
let systemCreated = false;

if (!element) {
systemCreated = true;
element = createWrapperAndAppendToBody(wrapperId);
}

setWrapperElement(element);

return () => {
if (systemCreated && element.parentNode) {
element.parentNode.removeChild(element);
}
};
}, [wrapperId]);

const handleDismiss = useCallback(() => setIsOpen(false), [setIsOpen]);
const handleStopPropagation = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
return false;
};

useEffect(() => {
if (isOpen) {
modalRef.current?.focus();
}
}, [isOpen]);

useEffect(() => {
if (isOpen) {
const handleKeypress = (e: KeyboardEvent) => {
if (e.key === "Escape") {
handleDismiss();
}
};

document.addEventListener("keyup", handleKeypress);

return () => document.removeEventListener("keyup", handleKeypress);
}
}, [handleDismiss, isOpen]);

if (wrapperElement === null) return null;

return createPortal(
<div
className={clsx(
"fixed justify-center items-center top-0 left-0 w-full h-full bg-black/25 origin-center transition-all transition-allow-discrete duration-300 z-50",
isOpen
? "flex starting:opacity-0 starting:backdrop-blur-0 opacity-100 backdrop-blur"
: "hidden starting:opacity-100 starting:backdrop-blur opacity-0 backdrop-blur-0"
)}
onClick={handleDismiss}>
<div
ref={modalRef}
role="dialog"
aria-modal={isOpen}
aria-labelledby={`modal-${title}`}
className={clsx(
"flex flex-col gap-4 w-full max-w-screen-sm max-h-screen p-4 m-4 rounded-xl bg-primary text-inverted-text dark:text-text origin-center transition-all duration-300 shadow-2xl",
isOpen
? "starting:-translate-y-8 translate-y-0"
: "starting:translate-y-0 translate-y-8"
)}
onClick={handleStopPropagation}
tabIndex={isOpen ? 0 : -1}>
<div className="flex justify-between items-center">
<h1 id={`modal-${title}`} className="font-bold text-xl">
{title}
</h1>
<button
className="flex justify-center items-center w-6 h-6 "
aria-label="Close modal"
onClick={handleDismiss}>
<Icon icon="close" size="24" aria-hidden />
</button>
</div>
{children}
</div>
</div>,
wrapperElement
);
};
102 changes: 56 additions & 46 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,61 @@ import type { Config } from "tailwindcss";
import plugin from "tailwindcss/plugin";

export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
boxShadow: {
"center-inset-2": "0 0 2px 0 var(--tw-shadow-colored) inset",
},
screens: {
xs: "360px",
},
colors: {
text: "rgb(var(--text) / <alpha-value>)",
"inverted-text": "rgb(var(--inverted-text) / <alpha-value>)",
background: "rgb(var(--background) / <alpha-value>)",
primary: "rgb(var(--primary) / <alpha-value>)",
secondary: "rgb(var(--secondary) / <alpha-value>)",
accent: "rgb(var(--accent) / <alpha-value>)",
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
boxShadow: {
"center-inset-2": "0 0 2px 0 var(--tw-shadow-colored) inset",
},
screens: {
xs: "360px",
},
colors: {
text: "rgb(var(--text) / <alpha-value>)",
"inverted-text": "rgb(var(--inverted-text) / <alpha-value>)",
background: "rgb(var(--background) / <alpha-value>)",
primary: "rgb(var(--primary) / <alpha-value>)",
secondary: "rgb(var(--secondary) / <alpha-value>)",
accent: "rgb(var(--accent) / <alpha-value>)",

slate: {
150: "#EAEFF5",
250: "#D7DFE9",
350: "#B0BCCD",
450: "#7C8CA2",
550: "#56657A",
650: "#3D4B5F",
750: "#293548",
850: "#172033",
950: "#090F21",
},
},
fontFamily: {
sans: ["Noto Sans", "sans-serif"],
},
},
},
darkMode: "class",
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
".scrollbar-none": {
"scrollbar-width": "none",
"&::-webkit-scrollbar": {
display: "none",
},
},
});
}),
],
slate: {
150: "#EAEFF5",
250: "#D7DFE9",
350: "#B0BCCD",
450: "#7C8CA2",
550: "#56657A",
650: "#3D4B5F",
750: "#293548",
850: "#172033",
950: "#090F21",
},
},
fontFamily: {
sans: ["Noto Sans", "sans-serif"],
},
},
},
darkMode: "class",
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
".scrollbar-none": {
"scrollbar-width": "none",
"&::-webkit-scrollbar": {
display: "none",
},
},
});
}),
plugin(({ addComponents }) => {
addComponents({
".transition-allow-discrete": {
"transition-behavior": "allow-discrete",
},
});
}),
({ addVariant }) => {
addVariant("starting", "@starting-style");
},
],
} satisfies Config;

0 comments on commit aef478f

Please sign in to comment.