@windrun-huaiin/third-ui 13.1.0 → 13.1.1
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/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/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 };
|
|
@@ -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.1",
|
|
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,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
|
}
|