Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
* limitations under the License.
*/

import PropTypes from 'prop-types';
import { Component } from 'react';


import { addMonths } from 'date-fns/addMonths';
import { startOfDay } from 'date-fns/startOfDay';

Expand All @@ -26,6 +30,8 @@ import {
BpkCalendarGridHeader,
BpkCalendarNav,
BpkCalendarDate,
withCalendarState,
composeCalendar,
} from '..';
import BpkText from '../../bpk-component-text';

Expand Down Expand Up @@ -332,6 +338,73 @@ const FocusedDateInThePastExample = () => (
/>
);

// Multi-city scenario: leg 1 = 5th (outbound), leg 2 = 12th (connection).
// The calendar is open for leg 3 — legs 1 & 2 dates show the inset ring as
// context, leg 3's selected date (20th) shows the filled selection style.
const TODAY = new Date();
const MULTI_CITY_LEG1 = new Date(TODAY.getFullYear(), TODAY.getMonth(), 5);
const MULTI_CITY_LEG2 = new Date(TODAY.getFullYear(), TODAY.getMonth(), 12);
const MULTI_CITY_LEG3 = new Date(TODAY.getFullYear(), TODAY.getMonth(), 20);

const isOtherLegDate = (date) =>
[MULTI_CITY_LEG1, MULTI_CITY_LEG2].some((d) => d.toDateString() === date.toDateString());

const MultiCityDateComponent = ({ date, ...rest }) => (
<BpkCalendarDateComponent {...rest} date={date} isAnnotated={isOtherLegDate(date)} />
);
MultiCityDateComponent.propTypes = {
date: PropTypes.instanceOf(Date).isRequired,
};

const MultiCityCalendar = withCalendarState(
composeCalendar(
BpkCalendarNav,
BpkCalendarGridHeader,
BpkCalendarGrid,
MultiCityDateComponent,
),
);

// minDate = leg 2 date so dates before it are blocked (unselectable).
// Legs 1 & 2 show the inset ring; clicking any selectable date fills it.
class MultiCityAnnotatedDatesExample extends Component {
constructor(props) {
super(props);
this.state = {
selectionConfiguration: {
type: CALENDAR_SELECTION_TYPE.single,
date: null,
},
};
}

render() {
return (
<MultiCityCalendar
id="multiCityCalendar"
formatMonth={formatMonth}
formatDateFull={formatDateFull}
daysOfWeek={weekDays}
weekStartsOn={1}
changeMonthLabel="Change month"
previousMonthLabel="Go to previous month"
nextMonthLabel="Go to next month"
minDate={MULTI_CITY_LEG2}
initiallyFocusedDate={MULTI_CITY_LEG3}
onDateSelect={(date) => {
this.setState({
selectionConfiguration: {
type: CALENDAR_SELECTION_TYPE.single,
date,
},
});
}}
selectionConfiguration={this.state.selectionConfiguration}
/>
);
}
}

const RangeDateCalendarExample = () => (
<CalendarContainer
minDate={new Date(2020, 3, 1)}
Expand Down Expand Up @@ -394,6 +467,8 @@ export const CustomComposedCalendar = { render: () => <CustomComposedCalendarExa

export const CustomComposedCalendarSafariDstBug = { render: () => <CustomComposedCalendarSafariBugExample /> };

export const CalendarMultiCityAnnotatedDates = { render: () => <MultiCityAnnotatedDatesExample /> };

export const Week = { render: () => <WeekExample /> };

export const VisualTest = { render: () => <FocusedDateInThePastExample /> };
Expand All @@ -413,3 +488,4 @@ export const VisualTestRangeWithZoom = {
zoomEnabled: true,
},
};

Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,18 @@ describe('BpkCalendarDate', () => {
);
expect(asFragment()).toMatchSnapshot();
});

it('should apply annotated class when isAnnotated is true', () => {
const { asFragment } = render(
<BpkCalendarDate date={new Date(2010, 1, 15)} isAnnotated />,
);
expect(asFragment()).toMatchSnapshot();
});

it('should not apply annotated class by default', () => {
const { container } = render(
<BpkCalendarDate date={new Date(2010, 1, 15)} />,
);
expect(container.querySelector('.bpk-calendar-date--annotated')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@
background-color: tokens.$bpk-core-accent-day;
}

&--annotated:not(.bpk-calendar-date--selected):not(
.bpk-calendar-date--start
):not(.bpk-calendar-date--end):not(.bpk-calendar-date--single):not(
.bpk-calendar-date--sameDay
) {
box-shadow: 0 0 0 2px tokens.$bpk-core-accent-day inset;
}

&--focused:not(:disabled):not(.bpk-calendar-date--selected) {
box-shadow: 0 0 0 2px tokens.$bpk-core-accent-day inset;
box-shadow: 0 0 0 2px
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type DefaultProps = {
* - `SELECTION_TYPES.end` - When an end date is selected in a range calendar i.e. Last date in the range
*/
selectionType?: SelectionTypes;
isAnnotated?: boolean;
style?: {};
};

Expand Down Expand Up @@ -128,6 +129,7 @@ class BpkCalendarDate extends PureComponent<Props> {
const {
className = null,
date,
isAnnotated = false,
isBlocked = false,
isFocused = false,
isKeyboardFocusable = true,
Expand Down Expand Up @@ -163,6 +165,9 @@ class BpkCalendarDate extends PureComponent<Props> {
if (isOutside) {
classNames.push(getClassName('bpk-calendar-date--outside'));
}
if (isAnnotated) {
classNames.push(getClassName('bpk-calendar-date--annotated'));
}

if (selectionType !== SELECTION_TYPES.none) {
classNames.push(getClassName(`bpk-calendar-date--${selectionType}`));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`BpkCalendarDate should apply annotated class when isAnnotated is true 1`] = `
<DocumentFragment>
<button
aria-label="15"
aria-pressed="false"
class="bpk-calendar-date bpk-calendar-date--annotated"
data-backpack-ds-component="CalendarDate"
tabindex="-1"
type="button"
>
<span
aria-hidden="true"
>
15
</span>
</button>
</DocumentFragment>
`;

exports[`BpkCalendarDate should pass props through to button 1`] = `
<DocumentFragment>
<button
Expand Down Expand Up @@ -133,4 +152,4 @@ exports[`BpkCalendarDate should set a custom class 1`] = `
</span>
</button>
</DocumentFragment>
`;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,30 @@ const ScrollableCalendarGridListExample = () => (
/>
);

// Simulates a multi-city picker: leg 1 selected May 10, leg 2 minDate is Jun 1.
// minScrollable extends the scroll range back to May so the user can see leg 1's
// date, while dates before Jun 1 remain blocked (unselectable).
const MultiCityMinScrollableExample = () => (
<div style={{ height: '500px', display: 'flex' }}>
<BpkScrollableCalendarGridList
month={new Date(2020, 5, 1)}
weekStartsOn={1}
daysOfWeek={weekDays}
onDateClick={action('Clicked day')}
formatMonth={formatMonth}
formatDateFull={formatDateFull}
DateComponent={BpkScrollableCalendarDate}
minDate={new Date(2020, 5, 1)}
minScrollable={new Date(2020, 4, 1)}
maxDate={new Date(2020, 7, 31)}
selectionConfiguration={{
type: CALENDAR_SELECTION_TYPE.single,
date: new Date(2020, 5, 15),
}}
/>
</div>
);

const WeekStartsOnSundayExample = () => (
<ScrollableCal
id="myCalendar"
Expand Down Expand Up @@ -511,6 +535,8 @@ export const ScrollableCalendarGrid = { render: () => <ScrollableCalendarGridExa

export const ScrollableCalendarGridList = { render: () => <ScrollableCalendarGridListExample /> };

export const ScrollableCalendarMultiCityMinScrollable = { render: () => <MultiCityMinScrollableExample /> };

export const VisualTest = { render: () => <DefaultExample /> };
export const VisualTestWithZoom = { render: () => <DefaultExample />, args: { zoomEnabled: true } };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ describe('BpkCalendarScrollGridList', () => {
expect(asFragment()).toMatchSnapshot();
});

it('should render months starting from minScrollable when earlier than minDate', () => {
const { getAllByRole } = render(
<BpkScrollableCalendarGridList
minDate={testDate}
minScrollable={DateUtils.addMonths(testDate, -2)}
maxDate={DateUtils.addMonths(testDate, 12)}
month={testDate}
formatMonth={formatMonth}
formatDateFull={formatDateFull}
DateComponent={BpkCalendarScrollDate}
weekStartsOn={0}
/>,
);
const buttons = getAllByRole('button');
const disabledButtons = buttons.filter((b) => b.hasAttribute('disabled'));
expect(disabledButtons.length).toBeGreaterThan(0);
});
Comment thread
jan-bock marked this conversation as resolved.

it('should render correctly with a custom date component', () => {
const MyCustomDate = (props: any) => {
const cx = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { useRef, useState, useMemo, useEffect } from 'react';
import type { ElementType, ReactNode } from 'react';

import { startOfDay, startOfMonth } from 'date-fns';
import { isBefore, startOfDay, startOfMonth } from 'date-fns';
import AutoSizer from 'react-virtualized-auto-sizer';
import { VariableSizeList as List } from 'react-window';

Expand Down Expand Up @@ -62,6 +62,10 @@ type Props = Partial<BpkCalendarGridProps> & {
* Sets the height of month rows in 'rem' units. If not specified, the default value of `2.75rem` will be used.
*/
customRowHeight?: number;
/**
* When set to a date earlier than minDate, extends the scrollable/rendered range back to this date while keeping minDate as the selection boundary.
*/
minScrollable?: Date;
};

const BpkScrollableCalendarGridList = (props: Props) => {
Expand All @@ -70,12 +74,14 @@ const BpkScrollableCalendarGridList = (props: Props) => {
customRowHeight = 2.75,
focusedDate = null,
minDate,
minScrollable,
selectionConfiguration,
...rest
} = props;
const listRef = useRef(null);
const sentinelRef = useRef<HTMLDivElement>(null);
const startDate = startOfDay(startOfMonth(minDate));
const scrollAnchor = minScrollable && isBefore(minScrollable, minDate) ? minScrollable : minDate;
const startDate = startOfDay(startOfMonth(scrollAnchor));
const endDate = startOfDay(startOfMonth(rest.maxDate));
const monthsCount = DateUtils.differenceInCalendarMonths(endDate, startDate);

Comment thread
jan-bock marked this conversation as resolved.
Comment thread
jan-bock marked this conversation as resolved.
Expand All @@ -94,7 +100,7 @@ const BpkScrollableCalendarGridList = (props: Props) => {

const months = useMemo(
() => getMonthsArray(startDate, monthsCount),
[minDate, monthsCount],
[startDate, monthsCount],
);

const monthItemHeights = useMemo(
Expand Down
Loading