@windrun-huaiin/third-ui 13.1.0 → 13.1.2
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/dist/main/faq-interactive.js +17 -30
- package/dist/main/faq-interactive.mjs +18 -31
- package/dist/main/faq.js +2 -1
- package/dist/main/faq.mjs +2 -1
- package/dist/main/gallery/gallery-mobile-swiper.js +6 -1
- package/dist/main/gallery/gallery-mobile-swiper.mjs +6 -1
- package/dist/main/loading.d.ts +9 -1
- package/dist/main/loading.js +43 -17
- package/dist/main/loading.mjs +43 -17
- package/dist/main/money-price/money-price-interactive.js +1 -1
- package/dist/main/money-price/money-price-interactive.mjs +2 -2
- package/dist/main/nprogress-bar.js +4 -0
- package/dist/main/nprogress-bar.mjs +4 -0
- package/package.json +3 -3
- package/src/main/faq-interactive.tsx +21 -37
- package/src/main/faq.tsx +13 -3
- package/src/main/gallery/gallery-mobile-swiper.tsx +10 -1
- package/src/main/loading.tsx +71 -19
- package/src/main/money-price/money-price-interactive.tsx +5 -4
- package/src/main/nprogress-bar.tsx +6 -1
- package/src/styles/third-ui.css +6 -4
|
@@ -4,48 +4,35 @@
|
|
|
4
4
|
var React = require('react');
|
|
5
5
|
|
|
6
6
|
function FAQInteractive({ data }) {
|
|
7
|
-
const [openStates, setOpenStates] = React.useState({});
|
|
8
7
|
React.useEffect(() => {
|
|
9
|
-
|
|
8
|
+
const cleanups = [];
|
|
10
9
|
data.items.forEach((item) => {
|
|
11
10
|
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`);
|
|
12
11
|
const contentDiv = document.querySelector(`[data-faq-content="${item.id}"]`);
|
|
13
12
|
const iconSvg = document.querySelector(`[data-faq-icon="${item.id}"]`);
|
|
14
|
-
|
|
13
|
+
const cardDiv = document.querySelector(`[data-faq-id="${item.id}"]`);
|
|
14
|
+
if (toggleButton && contentDiv && iconSvg && cardDiv) {
|
|
15
|
+
const syncOpenState = (isOpen) => {
|
|
16
|
+
contentDiv.classList.toggle('hidden', !isOpen);
|
|
17
|
+
toggleButton.setAttribute('aria-expanded', String(isOpen));
|
|
18
|
+
iconSvg.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
19
|
+
cardDiv.setAttribute('data-faq-open', String(isOpen));
|
|
20
|
+
};
|
|
15
21
|
const handleClick = () => {
|
|
16
|
-
const isOpen =
|
|
17
|
-
|
|
18
|
-
// Update state
|
|
19
|
-
setOpenStates(prev => (Object.assign(Object.assign({}, prev), { [item.id]: newOpenState })));
|
|
20
|
-
// Update DOM
|
|
21
|
-
if (newOpenState) {
|
|
22
|
-
contentDiv.classList.remove('hidden');
|
|
23
|
-
toggleButton.setAttribute('aria-expanded', 'true');
|
|
24
|
-
iconSvg.style.transform = 'rotate(90deg)';
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
contentDiv.classList.add('hidden');
|
|
28
|
-
toggleButton.setAttribute('aria-expanded', 'false');
|
|
29
|
-
iconSvg.style.transform = 'rotate(0deg)';
|
|
30
|
-
}
|
|
22
|
+
const isOpen = toggleButton.getAttribute('aria-expanded') === 'true';
|
|
23
|
+
syncOpenState(!isOpen);
|
|
31
24
|
};
|
|
25
|
+
syncOpenState(toggleButton.getAttribute('aria-expanded') === 'true');
|
|
32
26
|
toggleButton.addEventListener('click', handleClick);
|
|
33
|
-
|
|
27
|
+
cleanups.push(() => {
|
|
28
|
+
toggleButton.removeEventListener('click', handleClick);
|
|
29
|
+
});
|
|
34
30
|
}
|
|
35
31
|
});
|
|
36
|
-
// Cleanup event listeners
|
|
37
32
|
return () => {
|
|
38
|
-
|
|
39
|
-
var _a;
|
|
40
|
-
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`);
|
|
41
|
-
if (toggleButton) {
|
|
42
|
-
// Remove all event listeners by cloning the element
|
|
43
|
-
const newButton = toggleButton.cloneNode(true);
|
|
44
|
-
(_a = toggleButton.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(newButton, toggleButton);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
33
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
47
34
|
};
|
|
48
|
-
}, [data
|
|
35
|
+
}, [data.items]);
|
|
49
36
|
return null; // Progressive enhancement - no additional DOM rendering
|
|
50
37
|
}
|
|
51
38
|
|
|
@@ -1,49 +1,36 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
function FAQInteractive({ data }) {
|
|
5
|
-
const [openStates, setOpenStates] = useState({});
|
|
6
5
|
useEffect(() => {
|
|
7
|
-
|
|
6
|
+
const cleanups = [];
|
|
8
7
|
data.items.forEach((item) => {
|
|
9
8
|
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`);
|
|
10
9
|
const contentDiv = document.querySelector(`[data-faq-content="${item.id}"]`);
|
|
11
10
|
const iconSvg = document.querySelector(`[data-faq-icon="${item.id}"]`);
|
|
12
|
-
|
|
11
|
+
const cardDiv = document.querySelector(`[data-faq-id="${item.id}"]`);
|
|
12
|
+
if (toggleButton && contentDiv && iconSvg && cardDiv) {
|
|
13
|
+
const syncOpenState = (isOpen) => {
|
|
14
|
+
contentDiv.classList.toggle('hidden', !isOpen);
|
|
15
|
+
toggleButton.setAttribute('aria-expanded', String(isOpen));
|
|
16
|
+
iconSvg.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
17
|
+
cardDiv.setAttribute('data-faq-open', String(isOpen));
|
|
18
|
+
};
|
|
13
19
|
const handleClick = () => {
|
|
14
|
-
const isOpen =
|
|
15
|
-
|
|
16
|
-
// Update state
|
|
17
|
-
setOpenStates(prev => (Object.assign(Object.assign({}, prev), { [item.id]: newOpenState })));
|
|
18
|
-
// Update DOM
|
|
19
|
-
if (newOpenState) {
|
|
20
|
-
contentDiv.classList.remove('hidden');
|
|
21
|
-
toggleButton.setAttribute('aria-expanded', 'true');
|
|
22
|
-
iconSvg.style.transform = 'rotate(90deg)';
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
contentDiv.classList.add('hidden');
|
|
26
|
-
toggleButton.setAttribute('aria-expanded', 'false');
|
|
27
|
-
iconSvg.style.transform = 'rotate(0deg)';
|
|
28
|
-
}
|
|
20
|
+
const isOpen = toggleButton.getAttribute('aria-expanded') === 'true';
|
|
21
|
+
syncOpenState(!isOpen);
|
|
29
22
|
};
|
|
23
|
+
syncOpenState(toggleButton.getAttribute('aria-expanded') === 'true');
|
|
30
24
|
toggleButton.addEventListener('click', handleClick);
|
|
31
|
-
|
|
25
|
+
cleanups.push(() => {
|
|
26
|
+
toggleButton.removeEventListener('click', handleClick);
|
|
27
|
+
});
|
|
32
28
|
}
|
|
33
29
|
});
|
|
34
|
-
// Cleanup event listeners
|
|
35
30
|
return () => {
|
|
36
|
-
|
|
37
|
-
var _a;
|
|
38
|
-
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`);
|
|
39
|
-
if (toggleButton) {
|
|
40
|
-
// Remove all event listeners by cloning the element
|
|
41
|
-
const newButton = toggleButton.cloneNode(true);
|
|
42
|
-
(_a = toggleButton.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(newButton, toggleButton);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
31
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
45
32
|
};
|
|
46
|
-
}, [data
|
|
33
|
+
}, [data.items]);
|
|
47
34
|
return null; // Progressive enhancement - no additional DOM rendering
|
|
48
35
|
}
|
|
49
36
|
|
package/dist/main/faq.js
CHANGED
|
@@ -4,6 +4,7 @@ var tslib_es6 = require('../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var server = require('next-intl/server');
|
|
6
6
|
var utils = require('@windrun-huaiin/lib/utils');
|
|
7
|
+
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
7
8
|
var richTextExpert = require('./rich-text-expert.js');
|
|
8
9
|
var faqInteractive = require('./faq-interactive.js');
|
|
9
10
|
var sectionLayout = require('./section-layout.js');
|
|
@@ -22,7 +23,7 @@ function FAQ(_a) {
|
|
|
22
23
|
answer: richTextExpert.richText(t, `items.${index}.answer`)
|
|
23
24
|
}))
|
|
24
25
|
};
|
|
25
|
-
return (jsxRuntime.jsxs("section", { id: "faq", className: utils.cn(sectionLayout.responsiveSection, sectionClassName), children: [jsxRuntime.jsx("h2", { className: "text-3xl md:text-4xl font-bold text-center mb-4", children: data.title }), jsxRuntime.jsx("p", { className: "text-center text-gray-600 dark:text-gray-400 mb-12 text-base sm:text-lg mx-auto max-w-3xl", children: data.description }), jsxRuntime.jsx("div", { className: "space-y-6", children: data.items.map((item) => (jsxRuntime.jsxs("div", { "data-faq-id": item.id, className: "bg-white dark:bg-gray-800/60
|
|
26
|
+
return (jsxRuntime.jsxs("section", { id: "faq", className: utils.cn(sectionLayout.responsiveSection, sectionClassName), children: [jsxRuntime.jsx("h2", { className: "text-3xl md:text-4xl font-bold text-center mb-4", children: data.title }), jsxRuntime.jsx("p", { className: "text-center text-gray-600 dark:text-gray-400 mb-12 text-base sm:text-lg mx-auto max-w-3xl", children: data.description }), jsxRuntime.jsx("div", { className: "space-y-6", children: data.items.map((item) => (jsxRuntime.jsxs("div", { "data-faq-id": item.id, "data-faq-open": "false", className: utils.cn("bg-white dark:bg-gray-800/60 rounded-xl border border-gray-200 dark:border-gray-700 transition shadow-sm dark:shadow-none hover:border-current focus-within:border-current", lib.themeIconColor), children: [jsxRuntime.jsxs("button", { className: "w-full p-6 flex items-center justify-between text-left focus:outline-none", "data-faq-toggle": item.id, type: "button", "aria-expanded": "false", "aria-controls": `${item.id}-content`, children: [jsxRuntime.jsx("span", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: item.question }), jsxRuntime.jsx("svg", { className: "w-6 h-6 text-gray-400 ml-2 transition-transform duration-200", "data-faq-icon": item.id, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })] }), jsxRuntime.jsx("div", { id: `${item.id}-content`, className: "px-6 pb-6 text-gray-700 dark:text-gray-300 text-base hidden", "data-faq-content": item.id, children: jsxRuntime.jsx("div", { className: "pt-1", children: item.answer }) })] }, item.id))) }), jsxRuntime.jsx(faqInteractive.FAQInteractive, { data: data })] }));
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
|
package/dist/main/faq.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { __awaiter } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.1.
|
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { getTranslations } from 'next-intl/server';
|
|
4
4
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
|
+
import { themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
5
6
|
import { richText } from './rich-text-expert.mjs';
|
|
6
7
|
import { FAQInteractive } from './faq-interactive.mjs';
|
|
7
8
|
import { responsiveSection } from './section-layout.mjs';
|
|
@@ -20,7 +21,7 @@ function FAQ(_a) {
|
|
|
20
21
|
answer: richText(t, `items.${index}.answer`)
|
|
21
22
|
}))
|
|
22
23
|
};
|
|
23
|
-
return (jsxs("section", { id: "faq", className: cn(responsiveSection, sectionClassName), children: [jsx("h2", { className: "text-3xl md:text-4xl font-bold text-center mb-4", children: data.title }), jsx("p", { className: "text-center text-gray-600 dark:text-gray-400 mb-12 text-base sm:text-lg mx-auto max-w-3xl", children: data.description }), jsx("div", { className: "space-y-6", children: data.items.map((item) => (jsxs("div", { "data-faq-id": item.id, className: "bg-white dark:bg-gray-800/60
|
|
24
|
+
return (jsxs("section", { id: "faq", className: cn(responsiveSection, sectionClassName), children: [jsx("h2", { className: "text-3xl md:text-4xl font-bold text-center mb-4", children: data.title }), jsx("p", { className: "text-center text-gray-600 dark:text-gray-400 mb-12 text-base sm:text-lg mx-auto max-w-3xl", children: data.description }), jsx("div", { className: "space-y-6", children: data.items.map((item) => (jsxs("div", { "data-faq-id": item.id, "data-faq-open": "false", className: cn("bg-white dark:bg-gray-800/60 rounded-xl border border-gray-200 dark:border-gray-700 transition shadow-sm dark:shadow-none hover:border-current focus-within:border-current", themeIconColor), children: [jsxs("button", { className: "w-full p-6 flex items-center justify-between text-left focus:outline-none", "data-faq-toggle": item.id, type: "button", "aria-expanded": "false", "aria-controls": `${item.id}-content`, children: [jsx("span", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: item.question }), jsx("svg", { className: "w-6 h-6 text-gray-400 ml-2 transition-transform duration-200", "data-faq-icon": item.id, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })] }), jsx("div", { id: `${item.id}-content`, className: "px-6 pb-6 text-gray-700 dark:text-gray-300 text-base hidden", "data-faq-content": item.id, children: jsx("div", { className: "pt-1", children: item.answer }) })] }, item.id))) }), jsx(FAQInteractive, { data: data })] }));
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -5,9 +5,14 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
5
5
|
var swiperReact = require('../../node_modules/.pnpm/swiper@12.1.2/node_modules/swiper/swiper-react.js');
|
|
6
6
|
var pagination = require('../../node_modules/.pnpm/swiper@12.1.2/node_modules/swiper/modules/pagination.js');
|
|
7
7
|
var Image = require('next/image');
|
|
8
|
+
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
8
9
|
|
|
10
|
+
const swiperThemeStyle = {
|
|
11
|
+
"--gallery-swiper-bullet-active-color": lib.themeSvgIconColor,
|
|
12
|
+
"--swiper-theme-color": lib.themeSvgIconColor,
|
|
13
|
+
};
|
|
9
14
|
function GalleryMobileSwiper({ items }) {
|
|
10
|
-
return (jsxRuntime.jsx("div", { className: "block sm:hidden px-4", children: jsxRuntime.jsx("div", { className: "w-full overflow-hidden", style: { maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }, children: jsxRuntime.jsx(swiperReact.Swiper, { modules: [pagination.default], pagination: { clickable: true }, spaceBetween: 12, slidesPerView: 1, loop: true, grabCursor: true, className: "gallery-mobile-swiper rounded-2xl", children: items.map((item) => (jsxRuntime.jsx(swiperReact.SwiperSlide, { children: jsxRuntime.jsxs("div", { className: "relative w-full pb-[100%] bg-gray-100", children: [jsxRuntime.jsx(Image, { src: item.url, alt: item.altMsg, fill: true, sizes: "(max-width: 600px) min(100vw-48px, 350px)", className: "object-cover", "data-gallery-image": item.id }), jsxRuntime.jsx("button", { className: "absolute bottom-4 right-4 p-3 rounded-full bg-black/60 hover:bg-black/80 text-white transition-all z-10", "data-gallery-download": item.id, "aria-label": `Download ${item.altMsg}`, children: jsxRuntime.jsx("svg", { className: "h-5 w-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }) })] }) }, item.id))) }) }) }));
|
|
15
|
+
return (jsxRuntime.jsx("div", { className: "block sm:hidden px-4", children: jsxRuntime.jsx("div", { className: "w-full overflow-hidden", style: { maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }, children: jsxRuntime.jsx(swiperReact.Swiper, { modules: [pagination.default], pagination: { clickable: true }, spaceBetween: 12, slidesPerView: 1, loop: true, grabCursor: true, className: "gallery-mobile-swiper rounded-2xl", style: swiperThemeStyle, children: items.map((item) => (jsxRuntime.jsx(swiperReact.SwiperSlide, { children: jsxRuntime.jsxs("div", { className: "relative w-full pb-[100%] bg-gray-100", children: [jsxRuntime.jsx(Image, { src: item.url, alt: item.altMsg, fill: true, sizes: "(max-width: 600px) min(100vw-48px, 350px)", className: "object-cover", "data-gallery-image": item.id }), jsxRuntime.jsx("button", { className: "absolute bottom-4 right-4 p-3 rounded-full bg-black/60 hover:bg-black/80 text-white transition-all z-10", "data-gallery-download": item.id, "aria-label": `Download ${item.altMsg}`, children: jsxRuntime.jsx("svg", { className: "h-5 w-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }) })] }) }, item.id))) }) }) }));
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
exports.GalleryMobileSwiper = GalleryMobileSwiper;
|
|
@@ -3,9 +3,14 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
3
3
|
import { Swiper, SwiperSlide } from '../../node_modules/.pnpm/swiper@12.1.2/node_modules/swiper/swiper-react.mjs';
|
|
4
4
|
import Pagination from '../../node_modules/.pnpm/swiper@12.1.2/node_modules/swiper/modules/pagination.mjs';
|
|
5
5
|
import Image from 'next/image';
|
|
6
|
+
import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
6
7
|
|
|
8
|
+
const swiperThemeStyle = {
|
|
9
|
+
"--gallery-swiper-bullet-active-color": themeSvgIconColor,
|
|
10
|
+
"--swiper-theme-color": themeSvgIconColor,
|
|
11
|
+
};
|
|
7
12
|
function GalleryMobileSwiper({ items }) {
|
|
8
|
-
return (jsx("div", { className: "block sm:hidden px-4", children: jsx("div", { className: "w-full overflow-hidden", style: { maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }, children: jsx(Swiper, { modules: [Pagination], pagination: { clickable: true }, spaceBetween: 12, slidesPerView: 1, loop: true, grabCursor: true, className: "gallery-mobile-swiper rounded-2xl", children: items.map((item) => (jsx(SwiperSlide, { children: jsxs("div", { className: "relative w-full pb-[100%] bg-gray-100", children: [jsx(Image, { src: item.url, alt: item.altMsg, fill: true, sizes: "(max-width: 600px) min(100vw-48px, 350px)", className: "object-cover", "data-gallery-image": item.id }), jsx("button", { className: "absolute bottom-4 right-4 p-3 rounded-full bg-black/60 hover:bg-black/80 text-white transition-all z-10", "data-gallery-download": item.id, "aria-label": `Download ${item.altMsg}`, children: jsx("svg", { className: "h-5 w-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }) })] }) }, item.id))) }) }) }));
|
|
13
|
+
return (jsx("div", { className: "block sm:hidden px-4", children: jsx("div", { className: "w-full overflow-hidden", style: { maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }, children: jsx(Swiper, { modules: [Pagination], pagination: { clickable: true }, spaceBetween: 12, slidesPerView: 1, loop: true, grabCursor: true, className: "gallery-mobile-swiper rounded-2xl", style: swiperThemeStyle, children: items.map((item) => (jsx(SwiperSlide, { children: jsxs("div", { className: "relative w-full pb-[100%] bg-gray-100", children: [jsx(Image, { src: item.url, alt: item.altMsg, fill: true, sizes: "(max-width: 600px) min(100vw-48px, 350px)", className: "object-cover", "data-gallery-image": item.id }), jsx("button", { className: "absolute bottom-4 right-4 p-3 rounded-full bg-black/60 hover:bg-black/80 text-white transition-all z-10", "data-gallery-download": item.id, "aria-label": `Download ${item.altMsg}`, children: jsx("svg", { className: "h-5 w-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }) })] }) }, item.id))) }) }) }));
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export { GalleryMobileSwiper };
|
package/dist/main/loading.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
interface LoadingProps {
|
|
2
|
+
themeColor?: string;
|
|
3
|
+
compact?: boolean;
|
|
4
|
+
className?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
labelClassName?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function Loading({ themeColor, compact, className, label, labelClassName, }?: LoadingProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
package/dist/main/loading.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var utils = require('@windrun-huaiin/lib/utils');
|
|
6
|
+
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
5
7
|
|
|
6
8
|
const NUM_ROWS = 15;
|
|
7
9
|
const NUM_COLS = 15;
|
|
@@ -9,20 +11,44 @@ const DOT_SIZE = 6; // px, dot diameter
|
|
|
9
11
|
const SPACING = 12; // px, space between dot centers
|
|
10
12
|
const ANIMATION_DURATION = 1.8; // seconds
|
|
11
13
|
const STAGGER_DELAY_FACTOR = 0.08; // seconds, delay per unit of distance from center
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'#
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
function clampChannel(value) {
|
|
15
|
+
return Math.max(0, Math.min(255, Math.round(value)));
|
|
16
|
+
}
|
|
17
|
+
function hexToRgb(hex) {
|
|
18
|
+
const normalized = hex.replace('#', '').trim();
|
|
19
|
+
const fullHex = normalized.length === 3
|
|
20
|
+
? normalized.split('').map((char) => `${char}${char}`).join('')
|
|
21
|
+
: normalized;
|
|
22
|
+
if (!/^[0-9a-fA-F]{6}$/.test(fullHex)) {
|
|
23
|
+
return { r: 172, g: 98, b: 253 };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
r: Number.parseInt(fullHex.slice(0, 2), 16),
|
|
27
|
+
g: Number.parseInt(fullHex.slice(2, 4), 16),
|
|
28
|
+
b: Number.parseInt(fullHex.slice(4, 6), 16),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function shiftColor(hex, offset) {
|
|
32
|
+
var _a, _b, _c;
|
|
33
|
+
const { r, g, b } = hexToRgb(hex);
|
|
34
|
+
return `rgb(${clampChannel(r + ((_a = offset.r) !== null && _a !== void 0 ? _a : 0))}, ${clampChannel(g + ((_b = offset.g) !== null && _b !== void 0 ? _b : 0))}, ${clampChannel(b + ((_c = offset.b) !== null && _c !== void 0 ? _c : 0))})`;
|
|
35
|
+
}
|
|
36
|
+
function createLoadingPalette(baseHex) {
|
|
37
|
+
return [
|
|
38
|
+
shiftColor(baseHex, { r: 0, g: 0, b: 0 }),
|
|
39
|
+
shiftColor(baseHex, { r: 18, g: -10, b: 22 }),
|
|
40
|
+
shiftColor(baseHex, { r: 32, g: -18, b: 30 }),
|
|
41
|
+
shiftColor(baseHex, { r: 54, g: -30, b: 8 }),
|
|
42
|
+
shiftColor(baseHex, { r: 28, g: 4, b: 10 }),
|
|
43
|
+
shiftColor(baseHex, { r: 16, g: -6, b: 26 }),
|
|
44
|
+
shiftColor(baseHex, { r: 6, g: 14, b: -12 }),
|
|
45
|
+
shiftColor(baseHex, { r: -10, g: 8, b: 16 }),
|
|
46
|
+
shiftColor(baseHex, { r: -18, g: -6, b: 24 }),
|
|
47
|
+
shiftColor(baseHex, { r: -24, g: -14, b: 6 }),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function Loading({ themeColor = lib.themeSvgIconColor, compact = false, className, label = 'Loading...', labelClassName, } = {}) {
|
|
51
|
+
const colors = createLoadingPalette(themeColor);
|
|
26
52
|
const dots = [];
|
|
27
53
|
const centerX = (NUM_COLS - 1) / 2;
|
|
28
54
|
const centerY = (NUM_ROWS - 1) / 2;
|
|
@@ -37,14 +63,14 @@ function Loading() {
|
|
|
37
63
|
// Animation delay based on distance, creating a ripple effect
|
|
38
64
|
delay: distance * STAGGER_DELAY_FACTOR,
|
|
39
65
|
// Color selection based on distance rings
|
|
40
|
-
color:
|
|
66
|
+
color: colors[Math.floor(distance) % colors.length],
|
|
41
67
|
});
|
|
42
68
|
}
|
|
43
69
|
}
|
|
44
70
|
// Calculate the total width and height of the dot container
|
|
45
71
|
const containerWidth = (NUM_COLS - 1) * SPACING + DOT_SIZE;
|
|
46
72
|
const containerHeight = (NUM_ROWS - 1) * SPACING + DOT_SIZE;
|
|
47
|
-
return (jsxRuntime.jsx("div", { className:
|
|
73
|
+
return (jsxRuntime.jsx("div", { className: utils.cn('flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900', compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen', className), children: jsxRuntime.jsxs("div", { style: {
|
|
48
74
|
width: containerWidth,
|
|
49
75
|
height: containerHeight,
|
|
50
76
|
position: 'relative',
|
|
@@ -65,7 +91,7 @@ function Loading() {
|
|
|
65
91
|
animationDelay: `${dot.delay}s`,
|
|
66
92
|
opacity: 0,
|
|
67
93
|
transform: 'scale(0)',
|
|
68
|
-
} }, dot.id))), jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", style: { pointerEvents: 'none' }, children: jsxRuntime.jsx("p", { className:
|
|
94
|
+
} }, dot.id))), jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", style: { pointerEvents: 'none' }, children: jsxRuntime.jsx("p", { className: utils.cn('text-xl font-semibold text-white', labelClassName), children: label }) })] }) }));
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
exports.Loading = Loading;
|
package/dist/main/loading.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
3
5
|
|
|
4
6
|
const NUM_ROWS = 15;
|
|
5
7
|
const NUM_COLS = 15;
|
|
@@ -7,20 +9,44 @@ const DOT_SIZE = 6; // px, dot diameter
|
|
|
7
9
|
const SPACING = 12; // px, space between dot centers
|
|
8
10
|
const ANIMATION_DURATION = 1.8; // seconds
|
|
9
11
|
const STAGGER_DELAY_FACTOR = 0.08; // seconds, delay per unit of distance from center
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
function clampChannel(value) {
|
|
13
|
+
return Math.max(0, Math.min(255, Math.round(value)));
|
|
14
|
+
}
|
|
15
|
+
function hexToRgb(hex) {
|
|
16
|
+
const normalized = hex.replace('#', '').trim();
|
|
17
|
+
const fullHex = normalized.length === 3
|
|
18
|
+
? normalized.split('').map((char) => `${char}${char}`).join('')
|
|
19
|
+
: normalized;
|
|
20
|
+
if (!/^[0-9a-fA-F]{6}$/.test(fullHex)) {
|
|
21
|
+
return { r: 172, g: 98, b: 253 };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
r: Number.parseInt(fullHex.slice(0, 2), 16),
|
|
25
|
+
g: Number.parseInt(fullHex.slice(2, 4), 16),
|
|
26
|
+
b: Number.parseInt(fullHex.slice(4, 6), 16),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function shiftColor(hex, offset) {
|
|
30
|
+
var _a, _b, _c;
|
|
31
|
+
const { r, g, b } = hexToRgb(hex);
|
|
32
|
+
return `rgb(${clampChannel(r + ((_a = offset.r) !== null && _a !== void 0 ? _a : 0))}, ${clampChannel(g + ((_b = offset.g) !== null && _b !== void 0 ? _b : 0))}, ${clampChannel(b + ((_c = offset.b) !== null && _c !== void 0 ? _c : 0))})`;
|
|
33
|
+
}
|
|
34
|
+
function createLoadingPalette(baseHex) {
|
|
35
|
+
return [
|
|
36
|
+
shiftColor(baseHex, { r: 0, g: 0, b: 0 }),
|
|
37
|
+
shiftColor(baseHex, { r: 18, g: -10, b: 22 }),
|
|
38
|
+
shiftColor(baseHex, { r: 32, g: -18, b: 30 }),
|
|
39
|
+
shiftColor(baseHex, { r: 54, g: -30, b: 8 }),
|
|
40
|
+
shiftColor(baseHex, { r: 28, g: 4, b: 10 }),
|
|
41
|
+
shiftColor(baseHex, { r: 16, g: -6, b: 26 }),
|
|
42
|
+
shiftColor(baseHex, { r: 6, g: 14, b: -12 }),
|
|
43
|
+
shiftColor(baseHex, { r: -10, g: 8, b: 16 }),
|
|
44
|
+
shiftColor(baseHex, { r: -18, g: -6, b: 24 }),
|
|
45
|
+
shiftColor(baseHex, { r: -24, g: -14, b: 6 }),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
function Loading({ themeColor = themeSvgIconColor, compact = false, className, label = 'Loading...', labelClassName, } = {}) {
|
|
49
|
+
const colors = createLoadingPalette(themeColor);
|
|
24
50
|
const dots = [];
|
|
25
51
|
const centerX = (NUM_COLS - 1) / 2;
|
|
26
52
|
const centerY = (NUM_ROWS - 1) / 2;
|
|
@@ -35,14 +61,14 @@ function Loading() {
|
|
|
35
61
|
// Animation delay based on distance, creating a ripple effect
|
|
36
62
|
delay: distance * STAGGER_DELAY_FACTOR,
|
|
37
63
|
// Color selection based on distance rings
|
|
38
|
-
color:
|
|
64
|
+
color: colors[Math.floor(distance) % colors.length],
|
|
39
65
|
});
|
|
40
66
|
}
|
|
41
67
|
}
|
|
42
68
|
// Calculate the total width and height of the dot container
|
|
43
69
|
const containerWidth = (NUM_COLS - 1) * SPACING + DOT_SIZE;
|
|
44
70
|
const containerHeight = (NUM_ROWS - 1) * SPACING + DOT_SIZE;
|
|
45
|
-
return (jsx("div", { className:
|
|
71
|
+
return (jsx("div", { className: cn('flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900', compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen', className), children: jsxs("div", { style: {
|
|
46
72
|
width: containerWidth,
|
|
47
73
|
height: containerHeight,
|
|
48
74
|
position: 'relative',
|
|
@@ -63,7 +89,7 @@ function Loading() {
|
|
|
63
89
|
animationDelay: `${dot.delay}s`,
|
|
64
90
|
opacity: 0,
|
|
65
91
|
transform: 'scale(0)',
|
|
66
|
-
} }, dot.id))), jsx("div", { className: "absolute inset-0 flex items-center justify-center", style: { pointerEvents: 'none' }, children: jsx("p", { className:
|
|
92
|
+
} }, dot.id))), jsx("div", { className: "absolute inset-0 flex items-center justify-center", style: { pointerEvents: 'none' }, children: jsx("p", { className: cn('text-xl font-semibold text-white', labelClassName), children: label }) })] }) }));
|
|
67
93
|
}
|
|
68
94
|
|
|
69
95
|
export { Loading };
|
|
@@ -328,7 +328,7 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
|
|
|
328
328
|
const showBillingSubtitle = plan.showBillingSubTitle !== false;
|
|
329
329
|
const hasDiscount = !!pricing.discountPercent && !!pricing.originalAmount;
|
|
330
330
|
// 移动端宽度样式
|
|
331
|
-
return (jsxRuntime.jsxs("div", { "data-price-plan": planKey, className: utils.cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none w-[85vw] max-w-[360px]', 'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0', 'hover:border-2 hover:border-
|
|
331
|
+
return (jsxRuntime.jsxs("div", { "data-price-plan": planKey, className: utils.cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none w-[85vw] max-w-[360px]', 'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0', 'hover:border-2 hover:border-current', 'focus-within:border-2 focus-within:border-current', lib.themeIconColor), style: { minHeight: maxFeaturesCount * (isTouchDevice ? 86 : 100) }, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsxRuntime.jsx("span", { className: "text-lg md:text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsxRuntime.jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), jsxRuntime.jsxs("div", { className: "flex flex-col items-start w-full", "data-price-container": planKey, children: [jsxRuntime.jsxs("div", { className: "flex items-end gap-2", children: [jsxRuntime.jsx("span", { className: "text-3xl md:text-4xl font-extrabold text-gray-900 dark:text-gray-100", "data-price-value": planKey, children: pricing.amount === 0 ? 'Free' : `${data.currency}${pricing.amount}` }), pricing.amount > 0 && (jsxRuntime.jsx("span", { className: "text-base md:text-lg text-gray-700 dark:text-gray-300 font-medium mb-1", "data-price-unit": planKey, children: (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.unit) || '/month' }))] }), jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row items-start md:items-center gap-1 md:gap-2 min-h-[28px] mt-1", children: [hasDiscount && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("span", { className: "text-sm md:text-base text-gray-400 line-through", "data-price-original": planKey, children: [data.currency, pricing.originalAmount] }), (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.discountText) && (jsxRuntime.jsx("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", "data-price-discount": planKey, children: selectedBillingOption.discountText.replace('{percent}', String(pricing.discountPercent)) }))] })), jsxRuntime.jsx("div", { className: utils.cn('flex items-center gap-2 text-[11px] md:text-xs', !showBillingSubtitle && 'opacity-0 select-none'), "data-price-subtitle": planKey, children: showBillingSubtitle && billingType === 'onetime' ? (
|
|
332
332
|
// OneTime 模式下的特殊处理:普通文本 + 带样式的产品副标题
|
|
333
333
|
jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.subTitle) && (jsxRuntime.jsx("span", { className: "text-[11px] md:text-xs text-gray-700 dark:text-gray-300 font-medium", children: selectedBillingOption.subTitle })), plan.subtitle && (jsxRuntime.jsxs("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", children: ["+", plan.subtitle] }))] })) : (
|
|
334
334
|
// 其他模式下保持原逻辑
|
|
@@ -9,7 +9,7 @@ import { MoneyPriceButton } from './money-price-button.mjs';
|
|
|
9
9
|
import { getActiveProviderConfigUtil, getProductPricing } from './money-price-config-util.mjs';
|
|
10
10
|
import { UserState } from './money-price-types.mjs';
|
|
11
11
|
import { redirectToCustomerPortal } from './customer-portal.mjs';
|
|
12
|
-
import { themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
|
|
12
|
+
import { themeButtonGradientClass, themeButtonGradientHoverClass, themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
13
13
|
|
|
14
14
|
const PLAN_KEYS = ['F1', 'P2', 'U3'];
|
|
15
15
|
function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal = false, enabledBillingTypes, enableSubscriptionUpgrade = true, initialBillingType, disableAutoDetectBilling = false, initUserContext, }) {
|
|
@@ -326,7 +326,7 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
|
|
|
326
326
|
const showBillingSubtitle = plan.showBillingSubTitle !== false;
|
|
327
327
|
const hasDiscount = !!pricing.discountPercent && !!pricing.originalAmount;
|
|
328
328
|
// 移动端宽度样式
|
|
329
|
-
return (jsxs("div", { "data-price-plan": planKey, className: cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none w-[85vw] max-w-[360px]', 'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0', 'hover:border-2 hover:border-
|
|
329
|
+
return (jsxs("div", { "data-price-plan": planKey, className: cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none w-[85vw] max-w-[360px]', 'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0', 'hover:border-2 hover:border-current', 'focus-within:border-2 focus-within:border-current', themeIconColor), style: { minHeight: maxFeaturesCount * (isTouchDevice ? 86 : 100) }, children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("span", { className: "text-lg md:text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), jsxs("div", { className: "flex flex-col items-start w-full", "data-price-container": planKey, children: [jsxs("div", { className: "flex items-end gap-2", children: [jsx("span", { className: "text-3xl md:text-4xl font-extrabold text-gray-900 dark:text-gray-100", "data-price-value": planKey, children: pricing.amount === 0 ? 'Free' : `${data.currency}${pricing.amount}` }), pricing.amount > 0 && (jsx("span", { className: "text-base md:text-lg text-gray-700 dark:text-gray-300 font-medium mb-1", "data-price-unit": planKey, children: (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.unit) || '/month' }))] }), jsxs("div", { className: "flex flex-col md:flex-row items-start md:items-center gap-1 md:gap-2 min-h-[28px] mt-1", children: [hasDiscount && (jsxs(Fragment, { children: [jsxs("span", { className: "text-sm md:text-base text-gray-400 line-through", "data-price-original": planKey, children: [data.currency, pricing.originalAmount] }), (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.discountText) && (jsx("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", "data-price-discount": planKey, children: selectedBillingOption.discountText.replace('{percent}', String(pricing.discountPercent)) }))] })), jsx("div", { className: cn('flex items-center gap-2 text-[11px] md:text-xs', !showBillingSubtitle && 'opacity-0 select-none'), "data-price-subtitle": planKey, children: showBillingSubtitle && billingType === 'onetime' ? (
|
|
330
330
|
// OneTime 模式下的特殊处理:普通文本 + 带样式的产品副标题
|
|
331
331
|
jsxs(Fragment, { children: [(selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.subTitle) && (jsx("span", { className: "text-[11px] md:text-xs text-gray-700 dark:text-gray-300 font-medium", children: selectedBillingOption.subTitle })), plan.subtitle && (jsxs("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", children: ["+", plan.subtitle] }))] })) : (
|
|
332
332
|
// 其他模式下保持原逻辑
|
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
var NProgress = require('nprogress');
|
|
5
5
|
var navigation = require('next/navigation');
|
|
6
6
|
var React = require('react');
|
|
7
|
+
var lib = require('@windrun-huaiin/base-ui/lib');
|
|
7
8
|
|
|
8
9
|
// remove NProgress progress bar spinner circle
|
|
9
10
|
NProgress.configure({ showSpinner: false });
|
|
10
11
|
function NProgressBar() {
|
|
11
12
|
const pathname = navigation.usePathname();
|
|
12
13
|
const previousPath = React.useRef(pathname);
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
document.documentElement.style.setProperty('--nprogress-bar-color', lib.themeSvgIconColor);
|
|
16
|
+
}, []);
|
|
13
17
|
React.useEffect(() => {
|
|
14
18
|
if (previousPath.current !== pathname) {
|
|
15
19
|
NProgress.start();
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
import NProgress from 'nprogress';
|
|
3
3
|
import { usePathname } from 'next/navigation';
|
|
4
4
|
import { useRef, useEffect } from 'react';
|
|
5
|
+
import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
5
6
|
|
|
6
7
|
// remove NProgress progress bar spinner circle
|
|
7
8
|
NProgress.configure({ showSpinner: false });
|
|
8
9
|
function NProgressBar() {
|
|
9
10
|
const pathname = usePathname();
|
|
10
11
|
const previousPath = useRef(pathname);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
document.documentElement.style.setProperty('--nprogress-bar-color', themeSvgIconColor);
|
|
14
|
+
}, []);
|
|
11
15
|
useEffect(() => {
|
|
12
16
|
if (previousPath.current !== pathname) {
|
|
13
17
|
NProgress.start();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/third-ui",
|
|
3
|
-
"version": "13.1.
|
|
3
|
+
"version": "13.1.2",
|
|
4
4
|
"description": "Third-party integrated UI components for windrun-huaiin projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"react-medium-image-zoom": "^5.4.1",
|
|
87
87
|
"swiper": "^12.1.2",
|
|
88
88
|
"zod": "^4.3.6",
|
|
89
|
-
"@windrun-huaiin/
|
|
90
|
-
"@windrun-huaiin/
|
|
89
|
+
"@windrun-huaiin/base-ui": "^13.1.0",
|
|
90
|
+
"@windrun-huaiin/lib": "^13.0.0"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"clsx": "^2.1.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
4
|
|
|
5
5
|
interface FAQData {
|
|
6
6
|
title: string;
|
|
@@ -13,56 +13,40 @@ interface FAQData {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function FAQInteractive({ data }: { data: FAQData }) {
|
|
16
|
-
const [openStates, setOpenStates] = useState<Record<string, boolean>>({});
|
|
17
|
-
|
|
18
16
|
useEffect(() => {
|
|
19
|
-
|
|
17
|
+
const cleanups: Array<() => void> = [];
|
|
18
|
+
|
|
20
19
|
data.items.forEach((item) => {
|
|
21
20
|
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`) as HTMLButtonElement;
|
|
22
21
|
const contentDiv = document.querySelector(`[data-faq-content="${item.id}"]`) as HTMLDivElement;
|
|
23
22
|
const iconSvg = document.querySelector(`[data-faq-icon="${item.id}"]`) as SVGElement;
|
|
23
|
+
const cardDiv = document.querySelector(`[data-faq-id="${item.id}"]`) as HTMLDivElement;
|
|
24
|
+
|
|
25
|
+
if (toggleButton && contentDiv && iconSvg && cardDiv) {
|
|
26
|
+
const syncOpenState = (isOpen: boolean) => {
|
|
27
|
+
contentDiv.classList.toggle('hidden', !isOpen);
|
|
28
|
+
toggleButton.setAttribute('aria-expanded', String(isOpen));
|
|
29
|
+
iconSvg.style.transform = isOpen ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
30
|
+
cardDiv.setAttribute('data-faq-open', String(isOpen));
|
|
31
|
+
};
|
|
24
32
|
|
|
25
|
-
if (toggleButton && contentDiv && iconSvg) {
|
|
26
33
|
const handleClick = () => {
|
|
27
|
-
const isOpen =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// Update state
|
|
31
|
-
setOpenStates(prev => ({
|
|
32
|
-
...prev,
|
|
33
|
-
[item.id]: newOpenState
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
// Update DOM
|
|
37
|
-
if (newOpenState) {
|
|
38
|
-
contentDiv.classList.remove('hidden');
|
|
39
|
-
toggleButton.setAttribute('aria-expanded', 'true');
|
|
40
|
-
iconSvg.style.transform = 'rotate(90deg)';
|
|
41
|
-
} else {
|
|
42
|
-
contentDiv.classList.add('hidden');
|
|
43
|
-
toggleButton.setAttribute('aria-expanded', 'false');
|
|
44
|
-
iconSvg.style.transform = 'rotate(0deg)';
|
|
45
|
-
}
|
|
34
|
+
const isOpen = toggleButton.getAttribute('aria-expanded') === 'true';
|
|
35
|
+
syncOpenState(!isOpen);
|
|
46
36
|
};
|
|
47
37
|
|
|
38
|
+
syncOpenState(toggleButton.getAttribute('aria-expanded') === 'true');
|
|
48
39
|
toggleButton.addEventListener('click', handleClick);
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
cleanups.push(() => {
|
|
41
|
+
toggleButton.removeEventListener('click', handleClick);
|
|
42
|
+
});
|
|
51
43
|
}
|
|
52
44
|
});
|
|
53
45
|
|
|
54
|
-
// Cleanup event listeners
|
|
55
46
|
return () => {
|
|
56
|
-
|
|
57
|
-
const toggleButton = document.querySelector(`[data-faq-toggle="${item.id}"]`) as HTMLButtonElement;
|
|
58
|
-
if (toggleButton) {
|
|
59
|
-
// Remove all event listeners by cloning the element
|
|
60
|
-
const newButton = toggleButton.cloneNode(true);
|
|
61
|
-
toggleButton.parentNode?.replaceChild(newButton, toggleButton);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
47
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
64
48
|
};
|
|
65
|
-
}, [data
|
|
49
|
+
}, [data.items]);
|
|
66
50
|
|
|
67
51
|
return null; // Progressive enhancement - no additional DOM rendering
|
|
68
|
-
}
|
|
52
|
+
}
|
package/src/main/faq.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getTranslations } from 'next-intl/server';
|
|
2
2
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
3
|
+
import { themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
3
4
|
import { richText } from './rich-text-expert';
|
|
4
5
|
import { FAQInteractive } from './faq-interactive';
|
|
5
6
|
import { responsiveSection } from './section-layout';
|
|
@@ -52,12 +53,18 @@ export async function FAQ({
|
|
|
52
53
|
<div
|
|
53
54
|
key={item.id}
|
|
54
55
|
data-faq-id={item.id}
|
|
55
|
-
|
|
56
|
+
data-faq-open="false"
|
|
57
|
+
className={cn(
|
|
58
|
+
"bg-white dark:bg-gray-800/60 rounded-xl border border-gray-200 dark:border-gray-700 transition shadow-sm dark:shadow-none hover:border-current focus-within:border-current",
|
|
59
|
+
themeIconColor
|
|
60
|
+
)}
|
|
56
61
|
>
|
|
57
62
|
<button
|
|
58
|
-
className="w-full flex items-center justify-between text-left focus:outline-none"
|
|
63
|
+
className="w-full p-6 flex items-center justify-between text-left focus:outline-none"
|
|
59
64
|
data-faq-toggle={item.id}
|
|
65
|
+
type="button"
|
|
60
66
|
aria-expanded="false"
|
|
67
|
+
aria-controls={`${item.id}-content`}
|
|
61
68
|
>
|
|
62
69
|
<span className="text-lg font-semibold text-gray-900 dark:text-gray-100">{item.question}</span>
|
|
63
70
|
<svg
|
|
@@ -71,10 +78,13 @@ export async function FAQ({
|
|
|
71
78
|
</svg>
|
|
72
79
|
</button>
|
|
73
80
|
<div
|
|
74
|
-
|
|
81
|
+
id={`${item.id}-content`}
|
|
82
|
+
className="px-6 pb-6 text-gray-700 dark:text-gray-300 text-base hidden"
|
|
75
83
|
data-faq-content={item.id}
|
|
76
84
|
>
|
|
85
|
+
<div className="pt-1">
|
|
77
86
|
{item.answer}
|
|
87
|
+
</div>
|
|
78
88
|
</div>
|
|
79
89
|
</div>
|
|
80
90
|
))}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import type { CSSProperties } from "react";
|
|
3
4
|
import { Swiper, SwiperSlide } from "swiper/react";
|
|
4
5
|
import { Pagination } from "swiper/modules";
|
|
5
6
|
import Image from "next/image";
|
|
7
|
+
import { themeSvgIconColor } from "@windrun-huaiin/base-ui/lib";
|
|
6
8
|
import { GalleryItem } from "./gallery-types";
|
|
7
9
|
|
|
8
10
|
interface Props {
|
|
9
11
|
items: GalleryItem[];
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
const swiperThemeStyle = {
|
|
15
|
+
"--gallery-swiper-bullet-active-color": themeSvgIconColor,
|
|
16
|
+
"--swiper-theme-color": themeSvgIconColor,
|
|
17
|
+
} as CSSProperties;
|
|
18
|
+
|
|
12
19
|
export function GalleryMobileSwiper({ items }: Props) {
|
|
13
20
|
return (
|
|
14
21
|
<div className="block sm:hidden px-4">
|
|
15
22
|
{/* 外层容器:强制 maxWidth,防止任何溢出 */}
|
|
16
|
-
<div
|
|
23
|
+
<div
|
|
24
|
+
className="w-full overflow-hidden"
|
|
17
25
|
style={{ maxWidth: "min(calc(100vw - 48px), 350px)", margin: "0 auto" }}
|
|
18
26
|
>
|
|
19
27
|
<Swiper
|
|
@@ -24,6 +32,7 @@ export function GalleryMobileSwiper({ items }: Props) {
|
|
|
24
32
|
loop={true}
|
|
25
33
|
grabCursor={true}
|
|
26
34
|
className="gallery-mobile-swiper rounded-2xl"
|
|
35
|
+
style={swiperThemeStyle}
|
|
27
36
|
>
|
|
28
37
|
{items.map((item) => (
|
|
29
38
|
<SwiperSlide key={item.id}>
|
package/src/main/loading.tsx
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { cn } from '@windrun-huaiin/lib/utils';
|
|
4
|
+
import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
5
|
+
|
|
3
6
|
const NUM_ROWS = 15;
|
|
4
7
|
const NUM_COLS = 15;
|
|
5
8
|
const DOT_SIZE = 6; // px, dot diameter
|
|
@@ -7,21 +10,64 @@ const SPACING = 12; // px, space between dot centers
|
|
|
7
10
|
const ANIMATION_DURATION = 1.8; // seconds
|
|
8
11
|
const STAGGER_DELAY_FACTOR = 0.08; // seconds, delay per unit of distance from center
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
function clampChannel(value: number) {
|
|
14
|
+
return Math.max(0, Math.min(255, Math.round(value)));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hexToRgb(hex: string) {
|
|
18
|
+
const normalized = hex.replace('#', '').trim();
|
|
19
|
+
const fullHex = normalized.length === 3
|
|
20
|
+
? normalized.split('').map((char) => `${char}${char}`).join('')
|
|
21
|
+
: normalized;
|
|
22
|
+
|
|
23
|
+
if (!/^[0-9a-fA-F]{6}$/.test(fullHex)) {
|
|
24
|
+
return { r: 172, g: 98, b: 253 };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
r: Number.parseInt(fullHex.slice(0, 2), 16),
|
|
29
|
+
g: Number.parseInt(fullHex.slice(2, 4), 16),
|
|
30
|
+
b: Number.parseInt(fullHex.slice(4, 6), 16),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function shiftColor(hex: string, offset: { r?: number; g?: number; b?: number }) {
|
|
35
|
+
const { r, g, b } = hexToRgb(hex);
|
|
36
|
+
|
|
37
|
+
return `rgb(${clampChannel(r + (offset.r ?? 0))}, ${clampChannel(g + (offset.g ?? 0))}, ${clampChannel(b + (offset.b ?? 0))})`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createLoadingPalette(baseHex: string) {
|
|
41
|
+
return [
|
|
42
|
+
shiftColor(baseHex, { r: 0, g: 0, b: 0 }),
|
|
43
|
+
shiftColor(baseHex, { r: 18, g: -10, b: 22 }),
|
|
44
|
+
shiftColor(baseHex, { r: 32, g: -18, b: 30 }),
|
|
45
|
+
shiftColor(baseHex, { r: 54, g: -30, b: 8 }),
|
|
46
|
+
shiftColor(baseHex, { r: 28, g: 4, b: 10 }),
|
|
47
|
+
shiftColor(baseHex, { r: 16, g: -6, b: 26 }),
|
|
48
|
+
shiftColor(baseHex, { r: 6, g: 14, b: -12 }),
|
|
49
|
+
shiftColor(baseHex, { r: -10, g: 8, b: 16 }),
|
|
50
|
+
shiftColor(baseHex, { r: -18, g: -6, b: 24 }),
|
|
51
|
+
shiftColor(baseHex, { r: -24, g: -14, b: 6 }),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface LoadingProps {
|
|
56
|
+
themeColor?: string;
|
|
57
|
+
compact?: boolean;
|
|
58
|
+
className?: string;
|
|
59
|
+
label?: string;
|
|
60
|
+
labelClassName?: string;
|
|
61
|
+
}
|
|
23
62
|
|
|
24
|
-
export function Loading(
|
|
63
|
+
export function Loading({
|
|
64
|
+
themeColor = themeSvgIconColor,
|
|
65
|
+
compact = false,
|
|
66
|
+
className,
|
|
67
|
+
label = 'Loading...',
|
|
68
|
+
labelClassName,
|
|
69
|
+
}: LoadingProps = {}) {
|
|
70
|
+
const colors = createLoadingPalette(themeColor);
|
|
25
71
|
const dots = [];
|
|
26
72
|
const centerX = (NUM_COLS - 1) / 2;
|
|
27
73
|
const centerY = (NUM_ROWS - 1) / 2;
|
|
@@ -37,7 +83,7 @@ export function Loading() {
|
|
|
37
83
|
// Animation delay based on distance, creating a ripple effect
|
|
38
84
|
delay: distance * STAGGER_DELAY_FACTOR,
|
|
39
85
|
// Color selection based on distance rings
|
|
40
|
-
color:
|
|
86
|
+
color: colors[Math.floor(distance) % colors.length],
|
|
41
87
|
});
|
|
42
88
|
}
|
|
43
89
|
}
|
|
@@ -47,7 +93,13 @@ export function Loading() {
|
|
|
47
93
|
const containerHeight = (NUM_ROWS - 1) * SPACING + DOT_SIZE;
|
|
48
94
|
|
|
49
95
|
return (
|
|
50
|
-
<div
|
|
96
|
+
<div
|
|
97
|
+
className={cn(
|
|
98
|
+
'flex flex-col items-center justify-center bg-neutral-100 dark:bg-neutral-900',
|
|
99
|
+
compact ? 'min-h-[250px] rounded-[28px] px-4 py-2' : 'min-h-screen',
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
51
103
|
<div
|
|
52
104
|
style={{
|
|
53
105
|
width: containerWidth,
|
|
@@ -83,11 +135,11 @@ export function Loading() {
|
|
|
83
135
|
className="absolute inset-0 flex items-center justify-center"
|
|
84
136
|
style={{ pointerEvents: 'none' }} // So text doesn't interfere with potential mouse events on dots if any
|
|
85
137
|
>
|
|
86
|
-
<p className=
|
|
87
|
-
|
|
138
|
+
<p className={cn('text-xl font-semibold text-white', labelClassName)}>
|
|
139
|
+
{label}
|
|
88
140
|
</p>
|
|
89
141
|
</div>
|
|
90
142
|
</div>
|
|
91
143
|
</div>
|
|
92
144
|
);
|
|
93
|
-
}
|
|
145
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useClerk } from '@clerk/nextjs';
|
|
4
4
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
5
5
|
import { useRouter } from 'next/navigation';
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
7
|
useCallback,
|
|
8
8
|
useEffect,
|
|
9
9
|
useMemo,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type UserContext
|
|
19
19
|
} from './money-price-types';
|
|
20
20
|
import { redirectToCustomerPortal } from './customer-portal';
|
|
21
|
-
import { themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
|
|
21
|
+
import { themeButtonGradientClass, themeButtonGradientHoverClass, themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
22
22
|
|
|
23
23
|
type BillingType = string;
|
|
24
24
|
|
|
@@ -450,8 +450,9 @@ export function MoneyPriceInteractive({
|
|
|
450
450
|
className={cn(
|
|
451
451
|
'flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none w-[85vw] max-w-[360px]',
|
|
452
452
|
'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0',
|
|
453
|
-
'hover:border-2 hover:border-
|
|
454
|
-
'focus-within:border-2 focus-within:border-
|
|
453
|
+
'hover:border-2 hover:border-current',
|
|
454
|
+
'focus-within:border-2 focus-within:border-current',
|
|
455
|
+
themeIconColor
|
|
455
456
|
)}
|
|
456
457
|
style={{ minHeight: maxFeaturesCount * (isTouchDevice ? 86 : 100) }}
|
|
457
458
|
>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import NProgress from 'nprogress'
|
|
4
4
|
import { usePathname } from 'next/navigation'
|
|
5
5
|
import { useEffect, useRef } from 'react'
|
|
6
|
+
import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
6
7
|
|
|
7
8
|
// remove NProgress progress bar spinner circle
|
|
8
9
|
NProgress.configure({ showSpinner: false })
|
|
@@ -11,6 +12,10 @@ export function NProgressBar() {
|
|
|
11
12
|
const pathname = usePathname()
|
|
12
13
|
const previousPath = useRef(pathname)
|
|
13
14
|
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
document.documentElement.style.setProperty('--nprogress-bar-color', themeSvgIconColor)
|
|
17
|
+
}, [])
|
|
18
|
+
|
|
14
19
|
useEffect(() => {
|
|
15
20
|
if (previousPath.current !== pathname) {
|
|
16
21
|
NProgress.start()
|
|
@@ -22,4 +27,4 @@ export function NProgressBar() {
|
|
|
22
27
|
}, [pathname])
|
|
23
28
|
|
|
24
29
|
return null
|
|
25
|
-
}
|
|
30
|
+
}
|
package/src/styles/third-ui.css
CHANGED
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
|
|
51
51
|
/* NProgress progress bar style */
|
|
52
52
|
#nprogress .bar {
|
|
53
|
-
background: #AC62FD !important;
|
|
54
|
-
height:
|
|
53
|
+
background: var(--nprogress-bar-color, #AC62FD) !important;
|
|
54
|
+
height: 2px !important;
|
|
55
|
+
box-shadow: 0 0 10px var(--nprogress-bar-color, #AC62FD) !important;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/* @theme {
|
|
@@ -79,7 +80,8 @@ body {
|
|
|
79
80
|
.gallery-mobile-swiper { overflow: hidden !important; }
|
|
80
81
|
|
|
81
82
|
.gallery-mobile-swiper {
|
|
82
|
-
--swiper-
|
|
83
|
+
--gallery-swiper-bullet-active-color: #AC62FD;
|
|
84
|
+
--swiper-theme-color: var(--gallery-swiper-bullet-active-color);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
.gallery-mobile-swiper .swiper-pagination {
|
|
@@ -95,6 +97,6 @@ body {
|
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
.gallery-mobile-swiper .swiper-pagination-bullet-active {
|
|
98
|
-
background: #AC62FD !important;
|
|
100
|
+
background: var(--gallery-swiper-bullet-active-color, #AC62FD) !important;
|
|
99
101
|
transform: scale(1.5);
|
|
100
102
|
}
|