21 lines
6.4 KiB
JSON
21 lines
6.4 KiB
JSON
{
|
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
"name": "user-presence-avatar",
|
|
"type": "registry:ui",
|
|
"title": "User Presence Avatar",
|
|
"description": "User Presence Avatar Component",
|
|
"dependencies": [
|
|
"motion"
|
|
],
|
|
"registryDependencies": [
|
|
"https://animate-ui.com/r/avatar-group"
|
|
],
|
|
"files": [
|
|
{
|
|
"path": "registry/ui-elements/user-presence-avatar/index.tsx",
|
|
"content": "'use client';\n\nimport * as React from 'react';\nimport { motion, LayoutGroup } from 'motion/react';\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from '@/components/ui/avatar';\nimport {\n AvatarGroup,\n AvatarGroupTooltip,\n} from '@/components/animate-ui/components/avatar-group';\nimport { cn } from '@/lib/utils';\n\nconst USERS = [\n {\n id: 1,\n src: 'https://pbs.twimg.com/profile_images/1897311929028255744/otxpL-ke_400x400.jpg',\n fallback: 'AK',\n tooltip: 'Arhamkhnz',\n online: true,\n },\n {\n id: 2,\n src: 'https://pbs.twimg.com/profile_images/1909615404789506048/MTqvRsjo_400x400.jpg',\n fallback: 'SK',\n tooltip: 'Skyleen',\n online: true,\n },\n {\n id: 3,\n src: 'https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg',\n fallback: 'CN',\n tooltip: 'Shadcn',\n online: true,\n },\n {\n id: 4,\n src: 'https://pbs.twimg.com/profile_images/1677042510839857154/Kq4tpySA_400x400.jpg',\n fallback: 'AW',\n tooltip: 'Adam Wathan',\n online: false,\n },\n {\n id: 5,\n src: 'https://pbs.twimg.com/profile_images/1783856060249595904/8TfcCN0r_400x400.jpg',\n fallback: 'GR',\n tooltip: 'Guillermo Rauch',\n online: false,\n },\n {\n id: 6,\n src: 'https://pbs.twimg.com/profile_images/1534700564810018816/anAuSfkp_400x400.jpg',\n fallback: 'JH',\n tooltip: 'Jhey',\n online: false,\n },\n];\n\nconst AVATAR_MOTION_TRANSITION = {\n type: 'spring',\n stiffness: 200,\n damping: 25,\n} as const;\n\nconst GROUP_CONTAINER_TRANSITION = {\n type: 'spring',\n stiffness: 150,\n damping: 20,\n} as const;\n\nfunction UserPresenceAvatar() {\n const [users, setUsers] = React.useState(USERS);\n const [togglingGroup, setTogglingGroup] = React.useState<\n 'online' | 'offline' | null\n >(null);\n\n const online = users.filter((u) => u.online);\n const offline = users.filter((u) => !u.online);\n\n const toggleStatus = (id: number) => {\n const user = users.find((u) => u.id === id);\n if (!user) return;\n\n setTogglingGroup(user.online ? 'online' : 'offline');\n setUsers((prev) => {\n const idx = prev.findIndex((u) => u.id === id);\n if (idx === -1) return prev;\n const updated = [...prev];\n const target = updated[idx];\n if (!target) return prev;\n updated.splice(idx, 1);\n updated.push({ ...target, online: !target.online });\n return updated;\n });\n // Reset group z-index after the animation duration (keep in sync with animation timing)\n setTimeout(() => setTogglingGroup(null), 500);\n };\n\n return (\n <div className=\"flex items-center gap-5\">\n <LayoutGroup>\n {online.length > 0 && (\n <motion.div\n layout\n className={cn(\n 'bg-neutral-300 dark:bg-neutral-700 p-0.5 rounded-full',\n togglingGroup === 'online' ? 'z-5' : 'z-10',\n )}\n transition={GROUP_CONTAINER_TRANSITION}\n >\n <AvatarGroup\n key={online.map((u) => u.id).join('_') + '-online'}\n translate=\"0\"\n className=\"h-12 -space-x-3\"\n tooltipProps={{ side: 'top', sideOffset: 14 }}\n >\n {online.map((user) => (\n <motion.div\n key={user.id}\n layoutId={`avatar-${user.id}`}\n className=\"cursor-pointer\"\n onClick={() => toggleStatus(user.id)}\n animate={{\n filter: 'grayscale(0)',\n scale: 1,\n }}\n transition={AVATAR_MOTION_TRANSITION}\n title=\"Click to go offline\"\n initial={false}\n >\n <Avatar className=\"size-12 border-3 border-neutral-300 dark:border-neutral-700\">\n <AvatarImage src={user.src} />\n <AvatarFallback>{user.fallback}</AvatarFallback>\n <AvatarGroupTooltip>\n <p>{user.tooltip}</p>\n </AvatarGroupTooltip>\n </Avatar>\n </motion.div>\n ))}\n </AvatarGroup>\n </motion.div>\n )}\n\n {offline.length > 0 && (\n <motion.div\n layout\n className={cn(\n 'bg-neutral-300 dark:bg-neutral-700 p-0.5 rounded-full',\n togglingGroup === 'offline' ? 'z-5' : 'z-10',\n )}\n transition={GROUP_CONTAINER_TRANSITION}\n >\n <AvatarGroup\n key={offline.map((u) => u.id).join('_') + '-offline'}\n translate=\"0\"\n className=\"h-12 -space-x-3\"\n tooltipProps={{ side: 'top', sideOffset: 14 }}\n >\n {offline.map((user) => (\n <motion.div\n key={user.id}\n layoutId={`avatar-${user.id}`}\n className=\"cursor-pointer\"\n onClick={() => toggleStatus(user.id)}\n animate={{\n filter: 'grayscale(1)',\n scale: 1,\n }}\n transition={AVATAR_MOTION_TRANSITION}\n title=\"Click to go online\"\n initial={false}\n >\n <Avatar className=\"size-12 border-3 border-neutral-300 dark:border-neutral-700\">\n <AvatarImage src={user.src} />\n <AvatarFallback>{user.fallback}</AvatarFallback>\n <AvatarGroupTooltip>\n <p>{user.tooltip}</p>\n </AvatarGroupTooltip>\n </Avatar>\n </motion.div>\n ))}\n </AvatarGroup>\n </motion.div>\n )}\n </LayoutGroup>\n </div>\n );\n}\n\nexport { UserPresenceAvatar };\n",
|
|
"type": "registry:ui",
|
|
"target": "components/animate-ui/ui-elements/user-presence-avatar.tsx"
|
|
}
|
|
]
|
|
} |