- Calendar set to { args.timeZone ?? 'current' } timezone, - disabling selection for all dates before today, and starting - with a default date of 1 week from today. -
- > - ); - }, - args: { - timeZone: 'Pacific/Auckland', - }, - argTypes: { - selected: { - control: false, - }, - defaultSelected: { - control: false, - }, - disabled: { - control: false, - }, - }, -}; diff --git a/packages/components/src/calendar/stories/date-range-calendar.story.tsx b/packages/components/src/calendar/stories/date-range-calendar.story.tsx deleted file mode 100644 index b51fcb131400c2..00000000000000 --- a/packages/components/src/calendar/stories/date-range-calendar.story.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { fn } from 'storybook/test'; -import { - enUS, - fr, - es, - de, - it, - he, - ru, - ja, - ptBR, - nl, - ko, - tr, - id, - zhCN, - zhTW, - ar, - sv, -} from 'date-fns/locale'; -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { useState, useEffect } from '@wordpress/element'; -import { DateRangeCalendar, TZDate } from '..'; - -// Storybook date controls pass a number, but react-day-picker expects a Date for `endMonth`. -function toDate( value: Date | number | undefined ): Date | undefined { - return value === undefined ? undefined : new Date( value ); -} - -const meta: Meta< typeof DateRangeCalendar > = { - title: 'Components/Selection & Input/Time & Date/DateRangeCalendar', - component: DateRangeCalendar, - tags: [ 'status-private' ], - render: ( { endMonth, ...args } ) => ( -- Calendar set to { args.timeZone ?? 'current' } timezone, - disabling selection for all dates before today, and starting - with a default date range of 1 week from today to 2 weeks - from today. -
- > - ); - }, - args: { - timeZone: 'Pacific/Auckland', - }, - argTypes: { - disabled: { - control: false, - }, - }, -}; diff --git a/packages/components/src/calendar/style.scss b/packages/components/src/calendar/style.scss deleted file mode 100644 index 3d38c8eb3c8763..00000000000000 --- a/packages/components/src/calendar/style.scss +++ /dev/null @@ -1,440 +0,0 @@ -@use "@wordpress/base-styles/colors" as *; -@use "@wordpress/base-styles/variables" as *; -@use "../utils/theme-variables" as *; - -/* Root of the component. */ -// Internal variables -$wp-components-calendar-outline-focus: var(--wp-admin-border-width-focus) - solid $components-color-accent; -$wp-components-calendar-button-height: $grid-unit-40; -$wp-components-calendar-button-width: $grid-unit-40; -$wp-components-calendar-nav-height: $grid-unit-40; -$wp-components-calendar-range-middle-background-color: color-mix(in srgb, $components-color-accent 4%, transparent); -$wp-components-calendar-preview-border-color: color-mix(in srgb, $components-color-accent 16%, transparent); - -.components-calendar { - // TODO: add font family rule when Theme is ready - - position: relative; /* Required to position the navigation toolbar. */ - box-sizing: border-box; - display: inline flow-root; - color: $components-color-foreground; - background-color: $components-color-background; - font-size: $font-size-medium; - font-weight: var(--wpds-typography-font-weight-default); - z-index: 0; // Create a stacking context and render on top of the background. - - *, - *::before, - *::after { - box-sizing: border-box; - } -} - -.components-calendar__day { - padding: 0; - position: relative; - - // Setting text color on the day container instead of directly on the - // day button to ensure that the color of the dot used to indicate today's - // date follows the same color as the button's text, since the button - // inherits its text color. - &:has(.components-calendar__day-button:disabled) { - // Use a lighter shade of gray for less visual prominence. - color: $components-color-gray-400; - } - &:has(.components-calendar__day-button:hover:not(:disabled)), - &:has(.components-calendar__day-button:focus-visible) { - color: $components-color-accent; - } -} - -.components-calendar__day-button { - background: none; - padding: 0; - margin: 0; - cursor: var(--wpds-cursor-control); - justify-content: center; - align-items: center; - display: flex; - position: relative; - - width: $wp-components-calendar-button-width; - height: $wp-components-calendar-button-height; - - border: none; - border-radius: $radius-small; - - font: inherit; - font-variant-numeric: tabular-nums; - color: inherit; - - // Use the button's ::before to render date's background, which keeps the - // border-radius of the button intact. This technique allows the focus ring - // to have rounded corners even when the background needs square corners - // (eg. in the middle of a date range). - &::before { - content: ""; - position: absolute; - z-index: -1; - inset: 0; - border: none; // No default border to avoid polluting high-contrast mode. - border-radius: $radius-small; - } - - // Use the button's ::after to show the selection preview. - &::after { - content: ""; - position: absolute; - z-index: 1; - inset: 0; - pointer-events: none; - } - - &:disabled { - cursor: revert; - - @media ( forced-colors: active ) { - // As an extra visual cue, show a line-through on disabled days - // in forced-colors (high-contrast) mode. - text-decoration: line-through; - } - } - - &:focus-visible { - outline: $wp-components-calendar-outline-focus; - outline-offset: 1px; - } -} - -.components-calendar__caption-label { - z-index: 1; - - position: relative; - display: inline-flex; - align-items: center; - - white-space: nowrap; - border: 0; - - text-transform: capitalize; -} - -.components-calendar__button-next, -.components-calendar__button-previous { - border: none; - border-radius: $radius-small; - background: none; - padding: 0; - margin: 0; - cursor: var(--wpds-cursor-control); - appearance: none; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - - width: $wp-components-calendar-button-width; - height: $wp-components-calendar-button-height; - - color: inherit; - - &:disabled, - &[aria-disabled="true"] { - cursor: revert; - - color: $components-color-disabled; - } - - &:focus-visible { - outline: $wp-components-calendar-outline-focus; - } -} - -.components-calendar__chevron { - display: inline-block; - fill: currentColor; - width: $grid-unit-20; - height: $grid-unit-20; -} - -.components-calendar[dir="rtl"] -.components-calendar__nav -.components-calendar__chevron { - transform: rotate(180deg); - transform-origin: 50%; -} - -.components-calendar__month-caption { - display: flex; - justify-content: center; - align-content: center; - - height: $wp-components-calendar-nav-height; - margin-bottom: $grid-unit-15; -} - -.components-calendar__months { - position: relative; - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: $grid-unit-20; - max-width: fit-content; -} - -.components-calendar__month-grid { - border-collapse: separate; - border-spacing: 0 $grid-unit-05; -} - -.components-calendar__nav { - position: absolute; - inset-block-start: 0; - inset-inline-start: 0; - inset-inline-end: 0; - - display: flex; - align-items: center; - justify-content: space-between; - - height: $wp-components-calendar-nav-height; -} - -.components-calendar__weekday { - width: $wp-components-calendar-button-width; - height: $wp-components-calendar-button-height; - padding: 0; - - color: $components-color-gray-700; - text-align: center; - text-transform: uppercase; -} - -/* DAY MODIFIERS */ -// Today's date: show a dot in the top-right corner of the button -.components-calendar__day--today::after { - content: ""; - position: absolute; - z-index: 1; - inset-block-start: 2px; - inset-inline-end: 2px; - width: 0; - height: 0; - border-radius: 50%; - border: 2px solid currentColor; -} - -// Selected date button (individual date, range start, and range end) -.components-calendar__day--selected:not(.components-calendar__range-middle) { - &:has( - .components-calendar__day-button, - .components-calendar__day-button:hover:not(:disabled) -) { - color: $components-color-foreground-inverted; - } - - .components-calendar__day-button { - &::before { - background-color: $components-color-foreground; - // Render a transparent border to highlight the selected day in - // forced-colors (high-contrast) mode, since the background is not - // visible. - border: 1px solid transparent; - } - - &:disabled::before { - background-color: $components-color-gray-400; - } - - &:hover:not(:disabled)::before { - background-color: $components-color-gray-800; - } - } -} - -// Outside month days (visible when showOutsideDays is true): use a lighter text color. -.components-calendar__day--outside { - color: $components-color-gray-600; -} - -// Hidden button (ie. outside current month but still rendered) -.components-calendar__day--hidden { - visibility: hidden; -} - -// Range start button, but not when start and end are the same day. -.components-calendar__range-start:not(.components-calendar__range-end) -.components-calendar__day-button { - // Apply border-radius changes to the button itself too, so that the focus - // ring follows the same shape as the button's background. - &, - &::before { - border-start-end-radius: 0; - border-end-end-radius: 0; - } -} - -// Middle of date range -.components-calendar__range-middle .components-calendar__day-button { - &::before { - background-color: $wp-components-calendar-range-middle-background-color; - border-radius: 0; - - // Render a top and bottom transparent border to highlight the selected - // day in forced-colors (high-contrast) mode, since the background is not - // visible. - border-width: 1px 0; - border-color: transparent; - border-style: solid; - } -} - -// Range end button, but not when start and end are the same day. -.components-calendar__range-end:not(.components-calendar__range-start) -.components-calendar__day-button { - // Apply border-radius changes to the button itself too, so that the focus - // ring follows the same shape as the button's background. - &, - &::before { - border-start-start-radius: 0; - border-end-start-radius: 0; - } -} - -/* - * RANGE PREVIEW (range calendar only) - * - * The preview is rendered in the button's ::after pseudo-element, so that it - * can be rendered over the button's contents. - * The selection preview is shown with a dashed border. In order to have - * control over the dash pattern (especially the seams between days), the - * dashed borders are rendered as SVGs via the url() CSS function. - * Since SVGs rendered in the url() function don't seem to be able to access - * CSS variables, we're using the SVGs as masks, and using `background-color` - * to consume the accent color variable. - */ -.components-calendar__day--preview svg { - position: absolute; - inset: 0; - pointer-events: none; - color: $wp-components-calendar-preview-border-color; - - @media ( forced-colors: active ) { - color: inherit; - } - - .components-calendar[dir="rtl"] & { - transform: scaleX(-1); - } -} - -.components-calendar__day--preview.components-calendar__range-middle -.components-calendar__day-button::before { - // Remove the transparent border shown on the middle of the range - // in forced-colors (high-contrast) mode, to allow for the dashed border - // to be visible. - border: none; -} - -/* ANIMATIONS */ -@keyframes slide-in-left { - 0% { - transform: translateX(-100%); - } - 100% { - transform: translateX(0); - } -} - -@keyframes slide-in-right { - 0% { - transform: translateX(100%); - } - 100% { - transform: translateX(0); - } -} - -@keyframes slide-out-left { - 0% { - transform: translateX(0); - } - 100% { - transform: translateX(-100%); - } -} - -@keyframes slide-out-right { - 0% { - transform: translateX(0); - } - 100% { - transform: translateX(100%); - } -} - -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes fade-out { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -.components-calendar__weeks-before-enter, -.components-calendar__weeks-before-exit, -.components-calendar__weeks-after-enter, -.components-calendar__weeks-after-exit, -.components-calendar__caption-after-enter, -.components-calendar__caption-after-exit, -.components-calendar__caption-before-enter, -.components-calendar__caption-before-exit { - animation-duration: 0s; - animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - animation-fill-mode: forwards; - - @media not ( prefers-reduced-motion ) { - animation-duration: 0.3s; - } -} - -.components-calendar__weeks-before-enter, -.components-calendar[dir="rtl"] .components-calendar__weeks-after-enter { - animation-name: slide-in-left; -} -.components-calendar__weeks-before-exit, -.components-calendar[dir="rtl"] .components-calendar__weeks-after-exit { - animation-name: slide-out-left; -} -.components-calendar__weeks-after-enter, -.components-calendar[dir="rtl"] .components-calendar__weeks-before-enter { - animation-name: slide-in-right; -} -.components-calendar__weeks-after-exit, -.components-calendar[dir="rtl"] .components-calendar__weeks-before-exit { - animation-name: slide-out-right; -} - -.components-calendar__caption-after-enter { - animation-name: fade-in; -} -.components-calendar__caption-after-exit { - animation-name: fade-out; -} -.components-calendar__caption-before-enter { - animation-name: fade-in; -} -.components-calendar__caption-before-exit { - animation-name: fade-out; -} diff --git a/packages/components/src/calendar/utils/constants.ts b/packages/components/src/calendar/utils/constants.ts deleted file mode 100644 index 60d391c13afda4..00000000000000 --- a/packages/components/src/calendar/utils/constants.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Day } from './day-cell'; - -const CLASSNAMES = { - root: 'components-calendar', - day: 'components-calendar__day', - day_button: 'components-calendar__day-button', - outside: 'components-calendar__day--outside', - caption_label: 'components-calendar__caption-label', - button_next: 'components-calendar__button-next', - button_previous: 'components-calendar__button-previous', - chevron: 'components-calendar__chevron', - nav: 'components-calendar__nav', - month_caption: 'components-calendar__month-caption', - months: 'components-calendar__months', - month_grid: 'components-calendar__month-grid', - weekday: 'components-calendar__weekday', - today: 'components-calendar__day--today', - selected: 'components-calendar__day--selected', - disabled: 'components-calendar__day--disabled', - hidden: 'components-calendar__day--hidden', - range_start: 'components-calendar__range-start', - range_end: 'components-calendar__range-end', - range_middle: 'components-calendar__range-middle', - weeks_before_enter: 'components-calendar__weeks-before-enter', - weeks_before_exit: 'components-calendar__weeks-before-exit', - weeks_after_enter: 'components-calendar__weeks-after-enter', - weeks_after_exit: 'components-calendar__weeks-after-exit', - caption_after_enter: 'components-calendar__caption-after-enter', - caption_after_exit: 'components-calendar__caption-after-exit', - caption_before_enter: 'components-calendar__caption-before-enter', - caption_before_exit: 'components-calendar__caption-before-exit', -}; -export const MODIFIER_CLASSNAMES = { - preview: 'components-calendar__day--preview', - preview_start: 'components-calendar__day--preview-start', - preview_end: 'components-calendar__day--preview-end', -}; - -export const COMMON_PROPS = { - animate: true, - // Only show days in the current month - showOutsideDays: false, - // Hide week number column - showWeekNumber: false, - // Show weekdays row - hideWeekdays: false, - // Month and year caption are not interactive - captionLayout: 'label', - // Show a variable number of weeks depending on the month - fixedWeeks: false, - // Show navigation buttons - hideNavigation: false, - // Class names - classNames: CLASSNAMES, - // Default role - role: 'application', - components: { - Day, - }, -} as const; diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index a0777cfeb5335c..bd791f7f088791 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -5,7 +5,6 @@ import { ComponentsContext } from './context/context-system-provider'; import { Tabs } from './tabs'; import { lock } from './lock-unlock'; import Badge from './badge'; -import { DateCalendar, DateRangeCalendar, TZDate } from './calendar'; import { ValidatedCheckboxControl, ValidatedComboboxControl, @@ -30,9 +29,6 @@ lock( privateApis, { Tabs, Menu, Badge, - DateCalendar, - DateRangeCalendar, - TZDate, useDrag, ValidatedInputControl, ValidatedCheckboxControl, diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 6d85fd28f7ac60..936d6f05a1946f 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -4,7 +4,6 @@ @use "./badge/styles.scss" as *; @use "./button-group/style.scss" as *; @use "./button/style.scss" as *; -@use "./calendar/style.scss" as *; @use "./checkbox-control/style.scss" as *; @use "./circular-option-picker/style.scss" as *; @use "./palette-edit/style.scss" as *; diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 61cd2670576f5c..299d6939ac526d 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -27,6 +27,7 @@ - Remove obsolete dependency grouping comments as part of the repository-wide separator-free import migration. ([#81248](https://github.com/WordPress/gutenberg/pull/81248)) - DataViews: Inline a verbatim copy of the `kebabCase` utility instead of unlocking the private one from `@wordpress/components`, as part of removing the package's reliance on private cross-package APIs ([#81230](https://github.com/WordPress/gutenberg/issues/81230)). Adds a direct `change-case` dependency; no behavior change. ([#81284](https://github.com/WordPress/gutenberg/pull/81284)) - DataViews: Use the public `Badge` from `@wordpress/ui` in the `grid` and `picker-grid` layouts instead of the private one from `@wordpress/components`, removing the last `unlock()` call from those files. Badges pick up the `@wordpress/ui` neutral tokens, so they gain a border and a slightly different background ([#81236](https://github.com/WordPress/gutenberg/pull/81236)). +- DataForms: The `date` and `datetime` controls now import the public `Calendar` and `RangeCalendar` from `@wordpress/ui` instead of unlocking `DateCalendar` / `DateRangeCalendar` from `@wordpress/components`, as part of removing the package's reliance on private cross-package APIs ([#81230](https://github.com/WordPress/gutenberg/issues/81230)). The `date` control no longer unlocks anything; `datetime` keeps a single unlock for `ValidatedInputControl`. ([#81337](https://github.com/WordPress/gutenberg/pull/81337)) - Update `@ariakit/react` to 0.4.35 ([#80765](https://github.com/WordPress/gutenberg/pull/80765)). ## 17.3.0 (2026-07-29) diff --git a/packages/dataviews/src/components/dataform-controls/date.tsx b/packages/dataviews/src/components/dataform-controls/date.tsx index 02cbb7cd8110ba..e90d334d174fc8 100644 --- a/packages/dataviews/src/components/dataform-controls/date.tsx +++ b/packages/dataviews/src/components/dataform-controls/date.tsx @@ -12,7 +12,6 @@ import { BaseControl, Button, Icon as WCIcon, - privateApis as componentsPrivateApis, __experimentalInputControl as InputControl, } from '@wordpress/components'; import { speak } from '@wordpress/a11y'; @@ -26,7 +25,7 @@ import { import { __ } from '@wordpress/i18n'; import { getDate, getSettings } from '@wordpress/date'; import { error as errorIcon } from '@wordpress/icons'; -import { Stack } from '@wordpress/ui'; +import { Calendar, RangeCalendar, Stack } from '@wordpress/ui'; import RelativeDateControl from './utils/relative-date-control'; import useDisabledDateMatchers from './utils/use-disabled-date-matchers'; import { @@ -34,7 +33,6 @@ import { OPERATOR_OVER, OPERATOR_BETWEEN, } from '../../constants'; -import { unlock } from '../../lock-unlock'; import type { DataFormControlProps, FieldValidity, @@ -43,8 +41,6 @@ import type { } from '../../types'; import getCustomValidity from './utils/get-custom-validity'; -const { DateCalendar, DateRangeCalendar } = unlock( componentsPrivateApis ); - type DateRange = [ string, string ] | undefined; const DATE_PRESETS: { @@ -324,7 +320,7 @@ function CalendarDateControl< Item >( { ); const onSelectDate = useCallback( - ( newDate: Date | undefined | null ) => { + ( newDate: Date | null ) => { const dateValue = newDate ? format( newDate, 'yyyy-MM-dd' ) : undefined; @@ -443,12 +439,10 @@ function CalendarDateControl< Item >( { /> { /* Calendar widget */ } -+ Calendar set to { args.timeZone ?? 'current' } timezone, + disabling selection for all dates before today, and starting + with a default date of 1 week from today. +
+ > + ); + }, + args: { + timeZone: 'Pacific/Auckland', + }, + argTypes: { + value: { + control: false, + }, + defaultValue: { + control: false, + }, + disabled: { + control: false, + }, + }, +}; diff --git a/packages/ui/src/calendar/stories/range-calendar.story.tsx b/packages/ui/src/calendar/stories/range-calendar.story.tsx new file mode 100644 index 00000000000000..cf9ef1c638bce3 --- /dev/null +++ b/packages/ui/src/calendar/stories/range-calendar.story.tsx @@ -0,0 +1,150 @@ +import { fn } from 'storybook/test'; +import { TZDate } from 'react-day-picker'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { useState, useEffect } from '@wordpress/element'; +import { RangeCalendar } from '../index'; +import { + toDate, + SHARED_ARG_TYPES, + DISABLED_DATES_SAMPLE, + firstDayOfNextMonth, + fourthDayOfNextMonth, +} from './shared'; + +const meta: Meta< typeof RangeCalendar > = { + tags: [ 'manifest' ], + title: 'Design System/Components/Calendar/RangeCalendar', + component: RangeCalendar, + parameters: { + componentStatus: { + status: 'recommended', + whereUsed: 'global', + }, + }, + render: ( { endMonth, ...args } ) => ( ++ Calendar set to { args.timeZone ?? 'current' } timezone, + disabling selection for all dates before today, and starting + with a default date range of 1 week from today to 2 weeks + from today. +
+ > + ); + }, + args: { + timeZone: 'Pacific/Auckland', + }, + argTypes: { + disabled: { + control: false, + }, + }, +}; diff --git a/packages/ui/src/calendar/stories/shared.ts b/packages/ui/src/calendar/stories/shared.ts new file mode 100644 index 00000000000000..297fa0c0b5d3ce --- /dev/null +++ b/packages/ui/src/calendar/stories/shared.ts @@ -0,0 +1,139 @@ +import { + enUS, + fr, + es, + de, + it, + he, + ru, + ja, + ptBR, + nl, + ko, + tr, + id, + zhCN, + zhTW, + ar, + sv, +} from 'date-fns/locale'; + +// Storybook date controls pass a number, but react-day-picker expects a Date +// for props such as `endMonth`. +export function toDate( value: Date | number | undefined ): Date | undefined { + return value === undefined ? undefined : new Date( value ); +} + +/** + * The `argTypes` shared by `Calendar` and `RangeCalendar` — the entire + * `BaseProps` surface is common to both. + */ +export const SHARED_ARG_TYPES = { + locale: { + options: [ + 'English (US)', + 'French', + 'Spanish', + 'German', + 'Italian', + 'Hebrew', + 'Russian', + 'Japanese', + 'Portuguese (Brazil)', + 'Dutch', + 'Korean', + 'Turkish', + 'Indonesian', + 'Chinese (Simplified)', + 'Chinese (Traditional)', + 'Arabic', + 'Swedish', + ], + mapping: { + 'English (US)': enUS, + French: fr, + Spanish: es, + German: de, + Italian: it, + Hebrew: he, + Russian: ru, + Japanese: ja, + 'Portuguese (Brazil)': ptBR, + Dutch: nl, + Korean: ko, + Turkish: tr, + Indonesian: id, + 'Chinese (Simplified)': zhCN, + 'Chinese (Traditional)': zhTW, + Arabic: ar, + Swedish: sv, + }, + control: 'select', + }, + timeZone: { + options: [ + 'Pacific/Honolulu', + 'America/New_York', + 'Europe/London', + 'Asia/Tokyo', + 'Pacific/Auckland', + ], + control: 'select', + }, + labels: { + control: false, + }, + value: { control: 'date' }, + defaultValue: { control: 'date' }, + onValueChange: { + control: false, + }, + defaultMonth: { control: 'date' }, + month: { control: 'date' }, + onMonthChange: { + control: false, + }, + endMonth: { control: 'date' }, + startMonth: { control: 'date' }, + render: { control: false }, +} as const; + +/** + * A representative mix of every `Matcher` shape supported by the `disabled` + * prop. + */ +export const DISABLED_DATES_SAMPLE = [ + // Disable tomorrow (single date) + new Date( new Date().setDate( new Date().getDate() + 1 ) ), + // Disable all dates after Feb 1st of next year + { after: new Date( new Date().getFullYear() + 1, 1, 1 ) }, + // Disable all dates before Dec 1st of last year + { before: new Date( new Date().getFullYear() - 1, 11, 1 ) }, + // Disable all dates between 12th and 14th of August of this year + { + after: new Date( new Date().getFullYear(), 7, 11 ), + before: new Date( new Date().getFullYear(), 7, 15 ), + }, + // Disable all dates between 21st and 26th of October of this year + { + from: new Date( new Date().getFullYear(), 9, 21 ), + to: new Date( new Date().getFullYear(), 9, 26 ), + }, + // Disable all Wednesdays + { dayOfWeek: 3 }, + // Disable all prime day numbers + function isPrimeDate( date: Date ) { + return [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 ].includes( + date.getDate() + ); + }, +]; + +const nextMonth = new Date().getMonth() === 11 ? 0 : new Date().getMonth() + 1; +const nextMonthYear = + new Date().getMonth() === 11 + ? new Date().getFullYear() + 1 + : new Date().getFullYear(); + +export const firstDayOfNextMonth = new Date( nextMonthYear, nextMonth, 1 ); +export const fourthDayOfNextMonth = new Date( nextMonthYear, nextMonth, 4 ); diff --git a/packages/ui/src/calendar/stories/usage-guidelines.mdx b/packages/ui/src/calendar/stories/usage-guidelines.mdx new file mode 100644 index 00000000000000..3a2fc04a04ac53 --- /dev/null +++ b/packages/ui/src/calendar/stories/usage-guidelines.mdx @@ -0,0 +1,133 @@ +import { Meta } from '@storybook/addon-docs/blocks'; + + + +# Calendar + +`Calendar` selects a **single date**. `RangeCalendar` selects a **date range**. Both share the same prop surface apart from the shape of `value` / `defaultValue` / `onValueChange`, and the range-only `min`, `max` and `excludeDisabled` props. + +Both are built for accessibility and follow ARIA best practices for calendar widgets: keyboard navigation, screen reader support, and customizable labels for internationalization. + +## Usage + +### Uncontrolled Mode + +The component manages its own selection. Use `defaultValue` to set the initial value, and `onValueChange` to react to changes. + +```jsx +import { Calendar } from '@wordpress/ui'; + +const MyUncontrolledCalendar = () => ( +