300 lines
7.8 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useMemo, useState } from 'react'
import { useDropzone } from 'react-dropzone'
import {
ImageController,
MEDIA_DROPZONE_OPTIONS,
MEDIA_OPTIONS,
MediaOption
} from '../../controllers'
import '../../styles/styles.css'
import { errorFeedback } from '../../types'
import { InputError } from './Error'
import { MediaInputPopover } from './MediaInputPopover'
interface InputFieldProps {
label: string | React.ReactElement
description?: string
type?: 'text' | 'textarea'
placeholder: string
name: string
inputMode?: 'url'
value: string
error?: string
onChange: (name: string, value: string) => void
}
export const InputField = React.memo(
({
label,
description,
type = 'text',
placeholder,
name,
inputMode,
value,
error,
onChange
}: InputFieldProps) => {
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
onChange(name, e.target.value)
}
return (
<div className='inputLabelWrapperMain'>
<label className='form-label labelMain'>{label}</label>
{description && <p className='labelDescriptionMain'>{description}</p>}
{type === 'textarea' ? (
<textarea
className='inputMain'
placeholder={placeholder}
name={name}
value={value}
onChange={handleChange}
></textarea>
) : (
<input
type={type}
className='inputMain'
placeholder={placeholder}
name={name}
inputMode={inputMode}
value={value}
onChange={handleChange}
/>
)}
{error && <InputError message={error} />}
</div>
)
}
)
interface CheckboxFieldProps {
label: string
name: string
isChecked: boolean
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
type?: 'default' | 'stylized'
}
export const CheckboxField = React.memo(
({
label,
name,
isChecked,
handleChange,
type = 'default'
}: CheckboxFieldProps) => (
<div
className={`inputLabelWrapperMain inputLabelWrapperMainAlt${
type === 'stylized' ? ` inputLabelWrapperMainAltStylized` : ''
}`}
>
<label htmlFor={name} className='form-label labelMain'>
{label}
</label>
<input
id={name}
type='checkbox'
className='CheckboxMain'
name={name}
checked={isChecked}
onChange={handleChange}
/>
</div>
)
)
interface InputFieldUncontrolledProps extends React.ComponentProps<'input'> {
label: string | React.ReactElement
description?: string
error?: string
}
/**
* Uncontrolled input component with design classes, label, description and error support
*
* Extends {@link React.ComponentProps<'input'> React.ComponentProps<'input'>}
* @param label
* @param description
* @param error
*
* @see {@link React.ComponentProps<'input'>}
*/
export const InputFieldUncontrolled = ({
label,
description,
error,
...rest
}: InputFieldUncontrolledProps) => (
<div className='inputLabelWrapperMain'>
<label htmlFor={rest.id} className='form-label labelMain'>
{label}
</label>
{description && <p className='labelDescriptionMain'>{description}</p>}
<input className='inputMain' {...rest} />
{error && <InputError message={error} />}
</div>
)
interface CheckboxFieldUncontrolledProps extends React.ComponentProps<'input'> {
label: string
}
export const CheckboxFieldUncontrolled = ({
label,
...rest
}: CheckboxFieldUncontrolledProps) => (
<div className='inputLabelWrapperMain inputLabelWrapperMainAlt inputLabelWrapperMainAltStylized'>
<label htmlFor={rest.id} className='form-label labelMain'>
{label}
</label>
<input type='checkbox' className='CheckboxMain' {...rest} />
</div>
)
interface InputFieldWithImageUpload {
label: string | React.ReactElement
description?: string
multiple?: boolean | undefined
placeholder: string
name: string
inputMode?: 'url'
value: string
error?: string
onChange: (name: string, value: string) => void
}
export const InputFieldWithImageUpload = React.memo(
({
label,
description,
multiple = false,
placeholder,
name,
inputMode,
value,
error,
onChange
}: InputFieldWithImageUpload) => {
const [mediaOption, setMediaOption] = useState<MediaOption>(
MEDIA_OPTIONS[0]
)
const handleOptionChange = useCallback(
(mo: MediaOption) => () => {
setMediaOption(mo)
},
[]
)
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
onChange(name, e.target.value)
},
[name, onChange]
)
const handleUpload = useCallback(
async (acceptedFiles: File[]) => {
try {
const imageController = new ImageController(mediaOption)
const url = await imageController.post(acceptedFiles[0])
onChange(name, url)
} catch (error) {
errorFeedback(error)
}
},
[mediaOption, name, onChange]
)
const {
getRootProps,
getInputProps,
isDragActive,
acceptedFiles,
isFileDialogActive,
isDragAccept,
isDragReject,
fileRejections
} = useDropzone({
...MEDIA_DROPZONE_OPTIONS,
onDrop: handleUpload,
multiple: multiple
})
const dropzoneLabel = useMemo(
() =>
isFileDialogActive
? 'Select files in dialog'
: isDragActive
? isDragAccept
? 'Drop the files here...'
: isDragReject
? 'Drop the files here (one more more unsupported types)...'
: 'TODO'
: 'Click or drag files here',
[isDragAccept, isDragActive, isDragReject, isFileDialogActive]
)
return (
<div className='inputLabelWrapperMain'>
<label className='form-label labelMain'>{label}</label>
{typeof description !== 'undefined' && (
<p className='labelDescriptionMain'>{description}</p>
)}
<div aria-label='upload featuredImageUrl' className='uploadBoxMain'>
<MediaInputPopover
name={name}
acceptedFiles={acceptedFiles}
fileRejections={fileRejections}
/>
<div
className='uploadBoxMainInside'
{...getRootProps()}
tabIndex={-1}
>
<input id='featuredImageUrl-upload' {...getInputProps()} />
<span>{dropzoneLabel}</span>
<div
className='FiltersMainElement'
onClick={(e) => e.stopPropagation()}
>
<div className='dropdown dropdownMain'>
<button
className='btn dropdown-toggle btnMain btnMainDropdown'
aria-expanded='false'
data-bs-toggle='dropdown'
type='button'
>
Image Host: {mediaOption.name}
</button>
<div className='dropdown-menu dropdownMainMenu'>
{MEDIA_OPTIONS.map((mo) => {
return (
<div
key={mo.host}
onClick={handleOptionChange(mo)}
className='dropdown-item dropdownMainMenuItem'
>
{mo.name}
</div>
)
})}
</div>
</div>
</div>
</div>
</div>
<input
type='text'
className='inputMain'
placeholder={placeholder}
name={name}
inputMode={inputMode}
value={value}
onChange={handleChange}
/>
{error && <InputError message={error} />}
</div>
)
}
)