Repository: vazco/uniforms (ts)

Issue:
Add the option to select DateField type
Currently, there is only the option to select a date and time on the DateField component. 
https://github.com/vazco/uniforms/blob/master/packages/uniforms-material/src/DateField.tsx#L61

Maybe the `type` prop could be variable even if its default value is "datetime-local"

Pull request:
Implemented variable DateField type 'date' | 'datetime-local'. (closes #1099).
Allow the user to choose the date HTML5 input type, being either `date` or `datetime-local`.

`type=datetime-local`
![image](https://user-images.githubusercontent.com/2383329/161319099-733eb423-0be4-4ab7-8a52-66867df9ad73.png)

`type=date`
![image](https://user-images.githubusercontent.com/2383329/161319203-bed25794-31eb-48c4-8cbe-9b36f792f0f6.png)


Patch:
diff --git a/docs/api-fields.md b/docs/api-fields.md
index da2e547e..666c390b 100644
--- a/docs/api-fields.md
+++ b/docs/api-fields.md
@@ -12,7 +12,7 @@ The list below contains a guaranteed set of fields, implemented in every theme p
 |   `AutoField`   |       Automatically renders a given field.        |
 |  `AutoFields`   |        Automatically renders given fields.        |
 |   `BoolField`   |                     Checkbox.                     |
-|   `DateField`   |           HTML5 `datetime-local` input.           |
+|   `DateField`   |      HTML5 `date` or `datetime-local` input.      |
 |  `ErrorField`   |         Error message for a given field.          |
 |  `ErrorsField`  |  Error message with a list of validation errors.  |
 |  `HiddenField`  | Hidden field (with a possibility to omit in DOM). |
@@ -162,6 +162,7 @@ import { BoolField } from 'uniforms-unstyled';
 |       `min`       |                                                                          Minimal value. Date object.                                                                           |                        All                         |
 | `showInlineError` |                                                          Field inline error. _Some description would be great, huh?_                                                           | antd, bootstrap3, bootstrap4, bootstrap5, semantic |
 |   `timeFormat`    |                                                           Display time picker in ampm (12hr) format or 24hr format.                                                            |                      material                      |
+|      `type`       |                                                 Display date and time or date only picker. Format value according to its type                                                  |                  All except antd                   |
 |  `wrapClassName`  | Field and sourroundings wrap className. In SemanticUI theme, this class name is used on ui input wrapper, so you can pass classes like small, huge, inverted, transparent etc. |    bootstrap3, bootstrap4, bootstrap5, semantic    |
 |   `wrapperCol`    |                                                         Field layout. The layout for input controls. Same as labelCol.                                                         |                        antd                        |
 
diff --git a/packages/uniforms-bootstrap3/src/DateField.tsx b/packages/uniforms-bootstrap3/src/DateField.tsx
index de359fe7..f16d8f9c 100644
--- a/packages/uniforms-bootstrap3/src/DateField.tsx
+++ b/packages/uniforms-bootstrap3/src/DateField.tsx
@@ -18,6 +18,7 @@ export type DateFieldProps = HTMLFieldProps<
     max?: Date;
     min?: Date;
     wrapClassName?: string;
+    type?: 'date' | 'datetime-local';
   }
 >;
 
@@ -34,8 +35,11 @@ function Date({
   placeholder,
   readOnly,
   value,
+  type,
   ...props
 }: DateFieldProps) {
+  const dateType = type === 'date' ? type : 'datetime-local';
+
   return wrapField(
     { ...props, id },
     <input
@@ -58,7 +62,7 @@ function Date({
       placeholder={placeholder}
       readOnly={readOnly}
       ref={inputRef}
-      type="datetime-local"
+      type={dateType}
       value={dateFormat(value) ?? ''}
     />,
   );
diff --git a/packages/uniforms-bootstrap4/src/DateField.tsx b/packages/uniforms-bootstrap4/src/DateField.tsx
index efabfa73..e83c09aa 100644
--- a/packages/uniforms-bootstrap4/src/DateField.tsx
+++ b/packages/uniforms-bootstrap4/src/DateField.tsx
@@ -17,6 +17,7 @@ export type DateFieldProps = HTMLFieldProps<
     max?: Date;
     min?: Date;
     wrapClassName?: string;
+    type?: 'date' | 'datetime-local';
   }
 >;
 
@@ -33,8 +34,11 @@ function Date({
   placeholder,
   readOnly,
   value,
+  type,
   ...props
 }: DateFieldProps) {
+  const dateType = type === 'date' ? type : 'datetime-local';
+
   return wrapField(
     { ...props, id },
     <input
@@ -58,7 +62,7 @@ function Date({
       placeholder={placeholder}
       readOnly={readOnly}
       ref={inputRef}
-      type="datetime-local"
+      type={dateType}
       value={dateFormat(value) ?? ''}
     />,
   );
diff --git a/packages/uniforms-bootstrap5/src/DateField.tsx b/packages/uniforms-bootstrap5/src/DateField.tsx
index efabfa73..e83c09aa 100644
--- a/packages/uniforms-bootstrap5/src/DateField.tsx
+++ b/packages/uniforms-bootstrap5/src/DateField.tsx
@@ -17,6 +17,7 @@ export type DateFieldProps = HTMLFieldProps<
     max?: Date;
     min?: Date;
     wrapClassName?: string;
+    type?: 'date' | 'datetime-local';
   }
 >;
 
@@ -33,8 +34,11 @@ function Date({
   placeholder,
   readOnly,
   value,
+  type,
   ...props
 }: DateFieldProps) {
+  const dateType = type === 'date' ? type : 'datetime-local';
+
   return wrapField(
     { ...props, id },
     <input
@@ -58,7 +62,7 @@ function Date({
       placeholder={placeholder}
       readOnly={readOnly}
       ref={inputRef}
-      type="datetime-local"
+      type={dateType}
       value={dateFormat(value) ?? ''}
     />,
   );
diff --git a/packages/uniforms-material/src/DateField.tsx b/packages/uniforms-material/src/DateField.tsx
index 991a559a..6dd869f1 100644
--- a/packages/uniforms-material/src/DateField.tsx
+++ b/packages/uniforms-material/src/DateField.tsx
@@ -5,7 +5,8 @@ import { FieldProps, connectField, filterDOMProps } from 'uniforms';
 
 /* istanbul ignore next */
 const DateConstructor = (typeof global === 'object' ? global : window).Date;
-const dateFormat = (value?: Date) => value && value.toISOString().slice(0, -8);
+const dateFormat = (value?: Date, type = 'datetime-local') =>
+  value?.toISOString().slice(0, type === 'datetime-local' ? -8 : -14);
 const dateParse = (timestamp: number, onChange: DateFieldProps['onChange']) => {
   const date = new DateConstructor(timestamp);
   if (date.getFullYear() < 10000) {
@@ -18,7 +19,10 @@ const dateParse = (timestamp: number, onChange: DateFieldProps['onChange']) => {
 export type DateFieldProps = FieldProps<
   Date,
   TextFieldProps,
-  { labelProps?: object }
+  {
+    labelProps?: object;
+    type?: 'date' | 'datetime-local';
+  }
 >;
 
 function Date({
@@ -36,10 +40,12 @@ function Date({
   readOnly,
   showInlineError,
   value,
+  type,
   ...props
 }: DateFieldProps) {
   const theme = useTheme();
   const themeProps = theme.props?.MuiTextField;
+  const dateType = type === 'date' ? type : 'datetime-local';
 
   return (
     <TextField
@@ -58,8 +64,8 @@ function Date({
       }
       placeholder={placeholder}
       ref={inputRef}
-      type="datetime-local"
-      value={dateFormat(value) ?? ''}
+      type={dateType}
+      value={dateFormat(value, type) ?? ''}
       {...filterDOMProps(props)}
     />
   );
diff --git a/packages/uniforms-semantic/src/DateField.tsx b/packages/uniforms-semantic/src/DateField.tsx
index 84ceed0e..5b4af24a 100644
--- a/packages/uniforms-semantic/src/DateField.tsx
+++ b/packages/uniforms-semantic/src/DateField.tsx
@@ -17,6 +17,7 @@ export type DateFieldProps = HTMLFieldProps<
     max?: Date;
     min?: Date;
     wrapClassName?: string;
+    type?: 'date' | 'datetime-local';
   }
 >;
 
@@ -41,8 +42,11 @@ function Date({
   showInlineError,
   value,
   wrapClassName,
+  type,
   ...props
 }: DateFieldProps) {
+  const dateType = type === 'date' ? type : 'datetime-local';
+
   return (
     <div
       className={classnames(className, { disabled, error, required }, 'field')}
@@ -75,7 +79,7 @@ function Date({
           placeholder={placeholder}
           readOnly={readOnly}
           ref={inputRef}
-          type="datetime-local"
+          type={dateType}
           value={dateFormat(value) ?? ''}
         />
 
diff --git a/packages/uniforms-unstyled/src/DateField.tsx b/packages/uniforms-unstyled/src/DateField.tsx
index 6b9255cc..4e8d30c7 100644
--- a/packages/uniforms-unstyled/src/DateField.tsx
+++ b/packages/uniforms-unstyled/src/DateField.tsx
@@ -8,7 +8,12 @@ const dateFormat = (value?: Date) => value?.toISOString().slice(0, -8);
 export type DateFieldProps = HTMLFieldProps<
   Date,
   HTMLDivElement,
-  { inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date }
+  {
+    inputRef?: Ref<HTMLInputElement>;
+    max?: Date;
+    min?: Date;
+    type?: 'date' | 'datetime-local';
+  }
 >;
 
 function Date({
@@ -23,8 +28,11 @@ function Date({
   placeholder,
   readOnly,
   value,
+  type,
   ...props
 }: DateFieldProps) {
+  const dateType = type === 'date' ? type : 'datetime-local';
+
   return (
     <div {...filterDOMProps(props)}>
       {label && <label htmlFor={id}>{label}</label>}
@@ -46,7 +54,7 @@ function Date({
         placeholder={placeholder}
         readOnly={readOnly}
         ref={inputRef}
-        type="datetime-local"
+        type={dateType}
         value={dateFormat(value) ?? ''}
       />
     </div>
diff --git a/website/index.module.css b/website/index.module.css
index 7e077bb5..7f48dda6 100644
--- a/website/index.module.css
+++ b/website/index.module.css
@@ -292,6 +292,10 @@
   padding: 2em;
 }
 
+.no-padding {
+  padding: 0;
+}
+
 @media (max-width: 500px) {
   .preview {
     padding: 1em;
@@ -547,6 +551,20 @@
   margin-bottom: 8em;
 }
 
+.flex-center {
+  display: flex;
+  justify-content: center;
+}
+
+.top-margin {
+  margin-top: 70px;
+}
+
+.long-button {
+  max-width: 100%;
+  white-space: pre-line;
+}
+
 .text {
   font-family: Poppins, sans-serif;
   font-stretch: normal;
diff --git a/website/pages-parts/LandingPage/CommercialServices.tsx b/website/pages-parts/LandingPage/CommercialServices.tsx
index 73ad7ced..c21ab09e 100644
--- a/website/pages-parts/LandingPage/CommercialServices.tsx
+++ b/website/pages-parts/LandingPage/CommercialServices.tsx
@@ -3,66 +3,100 @@ import React from 'react';
 
 import { Button } from '../../components/Button';
 import { Heading } from '../../components/Heading';
-import { Subtitle } from '../../components/Subtitle';
 import styles from '../../index.module.css';
 
 export function CommercialServices() {
   return (
-    <div
-      className={classNames(
-        'hero hero--primary',
-        styles['overflow-hidden'],
-        styles.whyus,
-      )}
-    >
-      <div className="container">
-        <div className="section">
-          <div className="row">
+    <div className={classNames('section', styles.example)}>
+      <div
+        className={classNames(
+          'row',
+          styles['blue-accent'],
+          styles['reverse-wrap'],
+        )}
+      >
+        <div className="col">
+          <div
+            className={classNames(
+              styles['preview-border'],
+              styles['preview'],
+              styles['no-padding'],
+            )}
+          >
+            <a
+              className={styles['flex-center']}
+              href="https://bit.ly/3LaZkTl"
+              target="_blank"
+              rel="noopener noreferrer"
+            >
+              <img
+                alt="form-builder"
+                className={styles.company}
+                src="assets/form-builder-screenshot.png"
+              />
+            </a>
+          </div>
+        </div>
+
+        <div className="col col--4">
+          <div
+            className={classNames(
+              styles['solid-border-box'],
+              styles['simple-yet-powerful-text'],
+            )}
+          >
+            <p
+              className={classNames(
+                styles.centered,
+                styles.text,
+                styles['heading-helper'],
+              )}
+            >
+              Commercial support
+            </p>
+            <Heading>Form Builder</Heading>
+            <p
+              className={classNames(
+                styles.centered,
+                styles.text,
+                styles['heading-helper'],
+              )}
+            >
+              A commercial implementation of uniforms, which can save you up to
+              1500 hours of work.
+            </p>
+            <ul className={classNames(styles['commercial-bullets'])}>
+              <li>
+                <b>Ideal for building no-code tools</b> - thanks to a
+                schema-first approach
+              </li>
+              <li>
+                <b>Package</b> - easy to integrate with your project and design
+                system
+              </li>
+              <li>
+                <b>Powerful solution</b> - custom fields, full state machine
+              </li>
+              <li>
+                <b>Good level of support</b> - based on our popular OS solution,
+                uniforms
+              </li>
+            </ul>
             <div
-              className={classNames('col col--5', styles['section-content'])}
+              className={classNames(
+                styles['flex-center'],
+                styles['top-margin'],
+              )}
             >
-              <Subtitle>Commercial support</Subtitle>
-              <Heading className={styles['heading-white']}>
-                Form Builder
-              </Heading>
-              <p className={classNames(styles.text, styles.paragraph)}>
-                A commercial implementation of uniforms, which can save you up
-                to 1500 hours of work.
-              </p>
-              <ul
+              <Button
+                to="https://www.vazco.eu/form-builder"
                 className={classNames(
-                  styles.text,
-                  styles.bullets,
-                  styles['commercial-bullets'],
+                  styles.discussions,
+                  styles['long-button'],
                 )}
               >
-                <li>
-                  <b>Ideal for building no-code tools</b> - thanks to a
-                  schema-first approach
-                </li>
-                <li>
-                  <b>Package</b> - easy to integrate with your project and
-                  design system
-                </li>
-                <li>
-                  <b>Powerful solution</b> - custom fields, full state machine
-                </li>
-                <li>
-                  <b>Good level of support</b> - based on our popular OS
-                  solution, uniforms
-                </li>
-              </ul>
-              <div className={styles['center-if-sm']}>
-                <Button to="https://www.vazco.eu/form-builder">See more</Button>
-              </div>
-            </div>
-            <div className={classNames('col', styles['commercial-box'])}>
-              <img
-                width="600px"
-                height="600px"
-                src="/img/form-builder.png"
-                alt="Form Builder"
-              />
+                Check Form Builder in action
+              </Button>
             </div>
           </div>
         </div>
diff --git a/website/pages-parts/LandingPage/LandingPage.tsx b/website/pages-parts/LandingPage/LandingPage.tsx
index 9d93b775..77a2515a 100644
--- a/website/pages-parts/LandingPage/LandingPage.tsx
+++ b/website/pages-parts/LandingPage/LandingPage.tsx
@@ -33,7 +33,9 @@ export function LandingPage() {
         <div className="container">
           <Comparison />
         </div>
-        <CommercialServices />
+        <div className="container">
+          <CommercialServices />
+        </div>
         <Footer />
       </div>
     </div>


Test patch:
diff --git a/packages/uniforms-bootstrap3/__tests__/DateField.tsx b/packages/uniforms-bootstrap3/__tests__/DateField.tsx
index b189abbf..465f1b69 100644
--- a/packages/uniforms-bootstrap3/__tests__/DateField.tsx
+++ b/packages/uniforms-bootstrap3/__tests__/DateField.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { DateField } from 'uniforms-bootstrap3';
+import { DateField, DateFieldProps } from 'uniforms-bootstrap3';
 
 import createContext from './_createContext';
 import mount from './_mount';
@@ -51,6 +51,17 @@ test('<DateField> - renders an input with correct disabled state', () => {
   expect(wrapper.find('input').prop('disabled')).toBe(true);
 });
 
+test.each(['date', 'datetime-local'] as const)(
+  '<DateField> - renders a Input with correct type : "%s"',
+  (type: DateFieldProps['type']) => {
+    const element = <DateField name="x" type={type} />;
+    const wrapper = mount(element, createContext({ x: { type: Date } }));
+
+    expect(wrapper.find('input')).toHaveLength(1);
+    expect(wrapper.find('input').prop('type')).toBe(type);
+  },
+);
+
 test('<DateField> - renders an input with correct readOnly state', () => {
   const element = <DateField name="x" readOnly />;
   const wrapper = mount(element, createContext({ x: { type: Date } }));
diff --git a/packages/uniforms-bootstrap4/__tests__/DateField.tsx b/packages/uniforms-bootstrap4/__tests__/DateField.tsx
index 7aa96ffa..13d5e2ce 100644
--- a/packages/uniforms-bootstrap4/__tests__/DateField.tsx
+++ b/packages/uniforms-bootstrap4/__tests__/DateField.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { DateField } from 'uniforms-bootstrap4';
+import { DateField, DateFieldProps } from 'uniforms-bootstrap4';
 
 import createContext from './_createContext';
 import mount from './_mount';
@@ -43,6 +43,17 @@ test('<DateField> - renders an input with correct type', () => {
   expect(wrapper.find('input').prop('type')).toBe('datetime-local');
 });
 
+test.each(['datetime-local', 'date'] as const)(
+  '<DateField> - renders a Input with correct type : "%s"',
+  (type: DateFieldProps['type']) => {
+    const element = <DateField name="x" type={type} />;
+    const wrapper = mount(element, createContext({ x: { type: Date } }));
+
+    expect(wrapper.find('input')).toHaveLength(1);
+    expect(wrapper.find('input').prop('type')).toBe(type);
+  },
+);
+
 test('<DateField> - renders an input with correct disabled state', () => {
   const element = <DateField name="x" disabled />;
   const wrapper = mount(element, createContext({ x: { type: Date } }));
diff --git a/packages/uniforms-bootstrap5/__tests__/DateField.tsx b/packages/uniforms-bootstrap5/__tests__/DateField.tsx
index 39902a0f..8938b13a 100644
--- a/packages/uniforms-bootstrap5/__tests__/DateField.tsx
+++ b/packages/uniforms-bootstrap5/__tests__/DateField.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { DateField } from 'uniforms-bootstrap5';
+import { DateField, DateFieldProps } from 'uniforms-bootstrap5';
 
 import createContext from './_createContext';
 import mount from './_mount';
@@ -43,6 +43,17 @@ test('<DateField> - renders an input with correct type', () => {
   expect(wrapper.find('input').prop('type')).toBe('datetime-local');
 });
 
+test.each(['datetime-local', 'date'] as const)(
+  '<DateField> - renders a Input with correct type : "%s"',
+  (type: DateFieldProps['type']) => {
+    const element = <DateField name="x" type={type} />;
+    const wrapper = mount(element, createContext({ x: { type: Date } }));
+
+    expect(wrapper.find('input')).toHaveLength(1);
+    expect(wrapper.find('input').prop('type')).toBe(type);
+  },
+);
+
 test('<DateField> - renders an input with correct disabled state', () => {
   const element = <DateField name="x" disabled />;
   const wrapper = mount(element, createContext({ x: { type: Date } }));
diff --git a/packages/uniforms-material/__tests__/DateField.tsx b/packages/uniforms-material/__tests__/DateField.tsx
index 81a2476f..ee605768 100644
--- a/packages/uniforms-material/__tests__/DateField.tsx
+++ b/packages/uniforms-material/__tests__/DateField.tsx
@@ -5,7 +5,7 @@ import Input from '@material-ui/core/Input';
 import createMuiTheme from '@material-ui/core/styles/createMuiTheme';
 import ThemeProvider from '@material-ui/styles/ThemeProvider/ThemeProvider';
 import React from 'react';
-import { DateField } from 'uniforms-material';
+import { DateField, DateFieldProps } from 'uniforms-material';
 import { render } from 'uniforms/__suites__';
 
 import createContext from './_createContext';
@@ -132,18 +132,22 @@ test('<DateField> - renders a Input with correct value (default)', () => {
   expect(wrapper.find(Input).prop('value')).toBe('');
 });
 
-test('<DateField> - renders a Input with correct value (model)', () => {
-  const now = new Date();
-  const element = <DateField name="x" />;
-  const wrapper = mount(
-    element,
-    createContext({ x: { type: Date } }, { model: { x: now } }),
-  );
+test.each(['datetime-local', 'date'] as const)(
+  '<DateField> - renders a Input with correct value (model) when type is "%s"',
+  (type: DateFieldProps['type']) => {
+    const now = new Date();
+    const element = <DateField name="x" type={type} />;
+    const sliceEnd = type === 'datetime-local' ? -8 : -14;
+    const wrapper = mount(
+      element,
+      createContext({ x: { type: Date } }, { model: { x: now } }),
+    );
 
-  expect(wrapper.find(Input).prop('value')).toEqual(
-    now.toISOString().slice(0, -8),
-  );
-});
+    expect(wrapper.find(Input).prop('value')).toEqual(
+      now.toISOString().slice(0, sliceEnd),
+    );
+  },
+);
 
 test('<DateField> - renders a Input with correct value (specified)', () => {
   const now = new Date();
diff --git a/packages/uniforms-semantic/__tests__/DateField.tsx b/packages/uniforms-semantic/__tests__/DateField.tsx
index d3497bda..2edffc6c 100644
--- a/packages/uniforms-semantic/__tests__/DateField.tsx
+++ b/packages/uniforms-semantic/__tests__/DateField.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { DateField } from 'uniforms-semantic';
+import { DateField, DateFieldProps } from 'uniforms-semantic';
 
 import createContext from './_createContext';
 import mount from './_mount';
@@ -43,6 +43,17 @@ test('<DateField> - renders an input with correct type', () => {
   expect(wrapper.find('input').prop('type')).toBe('datetime-local');
 });
 
+test.each(['datetime-local', 'date'] as const)(
+  '<DateField> - renders a Input with correct type : "%s"',
+  (type: DateFieldProps['type']) => {
+    const element = <DateField name="x" type={type} />;
+    const wrapper = mount(element, createContext({ x: { type: Date } }));
+
+    expect(wrapper.find('input')).toHaveLength(1);
+    expect(wrapper.find('input').prop('type')).toBe(type);
+  },
+);
+
 test('<DateField> - renders an input with correct disabled state', () => {
   const element = <DateField name="x" disabled />;
   const wrapper = mount(element, createContext({ x: { type: Date } }));
diff --git a/packages/uniforms-unstyled/__tests__/DateField.tsx b/packages/uniforms-unstyled/__tests__/DateField.tsx
index c5dd6688..d068d40d 100644
--- a/packages/uniforms-unstyled/__tests__/DateField.tsx
+++ b/packages/uniforms-unstyled/__tests__/DateField.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { DateField } from 'uniforms-unstyled';
+import { DateField, DateFieldProps } from 'uniforms-unstyled';
 
 import createContext from './_createContext';
 import mount from './_mount';
@@ -43,6 +43,17 @@ test('<DateField> - renders an input with correct type', () => {
   expect(wrapper.find('input').prop('type')).toBe('datetime-local');
 });
 
+test.each(['datetime-local', 'date'] as const)(
+  '<DateField> - renders a Input with correct type : "%s"',
+  (type: DateFieldProps['type']) => {
+    const element = <DateField name="x" type={type} />;
+    const wrapper = mount(element, createContext({ x: { type: Date } }));
+
+    expect(wrapper.find('input')).toHaveLength(1);
+    expect(wrapper.find('input').prop('type')).toBe(type);
+  },
+);
+
 test('<DateField> - renders an input with correct disabled state', () => {
   const element = <DateField name="x" disabled />;
   const wrapper = mount(element, createContext({ x: { type: Date } }));