Skip to content

Commit

Permalink
feat(DateInput): next
Browse files Browse the repository at this point in the history
  • Loading branch information
zouxuoz committed Nov 22, 2018
1 parent 67bb02f commit dd7961b
Show file tree
Hide file tree
Showing 12 changed files with 866 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Expand Up @@ -3,5 +3,5 @@
"react-app",
"flow"
],
"plugins": ["inline-react-svg", "flow-react-proptypes", "jsx-control-statements", "emotion"]
"plugins": ["inline-react-svg", "flow-react-proptypes", "jsx-control-statements", "emotion", "babel-plugin-react-css-modules"]
}
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -42,6 +42,7 @@
"babel-plugin-flow-react-proptypes": "^23.0.0",
"babel-plugin-inline-react-svg": "^0.5.2",
"babel-plugin-jsx-control-statements": "^3.2.8",
"babel-plugin-react-css-modules": "3.4.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-react-app": "^3.1.1",
"babel-runtime": "^6.26.0",
Expand Down Expand Up @@ -72,10 +73,10 @@
"color": "^3.0.0",
"emotion": "^9.1.1",
"emotion-theming": "^9.1.2",
"moment": "^2.22.2",
"luxon": "^1.7.1",
"prop-types": "^15.6.1",
"react-datepicker": "^1.7.0",
"react-input-mask": "^2.0.1",
"react-datepicker": "^2.0.0",
"react-input-mask": "^2.0.4",
"react-onclickoutside": "^6.7.1",
"react-popper": "^0.10.1",
"react-portal": "^4.1.5",
Expand Down
137 changes: 110 additions & 27 deletions src/atoms/dataEntry/DateInput/DateInput.js
Expand Up @@ -2,57 +2,140 @@

import React from 'react';
import DatePicker from 'react-datepicker';
import moment from 'moment';
import { injectGlobal } from 'emotion';
import 'react-datepicker/dist/react-datepicker.css';
import 'react-datepicker/dist/react-datepicker-cssmodules.css';

import { createStyledTag, createTheme } from '../../../utils';
import { DateInputValue } from './DateInputValue';
import { Dropdown } from '../../Dropdown';
import * as utils from './DateInput.utils';

injectGlobal(`
.react-datepicker-wrapper, .react-datepicker__input-container {
display: block;
injectGlobal`
.react-datepicker__time-list-item {
display: flex;
justify-content: center;
align-items: center;
}
`);
`;

type DateInputProps = {|
onChange: (value: ?string) => void,
value: string,
value: ?string,
withTime?: boolean,
stretch?: boolean,
|};

const name = 'dateInput';
type DateInputState = {|
isOpen: boolean,
textValue: ?string,
|};

class DateInput extends React.Component<DateInputProps, DateInputState> {
static defaultProps = {
stretch: true,
};

constructor(props: DateInputProps) {
super(props);

this.state = {
textValue: utils.fromISOToViewFormat(props.value, props.withTime),
isOpen: false,
};
}

componentDidUpdate(prevProps: DateInputProps) {
const { value, withTime } = this.props;

if (value && value !== prevProps.value) {
this.setState({
textValue: utils.fromISOToViewFormat(value, withTime),
});
}
}

onChangeText = ({ target: { value }}: Object) => {
const { withTime } = this.props;

this.setState({ textValue: value });

if (!value) {
this.props.onChange(value);

return;
}

const theme = createTheme(name, {});
if (value) {
const luxonValue = utils.fromViewFormatToLuxon(value, withTime);

const DateInputTag = createStyledTag(name, {
display: 'flex',
width: '100%',
'> div': {
flex: '1',
},
});
if (luxonValue && luxonValue.isValid) {
value = utils.fromLuxonToISO(luxonValue, withTime);

class DateInput extends React.Component<DateInputProps> {
onChange = (value: any) => {
this.props.onChange(value ? value.format('YYYY-MM-DD') : null);
this.props.onChange(value);
}
}
};

onChangeDate = (selected: Date) => {
const { withTime, value } = this.props;

const isoValue = utils.fromJSDateToISO(selected, withTime);

this.props.onChange(isoValue);

if (!withTime || Math.abs(selected - (value ? new Date(value) : 0)) <= 24 * 60 * 60 * 1000) {
this.close();
}
};

collectProps() {
const { value } = this.props;
const { value, withTime, ...rest } = this.props;

const dateFormat = withTime ? utils.DATETIME_FORMAT : utils.DATE_FORMAT;

return {
selected: value ? moment(value) : null,
onChange: this.onChange,
// $FlowFixMe
customInput: <DateInputValue />,
selected: value ? new Date(value) : null,
dateFormat,
...rest,
showTimeSelect: withTime,
onChange: this.onChangeDate,
inline: true,
};
}

open = () => {
this.setState({ isOpen: true });
};

close = () => {
this.setState({ isOpen: false });
};

render() {
const collectedProps = this.collectProps();

return <DateInputTag><DatePicker { ...collectedProps } /></DateInputTag>;
const { value, withTime, stretch, onChange, ...rest } = this.props;

const { textValue, isOpen } = this.state;

const mask = withTime ? utils.DATETIME_MASK : utils.DATE_MASK;

return (
<Dropdown.Plate
isOpen={ isOpen }
stretch={ stretch }
onCloseDropdown={ this.close }
onOpenDropdown={ this.open }
{ ...rest }
>
<Dropdown.Head onClick={ this.open }>
<DateInputValue mask={ mask } value={ textValue } onChange={ this.onChangeText } />
</Dropdown.Head>
<Dropdown.Body withPortal preventOverflow>
<DatePicker { ...collectedProps } />
</Dropdown.Body>
</Dropdown.Plate>
);
}
}

export { DateInput, theme };
export { DateInput };
26 changes: 25 additions & 1 deletion src/atoms/dataEntry/DateInput/DateInput.stories.js
Expand Up @@ -2,9 +2,33 @@ import React from 'react';

export default (asStory) => {
asStory('ATOMS/DATA ENTRY/DateInput', module, (story, { DateInput }) => {
class DateInputContainer extends React.Component {
constructor(props) {
super(props);

this.state = { value: props.value };
}

onChange = (value) => {
// eslint-disable-next-line
console.log(value);
this.setState({ value });
};

render() {
const { value } = this.state;

return <DateInput { ...this.props } value={ value } onChange={ this.onChange } />;
}
}

story
.add('default', () => (
<DateInput value="11/02/2018" onChange={ () => null } />
<React.Fragment>
<DateInputContainer value={ null } />
<DateInputContainer value="2018-11-07" />
<DateInputContainer value={ null } withTime />
</React.Fragment>
));
});
};
Expand Down
51 changes: 51 additions & 0 deletions src/atoms/dataEntry/DateInput/DateInput.utils.js
@@ -0,0 +1,51 @@
// @flow

import { DateTime } from 'luxon';

export const DATE_MASK = '99/99/9999';
export const DATETIME_MASK = '99/99/9999, 99:99 aa';

export const DATE_FORMAT = 'MM/dd/yyyy';
export const DATETIME_FORMAT = 'MM/dd/yyyy, hh:mm a';

export const fromISOToViewFormat = (value: ?string, withTime: ?boolean) => {
if (value) {
value = DateTime.fromISO(value).toFormat(withTime ? DATETIME_FORMAT : DATE_FORMAT);
}

return value;
};

export const fromJSDateToISO = (value: ?Date, withTime: ?boolean) => {
if (value) {
value = DateTime.fromJSDate(value).toISO();

if (!withTime) {
value = value.slice(0, 10);
}
}

return value;
};

export const fromViewFormatToLuxon = (value: ?string, withTime: ?boolean) => {
if (value) {
value = DateTime.fromFormat(value, withTime ? DATETIME_FORMAT : DATE_FORMAT);
}

return value;
};

export const fromLuxonToISO = (value: ?Object, withTime: ?boolean) => {
if (value) {
value = value.toISO();

if (!withTime) {
value = value.slice(0, 10);
}
} else {
value = null;
}

return value;
};
3 changes: 2 additions & 1 deletion src/atoms/dataEntry/DateInput/DateInputValue.js
Expand Up @@ -7,7 +7,8 @@ import { Input } from '../Input';

type DateInputValueProps = {|
onChange: (value: Object) => void,
value: string,
value: ?string,
mask: string,
|};

const DateInputValue = ({ value, onChange, ...props }: DateInputValueProps) => (
Expand Down
2 changes: 1 addition & 1 deletion src/atoms/dataEntry/DateInput/index.js
@@ -1,3 +1,3 @@
// @flow

export { theme, DateInput } from './DateInput';
export { DateInput } from './DateInput';
35 changes: 35 additions & 0 deletions src/atoms/dataEntry/DateInputField/DateInputField.stories.js
@@ -0,0 +1,35 @@
import React from 'react';

export default (asStory) => {
asStory('ATOMS/DATA ENTRY/DateInputField', module, (story, { DateInputField }) => {
class DateInputFieldContainer extends React.Component {
constructor(props) {
super(props);

this.state = { value: props.value };
}

onChange = (value) => {
// eslint-disable-next-line
console.log(value);
this.setState({ value });
};

render() {
const { value } = this.state;

return <DateInputField { ...this.props } input={{ value, onChange: this.onChange }} />;
}
}

story
.add('default', () => (
<React.Fragment>
<DateInputFieldContainer label="Date" value={ null } />
<DateInputFieldContainer label="Date" value="2018-11-07" />
<DateInputFieldContainer label="Datetime" value={ null } withTime />
</React.Fragment>
));
});
};

2 changes: 1 addition & 1 deletion src/atoms/dataEntry/Input/Input.js
Expand Up @@ -36,7 +36,7 @@ type InputProps = {
/** input name */
name?: string,
/** input value */
value?: string | number,
value?: ?string | ?number,
/** possible input types */
type?: 'text' | 'number' | 'password' | 'email' | 'url',
/** then true when show error styles */
Expand Down
4 changes: 0 additions & 4 deletions src/atoms/theme.js
Expand Up @@ -32,8 +32,6 @@ import { theme as switchTheme } from './dataEntry/Switch';
import { theme as textTheme } from './typography/Text';
import { theme as loaderTheme } from './Loader';
import { theme as tableTheme } from './Table';
import { theme as dateInputFieldTheme } from './dataEntry/DateInputField';
import { theme as dateInputTheme } from './dataEntry/DateInput';
import { theme as secondaryNavigationTheme } from './SecondaryNavigation';

export const theme = {
Expand Down Expand Up @@ -69,7 +67,5 @@ export const theme = {
...loaderTheme,
...tableTheme,
...switchTheme,
...dateInputFieldTheme,
...dateInputTheme,
...secondaryNavigationTheme,
};

0 comments on commit dd7961b

Please sign in to comment.