@itwin/itwinui-react
Version:
A react component library for iTwinUI
144 lines (143 loc) • 4.88 kB
JavaScript
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);