-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement date picker component
- Loading branch information
1 parent
b181444
commit 4da2ec7
Showing
25 changed files
with
827 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
packages/lsd-react/src/components/Calendar/Calendar.classes.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export const calendarClasses = { | ||
root: `lsd-calendar`, | ||
container: 'lsd-calendar-container', | ||
|
||
open: 'lsd-calendar--open', | ||
|
||
header: 'lsd-calendar-header', | ||
grid: 'lsd-calendar-body', | ||
button: 'lsd-calendar-button', | ||
|
||
day: 'lsd-calendar-day', | ||
daySelected: 'lsd-calendar-day--selected', | ||
dayDisabled: 'lsd-calendar-day--diabled', | ||
} |
19 changes: 19 additions & 0 deletions
19
packages/lsd-react/src/components/Calendar/Calendar.context.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from 'react' | ||
|
||
export type CalendarContextType = { | ||
focusedDate: Date | null | ||
isDateFocused: (date: Date) => boolean | ||
isDateSelected: (date: Date) => boolean | ||
isDateHovered: (date: Date) => boolean | ||
isDateBlocked: (date: Date) => boolean | ||
isFirstOrLastSelectedDate: (date: Date) => boolean | ||
onDateFocus: (date: Date) => void | ||
onDateHover: (date: Date) => void | ||
onDateSelect: (date: Date) => void | ||
} | ||
|
||
export const CalendarContext = React.createContext<CalendarContextType>( | ||
null as any, | ||
) | ||
|
||
export const useCalendarContext = () => React.useContext(CalendarContext) |
29 changes: 29 additions & 0 deletions
29
packages/lsd-react/src/components/Calendar/Calendar.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Meta, Story } from '@storybook/react' | ||
import { useRef, useState } from 'react' | ||
import { Calendar, CalendarProps } from './Calendar' | ||
|
||
export default { | ||
title: 'Calendar', | ||
component: Calendar, | ||
} as Meta | ||
|
||
export const Root: Story<CalendarProps> = (arg) => { | ||
const ref = useRef<HTMLDivElement>(null) | ||
|
||
return ( | ||
<div ref={ref} style={{ width: '300px' }}> | ||
<Calendar | ||
{...arg} | ||
handleDateFieldChange={(date) => console.log(date?.toDateString())} | ||
open={true} | ||
handleRef={ref} | ||
> | ||
Calendar | ||
</Calendar> | ||
</div> | ||
) | ||
} | ||
|
||
Root.args = { | ||
value: '', | ||
} |
70 changes: 70 additions & 0 deletions
70
packages/lsd-react/src/components/Calendar/Calendar.styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { css } from '@emotion/react' | ||
import { calendarClasses } from './Calendar.classes' | ||
|
||
export const CalendarStyles = css` | ||
.${calendarClasses.root} { | ||
border: 1px solid rgb(var(--lsd-border-primary)); | ||
visibility: hidden; | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
opacity: 0; | ||
visibility: hidden; | ||
margin: 0; | ||
padding: 0; | ||
box-sizing: border-box; | ||
background: rgb(var(--lsd-surface-primary)); | ||
} | ||
.${calendarClasses.container} { | ||
display: grid; | ||
margin: 8px 2px 2px; | ||
grid-gap: 0 64px; | ||
} | ||
.${calendarClasses.open} { | ||
opacity: 1; | ||
visibility: visible; | ||
} | ||
.${calendarClasses.header} { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
padding-inline: 10px; | ||
padding-bottom: 10px; | ||
} | ||
.${calendarClasses.grid} { | ||
display: grid; | ||
grid-template-columns: repeat(7, 1fr); | ||
justify-content: center; | ||
} | ||
.${calendarClasses.day} { | ||
cursor: pointer; | ||
border: none; | ||
background: transparent; | ||
aspect-ratio: 1 / 1; | ||
} | ||
.${calendarClasses.daySelected} { | ||
border: 1px solid rgb(var(--lsd-border-primary)); | ||
} | ||
.${calendarClasses.dayDisabled} { | ||
opacity: 0.3; | ||
cursor: default; | ||
} | ||
.${calendarClasses.button} { | ||
border: 1px solid rgb(var(--lsd-border-primary)); | ||
cursor: pointer; | ||
background: transparent; | ||
width: 32px; | ||
height: 32px; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
` |
124 changes: 124 additions & 0 deletions
124
packages/lsd-react/src/components/Calendar/Calendar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { | ||
OnDatesChangeProps, | ||
START_DATE, | ||
useDatepicker, | ||
} from '@datepicker-react/hooks' | ||
import clsx from 'clsx' | ||
import React, { useEffect, useRef, useState } from 'react' | ||
import { useClickAway } from 'react-use' | ||
import { calendarClasses } from './Calendar.classes' | ||
import { CalendarContext } from './Calendar.context' | ||
import { Month } from './Month' | ||
|
||
export type CalendarProps = Omit< | ||
React.HTMLAttributes<HTMLUListElement>, | ||
'label' | ||
> & { | ||
open?: boolean | ||
value?: string | ||
onClose?: () => void | ||
handleDateFieldChange: (data: Date) => void | ||
handleRef: React.RefObject<HTMLElement> | ||
} | ||
|
||
export const Calendar: React.FC<CalendarProps> & { | ||
classes: typeof calendarClasses | ||
} = ({ | ||
open, | ||
handleRef, | ||
value = null, | ||
onClose, | ||
handleDateFieldChange, | ||
children, | ||
...props | ||
}) => { | ||
const ref = useRef<HTMLUListElement>(null) | ||
const [style, setStyle] = useState<React.CSSProperties>({}) | ||
|
||
const handleDateChange = (data: OnDatesChangeProps) => { | ||
handleDateFieldChange(data.startDate ?? new Date()) | ||
onDateFocus(data.startDate ?? new Date()) | ||
} | ||
|
||
const { | ||
activeMonths, | ||
isDateSelected, | ||
isDateHovered, | ||
isFirstOrLastSelectedDate, | ||
isDateBlocked, | ||
isDateFocused, | ||
focusedDate, | ||
onDateHover, | ||
onDateSelect, | ||
onDateFocus, | ||
goToPreviousMonths, | ||
goToNextMonths, | ||
} = useDatepicker({ | ||
startDate: value ? new Date(value) : null, | ||
endDate: null, | ||
focusedInput: START_DATE, | ||
onDatesChange: handleDateChange, | ||
numberOfMonths: 1, | ||
}) | ||
|
||
useClickAway(ref, (event) => { | ||
if (!open || event.composedPath().includes(handleRef.current!)) return | ||
|
||
onClose && onClose() | ||
}) | ||
|
||
const updateStyle = () => { | ||
const { width, height, top, left } = | ||
handleRef.current!.getBoundingClientRect() | ||
|
||
setStyle({ | ||
left, | ||
width, | ||
top: top + height, | ||
}) | ||
} | ||
|
||
useEffect(() => { | ||
updateStyle() | ||
}, [open]) | ||
|
||
return ( | ||
<CalendarContext.Provider | ||
value={{ | ||
focusedDate, | ||
isDateFocused, | ||
isDateSelected, | ||
isDateHovered, | ||
isDateBlocked, | ||
isFirstOrLastSelectedDate, | ||
onDateSelect, | ||
onDateFocus, | ||
onDateHover, | ||
}} | ||
> | ||
<div | ||
className={clsx( | ||
props.className, | ||
calendarClasses.root, | ||
open && calendarClasses.open, | ||
)} | ||
style={{ ...style, ...(props.style ?? {}) }} | ||
> | ||
<div className={clsx(calendarClasses.container)}> | ||
{activeMonths.map((month) => ( | ||
<Month | ||
key={`${month.year}-${month.month}`} | ||
year={month.year} | ||
month={month.month} | ||
firstDayOfWeek={0} | ||
goToPreviousMonths={goToPreviousMonths} | ||
goToNextMonths={goToNextMonths} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
</CalendarContext.Provider> | ||
) | ||
} | ||
|
||
Calendar.classes = calendarClasses |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { useRef, useContext } from 'react' | ||
import { useDay } from '@datepicker-react/hooks' | ||
import { CalendarContext } from './Calendar.context' | ||
import clsx from 'clsx' | ||
import { calendarClasses } from './Calendar.classes' | ||
import { Typography } from '../Typography' | ||
|
||
export type DayProps = { | ||
day?: string | ||
date: Date | ||
} | ||
|
||
export const Day = ({ day, date }: DayProps) => { | ||
const dayRef = useRef(null) | ||
const { | ||
focusedDate, | ||
isDateFocused, | ||
isDateSelected, | ||
isDateHovered, | ||
isDateBlocked, | ||
isFirstOrLastSelectedDate, | ||
onDateSelect, | ||
onDateFocus, | ||
onDateHover, | ||
} = useContext(CalendarContext) | ||
|
||
const { onClick, onKeyDown, onMouseEnter, tabIndex } = useDay({ | ||
date, | ||
focusedDate, | ||
isDateFocused, | ||
isDateSelected, | ||
isDateHovered, | ||
isDateBlocked, | ||
isFirstOrLastSelectedDate, | ||
onDateFocus, | ||
onDateSelect, | ||
onDateHover, | ||
dayRef, | ||
}) | ||
|
||
if (!day) { | ||
return null | ||
} | ||
|
||
return ( | ||
<button | ||
onClick={onClick} | ||
onKeyDown={onKeyDown} | ||
onMouseEnter={onMouseEnter} | ||
tabIndex={tabIndex} | ||
type="button" | ||
ref={dayRef} | ||
className={clsx( | ||
calendarClasses.day, | ||
isDateFocused(date) && calendarClasses.daySelected, | ||
)} | ||
> | ||
<Typography variant="label2">{parseInt(day, 10)}</Typography> | ||
</button> | ||
) | ||
} |
Oops, something went wrong.