@edu-tosel/design 1.0.303 → 1.0.304

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/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,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 ?? {};
@@ -64,9 +64,9 @@ export default function Carousel({ contents, option, }) {
64
64
  animations: "duration-300",
65
65
  };
66
66
  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",
67
+ displays: "static md:relative flex flex-col xxs:flex-row xxs:items-center md:items-start md:flex-col",
68
+ sizes: "w-full h-fit xxs:h-full 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 md:pt-20",
70
70
  animations: "duration-500",
71
71
  };
72
72
  const titleSet = {
@@ -93,15 +93,15 @@ export default function Carousel({ contents, option, }) {
93
93
  styles: "rounded-md",
94
94
  };
95
95
  const buttonBoxPosition = {
96
- display: "absolute flex justify-center items-center md:static md:justify-start",
96
+ display: "absolute flex justify-center items-center md:justify-start",
97
97
  sizes: "w-full h-fit left-0 bottom-0 z-10",
98
- spacings: "ml-0 md:ml-10 xm:ml-0",
98
+ spacings: "ml-0 md:ml-[calc(70px)] xm:ml-7.5",
99
99
  };
100
100
  const buttonBox = {
101
101
  displays: "relative justify-center items-center gap-1 flex z-10",
102
102
  animations: "duration-500",
103
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 ",
104
+ paddings: "px-1.5 mt-0 mb-5 md:mb-17 ",
105
105
  };
106
106
  const handlePosition = {
107
107
  displays: "absolute flex justify-between items-center",
@@ -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,7 +85,7 @@ 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: {
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: {
83
89
  width: "2xs",
84
90
  height: "xs",
85
91
  background: gradient.bg.greenToRed,
@@ -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.304",
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.304