jean-react-utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Jean-utils
2
+
3
+ ## 目的
4
+
5
+ 个人 Next.js 博客与网站的可复用 UI 组件库
6
+
7
+ ## 内容
8
+ - 通用组件(Button、Card)
9
+ - React Hooks(useForceRerender)
10
+ - Tailwind theme 配置
11
+ - i18n 封装,基于 next-intl
12
+ - 支持调用方自定义messages路径
13
+ - 兼容 getRequestConfig 自动集成功能
14
+
15
+ ## 项目结构
16
+ ```
17
+ jean-utils/
18
+ ├── src/
19
+ │ ├── components/ ← Button / Card 等通用组件
20
+ │ ├── layout/ ← Header / Footer / Nav
21
+ │ ├── hooks/ ← useForceRerender、useMediaQuery 等
22
+ │ ├── theme/ ← tailwind theme、token
23
+ │ ├── i18n/ ← next-intl 封装
24
+ │ └── index.ts ← 统一导出
25
+ ├── package.json
26
+ ├── tsconfig.json
27
+ ├── tailwind.config.ts
28
+ ├── vitest.config.ts
29
+ ├── postcss.config.js
30
+ └── README.md
31
+ ```
32
+
33
+ ## 现有痛点
34
+ - 每次调试都涉及多个package,心智负担大
35
+ - 更新组件时要发布版本、链接依赖、跑构建流程
36
+
37
+ ```
38
+
39
+ ```
@@ -0,0 +1,72 @@
1
+ import React, { ReactNode } from 'react';
2
+
3
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ variant?: 'primary' | 'secondary' | 'outline';
5
+ size?: 'sm' | 'md' | 'lg';
6
+ }
7
+ declare const Button: React.FC<ButtonProps>;
8
+
9
+ interface HeadingProps {
10
+ children?: ReactNode;
11
+ className?: string;
12
+ title?: string;
13
+ }
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;
18
+
19
+ interface NavItem {
20
+ key: string;
21
+ href: string;
22
+ label: string;
23
+ className?: {
24
+ pc?: string;
25
+ mobile?: string;
26
+ };
27
+ }
28
+ interface HeaderProps {
29
+ logo: React.ReactNode;
30
+ menuItems: NavItem[];
31
+ className?: string;
32
+ /** 是否啟用動畫效果 */
33
+ enableAnimation?: boolean;
34
+ }
35
+ declare const Header: React.FC<HeaderProps>;
36
+
37
+ interface FooterLink {
38
+ href: string;
39
+ title: string;
40
+ }
41
+ interface FooterTexts {
42
+ copyright: string;
43
+ privacy: string;
44
+ terms: string;
45
+ social: string;
46
+ }
47
+ interface FooterProps {
48
+ /** 網站基本信息 */
49
+ site: {
50
+ name: string;
51
+ description: string;
52
+ };
53
+ /** 導航鏈接 */
54
+ navigation: {
55
+ /** 快速鏈接區塊 */
56
+ quickLinks: {
57
+ title: string;
58
+ items: FooterLink[];
59
+ };
60
+ /** 社交媒體鏈接 */
61
+ socialLinks: FooterLink[];
62
+ };
63
+ /** 頁腳文本 */
64
+ texts: FooterTexts;
65
+ /** 自定義類名 */
66
+ className?: string;
67
+ }
68
+ declare const Footer: React.FC<FooterProps>;
69
+
70
+ declare const useForceRerender: () => [number, () => void];
71
+
72
+ export { Button, CardTitle, Footer, type FooterLink, type FooterProps, type FooterTexts, Header, PageTitle, SectionTitle, SubTitle, useForceRerender };
@@ -0,0 +1,72 @@
1
+ import React, { ReactNode } from 'react';
2
+
3
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ variant?: 'primary' | 'secondary' | 'outline';
5
+ size?: 'sm' | 'md' | 'lg';
6
+ }
7
+ declare const Button: React.FC<ButtonProps>;
8
+
9
+ interface HeadingProps {
10
+ children?: ReactNode;
11
+ className?: string;
12
+ title?: string;
13
+ }
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;
18
+
19
+ interface NavItem {
20
+ key: string;
21
+ href: string;
22
+ label: string;
23
+ className?: {
24
+ pc?: string;
25
+ mobile?: string;
26
+ };
27
+ }
28
+ interface HeaderProps {
29
+ logo: React.ReactNode;
30
+ menuItems: NavItem[];
31
+ className?: string;
32
+ /** 是否啟用動畫效果 */
33
+ enableAnimation?: boolean;
34
+ }
35
+ declare const Header: React.FC<HeaderProps>;
36
+
37
+ interface FooterLink {
38
+ href: string;
39
+ title: string;
40
+ }
41
+ interface FooterTexts {
42
+ copyright: string;
43
+ privacy: string;
44
+ terms: string;
45
+ social: string;
46
+ }
47
+ interface FooterProps {
48
+ /** 網站基本信息 */
49
+ site: {
50
+ name: string;
51
+ description: string;
52
+ };
53
+ /** 導航鏈接 */
54
+ navigation: {
55
+ /** 快速鏈接區塊 */
56
+ quickLinks: {
57
+ title: string;
58
+ items: FooterLink[];
59
+ };
60
+ /** 社交媒體鏈接 */
61
+ socialLinks: FooterLink[];
62
+ };
63
+ /** 頁腳文本 */
64
+ texts: FooterTexts;
65
+ /** 自定義類名 */
66
+ className?: string;
67
+ }
68
+ declare const Footer: React.FC<FooterProps>;
69
+
70
+ declare const useForceRerender: () => [number, () => void];
71
+
72
+ export { Button, CardTitle, Footer, type FooterLink, type FooterProps, type FooterTexts, Header, PageTitle, SectionTitle, SubTitle, useForceRerender };
package/dist/index.js ADDED
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Button: () => Button_default,
34
+ CardTitle: () => CardTitle,
35
+ Footer: () => Footer,
36
+ Header: () => Header_default,
37
+ PageTitle: () => PageTitle,
38
+ SectionTitle: () => SectionTitle,
39
+ SubTitle: () => SubTitle,
40
+ useForceRerender: () => useForceRerender_default
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/components/Button/index.tsx
45
+ var import_jsx_runtime = require("react/jsx-runtime");
46
+ var Button = ({
47
+ variant = "primary",
48
+ size = "md",
49
+ className = "",
50
+ children,
51
+ ...props
52
+ }) => {
53
+ const baseStyles = "rounded-full font-medium transition-colors";
54
+ const variants = {
55
+ primary: "bg-purple-600 text-white hover:bg-purple-700 shadow-lg shadow-purple-500/20",
56
+ secondary: "bg-purple-50 text-purple-700 hover:bg-purple-100",
57
+ outline: "border-2 border-purple-600 text-purple-600 hover:bg-purple-50"
58
+ };
59
+ const sizes = {
60
+ sm: "px-4 py-1.5 text-sm",
61
+ md: "px-6 py-2 text-sm",
62
+ lg: "px-8 py-3 text-base"
63
+ };
64
+ const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: classes, ...props, children });
66
+ };
67
+ var Button_default = Button;
68
+
69
+ // src/components/Heading/index.tsx
70
+ var import_framer_motion = require("framer-motion");
71
+ var import_jsx_runtime2 = require("react/jsx-runtime");
72
+ function PageTitle({ children, title, className = "" }) {
73
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
74
+ import_framer_motion.motion.h1,
75
+ {
76
+ initial: { opacity: 0, y: 20 },
77
+ animate: { opacity: 1, y: 0 },
78
+ transition: { duration: 0.8 },
79
+ 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}`,
80
+ children: title || children
81
+ }
82
+ );
83
+ }
84
+ function SectionTitle({ children, title, className = "" }) {
85
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
86
+ import_framer_motion.motion.h2,
87
+ {
88
+ initial: { opacity: 0, y: 20 },
89
+ animate: { opacity: 1, y: 0 },
90
+ transition: { duration: 0.8, delay: 0.2 },
91
+ 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}`,
92
+ children: title || children
93
+ }
94
+ );
95
+ }
96
+ function CardTitle({ children, title, className = "" }) {
97
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
98
+ import_framer_motion.motion.h3,
99
+ {
100
+ initial: { opacity: 0 },
101
+ animate: { opacity: 1 },
102
+ transition: { duration: 0.5 },
103
+ className: `text-2xl font-bold text-gray-800 ${className}`,
104
+ children: title || children
105
+ }
106
+ );
107
+ }
108
+ function SubTitle({ children, title, className = "" }) {
109
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
110
+ import_framer_motion.motion.h4,
111
+ {
112
+ initial: { opacity: 0 },
113
+ animate: { opacity: 1 },
114
+ transition: { duration: 0.5 },
115
+ className: `text-lg font-semibold text-gray-800 ${className}`,
116
+ children: title || children
117
+ }
118
+ );
119
+ }
120
+
121
+ // src/layout/Header/index.tsx
122
+ var import_react = require("react");
123
+ var import_link = __toESM(require("next/link"));
124
+ var import_framer_motion2 = require("framer-motion");
125
+
126
+ // src/layout/Header/BreadIcon.tsx
127
+ var import_jsx_runtime3 = require("react/jsx-runtime");
128
+ function BreadIcon({ isMenuOpen }) {
129
+ 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: `w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? "rotate-45 translate-y-2" : ""}` }),
131
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `w-full h-0.5 bg-gray-800 transition-opacity duration-300 ${isMenuOpen ? "opacity-0" : ""}` }),
132
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? "-rotate-45 -translate-y-2" : ""}` })
133
+ ] });
134
+ }
135
+
136
+ // src/layout/Header/index.tsx
137
+ var import_navigation = require("next/navigation");
138
+ var import_jsx_runtime4 = require("react/jsx-runtime");
139
+ var getActivedCls = (href, selectedKey) => {
140
+ if (href.includes(selectedKey) || href === "/" && selectedKey === "home") {
141
+ return "text-purple-600";
142
+ }
143
+ return "text-gray-600";
144
+ };
145
+ var Header = ({
146
+ logo,
147
+ menuItems,
148
+ className = "",
149
+ enableAnimation = true
150
+ }) => {
151
+ const [isMenuOpen, setIsMenuOpen] = (0, import_react.useState)(false);
152
+ const selectedKey = (0, import_navigation.usePathname)().split("/")[2] || "home";
153
+ console.log("selectedKey>>>", selectedKey);
154
+ const baseNavClassName = `fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 ${className}`;
155
+ const navVariants = {
156
+ hidden: { y: -20, opacity: 0 },
157
+ visible: {
158
+ y: 0,
159
+ opacity: 1,
160
+ transition: {
161
+ duration: 0.5,
162
+ ease: "easeOut"
163
+ }
164
+ }
165
+ };
166
+ const mobileMenuVariants = {
167
+ hidden: {
168
+ height: 0,
169
+ opacity: 0,
170
+ transition: {
171
+ duration: 0.3,
172
+ ease: "easeInOut"
173
+ }
174
+ },
175
+ visible: {
176
+ height: "auto",
177
+ opacity: 1,
178
+ transition: {
179
+ duration: 0.3,
180
+ ease: "easeInOut"
181
+ }
182
+ }
183
+ };
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
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
188
+ NavComponent,
189
+ {
190
+ className: baseNavClassName,
191
+ ...enableAnimation && {
192
+ initial: "hidden",
193
+ animate: "visible",
194
+ variants: navVariants
195
+ },
196
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "container mx-auto px-4", children: [
197
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center justify-between h-20", children: [
198
+ logo,
199
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("nav", { className: "hidden md:flex items-center space-x-8", children: menuItems.map((item) => (
200
+ // <Link
201
+ // key={item.key}
202
+ // href="/"
203
+ // className="flex items-center justify-center gap-2 text-lg font-bold tracking-wide transition-all duration-300 ease-in-out"
204
+ // >
205
+ // {item.label}
206
+ // </Link>
207
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavLink, { href: item.href, className: item.className?.pc, selectedKey, children: item.label }, item.key)
208
+ )) }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
210
+ "button",
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
+ ] })
243
+ }
244
+ );
245
+ };
246
+ var NavLink = ({ href, children, className, selectedKey }) => {
247
+ const combinedClassName = `${className || "text-gray-600 hover:text-black transition-colors"} ${getActivedCls(href, selectedKey)}`;
248
+ 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
+ };
250
+ var MobileNavLink = ({ href, children, className, selectedKey }) => {
251
+ const combinedClassName = `${className || "text-lg font-medium text-center text-gray-600 hover:text-purple-600 transition-colors"} ${getActivedCls(href, selectedKey)}`;
252
+ 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
+ };
254
+ var Header_default = Header;
255
+
256
+ // src/layout/Footer/index.tsx
257
+ var import_link2 = __toESM(require("next/link"));
258
+ var import_jsx_runtime5 = require("react/jsx-runtime");
259
+ var Footer = ({
260
+ site,
261
+ navigation,
262
+ texts,
263
+ className = ""
264
+ }) => {
265
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("footer", { className: `relative bg-gradient-to-b from-gray-50 to-gray-100 pt-16 pb-6 ${className}`, children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute inset-0 bg-grid-gray-200/25 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.6))]" }),
267
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "container mx-auto p-4 relative", children: [
268
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-8 mb-12", children: [
269
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-4", children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "text-xl font-bold bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent", children: site.name }),
271
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-gray-600 max-w-xs", children: site.description })
272
+ ] }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-4", children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h4", { className: "text-sm font-semibold text-gray-900 uppercase", children: navigation.quickLinks.title }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("nav", { className: "flex flex-col space-y-2", children: navigation.quickLinks.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
276
+ import_link2.default,
277
+ {
278
+ href: item.href,
279
+ className: "text-gray-600 hover:text-purple-600 transition-colors",
280
+ children: item.title
281
+ },
282
+ item.href
283
+ )) })
284
+ ] }),
285
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-4", children: [
286
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h4", { className: "text-sm font-semibold text-gray-900 uppercase", children: texts.social }),
287
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex space-x-4", children: navigation.socialLinks.map((link) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
288
+ "a",
289
+ {
290
+ href: link.href,
291
+ className: "text-gray-600 hover:text-purple-600 transition-colors",
292
+ target: "_blank",
293
+ rel: "noopener noreferrer",
294
+ children: link.title
295
+ },
296
+ link.href
297
+ )) })
298
+ ] })
299
+ ] }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "border-t border-gray-200 pt-6", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0", children: [
301
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm text-gray-600", children: texts.copyright }),
302
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex space-x-6", children: [
303
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_link2.default, { href: "/privacy", className: "text-sm text-gray-600 hover:text-purple-600 transition-colors", children: texts.privacy }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_link2.default, { href: "/terms", className: "text-sm text-gray-600 hover:text-purple-600 transition-colors", children: texts.terms })
305
+ ] })
306
+ ] }) })
307
+ ] }),
308
+ /* @__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
+ ] });
310
+ };
311
+
312
+ // src/hooks/useForceRerender.ts
313
+ var import_react2 = require("react");
314
+ var useForceRerender = () => {
315
+ const [key, setKey] = (0, import_react2.useState)(0);
316
+ const forceRerender = (0, import_react2.useCallback)(() => {
317
+ setKey((prevKey) => prevKey + 1);
318
+ }, []);
319
+ return [key, forceRerender];
320
+ };
321
+ var useForceRerender_default = useForceRerender;
322
+ // Annotate the CommonJS export names for ESM import in node:
323
+ 0 && (module.exports = {
324
+ Button,
325
+ CardTitle,
326
+ Footer,
327
+ Header,
328
+ PageTitle,
329
+ SectionTitle,
330
+ SubTitle,
331
+ useForceRerender
332
+ });
333
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,289 @@
1
+ // src/components/Button/index.tsx
2
+ import { jsx } from "react/jsx-runtime";
3
+ var Button = ({
4
+ variant = "primary",
5
+ size = "md",
6
+ className = "",
7
+ children,
8
+ ...props
9
+ }) => {
10
+ const baseStyles = "rounded-full font-medium transition-colors";
11
+ const variants = {
12
+ primary: "bg-purple-600 text-white hover:bg-purple-700 shadow-lg shadow-purple-500/20",
13
+ secondary: "bg-purple-50 text-purple-700 hover:bg-purple-100",
14
+ outline: "border-2 border-purple-600 text-purple-600 hover:bg-purple-50"
15
+ };
16
+ const sizes = {
17
+ sm: "px-4 py-1.5 text-sm",
18
+ md: "px-6 py-2 text-sm",
19
+ lg: "px-8 py-3 text-base"
20
+ };
21
+ const classes = `${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`;
22
+ return /* @__PURE__ */ jsx("button", { className: classes, ...props, children });
23
+ };
24
+ var Button_default = Button;
25
+
26
+ // src/components/Heading/index.tsx
27
+ import { motion } from "framer-motion";
28
+ import { jsx as jsx2 } from "react/jsx-runtime";
29
+ function PageTitle({ children, title, className = "" }) {
30
+ return /* @__PURE__ */ jsx2(
31
+ motion.h1,
32
+ {
33
+ initial: { opacity: 0, y: 20 },
34
+ animate: { opacity: 1, y: 0 },
35
+ transition: { duration: 0.8 },
36
+ 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}`,
37
+ children: title || children
38
+ }
39
+ );
40
+ }
41
+ function SectionTitle({ children, title, className = "" }) {
42
+ return /* @__PURE__ */ jsx2(
43
+ motion.h2,
44
+ {
45
+ initial: { opacity: 0, y: 20 },
46
+ animate: { opacity: 1, y: 0 },
47
+ transition: { duration: 0.8, delay: 0.2 },
48
+ 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}`,
49
+ children: title || children
50
+ }
51
+ );
52
+ }
53
+ function CardTitle({ children, title, className = "" }) {
54
+ return /* @__PURE__ */ jsx2(
55
+ motion.h3,
56
+ {
57
+ initial: { opacity: 0 },
58
+ animate: { opacity: 1 },
59
+ transition: { duration: 0.5 },
60
+ className: `text-2xl font-bold text-gray-800 ${className}`,
61
+ children: title || children
62
+ }
63
+ );
64
+ }
65
+ function SubTitle({ children, title, className = "" }) {
66
+ return /* @__PURE__ */ jsx2(
67
+ motion.h4,
68
+ {
69
+ initial: { opacity: 0 },
70
+ animate: { opacity: 1 },
71
+ transition: { duration: 0.5 },
72
+ className: `text-lg font-semibold text-gray-800 ${className}`,
73
+ children: title || children
74
+ }
75
+ );
76
+ }
77
+
78
+ // src/layout/Header/index.tsx
79
+ import { useState } from "react";
80
+ import Link from "next/link";
81
+ import { motion as motion2, AnimatePresence } from "framer-motion";
82
+
83
+ // src/layout/Header/BreadIcon.tsx
84
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
85
+ function BreadIcon({ isMenuOpen }) {
86
+ return /* @__PURE__ */ jsxs("div", { className: "w-6 h-5 relative flex flex-col justify-between", children: [
87
+ /* @__PURE__ */ jsx3("span", { className: `w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? "rotate-45 translate-y-2" : ""}` }),
88
+ /* @__PURE__ */ jsx3("span", { className: `w-full h-0.5 bg-gray-800 transition-opacity duration-300 ${isMenuOpen ? "opacity-0" : ""}` }),
89
+ /* @__PURE__ */ jsx3("span", { className: `w-full h-0.5 bg-gray-800 transition-transform duration-300 ${isMenuOpen ? "-rotate-45 -translate-y-2" : ""}` })
90
+ ] });
91
+ }
92
+
93
+ // src/layout/Header/index.tsx
94
+ import { usePathname } from "next/navigation";
95
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
96
+ var getActivedCls = (href, selectedKey) => {
97
+ if (href.includes(selectedKey) || href === "/" && selectedKey === "home") {
98
+ return "text-purple-600";
99
+ }
100
+ return "text-gray-600";
101
+ };
102
+ var Header = ({
103
+ logo,
104
+ menuItems,
105
+ className = "",
106
+ enableAnimation = true
107
+ }) => {
108
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
109
+ const selectedKey = usePathname().split("/")[2] || "home";
110
+ console.log("selectedKey>>>", selectedKey);
111
+ const baseNavClassName = `fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-lg border-b border-purple-100 ${className}`;
112
+ const navVariants = {
113
+ hidden: { y: -20, opacity: 0 },
114
+ visible: {
115
+ y: 0,
116
+ opacity: 1,
117
+ transition: {
118
+ duration: 0.5,
119
+ ease: "easeOut"
120
+ }
121
+ }
122
+ };
123
+ const mobileMenuVariants = {
124
+ hidden: {
125
+ height: 0,
126
+ opacity: 0,
127
+ transition: {
128
+ duration: 0.3,
129
+ ease: "easeInOut"
130
+ }
131
+ },
132
+ visible: {
133
+ height: "auto",
134
+ opacity: 1,
135
+ transition: {
136
+ duration: 0.3,
137
+ ease: "easeInOut"
138
+ }
139
+ }
140
+ };
141
+ const NavComponent = enableAnimation ? motion2.nav : "nav";
142
+ const MobileMenuComponent = enableAnimation ? motion2.div : "div";
143
+ console.log("menuItems>>>", menuItems);
144
+ return /* @__PURE__ */ jsx4(
145
+ NavComponent,
146
+ {
147
+ className: baseNavClassName,
148
+ ...enableAnimation && {
149
+ initial: "hidden",
150
+ animate: "visible",
151
+ variants: navVariants
152
+ },
153
+ children: /* @__PURE__ */ jsxs2("div", { className: "container mx-auto px-4", children: [
154
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between h-20", children: [
155
+ logo,
156
+ /* @__PURE__ */ jsx4("nav", { className: "hidden md:flex items-center space-x-8", children: menuItems.map((item) => (
157
+ // <Link
158
+ // key={item.key}
159
+ // href="/"
160
+ // className="flex items-center justify-center gap-2 text-lg font-bold tracking-wide transition-all duration-300 ease-in-out"
161
+ // >
162
+ // {item.label}
163
+ // </Link>
164
+ /* @__PURE__ */ jsx4(NavLink, { href: item.href, className: item.className?.pc, selectedKey, children: item.label }, item.key)
165
+ )) }),
166
+ /* @__PURE__ */ jsx4(
167
+ "button",
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
+ ] })
200
+ }
201
+ );
202
+ };
203
+ var NavLink = ({ href, children, className, selectedKey }) => {
204
+ const combinedClassName = `${className || "text-gray-600 hover:text-black transition-colors"} ${getActivedCls(href, selectedKey)}`;
205
+ return /* @__PURE__ */ jsx4(Link, { href, legacyBehavior: true, children: /* @__PURE__ */ jsx4("a", { className: combinedClassName, children }) });
206
+ };
207
+ var MobileNavLink = ({ href, children, className, selectedKey }) => {
208
+ const combinedClassName = `${className || "text-lg font-medium text-center text-gray-600 hover:text-purple-600 transition-colors"} ${getActivedCls(href, selectedKey)}`;
209
+ return /* @__PURE__ */ jsx4(Link, { href, legacyBehavior: true, children: /* @__PURE__ */ jsx4("a", { className: combinedClassName, children }) });
210
+ };
211
+ var Header_default = Header;
212
+
213
+ // src/layout/Footer/index.tsx
214
+ import Link2 from "next/link";
215
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
216
+ var Footer = ({
217
+ site,
218
+ navigation,
219
+ texts,
220
+ className = ""
221
+ }) => {
222
+ return /* @__PURE__ */ jsxs3("footer", { className: `relative bg-gradient-to-b from-gray-50 to-gray-100 pt-16 pb-6 ${className}`, children: [
223
+ /* @__PURE__ */ jsx5("div", { className: "absolute inset-0 bg-grid-gray-200/25 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.6))]" }),
224
+ /* @__PURE__ */ jsxs3("div", { className: "container mx-auto p-4 relative", children: [
225
+ /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-8 mb-12", children: [
226
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
227
+ /* @__PURE__ */ jsx5("h3", { className: "text-xl font-bold bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent", children: site.name }),
228
+ /* @__PURE__ */ jsx5("p", { className: "text-gray-600 max-w-xs", children: site.description })
229
+ ] }),
230
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
231
+ /* @__PURE__ */ jsx5("h4", { className: "text-sm font-semibold text-gray-900 uppercase", children: navigation.quickLinks.title }),
232
+ /* @__PURE__ */ jsx5("nav", { className: "flex flex-col space-y-2", children: navigation.quickLinks.items.map((item) => /* @__PURE__ */ jsx5(
233
+ Link2,
234
+ {
235
+ href: item.href,
236
+ className: "text-gray-600 hover:text-purple-600 transition-colors",
237
+ children: item.title
238
+ },
239
+ item.href
240
+ )) })
241
+ ] }),
242
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
243
+ /* @__PURE__ */ jsx5("h4", { className: "text-sm font-semibold text-gray-900 uppercase", children: texts.social }),
244
+ /* @__PURE__ */ jsx5("div", { className: "flex space-x-4", children: navigation.socialLinks.map((link) => /* @__PURE__ */ jsx5(
245
+ "a",
246
+ {
247
+ href: link.href,
248
+ className: "text-gray-600 hover:text-purple-600 transition-colors",
249
+ target: "_blank",
250
+ rel: "noopener noreferrer",
251
+ children: link.title
252
+ },
253
+ link.href
254
+ )) })
255
+ ] })
256
+ ] }),
257
+ /* @__PURE__ */ jsx5("div", { className: "border-t border-gray-200 pt-6", children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0", children: [
258
+ /* @__PURE__ */ jsx5("p", { className: "text-sm text-gray-600", children: texts.copyright }),
259
+ /* @__PURE__ */ jsxs3("div", { className: "flex space-x-6", children: [
260
+ /* @__PURE__ */ jsx5(Link2, { href: "/privacy", className: "text-sm text-gray-600 hover:text-purple-600 transition-colors", children: texts.privacy }),
261
+ /* @__PURE__ */ jsx5(Link2, { href: "/terms", className: "text-sm text-gray-600 hover:text-purple-600 transition-colors", children: texts.terms })
262
+ ] })
263
+ ] }) })
264
+ ] }),
265
+ /* @__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
+ ] });
267
+ };
268
+
269
+ // src/hooks/useForceRerender.ts
270
+ import { useState as useState2, useCallback } from "react";
271
+ var useForceRerender = () => {
272
+ const [key, setKey] = useState2(0);
273
+ const forceRerender = useCallback(() => {
274
+ setKey((prevKey) => prevKey + 1);
275
+ }, []);
276
+ return [key, forceRerender];
277
+ };
278
+ var useForceRerender_default = useForceRerender;
279
+ export {
280
+ Button_default as Button,
281
+ CardTitle,
282
+ Footer,
283
+ Header_default as Header,
284
+ PageTitle,
285
+ SectionTitle,
286
+ SubTitle,
287
+ useForceRerender_default as useForceRerender
288
+ };
289
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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":";AA+BI;AAxBG,IAAM,SAAgC,CAAC;AAAA,EAC5C,UAAU;AAAA,EACV,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,MAAM;AACJ,QAAM,aAAa;AAEnB,QAAM,WAAW;AAAA,IACf,SAAS;AAAA,IACT,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAEA,QAAM,QAAQ;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAEA,QAAM,UAAU,GAAG,UAAU,IAAI,SAAS,OAAO,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS;AAE9E,SACE,oBAAC,YAAO,WAAW,SAAU,GAAG,OAC7B,UACH;AAEJ;AAEA,IAAO,iBAAQ;;;ACnCf,SAAS,cAAc;AAYnB,gBAAAA,YAAA;AAFG,SAAS,UAAU,EAAE,UAAU,OAAO,YAAY,GAAG,GAAiB;AAC3E,SACE,gBAAAA;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAC5B,WAAW,kHAAkH,SAAS;AAAA,MAErI,mBAAS;AAAA;AAAA,EACZ;AAEJ;AAGO,SAAS,aAAa,EAAE,UAAU,OAAO,YAAY,GAAG,GAAiB;AAC9E,SACE,gBAAAA;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,KAAK,OAAO,IAAI;AAAA,MACxC,WAAW,mHAAmH,SAAS;AAAA,MAEtI,mBAAS;AAAA;AAAA,EACZ;AAEJ;AAGO,SAAS,UAAU,EAAE,UAAU,OAAM,YAAY,GAAG,GAAiB;AAC1E,SACE,gBAAAA;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,EAAE;AAAA,MACtB,SAAS,EAAE,SAAS,EAAE;AAAA,MACtB,YAAY,EAAE,UAAU,IAAI;AAAA,MAC5B,WAAW,oCAAoC,SAAS;AAAA,MAEvD,mBAAS;AAAA;AAAA,EACZ;AAEJ;AAGO,SAAS,SAAS,EAAE,UAAU,OAAO,YAAY,GAAG,GAAiB;AAC1E,SACE,gBAAAA;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,EAAE;AAAA,MACtB,SAAS,EAAE,SAAS,EAAE;AAAA,MACtB,YAAY,EAAE,UAAU,IAAI;AAAA,MAC5B,WAAW,uCAAuC,SAAS;AAAA,MAE1D,mBAAS;AAAA;AAAA,EACZ;AAEJ;;;AChEA,SAAgB,gBAAgB;AAChC,OAAO,UAAU;AACjB,SAAS,UAAAC,SAAQ,uBAAuB;;;ACDhC,SACM,OAAAC,MADN;AAFO,SAAR,UAA2B,EAAC,WAAU,GAA0B;AACnE,SACI,qBAAC,SAAI,WAAU,kDACT;AAAA,oBAAAA,KAAC,UAAK,WAAW,8DAA8D,aAAa,4BAA4B,EAAE,IAAI;AAAA,IAC9H,gBAAAA,KAAC,UAAK,WAAW,4DAA4D,aAAa,cAAc,EAAE,IAAI;AAAA,IAC9G,gBAAAA,KAAC,UAAK,WAAW,8DAA8D,aAAa,8BAA8B,EAAE,IAAI;AAAA,KACtI;AAER;;;ADHA,SAAS,mBAAmB;AAiFpB,SAcM,OAAAC,MAdN,QAAAC,aAAA;AA/DR,IAAM,gBAAgB,CAAC,MAAc,gBAAwB;AAC3D,MAAG,KAAK,SAAS,WAAW,KAAM,SAAS,OAAO,gBAAgB,QAAS;AACzE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AACO,IAAM,SAAgC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,kBAAkB;AACpB,MAAM;AACJ,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,cAAc,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK;AACnD,UAAQ,IAAI,kBAAiB,WAAW;AAExC,QAAM,mBAAmB,2FAA2F,SAAS;AAE7H,QAAM,cAAc;AAAA,IAClB,QAAQ,EAAE,GAAG,KAAK,SAAS,EAAE;AAAA,IAC7B,SAAS;AAAA,MACP,GAAG;AAAA,MACH,SAAS;AAAA,MACT,YAAY;AAAA,QACV,UAAU;AAAA,QACV,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,qBAAqB;AAAA,IACzB,QAAQ;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA,QACV,UAAU;AAAA,QACV,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA,QACV,UAAU;AAAA,QACV,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,eAAe,kBAAkBC,QAAO,MAAM;AACpD,QAAM,sBAAsB,kBAAkBA,QAAO,MAAM;AAC7D,UAAQ,IAAI,gBAAe,SAAS;AAClC,SACE,gBAAAF;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACV,GAAI,mBAAmB;AAAA,QACtB,SAAS;AAAA,QACT,SAAS;AAAA,QACT,UAAU;AAAA,MACZ;AAAA,MAEA,0BAAAC,MAAC,SAAI,WAAU,0BACb;AAAA,wBAAAA,MAAC,SAAI,WAAU,0CAEZ;AAAA;AAAA,UAGD,gBAAAD,KAAC,SAAI,WAAU,yCACZ,oBAAU,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAQd,gBAAAA,KAAC,WAAuB,MAAM,KAAK,MAAM,WAAW,KAAK,WAAW,IAAI,aACrE,eAAK,SADM,KAAK,GAEnB;AAAA,WACD,GACH;AAAA,UAGA,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,MAAM,cAAc,CAAC,UAAU;AAAA,cACxC,WAAU;AAAA,cAEV,0BAAAA,KAAC,aAAU,YAAuB;AAAA;AAAA,UACpC;AAAA,WACF;AAAA,QAGA,gBAAAA,KAAC,mBACE,wBACC,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACT,GAAI,mBAAmB;AAAA,cACtB,SAAS;AAAA,cACT,SAAS;AAAA,cACT,MAAM;AAAA,cACN,UAAU;AAAA,YACZ;AAAA,YAEA,0BAAAA,KAAC,SAAI,WAAU,6CACZ,oBAAU,IAAI,CAAC,SACd,gBAAAA;AAAA,cAACE,QAAO;AAAA,cAAP;AAAA,gBAEE,GAAI,mBAAmB;AAAA,kBACtB,SAAS,EAAE,GAAG,KAAK,SAAS,EAAE;AAAA,kBAC9B,SAAS,EAAE,GAAG,GAAG,SAAS,EAAE;AAAA,kBAC5B,YAAY,EAAE,OAAO,IAAI;AAAA,gBAC3B;AAAA,gBAGE,oBAAU,IAAI,CAACC,UACX,gBAAAH,KAAC,iBAA6B,MAAMG,MAAK,MAAM,WAAWA,MAAK,WAAW,QAAQ,aAA2B,UAAAA,MAAK,SAA9FA,MAAK,GAA+F,CAC3H;AAAA;AAAA,cAVE,KAAK;AAAA,YAYZ,CACD,GACH;AAAA;AAAA,QACF,GAEJ;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;AAGA,IAAM,UAAU,CAAC,EAAE,MAAM,UAAU,WAAW,YAAY,MAA4F;AACpJ,QAAM,oBAAoB,GAAG,aAAa,kDAAkD,IAAI,cAAc,MAAM,WAAW,CAAC;AAChI,SACE,gBAAAH,KAAC,QAAK,MAAY,gBAAc,MAC9B,0BAAAA,KAAC,OAAE,WAAW,mBAAoB,UAAS,GAC7C;AAEJ;AAEA,IAAM,gBAAgB,CAAC,EAAE,MAAM,UAAU,WAAW,YAAY,MAA4F;AAC1J,QAAM,oBAAoB,GAAG,aAAa,uFAAuF,IAAI,cAAc,MAAM,WAAW,CAAC;AACrK,SACE,gBAAAA,KAAC,QAAK,MAAY,gBAAc,MAC9B,0BAAAA,KAAC,OAAE,WAAW,mBAAoB,UAAS,GAC7C;AAEJ;AAEA,IAAO,iBAAQ;;;AE1Kf,OAAOI,WAAU;AA4CX,gBAAAC,MAKI,QAAAC,aALJ;AARC,IAAM,SAAgC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd,MAAM;AACJ,SACE,gBAAAA,MAAC,YAAO,WAAW,iEAAiE,SAAS,IAC3F;AAAA,oBAAAD,KAAC,SAAI,WAAU,uGAAsG;AAAA,IAErH,gBAAAC,MAAC,SAAI,WAAU,kCACb;AAAA,sBAAAA,MAAC,SAAI,WAAU,+CAEb;AAAA,wBAAAA,MAAC,SAAI,WAAU,aACb;AAAA,0BAAAD,KAAC,QAAG,WAAU,gGACX,eAAK,MACR;AAAA,UACA,gBAAAA,KAAC,OAAE,WAAU,0BACV,eAAK,aACR;AAAA,WACF;AAAA,QAGA,gBAAAC,MAAC,SAAI,WAAU,aACb;AAAA,0BAAAD,KAAC,QAAG,WAAU,iDACX,qBAAW,WAAW,OACzB;AAAA,UACA,gBAAAA,KAAC,SAAI,WAAU,2BACZ,qBAAW,WAAW,MAAM,IAAI,CAAC,SAChC,gBAAAA;AAAA,YAACD;AAAA,YAAA;AAAA,cAEC,MAAM,KAAK;AAAA,cACX,WAAU;AAAA,cAET,eAAK;AAAA;AAAA,YAJD,KAAK;AAAA,UAKZ,CACD,GACH;AAAA,WACF;AAAA,QAGA,gBAAAE,MAAC,SAAI,WAAU,aACb;AAAA,0BAAAD,KAAC,QAAG,WAAU,iDACX,gBAAM,QACT;AAAA,UACA,gBAAAA,KAAC,SAAI,WAAU,kBACZ,qBAAW,YAAY,IAAI,CAAC,SAC3B,gBAAAA;AAAA,YAAC;AAAA;AAAA,cAEC,MAAM,KAAK;AAAA,cACX,WAAU;AAAA,cACV,QAAO;AAAA,cACP,KAAI;AAAA,cAEH,eAAK;AAAA;AAAA,YAND,KAAK;AAAA,UAOZ,CACD,GACH;AAAA,WACF;AAAA,SACF;AAAA,MAGA,gBAAAA,KAAC,SAAI,WAAU,iCACb,0BAAAC,MAAC,SAAI,WAAU,iFACb;AAAA,wBAAAD,KAAC,OAAE,WAAU,yBACV,gBAAM,WACT;AAAA,QACA,gBAAAC,MAAC,SAAI,WAAU,kBACb;AAAA,0BAAAD,KAACD,OAAA,EAAK,MAAK,YAAW,WAAU,iEAC7B,gBAAM,SACT;AAAA,UACA,gBAAAC,KAACD,OAAA,EAAK,MAAK,UAAS,WAAU,iEAC3B,gBAAM,OACT;AAAA,WACF;AAAA,SACF,GACF;AAAA,OACF;AAAA,IAGA,gBAAAC,KAAC,SAAI,WAAU,iGAAgG;AAAA,KACjH;AAEJ;;;ACzHA,SAAS,YAAAE,WAAU,mBAAmB;AAEtC,IAAM,mBAAmB,MAA4B;AACnD,QAAM,CAAC,KAAK,MAAM,IAAIA,UAAS,CAAC;AAEhC,QAAM,gBAAgB,YAAY,MAAM;AACtC,WAAO,aAAW,UAAU,CAAC;AAAA,EAC/B,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,KAAK,aAAa;AAC5B;AAEA,IAAO,2BAAQ;","names":["jsx","motion","jsx","jsx","jsxs","motion","item","Link","jsx","jsxs","useState"]}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "jean-react-utils",
3
+ "version": "0.1.0",
4
+ "description": "Jean's personal reusable components, hooks, theme and i18n utils for Next.js projects",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
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
+ "dependencies": {
31
+ "classnames": "^2.5.1"
32
+ },
33
+ "devDependencies": {
34
+ "@testing-library/react": "^14.3.1",
35
+ "@testing-library/user-event": "^14.5.2",
36
+ "@vitejs/plugin-react": "^4.2.1",
37
+ "@vitest/coverage-v8": "^1.6.1",
38
+ "autoprefixer": "^10.4.21",
39
+ "happy-dom": "^13.3.8",
40
+ "postcss": "^8.5.4",
41
+ "tailwindcss": "^4.1.8",
42
+ "tsup": "^8.0.2",
43
+ "vitest": "^1.6.1"
44
+ },
45
+ "peerDependencies": {
46
+ "@emotion/is-prop-valid": "^1.2.1",
47
+ "framer-motion": "^12.1.6",
48
+ "next": "^14.0.0",
49
+ "react": "^18.2.0",
50
+ "react-dom": "^18.2.0"
51
+ },
52
+ "author": "jean-w",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/Jean-W-FE/jean-react-utils.git"
56
+ },
57
+ "keywords": [
58
+ "jean-react-utils",
59
+ "jean-dev",
60
+ "jean-blog-site"
61
+ ],
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "license": "MIT"
66
+ }