mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-27 02:56:47 -05:00
feat: mobile dropdown, minor changes
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--text-shade-3);
|
||||
scroll-margin-top: 90px;
|
||||
}
|
||||
|
||||
.center {
|
||||
|
||||
Reference in New Issue
Block a user