jean-react-utils 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +60 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -18
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 jean-w
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
4
5
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
@@ -11,10 +12,10 @@ interface HeadingProps {
|
|
|
11
12
|
className?: string;
|
|
12
13
|
title?: string;
|
|
13
14
|
}
|
|
14
|
-
declare function PageTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
15
|
-
declare function SectionTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
16
|
-
declare function CardTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
17
|
-
declare function SubTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
15
|
+
declare function PageTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
declare function SectionTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
17
|
+
declare function CardTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
declare function SubTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
18
19
|
|
|
19
20
|
interface NavItem {
|
|
20
21
|
key: string;
|
|
@@ -69,4 +70,4 @@ declare const Footer: React.FC<FooterProps>;
|
|
|
69
70
|
|
|
70
71
|
declare const useForceRerender: () => [number, () => void];
|
|
71
72
|
|
|
72
|
-
export { Button, CardTitle, Footer,
|
|
73
|
+
export { Button, CardTitle, Footer, Header, PageTitle, SectionTitle, SubTitle, useForceRerender };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
4
5
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
@@ -11,10 +12,10 @@ interface HeadingProps {
|
|
|
11
12
|
className?: string;
|
|
12
13
|
title?: string;
|
|
13
14
|
}
|
|
14
|
-
declare function PageTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
15
|
-
declare function SectionTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
16
|
-
declare function CardTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
17
|
-
declare function SubTitle({ children, title, className }: HeadingProps): JSX.Element;
|
|
15
|
+
declare function PageTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
declare function SectionTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
17
|
+
declare function CardTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
declare function SubTitle({ children, title, className }: HeadingProps): react_jsx_runtime.JSX.Element;
|
|
18
19
|
|
|
19
20
|
interface NavItem {
|
|
20
21
|
key: string;
|
|
@@ -69,4 +70,4 @@ declare const Footer: React.FC<FooterProps>;
|
|
|
69
70
|
|
|
70
71
|
declare const useForceRerender: () => [number, () => void];
|
|
71
72
|
|
|
72
|
-
export { Button, CardTitle, Footer,
|
|
73
|
+
export { Button, CardTitle, Footer, Header, PageTitle, SectionTitle, SubTitle, useForceRerender };
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
Button: () => Button_default,
|
|
34
34
|
CardTitle: () => CardTitle,
|
|
35
|
-
Footer: () =>
|
|
35
|
+
Footer: () => Footer_default,
|
|
36
36
|
Header: () => Header_default,
|
|
37
37
|
PageTitle: () => PageTitle,
|
|
38
38
|
SectionTitle: () => SectionTitle,
|
|
@@ -120,16 +120,19 @@ function SubTitle({ children, title, className = "" }) {
|
|
|
120
120
|
|
|
121
121
|
// src/layout/Header/index.tsx
|
|
122
122
|
var import_react = require("react");
|
|
123
|
+
var import_classnames = __toESM(require("classnames"));
|
|
123
124
|
var import_link = __toESM(require("next/link"));
|
|
124
125
|
var import_framer_motion2 = require("framer-motion");
|
|
125
126
|
|
|
126
127
|
// src/layout/Header/BreadIcon.tsx
|
|
127
128
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
129
|
+
var spanDefaultCls = "w-full h-0.5 bg-black transition-transform duration-300";
|
|
130
|
+
var spanOpacityCls = "w-full h-0.5 bg-black transition-opacity duration-300";
|
|
128
131
|
function BreadIcon({ isMenuOpen }) {
|
|
129
132
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "w-6 h-5 relative flex flex-col justify-between", children: [
|
|
130
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className:
|
|
131
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className:
|
|
132
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className:
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `${spanDefaultCls} ${isMenuOpen ? "rotate-45 translate-y-2" : ""}` }),
|
|
134
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `${spanOpacityCls} ${isMenuOpen ? "opacity-0" : ""}` }),
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `${spanDefaultCls} ${isMenuOpen ? "-rotate-45 -translate-y-2" : ""}` })
|
|
133
136
|
] });
|
|
134
137
|
}
|
|
135
138
|
|
|
@@ -140,7 +143,7 @@ var getActivedCls = (href, selectedKey) => {
|
|
|
140
143
|
if (href.includes(selectedKey) || href === "/" && selectedKey === "home") {
|
|
141
144
|
return "text-purple-600";
|
|
142
145
|
}
|
|
143
|
-
return "
|
|
146
|
+
return "";
|
|
144
147
|
};
|
|
145
148
|
var Header = ({
|
|
146
149
|
logo,
|
|
@@ -150,8 +153,7 @@ var Header = ({
|
|
|
150
153
|
}) => {
|
|
151
154
|
const [isMenuOpen, setIsMenuOpen] = (0, import_react.useState)(false);
|
|
152
155
|
const selectedKey = (0, import_navigation.usePathname)().split("/")[2] || "home";
|
|
153
|
-
|
|
154
|
-
const baseNavClassName = `fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 ${className}`;
|
|
156
|
+
const baseNavClassName = (0, import_classnames.default)(`fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 text-gray-600`, className);
|
|
155
157
|
const navVariants = {
|
|
156
158
|
hidden: { y: -20, opacity: 0 },
|
|
157
159
|
visible: {
|
|
@@ -163,6 +165,32 @@ var Header = ({
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
};
|
|
168
|
+
const HeaderWrapper = enableAnimation ? import_framer_motion2.motion.header : "header";
|
|
169
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
170
|
+
HeaderWrapper,
|
|
171
|
+
{
|
|
172
|
+
className: baseNavClassName,
|
|
173
|
+
...enableAnimation && {
|
|
174
|
+
initial: "hidden",
|
|
175
|
+
animate: "visible",
|
|
176
|
+
variants: navVariants
|
|
177
|
+
},
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "container flex items-center justify-between p-4", children: [
|
|
180
|
+
logo,
|
|
181
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DesktopNav, { menuItems, selectedKey, enableAnimation }),
|
|
182
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MobileMenuBtn, { menuItems, selectedKey, setIsMenuOpen, isMenuOpen })
|
|
183
|
+
] }),
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_framer_motion2.AnimatePresence, { children: isMenuOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MobileNav, { menuItems, selectedKey, enableAnimation }) })
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
var DesktopNav = ({ menuItems, selectedKey, enableAnimation }) => {
|
|
190
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("nav", { className: "hidden md:flex items-center space-x-8", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavLink, { href: item.href, selectedKey, className: item.className?.pc, children: item.label }, item.key)) });
|
|
191
|
+
};
|
|
192
|
+
var MobileNav = ({ menuItems, selectedKey, enableAnimation }) => {
|
|
193
|
+
const MobileMenuComponent = enableAnimation ? import_framer_motion2.motion.div : "div";
|
|
166
194
|
const mobileMenuVariants = {
|
|
167
195
|
hidden: {
|
|
168
196
|
height: 0,
|
|
@@ -181,74 +209,43 @@ var Header = ({
|
|
|
181
209
|
}
|
|
182
210
|
}
|
|
183
211
|
};
|
|
184
|
-
const NavComponent = enableAnimation ? import_framer_motion2.motion.nav : "nav";
|
|
185
|
-
const MobileMenuComponent = enableAnimation ? import_framer_motion2.motion.div : "div";
|
|
186
|
-
console.log("menuItems>>>", menuItems);
|
|
187
212
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
188
|
-
|
|
213
|
+
MobileMenuComponent,
|
|
189
214
|
{
|
|
190
|
-
className:
|
|
215
|
+
className: "md:hidden overflow-hidden",
|
|
191
216
|
...enableAnimation && {
|
|
192
217
|
initial: "hidden",
|
|
193
218
|
animate: "visible",
|
|
194
|
-
|
|
219
|
+
exit: "hidden",
|
|
220
|
+
variants: mobileMenuVariants
|
|
195
221
|
},
|
|
196
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{
|
|
212
|
-
onClick: () => setIsMenuOpen(!isMenuOpen),
|
|
213
|
-
className: "md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors",
|
|
214
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BreadIcon, { isMenuOpen })
|
|
215
|
-
}
|
|
216
|
-
)
|
|
217
|
-
] }),
|
|
218
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_framer_motion2.AnimatePresence, { children: isMenuOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
219
|
-
MobileMenuComponent,
|
|
220
|
-
{
|
|
221
|
-
className: "md:hidden overflow-hidden",
|
|
222
|
-
...enableAnimation && {
|
|
223
|
-
initial: "hidden",
|
|
224
|
-
animate: "visible",
|
|
225
|
-
exit: "hidden",
|
|
226
|
-
variants: mobileMenuVariants
|
|
227
|
-
},
|
|
228
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex flex-col space-y-4 py-4 items-center", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
229
|
-
import_framer_motion2.motion.div,
|
|
230
|
-
{
|
|
231
|
-
...enableAnimation && {
|
|
232
|
-
initial: { x: -20, opacity: 0 },
|
|
233
|
-
animate: { x: 0, opacity: 1 },
|
|
234
|
-
transition: { delay: 0.1 }
|
|
235
|
-
},
|
|
236
|
-
children: menuItems.map((item2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MobileNavLink, { href: item2.href, className: item2.className?.mobile, selectedKey, children: item2.label }, item2.key))
|
|
237
|
-
},
|
|
238
|
-
item.key
|
|
239
|
-
)) })
|
|
240
|
-
}
|
|
241
|
-
) })
|
|
242
|
-
] })
|
|
222
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("nav", { className: "flex flex-col space-y-4 py-4 items-center", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MobileMenuComponent, { ...enableAnimation && {
|
|
223
|
+
initial: { x: -20, opacity: 0 },
|
|
224
|
+
animate: { x: 0, opacity: 1 },
|
|
225
|
+
transition: { delay: 0.1 }
|
|
226
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MobileNavLink, { href: item.href, selectedKey, className: item.className?.mobile, children: item.label }) }, item.key)) })
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
var MobileMenuBtn = ({ setIsMenuOpen, isMenuOpen }) => {
|
|
231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
232
|
+
"button",
|
|
233
|
+
{
|
|
234
|
+
onClick: () => setIsMenuOpen(!isMenuOpen),
|
|
235
|
+
className: "md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors",
|
|
236
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BreadIcon, { isMenuOpen })
|
|
243
237
|
}
|
|
244
238
|
);
|
|
245
239
|
};
|
|
246
240
|
var NavLink = ({ href, children, className, selectedKey }) => {
|
|
247
|
-
const combinedClassName =
|
|
241
|
+
const combinedClassName = (0, import_classnames.default)(
|
|
242
|
+
className || "hover:text-black transition-colors",
|
|
243
|
+
getActivedCls(href, selectedKey)
|
|
244
|
+
);
|
|
248
245
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_link.default, { href, legacyBehavior: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { className: combinedClassName, children }) });
|
|
249
246
|
};
|
|
250
247
|
var MobileNavLink = ({ href, children, className, selectedKey }) => {
|
|
251
|
-
const combinedClassName = `${className || "text-lg font-medium text-center
|
|
248
|
+
const combinedClassName = `${className || "text-lg font-medium text-center hover:text-purple-600 transition-colors"} ${getActivedCls(href, selectedKey)}`;
|
|
252
249
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_link.default, { href, legacyBehavior: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { className: combinedClassName, children }) });
|
|
253
250
|
};
|
|
254
251
|
var Header_default = Header;
|
|
@@ -308,6 +305,7 @@ var Footer = ({
|
|
|
308
305
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600" })
|
|
309
306
|
] });
|
|
310
307
|
};
|
|
308
|
+
var Footer_default = Footer;
|
|
311
309
|
|
|
312
310
|
// src/hooks/useForceRerender.ts
|
|
313
311
|
var import_react2 = require("react");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/Button/index.tsx","../src/components/Heading/index.tsx","../src/layout/Header/index.tsx","../src/layout/Header/BreadIcon.tsx","../src/layout/Footer/index.tsx","../src/hooks/useForceRerender.ts"],"sourcesContent":["export * from './components'\nexport * from './layout'\nexport { default as useForceRerender } from './hooks/useForceRerender'\n// export * from './hooks'\n// export * from './layout'\n// export * from './theme'\n// export * from './i18n'","import React from 'react';\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'primary',\n size = 'md',\n className = '',\n children,\n ...props\n}) => {\n const baseStyles = 'rounded-full font-medium transition-colors';\n \n const variants = {\n primary: 'bg-purple-600 text-white hover:bg-purple-700 shadow-lg shadow-purple-500/20',\n secondary: 'bg-purple-50 text-purple-700 hover:bg-purple-100',\n outline: 'border-2 border-purple-600 text-purple-600 hover:bg-purple-50'\n };\n\n const sizes = {\n sm: 'px-4 py-1.5 text-sm',\n md: 'px-6 py-2 text-sm',\n lg: 'px-8 py-3 text-base'\n };\n\n const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;\n\n return (\n <button className={classes} {...props}>\n {children}\n </button>\n );\n}; \n\nexport default Button;","'use client';\n\nimport { motion } from 'framer-motion';\nimport { ReactNode } from 'react';\n\nexport interface HeadingProps {\n children?: ReactNode;\n className?: string;\n title?: string;\n}\n\n// 頁面主標題:大標題,帶漸變色\nexport function PageTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h1\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8 }}\n className={`text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h1>\n );\n}\n\n// 區塊標題:中等大小,帶漸變色\nexport function SectionTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h2\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8, delay: 0.2 }}\n className={`text-3xl font-bold text-center mb-16 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h2>\n );\n}\n\n// 卡片標題:較小,深灰色\nexport function CardTitle({ children, title,className = '' }: HeadingProps) {\n return (\n <motion.h3\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-2xl font-bold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h3>\n );\n}\n\n// 區塊子標題:最小,帶圖標位置\nexport function SubTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h4\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-lg font-semibold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h4>\n );\n} \n\nexport default {\n PageTitle,\n SectionTitle,\n CardTitle,\n SubTitle,\n}","'use client';\nimport React, { useState } from 'react';\nimport Link from 'next/link';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport BreadIcon from './BreadIcon';\nimport { usePathname } from 'next/navigation';\nexport interface NavItem {\n key: string;\n href: string;\n label: string;\n className?: {\n pc?: string;\n mobile?: string;\n };\n}\n\nexport interface HeaderProps {\n logo: React.ReactNode;\n menuItems: NavItem[];\n className?: string;\n /** 是否啟用動畫效果 */\n enableAnimation?: boolean;\n}\nconst getActivedCls = (href: string, selectedKey: string) => {\n if(href.includes(selectedKey) || (href === '/' && selectedKey === 'home')) {\n return 'text-purple-600';\n }\n return 'text-gray-600';\n}\nexport const Header: React.FC<HeaderProps> = ({\n logo,\n menuItems,\n className = '',\n enableAnimation = true,\n}) => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const selectedKey = usePathname().split('/')[2] || 'home'; // todo support home\n console.log('selectedKey>>>',selectedKey);\n // 基礎樣式\n const baseNavClassName = `fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 ${className}`;\n // 動畫配置\n const navVariants = {\n hidden: { y: -20, opacity: 0 },\n visible: { \n y: 0, \n opacity: 1,\n transition: {\n duration: 0.5,\n ease: \"easeOut\"\n }\n }\n };\n\n const mobileMenuVariants = {\n hidden: { \n height: 0,\n opacity: 0,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n },\n visible: { \n height: \"auto\",\n opacity: 1,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n }\n };\n\n // 根據是否啟用動畫返回不同的組件\n const NavComponent = enableAnimation ? motion.nav : 'nav';\n const MobileMenuComponent = enableAnimation ? motion.div : 'div';\nconsole.log('menuItems>>>',menuItems);\n return (\n <NavComponent \n className={baseNavClassName}\n {...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n variants: navVariants\n })}\n >\n <div className=\"container mx-auto px-4\">\n <div className=\"flex items-center justify-between h-20\">\n {/* Logo */}\n {logo}\n\n {/* Desktop Navigation */}\n <nav className=\"hidden md:flex items-center space-x-8\">\n {menuItems.map((item) => (\n // <Link\n // key={item.key}\n // href=\"/\"\n // className=\"flex items-center justify-center gap-2 text-lg font-bold tracking-wide transition-all duration-300 ease-in-out\"\n // >\n // {item.label}\n // </Link>\n <NavLink key={item.key} href={item.href} className={item.className?.pc} selectedKey={selectedKey}>\n {item.label}\n </NavLink>\n ))}\n </nav>\n\n {/* Mobile Menu Button */}\n <button \n onClick={() => setIsMenuOpen(!isMenuOpen)}\n className=\"md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors\"\n >\n <BreadIcon isMenuOpen={isMenuOpen}/>\n </button>\n </div>\n\n {/* Mobile Menu */}\n <AnimatePresence>\n {isMenuOpen && (\n <MobileMenuComponent\n className=\"md:hidden overflow-hidden\"\n {...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n exit: \"hidden\",\n variants: mobileMenuVariants\n })}\n >\n <div className=\"flex flex-col space-y-4 py-4 items-center\">\n {menuItems.map((item) => (\n <motion.div\n key={item.key}\n {...(enableAnimation && {\n initial: { x: -20, opacity: 0 },\n animate: { x: 0, opacity: 1 },\n transition: { delay: 0.1 }\n })}\n >\n {\n menuItems.map((item) => (\n <MobileNavLink key={item.key} href={item.href} className={item.className?.mobile} selectedKey={selectedKey}>{item.label}</MobileNavLink>\n ))\n }\n </motion.div>\n ))}\n </div>\n </MobileMenuComponent>\n )}\n </AnimatePresence>\n </div>\n </NavComponent>\n );\n}; \n\n\nconst NavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = `${className || 'text-gray-600 hover:text-black transition-colors'} ${getActivedCls(href, selectedKey)}`;\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nconst MobileNavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = `${className || 'text-lg font-medium text-center text-gray-600 hover:text-purple-600 transition-colors'} ${getActivedCls(href, selectedKey)}`;\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nexport default Header;","export default function BreadIcon({isMenuOpen}: {isMenuOpen: boolean}) {\n return (\n <div className=\"w-6 h-5 relative flex flex-col justify-between\">\n <span className={`w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? 'rotate-45 translate-y-2' : ''}`} />\n <span className={`w-full h-0.5 bg-gray-800 transition-opacity duration-300 ${isMenuOpen ? 'opacity-0' : ''}`} />\n <span className={`w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? '-rotate-45 -translate-y-2' : ''}`} />\n </div>\n )\n}","'use client';\nimport React from 'react';\nimport Link from 'next/link';\n\nexport interface FooterLink {\n href: string;\n title: string;\n}\n\nexport interface FooterTexts {\n copyright: string;\n privacy: string;\n terms: string;\n social: string;\n}\n\nexport interface FooterProps {\n /** 網站基本信息 */\n site: {\n name: string;\n description: string;\n };\n /** 導航鏈接 */\n navigation: {\n /** 快速鏈接區塊 */\n quickLinks: {\n title: string;\n items: FooterLink[];\n };\n /** 社交媒體鏈接 */\n socialLinks: FooterLink[];\n };\n /** 頁腳文本 */\n texts: FooterTexts;\n /** 自定義類名 */\n className?: string;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n site,\n navigation,\n texts,\n className = '',\n}) => {\n return (\n <footer className={`relative bg-gradient-to-b from-gray-50 to-gray-100 pt-16 pb-6 ${className}`}>\n <div className=\"absolute inset-0 bg-grid-gray-200/25 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.6))]\" />\n \n <div className=\"container mx-auto p-4 relative\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8 mb-12\">\n {/* Brand Section */}\n <div className=\"space-y-4\">\n <h3 className=\"text-xl font-bold bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent\">\n {site.name}\n </h3>\n <p className=\"text-gray-600 max-w-xs\">\n {site.description}\n </p>\n </div>\n\n {/* Quick Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {navigation.quickLinks.title}\n </h4>\n <nav className=\"flex flex-col space-y-2\">\n {navigation.quickLinks.items.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n >\n {item.title}\n </Link>\n ))}\n </nav>\n </div>\n\n {/* Social Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {texts.social}\n </h4>\n <div className=\"flex space-x-4\">\n {navigation.socialLinks.map((link) => (\n <a\n key={link.href}\n href={link.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {link.title}\n </a>\n ))}\n </div>\n </div>\n </div>\n\n {/* Bottom Bar */}\n <div className=\"border-t border-gray-200 pt-6\">\n <div className=\"flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0\">\n <p className=\"text-sm text-gray-600\">\n {texts.copyright}\n </p>\n <div className=\"flex space-x-6\">\n <Link href=\"/privacy\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.privacy}\n </Link>\n <Link href=\"/terms\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.terms}\n </Link>\n </div>\n </div>\n </div>\n </div>\n\n {/* Decorative gradient line */}\n <div className=\"absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600\" />\n </footer>\n );\n}; ","import { useState, useCallback } from 'react';\n\nconst useForceRerender = (): [number, () => void] => {\n const [key, setKey] = useState(0);\n\n const forceRerender = useCallback(() => {\n setKey(prevKey => prevKey + 1);\n }, []);\n\n return [key, forceRerender];\n};\n\nexport default useForceRerender;"],"mappings":"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","names":["import_jsx_runtime","import_framer_motion","import_jsx_runtime","import_jsx_runtime","item","Link","import_link","import_jsx_runtime","Link","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/Button/index.tsx","../src/components/Heading/index.tsx","../src/layout/Header/index.tsx","../src/layout/Header/BreadIcon.tsx","../src/layout/Footer/index.tsx","../src/hooks/useForceRerender.ts"],"sourcesContent":["export * from './components'\nexport * from './layout'\nexport { default as useForceRerender } from './hooks/useForceRerender'\n// export * from './hooks'\n// export * from './layout'\n// export * from './theme'\n// export * from './i18n'","import React from 'react';\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'primary',\n size = 'md',\n className = '',\n children,\n ...props\n}) => {\n const baseStyles = 'rounded-full font-medium transition-colors';\n \n const variants = {\n primary: 'bg-purple-600 text-white hover:bg-purple-700 shadow-lg shadow-purple-500/20',\n secondary: 'bg-purple-50 text-purple-700 hover:bg-purple-100',\n outline: 'border-2 border-purple-600 text-purple-600 hover:bg-purple-50'\n };\n\n const sizes = {\n sm: 'px-4 py-1.5 text-sm',\n md: 'px-6 py-2 text-sm',\n lg: 'px-8 py-3 text-base'\n };\n\n const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;\n\n return (\n <button className={classes} {...props}>\n {children}\n </button>\n );\n}; \n\nexport default Button;","'use client';\n\nimport { motion } from 'framer-motion';\nimport { ReactNode } from 'react';\n\nexport interface HeadingProps {\n children?: ReactNode;\n className?: string;\n title?: string;\n}\n\n// 頁面主標題:大標題,帶漸變色\nexport function PageTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h1\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8 }}\n className={`text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h1>\n );\n}\n\n// 區塊標題:中等大小,帶漸變色\nexport function SectionTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h2\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8, delay: 0.2 }}\n className={`text-3xl font-bold text-center mb-16 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h2>\n );\n}\n\n// 卡片標題:較小,深灰色\nexport function CardTitle({ children, title,className = '' }: HeadingProps) {\n return (\n <motion.h3\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-2xl font-bold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h3>\n );\n}\n\n// 區塊子標題:最小,帶圖標位置\nexport function SubTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h4\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-lg font-semibold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h4>\n );\n} \n\nexport default {\n PageTitle,\n SectionTitle,\n CardTitle,\n SubTitle,\n}","'use client';\nimport React, { useState } from 'react';\nimport classNames from 'classnames';\nimport Link from 'next/link';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport BreadIcon from './BreadIcon';\nimport { usePathname } from 'next/navigation';\nexport interface NavItem {\n key: string;\n href: string;\n label: string;\n className?: {\n pc?: string;\n mobile?: string;\n };\n}\n\nexport interface HeaderProps {\n logo: React.ReactNode;\n menuItems: NavItem[];\n className?: string;\n /** 是否啟用動畫效果 */\n enableAnimation?: boolean;\n}\nconst getActivedCls = (href: string, selectedKey: string) => {\n if(href.includes(selectedKey) || (href === '/' && selectedKey === 'home')) {\n return 'text-purple-600';\n }\n return '';\n}\nexport const Header: React.FC<HeaderProps> = ({\n logo,\n menuItems,\n className = '',\n enableAnimation = true,\n}) => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const selectedKey = usePathname().split('/')[2] || 'home';\n // 如何解决className 重复的问题,传入值与默认值有重复\n const baseNavClassName = classNames(`fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 text-gray-600`, className);\n\n // 動畫配置\n const navVariants = {\n hidden: { y: -20, opacity: 0 },\n visible: { \n y: 0, \n opacity: 1,\n transition: {\n duration: 0.5,\n ease: \"easeOut\"\n }\n }\n };\n\n // 根據是否啟用動畫返回不同的組件\n const HeaderWrapper = enableAnimation ? motion.header : 'header';\n return (\n <HeaderWrapper \n className={baseNavClassName}\n {...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n variants: navVariants\n })}\n >\n <div className='container flex items-center justify-between p-4'>\n {logo}\n {/* Desktop Navigation */}\n <DesktopNav menuItems={menuItems} selectedKey={selectedKey} enableAnimation={enableAnimation} />\n <MobileMenuBtn menuItems={menuItems} selectedKey={selectedKey} setIsMenuOpen={setIsMenuOpen} isMenuOpen={isMenuOpen} />\n </div>\n {/* Mobile Menu */}\n <AnimatePresence>\n {isMenuOpen && (\n <MobileNav menuItems={menuItems} selectedKey={selectedKey} enableAnimation={enableAnimation} />\n )}\n </AnimatePresence>\n </HeaderWrapper>\n );\n}; \nconst DesktopNav = ({ menuItems, selectedKey, enableAnimation }: { menuItems: NavItem[], selectedKey: string, enableAnimation: boolean }) => {\n return (\n <nav className='hidden md:flex items-center space-x-8'>\n {menuItems.map((item) => (\n <NavLink key={item.key} href={item.href} selectedKey={selectedKey} className={item.className?.pc}>{item.label}</NavLink>\n ))}\n </nav>\n )\n}\n\nconst MobileNav = ({ menuItems, selectedKey, enableAnimation }: { menuItems: NavItem[], selectedKey: string, enableAnimation: boolean }) => {\n const MobileMenuComponent = enableAnimation ? motion.div : 'div';\n const mobileMenuVariants = {\n hidden: { \n height: 0,\n opacity: 0,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n },\n visible: { \n height: \"auto\",\n opacity: 1,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n }\n };\n return (\n <MobileMenuComponent className='md:hidden overflow-hidden'\n {\n ...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n exit: \"hidden\",\n variants: mobileMenuVariants\n })\n }>\n <nav className='flex flex-col space-y-4 py-4 items-center'>\n {menuItems.map((item) => (\n <MobileMenuComponent key={item.key} {...(enableAnimation && {\n initial: { x: -20, opacity: 0 },\n animate: { x: 0, opacity: 1 },\n transition: { delay: 0.1 }\n })}>\n <MobileNavLink href={item.href} selectedKey={selectedKey} className={item.className?.mobile}>{item.label}</MobileNavLink>\n </MobileMenuComponent>\n ))}\n </nav>\n </MobileMenuComponent>\n )\n}\n\nconst MobileMenuBtn = ({ setIsMenuOpen, isMenuOpen }: { menuItems: NavItem[], selectedKey: string, setIsMenuOpen: (isMenuOpen: boolean) => void, isMenuOpen: boolean }) => {\n return (\n <button \n onClick={() => setIsMenuOpen(!isMenuOpen)}\n className=\"md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors\"\n >\n <BreadIcon isMenuOpen={isMenuOpen}/>\n </button>\n )\n}\n\nconst NavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = classNames(\n className || 'hover:text-black transition-colors',\n getActivedCls(href, selectedKey)\n );\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nconst MobileNavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = `${className || 'text-lg font-medium text-center hover:text-purple-600 transition-colors'} ${getActivedCls(href, selectedKey)}`;\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nexport default Header;","const spanDefaultCls= 'w-full h-0.5 bg-black transition-transform duration-300'\nconst spanOpacityCls= 'w-full h-0.5 bg-black transition-opacity duration-300'\n\nexport default function BreadIcon({isMenuOpen}: {isMenuOpen: boolean}) {\n return (\n <div className=\"w-6 h-5 relative flex flex-col justify-between\">\n <span className={`${spanDefaultCls} ${isMenuOpen ? 'rotate-45 translate-y-2' : ''}`} />\n <span className={`${spanOpacityCls} ${isMenuOpen ? 'opacity-0' : ''}`} />\n <span className={`${spanDefaultCls} ${isMenuOpen ? '-rotate-45 -translate-y-2' : ''}`} />\n </div>\n )\n}","'use client';\nimport React from 'react';\nimport Link from 'next/link';\n\nexport interface FooterLink {\n href: string;\n title: string;\n}\n\nexport interface FooterTexts {\n copyright: string;\n privacy: string;\n terms: string;\n social: string;\n}\n\nexport interface FooterProps {\n /** 網站基本信息 */\n site: {\n name: string;\n description: string;\n };\n /** 導航鏈接 */\n navigation: {\n /** 快速鏈接區塊 */\n quickLinks: {\n title: string;\n items: FooterLink[];\n };\n /** 社交媒體鏈接 */\n socialLinks: FooterLink[];\n };\n /** 頁腳文本 */\n texts: FooterTexts;\n /** 自定義類名 */\n className?: string;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n site,\n navigation,\n texts,\n className = '',\n}) => {\n return (\n <footer className={`relative bg-gradient-to-b from-gray-50 to-gray-100 pt-16 pb-6 ${className}`}>\n <div className=\"absolute inset-0 bg-grid-gray-200/25 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.6))]\" />\n \n <div className=\"container mx-auto p-4 relative\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8 mb-12\">\n {/* Brand Section */}\n <div className=\"space-y-4\">\n <h3 className=\"text-xl font-bold bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent\">\n {site.name}\n </h3>\n <p className=\"text-gray-600 max-w-xs\">\n {site.description}\n </p>\n </div>\n\n {/* Quick Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {navigation.quickLinks.title}\n </h4>\n <nav className=\"flex flex-col space-y-2\">\n {navigation.quickLinks.items.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n >\n {item.title}\n </Link>\n ))}\n </nav>\n </div>\n\n {/* Social Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {texts.social}\n </h4>\n <div className=\"flex space-x-4\">\n {navigation.socialLinks.map((link) => (\n <a\n key={link.href}\n href={link.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {link.title}\n </a>\n ))}\n </div>\n </div>\n </div>\n\n {/* Bottom Bar */}\n <div className=\"border-t border-gray-200 pt-6\">\n <div className=\"flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0\">\n <p className=\"text-sm text-gray-600\">\n {texts.copyright}\n </p>\n <div className=\"flex space-x-6\">\n <Link href=\"/privacy\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.privacy}\n </Link>\n <Link href=\"/terms\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.terms}\n </Link>\n </div>\n </div>\n </div>\n </div>\n\n {/* Decorative gradient line */}\n <div className=\"absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600\" />\n </footer>\n );\n}; \n\nexport default Footer;","import { useState, useCallback } from 'react';\n\nconst useForceRerender = (): [number, () => void] => {\n const [key, setKey] = useState(0);\n\n const forceRerender = useCallback(() => {\n setKey(prevKey => prevKey + 1);\n }, []);\n\n return [key, forceRerender];\n};\n\nexport default useForceRerender;"],"mappings":"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","names":["import_jsx_runtime","import_framer_motion","import_jsx_runtime","import_jsx_runtime","classNames","Link","import_link","import_jsx_runtime","Link","import_react"]}
|
package/dist/index.mjs
CHANGED
|
@@ -77,16 +77,19 @@ function SubTitle({ children, title, className = "" }) {
|
|
|
77
77
|
|
|
78
78
|
// src/layout/Header/index.tsx
|
|
79
79
|
import { useState } from "react";
|
|
80
|
+
import classNames from "classnames";
|
|
80
81
|
import Link from "next/link";
|
|
81
82
|
import { motion as motion2, AnimatePresence } from "framer-motion";
|
|
82
83
|
|
|
83
84
|
// src/layout/Header/BreadIcon.tsx
|
|
84
85
|
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
86
|
+
var spanDefaultCls = "w-full h-0.5 bg-black transition-transform duration-300";
|
|
87
|
+
var spanOpacityCls = "w-full h-0.5 bg-black transition-opacity duration-300";
|
|
85
88
|
function BreadIcon({ isMenuOpen }) {
|
|
86
89
|
return /* @__PURE__ */ jsxs("div", { className: "w-6 h-5 relative flex flex-col justify-between", children: [
|
|
87
|
-
/* @__PURE__ */ jsx3("span", { className:
|
|
88
|
-
/* @__PURE__ */ jsx3("span", { className:
|
|
89
|
-
/* @__PURE__ */ jsx3("span", { className:
|
|
90
|
+
/* @__PURE__ */ jsx3("span", { className: `${spanDefaultCls} ${isMenuOpen ? "rotate-45 translate-y-2" : ""}` }),
|
|
91
|
+
/* @__PURE__ */ jsx3("span", { className: `${spanOpacityCls} ${isMenuOpen ? "opacity-0" : ""}` }),
|
|
92
|
+
/* @__PURE__ */ jsx3("span", { className: `${spanDefaultCls} ${isMenuOpen ? "-rotate-45 -translate-y-2" : ""}` })
|
|
90
93
|
] });
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -97,7 +100,7 @@ var getActivedCls = (href, selectedKey) => {
|
|
|
97
100
|
if (href.includes(selectedKey) || href === "/" && selectedKey === "home") {
|
|
98
101
|
return "text-purple-600";
|
|
99
102
|
}
|
|
100
|
-
return "
|
|
103
|
+
return "";
|
|
101
104
|
};
|
|
102
105
|
var Header = ({
|
|
103
106
|
logo,
|
|
@@ -107,8 +110,7 @@ var Header = ({
|
|
|
107
110
|
}) => {
|
|
108
111
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
109
112
|
const selectedKey = usePathname().split("/")[2] || "home";
|
|
110
|
-
|
|
111
|
-
const baseNavClassName = `fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 ${className}`;
|
|
113
|
+
const baseNavClassName = classNames(`fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 text-gray-600`, className);
|
|
112
114
|
const navVariants = {
|
|
113
115
|
hidden: { y: -20, opacity: 0 },
|
|
114
116
|
visible: {
|
|
@@ -120,6 +122,32 @@ var Header = ({
|
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
};
|
|
125
|
+
const HeaderWrapper = enableAnimation ? motion2.header : "header";
|
|
126
|
+
return /* @__PURE__ */ jsxs2(
|
|
127
|
+
HeaderWrapper,
|
|
128
|
+
{
|
|
129
|
+
className: baseNavClassName,
|
|
130
|
+
...enableAnimation && {
|
|
131
|
+
initial: "hidden",
|
|
132
|
+
animate: "visible",
|
|
133
|
+
variants: navVariants
|
|
134
|
+
},
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ jsxs2("div", { className: "container flex items-center justify-between p-4", children: [
|
|
137
|
+
logo,
|
|
138
|
+
/* @__PURE__ */ jsx4(DesktopNav, { menuItems, selectedKey, enableAnimation }),
|
|
139
|
+
/* @__PURE__ */ jsx4(MobileMenuBtn, { menuItems, selectedKey, setIsMenuOpen, isMenuOpen })
|
|
140
|
+
] }),
|
|
141
|
+
/* @__PURE__ */ jsx4(AnimatePresence, { children: isMenuOpen && /* @__PURE__ */ jsx4(MobileNav, { menuItems, selectedKey, enableAnimation }) })
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
var DesktopNav = ({ menuItems, selectedKey, enableAnimation }) => {
|
|
147
|
+
return /* @__PURE__ */ jsx4("nav", { className: "hidden md:flex items-center space-x-8", children: menuItems.map((item) => /* @__PURE__ */ jsx4(NavLink, { href: item.href, selectedKey, className: item.className?.pc, children: item.label }, item.key)) });
|
|
148
|
+
};
|
|
149
|
+
var MobileNav = ({ menuItems, selectedKey, enableAnimation }) => {
|
|
150
|
+
const MobileMenuComponent = enableAnimation ? motion2.div : "div";
|
|
123
151
|
const mobileMenuVariants = {
|
|
124
152
|
hidden: {
|
|
125
153
|
height: 0,
|
|
@@ -138,74 +166,43 @@ var Header = ({
|
|
|
138
166
|
}
|
|
139
167
|
}
|
|
140
168
|
};
|
|
141
|
-
const NavComponent = enableAnimation ? motion2.nav : "nav";
|
|
142
|
-
const MobileMenuComponent = enableAnimation ? motion2.div : "div";
|
|
143
|
-
console.log("menuItems>>>", menuItems);
|
|
144
169
|
return /* @__PURE__ */ jsx4(
|
|
145
|
-
|
|
170
|
+
MobileMenuComponent,
|
|
146
171
|
{
|
|
147
|
-
className:
|
|
172
|
+
className: "md:hidden overflow-hidden",
|
|
148
173
|
...enableAnimation && {
|
|
149
174
|
initial: "hidden",
|
|
150
175
|
animate: "visible",
|
|
151
|
-
|
|
176
|
+
exit: "hidden",
|
|
177
|
+
variants: mobileMenuVariants
|
|
152
178
|
},
|
|
153
|
-
children: /* @__PURE__ */
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{
|
|
169
|
-
onClick: () => setIsMenuOpen(!isMenuOpen),
|
|
170
|
-
className: "md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors",
|
|
171
|
-
children: /* @__PURE__ */ jsx4(BreadIcon, { isMenuOpen })
|
|
172
|
-
}
|
|
173
|
-
)
|
|
174
|
-
] }),
|
|
175
|
-
/* @__PURE__ */ jsx4(AnimatePresence, { children: isMenuOpen && /* @__PURE__ */ jsx4(
|
|
176
|
-
MobileMenuComponent,
|
|
177
|
-
{
|
|
178
|
-
className: "md:hidden overflow-hidden",
|
|
179
|
-
...enableAnimation && {
|
|
180
|
-
initial: "hidden",
|
|
181
|
-
animate: "visible",
|
|
182
|
-
exit: "hidden",
|
|
183
|
-
variants: mobileMenuVariants
|
|
184
|
-
},
|
|
185
|
-
children: /* @__PURE__ */ jsx4("div", { className: "flex flex-col space-y-4 py-4 items-center", children: menuItems.map((item) => /* @__PURE__ */ jsx4(
|
|
186
|
-
motion2.div,
|
|
187
|
-
{
|
|
188
|
-
...enableAnimation && {
|
|
189
|
-
initial: { x: -20, opacity: 0 },
|
|
190
|
-
animate: { x: 0, opacity: 1 },
|
|
191
|
-
transition: { delay: 0.1 }
|
|
192
|
-
},
|
|
193
|
-
children: menuItems.map((item2) => /* @__PURE__ */ jsx4(MobileNavLink, { href: item2.href, className: item2.className?.mobile, selectedKey, children: item2.label }, item2.key))
|
|
194
|
-
},
|
|
195
|
-
item.key
|
|
196
|
-
)) })
|
|
197
|
-
}
|
|
198
|
-
) })
|
|
199
|
-
] })
|
|
179
|
+
children: /* @__PURE__ */ jsx4("nav", { className: "flex flex-col space-y-4 py-4 items-center", children: menuItems.map((item) => /* @__PURE__ */ jsx4(MobileMenuComponent, { ...enableAnimation && {
|
|
180
|
+
initial: { x: -20, opacity: 0 },
|
|
181
|
+
animate: { x: 0, opacity: 1 },
|
|
182
|
+
transition: { delay: 0.1 }
|
|
183
|
+
}, children: /* @__PURE__ */ jsx4(MobileNavLink, { href: item.href, selectedKey, className: item.className?.mobile, children: item.label }) }, item.key)) })
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
var MobileMenuBtn = ({ setIsMenuOpen, isMenuOpen }) => {
|
|
188
|
+
return /* @__PURE__ */ jsx4(
|
|
189
|
+
"button",
|
|
190
|
+
{
|
|
191
|
+
onClick: () => setIsMenuOpen(!isMenuOpen),
|
|
192
|
+
className: "md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors",
|
|
193
|
+
children: /* @__PURE__ */ jsx4(BreadIcon, { isMenuOpen })
|
|
200
194
|
}
|
|
201
195
|
);
|
|
202
196
|
};
|
|
203
197
|
var NavLink = ({ href, children, className, selectedKey }) => {
|
|
204
|
-
const combinedClassName =
|
|
198
|
+
const combinedClassName = classNames(
|
|
199
|
+
className || "hover:text-black transition-colors",
|
|
200
|
+
getActivedCls(href, selectedKey)
|
|
201
|
+
);
|
|
205
202
|
return /* @__PURE__ */ jsx4(Link, { href, legacyBehavior: true, children: /* @__PURE__ */ jsx4("a", { className: combinedClassName, children }) });
|
|
206
203
|
};
|
|
207
204
|
var MobileNavLink = ({ href, children, className, selectedKey }) => {
|
|
208
|
-
const combinedClassName = `${className || "text-lg font-medium text-center
|
|
205
|
+
const combinedClassName = `${className || "text-lg font-medium text-center hover:text-purple-600 transition-colors"} ${getActivedCls(href, selectedKey)}`;
|
|
209
206
|
return /* @__PURE__ */ jsx4(Link, { href, legacyBehavior: true, children: /* @__PURE__ */ jsx4("a", { className: combinedClassName, children }) });
|
|
210
207
|
};
|
|
211
208
|
var Header_default = Header;
|
|
@@ -265,6 +262,7 @@ var Footer = ({
|
|
|
265
262
|
/* @__PURE__ */ jsx5("div", { className: "absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600" })
|
|
266
263
|
] });
|
|
267
264
|
};
|
|
265
|
+
var Footer_default = Footer;
|
|
268
266
|
|
|
269
267
|
// src/hooks/useForceRerender.ts
|
|
270
268
|
import { useState as useState2, useCallback } from "react";
|
|
@@ -279,7 +277,7 @@ var useForceRerender_default = useForceRerender;
|
|
|
279
277
|
export {
|
|
280
278
|
Button_default as Button,
|
|
281
279
|
CardTitle,
|
|
282
|
-
Footer,
|
|
280
|
+
Footer_default as Footer,
|
|
283
281
|
Header_default as Header,
|
|
284
282
|
PageTitle,
|
|
285
283
|
SectionTitle,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Button/index.tsx","../src/components/Heading/index.tsx","../src/layout/Header/index.tsx","../src/layout/Header/BreadIcon.tsx","../src/layout/Footer/index.tsx","../src/hooks/useForceRerender.ts"],"sourcesContent":["import React from 'react';\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'primary',\n size = 'md',\n className = '',\n children,\n ...props\n}) => {\n const baseStyles = 'rounded-full font-medium transition-colors';\n \n const variants = {\n primary: 'bg-purple-600 text-white hover:bg-purple-700 shadow-lg shadow-purple-500/20',\n secondary: 'bg-purple-50 text-purple-700 hover:bg-purple-100',\n outline: 'border-2 border-purple-600 text-purple-600 hover:bg-purple-50'\n };\n\n const sizes = {\n sm: 'px-4 py-1.5 text-sm',\n md: 'px-6 py-2 text-sm',\n lg: 'px-8 py-3 text-base'\n };\n\n const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;\n\n return (\n <button className={classes} {...props}>\n {children}\n </button>\n );\n}; \n\nexport default Button;","'use client';\n\nimport { motion } from 'framer-motion';\nimport { ReactNode } from 'react';\n\nexport interface HeadingProps {\n children?: ReactNode;\n className?: string;\n title?: string;\n}\n\n// 頁面主標題:大標題,帶漸變色\nexport function PageTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h1\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8 }}\n className={`text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h1>\n );\n}\n\n// 區塊標題:中等大小,帶漸變色\nexport function SectionTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h2\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8, delay: 0.2 }}\n className={`text-3xl font-bold text-center mb-16 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h2>\n );\n}\n\n// 卡片標題:較小,深灰色\nexport function CardTitle({ children, title,className = '' }: HeadingProps) {\n return (\n <motion.h3\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-2xl font-bold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h3>\n );\n}\n\n// 區塊子標題:最小,帶圖標位置\nexport function SubTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h4\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-lg font-semibold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h4>\n );\n} \n\nexport default {\n PageTitle,\n SectionTitle,\n CardTitle,\n SubTitle,\n}","'use client';\nimport React, { useState } from 'react';\nimport Link from 'next/link';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport BreadIcon from './BreadIcon';\nimport { usePathname } from 'next/navigation';\nexport interface NavItem {\n key: string;\n href: string;\n label: string;\n className?: {\n pc?: string;\n mobile?: string;\n };\n}\n\nexport interface HeaderProps {\n logo: React.ReactNode;\n menuItems: NavItem[];\n className?: string;\n /** 是否啟用動畫效果 */\n enableAnimation?: boolean;\n}\nconst getActivedCls = (href: string, selectedKey: string) => {\n if(href.includes(selectedKey) || (href === '/' && selectedKey === 'home')) {\n return 'text-purple-600';\n }\n return 'text-gray-600';\n}\nexport const Header: React.FC<HeaderProps> = ({\n logo,\n menuItems,\n className = '',\n enableAnimation = true,\n}) => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const selectedKey = usePathname().split('/')[2] || 'home'; // todo support home\n console.log('selectedKey>>>',selectedKey);\n // 基礎樣式\n const baseNavClassName = `fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 ${className}`;\n // 動畫配置\n const navVariants = {\n hidden: { y: -20, opacity: 0 },\n visible: { \n y: 0, \n opacity: 1,\n transition: {\n duration: 0.5,\n ease: \"easeOut\"\n }\n }\n };\n\n const mobileMenuVariants = {\n hidden: { \n height: 0,\n opacity: 0,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n },\n visible: { \n height: \"auto\",\n opacity: 1,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n }\n };\n\n // 根據是否啟用動畫返回不同的組件\n const NavComponent = enableAnimation ? motion.nav : 'nav';\n const MobileMenuComponent = enableAnimation ? motion.div : 'div';\nconsole.log('menuItems>>>',menuItems);\n return (\n <NavComponent \n className={baseNavClassName}\n {...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n variants: navVariants\n })}\n >\n <div className=\"container mx-auto px-4\">\n <div className=\"flex items-center justify-between h-20\">\n {/* Logo */}\n {logo}\n\n {/* Desktop Navigation */}\n <nav className=\"hidden md:flex items-center space-x-8\">\n {menuItems.map((item) => (\n // <Link\n // key={item.key}\n // href=\"/\"\n // className=\"flex items-center justify-center gap-2 text-lg font-bold tracking-wide transition-all duration-300 ease-in-out\"\n // >\n // {item.label}\n // </Link>\n <NavLink key={item.key} href={item.href} className={item.className?.pc} selectedKey={selectedKey}>\n {item.label}\n </NavLink>\n ))}\n </nav>\n\n {/* Mobile Menu Button */}\n <button \n onClick={() => setIsMenuOpen(!isMenuOpen)}\n className=\"md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors\"\n >\n <BreadIcon isMenuOpen={isMenuOpen}/>\n </button>\n </div>\n\n {/* Mobile Menu */}\n <AnimatePresence>\n {isMenuOpen && (\n <MobileMenuComponent\n className=\"md:hidden overflow-hidden\"\n {...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n exit: \"hidden\",\n variants: mobileMenuVariants\n })}\n >\n <div className=\"flex flex-col space-y-4 py-4 items-center\">\n {menuItems.map((item) => (\n <motion.div\n key={item.key}\n {...(enableAnimation && {\n initial: { x: -20, opacity: 0 },\n animate: { x: 0, opacity: 1 },\n transition: { delay: 0.1 }\n })}\n >\n {\n menuItems.map((item) => (\n <MobileNavLink key={item.key} href={item.href} className={item.className?.mobile} selectedKey={selectedKey}>{item.label}</MobileNavLink>\n ))\n }\n </motion.div>\n ))}\n </div>\n </MobileMenuComponent>\n )}\n </AnimatePresence>\n </div>\n </NavComponent>\n );\n}; \n\n\nconst NavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = `${className || 'text-gray-600 hover:text-black transition-colors'} ${getActivedCls(href, selectedKey)}`;\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nconst MobileNavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = `${className || 'text-lg font-medium text-center text-gray-600 hover:text-purple-600 transition-colors'} ${getActivedCls(href, selectedKey)}`;\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nexport default Header;","export default function BreadIcon({isMenuOpen}: {isMenuOpen: boolean}) {\n return (\n <div className=\"w-6 h-5 relative flex flex-col justify-between\">\n <span className={`w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? 'rotate-45 translate-y-2' : ''}`} />\n <span className={`w-full h-0.5 bg-gray-800 transition-opacity duration-300 ${isMenuOpen ? 'opacity-0' : ''}`} />\n <span className={`w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? '-rotate-45 -translate-y-2' : ''}`} />\n </div>\n )\n}","'use client';\nimport React from 'react';\nimport Link from 'next/link';\n\nexport interface FooterLink {\n href: string;\n title: string;\n}\n\nexport interface FooterTexts {\n copyright: string;\n privacy: string;\n terms: string;\n social: string;\n}\n\nexport interface FooterProps {\n /** 網站基本信息 */\n site: {\n name: string;\n description: string;\n };\n /** 導航鏈接 */\n navigation: {\n /** 快速鏈接區塊 */\n quickLinks: {\n title: string;\n items: FooterLink[];\n };\n /** 社交媒體鏈接 */\n socialLinks: FooterLink[];\n };\n /** 頁腳文本 */\n texts: FooterTexts;\n /** 自定義類名 */\n className?: string;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n site,\n navigation,\n texts,\n className = '',\n}) => {\n return (\n <footer className={`relative bg-gradient-to-b from-gray-50 to-gray-100 pt-16 pb-6 ${className}`}>\n <div className=\"absolute inset-0 bg-grid-gray-200/25 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.6))]\" />\n \n <div className=\"container mx-auto p-4 relative\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8 mb-12\">\n {/* Brand Section */}\n <div className=\"space-y-4\">\n <h3 className=\"text-xl font-bold bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent\">\n {site.name}\n </h3>\n <p className=\"text-gray-600 max-w-xs\">\n {site.description}\n </p>\n </div>\n\n {/* Quick Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {navigation.quickLinks.title}\n </h4>\n <nav className=\"flex flex-col space-y-2\">\n {navigation.quickLinks.items.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n >\n {item.title}\n </Link>\n ))}\n </nav>\n </div>\n\n {/* Social Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {texts.social}\n </h4>\n <div className=\"flex space-x-4\">\n {navigation.socialLinks.map((link) => (\n <a\n key={link.href}\n href={link.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {link.title}\n </a>\n ))}\n </div>\n </div>\n </div>\n\n {/* Bottom Bar */}\n <div className=\"border-t border-gray-200 pt-6\">\n <div className=\"flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0\">\n <p className=\"text-sm text-gray-600\">\n {texts.copyright}\n </p>\n <div className=\"flex space-x-6\">\n <Link href=\"/privacy\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.privacy}\n </Link>\n <Link href=\"/terms\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.terms}\n </Link>\n </div>\n </div>\n </div>\n </div>\n\n {/* Decorative gradient line */}\n <div className=\"absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600\" />\n </footer>\n );\n}; ","import { useState, useCallback } from 'react';\n\nconst useForceRerender = (): [number, () => void] => {\n const [key, setKey] = useState(0);\n\n const forceRerender = useCallback(() => {\n setKey(prevKey => prevKey + 1);\n }, []);\n\n return [key, forceRerender];\n};\n\nexport default useForceRerender;"],"mappings":"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","names":["jsx","motion","jsx","jsx","jsxs","motion","item","Link","jsx","jsxs","useState"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/Button/index.tsx","../src/components/Heading/index.tsx","../src/layout/Header/index.tsx","../src/layout/Header/BreadIcon.tsx","../src/layout/Footer/index.tsx","../src/hooks/useForceRerender.ts"],"sourcesContent":["import React from 'react';\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'primary' | 'secondary' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n}\n\nexport const Button: React.FC<ButtonProps> = ({\n variant = 'primary',\n size = 'md',\n className = '',\n children,\n ...props\n}) => {\n const baseStyles = 'rounded-full font-medium transition-colors';\n \n const variants = {\n primary: 'bg-purple-600 text-white hover:bg-purple-700 shadow-lg shadow-purple-500/20',\n secondary: 'bg-purple-50 text-purple-700 hover:bg-purple-100',\n outline: 'border-2 border-purple-600 text-purple-600 hover:bg-purple-50'\n };\n\n const sizes = {\n sm: 'px-4 py-1.5 text-sm',\n md: 'px-6 py-2 text-sm',\n lg: 'px-8 py-3 text-base'\n };\n\n const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;\n\n return (\n <button className={classes} {...props}>\n {children}\n </button>\n );\n}; \n\nexport default Button;","'use client';\n\nimport { motion } from 'framer-motion';\nimport { ReactNode } from 'react';\n\nexport interface HeadingProps {\n children?: ReactNode;\n className?: string;\n title?: string;\n}\n\n// 頁面主標題:大標題,帶漸變色\nexport function PageTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h1\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8 }}\n className={`text-4xl md:text-5xl font-bold mb-6 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h1>\n );\n}\n\n// 區塊標題:中等大小,帶漸變色\nexport function SectionTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h2\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.8, delay: 0.2 }}\n className={`text-3xl font-bold text-center mb-16 bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent ${className}`}\n >\n {title || children}\n </motion.h2>\n );\n}\n\n// 卡片標題:較小,深灰色\nexport function CardTitle({ children, title,className = '' }: HeadingProps) {\n return (\n <motion.h3\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-2xl font-bold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h3>\n );\n}\n\n// 區塊子標題:最小,帶圖標位置\nexport function SubTitle({ children, title, className = '' }: HeadingProps) {\n return (\n <motion.h4\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: 0.5 }}\n className={`text-lg font-semibold text-gray-800 ${className}`}\n >\n {title || children}\n </motion.h4>\n );\n} \n\nexport default {\n PageTitle,\n SectionTitle,\n CardTitle,\n SubTitle,\n}","'use client';\nimport React, { useState } from 'react';\nimport classNames from 'classnames';\nimport Link from 'next/link';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport BreadIcon from './BreadIcon';\nimport { usePathname } from 'next/navigation';\nexport interface NavItem {\n key: string;\n href: string;\n label: string;\n className?: {\n pc?: string;\n mobile?: string;\n };\n}\n\nexport interface HeaderProps {\n logo: React.ReactNode;\n menuItems: NavItem[];\n className?: string;\n /** 是否啟用動畫效果 */\n enableAnimation?: boolean;\n}\nconst getActivedCls = (href: string, selectedKey: string) => {\n if(href.includes(selectedKey) || (href === '/' && selectedKey === 'home')) {\n return 'text-purple-600';\n }\n return '';\n}\nexport const Header: React.FC<HeaderProps> = ({\n logo,\n menuItems,\n className = '',\n enableAnimation = true,\n}) => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const selectedKey = usePathname().split('/')[2] || 'home';\n // 如何解决className 重复的问题,传入值与默认值有重复\n const baseNavClassName = classNames(`fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 text-gray-600`, className);\n\n // 動畫配置\n const navVariants = {\n hidden: { y: -20, opacity: 0 },\n visible: { \n y: 0, \n opacity: 1,\n transition: {\n duration: 0.5,\n ease: \"easeOut\"\n }\n }\n };\n\n // 根據是否啟用動畫返回不同的組件\n const HeaderWrapper = enableAnimation ? motion.header : 'header';\n return (\n <HeaderWrapper \n className={baseNavClassName}\n {...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n variants: navVariants\n })}\n >\n <div className='container flex items-center justify-between p-4'>\n {logo}\n {/* Desktop Navigation */}\n <DesktopNav menuItems={menuItems} selectedKey={selectedKey} enableAnimation={enableAnimation} />\n <MobileMenuBtn menuItems={menuItems} selectedKey={selectedKey} setIsMenuOpen={setIsMenuOpen} isMenuOpen={isMenuOpen} />\n </div>\n {/* Mobile Menu */}\n <AnimatePresence>\n {isMenuOpen && (\n <MobileNav menuItems={menuItems} selectedKey={selectedKey} enableAnimation={enableAnimation} />\n )}\n </AnimatePresence>\n </HeaderWrapper>\n );\n}; \nconst DesktopNav = ({ menuItems, selectedKey, enableAnimation }: { menuItems: NavItem[], selectedKey: string, enableAnimation: boolean }) => {\n return (\n <nav className='hidden md:flex items-center space-x-8'>\n {menuItems.map((item) => (\n <NavLink key={item.key} href={item.href} selectedKey={selectedKey} className={item.className?.pc}>{item.label}</NavLink>\n ))}\n </nav>\n )\n}\n\nconst MobileNav = ({ menuItems, selectedKey, enableAnimation }: { menuItems: NavItem[], selectedKey: string, enableAnimation: boolean }) => {\n const MobileMenuComponent = enableAnimation ? motion.div : 'div';\n const mobileMenuVariants = {\n hidden: { \n height: 0,\n opacity: 0,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n },\n visible: { \n height: \"auto\",\n opacity: 1,\n transition: {\n duration: 0.3,\n ease: \"easeInOut\"\n }\n }\n };\n return (\n <MobileMenuComponent className='md:hidden overflow-hidden'\n {\n ...(enableAnimation && {\n initial: \"hidden\",\n animate: \"visible\",\n exit: \"hidden\",\n variants: mobileMenuVariants\n })\n }>\n <nav className='flex flex-col space-y-4 py-4 items-center'>\n {menuItems.map((item) => (\n <MobileMenuComponent key={item.key} {...(enableAnimation && {\n initial: { x: -20, opacity: 0 },\n animate: { x: 0, opacity: 1 },\n transition: { delay: 0.1 }\n })}>\n <MobileNavLink href={item.href} selectedKey={selectedKey} className={item.className?.mobile}>{item.label}</MobileNavLink>\n </MobileMenuComponent>\n ))}\n </nav>\n </MobileMenuComponent>\n )\n}\n\nconst MobileMenuBtn = ({ setIsMenuOpen, isMenuOpen }: { menuItems: NavItem[], selectedKey: string, setIsMenuOpen: (isMenuOpen: boolean) => void, isMenuOpen: boolean }) => {\n return (\n <button \n onClick={() => setIsMenuOpen(!isMenuOpen)}\n className=\"md:hidden p-2 hover:bg-purple-50 rounded-lg transition-colors\"\n >\n <BreadIcon isMenuOpen={isMenuOpen}/>\n </button>\n )\n}\n\nconst NavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = classNames(\n className || 'hover:text-black transition-colors',\n getActivedCls(href, selectedKey)\n );\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nconst MobileNavLink = ({ href, children, className, selectedKey }: { href: string; children: React.ReactNode, className?: string, selectedKey: string }) => {\n const combinedClassName = `${className || 'text-lg font-medium text-center hover:text-purple-600 transition-colors'} ${getActivedCls(href, selectedKey)}`;\n return (\n <Link href={href} legacyBehavior>\n <a className={combinedClassName}>{children}</a>\n </Link>\n );\n};\n\nexport default Header;","const spanDefaultCls= 'w-full h-0.5 bg-black transition-transform duration-300'\nconst spanOpacityCls= 'w-full h-0.5 bg-black transition-opacity duration-300'\n\nexport default function BreadIcon({isMenuOpen}: {isMenuOpen: boolean}) {\n return (\n <div className=\"w-6 h-5 relative flex flex-col justify-between\">\n <span className={`${spanDefaultCls} ${isMenuOpen ? 'rotate-45 translate-y-2' : ''}`} />\n <span className={`${spanOpacityCls} ${isMenuOpen ? 'opacity-0' : ''}`} />\n <span className={`${spanDefaultCls} ${isMenuOpen ? '-rotate-45 -translate-y-2' : ''}`} />\n </div>\n )\n}","'use client';\nimport React from 'react';\nimport Link from 'next/link';\n\nexport interface FooterLink {\n href: string;\n title: string;\n}\n\nexport interface FooterTexts {\n copyright: string;\n privacy: string;\n terms: string;\n social: string;\n}\n\nexport interface FooterProps {\n /** 網站基本信息 */\n site: {\n name: string;\n description: string;\n };\n /** 導航鏈接 */\n navigation: {\n /** 快速鏈接區塊 */\n quickLinks: {\n title: string;\n items: FooterLink[];\n };\n /** 社交媒體鏈接 */\n socialLinks: FooterLink[];\n };\n /** 頁腳文本 */\n texts: FooterTexts;\n /** 自定義類名 */\n className?: string;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n site,\n navigation,\n texts,\n className = '',\n}) => {\n return (\n <footer className={`relative bg-gradient-to-b from-gray-50 to-gray-100 pt-16 pb-6 ${className}`}>\n <div className=\"absolute inset-0 bg-grid-gray-200/25 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.6))]\" />\n \n <div className=\"container mx-auto p-4 relative\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8 mb-12\">\n {/* Brand Section */}\n <div className=\"space-y-4\">\n <h3 className=\"text-xl font-bold bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent\">\n {site.name}\n </h3>\n <p className=\"text-gray-600 max-w-xs\">\n {site.description}\n </p>\n </div>\n\n {/* Quick Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {navigation.quickLinks.title}\n </h4>\n <nav className=\"flex flex-col space-y-2\">\n {navigation.quickLinks.items.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n >\n {item.title}\n </Link>\n ))}\n </nav>\n </div>\n\n {/* Social Links */}\n <div className=\"space-y-4\">\n <h4 className=\"text-sm font-semibold text-gray-900 uppercase\">\n {texts.social}\n </h4>\n <div className=\"flex space-x-4\">\n {navigation.socialLinks.map((link) => (\n <a\n key={link.href}\n href={link.href}\n className=\"text-gray-600 hover:text-purple-600 transition-colors\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {link.title}\n </a>\n ))}\n </div>\n </div>\n </div>\n\n {/* Bottom Bar */}\n <div className=\"border-t border-gray-200 pt-6\">\n <div className=\"flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0\">\n <p className=\"text-sm text-gray-600\">\n {texts.copyright}\n </p>\n <div className=\"flex space-x-6\">\n <Link href=\"/privacy\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.privacy}\n </Link>\n <Link href=\"/terms\" className=\"text-sm text-gray-600 hover:text-purple-600 transition-colors\">\n {texts.terms}\n </Link>\n </div>\n </div>\n </div>\n </div>\n\n {/* Decorative gradient line */}\n <div className=\"absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600\" />\n </footer>\n );\n}; \n\nexport default Footer;","import { useState, useCallback } from 'react';\n\nconst useForceRerender = (): [number, () => void] => {\n const [key, setKey] = useState(0);\n\n const forceRerender = useCallback(() => {\n setKey(prevKey => prevKey + 1);\n }, []);\n\n return [key, forceRerender];\n};\n\nexport default useForceRerender;"],"mappings":"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","names":["jsx","motion","jsx","jsx","jsxs","motion","Link","jsx","jsxs","useState"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jean-react-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Jean's personal reusable components, hooks, theme and i18n utils for Next.js projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
|
-
"require": "./dist/index.cjs"
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
13
12
|
}
|
|
14
13
|
},
|
|
15
14
|
"files": [
|
|
@@ -17,16 +16,6 @@
|
|
|
17
16
|
"README.md",
|
|
18
17
|
"LICENSE"
|
|
19
18
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"dev": "tsup src/index.ts --format esm,cjs --watch --dts --external react",
|
|
22
|
-
"build": "tsup src/index.ts",
|
|
23
|
-
"test": "vitest",
|
|
24
|
-
"lint": "eslint . --ext .ts,.tsx",
|
|
25
|
-
"typecheck": "tsc --noEmit",
|
|
26
|
-
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
27
|
-
"test:watch": "vitest",
|
|
28
|
-
"test:coverage": "vitest run --coverage"
|
|
29
|
-
},
|
|
30
19
|
"dependencies": {
|
|
31
20
|
"classnames": "^2.5.1"
|
|
32
21
|
},
|
|
@@ -35,10 +24,7 @@
|
|
|
35
24
|
"@testing-library/user-event": "^14.5.2",
|
|
36
25
|
"@vitejs/plugin-react": "^4.2.1",
|
|
37
26
|
"@vitest/coverage-v8": "^1.6.1",
|
|
38
|
-
"autoprefixer": "^10.4.21",
|
|
39
27
|
"happy-dom": "^13.3.8",
|
|
40
|
-
"postcss": "^8.5.4",
|
|
41
|
-
"tailwindcss": "^4.1.8",
|
|
42
28
|
"tsup": "^8.0.2",
|
|
43
29
|
"vitest": "^1.6.1"
|
|
44
30
|
},
|
|
@@ -62,5 +48,15 @@
|
|
|
62
48
|
"publishConfig": {
|
|
63
49
|
"access": "public"
|
|
64
50
|
},
|
|
65
|
-
"license": "MIT"
|
|
66
|
-
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"dev": "tsup src/index.ts --format esm,cjs --watch --dts --external react",
|
|
54
|
+
"build": "tsup src/index.ts",
|
|
55
|
+
"test": "vitest",
|
|
56
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:coverage": "vitest run --coverage"
|
|
61
|
+
}
|
|
62
|
+
}
|