@soul-refiner/design-system 0.1.19 → 0.1.21
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/package.json +2 -1
- package/src/common/date.ts +30 -0
- package/src/common/index.ts +7 -0
- package/src/components/feedback/index.ts +2 -0
- package/src/components/feedback/tooltip/index.ts +2 -0
- package/src/components/feedback/tooltip/tooltip.tsx +40 -0
- package/src/components/input/date/date.tsx +697 -0
- package/src/components/input/date/index.ts +2 -0
- package/src/components/input/index.ts +2 -0
- package/src/utils/date.ts +118 -0
- package/src/utils/index.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soul-refiner/design-system",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"react-dnd-html5-backend": "^16.0.1",
|
|
55
55
|
"react-dom": "^18.2.0",
|
|
56
56
|
"react-is": "^18.2.0",
|
|
57
|
+
"react-tooltip": "^5.18.0",
|
|
57
58
|
"scheduler": "^0.23.0",
|
|
58
59
|
"slate": "^0.94.1",
|
|
59
60
|
"slate-history": "^0.93.0",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const THIS_YEAR = +new Date().getFullYear();
|
|
2
|
+
|
|
3
|
+
export const THIS_MONTH = +new Date().getMonth() + 1;
|
|
4
|
+
|
|
5
|
+
export enum WEEKDAYS {
|
|
6
|
+
MON = 'Mon',
|
|
7
|
+
TUE = 'Tue',
|
|
8
|
+
WED = 'Wed',
|
|
9
|
+
THU = 'Thu',
|
|
10
|
+
FRI = 'Fri',
|
|
11
|
+
SAT = 'Sat',
|
|
12
|
+
SUN = 'Sun',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export enum MONTHS {
|
|
16
|
+
JAN = 'Jan',
|
|
17
|
+
FEB = 'Feb',
|
|
18
|
+
MAR = 'Mar',
|
|
19
|
+
APR = 'Apr',
|
|
20
|
+
MAY = 'May',
|
|
21
|
+
JUN = 'Jun',
|
|
22
|
+
JUL = 'Jul',
|
|
23
|
+
AUG = 'Aug',
|
|
24
|
+
SEP = 'Sep',
|
|
25
|
+
OCT = 'Oct',
|
|
26
|
+
NOV = 'Nov',
|
|
27
|
+
DEC = 'Dec',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const CALENDAR_WEEKS = 6;
|
package/src/common/index.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tooltip as ReactTooltip, PlacesType } from 'react-tooltip';
|
|
3
|
+
|
|
4
|
+
export interface TooltipProps extends React.PropsWithChildren {
|
|
5
|
+
content: string;
|
|
6
|
+
place: PlacesType;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Tooltip = ({ children, content, place = 'top' }: TooltipProps) => {
|
|
10
|
+
const id = React.useId();
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<ReactTooltip
|
|
14
|
+
id={id}
|
|
15
|
+
place={place}
|
|
16
|
+
style={{
|
|
17
|
+
fontFamily: 'Inter',
|
|
18
|
+
fontSize: '12px',
|
|
19
|
+
fontWeight: 400,
|
|
20
|
+
lineHeight: '150%',
|
|
21
|
+
textAlign: 'center',
|
|
22
|
+
padding: '8px',
|
|
23
|
+
maxWidth: '280px',
|
|
24
|
+
backgroundColor: 'var(--color-grey-3)',
|
|
25
|
+
boxShadow: 'var(--elevation-2)',
|
|
26
|
+
borderRadius: '4px',
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
{React.Children.map(children, (child) => {
|
|
30
|
+
return (
|
|
31
|
+
React.isValidElement(child) &&
|
|
32
|
+
React.cloneElement(child, {
|
|
33
|
+
'data-tooltip-id': id,
|
|
34
|
+
'data-tooltip-content': content,
|
|
35
|
+
} as any)
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { MONTHS, WEEKDAYS } from 'common';
|
|
4
|
+
import { Button, P } from 'components';
|
|
5
|
+
import { ChevronLeft, ChevronRight, ArrowRight } from 'icons';
|
|
6
|
+
import { buildCalendar, getMonthDays, zeroPad } from 'utils';
|
|
7
|
+
|
|
8
|
+
export interface DatePickerProps {
|
|
9
|
+
endDate?: Date;
|
|
10
|
+
label?: React.ReactNode;
|
|
11
|
+
onSelect: ({ type, value }: { type: 'end' | 'start'; value: Date }) => void;
|
|
12
|
+
startDate: Date;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DatePicker = ({
|
|
16
|
+
endDate,
|
|
17
|
+
label,
|
|
18
|
+
onSelect: parentOnSelect,
|
|
19
|
+
startDate,
|
|
20
|
+
}: DatePickerProps) => {
|
|
21
|
+
const today = new Date();
|
|
22
|
+
today.setHours(0);
|
|
23
|
+
today.setMinutes(0);
|
|
24
|
+
today.setSeconds(0);
|
|
25
|
+
today.setMilliseconds(0);
|
|
26
|
+
const firstOfMonth = new Date(today);
|
|
27
|
+
firstOfMonth.setDate(1);
|
|
28
|
+
const thisMonth = firstOfMonth.getMonth() + 1;
|
|
29
|
+
const thisYear = firstOfMonth.getFullYear();
|
|
30
|
+
|
|
31
|
+
const isSingleDate = !endDate;
|
|
32
|
+
|
|
33
|
+
const pickerRef = React.useRef<HTMLDivElement>(null);
|
|
34
|
+
|
|
35
|
+
const [show, setShow] = React.useState(false);
|
|
36
|
+
const [displayDate, setDisplayDate] = React.useState(
|
|
37
|
+
() => endDate?.toDateString() ?? startDate.toDateString()
|
|
38
|
+
);
|
|
39
|
+
const [componentStartDate, setComponentStartDate] = React.useState(() =>
|
|
40
|
+
startDate.toDateString()
|
|
41
|
+
);
|
|
42
|
+
const [componentEndDate, setComponentEndDate] = React.useState(
|
|
43
|
+
() => endDate?.toDateString() ?? startDate.toDateString()
|
|
44
|
+
);
|
|
45
|
+
const [firstClick, setFirstClick] = React.useState(true);
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
setComponentStartDate(startDate.toDateString());
|
|
49
|
+
}, [startDate]);
|
|
50
|
+
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
setComponentEndDate((endDate ?? new Date())?.toDateString());
|
|
53
|
+
}, [endDate]);
|
|
54
|
+
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (
|
|
57
|
+
!isSingleDate &&
|
|
58
|
+
new Date(componentStartDate).getTime() >
|
|
59
|
+
new Date(componentEndDate).getTime()
|
|
60
|
+
) {
|
|
61
|
+
const tempStart = componentStartDate;
|
|
62
|
+
setComponentStartDate(componentEndDate);
|
|
63
|
+
setComponentEndDate(tempStart);
|
|
64
|
+
}
|
|
65
|
+
}, [componentEndDate, componentStartDate]);
|
|
66
|
+
|
|
67
|
+
const handleShow = () => setShow((prev) => !prev);
|
|
68
|
+
|
|
69
|
+
const handleBlur = (event: React.FocusEvent) => {
|
|
70
|
+
if (
|
|
71
|
+
pickerRef?.current &&
|
|
72
|
+
!pickerRef?.current?.contains(event.relatedTarget)
|
|
73
|
+
) {
|
|
74
|
+
setShow(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handlePreviousMonth = () => {
|
|
79
|
+
setDisplayDate((prev) => {
|
|
80
|
+
const newDate = new Date(prev);
|
|
81
|
+
newDate.setMonth(newDate.getMonth() - 1);
|
|
82
|
+
return newDate.toDateString();
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleNextMonth = () => {
|
|
87
|
+
setDisplayDate((prev) => {
|
|
88
|
+
const newDate = new Date(prev);
|
|
89
|
+
newDate.setMonth(newDate.getMonth() + 1);
|
|
90
|
+
return newDate.toDateString();
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleFirstClick = () =>
|
|
95
|
+
!isSingleDate && setFirstClick((prev) => !prev);
|
|
96
|
+
|
|
97
|
+
const onSelect = ({
|
|
98
|
+
type,
|
|
99
|
+
value,
|
|
100
|
+
}: {
|
|
101
|
+
type: 'start' | 'end';
|
|
102
|
+
value: Date;
|
|
103
|
+
}) => {
|
|
104
|
+
if (type === 'start') {
|
|
105
|
+
setComponentStartDate(value.toDateString());
|
|
106
|
+
} else {
|
|
107
|
+
setComponentEndDate(value.toDateString());
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleClick = (event: React.MouseEvent) => {
|
|
112
|
+
handleFirstClick();
|
|
113
|
+
const target = event.target as HTMLElement;
|
|
114
|
+
const type = firstClick ? 'start' : 'end';
|
|
115
|
+
const day = target.getAttribute('data-day');
|
|
116
|
+
const month = target.getAttribute('data-month');
|
|
117
|
+
const year = target.getAttribute('data-year');
|
|
118
|
+
onSelect({ type, value: new Date(`${month}-${day}-${year}`) });
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const handleApply = () => {
|
|
122
|
+
parentOnSelect({ type: 'start', value: new Date(componentStartDate) });
|
|
123
|
+
!isSingleDate &&
|
|
124
|
+
parentOnSelect({ type: 'end', value: new Date(componentEndDate) });
|
|
125
|
+
!isSingleDate && setFirstClick(true);
|
|
126
|
+
setShow(false);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const handleCancel = () => {
|
|
130
|
+
setComponentStartDate(startDate.toDateString());
|
|
131
|
+
setComponentEndDate((endDate ?? new Date())?.toDateString());
|
|
132
|
+
setFirstClick(true);
|
|
133
|
+
setShow(false);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const handleSetToday = () => {
|
|
137
|
+
onSelect({ type: 'start', value: today });
|
|
138
|
+
onSelect({ type: 'end', value: today });
|
|
139
|
+
setDisplayDate(today.toDateString());
|
|
140
|
+
setFirstClick(true);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const handleSetYesterday = () => {
|
|
144
|
+
const yesterday = new Date(
|
|
145
|
+
today.getFullYear(),
|
|
146
|
+
today.getMonth(),
|
|
147
|
+
today.getDate() - 1
|
|
148
|
+
);
|
|
149
|
+
onSelect({ type: 'start', value: yesterday });
|
|
150
|
+
onSelect({ type: 'end', value: yesterday });
|
|
151
|
+
setDisplayDate(today.toDateString());
|
|
152
|
+
setFirstClick(true);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleLastSevenDays = () => {
|
|
156
|
+
const lastWeekDate = new Date(
|
|
157
|
+
today.getFullYear(),
|
|
158
|
+
today.getMonth(),
|
|
159
|
+
today.getDate() - 7
|
|
160
|
+
);
|
|
161
|
+
onSelect({ type: 'start', value: lastWeekDate });
|
|
162
|
+
onSelect({ type: 'end', value: today });
|
|
163
|
+
setDisplayDate(today.toDateString());
|
|
164
|
+
setFirstClick(true);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleLastThirtyDays = () => {
|
|
168
|
+
const lastMonthDate = new Date(today);
|
|
169
|
+
lastMonthDate.setMonth(thisMonth - 2);
|
|
170
|
+
onSelect({ type: 'start', value: lastMonthDate });
|
|
171
|
+
onSelect({ type: 'end', value: today });
|
|
172
|
+
setDisplayDate(today.toDateString());
|
|
173
|
+
setFirstClick(true);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleThisMonth = () => {
|
|
177
|
+
onSelect({ type: 'start', value: firstOfMonth });
|
|
178
|
+
onSelect({
|
|
179
|
+
type: 'end',
|
|
180
|
+
value: new Date(
|
|
181
|
+
firstOfMonth.getFullYear(),
|
|
182
|
+
firstOfMonth.getMonth(),
|
|
183
|
+
getMonthDays()
|
|
184
|
+
),
|
|
185
|
+
});
|
|
186
|
+
setDisplayDate(today.toDateString());
|
|
187
|
+
setFirstClick(true);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleLastMonth = () => {
|
|
191
|
+
const lastMonthDate = new Date(firstOfMonth);
|
|
192
|
+
lastMonthDate.setMonth(thisMonth - 2);
|
|
193
|
+
onSelect({ type: 'start', value: lastMonthDate });
|
|
194
|
+
onSelect({
|
|
195
|
+
type: 'end',
|
|
196
|
+
value: new Date(
|
|
197
|
+
lastMonthDate.getFullYear(),
|
|
198
|
+
lastMonthDate.getMonth(),
|
|
199
|
+
getMonthDays(lastMonthDate.getMonth() + 1)
|
|
200
|
+
),
|
|
201
|
+
});
|
|
202
|
+
setDisplayDate(lastMonthDate.toDateString());
|
|
203
|
+
setFirstClick(true);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleLastYear = () => {
|
|
207
|
+
const lastYearDate = new Date(today);
|
|
208
|
+
lastYearDate.setFullYear(thisYear - 1);
|
|
209
|
+
onSelect({ type: 'start', value: lastYearDate });
|
|
210
|
+
onSelect({ type: 'end', value: today });
|
|
211
|
+
setDisplayDate(today.toDateString());
|
|
212
|
+
setFirstClick(true);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const calendar = buildCalendar(
|
|
216
|
+
new Date(displayDate).getMonth() + 1,
|
|
217
|
+
new Date(displayDate).getFullYear()
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const minRange = new Date(componentStartDate).getTime();
|
|
221
|
+
const maxRange = new Date(componentEndDate).getTime();
|
|
222
|
+
|
|
223
|
+
const displayDateObj = new Date(displayDate);
|
|
224
|
+
const displayFirstOfMonth = new Date(displayDateObj);
|
|
225
|
+
displayFirstOfMonth.setDate(1);
|
|
226
|
+
const displayLastOfMonth = new Date(displayDateObj);
|
|
227
|
+
displayLastOfMonth.setDate(getMonthDays(displayDateObj.getMonth() + 1));
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<Wrapper onBlurCapture={handleBlur} ref={pickerRef} tabIndex={0}>
|
|
231
|
+
<Trigger onClick={handleShow}>
|
|
232
|
+
{label && (
|
|
233
|
+
<>
|
|
234
|
+
{label}
|
|
235
|
+
{' | '}
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
<span>
|
|
239
|
+
{startDate.toLocaleDateString('en-US').replaceAll('/', '-')}
|
|
240
|
+
</span>
|
|
241
|
+
{!!endDate && startDate.toDateString() !== endDate?.toDateString() && (
|
|
242
|
+
<ArrowRight sx="padding: 0px 8px; margin-bottom: -2px;" />
|
|
243
|
+
)}
|
|
244
|
+
{startDate.toDateString() !== endDate?.toDateString() && (
|
|
245
|
+
<span>
|
|
246
|
+
{endDate?.toLocaleDateString('en-US').replaceAll('/', '-')}
|
|
247
|
+
</span>
|
|
248
|
+
)}
|
|
249
|
+
</Trigger>
|
|
250
|
+
{show && (
|
|
251
|
+
<DateSelector>
|
|
252
|
+
<InnerWrapper>
|
|
253
|
+
{!!endDate && (
|
|
254
|
+
<Left>
|
|
255
|
+
<Button size="small" type="text" onClick={handleSetToday}>
|
|
256
|
+
Today
|
|
257
|
+
</Button>
|
|
258
|
+
<Button size="small" type="text" onClick={handleSetYesterday}>
|
|
259
|
+
Yesterday
|
|
260
|
+
</Button>
|
|
261
|
+
<Button size="small" type="text" onClick={handleLastSevenDays}>
|
|
262
|
+
Last 7 days
|
|
263
|
+
</Button>
|
|
264
|
+
<Button size="small" type="text" onClick={handleLastThirtyDays}>
|
|
265
|
+
Last 30 days
|
|
266
|
+
</Button>
|
|
267
|
+
<Button size="small" type="text" onClick={handleThisMonth}>
|
|
268
|
+
This month
|
|
269
|
+
</Button>
|
|
270
|
+
<Button size="small" type="text" onClick={handleLastMonth}>
|
|
271
|
+
Last month
|
|
272
|
+
</Button>
|
|
273
|
+
<Button size="small" type="text" onClick={handleLastYear}>
|
|
274
|
+
Last Year
|
|
275
|
+
</Button>
|
|
276
|
+
</Left>
|
|
277
|
+
)}
|
|
278
|
+
<div>
|
|
279
|
+
<MonthBar>
|
|
280
|
+
<Arrow onClick={handlePreviousMonth}>
|
|
281
|
+
<ChevronLeft size={24} />
|
|
282
|
+
</Arrow>
|
|
283
|
+
<P>
|
|
284
|
+
{`${
|
|
285
|
+
Object.values(MONTHS)[
|
|
286
|
+
Math.max(
|
|
287
|
+
0,
|
|
288
|
+
Math.min(new Date(displayDate).getMonth(), 11)
|
|
289
|
+
)
|
|
290
|
+
]
|
|
291
|
+
} ${new Date(displayDate).getFullYear()}`}
|
|
292
|
+
</P>
|
|
293
|
+
<Arrow onClick={handleNextMonth}>
|
|
294
|
+
<ChevronRight size={24} />
|
|
295
|
+
</Arrow>
|
|
296
|
+
</MonthBar>
|
|
297
|
+
<WeekdayWrapper>
|
|
298
|
+
{Object.values(WEEKDAYS).map((day) => (
|
|
299
|
+
<P key={day}>{day}</P>
|
|
300
|
+
))}
|
|
301
|
+
</WeekdayWrapper>
|
|
302
|
+
<Calendar>
|
|
303
|
+
{calendar.map((week, index) => {
|
|
304
|
+
return (
|
|
305
|
+
<Week key={'week-' + index.toString(36)}>
|
|
306
|
+
{week.map((day, dayIndex) => {
|
|
307
|
+
const isCurrMonth =
|
|
308
|
+
day[1] !==
|
|
309
|
+
zeroPad(new Date(displayDate).getMonth() + 1, 2);
|
|
310
|
+
const dateObj = new Date(
|
|
311
|
+
`${day[1]}-${day[2]}-${day[0]}`
|
|
312
|
+
);
|
|
313
|
+
const isToday =
|
|
314
|
+
Math.abs(dateObj.getTime() - today.getTime()) < 0.5;
|
|
315
|
+
const time = dateObj.toDateString();
|
|
316
|
+
const isStartDate = time === componentStartDate;
|
|
317
|
+
const isEndDate =
|
|
318
|
+
!isSingleDate && time === componentEndDate;
|
|
319
|
+
const between =
|
|
320
|
+
!isSingleDate &&
|
|
321
|
+
minRange <= dateObj.getTime() &&
|
|
322
|
+
dateObj.getTime() <= maxRange &&
|
|
323
|
+
!isStartDate &&
|
|
324
|
+
!isEndDate;
|
|
325
|
+
const isFirstOfMonth =
|
|
326
|
+
displayFirstOfMonth.toDateString() === time;
|
|
327
|
+
const isLastOfMonth =
|
|
328
|
+
displayLastOfMonth.toDateString() === time;
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<Day
|
|
332
|
+
className={`${isToday ? 'today' : ''}${
|
|
333
|
+
isCurrMonth ? ' not-current' : ''
|
|
334
|
+
}${isStartDate ? ' start-date' : ''}${
|
|
335
|
+
isEndDate ? ' end-date' : ''
|
|
336
|
+
}${between ? ' between' : ''}${
|
|
337
|
+
isFirstOfMonth ? ' first-of-month' : ''
|
|
338
|
+
}${isLastOfMonth ? ' last-of-month' : ''}`.trim()}
|
|
339
|
+
data-day={day[2]}
|
|
340
|
+
data-month={day[1]}
|
|
341
|
+
data-year={day[0]}
|
|
342
|
+
key={'day-' + dayIndex.toString(36)}
|
|
343
|
+
onClick={handleClick}
|
|
344
|
+
>
|
|
345
|
+
{`${day[2]}`.startsWith('0')
|
|
346
|
+
? `${day[2]}`[1]
|
|
347
|
+
: day[2]}
|
|
348
|
+
</Day>
|
|
349
|
+
);
|
|
350
|
+
})}
|
|
351
|
+
</Week>
|
|
352
|
+
);
|
|
353
|
+
})}
|
|
354
|
+
</Calendar>
|
|
355
|
+
<BottomWrapper>
|
|
356
|
+
<Button
|
|
357
|
+
onClick={handleCancel}
|
|
358
|
+
size="small"
|
|
359
|
+
sx="width: 80px;"
|
|
360
|
+
type="outlined"
|
|
361
|
+
>
|
|
362
|
+
Cancel
|
|
363
|
+
</Button>
|
|
364
|
+
<Button onClick={handleApply} size="small" sx="width: 80px;">
|
|
365
|
+
Apply
|
|
366
|
+
</Button>
|
|
367
|
+
</BottomWrapper>
|
|
368
|
+
</div>
|
|
369
|
+
</InnerWrapper>
|
|
370
|
+
</DateSelector>
|
|
371
|
+
)}
|
|
372
|
+
</Wrapper>
|
|
373
|
+
);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const Wrapper = styled.div`
|
|
377
|
+
position: relative;
|
|
378
|
+
width: fit-content;
|
|
379
|
+
font-family: Inter;
|
|
380
|
+
`;
|
|
381
|
+
|
|
382
|
+
const Trigger = styled.button`
|
|
383
|
+
padding: 12px 24px;
|
|
384
|
+
background-color: #ffffff;
|
|
385
|
+
border: 1px solid var(--color-grey-6);
|
|
386
|
+
border-radius: 24px;
|
|
387
|
+
color: var(--color-grey-3);
|
|
388
|
+
outline: none;
|
|
389
|
+
line-height: 100%;
|
|
390
|
+
font-family: Inter;
|
|
391
|
+
font-size: 14px;
|
|
392
|
+
line-height: 20px;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
|
|
395
|
+
span {
|
|
396
|
+
line-height: 20px;
|
|
397
|
+
color: var(--color-blue-5);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
&:hover {
|
|
401
|
+
background-color: var(--color-blue-5);
|
|
402
|
+
border-color: var(--color-blue-5);
|
|
403
|
+
color: #ffffff;
|
|
404
|
+
span {
|
|
405
|
+
color: #ffffff;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
&:active {
|
|
410
|
+
background-color: var(--color-blue-3);
|
|
411
|
+
border-color: var(--color-blue-3);
|
|
412
|
+
}
|
|
413
|
+
`;
|
|
414
|
+
|
|
415
|
+
const DateSelector = styled.div`
|
|
416
|
+
transform: translateY(26px);
|
|
417
|
+
position: absolute;
|
|
418
|
+
z-index: 5;
|
|
419
|
+
background-color: #ffffff;
|
|
420
|
+
border-radius: 25px;
|
|
421
|
+
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.25);
|
|
422
|
+
`;
|
|
423
|
+
|
|
424
|
+
const InnerWrapper = styled.div`
|
|
425
|
+
display: flex;
|
|
426
|
+
gap: 31px;
|
|
427
|
+
padding: 30px;
|
|
428
|
+
position: relative;
|
|
429
|
+
border-radius: 25px;
|
|
430
|
+
|
|
431
|
+
&::before {
|
|
432
|
+
position: absolute;
|
|
433
|
+
content: '';
|
|
434
|
+
width: 24px;
|
|
435
|
+
height: 24px;
|
|
436
|
+
top: -12px;
|
|
437
|
+
left: 48px;
|
|
438
|
+
background-color: #ffffff;
|
|
439
|
+
transform: rotate(45deg);
|
|
440
|
+
box-shadow: -3px -3px 5px rgba(0, 0, 0, 0.1);
|
|
441
|
+
}
|
|
442
|
+
`;
|
|
443
|
+
|
|
444
|
+
const Left = styled.div`
|
|
445
|
+
display: flex;
|
|
446
|
+
flex-flow: column nowrap;
|
|
447
|
+
gap: 24px;
|
|
448
|
+
align-items: flex-start;
|
|
449
|
+
width: max-content;
|
|
450
|
+
margin-bottom: 96px;
|
|
451
|
+
padding-right: 48px;
|
|
452
|
+
border-right: 1px solid var(--color-grey-8);
|
|
453
|
+
user-select: none;
|
|
454
|
+
|
|
455
|
+
button {
|
|
456
|
+
padding: 0px;
|
|
457
|
+
|
|
458
|
+
span {
|
|
459
|
+
font-size: 16px;
|
|
460
|
+
line-height: 24px;
|
|
461
|
+
color: var(--color-grey-5);
|
|
462
|
+
font-weight: 400;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
&:hover {
|
|
466
|
+
span {
|
|
467
|
+
color: var(--color-primary-blue-2);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
|
|
473
|
+
const MonthBar = styled.div`
|
|
474
|
+
display: flex;
|
|
475
|
+
justify-content: space-between;
|
|
476
|
+
align-items: center;
|
|
477
|
+
margin-bottom: 32px;
|
|
478
|
+
user-select: none;
|
|
479
|
+
`;
|
|
480
|
+
|
|
481
|
+
const Arrow = styled.button`
|
|
482
|
+
appearance: none;
|
|
483
|
+
border: none;
|
|
484
|
+
outline: none;
|
|
485
|
+
padding: 12px;
|
|
486
|
+
display: flex;
|
|
487
|
+
justify-content: center;
|
|
488
|
+
align-items: center;
|
|
489
|
+
border-radius: 50%;
|
|
490
|
+
cursor: pointer;
|
|
491
|
+
background-color: transparent;
|
|
492
|
+
|
|
493
|
+
&:hover {
|
|
494
|
+
background-color: var(--color-primary-blue-2);
|
|
495
|
+
border-color: #ffffff;
|
|
496
|
+
svg {
|
|
497
|
+
fill: #ffffff;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
`;
|
|
501
|
+
|
|
502
|
+
const WeekdayWrapper = styled.div`
|
|
503
|
+
display: flex;
|
|
504
|
+
gap: 32px;
|
|
505
|
+
user-select: none;
|
|
506
|
+
`;
|
|
507
|
+
|
|
508
|
+
const Calendar = styled.div`
|
|
509
|
+
display: flex;
|
|
510
|
+
flex-direction: column;
|
|
511
|
+
gap: 32px;
|
|
512
|
+
margin: 32px 0px;
|
|
513
|
+
user-select: none;
|
|
514
|
+
`;
|
|
515
|
+
|
|
516
|
+
const Week = styled.div`
|
|
517
|
+
display: flex;
|
|
518
|
+
gap: 32px;
|
|
519
|
+
border-radius: 24px;
|
|
520
|
+
`;
|
|
521
|
+
|
|
522
|
+
const Day = styled.button`
|
|
523
|
+
outline: none;
|
|
524
|
+
border: none;
|
|
525
|
+
background: transparent;
|
|
526
|
+
display: flex;
|
|
527
|
+
align-items: center;
|
|
528
|
+
justify-content: center;
|
|
529
|
+
width: 32px;
|
|
530
|
+
height: 32px;
|
|
531
|
+
border-radius: 50%;
|
|
532
|
+
cursor: pointer;
|
|
533
|
+
font-family: Inter;
|
|
534
|
+
font-size: 16px;
|
|
535
|
+
line-height: 24px;
|
|
536
|
+
|
|
537
|
+
&.today {
|
|
538
|
+
color: var(--color-primary-blue-2);
|
|
539
|
+
font-weight: 600;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
&:hover:not(&[class~='start-date']):not(&[class~='end-date']) {
|
|
543
|
+
background-color: var(--color-primary-blue-4);
|
|
544
|
+
color: var(--color-primary-blue-1);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
&[class~='not-current'] {
|
|
548
|
+
color: var(--color-grey-9);
|
|
549
|
+
cursor: default;
|
|
550
|
+
pointer-events: none;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
&[class~='start-date'],
|
|
554
|
+
&[class~='end-date'] {
|
|
555
|
+
background-color: var(--color-primary-blue-2);
|
|
556
|
+
color: #ffffff;
|
|
557
|
+
|
|
558
|
+
&[class~='not-current'] {
|
|
559
|
+
background-color: var(--color-blue-9);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
&[class~='start-date'] + [class~='end-date'] {
|
|
564
|
+
position: relative;
|
|
565
|
+
&::before {
|
|
566
|
+
content: '';
|
|
567
|
+
position: absolute;
|
|
568
|
+
z-index: -2;
|
|
569
|
+
height: 32px;
|
|
570
|
+
width: 64px;
|
|
571
|
+
background-color: var(--color-primary-blue-3);
|
|
572
|
+
right: 16px;
|
|
573
|
+
top: 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
&[class~='not-current'] {
|
|
577
|
+
&::before {
|
|
578
|
+
background-color: var(--color-blue-10);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
&[class~='not-current'] {
|
|
584
|
+
position: relative;
|
|
585
|
+
&[class~='end-date']:not(:first-of-type):not(.start-date) {
|
|
586
|
+
&::before {
|
|
587
|
+
content: '';
|
|
588
|
+
position: absolute;
|
|
589
|
+
z-index: -2;
|
|
590
|
+
height: 32px;
|
|
591
|
+
width: 62px;
|
|
592
|
+
background-color: var(--color-blue-10);
|
|
593
|
+
right: 16px;
|
|
594
|
+
top: 0;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
&[class~='first-of-month'] {
|
|
600
|
+
&[class~='end-date'] {
|
|
601
|
+
&::before {
|
|
602
|
+
background-color: var(--color-blue-10);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
&[class~='between'] {
|
|
608
|
+
position: relative;
|
|
609
|
+
|
|
610
|
+
&::before {
|
|
611
|
+
content: '';
|
|
612
|
+
position: absolute;
|
|
613
|
+
z-index: -2;
|
|
614
|
+
height: 32px;
|
|
615
|
+
width: 132px;
|
|
616
|
+
background-color: var(--color-primary-blue-3);
|
|
617
|
+
right: -48px;
|
|
618
|
+
top: 0;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
&:first-of-type::before {
|
|
622
|
+
width: 80px;
|
|
623
|
+
border-radius: 24px 0px 0px 24px;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
&:last-of-type::before {
|
|
627
|
+
width: 80px;
|
|
628
|
+
right: 0px;
|
|
629
|
+
border-radius: 0px 24px 24px 0px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
&[class~='not-current'] {
|
|
633
|
+
&::before {
|
|
634
|
+
background-color: var(--color-blue-10);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
&[class~='first-of-month']:not(:first-of-type) {
|
|
639
|
+
&::before {
|
|
640
|
+
width: 32px;
|
|
641
|
+
border-top-left-radius: 24px;
|
|
642
|
+
border-bottom-left-radius: 24px;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
&[class~='first-of-month']:not([class~='not-current']):not(
|
|
647
|
+
:first-of-type
|
|
648
|
+
):not(:last-of-type) {
|
|
649
|
+
&::before {
|
|
650
|
+
width: 64px;
|
|
651
|
+
border-top-left-radius: 24px;
|
|
652
|
+
border-bottom-left-radius: 24px;
|
|
653
|
+
background-color: var(--color-blue-10);
|
|
654
|
+
right: 16px;
|
|
655
|
+
}
|
|
656
|
+
&::after {
|
|
657
|
+
content: '';
|
|
658
|
+
position: absolute;
|
|
659
|
+
z-index: -2;
|
|
660
|
+
height: 32px;
|
|
661
|
+
width: 70px;
|
|
662
|
+
background-color: var(--color-primary-blue-3);
|
|
663
|
+
right: -32px;
|
|
664
|
+
top: 0;
|
|
665
|
+
border-top-left-radius: 24px;
|
|
666
|
+
border-bottom-left-radius: 24px;
|
|
667
|
+
}
|
|
668
|
+
&:last-of-type {
|
|
669
|
+
&::before {
|
|
670
|
+
width: 32px;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
&[class~='last-of-month']:not(&[class~='not-current']) {
|
|
676
|
+
&::before {
|
|
677
|
+
z-index: -1;
|
|
678
|
+
border-radius: 0px 24px 24px 0px;
|
|
679
|
+
width: 80px;
|
|
680
|
+
right: 0px;
|
|
681
|
+
}
|
|
682
|
+
&:first-of-type {
|
|
683
|
+
&::before {
|
|
684
|
+
border-top-left-radius: 24px;
|
|
685
|
+
border-bottom-left-radius: 24px;
|
|
686
|
+
width: 32px;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
`;
|
|
692
|
+
|
|
693
|
+
const BottomWrapper = styled.div`
|
|
694
|
+
display: flex;
|
|
695
|
+
justify-content: flex-end;
|
|
696
|
+
gap: 18px;
|
|
697
|
+
`;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Checkbox } from './checkbox';
|
|
2
2
|
export type { CheckboxProps } from './checkbox';
|
|
3
|
+
export { DatePicker } from './date';
|
|
4
|
+
export type { DatePickerProps } from './date';
|
|
3
5
|
export { Filter } from './filter';
|
|
4
6
|
export type { FilterProps, FilterTriggerProps } from './filter';
|
|
5
7
|
export { Radio } from './radio';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { THIS_MONTH, THIS_YEAR, CALENDAR_WEEKS } from 'common';
|
|
2
|
+
|
|
3
|
+
export const zeroPad = (value: number, length: number) => {
|
|
4
|
+
return `${value}`.padStart(length, '0');
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getMonthDays = (
|
|
8
|
+
month: number = THIS_MONTH,
|
|
9
|
+
year: number = THIS_YEAR
|
|
10
|
+
) => {
|
|
11
|
+
const MONTHS_WITH_30 = [4, 6, 9, 11];
|
|
12
|
+
const leapYear = year % 4 === 0;
|
|
13
|
+
return month === 2
|
|
14
|
+
? leapYear
|
|
15
|
+
? 29
|
|
16
|
+
: 28
|
|
17
|
+
: MONTHS_WITH_30.includes(month)
|
|
18
|
+
? 30
|
|
19
|
+
: 31;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const getMonthFirstDay = (month = THIS_MONTH, year = THIS_YEAR) => {
|
|
23
|
+
return +new Date(`${year}-${zeroPad(month, 2)}-01`).getDay() + 1;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const isDate = (date: Date) => {
|
|
27
|
+
const isDate = Object.prototype.toString.call(date) === '[object Date]';
|
|
28
|
+
const isValidDate = date && !Number.isNaN(date.valueOf());
|
|
29
|
+
|
|
30
|
+
return isDate && isValidDate;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const isSameMonth = (date: Date, basedate = new Date()) => {
|
|
34
|
+
if (!(isDate(date) && isDate(basedate))) return false;
|
|
35
|
+
const basedateMonth = +basedate.getMonth() + 1;
|
|
36
|
+
const basedateYear = basedate.getFullYear();
|
|
37
|
+
const dateMonth = +date.getMonth() + 1;
|
|
38
|
+
const dateYear = date.getFullYear();
|
|
39
|
+
return +basedateMonth === +dateMonth && +basedateYear === +dateYear;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const isSameDay = (date: Date, basedate = new Date()) => {
|
|
43
|
+
if (!(isDate(date) && isDate(basedate))) return false;
|
|
44
|
+
const basedateDate = basedate.getDate();
|
|
45
|
+
const basedateMonth = +basedate.getMonth() + 1;
|
|
46
|
+
const basedateYear = basedate.getFullYear();
|
|
47
|
+
const dateDate = date.getDate();
|
|
48
|
+
const dateMonth = +date.getMonth() + 1;
|
|
49
|
+
const dateYear = date.getFullYear();
|
|
50
|
+
return (
|
|
51
|
+
+basedateDate === +dateDate &&
|
|
52
|
+
+basedateMonth === +dateMonth &&
|
|
53
|
+
+basedateYear === +dateYear
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getDateISO = (date = new Date()) => {
|
|
58
|
+
if (!isDate(date)) return null;
|
|
59
|
+
return [
|
|
60
|
+
date.getFullYear(),
|
|
61
|
+
zeroPad(+date.getMonth() + 1, 2),
|
|
62
|
+
zeroPad(+date.getDate(), 2),
|
|
63
|
+
].join('-');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const getPreviousMonth = (month: number, year: number) => {
|
|
67
|
+
const prevMonth = month > 1 ? month - 1 : 12;
|
|
68
|
+
const prevMonthYear = month > 1 ? year : year - 1;
|
|
69
|
+
return { month: prevMonth, year: prevMonthYear };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const getNextMonth = (month: number, year: number) => {
|
|
73
|
+
const nextMonth = month < 12 ? month + 1 : 1;
|
|
74
|
+
const nextMonthYear = month < 12 ? year : year + 1;
|
|
75
|
+
return { month: nextMonth, year: nextMonthYear };
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const buildCalendar = (month = THIS_MONTH, year = THIS_YEAR) => {
|
|
79
|
+
const monthDays = getMonthDays(month, year);
|
|
80
|
+
const monthFirstDay = getMonthFirstDay(month, year);
|
|
81
|
+
const daysFromPrevMonth = monthFirstDay - 1;
|
|
82
|
+
const daysFromNextMonth =
|
|
83
|
+
CALENDAR_WEEKS * 7 - (daysFromPrevMonth + monthDays);
|
|
84
|
+
const { month: prevMonth, year: prevMonthYear } = getPreviousMonth(
|
|
85
|
+
month,
|
|
86
|
+
year
|
|
87
|
+
);
|
|
88
|
+
const { month: nextMonth, year: nextMonthYear } = getNextMonth(month, year);
|
|
89
|
+
const prevMonthDays = getMonthDays(prevMonth, prevMonthYear);
|
|
90
|
+
const prevMonthDates = [...new Array(daysFromPrevMonth)].map((n, index) => {
|
|
91
|
+
const day = index + 1 + (prevMonthDays - daysFromPrevMonth);
|
|
92
|
+
return [prevMonthYear, zeroPad(prevMonth, 2), zeroPad(day, 2)];
|
|
93
|
+
});
|
|
94
|
+
const thisMonthDates = [...new Array(monthDays)].map((n, index) => {
|
|
95
|
+
const day = index + 1;
|
|
96
|
+
return [year, zeroPad(month, 2), zeroPad(day, 2)];
|
|
97
|
+
});
|
|
98
|
+
const nextMonthDates = [...new Array(daysFromNextMonth)].map((n, index) => {
|
|
99
|
+
const day = index + 1;
|
|
100
|
+
return [nextMonthYear, zeroPad(nextMonth, 2), zeroPad(day, 2)];
|
|
101
|
+
});
|
|
102
|
+
return calendarIntoWeeks([
|
|
103
|
+
...prevMonthDates,
|
|
104
|
+
...thisMonthDates,
|
|
105
|
+
...nextMonthDates,
|
|
106
|
+
]);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const calendarIntoWeeks = (
|
|
110
|
+
calendar: (string | number)[][]
|
|
111
|
+
): (string | number)[][][] => {
|
|
112
|
+
const res = [];
|
|
113
|
+
while (calendar.length > 0) {
|
|
114
|
+
const chunk = calendar.splice(0, 7);
|
|
115
|
+
res.push(chunk);
|
|
116
|
+
}
|
|
117
|
+
return res;
|
|
118
|
+
};
|