From f009bc19cbb3c0c475e50d4e546a3f0e6a0bfd0e Mon Sep 17 00:00:00 2001 From: Timur Suleymanov Date: Tue, 21 May 2024 20:00:30 +0500 Subject: [PATCH] add my component folder --- 360/layout/footer-menu.tsx | 46 ++++++++++++ 360/layout/footer.tsx | 67 +++++++++++++++++ 360/layout/navbar/index.tsx | 57 +++++++++++++++ 360/layout/navbar/mobile-menu.tsx | 100 ++++++++++++++++++++++++++ 360/layout/navbar/search.tsx | 57 +++++++++++++++ 360/layout/product-grid-items.tsx | 28 ++++++++ 360/layout/search/collections.tsx | 37 ++++++++++ 360/layout/search/filter/dropdown.tsx | 64 +++++++++++++++++ 360/layout/search/filter/index.tsx | 41 +++++++++++ 360/layout/search/filter/item.tsx | 67 +++++++++++++++++ 10 files changed, 564 insertions(+) create mode 100644 360/layout/footer-menu.tsx create mode 100644 360/layout/footer.tsx create mode 100644 360/layout/navbar/index.tsx create mode 100644 360/layout/navbar/mobile-menu.tsx create mode 100644 360/layout/navbar/search.tsx create mode 100644 360/layout/product-grid-items.tsx create mode 100644 360/layout/search/collections.tsx create mode 100644 360/layout/search/filter/dropdown.tsx create mode 100644 360/layout/search/filter/index.tsx create mode 100644 360/layout/search/filter/item.tsx diff --git a/360/layout/footer-menu.tsx b/360/layout/footer-menu.tsx new file mode 100644 index 000000000..444406294 --- /dev/null +++ b/360/layout/footer-menu.tsx @@ -0,0 +1,46 @@ +'use client'; + +import clsx from 'clsx'; +import { Menu } from 'lib/shopify/types'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +const FooterMenuItem = ({ item }: { item: Menu }) => { + const pathname = usePathname(); + const [active, setActive] = useState(pathname === item.path); + + useEffect(() => { + setActive(pathname === item.path); + }, [pathname, item.path]); + + return ( +
  • + + {item.title} + +
  • + ); +}; + +export default function FooterMenu({ menu }: { menu: Menu[] }) { + if (!menu.length) return null; + + return ( + + ); +} diff --git a/360/layout/footer.tsx b/360/layout/footer.tsx new file mode 100644 index 000000000..704ff359b --- /dev/null +++ b/360/layout/footer.tsx @@ -0,0 +1,67 @@ +import Link from 'next/link'; + +import LogoSquare from 'components/logo-square'; +import { Suspense } from 'react'; + +const { COMPANY_NAME, SITE_NAME } = process.env; + +export default async function Footer() { + const currentYear = new Date().getFullYear(); + const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : ''); + const skeleton = 'w-full h-6 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700'; + // const menu = await getMenu('next-js-frontend-footer-menu'); + const copyrightName = COMPANY_NAME || SITE_NAME || ''; + + return ( + + ); +} diff --git a/360/layout/navbar/index.tsx b/360/layout/navbar/index.tsx new file mode 100644 index 000000000..2d56ebe2c --- /dev/null +++ b/360/layout/navbar/index.tsx @@ -0,0 +1,57 @@ +'use client'; + +import Cart from 'components/cart'; +import OpenCart from 'components/cart/open-cart'; +import LogoSquare from 'components/logo-square'; +import { Menu } from 'lib/shopify/types'; +import Link from 'next/link'; +import { Suspense } from 'react'; +import Search, { SearchSkeleton } from './search'; +const { SITE_NAME } = process.env; + +export default function Navbar() { + alert(123); + const menu = []; + return ( + + ); +} diff --git a/360/layout/navbar/mobile-menu.tsx b/360/layout/navbar/mobile-menu.tsx new file mode 100644 index 000000000..9091f93d3 --- /dev/null +++ b/360/layout/navbar/mobile-menu.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { Dialog, Transition } from '@headlessui/react'; +import Link from 'next/link'; +import { usePathname, useSearchParams } from 'next/navigation'; +import { Fragment, Suspense, useEffect, useState } from 'react'; + +import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'; +import { Menu } from 'lib/shopify/types'; +import Search, { SearchSkeleton } from './search'; + +export default function MobileMenu({ menu }: { menu: Menu[] }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const [isOpen, setIsOpen] = useState(false); + const openMobileMenu = () => setIsOpen(true); + const closeMobileMenu = () => setIsOpen(false); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth > 768) { + setIsOpen(false); + } + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [isOpen]); + + useEffect(() => { + setIsOpen(false); + }, [pathname, searchParams]); + + return ( + <> + + + + + + + + ); +} diff --git a/360/layout/navbar/search.tsx b/360/layout/navbar/search.tsx new file mode 100644 index 000000000..551d781c2 --- /dev/null +++ b/360/layout/navbar/search.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import { createUrl } from 'lib/utils'; +import { useRouter, useSearchParams } from 'next/navigation'; + +export default function Search() { + const router = useRouter(); + const searchParams = useSearchParams(); + + function onSubmit(e: React.FormEvent) { + e.preventDefault(); + + const val = e.target as HTMLFormElement; + const search = val.search as HTMLInputElement; + const newParams = new URLSearchParams(searchParams.toString()); + + if (search.value) { + newParams.set('q', search.value); + } else { + newParams.delete('q'); + } + + router.push(createUrl('/search', newParams)); + } + + return ( +
    + +
    + +
    +
    + ); +} + +export function SearchSkeleton() { + return ( +
    + +
    + +
    +
    + ); +} diff --git a/360/layout/product-grid-items.tsx b/360/layout/product-grid-items.tsx new file mode 100644 index 000000000..ea8a5ebf7 --- /dev/null +++ b/360/layout/product-grid-items.tsx @@ -0,0 +1,28 @@ +import Grid from 'components/grid'; +import { GridTileImage } from 'components/grid/tile'; +import { Product } from 'lib/shopify/types'; +import Link from 'next/link'; + +export default function ProductGridItems({ products }: { products: Product[] }) { + return ( + <> + {products.map((product) => ( + + + + + + ))} + + ); +} diff --git a/360/layout/search/collections.tsx b/360/layout/search/collections.tsx new file mode 100644 index 000000000..c45833a39 --- /dev/null +++ b/360/layout/search/collections.tsx @@ -0,0 +1,37 @@ +import clsx from 'clsx'; +import { Suspense } from 'react'; + +import { getCollections } from 'lib/shopify'; +import FilterList from './filter'; + +async function CollectionList() { + const collections = await getCollections(); + return ; +} + +const skeleton = 'mb-3 h-4 w-5/6 animate-pulse rounded'; +const activeAndTitles = 'bg-neutral-800 dark:bg-neutral-300'; +const items = 'bg-neutral-400 dark:bg-neutral-700'; + +export default function Collections() { + return ( + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + } + > + + + ); +} diff --git a/360/layout/search/filter/dropdown.tsx b/360/layout/search/filter/dropdown.tsx new file mode 100644 index 000000000..31daa25ce --- /dev/null +++ b/360/layout/search/filter/dropdown.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { usePathname, useSearchParams } from 'next/navigation'; +import { useEffect, useRef, useState } from 'react'; + +import { ChevronDownIcon } from '@heroicons/react/24/outline'; +import type { ListItem } from '.'; +import { FilterItem } from './item'; + +export default function FilterItemDropdown({ list }: { list: ListItem[] }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const [active, setActive] = useState(''); + const [openSelect, setOpenSelect] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + setOpenSelect(false); + } + }; + + window.addEventListener('click', handleClickOutside); + return () => window.removeEventListener('click', handleClickOutside); + }, []); + + useEffect(() => { + list.forEach((listItem: ListItem) => { + if ( + ('path' in listItem && pathname === listItem.path) || + ('slug' in listItem && searchParams.get('sort') === listItem.slug) + ) { + setActive(listItem.title); + } + }); + }, [pathname, list, searchParams]); + + return ( +
    +
    { + setOpenSelect(!openSelect); + }} + className="flex w-full items-center justify-between rounded border border-black/30 px-4 py-2 text-sm dark:border-white/30" + > +
    {active}
    + +
    + {openSelect && ( +
    { + setOpenSelect(false); + }} + className="absolute z-40 w-full rounded-b-md bg-white p-4 shadow-md dark:bg-black" + > + {list.map((item: ListItem, i) => ( + + ))} +
    + )} +
    + ); +} diff --git a/360/layout/search/filter/index.tsx b/360/layout/search/filter/index.tsx new file mode 100644 index 000000000..11a7cd367 --- /dev/null +++ b/360/layout/search/filter/index.tsx @@ -0,0 +1,41 @@ +import { SortFilterItem } from 'lib/constants'; +import { Suspense } from 'react'; +import FilterItemDropdown from './dropdown'; +import { FilterItem } from './item'; + +export type ListItem = SortFilterItem | PathFilterItem; +export type PathFilterItem = { title: string; path: string }; + +function FilterItemList({ list }: { list: ListItem[] }) { + return ( + <> + {list.map((item: ListItem, i) => ( + + ))} + + ); +} + +export default function FilterList({ list, title }: { list: ListItem[]; title?: string }) { + return ( + <> + + + ); +} diff --git a/360/layout/search/filter/item.tsx b/360/layout/search/filter/item.tsx new file mode 100644 index 000000000..3fce8e8a9 --- /dev/null +++ b/360/layout/search/filter/item.tsx @@ -0,0 +1,67 @@ +'use client'; + +import clsx from 'clsx'; +import type { SortFilterItem } from 'lib/constants'; +import { createUrl } from 'lib/utils'; +import Link from 'next/link'; +import { usePathname, useSearchParams } from 'next/navigation'; +import type { ListItem, PathFilterItem } from '.'; + +function PathFilterItem({ item }: { item: PathFilterItem }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const active = pathname === item.path; + const newParams = new URLSearchParams(searchParams.toString()); + const DynamicTag = active ? 'p' : Link; + + newParams.delete('q'); + + return ( +
  • + + {item.title} + +
  • + ); +} + +function SortFilterItem({ item }: { item: SortFilterItem }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const active = searchParams.get('sort') === item.slug; + const q = searchParams.get('q'); + const href = createUrl( + pathname, + new URLSearchParams({ + ...(q && { q }), + ...(item.slug && item.slug.length && { sort: item.slug }) + }) + ); + const DynamicTag = active ? 'p' : Link; + + return ( +
  • + + {item.title} + +
  • + ); +} + +export function FilterItem({ item }: { item: ListItem }) { + return 'path' in item ? : ; +}