diff --git a/components/header/header.css b/components/header/header.css new file mode 100644 index 0000000..fa33252 --- /dev/null +++ b/components/header/header.css @@ -0,0 +1,66 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --header-height: 8rem; + --header-height-scrolled: 4.5rem; +} + +@layer base { + header.scrolled { + height: var(--header-height-scrolled); + + @apply border-border; + @apply bg-background; + } + + .menuBtn { + --size: 1.5rem; + --duration: 250ms; + + position: relative; + transform: rotate(0deg); + transition: var(--duration) * 2 ease-in-out; + } + + .menuBtn > span { + display: block; + height: calc(var(--size) / 4); + left: 0; + opacity: 1; + position: absolute; + transform: rotate(0deg); + transition: var(--duration) ease-in-out; + width: var(--size); + + @apply bg-foreground; + } + + .menuBtn span:nth-child(1) { + top: 0; + } + + .menuBtn span:nth-child(2) { + top: calc(var(--size) / 4 + var(--size) / 8); + } + + .menuBtn span:nth-child(3) { + top: calc(var(--size) / 2 + var(--size) / 4); + } + + .menuBtn.opened span:nth-child(1) { + top: calc(var(--size) / 4 + var(--size) / 8); + transform: rotate(135deg); + } + + .menuBtn.opened span:nth-child(2) { + left: -60px; + opacity: 0; + } + + .menuBtn.opened span:nth-child(3) { + top: calc(var(--size) / 4 + var(--size) / 8); + transform: rotate(-135deg); + } +} diff --git a/components/header/index.tsx b/components/header/index.tsx new file mode 100644 index 0000000..2547522 --- /dev/null +++ b/components/header/index.tsx @@ -0,0 +1,20 @@ +import Logo from "./logo"; +import Nav from "./menu"; +import Sidebar from "./sidebar"; +import JumpToContent from "./util/jumpToContent"; +import HeaderWrapper from "./util/wrapper"; + +export default function Header() { + return ( + <> + + +
+ +
+ +
+ + ); +} diff --git a/components/header/logo.tsx b/components/header/logo.tsx new file mode 100644 index 0000000..0dc308b --- /dev/null +++ b/components/header/logo.tsx @@ -0,0 +1,28 @@ +"use client"; +import BaseLogo from "@/components/logo"; +import { useSidebarContext } from "@/context/sidebar"; +import { env } from "@/env/client"; +import { cn } from "@/lib/utils"; + +export default function Logo() { + const sidebar = useSidebarContext(); + return ( + <> + + + / + {env.NEXT_PUBLIC_SITE_NAME} + + + ); +} diff --git a/components/header/menu/index.tsx b/components/header/menu/index.tsx new file mode 100644 index 0000000..34ee9de --- /dev/null +++ b/components/header/menu/index.tsx @@ -0,0 +1,53 @@ +"use client"; +// import Link from "next/link"; +// import { usePathname } from "next/navigation"; + +import { + NavigationMenu, + //NavigationMenuItem, + //NavigationMenuLink, + NavigationMenuList, + //navigationMenuTriggerStyle, +} from "@/components/ui/navigation-menu"; +// import { cn } from "@/lib/utils"; + +export default function Nav() { + // const router = usePathname(); + return ( + <> + + + {/* + Item One + + Link + + */} + {/* + + + Archive + + + */} + {/* + + + What's Trendy + + + */} + + + + ); +} diff --git a/components/header/sidebar/index.tsx b/components/header/sidebar/index.tsx new file mode 100644 index 0000000..4a4c171 --- /dev/null +++ b/components/header/sidebar/index.tsx @@ -0,0 +1,21 @@ +import Nav from "./nav"; +import TopBar from "./topbar"; +import BackDrop from "./util/backdrop"; +import InnerWrapper from "./util/innerWrapper"; +import { MenuButton } from "./util/menuButton"; + +export default function Sidebar() { + return ( + <> +
+ + + {/* */} + + +
+ + ); +} diff --git a/components/header/sidebar/nav/index.tsx b/components/header/sidebar/nav/index.tsx new file mode 100644 index 0000000..402f53a --- /dev/null +++ b/components/header/sidebar/nav/index.tsx @@ -0,0 +1,34 @@ +"use client"; +import { FaBoxArchive, FaFileLines } from "react-icons/fa6"; + +import LogOut from "./logout"; +import Privacy from "./privacy"; +import SidebarItem from "./sidebarItem"; +export default function Nav() { + return ( + <> + + + ); +} diff --git a/components/header/sidebar/nav/logout.tsx b/components/header/sidebar/nav/logout.tsx new file mode 100644 index 0000000..fecd2eb --- /dev/null +++ b/components/header/sidebar/nav/logout.tsx @@ -0,0 +1,57 @@ +"use client"; +import { type KeyboardEventHandler, useState } from "react"; + +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +export default function LogOut() { + const [isLoggedIn] = useState(false); + const sidebar = useSidebarContext(); + const trapFocus: KeyboardEventHandler = (e) => { + if (e.code === "Tab" && sidebar.btnInSidebarRef.current) { + sidebar.isExpanded && sidebar.btnInSidebarRef.current.focus(); + } + }; + return ( + <> + {isLoggedIn && ( + <> + +
+ + + + +
+ Logga Ut +
+ + )} + + ); +} diff --git a/components/header/sidebar/nav/privacy.tsx b/components/header/sidebar/nav/privacy.tsx new file mode 100644 index 0000000..fd2fc8a --- /dev/null +++ b/components/header/sidebar/nav/privacy.tsx @@ -0,0 +1,30 @@ +"use client"; +import { type KeyboardEventHandler, useState } from "react"; +import { FaPassport } from "react-icons/fa6"; + +import { useSidebarContext } from "@/context/sidebar"; + +import SidebarItem from "./sidebarItem"; + +export default function Privacy() { + const sidebar = useSidebarContext(); + const [isLoggedIn] = useState(false); + const trapFocus: KeyboardEventHandler = (e) => { + if (e.code === "Tab" && !e.shiftKey && sidebar.mainMenuBtnRef.current) { + e.preventDefault(); + sidebar.isExpanded && sidebar.mainMenuBtnRef.current.focus(); + } + }; + return ( + <> + + + + + ); +} diff --git a/components/header/sidebar/nav/sidebarItem.tsx b/components/header/sidebar/nav/sidebarItem.tsx new file mode 100644 index 0000000..568fba6 --- /dev/null +++ b/components/header/sidebar/nav/sidebarItem.tsx @@ -0,0 +1,67 @@ +"use client"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import type { KeyboardEventHandler } from "react"; + +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +export default function SidebarItem({ + href, + classes = "", + text, + isLast = false, + onKeyDown, + children, +}: { + href: string; + classes?: string; + text: string; + isLast?: boolean; + onKeyDown?: KeyboardEventHandler | undefined; + children?: React.ReactNode; +}) { + const sidebar = useSidebarContext(); + const pathname = usePathname(); + let attr = {}; + attr = { + tabIndex: sidebar.isExpanded ? 0 : -1, + onClick: () => sidebar.toggle(), + }; + if (isLast) { + attr = { + ...attr, + onKeyDown, + }; + } + return ( + <> + +
+ {children} +
+ {text} + + + + ); +} diff --git a/components/header/sidebar/topbar/index.tsx b/components/header/sidebar/topbar/index.tsx new file mode 100644 index 0000000..5d154ee --- /dev/null +++ b/components/header/sidebar/topbar/index.tsx @@ -0,0 +1,21 @@ +import { cn } from "@/lib/utils"; + +import SearchButton from "./searchBtn"; + +export default function TopBar() { + return ( + <> +
+ + {/* */} +
+ + ); +} diff --git a/components/header/sidebar/topbar/searchBtn.tsx b/components/header/sidebar/topbar/searchBtn.tsx new file mode 100644 index 0000000..3323947 --- /dev/null +++ b/components/header/sidebar/topbar/searchBtn.tsx @@ -0,0 +1,33 @@ +export default function SearchButton() { + return ( + <> + {/* */} +
+ + ); +} diff --git a/components/header/sidebar/util/backdrop.tsx b/components/header/sidebar/util/backdrop.tsx new file mode 100644 index 0000000..54bd500 --- /dev/null +++ b/components/header/sidebar/util/backdrop.tsx @@ -0,0 +1,20 @@ +"use client"; +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +export default function BackDrop() { + const sidebar = useSidebarContext(); + return ( + <> +
+ + ); +} diff --git a/components/header/sidebar/util/innerMenuButton.tsx b/components/header/sidebar/util/innerMenuButton.tsx new file mode 100644 index 0000000..661551f --- /dev/null +++ b/components/header/sidebar/util/innerMenuButton.tsx @@ -0,0 +1,40 @@ +"use client"; +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +export default function InnerMenuButton() { + const sidebar = useSidebarContext(); + return ( + <> + + + ); +} diff --git a/components/header/sidebar/util/innerWrapper.tsx b/components/header/sidebar/util/innerWrapper.tsx new file mode 100644 index 0000000..4d5e3bf --- /dev/null +++ b/components/header/sidebar/util/innerWrapper.tsx @@ -0,0 +1,56 @@ +"use client"; +import { useState } from "react"; + +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +export default function InnerWrapper({ + children, +}: { + children: React.ReactNode; +}) { + const sidebar = useSidebarContext(); + const [touchStart, setTouchStart] = useState(0); + const [touchEnd, setTouchEnd] = useState(0); + //127 - 72 = 55 / 2 = 22 - 23 + return ( + <> + + + ); +} diff --git a/components/header/sidebar/util/menuButton.tsx b/components/header/sidebar/util/menuButton.tsx new file mode 100644 index 0000000..10005cd --- /dev/null +++ b/components/header/sidebar/util/menuButton.tsx @@ -0,0 +1,52 @@ +"use client"; +import { forwardRef } from "react"; + +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +type Props = JSX.IntrinsicElements["button"]; + +type Ref = HTMLButtonElement; +export const MenuButton = forwardRef(function MenuButton() { + const sidebar = useSidebarContext(); + return ( + <> + + + ); +}); diff --git a/components/header/sidebar/util/swipearea.tsx b/components/header/sidebar/util/swipearea.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/header/util/jumpToContent.tsx b/components/header/util/jumpToContent.tsx new file mode 100644 index 0000000..c3de3a2 --- /dev/null +++ b/components/header/util/jumpToContent.tsx @@ -0,0 +1,18 @@ +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +export default function JumpToContent() { + return ( + <> + + + ); +} diff --git a/components/header/util/wrapper.tsx b/components/header/util/wrapper.tsx new file mode 100644 index 0000000..ed5d25f --- /dev/null +++ b/components/header/util/wrapper.tsx @@ -0,0 +1,53 @@ +"use client"; +import { useEffect } from "react"; +import { useInView } from "react-intersection-observer"; + +import { useSidebarContext } from "@/context/sidebar"; +import { cn } from "@/lib/utils"; + +import "@/components/header/header.css"; + +export default function HeaderWrapper({ + children, +}: { + children: React.ReactNode; +}) { + const { setIsAtTop } = useSidebarContext(); + const { ref, inView } = useInView({ + threshold: 0, + initialInView: true, + fallbackInView: false, + }); + + useEffect(() => { + setIsAtTop(inView); + }, [inView, setIsAtTop]); + return ( + <> +
+
+
+ {children} +
+
+ + ); +} diff --git a/components/logo.tsx b/components/logo.tsx new file mode 100644 index 0000000..21dbebd --- /dev/null +++ b/components/logo.tsx @@ -0,0 +1,26 @@ +export default function Logo({ + className = "", + length = 32, +}: { + className?: string; + length?: number; +}) { + return ( + <> + + + + + ); +}