mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-30 16:54:57 +00:00
Accessibility: - All components: data-testid, aria-label, aria-pressed, aria-current - ComposeWindow: role=dialog, aria-modal, focus trap, Escape handler - EmailCard: role=article, keyboard nav (Enter/Space), aria-current - ThreadList: role=list with listitem wrappers, role=status empty state - FolderNavigation: role=navigation, role=list, aria-current - RecipientInput: role=group, aria-label per type, onKeyPress→onKeyDown - BodyEditor: role=toolbar, aria-pressed on mode buttons - StarButton: MaterialIcon (star/star_border), dynamic aria-label - MarkAsReadCheckbox: dynamic label based on read state - EmailHeader: role=banner, <time> element, data-testids Component decomposition: - Extract useEmailClient hook (state + callbacks) - Extract demo-emails.ts (data constants) - EmailClientContent.tsx: 267→127 LOC (composition only) New: useKeyboardShortcuts hook (Gmail-style, 47 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
// fakemui/react/components/email/inputs/RecipientInput.tsx
|
|
import React, { forwardRef, useState } from 'react'
|
|
import { Box } from '../../layout/Box'
|
|
import { TextField } from '../../inputs/TextField'
|
|
import { Chip } from '../../data-display/Chip'
|
|
import { useAccessible } from '../../../../hooks/useAccessible'
|
|
|
|
export interface RecipientInputProps
|
|
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
recipients?: string[]
|
|
onRecipientsChange?: (recipients: string[]) => void
|
|
recipientType?: 'to' | 'cc' | 'bcc'
|
|
testId?: string
|
|
}
|
|
|
|
export const RecipientInput = forwardRef<
|
|
HTMLInputElement,
|
|
RecipientInputProps
|
|
>(({
|
|
recipients = [],
|
|
onRecipientsChange,
|
|
recipientType = 'to',
|
|
testId: customTestId,
|
|
...props
|
|
}, ref) => {
|
|
const [inputValue, setInputValue] = useState('')
|
|
const accessible = useAccessible({
|
|
feature: 'email',
|
|
component: 'recipient-input',
|
|
identifier: customTestId || recipientType,
|
|
ariaLabel: `Add ${recipientType} recipient`
|
|
})
|
|
|
|
const addRecipient = () => {
|
|
if (inputValue && inputValue.includes('@')) {
|
|
onRecipientsChange?.([
|
|
...recipients, inputValue.trim()
|
|
])
|
|
setInputValue('')
|
|
}
|
|
}
|
|
|
|
const removeRecipient = (index: number) => {
|
|
onRecipientsChange?.(
|
|
recipients.filter((_, i) => i !== index)
|
|
)
|
|
}
|
|
|
|
const { size: _size, ...textFieldProps } = props
|
|
|
|
return (
|
|
<Box
|
|
className="recipient-input"
|
|
role="group"
|
|
aria-label={`${recipientType.toUpperCase()} recipients`}
|
|
>
|
|
<div className="recipient-chips">
|
|
{recipients.map((r, i) => (
|
|
<Chip
|
|
key={i}
|
|
onDelete={() => removeRecipient(i)}
|
|
aria-label={`Remove ${r}`}
|
|
>
|
|
{r}
|
|
</Chip>
|
|
))}
|
|
</div>
|
|
<TextField
|
|
ref={ref}
|
|
type="email"
|
|
placeholder={`Add ${recipientType} recipient...`}
|
|
value={inputValue}
|
|
onChange={(e) => setInputValue(e.target.value)}
|
|
onKeyDown={(e) =>
|
|
e.key === 'Enter' && addRecipient()
|
|
}
|
|
{...accessible}
|
|
{...textFieldProps}
|
|
/>
|
|
</Box>
|
|
)
|
|
})
|
|
|
|
RecipientInput.displayName = 'RecipientInput'
|