funuicss 2.7.7 → 2.7.9
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/css/fun.css +165 -2
- package/index.d.ts +3 -0
- package/index.js +8 -1
- package/index.tsx +3 -0
- package/package.json +2 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/ui/ScrollInView/ScrollInView.js +6 -6
- package/ui/ScrollInView/ScrollInView.tsx +6 -6
- package/ui/avatar/Avatar.d.ts +3 -2
- package/ui/avatar/Avatar.js +25 -4
- package/ui/avatar/Avatar.tsx +16 -8
- package/ui/calendar/Calendar.d.ts +21 -0
- package/ui/calendar/Calendar.js +196 -0
- package/ui/calendar/Calendar.tsx +293 -0
- package/ui/datepicker/DatePicker.d.ts +10 -0
- package/ui/datepicker/DatePicker.js +129 -0
- package/ui/datepicker/DatePicker.tsx +143 -0
- package/ui/input/Input.d.ts +1 -1
- package/ui/input/Input.tsx +1 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useMemo, useState } from 'react';
|
|
3
|
+
import dayjs, { Dayjs } from 'dayjs';
|
|
4
|
+
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
|
5
|
+
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
|
6
|
+
import { PiCaretLeft, PiCaretRight, PiPlus, PiCircle, PiChecks } from 'react-icons/pi';
|
|
7
|
+
import Avatar from '../avatar/Avatar';
|
|
8
|
+
import Circle from '../specials/Circle';
|
|
9
|
+
import RowFlex from '../specials/RowFlex';
|
|
10
|
+
import Input from '../input/Input';
|
|
11
|
+
import Button from '../button/Button';
|
|
12
|
+
import Text from '../text/Text';
|
|
13
|
+
|
|
14
|
+
dayjs.extend(isSameOrAfter);
|
|
15
|
+
dayjs.extend(isSameOrBefore);
|
|
16
|
+
|
|
17
|
+
interface Activity {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
date: Date;
|
|
21
|
+
color?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CalendarProps {
|
|
25
|
+
activities: Activity[];
|
|
26
|
+
onAdd?: (date: Date) => void;
|
|
27
|
+
onActivityClick?: (activity: Activity) => void;
|
|
28
|
+
onDateClick?: (date: Date) => void;
|
|
29
|
+
funcss?: string;
|
|
30
|
+
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6; // 0=Sunday, 1=Monday, etc.
|
|
31
|
+
renderActivity?: (activity: Activity) => React.ReactNode;
|
|
32
|
+
showAdjacentMonths?: boolean;
|
|
33
|
+
minDate?: Date;
|
|
34
|
+
maxDate?: Date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Calendar: React.FC<CalendarProps> = ({
|
|
38
|
+
activities,
|
|
39
|
+
onAdd,
|
|
40
|
+
onActivityClick,
|
|
41
|
+
onDateClick,
|
|
42
|
+
funcss = '',
|
|
43
|
+
weekStart = 0,
|
|
44
|
+
renderActivity,
|
|
45
|
+
showAdjacentMonths = true,
|
|
46
|
+
minDate,
|
|
47
|
+
maxDate,
|
|
48
|
+
}) => {
|
|
49
|
+
const [currentMonth, setCurrentMonth] = useState<Dayjs>(dayjs());
|
|
50
|
+
const [hoveredDate, setHoveredDate] = useState<string | null>(null);
|
|
51
|
+
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
|
52
|
+
|
|
53
|
+
// Memoized calculations
|
|
54
|
+
const { days, monthActivities } = useMemo(() => {
|
|
55
|
+
const startOfMonth = currentMonth.startOf('month');
|
|
56
|
+
const endOfMonth = currentMonth.endOf('month');
|
|
57
|
+
|
|
58
|
+
// Calculate days grid
|
|
59
|
+
const firstDay = startOfMonth.day();
|
|
60
|
+
const daysBefore = (firstDay - weekStart + 7) % 7;
|
|
61
|
+
const daysInMonth = currentMonth.daysInMonth();
|
|
62
|
+
const totalDays = Math.ceil((daysBefore + daysInMonth) / 7) * 7;
|
|
63
|
+
|
|
64
|
+
const days: (Dayjs | null)[] = [];
|
|
65
|
+
|
|
66
|
+
// Previous month days
|
|
67
|
+
for (let i = daysBefore - 1; i >= 0; i--) {
|
|
68
|
+
const date = startOfMonth.subtract(i + 1, 'day');
|
|
69
|
+
days.push(showAdjacentMonths ? date : null);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Current month days
|
|
73
|
+
for (let i = 0; i < daysInMonth; i++) {
|
|
74
|
+
days.push(startOfMonth.add(i, 'day'));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Next month days
|
|
78
|
+
const remaining = totalDays - days.length;
|
|
79
|
+
for (let i = 0; i < remaining; i++) {
|
|
80
|
+
const date = endOfMonth.add(i + 1, 'day');
|
|
81
|
+
days.push(showAdjacentMonths ? date : null);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Group activities by date
|
|
85
|
+
const monthActivities: Record<string, Activity[]> = {};
|
|
86
|
+
activities.forEach(activity => {
|
|
87
|
+
const date = dayjs(activity.date);
|
|
88
|
+
if (date.isSame(currentMonth, 'month') ||
|
|
89
|
+
(showAdjacentMonths && (date.isBefore(endOfMonth) && date.isAfter(startOfMonth)))) {
|
|
90
|
+
const key = date.format('YYYY-MM-DD');
|
|
91
|
+
if (!monthActivities[key]) monthActivities[key] = [];
|
|
92
|
+
monthActivities[key].push(activity);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return { days, monthActivities };
|
|
97
|
+
}, [currentMonth, activities, weekStart, showAdjacentMonths]);
|
|
98
|
+
|
|
99
|
+
// Navigation handlers
|
|
100
|
+
const prevMonth = () => setCurrentMonth(currentMonth.subtract(1, 'month'));
|
|
101
|
+
const nextMonth = () => setCurrentMonth(currentMonth.add(1, 'month'));
|
|
102
|
+
const goToToday = () => setCurrentMonth(dayjs());
|
|
103
|
+
|
|
104
|
+
// Date handlers
|
|
105
|
+
const handleDateClick = (date: Dayjs) => {
|
|
106
|
+
if (isDateDisabled(date)) return;
|
|
107
|
+
|
|
108
|
+
setSelectedDate(date.toDate());
|
|
109
|
+
onDateClick?.(date.toDate());
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleAdd = (e: React.MouseEvent, date: Dayjs) => {
|
|
113
|
+
e.stopPropagation();
|
|
114
|
+
if (isDateDisabled(date)) return;
|
|
115
|
+
onAdd?.(date.toDate());
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Utility functions
|
|
119
|
+
const isDateDisabled = (date: Dayjs) => {
|
|
120
|
+
return (
|
|
121
|
+
(minDate && date.isBefore(dayjs(minDate), 'day')) ||
|
|
122
|
+
(maxDate && date.isAfter(dayjs(maxDate), 'day'))
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const isToday = (date: Dayjs) => date.isSame(dayjs(), 'day');
|
|
127
|
+
const isSelected = (date: Dayjs) => selectedDate && date.isSame(selectedDate, 'day');
|
|
128
|
+
const isCurrentMonth = (date: Dayjs) => date.isSame(currentMonth, 'month');
|
|
129
|
+
|
|
130
|
+
// Weekday headers
|
|
131
|
+
const weekdays = useMemo(() => {
|
|
132
|
+
const days = [];
|
|
133
|
+
for (let i = 0; i < 7; i++) {
|
|
134
|
+
const day = dayjs().day((weekStart + i) % 7);
|
|
135
|
+
days.push(day.format('dd'));
|
|
136
|
+
}
|
|
137
|
+
return days;
|
|
138
|
+
}, [weekStart]);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div className={`calendar ${funcss}`}>
|
|
142
|
+
<div className="calendar-header">
|
|
143
|
+
<Avatar funcss='border' onClick={prevMonth} aria-label="Previous month">
|
|
144
|
+
<PiCaretLeft />
|
|
145
|
+
</Avatar>
|
|
146
|
+
|
|
147
|
+
<div className="calendar-title">
|
|
148
|
+
|
|
149
|
+
<Input
|
|
150
|
+
value={currentMonth.month()}
|
|
151
|
+
onChange={(e) => {
|
|
152
|
+
const newMonth = currentMonth.month(parseInt(e.target.value));
|
|
153
|
+
setCurrentMonth(newMonth);
|
|
154
|
+
}}
|
|
155
|
+
type="text"
|
|
156
|
+
label="Select Month"
|
|
157
|
+
borderless
|
|
158
|
+
fullWidth
|
|
159
|
+
funcss='round-edge'
|
|
160
|
+
select
|
|
161
|
+
options={[
|
|
162
|
+
{ value: "", text: "-- Select month --" },
|
|
163
|
+
{ value: "0", text: "January" },
|
|
164
|
+
{ value: "1", text: "February" },
|
|
165
|
+
{ value: "2", text: "March" },
|
|
166
|
+
{ value: "3", text: "April" },
|
|
167
|
+
{ value: "4", text: "May" },
|
|
168
|
+
{ value: "5", text: "June" },
|
|
169
|
+
{ value: "6", text: "July" },
|
|
170
|
+
{ value: "7", text: "August" },
|
|
171
|
+
{ value: "8", text: "September" },
|
|
172
|
+
{ value: "9", text: "October" },
|
|
173
|
+
{ value: "10", text: "November" },
|
|
174
|
+
{ value: "11", text: "December" }
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
<Input
|
|
178
|
+
type="text"
|
|
179
|
+
label="Select Year"
|
|
180
|
+
funcss='round-edge'
|
|
181
|
+
fullWidth
|
|
182
|
+
borderless
|
|
183
|
+
select
|
|
184
|
+
value={currentMonth.year().toString()}
|
|
185
|
+
onChange={(e) => {
|
|
186
|
+
const newYear = currentMonth.year(parseInt(e.target.value));
|
|
187
|
+
setCurrentMonth(newYear);
|
|
188
|
+
}}
|
|
189
|
+
options={Array.from({ length: 21 }, (_, i) => {
|
|
190
|
+
const year = dayjs().year() - 10 + i;
|
|
191
|
+
return {
|
|
192
|
+
value: year.toString(),
|
|
193
|
+
text: year.toString(),
|
|
194
|
+
};
|
|
195
|
+
})}
|
|
196
|
+
/>
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
<Button bg='lighter border' onClick={goToToday}>Today</Button>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
<Avatar funcss='border' onClick={nextMonth} aria-label="Next month">
|
|
204
|
+
<PiCaretRight />
|
|
205
|
+
</Avatar>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className="calendar-weekdays">
|
|
209
|
+
{weekdays.map((d, i) => (
|
|
210
|
+
<div key={i} className="weekday-header">{d}</div>
|
|
211
|
+
))}
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div className="calendar-grid">
|
|
215
|
+
{days.map((date, index) => {
|
|
216
|
+
if (!date) return <div key={index} className="calendar-cell empty" />;
|
|
217
|
+
|
|
218
|
+
const key = date.format('YYYY-MM-DD');
|
|
219
|
+
const activitiesToday = monthActivities[key] || [];
|
|
220
|
+
const disabled = isDateDisabled(date);
|
|
221
|
+
const today = isToday(date);
|
|
222
|
+
const selected = isSelected(date);
|
|
223
|
+
const currentMonth = isCurrentMonth(date);
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div
|
|
227
|
+
key={key}
|
|
228
|
+
className={`calendar-cell ${
|
|
229
|
+
!currentMonth ? 'adjacent-month' : ''} ${
|
|
230
|
+
disabled ? 'disabled' : ''} ${
|
|
231
|
+
today ? 'today' : ''} ${
|
|
232
|
+
selected ? 'selected' : ''}`
|
|
233
|
+
}
|
|
234
|
+
onClick={() => handleDateClick(date)}
|
|
235
|
+
onMouseEnter={() => setHoveredDate(key)}
|
|
236
|
+
onMouseLeave={() => setHoveredDate(null)}
|
|
237
|
+
aria-disabled={disabled}
|
|
238
|
+
aria-label={date.format('MMMM D, YYYY')}
|
|
239
|
+
role="button"
|
|
240
|
+
tabIndex={0}
|
|
241
|
+
>
|
|
242
|
+
<div className="day-number">
|
|
243
|
+
<RowFlex justify='space-between' align='center' gap={0.5}>
|
|
244
|
+
<Text
|
|
245
|
+
text= {date.date()}
|
|
246
|
+
color={today ? 'primary' : ''}
|
|
247
|
+
size='xl'
|
|
248
|
+
/>
|
|
249
|
+
{today && <PiChecks className="text-success" />}
|
|
250
|
+
</RowFlex>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div className="activities">
|
|
254
|
+
{activitiesToday.slice(0, 2).map(activity => (
|
|
255
|
+
<div
|
|
256
|
+
key={activity.id}
|
|
257
|
+
className="activity-tag"
|
|
258
|
+
style={{ backgroundColor: activity.color || '#e0e0e0' }}
|
|
259
|
+
onClick={e => {
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
onActivityClick?.(activity);
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
{renderActivity ? renderActivity(activity) : activity.title}
|
|
265
|
+
</div>
|
|
266
|
+
))}
|
|
267
|
+
{activitiesToday.length > 2 && (
|
|
268
|
+
<div className="activity-more">
|
|
269
|
+
+{activitiesToday.length - 2} more
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
{hoveredDate === key && !disabled && (
|
|
275
|
+
<div
|
|
276
|
+
className="add-icon"
|
|
277
|
+
onClick={e => handleAdd(e, date)}
|
|
278
|
+
aria-label={`Add event on ${date.format('MMMM D')}`}
|
|
279
|
+
>
|
|
280
|
+
<Circle hoverable funcss='card bg'>
|
|
281
|
+
<PiPlus />
|
|
282
|
+
</Circle>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
})}
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export default Calendar;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface DatePickerProps {
|
|
3
|
+
mode?: 'single' | 'interval';
|
|
4
|
+
funcss?: string;
|
|
5
|
+
onSelect?: (value: Date | [Date, Date]) => void;
|
|
6
|
+
value?: Date | [Date, Date];
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const DatePicker: React.FC<DatePickerProps>;
|
|
10
|
+
export default DatePicker;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
var react_1 = __importStar(require("react"));
|
|
41
|
+
var dayjs_1 = __importDefault(require("dayjs"));
|
|
42
|
+
var Avatar_1 = __importDefault(require("../avatar/Avatar"));
|
|
43
|
+
var pi_1 = require("react-icons/pi");
|
|
44
|
+
var DatePicker = function (_a) {
|
|
45
|
+
var _b = _a.mode, mode = _b === void 0 ? 'single' : _b, onSelect = _a.onSelect, value = _a.value, funcss = _a.funcss, _c = _a.className, className = _c === void 0 ? '' : _c;
|
|
46
|
+
var _d = (0, react_1.useState)((0, dayjs_1.default)()), currentMonth = _d[0], setCurrentMonth = _d[1];
|
|
47
|
+
var _e = (0, react_1.useState)(value || null), selected = _e[0], setSelected = _e[1];
|
|
48
|
+
var daysInMonth = currentMonth.daysInMonth();
|
|
49
|
+
var firstDay = currentMonth.startOf('month').day(); // 0 = Sunday
|
|
50
|
+
var days = [];
|
|
51
|
+
for (var i = 0; i < firstDay; i++) {
|
|
52
|
+
days.push(null); // padding
|
|
53
|
+
}
|
|
54
|
+
for (var i = 1; i <= daysInMonth; i++) {
|
|
55
|
+
days.push(currentMonth.date(i).toDate());
|
|
56
|
+
}
|
|
57
|
+
var handleSelect = function (date) {
|
|
58
|
+
if (mode === 'single') {
|
|
59
|
+
setSelected(date);
|
|
60
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(date);
|
|
61
|
+
}
|
|
62
|
+
else if (mode === 'interval') {
|
|
63
|
+
if (!selected || !Array.isArray(selected)) {
|
|
64
|
+
setSelected([date, null]);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
var start = selected[0], end = selected[1];
|
|
68
|
+
if (!end) {
|
|
69
|
+
var range = (0, dayjs_1.default)(date).isBefore((0, dayjs_1.default)(start)) ? [date, start] : [start, date];
|
|
70
|
+
setSelected(range);
|
|
71
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(range);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
setSelected([date, null]); // restart
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var isSelected = function (date) {
|
|
80
|
+
if (!selected)
|
|
81
|
+
return false;
|
|
82
|
+
if (mode === 'single' && selected instanceof Date) {
|
|
83
|
+
return (0, dayjs_1.default)(selected).isSame(date, 'day');
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(selected)) {
|
|
86
|
+
var start = selected[0], end = selected[1];
|
|
87
|
+
if (start && end) {
|
|
88
|
+
return ((0, dayjs_1.default)(date).isSame(start, 'day') ||
|
|
89
|
+
(0, dayjs_1.default)(date).isSame(end, 'day') ||
|
|
90
|
+
((0, dayjs_1.default)(date).isAfter(start) && (0, dayjs_1.default)(date).isBefore(end)));
|
|
91
|
+
}
|
|
92
|
+
return (0, dayjs_1.default)(date).isSame(start, 'day');
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
};
|
|
96
|
+
var nextMonth = function () { return setCurrentMonth(currentMonth.add(1, 'month')); };
|
|
97
|
+
var prevMonth = function () { return setCurrentMonth(currentMonth.subtract(1, 'month')); };
|
|
98
|
+
var handleClear = function () {
|
|
99
|
+
setSelected(null);
|
|
100
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(mode === 'single' ? null : null);
|
|
101
|
+
};
|
|
102
|
+
var formatDate = function (date) {
|
|
103
|
+
return date ? (0, dayjs_1.default)(date).format('MMM D, YYYY') : '...';
|
|
104
|
+
};
|
|
105
|
+
return (react_1.default.createElement("div", { className: "datepicker ".concat(funcss, " ").concat(className) },
|
|
106
|
+
react_1.default.createElement("div", { className: "datepicker-header" },
|
|
107
|
+
react_1.default.createElement(Avatar_1.default, { onClick: prevMonth },
|
|
108
|
+
react_1.default.createElement(pi_1.PiCaretLeft, null)),
|
|
109
|
+
react_1.default.createElement("span", null, currentMonth.format('MMMM YYYY')),
|
|
110
|
+
react_1.default.createElement(Avatar_1.default, { onClick: nextMonth },
|
|
111
|
+
react_1.default.createElement(pi_1.PiCaretRight, null))),
|
|
112
|
+
selected && (react_1.default.createElement("div", { className: "datepicker-selected" },
|
|
113
|
+
react_1.default.createElement("span", { className: "interval-display" },
|
|
114
|
+
mode === 'single' && selected instanceof Date && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
115
|
+
"Selected: ",
|
|
116
|
+
formatDate(selected))),
|
|
117
|
+
mode === 'interval' && Array.isArray(selected) && (react_1.default.createElement("div", { className: 'text-sm' }, selected[1]
|
|
118
|
+
? "From ".concat(formatDate(selected[0]), " to ").concat(formatDate(selected[1]))
|
|
119
|
+
: "Start: ".concat(formatDate(selected[0]), " - Select end date")))),
|
|
120
|
+
react_1.default.createElement("div", { style: { lineHeight: "0" } },
|
|
121
|
+
react_1.default.createElement(pi_1.PiX, { className: "clear-icon", onClick: handleClear, style: { cursor: 'pointer' } })))),
|
|
122
|
+
react_1.default.createElement("div", { className: "datepicker-weekdays" }, ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(function (d) { return (react_1.default.createElement("div", { key: d }, d)); })),
|
|
123
|
+
react_1.default.createElement("div", { className: "datepicker-grid" }, days.map(function (date, idx) {
|
|
124
|
+
var isSelectedClass = date && isSelected(date) ? 'selected' : '';
|
|
125
|
+
var isInRangeClass = date && isSelected(date) && Array.isArray(selected) ? 'in-range' : '';
|
|
126
|
+
return (react_1.default.createElement("div", { key: idx, onClick: function () { return date && handleSelect(date); }, className: "datepicker-day ".concat(!date ? 'empty' : '', " ").concat(isSelectedClass, " ").concat(isInRangeClass) }, date ? (0, dayjs_1.default)(date).date() : ''));
|
|
127
|
+
}))));
|
|
128
|
+
};
|
|
129
|
+
exports.default = DatePicker;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import Avatar from '../avatar/Avatar';
|
|
5
|
+
import { PiCaretLeft, PiCaretRight, PiX, PiXCircle } from 'react-icons/pi';
|
|
6
|
+
|
|
7
|
+
interface DatePickerProps {
|
|
8
|
+
mode?: 'single' | 'interval';
|
|
9
|
+
funcss?: string;
|
|
10
|
+
onSelect?: (value: Date | [Date, Date]) => void;
|
|
11
|
+
value?: Date | [Date, Date];
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Selection = Date | [Date, Date | null] | null;
|
|
16
|
+
|
|
17
|
+
const DatePicker: React.FC<DatePickerProps> = ({
|
|
18
|
+
mode = 'single',
|
|
19
|
+
onSelect,
|
|
20
|
+
value,
|
|
21
|
+
funcss,
|
|
22
|
+
className = '',
|
|
23
|
+
}) => {
|
|
24
|
+
const [currentMonth, setCurrentMonth] = useState(dayjs());
|
|
25
|
+
const [selected, setSelected] = useState<Selection>(value || null);
|
|
26
|
+
|
|
27
|
+
const daysInMonth = currentMonth.daysInMonth();
|
|
28
|
+
const firstDay = currentMonth.startOf('month').day(); // 0 = Sunday
|
|
29
|
+
|
|
30
|
+
const days: (Date | null)[] = [];
|
|
31
|
+
for (let i = 0; i < firstDay; i++) {
|
|
32
|
+
days.push(null); // padding
|
|
33
|
+
}
|
|
34
|
+
for (let i = 1; i <= daysInMonth; i++) {
|
|
35
|
+
days.push(currentMonth.date(i).toDate());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleSelect = (date: Date) => {
|
|
39
|
+
if (mode === 'single') {
|
|
40
|
+
setSelected(date);
|
|
41
|
+
onSelect?.(date);
|
|
42
|
+
} else if (mode === 'interval') {
|
|
43
|
+
if (!selected || !Array.isArray(selected)) {
|
|
44
|
+
setSelected([date, null]);
|
|
45
|
+
} else {
|
|
46
|
+
const [start, end] = selected;
|
|
47
|
+
if (!end) {
|
|
48
|
+
const range: [Date, Date] =
|
|
49
|
+
dayjs(date).isBefore(dayjs(start)) ? [date, start] : [start, date];
|
|
50
|
+
setSelected(range);
|
|
51
|
+
onSelect?.(range);
|
|
52
|
+
} else {
|
|
53
|
+
setSelected([date, null]); // restart
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const isSelected = (date: Date) => {
|
|
60
|
+
if (!selected) return false;
|
|
61
|
+
if (mode === 'single' && selected instanceof Date) {
|
|
62
|
+
return dayjs(selected).isSame(date, 'day');
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(selected)) {
|
|
65
|
+
const [start, end] = selected;
|
|
66
|
+
if (start && end) {
|
|
67
|
+
return (
|
|
68
|
+
dayjs(date).isSame(start, 'day') ||
|
|
69
|
+
dayjs(date).isSame(end, 'day') ||
|
|
70
|
+
(dayjs(date).isAfter(start) && dayjs(date).isBefore(end))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return dayjs(date).isSame(start, 'day');
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const nextMonth = () => setCurrentMonth(currentMonth.add(1, 'month'));
|
|
79
|
+
const prevMonth = () => setCurrentMonth(currentMonth.subtract(1, 'month'));
|
|
80
|
+
|
|
81
|
+
const handleClear = () => {
|
|
82
|
+
setSelected(null);
|
|
83
|
+
onSelect?.(mode === 'single' ? null as unknown as Date : null as unknown as [Date, Date]);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const formatDate = (date: Date | null | undefined) =>
|
|
87
|
+
date ? dayjs(date).format('MMM D, YYYY') : '...';
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className={`datepicker ${funcss} ${className}`}>
|
|
91
|
+
<div className="datepicker-header">
|
|
92
|
+
<Avatar onClick={prevMonth}><PiCaretLeft /></Avatar>
|
|
93
|
+
<span>{currentMonth.format('MMMM YYYY')}</span>
|
|
94
|
+
<Avatar onClick={nextMonth}><PiCaretRight /></Avatar>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{selected && (
|
|
98
|
+
<div className="datepicker-selected">
|
|
99
|
+
<span className="interval-display">
|
|
100
|
+
{mode === 'single' && selected instanceof Date && (
|
|
101
|
+
<>Selected: {formatDate(selected)}</>
|
|
102
|
+
)}
|
|
103
|
+
{mode === 'interval' && Array.isArray(selected) && (
|
|
104
|
+
<div className='text-sm'>
|
|
105
|
+
{selected[1]
|
|
106
|
+
? `From ${formatDate(selected[0])} to ${formatDate(selected[1])}`
|
|
107
|
+
: `Start: ${formatDate(selected[0])} - Select end date`}
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
</span>
|
|
111
|
+
<div style={{lineHeight:"0"}}>
|
|
112
|
+
<PiX className="clear-icon" onClick={handleClear} style={{ cursor: 'pointer' }} />
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
<div className="datepicker-weekdays">
|
|
118
|
+
{['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map((d) => (
|
|
119
|
+
<div key={d}>{d}</div>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="datepicker-grid">
|
|
124
|
+
{days.map((date, idx) => {
|
|
125
|
+
const isSelectedClass = date && isSelected(date) ? 'selected' : '';
|
|
126
|
+
const isInRangeClass =
|
|
127
|
+
date && isSelected(date) && Array.isArray(selected) ? 'in-range' : '';
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
key={idx}
|
|
131
|
+
onClick={() => date && handleSelect(date)}
|
|
132
|
+
className={`datepicker-day ${!date ? 'empty' : ''} ${isSelectedClass} ${isInRangeClass}`}
|
|
133
|
+
>
|
|
134
|
+
{date ? dayjs(date).date() : ''}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default DatePicker;
|
package/ui/input/Input.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ interface InputProps {
|
|
|
21
21
|
type?: string;
|
|
22
22
|
label?: string;
|
|
23
23
|
name?: string;
|
|
24
|
-
value?:
|
|
24
|
+
value?: any;
|
|
25
25
|
defaultValue?: string;
|
|
26
26
|
onChange?: (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void;
|
|
27
27
|
options?: {
|
package/ui/input/Input.tsx
CHANGED