Repository: vazco/uniforms (ts)

Issue:
Unable to propagate 'classes' prop for SelectField on uniforms-material
<!--
  Before you open an issue, please check if a similar one already exists or has been closed before.
  We will be grateful for using our reproduction sandbox available at
  https://codesandbox.io/s/github/vazco/uniforms/tree/master/reproductions
  whenever a code sample is presented for debugging purposes.
-->
I am trying to style the FormControl in a SelectField, but neither style or classes are being provided to the TextField component.

I believe that at least the classes prop should be explicitly provided to TextField: https://github.com/vazco/uniforms/blob/master/packages/uniforms-material/src/SelectField.tsx

Pull request description:
Added textFieldProps to SelectField in uniforms-material (closes #700).
This PR introduces `textFieldProps` for `SelectField`. It allows setting `TextField` properties as its the kinda `wrapper` for `Select` (in contrast to `checkboxes`, where `wrapField` holds inner components). 

Also, I removed  `defaultProps` from all components in `uniforms-material`.

Patch:
diff --git a/docs/api-fields.md b/docs/api-fields.md
index 13843aa7..011a3b40 100644
--- a/docs/api-fields.md
+++ b/docs/api-fields.md
@@ -529,8 +529,8 @@ import { RadioField } from 'uniforms-unstyled';
 | :---------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------: |
 |  `allowedValues`  |                                               Array of allowed values. By default, those are extracted from your schema.                                                |                  All                   |
 |   `appearance`    |                 Field appearance. Set to "toggle" to appear as a Material Toggle or to "checkbox" (or leave it undefined) to use a Checkbox appearance.                 |                material                |
-|   `checkboxes`    |                                       Turn on checkbox/radio mode. It's always true in mutltiple (i.e. fieldType === Array) mode.                                       |                  All                   |
-|      `extra`      |                                 Extra feedback text. In the antd theme, this renders addtional help text below any validation messages.                                 |                  antd                  |
+|   `checkboxes`    |                                       Turn on checkbox/radio mode. It's always true in multiple (i.e. fieldType === Array) mode.                                        |                  All                   |
+|      `extra`      |                                Extra feedback text. In the antd theme, this renders additional help text below any validation messages.                                 |                  antd                  |
 |      `help`       |                                                           Help text. _Some description would be great, huh?_                                                            |      antd, bootstrap3, bootstrap4      |
 |  `helpClassName`  |                                                      Help block className. _Some description would be great, huh?_                                                      |         bootstrap3, bootstrap4         |
 |     `inline`      |                      Checkbox inline state. In bootstrap themes, a label is rendered as a text but in inline mode, it's treated as a field label.                       |         bootstrap3, bootstrap4         |
@@ -543,7 +543,8 @@ import { RadioField } from 'uniforms-unstyled';
 | `showInlineError` |                                                       Field inline error. _Some description would be great, huh?_                                                       | antd, bootstrap3, bootstrap4, semantic |
 |    `transform`    |                                             Label transform. Allows to transform the each value into a human-readable label                                             |                  All                   |
 |   `wrapperCol`    |                                                     Field layout. The layout for input controls. Same as labelCol.                                                      |                  antd                  |
-|  `wrapClassName`  |                                             Field and sourroundings wrap className. _Some description would be great, huh?_                                             |         bootstrap3, bootstrap4         |
+|  `wrapClassName`  |                                             Field and surroundings wrap className. _Some description would be great, huh?_                                              |         bootstrap3, bootstrap4         |
+| `textFieldProps`  |                                          Props injected directly to `TextField` ( valid only for non-checkbox `SelectField` ).                                          |                material                |
 
 **Note:** All `BaseField` props are also accepted.
 
diff --git a/packages/uniforms-material/src/DateField.tsx b/packages/uniforms-material/src/DateField.tsx
index 54de80bc..74c7c82f 100644
--- a/packages/uniforms-material/src/DateField.tsx
+++ b/packages/uniforms-material/src/DateField.tsx
@@ -23,6 +23,7 @@ function Date({
   disabled,
   error,
   errorMessage,
+  fullWidth = true,
   helperText,
   InputLabelProps,
   inputRef,
@@ -37,13 +38,14 @@ function Date({
 }: DateFieldProps) {
   return (
     <TextField
-      disabled={!!disabled}
+      disabled={disabled}
       error={!!error}
+      fullWidth={fullWidth}
       helperText={(error && showInlineError && errorMessage) || helperText}
       label={label}
       InputLabelProps={{ ...labelProps, ...InputLabelProps }}
-      name={name}
       margin={props.margin ?? 'dense'}
+      name={name}
       onChange={event =>
         // FIXME: `valueAsNumber` is not available in `EventTarget`.
         disabled || dateParse((event.target as any).valueAsNumber, onChange)
@@ -57,8 +59,4 @@ function Date({
   );
 }
 
-Date.defaultProps = {
-  fullWidth: true,
-};
-
 export default connectField(Date, { kind: 'leaf' });
diff --git a/packages/uniforms-material/src/ListItemField.tsx b/packages/uniforms-material/src/ListItemField.tsx
index ca5f564e..092c4ea2 100644
--- a/packages/uniforms-material/src/ListItemField.tsx
+++ b/packages/uniforms-material/src/ListItemField.tsx
@@ -1,10 +1,5 @@
 import ListItemMaterial, { ListItemProps } from '@material-ui/core/ListItem';
-import React, {
-  Children,
-  ReactNode,
-  cloneElement,
-  isValidElement,
-} from 'react';
+import React, { ReactNode } from 'react';
 import { connectField } from 'uniforms';
 
 import AutoField from './AutoField';
diff --git a/packages/uniforms-material/src/LongTextField.tsx b/packages/uniforms-material/src/LongTextField.tsx
index 45638abf..b4fccf5e 100644
--- a/packages/uniforms-material/src/LongTextField.tsx
+++ b/packages/uniforms-material/src/LongTextField.tsx
@@ -8,6 +8,7 @@ const LongText = ({
   disabled,
   error,
   errorMessage,
+  fullWidth = true,
   helperText,
   inputRef,
   label,
@@ -15,12 +16,14 @@ const LongText = ({
   onChange,
   placeholder,
   showInlineError,
+  type = 'text',
   value,
   ...props
 }: LongTextFieldProps) => (
   <TextField
-    disabled={!!disabled}
+    disabled={disabled}
     error={!!error}
+    fullWidth={fullWidth}
     helperText={(error && showInlineError && errorMessage) || helperText}
     label={label}
     margin={props.margin ?? 'dense'}
@@ -29,14 +32,10 @@ const LongText = ({
     onChange={event => disabled || onChange(event.target.value)}
     placeholder={placeholder}
     ref={inputRef}
+    type={type}
     value={value ?? ''}
     {...filterDOMProps(props)}
   />
 );
 
-LongText.defaultProps = {
-  fullWidth: true,
-  type: 'text',
-};
-
 export default connectField(LongText, { kind: 'leaf' });
diff --git a/packages/uniforms-material/src/NestField.tsx b/packages/uniforms-material/src/NestField.tsx
index 4bbcb8b6..337b10c6 100644
--- a/packages/uniforms-material/src/NestField.tsx
+++ b/packages/uniforms-material/src/NestField.tsx
@@ -1,26 +1,29 @@
 import FormLabel from '@material-ui/core/FormLabel';
-import React, { HTMLProps } from 'react';
-import { HTMLFieldProps, connectField } from 'uniforms';
+import React from 'react';
+import { connectField, HTMLFieldProps } from 'uniforms';
 
 import AutoField from './AutoField';
 import wrapField from './wrapField';
 
+// FIXME: wrapField is not typed correctly.
 export type NestFieldProps = HTMLFieldProps<
   object,
   HTMLDivElement,
-  { helperText?: string; itemProps?: object }
+  { helperText?: string; itemProps?: object; fullWidth?: boolean; margin?: any }
 >;
 
 function Nest({
   children,
   fields,
+  fullWidth = true,
   itemProps,
   label,
+  margin = 'dense',
   name,
   ...props
 }: NestFieldProps) {
   return wrapField(
-    { ...props, component: undefined },
+    { ...props, component: undefined, fullWidth, margin },
     label && <FormLabel component="legend">{label}</FormLabel>,
     children ||
       fields?.map(field => (
@@ -29,10 +32,4 @@ function Nest({
   );
 }
 
-// FIXME: wrapField is not typed correctly.
-Nest.defaultProps = {
-  fullWidth: true,
-  margin: 'dense',
-} as any;
-
 export default connectField(Nest);
diff --git a/packages/uniforms-material/src/NumField.tsx b/packages/uniforms-material/src/NumField.tsx
index 42d0a6b4..8dfec316 100644
--- a/packages/uniforms-material/src/NumField.tsx
+++ b/packages/uniforms-material/src/NumField.tsx
@@ -13,10 +13,12 @@ function Num({
   disabled,
   error,
   errorMessage,
+  fullWidth = true,
   helperText,
   inputProps,
   inputRef,
   label,
+  margin = 'dense',
   max,
   min,
   name,
@@ -28,11 +30,13 @@ function Num({
 }: NumFieldProps) {
   return (
     <TextField
-      disabled={!!disabled}
+      disabled={disabled}
       error={!!error}
+      fullWidth={fullWidth}
       helperText={(error && showInlineError && errorMessage) || helperText}
       inputProps={{ min, max, step: decimal ? 0.01 : 1, ...inputProps }}
       label={label}
+      margin={margin}
       name={name}
       onChange={event => {
         const parse = decimal ? parseFloat : parseInt;
@@ -42,8 +46,6 @@ function Num({
       placeholder={placeholder}
       ref={inputRef}
       type="number"
-      margin={props.margin ?? 'dense'}
-      fullWidth={props.fullWidth ?? true}
       value={value ?? ''}
       {...filterDOMProps(props)}
     />
diff --git a/packages/uniforms-material/src/RadioField.tsx b/packages/uniforms-material/src/RadioField.tsx
index 82173492..f3db1598 100644
--- a/packages/uniforms-material/src/RadioField.tsx
+++ b/packages/uniforms-material/src/RadioField.tsx
@@ -13,7 +13,9 @@ export type RadioFieldProps = FieldProps<
   {
     allowedValues?: string[];
     checkboxes?: boolean;
+    fullWidth?: boolean;
     helperText?: string;
+    margin?: any;
     transform?(value: string): string;
   }
 >;
@@ -22,9 +24,11 @@ function Radio({
   allowedValues,
   checkboxes,
   disabled,
+  fullWidth = true,
   id,
   inputRef,
   label,
+  margin = 'dense',
   name,
   onChange,
   transform,
@@ -34,7 +38,7 @@ function Radio({
   const filteredProps = wrapField._filterDOMProps(filterDOMProps(props));
 
   return wrapField(
-    { ...props, disabled, component: 'fieldset' },
+    { ...props, disabled, component: 'fieldset', fullWidth, margin },
     label && (
       <FormLabel component="legend" htmlFor={name}>
         {label}
@@ -59,10 +63,4 @@ function Radio({
   );
 }
 
-// FIXME: wrapField is not typed correctly.
-Radio.defaultProps = {
-  fullWidth: true,
-  margin: 'dense',
-} as any;
-
 export default connectField(Radio, { kind: 'leaf' });
diff --git a/packages/uniforms-material/src/SelectField.tsx b/packages/uniforms-material/src/SelectField.tsx
index ec8b479f..756c10b1 100644
--- a/packages/uniforms-material/src/SelectField.tsx
+++ b/packages/uniforms-material/src/SelectField.tsx
@@ -1,7 +1,5 @@
 import Checkbox, { CheckboxProps } from '@material-ui/core/Checkbox';
-import FormControlLabel, {
-  FormControlLabelProps,
-} from '@material-ui/core/FormControlLabel';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
 import FormGroup from '@material-ui/core/FormGroup';
 import FormLabel from '@material-ui/core/FormLabel';
 import MenuItem from '@material-ui/core/MenuItem';
@@ -42,6 +40,7 @@ type SelectProps = FieldProps<
     labelProps?: object;
     required?: boolean;
     transform?(value: string): string;
+    textFieldProps: TextFieldProps;
   }
 >;
 
@@ -60,8 +59,6 @@ function Select(props: SelectFieldProps) {
     const {
       allowedValues,
       disabled,
-      error,
-      errorMessage,
       fieldType,
       id,
       inputRef,
@@ -69,7 +66,6 @@ function Select(props: SelectFieldProps) {
       legend,
       name,
       onChange,
-      showInlineError,
       transform,
     } = props;
     const appearance = props.appearance ?? 'checkbox';
@@ -122,10 +118,6 @@ function Select(props: SelectFieldProps) {
       {
         ...props,
         component: 'fieldset',
-        disabled,
-        error,
-        errorMessage,
-        showInlineError,
       },
       (legend || label) && (
         <FormLabel component="legend">{legend || label}</FormLabel>
@@ -133,20 +125,20 @@ function Select(props: SelectFieldProps) {
       children,
     );
   }
-  const fullWidth = props.fullWidth ?? true;
-  const margin = props.margin ?? 'dense';
   const {
     allowedValues,
     disabled,
     error,
     errorMessage,
     fieldType,
+    fullWidth = true,
     helperText,
     id,
     InputLabelProps,
     inputProps,
     label,
     labelProps,
+    margin = 'dense',
     name,
     native,
     onChange,
@@ -155,6 +147,7 @@ function Select(props: SelectFieldProps) {
     showInlineError,
     transform,
     variant,
+    textFieldProps,
   } = props;
   const Item = native ? 'option' : MenuItem;
   const hasPlaceholder = !!placeholder;
@@ -162,7 +155,7 @@ function Select(props: SelectFieldProps) {
 
   return (
     <TextField
-      disabled={!!disabled}
+      disabled={disabled}
       error={!!error}
       fullWidth={fullWidth}
       helperText={(error && showInlineError && errorMessage) || helperText}
@@ -189,6 +182,7 @@ function Select(props: SelectFieldProps) {
       }}
       value={native && !value ? '' : value}
       variant={variant}
+      {...textFieldProps}
     >
       {(hasPlaceholder || !required || !hasValue) && (
         <Item value="" disabled={!!required}>
diff --git a/packages/uniforms-material/src/SubmitField.tsx b/packages/uniforms-material/src/SubmitField.tsx
index cd6bc037..45bb7aec 100644
--- a/packages/uniforms-material/src/SubmitField.tsx
+++ b/packages/uniforms-material/src/SubmitField.tsx
@@ -12,8 +12,9 @@ function SubmitField({
   children,
   disabled,
   inputRef,
-  label,
+  label = 'Submit',
   value,
+  variant = 'contained',
   ...props
 }: SubmitFieldProps) {
   const { error, state } = useForm();
@@ -24,6 +25,7 @@ function SubmitField({
       ref={inputRef}
       type="submit"
       value={value}
+      variant={variant}
       {...filterDOMProps(props)}
     >
       {children || label}
@@ -31,6 +33,4 @@ function SubmitField({
   );
 }
 
-SubmitField.defaultProps = { label: 'Submit', variant: 'contained' };
-
 export default SubmitField;
diff --git a/packages/uniforms-material/src/TextField.tsx b/packages/uniforms-material/src/TextField.tsx
index db7d38e8..1f058217 100644
--- a/packages/uniforms-material/src/TextField.tsx
+++ b/packages/uniforms-material/src/TextField.tsx
@@ -8,38 +8,36 @@ function Text({
   disabled,
   error,
   errorMessage,
+  fullWidth = true,
   helperText,
   inputRef,
   label,
+  margin = 'dense',
   name,
   onChange,
   placeholder,
   showInlineError,
-  type,
-  value,
+  type = 'text',
+  value = '',
   ...props
 }: TextFieldProps) {
   return (
     <TextField
-      disabled={!!disabled}
+      disabled={disabled}
       error={!!error}
+      fullWidth={fullWidth}
       helperText={(error && showInlineError && errorMessage) || helperText}
       label={label}
+      margin={margin}
       name={name}
       onChange={event => disabled || onChange(event.target.value)}
       placeholder={placeholder}
       ref={inputRef}
       type={type}
-      margin={props.margin ?? 'dense'}
-      value={value ?? ''}
+      value={value}
       {...filterDOMProps(props)}
     />
   );
 }
 
-Text.defaultProps = {
-  fullWidth: true,
-  type: 'text',
-};
-
 export default connectField(Text, { kind: 'leaf' });


Test patch:
diff --git a/packages/uniforms-material/__tests__/SelectField.tsx b/packages/uniforms-material/__tests__/SelectField.tsx
index 62fea6f8..6ea0ca60 100644
--- a/packages/uniforms-material/__tests__/SelectField.tsx
+++ b/packages/uniforms-material/__tests__/SelectField.tsx
@@ -287,8 +287,19 @@ test('<SelectField> - works with special characters', () => {
   );
 });
 
+test('<SelectField> - renders with correct classnames', () => {
+  const wrapper = mount(
+    // @ts-ignore Fix SelectFieldProps.
+    <SelectField name="x" textFieldProps={{ className: 'select-class' }} />,
+    createContext({ x: { type: String, allowedValues: ['a', 'b'] } }),
+  );
+  expect(wrapper.find(TextField).props()).toHaveProperty(
+    'className',
+    'select-class',
+  );
+});
+
 test('<SelectField checkboxes> - renders a set of Radio buttons', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -299,7 +310,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons', () => {
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct disabled state', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" disabled />;
   const wrapper = mount(
     element,
@@ -312,7 +322,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct dis
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct id (inherited)', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -325,7 +334,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct id
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct id (specified)', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" id="y" />;
   const wrapper = mount(
     element,
@@ -338,7 +346,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct id
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct name', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -351,7 +358,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct nam
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct options', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -365,7 +371,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct opt
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct options (transform)', () => {
   const element = (
-    // @ts-ignore Fix SelectFieldProps.
     <SelectField checkboxes name="x" transform={x => x.toUpperCase()} />
   );
   const wrapper = mount(
@@ -379,7 +384,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct opt
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct value (default)', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -392,7 +396,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct val
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct value (model)', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -408,7 +411,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct val
 });
 
 test('<SelectField checkboxes> - renders a set of Radio buttons with correct value (specified)', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" value="b" />;
   const wrapper = mount(
     element,
@@ -423,7 +425,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons with correct val
 test('<SelectField checkboxes> - renders a set of Radio buttons which correctly reacts on change', () => {
   const onChange = jest.fn();
 
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -442,7 +443,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons which correctly
 test('<SelectField checkboxes> - renders a set of Checkboxes which correctly reacts on change (array check)', () => {
   const onChange = jest.fn();
 
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -462,7 +462,6 @@ test('<SelectField checkboxes> - renders a set of Checkboxes which correctly rea
 
 test('<SelectField checkboxes> - renders a set of Checkboxes which correctly reacts on change (array uncheck)', () => {
   const onChange = jest.fn();
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" value={['b']} />;
   const wrapper = mount(
     element,
@@ -503,7 +502,6 @@ test('<SelectField checkboxes> - renders a set of Checkboxes with correct labels
 test('<SelectField checkboxes> - renders a set of Checkboxes which correct labels (transform)', () => {
   const onChange = jest.fn();
   const element = (
-    // @ts-ignore Fix SelectFieldProps.
     <SelectField checkboxes name="x" transform={x => x.toUpperCase()} />
   );
   const wrapper = mount(
@@ -525,7 +523,6 @@ test('<SelectField checkboxes> - renders a set of Checkboxes which correct label
 test('<SelectField checkboxes> - renders a set of Radio buttons which correctly reacts on change (same value)', () => {
   const onChange = jest.fn();
 
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -544,7 +541,6 @@ test('<SelectField checkboxes> - renders a set of Radio buttons which correctly
 });
 
 test('<SelectField checkboxes> - renders a label', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField checkboxes name="x" label="y" />;
   const wrapper = mount(
     element,
@@ -558,7 +554,6 @@ test('<SelectField checkboxes> - renders a SelectField with correct error text (
   const error = new Error();
   const element = (
     <SelectField
-      // @ts-ignore Fix SelectFieldProps.
       checkboxes
       name="x"
       error={error}
@@ -578,7 +573,6 @@ test('<SelectField checkboxes> - renders a SelectField with correct error text (
   const error = new Error();
   const element = (
     <SelectField
-      // @ts-ignore Fix SelectFieldProps.
       checkboxes
       name="x"
       error={error}
@@ -595,7 +589,6 @@ test('<SelectField checkboxes> - renders a SelectField with correct error text (
 });
 
 test('<SelectField checkboxes> - renders Checkbox with appearance=checkbox', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField appearance="checkbox" checkboxes name="x" />;
   const wrapper = mount(
     element,
@@ -610,7 +603,6 @@ test('<SelectField checkboxes> - renders Checkbox with appearance=checkbox', ()
 });
 
 test('<SelectField checkboxes> - renders Switch with appearance=switch', () => {
-  // @ts-ignore Fix SelectFieldProps.
   const element = <SelectField appearance="switch" checkboxes name="x" />;
   const wrapper = mount(
     element,