MobileNavigation.jsx

  1import { createContext, Fragment, useContext } from 'react'
  2import { Dialog, Transition } from '@headlessui/react'
  3import { motion } from 'framer-motion'
  4import { create } from 'zustand'
  5
  6import { Header } from '@/components/Header'
  7import { Navigation } from '@/components/Navigation'
  8
  9function MenuIcon(props) {
 10  return (
 11    <svg
 12      viewBox="0 0 10 9"
 13      fill="none"
 14      strokeLinecap="round"
 15      aria-hidden="true"
 16      {...props}
 17    >
 18      <path d="M.5 1h9M.5 8h9M.5 4.5h9" />
 19    </svg>
 20  )
 21}
 22
 23function XIcon(props) {
 24  return (
 25    <svg
 26      viewBox="0 0 10 9"
 27      fill="none"
 28      strokeLinecap="round"
 29      aria-hidden="true"
 30      {...props}
 31    >
 32      <path d="m1.5 1 7 7M8.5 1l-7 7" />
 33    </svg>
 34  )
 35}
 36
 37const IsInsideMobileNavigationContext = createContext(false)
 38
 39export function useIsInsideMobileNavigation() {
 40  return useContext(IsInsideMobileNavigationContext)
 41}
 42
 43export const useMobileNavigationStore = create((set) => ({
 44  isOpen: false,
 45  open: () => set({ isOpen: true }),
 46  close: () => set({ isOpen: false }),
 47  toggle: () => set((state) => ({ isOpen: !state.isOpen })),
 48}))
 49
 50export function MobileNavigation() {
 51  let isInsideMobileNavigation = useIsInsideMobileNavigation()
 52  let { isOpen, toggle, close } = useMobileNavigationStore()
 53  let ToggleIcon = isOpen ? XIcon : MenuIcon
 54
 55  return (
 56    <IsInsideMobileNavigationContext.Provider value={true}>
 57      <button
 58        type="button"
 59        className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
 60        aria-label="Toggle navigation"
 61        onClick={toggle}
 62      >
 63        <ToggleIcon className="w-2.5 stroke-zinc-900 dark:stroke-white" />
 64      </button>
 65      {!isInsideMobileNavigation && (
 66        <Transition.Root show={isOpen} as={Fragment}>
 67          <Dialog onClose={close} className="fixed inset-0 z-50 lg:hidden">
 68            <Transition.Child
 69              as={Fragment}
 70              enter="duration-300 ease-out"
 71              enterFrom="opacity-0"
 72              enterTo="opacity-100"
 73              leave="duration-200 ease-in"
 74              leaveFrom="opacity-100"
 75              leaveTo="opacity-0"
 76            >
 77              <div className="fixed inset-0 top-14 bg-zinc-400/20 backdrop-blur-sm dark:bg-black/40" />
 78            </Transition.Child>
 79
 80            <Dialog.Panel>
 81              <Transition.Child
 82                as={Fragment}
 83                enter="duration-300 ease-out"
 84                enterFrom="opacity-0"
 85                enterTo="opacity-100"
 86                leave="duration-200 ease-in"
 87                leaveFrom="opacity-100"
 88                leaveTo="opacity-0"
 89              >
 90                <Header />
 91              </Transition.Child>
 92
 93              <Transition.Child
 94                as={Fragment}
 95                enter="duration-500 ease-in-out"
 96                enterFrom="-translate-x-full"
 97                enterTo="translate-x-0"
 98                leave="duration-500 ease-in-out"
 99                leaveFrom="translate-x-0"
100                leaveTo="-translate-x-full"
101              >
102                <motion.div
103                  layoutScroll
104                  className="fixed bottom-0 left-0 top-14 w-full overflow-y-auto bg-white px-4 pb-4 pt-6 shadow-lg shadow-zinc-900/10 ring-1 ring-zinc-900/7.5 dark:bg-zinc-900 dark:ring-zinc-800 min-[416px]:max-w-sm sm:px-6 sm:pb-10"
105                >
106                  <Navigation />
107                </motion.div>
108              </Transition.Child>
109            </Dialog.Panel>
110          </Dialog>
111        </Transition.Root>
112      )}
113    </IsInsideMobileNavigationContext.Provider>
114  )
115}