modules_MetaBox_index.js

import { classnames, PropTypes, React } from '@gravityforms/libraries';
import { ConditionalWrapper, useStateWithDep } from '@gravityforms/react-utils';
import { spacerClasses } from '@gravityforms/utils';
import Button from '../../elements/Button';

const { forwardRef } = React;

/**
 * @module MetaBox
 * @description A metabox component with optional header and footer.
 *
 * @since 1.1.15
 *
 * @param {object}                     props                       Component props.
 * @param {boolean}                    props.collapsible           Whether the meta box is collapsible.
 * @param {string}                     props.collapsibleIcon       The icon for the collapsible button.
 * @param {string}                     props.collapsibleIconPrefix The icon prefix for the collapsible button.
 * @param {boolean}                    props.collapsed             Whether the meta box is collapsed.
 * @param {string}                     props.contentId             The ID for the content, used for a11y when collapsible is true.
 * @param {JSX.Element}                props.children              React element children.
 * @param {object}                     props.customAttributes      Custom attributes for the layout.
 * @param {string|Array|object}        props.customClasses         Custom classes for the layout.
 * @param {JSX.Element}                props.HeaderContent         The header element.
 * @param {object}                     props.i18n                  Internationalization object.
 * @param {JSX.Element}                props.FooterContent         The footer element.
 * @param {string|number|Array|object} props.spacing               The spacing for the component, as a string, number, array, or object.
 * @param {string}                     props.tagName               The tag name for the layout.
 *
 * @return {JSX.Element} The MetaBox component.
 * @example
 * import MetaBox from '@gravityforms/components/admin/modules/MetaBox';
 *
 * const HeaderContent = ( props ) => <SomeComponent { ...props } />;
 * const FooterContent = ( props ) => <SomeComponent { ...props } />;
 *
 * <MetaBox
 *    HeaderContent={ HeaderContent }
 *    FooterContent={ FooterContent }
 *    customClasses={ 'my-custom-class' }
 *    customAttributes={ { 'data-custom-attribute': 'custom' } }
 *    tagName={ 'section' }
 * >
 *   <div>Content</div>
 *</MetaBox>
 *
 */
const MetaBox = forwardRef( ( {
	children = null,
	collapsible = false,
	collapsibleIcon = 'accordion-arrow',
	collapsibleIconPrefix = 'gform-icon',
	collapsed = false,
	contentId = '',
	customAttributes = {},
	customClasses = {},
	FooterContent = null,
	HeaderContent = null,
	i18n = {},
	spacing = '',
	tagName = 'div',
	...props
}, ref ) => {
	const [ isCollapsed, setIsCollapsed ] = useStateWithDep( collapsed );

	const {
		collapsedAriaLabel = '',
		expandedAriaLabel = '',
	} = i18n;

	const componentProps = {
		className: classnames( {
			'gform-meta-box': true,
			'gform-meta-box--is-collapsible': collapsible,
			'gform-meta-box--is-collapsed': isCollapsed,
			...spacerClasses( spacing ),
		}, customClasses ),
		...customAttributes,
		'data-testid': 'gform-meta-box',
		ref,
	};

	const toggleButtonProps = {
		customAttributes: {
			'aria-controls': contentId ? contentId : undefined,
			'aria-expanded': ! isCollapsed,
			'aria-label': isCollapsed ? collapsedAriaLabel : expandedAriaLabel,
		},
		customClasses: [ 'gform-meta-box__toggle' ],
		icon: collapsibleIcon,
		iconAttributes: {
			customClasses: [ 'gform-meta-box__toggle-icon' ],
		},
		iconPrefix: collapsibleIconPrefix,
		onClick: () => setIsCollapsed( ! isCollapsed ),
		type: 'unstyled',
	};

	const Container = tagName;

	return (
		<Container { ...componentProps }>
			{ HeaderContent &&
				<div
					className="gform-meta-box__header"
					data-testid="gform-meta-box-header"
				>
					<ConditionalWrapper
						condition={ collapsible }
						wrapper={ ( ch ) => <Button { ...toggleButtonProps }>{ ch }</Button> }
					>
						<HeaderContent { ...props } />
					</ConditionalWrapper>
				</div>
			}
			<div
				className="gform-meta-box__content"
				id={ contentId ? contentId : undefined }
				style={ { display: isCollapsed ? 'none' : '' } }
			>
				{ children }
			</div>
			{ FooterContent &&
				<div
					className="gform-meta-box__footer"
					data-testid="gform-meta-box-footer"
				>
					<FooterContent { ...props } />
				</div>
			}
		</Container>
	);
} );

MetaBox.propTypes = {
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ),
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	FooterContent: PropTypes.func,
	HeaderContent: PropTypes.func,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	tagName: PropTypes.string,
};

MetaBox.displayName = 'MetaBox';

export default MetaBox;