@rencar-dev/feature-modules-public 0.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -170
- package/dist/presence/index.cjs +230 -2
- package/dist/presence/index.cjs.map +1 -1
- package/dist/presence/index.d.cts +136 -1
- package/dist/presence/index.d.ts +136 -1
- package/dist/presence/index.js +228 -2
- package/dist/presence/index.js.map +1 -1
- package/dist/presence/styles.css +240 -0
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/presence/index.ts","../../src/presence/core/socket.ts","../../src/presence/hooks/usePresence.ts","../../src/presence/hooks/usePresenceWatch.ts","../../src/presence/headless/usePresenceAvatarsState.ts","../../src/presence/headless/usePresenceFloatingState.ts"],"sourcesContent":["// Core exports\nexport {\n initPresenceSocket,\n getPresenceSocket,\n disconnectPresenceSocket,\n isPresenceSocketInitialized,\n} from './core/socket';\n\nexport type {\n PresenceUser,\n PresenceConfig,\n UsePresenceOptions,\n UsePresenceWatchOptions,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n} from './core/types';\n\n// Hooks exports\nexport { usePresence } from './hooks/usePresence';\nexport { usePresenceWatch } from './hooks/usePresenceWatch';\n\n// Headless state exports\nexport { usePresenceAvatarsState } from './headless/usePresenceAvatarsState';\nexport { usePresenceFloatingState } from './headless/usePresenceFloatingState';\n","import { io, Socket } from 'socket.io-client';\nimport type { PresenceConfig } from './types';\n\nlet config: PresenceConfig | null = null;\nlet sharedSocket: Socket | null = null;\n\n/**\n * Initialize the presence socket with configuration\n * Must be called before using any presence hooks\n */\nexport function initPresenceSocket(presenceConfig: PresenceConfig): void {\n config = presenceConfig;\n\n // If socket already exists, disconnect and recreate\n if (sharedSocket) {\n sharedSocket.disconnect();\n sharedSocket = null;\n }\n}\n\n/**\n * Get the shared socket instance\n * @throws Error if socket is not initialized\n */\nexport function getPresenceSocket(): Socket {\n if (!config) {\n throw new Error(\n '[presence-module] Socket not initialized. Call initPresenceSocket() first.',\n );\n }\n\n if (sharedSocket && sharedSocket.connected) {\n return sharedSocket;\n }\n\n if (!sharedSocket) {\n sharedSocket = io(config.url, {\n transports: config.transports ?? ['websocket'],\n });\n } else if (!sharedSocket.connected) {\n sharedSocket.connect();\n }\n\n return sharedSocket;\n}\n\n/**\n * Disconnect and cleanup the socket connection\n */\nexport function disconnectPresenceSocket(): void {\n if (sharedSocket) {\n sharedSocket.disconnect();\n sharedSocket = null;\n }\n}\n\n/**\n * Check if socket is initialized\n */\nexport function isPresenceSocketInitialized(): boolean {\n return config !== null;\n}\n","import { useEffect, useState, useCallback } from 'react';\nimport { getPresenceSocket } from '../core/socket';\nimport type { PresenceUser, UsePresenceOptions } from '../core/types';\n\nconst DEFAULT_HEARTBEAT_INTERVAL = 10 * 60 * 1000; // 10 minutes\n\n/**\n * Hook to join a presence room and track users in that room\n *\n * @param options - Configuration options\n * @returns Array of users currently in the room\n *\n * @example\n * ```tsx\n * const users = usePresence({\n * roomId: 'my-app:page-123',\n * currentUser: { userId: '1', name: 'John' },\n * });\n * ```\n */\nexport function usePresence(options: UsePresenceOptions): PresenceUser[] {\n const {\n roomId,\n currentUser,\n enabled = true,\n heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,\n } = options;\n\n const [users, setUsers] = useState<PresenceUser[]>([]);\n\n // Normalize users from server response\n const normalizeUsers = useCallback((incoming: unknown): PresenceUser[] => {\n if (Array.isArray(incoming)) {\n return incoming as PresenceUser[];\n }\n if (\n incoming &&\n typeof incoming === 'object' &&\n Array.isArray((incoming as { users?: unknown }).users)\n ) {\n return (incoming as { users: unknown[] }).users as PresenceUser[];\n }\n return [];\n }, []);\n\n // Clear users when disabled\n useEffect(() => {\n if (!enabled) {\n setUsers([]);\n }\n }, [enabled]);\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const socket = getPresenceSocket();\n\n const joinRoom = () => {\n socket.emit('presence:join', { roomId, user: currentUser });\n };\n\n // Join immediately if connected, otherwise wait for connect event\n if (socket.connected) {\n joinRoom();\n }\n\n socket.on('connect', joinRoom);\n\n // Heartbeat to extend TTL\n const heartbeat = () => {\n socket.emit('presence:heartbeat', { roomId });\n };\n heartbeat();\n const heartbeatIntervalId = window.setInterval(\n heartbeat,\n heartbeatInterval,\n );\n\n // Handle presence updates from server\n const handlePresenceUpdate = (incoming: unknown) => {\n setUsers(normalizeUsers(incoming));\n };\n socket.on('presence:update', handlePresenceUpdate);\n\n // Notify server when tab/window closes\n const handleBeforeUnload = () => {\n socket.emit('presence:leave', { roomId, userId: currentUser.userId });\n };\n window.addEventListener('beforeunload', handleBeforeUnload);\n\n return () => {\n socket.off('connect', joinRoom);\n socket.off('presence:update', handlePresenceUpdate);\n window.removeEventListener('beforeunload', handleBeforeUnload);\n window.clearInterval(heartbeatIntervalId);\n socket.emit('presence:leave', { roomId, userId: currentUser.userId });\n };\n }, [\n roomId,\n currentUser.userId,\n currentUser.name,\n enabled,\n heartbeatInterval,\n normalizeUsers,\n ]);\n\n return users;\n}\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport { getPresenceSocket } from '../core/socket';\nimport type { PresenceUser, UsePresenceWatchOptions } from '../core/types';\n\ntype SnapshotPayload = {\n rooms?: Record<string, unknown>;\n};\n\ntype UpdatePayload = {\n roomId?: string;\n users?: unknown;\n};\n\nfunction normalizeUsers(value: unknown): PresenceUser[] {\n if (Array.isArray(value)) return value as PresenceUser[];\n if (\n value &&\n typeof value === 'object' &&\n Array.isArray((value as { users?: unknown }).users)\n ) {\n return (value as { users: unknown[] }).users as PresenceUser[];\n }\n return [];\n}\n\n/**\n * Hook to watch multiple rooms without joining them\n * (read-only observation, current user is not added to the rooms)\n *\n * @param options - Configuration options\n * @returns Map of roomId to array of users in that room\n *\n * @example\n * ```tsx\n * const roomsUsers = usePresenceWatch({\n * roomIds: ['room-1', 'room-2', 'room-3'],\n * });\n * // roomsUsers = { 'room-1': [...], 'room-2': [...], ... }\n * ```\n */\nexport function usePresenceWatch(\n options: UsePresenceWatchOptions,\n): Record<string, PresenceUser[]> {\n const { roomIds, enabled = true } = options;\n\n const [roomsUsers, setRoomsUsers] = useState<Record<string, PresenceUser[]>>(\n {},\n );\n\n const normalizedRoomIds = useMemo(() => {\n const uniq = Array.from(new Set((roomIds ?? []).filter(Boolean)));\n return uniq;\n }, [roomIds]);\n\n const watchedRef = useRef<Set<string>>(new Set());\n\n // Handle watch/unwatch based on roomIds changes\n useEffect(() => {\n if (!enabled) {\n setRoomsUsers({});\n watchedRef.current = new Set();\n return;\n }\n\n const socket = getPresenceSocket();\n\n const toWatch = normalizedRoomIds.filter((r) => !watchedRef.current.has(r));\n const toUnwatch = Array.from(watchedRef.current).filter(\n (r) => !normalizedRoomIds.includes(r),\n );\n\n if (toWatch.length > 0) {\n socket.emit('presence:watch', { roomIds: toWatch });\n for (const r of toWatch) watchedRef.current.add(r);\n }\n\n if (toUnwatch.length > 0) {\n socket.emit('presence:unwatch', { roomIds: toUnwatch });\n for (const r of toUnwatch) watchedRef.current.delete(r);\n setRoomsUsers((prev) => {\n const next = { ...prev };\n for (const r of toUnwatch) delete next[r];\n return next;\n });\n }\n }, [enabled, normalizedRoomIds]);\n\n // Handle socket events\n useEffect(() => {\n if (!enabled) return;\n\n const socket = getPresenceSocket();\n\n const handleSnapshot = (payload: SnapshotPayload) => {\n const rooms = payload?.rooms ?? {};\n setRoomsUsers((prev) => {\n const next = { ...prev };\n for (const [roomId, users] of Object.entries(rooms)) {\n next[roomId] = normalizeUsers(users);\n }\n return next;\n });\n };\n\n const handleUpdate = (payload: unknown) => {\n if (!payload || typeof payload !== 'object') return;\n const { roomId, users } = payload as UpdatePayload;\n if (!roomId) return;\n if (!watchedRef.current.has(roomId)) return;\n setRoomsUsers((prev) => ({ ...prev, [roomId]: normalizeUsers(users) }));\n };\n\n socket.on('presence:snapshot', handleSnapshot);\n socket.on('presence:update', handleUpdate);\n\n // Re-watch on reconnect\n const handleConnect = () => {\n const ids = Array.from(watchedRef.current);\n if (ids.length > 0) socket.emit('presence:watch', { roomIds: ids });\n };\n socket.on('connect', handleConnect);\n\n return () => {\n socket.off('presence:snapshot', handleSnapshot);\n socket.off('presence:update', handleUpdate);\n socket.off('connect', handleConnect);\n\n const ids = Array.from(watchedRef.current);\n if (ids.length > 0) socket.emit('presence:unwatch', { roomIds: ids });\n watchedRef.current = new Set();\n };\n }, [enabled]);\n\n return roomsUsers;\n}\n","import { useMemo, useState, useCallback } from 'react';\nimport type {\n PresenceUser,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n} from '../core/types';\n\n/**\n * Extract initial character from user name/id\n */\nfunction getInitialFromUser(user: PresenceUser): string {\n const base = (user?.name || user?.userId || '').trim();\n if (!base) return '?';\n const ch = base.charAt(0);\n return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;\n}\n\n/**\n * Headless hook for presence avatars UI state management\n *\n * This hook provides all the state and logic needed to build\n * a presence avatars component without any styling.\n *\n * @param options - Configuration options\n * @returns State and handlers for building presence avatars UI\n *\n * @example\n * ```tsx\n * const {\n * visibleUsers,\n * moreCount,\n * hoveredUser,\n * setHoveredIndex,\n * getInitial,\n * getZIndex,\n * } = usePresenceAvatarsState({ users, maxVisible: 3 });\n *\n * return (\n * <div>\n * {visibleUsers.map((user, idx) => (\n * <span\n * key={user.userId}\n * style={{ zIndex: getZIndex(idx, hoveredUser?.userId === user.userId) }}\n * onMouseEnter={() => setHoveredIndex(idx)}\n * onMouseLeave={() => setHoveredIndex(null)}\n * >\n * {getInitial(user)}\n * </span>\n * ))}\n * {moreCount > 0 && <span>+{moreCount}</span>}\n * </div>\n * );\n * ```\n */\nexport function usePresenceAvatarsState(\n options: UsePresenceAvatarsStateOptions,\n): UsePresenceAvatarsStateReturn {\n const { users, maxVisible = 3 } = options;\n\n const safeUsers = Array.isArray(users) ? users : [];\n\n const visibleUsers = useMemo(\n () => safeUsers.slice(0, maxVisible),\n [safeUsers, maxVisible],\n );\n\n const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);\n\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n const hoveredUser = hoveredIndex != null ? visibleUsers[hoveredIndex] : null;\n\n const baseTopZ = visibleUsers.length + 1;\n\n const getInitial = useCallback((user: PresenceUser): string => {\n return getInitialFromUser(user);\n }, []);\n\n const getZIndex = useCallback(\n (index: number, isHovered: boolean): number => {\n return isHovered ? 100 : baseTopZ - index;\n },\n [baseTopZ],\n );\n\n return {\n visibleUsers,\n moreCount,\n hoveredUser,\n hoveredIndex,\n setHoveredIndex,\n getInitial,\n getZIndex,\n };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n PresenceUser,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n} from \"../core/types\";\n\n/**\n * Headless hook for presence floating UI state management\n *\n * This hook provides all the state and logic needed to build\n * a draggable floating presence panel without any styling.\n *\n * @param options - Configuration options\n * @returns State and handlers for building floating presence UI\n *\n * @example\n * ```tsx\n * const {\n * containerRef,\n * inlineStyle,\n * isDragging,\n * visibleUsers,\n * moreCount,\n * hoveredUser,\n * tooltipTop,\n * onMouseDownHeader,\n * onAvatarEnter,\n * onAvatarLeave,\n * } = usePresenceFloatingState({ users, maxVisible: 8 });\n *\n * return (\n * <div ref={containerRef} style={inlineStyle} onMouseDown={onMouseDownHeader}>\n * <div className=\"header\">열람중 {users.length}</div>\n * <div className=\"body\">\n * {visibleUsers.map((user) => (\n * <span\n * key={user.userId}\n * onMouseEnter={(e) => onAvatarEnter(e, user)}\n * onMouseLeave={onAvatarLeave}\n * >\n * {user.name}\n * </span>\n * ))}\n * </div>\n * </div>\n * );\n * ```\n */\nexport function usePresenceFloatingState(\n options: UsePresenceFloatingStateOptions\n): UsePresenceFloatingStateReturn {\n const { users, maxVisible = 8, initialPosition = { top: 166 } } = options;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [dragging, setDragging] = useState(false);\n const [hasDragged, setHasDragged] = useState(false);\n const [dragOffset, setDragOffset] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [position, setPosition] = useState<{ top: number; left: number }>({\n top: initialPosition.top,\n left: 0,\n });\n const [hoveredUser, setHoveredUser] = useState<PresenceUser | null>(null);\n const [tooltipTop, setTooltipTop] = useState<number>(0);\n\n const safeUsers = Array.isArray(users) ? users : [];\n const visibleUsers = safeUsers.slice(0, maxVisible);\n const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);\n\n // Position adjustment after drag (resize handling)\n useEffect(() => {\n if (!hasDragged) return;\n\n const el = containerRef.current;\n if (!el) return;\n\n const adjustPosition = () => {\n const rect = el.getBoundingClientRect();\n const maxLeft = window.innerWidth - rect.width;\n const maxTop = window.innerHeight - rect.height - 8;\n setPosition((prev) => ({\n left: Math.min(Math.max(0, prev.left), Math.max(0, maxLeft)),\n top: Math.min(Math.max(8, prev.top), Math.max(8, maxTop)),\n }));\n };\n\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n resizeObserver.observe(el);\n\n window.addEventListener(\"resize\", adjustPosition);\n\n return () => {\n resizeObserver.disconnect();\n window.removeEventListener(\"resize\", adjustPosition);\n };\n }, [hasDragged]);\n\n // Helper to get coordinates from mouse or touch event\n const getEventCoordinates = useCallback(\n (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {\n if (\"touches\" in e && e.touches.length > 0) {\n return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };\n }\n if (\"changedTouches\" in e && e.changedTouches.length > 0) {\n return {\n clientX: e.changedTouches[0].clientX,\n clientY: e.changedTouches[0].clientY,\n };\n }\n if (\"clientX\" in e) {\n return { clientX: e.clientX, clientY: e.clientY };\n }\n return { clientX: 0, clientY: 0 };\n },\n []\n );\n\n const onMouseDownHeader = useCallback(\n (e: React.MouseEvent) => {\n const el = containerRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n\n // On first drag, switch to left-based positioning\n if (!hasDragged) {\n setHasDragged(true);\n setPosition({\n top: rect.top,\n left: rect.left,\n });\n }\n\n setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top });\n setDragging(true);\n e.preventDefault();\n },\n [hasDragged]\n );\n\n const onTouchStartHeader = useCallback(\n (e: React.TouchEvent) => {\n const el = containerRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n const { clientX, clientY } = getEventCoordinates(e);\n\n // On first drag, switch to left-based positioning\n if (!hasDragged) {\n setHasDragged(true);\n setPosition({\n top: rect.top,\n left: rect.left,\n });\n }\n\n setDragOffset({ x: clientX - rect.left, y: clientY - rect.top });\n setDragging(true);\n // Note: Don't prevent default to allow scrolling detection\n },\n [hasDragged, getEventCoordinates]\n );\n\n // Handle drag movement (mouse and touch)\n useEffect(() => {\n if (!dragging) return;\n\n const el = containerRef.current;\n\n const onMove = (e: MouseEvent | TouchEvent) => {\n if (!el) return;\n const rect = el.getBoundingClientRect();\n const { clientX, clientY } = getEventCoordinates(e);\n const newLeft = clientX - dragOffset.x;\n const newTop = clientY - dragOffset.y;\n const maxLeft = window.innerWidth - rect.width;\n const maxTop = window.innerHeight - rect.height - 8;\n setPosition({\n left: Math.min(Math.max(0, newLeft), Math.max(0, maxLeft)),\n top: Math.min(Math.max(8, newTop), Math.max(8, maxTop)),\n });\n\n // Prevent scrolling while dragging on touch\n if (\"touches\" in e) {\n e.preventDefault();\n }\n };\n\n const onUp = () => setDragging(false);\n\n // Mouse events\n window.addEventListener(\"mousemove\", onMove);\n window.addEventListener(\"mouseup\", onUp, { once: true });\n\n // Touch events\n window.addEventListener(\"touchmove\", onMove, { passive: false });\n window.addEventListener(\"touchend\", onUp, { once: true });\n window.addEventListener(\"touchcancel\", onUp, { once: true });\n\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n window.removeEventListener(\"touchmove\", onMove);\n window.removeEventListener(\"touchend\", onUp);\n window.removeEventListener(\"touchcancel\", onUp);\n };\n }, [dragging, dragOffset.x, dragOffset.y, getEventCoordinates]);\n\n const inlineStyle = useMemo<React.CSSProperties>(() => {\n if (hasDragged) {\n return {\n position: \"fixed\",\n top: position.top,\n left: position.left,\n right: \"auto\",\n };\n } else {\n return {\n position: \"fixed\",\n top: position.top,\n right: 0,\n left: \"auto\",\n };\n }\n }, [position.top, position.left, hasDragged]);\n\n const onAvatarEnter = useCallback(\n (e: React.MouseEvent, user: PresenceUser) => {\n const container = containerRef.current;\n if (!container) {\n setHoveredUser(user);\n setTooltipTop(0);\n return;\n }\n const containerRect = container.getBoundingClientRect();\n const targetRect = (\n e.currentTarget as HTMLElement\n ).getBoundingClientRect();\n const topWithinContainer = targetRect.top - containerRect.top;\n setHoveredUser(user);\n setTooltipTop(topWithinContainer);\n },\n []\n );\n\n const onAvatarLeave = useCallback(() => {\n setHoveredUser(null);\n }, []);\n\n return {\n containerRef,\n position,\n isDragging: dragging,\n hasDragged,\n inlineStyle,\n visibleUsers,\n moreCount,\n hoveredUser,\n tooltipTop,\n onMouseDownHeader,\n onTouchStartHeader,\n onAvatarEnter,\n onAvatarLeave,\n };\n}\n"],"mappings":"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","names":["normalizeUsers","import_react","import_react","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../../src/presence/index.ts","../../src/presence/core/socket.ts","../../src/presence/hooks/usePresence.ts","../../src/presence/hooks/usePresenceWatch.ts","../../src/presence/headless/usePresenceAvatarsState.ts","../../src/presence/headless/usePresenceFloatingState.ts","../../src/presence/components/PresenceAvatars.tsx","../../src/presence/components/PresenceFloating.tsx"],"sourcesContent":["// Core exports\nexport {\n initPresenceSocket,\n getPresenceSocket,\n disconnectPresenceSocket,\n isPresenceSocketInitialized,\n} from \"./core/socket\";\n\nexport type {\n PresenceUser,\n PresenceConfig,\n UsePresenceOptions,\n UsePresenceWatchOptions,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n // Component props types\n PresenceAvatarsProps,\n PresenceAvatarRenderProps,\n PresenceAvatarsTooltipRenderProps,\n PresenceFloatingProps,\n PresenceFloatingAvatarRenderProps,\n PresenceFloatingTooltipRenderProps,\n} from \"./core/types\";\n\n// Hooks exports\nexport { usePresence } from \"./hooks/usePresence\";\nexport { usePresenceWatch } from \"./hooks/usePresenceWatch\";\n\n// Headless state exports\nexport { usePresenceAvatarsState } from \"./headless/usePresenceAvatarsState\";\nexport { usePresenceFloatingState } from \"./headless/usePresenceFloatingState\";\n\n// Component exports\nexport { PresenceAvatars } from \"./components/PresenceAvatars\";\nexport { PresenceFloating } from \"./components/PresenceFloating\";\n","import { io, Socket } from 'socket.io-client';\nimport type { PresenceConfig } from './types';\n\nlet config: PresenceConfig | null = null;\nlet sharedSocket: Socket | null = null;\n\n/**\n * Initialize the presence socket with configuration\n * Must be called before using any presence hooks\n */\nexport function initPresenceSocket(presenceConfig: PresenceConfig): void {\n config = presenceConfig;\n\n // If socket already exists, disconnect and recreate\n if (sharedSocket) {\n sharedSocket.disconnect();\n sharedSocket = null;\n }\n}\n\n/**\n * Get the shared socket instance\n * @throws Error if socket is not initialized\n */\nexport function getPresenceSocket(): Socket {\n if (!config) {\n throw new Error(\n '[presence-module] Socket not initialized. Call initPresenceSocket() first.',\n );\n }\n\n if (sharedSocket && sharedSocket.connected) {\n return sharedSocket;\n }\n\n if (!sharedSocket) {\n sharedSocket = io(config.url, {\n transports: config.transports ?? ['websocket'],\n });\n } else if (!sharedSocket.connected) {\n sharedSocket.connect();\n }\n\n return sharedSocket;\n}\n\n/**\n * Disconnect and cleanup the socket connection\n */\nexport function disconnectPresenceSocket(): void {\n if (sharedSocket) {\n sharedSocket.disconnect();\n sharedSocket = null;\n }\n}\n\n/**\n * Check if socket is initialized\n */\nexport function isPresenceSocketInitialized(): boolean {\n return config !== null;\n}\n","import { useEffect, useState, useCallback } from 'react';\nimport { getPresenceSocket } from '../core/socket';\nimport type { PresenceUser, UsePresenceOptions } from '../core/types';\n\nconst DEFAULT_HEARTBEAT_INTERVAL = 10 * 60 * 1000; // 10 minutes\n\n/**\n * Hook to join a presence room and track users in that room\n *\n * @param options - Configuration options\n * @returns Array of users currently in the room\n *\n * @example\n * ```tsx\n * const users = usePresence({\n * roomId: 'my-app:page-123',\n * currentUser: { userId: '1', name: 'John' },\n * });\n * ```\n */\nexport function usePresence(options: UsePresenceOptions): PresenceUser[] {\n const {\n roomId,\n currentUser,\n enabled = true,\n heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,\n } = options;\n\n const [users, setUsers] = useState<PresenceUser[]>([]);\n\n // Normalize users from server response\n const normalizeUsers = useCallback((incoming: unknown): PresenceUser[] => {\n if (Array.isArray(incoming)) {\n return incoming as PresenceUser[];\n }\n if (\n incoming &&\n typeof incoming === 'object' &&\n Array.isArray((incoming as { users?: unknown }).users)\n ) {\n return (incoming as { users: unknown[] }).users as PresenceUser[];\n }\n return [];\n }, []);\n\n // Clear users when disabled\n useEffect(() => {\n if (!enabled) {\n setUsers([]);\n }\n }, [enabled]);\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const socket = getPresenceSocket();\n\n const joinRoom = () => {\n socket.emit('presence:join', { roomId, user: currentUser });\n };\n\n // Join immediately if connected, otherwise wait for connect event\n if (socket.connected) {\n joinRoom();\n }\n\n socket.on('connect', joinRoom);\n\n // Heartbeat to extend TTL\n const heartbeat = () => {\n socket.emit('presence:heartbeat', { roomId });\n };\n heartbeat();\n const heartbeatIntervalId = window.setInterval(\n heartbeat,\n heartbeatInterval,\n );\n\n // Handle presence updates from server\n const handlePresenceUpdate = (incoming: unknown) => {\n setUsers(normalizeUsers(incoming));\n };\n socket.on('presence:update', handlePresenceUpdate);\n\n // Notify server when tab/window closes\n const handleBeforeUnload = () => {\n socket.emit('presence:leave', { roomId, userId: currentUser.userId });\n };\n window.addEventListener('beforeunload', handleBeforeUnload);\n\n return () => {\n socket.off('connect', joinRoom);\n socket.off('presence:update', handlePresenceUpdate);\n window.removeEventListener('beforeunload', handleBeforeUnload);\n window.clearInterval(heartbeatIntervalId);\n socket.emit('presence:leave', { roomId, userId: currentUser.userId });\n };\n }, [\n roomId,\n currentUser.userId,\n currentUser.name,\n enabled,\n heartbeatInterval,\n normalizeUsers,\n ]);\n\n return users;\n}\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport { getPresenceSocket } from '../core/socket';\nimport type { PresenceUser, UsePresenceWatchOptions } from '../core/types';\n\ntype SnapshotPayload = {\n rooms?: Record<string, unknown>;\n};\n\ntype UpdatePayload = {\n roomId?: string;\n users?: unknown;\n};\n\nfunction normalizeUsers(value: unknown): PresenceUser[] {\n if (Array.isArray(value)) return value as PresenceUser[];\n if (\n value &&\n typeof value === 'object' &&\n Array.isArray((value as { users?: unknown }).users)\n ) {\n return (value as { users: unknown[] }).users as PresenceUser[];\n }\n return [];\n}\n\n/**\n * Hook to watch multiple rooms without joining them\n * (read-only observation, current user is not added to the rooms)\n *\n * @param options - Configuration options\n * @returns Map of roomId to array of users in that room\n *\n * @example\n * ```tsx\n * const roomsUsers = usePresenceWatch({\n * roomIds: ['room-1', 'room-2', 'room-3'],\n * });\n * // roomsUsers = { 'room-1': [...], 'room-2': [...], ... }\n * ```\n */\nexport function usePresenceWatch(\n options: UsePresenceWatchOptions,\n): Record<string, PresenceUser[]> {\n const { roomIds, enabled = true } = options;\n\n const [roomsUsers, setRoomsUsers] = useState<Record<string, PresenceUser[]>>(\n {},\n );\n\n const normalizedRoomIds = useMemo(() => {\n const uniq = Array.from(new Set((roomIds ?? []).filter(Boolean)));\n return uniq;\n }, [roomIds]);\n\n const watchedRef = useRef<Set<string>>(new Set());\n\n // Handle watch/unwatch based on roomIds changes\n useEffect(() => {\n if (!enabled) {\n setRoomsUsers({});\n watchedRef.current = new Set();\n return;\n }\n\n const socket = getPresenceSocket();\n\n const toWatch = normalizedRoomIds.filter((r) => !watchedRef.current.has(r));\n const toUnwatch = Array.from(watchedRef.current).filter(\n (r) => !normalizedRoomIds.includes(r),\n );\n\n if (toWatch.length > 0) {\n socket.emit('presence:watch', { roomIds: toWatch });\n for (const r of toWatch) watchedRef.current.add(r);\n }\n\n if (toUnwatch.length > 0) {\n socket.emit('presence:unwatch', { roomIds: toUnwatch });\n for (const r of toUnwatch) watchedRef.current.delete(r);\n setRoomsUsers((prev) => {\n const next = { ...prev };\n for (const r of toUnwatch) delete next[r];\n return next;\n });\n }\n }, [enabled, normalizedRoomIds]);\n\n // Handle socket events\n useEffect(() => {\n if (!enabled) return;\n\n const socket = getPresenceSocket();\n\n const handleSnapshot = (payload: SnapshotPayload) => {\n const rooms = payload?.rooms ?? {};\n setRoomsUsers((prev) => {\n const next = { ...prev };\n for (const [roomId, users] of Object.entries(rooms)) {\n next[roomId] = normalizeUsers(users);\n }\n return next;\n });\n };\n\n const handleUpdate = (payload: unknown) => {\n if (!payload || typeof payload !== 'object') return;\n const { roomId, users } = payload as UpdatePayload;\n if (!roomId) return;\n if (!watchedRef.current.has(roomId)) return;\n setRoomsUsers((prev) => ({ ...prev, [roomId]: normalizeUsers(users) }));\n };\n\n socket.on('presence:snapshot', handleSnapshot);\n socket.on('presence:update', handleUpdate);\n\n // Re-watch on reconnect\n const handleConnect = () => {\n const ids = Array.from(watchedRef.current);\n if (ids.length > 0) socket.emit('presence:watch', { roomIds: ids });\n };\n socket.on('connect', handleConnect);\n\n return () => {\n socket.off('presence:snapshot', handleSnapshot);\n socket.off('presence:update', handleUpdate);\n socket.off('connect', handleConnect);\n\n const ids = Array.from(watchedRef.current);\n if (ids.length > 0) socket.emit('presence:unwatch', { roomIds: ids });\n watchedRef.current = new Set();\n };\n }, [enabled]);\n\n return roomsUsers;\n}\n","import { useMemo, useState, useCallback } from 'react';\nimport type {\n PresenceUser,\n UsePresenceAvatarsStateOptions,\n UsePresenceAvatarsStateReturn,\n} from '../core/types';\n\n/**\n * Extract initial character from user name/id\n */\nfunction getInitialFromUser(user: PresenceUser): string {\n const base = (user?.name || user?.userId || '').trim();\n if (!base) return '?';\n const ch = base.charAt(0);\n return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;\n}\n\n/**\n * Headless hook for presence avatars UI state management\n *\n * This hook provides all the state and logic needed to build\n * a presence avatars component without any styling.\n *\n * @param options - Configuration options\n * @returns State and handlers for building presence avatars UI\n *\n * @example\n * ```tsx\n * const {\n * visibleUsers,\n * moreCount,\n * hoveredUser,\n * setHoveredIndex,\n * getInitial,\n * getZIndex,\n * } = usePresenceAvatarsState({ users, maxVisible: 3 });\n *\n * return (\n * <div>\n * {visibleUsers.map((user, idx) => (\n * <span\n * key={user.userId}\n * style={{ zIndex: getZIndex(idx, hoveredUser?.userId === user.userId) }}\n * onMouseEnter={() => setHoveredIndex(idx)}\n * onMouseLeave={() => setHoveredIndex(null)}\n * >\n * {getInitial(user)}\n * </span>\n * ))}\n * {moreCount > 0 && <span>+{moreCount}</span>}\n * </div>\n * );\n * ```\n */\nexport function usePresenceAvatarsState(\n options: UsePresenceAvatarsStateOptions,\n): UsePresenceAvatarsStateReturn {\n const { users, maxVisible = 3 } = options;\n\n const safeUsers = Array.isArray(users) ? users : [];\n\n const visibleUsers = useMemo(\n () => safeUsers.slice(0, maxVisible),\n [safeUsers, maxVisible],\n );\n\n const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);\n\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n const hoveredUser = hoveredIndex != null ? visibleUsers[hoveredIndex] : null;\n\n const baseTopZ = visibleUsers.length + 1;\n\n const getInitial = useCallback((user: PresenceUser): string => {\n return getInitialFromUser(user);\n }, []);\n\n const getZIndex = useCallback(\n (index: number, isHovered: boolean): number => {\n return isHovered ? 100 : baseTopZ - index;\n },\n [baseTopZ],\n );\n\n return {\n visibleUsers,\n moreCount,\n hoveredUser,\n hoveredIndex,\n setHoveredIndex,\n getInitial,\n getZIndex,\n };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n PresenceUser,\n UsePresenceFloatingStateOptions,\n UsePresenceFloatingStateReturn,\n} from \"../core/types\";\n\n/**\n * Headless hook for presence floating UI state management\n *\n * This hook provides all the state and logic needed to build\n * a draggable floating presence panel without any styling.\n *\n * @param options - Configuration options\n * @returns State and handlers for building floating presence UI\n *\n * @example\n * ```tsx\n * const {\n * containerRef,\n * inlineStyle,\n * isDragging,\n * visibleUsers,\n * moreCount,\n * hoveredUser,\n * tooltipTop,\n * onMouseDownHeader,\n * onAvatarEnter,\n * onAvatarLeave,\n * } = usePresenceFloatingState({ users, maxVisible: 8 });\n *\n * return (\n * <div ref={containerRef} style={inlineStyle} onMouseDown={onMouseDownHeader}>\n * <div className=\"header\">열람중 {users.length}</div>\n * <div className=\"body\">\n * {visibleUsers.map((user) => (\n * <span\n * key={user.userId}\n * onMouseEnter={(e) => onAvatarEnter(e, user)}\n * onMouseLeave={onAvatarLeave}\n * >\n * {user.name}\n * </span>\n * ))}\n * </div>\n * </div>\n * );\n * ```\n */\nexport function usePresenceFloatingState(\n options: UsePresenceFloatingStateOptions\n): UsePresenceFloatingStateReturn {\n const { users, maxVisible = 8, initialPosition = { top: 166 } } = options;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [dragging, setDragging] = useState(false);\n const [hasDragged, setHasDragged] = useState(false);\n const [dragOffset, setDragOffset] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [position, setPosition] = useState<{ top: number; left: number }>({\n top: initialPosition.top,\n left: 0,\n });\n const [hoveredUser, setHoveredUser] = useState<PresenceUser | null>(null);\n const [tooltipTop, setTooltipTop] = useState<number>(0);\n\n const safeUsers = Array.isArray(users) ? users : [];\n const visibleUsers = safeUsers.slice(0, maxVisible);\n const moreCount = Math.max(0, safeUsers.length - visibleUsers.length);\n\n // Position adjustment after drag (resize handling)\n useEffect(() => {\n if (!hasDragged) return;\n\n const el = containerRef.current;\n if (!el) return;\n\n const adjustPosition = () => {\n const rect = el.getBoundingClientRect();\n const maxLeft = window.innerWidth - rect.width;\n const maxTop = window.innerHeight - rect.height - 8;\n setPosition((prev) => ({\n left: Math.min(Math.max(0, prev.left), Math.max(0, maxLeft)),\n top: Math.min(Math.max(8, prev.top), Math.max(8, maxTop)),\n }));\n };\n\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n resizeObserver.observe(el);\n\n window.addEventListener(\"resize\", adjustPosition);\n\n return () => {\n resizeObserver.disconnect();\n window.removeEventListener(\"resize\", adjustPosition);\n };\n }, [hasDragged]);\n\n // Helper to get coordinates from mouse or touch event\n const getEventCoordinates = useCallback(\n (e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent) => {\n if (\"touches\" in e && e.touches.length > 0) {\n return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };\n }\n if (\"changedTouches\" in e && e.changedTouches.length > 0) {\n return {\n clientX: e.changedTouches[0].clientX,\n clientY: e.changedTouches[0].clientY,\n };\n }\n if (\"clientX\" in e) {\n return { clientX: e.clientX, clientY: e.clientY };\n }\n return { clientX: 0, clientY: 0 };\n },\n []\n );\n\n const onMouseDownHeader = useCallback(\n (e: React.MouseEvent) => {\n const el = containerRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n\n // On first drag, switch to left-based positioning\n if (!hasDragged) {\n setHasDragged(true);\n setPosition({\n top: rect.top,\n left: rect.left,\n });\n }\n\n setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top });\n setDragging(true);\n e.preventDefault();\n },\n [hasDragged]\n );\n\n const onTouchStartHeader = useCallback(\n (e: React.TouchEvent) => {\n const el = containerRef.current;\n if (!el) return;\n const rect = el.getBoundingClientRect();\n const { clientX, clientY } = getEventCoordinates(e);\n\n // On first drag, switch to left-based positioning\n if (!hasDragged) {\n setHasDragged(true);\n setPosition({\n top: rect.top,\n left: rect.left,\n });\n }\n\n setDragOffset({ x: clientX - rect.left, y: clientY - rect.top });\n setDragging(true);\n // Note: Don't prevent default to allow scrolling detection\n },\n [hasDragged, getEventCoordinates]\n );\n\n // Handle drag movement (mouse and touch)\n useEffect(() => {\n if (!dragging) return;\n\n const el = containerRef.current;\n\n const onMove = (e: MouseEvent | TouchEvent) => {\n if (!el) return;\n const rect = el.getBoundingClientRect();\n const { clientX, clientY } = getEventCoordinates(e);\n const newLeft = clientX - dragOffset.x;\n const newTop = clientY - dragOffset.y;\n const maxLeft = window.innerWidth - rect.width;\n const maxTop = window.innerHeight - rect.height - 8;\n setPosition({\n left: Math.min(Math.max(0, newLeft), Math.max(0, maxLeft)),\n top: Math.min(Math.max(8, newTop), Math.max(8, maxTop)),\n });\n\n // Prevent scrolling while dragging on touch\n if (\"touches\" in e) {\n e.preventDefault();\n }\n };\n\n const onUp = () => setDragging(false);\n\n // Mouse events\n window.addEventListener(\"mousemove\", onMove);\n window.addEventListener(\"mouseup\", onUp, { once: true });\n\n // Touch events\n window.addEventListener(\"touchmove\", onMove, { passive: false });\n window.addEventListener(\"touchend\", onUp, { once: true });\n window.addEventListener(\"touchcancel\", onUp, { once: true });\n\n return () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n window.removeEventListener(\"touchmove\", onMove);\n window.removeEventListener(\"touchend\", onUp);\n window.removeEventListener(\"touchcancel\", onUp);\n };\n }, [dragging, dragOffset.x, dragOffset.y, getEventCoordinates]);\n\n const inlineStyle = useMemo<React.CSSProperties>(() => {\n if (hasDragged) {\n return {\n position: \"fixed\",\n top: position.top,\n left: position.left,\n right: \"auto\",\n };\n } else {\n return {\n position: \"fixed\",\n top: position.top,\n right: 0,\n left: \"auto\",\n };\n }\n }, [position.top, position.left, hasDragged]);\n\n const onAvatarEnter = useCallback(\n (e: React.MouseEvent, user: PresenceUser) => {\n const container = containerRef.current;\n if (!container) {\n setHoveredUser(user);\n setTooltipTop(0);\n return;\n }\n const containerRect = container.getBoundingClientRect();\n const targetRect = (\n e.currentTarget as HTMLElement\n ).getBoundingClientRect();\n const topWithinContainer = targetRect.top - containerRect.top;\n setHoveredUser(user);\n setTooltipTop(topWithinContainer);\n },\n []\n );\n\n const onAvatarLeave = useCallback(() => {\n setHoveredUser(null);\n }, []);\n\n return {\n containerRef,\n position,\n isDragging: dragging,\n hasDragged,\n inlineStyle,\n visibleUsers,\n moreCount,\n hoveredUser,\n tooltipTop,\n onMouseDownHeader,\n onTouchStartHeader,\n onAvatarEnter,\n onAvatarLeave,\n };\n}\n","import React, { useState, useCallback } from \"react\";\nimport { usePresenceAvatarsState } from \"../headless/usePresenceAvatarsState\";\nimport type {\n PresenceUser,\n PresenceAvatarsProps,\n PresenceAvatarRenderProps,\n PresenceAvatarsTooltipRenderProps,\n} from \"../core/types\";\n\n/**\n * PresenceAvatars - Displays a stack of user avatars with hover tooltips\n *\n * @example\n * ```tsx\n * // Basic usage\n * <PresenceAvatars users={users} />\n *\n * // Custom avatar\n * <PresenceAvatars\n * users={users}\n * renderAvatar={({ user, initial }) => (\n * <img src={user.avatarUrl} alt={user.name} />\n * )}\n * />\n *\n * // Custom tooltip\n * <PresenceAvatars\n * users={users}\n * renderTooltip={({ user, position }) => (\n * <div style={{ top: position.top, left: position.left }}>\n * {user.name}\n * </div>\n * )}\n * />\n * ```\n */\nexport function PresenceAvatars({\n users,\n maxVisible = 3,\n className,\n renderAvatar,\n renderTooltip,\n renderMore,\n}: PresenceAvatarsProps) {\n const {\n visibleUsers,\n moreCount,\n hoveredUser,\n hoveredIndex,\n setHoveredIndex,\n getInitial,\n getZIndex,\n } = usePresenceAvatarsState({ users, maxVisible });\n\n const [tooltipPos, setTooltipPos] = useState<{\n top: number;\n left: number;\n } | null>(null);\n\n const updateTooltipPos = useCallback((el: HTMLElement) => {\n const rect = el.getBoundingClientRect();\n const tooltipWidth = 200;\n const half = tooltipWidth / 2;\n const centerX = rect.left + rect.width / 2;\n const left = Math.min(\n Math.max(centerX, half + 8),\n window.innerWidth - half - 8\n );\n const top = Math.min(rect.bottom + 8, window.innerHeight - 8);\n setTooltipPos({ top, left });\n }, []);\n\n const handleMouseEnter = useCallback(\n (e: React.MouseEvent, idx: number) => {\n setHoveredIndex(idx);\n updateTooltipPos(e.currentTarget as HTMLElement);\n },\n [setHoveredIndex, updateTooltipPos]\n );\n\n const handleMouseLeave = useCallback(() => {\n setHoveredIndex(null);\n }, [setHoveredIndex]);\n\n // Default tooltip renderer\n const defaultTooltipRenderer = useCallback(\n ({ user, position }: PresenceAvatarsTooltipRenderProps) => (\n <div\n className=\"presence-avatars__tooltip\"\n style={{ top: position.top, left: position.left }}\n >\n <div className=\"presence-avatars__tooltip-row\">\n <span className=\"presence-avatars__tooltip-key\">Name</span>\n <span className=\"presence-avatars__tooltip-val\">\n {user.name || \"-\"}\n </span>\n </div>\n <div className=\"presence-avatars__tooltip-row\">\n <span className=\"presence-avatars__tooltip-key\">ID</span>\n <span className=\"presence-avatars__tooltip-val\">\n {user.userId || \"-\"}\n </span>\n </div>\n </div>\n ),\n []\n );\n\n // Default avatar renderer\n const defaultAvatarRenderer = useCallback(\n ({ initial, isHovered }: PresenceAvatarRenderProps) => <>{initial}</>,\n []\n );\n\n return (\n <div className={`presence-avatars ${className ?? \"\"}`}>\n <div className=\"presence-avatars__stack\">\n {visibleUsers.map((user, idx) => {\n const isHovered = hoveredIndex === idx;\n const initial = getInitial(user);\n const zIndex = getZIndex(idx, isHovered);\n\n const renderProps: PresenceAvatarRenderProps = {\n user,\n index: idx,\n initial,\n isHovered,\n zIndex,\n };\n\n return (\n <span\n key={`${user.userId}-${idx}`}\n className={`presence-avatars__avatar ${\n isHovered ? \"is-hovered\" : \"\"\n }`}\n style={{ zIndex }}\n onMouseEnter={(e) => handleMouseEnter(e, idx)}\n onMouseLeave={handleMouseLeave}\n aria-label={user.name || user.userId || \"\"}\n >\n {renderAvatar\n ? renderAvatar(renderProps)\n : defaultAvatarRenderer(renderProps)}\n </span>\n );\n })}\n {moreCount > 0 &&\n (renderMore ? (\n renderMore(moreCount)\n ) : (\n <span className=\"presence-avatars__more\">+{moreCount}</span>\n ))}\n </div>\n\n {hoveredUser &&\n tooltipPos &&\n (renderTooltip\n ? renderTooltip({ user: hoveredUser, position: tooltipPos })\n : defaultTooltipRenderer({\n user: hoveredUser,\n position: tooltipPos,\n }))}\n </div>\n );\n}\n\nexport default PresenceAvatars;\n","import React, { useCallback } from \"react\";\nimport { usePresenceFloatingState } from \"../headless/usePresenceFloatingState\";\nimport type {\n PresenceUser,\n PresenceFloatingProps,\n PresenceFloatingAvatarRenderProps,\n PresenceFloatingTooltipRenderProps,\n} from \"../core/types\";\n\n/**\n * Extract initial character from user name/id\n */\nfunction getInitial(user: PresenceUser): string {\n const base = (user?.name || user?.userId || \"\").trim();\n if (!base) return \"?\";\n const ch = base.charAt(0);\n return /[a-z]/i.test(ch) ? ch.toUpperCase() : ch;\n}\n\n/**\n * PresenceFloating - Draggable floating panel showing presence users\n *\n * @example\n * ```tsx\n * // Basic usage\n * <PresenceFloating users={users} />\n *\n * // Custom text\n * <PresenceFloating\n * users={users}\n * title=\"Viewing\"\n * emptyText=\"No one\"\n * moreText={(n) => `+${n} more`}\n * />\n *\n * // Custom avatar\n * <PresenceFloating\n * users={users}\n * renderAvatar={({ user, initial }) => (\n * <img src={user.avatarUrl} alt={user.name} />\n * )}\n * />\n * ```\n */\nexport function PresenceFloating({\n users,\n maxVisible = 8,\n className,\n style,\n initialPosition,\n title = \"열람중\",\n emptyText = \"없음\",\n moreText = (n) => `+${n}명`,\n renderAvatar,\n renderTooltip,\n}: PresenceFloatingProps) {\n const {\n containerRef,\n inlineStyle,\n isDragging,\n visibleUsers,\n moreCount,\n hoveredUser,\n tooltipTop,\n onMouseDownHeader,\n onTouchStartHeader,\n onAvatarEnter,\n onAvatarLeave,\n } = usePresenceFloatingState({ users, maxVisible, initialPosition });\n\n // Default tooltip renderer\n const defaultTooltipRenderer = useCallback(\n ({ user, top }: PresenceFloatingTooltipRenderProps) => (\n <div\n className=\"presence-floating__tooltip\"\n style={{ top: Math.max(0, top - 6) }}\n onMouseLeave={onAvatarLeave}\n >\n <div className=\"presence-floating__tooltip-row\">\n <span className=\"presence-floating__tooltip-key\">Name</span>\n <span className=\"presence-floating__tooltip-val\">\n {user.name || \"-\"}\n </span>\n </div>\n <div className=\"presence-floating__tooltip-row\">\n <span className=\"presence-floating__tooltip-key\">ID</span>\n <span className=\"presence-floating__tooltip-val\">\n {user.userId || \"-\"}\n </span>\n </div>\n </div>\n ),\n [onAvatarLeave]\n );\n\n // Default avatar renderer\n const defaultAvatarRenderer = useCallback(\n ({ initial }: PresenceFloatingAvatarRenderProps) => <>{initial}</>,\n []\n );\n\n const combinedStyle: React.CSSProperties = {\n ...inlineStyle,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={`presence-floating ${isDragging ? \"is-dragging\" : \"\"} ${\n className ?? \"\"\n }`}\n style={combinedStyle}\n >\n <div\n className=\"presence-floating__header\"\n onMouseDown={onMouseDownHeader}\n onTouchStart={onTouchStartHeader}\n >\n <span\n className=\"presence-floating__title\"\n title={title}\n aria-label={title}\n >\n {title}\n </span>\n <span className=\"presence-floating__count\">{users.length}</span>\n </div>\n\n {hoveredUser &&\n (renderTooltip\n ? renderTooltip({ user: hoveredUser, top: tooltipTop })\n : defaultTooltipRenderer({ user: hoveredUser, top: tooltipTop }))}\n\n <div className=\"presence-floating__body\">\n {visibleUsers.length === 0 ? (\n <span className=\"presence-floating__empty\">{emptyText}</span>\n ) : (\n visibleUsers.map((user, idx) => {\n const initial = getInitial(user);\n const label = user.name || user.userId || \"\";\n\n const renderProps: PresenceFloatingAvatarRenderProps = {\n user,\n initial,\n };\n\n return (\n <span\n key={`${user.userId}-${idx}`}\n className=\"presence-floating__avatar\"\n title={label}\n aria-label={label}\n onMouseEnter={(e) => onAvatarEnter(e, user)}\n onMouseLeave={onAvatarLeave}\n >\n {renderAvatar\n ? renderAvatar(renderProps)\n : defaultAvatarRenderer(renderProps)}\n </span>\n );\n })\n )}\n {moreCount > 0 && (\n <span className=\"presence-floating__more\">{moreText(moreCount)}</span>\n )}\n </div>\n </div>\n );\n}\n\nexport default PresenceFloating;\n"],"mappings":"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","names":["normalizeUsers","import_react","import_react","getInitial","import_react","import_react","getInitial","import_react","import_jsx_runtime"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Socket } from 'socket.io-client';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Presence User Type
|
|
@@ -92,6 +93,84 @@ type UsePresenceFloatingStateReturn = {
|
|
|
92
93
|
onAvatarEnter: (e: React.MouseEvent, user: PresenceUser) => void;
|
|
93
94
|
onAvatarLeave: () => void;
|
|
94
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* Render props for individual avatar in PresenceAvatars
|
|
98
|
+
*/
|
|
99
|
+
type PresenceAvatarRenderProps = {
|
|
100
|
+
user: PresenceUser;
|
|
101
|
+
index: number;
|
|
102
|
+
initial: string;
|
|
103
|
+
isHovered: boolean;
|
|
104
|
+
zIndex: number;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Render props for tooltip in PresenceAvatars
|
|
108
|
+
*/
|
|
109
|
+
type PresenceAvatarsTooltipRenderProps = {
|
|
110
|
+
user: PresenceUser;
|
|
111
|
+
position: {
|
|
112
|
+
top: number;
|
|
113
|
+
left: number;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Props for PresenceAvatars component
|
|
118
|
+
*/
|
|
119
|
+
type PresenceAvatarsProps = {
|
|
120
|
+
/** List of presence users */
|
|
121
|
+
users: PresenceUser[];
|
|
122
|
+
/** Maximum visible avatars (default: 3) */
|
|
123
|
+
maxVisible?: number;
|
|
124
|
+
/** Additional CSS class */
|
|
125
|
+
className?: string;
|
|
126
|
+
/** Custom avatar renderer */
|
|
127
|
+
renderAvatar?: (props: PresenceAvatarRenderProps) => React.ReactNode;
|
|
128
|
+
/** Custom tooltip renderer */
|
|
129
|
+
renderTooltip?: (props: PresenceAvatarsTooltipRenderProps) => React.ReactNode;
|
|
130
|
+
/** Custom "more" badge renderer */
|
|
131
|
+
renderMore?: (count: number) => React.ReactNode;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Render props for individual avatar in PresenceFloating
|
|
135
|
+
*/
|
|
136
|
+
type PresenceFloatingAvatarRenderProps = {
|
|
137
|
+
user: PresenceUser;
|
|
138
|
+
initial: string;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Render props for tooltip in PresenceFloating
|
|
142
|
+
*/
|
|
143
|
+
type PresenceFloatingTooltipRenderProps = {
|
|
144
|
+
user: PresenceUser;
|
|
145
|
+
top: number;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Props for PresenceFloating component
|
|
149
|
+
*/
|
|
150
|
+
type PresenceFloatingProps = {
|
|
151
|
+
/** List of presence users */
|
|
152
|
+
users: PresenceUser[];
|
|
153
|
+
/** Maximum visible avatars (default: 8) */
|
|
154
|
+
maxVisible?: number;
|
|
155
|
+
/** Additional CSS class */
|
|
156
|
+
className?: string;
|
|
157
|
+
/** Additional inline styles */
|
|
158
|
+
style?: React.CSSProperties;
|
|
159
|
+
/** Initial position */
|
|
160
|
+
initialPosition?: {
|
|
161
|
+
top: number;
|
|
162
|
+
};
|
|
163
|
+
/** Header title text (default: "열람중") */
|
|
164
|
+
title?: string;
|
|
165
|
+
/** Empty state text (default: "없음") */
|
|
166
|
+
emptyText?: string;
|
|
167
|
+
/** More count text formatter (default: (n) => `+${n}명`) */
|
|
168
|
+
moreText?: (count: number) => string;
|
|
169
|
+
/** Custom avatar renderer */
|
|
170
|
+
renderAvatar?: (props: PresenceFloatingAvatarRenderProps) => React.ReactNode;
|
|
171
|
+
/** Custom tooltip renderer */
|
|
172
|
+
renderTooltip?: (props: PresenceFloatingTooltipRenderProps) => React.ReactNode;
|
|
173
|
+
};
|
|
95
174
|
|
|
96
175
|
/**
|
|
97
176
|
* Initialize the presence socket with configuration
|
|
@@ -228,4 +307,60 @@ declare function usePresenceAvatarsState(options: UsePresenceAvatarsStateOptions
|
|
|
228
307
|
*/
|
|
229
308
|
declare function usePresenceFloatingState(options: UsePresenceFloatingStateOptions): UsePresenceFloatingStateReturn;
|
|
230
309
|
|
|
231
|
-
|
|
310
|
+
/**
|
|
311
|
+
* PresenceAvatars - Displays a stack of user avatars with hover tooltips
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```tsx
|
|
315
|
+
* // Basic usage
|
|
316
|
+
* <PresenceAvatars users={users} />
|
|
317
|
+
*
|
|
318
|
+
* // Custom avatar
|
|
319
|
+
* <PresenceAvatars
|
|
320
|
+
* users={users}
|
|
321
|
+
* renderAvatar={({ user, initial }) => (
|
|
322
|
+
* <img src={user.avatarUrl} alt={user.name} />
|
|
323
|
+
* )}
|
|
324
|
+
* />
|
|
325
|
+
*
|
|
326
|
+
* // Custom tooltip
|
|
327
|
+
* <PresenceAvatars
|
|
328
|
+
* users={users}
|
|
329
|
+
* renderTooltip={({ user, position }) => (
|
|
330
|
+
* <div style={{ top: position.top, left: position.left }}>
|
|
331
|
+
* {user.name}
|
|
332
|
+
* </div>
|
|
333
|
+
* )}
|
|
334
|
+
* />
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
declare function PresenceAvatars({ users, maxVisible, className, renderAvatar, renderTooltip, renderMore, }: PresenceAvatarsProps): react_jsx_runtime.JSX.Element;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* PresenceFloating - Draggable floating panel showing presence users
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```tsx
|
|
344
|
+
* // Basic usage
|
|
345
|
+
* <PresenceFloating users={users} />
|
|
346
|
+
*
|
|
347
|
+
* // Custom text
|
|
348
|
+
* <PresenceFloating
|
|
349
|
+
* users={users}
|
|
350
|
+
* title="Viewing"
|
|
351
|
+
* emptyText="No one"
|
|
352
|
+
* moreText={(n) => `+${n} more`}
|
|
353
|
+
* />
|
|
354
|
+
*
|
|
355
|
+
* // Custom avatar
|
|
356
|
+
* <PresenceFloating
|
|
357
|
+
* users={users}
|
|
358
|
+
* renderAvatar={({ user, initial }) => (
|
|
359
|
+
* <img src={user.avatarUrl} alt={user.name} />
|
|
360
|
+
* )}
|
|
361
|
+
* />
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
declare function PresenceFloating({ users, maxVisible, className, style, initialPosition, title, emptyText, moreText, renderAvatar, renderTooltip, }: PresenceFloatingProps): react_jsx_runtime.JSX.Element;
|
|
365
|
+
|
|
366
|
+
export { type PresenceAvatarRenderProps, PresenceAvatars, type PresenceAvatarsProps, type PresenceAvatarsTooltipRenderProps, type PresenceConfig, PresenceFloating, type PresenceFloatingAvatarRenderProps, type PresenceFloatingProps, type PresenceFloatingTooltipRenderProps, type PresenceUser, type UsePresenceAvatarsStateOptions, type UsePresenceAvatarsStateReturn, type UsePresenceFloatingStateOptions, type UsePresenceFloatingStateReturn, type UsePresenceOptions, type UsePresenceWatchOptions, disconnectPresenceSocket, getPresenceSocket, initPresenceSocket, isPresenceSocketInitialized, usePresence, usePresenceAvatarsState, usePresenceFloatingState, usePresenceWatch };
|
package/dist/presence/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Socket } from 'socket.io-client';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Presence User Type
|
|
@@ -92,6 +93,84 @@ type UsePresenceFloatingStateReturn = {
|
|
|
92
93
|
onAvatarEnter: (e: React.MouseEvent, user: PresenceUser) => void;
|
|
93
94
|
onAvatarLeave: () => void;
|
|
94
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* Render props for individual avatar in PresenceAvatars
|
|
98
|
+
*/
|
|
99
|
+
type PresenceAvatarRenderProps = {
|
|
100
|
+
user: PresenceUser;
|
|
101
|
+
index: number;
|
|
102
|
+
initial: string;
|
|
103
|
+
isHovered: boolean;
|
|
104
|
+
zIndex: number;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Render props for tooltip in PresenceAvatars
|
|
108
|
+
*/
|
|
109
|
+
type PresenceAvatarsTooltipRenderProps = {
|
|
110
|
+
user: PresenceUser;
|
|
111
|
+
position: {
|
|
112
|
+
top: number;
|
|
113
|
+
left: number;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Props for PresenceAvatars component
|
|
118
|
+
*/
|
|
119
|
+
type PresenceAvatarsProps = {
|
|
120
|
+
/** List of presence users */
|
|
121
|
+
users: PresenceUser[];
|
|
122
|
+
/** Maximum visible avatars (default: 3) */
|
|
123
|
+
maxVisible?: number;
|
|
124
|
+
/** Additional CSS class */
|
|
125
|
+
className?: string;
|
|
126
|
+
/** Custom avatar renderer */
|
|
127
|
+
renderAvatar?: (props: PresenceAvatarRenderProps) => React.ReactNode;
|
|
128
|
+
/** Custom tooltip renderer */
|
|
129
|
+
renderTooltip?: (props: PresenceAvatarsTooltipRenderProps) => React.ReactNode;
|
|
130
|
+
/** Custom "more" badge renderer */
|
|
131
|
+
renderMore?: (count: number) => React.ReactNode;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Render props for individual avatar in PresenceFloating
|
|
135
|
+
*/
|
|
136
|
+
type PresenceFloatingAvatarRenderProps = {
|
|
137
|
+
user: PresenceUser;
|
|
138
|
+
initial: string;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Render props for tooltip in PresenceFloating
|
|
142
|
+
*/
|
|
143
|
+
type PresenceFloatingTooltipRenderProps = {
|
|
144
|
+
user: PresenceUser;
|
|
145
|
+
top: number;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Props for PresenceFloating component
|
|
149
|
+
*/
|
|
150
|
+
type PresenceFloatingProps = {
|
|
151
|
+
/** List of presence users */
|
|
152
|
+
users: PresenceUser[];
|
|
153
|
+
/** Maximum visible avatars (default: 8) */
|
|
154
|
+
maxVisible?: number;
|
|
155
|
+
/** Additional CSS class */
|
|
156
|
+
className?: string;
|
|
157
|
+
/** Additional inline styles */
|
|
158
|
+
style?: React.CSSProperties;
|
|
159
|
+
/** Initial position */
|
|
160
|
+
initialPosition?: {
|
|
161
|
+
top: number;
|
|
162
|
+
};
|
|
163
|
+
/** Header title text (default: "열람중") */
|
|
164
|
+
title?: string;
|
|
165
|
+
/** Empty state text (default: "없음") */
|
|
166
|
+
emptyText?: string;
|
|
167
|
+
/** More count text formatter (default: (n) => `+${n}명`) */
|
|
168
|
+
moreText?: (count: number) => string;
|
|
169
|
+
/** Custom avatar renderer */
|
|
170
|
+
renderAvatar?: (props: PresenceFloatingAvatarRenderProps) => React.ReactNode;
|
|
171
|
+
/** Custom tooltip renderer */
|
|
172
|
+
renderTooltip?: (props: PresenceFloatingTooltipRenderProps) => React.ReactNode;
|
|
173
|
+
};
|
|
95
174
|
|
|
96
175
|
/**
|
|
97
176
|
* Initialize the presence socket with configuration
|
|
@@ -228,4 +307,60 @@ declare function usePresenceAvatarsState(options: UsePresenceAvatarsStateOptions
|
|
|
228
307
|
*/
|
|
229
308
|
declare function usePresenceFloatingState(options: UsePresenceFloatingStateOptions): UsePresenceFloatingStateReturn;
|
|
230
309
|
|
|
231
|
-
|
|
310
|
+
/**
|
|
311
|
+
* PresenceAvatars - Displays a stack of user avatars with hover tooltips
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```tsx
|
|
315
|
+
* // Basic usage
|
|
316
|
+
* <PresenceAvatars users={users} />
|
|
317
|
+
*
|
|
318
|
+
* // Custom avatar
|
|
319
|
+
* <PresenceAvatars
|
|
320
|
+
* users={users}
|
|
321
|
+
* renderAvatar={({ user, initial }) => (
|
|
322
|
+
* <img src={user.avatarUrl} alt={user.name} />
|
|
323
|
+
* )}
|
|
324
|
+
* />
|
|
325
|
+
*
|
|
326
|
+
* // Custom tooltip
|
|
327
|
+
* <PresenceAvatars
|
|
328
|
+
* users={users}
|
|
329
|
+
* renderTooltip={({ user, position }) => (
|
|
330
|
+
* <div style={{ top: position.top, left: position.left }}>
|
|
331
|
+
* {user.name}
|
|
332
|
+
* </div>
|
|
333
|
+
* )}
|
|
334
|
+
* />
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
declare function PresenceAvatars({ users, maxVisible, className, renderAvatar, renderTooltip, renderMore, }: PresenceAvatarsProps): react_jsx_runtime.JSX.Element;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* PresenceFloating - Draggable floating panel showing presence users
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```tsx
|
|
344
|
+
* // Basic usage
|
|
345
|
+
* <PresenceFloating users={users} />
|
|
346
|
+
*
|
|
347
|
+
* // Custom text
|
|
348
|
+
* <PresenceFloating
|
|
349
|
+
* users={users}
|
|
350
|
+
* title="Viewing"
|
|
351
|
+
* emptyText="No one"
|
|
352
|
+
* moreText={(n) => `+${n} more`}
|
|
353
|
+
* />
|
|
354
|
+
*
|
|
355
|
+
* // Custom avatar
|
|
356
|
+
* <PresenceFloating
|
|
357
|
+
* users={users}
|
|
358
|
+
* renderAvatar={({ user, initial }) => (
|
|
359
|
+
* <img src={user.avatarUrl} alt={user.name} />
|
|
360
|
+
* )}
|
|
361
|
+
* />
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
declare function PresenceFloating({ users, maxVisible, className, style, initialPosition, title, emptyText, moreText, renderAvatar, renderTooltip, }: PresenceFloatingProps): react_jsx_runtime.JSX.Element;
|
|
365
|
+
|
|
366
|
+
export { type PresenceAvatarRenderProps, PresenceAvatars, type PresenceAvatarsProps, type PresenceAvatarsTooltipRenderProps, type PresenceConfig, PresenceFloating, type PresenceFloatingAvatarRenderProps, type PresenceFloatingProps, type PresenceFloatingTooltipRenderProps, type PresenceUser, type UsePresenceAvatarsStateOptions, type UsePresenceAvatarsStateReturn, type UsePresenceFloatingStateOptions, type UsePresenceFloatingStateReturn, type UsePresenceOptions, type UsePresenceWatchOptions, disconnectPresenceSocket, getPresenceSocket, initPresenceSocket, isPresenceSocketInitialized, usePresence, usePresenceAvatarsState, usePresenceFloatingState, usePresenceWatch };
|