mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-26 18:47:47 -05:00
feat: improve state management, fix bug...
...where the header buttons would just rerender on every mouse *move*, which, yanno, is not great.
This commit is contained in:
@@ -9,10 +9,13 @@ import styles from './Dropdown.module.css';
|
||||
*
|
||||
* A reusable object that renders a dropdown.
|
||||
*
|
||||
* @param {React.useState} openState: a state object [state, setState],
|
||||
* 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 {React.useState} openState: a state object [state, setState] storing 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)
|
||||
*
|
||||
*
|
||||
* @param {React.useState} clickedState: a state object [state, setState] storing a
|
||||
* boolean indicating whether the element was clicked on (true) or hovered on (false)
|
||||
*
|
||||
* @param {Array} dropdownContents: an array of objects, structured as
|
||||
* follows:
|
||||
@@ -33,6 +36,7 @@ import styles from './Dropdown.module.css';
|
||||
* { el: <El2 />, tr: tr2 },
|
||||
* ]}
|
||||
* openState={[open, setOpen]}
|
||||
* clickedState={[clicked, setClicked]}
|
||||
* boundaryRef={navbarRef}
|
||||
* boundaryTolerance={20}
|
||||
* />
|
||||
@@ -40,6 +44,7 @@ import styles from './Dropdown.module.css';
|
||||
|
||||
export default function Dropdown(ctx) {
|
||||
const [open, setOpen] = ctx.openState;
|
||||
const [clicked, setClicked] = ctx.clickedState;
|
||||
const { boundaryRef, boundaryTolerance } = ctx;
|
||||
|
||||
const dropdownElements = useMemo(() => {
|
||||
@@ -66,7 +71,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.e) {
|
||||
if (dropdownContentRect && open) {
|
||||
const sectionWidth = pxToInt(getComputedStyle(boundaryRef.current).width);
|
||||
const dropdownWidth = dropdownRect.width;
|
||||
|
||||
@@ -93,8 +98,8 @@ export default function Dropdown(ctx) {
|
||||
return getTriggerOffset(trRef) + getRealCenter(trRef);
|
||||
}
|
||||
|
||||
const arrowOffset = getLeftOffset(dropdownTriggers[open.e]);
|
||||
const leftOffset = getLeftOffset(dropdownTriggers[open.e]) - dropdownWidth / 2;
|
||||
const arrowOffset = getLeftOffset(dropdownTriggers[open]);
|
||||
const leftOffset = getLeftOffset(dropdownTriggers[open]) - dropdownWidth / 2;
|
||||
|
||||
const leftBoundary = 0 - boundaryTolerance;
|
||||
const rightBoundary = sectionWidth - dropdownWidth + boundaryTolerance;
|
||||
@@ -115,7 +120,8 @@ export default function Dropdown(ctx) {
|
||||
|
||||
const closeOnClickOutside = (e) => {
|
||||
if (dropdown.current && !dropdown.current.contains(e.target)) {
|
||||
setOpen({ e: 0, c: false });
|
||||
setOpen(0);
|
||||
setClicked(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,33 +129,37 @@ export default function Dropdown(ctx) {
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', closeOnClickOutside);
|
||||
};
|
||||
}, [open, setOpen, dropdownTriggers, boundaryTolerance, boundaryRef]);
|
||||
}, [open, setOpen, clicked, setClicked, dropdownTriggers, boundaryTolerance, boundaryRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.dropdown}
|
||||
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 });
|
||||
onPointerEnter={() => {
|
||||
setOpen(open);
|
||||
}}
|
||||
onPointerLeave={() => {
|
||||
!clicked && setOpen(0);
|
||||
}}
|
||||
onPointerDown={() => {
|
||||
setTimeout(() => {
|
||||
setOpen(0);
|
||||
setClicked(false);
|
||||
}, 200);
|
||||
}}
|
||||
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.e ? '' : 'none',
|
||||
border: open ? '' : 'none',
|
||||
}}
|
||||
>
|
||||
<div ref={dropdownContent} className={styles.dropdownContent}>
|
||||
{dropdownElements[open.e]}
|
||||
{dropdownElements[open]}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles.dropdownArrow, {
|
||||
[styles.arrowAppear]: Boolean(open.e),
|
||||
[styles.arrowDisappear]: !Boolean(open.e),
|
||||
[styles.arrowAppear]: Boolean(open),
|
||||
[styles.arrowDisappear]: !Boolean(open),
|
||||
})}
|
||||
ref={dropdownArrow}
|
||||
/>
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 1.4rem;
|
||||
top: -1.4rem;
|
||||
height: 1.6rem;
|
||||
top: -1.6rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import HeaderDropdown3 from './HeaderDropdowns/HeaderDropdown3';
|
||||
import HeaderDropdown4 from './HeaderDropdowns/HeaderDropdown4';
|
||||
|
||||
export default function Header({ locale }) {
|
||||
const [open, setOpen] = useState({ e: 0, c: false });
|
||||
const [open, setOpen] = useState(0);
|
||||
const [clicked, setClicked] = useState(false);
|
||||
|
||||
const headerInnerSection = useRef(null);
|
||||
|
||||
@@ -35,14 +36,18 @@ export default function Header({ locale }) {
|
||||
return (
|
||||
<button
|
||||
ref={refForEl}
|
||||
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 })}
|
||||
onPointerEnter={() => {
|
||||
setOpen(n);
|
||||
}}
|
||||
onPointerLeave={() => {
|
||||
!clicked && setOpen(0);
|
||||
}}
|
||||
onPointerDown={() => {
|
||||
setOpen(n);
|
||||
setClicked(true);
|
||||
}}
|
||||
style={{
|
||||
color: open.e === n && 'var(--text-shade-3)',
|
||||
color: open === n && 'var(--text-shade-3)',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -109,6 +114,7 @@ export default function Header({ locale }) {
|
||||
{ el: <HeaderDropdown4 key={4} locale={locale} />, tr: trigger4 },
|
||||
]}
|
||||
openState={[open, setOpen]}
|
||||
clickedState={[clicked, setClicked]}
|
||||
boundaryRef={headerInnerSection}
|
||||
boundaryTolerance={24}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user