/** * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) Infonomic Company Limited */ import { useCallback } from 'react' import type { Field, FieldComponentSlots, TextField as FieldType } from '@byline/core' import { Input, Label } from '@byline/ui/react' import cx from 'clsx' import { useFieldError, useFieldValue } from '../../forms/form-context' import { useScopedDomId } from '../../forms/form-dom-scope' import { LocaleBadge } from '../locale-badge' import styles from './text-field.module.css' export const TextField = ({ field, value, defaultValue, onChange, id, path, locale, components, }: { field: FieldType value?: string defaultValue?: string onChange?: (value: string) => void id?: string path?: string /** When provided, renders a LocaleBadge next to the field label. */ locale?: string /** Optional UI component slot overrides from the admin config. */ components?: FieldComponentSlots }) => { const fieldPath = path ?? field.name const fieldError = useFieldError(fieldPath) const fieldValue = useFieldValue(fieldPath) const incomingValue = value ?? fieldValue ?? defaultValue ?? '' const htmlId = useScopedDomId(fieldPath, id) const handleChange = useCallback( (value: string) => { if (onChange && !field.readOnly) { onChange(value) } }, [onChange, field.readOnly] ) // Custom component slots (from admin config) const slots = components const CustomLabel = slots?.Label const CustomHelpText = slots?.HelpText const CustomField = slots?.Field const BeforeField = slots?.beforeField const AfterField = slots?.afterField // Shared props available to every slot component const slotBaseProps = { field: field as Field, path: fieldPath, value: incomingValue, error: fieldError, id: htmlId, } // When a locale is active, render a custom Label+badge and suppress the // Input's own label so the locale indicator appears in the label row. const showBadge = !!locale && !!field.label // Determine whether the label is handled externally (by a custom slot or // the locale badge row) so Input doesn't render its own. const hasCustomLabel = !!CustomLabel const suppressInputLabel = showBadge || hasCustomLabel const suppressInputHelpText = !!CustomHelpText const labelRowClass = cx('byline-field-text-label-row', styles['label-row']) // ── Label rendering ────────────────────────────────────────── const renderLabel = () => { if (hasCustomLabel) { return (
{showBadge && }
) } if (showBadge) { return (
) } return null } // ── Field input rendering ──────────────────────────────────── const renderInput = () => { if (CustomField) { return ( ) } return ( handleChange(e.target.value)} error={fieldError != null} errorText={fieldError} /> ) } return (
{renderLabel()} {BeforeField && } {renderInput()} {AfterField && } {CustomHelpText && }
) }