@edu-tosel/design 1.0.303 → 1.0.305

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/asset/SVG.d.ts CHANGED
@@ -54,8 +54,8 @@ declare const SVG: {
54
54
  Print: typeof Print;
55
55
  Profile: typeof Profile;
56
56
  Symbol: {
57
- LessThan: ({ className, onClick }: import("./svg/Symbol").SymbolProps) => import("react/jsx-runtime").JSX.Element;
58
- GreaterThan: ({ className, onClick }: import("./svg/Symbol").SymbolProps) => import("react/jsx-runtime").JSX.Element;
57
+ LessThan: ({ className, onClick, color }: import("./svg/Symbol").SymbolProps) => import("react/jsx-runtime").JSX.Element;
58
+ GreaterThan: ({ className, onClick, color }: import("./svg/Symbol").SymbolProps) => import("react/jsx-runtime").JSX.Element;
59
59
  };
60
60
  Login: {
61
61
  User: ({ size }: import("./svg/Login").IconProps) => import("react/jsx-runtime").JSX.Element;
@@ -2,11 +2,12 @@ import { OnClick } from "../../interface";
2
2
  export interface SymbolProps {
3
3
  className?: string;
4
4
  onClick?: OnClick;
5
+ color?: string;
5
6
  }
6
7
  declare const Symbol: {
7
8
  LessThan: typeof LessThan;
8
9
  GreaterThan: typeof GreaterThan;
9
10
  };
10
- declare function LessThan({ className, onClick }: SymbolProps): import("react/jsx-runtime").JSX.Element;
11
- declare function GreaterThan({ className, onClick }: SymbolProps): import("react/jsx-runtime").JSX.Element;
11
+ declare function LessThan({ className, onClick, color }: SymbolProps): import("react/jsx-runtime").JSX.Element;
12
+ declare function GreaterThan({ className, onClick, color }: SymbolProps): import("react/jsx-runtime").JSX.Element;
12
13
  export default Symbol;
@@ -4,18 +4,18 @@ const Symbol = {
4
4
  LessThan,
5
5
  GreaterThan,
6
6
  };
7
- function LessThan({ className, onClick }) {
7
+ function LessThan({ className, onClick, color }) {
8
8
  const container = {
9
9
  className,
10
10
  cursors: onClick ? "cursor-pointer" : "cursor-default",
11
11
  };
12
- return (_jsx("svg", { className: cn(container), onClick: onClick, xmlns: "http://www.w3.org/2000/svg", height: "11", width: "6", viewBox: "0 0 6 11", fill: "none", children: _jsx("path", { d: "M5.2475 10.7488C5.0575 10.7488 4.8675 10.6788 4.7175 10.5288L0.2175 6.02875C-0.0725 5.73875 -0.0725 5.25875 0.2175 4.96875L4.7175 0.468751C5.0075 0.178751 5.4875 0.178751 5.7775 0.468751C6.0675 0.758751 6.0675 1.23875 5.7775 1.52875L1.8075 5.49875L5.7775 9.46875C6.0675 9.75875 6.0675 10.2388 5.7775 10.5288C5.6275 10.6788 5.4375 10.7488 5.2475 10.7488Z", fill: "white" }) }));
12
+ return (_jsx("svg", { className: cn(container, color || "text-white"), onClick: onClick, xmlns: "http://www.w3.org/2000/svg", height: "11", width: "6", viewBox: "0 0 6 11", fill: "none", children: _jsx("path", { d: "M5.2475 10.7488C5.0575 10.7488 4.8675 10.6788 4.7175 10.5288L0.2175 6.02875C-0.0725 5.73875 -0.0725 5.25875 0.2175 4.96875L4.7175 0.468751C5.0075 0.178751 5.4875 0.178751 5.7775 0.468751C6.0675 0.758751 6.0675 1.23875 5.7775 1.52875L1.8075 5.49875L5.7775 9.46875C6.0675 9.75875 6.0675 10.2388 5.7775 10.5288C5.6275 10.6788 5.4375 10.7488 5.2475 10.7488Z", fill: "currentColor" }) }));
13
13
  }
14
- function GreaterThan({ className, onClick }) {
14
+ function GreaterThan({ className, onClick, color }) {
15
15
  const container = {
16
16
  className,
17
17
  cursors: onClick ? "cursor-pointer" : "cursor-default",
18
18
  };
19
- return (_jsx("svg", { className: cn(container), onClick: onClick, xmlns: "http://www.w3.org/2000/svg", width: "6", height: "11", viewBox: "0 0 6 11", fill: "none", children: _jsx("path", { d: "M0.74249 0.251248C0.93249 0.251248 1.12249 0.321249 1.27249 0.471249L5.77249 4.97125C6.06249 5.26125 6.06249 5.74125 5.77249 6.03125L1.27249 10.5312C0.982491 10.8212 0.50249 10.8212 0.21249 10.5312C-0.0775099 10.2412 -0.0775099 9.76125 0.21249 9.47125L4.18249 5.50125L0.21249 1.53125C-0.0775099 1.24125 -0.0775099 0.761249 0.21249 0.471249C0.36249 0.321249 0.55249 0.251248 0.74249 0.251248Z", fill: "white" }) }));
19
+ return (_jsx("svg", { className: cn(container, color || "text-white"), onClick: onClick, xmlns: "http://www.w3.org/2000/svg", width: "6", height: "11", viewBox: "0 0 6 11", fill: "none", children: _jsx("path", { d: "M0.74249 0.251248C0.93249 0.251248 1.12249 0.321249 1.27249 0.471249L5.77249 4.97125C6.06249 5.26125 6.06249 5.74125 5.77249 6.03125L1.27249 10.5312C0.982491 10.8212 0.50249 10.8212 0.21249 10.5312C-0.0775099 10.2412 -0.0775099 9.76125 0.21249 9.47125L4.18249 5.50125L0.21249 1.53125C-0.0775099 1.24125 -0.0775099 0.761249 0.21249 0.471249C0.36249 0.321249 0.55249 0.251248 0.74249 0.251248Z", fill: "currentColor" }) }));
20
20
  }
21
21
  export default Symbol;
@@ -4,6 +4,7 @@ import { cn } from "../../util";
4
4
  export interface SymbolProps {
5
5
  className?: string;
6
6
  onClick?: OnClick;
7
+ color?: string; // 테일윈드 텍스트 색상 클래스 (예: "text-red-500", "text-blue-400")
7
8
  }
8
9
 
9
10
  const Symbol = {
@@ -11,14 +12,14 @@ const Symbol = {
11
12
  GreaterThan,
12
13
  };
13
14
 
14
- function LessThan({ className, onClick }: SymbolProps) {
15
+ function LessThan({ className, onClick, color }: SymbolProps) {
15
16
  const container = {
16
17
  className,
17
18
  cursors: onClick ? "cursor-pointer" : "cursor-default",
18
19
  };
19
20
  return (
20
21
  <svg
21
- className={cn(container)}
22
+ className={cn(container, color || "text-white")}
22
23
  onClick={onClick}
23
24
  xmlns="http://www.w3.org/2000/svg"
24
25
  height="11"
@@ -28,20 +29,20 @@ function LessThan({ className, onClick }: SymbolProps) {
28
29
  >
29
30
  <path
30
31
  d="M5.2475 10.7488C5.0575 10.7488 4.8675 10.6788 4.7175 10.5288L0.2175 6.02875C-0.0725 5.73875 -0.0725 5.25875 0.2175 4.96875L4.7175 0.468751C5.0075 0.178751 5.4875 0.178751 5.7775 0.468751C6.0675 0.758751 6.0675 1.23875 5.7775 1.52875L1.8075 5.49875L5.7775 9.46875C6.0675 9.75875 6.0675 10.2388 5.7775 10.5288C5.6275 10.6788 5.4375 10.7488 5.2475 10.7488Z"
31
- fill="white"
32
+ fill="currentColor"
32
33
  />
33
34
  </svg>
34
35
  );
35
36
  }
36
37
 
37
- function GreaterThan({ className, onClick }: SymbolProps) {
38
+ function GreaterThan({ className, onClick, color }: SymbolProps) {
38
39
  const container = {
39
40
  className,
40
41
  cursors: onClick ? "cursor-pointer" : "cursor-default",
41
42
  };
42
43
  return (
43
44
  <svg
44
- className={cn(container)}
45
+ className={cn(container, color || "text-white")}
45
46
  onClick={onClick}
46
47
  xmlns="http://www.w3.org/2000/svg"
47
48
  width="6"
@@ -51,7 +52,7 @@ function GreaterThan({ className, onClick }: SymbolProps) {
51
52
  >
52
53
  <path
53
54
  d="M0.74249 0.251248C0.93249 0.251248 1.12249 0.321249 1.27249 0.471249L5.77249 4.97125C6.06249 5.26125 6.06249 5.74125 5.77249 6.03125L1.27249 10.5312C0.982491 10.8212 0.50249 10.8212 0.21249 10.5312C-0.0775099 10.2412 -0.0775099 9.76125 0.21249 9.47125L4.18249 5.50125L0.21249 1.53125C-0.0775099 1.24125 -0.0775099 0.761249 0.21249 0.471249C0.36249 0.321249 0.55249 0.251248 0.74249 0.251248Z"
54
- fill="white"
55
+ fill="currentColor"
55
56
  />
56
57
  </svg>
57
58
  );
package/globals.css CHANGED
@@ -2,6 +2,29 @@
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
4
 
5
+ /* 기본 HTML 리셋 및 스크롤 제어 */
6
+ html {
7
+ scroll-behavior: auto; /* smooth scroll 비활성화 */
8
+ -webkit-overflow-scrolling: auto; /* iOS momentum scrolling 비활성화 */
9
+ overflow-x: hidden;
10
+ scrollbar-width: thin;
11
+ scrollbar-color: rgba(128, 128, 128, 0.5) transparent;
12
+ -ms-overflow-style: none; /* IE and Edge */
13
+ }
14
+
15
+ body {
16
+ margin: 0;
17
+ padding: 0;
18
+ overflow-x: hidden;
19
+ -webkit-overflow-scrolling: auto; /* iOS momentum scrolling 비활성화 */
20
+ }
21
+
22
+ #root {
23
+ min-height: 100vh;
24
+ width: 100%;
25
+ overflow-x: hidden;
26
+ }
27
+
5
28
  .image-container {
6
29
  display: -webkit-box;
7
30
  display: -ms-flexbox;
@@ -59,18 +82,44 @@ input[type="date"]::-webkit-calendar-picker-indicator {
59
82
  -webkit-appearance: none;
60
83
  }
61
84
 
85
+ /* 스크롤바 스타일링 - 스크롤 시에만 표시 */
62
86
  ::-webkit-scrollbar {
63
- width: 3px;
64
- padding-right: 10px;
87
+ width: 8px;
88
+ opacity: 0;
89
+ transition: opacity 0.3s ease;
65
90
  }
66
91
 
67
92
  ::-webkit-scrollbar-track {
68
- background-color: transparent;
93
+ background: transparent;
69
94
  }
95
+
70
96
  ::-webkit-scrollbar-thumb {
71
- background-color: #808080;
72
- opacity: 0.5;
97
+ background-color: rgba(128, 128, 128, 0.5);
73
98
  border-radius: 10px;
99
+ border: 2px solid transparent;
100
+ background-clip: content-box;
101
+ transition: background-color 0.2s ease;
102
+ }
103
+
104
+ ::-webkit-scrollbar-thumb:hover {
105
+ background-color: rgba(128, 128, 128, 0.8);
106
+ }
107
+
108
+ /* 스크롤 중일 때 스크롤바 표시 */
109
+ .scrolling::-webkit-scrollbar {
110
+ opacity: 1;
111
+ }
112
+
113
+ /* body와 html에도 스크롤바 스타일 적용 */
114
+ body.scrolling::-webkit-scrollbar,
115
+ html.scrolling::-webkit-scrollbar {
116
+ opacity: 1;
117
+ }
118
+
119
+ /* Firefox용 스크롤바 (기본적으로 auto-hide) */
120
+ html {
121
+ scrollbar-width: thin;
122
+ scrollbar-color: rgba(128, 128, 128, 0.5) transparent;
74
123
  }
75
124
 
76
125
  .box-shadow {
@@ -268,6 +317,7 @@ input[type="date"]::-webkit-calendar-picker-indicator {
268
317
 
269
318
  /* related to font rendering in safari engine */
270
319
  * {
320
+ box-sizing: border-box;
271
321
  font-synthesis: none !important;
272
322
  -webkit-font-smoothing: antialiased;
273
323
  -moz-osx-font-smoothing: grayscale;
@@ -279,3 +329,13 @@ input[type="date"]::-webkit-calendar-picker-indicator {
279
329
  -webkit-mask-composite: destination-in;
280
330
  mask-composite: intersect;
281
331
  }
332
+
333
+ /* Navigation이 열렸을 때 스크롤바 완전히 숨김 */
334
+ .navigation-open::-webkit-scrollbar {
335
+ display: none !important;
336
+ }
337
+
338
+ .navigation-open {
339
+ scrollbar-width: none !important;
340
+ -ms-overflow-style: none !important;
341
+ }
@@ -0,0 +1 @@
1
+ export default function useScrollbarVisibility(): void;
@@ -0,0 +1,66 @@
1
+ import { useEffect } from "react";
2
+ export default function useScrollbarVisibility() {
3
+ useEffect(() => {
4
+ let scrollTimer = null;
5
+ const handleScroll = () => {
6
+ // Navigation이 열렸을 때는 스크롤바 제어 비활성화
7
+ if (document.body.classList.contains("navigation-open")) {
8
+ return;
9
+ }
10
+ // 스크롤 중일 때 클래스 추가
11
+ document.documentElement.classList.add("scrolling");
12
+ document.body.classList.add("scrolling");
13
+ // 기존 타이머 클리어
14
+ if (scrollTimer) {
15
+ clearTimeout(scrollTimer);
16
+ }
17
+ // 1.5초 후 스크롤바 숨김
18
+ scrollTimer = setTimeout(() => {
19
+ document.documentElement.classList.remove("scrolling");
20
+ document.body.classList.remove("scrolling");
21
+ }, 1500);
22
+ };
23
+ // 스크롤 이벤트 리스너 추가
24
+ window.addEventListener("scroll", handleScroll, { passive: true });
25
+ // 마우스가 스크롤바 영역에 있을 때 표시
26
+ const handleMouseMove = (event) => {
27
+ // Navigation이 열렸을 때는 스크롤바 제어 비활성화
28
+ if (document.body.classList.contains("navigation-open")) {
29
+ return;
30
+ }
31
+ const isNearScrollbar = window.innerWidth - event.clientX < 20;
32
+ if (isNearScrollbar) {
33
+ document.documentElement.classList.add("scrolling");
34
+ document.body.classList.add("scrolling");
35
+ }
36
+ };
37
+ const handleMouseLeave = () => {
38
+ // Navigation이 열렸을 때는 스크롤바 제어 비활성화
39
+ if (document.body.classList.contains("navigation-open")) {
40
+ return;
41
+ }
42
+ // 마우스가 창을 벗어나면 스크롤바 숨김
43
+ if (scrollTimer) {
44
+ clearTimeout(scrollTimer);
45
+ }
46
+ scrollTimer = setTimeout(() => {
47
+ document.documentElement.classList.remove("scrolling");
48
+ document.body.classList.remove("scrolling");
49
+ }, 500);
50
+ };
51
+ window.addEventListener("mousemove", handleMouseMove, { passive: true });
52
+ window.addEventListener("mouseleave", handleMouseLeave);
53
+ // 클린업 함수
54
+ return () => {
55
+ if (scrollTimer) {
56
+ clearTimeout(scrollTimer);
57
+ }
58
+ window.removeEventListener("scroll", handleScroll);
59
+ window.removeEventListener("mousemove", handleMouseMove);
60
+ window.removeEventListener("mouseleave", handleMouseLeave);
61
+ // 클래스 제거
62
+ document.documentElement.classList.remove("scrolling");
63
+ document.body.classList.remove("scrolling");
64
+ };
65
+ }, []);
66
+ }
@@ -1,9 +1,39 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../../util";
3
- import { useRef } from "react";
3
+ import { useRef, useState, useEffect } from "react";
4
4
  export default function Announcement({ banners }) {
5
5
  const scrollContainerRef = useRef(null);
6
6
  const cardWidth = 320; //card width
7
+ // 스크롤 위치 상태 관리
8
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
9
+ const [canScrollRight, setCanScrollRight] = useState(true);
10
+ // 스크롤 위치 확인 함수
11
+ const checkScrollPosition = () => {
12
+ if (scrollContainerRef.current) {
13
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
14
+ // 왼쪽 끝 확인 (약간의 여유값 포함)
15
+ setCanScrollLeft(scrollLeft > 5);
16
+ // 오른쪽 끝 확인 (약간의 여유값 포함)
17
+ setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 5);
18
+ }
19
+ };
20
+ // 초기 로드시와 banners 변경시 스크롤 위치 확인
21
+ useEffect(() => {
22
+ const timer = setTimeout(() => {
23
+ checkScrollPosition();
24
+ }, 100); // DOM 렌더링 후 확인
25
+ return () => clearTimeout(timer);
26
+ }, [banners]);
27
+ // 스크롤 이벤트 리스너 등록
28
+ useEffect(() => {
29
+ const scrollContainer = scrollContainerRef.current;
30
+ if (scrollContainer) {
31
+ scrollContainer.addEventListener("scroll", checkScrollPosition);
32
+ return () => {
33
+ scrollContainer.removeEventListener("scroll", checkScrollPosition);
34
+ };
35
+ }
36
+ }, []);
7
37
  const handleScroll = (direction) => {
8
38
  if (scrollContainerRef.current) {
9
39
  const scrollAmount = cardWidth;
@@ -19,7 +49,7 @@ export default function Announcement({ banners }) {
19
49
  };
20
50
  const bgWrapper = {
21
51
  displays: "flex",
22
- sizes: "h-fit w-full mt-10",
52
+ sizes: "h-fit w-full mt-10 pb-10",
23
53
  bacgrounds: "bg-gray-light/80",
24
54
  };
25
55
  const container = {
@@ -52,18 +82,23 @@ export default function Announcement({ banners }) {
52
82
  spacings: "gap-5 xl:mr-[calc(100vw-1200px)]",
53
83
  };
54
84
  const buttonPositioning = {
55
- displays: "hidden flex-row md:flex",
85
+ displays: "flex flex-row",
56
86
  sizes: "w-full h-full",
57
- spacings: "xl:pl-[calc(50vw-600px)] md:pl-[62px] pl-2 pr-2",
58
- positions: "absolute top-0 left-0 justify-between items-center opacity-0 group-hover:opacity-100 duration-300",
87
+ spacings: "xl:pr-[calc(50vw-600px)] md:pr-[62px] pl-2 pr-4 gap-5",
88
+ positions: "justify-end items-center duration-300",
59
89
  hovering: "group pointer-events-none",
60
90
  };
61
91
  const hoverButton = {
62
- sizes: "rounded-full w-12 h-12 scale-50 group-hover:scale-100",
63
- animation: "duration-300 ",
64
- test: "bg-gray-medium/20 hover:bg-gray-medium/50 pointer-events-auto backdrop-blur-sm fill-red-500",
92
+ sizes: "rounded-full w-9 h-9",
93
+ animation: "duration-300",
94
+ test: "bg-gray-medium/50 hover:bg-gray-medium pointer-events-auto backdrop-blur-sm",
95
+ };
96
+ const disabledButton = {
97
+ sizes: "rounded-full w-9 h-9",
98
+ animation: "duration-300",
99
+ test: "bg-gray-medium/20 pointer-events-none backdrop-blur-sm opacity-30",
65
100
  };
66
- return (_jsx("div", { className: cn(bgWrapper), children: _jsxs("div", { className: cn(container), children: [_jsx("div", { className: cn(deckTitlePositioning), children: _jsx("div", { className: cn(deckTitle), children: "\uACF5\uC9C0\uC0AC\uD56D" }) }), _jsx("div", { className: cn(cardPositioning), children: _jsx("div", { className: cn(cardWrapper), ref: scrollContainerRef, children: _jsx("div", { className: cn(cardDeck), children: banners.map((banner) => (_jsx(Banner, { ...banner }, banner.titles.title))) }) }) }), _jsxs("div", { className: cn(buttonPositioning), children: [_jsx("div", { className: cn(hoverButton), onClick: () => handleScroll("left"), children: _jsx("img", { src: "images/home/handle-left.svg", alt: "" }) }), _jsx("div", { className: cn(hoverButton), onClick: () => handleScroll("right"), children: _jsx("img", { src: "images/home/handle-right.svg", alt: "" }) })] })] }) }));
101
+ return (_jsx("div", { className: cn(bgWrapper), children: _jsxs("div", { className: cn(container), children: [_jsx("div", { className: cn(deckTitlePositioning), children: _jsx("div", { className: cn(deckTitle), children: "\uACF5\uC9C0\uC0AC\uD56D" }) }), _jsx("div", { className: cn(cardPositioning), children: _jsx("div", { className: cn(cardWrapper), ref: scrollContainerRef, children: _jsx("div", { className: cn(cardDeck), children: banners.map((banner) => (_jsx(Banner, { ...banner }, banner.titles.title))) }) }) }), _jsxs("div", { className: cn(buttonPositioning), children: [_jsx("div", { className: cn(canScrollLeft ? hoverButton : disabledButton), onClick: canScrollLeft ? () => handleScroll("left") : undefined, children: _jsx("img", { src: "images/home/handle-left.svg", alt: "" }) }), _jsx("div", { className: cn(canScrollRight ? hoverButton : disabledButton), onClick: canScrollRight ? () => handleScroll("right") : undefined, children: _jsx("img", { src: "images/home/handle-right.svg", alt: "" }) })] })] }) }));
67
102
  }
68
103
  function Banner({ onClick, titles }) {
69
104
  //ghost card snaps to the end of the viewport
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
2
3
  import { cn } from "../../../util";
3
4
  export default function Float({ contents }) {
5
+ const [hoveredButton, setHoveredButton] = useState(null);
4
6
  const container = cn("fixed flex flex-col bottom-20 md:bottom-16 right-5 md:right-16 z-40 overflow-visible", "gap-5");
5
7
  const buttonBox = (background) => cn("flex justify-center items-center", "p-1.5 md:p-3", "w-12 h-12 md:w-16 md:h-16", "rounded-full overflow-hidden shadow-main hover:scale-110 duration-300 hover:shadow-icon-hover", background ?? "bg-white");
6
- return (_jsx("div", { className: container, children: contents.map(({ image, onClick, background, hoverContent }) => (_jsxs("div", { className: "relative group flex items-center", children: [hoverContent && (_jsx("div", { className: cn("absolute right-full mr-3", "bg-gray-dark text-white text-sm px-5 py-2 rounded shadow-md whitespace-nowrap", "opacity-0 group-hover:opacity-100 transition-opacity duration-200"), children: hoverContent })), _jsx("button", { onClick: onClick, className: buttonBox(background), children: _jsx("img", { src: image, alt: "image", className: "object-contain" }) })] }, image))) }));
8
+ return (_jsx("div", { className: container, children: contents.map(({ image, onClick, background, hoverContent }, index) => (_jsxs("div", { className: "relative flex items-center", children: [hoverContent && (_jsx("div", { className: cn("absolute right-full mr-3 pointer-events-none", "bg-gray-dark text-white text-sm px-5 py-2 rounded shadow-md whitespace-nowrap", "transition-opacity duration-200", hoveredButton === index ? "opacity-100" : "opacity-0"), children: hoverContent })), _jsx("button", { onClick: onClick, className: buttonBox(background), onMouseEnter: () => setHoveredButton(index), onMouseLeave: () => setHoveredButton(null), children: _jsx("img", { src: image, alt: "image", className: "object-contain" }) })] }, image))) }));
7
9
  }
@@ -1,11 +1,41 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../../util";
3
- import { useRef } from "react";
3
+ import { useRef, useState, useEffect } from "react";
4
4
  import { Label } from "../../../widget";
5
5
  export default function NewsPaper({ banners, browse, option, }) {
6
6
  const { className } = option ?? {};
7
7
  const scrollContainerRef = useRef(null);
8
8
  const cardWidth = 480; //card width
9
+ // 스크롤 위치 상태 관리
10
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
11
+ const [canScrollRight, setCanScrollRight] = useState(true);
12
+ // 스크롤 위치 확인 함수
13
+ const checkScrollPosition = () => {
14
+ if (scrollContainerRef.current) {
15
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
16
+ // 왼쪽 끝 확인 (약간의 여유값 포함)
17
+ setCanScrollLeft(scrollLeft > 5);
18
+ // 오른쪽 끝 확인 (약간의 여유값 포함)
19
+ setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 5);
20
+ }
21
+ };
22
+ // 초기 로드시와 banners 변경시 스크롤 위치 확인
23
+ useEffect(() => {
24
+ const timer = setTimeout(() => {
25
+ checkScrollPosition();
26
+ }, 100); // DOM 렌더링 후 확인
27
+ return () => clearTimeout(timer);
28
+ }, [banners]);
29
+ // 스크롤 이벤트 리스너 등록
30
+ useEffect(() => {
31
+ const scrollContainer = scrollContainerRef.current;
32
+ if (scrollContainer) {
33
+ scrollContainer.addEventListener("scroll", checkScrollPosition);
34
+ return () => {
35
+ scrollContainer.removeEventListener("scroll", checkScrollPosition);
36
+ };
37
+ }
38
+ }, []);
9
39
  const handleScroll = (direction) => {
10
40
  if (scrollContainerRef.current) {
11
41
  const scrollAmount = cardWidth;
@@ -49,23 +79,28 @@ export default function NewsPaper({ banners, browse, option, }) {
49
79
  spacings: "gap-5 xl:mr-[calc(100vw-1200px)]",
50
80
  };
51
81
  const buttonPositioning = {
52
- displays: "hidden flex-row md:flex",
82
+ displays: "flex flex-row",
53
83
  sizes: "w-full h-full",
54
- spacings: "xl:pl-[calc(50vw-600px)] md:pl-[62px] pl-2 pr-2",
55
- positions: "absolute top-0 left-0 justify-between items-center opacity-0 group-hover:opacity-100 duration-300",
84
+ spacings: "xl:pr-[calc(50vw-600px)] md:pr-[62px] pl-2 pr-4 gap-5",
85
+ positions: "justify-end items-center duration-300",
56
86
  hovering: "group pointer-events-none",
57
87
  };
58
88
  const hoverButton = {
59
- sizes: "rounded-full w-12 h-12 scale-50 group-hover:scale-100",
60
- animation: "duration-300 ",
61
- test: "bg-gray-medium/20 hover:bg-gray-medium/50 pointer-events-auto backdrop-blur-sm fill-red-500",
89
+ sizes: "rounded-full w-9 h-9",
90
+ animation: "duration-300",
91
+ test: "bg-gray-medium/50 hover:bg-gray-medium pointer-events-auto backdrop-blur-sm",
92
+ };
93
+ const disabledButton = {
94
+ sizes: "rounded-full w-9 h-9",
95
+ animation: "duration-300",
96
+ test: "bg-gray-medium/20 pointer-events-none backdrop-blur-sm opacity-30",
62
97
  };
63
98
  return (_jsxs("div", { className: cn(container), children: [_jsx("div", { className: cn(deckTitlePositioning), children: _jsxs("div", { className: cn(deckTitle), children: [_jsx("div", { children: "\uC0C8\uB85C\uC6B4 \uC18C\uC2DD" }), _jsx(Label.Button, { title: "\uB354 \uBCF4\uAE30", onClick: browse, option: {
64
99
  width: "xs",
65
100
  height: "xs",
66
101
  text: "text-white hover:text-green-dark font-bold",
67
102
  background: "bg-green-dark hover:bg-green-light",
68
- } })] }) }), _jsx("div", { className: cn(cardPositioning), children: _jsx("div", { className: cn(cardWrapper), ref: scrollContainerRef, children: _jsx("div", { className: cn(cardDeck), children: banners.map((banner) => (_jsx(Banner, { ...banner }, banner.image.src))) }) }) }), _jsxs("div", { className: cn(buttonPositioning), children: [_jsx("div", { className: cn(hoverButton), onClick: () => handleScroll("left"), children: _jsx("img", { src: "images/home/handle-left.svg", alt: "" }) }), _jsx("div", { className: cn(hoverButton), onClick: () => handleScroll("right"), children: _jsx("img", { src: "images/home/handle-right.svg", alt: "" }) })] })] }));
103
+ } })] }) }), _jsx("div", { className: cn(cardPositioning), children: _jsx("div", { className: cn(cardWrapper), ref: scrollContainerRef, children: _jsx("div", { className: cn(cardDeck), children: banners.map((banner) => (_jsx(Banner, { ...banner }, banner.image.src))) }) }) }), _jsxs("div", { className: cn(buttonPositioning), children: [_jsx("div", { className: cn(canScrollLeft ? hoverButton : disabledButton), onClick: canScrollLeft ? () => handleScroll("left") : undefined, children: _jsx("img", { src: "images/home/handle-left.svg", alt: "" }) }), _jsx("div", { className: cn(canScrollRight ? hoverButton : disabledButton), onClick: canScrollRight ? () => handleScroll("right") : undefined, children: _jsx("img", { src: "images/home/handle-right.svg", alt: "" }) })] })] }));
69
104
  }
70
105
  function Banner({ onClick, image, option }) {
71
106
  const { background } = option ?? {};
@@ -1,9 +1,39 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from "react";
2
+ import { useRef, useState, useEffect } from "react";
3
3
  import { cn } from "../../../util";
4
4
  export default function Service({ banners }) {
5
5
  const scrollContainerRef = useRef(null);
6
6
  const cardWidth = 400; // card width
7
+ // 스크롤 위치 상태 관리
8
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
9
+ const [canScrollRight, setCanScrollRight] = useState(true);
10
+ // 스크롤 위치 확인 함수
11
+ const checkScrollPosition = () => {
12
+ if (scrollContainerRef.current) {
13
+ const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
14
+ // 왼쪽 끝 확인 (약간의 여유값 포함)
15
+ setCanScrollLeft(scrollLeft > 5);
16
+ // 오른쪽 끝 확인 (약간의 여유값 포함)
17
+ setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 5);
18
+ }
19
+ };
20
+ // 초기 로드시와 banners 변경시 스크롤 위치 확인
21
+ useEffect(() => {
22
+ const timer = setTimeout(() => {
23
+ checkScrollPosition();
24
+ }, 100); // DOM 렌더링 후 확인
25
+ return () => clearTimeout(timer);
26
+ }, [banners]);
27
+ // 스크롤 이벤트 리스너 등록
28
+ useEffect(() => {
29
+ const scrollContainer = scrollContainerRef.current;
30
+ if (scrollContainer) {
31
+ scrollContainer.addEventListener("scroll", checkScrollPosition);
32
+ return () => {
33
+ scrollContainer.removeEventListener("scroll", checkScrollPosition);
34
+ };
35
+ }
36
+ }, []);
7
37
  const handleScroll = (direction) => {
8
38
  if (scrollContainerRef.current) {
9
39
  const scrollAmount = cardWidth;
@@ -48,18 +78,23 @@ export default function Service({ banners }) {
48
78
  spacings: "gap-5 xl:mr-[calc(100vw-1200px)]",
49
79
  };
50
80
  const buttonPositioning = {
51
- displays: "hidden flex-row md:flex",
81
+ displays: "flex flex-row",
52
82
  sizes: "w-full h-full",
53
- spacings: "xl:pl-[calc(50vw-600px)] md:pl-[62px] pl-2 pr-2",
54
- positions: "absolute top-0 left-0 justify-between items-center opacity-0 group-hover:opacity-100 duration-300",
83
+ spacings: "xl:pr-[calc(50vw-600px)] md:pr-[62px] pl-2 pr-4 gap-5",
84
+ positions: "justify-end items-center duration-300",
55
85
  hovering: "group pointer-events-none",
56
86
  };
57
87
  const hoverButton = {
58
- sizes: "rounded-full w-12 h-12 scale-50 group-hover:scale-100",
59
- animation: "duration-300 ",
60
- test: "bg-gray-medium/20 hover:bg-gray-medium/50 pointer-events-auto backdrop-blur-sm fill-red-500",
88
+ sizes: "rounded-full w-9 h-9",
89
+ animation: "duration-300",
90
+ test: "bg-gray-medium/50 hover:bg-gray-medium pointer-events-auto backdrop-blur-sm",
91
+ };
92
+ const disabledButton = {
93
+ sizes: "rounded-full w-9 h-9",
94
+ animation: "duration-300",
95
+ test: "bg-gray-medium/20 pointer-events-none backdrop-blur-sm opacity-30",
61
96
  };
62
- return (_jsxs("div", { className: cn(container), children: [_jsx("div", { className: cn(deckTitlePositioning), children: _jsx("div", { className: cn(deckTitle), children: "\uD1A0\uC140\uC758 \uCC28\uBCC4\uD654\uB41C \uC11C\uBE44\uC2A4" }) }), _jsx("div", { className: cn(cardPositioning), children: _jsx("div", { className: cn(cardWrapper), ref: scrollContainerRef, children: _jsx("div", { className: cn(cardDeck), children: banners.map((banner) => (_jsx(Banner, { ...banner }, banner.titles.title))) }) }) }), _jsxs("div", { className: cn(buttonPositioning), children: [_jsx("div", { className: cn(hoverButton), onClick: () => handleScroll("left"), children: _jsx("img", { src: "images/home/handle-left.svg", alt: "" }) }), _jsx("div", { className: cn(hoverButton), onClick: () => handleScroll("right"), children: _jsx("img", { src: "images/home/handle-right.svg", alt: "" }) })] })] }));
97
+ return (_jsxs("div", { className: cn(container), children: [_jsx("div", { className: cn(deckTitlePositioning), children: _jsx("div", { className: cn(deckTitle), children: "\uD1A0\uC140\uC758 \uCC28\uBCC4\uD654\uB41C \uC11C\uBE44\uC2A4" }) }), _jsx("div", { className: cn(cardPositioning), children: _jsx("div", { className: cn(cardWrapper), ref: scrollContainerRef, children: _jsx("div", { className: cn(cardDeck), children: banners.map((banner) => (_jsx(Banner, { ...banner }, banner.titles.title))) }) }) }), _jsxs("div", { className: cn(buttonPositioning), children: [_jsx("div", { className: cn(canScrollLeft ? hoverButton : disabledButton), onClick: canScrollLeft ? () => handleScroll("left") : undefined, children: _jsx("img", { src: "images/home/handle-left.svg", alt: "" }) }), _jsx("div", { className: cn(canScrollRight ? hoverButton : disabledButton), onClick: canScrollRight ? () => handleScroll("right") : undefined, children: _jsx("img", { src: "images/home/handle-right.svg", alt: "" }) })] })] }));
63
98
  }
64
99
  function Banner({ titles, onClick, image, option }) {
65
100
  const { background } = option ?? {};
@@ -11,6 +11,7 @@ export default function Carousel({ contents, option, }) {
11
11
  const [index, setIndex] = useState(0);
12
12
  const [flag, inTime] = useEase(10000, 1000);
13
13
  const [loaded, setLoaded] = useState(false);
14
+ const [isPlaying, setIsPlaying] = useState(true);
14
15
  const isMD = useResponsive("md");
15
16
  const isXL = useResponsive("xl");
16
17
  const intervalRef = useRef(null);
@@ -24,20 +25,29 @@ export default function Carousel({ contents, option, }) {
24
25
  easing: easings.easeOutQuad,
25
26
  },
26
27
  });
28
+ // 인덱스 변경 시 애니메이션을 위해 loaded를 false로 설정
29
+ useEffect(() => {
30
+ setLoaded(false);
31
+ // 짧은 지연 후 애니메이션 시작 (이미지가 캐시된 경우를 위해)
32
+ const timer = setTimeout(() => {
33
+ setLoaded(true);
34
+ }, 50);
35
+ return () => clearTimeout(timer);
36
+ }, [index]);
27
37
  const resetTimer = useCallback(() => {
28
38
  // resetting the timer
29
39
  if (intervalRef.current) {
30
40
  clearInterval(intervalRef.current);
31
41
  }
32
- intervalRef.current = setInterval(() => {
33
- setIndex((prevIndex) => (prevIndex + 1) % contents.length);
34
- setLoaded(false); // trigger animation
35
- }, 3000);
36
- }, [contents.length]);
37
- useEffect(() => {
38
- if (!option?.isStatic) {
39
- resetTimer(); // timerstarts after mounting component
42
+ if (isPlaying && !option?.isStatic) {
43
+ intervalRef.current = setInterval(() => {
44
+ setIndex((prevIndex) => (prevIndex + 1) % contents.length);
45
+ setLoaded(false); // trigger animation
46
+ }, 3000);
40
47
  }
48
+ }, [contents.length, isPlaying, option?.isStatic]);
49
+ useEffect(() => {
50
+ resetTimer(); // timer starts/stops based on isPlaying state
41
51
  return () => {
42
52
  // clear timer after unmounting component
43
53
  if (intervalRef.current) {
@@ -55,6 +65,10 @@ export default function Carousel({ contents, option, }) {
55
65
  setIndex((prevIndex) => (prevIndex - 1 + contents.length) % contents.length);
56
66
  resetTimer();
57
67
  };
68
+ const handlePlayPause = (e) => {
69
+ e.stopPropagation();
70
+ setIsPlaying(!isPlaying);
71
+ };
58
72
  // Styles definition
59
73
  const container = {
60
74
  displays: "relative flex justify-center items-center",
@@ -64,9 +78,9 @@ export default function Carousel({ contents, option, }) {
64
78
  animations: "duration-300",
65
79
  };
66
80
  const titleWrapper = {
67
- displays: "flex flex-col xxs:flex-row xxs:items-center md:items-start md:flex-col",
68
- sizes: "w-full h-fit xxs:w-fit md:w-72",
69
- spacings: "mt-4 px-5 xxs:mt-8 xxs:gap-8 md:p-0 md:mt-0 md:gap-6 md:pl-7.5",
81
+ displays: "static md:relative flex flex-col xxs:flex-row xxs:items-center md:items-start md:flex-col",
82
+ sizes: "w-full h-fit xxs:h-full xxs:w-fit md:w-72",
83
+ spacings: "mt-4 px-5 xxs:mt-8 xxs:gap-8 md:p-0 md:mt-0 md:gap-6 md:pl-7.5 md:pt-10 ml:pt-20",
70
84
  animations: "duration-500",
71
85
  };
72
86
  const titleSet = {
@@ -80,7 +94,7 @@ export default function Carousel({ contents, option, }) {
80
94
  textStyles: "text-sm md:text-base break-keep",
81
95
  };
82
96
  const body = {
83
- displays: "flex flex-none flex-col items-center justify-center md:flex-row",
97
+ displays: "relative flex flex-none flex-col items-center justify-center md:flex-row",
84
98
  sizes: "w-full max-w-256 h-full",
85
99
  text: contents[index].option?.text ?? "text-gray-dark",
86
100
  animations: "duration-500",
@@ -93,19 +107,26 @@ export default function Carousel({ contents, option, }) {
93
107
  styles: "rounded-md",
94
108
  };
95
109
  const buttonBoxPosition = {
96
- display: "absolute flex justify-center items-center md:static md:justify-start",
110
+ display: "absolute flex justify-center items-center md:justify-start",
97
111
  sizes: "w-full h-fit left-0 bottom-0 z-10",
98
- spacings: "ml-0 md:ml-10 xm:ml-0",
112
+ spacings: "ml-0 md:ml-[calc(70px)] xm:ml-7.5",
99
113
  };
100
114
  const buttonBox = {
101
115
  displays: "relative justify-center items-center gap-1 flex z-10",
102
116
  animations: "duration-500",
103
- sizes: "w-34 h-10 md:h-6.25 md:w-26 bg-gray-dark rounded-full",
104
- paddings: "px-1.5 mt-0 mb-5 md:mt-12 ",
117
+ sizes: "w-34 h-10 md:h-6.25 md:w-26 rounded-full",
118
+ paddings: "px-1.5 mt-0 mb-5 md:mb-10 ml:mb-17 ",
119
+ background: "bg-white shadow-main",
105
120
  };
106
121
  const handlePosition = {
107
122
  displays: "absolute flex justify-between items-center",
108
123
  sizes: "w-full h-full",
109
124
  };
110
- return (_jsx("div", { onClick: contents[index].onClick, className: cn(container), children: _jsxs("div", { className: cn(body), children: [_jsxs("div", { className: cn(titleWrapper), children: [_jsxs("div", { className: cn(titleSet), children: [_jsx("div", { className: cn(tagBox), children: tagString[tag.type] }), _jsx("div", { className: "font-pretendard-bold text-xl md:text-2xl", children: titles.title })] }), _jsx(LineBreaks, { className: cn(subtitleStyles), texts: titles.subtitle }), _jsx("div", { className: cn(buttonBoxPosition), onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: cn(buttonBox), children: [_jsxs("div", { className: cn(handlePosition), children: [_jsx("button", { onClick: handlePrev, className: "w-10 md:w-6 h-full flex justify-center items-center", children: _jsx(SVG.Symbol.LessThan, {}) }), _jsx("button", { onClick: handleNext, className: "w-10 md:w-6 h-full flex justify-center items-center", children: _jsx(SVG.Symbol.GreaterThan, {}) })] }), _jsxs("div", { className: "flex flex-row md:gap-2 gap-4 justify-center items-center", children: [_jsx("div", { className: "text-white w-4 text-sm text-center", children: index + 1 }), _jsx("div", { className: "w-0.5 h-3 bg-gray-medium" }), _jsx("div", { className: "text-white w-4 text-sm text-center", children: contents.length })] })] }) })] }), _jsx("div", { className: "h-full overflow-hidden w-fit image-container", children: image && (_jsx(animated.img, { src: image, alt: "carousel-image", onLoad: () => setLoaded(true), className: "object-cover h-full z-0", style: transitionStyles })) })] }) }));
125
+ const playbutton = {
126
+ displays: "absolute flex justify-center items-center",
127
+ positions: "bottom-10 right-10",
128
+ sizes: "w-10 h-10 rounded-full",
129
+ background: "bg-white text-gray-dark shadow-main",
130
+ };
131
+ return (_jsx("div", { onClick: contents[index].onClick, className: cn(container), children: _jsxs("div", { className: cn(body), children: [_jsxs("div", { className: cn(titleWrapper), children: [_jsxs("div", { className: cn(titleSet), children: [_jsx("div", { className: cn(tagBox), children: tagString[tag.type] }), _jsx("div", { className: "font-pretendard-bold text-xl md:text-2xl", children: titles.title })] }), _jsx(LineBreaks, { className: cn(subtitleStyles), texts: titles.subtitle }), _jsx("div", { className: cn(buttonBoxPosition), onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: cn(buttonBox), children: [_jsxs("div", { className: cn(handlePosition), children: [_jsx("button", { onClick: handlePrev, className: "w-10 md:w-6 h-full flex justify-center items-center", children: _jsx(SVG.Symbol.LessThan, { color: "text-gray-dark" }) }), _jsx("button", { onClick: handleNext, className: "w-10 md:w-6 h-full flex justify-center items-center", children: _jsx(SVG.Symbol.GreaterThan, { color: "text-gray-dark" }) })] }), _jsxs("div", { className: "flex flex-row md:gap-2 gap-4 justify-center items-center", children: [_jsx("div", { className: "text-gray-dark w-4 text-sm text-center", children: index + 1 }), _jsx("div", { className: "w-0.5 h-3 bg-gray-medium" }), _jsx("div", { className: "text-gray-dark w-4 text-sm text-center", children: contents.length })] })] }) })] }), _jsx("div", { className: "h-full overflow-hidden w-fit image-container", children: image && (_jsx(animated.img, { src: image, alt: "carousel-image", onLoad: () => setLoaded(true), className: "object-cover h-full z-0", style: transitionStyles })) }), isMD && (_jsx("div", { className: cn(playbutton), onClick: handlePlayPause, children: isPlaying ? (_jsx("svg", { width: "12", height: "14", viewBox: "0 0 12 14", fill: "none", children: _jsx("path", { fill: "currentColor", fillRule: "evenodd", d: "M9.5.25A1.5 1.5 0 008 1.75v10.5a1.5 1.5 0 003 0V1.75A1.5 1.5 0 009.5.25zm-7 0A1.5 1.5 0 001 1.75v10.5a1.5 1.5 0 003 0V1.75A1.5 1.5 0 002.5.25z", clipRule: "evenodd" }) })) : (_jsx("svg", { width: "12", height: "14", viewBox: "0 0 12 14", fill: "none", children: _jsx("path", { fill: "currentColor", d: "M1.688 12.952a1 1 0 001.614.789l7.4-5.77a1 1 0 00.019-1.562L3.32.34a1 1 0 00-1.634.774v11.838z" }) })) }))] }) }));
111
132
  }
@@ -5,8 +5,10 @@ import { Label } from "../../../../widget";
5
5
  import { useResponsive } from "../../../../hook";
6
6
  export default function Header({ logo, rightButton, contents, }) {
7
7
  const [isExpanded, setIsExpanded] = useState(false);
8
+ const [hasToggled, setHasToggled] = useState(false);
8
9
  const toggleHeight = () => {
9
10
  setIsExpanded(!isExpanded);
11
+ setHasToggled(true); // 한 번이라도 토글되었음을 표시
10
12
  };
11
13
  const handleClickOutside = (event) => {
12
14
  const menuElement = document.getElementById("buttonArea");
@@ -34,7 +36,11 @@ export default function Header({ logo, rightButton, contents, }) {
34
36
  sizes: "w-full",
35
37
  styles: "bg-white box-shadow",
36
38
  textstyles: "antialiased",
37
- animation: !isMD && isExpanded ? "animate-grow" : "animate-shrink"
39
+ animation: hasToggled && !isMD
40
+ ? isExpanded
41
+ ? "animate-grow"
42
+ : "animate-shrink"
43
+ : "",
38
44
  };
39
45
  const body = {
40
46
  displays: "flex md:flex-row flex-col md:items-center items-start justify-between",
@@ -53,7 +59,7 @@ export default function Header({ logo, rightButton, contents, }) {
53
59
  animations: "duration-300",
54
60
  sizes: "h-10 rounded-md w-fit flex-none",
55
61
  spacings: "px-2.5",
56
- scrollMargin: "translate-x-5 md:translate-x-0 "
62
+ scrollMargin: "translate-x-5 md:translate-x-0 ",
57
63
  };
58
64
  const leftWrapper = {
59
65
  displays: "flex flex-row gap-6 items-center flex-none",
@@ -79,8 +85,8 @@ export default function Header({ logo, rightButton, contents, }) {
79
85
  height: "xs",
80
86
  background: gradient.bg.greenToRed,
81
87
  text: "text-white",
82
- } })), _jsx("button", { onClick: toggleHeight, className: cn(menuButton), children: _jsx("div", { className: "w-6 h-6", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", children: [!isExpanded && _jsx("path", { id: "menu", fill: "#B0B8C1", d: "M4.118 6.2h16a1.2 1.2 0 100-2.4h-16a1.2 1.2 0 100 2.4m16 4.6h-16a1.2 1.2 0 100 2.4h16a1.2 1.2 0 100-2.4m0 7h-16a1.2 1.2 0 100 2.4h16a1.2 1.2 0 100-2.4", "fill-rule": "evenodd" }), isExpanded && _jsx("path", { id: "close", fill: "#B0B8C1", "fill-rule": "evenodd", d: "M13.815 12l5.651-5.651a1.2 1.2 0 00-1.697-1.698l-5.651 5.652-5.652-5.652a1.201 1.201 0 00-1.697 1.698L10.421 12l-5.652 5.651a1.202 1.202 0 00.849 2.049c.307 0 .614-.117.848-.351l5.652-5.652 5.651 5.652a1.198 1.198 0 001.697 0 1.2 1.2 0 000-1.698L13.815 12z" })] }) }) })] })] }), _jsxs("div", { className: cn(rightWrapper), children: [_jsx("div", { className: cn(buttonBoxWrapper), children: contents.map((button, index) => (_jsx("button", { onClick: button.onClick, className: cn(buttonBox), children: button.title }, index))) }), _jsx("div", { className: "hidden md:block", children: rightButton && (_jsx(Label.Button, { title: rightButton.title, onClick: rightButton?.onClick, option: {
83
- width: "2xs",
88
+ } })), _jsx("button", { onClick: toggleHeight, className: cn(menuButton), children: _jsx("div", { className: "w-6 h-6", children: _jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", children: [!isExpanded && (_jsx("path", { id: "menu", fill: "#B0B8C1", d: "M4.118 6.2h16a1.2 1.2 0 100-2.4h-16a1.2 1.2 0 100 2.4m16 4.6h-16a1.2 1.2 0 100 2.4h16a1.2 1.2 0 100-2.4m0 7h-16a1.2 1.2 0 100 2.4h16a1.2 1.2 0 100-2.4", "fill-rule": "evenodd" })), isExpanded && (_jsx("path", { id: "close", fill: "#B0B8C1", "fill-rule": "evenodd", d: "M13.815 12l5.651-5.651a1.2 1.2 0 00-1.697-1.698l-5.651 5.652-5.652-5.652a1.201 1.201 0 00-1.697 1.698L10.421 12l-5.652 5.651a1.202 1.202 0 00.849 2.049c.307 0 .614-.117.848-.351l5.652-5.652 5.651 5.652a1.198 1.198 0 001.697 0 1.2 1.2 0 000-1.698L13.815 12z" }))] }) }) })] })] }), _jsxs("div", { className: cn(rightWrapper), children: [_jsx("div", { className: cn(buttonBoxWrapper), children: contents.map((button, index) => (_jsx("button", { onClick: button.onClick, className: cn(buttonBox), children: button.title }, index))) }), _jsx("div", { className: "hidden md:block", children: rightButton && (_jsx(Label.Button, { title: rightButton.title, onClick: rightButton?.onClick, option: {
89
+ width: "xs",
84
90
  height: "xs",
85
91
  background: gradient.bg.greenToRed,
86
92
  text: "text-white",
@@ -25,18 +25,81 @@ export default function Navigation({ browser, calendar, notice, event, }) {
25
25
  });
26
26
  // Scroll Lock 처리
27
27
  useEffect(() => {
28
- const preventScroll = (e) => e.preventDefault();
28
+ const preventScroll = (e) => {
29
+ // Navigation 내부 요소에서 발생한 이벤트는 허용
30
+ const target = e.target;
31
+ const navigationContainer = target?.closest("[data-navigation-content]");
32
+ if (navigationContainer) {
33
+ return;
34
+ }
35
+ e.preventDefault();
36
+ };
37
+ const preventWheel = (e) => {
38
+ // Navigation 내부 요소에서 발생한 이벤트는 허용
39
+ const target = e.target;
40
+ const navigationContainer = target?.closest("[data-navigation-content]");
41
+ if (navigationContainer) {
42
+ return;
43
+ }
44
+ e.preventDefault();
45
+ };
46
+ const preventKeyScroll = (e) => {
47
+ // Navigation 내부 요소에서 발생한 이벤트는 허용
48
+ const target = e.target;
49
+ const navigationContainer = target?.closest("[data-navigation-content]");
50
+ if (navigationContainer) {
51
+ return;
52
+ }
53
+ // 스크롤 관련 키 차단
54
+ if ([
55
+ "ArrowUp",
56
+ "ArrowDown",
57
+ "PageUp",
58
+ "PageDown",
59
+ "Home",
60
+ "End",
61
+ " ",
62
+ ].includes(e.key)) {
63
+ e.preventDefault();
64
+ }
65
+ };
29
66
  if (isOpen) {
67
+ // 스크롤 완전 차단
30
68
  document.body.style.overflow = "hidden";
69
+ document.documentElement.style.overflow = "hidden";
70
+ // 터치 스크롤 차단
31
71
  document.addEventListener("touchmove", preventScroll, { passive: false });
72
+ // 마우스 휠 스크롤 차단
73
+ document.addEventListener("wheel", preventWheel, { passive: false });
74
+ // 키보드 스크롤 차단
75
+ document.addEventListener("keydown", preventKeyScroll, {
76
+ passive: false,
77
+ });
78
+ // 스크롤바 가시성 제어 비활성화
79
+ document.documentElement.classList.add("navigation-open");
80
+ document.body.classList.add("navigation-open");
32
81
  }
33
82
  else {
83
+ // 스크롤 복원
34
84
  document.body.style.overflow = "";
85
+ document.documentElement.style.overflow = "";
86
+ // 이벤트 리스너 제거
35
87
  document.removeEventListener("touchmove", preventScroll);
88
+ document.removeEventListener("wheel", preventWheel);
89
+ document.removeEventListener("keydown", preventKeyScroll);
90
+ // 스크롤바 가시성 제어 복원
91
+ document.documentElement.classList.remove("navigation-open");
92
+ document.body.classList.remove("navigation-open");
36
93
  }
37
94
  return () => {
95
+ // 클린업
38
96
  document.body.style.overflow = "";
97
+ document.documentElement.style.overflow = "";
39
98
  document.removeEventListener("touchmove", preventScroll);
99
+ document.removeEventListener("wheel", preventWheel);
100
+ document.removeEventListener("keydown", preventKeyScroll);
101
+ document.documentElement.classList.remove("navigation-open");
102
+ document.body.classList.remove("navigation-open");
40
103
  };
41
104
  }, [isOpen]);
42
105
  useEffect(() => {
@@ -130,7 +193,7 @@ export default function Navigation({ browser, calendar, notice, event, }) {
130
193
  textStyles: "text-lg font-bold text-green-dark",
131
194
  };
132
195
  return (_jsxs(_Fragment, { children: [overlayCoverTransition((styles, item) => item && (_jsx(animated.div, { style: styles, className: "bg-white h-screen fixed top-0 left-0 z-40 flex flex-col justify-center items-center overflow-hidden gap-y-14 ", children: _jsxs("div", { className: "flex flex-col justify-center items-center overflow-hidden gap-y-14 transition-none", children: [_jsx("img", { src: "/images/logos/tosel.png", alt: "tosel", width: 368.56, height: 80.07 }), _jsxs("div", { role: "status", children: [_jsxs("svg", { "aria-hidden": "true", className: "w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-green-dark", viewBox: "0 0 100 101", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", fill: "currentColor" }), _jsx("path", { d: "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", fill: "currentFill" })] }), _jsx("span", { className: "sr-only", children: "Loading..." })] }), _jsx("div", { children: "dashboard loading..." })] }) }))), overlayPopTransition((styles, item) => type &&
133
- item && (_jsx(animated.div, { style: styles, className: cn(blurContainer), children: _jsxs("div", { className: cn(itemBody), onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: cn(scrollTitleWrapper), children: [_jsx("div", { className: cn(scrollTitle), children: navigationTypeString[type] }), _jsx("div", { className: "" })] }), _jsx("div", { className: cn(itemsContainer), children: _jsx(NavigationItem, { type: type, calendar: calendar, notice: notice, event: event }) })] }) }))), _jsx("div", { className: "fixed bottom-0 md:top-1/2 md:-translate-y-1/2 flex justify-center items-center z-45", children: _jsxs("div", { onClick: (e) => e.stopPropagation(), className: cn(container), children: [_jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("calendar"), "data-tooltip": "\uC2DC\uD5D8\uC77C\uC815", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Calendar, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("notification"), "data-tooltip": "\uACF5\uC9C0\uC0AC\uD56D", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Notification, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("search"), "data-tooltip": "\uAC80\uC0C9\uD558\uAE30", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Search, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("browser"), "data-tooltip": "\uB300\uC2DC\uBCF4\uB4DC", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Browser, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("event"), "data-tooltip": "\uC0C8\uC18C\uC2DD", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Newspaper, { size: isMobile ? "md" : "lg" }) }), tooltipText && (_jsx("div", { className: "absolute bg-gray-900 text-white text-sm px-3 py-1 rounded-lg transition-opacity duration-200 w-20 text-center", style: {
196
+ item && (_jsx(animated.div, { style: styles, className: cn(blurContainer), children: _jsxs("div", { className: cn(itemBody), onClick: (e) => e.stopPropagation(), "data-navigation-content": true, children: [_jsxs("div", { className: cn(scrollTitleWrapper), children: [_jsx("div", { className: cn(scrollTitle), children: navigationTypeString[type] }), _jsx("div", { className: "" })] }), _jsx("div", { className: cn(itemsContainer), children: _jsx(NavigationItem, { type: type, calendar: calendar, notice: notice, event: event }) })] }) }))), _jsx("div", { className: "fixed bottom-0 md:top-1/2 md:-translate-y-1/2 flex justify-center items-center z-45", children: _jsxs("div", { onClick: (e) => e.stopPropagation(), className: cn(container), children: [_jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("calendar"), "data-tooltip": "\uC2DC\uD5D8\uC77C\uC815", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Calendar, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("notification"), "data-tooltip": "\uACF5\uC9C0\uC0AC\uD56D", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Notification, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("search"), "data-tooltip": "\uAC80\uC0C9\uD558\uAE30", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Search, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("browser"), "data-tooltip": "\uB300\uC2DC\uBCF4\uB4DC", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Browser, { size: isMobile ? "md" : "lg" }) }), _jsx("button", { className: cn(iconWrapper), onClick: () => handleOpen("event"), "data-tooltip": "\uC0C8\uC18C\uC2DD", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseMove: handleMouseMove, children: _jsx(SVG.Icon.Newspaper, { size: isMobile ? "md" : "lg" }) }), tooltipText && (_jsx("div", { className: "absolute bg-gray-900 text-white text-sm px-3 py-1 rounded-lg transition-opacity duration-200 w-20 text-center", style: {
134
197
  left: `${position.x}px`,
135
198
  top: `${position.y}px`,
136
199
  pointerEvents: "none",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edu-tosel/design",
3
- "version": "1.0.303",
3
+ "version": "1.0.305",
4
4
  "description": "UI components for International TOSEL Committee",
5
5
  "keywords": [
6
6
  "jsx",
package/version.txt CHANGED
@@ -1 +1 @@
1
- 1.0.303
1
+ 1.0.305