@windrun-huaiin/third-ui 7.3.9 → 7.3.10

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.js CHANGED
@@ -14,6 +14,7 @@ var faqInteractive = require('./faq-interactive.js');
14
14
  require('next/navigation');
15
15
  require('@clerk/nextjs');
16
16
  require('../clerk/fingerprint/fingerprint-provider.js');
17
+ require('react-dom');
17
18
  require('next-themes');
18
19
  require('fumadocs-core/framework');
19
20
  require('next/link');
package/dist/main/faq.mjs CHANGED
@@ -12,6 +12,7 @@ import { FAQInteractive } from './faq-interactive.mjs';
12
12
  import 'next/navigation';
13
13
  import '@clerk/nextjs';
14
14
  import '../clerk/fingerprint/fingerprint-provider.mjs';
15
+ import 'react-dom';
15
16
  import 'next-themes';
16
17
  import 'fumadocs-core/framework';
17
18
  import 'next/link';
@@ -14,6 +14,7 @@ require('next/navigation');
14
14
  var galleryInteractive = require('./gallery-interactive.js');
15
15
  require('@clerk/nextjs');
16
16
  require('../clerk/fingerprint/fingerprint-provider.js');
17
+ require('react-dom');
17
18
  require('next-themes');
18
19
  require('fumadocs-core/framework');
19
20
  require('next/link');
@@ -12,6 +12,7 @@ import 'next/navigation';
12
12
  import { GalleryInteractive } from './gallery-interactive.mjs';
13
13
  import '@clerk/nextjs';
14
14
  import '../clerk/fingerprint/fingerprint-provider.mjs';
15
+ import 'react-dom';
15
16
  import 'next-themes';
16
17
  import 'fumadocs-core/framework';
17
18
  import 'next/link';
@@ -8,6 +8,7 @@ var React = require('react');
8
8
  var fingerprintProvider = require('../../clerk/fingerprint/fingerprint-provider.js');
9
9
  var utils = require('@windrun-huaiin/lib/utils');
10
10
  var navigation = require('next/navigation');
11
+ var ReactDOM = require('react-dom');
11
12
  var moneyPriceButton = require('./money-price-button.js');
12
13
  var moneyPriceConfigUtil = require('./money-price-config-util.js');
13
14
  var moneyPriceTypes = require('./money-price-types.js');
@@ -131,55 +132,47 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
131
132
  if (priceDiscountElement)
132
133
  priceDiscountElement.style.display = 'none';
133
134
  }
134
- const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`);
135
- if (priceSubtitleElement && plan.showBillingSubTitle !== false) {
136
- const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
137
- priceSubtitleElement.textContent = (billingOption === null || billingOption === void 0 ? void 0 : billingOption.subTitle) || '';
138
- }
139
135
  });
140
136
  }, [config, data]);
141
- // 更新按钮样式
142
- const updateButtonStyles = React.useCallback((newBillingType) => {
143
- const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
144
- const yearlyButton = document.querySelector('[data-billing-button="yearly"]');
145
- const activeClasses = 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm';
146
- const inactiveClasses = 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full';
147
- if (monthlyButton && yearlyButton) {
148
- if (newBillingType === 'monthly') {
149
- monthlyButton.className = utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
150
- yearlyButton.className = utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', inactiveClasses);
151
- }
152
- else {
153
- monthlyButton.className = utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', inactiveClasses);
154
- yearlyButton.className = utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
155
- }
156
- }
157
- }, []);
158
137
  // 更新折扣信息
159
138
  const updateDiscountInfo = React.useCallback((newBillingType) => {
160
139
  const discountInfoElement = document.querySelector('[data-discount-info]');
161
- if (!discountInfoElement)
162
- return;
163
- const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
164
- const providerConfig = moneyPriceConfigUtil.getActiveProviderConfig(config);
165
- let hasDiscount = false;
166
- let discountPercent = 0;
167
- ['pro', 'ultra'].forEach(planKey => {
168
- const product = providerConfig.products[planKey];
169
- const pricing = product.plans[newBillingType];
170
- if (pricing.discountPercent) {
171
- hasDiscount = true;
172
- discountPercent = pricing.discountPercent;
140
+ if (discountInfoElement) {
141
+ const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
142
+ let hasDiscount = false;
143
+ let discountPercent = 0;
144
+ const providerConfig = moneyPriceConfigUtil.getActiveProviderConfig(config);
145
+ ['pro', 'ultra'].forEach(planKey => {
146
+ const pricing = providerConfig.products[planKey].plans[newBillingType];
147
+ if (pricing.discountPercent) {
148
+ hasDiscount = true;
149
+ discountPercent = pricing.discountPercent;
150
+ }
151
+ });
152
+ discountInfoElement.innerHTML = '';
153
+ if (hasDiscount && (billingOption === null || billingOption === void 0 ? void 0 : billingOption.discountText)) {
154
+ const discountBadge = document.createElement('span');
155
+ discountBadge.className = 'px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap';
156
+ discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
157
+ discountInfoElement.appendChild(discountBadge);
173
158
  }
174
- });
175
- discountInfoElement.innerHTML = '';
176
- if (hasDiscount && (billingOption === null || billingOption === void 0 ? void 0 : billingOption.discountText)) {
177
- const discountBadge = document.createElement('span');
178
- discountBadge.className = 'px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap';
179
- discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
180
- discountInfoElement.appendChild(discountBadge);
181
159
  }
182
160
  }, [config, data]);
161
+ // 更新按钮样式
162
+ const updateButtonStyles = React.useCallback((newBillingType) => {
163
+ const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
164
+ const yearlyButton = document.querySelector('[data-billing-button="yearly"]');
165
+ if (monthlyButton) {
166
+ monthlyButton.className = newBillingType === 'monthly'
167
+ ? utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm')
168
+ : utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full');
169
+ }
170
+ if (yearlyButton) {
171
+ yearlyButton.className = newBillingType === 'yearly'
172
+ ? utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm')
173
+ : utils.cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full');
174
+ }
175
+ }, []);
183
176
  // 处理月付/年付切换和 tooltip 功能
184
177
  React.useEffect(() => {
185
178
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
@@ -233,9 +226,17 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
233
226
  }
234
227
  });
235
228
  });
229
+ // Inject buttons into placeholders using createPortal
230
+ data.plans.forEach((plan) => {
231
+ const placeholder = document.querySelector(`[data-button-placeholder="${plan.key}"]`);
232
+ if (placeholder) {
233
+ ReactDOM.createPortal(jsxRuntime.jsx(moneyPriceButton.MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }), placeholder);
234
+ }
235
+ });
236
+ // Initial updates
236
237
  updatePriceDisplay(billingType);
237
238
  updateDiscountInfo(billingType);
238
- data.plans.map((plan) => (jsxRuntime.jsx("div", { "data-button-placeholder": plan.key, children: jsxRuntime.jsx(moneyPriceButton.MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }) }, plan.key)));
239
+ updateButtonStyles(billingType);
239
240
  return () => {
240
241
  if (monthlyButton) {
241
242
  monthlyButton.removeEventListener('click', handleMonthlyClick);
@@ -249,7 +250,7 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
249
250
  element.removeEventListener('mouseleave', handlers.mouseleave);
250
251
  });
251
252
  };
252
- }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo]);
253
+ }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo, userContext, handleLogin, handleUpgrade, isProcessing]);
253
254
  // Tooltip 组件
254
255
  const Tooltip = ({ show, content, x, y }) => {
255
256
  if (!show)
@@ -6,6 +6,7 @@ import { useState, useCallback, useMemo, useEffect } from 'react';
6
6
  import { useFingerprintContextSafe } from '../../clerk/fingerprint/fingerprint-provider.mjs';
7
7
  import { cn } from '@windrun-huaiin/lib/utils';
8
8
  import { useRouter } from 'next/navigation';
9
+ import { createPortal } from 'react-dom';
9
10
  import { MoneyPriceButton } from './money-price-button.mjs';
10
11
  import { getActiveProviderConfig, getProductPricing } from './money-price-config-util.mjs';
11
12
  import { UserState } from './money-price-types.mjs';
@@ -129,55 +130,47 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
129
130
  if (priceDiscountElement)
130
131
  priceDiscountElement.style.display = 'none';
131
132
  }
132
- const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`);
133
- if (priceSubtitleElement && plan.showBillingSubTitle !== false) {
134
- const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
135
- priceSubtitleElement.textContent = (billingOption === null || billingOption === void 0 ? void 0 : billingOption.subTitle) || '';
136
- }
137
133
  });
138
134
  }, [config, data]);
139
- // 更新按钮样式
140
- const updateButtonStyles = useCallback((newBillingType) => {
141
- const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
142
- const yearlyButton = document.querySelector('[data-billing-button="yearly"]');
143
- const activeClasses = 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm';
144
- const inactiveClasses = 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full';
145
- if (monthlyButton && yearlyButton) {
146
- if (newBillingType === 'monthly') {
147
- monthlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
148
- yearlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', inactiveClasses);
149
- }
150
- else {
151
- monthlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', inactiveClasses);
152
- yearlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
153
- }
154
- }
155
- }, []);
156
135
  // 更新折扣信息
157
136
  const updateDiscountInfo = useCallback((newBillingType) => {
158
137
  const discountInfoElement = document.querySelector('[data-discount-info]');
159
- if (!discountInfoElement)
160
- return;
161
- const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
162
- const providerConfig = getActiveProviderConfig(config);
163
- let hasDiscount = false;
164
- let discountPercent = 0;
165
- ['pro', 'ultra'].forEach(planKey => {
166
- const product = providerConfig.products[planKey];
167
- const pricing = product.plans[newBillingType];
168
- if (pricing.discountPercent) {
169
- hasDiscount = true;
170
- discountPercent = pricing.discountPercent;
138
+ if (discountInfoElement) {
139
+ const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
140
+ let hasDiscount = false;
141
+ let discountPercent = 0;
142
+ const providerConfig = getActiveProviderConfig(config);
143
+ ['pro', 'ultra'].forEach(planKey => {
144
+ const pricing = providerConfig.products[planKey].plans[newBillingType];
145
+ if (pricing.discountPercent) {
146
+ hasDiscount = true;
147
+ discountPercent = pricing.discountPercent;
148
+ }
149
+ });
150
+ discountInfoElement.innerHTML = '';
151
+ if (hasDiscount && (billingOption === null || billingOption === void 0 ? void 0 : billingOption.discountText)) {
152
+ const discountBadge = document.createElement('span');
153
+ discountBadge.className = 'px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap';
154
+ discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
155
+ discountInfoElement.appendChild(discountBadge);
171
156
  }
172
- });
173
- discountInfoElement.innerHTML = '';
174
- if (hasDiscount && (billingOption === null || billingOption === void 0 ? void 0 : billingOption.discountText)) {
175
- const discountBadge = document.createElement('span');
176
- discountBadge.className = 'px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap';
177
- discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
178
- discountInfoElement.appendChild(discountBadge);
179
157
  }
180
158
  }, [config, data]);
159
+ // 更新按钮样式
160
+ const updateButtonStyles = useCallback((newBillingType) => {
161
+ const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
162
+ const yearlyButton = document.querySelector('[data-billing-button="yearly"]');
163
+ if (monthlyButton) {
164
+ monthlyButton.className = newBillingType === 'monthly'
165
+ ? cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm')
166
+ : cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full');
167
+ }
168
+ if (yearlyButton) {
169
+ yearlyButton.className = newBillingType === 'yearly'
170
+ ? cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm')
171
+ : cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full');
172
+ }
173
+ }, []);
181
174
  // 处理月付/年付切换和 tooltip 功能
182
175
  useEffect(() => {
183
176
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]');
@@ -231,9 +224,17 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
231
224
  }
232
225
  });
233
226
  });
227
+ // Inject buttons into placeholders using createPortal
228
+ data.plans.forEach((plan) => {
229
+ const placeholder = document.querySelector(`[data-button-placeholder="${plan.key}"]`);
230
+ if (placeholder) {
231
+ createPortal(jsx(MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }), placeholder);
232
+ }
233
+ });
234
+ // Initial updates
234
235
  updatePriceDisplay(billingType);
235
236
  updateDiscountInfo(billingType);
236
- data.plans.map((plan) => (jsx("div", { "data-button-placeholder": plan.key, children: jsx(MoneyPriceButton, { planKey: plan.key, userContext: userContext, billingType: billingType, onLogin: handleLogin, onUpgrade: handleUpgrade, texts: data.buttonTexts, isProcessing: isProcessing }) }, plan.key)));
237
+ updateButtonStyles(billingType);
237
238
  return () => {
238
239
  if (monthlyButton) {
239
240
  monthlyButton.removeEventListener('click', handleMonthlyClick);
@@ -247,7 +248,7 @@ function MoneyPriceInteractive({ data, config, upgradeApiEndpoint, signInPath })
247
248
  element.removeEventListener('mouseleave', handlers.mouseleave);
248
249
  });
249
250
  };
250
- }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo]);
251
+ }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo, userContext, handleLogin, handleUpgrade, isProcessing]);
251
252
  // Tooltip 组件
252
253
  const Tooltip = ({ show, content, x, y }) => {
253
254
  if (!show)
@@ -14,6 +14,7 @@ var pricePlanInteractive = require('./price-plan-interactive.js');
14
14
  require('@clerk/nextjs');
15
15
  require('../clerk/fingerprint/fingerprint-provider.js');
16
16
  require('next/navigation');
17
+ require('react-dom');
17
18
  require('next-themes');
18
19
  require('fumadocs-core/framework');
19
20
  require('next/link');
@@ -12,6 +12,7 @@ import { PricePlanInteractive } from './price-plan-interactive.mjs';
12
12
  import '@clerk/nextjs';
13
13
  import '../clerk/fingerprint/fingerprint-provider.mjs';
14
14
  import 'next/navigation';
15
+ import 'react-dom';
15
16
  import 'next-themes';
16
17
  import 'fumadocs-core/framework';
17
18
  import 'next/link';
@@ -1,4 +1,4 @@
1
- import { __module as coseBase$1 } from '../../../../../_virtual/cose-base2.mjs';
1
+ import { __module as coseBase$1 } from '../../../../../_virtual/cose-base.mjs';
2
2
  import { __require as requireLayoutBase } from '../../../layout-base@1.0.2/node_modules/layout-base/layout-base.mjs';
3
3
 
4
4
  var coseBase = coseBase$1.exports;
@@ -1,4 +1,4 @@
1
- import { __module as coseBase$1 } from '../../../../../_virtual/cose-base.mjs';
1
+ import { __module as coseBase$1 } from '../../../../../_virtual/cose-base2.mjs';
2
2
  import { __require as requireLayoutBase } from '../../../layout-base@2.0.1/node_modules/layout-base/layout-base.mjs';
3
3
 
4
4
  var coseBase = coseBase$1.exports;
@@ -1,4 +1,4 @@
1
- import { __module as layoutBase$1 } from '../../../../../_virtual/layout-base2.mjs';
1
+ import { __module as layoutBase$1 } from '../../../../../_virtual/layout-base.mjs';
2
2
 
3
3
  var layoutBase = layoutBase$1.exports;
4
4
 
@@ -1,4 +1,4 @@
1
- import { __module as layoutBase$1 } from '../../../../../_virtual/layout-base.mjs';
1
+ import { __module as layoutBase$1 } from '../../../../../_virtual/layout-base2.mjs';
2
2
 
3
3
  var layoutBase = layoutBase$1.exports;
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "7.3.9",
3
+ "version": "7.3.10",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -75,8 +75,8 @@
75
75
  "mermaid": "^11.6.0",
76
76
  "react-medium-image-zoom": "^5.2.14",
77
77
  "zod": "^3.22.4",
78
- "@windrun-huaiin/base-ui": "^8.1.2",
79
- "@windrun-huaiin/lib": "^7.1.2"
78
+ "@windrun-huaiin/lib": "^7.1.2",
79
+ "@windrun-huaiin/base-ui": "^8.1.2"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "react": "19.1.0",
@@ -5,6 +5,7 @@ import { useFingerprintContextSafe } from '@third-ui/clerk/fingerprint';
5
5
  import { cn } from '@windrun-huaiin/lib/utils';
6
6
  import { useRouter } from 'next/navigation';
7
7
  import React, { useEffect, useState, useMemo, useCallback } from 'react';
8
+ import { createPortal } from 'react-dom';
8
9
  import { MoneyPriceButton } from './money-price-button';
9
10
  import { getActiveProviderConfig, getProductPricing } from './money-price-config-util';
10
11
  import {
@@ -130,7 +131,6 @@ export function MoneyPriceInteractive({
130
131
 
131
132
  const priceOriginalElement = document.querySelector(`[data-price-original="${plan.key}"]`) as HTMLElement;
132
133
  const priceDiscountElement = document.querySelector(`[data-price-discount="${plan.key}"]`) as HTMLElement;
133
-
134
134
  if (pricing.originalAmount && pricing.discountPercent) {
135
135
  if (priceOriginalElement) {
136
136
  priceOriginalElement.style.display = 'inline';
@@ -140,74 +140,57 @@ export function MoneyPriceInteractive({
140
140
  priceDiscountElement.style.display = 'inline';
141
141
  const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
142
142
  if (billingOption?.discountText) {
143
- priceDiscountElement.textContent = billingOption.discountText.replace(
144
- '{percent}',
145
- String(pricing.discountPercent)
146
- );
143
+ priceDiscountElement.textContent = billingOption.discountText.replace('{percent}', String(pricing.discountPercent));
147
144
  }
148
145
  }
149
146
  } else {
150
147
  if (priceOriginalElement) priceOriginalElement.style.display = 'none';
151
148
  if (priceDiscountElement) priceDiscountElement.style.display = 'none';
152
149
  }
153
-
154
- const priceSubtitleElement = document.querySelector(`[data-price-subtitle="${plan.key}"]`) as HTMLElement;
155
- if (priceSubtitleElement && plan.showBillingSubTitle !== false) {
156
- const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
157
- priceSubtitleElement.textContent = billingOption?.subTitle || '';
158
- }
159
150
  });
160
151
  }, [config, data]);
161
152
 
162
- // 更新按钮样式
163
- const updateButtonStyles = useCallback((newBillingType: string) => {
164
- const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLButtonElement;
165
- const yearlyButton = document.querySelector('[data-billing-button="yearly"]') as HTMLButtonElement;
166
-
167
- const activeClasses = 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm';
168
- const inactiveClasses = 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full';
169
-
170
- if (monthlyButton && yearlyButton) {
171
- if (newBillingType === 'monthly') {
172
- monthlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
173
- yearlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', inactiveClasses);
174
- } else {
175
- monthlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', inactiveClasses);
176
- yearlyButton.className = cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', activeClasses);
177
- }
178
- }
179
- }, []);
180
-
181
153
  // 更新折扣信息
182
154
  const updateDiscountInfo = useCallback((newBillingType: string) => {
183
155
  const discountInfoElement = document.querySelector('[data-discount-info]') as HTMLElement;
184
- if (!discountInfoElement) return;
185
-
186
- const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
187
- const providerConfig = getActiveProviderConfig(config);
188
-
189
- let hasDiscount = false;
190
- let discountPercent = 0;
191
-
192
- ['pro', 'ultra'].forEach(planKey => {
193
- const product = providerConfig.products[planKey as 'pro' | 'ultra'];
194
- const pricing = product.plans[newBillingType as 'monthly' | 'yearly'];
195
- if (pricing.discountPercent) {
196
- hasDiscount = true;
197
- discountPercent = pricing.discountPercent;
156
+ if (discountInfoElement) {
157
+ const billingOption = data.billingSwitch.options.find(opt => opt.key === newBillingType);
158
+ let hasDiscount = false;
159
+ let discountPercent = 0;
160
+ const providerConfig = getActiveProviderConfig(config);
161
+ ['pro', 'ultra'].forEach(planKey => {
162
+ const pricing = providerConfig.products[planKey as 'pro' | 'ultra'].plans[newBillingType as 'monthly' | 'yearly'];
163
+ if (pricing.discountPercent) {
164
+ hasDiscount = true;
165
+ discountPercent = pricing.discountPercent;
166
+ }
167
+ });
168
+ discountInfoElement.innerHTML = '';
169
+ if (hasDiscount && billingOption?.discountText) {
170
+ const discountBadge = document.createElement('span');
171
+ discountBadge.className = 'px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap';
172
+ discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
173
+ discountInfoElement.appendChild(discountBadge);
198
174
  }
199
- });
200
-
201
- discountInfoElement.innerHTML = '';
202
-
203
- if (hasDiscount && billingOption?.discountText) {
204
- const discountBadge = document.createElement('span');
205
- discountBadge.className = 'px-2 py-1 text-xs rounded bg-yellow-100 text-yellow-800 font-semibold align-middle text-center inline-flex items-center justify-center whitespace-nowrap';
206
- discountBadge.textContent = billingOption.discountText.replace('{percent}', String(discountPercent));
207
- discountInfoElement.appendChild(discountBadge);
208
175
  }
209
176
  }, [config, data]);
210
177
 
178
+ // 更新按钮样式
179
+ const updateButtonStyles = useCallback((newBillingType: string) => {
180
+ const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLElement;
181
+ const yearlyButton = document.querySelector('[data-billing-button="yearly"]') as HTMLElement;
182
+ if (monthlyButton) {
183
+ monthlyButton.className = newBillingType === 'monthly'
184
+ ? cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm')
185
+ : cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full');
186
+ }
187
+ if (yearlyButton) {
188
+ yearlyButton.className = newBillingType === 'yearly'
189
+ ? cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-white bg-gradient-to-r from-purple-400 to-pink-500 hover:from-purple-500 hover:to-pink-600 dark:from-purple-500 dark:to-pink-600 dark:hover:from-purple-600 rounded-full shadow-sm')
190
+ : cn('min-w-[120px] px-6 py-2 font-medium transition text-lg relative', 'text-gray-800 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-100 rounded-full');
191
+ }
192
+ }, []);
193
+
211
194
  // 处理月付/年付切换和 tooltip 功能
212
195
  useEffect(() => {
213
196
  const monthlyButton = document.querySelector('[data-billing-button="monthly"]') as HTMLButtonElement;
@@ -275,23 +258,30 @@ export function MoneyPriceInteractive({
275
258
  });
276
259
  });
277
260
 
261
+ // Inject buttons into placeholders using createPortal
262
+ data.plans.forEach((plan: any) => {
263
+ const placeholder = document.querySelector(`[data-button-placeholder="${plan.key}"]`) as HTMLElement;
264
+ if (placeholder) {
265
+ createPortal(
266
+ <MoneyPriceButton
267
+ planKey={plan.key}
268
+ userContext={userContext}
269
+ billingType={billingType}
270
+ onLogin={handleLogin}
271
+ onUpgrade={handleUpgrade}
272
+ texts={data.buttonTexts}
273
+ isProcessing={isProcessing}
274
+ />,
275
+ placeholder
276
+ );
277
+ }
278
+ });
279
+
280
+ // Initial updates
278
281
  updatePriceDisplay(billingType);
279
282
  updateDiscountInfo(billingType);
283
+ updateButtonStyles(billingType);
280
284
 
281
- data.plans.map((plan: any) => (
282
- <div key={plan.key} data-button-placeholder={plan.key}>
283
- <MoneyPriceButton
284
- planKey={plan.key}
285
- userContext={userContext}
286
- billingType={billingType}
287
- onLogin={handleLogin}
288
- onUpgrade={handleUpgrade}
289
- texts={data.buttonTexts}
290
- isProcessing={isProcessing}
291
- />
292
- </div>
293
- ))
294
-
295
285
  return () => {
296
286
  if (monthlyButton) {
297
287
  monthlyButton.removeEventListener('click', handleMonthlyClick);
@@ -306,7 +296,7 @@ export function MoneyPriceInteractive({
306
296
  element.removeEventListener('mouseleave', handlers.mouseleave);
307
297
  });
308
298
  };
309
- }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo]);
299
+ }, [data, billingType, updatePriceDisplay, updateButtonStyles, updateDiscountInfo, userContext, handleLogin, handleUpgrade, isProcessing]);
310
300
 
311
301
  // Tooltip 组件
312
302
  const Tooltip = ({ show, content, x, y }: typeof tooltip) => {
@@ -331,5 +321,5 @@ export function MoneyPriceInteractive({
331
321
  );
332
322
  };
333
323
 
334
- return <Tooltip {...tooltip} />
324
+ return <Tooltip {...tooltip} />;
335
325
  }