Skip to content

Commit

Permalink
Separate DatePicker and DatePickerRange
Browse files Browse the repository at this point in the history
  • Loading branch information
nighto committed Dec 6, 2024
1 parent b7c83d6 commit 6c1ea5a
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,5 @@ $navigation-distance: 1rem;
display: flex;
flex-direction: row;
align-items: center;

.bk-date-picker__date {
text-align: right;
}
}
}
51 changes: 0 additions & 51 deletions src/components/forms/controls/DatePicker/DatePicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ type DatePickerArgs = React.ComponentProps<typeof DatePicker>;
type Story = StoryObj<DatePickerArgs>;

export default {
// TODO: Why this error???
component: DatePicker,
parameters: {
layout: 'centered',
Expand Down Expand Up @@ -45,53 +44,3 @@ export const DatePickerStory: Story = {
);
}
};

export const DatePickerStoryWithRange: Story = {
name: 'Date Picker with Dates Range',
render: (args) => {
const [startDate, setStartDate] = React.useState<Date | null>(new Date());
const [endDate, setEndDate] = React.useState<Date | null>(new Date());
const onChange = (dates: (Date | null)[]) => {
let [start, end] = dates;
// TODO: should I make sure start and end are not undefined in a different way?
// apparently linter thinks they can be undefined because of the destructuring.
if (start !== undefined && end !== undefined) {
setStartDate(start);
setEndDate(end);
}
};

return (
<div style={{height: '500px'}}>
<DatePicker
{...args}
// TODO: same error as previous story
selected={startDate}
onChange={onChange}
startDate={startDate}
endDate={endDate}
selectsRange={true}
inline={true}
/>
</div>
);

// const [startDate, setStartDate] = React.useState(new Date());
// const [endDate, setEndDate] = React.useState(null);
// const onChange = (dates: any) => {
// const [start, end] = dates;
// setStartDate(start);
// setEndDate(end);
// };
// return (
// <DatePicker
// selected={startDate}
// onChange={onChange}
// startDate={startDate}
// endDate={endDate}
// selectsRange
// inline
// />
// );
}
};
21 changes: 17 additions & 4 deletions src/components/forms/controls/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,37 @@
import * as React from 'react';
import ReactDatePicker from 'react-datepicker';

import { classNames as cx, type ClassNameArgument } from '../../../../util/componentUtil.ts';
import { classNames as cx, type ClassNameArgument, type ComponentProps } from '../../../../util/componentUtil.ts';

import 'react-datepicker/dist/react-datepicker.css';
import cl from './DatePicker.module.scss';

import { Input } from '../Input/Input.tsx';


// Omit<ComponentPropsWithoutRef<typeof ReactDatePicker>, 'onChange'> & {
export type DatePickerProps = typeof ReactDatePicker & {
// TODO: eventually move this to a separate file, as this tends to be repeated on every component?
type GenericProps = {
/** Whether this component should be unstyled. */
unstyled?: undefined | boolean,

/** An optional class name to be appended to the class list. */
className?: ClassNameArgument,
}

// copying props from react-datepicker and restricting them to specific versions
// TODO: I would like to reuse this on DatePickerRange.tsx, how can I reuse it there without exporting it?
type GenericReactDatePickerOmittedProps = Omit<ComponentProps<typeof ReactDatePicker>, 'selectsRange' | 'selectsMultiple' | 'onChange'>;

type ReactDatePickerProps = GenericReactDatePickerOmittedProps & {
// TODO: considering we omitted them, do we still need to include it's properties as "never" (as defined on original library),
// or in this case is it redundant?
// selectsRange?: never,
// selectsMultiple?: never,
onChange?: (date: Date | null, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void,
};

/** A wrapper for ReactDatePicker */
export type DatePickerProps = GenericProps & ReactDatePickerProps;

export const DatePicker = (props: DatePickerProps) => {
const {
unstyled = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Copyright (c) Fortanix, Inc.
|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import type { Meta, StoryObj } from '@storybook/react';

import * as React from 'react';

import { DatePickerRange } from './DatePickerRange.tsx';


type DatePickerRangeArgs = React.ComponentProps<typeof DatePickerRange>;
type Story = StoryObj<DatePickerRangeArgs>;

export default {
component: DatePickerRange,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
},
args: {},
decorators: [
Story => <form onSubmit={event => { event.preventDefault(); }}><Story/></form>,
],
} satisfies Meta<DatePickerRangeArgs>;

export const DatePickerRangeStory: Story = {
name: 'Date Picker with Dates Range',
render: (args) => {
const [startDate, setStartDate] = React.useState(new Date());
const [endDate, setEndDate] = React.useState(new Date());
const onChange = (dates: [Date, Date]) => {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
};

return (
<div style={{height: '500px'}}>
<DatePickerRange
{...args}
selected={startDate}
onChange={onChange}
startDate={startDate}
endDate={endDate}
inline={true}
/>
</div>
);
}
};
72 changes: 72 additions & 0 deletions src/components/forms/controls/DatePicker/DatePickerRange.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* Copyright (c) Fortanix, Inc.
|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react';
import ReactDatePicker from 'react-datepicker';

import { classNames as cx, type ClassNameArgument, type ComponentProps } from '../../../../util/componentUtil.ts';

import 'react-datepicker/dist/react-datepicker.css';
import cl from './DatePicker.module.scss';

import { Input } from '../Input/Input.tsx';


// TODO: eventually move this to a separate file, as this tends to be repeated on every component?
type GenericProps = {
/** Whether this component should be unstyled. */
unstyled?: undefined | boolean,

/** An optional class name to be appended to the class list. */
className?: ClassNameArgument,
}

// copying props from react-datepicker and restricting them to specific versions
// TODO: I would like to reuse this from DatePicker.tsx, how can I reuse it from there without exporting it?
// Or maybe the solution is to define all variants from the same file?
type GenericReactDatePickerOmittedProps = Omit<ComponentProps<typeof ReactDatePicker>, 'selectsRange' | 'selectsMultiple' | 'onChange'>;

type ReactDatePickerRangeProps = GenericReactDatePickerOmittedProps & {
// not including selectsRange because we will pass that manually to react-datepicker
// selectsMultiple?: never,
// TODO: it seems like react-datepicker has a bug? the onChange accepts always "Date | null" or variations -
// in this case, an array of exactly two elements of Date | null)
// but then the startDate and endDate parameters do NOT take null.
// therefore I think it'd make sense to handle them as [Date, Date] and
// somehow make it accept (as our more strict variant is within what they accept)
onChange?: (date: [Date, Date], event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void,
};

export type DatePickerRangeProps = GenericProps & ReactDatePickerRangeProps;

export const DatePickerRange = (props: DatePickerRangeProps) => {
// TODO how could I reuse DatePicker component only passing the props that I want? Something as simple as
// <DatePicker selectsRange={true} {...propsRest} />

const {
unstyled = false,
className,
...propsRest
} = props;

return (
<div className={cx(
'bk',
{ [cl['bk-datepicker']]: !unstyled },
className,
)}>
<ReactDatePicker

Check failure on line 59 in src/components/forms/controls/DatePicker/DatePickerRange.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

Type '{ ref?: Ref<DatePicker> | undefined; children?: ReactNode; form?: string; title?: string; key?: Key | null | undefined; autoFocus?: boolean; id?: string; tabIndex?: number; onFocus?: FocusEventHandler<HTMLElement>; onBlur?: FocusEventHandler<HTMLElement>; onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void; onSelect?: (date: Date | null, event?: KeyboardEvent<HTMLElement> | MouseEvent<HTMLElement, MouseEvent> | undefined) => void; inline?: boolean; date?: Date; name?: string; icon?: ReactNode; value?: string; disabled?: boolean; open?: boolean; autoComplete?: string; readOnly?: boolean; required?: boolean; minDate?: Date; maxDate?: Date; scrollableYearDropdown?: boolean; yearDropdownItemNumber?: number; adjustDateOnChange?: boolean; dropdownMode?: "select" | "scroll"; locale?: Locale; useShortMonthInDropdown?: boolean; scrollableMonthYearDropdown?: boolean; dateFormat: string | string[]; selectingDate?: Date; onYearMouseEnter?: (event: MouseEvent<HTMLDivElement, MouseEvent>, year: number) => void; onYearMouseLeave?: (event: MouseEvent<HTMLDivElement, MouseEvent>, year: number) => void; excludeDates?: { date: Date; message?: string; }[] | Date[]; includeDates?: Date[]; filterDate?: (date: Date) => boolean; disabledKeyboardNavigation?: boolean; endDate?: Date; selected?: Date | null; usePointerEvent?: boolean; renderYearContent?: (year: number) => ReactNode; selectsEnd?: boolean; selectsStart?: boolean; startDate?: Date; yearItemNumber?: number; yearClassName?: (date: Date) => string; onDayMouseEnter?: (date: Date) => void; chooseDayAriaLabelPrefix?: string | undefined; disabledDayAriaLabelPrefix?: string | undefined; excludeDateIntervals?: { start: Date; end: Date; }[]; includeDateIntervals?: { start: Date; end: Date; }[]; dayClassName?: (date: Date) => string; highlightDates?: (Date | HighlightDate)[]; holidays?: Holiday[]; showWeekPicker?: boolean; selectsDisabledDaysInRange?: boolean; selectedDates?: Date[]; renderDayContents?: (day: number, date: Date) => ReactNode; containerRef?: RefObject<HTMLDivElement>; calendarStartDay?: Day; shouldCloseOnSelect?: boolean; formatWeekNumber?: (date: Date) => number; onWeekSelect?: (day: Date, weekNumber: number, event: MouseEvent<HTMLDivElement, MouseEvent>) => void; monthClassName?: (date: Date) => string; renderMonthContent?: (m: number, shortMonthText: string, fullMonthText: string, day: Date) => ReactNode; renderQuarterContent?: (q: number, shortQuarter: string) => ReactNode; fixedHeight?: boolean; peekNextMonth?: boolean; showWeekNumbers?: boolean | undefined; showMonthYearPicker?: boolean; showFullMonthYearPicker?: boolean; showTwoColumnMonthYearPicker?: boolean; showFourColumnMonthYearPicker?: boolean; showQuarterYearPicker?: boolean; weekAriaLabelPrefix?: string | undefined; minTime?: Date; maxTime?: Date; excludeTimes?: Date[]; includeTimes?: Date[]; filterTime?: (time: Date) => boolean; openToDate?: Date; timeClassName?: (time: Date) => string; todayButton?: ReactNode; timeCaption?: string; injectTimes?: Date[]; showTimeSelectOnly?: boolean; showTimeCaption?: boolean; timeInputLabel?: string; customTimeInput?: any; showYearPicker?: boolean; showTimeSelect?: boolean; showTimeInput?: boolean; showYearDropdown?: boolean; showMonthDropdown?: boolean; useWeekdaysShort?: boolean; forceShowMonthNavigation?: boolean; showDisabledMonthNavigation?: boolean; formatWeekDay?: (date: string) => string; weekDayClassName?: (date: Date) => string; onMonthChange?: (date: Date) => void; onYearChange?: (date: Date) => void; onMonthMouseLeave?: VoidFunction; weekLabel?: string; onClickOutside?: ClickOutsideHandler; previousMonthButtonLabel?: ReactNode; previousYearButtonLabel?: string; previousMonthAriaLabel?: string; previousYearAriaLabel?: string; nextMonthButtonLabel?: ReactNode; nextYearButtonLabel?: string; nextMonthAriaLabel?: string; nextYearAriaLabel?: string; showPreviousMonths?: boolean; monthsShown?: number; renderCustomHeader?: (props: ReactDatePickerCustomHeaderProps) => JSX.Element; monthAriaLabelPrefix?: string | undefined; timeFormat?: string | undefined; timeInterv
selectsRange={true}
// everything else is the same
className={cx(
cl['bk-date-picker__date'],
)}
dateFormat="MM/dd/yyyy"
placeholderText="MM/DD/YYYY"
customInput={<Input />}
{...propsRest}
/>
</div>
);
};

0 comments on commit 6c1ea5a

Please sign in to comment.