Skip to content

Commit

Permalink
feat(date-field): add new DateField component
Browse files Browse the repository at this point in the history
  • Loading branch information
dackmin committed Jun 9, 2020
1 parent c72ef37 commit ee0cc52
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 58 deletions.
94 changes: 64 additions & 30 deletions lib/DateTimeField/index.js → lib/DateField/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import React, {
forwardRef,
useEffect,
useRef,
useReducer,
useImperativeHandle,
} from 'react';
import PropTypes from 'prop-types';

import { classNames, mockState } from '../utils';
import {
classNames,
mockState,
startOfMonth,
subMonths,
addMonths,
getDaysInMonth,
} from '../utils';
import TextField from '../TextField';
import Dropdown from '../Dropdown';
import DropdownToggle from '../DropdownToggle';
import DropdownMenu from '../DropdownMenu';

const DateTimeField = forwardRef(({
const DateField = forwardRef(({
animateMenu,
autoFocus,
className,
label,
max,
Expand All @@ -25,19 +34,36 @@ const DateTimeField = forwardRef(({
'July', 'August', 'September', 'October', 'November', 'December'],
required = false,
weekDaysNames = ['Mon', 'Tue', 'Wen', 'Thu', 'Fri', 'Sat', 'Sun'],
onChange = () => {},
onFocus = () => {},
parseTitle = val => val?.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}),
parseValue = val => val,
validate = val => typeof val !== 'undefined',
}, ref) => {
const innerRef = useRef();
const dropdownRef = useRef();
const inputRef = useRef();
const [state, dispatch] = useReducer(mockState, {
value,
displayed: value || new Date(),
selected: value || new Date(),
valid: false,
});

useEffect(() => {
if (autoFocus) {
dropdownRef.current?.open();
}
}, []);

useImperativeHandle(ref, () => ({
innerRef,
dropdownRef,
}));

const onFocus_ = e => {
Expand All @@ -48,7 +74,20 @@ const DateTimeField = forwardRef(({
const onChange_ = (date, e) => {
e.preventDefault();

dispatch({ selected: date, value: date });
state.value = date;
state.valid = validate(date);

dispatch({
valid: state.valid,
selected: state.value,
value: state.value,
displayed: state.value,
});

dropdownRef.current?.close();
inputRef.current?.setDirty(true);

onChange({ value: parseValue(state.value), valid: state.valid });
};

const onPreviousMonthClick = e => {
Expand Down Expand Up @@ -79,9 +118,6 @@ const DateTimeField = forwardRef(({
dispatch({ displayed: state.displayed });
};

const isEmpty = () =>
!state.value;

const isBeforeMinDate = date => {
if (!min) {
return false;
Expand Down Expand Up @@ -110,42 +146,30 @@ const DateTimeField = forwardRef(({
const getMonthName = month =>
monthNames[month];

const getDaysCount = date =>
new Date(date.getFullYear(), date.getMonth(), 0).getDate();

const getWeekDayOfMonth = date => {
const weekDay = date.getDay();
return weekDay === 0 ? 7 : weekDay;
};

const getMonthDays = (date, props = {}) => Array
.from({ length: getDaysCount(date) }, (v, k) => ({
.from({ length: getDaysInMonth(date) }, (v, k) => ({
date: new Date(date.getFullYear(), date.getMonth(), k + 1),
...props,
}));

const getPreviousMonthDays = date => {
const previousDate = new Date(
date.getMonth() === 0 ? date.getFullYear() - 1 : date.getFullYear(),
date.getMonth() === 0 ? 11 : date.getMonth() - 1,
0
);

const selectedWeekDay = getWeekDayOfMonth(date);
const selectedDaysCount = getDaysCount(previousDate);
const previousDate = startOfMonth(subMonths(date, 1));
const selectedWeekDay = getWeekDayOfMonth(startOfMonth(date));
const selectedDaysCount = getDaysInMonth(previousDate);

return getMonthDays(previousDate, { inactive: true })
.slice(selectedDaysCount - (selectedWeekDay - 1));
};

const getNextMonthDays = date => {
const nextDate = new Date(
date.getMonth() === 11 ? date.getFullYear() + 1 : date.getFullYear(),
date.getMonth() === 11 ? 0 : date.getMonth() + 1,
0
);
const daysCount = getDaysCount(date);
const weekDay = getWeekDayOfMonth(date) - 1;
const nextDate = startOfMonth(addMonths(date, 1));
const daysCount = getDaysInMonth(date);
const weekDay = getWeekDayOfMonth(startOfMonth(date)) - 1;

return Array
.from({ length: 42 - daysCount - weekDay }, (v, k) => ({
Expand All @@ -159,17 +183,22 @@ const DateTimeField = forwardRef(({
className={classNames(
'junipero',
'field',
'date-time-picker',
'date-picker',
)}
ref={innerRef}
>
<Dropdown ref={dropdownRef}>
<Dropdown disabled={disabled} ref={dropdownRef}>
<DropdownToggle trigger="manual" href={null} tag="div">
<TextField
ref={inputRef}
disabled={disabled}
value={state.value}
placeholder={placeholder}
label={label}
value={parseTitle(state.value)}
valid={state.valid}
validate={() => state.valid}
readOnly={true}
autoFocus={autoFocus}
onFocus={onFocus_}
/>
</DropdownToggle>
Expand Down Expand Up @@ -239,7 +268,8 @@ const DateTimeField = forwardRef(({
);
});

DateTimeField.propTypes = {
DateField.propTypes = {
autoFocus: PropTypes.bool,
animateMenu: PropTypes.func,
disabled: PropTypes.bool,
label: PropTypes.oneOfType([
Expand All @@ -252,16 +282,20 @@ DateTimeField.propTypes = {
max: PropTypes.instanceOf(Date),
min: PropTypes.instanceOf(Date),
monthNames: PropTypes.array,
onChange: PropTypes.func,
onFocus: PropTypes.func,
placeholder: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.node,
PropTypes.func,
]),
parseTitle: PropTypes.func,
parseValue: PropTypes.func,
required: PropTypes.bool,
validate: PropTypes.func,
value: PropTypes.instanceOf(Date),
weekDaysNames: PropTypes.array,
};

export default DateTimeField;
export default DateField;
45 changes: 45 additions & 0 deletions lib/DateField/index.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { action } from '@storybook/addon-actions';

import DateField from './index';

export default { title: 'DateField' };

export const basic = () => (
<DateField placeholder="Date of birth" onChange={action('change')} />
);

export const withPlaceholder = () => (
<DateField
placeholder="Select a date"
label="Chosen date"
onChange={action('change')}
/>
);

export const autoFocused = () => (
<DateField autoFocus onChange={action('change')} />
);

export const withValue = () => (
<DateField value={new Date(2019, 0, 1)} onChange={action('change')} />
);

export const disabled = () => (
<DateField disabled value={new Date()} />
);

export const withValidation = () => (
<DateField
validate={val => val?.getFullYear() === 2020}
onChange={action('change')}
/>
);

export const withMinAndMax = () => (
<DateField
min={new Date(2020, 0, 1)}
max={new Date(2020, 11, 1)}
onChange={action('change')}
/>
);
2 changes: 1 addition & 1 deletion lib/DateTimeField/index.styl → lib/DateField/index.styl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.junipero.date-time-picker
.junipero.date-picker
display: inline-block

input
Expand Down
26 changes: 0 additions & 26 deletions lib/DateTimeField/index.stories.js

This file was deleted.

2 changes: 1 addition & 1 deletion lib/index.styl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
@import "./CheckboxField"
@import "./CodeField"
@import "./ColorField"
@import "./DateTimeField"
@import "./DateField"
@import "./Dropdown"
@import "./DropdownToggle"
@import "./DropdownMenu"
Expand Down
40 changes: 40 additions & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,43 @@ export const stringifyColor = (color, format = 'auto') => {
return rgba2hex(denormalizeRGBA(hsva2rgba(color)));
}
};

export const startOfMonth = date => {
const d = new Date(date);
d.setDate(1);
return d;
};

export const endOfMonth = date => {
const d = addMonths(date, 1);
d.setDate(0);
return d;
};

export const addMonths = (date, amount) => {
const d = new Date(date);

if (d.getMonth() === 11) {
d.setFullYear(d.getFullYear() + 1);
}

d.setMonth(d.getMonth() === 11 ? 0 : d.getMonth() + 1);

return d;
};

export const subMonths = (date, amount) => {
const d = new Date(date);

if (d.getMonth() === 0) {
d.setFullYear(d.getFullYear() - 1);
}

d.setMonth(d.getMonth() === 0 ? 11 : d.getMonth() - 1);

return d;
};

export const getDaysInMonth = date => {
return endOfMonth(date).getDate();
};

0 comments on commit ee0cc52

Please sign in to comment.