Skip to content

Commit

Permalink
feat: add DatePicker and DataRangePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
Lisa18289 authored Aug 29, 2024
1 parent c6d16b3 commit 3d20fbc
Show file tree
Hide file tree
Showing 36 changed files with 631 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@
"types": "./dist/types/components/CounterBadge/index.d.ts",
"import": "./dist/CounterBadge.js"
},
"./DatePicker": {
"types": "./dist/types/components/DatePicker/index.d.ts",
"import": "./dist/DatePicker.js"
},
"./DateRangePicker": {
"types": "./dist/types/components/DateRangePicker/index.d.ts",
"import": "./dist/DateRangePicker.js"
},
"./FieldDescription": {
"types": "./dist/types/components/FieldDescription/index.d.ts",
"import": "./dist/FieldDescription.js"
Expand Down Expand Up @@ -291,6 +299,7 @@
},
"dependencies": {
"@chakra-ui/live-region": "^2.1.0",
"@internationalized/date": "^3.5.1",
"@internationalized/string-compiler": "^3.2.4",
"@mittwald/react-tunnel": "workspace:^",
"@mittwald/react-use-promise": "^2.3.13",
Expand Down
48 changes: 48 additions & 0 deletions packages/components/src/components/Calendar/Calendar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.calendar {
padding: var(--calendar--padding);

header {
display: flex;
align-items: center;
justify-content: space-between;
h2 {
font-size: var(--font-size--default);
font-weight: var(--font-weight--normal);
}
}

:global(.react-aria-CalendarHeaderCell) {
color: var(--label--color);
}

:global(.react-aria-CalendarCell) {
width: var(--calendar--cell-size);
height: var(--calendar--cell-size);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: default;
border-radius: var(--button--corner-radius);

&[data-outside-month] {
display: none;
}

&:hover {
background: var(--calendar--day-background-color--hover);
}

&[data-pressed] {
background: var(--calendar--day-background-color--pressed);
}

&[data-selected] {
background: var(--calendar--day-background-color--pressed);
}

&[data-disabled] {
background: var(--calendar--day-background-color--disabled);
color: var(--calendar--day-color--disabled);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { FC } from "react";
import React from "react";
import { Button } from "@/components/Button";
import * as Aria from "react-aria-components";
import {
IconChevronLeft,
IconChevronRight,
} from "@/components/Icon/components/icons";

export const CalendarHeader: FC = () => {
return (
<header>
<Button ariaSlot="previous" variant="plain">
<IconChevronLeft />
</Button>
<Aria.Heading />
<Button ariaSlot="next" variant="plain">
<IconChevronRight />
</Button>
</header>
);
};

export default CalendarHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { CalendarHeader } from "./CalendarHeader";
export { CalendarHeader } from "./CalendarHeader";
export default CalendarHeader;
1 change: 1 addition & 0 deletions packages/components/src/components/Calendar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./components/CalendarHeader";
60 changes: 60 additions & 0 deletions packages/components/src/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { FC, PropsWithChildren, ReactNode } from "react";
import React from "react";
import clsx from "clsx";
import type { PropsContext } from "@/lib/propsContext";
import { PropsContextProvider } from "@/lib/propsContext";
import * as Aria from "react-aria-components";
import { Calendar } from "./components/Calendar";
import { DateInput } from "./components/DateInput";
import { FieldError } from "@/components/FieldError";
import styles from "../FormField/FormField.module.scss";
import { Popover } from "@/components/Popover";
import { useOverlayController } from "@/lib/controller";

export interface DatePickerProps<T extends Aria.DateValue>
extends PropsWithChildren<Omit<Aria.DatePickerProps<T>, "children">> {
errorMessage?: ReactNode;
}

export const DatePicker: FC<DatePickerProps<Aria.DateValue>> = (props) => {
const { children, className, errorMessage, ...rest } = props;

const rootClassName = clsx(styles.formField, className);

const propsContext: PropsContext = {
Label: {
className: styles.label,
optional: !props.isRequired,
},
FieldDescription: {
className: styles.fieldDescription,
},
};

const popoverController = useOverlayController("Popover");

return (
<Aria.DatePicker
{...rest}
className={rootClassName}
onOpenChange={(v) => popoverController.setOpen(v)}
isOpen={popoverController.isOpen}
onChange={popoverController.close}
>
<DateInput isDisabled={props.isDisabled} />
<PropsContextProvider props={propsContext}>
{children}
</PropsContextProvider>
<FieldError className={styles.fieldError}>{errorMessage}</FieldError>
<Popover
placement="bottom end"
isDialogContent
controller={popoverController}
>
<Calendar />
</Popover>
</Aria.DatePicker>
);
};

export default DatePicker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { FC } from "react";
import React from "react";
import * as Aria from "react-aria-components";
import styles from "@/components/Calendar/Calendar.module.css";
import { CalendarHeader } from "@/components/Calendar";

export const Calendar: FC = () => {
return (
<Aria.Calendar className={styles.calendar}>
<CalendarHeader />
<Aria.CalendarGrid>
{(date) => <Aria.CalendarCell date={date} />}
</Aria.CalendarGrid>
</Aria.Calendar>
);
};

export default Calendar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Calendar } from "./Calendar";
export { Calendar } from "./Calendar";
export default Calendar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@use "@/styles/mixins/formControl.scss";

.dateInput {
@include formControl.formControl();
display: flex;

:global(.react-aria-DateInput) {
display: flex;
flex-grow: 1;
}

:global(.react-aria-DateSegment) {
border-radius: var(--date-picker--date-segment-corner-radius);

&:focus {
background: var(--date-picker--date-segment-background-color--focused);
outline: none;
}
}

:global(.flow--button) {
margin-block: calc((var(--form-control--padding-y) - 1px) * -1);
margin-inline-end: calc(var(--form-control--padding-x) * -1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { FC } from "react";
import React from "react";
import styles from "./DateInput.module.scss";
import * as Aria from "react-aria-components";
import { Button } from "@/components/Button";
import { IconCalendar } from "@/components/Icon/components/icons";

export interface DateInputProps {
isDisabled?: boolean;
}

export const DateInput: FC<DateInputProps> = (props) => {
const { isDisabled } = props;

return (
<Aria.Group className={styles.dateInput}>
<Aria.DateInput>
{(segment) => <Aria.DateSegment segment={segment} />}
</Aria.DateInput>
<Button variant="plain" color="secondary" isDisabled={isDisabled}>
<IconCalendar />
</Button>
</Aria.Group>
);
};

export default DateInput;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { DateInput } from "./DateInput";
export { type DateInputProps, DateInput } from "./DateInput";
export default DateInput;
3 changes: 3 additions & 0 deletions packages/components/src/components/DatePicker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { DatePicker } from "./DatePicker";
export { type DatePickerProps, DatePicker } from "./DatePicker";
export default DatePicker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import DatePicker from "../index";
import { Label } from "@/components/Label";
import { FieldDescription } from "@/components/FieldDescription";
import { today, getLocalTimeZone, parseDate } from "@internationalized/date";

const meta: Meta<typeof DatePicker> = {
title: "Form Controls/DatePicker",
component: DatePicker,
render: (props) => (
<DatePicker {...props} isRequired>
<Label>Date</Label>
</DatePicker>
),
parameters: {
controls: { exclude: ["errorMessage"] },
},
};

export default meta;

type Story = StoryObj<typeof DatePicker>;

export const Default: Story = {};

export const Disabled: Story = { args: { isDisabled: true } };

export const Invalid: Story = {
args: { minValue: today(getLocalTimeZone()) },
render: (props) => (
<DatePicker
isRequired
defaultValue={parseDate("2012-07-03")}
{...props}
isInvalid
errorMessage="Date is in the past"
>
<Label>Future Date</Label>
</DatePicker>
),
};

export const FutureDatesOnly: Story = {
args: { minValue: today(getLocalTimeZone()) },
render: (props) => (
<DatePicker isRequired {...props}>
<Label>Future Date</Label>
<FieldDescription>Select a future date</FieldDescription>
</DatePicker>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { FC, PropsWithChildren, ReactNode } from "react";
import React from "react";
import styles from "../FormField/FormField.module.scss";
import clsx from "clsx";
import type { PropsContext } from "@/lib/propsContext";
import { PropsContextProvider } from "@/lib/propsContext";
import * as Aria from "react-aria-components";
import { Popover } from "@/components/Popover";
import { RangeCalendar } from "./components/RangeCalendar";
import { DateRangeInput } from "./components/DateRangeInput";
import { FieldError } from "@/components/FieldError";
import { useOverlayController } from "@/lib/controller";

export interface DateRangePickerProps<T extends Aria.DateValue>
extends PropsWithChildren<Omit<Aria.DateRangePickerProps<T>, "children">> {
errorMessage?: ReactNode;
}

export const DateRangePicker: FC<DateRangePickerProps<Aria.DateValue>> = (
props,
) => {
const { children, className, errorMessage, ...rest } = props;

const rootClassName = clsx(styles.formField, className);

const propsContext: PropsContext = {
Label: {
className: styles.label,
optional: !props.isRequired,
},
FieldDescription: {
className: styles.fieldDescription,
},
};

const popoverController = useOverlayController("Popover");

return (
<Aria.DateRangePicker
{...rest}
className={rootClassName}
onOpenChange={(v) => popoverController.setOpen(v)}
isOpen={popoverController.isOpen}
onChange={popoverController.close}
>
<DateRangeInput isDisabled={props.isDisabled} />
<PropsContextProvider props={propsContext}>
{children}
</PropsContextProvider>
<FieldError className={styles.fieldError}>{errorMessage}</FieldError>
<Popover
placement="bottom end"
isDialogContent
controller={popoverController}
>
<RangeCalendar />
</Popover>
</Aria.DateRangePicker>
);
};

export default DateRangePicker;
Loading

0 comments on commit 3d20fbc

Please sign in to comment.