mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-02 17:55:07 +00:00
feat(fakemui): add email input components (addresses, body)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
// fakemui/react/components/email/inputs/BodyEditor.tsx
|
||||
import React, { forwardRef } from 'react'
|
||||
import { Box } from '../../layout/Box'
|
||||
import { useAccessible } from '../../../../src/utils/useAccessible'
|
||||
|
||||
export interface BodyEditorProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
mode?: 'plain' | 'html'
|
||||
onModeChange?: (mode: 'plain' | 'html') => void
|
||||
testId?: string
|
||||
}
|
||||
|
||||
export const BodyEditor = forwardRef<HTMLTextAreaElement, BodyEditorProps>(
|
||||
({ mode = 'plain', onModeChange, testId: customTestId, ...props }, ref) => {
|
||||
const accessible = useAccessible({
|
||||
feature: 'email',
|
||||
component: 'body-editor',
|
||||
identifier: customTestId || 'body'
|
||||
})
|
||||
|
||||
return (
|
||||
<Box className="body-editor">
|
||||
<div className="body-editor-toolbar">
|
||||
<button
|
||||
type="button"
|
||||
className={`mode-btn ${mode === 'plain' ? 'mode-btn--active' : ''}`}
|
||||
onClick={() => onModeChange?.('plain')}
|
||||
>
|
||||
Plain Text
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`mode-btn ${mode === 'html' ? 'mode-btn--active' : ''}`}
|
||||
onClick={() => onModeChange?.('html')}
|
||||
>
|
||||
HTML
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
ref={ref}
|
||||
className="body-editor-textarea"
|
||||
placeholder="Write your message here..."
|
||||
{...accessible}
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
BodyEditor.displayName = 'BodyEditor'
|
||||
@@ -0,0 +1,48 @@
|
||||
// fakemui/react/components/email/inputs/EmailAddressInput.tsx
|
||||
import React, { forwardRef } from 'react'
|
||||
import { TextField, TextFieldProps } from '../../inputs/TextField'
|
||||
import { useAccessible } from '../../../../src/utils/useAccessible'
|
||||
|
||||
export interface EmailAddressInputProps extends Omit<TextFieldProps, 'type'> {
|
||||
onValidate?: (valid: boolean) => void
|
||||
allowMultiple?: boolean
|
||||
}
|
||||
|
||||
export const EmailAddressInput = forwardRef<HTMLInputElement, EmailAddressInputProps>(
|
||||
({ onValidate, allowMultiple = false, testId: customTestId, ...props }, ref) => {
|
||||
const accessible = useAccessible({
|
||||
feature: 'email',
|
||||
component: 'email-input',
|
||||
identifier: customTestId || 'email'
|
||||
})
|
||||
|
||||
const validateEmail = (value: string) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (allowMultiple) {
|
||||
const emails = value.split(',').map(e => e.trim())
|
||||
return emails.every(e => emailRegex.test(e) || e === '')
|
||||
}
|
||||
return emailRegex.test(value) || value === ''
|
||||
}
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const valid = validateEmail(e.target.value)
|
||||
onValidate?.(valid)
|
||||
props.onChange?.(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
ref={ref}
|
||||
type="email"
|
||||
label={props.label || (allowMultiple ? 'Recipients' : 'Email Address')}
|
||||
placeholder={allowMultiple ? 'user@example.com, another@example.com' : 'user@example.com'}
|
||||
{...accessible}
|
||||
{...props}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
EmailAddressInput.displayName = 'EmailAddressInput'
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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 '../../../../src/utils/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
|
||||
})
|
||||
|
||||
const handleAddRecipient = () => {
|
||||
if (inputValue && inputValue.includes('@')) {
|
||||
const newRecipients = [...recipients, inputValue.trim()]
|
||||
onRecipientsChange?.(newRecipients)
|
||||
setInputValue('')
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveRecipient = (index: number) => {
|
||||
const newRecipients = recipients.filter((_, i) => i !== index)
|
||||
onRecipientsChange?.(newRecipients)
|
||||
}
|
||||
|
||||
// Filter out incompatible HTML input attributes
|
||||
const { size: _size, ...textFieldProps } = props
|
||||
|
||||
return (
|
||||
<Box className="recipient-input">
|
||||
<div className="recipient-chips">
|
||||
{recipients.map((recipient, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
onDelete={() => handleRemoveRecipient(index)}
|
||||
>
|
||||
{recipient}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
<TextField
|
||||
ref={ref}
|
||||
type="email"
|
||||
placeholder={`Add ${recipientType} recipient...`}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleAddRecipient()}
|
||||
{...accessible}
|
||||
{...textFieldProps}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
RecipientInput.displayName = 'RecipientInput'
|
||||
@@ -0,0 +1,3 @@
|
||||
export { EmailAddressInput, type EmailAddressInputProps } from './EmailAddressInput'
|
||||
export { RecipientInput, type RecipientInputProps } from './RecipientInput'
|
||||
export { BodyEditor, type BodyEditorProps } from './BodyEditor'
|
||||
Reference in New Issue
Block a user