@edu-tosel/design 1.0.349 → 1.0.351
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/layout/template/Action.js +1 -1
- package/layout/template/home/Announcement.js +2 -2
- package/layout/template/home/NewsPaper.js +2 -2
- package/layout/template/home/Service.js +2 -2
- package/layout/template/home/layout/Header.d.ts +28 -1
- package/layout/template/home/layout/Header.js +96 -3
- package/package.json +1 -1
- package/version.txt +1 -1
|
@@ -3,7 +3,7 @@ import React, { Fragment } from "react";
|
|
|
3
3
|
import { useActionStore } from "../../store";
|
|
4
4
|
function Show({ actions, children }) {
|
|
5
5
|
const { events } = useActionStore();
|
|
6
|
-
return (_jsxs(_Fragment, { children: [children, actions?.map(([flag, Component], index) => {
|
|
6
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "no-scrollbar", children: children }), actions?.map(([flag, Component], index) => {
|
|
7
7
|
const isVisible = typeof flag === "boolean"
|
|
8
8
|
? flag
|
|
9
9
|
: events?.some(({ event }) => event === flag);
|
|
@@ -80,7 +80,7 @@ export default function Announcement({ banners, browse }) {
|
|
|
80
80
|
//adjust the responsive right margin of scroller
|
|
81
81
|
const cardDeck = {
|
|
82
82
|
displays: "inline-flex",
|
|
83
|
-
spacings: "gap-5 xl:mr-[calc(100vw-
|
|
83
|
+
spacings: "gap-5 xl:mr-[calc(100vw-1200px)]",
|
|
84
84
|
};
|
|
85
85
|
const buttonPositioning = {
|
|
86
86
|
displays: "flex flex-row",
|
|
@@ -120,7 +120,7 @@ function Banner({ onClick, titles }) {
|
|
|
120
120
|
sizes: "w-65 h-40 xxxs:w-76 md:w-80 md:h-40 rounded-lg md:rounded-lg",
|
|
121
121
|
backgrounds: "bg-white",
|
|
122
122
|
hover: "hover:shadow-main-hover hover:scale-[1.03]",
|
|
123
|
-
position: "xl:translate-x-[calc(50vw-
|
|
123
|
+
position: "xl:translate-x-[calc(50vw-665px)] md:translate-x-[56px]",
|
|
124
124
|
transition: "duration-300",
|
|
125
125
|
displays: "relative display-block overflow-hidden",
|
|
126
126
|
fonts: "font-pretendard-var",
|
|
@@ -76,7 +76,7 @@ export default function NewsPaper({ banners, browse, option, }) {
|
|
|
76
76
|
//adjust the responsive right margin of scroller
|
|
77
77
|
const cardDeck = {
|
|
78
78
|
displays: "inline-flex",
|
|
79
|
-
spacings: "gap-5 xl:mr-[calc(100vw-
|
|
79
|
+
spacings: "gap-5 xl:mr-[calc(100vw-1200px)]",
|
|
80
80
|
};
|
|
81
81
|
const buttonPositioning = {
|
|
82
82
|
displays: "flex flex-row",
|
|
@@ -117,7 +117,7 @@ function Banner({ onClick, image, option }) {
|
|
|
117
117
|
sizes: "w-65 h-40 xxxs:w-76 xxxs:h-47.5 md:w-120 md:h-75 rounded-xl md:rounded-2xl",
|
|
118
118
|
backgrounds: background,
|
|
119
119
|
hover: "hover:shadow-main-hover hover:scale-[1.03]",
|
|
120
|
-
position: "xl:translate-x-[calc(50vw-
|
|
120
|
+
position: "xl:translate-x-[calc(50vw-665px)] md:translate-x-[56px]",
|
|
121
121
|
transition: "duration-300",
|
|
122
122
|
displays: "relative display-block overflow-hidden",
|
|
123
123
|
fonts: "font-pretendard-var",
|
|
@@ -75,7 +75,7 @@ export default function Service({ banners }) {
|
|
|
75
75
|
//adjust the responsive right margin of scroller
|
|
76
76
|
const cardDeck = {
|
|
77
77
|
displays: "inline-flex",
|
|
78
|
-
spacings: "gap-5 xl:mr-[calc(100vw-
|
|
78
|
+
spacings: "gap-5 xl:mr-[calc(100vw-1200px)]",
|
|
79
79
|
};
|
|
80
80
|
const buttonPositioning = {
|
|
81
81
|
displays: "flex flex-row",
|
|
@@ -111,7 +111,7 @@ function Banner({ titles, onClick, image, option }) {
|
|
|
111
111
|
sizes: "h-100 w-65 xxxs:w-76 md:w-100 md:h-125 rounded-xl md:rounded-2xl",
|
|
112
112
|
backgrounds: background,
|
|
113
113
|
hover: "hover:shadow-main-hover hover:scale-[1.03]",
|
|
114
|
-
position: "xl:translate-x-[calc(50vw-
|
|
114
|
+
position: "xl:translate-x-[calc(50vw-665px)] md:translate-x-[56px]",
|
|
115
115
|
transition: "duration-300",
|
|
116
116
|
displays: "relative display-block overflow-hidden",
|
|
117
117
|
fonts: "font-pretendard-var",
|
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
import { Button as _Button, OnClick } from "../../../../interface";
|
|
3
|
+
type MenuItem = {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
tag?: {
|
|
7
|
+
title: string;
|
|
8
|
+
background?: string;
|
|
9
|
+
};
|
|
10
|
+
onClick?: OnClick;
|
|
11
|
+
};
|
|
12
|
+
type MenuData = {
|
|
13
|
+
title: {
|
|
14
|
+
title: string;
|
|
15
|
+
image?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
button?: {
|
|
18
|
+
title: string;
|
|
19
|
+
style?: string;
|
|
20
|
+
align?: "left" | "right" | "center";
|
|
21
|
+
onClick?: OnClick;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
content: MenuItem[];
|
|
25
|
+
};
|
|
26
|
+
type ExtendedButton = _Button & {
|
|
27
|
+
menu?: MenuData;
|
|
28
|
+
};
|
|
3
29
|
export default function Header({ logo, rightButton, contents, }: {
|
|
4
30
|
logo: {
|
|
5
31
|
node: ReactNode;
|
|
6
32
|
onClick?: OnClick;
|
|
7
33
|
};
|
|
8
34
|
rightButton?: React.ReactNode;
|
|
9
|
-
contents:
|
|
35
|
+
contents: ExtendedButton[];
|
|
10
36
|
}): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from "react";
|
|
3
3
|
import { cn } from "../../../../util";
|
|
4
4
|
import { useResponsive } from "../../../../hook";
|
|
@@ -6,13 +6,49 @@ import { gsap } from "gsap";
|
|
|
6
6
|
export default function Header({ logo, rightButton, contents, }) {
|
|
7
7
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
8
8
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
9
|
+
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
10
|
+
const [mobileMenuIndex, setMobileMenuIndex] = useState(null);
|
|
9
11
|
const headerRef = useRef(null);
|
|
10
12
|
const menuContentRef = useRef(null);
|
|
13
|
+
const dropdownRef = useRef(null);
|
|
14
|
+
const mobileMenuRef = useRef(null);
|
|
15
|
+
const hoverTimeoutRef = useRef(null);
|
|
11
16
|
const toggleHeight = () => {
|
|
12
17
|
setIsExpanded(!isExpanded);
|
|
18
|
+
if (!isExpanded) {
|
|
19
|
+
setMobileMenuIndex(null);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const handleMobileMenuClick = (index, e) => {
|
|
23
|
+
e?.stopPropagation(); // 이벤트 전파 방지
|
|
24
|
+
const button = contents[index];
|
|
25
|
+
if (button?.menu) {
|
|
26
|
+
// 메뉴가 있으면 토글 (함수형 업데이트 사용)
|
|
27
|
+
setMobileMenuIndex((prevIndex) => {
|
|
28
|
+
const newIndex = prevIndex === index ? null : index;
|
|
29
|
+
return newIndex;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// 메뉴가 없으면 onClick 실행하고 닫기
|
|
34
|
+
button?.onClick?.();
|
|
35
|
+
setIsExpanded(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const closeMobileMenu = () => {
|
|
39
|
+
setIsExpanded(false);
|
|
40
|
+
setMobileMenuIndex(null);
|
|
13
41
|
};
|
|
14
42
|
const handleClickOutside = (event) => {
|
|
15
43
|
const menuElement = document.getElementById("buttonArea");
|
|
44
|
+
const mobileMenuElement = mobileMenuRef.current;
|
|
45
|
+
// 모바일 메뉴가 열려있고, 클릭이 모바일 메뉴 내부나 버튼 영역이면 닫지 않음
|
|
46
|
+
if (isExpanded && !isMD) {
|
|
47
|
+
if (mobileMenuElement?.contains(event.target) ||
|
|
48
|
+
menuElement?.contains(event.target)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
16
52
|
if (menuElement && !menuElement.contains(event.target)) {
|
|
17
53
|
setIsExpanded(false);
|
|
18
54
|
}
|
|
@@ -136,7 +172,7 @@ export default function Header({ logo, rightButton, contents, }) {
|
|
|
136
172
|
boundaries: "border-gray-light md:border-t-0 border-t-2",
|
|
137
173
|
visibility: isMD
|
|
138
174
|
? "opacity-100 visible"
|
|
139
|
-
: "
|
|
175
|
+
: "hidden md:opacity-100 md:visible",
|
|
140
176
|
};
|
|
141
177
|
const essenstialsWrapper = {
|
|
142
178
|
displays: "flex flex-row items-center h-15",
|
|
@@ -147,5 +183,62 @@ export default function Header({ logo, rightButton, contents, }) {
|
|
|
147
183
|
const menuButton = {
|
|
148
184
|
displays: "flex justify-center items-center p-2",
|
|
149
185
|
};
|
|
150
|
-
|
|
186
|
+
// 드롭다운 메뉴 애니메이션
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (!dropdownRef.current)
|
|
189
|
+
return;
|
|
190
|
+
if (hoveredIndex !== null && contents[hoveredIndex]?.menu) {
|
|
191
|
+
gsap.fromTo(dropdownRef.current, { opacity: 0, y: -10 }, { opacity: 1, y: 0, duration: 0.2, ease: "power2.out" });
|
|
192
|
+
}
|
|
193
|
+
}, [hoveredIndex, contents]);
|
|
194
|
+
const handleMouseEnter = (index) => {
|
|
195
|
+
if (contents[index]?.menu && isMD) {
|
|
196
|
+
// 기존 타이머가 있으면 취소
|
|
197
|
+
if (hoverTimeoutRef.current) {
|
|
198
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
199
|
+
hoverTimeoutRef.current = null;
|
|
200
|
+
}
|
|
201
|
+
setHoveredIndex(index);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const handleMouseLeave = () => {
|
|
205
|
+
// 약간의 지연을 주어 드롭다운으로 이동할 시간을 줌
|
|
206
|
+
hoverTimeoutRef.current = setTimeout(() => {
|
|
207
|
+
setHoveredIndex(null);
|
|
208
|
+
}, 100);
|
|
209
|
+
};
|
|
210
|
+
const handleDropdownMouseEnter = () => {
|
|
211
|
+
// 드롭다운에 마우스가 들어오면 타이머 취소
|
|
212
|
+
if (hoverTimeoutRef.current) {
|
|
213
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
214
|
+
hoverTimeoutRef.current = null;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const handleDropdownMouseLeave = () => {
|
|
218
|
+
setHoveredIndex(null);
|
|
219
|
+
};
|
|
220
|
+
// 컴포넌트 언마운트 시 타이머 정리
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
return () => {
|
|
223
|
+
if (hoverTimeoutRef.current) {
|
|
224
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}, []);
|
|
228
|
+
const activeMenu = hoveredIndex !== null ? contents[hoveredIndex]?.menu : null;
|
|
229
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: headerRef, className: cn(container), children: _jsxs("div", { className: cn(body), children: [_jsxs("div", { className: cn(leftWrapper), children: [_jsx("div", { onClick: logo.onClick, children: logo.node }), _jsxs("div", { className: cn(essenstialsWrapper), id: "buttonArea", children: [rightButton && rightButton, _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", { ref: menuContentRef, className: cn(rightWrapper), children: [_jsx("div", { className: cn(buttonBoxWrapper), children: contents.map((button, index) => (_jsx("div", { className: "relative", onMouseEnter: () => handleMouseEnter(index), onMouseLeave: handleMouseLeave, children: _jsx("button", { onClick: button.onClick, className: cn(buttonBox), children: button.title }) }, index))) }), _jsx("div", { className: "hidden md:block", children: rightButton && rightButton })] })] }) }), activeMenu && hoveredIndex !== null && isMD && (_jsx("div", { ref: dropdownRef, className: "fixed top-[60px] left-0 w-full bg-white shadow-lg z-50 border-t border-gray-200 hidden md:block", onMouseEnter: handleDropdownMouseEnter, onMouseLeave: handleDropdownMouseLeave, style: { maxWidth: "100vw" }, children: _jsx("div", { className: "max-w-[1200px] mx-auto px-6 md:px-12 py-4", children: _jsxs("div", { className: "flex flex-col md:flex-row gap-4 md:gap-4 lg:gap-6", children: [_jsx("div", { className: "flex-shrink-0 w-full md:w-64 lg:w-80", children: _jsxs("div", { className: "flex flex-col gap-1.5", children: [activeMenu.title.image && (_jsx("div", { className: "mb-0.5", children: _jsx("img", { src: activeMenu.title.image, alt: activeMenu.title.title, className: "w-full object-contain" }) })), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("h3", { className: "text-xl md:text-2xl font-bold text-blue-600", children: activeMenu.title.title }), activeMenu.title.description && (_jsx("p", { className: "text-gray-600 text-xs leading-relaxed", children: activeMenu.title.description }))] }), activeMenu.title.button && (_jsx("div", { className: cn("mt-0.5 flex", activeMenu.title.button.align === "right" ? "justify-end" : "", activeMenu.title.button.align === "center" ? "justify-center" : "", activeMenu.title.button.align === "left" ? "justify-start" : ""), children: _jsx("button", { onClick: () => activeMenu.title.button?.onClick?.(), className: cn(activeMenu.title.button.style ||
|
|
230
|
+
"px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-xs font-medium"), children: activeMenu.title.button.title }) }))] }) }), _jsx("div", { className: "flex-1", children: _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-4 md:gap-x-6 gap-y-3 h-full pt-0 md:pt-4 pl-0 md:pl-4 lg:pl-8", children: activeMenu.content.map((item, itemIndex) => (_jsxs("div", { className: "cursor-pointer group", onClick: () => item.onClick?.(), children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx("h4", { className: "text-base md:text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors", children: item.title }), item.tag && (_jsx("span", { className: cn("px-2 py-0.5 text-xs font-medium text-white rounded-full whitespace-nowrap", item.tag.background || "bg-blue-600"), children: item.tag.title }))] }), item.description && (_jsx("p", { className: "text-xs text-gray-600 leading-relaxed", children: item.description }))] }, itemIndex))) }) })] }) }) })), isExpanded && !isMD && (_jsxs("div", { ref: mobileMenuRef, className: "fixed inset-0 bg-white z-50 overflow-hidden flex flex-col", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "bg-white px-4 py-3 flex items-center justify-between", children: [_jsx("div", { className: "flex items-center gap-4", children: _jsx("img", { src: "/images/logos/tosel.png", alt: "Tosel", className: "h-7" }) }), _jsx("button", { onClick: closeMobileMenu, className: "text-black w-6 h-6 flex items-center justify-center", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-6 h-6", children: _jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }) })] }), _jsx("div", { className: "flex-1 overflow-y-scroll", children: _jsx("div", { className: "border-b border-gray-200", children: contents.map((button, index) => (_jsxs("div", { children: [_jsxs("button", { onClick: (e) => handleMobileMenuClick(index, e), className: "w-full px-4 py-4 flex items-center justify-between border-b border-gray-100 hover:bg-gray-50 transition-colors", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "w-2 h-2 rounded-full bg-green-500" }), _jsx("span", { className: "text-gray-900 font-medium", children: button.title })] }), button.menu && (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "w-5 h-5 text-gray-400", children: _jsx("path", { d: "M12 4v16m8-8H4" }) }))] }), button.menu && mobileMenuIndex === index && (_jsxs("div", { className: "bg-gray-50 border-b border-gray-200 animate-in slide-in-from-top-2 duration-200", children: [button.menu.title && (_jsxs("div", { className: "px-4 py-4 border-b border-gray-200", children: [button.menu.title.image && (_jsx("div", { className: "mb-3", children: _jsx("img", { src: button.menu.title.image, alt: button.menu.title.title, className: "w-full max-w-xs h-auto object-contain" }) })), _jsx("h3", { className: "text-xl font-bold text-blue-600 mb-2", children: button.menu.title.title }), button.menu.title.button && (_jsx("div", { className: cn("flex", button.menu.title.button.align === "right"
|
|
231
|
+
? "justify-end"
|
|
232
|
+
: "", button.menu.title.button.align === "center"
|
|
233
|
+
? "justify-center"
|
|
234
|
+
: "", button.menu.title.button.align === "left"
|
|
235
|
+
? "justify-start"
|
|
236
|
+
: ""), children: _jsx("button", { onClick: () => {
|
|
237
|
+
button.menu?.title.button?.onClick?.();
|
|
238
|
+
closeMobileMenu();
|
|
239
|
+
}, className: cn(button.menu.title.button.style ||
|
|
240
|
+
"px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"), children: button.menu.title.button.title }) }))] })), button.menu.content && button.menu.content.length > 0 && (_jsx("div", { className: "px-4 py-2", children: button.menu.content.map((item, itemIndex) => (_jsxs("button", { onClick: () => {
|
|
241
|
+
item.onClick?.();
|
|
242
|
+
closeMobileMenu();
|
|
243
|
+
}, className: "w-full px-4 py-3 flex items-center justify-between border-b border-gray-100 hover:bg-gray-100 transition-colors text-left", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-gray-900 font-medium", children: item.title }), item.tag && (_jsx("span", { className: cn("px-2 py-0.5 text-xs font-medium text-white rounded-full", item.tag.background || "bg-green-500"), children: item.tag.title }))] }), _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "w-5 h-5 text-gray-400", children: _jsx("path", { d: "M12 4v16m8-8H4" }) })] }, itemIndex))) }))] }))] }, index))) }) })] }))] }));
|
|
151
244
|
}
|
package/package.json
CHANGED
package/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.351
|