UNPKG

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