Files
EpicNext-Cms/src/components/cms-info-popup.tsx
T
openhands 3827f3e686
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 2m4s
Migrate from framer-motion to motion/react
2026-07-28 18:49:25 +02:00

105 lines
2.7 KiB
TypeScript

"use client";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
const cmsDetails = [
["Edition", "EpicNext"],
["Interface", "Modern CMS"],
["Runtime", "Next.js App Router"],
];
const credits = [
["Remco", "EpicNext direction and development"],
["Life", "Next.js architecture and CMS refinements"],
];
export function CmsInfoPopup() {
const [open, setOpen] = useState(false);
useEffect(() => {
if (window.location.hash === "#cms-info") setOpen(true);
}, []);
useEffect(() => {
if (!open) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [open]);
return (
<>
<button
type="button"
className="cms-info-trigger"
onClick={() => setOpen(true)}
>
EpicNext Info
</button>
<AnimatePresence>
{open ? (
<motion.div
key="cms-info-backdrop"
className="cms-info-backdrop"
role="presentation"
onMouseDown={() => setOpen(false)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<motion.section
key="cms-info-dialog"
aria-labelledby="cms-info-title"
aria-modal="true"
className="cms-info-dialog"
role="dialog"
onMouseDown={(event) => event.stopPropagation()}
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.2 }}
>
<button
type="button"
aria-label="Close CMS info"
className="cms-info-close"
onClick={() => setOpen(false)}
>
x
</button>
<div className="cms-info-kicker">CMS information</div>
<h2 id="cms-info-title">EpicNext</h2>
<div className="cms-info-badge">Modern Next.js CMS</div>
<p className="cms-info-copy">
A modern web layer for the hotel community, focused on fast
navigation, clean administration, and a coherent EpicNext
experience.
</p>
<dl className="cms-info-grid">
{cmsDetails.map(([label, value]) => (
<div key={label}>
<dt>{label}</dt>
<dd>{value}</dd>
</div>
))}
</dl>
<section className="cms-info-credit-list" aria-label="Credits">
{credits.map(([name, role]) => (
<div className="cms-info-credit" key={name}>
<span>{name}</span>
<small>{role}</small>
</div>
))}
</section>
</motion.section>
</motion.div>
) : null}
</AnimatePresence>
</>
);
}