Refactor
continuous-integration/drone/push Build is passing Details

This commit is contained in:
William Tran 2021-07-24 23:29:07 -04:00
parent 965093b6d0
commit f1592e490a
2 changed files with 157 additions and 186 deletions

View File

@ -111,8 +111,21 @@
}
.burger {
position: fixed;
display: none;
}
.mobileNavTitle {
display: none;
}
@media only screen and (max-width: calc(768rem / 16)) {
.burger {
display: flex;
}
.burger {
position: fixed;
border: none;
bottom: calc(32rem / 16);
left: calc(16rem / 16);
width: calc(62rem / 16);
@ -121,68 +134,57 @@
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;
transition: transform 0.6s ease-in-out;
transform: translateY(calc(94rem / 16));
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);
}
.burgerVisible {
transform: translateY(0);
}
.mobileNav {
position: fixed;
display: flex;
flex-direction: column;
background: var(--light-blue-1);
width: 90%;
.burger > svg {
width: 100%;
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 {
font-size: calc(24rem / 16);
font-weight: 700;
margin: calc(14rem / 16);
margin-top: calc(39rem / 16);
}
@media only screen and (max-width: calc(768rem / 16)) {
.burger {
display: flex;
}
.wrapper > .nav {
display: none;
stroke: var(--blue-2);
}
.navItem {
width: auto;
padding: 0 calc(16rem / 16);
}
.nav {
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);
}
}

View File

@ -35,7 +35,7 @@ interface Props {
export function OrganizedContent(props: Props) {
const sections = createSections(props.sections);
const currentIndex = sections.findIndex(({ id }) => id === props.currentId);
const [open, setOpen] = useState(false);
const [mobileNavOpen, setMobileNavOpen] = useState(false);
if (currentIndex < 0) {
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 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(() => {
open
mobileNavOpen
? (document.body.style.overflow = "hidden")
: (document.body.style.overflow = "visible");
}, [open]);
const ref = useRef<HTMLDivElement>(null);
const isVisible = useOnScreen(ref);
}, [mobileNavOpen]);
return (
<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}>
{isReadAll ? (
<section.Content />
@ -73,15 +91,49 @@ export function OrganizedContent(props: Props) {
</>
)}
</div>
<MobileWrapper
open={open}
setOpen={setOpen}
sections={sections}
currentIndex={currentIndex}
link={props.link}
pageTitle={props.pageTitle}
componentIsVisible={isVisible}
<button
className={`${styles.burger} ${
burgerVisible ? styles.burgerVisible : ""
}`}
onClick={() => setMobileNavOpen(!mobileNavOpen)}
>
{/* this is copied from hamburger.svg with changed colors */}
<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>
);
}
@ -90,13 +142,13 @@ interface NavProps {
sections: Section[];
currentIndex: number;
link: Link;
pageTitle?: string;
pageTitle: string;
}
function Nav({ sections, currentIndex, link: Link, pageTitle }: NavProps) {
return (
<div className={styles.nav}>
{pageTitle && <h1 className={styles.mobileNavTitle}>{pageTitle}</h1>}
<h1 className={styles.mobileNavTitle}>{pageTitle}</h1>
{sections.map((section, index) => {
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) {
const timerRef = useRef<number | undefined>(undefined);
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);
useEffect(() => {
@ -334,14 +276,41 @@ function useOnScreen(ref: React.RefObject<HTMLDivElement>) {
setIntersecting(entry.isIntersecting)
);
if (ref.current) {
observer.observe(ref.current);
if (element) {
observer.observe(element);
}
// Remove the observer as soon as the component is unmounted
return () => {
observer.disconnect();
};
}, [ref]);
}, [element]);
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;
}