Skip to content

Commit cd34b04

Browse files
authored
feat: improve colors, navigation states and calendar header ui (#148)
1 parent b7b8a51 commit cd34b04

18 files changed

+318
-171
lines changed

src/components/calendar-month/MonthCalendar.tsx

+35-20
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import { capitalizeFirstLetter } from '@utils';
77
import clsx from 'clsx';
88
import React from 'react';
99
import { type AriaButtonProps, useCalendar, useLocale } from 'react-aria';
10+
import { useLocation } from 'react-router-dom';
1011
import { useCalendarState } from 'react-stately';
1112

12-
import CalendarGrid from './CalendarGrid';
13-
import CalendarHeader from './CalendarHeader';
13+
import MonthCalendarGrid from './MonthCalendarGrid';
14+
import MonthCalendarHeader from './MonthCalendarHeader';
1415
import NoteDialog from './NoteDialog';
1516
import OccurrenceDialog from './OccurrenceDialog';
1617

@@ -26,12 +27,12 @@ const createCalendar = (identifier: string) => {
2627
const MonthCalendar = () => {
2728
const { onRangeChange } = useOccurrencesStore();
2829
const { locale } = useLocale();
29-
const state = useCalendarState({
30+
const calendarState = useCalendarState({
3031
locale,
3132
createCalendar,
3233
});
3334
const { calendarProps, prevButtonProps, nextButtonProps, title } =
34-
useCalendar({}, state);
35+
useCalendar({}, calendarState);
3536
const {
3637
isOpen: isNoteDialogOpen,
3738
onOpen: openNoteDialog,
@@ -43,17 +44,36 @@ const MonthCalendar = () => {
4344
onClose: closeOccurrenceDialog,
4445
} = useDisclosure();
4546
const [activeDate, setActiveDate] = React.useState<Date | null>(null);
47+
const { state: locationState } = useLocation();
48+
49+
const setFocusedDate = React.useCallback(
50+
(year: number, month: number, day: number) => {
51+
const nextFocusedDate = new CalendarDate(year, month, day);
52+
calendarState.setFocusedDate(nextFocusedDate);
53+
},
54+
[calendarState]
55+
);
56+
57+
React.useEffect(() => {
58+
if (!locationState) {
59+
return;
60+
}
61+
62+
const { year, month } = locationState;
63+
64+
setFocusedDate(year, month, 1);
65+
}, [locationState, setFocusedDate]);
4666

4767
React.useEffect(() => {
4868
onRangeChange(
4969
generateCalendarRange(
50-
state.visibleRange.start.year,
51-
state.visibleRange.start.month
70+
calendarState.visibleRange.start.year,
71+
calendarState.visibleRange.start.month
5272
)
5373
);
5474
}, [
55-
state.visibleRange.start.year,
56-
state.visibleRange.start.month,
75+
calendarState.visibleRange.start.year,
76+
calendarState.visibleRange.start.month,
5777
onRangeChange,
5878
]);
5979

@@ -63,18 +83,13 @@ const MonthCalendar = () => {
6383
`${activeMonthLabel.slice(0, 3)} ${activeYear} | Habitrack Calendar`
6484
);
6585

66-
const setFocusedDate = (year: number, month: number, day: number) => {
67-
const nextFocusedDate = new CalendarDate(year, month, day);
68-
state.setFocusedDate(nextFocusedDate);
69-
};
70-
7186
const navigateToMonth = (month: number) => {
72-
const { year, day } = state.focusedDate;
87+
const { year, day } = calendarState.focusedDate;
7388
setFocusedDate(year, month, day);
7489
};
7590

7691
const navigateToYear = (year: number) => {
77-
const { month, day } = state.focusedDate;
92+
const { month, day } = calendarState.focusedDate;
7893
setFocusedDate(year, month, day);
7994
};
8095

@@ -133,21 +148,21 @@ const MonthCalendar = () => {
133148
return (
134149
<>
135150
<div {...calendarProps} className={calendarContainerClassName}>
136-
<CalendarHeader
151+
<MonthCalendarHeader
137152
activeMonthLabel={capitalizeFirstLetter(activeMonthLabel)}
138153
activeYear={activeYear}
139154
prevButtonProps={transformButtonProps(prevButtonProps)}
140155
nextButtonProps={transformButtonProps(nextButtonProps)}
141-
onNavigateBack={state.focusPreviousPage}
142-
onNavigateForward={state.focusNextPage}
156+
onNavigateBack={calendarState.focusPreviousPage}
157+
onNavigateForward={calendarState.focusNextPage}
143158
onNavigateToMonth={navigateToMonth}
144159
onNavigateToYear={navigateToYear}
145160
onResetFocusedDate={resetFocusedDate}
146161
/>
147-
<CalendarGrid
162+
<MonthCalendarGrid
148163
activeMonthLabel={capitalizeFirstLetter(activeMonthLabel)}
149164
activeYear={Number(activeYear)}
150-
state={state}
165+
state={calendarState}
151166
onAddOccurrence={handleOccurrenceModalOpen}
152167
onAddNote={handleNoteModalOpen}
153168
/>

src/components/calendar-month/CalendarCell.tsx src/components/calendar-month/MonthCalendarCell.tsx

+12-7
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type CalendarCellProps = {
4545
position: CellPosition;
4646
};
4747

48-
const CalendarCell = ({
48+
const MonthCalendarCell = ({
4949
dateNumber,
5050
monthNumber,
5151
fullYear,
@@ -64,6 +64,7 @@ const CalendarCell = ({
6464
const screenSize = useScreenSize();
6565
const cellDate = new Date(fullYear, monthNumber - 1, dateNumber);
6666
const isTodayCell = isToday(cellDate);
67+
const isFutureCell = isFuture(cellDate);
6768
const date = format(cellDate, 'yyyy-MM-dd');
6869
const isDesktop = screenSize >= 1024;
6970

@@ -97,7 +98,10 @@ const CalendarCell = ({
9798
}
9899
}
99100

100-
if (isMobile || e?.currentTarget instanceof HTMLButtonElement) {
101+
if (
102+
!isFutureCell &&
103+
(isMobile || e?.currentTarget instanceof HTMLButtonElement)
104+
) {
101105
return onAddOccurrence(dateNumber, monthNumber, fullYear);
102106
}
103107
},
@@ -113,6 +117,7 @@ const CalendarCell = ({
113117
user,
114118
isMobile,
115119
isTodayCell,
120+
isFutureCell,
116121
]
117122
);
118123

@@ -161,7 +166,7 @@ const CalendarCell = ({
161166
position === 'bottom-left' && 'rounded-bl-md',
162167
position === 'bottom-right' && 'rounded-br-md',
163168
isTodayCell &&
164-
'bg-background-200 hover:bg-background-300 dark:bg-background-800 dark:hover:bg-background-700'
169+
'bg-background-100 hover:bg-background-300 dark:bg-background-700 dark:hover:bg-background-700'
165170
);
166171

167172
const cellHeaderClassName = clsx(
@@ -182,13 +187,13 @@ const CalendarCell = ({
182187
<div className="flex items-center justify-between gap-2">
183188
{rangeStatus === 'in-range' && !isMobile && (
184189
<div className="flex items-center gap-1">
185-
{!isFuture(cellDate) && (
190+
{!isFutureCell && (
186191
<Tooltip content="Log habit" closeDelay={0}>
187192
<Button
188193
className="h-5 min-w-fit px-2 opacity-100 transition-opacity group-hover/cell:opacity-100 md:opacity-0 lg:h-6 lg:px-4"
189194
radius="sm"
190195
onClick={handleAddOccurrenceClick}
191-
color="primary"
196+
color="secondary"
192197
isDisabled={fetchingOccurrences || !user}
193198
>
194199
<CalendarPlus weight="bold" size={isDesktop ? 18 : 14} />
@@ -206,7 +211,7 @@ const CalendarCell = ({
206211
)}
207212
radius="sm"
208213
onClick={handleAddNoteClick}
209-
color={hasNote ? 'success' : 'primary'}
214+
color={hasNote ? 'success' : 'secondary'}
210215
isDisabled={fetchingNotes || !user}
211216
>
212217
{hasNote ? (
@@ -251,4 +256,4 @@ const CalendarCell = ({
251256
);
252257
};
253258

254-
export default CalendarCell;
259+
export default MonthCalendarCell;

src/components/calendar-month/CalendarGrid.tsx src/components/calendar-month/MonthCalendarGrid.tsx

+22-21
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { getYearWeekNumberFromMonthWeek } from '@helpers';
22
import { type CalendarDate, getWeeksInMonth } from '@internationalized/date';
33
import { Button } from '@nextui-org/react';
4+
import { isTruthy } from '@utils';
45
import clsx from 'clsx';
56
import { AnimatePresence, motion } from 'framer-motion';
67
import React from 'react';
78
import { useCalendarGrid, useLocale } from 'react-aria';
8-
import { useNavigate } from 'react-router-dom';
9+
import { Link, useLocation } from 'react-router-dom';
910
import { type CalendarState } from 'react-stately';
1011

11-
import type { CellPosition, CellRangeStatus } from './CalendarCell';
12-
import CalendarCell from './CalendarCell';
12+
import type { CellPosition, CellRangeStatus } from './MonthCalendarCell';
13+
import MonthCalendarCell from './MonthCalendarCell';
1314

1415
type CalendarGridProps = {
1516
state: CalendarState;
@@ -25,31 +26,19 @@ type CalendarGridProps = {
2526

2627
const WEEK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
2728

28-
const CalendarGrid = ({
29+
const MonthCalendarGrid = ({
2930
state,
3031
onAddNote,
3132
onAddOccurrence,
3233
activeMonthLabel,
3334
activeYear,
3435
}: CalendarGridProps) => {
35-
const navigate = useNavigate();
3636
const { gridProps } = useCalendarGrid({}, state);
3737
const { locale } = useLocale();
3838
const weeksInMonthCount = getWeeksInMonth(state.visibleRange.start, locale);
3939
const weekIndexes = [...new Array(weeksInMonthCount).keys()];
4040
const { month: activeMonth } = state.visibleRange.start;
41-
42-
const handleWeekClick = (startDate: CalendarDate | null) => {
43-
if (!startDate) {
44-
return;
45-
}
46-
47-
navigate('/calendar/week', {
48-
state: {
49-
startDate: new Date(startDate.year, startDate.month - 1, startDate.day),
50-
},
51-
});
52-
};
41+
const { pathname } = useLocation();
5342

5443
const getCellPosition = (
5544
weekIndex: number,
@@ -103,19 +92,31 @@ const CalendarGrid = ({
10392
activeYear,
10493
weekIndex
10594
);
106-
const dates = state.getDatesInWeek(weekIndex);
95+
const dates = state.getDatesInWeek(weekIndex).filter(isTruthy);
96+
const [firstDate] = dates;
10797

10898
return (
10999
<div
110100
key={weekIndex}
111101
className="group relative flex items-end gap-1 md:gap-2"
112102
>
113103
<Button
104+
as={Link}
114105
className={clsx(
115106
'absolute -left-[24px] bottom-0 h-[107px] w-[20px] min-w-fit p-0 md:-left-[48px] md:w-[40px]'
116107
)}
117108
variant="light"
118-
onClick={() => handleWeekClick(dates[0])}
109+
to="/calendar/week"
110+
state={{
111+
prevPathname: pathname,
112+
month: activeMonth,
113+
year: activeYear,
114+
startDate: new Date(
115+
firstDate.year,
116+
firstDate.month - 1,
117+
firstDate.day
118+
),
119+
}}
119120
>
120121
{week}
121122
</Button>
@@ -150,7 +151,7 @@ const CalendarGrid = ({
150151
const position = getCellPosition(weekIndex, dayIndex);
151152

152153
return (
153-
<CalendarCell
154+
<MonthCalendarCell
154155
key={cellKey}
155156
dateNumber={day}
156157
monthNumber={month}
@@ -174,4 +175,4 @@ const CalendarGrid = ({
174175
);
175176
};
176177

177-
export default CalendarGrid;
178+
export default MonthCalendarGrid;

src/components/calendar-month/CalendarHeader.test.tsx src/components/calendar-month/MonthCalendarHeader.test.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { render } from '@testing-library/react';
22
import React from 'react';
33

4-
import CalendarHeader, { type CalendarHeaderProps } from './CalendarHeader';
4+
import MonthCalendarHeader, {
5+
type MonthCalendarHeaderProps,
6+
} from './MonthCalendarHeader';
57

6-
describe(CalendarHeader.name, () => {
7-
const props: CalendarHeaderProps = {
8+
describe(MonthCalendarHeader.name, () => {
9+
const props: MonthCalendarHeaderProps = {
810
activeMonthLabel: 'January',
911
activeYear: '2022',
1012
prevButtonProps: {
@@ -23,13 +25,13 @@ describe(CalendarHeader.name, () => {
2325
};
2426

2527
it.skip('should render month and year', () => {
26-
const { getByText } = render(<CalendarHeader {...props} />);
28+
const { getByText } = render(<MonthCalendarHeader {...props} />);
2729
expect(getByText('January 2022')).toBeInTheDocument();
2830
});
2931

3032
it.skip('should disable previous button', () => {
3133
const { getByRole } = render(
32-
<CalendarHeader
34+
<MonthCalendarHeader
3335
{...props}
3436
prevButtonProps={{ ...props.prevButtonProps, disabled: true }}
3537
/>
@@ -39,7 +41,7 @@ describe(CalendarHeader.name, () => {
3941

4042
it.skip('should disable next button', () => {
4143
const { getByRole } = render(
42-
<CalendarHeader
44+
<MonthCalendarHeader
4345
{...props}
4446
nextButtonProps={{ ...props.nextButtonProps, disabled: true }}
4547
/>
@@ -48,13 +50,13 @@ describe(CalendarHeader.name, () => {
4850
});
4951

5052
it.skip('should call onNavigateBack', () => {
51-
const { getByRole } = render(<CalendarHeader {...props} />);
53+
const { getByRole } = render(<MonthCalendarHeader {...props} />);
5254
getByRole('navigate-back').click();
5355
expect(props.onNavigateBack).toHaveBeenCalled();
5456
});
5557

5658
it.skip('should call onNavigateForward', () => {
57-
const { getByRole } = render(<CalendarHeader {...props} />);
59+
const { getByRole } = render(<MonthCalendarHeader {...props} />);
5860
getByRole('navigate-forward').click();
5961
expect(props.onNavigateForward).toHaveBeenCalled();
6062
});

0 commit comments

Comments
 (0)