├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .huskyrc
├── .npmignore
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── assets
└── styles
│ ├── _mixin.scss
│ ├── _variable.scss
│ ├── app.scss
│ ├── calendar.scss
│ └── theme
│ └── red.scss
├── examples
└── CalendarSelectedController.tsx
├── package.json
├── src
├── common
│ ├── @types.ts
│ └── Constant.ts
├── components
│ ├── Backdrop.tsx
│ ├── Calendar.tsx
│ ├── CalendarBody.tsx
│ ├── CalendarContainer.tsx
│ ├── CalendarHead.tsx
│ ├── DatePicker.tsx
│ ├── DayView.tsx
│ ├── Picker.tsx
│ ├── PickerInput.tsx
│ ├── RangeDatePicker.tsx
│ ├── RangePickerInput.tsx
│ ├── SVGIcon
│ │ ├── IconBase.tsx
│ │ ├── Icons.tsx
│ │ ├── SVGIcon.tsx
│ │ └── index.tsx
│ ├── TableCell.tsx
│ ├── TableMatrixView.tsx
│ ├── TimeContainer.tsx
│ ├── TimeInput.tsx
│ └── TodayPanel.tsx
├── index.ts
└── utils
│ ├── ArrayUtil.ts
│ ├── DOMUtil.ts
│ ├── DateUtil.ts
│ ├── FunctionUtil.ts
│ ├── LocaleUtil.ts
│ ├── StringUtil.ts
│ └── TypeUtil.ts
├── stories
├── Calendar.stories.tsx
├── DatePicker.stories.tsx
├── PickerInput.stories.tsx
├── RangeDatePicker.stories.tsx
├── TimeContainer.stories.tsx
├── TimeInput.stories.tsx
├── css
│ └── custom.css
└── decorator
│ └── LayoutDecorator.tsx
├── test-preprocessor.js
├── test-setup.js
├── test-shim.js
├── test
├── ArrayUtil.test.ts
├── Calendar.test.tsx
├── CalendarBody.test.tsx
├── CalendarContainer.test.tsx
├── CalendarHead.test.tsx
├── DOMUtil.test.ts
├── DatePicker.test.tsx
├── DateUtil.test.ts
├── DayView.test.tsx
├── LocaleUtil.test.ts
├── Picker.test.tsx
├── PickerInput.test.tsx
├── RangeDatePicker.test.tsx
├── RangePickerInput.test.tsx
├── StringUtil.test.ts
├── TableCell.test.tsx
├── TableMatrixView.test.tsx
├── TimeContainer.test.tsx
├── TimeInput.test.tsx
├── TodayPanel.test.tsx
├── __snapshots__
│ ├── Calendar.test.tsx.snap
│ ├── CalendarBody.test.tsx.snap
│ ├── CalendarContainer.test.tsx.snap
│ ├── CalendarHead.test.tsx.snap
│ ├── DayView.test.tsx.snap
│ ├── RangeDatePicker.test.tsx.snap
│ ├── RangePickerInput.test.tsx.snap
│ ├── TableCell.test.tsx.snap
│ ├── TableMatrixView.test.tsx.snap
│ └── TodayPanel.test.tsx.snap
└── utils
│ └── TestingUtil.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## System and generated files
2 | .idea/
3 | .DS_Store
4 | .vscode/
5 | .sass-cache
6 | .jest.test.result.json
7 |
8 | ## Directories
9 | log/
10 | dist/
11 | node_modules/
12 | coverage/
13 | bower_components/
14 | storybook-static/
15 | demo/
16 | lib/
17 |
18 | ## Files
19 | result.xml
20 | *.log
21 | out*/
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-commit": "npx lint-staged"
4 | }
5 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | demo
2 | examples
3 | node_modules
4 | src
5 | .babelrc
6 | tsconfig.json
7 | tslint.json
8 | webpack.config.build.js
9 | webpack.config.dev.js
10 | webpack.config.js
11 | yarn-error.log
12 | yarn.lock
13 | .storybook
14 | .idea
15 | .vscode
16 | coverage
17 | examples
18 | stories
19 | storybook-static
20 | test
21 | .jest.test.result.json
22 | .travis.yml
23 | test-*.js
24 | webpack*.js
25 | yarn-error.log
26 | assets/images
27 | dist
28 | out*/
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-knobs/register';
2 | import '@storybook/addon-actions/register';
3 | import '@storybook/addon-jest/register';
4 | import '@storybook/addon-options/register';
5 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { addDecorator, configure } from '@storybook/react';
2 | import { withTests } from '@storybook/addon-jest';
3 | import results from '../.jest.test.result.json';
4 | import { withKnobs } from '@storybook/addon-knobs';
5 | import { withInfo } from '@storybook/addon-info';
6 | import { withOptions } from '@storybook/addon-options';
7 | import { themes } from '@storybook/components';
8 | import 'assets/styles/calendar.scss';
9 | // automatically import all files ending in *.stories.js
10 | const req = require.context('../stories', true, /.stories.tsx$/);
11 | function loadStories() {
12 | req.keys().forEach(filename => req(filename));
13 | }
14 |
15 | addDecorator(
16 | withOptions({
17 | name: 'React Datepicker',
18 | url: 'https://github.com/y0c/react-datepicker',
19 | addonPanelInRight: true,
20 | })
21 | );
22 |
23 | addDecorator(
24 | withTests({
25 | results,
26 | filesExt: '.test.tsx',
27 | })
28 | );
29 |
30 | addDecorator(withKnobs);
31 | addDecorator(
32 | withInfo({
33 | inline: true,
34 | })
35 | );
36 |
37 | configure(loadStories, module);
38 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // load the default config generator.
2 | const path = require('path');
3 |
4 | module.exports = ({ config }) => {
5 | // Extend it as you need.
6 | // For example, add typescript loader:
7 | config.module.rules.push({
8 | test: /\.(ts|tsx)$/,
9 | use: [
10 | require.resolve('awesome-typescript-loader'),
11 | require.resolve('react-docgen-typescript-loader'),
12 | ],
13 | });
14 |
15 | config.module.rules.push({
16 | test: /\.scss$/,
17 | use: [
18 | require.resolve('style-loader'),
19 | require.resolve('css-loader'),
20 | require.resolve('sass-loader'),
21 | ],
22 | });
23 |
24 | config.resolve.modules.push(path.join(__dirname, '../'));
25 | config.resolve.extensions.push('.ts', '.tsx');
26 | return config;
27 | };
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 10
4 |
5 | before_install:
6 | - npm i -g npm@latest
7 | - npm install codecov -g
8 |
9 | script:
10 | - yarn test
11 |
12 | after_success:
13 | - codecov
14 | - bash <(curl -s https://codecov.io/bash)
15 |
16 | cache: yarn
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at holnet1026@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | Thanks for your interest in improving react-datepicker!
4 |
5 | This repo uses yarn workspaces, so you should install yarn@1.3.2 or higher as a package manager.
6 |
7 | * The development environment is Node 9+.
8 | * Unit tests run with jest
9 | * Code has 100% test coverage and it should stay so (yarn test --coverage to check it)
10 | * Code must be linted to pass our CI (yarn lint)
11 |
12 | ## Coding Convention
13 |
14 | * We use prettier for code styling. Don't worry about tabs vs spaces, or how to indent your code.
15 | * We use ESlint for all other coding standards. We try to be consistent and helpful.
16 |
17 | ## Commit Message Guide
18 | We are following [Karama Commit Message Guide](http://karma-runner.github.io/3.0/dev/git-commit-msg.html)
19 | * feat (new feature for the user, not a new feature for build script)
20 | * fix (bug fix for the user, not a fix to a build script)
21 | * docs (changes to the documentation)
22 | * style (formatting, missing semi colons, etc; no production code change)
23 | * refactor (refactoring production code, eg. renaming a variable)
24 | * test (adding missing tests, refactoring tests; no production code change)
25 | * chore (updating grunt tasks etc; no production code change)
26 |
27 | ## Start now!
28 |
29 | Pick an issue you would like to fix or a feature you would like to see. good-first-issues are a good place to start.
30 |
31 |
32 | ## Fork this project
33 | ```
34 | # clone your fork repository
35 | git clone https://github.com/y0c/react-datepicker
36 | # install dependency
37 | yarn
38 | # run storybook local
39 | yarn run storybook
40 | ```
41 | Then open http://localhost:6006
42 |
43 | ## Create Topic Branch
44 | ```
45 | # branch naming is (feature/fix)/iss-{issue_nubmer}-{feature_name}
46 | git checkout -b feature/iss-01-feature-name
47 | # if you develop complete?
48 | # commit & pull request
49 | ```
50 | if your PR pass code review then merge to master branch
51 |
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 HoSung Lim
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React DatePicker
10 |
11 |
12 |
13 |
14 |
15 |
16 | [](https://opensource.org/licenses/MIT) [](https://badge.fury.io/js/%40y0c%2Freact-datepicker)
17 | [](https://travis-ci.com/y0c/react-datepicker)
18 | [](https://codecov.io/gh/y0c/react-datepicker)
19 | [](https://codeclimate.com/github/y0c/react-datepicker/maintainability)
20 | [](https://github.com/prettier/prettier)
21 | [](https://david-dm.org/y0c/react-datepicker)
22 | [](https://david-dm.org/y0c/react-datepicker?type=dev)
23 | [](https://y0c.github.io/react-datepicker)
24 | [](https://www.npmjs.com/package/@y0c/react-datepicker)
25 | [](https://gitter.im/react-datepicker/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
26 |
27 |
28 |
29 | > Flexible, Reusable, Mobile friendly DatePicker Component
30 |
31 | ## 🎬 Intro
32 |
33 | ### DatePicker
34 |
35 | 
36 |
37 |
38 | ### RangeDatePicker
39 |
40 | 
41 |
42 |
43 | [Demo in Storybook](https://y0c.github.io/react-datepicker)
44 |
45 | [](https://codesandbox.io/s/pw6n17pk57)
46 |
47 | ## ✨ Major Component
48 |
49 | * RangeDatePicker
50 | * DatePicker
51 | * Standalone Calendar
52 |
53 | The components that you can use are as follows: If you want to configure the `DatePicker` yourself, you can configure it any way you want through the `Default Calendar component`.
54 |
55 | ## 🔧 Built With
56 |
57 | * TypeScript
58 | * Sass
59 | * React
60 |
61 | ## 📦 Dependency
62 |
63 | * Moment.js
64 |
65 | In previous versions, moment.js were used. but now it is changed to `Day.js` to because of bundle size issue (#14)
66 |
67 | * [Day.js](https://github.com/iamkun/dayjs)
68 |
69 | `Day.js` is a javascript library for Parse, validate, manipulate, and display dates and times. this component use `Day.js` library to globalize and control date. You can check the locale list through this [link](https://github.com/iamkun/dayjs/tree/dev/src/locale).
70 |
71 | ## 📲 Installation
72 |
73 | ```sh
74 | yarn add @y0c/react-datepicker
75 | # or
76 | npm install --save @y0c/react-datepicker
77 | ```
78 |
79 | ## 💡 Examples
80 |
81 | ### Simple DatePicker
82 |
83 | ```javascript
84 | // import Calendar Component
85 | import React, { Component } from 'react';
86 | import { DatePicker } from '@y0c/react-datepicker';
87 | // import calendar style
88 | // You can customize style by copying asset folder.
89 | import '@y0c/react-datepicker/assets/styles/calendar.scss';
90 |
91 | class DatePickerExample extends Component {
92 |
93 | onChange = (date) => {
94 | // Day.js object
95 | console.log(date);
96 |
97 | // to normal Date object
98 | console.log(date.toDate());
99 | }
100 |
101 | render() {
102 | return (
103 |
104 | )
105 | }
106 | }
107 | ```
108 |
109 | You can find more Exmaples and Demo in story book link
110 |
111 | ## 🌎 i18n
112 |
113 | Features for i18n are provided by Day.js by default.
114 |
115 | see locale list https://github.com/iamkun/dayjs/tree/dev/src/
116 |
117 | and you can customize the locale object
118 |
119 | ```javascript
120 | // use day.js locale
121 | import 'dayjs/locale/ko'
122 |
123 | // delivery prop locale string
124 |
125 |
126 | // or define customize locale object
127 | const locale = {
128 | name: 'ko',
129 | weekdays: '일요일_월요일_화요일_수요일_목요일_금요일_토요일'.split('_'),
130 | weekdaysShort: '일_월_화_수_목_금_토'.split('_'),
131 | months: '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'),
132 | };
133 |
134 | // delivery propr locale object
135 |
136 | ```
137 |
138 | Defaults locale `en`
139 |
140 | ### 🎨 Themeing
141 |
142 | 1. Copy this project asset folder under scss file
143 | 2. Override scss variable you want(_variable.scss)
144 | ( red theme examples )
145 |
146 | ```scss
147 | // red_theme.scss
148 | $base-font-size: 12px;
149 | $title-font-size: 1.3em;
150 |
151 | // override scss variable
152 | $primary-color-dark: #e64a19;
153 | $primary-color: #ff5722;
154 | $primary-color-light: #ffccbc;
155 | $primary-color-text: #ffffff;
156 | $accent-color: #ff5252;
157 | $primary-text-color: #212121;
158 | $secondary-text-color: #757575;
159 | $divider-color: #e4e4e4;
160 | $today-bg-color: #fff9c4;
161 |
162 | // import mixin
163 | @import "../node_modules/@y0c/react-datepicker/assets/styles/_mixin.scss";
164 | // import app scss
165 | // if you want other style customize
166 | // app.scss copy & rewrite !
167 | @import "../node_modules/@y0c/react-datepicker/assets/styles/app.scss";
168 |
169 | ```
170 |
171 | if you want custom css rewrite `app.scss` file
172 |
173 | Try this example!
174 |
175 | [](https://codesandbox.io/s/1rw1lp8w7j)
176 |
177 | ## ⚙️ Local Development
178 |
179 | This component is managed by a `storybook` which is combined with `develop environment` and `documentation`. If you want develop in local environment, clone project and develop through a storybook
180 |
181 | ```sh
182 | # clone this project
183 | git clone https://github.com/y0c/react-datepicker.git
184 | # install dependency
185 | yarn
186 | # start storybook
187 | yarn run storybook
188 | ```
189 | Open your browser and connect http://localhost:6006
190 |
191 | ## 💼 Get Support
192 |
193 | Please fork and use [https://codesandbox.io/s/pw6n17pk57](https://codesandbox.io/s/pw6n17pk57) to reproduce your problem.
194 |
195 | * Open a new issue(Bug or Feature) on [Github](https://github.com/y0c/react-datepicker/issues/new/choose)
196 | * Join the [Gitter room](https://gitter.im/react-datepicker/community) to chat with other developers.
197 |
198 | ## 👨👦👦 Contribution
199 |
200 | Issue and Pull Request are always welcome!
201 |
202 | ## 📝 License
203 | MIT
204 |
205 |
--------------------------------------------------------------------------------
/assets/styles/_mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin inline-center {
2 | display: inline-flex;
3 | align-items: center;
4 | }
--------------------------------------------------------------------------------
/assets/styles/_variable.scss:
--------------------------------------------------------------------------------
1 | $base-font-size: 12px;
2 | $title-font-size: 1.3em;
3 |
4 | $primary-color-dark: #49599a;
5 | $primary-color: #7986cb;
6 | $primary-color-light: #aab6fe;
7 | $primary-color-text: #FFFFFF;
8 | $accent-color: #03A9F4;
9 | $primary-text-color: #212121;
10 | $secondary-text-color: #757575;
11 | $divider-color: #e4e4e4;
12 | $today-bg-color: #FFF9C4;
13 |
--------------------------------------------------------------------------------
/assets/styles/app.scss:
--------------------------------------------------------------------------------
1 |
2 | @import url('https://fonts.googleapis.com/css?family=Lato');
3 |
4 | .rc-backdrop {
5 | position: fixed;
6 | top:0;
7 | left:0;
8 | bottom:0;
9 | right:0;
10 | z-index:80;
11 | &.invert {
12 | background: rgba(1,1,1,.7)
13 | }
14 | }
15 |
16 | .range-picker-input {
17 | display: inline-flex;
18 | border: 1px solid $divider-color;
19 | width: 300px;
20 | * {
21 | box-sizing: border-box;
22 | }
23 | &__icon {
24 | display: inline-flex;
25 | align-items: center;
26 | }
27 | &__start, &__end {
28 | display: inline-flex;
29 | flex: 1;
30 | .picker-input.range {
31 | input {
32 | width: 100%;
33 | border: none;
34 | }
35 | }
36 | }
37 | }
38 |
39 | .picker-input {
40 | display: inline-block;
41 | position: relative;
42 | &__icon {
43 | position:absolute;
44 | top: 50%;
45 | transform: translateY(-50%);
46 | left: 10px;
47 | @include inline-center
48 | }
49 | &__text {
50 | padding: 10px;
51 | border: 1px solid $divider-color;
52 | outline: none;
53 | font-size: $base-font-size * 1.4;
54 | &:disabled {
55 | background: $divider-color;
56 | }
57 | }
58 | &__clear {
59 | position:absolute;
60 | top: 50%;
61 | transform: translateY(-50%);
62 | right: 10px;
63 | cursor: pointer;
64 | }
65 | }
66 |
67 | .picker {
68 | display: inline-block;
69 | &__container {
70 | position: absolute;
71 | z-index:100;
72 | &.portal {
73 | position: fixed;
74 | top: 50%;
75 | left: 50%;
76 | transform: translateX(-50%) translateY(-50%);
77 | }
78 | &__include-time {
79 | border: 1px solid $divider-color;
80 | .calendar__item,
81 | .time__container {
82 | border: none;
83 | }
84 | }
85 | &__tab {
86 | & button {
87 | padding: 5px 10px;
88 | outline: none;
89 | display: inline-flex;
90 | align-items: center;
91 | background: none;
92 | border:none;
93 | border-bottom: 2px solid $divider-color;
94 | &.active {
95 | color: $primary-color-dark;
96 | border-bottom: 2px solid $primary-color-dark;
97 | }
98 | &:first-child {
99 | border-right: none;
100 | }
101 | svg {
102 | margin-right: 5px;
103 | }
104 | }
105 | margin: 10px 0;
106 | }
107 | }
108 | }
109 |
110 |
111 | .time__container {
112 | display: inline-flex;
113 | align-items: center;
114 | border: 1px solid $divider-color;
115 | padding: 15px;
116 | background: white;
117 | font-family: 'Lato';
118 | font-size: $base-font-size;
119 | &__div {
120 | margin: 0 10px;
121 | }
122 | &__type {
123 | display: flex;
124 | flex-direction: column;
125 | margin-left: 10px;
126 | }
127 | }
128 |
129 | .time-input {
130 | display: inline-block;
131 | width: 40px;
132 | overflow: hidden;
133 | &__up, &__down {
134 | border: 1px solid $divider-color;
135 | button {
136 | outline: none;
137 | width: 100%;
138 | cursor: pointer;
139 | border: none;
140 | }
141 | }
142 |
143 | &__text {
144 | width: 100%;
145 | border-left: 1px solid $divider-color;
146 | border-right: 1px solid $divider-color;
147 | box-sizing: border-box;
148 | input {
149 | width: 100%;
150 | box-sizing: border-box;
151 | border: none;
152 | font-size: 15px;
153 | padding: 5px;
154 | text-align: center;
155 | outline: none;
156 | }
157 | }
158 |
159 | }
160 |
161 | .calendar{
162 | display:inline-block;
163 | background: white;
164 | font-size: $base-font-size;
165 | *, *:before, *:after {
166 | box-sizing: border-box;
167 | }
168 |
169 | &__container {
170 | width: 270px;
171 | font-family: 'Roboto', sans-serif;
172 | display:none;
173 | }
174 |
175 | &__list {
176 | display:table;
177 | }
178 |
179 | &__item {
180 | display: table-cell;
181 | border: 1px solid lighten($divider-color,3%);
182 | &:not(:first-child) {
183 | border-left: none !important;
184 | }
185 | }
186 |
187 | &--show {
188 | display:inline-block;
189 | }
190 |
191 | &__head {
192 | position:relative;
193 | background: $primary-color;
194 | padding: 10px 6px;
195 | &--title {
196 | font-size: $title-font-size;
197 | color: white;
198 | text-align: center;
199 | margin: 4px;
200 | }
201 | &--button{
202 | outline: none;
203 | border: none;
204 | cursor: pointer;
205 | background: none;
206 | font-size: 20px;
207 | svg {
208 | fill: white;
209 | }
210 | }
211 | &--prev, &--next {
212 | position: absolute;
213 | top: 0;
214 | bottom: 0;
215 | display: flex;
216 | align-items: center;
217 | }
218 | &--prev {
219 | left:0;
220 | }
221 | &--next {
222 | right:0;
223 | }
224 | }
225 |
226 | &__panel {
227 | &--show {
228 | display: block !important;
229 | }
230 |
231 | &--today {
232 | background: $primary-color-light;
233 | padding: 5px;
234 | display:none;
235 | h2 {
236 | margin: 0;
237 | cursor: pointer;
238 | font-size: $base-font-size;
239 | text-align: center;
240 | }
241 | }
242 | }
243 |
244 | &__body {
245 | &--table{
246 | width: 100%;
247 | table-layout:fixed;
248 | text-align: center;
249 | border-spacing: none;
250 | border-collapse: collapse;
251 | th {
252 | height: 30px;
253 | vertical-align: middle;
254 | color: $primary-text-color;
255 | }
256 | }
257 | }
258 |
259 | &__day {
260 | vertical-align: top;
261 | padding-top:5px;
262 | height: 40px;
263 | &:hover:not(&--disabled) {
264 | background: $primary-color-light;
265 | cursor: pointer;
266 | }
267 | cursor:pointer;
268 | &--0 { color:red; }
269 | &--6 { color:blue; }
270 | &--today{ background: $today-bg-color; }
271 | &--disabled { color: #ddd; cursor: initial}
272 | &--start, &--end, &--selected {
273 | background: $primary-color;
274 | color: $primary-color-text;
275 | &:hover {
276 | background: $primary-color;
277 | }
278 | }
279 | &--range { background: lighten($primary-color-light,10%); }
280 | &--text{
281 | display: block;
282 | font-size: 10px;
283 | }
284 | }
285 |
286 | &__year, &__month {
287 | height: 55px;
288 | vertical-align: middle;
289 | &:hover {
290 | background: $primary-color-light;
291 | cursor: pointer;
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/assets/styles/calendar.scss:
--------------------------------------------------------------------------------
1 | @import "_variable.scss";
2 | @import "_mixin.scss";
3 | @import "app.scss";
--------------------------------------------------------------------------------
/assets/styles/theme/red.scss:
--------------------------------------------------------------------------------
1 | $base-font-size: 12px;
2 | $title-font-size: 1.3em;
3 |
4 | $primary-color-dark: #49599a;
5 | $primary-color: red;
6 | $primary-color-light: #aab6fe;
7 | $primary-color-text: #FFFFFF;
8 | $accent-color: #03A9F4;
9 | $primary-text-color: #212121;
10 | $secondary-text-color: #757575;
11 | $divider-color: #e4e4e4;
12 | $today-bg-color: #FFF9C4;
13 |
14 | @import "../_mixin.scss";
15 | @import "../app.scss";
16 |
--------------------------------------------------------------------------------
/examples/CalendarSelectedController.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as dayjs from 'dayjs';
3 | import Calendar, { Props as ICalendarProps } from '../src/components/Calendar';
4 | import { Omit, Merge } from '../src/utils/TypeUtil';
5 |
6 | type CalendarProps = Merge<
7 | Omit,
8 | {
9 | /** showMonth count at once */
10 | showMonthCnt?: number;
11 | }
12 | >;
13 |
14 | interface IProps {
15 | multiple?: boolean;
16 | }
17 |
18 | interface State {
19 | selected: dayjs.Dayjs[];
20 | }
21 |
22 | type Props = CalendarProps & IProps;
23 | class CalendarSelectedController extends React.Component {
24 | public static defaultProps = {
25 | multiple: false,
26 | };
27 |
28 | public state = {
29 | selected: [],
30 | };
31 |
32 | public handleChange = (date: dayjs.Dayjs) => {
33 | const { multiple } = this.props;
34 | this.setState({
35 | selected: multiple ? [...this.state.selected, date] : [date],
36 | });
37 | };
38 |
39 | public handleClear = () => {
40 | this.setState({
41 | selected: [],
42 | });
43 | };
44 |
45 | public render() {
46 | const { selected } = this.state;
47 | return (
48 |
49 |
50 | {this.props.multiple && Clear }
51 |
52 | );
53 | }
54 | }
55 |
56 | export default CalendarSelectedController;
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@y0c/react-datepicker",
3 | "version": "1.0.4",
4 | "description": "Flexible, Reusable, Mobile friendly DatePicker Component",
5 | "author": "y0c",
6 | "license": "MIT",
7 | "main": "lib/index.js",
8 | "types": "lib/index.d.ts",
9 | "homepage": "https://github.com/y0c/react-datepicker",
10 | "scripts": {
11 | "dev": "cross-env NODE_ENV=dev webpack-dev-server --progress --mode development --config webpack.config.dev.js",
12 | "build": "npx tsc",
13 | "test": "npx cross-env TZ=Asia/Seoul npx jest",
14 | "test:watch": "yarn test --watch",
15 | "test:coverage": "yarn test --coverage && codecov",
16 | "lint": "tslint --project . --config ./tslint.json",
17 | "storybook": "start-storybook -p 6006",
18 | "build-storybook": "build-storybook",
19 | "bump:patch": "yarn version --patch",
20 | "bump:minor": "yarn version --minor",
21 | "bump:major": "yarn version --major",
22 | "prepublish": "yarn lint && yarn test",
23 | "deploy-storybook": "storybook-to-ghpages",
24 | "test:unit:output": "yarn test --json --outputFile=.jest.test.result.json",
25 | "start": "yarn test:unit:output && start-storybook -p 6006"
26 | },
27 | "lint-staged": {
28 | "{src}/**/*.ts*": [
29 | "prettier --write",
30 | "git add"
31 | ]
32 | },
33 | "repository": {
34 | "type": "git",
35 | "url": "git+https://y0c@github.com/y0c/react-datepicker.git"
36 | },
37 | "dependencies": {
38 | "classnames": "^2.2.6",
39 | "dayjs": "^1.8.21",
40 | "react": "^16.8.4",
41 | "react-dom": "^16.8.4"
42 | },
43 | "prettier": {
44 | "printWidth": 100,
45 | "parser": "typescript",
46 | "singleQuote": true,
47 | "useTabs": false,
48 | "tabWidth": 2,
49 | "trailingComma": "es5"
50 | },
51 | "jest": {
52 | "preset": "ts-jest",
53 | "snapshotSerializers": [
54 | "enzyme-to-json/serializer"
55 | ],
56 | "setupFiles": [
57 | "/test-shim.js",
58 | "/test-setup.js"
59 | ],
60 | "collectCoverage": true,
61 | "collectCoverageFrom": [
62 | "**/src/components/**/*.{ts,tsx}",
63 | "**/src/utils/**/*.{ts,tsx}",
64 | "!**/index.ts",
65 | "!**/node_modules/**",
66 | "!**/lib/**",
67 | "!**/examples/**",
68 | "!**/vendor/**"
69 | ],
70 | "moduleFileExtensions": [
71 | "ts",
72 | "tsx",
73 | "js"
74 | ],
75 | "testMatch": [
76 | "**/test/*test.(ts|tsx|js)"
77 | ]
78 | },
79 | "devDependencies": {
80 | "@babel/core": "^7.4.3",
81 | "@emotion/core": "^10.0.10",
82 | "@storybook/addon-actions": "^5.0.6",
83 | "@storybook/addon-info": "^5.0.6",
84 | "@storybook/addon-jest": "^5.0.6",
85 | "@storybook/addon-knobs": "^5.0.6",
86 | "@storybook/addon-links": "^5.0.6",
87 | "@storybook/addon-options": "^5.0.6",
88 | "@storybook/addons": "^5.0.6",
89 | "@storybook/cli": "^5.0.6",
90 | "@storybook/react": "^5.0.6",
91 | "@storybook/storybook-deployer": "^2.8.1",
92 | "@types/classnames": "^2.2.6",
93 | "@types/enzyme": "^3.1.15",
94 | "@types/jest": "^24.0.11",
95 | "@types/lodash": "^4.14.118",
96 | "@types/react": "^16.7.13",
97 | "@types/react-dom": "^16.0.11",
98 | "@types/sinon": "^7.0.3",
99 | "@types/storybook__addon-actions": "^3.4.1",
100 | "@types/storybook__addon-knobs": "^4.0.0",
101 | "@types/storybook__react": "^4.0.0",
102 | "awesome-typescript-loader": "^5.2.1",
103 | "babel-loader": "^8.0.5",
104 | "clean-webpack-plugin": "2.0.1",
105 | "cross-env": "5.2.0",
106 | "css-loader": "2.1.0",
107 | "enzyme": "^3.8.0",
108 | "enzyme-adapter-react-16": "^1.7.1",
109 | "enzyme-to-json": "^3.3.5",
110 | "file-loader": "3.0.1",
111 | "html-webpack-plugin": "3.2.0",
112 | "husky": "^1.3.1",
113 | "jest": "^24.7.0",
114 | "lint-staged": "^8.1.0",
115 | "node-sass": "4.12.0",
116 | "prettier": "^1.15.3",
117 | "prop-types": "^15.6.2",
118 | "react-docgen-typescript-loader": "^3.0.1",
119 | "react-test-renderer": "^16.7.0",
120 | "sass-loader": "7.1.0",
121 | "sinon": "^7.2.2",
122 | "style-loader": "0.23.1",
123 | "ts-jest": "^24.0.1",
124 | "ts-loader": "^5.3.3",
125 | "ts-mockito": "^2.3.1",
126 | "tslint": "^5.12.1",
127 | "tslint-config-airbnb": "^5.11.1",
128 | "tslint-config-prettier": "^1.17.0",
129 | "tslint-react": "^4.0.0",
130 | "typescript": "^3.2.2",
131 | "url-loader": "^1.1.2",
132 | "webpack": "4.28.4",
133 | "webpack-cli": "3.2.1",
134 | "webpack-dev-server": "3.1.14",
135 | "webpack-merge": "4.2.1"
136 | },
137 | "bugs": {
138 | "url": "https://github.com/y0c/react-datepicker/issues"
139 | },
140 | "keywords": [
141 | "react",
142 | "datepicker",
143 | "calendar",
144 | "rangepicker",
145 | "rangedatepicker"
146 | ]
147 | }
148 |
--------------------------------------------------------------------------------
/src/common/@types.ts:
--------------------------------------------------------------------------------
1 | export namespace IDatePicker {
2 | export type Locale = any;
3 | export enum PickerDirection {
4 | TOP,
5 | BOTTOM,
6 | }
7 |
8 | export enum ViewMode {
9 | YEAR,
10 | MONTH,
11 | DAY,
12 | }
13 |
14 | export enum TimeType {
15 | AM = 'AM',
16 | PM = 'PM',
17 | }
18 | export interface Position {
19 | left?: string;
20 | top?: string;
21 | bottom?: string;
22 | right?: string;
23 | }
24 |
25 | export interface SVGIconProps {
26 | size?: string;
27 | className?: string;
28 | color?: string;
29 | onClick?: () => void;
30 | style?: {};
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/common/Constant.ts:
--------------------------------------------------------------------------------
1 | export const DatePickerDefaults = {
2 | dateFormat: 'YYYY-MM-DD',
3 | dateTimeFormat: 'YYYY-MM-DD HH:mm A',
4 | timeFormat: 'HH:mm A',
5 | locale: 'en',
6 | };
7 |
--------------------------------------------------------------------------------
/src/components/Backdrop.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 |
4 | interface Props {
5 | /** Backdrop background color invert option */
6 | invert?: boolean;
7 | /** Backdrop show or hide */
8 | show?: boolean;
9 | /** Backdrop click event */
10 | onClick?: () => void;
11 | }
12 |
13 | const Backdrop: React.FunctionComponent = ({ invert, show, onClick }) => (
14 |
15 | {show &&
}
16 |
17 | );
18 |
19 | export default Backdrop;
20 |
--------------------------------------------------------------------------------
/src/components/Calendar.tsx:
--------------------------------------------------------------------------------
1 | import { range } from '../utils/ArrayUtil';
2 | import * as dayjs from 'dayjs';
3 | import * as React from 'react';
4 | import CalendarContainer, { InheritProps as ContainerProps } from './CalendarContainer';
5 |
6 | export interface Props extends ContainerProps {
7 | /** Calendar Initial Date Parameters */
8 | base: dayjs.Dayjs;
9 | /** Number of months to show at once */
10 | showMonthCnt: number;
11 | }
12 |
13 | export interface State {
14 | base: dayjs.Dayjs;
15 | }
16 |
17 | class Calendar extends React.Component {
18 | public static defaultProps = {
19 | base: dayjs(),
20 | showMonthCnt: 1,
21 | showToday: false,
22 | };
23 |
24 | constructor(props: Props) {
25 | super(props);
26 | this.state = {
27 | base: props.base,
28 | };
29 | }
30 |
31 | public setBase = (base: dayjs.Dayjs) => {
32 | this.setState({ base });
33 | };
34 |
35 | public render() {
36 | const { showMonthCnt } = this.props;
37 | const { base } = this.state;
38 |
39 | return (
40 |
41 |
42 | {range(showMonthCnt).map(idx => (
43 |
44 |
52 |
53 | ))}
54 |
55 |
56 | );
57 | }
58 | }
59 |
60 | export default Calendar;
61 |
--------------------------------------------------------------------------------
/src/components/CalendarBody.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as dayjs from 'dayjs';
3 | import { IDatePicker } from '../common/@types';
4 | import { DatePickerDefaults } from '../common/Constant';
5 | import { getMonthMatrix, getYearMatrix } from '../utils/DateUtil';
6 | import DayView, { Props as DayViewProps } from './DayView';
7 | import TableCell from './TableCell';
8 | import TableMatrixView from './TableMatrixView';
9 |
10 | interface CalendarBodyProps {
11 | /** Calendar viewMode(Year, Month, Day) */
12 | viewMode: IDatePicker.ViewMode;
13 | /** Calendar current Date */
14 | current: dayjs.Dayjs;
15 | /** DayClick Event */
16 | onClick: (value: string) => void;
17 | /** Locale to use */
18 | locale: IDatePicker.Locale;
19 | }
20 | type Props = DayViewProps & CalendarBodyProps;
21 |
22 | const YEAR_VIEW_CLASS = 'calendar__year';
23 | const MONTH_VIEW_CLASS = 'calendar__month';
24 |
25 | const buildMatrixView = (
26 | matrix: string[][],
27 | className: string,
28 | onClick: (key: number, value: string) => () => void
29 | ) => {
30 | return (
31 | (
34 |
35 | )}
36 | />
37 | );
38 | };
39 |
40 | class CalendarBody extends React.Component {
41 | public static defaultProps = {
42 | viewMode: IDatePicker.ViewMode.DAY,
43 | locale: DatePickerDefaults.locale,
44 | };
45 |
46 | public render() {
47 | const { current, onClick, locale } = this.props;
48 | const viewMap = {
49 | [IDatePicker.ViewMode.YEAR]: buildMatrixView(
50 | getYearMatrix(dayjs(current).year()),
51 | YEAR_VIEW_CLASS,
52 | (_, v) => () => onClick(v)
53 | ),
54 | [IDatePicker.ViewMode.MONTH]: buildMatrixView(
55 | getMonthMatrix(locale),
56 | MONTH_VIEW_CLASS,
57 | (k, _) => () => onClick(String(k))
58 | ),
59 | [IDatePicker.ViewMode.DAY]: ,
60 | };
61 |
62 | return {viewMap[this.props.viewMode]}
;
63 | }
64 | }
65 | export default CalendarBody;
66 |
--------------------------------------------------------------------------------
/src/components/CalendarContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as classNames from 'classnames';
2 | import * as dayjs from 'dayjs';
3 | import * as React from 'react';
4 | import { IDatePicker } from '../common/@types';
5 | import CalendarBody from './CalendarBody';
6 | import CalendarHead from './CalendarHead';
7 | import { Props as DayViewProps } from './DayView';
8 | import TodayPanel from './TodayPanel';
9 | import { ifExistCall } from '../utils/FunctionUtil';
10 | import { DatePickerDefaults } from '../common/Constant';
11 | import { getToday } from '../utils/LocaleUtil';
12 |
13 | interface CalendarContainerProps {
14 | /** Locale to use */
15 | locale?: IDatePicker.Locale;
16 | /** Calendar Show or Hide */
17 | show?: boolean;
18 | /** PrevIcon Show or Hide */
19 | prevIcon?: boolean;
20 | /** NextIcon Show or Hide */
21 | nextIcon?: boolean;
22 | /** Event for Calendar day click */
23 | onChange?: (date: dayjs.Dayjs) => void;
24 | /** TodayPanel show or hide */
25 | showToday?: boolean;
26 | }
27 |
28 | interface PrivateProps {
29 | /** CalendarContainer base prop */
30 | current: dayjs.Dayjs;
31 | /** Default Date parameter in calendar, which is the parent component */
32 | base: dayjs.Dayjs;
33 | /** Number of months to show at once */
34 | showMonthCnt: number;
35 | /** Set Calendar initial Date */
36 | setBase: (base: dayjs.Dayjs) => void;
37 | }
38 |
39 | export interface State {
40 | viewMode: IDatePicker.ViewMode;
41 | }
42 |
43 | export type InheritProps = DayViewProps & CalendarContainerProps;
44 | export type Props = CalendarContainerProps & DayViewProps & PrivateProps;
45 |
46 | class CalendarContainer extends React.Component {
47 | public static defaultProps = {
48 | current: dayjs(),
49 | show: true,
50 | showMonthCnt: 1,
51 | showToday: false,
52 | locale: DatePickerDefaults.locale,
53 | };
54 |
55 | public state = {
56 | viewMode: IDatePicker.ViewMode.DAY,
57 | };
58 |
59 | constructor(props: Props) {
60 | super(props);
61 | }
62 |
63 | public getHeaderTitle = () => {
64 | const { current } = this.props;
65 | const year = dayjs(current).year();
66 | return {
67 | [IDatePicker.ViewMode.YEAR]: `${year - 4} - ${year + 5}`,
68 | [IDatePicker.ViewMode.MONTH]: `${year}`,
69 | [IDatePicker.ViewMode.DAY]: dayjs(current).format('YYYY.MM'),
70 | }[this.state.viewMode];
71 | };
72 |
73 | public handleTitleClick = () => {
74 | const { viewMode } = this.state;
75 | const { showMonthCnt } = this.props;
76 | let changedMode: IDatePicker.ViewMode = viewMode;
77 |
78 | if (viewMode === IDatePicker.ViewMode.MONTH) {
79 | changedMode = IDatePicker.ViewMode.YEAR;
80 | } else if (viewMode === IDatePicker.ViewMode.DAY) {
81 | changedMode = IDatePicker.ViewMode.MONTH;
82 | }
83 | this.setState({
84 | viewMode: showMonthCnt > 1 ? IDatePicker.ViewMode.DAY : changedMode,
85 | });
86 | };
87 |
88 | public handleChange = (value: string) => {
89 | const { viewMode } = this.state;
90 | const { current, onChange, setBase, showMonthCnt, base } = this.props;
91 | if (!value.trim()) return;
92 | if (showMonthCnt > 1) {
93 | const date = dayjs(current)
94 | .date(parseInt(value, 10))
95 | .toDate();
96 | ifExistCall(onChange, date);
97 | return;
98 | }
99 |
100 | if (viewMode === IDatePicker.ViewMode.YEAR) {
101 | setBase(dayjs(base).year(parseInt(value, 10)));
102 | this.setState({
103 | viewMode: IDatePicker.ViewMode.MONTH,
104 | });
105 | } else if (viewMode === IDatePicker.ViewMode.MONTH) {
106 | setBase(dayjs(base).month(parseInt(value, 10)));
107 | this.setState({
108 | viewMode: IDatePicker.ViewMode.DAY,
109 | });
110 | } else {
111 | const date = dayjs(current).date(parseInt(value, 10));
112 | ifExistCall(onChange, date);
113 | }
114 | };
115 |
116 | public handleBase = (method: string) => () => {
117 | const { base, setBase } = this.props;
118 | const { viewMode } = this.state;
119 | const date = dayjs(base);
120 | if (viewMode === IDatePicker.ViewMode.YEAR) {
121 | setBase(date[method](10, 'year'));
122 | } else if (viewMode === IDatePicker.ViewMode.MONTH) {
123 | setBase(date[method](1, 'year'));
124 | } else {
125 | setBase(date[method](1, 'month'));
126 | }
127 | };
128 |
129 | public handleToday = () => {
130 | const { setBase } = this.props;
131 | setBase(dayjs());
132 | };
133 |
134 | public renderCalendarHead = () => {
135 | const { prevIcon, nextIcon } = this.props;
136 | return (
137 |
145 | );
146 | };
147 |
148 | public renderTodayPane = () => {
149 | const { showToday, locale = DatePickerDefaults.locale } = this.props;
150 | return ;
151 | };
152 |
153 | public renderCalendarBody = () => {
154 | const {
155 | customDayClass,
156 | customDayText,
157 | disableDay,
158 | selected,
159 | startDay,
160 | endDay,
161 | onMouseOver,
162 | current,
163 | locale = DatePickerDefaults.locale,
164 | } = this.props;
165 |
166 | return (
167 |
180 | );
181 | };
182 |
183 | public render() {
184 | const { show, showToday } = this.props;
185 | const calendarClass = classNames('calendar__container', {
186 | 'calendar--show': show,
187 | });
188 |
189 | return (
190 |
191 | {this.renderCalendarHead()}
192 | {showToday && this.renderTodayPane()}
193 | {this.renderCalendarBody()}
194 |
195 | );
196 | }
197 | }
198 |
199 | export default CalendarContainer;
200 |
--------------------------------------------------------------------------------
/src/components/CalendarHead.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import SVGIcon from './SVGIcon';
3 |
4 | interface Props {
5 | /** Prev button click event */
6 | onPrev?: () => void;
7 | /** Next button click event */
8 | onNext?: () => void;
9 | /** Calenar Title Click Event */
10 | onTitleClick?: () => void;
11 | /** Prev Icon show or Hide */
12 | prevIcon?: boolean;
13 | /** Next icon show or hide */
14 | nextIcon?: boolean;
15 | /** Title to show in calendar */
16 | title?: string;
17 | }
18 |
19 | const defaultProps = {
20 | title: '',
21 | };
22 |
23 | const CalendarHead: React.FunctionComponent = ({
24 | onPrev,
25 | onNext,
26 | prevIcon,
27 | nextIcon,
28 | title,
29 | onTitleClick,
30 | }) => {
31 | return (
32 |
33 |
34 | {prevIcon && (
35 |
36 |
37 |
38 | )}
39 |
40 |
41 | {title}
42 |
43 |
44 | {nextIcon && (
45 |
46 |
47 |
48 | )}
49 |
50 |
51 | );
52 | };
53 |
54 | CalendarHead.defaultProps = defaultProps;
55 |
56 | export default CalendarHead;
57 |
--------------------------------------------------------------------------------
/src/components/DatePicker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as dayjs from 'dayjs';
3 | import * as customParseFormat from 'dayjs/plugin/customParseFormat';
4 | import * as CX from 'classnames';
5 | import Calendar, { Props as ICalendarProps } from './Calendar';
6 | import TimeContainer from './TimeContainer';
7 | import Picker, { PickerProps, PickerAction } from './Picker';
8 | import { Omit, Merge } from '../utils/TypeUtil';
9 | import { ifExistCall } from '../utils/FunctionUtil';
10 | import { formatDate } from '../utils/DateUtil';
11 | import { DatePickerDefaults } from '../common/Constant';
12 | import PickerInput, { Props as InputProps } from './PickerInput';
13 | import SVGIcon from './SVGIcon';
14 |
15 | export enum TabValue {
16 | DATE,
17 | TIME,
18 | }
19 |
20 | interface DatePickerProps {
21 | /** To display input format (dayjs format) */
22 | dateFormat?: string;
23 | /** include TimePicker true/false */
24 | includeTime?: boolean;
25 | /** show time only */
26 | showTimeOnly?: boolean;
27 | /** Initial display date */
28 | initialDate?: dayjs.Dayjs;
29 | /** Override InputComponent */
30 | inputComponent?: (props: InputProps) => JSX.Element;
31 | /** DatePicker value change Event */
32 | onChange?: (date: dayjs.Dayjs, rawValue: string) => void;
33 | /** DatePicker Input default Icon */
34 | showDefaultIcon: boolean;
35 | }
36 |
37 | export interface State {
38 | tabValue: TabValue;
39 | date?: dayjs.Dayjs;
40 | inputValue: string;
41 | selected: dayjs.Dayjs[];
42 | }
43 |
44 | type CalendarProps = Merge<
45 | Omit,
46 | {
47 | /** showMonth count at once */
48 | showMonthCnt?: number;
49 | }
50 | >;
51 |
52 | export type Props = DatePickerProps & Omit & CalendarProps & PickerProps;
53 |
54 | class DatePicker extends React.Component {
55 | public static defaultProps = {
56 | includeTime: false,
57 | showMonthCnt: 1,
58 | locale: DatePickerDefaults.locale,
59 | portal: false,
60 | showDefaultIcon: false,
61 | };
62 |
63 | constructor(props: Props) {
64 | super(props);
65 | dayjs.extend(customParseFormat);
66 | const { initialDate, includeTime, showTimeOnly } = this.props;
67 | const selected = [];
68 | let date;
69 |
70 | if (initialDate) {
71 | date = initialDate;
72 | selected.push(date);
73 | }
74 |
75 | if (includeTime && showTimeOnly) {
76 | throw new Error('incldueTime & showTimeOnly cannot be used together');
77 | }
78 |
79 | this.state = {
80 | date,
81 | selected,
82 | tabValue: TabValue.DATE,
83 | inputValue: formatDate(date, this.getDateFormat()),
84 | };
85 | }
86 |
87 | public getDateFormat() {
88 | const { dateFormat, includeTime, showTimeOnly } = this.props;
89 |
90 | if (!dateFormat) {
91 | if (includeTime) {
92 | return DatePickerDefaults.dateTimeFormat;
93 | }
94 | if (showTimeOnly) {
95 | return DatePickerDefaults.timeFormat;
96 | }
97 | return DatePickerDefaults.dateFormat;
98 | }
99 | return dateFormat;
100 | }
101 |
102 | public handleDateChange = (date: dayjs.Dayjs) => {
103 | const { onChange } = this.props;
104 | const value = dayjs(date).format(this.getDateFormat());
105 |
106 | ifExistCall(onChange, date, value);
107 |
108 | this.setState({
109 | ...this.state,
110 | date,
111 | inputValue: value,
112 | selected: [date],
113 | });
114 | };
115 |
116 | public handleTimeChange = (hour: number, minute: number) => {
117 | const { onChange } = this.props;
118 | let date = this.state.date;
119 | let selected = this.state.selected;
120 |
121 | if (!date) {
122 | date = dayjs();
123 | selected = [date];
124 | }
125 |
126 | date = date.hour(hour).minute(minute);
127 | const inputValue = date.format(this.getDateFormat());
128 |
129 | ifExistCall(onChange, date, inputValue);
130 |
131 | this.setState({
132 | ...this.state,
133 | date,
134 | selected,
135 | inputValue,
136 | });
137 | };
138 |
139 | public handleInputChange = (e: React.FormEvent) => {
140 | const { onChange } = this.props;
141 | const value = e.currentTarget.value;
142 |
143 | ifExistCall(onChange, value, undefined);
144 |
145 | this.setState({
146 | ...this.state,
147 | inputValue: e.currentTarget.value,
148 | });
149 | };
150 |
151 | public handleInputClear = () => {
152 | const { onChange } = this.props;
153 |
154 | ifExistCall(onChange, '', undefined);
155 |
156 | this.setState({
157 | ...this.state,
158 | inputValue: '',
159 | });
160 | };
161 |
162 | public handleInputBlur = (e: React.FormEvent) => {
163 | const { date } = this.state;
164 | const value = e.currentTarget.value;
165 | const parsedDate = dayjs(value, this.getDateFormat());
166 | let updateDate: dayjs.Dayjs | undefined;
167 |
168 | updateDate = date;
169 |
170 | if (dayjs(parsedDate).isValid()) {
171 | updateDate = parsedDate;
172 | }
173 |
174 | this.setState({
175 | ...this.state,
176 | date: updateDate,
177 | inputValue: dayjs(updateDate).format(this.getDateFormat()),
178 | });
179 | };
180 |
181 | public renderInputComponent = (): JSX.Element => {
182 | const { inputComponent, readOnly, disabled, clear, autoFocus, showDefaultIcon, placeholder } = this.props;
183 | const { inputValue } = this.state;
184 | const inputProps = {
185 | readOnly,
186 | autoFocus,
187 | disabled,
188 | clear,
189 | placeholder,
190 | onChange: this.handleInputChange,
191 | onClear: this.handleInputClear,
192 | onBlur: this.handleInputBlur,
193 | value: inputValue,
194 | icon: showDefaultIcon ? : undefined
195 | };
196 | return inputComponent ? inputComponent({ ...inputProps }) : ;
197 | };
198 |
199 | public handleTab = (val: TabValue) => () => {
200 | this.setState({
201 | ...this.state,
202 | tabValue: val,
203 | });
204 | };
205 |
206 | public renderTabMenu = (): JSX.Element | null => {
207 | const { tabValue } = this.state;
208 |
209 | const renderButton = (type: TabValue, label: string, icon: string) => (
210 |
217 |
218 | {label}
219 |
220 | );
221 | return (
222 |
223 | {renderButton(TabValue.DATE, 'DATE', 'calendar')}
224 | {renderButton(TabValue.TIME, 'TIME', 'time')}
225 |
226 | );
227 | };
228 |
229 | public renderCalendar = (actions: PickerAction): JSX.Element | null => {
230 | const { selected, date } = this.state;
231 | return (
232 | {
236 | this.handleDateChange(e);
237 | actions.hide();
238 | }}
239 | selected={selected}
240 | />
241 | );
242 | };
243 |
244 | public renderTime = (): JSX.Element | null => {
245 | const date = this.state.date || dayjs();
246 |
247 | return (
248 |
249 | );
250 | };
251 |
252 | public renderContents = (actions: PickerAction): JSX.Element => {
253 | const { includeTime, showTimeOnly } = this.props;
254 | const { tabValue } = this.state;
255 | let component: JSX.Element;
256 |
257 | component = {this.renderCalendar(actions)}
;
258 |
259 | if (showTimeOnly) {
260 | component = {this.renderTime()}
;
261 | }
262 |
263 | if (includeTime) {
264 | component = (
265 |
266 | {this.renderTabMenu()}
267 | {tabValue === TabValue.DATE ? this.renderCalendar(actions) : this.renderTime()}
268 |
269 | );
270 | }
271 | return component;
272 | };
273 |
274 | public render() {
275 | const { includeTime, portal, direction, disabled, readOnly } = this.props;
276 |
277 | return (
278 | this.renderInputComponent()}
285 | renderContents={({ actions }) => this.renderContents(actions)}
286 | />
287 | );
288 | }
289 | }
290 |
291 | export default DatePicker;
292 |
--------------------------------------------------------------------------------
/src/components/DayView.tsx:
--------------------------------------------------------------------------------
1 | import * as classNames from 'classnames';
2 | import * as dayjs from 'dayjs';
3 | import * as React from 'react';
4 | import { DatePickerDefaults } from '../common/Constant';
5 | import TableCell from './TableCell';
6 | import TableMatrixView from './TableMatrixView';
7 | import { ifExistCall } from '../utils/FunctionUtil';
8 | import { getWeekDays } from '../utils/LocaleUtil';
9 |
10 | import { getDayMatrix, isDayEqual, isDayRange } from '../utils/DateUtil';
11 | import { IDatePicker } from '../common/@types';
12 |
13 | export interface Props {
14 | /** Selected days to show in calendar */
15 | selected?: dayjs.Dayjs[];
16 | /** Start day to show in calendar */
17 | startDay?: dayjs.Dayjs;
18 | /** End day to show in calendar */
19 | endDay?: dayjs.Dayjs;
20 | /** Calendar day click Event */
21 | onClick?: (date: string) => void;
22 | /** Calendar day Mouseover Event */
23 | onMouseOver?: (date: dayjs.Dayjs) => void;
24 | /** Custom day class to show in day */
25 | customDayClass?: (date: dayjs.Dayjs) => string | string[];
26 | /** Custom day text to show in day */
27 | customDayText?: (date: dayjs.Dayjs) => string;
28 | /** Calendar day disable */
29 | disableDay?: (date: dayjs.Dayjs) => void;
30 | }
31 |
32 | interface PrivateProps {
33 | current: dayjs.Dayjs;
34 | locale: IDatePicker.Locale;
35 | }
36 |
37 | class DayView extends React.Component {
38 | public static defaultProps = {
39 | locale: DatePickerDefaults.locale,
40 | };
41 |
42 | public getDayClass = (date: string): string => {
43 | const { current, customDayClass, startDay, endDay, selected, disableDay } = this.props;
44 | const currentDate = dayjs(current).date(parseInt(date, 10));
45 |
46 | let classArr: string[] = [];
47 |
48 | if (!date.trim()) {
49 | return '';
50 | }
51 |
52 | if (customDayClass !== undefined) {
53 | const customClass = customDayClass(currentDate);
54 | classArr = classArr.concat(typeof customClass === 'string' ? [customClass] : customClass);
55 | }
56 |
57 | const dayClass = classNames(
58 | 'calendar__day',
59 | `calendar__day--${dayjs(currentDate).day()}`,
60 | classArr,
61 | {
62 | 'calendar__day--end': isDayEqual(currentDate, endDay),
63 | 'calendar__day--range': isDayRange(currentDate, startDay, endDay),
64 | 'calendar__day--selected': this.isIncludeDay(date, selected),
65 | 'calendar__day--disabled': disableDay ? disableDay(currentDate) : false,
66 | 'calendar__day--start': isDayEqual(currentDate, startDay),
67 | 'calendar__day--today': isDayEqual(currentDate, dayjs()),
68 | }
69 | );
70 |
71 | return dayClass;
72 | };
73 |
74 | public getCustomText = (date: string): string => {
75 | const { current, customDayText } = this.props;
76 | const currentDate = dayjs(current).date(parseInt(date, 10));
77 |
78 | if (!date.trim()) {
79 | return '';
80 | }
81 | if (!customDayText) {
82 | return '';
83 | }
84 |
85 | return customDayText(currentDate);
86 | };
87 |
88 | public isIncludeDay = (date: string, dates?: dayjs.Dayjs[]): boolean => {
89 | const { current } = this.props;
90 | if (dates === undefined) {
91 | return false;
92 | }
93 | return dates.some(v => isDayEqual(dayjs(current).date(parseInt(date, 10)), v));
94 | };
95 |
96 | public handleClick = (date: string) => {
97 | const { current, disableDay } = this.props;
98 | const currentDate = dayjs(current).date(parseInt(date, 10));
99 | if (!(disableDay && disableDay(currentDate))) {
100 | ifExistCall(this.props.onClick, date);
101 | }
102 | };
103 |
104 | public handleMouseOver = (date: string) => {
105 | const { onMouseOver, current } = this.props;
106 | ifExistCall(onMouseOver, dayjs(current).date(parseInt(date, 10)));
107 | };
108 |
109 | public render() {
110 | const { current, locale } = this.props;
111 |
112 | const dayMatrix = getDayMatrix(dayjs(current).year(), dayjs(current).month());
113 | const weekdays = getWeekDays(locale);
114 |
115 | return (
116 | (
120 |
128 | )}
129 | />
130 | );
131 | }
132 | }
133 |
134 | export default DayView;
135 |
--------------------------------------------------------------------------------
/src/components/Picker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as CX from 'classnames';
3 | import { IDatePicker } from '../common/@types';
4 | import { getDivPosition, getDomHeight } from '../utils/DOMUtil';
5 | import Backdrop from './Backdrop';
6 |
7 | export interface PickerAction {
8 | show: () => void;
9 | hide: () => void;
10 | }
11 |
12 | export interface PickerRenderProps {
13 | actions: PickerAction;
14 | }
15 |
16 | export interface PickerProps {
17 | /** DatePicker portal version */
18 | portal?: boolean;
19 | /** DatePicker show direction (0 = TOP , 1 = BOTTOM) */
20 | direction?: IDatePicker.PickerDirection;
21 | }
22 |
23 | export interface Props {
24 | readOnly?: boolean;
25 | disabled?: boolean;
26 | className?: string;
27 | renderTrigger: (props: PickerRenderProps) => JSX.Element;
28 | renderContents: (props: PickerRenderProps) => JSX.Element;
29 | }
30 |
31 | export interface State {
32 | show: boolean;
33 | position: IDatePicker.Position;
34 | }
35 | class Picker extends React.Component {
36 | public state = {
37 | show: false,
38 | position: {
39 | left: '',
40 | top: '',
41 | },
42 | };
43 |
44 | private triggerRef: React.RefObject;
45 | private contentsRef: React.RefObject;
46 |
47 | constructor(props: Props) {
48 | super(props);
49 | this.triggerRef = React.createRef();
50 | this.contentsRef = React.createRef();
51 | }
52 |
53 | public showContents = () => {
54 | const { portal, disabled, readOnly } = this.props;
55 | if (disabled || readOnly) return;
56 |
57 | this.setState(
58 | {
59 | show: true,
60 | },
61 | () => {
62 | if (!portal) {
63 | this.setPosition();
64 | }
65 | }
66 | );
67 | };
68 |
69 | public hideContents = () => {
70 | this.setState({
71 | show: false,
72 | });
73 | };
74 |
75 | public setPosition = () => {
76 | const { direction } = this.props;
77 | this.setState({
78 | position: getDivPosition(
79 | this.triggerRef.current,
80 | direction,
81 | getDomHeight(this.contentsRef.current)
82 | ),
83 | });
84 | };
85 |
86 | public render() {
87 | const { portal, className, renderTrigger, renderContents } = this.props;
88 | const { show, position } = this.state;
89 | const actions = {
90 | show: this.showContents,
91 | hide: this.hideContents,
92 | };
93 |
94 | return (
95 |
96 |
97 | {renderTrigger({ actions })}
98 |
99 | {show && (
100 |
107 | {renderContents({ actions })}
108 |
109 | )}
110 |
111 |
112 | );
113 | }
114 | }
115 |
116 | export default Picker;
117 |
--------------------------------------------------------------------------------
/src/components/PickerInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 | import SVGIcon from './SVGIcon';
4 |
5 | export interface Props {
6 | /** Picker Input value */
7 | value?: string;
8 | /** Picker Input Readonly */
9 | readOnly?: boolean;
10 | /** Picker Input Disabled */
11 | disabled?: boolean;
12 | /** Picker Input clear icon */
13 | clear?: boolean;
14 | /** Picker Input show icon */
15 | icon?: JSX.Element;
16 | /** Picker Input onChange Event */
17 | onChange: (e: React.FormEvent) => void;
18 | /** Picker Input onBlur Event */
19 | onBlur?: (e: React.FormEvent) => void;
20 | /** Picker Input onClick Event */
21 | onClick?: () => void;
22 | /** Picker Input onClear Event */
23 | onClear?: () => void;
24 | /** Picker Input Auto Focus */
25 | autoFocus?: boolean;
26 | /** Picker Input ClassName */
27 | className?: string;
28 | /** Picker Input Placeholder */
29 | placeholder?: string;
30 | }
31 |
32 | class PickerInput extends React.Component {
33 | public inputRef: React.RefObject;
34 |
35 | constructor(props: Props) {
36 | super(props);
37 | this.inputRef = React.createRef();
38 | }
39 |
40 | public componentDidMount() {
41 | const { current } = this.inputRef;
42 | const { autoFocus } = this.props;
43 |
44 | if (current && autoFocus) {
45 | current.focus();
46 | }
47 | }
48 |
49 | public handleClear = (e: React.MouseEvent) => {
50 | const { onClear } = this.props;
51 | if (onClear) onClear();
52 | e.stopPropagation();
53 | };
54 |
55 | public renderInput = () => {
56 | const {
57 | readOnly = false,
58 | disabled = false,
59 | value = '',
60 | icon,
61 | onChange,
62 | onClick,
63 | onBlur,
64 | placeholder,
65 | } = this.props;
66 |
67 | return (
68 |
83 | );
84 | };
85 |
86 | public renderClear = () => {
87 | return (
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | public render() {
95 | const { clear, icon, className } = this.props;
96 | return (
97 |
98 | {icon && {icon} }
99 | {this.renderInput()}
100 | {clear && this.renderClear()}
101 |
102 | );
103 | }
104 | }
105 |
106 | export default PickerInput;
107 |
--------------------------------------------------------------------------------
/src/components/RangeDatePicker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as dayjs from 'dayjs';
3 | import { isDayAfter, isDayBefore, isDayEqual, isDayRange, formatDate } from '../utils/DateUtil';
4 | import { DatePickerDefaults } from '../common/Constant';
5 | import Picker, { PickerProps, PickerAction } from './Picker';
6 | import RangePickerInput, { FieldType, InputProps } from './RangePickerInput';
7 | import Calendar, { Props as ICalendarProps } from './Calendar';
8 | import { Merge, Omit } from '../utils/TypeUtil';
9 | import { ifExistCall } from '../utils/FunctionUtil';
10 |
11 | interface RangeDatePickerProps {
12 | /** To display input format (Day.js format) */
13 | dateFormat: string;
14 | /** Initial Calendar base date(if start date not set) */
15 | initialDate: dayjs.Dayjs;
16 | /** Initial Start Date */
17 | initialStartDate?: dayjs.Dayjs;
18 | /** Initial End Date */
19 | initialEndDate?: dayjs.Dayjs;
20 | /** RangeDatePicker change event */
21 | onChange?: (start?: dayjs.Dayjs, end?: dayjs.Dayjs) => void;
22 | /** start day display this prop(optional) */
23 | startText: string;
24 | /** end day display this prop(optional) */
25 | endText: string;
26 | /** calendar wrapping element */
27 | wrapper?: (calendar: JSX.Element) => JSX.Element;
28 | }
29 |
30 | export interface State {
31 | start?: dayjs.Dayjs;
32 | end?: dayjs.Dayjs;
33 | hoverDate?: dayjs.Dayjs;
34 | startValue: string;
35 | endValue: string;
36 | mode?: FieldType;
37 | }
38 |
39 | type CalendarProps = Merge<
40 | Omit,
41 | {
42 | showMonthCnt?: number;
43 | }
44 | >;
45 |
46 | export type Props = RangeDatePickerProps & CalendarProps & InputProps & PickerProps;
47 |
48 | class RangeDatePicker extends React.Component {
49 | public static defaultProps = {
50 | dateFormat: DatePickerDefaults.dateFormat,
51 | portal: false,
52 | initialDate: dayjs(),
53 | showMonthCnt: 2,
54 | startText: '',
55 | endText: '',
56 | };
57 |
58 | public constructor(props: Props) {
59 | super(props);
60 | const { dateFormat, initialStartDate, initialEndDate } = props;
61 | const start = initialStartDate;
62 | const end = initialEndDate;
63 |
64 | this.state = {
65 | start,
66 | end,
67 | startValue: formatDate(start, dateFormat),
68 | endValue: formatDate(end, dateFormat),
69 | };
70 | }
71 |
72 | public handleDateChange = (actions: PickerAction) => (date: dayjs.Dayjs) => {
73 | const { onChange, dateFormat } = this.props;
74 | const { start, end } = this.state;
75 | let startDate: dayjs.Dayjs | undefined;
76 | let endDate: dayjs.Dayjs | undefined;
77 |
78 | startDate = start;
79 | endDate = end;
80 |
81 | if (!start) {
82 | startDate = date;
83 | } else {
84 | if (end) {
85 | startDate = date;
86 | endDate = undefined;
87 | } else {
88 | if (!isDayBefore(date, start)) {
89 | endDate = date;
90 | } else {
91 | startDate = date;
92 | }
93 | }
94 | }
95 |
96 | ifExistCall(onChange, startDate, endDate);
97 |
98 | this.setState(
99 | {
100 | ...this.state,
101 | start: startDate,
102 | end: endDate,
103 | startValue: formatDate(startDate, dateFormat),
104 | endValue: formatDate(endDate, dateFormat),
105 | },
106 | () => {
107 | if (this.state.start && this.state.end) {
108 | actions.hide();
109 | }
110 | }
111 | );
112 | };
113 |
114 | public handleInputChange = (fieldType: FieldType, value: string) => {
115 | const key = fieldType === FieldType.START ? 'startValue' : 'endValue';
116 | this.setState({
117 | ...this.state,
118 | [key]: value,
119 | });
120 | };
121 |
122 | public handleMouseOver = (date: dayjs.Dayjs) => {
123 | this.setState({
124 | ...this.state,
125 | hoverDate: date,
126 | });
127 | };
128 |
129 | public handleInputBlur = (fieldType: FieldType, value: string) => {
130 | const { dateFormat } = this.props;
131 | const { start, end } = this.state;
132 | const parsedDate = dayjs(value, dateFormat);
133 | let startDate = start;
134 | let endDate = end;
135 |
136 | if (parsedDate.isValid() && dateFormat.length === value.length) {
137 | if (fieldType === FieldType.END) {
138 | endDate = parsedDate;
139 | } else if (fieldType === FieldType.START) {
140 | startDate = parsedDate;
141 | }
142 | }
143 |
144 | if (startDate && endDate) {
145 | if (isDayBefore(endDate, startDate) || isDayAfter(startDate, endDate)) {
146 | // Swapping Date
147 | let temp: dayjs.Dayjs;
148 | temp = startDate;
149 | startDate = endDate;
150 | endDate = temp;
151 | }
152 | }
153 |
154 | this.setState({
155 | ...this.state,
156 | start: startDate,
157 | end: endDate,
158 | startValue: formatDate(startDate, dateFormat),
159 | endValue: formatDate(endDate, dateFormat),
160 | });
161 | };
162 |
163 | public handleCalendarText = (date: dayjs.Dayjs) => {
164 | const { startText, endText, customDayText } = this.props;
165 | const { start, end } = this.state;
166 | if (isDayEqual(start, date)) return startText;
167 | if (isDayEqual(end, date)) return endText;
168 | ifExistCall(customDayText, date);
169 | return '';
170 | };
171 |
172 | public handleCalendarClass = (date: dayjs.Dayjs) => {
173 | const { customDayClass } = this.props;
174 | const { start, end, hoverDate } = this.state;
175 | if (start && !end && hoverDate) {
176 | if (isDayRange(date, start, hoverDate)) {
177 | return 'calendar__day--range';
178 | }
179 | }
180 | ifExistCall(customDayClass, date);
181 | return '';
182 | };
183 |
184 | public handleInputClear = (fieldType: FieldType) => {
185 | if (fieldType === FieldType.START) {
186 | this.setState({
187 | ...this.state,
188 | start: undefined,
189 | startValue: '',
190 | });
191 | } else if (fieldType === FieldType.END) {
192 | this.setState({
193 | ...this.state,
194 | end: undefined,
195 | endValue: '',
196 | });
197 | }
198 | };
199 |
200 | public renderRangePickerInput = () => {
201 | const { startPlaceholder, endPlaceholder, readOnly, disabled, clear, onChange } = this.props;
202 | const { startValue, endValue } = this.state;
203 | return (
204 |
216 | );
217 | };
218 |
219 | public renderCalendar = (actions: PickerAction) => {
220 | const { showMonthCnt, initialDate, wrapper } = this.props;
221 | const { start, end } = this.state;
222 | let component: JSX.Element;
223 |
224 | const calendar = (
225 |
236 | );
237 |
238 | component = calendar;
239 |
240 | if (wrapper) {
241 | component = wrapper(calendar);
242 | }
243 |
244 | return component;
245 | };
246 |
247 | public render() {
248 | const { portal, direction, disabled, readOnly } = this.props;
249 |
250 | return (
251 | this.renderRangePickerInput()}
257 | renderContents={({ actions }) => this.renderCalendar(actions)}
258 | />
259 | );
260 | }
261 | }
262 |
263 | export default RangeDatePicker;
264 |
--------------------------------------------------------------------------------
/src/components/RangePickerInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PickerInput, { Props as IPickerInputProps } from './PickerInput';
3 | import { Merge, Omit } from '../utils/TypeUtil';
4 | import { ifExistCall } from '../utils/FunctionUtil';
5 | import SVGIcon from './SVGIcon';
6 |
7 | export enum FieldType {
8 | START,
9 | END,
10 | }
11 |
12 | interface RangePickerInputProps {
13 | /** start input value */
14 | startValue?: string;
15 | /** end input value */
16 | endValue?: string;
17 | /** RangePickerInput change event field type is start or end */
18 | onChange?: (fieldType: FieldType, value: string) => void;
19 | /** RangePickerInput Blur event field type is start or end */
20 | onBlur?: (fieldType: FieldType, value: string) => void;
21 | /** RangePickerInput click event field type is start or end */
22 | onClick?: (fieldTyp: FieldType) => void;
23 | /** RangePickerInput clear event */
24 | onClear?: (fieldType: FieldType) => void;
25 | }
26 |
27 | export type InputProps = Merge<
28 | Omit,
29 | {
30 | /** start input placeholder */
31 | startPlaceholder?: string;
32 | /** end input placeholder */
33 | endPlaceholder?: string;
34 | }
35 | >;
36 |
37 | type Props = RangePickerInputProps & InputProps;
38 |
39 | class RangePickerInput extends React.Component {
40 | public handleChange = (fieldType: FieldType) => (e: React.FormEvent) =>
41 | ifExistCall(this.props.onChange, fieldType, e.currentTarget.value);
42 | public handleBlur = (fieldType: FieldType) => (e: React.FormEvent) =>
43 | ifExistCall(this.props.onBlur, fieldType, e.currentTarget.value);
44 | public handleClick = (fieldType: FieldType) => () => ifExistCall(this.props.onClick, fieldType);
45 | public handleClear = (fieldType: FieldType) => () => ifExistCall(this.props.onClear, fieldType);
46 |
47 | public renderStartInput = () => {
48 | const { startValue, startPlaceholder } = this.props;
49 | return this.renderPickerInput(FieldType.START, startValue, startPlaceholder);
50 | };
51 |
52 | public renderEndInput = () => {
53 | const { endValue, endPlaceholder } = this.props;
54 | return this.renderPickerInput(FieldType.END, endValue, endPlaceholder);
55 | };
56 |
57 | public renderPickerInput = (fieldType: FieldType, value?: string, placeholder?: string) => {
58 | const { readOnly, disabled, clear } = this.props;
59 | return (
60 |
72 | );
73 | };
74 |
75 | public render() {
76 | return (
77 |
78 | {this.renderStartInput()}
79 |
80 |
81 |
82 | {this.renderEndInput()}
83 |
84 | );
85 | }
86 | }
87 |
88 | export default RangePickerInput;
89 |
--------------------------------------------------------------------------------
/src/components/SVGIcon/IconBase.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IDatePicker } from '../../common/@types';
3 |
4 | const IconBase: React.FunctionComponent = props => (
5 |
13 | {props.children}
14 |
15 | );
16 |
17 | export default IconBase;
18 |
--------------------------------------------------------------------------------
/src/components/SVGIcon/Icons.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IDatePicker } from '../../common/@types';
3 | import IconBase from './IconBase';
4 |
5 | const Calendar: React.FunctionComponent = props => (
6 |
7 |
8 |
9 |
10 | );
11 |
12 | const Clear: React.FunctionComponent = props => (
13 |
14 |
15 |
16 |
17 | );
18 |
19 | const LeftArrow: React.FunctionComponent = props => (
20 |
21 |
22 |
23 |
24 | );
25 |
26 | const RightArrow: React.FunctionComponent = props => (
27 |
28 |
29 |
30 |
31 | );
32 |
33 | const Time: React.FunctionComponent = props => (
34 |
35 |
36 |
37 |
38 | );
39 |
40 | const Up: React.FunctionComponent = props => (
41 |
42 |
43 |
44 |
45 | );
46 |
47 | const Down: React.FunctionComponent = props => (
48 |
49 |
50 |
51 |
52 | );
53 | export { Calendar, Clear, LeftArrow, RightArrow, Time, Up, Down };
54 |
--------------------------------------------------------------------------------
/src/components/SVGIcon/SVGIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IDatePicker } from '../../common/@types';
3 | import { Calendar, Clear, Down, LeftArrow, RightArrow, Time, Up} from './Icons';
4 |
5 | interface Props extends IDatePicker.SVGIconProps {
6 | id: string;
7 | }
8 |
9 | const SVGIcon: React.FunctionComponent = props => {
10 | const iconMap = {
11 | 'calendar': Calendar,
12 | 'clear': Clear,
13 | 'time': Time,
14 | 'left-arrow': LeftArrow,
15 | 'right-arrow': RightArrow,
16 | 'down': Down,
17 | 'up': Up,
18 | };
19 |
20 | const Icon = iconMap[props.id];
21 |
22 | return ;
23 | };
24 |
25 | SVGIcon.defaultProps = {
26 | size: '16',
27 | color: 'currentColor',
28 | };
29 |
30 | export default SVGIcon;
31 |
--------------------------------------------------------------------------------
/src/components/SVGIcon/index.tsx:
--------------------------------------------------------------------------------
1 | import SVGIcon from './SVGIcon';
2 |
3 | export default SVGIcon;
4 |
--------------------------------------------------------------------------------
/src/components/TableCell.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { ifExistCall } from '../utils/FunctionUtil';
3 |
4 | interface Props {
5 | className?: string;
6 | text?: string;
7 | subText?: string;
8 | onClick?: (text: string) => void;
9 | onMouseOver?: (text: string) => void;
10 | }
11 |
12 | const Cell: React.FunctionComponent = ({ className, text, subText, onClick, onMouseOver }) => {
13 | return (
14 | ifExistCall(onClick, text)}
16 | onMouseOver={() => ifExistCall(onMouseOver, text)}
17 | className={className}
18 | >
19 | {text}
20 | {subText && {subText} }
21 |
22 | );
23 | };
24 |
25 | export default Cell;
26 |
--------------------------------------------------------------------------------
/src/components/TableMatrixView.tsx:
--------------------------------------------------------------------------------
1 | import * as classNames from 'classnames';
2 | import * as React from 'react';
3 |
4 | interface Props {
5 | matrix: string[][];
6 | headers?: string[];
7 | className?: string;
8 | cell: (value: string, key: number) => JSX.Element;
9 | }
10 |
11 | const TableMatrixView: React.FunctionComponent = ({ className, matrix, cell, headers }) => {
12 | return (
13 |
14 | {headers && (
15 |
16 |
17 | {headers.map((v, i) => (
18 | {v}
19 | ))}
20 |
21 |
22 | )}
23 |
24 | {matrix.map((row, i) => (
25 | {row.map((v, j) => cell(v, i * matrix[i].length + j))}
26 | ))}
27 |
28 |
29 | );
30 | };
31 |
32 | export default TableMatrixView;
33 |
--------------------------------------------------------------------------------
/src/components/TimeContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import TimeInput from './TimeInput';
3 | import { ifExistCall } from '../utils/FunctionUtil';
4 |
5 | interface Props {
6 | /** hour to display */
7 | hour?: number;
8 | /** minute to display */
9 | minute?: number;
10 | /** hour, minute, type change event */
11 | onChange?: (hour: number, minute: number) => void;
12 | /** hour, minute blur event */
13 | onBlur?: (hour: number, minute: number) => void;
14 | }
15 |
16 | interface State {
17 | hour: number;
18 | minute: number;
19 | }
20 |
21 | class TimeContainer extends React.Component {
22 | public state = {
23 | hour: this.props.hour || 0,
24 | minute: this.props.minute || 0,
25 | };
26 |
27 | public handleChange = (item: string) => (e: React.FormEvent) => {
28 | const min = 0;
29 | const max = item === 'hour' ? 23 : 59;
30 | let value = parseInt(e.currentTarget.value, 10);
31 |
32 | if (isNaN(value)) {
33 | value = 0;
34 | }
35 |
36 | if (max < value) {
37 | value = max;
38 | }
39 |
40 | if (min > value) {
41 | value = min;
42 | }
43 |
44 | this.setState(
45 | {
46 | ...this.state,
47 | [item]: value,
48 | },
49 | () => this.invokeOnChange()
50 | );
51 | };
52 |
53 | public handleUp = (item: string) => () => {
54 | const max = item === 'hour' ? 23 : 59;
55 |
56 | const value = this.state[item];
57 |
58 | this.setState(
59 | {
60 | ...this.state,
61 | [item]: Math.min(value + 1, max),
62 | },
63 | () => this.invokeOnChange()
64 | );
65 | };
66 |
67 | public handleDown = (item: string) => () => {
68 | const min = 0;
69 | const value = this.state[item];
70 | this.setState(
71 | {
72 | ...this.state,
73 | [item]: Math.max(value - 1, min),
74 | },
75 | () => this.invokeOnChange()
76 | );
77 | };
78 |
79 | public handleBlur = () => {
80 | const { onBlur } = this.props;
81 | const { hour, minute } = this.state;
82 | ifExistCall(onBlur, hour, minute);
83 | };
84 |
85 | public invokeOnChange = () => {
86 | const { onChange } = this.props;
87 | const { hour, minute } = this.state;
88 | ifExistCall(onChange, hour, minute);
89 | };
90 |
91 | public render() {
92 | const { hour, minute } = this.state;
93 | return (
94 |
111 | );
112 | }
113 | }
114 |
115 | export default TimeContainer;
116 |
--------------------------------------------------------------------------------
/src/components/TimeInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import SVGIcon from './SVGIcon';
3 |
4 | export interface Props {
5 | /** TimeInput onUp event */
6 | onUp: () => void;
7 | /** TimeInput onDown event */
8 | onDown: () => void;
9 | /** TimeInput onChange event */
10 | onChange: (e: React.FormEvent) => void;
11 | /** TimeInput onBlur event */
12 | onBlur: (e: React.FormEvent) => void;
13 | /** TimeInput value */
14 | value: number;
15 | }
16 |
17 | const TimeInput: React.FunctionComponent = ({ onUp, onDown, onChange, onBlur, value }) => {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | TimeInput.defaultProps = {
38 | value: 0,
39 | };
40 |
41 | export default TimeInput;
42 |
--------------------------------------------------------------------------------
/src/components/TodayPanel.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 |
4 | interface IProps {
5 | /** panel display today string */
6 | today: string;
7 | /** today panel click event */
8 | onClick?: () => void;
9 | /** today panel show or hide */
10 | show?: boolean;
11 | }
12 |
13 | const TodayPanel: React.FunctionComponent = ({ today, show, onClick }) => (
14 |
15 |
{today}
16 |
17 | );
18 |
19 | export default TodayPanel;
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import Calendar from './components/Calendar';
2 | import DatePicker from './components/DatePicker';
3 | import RangeDatePicker from './components/RangeDatePicker';
4 |
5 | export { Calendar, DatePicker, RangeDatePicker };
6 |
--------------------------------------------------------------------------------
/src/utils/ArrayUtil.ts:
--------------------------------------------------------------------------------
1 | export const chunk = (arr: any[], n: number) => {
2 | const result = [];
3 | let i = 0;
4 | while (i < arr.length / n) {
5 | result.push(arr.slice(i * n, i * n + n));
6 | i += 1;
7 | }
8 |
9 | return result;
10 | };
11 |
12 | export const range = (n1: number, n2?: number) => {
13 | const result = [];
14 | let first = !n2 ? 0 : n1;
15 | let last = n2;
16 |
17 | if (!last) {
18 | last = n1;
19 | }
20 |
21 | while (first < last) {
22 | result.push(first);
23 | first += 1;
24 | }
25 | return result;
26 | };
27 |
28 | export const repeat = (el: any, n: number) => {
29 | return range(n).map(() => el);
30 | };
31 |
--------------------------------------------------------------------------------
/src/utils/DOMUtil.ts:
--------------------------------------------------------------------------------
1 | import { IDatePicker } from '../common/@types';
2 | const convertPx = (value: number) => `${value}px`;
3 | /**
4 | * Getting Div position as far as distance
5 | * @param node
6 | * @param direction
7 | * @param distance
8 | */
9 | export const getDivPosition = (
10 | node: HTMLDivElement | null,
11 | direction: IDatePicker.PickerDirection = IDatePicker.PickerDirection.BOTTOM,
12 | height: number,
13 | distance: number = 5
14 | ): IDatePicker.Position => {
15 | if (!node) return { left: '', top: '', bottom: '' };
16 |
17 | let top = 0;
18 | let left = 0;
19 |
20 | switch (direction) {
21 | case IDatePicker.PickerDirection.BOTTOM:
22 | top = node.offsetTop + node.offsetHeight + distance;
23 | left = node.offsetLeft;
24 | break;
25 | case IDatePicker.PickerDirection.TOP:
26 | top = node.offsetTop - height - distance;
27 | left = node.offsetLeft;
28 | break;
29 | }
30 |
31 | return {
32 | top: convertPx(top),
33 | left: convertPx(left),
34 | };
35 | };
36 |
37 | export const getDomHeight = (node: HTMLDivElement | null): number => {
38 | return node ? node.clientHeight : 0;
39 | };
40 |
--------------------------------------------------------------------------------
/src/utils/DateUtil.ts:
--------------------------------------------------------------------------------
1 | import { chunk, repeat, range } from './ArrayUtil';
2 | import { IDatePicker } from '../common/@types';
3 | import * as dayjs from 'dayjs';
4 | import { getMonthShort } from './LocaleUtil';
5 |
6 | export const getDayMatrix = (year: number, month: number): string[][] => {
7 | const date = dayjs()
8 | .year(year)
9 | .month(month);
10 |
11 | const startOfMonth = date.startOf('month').date();
12 | const endOfMonth = date.endOf('month').date();
13 |
14 | const startDay = date.startOf('month').day();
15 | const remain = (startDay + endOfMonth) % 7;
16 |
17 | return chunk(
18 | [
19 | ...repeat(' ', startDay),
20 | ...range(startOfMonth, endOfMonth + 1).map(v => `${v}`),
21 | ...(7 - remain === 7 ? [] : repeat(' ', 7 - remain)),
22 | ],
23 | 7
24 | );
25 | };
26 |
27 | export const getMonthMatrix = (locale: IDatePicker.Locale) => {
28 | return chunk(getMonthShort(locale), 3);
29 | };
30 |
31 | export const getYearMatrix = (year: number): string[][] => {
32 | return chunk(range(year - 4, year + 5).map(v => `${v}`), 3);
33 | };
34 |
35 | export const isDayEqual = (day1?: dayjs.Dayjs, day2?: dayjs.Dayjs) => {
36 | if (!day1 || !day2) return false;
37 | return dayjs(day1).isSame(day2, 'date');
38 | };
39 |
40 | export const isDayAfter = (day1: dayjs.Dayjs, day2: dayjs.Dayjs) => {
41 | return dayjs(day1).isAfter(day2, 'date');
42 | };
43 |
44 | export const isDayBefore = (day1: dayjs.Dayjs, day2: dayjs.Dayjs) => {
45 | return dayjs(day1).isBefore(day2, 'date');
46 | };
47 |
48 | export const isDayRange = (date: dayjs.Dayjs, start?: dayjs.Dayjs, end?: dayjs.Dayjs) => {
49 | if (!start || !end) return false;
50 |
51 | return isDayAfter(date, start) && isDayBefore(date, end);
52 | };
53 |
54 | export const formatDate = (date: dayjs.Dayjs | undefined, format: string) => {
55 | if (date === undefined) return '';
56 | return dayjs(date).format(format);
57 | };
58 |
--------------------------------------------------------------------------------
/src/utils/FunctionUtil.ts:
--------------------------------------------------------------------------------
1 | export const ifExistCall = (func?: (...args: any[]) => void, ...args: any[]) =>
2 | func && func(...args);
3 |
--------------------------------------------------------------------------------
/src/utils/LocaleUtil.ts:
--------------------------------------------------------------------------------
1 | import * as dayjs from 'dayjs';
2 | import { IDatePicker } from '../common/@types';
3 | import { range } from '../utils/ArrayUtil';
4 | import * as localeData from 'dayjs/plugin/localeData';
5 | import * as localizedFormat from 'dayjs/plugin/localizedFormat';
6 | import * as weekday from 'dayjs/plugin/weekday';
7 |
8 | dayjs.extend(localeData);
9 | dayjs.extend(localizedFormat);
10 | dayjs.extend(weekday);
11 |
12 | export const getMonthShort = (locale: IDatePicker.Locale) => {
13 | dayjs.locale(locale);
14 | return range(0, 12).map(v =>
15 | dayjs()
16 | .localeData()
17 | .monthsShort(dayjs().month(v))
18 | );
19 | };
20 |
21 | export const getWeekDays = (locale: IDatePicker.Locale) => {
22 | dayjs.locale(locale);
23 | return range(7).map(v =>
24 | dayjs()
25 | .localeData()
26 | .weekdaysShort(dayjs().weekday(v))
27 | );
28 | };
29 |
30 | export const getToday = (locale: IDatePicker.Locale) => {
31 | return dayjs()
32 | .locale(locale)
33 | .format('LL');
34 | };
35 |
--------------------------------------------------------------------------------
/src/utils/StringUtil.ts:
--------------------------------------------------------------------------------
1 | export const lpad = (val: string, length: number, char: string = '0') =>
2 | val.length < length ? char.repeat(length - val.length) + val : val;
3 |
--------------------------------------------------------------------------------
/src/utils/TypeUtil.ts:
--------------------------------------------------------------------------------
1 | type Omit = Pick>;
2 | type Merge = Omit> & N;
3 |
4 | export { Omit, Merge };
5 |
--------------------------------------------------------------------------------
/stories/Calendar.stories.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import * as dayjs from 'dayjs';
4 | import CalendarSelectedController from '../examples/CalendarSelectedController';
5 | import { number } from '@storybook/addon-knobs';
6 | import './css/custom.css';
7 | import 'dayjs/locale/ko';
8 | import 'dayjs/locale/zh-cn';
9 | import 'dayjs/locale/ja';
10 |
11 | storiesOf('Calendar', module)
12 | .add('default', () => {
13 | return ;
14 | })
15 | .add('i18n internalizeation', () => {
16 | return (
17 |
18 |
19 |
Korea
20 |
21 |
22 |
23 |
Japen
24 |
25 |
26 |
27 |
China
28 |
29 |
30 |
31 | );
32 | })
33 | .add('custom locale object', () => {
34 | return (
35 |
43 | );
44 | })
45 | .add('todayPanel', () => {
46 | return ;
47 | })
48 | .add('showMonthCnt', () => {
49 | const showMontCnt = number('showMonthCnt', 2);
50 | return ;
51 | })
52 | .add('disableDay', () => {
53 | const disableDay = (date: dayjs.Dayjs) => {
54 | return dayjs(date).date() < 7;
55 | };
56 | return ;
57 | })
58 | .add('selected & onChange', () => {
59 | return ;
60 | })
61 | .add('multiple select', () => {
62 | return ;
63 | })
64 | .add('custom day class', () => {
65 | const customDayClass = (date: dayjs.Dayjs) => {
66 | // for test (year, month remove)
67 | const classMap = {
68 | '01': 'custom-class',
69 | '02': 'day-test1',
70 | };
71 |
72 | return classMap[dayjs(date).format('DD')];
73 | };
74 | return (
75 |
76 |
77 | custom-class, day-test class example
78 |
79 | );
80 | })
81 | .add('custom day text', () => {
82 | const customDayText = (date: dayjs.Dayjs) => {
83 | // for test (year, month remove)
84 | const classMap = {
85 | '01': '첫째날',
86 | '02': '둘째날',
87 | };
88 |
89 | return classMap[dayjs(date).format('DD')];
90 | };
91 | return ;
92 | });
93 |
--------------------------------------------------------------------------------
/stories/DatePicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/react';
2 | import * as React from 'react';
3 | import * as dayjs from 'dayjs';
4 | import { action } from '@storybook/addon-actions';
5 | import { text } from '@storybook/addon-knobs';
6 | import DatePicker from '../src/components/DatePicker';
7 | import LayoutDecorator from './decorator/LayoutDecorator';
8 |
9 | const defaultProps = {
10 | onChange: action('onChange'),
11 | locale: 'en',
12 | };
13 | storiesOf('DatePicker', module)
14 | .addDecorator(LayoutDecorator)
15 | .add('default', () => {
16 | return ;
17 | })
18 | .add('initialDate', () => {
19 | return ;
20 | })
21 | .add('portal version', () => )
22 | .add('includeTime', () => {
23 | return ;
24 | })
25 | .add('showTimeOnly', () => {
26 | return ;
27 | })
28 | .add('dateFormat', () => {
29 | return ;
30 | })
31 | .add('showMonthCnt', () => {
32 | return ;
33 | })
34 | .add('onTop', () => {
35 | return (
36 |
37 |
38 |
39 | );
40 | })
41 | .add('placeholder', () => {
42 | return ;
43 | });
44 |
45 | storiesOf('DatePicker - Input Props', module)
46 | .addDecorator(LayoutDecorator)
47 | .add('showDefaultIcon', () => {
48 | return ;
49 | })
50 | .add('readOnly', () => {
51 | return ;
52 | })
53 | .add('disabled', () => {
54 | return ;
55 | })
56 | .add('clear', () => {
57 | return ;
58 | });
59 |
--------------------------------------------------------------------------------
/stories/PickerInput.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { action } from '@storybook/addon-actions';
4 | import PickerInput from '../src/components/PickerInput';
5 |
6 | if (process.env.NODE_ENV === 'development') {
7 | storiesOf('PickerInput', module)
8 | .add('default', () => )
9 | .add('readOnly', () => )
10 | .add('disabled', () => )
11 | .add('autoFocus', () => )
12 | .add('clear', () => );
13 | }
14 |
--------------------------------------------------------------------------------
/stories/RangeDatePicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as dayjs from 'dayjs';
3 | import { storiesOf } from '@storybook/react';
4 | import { text } from '@storybook/addon-knobs';
5 | import RangeDatePicker from '../src/components/RangeDatePicker';
6 | import LayoutDecorator from './decorator/LayoutDecorator';
7 |
8 | storiesOf('RangeDatePicker', module)
9 | .addDecorator(LayoutDecorator)
10 | .add('default', () => )
11 | .add('initial Start & End Date', () => {
12 | return (
13 |
14 | );
15 | })
16 | .add('startText & endText', () => (
17 |
18 | ))
19 | .add('portal version', () => (
20 |
25 | ))
26 | .add('onTop', () => {
27 | return (
28 |
29 |
34 |
35 | );
36 | })
37 | .add('show 3 month', () => {
38 | return ;
39 | })
40 | .add('dateFormat', () => {
41 | return ;
42 | })
43 | .add('wrapping calendar', () => {
44 | const wrapper = (calendar: JSX.Element) => (
45 |
46 |
47 | Please select a reservation date
48 |
49 | {calendar}
50 |
51 | );
52 |
53 | return (
54 |
61 | );
62 | });
63 |
64 | storiesOf('RangeDatePicker - Input Props', module)
65 | .addDecorator(LayoutDecorator)
66 | .add('readOnly', () => )
67 | .add('disabled', () => )
68 | .add('clear', () => )
69 | .add('placeholder', () => (
70 |
74 | ));
75 |
--------------------------------------------------------------------------------
/stories/TimeContainer.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { action } from '@storybook/addon-actions';
4 | import TimeContainer from '../src/components/TimeContainer';
5 |
6 | storiesOf('TimeContainer', module).add(
7 | 'default',
8 | () => ,
9 | {
10 | jest: ['TimeContainer'],
11 | }
12 | );
13 |
--------------------------------------------------------------------------------
/stories/TimeInput.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { action } from '@storybook/addon-actions';
4 | import TimeInput from '../src/components/TimeInput';
5 |
6 | storiesOf('TimeInput', module)
7 | // .addParameters({ jest: ['TimeInput'] })
8 | .add(
9 | 'default(min, max)',
10 | () => (
11 |
18 | ),
19 | {
20 | jest: ['TimeInput'],
21 | }
22 | );
23 |
--------------------------------------------------------------------------------
/stories/css/custom.css:
--------------------------------------------------------------------------------
1 | .custom-class {
2 | background: red;
3 | color: white;
4 | }
5 |
6 | .day-test1 {
7 | background: blue;
8 | color: white;
9 | }
10 |
11 | .panel {
12 | display: inline-flex;
13 | flex-direction: column;
14 | margin: 0 10px;
15 | }
--------------------------------------------------------------------------------
/stories/decorator/LayoutDecorator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const LayoutDecorator = (storyFn: any) => (
4 |
9 | {storyFn()}
10 |
11 | );
12 |
13 | export default LayoutDecorator;
14 |
--------------------------------------------------------------------------------
/test-preprocessor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test Code Preprocessing
3 | * Typescript code transplie to javascript
4 | */
5 | const tsc = require('typescript');
6 | const tsConfig = require('./tsconfig.json');
7 |
8 | module.exports = {
9 | process(src, path) {
10 | if (path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.js')) {
11 | return tsc.transpile(src, tsConfig.compilerOptions, path, []);
12 | }
13 | return src;
14 | },
15 | };
--------------------------------------------------------------------------------
/test-setup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React 16 Adapter for Enzyme
3 | */
4 | const enzyme = require('enzyme');
5 | const Adapter = require('enzyme-adapter-react-16');
6 |
7 | enzyme.configure({ adapter: new Adapter() });
--------------------------------------------------------------------------------
/test-shim.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get rids of the missing requestAnimatinoFrame polyfill warning
3 | */
4 | global.requestAnimationFrame = function(callback) {
5 | setTimeout(callback, 0);
6 | };
7 |
--------------------------------------------------------------------------------
/test/ArrayUtil.test.ts:
--------------------------------------------------------------------------------
1 | import { range, chunk, repeat } from '../src/utils/ArrayUtil';
2 | describe('ArrayUtil', () => {
3 | it('should range negative number return empty array', () => {
4 | expect(range(-1)).toEqual([]);
5 | });
6 | it('should range return number array', () => {
7 | expect(range(3)).toEqual([0, 1, 2]);
8 | expect(range(3, 5)).toEqual([3, 4]);
9 | });
10 |
11 | it('should chunk array return matrix array', () => {
12 | expect(chunk([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)).toEqual([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
13 | expect(chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3)).toEqual([
14 | [1, 2, 3],
15 | [4, 5, 6],
16 | [7, 8, 9],
17 | [10],
18 | ]);
19 | });
20 |
21 | it('should repeat return character fill array', () => {
22 | expect(repeat(' ', 5)).toEqual([' ', ' ', ' ', ' ', ' ']);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/Calendar.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallow, mount, ShallowWrapper, ReactWrapper } from 'enzyme';
2 | import * as dayjs from 'dayjs';
3 | import * as React from 'react';
4 | import Calendar, { Props, State } from '../src/components/Calendar';
5 | import CalendarContainer from '../src/components/CalendarContainer';
6 |
7 | describe(' ', () => {
8 | // 20180501
9 | let shallowComponent: ShallowWrapper>;
10 | let mountComponent: ReactWrapper;
11 | const defaultProps = {
12 | base: dayjs(new Date(2018, 4, 1)),
13 | };
14 |
15 | it('redners with no props', () => {
16 | shallowComponent = shallow( );
17 |
18 | expect(shallowComponent).toBeTruthy();
19 | expect(shallowComponent).toMatchSnapshot();
20 | });
21 |
22 | it('props showMonthCnt correctly', () => {
23 | shallowComponent = shallow( );
24 |
25 | expect(shallowComponent.find('.calendar__item')).toHaveLength(3);
26 | // first calendar only previcon true
27 | expect(
28 | shallowComponent
29 | .find(CalendarContainer)
30 | .first()
31 | .props().prevIcon
32 | ).toBeTruthy();
33 | // last calendar only nextIcon true
34 | expect(
35 | shallowComponent
36 | .find(CalendarContainer)
37 | .last()
38 | .props().nextIcon
39 | ).toBeTruthy();
40 | // another calendar both hide
41 | expect(
42 | shallowComponent
43 | .find(CalendarContainer)
44 | .at(1)
45 | .props().nextIcon
46 | ).toBeFalsy();
47 | expect(
48 | shallowComponent
49 | .find(CalendarContainer)
50 | .at(1)
51 | .props().prevIcon
52 | ).toBeFalsy();
53 | });
54 |
55 | it('should setBase test', () => {
56 | mountComponent = mount( );
57 | // change view mode to month
58 | mountComponent.find('.calendar__head--title').simulate('click');
59 |
60 | mountComponent
61 | .find('td')
62 | .at(1)
63 | .simulate('click');
64 |
65 | expect(dayjs(mountComponent.state().base).format('MM')).toEqual('02');
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/test/CalendarBody.test.tsx:
--------------------------------------------------------------------------------
1 | import { mount, shallow, ReactWrapper, ShallowWrapper } from 'enzyme';
2 | import * as sinon from 'sinon';
3 | import * as dayjs from 'dayjs';
4 | import * as React from 'react';
5 | import DayView from '../src/components/DayView';
6 | import CalendarBody from '../src/components/CalendarBody';
7 | import { IDatePicker } from '../src/common/@types';
8 |
9 | describe(' ', () => {
10 | const defaultProps = {
11 | current: dayjs(new Date(2018, 11, 1)),
12 | onClick: sinon.spy(),
13 | };
14 | let shallowComponent: ShallowWrapper;
15 | let mountComponent: ReactWrapper;
16 |
17 | it('should render correctly', () => {
18 | shallowComponent = shallow( );
19 | expect(shallowComponent).toBeTruthy();
20 | expect(shallowComponent).toMatchSnapshot();
21 | });
22 |
23 | describe('prop: viewMode', () => {
24 | it('should ViewMode.DAY correctly', () => {
25 | shallowComponent = shallow(
26 |
27 | );
28 | expect(shallowComponent.find(DayView)).toHaveLength(1);
29 | });
30 |
31 | it('should ViewMode.MONTH correctly', () => {
32 | mountComponent = mount(
33 |
34 | );
35 | expect(mountComponent.find('td.calendar__month')).toHaveLength(12);
36 | });
37 |
38 | it('should ViewMode.YEAR correctly', () => {
39 | mountComponent = mount(
40 |
41 | );
42 | expect(mountComponent.find('td.calendar__year')).toHaveLength(9);
43 | });
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/test/CalendarContainer.test.tsx:
--------------------------------------------------------------------------------
1 | import { mount, shallow, ShallowWrapper, ReactWrapper } from 'enzyme';
2 |
3 | import * as dayjs from 'dayjs';
4 | import * as React from 'react';
5 | import * as sinon from 'sinon';
6 |
7 | import 'dayjs/locale/en';
8 | import 'dayjs/locale/ko';
9 |
10 | import CalendarContainer, { Props, State } from '../src/components/CalendarContainer';
11 | import { IDatePicker } from '../src/common/@types';
12 |
13 | describe(' ', () => {
14 | const current = dayjs(new Date(2018, 11, 5));
15 | let base = current;
16 | const setBase = (val: dayjs.Dayjs) => {
17 | base = val;
18 | };
19 | const defaultProps = {
20 | base,
21 | setBase,
22 | current,
23 | };
24 |
25 | let shallowComponent: ShallowWrapper>;
26 | let mountComponent: ReactWrapper;
27 |
28 | describe('prop: show', () => {
29 | beforeEach(() => {
30 | shallowComponent = shallow( );
31 | });
32 |
33 | it('should render correctly', () => {
34 | expect(shallowComponent).toBeTruthy();
35 | expect(shallowComponent).toMatchSnapshot();
36 | });
37 |
38 | it('props show correctly', () => {
39 | expect(shallowComponent.hasClass('calendar--show')).toBeTruthy();
40 | });
41 | });
42 |
43 | describe('prop: locale', () => {
44 | let en: ReactWrapper;
45 | let ko: ReactWrapper;
46 |
47 | beforeEach(() => {
48 | en = mount( );
49 | ko = mount( );
50 | });
51 |
52 | it('should locale prop correctly', () => {
53 | expect(ko).toMatchSnapshot();
54 | expect(
55 | en
56 | .find('th')
57 | .first()
58 | .text()
59 | ).toEqual('Sun');
60 | expect(
61 | ko
62 | .find('th')
63 | .first()
64 | .text()
65 | ).toEqual('일');
66 | });
67 | });
68 |
69 | describe('handle base test(onPrev, onNext)', () => {
70 | beforeEach(() => {
71 | mountComponent = mount(
72 |
73 | );
74 | });
75 |
76 | it('should onPrev correctly', () => {
77 | mountComponent.setState({
78 | viewMode: IDatePicker.ViewMode.DAY,
79 | });
80 | mountComponent.find('.calendar__head--prev > button').simulate('click');
81 | expect(dayjs(base).format('MM')).toEqual('11');
82 |
83 | mountComponent.setState({
84 | viewMode: IDatePicker.ViewMode.MONTH,
85 | });
86 | mountComponent.find('.calendar__head--prev > button').simulate('click');
87 | expect(dayjs(base).format('YYYY')).toEqual('2017');
88 |
89 | mountComponent.setState({
90 | viewMode: IDatePicker.ViewMode.YEAR,
91 | });
92 | mountComponent.find('.calendar__head--prev > button').simulate('click');
93 | expect(dayjs(base).format('YYYY')).toEqual('2008');
94 | });
95 | });
96 |
97 | describe('handle today test', () => {
98 | it('should handle today correctly', () => {
99 | mountComponent = mount( );
100 | mountComponent.setState({
101 | viewMode: IDatePicker.ViewMode.DAY,
102 | });
103 | mountComponent.find('.calendar__head--prev > button').simulate('click');
104 | mountComponent.find('.calendar__head--prev > button').simulate('click');
105 | mountComponent.find('.calendar__panel--today h2').simulate('click');
106 | expect(dayjs(base).format('YYYYMMDD')).toEqual(dayjs().format('YYYYMMDD'));
107 | });
108 | });
109 |
110 | describe('handle title click test', () => {
111 | it('should viewMode change correctly', () => {
112 | mountComponent = mount( );
113 | // once click expect month
114 | mountComponent.find('.calendar__head--title').simulate('click');
115 | expect(mountComponent.state().viewMode).toEqual(IDatePicker.ViewMode.MONTH);
116 |
117 | // twice click expect year
118 | mountComponent.find('.calendar__head--title').simulate('click');
119 | expect(mountComponent.state().viewMode).toEqual(IDatePicker.ViewMode.YEAR);
120 | });
121 |
122 | it('should showMontCnt > 1 viewMode noChange', () => {
123 | mountComponent = mount( );
124 | // once click expect day(showMontCnt > 1)
125 | mountComponent.find('.calendar__head--title').simulate('click');
126 | expect(mountComponent.state().viewMode).toEqual(IDatePicker.ViewMode.DAY);
127 | });
128 | });
129 |
130 | describe('handle change test', () => {
131 | it('should showMontCnt > 1 onChange call', () => {
132 | const onChange = sinon.spy();
133 | mountComponent = mount(
134 |
140 | );
141 |
142 | // empty date click
143 | mountComponent
144 | .find('td')
145 | .at(1)
146 | .simulate('click');
147 |
148 | expect(onChange).toHaveProperty('callCount', 0);
149 |
150 | mountComponent
151 | .find('td')
152 | .at(6)
153 | .simulate('click');
154 |
155 | expect(onChange).toHaveProperty('callCount', 1);
156 | });
157 |
158 | describe('Mode Specific test', () => {
159 | let onChange: sinon.SinonSpy;
160 | beforeEach(() => {
161 | onChange = sinon.spy();
162 | mountComponent = mount( );
163 | });
164 |
165 | it('should year mode test', () => {
166 | mountComponent.setState({
167 | viewMode: IDatePicker.ViewMode.YEAR,
168 | });
169 |
170 | mountComponent
171 | .find('td')
172 | .at(6)
173 | .simulate('click');
174 |
175 | expect(dayjs(base).format('YYYY')).toEqual('2020');
176 | expect(mountComponent.state().viewMode).toEqual(IDatePicker.ViewMode.MONTH);
177 | });
178 |
179 | it('should month mode test', () => {
180 | mountComponent.setState({
181 | viewMode: IDatePicker.ViewMode.MONTH,
182 | });
183 | mountComponent
184 | .find('td')
185 | .at(1)
186 | .simulate('click');
187 |
188 | expect(dayjs(base).format('MM')).toEqual('02');
189 | expect(mountComponent.state().viewMode).toEqual(IDatePicker.ViewMode.DAY);
190 | });
191 |
192 | it('should day mode test', () => {
193 | mountComponent.setState({
194 | viewMode: IDatePicker.ViewMode.DAY,
195 | });
196 |
197 | mountComponent
198 | .find('td')
199 | .at(6)
200 | .simulate('click');
201 |
202 | expect(onChange).toHaveProperty('callCount', 1);
203 | });
204 | });
205 | });
206 | });
207 |
--------------------------------------------------------------------------------
/test/CalendarHead.test.tsx:
--------------------------------------------------------------------------------
1 | import { mount, shallow, ShallowWrapper } from 'enzyme';
2 | import * as React from 'react';
3 | import * as sinon from 'sinon';
4 | import CalendarHead from '../src/components/CalendarHead';
5 |
6 | describe(' ', () => {
7 | let shallowComponent: ShallowWrapper;
8 |
9 | it('should render correctly', () => {
10 | shallowComponent = shallow( );
11 |
12 | expect(shallowComponent).toBeTruthy();
13 | expect(shallowComponent).toMatchSnapshot();
14 | });
15 |
16 | it('should props title correctly', () => {
17 | shallowComponent = shallow( );
18 |
19 | expect(shallowComponent).toMatchSnapshot();
20 | expect(shallowComponent.find('h2.calendar__head--title').text()).toEqual('2019/01');
21 | });
22 |
23 | it('should props prevIcon, nextIcon correctly', () => {
24 | shallowComponent = shallow( );
25 |
26 | expect(shallowComponent).toMatchSnapshot();
27 | expect(shallowComponent.find('div.calendar__head--prev > button')).toHaveLength(1);
28 | expect(shallowComponent.find('div.calendar__head--next > button')).toHaveLength(1);
29 | });
30 |
31 | it('should props onPrev, onNext correctly', () => {
32 | const onPrev = sinon.spy();
33 | const onNext = sinon.spy();
34 |
35 | shallowComponent = shallow( );
36 |
37 | expect(shallowComponent).toMatchSnapshot();
38 | shallowComponent.find('div.calendar__head--prev > button').simulate('click');
39 | shallowComponent.find('div.calendar__head--next > button').simulate('click');
40 |
41 | expect(onPrev).toHaveProperty('callCount', 1);
42 | expect(onNext).toHaveProperty('callCount', 1);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/test/DOMUtil.test.ts:
--------------------------------------------------------------------------------
1 | import { getDivPosition } from '../src/utils/DOMUtil';
2 | import { IDatePicker } from '../src/common/@types';
3 | import { mock, instance, when } from 'ts-mockito';
4 |
5 | describe('utils.DOMUtil', () => {
6 | let mockDiv: HTMLDivElement;
7 | let instanceDiv: HTMLDivElement;
8 |
9 | describe('getDivPosition', () => {
10 | it('works with if direction top', () => {
11 | // given
12 | mockDiv = mock(HTMLDivElement);
13 | when(mockDiv.offsetLeft).thenReturn(10);
14 | when(mockDiv.offsetHeight).thenReturn(10);
15 | when(mockDiv.offsetTop).thenReturn(10);
16 | instanceDiv = instance(mockDiv);
17 |
18 | // when
19 | const position = getDivPosition(instanceDiv, IDatePicker.PickerDirection.TOP, 30);
20 |
21 | // then
22 | expect(position.top).toEqual('-25px');
23 | expect(position.left).toEqual('10px');
24 | });
25 |
26 | it('works with if direction bottom', () => {
27 | // given
28 | mockDiv = mock(HTMLDivElement);
29 | when(mockDiv.offsetLeft).thenReturn(10);
30 | when(mockDiv.offsetHeight).thenReturn(10);
31 | when(mockDiv.offsetTop).thenReturn(10);
32 | instanceDiv = instance(mockDiv);
33 |
34 | // when
35 | const position = getDivPosition(instanceDiv, IDatePicker.PickerDirection.BOTTOM, 30);
36 |
37 | // then
38 | expect(position.top).toEqual('25px');
39 | expect(position.left).toEqual('10px');
40 | });
41 |
42 | it('does calculate distance if distance parameter not default', () => {
43 | // given
44 | mockDiv = mock(HTMLDivElement);
45 | when(mockDiv.offsetLeft).thenReturn(10);
46 | when(mockDiv.offsetHeight).thenReturn(10);
47 | when(mockDiv.offsetTop).thenReturn(10);
48 | instanceDiv = instance(mockDiv);
49 |
50 | // when
51 | const position = getDivPosition(instanceDiv, IDatePicker.PickerDirection.BOTTOM, 10, 10);
52 |
53 | // then
54 | expect(position.top).toEqual('30px');
55 | expect(position.left).toEqual('10px');
56 | });
57 |
58 | it('if node null return empty position object', () => {
59 | // given
60 | const nullNode = null;
61 |
62 | // when
63 | const position = getDivPosition(nullNode, IDatePicker.PickerDirection.BOTTOM, 10);
64 |
65 | // then
66 | expect(position.top).toEqual('');
67 | expect(position.left).toEqual('');
68 | expect(position.bottom).toEqual('');
69 | });
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/test/DatePicker.test.tsx:
--------------------------------------------------------------------------------
1 | import { mount, shallow, ShallowWrapper, ReactWrapper } from 'enzyme';
2 | import * as dayjs from 'dayjs';
3 | import * as React from 'react';
4 | import * as sinon from 'sinon';
5 | import { DatePickerDefaults } from '../src/common/Constant';
6 | import Calendar from '../src/components/Calendar';
7 | import DatePicker, { Props, State, TabValue } from '../src/components/DatePicker';
8 |
9 | const PICKER_INPUT_CLASS = '.picker-input__text';
10 |
11 | describe(' ', () => {
12 | let shallowComponent: ShallowWrapper>;
13 | let mountComponent: ReactWrapper;
14 |
15 | const defaultProps = {
16 | initialDate: dayjs(new Date(2018, 11, 1)),
17 | };
18 |
19 | const pickerShow = (component: ReactWrapper) => {
20 | component.find('Picker').setState({
21 | show: true,
22 | });
23 | };
24 | const daySelect = (at: number) => {
25 | mountComponent
26 | .find('td')
27 | .at(at)
28 | .simulate('click');
29 | };
30 |
31 | describe('shallow render test', () => {
32 | beforeEach(() => {
33 | shallowComponent = shallow( );
34 | });
35 |
36 | it('renders with no props', () => {
37 | expect(shallowComponent).toBeTruthy();
38 | });
39 |
40 | it('should includeTime & showTimeOnly cannot be used together', () => {
41 | expect(() => {
42 | shallowComponent = shallow( );
43 | }).toThrowError(Error);
44 | });
45 |
46 | describe('dateFormat', () => {
47 | it('should includeTime dateFormat correctly', () => {
48 | const shallowDatePicker = shallow( );
49 | expect(shallowDatePicker.instance().getDateFormat()).toBe(
50 | DatePickerDefaults.dateTimeFormat
51 | );
52 | });
53 |
54 | it('should calendarOnly dateFormat correctly', () => {
55 | const shallowDatePicker = shallow( );
56 | expect(shallowDatePicker.instance().getDateFormat()).toBe(DatePickerDefaults.dateFormat);
57 | });
58 |
59 | it('should timeOnly dateFormat correctly', () => {
60 | const shallowDatePicker = shallow(
61 |
62 | );
63 | expect(shallowDatePicker.instance().getDateFormat()).toBe(DatePickerDefaults.timeFormat);
64 | });
65 | });
66 | });
67 |
68 | describe('mount test', () => {
69 | let onChange: sinon.SinonSpy;
70 | beforeEach(() => {
71 | onChange = sinon.spy();
72 | mountComponent = mount(
73 |
74 | );
75 | pickerShow(mountComponent);
76 | });
77 |
78 | it('should showTimeonly render only timeContainer', () => {
79 | mountComponent = mount( );
80 | pickerShow(mountComponent);
81 | expect(mountComponent.find('.picker__container__timeonly')).toHaveLength(1);
82 | });
83 |
84 | it('should input ref correctly', () => {
85 | mountComponent.find('.picker__trigger').simulate('click');
86 | expect(mountComponent.find('Calendar')).toHaveLength(1);
87 | });
88 |
89 | it('should props dateFormat correctly', () => {
90 | daySelect(7);
91 | expect(mountComponent.find('.picker__trigger input').prop('value')).toEqual('2018/12/02');
92 | });
93 |
94 | it('should props onChange correctly', () => {
95 | daySelect(6);
96 | expect(onChange).toHaveProperty('callCount', 1);
97 | expect(mountComponent.state('calendarShow')).toBeFalsy();
98 | expect(mountComponent.state('selected')).toHaveLength(1);
99 | });
100 |
101 | it('should props showMonthCnt correctly', () => {
102 | // 20180501
103 | const mockDate = dayjs(new Date(2018, 5, 1));
104 | mountComponent = mount( );
105 | pickerShow(mountComponent);
106 | expect(mountComponent.find('.calendar__container')).toHaveLength(3);
107 | });
108 |
109 | it('should input interaction correctly', () => {
110 | mountComponent.find('.picker__trigger').simulate('click');
111 | expect(mountComponent.find('.rc-backdrop')).toHaveLength(1);
112 | expect(mountComponent.find(Calendar)).toBeTruthy();
113 | expect(mountComponent.find('Picker').state('show')).toBeTruthy();
114 | });
115 |
116 | it('should hideCalendar correctly', () => {
117 | mountComponent.find('.picker__trigger').simulate('click');
118 | mountComponent.find('.rc-backdrop').simulate('click');
119 | expect(mountComponent.find('Picker').state('show')).toBeFalsy();
120 | });
121 | });
122 |
123 | describe('include time', () => {
124 | beforeEach(() => {
125 | mountComponent = mount( );
126 | });
127 |
128 | it('should time container render correctly', () => {
129 | expect(mountComponent.find('.time__container')).toBeTruthy();
130 | });
131 |
132 | it('should tab TIME click correctly', () => {
133 | mountComponent.setState({
134 | ...mountComponent.state,
135 | tabValue: TabValue.DATE,
136 | });
137 | pickerShow(mountComponent);
138 |
139 | mountComponent
140 | .find('.picker__container__tab button')
141 | .at(1)
142 | .simulate('click');
143 | expect(mountComponent.state('tabValue')).toEqual(TabValue.TIME);
144 | });
145 |
146 | describe('handleTimeChange', () => {
147 | const handleClick = (type: string, at: number) => {
148 | mountComponent
149 | .find(`.time-input__${type} button`)
150 | .at(at)
151 | .simulate('click');
152 | };
153 |
154 | beforeEach(() => {
155 | mountComponent = mount( );
156 | mountComponent.setState({
157 | tabValue: TabValue.TIME,
158 | });
159 | pickerShow(mountComponent);
160 | });
161 |
162 | it('should set new date when state date is null', () => {
163 | mountComponent = mount( );
164 | mountComponent.setState({
165 | tabValue: TabValue.TIME,
166 | });
167 |
168 | pickerShow(mountComponent);
169 | handleClick('up', 0);
170 |
171 | const date = mountComponent.state().date;
172 |
173 | expect(date).toBeDefined();
174 | if (date) {
175 | expect(date.format(DatePickerDefaults.dateFormat)).toBe(
176 | dayjs().format(DatePickerDefaults.dateFormat)
177 | );
178 | }
179 | });
180 |
181 | it('should time change set date & input value correctly', () => {
182 | mountComponent = mount( );
183 | mountComponent.setState({
184 | tabValue: TabValue.TIME,
185 | });
186 |
187 | pickerShow(mountComponent);
188 | handleClick('up', 0);
189 | const date = mountComponent.state().date;
190 | if (date) {
191 | expect(date.format('YYYYMMDDHHmm')).toBe('201904032320');
192 | }
193 | });
194 |
195 | it('should fire onChange Event', () => {
196 | const onChange = sinon.spy();
197 | mountComponent = mount( );
198 | mountComponent.setState({
199 | tabValue: TabValue.TIME,
200 | });
201 | pickerShow(mountComponent);
202 | handleClick('up', 0);
203 | expect(onChange).toHaveProperty('callCount', 1);
204 | });
205 | });
206 | });
207 |
208 | describe('handleInputBlur', () => {
209 | beforeEach(() => {
210 | mountComponent = mount( );
211 | });
212 |
213 | it('should date picker input invalid value return original date', () => {
214 | const { dateFormat } = DatePickerDefaults;
215 | const originalDate = dayjs(new Date(2018, 4, 1));
216 | const testValue = 'teste333';
217 | mountComponent.setState({
218 | ...mountComponent.state,
219 | date: originalDate,
220 | inputValue: testValue,
221 | });
222 |
223 | mountComponent.find(PICKER_INPUT_CLASS).simulate('blur');
224 |
225 | const dateState = mountComponent.state('date');
226 | expect(dateState).not.toBeUndefined();
227 |
228 | if (dateState) {
229 | expect(dayjs(dateState).format(dateFormat)).toEqual(dayjs(originalDate).format(dateFormat));
230 | }
231 | });
232 |
233 | it('should date picker input valid value setState date', () => {
234 | const { dateFormat } = DatePickerDefaults;
235 | const originalDate = dayjs(new Date(2018, 4, 1));
236 | const correctValue = '2018-05-02';
237 | mountComponent.setState({
238 | ...mountComponent.state,
239 | date: originalDate,
240 | inputValue: correctValue,
241 | });
242 |
243 | mountComponent.find(PICKER_INPUT_CLASS).simulate('blur');
244 |
245 | const dateState = mountComponent.state('date');
246 | expect(dateState).not.toBeUndefined();
247 |
248 | if (dateState) {
249 | expect(dayjs(dateState).format(dateFormat)).toEqual(correctValue);
250 | }
251 | });
252 |
253 | it('should time picker input invalid value return original time', () => {
254 | mountComponent = mount( );
255 | const originalDate = dayjs('201904031022');
256 |
257 | mountComponent.setState({
258 | ...mountComponent.state,
259 | date: originalDate,
260 | inputValue: `${dayjs(originalDate).format(DatePickerDefaults.dateFormat)} 03:e0A`,
261 | });
262 |
263 | mountComponent.find(PICKER_INPUT_CLASS).simulate('blur');
264 |
265 | const inputValue = mountComponent.state('inputValue');
266 |
267 | expect(inputValue).toBe(originalDate.format(DatePickerDefaults.dateTimeFormat));
268 | });
269 | });
270 |
271 | describe('handleInputClear', () => {
272 | beforeEach(() => {
273 | mountComponent = mount( );
274 | });
275 |
276 | it('should clear button render correctly', () => {
277 | expect(mountComponent.find('.icon-clear').first()).toHaveLength(1);
278 | });
279 |
280 | it('should clear click state change correctly', () => {
281 | mountComponent
282 | .find('.icon-clear')
283 | .first()
284 | .simulate('click');
285 | expect(mountComponent.state('inputValue')).toEqual('');
286 | });
287 |
288 | it('should clear click onChange fired!', () => {
289 | const onChange = sinon.spy();
290 | mountComponent = mount( );
291 | mountComponent
292 | .find('.icon-clear')
293 | .first()
294 | .simulate('click');
295 | expect(onChange).toHaveProperty('callCount', 1);
296 | });
297 | });
298 |
299 | describe('handleInputChange', () => {
300 | it('should input change correctly', () => {
301 | const onChange = sinon.spy();
302 | mountComponent = mount( );
303 | mountComponent.find(PICKER_INPUT_CLASS).simulate('change');
304 | expect(onChange).toHaveProperty('callCount', 1);
305 | mountComponent = mount( );
306 | mountComponent.find(PICKER_INPUT_CLASS).simulate('change');
307 | });
308 | });
309 |
310 | describe('input props', () => {
311 | it('should input disabled do not run handleCalendar', () => {
312 | mountComponent = mount( );
313 | mountComponent.find(PICKER_INPUT_CLASS).simulate('click');
314 | expect(mountComponent.state('show')).toBeFalsy();
315 | });
316 |
317 | it('should showDefaultIcon correctly', () => {
318 | mountComponent = mount( );
319 | expect(mountComponent.find('.icon-calendar').first()).toHaveLength(1);
320 | });
321 | });
322 |
323 | describe('custom input test', () => {
324 | beforeEach(() => {
325 | const customInputComponent = (props: any) => ;
326 |
327 | mountComponent = mount(
328 |
333 | );
334 | pickerShow(mountComponent);
335 | });
336 |
337 | it('should customInput render correctly', () => {
338 | expect(mountComponent.find('.picker__trigger textarea')).toBeTruthy();
339 | });
340 |
341 | it('should customInput value correctly', () => {
342 | daySelect(6);
343 | expect(mountComponent.find('.picker__trigger textarea').props().value).toBeDefined();
344 | expect(mountComponent.find('.picker__trigger textarea').props().value).toEqual('2018.12.01');
345 | });
346 | });
347 | });
348 |
--------------------------------------------------------------------------------
/test/DateUtil.test.ts:
--------------------------------------------------------------------------------
1 | import * as dayjs from 'dayjs';
2 | import { formatDate, isDayEqual, isDayRange } from '../src/utils/DateUtil';
3 |
4 | describe('DateUtil', () => {
5 | describe('isDayEqual', () => {
6 | let day1: dayjs.Dayjs;
7 | let day2: dayjs.Dayjs;
8 |
9 | it('should eq date return true', () => {
10 | // given
11 | day1 = dayjs(new Date(2019, 1, 1));
12 | day2 = dayjs(new Date(2019, 1, 1));
13 |
14 | // when
15 | const isEqual = isDayEqual(day1, day2);
16 |
17 | // then
18 | expect(isEqual).toBeTruthy();
19 | });
20 |
21 | it('should not eq date return false', () => {
22 | // given
23 | day1 = dayjs(new Date(2019, 1, 1));
24 | day2 = dayjs(new Date(2019, 1, 2));
25 |
26 | // when
27 | const isEqual = isDayEqual(day1, day2);
28 |
29 | // then
30 | expect(isEqual).toBeFalsy();
31 | });
32 | });
33 |
34 | describe('isDayRange', () => {
35 | let between: dayjs.Dayjs;
36 | let start: dayjs.Dayjs;
37 | let end: dayjs.Dayjs;
38 |
39 | it('should between eq start return false', () => {
40 | // given
41 | between = dayjs(new Date(2019, 1, 1));
42 | start = dayjs(new Date(2019, 1, 1));
43 | end = dayjs(new Date(2019, 1, 6));
44 |
45 | // when
46 | const isRange = isDayRange(between, start, end);
47 |
48 | // then
49 | expect(isRange).toBeFalsy();
50 | });
51 |
52 | it('should between eq end return false', () => {
53 | // given
54 | between = dayjs(new Date(2019, 1, 6));
55 | start = dayjs(new Date(2019, 1, 1));
56 | end = dayjs(new Date(2019, 1, 6));
57 |
58 | // when
59 | const isRange = isDayRange(between, start, end);
60 |
61 | // then
62 | expect(isRange).toBeFalsy();
63 | });
64 |
65 | it('should between gt start return true', () => {
66 | // given
67 | between = dayjs(new Date(2019, 1, 2));
68 | start = dayjs(new Date(2019, 1, 1));
69 | end = dayjs(new Date(2019, 1, 6));
70 |
71 | // when
72 | const isRange = isDayRange(between, start, end);
73 |
74 | // then
75 | expect(isRange).toBeTruthy();
76 | });
77 | });
78 |
79 | describe('formatDate', () => {
80 | it('should return empty value when date is null', () => {
81 | expect(formatDate(undefined, 'YYYY-MM-DD')).toBe('');
82 | });
83 |
84 | it('should return formattedValue value when date is not null', () => {
85 | const date = dayjs('20181201');
86 | expect(formatDate(date, 'YYYY-MM-DD')).toBe('2018-12-01');
87 | });
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/test/DayView.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import * as dayjs from 'dayjs';
4 | import 'dayjs/locale/es';
5 | import DayView from '../src/components/DayView';
6 | import { mount, ReactWrapper } from 'enzyme';
7 |
8 | describe(' ', () => {
9 | const defaultProps = {
10 | current: dayjs(new Date(2018, 11, 5)),
11 | };
12 | let mountComponent: ReactWrapper;
13 |
14 | describe('prop: selected', () => {
15 | beforeEach(() => {
16 | const selected = [dayjs('20181201'), dayjs('20181212'), dayjs('20181215'), dayjs('20181222')];
17 |
18 | mountComponent = mount( );
19 | });
20 |
21 | it('should render correctly', () => {
22 | expect(mountComponent).toBeTruthy();
23 | expect(mountComponent).toMatchSnapshot();
24 | });
25 |
26 | it('does selected add class --selected', () => {
27 | expect(mountComponent.find('td.calendar__day--selected')).toHaveLength(4);
28 | });
29 | });
30 |
31 | describe('prop: disabled', () => {
32 | let onClick: sinon.SinonSpy;
33 |
34 | beforeEach(() => {
35 | const disableDay = (date: dayjs.Dayjs) => {
36 | return dayjs(date).isSame(dayjs('20181201'), 'date');
37 | };
38 | onClick = sinon.spy();
39 | mountComponent = mount(
40 |
41 | );
42 | });
43 |
44 | it('does disabled dates add class --disabled', () => {
45 | expect(mountComponent.find('td.calendar__day--disabled')).toHaveLength(1);
46 | });
47 |
48 | it('does disabled dates onClick not fired', () => {
49 | mountComponent
50 | .find('td.calendar__day--disabled')
51 | .first()
52 | .simulate('click');
53 | expect(onClick).toHaveProperty('callCount', 0);
54 | });
55 | });
56 |
57 | describe('prop: startDay, endDay', () => {
58 | beforeEach(() => {
59 | const startDay = dayjs('20181205');
60 | const endDay = dayjs('20181211');
61 |
62 | mountComponent = mount( );
63 | });
64 | it('should render correctly', () => {
65 | expect(mountComponent).toBeTruthy();
66 | expect(mountComponent).toMatchSnapshot();
67 | });
68 |
69 | it('should have startDay, endDay class td', () => {
70 | expect(mountComponent.find('td.calendar__day--start > div').text()).toEqual('5');
71 | expect(mountComponent.find('td.calendar__day--end > div').text()).toEqual('11');
72 | });
73 |
74 | it('should only occur when startDay, endDay exists', () => {
75 | const startDay = dayjs('20181205');
76 | mountComponent = mount( );
77 | expect(mountComponent.find('td.calendar__day--range')).toHaveLength(0);
78 | });
79 | });
80 |
81 | describe('prop:customClass, customText', () => {
82 | beforeEach(() => {
83 | const customDayClass = (date: dayjs.Dayjs) => {
84 | const dayClassMap = {
85 | '20181202': ['custom-day', 'day-test1', 'day-test2'],
86 | '20181211': 'custom-day',
87 | };
88 | return dayClassMap[dayjs(date).format('YYYYMMDD')];
89 | };
90 |
91 | const customDayText = (date: dayjs.Dayjs) => {
92 | // custom day class string or array
93 | const dayTextMap = {
94 | '20181202': '신정',
95 | '20181211': '공휴일',
96 | };
97 | return dayTextMap[dayjs(date).format('YYYYMMDD')];
98 | };
99 | mountComponent = mount(
100 |
101 | );
102 | });
103 |
104 | it('should render correctly', () => {
105 | expect(mountComponent).toBeTruthy();
106 | expect(mountComponent).toMatchSnapshot();
107 | });
108 |
109 | it('should have custom classes', () => {
110 | expect(mountComponent.find('td.day-test1')).toHaveLength(1);
111 | expect(mountComponent.find('td.day-test2')).toHaveLength(1);
112 | expect(mountComponent.find('td.day-test1 > div').text()).toEqual('2');
113 | expect(mountComponent.find('td.custom-day')).toHaveLength(2);
114 | });
115 |
116 | it('should have custom text', () => {
117 | expect(mountComponent.find('.sub__text')).toHaveLength(2);
118 | });
119 | });
120 |
121 | describe('prop: onClick', () => {
122 | let onClick: sinon.SinonSpy;
123 | beforeEach(() => {
124 | onClick = sinon.spy();
125 | mountComponent = mount( );
126 | });
127 |
128 | it('should fire click event', () => {
129 | mountComponent
130 | .find('td')
131 | .at(6)
132 | .simulate('click');
133 | expect(onClick).toHaveProperty('callCount', 1);
134 | });
135 | });
136 |
137 | describe('prop: onMouseOver', () => {
138 | let onMouseOver: sinon.SinonSpy;
139 | beforeEach(() => {
140 | onMouseOver = sinon.spy();
141 | mountComponent = mount( );
142 | });
143 |
144 | it('should fire click event', () => {
145 | mountComponent
146 | .find('td')
147 | .at(6)
148 | .simulate('mouseover');
149 | expect(onMouseOver).toHaveProperty('callCount', 1);
150 | });
151 | });
152 | });
153 |
--------------------------------------------------------------------------------
/test/LocaleUtil.test.ts:
--------------------------------------------------------------------------------
1 | import { getWeekDays } from '../src/utils/LocaleUtil';
2 | import 'dayjs/locale/en';
3 |
4 | describe('LocaleUtil', () => {
5 | it('should locale string getWeekdays return correctly', () => {
6 | expect(getWeekDays('en')[0]).toEqual('Sun');
7 | });
8 |
9 | it('should custom locale object getWeekdays return correctly', () => {
10 | const customObject = {
11 | name: 'ko',
12 | weekdays: '일요일_월요일_화요일_수요일_목요일_금요일_토요일'.split('_'),
13 | weekdaysShort: '일_월_화_수_목_금_토'.split('_'),
14 | months: '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'),
15 | };
16 | expect(getWeekDays(customObject)[0]).toEqual('일');
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/test/Picker.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { shallow, mount, ReactWrapper } from 'enzyme';
3 | import Picker, { Props, State } from '../src/components/Picker';
4 |
5 | describe(' ', () => {
6 | const defaultProps = {
7 | portal: false,
8 | className: 'test-picker',
9 | renderTrigger: () => test
,
10 | renderContents: () => contents
,
11 | };
12 |
13 | describe('show & hide', () => {
14 | let mountComponent: ReactWrapper;
15 |
16 | beforeEach(() => {
17 | mountComponent = mount( );
18 | });
19 |
20 | it('should trigger DOM click show backdrop & picker dialog', () => {
21 | mountComponent.find('.picker__trigger').simulate('click');
22 | expect(mountComponent.state('show')).toBeTruthy();
23 | });
24 |
25 | it('shoudl prop disabled click -> dilaog show not working', () => {
26 | mountComponent = mount( );
27 | mountComponent.find('.picker__trigger').simulate('click');
28 | expect(mountComponent.state('show')).toBeFalsy();
29 | });
30 |
31 | it('shoudl prop disabled click -> dilaog show not working', () => {
32 | mountComponent = mount( );
33 | mountComponent.find('.picker__trigger').simulate('click');
34 | expect(mountComponent.state('show')).toBeFalsy();
35 | });
36 |
37 | it('should show state & backdrop click -> hide dialog', () => {
38 | mountComponent.setState({
39 | show: true,
40 | });
41 | mountComponent.find('Backdrop').simulate('click');
42 | });
43 | });
44 |
45 | describe('position', () => {
46 | let mountComponent: ReactWrapper;
47 |
48 | it('should portal false setPosition correctly', () => {
49 | mountComponent = mount( );
50 | mountComponent.find('.picker__trigger').simulate('click');
51 |
52 | expect(mountComponent.state().position.left).not.toEqual('');
53 | expect(mountComponent.state().position.top).not.toEqual('');
54 | });
55 |
56 | it('should portal true position not set', () => {
57 | mountComponent = mount( );
58 | mountComponent.find('.picker__trigger').simulate('click');
59 | expect(mountComponent.state().position.left).toEqual('');
60 | expect(mountComponent.state().position.top).toEqual('');
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/PickerInput.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { mount, ReactWrapper } from 'enzyme';
3 | import PickerInput from '../src/components/PickerInput';
4 | import * as sinon from 'sinon';
5 |
6 | describe(' ', () => {
7 | let onChange: sinon.SinonSpy;
8 | let onClear: sinon.SinonSpy;
9 | let mountComponent: ReactWrapper;
10 |
11 | beforeEach(() => {
12 | onChange = sinon.spy();
13 | onClear = sinon.spy();
14 | mountComponent = mount( );
15 | });
16 |
17 | it('should props autoFocus correctly', () => {
18 | expect(mountComponent.find('input').getDOMNode()).toEqual(document.activeElement);
19 | });
20 |
21 | it('should onClear correctly', () => {
22 | mountComponent.find('.icon-clear').first().simulate('click');
23 | expect(onClear).toHaveProperty('callCount', 1);
24 | });
25 |
26 | it('should onChange correctly', () => {
27 | mountComponent.find('input').simulate('change');
28 | expect(onChange).toHaveProperty('callCount', 1);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/test/RangeDatePicker.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import * as dayjs from 'dayjs';
4 | import { shallow, mount, ReactWrapper } from 'enzyme';
5 | import RangeDatePicker, { Props, State } from '../src/components/RangeDatePicker';
6 | import { mountInputSimulateChange } from './utils/TestingUtil';
7 |
8 | const START_INPUT_CLASS = '.range-picker-input__start .picker-input__text';
9 | const END_INPUT_CLASS = '.range-picker-input__end .picker-input__text';
10 | const INPUT_FORMAT = 'YYYY-MM-DD';
11 |
12 | describe(' ', () => {
13 | const defaultProps = {
14 | initialDate: dayjs(new Date(2018, 4, 1)),
15 | };
16 |
17 | const pickerShow = (component: ReactWrapper) => {
18 | component.find('Picker').setState({
19 | show: true,
20 | });
21 | };
22 |
23 | describe('shallow test', () => {
24 | it('should render without crash', () => {
25 | const shallowComponent = shallow( );
26 | expect(shallowComponent).toBeTruthy();
27 | expect(shallowComponent).toMatchSnapshot();
28 | });
29 | });
30 |
31 | describe('handleDateChange', () => {
32 | let mountComponent: ReactWrapper;
33 | const dayClick = (calendarAt: number) => (dayAt: number) =>
34 | mountComponent
35 | .find('.calendar__item')
36 | .at(calendarAt)
37 | .find('td')
38 | .at(dayAt)
39 | .simulate('click');
40 |
41 | it('should first select then state start change', () => {
42 | mountComponent = mount( );
43 | mountComponent.find(START_INPUT_CLASS).simulate('click');
44 | dayClick(0)(2);
45 | const start = mountComponent.state('start');
46 | expect(start).not.toBeUndefined();
47 | if (start) {
48 | expect(dayjs(start).format('YYYYMMDD')).toEqual('20180501');
49 | }
50 | });
51 |
52 | it('should state start be set & select start before then state start change', () => {
53 | mountComponent = mount( );
54 | mountComponent.find(START_INPUT_CLASS).simulate('click');
55 | mountComponent.setState({
56 | start: dayjs(new Date(2018, 4, 5)),
57 | });
58 | dayClick(0)(2);
59 | const start = mountComponent.state('start');
60 | expect(start).not.toBeUndefined();
61 | if (start) {
62 | expect(dayjs(start).format('YYYYMMDD')).toEqual('20180501');
63 | }
64 | });
65 |
66 | it('should state start be set & select start after then state end change', () => {
67 | mountComponent = mount( );
68 | mountComponent.find(START_INPUT_CLASS).simulate('click');
69 | mountComponent.setState({
70 | start: dayjs(new Date(2018, 4, 2)),
71 | });
72 | dayClick(0)(5);
73 | const end = mountComponent.state('end');
74 | expect(end).not.toBeUndefined();
75 | if (end) {
76 | expect(dayjs(end).format('YYYYMMDD')).toEqual('20180504');
77 | }
78 | });
79 |
80 | it('should state start, end be set then state start change', () => {
81 | mountComponent = mount( );
82 | mountComponent.find(START_INPUT_CLASS).simulate('click');
83 | mountComponent.setState({
84 | start: dayjs(new Date(2019, 4, 2)),
85 | end: dayjs(new Date(2018, 4, 9)),
86 | });
87 | dayClick(0)(5);
88 | const start = mountComponent.state('start');
89 | const end = mountComponent.state('end');
90 | expect(start).not.toBeUndefined();
91 | expect(end).toBeUndefined();
92 | if (start) {
93 | expect(dayjs(start).format('YYYYMMDD')).toEqual('20180504');
94 | }
95 | });
96 |
97 | it('should prop onChange fire correctly', () => {
98 | const onChange = sinon.spy();
99 | mountComponent = mount( );
100 | mountComponent.find(START_INPUT_CLASS).simulate('click');
101 | dayClick(0)(5);
102 | expect(onChange).toHaveProperty('callCount', 1);
103 | });
104 | });
105 |
106 | describe('handleInputChange', () => {
107 | let mountComponent: ReactWrapper;
108 |
109 | beforeEach(() => {
110 | mountComponent = mount( );
111 | });
112 |
113 | it('should start & end input change set state value', () => {
114 | const startInput = mountComponent.find(START_INPUT_CLASS);
115 | const endInput = mountComponent.find(END_INPUT_CLASS);
116 | const testValue = 'test';
117 | mountInputSimulateChange(startInput, testValue);
118 | expect(mountComponent.state('startValue')).toEqual(testValue);
119 |
120 | mountInputSimulateChange(endInput, testValue);
121 | expect(mountComponent.state('endValue')).toEqual(testValue);
122 | });
123 | });
124 |
125 | describe('handleInputBlur', () => {
126 | let mountComponent: ReactWrapper;
127 | const start = dayjs(new Date(2018, 4, 1));
128 | const end = dayjs(new Date(2018, 4, 11));
129 |
130 | beforeEach(() => {
131 | mountComponent = mount( );
132 | });
133 |
134 | it('should state start & end undefined return empty value', () => {
135 | mountComponent.setState({
136 | ...mountComponent.state,
137 | start: undefined,
138 | end: undefined,
139 | });
140 |
141 | mountComponent.find(START_INPUT_CLASS).simulate('blur');
142 | expect(mountComponent.state('startValue')).toEqual('');
143 |
144 | mountComponent.find(END_INPUT_CLASS).simulate('blur');
145 | expect(mountComponent.state('endValue')).toEqual('');
146 | });
147 |
148 | it('should start input invalid value recover original date', () => {
149 | mountComponent.setState({
150 | ...mountComponent.state,
151 | start,
152 | end,
153 | startValue: '20183343j',
154 | endValue: dayjs(end).format(INPUT_FORMAT),
155 | });
156 |
157 | mountComponent.find(START_INPUT_CLASS).simulate('blur');
158 | expect(mountComponent.state('startValue')).toEqual('2018-05-01');
159 | });
160 |
161 | it('should start input correct value set state date', () => {
162 | const changedValue = '2018-04-23';
163 | mountComponent.setState({
164 | ...mountComponent.state,
165 | start,
166 | end,
167 | startValue: changedValue,
168 | endValue: dayjs(end).format(INPUT_FORMAT),
169 | });
170 |
171 | mountComponent.find(START_INPUT_CLASS).simulate('blur');
172 | const startState = mountComponent.state('start');
173 | expect(startState).not.toBeNull();
174 | if (startState) {
175 | expect(dayjs(startState).format(INPUT_FORMAT)).toEqual(changedValue);
176 | }
177 | });
178 |
179 | it('should end input invalid value recover original date', () => {
180 | mountComponent.setState({
181 | ...mountComponent.state,
182 | start,
183 | end,
184 | startValue: dayjs(start).format(INPUT_FORMAT),
185 | endValue: '201804244',
186 | });
187 |
188 | mountComponent.find(END_INPUT_CLASS).simulate('blur');
189 | expect(mountComponent.state('endValue')).toEqual('2018-05-11');
190 | });
191 |
192 | it('should end input correct value set state date', () => {
193 | const changedValue = '2018-05-23';
194 | mountComponent.setState({
195 | ...mountComponent.state,
196 | start,
197 | end,
198 | startValue: dayjs(start).format(INPUT_FORMAT),
199 | endValue: changedValue,
200 | });
201 |
202 | mountComponent.find(END_INPUT_CLASS).simulate('blur');
203 | const endState = mountComponent.state('end');
204 | expect(endState).not.toBeNull();
205 | if (endState) {
206 | expect(dayjs(endState).format(INPUT_FORMAT)).toEqual(changedValue);
207 | }
208 | });
209 |
210 | it('should startDate < endDate swap date', () => {
211 | const endInput = mountComponent.find(END_INPUT_CLASS);
212 | const testValue = '2018-04-23';
213 | // 2018-04-01
214 | const startValue = dayjs(start).format(INPUT_FORMAT);
215 | mountComponent.setState({
216 | ...mountComponent.state,
217 | start,
218 | end,
219 | startValue,
220 | endValue: testValue,
221 | });
222 |
223 | endInput.simulate('blur');
224 |
225 | const startState = mountComponent.state('start');
226 | const endState = mountComponent.state('end');
227 |
228 | expect(startState).not.toBeNull();
229 | expect(endState).not.toBeNull();
230 |
231 | if (startState && endState) {
232 | expect(dayjs(startState).format(INPUT_FORMAT)).toEqual(testValue);
233 | expect(dayjs(endState).format(INPUT_FORMAT)).toEqual(startValue);
234 | }
235 | });
236 |
237 | it('should startDate > endDate swap date', () => {
238 | const startInput = mountComponent.find(START_INPUT_CLASS);
239 | const testValue = '2018-06-23';
240 | // 2018-04-01
241 | const endValue = dayjs(end).format(INPUT_FORMAT);
242 | mountComponent.setState({
243 | ...mountComponent.state,
244 | start,
245 | end,
246 | endValue,
247 | startValue: testValue,
248 | });
249 |
250 | startInput.simulate('blur');
251 |
252 | const startState = mountComponent.state('start');
253 | const endState = mountComponent.state('end');
254 |
255 | expect(startState).not.toBeNull();
256 | expect(endState).not.toBeNull();
257 |
258 | if (startState && endState) {
259 | expect(dayjs(startState).format(INPUT_FORMAT)).toEqual(endValue);
260 | expect(dayjs(endState).format(INPUT_FORMAT)).toEqual(testValue);
261 | }
262 | });
263 | });
264 |
265 | describe('handleInputClear', () => {
266 | let mountComponent: ReactWrapper;
267 | const rangeInputClass = (value: string) => `.range-picker-input__${value}`;
268 | const start = dayjs(new Date(2018, 4, 1));
269 | const end = dayjs(new Date(2018, 4, 11));
270 |
271 | beforeEach(() => {
272 | mountComponent = mount( );
273 | });
274 |
275 | it('should start input clear click start & startValue init correctly', () => {
276 | mountComponent.setState({
277 | ...mountComponent.state,
278 | start,
279 | startValue: dayjs(start).format(INPUT_FORMAT),
280 | });
281 | mountComponent
282 | .find(rangeInputClass('start'))
283 | .find('.icon-clear')
284 | .first()
285 | .simulate('click');
286 |
287 | const startState = mountComponent.state('start');
288 | const startValue = mountComponent.state('startValue');
289 |
290 | if (startState) {
291 | expect(startState).toBeUndefined();
292 | expect(startValue).toEqual('');
293 | }
294 | });
295 |
296 | it('should end input clear click end & endValue init correctly', () => {
297 | mountComponent.setState({
298 | ...mountComponent.state,
299 | end,
300 | endValue: dayjs(start).format(INPUT_FORMAT),
301 | });
302 | mountComponent
303 | .find(rangeInputClass('end'))
304 | .find('.icon-clear')
305 | .first()
306 | .simulate('click');
307 |
308 | const endState = mountComponent.state('end');
309 | const endValue = mountComponent.state('endValue');
310 |
311 | if (endState) {
312 | expect(endState).toBeUndefined();
313 | expect(endValue).toEqual('');
314 | }
315 | });
316 | });
317 |
318 | describe('handleMouseOver', () => {
319 | let mountComponent: ReactWrapper;
320 |
321 | beforeEach(() => {
322 | mountComponent = mount( );
323 | });
324 |
325 | it('does mouseover event set state hoverDate', () => {
326 | pickerShow(mountComponent);
327 | mountComponent
328 | .find('td')
329 | .at(5)
330 | .simulate('mouseover');
331 | const hoverDate = mountComponent.state('hoverDate');
332 | expect(hoverDate).not.toBeUndefined();
333 | if (hoverDate) {
334 | expect(dayjs(hoverDate).format('YYYYMMDD')).toEqual('20180504');
335 | }
336 | });
337 |
338 | it('should mouseover range hover correctly', () => {
339 | pickerShow(mountComponent);
340 | mountComponent.setState({
341 | ...mountComponent.state,
342 | start: dayjs(new Date(2018, 4, 1)),
343 | });
344 |
345 | mountComponent
346 | .find('td')
347 | .at(5)
348 | .simulate('mouseover');
349 |
350 | expect(
351 | mountComponent
352 | .find('td')
353 | .at(4)
354 | .hasClass('calendar__day--range')
355 | ).toBeTruthy();
356 | });
357 | });
358 |
359 | describe('Calendar Wrapper', () => {
360 | let mountComponent: ReactWrapper;
361 |
362 | beforeEach(() => {
363 | const wrapper = (calendar: JSX.Element) => {
364 | const container = {calendar}
;
365 | return container;
366 | };
367 | mountComponent = mount( );
368 | });
369 |
370 | it('should calendar wrapper render without crash', () => {
371 | pickerShow(mountComponent);
372 | expect(mountComponent.find('.wrapper-test')).toHaveLength(1);
373 | });
374 | });
375 | });
376 |
--------------------------------------------------------------------------------
/test/RangePickerInput.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { shallow, mount, ShallowWrapper, ReactWrapper, HTMLAttributes } from 'enzyme';
4 | import RangePickerInput, { FieldType } from '../src/components/RangePickerInput';
5 |
6 | describe(' ', () => {
7 | let shallowComponent: ShallowWrapper;
8 |
9 | beforeEach(() => {
10 | shallowComponent = shallow( );
11 | });
12 |
13 | it('should render without crash', () => {
14 | expect(shallowComponent).toBeTruthy();
15 | expect(shallowComponent).toMatchSnapshot();
16 | });
17 |
18 | describe('event test', () => {
19 | let onChange: sinon.SinonSpy;
20 | let onClick: sinon.SinonSpy;
21 | let mountComponent: ReactWrapper;
22 | let startInput: ReactWrapper;
23 | let endInput: ReactWrapper;
24 |
25 | beforeEach(() => {
26 | onChange = sinon.spy();
27 | onClick = sinon.spy();
28 | mountComponent = mount( );
29 | startInput = mountComponent.find('.range-picker-input__start .picker-input__text');
30 | endInput = mountComponent.find('.range-picker-input__end .picker-input__text');
31 | });
32 |
33 | it('should onClick start & end operate separately', () => {
34 | startInput.simulate('click');
35 | expect(onClick.getCalls()[0].args[0]).toEqual(FieldType.START);
36 |
37 | endInput.simulate('click');
38 | expect(onClick.getCalls()[1].args[0]).toEqual(FieldType.END);
39 | expect(onClick).toHaveProperty('callCount', 2);
40 | });
41 |
42 | it('should onChange start & end operate separately', () => {
43 | startInput.simulate('change');
44 | expect(onChange.getCalls()[0].args[0]).toEqual(FieldType.START);
45 |
46 | endInput.simulate('change');
47 | expect(onChange.getCalls()[1].args[0]).toEqual(FieldType.END);
48 | expect(onChange).toHaveProperty('callCount', 2);
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/test/StringUtil.test.ts:
--------------------------------------------------------------------------------
1 | import { lpad } from '../src/utils/StringUtil';
2 |
3 | describe('StringUtil', () => {
4 | describe('lpad', () => {
5 | it('should str.length === length', () => {
6 | expect(lpad('33', 2)).toEqual('33');
7 | });
8 |
9 | it('should str.length < length', () => {
10 | expect(lpad('3', 2)).toEqual('03');
11 | });
12 |
13 | it('should specific char', () => {
14 | expect(lpad('3', 2, '*')).toEqual('*3');
15 | });
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/TableCell.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { shallow, ShallowWrapper } from 'enzyme';
4 | import TableCell from '../src/components/TableCell';
5 |
6 | describe(' ', () => {
7 | let component: ShallowWrapper;
8 | let onClick: sinon.SinonSpy;
9 | let onMouseOver: sinon.SinonSpy;
10 | beforeEach(() => {
11 | onClick = sinon.spy();
12 | onMouseOver = sinon.spy();
13 | component = shallow(
14 |
15 | );
16 | jest.resetModules();
17 | });
18 |
19 | it('renders with no props', () => {
20 | expect(component).toBeTruthy();
21 | expect(component).toMatchSnapshot();
22 | });
23 |
24 | it('props text correctly', () => {
25 | expect(component.find('div').text()).toEqual('test');
26 | });
27 |
28 | it('props onClick correctly', () => {
29 | component.simulate('click');
30 | expect(onClick).toHaveProperty('callCount', 1);
31 | });
32 |
33 | it('props onMouseOver correctly', () => {
34 | component.simulate('mouseover');
35 | expect(onMouseOver).toHaveProperty('callCount', 1);
36 | });
37 |
38 | it('props subText correctly', () => {
39 | expect(component.find('span')).toHaveLength(1);
40 | expect(component.find('span').text()).toEqual('test');
41 | });
42 |
43 | it('props className correctly', () => {
44 | expect(component.hasClass('test')).toBeTruthy();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/TableMatrixView.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { shallow, ShallowWrapper } from 'enzyme';
4 | import TableMatrixView from '../src/components/TableMatrixView';
5 |
6 | describe(' ', () => {
7 | let component: ShallowWrapper;
8 | beforeEach(() => {
9 | const arr = [['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']];
10 | const header = ['1', '2', '3'];
11 | component = shallow(
12 | value }
16 | />
17 | );
18 | });
19 |
20 | it('render with no props', () => {
21 | expect(component).toBeTruthy();
22 | expect(component).toMatchSnapshot();
23 | });
24 |
25 | it('props matrix correctly', () => {
26 | expect(component).toMatchSnapshot();
27 | expect(component.find('td')).toHaveLength(9);
28 | });
29 |
30 | it('props headers correctly', () => {
31 | expect(component.find('thead tr').children()).toHaveLength(3);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/test/TimeContainer.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { shallow, mount, ReactWrapper } from 'enzyme';
4 | import { mountInputSimulateChange } from './utils/TestingUtil';
5 | import TimeContainer from '../src/components/TimeContainer';
6 |
7 | describe(' ', () => {
8 | let mountComponent: ReactWrapper;
9 | let onChange: sinon.SinonSpy;
10 | let onBlur: sinon.SinonSpy;
11 |
12 | const btnClick = (type: string, at: number) => {
13 | mountComponent
14 | .find(`.time-input__${type} button`)
15 | .at(at)
16 | .simulate('click');
17 | };
18 |
19 | beforeEach(() => {
20 | onChange = sinon.spy();
21 | onBlur = sinon.spy();
22 | mountComponent = mount( );
23 | });
24 |
25 | it('should hour not be exceeded maximum value', () => {
26 | mountComponent.setState({
27 | ...mountComponent.state,
28 | hour: 23,
29 | });
30 | btnClick('up', 0);
31 | expect(mountComponent.state('hour')).toBe(23);
32 | });
33 |
34 | it('should hour not be below minimum value', () => {
35 | mountComponent.setState({
36 | ...mountComponent.state,
37 | hour: 0,
38 | });
39 | btnClick('down', 0);
40 | expect(mountComponent.state('hour')).toBe(0);
41 | });
42 |
43 | it('should minute not be exceeded maximum value', () => {
44 | mountComponent.setState({
45 | ...mountComponent.state,
46 | minute: 59,
47 | });
48 | btnClick('up', 1);
49 | expect(mountComponent.state('minute')).toBe(59);
50 | });
51 |
52 | it('should minute not be below minimum value', () => {
53 | mountComponent.setState({
54 | ...mountComponent.state,
55 | minute: 0,
56 | });
57 | btnClick('down', 0);
58 | expect(mountComponent.state('minute')).toBe(0);
59 | });
60 |
61 | it('should input change call onChnage func', () => {
62 | mountComponent
63 | .find('.time-input__text input')
64 | .at(0)
65 | .simulate('change');
66 |
67 | expect(onChange).toHaveProperty('callCount', 1);
68 | });
69 |
70 | it('should input blur call onBlur func', () => {
71 | mountComponent
72 | .find('.time-input__text input')
73 | .at(0)
74 | .simulate('blur');
75 |
76 | expect(onBlur).toHaveProperty('callCount', 1);
77 | });
78 |
79 | describe('handleChange', () => {
80 | it('should input non number set value 0', () => {
81 | mountInputSimulateChange(mountComponent.find('.time-input__text input').at(0), 'test');
82 | expect(mountComponent.state('hour')).toBe(0);
83 | });
84 |
85 | it('should input value exceeded maximum value set value max value', () => {
86 | mountInputSimulateChange(mountComponent.find('.time-input__text input').at(0), '40');
87 | expect(mountComponent.state('hour')).toBe(23);
88 | });
89 |
90 | it('should input value below minimum value set value min value', () => {
91 | mountInputSimulateChange(mountComponent.find('.time-input__text input').at(0), '-1');
92 | expect(mountComponent.state('hour')).toBe(0);
93 | });
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/test/TimeInput.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import { shallow, mount, ShallowWrapper, ReactWrapper } from 'enzyme';
4 | import TimeInput from '../src/components/TimeInput';
5 |
6 | describe(' ', () => {
7 | let onChange: sinon.SinonSpy;
8 | let onUp: sinon.SinonSpy;
9 | let onDown: sinon.SinonSpy;
10 | let onBlur: sinon.SinonSpy;
11 |
12 | let shallowComponent: ShallowWrapper;
13 |
14 | beforeEach(() => {
15 | onUp = sinon.spy();
16 | onDown = sinon.spy();
17 | onChange = sinon.spy();
18 | onBlur = sinon.spy();
19 | shallowComponent = shallow(
20 |
21 | );
22 | });
23 |
24 | it('should onUp called correctly', () => {
25 | shallowComponent.find('.time-input__up button').simulate('click');
26 | expect(onUp).toHaveProperty('callCount', 1);
27 | });
28 |
29 | it('should onDown called correctly', () => {
30 | shallowComponent.find('.time-input__down button').simulate('click');
31 | expect(onDown).toHaveProperty('callCount', 1);
32 | });
33 |
34 | it('should onChange called correctly', () => {
35 | shallowComponent.find('.time-input__text input').simulate('change');
36 | expect(onChange).toHaveProperty('callCount', 1);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/TodayPanel.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as sinon from 'sinon';
3 | import TodayPanel from '../src/components/TodayPanel';
4 | import { shallow, ShallowWrapper } from 'enzyme';
5 |
6 | describe(' ', () => {
7 | let component: ShallowWrapper;
8 | let onClick: sinon.SinonSpy;
9 |
10 | beforeEach(() => {
11 | onClick = sinon.spy();
12 | component = shallow( );
13 | });
14 |
15 | it('renders without props', () => {
16 | expect(component).toBeTruthy();
17 | expect(component).toMatchSnapshot();
18 | });
19 |
20 | it('props show correctly', () => {
21 | expect(component.hasClass('calendar__panel--show')).toBeTruthy();
22 | });
23 |
24 | it('props onClick correctly', () => {
25 | component.find('h2').simulate('click');
26 | expect(onClick).toHaveProperty('callCount', 1);
27 | });
28 |
29 | it('props today correctly', () => {
30 | expect(component.find('h2').text()).toEqual('20190101');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/__snapshots__/Calendar.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` redners with no props 1`] = `
4 |
28 | `;
29 |
--------------------------------------------------------------------------------
/test/__snapshots__/CalendarBody.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should render correctly 1`] = `
4 |
7 |
13 |
14 | `;
15 |
--------------------------------------------------------------------------------
/test/__snapshots__/CalendarContainer.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` prop: locale should locale prop correctly 1`] = `
4 |
13 |
16 |
22 |
25 |
28 |
32 | 2018.12
33 |
34 |
37 |
38 |
39 |
45 |
48 |
54 |
126 |
129 |
130 |
131 |
134 | 일
135 |
136 |
139 | 월
140 |
141 |
144 | 화
145 |
146 |
149 | 수
150 |
151 |
154 | 목
155 |
156 |
159 | 금
160 |
161 |
164 | 토
165 |
166 |
167 |
168 |
169 |
172 |
180 |
185 |
186 |
187 |
188 |
189 | |
190 |
198 |
203 |
204 |
205 |
206 |
207 | |
208 |
216 |
221 |
222 |
223 |
224 |
225 | |
226 |
234 |
239 |
240 |
241 |
242 |
243 | |
244 |
252 |
257 |
258 |
259 |
260 |
261 | |
262 |
270 |
275 |
276 |
277 |
278 |
279 | |
280 |
288 |
293 |
294 | 1
295 |
296 |
297 | |
298 |
299 |
302 |
310 |
315 |
316 | 2
317 |
318 |
319 | |
320 |
328 |
333 |
334 | 3
335 |
336 |
337 | |
338 |
346 |
351 |
352 | 4
353 |
354 |
355 | |
356 |
364 |
369 |
370 | 5
371 |
372 |
373 | |
374 |
382 |
387 |
388 | 6
389 |
390 |
391 | |
392 |
400 |
405 |
406 | 7
407 |
408 |
409 | |
410 |
418 |
423 |
424 | 8
425 |
426 |
427 | |
428 |
429 |
432 |
440 |
445 |
446 | 9
447 |
448 |
449 | |
450 |
458 |
463 |
464 | 10
465 |
466 |
467 | |
468 |
476 |
481 |
482 | 11
483 |
484 |
485 | |
486 |
494 |
499 |
500 | 12
501 |
502 |
503 | |
504 |
512 |
517 |
518 | 13
519 |
520 |
521 | |
522 |
530 |
535 |
536 | 14
537 |
538 |
539 | |
540 |
548 |
553 |
554 | 15
555 |
556 |
557 | |
558 |
559 |
562 |
570 |
575 |
576 | 16
577 |
578 |
579 | |
580 |
588 |
593 |
594 | 17
595 |
596 |
597 | |
598 |
606 |
611 |
612 | 18
613 |
614 |
615 | |
616 |
624 |
629 |
630 | 19
631 |
632 |
633 | |
634 |
642 |
647 |
648 | 20
649 |
650 |
651 | |
652 |
660 |
665 |
666 | 21
667 |
668 |
669 | |
670 |
678 |
683 |
684 | 22
685 |
686 |
687 | |
688 |
689 |
692 |
700 |
705 |
706 | 23
707 |
708 |
709 | |
710 |
718 |
723 |
724 | 24
725 |
726 |
727 | |
728 |
736 |
741 |
742 | 25
743 |
744 |
745 | |
746 |
754 |
759 |
760 | 26
761 |
762 |
763 | |
764 |
772 |
777 |
778 | 27
779 |
780 |
781 | |
782 |
790 |
795 |
796 | 28
797 |
798 |
799 | |
800 |
808 |
813 |
814 | 29
815 |
816 |
817 | |
818 |
819 |
822 |
830 |
835 |
836 | 30
837 |
838 |
839 | |
840 |
848 |
853 |
854 | 31
855 |
856 |
857 | |
858 |
866 |
871 |
872 |
873 |
874 |
875 | |
876 |
884 |
889 |
890 |
891 |
892 |
893 | |
894 |
902 |
907 |
908 |
909 |
910 |
911 | |
912 |
920 |
925 |
926 |
927 |
928 |
929 | |
930 |
938 |
943 |
944 |
945 |
946 |
947 | |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
957 | `;
958 |
959 | exports[` prop: show should render correctly 1`] = `
960 |
963 |
969 |
975 |
976 | `;
977 |
--------------------------------------------------------------------------------
/test/__snapshots__/CalendarHead.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should props onPrev, onNext correctly 1`] = `
4 |
7 |
10 |
15 |
20 |
21 |
22 |
25 |
28 |
33 |
38 |
39 |
40 |
41 | `;
42 |
43 | exports[` should props prevIcon, nextIcon correctly 1`] = `
44 |
47 |
50 |
54 |
59 |
60 |
61 |
64 |
67 |
71 |
76 |
77 |
78 |
79 | `;
80 |
81 | exports[` should props title correctly 1`] = `
82 |
85 |
88 |
91 | 2019/01
92 |
93 |
96 |
97 | `;
98 |
99 | exports[` should render correctly 1`] = `
100 |
113 | `;
114 |
--------------------------------------------------------------------------------
/test/__snapshots__/RangeDatePicker.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` shallow test should render without crash 1`] = `
4 |
9 | `;
10 |
--------------------------------------------------------------------------------
/test/__snapshots__/RangePickerInput.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should render without crash 1`] = `
4 |
7 |
10 |
17 |
18 |
21 |
26 |
27 |
30 |
37 |
38 |
39 | `;
40 |
--------------------------------------------------------------------------------
/test/__snapshots__/TableCell.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` renders with no props 1`] = `
4 |
9 |
10 | test
11 |
12 |
15 | test
16 |
17 |
18 | `;
19 |
--------------------------------------------------------------------------------
/test/__snapshots__/TableMatrixView.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` props matrix correctly 1`] = `
4 |
7 |
8 |
9 |
12 | 1
13 |
14 |
17 | 2
18 |
19 |
22 | 3
23 |
24 |
25 |
26 |
27 |
30 |
33 | value
34 |
35 |
38 | value
39 |
40 |
43 | value
44 |
45 |
46 |
49 |
52 | value
53 |
54 |
57 | value
58 |
59 |
62 | value
63 |
64 |
65 |
68 |
71 | value
72 |
73 |
76 | value
77 |
78 |
81 | value
82 |
83 |
84 |
85 |
86 | `;
87 |
88 | exports[` render with no props 1`] = `
89 |
92 |
93 |
94 |
97 | 1
98 |
99 |
102 | 2
103 |
104 |
107 | 3
108 |
109 |
110 |
111 |
112 |
115 |
118 | value
119 |
120 |
123 | value
124 |
125 |
128 | value
129 |
130 |
131 |
134 |
137 | value
138 |
139 |
142 | value
143 |
144 |
147 | value
148 |
149 |
150 |
153 |
156 | value
157 |
158 |
161 | value
162 |
163 |
166 | value
167 |
168 |
169 |
170 |
171 | `;
172 |
--------------------------------------------------------------------------------
/test/__snapshots__/TodayPanel.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` renders without props 1`] = `
4 |
7 |
10 | 20190101
11 |
12 |
13 | `;
14 |
--------------------------------------------------------------------------------
/test/utils/TestingUtil.ts:
--------------------------------------------------------------------------------
1 | import { ReactWrapper } from 'enzyme';
2 |
3 | /**
4 | * mount input simulate change value utility
5 | * @description
6 | * shallow render case don't use this func
7 | * @param node - enzyme wrapper
8 | * @param testValue - change value
9 | */
10 | export const mountInputSimulateChange = (node: ReactWrapper, testValue: string) => {
11 | (node.getDOMNode() as HTMLInputElement).value = testValue;
12 | node.simulate('change');
13 | };
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "lib",
4 | "module": "commonjs",
5 | "target": "es5",
6 | "lib": ["es6", "dom"],
7 | "sourceMap": true,
8 | "jsx": "react",
9 | "moduleResolution": "node",
10 | "rootDirs": ["./src", "./examples"],
11 | "noImplicitReturns": true,
12 | "noImplicitThis": true,
13 | "noImplicitAny": true,
14 | "strictNullChecks": true,
15 | "suppressImplicitAnyIndexErrors": true,
16 | "declaration": true,
17 | "experimentalDecorators": true
18 | },
19 | "include": [
20 | "src/**/*.ts"
21 | ],
22 | "exclude": [
23 | "node_modules",
24 | "build",
25 | "scripts",
26 | "acceptance-tests",
27 | "webpack",
28 | "jest",
29 | "src/setupTests.ts"
30 | ],
31 | "types": [
32 | "typePatches"
33 | ]
34 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:recommended",
4 | "tslint-react",
5 | "tslint-config-airbnb",
6 | "tslint-config-prettier"
7 | ],
8 | "linterOptions": {
9 | "exclude": [
10 | "config/**/*.js",
11 | "node_modules/**/*.ts",
12 | "coverage/lcov-report/*.js"
13 | ]
14 | },
15 | "rules": {
16 | "import-name": false,
17 | "interface-name": false,
18 | "semicolon": [true, "always", "ignore-bound-class-methods"],
19 | "variable-name": false,
20 | "jsx-boolean-value": false,
21 | "jsx-no-lambda": false,
22 | "no-namespace": false,
23 | "no-empty-interface": false,
24 | "object-literal-sort-keys": false,
25 | "ordered-imports": false,
26 | "no-empty": false
27 | }
28 | }
29 |
--------------------------------------------------------------------------------