diff --git a/src/components/Calendar.js b/src/components/Calendar.js index dbe0ca9c4..a72c08f78 100644 --- a/src/components/Calendar.js +++ b/src/components/Calendar.js @@ -20,8 +20,10 @@ import style from './Calendar.scss'; const Day = ({ day, dateFormat, ...props }) => { const classNames = classnames( 'text-center', - { 'bg-primary text-white': day.selected }, - { 'text-muted': !day.sameMonth }, + { 'bg-faded text-muted': !day.sameMonth }, // Lighten days in months before & after + { 'bg-primary text-white': day.selected }, // Highlight selected date + { 'text-primary font-weight-bold': !day.selected && isToday(day.date) }, // Highlight today's date + { invisible: !day.visible }, // If date is (optionally) filtered out style.date ); return ( @@ -40,6 +42,7 @@ class Calendar extends Component { className: React.PropTypes.string, date: React.PropTypes.instanceOf(Date), dateFormat: React.PropTypes.string, + dateVisible: React.PropTypes.func, onSelect: React.PropTypes.func, weekDayFormat: React.PropTypes.string }; @@ -48,6 +51,7 @@ class Calendar extends Component { className: '', date: new Date(), dateFormat: 'D', + dateVisible: () => true, weekDayFormat: 'dd', onSelect: () => {} }; @@ -63,6 +67,7 @@ class Calendar extends Component { return { selected: isSameDay(currentDate, date), date: startOfDay(date), + visible: this.props.dateVisible(date), past: isPast(date), today: isToday(date), sameMonth: isSameMonth(currentDate, date), @@ -84,6 +89,7 @@ class Calendar extends Component { render() { const { date, dateFormat, onSelect, weekDayFormat, ...props } = this.props; const weeks = this.visibleWeeks(date); + delete props.dateVisible; // Table doesn't need dateVisible return ( {weeks.map((days, w) => ( - {days.map((day, d) => onSelect(day.date)} />)} + {days.map((day, d) => day.visible && onSelect(day.date)} />)} ))} diff --git a/src/components/DateInput.js b/src/components/DateInput.js index b5a2d4a0f..3ae21de92 100644 --- a/src/components/DateInput.js +++ b/src/components/DateInput.js @@ -49,25 +49,29 @@ export default class DateInput extends Component { static propTypes = { className: React.PropTypes.string, + dateVisible: React.PropTypes.func, dateFormat: React.PropTypes.string, defaultValue: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.object ]), - value: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.object - ]), + disabled: React.PropTypes.bool, + header: React.PropTypes.node, + footer: React.PropTypes.node, keyboard: React.PropTypes.bool, onBlur: React.PropTypes.func, onChange: React.PropTypes.func, showOnFocus: React.PropTypes.bool, - disabled: React.PropTypes.bool, + value: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.object + ]) // TODO allow custom header/footer, header & day format? } static defaultProps = { className: '', + dateVisible: () => true, dateFormat: 'M/D/YYYY', keyboard: true, onBlur: () => {}, @@ -175,7 +179,7 @@ export default class DateInput extends Component { toggle = () => (this.state.open ? this.close() : this.show()); render() { - const { className, disabled, onBlur, showOnFocus } = this.props; + const { className, dateVisible, disabled, footer, header, onBlur, showOnFocus } = this.props; const { open } = this.state; const value = this.getCurrentValue(); const date = this.getCurrentDate(); @@ -207,41 +211,46 @@ export default class DateInput extends Component { onKeyDown={this.onKeyDown} style={{ minWidth: '19rem' }} > -
- - - - - - - {format(date, 'MMMM YYYY')} - - - - - - -
+ {header || ( +
+ + + + + + + {format(date, 'MMMM YYYY')} + + + + + + +
+ )} - + {footer || ( + + )} ); diff --git a/stories/DateInput.js b/stories/DateInput.js index 1ebc07db5..5871caa7a 100644 --- a/stories/DateInput.js +++ b/stories/DateInput.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Calendar, DateInput, FormRow } from '../src'; +import { Calendar, DateInput, Icon, FormRow } from '../src'; import { action, storiesOf } from '@storybook/react'; import { boolean, text } from '@storybook/addon-knobs'; @@ -41,6 +41,21 @@ storiesOf('DateInput', module) )) + .addWithInfo('Custom header and footer', () => ( +
+ PIRELLI} + footer={( +
+ + + + + +
)} + /> +
+ )) .addWithInfo('Calendar', () => (
', () => { firstDate.simulate('click'); assert(callback.calledWith(expectedDate)); }); + + it('should hide dates which are not visible based on dateVisible', () => { + const specifiedDate = new Date(2017, 7, 14); + const dateVisible = (date) => isSameDay(date, specifiedDate); + const component = mount(); + component.find('Day').forEach((dayComponent) => { + if (isSameDay(dayComponent.props().day.date, specifiedDate)) { + assert.equal(dayComponent.props().day.visible, true); + } else { + assert.equal(dayComponent.props().day.visible, false); + } + }); + }); + + it('should not call onSelect if clicking on a invisible date', () => { + const specifiedDate = new Date(2017, 7, 14); + const dateVisible = (date) => isSameDay(date, specifiedDate); + const callback = sinon.spy(); + const component = mount(); + const firstDate = component.find('Day').first(); + assert.equal(firstDate.props().day.visible, false); + firstDate.simulate('click'); + assert(callback.notCalled); + }); }); diff --git a/test/components/DateInput.spec.js b/test/components/DateInput.spec.js index 4c27d2b57..0c7ce4d89 100644 --- a/test/components/DateInput.spec.js +++ b/test/components/DateInput.spec.js @@ -231,4 +231,43 @@ describe('', () => { assert(isSameDay(component.instance().getCurrentDate(), expectedDate)); }); }); + + context('date picker with controlled visible dates', () => { + const callback = sinon.spy(); + const defaultDate = new Date(2017, 7, 14); + const dateVisible = (date) => isSameDay(date, defaultDate); + const component = mount(); + const toggle = component.find('InputGroupButton'); + toggle.simulate('click'); + + it('should pass dateVisible func to Calendar component', () => { + const calendar = component.find('Calendar'); + assert.equal(calendar.props().dateVisible, dateVisible); + }); + + it('should not allow to pick invisible date', () => { + callback.reset(); + const currentDate = component.instance().getCurrentDate(); + const firstDate = component.find('Day').first(); + assert.equal(isSameDay(currentDate, firstDate.props().day.date), false); + + firstDate.simulate('click'); + assert(callback.notCalled); + assert(isSameDay(currentDate, component.instance().getCurrentDate())); + }); + }); + + it('should render custom header prop', () => { + const Custom = () => (
Custom Header
); + const component = mount(} />); + assert.equal(component.find('div.custom-header').length, 1); + assert.equal(component.find('header.py-2').length, 0); + }); + + it('should render custom footer prop', () => { + const Custom = () => (
Custom Footer
); + const component = mount(} />); + assert.equal(component.find('div.custom-footer').length, 1); + assert.equal(component.find('footer.pb-2').length, 0); + }); });