feat: mobile dropdown, minor changes

This commit is contained in:
gitlimes
2023-09-19 18:15:35 +02:00
parent e831bc69c7
commit be8422d4a5
7 changed files with 163 additions and 36 deletions

View File

@@ -10,8 +10,9 @@ import styles from './Dropdown.module.css';
* A reusable object that renders a dropdown.
*
* @param {React.useState} openState: a state object [state, setState],
* where state is an int which refers to the index of the dropdownContent to render + 1
* (having it be +1 lets us use 0 as a "do not render" magic number)
* where state.e is an int which refers to the index of the dropdownContent to render + 1
* (having it be +1 lets us use 0 as a "do not render" magic number), and c notes if the
* element was clicked (true) or hovered (falase)
*
* @param {Array} dropdownContents: an array of objects, structured as
* follows:
@@ -20,12 +21,12 @@ import styles from './Dropdown.module.css';
* the corresponding state, and tr1 is the reference to the element that triggered
* the dropdown in the first place
*
* @param {React.useRef} boundaryRef: a ref to the object to use as a boundary (the
* dropdown will always be rendered to be [horizontally] inside of the element)
* @param {React.useRef} boundaryRef: a ref to the object to use as a boundary (the
* dropdown will always be rendered to be [horizontally] inside of the element)
*
* @param {Number} boundaryTolerance: the amount of pixels by which the dropdown can poke out of the boundary.
* @param {Number} boundaryTolerance: the amount of pixels by which the dropdown can poke out of the boundary.
*
* @example
* @example
* <Dropdown
* dropdownContents={[
* { el: <El1 />, tr: tr1 },
@@ -65,7 +66,7 @@ export default function Dropdown(ctx) {
const pxToInt = (px) => parseInt(px.replace('px', ''));
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
if (dropdownContentRect && open) {
if (dropdownContentRect && open.e) {
const sectionWidth = pxToInt(getComputedStyle(boundaryRef.current).width);
const dropdownWidth = dropdownRect.width;
@@ -92,8 +93,8 @@ export default function Dropdown(ctx) {
return getTriggerOffset(trRef) + getRealCenter(trRef);
}
const arrowOffset = getLeftOffset(dropdownTriggers[open]);
const leftOffset = getLeftOffset(dropdownTriggers[open]) - dropdownWidth / 2;
const arrowOffset = getLeftOffset(dropdownTriggers[open.e]);
const leftOffset = getLeftOffset(dropdownTriggers[open.e]) - dropdownWidth / 2;
const leftBoundary = 0 - boundaryTolerance;
const rightBoundary = sectionWidth - dropdownWidth + boundaryTolerance;
@@ -111,29 +112,44 @@ export default function Dropdown(ctx) {
//dropdown.current.style.width = 0;
dropdown.current.style.height = 0;
}
}, [open, dropdownTriggers, boundaryTolerance, boundaryRef]);
const closeOnClickOutside = (e) => {
if (dropdown.current && !dropdown.current.contains(e.target)) {
setOpen({ e: 0, c: false });
}
};
document.addEventListener('mousedown', closeOnClickOutside);
return () => {
document.removeEventListener('mousedown', closeOnClickOutside);
};
}, [open, setOpen, dropdownTriggers, boundaryTolerance, boundaryRef]);
return (
<>
<div
className={styles.dropdown}
onMouseEnter={() => setOpen(open)}
onPointerEnter={() => setOpen(open)}
onMouseLeave={() => setOpen(0)}
onPointerLeave={() => setOpen(0)}
onMouseEnter={() => setOpen({ e: open.e, c: open.c })}
onPointerEnter={() => setOpen({ e: open.e, c: open.c })}
onMouseLeave={() => {
open.c ? null : setOpen({ e: 0, c: false });
}}
onPointerLeave={() => (open.c ? null : setOpen({ e: 0, c: false }))}
onMouseDown={() => setTimeout(() => setOpen({ e: 0, c: false }), 200)}
onPointerDown={() => setTimeout(() => setOpen({ e: 0, c: false }), 200)}
ref={dropdown}
style={{
border: open ? '' : 'none',
border: open.e ? '' : 'none',
}}
>
<div ref={dropdownContent} className={styles.dropdownContent}>
{dropdownElements[open]}
{dropdownElements[open.e]}
</div>
</div>
<div
className={classNames(styles.dropdownArrow, {
[styles.arrowAppear]: Boolean(open),
[styles.arrowDisappear]: !Boolean(open),
[styles.arrowAppear]: Boolean(open.e),
[styles.arrowDisappear]: !Boolean(open.e),
})}
ref={dropdownArrow}
/>

View File

@@ -27,12 +27,12 @@
.dropdownArrow.arrowAppear {
transform: scale(1) translateX(-50%) rotate(45deg);
transition: transform 250ms ease-out 200ms;
transition: transform 250ms ease-out 200ms, left ease-out 200ms;
}
.dropdownArrow.arrowDisappear {
transform: scale(0) translateX(-50%) rotate(45deg);
transition: transform 50ms ease-in;
transition: transform 50ms ease-in, left ease-out 200ms;
}
.dropdown::before {
@@ -48,7 +48,7 @@
.dropdown .dropdownContent {
width: max-content;
height: max-content;
padding: 1.25rem;
padding: 1rem;
}
.dropdownContent .link {
@@ -57,7 +57,7 @@
flex-flow: row nowrap;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1rem;
padding-right: 5rem;
border-radius: 6px;
max-width: 90vw;
@@ -113,3 +113,27 @@
.dropdownContent .link:hover .arrow {
display: block;
}
@media screen and (max-width: 840px) {
.dropdown {
position: fixed;
bottom: 0 !important;
left: 5% !important;
width: 90% !important;
z-index: -1;
}
.dropdownArrow {
display: none;
}
.dropdown .dropdownContent {
width: 100%;
}
}
@media screen and (max-width: 630px) {
.dropdown .dropdownContent {
padding: 0;
}
}

View File

@@ -22,7 +22,7 @@ import HeaderDropdown3 from './HeaderDropdowns/HeaderDropdown3';
import HeaderDropdown4 from './HeaderDropdowns/HeaderDropdown4';
export default function Header({ locale }) {
const [open, setOpen] = useState(0);
const [open, setOpen] = useState({ e: 0, c: false });
const headerInnerSection = useRef(null);
@@ -35,12 +35,14 @@ export default function Header({ locale }) {
return (
<button
ref={refForEl}
onMouseEnter={() => setOpen(n)}
onPointerEnter={() => setOpen(n)}
onMouseLeave={() => setOpen(0)}
onPointerLeave={() => setOpen(0)}
onMouseEnter={() => setOpen({ e: n, c: open.c })}
onPointerEnter={() => setOpen({ e: n, c: open.c })}
onMouseLeave={() => (open.c ? null : setOpen({ e: 0, c: false }))}
onPointerLeave={() => (open.c ? null : setOpen({ e: 0, c: false }))}
onMouseDown={() => setOpen({ e: n, c: true })}
onPointerDown={() => setOpen({ e: n, c: true })}
style={{
color: open === n && 'var(--text-shade-3)',
color: open.e === n && 'var(--text-shade-3)',
}}
>
{children}
@@ -74,7 +76,7 @@ export default function Header({ locale }) {
</li>
</ul>
<ul>
<li>
<li className={styles.progress}>
<Link href="/progress">
<button>{locale.nav.progress}</button>
</Link>

View File

@@ -28,6 +28,7 @@
width: 100%;
justify-content: space-between;
align-items: center;
gap: 2rem;
}
.nav {
@@ -52,11 +53,13 @@
gap: 0;
}
.nav .dropdownTriggers li:first-of-type button {
.nav .dropdownTriggers li:first-of-type button,
.rightIconsWrapper button:first-of-type {
padding-right: 1rem;
}
.nav .dropdownTriggers li:last-of-type button {
.nav .dropdownTriggers li:last-of-type button,
.rightIconsWrapper button:last-of-type {
padding-left: 1rem;
}
@@ -97,5 +100,53 @@
display: flex;
flex-flow: row nowrap;
align-items: center;
gap: 1rem;
}
@media screen and (max-width: 840px) {
.progress {
display: none;
}
}
@media screen and (max-width: 630px) {
.nav li.upgrade button {
padding: 6px;
}
.nav li.upgrade span {
display: none;
}
.logo {
margin-right: -104px;
}
.logo text {
display: none;
}
.nav {
width: 100%;
}
.dropdownTriggers {
width: 100%;
}
.navWrapper {
gap: 1rem;
}
.nav,
.nav ul {
gap: 1rem;
}
.nav .dropdownTriggers li:first-of-type button,
.rightIconsWrapper button:first-of-type {
padding-right: 0.5rem;
}
.nav .dropdownTriggers li:last-of-type button,
.rightIconsWrapper button:last-of-type {
padding-left: 0.5rem;
}
}

View File

@@ -4,7 +4,7 @@
flex-flow: row nowrap;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1rem;
padding-right: 5rem;
border-radius: 6px;
max-width: 90vw;
@@ -25,7 +25,7 @@
margin: 0;
margin-top: 4px;
font-weight: 300;
color: var(--text-shade-3);
color: var(--text-shade-1);
}
.dropdownContent .link .arrow {
@@ -40,6 +40,10 @@
animation: appear 0.75s;
}
.link.progress {
display: none;
}
@keyframes appear {
0% {
opacity: 0;
@@ -54,9 +58,27 @@
}
.dropdownContent .link:hover .iconWrapper {
color: var(--accent-shade-1);
color: var(--accent-shade-3);
}
.dropdownContent .link:hover .arrow {
display: block;
}
@media screen and (max-width: 840px) {
.link.progress {
display: flex;
}
}
@media screen and (max-width: 630px) {
.dropdownContent .link:hover .arrow {
display: none;
}
}
@media screen and (max-width: 360px) {
.dropdownContent .link .iconWrapper {
padding: 6px;
}
}

View File

@@ -1,4 +1,4 @@
import { ArrowRight, Users, Info, SealQuestion, NewspaperClipping } from '@phosphor-icons/react';
import { ArrowRight, Users, Info, SealQuestion, NewspaperClipping, EggCrack } from '@phosphor-icons/react';
import Link from 'next/link';
import Title from '@/components/Title/Title';
@@ -51,6 +51,17 @@ export default function HeaderDropdown1({ locale }) {
</div>
<ArrowRight size={28} className={styles.arrow} />
</Link>
<Link className={`${styles.link} ${styles.progress}`} href="/progress">
<div className={styles.iconWrapper}>
<EggCrack weight="fill" size={32} />
</div>
<div>
<Title element="h4">{locale.nav.progress}</Title>
<p>{locale.nav.dropdown.captions.progress}</p>
</div>
<ArrowRight size={28} className={styles.arrow} />
</Link>
</div>
);
}

View File

@@ -2,6 +2,7 @@
font-weight: 700;
margin: 0;
color: var(--text-shade-3);
scroll-margin-top: 90px;
}
.center {