feat: implement date picker component

This commit is contained in:
jinhojang6 2023-02-28 03:01:18 +09:00 committed by Jon
parent 41b2c62e63
commit 51d995a264
24 changed files with 811 additions and 0 deletions

View File

@ -14,6 +14,7 @@
"prepublish": "yarn build" "prepublish": "yarn build"
}, },
"dependencies": { "dependencies": {
"@datepicker-react/hooks": "^2.8.4",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.10.5",
"clsx": "^1.2.1", "clsx": "^1.2.1",

View File

@ -5,6 +5,7 @@ import { BadgeStyles } from '../Badge/Badge.styles'
import { BreadcrumbStyles } from '../Breadcrumb/Breadcrumb.styles' import { BreadcrumbStyles } from '../Breadcrumb/Breadcrumb.styles'
import { BreadcrumbItemStyles } from '../BreadcrumbItem/BreadcrumbItem.styles' import { BreadcrumbItemStyles } from '../BreadcrumbItem/BreadcrumbItem.styles'
import { ButtonStyles } from '../Button/Button.styles' import { ButtonStyles } from '../Button/Button.styles'
import { CalendarStyles } from '../Calendar/Calendar.styles'
import { CardStyles } from '../Card/Card.styles' import { CardStyles } from '../Card/Card.styles'
import { CardBodyStyles } from '../CardBody/CardBody.styles' import { CardBodyStyles } from '../CardBody/CardBody.styles'
import { CardHeaderStyles } from '../CardHeader/CardHeader.styles' import { CardHeaderStyles } from '../CardHeader/CardHeader.styles'
@ -12,6 +13,8 @@ import { CheckboxStyles } from '../Checkbox/Checkbox.styles'
import { CheckboxGroupStyles } from '../CheckboxGroup/CheckboxGroup.styles' import { CheckboxGroupStyles } from '../CheckboxGroup/CheckboxGroup.styles'
import { CollapseStyles } from '../Collapse/Collapse.styles' import { CollapseStyles } from '../Collapse/Collapse.styles'
import { CollapseHeaderStyles } from '../CollapseHeader/CollapseHeader.styles' import { CollapseHeaderStyles } from '../CollapseHeader/CollapseHeader.styles'
import { DateFieldStyles } from '../DateField/DateField.styles'
import { DatePickerStyles } from '../DatePicker/DatePicker.styles'
import { DropdownStyles } from '../Dropdown/Dropdown.styles' import { DropdownStyles } from '../Dropdown/Dropdown.styles'
import { DropdownItemStyles } from '../DropdownItem/DropdownItem.styles' import { DropdownItemStyles } from '../DropdownItem/DropdownItem.styles'
import { IconButtonStyles } from '../IconButton/IconButton.styles' import { IconButtonStyles } from '../IconButton/IconButton.styles'

View File

@ -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',
}

View File

@ -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)

View File

@ -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: '',
}

View File

@ -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;
}
`

View File

@ -0,0 +1,114 @@
import {
OnDatesChangeProps,
START_DATE,
useDatepicker,
} from '@datepicker-react/hooks'
import clsx from 'clsx'
import React, { useEffect, useRef, useState } from 'react'
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
handleDateFieldChange: (data: Date) => void
handleRef: React.RefObject<HTMLElement>
}
export const Calendar: React.FC<CalendarProps> & {
classes: typeof calendarClasses
} = ({
open,
handleRef,
value = null,
handleDateFieldChange,
children,
...props
}) => {
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,
})
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

View File

@ -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>
)
}

View File

@ -0,0 +1,99 @@
import { FirstDayOfWeek, useMonth } from '@datepicker-react/hooks'
import clsx from 'clsx'
import { NavigateBeforeIcon, NavigateNextIcon } from '../Icons'
import { Typography } from '../Typography'
import { calendarClasses } from './Calendar.classes'
import { Day } from './Day'
export type MonthProps = {
year: number
month: number
firstDayOfWeek: FirstDayOfWeek
goToPreviousMonths: () => void
goToNextMonths: () => void
}
export const Month = ({
year,
month,
firstDayOfWeek,
goToPreviousMonths,
goToNextMonths,
}: MonthProps) => {
const { days, weekdayLabels, monthLabel } = useMonth({
year,
month,
firstDayOfWeek,
})
const renderOtherDays = (idx: number, firstDate: Date) => {
const date = new Date(firstDate)
date.setDate(date.getDate() + idx)
return date.getDate()
}
return (
<div>
<div className={calendarClasses.header}>
<button
className={clsx(calendarClasses.button)}
type="button"
onClick={goToPreviousMonths}
>
<NavigateBeforeIcon color="primary" />
</button>
<Typography variant="label1">{monthLabel}</Typography>
<button
className={clsx(calendarClasses.button)}
type="button"
onClick={goToNextMonths}
>
<NavigateNextIcon color="primary" />
</button>
</div>
<div className={clsx(calendarClasses.grid)}>
{weekdayLabels.map((dayLabel, idx) => (
<Typography
key={idx}
variant="label2"
style={{ textAlign: 'center' }}
>
{dayLabel[0]}
</Typography>
))}
</div>
<div className={clsx(calendarClasses.grid)}>
{days.map((ele, idx) =>
typeof ele !== 'number' ? (
<Day date={ele.date} day={ele.dayLabel} key={ele.dayLabel} />
) : (
<button
disabled
key={`prev-${idx}`}
className={clsx(calendarClasses.day, calendarClasses.dayDisabled)}
>
<Typography variant="label2">
{renderOtherDays(
idx - days.filter((day) => day === 0).length,
days[days.lastIndexOf(0) + 1].date,
)}
</Typography>
</button>
),
)}
{days.length % 7 !== 0 &&
new Array(7 - (days.length % 7)).fill(null).map((ele, idx) => (
<button
disabled
key={`after-${ele}`}
className={clsx(calendarClasses.day, calendarClasses.dayDisabled)}
>
<Typography variant="label2">
{renderOtherDays(idx, days[days.lastIndexOf(0) + 1].date)}
</Typography>
</button>
))}
</div>
</div>
)
}

View File

@ -0,0 +1 @@
export * from './Calendar'

View File

@ -0,0 +1,16 @@
export const dateFieldClasses = {
root: `lsd-date-field`,
inputContainer: `lsd-date-field-input-container`,
input: `lsd-date-field-input-container__input`,
icon: `lsd-date-field-input-container__icon`,
iconButton: `lsd-date-field-input-container__icon-button`,
supportingText: 'lsd-date-field__supporting-text',
disabled: `lsd-date-field--disabled`,
error: 'lsd-date-field--error',
large: `lsd-date-field--large`,
medium: `lsd-date-field--medium`,
}

View File

@ -0,0 +1,30 @@
import { Meta, Story } from '@storybook/react'
import { DateField, DateFieldProps } from './DateField'
export default {
title: 'DateField',
component: DateField,
argTypes: {
size: {
type: {
name: 'enum',
value: ['medium', 'large'],
},
defaultValue: 'large',
},
},
} as Meta
export const Root: Story<DateFieldProps> = ({ ...args }) => {
return <DateField {...args} />
}
Root.args = {
size: 'large',
supportingText: 'Supporting text',
disabled: false,
error: false,
errorIcon: false,
clearButton: true,
onChange: undefined,
}

View File

@ -0,0 +1,79 @@
import { css } from '@emotion/react'
import { dateFieldClasses } from './DateField.classes'
export const DateFieldStyles = css`
.${dateFieldClasses.root} {
width: auto;
border-bottom: 1px solid rgb(var(--lsd-border-primary));
box-sizing: border-box;
}
.${dateFieldClasses.inputContainer} {
display: flex;
align-items: center;
justify-content: space-between;
}
.${dateFieldClasses.disabled} {
opacity: 0.34;
}
.${dateFieldClasses.input} {
border: none;
outline: none;
font-size: 14px;
color: rgb(var(--lsd-text-primary));
background: none;
width: 100%;
}
.${dateFieldClasses.input}::-webkit-inner-spin-button,
.${dateFieldClasses.input}::-webkit-calendar-picker-indicator {
display: none;
-webkit-appearance: none;
}
.${dateFieldClasses.input}:hover {
outline: none;
}
.${dateFieldClasses.input}:focus::-webkit-datetime-edit {
color: rgb(var(--lsd-text-primary));
opacity: 0.3;
}
.${dateFieldClasses.error}
.${dateFieldClasses.input}::-webkit-datetime-edit-fields-wrapper {
text-decoration: line-through;
}
.${dateFieldClasses.supportingText} {
width: fit-content;
margin-top: 20px;
}
.${dateFieldClasses.large} {
width: 208px;
height: 40px;
padding: 10px 14px;
}
.${dateFieldClasses.medium} {
width: 188px;
height: 32px;
padding: 6px 12px;
}
.${dateFieldClasses.iconButton} {
padding: 0;
width: auto;
height: auto;
margin: 0;
border: 0;
svg {
width: 100%;
height: auto;
}
}
`

View File

@ -0,0 +1,110 @@
import clsx from 'clsx'
import React, { useRef } from 'react'
import { useInput } from '../../utils/useInput'
import { IconButton } from '../IconButton'
import { CloseIcon, ErrorIcon } from '../Icons'
import { Typography } from '../Typography'
import { dateFieldClasses } from './DateField.classes'
export type DateFieldProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
'onChange' | 'value'
> &
Pick<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
size?: 'large' | 'medium'
error?: boolean
errorIcon?: boolean
clearButton?: boolean
disabled?: boolean
supportingText?: string
value?: string
defaultValue?: string
placeholder?: string
icon?: React.ReactNode
onIconClick?: () => void
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
}
export const DateField: React.FC<DateFieldProps> & {
classes: typeof dateFieldClasses
} = ({
size = 'large',
error = false,
errorIcon = false,
clearButton,
supportingText,
children,
value,
placeholder,
defaultValue,
disabled,
onChange,
icon,
onIconClick,
inputProps = {},
...props
}) => {
const ref = useRef<HTMLInputElement>(null)
const input = useInput({ defaultValue, value, onChange, ref })
const onCancel = () => input.setValue('')
return (
<div
aria-disabled={disabled ? 'true' : 'false'}
{...props}
className={clsx(
props.className,
dateFieldClasses.root,
dateFieldClasses[size],
disabled && dateFieldClasses.disabled,
error && dateFieldClasses.error,
)}
>
<div className={dateFieldClasses.inputContainer}>
<input
type="date"
placeholder={placeholder}
{...inputProps}
ref={ref}
value={input.value}
onChange={input.onChange}
className={clsx(inputProps.className, dateFieldClasses.input)}
/>
{icon ? (
<IconButton
disabled={disabled}
className={dateFieldClasses.iconButton}
onClick={() => !disabled && onIconClick && onIconClick()}
>
{icon}
</IconButton>
) : error && errorIcon ? (
<ErrorIcon color="primary" className={dateFieldClasses.icon} />
) : clearButton && input.filled ? (
<IconButton
disabled={disabled}
onClick={() => !disabled && onCancel()}
aria-label="clear"
className={dateFieldClasses.iconButton}
>
<CloseIcon color="primary" className={dateFieldClasses.icon} />
</IconButton>
) : null}
</div>
{supportingText && (
<div className={clsx(dateFieldClasses.supportingText)}>
<Typography
variant={size === 'large' ? 'label1' : 'label2'}
component="p"
>
{supportingText}
</Typography>
</div>
)}
{children}
</div>
)
}
DateField.classes = dateFieldClasses

View File

@ -0,0 +1 @@
export * from './DateField'

View File

@ -0,0 +1,4 @@
export const datePickerClasses = {
root: `lsd-date-picker`,
withCalendar: `lsd-date-picker--with-calendar`,
}

View File

@ -0,0 +1,32 @@
import { Meta, Story } from '@storybook/react'
import { DatePicker, DatePickerProps } from './DatePicker'
export default {
title: 'DatePicker',
component: DatePicker,
argTypes: {
size: {
type: {
name: 'enum',
value: ['medium', 'large'],
},
defaultValue: 'large',
},
},
} as Meta
export const Root: Story<DatePickerProps> = ({ ...args }) => {
return <DatePicker {...args}>DatePicker</DatePicker>
}
Root.args = {
size: 'large',
supportingText: 'Supporting text',
disabled: false,
error: false,
value: '',
onChange: undefined,
errorIcon: false,
clearButton: true,
withCalendar: true,
}

View File

@ -0,0 +1,13 @@
import { css } from '@emotion/react'
import { calendarClasses } from '../Calendar/Calendar.classes'
import { datePickerClasses } from './DatePicker.classes'
export const DatePickerStyles = css`
.${datePickerClasses.root} {
width: fit-content;
}
#lsd-presentation .${calendarClasses.root} {
border-top: none;
}
`

View File

@ -0,0 +1,76 @@
import clsx from 'clsx'
import React, { useRef, useState } from 'react'
import { Calendar } from '../Calendar'
import { DateField } from '../DateField'
import { CalendarIcon } from '../Icons'
import { Portal } from '../PortalProvider/Portal'
import { datePickerClasses } from './DatePicker.classes'
export type DatePickerProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
'onChange' | 'value'
> &
Pick<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
size?: 'large' | 'medium'
error?: boolean
errorIcon?: boolean
clearButton?: boolean
disabled?: boolean
withCalendar?: boolean
supportingText?: string
value?: string
onChange?: (value: string) => void
defaultValue?: string
placeholder?: string
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
}
export const DatePicker: React.FC<DatePickerProps> & {
classes: typeof datePickerClasses
} = ({ value, onChange, withCalendar = true, ...props }) => {
const ref = useRef<HTMLDivElement>(null)
const [openCalendar, setOpenCalendar] = useState(false)
const [date, setDate] = useState<string>(value || '')
const handleDateFieldChange = (date: any) => {
const offset = new Date(date).getTimezoneOffset()
const formattedDate = new Date(date.getTime() - offset * 60 * 1000)
const value = formattedDate.toISOString().split('T')[0]
setDate(value)
setOpenCalendar(false)
onChange && onChange(value)
}
return (
<div
ref={ref}
className={clsx(
props.className,
datePickerClasses.root,
withCalendar && datePickerClasses.withCalendar,
)}
>
<DateField
icon={withCalendar && <CalendarIcon color="primary" />}
onIconClick={() => setOpenCalendar((prev) => !prev)}
value={date}
onChange={(data) => setDate(data.target.value)}
style={{ width: '310px' }}
{...props}
>
<Portal id="calendar">
{withCalendar && (
<Calendar
handleDateFieldChange={handleDateFieldChange}
open={openCalendar}
handleRef={ref}
value={date}
/>
)}
</Portal>
</DateField>
</div>
)
}
DatePicker.classes = datePickerClasses

View File

@ -0,0 +1 @@
export * from './DatePicker'

View File

@ -0,0 +1,24 @@
import { LsdIcon } from '../LsdIcon'
export const CalendarIcon = LsdIcon(
(props) => (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.0833 2.33332H10.5V1.16666H9.33333V2.33332H4.66667V1.16666H3.5V2.33332H2.91667C2.26917 2.33332 1.75 2.85832 1.75 3.49999V11.6667C1.75 12.3083 2.26917 12.8333 2.91667 12.8333H11.0833C11.725 12.8333 12.25 12.3083 12.25 11.6667V3.49999C12.25 2.85832 11.725 2.33332 11.0833 2.33332ZM11.0833 11.6667H2.91667V5.24999H11.0833V11.6667ZM3.79167 7.58332C3.79167 6.77832 4.445 6.12499 5.25 6.12499C6.055 6.12499 6.70833 6.77832 6.70833 7.58332C6.70833 8.38832 6.055 9.04166 5.25 9.04166C4.445 9.04166 3.79167 8.38832 3.79167 7.58332Z"
fill="black"
/>
</svg>
),
{
filled: true,
},
)

View File

@ -0,0 +1 @@
export * from './CalendarIcon'

View File

@ -19,4 +19,5 @@ export * from './SearchIcon'
export * from './PickIcon' export * from './PickIcon'
export * from './RadioButtonIcon' export * from './RadioButtonIcon'
export * from './RadioButtonFilledIcon' export * from './RadioButtonFilledIcon'
export * from './CalendarIcon'
export * from './RemoveIcon' export * from './RemoveIcon'

View File

@ -1344,6 +1344,13 @@
dependencies: dependencies:
"@jridgewell/trace-mapping" "0.3.9" "@jridgewell/trace-mapping" "0.3.9"
"@datepicker-react/hooks@^2.8.4":
version "2.8.4"
resolved "https://registry.yarnpkg.com/@datepicker-react/hooks/-/hooks-2.8.4.tgz#6e07aa98bf21b90b7c88fb35919cca6eb08f2c31"
integrity sha512-qaYJKK5sOSdqcL/OnCtyv3/Q6fRRljfeAyl5ISTPgEO0CM5xZzkGmTx40+6wvqjH5lEZH4ysS95nPyLwZS2tlw==
dependencies:
date-fns "^2.14.0"
"@design-systems/utils@2.12.0": "@design-systems/utils@2.12.0":
version "2.12.0" version "2.12.0"
resolved "https://registry.npmjs.org/@design-systems/utils/-/utils-2.12.0.tgz" resolved "https://registry.npmjs.org/@design-systems/utils/-/utils-2.12.0.tgz"
@ -6789,6 +6796,11 @@ dargs@^7.0.0:
resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz"
integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==
date-fns@^2.14.0:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
dateformat@^3.0.0: dateformat@^3.0.0:
version "3.0.3" version "3.0.3"
resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz" resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz"