@jk-core/components 1.1.18 → 1.1.20

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.
@@ -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
+ }
@@ -1,5 +1,5 @@
1
- $color: #eeeeee;
2
- $shadow: #e5e5e5;
1
+ $color: #f2f2f2;
2
+ $shadow: #e8e8e8;
3
3
 
4
4
  @keyframes skeleton-blink {
5
5
  0% {
@@ -32,7 +32,7 @@ $shadow: #e5e5e5;
32
32
  background-color: $color;
33
33
  border-radius: 100%;
34
34
  overflow: hidden;
35
- flex-shrink: 0;
35
+ flex: 1 0 auto;
36
36
  }
37
37
 
38
38
  .skeleton-round {
@@ -42,7 +42,7 @@ $shadow: #e5e5e5;
42
42
  background-color: $color;
43
43
  border-radius: 10px;
44
44
  overflow: hidden;
45
- flex-shrink: 0;
45
+ flex: 1 0 auto;
46
46
  }
47
47
 
48
48
  .skeleton-rectangle {
@@ -50,7 +50,7 @@ $shadow: #e5e5e5;
50
50
  height: 100%;
51
51
  position: relative;
52
52
  background-color: $color;
53
- flex-shrink: 0;
53
+ flex: 1 0 auto;
54
54
  overflow: hidden;
55
55
  }
56
56
 
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';