diff --git a/package-lock.json b/package-lock.json index 1ba070a..f5377ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@apollo/experimental-nextjs-app-support": "^0.11.2", "@contentful/rich-text-plain-text-renderer": "^16.2.1", "@contentful/rich-text-react-renderer": "^15.22.1", + "@headlessui/react": "^2.1.1", "@next/third-parties": "^14.2.4", "@prisma/client": "^5.16.0", "@uidotdev/usehooks": "^2.4.1", @@ -1316,6 +1317,59 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.3.tgz", + "integrity": "sha512-1ZpCvYf788/ZXOhRQGFxnYQOVgeU+pi0i+d0Ow34La7qjIXETi6RNswGVKkA6KcDO8/+Ysu2E/CeUmmeEBDvTg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.6.tgz", + "integrity": "sha512-qiTYajAnh3P+38kECeffMSQgbvXty2VB6rS+42iWR4FPIlZjLK84E9qtLnMTLIpPz2znD/TaFqaiavMUrS+Hcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.3" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.18", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.18.tgz", + "integrity": "sha512-enDDX09Jpi3kmhcXXpvs+fvRXOfBj1jUV2KF6uDMf5HjS+SOZJzNTFUW71lKbFcxz0BkmQqwbvqdmHIxMq/fyQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.0", + "@floating-ui/utils": "^0.2.3", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.3.tgz", + "integrity": "sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww==", + "license": "MIT" + }, "node_modules/@graphql-codegen/add": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.3.tgz", @@ -2236,6 +2290,25 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/@headlessui/react": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.1.1.tgz", + "integrity": "sha512-808gVNUbRDbDR3GMNPHy+ON0uvR8b9H7IA+Q2UbhOsNCIjgwuwb2Iuv8VPT/1AW0UzLX8g10tN6LhF15GaUJCQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@tanstack/react-virtual": "3.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18", + "react-dom": "^18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2782,6 +2855,89 @@ "@prisma/debug": "5.16.0" } }, + "node_modules/@react-aria/focus": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.17.1.tgz", + "integrity": "sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.3.tgz", + "integrity": "sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", + "integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.24.1.tgz", + "integrity": "sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.4", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.1.tgz", + "integrity": "sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/shared": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.23.1.tgz", + "integrity": "sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, "node_modules/@repeaterjs/repeater": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz", @@ -2808,6 +2964,33 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.5.0.tgz", + "integrity": "sha512-rtvo7KwuIvqK9zb0VZ5IL7fiJAEnG+0EiFZz8FUOs+2mhGqdGmjKIaT1XU7Zq0eFqL0jonLlhbayJI/J2SA/Bw==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.5.0.tgz", + "integrity": "sha512-KnPRCkQTyqhanNC0K63GBG3wA8I+D1fQuVnAvcBF8f13akOKeQp1gSbu6f77zCxhEk727iV5oQnbHLYzHrECLg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -4096,6 +4279,15 @@ "node": ">=0.8" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8981,6 +9173,12 @@ "node": ">=0.10" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", diff --git a/package.json b/package.json index 76ae2df..c3c8007 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": false, "scripts": { "dev": "npm run prisma:generate && next dev --turbo", - "build": "npm run prisma:generate && npm run prisma:migrate:deploy && next build", + "build": "echo $POSTGRES_PRISMA_URL && npm run prisma:generate && next build", "start": "next start", "lint": "next lint", "lint:fix": "next lint --fix", @@ -24,6 +24,7 @@ "@apollo/experimental-nextjs-app-support": "^0.11.2", "@contentful/rich-text-plain-text-renderer": "^16.2.1", "@contentful/rich-text-react-renderer": "^15.22.1", + "@headlessui/react": "^2.1.1", "@next/third-parties": "^14.2.4", "@prisma/client": "^5.16.0", "@uidotdev/usehooks": "^2.4.1", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1d3ad52..f356160 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,7 +8,7 @@ import Script from "next/script"; import { Organization, WithContext } from "schema-dts"; import { Footer } from "@/components/Footer"; -import { Navbar } from "@/components/Navbar"; +import { Navbar } from "@/components/Navbar/Navbar"; import { PreviewBadge } from "@/components/PreviewBadge"; import { bodoni, manrope } from "@/utils/fonts"; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx deleted file mode 100644 index 5a63b11..0000000 --- a/src/components/Navbar.tsx +++ /dev/null @@ -1,91 +0,0 @@ -"use client"; - -import classNames from "classnames"; -import { motion, Variants } from "framer-motion"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; - -import { BeatriceCoxWrittenLogo } from "@/icons/BeatriceCoxWrittenLogo"; - -import { FadeFromLeft } from "./animated/FadeFromLeft"; - -const LINKS = [ - { label: "Projects", href: "/#projects" }, - { label: "About", href: "/about" }, - { label: "Services", href: "/services" }, - { label: "Shop", href: "https://society6.com/beatricecox", target: "_blank" }, -]; - -const linkVariants: Variants = { - offscreen: { - y: 20, - opacity: 0, - }, - onscreen: { - y: 0, - opacity: 1, - transition: { - opacity: { - type: "spring", - bounce: 0, - duration: 0.2, - }, - y: { - type: "spring", - bounce: 0.6, - duration: 2, - }, - }, - }, -}; - -export const Navbar: React.FC = () => { - const pathname = usePathname(); - - const hasBlackBackground = ["/about", "/services"].includes(pathname); - - return ( -
-
- - - Beatrice Duguid Cox Logo - - - -
- - - {LINKS.map(({ label, href, target }) => ( - - - {label} - - - ))} - -
- ); -}; diff --git a/src/components/Navbar/MobilePanel.tsx b/src/components/Navbar/MobilePanel.tsx new file mode 100644 index 0000000..69e9c9e --- /dev/null +++ b/src/components/Navbar/MobilePanel.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useEffect } from "react"; +import { DisclosureButton } from "@headlessui/react"; +import { useLockBodyScroll } from "@uidotdev/usehooks"; +import classNames from "classnames"; +import { motion, Variants } from "framer-motion"; + +import { DribbleIcon } from "@/icons/Dribble"; +import { FlickrIcon } from "@/icons/Flickr"; +import { InstagramIcon } from "@/icons/Instagram"; +import { LinkedInIcon } from "@/icons/LinkedIn"; +import { + DRIBBLE_LINK, + FLICKR_LINK, + INSTAGRAM_LINK, + LINKEDIN_LINK, +} from "@/utils/constants"; + +import { LINKS } from "./links"; + +const iconsVariants: Variants = { + offscreen: { + y: 20, + opacity: 0, + }, + onscreen: { + y: 0, + opacity: 1, + transition: { + y: { + type: "spring", + bounce: 0.8, + duration: 3, + }, + opacity: { + bounce: 0, + duration: 0.3, + }, + }, + }, +}; + +export const MobilePanel: React.FC = () => { + useLockBodyScroll(); + + return ( +
+
+ {LINKS.map(({ label, href, target }) => ( + + {label} + + ))} +
+ +
+ + + + + + + + + + + + + + + + + +
+
+ ); +}; diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx new file mode 100644 index 0000000..141ff29 --- /dev/null +++ b/src/components/Navbar/Navbar.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { + Disclosure, + DisclosureButton, + DisclosurePanel, +} from "@headlessui/react"; +import classNames from "classnames"; +import { motion, Variants } from "framer-motion"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { BarsIcon } from "@/icons/Bars"; +import { BeatriceCoxWrittenLogo } from "@/icons/BeatriceCoxWrittenLogo"; +import { XMarkIcon } from "@/icons/XMark"; + +import { FadeFromLeft } from "../animated/FadeFromLeft"; + +import { LINKS } from "./links"; +import { MobilePanel } from "./MobilePanel"; + +const linkVariants: Variants = { + offscreen: { + y: 20, + opacity: 0, + }, + onscreen: { + y: 0, + opacity: 1, + transition: { + opacity: { + type: "spring", + bounce: 0, + duration: 0.2, + }, + y: { + type: "spring", + bounce: 0.6, + duration: 2, + }, + }, + }, +}; + +export const Navbar: React.FC = () => { + const pathname = usePathname(); + + const hasBlackBackground = ["/about", "/services"].includes(pathname); + + return ( + + {({ open }: { open: boolean }) => ( + <> +
+
+ + + Beatrice Duguid Cox Logo + + + +
+ +
+ {/* Mobile menu button*/} + + + Open main menu + {open ? ( + +
+ + + {LINKS.map(({ label, href, target }) => ( + + + {label} + + + ))} + +
+ + + + + + )} +
+ ); +}; diff --git a/src/components/Navbar/links.ts b/src/components/Navbar/links.ts new file mode 100644 index 0000000..7afe903 --- /dev/null +++ b/src/components/Navbar/links.ts @@ -0,0 +1,6 @@ +export const LINKS = [ + { label: "Projects", href: "/#projects" }, + { label: "About", href: "/about" }, + { label: "Services", href: "/services" }, + { label: "Shop", href: "https://society6.com/beatricecox", target: "_blank" }, +]; diff --git a/src/icons/Bars.tsx b/src/icons/Bars.tsx new file mode 100644 index 0000000..c50a5f9 --- /dev/null +++ b/src/icons/Bars.tsx @@ -0,0 +1,21 @@ +interface BarsIconProps { + className?: string; + onClick?: VoidFunction; +} + +export const BarsIcon: React.FC> = ({ + className = "", + onClick, +}) => ( + + + + + +); diff --git a/src/icons/XMark.tsx b/src/icons/XMark.tsx new file mode 100644 index 0000000..2fc8919 --- /dev/null +++ b/src/icons/XMark.tsx @@ -0,0 +1,25 @@ +interface XMarkIconProps { + className?: string; + onClick?: VoidFunction; +} + +export const XMarkIcon: React.FC> = ({ + className = "", + onClick, +}) => ( + + + +);