UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

165 lines (162 loc) • 9.49 kB
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 };