300 lines
7.8 KiB
TypeScript
300 lines
7.8 KiB
TypeScript
|
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>
|
||
|
)
|
||
|
}
|
||
|
)
|