Skip to content
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

feat: Added a WeekView #966

Open
wants to merge 1 commit into
base: main
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
"devDependencies": {
"husky": "^9.0.0"
},
"packageManager": "yarn@4.3.1"
"packageManager": "yarn@4.6.0"
}
59 changes: 47 additions & 12 deletions packages/react-calendar/src/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ import type {
formatWeekday as defaultFormatWeekday,
formatYear as defaultFormatYear,
} from './shared/dateFormatter.js';
import WeekView from './WeekView.js';

const baseClassName = 'react-calendar';
const allViews = ['century', 'decade', 'year', 'month'] as const;
const allValueTypes = ['decade', 'year', 'month', 'day'] as const;
const allViews = ['century', 'decade', 'year', 'month', 'week'] as const;
const allValueTypes = ['decade', 'year', 'month', 'day', 'day'] as const;

const defaultMinDate = new Date();
defaultMinDate.setFullYear(1, 0, 1);
Expand Down Expand Up @@ -487,13 +488,14 @@ function getValue(value: LooseValue | undefined, index: 0 | 1): Date | null {
}

type DetailArgs = {
calendarType?: CalendarType;
value?: LooseValue;
minDate?: Date;
maxDate?: Date;
maxDetail: Detail;
};

function getDetailValue({ value, minDate, maxDate, maxDetail }: DetailArgs, index: 0 | 1) {
function getDetailValue({ calendarType, value, minDate, maxDate, maxDetail }: DetailArgs, index: 0 | 1) {
const valuePiece = getValue(value, index);

if (!valuePiece) {
Expand All @@ -505,9 +507,9 @@ function getDetailValue({ value, minDate, maxDate, maxDetail }: DetailArgs, inde
const detailValueFrom = (() => {
switch (index) {
case 0:
return getBegin(valueType, valuePiece);
return getBegin(valueType, valuePiece, calendarType);
case 1:
return getEnd(valueType, valuePiece);
return getEnd(valueType, valuePiece, calendarType);
default:
throw new Error(`Invalid index value: ${index}`);
}
Expand All @@ -527,6 +529,7 @@ const getDetailValueArray = (args: DetailArgs) =>
];

function getActiveStartDate({
calendarType,
maxDate,
maxDetail,
minDate,
Expand All @@ -540,17 +543,19 @@ function getActiveStartDate({
const rangeType = getView(view, minDetail, maxDetail);
const valueFrom =
getDetailValueFrom({
calendarType,
value,
minDate,
maxDate,
maxDetail,
}) || new Date();

return getBegin(rangeType, valueFrom);
return getBegin(rangeType, valueFrom, calendarType);
}

function getInitialActiveStartDate({
activeStartDate,
calendarType,
defaultActiveStartDate,
defaultValue,
defaultView,
Expand All @@ -562,6 +567,7 @@ function getInitialActiveStartDate({
view,
}: {
activeStartDate?: Date;
calendarType?: CalendarType;
defaultActiveStartDate?: Date;
defaultValue?: LooseValue;
defaultView?: View;
Expand All @@ -576,10 +582,11 @@ function getInitialActiveStartDate({
const valueFrom = activeStartDate || defaultActiveStartDate;

if (valueFrom) {
return getBegin(rangeType, valueFrom);
return getBegin(rangeType, valueFrom, calendarType);
}

return getActiveStartDate({
calendarType,
maxDate,
maxDetail,
minDate,
Expand Down Expand Up @@ -675,6 +682,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
activeStartDateProps ||
activeStartDateState ||
getInitialActiveStartDate({
calendarType,
activeStartDate: activeStartDateProps,
defaultActiveStartDate,
defaultValue,
Expand Down Expand Up @@ -736,13 +744,14 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
})();

return processFunction({
calendarType,
maxDate,
maxDetail,
minDate,
value,
});
},
[maxDate, maxDetail, minDate, returnValue],
[calendarType, maxDate, maxDetail, minDate, returnValue],
);

const setActiveStartDate = useCallback(
Expand Down Expand Up @@ -773,6 +782,8 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
return onClickYear;
case 'year':
return onClickMonth;
case 'week':
return onClickDay;
case 'month':
return onClickDay;
default:
Expand Down Expand Up @@ -845,7 +856,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
throw new Error('Attempted to drill up from the highest view.');
}

const nextActiveStartDate = getBegin(nextView, activeStartDate);
const nextActiveStartDate = getBegin(nextView, activeStartDate, calendarType);

setActiveStartDateState(nextActiveStartDate);
setViewState(nextView);
Expand All @@ -870,6 +881,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
}
}, [
activeStartDate,
calendarType,
drillUpAvailable,
onActiveStartDateChange,
onDrillUp,
Expand All @@ -894,7 +906,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
if (isFirstValueInRange) {
// Value has 0 or 2 elements - either way we're starting a new array
// First value
nextValue = getBegin(valueType, rawNextValue);
nextValue = getBegin(valueType, rawNextValue, calendarType);
} else {
if (!previousValue) {
throw new Error('previousValue is required');
Expand All @@ -920,6 +932,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
// Range selection turned on, second value, goToRangeStartOnSelect toggled on
goToRangeStartOnSelect
? getActiveStartDate({
calendarType,
maxDate,
maxDetail,
minDate,
Expand Down Expand Up @@ -966,6 +979,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
[
activeStartDate,
allowPartialRange,
calendarType,
getProcessedValue,
goToRangeStartOnSelect,
maxDate,
Expand Down Expand Up @@ -1006,8 +1020,8 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu

function renderContent(next?: boolean) {
const currentActiveStartDate = next
? getBeginNext(view, activeStartDate)
: getBegin(view, activeStartDate);
? getBeginNext(view, activeStartDate, calendarType)
: getBegin(view, activeStartDate, calendarType);

const onClick = drillDownAvailable ? drillDown : onChange;

Expand Down Expand Up @@ -1075,6 +1089,26 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
/>
);
}
case 'week': {
return (
<WeekView
calendarType={calendarType}
formatDay={formatDay}
formatLongDate={formatLongDate}
formatShortWeekday={formatShortWeekday}
formatWeekday={formatWeekday}
onClickWeekNumber={onClickWeekNumber}
onMouseLeave={selectRange ? onMouseLeave : undefined}
showFixedNumberOfWeeks={
typeof showFixedNumberOfWeeks !== 'undefined'
? showFixedNumberOfWeeks
: showDoubleView
}
showWeekNumbers={showWeekNumbers}
{...commonProps}
/>
);
}
default:
throw new Error(`Invalid view: ${view}.`);
}
Expand All @@ -1088,6 +1122,7 @@ const Calendar: React.ForwardRefExoticComponent<CalendarProps & React.RefAttribu
return (
<Navigation
activeStartDate={activeStartDate}
calendarType={calendarType}
drillUp={drillUp}
formatMonthYear={formatMonthYear}
formatYear={formatYear}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-calendar/src/Calendar/Navigation.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fireEvent, render } from '@testing-library/react';

import Navigation from './Navigation.js';

const allViews = ['century', 'decade', 'year', 'month'];
const allViews = ['century', 'decade', 'year', 'month', 'week'];

describe('Navigation', () => {
const defaultProps = {
Expand Down
37 changes: 27 additions & 10 deletions packages/react-calendar/src/Calendar/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import {
getBeginPrevious2,
getEndPrevious,
getEndPrevious2,
getWeekEndDate,
getWeekStartDate,
} from '../shared/dates.js';
import {
formatMonthYear as defaultFormatMonthYear,
formatYear as defaultFormatYear,
formatShortDayMonthYear,
} from '../shared/dateFormatter.js';

import type { Action, NavigationLabelFunc, RangeType } from '../shared/types.js';
import type { Action, CalendarType, NavigationLabelFunc, RangeType } from '../shared/types.js';

const className = 'react-calendar__navigation';

Expand All @@ -28,6 +31,12 @@ type NavigationProps = {
* @example new Date(2017, 0, 1)
*/
activeStartDate: Date;

/**
* Assists with "week" view to know which day to start on
*/
calendarType?: CalendarType;

drillUp: () => void;
/**
* Function called to override default formatting of months and years. Can be used to use your own formatting function.
Expand Down Expand Up @@ -151,6 +160,7 @@ type NavigationProps = {

export default function Navigation({
activeStartDate,
calendarType,
drillUp,
formatMonthYear = defaultFormatMonthYear,
formatYear = defaultFormatYear,
Expand All @@ -176,20 +186,20 @@ export default function Navigation({
const drillUpAvailable = views.indexOf(view) > 0;
const shouldShowPrevNext2Buttons = view !== 'century';

const previousActiveStartDate = getBeginPrevious(view, activeStartDate);
const previousActiveStartDate = getBeginPrevious(view, activeStartDate, calendarType);
const previousActiveStartDate2 = shouldShowPrevNext2Buttons
? getBeginPrevious2(view, activeStartDate)
? getBeginPrevious2(view, activeStartDate, calendarType)
: undefined;
const nextActiveStartDate = getBeginNext(view, activeStartDate);
const nextActiveStartDate = getBeginNext(view, activeStartDate, calendarType);
const nextActiveStartDate2 = shouldShowPrevNext2Buttons
? getBeginNext2(view, activeStartDate)
? getBeginNext2(view, activeStartDate, calendarType)
: undefined;

const prevButtonDisabled = (() => {
if (previousActiveStartDate.getFullYear() < 0) {
return true;
}
const previousActiveEndDate = getEndPrevious(view, activeStartDate);
const previousActiveEndDate = getEndPrevious(view, activeStartDate, calendarType);
return minDate && minDate >= previousActiveEndDate;
})();

Expand All @@ -199,7 +209,7 @@ export default function Navigation({
if ((previousActiveStartDate2 as Date).getFullYear() < 0) {
return true;
}
const previousActiveEndDate = getEndPrevious2(view, activeStartDate);
const previousActiveEndDate = getEndPrevious2(view, activeStartDate, calendarType);
return minDate && minDate >= previousActiveEndDate;
})();

Expand All @@ -224,7 +234,7 @@ export default function Navigation({
setActiveStartDate(nextActiveStartDate2 as Date, 'next2');
}

function renderLabel(date: Date) {
function renderLabel(date: Date, calendarType?: CalendarType) {
const label = (() => {
switch (view) {
case 'century':
Expand All @@ -235,6 +245,13 @@ export default function Navigation({
return formatYear(locale, date);
case 'month':
return formatMonthYear(locale, date);
case 'week': {
// start
const startDate = getWeekStartDate(date, calendarType);
// end
const endDate = getWeekEndDate(date, calendarType);
return `${formatShortDayMonthYear(locale, startDate)}-${formatShortDayMonthYear(locale, endDate)}`;
}
default:
throw new Error(`Invalid view: ${view}.`);
}
Expand Down Expand Up @@ -263,13 +280,13 @@ export default function Navigation({
type="button"
>
<span className={`${labelClassName}__labelText ${labelClassName}__labelText--from`}>
{renderLabel(activeStartDate)}
{renderLabel(activeStartDate, calendarType)}
</span>
{showDoubleView ? (
<>
<span className={`${labelClassName}__divider`}> – </span>
<span className={`${labelClassName}__labelText ${labelClassName}__labelText--to`}>
{renderLabel(nextActiveStartDate)}
{renderLabel(nextActiveStartDate, calendarType)}
</span>
</>
) : null}
Expand Down
Loading