Skip to content

feat(datepicker): Added a "week" date picker type #666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/components/calendar/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface CalendarProps extends RenderProps {
previousMonth: string;
previousYear: string;
showToday: SemanticDatepickerProps['showToday'];
type: SemanticDatepickerProps['type'];
thisWeekButton: string;
todayButton: string;
weekdays: Locale['weekdays'];
}
Expand Down Expand Up @@ -55,7 +57,9 @@ const Calendar: React.FC<CalendarProps> = ({
previousMonth,
previousYear,
showToday,
type,
todayButton,
thisWeekButton,
weekdays,
pointing,
}) => {
Expand Down Expand Up @@ -210,7 +214,7 @@ const Calendar: React.FC<CalendarProps> = ({
onClick: onPressBtn,
})}
>
{todayButton}
{(type === 'week' && thisWeekButton) ? thisWeekButton : todayButton}
</TodayButton>
)}
</Segment>
Expand Down
20 changes: 15 additions & 5 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
parseRangeOnBlur,
pick,
} from './utils';
import { BasicDatePicker, RangeDatePicker } from './pickers';
import { BasicDatePicker, RangeDatePicker, WeekDatePicker } from './pickers';
import { Locale, SemanticDatepickerProps } from './types';
import Calendar from './components/calendar';
import Input from './components/input';
Expand Down Expand Up @@ -125,9 +125,17 @@ class SemanticDatepicker extends React.Component<
return this.props.type === 'range';
}

get isWeekInput() {
return this.props.type === 'week';
}

get isWeekOrRangeInput() {
return this.isWeekInput || this.isRangeInput;
}

get initialState() {
const { format, value, formatOptions } = this.props;
const initialSelectedDate = this.isRangeInput ? [] : null;
const initialSelectedDate = this.isWeekOrRangeInput ? [] : null;

return {
isVisible: false,
Expand Down Expand Up @@ -161,7 +169,7 @@ class SemanticDatepicker extends React.Component<
return date;
}

return this.isRangeInput ? selectedDate[0] : selectedDate;
return this.isWeekOrRangeInput ? selectedDate[0] : selectedDate;
}

get locale() {
Expand All @@ -188,7 +196,9 @@ class SemanticDatepicker extends React.Component<

state = this.initialState;

Component: React.ElementType = this.isRangeInput
Component: React.ElementType = this.isWeekInput
? WeekDatePicker
: this.isRangeInput
? RangeDatePicker
: BasicDatePicker;

Expand Down Expand Up @@ -343,7 +353,7 @@ class SemanticDatepicker extends React.Component<
return;
}

if (this.isRangeInput) {
if (this.isWeekOrRangeInput) {
const parsedValue = parseRangeOnBlur(String(typedValue), format);
const areDatesValid = parsedValue.every(isValid);

Expand Down
1 change: 1 addition & 0 deletions src/locales/en-US.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"todayButton": "Today",
"thisWeekButton" : "This Week",
"nextMonth": "Next month",
"previousMonth": "Previous month",
"nextYear": "Next year",
Expand Down
3 changes: 2 additions & 1 deletion src/pickers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ He didn't publish the components to npm, so I copied his work */

import BasicDatePicker from './basic';
import RangeDatePicker from './range';
import WeekDatePicker from './week';

export { BasicDatePicker, RangeDatePicker };
export { BasicDatePicker, RangeDatePicker, WeekDatePicker };
27 changes: 27 additions & 0 deletions src/pickers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,30 @@ export function getArrowKeyHandlers(config) {
export function isInRange(range, date) {
return range.length === 2 && range[0] <= date && range[1] >= date;
}

/**
* Generates an array of all week dates in the same week for a given date
* @param {Date} date a given date
* @param {number} firstDayOfWeek first day of the week (e.g. 1 for Monday)
*/
export function findWeekDatesForDate(
date: Date,
firstDayOfWeek: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
) {
firstDayOfWeek = firstDayOfWeek !== undefined ? firstDayOfWeek : 0;
let weekStartDay = new Date(date.getTime());
let dayOfWeek = date.getDay() - firstDayOfWeek;

if (dayOfWeek < 0) {
dayOfWeek = dayOfWeek + 7;
}
weekStartDay.setDate(date.getDate() - dayOfWeek);

let dates = [weekStartDay];
while (dates.length !== 7) {
let nextDay = new Date(dates[dates.length - 1]);
nextDay.setDate(nextDay.getDate() + 1);
dates.push(nextDay);
}
return dates;
}
122 changes: 122 additions & 0 deletions src/pickers/week.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import compareAsc from 'date-fns/compareAsc';
import isSameDay from 'date-fns/isSameDay';
import React from 'react';
import { WeekDataPickerProps } from '../types';
import BaseDatePicker from './base';
import { composeEventHandlers, isInRange, findWeekDatesForDate } from './utils';

type WeekDatePickerState = {
hoveredDates: Date[] | null;
};

class WeekDatePicker extends React.Component<
WeekDataPickerProps,
WeekDatePickerState
> {
static defaultProps = {
selected: [],
};

state: WeekDatePickerState = { hoveredDates: null };

setHoveredDates = (dates: Date[] | null) => {
this.setState((state) =>
state.hoveredDates === dates ? null : { hoveredDates: dates }
);
};

// Calendar level
onMouseLeave = () => {
this.setHoveredDates(null);
};

// Date level
onHoverFocusDate(date: Date | null) {
const { firstDayOfWeek } = this.props;

if (date === null) return;
this.setHoveredDates(findWeekDatesForDate(date, firstDayOfWeek));
}

_handleOnDateSelected = (
{ selectable, date },
event: React.SyntheticEvent
) => {
const { onChange, firstDayOfWeek } = this.props;

if (!selectable) {
return;
}

let newDates = findWeekDatesForDate(
date,
firstDayOfWeek ? firstDayOfWeek : 0
);

if (onChange) {
onChange(event, newDates);
}

if (newDates.length === 2) {
this.setHoveredDates(null);
}
};

getEnhancedDateProps = (
getDateProps,
dateBounds,
{ onMouseEnter, onFocus, ...restProps }
) => {
const { hoveredDates } = this.state;
const { date } = restProps.dateObj;
return getDateProps({
...restProps,
inRange: isInRange(dateBounds, date),
start: dateBounds[0] && isSameDay(dateBounds[0], date),
end: dateBounds[1] && isSameDay(dateBounds[1], date),
// @ts-ignore
hovered: hoveredDates && isSameDay(hoveredDates, date),
onMouseEnter: composeEventHandlers(onMouseEnter, () => {
this.onHoverFocusDate(date);
}),
onFocus: composeEventHandlers(onFocus, () => {
this.onHoverFocusDate(date);
}),
});
};

getEnhancedRootProps = (getRootProps, props) =>
getRootProps({
...props,
onMouseLeave: this.onMouseLeave,
});

render() {
const { children, ...rest } = this.props;
const { hoveredDates } = this.state;

const dateBounds = hoveredDates
? [hoveredDates[0], hoveredDates[hoveredDates.length - 1]].sort(
compareAsc
)
: [];

return (
<BaseDatePicker {...rest} onDateSelected={this._handleOnDateSelected}>
{({ getRootProps, getDateProps, ...renderProps }) =>
children({
...renderProps,
getRootProps: this.getEnhancedRootProps.bind(this, getRootProps),
getDateProps: this.getEnhancedDateProps.bind(
this,
getDateProps,
dateBounds
),
})
}
</BaseDatePicker>
);
}
}

export default WeekDatePicker;
7 changes: 6 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export type SemanticDatepickerProps = PickedDayzedProps &
pointing: 'left' | 'right' | 'top left' | 'top right';
required?: boolean;
showToday: boolean;
type: 'basic' | 'range';
type: 'basic' | 'range' | 'week';
datePickerOnly: boolean;
value: DayzedProps['selected'] | null;
};
Expand All @@ -108,4 +108,9 @@ export interface RangeDatePickerProps extends BaseDatePickerProps {
selected: Date[];
}

export interface WeekDataPickerProps extends BaseDatePickerProps {
onChange: (event: React.SyntheticEvent, dates: Date[] | null) => void;
selected: Date[];
}

export { DayzedProps, RenderProps };
2 changes: 1 addition & 1 deletion stories/data.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ALL_ICONS_IN_ALL_CONTEXTS } from 'semantic-ui-react/src/lib/SUI';
import { SemanticICONS } from 'semantic-ui-react';

const types = <const>['basic', 'range'];
const types = <const>['basic', 'range', 'week'];
const pointing = <const>['left', 'right', 'top left', 'top right'];
const locale = <const>[
'bg-BG',
Expand Down
21 changes: 21 additions & 0 deletions stories/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,24 @@ export const inverted = () => {
</Content>
);
};

export const week = () => {
const firstDayOfWeek = number('First Day Of Week', 1, { max: 6, min: 0 });
const disabledWeekend = boolean('Disabled Weekend', false);

return (
<Content>
<SemanticDatepicker
showOutsideDays
filterDate={
disabledWeekend
? (date) => date.getDay() != 0 && date.getDay() !== 6
: undefined
}
firstDayOfWeek={firstDayOfWeek as 0 | 1 | 2 | 3 | 4 | 5 | 6}
onChange={onChange}
type="week"
/>
</Content>
);
};