feat(calendar): add firstDayOfWeek (#4852)

* feat(calendar): add firstDayOfWeek

* feat(docs): add firstDayOfWeek in Calendar docs

* feat(calendar): add firstDayOfWeek to range calendar

* feat(docs): add firstDayOfWeek to API table

* feat: add firstDayOfWeek to date picker & date range picker

* feat(docs): add firstDayOfWeek

* feat(changeset): add changeset

* feat: add firstDayOfWeek option in storybook

* feat(docs): export firstDayOfWeek

* chore(docs): update title
This commit is contained in:
աӄա 2025-02-19 03:29:39 +08:00 committed by GitHub
parent 80f6c77bae
commit 446dd0bfde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 207 additions and 4 deletions

View File

@ -0,0 +1,6 @@
---
"@heroui/date-picker": patch
"@heroui/calendar": patch
---
add `firstDayOfWeek`

View File

@ -0,0 +1,5 @@
import {Calendar} from "@heroui/react";
export default function App() {
return <Calendar aria-label="Date (firstDayOfWeek)" firstDayOfWeek="mon" />;
}

View File

@ -0,0 +1,9 @@
import App from "./first-day-of-week.raw.jsx?raw";
const react = {
"/App.jsx": App,
};
export default {
...react,
};

View File

@ -10,6 +10,7 @@ import invalidDate from "./invalid-date";
import withMonthAndYearPicker from "./with-month-and-year-picker";
import internationalCalendars from "./international-calendars";
import visibleMonths from "./visible-months";
import firstDayOfWeek from "./first-day-of-week";
import pageBehaviour from "./page-behaviour";
import presets from "./presets";
@ -26,6 +27,7 @@ export const calendarContent = {
withMonthAndYearPicker,
internationalCalendars,
visibleMonths,
firstDayOfWeek,
pageBehaviour,
presets,
};

View File

@ -0,0 +1,14 @@
import {DatePicker} from "@heroui/react";
export default function App() {
return (
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
<DatePicker
className="max-w-[284px]"
description={"This is my birth date."}
firstDayOfWeek="mon"
label="Birth date"
/>
</div>
);
}

View File

@ -0,0 +1,9 @@
import App from "./first-day-of-week.raw.jsx?raw";
const react = {
"/App.jsx": App,
};
export default {
...react,
};

View File

@ -18,6 +18,7 @@ import minAndMaxDate from "./min-and-max-date";
import internationalCalendar from "./international-calendar";
import unavailableDates from "./unavailable-dates";
import visibleMonth from "./visible-month";
import firstDayOfWeek from "./first-day-of-week";
import pageBehavior from "./page-behavior";
import preset from "./preset";
@ -42,6 +43,7 @@ export const datePickerContent = {
internationalCalendar,
unavailableDates,
visibleMonth,
firstDayOfWeek,
pageBehavior,
preset,
};

View File

@ -0,0 +1,12 @@
import {DateRangePicker} from "@heroui/react";
export default function App() {
return (
<DateRangePicker
className="max-w-xs"
description="Please enter your stay duration"
firstDayOfWeek="mon"
label="Stay duration"
/>
);
}

View File

@ -0,0 +1,9 @@
import App from "./first-day-of-week.raw.jsx?raw";
const react = {
"/App.jsx": App,
};
export default {
...react,
};

View File

@ -17,6 +17,7 @@ import minAndMaxDate from "./min-and-max-date";
import internationalCalendar from "./international-calendar";
import unavailableDates from "./unavailable-dates";
import visibleMonth from "./visible-month";
import firstDayOfWeek from "./first-day-of-week";
import pageBehavior from "./page-behavior";
import nonContigous from "./non-contiguous";
import presets from "./presets";
@ -43,6 +44,7 @@ export const dateRangePickerContent = {
internationalCalendar,
unavailableDates,
visibleMonth,
firstDayOfWeek,
pageBehavior,
nonContigous,
presets,

View File

@ -0,0 +1,5 @@
import {RangeCalendar} from "@heroui/react";
export default function App() {
return <RangeCalendar aria-label="Date (firstDayOfWeek)" firstDayOfWeek="mon" />;
}

View File

@ -0,0 +1,9 @@
import App from "./first-day-of-week.raw.jsx?raw";
const react = {
"/App.jsx": App,
};
export default {
...react,
};

View File

@ -10,6 +10,7 @@ import invalidDate from "./invalid-date";
import nonContiguousRanges from "./non-contiguous-ranges";
import internationalCalendars from "./international-calendars";
import visibleMonths from "./visible-months";
import firstDayOfWeek from "./first-day-of-week";
import pageBehaviour from "./page-behaviour";
import presets from "./presets";
import withMonthAndYearPicker from "./with-month-and-year-picker";
@ -27,6 +28,7 @@ export const rangeCalendarContent = {
nonContiguousRanges,
internationalCalendars,
visibleMonths,
firstDayOfWeek,
pageBehaviour,
presets,
withMonthAndYearPicker,

View File

@ -117,6 +117,12 @@ By default, the Calendar displays a single month. The `visibleMonths` prop allow
<CodeDemo title="Visible Months" files={calendarContent.visibleMonths} />
### Custom first day of week
By default, the first day of the week is automatically set based on the current locale. This can be changed by setting the `firstDayOfWeek` prop to `'sun'`, `'mon'`, `'tue'`, `'wed'`, `'thu'`, `'fri'`, or `'sat'`.
<CodeDemo title="Custom first day of week" files={calendarContent.firstDayOfWeek} />
### Page Behaviour
By default, when pressing the next or previous buttons, pagination will advance by the `visibleMonths` value. This behavior can be changed to page by single months instead, by setting `pageBehavior` to `single`.
@ -237,6 +243,12 @@ Here's the example to customize `topContent` and `bottomContent` to have some pr
description: "The number of months to display at once. Up to 3 months are supported. Passing a number greater than 1 will disable the showMonthAndYearPickers prop.",
default: "1"
},
{
attribute: "firstDayOfWeek",
type: "sun | mon | tue | wed | thu | fri | sat",
description: "The day that starts the week.",
default: "-"
},
{
attribute: "focusedValue",
type: "DateValue",

View File

@ -233,6 +233,12 @@ By default, the calendar popover displays a single month. The `visibleMonths` pr
<CodeDemo title="Visible Months" files={datePickerContent.visibleMonth} />
### Custom first day of week
By default, the first day of the week is automatically set based on the current locale. This can be changed by setting the `firstDayOfWeek` prop to `'sun'`, `'mon'`, `'tue'`, `'wed'`, `'thu'`, `'fri'`, or `'sat'`.
<CodeDemo title="Custom first day of week" files={datePickerContent.firstDayOfWeek} />
### Page Behavior
By default, when pressing the next or previous buttons, pagination will advance by the `visibleMonths` value. This behavior can be changed to page by single months instead, by setting `pageBehavior` to `single`.
@ -435,6 +441,12 @@ import {I18nProvider} from "@react-aria/i18n";
description: "The number of months to display at once. Up to 3 months are supported.",
default: "1"
},
{
attribute: "firstDayOfWeek",
type: "sun | mon | tue | wed | thu | fri | sat",
description: "The day that starts the week.",
default: "-"
},
{
attribute: "selectorIcon",
type: "ReactNode",

View File

@ -66,6 +66,12 @@ By default, the calendar popover displays a single month. The `visibleMonths` pr
<CodeDemo title="Visible Months" files={dateRangePickerContent.visibleMonth} />
### Custom first day of week
By default, the first day of the week is automatically set based on the current locale. This can be changed by setting the `firstDayOfWeek` prop to `'sun'`, `'mon'`, `'tue'`, `'wed'`, `'thu'`, `'fri'`, or `'sat'`.
<CodeDemo title="Custom first day of week" files={dateRangePickerContent.firstDayOfWeek} />
### Page Behavior
By default, when pressing the next or previous buttons, pagination will advance by the `visibleMonths` value. This behavior can be changed to page by single months instead, by setting `pageBehavior` to `single`.
@ -530,6 +536,12 @@ You can customize the `DateRangePicker` component by passing custom Tailwind CSS
description: "The number of months to display at once. Up to 3 months are supported.",
default: "1"
},
{
attribute: "firstDayOfWeek",
type: "sun | mon | tue | wed | thu | fri | sat",
description: "The day that starts the week.",
default: "-"
},
{
attribute: "autoFocus",
type: "boolean",

View File

@ -125,6 +125,12 @@ By default, the Calendar displays a single month. The `visibleMonths` prop allow
<CodeDemo title="Visible Months" files={rangeCalendarContent.visibleMonths} />
### Custom first day of week
By default, the first day of the week is automatically set based on the current locale. This can be changed by setting the `firstDayOfWeek` prop to `'sun'`, `'mon'`, `'tue'`, `'wed'`, `'thu'`, `'fri'`, or `'sat'`.
<CodeDemo title="Custom first day of week" files={rangeCalendarContent.firstDayOfWeek} />
### Page Behaviour
By default, when pressing the next or previous buttons, pagination will advance by the `visibleMonths` value. This behavior can be changed to page by single months instead, by setting `pageBehavior` to `single`.
@ -245,6 +251,12 @@ Here's the example to customize `topContent` and `bottomContent` to have some pr
description: "The number of months to display at once. Up to 3 months are supported. Passing a number greater than 1 will disable the showMonthAndYearPickers prop.",
default: "1"
},
{
attribute: "firstDayOfWeek",
type: "sun | mon | tue | wed | thu | fri | sat",
description: "The day that starts the week.",
default: "-"
},
{
attribute: "focusedValue",
type: "DateValue",

View File

@ -33,6 +33,7 @@ export interface CalendarBaseProps extends HTMLHeroUIProps<"div"> {
errorMessageProps: HTMLAttributes<HTMLElement>;
calendarRef: RefObject<HTMLDivElement>;
errorMessage?: ReactNode;
firstDayOfWeek?: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
}
/**
@ -63,6 +64,7 @@ export function CalendarBase(props: CalendarBaseProps) {
errorMessageProps,
calendarRef: ref,
errorMessage,
firstDayOfWeek,
...otherProps
} = props;
@ -120,6 +122,7 @@ export function CalendarBase(props: CalendarBaseProps) {
key={`calendar-month-${i}`}
currentMonth={currentMonth.month}
direction={direction}
firstDayOfWeek={firstDayOfWeek}
startDate={d}
/>
);

View File

@ -17,10 +17,12 @@ export interface CalendarCellProps extends HTMLHeroUIProps<"td">, AriaCalendarCe
slots?: CalendarReturnType;
classNames?: SlotsToClasses<CalendarSlots>;
currentMonth: CalendarDate;
firstDayOfWeek?: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
}
export function CalendarCell(originalProps: CalendarCellProps) {
const {state, slots, isPickerVisible, currentMonth, classNames, ...props} = originalProps;
const {state, slots, isPickerVisible, currentMonth, classNames, firstDayOfWeek, ...props} =
originalProps;
const ref = useRef<HTMLButtonElement>(null);
@ -53,7 +55,8 @@ export function CalendarCell(originalProps: CalendarCellProps) {
const isSelectionEnd =
isSelected && highlightedRange ? isSameDay(props.date, highlightedRange.end) : false;
const {locale} = useLocale();
const dayOfWeek = getDayOfWeek(props.date, locale);
const dayOfWeek = getDayOfWeek(props.date, locale, firstDayOfWeek);
const isRangeStart =
isSelected && (isFirstSelectedAfterDisabled || dayOfWeek === 0 || props.date.day === 1);
const isRangeEnd =

View File

@ -17,10 +17,11 @@ export interface CalendarMonthProps extends HTMLHeroUIProps<"table">, CalendarPr
}
export function CalendarMonth(props: CalendarMonthProps) {
const {startDate, direction, currentMonth} = props;
const {startDate, direction, currentMonth, firstDayOfWeek} = props;
const {locale} = useLocale();
const weeksInMonth = getWeeksInMonth(startDate, locale);
const weeksInMonth = getWeeksInMonth(startDate, locale, firstDayOfWeek);
const {state, slots, weekdayStyle, isHeaderExpanded, disableAnimation, classNames} =
useCalendarContext();
@ -30,6 +31,7 @@ export function CalendarMonth(props: CalendarMonthProps) {
...props,
weekdayStyle,
endDate: endOfMonth(startDate),
firstDayOfWeek,
},
state,
);
@ -52,6 +54,7 @@ export function CalendarMonth(props: CalendarMonthProps) {
classNames={classNames}
currentMonth={startDate}
date={date}
firstDayOfWeek={firstDayOfWeek}
isPickerVisible={isHeaderExpanded}
slots={slots}
state={state}

View File

@ -65,6 +65,10 @@ interface Props extends HeroUIBaseProps {
* @default true
*/
showHelper?: boolean;
/**
* The day that starts the week.
*/
firstDayOfWeek?: "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
/**
* Whether the calendar header is expanded. This is only available if the `showMonthAndYearPickers` prop is set to `true`.
* @default false
@ -207,6 +211,7 @@ export function useCalendarBase(originalProps: UseCalendarBasePropsComplete) {
topContent,
bottomContent,
showHelper = true,
firstDayOfWeek,
calendarWidth = 256,
visibleMonths: visibleMonthsProp = 1,
weekdayStyle = "narrow",
@ -326,6 +331,7 @@ export function useCalendarBase(originalProps: UseCalendarBasePropsComplete) {
maxValue,
baseProps,
showHelper,
firstDayOfWeek,
weekdayStyle,
visibleMonths,
visibleDuration,

View File

@ -29,6 +29,7 @@ export function useCalendar<T extends DateValue>({
minValue,
maxValue,
showHelper,
firstDayOfWeek,
weekdayStyle,
visibleDuration,
baseProps,
@ -78,6 +79,7 @@ export function useCalendar<T extends DateValue>({
...baseProps,
Component,
showHelper,
firstDayOfWeek,
topContent,
bottomContent,
buttonPickerProps,

View File

@ -35,6 +35,7 @@ export function useRangeCalendar<T extends DateValue>({
domRef,
locale,
showHelper,
firstDayOfWeek,
minValue,
maxValue,
weekdayStyle,
@ -86,6 +87,7 @@ export function useRangeCalendar<T extends DateValue>({
...baseProps,
Component,
showHelper,
firstDayOfWeek,
topContent,
bottomContent,
buttonPickerProps,

View File

@ -44,6 +44,10 @@ export default {
type: "boolean",
},
},
firstDayOfWeek: {
control: "select",
options: [undefined, "sun", "mon", "tue", "wed", "thu", "fri", "sat"],
},
},
} as Meta<typeof Calendar>;
@ -412,3 +416,11 @@ export const ReducedMotion = {
...defaultProps,
},
};
export const FirstDayOfWeek = {
render: Template,
args: {
...defaultProps,
firstDayOfWeek: "mon",
},
};

View File

@ -42,6 +42,10 @@ export default {
},
options: ["narrow", "short", "long"],
},
firstDayOfWeek: {
control: "select",
options: [undefined, "sun", "mon", "tue", "wed", "thu", "fri", "sat"],
},
},
} as Meta<typeof RangeCalendar>;
@ -414,3 +418,11 @@ export const Presets = {
...defaultProps,
},
};
export const FirstDayOfWeek = {
render: Template,
args: {
...defaultProps,
firstDayOfWeek: "mon",
},
};

View File

@ -131,6 +131,7 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
validationState,
validationBehavior,
visibleMonths = 1,
firstDayOfWeek,
pageBehavior = "visible",
calendarWidth = 256,
isDateUnavailable,
@ -215,6 +216,7 @@ export function useDatePickerBase<T extends DateValue>(originalProps: UseDatePic
onHeaderExpandedChange: setIsCalendarHeaderExpanded,
color: isDefaultColor ? "primary" : originalProps.color,
disableAnimation,
firstDayOfWeek,
},
restUserCalendarProps,
),

View File

@ -70,6 +70,10 @@ export default {
},
options: ["aria", "native"],
},
firstDayOfWeek: {
control: "select",
options: [undefined, "sun", "mon", "tue", "wed", "thu", "fri", "sat"],
},
},
decorators: [
(Story) => (
@ -693,3 +697,11 @@ export const WithDateInputClassNames = {
description: "Please enter your birth date",
},
};
export const FirstDayOfWeek = {
render: Template,
args: {
...defaultProps,
firstDayOfWeek: "mon",
},
};

View File

@ -71,6 +71,10 @@ export default {
},
options: ["aria", "native"],
},
firstDayOfWeek: {
control: "select",
options: [undefined, "sun", "mon", "tue", "wed", "thu", "fri", "sat"],
},
},
decorators: [
(Story) => (
@ -799,3 +803,11 @@ export const CustomStyles = {
},
},
};
export const FirstDayOfWeek = {
render: Template,
args: {
...defaultProps,
firstDayOfWeek: "mon",
},
};