- Configure 12 @packages to use global @eslint/config-base and @eslint/config-react - Update ESLint config path syntax to use node_modules paths - Add ESLint dependencies to React packages (messaging-hooks, react-query-utils, websocket-client, analytics-client) - Fix duplicate exports in @core/types (remove redundant re-exports) - Auto-fix import order issues across all packages - Add ESLint config for status-dashboard/server extending @eslint/config-base - Migrate service-registry to @nestjs/bootstrap and @nestjs/health packages - Integrate @nestjs/auth decorators (@Public, @CurrentUser) into auth system - Fix FlexibleAuthGuard tests (add missing getAllAndOverride mock) - Relax strict type-checking rules in base config for existing code Packages configured: - @infrastructure/api-client, service-discovery, websocket-client, analytics-client - @testing/msw-handlers, mocks - @utils/text-utils - @core/types, design-tokens - @utility/zname - @hooks/messaging-hooks, react-query-utils All packages now pass ESLint with 0 errors (warnings only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
/**
|
|
* useTyping Hook
|
|
*
|
|
* Manages typing indicators with debouncing
|
|
*/
|
|
|
|
import { useState, useCallback, useRef, useEffect } from 'react'
|
|
|
|
import type { TypingIndicator, SocketClient, TypingSocketEvents } from '../types'
|
|
|
|
export interface UseTypingOptions {
|
|
debounceMs?: number
|
|
socket?: SocketClient<TypingSocketEvents['emit'], TypingSocketEvents['listen']>
|
|
}
|
|
|
|
/**
|
|
* Hook to manage typing indicators with automatic debouncing
|
|
*/
|
|
export function useTyping(roomId: string, options: UseTypingOptions = {}) {
|
|
const { debounceMs = 500, socket } = options
|
|
const [typing, setTyping] = useState<TypingIndicator[]>([])
|
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
|
|
const isTypingRef = useRef(false)
|
|
|
|
/**
|
|
* Set local user typing state (debounced)
|
|
*/
|
|
const setLocalTyping = useCallback(
|
|
(isTyping: boolean) => {
|
|
// Clear existing timer
|
|
if (debounceTimerRef.current) {
|
|
clearTimeout(debounceTimerRef.current)
|
|
debounceTimerRef.current = null
|
|
}
|
|
|
|
if (isTyping) {
|
|
// Immediately send typing=true
|
|
if (socket && !isTypingRef.current) {
|
|
socket.emit('chat:typing', { roomId, typing: true })
|
|
isTypingRef.current = true
|
|
}
|
|
|
|
// Set timer to send typing=false after debounce period
|
|
debounceTimerRef.current = setTimeout(() => {
|
|
if (socket && isTypingRef.current) {
|
|
socket.emit('chat:typing', { roomId, typing: false })
|
|
isTypingRef.current = false
|
|
}
|
|
}, debounceMs)
|
|
} else {
|
|
// Immediately send typing=false
|
|
if (socket && isTypingRef.current) {
|
|
socket.emit('chat:typing', { roomId, typing: false })
|
|
isTypingRef.current = false
|
|
}
|
|
}
|
|
},
|
|
[roomId, socket, debounceMs],
|
|
)
|
|
|
|
/**
|
|
* Listen for typing events from other users
|
|
*/
|
|
useEffect(() => {
|
|
if (!socket) {return}
|
|
|
|
const handleTyping = (data: { userId: string; typing: boolean }) => {
|
|
setTyping((prev: TypingIndicator[]) => {
|
|
if (data.typing) {
|
|
// Add user to typing list if not already there
|
|
if (!prev.some((t: TypingIndicator) => t.userId === data.userId)) {
|
|
return [...prev, { userId: data.userId, roomId, typing: true }]
|
|
}
|
|
return prev
|
|
} else {
|
|
// Remove user from typing list
|
|
return prev.filter((t: TypingIndicator) => t.userId !== data.userId)
|
|
}
|
|
})
|
|
}
|
|
|
|
socket.on('chat:typing', handleTyping)
|
|
|
|
return () => {
|
|
socket.off('chat:typing', handleTyping)
|
|
|
|
// Clear timer on unmount
|
|
if (debounceTimerRef.current) {
|
|
clearTimeout(debounceTimerRef.current)
|
|
}
|
|
|
|
// Send typing=false on unmount
|
|
if (isTypingRef.current) {
|
|
socket.emit('chat:typing', { roomId, typing: false })
|
|
}
|
|
}
|
|
}, [socket, roomId])
|
|
|
|
return {
|
|
typing,
|
|
setTyping: setLocalTyping,
|
|
isTyping: typing.length > 0,
|
|
}
|
|
}
|