Skip to content

feat(DatePicker): New DatePicker component#3286

Draft
aresnik11 wants to merge 48 commits intomainfrom
ajr-datepicker-localization
Draft

feat(DatePicker): New DatePicker component#3286
aresnik11 wants to merge 48 commits intomainfrom
ajr-datepicker-localization

Conversation

@aresnik11
Copy link
Contributor

@aresnik11 aresnik11 commented Mar 17, 2026

Overview

WIP of the new DatePicker component. Looking for some early feedback, comments, questions, concerns. I left some comments in the code of things I'm questioning.

Things I know are missing/not completely working:

  • input validation when typing in a date (working on now)
  • calendar is supposed to close after selecting a date(s)
  • supporting small input size
  • tests
  • calendar quick actions

PR Checklist

  • Related to designs:
  • Related to JIRA ticket: GMT-1520
  • I have run this code to verify it works
  • This PR includes unit tests for the code change
  • This PR includes testing instructions tests for the code change
  • The alpha package of this PR is passing end-to-end tests in all relevant Codecademy repositories

Testing Instructions

Don't make me tap the sign.

  1. Go to story X
  2. Do something
  3. Do that something in dark mode
  4. Check it with VO
  5. Finish and do a celebratory dance

PR Links and Envs

Repository PR Link
Monolith Monolith PR
Mono Mono PR

@nx-cloud
Copy link

nx-cloud bot commented Mar 17, 2026

View your CI Pipeline Execution ↗ for commit afaea71


☁️ Nx Cloud last updated this comment at 2026-03-23 18:06:36 UTC

@codecov
Copy link

codecov bot commented Mar 17, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
811 1 810 0
View the top 1 failed test(s) by shortest run time
 Gamut Exported Keys
Stack Traces | 0.005s run time
Error: expect(received).toMatchSnapshot()

Snapshot name: `Gamut Exported Keys 1`

- Snapshot  - 5
+ Received  + 6

@@ -12,13 +12,13 @@
    "Breadcrumbs",
    "Calendar",
    "CalendarBody",
    "CalendarFooter",
    "CalendarHeader",
+   "capitalizeFirst",
    "Card",
    "Checkbox",
-   "clampToMonth",
    "Coachmark",
    "Column",
    "ConnectedCheckbox",
    "ConnectedForm",
    "ConnectedFormGroup",
@@ -52,24 +52,26 @@
    "Flyout",
    "FocusTrap",
    "focusVisibleStyle",
    "Form",
    "formatDateForInput",
-   "formatDateRangeForInput",
    "formatMonthYear",
    "FormError",
    "FormGroup",
    "FormGroupDescription",
    "FormGroupLabel",
    "FormPropsContext",
    "FormRequiredText",
    "generateResponsiveClassnames",
+   "getDateFormatPattern",
+   "getDatesWithRow",
    "getDayOfWeek",
    "getFocusableElements",
    "getMonthGrid",
-   "getWeekdayFullNames",
-   "getWeekdayLabels",
+   "getRelativeMonthLabels",
+   "getRelativeTodayLabel",
+   "getWeekdayNames",
    "GridBox",
    "GridForm",
    "GridFormContent",
    "HiddenText",
    "IconButton",
@@ -93,11 +95,10 @@
    "Modal",
    "omitProps",
    "Overlay",
    "Pagination",
    "parseDateFromInput",
-   "parseDateRangeFromInput",
    "Popover",
    "PopoverContainer",
    "PreviewTip",
    "ProgressBar",
    "RadialProgress",
    at Object.<anonymous> (.../gamut/__tests__/gamut.test.ts:7:25)
    at Promise.then.completed (.../jest-circus/build/utils.js:298:28)
    at new Promise (<anonymous>)
    at callAsyncCircusFn (.../jest-circus/build/utils.js:231:10)
    at _callCircusTest (.../jest-circus/build/run.js:316:40)
    at async _runTest (.../jest-circus/build/run.js:252:3)
    at async _runTestsForDescribeBlock (.../jest-circus/build/run.js:126:9)
    at async run (.../jest-circus/build/run.js:71:3)
    at async runAndTransformResultsToJestFormat (.../build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)
    at async jestAdapter (.../build/legacy-code-todo-rewrite/jestAdapter.js:79:19)
    at async runTestInternal (.../jest-runner/build/runTest.js:367:16)
    at async runTest (.../jest-runner/build/runTest.js:444:34)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@aresnik11 aresnik11 changed the base branch from ajr-datepicker-styles to main March 18, 2026 15:09
@aresnik11 aresnik11 mentioned this pull request Mar 18, 2026
6 tasks
@aresnik11 aresnik11 changed the title Ajr datepicker localization feat(DatePicker): New DatePicker component Mar 18, 2026
Copy link
Contributor

@LinKCoding LinKCoding left a comment

Choose a reason for hiding this comment

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

nice start!
I wasn't sure entirely where to hone in for DatePicker related files specifically tbh
but I gave some thoughts for Calendar files.

Something for much later on is to use JSDoc style comments for the util functions

Comment on lines +8 to +10
/**
* Normalize to start of day in local time for comparison.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider converting these comments for functions to JSDoc format

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the robot wrote all of these so maybe we can teach it to write them as jsdocs

@@ -0,0 +1,122 @@
import {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is missing a .mdx file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i know

const [selectedDate, setSelectedDate] = useState<Date | null>(null);
return (
<Box>
<DatePicker
Copy link
Contributor

Choose a reason for hiding this comment

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

small thing — noticed that the tooltip lingers even after clicking and moving away from the previous or next month IconButton

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dreamwasp said they would help look into this

Copy link
Member

@sh0ji sh0ji left a comment

Choose a reason for hiding this comment

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

I'm still reviewing but wanted to get my first set of notes up, so here they are. I haven't looked at the React code yet.

Initial focus

There are two possibly related issues here:

  1. Focus shouldn't move into the calendar when I click into the input with a pointer.

    When I first click into the input element with a pointer (mouse), the calendar opens and focus moves into it. I think it's okay for the calendar to open on click, but focus shouldn't move into it unless the user specifically chooses to move focus into it. As-is, I can't click the input and start typing.

    Interestingly, if I navigate to the input with a keyboard or screen reader, focus doesn't move into the calendar until I press ArrowDown so I'm guessing this is specifically tied to pointer or mouse events?

    This is a violation of WCAG 3.2.1 On Focus since focus moves on focus (technically it's probably moving on click, but the effect is the same).

  2. When focus does move into the calendar the first time, it should always be visible.

When the date is empty and I open the calendar, I can't tell where focus is. It's okay if focus isn't visible for pointer users after they click around in it—this is just about the initial state on open.

My rationale for this is that the input retains a blue hover indicator that makes it look like it's focused when it's not, which is confusing. Pointer users might expect to switch to using their keyboard when they interact with input fields, so the visual focus indicator is important for everyone here, not just keyboard users.

Note that it's visible when I open it with my keyboard but it's not visible when I open it with my mouse, so I'm guessing this is an artifact of :focus-visible.

Focus ring

The focus ring for the grid cells in the calendar uses what looks like an opacity transition. Generally for grids like this you want to keep motion minimal (including opacity transitions) since it's dense and this can be distracting. I'd recommend nixing the transition for the grid cells.

This isn't a WCAG violation but COGA Usable covers distractions pretty clearly and I always try to look out for this kind of thing.

Grid cell selected state

Currently, it's not possible to discern whether a grid cell is selected or not with a screen reader. After digging into this, I'm not 100% what the best solution to this would be so I'm going to keep digging, but I'll describe the problem and our options at least.

The grid cells (role="gridcell") currently have the following structure:

<td aria-selected={state} role="gridcell">
  <button tabindex="0" type="button" />
</td>

Since the interactive element is the <button>, screen reader users can't access the aria-selected state while interacting with the buttons (when they need to know it). And we can't move aria-selected to the button because aria-selected isn't valid on buttons.

Given all of that, I think we have two options:

<!-- 1. take the td out of the a11y tree; use the button as our cell -->
<td role="none">
  <button aria-selected={state} role="gridcell" tabindex="0" />
</td>

<!-- 2. get rid of the button -->
<td aria-selected={state} role="gridcell" tabindex="0">
  {the_day_number}
</td>

I don't love option 1 since it feels wrong to override so many semantics like that but it's technically probably okay. I'll keep digging to see if there's a good reason to prefer one of the two choices, or if there are other options.

Placeholder isn't accessible to anyone

The date format is currently only accessible via the input's placeholder. But once I click into it, I can't see it anymore. In other words, when I'm typing my date, I can't see what format the date needs to be in, which is exactly when the format is most important to me.

The only way for a user to solve this problem is to commit the placeholder format to their working memory, a difficult task for everyone and an impossible task for some. WCAG famously doesn't have a success criterion to address this but it is something they're looking to address in WCAG 3. For now, COGA Usable does cover it under Ensure Processes Do Not Rely on Memory.

My recommendation is a controversial one: never use placeholder, for any reason. Use a field description or a static floating label that moves when the input is focused (Material design uses this).

@aresnik11
Copy link
Contributor Author

@sh0ji re focus ring transition: this is our default gamut focus ring. do you want me to override it for this case or can i cut a separate ticket to update the focus ring opacity transition across the board?

@codecademydev
Copy link
Collaborator

📬 Published Alpha Packages:

@codecademy/gamut@68.1.3-alpha.422537.0
@codecademy/gamut-kit@0.6.588-alpha.422537.0
@codecademy/styleguide@79.2.2-alpha.422537.0

@github-actions
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants