Skip to content

Commit

Permalink
Merge pull request #33 from tinloof/filters-dropdown
Browse files Browse the repository at this point in the history
Filter dropdown mobile/ desktop
  • Loading branch information
haninekkoub authored Oct 22, 2024
2 parents 6ec08bc + c9585b9 commit d808ca2
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,11 @@ export default function DropDown({
</button>
<div
className={cx(
"border-border-grey absolute left-0 z-50 my-1 origin-top cursor-pointer overflow-y-scroll rounded-lg rounded-b-lg border-[1.5px] border-accent bg-background transition-[max-height]",
"absolute left-0 z-50 my-1 origin-top cursor-pointer overflow-y-scroll rounded-lg rounded-b-lg border-[1.5px] border-accent bg-background",
{
hidden: !isOpen,
},
)}
style={{
maxHeight: isOpen ? 300 : 0,
}}
>
{children}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Icon from "@/components/shared/icon";
import Body from "@/components/shared/typography/body";
import {cx} from "cva";
import {parseAsArrayOf, parseAsString, useQueryState} from "nuqs";
import {type ComponentProps, useState} from "react";
import {type ComponentProps, useEffect, useRef, useState} from "react";

import DropDown from "./drop-down";

Expand Down Expand Up @@ -37,39 +37,96 @@ export default function FilterSelect(
const {filter, setFilter} = useMultiFilter(props.name);

const [isOpen, setOpen] = useState(false);
const [showTopArrow, setShowTopArrow] = useState(false);
const [showBottomArrow, setShowBottomArrow] = useState(false);
const scrollContainerRef = useRef<HTMLDivElement>(null);

const handleScroll = () => {
const container = scrollContainerRef.current;
if (container) {
setShowTopArrow(container.scrollTop > 0);
setShowBottomArrow(
container.scrollHeight - container.clientHeight >
container.scrollTop + 1,
);
}
};

useEffect(() => {
handleScroll();
}, [isOpen]);

const scrollBy = (amount: number) => {
const container = scrollContainerRef.current;
if (container) {
container.scrollBy({behavior: "smooth", top: amount});
}
};

return (
<DropDown isOpen={isOpen} placeholder={props.placeholder} setOpen={setOpen}>
<div className="group flex w-full flex-col gap-2 rounded p-xs py-2">
{props.options.map((option) => {
const selected = filter?.includes(option.value);
return (
<button
className="flex cursor-pointer items-center gap-2 rounded-lg px-s py-xs hover:bg-secondary disabled:pointer-events-none"
key={option.value}
onClick={() => setFilter(option.value)}
>
<div className="flex !size-4 items-center justify-center rounded-[4px] border border-accent">
<Icon
className={cx(
"!size-3 shrink-0 transform opacity-0 transition-transform duration-300",
{"opacity-100": selected},
)}
height={24}
name="Check"
width={24}
/>
</div>
<Body
className="truncate text-nowrap text-left"
font="sans"
mobileSize="base"
<div
className="relative max-h-[320px] w-full overflow-y-auto rounded"
onScroll={handleScroll}
ref={scrollContainerRef}
>
<div className="sticky top-0">
<div
className={cx(
"absolute left-0 top-0 flex w-full cursor-pointer items-center justify-center border-b-[1.5px] border-accent bg-background transition-all duration-300",
{
"translate-y-[-100%]": !showTopArrow,
},
)}
onClick={() => scrollBy(-250)}
>
<Icon className="size-6" name="AccordionBottom" />
</div>
</div>
<div className="group flex w-full flex-col gap-2 p-xs">
{props.options.map((option) => {
const selected = filter?.includes(option.value);
return (
<button
className="flex cursor-pointer items-center gap-2 rounded-lg px-s py-xs hover:bg-secondary disabled:pointer-events-none"
key={option.value}
onClick={() => setFilter(option.value)}
>
{option.label}
</Body>
</button>
);
})}
<div className="flex !size-4 items-center justify-center rounded-[4px] border border-accent">
<Icon
className={cx(
"!size-3 shrink-0 transform opacity-0 transition-transform duration-300",
{"opacity-100": selected},
)}
height={24}
name="Check"
width={24}
/>
</div>
<Body
className="truncate text-nowrap text-left"
font="sans"
mobileSize="base"
>
{option.label}
</Body>
</button>
);
})}
</div>
<div className="sticky bottom-0 -mt-[25.5px] h-[25.5px] overflow-hidden">
<div
className={cx(
"sticky bottom-0 left-0 flex w-full cursor-pointer items-center justify-center border-t-[1.5px] border-accent bg-background transition-all duration-300",
{
"translate-y-[100%]": !showBottomArrow,
},
)}
onClick={() => scrollBy(250)}
>
<Icon className="size-6" name="AccordionTop" />
</div>
</div>
</div>
</DropDown>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
"use client";
import {Cta} from "@/components/shared/button";
import Icon from "@/components/shared/icon";
import Body from "@/components/shared/typography/body";
import {cx} from "cva";
import {usePathname, useRouter} from "next/navigation";
import {type PropsWithChildren, useCallback, useState} from "react";
import {
type PropsWithChildren,
useCallback,
useEffect,
useRef,
useState,
} from "react";

import DropDown from "../drop-down";

Expand All @@ -17,11 +25,68 @@ export default function MobileFilterDropdown({children}: Props) {
router.push(pathname);
setOpen(false);
}, [router, pathname]);
const [showTopArrow, setShowTopArrow] = useState(false);
const [showBottomArrow, setShowBottomArrow] = useState(false);
const scrollContainerRef = useRef<HTMLDivElement>(null);

const handleScroll = () => {
const container = scrollContainerRef.current;
if (container) {
setShowTopArrow(container.scrollTop > 0);
setShowBottomArrow(
container.scrollHeight - container.clientHeight >
container.scrollTop + 1,
);
}
};

useEffect(() => {
handleScroll();
}, [isOpen]);

const scrollBy = (amount: number) => {
const container = scrollContainerRef.current;
if (container) {
container.scrollBy({behavior: "smooth", top: amount});
}
};
return (
<DropDown isOpen={isOpen} placeholder="Filter" setOpen={setOpen}>
{children}
<div className="my-2 flex w-[calc(100vw-40px)] flex-col justify-center gap-s px-xs">
<div
className="relative max-h-[360px] w-full overflow-y-auto rounded"
onScroll={handleScroll}
ref={scrollContainerRef}
>
<div className="sticky top-0">
<div
className={cx(
"absolute left-0 top-0 flex w-full cursor-pointer items-center justify-center border-b-[1.5px] border-accent bg-background transition-all duration-300",
{
"translate-y-[-100%]": !showTopArrow,
},
)}
onClick={() => scrollBy(-250)}
>
<Icon className="my-1 size-6" name="AccordionBottom" />
</div>
</div>
{children}
<div className="sticky bottom-0 h-[25.5px] overflow-hidden">
<div
className={cx(
"sticky bottom-0 left-0 flex w-full cursor-pointer items-center justify-center border-t-[1.5px] border-accent bg-background transition-all duration-300",
{
"translate-y-[100%]": !showBottomArrow,
},
)}
onClick={() => scrollBy(250)}
>
<Icon className="my-1 size-6" name="AccordionTop" />
</div>
</div>
</div>

<div className="sticky bottom-0 my-2 flex w-[calc(100vw-40px)] flex-col justify-center gap-s px-xs">
<Cta className="w-full" onClick={() => setOpen(false)} size="md">
Show Results
</Cta>
Expand Down

0 comments on commit d808ca2

Please sign in to comment.