Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- 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.
104 lines
2.4 KiB
TypeScript
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>
|
|
)
|
|
}
|