Collaborative Canvas
React collaborative canvas with real-time multiplayer editing. Build Figma-style interfaces with cursors, avatars, and live collaboration using TypeScript and shadcn/ui.
Powered by
Loading component...
'use client';import { AvatarGroup } from '@/components/ui/shadcn-io/avatar-group';import { Cursor, CursorBody, CursorMessage, CursorName, CursorPointer,} from '@/components/ui/shadcn-io/cursor';import { cn } from '@/lib/utils';import Image from 'next/image';import { useEffect, useState } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';const users = [ { id: 1, name: 'Hayden Bleasel', avatar: 'https://github.com/haydenbleasel.png', }, { id: 2, name: 'shadcn', avatar: 'https://github.com/shadcn.png', message: 'Can we adjust the color?', }, { id: 3, name: 'Lee Robinson', avatar: 'https://github.com/leerob.png', },];const colors = [ { foreground: 'text-emerald-800', background: 'bg-emerald-50', }, { foreground: 'text-rose-800', background: 'bg-rose-50', }, { foreground: 'text-sky-800', background: 'bg-sky-50', },];// Helper function to generate random positionconst getRandomPosition = () => ({ x: Math.floor(Math.random() * 80) + 10, // Keep within 10-90% range y: Math.floor(Math.random() * 80) + 10, // Keep within 10-90% range});const Example = () => { const [user1Position, setUser1Position] = useState({ x: 10, y: 8, }); const [user2Position, setUser2Position] = useState({ x: 30, y: 40, }); const [user3Position, setUser3Position] = useState({ x: 70, y: 50, }); // Store all user positions in a single array for easier access const userPositions = [user1Position, user2Position, user3Position]; // Create separate useEffects for each user to move at different intervals useEffect(() => { const interval = setInterval( () => { setUser1Position(getRandomPosition()); }, Math.random() * 3000 + 2000 ); // Random interval between 2-5 seconds return () => clearInterval(interval); }, []); useEffect(() => { const interval = setInterval( () => { setUser2Position(getRandomPosition()); }, Math.random() * 4000 + 3000 ); // Random interval between 3-7 seconds return () => clearInterval(interval); }, []); useEffect(() => { const interval = setInterval( () => { setUser3Position(getRandomPosition()); }, Math.random() * 2500 + 1500 ); // Random interval between 1.5-4 seconds return () => clearInterval(interval); }, []); // Assign positions to users const usersWithPositions = users.map((user, index) => ({ ...user, position: userPositions[index], })); return ( <div className="relative aspect-[4/3] size-full bg-[radial-gradient(var(--color-secondary),transparent_1px)]" style={{ backgroundSize: '16px 16px' }}> <div className="absolute top-8 right-8"> <AvatarGroup variant="stack" animate size={32}> {usersWithPositions.map((user) => ( <Avatar key={user.id}> <AvatarImage className="mt-0 mb-0" src={user.avatar} /> <AvatarFallback>{user.name.slice(0, 2)}</AvatarFallback> </Avatar> ))} </AvatarGroup> </div> {usersWithPositions.map((user, index) => ( <Cursor className="absolute transition-all duration-1000" key={user.id} style={{ top: `${user.position.y}%`, left: `${user.position.x}%`, }} > <CursorPointer className={cn(colors[index % colors.length].foreground)} /> <CursorBody className={cn( colors[index % colors.length].background, colors[index % colors.length].foreground, 'gap-1 px-3 py-2' )} > <div className="flex items-center gap-2 !opacity-100"> <Image alt={user.name} className="mt-0 mb-0 size-4 rounded-full" height={16} src={user.avatar} unoptimized width={16} /> <CursorName>{user.name}</CursorName> </div> {user.message && <CursorMessage>{user.message}</CursorMessage>} </CursorBody> </Cursor> ))} </div> );};export default Example;
'use client';import * as React from 'react';import { motion, type Transition } from 'motion/react';import { Children, type ReactNode } from 'react';import { cn } from '@/lib/utils';import { TooltipContent, TooltipProvider, TooltipTrigger,} from '@/components/ui/tooltip';import * as TooltipPrimitive from '@radix-ui/react-tooltip';// Define types based on componentstype TooltipContentProps = React.ComponentProps<typeof TooltipContent>;// Avatar Container for motion-based interactionstype AvatarMotionProps = { children: React.ReactNode; zIndex: number; translate: string | number; transition: Transition; tooltipContent?: React.ReactNode; tooltipProps?: Partial<TooltipContentProps>;};function AvatarMotionContainer({ children, zIndex, translate, transition, tooltipContent, tooltipProps,}: AvatarMotionProps) { return ( <TooltipPrimitive.Root> <TooltipTrigger> <motion.div data-slot="avatar-container" className="relative" style={{ zIndex }} whileHover={{ y: translate, }} transition={transition} > {children} </motion.div> </TooltipTrigger> {tooltipContent && ( <AvatarGroupTooltip {...tooltipProps}> {tooltipContent} </AvatarGroupTooltip> )} </TooltipPrimitive.Root> );}// Avatar Container for CSS-based interactionstype AvatarCSSProps = { children: React.ReactNode; zIndex: number; tooltipContent?: React.ReactNode; tooltipProps?: Partial<TooltipContentProps>;};function AvatarCSSContainer({ children, zIndex, tooltipContent, tooltipProps,}: AvatarCSSProps) { return ( <TooltipPrimitive.Root> <TooltipTrigger> <div data-slot="avatar-container" className="relative transition-transform duration-300 ease-out hover:-translate-y-2" style={{ zIndex }} > {children} </div> </TooltipTrigger> {tooltipContent && ( <AvatarGroupTooltip {...tooltipProps}> {tooltipContent} </AvatarGroupTooltip> )} </TooltipPrimitive.Root> );}// Avatar Container for stack variant with masktype AvatarStackItemProps = { children: React.ReactNode; index: number; size: number; className?: string;};function AvatarStackItem({ children, index, size, className }: AvatarStackItemProps) { return ( <div className={cn( 'size-full shrink-0 overflow-hidden rounded-full', '[&_[data-slot="avatar"]]:size-full', className )} style={{ width: size, height: size, maskImage: index ? `radial-gradient(circle ${size / 2}px at -${size / 4 + size / 10}px 50%, transparent 99%, white 100%)` : '', }} > {children} </div> );}type AvatarGroupTooltipProps = TooltipContentProps;function AvatarGroupTooltip(props: AvatarGroupTooltipProps) { return <TooltipContent {...props} />;}type AvatarGroupVariant = 'motion' | 'css' | 'stack';type AvatarGroupProps = Omit<React.ComponentProps<'div'>, 'translate'> & { children: React.ReactElement[]; variant?: AvatarGroupVariant; transition?: Transition; invertOverlap?: boolean; translate?: string | number; tooltipProps?: Partial<TooltipContentProps>; // Stack-specific props animate?: boolean; size?: number;};function AvatarGroup({ ref, children, className, variant = 'motion', transition = { type: 'spring', stiffness: 300, damping: 17 }, invertOverlap = false, translate = '-30%', tooltipProps = { side: 'top', sideOffset: 24 }, animate = false, size = 40, ...props}: AvatarGroupProps) { // Stack variant if (variant === 'stack') { return ( <div ref={ref} className={cn( '-space-x-1 flex items-center', animate && 'hover:space-x-0 [&>*]:transition-all', className )} {...props} > {Children.map(children, (child, index) => { if (!child) { return null; } return ( <AvatarStackItem key={index} index={index} size={size} className={className} > {child} </AvatarStackItem> ); })} </div> ); } // Motion and CSS variants with tooltips return ( <TooltipProvider delayDuration={0}> <div ref={ref} data-slot="avatar-group" className={cn( 'flex items-center', variant === 'css' && '-space-x-3', variant === 'motion' && 'flex-row -space-x-2 h-8', className )} {...props} > {children?.map((child, index) => { const zIndex = invertOverlap ? React.Children.count(children) - index : index; if (variant === 'motion') { return ( <AvatarMotionContainer key={index} zIndex={zIndex} translate={translate} transition={transition} tooltipProps={tooltipProps} > {child} </AvatarMotionContainer> ); } return ( <AvatarCSSContainer key={index} zIndex={zIndex} tooltipProps={tooltipProps} > {child} </AvatarCSSContainer> ); })} </div> </TooltipProvider> );}export { AvatarGroup, AvatarGroupTooltip, type AvatarGroupProps, type AvatarGroupTooltipProps, type AvatarGroupVariant,};
React AI Chatbot Interface
React AI chatbot with streaming responses and reasoning display. Complete ChatGPT-style interface with model selection, sources, and TypeScript support using shadcn/ui.
Roadmap
React roadmap components with timeline visualization. Build product roadmaps, project timelines, and feature releases with Gantt charts, TypeScript, and shadcn/ui.