From e190497187b513bd543332fc8a667f5d89cb3fae Mon Sep 17 00:00:00 2001 From: Alvaro Date: Mon, 7 Oct 2024 14:00:54 +0200 Subject: [PATCH] Add menu --- package.json | 5 +- pnpm-lock.yaml | 34 ++++++++++ public/icons/close-small.svg | 3 + public/icons/instagram.svg | 6 +- public/icons/linkedin.svg | 2 +- public/logo-green.svg | 13 ++++ src/app/layout.tsx | 2 + src/components/animations/hover-repeat.tsx | 20 ++++-- src/components/button/index.tsx | 74 ++++++++++++++++++++++ src/components/header/index.tsx | 20 ++++-- src/components/menu/index.tsx | 73 +++++++++++++++++++++ src/store/index.tsx | 6 ++ 12 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 public/icons/close-small.svg create mode 100644 public/logo-green.svg create mode 100644 src/components/button/index.tsx create mode 100644 src/components/menu/index.tsx create mode 100644 src/store/index.tsx diff --git a/package.json b/package.json index c91db91..fe05008 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd8ea93..8fe8b22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.3)(react@18.3.1) '@react-three/drei': specifier: ^9.111.4 version: 9.114.0(@react-three/fiber@8.17.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.168.0))(@types/react@18.3.3)(@types/three@0.163.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.168.0) @@ -240,6 +243,24 @@ packages: engines: {node: '>=18'} hasBin: true + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@react-spring/animated@9.6.1': resolution: {integrity: sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==} peerDependencies: @@ -2125,6 +2146,19 @@ snapshots: dependencies: playwright: 1.45.1 + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + '@react-spring/animated@9.6.1(react@18.3.1)': dependencies: '@react-spring/shared': 9.6.1(react@18.3.1) diff --git a/public/icons/close-small.svg b/public/icons/close-small.svg new file mode 100644 index 0000000..a369a70 --- /dev/null +++ b/public/icons/close-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/instagram.svg b/public/icons/instagram.svg index 2b817cf..6b7253a 100644 --- a/public/icons/instagram.svg +++ b/public/icons/instagram.svg @@ -1,8 +1,8 @@ - - - + + + diff --git a/public/icons/linkedin.svg b/public/icons/linkedin.svg index 7f7168d..4165159 100644 --- a/public/icons/linkedin.svg +++ b/public/icons/linkedin.svg @@ -1,6 +1,6 @@ - + diff --git a/public/logo-green.svg b/public/logo-green.svg new file mode 100644 index 0000000..493d275 --- /dev/null +++ b/public/logo-green.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index edd016e..2a42b8d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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"], @@ -29,6 +30,7 @@ export default function RootLayout({ )}>
+
{children} diff --git a/src/components/animations/hover-repeat.tsx b/src/components/animations/hover-repeat.tsx index 7d7e759..96872db 100644 --- a/src/components/animations/hover-repeat.tsx +++ b/src/components/animations/hover-repeat.tsx @@ -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; @@ -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 ( {children} diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx new file mode 100644 index 0000000..d512695 --- /dev/null +++ b/src/components/button/index.tsx @@ -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, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); + +Button.displayName = "Button"; + +type MotionButtonProps = React.ButtonHTMLAttributes & ButtonProps & MotionProps; + +const MotionButton = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + return ( + + ); + } +); + +MotionButton.displayName = "MotionButton"; + + +export { Button, buttonVariants, MotionButton }; diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index f7bff1f..7d4099b 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -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 = () => ( - + ); return ( -
+
Logo diff --git a/src/components/menu/index.tsx b/src/components/menu/index.tsx new file mode 100644 index 0000000..1c5946d --- /dev/null +++ b/src/components/menu/index.tsx @@ -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 = () => ( + setOpenedMenu(false)} + variant="green" + initial="rest" + whileHover="hover" + animate="rest" + > + +
MENU
+
+ Menu button +
+ ); + + return ( + +
+ +
    +
  • + + Home +
  • +
  • + Case Studies: Energy +
  • +
  • + About Us +
  • +
  • + Contact +
  • +
+
+ +
+ ); +}; \ No newline at end of file diff --git a/src/store/index.tsx b/src/store/index.tsx new file mode 100644 index 0000000..a3cbac5 --- /dev/null +++ b/src/store/index.tsx @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const menuAtom = atom({ + key: 'openedMenu', + default: false, +}); \ No newline at end of file