add: dynamic fixed categories list to menu page

This commit is contained in:
Mahyar Khanbolooki
2025-07-18 21:29:04 +03:30
parent ee056ee947
commit 43f905ac14
3 changed files with 89 additions and 7 deletions
+67 -6
View File
@@ -1,6 +1,6 @@
'use client';
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import SearchBox from "@/components/input/SearchBox";
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
@@ -18,6 +18,9 @@ import { TicketPercentIcon } from "lucide-react";
import { MedalStar } from 'iconsax-react'
import { useQueryState } from 'next-usequerystate'
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: "" });
@@ -148,15 +151,40 @@ const MenuIndex = () => {
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
const [selectedContentId, setSelectedContentId] = useQueryState('contentType', { 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>) => {
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) => {
setSelectedCategory(String(id));
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const toggleFilterModal = useCallback(() => {
@@ -190,13 +218,15 @@ const MenuIndex = () => {
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} />
<HorizontalScrollView className="w-full noscrollbar py-4!">
{categories.map((item, index) => (
<CategoryItemRenderer
key={index}
className={`${index === +selectedCategory ? "bg-white!" : ""}`}
className={clsx(
index === +selectedCategory && "bg-white!",
)}
onClick={() => updateCategory(index)}
>
<Image
@@ -212,7 +242,8 @@ const MenuIndex = () => {
</HorizontalScrollView>
<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">
{categories[+selectedCategory]?.title}
</span>
@@ -227,6 +258,36 @@ const MenuIndex = () => {
</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">
{filteredReceiptItems.map((food) => (
<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
} & 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 (
<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'>