UNPKG

@gravityforms/components

Version:

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

373 lines (355 loc) 13.4 kB
import { React, PropTypes, classnames } from '@gravityforms/libraries'; import { spacerClasses, sprintf, trigger } from '@gravityforms/utils'; import { BLOCK, INLINE } from './constants'; import RepeaterItem from './RepeaterItem'; import Button from '../../elements/Button'; const { useState } = React; /** * @module Repeater * @description A Repeater component with id wrapper. * * @since 4.7.0 * * @param {object} props Component props. * @param {object} props.addButtonAttributes Custom attributes for the add button. * @param {string|Array|object} props.addButtonClasses Custom classes for the add button. * @param {object} props.blockHeaderAttributes Custom attributes for the block header (only used in block mode). * @param {string|Array|object} props.blockHeaderClasses Custom classes for the block header (only used in block mode). * @param {boolean} props.collapsible If the repeater is collapsible. * @param {object} props.collapsibleButtonAttributes Custom attributes for the collapsible button. * @param {string|Array|object} props.collapsibleButtonClasses Custom classes for the collapsible button. * @param {object} props.customAttributes Custom attributes for the component. * @param {string|Array|object} props.customClasses Custom classes for the component. * @param {object} props.deleteButtonAttributes Custom attributes for the delete button. * @param {string|Array|object} props.deleteButtonClasses Custom classes for the delete button. * @param {object} props.downButtonAttributes Custom attributes for the down button. * @param {string|Array|object} props.downButtonClasses Custom classes for the down button. * @param {object} props.dragHandleAttributes Custom attributes for the drag handle. * @param {string|Array|object} props.dragHandleClasses Custom classes for the drag handle. * @param {boolean} props.fillContent If the content should fill the available space. * @param {object} props.i18n Internationalization strings. * @param {string} props.id The id for the repeater. Required. * @param {boolean} props.isDraggable If the items are draggable. * @param {boolean} props.isSortable If the items are sortable. * @param {object} props.itemAttributes Custom attributes for the item. * @param {string|Array|object} props.itemClasses Custom classes for the item. * @param {boolean} props.itemDraggable If the items are draggable. * @param {Array} props.items The items to display, managed by external state. * @param {string|number|Array|object} props.itemSpacing The spacing for the item, as a string, number, array, or object. * @param {number} props.maxItems The maximum number of items allowed. * @param {number} props.minItems The minimum number of items allowed. * @param {object} props.newItemState The state for a new item. * @param {Function} props.onChange The function to call when the items change. * @param {Function} props.renderItem The function to render the item. * @param {object} props.screenReaderAttributes Custom attributes for the screen reader text. * @param {string|Array|object} props.screenReaderClasses Custom classes for the screen reader text. * @param {boolean} props.showAdd If the add button should be displayed. * @param {boolean} props.showArrows If the arrows should be displayed. * @param {boolean} props.showDelete If the delete button should be displayed. * @param {boolean} props.showDragHandle If the drag handle should be displayed. * @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object. * @param {string} props.type The type of the repeater. * @param {object} props.upButtonAttributes Custom attributes for the up button. * @param {string|Array|object} props.upButtonClasses Custom classes for the up button. * * @return {JSX.Element} The Repeater component. * * @example * import Repeater from '@gravityforms/components/react/admin/modules/Repeater'; * * return ( * <Repeater * id="repeater-test" * items={ [ * { * some_key: 1, * name: 'Item 1', * repeater_item_collapsed: true, * repeater_item_block_content_title: 'Block Content Title', * repeater_item_id: 'repeater-test-1' * }, * ] } * spacing={ { '': 6, md: 8 } } * /> * ); * */ const Repeater = ( { addButtonAttributes = {}, addButtonClasses = [], blockHeaderAttributes = {}, blockHeaderClasses = [], collapsible = false, collapsibleButtonAttributes = {}, collapsibleButtonClasses = [], customAttributes = {}, customClasses = [], deleteButtonAttributes = {}, deleteButtonClasses = [], downButtonAttributes = {}, downButtonClasses = [], dragHandleAttributes = {}, dragHandleClasses = [], fillContent = false, i18n = { beginDrag: 'Entering drag and drop for item %1$s.', deleteLabel: 'Click to delete this item.', downLabel: 'Move item %1$s down.', dragLabel: 'Click to toggle drag and drop.', endDrag: 'Exiting drag and drop for item %1$s.', endDrop: 'Item %1$s moved to position %2$s.', moveItem: 'Moving item %1$s to %2$s', upLabel: 'Move item %1$s up.', }, isDraggable = false, isSortable = false, itemDraggable = false, id = '', itemAttributes = {}, itemClasses = [], items = [], itemSpacing = '', maxItems = 0, minItems = 0, newItemState = {}, onChange = () => {}, renderItem = () => {}, screenReaderAttributes = {}, screenReaderClasses = [], showAdd = true, showArrows = false, showDelete = false, showDragHandle = false, spacing = '', type = INLINE, upButtonAttributes = {}, upButtonClasses = [], } ) => { const [ screenReaderText, setScreenReaderText ] = useState( '' ); const addItem = () => { const newItem = { repeater_item_id: `${ id }-${ Date.now() }`, ...newItemState }; onChange( [ ...items, newItem ] ); trigger( { event: 'gform/repeater/item_added', native: false, data: { id, index: items.length, itemId: newItem.repeater_item_id, state: [ ...items, newItem ], } } ); }; const collapseItem = ( index, itemId ) => { const updatedItems = Array.from( items ); updatedItems[ index ].repeater_item_collapsed = ! updatedItems[ index ].repeater_item_collapsed; onChange( updatedItems ); trigger( { event: 'gform/repeater/item_collapsed', native: false, data: { id, index, itemId, state: updatedItems, } } ); }; const deleteItem = ( index, itemId ) => { const updatedItems = Array.from( items ); updatedItems.splice( index, 1 ); onChange( updatedItems ); trigger( { event: 'gform/repeater/item_deleted', native: false, data: { id, index, itemId, state: updatedItems, } } ); }; const moveItem = ( fromIndex, toIndex, itemId ) => { const updatedItems = Array.from( items ); const [ movedItem ] = updatedItems.splice( fromIndex, 1 ); updatedItems.splice( toIndex, 0, movedItem ); setScreenReaderText( sprintf( i18n.moveItem, itemId, toIndex ) ); onChange( updatedItems ); trigger( { event: 'gform/repeater/item_moved', native: false, data: { fromIndex, id, itemId, item: movedItem, state: updatedItems, toIndex, } } ); }; const addButtonProps = { label: 'Add', type: 'white', iconPosition: 'leading', onClick: addItem, iconPrefix: 'gravity-component-icon', icon: 'plus-regular', size: 'size-height-m', customClasses: classnames( [ 'gform-repeater__add-button', ], addButtonClasses ), disabled: !! ( maxItems && items.length >= maxItems ), ...addButtonAttributes, }; const screenReaderProps = { id: `${ id }-repeater-screen-reader-text`, className: classnames( [ 'gform-repeater__screen-reader-text', 'gform-visually-hidden', ], screenReaderClasses ), 'aria-live': 'polite', ...screenReaderAttributes, }; const componentProps = { className: classnames( { 'gform-repeater': true, 'gform-repeater--collapsible': collapsible, [ `gform-repeater--type-${ type }` ]: true, ...spacerClasses( spacing ), }, customClasses ), id: `${ id }-list-wrapper`, ...customAttributes, }; return ( <div { ...componentProps }> <div { ...screenReaderProps } > { screenReaderText } </div> { items.map( ( item, index ) => ( <RepeaterItem blockContentTitle={ item?.repeater_item_block_content_title || '' } blockHeaderAttributes={ blockHeaderAttributes } blockHeaderClasses={ blockHeaderClasses } collapseItem={ collapseItem } collapsible={ collapsible } collapsibleButtonAttributes={ collapsibleButtonAttributes } collapsibleButtonClasses={ collapsibleButtonClasses } deleteButtonAttributes={ deleteButtonAttributes } deleteButtonClasses={ deleteButtonClasses } deleteItem={ deleteItem } downButtonAttributes={ downButtonAttributes } downButtonClasses={ downButtonClasses } dragHandleAttributes={ dragHandleAttributes } dragHandleClasses={ dragHandleClasses } fillContent={ fillContent } i18n={ i18n } id={ item.repeater_item_id } index={ index } isCollapsed={ item.repeater_item_collapsed } isDraggable={ isDraggable } isSortable={ isSortable } itemAttributes={ itemAttributes } itemClasses={ itemClasses } itemCount={ items.length } itemDraggable={ itemDraggable } itemSpacing={ itemSpacing } key={ item.repeater_item_id } minItems={ minItems } moveItem={ moveItem } showArrows={ showArrows } showDelete={ showDelete } showDragHandle={ showDragHandle } speak={ setScreenReaderText } type={ type } upButtonAttributes={ upButtonAttributes } upButtonClasses={ upButtonClasses } > { renderItem( item, index ) } </RepeaterItem> ) ) } { showAdd && <Button { ...addButtonProps } /> } </div> ); }; Repeater.propTypes = { addButtonAttributes: PropTypes.object, addButtonClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), blockHeaderAttributes: PropTypes.object, blockHeaderClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), collapsible: PropTypes.bool, collapsibleButtonAttributes: PropTypes.object, collapsibleButtonClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), customAttributes: PropTypes.object, customClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), deleteButtonAttributes: PropTypes.object, deleteButtonClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), downButtonAttributes: PropTypes.object, downButtonClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), dragHandleAttributes: PropTypes.object, dragHandleClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), fillContent: PropTypes.bool, i18n: PropTypes.object, id: PropTypes.string.isRequired, items: PropTypes.array, isDraggable: PropTypes.bool, isSortable: PropTypes.bool, itemAttributes: PropTypes.object, itemClasses: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array, PropTypes.object, ] ), itemDraggable: PropTypes.bool, itemSpacing: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object, ] ), maxItems: PropTypes.number, minItems: PropTypes.number, newItemState: PropTypes.object, onChange: PropTypes.func, renderItem: PropTypes.elementType, screenReaderAttributes: PropTypes.object, screenReaderClasses: PropTypes.oneOfType( [ PropTypes.element, PropTypes.func, PropTypes.array, PropTypes.string, ] ), showAdd: PropTypes.bool, showArrows: PropTypes.bool, showDelete: PropTypes.bool, showDragHandle: PropTypes.bool, spacing: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object, ] ), type: PropTypes.oneOf( [ BLOCK, INLINE ] ), upButtonAttributes: PropTypes.object, upButtonClasses: PropTypes.oneOfType( [ PropTypes.element, PropTypes.func, PropTypes.array, PropTypes.string, ] ), }; Repeater.displayName = 'Repeater'; export default Repeater;