Skip to content

Commit 85b085b

Browse files
committed
feat: withPortal, exports CSS
1 parent ae86fee commit 85b085b

File tree

9 files changed

+200
-61
lines changed

9 files changed

+200
-61
lines changed

package/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shinyongjun/react-datepicker",
3-
"version": "1.8.0",
3+
"version": "1.9.0",
44
"main": "./dist/cjs/index.js",
55
"module": "./dist/esm/index.js",
66
"source": "./src/index.tsx",
@@ -12,7 +12,8 @@
1212
".": {
1313
"import": "./dist/esm/index.js",
1414
"require": "./dist/cjs/index.js"
15-
}
15+
},
16+
"./css": "./dist/assets/ReactDatepicker.css"
1617
},
1718
"description": "Datepicker component in React App.",
1819
"keywords": [

package/src/assets/ReactDatepicker.css

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,29 @@
3333
.react-datepicker__clear:hover {
3434
background-color: #f7f7f7;
3535
}
36-
.react-datepicker__datepicker-container {
36+
.react-datepicker__layer {
3737
position: absolute;
3838
top: 50px;
3939
left: 0;
4040
z-index: 1000;
41-
background-color: #fff;
4241
box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 4px;
4342
}
43+
.react-datepicker__portal {
44+
position: fixed;
45+
inset: 0;
46+
z-index: 1000;
47+
display: flex;
48+
align-items: center;
49+
justify-content: center;
50+
background-color: rgba(0, 0, 0, 0.7);
51+
}
52+
.react-datepicker__portal .react-datepicker__layer {
53+
position: relative;
54+
inset: auto;
55+
}
56+
.react-datepicker__datepicker-container {
57+
background-color: #fff;
58+
}
4459

4560
/* controller */
4661
.react-datepicker__controller {

package/src/components/Datepicker.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
'use client';
22

3-
import '../../assets/ReactDatepicker.css';
43
import * as React from 'react';
5-
import { useState, useMemo, useRef, useEffect } from 'react';
4+
import { useEffect, useMemo, useRef, useState } from 'react';
5+
import { NAME_SPACE } from '../constants/core';
6+
import useOutsideClick from '../hooks/useOutsideClick';
67
import { formatDate } from '../utils/datetime';
78
import { setMonthPage } from '../utils/page';
8-
import { NAME_SPACE } from '../constants/core';
9+
import Layer from './common/Layer';
10+
import ControllerContainer from './controller/Container';
11+
import DatepickerCentury from './datepicker/Century';
12+
import DatepickerDecade from './datepicker/Decade';
913
import DatepickerMonth from './datepicker/Month';
1014
import DatepickerYear from './datepicker/Year';
11-
import DatepickerDecade from './datepicker/Decade';
12-
import DatepickerCentury from './datepicker/Century';
13-
import useOutsideClick from '../hooks/useOutsideClick';
14-
import ControllerContainer from './controller/Container';
1515
import InputDate from './input/Date';
1616

1717
interface IProps {
@@ -22,6 +22,7 @@ interface IProps {
2222
labelFormat?: string;
2323
closesAfterChange?: boolean;
2424
weekdayLabels?: string[];
25+
withPortal?: boolean;
2526
onChange?: (activeDate: Date | null) => void;
2627
}
2728

@@ -33,6 +34,7 @@ function Datepicker({
3334
labelFormat = 'YYYY / MM',
3435
closesAfterChange = true,
3536
weekdayLabels = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
37+
withPortal = false,
3638
onChange,
3739
}: IProps) {
3840
// 인수가 없을 땐 LOCAL 기준 현재 시간을 반환한다.
@@ -47,9 +49,9 @@ function Datepicker({
4749
const [isVisible, setIsVisible] = useState<boolean>(false);
4850

4951
const monthPage = useMemo(() => setMonthPage(viewDate), [viewDate]);
50-
const container = useRef(null);
52+
const layer = useRef(null);
5153

52-
useOutsideClick(container, () => {
54+
useOutsideClick(layer, () => {
5355
setIsVisible(false);
5456
});
5557

@@ -73,8 +75,12 @@ function Datepicker({
7375
setIsVisible={setIsVisible}
7476
useClearButton={useClearButton}
7577
/>
76-
{isVisible && (
77-
<div className={`${NAME_SPACE}__datepicker-container`} ref={container}>
78+
<Layer
79+
isVisible={isVisible}
80+
setIsVisible={setIsVisible}
81+
withPortal={withPortal}
82+
>
83+
<div className={`${NAME_SPACE}__datepicker-container`}>
7884
<ControllerContainer
7985
viewDate={viewDate}
8086
viewType={viewType}
@@ -130,7 +136,7 @@ function Datepicker({
130136
)}
131137
</div>
132138
</div>
133-
)}
139+
</Layer>
134140
</div>
135141
);
136142
}

package/src/components/Rangepicker.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
'use client';
22

3-
import '../../assets/ReactDatepicker.css';
43
import * as React from 'react';
5-
import { useState, useMemo, useRef, useEffect } from 'react';
6-
import { formatDate } from '../utils/datetime';
7-
import { setMonthPage } from '../utils/page';
4+
import { useEffect, useMemo, useRef, useState } from 'react';
85
import { NAME_SPACE } from '../constants/core';
9-
import RangepickerMonth from './rangepicker/Month';
10-
import DatepickerYear from './datepicker/Year';
11-
import DatepickerDecade from './datepicker/Decade';
12-
import DatepickerCentury from './datepicker/Century';
136
import useOutsideClick from '../hooks/useOutsideClick';
7+
import { formatDate } from '../utils/datetime';
8+
import { setMonthPage } from '../utils/page';
9+
import Layer from './common/Layer';
1410
import ControllerContainer from './controller/Container';
11+
import DatepickerCentury from './datepicker/Century';
12+
import DatepickerDecade from './datepicker/Decade';
13+
import DatepickerYear from './datepicker/Year';
1514
import InputRange from './input/Range';
15+
import RangepickerMonth from './rangepicker/Month';
1616

1717
interface IProps {
1818
// initValue?: Date | null;
@@ -24,6 +24,7 @@ interface IProps {
2424
labelFormat?: string;
2525
closesAfterChange?: boolean;
2626
weekdayLabels?: string[];
27+
withPortal?: boolean;
2728
onChange?: (startDate: Date | null, endDate: Date | null) => void;
2829
}
2930

@@ -36,6 +37,7 @@ function Rangepicker({
3637
labelFormat = 'YYYY / MM',
3738
closesAfterChange = true,
3839
weekdayLabels = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
40+
withPortal = false,
3941
onChange,
4042
}: IProps) {
4143
// 인수가 없을 땐 LOCAL 기준 현재 시간을 반환한다.
@@ -52,9 +54,9 @@ function Rangepicker({
5254
const [isVisible, setIsVisible] = useState<boolean>(false);
5355

5456
const monthPage = useMemo(() => setMonthPage(viewDate), [viewDate]);
55-
const container = useRef(null);
57+
const layer = useRef(null);
5658

57-
useOutsideClick(container, () => {
59+
useOutsideClick(layer, () => {
5860
setIsVisible(false);
5961
});
6062

@@ -84,8 +86,12 @@ function Rangepicker({
8486
setStartValue={setStartValue}
8587
setEndValue={setEndValue}
8688
/>
87-
{isVisible && (
88-
<div className={`${NAME_SPACE}__datepicker-container`} ref={container}>
89+
<Layer
90+
isVisible={isVisible}
91+
setIsVisible={setIsVisible}
92+
withPortal={withPortal}
93+
>
94+
<div className={`${NAME_SPACE}__datepicker-container`}>
8995
<ControllerContainer
9096
viewDate={viewDate}
9197
viewType={viewType}
@@ -149,7 +155,7 @@ function Rangepicker({
149155
)}
150156
</div>
151157
</div>
152-
)}
158+
</Layer>
153159
</div>
154160
);
155161
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface IProps {
2+
condition: boolean;
3+
wrapper: (children: React.ReactNode) => React.ReactNode;
4+
children: React.ReactNode;
5+
}
6+
7+
export default function ConditionalWrapper({
8+
condition,
9+
wrapper,
10+
children,
11+
}: IProps) {
12+
return condition ? wrapper(children) : children;
13+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { useRef } from 'react';
5+
import { NAME_SPACE } from '../../constants/core';
6+
import useOutsideClick from '../../hooks/useOutsideClick';
7+
import ConditionalWrapper from './ConditionalWrapper';
8+
import Portal from './Portal';
9+
10+
interface IProps {
11+
isVisible: boolean;
12+
setIsVisible: (isVisible: boolean) => void;
13+
withPortal: boolean;
14+
children: React.ReactNode;
15+
}
16+
17+
function Layer({ isVisible, setIsVisible, withPortal, children }: IProps) {
18+
const layer = useRef(null);
19+
20+
useOutsideClick(layer, () => {
21+
setIsVisible(false);
22+
});
23+
24+
return (
25+
<>
26+
{isVisible && (
27+
<ConditionalWrapper
28+
condition={withPortal}
29+
wrapper={(children) => (
30+
<Portal selector="body">
31+
<div className={`${NAME_SPACE}__portal`}>{children}</div>
32+
</Portal>
33+
)}
34+
>
35+
<div className={`${NAME_SPACE}__layer`} ref={layer}>
36+
{children}
37+
</div>
38+
</ConditionalWrapper>
39+
)}
40+
</>
41+
);
42+
}
43+
44+
export default Layer;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createPortal } from 'react-dom';
2+
3+
interface IProps {
4+
children: React.ReactNode;
5+
selector: string;
6+
}
7+
8+
const Portal = ({ children, selector }: IProps) => {
9+
const element =
10+
typeof window !== 'undefined' && document.querySelector(selector);
11+
return element && children ? createPortal(children, element) : null;
12+
};
13+
14+
export default Portal;

test/src/App.tsx

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as React from 'react';
21
import { Datepicker, Rangepicker } from '@shinyongjun/react-datepicker';
32
import { useState } from 'react';
43

@@ -7,37 +6,77 @@ function App() {
76

87
return (
98
<div>
10-
<Datepicker
11-
initValue={dateValue}
12-
onChange={(value) => {
13-
console.log('value', value);
14-
}}
15-
/>
16-
<Datepicker />
17-
<Datepicker showsMultipleCalendar />
18-
<Datepicker useClearButton />
19-
<Datepicker valueFormat="MM/DD/YYYY" />
20-
<Datepicker labelFormat="YYYY년 MM월" />
21-
<Rangepicker />
22-
<Rangepicker showsMultipleCalendar />
23-
<Rangepicker closesAfterChange={false} />
24-
<Rangepicker
25-
weekdayLabels={['일', '월', '화', '수', '목', '금', '토']}
26-
showsMultipleCalendar
27-
closesAfterChange={false}
28-
/>
29-
<Rangepicker
30-
weekdayLabels={['일', '월', '화', '수', '목', '금', '토']}
31-
showsMultipleCalendar
32-
closesAfterChange={false}
33-
onChange={(startDate, endDate) => {
34-
console.log(startDate, endDate);
35-
}}
36-
/>
37-
<Rangepicker
38-
initStartValue={new Date(2023, 7, 1)}
39-
initEndValue={new Date(2023, 8, 7)}
40-
/>
9+
<section>
10+
<h3>Datepicker</h3>
11+
<Datepicker />
12+
</section>
13+
<section>
14+
<h3>initValue</h3>
15+
<Datepicker
16+
initValue={dateValue}
17+
onChange={(value) => {
18+
console.log('value', value);
19+
}}
20+
/>
21+
</section>
22+
<section>
23+
<h3>showsMultipleCalendar</h3>
24+
<Datepicker showsMultipleCalendar />
25+
</section>
26+
<section>
27+
<h3>useClearButton</h3>
28+
<Datepicker useClearButton />
29+
</section>
30+
<section>
31+
<h3>valueFormat=MM/DD/YYYY</h3>
32+
<Datepicker valueFormat="MM/DD/YYYY" />
33+
</section>
34+
<section>
35+
<h3>labelFormat=YYYY년 MM월</h3>
36+
<Datepicker labelFormat="YYYY년 MM월" />
37+
</section>
38+
<section>
39+
<h3>Rangepicker</h3>
40+
<Rangepicker />
41+
</section>
42+
<section>
43+
<h3>Rangepicker - showsMultipleCalendar</h3>
44+
<Rangepicker showsMultipleCalendar />
45+
</section>
46+
<section>
47+
<h3>Rangepicker - showsMultipleCalendar, closesAfterChange</h3>
48+
<Rangepicker
49+
weekdayLabels={['일', '월', '화', '수', '목', '금', '토']}
50+
showsMultipleCalendar
51+
closesAfterChange={false}
52+
/>
53+
</section>
54+
<section>
55+
<h3>onChange</h3>
56+
<Rangepicker
57+
weekdayLabels={['일', '월', '화', '수', '목', '금', '토']}
58+
showsMultipleCalendar
59+
closesAfterChange={false}
60+
onChange={(startDate, endDate) => {
61+
console.log(startDate, endDate);
62+
}}
63+
/>
64+
</section>
65+
<section>
66+
<h3>initStartValue, initEndValue</h3>
67+
<Rangepicker
68+
initStartValue={new Date(2023, 7, 1)}
69+
initEndValue={new Date(2023, 8, 7)}
70+
/>
71+
</section>
72+
<section>
73+
<h3>withPortal</h3>
74+
<Datepicker withPortal={true} closesAfterChange={false} />
75+
</section>
76+
<section>
77+
<h3>datetime-local</h3>
78+
<input type="datetime-local" />
79+
</section>
4180
</div>
4281
);
4382
}

test/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import '@shinyongjun/react-datepicker/css';
12
import * as React from 'react';
23
import * as ReactDOM from 'react-dom/client';
34
import App from './App';

0 commit comments

Comments
 (0)