UNPKG

@toolpad/core

Version:

Dashboard framework powered by Material UI.

305 lines (304 loc) 9.83 kB
'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; import invariant from 'invariant'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControl from '@mui/material/FormControl'; import FormControlLabel from '@mui/material/FormControlLabel'; import FormGroup from '@mui/material/FormGroup'; import FormHelperText from '@mui/material/FormHelperText'; import Grid from '@mui/material/Grid'; import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import Select from '@mui/material/Select'; import TextField from '@mui/material/TextField'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import dayjs from 'dayjs'; import { CrudContext } from "../shared/context.js"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * * Demos: * * - [CRUD](https://mui.com/toolpad/core/react-crud/) * * API: * * - [CrudForm API](https://mui.com/toolpad/core/api/crud-form) */ function CrudForm(props) { const { formState, onFieldChange, onSubmit, onReset, submitButtonLabel, slots, slotProps } = props; const formValues = formState.values; const formErrors = formState.errors; const crudContext = React.useContext(CrudContext); const dataSource = props.dataSource ?? crudContext.dataSource; invariant(dataSource, 'No data source found.'); const { fields } = dataSource; const [, submitAction, isSubmitting] = React.useActionState(async () => { try { await onSubmit(formValues); } catch (error) { return error; } return null; }, null); const handleTextFieldChange = React.useCallback(event => { onFieldChange(event.target.name, event.target.value); }, [onFieldChange]); const handleNumberFieldChange = React.useCallback(event => { onFieldChange(event.target.name, Number(event.target.value)); }, [onFieldChange]); const handleCheckboxFieldChange = React.useCallback((event, checked) => { onFieldChange(event.target.name, checked); }, [onFieldChange]); const handleDateFieldChange = React.useCallback(name => value => { if (value?.isValid()) { onFieldChange(name, value.toISOString() ?? null); } else if (formValues[name]) { onFieldChange(name, null); } }, [formValues, onFieldChange]); const handleSelectFieldChange = React.useCallback(event => { onFieldChange(event.target.name, event.target.value); }, [onFieldChange]); const renderField = React.useCallback(formField => { const { field, type, headerName } = formField; const fieldValue = formValues[field]; const fieldError = formErrors[field]; let fieldElement = null; if (!type || type === 'string') { const TextFieldComponent = slots?.textField ?? TextField; fieldElement = /*#__PURE__*/_jsx(TextFieldComponent, { value: fieldValue ?? '', onChange: handleTextFieldChange, name: field, label: headerName, error: !!fieldError, helperText: fieldError ?? ' ', fullWidth: true, ...slotProps?.textField }); } if (type === 'number') { fieldElement = /*#__PURE__*/_jsx(TextField, { value: fieldValue ?? '', onChange: handleNumberFieldChange, name: field, type: "number", label: headerName, error: !!fieldError, helperText: fieldError ?? ' ', fullWidth: true }); } if (type === 'boolean') { fieldElement = /*#__PURE__*/_jsxs(FormControl, { children: [/*#__PURE__*/_jsx(FormControlLabel, { name: field, control: /*#__PURE__*/_jsx(Checkbox, { size: "large", checked: fieldValue, onChange: handleCheckboxFieldChange }), label: headerName }), /*#__PURE__*/_jsx(FormHelperText, { error: !!fieldError, children: fieldError ?? ' ' })] }); } if (type === 'date') { fieldElement = /*#__PURE__*/_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: /*#__PURE__*/_jsx(DatePicker, { value: fieldValue ? dayjs(fieldValue) : null, onChange: handleDateFieldChange(field), name: field, label: headerName, slotProps: { textField: { error: !!fieldError, helperText: fieldError ?? ' ', fullWidth: true } } }) }); } if (type === 'dateTime') { fieldElement = /*#__PURE__*/_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: /*#__PURE__*/_jsx(DateTimePicker, { value: fieldValue ? dayjs(fieldValue) : null, onChange: handleDateFieldChange(field), name: field, label: headerName, slotProps: { textField: { error: !!fieldError, helperText: fieldError ?? ' ', fullWidth: true } } }) }); } if (type === 'singleSelect') { const { getOptionValue, getOptionLabel, valueOptions } = formField; if (valueOptions && Array.isArray(valueOptions)) { const labelId = `${field}-label`; fieldElement = /*#__PURE__*/_jsxs(FormControl, { error: !!fieldError, fullWidth: true, children: [/*#__PURE__*/_jsx(InputLabel, { id: labelId, children: headerName }), /*#__PURE__*/_jsx(Select, { value: fieldValue ?? '', onChange: handleSelectFieldChange, labelId: labelId, name: field, label: headerName, defaultValue: "", fullWidth: true, children: valueOptions.map(option => { let optionValue = option; let optionLabel = option; if (typeof option !== 'string' && typeof option !== 'number') { optionValue = getOptionValue ? getOptionValue(option) : option.value; optionLabel = getOptionLabel ? getOptionLabel(option) : option.label; } return /*#__PURE__*/_jsx(MenuItem, { value: optionValue, children: optionLabel }, optionValue); }) }), /*#__PURE__*/_jsx(FormHelperText, { children: fieldError ?? ' ' })] }); } } return /*#__PURE__*/_jsx(Grid, { size: { xs: 12, sm: 6 }, sx: { display: 'flex' }, children: fieldElement }, field); }, [formErrors, formValues, handleCheckboxFieldChange, handleDateFieldChange, handleNumberFieldChange, handleSelectFieldChange, handleTextFieldChange, slotProps?.textField, slots]); const handleReset = React.useCallback(async () => { if (onReset) { await onReset(formValues); } }, [formValues, onReset]); return /*#__PURE__*/_jsxs(Box, { component: "form", action: submitAction, noValidate: true, autoComplete: "off", onReset: handleReset, sx: { width: '100%' }, children: [/*#__PURE__*/_jsx(FormGroup, { children: /*#__PURE__*/_jsx(Grid, { container: true, spacing: 2, sx: { mb: 2, width: '100%' }, children: fields.filter(({ field }) => field !== 'id').map(renderField) }) }), /*#__PURE__*/_jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end' }, children: /*#__PURE__*/_jsx(Button, { type: "submit", variant: "contained", size: "large", loading: isSubmitting, children: submitButtonLabel }) })] }); } process.env.NODE_ENV !== "production" ? CrudForm.propTypes /* remove-proptypes */ = { // ┌────────────────────────────── Warning ──────────────────────────────┐ // │ These PropTypes are generated from the TypeScript type definitions. │ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ // └─────────────────────────────────────────────────────────────────────┘ /** * Server-side [data source](https://mui.com/toolpad/core/react-crud/#data-sources). */ dataSource: PropTypes.object, /** * Form state object, including field values and errors. */ formState: PropTypes.shape({ errors: PropTypes.object.isRequired, values: PropTypes.object.isRequired }).isRequired, /** * Callback fired when a form field is changed. */ onFieldChange: PropTypes.func.isRequired, /** * Callback fired when the form is reset. */ onReset: PropTypes.func, /** * Callback fired when the form is submitted. */ onSubmit: PropTypes.func.isRequired, /** * The props used for each slot inside. * @default {} */ slotProps: PropTypes.shape({ textField: PropTypes.object }), /** * The components used for each slot inside. * @default {} */ slots: PropTypes.shape({ textField: PropTypes.elementType }), /** * Text for form submit button. */ submitButtonLabel: PropTypes.string.isRequired } : void 0; export { CrudForm };