@rencar-dev/feature-modules-public 0.0.7
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 +215 -0
- package/dist/hooks/index.cjs +51 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +15 -0
- package/dist/hooks/index.d.ts +15 -0
- package/dist/hooks/index.js +24 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/presence/index.cjs +459 -0
- package/dist/presence/index.cjs.map +1 -0
- package/dist/presence/index.d.cts +231 -0
- package/dist/presence/index.d.ts +231 -0
- package/dist/presence/index.js +425 -0
- package/dist/presence/index.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Socket } from 'socket.io-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Presence User Type
|
|
5
|
+
*/
|
|
6
|
+
type PresenceUser = {
|
|
7
|
+
userId: string;
|
|
8
|
+
name: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Socket configuration
|
|
13
|
+
*/
|
|
14
|
+
type PresenceConfig = {
|
|
15
|
+
url: string;
|
|
16
|
+
transports?: ("websocket" | "polling")[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* usePresence hook options
|
|
20
|
+
*/
|
|
21
|
+
type UsePresenceOptions = {
|
|
22
|
+
/** Room identifier to join */
|
|
23
|
+
roomId: string;
|
|
24
|
+
/** Current user info to broadcast */
|
|
25
|
+
currentUser: PresenceUser;
|
|
26
|
+
/** Enable/disable the hook (default: true) */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
/** Heartbeat interval in ms (default: 10 minutes) */
|
|
29
|
+
heartbeatInterval?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* usePresenceWatch hook options
|
|
33
|
+
*/
|
|
34
|
+
type UsePresenceWatchOptions = {
|
|
35
|
+
/** Room IDs to watch */
|
|
36
|
+
roomIds: string[];
|
|
37
|
+
/** Enable/disable the hook (default: true) */
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* usePresenceAvatarsState hook options
|
|
42
|
+
*/
|
|
43
|
+
type UsePresenceAvatarsStateOptions = {
|
|
44
|
+
/** List of presence users */
|
|
45
|
+
users: PresenceUser[];
|
|
46
|
+
/** Maximum visible avatars (default: 3) */
|
|
47
|
+
maxVisible?: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* usePresenceAvatarsState return type
|
|
51
|
+
*/
|
|
52
|
+
type UsePresenceAvatarsStateReturn = {
|
|
53
|
+
visibleUsers: PresenceUser[];
|
|
54
|
+
moreCount: number;
|
|
55
|
+
hoveredUser: PresenceUser | null;
|
|
56
|
+
hoveredIndex: number | null;
|
|
57
|
+
setHoveredIndex: (index: number | null) => void;
|
|
58
|
+
getInitial: (user: PresenceUser) => string;
|
|
59
|
+
getZIndex: (index: number, isHovered: boolean) => number;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* usePresenceFloatingState hook options
|
|
63
|
+
*/
|
|
64
|
+
type UsePresenceFloatingStateOptions = {
|
|
65
|
+
/** List of presence users */
|
|
66
|
+
users: PresenceUser[];
|
|
67
|
+
/** Maximum visible avatars (default: 8) */
|
|
68
|
+
maxVisible?: number;
|
|
69
|
+
/** Initial position */
|
|
70
|
+
initialPosition?: {
|
|
71
|
+
top: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* usePresenceFloatingState return type
|
|
76
|
+
*/
|
|
77
|
+
type UsePresenceFloatingStateReturn = {
|
|
78
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
79
|
+
position: {
|
|
80
|
+
top: number;
|
|
81
|
+
left: number;
|
|
82
|
+
};
|
|
83
|
+
isDragging: boolean;
|
|
84
|
+
hasDragged: boolean;
|
|
85
|
+
inlineStyle: React.CSSProperties;
|
|
86
|
+
visibleUsers: PresenceUser[];
|
|
87
|
+
moreCount: number;
|
|
88
|
+
hoveredUser: PresenceUser | null;
|
|
89
|
+
tooltipTop: number;
|
|
90
|
+
onMouseDownHeader: (e: React.MouseEvent) => void;
|
|
91
|
+
onTouchStartHeader: (e: React.TouchEvent) => void;
|
|
92
|
+
onAvatarEnter: (e: React.MouseEvent, user: PresenceUser) => void;
|
|
93
|
+
onAvatarLeave: () => void;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize the presence socket with configuration
|
|
98
|
+
* Must be called before using any presence hooks
|
|
99
|
+
*/
|
|
100
|
+
declare function initPresenceSocket(presenceConfig: PresenceConfig): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get the shared socket instance
|
|
103
|
+
* @throws Error if socket is not initialized
|
|
104
|
+
*/
|
|
105
|
+
declare function getPresenceSocket(): Socket;
|
|
106
|
+
/**
|
|
107
|
+
* Disconnect and cleanup the socket connection
|
|
108
|
+
*/
|
|
109
|
+
declare function disconnectPresenceSocket(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Check if socket is initialized
|
|
112
|
+
*/
|
|
113
|
+
declare function isPresenceSocketInitialized(): boolean;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Hook to join a presence room and track users in that room
|
|
117
|
+
*
|
|
118
|
+
* @param options - Configuration options
|
|
119
|
+
* @returns Array of users currently in the room
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* const users = usePresence({
|
|
124
|
+
* roomId: 'my-app:page-123',
|
|
125
|
+
* currentUser: { userId: '1', name: 'John' },
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare function usePresence(options: UsePresenceOptions): PresenceUser[];
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Hook to watch multiple rooms without joining them
|
|
133
|
+
* (read-only observation, current user is not added to the rooms)
|
|
134
|
+
*
|
|
135
|
+
* @param options - Configuration options
|
|
136
|
+
* @returns Map of roomId to array of users in that room
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```tsx
|
|
140
|
+
* const roomsUsers = usePresenceWatch({
|
|
141
|
+
* roomIds: ['room-1', 'room-2', 'room-3'],
|
|
142
|
+
* });
|
|
143
|
+
* // roomsUsers = { 'room-1': [...], 'room-2': [...], ... }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function usePresenceWatch(options: UsePresenceWatchOptions): Record<string, PresenceUser[]>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Headless hook for presence avatars UI state management
|
|
150
|
+
*
|
|
151
|
+
* This hook provides all the state and logic needed to build
|
|
152
|
+
* a presence avatars component without any styling.
|
|
153
|
+
*
|
|
154
|
+
* @param options - Configuration options
|
|
155
|
+
* @returns State and handlers for building presence avatars UI
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```tsx
|
|
159
|
+
* const {
|
|
160
|
+
* visibleUsers,
|
|
161
|
+
* moreCount,
|
|
162
|
+
* hoveredUser,
|
|
163
|
+
* setHoveredIndex,
|
|
164
|
+
* getInitial,
|
|
165
|
+
* getZIndex,
|
|
166
|
+
* } = usePresenceAvatarsState({ users, maxVisible: 3 });
|
|
167
|
+
*
|
|
168
|
+
* return (
|
|
169
|
+
* <div>
|
|
170
|
+
* {visibleUsers.map((user, idx) => (
|
|
171
|
+
* <span
|
|
172
|
+
* key={user.userId}
|
|
173
|
+
* style={{ zIndex: getZIndex(idx, hoveredUser?.userId === user.userId) }}
|
|
174
|
+
* onMouseEnter={() => setHoveredIndex(idx)}
|
|
175
|
+
* onMouseLeave={() => setHoveredIndex(null)}
|
|
176
|
+
* >
|
|
177
|
+
* {getInitial(user)}
|
|
178
|
+
* </span>
|
|
179
|
+
* ))}
|
|
180
|
+
* {moreCount > 0 && <span>+{moreCount}</span>}
|
|
181
|
+
* </div>
|
|
182
|
+
* );
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
declare function usePresenceAvatarsState(options: UsePresenceAvatarsStateOptions): UsePresenceAvatarsStateReturn;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Headless hook for presence floating UI state management
|
|
189
|
+
*
|
|
190
|
+
* This hook provides all the state and logic needed to build
|
|
191
|
+
* a draggable floating presence panel without any styling.
|
|
192
|
+
*
|
|
193
|
+
* @param options - Configuration options
|
|
194
|
+
* @returns State and handlers for building floating presence UI
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```tsx
|
|
198
|
+
* const {
|
|
199
|
+
* containerRef,
|
|
200
|
+
* inlineStyle,
|
|
201
|
+
* isDragging,
|
|
202
|
+
* visibleUsers,
|
|
203
|
+
* moreCount,
|
|
204
|
+
* hoveredUser,
|
|
205
|
+
* tooltipTop,
|
|
206
|
+
* onMouseDownHeader,
|
|
207
|
+
* onAvatarEnter,
|
|
208
|
+
* onAvatarLeave,
|
|
209
|
+
* } = usePresenceFloatingState({ users, maxVisible: 8 });
|
|
210
|
+
*
|
|
211
|
+
* return (
|
|
212
|
+
* <div ref={containerRef} style={inlineStyle} onMouseDown={onMouseDownHeader}>
|
|
213
|
+
* <div className="header">열람중 {users.length}</div>
|
|
214
|
+
* <div className="body">
|
|
215
|
+
* {visibleUsers.map((user) => (
|
|
216
|
+
* <span
|
|
217
|
+
* key={user.userId}
|
|
218
|
+
* onMouseEnter={(e) => onAvatarEnter(e, user)}
|
|
219
|
+
* onMouseLeave={onAvatarLeave}
|
|
220
|
+
* >
|
|
221
|
+
* {user.name}
|
|
222
|
+
* </span>
|
|
223
|
+
* ))}
|
|
224
|
+
* </div>
|
|
225
|
+
* </div>
|
|
226
|
+
* );
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function usePresenceFloatingState(options: UsePresenceFloatingStateOptions): UsePresenceFloatingStateReturn;
|
|
230
|
+
|
|
231
|
+
export { type PresenceConfig, type PresenceUser, type UsePresenceAvatarsStateOptions, type UsePresenceAvatarsStateReturn, type UsePresenceFloatingStateOptions, type UsePresenceFloatingStateReturn, type UsePresenceOptions, type UsePresenceWatchOptions, disconnectPresenceSocket, getPresenceSocket, initPresenceSocket, isPresenceSocketInitialized, usePresence, usePresenceAvatarsState, usePresenceFloatingState, usePresenceWatch };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Socket } from 'socket.io-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Presence User Type
|
|
5
|
+
*/
|
|
6
|
+
type PresenceUser = {
|
|
7
|
+
userId: string;
|
|
8
|
+
name: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Socket configuration
|
|
13
|
+
*/
|
|
14
|
+
type PresenceConfig = {
|
|
15
|
+
url: string;
|
|
16
|
+
transports?: ("websocket" | "polling")[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* usePresence hook options
|
|
20
|
+
*/
|
|
21
|
+
type UsePresenceOptions = {
|
|
22
|
+
/** Room identifier to join */
|
|
23
|
+
roomId: string;
|
|
24
|
+
/** Current user info to broadcast */
|
|
25
|
+
currentUser: PresenceUser;
|
|
26
|
+
/** Enable/disable the hook (default: true) */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
/** Heartbeat interval in ms (default: 10 minutes) */
|
|
29
|
+
heartbeatInterval?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* usePresenceWatch hook options
|
|
33
|
+
*/
|
|
34
|
+
type UsePresenceWatchOptions = {
|
|
35
|
+
/** Room IDs to watch */
|
|
36
|
+
roomIds: string[];
|
|
37
|
+
/** Enable/disable the hook (default: true) */
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* usePresenceAvatarsState hook options
|
|
42
|
+
*/
|
|
43
|
+
type UsePresenceAvatarsStateOptions = {
|
|
44
|
+
/** List of presence users */
|
|
45
|
+
users: PresenceUser[];
|
|
46
|
+
/** Maximum visible avatars (default: 3) */
|
|
47
|
+
maxVisible?: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* usePresenceAvatarsState return type
|
|
51
|
+
*/
|
|
52
|
+
type UsePresenceAvatarsStateReturn = {
|
|
53
|
+
visibleUsers: PresenceUser[];
|
|
54
|
+
moreCount: number;
|
|
55
|
+
hoveredUser: PresenceUser | null;
|
|
56
|
+
hoveredIndex: number | null;
|
|
57
|
+
setHoveredIndex: (index: number | null) => void;
|
|
58
|
+
getInitial: (user: PresenceUser) => string;
|
|
59
|
+
getZIndex: (index: number, isHovered: boolean) => number;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* usePresenceFloatingState hook options
|
|
63
|
+
*/
|
|
64
|
+
type UsePresenceFloatingStateOptions = {
|
|
65
|
+
/** List of presence users */
|
|
66
|
+
users: PresenceUser[];
|
|
67
|
+
/** Maximum visible avatars (default: 8) */
|
|
68
|
+
maxVisible?: number;
|
|
69
|
+
/** Initial position */
|
|
70
|
+
initialPosition?: {
|
|
71
|
+
top: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* usePresenceFloatingState return type
|
|
76
|
+
*/
|
|
77
|
+
type UsePresenceFloatingStateReturn = {
|
|
78
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
79
|
+
position: {
|
|
80
|
+
top: number;
|
|
81
|
+
left: number;
|
|
82
|
+
};
|
|
83
|
+
isDragging: boolean;
|
|
84
|
+
hasDragged: boolean;
|
|
85
|
+
inlineStyle: React.CSSProperties;
|
|
86
|
+
visibleUsers: PresenceUser[];
|
|
87
|
+
moreCount: number;
|
|
88
|
+
hoveredUser: PresenceUser | null;
|
|
89
|
+
tooltipTop: number;
|
|
90
|
+
onMouseDownHeader: (e: React.MouseEvent) => void;
|
|
91
|
+
onTouchStartHeader: (e: React.TouchEvent) => void;
|
|
92
|
+
onAvatarEnter: (e: React.MouseEvent, user: PresenceUser) => void;
|
|
93
|
+
onAvatarLeave: () => void;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize the presence socket with configuration
|
|
98
|
+
* Must be called before using any presence hooks
|
|
99
|
+
*/
|
|
100
|
+
declare function initPresenceSocket(presenceConfig: PresenceConfig): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get the shared socket instance
|
|
103
|
+
* @throws Error if socket is not initialized
|
|
104
|
+
*/
|
|
105
|
+
declare function getPresenceSocket(): Socket;
|
|
106
|
+
/**
|
|
107
|
+
* Disconnect and cleanup the socket connection
|
|
108
|
+
*/
|
|
109
|
+
declare function disconnectPresenceSocket(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Check if socket is initialized
|
|
112
|
+
*/
|
|
113
|
+
declare function isPresenceSocketInitialized(): boolean;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Hook to join a presence room and track users in that room
|
|
117
|
+
*
|
|
118
|
+
* @param options - Configuration options
|
|
119
|
+
* @returns Array of users currently in the room
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* const users = usePresence({
|
|
124
|
+
* roomId: 'my-app:page-123',
|
|
125
|
+
* currentUser: { userId: '1', name: 'John' },
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare function usePresence(options: UsePresenceOptions): PresenceUser[];
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Hook to watch multiple rooms without joining them
|
|
133
|
+
* (read-only observation, current user is not added to the rooms)
|
|
134
|
+
*
|
|
135
|
+
* @param options - Configuration options
|
|
136
|
+
* @returns Map of roomId to array of users in that room
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```tsx
|
|
140
|
+
* const roomsUsers = usePresenceWatch({
|
|
141
|
+
* roomIds: ['room-1', 'room-2', 'room-3'],
|
|
142
|
+
* });
|
|
143
|
+
* // roomsUsers = { 'room-1': [...], 'room-2': [...], ... }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function usePresenceWatch(options: UsePresenceWatchOptions): Record<string, PresenceUser[]>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Headless hook for presence avatars UI state management
|
|
150
|
+
*
|
|
151
|
+
* This hook provides all the state and logic needed to build
|
|
152
|
+
* a presence avatars component without any styling.
|
|
153
|
+
*
|
|
154
|
+
* @param options - Configuration options
|
|
155
|
+
* @returns State and handlers for building presence avatars UI
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```tsx
|
|
159
|
+
* const {
|
|
160
|
+
* visibleUsers,
|
|
161
|
+
* moreCount,
|
|
162
|
+
* hoveredUser,
|
|
163
|
+
* setHoveredIndex,
|
|
164
|
+
* getInitial,
|
|
165
|
+
* getZIndex,
|
|
166
|
+
* } = usePresenceAvatarsState({ users, maxVisible: 3 });
|
|
167
|
+
*
|
|
168
|
+
* return (
|
|
169
|
+
* <div>
|
|
170
|
+
* {visibleUsers.map((user, idx) => (
|
|
171
|
+
* <span
|
|
172
|
+
* key={user.userId}
|
|
173
|
+
* style={{ zIndex: getZIndex(idx, hoveredUser?.userId === user.userId) }}
|
|
174
|
+
* onMouseEnter={() => setHoveredIndex(idx)}
|
|
175
|
+
* onMouseLeave={() => setHoveredIndex(null)}
|
|
176
|
+
* >
|
|
177
|
+
* {getInitial(user)}
|
|
178
|
+
* </span>
|
|
179
|
+
* ))}
|
|
180
|
+
* {moreCount > 0 && <span>+{moreCount}</span>}
|
|
181
|
+
* </div>
|
|
182
|
+
* );
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
declare function usePresenceAvatarsState(options: UsePresenceAvatarsStateOptions): UsePresenceAvatarsStateReturn;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Headless hook for presence floating UI state management
|
|
189
|
+
*
|
|
190
|
+
* This hook provides all the state and logic needed to build
|
|
191
|
+
* a draggable floating presence panel without any styling.
|
|
192
|
+
*
|
|
193
|
+
* @param options - Configuration options
|
|
194
|
+
* @returns State and handlers for building floating presence UI
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```tsx
|
|
198
|
+
* const {
|
|
199
|
+
* containerRef,
|
|
200
|
+
* inlineStyle,
|
|
201
|
+
* isDragging,
|
|
202
|
+
* visibleUsers,
|
|
203
|
+
* moreCount,
|
|
204
|
+
* hoveredUser,
|
|
205
|
+
* tooltipTop,
|
|
206
|
+
* onMouseDownHeader,
|
|
207
|
+
* onAvatarEnter,
|
|
208
|
+
* onAvatarLeave,
|
|
209
|
+
* } = usePresenceFloatingState({ users, maxVisible: 8 });
|
|
210
|
+
*
|
|
211
|
+
* return (
|
|
212
|
+
* <div ref={containerRef} style={inlineStyle} onMouseDown={onMouseDownHeader}>
|
|
213
|
+
* <div className="header">열람중 {users.length}</div>
|
|
214
|
+
* <div className="body">
|
|
215
|
+
* {visibleUsers.map((user) => (
|
|
216
|
+
* <span
|
|
217
|
+
* key={user.userId}
|
|
218
|
+
* onMouseEnter={(e) => onAvatarEnter(e, user)}
|
|
219
|
+
* onMouseLeave={onAvatarLeave}
|
|
220
|
+
* >
|
|
221
|
+
* {user.name}
|
|
222
|
+
* </span>
|
|
223
|
+
* ))}
|
|
224
|
+
* </div>
|
|
225
|
+
* </div>
|
|
226
|
+
* );
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function usePresenceFloatingState(options: UsePresenceFloatingStateOptions): UsePresenceFloatingStateReturn;
|
|
230
|
+
|
|
231
|
+
export { type PresenceConfig, type PresenceUser, type UsePresenceAvatarsStateOptions, type UsePresenceAvatarsStateReturn, type UsePresenceFloatingStateOptions, type UsePresenceFloatingStateReturn, type UsePresenceOptions, type UsePresenceWatchOptions, disconnectPresenceSocket, getPresenceSocket, initPresenceSocket, isPresenceSocketInitialized, usePresence, usePresenceAvatarsState, usePresenceFloatingState, usePresenceWatch };
|