UNPKG

9.46 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import clsx from 'clsx';
6import composeClasses from '@mui/utils/composeClasses';
7import { styled } from "../zero-styled/index.js";
8import { useDefaultProps } from "../DefaultPropsProvider/index.js";
9import { isFilled, isAdornedStart } from "../InputBase/utils.js";
10import capitalize from "../utils/capitalize.js";
11import isMuiElement from "../utils/isMuiElement.js";
12import FormControlContext from "./FormControlContext.js";
13import { getFormControlUtilityClasses } from "./formControlClasses.js";
14import { jsx as _jsx } from "react/jsx-runtime";
15const useUtilityClasses = ownerState => {
16 const {
17 classes,
18 margin,
19 fullWidth
20 } = ownerState;
21 const slots = {
22 root: ['root', margin !== 'none' && `margin${capitalize(margin)}`, fullWidth && 'fullWidth']
23 };
24 return composeClasses(slots, getFormControlUtilityClasses, classes);
25};
26const FormControlRoot = styled('div', {
27 name: 'MuiFormControl',
28 slot: 'Root',
29 overridesResolver: (props, styles) => {
30 const {
31 ownerState
32 } = props;
33 return [styles.root, styles[`margin${capitalize(ownerState.margin)}`], ownerState.fullWidth && styles.fullWidth];
34 }
35})({
36 display: 'inline-flex',
37 flexDirection: 'column',
38 position: 'relative',
39 // Reset fieldset default style.
40 minWidth: 0,
41 padding: 0,
42 margin: 0,
43 border: 0,
44 verticalAlign: 'top',
45 // Fix alignment issue on Safari.
46 variants: [{
47 props: {
48 margin: 'normal'
49 },
50 style: {
51 marginTop: 16,
52 marginBottom: 8
53 }
54 }, {
55 props: {
56 margin: 'dense'
57 },
58 style: {
59 marginTop: 8,
60 marginBottom: 4
61 }
62 }, {
63 props: {
64 fullWidth: true
65 },
66 style: {
67 width: '100%'
68 }
69 }]
70});
71
72/**
73 * Provides context such as filled/focused/error/required for form inputs.
74 * Relying on the context provides high flexibility and ensures that the state always stays
75 * consistent across the children of the `FormControl`.
76 * This context is used by the following components:
77 *
78 * - FormLabel
79 * - FormHelperText
80 * - Input
81 * - InputLabel
82 *
83 * You can find one composition example below and more going to [the demos](/material-ui/react-text-field/#components).
84 *
85 * ```jsx
86 * <FormControl>
87 * <InputLabel htmlFor="my-input">Email address</InputLabel>
88 * <Input id="my-input" aria-describedby="my-helper-text" />
89 * <FormHelperText id="my-helper-text">We'll never share your email.</FormHelperText>
90 * </FormControl>
91 * ```
92 *
93 * ⚠️ Only one `InputBase` can be used within a FormControl because it creates visual inconsistencies.
94 * For instance, only one input can be focused at the same time, the state shouldn't be shared.
95 */
96const FormControl = /*#__PURE__*/React.forwardRef(function FormControl(inProps, ref) {
97 const props = useDefaultProps({
98 props: inProps,
99 name: 'MuiFormControl'
100 });
101 const {
102 children,
103 className,
104 color = 'primary',
105 component = 'div',
106 disabled = false,
107 error = false,
108 focused: visuallyFocused,
109 fullWidth = false,
110 hiddenLabel = false,
111 margin = 'none',
112 required = false,
113 size = 'medium',
114 variant = 'outlined',
115 ...other
116 } = props;
117 const ownerState = {
118 ...props,
119 color,
120 component,
121 disabled,
122 error,
123 fullWidth,
124 hiddenLabel,
125 margin,
126 required,
127 size,
128 variant
129 };
130 const classes = useUtilityClasses(ownerState);
131 const [adornedStart, setAdornedStart] = React.useState(() => {
132 // We need to iterate through the children and find the Input in order
133 // to fully support server-side rendering.
134 let initialAdornedStart = false;
135 if (children) {
136 React.Children.forEach(children, child => {
137 if (!isMuiElement(child, ['Input', 'Select'])) {
138 return;
139 }
140 const input = isMuiElement(child, ['Select']) ? child.props.input : child;
141 if (input && isAdornedStart(input.props)) {
142 initialAdornedStart = true;
143 }
144 });
145 }
146 return initialAdornedStart;
147 });
148 const [filled, setFilled] = React.useState(() => {
149 // We need to iterate through the children and find the Input in order
150 // to fully support server-side rendering.
151 let initialFilled = false;
152 if (children) {
153 React.Children.forEach(children, child => {
154 if (!isMuiElement(child, ['Input', 'Select'])) {
155 return;
156 }
157 if (isFilled(child.props, true) || isFilled(child.props.inputProps, true)) {
158 initialFilled = true;
159 }
160 });
161 }
162 return initialFilled;
163 });
164 const [focusedState, setFocused] = React.useState(false);
165 if (disabled && focusedState) {
166 setFocused(false);
167 }
168 const focused = visuallyFocused !== undefined && !disabled ? visuallyFocused : focusedState;
169 let registerEffect;
170 const registeredInput = React.useRef(false);
171 if (process.env.NODE_ENV !== 'production') {
172 registerEffect = () => {
173 if (registeredInput.current) {
174 console.error(['MUI: There are multiple `InputBase` components inside a FormControl.', 'This creates visual inconsistencies, only use one `InputBase`.'].join('\n'));
175 }
176 registeredInput.current = true;
177 return () => {
178 registeredInput.current = false;
179 };
180 };
181 }
182 const onFilled = React.useCallback(() => {
183 setFilled(true);
184 }, []);
185 const onEmpty = React.useCallback(() => {
186 setFilled(false);
187 }, []);
188 const childContext = React.useMemo(() => {
189 return {
190 adornedStart,
191 setAdornedStart,
192 color,
193 disabled,
194 error,
195 filled,
196 focused,
197 fullWidth,
198 hiddenLabel,
199 size,
200 onBlur: () => {
201 setFocused(false);
202 },
203 onFocus: () => {
204 setFocused(true);
205 },
206 onEmpty,
207 onFilled,
208 registerEffect,
209 required,
210 variant
211 };
212 }, [adornedStart, color, disabled, error, filled, focused, fullWidth, hiddenLabel, registerEffect, onEmpty, onFilled, required, size, variant]);
213 return /*#__PURE__*/_jsx(FormControlContext.Provider, {
214 value: childContext,
215 children: /*#__PURE__*/_jsx(FormControlRoot, {
216 as: component,
217 ownerState: ownerState,
218 className: clsx(classes.root, className),
219 ref: ref,
220 ...other,
221 children: children
222 })
223 });
224});
225process.env.NODE_ENV !== "production" ? FormControl.propTypes /* remove-proptypes */ = {
226 // ┌────────────────────────────── Warning ──────────────────────────────┐
227 // │ These PropTypes are generated from the TypeScript type definitions. │
228 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
229 // └─────────────────────────────────────────────────────────────────────┘
230 /**
231 * The content of the component.
232 */
233 children: PropTypes.node,
234 /**
235 * Override or extend the styles applied to the component.
236 */
237 classes: PropTypes.object,
238 /**
239 * @ignore
240 */
241 className: PropTypes.string,
242 /**
243 * The color of the component.
244 * It supports both default and custom theme colors, which can be added as shown in the
245 * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
246 * @default 'primary'
247 */
248 color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['primary', 'secondary', 'error', 'info', 'success', 'warning']), PropTypes.string]),
249 /**
250 * The component used for the root node.
251 * Either a string to use a HTML element or a component.
252 */
253 component: PropTypes.elementType,
254 /**
255 * If `true`, the label, input and helper text should be displayed in a disabled state.
256 * @default false
257 */
258 disabled: PropTypes.bool,
259 /**
260 * If `true`, the label is displayed in an error state.
261 * @default false
262 */
263 error: PropTypes.bool,
264 /**
265 * If `true`, the component is displayed in focused state.
266 */
267 focused: PropTypes.bool,
268 /**
269 * If `true`, the component will take up the full width of its container.
270 * @default false
271 */
272 fullWidth: PropTypes.bool,
273 /**
274 * If `true`, the label is hidden.
275 * This is used to increase density for a `FilledInput`.
276 * Be sure to add `aria-label` to the `input` element.
277 * @default false
278 */
279 hiddenLabel: PropTypes.bool,
280 /**
281 * If `dense` or `normal`, will adjust vertical spacing of this and contained components.
282 * @default 'none'
283 */
284 margin: PropTypes.oneOf(['dense', 'none', 'normal']),
285 /**
286 * If `true`, the label will indicate that the `input` is required.
287 * @default false
288 */
289 required: PropTypes.bool,
290 /**
291 * The size of the component.
292 * @default 'medium'
293 */
294 size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['medium', 'small']), PropTypes.string]),
295 /**
296 * The system prop that allows defining system overrides as well as additional CSS styles.
297 */
298 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
299 /**
300 * The variant to use.
301 * @default 'outlined'
302 */
303 variant: PropTypes.oneOf(['filled', 'outlined', 'standard'])
304} : void 0;
305export default FormControl;
\No newline at end of file