Skip to content

Commit ab301a6

Browse files
common calendar bits
1 parent 7b998aa commit ab301a6

File tree

2 files changed

+378
-218
lines changed

2 files changed

+378
-218
lines changed

src/components/DatePicker/Common.tsx

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import styled from "styled-components";
22
import { InputElement, InputWrapper } from "../Input/InputWrapper";
3-
import { useId } from "react";
3+
import { ReactNode, useId } from "react";
44
import { Icon } from "../Icon/Icon";
5+
import { Container, ContainerProps } from "../Container/Container";
6+
import { useCalendar, UseCalendarOptions } from "@h6s/calendar";
7+
import { IconButton } from "../IconButton/IconButton";
58

69
const locale = "en-US";
710
const selectedDateFormatter = new Intl.DateTimeFormat(locale, {
@@ -104,3 +107,189 @@ export const DateRangePickerInput = ({
104107
</HighlightedInputWrapper>
105108
);
106109
};
110+
111+
const weekdayFormatter = new Intl.DateTimeFormat(locale, { weekday: "short" });
112+
const headerDateFormatter = new Intl.DateTimeFormat(locale, {
113+
month: "short",
114+
year: "numeric",
115+
});
116+
117+
const DatePickerContainer = styled(Container)`
118+
background: ${({ theme }) =>
119+
theme.click.datePicker.dateOption.color.background.default};
120+
`;
121+
122+
const UnselectableTitle = styled.h2`
123+
${({ theme }) => `
124+
color: ${theme.click.datePicker.color.title.default};
125+
font: ${theme.click.datePicker.typography.title.default};
126+
`}
127+
128+
user-select: none;
129+
`;
130+
131+
const DateTable = styled.table`
132+
border-collapse: separate;
133+
border-spacing: 0;
134+
font: ${({ theme }) => theme.typography.styles.product.text.normal.md}
135+
table-layout: fixed;
136+
user-select: none;
137+
width: ${explicitWidth};
138+
139+
thead tr {
140+
height: ${({ theme }) => theme.click.datePicker.dateOption.size.height};
141+
}
142+
143+
tbody {
144+
cursor: pointer;
145+
}
146+
147+
td, th {
148+
padding: 4px;
149+
}
150+
`;
151+
152+
const DateTableHeader = styled.th`
153+
${({ theme }) => `
154+
color: ${theme.click.datePicker.color.daytitle.default};
155+
font: ${theme.click.datePicker.typography.daytitle.default};
156+
`}
157+
158+
width: 14%;
159+
`;
160+
161+
export const DateTableCell = styled.td<{
162+
$isCurrentMonth?: boolean;
163+
$isDisabled?: boolean;
164+
$isSelected?: boolean;
165+
$isToday?: boolean;
166+
}>`
167+
${({ theme }) => `
168+
border: ${theme.click.datePicker.dateOption.stroke} solid ${theme.click.datePicker.dateOption.color.stroke.default};
169+
border-radius: ${theme.click.datePicker.dateOption.radii.default};
170+
font: ${theme.click.datePicker.dateOption.typography.label.default};
171+
`}
172+
173+
${({ $isCurrentMonth, $isDisabled, theme }) =>
174+
(!$isCurrentMonth || $isDisabled) &&
175+
`
176+
color: ${theme.click.datePicker.dateOption.color.label.disabled};
177+
font: ${theme.click.datePicker.dateOption.typography.label.disabled};
178+
`}
179+
180+
${({ $isSelected, theme }) =>
181+
$isSelected &&
182+
`
183+
background: ${theme.click.datePicker.dateOption.color.background.active};
184+
color: ${theme.click.datePicker.dateOption.color.label.active};
185+
`}
186+
187+
188+
text-align: center;
189+
190+
${({ $isToday, theme }) =>
191+
$isToday && `font: ${theme.click.datePicker.dateOption.typography.label.active};`}
192+
193+
&:hover {
194+
${({ $isDisabled, theme }) =>
195+
`border: ${theme.click.datePicker.dateOption.stroke} solid ${
196+
$isDisabled
197+
? theme.click.datePicker.dateOption.color.stroke.disabled
198+
: theme.click.datePicker.dateOption.color.stroke.hover
199+
};
200+
201+
202+
border-radius: ${theme.click.datePicker.dateOption.radii.default};`};
203+
}
204+
`;
205+
206+
// Taken from h6s/calendar
207+
type Week = {
208+
value: Date;
209+
} & {
210+
date: number;
211+
isCurrentMonth: boolean;
212+
isCurrentDate: boolean;
213+
isWeekend: boolean;
214+
} & {
215+
key: string;
216+
};
217+
218+
export type WeekRenderer = (week: Week) => ReactNode;
219+
220+
interface CalendarRendererProps extends ContainerProps {
221+
calendarOptions: UseCalendarOptions;
222+
renderWeek: WeekRenderer;
223+
}
224+
225+
export const CalendarRenderer = ({
226+
calendarOptions,
227+
renderWeek,
228+
...props
229+
}: CalendarRendererProps) => {
230+
const { body, headers, month, navigation, year } = useCalendar({
231+
defaultWeekStart: 1,
232+
...calendarOptions,
233+
});
234+
235+
const handleNextClick = (): void => {
236+
navigation.toNext();
237+
};
238+
239+
const handlePreviousClick = (): void => {
240+
navigation.toPrev();
241+
};
242+
243+
const headerDate = new Date();
244+
headerDate.setMonth(month);
245+
headerDate.setFullYear(year);
246+
247+
return (
248+
<DatePickerContainer
249+
data-testid="datepicker-calendar-container"
250+
isResponsive={false}
251+
fillWidth={false}
252+
orientation="vertical"
253+
padding="sm"
254+
{...props}
255+
>
256+
<Container
257+
isResponsive={false}
258+
justifyContent="space-between"
259+
orientation="horizontal"
260+
>
261+
<IconButton
262+
icon="chevron-left"
263+
onClick={handlePreviousClick}
264+
size="sm"
265+
type="ghost"
266+
/>
267+
<UnselectableTitle>{headerDateFormatter.format(headerDate)}</UnselectableTitle>
268+
<IconButton
269+
icon="chevron-right"
270+
onClick={handleNextClick}
271+
size="sm"
272+
type="ghost"
273+
/>
274+
</Container>
275+
<DateTable>
276+
<thead>
277+
<tr>
278+
{headers.weekDays.map(({ key, value: date }) => {
279+
return (
280+
<DateTableHeader key={key}>
281+
{weekdayFormatter.format(date)}
282+
</DateTableHeader>
283+
);
284+
})}
285+
</tr>
286+
</thead>
287+
<tbody>
288+
{body.value.map(({ key: weekKey, value: week }) => {
289+
return <tr key={weekKey}>{week.map(renderWeek)}</tr>;
290+
})}
291+
</tbody>
292+
</DateTable>
293+
</DatePickerContainer>
294+
);
295+
};

0 commit comments

Comments
 (0)