├── .coveralls.yaml ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── 1.bug.md │ ├── 2.feature.md │ ├── 3.support.md │ └── config.yml └── workflows │ ├── formik-mui-x-date-pickers.yaml │ ├── formik-mui.yaml │ ├── linting.yaml │ ├── pages.yaml │ └── storybook.yaml ├── .gitignore ├── .prettierignore ├── .storybook ├── main.js ├── preview-head.html └── preview.js ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── README.md ├── docs │ ├── api │ │ ├── mui-x-date-pickers.md │ │ └── mui.md │ └── guide │ │ ├── custom-component.md │ │ ├── faq.md │ │ ├── getting-started.md │ │ └── migrating.md ├── docusaurus.config.ts ├── package-lock.json ├── package.json ├── sidebars.ts ├── src │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.tsx │ │ └── styles.module.css └── static │ ├── .nojekyll │ └── img │ ├── docusaurus-social-card.jpg │ ├── favicon.ico │ ├── formik-logo.png │ ├── logo.svg │ └── mui-logo.svg ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── formik-mui-x-date-pickers │ ├── README.md │ ├── package.json │ ├── src │ │ ├── DatePicker.tsx │ │ ├── DateTimePicker.tsx │ │ ├── DesktopDatePicker.tsx │ │ ├── DesktopDateTimePicker.tsx │ │ ├── DesktopTimePicker.tsx │ │ ├── MobileDatePicker.tsx │ │ ├── MobileDateTimePicker.tsx │ │ ├── MobileTimePicker.tsx │ │ ├── StaticDatePicker.tsx │ │ ├── StaticDateTimePicker.tsx │ │ ├── StaticTimePicker.tsx │ │ ├── TimePicker.tsx │ │ ├── __tests__ │ │ │ ├── DatePicker.test.tsx │ │ │ ├── DateTimePicker.test.tsx │ │ │ ├── DesktopDatePicker.test.tsx │ │ │ ├── DesktopDateTimePicker.test.tsx │ │ │ ├── DesktopTimePicker.test.tsx │ │ │ ├── MobileDatePicker.test.tsx │ │ │ ├── MobileDateTimePicker.test.tsx │ │ │ ├── MobileTimePicker.test.tsx │ │ │ ├── StaticDatePicker.test.tsx │ │ │ ├── StaticDateTimePicker.test.tsx │ │ │ ├── StaticTimePicker.test.tsx │ │ │ ├── TimePicker.test.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── DatePicker.test.tsx.snap │ │ │ │ ├── DateTimePicker.test.tsx.snap │ │ │ │ ├── DesktopDatePicker.test.tsx.snap │ │ │ │ ├── DesktopDateTimePicker.test.tsx.snap │ │ │ │ ├── DesktopTimePicker.test.tsx.snap │ │ │ │ ├── MobileDatePicker.test.tsx.snap │ │ │ │ ├── MobileDateTimePicker.test.tsx.snap │ │ │ │ ├── MobileTimePicker.test.tsx.snap │ │ │ │ ├── StaticTimePicker.test.tsx.snap │ │ │ │ └── TimePicker.test.tsx.snap │ │ │ └── timezone.test.ts │ │ ├── errorHandler.ts │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── vitest-setup.js └── formik-mui │ ├── README.md │ ├── package.json │ ├── src │ ├── Autocomplete.tsx │ ├── Checkbox.tsx │ ├── CheckboxWithLabel.tsx │ ├── InputBase.tsx │ ├── RadioGroup.tsx │ ├── Select.tsx │ ├── SimpleFileUpload.tsx │ ├── Switch.tsx │ ├── TextField.tsx │ ├── ToggleButtonGroup.tsx │ ├── __tests__ │ │ ├── Autocomplete.test.tsx │ │ ├── Checkbox.test.tsx │ │ ├── InputBase.test.tsx │ │ ├── RadioGroup.test.tsx │ │ ├── Select.test.tsx │ │ ├── SimpleFileUpload.test.tsx │ │ ├── Switch.test.tsx │ │ ├── TextField.test.tsx │ │ ├── ToggleButtonGroup.test.tsx │ │ ├── __snapshots__ │ │ │ ├── Autocomplete.test.tsx.snap │ │ │ ├── Checkbox.test.tsx.snap │ │ │ ├── InputBase.test.tsx.snap │ │ │ ├── RadioGroup.test.tsx.snap │ │ │ ├── Select.test.tsx.snap │ │ │ ├── SimpleFileUpload.test.tsx.snap │ │ │ ├── Switch.test.tsx.snap │ │ │ ├── TextField.test.tsx.snap │ │ │ └── ToggleButtonGroup.test.tsx.snap │ │ └── utils.tsx │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── vitest-setup.js ├── public └── .gitkeep └── src └── stories ├── Autocomplete.stories.tsx ├── Checkbox.stories.tsx ├── DatePicker.stories.tsx ├── DateTimePicker.stories.tsx ├── FormValues.tsx ├── InputBase.stories.tsx ├── RadioGroup.stories.tsx ├── Select.stories.tsx ├── SimpleFileUpload.stories.tsx ├── Switches.stories.tsx ├── TextField.stories.tsx ├── TimePicker.stories.tsx ├── ToggleButtonGroup.stories.tsx └── Wrapper.tsx /.coveralls.yaml: -------------------------------------------------------------------------------- 1 | repo_token: EHws3h3iNAsrtTLAMhIW3s4leykz5ZSkj 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | env: { 4 | browser: true, 5 | }, 6 | settings: { 7 | react: { 8 | version: 'detect', 9 | }, 10 | }, 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:react/recommended', 15 | 'plugin:prettier/recommended', 16 | ], 17 | plugins: ['react-hooks'], 18 | overrides: [ 19 | { 20 | files: ['**/*.test.tsx', '**/*.test.ts'], 21 | env: { 22 | jest: true, // now **/*.test.js files' env has both es6 *and* jest 23 | }, 24 | // Can't extend in overrides: https://github.com/eslint/eslint/issues/8813 25 | // 'extends': ['plugin:jest/recommended'] 26 | plugins: ['jest'], 27 | rules: { 28 | 'jest/no-disabled-tests': 'warn', 29 | 'jest/no-focused-tests': 'error', 30 | 'jest/no-identical-title': 'error', 31 | 'jest/prefer-to-have-length': 'warn', 32 | 'jest/valid-expect': 'error', 33 | '@typescript-eslint/ban-ts-ignore': 'off', 34 | '@typescript-eslint/no-empty-function': 'off', 35 | 'no-restricted-imports': 'off', 36 | }, 37 | }, 38 | { 39 | files: ['src/stories/*.tsx'], 40 | env: { 41 | node: true, 42 | }, 43 | }, 44 | { 45 | files: ['stories/*.story.tsx'], 46 | rules: { 'react/display-name': 0 }, 47 | }, 48 | ], 49 | rules: { 50 | '@typescript-eslint/explicit-module-boundary-types': 0, 51 | '@typescript-eslint/no-unused-vars': [ 52 | 'error', 53 | { ignoreRestSiblings: true }, 54 | ], 55 | 'react/prop-types': 0, 56 | 'no-restricted-imports': ['error', '@mui/material', '@mui/x-date-pickers'], 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1.bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 🐛 3 | about: Create a bug report. 4 | labels: 'bug' 5 | --- 6 | 7 | ## Current Behavior 😯 8 | 9 | 10 | 11 | ## Expected Behavior 🤔 12 | 13 | 14 | 15 | ## Steps to Reproduce 🕹 16 | 17 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 🎉 3 | about: Suggest a new idea for the project. 4 | labels: 'enhancement' 5 | --- 6 | 7 | 8 | 9 | ## Summary 💡 10 | 11 | 12 | 13 | ## Motivation 🔦 14 | 15 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3.support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: General support 4 | labels: 'question' 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false # force the usage of a template 2 | -------------------------------------------------------------------------------- /.github/workflows/formik-mui-x-date-pickers.yaml: -------------------------------------------------------------------------------- 1 | name: Build formik-mui-x-date-pickers 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [18.x, 20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - name: build 26 | run: | 27 | npm run typecheck 28 | npm run test 29 | npm run build 30 | working-directory: packages/formik-mui-x-date-pickers 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /.github/workflows/formik-mui.yaml: -------------------------------------------------------------------------------- 1 | name: Build formik-mui 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [18.x, 20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - name: build 26 | run: | 27 | npm run typecheck 28 | npm run test 29 | npm run build 30 | working-directory: packages/formik-mui 31 | env: 32 | CI: true 33 | -------------------------------------------------------------------------------- /.github/workflows/linting.yaml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: build 25 | run: | 26 | npm ci 27 | npm run lint 28 | env: 29 | CI: true 30 | -------------------------------------------------------------------------------- /.github/workflows/pages.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | 13 | 14 | - name: Build Pages 15 | run: | 16 | # Build Storybook 17 | npm ci 18 | npm run build-storybook 19 | cd docs 20 | npm ci 21 | git config --global user.email "actions@github.com" 22 | git config --global user.name "GitHub Pages Action" 23 | npm run build 24 | mv ../storybook-static ./build/storybook 25 | npm run deploy -- --skip-build 26 | env: 27 | GIT_USER: ${{ secrets.ACCESS_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/storybook.yaml: -------------------------------------------------------------------------------- 1 | name: Build Storybook 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | 8 | strategy: 9 | matrix: 10 | node-version: [20.x] 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - name: build 19 | run: | 20 | npm ci 21 | npm run build-storybook 22 | env: 23 | CI: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | .DS_Store 5 | .rpt2_cache 6 | yarn-error.log 7 | .idea 8 | yarn.lock 9 | 10 | storybook-static/ 11 | coverage 12 | lerna-debug.log 13 | 14 | # IDE Files 15 | *.code-workspace -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | storybook-static -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react-vite').StorybookConfig } */ 2 | const config = { 3 | stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"], 4 | staticDirs: ["../public"], 5 | addons: [ 6 | "@storybook/addon-links", 7 | "@storybook/addon-essentials", 8 | "@storybook/addon-interactions", 9 | ], 10 | framework: { 11 | name: "@storybook/react-vite", 12 | options: {}, 13 | }, 14 | }; 15 | export default config; 16 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react').Preview } */ 2 | const preview = { 3 | parameters: { 4 | controls: { 5 | matchers: { 6 | color: /(background|color)$/i, 7 | date: /Date$/, 8 | }, 9 | }, 10 | }, 11 | 12 | // tags: ["autodocs"] 13 | }; 14 | 15 | export default preview; -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "cSpell.words": ["testid"] 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.16 2 | 3 | - Fixed TextField select variation 4 | - Updated storybook and other dependencies 5 | 6 | ## 0.0.15 7 | 8 | - Fixed switch behaviour (#42) 9 | 10 | ## 0.0.14 11 | 12 | - BREAKING: Remove all default exports 13 | - Export initerfaces at root 14 | - add typings reference to package.json 15 | 16 | ## 0.0.10 17 | 18 | - Updated dependencies 19 | - Export mapper functions 20 | - More aggressive omits on Material UI types 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Testing builds locally 2 | 3 | ```bash 4 | # from the root of this workspace 5 | yarn workspace @mercantile/formik-mui package 6 | yarn workspace @mercantile/formik-mui-x-date-pickers package 7 | npm pack --workspace ./packages 8 | 9 | # in your project's folder 10 | npm i ../formik-mui/formik-mui-4.0.0.tgz 11 | npm i ../formik-mui/formik-mui-x-date-pickers-1.0.0.tgz 12 | ``` 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ciaran Liedeman https://github.com/cliedeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./packages/formik-mui/README.md -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ npm install 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ npm run storybook 15 | ``` 16 | 17 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ npm run build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | ``` 30 | $ GIT_USER= USE_SSH=true npm run deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /docs/docs/api/mui-x-date-pickers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: mui-x-date-pickers 3 | title: MUI X Date and Time Pickers 4 | --- 5 | 6 | The following props are always excluded: `name`, `value`, `error`, and additional ones where it makes sense. 7 | 8 | `TextField` props may be specified inside the `textField` prop. If no `renderInput` function is provided, the `textField` props are forwarded to the `TextField` input. 9 | 10 | When using picker components initialize the starting value to `new Date()` and not the empty string. 11 | 12 | ## DatePicker 13 | 14 | #### Example 15 | 16 | ```jsx 17 | import { DatePicker } from 'formik-mui-x-date-pickers'; 18 | 19 | ; 26 | ``` 27 | 28 | #### [MUI DatePicker Documentation](https://mui.com/x/api/date-pickers/date-picker/) 29 | 30 | ## DateTimePicker 31 | 32 | #### Example 33 | 34 | ```jsx 35 | import { DateTimePicker } from 'formik-mui-x-date-pickers'; 36 | 37 | ; 38 | ``` 39 | 40 | #### [MUI DateTimePicker Documentation](https://mui.com/x/api/date-pickers/date-time-picker/) 41 | 42 | ## DesktopDatePicker 43 | 44 | #### Example 45 | 46 | ```jsx 47 | import { DesktopDatePicker } from 'formik-mui-x-date-pickers'; 48 | 49 | ; 50 | ``` 51 | 52 | #### [MUI DesktopDatePicker Documentation](https://mui.com/x/api/date-pickers/desktop-date-picker/) 53 | 54 | ## DesktopDateTimePicker 55 | 56 | #### Example 57 | 58 | ```jsx 59 | import { DesktopDateTimePicker } from 'formik-mui-x-date-pickers'; 60 | 61 | ; 62 | ``` 63 | 64 | #### [MUI DesktopDateTimePicker Documentation](https://mui.com/x/api/date-pickers/desktop-date-time-picker/) 65 | 66 | ## DesktopTimePicker 67 | 68 | #### Example 69 | 70 | ```jsx 71 | import { DesktopTimePicker } from 'formik-mui-x-date-pickers'; 72 | 73 | ; 74 | ``` 75 | 76 | #### [MUI DesktopTimePicker Documentation](https://mui.com/x/api/date-pickers/desktop-time-picker/) 77 | 78 | ## MobileDatePicker 79 | 80 | #### Example 81 | 82 | ```jsx 83 | import { MobileDatePicker } from 'formik-mui-x-date-pickers'; 84 | 85 | ; 86 | ``` 87 | 88 | #### [MUI MobileDatePicker Documentation](https://mui.com/x/api/date-pickers/mobile-date-picker/) 89 | 90 | ## MobileDateTimePicker 91 | 92 | #### Example 93 | 94 | ```jsx 95 | import { MobileDateTimePicker } from 'formik-mui-x-date-pickers'; 96 | 97 | ; 98 | ``` 99 | 100 | #### [MUI MobileDateTimePicker Documentation](https://mui.com/x/api/date-pickers/mobile-date-time-picker/) 101 | 102 | ## MobileTimePicker 103 | 104 | #### Example 105 | 106 | ```jsx 107 | import { MobileTimePicker } from 'formik-mui-x-date-pickers'; 108 | 109 | ; 110 | ``` 111 | 112 | #### [MUI MobileTimePicker Documentation](https://mui.com/x/api/date-pickers/mobile-time-picker/) 113 | 114 | ## StaticDatePicker 115 | 116 | #### Example 117 | 118 | ```jsx 119 | import { StaticDatePicker } from 'formik-mui-x-date-pickers'; 120 | 121 | ; 122 | ``` 123 | 124 | #### [MUI StaticDatePicker Documentation](https://mui.com/x/api/date-pickers/static-date-picker/) 125 | 126 | ## StaticDateTimePicker 127 | 128 | #### Example 129 | 130 | ```jsx 131 | import { StaticDateTimePicker } from 'formik-mui-x-date-pickers'; 132 | 133 | ; 134 | ``` 135 | 136 | #### [MUI StaticDateTimePicker Documentation](https://mui.com/x/api/date-pickers/static-date-time-picker/) 137 | 138 | ## StaticTimePicker 139 | 140 | #### Example 141 | 142 | ```jsx 143 | import { StaticTimePicker } from 'formik-mui-x-date-pickers'; 144 | 145 | ; 146 | ``` 147 | 148 | #### [MUI StaticTimePicker Documentation](https://mui.com/x/api/date-pickers/static-time-picker/) 149 | 150 | ## TimePicker 151 | 152 | #### Example 153 | 154 | ```jsx 155 | import { TimePicker } from 'formik-mui-x-date-pickers'; 156 | 157 | ; 158 | ``` 159 | 160 | #### [MUI TimePicker Documentation](https://mui.com/x/api/date-pickers/time-picker/) 161 | -------------------------------------------------------------------------------- /docs/docs/api/mui.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: mui 3 | title: MUI 4 | --- 5 | 6 | The following props are always excluded: `name`, `value`, `error`, and additional ones where it makes sense. 7 | 8 | ## Autocomplete 9 | 10 | #### Example 11 | 12 | ```jsx 13 | import { Autocomplete } from 'formik-mui'; 14 | 15 | const options = [{ title: 'The Shawshank Redemption', year: 1994 }, ...] 16 | 17 | option.title} 22 | style={{ width: 300 }} 23 | renderInput={(params: AutocompleteRenderInputParams) => ( 24 | 33 | )} 34 | />; 35 | ``` 36 | 37 | _Note the manual inclusion of the error._ 38 | 39 | #### [MUI Autocomplete Documentation](https://mui.com/api/autocomplete/) 40 | 41 | ## Checkbox 42 | 43 | #### Example 44 | 45 | ```jsx 46 | import { Checkbox } from 'formik-mui'; 47 | 48 | ; 49 | ``` 50 | 51 | #### [MUI Checkbox Documentation](https://mui.com/api/checkbox/) 52 | 53 | ## CheckboxWithLabel 54 | 55 | A convenience wrapper that adds label to `Checkbox` using `FormControlLabel`. Supports all the same properties as `Checkbox` and accepts an additional `Label` prop, which are props applied to `FormControlLabel`. 56 | 57 | #### Example 58 | 59 | ```jsx 60 | import { CheckboxWithLabel } from 'formik-mui'; 61 | 62 | ; 68 | ``` 69 | 70 | #### [MUI Documentation](https://mui.com/api/form-control-label/) 71 | 72 | ## InputBase 73 | 74 | #### Example 75 | 76 | ```jsx 77 | import { InputBase } from 'formik-mui'; 78 | 79 | ; 80 | ``` 81 | 82 | #### [MUI InputBase Documentation](https://mui.com/api/input-base/) 83 | 84 | ## RadioGroup 85 | 86 | #### Example 87 | 88 | ```jsx 89 | import { FormControlLabel, Radio, LinearProgress } from '@mui/material/core'; 90 | import { Formik, Field } from 'formik'; 91 | import { RadioGroup } from 'formik-mui'; 92 | 93 | 94 | {({ isSubmitting }) => ( 95 | 96 | } 99 | label="Painting" 100 | disabled={isSubmitting} 101 | /> 102 | } 105 | label="Drawing" 106 | disabled={isSubmitting} 107 | /> 108 | } 111 | label="None" 112 | disabled 113 | /> 114 | 115 | )} 116 | ; 117 | ``` 118 | 119 | #### [MUI RadioGroup Documentation](https://mui.com/api/radio-group/) 120 | 121 | ## Select 122 | 123 | #### Example 124 | 125 | ```jsx 126 | import { Field } from 'formik'; 127 | import MenuItem from '@mui/material/MenuItem'; 128 | import InputLabel from '@mui/material/InputLabel'; 129 | import FormControl from '@mui/material/FormControl'; 130 | import { Select } from 'formik-mui'; 131 | 132 | 141 | !age 142 | ? 'Please enter your age' 143 | : age < 21 144 | ? 'You must be 21 or older' 145 | : undefined 146 | } 147 | > 148 | Ten 149 | Twenty 150 | Thirty 151 | ; 152 | ``` 153 | 154 | #### [MUI Select Documentation](https://mui.com/api/select/) 155 | 156 | ## SimpleFileUpload 157 | 158 | #### Example 159 | 160 | ```jsx 161 | import { SimpleFileUpload } from 'formik-mui'; 162 | 163 | ; 164 | ``` 165 | 166 | #### API 167 | 168 | ```jsx 169 | interface SimpleFileUploadProps { 170 | name: string; // Field Name 171 | label: string; // Field Label 172 | disabled?: boolean; 173 | // Customize the Input Component 174 | InputProps?: Omit; 175 | // Customize the Input Label Component 176 | InputLabelProps?: InputLabelProps; 177 | } 178 | ``` 179 | 180 | ## Switch 181 | 182 | ```jsx 183 | import { Switch } from 'formik-mui'; 184 | 185 | ; 186 | ``` 187 | 188 | #### [MUI Switch Documentation](https://mui.com/api/switch/) 189 | 190 | ## TextField 191 | 192 | #### Example 193 | 194 | ```jsx 195 | import { TextField } from 'formik-mui'; 196 | 197 | ; 204 | ``` 205 | 206 | #### [MUI TextField Documentation](https://mui.com/api/text-field/) 207 | 208 | ## ToggleButtonGroup 209 | 210 | #### Example 211 | 212 | ```jsx 213 | import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter'; 214 | import FormatAlignJustifyIcon from '@mui/icons-material/FormatAlignJustify'; 215 | import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft'; 216 | import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight'; 217 | import ToggleButton from '@mui/material/ToggleButton'; 218 | import { ToggleButtonGroup } from 'formik-mui'; 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | ; 234 | ``` 235 | 236 | _Note the `type=checkbox` attribute._ 237 | 238 | #### [MUI ToggleButtonGroup Documentation](https://mui.com/api/toggle-button-group/) 239 | -------------------------------------------------------------------------------- /docs/docs/guide/custom-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: custom-component 3 | title: Creating Custom Components 4 | --- 5 | 6 | # Notes 7 | 8 | Wrappers have a corresponding function export (`fieldToTextField`, `fieldToCheckbox` etc.) that encapsulates the logic used to map Formik props into the MUI shapes. 9 | 10 | # Examples 11 | 12 | ## Upper Casing Field 13 | 14 | A simple test input that always uppercases the input: 15 | 16 | ```jsx 17 | import TextField from '@mui/material/TextField'; 18 | import { fieldToTextField, TextFieldProps } from 'formik-mui'; 19 | 20 | function UpperCasingTextField(props: TextFieldProps) { 21 | const { 22 | form: { setFieldValue }, 23 | field: { name }, 24 | } = props; 25 | const onChange = React.useCallback( 26 | (event) => { 27 | const { value } = event.target; 28 | setFieldValue(name, value ? value.toUpperCase() : ''); 29 | }, 30 | [setFieldValue, name] 31 | ); 32 | return ; 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/docs/guide/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: faq 3 | title: FAQ 4 | --- 5 | 6 | ## Why can't I pass in `name`, `value`, `error` etc. into my component? 7 | 8 | To prevent 2 sources of truth for values. For example: 9 | 10 | ```jsx 11 | function App() { 12 | const [email, setEmail] = useState(''); 13 | const [error, setError] = useState(null); 14 | 15 | return ( 16 | 25 | ); 26 | } 27 | ``` 28 | 29 | From this code it is not obvious at all who is in control of the state. Therefore, we purposefully hand over all control (`name`, `value`, `onChange`, `onBlur`) to Formik. This ensures there is no confusion. You can do this to a custom field: 30 | 31 | ```jsx 32 | import TextField from '@mui/material/TextField'; 33 | import { Field } from 'formik'; 34 | import { fieldToTextField, TextFieldProps } from 'formik-mui'; 35 | 36 | function UpperCasingTextField(props: TextFieldProps) { 37 | const { 38 | form: { setFieldValue }, 39 | field: { name }, 40 | } = props; 41 | const onChange = React.useCallback( 42 | (event) => { 43 | const { value } = event.target; 44 | setFieldValue(name, value ? value.toUpperCase() : ''); 45 | }, 46 | [setFieldValue, name] 47 | ); 48 | return ; 49 | } 50 | ``` 51 | 52 | ## Why does a wrapper for a certain component not exist? 53 | 54 | Some components require many decisions to be made which would only make it work in some situations. 55 | A good example is the `slider`. Should the `onChange` event be fired while dragging or only when it's dropped? It depends on your use-case. 56 | 57 | ## Why do the fields become disabled during submission? 58 | 59 | This is a convenience meant for the standard form. If this was omitted the `isSubmitting` property would have to be manually passed into every single field like this: 60 | 61 | ```jsx {8} 62 | 63 | {({ isSubmitting }) => ( 64 |
65 | 71 | 72 | )} 73 |
74 | ``` 75 | 76 | A common scenario where this is problematic is when we use Formik for search. Here we never want the field to be disabled. This can be accomplished by setting disabled to `false`. 77 | 78 | ```jsx {4} 79 | 80 | {({ isSubmitting }) => ( 81 |
82 | 83 | 84 | )} 85 |
86 | ``` 87 | -------------------------------------------------------------------------------- /docs/docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getting-started 3 | title: Getting Started 4 | --- 5 | 6 | ## Installation 7 | 8 | ``` 9 | npm add formik formik-mui @mui/material @emotion/react @emotion/styled 10 | ``` 11 | 12 | ### MUI X Date and Time Pickers (Optional) 13 | 14 | ``` 15 | npm add formik-mui-x-date-pickers @mui/x-date-pickers @mui/system 16 | ``` 17 | 18 | Note: You also need to install a date-library of your choice. Visit [MUI documentation](https://mui.com/x/react-date-pickers/getting-started/) for more information. 19 | 20 | ## Quick Start 21 | 22 | ```jsx {3,39,46} 23 | import { Button, LinearProgress } from '@mui/material'; 24 | import { Formik, Form, Field } from 'formik'; 25 | import { TextField } from 'formik-mui'; 26 | import * as React from 'react'; 27 | 28 | interface Values { 29 | email: string; 30 | password: string; 31 | } 32 | 33 | function App() { 34 | return ( 35 | { 41 | const errors: Partial = {}; 42 | if (!values.email) { 43 | errors.email = 'Required'; 44 | } else if ( 45 | !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email) 46 | ) { 47 | errors.email = 'Invalid email address'; 48 | } 49 | return errors; 50 | }} 51 | onSubmit={(values, { setSubmitting }) => { 52 | setTimeout(() => { 53 | setSubmitting(false); 54 | alert(JSON.stringify(values, null, 2)); 55 | }, 500); 56 | }} 57 | > 58 | {({ submitForm, isSubmitting }) => ( 59 |
60 | 66 |
67 | 73 | {isSubmitting && } 74 |
75 | 83 | 84 | )} 85 |
86 | ); 87 | } 88 | ``` 89 | 90 | Note: that the `Field` wrapper is not used, for more details on why see the [FAQ](guide/faq.md). 91 | 92 | ## Configuring Components 93 | 94 | Several properties are purposefully excluded, please see the [FAQ](guide/faq.md) for details. 95 | 96 | ```jsx 97 | import AccountCircle from '@mui/icons-material/AccountCircle'; 98 | import InputAdornment from '@mui/material/InputAdornment'; 99 | import { TextField } from 'formik-mui'; 100 | 101 | 108 | 109 | 110 | ), 111 | }} 112 | />; 113 | ``` 114 | 115 | ## Quick Start (MUI X Date and Time Pickers) 116 | 117 | See [MUI X Date and Time Pickers _Getting started_](https://mui.com/x/react-date-pickers/getting-started/) for more information. 118 | 119 | ```jsx {1-2,6-10,15,32,39,43} 120 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; // Depending on the library you picked 121 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 122 | import Button from '@mui/material/Button'; 123 | import LinearProgress from '@mui/material/LinearProgress'; 124 | import { Formik, Form, Field } from 'formik'; 125 | import { 126 | DatePicker, 127 | DateTimePicker, 128 | TimePicker, 129 | } from 'formik-mui-x-date-pickers'; 130 | import * as React from 'react'; 131 | 132 | function App() { 133 | return ( 134 | 135 | { 142 | setTimeout(() => { 143 | setSubmitting(false); 144 | alert(JSON.stringify(values, null, 2)); 145 | }, 500); 146 | }} 147 | > 148 | {({ submitForm, isSubmitting }) => ( 149 |
150 | 156 |
157 | 162 | 163 |
164 | {isSubmitting && } 165 |
166 | 174 | 175 | )} 176 |
177 |
178 | ); 179 | } 180 | ``` 181 | 182 | ## Configuring Components 183 | 184 | Several properties are purposefully excluded, please see the [FAQ](guide/faq.md) for details. 185 | 186 | ```jsx 187 | import AccountCircle from '@mui/icons-material/AccountCircle'; 188 | import InputAdornment from '@mui/material/InputAdornment'; 189 | import { TextField } from 'formik-mui'; 190 | 191 | 198 | 199 | 200 | ), 201 | }} 202 | />; 203 | ``` 204 | -------------------------------------------------------------------------------- /docs/docs/guide/migrating.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: migrating 3 | title: Migrating 4 | --- 5 | 6 | ## Migrating from formik-mui 1.0.0 7 | 8 | ### Standard Components 9 | 10 | ### Changes 11 | 12 | - `type=checkbox` recommended for Switches and Checkboxes - See [here](https://jaredpalmer.com/formik/docs/migrating-v2#checkboxes-and-select-multiple) 13 | 14 | #### Before 15 | 16 | ```jsx 17 | import { Field } from 'formik'; 18 | import { Checkbox, Switch } from 'formik-mui'; 19 | 20 | ; 21 | ; 22 | ``` 23 | 24 | - Rename fieldToTextField to useFieldToTextField 25 | 26 | #### After 27 | 28 | ```jsx 29 | import { Field } from 'formik'; 30 | import { Checkbox, Switch } from 'formik-mui'; 31 | 32 | import { 33 | // Field Helpers 34 | useField, 35 | // Form Helpers 36 | useFormik, 37 | } from 'formik'; 38 | 39 | ; 40 | ; 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | import {themes as prismThemes} from 'prism-react-renderer'; 2 | import type {Config} from '@docusaurus/types'; 3 | import type * as Preset from '@docusaurus/preset-classic'; 4 | 5 | const config: Config = { 6 | title: 'Formik MUI', 7 | tagline: 'Easily combine Formik with MUI', 8 | url: 'https://stackworx.github.io/', 9 | baseUrl: '/formik-mui/', 10 | favicon: 'img/favicon.ico', 11 | organizationName: 'stackworx', 12 | projectName: 'formik-mui', 13 | 14 | onBrokenLinks: 'throw', 15 | onBrokenMarkdownLinks: 'warn', 16 | 17 | // Github recommendation 18 | trailingSlash: true, 19 | 20 | // Even if you don't use internationalization, you can use this field to set 21 | // useful metadata like html lang. For example, if your site is Chinese, you 22 | // may want to replace "en" with "zh-Hans". 23 | i18n: { 24 | defaultLocale: 'en', 25 | locales: ['en'], 26 | }, 27 | 28 | presets: [ 29 | [ 30 | 'classic', 31 | { 32 | docs: { 33 | sidebarPath: './sidebars.ts', 34 | editUrl: 'https://github.com/stackworx/formik-mui/edit/main/', 35 | }, 36 | blog: { 37 | showReadingTime: true, 38 | feedOptions: { 39 | type: ['rss', 'atom'], 40 | xslt: true, 41 | }, 42 | // Please change this to your repo. 43 | // Remove this to remove the "edit this page" links. 44 | editUrl: 45 | 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', 46 | // Useful options to enforce blogging best practices 47 | onInlineTags: 'warn', 48 | onInlineAuthors: 'warn', 49 | onUntruncatedBlogPosts: 'warn', 50 | }, 51 | theme: { 52 | customCss: './src/css/custom.css', 53 | }, 54 | } satisfies Preset.Options, 55 | ], 56 | ], 57 | 58 | themeConfig: { 59 | // Replace with your project's social card 60 | image: 'img/docusaurus-social-card.jpg', 61 | navbar: { 62 | title: 'Formik MUI', 63 | logo: { 64 | alt: 'My Site Logo', 65 | src: 'img/logo.svg', 66 | }, 67 | items: [ 68 | { to: 'docs/guide/getting-started', label: 'Guide', position: 'left' }, 69 | { to: 'docs/api/mui', label: 'API', position: 'left' }, 70 | // { to: 'blog', label: 'Blog', position: 'left' }, 71 | { 72 | href: 'https://github.com/stackworx/formik-mui', 73 | label: 'GitHub', 74 | position: 'right', 75 | }, 76 | ], 77 | }, 78 | footer: { 79 | style: 'dark', 80 | copyright: `Copyright © ${new Date().getFullYear()} Ciaran Liedeman, Built with Docusaurus.`, 81 | }, 82 | prism: { 83 | theme: prismThemes.github, 84 | darkTheme: prismThemes.dracula, 85 | }, 86 | } satisfies Preset.ThemeConfig, 87 | }; 88 | 89 | export default config; -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.5.2", 19 | "@docusaurus/preset-classic": "3.5.2", 20 | "@mdx-js/react": "^3.0.0", 21 | "classnames": "^2.5.1", 22 | "clsx": "^2.0.0", 23 | "prism-react-renderer": "^2.3.0", 24 | "react": "^18.0.0", 25 | "react-dom": "^18.0.0" 26 | }, 27 | "devDependencies": { 28 | "@docusaurus/module-type-aliases": "3.5.2", 29 | "@docusaurus/tsconfig": "3.5.2", 30 | "@docusaurus/types": "3.5.2", 31 | "typescript": "~5.5.2" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.5%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 3 chrome version", 41 | "last 3 firefox version", 42 | "last 5 safari version" 43 | ] 44 | }, 45 | "engines": { 46 | "node": ">=18.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; 2 | 3 | /* 4 | module.exports = { 5 | someSidebar: { 6 | Guide: [ 7 | 'guide/getting-started', 8 | 'guide/custom-component', 9 | 'guide/migrating', 10 | 'guide/faq', 11 | ], 12 | API: ['api/mui', 'api/mui-x-date-pickers'], 13 | }, 14 | }; 15 | */ 16 | 17 | const sidebars: SidebarsConfig = { 18 | // By default, Docusaurus generates a sidebar from the docs folder structure 19 | // tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 20 | someSidebar: { 21 | Guide: [ 22 | 'guide/getting-started', 23 | 'guide/custom-component', 24 | 'guide/migrating', 25 | 'guide/faq', 26 | ], 27 | API: ['api/mui', 'api/mui-x-date-pickers'], 28 | }, 29 | }; 30 | 31 | export default sidebars; -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #25c2a0; 10 | --ifm-color-primary-dark: rgb(33, 175, 144); 11 | --ifm-color-primary-darker: rgb(31, 165, 136); 12 | --ifm-color-primary-darkest: rgb(26, 136, 112); 13 | --ifm-color-primary-light: rgb(70, 203, 174); 14 | --ifm-color-primary-lighter: rgb(102, 212, 189); 15 | --ifm-color-primary-lightest: rgb(146, 224, 208); 16 | --ifm-code-font-size: 95%; 17 | } 18 | 19 | .docusaurus-highlight-code-line { 20 | background-color: rgb(72, 77, 91); 21 | display: block; 22 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 23 | padding: 0 var(--ifm-pre-padding); 24 | } 25 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@docusaurus/Link'; 2 | import useBaseUrl from '@docusaurus/useBaseUrl'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import Layout from '@theme/Layout'; 5 | import classnames from 'classnames'; 6 | import React from 'react'; 7 | import styles from './styles.module.css'; 8 | 9 | const features = [ 10 | { 11 | title: <>Easy to Use, 12 | // imageUrl: 'img/undraw_docusaurus_mountain.svg', 13 | description: <>Get going quickly with Formik and MUI, 14 | }, 15 | { 16 | title: MUI, 17 | // imageUrl: 'img/mui-logo.svg', 18 | description: ( 19 | <> 20 | React components for faster and easier web development. Build your own 21 | design system, or start with Material Design. 22 | 23 | ), 24 | }, 25 | { 26 | title: Formik, 27 | // imageUrl: 'img/formik-logo.png', 28 | description: <>Build forms in React, without the tears., 29 | }, 30 | ]; 31 | 32 | function Feature({ imageUrl, title, description }) { 33 | const imgUrl = useBaseUrl(imageUrl); 34 | return ( 35 |
36 | {imgUrl && ( 37 |
38 | {title} 39 |
40 | )} 41 |

{title}

42 |

{description}

43 |
44 | ); 45 | } 46 | 47 | function Home() { 48 | const context = useDocusaurusContext(); 49 | const { siteConfig = {} } = context; 50 | return ( 51 | 55 |
56 |
57 |

{siteConfig.title}

58 |

{siteConfig.tagline}

59 |
60 | 67 | Get Started 68 | 69 |
70 |
71 |
72 |
73 | {features && features.length && ( 74 |
75 |
76 |
77 | {features.map((props, idx) => ( 78 | 79 | ))} 80 |
81 |
82 |
83 | )} 84 |
85 |
86 | ); 87 | } 88 | 89 | export default Home; 90 | -------------------------------------------------------------------------------- /docs/src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 966px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | 25 | .features { 26 | display: flex; 27 | align-items: center; 28 | padding: 2rem 0; 29 | width: 100%; 30 | } 31 | 32 | .featureImage { 33 | height: 200px; 34 | width: 200px; 35 | } 36 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackworx/formik-mui/d65e4627b30cfaad65d88fe51acb3f2925ef9464/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackworx/formik-mui/d65e4627b30cfaad65d88fe51acb3f2925ef9464/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackworx/formik-mui/d65e4627b30cfaad65d88fe51acb3f2925ef9464/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/formik-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackworx/formik-mui/d65e4627b30cfaad65d88fe51acb3f2925ef9464/docs/static/img/formik-logo.png -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/mui-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "independent" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-mui-root", 3 | "version": "0.0.0", 4 | "repository": "git@github.com:stackworx/formik-mui.git", 5 | "author": "Ciaran Liedeman ", 6 | "license": "MIT", 7 | "typings": "dist/main.d.ts", 8 | "private": true, 9 | "workspaces": [ 10 | "packages/*" 11 | ], 12 | "prettier": { 13 | "singleQuote": true, 14 | "trailingComma": "es5" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "7.25.2", 18 | "@emotion/react": "11.13.0", 19 | "@emotion/styled": "11.13.0", 20 | "@eslint/compat": "^1.1.1", 21 | "@eslint/js": "^9.9.1", 22 | "@mui/icons-material": "5.16.7", 23 | "@mui/material": "5.16.7", 24 | "@mui/x-date-pickers": "^7.12.1", 25 | "@mui/x-date-pickers-pro": "7.12.1", 26 | "@storybook/addon-actions": "^8.2.9", 27 | "@storybook/addon-essentials": "^8.2.9", 28 | "@storybook/addon-interactions": "^8.2.9", 29 | "@storybook/addon-links": "^8.2.9", 30 | "@storybook/builder-vite": "^8.2.9", 31 | "@storybook/react": "^8.2.9", 32 | "@storybook/react-vite": "^8.2.9", 33 | "@testing-library/react": "^16.0.0", 34 | "@types/eslint__js": "^8.42.3", 35 | "@types/react": "^18.0.0", 36 | "@types/react-dom": "^18.0.0", 37 | "@vitejs/plugin-react": "4.3.1", 38 | "babel-loader": "9.1.3", 39 | "date-fns": "^2.30.0", 40 | "eslint": "^8.57.0", 41 | "eslint-config-prettier": "^9.1.0", 42 | "eslint-plugin-jest": "^28.8.0", 43 | "eslint-plugin-prettier": "^5.2.1", 44 | "eslint-plugin-react": "^7.35.0", 45 | "formik": "^2.2.9", 46 | "jsdom": "^25.0.0", 47 | "lerna": "^8.1.8", 48 | "prettier": "^3.3.3", 49 | "typescript": "^5.5.4", 50 | "typescript-eslint": "^8.3.0", 51 | "vite": "5.4.1", 52 | "vite-plugin-dts": "^4.0.3", 53 | "vitest": "^2.0.5", 54 | "yup": "^0.32.9" 55 | }, 56 | "scripts": { 57 | "build": "tsc && vite build", 58 | "typecheck": "tsc", 59 | "storybook": "storybook dev -p 6006", 60 | "build-storybook": "storybook build", 61 | "lint": "eslint \"./{src,packages/*/src}/**/*.{tsx,ts,js}\"", 62 | "lint:fix": "eslint --fix \"./{src,packages/*/src}/**/*.{tsx,ts,js}\"" 63 | }, 64 | "dependencies": { 65 | "@testing-library/jest-dom": "^6.5.0", 66 | "eslint-plugin-react-hooks": "^5.1.0-beta-26f2496093-20240514", 67 | "react": "^18.3.1", 68 | "react-dom": "^18.3.1" 69 | }, 70 | "optionalDependencies": { 71 | "@rollup/rollup-linux-x64-gnu": "^4.21.1" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/README.md: -------------------------------------------------------------------------------- 1 | # Formik MUI 2 | 3 | ![](https://github.com/stackworx/formik-mui/workflows/Build%20formik-mui/badge.svg) 4 | ![](https://github.com/stackworx/formik-mui/workflows/Build%20formik-mui-x-date-pickers/badge.svg)[![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE) 5 | 6 | Bindings for using [Formik](https://github.com/jaredpalmer/formik) with [MUI](https://mui.com/). 7 | 8 | - [Documentation](https://stackworx.github.io/formik-mui) 9 | - [Code Sandbox](https://codesandbox.io/s/915qlr56rp) 10 | 11 | This project requires Formik>= 2.0.0. For Formik one please use formik-mui@1.0.x 12 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-mui-x-date-pickers", 3 | "type": "module", 4 | "main": "./dist/main.js", 5 | "version": "2.0.0-alpha.0", 6 | "license": "MIT", 7 | "typings": "dist/main.d.ts", 8 | "sideEffects": false, 9 | "peerDependencies": { 10 | "@mui/material": ">5.6.0", 11 | "@mui/x-date-pickers": ">=7.0.0", 12 | "formik": ">=2.2.9", 13 | "react": ">=17.0.2", 14 | "tiny-warning": ">=1.0.3" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "formik", 19 | "material-ui", 20 | "mui", 21 | "mui-x", 22 | "form", 23 | "pickers" 24 | ], 25 | "prettier": { 26 | "singleQuote": true, 27 | "trailingComma": "es5" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/stackworx/formik-mui" 32 | }, 33 | "files": [ 34 | "dist" 35 | ], 36 | "scripts": { 37 | "typecheck": "tsc", 38 | "build": "tsc && vite build", 39 | "test": "TZ=UTC vitest", 40 | "prepublishOnly": "npm run build" 41 | }, 42 | "gitHead": "014c524940fd5c07f1492dab6147d2e7261229e7" 43 | } 44 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/DatePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DatePicker as MuiDatePicker, 3 | DatePickerProps as MuiDatePickerProps, 4 | } from '@mui/x-date-pickers/DatePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface DatePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToDatePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: DatePickerProps): MuiDatePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function DatePicker(props: DatePickerProps) { 65 | return ; 66 | } 67 | 68 | DatePicker.displayName = 'FormikMUIDatePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/DateTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DateTimePicker as MuiDateTimePicker, 3 | DateTimePickerProps as MuiDateTimePickerProps, 4 | } from '@mui/x-date-pickers/DateTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface DateTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToDateTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: DateTimePickerProps): MuiDateTimePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function DateTimePicker(props: DateTimePickerProps) { 65 | return ; 66 | } 67 | 68 | DateTimePicker.displayName = 'FormikMUIDateTimePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/DesktopDatePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DesktopDatePicker as MuiDesktopDatePicker, 3 | DesktopDatePickerProps as MuiDesktopDatePickerProps, 4 | } from '@mui/x-date-pickers/DesktopDatePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface DesktopDatePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToDesktopDatePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: DesktopDatePickerProps): MuiDesktopDatePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function DesktopDatePicker(props: DesktopDatePickerProps) { 65 | return ; 66 | } 67 | 68 | DesktopDatePicker.displayName = 'FormikMUIDesktopDatePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/DesktopDateTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DesktopDateTimePicker as MuiDesktopDateTimePicker, 3 | DesktopDateTimePickerProps as MuiDesktopDateTimePickerProps, 4 | } from '@mui/x-date-pickers/DesktopDateTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface DesktopDateTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToDesktopDateTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: DesktopDateTimePickerProps): MuiDesktopDateTimePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function DesktopDateTimePicker(props: DesktopDateTimePickerProps) { 65 | return ; 66 | } 67 | 68 | DesktopDateTimePicker.displayName = 'FormikMUIDesktopDateTimePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/DesktopTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DesktopTimePicker as MuiDesktopTimePicker, 3 | DesktopTimePickerProps as MuiDesktopTimePickerProps, 4 | } from '@mui/x-date-pickers/DesktopTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface DesktopTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToDesktopTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: DesktopTimePickerProps): MuiDesktopTimePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function DesktopTimePicker(props: DesktopTimePickerProps) { 65 | return ; 66 | } 67 | 68 | DesktopTimePicker.displayName = 'FormikMUIDesktopTimePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/MobileDatePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | MobileDatePicker as MuiMobileDatePicker, 3 | MobileDatePickerProps as MuiMobileDatePickerProps, 4 | } from '@mui/x-date-pickers/MobileDatePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface MobileDatePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToMobileDatePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: MobileDatePickerProps): MuiMobileDatePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function MobileDatePicker(props: MobileDatePickerProps) { 65 | return ; 66 | } 67 | 68 | MobileDatePicker.displayName = 'FormikMUIMobileDatePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/MobileDateTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | MobileDateTimePicker as MuiMobileDateTimePicker, 3 | MobileDateTimePickerProps as MuiMobileDateTimePickerProps, 4 | } from '@mui/x-date-pickers/MobileDateTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface MobileDateTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToMobileDateTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: MobileDateTimePickerProps): MuiMobileDateTimePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function MobileDateTimePicker(props: MobileDateTimePickerProps) { 65 | return ; 66 | } 67 | 68 | MobileDateTimePicker.displayName = 'FormikMUIMobileDateTimePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/MobileTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | MobileTimePicker as MuiMobileTimePicker, 3 | MobileTimePickerProps as MuiMobileTimePickerProps, 4 | } from '@mui/x-date-pickers/MobileTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface MobileTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToMobileTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: MobileTimePickerProps): MuiMobileTimePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function MobileTimePicker(props: MobileTimePickerProps) { 65 | return ; 66 | } 67 | 68 | MobileTimePicker.displayName = 'FormikMUIMobileTimePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/StaticDatePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StaticDatePicker as MuiStaticDatePicker, 3 | StaticDatePickerProps as MuiStaticDatePickerProps, 4 | } from '@mui/x-date-pickers/StaticDatePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface StaticDatePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToStaticDatePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { isSubmitting, errors, setFieldValue, setFieldError, setFieldTouched }, 19 | disabled, 20 | onChange, 21 | onError, 22 | ...props 23 | }: StaticDatePickerProps): MuiStaticDatePickerProps { 24 | const fieldError = getIn(errors, field.name); 25 | const onChangeDefault = (date: Date | null) => { 26 | // Do not switch this order, otherwise you might cause a race condition 27 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 28 | setFieldTouched(field.name, true, false); 29 | setFieldValue(field.name, date, true); 30 | }; 31 | 32 | return { 33 | disabled: disabled ?? isSubmitting, 34 | onChange: onChange ?? onChangeDefault, 35 | onError: 36 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 37 | ...field, 38 | ...props, 39 | }; 40 | } 41 | 42 | export function StaticDatePicker(props: StaticDatePickerProps) { 43 | return ; 44 | } 45 | 46 | StaticDatePicker.displayName = 'FormikMUIStaticDatePicker'; 47 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/StaticDateTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StaticDateTimePicker as MuiStaticDateTimePicker, 3 | StaticDateTimePickerProps as MuiStaticDateTimePickerProps, 4 | } from '@mui/x-date-pickers/StaticDateTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface StaticDateTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToStaticDateTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { isSubmitting, errors, setFieldValue, setFieldError, setFieldTouched }, 19 | disabled, 20 | onChange, 21 | onError, 22 | ...props 23 | }: StaticDateTimePickerProps): MuiStaticDateTimePickerProps { 24 | const fieldError = getIn(errors, field.name); 25 | const onChangeDefault = (date: Date | null) => { 26 | // Do not switch this order, otherwise you might cause a race condition 27 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 28 | setFieldTouched(field.name, true, false); 29 | setFieldValue(field.name, date, true); 30 | }; 31 | 32 | return { 33 | disabled: disabled ?? isSubmitting, 34 | onChange: onChange ?? onChangeDefault, 35 | onError: 36 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 37 | ...field, 38 | ...props, 39 | }; 40 | } 41 | 42 | export function StaticDateTimePicker(props: StaticDateTimePickerProps) { 43 | return ; 44 | } 45 | 46 | StaticDateTimePicker.displayName = 'FormikMUIStaticDateTimePicker'; 47 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/StaticTimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StaticTimePicker as MuiStaticTimePicker, 3 | StaticTimePickerProps as MuiStaticTimePickerProps, 4 | } from '@mui/x-date-pickers/StaticTimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface StaticTimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToStaticTimePicker({ 17 | field: { onChange: _onChange, ...field }, 18 | form: { isSubmitting, errors, setFieldValue, setFieldError, setFieldTouched }, 19 | disabled, 20 | onChange, 21 | onError, 22 | ...props 23 | }: StaticTimePickerProps): MuiStaticTimePickerProps { 24 | const fieldError = getIn(errors, field.name); 25 | const onChangeDefault = (date: Date | null) => { 26 | // Do not switch this order, otherwise you might cause a race condition 27 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 28 | setFieldTouched(field.name, true, false); 29 | setFieldValue(field.name, date, true); 30 | }; 31 | 32 | return { 33 | disabled: disabled ?? isSubmitting, 34 | onChange: onChange ?? onChangeDefault, 35 | onError: 36 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 37 | ...field, 38 | ...props, 39 | }; 40 | } 41 | 42 | export function StaticTimePicker(props: StaticTimePickerProps) { 43 | return ; 44 | } 45 | 46 | StaticTimePicker.displayName = 'FormikMUIStaticTimePicker'; 47 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/TimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | TimePicker as MuiTimePicker, 3 | TimePickerProps as MuiTimePickerProps, 4 | } from '@mui/x-date-pickers/TimePicker'; 5 | import type { TextFieldProps } from '@mui/material/TextField'; 6 | import { FieldProps, getIn } from 'formik'; 7 | import * as React from 'react'; 8 | import { createErrorHandler } from './errorHandler'; 9 | 10 | export interface TimePickerProps 11 | extends FieldProps, 12 | Omit, 'name' | 'value' | 'error'> { 13 | textField?: TextFieldProps; 14 | } 15 | 16 | export function fieldToTimePicker({ 17 | field: { onChange: _onChange, onBlur: fieldOnBlur, ...field }, 18 | form: { 19 | isSubmitting, 20 | touched, 21 | errors, 22 | setFieldValue, 23 | setFieldError, 24 | setFieldTouched, 25 | }, 26 | textField: { helperText, onBlur, ...textField } = {}, 27 | disabled, 28 | label, 29 | onChange, 30 | onError, 31 | ...props 32 | }: TimePickerProps): MuiTimePickerProps { 33 | const fieldError = getIn(errors, field.name); 34 | const showError = getIn(touched, field.name) && !!fieldError; 35 | const onBlurDefault = () => { 36 | setFieldTouched(field.name, true, true); 37 | }; 38 | const onChangeDefault = (date: Date | null) => { 39 | // Do not switch this order, otherwise you might cause a race condition 40 | // See https://github.com/formium/formik/issues/2083#issuecomment-884831583 41 | setFieldTouched(field.name, true, false); 42 | setFieldValue(field.name, date, true); 43 | }; 44 | 45 | return { 46 | disabled: disabled ?? isSubmitting, 47 | onChange: onChange ?? onChangeDefault, 48 | onError: 49 | onError ?? createErrorHandler(fieldError, field.name, setFieldError), 50 | slotProps: { 51 | textField: { 52 | error: showError, 53 | helperText: showError ? fieldError : helperText, 54 | label, 55 | onBlur: onBlur ?? onBlurDefault, 56 | ...textField, 57 | }, 58 | }, 59 | ...field, 60 | ...props, 61 | }; 62 | } 63 | 64 | export function TimePicker(props: TimePickerProps) { 65 | return ; 66 | } 67 | 68 | TimePicker.displayName = 'FormikMUITimePicker'; 69 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/DatePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { DatePicker } from '../DatePicker'; 8 | 9 | test('DatePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/DateTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { DateTimePicker } from '../DateTimePicker'; 8 | 9 | test('DateTimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/DesktopDatePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { DesktopDatePicker } from '../DesktopDatePicker'; 8 | 9 | test('DesktopDatePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/DesktopDateTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { DesktopDateTimePicker } from '../DesktopDateTimePicker'; 8 | 9 | test('DesktopDateTimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/DesktopTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { DesktopTimePicker } from '../DesktopTimePicker'; 8 | 9 | test('DesktopTimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/MobileDatePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { MobileDatePicker } from '../MobileDatePicker'; 8 | 9 | test('MobileDatePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/MobileDateTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { MobileDateTimePicker } from '../MobileDateTimePicker'; 8 | 9 | test('MobileDateTimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/MobileTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { MobileTimePicker } from '../MobileTimePicker'; 8 | 9 | test('MobileTimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/StaticDatePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { render } from '@testing-library/react'; 5 | import { Field, Form, Formik } from 'formik'; 6 | import * as React from 'react'; 7 | import { StaticDatePicker } from '../StaticDatePicker'; 8 | 9 | test('StaticDatePicker Renders Correctly', () => { 10 | const { getByText } = render( 11 | 12 | {}} 15 | > 16 |
17 | 22 | 23 |
24 |
25 | ); 26 | 27 | expect(getByText('Wed, Jan 1')).toBeInTheDocument(); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/StaticDateTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { render } from '@testing-library/react'; 5 | import { Field, Form, Formik } from 'formik'; 6 | import * as React from 'react'; 7 | import { StaticDateTimePicker } from '../StaticDateTimePicker'; 8 | 9 | test('StaticDateTimePicker Renders Correctly', () => { 10 | const { getByText } = render( 11 | 12 | {}} 15 | > 16 |
17 | 22 | 23 |
24 |
25 | ); 26 | 27 | expect(getByText('2020')).toBeInTheDocument(); 28 | expect(getByText('Jan 1')).toBeInTheDocument(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/StaticTimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest'; 2 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 3 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import * as React from 'react'; 6 | import { render } from '@testing-library/react'; 7 | import { StaticTimePicker } from '../StaticTimePicker'; 8 | 9 | test('StaticTimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 22 | 23 |
24 |
25 | ); 26 | 27 | expect(asFragment()).toMatchSnapshot(); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/TimePicker.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 4 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 5 | import { Field, Form, Formik } from 'formik'; 6 | import { render } from '@testing-library/react'; 7 | import { TimePicker } from '../TimePicker'; 8 | 9 | test('TimePicker Renders Correctly', () => { 10 | const { asFragment } = render( 11 | 12 | {}} 15 | > 16 |
17 | 23 | 24 |
25 |
26 | ); 27 | 28 | expect(asFragment()).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/DatePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`DatePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 34 |
37 | 58 |
59 | 71 |
72 |

76 | Helper text 77 |

78 |
79 |
80 |
81 | `; 82 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/DateTimePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`DateTimePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 34 |
37 | 58 |
59 | 71 |
72 |

76 | Helper text 77 |

78 |
79 |
80 |
81 | `; 82 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/DesktopDatePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`DesktopDatePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 34 |
37 | 58 |
59 | 71 |
72 |

76 | Helper text 77 |

78 |
79 |
80 |
81 | `; 82 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/DesktopDateTimePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`DesktopDateTimePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 34 |
37 | 58 |
59 | 71 |
72 |

76 | Helper text 77 |

78 |
79 |
80 |
81 | `; 82 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/DesktopTimePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`DesktopTimePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 34 |
37 | 61 |
62 | 74 |
75 |

79 | Helper text 80 |

81 |
82 |
83 |
84 | `; 85 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/MobileDatePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`MobileDatePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 36 | 48 |
49 |

53 | Helper text 54 |

55 |
56 |
57 |
58 | `; 59 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/MobileDateTimePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`MobileDateTimePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 36 | 48 |
49 |

53 | Helper text 54 |

55 |
56 |
57 |
58 | `; 59 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/MobileTimePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`MobileTimePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 36 | 48 |
49 |

53 | Helper text 54 |

55 |
56 |
57 |
58 | `; 59 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/__snapshots__/TimePicker.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`TimePicker Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 34 |
37 | 61 |
62 | 74 |
75 |

79 | Helper text 80 |

81 |
82 |
83 |
84 | `; 85 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/__tests__/timezone.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | describe('Timezones', () => { 4 | it('should always be UTC', () => { 5 | expect(new Date().getTimezoneOffset()).toBe(0); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/errorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export function createErrorHandler( 4 | fieldError: unknown, 5 | fieldName: string, 6 | setFieldError: (field: string, message?: string) => void 7 | ) { 8 | return (error?: ReactNode) => { 9 | if (error !== fieldError && error !== '') { 10 | setFieldError(fieldName, error ? String(error) : undefined); 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/src/main.ts: -------------------------------------------------------------------------------- 1 | export * from './DatePicker'; 2 | export * from './DateTimePicker'; 3 | export * from './DesktopDatePicker'; 4 | export * from './DesktopDateTimePicker'; 5 | export * from './DesktopTimePicker'; 6 | export * from './MobileDatePicker'; 7 | export * from './MobileDateTimePicker'; 8 | export * from './MobileTimePicker'; 9 | export * from './StaticDatePicker'; 10 | export * from './StaticDateTimePicker'; 11 | export * from './StaticTimePicker'; 12 | export * from './TimePicker'; 13 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite"; 3 | import * as path from "path"; 4 | import react from "@vitejs/plugin-react"; 5 | import dts from "vite-plugin-dts"; 6 | 7 | const external = [ 8 | /node_modules/, 9 | "react", 10 | "react/jsx-runtime", 11 | "formik", 12 | "tiny-warning", 13 | "@mui/utils", 14 | /@mui\/x-date-pickers/, 15 | ]; 16 | 17 | // https://vitejs.dev/config/ 18 | export default defineConfig({ 19 | plugins: [react(), dts({ 20 | rollupTypes: true, 21 | // https://github.com/qmhc/vite-plugin-dts/issues/344#issuecomment-2231355823 22 | tsconfigPath: "./tsconfig.app.json", 23 | })], 24 | build: { 25 | lib: { 26 | entry: path.resolve(__dirname, "src/main.ts"), 27 | }, 28 | minify: false, 29 | sourcemap: true, 30 | rollupOptions: { 31 | external, 32 | output: [ 33 | { 34 | format: "es", 35 | dir: "./dist", 36 | preserveModules: true, 37 | entryFileNames: ({ name: fileName }) => { 38 | return `${fileName}.js`; 39 | }, 40 | } 41 | ], 42 | }, 43 | }, 44 | test: { 45 | environment: 'jsdom', 46 | setupFiles: ['./vitest-setup.js'], 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /packages/formik-mui-x-date-pickers/vitest-setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest'; 2 | import { cleanup } from '@testing-library/react'; 3 | import { afterEach } from 'vitest'; 4 | 5 | afterEach(async () => { 6 | cleanup(); 7 | }) -------------------------------------------------------------------------------- /packages/formik-mui/README.md: -------------------------------------------------------------------------------- 1 | # Formik MUI 2 | 3 | ![](https://github.com/stackworx/formik-mui/workflows/Build%20formik-mui/badge.svg) 4 | ![](https://github.com/stackworx/formik-mui/workflows/Build%20formik-mui-x-date-pickers/badge.svg) 5 | [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE) 6 | 7 | Bindings for using [Formik](https://github.com/jaredpalmer/formik) with [MUI](https://mui.com/). 8 | 9 | - [Documentation](https://stackworx.github.io/formik-mui) 10 | - [Code Sandbox](https://codesandbox.io/s/915qlr56rp) 11 | 12 | ## Versions 13 | 14 | ```bash 15 | npm add formik-mui formik-mui-x-date-pickers 16 | ``` 17 | 18 | This project requires Formik>= 2.0.0 -------------------------------------------------------------------------------- /packages/formik-mui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik-mui", 3 | "type": "module", 4 | "main": "./dist/main.js", 5 | "version": "5.0.0-alpha.1", 6 | "license": "MIT", 7 | "typings": "dist/main.d.ts", 8 | "sideEffects": false, 9 | "peerDependencies": { 10 | "@mui/material": ">5.6.0", 11 | "formik": ">=2.2.9", 12 | "react": ">=17.0.2", 13 | "tiny-warning": ">=1.0.3" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "formik", 18 | "material-ui", 19 | "mui", 20 | "form" 21 | ], 22 | "prettier": { 23 | "singleQuote": true, 24 | "trailingComma": "es5" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/stackworx/formik-mui" 29 | }, 30 | "files": [ 31 | "dist" 32 | ], 33 | "scripts": { 34 | "typecheck": "tsc", 35 | "build": "tsc && vite build", 36 | "test": "vitest", 37 | "test:updateSnapshot": "jest --updateSnapshot", 38 | "prepublishOnly": "npm run build" 39 | }, 40 | "gitHead": "014c524940fd5c07f1492dab6147d2e7261229e7" 41 | } 42 | -------------------------------------------------------------------------------- /packages/formik-mui/src/Autocomplete.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiAutocomplete, { 3 | AutocompleteProps as MuiAutocompleteProps, 4 | } from '@mui/material/Autocomplete'; 5 | import { FieldProps } from 'formik'; 6 | import invariant from 'tiny-warning'; 7 | 8 | export type { AutocompleteRenderInputParams } from '@mui/material/Autocomplete'; 9 | 10 | export interface AutocompleteProps< 11 | T, 12 | Multiple extends boolean | undefined, 13 | DisableClearable extends boolean | undefined, 14 | FreeSolo extends boolean | undefined, 15 | > extends FieldProps, 16 | Omit< 17 | MuiAutocompleteProps, 18 | 'name' | 'value' | 'defaultValue' 19 | > { 20 | type?: string; 21 | } 22 | 23 | export function fieldToAutocomplete< 24 | T, 25 | Multiple extends boolean | undefined, 26 | DisableClearable extends boolean | undefined, 27 | FreeSolo extends boolean | undefined, 28 | >({ 29 | disabled, 30 | field, 31 | form: { isSubmitting, setFieldValue }, 32 | type, 33 | onChange, 34 | onBlur, 35 | freeSolo, 36 | ...props 37 | }: AutocompleteProps< 38 | T, 39 | Multiple, 40 | DisableClearable, 41 | FreeSolo 42 | >): MuiAutocompleteProps { 43 | if (process.env.NODE_ENV !== 'production') { 44 | if (props.multiple) { 45 | invariant( 46 | Array.isArray(field.value), 47 | `value for ${field.name} is not an array, this can caused unexpected behaviour` 48 | ); 49 | } 50 | } 51 | 52 | const { 53 | onChange: _onChange, 54 | onBlur: _onBlur, 55 | multiple: _multiple, 56 | ...fieldSubselection 57 | } = field; 58 | 59 | return { 60 | freeSolo, 61 | onBlur: 62 | onBlur ?? 63 | function (event) { 64 | field.onBlur(event ?? field.name); 65 | }, 66 | onChange: 67 | onChange ?? 68 | function (_event, value) { 69 | setFieldValue(field.name, value); 70 | }, 71 | disabled: disabled ?? isSubmitting, 72 | loading: isSubmitting, 73 | ...fieldSubselection, 74 | ...props, 75 | }; 76 | } 77 | 78 | export function Autocomplete< 79 | T, 80 | Multiple extends boolean | undefined, 81 | DisableClearable extends boolean | undefined, 82 | FreeSolo extends boolean | undefined, 83 | >(props: AutocompleteProps) { 84 | return ; 85 | } 86 | 87 | Autocomplete.displayName = 'FormikMaterialUIAutocomplete'; 88 | -------------------------------------------------------------------------------- /packages/formik-mui/src/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiCheckbox, { 3 | CheckboxProps as MuiCheckboxProps, 4 | } from '@mui/material/Checkbox'; 5 | import { FieldProps } from 'formik'; 6 | import invariant from 'tiny-warning'; 7 | 8 | export interface CheckboxProps 9 | extends FieldProps, 10 | Omit< 11 | MuiCheckboxProps, 12 | | 'name' 13 | | 'value' 14 | | 'error' 15 | | 'form' 16 | | 'checked' 17 | | 'defaultChecked' 18 | // Excluded for conflict with Field type 19 | | 'type' 20 | > { 21 | type?: string; 22 | } 23 | 24 | export function fieldToCheckbox({ 25 | disabled, 26 | field: { onBlur: fieldOnBlur, ...field }, 27 | form: { isSubmitting }, 28 | type, 29 | onBlur, 30 | ...props 31 | }: CheckboxProps): MuiCheckboxProps { 32 | const indeterminate = !Array.isArray(field.value) && field.value == null; 33 | 34 | if (process.env.NODE_ENV !== 'production') { 35 | invariant( 36 | type === 'checkbox', 37 | `property type=checkbox is missing from field ${field.name}, this can caused unexpected behaviour` 38 | ); 39 | } 40 | 41 | return { 42 | disabled: disabled ?? isSubmitting, 43 | indeterminate, 44 | onBlur: 45 | onBlur ?? 46 | function (e) { 47 | fieldOnBlur(e ?? field.name); 48 | }, 49 | ...field, 50 | ...props, 51 | }; 52 | } 53 | 54 | export function Checkbox(props: CheckboxProps) { 55 | return ; 56 | } 57 | 58 | Checkbox.displayName = 'FormikMaterialUICheckbox'; 59 | -------------------------------------------------------------------------------- /packages/formik-mui/src/CheckboxWithLabel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiCheckbox from '@mui/material/Checkbox'; 3 | import FormControlLabel, { 4 | FormControlLabelProps as MuiFormControlLabelProps, 5 | } from '@mui/material/FormControlLabel'; 6 | import { FieldProps } from 'formik'; 7 | 8 | import { CheckboxProps, fieldToCheckbox } from './Checkbox'; 9 | 10 | /** 11 | * Exclude props that are passed directly to the control 12 | * https://github.com/mui-org/material-ui/blob/v3.1.1/packages/material-ui/src/FormControlLabel/FormControlLabel.js#L71 13 | */ 14 | export interface CheckboxWithLabelProps extends FieldProps, CheckboxProps { 15 | Label: Omit< 16 | MuiFormControlLabelProps, 17 | 'checked' | 'name' | 'value' | 'control' 18 | >; 19 | } 20 | 21 | export function CheckboxWithLabel({ Label, ...props }: CheckboxWithLabelProps) { 22 | return ( 23 | } 25 | {...Label} 26 | /> 27 | ); 28 | } 29 | 30 | CheckboxWithLabel.displayName = 'FormikMaterialUICheckboxWithLabel'; 31 | -------------------------------------------------------------------------------- /packages/formik-mui/src/InputBase.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiInputBase, { 3 | InputBaseProps as MuiInputBaseProps, 4 | } from '@mui/material/InputBase'; 5 | import { FieldProps } from 'formik'; 6 | 7 | export interface InputBaseProps 8 | extends FieldProps, 9 | Omit {} 10 | 11 | export function fieldToInputBase({ 12 | disabled, 13 | field: { onBlur: fieldOnBlur, ...field }, 14 | form: { isSubmitting }, 15 | onBlur, 16 | ...props 17 | }: InputBaseProps): MuiInputBaseProps { 18 | return { 19 | disabled: disabled ?? isSubmitting, 20 | onBlur: 21 | onBlur ?? 22 | function (e) { 23 | fieldOnBlur(e ?? field.name); 24 | }, 25 | ...field, 26 | ...props, 27 | }; 28 | } 29 | 30 | export function InputBase(props: InputBaseProps) { 31 | return ; 32 | } 33 | 34 | InputBase.displayName = 'FormikMaterialUIInputBase'; 35 | -------------------------------------------------------------------------------- /packages/formik-mui/src/RadioGroup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiRadioGroup, { 3 | RadioGroupProps as MuiRadioGroupProps, 4 | } from '@mui/material/RadioGroup'; 5 | import { FieldProps } from 'formik'; 6 | 7 | export interface RadioGroupProps 8 | extends FieldProps, 9 | Omit {} 10 | 11 | export function fieldToRadioGroup({ 12 | field: { onBlur: fieldOnBlur, ...field }, 13 | form, 14 | onBlur, 15 | ...props 16 | }: RadioGroupProps): MuiRadioGroupProps { 17 | return { 18 | onBlur: 19 | onBlur ?? 20 | function (e) { 21 | fieldOnBlur(e ?? field.name); 22 | }, 23 | ...field, 24 | ...props, 25 | }; 26 | } 27 | 28 | export function RadioGroup(props: RadioGroupProps) { 29 | return ; 30 | } 31 | 32 | RadioGroup.displayName = 'FormikMaterialUIRadioGroup'; 33 | -------------------------------------------------------------------------------- /packages/formik-mui/src/Select.tsx: -------------------------------------------------------------------------------- 1 | import FormControl, { FormControlProps } from '@mui/material/FormControl'; 2 | import FormHelperText, { 3 | FormHelperTextProps, 4 | } from '@mui/material/FormHelperText'; 5 | import InputLabel, { InputLabelProps } from '@mui/material/InputLabel'; 6 | import MuiSelect, { SelectProps as MuiSelectProps } from '@mui/material/Select'; 7 | import { FieldProps, getIn } from 'formik'; 8 | import * as React from 'react'; 9 | 10 | export interface SelectProps 11 | extends FieldProps, 12 | Omit { 13 | formControl?: FormControlProps; 14 | formHelperText?: FormHelperTextProps; 15 | inputLabel?: InputLabelProps; 16 | } 17 | 18 | export function fieldToSelect({ 19 | disabled, 20 | field: { onBlur: _onBlur, onChange: fieldOnChange, ...field }, 21 | form: { isSubmitting, touched, errors, setFieldTouched, setFieldValue }, 22 | onClose, 23 | ...props 24 | }: Omit< 25 | SelectProps, 26 | 'formControl' | 'formHelperText' | 'inputLabel' 27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 | >): MuiSelectProps & { formError: any } { 29 | const fieldError = getIn(errors, field.name); 30 | const showError = getIn(touched, field.name) && !!fieldError; 31 | 32 | return { 33 | disabled: disabled ?? isSubmitting, 34 | error: showError, 35 | formError: showError ? fieldError : undefined, 36 | onBlur: () => { 37 | // no-op 38 | }, 39 | onChange: 40 | fieldOnChange ?? 41 | (() => { 42 | // no-op 43 | }), 44 | // we must use `onClose` instead of `onChange` to be able to trigger validation when users click outside of the select list. 45 | onClose: 46 | onClose ?? 47 | (async (e: React.SyntheticEvent) => { 48 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 49 | const dataset = (e.target as any).dataset as DOMStringMap; 50 | if (dataset && dataset.value) { 51 | // out-of-sync issue since November 2019: https://github.com/formium/formik/issues/2059#issuecomment-890613538 52 | // without the await, formik validates with the former value 53 | await setFieldValue(field.name, dataset.value, false); 54 | } 55 | setFieldTouched(field.name, true, true); 56 | }), 57 | ...field, 58 | ...props, 59 | }; 60 | } 61 | 62 | export function Select({ 63 | formControl, 64 | inputLabel, 65 | formHelperText, 66 | ...selectProps 67 | }: SelectProps) { 68 | const { error, formError, disabled, ...selectFieldProps } = 69 | fieldToSelect(selectProps); 70 | const { children: formHelperTextChildren, ...formHelperTextProps } = 71 | formHelperText || {}; 72 | const shouldDisplayFormHelperText = error || formHelperTextChildren; 73 | 74 | return ( 75 | 76 | 77 | {selectFieldProps.label} 78 | 79 | 80 | {shouldDisplayFormHelperText && ( 81 | 82 | {error ? formError : formHelperTextChildren} 83 | 84 | )} 85 | 86 | ); 87 | } 88 | 89 | Select.displayName = 'FormikMaterialUISelect'; 90 | -------------------------------------------------------------------------------- /packages/formik-mui/src/SimpleFileUpload.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FieldProps, getIn } from 'formik'; 3 | import FormControl, { FormControlProps } from '@mui/material/FormControl'; 4 | import InputLabel, { InputLabelProps } from '@mui/material/InputLabel'; 5 | import Input, { InputProps } from '@mui/material/Input'; 6 | import FormHelperText from '@mui/material/FormHelperText'; 7 | 8 | export interface SimpleFileUploadProps extends FieldProps { 9 | label: string; 10 | accept: string; 11 | disabled?: boolean; 12 | InputProps?: Omit; 13 | InputLabelProps?: InputLabelProps; 14 | FormControlProps?: FormControlProps; 15 | } 16 | 17 | export const SimpleFileUpload = ({ 18 | field, 19 | form: { isSubmitting, touched, errors, setFieldValue }, 20 | label, 21 | accept, 22 | disabled = false, 23 | InputProps: inputProps, 24 | InputLabelProps: inputLabelProps, 25 | FormControlProps: formControlProps, 26 | }: SimpleFileUploadProps) => { 27 | const error = getIn(touched, field.name) && getIn(errors, field.name); 28 | 29 | return ( 30 | 31 | {label && ( 32 | 33 | {label} 34 | 35 | )} 36 | ) => { 45 | if (inputProps?.onChange) { 46 | inputProps.onChange(event); 47 | } else { 48 | const file = event.currentTarget.files[0]; 49 | setFieldValue(field.name, file); 50 | } 51 | }, 52 | }} 53 | {...inputProps} 54 | /> 55 | {error && {error}} 56 | 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /packages/formik-mui/src/Switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiSwitch, { SwitchProps as MuiSwitchProps } from '@mui/material/Switch'; 3 | import { FieldProps } from 'formik'; 4 | import invariant from 'tiny-warning'; 5 | 6 | export interface SwitchProps 7 | extends FieldProps, 8 | Omit< 9 | MuiSwitchProps, 10 | | 'checked' 11 | | 'name' 12 | | 'value' 13 | | 'defaultChecked' 14 | | 'form' 15 | // Excluded for conflict with Field type 16 | | 'type' 17 | > { 18 | type?: string; 19 | } 20 | 21 | export function fieldToSwitch({ 22 | disabled, 23 | field: { onBlur: fieldOnBlur, ...field }, 24 | form: { isSubmitting }, 25 | type, 26 | onBlur, 27 | ...props 28 | }: SwitchProps): MuiSwitchProps { 29 | if (process.env.NODE_ENV !== 'production') { 30 | invariant( 31 | type === 'checkbox', 32 | `property type=checkbox is missing from field ${field.name}, this can caused unexpected behaviour` 33 | ); 34 | } 35 | 36 | return { 37 | disabled: disabled ?? isSubmitting, 38 | onBlur: 39 | onBlur ?? 40 | function (e) { 41 | fieldOnBlur(e ?? field.name); 42 | }, 43 | ...field, 44 | ...props, 45 | }; 46 | } 47 | 48 | export function Switch(props: SwitchProps) { 49 | return ; 50 | } 51 | 52 | Switch.displayName = 'FormikMaterialUISwitch'; 53 | -------------------------------------------------------------------------------- /packages/formik-mui/src/TextField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiTextField, { 3 | TextFieldProps as MuiTextFieldProps, 4 | } from '@mui/material/TextField'; 5 | import { FieldProps, getIn } from 'formik'; 6 | 7 | export interface TextFieldProps 8 | extends FieldProps, 9 | Omit {} 10 | 11 | export function fieldToTextField({ 12 | disabled, 13 | field: { onBlur: fieldOnBlur, ...field }, 14 | form: { isSubmitting, touched, errors }, 15 | onBlur, 16 | helperText, 17 | ...props 18 | }: TextFieldProps): MuiTextFieldProps { 19 | const fieldError = getIn(errors, field.name); 20 | const showError = getIn(touched, field.name) && !!fieldError; 21 | 22 | return { 23 | error: showError, 24 | helperText: showError ? fieldError : helperText, 25 | disabled: disabled ?? isSubmitting, 26 | onBlur: 27 | onBlur ?? 28 | function (e) { 29 | fieldOnBlur(e ?? field.name); 30 | }, 31 | ...field, 32 | ...props, 33 | }; 34 | } 35 | 36 | export function TextField({ children, ...props }: TextFieldProps) { 37 | return {children}; 38 | } 39 | 40 | TextField.displayName = 'FormikMaterialUITextField'; 41 | -------------------------------------------------------------------------------- /packages/formik-mui/src/ToggleButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiToggleButtonGroup, { 3 | ToggleButtonGroupProps as MuiToggleButtonGroupProps, 4 | } from '@mui/material/ToggleButtonGroup'; 5 | import { FieldProps } from 'formik'; 6 | import invariant from 'tiny-warning'; 7 | 8 | export interface ToggleButtonGroupProps 9 | extends FieldProps, 10 | Omit { 11 | type?: string; 12 | } 13 | 14 | export function fieldToToggleButtonGroup({ 15 | field: { onChange: _onChange, onBlur: fieldOnBlur, ...field }, 16 | type, 17 | onChange, 18 | onBlur, 19 | form, 20 | ...props 21 | }: ToggleButtonGroupProps): MuiToggleButtonGroupProps { 22 | if (process.env.NODE_ENV !== 'production') { 23 | invariant( 24 | type === 'checkbox', 25 | `property type=checkbox is missing from field ${field.name}, this can caused unexpected behaviour` 26 | ); 27 | 28 | if (!props.exclusive) { 29 | invariant( 30 | Array.isArray(field.value), 31 | `value for ${field.name} is not an array, this can caused unexpected behaviour` 32 | ); 33 | } 34 | } 35 | 36 | return { 37 | onBlur: 38 | onBlur ?? 39 | function () { 40 | fieldOnBlur(field.name); 41 | }, 42 | onChange: 43 | onChange ?? 44 | function (_event, newValue) { 45 | form.setFieldValue(field.name, newValue); 46 | }, 47 | ...field, 48 | ...props, 49 | }; 50 | } 51 | 52 | export function ToggleButtonGroup(props: ToggleButtonGroupProps) { 53 | return ; 54 | } 55 | 56 | ToggleButtonGroup.displayName = 'FormikMaterialUIToggleButtonGroup'; 57 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/Autocomplete.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { test, expect, vi } from 'vitest'; 3 | import { Field } from 'formik'; 4 | import TextField from '@mui/material/TextField'; 5 | import type { AutocompleteRenderInputParams } from '@mui/material/Autocomplete'; 6 | 7 | import { render } from './utils'; 8 | import { Autocomplete } from '../Autocomplete'; 9 | 10 | const options = [ 11 | { value: 'blues', label: 'Blues' }, 12 | { value: 'rock', label: 'Rock' }, 13 | { value: 'jazz', label: 'Jazz' }, 14 | { value: 'orchestra', label: 'Orchestra' }, 15 | ]; 16 | 17 | test('renders', async () => { 18 | const onSubmit = vi.fn(); 19 | const { asFragment } = render( 20 | 26 | option.label 27 | } 28 | renderInput={(params: AutocompleteRenderInputParams) => ( 29 | 30 | )} 31 | />, 32 | { 33 | initialValues: { 34 | autocomplete: null, 35 | }, 36 | onSubmit, 37 | } 38 | ); 39 | 40 | expect(asFragment()).toMatchSnapshot(); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/Checkbox.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@testing-library/jest-dom/vitest'; 3 | import { test, expect, vi } from 'vitest'; 4 | import { Field } from 'formik'; 5 | 6 | import { render, fireEvent } from './utils'; 7 | import { Checkbox } from '../Checkbox'; 8 | 9 | test('renders', async () => { 10 | const onSubmit = vi.fn(); 11 | const { asFragment } = render( 12 | , 13 | { 14 | initialValues: { 15 | checked: false, 16 | }, 17 | onSubmit, 18 | } 19 | ); 20 | 21 | expect(asFragment()).toMatchSnapshot(); 22 | }); 23 | 24 | test('checked', async () => { 25 | // given 26 | const onSubmit = vi.fn(); 27 | const { getByTestId, findByText } = render( 28 | , 37 | { 38 | onSubmit, 39 | initialValues: { 40 | checked: false, 41 | }, 42 | } 43 | ); 44 | 45 | const input = getByTestId('checkbox'); 46 | const submit = getByTestId('submit'); 47 | 48 | // when 49 | fireEvent.change(input, { target: { value: 'checked' } }); 50 | fireEvent.click(submit); 51 | 52 | await findByText('submitted'); 53 | 54 | // then 55 | expect(onSubmit).toBeCalledWith( 56 | { 57 | checked: false, 58 | }, 59 | expect.anything() 60 | ); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/InputBase.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { Formik, Form, Field } from 'formik'; 4 | import { render, fireEvent } from '@testing-library/react'; 5 | 6 | import { InputBase } from '../InputBase'; 7 | import { act } from 'react-dom/test-utils'; 8 | 9 | test('InputBase Renders Correctly', () => { 10 | const { asFragment } = render( 11 | {}} initialValues={{ test: 'Input' }}> 12 |
13 | 14 | 15 |
16 | ); 17 | 18 | expect(asFragment()).toMatchSnapshot(); 19 | }); 20 | 21 | test('InputBase Renders Correctly disabled', async () => { 22 | const { asFragment, getByTestId } = render( 23 | {}} initialValues={{ test: 'Input' }}> 24 |
25 | 30 | 31 |
32 | ); 33 | 34 | await act(async () => { 35 | fireEvent.change(getByTestId('input'), { target: { value: 'a' } }); 36 | }); 37 | expect(asFragment()).toMatchSnapshot(); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/RadioGroup.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { Formik, Form, Field } from 'formik'; 4 | import Radio from '@mui/material/Radio'; 5 | import FormControlLabel from '@mui/material/FormControlLabel'; 6 | import { render } from '@testing-library/react'; 7 | 8 | import { RadioGroup } from '../RadioGroup'; 9 | 10 | test('Radio Group Renders Correctly', () => { 11 | const { asFragment } = render( 12 | {}} initialValues={{ test: '' }}> 13 |
14 | 15 | } 18 | label="Painting" 19 | /> 20 | } 23 | label="Drawing" 24 | /> 25 | } 28 | label="None" 29 | /> 30 | 31 |
32 |
33 | ); 34 | 35 | expect(asFragment()).toMatchSnapshot(); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/Select.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { Formik, Form, Field } from 'formik'; 4 | import { render } from '@testing-library/react'; 5 | import MenuItem from '@mui/material/MenuItem'; 6 | 7 | import { Select } from '../Select'; 8 | 9 | test('Select Renders Correctly', () => { 10 | const { asFragment } = render( 11 | {}} initialValues={{ test: '' }}> 12 |
13 | 14 | 15 | None 16 | 17 | Ten 18 | Twenty 19 | Thirty 20 | 21 |
22 |
23 | ); 24 | 25 | expect(asFragment()).toMatchSnapshot(); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/SimpleFileUpload.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { Formik, Form, Field } from 'formik'; 4 | import { render } from '@testing-library/react'; 5 | 6 | import { SimpleFileUpload } from '../SimpleFileUpload'; 7 | 8 | test('SimpleFileUpload Renders Correctly', () => { 9 | const { asFragment } = render( 10 | {}} initialValues={{ test: '' }}> 11 |
12 | 13 | 14 |
15 | ); 16 | 17 | expect(asFragment()).toMatchSnapshot(); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/Switch.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { Formik, Form, Field } from 'formik'; 4 | import { render } from '@testing-library/react'; 5 | 6 | import { Switch } from '../Switch'; 7 | 8 | test('Switch Renders Correctly', () => { 9 | const { asFragment } = render( 10 | {}}> 11 |
12 | 13 | 14 |
15 | ); 16 | 17 | expect(asFragment()).toMatchSnapshot(); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/TextField.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test, expect } from 'vitest'; 3 | import { Formik, Form, Field } from 'formik'; 4 | import { createTheme, ThemeProvider } from '@mui/material/styles'; 5 | import { render } from '@testing-library/react'; 6 | 7 | import { TextField } from '../TextField'; 8 | 9 | test('TextField Renders Correctly', () => { 10 | const { asFragment } = render( 11 | {}}> 12 |
13 | 14 | 15 |
16 | ); 17 | 18 | expect(asFragment()).toMatchSnapshot(); 19 | }); 20 | 21 | test('Outlined TextField', () => { 22 | const { asFragment } = render( 23 | {}}> 24 |
25 | 31 | 32 |
33 | ); 34 | 35 | expect(asFragment()).toMatchSnapshot(); 36 | }); 37 | 38 | test('Override TextField Theme Variant', () => { 39 | const theme = createTheme({}); 40 | 41 | const { asFragment } = render( 42 | 43 | {}}> 44 |
45 | 46 | 47 |
48 |
49 | ); 50 | 51 | expect(asFragment()).toMatchSnapshot(); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/ToggleButtonGroup.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { test, expect, vi } from 'vitest'; 3 | import { Field } from 'formik'; 4 | 5 | import { render } from './utils'; 6 | 7 | import { ToggleButtonGroup } from '../ToggleButtonGroup'; 8 | 9 | test('renders', async () => { 10 | const onSubmit = vi.fn(); 11 | const { asFragment } = render( 12 | , 13 | { 14 | initialValues: { 15 | toggle: [], 16 | }, 17 | onSubmit, 18 | } 19 | ); 20 | 21 | expect(asFragment()).toMatchSnapshot(); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`renders 1`] = ` 4 | 5 |
9 |
13 |
16 | 24 |
27 | 40 |
43 | 65 |
66 | 78 |
79 |
80 |
81 | 86 |
87 |
88 | `; 89 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/Checkbox.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`renders 1`] = ` 4 | 5 |
9 | 12 | 19 | 30 | 33 | 34 | 39 | 40 |
41 | `; 42 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/InputBase.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`InputBase Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 17 |
18 |
19 |
20 | `; 21 | 22 | exports[`InputBase Renders Correctly disabled 1`] = ` 23 | 24 |
27 |
30 | 37 |
38 |
39 |
40 | `; 41 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/Select.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Select Renders Correctly 1`] = ` 4 | 5 |
8 |
11 |
69 |
70 |
71 | `; 72 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/SimpleFileUpload.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`SimpleFileUpload Renders Correctly 1`] = ` 4 | 5 |
8 |
11 | 17 |
20 | 27 |
28 |
29 |
30 |
31 | `; 32 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/Switch.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Switch Renders Correctly 1`] = ` 4 | 5 |
8 | 11 | 14 | 19 | 22 | 25 | 26 | 29 | 30 | 31 |
32 | `; 33 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/TextField.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Outlined TextField 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 30 | 42 |
43 |
44 |
45 |
46 | `; 47 | 48 | exports[`Override TextField Theme Variant 1`] = ` 49 | 50 |
53 |
56 | 64 |
67 | 75 | 87 |
88 |
89 |
90 |
91 | `; 92 | 93 | exports[`TextField Renders Correctly 1`] = ` 94 | 95 |
98 |
101 | 109 |
112 | 120 | 132 |
133 |
134 |
135 |
136 | `; 137 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/__snapshots__/ToggleButtonGroup.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`renders 1`] = ` 4 | 5 |
9 |
14 | 19 | 20 | 21 | `; 22 | -------------------------------------------------------------------------------- /packages/formik-mui/src/__tests__/utils.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { test } from 'vitest'; 3 | import { render, RenderOptions } from '@testing-library/react'; 4 | import { Formik, Form, FormikConfig } from 'formik'; 5 | 6 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 7 | interface Props extends Omit, 'render' | 'children'> { 8 | children?: React.ReactNode; 9 | } 10 | 11 | // Your test suite must contain at least one test. 12 | /* eslint-disable-next-line no-undef */ 13 | test.skip('skip', () => {}); // eslint-disable-line @typescript-eslint/no-empty-function 14 | 15 | function FormikWrapper({ children, ...config }: Props): React.ReactElement { 16 | return ( 17 | 18 | {({ submitForm, submitCount }) => { 19 | return ( 20 |
21 | {children} 22 | {submitCount > 0 && submitted} 23 | 26 |
27 | ); 28 | }} 29 |
30 | ); 31 | } 32 | 33 | const customRender = ( 34 | ui: React.ReactElement, 35 | formikOpts: Omit, 36 | options?: Omit 37 | ) => { 38 | return render(ui, { 39 | wrapper: function CustomRendererWrapper(props) { 40 | return ; 41 | }, 42 | ...options, 43 | }); 44 | }; 45 | 46 | export * from '@testing-library/react'; 47 | 48 | export { customRender as render }; 49 | -------------------------------------------------------------------------------- /packages/formik-mui/src/main.ts: -------------------------------------------------------------------------------- 1 | export * from './Autocomplete'; 2 | export * from './Checkbox'; 3 | export * from './CheckboxWithLabel'; 4 | export * from './InputBase'; 5 | export * from './RadioGroup'; 6 | export * from './Select'; 7 | export * from './SimpleFileUpload'; 8 | export * from './Switch'; 9 | export * from './TextField'; 10 | export * from './ToggleButtonGroup'; 11 | -------------------------------------------------------------------------------- /packages/formik-mui/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/formik-mui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/formik-mui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/formik-mui/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite"; 3 | import * as path from "path"; 4 | import react from "@vitejs/plugin-react"; 5 | import dts from "vite-plugin-dts"; 6 | 7 | const external = [ 8 | /node_modules/, 9 | "react", 10 | "react/jsx-runtime", 11 | "formik", 12 | "tiny-warning", 13 | "@mui/utils", 14 | /@mui\/material/, 15 | ]; 16 | 17 | // https://vitejs.dev/config/ 18 | export default defineConfig({ 19 | plugins: [react(), dts({ 20 | rollupTypes: true, 21 | // https://github.com/qmhc/vite-plugin-dts/issues/344#issuecomment-2231355823 22 | tsconfigPath: "./tsconfig.app.json", 23 | })], 24 | build: { 25 | lib: { 26 | entry: path.resolve(__dirname, "src/main.ts"), 27 | }, 28 | sourcemap: true, 29 | minify: false, 30 | rollupOptions: { 31 | external, 32 | output: [ 33 | { 34 | format: "es", 35 | dir: "./dist", 36 | preserveModules: true, 37 | entryFileNames: ({ name: fileName }) => { 38 | return `${fileName}.js`; 39 | }, 40 | }, 41 | ], 42 | }, 43 | }, 44 | test: { 45 | environment: 'jsdom', 46 | setupFiles: ['./vitest-setup.js'], 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /packages/formik-mui/vitest-setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest'; 2 | import { cleanup } from '@testing-library/react'; 3 | import { afterEach } from 'vitest'; 4 | 5 | afterEach(async () => { 6 | cleanup(); 7 | }) -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackworx/formik-mui/d65e4627b30cfaad65d88fe51acb3f2925ef9464/public/.gitkeep -------------------------------------------------------------------------------- /src/stories/Checkbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import Button from '@mui/material/Button'; 4 | import Box from '@mui/material/Box'; 5 | import { Formik, Form, Field } from 'formik'; 6 | import { action } from '@storybook/addon-actions'; 7 | 8 | import Wrapper from './Wrapper'; 9 | import FormValues from './FormValues'; 10 | 11 | import { Checkbox } from '../../packages/formik-mui/src/Checkbox'; 12 | import { CheckboxWithLabel } from '../../packages/formik-mui/src/CheckboxWithLabel'; 13 | import Typography from '@mui/material/Typography'; 14 | 15 | export default { 16 | title: 'Core/Checkbox', 17 | component: Checkbox, 18 | parameters: { 19 | layout: 'fullscreen', 20 | }, 21 | argTypes: { onSubmit: { action: 'submit' } }, 22 | } as Meta; 23 | 24 | const Template: StoryFn = () => ( 25 | 26 | { 35 | setTimeout(() => { 36 | setSubmitting(false); 37 | action('submit')(values); 38 | }, 2000); 39 | }} 40 | > 41 | {({ submitForm, values }) => ( 42 |
43 | 44 | On 45 | 46 | Off 47 | 48 | Null 49 | 50 | 51 | 52 | 58 | 59 | 60 | 67 | 74 | 81 | 82 | 85 |
86 | 87 | 88 | )} 89 |
90 |
91 | ); 92 | 93 | export const Default = { 94 | render: Template, 95 | }; 96 | -------------------------------------------------------------------------------- /src/stories/DatePicker.stories.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { subDays } from 'date-fns'; 6 | import { Field, Form, Formik } from 'formik'; 7 | import * as React from 'react'; 8 | import * as yup from 'yup'; 9 | import { DatePicker } from '../../packages/formik-mui-x-date-pickers/src/DatePicker'; 10 | import { DesktopDatePicker } from '../../packages/formik-mui-x-date-pickers/src/DesktopDatePicker'; 11 | import { MobileDatePicker } from '../../packages/formik-mui-x-date-pickers/src/MobileDatePicker'; 12 | import { StaticDatePicker } from '../../packages/formik-mui-x-date-pickers/src/StaticDatePicker'; 13 | import FormValues from './FormValues'; 14 | import Wrapper from './Wrapper'; 15 | 16 | interface Values { 17 | date: Date | null; 18 | } 19 | 20 | const schema = yup.object().shape({ 21 | date: yup.date().required(), 22 | futureDate: yup.date().required().min(subDays(new Date(), 1)), 23 | }); 24 | 25 | export default { 26 | title: 'X-Mui/DatePicker', 27 | component: DatePicker, 28 | parameters: { 29 | layout: 'fullscreen', 30 | }, 31 | argTypes: { onSubmit: { action: 'submit' } }, 32 | } as Meta; 33 | 34 | const Template: StoryFn = () => ( 35 | 36 | 37 | initialValues={{ 38 | date: new Date(), 39 | }} 40 | validationSchema={schema} 41 | onSubmit={(values, { setSubmitting }) => { 42 | setTimeout(() => { 43 | setSubmitting(false); 44 | action('submit')(values); 45 | }, 2000); 46 | }} 47 | > 48 | {({ submitForm, isSubmitting, values }) => ( 49 |
50 | 56 |
57 |
58 | 64 |
65 |
66 | 72 |
73 |
74 | 80 |
81 |
82 | 88 |
89 |
90 | 96 |
97 | {isSubmitting && } 98 |
99 | 107 |
108 | 109 | 110 | )} 111 | 112 |
113 | ); 114 | 115 | export const Default = { 116 | render: Template, 117 | }; 118 | -------------------------------------------------------------------------------- /src/stories/DateTimePicker.stories.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { Field, Form, Formik } from 'formik'; 6 | import * as React from 'react'; 7 | import * as yup from 'yup'; 8 | import { DateTimePicker } from '../../packages/formik-mui-x-date-pickers/src/DateTimePicker'; 9 | import { DesktopDateTimePicker } from '../../packages/formik-mui-x-date-pickers/src/DesktopDateTimePicker'; 10 | import { MobileDateTimePicker } from '../../packages/formik-mui-x-date-pickers/src/MobileDateTimePicker'; 11 | import FormValues from './FormValues'; 12 | import Wrapper from './Wrapper'; 13 | 14 | interface Values { 15 | date: Date | null; 16 | } 17 | 18 | const schema = yup.object().shape({ 19 | date: yup.date().required(), 20 | }); 21 | 22 | export default { 23 | title: 'X-Mui/DateTimePicker', 24 | component: DateTimePicker, 25 | parameters: { 26 | layout: 'fullscreen', 27 | }, 28 | argTypes: { onSubmit: { action: 'submit' } }, 29 | } as Meta; 30 | 31 | const Template: StoryFn = () => ( 32 | 33 | 34 | initialValues={{ 35 | date: new Date(), 36 | }} 37 | validationSchema={schema} 38 | onSubmit={(values, { setSubmitting }) => { 39 | setTimeout(() => { 40 | setSubmitting(false); 41 | action('submit')(values); 42 | }, 2000); 43 | }} 44 | > 45 | {({ submitForm, isSubmitting, values }) => ( 46 |
47 | 53 |
54 |
55 | 61 |
62 |
63 | 69 |
70 |
71 | 77 | {/* TODO: This static date-time picker is not contained within its section like the other pickers. 78 | The "edit" pen icon appears at the very top right of the wrapper. 79 | The `StaticDatePicker` and `StaticTimePicker` both do not have this issue. 80 |
81 |
82 | */} 88 |
89 | {isSubmitting && } 90 |
91 | 99 |
100 | 101 | 102 | )} 103 | 104 |
105 | ); 106 | 107 | export const Default = { 108 | render: Template, 109 | }; 110 | -------------------------------------------------------------------------------- /src/stories/FormValues.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import Typography from '@mui/material/Typography'; 3 | import React from 'react'; 4 | 5 | function replacer(_: string, value: unknown) { 6 | // Filtering out properties 7 | if (value instanceof File) { 8 | return { 9 | size: value.size, 10 | name: value.name, 11 | type: value.type, 12 | }; 13 | } 14 | return value; 15 | } 16 | 17 | interface Props { 18 | values: unknown; 19 | } 20 | 21 | const FormValues = ({ values }: Props) => ( 22 | 23 | 24 | State 25 | 26 | 27 | {JSON.stringify(values, replacer, 2)} 28 | 29 | 30 | ); 31 | 32 | export default FormValues; 33 | -------------------------------------------------------------------------------- /src/stories/InputBase.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import Button from '@mui/material/Button'; 4 | import { Formik, Form, Field } from 'formik'; 5 | import LinearProgress from '@mui/material/LinearProgress'; 6 | import { action } from '@storybook/addon-actions'; 7 | 8 | import Wrapper from './Wrapper'; 9 | 10 | import { InputBase } from '../../packages/formik-mui/src/InputBase'; 11 | import FormValues from './FormValues'; 12 | 13 | interface Values { 14 | inputBase: string; 15 | } 16 | 17 | export default { 18 | title: 'Core/InputBase', 19 | component: InputBase, 20 | parameters: { 21 | layout: 'fullscreen', 22 | }, 23 | argTypes: { onSubmit: { action: 'submit' } }, 24 | } as Meta; 25 | 26 | const Template: StoryFn = () => ( 27 | 28 | 29 | initialValues={{ 30 | inputBase: 'Naked input', 31 | }} 32 | onSubmit={(values, { setSubmitting }) => { 33 | setTimeout(() => { 34 | setSubmitting(false); 35 | action('submit')(values); 36 | }, 2000); 37 | }} 38 | > 39 | {({ submitForm, isSubmitting, values }) => ( 40 |
41 | 42 |
43 | {isSubmitting && } 44 |
45 | 53 |
54 | 55 | 56 | )} 57 | 58 |
59 | ); 60 | 61 | export const Default = { 62 | render: Template, 63 | }; 64 | -------------------------------------------------------------------------------- /src/stories/RadioGroup.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import Button from '@mui/material/Button'; 4 | import { Formik, Form, Field } from 'formik'; 5 | import Wrapper from './Wrapper'; 6 | import { action } from '@storybook/addon-actions'; 7 | 8 | import { RadioGroup } from '../../packages/formik-mui/src/RadioGroup'; 9 | import LinearProgress from '@mui/material/LinearProgress'; 10 | import FormControlLabel from '@mui/material/FormControlLabel'; 11 | import Radio from '@mui/material/Radio'; 12 | import FormValues from './FormValues'; 13 | 14 | interface Values { 15 | activity: string; 16 | } 17 | 18 | export default { 19 | title: 'Core/RadioGroup', 20 | component: RadioGroup, 21 | parameters: { 22 | layout: 'fullscreen', 23 | }, 24 | argTypes: { onSubmit: { action: 'submit' } }, 25 | } as Meta; 26 | 27 | const Template: StoryFn = () => ( 28 | 29 | 30 | initialValues={{ 31 | activity: '', 32 | }} 33 | onSubmit={(values, { setSubmitting }) => { 34 | setTimeout(() => { 35 | setSubmitting(false); 36 | action('submit')(values); 37 | }, 2000); 38 | }} 39 | > 40 | {({ submitForm, values, isSubmitting, isValid }) => { 41 | return ( 42 |
43 | 44 | } 47 | label="Painting" 48 | disabled={isSubmitting} 49 | /> 50 | } 53 | label="Drawing" 54 | disabled={isSubmitting} 55 | /> 56 | } 59 | label="None" 60 | disabled 61 | /> 62 | 63 |
64 | {isSubmitting && } 65 |
66 | 74 | 75 | 76 | ); 77 | }} 78 | 79 |
80 | ); 81 | 82 | export const Default = { 83 | render: Template, 84 | }; 85 | -------------------------------------------------------------------------------- /src/stories/Select.stories.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import MenuItem from '@mui/material/MenuItem'; 4 | import { SxProps } from '@mui/system'; 5 | import { action } from '@storybook/addon-actions'; 6 | import { Field, Form, Formik } from 'formik'; 7 | import * as React from 'react'; 8 | import { Select } from '../../packages/formik-mui/src/Select'; 9 | import FormValues from './FormValues'; 10 | import Wrapper from './Wrapper'; 11 | 12 | interface Values { 13 | age: string; 14 | pets: string[]; 15 | } 16 | 17 | const sxFormControl: SxProps = { 18 | m: 1, 19 | minWidth: 140, 20 | }; 21 | 22 | export default { 23 | title: 'Core/Select', 24 | component: Select, 25 | parameters: { 26 | layout: 'fullscreen', 27 | }, 28 | argTypes: { onSubmit: { action: 'submit' } }, 29 | } as Meta; 30 | 31 | const Template: StoryFn = () => ( 32 | 33 | 34 | initialValues={{ age: '', pets: [] }} 35 | onSubmit={(values, { setSubmitting }) => { 36 | setTimeout(() => { 37 | setSubmitting(false); 38 | action('submit')(values); 39 | }, 2000); 40 | }} 41 | > 42 | {({ submitForm, values }) => ( 43 |
44 | 52 | Ten 53 | Twenty 54 | Thirty 55 | 56 |
57 | 66 | Ten 67 | Twenty 68 | Thirty 69 | 70 |
71 | 80 | !age 81 | ? 'Please enter your age' 82 | : age < 21 83 | ? 'You must be 21 or older' 84 | : undefined 85 | } 86 | > 87 | Ten 88 | Twenty 89 | Thirty 90 | 91 |
92 | 100 | 101 | None 102 | 103 | Ten 104 | Twenty 105 | Thirty 106 | 107 |
108 | 126 | 127 | 128 | 129 | 130 | 131 |
132 | 141 | Dogs 142 | Cats 143 | Rats 144 | Snakes 145 | 146 |
147 | 148 | 162 | 163 | 164 | 165 | 166 | 167 |
168 | 171 |
172 | 173 | 174 | )} 175 | 176 |
177 | ); 178 | 179 | export const Default = { 180 | render: Template, 181 | }; 182 | -------------------------------------------------------------------------------- /src/stories/SimpleFileUpload.stories.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import { Field, Form, Formik } from 'formik'; 5 | import React from 'react'; 6 | import * as Yup from 'yup'; 7 | import { SimpleFileUpload } from '../../packages/formik-mui/src/SimpleFileUpload'; 8 | import FormValues from './FormValues'; 9 | import Wrapper from './Wrapper'; 10 | 11 | // 10 Megs 12 | const MAX_FILE_SIZE = 10485760; 13 | 14 | const schema = Yup.object().shape({ 15 | file: Yup.mixed() 16 | .required('Required') 17 | .test( 18 | 'file', 19 | 'File must be less than 10MB', 20 | (value: File) => value == null || value.size < MAX_FILE_SIZE 21 | ), 22 | }); 23 | 24 | interface Values { 25 | file: '' | File; 26 | } 27 | 28 | export default { 29 | title: 'Core/SimpleFileUpload', 30 | component: SimpleFileUpload, 31 | parameters: { 32 | layout: 'fullscreen', 33 | }, 34 | argTypes: { onSubmit: { action: 'submit' } }, 35 | } as Meta; 36 | 37 | const Template: StoryFn = () => ( 38 | 39 | 40 | validationSchema={schema} 41 | initialValues={{ file: '' }} 42 | onSubmit={(values, { setSubmitting }) => { 43 | setTimeout(() => { 44 | setSubmitting(false); 45 | action('submit')(values); 46 | }, 2000); 47 | }} 48 | > 49 | {({ submitForm, values }) => ( 50 |
51 | 56 |
57 | 60 |
61 | 62 | 63 | )} 64 | 65 |
66 | ); 67 | 68 | export const Default = { 69 | render: Template, 70 | }; 71 | -------------------------------------------------------------------------------- /src/stories/Switches.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import { StoryFn, Meta } from '@storybook/react'; 4 | import FormControlLabel from '@mui/material/FormControlLabel'; 5 | import { Formik, Form, Field } from 'formik'; 6 | import { action } from '@storybook/addon-actions'; 7 | 8 | import Wrapper from './Wrapper'; 9 | import FormValues from './FormValues'; 10 | 11 | import { Switch } from '../../packages/formik-mui/src/Switch'; 12 | import Box from '@mui/material/Box'; 13 | 14 | export default { 15 | title: 'Core/Switch', 16 | component: Switch, 17 | parameters: { 18 | layout: 'fullscreen', 19 | }, 20 | argTypes: { onSubmit: { action: 'submit' } }, 21 | } as Meta; 22 | 23 | const Template: StoryFn = () => ( 24 | 25 | { 33 | setTimeout(() => { 34 | setSubmitting(false); 35 | action('submit')(values); 36 | }, 2000); 37 | }} 38 | > 39 | {({ submitForm, values }) => ( 40 |
41 | 42 |
43 | 44 |
45 | 49 | } 50 | /> 51 |
52 | 53 | 62 | } 63 | /> 64 | 73 | } 74 | /> 75 | 84 | } 85 | /> 86 | 87 | 90 |
91 | 92 | 93 | )} 94 |
95 |
96 | ); 97 | 98 | export const Default = { 99 | render: Template, 100 | }; 101 | -------------------------------------------------------------------------------- /src/stories/TextField.stories.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | import MenuItem from '@mui/material/MenuItem'; 5 | import MuiTextField from '@mui/material/TextField'; 6 | import { action } from '@storybook/addon-actions'; 7 | import { Field, Form, Formik } from 'formik'; 8 | import * as React from 'react'; 9 | import * as yup from 'yup'; 10 | import { 11 | fieldToTextField, 12 | TextField, 13 | TextFieldProps, 14 | } from '../../packages/formik-mui/src/TextField'; 15 | import FormValues from './FormValues'; 16 | import Wrapper from './Wrapper'; 17 | 18 | interface Values { 19 | user: { email: string }; 20 | password: string; 21 | helperText: string; 22 | uppercasing: string; 23 | select: string; 24 | outlined: string; 25 | } 26 | 27 | const schema = yup.object().shape({ 28 | user: yup.object().shape({ 29 | email: yup.string().email().required(), 30 | }), 31 | password: yup.string().required(), 32 | helperText: yup.string().required(), 33 | select: yup.mixed().required(), 34 | }); 35 | 36 | const ranges = [ 37 | { 38 | value: '0-20', 39 | label: '0 to 20', 40 | }, 41 | { 42 | value: '21-50', 43 | label: '21 to 50', 44 | }, 45 | { 46 | value: '51-100', 47 | label: '51 to 100', 48 | }, 49 | ]; 50 | 51 | const UpperCasingTextField = (props: TextFieldProps) => { 52 | return ( 53 | { 56 | const { value } = event.target; 57 | 58 | const { form, field } = props; 59 | form.setFieldValue(field.name, value ? value.toUpperCase() : ''); 60 | }} 61 | /> 62 | ); 63 | }; 64 | 65 | export default { 66 | title: 'Core/TextField', 67 | component: TextField, 68 | parameters: { 69 | layout: 'fullscreen', 70 | }, 71 | argTypes: { onSubmit: { action: 'submit' } }, 72 | } as Meta; 73 | 74 | const Template: StoryFn = () => ( 75 | 76 | 77 | initialValues={{ 78 | user: { email: '' }, 79 | password: '', 80 | helperText: '', 81 | uppercasing: '', 82 | select: '', 83 | outlined: '', 84 | }} 85 | validationSchema={schema} 86 | onSubmit={(values, { setSubmitting }) => { 87 | setTimeout(() => { 88 | setSubmitting(false); 89 | action('submit')(values); 90 | }, 2000); 91 | }} 92 | > 93 | {({ submitForm, isSubmitting, values }) => ( 94 |
95 | 102 |
103 | 109 |
110 | 117 |
118 | 123 |
124 |
125 | 132 |
133 |
134 | 140 |
141 |
142 | 151 | {ranges.map((option) => ( 152 | 153 | {option.label} 154 | 155 | ))} 156 | 157 |
158 | {isSubmitting && } 159 |
160 | 168 |
169 | 170 | 171 | )} 172 | 173 |
174 | ); 175 | 176 | export const Default = { 177 | render: Template, 178 | }; 179 | -------------------------------------------------------------------------------- /src/stories/TimePicker.stories.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { Field, Form, Formik } from 'formik'; 6 | import * as React from 'react'; 7 | import * as yup from 'yup'; 8 | import { DesktopTimePicker } from '../../packages/formik-mui-x-date-pickers/src/DesktopTimePicker'; 9 | import { MobileTimePicker } from '../../packages/formik-mui-x-date-pickers/src/MobileTimePicker'; 10 | import { StaticTimePicker } from '../../packages/formik-mui-x-date-pickers/src/StaticTimePicker'; 11 | import { TimePicker } from '../../packages/formik-mui-x-date-pickers/src/TimePicker'; 12 | import FormValues from './FormValues'; 13 | import Wrapper from './Wrapper'; 14 | 15 | interface Values { 16 | time: Date | null; 17 | } 18 | 19 | const schema = yup.object().shape({ 20 | time: yup.date().required(), 21 | }); 22 | 23 | export default { 24 | title: 'X-Mui/TimePicker', 25 | component: TimePicker, 26 | parameters: { 27 | layout: 'fullscreen', 28 | }, 29 | argTypes: { onSubmit: { action: 'submit' } }, 30 | } as Meta; 31 | 32 | const Template: StoryFn = () => ( 33 | 34 | 35 | initialValues={{ 36 | time: new Date(), 37 | }} 38 | validationSchema={schema} 39 | onSubmit={(values, { setSubmitting }) => { 40 | setTimeout(() => { 41 | setSubmitting(false); 42 | action('submit')(values); 43 | }, 2000); 44 | }} 45 | > 46 | {({ submitForm, isSubmitting, values }) => ( 47 |
48 | 54 |
55 |
56 | 62 |
63 |
64 | 69 |
70 |
71 | 76 | {isSubmitting && } 77 |
78 | 86 |
87 | 88 | 89 | )} 90 | 91 |
92 | ); 93 | 94 | export const Default = { 95 | render: Template, 96 | }; 97 | -------------------------------------------------------------------------------- /src/stories/ToggleButtonGroup.stories.tsx: -------------------------------------------------------------------------------- 1 | import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter'; 2 | import FormatAlignJustifyIcon from '@mui/icons-material/FormatAlignJustify'; 3 | import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft'; 4 | import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight'; 5 | import { StoryFn, Meta } from '@storybook/react'; 6 | import Box from '@mui/material/Box'; 7 | import Button from '@mui/material/Button'; 8 | import ToggleButton from '@mui/material/ToggleButton'; 9 | import Typography from '@mui/material/Typography'; 10 | import { action } from '@storybook/addon-actions'; 11 | import { Field, Form, Formik } from 'formik'; 12 | import * as React from 'react'; 13 | import { ToggleButtonGroup } from '../../packages/formik-mui/src/ToggleButtonGroup'; 14 | import FormValues from './FormValues'; 15 | import Wrapper from './Wrapper'; 16 | 17 | export default { 18 | title: 'Core/ToggleButtonGroup', 19 | component: ToggleButtonGroup, 20 | parameters: { 21 | layout: 'fullscreen', 22 | }, 23 | argTypes: { onSubmit: { action: 'submit' } }, 24 | } as Meta; 25 | 26 | const Template: StoryFn = () => ( 27 | 28 | { 34 | setTimeout(() => { 35 | setSubmitting(false); 36 | action('submit')(values); 37 | }, 2000); 38 | }} 39 | > 40 | {({ submitForm, values }) => ( 41 |
42 | Multiple 43 | 44 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Exclusive 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 89 | 90 | 91 | 92 | )} 93 |
94 |
95 | ); 96 | 97 | export const Default = { 98 | render: Template, 99 | }; 100 | -------------------------------------------------------------------------------- /src/stories/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; 2 | import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; 3 | import Paper from '@mui/material/Paper'; 4 | import React, { ReactNode } from 'react'; 5 | 6 | interface Props { 7 | children: ReactNode; 8 | } 9 | 10 | const Wrapper = ({ children }: Props) => ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ); 17 | 18 | export default Wrapper; 19 | --------------------------------------------------------------------------------