Skip to content

Commit

Permalink
Add menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Bluesmile82 committed Oct 7, 2024
1 parent c53c9c5 commit e190497
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 15 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"test:report": "pnpm exec playwright show-report"
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"@react-three/drei": "^9.111.4",
"@react-three/fiber": "^8.17.6",
"@tanstack/react-query": "^5.50.1",
"class-variance-authority": "^0.7.0",
"clsx": "2.1.1",
Expand All @@ -26,8 +29,6 @@
"recoil": "^0.7.7",
"tailwind-merge": "2.4.0",
"tailwindcss-animate": "1.0.7",
"@react-three/drei": "^9.111.4",
"@react-three/fiber": "^8.17.6",
"three": "^0.168.0"
},
"devDependencies": {
Expand Down
34 changes: 34 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions public/icons/close-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions public/icons/instagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/icons/linkedin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions public/logo-green.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Roboto } from "next/font/google"
import { cn } from "@/lib/utils"
import "./globals.css";
import Header from "@/components/header";
import Menu from "@/components/menu";

const fontSans = Roboto({
subsets: ["latin"],
Expand All @@ -29,6 +30,7 @@ export default function RootLayout({
)}>
<ContextWrapper>
<Header />
<Menu />
{children}
</ContextWrapper>
</body>
Expand Down
20 changes: 15 additions & 5 deletions src/components/animations/hover-repeat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { PropsWithChildren } from "react";
import { EasingDefinition, Variants, motion } from "framer-motion";
import { cn } from "@/lib/utils";

const ANIMATION_EASE: EasingDefinition = "linear";
const ANIMATION_DURATION = 0.15;
Expand All @@ -28,13 +29,22 @@ const duplicatedContentAnimation: Variants = {
},
};

const HoverRepeatAnimation = ({ children }: PropsWithChildren) => {
interface HoverRepeatAnimationProps {
className?: string;
isChild?: boolean;
}

const HoverRepeatAnimation = ({ children, className, isChild }: PropsWithChildren & HoverRepeatAnimationProps) => {
const parentProps = isChild ? {} : {
initial: "rest",
whileHover: "hover",
animate: "rest",
};

return (
<motion.div
initial="rest"
whileHover="hover"
animate="rest"
className="relative overflow-hidden"
{...parentProps}
className={cn("relative overflow-hidden", className)}
>
<motion.div className="absolute" aria-hidden variants={duplicatedContentAnimation}>
{children}
Expand Down
74 changes: 74 additions & 0 deletions src/components/button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { motion, MotionProps } from "framer-motion";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap justify-center items-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "border-2 border-white gap-2.5",
"light-green": "border-2 border-light-green gap-2.5",
green: "border-2 border-green-700 gap-2.5",
// white: "bg-white text-black",
// outline: "border border-black",
// "outline-white": "border border-white",
icon: "gap-2.5",
// transparent: "bg-transparent",
// destructive: "bg-red-500 text-neutral-50 hover:bg-red-500/90 ",
// outline: "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 ",
// secondary: "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80",
// ghost: "hover:bg-neutral-100 hover:text-neutral-900",
// link: "text-black",
},
size: {
default: "h-10 px-4 py-3",
// sm: "px-6 py-1.5",
// md: "w-[102px] h-10 px-4 py-3",
// lg: "px-11 py-2.5",
auto: "",
// lg: "h-11 rounded-md px-8",
// icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
},
);

Button.displayName = "Button";

type MotionButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & ButtonProps & MotionProps;

const MotionButton = React.forwardRef<HTMLButtonElement, MotionButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
return (
<motion.button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
}
);

MotionButton.displayName = "MotionButton";


export { Button, buttonVariants, MotionButton };
20 changes: 16 additions & 4 deletions src/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
'use client';

import HoverRepeatAnimation from "@/components/animations/hover-repeat";
import { useRecoilState } from "recoil";
import { menuAtom } from "@/store";
import { MotionButton } from "@/components/button";

export default function Header() {
const [, setOpenedMenu] = useRecoilState(menuAtom);

const MenuButton = () => (
<button className="h-10 px-4 py-3 border-2 border-light-green justify-center items-center gap-2.5 inline-flex">
<HoverRepeatAnimation>
<MotionButton onClick={() => setOpenedMenu(true)}
variant="light-green"
initial="rest"
whileHover="hover"
animate="rest"
>
<HoverRepeatAnimation isChild>
<div className="text-light-green text-sm font-medium uppercase">MENU</div>
</HoverRepeatAnimation>
<img alt="Menu button" className="w-4 h-4" src="/icons/menu.svg" />
</button>
</MotionButton >
);

return (
<div className="fixed inset-0 w-full h-[90px] py-6 bg-blue-800 flex">
<div className="fixed inset-0 w-full h-[90px] py-6 bg-blue-900 flex">
<div className="container flex justify-between items-center">
<img alt="Logo" className="w-60 h-10" src="/logo.svg" />
<MenuButton />
Expand Down
73 changes: 73 additions & 0 deletions src/components/menu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use client';

import HoverRepeatAnimation from "@/components/animations/hover-repeat";
import { useRecoilState } from "recoil";
import { menuAtom } from "@/store";
import Link from "next/link";
import { MotionButton } from "@/components/button";
import { motion } from "framer-motion";

export default function Menu() {
const [openedMenu, setOpenedMenu] = useRecoilState(menuAtom);

const CloseMenuButton = () => (
<MotionButton
className="self-end"
onClick={() => setOpenedMenu(false)}
variant="green"
initial="rest"
whileHover="hover"
animate="rest"
>
<HoverRepeatAnimation isChild>
<div className="text-green-700 text-sm font-medium uppercase">MENU</div>
</HoverRepeatAnimation>
<img alt="Menu button" className="w-[10px] h-[10px]" src="/icons/close-small.svg" />
</MotionButton>
);

return (
<motion.div
className="fixed top-0 w-[470px] right-0 h-full bg-light-green flex flex-col p-10 justify-between"
initial={{ x: "100%" }}
animate={{ x: openedMenu ? 0 : "100%" }}
transition={{ type: "linear", duration: 0.3 }}
>
<div className="flex flex-col gap-[72px]">
<CloseMenuButton />
<ul className="text-3xl text-green-700 font-semibold space-y-8 max-w-[292px]">
<li>
<Link href="/">
<HoverRepeatAnimation>Home</HoverRepeatAnimation></Link>
</li>
<li>
<Link href="/"><HoverRepeatAnimation className="pb-1">Case Studies: Energy</HoverRepeatAnimation></Link>
</li>
<li>
<Link href="/"><HoverRepeatAnimation>About Us</HoverRepeatAnimation></Link>
</li>
<li>
<a href="mailto:#"><HoverRepeatAnimation>Contact</HoverRepeatAnimation></a>
</li>
</ul>
</div>
<div className="flex justify-between items-end">
<img alt="Logo" className="w-60 h-10" src="/logo-green.svg" />
<ul className="flex gap-[15px]">
<li>
<a href="/">
<img alt="Instagram" className="w-6 h-6" src="/icons/instagram.svg" />
<div className="sr-only">Instagram</div>
</a>
</li>
<li>
<a href="/">
<div className="sr-only">Linkedin</div>
<img alt="Linkedin" className="w-6 h-6" src="/icons/linkedin.svg" />
</a>
</li>
</ul>
</div>
</motion.div>
);
};
6 changes: 6 additions & 0 deletions src/store/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { atom } from 'recoil';

export const menuAtom = atom({
key: 'openedMenu',
default: false,
});

0 comments on commit e190497

Please sign in to comment.