diff --git a/packages/web/src/components/MentionInput.tsx b/packages/web/src/components/MentionInput.tsx deleted file mode 100644 index c5bed47..0000000 --- a/packages/web/src/components/MentionInput.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { useState, useRef, useEffect, useCallback } from 'react' -import { api } from '../lib/api' -import Avatar from './Avatar' - -interface MentionUser { - id: string - username: string - avatarUrl: string | null -} - -interface Props { - value: string - onChange: (value: string) => void - placeholder?: string - rows?: number - ariaLabel?: string -} - -export default function MentionInput({ value, onChange, placeholder, rows = 3, ariaLabel }: Props) { - const ref = useRef(null) - const dropdownRef = useRef(null) - const [users, setUsers] = useState([]) - const [active, setActive] = useState(0) - const [query, setQuery] = useState('') - const debounceRef = useRef>() - - const getMentionQuery = useCallback((): string | null => { - const ta = ref.current - if (!ta) return null - const pos = ta.selectionStart - const before = value.slice(0, pos) - const match = before.match(/@([a-zA-Z0-9_]{2,30})$/) - return match ? match[1] : null - }, [value]) - - const handleChange = (e: React.ChangeEvent) => { - onChange(e.target.value) - } - - useEffect(() => { - const q = getMentionQuery() - if (!q || q.length < 2) { - setUsers([]) - setQuery('') - return - } - if (q === query) return - setQuery(q) - if (debounceRef.current) clearTimeout(debounceRef.current) - debounceRef.current = setTimeout(() => { - api.get<{ users: MentionUser[] }>(`/users/search?q=${encodeURIComponent(q)}`) - .then((r) => { - setUsers(r.users) - setActive(0) - }) - .catch(() => setUsers([])) - }, 200) - return () => { if (debounceRef.current) clearTimeout(debounceRef.current) } - }, [value, getMentionQuery]) - - const insertMention = (username: string) => { - const ta = ref.current - if (!ta) return - const pos = ta.selectionStart - const before = value.slice(0, pos) - const after = value.slice(pos) - const atIdx = before.lastIndexOf('@') - if (atIdx === -1) return - const newValue = before.slice(0, atIdx) + '@' + username + ' ' + after - onChange(newValue) - setUsers([]) - setQuery('') - const newPos = atIdx + username.length + 2 - requestAnimationFrame(() => { - ta.focus() - ta.setSelectionRange(newPos, newPos) - }) - } - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (users.length === 0) return - if (e.key === 'ArrowDown') { - e.preventDefault() - setActive((a) => (a + 1) % users.length) - } else if (e.key === 'ArrowUp') { - e.preventDefault() - setActive((a) => (a - 1 + users.length) % users.length) - } else if (e.key === 'Enter' || e.key === 'Tab') { - if (users[active]) { - e.preventDefault() - insertMention(users[active].username) - } - } else if (e.key === 'Escape') { - setUsers([]) - setQuery('') - } - } - - useEffect(() => { - const handler = (e: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { - setUsers([]) - } - } - document.addEventListener('mousedown', handler) - return () => document.removeEventListener('mousedown', handler) - }, []) - - return ( -
-