mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
* chore(deps): bump RA versions * chore(deps): bump RA versions * chore(deps): bump RA versions * chore: changeset * chore(deps): remove unnecessary dependencies * fix(calendar): typing issue * refactor(system): remove unused SupportedCalendars * refactor(system): move I18nProviderProps to type * refactor: use `spectrumCalendarProps<DateValue>["createCalendar"]` * feat: add consistent-type-imports * fix: eslint * chore: add changeset * refactor: remove unused deps
429 lines
10 KiB
TypeScript
429 lines
10 KiB
TypeScript
import type {Meta} from "@storybook/react";
|
|
import type {CalendarProps, DateValue} from "../src";
|
|
|
|
import React from "react";
|
|
import {calendar} from "@heroui/theme";
|
|
import {
|
|
today,
|
|
parseDate,
|
|
getLocalTimeZone,
|
|
isWeekend,
|
|
startOfWeek,
|
|
startOfMonth,
|
|
} from "@internationalized/date";
|
|
import {I18nProvider, useLocale} from "@react-aria/i18n";
|
|
import {Button, ButtonGroup} from "@heroui/button";
|
|
import {Radio, RadioGroup} from "@heroui/radio";
|
|
import {cn} from "@heroui/theme";
|
|
import {HeroUIProvider} from "@heroui/system";
|
|
|
|
import {Calendar} from "../src";
|
|
|
|
export default {
|
|
title: "Components/Calendar",
|
|
component: Calendar,
|
|
parameters: {
|
|
layout: "centered",
|
|
},
|
|
argTypes: {
|
|
visibleMonths: {
|
|
control: {type: "number", min: 1, max: 3},
|
|
},
|
|
color: {
|
|
control: {
|
|
type: "select",
|
|
},
|
|
options: ["foreground", "primary", "secondary", "success", "warning", "danger"],
|
|
},
|
|
weekdayStyle: {
|
|
control: {
|
|
type: "select",
|
|
},
|
|
options: ["narrow", "short", "long"],
|
|
},
|
|
disableAnimation: {
|
|
control: {
|
|
type: "boolean",
|
|
},
|
|
},
|
|
firstDayOfWeek: {
|
|
control: "select",
|
|
options: [undefined, "sun", "mon", "tue", "wed", "thu", "fri", "sat"],
|
|
},
|
|
},
|
|
} as Meta<typeof Calendar>;
|
|
|
|
const defaultProps = {
|
|
...calendar.defaultVariants,
|
|
visibleMonths: 1,
|
|
};
|
|
|
|
const Template = (args: CalendarProps) => <Calendar {...args} />;
|
|
|
|
const ControlledTemplate = (args: CalendarProps) => {
|
|
let [value, setValue] = React.useState<DateValue | null>(parseDate("2024-03-07"));
|
|
|
|
return (
|
|
<div className="flex flex-wrap gap-4">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">Date (uncontrolled)</p>
|
|
<Calendar
|
|
aria-label="Date (uncontrolled)"
|
|
defaultValue={parseDate("2024-03-07")}
|
|
{...args}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">Date (controlled)</p>
|
|
<Calendar
|
|
aria-label="Date (controlled)"
|
|
value={value}
|
|
onChange={setValue}
|
|
{...args}
|
|
color="secondary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const UnavailableDatesTemplate = (args: CalendarProps) => {
|
|
let now = today(getLocalTimeZone());
|
|
|
|
let disabledRanges = [
|
|
[now, now.add({days: 5})],
|
|
[now.add({days: 14}), now.add({days: 16})],
|
|
[now.add({days: 23}), now.add({days: 24})],
|
|
];
|
|
|
|
let {locale} = useLocale();
|
|
|
|
let isDateUnavailable = (date) =>
|
|
isWeekend(date, locale) ||
|
|
disabledRanges.some(
|
|
(interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0,
|
|
);
|
|
|
|
return (
|
|
<Calendar
|
|
aria-label="Appointment date"
|
|
isDateUnavailable={isDateUnavailable}
|
|
minValue={today(getLocalTimeZone())}
|
|
{...args}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const ControlledFocusedValueTemplate = (args: CalendarProps) => {
|
|
let defaultDate = today(getLocalTimeZone());
|
|
let [focusedDate, setFocusedDate] = React.useState<DateValue | null>(defaultDate);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<Calendar
|
|
focusedValue={focusedDate}
|
|
value={defaultDate}
|
|
onFocusChange={setFocusedDate}
|
|
{...args}
|
|
/>
|
|
<Button
|
|
className="max-w-fit"
|
|
color="primary"
|
|
variant="flat"
|
|
onPress={() => setFocusedDate(defaultDate)}
|
|
>
|
|
Reset focused date
|
|
</Button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const InvalidDateTemplate = (args: CalendarProps) => {
|
|
let [date, setDate] = React.useState<DateValue>(today(getLocalTimeZone()));
|
|
let {locale} = useLocale();
|
|
let isInvalid = isWeekend(date, locale);
|
|
|
|
return (
|
|
<Calendar
|
|
{...args}
|
|
aria-label="Appointment date"
|
|
errorMessage={isInvalid ? "We are closed on weekends" : undefined}
|
|
isInvalid={isInvalid}
|
|
value={date}
|
|
onChange={setDate}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const InternationalCalendarsTemplate = (args: CalendarProps) => {
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<I18nProvider locale="zh-CN-u-ca-chinese">
|
|
<Calendar aria-label="Appointment date" {...args} />
|
|
</I18nProvider>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const PresetsTemplate = (args: CalendarProps) => {
|
|
let defaultDate = today(getLocalTimeZone());
|
|
let [value, setValue] = React.useState<DateValue | null>(defaultDate);
|
|
let {locale} = useLocale();
|
|
|
|
let now = today(getLocalTimeZone());
|
|
let nextWeek = startOfWeek(now.add({weeks: 1}), locale);
|
|
let nextMonth = startOfMonth(now.add({months: 1}));
|
|
|
|
const CustomRadio = (props) => {
|
|
const {children, ...otherProps} = props;
|
|
|
|
return (
|
|
<Radio
|
|
{...otherProps}
|
|
classNames={{
|
|
base: cn(
|
|
"flex-none m-0 h-8 bg-content1 hover:bg-content2 items-center justify-between",
|
|
"cursor-pointer rounded-full border-2 border-default-200/60",
|
|
"data-[selected=true]:border-primary",
|
|
),
|
|
label: "text-tiny text-default-500",
|
|
labelWrapper: "px-1 m-0",
|
|
wrapper: "hidden",
|
|
}}
|
|
>
|
|
{children}
|
|
</Radio>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<Calendar
|
|
bottomContent={
|
|
<RadioGroup
|
|
aria-label="Date precision"
|
|
classNames={{
|
|
base: "w-full pb-2",
|
|
wrapper: "-my-2.5 py-2.5 px-3 gap-1 flex-nowrap max-w-[280px] overflow-scroll",
|
|
}}
|
|
defaultValue="exact_dates"
|
|
orientation="horizontal"
|
|
>
|
|
<CustomRadio value="exact_dates">Exact dates</CustomRadio>
|
|
<CustomRadio value="1_day">1 day</CustomRadio>
|
|
<CustomRadio value="2_days">2 days</CustomRadio>
|
|
<CustomRadio value="3_days">3 days</CustomRadio>
|
|
<CustomRadio value="7_days">7 days</CustomRadio>
|
|
<CustomRadio value="14_days">14 days</CustomRadio>
|
|
</RadioGroup>
|
|
}
|
|
classNames={{
|
|
content: "w-full",
|
|
}}
|
|
focusedValue={value}
|
|
nextButtonProps={{
|
|
variant: "bordered",
|
|
}}
|
|
prevButtonProps={{
|
|
variant: "bordered",
|
|
}}
|
|
topContent={
|
|
<ButtonGroup
|
|
fullWidth
|
|
className="px-3 pb-2 pt-3 bg-content1 [&>button]:text-default-500 [&>button]:border-default-200/60"
|
|
radius="full"
|
|
size="sm"
|
|
variant="bordered"
|
|
>
|
|
<Button onPress={() => setValue(now)}>Today</Button>
|
|
<Button onPress={() => setValue(nextWeek)}>Next week</Button>
|
|
<Button onPress={() => setValue(nextMonth)}>Next month</Button>
|
|
</ButtonGroup>
|
|
}
|
|
value={value}
|
|
onChange={setValue}
|
|
onFocusChange={setValue}
|
|
{...args}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const CalendarWidthTemplate = (args: CalendarProps) => {
|
|
return (
|
|
<div className="flex gap-4">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">calendarWidth: 300</p>
|
|
<Calendar {...args} calendarWidth={300} />
|
|
</div>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">calendarWidth: 300px</p>
|
|
<Calendar {...args} calendarWidth="300px" />
|
|
</div>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">calendarWidth: 30em</p>
|
|
<Calendar {...args} calendarWidth="30em" />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ReducedMotionTemplate = (args: CalendarProps) => {
|
|
return (
|
|
<div className="flex gap-4">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">reducedMotion: never</p>
|
|
<HeroUIProvider reducedMotion="never">
|
|
<Calendar {...args} />
|
|
</HeroUIProvider>
|
|
</div>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">reducedMotion: always</p>
|
|
<HeroUIProvider reducedMotion="always">
|
|
<Calendar {...args} />
|
|
</HeroUIProvider>
|
|
</div>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<p className="text-small text-default-600">reducedMotion: user</p>
|
|
<HeroUIProvider reducedMotion="user">
|
|
<Calendar {...args} />
|
|
</HeroUIProvider>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const Default = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const Disabled = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
isDisabled: true,
|
|
},
|
|
};
|
|
|
|
export const ReadOnly = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
value: today(getLocalTimeZone()),
|
|
isReadOnly: true,
|
|
},
|
|
};
|
|
|
|
export const Controlled = {
|
|
render: ControlledTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const MinDateValue = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
defaultValue: today(getLocalTimeZone()),
|
|
minValue: today(getLocalTimeZone()),
|
|
},
|
|
};
|
|
|
|
export const MaxDateValue = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
defaultValue: today(getLocalTimeZone()),
|
|
maxValue: today(getLocalTimeZone()),
|
|
},
|
|
};
|
|
|
|
export const UnavailableDates = {
|
|
render: UnavailableDatesTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
defaultValue: today(getLocalTimeZone()),
|
|
unavailableDates: [today(getLocalTimeZone())],
|
|
},
|
|
};
|
|
|
|
export const ControlledFocusedValue = {
|
|
render: ControlledFocusedValueTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const InvalidDate = {
|
|
render: InvalidDateTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const WithMonthAndYearPickers = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
showMonthAndYearPickers: true,
|
|
},
|
|
};
|
|
|
|
export const InternationalCalendars = {
|
|
render: InternationalCalendarsTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
showMonthAndYearPickers: true,
|
|
},
|
|
};
|
|
|
|
export const VisibleMonths = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
visibleMonths: 3,
|
|
},
|
|
};
|
|
|
|
export const PageBehavior = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
visibleMonths: 3,
|
|
pageBehavior: "single",
|
|
},
|
|
};
|
|
|
|
export const Presets = {
|
|
render: PresetsTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const CalendarWidth = {
|
|
render: CalendarWidthTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const ReducedMotion = {
|
|
render: ReducedMotionTemplate,
|
|
args: {
|
|
...defaultProps,
|
|
},
|
|
};
|
|
|
|
export const FirstDayOfWeek = {
|
|
render: Template,
|
|
args: {
|
|
...defaultProps,
|
|
firstDayOfWeek: "mon",
|
|
},
|
|
};
|