add: dropdown component
This commit is contained in:
+54
-11
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { MouseEventHandler, useCallback, useMemo, useState } from "react";
|
||||
import SearchBox from "@/components/input/SearchBox";
|
||||
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
|
||||
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
|
||||
@@ -11,9 +11,9 @@ import VerticalScrollView from "@/components/listview/VerticalScrollView";
|
||||
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
|
||||
import React from "react";
|
||||
import MenuItem from "@/components/listview/MenuItem";
|
||||
import CloseIcon from "@/components/icons/CloseIcon";
|
||||
import Button from "@/components/button/PrimaryButton";
|
||||
import AnimatedBottomSheet from "@/components/bottomsheet/AnimatedBottomSheet";
|
||||
import MultiOption, { DropdownOption } from "@/components/dropdown/MultiOption";
|
||||
|
||||
const categories = new Array(13).fill({ title: "خوراک", icon: "" });
|
||||
|
||||
@@ -118,10 +118,26 @@ const foods = [
|
||||
}
|
||||
];
|
||||
|
||||
const sortings = [
|
||||
'محبوب ترین',
|
||||
'بالاترین امتیاز',
|
||||
'جدید ترین',
|
||||
'ارزان ترین',
|
||||
'گران ترین',
|
||||
]
|
||||
|
||||
const contents: Array<DropdownOption> = [
|
||||
{ id: '0', title: 'گیاهی' },
|
||||
{ id: '1', title: 'حیوانی' }
|
||||
]
|
||||
|
||||
const MenuIndex = () => {
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState(0);
|
||||
const [filterModal, setFilterModal] = useState(false);
|
||||
const [sortingModal, setSortingModal] = useState(false);
|
||||
const [sorting, setSorting] = useState(0);
|
||||
const [selectedContentId, setSelectedContentId] = useState('0');
|
||||
|
||||
const updateSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value);
|
||||
@@ -135,6 +151,17 @@ const MenuIndex = () => {
|
||||
setFilterModal((state) => !state);
|
||||
}, []);
|
||||
|
||||
const toggleSortingModal = useCallback(() => {
|
||||
setSortingModal((state) => !state);
|
||||
}, []);
|
||||
|
||||
const changeSorting = (index: number) => {
|
||||
setSorting(() => index);
|
||||
}
|
||||
|
||||
const changeSelectedContent = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
|
||||
setSelectedContentId(() => contents[index]?.id || contents[0].id);
|
||||
}
|
||||
|
||||
const filteredReceiptItems = useMemo(() => {
|
||||
const lowerSearch = search.toLowerCase();
|
||||
@@ -178,7 +205,7 @@ const MenuIndex = () => {
|
||||
<EqualizerIcon />
|
||||
<span className="text-xs leading-5">فیلتر بر اساس</span>
|
||||
</button>
|
||||
<button className="rounded-lg h-8 bg-white p-1.5">
|
||||
<button onClick={toggleSortingModal} className="rounded-lg h-8 bg-white p-1.5">
|
||||
<TextAlignIcon />
|
||||
</button>
|
||||
</div>
|
||||
@@ -193,15 +220,16 @@ const MenuIndex = () => {
|
||||
</VerticalScrollView>
|
||||
</section>
|
||||
|
||||
<AnimatedBottomSheet visible={filterModal} changeDelay="150" onClick={toggleFilterModal}>
|
||||
<div className="px-8 flex justify-between">
|
||||
<div>فیلتر ها</div>
|
||||
<div onClick={toggleFilterModal} className="bg-white/38 w-8 h-8 rounded-full p-1.5">
|
||||
<CloseIcon size={20} />
|
||||
</div>
|
||||
<AnimatedBottomSheet title="فیلتر ها" visible={filterModal} changeDelay="150" onClick={toggleFilterModal}>
|
||||
<div className="ps-8.5 pe-[31px] mt-[89px]">
|
||||
<MultiOption
|
||||
title="محتویات"
|
||||
options={contents}
|
||||
selectedId={selectedContentId}
|
||||
onSelectionChange={changeSelectedContent} />
|
||||
</div>
|
||||
<hr className="text-white/40 mb-7 mt-12" />
|
||||
<div className="px-9 flex justify-between gap-[22px]">
|
||||
<div className="px-9 pt-13 flex justify-between gap-[22px]">
|
||||
<hr className="text-white/40 mb-7 mt-12" />
|
||||
<div className="w-full">
|
||||
<Button>اعمال فیلتر</Button>
|
||||
</div>
|
||||
@@ -210,6 +238,21 @@ const MenuIndex = () => {
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedBottomSheet>
|
||||
|
||||
<AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} changeDelay="150" onClick={toggleSortingModal}>
|
||||
<div className="px-8.5 py-10 justify-between">
|
||||
{sortings.map((v, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<div onClick={() => changeSorting(i)} className="text-sm2 font-medium cursor-pointer">
|
||||
{v}
|
||||
</div>
|
||||
{i < sortings.length - 1 && <hr className="text-white/40 mb-4 mt-4" />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</AnimatedBottomSheet>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,10 @@ const AnimatedBottomSheet = (props: Props) => {
|
||||
className={clsx(
|
||||
'absolute -bottom-full left-0 w-full bg-white/64 rounded-t-4xl pt-[39px] pb-6',
|
||||
'data-[visible=true]:bottom-0 transition-all duration-150'
|
||||
)}>
|
||||
)}
|
||||
style={{
|
||||
backdropFilter: 'blur(44px)'
|
||||
}}>
|
||||
<div className="ps-[31px] pe-[33px] flex justify-between">
|
||||
<div className='text-lg font-medium text-black leading-8 mt-2.5'>{props.title}</div>
|
||||
<div onClick={props.onClick} className="bg-white/38 w-8 h-8 rounded-full p-1.5">
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useState } from 'react'
|
||||
import ArrowDownIcon from '../icons/ArrowDownIcon';
|
||||
|
||||
export interface DropdownOption {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
options: Array<DropdownOption>;
|
||||
expanded?: boolean;
|
||||
selectedId: string;
|
||||
onSelectionChange: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => void,
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
function MultiOption({ title, options, expanded, selectedId, onSelectionChange, ...props }: Props) {
|
||||
const [expand, setExpand] = useState(expanded);
|
||||
const toggleExpand = () => {
|
||||
setExpand((state) => !state);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative" {...props}>
|
||||
<div
|
||||
className="flex w-full h-11 items-center justify-end gap-2 px-3 py-4 relative bg-white/29 rounded-xl border border-solid border-neutral-200 cursor-pointer focus:outline-none focus:ring-2 focus:ring-black"
|
||||
tabIndex={0}
|
||||
onClick={toggleExpand}
|
||||
role='combobox'
|
||||
aria-expanded={expanded}
|
||||
aria-haspopup='listbox'
|
||||
aria-controls=''
|
||||
aria-label={title}>
|
||||
<label
|
||||
htmlFor='content-select'
|
||||
className='inline-flex flex-col items-end justify-center px-1 py-0 absolute -top-6 right-0 z-[2]'>
|
||||
<span className='relative w-fit mt-[-1px] text-xs tracking-[0] leading-4 whitespace-nowrap'>
|
||||
{title}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
|
||||
<div className='w-full text-sm2'>
|
||||
{options.filter(x => x.id === selectedId)[0]?.title ?? ''}
|
||||
</div>
|
||||
|
||||
<ArrowDownIcon />
|
||||
</div>
|
||||
|
||||
{expand &&
|
||||
<div
|
||||
className='absolute top-full left-0 w-full mt-1 bg-white rounded-xl border border-solid border-neutral-200 shadow-lg z-10'
|
||||
role='listbox'
|
||||
aria-label={`${title} options`}>
|
||||
{options.map((v, i) => {
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => {setExpand(() => false); onSelectionChange(e, i) }}
|
||||
key={v.id}
|
||||
data-selected={v.id === selectedId}
|
||||
className='flex items-center justify-end px-3 py-3 cursor-pointer hover:bg-gray-50 first:rounded-t-xl last:rounded-b-xl'
|
||||
tabIndex={0}
|
||||
role='option'
|
||||
aria-selected>
|
||||
<span
|
||||
className='text-sm2 tracking-[0.13px] leading-5 w-full'
|
||||
>
|
||||
{v.title}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MultiOption
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ArrowDownIconProps extends React.SVGProps<SVGSVGElement> {
|
||||
width?: number;
|
||||
height?: number;
|
||||
strokeColor?: string;
|
||||
}
|
||||
|
||||
const ArrowDownIcon: React.FC<ArrowDownIconProps> = ({
|
||||
width = 16,
|
||||
height = 16,
|
||||
strokeColor = '#999999',
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M13.2797 5.96655L8.93306 10.3132C8.41973 10.8266 7.57973 10.8266 7.06639 10.3132L2.71973 5.96655"
|
||||
stroke={strokeColor}
|
||||
strokeWidth="2"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default ArrowDownIcon;
|
||||
@@ -6,7 +6,7 @@ type Props = {
|
||||
|
||||
function CategoryItemRenderer({ children, ...rest }: Props) {
|
||||
return (
|
||||
<div className={`${rest.className} transition-all duration-200 ease-out border-2 border-solid border-white overflow-hidden rounded-xl backdrop-blur-md bg-white/40`}>
|
||||
<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 w-[88px] h-[88px] flex flex-col justify-center items-center gap-2"
|
||||
|
||||
@@ -8,9 +8,7 @@ import HeartIcon from '../icons/HeartIcon'
|
||||
import { useParams, usePathname } from 'next/navigation'
|
||||
import HomeIcon from '../icons/HomeIcon'
|
||||
|
||||
type Props = object
|
||||
|
||||
function BottomNavBar({ }: Props) {
|
||||
function BottomNavBar() {
|
||||
const params = useParams();
|
||||
const { name } = params;
|
||||
const pathname = usePathname();
|
||||
|
||||
Reference in New Issue
Block a user