Fortura/apps/www/public/r/avatar-group-mask.json
2025-08-20 04:12:49 -06:00

18 lines
6.3 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "avatar-group-mask",
"type": "registry:ui",
"title": "Avatar Group Mask",
"description": "An animated avatar group that displays overlapping user images with a mask effect and smoothly shifts each avatar forward on hover to highlight it.",
"registryDependencies": [
"https://animate-ui.com/r/tooltip"
],
"files": [
{
"path": "registry/components/avatar-group-mask/index.tsx",
"content": "'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n type TooltipProps,\n type TooltipContentProps,\n} from '@/components/animate-ui/components/tooltip';\n\ntype Align = 'start' | 'center' | 'end';\n\ntype AvatarProps = TooltipProps & {\n children: React.ReactNode;\n align?: Align;\n invertOverlap?: boolean;\n};\n\nfunction AvatarContainer({\n children,\n align,\n invertOverlap,\n ...props\n}: AvatarProps) {\n return (\n <Tooltip {...props}>\n <TooltipTrigger>\n <span\n data-slot=\"avatar-container\"\n className={cn(\n align === 'start'\n ? 'items-start'\n : align === 'center'\n ? 'items-center'\n : 'items-end',\n 'relative grid w-[var(--avatar-size)] aspect-[1/calc(1+var(--avatar-mask-ratio))]',\n '[&_[data-slot=avatar]]:size-[var(--avatar-size)] [&_[data-slot=avatar]]:rounded-full',\n invertOverlap\n ? cn(\n '[&:not(:first-of-type)]:[--circle:calc(((var(--avatar-border)*2)+var(--avatar-size))*0.5)]',\n '[&:not(:first-of-type)]:mask-[radial-gradient(var(--circle)_var(--circle)_at_calc(var(--circle)-var(--avatar-column-size)-var(--avatar-border))_50%_,#0000_calc(var(--circle)-0.5px),#fff_var(--circle))]',\n '[&:not(:first-of-type)]:mask-size-[100%_100%]',\n '[&:not(:first-of-type)]:mask-position-[0_calc(var(--avatar-size)*var(--avatar-mask-base))]',\n '[&:not(:first-of-type)]:transition-[mask-position] [&:not(:first-of-type)]:duration-300 [&:not(:first-of-type)]:ease-in-out',\n '[&:hover+&]:mask-position-[0_calc(var(--avatar-size)_-_calc(var(--avatar-size)*(var(--avatar-mask-factor)+var(--avatar-mask-offset))))]',\n )\n : cn(\n '[&:not(:last-of-type)]:[--circle:calc(((var(--avatar-border)*2)+var(--avatar-size))*0.5)]',\n '[&:not(:last-of-type)]:mask-[radial-gradient(var(--circle)_var(--circle)_at_calc(var(--circle)+var(--avatar-column-size)-var(--avatar-border))_50%_,#0000_calc(var(--circle)-0.5px),#fff_var(--circle))]',\n '[&:not(:last-of-type)]:mask-size-[100%_100%]',\n '[&:not(:last-of-type)]:mask-position-[0_calc(var(--avatar-size)*var(--avatar-mask-base))]',\n '[&:not(:last-of-type)]:transition-[mask-position] [&:not(:last-of-type)]:duration-300 [&:not(:last-of-type)]:ease-in-out',\n '[&:has(+&:hover)]:mask-position-[0_calc(var(--avatar-size)_-_calc(var(--avatar-size)*(var(--avatar-mask-factor)+var(--avatar-mask-offset))))]',\n ),\n '[&>span]:transition-[translate] [&>span]:duration-300 [&>span]:ease-in-out',\n '[&:hover_span:first-of-type]:translate-y-[var(--avatar-translate-pct)]',\n )}\n >\n {children}\n </span>\n </TooltipTrigger>\n </Tooltip>\n );\n}\n\ntype AvatarGroupTooltipProps = TooltipContentProps;\n\nfunction AvatarGroupTooltip(props: AvatarGroupTooltipProps) {\n return <TooltipContent {...props} />;\n}\n\ntype AvatarGroupProps = Omit<React.ComponentProps<'div'>, 'translate'> & {\n children: React.ReactElement[];\n invertOverlap?: boolean;\n translate?: number;\n size?: string | number;\n border?: string | number;\n columnSize?: string | number;\n align?: Align;\n tooltipProps?: Omit<TooltipProps, 'children'>;\n};\n\nfunction AvatarGroup({\n ref,\n children,\n className,\n invertOverlap = false,\n size = '43px',\n border = '3px',\n columnSize = '37px',\n align = 'end',\n translate = -30,\n tooltipProps = { side: 'top', sideOffset: 12 },\n ...props\n}: AvatarGroupProps) {\n const maskRatio = Math.abs(translate / 100);\n const alignOffset =\n align === 'start' ? 0 : align === 'center' ? maskRatio / 2 : maskRatio;\n const maskBase = alignOffset - maskRatio / 2;\n const maskFactor = 1 - alignOffset + maskRatio / 2;\n\n return (\n <TooltipProvider openDelay={0} closeDelay={0}>\n <div\n ref={ref}\n data-slot=\"avatar-group\"\n style={\n {\n '--avatar-size': size,\n '--avatar-border': border,\n '--avatar-column-size': columnSize,\n '--avatar-translate-pct': `${translate}%`,\n '--avatar-mask-offset': -(translate / 100),\n '--avatar-mask-ratio': maskRatio,\n '--avatar-mask-base': maskBase,\n '--avatar-mask-factor': maskFactor,\n '--avatar-columns': React.Children.count(children),\n } as React.CSSProperties\n }\n className=\"h-[var(--avatar-size)] w-[calc(var(--avatar-column-size)*(var(--avatar-columns))+calc(var(--avatar-size)-var(--avatar-column-size)))]\"\n >\n <span\n className={cn(\n 'grid h-[var(--avatar-size)] grid-cols-[repeat(var(--avatar-columns),var(--avatar-column-size))]',\n align === 'start'\n ? 'content-start'\n : align === 'center'\n ? 'content-center'\n : 'content-end',\n className,\n )}\n {...props}\n >\n {children?.map((child, index) => (\n <AvatarContainer\n key={index}\n invertOverlap={invertOverlap}\n {...tooltipProps}\n align={align}\n >\n {child}\n </AvatarContainer>\n ))}\n </span>\n </div>\n </TooltipProvider>\n );\n}\n\nexport {\n AvatarGroup,\n AvatarGroupTooltip,\n type AvatarGroupProps,\n type AvatarGroupTooltipProps,\n};\n",
"type": "registry:ui",
"target": "components/animate-ui/components/avatar-group-mask.tsx"
}
]
}