git0 0.3.16 → 0.3.18
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/docs/README.md +20 -0
- package/docs/app/(home)/docs-home.css +235 -0
- package/docs/app/(home)/layout.tsx +12 -0
- package/docs/app/(home)/page.tsx +27 -0
- package/docs/app/docs/[[...slug]]/page.tsx +84 -0
- package/docs/app/docs/api/docs-search/route.ts +11 -0
- package/docs/app/docs/layout.tsx +16 -0
- package/docs/app/docs/llms-full.txt/route.ts +15 -0
- package/docs/app/docs/llms.mdx/docs/[[...slug]]/route.ts +24 -0
- package/docs/app/globals.css +814 -0
- package/docs/app/layout.config.tsx +32 -0
- package/docs/app/layout.tsx +22 -0
- package/docs/app/provider.tsx +20 -0
- package/docs/cli.json +11 -0
- package/docs/components/DocsHomepage/code-example.tsx +123 -0
- package/docs/components/DocsHomepage/features-grid.tsx +92 -0
- package/docs/components/DocsHomepage/footer.tsx +58 -0
- package/docs/components/DocsHomepage/hero-section.tsx +114 -0
- package/docs/components/DocsHomepage/project-types.tsx +114 -0
- package/docs/components/fumadocs/ai/ask-ai-dropdown.tsx +299 -0
- package/docs/components/fumadocs/ai/llm-copy-button.tsx +61 -0
- package/docs/components/fumadocs/api/api-page.client.tsx +7 -0
- package/docs/components/fumadocs/api/api-page.tsx +7 -0
- package/docs/components/fumadocs/layout/breadcrumb.tsx +36 -0
- package/docs/components/fumadocs/layout/search.tsx +55 -0
- package/docs/components/fumadocs/layout/theme-dropdown.tsx +3 -0
- package/docs/components/fumadocs/layout/theme-provider.tsx +15 -0
- package/docs/components/fumadocs/layout/theme-toggle.tsx +40 -0
- package/docs/components/ui/badge.tsx +50 -0
- package/docs/components/ui/button.tsx +66 -0
- package/docs/components/ui/collapsible.tsx +44 -0
- package/docs/components/ui/slider.tsx +36 -0
- package/docs/components/ui/tooltip.tsx +63 -0
- package/docs/components/ui/video-modal.tsx +238 -0
- package/docs/content/docs/fm.mdx +11 -0
- package/docs/content/docs/git0.mdx +104 -0
- package/docs/content/docs/github-api.mdx +764 -0
- package/docs/content/docs/index.mdx +98 -0
- package/docs/content/docs/meta.json +15 -0
- package/docs/content/docs/modules.mdx +10 -0
- package/docs/lib/fumadocs/customize-docs.ts +47 -0
- package/docs/lib/fumadocs/generate-api-docs.ts +73 -0
- package/docs/lib/fumadocs/source.tsx +50 -0
- package/docs/lib/utils.ts +10 -0
- package/docs/mdx-components.tsx +21 -0
- package/docs/next-env.d.ts +7 -0
- package/docs/next.config.ts +35 -0
- package/docs/package.json +61 -0
- package/docs/postcss.config.mjs +5 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/favicon-192.png +0 -0
- package/docs/public/favicon-512.png +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/public/manifest.webmanifest +14 -0
- package/docs/source.config.ts +29 -0
- package/docs/tsconfig.json +50 -0
- package/docs/vite.config.ts +2 -0
- package/docs/wrangler.jsonc +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ask-ai-dropdown.tsx
|
|
3
|
+
* @description Dropdown component providing links to various AI providers for page interaction.
|
|
4
|
+
*/
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
|
+
import { ChevronDown, ExternalLinkIcon } from "lucide-react";
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
import {
|
|
11
|
+
Popover,
|
|
12
|
+
PopoverContent,
|
|
13
|
+
PopoverTrigger as PopoverTriggerBase,
|
|
14
|
+
} from "fumadocs-ui/components/ui/popover";
|
|
15
|
+
import { cva } from "class-variance-authority";
|
|
16
|
+
import { buttonVariants } from "../../ui/button";
|
|
17
|
+
|
|
18
|
+
const PopoverTrigger = PopoverTriggerBase as any;
|
|
19
|
+
|
|
20
|
+
const optionVariants = cva(
|
|
21
|
+
"text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
interface AIProvider {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
getHref: (params: { q: string; githubUrl?: string }) => string;
|
|
28
|
+
icon?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const AI_PROVIDERS: AIProvider[] = [
|
|
32
|
+
{
|
|
33
|
+
id: "github",
|
|
34
|
+
title: "Open in GitHub",
|
|
35
|
+
getHref: ({ githubUrl }) => githubUrl || "",
|
|
36
|
+
icon: (
|
|
37
|
+
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
|
|
38
|
+
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
|
39
|
+
</svg>
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "qwksearch",
|
|
44
|
+
title: "QwkSearch",
|
|
45
|
+
getHref: ({ q }) => `https://qwksearch.com/?q=${new URLSearchParams({ q })}`,
|
|
46
|
+
icon: (
|
|
47
|
+
<svg
|
|
48
|
+
role="img"
|
|
49
|
+
viewBox="0 0 24 24"
|
|
50
|
+
fill="currentColor"
|
|
51
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
>
|
|
53
|
+
<path
|
|
54
|
+
fillRule="evenodd"
|
|
55
|
+
clipRule="evenodd"
|
|
56
|
+
d="
|
|
57
|
+
M10 1
|
|
58
|
+
a9 9 0 1 0 0 18
|
|
59
|
+
a9 9 0 0 0 0 -18
|
|
60
|
+
z
|
|
61
|
+
M10 3.7
|
|
62
|
+
a6.3 6.3 0 1 1 0 12.6
|
|
63
|
+
a6.3 6.3 0 0 1 0 -12.6
|
|
64
|
+
z
|
|
65
|
+
"
|
|
66
|
+
/>
|
|
67
|
+
<path
|
|
68
|
+
d="M15.5 15.5 L20.8 20.8"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
strokeWidth="2.9"
|
|
71
|
+
strokeLinecap="round"
|
|
72
|
+
fill="none"
|
|
73
|
+
/>
|
|
74
|
+
<path
|
|
75
|
+
d="M4.3 4.3 L1.5 1.5"
|
|
76
|
+
stroke="currentColor"
|
|
77
|
+
strokeWidth="2.1"
|
|
78
|
+
strokeLinecap="round"
|
|
79
|
+
fill="none"
|
|
80
|
+
/>
|
|
81
|
+
<circle cx="1.1" cy="1.1" r="1.4" />
|
|
82
|
+
<path
|
|
83
|
+
d="M15.7 4.3 L18.5 1.5"
|
|
84
|
+
stroke="currentColor"
|
|
85
|
+
strokeWidth="2.1"
|
|
86
|
+
strokeLinecap="round"
|
|
87
|
+
fill="none"
|
|
88
|
+
/>
|
|
89
|
+
<circle cx="18.9" cy="1.1" r="1.4" />
|
|
90
|
+
<path
|
|
91
|
+
d="M4.3 15.7 L1.5 18.5"
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
strokeWidth="2.1"
|
|
94
|
+
strokeLinecap="round"
|
|
95
|
+
fill="none"
|
|
96
|
+
/>
|
|
97
|
+
<circle cx="1.1" cy="18.9" r="1.4" />
|
|
98
|
+
</svg>
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "claude",
|
|
103
|
+
title: "Claude",
|
|
104
|
+
getHref: ({ q }) => `https://claude.ai/new?${new URLSearchParams({ q })}`,
|
|
105
|
+
icon: (
|
|
106
|
+
<svg
|
|
107
|
+
fill="currentColor"
|
|
108
|
+
role="img"
|
|
109
|
+
viewBox="0 0 24 24"
|
|
110
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
111
|
+
>
|
|
112
|
+
<path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z" />
|
|
113
|
+
</svg>
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "perplexity",
|
|
118
|
+
title: "Perplexity",
|
|
119
|
+
getHref: ({ q }) =>
|
|
120
|
+
`https://www.perplexity.ai/search?${new URLSearchParams({ q })}`,
|
|
121
|
+
icon: (
|
|
122
|
+
<svg
|
|
123
|
+
role="img"
|
|
124
|
+
viewBox="0 0 24 24"
|
|
125
|
+
fill="currentColor"
|
|
126
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
127
|
+
>
|
|
128
|
+
<path d="M22.3977 7.0896h-2.3106V.0676l-7.5094 6.3542V.1577h-1.1554v6.1966L4.4904 0v7.0896H1.6023v10.3976h2.8882V24l6.932-6.3591v6.2005h1.1554v-6.0469l6.9318 6.1807v-6.4879h2.8882V7.0896zm-3.4657-4.531v4.531h-5.355l5.355-4.531zm-13.2862.0676 4.8691 4.4634H5.6458V2.6262zM2.7576 16.332V8.245h7.8476l-6.1149 6.1147v1.9723H2.7576zm2.8882 5.0404v-3.8852h.0001v-2.6488l5.7763-5.7764v7.0111l-5.7764 5.2993zm12.7086.0248-5.7766-5.1509V9.0618l5.7766 5.7766v6.5588zm2.8882-5.0652h-1.733v-1.9723L13.3948 8.245h7.8478v8.087z" />
|
|
129
|
+
</svg>
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "chatgpt",
|
|
134
|
+
title: "ChatGPT",
|
|
135
|
+
getHref: ({ q }) =>
|
|
136
|
+
`https://chatgpt.com/?${new URLSearchParams({ hints: "search", q })}`,
|
|
137
|
+
icon: (
|
|
138
|
+
<svg
|
|
139
|
+
role="img"
|
|
140
|
+
viewBox="0 0 24 24"
|
|
141
|
+
fill="currentColor"
|
|
142
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
143
|
+
>
|
|
144
|
+
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
|
145
|
+
</svg>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "gemini",
|
|
150
|
+
title: "Gemini",
|
|
151
|
+
getHref: ({ q }) =>
|
|
152
|
+
`https://aistudio.google.com/prompts/new_chat?prompt=${encodeURIComponent(q)}`,
|
|
153
|
+
icon: (
|
|
154
|
+
<svg
|
|
155
|
+
role="img"
|
|
156
|
+
viewBox="0 0 24 24"
|
|
157
|
+
fill="currentColor"
|
|
158
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
159
|
+
>
|
|
160
|
+
<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" />
|
|
161
|
+
</svg>
|
|
162
|
+
),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "brave",
|
|
166
|
+
title: "Brave",
|
|
167
|
+
getHref: ({ q }) =>
|
|
168
|
+
`https://search.brave.com/ask?q=${encodeURIComponent(q)}`,
|
|
169
|
+
icon: (<svg fill="currentColor" viewBox="0 0 14 14" role="img">
|
|
170
|
+
<g strokeWidth="0"/>
|
|
171
|
+
<g strokeLinecap="round" strokeLinejoin="round"/>
|
|
172
|
+
<g >
|
|
173
|
+
<path d="m 6.5,12.77 c -.21,-.13 -.87,-.57 -1.48,-.98 -1.06,-.71 -1.5,-1.05 -1.45,-1.13 .01,-.02 -.01,-.05 -.05,-.06 -.04,-.01 -.06,-.04 -.04,-.07 .01,-.02 0,-.04 -.02,-.04 -.05,0 -.21,-.34 -.23,-.5 -.01,-.05 -.03,-.12 -.05,-.15 -.02,-.03 -.02,-.08 -0,-.1 .02,-.03 .01,-.06 -.01,-.08 -.02,-.01 -.06,-.12 -.09,-.23 -.03,-.11 -.09,-.35 -.14,-.53 -.05,-.18 -.1,-.37 -.11,-.43 -.01,-.05 -.12,-.48 -.24,-.95 -.12,-.47 -.3,-1.12 -.38,-1.45 -.09,-.33 -.19,-.73 -.24,-.9 l -.08,-.3 .19,-.46 c .21,-.51 .21,-.42 -.02,-1.01 l -.1,-.26 .44,-.44 c .45,-.46 .6,-.56 .95,-.65 .17,-.05 .24,-.04 .48,.02 l .28,.08 .25,-.26 c .14,-.14 .38,-.41 .55,-.58 l .3,-.32 1.82,0 1.82,0 .35,.36 c .19,.2 .44,.46 .55,.58 l .2,.22 .28,-.1 c .3,-.11 .34,-.11 .78,.09 .17,.07 1.02,.89 1.07,1.01 .01,.02 -.02,.13 -.07,.23 -.22,.47 -.22,.46 -.02,1 l .18,.49 -.3,1.16 c -.17,.64 -.4,1.53 -.52,2 -.48,1.88 -.54,2.08 -.65,2.29 -.25,.51 -.39,.62 -2.22,1.85 -1.42,.96 -1.38,.95 -1.95,.58 z m 1.07,-2.62 C 7.82,9.93 8.17,9.65 8.36,9.52 c .33,-.23 .4,-.34 .29,-.43 -.1,-.08 -.67,-.37 -1.07,-.54 -.39,-.16 -.73,-.25 -.73,-.19 0,.02 -.04,.04 -.1,.04 -.05,0 -.11,.03 -.11,.05 -.01,.03 -.03,.04 -.06,.02 -.02,-.01 -.04,0 -.04,.02 0,.03 -.03,.04 -.06,.02 -.03,-.01 -.09,.01 -.13,.04 -.04,.04 -.08,.05 -.09,.04 -.01,-.01 -.03,0 -.03,.03 0,.03 -.02,.04 -.04,.03 -.02,-.01 -.04,-.01 -.04,.02 0,.03 -.02,.04 -.04,.02 -.02,-.01 -.04,0 -.04,.02 0,.03 -.02,.04 -.04,.02 -.02,-.01 -.04,-.01 -.04,.02 0,.03 -.02,.04 -.04,.02 -.02,-.01 -.04,-.01 -.04,.02 0,.03 -.02,.04 -.04,.02 -.02,-.01 -.04,-.01 -.04,.02 0,.03 -.02,.04 -.04,.02 -.02,-.01 -.04,-.01 -.04,.02 0,.03 -.01,.04 -.03,.04 -.09,-.01 -.15,.01 -.26,.12 -.11,.1 -.11,.12 -.07,.22 .03,.06 .07,.1 .09,.09 .02,-.01 .05,.01 .07,.05 .01,.04 .04,.06 .06,.04 .01,-.01 .06,.01 .09,.06 .03,.05 .08,.08 .1,.07 .02,-.01 .05,.01 .06,.05 .01,.04 .04,.06 .05,.05 .01,-.01 .06,.02 .11,.07 .04,.05 .08,.08 .08,.06 0,-.02 .05,.03 .12,.1 .06,.08 .13,.13 .14,.12 .01,-.01 .09,.06 .18,.15 .09,.1 .17,.16 .18,.15 .01,-.01 .06,.02 .1,.08 .05,.06 .14,.1 .22,.11 .12,.01 .21,-.05 .59,-.38 z M 5.21,8.77 c 0,-.03 .02,-.04 .04,-.02 .05,.03 .36,-.11 .36,-.16 0,-.02 .02,-.03 .04,-.01 .02,.01 .04,.01 .04,-.02 0,-.03 .02,-.04 .04,-.02 .02,.01 .04,.01 .04,-.02 0,-.03 .02,-.04 .04,-.02 .02,.01 .04,0 .04,-.02 0,-.03 .02,-.04 .04,-.02 .02,.01 .04,.01 .04,-.02 0,-.03 .02,-.04 .04,-.02 .02,.01 .09,-.02 .15,-.08 .06,-.06 .12,-.1 .13,-.09 .01,.01 .1,-.07 .2,-.18 .1,-.11 .18,-.18 .18,-.17 0,.02 .05,-.02 .11,-.07 .06,-.06 .1,-.12 .09,-.14 -.01,-.02 -.01,-.04 .01,-.04 .05,0 .04,-.18 -0,-.26 -.04,-.07 -1.09,-.77 -1.09,-.73 0,.02 -.04,-.02 -.09,-.08 -.11,-.14 -.11,-.21 -0,-.43 .05,-.09 .07,-.18 .06,-.2 -.01,-.01 -0,-.03 .03,-.03 .03,0 .04,-.02 .03,-.04 -.01,-.02 -.01,-.04 .02,-.04 .03,0 .04,-.02 .02,-.04 -.01,-.02 -.01,-.04 .02,-.04 .08,0 .2,-.28 .2,-.5 0,-.12 -.01,-.21 -.03,-.21 -.02,0 -.03,-.02 -.01,-.04 .01,-.02 -.02,-.07 -.06,-.12 -.05,-.04 -.09,-.07 -.09,-.05 0,.02 -.03,0 -.07,-.04 -.04,-.04 -.09,-.06 -.11,-.04 -.02,.01 -.04,0 -.04,-.03 0,-.03 -.03,-.04 -.06,-.03 -.04,.01 -.07,0 -.08,-.02 -.01,-.02 -.06,-.05 -.11,-.05 -.05,0 -.1,-.03 -.1,-.05 0,-.02 -.03,-.03 -.06,-.02 -.03,.01 -.08,-.01 -.12,-.04 -.03,-.03 -.09,-.05 -.12,-.04 -.03,.01 -.06,0 -.06,-.02 0,-.03 -.03,-.04 -.07,-.02 -.04,.01 -.07,0 -.07,-.02 0,-.03 -.02,-.03 -.04,-.02 -.02,.01 -.05,0 -.07,-.02 -.01,-.03 -.05,-.03 -.07,-.02 -.02,.01 -.04,0 -.04,-.03 0,-.03 -.03,-.04 -.07,-.03 -.04,.01 -.07,.01 -.07,-.02 0,-.02 .08,-.04 .17,-.04 .09,0 .18,-.01 .2,-.03 .04,-.04 .73,-.04 .75,0 .01,.02 .07,.04 .14,.05 .07,.01 .17,.04 .22,.06 .05,.03 .18,.07 .29,.1 .18,.05 .2,.07 .2,.18 -0,.07 -.02,.14 -.04,.17 -.02,.03 -.02,.09 -0,.14 .02,.05 .01,.09 -.02,.09 -.03,0 -.04,.04 -.03,.11 .01,.06 0,.11 -.02,.11 -.02,0 -.03,.05 -.02,.11 .01,.06 0,.11 -.02,.11 -.02,0 -.03,.06 -.02,.13 .01,.07 0,.13 -.02,.13 -.02,0 -.03,.06 -.01,.13 .02,.08 .01,.13 -.02,.13 -.03,0 -.04,.04 -.02,.11 .01,.06 .01,.12 -.02,.14 -.07,.04 -.05,.24 .04,.34 .05,.05 .11,.08 .13,.06 .03,-.02 .05,-.01 .05,.02 0,.03 .04,.04 .09,.03 .05,-.01 .09,0 .09,.02 0,.03 .05,.03 .11,.02 .06,-.01 .12,-.01 .14,.02 .02,.03 .1,.04 .19,.02 .82,-.13 .93,-.17 .94,-.34 0,-.05 -.07,-.5 -.16,-.99 -.14,-.77 -.15,-.9 -.1,-.96 .04,-.03 .27,-.12 .53,-.18 .44,-.11 .5,-.12 1.03,-.09 .31,.02 .56,.04 .55,.05 -.01,.01 -.35,.14 -.76,.3 -1.16,.44 -1.18,.48 -.75,1.31 .16,.31 .28,.59 .27,.63 -.01,.04 -.31,.26 -.67,.49 -.35,.24 -.65,.46 -.67,.5 -.05,.12 .05,.26 .39,.56 .23,.21 .48,.37 .81,.54 .26,.13 .51,.24 .55,.24 .19,-0 .59,-.36 .72,-.64 .11,-.24 .1,-.45 -.03,-.75 -.06,-.14 -.11,-.31 -.11,-.39 0,-.15 .02,-.18 .96,-1.19 .57,-.61 .58,-.63 .58,-.82 0,-.11 -.03,-.26 -.07,-.34 -.13,-.26 -.51,-.8 -.91,-1.3 -.38,-.48 -.4,-.49 -.53,-.47 -.08,.01 -.33,.06 -.55,.11 -.56,.11 -.67,.11 -1.24,-.07 -.62,-.2 -.8,-.2 -1.42,0 -.27,.09 -.56,.16 -.65,.16 -.09,0 -.41,-.05 -.71,-.11 l -.55,-.11 -.18,.18 c -.1,.1 -.18,.19 -.17,.2 .01,.01 -.02,.05 -.07,.08 -.05,.03 -.08,.08 -.07,.1 .01,.02 -.03,.07 -.09,.11 -.06,.04 -.1,.09 -.09,.1 .01,.01 -.01,.05 -.06,.09 -.05,.03 -.08,.08 -.07,.1 .01,.02 -.01,.05 -.05,.06 -.04,.01 -.06,.04 -.04,.07 .01,.02 -.01,.05 -.04,.07 -.04,.01 -.06,.04 -.04,.07 .01,.02 -.01,.05 -.05,.07 -.04,.01 -.06,.04 -.05,.05 .01,.01 -.02,.07 -.08,.14 -.06,.06 -.1,.13 -.08,.15 .01,.02 -.01,.05 -.05,.06 -.04,.01 -.06,.05 -.04,.07 .01,.02 .01,.04 -.02,.04 -.03,0 -.04,.02 -.02,.04 .01,.02 .01,.05 -.02,.07 -.06,.04 -.06,.38 0,.38 .03,0 .04,.02 .02,.04 -.01,.02 -.01,.04 .02,.04 .03,0 .05,.03 .05,.06 0,.03 .08,.14 .19,.23 .1,.09 .18,.18 .18,.2 -.01,.02 .15,.18 .34,.36 .19,.18 .34,.34 .33,.35 -.01,.01 .07,.1 .18,.21 .11,.11 .2,.22 .2,.25 0,.03 .03,.06 .06,.08 .04,.02 .04,.03 -0,.06 -.03,.02 -.06,.09 -.07,.16 -0,.06 -.03,.12 -.05,.12 -.02,0 -.03,.01 -.01,.03 .01,.01 -0,.06 -.04,.09 -.04,.04 -.06,.09 -.04,.13 .01,.03 0,.06 -.02,.06 -.02,0 -.04,.07 -.04,.15 0,.09 .02,.15 .04,.15 .02,0 .03,.03 .02,.07 -.01,.04 -0,.07 .03,.07 .03,0 .04,.02 .02,.04 -.01,.02 -.01,.04 .02,.04 .03,0 .04,.02 .02,.04 -.01,.02 -.01,.04 .02,.04 .03,0 .04,.01 .04,.03 -.01,.05 .37,.43 .41,.42 .02,-.01 .04,.01 .04,.04 0,.03 .02,.04 .04,.02 .02,-.01 .05,-.01 .07,.02 .04,.06 .15,.06 .15,0 z"/>
|
|
174
|
+
</g>
|
|
175
|
+
</svg>
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "cursor",
|
|
180
|
+
title: "Cursor",
|
|
181
|
+
getHref: ({ q }) =>
|
|
182
|
+
`https://cursor.com/link/prompt?text=${encodeURIComponent(q)}`,
|
|
183
|
+
icon: (
|
|
184
|
+
<svg
|
|
185
|
+
role="img"
|
|
186
|
+
viewBox="0 0 24 24"
|
|
187
|
+
fill="currentColor"
|
|
188
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
189
|
+
>
|
|
190
|
+
<path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" />
|
|
191
|
+
</svg>
|
|
192
|
+
),
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
export type AIProviderID = (typeof AI_PROVIDERS)[number]["id"];
|
|
197
|
+
|
|
198
|
+
export function AskAIDropdown({
|
|
199
|
+
markdownUrl,
|
|
200
|
+
githubUrl,
|
|
201
|
+
providers,
|
|
202
|
+
}: {
|
|
203
|
+
/**
|
|
204
|
+
* A URL to the raw Markdown/MDX content of page
|
|
205
|
+
*/
|
|
206
|
+
markdownUrl: string;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Source file URL on GitHub
|
|
210
|
+
*/
|
|
211
|
+
githubUrl?: string;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* List of provider IDs to show (e.g. ['claude', 'chatgpt']),
|
|
215
|
+
* or custom AIProvider objects, or a mix of both.
|
|
216
|
+
* Defaults to all providers.
|
|
217
|
+
*/
|
|
218
|
+
providers?: (AIProviderID | AIProvider)[];
|
|
219
|
+
}) {
|
|
220
|
+
const items = useMemo(() => {
|
|
221
|
+
const fullMarkdownUrl =
|
|
222
|
+
typeof window !== "undefined"
|
|
223
|
+
? new URL(markdownUrl, window.location.origin)
|
|
224
|
+
: "loading";
|
|
225
|
+
const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`;
|
|
226
|
+
|
|
227
|
+
const resolvedProviders: AIProvider[] = providers
|
|
228
|
+
? providers
|
|
229
|
+
.map((p) =>
|
|
230
|
+
typeof p === "string" ? AI_PROVIDERS.find((ap) => ap.id === p)! : p,
|
|
231
|
+
)
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
: AI_PROVIDERS;
|
|
234
|
+
|
|
235
|
+
return resolvedProviders
|
|
236
|
+
.map((provider) => {
|
|
237
|
+
if (provider.id === "github" && !githubUrl) return null;
|
|
238
|
+
|
|
239
|
+
const href = provider.getHref({ q, githubUrl });
|
|
240
|
+
const icon =
|
|
241
|
+
provider.icon ??
|
|
242
|
+
(href ? (
|
|
243
|
+
<img
|
|
244
|
+
src={`https://s2.googleusercontent.com/s2/favicons?domain_url=${encodeURIComponent(new URL(href).origin)}`}
|
|
245
|
+
alt=""
|
|
246
|
+
className="size-4"
|
|
247
|
+
/>
|
|
248
|
+
) : null);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
id: provider.id,
|
|
252
|
+
title: provider.title,
|
|
253
|
+
href,
|
|
254
|
+
icon,
|
|
255
|
+
};
|
|
256
|
+
})
|
|
257
|
+
.filter((item): item is NonNullable<typeof item> => item !== null);
|
|
258
|
+
}, [githubUrl, markdownUrl, providers]);
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<Popover>
|
|
262
|
+
<PopoverTrigger
|
|
263
|
+
className={cn(
|
|
264
|
+
buttonVariants({
|
|
265
|
+
size: "sm",
|
|
266
|
+
className: "gap-2",
|
|
267
|
+
}),
|
|
268
|
+
)}
|
|
269
|
+
>
|
|
270
|
+
{items.find((p) => p.id !== "github")?.icon}
|
|
271
|
+
Ask AI
|
|
272
|
+
<ChevronDown className="size-3.5 text-fd-muted-foreground" />
|
|
273
|
+
</PopoverTrigger>
|
|
274
|
+
<PopoverContent className="flex flex-col overflow-auto !w-48 !min-w-0 !p-1">
|
|
275
|
+
{items.map((item, index) => (
|
|
276
|
+
<React.Fragment key={item.href}>
|
|
277
|
+
<a
|
|
278
|
+
href={item.href}
|
|
279
|
+
rel="noreferrer noopener"
|
|
280
|
+
target="_blank"
|
|
281
|
+
className={cn(optionVariants())}
|
|
282
|
+
>
|
|
283
|
+
{item.icon}
|
|
284
|
+
{item.title}
|
|
285
|
+
<ExternalLinkIcon className="text-fd-muted-foreground size-3.5 ms-auto" />
|
|
286
|
+
</a>
|
|
287
|
+
{index === 0 && (
|
|
288
|
+
<div className="flex items-center gap-2 px-2 py-2 text-xs font-medium text-fd-muted-foreground">
|
|
289
|
+
<span className="h-px flex-1 bg-fd-border" />
|
|
290
|
+
Ask AI Model
|
|
291
|
+
<span className="h-px flex-1 bg-fd-border" />
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
</React.Fragment>
|
|
295
|
+
))}
|
|
296
|
+
</PopoverContent>
|
|
297
|
+
</Popover>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file llm-copy-button.tsx
|
|
3
|
+
* @description Button component that fetches and copies raw MDX content for LLMs.
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import { Check, Copy } from 'lucide-react';
|
|
9
|
+
import { cn } from '@/lib/utils';
|
|
10
|
+
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button';
|
|
11
|
+
import { buttonVariants } from '../../ui/button';
|
|
12
|
+
|
|
13
|
+
const cache = new Map<string, string>();
|
|
14
|
+
|
|
15
|
+
export function LLMCopyButton({
|
|
16
|
+
/**
|
|
17
|
+
* A URL to fetch the raw Markdown/MDX content of page
|
|
18
|
+
*/
|
|
19
|
+
markdownUrl,
|
|
20
|
+
}: {
|
|
21
|
+
markdownUrl: string;
|
|
22
|
+
}) {
|
|
23
|
+
const [isLoading, setLoading] = useState(false);
|
|
24
|
+
const [checked, onClick] = useCopyButton(async () => {
|
|
25
|
+
const cached = cache.get(markdownUrl);
|
|
26
|
+
if (cached) return navigator.clipboard.writeText(cached);
|
|
27
|
+
|
|
28
|
+
setLoading(true);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await navigator.clipboard.write([
|
|
32
|
+
new ClipboardItem({
|
|
33
|
+
'text/plain': fetch(markdownUrl).then(async (res) => {
|
|
34
|
+
const content = await res.text();
|
|
35
|
+
cache.set(markdownUrl, content);
|
|
36
|
+
|
|
37
|
+
return content;
|
|
38
|
+
}),
|
|
39
|
+
}),
|
|
40
|
+
]);
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<button
|
|
48
|
+
disabled={isLoading}
|
|
49
|
+
className={cn(
|
|
50
|
+
buttonVariants({
|
|
51
|
+
size: 'sm',
|
|
52
|
+
className: 'gap-2 [&_svg]:size-3.5 [&_svg]:text-fd-muted-foreground',
|
|
53
|
+
}),
|
|
54
|
+
)}
|
|
55
|
+
onClick={onClick}
|
|
56
|
+
>
|
|
57
|
+
{checked ? <Check /> : <Copy />}
|
|
58
|
+
Copy
|
|
59
|
+
</button>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { usePathname } from 'next/navigation';
|
|
3
|
+
import { useBreadcrumb } from 'fumadocs-core/breadcrumb';
|
|
4
|
+
import type { Root } from 'fumadocs-core/page-tree';
|
|
5
|
+
import { Fragment } from 'react';
|
|
6
|
+
import { ChevronRight } from 'lucide-react';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
|
|
9
|
+
export function Breadcrumb({ tree }: { tree: Root }) {
|
|
10
|
+
const pathname = usePathname();
|
|
11
|
+
const items = useBreadcrumb(pathname, tree);
|
|
12
|
+
|
|
13
|
+
if (items.length === 0) return null;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="-mb-3 flex flex-row items-center gap-1 text-sm font-medium text-fd-muted-foreground">
|
|
17
|
+
{items.map((item, i) => (
|
|
18
|
+
<Fragment key={i}>
|
|
19
|
+
{i !== 0 && (
|
|
20
|
+
<ChevronRight className="size-4 shrink-0 rtl:rotate-180" />
|
|
21
|
+
)}
|
|
22
|
+
{item.url ? (
|
|
23
|
+
<Link
|
|
24
|
+
href={item.url}
|
|
25
|
+
className="truncate hover:text-fd-accent-foreground"
|
|
26
|
+
>
|
|
27
|
+
{item.name}
|
|
28
|
+
</Link>
|
|
29
|
+
) : (
|
|
30
|
+
<span className="truncate">{item.name}</span>
|
|
31
|
+
)}
|
|
32
|
+
</Fragment>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file search.tsx
|
|
3
|
+
* @description Custom search dialog component using Orama for documentation search.
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
import {
|
|
7
|
+
SearchDialog,
|
|
8
|
+
SearchDialogClose,
|
|
9
|
+
SearchDialogContent,
|
|
10
|
+
SearchDialogHeader,
|
|
11
|
+
SearchDialogIcon,
|
|
12
|
+
SearchDialogInput,
|
|
13
|
+
SearchDialogList,
|
|
14
|
+
SearchDialogOverlay,
|
|
15
|
+
type SharedProps,
|
|
16
|
+
} from 'fumadocs-ui/components/dialog/search';
|
|
17
|
+
import { useDocsSearch } from 'fumadocs-core/search/client';
|
|
18
|
+
import { create } from 'zbsearch';
|
|
19
|
+
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
|
20
|
+
|
|
21
|
+
function initOrama() {
|
|
22
|
+
return create({
|
|
23
|
+
schema: { _: 'string' },
|
|
24
|
+
language: 'english',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function DefaultSearchDialog(props: SharedProps) {
|
|
29
|
+
const { locale } = useI18n(); // (optional) for i18n
|
|
30
|
+
const { search, setSearch, query } = useDocsSearch({
|
|
31
|
+
type: 'static',
|
|
32
|
+
from: '/docs/api/docs-search',
|
|
33
|
+
initOrama,
|
|
34
|
+
locale,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<SearchDialog
|
|
39
|
+
search={search}
|
|
40
|
+
onSearchChange={setSearch}
|
|
41
|
+
isLoading={query.isLoading}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<SearchDialogOverlay />
|
|
45
|
+
<SearchDialogContent>
|
|
46
|
+
<SearchDialogHeader>
|
|
47
|
+
<SearchDialogIcon />
|
|
48
|
+
<SearchDialogInput />
|
|
49
|
+
<SearchDialogClose />
|
|
50
|
+
</SearchDialogHeader>
|
|
51
|
+
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
|
|
52
|
+
</SearchDialogContent>
|
|
53
|
+
</SearchDialog>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file theme-provider.tsx
|
|
3
|
+
* @description Theme provider component for managing light and dark mode.
|
|
4
|
+
*/
|
|
5
|
+
'use client'
|
|
6
|
+
|
|
7
|
+
import * as React from 'react'
|
|
8
|
+
import {
|
|
9
|
+
ThemeProvider as NextThemesProvider,
|
|
10
|
+
type ThemeProviderProps,
|
|
11
|
+
} from 'next-themes'
|
|
12
|
+
|
|
13
|
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
14
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file theme-toggle.tsx
|
|
3
|
+
* @description Component for toggling between light and dark themes.
|
|
4
|
+
*/
|
|
5
|
+
"use client"
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect } from "react"
|
|
8
|
+
import { Moon, Sun } from "lucide-react"
|
|
9
|
+
import { Button } from "@/components/ui/button"
|
|
10
|
+
|
|
11
|
+
export function ThemeToggle() {
|
|
12
|
+
const [theme, setTheme] = useState<"light" | "dark">("light")
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null
|
|
16
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
17
|
+
const initialTheme = savedTheme || (prefersDark ? "dark" : "light")
|
|
18
|
+
setTheme(initialTheme)
|
|
19
|
+
document.documentElement.classList.toggle("dark", initialTheme === "dark")
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
const toggleTheme = () => {
|
|
23
|
+
const newTheme = theme === "light" ? "dark" : "light"
|
|
24
|
+
setTheme(newTheme)
|
|
25
|
+
localStorage.setItem("theme", newTheme)
|
|
26
|
+
document.documentElement.classList.toggle("dark", newTheme === "dark")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Button
|
|
31
|
+
variant="outline"
|
|
32
|
+
size="icon"
|
|
33
|
+
onClick={toggleTheme}
|
|
34
|
+
className="fixed top-4 right-4 z-50 bg-card/80 backdrop-blur-sm border-border hover:bg-secondary"
|
|
35
|
+
>
|
|
36
|
+
{theme === "light" ? <Moon className="h-5 w-5 text-foreground" /> : <Sun className="h-5 w-5 text-foreground" />}
|
|
37
|
+
<span className="sr-only">Toggle theme</span>
|
|
38
|
+
</Button>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file badge.tsx
|
|
3
|
+
* @description Badge component for displaying small amounts of information or status.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
7
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
8
|
+
|
|
9
|
+
import { cn } from '@/lib/utils'
|
|
10
|
+
|
|
11
|
+
const badgeVariants = cva(
|
|
12
|
+
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default:
|
|
17
|
+
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
|
18
|
+
secondary:
|
|
19
|
+
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
|
20
|
+
destructive:
|
|
21
|
+
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
22
|
+
outline:
|
|
23
|
+
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
variant: 'default',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
function Badge({
|
|
33
|
+
className,
|
|
34
|
+
variant,
|
|
35
|
+
asChild = false,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<'span'> &
|
|
38
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
39
|
+
const Comp = asChild ? Slot : 'span'
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Comp
|
|
43
|
+
data-slot="badge"
|
|
44
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file button.tsx
|
|
3
|
+
* @description Versatile button component with multiple variants and sizes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
|
|
8
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
9
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
10
|
+
|
|
11
|
+
import { cn } from '@/lib/utils'
|
|
12
|
+
|
|
13
|
+
const buttonVariants = cva(
|
|
14
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
15
|
+
{
|
|
16
|
+
variants: {
|
|
17
|
+
variant: {
|
|
18
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
19
|
+
destructive:
|
|
20
|
+
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
21
|
+
outline:
|
|
22
|
+
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
|
23
|
+
secondary:
|
|
24
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
25
|
+
ghost:
|
|
26
|
+
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
27
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
28
|
+
},
|
|
29
|
+
size: {
|
|
30
|
+
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
|
31
|
+
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
32
|
+
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
|
33
|
+
icon: 'size-9',
|
|
34
|
+
'icon-sm': 'size-8',
|
|
35
|
+
'icon-lg': 'size-10',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: 'default',
|
|
40
|
+
size: 'default',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
function Button({
|
|
46
|
+
className,
|
|
47
|
+
variant,
|
|
48
|
+
size,
|
|
49
|
+
asChild = false,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<'button'> &
|
|
52
|
+
VariantProps<typeof buttonVariants> & {
|
|
53
|
+
asChild?: boolean
|
|
54
|
+
}) {
|
|
55
|
+
const Comp = asChild ? Slot : 'button'
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Comp
|
|
59
|
+
data-slot="button"
|
|
60
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
61
|
+
{...props as any}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Button, buttonVariants }
|