UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

144 lines (143 loc) 4.88 kB
import * as React from 'react'; import { Box, InputWithIcon, cloneElementWithRef, useId, } from '../../utils/index.js'; import cx from 'classnames'; import { Label } from '../Label/Label.js'; import { Input } from '../Input/Input.js'; import { Textarea } from '../Textarea/Textarea.js'; import { StatusMessage } from '../StatusMessage/StatusMessage.js'; import { InputWithDecorations } from '../InputWithDecorations/InputWithDecorations.js'; import { ComboBox } from '../ComboBox/ComboBox.js'; import { Select } from '../Select/Select.js'; export const InputGrid = React.forwardRef((props, ref) => { let { children: childrenProp, className, labelPlacement, ...rest } = props; let children = useChildrenWithIds(childrenProp); return React.createElement( Box, { className: cx('iui-input-grid', className), 'data-iui-label-placement': labelPlacement, ref: ref, ...rest, }, children, ); }); if ('development' === process.env.NODE_ENV) InputGrid.displayName = 'InputGrid'; let useChildrenWithIds = (children) => { let { labelId, inputId, messageId } = useSetup(children); return React.useMemo( () => React.Children.map(children, (child) => { if (!React.isValidElement(child)) return child; if (child.type === Label || 'label' === child.type) return cloneElementWithRef(child, (child) => ({ ...child.props, htmlFor: child.props.htmlFor || inputId, id: child.props.id || labelId, })); if (child.type === StatusMessage) return cloneElementWithRef(child, (child) => ({ ...child.props, id: child.props.id || messageId, })); if ( isInput(child) || child.type === InputWithDecorations || child.type === InputWithIcon ) return handleCloningInputs(child, { labelId, inputId, messageId, }); return child; }), [children, inputId, labelId, messageId], ); }; let useSetup = (children) => { let idPrefix = useId(); let labelId; let inputId; let messageId; let hasLabel = false; let hasSelect = false; let findInputId = (child) => { if (!React.isValidElement(child)) return; if (child.type === ComboBox) return child.props.inputProps?.id || `${idPrefix}--input`; if (child.type !== Select) return child.props.id || `${idPrefix}--input`; }; React.Children.forEach(children, (child) => { if (!React.isValidElement(child)) return; if (child.type === Label || 'label' === child.type) { hasLabel = true; labelId || (labelId = child.props.id || `${idPrefix}--label`); } if (child.type === StatusMessage) messageId || (messageId = child.props.id || `${idPrefix}--message`); if (child.type === InputWithDecorations || child.type === InputWithIcon) React.Children.forEach(child.props.children, (child) => { if (isInput(child)) inputId || (inputId = findInputId(child)); }); else if (isInput(child)) inputId || (inputId = findInputId(child)); if (child.type === Select) hasSelect = true; }); return { labelId: hasSelect ? labelId : void 0, inputId: hasLabel && !hasSelect ? inputId : void 0, messageId, }; }; let handleCloningInputs = (child, { labelId, inputId, messageId }) => { let inputProps = (props = {}) => { let ariaDescribedBy = [props['aria-describedby'], messageId] .filter(Boolean) .join(' '); return { ...props, ...(child.type !== Select && { id: props.id || inputId, }), 'aria-describedby': ariaDescribedBy?.trim() || void 0, }; }; let cloneInput = (child) => { if (child.type === ComboBox) return cloneElementWithRef(child, (child) => ({ inputProps: inputProps(child.props.inputProps), })); if (child.type === Select) return cloneElementWithRef(child, (child) => ({ triggerProps: { 'aria-labelledby': labelId, ...inputProps(child.props.triggerProps), }, })); return cloneElementWithRef(child, (child) => inputProps(child.props)); }; if (child.type === InputWithDecorations || child.type === InputWithIcon) return cloneElementWithRef(child, (child) => ({ children: React.Children.map(child.props.children, (child) => { if (React.isValidElement(child) && isInput(child)) return cloneInput(child); return child; }), })); return cloneInput(child); }; let isInput = (child) => React.isValidElement(child) && ('input' === child.type || 'textarea' === child.type || 'select' === child.type || child.type === Input || child.type === Textarea || child.type === InputWithDecorations.Input || child.type === Select || child.type === ComboBox);