@jk-core/components 1.0.1 → 1.1.0

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.
Files changed (87) hide show
  1. package/dist/index.js +1287 -730
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.umd.cjs +5 -5
  4. package/dist/index.umd.cjs.map +1 -1
  5. package/dist/src/Calendar/components/DateLabel/index.d.ts +1 -1
  6. package/dist/src/Calendar/components/DayTile/index.d.ts +1 -1
  7. package/dist/src/Calendar/components/ViewSelector/index.d.ts +1 -1
  8. package/dist/src/common/Accordion/index.d.ts +12 -0
  9. package/dist/src/common/Breadcrumbs/index.d.ts +40 -0
  10. package/dist/src/common/Button/index.d.ts +27 -0
  11. package/dist/src/common/Card/index.d.ts +6 -0
  12. package/dist/src/common/DropDown/List.d.ts +10 -0
  13. package/dist/src/common/DropDown/index.d.ts +29 -0
  14. package/dist/src/common/Pagination/index.d.ts +16 -0
  15. package/dist/src/common/SegmentButton/index.d.ts +26 -0
  16. package/dist/src/common/Skeleton/index.d.ts +28 -0
  17. package/dist/src/common/SwitchButton/index.d.ts +19 -0
  18. package/dist/src/index.d.ts +10 -1
  19. package/dist/src/utils/ts/allowDecimal.d.ts +1 -0
  20. package/dist/src/utils/ts/autoHypen.d.ts +1 -0
  21. package/dist/src/utils/ts/calculateMax.d.ts +9 -0
  22. package/dist/src/utils/ts/checkIsMobilePlatform.d.ts +2 -0
  23. package/dist/src/utils/ts/formatFileSize.d.ts +4 -0
  24. package/dist/src/utils/ts/formatMoney.d.ts +2 -0
  25. package/dist/src/utils/ts/gradientRatio.d.ts +19 -0
  26. package/dist/src/utils/ts/kiloToMega.d.ts +6 -0
  27. package/dist/src/utils/ts/maskingPhone.d.ts +2 -0
  28. package/dist/src/utils/ts/toQueryString.d.ts +5 -0
  29. package/dist/src/utils/ts/valueAsNumber.d.ts +12 -0
  30. package/package.json +33 -25
  31. package/src/Calendar/RangeCalendar.tsx +5 -5
  32. package/src/Calendar/ScrollCalendar.tsx +3 -3
  33. package/src/Calendar/SingleCalendar.tsx +15 -15
  34. package/src/Calendar/components/DateLabel/index.tsx +19 -19
  35. package/src/Calendar/components/DayTile/index.tsx +13 -5
  36. package/src/Calendar/components/MonthTile/index.tsx +2 -2
  37. package/src/Calendar/components/ViewSelector/index.tsx +7 -7
  38. package/src/Calendar/components/YearTile/YearTile.module.scss +0 -1
  39. package/src/Calendar/components/YearTile/index.tsx +1 -1
  40. package/src/Calendar/hooks/useCalendarNav.ts +4 -1
  41. package/src/Calendar/index.tsx +3 -3
  42. package/src/Calendar/utils/isInRange.ts +1 -1
  43. package/src/common/Accordion/Accordion.module.scss +52 -0
  44. package/src/common/Accordion/arrow-down.svg +3 -0
  45. package/src/common/Accordion/arrow-up.svg +3 -0
  46. package/src/common/Accordion/index.tsx +55 -0
  47. package/src/common/Breadcrumbs/Breadcrumbs.module.scss +45 -0
  48. package/src/common/Breadcrumbs/home.svg +5 -0
  49. package/src/common/Breadcrumbs/index.tsx +82 -0
  50. package/src/common/Button/Button.module.scss +130 -0
  51. package/src/common/Button/index.tsx +60 -0
  52. package/src/common/Card/Card.module.scss +27 -0
  53. package/src/common/Card/index.tsx +19 -0
  54. package/src/common/DropDown/DropDown.module.scss +135 -0
  55. package/src/common/DropDown/List.tsx +157 -0
  56. package/src/common/DropDown/arrow-down.svg +3 -0
  57. package/src/common/DropDown/index.tsx +104 -0
  58. package/src/common/DropDown/search.svg +4 -0
  59. package/src/common/Pagination/Pagination.module.scss +177 -0
  60. package/src/common/Pagination/arrow-left.svg +12 -0
  61. package/src/common/Pagination/arrow-right.svg +12 -0
  62. package/src/common/Pagination/index.tsx +141 -0
  63. package/src/common/SegmentButton/SegmentButton.module.scss +44 -0
  64. package/src/common/SegmentButton/index.tsx +66 -0
  65. package/src/common/Skeleton/Skeleton.module.scss +80 -0
  66. package/src/common/Skeleton/index.tsx +48 -0
  67. package/src/common/SwitchButton/SwitchButton.module.scss +65 -0
  68. package/src/common/SwitchButton/index.tsx +56 -0
  69. package/src/index.scss +1 -0
  70. package/src/index.tsx +17 -1
  71. package/src/styles/color.scss +94 -0
  72. package/src/styles/font-face.scss +18 -0
  73. package/src/styles/font.scss +49 -0
  74. package/src/styles/scrollbar.scss +71 -0
  75. package/src/svg.d.ts +4 -2
  76. package/src/utils/styles/mediaQuery.scss +22 -0
  77. package/src/utils/ts/allowDecimal.ts +5 -0
  78. package/src/utils/ts/autoHypen.ts +33 -0
  79. package/src/utils/ts/calculateMax.ts +24 -0
  80. package/src/utils/ts/checkIsMobilePlatform.ts +15 -0
  81. package/src/utils/ts/formatFileSize.ts +16 -0
  82. package/src/utils/ts/formatMoney.ts +16 -0
  83. package/src/utils/ts/gradientRatio.ts +61 -0
  84. package/src/utils/ts/kiloToMega.ts +30 -0
  85. package/src/utils/ts/maskingPhone.ts +9 -0
  86. package/src/utils/ts/toQueryString.ts +7 -0
  87. package/src/utils/ts/valueAsNumber.ts +16 -0
@@ -0,0 +1,157 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { cn } from '@jk-core/utils';
3
+ import styles from './DropDown.module.scss';
4
+ import SearchIcon from './search.svg';
5
+
6
+ interface ListProps {
7
+ selectedItem?: string;
8
+ list: string[];
9
+ filteredList: string[];
10
+ parent: React.RefObject<HTMLDivElement | null>;
11
+ onSelect: (item: string, index: number) => void;
12
+ setFilteredList?: React.Dispatch<React.SetStateAction<string[]>>
13
+ ;
14
+ }
15
+
16
+ interface Position {
17
+ top?: number;
18
+ left?: number;
19
+ bottom?: number;
20
+ height: number;
21
+ }
22
+
23
+ export default function List({ parent, selectedItem, list, filteredList, onSelect, setFilteredList }: ListProps) {
24
+ const listHeight = useMemo(() => {
25
+ if (setFilteredList) {
26
+ return Math.min((filteredList.length * 50) + 40, 300);
27
+ }
28
+ return Math.min(list.length * 50, 300);
29
+ }, [filteredList.length, list.length, setFilteredList]);
30
+
31
+ const listRef = useRef<HTMLDivElement>(null);
32
+ const [position, setPosition] = useState<Position>(() => {
33
+ if (!parent || !parent.current) {
34
+ return { top: 0, left: 0, bottom: 0, height: 0 };
35
+ }
36
+ const rect = parent.current.getBoundingClientRect();
37
+
38
+ const spaceBelow = window.innerHeight - rect.bottom;
39
+ const spaceAbove = rect.top;
40
+ if (spaceBelow < listHeight && spaceAbove > listHeight) {
41
+ // 위에 열림: bottom 사용, top은 undefined
42
+ return {
43
+ top: undefined,
44
+ left: rect.left,
45
+ bottom: window.innerHeight - rect.top - window.scrollY,
46
+ height: 0,
47
+ };
48
+ }
49
+ // 아래에 열림: top 사용, bottom은 undefined
50
+ return {
51
+ top: rect.bottom + window.scrollY,
52
+ left: rect.left,
53
+ bottom: undefined,
54
+ height: 0,
55
+ scroll: window.scrollY,
56
+ };
57
+ });
58
+
59
+ useEffect(() => {
60
+ function updatePosition() {
61
+ setPosition(prev => ({
62
+ ...prev,
63
+ height: listHeight,
64
+ }));
65
+ }
66
+
67
+ updatePosition();
68
+
69
+ window.addEventListener('resize', updatePosition);
70
+
71
+ return () => {
72
+ window.removeEventListener('resize', updatePosition);
73
+ };
74
+ }, [listHeight]);
75
+
76
+ useEffect(() => {
77
+ const handleScroll = () => {
78
+ if (!parent || !parent.current) return;
79
+ const rect = parent.current.getBoundingClientRect();
80
+ const spaceBelow = window.innerHeight - rect.bottom;
81
+ const spaceAbove = rect.top;
82
+ if (spaceBelow < listHeight && spaceAbove > listHeight) {
83
+ // 위에 열림
84
+ setPosition({
85
+ top: undefined,
86
+ left: rect.left,
87
+ bottom: window.innerHeight - rect.top - window.scrollY,
88
+ height: listHeight,
89
+ });
90
+ } else {
91
+ // 아래에 열림
92
+ setPosition({
93
+ top: rect.bottom + window.scrollY,
94
+ left: rect.left,
95
+ bottom: undefined,
96
+ height: listHeight,
97
+ });
98
+ }
99
+ };
100
+
101
+ window.addEventListener('scroll', handleScroll, true);
102
+ window.addEventListener('resize', handleScroll, true);
103
+
104
+ return () => {
105
+ window.removeEventListener('scroll', handleScroll, true);
106
+ window.removeEventListener('resize', handleScroll, true);
107
+ };
108
+ }, [listHeight, parent, position.height]);
109
+
110
+ return (
111
+ <div
112
+ className={cn({
113
+ [styles['list-wrapper']]: true,
114
+ [styles['list-wrapper--up']]: position.bottom === undefined,
115
+ [styles['list-wrapper--down']]: position.top === undefined,
116
+ })}
117
+ style={{
118
+ width: parent?.current?.offsetWidth,
119
+ height: position.height,
120
+ top: position.top,
121
+ left: position.left,
122
+ bottom: position.bottom,
123
+ }}
124
+ >
125
+ <div className={styles.list} ref={listRef}>
126
+ {setFilteredList && (
127
+ <label className={styles.list__search}>
128
+ <SearchIcon />
129
+ <input
130
+ type="text"
131
+ placeholder="검색"
132
+ onChange={(e) => setFilteredList(list.filter((item) => item.includes(e.target.value)))}
133
+ />
134
+ </label>
135
+ )}
136
+ {filteredList.length === 0 && (
137
+ <div className={styles.list__empty}>
138
+ 검색 결과가 없습니다.
139
+ </div>
140
+ )}
141
+ {filteredList.map((item, index) => (
142
+ <button
143
+ className={cn({
144
+ [styles.list__item]: true,
145
+ [styles['list__item--selected']]: item === selectedItem,
146
+ })}
147
+ type="button"
148
+ key={item + index}
149
+ onClick={() => onSelect(item, index)}
150
+ >
151
+ {item}
152
+ </button>
153
+ ))}
154
+ </div>
155
+ </div>
156
+ );
157
+ }
@@ -0,0 +1,3 @@
1
+ <svg width="12" height="9" viewBox="0 0 12 9" stroke="black" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M1 1.5L6 7L11 1.5" stroke-width="1.4" stroke-linecap="round"/>
3
+ </svg>
@@ -0,0 +1,104 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { cn } from '@jk-core/utils';
4
+ import DownIcon from './arrow-down.svg';
5
+ import styles from './DropDown.module.scss';
6
+ import List from './List';
7
+
8
+ interface DropDownProps {
9
+ /**
10
+ * 드롭다운 목록
11
+ */
12
+ list: string[];
13
+ /**
14
+ * 선택된 아이템을 처리하는 함수
15
+ */
16
+ onSelect: (item: string, index:number) => void;
17
+ /**
18
+ * 필터링 기능 활성화 여부
19
+ */
20
+ filter?: boolean;
21
+ /**
22
+ * 드롭다운의 플레이스홀더 텍스트
23
+ */
24
+ placeholder?: string;
25
+ /**
26
+ * 드롭다운의 스타일
27
+ */
28
+ style?: React.CSSProperties;
29
+ }
30
+
31
+ /**
32
+ * 드롭다운 컴포넌트
33
+ * 사용자가 선택할 수 있는 목록을 제공하며, 선택된 아이템을 표시합니다.
34
+ * 필터링 기능을 활성화할 수 있으며, 플레이스홀더 텍스트를 설정할 수 있습니다.
35
+ */
36
+ export default function DropDown({
37
+ list = [], onSelect, filter,
38
+ placeholder, style }: DropDownProps) {
39
+ const wrapperRef = useRef<HTMLDivElement>(null);
40
+ const listRef = useRef<HTMLDivElement>(null);
41
+ const [selectedItem, setSelectedItem] = useState<string>(placeholder || '선택하세요');
42
+ const [isOpen, setIsOpen] = useState(false);
43
+ const [filteredList, setFilteredList] = useState(list);
44
+
45
+ useEffect(() => {
46
+ if (!isOpen) {
47
+ setFilteredList(list);
48
+ }
49
+
50
+ function handleClick(event: MouseEvent) {
51
+ if (
52
+ wrapperRef.current &&
53
+ !wrapperRef.current.contains(event.target as Node) &&
54
+ listRef.current &&
55
+ !listRef.current.contains(event.target as Node)
56
+ ) {
57
+ setIsOpen(false);
58
+ }
59
+ }
60
+
61
+ if (isOpen) {
62
+ document.addEventListener('mousedown', handleClick);
63
+ }
64
+ return () => {
65
+ document.removeEventListener('mousedown', handleClick);
66
+ };
67
+ }, [isOpen, list]);
68
+
69
+ return (
70
+ <div className={styles.wrapper} ref={wrapperRef}>
71
+ <button
72
+ className={styles.title}
73
+ type="button"
74
+ onClick={() => setIsOpen(!isOpen)}
75
+ style={style}
76
+ >
77
+ <span>{selectedItem}</span>
78
+ <DownIcon className={cn({
79
+ [styles.icon]: true,
80
+ [styles['icon--up']]: isOpen,
81
+ })}
82
+ />
83
+ </button>
84
+ {isOpen && (
85
+ createPortal(
86
+ <div ref={listRef} className={styles['list-portal']}>
87
+ <List
88
+ selectedItem={selectedItem}
89
+ list={list}
90
+ filteredList={filteredList}
91
+ parent={wrapperRef}
92
+ setFilteredList={filter ? setFilteredList : undefined}
93
+ onSelect={(item, index) => {
94
+ onSelect(item, index);
95
+ setSelectedItem(item);
96
+ setFilteredList(list);
97
+ setIsOpen(false);
98
+ }}
99
+ />
100
+ </div>, document.body)
101
+ )}
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,4 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="0.7" y="0.7" width="12.6" height="12.6" rx="6.3" stroke="#2D2D2D" stroke-width="1.4"/>
3
+ <path d="M11.459 11.458L14.9945 14.9935" stroke="#2D2D2D" stroke-width="1.4" stroke-linecap="square"/>
4
+ </svg>
@@ -0,0 +1,177 @@
1
+ $delay: 0.2s;
2
+ $size: 40px;
3
+
4
+ @keyframes slideLeft {
5
+ from {
6
+ transform: translateX(-$size);
7
+ }
8
+ to {
9
+ transform: translateX(0);
10
+ }
11
+ }
12
+
13
+ @keyframes slideRight {
14
+ from {
15
+ transform: translateX($size);
16
+ }
17
+ to {
18
+ transform: translateX(0);
19
+ }
20
+ }
21
+
22
+ @keyframes slideDoubleLeft {
23
+ from {
24
+ transform: translateX(-$size * 2);
25
+ }
26
+ to {
27
+ transform: translateX(0);
28
+ }
29
+ }
30
+
31
+ @keyframes slideDoubleRight {
32
+ from {
33
+ transform: translateX($size * 2);
34
+ }
35
+ to {
36
+ transform: translateX(0);
37
+ }
38
+ }
39
+
40
+ .pagination {
41
+ position: relative;
42
+ width: 100%;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ gap: 10px;
47
+ padding: 10px 0;
48
+ }
49
+
50
+ .arrow {
51
+ width: $size;
52
+ height: $size;
53
+ border-radius: 100%;
54
+ background-color: var(--G-20);
55
+ cursor: pointer;
56
+ user-select: none;
57
+ -webkit-user-drag: none;
58
+ pointer-events: auto;
59
+ transition: background-color 0.1s ease;
60
+
61
+ & > svg {
62
+ width: 20px;
63
+ height: 20px;
64
+ fill: var(--G-80);
65
+ transition: fill 0.1s ease;
66
+ }
67
+
68
+ &:hover {
69
+ background-color: var(--P-50);
70
+
71
+ & > svg {
72
+ fill: var(--white);
73
+ }
74
+ }
75
+
76
+ &--disabled {
77
+ background-color: var(--G-20) !important;
78
+ cursor: default;
79
+
80
+ & > svg {
81
+ fill: var(--G-40) !important; // Gray color for disabled state
82
+ }
83
+ }
84
+ }
85
+
86
+ .slider {
87
+ position: relative;
88
+ width: $size * 8;
89
+ height: $size;
90
+ display: flex;
91
+ overflow: visible;
92
+ background-color: var(--G-20);
93
+ border-radius: 10px;
94
+ }
95
+
96
+ .track {
97
+ position: absolute;
98
+ width: $size;
99
+ height: $size;
100
+ border-radius: 10px;
101
+ background: linear-gradient(135deg, var(--P-40) 0%, var(--P-60) 100%);
102
+ box-shadow: 0 0 7px 2px #3234bf56;
103
+ transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
104
+ z-index: 0;
105
+ }
106
+
107
+ .pages {
108
+ width: $size * 5;
109
+ overflow: hidden visible;
110
+ display: flex;
111
+ }
112
+
113
+ .pageWrapper {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ transition: transform $delay ease-in-out;
118
+ border-radius: 10px;
119
+ }
120
+
121
+ .page {
122
+ width: $size;
123
+ height: $size;
124
+ flex-shrink: 0;
125
+ z-index: 2;
126
+ color: #6b7280;
127
+ user-select: none;
128
+ font-size: 14px;
129
+ font-weight: 600;
130
+
131
+ &:disabled {
132
+ cursor: default;
133
+ }
134
+
135
+ &:hover {
136
+ color: var(--P-50);
137
+ }
138
+
139
+ &--selected {
140
+ transition: color $delay;
141
+ color: #ffffff !important;
142
+ }
143
+ }
144
+
145
+ .left {
146
+ animation: slideLeft $delay;
147
+ }
148
+
149
+ .right {
150
+ animation: slideRight $delay;
151
+ }
152
+
153
+ .doubleLeft {
154
+ animation: slideDoubleLeft $delay;
155
+ }
156
+
157
+ .doubleRight {
158
+ animation: slideDoubleRight $delay;
159
+ }
160
+
161
+ .portal {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 5px;
165
+ width: 60px;
166
+ height: $size;
167
+ flex-shrink: 0;
168
+ overflow: hidden;
169
+ z-index: 2;
170
+ font-size: 14px;
171
+ font-weight: 600;
172
+
173
+ &__ellipsis {
174
+ color: #6b7280;
175
+ font-size: 12px;
176
+ }
177
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2
+
3
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
4
+ <svg width="30px" height="30px" viewBox="0 0 24 24" fill="#000000" xmlns="http://www.w3.org/2000/svg">
5
+
6
+ <g id="SVGRepo_bgCarrier" stroke-width="0"/>
7
+
8
+ <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
9
+
10
+ <g id="SVGRepo_iconCarrier"> <path d="M16.1795 3.26875C15.7889 2.87823 15.1558 2.87823 14.7652 3.26875L8.12078 9.91322C6.94952 11.0845 6.94916 12.9833 8.11996 14.155L14.6903 20.7304C15.0808 21.121 15.714 21.121 16.1045 20.7304C16.495 20.3399 16.495 19.7067 16.1045 19.3162L9.53246 12.7442C9.14194 12.3536 9.14194 11.7205 9.53246 11.33L16.1795 4.68297C16.57 4.29244 16.57 3.65928 16.1795 3.26875Z" /> </g>
11
+
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2
+
3
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
4
+ <svg width="30px" height="30px" viewBox="0 0 24 24" fill="#000000" xmlns="http://www.w3.org/2000/svg">
5
+
6
+ <g id="SVGRepo_bgCarrier"/>
7
+
8
+ <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
9
+
10
+ <g id="SVGRepo_iconCarrier"> <path d="M7.82054 20.7313C8.21107 21.1218 8.84423 21.1218 9.23476 20.7313L15.8792 14.0868C17.0505 12.9155 17.0508 11.0167 15.88 9.84497L9.3097 3.26958C8.91918 2.87905 8.28601 2.87905 7.89549 3.26958C7.50497 3.6601 7.50497 4.29327 7.89549 4.68379L14.4675 11.2558C14.8581 11.6464 14.8581 12.2795 14.4675 12.67L7.82054 19.317C7.43002 19.7076 7.43002 20.3407 7.82054 20.7313Z"/> </g>
11
+
12
+ </svg>
@@ -0,0 +1,141 @@
1
+
2
+ import { useMemo, useState } from 'react';
3
+ import LeftArrowIcon from './arrow-left.svg';
4
+ import RightArrowIcon from './arrow-right.svg';
5
+ import styles from './Pagination.module.scss';
6
+
7
+ interface PaginationProps {
8
+ /**
9
+ * 총 페이지 수
10
+ */
11
+ totalPage: number;
12
+ /**
13
+ * 현재 페이지
14
+ */
15
+ currentPage: number;
16
+
17
+ /**
18
+ * 페이지 클릭 핸들러
19
+ */
20
+ onPageClick: (page: number) => void;
21
+ }
22
+
23
+ export default function Pagination({ totalPage, currentPage, onPageClick }: PaginationProps) {
24
+ const [animation, setAnimation] = useState<'left' | 'right' | 'doubleRight' | 'doubleLeft' | ''>('');
25
+ const [selectedPage, setSelectedPage] = useState(currentPage || 1);
26
+
27
+ const pageArray = useMemo(() => {
28
+ if (!totalPage) return Array.from({ length: 9 }).map((_, i) => (i === 2 ? 1 : null));
29
+
30
+ return Array.from({ length: 9 }).map((_, i) => {
31
+ const pageIndex = selectedPage + i - 2;
32
+
33
+ if (pageIndex < 1 || pageIndex > totalPage) {
34
+ return null;
35
+ }
36
+ return pageIndex;
37
+ });
38
+ }, [selectedPage, totalPage]);
39
+
40
+ const handlePageClick = (page: number) => {
41
+ if (page > totalPage || page < 1) return;
42
+ onPageClick(page);
43
+ if (page === null) return;
44
+ const diff = page - selectedPage;
45
+
46
+ switch (diff) {
47
+ case 1:
48
+ setAnimation('right');
49
+ break;
50
+ case -1:
51
+ setAnimation('left');
52
+ break;
53
+ case 2:
54
+ setAnimation('doubleRight');
55
+ break;
56
+ case -2:
57
+ setAnimation('doubleLeft');
58
+ break;
59
+ default:
60
+ setAnimation('');
61
+ }
62
+
63
+ setSelectedPage(page);
64
+
65
+ setTimeout(() => {
66
+ setAnimation('');
67
+ }, 200);
68
+ };
69
+
70
+ return (
71
+ <div className={styles.pagination}>
72
+ <button
73
+ className={`${styles.arrow} ${selectedPage <= 1 ? styles['arrow--disabled'] : '' }`}
74
+ type="button"
75
+ onClick={() => handlePageClick(selectedPage - 1)}
76
+ >
77
+ <LeftArrowIcon />
78
+ </button>
79
+ <div className={styles.slider}>
80
+ <div
81
+ className={styles.track}
82
+ style={{
83
+ left: '140px',
84
+ }}
85
+ />
86
+ <div className={styles.portal}>
87
+ {selectedPage > 3 && (
88
+ < >
89
+ <button
90
+ className={styles.page}
91
+ type="button"
92
+ onClick={() => handlePageClick(1)}
93
+ >1
94
+ </button>
95
+ <span className={styles.portal__ellipsis}>•••</span>
96
+ </>
97
+ )}
98
+ </div>
99
+ <div className={styles.pages}>
100
+ <div className={`${styles.pageWrapper} ${animation === 'left' ? styles.left : ''} ${styles[animation]}`}>
101
+ {pageArray.map((page, index) => (
102
+ <button
103
+ className={`${styles.page} ${selectedPage === page ? styles['page--selected'] : ''}`}
104
+ key={index}
105
+ type="button"
106
+ disabled={page === null}
107
+ onClick={() => {
108
+ if (page !== null) {
109
+ handlePageClick(page);
110
+ }
111
+ }}
112
+ >
113
+ <span>{page}</span>
114
+ </button>
115
+ ))}
116
+ </div>
117
+ </div>
118
+ <div className={styles.portal}>
119
+ {selectedPage < totalPage - 2 && (
120
+ <>
121
+ <span className={styles.portal__ellipsis}>•••</span>
122
+ <button
123
+ className={styles.page}
124
+ type="button"
125
+ onClick={() => handlePageClick(totalPage)}
126
+ >{totalPage}
127
+ </button>
128
+ </>
129
+ )}
130
+ </div>
131
+ </div>
132
+ <button
133
+ className={`${styles.arrow} ${selectedPage >= totalPage ? styles['arrow--disabled'] : ''}`}
134
+ type="button"
135
+ onClick={() => handlePageClick(selectedPage + 1)}
136
+ >
137
+ <RightArrowIcon />
138
+ </button>
139
+ </div>
140
+ );
141
+ }
@@ -0,0 +1,44 @@
1
+ .segment {
2
+ position: relative;
3
+ display: grid;
4
+ width: fit-content;
5
+ background-color: var(--G-10);
6
+ border-radius: 8px;
7
+ z-index: 0;
8
+
9
+ &--selector {
10
+ position: absolute;
11
+ top: 0;
12
+ left: 0;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ height: 100%;
17
+ background-color: var(--white);
18
+ border: 2px solid var(--G-10);
19
+ border-radius: 8px;
20
+ transition: all 0.2s;
21
+ z-index: 1;
22
+ }
23
+ }
24
+
25
+ .button {
26
+ height: 100%;
27
+ padding: 8px 5px;
28
+ color: var(--G-40);
29
+ transition: color 0.2s;
30
+ font-size: 17px;
31
+ font-weight: 500;
32
+
33
+ & > div {
34
+ position: relative;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ z-index: 10;
39
+ }
40
+
41
+ &--selected {
42
+ color: var(--G-80);
43
+ }
44
+ }