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 +22 -0
- package/README.md +266 -0
- package/components/DarkModeToggle.tsx +52 -0
- package/components/DatePicker.tsx +350 -0
- package/components/DateRangePicker.tsx +79 -0
- package/components/LanguageSelector.tsx +72 -0
- package/components/index.ts +11 -0
- package/lib/i18n.ts +237 -0
- package/package.json +61 -0
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
|
+
}
|