@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.
@@ -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
- // Progressive enhancement: Add interactivity to existing DOM elements
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
- if (toggleButton && contentDiv && iconSvg) {
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 = openStates[item.id] || false;
17
- const newOpenState = !isOpen;
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
- // Cleanup function will be handled by the effect cleanup
27
+ cleanups.push(() => {
28
+ toggleButton.removeEventListener('click', handleClick);
29
+ });
34
30
  }
35
31
  });
36
- // Cleanup event listeners
37
32
  return () => {
38
- data.items.forEach((item) => {
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, openStates]);
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 { useState, useEffect } from 'react';
2
+ import { useEffect } from 'react';
3
3
 
4
4
  function FAQInteractive({ data }) {
5
- const [openStates, setOpenStates] = useState({});
6
5
  useEffect(() => {
7
- // Progressive enhancement: Add interactivity to existing DOM elements
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
- if (toggleButton && contentDiv && iconSvg) {
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 = openStates[item.id] || false;
15
- const newOpenState = !isOpen;
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
- // Cleanup function will be handled by the effect cleanup
25
+ cleanups.push(() => {
26
+ toggleButton.removeEventListener('click', handleClick);
27
+ });
32
28
  }
33
29
  });
34
- // Cleanup event listeners
35
30
  return () => {
36
- data.items.forEach((item) => {
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, openStates]);
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 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover:border-purple-300 dark:hover:border-purple-500/50 transition shadow-sm dark:shadow-none", children: [jsxRuntime.jsxs("button", { className: "w-full flex items-center justify-between text-left focus:outline-none", "data-faq-toggle": item.id, "aria-expanded": "false", 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", { className: "mt-4 text-gray-700 dark:text-gray-300 text-base hidden", "data-faq-content": item.id, children: item.answer })] }, item.id))) }), jsxRuntime.jsx(faqInteractive.FAQInteractive, { data: data })] }));
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 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover:border-purple-300 dark:hover:border-purple-500/50 transition shadow-sm dark:shadow-none", children: [jsxs("button", { className: "w-full flex items-center justify-between text-left focus:outline-none", "data-faq-toggle": item.id, "aria-expanded": "false", 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", { className: "mt-4 text-gray-700 dark:text-gray-300 text-base hidden", "data-faq-content": item.id, children: item.answer })] }, item.id))) }), jsx(FAQInteractive, { data: data })] }));
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 };
@@ -1 +1,9 @@
1
- export declare function Loading(): import("react/jsx-runtime").JSX.Element;
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 {};
@@ -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
- // Expanded palette for more "dazzling" effect
13
- const COLORS = [
14
- '#AC62FD', // Base Purple
15
- '#C364FA',
16
- '#DD59F7',
17
- '#FF4FF2', // Bright Pink/Magenta
18
- '#F067DD',
19
- '#E26AF8',
20
- '#DA70D6', // Orchid
21
- '#BA55D3', // Medium Orchid
22
- '#9370DB', // Medium Purple
23
- '#8A2BE2' // Blue Violet
24
- ];
25
- function Loading() {
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: COLORS[Math.floor(distance) % COLORS.length],
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: "flex flex-col items-center justify-center min-h-screen bg-neutral-900", children: jsxRuntime.jsxs("div", { style: {
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: "text-xl font-semibold text-white", children: "Loading..." }) })] }) }));
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;
@@ -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
- // Expanded palette for more "dazzling" effect
11
- const COLORS = [
12
- '#AC62FD', // Base Purple
13
- '#C364FA',
14
- '#DD59F7',
15
- '#FF4FF2', // Bright Pink/Magenta
16
- '#F067DD',
17
- '#E26AF8',
18
- '#DA70D6', // Orchid
19
- '#BA55D3', // Medium Orchid
20
- '#9370DB', // Medium Purple
21
- '#8A2BE2' // Blue Violet
22
- ];
23
- function Loading() {
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: COLORS[Math.floor(distance) % COLORS.length],
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: "flex flex-col items-center justify-center min-h-screen bg-neutral-900", children: jsxs("div", { style: {
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: "text-xl font-semibold text-white", children: "Loading..." }) })] }) }));
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-purple-500', 'focus-within:border-2 focus-within:border-purple-500'), 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' ? (
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-purple-500', 'focus-within:border-2 focus-within:border-purple-500'), 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' ? (
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.0",
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/lib": "^13.0.0",
90
- "@windrun-huaiin/base-ui": "^13.1.0"
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 { useState, useEffect } from 'react';
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
- // Progressive enhancement: Add interactivity to existing DOM elements
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 = openStates[item.id] || false;
28
- const newOpenState = !isOpen;
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
- // Cleanup function will be handled by the effect cleanup
40
+ cleanups.push(() => {
41
+ toggleButton.removeEventListener('click', handleClick);
42
+ });
51
43
  }
52
44
  });
53
45
 
54
- // Cleanup event listeners
55
46
  return () => {
56
- data.items.forEach((item) => {
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, openStates]);
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
- className="bg-white dark:bg-gray-800/60 p-6 rounded-xl border border-gray-200 dark:border-gray-700 hover:border-purple-300 dark:hover:border-purple-500/50 transition shadow-sm dark:shadow-none"
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
- className="mt-4 text-gray-700 dark:text-gray-300 text-base hidden"
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 className="w-full overflow-hidden"
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}>
@@ -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
- // Expanded palette for more "dazzling" effect
11
- const COLORS = [
12
- '#AC62FD', // Base Purple
13
- '#C364FA',
14
- '#DD59F7',
15
- '#FF4FF2', // Bright Pink/Magenta
16
- '#F067DD',
17
- '#E26AF8',
18
- '#DA70D6', // Orchid
19
- '#BA55D3', // Medium Orchid
20
- '#9370DB', // Medium Purple
21
- '#8A2BE2' // Blue Violet
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: COLORS[Math.floor(distance) % COLORS.length],
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 className="flex flex-col items-center justify-center min-h-screen bg-neutral-900">
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="text-xl font-semibold text-white">
87
- Loading...
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 React, {
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-purple-500',
454
- 'focus-within:border-2 focus-within:border-purple-500'
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
+ }
@@ -50,8 +50,9 @@
50
50
 
51
51
  /* NProgress progress bar style */
52
52
  #nprogress .bar {
53
- background: #AC62FD !important; /* purple */
54
- height: 1px !important;
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-theme-color: #AC62FD;
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
  }