@windrun-huaiin/third-ui 25.0.0 → 27.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/dist/ai/ai-prompt-textarea.d.ts +72 -0
  2. package/dist/ai/ai-prompt-textarea.js +114 -0
  3. package/dist/ai/ai-prompt-textarea.mjs +112 -0
  4. package/dist/ai/index.d.ts +1 -0
  5. package/dist/ai/index.js +2 -0
  6. package/dist/ai/index.mjs +1 -0
  7. package/dist/clerk/clerk-provider-client.js +0 -1
  8. package/dist/clerk/clerk-provider-client.mjs +0 -1
  9. package/dist/clerk/fingerprint/fingerprint-client.js +0 -4
  10. package/dist/clerk/fingerprint/fingerprint-client.mjs +0 -4
  11. package/dist/clerk/fingerprint/use-fingerprint.js +0 -6
  12. package/dist/clerk/fingerprint/use-fingerprint.mjs +0 -6
  13. package/dist/clerk/signin-with-fingerprint-client.js +0 -9
  14. package/dist/clerk/signin-with-fingerprint-client.mjs +0 -9
  15. package/dist/clerk/signup-button-with-fingerprint-client.js +0 -16
  16. package/dist/clerk/signup-button-with-fingerprint-client.mjs +0 -16
  17. package/dist/clerk/signup-with-fingerprint-client.js +0 -9
  18. package/dist/clerk/signup-with-fingerprint-client.mjs +0 -9
  19. package/dist/fuma/base/custom-header.js +10 -8
  20. package/dist/fuma/base/custom-header.mjs +10 -8
  21. package/dist/fuma/base/custom-home-layout.d.ts +1 -0
  22. package/dist/fuma/base/index.d.ts +1 -0
  23. package/dist/fuma/base/index.js +4 -0
  24. package/dist/fuma/base/index.mjs +1 -0
  25. package/dist/fuma/base/nav-config.d.ts +10 -0
  26. package/dist/fuma/base/nav-config.js +32 -0
  27. package/dist/fuma/base/nav-config.mjs +28 -0
  28. package/dist/fuma/base/site-layout.d.ts +4 -0
  29. package/dist/fuma/base/site-layout.js +2 -2
  30. package/dist/fuma/base/site-layout.mjs +2 -2
  31. package/dist/fuma/fuma-page-genarator.d.ts +1 -1
  32. package/dist/fuma/fuma-page-genarator.js +60 -5
  33. package/dist/fuma/fuma-page-genarator.mjs +60 -5
  34. package/dist/fuma/llm-copy-handler.js +0 -9
  35. package/dist/fuma/llm-copy-handler.mjs +0 -9
  36. package/dist/fuma/mdx/index.d.ts +0 -1
  37. package/dist/fuma/mdx/index.js +0 -2
  38. package/dist/fuma/mdx/index.mjs +0 -1
  39. package/dist/fuma/mdx/suno-embed.js +3 -1
  40. package/dist/fuma/mdx/suno-embed.mjs +3 -1
  41. package/dist/fuma/mdx/toc-base.js +0 -1
  42. package/dist/fuma/mdx/toc-base.mjs +0 -1
  43. package/dist/fuma/server/features/widgets.js +5 -1
  44. package/dist/fuma/server/features/widgets.mjs +5 -1
  45. package/dist/lib/site-docs-helper.d.ts +51 -0
  46. package/dist/lib/site-docs-helper.js +68 -0
  47. package/dist/lib/site-docs-helper.mjs +66 -0
  48. package/dist/main/alert-dialog/index.js +14 -0
  49. package/dist/main/alert-dialog/index.mjs +5 -0
  50. package/dist/main/buttons/gradient-button.d.ts +20 -0
  51. package/dist/main/buttons/gradient-button.js +88 -0
  52. package/dist/main/buttons/gradient-button.mjs +86 -0
  53. package/dist/main/buttons/index.d.ts +3 -0
  54. package/dist/main/buttons/index.js +12 -0
  55. package/dist/main/buttons/index.mjs +4 -0
  56. package/dist/main/buttons/x-button.d.ts +39 -0
  57. package/dist/main/buttons/x-button.js +92 -0
  58. package/dist/main/buttons/x-button.mjs +90 -0
  59. package/dist/main/buttons/x-toggle-button.d.ts +32 -0
  60. package/dist/main/buttons/x-toggle-button.js +95 -0
  61. package/dist/main/buttons/x-toggle-button.mjs +74 -0
  62. package/dist/main/credit/credit-overview-client.js +3 -2
  63. package/dist/main/credit/credit-overview-client.mjs +3 -2
  64. package/dist/main/credit/index.d.ts +4 -0
  65. package/dist/main/credit/index.js +10 -0
  66. package/dist/main/credit/index.mjs +3 -0
  67. package/dist/main/credit/server.d.ts +2 -0
  68. package/dist/main/credit/server.js +7 -0
  69. package/dist/main/credit/server.mjs +1 -0
  70. package/dist/main/cta.js +4 -2
  71. package/dist/main/cta.mjs +4 -2
  72. package/dist/main/hero/index.d.ts +2 -0
  73. package/dist/main/hero/index.js +10 -0
  74. package/dist/main/hero/index.mjs +3 -0
  75. package/dist/main/home/server.d.ts +7 -0
  76. package/dist/main/home/server.js +19 -0
  77. package/dist/main/home/server.mjs +7 -0
  78. package/dist/main/index.d.ts +0 -15
  79. package/dist/main/index.js +0 -43
  80. package/dist/main/index.mjs +0 -21
  81. package/dist/main/loading/index.d.ts +1 -0
  82. package/dist/main/loading/index.js +9 -0
  83. package/dist/main/loading/index.mjs +2 -0
  84. package/dist/main/loading-frame/index.d.ts +1 -0
  85. package/dist/main/loading-frame/index.js +9 -0
  86. package/dist/main/loading-frame/index.mjs +2 -0
  87. package/dist/main/money-price/index.d.ts +4 -0
  88. package/dist/main/money-price/index.js +15 -0
  89. package/dist/main/money-price/index.mjs +4 -0
  90. package/dist/main/money-price/money-price-button.d.ts +1 -1
  91. package/dist/main/money-price/money-price-button.js +10 -7
  92. package/dist/main/money-price/money-price-button.mjs +10 -7
  93. package/dist/main/money-price/money-price-interactive.js +9 -8
  94. package/dist/main/money-price/money-price-interactive.mjs +9 -8
  95. package/dist/main/money-price/money-price-types.d.ts +1 -0
  96. package/dist/main/money-price/server.d.ts +5 -0
  97. package/dist/main/money-price/server.js +18 -0
  98. package/dist/main/money-price/server.mjs +4 -0
  99. package/package.json +54 -4
  100. package/src/ai/index.ts +1 -0
  101. package/src/clerk/clerk-provider-client.tsx +1 -3
  102. package/src/clerk/fingerprint/fingerprint-client.ts +0 -4
  103. package/src/clerk/fingerprint/use-fingerprint.ts +0 -6
  104. package/src/clerk/signin-with-fingerprint-client.tsx +0 -10
  105. package/src/clerk/signup-button-with-fingerprint-client.tsx +0 -17
  106. package/src/clerk/signup-with-fingerprint-client.tsx +0 -10
  107. package/src/fuma/base/custom-header.tsx +12 -8
  108. package/src/fuma/base/custom-home-layout.tsx +7 -4
  109. package/src/fuma/base/index.ts +1 -0
  110. package/src/fuma/base/nav-config.ts +81 -0
  111. package/src/fuma/base/site-layout.tsx +6 -0
  112. package/src/fuma/fuma-banner-suit.tsx +1 -1
  113. package/src/fuma/fuma-page-genarator.tsx +60 -7
  114. package/src/fuma/llm-copy-handler.ts +0 -11
  115. package/src/fuma/mdx/index.ts +0 -1
  116. package/src/fuma/mdx/suno-embed.tsx +1 -1
  117. package/src/fuma/mdx/toc-base.tsx +0 -1
  118. package/src/fuma/mdx/toc-footer-wrapper.tsx +2 -2
  119. package/src/fuma/server/features/widgets.tsx +1 -1
  120. package/src/lib/server.ts +1 -1
  121. package/src/{fuma/mdx → main/buttons}/gradient-button.tsx +10 -21
  122. package/src/main/buttons/index.ts +5 -0
  123. package/src/main/{x-button.tsx → buttons/x-button.tsx} +28 -42
  124. package/src/main/credit/credit-overview-client.tsx +1 -1
  125. package/src/main/credit/index.ts +11 -0
  126. package/src/main/credit/server.ts +7 -0
  127. package/src/main/cta.tsx +1 -1
  128. package/src/main/hero/index.ts +4 -0
  129. package/src/main/home/server.ts +7 -0
  130. package/src/main/index.ts +1 -20
  131. package/src/main/language-detector.tsx +0 -1
  132. package/src/main/loading/index.ts +3 -0
  133. package/src/main/loading-frame/index.ts +3 -0
  134. package/src/main/money-price/index.ts +18 -0
  135. package/src/main/money-price/money-price-button.tsx +12 -6
  136. package/src/main/money-price/money-price-interactive.tsx +17 -10
  137. package/src/main/money-price/money-price-types.ts +1 -0
  138. package/src/main/money-price/server.ts +22 -0
  139. package/dist/fuma/mdx/features.d.ts +0 -8
  140. package/dist/fuma/mdx/features.js +0 -92
  141. package/dist/fuma/mdx/features.mjs +0 -85
  142. package/dist/fuma/mdx/image-grid.d.ts +0 -6
  143. package/dist/fuma/mdx/image-grid.js +0 -17
  144. package/dist/fuma/mdx/image-grid.mjs +0 -15
  145. package/dist/fuma/mdx/image-zoom.d.ts +0 -22
  146. package/dist/fuma/mdx/image-zoom.js +0 -39
  147. package/dist/fuma/mdx/image-zoom.mjs +0 -37
  148. package/dist/fuma/mdx/markdown-component-map.d.ts +0 -3
  149. package/dist/fuma/mdx/markdown-component-map.js +0 -79
  150. package/dist/fuma/mdx/markdown-component-map.mjs +0 -77
  151. package/dist/fuma/mdx/math.d.ts +0 -17
  152. package/dist/fuma/mdx/math.js +0 -60
  153. package/dist/fuma/mdx/math.mjs +0 -57
  154. package/dist/fuma/mdx/mermaid.d.ts +0 -13
  155. package/dist/fuma/mdx/mermaid.js +0 -360
  156. package/dist/fuma/mdx/mermaid.mjs +0 -358
  157. package/dist/fuma/mdx/site-mdx-components.d.ts +0 -13
  158. package/dist/fuma/mdx/site-mdx-components.js +0 -19
  159. package/dist/fuma/mdx/site-mdx-components.mjs +0 -17
  160. package/dist/fuma/mdx/site-mdx-presets.d.ts +0 -13
  161. package/dist/fuma/mdx/site-mdx-presets.js +0 -49
  162. package/dist/fuma/mdx/site-mdx-presets.mjs +0 -45
  163. package/dist/fuma/server/optional-features.d.ts +0 -6
  164. package/dist/fuma/server/optional-features.js +0 -17
  165. package/dist/fuma/server/optional-features.mjs +0 -6
  166. package/dist/fuma/server/site-mdx-components.d.ts +0 -13
  167. package/dist/fuma/server/site-mdx-components.js +0 -18
  168. package/dist/fuma/server/site-mdx-components.mjs +0 -16
  169. package/dist/fuma/server/site-mdx-presets.d.ts +0 -195
  170. package/dist/fuma/server/site-mdx-presets.js +0 -55
  171. package/dist/fuma/server/site-mdx-presets.mjs +0 -52
  172. /package/src/{main → ai}/ai-prompt-textarea.tsx +0 -0
  173. /package/src/main/{x-toggle-button.tsx → buttons/x-toggle-button.tsx} +0 -0
@@ -0,0 +1,81 @@
1
+ import type {
2
+ CreateSiteNavGroupOptions,
3
+ CreateSiteNavItemContext,
4
+ SiteMenuGroupConfig,
5
+ SiteMenuLeafConfig,
6
+ SiteNavItemConfig,
7
+ SiteNavLinkItemConfig,
8
+ } from './site-layout';
9
+
10
+ export interface LocalizedNavContextOptions {
11
+ locale: string;
12
+ localePrefixAsNeeded?: boolean;
13
+ defaultLocale?: string;
14
+ localizeHref: (locale: string, path: string, localePrefixAsNeeded: boolean, defaultLocale: string) => string;
15
+ }
16
+
17
+ export function createLocalizedNavContext(
18
+ options: LocalizedNavContextOptions,
19
+ ): CreateSiteNavItemContext {
20
+ const {
21
+ locale,
22
+ localePrefixAsNeeded = true,
23
+ defaultLocale = 'en',
24
+ localizeHref,
25
+ } = options;
26
+
27
+ return {
28
+ resolveUrl(path: string) {
29
+ return localizeHref(locale, path, localePrefixAsNeeded, defaultLocale);
30
+ },
31
+ };
32
+ }
33
+
34
+ export function createLocalizedNavLink(
35
+ item: SiteMenuLeafConfig,
36
+ context: CreateSiteNavItemContext,
37
+ ): SiteNavItemConfig {
38
+ return {
39
+ type: 'main',
40
+ text: item.text,
41
+ ...(item.description ? { description: item.description } : {}),
42
+ url: context.resolveUrl(item.path),
43
+ ...(item.external ? { external: item.external } : {}),
44
+ ...(item.prefetch !== undefined ? { prefetch: item.prefetch } : {}),
45
+ ...(item.icon || item.className
46
+ ? {
47
+ menu: {
48
+ ...(item.icon ? { banner: item.icon } : {}),
49
+ ...(item.className ? { className: item.className } : {}),
50
+ },
51
+ }
52
+ : {}),
53
+ };
54
+ }
55
+
56
+ export function createLocalizedNavGroup(
57
+ item: SiteMenuGroupConfig,
58
+ context: CreateSiteNavItemContext,
59
+ options?: CreateSiteNavGroupOptions,
60
+ ): SiteNavItemConfig {
61
+ return {
62
+ type: 'menu',
63
+ text: item.text,
64
+ ...(item.path ? { url: context.resolveUrl(item.path) } : {}),
65
+ ...(item.prefetch !== undefined ? { prefetch: item.prefetch } : {}),
66
+ items: [
67
+ ...(item.landing
68
+ ? [
69
+ {
70
+ ...createLocalizedNavLink(item.landing, context),
71
+ menu: {
72
+ ...(options?.featuredBanner ? { banner: options.featuredBanner } : {}),
73
+ className: options?.featuredClassName ?? 'md:row-span-2',
74
+ },
75
+ } as SiteNavLinkItemConfig,
76
+ ]
77
+ : []),
78
+ ...item.items.map((child) => createLocalizedNavLink(child, context)),
79
+ ],
80
+ };
81
+ }
@@ -25,6 +25,7 @@ export interface SiteNavLinkItemConfig extends SiteNavSharedFields {
25
25
  text: ReactNode;
26
26
  url: string;
27
27
  external?: boolean;
28
+ prefetch?: boolean;
28
29
  icon?: ReactNode;
29
30
  description?: ReactNode;
30
31
  menu?: SiteMenuConfig;
@@ -37,6 +38,7 @@ export interface SiteNavMenuItemConfig extends SiteNavSharedFields {
37
38
  text: ReactNode;
38
39
  url?: string;
39
40
  external?: boolean;
41
+ prefetch?: boolean;
40
42
  icon?: ReactNode;
41
43
  description?: ReactNode;
42
44
  items: SiteNavItemConfig[];
@@ -92,11 +94,13 @@ export interface SiteMenuLeafConfig {
92
94
  icon?: ReactNode;
93
95
  className?: string;
94
96
  external?: boolean;
97
+ prefetch?: boolean;
95
98
  }
96
99
 
97
100
  export interface SiteMenuGroupConfig {
98
101
  text: ReactNode;
99
102
  path?: string;
103
+ prefetch?: boolean;
100
104
  landing?: SiteMenuLeafConfig;
101
105
  items: SiteMenuLeafConfig[];
102
106
  }
@@ -147,6 +151,7 @@ export function createSiteNavLink(
147
151
  ...(item.description ? { description: item.description } : {}),
148
152
  url: context.resolveUrl(item.path),
149
153
  ...(item.external ? { external: item.external } : {}),
154
+ ...(item.prefetch !== undefined ? { prefetch: item.prefetch } : {}),
150
155
  ...(item.icon || item.className
151
156
  ? {
152
157
  menu: {
@@ -167,6 +172,7 @@ export function createSiteNavGroup(
167
172
  type: 'menu',
168
173
  text: item.text,
169
174
  ...(item.path ? { url: context.resolveUrl(item.path) } : {}),
175
+ ...(item.prefetch !== undefined ? { prefetch: item.prefetch } : {}),
170
176
  items: [
171
177
  ...(item.landing
172
178
  ? [
@@ -1,5 +1,5 @@
1
1
  import { getTranslations } from 'next-intl/server';
2
- import { Banner } from '@third-ui/fuma/mdx/banner';
2
+ import { Banner } from './mdx/banner';
3
3
 
4
4
  export async function FumaBannerSuit({
5
5
  locale,
@@ -1,9 +1,9 @@
1
1
  import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
2
2
  import { ReactNode, ReactElement, cloneElement, type CSSProperties } from 'react';
3
- import { TocFooterWrapper } from '@third-ui/fuma/mdx/toc-footer-wrapper';
4
- import type { LLMCopyButtonProps, LLMCopyButton } from '@third-ui/fuma/mdx/toc-base';
3
+ import { TocFooterWrapper } from './mdx/toc-footer-wrapper';
4
+ import type { LLMCopyButtonProps, LLMCopyButton } from './mdx/toc-base';
5
5
  import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
6
- import { PortableClerkTOC, PortableClerkTOCTitle } from '@third-ui/fuma/mdx/toc-clerk-portable';
6
+ import { PortableClerkTOC, PortableClerkTOCTitle } from './mdx/toc-clerk-portable';
7
7
  import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
8
8
 
9
9
  export type FumaPageTocRenderMode =
@@ -99,6 +99,14 @@ export function createFumaPage({
99
99
  localePrefixAsNeeded = true,
100
100
  defaultLocale = 'en',
101
101
  }: FumaPageParams) {
102
+ const isLocalMdDebugEnabled = process.env.LOCAL_MD_DEBUG?.toLowerCase() === 'true';
103
+ const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
104
+ const durationMs = (startedAt: number) => Number((now() - startedAt).toFixed(1));
105
+ const logFumaPageDebug = (message: string, details?: Record<string, unknown>) => {
106
+ if (!isLocalMdDebugEnabled) return;
107
+ console.log(`[fuma-page] ${message}`, details ?? {});
108
+ };
109
+
102
110
  const getSource = async () => {
103
111
  if (typeof mdxContentSource === 'function') {
104
112
  return await mdxContentSource();
@@ -108,11 +116,28 @@ export function createFumaPage({
108
116
  };
109
117
 
110
118
  const Page = async function Page({ params }: { params: Promise<{ locale: string; slug?: string[] }> }) {
119
+ const pageStartedAt = now();
111
120
  const { slug, locale } = await params;
121
+ const sourceStartedAt = now();
112
122
  const source = await getSource();
123
+ logFumaPageDebug('page:source-ready', {
124
+ sourceKey,
125
+ locale,
126
+ slug,
127
+ durationMs: durationMs(sourceStartedAt),
128
+ });
129
+
130
+ const getPageStartedAt = now();
113
131
  const page = source.getPage(slug, locale);
132
+ logFumaPageDebug('page:get-page', {
133
+ sourceKey,
134
+ locale,
135
+ slug,
136
+ found: Boolean(page),
137
+ durationMs: durationMs(getPageStartedAt),
138
+ totalElapsedMs: durationMs(pageStartedAt),
139
+ });
114
140
  if (!page) {
115
- console.log('[FumaPage] missing page', { slug, locale, available: source.pageTree?.[locale]?.children?.map((c: any) => c.url) });
116
141
  return (
117
142
  <DocsPage
118
143
  full
@@ -143,7 +168,19 @@ export function createFumaPage({
143
168
 
144
169
  const content =
145
170
  typeof page.data.load === 'function'
146
- ? await page.data.load(getMDXComponents())
171
+ ? await (async () => {
172
+ const loadStartedAt = now();
173
+ const result = await page.data.load(getMDXComponents());
174
+ logFumaPageDebug('page:load', {
175
+ sourceKey,
176
+ locale,
177
+ slug,
178
+ pagePath: page.path,
179
+ durationMs: durationMs(loadStartedAt),
180
+ totalElapsedMs: durationMs(pageStartedAt),
181
+ });
182
+ return result;
183
+ })()
147
184
  : {
148
185
  body: await page.data.body({ components: getMDXComponents() }),
149
186
  toc: page.data.toc ?? [],
@@ -174,14 +211,30 @@ export function createFumaPage({
174
211
  );
175
212
  };
176
213
 
177
- function generateStaticParams() {
178
- return getSource().then((source) => source.generateParams('slug', 'locale'));
214
+ async function generateStaticParams() {
215
+ const startedAt = now();
216
+ const source = await getSource();
217
+ const params = source.generateParams('slug', 'locale');
218
+ logFumaPageDebug('generateStaticParams', {
219
+ sourceKey,
220
+ count: Array.isArray(params) ? params.length : undefined,
221
+ durationMs: durationMs(startedAt),
222
+ });
223
+ return params;
179
224
  }
180
225
 
181
226
  async function generateMetadata(props: { params: Promise<{ slug?: string[]; locale?: string }> }) {
227
+ const startedAt = now();
182
228
  const { slug, locale } = await props.params;
183
229
  const source = await getSource();
184
230
  const page = source.getPage(slug, locale);
231
+ logFumaPageDebug('generateMetadata:get-page', {
232
+ sourceKey,
233
+ locale,
234
+ slug,
235
+ found: Boolean(page),
236
+ durationMs: durationMs(startedAt),
237
+ });
185
238
  if (!page) {
186
239
  return {
187
240
  title: '404 - Page Not Found',
@@ -21,17 +21,10 @@ export type LLMCopyHandlerOptions = {
21
21
  */
22
22
  export async function LLMCopyHandler(options: LLMCopyHandlerOptions): Promise<{ text?: string; error?: string; status: number }> {
23
23
  const { sourceDir, dataSource, requestedPath, locale } = options;
24
-
25
- // log received parameters
26
- console.log(`[LLMCopy] Received, locale=${locale}, path=${requestedPath}`);
27
-
28
24
  const slug = requestedPath?.split('/') || [];
29
25
 
30
26
  try {
31
- console.log('[LLMCopy] Attempting to call getPage()');
32
27
  const page = dataSource.getPage(slug, locale);
33
- // console.log(page);
34
- console.log('[LLMCopy] Call to getPage() completed.');
35
28
 
36
29
  if (!page) {
37
30
  console.error(`[LLMCopy] Page or page data not found for locale=${locale}, path=${requestedPath}`);
@@ -46,19 +39,15 @@ export async function LLMCopyHandler(options: LLMCopyHandlerOptions): Promise<{
46
39
  const description = page.data?.description ?? page.description;
47
40
  const relativeMdxFilePath = page.path;
48
41
  const absoluteFilePath = nodePath.join(process.cwd(), sourceDir, relativeMdxFilePath);
49
- console.log(`[LLMCopy] Attempting to read MDX content from: ${absoluteFilePath}`);
50
42
 
51
43
  let mdxContent: string;
52
44
  try {
53
45
  mdxContent = fs.readFileSync(absoluteFilePath, 'utf-8');
54
- console.log(`[LLMCopy] Successfully read MDX content from: ${absoluteFilePath}`);
55
46
  } catch (readError: any) {
56
47
  console.error(`[LLMCopy] Failed to read file at: ${absoluteFilePath}. Error: ${readError.message}`);
57
48
  console.error('[LLMCopy] Read Error object details:', JSON.stringify(readError, Object.getOwnPropertyNames(readError), 2));
58
49
  // directory traversal debug logs
59
50
  try {
60
- console.log(`[LLMCopy] Current CWD: ${process.cwd()}`);
61
- console.log(`[LLMCopy] CWD contents: ${fs.readdirSync(process.cwd()).join(', ')}`);
62
51
  const srcPath = nodePath.join(process.cwd(), 'src');
63
52
  if (fs.existsSync(srcPath)) {
64
53
  console.log(`[LLMCopy] src dir contents: ${fs.readdirSync(srcPath).join(', ')}`);
@@ -2,7 +2,6 @@
2
2
 
3
3
  export * from './trophy-card';
4
4
  export * from './zia-card';
5
- export * from './gradient-button';
6
5
  export * from './toc-base';
7
6
  export * from './fuma-github-info';
8
7
  export * from './zia-file';
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react';
4
4
  import { cn } from '@windrun-huaiin/lib/utils';
5
5
  import { themeBgColor, themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
6
6
  import { SnakeLoadingFrame } from '../../main/snake-loading-frame';
7
- import { GradientButton } from './gradient-button';
7
+ import { GradientButton } from '../../main/buttons';
8
8
 
9
9
  interface SunoEmbedProps {
10
10
  src: string;
@@ -32,7 +32,6 @@ export function LLMCopyButton({ llmApiUrl, sourceKey }: LLMCopyButtonProps = {})
32
32
  if (sourceKey) {
33
33
  apiUrl += `&sourceKey=${encodeURIComponent(sourceKey)}`;
34
34
  }
35
- console.log('Fetching LLM content from:', apiUrl);
36
35
 
37
36
  let content: string;
38
37
  try {
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { EditOnGitHub, LastUpdatedDate } from '@third-ui/fuma/mdx/toc-base';
3
+ import { EditOnGitHub, LastUpdatedDate } from './toc-base';
4
4
  import React from 'react';
5
5
 
6
6
  interface TocFooterProps {
@@ -19,4 +19,4 @@ export function TocFooterWrapper({ lastModified, editPath, githubBaseUrl, copyBu
19
19
  {showEdit && <EditOnGitHub url={`${githubBaseUrl}${editPath}`} />}
20
20
  </div>
21
21
  );
22
- }
22
+ }
@@ -2,7 +2,7 @@ import type { MDXComponents } from 'mdx/types';
2
2
  import { lazy } from 'react';
3
3
  import { TrophyCard } from '../../mdx/trophy-card';
4
4
  import { ZiaCard } from '../../mdx/zia-card';
5
- import { GradientButton } from '../../mdx/gradient-button';
5
+ import { GradientButton } from '../../../main/buttons';
6
6
  import { ZiaFile, ZiaFolder } from '../../mdx/zia-file';
7
7
  import { SunoEmbed } from '../../mdx/suno-embed';
8
8
 
package/src/lib/server.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  export * from './clerk-intl';
4
4
  export * from './fuma-schema-check-util';
5
5
  export * from './seo-util';
6
- export * from './t-intl';
6
+ export * from './t-intl';
@@ -23,12 +23,9 @@ export interface GradientButtonProps {
23
23
  disabled?: boolean;
24
24
  className?: string;
25
25
  iconClassName?: string;
26
- // for Link
27
26
  href?: string;
28
27
  openInNewTab?: boolean;
29
28
  preserveReferrer?: boolean;
30
-
31
- // for click
32
29
  onClick?: () => void | Promise<void>;
33
30
  loadingText?: React.ReactNode;
34
31
  preventDoubleClick?: boolean;
@@ -52,7 +49,7 @@ export function GradientButton({
52
49
  variant = 'default',
53
50
  }: GradientButtonProps) {
54
51
  const [isLoading, setIsLoading] = useState(false);
55
- const actualLoadingText = loadingText || title?.toString().trim() || 'Loading...'
52
+ const actualLoadingText = loadingText || title?.toString().trim() || 'Loading...';
56
53
 
57
54
  const defaultIconClass = "h-4 w-4";
58
55
  const finalIconClass = cn(
@@ -60,14 +57,13 @@ export function GradientButton({
60
57
  iconClassName || defaultIconClass
61
58
  );
62
59
 
63
- // set justify class according to alignment
64
60
  const getAlignmentClass = () => {
65
61
  switch (align) {
66
62
  case 'center':
67
63
  return 'justify-center';
68
64
  case 'right':
69
65
  return 'justify-end';
70
- default: // 'left'
66
+ default:
71
67
  return 'justify-start';
72
68
  }
73
69
  };
@@ -80,11 +76,11 @@ export function GradientButton({
80
76
 
81
77
  if (onClick) {
82
78
  e.preventDefault();
83
-
79
+
84
80
  if (preventDoubleClick) {
85
81
  setIsLoading(true);
86
82
  }
87
-
83
+
88
84
  try {
89
85
  await onClick();
90
86
  } catch (error) {
@@ -98,10 +94,7 @@ export function GradientButton({
98
94
  };
99
95
 
100
96
  const isDisabled = disabled || isLoading;
101
-
102
97
  const displayTitle = isLoading ? actualLoadingText : title;
103
-
104
- // icon
105
98
  const iconProvided = icon !== undefined;
106
99
 
107
100
  const iconNode = (() => {
@@ -147,8 +140,6 @@ export function GradientButton({
147
140
  ? 'justify-center'
148
141
  : 'justify-start';
149
142
 
150
- // Base styles extracted from Button component + size="lg" (h-11 px-8)
151
- // Removed [&_svg] constraints
152
143
  const baseButtonStyles = "inline-flex items-center gap-2 whitespace-nowrap h-11 px-8 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
153
144
  const variantClassName = variant === 'soft'
154
145
  ? cn(
@@ -163,11 +154,11 @@ export function GradientButton({
163
154
  themeIconColor,
164
155
  'border border-neutral-200 shadow-sm hover:shadow-md hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800'
165
156
  )
166
- : cn(
167
- themeButtonGradientClass,
168
- themeButtonGradientHoverClass,
169
- 'text-white shadow-lg hover:shadow-xl'
170
- );
157
+ : cn(
158
+ themeButtonGradientClass,
159
+ themeButtonGradientHoverClass,
160
+ 'text-white shadow-lg hover:shadow-xl'
161
+ );
171
162
 
172
163
  const buttonClassName = cn(
173
164
  baseButtonStyles,
@@ -181,7 +172,6 @@ export function GradientButton({
181
172
  return (
182
173
  <div className={`flex flex-row gap-3 ${getAlignmentClass()}`}>
183
174
  {onClick ? (
184
- // for click
185
175
  <button
186
176
  type="button"
187
177
  className={buttonClassName}
@@ -191,7 +181,6 @@ export function GradientButton({
191
181
  {buttonContent}
192
182
  </button>
193
183
  ) : (
194
- // for Link
195
184
  <Link
196
185
  href={href || "#"}
197
186
  className={cn(buttonClassName, "no-underline hover:no-underline")}
@@ -204,4 +193,4 @@ export function GradientButton({
204
193
  )}
205
194
  </div>
206
195
  );
207
- }
196
+ }
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+
3
+ export * from './gradient-button';
4
+ export * from './x-button';
5
+ export * from './x-toggle-button';
@@ -7,7 +7,6 @@ import { cn } from '@windrun-huaiin/lib/utils'
7
7
 
8
8
  type XButtonVariant = 'default' | 'soft' | 'subtle'
9
9
 
10
- // base button config
11
10
  interface BaseButtonConfig {
12
11
  icon: ReactNode
13
12
  text: string
@@ -15,7 +14,6 @@ interface BaseButtonConfig {
15
14
  disabled?: boolean
16
15
  }
17
16
 
18
- // menu item config
19
17
  interface MenuItemConfig extends BaseButtonConfig {
20
18
  tag?: {
21
19
  text: string
@@ -24,7 +22,6 @@ interface MenuItemConfig extends BaseButtonConfig {
24
22
  splitTopBorder?: boolean
25
23
  }
26
24
 
27
- // single button config
28
25
  interface SingleButtonProps {
29
26
  type: 'single'
30
27
  button: BaseButtonConfig
@@ -35,7 +32,6 @@ interface SingleButtonProps {
35
32
  variant?: XButtonVariant
36
33
  }
37
34
 
38
- // split button config
39
35
  interface SplitButtonProps {
40
36
  type: 'split'
41
37
  mainButton: BaseButtonConfig
@@ -77,7 +73,6 @@ export function XButton(props: xButtonProps) {
77
73
  return icon;
78
74
  };
79
75
 
80
- // click outside to close menu
81
76
  useEffect(() => {
82
77
  if (props.type === 'split') {
83
78
  const handleClickOutside = (event: MouseEvent) => {
@@ -96,7 +91,6 @@ export function XButton(props: xButtonProps) {
96
91
  }
97
92
  }, [menuOpen, props.type])
98
93
 
99
- // handle button click
100
94
  const handleButtonClick = async (onClick: () => void | Promise<void>) => {
101
95
  if (isLoading) return
102
96
 
@@ -110,7 +104,6 @@ export function XButton(props: xButtonProps) {
110
104
  }
111
105
  }
112
106
 
113
- // base style class
114
107
  const baseButtonClass = "flex items-center justify-center gap-2 px-4 py-2 text-sm font-semibold transition-colors"
115
108
  const singleButtonVariantClass = variant === 'soft'
116
109
  ? cn(
@@ -125,7 +118,7 @@ export function XButton(props: xButtonProps) {
125
118
  themeIconColor,
126
119
  "border border-neutral-200 hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800"
127
120
  )
128
- : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700"
121
+ : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700"
129
122
  const splitMainButtonVariantClass = variant === 'soft'
130
123
  ? cn(
131
124
  "bg-transparent hover:bg-black/5 dark:hover:bg-white/5",
@@ -136,7 +129,7 @@ export function XButton(props: xButtonProps) {
136
129
  "bg-transparent hover:bg-neutral-50 dark:hover:bg-neutral-800",
137
130
  themeIconColor
138
131
  )
139
- : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700"
132
+ : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700"
140
133
  const splitDropdownVariantClass = variant === 'soft'
141
134
  ? cn(
142
135
  "bg-transparent hover:bg-black/5 dark:hover:bg-white/5 sm:border-l",
@@ -149,13 +142,12 @@ export function XButton(props: xButtonProps) {
149
142
  themeIconColor,
150
143
  "border-neutral-200 dark:border-neutral-800"
151
144
  )
152
- : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700 sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700"
145
+ : "bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-white hover:bg-neutral-300 dark:hover:bg-neutral-700 sm:border-l sm:border-neutral-300 sm:dark:border-neutral-700"
153
146
  const disabledClass = "opacity-60 cursor-not-allowed"
154
147
 
155
148
  if (props.type === 'single') {
156
149
  const { button, loadingText, minWidth = 'min-w-[110px]', className = '' } = props
157
150
  const isDisabled = button.disabled || isLoading
158
- // loadingText: props.loadingText > button.text > 'Loading...'
159
151
  const actualLoadingText = loadingText || button.text?.trim() || 'Loading...'
160
152
 
161
153
  return (
@@ -188,36 +180,28 @@ export function XButton(props: xButtonProps) {
188
180
  )
189
181
  }
190
182
 
191
- // Split button
192
183
  const { mainButton, menuItems, loadingText, menuWidth = 'w-full sm:w-40', className = '', mainButtonClassName = '', dropdownButtonClassName = '' } = props
193
184
  const isMainDisabled = mainButton.disabled || isLoading
194
- // loadingText prioty:props.loadingText > mainButton.text > 'Loading...'
195
185
  const actualLoadingText = loadingText || mainButton.text?.trim() || 'Loading...'
196
186
 
197
187
  return (
198
188
  <div className={cn(
199
189
  "relative flex flex-row items-stretch w-full sm:w-auto rounded-full gap-0",
200
190
  menuOpen && "z-90",
201
- variant === 'soft'
202
- ? cn(themeBgColor, themeBorderColor, "border")
203
- : variant === 'subtle'
204
- ? cn(themeMainBgColor, "border border-neutral-200 dark:border-neutral-800")
205
- : "bg-neutral-200 dark:bg-neutral-800",
206
191
  className
207
192
  )}>
208
- {/* left main button */}
209
193
  <button
210
194
  onClick={() => handleButtonClick(mainButton.onClick)}
211
195
  disabled={isMainDisabled}
212
196
  className={cn(
213
- "min-w-0 flex-1",
197
+ "flex-1 min-w-0 sm:min-w-[100px] sm:flex-initial rounded-l-full",
214
198
  baseButtonClass,
215
199
  splitMainButtonVariantClass,
216
- "rounded-l-full rounded-r-none",
217
200
  isMainDisabled && disabledClass,
218
201
  mainButtonClassName
219
202
  )}
220
203
  onMouseDown={e => { if (e.button === 2) e.preventDefault() }}
204
+ title={mainButton.text}
221
205
  >
222
206
  {isLoading ? (
223
207
  <>
@@ -227,52 +211,54 @@ export function XButton(props: xButtonProps) {
227
211
  ) : (
228
212
  <>
229
213
  {renderIcon(mainButton.icon)}
230
- <span className="min-w-0 truncate">{mainButton.text}</span>
214
+ <span>{mainButton.text}</span>
231
215
  </>
232
216
  )}
233
217
  </button>
234
218
 
235
- {/* right dropdown button */}
236
219
  <button
237
220
  type="button"
221
+ onClick={() => setMenuOpen(!menuOpen)}
222
+ disabled={isLoading}
238
223
  className={cn(
239
- "flex h-full w-9 shrink-0 items-center justify-center px-0 py-1.5 cursor-pointer transition rounded-r-full rounded-l-none border-l sm:w-10",
224
+ "w-12 rounded-r-full",
225
+ baseButtonClass,
240
226
  splitDropdownVariantClass,
227
+ isLoading && disabledClass,
241
228
  dropdownButtonClassName
242
229
  )}
243
- onClick={e => { e.stopPropagation(); setMenuOpen(v => !v) }}
244
- aria-label="More actions"
245
- aria-expanded={menuOpen}
230
+ aria-label="Open menu"
246
231
  >
247
- <ChevronDownIcon className={chevronIconClass} />
232
+ <ChevronDownIcon className={cn(chevronIconClass, menuOpen && "rotate-180", "transition-transform")} />
248
233
  </button>
249
234
 
250
- {/* dropdown menu */}
251
235
  {menuOpen && (
252
236
  <div
253
237
  ref={menuRef}
254
- className={`absolute right-0 top-full ${menuWidth} bg-white dark:bg-neutral-800 text-neutral-800 dark:text-white text-sm rounded-xl shadow-lg z-100 border border-neutral-200 dark:border-neutral-700 overflow-hidden animate-fade-in`}
238
+ className={cn(
239
+ "absolute top-full right-0 mt-2 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow-lg z-50 overflow-hidden",
240
+ menuWidth
241
+ )}
255
242
  >
256
243
  {menuItems.map((item, index) => (
257
244
  <button
258
245
  key={index}
246
+ type="button"
259
247
  onClick={() => {
260
- handleButtonClick(item.onClick)
261
248
  setMenuOpen(false)
249
+ handleButtonClick(item.onClick)
262
250
  }}
263
- disabled={item.disabled}
264
- className={`flex items-center w-full px-4 py-3 transition hover:bg-neutral-300 dark:hover:bg-neutral-600 text-left relative ${item.disabled ? disabledClass : ''}`}
265
- style={item.splitTopBorder ? { borderTop: '1px solid #AC62FD' } : undefined}
251
+ disabled={item.disabled || isLoading}
252
+ className={cn(
253
+ "w-full flex items-center gap-2 px-3 py-2 text-sm text-left hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors",
254
+ item.disabled && disabledClass,
255
+ item.splitTopBorder && "border-t border-neutral-200 dark:border-neutral-700"
256
+ )}
266
257
  >
267
- <span className="flex items-center">
268
- {item.icon}
269
- <span>{item.text}</span>
270
- </span>
258
+ {renderIcon(item.icon)}
259
+ <span className="flex-1">{item.text}</span>
271
260
  {item.tag && (
272
- <span
273
- className="absolute right-3 top-1 text-[10px] font-semibold"
274
- style={{ color: item.tag.color || '#A855F7', pointerEvents: 'none' }}
275
- >
261
+ <span className={cn("px-1.5 py-0.5 text-xs rounded", item.tag.color || "bg-blue-100 text-blue-800")}>
276
262
  {item.tag.text}
277
263
  </span>
278
264
  )}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useClerk } from '@clerk/nextjs';
4
- import { GradientButton } from '@third-ui/fuma/mdx/gradient-button';
4
+ import { GradientButton } from '../buttons';
5
5
  import {
6
6
  BellIcon,
7
7
  ChevronDownIcon,