react-datepicker-bkrdmrcioglu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # React DatePicker
2
+
3
+ Modern, özelleştirilebilir ve kullanıcı dostu React DatePicker komponenti. Next.js ve Tailwind CSS ile geliştirilmiştir.
4
+
5
+ ## 🚀 Özellikler
6
+
7
+ - ✅ Modern ve şık tasarım
8
+ - ✅ **Dark Mode** - Tam karanlık mod desteği
9
+ - ✅ **11 Dil Desteği** - English, Türkçe, Deutsch, Français, Español, Italiano, Português, Русский, 日本語, 中文, العربية
10
+ - ✅ Tarih seçimi
11
+ - ✅ Tarih ve saat seçimi
12
+ - ✅ Tarih aralığı seçimi (DateRangePicker)
13
+ - ✅ Min/Max tarih kısıtlaması
14
+ - ✅ Devre dışı bırakılabilir
15
+ - ✅ TypeScript desteği
16
+ - ✅ Tamamen özelleştirilebilir
17
+ - ✅ Responsive tasarım
18
+
19
+ ## 📦 Kurulum
20
+
21
+ ### NPM ile Kurulum
22
+
23
+ ```bash
24
+ npm install react-datepicker-bkrdmrcioglu
25
+ # veya
26
+ yarn add react-datepicker-bkrdmrcioglu
27
+ # veya
28
+ pnpm add react-datepicker-bkrdmrcioglu
29
+ ```
30
+
31
+ ### Gereksinimler
32
+
33
+ Bu paket Tailwind CSS kullanır. Projenizde Tailwind CSS kurulu olmalıdır:
34
+
35
+ ```bash
36
+ npm install -D tailwindcss postcss autoprefixer
37
+ npx tailwindcss init -p
38
+ ```
39
+
40
+ `tailwind.config.js` dosyanıza şunu ekleyin:
41
+
42
+ ```js
43
+ module.exports = {
44
+ content: [
45
+ "./src/**/*.{js,jsx,ts,tsx}",
46
+ "./node_modules/react-datepicker-bkrdmrcioglu/**/*.{js,jsx,ts,tsx}"
47
+ ],
48
+ darkMode: 'class', // Dark mode için
49
+ // ... diğer ayarlarınız
50
+ }
51
+ ```
52
+
53
+ ## 🏃 Geliştirme (Bu Repo İçin)
54
+
55
+ ```bash
56
+ npm install
57
+ npm run dev
58
+ ```
59
+
60
+ Tarayıcınızda [http://localhost:3000](http://localhost:3000) adresini açın.
61
+
62
+ ## 📖 Kullanım
63
+
64
+ ### Temel Kullanım
65
+
66
+ ```tsx
67
+ import { DatePicker } from 'react-datepicker-bkrdmrcioglu';
68
+ import { useState } from 'react';
69
+
70
+ function MyComponent() {
71
+ const [date, setDate] = useState<Date | null>(null);
72
+
73
+ return (
74
+ <DatePicker
75
+ value={date}
76
+ onChange={setDate}
77
+ language="en" // Default: 'en'
78
+ />
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### Dil Desteği
84
+
85
+ ```tsx
86
+ import { DatePicker } from 'react-datepicker-bkrdmrcioglu';
87
+ import type { Language } from 'react-datepicker-bkrdmrcioglu';
88
+
89
+ // İngilizce (varsayılan)
90
+ <DatePicker language="en" />
91
+
92
+ // Türkçe
93
+ <DatePicker language="tr" />
94
+
95
+ // Almanca
96
+ <DatePicker language="de" />
97
+
98
+ // Fransızca
99
+ <DatePicker language="fr" />
100
+
101
+ // İspanyolca
102
+ <DatePicker language="es" />
103
+
104
+ // İtalyanca
105
+ <DatePicker language="it" />
106
+
107
+ // Portekizce
108
+ <DatePicker language="pt" />
109
+
110
+ // Rusça
111
+ <DatePicker language="ru" />
112
+
113
+ // Japonca
114
+ <DatePicker language="ja" />
115
+
116
+ // Çince
117
+ <DatePicker language="zh" />
118
+
119
+ // Arapça
120
+ <DatePicker language="ar" />
121
+ ```
122
+
123
+ ### Tarih ve Saat Seçimi
124
+
125
+ ```tsx
126
+ <DatePicker
127
+ value={date}
128
+ onChange={setDate}
129
+ showTime={true}
130
+ placeholder="Tarih ve saat seçin..."
131
+ />
132
+ ```
133
+
134
+ ### Min/Max Tarih Kısıtlaması
135
+
136
+ ```tsx
137
+ const minDate = new Date();
138
+ minDate.setDate(minDate.getDate() - 7);
139
+ const maxDate = new Date();
140
+ maxDate.setDate(maxDate.getDate() + 30);
141
+
142
+ <DatePicker
143
+ value={date}
144
+ onChange={setDate}
145
+ minDate={minDate}
146
+ maxDate={maxDate}
147
+ />
148
+ ```
149
+
150
+ ### Tarih Aralığı Seçimi
151
+
152
+ ```tsx
153
+ import { DateRangePicker } from 'react-datepicker-bkrdmrcioglu';
154
+ import { useState } from 'react';
155
+
156
+ function MyComponent() {
157
+ const [startDate, setStartDate] = useState<Date | null>(null);
158
+ const [endDate, setEndDate] = useState<Date | null>(null);
159
+
160
+ return (
161
+ <DateRangePicker
162
+ startDate={startDate}
163
+ endDate={endDate}
164
+ onChange={(start, end) => {
165
+ setStartDate(start);
166
+ setEndDate(end);
167
+ }}
168
+ />
169
+ );
170
+ }
171
+ ```
172
+
173
+ ## 🔧 API Referansı
174
+
175
+ ### DatePicker Props
176
+
177
+ | Prop | Tip | Varsayılan | Açıklama |
178
+ |------|-----|------------|----------|
179
+ | `value` | `Date \| null` | `undefined` | Seçili tarih |
180
+ | `onChange` | `(date: Date \| null) => void` | `undefined` | Tarih değiştiğinde çağrılır |
181
+ | `placeholder` | `string` | `"Tarih seçin..."` | Input placeholder metni |
182
+ | `minDate` | `Date` | `undefined` | Minimum seçilebilir tarih |
183
+ | `maxDate` | `Date` | `undefined` | Maksimum seçilebilir tarih |
184
+ | `disabled` | `boolean` | `false` | Devre dışı bırakma |
185
+ | `className` | `string` | `""` | Ek CSS sınıfları |
186
+ | `showTime` | `boolean` | `false` | Saat seçimi göster |
187
+ | `format` | `string` | `"DD/MM/YYYY"` | Tarih formatı |
188
+ | `language` | `Language` | `"en"` | Dil seçimi (en, tr, de, fr, es, it, pt, ru, ja, zh, ar) |
189
+
190
+ ### DateRangePicker Props
191
+
192
+ | Prop | Tip | Varsayılan | Açıklama |
193
+ |------|-----|------------|----------|
194
+ | `startDate` | `Date \| null` | `undefined` | Başlangıç tarihi |
195
+ | `endDate` | `Date \| null` | `undefined` | Bitiş tarihi |
196
+ | `onChange` | `(start: Date \| null, end: Date \| null) => void` | `undefined` | Tarih değiştiğinde çağrılır |
197
+ | `placeholder` | `string` | `"Tarih aralığı seçin..."` | Placeholder metni |
198
+ | `minDate` | `Date` | `undefined` | Minimum seçilebilir tarih |
199
+ | `maxDate` | `Date` | `undefined` | Maksimum seçilebilir tarih |
200
+ | `disabled` | `boolean` | `false` | Devre dışı bırakma |
201
+ | `className` | `string` | `""` | Ek CSS sınıfları |
202
+
203
+ ## 🎨 Özelleştirme
204
+
205
+ Komponentler Tailwind CSS kullanılarak oluşturulmuştur. Stilleri özelleştirmek için `components/DatePicker.tsx` dosyasındaki className'leri değiştirebilirsiniz.
206
+
207
+ ### Dark Mode
208
+
209
+ Dark mode otomatik olarak sistem tercihine göre ayarlanır veya `DarkModeToggle` komponenti ile manuel olarak kontrol edilebilir. Tüm komponentler dark mode'u destekler.
210
+
211
+ **Önemli:** Dark mode için HTML elementine `dark` class'ını eklemeniz gerekir:
212
+
213
+ ```tsx
214
+ import { DarkModeToggle } from 'react-datepicker-bkrdmrcioglu';
215
+
216
+ function MyComponent() {
217
+ return <DarkModeToggle />;
218
+ }
219
+ ```
220
+
221
+ Dark mode'u manuel olarak kontrol etmek için:
222
+
223
+ ```tsx
224
+ // Dark mode'u aç
225
+ document.documentElement.classList.add('dark');
226
+
227
+ // Dark mode'u kapat
228
+ document.documentElement.classList.remove('dark');
229
+ ```
230
+
231
+ ### Dil Seçici
232
+
233
+ Dil seçimi için `LanguageSelector` komponentini kullanabilirsiniz:
234
+
235
+ ```tsx
236
+ import { LanguageSelector, DatePicker } from 'react-datepicker-bkrdmrcioglu';
237
+ import type { Language } from 'react-datepicker-bkrdmrcioglu';
238
+ import { useState } from 'react';
239
+
240
+ function MyComponent() {
241
+ const [language, setLanguage] = useState<Language>('en');
242
+
243
+ return (
244
+ <>
245
+ <LanguageSelector value={language} onChange={setLanguage} />
246
+ <DatePicker language={language} />
247
+ </>
248
+ );
249
+ }
250
+ ```
251
+
252
+ ## 📝 Lisans
253
+
254
+ MIT License - Özgürce kullanabilirsiniz.
255
+
256
+ ## 🤝 Katkıda Bulunma
257
+
258
+ Katkılarınızı bekliyoruz! Lütfen bir issue açın veya pull request gönderin.
259
+
260
+ ## 📧 İletişim
261
+
262
+ Sorularınız için GitHub Issues kullanabilirsiniz.
263
+
264
+ ---
265
+
266
+ ⭐ Bu projeyi beğendiyseniz yıldız vermeyi unutmayın!
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+
5
+ export default function DarkModeToggle() {
6
+ const [isDark, setIsDark] = useState(false);
7
+
8
+ useEffect(() => {
9
+ // Check initial theme
10
+ const darkMode = localStorage.getItem('darkMode') === 'true' ||
11
+ (!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches);
12
+ setIsDark(darkMode);
13
+
14
+ if (darkMode) {
15
+ document.documentElement.classList.add('dark');
16
+ } else {
17
+ document.documentElement.classList.remove('dark');
18
+ }
19
+ }, []);
20
+
21
+ const toggleDarkMode = () => {
22
+ const newDarkMode = !isDark;
23
+ setIsDark(newDarkMode);
24
+ localStorage.setItem('darkMode', String(newDarkMode));
25
+
26
+ if (newDarkMode) {
27
+ document.documentElement.classList.add('dark');
28
+ } else {
29
+ document.documentElement.classList.remove('dark');
30
+ }
31
+ };
32
+
33
+ return (
34
+ <button
35
+ onClick={toggleDarkMode}
36
+ className="relative p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
37
+ aria-label="Toggle dark mode"
38
+ type="button"
39
+ >
40
+ {isDark ? (
41
+ <svg className="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
42
+ <path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd" />
43
+ </svg>
44
+ ) : (
45
+ <svg className="w-5 h-5 text-gray-700" fill="currentColor" viewBox="0 0 20 20">
46
+ <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
47
+ </svg>
48
+ )}
49
+ </button>
50
+ );
51
+ }
52
+
@@ -0,0 +1,350 @@
1
+ 'use client';
2
+
3
+ import { useState, useRef, useEffect } from 'react';
4
+ import { getTranslations, type Language } from '../lib/i18n';
5
+
6
+ export interface DatePickerProps {
7
+ value?: Date | null;
8
+ onChange?: (date: Date | null) => void;
9
+ placeholder?: string;
10
+ minDate?: Date;
11
+ maxDate?: Date;
12
+ disabled?: boolean;
13
+ className?: string;
14
+ showTime?: boolean;
15
+ format?: string;
16
+ language?: Language;
17
+ }
18
+
19
+ export default function DatePicker({
20
+ value,
21
+ onChange,
22
+ placeholder,
23
+ minDate,
24
+ maxDate,
25
+ disabled = false,
26
+ className = '',
27
+ showTime = false,
28
+ format = 'DD/MM/YYYY',
29
+ language = 'en'
30
+ }: DatePickerProps) {
31
+ const t = getTranslations(language);
32
+ const [isOpen, setIsOpen] = useState(false);
33
+ const [currentMonth, setCurrentMonth] = useState(new Date());
34
+ const [selectedDate, setSelectedDate] = useState<Date | null>(value || null);
35
+ const [time, setTime] = useState({ hours: 0, minutes: 0 });
36
+ const pickerRef = useRef<HTMLDivElement>(null);
37
+
38
+ useEffect(() => {
39
+ if (value) {
40
+ setSelectedDate(value);
41
+ setTime({
42
+ hours: value.getHours(),
43
+ minutes: value.getMinutes()
44
+ });
45
+ }
46
+ }, [value]);
47
+
48
+ useEffect(() => {
49
+ const handleClickOutside = (event: MouseEvent) => {
50
+ if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {
51
+ setIsOpen(false);
52
+ }
53
+ };
54
+
55
+ if (isOpen) {
56
+ document.addEventListener('mousedown', handleClickOutside);
57
+ }
58
+
59
+ return () => {
60
+ document.removeEventListener('mousedown', handleClickOutside);
61
+ };
62
+ }, [isOpen]);
63
+
64
+ const formatDate = (date: Date | null): string => {
65
+ if (!date) return '';
66
+
67
+ if (format === 'DD/MM/YYYY') {
68
+ const day = date.getDate().toString().padStart(2, '0');
69
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
70
+ const year = date.getFullYear();
71
+ const timeStr = showTime
72
+ ? ` ${time.hours.toString().padStart(2, '0')}:${time.minutes.toString().padStart(2, '0')}`
73
+ : '';
74
+ return `${day}/${month}/${year}${timeStr}`;
75
+ }
76
+
77
+ return date.toLocaleDateString(language === 'en' ? 'en-US' : language);
78
+ };
79
+
80
+ const getDaysInMonth = (date: Date): number => {
81
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
82
+ };
83
+
84
+ const getFirstDayOfMonth = (date: Date): number => {
85
+ const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
86
+ return firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1; // Monday = 0
87
+ };
88
+
89
+ const isDateDisabled = (date: Date): boolean => {
90
+ if (minDate && date < minDate) return true;
91
+ if (maxDate && date > maxDate) return true;
92
+ return false;
93
+ };
94
+
95
+ const isSameDay = (date1: Date, date2: Date): boolean => {
96
+ return (
97
+ date1.getDate() === date2.getDate() &&
98
+ date1.getMonth() === date2.getMonth() &&
99
+ date1.getFullYear() === date2.getFullYear()
100
+ );
101
+ };
102
+
103
+ const handleDateSelect = (day: number) => {
104
+ const newDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
105
+
106
+ if (showTime) {
107
+ newDate.setHours(time.hours, time.minutes);
108
+ }
109
+
110
+ if (!isDateDisabled(newDate)) {
111
+ setSelectedDate(newDate);
112
+ onChange?.(newDate);
113
+ if (!showTime) {
114
+ setIsOpen(false);
115
+ }
116
+ }
117
+ };
118
+
119
+ const handleTimeChange = (type: 'hours' | 'minutes', value: number) => {
120
+ const newTime = { ...time, [type]: value };
121
+ setTime(newTime);
122
+
123
+ if (selectedDate) {
124
+ const newDate = new Date(selectedDate);
125
+ newDate.setHours(newTime.hours, newTime.minutes);
126
+ setSelectedDate(newDate);
127
+ onChange?.(newDate);
128
+ }
129
+ };
130
+
131
+ const navigateMonth = (direction: 'prev' | 'next') => {
132
+ setCurrentMonth(prev => {
133
+ const newDate = new Date(prev);
134
+ if (direction === 'prev') {
135
+ newDate.setMonth(prev.getMonth() - 1);
136
+ } else {
137
+ newDate.setMonth(prev.getMonth() + 1);
138
+ }
139
+ return newDate;
140
+ });
141
+ };
142
+
143
+ const goToToday = () => {
144
+ const today = new Date();
145
+ setCurrentMonth(today);
146
+ if (!isDateDisabled(today)) {
147
+ setSelectedDate(today);
148
+ onChange?.(today);
149
+ }
150
+ };
151
+
152
+ const clearDate = () => {
153
+ setSelectedDate(null);
154
+ onChange?.(null);
155
+ };
156
+
157
+ const daysInMonth = getDaysInMonth(currentMonth);
158
+ const firstDay = getFirstDayOfMonth(currentMonth);
159
+ const days: (number | null)[] = [];
160
+
161
+ // Empty days
162
+ for (let i = 0; i < firstDay; i++) {
163
+ days.push(null);
164
+ }
165
+
166
+ // Month days
167
+ for (let day = 1; day <= daysInMonth; day++) {
168
+ days.push(day);
169
+ }
170
+
171
+ const defaultPlaceholder = showTime ? t.selectTime : t.selectDate;
172
+
173
+ return (
174
+ <div ref={pickerRef} className={`relative ${className}`}>
175
+ <div className="relative">
176
+ <input
177
+ type="text"
178
+ value={formatDate(selectedDate)}
179
+ placeholder={placeholder || defaultPlaceholder}
180
+ readOnly
181
+ disabled={disabled}
182
+ onClick={() => !disabled && setIsOpen(!isOpen)}
183
+ className={`
184
+ w-full px-4 py-2.5 rounded-lg border border-gray-300
185
+ bg-white text-gray-900 placeholder-gray-400
186
+ dark:bg-gray-800 dark:text-gray-100 dark:border-gray-600 dark:placeholder-gray-500
187
+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
188
+ dark:focus:ring-blue-400
189
+ disabled:bg-gray-100 disabled:cursor-not-allowed
190
+ dark:disabled:bg-gray-700 dark:disabled:text-gray-400
191
+ cursor-pointer transition-all
192
+ ${selectedDate ? 'font-medium' : ''}
193
+ `}
194
+ />
195
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
196
+ <svg
197
+ className="w-5 h-5 text-gray-400 dark:text-gray-500"
198
+ fill="none"
199
+ stroke="currentColor"
200
+ viewBox="0 0 24 24"
201
+ >
202
+ <path
203
+ strokeLinecap="round"
204
+ strokeLinejoin="round"
205
+ strokeWidth={2}
206
+ d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
207
+ />
208
+ </svg>
209
+ </div>
210
+ </div>
211
+
212
+ {isOpen && (
213
+ <div className="absolute z-50 mt-2 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 p-4 w-80">
214
+ {/* Header */}
215
+ <div className="flex items-center justify-between mb-4">
216
+ <button
217
+ onClick={() => navigateMonth('prev')}
218
+ className="p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
219
+ type="button"
220
+ >
221
+ <svg className="w-5 h-5 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
222
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
223
+ </svg>
224
+ </button>
225
+
226
+ <div className="flex items-center gap-2">
227
+ <span className="font-semibold text-gray-900 dark:text-gray-100">
228
+ {t.months[currentMonth.getMonth()]} {currentMonth.getFullYear()}
229
+ </span>
230
+ </div>
231
+
232
+ <button
233
+ onClick={() => navigateMonth('next')}
234
+ className="p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
235
+ type="button"
236
+ >
237
+ <svg className="w-5 h-5 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
238
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
239
+ </svg>
240
+ </button>
241
+ </div>
242
+
243
+ {/* Weekday Headers */}
244
+ <div className="grid grid-cols-7 gap-1 mb-2">
245
+ {t.weekdaysShort.map(day => (
246
+ <div key={day} className="text-center text-xs font-medium text-gray-500 dark:text-gray-400 py-1">
247
+ {day}
248
+ </div>
249
+ ))}
250
+ </div>
251
+
252
+ {/* Calendar Days */}
253
+ <div className="grid grid-cols-7 gap-1">
254
+ {days.map((day, index) => {
255
+ if (day === null) {
256
+ return <div key={index} className="aspect-square" />;
257
+ }
258
+
259
+ const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
260
+ const isDisabled = isDateDisabled(date);
261
+ const isSelected = selectedDate && isSameDay(date, selectedDate);
262
+ const isToday = isSameDay(date, new Date());
263
+
264
+ return (
265
+ <button
266
+ key={index}
267
+ onClick={() => handleDateSelect(day)}
268
+ disabled={isDisabled}
269
+ className={`
270
+ aspect-square rounded-lg text-sm font-medium transition-all
271
+ ${isSelected
272
+ ? 'bg-blue-600 dark:bg-blue-500 text-white shadow-md'
273
+ : isToday
274
+ ? 'bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 font-semibold'
275
+ : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
276
+ }
277
+ ${isDisabled ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer'}
278
+ `}
279
+ type="button"
280
+ >
281
+ {day}
282
+ </button>
283
+ );
284
+ })}
285
+ </div>
286
+
287
+ {/* Time Picker */}
288
+ {showTime && (
289
+ <div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
290
+ <div className="flex items-center justify-center gap-4">
291
+ <div className="flex items-center gap-2">
292
+ <label className="text-sm text-gray-600 dark:text-gray-400">{t.hours}:</label>
293
+ <input
294
+ type="number"
295
+ min="0"
296
+ max="23"
297
+ value={time.hours}
298
+ onChange={(e) => handleTimeChange('hours', parseInt(e.target.value) || 0)}
299
+ className="w-16 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded text-center focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400"
300
+ />
301
+ </div>
302
+ <span className="text-gray-400 dark:text-gray-500">:</span>
303
+ <div className="flex items-center gap-2">
304
+ <label className="text-sm text-gray-600 dark:text-gray-400">{t.minutes}:</label>
305
+ <input
306
+ type="number"
307
+ min="0"
308
+ max="59"
309
+ value={time.minutes}
310
+ onChange={(e) => handleTimeChange('minutes', parseInt(e.target.value) || 0)}
311
+ className="w-16 px-2 py-1 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded text-center focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400"
312
+ />
313
+ </div>
314
+ </div>
315
+ </div>
316
+ )}
317
+
318
+ {/* Footer Actions */}
319
+ <div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
320
+ <button
321
+ onClick={goToToday}
322
+ className="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors"
323
+ type="button"
324
+ >
325
+ {t.today}
326
+ </button>
327
+ {selectedDate && (
328
+ <button
329
+ onClick={clearDate}
330
+ className="text-sm text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 font-medium transition-colors"
331
+ type="button"
332
+ >
333
+ {t.clear}
334
+ </button>
335
+ )}
336
+ {showTime && (
337
+ <button
338
+ onClick={() => setIsOpen(false)}
339
+ className="px-4 py-1.5 bg-blue-600 dark:bg-blue-500 text-white rounded-lg hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors text-sm font-medium"
340
+ type="button"
341
+ >
342
+ {t.ok}
343
+ </button>
344
+ )}
345
+ </div>
346
+ </div>
347
+ )}
348
+ </div>
349
+ );
350
+ }
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import DatePicker from './DatePicker';
5
+ import { getTranslations, type Language } from '../lib/i18n';
6
+
7
+ export interface DateRangePickerProps {
8
+ startDate?: Date | null;
9
+ endDate?: Date | null;
10
+ onChange?: (startDate: Date | null, endDate: Date | null) => void;
11
+ placeholder?: string;
12
+ minDate?: Date;
13
+ maxDate?: Date;
14
+ disabled?: boolean;
15
+ className?: string;
16
+ language?: Language;
17
+ }
18
+
19
+ export default function DateRangePicker({
20
+ startDate,
21
+ endDate,
22
+ onChange,
23
+ placeholder,
24
+ minDate,
25
+ maxDate,
26
+ disabled = false,
27
+ className = '',
28
+ language = 'en'
29
+ }: DateRangePickerProps) {
30
+ const t = getTranslations(language);
31
+ const [localStartDate, setLocalStartDate] = useState<Date | null>(startDate || null);
32
+ const [localEndDate, setLocalEndDate] = useState<Date | null>(endDate || null);
33
+
34
+ useEffect(() => {
35
+ setLocalStartDate(startDate || null);
36
+ setLocalEndDate(endDate || null);
37
+ }, [startDate, endDate]);
38
+
39
+ const handleStartDateChange = (date: Date | null) => {
40
+ setLocalStartDate(date);
41
+ if (date && localEndDate && date > localEndDate) {
42
+ setLocalEndDate(null);
43
+ onChange?.(date, null);
44
+ } else {
45
+ onChange?.(date, localEndDate);
46
+ }
47
+ };
48
+
49
+ const handleEndDateChange = (date: Date | null) => {
50
+ setLocalEndDate(date);
51
+ onChange?.(localStartDate, date);
52
+ };
53
+
54
+ return (
55
+ <div className={`flex items-center gap-2 ${className}`}>
56
+ <DatePicker
57
+ value={localStartDate}
58
+ onChange={handleStartDateChange}
59
+ placeholder={t.startDate}
60
+ minDate={minDate}
61
+ maxDate={localEndDate || maxDate}
62
+ disabled={disabled}
63
+ className="flex-1"
64
+ language={language}
65
+ />
66
+ <span className="text-gray-400 dark:text-gray-500">-</span>
67
+ <DatePicker
68
+ value={localEndDate}
69
+ onChange={handleEndDateChange}
70
+ placeholder={t.endDate}
71
+ minDate={localStartDate || minDate}
72
+ maxDate={maxDate}
73
+ disabled={disabled}
74
+ className="flex-1"
75
+ language={language}
76
+ />
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+
3
+ import { useState, useRef, useEffect } from 'react';
4
+ import { languageNames, type Language } from '../lib/i18n';
5
+
6
+ interface LanguageSelectorProps {
7
+ value: Language;
8
+ onChange: (lang: Language) => void;
9
+ }
10
+
11
+ export default function LanguageSelector({ value, onChange }: LanguageSelectorProps) {
12
+ const [isOpen, setIsOpen] = useState(false);
13
+ const selectorRef = useRef<HTMLDivElement>(null);
14
+
15
+ useEffect(() => {
16
+ const handleClickOutside = (event: MouseEvent) => {
17
+ if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) {
18
+ setIsOpen(false);
19
+ }
20
+ };
21
+
22
+ if (isOpen) {
23
+ document.addEventListener('mousedown', handleClickOutside);
24
+ }
25
+
26
+ return () => {
27
+ document.removeEventListener('mousedown', handleClickOutside);
28
+ };
29
+ }, [isOpen]);
30
+
31
+ const languages: Language[] = ['en', 'tr', 'de', 'fr', 'es', 'it', 'pt', 'ru', 'ja', 'zh', 'ar'];
32
+
33
+ return (
34
+ <div ref={selectorRef} className="relative">
35
+ <button
36
+ onClick={() => setIsOpen(!isOpen)}
37
+ className="flex items-center gap-2 px-4 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
38
+ type="button"
39
+ >
40
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" />
42
+ </svg>
43
+ <span className="font-medium">{languageNames[value]}</span>
44
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
45
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
46
+ </svg>
47
+ </button>
48
+
49
+ {isOpen && (
50
+ <div className="absolute z-50 mt-2 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 py-2 min-w-[200px] max-h-64 overflow-y-auto">
51
+ {languages.map((lang) => (
52
+ <button
53
+ key={lang}
54
+ onClick={() => {
55
+ onChange(lang);
56
+ setIsOpen(false);
57
+ }}
58
+ className={`
59
+ w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors
60
+ ${value === lang ? 'bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 font-medium' : 'text-gray-700 dark:text-gray-300'}
61
+ `}
62
+ type="button"
63
+ >
64
+ {languageNames[lang]}
65
+ </button>
66
+ ))}
67
+ </div>
68
+ )}
69
+ </div>
70
+ );
71
+ }
72
+
@@ -0,0 +1,11 @@
1
+ export { default as DatePicker } from './DatePicker';
2
+ export { default as DateRangePicker } from './DateRangePicker';
3
+ export { default as DarkModeToggle } from './DarkModeToggle';
4
+ export { default as LanguageSelector } from './LanguageSelector';
5
+ export type { DatePickerProps } from './DatePicker';
6
+ export type { DateRangePickerProps } from './DateRangePicker';
7
+
8
+ // Export i18n utilities
9
+ export { getTranslations, languageNames } from '../lib/i18n';
10
+ export type { Language, Translations } from '../lib/i18n';
11
+
package/lib/i18n.ts ADDED
@@ -0,0 +1,237 @@
1
+ export type Language = 'en' | 'tr' | 'de' | 'fr' | 'es' | 'it' | 'pt' | 'ru' | 'ja' | 'zh' | 'ar';
2
+
3
+ export interface Translations {
4
+ months: string[];
5
+ weekdays: string[];
6
+ weekdaysShort: string[];
7
+ today: string;
8
+ clear: string;
9
+ selectDate: string;
10
+ selectTime: string;
11
+ selectRange: string;
12
+ startDate: string;
13
+ endDate: string;
14
+ hours: string;
15
+ minutes: string;
16
+ ok: string;
17
+ }
18
+
19
+ export const translations: Record<Language, Translations> = {
20
+ en: {
21
+ months: [
22
+ 'January', 'February', 'March', 'April', 'May', 'June',
23
+ 'July', 'August', 'September', 'October', 'November', 'December'
24
+ ],
25
+ weekdays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
26
+ weekdaysShort: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
27
+ today: 'Today',
28
+ clear: 'Clear',
29
+ selectDate: 'Select a date...',
30
+ selectTime: 'Select date and time...',
31
+ selectRange: 'Select date range...',
32
+ startDate: 'Start',
33
+ endDate: 'End',
34
+ hours: 'Hours',
35
+ minutes: 'Minutes',
36
+ ok: 'OK'
37
+ },
38
+ tr: {
39
+ months: [
40
+ 'Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran',
41
+ 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'
42
+ ],
43
+ weekdays: ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'],
44
+ weekdaysShort: ['Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'],
45
+ today: 'Bugün',
46
+ clear: 'Temizle',
47
+ selectDate: 'Bir tarih seçin...',
48
+ selectTime: 'Tarih ve saat seçin...',
49
+ selectRange: 'Tarih aralığı seçin...',
50
+ startDate: 'Başlangıç',
51
+ endDate: 'Bitiş',
52
+ hours: 'Saat',
53
+ minutes: 'Dakika',
54
+ ok: 'Tamam'
55
+ },
56
+ de: {
57
+ months: [
58
+ 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
59
+ 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
60
+ ],
61
+ weekdays: ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'],
62
+ weekdaysShort: ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'],
63
+ today: 'Heute',
64
+ clear: 'Löschen',
65
+ selectDate: 'Datum auswählen...',
66
+ selectTime: 'Datum und Uhrzeit auswählen...',
67
+ selectRange: 'Datumsbereich auswählen...',
68
+ startDate: 'Anfang',
69
+ endDate: 'Ende',
70
+ hours: 'Stunden',
71
+ minutes: 'Minuten',
72
+ ok: 'OK'
73
+ },
74
+ fr: {
75
+ months: [
76
+ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
77
+ 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'
78
+ ],
79
+ weekdays: ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'],
80
+ weekdaysShort: ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'],
81
+ today: "Aujourd'hui",
82
+ clear: 'Effacer',
83
+ selectDate: 'Sélectionner une date...',
84
+ selectTime: 'Sélectionner la date et l\'heure...',
85
+ selectRange: 'Sélectionner une plage de dates...',
86
+ startDate: 'Début',
87
+ endDate: 'Fin',
88
+ hours: 'Heures',
89
+ minutes: 'Minutes',
90
+ ok: 'OK'
91
+ },
92
+ es: {
93
+ months: [
94
+ 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
95
+ 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
96
+ ],
97
+ weekdays: ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'],
98
+ weekdaysShort: ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'],
99
+ today: 'Hoy',
100
+ clear: 'Limpiar',
101
+ selectDate: 'Seleccionar una fecha...',
102
+ selectTime: 'Seleccionar fecha y hora...',
103
+ selectRange: 'Seleccionar rango de fechas...',
104
+ startDate: 'Inicio',
105
+ endDate: 'Fin',
106
+ hours: 'Horas',
107
+ minutes: 'Minutos',
108
+ ok: 'OK'
109
+ },
110
+ it: {
111
+ months: [
112
+ 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
113
+ 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
114
+ ],
115
+ weekdays: ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'],
116
+ weekdaysShort: ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom'],
117
+ today: 'Oggi',
118
+ clear: 'Cancella',
119
+ selectDate: 'Seleziona una data...',
120
+ selectTime: 'Seleziona data e ora...',
121
+ selectRange: 'Seleziona intervallo di date...',
122
+ startDate: 'Inizio',
123
+ endDate: 'Fine',
124
+ hours: 'Ore',
125
+ minutes: 'Minuti',
126
+ ok: 'OK'
127
+ },
128
+ pt: {
129
+ months: [
130
+ 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
131
+ 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'
132
+ ],
133
+ weekdays: ['Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado', 'Domingo'],
134
+ weekdaysShort: ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'],
135
+ today: 'Hoje',
136
+ clear: 'Limpar',
137
+ selectDate: 'Selecione uma data...',
138
+ selectTime: 'Selecione data e hora...',
139
+ selectRange: 'Selecione intervalo de datas...',
140
+ startDate: 'Início',
141
+ endDate: 'Fim',
142
+ hours: 'Horas',
143
+ minutes: 'Minutos',
144
+ ok: 'OK'
145
+ },
146
+ ru: {
147
+ months: [
148
+ 'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
149
+ 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
150
+ ],
151
+ weekdays: ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'],
152
+ weekdaysShort: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
153
+ today: 'Сегодня',
154
+ clear: 'Очистить',
155
+ selectDate: 'Выберите дату...',
156
+ selectTime: 'Выберите дату и время...',
157
+ selectRange: 'Выберите диапазон дат...',
158
+ startDate: 'Начало',
159
+ endDate: 'Конец',
160
+ hours: 'Часы',
161
+ minutes: 'Минуты',
162
+ ok: 'ОК'
163
+ },
164
+ ja: {
165
+ months: [
166
+ '1月', '2月', '3月', '4月', '5月', '6月',
167
+ '7月', '8月', '9月', '10月', '11月', '12月'
168
+ ],
169
+ weekdays: ['月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日'],
170
+ weekdaysShort: ['月', '火', '水', '木', '金', '土', '日'],
171
+ today: '今日',
172
+ clear: 'クリア',
173
+ selectDate: '日付を選択...',
174
+ selectTime: '日付と時刻を選択...',
175
+ selectRange: '日付範囲を選択...',
176
+ startDate: '開始',
177
+ endDate: '終了',
178
+ hours: '時',
179
+ minutes: '分',
180
+ ok: 'OK'
181
+ },
182
+ zh: {
183
+ months: [
184
+ '一月', '二月', '三月', '四月', '五月', '六月',
185
+ '七月', '八月', '九月', '十月', '十一月', '十二月'
186
+ ],
187
+ weekdays: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
188
+ weekdaysShort: ['一', '二', '三', '四', '五', '六', '日'],
189
+ today: '今天',
190
+ clear: '清除',
191
+ selectDate: '选择日期...',
192
+ selectTime: '选择日期和时间...',
193
+ selectRange: '选择日期范围...',
194
+ startDate: '开始',
195
+ endDate: '结束',
196
+ hours: '小时',
197
+ minutes: '分钟',
198
+ ok: '确定'
199
+ },
200
+ ar: {
201
+ months: [
202
+ 'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
203
+ 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
204
+ ],
205
+ weekdays: ['الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد'],
206
+ weekdaysShort: ['اث', 'ثل', 'أر', 'خم', 'جم', 'سب', 'أح'],
207
+ today: 'اليوم',
208
+ clear: 'مسح',
209
+ selectDate: 'اختر تاريخاً...',
210
+ selectTime: 'اختر التاريخ والوقت...',
211
+ selectRange: 'اختر نطاق التاريخ...',
212
+ startDate: 'البداية',
213
+ endDate: 'النهاية',
214
+ hours: 'ساعات',
215
+ minutes: 'دقائق',
216
+ ok: 'موافق'
217
+ }
218
+ };
219
+
220
+ export const getTranslations = (lang: Language = 'en'): Translations => {
221
+ return translations[lang] || translations.en;
222
+ };
223
+
224
+ export const languageNames: Record<Language, string> = {
225
+ en: 'English',
226
+ tr: 'Türkçe',
227
+ de: 'Deutsch',
228
+ fr: 'Français',
229
+ es: 'Español',
230
+ it: 'Italiano',
231
+ pt: 'Português',
232
+ ru: 'Русский',
233
+ ja: '日本語',
234
+ zh: '中文',
235
+ ar: 'العربية'
236
+ };
237
+
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "react-datepicker-bkrdmrcioglu",
3
+ "version": "0.1.0",
4
+ "description": "Modern, customizable and user-friendly React DatePicker component with dark mode and multi-language support. Built with Next.js and Tailwind CSS.",
5
+ "main": "components/index.ts",
6
+ "types": "components/index.ts",
7
+ "files": [
8
+ "components",
9
+ "lib",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "keywords": [
14
+ "react",
15
+ "datepicker",
16
+ "date-picker",
17
+ "calendar",
18
+ "tailwindcss",
19
+ "typescript",
20
+ "date-range-picker",
21
+ "dark-mode",
22
+ "i18n",
23
+ "multi-language"
24
+ ],
25
+ "author": "bkrdmrcioglu <bkrdmrcioglu@gmail.com>",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/bkrdmrcioglu/react-datepicker.git"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/bkrdmrcioglu/react-datepicker/issues"
33
+ },
34
+ "homepage": "https://github.com/bkrdmrcioglu/react-datepicker#readme",
35
+ "private": false,
36
+ "scripts": {
37
+ "dev": "next dev",
38
+ "build": "next build",
39
+ "start": "next start",
40
+ "lint": "eslint",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "peerDependencies": {
44
+ "react": ">=16.8.0",
45
+ "react-dom": ">=16.8.0"
46
+ },
47
+ "dependencies": {},
48
+ "devDependencies": {
49
+ "@tailwindcss/postcss": "^4",
50
+ "@types/node": "^20",
51
+ "@types/react": "^19",
52
+ "@types/react-dom": "^19",
53
+ "eslint": "^9",
54
+ "eslint-config-next": "16.1.1",
55
+ "next": "16.1.1",
56
+ "react": "19.2.3",
57
+ "react-dom": "19.2.3",
58
+ "tailwindcss": "^4",
59
+ "typescript": "^5"
60
+ }
61
+ }