Skip to content

Commit

Permalink
- Add render prop for rendering custom content below calendar in popo…
Browse files Browse the repository at this point in the history
…ver.

- Add stories with render prop examples.
- Add `onHideCalendar` callback.
- Fix comment typo.
- Add `firstMonthInMonthPicker` and `numMonthsInMonthPicker` props.
  • Loading branch information
mattias800 committed Jul 4, 2024
1 parent 0fab0ab commit 715966a
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface TravelDateRangeCalendarProps
nextMonthButtonAriaLabel?: string;
heading?: string;
headingLevel?: HeadingVariant;
firstMonthInMonthPicker?: Date;
numMonthsInMonthPicker?: number;
}

export const TravelDateRangeCalendar: React.FC<
Expand All @@ -33,6 +35,8 @@ export const TravelDateRangeCalendar: React.FC<
nextMonthButtonAriaLabel = "Next month",
heading,
headingLevel,
numMonthsInMonthPicker = 12,
firstMonthInMonthPicker = new Date(),
}) => {
const inputProps = useTravelDateRangeInput(
value,
Expand Down Expand Up @@ -75,8 +79,8 @@ export const TravelDateRangeCalendar: React.FC<

{visiblePanel === "month-picker" && (
<MonthPicker
firstMonth={new Date()}
numMonths={12}
firstMonth={firstMonthInMonthPicker}
numMonths={numMonthsInMonthPicker}
value={visibleMonth}
onValueChange={(v) => {
setVisibleMonth(v);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import * as React from "react";
import { useState } from "react";
import { TravelDateRangeInput } from "./TravelDateRangeInput";
import { Column, Row, Spacing } from "@stenajs-webui/core";
import { Banner, Label } from "@stenajs-webui/elements";
import {
Banner,
Label,
PrimaryButton,
SecondaryButton,
} from "@stenajs-webui/elements";
import { TravelDateRangeInputValue } from "../../../features/travel-calendar/types";
import { parseLocalizedDateString } from "../../../features/localize-date-format/LocalizedDateParser";
import { formatLocalizedDate } from "../../../features/localize-date-format/LocalizedDateFormatter";
import { addWeeks } from "date-fns";

export default {
title: "calendar/Input/TravelDateRangeInput",
Expand Down Expand Up @@ -134,3 +141,69 @@ const LocaleDemo = ({ localeCode }: { localeCode: string }) => {
</Column>
);
};

export const WithValidationAndCloseButton = () => {
const [value, setValue] = useState<TravelDateRangeInputValue | undefined>(
undefined
);

return (
<div style={{ display: "inline-block", padding: "150px 80px" }}>
<TravelDateRangeInput
value={value}
onValueChange={setValue}
localeCode={"sv"}
heading={"Select dates"}
renderBelowCalendar={({ hideCalendar }) => {
return (
<Column gap={2}>
<Banner variant={"error"} text={"Your dates are not good."} />
<PrimaryButton label={"Close"} onClick={hideCalendar} />
</Column>
);
}}
/>
</div>
);
};

export const WithPresets = () => {
const [value, setValue] = useState<TravelDateRangeInputValue | undefined>(
undefined
);

return (
<div style={{ display: "inline-block", padding: "150px 80px" }}>
<TravelDateRangeInput
value={value}
onValueChange={setValue}
localeCode={"sv"}
heading={"Select dates"}
renderBelowCalendar={() => {
return (
<Column gap={2}>
<SecondaryButton
label={"1 week starting today"}
onClick={() =>
setValue({
startDate: formatLocalizedDate(new Date(), "sv"),
endDate: formatLocalizedDate(addWeeks(new Date(), 1), "sv"),
})
}
/>
<SecondaryButton
label={"2 weeks starting today"}
onClick={() =>
setValue({
startDate: formatLocalizedDate(new Date(), "sv"),
endDate: formatLocalizedDate(addWeeks(new Date(), 2), "sv"),
})
}
/>
</Column>
);
}}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as React from "react";
import {
KeyboardEventHandler,
ReactNode,
useCallback,
useLayoutEffect,
useRef,
useState,
} from "react";
import {
Box,
Column,
Heading,
HeadingVariant,
useOnClickOutside,
Expand All @@ -24,6 +24,10 @@ import { TravelDateRangeInputValue } from "../../../features/travel-calendar/typ
import styles from "./TravelDateRangeInput.module.css";
import cx from "classnames";

export interface RenderBelowCalendarArgs {
hideCalendar: () => void;
}

export interface TravelDateRangeInputProps
extends ValueAndOnValueChangeProps<TravelDateRangeInputValue> {
localeCode?: string;
Expand All @@ -34,8 +38,12 @@ export interface TravelDateRangeInputProps
nextMonthButtonAriaLabel?: string;
heading?: string;
headingLevel?: HeadingVariant;
firstMonthInMonthPicker?: Date;
numMonthsInMonthPicker?: number;
zIndex?: number;
zIndexWhenClosed?: number;
onHideCalendar?: () => void;
renderBelowCalendar?: (args: RenderBelowCalendarArgs) => ReactNode;
}

export const TravelDateRangeInput: React.FC<TravelDateRangeInputProps> = ({
Expand All @@ -49,8 +57,12 @@ export const TravelDateRangeInput: React.FC<TravelDateRangeInputProps> = ({
nextMonthButtonAriaLabel = "Next month",
heading,
headingLevel,
numMonthsInMonthPicker = 12,
firstMonthInMonthPicker = new Date(),
zIndex = 1000,
zIndexWhenClosed,
onHideCalendar,
renderBelowCalendar,
}) => {
const [calendarOpen, setCalendarOpen] = useState(false);
const [calendarInDom, setCalendarInDom] = useState(false);
Expand All @@ -77,12 +89,14 @@ export const TravelDateRangeInput: React.FC<TravelDateRangeInputProps> = ({

setCalendarOpen(false);
calendarOpenRef.current = false;
onHideCalendar?.();

setTimeout(() => {
if (!calendarOpenRef.current) {
setCalendarInDom(false);
}
}, 120);
}, [calendarInDom]);
}, [calendarInDom, onHideCalendar]);

const ref = useRef<HTMLDivElement>(null);
const sizeSourceRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -152,50 +166,49 @@ export const TravelDateRangeInput: React.FC<TravelDateRangeInputProps> = ({
<Box
position={"absolute"}
zIndex={zIndex - 1}
left={-24}
top={heading ? -80 : -24}
left={"-2.4rem"}
top={heading ? "-8.0rem" : "-2.4rem"}
className={cx(styles.overlay, calendarOpen && styles.calendarVisible)}
>
<Box
background={"white"}
shadow={"popover"}
borderRadius={"var(--swui-border-radius-large)"}
>
<CardBody>
<Column gap={3}>
{heading && (
<Heading variant={"h2"} as={headingLevel}>
{heading}
</Heading>
)}
<Box height={"68px"} />
<MonthHeader
{...inputProps}
previousMonthButtonAriaLabel={previousMonthButtonAriaLabel}
nextMonthButtonAriaLabel={nextMonthButtonAriaLabel}
<CardBody gap={3}>
{heading && (
<Heading variant={"h2"} as={headingLevel}>
{heading}
</Heading>
)}
<Box height={"6.8rem"} />
<MonthHeader
{...inputProps}
previousMonthButtonAriaLabel={previousMonthButtonAriaLabel}
nextMonthButtonAriaLabel={nextMonthButtonAriaLabel}
/>

{visiblePanel === "calendar" && (
<TravelCalendar {...inputProps} />
)}

{visiblePanel === "month-picker" && (
<MonthPicker
firstMonth={firstMonthInMonthPicker}
numMonths={numMonthsInMonthPicker}
value={visibleMonth}
onValueChange={(v) => {
setVisibleMonth(v);
setVisiblePanel("calendar");
monthPickerButtonRef.current?.focus();
}}
onCancel={() => {
setVisiblePanel("calendar");
monthPickerButtonRef.current?.focus();
}}
/>

{visiblePanel === "calendar" && (
<TravelCalendar {...inputProps} />
)}

{visiblePanel === "month-picker" && (
<MonthPicker
firstMonth={new Date()}
numMonths={12}
value={visibleMonth}
onValueChange={(v) => {
setVisibleMonth(v);
setVisiblePanel("calendar");
monthPickerButtonRef.current?.focus();
}}
onCancel={() => {
setVisiblePanel("calendar");
monthPickerButtonRef.current?.focus();
}}
/>
)}
</Column>
)}
{renderBelowCalendar?.({ hideCalendar })}
</CardBody>
</Box>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const getTabIndex = (
/**
* If date has been selected that date should be tabIndex = 0.
* If no date has been selected, today's date should be tabIndex = 0.
* All else should be 1.
* All else should be -1.
*/
if (
selectedStartDate && selectedStartDateIsVisible
Expand Down

0 comments on commit 715966a

Please sign in to comment.