UNPKG

@gravityforms/components

Version:

UI components for use in Gravity Forms development. Both React and vanilla js flavors.

189 lines (171 loc) 5.84 kB
import { React, PropTypes, classnames } from '@gravityforms/libraries'; import { spacerClasses } from '@gravityforms/utils'; const { forwardRef, useEffect, useRef } = React; /** * @module Text * @description Wraps html text with some preset style options in a configurable tag container. * When editable is true, the text becomes editable on focus, with accessibility and onChange support. * Optionally, a hidden input can store the value for form submission. * * @since 1.1.15 * * @param {object} props Component props. * @param {boolean} props.asHtml Whether or not to accept HTML in the content (ignored when editable). * @param {JSX.Element} props.children React element children (ignored when editable). * @param {string} props.color The text color. * @param {string} props.content The text content. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {string} props.size The font size for the heading. * @param {string|number|Array|object} props.spacing The spacing for the component. * @param {string} props.tagName The tag used for the container element. * @param {string} props.weight The font weight for the heading. * @param {boolean} props.editable Whether the text is editable on focus (default false). * @param {function} props.onChange Handler for text changes when editable. * @param {string} props.name Optional name attribute when editable. * @param {string} props.id Optional id attribute when editable. * @param {boolean} props.useHiddenInput Whether to include a hidden input for form submission (default false). * @param {object|null} ref Ref to the component. * * @return {JSX.Element} The text component. * * @example * // Controlled usage * const [text, setText] = useState("Hello world"); * <Text content={text} editable onChange={setText} /> * * // Self-managed form usage * <form> * <Text content="Hello world" editable useHiddenInput name="text-field" id="text-1" /> * <button type="submit">Submit</button> * </form> */ const Text = forwardRef( ( { asHtml = false, children = null, color = 'port', content = '', customAttributes = {}, customClasses = [], editable = false, id = '', name = '', onChange = () => {}, size = 'text-md', spacing = '', tagName = 'div', useHiddenInput = false, weight = 'regular', }, ref ) => { const effectiveAsHtml = asHtml && ! editable; const internalRef = useRef( null ); const combinedRef = ref || internalRef; const hiddenInputRef = useRef( null ); // Ref for hidden input const componentProps = { className: classnames( { 'gform-text': true, [ `gform-text--color-${ color }` ]: true, [ `gform-typography--size-${ size }` ]: true, [ `gform-typography--weight-${ weight }` ]: true, ...spacerClasses( spacing ), }, customClasses ), ref: combinedRef, ...customAttributes, }; if ( editable ) { componentProps.contentEditable = true; componentProps.tabIndex = 0; componentProps.role = 'textbox'; // Only apply name/id to container if not using hidden input if ( ! useHiddenInput ) { if ( name ) { componentProps.name = name; } if ( id ) { componentProps.id = id; } } if ( ! onChange && ! useHiddenInput ) { console.warn( 'Text component: when editable is true and useHiddenInput is false, onChange prop is recommended for controlled behavior.' ); } } if ( effectiveAsHtml ) { componentProps.dangerouslySetInnerHTML = { __html: content }; } useEffect( () => { if ( editable && combinedRef.current ) { const element = combinedRef.current; if ( element.textContent !== content ) { element.textContent = content; } const handleInput = () => { const newText = element.textContent; if ( onChange ) { onChange( newText ); } if ( useHiddenInput && hiddenInputRef.current ) { hiddenInputRef.current.value = newText; // Sync hidden input } }; element.addEventListener( 'input', handleInput ); return () => element.removeEventListener( 'input', handleInput ); } }, [ editable, content, onChange, useHiddenInput, combinedRef ] ); const Container = tagName; if ( effectiveAsHtml ) { return <Container { ...componentProps } />; } if ( editable ) { return ( <> <Container { ...componentProps } /> { useHiddenInput && ( <input type="hidden" ref={ hiddenInputRef } name={ name } id={ id } defaultValue={ content } /> ) } </> ); } return ( <Container { ...componentProps }> { content } { children } </Container> ); } ); Text.propTypes = { asHtml: PropTypes.bool, children: PropTypes.oneOfType( [ PropTypes.arrayOf( PropTypes.node ), PropTypes.node, ] ), color: PropTypes.string, content: PropTypes.string, customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), editable: PropTypes.bool, id: PropTypes.string, name: PropTypes.string, onChange: PropTypes.func, size: PropTypes.string, spacing: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object, ] ), tagName: PropTypes.string, useHiddenInput: PropTypes.bool, weight: PropTypes.string, }; Text.displayName = 'Text'; export default Text;