Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions src/components/Sidenav/Sidenav.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@
}

/* Links */
.linkButton {
flex-shrink: 0;
cursor: pointer;
}

.link {
display: flex;
justify-content: flex-start;
Expand Down Expand Up @@ -136,8 +131,24 @@
}

.linkIcon {
flex-shrink: 0;
height: fit-content;
position: relative;
display: inline-block;
flex: 0 0 12px;
width: 12px;
height: 12px;
overflow: hidden;
font-size: 0;
line-height: 0;
color: currentcolor;
}

.linkIconSvg {
position: absolute;
inset: 0;
display: block;
width: 12px;
height: 12px;
overflow: hidden;
}

/* TODO: Maybe remove sections? */
Expand Down
31 changes: 22 additions & 9 deletions src/components/Sidenav/Sidenav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ const Item = ({ item }: ItemProps) => {
const pathname = usePathname();
const { setNavigationState } = useNavigation();
const isCurrentPageActive = item.path === pathname && !item.children;
const isExpandable = Boolean(item.children);
const isHashAnchorParent = Boolean(item.children && item.path?.includes('#'));
const shouldBeExpanded = isExpandedByDefault(item, pathname);
const [isExpanded, setIsExpanded] = useState(shouldBeExpanded);

Expand All @@ -99,6 +101,13 @@ const Item = ({ item }: ItemProps) => {
function toggleExpand(event: React.MouseEvent<HTMLAnchorElement>) {
const isMobileViewport = window.matchMedia('(max-width: 767px)').matches;

// Hash-anchor parents should only expand/collapse in the sidenav.
if (isHashAnchorParent) {
event.preventDefault();
setIsExpanded((expanded) => !expanded);
return;
}

// Mobile will always link to the clicked item
if (isCurrentPageActive && isMobileViewport) {
event.preventDefault();
Expand All @@ -109,7 +118,9 @@ const Item = ({ item }: ItemProps) => {
setNavigationState('closed');
}

setIsExpanded((isExpanded) => !isExpanded);
if (isExpandable) {
setIsExpanded((expanded) => !expanded);
}
}

return (
Expand All @@ -120,14 +131,16 @@ const Item = ({ item }: ItemProps) => {
href={item.path}
onClick={toggleExpand}>
{item.children ? (
<ChevronSmallIcon
chevronDirection={isExpanded ? 'down' : 'right'}
as="svg"
size="small"
title={isExpanded ? 'Expand icon' : 'Collapse icon'}
className={styles.linkIcon}
transition={openCloseTransition}
/>
<span className={styles.linkIcon} aria-hidden="true">
<ChevronSmallIcon
chevronDirection={isExpanded ? 'down' : 'right'}
as="svg"
size="small"
title=""
className={styles.linkIconSvg}
disableAnimation
/>
</span>
) : (
<div className={styles.emptyIcon} />
)}
Expand Down
52 changes: 33 additions & 19 deletions src/icons/svgs/ChevronSmall.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,53 @@
import { Transition } from 'motion/react';
import * as motion from 'motion/react-client';

import { createIcon, SpecificIconProps } from '../util/create-icon';

export const ChevronSmallIcon = createIcon<ChevronSmallIconProps>(
'chevronSmall',
({ chevronDirection = 'up', transition = { duration: 1 }, ...props }) => ({
...props,
children: (
<motion.path
fillRule="evenodd"
clipRule="evenodd"
d="M2.68994 4.70024L3.22028 4.16992L6.30219 7.25196H7.29208L10.2765 4.17404L10.815 4.69613L7.60954 8.00196H5.99152L2.68994 4.70024Z"
fill="var(--svg-path-fill)"
animate={{ rotate: getRotationDeg[chevronDirection] }}
transition={transition}
initial={false}
/>
),
}),
({ chevronDirection = 'up', disableAnimation = false, transition = { duration: 1 }, ...props }) => {
const rotationDeg = getRotationDeg[chevronDirection];

return {
...props,
children: disableAnimation ? (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.68994 4.70024L3.22028 4.16992L6.30219 7.25196H7.29208L10.2765 4.17404L10.815 4.69613L7.60954 8.00196H5.99152L2.68994 4.70024Z"
fill="var(--svg-path-fill)"
transform={rotationDeg === 0 ? undefined : `rotate(${rotationDeg} 6 6)`}
/>
) : (
<motion.path
fillRule="evenodd"
clipRule="evenodd"
d="M2.68994 4.70024L3.22028 4.16992L6.30219 7.25196H7.29208L10.2765 4.17404L10.815 4.69613L7.60954 8.00196H5.99152L2.68994 4.70024Z"
fill="var(--svg-path-fill)"
animate={{ rotate: `${rotationDeg}deg` }}
transition={transition}
initial={false}
/>
),
};
},
);

const getRotationDeg: Rotate = {
up: '-180deg',
right: '-90deg',
down: '0deg',
left: '90deg',
up: -180,
right: -90,
down: 0,
left: 90,
};

interface ChevronSmallIconProps extends SpecificIconProps {
chevronDirection?: Direction;
disableAnimation?: boolean;
transition?: Transition;
}

type Direction = 'up' | 'right' | 'down' | 'left';

type Rotate = {
[key in Direction]: `${number}deg`;
[key in Direction]: number;
};