remove unused MentionInput component

This commit is contained in:
2026-03-21 17:41:57 +02:00
parent 5ba25fb956
commit a8ac768e3f

View File

@@ -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<HTMLTextAreaElement>(null)
const dropdownRef = useRef<HTMLDivElement>(null)
const [users, setUsers] = useState<MentionUser[]>([])
const [active, setActive] = useState(0)
const [query, setQuery] = useState('')
const debounceRef = useRef<ReturnType<typeof setTimeout>>()
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<HTMLTextAreaElement>) => {
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 (
<div style={{ position: 'relative' }}>
<textarea
ref={ref}
className="input w-full"
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
rows={rows}
style={{ resize: 'vertical' }}
aria-label={ariaLabel || 'Comment'}
/>
{users.length > 0 && (
<div
ref={dropdownRef}
style={{
position: 'absolute',
left: 0,
bottom: -4,
transform: 'translateY(100%)',
zIndex: 50,
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
minWidth: 200,
maxHeight: 240,
overflow: 'auto',
}}
>
{users.map((u, i) => (
<button
key={u.id}
onClick={() => insertMention(u.username)}
className="flex items-center gap-2 w-full px-3"
style={{
minHeight: 44,
background: i === active ? 'var(--surface-hover)' : 'transparent',
border: 'none',
cursor: 'pointer',
fontSize: 'var(--text-sm)',
color: 'var(--text)',
textAlign: 'left',
}}
onMouseEnter={() => setActive(i)}
>
<Avatar userId={u.id} name={u.username} avatarUrl={u.avatarUrl} size={22} />
<span>@{u.username}</span>
</button>
))}
</div>
)}
</div>
)
}