add: dynamic fixed categories list to menu page
This commit is contained in:
+67
-6
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import SearchBox from "@/components/input/SearchBox";
|
import SearchBox from "@/components/input/SearchBox";
|
||||||
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
|
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
|
||||||
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
|
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
|
||||||
@@ -18,6 +18,9 @@ import { TicketPercentIcon } from "lucide-react";
|
|||||||
import { MedalStar } from 'iconsax-react'
|
import { MedalStar } from 'iconsax-react'
|
||||||
import { useQueryState } from 'next-usequerystate'
|
import { useQueryState } from 'next-usequerystate'
|
||||||
import ComboBox, { DropdownOption } from "@/components/combobox/ComboBox";
|
import ComboBox, { DropdownOption } from "@/components/combobox/ComboBox";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import CategorySmallItemRenderer from "@/components/listview/CategorySmallItemRenderer";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
const categories = new Array(13).fill({ title: "خوراک", icon: "" });
|
const categories = new Array(13).fill({ title: "خوراک", icon: "" });
|
||||||
|
|
||||||
@@ -148,15 +151,40 @@ const MenuIndex = () => {
|
|||||||
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
|
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
|
||||||
const [selectedContentId, setSelectedContentId] = useQueryState('contentType', { defaultValue: '0' });
|
const [selectedContentId, setSelectedContentId] = useQueryState('contentType', { defaultValue: '0' });
|
||||||
const [selectedShippingId, setSelectedShippingId] = useQueryState('shippingType', { defaultValue: '0' });
|
const [selectedShippingId, setSelectedShippingId] = useQueryState('shippingType', { defaultValue: '0' });
|
||||||
|
const smallCategoriesRef: React.RefObject<HTMLDivElement | null> = useRef(null);
|
||||||
|
const wrapperRef: React.RefObject<HTMLDivElement | null> = useRef(null);
|
||||||
|
const [smallCategoriesVisible, setSmallCategoriesVisibility] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let m_wrapperRef = null;
|
||||||
|
if (wrapperRef.current) {
|
||||||
|
m_wrapperRef = wrapperRef;
|
||||||
|
wrapperRef.current?.parentElement?.addEventListener('scroll', onScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (m_wrapperRef?.current)
|
||||||
|
m_wrapperRef.current?.parentElement?.removeEventListener('scroll', onScroll)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onScroll = () => {
|
||||||
|
if (!wrapperRef?.current?.parentElement?.scrollTop || !smallCategoriesRef.current?.offsetTop) return;
|
||||||
|
if (wrapperRef?.current?.parentElement?.scrollTop >= smallCategoriesRef.current?.offsetTop) {
|
||||||
|
setSmallCategoriesVisibility(true);
|
||||||
|
} else {
|
||||||
|
setSmallCategoriesVisibility(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updateSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const updateSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value);
|
setSearch(e.target.value);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateCategory = useCallback((id: number) => {
|
const updateCategory = useCallback((id: number) => {
|
||||||
setSelectedCategory(String(id));
|
setSelectedCategory(String(id));
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleFilterModal = useCallback(() => {
|
const toggleFilterModal = useCallback(() => {
|
||||||
@@ -190,13 +218,15 @@ const MenuIndex = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 items-center">
|
<div className="flex flex-col gap-4 items-center" ref={wrapperRef}>
|
||||||
<SearchBox value={search} onChange={updateSearch} />
|
<SearchBox value={search} onChange={updateSearch} />
|
||||||
<HorizontalScrollView className="w-full noscrollbar py-4!">
|
<HorizontalScrollView className="w-full noscrollbar py-4!">
|
||||||
{categories.map((item, index) => (
|
{categories.map((item, index) => (
|
||||||
<CategoryItemRenderer
|
<CategoryItemRenderer
|
||||||
key={index}
|
key={index}
|
||||||
className={`${index === +selectedCategory ? "bg-white!" : ""}`}
|
className={clsx(
|
||||||
|
index === +selectedCategory && "bg-white!",
|
||||||
|
)}
|
||||||
onClick={() => updateCategory(index)}
|
onClick={() => updateCategory(index)}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@@ -212,7 +242,8 @@ const MenuIndex = () => {
|
|||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
||||||
<section className="w-full">
|
<section className="w-full">
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
|
<div className="flex justify-between items-center relative" ref={smallCategoriesRef} >
|
||||||
<span className="text-base font-medium">
|
<span className="text-base font-medium">
|
||||||
{categories[+selectedCategory]?.title}
|
{categories[+selectedCategory]?.title}
|
||||||
</span>
|
</span>
|
||||||
@@ -227,6 +258,36 @@ const MenuIndex = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -200 }}
|
||||||
|
animate={{ y: smallCategoriesVisible ? 0 : -200, opacity: smallCategoriesVisible ? 1 : 0 }}
|
||||||
|
transition={{ duration: .1 }}
|
||||||
|
className={clsx(
|
||||||
|
'fixed left-0 z-10 top-0 px-4 pt-16 bg-[#F4F5F9CC] backdrop-blur-[44px] right-0',
|
||||||
|
``
|
||||||
|
)}>
|
||||||
|
<HorizontalScrollView className="w-full noscrollbar py-4!">
|
||||||
|
{categories.map((item, index) => (
|
||||||
|
<CategorySmallItemRenderer
|
||||||
|
key={index}
|
||||||
|
className={clsx(
|
||||||
|
index === +selectedCategory && "bg-white!",
|
||||||
|
)}
|
||||||
|
onClick={() => updateCategory(index)}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
priority
|
||||||
|
src="/assets/images/food-image.png"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
alt="category image"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-black">{item.title}</span>
|
||||||
|
</CategorySmallItemRenderer>
|
||||||
|
))}
|
||||||
|
</HorizontalScrollView>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
<VerticalScrollView className="mt-5! overflow-y-auto h-full">
|
<VerticalScrollView className="mt-5! overflow-y-auto h-full">
|
||||||
{filteredReceiptItems.map((food) => (
|
{filteredReceiptItems.map((food) => (
|
||||||
<MenuItemRenderer key={food.id}>
|
<MenuItemRenderer key={food.id}>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
} & React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
function CategorySmallItemRenderer({ children, ...rest }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={`${rest.className} cursor-pointer transition-all duration-200 ease-out border-2 border-solid border-white overflow-hidden rounded-xl backdrop-blur-md bg-white/40`}>
|
||||||
|
<div
|
||||||
|
{...rest}
|
||||||
|
className="rounded-normal h-[44px] flex flex-row justify-center items-center p-2.5 gap-2"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategorySmallItemRenderer;
|
||||||
@@ -5,7 +5,8 @@ type Props = {
|
|||||||
isPending: boolean
|
isPending: boolean
|
||||||
} & Omit<React.FormHTMLAttributes<HTMLFormElement>, "id">;
|
} & Omit<React.FormHTMLAttributes<HTMLFormElement>, "id">;
|
||||||
|
|
||||||
function AuthFormWrapper({ children, ...restProps }: Props) {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
function AuthFormWrapper({ children, isPending, ...restProps }: Props) {
|
||||||
return (
|
return (
|
||||||
<form {...restProps} className='p-6 lg:py-[75px] w-full flex items-center justify-center py-4 lg:items-center lg:px-10 px-4 drop-shadow-black h-full'>
|
<form {...restProps} className='p-6 lg:py-[75px] w-full flex items-center justify-center py-4 lg:items-center lg:px-10 px-4 drop-shadow-black h-full'>
|
||||||
<div className='relative h-full w-full px-4 sm:px-6 lg:p-0 flex flex-col max-h-[812px] max-w-[1200px] bg-container rounded-container overflow-clip lg:flex-row justify-between'>
|
<div className='relative h-full w-full px-4 sm:px-6 lg:p-0 flex flex-col max-h-[812px] max-w-[1200px] bg-container rounded-container overflow-clip lg:flex-row justify-between'>
|
||||||
|
|||||||
Reference in New Issue
Block a user