chore(ui): consolidates field label, error, and description components

This commit is contained in:
Jacob Fletcher
2024-03-21 11:23:51 -04:00
parent 85ffc5d8bf
commit 64d6163f13
30 changed files with 124 additions and 87 deletions

View File

@@ -28,12 +28,14 @@ const Iterable: React.FC<Props> = ({
return (
<div className={baseClass}>
{'label' in field.fieldComponentProps && field.fieldComponentProps.label && (
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.fieldComponentProps.label, i18n)}
</Label>
)}
{'label' in field.fieldComponentProps &&
field.fieldComponentProps.label &&
typeof field.fieldComponentProps.label !== 'function' && (
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.fieldComponentProps.label, i18n)}
</Label>
)}
{maxRows > 0 && (
<React.Fragment>
{Array.from(Array(maxRows).keys()).map((row, i) => {

View File

@@ -23,12 +23,14 @@ const Nested: React.FC<Props> = ({
}) => {
return (
<div className={baseClass}>
{'label' in field.fieldComponentProps && field.fieldComponentProps.label && (
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.fieldComponentProps.label, i18n)}
</Label>
)}
{'label' in field.fieldComponentProps &&
field.fieldComponentProps.label &&
typeof field.fieldComponentProps.label !== 'function' && (
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.fieldComponentProps.label, i18n)}
</Label>
)}
<div
className={[`${baseClass}__wrap`, !disableGutter && `${baseClass}__wrap--gutter`]
.filter(Boolean)

View File

@@ -99,7 +99,9 @@ const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, versio
}
const label =
'label' in field.fieldComponentProps && typeof field.fieldComponentProps.label !== 'boolean'
'label' in field.fieldComponentProps &&
typeof field.fieldComponentProps.label !== 'boolean' &&
typeof field.fieldComponentProps.label !== 'function'
? field.fieldComponentProps.label
: ''

View File

@@ -36,6 +36,7 @@ const Text: React.FC<Props> = ({
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{'label' in field.fieldComponentProps &&
typeof field.fieldComponentProps.label !== 'function' &&
getTranslation(field.fieldComponentProps.label || '', i18n)}
</Label>
<DiffViewer

View File

@@ -1,4 +1,5 @@
export type ErrorProps = {
CustomError?: React.ReactNode
alignCaret?: 'center' | 'left' | 'right'
message?: string
path?: string

View File

@@ -1,4 +1,5 @@
export type LabelProps = {
CustomLabel?: React.ReactNode
htmlFor?: string
label?: JSX.Element | Record<string, string> | false | string
required?: boolean

View File

@@ -79,7 +79,9 @@ export const buildColumns = (args: {
undefined
}
label={
'label' in field.fieldComponentProps && field.fieldComponentProps.label
'label' in field.fieldComponentProps &&
field.fieldComponentProps.label &&
typeof field.fieldComponentProps.label !== 'function'
? field.fieldComponentProps.label
: 'name' in field
? field.name
@@ -102,7 +104,11 @@ export const buildColumns = (args: {
Cell,
Heading,
},
label: 'label' in field.fieldComponentProps ? field.fieldComponentProps.label : undefined,
label:
'label' in field.fieldComponentProps &&
typeof field.fieldComponentProps.label !== 'function'
? field.fieldComponentProps.label
: undefined,
}
acc.push(column)

View File

@@ -198,14 +198,14 @@ export const ArrayField: React.FC<ArrayFieldProps> = (props) => {
>
{showError && (
<div className={`${baseClass}__error-wrap`}>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
</div>
)}
<header className={`${baseClass}__header`}>
<div className={`${baseClass}__header-wrap`}>
<div className={`${baseClass}__header-content`}>
<h3 className={`${baseClass}__title`}>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
</h3>
{fieldHasErrors && fieldErrorCount > 0 && (
<ErrorPill count={fieldErrorCount} i18n={i18n} withMessage />
@@ -234,11 +234,7 @@ export const ArrayField: React.FC<ArrayFieldProps> = (props) => {
</ul>
)}
</div>
{CustomDescription !== undefined ? (
CustomDescription
) : (
<FieldDescription {...(descriptionProps || {})} />
)}
<FieldDescription CustomDescription={CustomDescription} {...(descriptionProps || {})} />
</header>
<NullifyLocaleField fieldValue={value} localized={localized} path={path} />
{(rows.length > 0 || (!valid && (showRequired || showMinRows))) && (

View File

@@ -208,14 +208,14 @@ export const BlocksField: React.FC<BlocksFieldProps> = (props) => {
>
{showError && (
<div className={`${baseClass}__error-wrap`}>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
</div>
)}
<header className={`${baseClass}__header`}>
<div className={`${baseClass}__header-wrap`}>
<div className={`${baseClass}__heading-with-error`}>
<h3>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
</h3>
{fieldHasErrors && fieldErrorCount > 0 && (
<ErrorPill count={fieldErrorCount} i18n={i18n} withMessage />
@@ -244,11 +244,7 @@ export const BlocksField: React.FC<BlocksFieldProps> = (props) => {
</ul>
)}
</div>
{CustomDescription !== undefined ? (
CustomDescription
) : (
<FieldDescription {...(descriptionProps || {})} />
)}
<FieldDescription CustomDescription={CustomDescription} {...(descriptionProps || {})} />
</header>
<NullifyLocaleField fieldValue={value} localized={localized} path={path} />
{(rows.length > 0 || (!valid && (showRequired || showMinRows))) && (

View File

@@ -74,7 +74,7 @@ export const CheckboxInput: React.FC<Props> = ({
</span>
{AfterInput}
</div>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
</div>
)
}

View File

@@ -90,7 +90,7 @@ const CheckboxField: React.FC<CheckboxFieldProps> = (props) => {
}}
>
<div className={`${baseClass}__error-wrap`}>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
</div>
<CheckboxInput
AfterInput={AfterInput}

View File

@@ -83,8 +83,8 @@ const CodeField: React.FC<CodeFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div>
{BeforeInput}
<CodeEditor

View File

@@ -124,7 +124,7 @@ const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
header={
<div className={`${baseClass}__row-label-wrap`}>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
</div>
}
@@ -141,11 +141,7 @@ const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
schemaPath={schemaPath}
/>
</CollapsibleElement>
{CustomDescription !== undefined ? (
CustomDescription
) : (
<FieldDescription {...(descriptionProps || {})} />
)}
<FieldDescription CustomDescription={CustomDescription} {...(descriptionProps || {})} />
</div>
</Fragment>
)

View File

@@ -87,9 +87,9 @@ const DateTimeField: React.FC<DateFieldProps> = (props) => {
}}
>
<div className={`${baseClass}__error-wrap`}>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
</div>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div className={`${baseClass}__input-wrapper`} id={`field-${path.replace(/\./g, '__')}`}>
{BeforeInput}
<DatePickerField

View File

@@ -73,8 +73,8 @@ const EmailField: React.FC<EmailFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div>
{BeforeInput}
<input

View File

@@ -111,8 +111,8 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div>
{BeforeInput}
<CodeEditor

View File

@@ -151,8 +151,8 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
width,
}}
>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<FieldError CustomError={CustomError} {...(errorProps || {})} />
{hasMany ? (
<ReactSelect
className={`field-${path.replace(/\./g, '__')}`}

View File

@@ -67,8 +67,8 @@ export const PasswordField: React.FC<PasswordFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<input
autoComplete={autoComplete}
disabled={formProcessing || disabled}

View File

@@ -100,10 +100,10 @@ const PointField: React.FC<PointFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<ul className={`${baseClass}__wrap`}>
<li>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div className="input-wrapper">
{BeforeInput}
<input
@@ -120,7 +120,7 @@ const PointField: React.FC<PointFieldProps> = (props) => {
</div>
</li>
<li>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div className="input-wrapper">
{BeforeInput}
<input

View File

@@ -96,9 +96,9 @@ const RadioGroupField: React.FC<RadioFieldProps> = (props) => {
}}
>
<div className={`${baseClass}__error-wrap`}>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
</div>
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
{options.map((option) => {
let optionValue = ''

View File

@@ -440,8 +440,8 @@ const RelationshipField: React.FC<RelationshipFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
{!errorLoading && (
<div className={`${baseClass}__wrap`}>
<ReactSelect

View File

@@ -145,8 +145,8 @@ export const SelectField: React.FC<SelectFieldProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<div>
{BeforeInput}
<ReactSelect

View File

@@ -58,8 +58,8 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
{hasMany ? (
<ReactSelect
className={`field-${path.replace(/\./g, '__')}`}

View File

@@ -52,8 +52,8 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
{BeforeInput}
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
<div className="textarea-inner">

View File

@@ -131,8 +131,8 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
width,
}}
>
{CustomError !== undefined ? CustomError : <FieldError {...(errorProps || {})} />}
{CustomLabel !== undefined ? CustomLabel : <FieldLabel {...(labelProps || {})} />}
<FieldError CustomError={CustomError} {...(errorProps || {})} />
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
{collection?.upload && (
<React.Fragment>
{file && !missingFile && (

View File

@@ -12,7 +12,7 @@ export { FieldDescriptionProps }
const baseClass = 'field-description'
export const FieldDescription: React.FC<FieldDescriptionProps> = (props) => {
const DefaultFieldDescription: React.FC<FieldDescriptionProps> = (props) => {
const { className, description, marginPlacement } = props
const { path } = useFieldProps()
@@ -38,3 +38,13 @@ export const FieldDescription: React.FC<FieldDescriptionProps> = (props) => {
return null
}
export const FieldDescription: React.FC<FieldDescriptionProps> = (props) => {
const { CustomDescription } = props
if (CustomDescription !== undefined) {
return CustomDescription
}
return <DefaultFieldDescription {...props} />
}

View File

@@ -1,4 +1,5 @@
export type FieldDescriptionProps = {
CustomDescription?: React.ReactNode
className?: string
description?: Record<string, string> | string
marginPlacement?: 'bottom' | 'top'

View File

@@ -11,7 +11,7 @@ import './index.scss'
const baseClass = 'field-error'
export const FieldError: React.FC<ErrorProps> = (props) => {
const DefaultFieldError: React.FC<ErrorProps> = (props) => {
const {
alignCaret = 'right',
message: messageFromProps,
@@ -40,3 +40,13 @@ export const FieldError: React.FC<ErrorProps> = (props) => {
return null
}
export const FieldError: React.FC<ErrorProps> = (props) => {
const { CustomError } = props
if (CustomError !== undefined) {
return CustomError
}
return <DefaultFieldError {...props} />
}

View File

@@ -10,8 +10,9 @@ import { useFieldProps } from '../FieldPropsProvider/index.js'
import { useForm } from '../Form/context.js'
import './index.scss'
export const FieldLabel: React.FC<LabelProps> = (props) => {
const DefaultFieldLabel: React.FC<LabelProps> = (props) => {
const { htmlFor: htmlForFromProps, label: labelFromProps, required = false } = props
const { uuid } = useForm()
const { path } = useFieldProps()
const htmlFor = htmlForFromProps || generateFieldID(path, uuid)
@@ -29,3 +30,13 @@ export const FieldLabel: React.FC<LabelProps> = (props) => {
return null
}
export const FieldLabel: React.FC<LabelProps> = (props) => {
const { CustomLabel } = props
if (CustomLabel !== undefined) {
return CustomLabel
}
return <DefaultFieldLabel {...props} />
}

View File

@@ -1,32 +1,34 @@
import type { FieldMap } from '../../providers/ComponentMap/buildComponentMap/types.js'
export const buildPathSegments = (parentPath: string, fieldMap: FieldMap): string[] => {
const pathNames = fieldMap.reduce((acc, subField) => {
if ('fieldMap' in subField.fieldComponentProps) {
const fieldMap = subField.fieldComponentProps.fieldMap
const pathNames = fieldMap.reduce((acc, field) => {
const fieldMap =
'fieldMap' in field.fieldComponentProps ? field.fieldComponentProps.fieldMap : undefined
if (fieldMap && subField.isFieldAffectingData) {
if (fieldMap) {
if (field.isFieldAffectingData) {
// group, block, array
const name = 'name' in subField ? subField.name : 'unnamed'
const name = 'name' in field ? field.name : 'unnamed'
acc.push(parentPath ? `${parentPath}.${name}.` : `${name}.`)
} else if (fieldMap) {
} else {
// rows, collapsibles, unnamed-tab
acc.push(...buildPathSegments(parentPath, fieldMap))
} else if (subField.type === 'tabs') {
// tabs
'tabs' in subField.fieldComponentProps &&
subField.fieldComponentProps?.tabs?.forEach((tab) => {
let tabPath = parentPath
if ('name' in tab) {
tabPath = parentPath ? `${parentPath}.${tab.name}` : tab.name
}
acc.push(...buildPathSegments(tabPath, tab.fieldMap))
})
} else if (subField.isFieldAffectingData) {
// text, number, date, etc.
const name = 'name' in subField ? subField.name : 'unnamed'
acc.push(parentPath ? `${parentPath}.${name}` : name)
}
} else if (field.type === 'tabs') {
// tabs
if ('tabs' in field.fieldComponentProps) {
field.fieldComponentProps.tabs?.forEach((tab) => {
let tabPath = parentPath
if ('name' in tab) {
tabPath = parentPath ? `${parentPath}.${tab.name}` : tab.name
}
acc.push(...buildPathSegments(tabPath, tab.fieldMap))
})
}
} else if (field.isFieldAffectingData) {
// text, number, date, etc.
const name = 'name' in field ? field.name : 'unnamed'
acc.push(parentPath ? `${parentPath}.${name}` : name)
}
return acc