@windrun-huaiin/third-ui 3.2.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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/dist/clerk/index.d.mts +33 -0
  4. package/dist/clerk/index.d.ts +33 -0
  5. package/dist/clerk/index.js +2395 -0
  6. package/dist/clerk/index.js.map +1 -0
  7. package/dist/clerk/index.mjs +2361 -0
  8. package/dist/clerk/index.mjs.map +1 -0
  9. package/dist/cta.css +16 -0
  10. package/dist/fuma/index.d.mts +51 -0
  11. package/dist/fuma/index.d.ts +51 -0
  12. package/dist/fuma/index.js +2976 -0
  13. package/dist/fuma/index.js.map +1 -0
  14. package/dist/fuma/index.mjs +2944 -0
  15. package/dist/fuma/index.mjs.map +1 -0
  16. package/dist/fuma/mdx/index.d.mts +94 -0
  17. package/dist/fuma/mdx/index.d.ts +94 -0
  18. package/dist/fuma/mdx/index.js +2866 -0
  19. package/dist/fuma/mdx/index.js.map +1 -0
  20. package/dist/fuma/mdx/index.mjs +2827 -0
  21. package/dist/fuma/mdx/index.mjs.map +1 -0
  22. package/dist/fuma.css +132 -0
  23. package/dist/index.d.mts +5 -0
  24. package/dist/index.d.ts +5 -0
  25. package/dist/index.js +3597 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/index.mjs +3549 -0
  28. package/dist/index.mjs.map +1 -0
  29. package/dist/lib/index.d.mts +4414 -0
  30. package/dist/lib/index.d.ts +4414 -0
  31. package/dist/lib/index.js +127 -0
  32. package/dist/lib/index.js.map +1 -0
  33. package/dist/lib/index.mjs +95 -0
  34. package/dist/lib/index.mjs.map +1 -0
  35. package/dist/main/index.d.mts +25 -0
  36. package/dist/main/index.d.ts +25 -0
  37. package/dist/main/index.js +3003 -0
  38. package/dist/main/index.js.map +1 -0
  39. package/dist/main/index.mjs +2963 -0
  40. package/dist/main/index.mjs.map +1 -0
  41. package/dist/third-ui.css +44 -0
  42. package/package.json +106 -0
  43. package/src/clerk/clerk-organization.tsx +47 -0
  44. package/src/clerk/clerk-page-generator.tsx +42 -0
  45. package/src/clerk/clerk-provider-client.tsx +57 -0
  46. package/src/clerk/clerk-user.tsx +59 -0
  47. package/src/clerk/index.ts +5 -0
  48. package/src/fuma/fuma-banner-suit.tsx +16 -0
  49. package/src/fuma/fuma-github-info.tsx +194 -0
  50. package/src/fuma/fuma-page-genarator.tsx +94 -0
  51. package/src/fuma/index.ts +4 -0
  52. package/src/fuma/mdx/airtical-card.tsx +56 -0
  53. package/src/fuma/mdx/gradient-button.tsx +62 -0
  54. package/src/fuma/mdx/image-grid.tsx +35 -0
  55. package/src/fuma/mdx/image-zoom.tsx +84 -0
  56. package/src/fuma/mdx/index.ts +8 -0
  57. package/src/fuma/mdx/mermaid.tsx +87 -0
  58. package/src/fuma/mdx/toc-base.tsx +88 -0
  59. package/src/fuma/mdx/toc.tsx +35 -0
  60. package/src/fuma/mdx/trophy-card.tsx +36 -0
  61. package/src/fuma/mdx/zia-card.tsx +46 -0
  62. package/src/index.ts +4 -0
  63. package/src/lib/clerk-intl.ts +13 -0
  64. package/src/lib/fuma-schema-check-util.ts +73 -0
  65. package/src/lib/fuma-search-util.ts +6 -0
  66. package/src/lib/index.ts +3 -0
  67. package/src/main/ads-alert-dialog.tsx +133 -0
  68. package/src/main/cta.tsx +28 -0
  69. package/src/main/faq.tsx +58 -0
  70. package/src/main/features.tsx +35 -0
  71. package/src/main/footer.tsx +37 -0
  72. package/src/main/gallery.tsx +68 -0
  73. package/src/main/go-to-top.tsx +44 -0
  74. package/src/main/index.ts +12 -0
  75. package/src/main/loading.tsx +93 -0
  76. package/src/main/nprogress-bar.tsx +24 -0
  77. package/src/main/seo-content.tsx +34 -0
  78. package/src/main/tips.tsx +38 -0
  79. package/src/main/usage.tsx +45 -0
  80. package/src/styles/cta.css +16 -0
  81. package/src/styles/fuma.css +132 -0
  82. package/src/styles/third-ui.css +43 -0
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@lib/utils';
4
+ import Link from 'fumadocs-core/link';
5
+ import type { HTMLAttributes, ReactNode } from 'react';
6
+
7
+ export function AirticalCards(props: HTMLAttributes<HTMLDivElement>) {
8
+ return (
9
+ <div
10
+ {...props}
11
+ className={cn('grid grid-cols-2 gap-4 @container', props.className)}
12
+ >
13
+ {props.children}
14
+ </div>
15
+ );
16
+ }
17
+
18
+ export type AirticalCardProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
19
+ icon?: ReactNode;
20
+ title: ReactNode;
21
+ description?: ReactNode;
22
+
23
+ href?: string;
24
+ external?: boolean;
25
+ };
26
+
27
+ export function AirticalCard({ icon, title, description, ...props }: AirticalCardProps) {
28
+ const E = props.href ? Link : 'div';
29
+
30
+ return (
31
+ <E
32
+ {...props}
33
+ data-card
34
+ className={cn(
35
+ 'block rounded-lg border bg-fd-card p-4 text-fd-card-foreground shadow-md transition-colors @max-lg:col-span-full',
36
+ props.href && 'hover:bg-fd-accent/80',
37
+ props.className,
38
+ )}
39
+ >
40
+ {icon ? (
41
+ <div className="not-prose mb-2 w-fit rounded-md border bg-fd-muted p-1.5 text-fd-muted-foreground [&_svg]:size-4">
42
+ {icon}
43
+ </div>
44
+ ) : null}
45
+ <h3 className="not-prose mb-1 text-sm font-medium">{title}</h3>
46
+ {description ? (
47
+ <p className="!my-0 text-sm text-fd-muted-foreground">{description}</p>
48
+ ) : null}
49
+ {props.children ? (
50
+ <div className="text-sm text-fd-muted-foreground prose-no-margin">
51
+ {props.children}
52
+ </div>
53
+ ) : null}
54
+ </E>
55
+ );
56
+ }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ import { Button } from "@base-ui/ui/button";
4
+ import { globalLucideIcons as icons } from "@base-ui/components/global-icon";
5
+ import Link from "fumadocs-core/link";
6
+ import React from 'react';
7
+
8
+ export function GradientButton({
9
+ title,
10
+ icon,
11
+ href,
12
+ align = 'left',
13
+ }: {
14
+ title: React.ReactNode;
15
+ icon?: React.ReactNode;
16
+ href: string;
17
+ align?: 'left' | 'center' | 'right';
18
+ }) {
19
+ // set justify class according to alignment
20
+ const getAlignmentClass = () => {
21
+ switch (align) {
22
+ case 'center':
23
+ return 'justify-center';
24
+ case 'right':
25
+ return 'justify-end';
26
+ default: // 'left'
27
+ return 'justify-start';
28
+ }
29
+ };
30
+
31
+ return (
32
+ <div className={`flex flex-col sm:flex-row gap-3 ${getAlignmentClass()}`}>
33
+ <Button
34
+ asChild
35
+ size="lg"
36
+ className="
37
+ bg-gradient-to-r
38
+ from-purple-400 to-pink-500
39
+ hover:from-purple-500 hover:to-pink-600
40
+ dark:from-purple-500 dark:to-pink-600
41
+ dark:hover:from-purple-600 dark:hover:to-pink-700
42
+ text-white text-base font-bold shadow-lg hover:shadow-xl
43
+ transition-all duration-300
44
+ rounded-full
45
+ "
46
+ >
47
+ <Link href={href} target="_blank" rel="noopener noreferrer" className="no-underline hover:no-underline">
48
+ <span>{title}</span>
49
+ <span className="ml-1">
50
+ {icon ?
51
+ React.cloneElement(icon as React.ReactElement<{ className?: string }>, {
52
+ className: "h-4 w-4 text-white"
53
+ }) :
54
+ <icons.ArrowRight className="h-4 w-4 text-white" />
55
+ }
56
+ </span>
57
+ </Link>
58
+ </Button>
59
+ </div>
60
+
61
+ );
62
+ }
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { ImageZoom } from '@third-ui/fuma/mdx/image-zoom';
4
+
5
+ export function ImageGrid({
6
+ type="url",
7
+ images,
8
+ altPrefix = '',
9
+ cdnBaseUrl,
10
+ }: {
11
+ type: "url" | "local";
12
+ images: string[];
13
+ altPrefix?: string;
14
+ cdnBaseUrl?: string;
15
+ }) {
16
+ return (
17
+ <div
18
+ style={{
19
+ display: 'grid',
20
+ gridTemplateColumns: 'repeat(2, 1fr)',
21
+ gap: '16px',
22
+ justifyItems: 'center',
23
+ alignItems: 'center',
24
+ }}
25
+ >
26
+ {images.map((img, idx) => (
27
+ <ImageZoom
28
+ key={img}
29
+ src={type === "url" ? `${cdnBaseUrl}/${img}` : img}
30
+ alt={`${altPrefix}-${idx+1}`}
31
+ />
32
+ ))}
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import { Image, type ImageProps } from 'fumadocs-core/framework';
4
+ import { type ImgHTMLAttributes, useState } from 'react';
5
+ import Zoom, { type UncontrolledProps } from 'react-medium-image-zoom';
6
+
7
+ export type ImageZoomProps = ImageProps & {
8
+ /**
9
+ * Image props when zoom in
10
+ */
11
+ zoomInProps?: ImgHTMLAttributes<HTMLImageElement>;
12
+
13
+ /**
14
+ * Props for `react-medium-image-zoom`
15
+ */
16
+ rmiz?: UncontrolledProps;
17
+
18
+ /**
19
+ * placeholder image path
20
+ */
21
+ fallbackSrc?: string;
22
+ };
23
+
24
+ function getImageSrc(src: ImageProps['src']): string {
25
+ if (typeof src === 'string') return src;
26
+
27
+ if (typeof src === 'object') {
28
+ // Next.js
29
+ if ('default' in src)
30
+ return (src as { default: { src: string } }).default.src;
31
+ return src.src;
32
+ }
33
+
34
+ return '';
35
+ }
36
+
37
+ /**
38
+ * @example
39
+ * <ImageZoom src="URL" fallbackSrc="/my-placeholder.png" />
40
+ */
41
+ export function ImageZoom({
42
+ zoomInProps,
43
+ children,
44
+ rmiz,
45
+ fallbackSrc = '/default.webp',
46
+ ...props
47
+ }: ImageZoomProps) {
48
+ const [imgSrc, setImgSrc] = useState(getImageSrc(props.src));
49
+
50
+ // fallback logic
51
+ const handleError = () => {
52
+ console.warn('ImageZoom check error:', imgSrc, fallbackSrc);
53
+ if (imgSrc !== fallbackSrc) {
54
+ setImgSrc(fallbackSrc);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <Zoom
60
+ zoomMargin={20}
61
+ wrapElement="span"
62
+ {...rmiz}
63
+ zoomImg={{
64
+ src: imgSrc,
65
+ sizes: undefined,
66
+ ...zoomInProps,
67
+ onError: handleError,
68
+ }}
69
+ >
70
+ {children ?? (
71
+ <Image
72
+ {...props}
73
+ src={imgSrc}
74
+ onError={handleError}
75
+ sizes="(max-width: 400px) 100vw, 300px"
76
+ style={{ width: '100%', height: 'auto', maxWidth: 300 }}
77
+ alt={props.alt ?? ''}
78
+ width={300}
79
+ height={225}
80
+ />
81
+ )}
82
+ </Zoom>
83
+ );
84
+ }
@@ -0,0 +1,8 @@
1
+ export * from './mermaid';
2
+ export * from './image-zoom';
3
+ export * from './trophy-card';
4
+ export * from './image-grid';
5
+ export * from './zia-card';
6
+ export * from './gradient-button';
7
+ export * from './toc';
8
+ export * from './toc-base';
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+
3
+ import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
4
+ import type { MermaidConfig } from 'mermaid';
5
+ import { useTheme } from 'next-themes';
6
+ import { useEffect, useId, useState } from 'react';
7
+
8
+ interface MermaidProps {
9
+ chart: string;
10
+ title?: string;
11
+ watermarkEnabled?: boolean;
12
+ watermarkText?: string;
13
+ }
14
+
15
+ export function Mermaid({ chart, title, watermarkEnabled, watermarkText }: MermaidProps) {
16
+ const id = useId();
17
+ const [svg, setSvg] = useState('');
18
+ const { resolvedTheme } = useTheme();
19
+
20
+ useEffect(() => {
21
+ let isMounted = true;
22
+ void renderChart();
23
+
24
+ async function renderChart() {
25
+ const mermaidConfig: MermaidConfig = {
26
+ startOnLoad: false,
27
+ securityLevel: 'loose',
28
+ fontFamily: 'inherit',
29
+ themeCSS: 'margin: 1.5rem auto 0;',
30
+ theme: resolvedTheme === 'dark' ? 'dark' : 'default',
31
+ };
32
+
33
+ const { default: mermaid } = await import('mermaid');
34
+
35
+ try {
36
+ mermaid.initialize(mermaidConfig);
37
+ const { svg } = await mermaid.render(
38
+ id.replaceAll(':', ''),
39
+ chart.replaceAll('\\n', '\n')
40
+ );
41
+ let svgWithWatermark = svg;
42
+ if (watermarkEnabled && watermarkText) {
43
+ svgWithWatermark = addWatermarkToSvg(svg, watermarkText);
44
+ }
45
+ if (isMounted) setSvg(svgWithWatermark);
46
+ } catch (error) {
47
+ console.error('Error while rendering mermaid', error);
48
+ }
49
+ }
50
+ return () => {
51
+ isMounted = false;
52
+ setSvg('');
53
+ };
54
+ }, [chart, id, resolvedTheme, watermarkEnabled, watermarkText]);
55
+
56
+ return (
57
+ <div>
58
+ <div dangerouslySetInnerHTML={{ __html: svg }} />
59
+ {title && (
60
+ <div
61
+ className="mt-2 flex items-center justify-center text-center text-[13px] font-italic text-[#AC62FD]"
62
+ >
63
+ <icons.Mmd className='mr-1 h-4 w-4' />
64
+ <span>{title}</span>
65
+ </div>
66
+ )}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ function addWatermarkToSvg(svg: string, watermark: string) {
72
+ const watermarkText = `
73
+ <text
74
+ x="100%"
75
+ y="98%"
76
+ text-anchor="end"
77
+ font-size="12"
78
+ font-style="italic"
79
+ fill="#AC62FD"
80
+ opacity="0.40"
81
+ class="pointer-events-none"
82
+ dx="-8"
83
+ dy="-4"
84
+ >${watermark}</text>
85
+ `;
86
+ return svg.replace('</svg>', `${watermarkText}</svg>`);
87
+ }
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import { useParams } from 'next/navigation';
4
+ import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button';
5
+ import Link from 'fumadocs-core/link';
6
+ import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
7
+ import { Button } from '@base-ui/ui/button';
8
+
9
+ const cache = new Map<string, string>();
10
+
11
+ export function LLMCopyButton({ llmApiUrl }: { llmApiUrl?: string } = {}) {
12
+ const [isLoading, setLoading] = useState(false);
13
+ const params = useParams();
14
+ const locale = params.locale as string;
15
+ const slug = params.slug as string[];
16
+
17
+ const [checked, onClick] = useCopyButton(async () => {
18
+ setLoading(true);
19
+
20
+ // Handle cases where slug might be undefined or empty
21
+ const path = (slug && Array.isArray(slug)) ? slug.join('/') : '';
22
+ const apiPrefix = llmApiUrl || '/api/llm-content';
23
+ const apiUrl = `${apiPrefix}?locale=${encodeURIComponent(locale)}&path=${encodeURIComponent(path)}`;
24
+ console.log('Fetching LLM content from:', apiUrl);
25
+
26
+ try {
27
+ const content: string =
28
+ cache.get(apiUrl) ?? (await fetch(apiUrl).then((res) => {
29
+ if (!res.ok) {
30
+ throw new Error(`Failed to fetch LLM content: ${res.status} ${res.statusText}`);
31
+ }
32
+ return res.text();
33
+ }));
34
+
35
+ cache.set(apiUrl, content);
36
+ await navigator.clipboard.writeText(content);
37
+ } catch (error) {
38
+ console.error("Error fetching or copying LLM content:", error);
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ });
43
+
44
+ return (
45
+ <Button
46
+ variant="ghost"
47
+ size="sm"
48
+ loading={isLoading}
49
+ // force button to left align
50
+ className="justify-start px-0 text-stone-600 hover:text-stone-500 dark:text-stone-400 dark:hover:text-stone-300"
51
+ onClick={onClick}
52
+ >
53
+ {checked ? (
54
+ <>
55
+ <icons.Check/>
56
+ Copied!
57
+ </>
58
+ ) : (
59
+ <>
60
+ <icons.Markdown/>
61
+ Copy page as Markdown
62
+ </>
63
+ )}
64
+ </Button>
65
+ );
66
+ }
67
+
68
+ export function EditOnGitHub({ url }: { url: string }) {
69
+ return (
70
+ <Link
71
+ className="flex items-center gap-x-2 text-stone-600 hover:text-stone-500 dark:text-stone-400 dark:hover:text-stone-300 text-sm"
72
+ href={url}
73
+ >
74
+ <icons.GitHub/>
75
+ Edit this page on GitHub
76
+ </Link>
77
+ );
78
+ }
79
+
80
+ // New component for displaying the last updated date with an icon
81
+ export function LastUpdatedDate({ date }: { date: string | undefined }) {
82
+ return (
83
+ <div className="flex items-center gap-x-2 text-stone-600 dark:text-stone-400 text-sm">
84
+ <icons.LastUpdated/>
85
+ Lastest on {date ? date : "Ages ago"}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { EditOnGitHub, LastUpdatedDate, LLMCopyButton } from '@third-ui/fuma/mdx/toc-base';
4
+
5
+ interface TocFooterProps {
6
+ /**
7
+ * The last modified date of the page.
8
+ */
9
+ lastModified: string | undefined;
10
+ /**
11
+ * The path to the file for the \"Edit on GitHub\" link.
12
+ * This should be the relative path from the repository root, e.g., 'src/mdx/docs/your-page.mdx'.
13
+ */
14
+ editPath?: string | undefined;
15
+ /**
16
+ * Whether to show the copy button.
17
+ */
18
+ showCopy?: boolean | undefined;
19
+ /**
20
+ * GitHub base URL for edit links
21
+ */
22
+ githubBaseUrl?: string;
23
+ }
24
+
25
+ export function TocFooter({ lastModified, showCopy, editPath, githubBaseUrl }: TocFooterProps) {
26
+ const showEdit = githubBaseUrl && editPath;
27
+
28
+ return (
29
+ <div className="flex flex-col gap-y-2 items-start m-4">
30
+ <LastUpdatedDate date={lastModified} />
31
+ {showCopy && <LLMCopyButton />}
32
+ {showEdit && <EditOnGitHub url={`${githubBaseUrl}${editPath}`} />}
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export function TrophyCard({
6
+ icon,
7
+ title,
8
+ children,
9
+ }: {
10
+ icon?: React.ReactNode;
11
+ title: React.ReactNode;
12
+ children?: React.ReactNode;
13
+ }) {
14
+ return (
15
+ <div
16
+ className="
17
+ border-2 rounded-xl px-4 py-2
18
+ border-purple-200 dark:border-gray-500
19
+ "
20
+ >
21
+ <div className="flex items-center font-bold text-sm">
22
+ <span className="mr-2">{icon}</span>
23
+ <span>{title}</span>
24
+ </div>
25
+ {/* leading-none:line-height: 1
26
+ leading-tight:line-height: 1.25
27
+ leading-snug:line-height: 1.375
28
+ leading-normal:line-height: 1.5
29
+ leading-relaxed:line-height: 1.625
30
+ leading-loose:line-height: 2 */}
31
+ <div className="text-sm -mt-1 leading-none">
32
+ {children}
33
+ </div>
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
4
+ import { cn } from '@lib/utils';
5
+ import Link from 'fumadocs-core/link';
6
+ import type { HTMLAttributes, ReactNode } from 'react';
7
+
8
+ export type ZiaCardProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
9
+ icon?: ReactNode;
10
+ title: ReactNode;
11
+ description?: ReactNode;
12
+
13
+ href?: string;
14
+ external?: boolean;
15
+ };
16
+
17
+ export function ZiaCard({ icon, title, description, ...props }: ZiaCardProps) {
18
+ const E = props.href ? Link : 'div';
19
+
20
+ return (
21
+ <E
22
+ {...props}
23
+ data-card
24
+ className={cn(
25
+ 'block rounded-lg border bg-fd-card p-4 text-fd-card-foreground shadow-md transition-colors @max-lg:col-span-full',
26
+ props.href && 'hover:bg-fd-accent/80',
27
+ props.className,
28
+ )}
29
+ >
30
+ <div className="not-prose mb-2 w-fit rounded-md border bg-fd-muted p-1.5 text-fd-muted-foreground [&_svg]:size-4">
31
+ {icon ? icon : <icons.CircleSmall />}
32
+ </div>
33
+ <h3 className="not-prose mb-1 text-sm font-medium line-clamp-2 min-h-[2.5rem]">{title}</h3>
34
+ {description ? (
35
+ <p className="!my-0 text-sm text-fd-muted-foreground">{description}</p>
36
+ ) : (
37
+ <p className="!my-0 text-sm text-fd-muted-foreground opacity-0 select-none">&nbsp;</p>
38
+ )}
39
+ {props.children ? (
40
+ <div className="text-sm text-fd-muted-foreground prose-no-margin">
41
+ {props.children}
42
+ </div>
43
+ ) : null}
44
+ </E>
45
+ );
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Re-export all modules
2
+ export * from './clerk';
3
+ export * from './main';
4
+ export * from './fuma';
@@ -0,0 +1,13 @@
1
+ import { enUS, zhCN } from '@clerk/localizations';
2
+ import type { LocalizationResource } from '@clerk/types';
3
+
4
+ // https://github.com/clerk/javascript/blob/main/packages/localizations/src/en-US.ts#L492
5
+ // https://clerk.com/docs/customization/localization
6
+ const customZH: LocalizationResource = {
7
+ ...zhCN,
8
+ }
9
+
10
+ export const clerkIntl = {
11
+ en: enUS,
12
+ zh: customZH,
13
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ import { frontmatterSchema, metaSchema } from 'fumadocs-mdx/config';
3
+
4
+
5
+ // Reusable schema for title
6
+ export const createTitleSchema = () =>
7
+ z.string({
8
+ required_error: "Title is required",
9
+ invalid_type_error: "Title must be a string and cannot be null",
10
+ })
11
+ .trim()
12
+ .min(1, { message: "Title cannot be empty or consist only of whitespace" });
13
+
14
+ // Reusable schema for description
15
+ export const createDescriptionSchema = () =>
16
+ z.preprocess(
17
+ (val: any) => {
18
+ if (typeof val === 'string') {
19
+ return val.trim() === "" || val === null ? undefined : val.trim();
20
+ }
21
+ return val === null ? undefined : val;
22
+ },
23
+ z.string().optional()
24
+ );
25
+
26
+ // Reusable schema for icon
27
+ export const createIconSchema = () =>
28
+ z.preprocess(
29
+ (val: any) => (val === "" || val === null ? undefined : val),
30
+ z.string().optional()
31
+ );
32
+
33
+ // Reusable schema for date
34
+ export const createDateSchema = () =>
35
+ z.preprocess((arg: any) => {
36
+ if (arg instanceof Date) {
37
+ // Format Date object to YYYY-MM-DD string
38
+ const year = arg.getFullYear();
39
+ const month = (arg.getMonth() + 1).toString().padStart(2, '0');
40
+ const day = arg.getDate().toString().padStart(2, '0');
41
+ return `${year}-${month}-${day}`;
42
+ }
43
+ if (typeof arg === 'string') {
44
+ return arg.trim();
45
+ }
46
+ // For other types or null/undefined, let the subsequent string validation handle it
47
+ return arg;
48
+ },
49
+ z.string()
50
+ .regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format or a valid Date object")
51
+ .refine((val: any) => !isNaN(new Date(val).getTime()), 'Invalid date!')
52
+ );
53
+
54
+ // common docs frontmatter schema
55
+ export const createCommonDocsSchema = () => frontmatterSchema.extend({
56
+ title: createTitleSchema(),
57
+ description: createDescriptionSchema(),
58
+ icon: createIconSchema(),
59
+ date: createDateSchema(),
60
+ author: z.string().optional(),
61
+ keywords: z.array(z.string()).optional(),
62
+ });
63
+
64
+ // common meta schema
65
+ export const createCommonMetaSchema = () => metaSchema.extend({
66
+ description: z.string().optional(),
67
+ });
68
+
69
+ export const remarkInstallOptions = {
70
+ persist: {
71
+ id: 'package-manager',
72
+ },
73
+ };
@@ -0,0 +1,6 @@
1
+ import { Translations } from 'fumadocs-ui/i18n';
2
+
3
+ export const fumaI18nCn: Partial<Translations> = {
4
+ search: 'Translated Content',
5
+ // other translations
6
+ };
@@ -0,0 +1,3 @@
1
+ export * from './fuma-search-util';
2
+ export * from './clerk-intl';
3
+ export * from './fuma-schema-check-util';