mirror of https://github.com/acid-info/lsd.git
feat: adds year selection dropdown and other minor improvements
This commit is contained in:
parent
69a46e613b
commit
e36753b3f4
|
@ -8,14 +8,18 @@ export const calendarClasses = {
|
|||
header: 'lsd-calendar-header',
|
||||
weekDay: 'lsd-calendar__week_day',
|
||||
button: 'lsd-calendar__button',
|
||||
row: 'lsd-calendar__row',
|
||||
changeYear: 'lsd-calendar__change-year',
|
||||
changeYearButton: 'lsd-calendar__change-year__button',
|
||||
changeYearActive: 'lsd-calendar__change-year--active',
|
||||
changeYearIconContainer: 'lsd-calendar__change-year-icon-container',
|
||||
|
||||
year: 'lsd-calendar-year',
|
||||
month: 'lsd-calendar-month',
|
||||
day: 'lsd-calendar-day',
|
||||
|
||||
yearAndIcon: 'lsd-calendar__year-and-icon',
|
||||
|
||||
monthAndYear: 'lsd-calendar__month-and-year',
|
||||
|
||||
dayContainer: 'lsd-calendar-day__container',
|
||||
dayRange: 'lsd-calendar-day--range',
|
||||
daySelected: 'lsd-calendar-day--selected',
|
||||
|
@ -33,4 +37,6 @@ export const calendarClasses = {
|
|||
|
||||
nextMonthButton: 'lsd-calendar__next-month-button',
|
||||
previousMonthButton: 'lsd-calendar__previous-month-button',
|
||||
|
||||
yearDropdown: 'lsd-calendar__year-dropdown',
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ export type CalendarContextType = {
|
|||
onDateSelect: (date: Date) => void
|
||||
goToPreviousMonths: () => void
|
||||
goToNextMonths: () => void
|
||||
goToNextYear: () => void
|
||||
goToPreviousYear: () => void
|
||||
changeYearMode: boolean
|
||||
setChangeYearMode: (value: boolean) => void
|
||||
goToDate: (date: Date) => void
|
||||
}
|
||||
|
||||
export const CalendarContext = React.createContext<CalendarContextType>(
|
||||
|
|
|
@ -45,36 +45,52 @@ export const CalendarStyles = css`
|
|||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.${calendarClasses.row} {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.${calendarClasses.changeYear} {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
padding: 2px 6px;
|
||||
padding: 2px 0xp 2px 8px;
|
||||
gap: 6px;
|
||||
|
||||
/* The transparent border prevents slight layout shifts when the hover border shows up. */
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.${calendarClasses.changeYearButton} {
|
||||
.${calendarClasses.changeYearActive} {
|
||||
.${calendarClasses.year} {
|
||||
padding: 5px 0px 5px 10px;
|
||||
}
|
||||
|
||||
.${calendarClasses.yearAndIcon} {
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
}
|
||||
|
||||
.${calendarClasses.changeYearIconContainer} {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.${calendarClasses.changeYearIconContainer} {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.${calendarClasses.month} {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.${calendarClasses.monthAndYear} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.${calendarClasses.dayContainer} {
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
|
@ -180,4 +196,45 @@ export const CalendarStyles = css`
|
|||
.${calendarClasses.monthTable} {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.${calendarClasses.yearDropdown} {
|
||||
box-sizing: border-box;
|
||||
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
z-index: 1;
|
||||
|
||||
.${calendarClasses.year} {
|
||||
border-bottom: 1px solid rgb(var(--lsd-border-primary));
|
||||
}
|
||||
}
|
||||
|
||||
.${calendarClasses.year} {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
align-items: center;
|
||||
|
||||
background: rgb(var(--lsd-surface-primary));
|
||||
|
||||
:hover {
|
||||
text-decoration: underline;
|
||||
padding: 5px 0px 5px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.${calendarClasses.yearAndIcon}:hover {
|
||||
border: 1px solid rgb(var(--lsd-border-primary));
|
||||
|
||||
.${calendarClasses.changeYearIconContainer} {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -70,6 +70,7 @@ export const Calendar: React.FC<CalendarProps> & {
|
|||
const [endDate, setEndDate] = useState<Date | null>(
|
||||
endDateProp ? safeConvertDate(endDateProp, minDate, maxDate).date : null,
|
||||
)
|
||||
const [changeYearMode, setChangeYearMode] = useState(false)
|
||||
|
||||
useClickAway(ref, (event) => {
|
||||
if (!open) return
|
||||
|
@ -108,8 +109,7 @@ export const Calendar: React.FC<CalendarProps> & {
|
|||
onDateFocus,
|
||||
goToPreviousMonths,
|
||||
goToNextMonths,
|
||||
goToNextYear,
|
||||
goToPreviousYear,
|
||||
goToDate,
|
||||
} = useDatepicker({
|
||||
startDate,
|
||||
endDate,
|
||||
|
@ -183,8 +183,9 @@ export const Calendar: React.FC<CalendarProps> & {
|
|||
onDateHover,
|
||||
goToPreviousMonths,
|
||||
goToNextMonths,
|
||||
goToNextYear,
|
||||
goToPreviousYear,
|
||||
goToDate,
|
||||
changeYearMode,
|
||||
setChangeYearMode,
|
||||
}}
|
||||
>
|
||||
<TooltipBase
|
||||
|
|
|
@ -17,8 +17,9 @@ export const Month = ({
|
|||
month,
|
||||
firstDayOfWeek,
|
||||
}: MonthProps) => {
|
||||
const sizeContext = useCalendarContext()
|
||||
const size = sizeContext?.size ?? _size
|
||||
const calendarContext = useCalendarContext()
|
||||
const size = calendarContext?.size ?? _size
|
||||
|
||||
const { days, weekdayLabels, monthLabel } = useMonth({
|
||||
year,
|
||||
month,
|
||||
|
@ -27,7 +28,7 @@ export const Month = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<MonthHeader monthLabel={monthLabel} size={size} />
|
||||
<MonthHeader monthLabel={monthLabel} monthNumber={month} size={size} />
|
||||
<table className={calendarClasses.monthTable}>
|
||||
<thead>
|
||||
<WeekdayHeader weekdayLabels={weekdayLabels} />
|
||||
|
|
|
@ -40,50 +40,91 @@ export const CalendarNavigationButton: FC<CalendarNavigationButtonProps> = ({
|
|||
|
||||
type YearControlProps = {
|
||||
year: string
|
||||
monthNumber: number
|
||||
size: 'large' | 'medium' | 'small'
|
||||
}
|
||||
|
||||
export const YearControl: FC<YearControlProps> = ({ year, size }) => {
|
||||
export const YearControl: FC<YearControlProps> = ({
|
||||
year,
|
||||
monthNumber,
|
||||
size,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { goToNextYear, goToPreviousYear } = useCalendarContext()
|
||||
const { goToDate, changeYearMode, setChangeYearMode } = useCalendarContext()
|
||||
|
||||
useClickAway(ref, () => {
|
||||
setChangeYearMode(false)
|
||||
})
|
||||
|
||||
const handleYearClick = (selectedYear: number) => {
|
||||
const selectedDate = new Date(selectedYear, monthNumber, 1)
|
||||
goToDate(selectedDate)
|
||||
setChangeYearMode(false)
|
||||
}
|
||||
|
||||
const yearsList = Array.from({ length: 101 }, (_, i) => 1950 + i)
|
||||
|
||||
return (
|
||||
<div ref={ref} className={calendarClasses.changeYear}>
|
||||
<Typography
|
||||
component="span"
|
||||
className={calendarClasses.year}
|
||||
variant={size === 'large' ? 'label1' : 'label2'}
|
||||
>
|
||||
{year}
|
||||
</Typography>
|
||||
<div className={calendarClasses.row}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
goToNextYear()
|
||||
}}
|
||||
className={calendarClasses.changeYearButton}
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
calendarClasses.changeYear,
|
||||
changeYearMode && calendarClasses.changeYearActive,
|
||||
)}
|
||||
onClick={() => {
|
||||
setChangeYearMode(!changeYearMode)
|
||||
}}
|
||||
>
|
||||
<div className={clsx(calendarClasses.year, calendarClasses.yearAndIcon)}>
|
||||
<Typography
|
||||
component="span"
|
||||
variant={size === 'large' ? 'label1' : 'label2'}
|
||||
>
|
||||
<ArrowUpIcon color="primary" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
goToPreviousYear()
|
||||
}}
|
||||
className={calendarClasses.changeYearButton}
|
||||
>
|
||||
<ArrowDownIcon color="primary" />
|
||||
</IconButton>
|
||||
{year}
|
||||
</Typography>
|
||||
|
||||
<div className={calendarClasses.changeYearIconContainer}>
|
||||
{changeYearMode ? (
|
||||
<ArrowUpIcon color="primary" />
|
||||
) : (
|
||||
<ArrowDownIcon color="primary" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{changeYearMode && (
|
||||
<div className={calendarClasses.yearDropdown}>
|
||||
{yearsList.map((year) => (
|
||||
<div
|
||||
key={year}
|
||||
className={calendarClasses.year}
|
||||
onClick={() => handleYearClick(year)}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
variant={size === 'large' ? 'label1' : 'label2'}
|
||||
>
|
||||
{year}
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type MonthHeaderProps = {
|
||||
monthLabel: string
|
||||
monthNumber: number
|
||||
size: 'large' | 'medium' | 'small'
|
||||
}
|
||||
|
||||
export const MonthHeader: FC<MonthHeaderProps> = ({ monthLabel, size }) => {
|
||||
export const MonthHeader: FC<MonthHeaderProps> = ({
|
||||
monthLabel,
|
||||
monthNumber,
|
||||
size,
|
||||
}) => {
|
||||
const { goToPreviousMonths, goToNextMonths } = useCalendarContext()
|
||||
const [month, year] = monthLabel.split(' ')
|
||||
|
||||
|
@ -94,7 +135,7 @@ export const MonthHeader: FC<MonthHeaderProps> = ({ monthLabel, size }) => {
|
|||
onClick={goToPreviousMonths}
|
||||
className={calendarClasses.previousMonthButton}
|
||||
/>
|
||||
<div className={calendarClasses.row}>
|
||||
<div className={calendarClasses.monthAndYear}>
|
||||
<Typography
|
||||
className={calendarClasses.month}
|
||||
component="span"
|
||||
|
@ -103,7 +144,7 @@ export const MonthHeader: FC<MonthHeaderProps> = ({ monthLabel, size }) => {
|
|||
{month}
|
||||
</Typography>
|
||||
|
||||
<YearControl year={year} size={size} />
|
||||
<YearControl year={year} monthNumber={monthNumber} size={size} />
|
||||
</div>
|
||||
<CalendarNavigationButton
|
||||
direction="next"
|
||||
|
|
|
@ -10,14 +10,12 @@ export default {
|
|||
name: 'enum',
|
||||
value: ['small', 'medium', 'large'],
|
||||
},
|
||||
defaultValue: 'large',
|
||||
},
|
||||
variant: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: ['outlined', 'outlined-bottom'],
|
||||
},
|
||||
defaultValue: 'large',
|
||||
},
|
||||
},
|
||||
} as Meta
|
||||
|
|
|
@ -77,9 +77,6 @@ export const DateRangePicker: React.FC<DateRangePickerProps> & {
|
|||
const onStartInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!endInput.value || isValidRange(e.target.value, endInput.value)) {
|
||||
startInput.onChange(e)
|
||||
|
||||
// Switch to endDate calendar when the startDate is set.
|
||||
setCalendarType('endDate')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,9 +86,13 @@ export const DateRangePicker: React.FC<DateRangePickerProps> & {
|
|||
}
|
||||
}
|
||||
|
||||
const calendarStartDateChange = (date: Date) =>
|
||||
const calendarStartDateChange = (date: Date) => {
|
||||
startInput.setValue(dateToISODateString(removeDateTimezoneOffset(date)))
|
||||
|
||||
// Switch to endDate calendar when the startDate is set.
|
||||
setCalendarType('endDate')
|
||||
}
|
||||
|
||||
const calendarEndDateChange = (date: Date) =>
|
||||
endInput.setValue(dateToISODateString(removeDateTimezoneOffset(date)))
|
||||
|
||||
|
@ -143,7 +144,7 @@ export const DateRangePicker: React.FC<DateRangePickerProps> & {
|
|||
icon={withCalendar && <CalendarIcon color="primary" />}
|
||||
// The DateField component is only controlled when the value prop is provided OR the calendar is open.
|
||||
value={
|
||||
isStartValueControlled || isStartDateCalendar
|
||||
isStartValueControlled || isCalendarOpen
|
||||
? startInput.value
|
||||
: undefined
|
||||
}
|
||||
|
@ -164,9 +165,7 @@ export const DateRangePicker: React.FC<DateRangePickerProps> & {
|
|||
icon={withCalendar && <CalendarIcon color="primary" />}
|
||||
// The DateField component is only controlled when the value prop is provided OR the calendar is open.
|
||||
value={
|
||||
isEndValueControlled || isEndDateCalendar
|
||||
? endInput.value
|
||||
: undefined
|
||||
isEndValueControlled || isCalendarOpen ? endInput.value : undefined
|
||||
}
|
||||
onIconClick={() =>
|
||||
setCalendarType((currentCalendarType) =>
|
||||
|
@ -217,6 +216,7 @@ export const DateRangePicker: React.FC<DateRangePickerProps> & {
|
|||
calendarType,
|
||||
size,
|
||||
)}
|
||||
size={size}
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
|
|
Loading…
Reference in New Issue