diff --git a/components/Menu/Menu.tsx b/components/Menu/Menu.tsx
index d7f1f90..2c3296a 100644
--- a/components/Menu/Menu.tsx
+++ b/components/Menu/Menu.tsx
@@ -4,6 +4,7 @@ import MenuItem from './MenuItem';
import MenuToggle from './MenuToggle';
import MenuList from './MenuList';
import {MenuContext} from './MenuContext';
+import {MenuPosition} from './MenuPosition';
/**
* Props.
@@ -12,17 +13,19 @@ type MenuProps = {
/** @default false */
initialOpen?: boolean;
children?: ReactNode;
-};
+} & MenuPosition;
/**
* @param {MenuProps} props Props.
* @returns React component.
*/
-export default function Menu({initialOpen = false, children}: MenuProps) {
+export default function Menu({initialOpen = false, children, ...position}: MenuProps) {
const [isOpen, setIsOpen] = useState(initialOpen);
useDisableBodyScroll(isOpen);
- return {children};
+ return (
+ {children}
+ );
}
Menu.Item = MenuItem;
diff --git a/components/Menu/MenuContext.ts b/components/Menu/MenuContext.ts
index 144964a..41ab8bc 100644
--- a/components/Menu/MenuContext.ts
+++ b/components/Menu/MenuContext.ts
@@ -1,4 +1,5 @@
import {createContext} from 'react';
+import {MenuPosition} from './MenuPosition';
/**
* Context type.
@@ -6,6 +7,7 @@ import {createContext} from 'react';
type MenuConextType = {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
+ position: MenuPosition;
};
/**
diff --git a/components/Menu/MenuItem.tsx b/components/Menu/MenuItem.tsx
index 5b43799..de86c59 100644
--- a/components/Menu/MenuItem.tsx
+++ b/components/Menu/MenuItem.tsx
@@ -29,7 +29,7 @@ const variants = {
},
};
-const itemCn = clsx('text-4xl', 'm-2', 'text-center');
+const itemCn = clsx('text-5xl', 'm-2', 'text-center');
/**
* @param {MenuItemProps} props Props.
diff --git a/components/Menu/MenuList.tsx b/components/Menu/MenuList.tsx
index 83bacd0..ed2e059 100644
--- a/components/Menu/MenuList.tsx
+++ b/components/Menu/MenuList.tsx
@@ -5,6 +5,8 @@ import clsx from 'clsx';
import useScroll from '@/lib/shared/useScroll';
import useWindowDimensions from '@/lib/shared/useWindowDimensions';
import {MenuContext} from './MenuContext';
+import {MenuPosition} from './MenuPosition';
+import menuButtonSize from './menuButtonSize';
/**
* Props.
@@ -13,25 +15,53 @@ type MenuListProps = {
children: ReactNode;
};
+// eslint-disable-next-line jsdoc/require-jsdoc
+function getClipPath({height, position}: {height?: number; position: MenuPosition}) {
+ let coordinates = '';
+
+ const {top, bottom, left, right} = position;
+
+ if (left !== undefined) {
+ coordinates += `${left + menuButtonSize / 2}px`;
+ }
+
+ if (right !== undefined) {
+ coordinates += `calc(100% - ${right + menuButtonSize / 2}px)`;
+ }
+
+ if (top !== undefined) {
+ coordinates += ' ' + `${top + menuButtonSize / 2}px`;
+ }
+
+ if (bottom !== undefined) {
+ coordinates += ' ' + `calc(100% - ${bottom + menuButtonSize / 2}px)`;
+ }
+
+ const size = height ? `${height * 2 + 200}px` : '0px';
+
+ return `circle(${size} at ${coordinates}`;
+}
+
const navVariants = {
// eslint-disable-next-line jsdoc/require-jsdoc
- open: (height: number) => ({
- clipPath: `circle(${height * 2 + 200}px at 95% 5%)`,
+ open: ({height, position}: {height: number; position: MenuPosition}) => ({
+ clipPath: getClipPath({height, position}),
transition: {
type: 'spring',
stiffness: 20,
restDelta: 2,
},
}),
- closed: {
- clipPath: 'circle(30px at 95% 5%)',
+ // eslint-disable-next-line jsdoc/require-jsdoc
+ closed: ({position}: {height: number; position: MenuPosition}) => ({
+ clipPath: getClipPath({position}),
transition: {
delay: 0.5,
type: 'spring',
stiffness: 400,
damping: 40,
},
- },
+ }),
};
const navCn = clsx(
@@ -41,7 +71,7 @@ const navCn = clsx(
'w-full',
'h-full',
'z-10',
- 'bg-alternate',
+ 'bg-gradient-to-bl from-accent0 to-alternate',
'flex',
'flex-col',
'items-center',
@@ -53,7 +83,7 @@ const navCn = clsx(
* @returns React component.
*/
export default function MenuList({children}: MenuListProps) {
- const {isOpen, setIsOpen} = useContext(MenuContext);
+ const {isOpen, setIsOpen, position} = useContext(MenuContext);
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
useHotkeys('esc', close);
useScroll(close);
@@ -71,7 +101,7 @@ export default function MenuList({children}: MenuListProps) {
>
+>;
diff --git a/components/Menu/MenuToggle.tsx b/components/Menu/MenuToggle.tsx
index 1fecb58..c1bba75 100644
--- a/components/Menu/MenuToggle.tsx
+++ b/components/Menu/MenuToggle.tsx
@@ -1,11 +1,12 @@
'use client';
-import {SVGMotionProps, Variants, m} from 'framer-motion';
+import {Variants, m} from 'framer-motion';
import {forwardRef, memo, useContext} from 'react';
import clsx from 'clsx';
-import {twMerge} from 'tailwind-merge';
import {MenuContext} from './MenuContext';
+import menuButtonSize from './menuButtonSize';
import Button from '../Button/Button';
+import MenuToggleSvgContent from './MenuToggleSvgContent';
/**
* Props.
@@ -20,29 +21,18 @@ const menuVariants: Variants = {
hidden: {opacity: 0},
};
-const containerCn = clsx('absolute', 'top-16', 'right-16', 'z-20');
-const btnCn = clsx('rounded-full w-24 h-24');
-
-// eslint-disable-next-line jsdoc/require-jsdoc
-const Path = ({variants}: SVGMotionProps) => (
-
-);
+const containerCn = clsx('absolute', 'z-20');
+const btnCn = clsx('rounded-full');
/**
* @param {MenuToggleProps} props Props.
* @returns React component.
*/
const MenuToggle = forwardRef(function MenuToggle(
- {onToggle, className},
+ {onToggle},
ref,
) {
- const {setIsOpen, isOpen} = useContext(MenuContext);
+ const {setIsOpen, isOpen, position} = useContext(MenuContext);
// eslint-disable-next-line jsdoc/require-jsdoc
const onClick = () => {
setIsOpen(!isOpen);
@@ -57,32 +47,17 @@ const MenuToggle = forwardRef(function MenuT
transition={{duration: 0.75, delay: 1}}
variants={menuVariants}
className={containerCn}
+ style={position}
animate={isOpen ? 'open' : 'closed'}
>
);
diff --git a/components/Menu/MenuToggleSvgContent.tsx b/components/Menu/MenuToggleSvgContent.tsx
new file mode 100644
index 0000000..cb8fda5
--- /dev/null
+++ b/components/Menu/MenuToggleSvgContent.tsx
@@ -0,0 +1,59 @@
+import {SVGMotionProps, m} from 'framer-motion';
+import clsx from 'clsx';
+import menuButtonSize from './menuButtonSize';
+
+const pathCn = clsx('fill-white', 'stroke-white');
+
+// eslint-disable-next-line jsdoc/require-jsdoc
+const Path = ({variants}: SVGMotionProps) => (
+
+);
+
+/**
+ * @returns React component.
+ */
+export default function MenuToggleSvgContent() {
+ const svgViewBoxSize = menuButtonSize / 2;
+ const svgElSize = svgViewBoxSize / 1.5;
+ const svgElOffset = (svgViewBoxSize - svgElSize) / 2;
+
+ return (
+
+ );
+}
diff --git a/components/Menu/menuButtonSize.ts b/components/Menu/menuButtonSize.ts
new file mode 100644
index 0000000..1aa902d
--- /dev/null
+++ b/components/Menu/menuButtonSize.ts
@@ -0,0 +1,3 @@
+const menuButtonSize = 80;
+
+export default menuButtonSize;
diff --git a/components/TopMenu/TopMenu.tsx b/components/TopMenu/TopMenu.tsx
index 66391d2..6aa1465 100644
--- a/components/TopMenu/TopMenu.tsx
+++ b/components/TopMenu/TopMenu.tsx
@@ -4,7 +4,7 @@ import {m} from 'framer-motion';
import {menuItemsConfig, socialLinksConfig} from './topMenuCongif';
import Menu from '../Menu/Menu';
-const itemsVariants = {
+const variants = {
open: {
transition: {staggerChildren: 0.07, delayChildren: 0.2},
},
@@ -13,28 +13,35 @@ const itemsVariants = {
},
};
-const socialLinksVariants = itemsVariants;
-
const linkCn = clsx('animated-link');
-
const socialLiksListCn = clsx('flex', 'gap-8', 'mt-auto', 'mb-16');
-
const socialLinkMenuItem = clsx('text-xs');
-
-const itemsListCn = clsx('mt-48');
+const itemsListCn = clsx('mt-24');
+const homeMenuItem = clsx('mb-32', 'text-xl');
/**
* @returns React component.
*/
export default function TopMenu() {
return (
-