Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c1d6554
[WB-1112] Initial commit
marcysutton Nov 19, 2025
561f52f
[WB-1112] Update react-day-picker
marcysutton Nov 19, 2025
2acce1e
[WB-1112] Initial port from frontend
marcysutton Nov 19, 2025
2cfc85e
[WB-1112] WIP: dependency migration
marcysutton Nov 21, 2025
50cfde8
[WB-1112] Continued Date Picker migration
marcysutton Nov 24, 2025
0cf4e5e
[WB-1112] Improve rootWithEsc props
marcysutton Dec 3, 2025
451b6b7
[WB-1112] Fix date-picker tests
marcysutton Dec 3, 2025
05a6836
[WB-1112] Fix remaining tests, add stories
marcysutton Dec 3, 2025
2c8250a
[WB-1112] docs(changeset): Add new Date Picker package and related de…
marcysutton Dec 3, 2025
b00e5ee
[WB-1112] Clean up moment references
marcysutton Dec 3, 2025
beb90f2
[WB-1112] Add modal dependency
marcysutton Dec 3, 2025
fbdf13b
[WB-1112] Add missing deps
marcysutton Dec 3, 2025
2cc7e5f
[WB-1112] Update tsconfig
marcysutton Dec 3, 2025
c05384d
[WB-1112] Fine tune snapshots
marcysutton Dec 4, 2025
72a41f2
[WB-1112] Remove unused theme files
marcysutton Dec 4, 2025
6146853
[WB-1112] Improve styling
marcysutton Dec 4, 2025
6357c82
[WB-1112] Update dependencies
marcysutton Dec 4, 2025
a648bc9
[WB-1112] Restore deps for Storybook
marcysutton Dec 4, 2025
39dd0b9
[WB-1112] Remove empty files
marcysutton Dec 4, 2025
4ce062e
[WB-1112] Remove skipped tests
marcysutton Dec 4, 2025
f773387
[WB-1112] Rework CSS bundling
marcysutton Dec 4, 2025
a44753f
[WB-1112] Update exports, remove stories
marcysutton Dec 5, 2025
b92e51a
[WB-1112] Remove unused file
marcysutton Dec 5, 2025
2dd7f84
[WB-1112] Update utils and tests
marcysutton Dec 5, 2025
7b77ef2
[WB-1112] Simplify PR
marcysutton Dec 5, 2025
ccc5db3
[WB-1112] Update docs
marcysutton Dec 5, 2025
d77cc9b
[WB-1112] Restore CSS handling approach
marcysutton Dec 5, 2025
6ed6765
[WB-1112] Add more dates to overlay story
marcysutton Dec 5, 2025
4dbb09e
[WB-1112] Add theme accent color
marcysutton Dec 5, 2025
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: 6 additions & 0 deletions .changeset/odd-scissors-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/wonder-blocks-date-picker": minor
"@khanacademy/wb-dev-build-settings": minor
---

Add new Date Picker package and related dev settings
183 changes: 183 additions & 0 deletions __docs__/wonder-blocks-date-picker/date-picker-input.stories.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I don't think we need to expose the package internals as we only expect devs to use DatePicker directly. These stories can be removed. Same with DatePickerOverlay and probably with MaybeNativeDatePicker (still TBD).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I removed them!

Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import {Temporal} from "temporal-polyfill";
import * as React from "react";
import {enUS} from "react-day-picker/locale";
import type {CustomModifiers} from "@khanacademy/wonder-blocks-date-picker";

import {View} from "@khanacademy/wonder-blocks-core";
import {BodyText} from "@khanacademy/wonder-blocks-typography";

import {
DatePickerInput,
TemporalLocaleUtils,
} from "@khanacademy/wonder-blocks-date-picker";
import ComponentInfo from "../components/component-info";
import packageConfig from "../../packages/wonder-blocks-date-picker/package.json";

type StoryArgs = React.ComponentProps<typeof DatePickerInput>;

// A wrapper component that handles state management for the stories
const DateInputWrapper = (props: StoryArgs) => {
const [isInvalid, setIsInvalid] = React.useState(false);
const [selectedDate, setSelectedDate] = React.useState<
Date | null | undefined
>(() => {
if (props.value && typeof props.value === "string") {
try {
const locale = enUS.code;
const parsed = TemporalLocaleUtils.parseDate(
props.value,
props.dateFormat,
locale,
);
return parsed
? TemporalLocaleUtils.temporalDateToJsDate(parsed)
: null;
} catch {
return null;
}
}
return null;
});

const handleChange = (
date: Date | null | undefined,
modifiers: Partial<CustomModifiers>,
) => {
// We don't want to send back invalid dates.
if (!date || modifiers.disabled) {
setIsInvalid(true);
return;
}

setIsInvalid(false);
setSelectedDate(date);
};
const locale = navigator.language || "en";

const selectedDateAsValue = selectedDate
? TemporalLocaleUtils.formatDate(
TemporalLocaleUtils.jsDateToTemporalDate(selectedDate),
props.dateFormat,
locale,
)
: props.value;

return (
<View>
<DatePickerInput
{...props}
onBlur={() => {
setSelectedDate(selectedDate);
}}
onChange={handleChange}
value={selectedDateAsValue}
/>
{isInvalid && <BodyText>Invalid date</BodyText>}
</View>
);
};

const minDate = Temporal.Now.plainDateISO();
const maxDate = minDate.add({days: 10});

export default {
title: "Packages / Date Picker / DatePickerInput",
component: DatePickerInput,
parameters: {
componentSubtitle: (
<ComponentInfo
name={packageConfig.name}
version={packageConfig.version}
/>
),
},
};

export const SelectedDateIsNow = {
args: {
disabled: false,
value: TemporalLocaleUtils.formatDate(
Temporal.Now.plainDateISO(),
"MMMM D, YYYY",
"en-US",
),
dateFormat: "MMMM D, YYYY",
parseDate: TemporalLocaleUtils.parseDateToJsDate,
getModifiersForDay: TemporalLocaleUtils.getModifiersForDay,
modifiers: {
selected: TemporalLocaleUtils.temporalDateToJsDate(
Temporal.Now.plainDateISO(),
),
// We want to disable past dates and dates after 10 days from now
disabled: (date: Date) => {
const temporalDate =
TemporalLocaleUtils.jsDateToTemporalDate(date);

return (
(minDate &&
Temporal.PlainDate.compare(temporalDate, minDate) <
0) ||
(maxDate &&
Temporal.PlainDate.compare(temporalDate, maxDate) > 0)
);
},
},
},
render: (args: StoryArgs) => <DateInputWrapper {...args} />,
};

export const DisabledState = {
args: {
disabled: true,
value: "May 7, 2021",
parseDate: TemporalLocaleUtils.parseDateToJsDate,
dateFormat: "MMMM D, YYYY",
},
chromatic: {
// Disabling because this is behavior is tested in TextField stories
disableSnapshot: true,
},
render: (args: StoryArgs) => <DateInputWrapper {...args} />,
};

export const InvalidDate = {
args: {
value: "May 7, 2024",
dateFormat: "MMMM D, YYYY",
parseDate: TemporalLocaleUtils.parseDateToJsDate,
getModifiersForDay: TemporalLocaleUtils.getModifiersForDay,
modifiers: {
selected: TemporalLocaleUtils.temporalDateToJsDate(
Temporal.Now.plainDateISO(),
),
// We want to disable past dates and dates after 10 days from now
disabled: (date: Date) => {
const temporalDate =
TemporalLocaleUtils.jsDateToTemporalDate(date);
return (
(minDate &&
Temporal.PlainDate.compare(temporalDate, minDate) <
0) ||
(maxDate &&
Temporal.PlainDate.compare(temporalDate, maxDate) > 0)
);
},
},
},
render: (args: StoryArgs) => <DateInputWrapper {...args} />,
};

export const WithAriaLabel = {
args: {
"aria-label": "Choose a date",
value: "May 7, 2021",
dateFormat: "MMMM D, YYYY",
},
parameters: {
chromatic: {
// Disabling because this doesn't test anything visual.
disableSnapshot: true,
},
},
render: (args: StoryArgs) => <DatePickerInput {...args} />,
};
3 changes: 3 additions & 0 deletions __docs__/wonder-blocks-date-picker/date-picker.argtypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type {ArgTypes} from "@storybook/react-vite";

export default {} satisfies ArgTypes;
Loading
Loading