Source: components/form-renderer-custom-fields/LabelField.es.js

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

import ClayForm, {ClayInput} from '@clayui/form';
import ClayIcon from '@clayui/icon';
import {DataLayoutBuilderActions} from 'data-engine-taglib';
import React, {useEffect, useState} from 'react';

import useDebounce from '../../hooks/useDebounce.es';
import {sub} from '../../utils/lang.es';
import FieldBase from './shared/FieldBase.es';
import {STRUCTURE_LEVEL, VIEW_LEVEL} from './shared/constants.es';
import {
	containsFieldInsideFormBuilder,
	setPropertyAtStructureLevel,
	setPropertyAtViewLevel,
} from './shared/utils.es';

const PROPERTY_NAME = 'label';

const LEVEL = {
	[STRUCTURE_LEVEL]: {
		label: Liferay.Language.get('label-for-all-forms-using-this-field'),
	},
	[VIEW_LEVEL]: {
		label: Liferay.Language.get('label-for-only-this-form'),
	},
};

/**
 * Get initial selected value
 * @param {object} state
 */
function getInitialSelectedValue({dataDefinitionField: {customProperties}}) {
	if (customProperties.labelAtStructureLevel) {
		return STRUCTURE_LEVEL;
	}

	return VIEW_LEVEL;
}

/**
 * Get localized value
 * @param {object} value
 * @param {object} state
 */
function getLocalizedValue(value, {defaultLanguageId, editingLanguageId}) {
	return {
		...value,
		[editingLanguageId]:
			value[editingLanguageId] || value[defaultLanguageId],
	};
}

/**
 * Get initial value
 * @param {string} selectedValue
 * @param {object} state
 */
function getInitialValue(selectedValue, state) {
	const {dataDefinitionField, dataLayoutField} = state;

	if (selectedValue === VIEW_LEVEL) {
		return getLocalizedValue(dataLayoutField.label, state);
	}

	return getLocalizedValue(dataDefinitionField.label, state);
}

/**
 * Update variable labelAtStructureLevel
 * @param {object} state
 * @param {function} dispatch
 * @param {string} selectedValue
 */
function updateLabelAtStructureLevel(
	{dataDefinitionFields, fieldName},
	dispatch,
	selectedValue
) {
	dispatch({
		payload: {
			dataDefinitionFields: dataDefinitionFields.map((field) => {
				if (field.name === fieldName) {
					return {
						...field,
						customProperties: {
							...field.customProperties,
							labelAtStructureLevel:
								selectedValue === STRUCTURE_LEVEL,
						},
					};
				}

				return field;
			}),
		},
		type: DataLayoutBuilderActions.UPDATE_DATA_DEFINITION_FIELDS,
	});
}

export default function LabelField({
	dataLayoutBuilder,
	dispatch,
	field,
	state,
}) {
	const [selectedValue, setSelectedValue] = useState(
		getInitialSelectedValue(state)
	);

	const [value, setValue] = useState(getInitialValue(selectedValue, state));

	const {
		availableLanguageIds,
		dataDefinitionField,
		dataLayoutField,
		defaultLanguageId,
		editingLanguageId,
	} = state;

	const debounce = useDebounce(value);

	useEffect(() => {
		if (containsFieldInsideFormBuilder(dataLayoutBuilder, state)) {
			dataLayoutBuilder.formBuilderWithLayoutProvider.refs.layoutProvider?.dispatch?.(
				'fieldEdited',
				{
					propertyName: PROPERTY_NAME,
					propertyValue:
						value[editingLanguageId] || value[defaultLanguageId],
				}
			);
		}

		const callbackFn = (fn) => fn(state, dispatch);

		if (selectedValue === VIEW_LEVEL) {
			callbackFn(
				setPropertyAtViewLevel(PROPERTY_NAME, {
					...dataLayoutField.label,
					...value,
				})
			);
		}
		else {
			callbackFn(
				setPropertyAtStructureLevel(PROPERTY_NAME, {
					...dataDefinitionField.label,
					...value,
				})
			);
		}

		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [debounce]);

	useEffect(() => {
		setSelectedValue(getInitialSelectedValue(state));
		setValue(getInitialValue(selectedValue, state));

		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [field.name, selectedValue]);

	return (
		<FieldBase
			className="form-renderer-label-field"
			onSelectedValueChange={(selectedValue) => {
				if (selectedValue === VIEW_LEVEL) {
					setValue(getLocalizedValue(dataLayoutField.label, state));
				}
				else {
					setValue(
						getLocalizedValue(dataDefinitionField.label, state)
					);
				}

				updateLabelAtStructureLevel(state, dispatch, selectedValue);

				setSelectedValue(selectedValue);
			}}
			options={LEVEL}
			selectedValue={selectedValue}
			title={Liferay.Language.get('label-options')}
		>
			<label className="ddm-label">
				{field.label}

				<span className="ddm-tooltip">
					<ClayIcon
						symbol="question-circle-full"
						title={field.tooltip}
					/>
				</span>
			</label>

			<ClayInput
				autoFocus
				className="ddm-field-text"
				name={field.name}
				onChange={({target: {value: currentValue}}) => {
					setValue({
						...value,
						[editingLanguageId]: currentValue,
					});

					if (!availableLanguageIds.includes(editingLanguageId)) {
						dispatch({
							payload: editingLanguageId,
							type:
								DataLayoutBuilderActions.UPDATE_DATA_DEFINITION_AVAILABLE_LANGUAGE,
						});
					}
				}}
				placeholder={field.placeholder}
				type="text"
				value={value[editingLanguageId] || value[defaultLanguageId]}
			/>

			{selectedValue === VIEW_LEVEL && (
				<ClayForm.FeedbackGroup>
					<ClayForm.FeedbackItem>
						{sub(Liferay.Language.get('object-field-label-x'), [
							getLocalizedValue(dataDefinitionField.label, state)[
								editingLanguageId
							],
						])}
					</ClayForm.FeedbackItem>
				</ClayForm.FeedbackGroup>
			)}
		</FieldBase>
	);
}