Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/component-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@amplitude/analytics-browser": "catalog:",
"@amplitude/plugin-autocapture-browser": "catalog:",
"@axe-core/react": "catalog:",
"@internationalized/date": "catalog:",
"@next/third-parties": "catalog:",
"ag-grid-community": "catalog:",
"ag-grid-react": "catalog:",
Expand Down
189 changes: 189 additions & 0 deletions packages/component-library/src/DateRangeCalendar/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
@use "../theme";

.dateRangeCalendar {
display: flex;
flex-flow: column nowrap;
gap: theme.spacing(4);
color: theme.color("paragraph");
font-size: theme.font-size("sm");
}

.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: theme.spacing(2);
}

.heading {
font-size: theme.font-size("lg");
font-weight: theme.font-weight("semibold");
text-align: center;
flex: 1;
}

.navButton {
display: flex;
align-items: center;
justify-content: center;
width: theme.spacing(8);
height: theme.spacing(8);
border-radius: theme.border-radius("lg");
background-color: transparent;
border: none;
cursor: pointer;
color: theme.color("paragraph");
transition-property: background-color;
transition-duration: 100ms;
transition-timing-function: linear;
font-size: theme.spacing(4);
-webkit-tap-highlight-color: transparent;

&[data-hovered] {
background-color: theme.color("button", "outline", "background", "hover");
}

&[data-pressed] {
background-color: theme.color("button", "outline", "background", "active");
}

&[data-disabled] {
cursor: not-allowed;
}
}

.calendars {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: theme.spacing(6);
}

.calendar {
display: table;
border-collapse: collapse;
border-spacing: 0;

td {
padding: theme.spacing(0.5) 0;
}

td:first-child .cell {
border-top-left-radius: theme.border-radius("lg");
border-bottom-left-radius: theme.border-radius("lg");
}

td:last-child .cell {
border-top-right-radius: theme.border-radius("lg");
border-bottom-right-radius: theme.border-radius("lg");
}
}

.calendarHeader {
display: table-header-group;
}

.dayHeader {
display: table-cell;
text-align: center;
font-size: theme.font-size("xs");
font-weight: theme.font-weight("medium");
color: theme.color("muted");
padding: theme.spacing(2);
width: theme.spacing(10);
}

.calendarBody {
display: table-row-group;
}

.cell {
display: table-cell;
text-align: center;
cursor: pointer;
outline: none;
color: theme.color("button", "outline", "foreground");
padding: theme.spacing(1);

.cellContent {
border-radius: theme.border-radius("lg");
}

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

&[data-today] {
.cellContent {
border: 1px solid theme.color("button", "outline", "border");
}
}

&[data-outside-month] {
.cellContent {
color: theme.color("muted");
}
}

&[data-disabled] {
.cellContent {
background: theme.color("button", "disabled", "background");
color: theme.color("button", "disabled", "foreground");
cursor: not-allowed;
}
}

&[data-unavailable] {
.cellContent {
text-decoration: line-through;
color: theme.color("states", "error", "normal");
cursor: not-allowed;
}
}

&[data-selected] {
background-color: theme.color("background", "card-highlight");
}

&[data-hovered] {
.cellContent {
background-color: theme.color("button", "outline", "background", "hover");
}
}

&[data-selection-start],
&[data-selection-end] {
.cellContent {
background-color: theme.color("button", "primary", "background", "normal");
color: theme.color("button", "primary", "foreground");
}
}

&[data-selection-start] {
border-top-left-radius: theme.border-radius("lg");
border-bottom-left-radius: theme.border-radius("lg");
}

&[data-selection-end] {
border-top-right-radius: theme.border-radius("lg");
border-bottom-right-radius: theme.border-radius("lg");
}

&[data-focused] {
.cellContent {
outline: 2px solid theme.color("focus");
outline-offset: 2px;
border-radius: theme.border-radius("lg");
}
}
}

.cellContent {
display: flex;
align-items: center;
justify-content: center;
width: theme.spacing(9);
height: theme.spacing(9);
transition: 100ms;
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Meta, StoryObj } from "@storybook/react";
import { parseDate } from "@internationalized/date";

import { DateRangeCalendar as DateRangeCalendarComponent } from "./index.jsx";

const meta = {
component: DateRangeCalendarComponent,
argTypes: {
isDisabled: {
control: "boolean",
table: {
category: "Behavior",
},
},
isReadOnly: {
control: "boolean",
table: {
category: "Behavior",
},
},
isInvalid: {
control: "boolean",
table: {
category: "Behavior",
},
},
},
} satisfies Meta<typeof DateRangeCalendarComponent>;
export default meta;

export const Default = {
args: {
isDisabled: false,
isReadOnly: false,
isInvalid: false,
defaultValue: {
start: parseDate("2025-05-01"),
end: parseDate("2025-05-13"),
},
},
} satisfies StoryObj<typeof DateRangeCalendarComponent>;

export const SingleDay = {
args: {
isDisabled: false,
isReadOnly: false,
defaultValue: {
start: parseDate("2025-05-13"),
end: parseDate("2025-05-13"),
},
},
} satisfies StoryObj<typeof DateRangeCalendarComponent>;

export const ReadOnly = {
args: {
isDisabled: false,
isReadOnly: true,
defaultValue: {
start: parseDate("2025-05-01"),
end: parseDate("2025-05-13"),
},
},
} satisfies StoryObj<typeof DateRangeCalendarComponent>;


63 changes: 63 additions & 0 deletions packages/component-library/src/DateRangeCalendar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

import { CaretLeft } from "@phosphor-icons/react/dist/ssr/CaretLeft";
import { CaretRight } from "@phosphor-icons/react/dist/ssr/CaretRight";
import clsx from "clsx";
import type { ComponentProps } from "react";
import {
RangeCalendar,
Heading,
CalendarGrid,
CalendarGridBody,
CalendarCell,
} from "react-aria-components";

import styles from "./index.module.scss";
import { Button as UnstyledButton } from "../unstyled/Button/index.jsx";

type Props = Omit<
ComponentProps<typeof RangeCalendar>,
"children" | "visibleDuration"
>;

export const DateRangeCalendar = ({ className, ...props }: Props) => (
<RangeCalendar
className={clsx(styles.dateRangeCalendar, className)}
visibleDuration={{ months: 2 }}
{...props}
>
<header className={styles.header}>
<UnstyledButton className={styles.navButton ?? ""} slot="previous">
<CaretLeft weight="bold" />
</UnstyledButton>
<Heading className={styles.heading} />
<UnstyledButton className={styles.navButton ?? ""} slot="next">
<CaretRight weight="bold" />
</UnstyledButton>
</header>
<div className={styles.calendars}>
<CalendarGrid className={styles.calendar ?? ""}>
<CalendarGridBody className={styles.calendarBody ?? ""}>
{(date) => (
<CalendarCell className={styles.cell ?? ""} date={date}>
{({ formattedDate }) => (
<div className={styles.cellContent}>{formattedDate}</div>
)}
</CalendarCell>
)}
</CalendarGridBody>
</CalendarGrid>
<CalendarGrid className={styles.calendar ?? ""} offset={{ months: 1 }}>
<CalendarGridBody className={styles.calendarBody ?? ""}>
{(date) => (
<CalendarCell className={styles.cell ?? ""} date={date}>
{({ formattedDate }) => (
<div className={styles.cellContent}>{formattedDate}</div>
)}
</CalendarCell>
)}
</CalendarGridBody>
</CalendarGrid>
</div>
</RangeCalendar>
);
Loading
Loading