Skip to content

Commit 85d3d06

Browse files
committed
feat: timeselector, hourStep, minuteStep, secondStep
1 parent b47f6fc commit 85d3d06

File tree

17 files changed

+634
-82
lines changed

17 files changed

+634
-82
lines changed

package/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shinyongjun/react-datepicker",
3-
"version": "1.11.0",
3+
"version": "1.12.0",
44
"main": "./dist/cjs/index.js",
55
"module": "./dist/esm/index.js",
66
"source": "./src/index.tsx",

package/src/assets/ReactDatepicker.css

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,8 @@
3333
.react-datepicker__clear:hover {
3434
background-color: #f7f7f7;
3535
}
36-
.react-datepicker__layer {
37-
position: absolute;
38-
top: 50px;
39-
left: 0;
40-
z-index: 1000;
41-
box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 4px;
42-
}
36+
37+
/* portal */
4338
.react-datepicker__portal {
4439
position: fixed;
4540
inset: 0;
@@ -53,25 +48,39 @@
5348
position: relative;
5449
inset: auto;
5550
}
56-
.react-datepicker__datepicker-container {
51+
52+
/* layer */
53+
.react-datepicker__layer {
54+
display: flex;
55+
align-items: flex-start;
56+
position: absolute;
57+
top: 50px;
58+
left: 0;
59+
z-index: 1000;
60+
box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 4px;
61+
border: 1px solid #ddd;
5762
background-color: #fff;
5863
}
5964

60-
/* controller */
65+
/* datepicker */
66+
.react-datepicker__datepicker-container {
67+
/* flex-grow: 1; */
68+
}
6169
.react-datepicker__controller {
6270
display: flex;
71+
border-bottom: 1px solid #ddd;
6372
}
64-
.react-datepicker__label {
73+
.react-datepicker__controller-label {
6574
height: 36px;
6675
flex-grow: 1;
6776
border: none;
6877
background-color: transparent;
6978
cursor: pointer;
7079
}
71-
.react-datepicker__label:not(:disabled):hover {
80+
.react-datepicker__controller-label:not(:disabled):hover {
7281
background-color: #f7f7f7;
7382
}
74-
.react-datepicker__label:disabled {
83+
.react-datepicker__controller-label:disabled {
7584
cursor: default;
7685
}
7786
.react-datepicker__controller-arrow {
@@ -142,7 +151,7 @@
142151
background-color: #f7f7f7;
143152
}
144153
.react-datepicker__datepicker-button[data-active='true'] {
145-
background-color: #999;
154+
background-color: #333;
146155
color: #fff;
147156
}
148157
.react-datepicker__datepicker-button[data-hover-active='true'] {
@@ -152,12 +161,66 @@
152161
background-color: #eee;
153162
}
154163
.react-datepicker__datepicker-button[data-start-active='true'] {
155-
background-color: #999;
164+
background-color: #333;
156165
color: #fff;
157166
border-radius: 6px 0 0 6px;
158167
}
159168
.react-datepicker__datepicker-button[data-end-active='true'] {
160-
background-color: #999;
169+
background-color: #333;
161170
color: #fff;
162171
border-radius: 0 6px 6px 0;
163172
}
173+
174+
/* timeselector */
175+
.react-datepicker__timeselector-container {
176+
display: flex;
177+
flex-flow: column;
178+
position: relative;
179+
border-left: 1px solid #ddd;
180+
}
181+
.react-datepicker__timeselector-header {
182+
display: flex;
183+
justify-content: center;
184+
align-items: center;
185+
height: 36px;
186+
flex-shrink: 0;
187+
border-bottom: 1px solid #ddd;
188+
}
189+
.react-datepicker__timeselector-selector {
190+
overflow: hidden;
191+
display: flex;
192+
}
193+
.react-datepicker__timeselector-selector .react-datepicker__timeselector-list {
194+
overflow-y: auto;
195+
width: 40px;
196+
/* -ms-overflow-style: none;
197+
scrollbar-width: none; */
198+
scrollbar-width: thin;
199+
}
200+
/* .react-datepicker__timeselector-selector
201+
.react-datepicker__timeselector-list::-webkit-scrollbar {
202+
scrollbar-width: thin;
203+
} */
204+
.react-datepicker__timeselector-selector
205+
.react-datepicker__timeselector-list-item {
206+
display: block;
207+
}
208+
.react-datepicker__timeselector-selector
209+
.react-datepicker__timeselector-list-button {
210+
display: block;
211+
width: 100%;
212+
height: 36px;
213+
background: none;
214+
border: none;
215+
border-radius: none;
216+
cursor: pointer;
217+
}
218+
.react-datepicker__timeselector-selector
219+
.react-datepicker__timeselector-list-button:hover {
220+
background-color: #f7f7f7;
221+
}
222+
.react-datepicker__timeselector-selector
223+
.react-datepicker__timeselector-list-button[data-active='true'] {
224+
background-color: #333;
225+
color: #fff;
226+
}

package/src/components/Datepicker.tsx

Lines changed: 108 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
'use client';
22

33
import * as React from 'react';
4-
import { useEffect, useMemo, useRef, useState } from 'react';
4+
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
55
import { NAME_SPACE } from '../constants/core';
6+
import { useElementSize } from '../hooks/useElementSize';
67
import useOutsideClick from '../hooks/useOutsideClick';
8+
import { IDateValue, ITimeValue, ITimeselector } from '../types/props';
79
import { formatDate } from '../utils/datetime';
810
import { setMonthPage } from '../utils/page';
911
import Layer from './common/Layer';
@@ -13,6 +15,8 @@ import DatepickerDecade from './datepicker/Decade';
1315
import DatepickerMonth from './datepicker/Month';
1416
import DatepickerYear from './datepicker/Year';
1517
import InputDate from './input/Date';
18+
import TimeselectorHeader from './timeselector/Header';
19+
import TimeselectorSelector from './timeselector/Selector';
1620

1721
interface IProps {
1822
initValue?: Date | null;
@@ -26,26 +30,47 @@ interface IProps {
2630
className?: string;
2731
placeholder?: string;
2832
disabled?: boolean;
33+
timeselector?: false | ITimeselector;
34+
hourStep?: number;
35+
minuteStep?: number;
36+
secondStep?: number;
2937
onChange?: (activeDate: Date | null) => void;
3038
}
3139

3240
function Datepicker({
3341
initValue = null,
3442
useClearButton = false,
3543
showsMultipleCalendar = false,
36-
valueFormat = 'YYYY-MM-DD',
44+
valueFormat = '',
3745
labelFormat = 'YYYY / MM',
3846
closesAfterChange = true,
3947
weekdayLabels = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
4048
withPortal = false,
4149
className = '',
4250
placeholder = '',
4351
disabled = false,
52+
timeselector = false,
53+
hourStep = 1,
54+
minuteStep = 1,
55+
secondStep = 1,
4456
onChange,
4557
}: IProps) {
46-
// 인수가 없을 땐 LOCAL 기준 현재 시간을 반환한다.
4758
const NEW_DATE = new Date();
59+
const initialValueFormat = timeselector
60+
? 'YYYY-MM-DD HH:mm:ss'
61+
: 'YYYY-MM-DD';
62+
const comValueFormat = valueFormat ? valueFormat : initialValueFormat;
4863
const [value, setValue] = useState<Date | null>(initValue);
64+
const [timeValue, setTimeValue] = useState<ITimeValue>({
65+
hour: initValue?.getHours() || 0,
66+
minute: initValue?.getMinutes() || 0,
67+
second: initValue?.getSeconds() || 0,
68+
});
69+
const [dateValue, setDateValue] = useState<IDateValue>({
70+
year: initValue?.getFullYear() || null,
71+
month: initValue?.getMonth() || null,
72+
date: initValue?.getDate() || null,
73+
});
4974
const [viewDate, setViewDate] = useState<string>(
5075
formatDate(value || NEW_DATE, 'YYYY-MM-DD')
5176
);
@@ -56,14 +81,15 @@ function Datepicker({
5681

5782
const monthPage = useMemo(() => setMonthPage(viewDate), [viewDate]);
5883
const layer = useRef(null);
84+
const [, datepickerContainerRef, { height: datepickerContainerHeight }] =
85+
useElementSize();
5986

6087
useOutsideClick(layer, () => {
6188
setIsVisible(false);
6289
});
6390

6491
useEffect(() => {
65-
// setViewDate(formatDate(value || NEW_DATE, 'YYYY-MM-DD'));
66-
if (closesAfterChange) {
92+
if (closesAfterChange && !timeselector) {
6793
setIsVisible(false);
6894
}
6995
if (onChange) {
@@ -72,11 +98,48 @@ function Datepicker({
7298
// eslint-disable-next-line react-hooks/exhaustive-deps
7399
}, [value, onChange, setViewDate]);
74100

101+
useEffect(() => {
102+
if (!value) return;
103+
104+
const newDate = new Date(
105+
value.getFullYear(),
106+
value.getMonth(),
107+
value.getDate(),
108+
timeValue.hour,
109+
timeValue.minute,
110+
timeValue.second
111+
);
112+
113+
setValue(newDate);
114+
// eslint-disable-next-line react-hooks/exhaustive-deps
115+
}, [timeValue]);
116+
117+
useEffect(() => {
118+
if (
119+
dateValue.year === null ||
120+
dateValue.month === null ||
121+
dateValue.date === null
122+
)
123+
return;
124+
125+
const newDate = new Date(
126+
dateValue.year,
127+
dateValue.month,
128+
dateValue.date,
129+
value?.getHours() || 0,
130+
value?.getMinutes() || 0,
131+
value?.getSeconds() || 0
132+
);
133+
134+
setValue(newDate);
135+
// eslint-disable-next-line react-hooks/exhaustive-deps
136+
}, [dateValue]);
137+
75138
return (
76139
<div className={`${NAME_SPACE}__wrapper ${className}`}>
77140
<InputDate
78141
value={value}
79-
valueFormat={valueFormat}
142+
valueFormat={comValueFormat}
80143
useClearButton={useClearButton}
81144
placeholder={placeholder}
82145
disabled={disabled}
@@ -88,7 +151,10 @@ function Datepicker({
88151
setIsVisible={setIsVisible}
89152
withPortal={withPortal}
90153
>
91-
<div className={`${NAME_SPACE}__datepicker-container`}>
154+
<div
155+
className={`${NAME_SPACE}__datepicker-container`}
156+
ref={datepickerContainerRef}
157+
>
92158
<ControllerContainer
93159
viewDate={viewDate}
94160
viewType={viewType}
@@ -98,26 +164,20 @@ function Datepicker({
98164
setViewDate={setViewDate}
99165
/>
100166
<div className={`${NAME_SPACE}__datepicker`}>
101-
{viewType === 'month' && (
102-
<>
103-
<DatepickerMonth
104-
value={value}
105-
valueFormat={valueFormat}
106-
monthPage={monthPage}
107-
weekdayLabels={weekdayLabels}
108-
setValue={setValue}
109-
/>
110-
{showsMultipleCalendar && (
111-
<DatepickerMonth
112-
value={value}
113-
valueFormat={valueFormat}
114-
monthPage={monthPage + 1}
115-
weekdayLabels={weekdayLabels}
116-
setValue={setValue}
117-
/>
118-
)}
119-
</>
120-
)}
167+
{viewType === 'month' &&
168+
[true, showsMultipleCalendar].map((isShow, index) => (
169+
<Fragment key={index}>
170+
{isShow && (
171+
<DatepickerMonth
172+
dateValue={dateValue}
173+
setDateValue={setDateValue}
174+
valueFormat={comValueFormat}
175+
monthPage={monthPage + index}
176+
weekdayLabels={weekdayLabels}
177+
/>
178+
)}
179+
</Fragment>
180+
))}
121181
{viewType === 'year' && (
122182
<DatepickerYear
123183
value={value}
@@ -144,6 +204,27 @@ function Datepicker({
144204
)}
145205
</div>
146206
</div>
207+
{timeselector && (
208+
<div
209+
className={`${NAME_SPACE}__timeselector-container`}
210+
style={{
211+
height: datepickerContainerHeight,
212+
}}
213+
>
214+
<TimeselectorHeader
215+
timeValue={timeValue}
216+
timeselector={timeselector}
217+
/>
218+
<TimeselectorSelector
219+
timeValue={timeValue}
220+
setTimeValue={setTimeValue}
221+
timeselector={timeselector}
222+
hourStep={hourStep}
223+
minuteStep={minuteStep}
224+
secondStep={secondStep}
225+
/>
226+
</div>
227+
)}
147228
</Layer>
148229
</div>
149230
);

package/src/components/controller/Arrow.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function ControllerArrow({
5353

5454
return (
5555
<button
56+
type="button"
5657
className={`${NAME_SPACE}__controller-arrow ${NAME_SPACE}__controller-${direction}`}
5758
onClick={() => handleControl(direction)}
5859
>

0 commit comments

Comments
 (0)