@jk-core/components 1.1.18 → 1.1.19
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/dist/index.js +1297 -995
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +8 -8
- package/dist/index.umd.cjs.map +1 -1
- package/dist/src/common/Carousel/index.d.ts +15 -0
- package/dist/src/common/RollingBanner/index.d.ts +11 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/utils/ts/formatMoney.d.ts +1 -1
- package/package.json +1 -1
- package/src/common/Carousel/Carousel.module.scss +222 -0
- package/src/common/Carousel/index.tsx +411 -0
- package/src/common/RollingBanner/RollingBanner.module.scss +126 -0
- package/src/common/RollingBanner/index.tsx +140 -0
- package/src/index.tsx +4 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
@keyframes scroll {
|
|
2
|
+
0% {
|
|
3
|
+
transform: translateX(0%);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
100% {
|
|
7
|
+
transform: translateX(-100%);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@keyframes scroll-column {
|
|
12
|
+
0% {
|
|
13
|
+
transform: translateY(0%);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
100% {
|
|
17
|
+
transform: translateY(-100%);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.marquee-container {
|
|
22
|
+
overflow-x: hidden;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
position: relative;
|
|
26
|
+
width: 100%;
|
|
27
|
+
|
|
28
|
+
&--column {
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
height: 100%;
|
|
32
|
+
width: auto;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.overlay {
|
|
37
|
+
position: absolute;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
|
|
41
|
+
@mixin gradient {
|
|
42
|
+
background: linear-gradient(to right, var(--gradient-color), rgb(255, 255, 255, 0));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@mixin gradientColumn {
|
|
46
|
+
background: linear-gradient(to bottom, var(--gradient-color), rgb(255, 255, 255, 0));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&::before,
|
|
50
|
+
&::after {
|
|
51
|
+
@include gradient;
|
|
52
|
+
content: "";
|
|
53
|
+
height: 100%;
|
|
54
|
+
position: absolute;
|
|
55
|
+
width: var(--gradient-width);
|
|
56
|
+
z-index: 2;
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
touch-action: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&::after {
|
|
62
|
+
right: 0;
|
|
63
|
+
top: 0;
|
|
64
|
+
transform: rotateZ(180deg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&::before {
|
|
68
|
+
left: 0;
|
|
69
|
+
top: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Column direction용 gradient
|
|
73
|
+
&--column {
|
|
74
|
+
&::before,
|
|
75
|
+
&::after {
|
|
76
|
+
@include gradientColumn;
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: var(--gradient-width);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&::before {
|
|
82
|
+
left: 0;
|
|
83
|
+
top: 0;
|
|
84
|
+
transform: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&::after {
|
|
88
|
+
inset: auto auto 0 0;
|
|
89
|
+
transform: rotateZ(180deg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.marquee {
|
|
95
|
+
flex: 0 0 auto;
|
|
96
|
+
width: auto;
|
|
97
|
+
z-index: 1;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
animation: scroll var(--duration, 10s) linear infinite;
|
|
101
|
+
animation-direction: var(--direction, normal);
|
|
102
|
+
flex-direction: var(--flex-direction, row);
|
|
103
|
+
|
|
104
|
+
// Column direction 전용 클래스
|
|
105
|
+
&--column {
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
animation-name: scroll-column;
|
|
108
|
+
animation-duration: var(--duration, 10s);
|
|
109
|
+
animation-timing-function: linear;
|
|
110
|
+
animation-iteration-count: infinite;
|
|
111
|
+
animation-direction: var(--direction, normal);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.initial-child-container {
|
|
116
|
+
flex: 0 0 auto;
|
|
117
|
+
width: auto;
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-flow: var(--flex-direction, row) nowrap;
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
align-items: center;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.child {
|
|
125
|
+
transform: var(--transform);
|
|
126
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Children, Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import styles from './RollingBanner.module.scss';
|
|
3
|
+
|
|
4
|
+
interface RollingBannerProps {
|
|
5
|
+
speed?: number; // 속도 (픽셀/초)
|
|
6
|
+
reverse?: boolean; // 역방향 재생 여부
|
|
7
|
+
children?: React.ReactNode | React.ReactNode[];
|
|
8
|
+
gradientColor?: string;
|
|
9
|
+
gradient?: boolean;
|
|
10
|
+
gradientWidth?: number | string;
|
|
11
|
+
direction?: 'row' | 'column';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function RollingBanner({
|
|
15
|
+
speed = 50, gradient = true, gradientColor = 'white', gradientWidth = 200,
|
|
16
|
+
reverse = false, children, direction = 'row' }: RollingBannerProps) {
|
|
17
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
const marqueeRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
20
|
+
const [marqueeWidth, setMarqueeWidth] = useState(0);
|
|
21
|
+
const [repeat, setRepeat] = useState(1);
|
|
22
|
+
|
|
23
|
+
const calculateWidth = useCallback(() => {
|
|
24
|
+
if (marqueeRef.current && containerRef.current) {
|
|
25
|
+
const containerRect = containerRef.current.getBoundingClientRect();
|
|
26
|
+
const marqueeRect = marqueeRef.current.getBoundingClientRect();
|
|
27
|
+
|
|
28
|
+
// direction에 따라 width 또는 height 계산
|
|
29
|
+
const containerSize = direction === 'row' ? containerRect.width : containerRect.height;
|
|
30
|
+
const marqueeSize = direction === 'row' ? marqueeRect.width : marqueeRect.height;
|
|
31
|
+
|
|
32
|
+
if (containerSize && marqueeSize) {
|
|
33
|
+
setRepeat(
|
|
34
|
+
marqueeSize < containerSize
|
|
35
|
+
? Math.ceil(containerSize / marqueeSize)
|
|
36
|
+
: 1,
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
setRepeat(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setMarqueeWidth(marqueeSize);
|
|
43
|
+
}
|
|
44
|
+
}, [containerRef, direction]);
|
|
45
|
+
|
|
46
|
+
const duration = useMemo(() => {
|
|
47
|
+
const calculatedDuration = (marqueeWidth * repeat) / speed;
|
|
48
|
+
// duration이 0이거나 유효하지 않으면 기본값 사용
|
|
49
|
+
return calculatedDuration > 0 && Number.isFinite(calculatedDuration) ? calculatedDuration : 10;
|
|
50
|
+
}, [marqueeWidth, repeat, speed]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setIsMounted(true);
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!isMounted) return;
|
|
58
|
+
|
|
59
|
+
calculateWidth();
|
|
60
|
+
if (marqueeRef.current && containerRef.current) {
|
|
61
|
+
const resizeObserver = new ResizeObserver(() => calculateWidth());
|
|
62
|
+
resizeObserver.observe(containerRef.current);
|
|
63
|
+
resizeObserver.observe(marqueeRef.current);
|
|
64
|
+
return () => {
|
|
65
|
+
if (!resizeObserver) return;
|
|
66
|
+
resizeObserver.disconnect();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}, [calculateWidth, containerRef, isMounted]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
calculateWidth();
|
|
73
|
+
}, [calculateWidth, children]);
|
|
74
|
+
|
|
75
|
+
const marqueeStyle = useMemo(
|
|
76
|
+
() => {
|
|
77
|
+
const style = {
|
|
78
|
+
['--direction' as string]: reverse ? 'reverse' : 'normal',
|
|
79
|
+
['--duration' as string]: `${duration}s`,
|
|
80
|
+
['--flex-direction' as string]: direction,
|
|
81
|
+
['--animation-name' as string]: direction === 'column' ? 'scroll-column' : 'scroll',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return style;
|
|
85
|
+
},
|
|
86
|
+
[reverse, duration, direction],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const repeatChildren = useCallback((repeat: number) => {
|
|
90
|
+
return [
|
|
91
|
+
...Array(Number.isFinite(repeat) && repeat >= 0 ? repeat : 0),
|
|
92
|
+
].map((_, index) => (
|
|
93
|
+
<Fragment key={index}>
|
|
94
|
+
{Children.map(children, (child) => (
|
|
95
|
+
<div className={styles.child}>
|
|
96
|
+
{child}
|
|
97
|
+
</div>
|
|
98
|
+
))}
|
|
99
|
+
</Fragment>
|
|
100
|
+
));
|
|
101
|
+
}, [children]);
|
|
102
|
+
|
|
103
|
+
const gradientStyle = useMemo(
|
|
104
|
+
() => ({
|
|
105
|
+
['--gradient-color' as string]: gradientColor,
|
|
106
|
+
['--gradient-width' as string]:
|
|
107
|
+
typeof gradientWidth === 'number'
|
|
108
|
+
? `${gradientWidth}px`
|
|
109
|
+
: gradientWidth,
|
|
110
|
+
}),
|
|
111
|
+
[gradientColor, gradientWidth]);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
className={`${styles['marquee-container']} ${direction === 'column' ? styles['marquee-container--column'] : ''}`}
|
|
116
|
+
ref={containerRef}
|
|
117
|
+
>
|
|
118
|
+
{gradient && <div style={gradientStyle} className={`${styles.overlay} ${direction === 'column' ? styles['overlay--column'] : ''}`} />}
|
|
119
|
+
<div
|
|
120
|
+
className={`${styles.marquee} ${direction === 'column' ? styles['marquee--column'] : ''}`}
|
|
121
|
+
style={marqueeStyle}
|
|
122
|
+
>
|
|
123
|
+
<div className={styles['initial-child-container']} ref={marqueeRef} style={marqueeStyle}>
|
|
124
|
+
{Children.map(children, (child) => {
|
|
125
|
+
return (
|
|
126
|
+
<div className={styles.child}>
|
|
127
|
+
{child}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</div>
|
|
132
|
+
{repeatChildren(repeat - 1)}
|
|
133
|
+
</div>
|
|
134
|
+
<div className={`${styles.marquee} ${direction === 'column' ? styles['marquee--column'] : ''}`} style={marqueeStyle}>
|
|
135
|
+
{repeatChildren(repeat)}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Breadcrumbs, { BreadcrumbsItem } from '@/common/Breadcrumbs';
|
|
2
2
|
import Button from '@/common/Button';
|
|
3
3
|
import Pagination from '@/common/Pagination';
|
|
4
|
+
import RollingBanner from '@/common/RollingBanner';
|
|
4
5
|
import Skeleton from '@/common/Skeleton';
|
|
5
6
|
import SwitchButton from '@/common/SwitchButton';
|
|
6
7
|
import Table from '@/common/Table';
|
|
@@ -8,15 +9,16 @@ import Calendar from './Calendar';
|
|
|
8
9
|
import { CalendarRange, CalendarView } from './Calendar/type';
|
|
9
10
|
import Accordion from './common/Accordion';
|
|
10
11
|
import Card from './common/Card';
|
|
12
|
+
import Carousel from './common/Carousel';
|
|
11
13
|
import DropDown from './common/DropDown';
|
|
12
14
|
import SegmentButton from './common/SegmentButton';
|
|
13
15
|
|
|
14
16
|
export {
|
|
15
17
|
Calendar, Accordion, Breadcrumbs, Button,
|
|
16
18
|
Pagination, Skeleton, SwitchButton,
|
|
17
|
-
Card, DropDown, SegmentButton, BreadcrumbsItem, Table,
|
|
19
|
+
Card, DropDown, SegmentButton, BreadcrumbsItem, Table, Carousel, RollingBanner,
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
export type { CalendarView, CalendarRange };
|
|
21
23
|
|
|
22
|
-
import './index.scss';
|
|
24
|
+
import './index.scss';
|