219 lines
6.7 KiB
React
219 lines
6.7 KiB
React
import { useEffect, useState } from "react";
|
||
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
|
||
import { company, navigation, phoneHref } from "../data";
|
||
import { Arrow, WhatsAppAction } from "./Primitives";
|
||
import "./Layout.css";
|
||
|
||
function Brand() {
|
||
return (
|
||
<Link className="brand" to="/" aria-label="RRS home">
|
||
<img
|
||
className="brand__logo"
|
||
src="/images/RRSLOGO.png"
|
||
alt="Radhe Radhe Synthetics"
|
||
width="1024"
|
||
height="809"
|
||
/>
|
||
<span className="brand__name">{company.shortName}</span>
|
||
</Link>
|
||
);
|
||
}
|
||
|
||
function Header() {
|
||
const [menuOpen, setMenuOpen] = useState(false);
|
||
const closeMenu = () => setMenuOpen(false);
|
||
return (
|
||
<header
|
||
className="site-header"
|
||
onKeyDown={(event) => {
|
||
if (menuOpen && event.key === "Escape") {
|
||
closeMenu();
|
||
document.getElementById("menu-toggle")?.focus();
|
||
}
|
||
}}
|
||
>
|
||
<div className="contact-strip">
|
||
<span>Contact RRS</span>
|
||
<div className="contact-strip__details">
|
||
<a href={`mailto:${company.email}`}>{company.email}</a>
|
||
<span aria-hidden="true">·</span>
|
||
<a href={phoneHref(company.phone)}>{company.phone}</a>
|
||
</div>
|
||
</div>
|
||
<div className="site-header__inner">
|
||
<Brand />
|
||
<button
|
||
id="menu-toggle"
|
||
className="site-header__toggle"
|
||
type="button"
|
||
aria-controls="primary-navigation"
|
||
aria-expanded={menuOpen}
|
||
onClick={() => setMenuOpen((open) => !open)}
|
||
>
|
||
{menuOpen ? "Close" : "Menu"}
|
||
<span aria-hidden="true">{menuOpen ? "−" : "+"}</span>
|
||
</button>
|
||
<nav
|
||
id="primary-navigation"
|
||
aria-label="Main navigation"
|
||
className={`site-nav${menuOpen ? " site-nav--open" : ""}`}
|
||
>
|
||
{navigation.map(({ to, label }) => (
|
||
<NavLink
|
||
key={to}
|
||
to={to}
|
||
end={to === "/"}
|
||
onClick={closeMenu}
|
||
className={({ isActive }) =>
|
||
`site-nav__link${isActive ? " site-nav__link--active" : ""}`
|
||
}
|
||
>
|
||
{label}
|
||
</NavLink>
|
||
))}
|
||
<Link
|
||
className="site-nav__contact"
|
||
to="/contact#whatsapp"
|
||
onClick={closeMenu}
|
||
>
|
||
Text us on WhatsApp <Arrow diagonal />
|
||
</Link>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|
||
|
||
function Footer() {
|
||
return (
|
||
<footer className="site-footer">
|
||
<div className="site-footer__top">
|
||
<div className="site-footer__intro">
|
||
<p className="site-footer__name">{company.name}</p>
|
||
<p className="site-footer__description">{company.description}</p>
|
||
</div>
|
||
<nav aria-label="Footer navigation" className="site-footer__nav">
|
||
<span className="site-footer__label">Explore</span>
|
||
{navigation.map(({ to, label }) => (
|
||
<Link className="site-footer__link" to={to} key={to}>
|
||
{label}
|
||
</Link>
|
||
))}
|
||
</nav>
|
||
<div className="site-footer__contact">
|
||
<span className="site-footer__label">Commercial enquiries</span>
|
||
<Link className="site-footer__link" to="/contact#whatsapp">
|
||
WhatsApp <Arrow diagonal />
|
||
</Link>
|
||
<Link className="site-footer__link" to="/contact#direct-contact">
|
||
Phone · Email · Address <Arrow diagonal />
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
<div className="site-footer__bottom">
|
||
<span className="site-footer__wordmark" aria-hidden="true">
|
||
RRS.
|
||
</span>
|
||
<p className="site-footer__signature">
|
||
Built to develop.
|
||
<br />
|
||
<em>Built to repeat.</em>
|
||
</p>
|
||
</div>
|
||
<div className="site-footer__legal">
|
||
<span>
|
||
© {new Date().getFullYear()} Radhe Radhe Synthetics Pvt. Ltd.
|
||
</span>
|
||
<span>Commercial supply: grey fabric only.</span>
|
||
<a className="site-footer__top-link" href="#top">
|
||
Back to top ↑
|
||
</a>
|
||
</div>
|
||
</footer>
|
||
);
|
||
}
|
||
|
||
export function ContactBanner({
|
||
title = (
|
||
<>
|
||
Developing your
|
||
<br />
|
||
<em>next construction?</em>
|
||
</>
|
||
),
|
||
text = "Talk to us about fabric structure, yarn combinations, bulk requirements or new developments.",
|
||
}) {
|
||
return (
|
||
<section className="contact-banner">
|
||
<div className="contact-banner__copy">
|
||
<p className="contact-banner__label">
|
||
A conversation is where it begins
|
||
</p>
|
||
<h2 className="contact-banner__title">{title}</h2>
|
||
<p className="contact-banner__text">{text}</p>
|
||
<WhatsAppAction variant="light" />
|
||
</div>
|
||
<div className="contact-banner__motif" aria-hidden="true">
|
||
<span className="contact-banner__loop" />
|
||
<span className="contact-banner__loop" />
|
||
<span className="contact-banner__loop" />
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
export default function Layout() {
|
||
const { pathname, hash, key } = useLocation();
|
||
useEffect(() => {
|
||
const title =
|
||
navigation.find(({ to }) => to === pathname)?.label ?? "Page not found";
|
||
document.title = `${title === "Home" ? "RRS" : `${title} - RRS`} | Radhe Radhe Synthetics`;
|
||
const target = hash
|
||
? document.getElementById(hash.slice(1))
|
||
: document.getElementById("main-content");
|
||
if (hash && target) target.scrollIntoView({ behavior: "instant" });
|
||
else window.scrollTo({ top: 0, behavior: "instant" });
|
||
target?.focus({ preventScroll: true });
|
||
}, [pathname, hash, key]);
|
||
|
||
useEffect(() => {
|
||
const sections = [...document.querySelectorAll("#main-content > section")];
|
||
const reducedMotion = window.matchMedia(
|
||
"(prefers-reduced-motion: reduce)",
|
||
).matches;
|
||
|
||
if (reducedMotion || !("IntersectionObserver" in window)) {
|
||
sections.forEach((section) => section.classList.add("reveal--visible"));
|
||
return undefined;
|
||
}
|
||
|
||
sections.forEach((section) => section.classList.add("reveal"));
|
||
const observer = new IntersectionObserver(
|
||
(entries) => {
|
||
entries.forEach((entry) => {
|
||
if (!entry.isIntersecting) return;
|
||
entry.target.classList.add("reveal--visible");
|
||
observer.unobserve(entry.target);
|
||
});
|
||
},
|
||
{ threshold: 0.08, rootMargin: "0px 0px -8% 0px" },
|
||
);
|
||
sections.forEach((section) => observer.observe(section));
|
||
|
||
return () => observer.disconnect();
|
||
}, [pathname, key]);
|
||
|
||
return (
|
||
<div className="site-shell" id="top">
|
||
<a className="skip-link" href="#main-content">
|
||
Skip to content
|
||
</a>
|
||
<Header key={key} />
|
||
<main className="site-main" id="main-content" tabIndex={-1}>
|
||
<Outlet />
|
||
</main>
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|