@primer/react
Version:
An implementation of GitHub's Primer Design System using React
165 lines (162 loc) • 9.49 kB
JavaScript
import { clsx } from 'clsx';
import React, { useContext } from 'react';
import Autocomplete from '../Autocomplete/Autocomplete.js';
import Checkbox from '../Checkbox/Checkbox.js';
import Radio from '../Radio/Radio.js';
import Select from '../Select/Select.js';
import { SelectPanel } from '../SelectPanel/SelectPanel.js';
import TextInput from '../TextInput/TextInput.js';
import TextInputWithTokens from '../TextInputWithTokens/TextInputWithTokens.js';
import Textarea from '../Textarea/Textarea.js';
import Box from '../Box/Box.js';
import '../internal/components/CheckboxOrRadioGroup/CheckboxOrRadioGroup.js';
import CheckboxOrRadioGroupContext from '../internal/components/CheckboxOrRadioGroup/CheckboxOrRadioGroupContext.js';
import ValidationAnimationContainer from '../internal/components/ValidationAnimationContainer.js';
import { useSlots } from '../hooks/useSlots.js';
import { useId } from '../hooks/useId.js';
import { FormControlCaption } from './FormControlCaption.js';
import FormControlLabel from './FormControlLabel.js';
import FormControlLeadingVisual from './FormControlLeadingVisual.js';
import FormControlValidation from './_FormControlValidation.js';
import { FormControlContextProvider } from './_FormControlContext.js';
import { warning } from '../utils/warning.js';
import styled from 'styled-components';
import sx from '../sx.js';
import { toggleStyledComponent } from '../internal/utils/toggleStyledComponent.js';
import { cssModulesFlag } from './feature-flags.js';
import '../FeatureFlags/FeatureFlags.js';
import { useFeatureFlag } from '../FeatureFlags/useFeatureFlag.js';
import '../FeatureFlags/DefaultFeatureFlags.js';
import classes from './FormControl.module.css.js';
const FormControl = /*#__PURE__*/React.forwardRef(({
children,
disabled: disabledProp,
layout = 'vertical',
id: idProp,
required,
sx,
className
}, ref) => {
var _slots$validation, _slots$label;
const enabled = useFeatureFlag(cssModulesFlag);
const [slots, childrenWithoutSlots] = useSlots(children, {
caption: FormControlCaption,
label: FormControlLabel,
leadingVisual: FormControlLeadingVisual,
validation: FormControlValidation
});
const expectedInputComponents = [Autocomplete, Checkbox, Radio, Select, TextInput, TextInputWithTokens, Textarea, SelectPanel];
const choiceGroupContext = useContext(CheckboxOrRadioGroupContext);
const disabled = choiceGroupContext.disabled || disabledProp;
const id = useId(idProp);
const validationMessageId = slots.validation ? `${id}-validationMessage` : undefined;
const captionId = slots.caption ? `${id}-caption` : undefined;
const validationStatus = (_slots$validation = slots.validation) === null || _slots$validation === undefined ? undefined : _slots$validation.props.variant;
const InputComponent = childrenWithoutSlots.find(child => expectedInputComponents.some(inputComponent => /*#__PURE__*/React.isValidElement(child) && child.type === inputComponent));
const inputProps = /*#__PURE__*/React.isValidElement(InputComponent) && InputComponent.props;
const isChoiceInput = /*#__PURE__*/React.isValidElement(InputComponent) && (InputComponent.type === Checkbox || InputComponent.type === Radio);
const isRadioInput = /*#__PURE__*/React.isValidElement(InputComponent) && InputComponent.type === Radio;
if (InputComponent) {
process.env.NODE_ENV !== "production" ? warning(inputProps === null || inputProps === undefined ? undefined : inputProps.id, `instead of passing the 'id' prop directly to the input component, it should be passed to the parent component, <FormControl>`) : undefined;
process.env.NODE_ENV !== "production" ? warning(inputProps === null || inputProps === undefined ? undefined : inputProps.disabled, `instead of passing the 'disabled' prop directly to the input component, it should be passed to the parent component, <FormControl>`) : undefined;
process.env.NODE_ENV !== "production" ? warning(inputProps === null || inputProps === undefined ? undefined : inputProps.required, `instead of passing the 'required' prop directly to the input component, it should be passed to the parent component, <FormControl>`) : undefined;
}
if (!slots.label) {
// eslint-disable-next-line no-console
console.error(`The input field with the id ${id} MUST have a FormControl.Label child.\n\nIf you want to hide the label, pass the 'visuallyHidden' prop to the FormControl.Label component.`);
}
if (isChoiceInput) {
process.env.NODE_ENV !== "production" ? warning(!!slots.validation, 'Validation messages are not rendered for an individual checkbox or radio. The validation message should be shown for all options.') : undefined;
process.env.NODE_ENV !== "production" ? warning(isRadioInput && childrenWithoutSlots.find(child => {
var _child$props;
return /*#__PURE__*/React.isValidElement(child) && ((_child$props = child.props) === null || _child$props === undefined ? undefined : _child$props.required);
}), 'An individual radio cannot be a required field.') : undefined;
} else {
process.env.NODE_ENV !== "production" ? warning(!!slots.leadingVisual, 'A leading visual is only rendered for a checkbox or radio form control. If you want to render a leading visual inside of your input, check if your input supports a leading visual.') : undefined;
}
const isLabelHidden = (_slots$label = slots.label) === null || _slots$label === undefined ? undefined : _slots$label.props.visuallyHidden;
return /*#__PURE__*/React.createElement(FormControlContextProvider, {
value: {
captionId,
disabled,
id,
required,
validationMessageId
}
}, isChoiceInput || layout === 'horizontal' ? /*#__PURE__*/React.createElement(StyledHorizontalLayout, {
ref: ref,
"data-has-leading-visual": slots.leadingVisual ? '' : undefined,
sx: sx,
className: clsx(className, {
[classes.ControlHorizontalLayout]: enabled
})
}, /*#__PURE__*/React.createElement(StyledChoiceInputs, {
className: classes.ControlChoiceInputs
}, /*#__PURE__*/React.isValidElement(InputComponent) ? /*#__PURE__*/React.cloneElement(InputComponent, {
id,
disabled,
// allow checkboxes to be required
required: required && !isRadioInput,
['aria-describedby']: captionId
}) : null, childrenWithoutSlots.filter(child => /*#__PURE__*/React.isValidElement(child) && ![Checkbox, Radio].some(inputComponent => child.type === inputComponent))), slots.leadingVisual ? /*#__PURE__*/React.createElement(StyledLeadingVisual, {
className: clsx({
[classes.LeadingVisual]: enabled
}),
"data-disabled": disabled ? '' : undefined,
"data-has-caption": slots.caption ? '' : undefined
}, slots.leadingVisual) : null, /*#__PURE__*/React.createElement(StyledLabelContainer, {
className: classes.LabelContainer
}, slots.label, slots.caption)) : /*#__PURE__*/React.createElement(Box, {
ref: ref,
"data-has-label": !isLabelHidden ? '' : undefined,
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
sx: enabled ? sx : {
...(isLabelHidden ? {
'> *:not(label) + *': {
marginTop: 1
}
} : {
'> * + *': {
marginTop: 1
}
}),
...sx
},
className: clsx(className, {
[classes.ControlVerticalLayout]: enabled
})
}, slots.label, /*#__PURE__*/React.isValidElement(InputComponent) && /*#__PURE__*/React.cloneElement(InputComponent, Object.assign({
id,
required,
disabled,
validationStatus,
['aria-describedby']: [validationMessageId, captionId].filter(Boolean).join(' ')
}, InputComponent.props)), childrenWithoutSlots.filter(child => /*#__PURE__*/React.isValidElement(child) && !expectedInputComponents.some(inputComponent => child.type === inputComponent)), slots.validation ? /*#__PURE__*/React.createElement(ValidationAnimationContainer, {
show: true
}, slots.validation) : null, slots.caption));
});
const StyledHorizontalLayout = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({
displayName: "FormControl__StyledHorizontalLayout",
componentId: "sc-1t6d2k4-0"
})(["display:flex;&:where([data-has-leading-visual]){align-items:center;}", ""], sx));
const StyledChoiceInputs = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({
displayName: "FormControl__StyledChoiceInputs",
componentId: "sc-1t6d2k4-1"
})(["> input{margin-left:0;margin-right:0;}"]));
const StyledLabelContainer = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({
displayName: "FormControl__StyledLabelContainer",
componentId: "sc-1t6d2k4-2"
})(["> *{padding-left:var(--stack-gap-condensed);}> label{font-weight:var(--base-text-weight-normal);}"]));
const StyledLeadingVisual = toggleStyledComponent(cssModulesFlag, 'div', styled.div.withConfig({
displayName: "FormControl__StyledLeadingVisual",
componentId: "sc-1t6d2k4-3"
})(["color:var(--fgColor-default);margin-left:var(--base-size-8);&:where([data-disabled]){color:var(--fgColor-muted);}> *{fill:currentColor;min-width:var(--text-body-size-large);min-height:var(--text-body-size-large);}> *:where([data-has-caption]){min-width:var(--base-size-24);min-height:var(--base-size-24);}"]));
var FormControl$1 = Object.assign(FormControl, {
Caption: FormControlCaption,
Label: FormControlLabel,
LeadingVisual: FormControlLeadingVisual,
Validation: FormControlValidation
});
export { FormControl$1 as default };