Skip to content

Commit a364682

Browse files
committed
Improved calendar grid touch on mobile
1 parent d23290e commit a364682

3 files changed

Lines changed: 75 additions & 36 deletions

File tree

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"react": "^19.2.0",
1414
"react-dom": "^19.2.0",
1515
"react-scripts": "5.0.1",
16+
"react-swipeable": "^7.0.2",
1617
"web-vitals": "^2.1.4"
1718
},
1819
"scripts": {

src/App.js

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import startOfMonth from 'date-fns/startOfMonth';
88
import endOfMonth from 'date-fns/endOfMonth';
99
import format from 'date-fns/format';
1010
import isToday from 'date-fns/isToday';
11+
import subWeeks from 'date-fns/subWeeks';
12+
import addWeeks from 'date-fns/addWeeks';
13+
import subMonths from 'date-fns/subMonths';
14+
import addMonths from 'date-fns/addMonths';
15+
import { useSwipeable } from 'react-swipeable';
1116
import { Award, Star, Medal } from 'lucide-react';
1217
import { motion } from 'framer-motion';
1318

@@ -95,6 +100,7 @@ function App() {
95100
const [newlyUnlocked, setNewlyUnlocked] = useState([]);
96101
const [modalOpen, setModalOpen] = useState(false);
97102
const [selectedHabit, setSelectedHabit] = useState(null);
103+
const [currentDate, setCurrentDate] = useState(new Date());
98104

99105
const filteredHabits = selectedCategory === 'All'
100106
? habits
@@ -105,12 +111,12 @@ function App() {
105111

106112
useEffect(() => {
107113
if (calendarMode === 'weekly') {
108-
const start = startOfWeek(new Date(), { weekStartsOn: 0 }); // Sunday
109-
const end = endOfWeek(new Date(), { weekStartsOn: 0 });
114+
const start = startOfWeek(currentDate, { weekStartsOn: 0 }); // Sunday
115+
const end = endOfWeek(currentDate, { weekStartsOn: 0 });
110116
setCurrentWeekDays(eachDayOfInterval({ start, end }));
111117
} else if (calendarMode === 'monthly') {
112-
const start = startOfMonth(new Date());
113-
const end = endOfMonth(new Date());
118+
const start = startOfMonth(currentDate);
119+
const end = endOfMonth(currentDate);
114120
const daysInMonth = eachDayOfInterval({ start, end });
115121

116122
// Pad days to start on Sunday and end on Saturday
@@ -125,7 +131,7 @@ function App() {
125131

126132
setCurrentWeekDays(paddedDays);
127133
}
128-
}, [calendarMode]);
134+
}, [calendarMode, currentDate]);
129135

130136
// Toggle calendar mode handler
131137
const toggleCalendarMode = () => {
@@ -136,6 +142,11 @@ function App() {
136142
});
137143
};
138144

145+
const goToPrevWeek = () => setCurrentDate(prev => subWeeks(prev, 1));
146+
const goToNextWeek = () => setCurrentDate(prev => addWeeks(prev, 1));
147+
const goToPrevMonth = () => setCurrentDate(prev => subMonths(prev, 1));
148+
const goToNextMonth = () => setCurrentDate(prev => addMonths(prev, 1));
149+
139150
// Check if habit is completed on a given day
140151
const isCompletedOn = (habit, day) => {
141152
return habit.completions.some(date => format(date, 'yyyy-MM-dd') === format(day, 'yyyy-MM-dd'));
@@ -168,6 +179,16 @@ function App() {
168179
toggleCompletion(habitId, day);
169180
};
170181

182+
const weeklySwipeHandlers = useSwipeable({
183+
onSwipedLeft: goToNextWeek,
184+
onSwipedRight: goToPrevWeek,
185+
});
186+
187+
const monthlySwipeHandlers = useSwipeable({
188+
onSwipedLeft: goToNextMonth,
189+
onSwipedRight: goToPrevMonth,
190+
});
191+
171192
return (
172193
<div className="App">
173194
<header className="App-header">
@@ -246,6 +267,13 @@ function App() {
246267
</button>
247268
</div>
248269

270+
{calendarMode !== '90day' && (
271+
<div className="mb-4 flex gap-2">
272+
<button onClick={calendarMode === 'weekly' ? goToPrevWeek : goToPrevMonth} className="px-4 py-2 bg-gray-600 text-white rounded">Prev</button>
273+
<button onClick={calendarMode === 'weekly' ? goToNextWeek : goToNextMonth} className="px-4 py-2 bg-gray-600 text-white rounded">Next</button>
274+
</div>
275+
)}
276+
249277
{/* Display current habits */}
250278
<div style={{ marginTop: '20px', textAlign: 'left' }}>
251279
<h3>Current Habits:</h3>
@@ -254,37 +282,37 @@ function App() {
254282
) : (
255283
<>
256284
{calendarMode === 'weekly' ? (
257-
<div className="overflow-x-auto">
258-
<div className="grid grid-cols-7 gap-2 text-center font-semibold mb-2 min-w-max">
259-
{currentWeekDays.map(day => (
260-
<div key={day ? day.toISOString() : 'empty-' + Math.random()}>{day ? format(day, 'EEE') : ''}</div>
285+
<div className="overflow-x-auto" {...weeklySwipeHandlers}>
286+
<div className="grid grid-cols-7 gap-2 text-center font-semibold mb-2 min-w-max">
287+
{currentWeekDays.map(day => (
288+
<div key={day ? day.toISOString() : 'empty-' + Math.random()}>{day ? format(day, 'EEE') : ''}</div>
289+
))}
290+
</div>
291+
{filteredHabits.map(habit => (
292+
<div key={habit.id} className="mb-4">
293+
<div className="font-bold">{habit.name}</div>
294+
<div className="grid grid-cols-7 gap-2 text-center min-w-max">
295+
{currentWeekDays.map(day => {
296+
if (!day) return <div key={'empty-' + Math.random()} className="min-h-11 min-w-11 rounded bg-gray-100 pointer-events-none p-1" />;
297+
const done = isCompletedOn(habit, day);
298+
const today = isToday(day);
299+
return (
300+
<div
301+
key={day.toISOString()}
302+
className={`min-h-11 min-w-11 rounded cursor-pointer p-1 ${
303+
done ? 'bg-green-500' : today ? 'bg-blue-500' : 'bg-gray-200'
304+
}`}
305+
title={format(day, 'yyyy-MM-dd')}
306+
onClick={() => handleCompletionClick(habit.id, day)}
307+
/>
308+
);
309+
})}
310+
</div>
311+
</div>
261312
))}
262313
</div>
263-
{filteredHabits.map(habit => (
264-
<div key={habit.id} className="mb-4">
265-
<div className="font-bold">{habit.name}</div>
266-
<div className="grid grid-cols-7 gap-2 text-center min-w-max">
267-
{currentWeekDays.map(day => {
268-
if (!day) return <div key={'empty-' + Math.random()} className="h-8 w-8 rounded bg-gray-100 pointer-events-none" />;
269-
const done = isCompletedOn(habit, day);
270-
const today = isToday(day);
271-
return (
272-
<div
273-
key={day.toISOString()}
274-
className={`h-8 w-8 rounded cursor-pointer ${
275-
done ? 'bg-green-500' : today ? 'bg-blue-500' : 'bg-gray-200'
276-
}`}
277-
title={format(day, 'yyyy-MM-dd')}
278-
onClick={() => handleCompletionClick(habit.id, day)}
279-
/>
280-
);
281-
})}
282-
</div>
283-
</div>
284-
))}
285-
</div>
286314
) : calendarMode === 'monthly' ? (
287-
<div className="overflow-x-auto">
315+
<div className="overflow-x-auto" {...monthlySwipeHandlers}>
288316
<div className="grid grid-cols-7 gap-2 text-center font-semibold mb-2 min-w-max">
289317
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (
290318
<div key={day}>{day}</div>
@@ -296,14 +324,14 @@ function App() {
296324
<div className="grid grid-cols-7 gap-2 text-center min-w-max">
297325
{currentWeekDays.map((day, idx) => {
298326
if (day === null) {
299-
return <div key={idx} className="h-6 w-6 rounded bg-gray-100 pointer-events-none" />;
327+
return <div key={idx} className="min-h-11 min-w-11 rounded bg-gray-100 pointer-events-none p-1" />;
300328
}
301329
const done = isCompletedOn(habit, day);
302330
const today = isToday(day);
303331
return (
304332
<div
305333
key={idx}
306-
className={`h-6 w-6 rounded cursor-pointer ${
334+
className={`min-h-11 min-w-11 rounded cursor-pointer p-1 ${
307335
done ? 'bg-green-500' : today ? 'bg-blue-500' : 'bg-gray-200'
308336
}`}
309337
title={format(day, 'yyyy-MM-dd')}
@@ -316,7 +344,7 @@ function App() {
316344
))}
317345
</div>
318346
) : (
319-
<div className="habit-list">
347+
<div className="habit-list overflow-y-auto">
320348
{filteredHabits.map((habit) => (
321349
<div key={habit.id} className="habit-card p-3 sm:p-4 border rounded mb-4 w-full">
322350
<div className="font-bold text-sm sm:text-base">{habit.name} - {habit.category}</div>

0 commit comments

Comments
 (0)