Skip to content

Commit

Permalink
Add <CalendarMonth /> component
Browse files Browse the repository at this point in the history
Component for rendering a one month calendar
  • Loading branch information
Richard Palmer committed Feb 21, 2017
1 parent 2a46edf commit 4857777
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 5 deletions.
33 changes: 33 additions & 0 deletions components/Calendar/CalendarMonth.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.root {
width: 100%;
border-collapse: collapse;
margin-bottom: 0;
table-layout: fixed;
}

.root * {
box-sizing: border-box;
}

.body {
border: 1px solid var(--color-greyLighter);
}

.cell {
width: auto;
border: 1px solid var(--color-greyLighter);
padding: 0;
border-bottom: 0;
}

.row:first-child .cell {
border-top: 0;
}

.row .cell:first-child {
border-left: 0;
}

.row .cell:last-child {
border-right: 0;
}
126 changes: 126 additions & 0 deletions components/Calendar/CalendarMonth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { Component, PropTypes } from 'react';
import momentPropTypes from 'react-moment-proptypes';
import moment from 'moment';

import css from './CalendarMonth.css';
import CalendarItem from './CalendarItem';
import { generateNumberFilledArray } from '../../utils/array/array';
import getCalendarMonth, {
getPreDayCount,
getPostDayCount,
DAYS_PER_WEEK,
} from './getCalendarMonth';

const today = moment();

export const defaultClassNames = {
root: css.root,
head: css.head,
body: css.body,
row: css.row,
cell: css.cell,
};

export default class CalendarMonth extends Component {
static propTypes = {
month: momentPropTypes.momentObj,
DayComponent: PropTypes.func,
ColumnHeadingComponent: PropTypes.func,
showOutOfRange: PropTypes.bool,
dayProps: PropTypes.object,
columnHeadingProps: PropTypes.object,
classNames: PropTypes.shape({
root: PropTypes.string,
head: PropTypes.string,
body: PropTypes.string,
row: PropTypes.string,
cell: PropTypes.string,
}),
};

static defaultProps = {
month: today,
ColumnHeadingComponent: CalendarItem,
DayComponent: CalendarItem,
showOutOfRange: false,
dayProps: {},
columnHeadingProps: {},
classNames: defaultClassNames,
};

render() {
const {
month,
showOutOfRange,
DayComponent,
ColumnHeadingComponent,
dayProps,
columnHeadingProps,
classNames,
} = this.props;

const head = generateNumberFilledArray(DAYS_PER_WEEK, 1);
const startOfMonth = month.clone().startOf('month');
const endOfMonth = month.clone().endOf('month');
const calendarMonth = getCalendarMonth(
month,
getPreDayCount(month),
getPostDayCount(month),
);

return (
<table className={ classNames.root }>
<thead className={ classNames.head }>
<tr className={ classNames.row }>
{ head.map((offset) => {
const day = moment().clone().day(offset);
return (
<td key={ `${month.format('MM')}-${day.format('dd')}` }>
<ColumnHeadingComponent
{ ...columnHeadingProps }
day={ day }
format={ columnHeadingProps.format || 'dd' }
/>
</td>
);
}) }
</tr>
</thead>
<tbody className={ classNames.body }>
{ calendarMonth.map(row => (
<tr
className={ classNames.row }
key={ `${month.format('MM')}-${row[0].format('DD/MM')}` }
>
{ row.map((date) => {
const isCurrentMonth = date.isSameOrAfter(startOfMonth) &&
date.isSameOrBefore(endOfMonth);

const day = showOutOfRange || isCurrentMonth
? date
: null;

return (
<td
className={ classNames.cell }
key={ `${date.format('DD/MM/YYYY')}` }
>
<DayComponent
{ ...dayProps }
modifiers={ {
outOfRange: showOutOfRange && !isCurrentMonth,
today: date.isSame(today, 'day'),
...dayProps.modifiers,
} }
day={ day }
/>
</td>
);
}) }
</tr>
)) }
</tbody>
</table>
);
}
}
42 changes: 42 additions & 0 deletions components/Calendar/CalendarMonth.story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { PropTypes } from 'react';
import { storiesOf } from '@kadira/storybook';
import momentPropTypes from 'react-moment-proptypes';
import CalendarMonth from './CalendarMonth';
import CalendarItem from './CalendarItem';

const ExampleCalendarItem = (props) => {
const { day, modifiers, ...rest } = props;

const m = {
...modifiers,
selected: day && day.date() % 2 === 0,
};

return <CalendarItem { ...rest } day={ day } modifiers={ m } />;
};

ExampleCalendarItem.propTypes = {
isSelected: PropTypes.object,
day: momentPropTypes.momentObj,
modifiers: PropTypes.object,
};

storiesOf('CalendarMonth', module)
.add('Default', () => (
<CalendarMonth />
))
.add('with external dates', () => (
<CalendarMonth showOutOfRange />
))
.add('with custom `DayComponent`', () => (
<CalendarMonth
DayComponent={ ExampleCalendarItem }
/>
))
.add('with custom heading format', () => (
<CalendarMonth
columnHeadingProps={ {
format: 'dddd',
} }
/>
));
9 changes: 9 additions & 0 deletions components/Calendar/CalendarMonth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { render } from 'react-dom';

import CalendarMonth from './CalendarMonth';

it('renders with a date without crashing', () => {
const div = document.createElement('div');
render(<CalendarMonth />, div);
});
9 changes: 4 additions & 5 deletions components/Calendar/getCalendarMonth.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import moment from 'moment';
import {
generateArray,
generateNumberFilledArray,
reshapeArray
reshapeArray,
} from '../../utils/array/array';

export const CALENDAR_MONTH_LENGTH = 42;
export const DAYS_PER_WEEK = 7;
export const CALENDAR_ROWS = CALENDAR_MONTH_LENGTH / DAYS_PER_WEEK;

export const getPreDayCount = (date) => date.weekday() - 1;
export const getPreDayCount = date => date.weekday() - 1;
export const getPostDayCount = (date) => {
const preDayCount = getPreDayCount(date);
return CALENDAR_MONTH_LENGTH - date.daysInMonth() - preDayCount;
}
};

const getCalendarMonth = (date, preDayCount, postDayCount) => {
const month = date.clone().startOf('month');
Expand All @@ -22,7 +21,7 @@ const getCalendarMonth = (date, preDayCount, postDayCount) => {
.fill('')
.map((_, i, { length }) => {
const offset = length - i;
return month.clone().subtract(offset, 'days')
return month.clone().subtract(offset, 'days');
});

const postDays = generateArray(postDayCount)
Expand Down

0 comments on commit 4857777

Please sign in to comment.