Skip to content

Commit 0a45099

Browse files
committed
WIP
1 parent 2ff48dd commit 0a45099

File tree

12 files changed

+1270
-20
lines changed

12 files changed

+1270
-20
lines changed

packages/component-library/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@amplitude/analytics-browser": "catalog:",
4545
"@amplitude/plugin-autocapture-browser": "catalog:",
4646
"@axe-core/react": "catalog:",
47+
"@internationalized/date": "catalog:",
4748
"@next/third-parties": "catalog:",
4849
"@react-hookz/web": "catalog:",
4950
"bcp-47": "catalog:",
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
@use "../theme";
2+
3+
.dateRangeCalendar {
4+
display: flex;
5+
flex-flow: column nowrap;
6+
gap: theme.spacing(4);
7+
color: theme.color("paragraph");
8+
font-size: theme.font-size("sm");
9+
}
10+
11+
.header {
12+
display: flex;
13+
align-items: center;
14+
justify-content: space-between;
15+
gap: theme.spacing(2);
16+
}
17+
18+
.heading {
19+
font-size: theme.font-size("lg");
20+
font-weight: theme.font-weight("semibold");
21+
text-align: center;
22+
flex: 1;
23+
}
24+
25+
.navButton {
26+
display: flex;
27+
align-items: center;
28+
justify-content: center;
29+
width: theme.spacing(8);
30+
height: theme.spacing(8);
31+
border-radius: theme.border-radius("lg");
32+
background-color: transparent;
33+
border: none;
34+
cursor: pointer;
35+
color: theme.color("paragraph");
36+
transition-property: background-color;
37+
transition-duration: 100ms;
38+
transition-timing-function: linear;
39+
font-size: theme.spacing(4);
40+
-webkit-tap-highlight-color: transparent;
41+
42+
&[data-hovered] {
43+
background-color: theme.color("button", "outline", "background", "hover");
44+
}
45+
46+
&[data-pressed] {
47+
background-color: theme.color("button", "outline", "background", "active");
48+
}
49+
50+
&[data-disabled] {
51+
cursor: not-allowed;
52+
}
53+
}
54+
55+
.calendars {
56+
display: grid;
57+
grid-template-columns: repeat(2, 1fr);
58+
gap: theme.spacing(6);
59+
}
60+
61+
.calendar {
62+
display: table;
63+
border-collapse: collapse;
64+
border-spacing: 0;
65+
66+
td {
67+
padding: theme.spacing(0.5) 0;
68+
}
69+
70+
td:first-child .cell {
71+
border-top-left-radius: theme.border-radius("lg");
72+
border-bottom-left-radius: theme.border-radius("lg");
73+
}
74+
75+
td:last-child .cell {
76+
border-top-right-radius: theme.border-radius("lg");
77+
border-bottom-right-radius: theme.border-radius("lg");
78+
}
79+
}
80+
81+
.calendarHeader {
82+
display: table-header-group;
83+
}
84+
85+
.dayHeader {
86+
display: table-cell;
87+
text-align: center;
88+
font-size: theme.font-size("xs");
89+
font-weight: theme.font-weight("medium");
90+
color: theme.color("muted");
91+
padding: theme.spacing(2);
92+
width: theme.spacing(10);
93+
}
94+
95+
.calendarBody {
96+
display: table-row-group;
97+
}
98+
99+
.cell {
100+
display: table-cell;
101+
text-align: center;
102+
cursor: pointer;
103+
outline: none;
104+
color: theme.color("button", "outline", "foreground");
105+
padding: theme.spacing(1);
106+
107+
.cellContent {
108+
border-radius: theme.border-radius("lg");
109+
}
110+
111+
&[data-outside-month] {
112+
display: none;
113+
}
114+
115+
&[data-today] {
116+
.cellContent {
117+
border: 1px solid theme.color("button", "outline", "border");
118+
}
119+
}
120+
121+
&[data-outside-month] {
122+
.cellContent {
123+
color: theme.color("muted");
124+
}
125+
}
126+
127+
&[data-disabled] {
128+
.cellContent {
129+
background: theme.color("button", "disabled", "background");
130+
color: theme.color("button", "disabled", "foreground");
131+
cursor: not-allowed;
132+
}
133+
}
134+
135+
&[data-unavailable] {
136+
.cellContent {
137+
text-decoration: line-through;
138+
color: theme.color("states", "error", "normal");
139+
cursor: not-allowed;
140+
}
141+
}
142+
143+
&[data-selected] {
144+
background-color: theme.color("background", "card-highlight");
145+
}
146+
147+
&[data-hovered] {
148+
.cellContent {
149+
background-color: theme.color("button", "outline", "background", "hover");
150+
}
151+
}
152+
153+
&[data-selection-start],
154+
&[data-selection-end] {
155+
.cellContent {
156+
background-color: theme.color("button", "primary", "background", "normal");
157+
color: theme.color("button", "primary", "foreground");
158+
}
159+
}
160+
161+
&[data-selection-start] {
162+
border-top-left-radius: theme.border-radius("lg");
163+
border-bottom-left-radius: theme.border-radius("lg");
164+
}
165+
166+
&[data-selection-end] {
167+
border-top-right-radius: theme.border-radius("lg");
168+
border-bottom-right-radius: theme.border-radius("lg");
169+
}
170+
171+
&[data-focused] {
172+
.cellContent {
173+
outline: 2px solid theme.color("focus");
174+
outline-offset: 2px;
175+
border-radius: theme.border-radius("lg");
176+
}
177+
}
178+
}
179+
180+
.cellContent {
181+
display: flex;
182+
align-items: center;
183+
justify-content: center;
184+
width: theme.spacing(9);
185+
height: theme.spacing(9);
186+
transition: 100ms;
187+
}
188+
189+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { parseDate } from "@internationalized/date";
3+
4+
import { DateRangeCalendar as DateRangeCalendarComponent } from "./index.jsx";
5+
6+
const meta = {
7+
component: DateRangeCalendarComponent,
8+
argTypes: {
9+
isDisabled: {
10+
control: "boolean",
11+
table: {
12+
category: "Behavior",
13+
},
14+
},
15+
isReadOnly: {
16+
control: "boolean",
17+
table: {
18+
category: "Behavior",
19+
},
20+
},
21+
isInvalid: {
22+
control: "boolean",
23+
table: {
24+
category: "Behavior",
25+
},
26+
},
27+
},
28+
} satisfies Meta<typeof DateRangeCalendarComponent>;
29+
export default meta;
30+
31+
export const Default = {
32+
args: {
33+
isDisabled: false,
34+
isReadOnly: false,
35+
isInvalid: false,
36+
defaultValue: {
37+
start: parseDate("2025-05-01"),
38+
end: parseDate("2025-05-13"),
39+
},
40+
},
41+
} satisfies StoryObj<typeof DateRangeCalendarComponent>;
42+
43+
export const SingleDay = {
44+
args: {
45+
isDisabled: false,
46+
isReadOnly: false,
47+
defaultValue: {
48+
start: parseDate("2025-05-13"),
49+
end: parseDate("2025-05-13"),
50+
},
51+
},
52+
} satisfies StoryObj<typeof DateRangeCalendarComponent>;
53+
54+
export const ReadOnly = {
55+
args: {
56+
isDisabled: false,
57+
isReadOnly: true,
58+
defaultValue: {
59+
start: parseDate("2025-05-01"),
60+
end: parseDate("2025-05-13"),
61+
},
62+
},
63+
} satisfies StoryObj<typeof DateRangeCalendarComponent>;
64+
65+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use client";
2+
3+
import { CaretLeft } from "@phosphor-icons/react/dist/ssr/CaretLeft";
4+
import { CaretRight } from "@phosphor-icons/react/dist/ssr/CaretRight";
5+
import clsx from "clsx";
6+
import type { ComponentProps } from "react";
7+
import {
8+
RangeCalendar,
9+
Heading,
10+
CalendarGrid,
11+
CalendarGridBody,
12+
CalendarCell,
13+
} from "react-aria-components";
14+
15+
import styles from "./index.module.scss";
16+
import { Button as UnstyledButton } from "../unstyled/Button/index.jsx";
17+
18+
type Props = Omit<
19+
ComponentProps<typeof RangeCalendar>,
20+
"children" | "visibleDuration"
21+
>;
22+
23+
export const DateRangeCalendar = ({ className, ...props }: Props) => (
24+
<RangeCalendar
25+
className={clsx(styles.dateRangeCalendar, className)}
26+
visibleDuration={{ months: 2 }}
27+
{...props}
28+
>
29+
<header className={styles.header}>
30+
<UnstyledButton className={styles.navButton ?? ""} slot="previous">
31+
<CaretLeft weight="bold" />
32+
</UnstyledButton>
33+
<Heading className={styles.heading} />
34+
<UnstyledButton className={styles.navButton ?? ""} slot="next">
35+
<CaretRight weight="bold" />
36+
</UnstyledButton>
37+
</header>
38+
<div className={styles.calendars}>
39+
<CalendarGrid className={styles.calendar ?? ""}>
40+
<CalendarGridBody className={styles.calendarBody ?? ""}>
41+
{(date) => (
42+
<CalendarCell className={styles.cell ?? ""} date={date}>
43+
{({ formattedDate }) => (
44+
<div className={styles.cellContent}>{formattedDate}</div>
45+
)}
46+
</CalendarCell>
47+
)}
48+
</CalendarGridBody>
49+
</CalendarGrid>
50+
<CalendarGrid className={styles.calendar ?? ""} offset={{ months: 1 }}>
51+
<CalendarGridBody className={styles.calendarBody ?? ""}>
52+
{(date) => (
53+
<CalendarCell className={styles.cell ?? ""} date={date}>
54+
{({ formattedDate }) => (
55+
<div className={styles.cellContent}>{formattedDate}</div>
56+
)}
57+
</CalendarCell>
58+
)}
59+
</CalendarGridBody>
60+
</CalendarGrid>
61+
</div>
62+
</RangeCalendar>
63+
);

0 commit comments

Comments
 (0)