Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
8aab3e9
feat(ui-components): implement new Menu component and replace Popover…
rogerantony-dev Mar 9, 2026
6ae23c4
refactor(dashboard): update DeleteCollectionModal and CollectionOptio…
rogerantony-dev Mar 9, 2026
d181d02
refactor(dashboard): replace Popover with Menu component in HeaderOpt…
rogerantony-dev Mar 9, 2026
6f20a63
refactor(dashboard): streamline Menu component usage and remove unnec…
rogerantony-dev Mar 9, 2026
527096f
refactor(dashboard): update dismiss logic in HeaderOptionsPopover and…
rogerantony-dev Mar 9, 2026
1cb8f2e
refactor(ui-components): replace Popover imports with new Recollect P…
rogerantony-dev Mar 10, 2026
9bd49a9
Merge branch 'dev' into feat/popover-dropdown-open-close-animations
rogerantony-dev Mar 10, 2026
8f089ff
fix(categoryIconsDropdown): adjust padding in Popover.Popup for bette…
rogerantony-dev Mar 10, 2026
7b7ce41
fix(combobox): increase z-index for Positioner to improve stacking co…
rogerantony-dev Mar 10, 2026
186c13f
feat(animated-size): add AnimatedSize component for smooth resizing a…
rogerantony-dev Mar 10, 2026
5ecaf9e
refactor(dashboard): rename isItemClick to isItemClickRef for clarity
rogerantony-dev Mar 10, 2026
ba1648f
feat(dashboard): enhance EditPopover to conditionally render
rogerantony-dev Mar 10, 2026
7122a68
refactor(dashboard): simplify EditPopover state management and improv…
rogerantony-dev Mar 10, 2026
0572823
fix(popover): correct z-index class format in Positioner component
rogerantony-dev Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/clearTrashDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Popover } from "@base-ui/react/popover";
import { Popover } from "@/components/ui/recollect/popover";

import { ClearTrashContent } from "./clearTrashContent";
import TrashIconGray from "@/icons/actionIcons/trashIconGray";
Expand Down Expand Up @@ -53,9 +53,9 @@ export function ClearTrashDropdown(props: ClearTrashDropdownProps) {
)}
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner align="start" className="z-10" sideOffset={1}>
<Popover.Positioner align="start">
<Popover.Popup
className={`${!isBottomBar ? "ml-2" : ""} w-[180px] rounded-xl bg-gray-50 p-1 leading-[20px] shadow-custom-3 outline-hidden`}
className={`${!isBottomBar ? "ml-2" : ""} w-[180px] leading-[20px]`}
>
<ClearTrashContent
onClearTrash={onClearTrash}
Expand Down
13 changes: 5 additions & 8 deletions src/components/customDropdowns.tsx/addBookmarkDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { Popover } from "@base-ui/react/popover";
import { Popover } from "@/components/ui/recollect/popover";
import isEmpty from "lodash/isEmpty";
import isNil from "lodash/isNil";
import { useForm, type SubmitHandler } from "react-hook-form";
Expand All @@ -12,10 +12,7 @@ import { useFileUploadDrop } from "@/hooks/useFileUploadDrop";
import { AddBookmarkInputIcon } from "@/icons/miscellaneousIcons/add-bookmark-input-icon";
import PlusIconWhite from "@/icons/plusIconWhite";
import { type FileType } from "@/types/componentTypes";
import {
dropdownMenuClassName,
grayInputClassName,
} from "@/utils/commonClassNames";
import { grayInputClassName } from "@/utils/commonClassNames";
import { URL_PATTERN } from "@/utils/constants";

const AddBookmarkDropdown = () => {
Expand Down Expand Up @@ -44,8 +41,8 @@ const AddBookmarkDropdown = () => {
</figure>
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner align="end" className="z-10" sideOffset={1}>
<Popover.Popup className="leading-5 outline-hidden">
<Popover.Positioner align="end">
<Popover.Popup className="w-auto p-0 leading-5">
<AddBookmarkPopupContent onClose={() => setOpen(false)} />
</Popover.Popup>
</Popover.Positioner>
Expand Down Expand Up @@ -93,7 +90,7 @@ const AddBookmarkPopupContent = ({ onClose }: AddBookmarkPopupContentProps) => {
});

return (
<div className={`relative w-[326px] ${dropdownMenuClassName}`}>
<div className="relative w-[326px] p-1">
<input
className="hidden"
onChange={(event) =>
Expand Down
4 changes: 2 additions & 2 deletions src/components/customDropdowns.tsx/bookmarksViewDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Popover } from "@base-ui/react/popover";
import { Popover } from "@/components/ui/recollect/popover";
import find from "lodash/find";

import useGetViewValue from "../../hooks/useGetViewValue";
Expand Down Expand Up @@ -105,7 +105,7 @@ const BookmarksViewPopover = ({
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner sideOffset={8}>
<Popover.Popup className="z-20 w-[195px] origin-(--transform-origin) rounded-xl bg-white px-[6px] pt-[6px] pb-3 shadow-custom-1 ring-1 ring-black/5">
<Popover.Popup className="w-[195px] bg-white px-[6px] pt-[6px] pb-3 shadow-custom-1 ring-1 ring-black/5">
{children}
</Popover.Popup>
</Popover.Positioner>
Expand Down
4 changes: 2 additions & 2 deletions src/components/customDropdowns.tsx/categoryIconsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { Popover } from "@base-ui/react/popover";
import { Popover } from "@/components/ui/recollect/popover";
import { matchSorter } from "match-sorter";

import { useUpdateCategoryOptimisticMutation } from "../../async/mutationHooks/category/use-update-category-optimistic-mutation";
Expand Down Expand Up @@ -83,7 +83,7 @@ export const CategoryIconsDropdown = (props: CategoryIconsDropdownProps) => {
</Popover.Trigger>
<Popover.Portal>
<Popover.Positioner className="z-103" sideOffset={8}>
<Popover.Popup className="h-[368px] w-[310px] rounded-xl bg-gray-50 px-1 shadow-custom-1 ring-1 ring-black/5 outline-hidden">
<Popover.Popup className="h-[368px] w-[310px] p-0 px-1 shadow-custom-1 ring-1 ring-black/5">
<IconPickerHeader
searchValue={searchValue}
setSearchValue={setSearchValue}
Expand Down
59 changes: 59 additions & 0 deletions src/components/ui/recollect/animated-size.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use something framer motion native solution using layout - https://motion.dev/examples/react-base-tabs?platform=react&search=layout & https://motion.dev/docs/react-layout-animations?

Explore if its possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats what i tried initially, but animation was not this good, felt a bit janky, Thats why i choose to go this way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like the popover was changing correctly, but the text and icons would get stretched to achieve that

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, okay approving as it is.

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { motion, useReducedMotion } from "motion/react";

interface AnimatedSizeProps {
children: React.ReactNode;
}

/**
* Wraps children in a container that smoothly animates width/height changes
* using ResizeObserver + Framer Motion spring animation.
* Respects prefers-reduced-motion.
*/
export function AnimatedSize({ children }: AnimatedSizeProps) {
const shouldReduceMotion = useReducedMotion();
const ref = useRef<HTMLDivElement>(null);
const [size, setSize] = useState<{
height: number;
width: number;
} | null>(null);

useEffect(() => {
const el = ref.current;
if (!el) {
return () => {};
}

const observer = new ResizeObserver(([entry]) => {
if (!entry) {
return;
}

const { height, width } = entry.contentRect;
// Skip zero sizes (popup closing) — let parent CSS animation handle exit
if (height > 0 && width > 0) {
setSize({ height, width });
}
});
observer.observe(el);
return () => observer.disconnect();
}, []);

return (
<motion.div
animate={size ?? undefined}
transition={
shouldReduceMotion
? { duration: 0 }
: { type: "spring", bounce: 0.15, duration: 0.3 }
}
style={{ overflow: "clip" }}
>
<div ref={ref} className="w-fit">
{children}
</div>
</motion.div>
);
}
4 changes: 2 additions & 2 deletions src/components/ui/recollect/combobox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ function Positioner({

return (
<ComboboxPrimitive.Positioner
className={cn("z-50 select-none", className)}
className={cn("z-52 select-none", className)}
sideOffset={sideOffset}
anchor={anchor ?? containerRef}
data-slot="combobox-positioner"
Expand All @@ -330,7 +330,7 @@ function Popup({
<ComboboxPrimitive.Popup
data-slot="combobox-popup"
className={cn(
"w-(--anchor-width) origin-(--transform-origin) rounded-xl bg-gray-0 shadow-custom-7 transition-[scale,opacity,shadow] data-starting-style:scale-98 data-starting-style:opacity-0",
"w-(--anchor-width) origin-center rounded-xl bg-gray-0 shadow-custom-7 transition-[transform,scale,opacity,shadow] data-ending-style:scale-95 data-ending-style:opacity-0 data-open:origin-(--transform-origin) data-starting-style:scale-95 data-starting-style:opacity-0 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-starting-style:translate-y-1",
className,
)}
{...props}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/recollect/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function Positioner({
}: MenuPrimitive.Positioner.Props) {
return (
<MenuPrimitive.Positioner
className={cn("z-[51]", className)}
className={cn("z-51", className)}
sideOffset={sideOffset}
{...props}
/>
Expand All @@ -34,7 +34,7 @@ function Popup({ className, ...props }: MenuPrimitive.Popup.Props) {
return (
<MenuPrimitive.Popup
className={cn(
"w-48 origin-(--transform-origin) rounded-xl bg-gray-50 p-1 shadow-custom-3 outline-hidden transition-[transform,scale,opacity] data-starting-style:scale-98 data-starting-style:opacity-0",
"w-48 origin-center rounded-xl bg-gray-50 p-1 shadow-custom-3 outline-hidden transition-[transform,scale,opacity] data-ending-style:scale-95 data-ending-style:opacity-0 data-open:origin-(--transform-origin) data-starting-style:scale-95 data-starting-style:opacity-0 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=left]:data-starting-style:translate-x-1 data-[side=right]:data-starting-style:-translate-x-1 data-[side=top]:data-starting-style:translate-y-1",
className,
)}
{...props}
Expand Down
54 changes: 54 additions & 0 deletions src/components/ui/recollect/popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { Popover as PopoverPrimitive } from "@base-ui/react/popover";

import { cn } from "@/utils/tailwind-merge";

function Root({ ...props }: PopoverPrimitive.Root.Props) {
return <PopoverPrimitive.Root {...props} />;
}

function Trigger({ className, ...props }: PopoverPrimitive.Trigger.Props) {
return <PopoverPrimitive.Trigger className={cn(className)} {...props} />;
}

function Portal(props: PopoverPrimitive.Portal.Props) {
return <PopoverPrimitive.Portal {...props} />;
}

function Positioner({
className,
sideOffset = 1,
...props
}: PopoverPrimitive.Positioner.Props) {
return (
<PopoverPrimitive.Positioner
className={cn("z-51", className)}
sideOffset={sideOffset}
{...props}
/>
);
}

function Popup({ className, ...props }: PopoverPrimitive.Popup.Props) {
return (
<PopoverPrimitive.Popup
className={cn(
"origin-center rounded-xl bg-gray-50 p-1 shadow-custom-3 outline-hidden transition-[transform,scale,opacity] data-ending-style:scale-95 data-ending-style:opacity-0 data-open:origin-(--transform-origin) data-starting-style:scale-95 data-starting-style:opacity-0 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=left]:data-starting-style:translate-x-1 data-[side=right]:data-starting-style:-translate-x-1 data-[side=top]:data-starting-style:translate-y-1",
className,
)}
{...props}
/>
);
}

const Backdrop = PopoverPrimitive.Backdrop;

export const Popover = {
Backdrop,
Popup,
Portal,
Positioner,
Root,
Trigger,
};
2 changes: 1 addition & 1 deletion src/components/ui/recollect/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function Popup({ className, children, ...props }: SelectPrimitive.Popup.Props) {
return (
<SelectPrimitive.Popup
className={cn(
"origin-(--transform-origin) rounded-xl bg-gray-50 p-1 shadow-custom-3 ring-1 ring-black/5 outline-hidden transition-[transform,scale,opacity] data-starting-style:scale-98 data-starting-style:opacity-0",
"origin-center rounded-xl bg-gray-50 p-1 shadow-custom-3 ring-1 ring-black/5 outline-hidden transition-[transform,scale,opacity] data-ending-style:scale-95 data-ending-style:opacity-0 data-open:origin-(--transform-origin) data-starting-style:scale-95 data-starting-style:opacity-0 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-starting-style:translate-y-1",
className,
)}
{...props}
Expand Down
Loading