Skip to content

Add DateCalendar and DateRangeCalendar components #102989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 65 commits into from
May 22, 2025

Conversation

ciampo
Copy link
Contributor

@ciampo ciampo commented Apr 29, 2025

Related to DS-201

Proposed Changes

Create new DateCalendar and DateRangeCalendar components based on the react-day-picker library

Kapture.2025-05-16.at.17.13.29.mp4

Why are these changes being made?

We need a complete and accessible set of calendar components across A8C

Testing Instructions

  • Check the PR out
  • run yarn workspace @automattic/components run storybook:start and visit the DateCalendar and DateRangeCalendar stories

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages.
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

Copy link

github-actions bot commented Apr 29, 2025

@ciampo ciampo force-pushed the try/calendar-react-day-picker branch from 026ee11 to 2233c42 Compare April 29, 2025 22:27
@matticbot
Copy link
Contributor

matticbot commented Apr 29, 2025

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

Sections (~1150 bytes added 📈 [gzipped])

name                             parsed_size           gzip_size
reader                              +39410 B  (+1.1%)     +491 B  (+0.1%)
patterns                            +39410 B  (+2.0%)     +491 B  (+0.1%)
subscribers                         +39375 B  (+3.7%)     +488 B  (+0.2%)
staging-site                        +39375 B  (+3.1%)     +488 B  (+0.1%)
sites-dashboard                     +39375 B  (+3.8%)     +488 B  (+0.2%)
site-settings                       +39375 B  (+2.7%)     +488 B  (+0.1%)
site-purchases                      +39375 B  (+1.8%)     +488 B  (+0.1%)
site-performance                    +39375 B  (+2.9%)     +488 B  (+0.1%)
site-monitoring                     +39375 B  (+3.4%)     +488 B  (+0.1%)
purchases                           +39375 B  (+1.6%)     +488 B  (+0.1%)
plans                               +39375 B  (+1.9%)     +488 B  (+0.1%)
overview                            +39375 B  (+1.5%)     +488 B  (+0.1%)
jetpack-cloud-plugin-management     +39375 B  (+2.1%)     +488 B  (+0.1%)
hosting                             +39375 B  (+2.3%)     +488 B  (+0.1%)
github-deployments                  +39375 B  (+3.3%)     +488 B  (+0.1%)
domains                             +39375 B  (+1.5%)     +488 B  (+0.1%)
a8c-for-agencies-woopayments        +39375 B  (+4.6%)     +488 B  (+0.2%)
a8c-for-agencies-team               +39375 B  (+4.7%)     +488 B  (+0.2%)
a8c-for-agencies-referrals          +39375 B  (+4.3%)     +488 B  (+0.2%)
a8c-for-agencies-plugins            +39375 B  (+2.3%)     +488 B  (+0.1%)
a8c-for-agencies-overview           +39375 B  (+3.6%)     +488 B  (+0.2%)
a8c-for-agencies-migrations         +39375 B  (+4.6%)     +488 B  (+0.2%)
a8c-for-agencies-client             +39375 B  (+1.8%)     +488 B  (+0.1%)
site-logs                           +39373 B  (+3.3%)     +491 B  (+0.1%)
plugins                             +39373 B  (+1.4%)     +754 B  (+0.1%)
a8c-for-agencies-sites              +39373 B  (+1.6%)     +878 B  (+0.1%)

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

Async-loaded Components (~491 bytes added 📈 [gzipped])

name                                                                         parsed_size           gzip_size
async-load-design-wordpress-components-gallery                                  +39410 B  (+5.7%)     +491 B  (+0.3%)
async-load-comment-block-editor                                                 +39410 B  (+1.5%)     +491 B  (+0.1%)
async-load-automattic-global-styles-src-components-global-styles-variations     +39410 B  (+2.2%)     +491 B  (+0.1%)

React components that are loaded lazily, when a certain part of UI is displayed for the first time.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@matticbot
Copy link
Contributor

matticbot commented Apr 29, 2025

This PR modifies the release build for the following Calypso Apps:

For info about this notification, see here: PCYsg-OT6-p2

  • help-center
  • notifications
  • odyssey-stats
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug try/calendar-react-day-picker on your sandbox.

@ciampo
Copy link
Contributor Author

ciampo commented Apr 30, 2025

@jameskoster @verofasulo @mirka @retrofox Could you take a look and share some early feedback? This is a prototype but, if we agree on the approach, I'm going to iterate on it and refine it.

@ciampo
Copy link
Contributor Author

ciampo commented May 3, 2025

Update:

  • I re-wrote the styles (instead importing the original styles and partially overriding them, we're now supplying all the styles first-party) and added a few tweaks (like day button hover colors)
  • I added first-party types, which involved deciding which of the many react-day-picker props should be forwarded/exposed to our consumers;
  • I removed localization from the component, since it would impose the choice of tech stack on the consumer when providing translations data. Consumers of the component will be able to localize it via the locale (used for formatting dates and other calendar preferences such as starting day of the week), labels (used for strings), and dir (used to change visual styles and keyboard interaction for RTL languages)

Next up, I'm going to take a look at controlled vs uncontrolled. react-day-picker only works in controlled mode, and I'm going to try to understand how feasible it is to add support for uncontrolled mode on our end.

In any case, I've added some TODO items in the PR description.

@ciampo ciampo self-assigned this May 3, 2025
@oandregal
Copy link
Contributor

I'm following the linear project and just saw this PR. I thought I'd share the following, though I suppose you're already aware of this.

The "date filter" is one of the features we'd like to have in DataViews (see original conversation). One of the key features would be providing presets ("one day", "past week", etc.). Is this something the new Calendar component will support? or would it be up to consumers to implement?

For reference, in the Site Logs screen, we've reused the existing DateControl (it's also in use in the stats screen, but the component doesn't have a story):

Logs Stats
Screenshot 2025-05-05 at 12 03 58 Screenshot 2025-05-05 at 12 02 21

@ciampo
Copy link
Contributor Author

ciampo commented May 5, 2025

One of the key features would be providing presets ("one day", "past week", etc.). Is this something the new Calendar component will support? or would it be up to consumers to implement?

Hey @oandregal ! Yes, we're aware of this need, and we already discussed it on Gutenberg (link). These components are focused on displaying only the Calendar UI and handling the user interaction. We deliberately decided to exclude:

  • any features that overlap with a datetime input
  • any UI external to the calendar itself (such as presets)

In short — we expect the consumers of the component to handle the presets. I added a "With Presets" Storybook examples to both DateCalendar and DateRangeCalendar which should help with the dataviews case.

Screenshot 2025-05-05 at 14 00 33

Sidenote: happy to help with building that filter dialog, especially when it comes to choosing the componentry and adopting the correct semantics!

@ciampo
Copy link
Contributor Author

ciampo commented May 5, 2025

Update:

  • added support for controlled / uncontrolled use (specific to selected, defaultSelected and onSelect props);
  • added more Storybook examples (including one about localizing the component, very much needed after it was decided to entirely delegate localization to consumers of the components);
  • added an initial implementation for range previews on mouse hover;
Kapture.2025-05-05.at.11.57.30.mp4

The PR is now ready for a preliminary review, with the goal of flagging any missing requirements, any visual tweaks, and discuss the API approach.

I'd like to highlight a few aspects to receive feedback on.

Code, API design and general functionality (cc @Automattic/team-calypso @retrofox @kangzj @miguelpeixe @dkoo @oandregal )

  • Keeping two separate components (DateCalendar and DateRangeCalendar)
  • The general choice of props exposed (I kept the same names as react-day-picker, but decided to write first-party types for better control and more fitting JSDocs);
  • The footer and autoFocus props — do we think they're useful, or should we omit them initially?
  • Clamping numberOfMonths between 1 and 3;
  • I haven't exposed any modifiers-related props in the component's public APIs. We could use them at a later point to implement features like the events prop from the DatePicker component from @wordpress/components — either by implementing the feature internally, or by exposing the props and allow consumers to implement their own modifiers;
  • general a11y (like the default role="application" or the default aria-labels)
  • hooking to Calypso variables (as I'm currently using some temporary, local variables that emulate guenberg vars) (cc @mirka @jameskoster )

Design & visual styles (@jameskoster @verofasulo ):

  • Notice how the calendars break on multiple rows when they don't fit on one row. There isn't much we can do about it, as we expect the consumer of the component to resize its container appropriately.
  • Are we ok with the general look of calendar days? (resting state / hover / focus / selected / selected range / preview start and end);
  • Are we ok with not having any hover styles on the starting and ending range dates?
  • Notice how the focus ring around each day overlaps the neighbouring days — this may not look as good when the neighbouring day is today's date, or the start/end day of a selected range (see screenshots below)

Screenshot 2025-05-05 at 13 45 26
Screenshot 2025-05-05 at 13 47 34

@oandregal oandregal mentioned this pull request May 5, 2025
@oandregal
Copy link
Contributor

I've started to look into adding a datetime filter to DataViews at #103136 It should serve to test the new calendar component developed in this PR as well.

Copy link
Contributor

Generally the design is in a decent spot. I think we might need a different treatment for marking today though; currently it disappears when it's part of a selection.

I'm going to work on all the different states in Figma. I don't think design needs to hold up the PR.

@verofasulo
Copy link

Thank you so much for working on this, @ciampo !

Not sure if it's still in progress, but I can't see this logic implemented in the video above: "Clicking outside the range updates the start/end date accordingly. Clicking within a range deselects as well."

Or maybe it is, and I couldn't get it from the video.

we might need a different treatment for marking today though; currently it disappears when it's part of a selection.

@jameskoster, can't we just keep the outline persistent? Like in the MUI calendar:

image

Copy link
Contributor

@veronica.fasulo I should have been more specific, it disappears when it's the first or last date in a range.

@ciampo
Copy link
Contributor Author

ciampo commented May 6, 2025

Not sure if it's still in progress, but I can't see this logic implemented in the video above: "Clicking outside the range updates the start/end date accordingly. Clicking within a range deselects as well."

By using react-day-picker we are also inheriting the date range selection / deselection behaviour. I personally don't mind their logic, but if we think it's a blocker I can try to work a new behaviour out (although it will take time and add complexity to the code).

I don't think design needs to hold up the PR.

Agree, I don't see any large changes needed — more like small tweaks.

I'll wait for technical feedback before moving forward.

Copy link
Contributor

@kangzj kangzj left a comment

Choose a reason for hiding this comment

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

It looks awesome and works like charm! Thanks for working on this.

Could we please make timeZone a explicit param, which would be useful for Jetpack Stats because all the stats is in the timezone of the site right now?

Plus what's the size implication of adapting the component?

Thanks again!

@ciampo
Copy link
Contributor Author

ciampo commented May 7, 2025

Thank you for taking a look, @kangzj !

Could we please make timeZone a explicit param, which would be useful for Jetpack Stats because all the stats is in the timezone of the site right now?

I don't think handling time zones is relevant since the component only handles dates (and no time) — although I may be missing something.

Plus what's the size implication of adapting the component?

react-day-picker is 17.4Kb minified + zipped, although ~70% of that comes from date-fns, which we would have used directly if we went for a first-party implementation. Also, we're not using the bundled styles, but we're providing our own styles.

Beyond that, though, building a well-functioning, feature-full and accessible Calendar component first-hand would require a big effort, and I'm not sure about what the potential savings in terms of bundle size would be.
We believe that delegating to third party is a better compromise — any inefficiencies in bundle size are compensated by how quickly we were able to put a complex and high-quality component together.

@kangzj
Copy link
Contributor

kangzj commented May 7, 2025

I don't think handling time zones is relevant since the component only handles dates (and no time) — although I may be missing something.

AFAIK, we need to highlight today in the timezone of the site, which otherwise would be incorrect for some pps especially those ones who have a site set to a different timezone as their browser. For example, I'm in +12 zone, and when I look at stats for FG, it always lags 12 hours.

This gets trickier when the Calendar accepts Date objects 🤔

react-day-picker is 17.4Kb minified + zipped, although ~70% of that comes from date-fns, which we would have used directly if we went for a first-party implementation. Also, we're not using the bundled styles, but we're providing our own styles.

Not bad at all. Thanks!

@kangzj
Copy link
Contributor

kangzj commented May 7, 2025

I know this is still a draft but maybe we should put the stories in packages/components path:

Screenshot 2025-05-08 at 11 31 02 AM

@ciampo
Copy link
Contributor Author

ciampo commented May 8, 2025

AFAIK, we need to highlight today in the timezone of the site, which otherwise would be incorrect for some pps especially those ones who have a site set to a different timezone as their browser. For example, I'm in +12 zone, and when I look at stats for FG, it always lags 12 hours.
This gets trickier when the Calendar accepts Date objects 🤔

I assume that the "today" date would always works as expected, given that it's calculated on the client — new Date() should respect the local datetime settings, right? I expect any logic that needs to coordinate time (and time + date) to be external to these Calendar components as they only deal with dates (and not with time).

I know this is still a draft but maybe we should put the stories in packages/components path:

Absolutely, I'll ensure that the Storybook examples' path is right before merging.

@kangzj kangzj requested a review from a team May 8, 2025 21:20
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label May 8, 2025
@kangzj
Copy link
Contributor

kangzj commented May 11, 2025

I assume that the "today" date would always works as expected, given that it's calculated on the client — new Date() should respect the local datetime settings, right?

Like I shared in this comment, Jetpack Stats is always in the timezone of the site, regardless of the browser timezone. It means, today would be in site timezone as well. We only allow selection of past dates + today, so if site timezone and browser timezone don't match, then users could have problems selecting dates. For example, if my browser is +12, and site is UTC, when it's 8am, 12 May, my today would be 12 May, and the today for the site is still 11 May.

image

(I passed 'UTC' as timeZone in the example above and my local timezone is +12)

I understand this might not be super intuitive to users and this is definitely a point to improve; however, it would cover a case where multiple users access the same data set, their data would match.

@ciampo
Copy link
Contributor Author

ciampo commented May 12, 2025

Jetpack Stats is always in the timezone of the site, regardless of the browser timezone. It means, today would be in site timezone as well. We only allow selection of past dates + today, so if site timezone and browser timezone don't match, then users could have problems selecting dates. For example, if my browser is +12, and site is UTC, when it's 8am, 12 May, my today would be 12 May, and the today for the site is still 11 May.

@kangzj Thank you for sharing a more detailed example.

I tried exposing the experimental timeZone prop, and added a quick Storybook example:

https://github.com/Automattic/wp-calypso/blob/9b1cc7d27fcdeed49a74082b2f0697873482df47/packages/components/src/calendar-react-day-picker/date-calendar.stories.tsx/#L58-L70

Could you take a look and let me know if it fulfils your needs?

Things to keep in mind for more context:

  • the onSelect callback will be called with the Date, but I'm not sure about retaining the time zone information
  • the official react-day-picker docs suggest using TZDate from date-fns, so we may have to do that

(@mirka , FYI react-day-picker lists date-fns and date-fns/tz as dependencies anyway)

@kangzj
Copy link
Contributor

kangzj commented May 13, 2025

Could you take a look and let me know if it fulfils your needs?

LGTM! Thanks for doing that 👍

@tyxla
Copy link
Member

tyxla commented May 13, 2025

@garymurray I understand this component is still a necessity for the Woo Analytics project (as per pfKYZu-2M1-p2) - could we get some feedback from the team to ensure it covers all needs? Thanks!

@tyxla tyxla requested review from garysmurray and retrofox May 13, 2025 07:28
@louwie17
Copy link
Contributor

@garymurray I understand this component is still a necessity for the Woo Analytics project (as per pfKYZu-2M1-p2) - could we get some feedback from the team to ensure it covers all needs? Thanks!

Yup, thanks for the reminder @tyxla :)

@ciampo ciampo force-pushed the try/calendar-react-day-picker branch from e812b27 to 8b2838b Compare May 22, 2025 10:17
@ciampo
Copy link
Contributor Author

ciampo commented May 22, 2025

Now that the general implementation feels stable enough both in terms of design, functionality and APIs, I'm going to merge this PR, and continue to iterate in follow-ups.

Planned follow-ups:

  • remove old WIP calendar prototype, and refactor folder naming and structure;
  • convert to CSS modules;
  • add unit tests
  • add README and any additional best practices docs
  • add a more detailed Storybook example resembling the date filtering dialog (ie. with date inputs and presets inside a dialog)
  • potentially refine the code for range selection preview, enabling it when min, max and excludeDates props are used.

@ciampo ciampo merged commit d74e7d7 into trunk May 22, 2025
11 checks passed
@ciampo ciampo deleted the try/calendar-react-day-picker branch May 22, 2025 10:47
@github-actions github-actions bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label May 22, 2025
@a8ci18n
Copy link

a8ci18n commented May 22, 2025

This Pull Request is now available for translation here: https://translate.wordpress.com/deliverables/17503189

Some locales (Hebrew) have been temporarily machine-translated due to translator availability. All other translations are usually ready within a few days. Untranslated and machine-translated strings will be sent for translation next Monday and are expected to be completed by the following Friday.

Hi @ciampo, could you please edit the description of this PR and add a screenshot for our translators? Ideally it'd include all of the following strings:

  • Date calendar
  • Date range calendar
  • Go to the Next Month
  • Go to the Previous Month
  • %s, selected

Thank you in advance!

@ciampo ciampo mentioned this pull request May 22, 2025
8 tasks
@tyxla
Copy link
Member

tyxla commented May 22, 2025

Great work with this one @ciampo, happy to see it land 🚀

@donaghokeeffe
Copy link

Coming late to this, apologies! Last year sometime, Jetpack design and VIP design did some work on establishing a date-time picker component for our charts/analytics products. Posting an image of this below.

SCR-20250523-izmz
Figma File Link

Timelines were short and MVP scope limitations meant that each team ended up building their own thing. Nevertheless, if we're thinking about doing another round with this component, there's a couple of things to note from that collab that might be relevant here.

  1. We were both looking at an upper left side location for the input, thus locating presets on the right side of the flyout made more sense.
  2. After a design review with the design council, we agreed to go for a progressive disclosure approach, i.e. Presets only flyout at first, with the calendar view appearing only once the "custom range" preset was selected. Obviously if a custom range was currently showing, the calendar view would be open already when the flyout appears.
  3. For the component to be useful for VIP, it needed to include time. The timezone was to default to UTC and state this somewhere in the component, because this is the VIP standard for looking at server data (which our charts are normally doing - Jetpack didn't require time). Alternatively, the timezone could be selected by the user, but I'm not sure this adds value (or just complexity).
  4. The time (hour and minute) was also needed to be shown in the date-picker input once a custom range was selected. This complicated things somewhat, for VIP at least. The view below is what we ended up implementing in VIP.
    SCR-20250523-jhxl
  5. VIP has fairly strict A11y requirements and while researching this component collaboration, I don't think we found even one example that claimed to be fully accessible. The reason was mostly due to the nesting of numerous fields/buttons and inputs, as well as the complex interaction that's needed to define a custom range. Adding time into the equation makes this even more difficult.
    Below you can see what was eventually implemented. I wasn't directly involved in the project, so I'm not 100% on where A11y compliance ended up.
    SCR-20250523-jhtm

@ciampo
Copy link
Contributor Author

ciampo commented May 23, 2025

Hey @donaghokeeffe , thank you for sharing more details!

The components from this PR focus on proving a calendar UI for date picking in a Calendar interface — ie. the part highlighted in green:

Screenshot 2025-05-23 at 12 26 39

While components take timezone into account to "localize" date picking (for example, right now it's Friday in Italy, but soon in New Zealand will be Saturday), they don't provide UI for time picking.

At the same time, the components accept full date objects, meaning that you should be able to build extra UI for time picking and integrate it with the Calendar UI, as in the screenshot above.

I plan on working on a comprehensive Storybook example for a very similar use case.

timeZone: BaseProps[ 'timeZone' ];
mode: 'single' | 'range';
} ) => {
const { __ } = useI18n();
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to use the hook here instead of directly using __() from the i18n package?

Copy link
Member

Choose a reason for hiding this comment

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

TL;DR: @wordpress/i18n is not reactive. So, if you have lazy-loaded components with translations, their translation chunks will be lazy-loaded too. when that happens, @wordpress/i18n won't trigger rerenders.
@wordpress/react-i18n is the reactive version that solves that reactivity issue.

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.