Refactor
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
965093b6d0
commit
f1592e490a
|
@ -111,78 +111,80 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.burger {
|
.burger {
|
||||||
position: fixed;
|
|
||||||
display: none;
|
display: none;
|
||||||
bottom: calc(32rem / 16);
|
|
||||||
left: calc(16rem / 16);
|
|
||||||
width: calc(62rem / 16);
|
|
||||||
height: calc(57rem / 16);
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10;
|
|
||||||
background: var(--light-blue-2);
|
|
||||||
border-radius: calc(5rem / 16);
|
|
||||||
transition: bottom 0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hiddenBurger {
|
|
||||||
bottom: calc(-94rem / 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.burger > div {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: calc(11rem / 16) calc(9rem / 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.burger > div > div {
|
|
||||||
border-radius: calc(5rem / 16);
|
|
||||||
border: calc(2.5rem / 16) solid var(--blue-2);
|
|
||||||
position: relative;
|
|
||||||
background: var(--blue-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobileNav {
|
|
||||||
position: fixed;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: var(--light-blue-1);
|
|
||||||
width: 90%;
|
|
||||||
height: 100%;
|
|
||||||
text-align: left;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
transform: translateX(0);
|
|
||||||
z-index: 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobileNavClosed {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobileNavTitle {
|
.mobileNavTitle {
|
||||||
font-size: calc(24rem / 16);
|
display: none;
|
||||||
font-weight: 700;
|
|
||||||
margin: calc(14rem / 16);
|
|
||||||
margin-top: calc(39rem / 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: calc(768rem / 16)) {
|
@media only screen and (max-width: calc(768rem / 16)) {
|
||||||
.burger {
|
.burger {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.wrapper > .nav {
|
|
||||||
display: none;
|
.burger {
|
||||||
|
position: fixed;
|
||||||
|
border: none;
|
||||||
|
bottom: calc(32rem / 16);
|
||||||
|
left: calc(16rem / 16);
|
||||||
|
width: calc(62rem / 16);
|
||||||
|
height: calc(57rem / 16);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
background: var(--light-blue-2);
|
||||||
|
border-radius: calc(5rem / 16);
|
||||||
|
transition: transform 0.6s ease-in-out;
|
||||||
|
transform: translateY(calc(94rem / 16));
|
||||||
|
padding: calc(11rem / 16) calc(9rem / 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.burgerVisible {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.burger > svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
stroke: var(--blue-2);
|
||||||
|
}
|
||||||
|
|
||||||
.navItem {
|
.navItem {
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: 0 calc(16rem / 16);
|
padding: 0 calc(16rem / 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navWrapper {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9;
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileNav {
|
||||||
|
background: var(--light-blue-1);
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileNavOpen {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileNavTitle {
|
||||||
|
display: flex;
|
||||||
|
font-size: calc(24rem / 16);
|
||||||
|
font-weight: 700;
|
||||||
|
margin: calc(14rem / 16);
|
||||||
|
margin-top: calc(39rem / 16);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ interface Props {
|
||||||
export function OrganizedContent(props: Props) {
|
export function OrganizedContent(props: Props) {
|
||||||
const sections = createSections(props.sections);
|
const sections = createSections(props.sections);
|
||||||
const currentIndex = sections.findIndex(({ id }) => id === props.currentId);
|
const currentIndex = sections.findIndex(({ id }) => id === props.currentId);
|
||||||
const [open, setOpen] = useState(false);
|
const [mobileNavOpen, setMobileNavOpen] = useState(false);
|
||||||
|
|
||||||
if (currentIndex < 0) {
|
if (currentIndex < 0) {
|
||||||
throw new Error(`Section with ID ${props.currentId} was not found`);
|
throw new Error(`Section with ID ${props.currentId} was not found`);
|
||||||
|
@ -43,19 +43,37 @@ export function OrganizedContent(props: Props) {
|
||||||
|
|
||||||
const section = sections[currentIndex];
|
const section = sections[currentIndex];
|
||||||
const isReadAll = section.id === READ_ALL_ID;
|
const isReadAll = section.id === READ_ALL_ID;
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const isVisible = useOnScreen(ref.current);
|
||||||
|
const burgerVisible = useBurger(isVisible);
|
||||||
|
const navWrapperStyles = mobileNavOpen
|
||||||
|
? [styles.navWrapper, styles.mobileNavOpen]
|
||||||
|
: [styles.navWrapper];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
open
|
mobileNavOpen
|
||||||
? (document.body.style.overflow = "hidden")
|
? (document.body.style.overflow = "hidden")
|
||||||
: (document.body.style.overflow = "visible");
|
: (document.body.style.overflow = "visible");
|
||||||
}, [open]);
|
}, [mobileNavOpen]);
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
const isVisible = useOnScreen(ref);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper} ref={ref}>
|
<div className={styles.wrapper} ref={ref}>
|
||||||
<Nav sections={sections} currentIndex={currentIndex} link={props.link} />
|
<div
|
||||||
|
className={navWrapperStyles.join(" ")}
|
||||||
|
onClick={() => setMobileNavOpen(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={styles.mobileNav}
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Nav
|
||||||
|
sections={sections}
|
||||||
|
currentIndex={currentIndex}
|
||||||
|
link={props.link}
|
||||||
|
pageTitle={props.pageTitle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{isReadAll ? (
|
{isReadAll ? (
|
||||||
<section.Content />
|
<section.Content />
|
||||||
|
@ -73,15 +91,49 @@ export function OrganizedContent(props: Props) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<MobileWrapper
|
<button
|
||||||
open={open}
|
className={`${styles.burger} ${
|
||||||
setOpen={setOpen}
|
burgerVisible ? styles.burgerVisible : ""
|
||||||
sections={sections}
|
}`}
|
||||||
currentIndex={currentIndex}
|
onClick={() => setMobileNavOpen(!mobileNavOpen)}
|
||||||
link={props.link}
|
>
|
||||||
pageTitle={props.pageTitle}
|
{/* this is copied from hamburger.svg with changed colors */}
|
||||||
componentIsVisible={isVisible}
|
<svg
|
||||||
/>
|
width="30"
|
||||||
|
height="23"
|
||||||
|
viewBox="0 0 30 23"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<line
|
||||||
|
x1="28"
|
||||||
|
y1="2"
|
||||||
|
x2="2"
|
||||||
|
y2="2"
|
||||||
|
strokeWidth="4"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="28"
|
||||||
|
y1="11.375"
|
||||||
|
x2="2"
|
||||||
|
y2="11.375"
|
||||||
|
strokeWidth="4"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="28"
|
||||||
|
y1="20.75"
|
||||||
|
x2="2"
|
||||||
|
y2="20.75"
|
||||||
|
strokeWidth="4"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -90,13 +142,13 @@ interface NavProps {
|
||||||
sections: Section[];
|
sections: Section[];
|
||||||
currentIndex: number;
|
currentIndex: number;
|
||||||
link: Link;
|
link: Link;
|
||||||
pageTitle?: string;
|
pageTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Nav({ sections, currentIndex, link: Link, pageTitle }: NavProps) {
|
function Nav({ sections, currentIndex, link: Link, pageTitle }: NavProps) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.nav}>
|
<div className={styles.nav}>
|
||||||
{pageTitle && <h1 className={styles.mobileNavTitle}>{pageTitle}</h1>}
|
<h1 className={styles.mobileNavTitle}>{pageTitle}</h1>
|
||||||
{sections.map((section, index) => {
|
{sections.map((section, index) => {
|
||||||
const classNames = [styles.navItem];
|
const classNames = [styles.navItem];
|
||||||
|
|
||||||
|
@ -164,116 +216,6 @@ function Footer({ sections, currentIndex, link: Link }: FooterProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MobileProps {
|
|
||||||
open: boolean;
|
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
sections: Section[];
|
|
||||||
currentIndex: number;
|
|
||||||
link: Link;
|
|
||||||
pageTitle: string;
|
|
||||||
componentIsVisible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MobileWrapper(mobileProps: MobileProps) {
|
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
||||||
useOutsideAlerter(wrapperRef, mobileProps.setOpen);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={wrapperRef}>
|
|
||||||
<Burger {...mobileProps} />
|
|
||||||
<Menu {...mobileProps} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BurgerProps {
|
|
||||||
open: boolean;
|
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
componentIsVisible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Burger = ({ open, setOpen, componentIsVisible }: BurgerProps) => {
|
|
||||||
const [prevScrollPos, setPrevScrollPos] = useState(0);
|
|
||||||
const [burgerVisible, setBurgerVisible] = useState(true);
|
|
||||||
|
|
||||||
const handleScroll = useDebounce(() => {
|
|
||||||
// find current scroll position
|
|
||||||
const currentScrollPos = window.pageYOffset;
|
|
||||||
setBurgerVisible(
|
|
||||||
componentIsVisible &&
|
|
||||||
((prevScrollPos > currentScrollPos &&
|
|
||||||
prevScrollPos - currentScrollPos > 70) ||
|
|
||||||
currentScrollPos < 10)
|
|
||||||
);
|
|
||||||
|
|
||||||
// set state to new scroll position
|
|
||||||
setPrevScrollPos(currentScrollPos);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("scroll", handleScroll);
|
|
||||||
|
|
||||||
return () => window.removeEventListener("scroll", handleScroll);
|
|
||||||
}, [handleScroll]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
styles.burger + " " + (burgerVisible ? "" : styles.hiddenBurger)
|
|
||||||
}
|
|
||||||
onClick={() => setOpen(!open)}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MenuProps {
|
|
||||||
open: boolean;
|
|
||||||
sections: Section[];
|
|
||||||
currentIndex: number;
|
|
||||||
link: Link;
|
|
||||||
pageTitle: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Menu = ({ open, sections, currentIndex, link, pageTitle }: MenuProps) => {
|
|
||||||
const mobileNav = open
|
|
||||||
? styles.mobileNav
|
|
||||||
: styles.mobileNav + " " + styles.mobileNavClosed;
|
|
||||||
return (
|
|
||||||
<div className={mobileNav}>
|
|
||||||
<Nav
|
|
||||||
sections={sections}
|
|
||||||
currentIndex={currentIndex}
|
|
||||||
link={link}
|
|
||||||
pageTitle={pageTitle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function useOutsideAlerter(
|
|
||||||
ref: React.RefObject<HTMLDivElement>,
|
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
|
||||||
) {
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event: Event) => {
|
|
||||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [ref, setOpen]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDebounce(func: () => void, delay = 300) {
|
function useDebounce(func: () => void, delay = 300) {
|
||||||
const timerRef = useRef<number | undefined>(undefined);
|
const timerRef = useRef<number | undefined>(undefined);
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
|
@ -326,7 +268,7 @@ function Arrow({ direction }: { direction: "left" | "right" }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useOnScreen(ref: React.RefObject<HTMLDivElement>) {
|
function useOnScreen(element: HTMLDivElement | null) {
|
||||||
const [isIntersecting, setIntersecting] = useState(false);
|
const [isIntersecting, setIntersecting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -334,14 +276,41 @@ function useOnScreen(ref: React.RefObject<HTMLDivElement>) {
|
||||||
setIntersecting(entry.isIntersecting)
|
setIntersecting(entry.isIntersecting)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ref.current) {
|
if (element) {
|
||||||
observer.observe(ref.current);
|
observer.observe(element);
|
||||||
}
|
}
|
||||||
// Remove the observer as soon as the component is unmounted
|
// Remove the observer as soon as the component is unmounted
|
||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [ref]);
|
}, [element]);
|
||||||
|
|
||||||
return isIntersecting;
|
return isIntersecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useBurger(componentIsVisible: boolean): boolean {
|
||||||
|
const [prevScrollPos, setPrevScrollPos] = useState(0);
|
||||||
|
const [burgerVisible, setBurgerVisible] = useState(true);
|
||||||
|
|
||||||
|
const handleScroll = useDebounce(() => {
|
||||||
|
// find current scroll position
|
||||||
|
const currentScrollPos = window.pageYOffset;
|
||||||
|
setBurgerVisible(
|
||||||
|
componentIsVisible &&
|
||||||
|
((prevScrollPos > currentScrollPos &&
|
||||||
|
prevScrollPos - currentScrollPos > 70) ||
|
||||||
|
currentScrollPos < 10)
|
||||||
|
);
|
||||||
|
|
||||||
|
// set state to new scroll position
|
||||||
|
setPrevScrollPos(currentScrollPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
|
||||||
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
|
}, [handleScroll]);
|
||||||
|
|
||||||
|
return burgerVisible;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue