diff --git a/packages/lsd-react/src/components/Calendar/Calendar.classes.ts b/packages/lsd-react/src/components/Calendar/Calendar.classes.ts index 026cf75..ea33f38 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.classes.ts +++ b/packages/lsd-react/src/components/Calendar/Calendar.classes.ts @@ -3,12 +3,20 @@ export const calendarClasses = { container: 'lsd-calendar-container', open: 'lsd-calendar--open', + disabled: 'lsd-calendar--disabled', header: 'lsd-calendar-header', grid: 'lsd-calendar-body', + weekDay: 'lsd-calendar__weekDay', button: 'lsd-calendar-button', + row: 'lsd-calendar__row', + changeYear: 'lsd-calendar__change-year', + changeYearButton: 'lsd-calendar__change-year__button', + year: 'lsd-calendar-year', + month: 'lsd-calendar-month', day: 'lsd-calendar-day', daySelected: 'lsd-calendar-day--selected', - dayDisabled: 'lsd-calendar-day--diabled', + dayDisabled: 'lsd-calendar-day--disabled', + today: 'lsd-calendar-today', } diff --git a/packages/lsd-react/src/components/Calendar/Calendar.context.ts b/packages/lsd-react/src/components/Calendar/Calendar.context.ts index 0a8cb3e..97a2163 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.context.ts +++ b/packages/lsd-react/src/components/Calendar/Calendar.context.ts @@ -2,6 +2,7 @@ import React from 'react' export type CalendarContextType = { focusedDate: Date | null + size?: 'large' | 'medium' isDateFocused: (date: Date) => boolean isDateSelected: (date: Date) => boolean isDateHovered: (date: Date) => boolean diff --git a/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx b/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx index 8bf8f27..7e45d38 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx +++ b/packages/lsd-react/src/components/Calendar/Calendar.stories.tsx @@ -1,13 +1,22 @@ import { Meta, Story } from '@storybook/react' -import { useRef, useState } from 'react' +import { useRef } from 'react' import { Calendar, CalendarProps } from './Calendar' export default { title: 'Calendar', component: Calendar, + argTypes: { + size: { + type: { + name: 'enum', + value: ['medium', 'large'], + }, + defaultValue: 'large', + }, + }, } as Meta -export const Root: Story = (arg) => { +export const Uncontrolled: Story = (arg) => { const ref = useRef(null) return ( @@ -24,7 +33,31 @@ export const Root: Story = (arg) => { ) } -Root.args = { +export const Controlled: Story = (arg) => { + const ref = useRef(null) + + return ( +
+ console.log(date?.toDateString())} + open={true} + handleRef={ref} + > + Calendar + +
+ ) +} + +Uncontrolled.args = { value: undefined, onChange: undefined, + size: 'large', +} + +Controlled.args = { + value: '2023-01-01', + onChange: undefined, + size: 'large', } diff --git a/packages/lsd-react/src/components/Calendar/Calendar.styles.ts b/packages/lsd-react/src/components/Calendar/Calendar.styles.ts index 904d0c9..f483f03 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.styles.ts +++ b/packages/lsd-react/src/components/Calendar/Calendar.styles.ts @@ -39,6 +39,48 @@ export const CalendarStyles = css` display: grid; grid-template-columns: repeat(7, 1fr); justify-content: center; + cursor: pointer; + } + + .${calendarClasses.weekDay} { + text-align: center; + aspect-ratio: 1 / 1; + display: flex; + justify-content: center; + align-items: center; + } + + .${calendarClasses.row} { + display: flex; + justify-content: center; + align-items: center; + } + + .${calendarClasses.changeYear} { + display: flex; + justify-content: center; + align-items: center; + border: 1px solid rgb(var(--lsd-border-primary)); + padding: 2px 6px; + gap: 6px; + } + + .${calendarClasses.changeYearButton} { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + background: transparent; + } + + .${calendarClasses.month} { + margin-right: 8px; + } + + .${calendarClasses.year}:hover { + cursor: pointer; + text-decoration: underline; + text-decoration-color: rgb(var(--lsd-border-primary)); } .${calendarClasses.day} { @@ -46,6 +88,17 @@ export const CalendarStyles = css` border: none; background: transparent; aspect-ratio: 1 / 1; + position: relative; + } + + .${calendarClasses.day}:hover { + cursor: pointer; + text-decoration: underline; + text-decoration-color: rgb(var(--lsd-border-primary)); + } + + .${calendarClasses.day} label:hover { + cursor: pointer; } .${calendarClasses.daySelected} { @@ -57,6 +110,27 @@ export const CalendarStyles = css` cursor: default; } + .${calendarClasses.today} { + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: 0; + } + + .${calendarClasses.disabled} { + pointer-events: none; + border: 1px solid rgba(var(--lsd-border-primary), 0.3); + label { + opacity: 0.3; + } + .${calendarClasses.button} { + opacity: 0.3; + } + .${calendarClasses.daySelected} { + opacity: 0.3; + } + } + .${calendarClasses.button} { border: 1px solid rgb(var(--lsd-border-primary)); cursor: pointer; diff --git a/packages/lsd-react/src/components/Calendar/Calendar.tsx b/packages/lsd-react/src/components/Calendar/Calendar.tsx index f6c782e..15d0acb 100644 --- a/packages/lsd-react/src/components/Calendar/Calendar.tsx +++ b/packages/lsd-react/src/components/Calendar/Calendar.tsx @@ -8,15 +8,19 @@ import React, { useEffect, useRef, useState } from 'react' import { calendarClasses } from './Calendar.classes' import { CalendarContext } from './Calendar.context' import { Month } from './Month' +import { useClickAway } from 'react-use' export type CalendarProps = Omit< React.HTMLAttributes, 'label' > & { open?: boolean + disabled?: boolean value?: string handleDateFieldChange: (data: Date) => void handleRef: React.RefObject + size?: 'large' | 'medium' + onClose?: () => void } export const Calendar: React.FC & { @@ -25,12 +29,22 @@ export const Calendar: React.FC & { open, handleRef, value = null, + size = 'large', + disabled = false, handleDateFieldChange, + onClose, children, ...props }) => { + const ref = useRef(null) const [style, setStyle] = useState({}) + useClickAway(ref, (event) => { + if (!open || event.composedPath().includes(handleRef.current!)) return + + onClose && onClose() + }) + const handleDateChange = (data: OnDatesChangeProps) => { handleDateFieldChange(data.startDate ?? new Date()) onDateFocus(data.startDate ?? new Date()) @@ -75,6 +89,7 @@ export const Calendar: React.FC & { return ( & { props.className, calendarClasses.root, open && calendarClasses.open, + disabled && calendarClasses.disabled, )} + ref={ref} style={{ ...style, ...(props.style ?? {}) }} >
- {activeMonths.map((month) => ( + {activeMonths.map((month, idx) => ( diff --git a/packages/lsd-react/src/components/Calendar/ControlledCalendar.stories.tsx b/packages/lsd-react/src/components/Calendar/ControlledCalendar.stories.tsx deleted file mode 100644 index 34874fe..0000000 --- a/packages/lsd-react/src/components/Calendar/ControlledCalendar.stories.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Meta, Story } from '@storybook/react' -import { useRef } from 'react' -import { Calendar, CalendarProps } from './Calendar' - -export default { - title: 'ControlledCalendar', - component: Calendar, -} as Meta - -export const Root: Story = (arg) => { - const ref = useRef(null) - - return ( -
- console.log(date?.toDateString())} - open={true} - handleRef={ref} - > - Calendar - -
- ) -} - -Root.args = { - value: '2023-01-01', - onChange: undefined, -} diff --git a/packages/lsd-react/src/components/Calendar/Day.tsx b/packages/lsd-react/src/components/Calendar/Day.tsx index 64a1434..895ecbb 100644 --- a/packages/lsd-react/src/components/Calendar/Day.tsx +++ b/packages/lsd-react/src/components/Calendar/Day.tsx @@ -8,9 +8,10 @@ import { Typography } from '../Typography' export type DayProps = { day?: string date: Date + disabled?: boolean } -export const Day = ({ day, date }: DayProps) => { +export const Day = ({ day, date, disabled = false }: DayProps) => { const dayRef = useRef(null) const { focusedDate, @@ -52,10 +53,17 @@ export const Day = ({ day, date }: DayProps) => { ref={dayRef} className={clsx( calendarClasses.day, - isDateFocused(date) && calendarClasses.daySelected, + !disabled && isDateFocused(date) && calendarClasses.daySelected, + disabled && calendarClasses.dayDisabled, )} > {parseInt(day, 10)} + {new Date(date).setHours(0, 0, 0, 0) === + new Date().setHours(0, 0, 0, 0) && ( + + ■ + + )} ) } diff --git a/packages/lsd-react/src/components/Calendar/Month.tsx b/packages/lsd-react/src/components/Calendar/Month.tsx index 922eed5..93cb99d 100644 --- a/packages/lsd-react/src/components/Calendar/Month.tsx +++ b/packages/lsd-react/src/components/Calendar/Month.tsx @@ -1,8 +1,16 @@ import { FirstDayOfWeek, useMonth } from '@datepicker-react/hooks' import clsx from 'clsx' -import { NavigateBeforeIcon, NavigateNextIcon } from '../Icons' +import { useRef, useState } from 'react' +import { useClickAway } from 'react-use' +import { + ArrowDownIcon, + ArrowUpIcon, + NavigateBeforeIcon, + NavigateNextIcon, +} from '../Icons' import { Typography } from '../Typography' import { calendarClasses } from './Calendar.classes' +import { useCalendarContext } from './Calendar.context' import { Day } from './Day' export type MonthProps = { @@ -11,27 +19,40 @@ export type MonthProps = { firstDayOfWeek: FirstDayOfWeek goToPreviousMonths: () => void goToNextMonths: () => void + size?: 'large' | 'medium' | 'small' } export const Month = ({ - year, + size: _size = 'large', + year: _year, month, firstDayOfWeek, goToPreviousMonths, goToNextMonths, }: MonthProps) => { + const sizeContext = useCalendarContext() + const size = sizeContext?.size ?? _size + const [year, setYear] = useState(_year) const { days, weekdayLabels, monthLabel } = useMonth({ year, month, firstDayOfWeek, }) + const [changeYear, setChangeYear] = useState(false) + const ref = useRef(null) - const renderOtherDays = (idx: number, firstDate: Date) => { - const date = new Date(firstDate) + const renderOtherDays = (idx: number, referenceDate: Date) => { + const date = new Date(referenceDate) date.setDate(date.getDate() + idx) return date.getDate() } + useClickAway(ref, (event) => { + if (!changeYear) return + + setChangeYear(false) + }) + return (
@@ -42,7 +63,44 @@ export const Month = ({ > - {monthLabel} +
+ + {monthLabel.split(' ')[0]} + + {changeYear ? ( +
+ + {monthLabel.split(' ')[1]} + +
+ setYear(year + 1)} + className={calendarClasses.changeYearButton} + color="primary" + /> + setYear(year - 1)} + className={calendarClasses.changeYearButton} + color="primary" + /> +
+
+ ) : ( + setChangeYear(true)} + variant={size === 'large' ? 'label1' : 'label2'} + className={calendarClasses.year} + > + {monthLabel.split(' ')[1]} + + )} +
+ {days.length == 28 && + new Array(7) + .fill(null) + .map((_, idx) => ( + + ))} {days.map((ele, idx) => typeof ele !== 'number' ? ( ) : ( - + disabled={true} + /> ), )} - {days.length % 7 !== 0 && - new Array(7 - (days.length % 7)).fill(null).map((ele, idx) => ( - + {new Array( + days.length % 7 !== 0 && days.length <= 35 + ? 7 - (days.length % 7) + 7 + : 7 - (days.length % 7), + ) + .fill(null) + .map((ele, idx) => ( + ))}
diff --git a/packages/lsd-react/src/components/DateField/ControlledDateField.stories.tsx b/packages/lsd-react/src/components/DateField/ControlledDateField.stories.tsx deleted file mode 100644 index 943160a..0000000 --- a/packages/lsd-react/src/components/DateField/ControlledDateField.stories.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Meta, Story } from '@storybook/react' -import { DateField, DateFieldProps } from './DateField' - -export default { - title: 'ControlledDateField', - component: DateField, - argTypes: { - size: { - type: { - name: 'enum', - value: ['medium', 'large'], - }, - defaultValue: 'large', - }, - }, -} as Meta - -export const Root: Story = ({ ...args }) => { - return -} - -Root.args = { - size: 'large', - supportingText: 'Supporting text', - disabled: false, - value: '2023-01-01', - onChange: undefined, - error: false, - errorIcon: false, - clearButton: true, -} diff --git a/packages/lsd-react/src/components/DateField/DateField.stories.tsx b/packages/lsd-react/src/components/DateField/DateField.stories.tsx index 15b82d1..0f6af24 100644 --- a/packages/lsd-react/src/components/DateField/DateField.stories.tsx +++ b/packages/lsd-react/src/components/DateField/DateField.stories.tsx @@ -15,11 +15,15 @@ export default { }, } as Meta -export const Root: Story = ({ ...args }) => { +export const Uncontrolled: Story = ({ ...args }) => { return } -Root.args = { +export const Controlled: Story = ({ ...args }) => { + return +} + +Uncontrolled.args = { size: 'large', supportingText: 'Supporting text', disabled: false, @@ -29,3 +33,14 @@ Root.args = { errorIcon: false, clearButton: true, } + +Controlled.args = { + size: 'large', + supportingText: 'Supporting text', + disabled: false, + value: '2023-01-01', + onChange: undefined, + error: false, + errorIcon: false, + clearButton: true, +} diff --git a/packages/lsd-react/src/components/DatePicker/ControlledDatePicker.stories.tsx b/packages/lsd-react/src/components/DatePicker/ControlledDatePicker.stories.tsx deleted file mode 100644 index 91a7b80..0000000 --- a/packages/lsd-react/src/components/DatePicker/ControlledDatePicker.stories.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Meta, Story } from '@storybook/react' -import { DatePicker, DatePickerProps } from './DatePicker' - -export default { - title: 'ControlledDatePicker', - component: DatePicker, - argTypes: { - size: { - type: { - name: 'enum', - value: ['medium', 'large'], - }, - defaultValue: 'large', - }, - }, -} as Meta - -export const Root: Story = ({ ...args }) => { - return DatePicker -} - -Root.args = { - size: 'large', - supportingText: 'Supporting text', - disabled: false, - error: false, - value: '2023-01-01', - onChange: undefined, - errorIcon: false, - clearButton: true, - withCalendar: true, -} diff --git a/packages/lsd-react/src/components/DatePicker/DatePicker.stories.tsx b/packages/lsd-react/src/components/DatePicker/DatePicker.stories.tsx index 976c3a3..9815917 100644 --- a/packages/lsd-react/src/components/DatePicker/DatePicker.stories.tsx +++ b/packages/lsd-react/src/components/DatePicker/DatePicker.stories.tsx @@ -15,12 +15,15 @@ export default { }, } as Meta -export const Root: Story = ({ ...args }) => { +export const Uncontrolled: Story = ({ ...args }) => { return DatePicker } -Root.args = { - size: 'large', +export const Contolled: Story = ({ ...args }) => { + return DatePicker +} + +Uncontrolled.args = { supportingText: 'Supporting text', disabled: false, error: false, @@ -29,4 +32,17 @@ Root.args = { errorIcon: false, clearButton: true, withCalendar: true, + size: 'large', +} + +Contolled.args = { + supportingText: 'Supporting text', + disabled: false, + error: false, + value: '2023-01-01', + onChange: undefined, + errorIcon: false, + clearButton: true, + withCalendar: true, + size: 'large', } diff --git a/packages/lsd-react/src/components/DatePicker/DatePicker.tsx b/packages/lsd-react/src/components/DatePicker/DatePicker.tsx index 447f609..bfe3bd9 100644 --- a/packages/lsd-react/src/components/DatePicker/DatePicker.tsx +++ b/packages/lsd-react/src/components/DatePicker/DatePicker.tsx @@ -11,7 +11,6 @@ export type DatePickerProps = Omit< 'onChange' | 'value' > & Pick, 'onChange'> & { - size?: 'large' | 'medium' error?: boolean errorIcon?: boolean clearButton?: boolean @@ -22,6 +21,7 @@ export type DatePickerProps = Omit< onChange?: (value: string) => void defaultValue?: string placeholder?: string + size?: 'large' | 'medium' inputProps?: React.InputHTMLAttributes } @@ -36,9 +36,12 @@ export const DatePicker: React.FC & { 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) + + if (typeof onChange === 'function') { + onChange(value) + } else { + setDate(value) + } } return ( @@ -63,8 +66,10 @@ export const DatePicker: React.FC & { setOpenCalendar(false)} handleRef={ref} value={date} + disabled={props.disabled} /> )}