ultisuite-client/components/gmail/contacts-page/discovery-cards-masonry.tsx
R3D347HR4Y 07d57f13a8
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Add Contact Avatar Features and Improve UI Components
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views.
- Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts.
- Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface.
- Added loading and error states in ContactsListView for better user feedback during data fetching.
- Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list.
- Updated package.json to include @formkit/auto-animate for improved UI animations.
2026-06-06 20:26:51 +02:00

104 lines
2.4 KiB
TypeScript

"use client"
import {
Children,
isValidElement,
useMemo,
type ReactNode,
type RefObject,
} from "react"
import { useAutoAnimate } from "@formkit/auto-animate/react"
import {
CONTACTS_DISCOVERY_CARD_MASONRY_ITEM_ENTER_CLASS,
CONTACTS_DISCOVERY_CARD_MASONRY_ROOT_CLASS,
CONTACTS_DISCOVERY_CARD_MASONRY_SENTINEL_CLASS,
} from "@/lib/contacts-chrome-classes"
import { useEnteringItemKeys } from "@/components/gmail/contacts-page/use-entering-item-keys"
import { cn } from "@/lib/utils"
interface DiscoveryCardsMasonryProps {
children: ReactNode
className?: string
footer?: ReactNode
animateEnter?: boolean
}
function childKey(child: ReactNode, index: number): string {
if (isValidElement(child) && child.key != null) {
return String(child.key)
}
return String(index)
}
/** Une seule liste DOM — grille responsive 1 col / 2 cols lg+. */
export function DiscoveryCardsMasonry({
children,
className,
footer,
animateEnter = true,
}: DiscoveryCardsMasonryProps) {
const items = Children.toArray(children)
const enteringKeys = useEnteringItemKeys(items)
const [gridRef] = useAutoAnimate({ duration: 140, easing: "ease-out" })
const renderedItems = useMemo(
() =>
items.map((child, index) => {
const key = childKey(child, index)
return (
<div
key={key}
className={cn(
"min-w-0",
animateEnter && enteringKeys.has(key) && CONTACTS_DISCOVERY_CARD_MASONRY_ITEM_ENTER_CLASS,
)}
>
{child}
</div>
)
}),
[items, enteringKeys, animateEnter],
)
return (
<div className={cn(CONTACTS_DISCOVERY_CARD_MASONRY_ROOT_CLASS, className)}>
<div
ref={gridRef}
className="grid grid-cols-1 items-start gap-5 lg:grid-cols-2"
>
{renderedItems}
</div>
{footer}
</div>
)
}
export function DiscoveryCardsMasonryItem({
children,
className,
}: {
children: ReactNode
className?: string
}) {
return <div className={cn("min-w-0", className)}>{children}</div>
}
export function DiscoveryCardsMasonrySentinel({
children,
className,
sentinelRef,
}: {
children?: ReactNode
className?: string
sentinelRef?: RefObject<HTMLDivElement | null>
}) {
return (
<div
ref={sentinelRef}
className={cn(CONTACTS_DISCOVERY_CARD_MASONRY_SENTINEL_CLASS, className)}
>
{children}
</div>
)
}