-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1250 from cozy/date-month-picker
feat: DateMonthPicker
- Loading branch information
Showing
5 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
``` | ||
import I18n from 'cozy-ui/transpiled/react/I18n'; | ||
import DateMonthPicker from 'cozy-ui/transpiled/react/DateMonthPicker'; | ||
import Stack from 'cozy-ui/transpiled/react/Stack'; | ||
import Button from 'cozy-ui/transpiled/react/Button'; | ||
import Modal from 'cozy-ui/transpiled/react/Modal'; | ||
const dictRequire = x => ({}) | ||
const initialState = { choosing: isTesting(), monthDate: '2019-08' } | ||
const showPicker = () => setState({ choosing: true }); | ||
const hidePicker = () => setState({ choosing: false }); | ||
const handleSelect = monthDate => { | ||
setState({ monthDate }) | ||
hidePicker() | ||
} | ||
<I18n dictRequire={dictRequire} lang='en'> | ||
<Stack> | ||
Month chosen: { state.monthDate ? state.monthDate : 'No date chosen yet'}<br/> | ||
<Button onClick={showPicker} label='Choose month'/> | ||
{ state.choosing ? <Modal dismissAction={hidePicker}> | ||
<DateMonthPicker | ||
f={x => x} | ||
onSelect={handleSelect} | ||
initialValue={state.monthDate} | ||
/> | ||
<div className='u-mb-2'>{' '}</div> | ||
</Modal>: null } | ||
</Stack> | ||
</I18n> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import React, { useState } from 'react' | ||
import PropTypes from 'prop-types' | ||
import Button from '../Button' | ||
import Icon from '../Icon' | ||
import { translate } from '../I18n' | ||
import range from 'lodash/range' | ||
import { format } from 'date-fns' | ||
import styles from './styles.styl' | ||
import cx from 'classnames' | ||
|
||
const MonthButton = translate()(({ monthNum, f, onClick, isSelected }) => { | ||
const d = new Date(2019, monthNum, 15) | ||
const handleClick = () => { | ||
onClick(monthNum) | ||
} | ||
return ( | ||
<Button | ||
theme="secondary" | ||
className={cx( | ||
styles.DateMonthPicker__MonthButton, | ||
isSelected ? styles.ValueSelected : null | ||
)} | ||
onClick={handleClick} | ||
label={f(d, 'MMM')} | ||
/> | ||
) | ||
}) | ||
|
||
const useCounter = (initialValue, min, max) => { | ||
const [state, setState] = useState(initialValue) | ||
const increment = () => setState(Math.min(state + 1, max)) | ||
const decrement = () => setState(Math.max(state - 1, min)) | ||
return [state, decrement, increment] | ||
} | ||
|
||
const DateMonthPicker = ({ initialValue, onSelect }) => { | ||
const [initialYear, initialMonth] = initialValue | ||
.split('-') | ||
.map(x => parseInt(x, 10)) | ||
const [year, decreaseYear, increaseYear] = useCounter( | ||
parseInt(initialYear, 10), | ||
1990, | ||
Infinity | ||
) | ||
|
||
const handleClickMonth = month => { | ||
const d = new Date(year, month, 1) | ||
onSelect(format(d, 'YYYY-MM-DD')) | ||
} | ||
|
||
return ( | ||
<> | ||
<div className={styles.DateMonthPicker__YearControls}> | ||
<Button | ||
className={styles.DateMonthPicker__YearButton} | ||
theme="secondary" | ||
size="small" | ||
label="" | ||
onClick={decreaseYear} | ||
icon={<Icon icon="left" />} | ||
/> | ||
<div | ||
className={cx( | ||
styles.DateMonthPicker__Year, | ||
year === initialYear && styles.ValueSelected | ||
)} | ||
> | ||
{year} | ||
</div> | ||
<Button | ||
className={styles.DateMonthPicker__YearButton} | ||
theme="secondary" | ||
size="small" | ||
label="" | ||
onClick={increaseYear} | ||
icon={<Icon icon="right" />} | ||
/> | ||
</div> | ||
<div className={styles.DateMonthPicker__MonthGrid}> | ||
{range(0, 12).map(i => ( | ||
<MonthButton | ||
key={i} | ||
isSelected={initialMonth - 1 === i && year == initialYear} | ||
monthNum={i} | ||
onClick={handleClickMonth} | ||
/> | ||
))} | ||
</div> | ||
</> | ||
) | ||
} | ||
|
||
const dateMonthProp = function(props, propName, componentName) { | ||
if (!/^[0-9]{4}-[0-9]{2}$/.test(props[propName])) { | ||
return new Error( | ||
'Invalid prop `' + | ||
propName + | ||
'` supplied to' + | ||
' `' + | ||
componentName + | ||
'`. Should be in the form YYYY-MM.' | ||
) | ||
} | ||
} | ||
|
||
DateMonthPicker.propTypes = { | ||
onSelect: PropTypes.func.isRequired, | ||
initialValue: dateMonthProp | ||
} | ||
|
||
export default DateMonthPicker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { mount } from 'enzyme' | ||
import React from 'react' | ||
import DateMonthPicker from './index' | ||
import Button from '../Button' | ||
import I18n from '../I18n' | ||
import { act } from 'react-dom/test-utils' | ||
const findButtonWithLabel = (root, label) => | ||
root.findWhere(n => n.type() == Button && n.props().label === label) | ||
|
||
const findButtonWithIcon = (root, iconName) => | ||
root.findWhere(n => { | ||
const props = n.props() | ||
if (n.type() !== Button) { | ||
return | ||
} | ||
if (!props.icon) { | ||
return | ||
} | ||
return props.icon.props.icon === iconName | ||
}) | ||
|
||
describe('DateMonthPicker', () => { | ||
const setup = ({ initialValue }) => { | ||
const handleSelect = jest.fn() | ||
const root = mount( | ||
<I18n lang="en" dictRequire={() => {}}> | ||
<DateMonthPicker initialValue={initialValue} onSelect={handleSelect} /> | ||
</I18n> | ||
) | ||
return { root, handleSelect } | ||
} | ||
|
||
it('should be able to select a month', async () => { | ||
const { root, handleSelect } = setup({ initialValue: '2015-08' }) | ||
const februaryButton = findButtonWithLabel(root, 'Feb') | ||
expect(februaryButton.length).toBe(1) | ||
februaryButton.props().onClick() | ||
expect(handleSelect).toHaveBeenCalledTimes(1) | ||
expect(handleSelect).toHaveBeenCalledWith('2015-02-01') | ||
}) | ||
|
||
it('should be able to go to previous year', async () => { | ||
const { root, handleSelect } = setup({ initialValue: '2015-08' }) | ||
const prevYearButton = findButtonWithIcon(root, 'left') | ||
act(() => { | ||
prevYearButton.props().onClick() | ||
}) | ||
root.update() | ||
const februaryButton = findButtonWithLabel(root, 'Feb') | ||
februaryButton.props().onClick() | ||
expect(handleSelect).toHaveBeenCalledTimes(1) | ||
expect(handleSelect).toHaveBeenCalledWith('2014-02-01') | ||
}) | ||
|
||
it('should be able to go to next year', async () => { | ||
const { root, handleSelect } = setup({ initialValue: '2015-08' }) | ||
const nextYearButton = findButtonWithIcon(root, 'right') | ||
act(() => { | ||
nextYearButton.props().onClick() | ||
}) | ||
root.update() | ||
const februaryButton = findButtonWithLabel(root, 'Feb') | ||
februaryButton.props().onClick() | ||
expect(handleSelect).toHaveBeenCalledTimes(1) | ||
expect(handleSelect).toHaveBeenCalledWith('2016-02-01') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
.DateMonthPicker__YearControls | ||
justify-content center | ||
display flex | ||
align-items center | ||
|
||
.DateMonthPicker__YearButton | ||
flex-grow 0 | ||
min-width 3rem | ||
min-height 3rem | ||
text-align center | ||
border-width 0 | ||
margin 0 | ||
color var(--coolGrey) | ||
background none !important | ||
|
||
.DateMonthPicker__Year | ||
display inline-flex | ||
|
||
.DateMonthPicker__MonthGrid | ||
display grid | ||
grid-template-columns repeat(4, auto) | ||
grid-template-rows repeat(3, 1fr) | ||
overflow hidden | ||
|
||
|
||
.DateMonthPicker__MonthButton | ||
min-height 3rem | ||
min-width 0 | ||
margin 0 | ||
border 0 | ||
border-radius 0 | ||
font-weight normal | ||
text-transform none | ||
|
||
.ValueSelected | ||
color var(--primaryColor) | ||
font-weight bold |