Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
244 lines
7.1 KiB
TypeScript
244 lines
7.1 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* ConsumerInput — consumer-only form fields (direct heroui + RHF).
|
|
*
|
|
* **Design props**
|
|
* - `size`: `sm` | `md` | `lg`
|
|
* - `radius`: `full` (pill) | `control` (10px)
|
|
* - `tone`: `muted` (#E2E2E280 fill) | `bordered` (white + border). Select is always `muted`.
|
|
*
|
|
* Use in `app/(consumer)/**` and `components/consumer/**` instead of
|
|
* `components/formElements/Input`.
|
|
*
|
|
* Guidelines: `frontend/docs/consumer-ui-guidelines.md`
|
|
*/
|
|
|
|
import { useEffect } from 'react'
|
|
import { FormProvider, useForm, useFormContext, type UseFormReturn } from 'react-hook-form'
|
|
|
|
import ConsumerChoiceField from '@/components/consumer/input/ConsumerChoiceField'
|
|
import ConsumerSelectField from '@/components/consumer/input/ConsumerSelectField'
|
|
import ConsumerTextareaField from '@/components/consumer/input/ConsumerTextareaField'
|
|
import { ConsumerNumberField, ConsumerTextField } from '@/components/consumer/input/ConsumerTextField'
|
|
import { defaultValueForConsumerField, type ConsumerInputProps } from '@/components/consumer/input/consumerInputTypes'
|
|
import { texts, format } from '@/texts'
|
|
|
|
export type { ConsumerInputProps } from '@/components/consumer/input/consumerInputTypes'
|
|
|
|
function useOptionalFormContext(): UseFormReturn | null {
|
|
const context = useFormContext() as UseFormReturn | null
|
|
|
|
return context?.control ? context : null
|
|
}
|
|
|
|
function ConsumerInputFields({
|
|
radius = 'control',
|
|
size = 'md',
|
|
tone,
|
|
isClearable = false,
|
|
inputWrapper,
|
|
orientation = 'vertical',
|
|
direction = 'rtl',
|
|
englishDigitsOnly = false,
|
|
otpLength = 4,
|
|
otpClassNames,
|
|
selectKey = 'code',
|
|
selectValue = 'name',
|
|
textAreaMinRows = 5,
|
|
...props
|
|
}: ConsumerInputProps) {
|
|
const { control } = useFormContext()
|
|
const placeholder =
|
|
props.placeholder ??
|
|
(props.generalType === 'select'
|
|
? undefined
|
|
: typeof props.label === 'string'
|
|
? format(texts.common.enterField, { label: props.label })
|
|
: '')
|
|
|
|
switch (props.generalType) {
|
|
case 'numberInput':
|
|
return (
|
|
<ConsumerNumberField
|
|
ariaLabel={props.ariaLabel}
|
|
autoFocus={props.autoFocus}
|
|
className={props.className}
|
|
control={control}
|
|
description={props.description}
|
|
direction={direction}
|
|
disabled={props.disabled}
|
|
englishDigitsOnly={englishDigitsOnly}
|
|
formatOptions={props.formatOptions}
|
|
iconEnd={props.iconEnd}
|
|
iconStart={props.iconStart}
|
|
inputWrapper={inputWrapper}
|
|
isClearable={isClearable}
|
|
label={props.label}
|
|
labelIcon={props.labelIcon}
|
|
maxValue={props.maxValue}
|
|
minValue={props.minValue}
|
|
name={props.name}
|
|
placeholder={placeholder}
|
|
radius={radius}
|
|
readonly={props.readonly}
|
|
required={props.required}
|
|
size={size}
|
|
tone={tone}
|
|
onBlur={props.onBlur}
|
|
onClear={props.onClear}
|
|
/>
|
|
)
|
|
|
|
case 'input':
|
|
return (
|
|
<ConsumerTextField
|
|
ariaLabel={props.ariaLabel}
|
|
autoFocus={props.autoFocus}
|
|
className={props.className}
|
|
control={control}
|
|
description={props.description}
|
|
direction={direction}
|
|
disabled={props.disabled}
|
|
englishDigitsOnly={englishDigitsOnly}
|
|
iconEnd={props.iconEnd}
|
|
iconStart={props.iconStart}
|
|
inputType={props.inputType}
|
|
inputWrapper={inputWrapper}
|
|
isClearable={isClearable}
|
|
label={props.label}
|
|
labelIcon={props.labelIcon}
|
|
maxValue={props.maxValue}
|
|
minValue={props.minValue}
|
|
name={props.name}
|
|
placeholder={placeholder}
|
|
radius={radius}
|
|
readonly={props.readonly}
|
|
required={props.required}
|
|
size={size}
|
|
tone={tone}
|
|
onBlur={props.onBlur}
|
|
onClear={props.onClear}
|
|
onKeyDown={props.onKeyDown}
|
|
/>
|
|
)
|
|
|
|
case 'textarea':
|
|
return (
|
|
<ConsumerTextareaField
|
|
autoFocus={props.autoFocus}
|
|
className={props.className}
|
|
control={control}
|
|
description={props.description}
|
|
disabled={props.disabled}
|
|
inputWrapper={inputWrapper}
|
|
isClearable={isClearable}
|
|
label={props.label}
|
|
name={props.name}
|
|
placeholder={placeholder}
|
|
radius={radius}
|
|
readonly={props.readonly}
|
|
required={props.required}
|
|
textAreaMinRows={textAreaMinRows}
|
|
tone={tone}
|
|
onBlur={props.onBlur}
|
|
/>
|
|
)
|
|
|
|
case 'select':
|
|
return (
|
|
<ConsumerSelectField
|
|
ariaLabel={props.ariaLabel}
|
|
autoFocus={props.autoFocus}
|
|
className={props.className}
|
|
control={control}
|
|
description={props.description}
|
|
disabled={props.disabled}
|
|
iconEnd={props.iconEnd}
|
|
iconStart={props.iconStart}
|
|
inputWrapper={inputWrapper}
|
|
label={props.label}
|
|
multiple={props.multiple}
|
|
name={props.name}
|
|
options={props.selectOptions ?? []}
|
|
placeholder={placeholder}
|
|
radius={radius}
|
|
readonly={props.readonly}
|
|
required={props.required}
|
|
selectKey={selectKey}
|
|
selectValue={selectValue}
|
|
size={size}
|
|
tone={tone}
|
|
/>
|
|
)
|
|
|
|
case 'radio':
|
|
case 'checkbox':
|
|
case 'otp':
|
|
case 'switch':
|
|
return (
|
|
<ConsumerChoiceField
|
|
autoFocus={props.autoFocus}
|
|
className={props.className}
|
|
control={control}
|
|
description={props.description}
|
|
disabled={props.disabled}
|
|
kind={props.generalType}
|
|
label={props.label}
|
|
name={props.name}
|
|
options={props.radioOptions ?? []}
|
|
orientation={orientation}
|
|
otpClassNames={otpClassNames}
|
|
otpLength={otpLength}
|
|
placeholder={placeholder}
|
|
radius={radius}
|
|
readonly={props.readonly}
|
|
required={props.required}
|
|
selectKey={selectKey}
|
|
size={size}
|
|
/>
|
|
)
|
|
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
function ConsumerInputStandalone(props: ConsumerInputProps) {
|
|
const { name, generalType, value, onValueChange } = props
|
|
const methods = useForm({
|
|
defaultValues: { [name]: value ?? defaultValueForConsumerField(generalType) },
|
|
values: value !== undefined ? { [name]: value } : undefined,
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!onValueChange) return
|
|
|
|
const subscription = methods.watch((values, info) => {
|
|
if (info.name && info.name !== name) return
|
|
onValueChange(values[name])
|
|
})
|
|
|
|
return () => {
|
|
subscription.unsubscribe()
|
|
}
|
|
}, [methods, name, onValueChange])
|
|
|
|
return (
|
|
<FormProvider {...methods}>
|
|
<ConsumerInputFields {...props} />
|
|
</FormProvider>
|
|
)
|
|
}
|
|
|
|
export default function ConsumerInput(props: ConsumerInputProps) {
|
|
const form = useOptionalFormContext()
|
|
const isControlled = props.value !== undefined || props.onValueChange !== undefined
|
|
|
|
if (!form || isControlled) {
|
|
return <ConsumerInputStandalone {...props} />
|
|
}
|
|
|
|
return <ConsumerInputFields {...props} />
|
|
}
|