mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 12:27:44 +00:00
feat: new oauth providers and docs
This commit is contained in:
@@ -12,9 +12,6 @@
|
|||||||
"noImplicitAnyLet": "warn",
|
"noImplicitAnyLet": "warn",
|
||||||
"noDuplicateObjectKeys": "warn"
|
"noDuplicateObjectKeys": "warn"
|
||||||
},
|
},
|
||||||
"style": {
|
|
||||||
"useImportType": "error"
|
|
||||||
},
|
|
||||||
"performance": {
|
"performance": {
|
||||||
"noDelete": "error"
|
"noDelete": "error"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function SignUpForm() {
|
|||||||
)
|
)
|
||||||
return alert("Please fill all fields");
|
return alert("Please fill all fields");
|
||||||
|
|
||||||
const res = await authClient.signUp.credential({
|
const res = await authClient.signUp.email({
|
||||||
email,
|
email,
|
||||||
name: `${firstName} ${lastName}`,
|
name: `${firstName} ${lastName}`,
|
||||||
password,
|
password,
|
||||||
|
|||||||
28
docs/.gitignore
vendored
Normal file
28
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# deps
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# generated content
|
||||||
|
.map.ts
|
||||||
|
.contentlayer
|
||||||
|
.content-collections
|
||||||
|
|
||||||
|
# test & build
|
||||||
|
/coverage
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
/build
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# others
|
||||||
|
.env*.local
|
||||||
|
.vercel
|
||||||
|
next-env.d.ts
|
||||||
26
docs/README.md
Normal file
26
docs/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# docs
|
||||||
|
|
||||||
|
This is a Next.js application generated with
|
||||||
|
[Create Fumadocs](https://github.com/fuma-nama/fumadocs).
|
||||||
|
|
||||||
|
Run development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open http://localhost:3000 with your browser to see the result.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js and Fumadocs, take a look at the following
|
||||||
|
resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js
|
||||||
|
features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
- [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs
|
||||||
11
docs/app/api/search/route.ts
Normal file
11
docs/app/api/search/route.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { getPages } from '@/app/source';
|
||||||
|
import { createSearchAPI } from 'fumadocs-core/search/server';
|
||||||
|
|
||||||
|
export const { GET } = createSearchAPI('advanced', {
|
||||||
|
indexes: getPages().map((page) => ({
|
||||||
|
title: page.data.title,
|
||||||
|
structuredData: page.data.exports.structuredData,
|
||||||
|
id: page.url,
|
||||||
|
url: page.url,
|
||||||
|
})),
|
||||||
|
});
|
||||||
64
docs/app/docs/[[...slug]]/page.tsx
Normal file
64
docs/app/docs/[[...slug]]/page.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { getPage, getPages } from '@/app/source';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import {
|
||||||
|
DocsPage,
|
||||||
|
DocsBody,
|
||||||
|
DocsDescription,
|
||||||
|
DocsTitle,
|
||||||
|
} from 'fumadocs-ui/page';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
|
||||||
|
import PromoCard from '@/components/promo-card';
|
||||||
|
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { slug?: string[] };
|
||||||
|
}) {
|
||||||
|
const page = getPage(params.slug);
|
||||||
|
|
||||||
|
if (page == null) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const MDX = page.data.exports.default;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DocsPage toc={page.data.exports.toc} full={page.data.full} editOnGithub={{
|
||||||
|
owner: "better-auth",
|
||||||
|
repo: "better-auth",
|
||||||
|
path: "/docs/content/docs",
|
||||||
|
}} tableOfContent={{
|
||||||
|
style: "clerk",
|
||||||
|
header: (
|
||||||
|
<div className='h-4 w-10'>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}} footer={{
|
||||||
|
enabled: false,
|
||||||
|
}} >
|
||||||
|
<DocsTitle>{page.data.title}</DocsTitle>
|
||||||
|
<DocsBody>
|
||||||
|
<MDX />
|
||||||
|
</DocsBody>
|
||||||
|
</DocsPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return getPages().map((page) => ({
|
||||||
|
slug: page.slugs,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMetadata({ params }: { params: { slug?: string[] } }) {
|
||||||
|
const page = getPage(params.slug);
|
||||||
|
|
||||||
|
if (page == null) notFound();
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: page.data.title,
|
||||||
|
description: page.data.description,
|
||||||
|
} satisfies Metadata;
|
||||||
|
}
|
||||||
12
docs/app/docs/layout.tsx
Normal file
12
docs/app/docs/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { DocsLayout } from 'fumadocs-ui/layout';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { docsOptions } from '../layout.config';
|
||||||
|
import ArticleLayout from '@/components/side-bar';
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return <DocsLayout {...docsOptions} sidebar={{
|
||||||
|
component: (
|
||||||
|
<ArticleLayout />
|
||||||
|
)
|
||||||
|
}}>{children}</DocsLayout>;
|
||||||
|
}
|
||||||
71
docs/app/global.css
Normal file
71
docs/app/global.css
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: 360 74% 95%;
|
||||||
|
--foreground: 360 5% 0%;
|
||||||
|
--card: 360 50% 90%;
|
||||||
|
--card-foreground: 360 5% 10%;
|
||||||
|
--popover: 360 74% 95%;
|
||||||
|
--popover-foreground: 360 95% 0%;
|
||||||
|
--primary: 360 0% 90%;
|
||||||
|
--primary-foreground: 0 0% 0%;
|
||||||
|
--secondary: 360 30% 70%;
|
||||||
|
--secondary-foreground: 0 0% 0%;
|
||||||
|
--muted: 322 30% 85%;
|
||||||
|
--muted-foreground: 360 5% 35%;
|
||||||
|
--accent: 322 30% 80%;
|
||||||
|
--accent-foreground: 360 5% 10%;
|
||||||
|
--destructive: 0 74% 30%;
|
||||||
|
--destructive-foreground: 360 5% 90%;
|
||||||
|
--border: 360 30% 50%;
|
||||||
|
--input: 360 30% 18%;
|
||||||
|
--ring: 360 0% 90%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--background: 360 50% 5%;
|
||||||
|
--foreground: 360 5% 90%;
|
||||||
|
--card: 360 50% 0%;
|
||||||
|
--card-foreground: 360 5% 90%;
|
||||||
|
--popover: 360 50% 5%;
|
||||||
|
--popover-foreground: 360 5% 90%;
|
||||||
|
--primary: 360 0% 90%;
|
||||||
|
--primary-foreground: 0 0% 0%;
|
||||||
|
--secondary: 360 30% 10%;
|
||||||
|
--secondary-foreground: 0 0% 100%;
|
||||||
|
--muted: 322 30% 15%;
|
||||||
|
--muted-foreground: 360 5% 60%;
|
||||||
|
--accent: 322 30% 15%;
|
||||||
|
--accent-foreground: 360 5% 90%;
|
||||||
|
--destructive: 0 74% 30%;
|
||||||
|
--destructive-foreground: 360 5% 90%;
|
||||||
|
--border: 360 30% 18%;
|
||||||
|
--input: 360 30% 18%;
|
||||||
|
--ring: 360 0% 90%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hide scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
23
docs/app/layout.config.tsx
Normal file
23
docs/app/layout.config.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { DocsLayoutProps } from 'fumadocs-ui/layout';
|
||||||
|
import type { HomeLayoutProps } from 'fumadocs-ui/home-layout';
|
||||||
|
import { pageTree } from '@/app/source';
|
||||||
|
|
||||||
|
// shared configuration
|
||||||
|
export const baseOptions: HomeLayoutProps = {
|
||||||
|
nav: {
|
||||||
|
title: 'Better Auth',
|
||||||
|
},
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
text: 'Documentation',
|
||||||
|
url: '/docs',
|
||||||
|
active: 'nested-url',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// docs layout configuration
|
||||||
|
export const docsOptions: DocsLayoutProps = {
|
||||||
|
...baseOptions,
|
||||||
|
tree: pageTree,
|
||||||
|
};
|
||||||
25
docs/app/layout.tsx
Normal file
25
docs/app/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Navbar } from '@/components/nav-bar';
|
||||||
|
import './global.css';
|
||||||
|
import { RootProvider } from 'fumadocs-ui/provider';
|
||||||
|
import { Inter } from 'next/font/google';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { NavbarProvider } from '@/components/nav-mobile';
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ['latin'],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className={inter.className} suppressHydrationWarning>
|
||||||
|
<body>
|
||||||
|
<RootProvider>
|
||||||
|
<NavbarProvider>
|
||||||
|
<Navbar />
|
||||||
|
{children}
|
||||||
|
</NavbarProvider>
|
||||||
|
</RootProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
docs/app/page.tsx
Normal file
19
docs/app/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import { } from "better-auth/client";
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<main className="flex h-screen flex-col justify-center text-center">
|
||||||
|
<h1 className="mb-4 text-2xl font-bold">Hello World</h1>
|
||||||
|
<p className="text-fd-muted-foreground">
|
||||||
|
You can open{' '}
|
||||||
|
<Link
|
||||||
|
href="/docs"
|
||||||
|
className="text-fd-foreground font-semibold underline"
|
||||||
|
>
|
||||||
|
/docs
|
||||||
|
</Link>{' '}
|
||||||
|
and see the documentation.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
docs/app/source.ts
Normal file
9
docs/app/source.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { map } from "@/.map";
|
||||||
|
import { createMDXSource } from "fumadocs-mdx";
|
||||||
|
import { loader } from "fumadocs-core/source";
|
||||||
|
|
||||||
|
export const { getPage, getPages, pageTree } = loader({
|
||||||
|
baseUrl: "/docs",
|
||||||
|
rootDir: "docs",
|
||||||
|
source: createMDXSource(map),
|
||||||
|
});
|
||||||
20
docs/components.json
Normal file
20
docs/components.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.js",
|
||||||
|
"css": "app/global.css",
|
||||||
|
"baseColor": "stone",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
docs/components/generate-secret.tsx
Normal file
35
docs/components/generate-secret.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
import { alphabet, generateRandomString } from "oslo/crypto";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
export const GenerateSecret = () => {
|
||||||
|
const [generated, setGenerated] = useState(false);
|
||||||
|
return (
|
||||||
|
<div className="my-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={generated}
|
||||||
|
onClick={() => {
|
||||||
|
const elements = document.getElementsByTagName("code"); // or any other selector
|
||||||
|
for (let i = 0; i < elements.length; i++) {
|
||||||
|
if (elements[i].textContent === "BETTER_AUTH_SECRET=") {
|
||||||
|
elements[i].textContent =
|
||||||
|
`BETTER_AUTH_SECRET=${generateRandomString(
|
||||||
|
32,
|
||||||
|
alphabet("A-Z", "a-z", "0-9"),
|
||||||
|
)}`;
|
||||||
|
setGenerated(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
elements[i].textContent = "BETTER_AUTH_SECRET=";
|
||||||
|
setGenerated(false);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{generated ? "Generated" : "Generate Secret"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
16
docs/components/logo.tsx
Normal file
16
docs/components/logo.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { SVGProps } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export const Logo = (props: SVGProps<any>) => {
|
||||||
|
return (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
|
<rect width="200" height="200" fill="#D9D9D9" />
|
||||||
|
<line x1="21.5" y1="2.18557e-08" x2="21.5" y2="200" stroke="#C4C4C4" />
|
||||||
|
<line x1="173.5" y1="2.18557e-08" x2="173.5" y2="200" stroke="#C4C4C4" />
|
||||||
|
<line x1="200" y1="176.5" x2="-4.37114e-08" y2="176.5" stroke="#C4C4C4" />
|
||||||
|
<line x1="200" y1="24.5" x2="-4.37114e-08" y2="24.5" stroke="#C4C4C4" />
|
||||||
|
<path d="M64.4545 135V65.1818H88.8636C93.7273 65.1818 97.7386 66.0227 100.898 67.7045C104.057 69.3636 106.409 71.6023 107.955 74.4205C109.5 77.2159 110.273 80.3182 110.273 83.7273C110.273 86.7273 109.739 89.2045 108.67 91.1591C107.625 93.1136 106.239 94.6591 104.511 95.7955C102.807 96.9318 100.955 97.7727 98.9545 98.3182V99C101.091 99.1364 103.239 99.8864 105.398 101.25C107.557 102.614 109.364 104.568 110.818 107.114C112.273 109.659 113 112.773 113 116.455C113 119.955 112.205 123.102 110.614 125.898C109.023 128.693 106.511 130.909 103.08 132.545C99.6477 134.182 95.1818 135 89.6818 135H64.4545ZM72.9091 127.5H89.6818C95.2045 127.5 99.125 126.432 101.443 124.295C103.784 122.136 104.955 119.523 104.955 116.455C104.955 114.091 104.352 111.909 103.148 109.909C101.943 107.886 100.227 106.273 98 105.068C95.7727 103.841 93.1364 103.227 90.0909 103.227H72.9091V127.5ZM72.9091 95.8636H88.5909C91.1364 95.8636 93.4318 95.3636 95.4773 94.3636C97.5455 93.3636 99.1818 91.9545 100.386 90.1364C101.614 88.3182 102.227 86.1818 102.227 83.7273C102.227 80.6591 101.159 78.0568 99.0227 75.9205C96.8864 73.7614 93.5 72.6818 88.8636 72.6818H72.9091V95.8636ZM131.665 135.545C129.983 135.545 128.54 134.943 127.335 133.739C126.131 132.534 125.528 131.091 125.528 129.409C125.528 127.727 126.131 126.284 127.335 125.08C128.54 123.875 129.983 123.273 131.665 123.273C133.347 123.273 134.79 123.875 135.994 125.08C137.199 126.284 137.801 127.727 137.801 129.409C137.801 130.523 137.517 131.545 136.949 132.477C136.403 133.409 135.665 134.159 134.733 134.727C133.824 135.273 132.801 135.545 131.665 135.545Z" fill="#302208" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
56
docs/components/nav-bar.tsx
Normal file
56
docs/components/nav-bar.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { ThemeToggle } from "@/components/theme-toggler";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { NavbarMobileBtn } from "./nav-mobile";
|
||||||
|
import { NavLink } from "./nav-link";
|
||||||
|
import { Logo } from "./logo";
|
||||||
|
|
||||||
|
export const Navbar = () => {
|
||||||
|
return (
|
||||||
|
<nav className="md:grid grid-cols-12 border-b pb-1 sticky top-0 flex items-center justify-end bg-background backdrop-blur-md z-50">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="md:border-r md:px-5 px-2.5 py-4 text-foreground md:col-span-4 lg:col-span-2 shrink-0 transition-colors min-w-[--fd-sidebar-width]"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Logo />
|
||||||
|
<p>BETTER-AUTH.</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div className="md:col-span-9 lg:col-span-10 flex items-center justify-end ">
|
||||||
|
<ul className="md:flex items-center divide-x w-max border-r hidden shrink-0">
|
||||||
|
{navMenu.map((menu, i) => (
|
||||||
|
<NavLink key={menu.name} href={menu.path}>
|
||||||
|
{menu.name}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<ThemeToggle />
|
||||||
|
<NavbarMobileBtn />
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const navMenu = [
|
||||||
|
{
|
||||||
|
name: "helo_",
|
||||||
|
path: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "docs",
|
||||||
|
path: "/docs",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: "plugins",
|
||||||
|
// path: "/plugins",
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
name: "changelogs",
|
||||||
|
path: "/changelogs",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: "resources",
|
||||||
|
// path: "/resources",
|
||||||
|
// },
|
||||||
|
];
|
||||||
37
docs/components/nav-link.tsx
Normal file
37
docs/components/nav-link.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"use client";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useSelectedLayoutSegment } from "next/navigation";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavLink = ({ href, children }: Props) => {
|
||||||
|
const segment = useSelectedLayoutSegment();
|
||||||
|
const isActive =
|
||||||
|
segment === href.slice(1) || (segment === null && href === "/");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="relative group">
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
className={cn(
|
||||||
|
"w-full h-full block py-4 px-5 transition-colors",
|
||||||
|
"group-hover:text-foreground",
|
||||||
|
isActive ? "text-foreground" : "text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute bottom-0 h-0.5 bg-muted-foreground opacity-0 transition-all duration-500",
|
||||||
|
"group-hover:opacity-100 group-hover:w-full",
|
||||||
|
isActive ? "opacity-100 w-full" : "w-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
155
docs/components/nav-mobile.tsx
Normal file
155
docs/components/nav-mobile.tsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
"use client";
|
||||||
|
import { Menu } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Fragment, createContext, useContext, useState } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { AnimatePresence, FadeIn } from "@/components/ui/fade-in";
|
||||||
|
|
||||||
|
interface NavbarMobileContextProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
toggleNavbar: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NavbarContext = createContext<NavbarMobileContextProps | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const NavbarProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const toggleNavbar = () => {
|
||||||
|
setIsOpen((prevIsOpen) => !prevIsOpen);
|
||||||
|
};
|
||||||
|
// @ts-ignore
|
||||||
|
return (
|
||||||
|
<NavbarContext.Provider value={{ isOpen, toggleNavbar }}>
|
||||||
|
{children}
|
||||||
|
</NavbarContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useNavbarMobile = (): NavbarMobileContextProps => {
|
||||||
|
const context = useContext(NavbarContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useNavbarMobile must be used within a NavbarMobileProvider",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavbarMobileBtn: React.FC = () => {
|
||||||
|
const { toggleNavbar } = useNavbarMobile();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="text-muted-foreground ml-auto px-2.5 block md:hidden"
|
||||||
|
onClick={toggleNavbar}
|
||||||
|
data-umami-event="navbar-mobile-trigger"
|
||||||
|
>
|
||||||
|
<Menu />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavbarMobile = () => {
|
||||||
|
const { isOpen, toggleNavbar } = useNavbarMobile();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<FadeIn
|
||||||
|
fromTopToBottom
|
||||||
|
className="absolute top-[57px] left-0 bg-background h-[calc(100%-57px-27px)] w-full z-50 p-5 divide-y overflow-y-auto"
|
||||||
|
>
|
||||||
|
{navMenu.map((menu, i) => (
|
||||||
|
<Fragment key={menu.name}>
|
||||||
|
{menu.child ? (
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value={menu.name}>
|
||||||
|
<AccordionTrigger
|
||||||
|
className="text-2xl font-normal text-foreground"
|
||||||
|
data-umami-event={`navbar-accordion-${menu.name}`}
|
||||||
|
>
|
||||||
|
{menu.name}
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pl-5 divide-y">
|
||||||
|
{menu.child.map((child, j) => (
|
||||||
|
<Link
|
||||||
|
href={child.path}
|
||||||
|
key={child.name}
|
||||||
|
className="block text-xl py-2 first:pt-0 last:pb-0 border-b last:border-0 text-muted-foreground"
|
||||||
|
onClick={toggleNavbar}
|
||||||
|
>
|
||||||
|
{child.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href={menu.path}
|
||||||
|
className="block text-2xl py-4 first:pt-0 last:pb-0"
|
||||||
|
onClick={toggleNavbar}
|
||||||
|
>
|
||||||
|
{menu.name}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</FadeIn>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const navMenu = [
|
||||||
|
{
|
||||||
|
name: "_hello",
|
||||||
|
path: "/",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "_blogs",
|
||||||
|
path: "/blogs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_docs",
|
||||||
|
child: [
|
||||||
|
{
|
||||||
|
name: "Introduction to better-auth",
|
||||||
|
path: "/docs/index",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Setting up the better-auth",
|
||||||
|
path: "/docs/setting-up",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Adding a plug",
|
||||||
|
path: "/docs/plugins",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Org support",
|
||||||
|
path: "/docs/org-support",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Compoent UI libs",
|
||||||
|
path: "/docs/lib-ui-componnts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Adapter Suppor",
|
||||||
|
path: "/docs/adapter-support",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Framework Support",
|
||||||
|
path: "/docs/framework-support",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
61
docs/components/promo-card.tsx
Normal file
61
docs/components/promo-card.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardFooter } from "@/components/ui/card"
|
||||||
|
import { Progress } from "@/components/ui/progress"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
|
import { Sparkles, Clock, ArrowRight } from "lucide-react"
|
||||||
|
|
||||||
|
export default function PromoCard() {
|
||||||
|
const [isHovered, setIsHovered] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Card
|
||||||
|
className="w-full overflow-hidden bg-gradient-to-br from-purple-500 to-indigo-600 text-white"
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
>
|
||||||
|
<CardContent className="p-6 pb-0">
|
||||||
|
<div className="flex justify-between items-start mb-4">
|
||||||
|
<Badge className="bg-yellow-500 text-black hover:bg-yellow-600">New</Badge>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Clock className="h-5 w-5 text-white/80" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Limited time offer</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold mb-2">Unlock Pro Features</h3>
|
||||||
|
<p className="text-sm text-white/80 mb-4">
|
||||||
|
Supercharge your workflow with our advanced tools and exclusive content.
|
||||||
|
</p>
|
||||||
|
<div className="relative">
|
||||||
|
<Progress value={67} className="h-2 mb-2" />
|
||||||
|
<span className="text-xs text-white/80">67% of slots filled</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="p-6 pt-4">
|
||||||
|
<Button
|
||||||
|
className={`w-full bg-white text-purple-600 hover:bg-white/90 transition-all duration-300 ${isHovered ? 'translate-y-[-2px] shadow-lg' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="mr-2">Upgrade Now</span>
|
||||||
|
<Sparkles className="h-4 w-4 mr-2" />
|
||||||
|
<ArrowRight className={`h-4 w-4 transition-transform duration-300 ${isHovered ? 'translate-x-1' : ''
|
||||||
|
}`} />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
434
docs/components/side-bar.tsx
Normal file
434
docs/components/side-bar.tsx
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { AsideLink } from "@/components/ui/aside-link";
|
||||||
|
import { FadeIn, FadeInStagger } from "@/components/ui/fade-in";
|
||||||
|
import { Key, LucideIcon, MailCheck, ScanFace, Search, Users2 } from "lucide-react";
|
||||||
|
import { ReactNode, Suspense, SVGProps, useState } from "react";
|
||||||
|
import {
|
||||||
|
SearchDialog,
|
||||||
|
type SharedProps
|
||||||
|
} from 'fumadocs-ui/components/dialog/search'
|
||||||
|
import { useSearchContext } from "fumadocs-ui/provider";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
export default function ArticleLayout() {
|
||||||
|
const { setOpenSearch } = useSearchContext()
|
||||||
|
const pathname = usePathname()
|
||||||
|
function getDefaultValue() {
|
||||||
|
const defaultValue = contents.findIndex((item) => item.list.some((listItem) => listItem.href === pathname))
|
||||||
|
return defaultValue === -1 ? 0 : defaultValue
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<aside className="border-r border-lines md:block hidden overflow-y-auto min-w-[--fd-sidebar-width] h-full sticky top-[60px] min-h-[92dvh]">
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
className="pl-8 bg-muted/50 focus-visible:ring-0 focus-visible:border-0 rounded-none dark:bg-stone-950 border-none border-b border-white"
|
||||||
|
placeholder="Search documentation..."
|
||||||
|
type="search"
|
||||||
|
onClick={() => setOpenSearch(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Accordion type="single" collapsible defaultValue={`item-${getDefaultValue()}`}>
|
||||||
|
{contents.map((item, i) => (
|
||||||
|
<AccordionItem value={`item-${i}`} key={item.title}>
|
||||||
|
<AccordionTrigger
|
||||||
|
className="border-b border-lines px-5 py-2.5 text-left"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{item.Icon && <item.Icon />}
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className=" space-y-1 p-0">
|
||||||
|
<FadeInStagger faster>
|
||||||
|
{item.list.map((listItem, j) => (
|
||||||
|
<FadeIn key={listItem.title}>
|
||||||
|
<Suspense fallback={<>Loading...</>}>
|
||||||
|
{
|
||||||
|
listItem.group ?
|
||||||
|
<div className="flex flex-row gap-2 items-center mx-5 my-1 ">
|
||||||
|
|
||||||
|
<p className="text-sm bg-gradient-to-tr from-gray-200 to-stone-500 bg-clip-text text-transparent">
|
||||||
|
{listItem.title}
|
||||||
|
</p>
|
||||||
|
<line
|
||||||
|
className="flex-grow h-px bg-gradient-to-r from-orange-500/40 to-stone-600/60"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
: <AsideLink
|
||||||
|
href={listItem.href}
|
||||||
|
startWith="/docs"
|
||||||
|
title={listItem.title}
|
||||||
|
>
|
||||||
|
<listItem.icon className="w-4 h-4 text-white" />
|
||||||
|
{listItem.title}
|
||||||
|
</AsideLink>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Suspense>
|
||||||
|
</FadeIn>
|
||||||
|
))}
|
||||||
|
</FadeInStagger>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Content {
|
||||||
|
title: string;
|
||||||
|
href?: string;
|
||||||
|
Icon: ((props?: SVGProps<any>) => ReactNode) | LucideIcon;
|
||||||
|
list: {
|
||||||
|
title: string;
|
||||||
|
href: string;
|
||||||
|
icon: ((props?: SVGProps<any>) => ReactNode) | LucideIcon;
|
||||||
|
group?: boolean;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents: Content[] = [
|
||||||
|
{
|
||||||
|
title: "Get Started",
|
||||||
|
Icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.4em"
|
||||||
|
height="1.4em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m-1 14H9V8h2zm1 0V8l5 4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
title: "Introduction",
|
||||||
|
href: "/docs/introduction",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M232 48h-64a32 32 0 0 0-32 32v87.73a8.17 8.17 0 0 1-7.47 8.25a8 8 0 0 1-8.53-8V80a32 32 0 0 0-32-32H24a8 8 0 0 0-8 8v144a8 8 0 0 0 8 8h72a24 24 0 0 1 24 23.94a7.9 7.9 0 0 0 5.12 7.55A8 8 0 0 0 136 232a24 24 0 0 1 24-24h72a8 8 0 0 0 8-8V56a8 8 0 0 0-8-8m-24 120h-39.73a8.17 8.17 0 0 1-8.25-7.47a8 8 0 0 1 8-8.53h39.73a8.17 8.17 0 0 1 8.25 7.47a8 8 0 0 1-8 8.53m0-32h-39.73a8.17 8.17 0 0 1-8.25-7.47a8 8 0 0 1 8-8.53h39.73a8.17 8.17 0 0 1 8.25 7.47a8 8 0 0 1-8 8.53m0-32h-39.73a8.17 8.17 0 0 1-8.27-7.47a8 8 0 0 1 8-8.53h39.73a8.17 8.17 0 0 1 8.27 7.47a8 8 0 0 1-8 8.53"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Installation",
|
||||||
|
href: "/docs/installation",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M2 12c0-4.714 0-7.071 1.464-8.536C4.93 2 7.286 2 12 2c4.714 0 7.071 0 8.535 1.464C22 4.93 22 7.286 22 12c0 4.714 0 7.071-1.465 8.535C19.072 22 16.714 22 12 22s-7.071 0-8.536-1.465C2 19.072 2 16.714 2 12m10-5.75a.75.75 0 0 1 .75.75v5.19l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06l1.72 1.72V7a.75.75 0 0 1 .75-.75m-4 10a.75.75 0 0 0 0 1.5h8a.75.75 0 0 0 0-1.5z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Basic Usage",
|
||||||
|
href: "/docs/basic-usage",
|
||||||
|
icon: () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"><path fill="currentColor" d="M21 2H3c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2M10 19.4l-1.6.6C6.9 18.6 6 16.6 6 14.5s.9-4.1 2.4-5.5l1.6.6c-1.3 1.1-2 3-2 4.9s.7 3.7 2 4.9m5.6.6l-1.6-.6c1.3-1.2 2-3 2-4.9s-.7-3.7-2-4.9l1.6-.6c1.5 1.4 2.4 3.4 2.4 5.5s-.9 4.1-2.4 5.5M21 7H3V4h18z"></path></svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Authentication",
|
||||||
|
Icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.4em"
|
||||||
|
height="1.4em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10 4h4c3.771 0 5.657 0 6.828 1.172C22 6.343 22 8.229 22 12c0 3.771 0 5.657-1.172 6.828C19.657 20 17.771 20 14 20h-4c-3.771 0-5.657 0-6.828-1.172C2 17.657 2 15.771 2 12c0-3.771 0-5.657 1.172-6.828C4.343 4 6.229 4 10 4m3.25 5a.75.75 0 0 1 .75-.75h5a.75.75 0 0 1 0 1.5h-5a.75.75 0 0 1-.75-.75m1 3a.75.75 0 0 1 .75-.75h4a.75.75 0 0 1 0 1.5h-4a.75.75 0 0 1-.75-.75m1 3a.75.75 0 0 1 .75-.75h3a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1-.75-.75M11 9a2 2 0 1 1-4 0a2 2 0 0 1 4 0m-2 8c4 0 4-.895 4-2s-1.79-2-4-2s-4 .895-4 2s0 2 4 2"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
title: "Email & Password",
|
||||||
|
group: true,
|
||||||
|
href: "/docs/email-password",
|
||||||
|
icon: () => (
|
||||||
|
<svg>
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm4.59-12.42L10 14.17l-2.59-2.58L6 13l4 4 8-8z"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Sign-In & Sign-Up",
|
||||||
|
href: "/docs/email-password/sign-in-and-sign-up",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M3.172 5.172C2 6.343 2 8.229 2 12c0 3.771 0 5.657 1.172 6.828C4.343 20 6.229 20 10 20h4c3.771 0 5.657 0 6.828-1.172C22 17.657 22 15.771 22 12c0-3.771 0-5.657-1.172-6.828C19.657 4 17.771 4 14 4h-4C6.229 4 4.343 4 3.172 5.172M8 13a1 1 0 1 0 0-2a1 1 0 0 0 0 2m5-1a1 1 0 1 1-2 0a1 1 0 0 1 2 0m3 1a1 1 0 1 0 0-2a1 1 0 0 0 0 2"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Password Reset",
|
||||||
|
href: "/docs/email-password/password-reset",
|
||||||
|
icon: () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 2a.75.75 0 0 0-1.5 0v20a.75.75 0 0 0 1.5 0v-2.006c2.636-.027 4.104-.191 5.078-1.166C22 17.657 22 15.771 22 12s0-5.657-1.172-6.828c-.974-.975-2.442-1.139-5.078-1.166z"></path><path fill="currentColor" fillRule="evenodd" d="M3.172 18.828C4.343 20 6.229 20 10 20h3V4h-3C6.229 4 4.343 4 3.172 5.172S2 8.229 2 12s0 5.657 1.172 6.828M13 12a1 1 0 1 0-2 0a1 1 0 0 0 2 0m-4 0a1 1 0 1 1-2 0a1 1 0 0 1 2 0" clipRule="evenodd"></path></svg>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Configuration",
|
||||||
|
href: "/docs/email-password/configuration",
|
||||||
|
icon: () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 48 48"><defs><mask id="ipSConfig0"><g fill="none" stroke-linejoin="round" stroke-width="4"><path fill="#fff" stroke="#fff" d="m24 4l-6 6h-8v8l-6 6l6 6v8h8l6 6l6-6h8v-8l6-6l-6-6v-8h-8z"></path><path fill="#000" stroke="#000" d="M24 30a6 6 0 1 0 0-12a6 6 0 0 0 0 12Z"></path></g></mask></defs><path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipSConfig0)"></path></svg>
|
||||||
|
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Social Sign-On",
|
||||||
|
group: true,
|
||||||
|
href: "/docs/providers/social-providers",
|
||||||
|
icon: () => (
|
||||||
|
<svg>
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm4.59-12.42L10 14.17l-2.59-2.58L6 13l4 4 8-8z"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Apple",
|
||||||
|
href: "/docs/providers/apple",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M14.122 4.682c1.35 0 2.781.743 3.8 2.028c-3.34 1.851-2.797 6.674.578 7.963c-.465 1.04-.687 1.505-1.285 2.426c-.835 1.284-2.01 2.884-3.469 2.898c-1.295.012-1.628-.853-3.386-.843c-1.758.01-2.125.858-3.42.846c-1.458-.014-2.573-1.458-3.408-2.743C1.198 13.665.954 9.45 2.394 7.21C3.417 5.616 5.03 4.683 6.548 4.683c1.545 0 2.516.857 3.794.857c1.24 0 1.994-.858 3.78-.858M13.73 0c.18 1.215-.314 2.405-.963 3.247c-.695.902-1.892 1.601-3.05 1.565c-.21-1.163.332-2.36.99-3.167C11.43.755 12.67.074 13.73 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Discord",
|
||||||
|
href: "/docs/providers/discord",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M18.59 5.89c-1.23-.57-2.54-.99-3.92-1.23c-.17.3-.37.71-.5 1.04c-1.46-.22-2.91-.22-4.34 0c-.14-.33-.34-.74-.51-1.04c-1.38.24-2.69.66-3.92 1.23c-2.48 3.74-3.15 7.39-2.82 10.98c1.65 1.23 3.24 1.97 4.81 2.46c.39-.53.73-1.1 1.03-1.69c-.57-.21-1.11-.48-1.62-.79c.14-.1.27-.21.4-.31c3.13 1.46 6.52 1.46 9.61 0c.13.11.26.21.4.31c-.51.31-1.06.57-1.62.79c.3.59.64 1.16 1.03 1.69c1.57-.49 3.17-1.23 4.81-2.46c.39-4.17-.67-7.78-2.82-10.98Zm-9.75 8.78c-.94 0-1.71-.87-1.71-1.94s.75-1.94 1.71-1.94s1.72.87 1.71 1.94c0 1.06-.75 1.94-1.71 1.94m6.31 0c-.94 0-1.71-.87-1.71-1.94s.75-1.94 1.71-1.94s1.72.87 1.71 1.94c0 1.06-.75 1.94-1.71 1.94"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Facebook",
|
||||||
|
href: "/docs/providers/facebook",
|
||||||
|
icon: () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"><g fill="none"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path><path fill="currentColor" d="M13.5 21.888C18.311 21.164 22 17.013 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 5.013 3.689 9.165 8.5 9.888V15H9a1.5 1.5 0 0 1 0-3h1.5v-2A3.5 3.5 0 0 1 14 6.5h.5a1.5 1.5 0 0 1 0 3H14a.5.5 0 0 0-.5.5v2H15a1.5 1.5 0 0 1 0 3h-1.5z"></path></g></svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Github",
|
||||||
|
href: "/docs/providers/github",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M7.5.25a7.25 7.25 0 0 0-2.292 14.13c.363.066.495-.158.495-.35c0-.172-.006-.628-.01-1.233c-2.016.438-2.442-.972-2.442-.972c-.33-.838-.805-1.06-.805-1.06c-.658-.45.05-.441.05-.441c.728.051 1.11.747 1.11.747c.647 1.108 1.697.788 2.11.602c.066-.468.254-.788.46-.969c-1.61-.183-3.302-.805-3.302-3.583a2.8 2.8 0 0 1 .747-1.945c-.075-.184-.324-.92.07-1.92c0 0 .61-.194 1.994.744A6.963 6.963 0 0 1 7.5 3.756A6.97 6.97 0 0 1 9.315 4c1.384-.938 1.992-.743 1.992-.743c.396.998.147 1.735.072 1.919c.465.507.745 1.153.745 1.945c0 2.785-1.695 3.398-3.31 3.577c.26.224.492.667.492 1.343c0 .97-.009 1.751-.009 1.989c0 .194.131.42.499.349A7.25 7.25 0 0 0 7.499.25"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Google",
|
||||||
|
href: "/docs/providers/google",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 56 56"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M28.458 5c6.167 0 11.346 2.2 15.368 5.804l.323.295l-6.62 6.464c-1.695-1.59-4.666-3.493-9.07-3.493c-6.204 0-11.47 4.093-13.372 9.749c-.47 1.46-.756 3.023-.756 4.64c0 1.615.287 3.18.782 4.639c1.877 5.656 7.142 9.748 13.345 9.748c3.347 0 5.928-.886 7.881-2.176l.251-.17l.307-.222c2.813-2.108 4.144-5.084 4.46-7.169l.03-.22h-12.93v-8.705h22.025c.339 1.46.495 2.867.495 4.795c0 7.142-2.554 13.163-6.985 17.255c-3.884 3.597-9.201 5.682-15.535 5.682c-9.031 0-16.85-5.102-20.772-12.57l-.184-.358l-.222-.457A23.45 23.45 0 0 1 5 28.458c0-3.6.827-7.01 2.28-10.073l.222-.457l.184-.357C11.608 10.1 19.426 5 28.458 5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Twitch",
|
||||||
|
href: "/docs/providers/twitch",
|
||||||
|
icon: () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" fillRule="evenodd" d="M3.9 2.5a.9.9 0 0 0-.9.9v14.194a.9.9 0 0 0 .9.9h4.116v3.03a.7.7 0 0 0 1.194.494l3.525-3.524h4.643a.9.9 0 0 0 .636-.264l2.722-2.722a.9.9 0 0 0 .264-.636V3.4a.9.9 0 0 0-.9-.9zm7.319 5.2a.75.75 0 0 0-1.5 0v4.272a.75.75 0 1 0 1.5 0zm5.016 0a.75.75 0 0 0-1.5 0v4.272a.75.75 0 1 0 1.5 0z" clipRule="evenodd"></path></svg>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "X (Twitter)",
|
||||||
|
href: "/docs/providers/twitter",
|
||||||
|
icon: () => (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="0.88em" height="1em" viewBox="0 0 448 512"><path fill="currentColor" d="M64 32C28.7 32 0 60.7 0 96v320c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm297.1 84L257.3 234.6L379.4 396h-95.6L209 298.1L123.3 396H75.8l111-126.9L69.7 116h98l67.7 89.5l78.2-89.5zm-37.8 251.6L153.4 142.9h-28.3l171.8 224.7h26.3z"></path></svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Adapters",
|
||||||
|
Icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.4em"
|
||||||
|
height="1.4em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M20 18c0 2.21-3.582 4-8 4s-8-1.79-8-4v-4.026c.502.617 1.215 1.129 2.008 1.525C7.58 16.285 9.7 16.75 12 16.75s4.42-.465 5.992-1.25c.793-.397 1.506-.91 2.008-1.526z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 10.75c2.3 0 4.42-.465 5.992-1.25c.793-.397 1.506-.91 2.008-1.526V12c0 .5-1.786 1.591-2.679 2.158c-1.323.661-3.203 1.092-5.321 1.092s-3.998-.43-5.321-1.092C5.5 13.568 4 12.5 4 12V7.974c.502.617 1.215 1.129 2.008 1.525C7.58 10.285 9.7 10.75 12 10.75"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M17.321 8.158C15.998 8.819 14.118 9.25 12 9.25s-3.998-.43-5.321-1.092c-.515-.202-1.673-.843-2.477-1.879a.81.81 0 0 1-.162-.621c.023-.148.055-.301.096-.396C4.828 3.406 8.086 2 12 2c3.914 0 7.172 1.406 7.864 3.262c.041.095.073.248.096.396a.81.81 0 0 1-.162.621c-.804 1.036-1.962 1.677-2.477 1.879"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
title: "Drizzle",
|
||||||
|
href: "/docs/adapters/drizzle",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M5.353 11.823a1.036 1.036 0 0 0-.395-1.422a1.063 1.063 0 0 0-1.437.399L.138 16.702a1.035 1.035 0 0 0 .395 1.422a1.063 1.063 0 0 0 1.437-.398zm11.216 0a1.036 1.036 0 0 0-.394-1.422a1.064 1.064 0 0 0-1.438.399l-3.382 5.902a1.036 1.036 0 0 0 .394 1.422c.506.283 1.15.104 1.438-.398zm7.293-4.525a1.036 1.036 0 0 0-.395-1.422a1.06 1.06 0 0 0-1.437.399l-3.383 5.902a1.036 1.036 0 0 0 .395 1.422a1.063 1.063 0 0 0 1.437-.399zm-11.219 0a1.035 1.035 0 0 0-.394-1.422a1.064 1.064 0 0 0-1.438.398l-3.382 5.903a1.036 1.036 0 0 0 .394 1.422c.506.282 1.15.104 1.438-.399z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Prisma",
|
||||||
|
href: "/docs/adapters/prisma",
|
||||||
|
icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M21.807 18.285L13.553.757a1.32 1.32 0 0 0-1.129-.755a1.31 1.31 0 0 0-1.206.626l-8.952 14.5a1.36 1.36 0 0 0 .016 1.455l4.376 6.778a1.41 1.41 0 0 0 1.58.581l12.703-3.757c.389-.115.707-.39.873-.755s.164-.783-.007-1.145m-1.848.752L9.18 22.224a.452.452 0 0 1-.575-.52l3.85-18.438c.072-.345.549-.4.699-.08l7.129 15.138a.515.515 0 0 1-.325.713"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Plugins",
|
||||||
|
Icon: () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.4em"
|
||||||
|
height="1.4em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="none">
|
||||||
|
<path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M15 20a1 1 0 1 1 0 2H9a1 1 0 1 1 0-2zm0-18a1 1 0 0 1 .993.883L16 3v3h2a2 2 0 0 1 1.995 1.85L20 8v5a6 6 0 0 1-5.775 5.996L14 19h-4a6 6 0 0 1-5.996-5.775L4 13V8a2 2 0 0 1 1.85-1.995L6 6h2V3a1 1 0 0 1 1.993-.117L10 3v3h4V3a1 1 0 0 1 1-1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
title: "2 Factor",
|
||||||
|
icon: ScanFace,
|
||||||
|
href: "/docs/plugins/2fa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Bearer",
|
||||||
|
icon: Key,
|
||||||
|
href: "/docs/plugins/email-verifier",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Email Verifier",
|
||||||
|
icon: MailCheck,
|
||||||
|
href: "/docs/plugins/email-verifier",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Organization",
|
||||||
|
icon: Users2,
|
||||||
|
href: "/docs/plugins/organization",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
53
docs/components/theme-toggler.tsx
Normal file
53
docs/components/theme-toggler.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="border-l rounded-none h-14 w-14 hidden md:flex shrink-0"
|
||||||
|
data-umami-event="theme-toggle"
|
||||||
|
>
|
||||||
|
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||||
|
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
data-umami-event="theme-toggle-light"
|
||||||
|
onClick={() => setTheme("light")}
|
||||||
|
>
|
||||||
|
Light
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
data-umami-event="theme-toggle-dark"
|
||||||
|
onClick={() => setTheme("dark")}
|
||||||
|
>
|
||||||
|
Dark
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
data-umami-event="theme-toggle-system"
|
||||||
|
onClick={() => setTheme("system")}
|
||||||
|
>
|
||||||
|
System
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
docs/components/ui/accordion.tsx
Normal file
57
docs/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
|
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Accordion = AccordionPrimitive.Root
|
||||||
|
|
||||||
|
const AccordionItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn("border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AccordionItem.displayName = "AccordionItem"
|
||||||
|
|
||||||
|
const AccordionTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Header className="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronDownIcon className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
))
|
||||||
|
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const AccordionContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
))
|
||||||
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||||
141
docs/components/ui/alert-dialog.tsx
Normal file
141
docs/components/ui/alert-dialog.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|
||||||
|
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||||
|
|
||||||
|
const AlertDialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const AlertDialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
))
|
||||||
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const AlertDialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||||
|
|
||||||
|
const AlertDialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||||
|
|
||||||
|
const AlertDialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const AlertDialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogDescription.displayName =
|
||||||
|
AlertDialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
const AlertDialogAction = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
ref={ref}
|
||||||
|
className={cn(buttonVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||||
|
|
||||||
|
const AlertDialogCancel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"mt-2 sm:mt-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
}
|
||||||
59
docs/components/ui/alert.tsx
Normal file
59
docs/components/ui/alert.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
43
docs/components/ui/aside-link.tsx
Normal file
43
docs/components/ui/aside-link.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
import type { ClassValue } from "clsx";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useSelectedLayoutSegment } from "next/navigation";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
href: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
startWith: string;
|
||||||
|
title?: string | null;
|
||||||
|
className?: ClassValue;
|
||||||
|
} & React.AnchorHTMLAttributes<HTMLAnchorElement>;
|
||||||
|
|
||||||
|
export const AsideLink = ({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
startWith,
|
||||||
|
title,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: Props) => {
|
||||||
|
const segment = useSelectedLayoutSegment();
|
||||||
|
const path = href;
|
||||||
|
const isActive = path.replace("/docs/", "") === segment;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
className={cn(
|
||||||
|
isActive
|
||||||
|
? "text-foreground bg-primary/10"
|
||||||
|
: "text-muted-foreground hover:text-foreground hover:bg-primary/10",
|
||||||
|
"w-full transition-colors flex items-center gap-x-2.5 hover:bg-primary/10 px-5 py-1",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
7
docs/components/ui/aspect-ratio.tsx
Normal file
7
docs/components/ui/aspect-ratio.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
||||||
|
|
||||||
|
const AspectRatio = AspectRatioPrimitive.Root
|
||||||
|
|
||||||
|
export { AspectRatio }
|
||||||
50
docs/components/ui/avatar.tsx
Normal file
50
docs/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Avatar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const AvatarImage = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Image
|
||||||
|
ref={ref}
|
||||||
|
className={cn("aspect-square h-full w-full", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Fallback
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback }
|
||||||
36
docs/components/ui/badge.tsx
Normal file
36
docs/components/ui/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||||
|
outline: "text-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants }
|
||||||
115
docs/components/ui/breadcrumb.tsx
Normal file
115
docs/components/ui/breadcrumb.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Breadcrumb = React.forwardRef<
|
||||||
|
HTMLElement,
|
||||||
|
React.ComponentPropsWithoutRef<"nav"> & {
|
||||||
|
separator?: React.ReactNode
|
||||||
|
}
|
||||||
|
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
||||||
|
Breadcrumb.displayName = "Breadcrumb"
|
||||||
|
|
||||||
|
const BreadcrumbList = React.forwardRef<
|
||||||
|
HTMLOListElement,
|
||||||
|
React.ComponentPropsWithoutRef<"ol">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ol
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
BreadcrumbList.displayName = "BreadcrumbList"
|
||||||
|
|
||||||
|
const BreadcrumbItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentPropsWithoutRef<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li
|
||||||
|
ref={ref}
|
||||||
|
className={cn("inline-flex items-center gap-1.5", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
BreadcrumbItem.displayName = "BreadcrumbItem"
|
||||||
|
|
||||||
|
const BreadcrumbLink = React.forwardRef<
|
||||||
|
HTMLAnchorElement,
|
||||||
|
React.ComponentPropsWithoutRef<"a"> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
>(({ asChild, className, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "a"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
ref={ref}
|
||||||
|
className={cn("transition-colors hover:text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
BreadcrumbLink.displayName = "BreadcrumbLink"
|
||||||
|
|
||||||
|
const BreadcrumbPage = React.forwardRef<
|
||||||
|
HTMLSpanElement,
|
||||||
|
React.ComponentPropsWithoutRef<"span">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<span
|
||||||
|
ref={ref}
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
className={cn("font-normal text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
BreadcrumbPage.displayName = "BreadcrumbPage"
|
||||||
|
|
||||||
|
const BreadcrumbSeparator = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"li">) => (
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? <ChevronRightIcon />}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
||||||
|
|
||||||
|
const BreadcrumbEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<DotsHorizontalIcon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
BreadcrumbEllipsis,
|
||||||
|
}
|
||||||
57
docs/components/ui/button.tsx
Normal file
57
docs/components/ui/button.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2",
|
||||||
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
|
lg: "h-10 rounded-md px-8",
|
||||||
|
icon: "h-9 w-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
72
docs/components/ui/calendar.tsx
Normal file
72
docs/components/ui/calendar.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"
|
||||||
|
import { DayPicker } from "react-day-picker"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||||
|
|
||||||
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
...props
|
||||||
|
}: CalendarProps) {
|
||||||
|
return (
|
||||||
|
<DayPicker
|
||||||
|
showOutsideDays={showOutsideDays}
|
||||||
|
className={cn("p-3", className)}
|
||||||
|
classNames={{
|
||||||
|
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||||
|
month: "space-y-4",
|
||||||
|
caption: "flex justify-center pt-1 relative items-center",
|
||||||
|
caption_label: "text-sm font-medium",
|
||||||
|
nav: "space-x-1 flex items-center",
|
||||||
|
nav_button: cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||||
|
),
|
||||||
|
nav_button_previous: "absolute left-1",
|
||||||
|
nav_button_next: "absolute right-1",
|
||||||
|
table: "w-full border-collapse space-y-1",
|
||||||
|
head_row: "flex",
|
||||||
|
head_cell:
|
||||||
|
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||||
|
row: "flex w-full mt-2",
|
||||||
|
cell: cn(
|
||||||
|
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||||
|
props.mode === "range"
|
||||||
|
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||||
|
: "[&:has([aria-selected])]:rounded-md"
|
||||||
|
),
|
||||||
|
day: cn(
|
||||||
|
buttonVariants({ variant: "ghost" }),
|
||||||
|
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
|
||||||
|
),
|
||||||
|
day_range_start: "day-range-start",
|
||||||
|
day_range_end: "day-range-end",
|
||||||
|
day_selected:
|
||||||
|
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||||
|
day_today: "bg-accent text-accent-foreground",
|
||||||
|
day_outside:
|
||||||
|
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
||||||
|
day_disabled: "text-muted-foreground opacity-50",
|
||||||
|
day_range_middle:
|
||||||
|
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||||
|
day_hidden: "invisible",
|
||||||
|
...classNames,
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
IconLeft: ({ ...props }) => <ChevronLeftIcon className="h-4 w-4" />,
|
||||||
|
IconRight: ({ ...props }) => <ChevronRightIcon className="h-4 w-4" />,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Calendar.displayName = "Calendar"
|
||||||
|
|
||||||
|
export { Calendar }
|
||||||
76
docs/components/ui/card.tsx
Normal file
76
docs/components/ui/card.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-xl border bg-card text-card-foreground shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
262
docs/components/ui/carousel.tsx
Normal file
262
docs/components/ui/carousel.tsx
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons"
|
||||||
|
import useEmblaCarousel, {
|
||||||
|
type UseEmblaCarouselType,
|
||||||
|
} from "embla-carousel-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
|
type CarouselApi = UseEmblaCarouselType[1]
|
||||||
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||||
|
type CarouselOptions = UseCarouselParameters[0]
|
||||||
|
type CarouselPlugin = UseCarouselParameters[1]
|
||||||
|
|
||||||
|
type CarouselProps = {
|
||||||
|
opts?: CarouselOptions
|
||||||
|
plugins?: CarouselPlugin
|
||||||
|
orientation?: "horizontal" | "vertical"
|
||||||
|
setApi?: (api: CarouselApi) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type CarouselContextProps = {
|
||||||
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
||||||
|
api: ReturnType<typeof useEmblaCarousel>[1]
|
||||||
|
scrollPrev: () => void
|
||||||
|
scrollNext: () => void
|
||||||
|
canScrollPrev: boolean
|
||||||
|
canScrollNext: boolean
|
||||||
|
} & CarouselProps
|
||||||
|
|
||||||
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
||||||
|
|
||||||
|
function useCarousel() {
|
||||||
|
const context = React.useContext(CarouselContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useCarousel must be used within a <Carousel />")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const Carousel = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
opts,
|
||||||
|
setApi,
|
||||||
|
plugins,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const [carouselRef, api] = useEmblaCarousel(
|
||||||
|
{
|
||||||
|
...opts,
|
||||||
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
|
},
|
||||||
|
plugins
|
||||||
|
)
|
||||||
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
||||||
|
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
||||||
|
|
||||||
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||||
|
if (!api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanScrollPrev(api.canScrollPrev())
|
||||||
|
setCanScrollNext(api.canScrollNext())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const scrollPrev = React.useCallback(() => {
|
||||||
|
api?.scrollPrev()
|
||||||
|
}, [api])
|
||||||
|
|
||||||
|
const scrollNext = React.useCallback(() => {
|
||||||
|
api?.scrollNext()
|
||||||
|
}, [api])
|
||||||
|
|
||||||
|
const handleKeyDown = React.useCallback(
|
||||||
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (event.key === "ArrowLeft") {
|
||||||
|
event.preventDefault()
|
||||||
|
scrollPrev()
|
||||||
|
} else if (event.key === "ArrowRight") {
|
||||||
|
event.preventDefault()
|
||||||
|
scrollNext()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[scrollPrev, scrollNext]
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!api || !setApi) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setApi(api)
|
||||||
|
}, [api, setApi])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(api)
|
||||||
|
api.on("reInit", onSelect)
|
||||||
|
api.on("select", onSelect)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
api?.off("select", onSelect)
|
||||||
|
}
|
||||||
|
}, [api, onSelect])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CarouselContext.Provider
|
||||||
|
value={{
|
||||||
|
carouselRef,
|
||||||
|
api: api,
|
||||||
|
opts,
|
||||||
|
orientation:
|
||||||
|
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||||
|
scrollPrev,
|
||||||
|
scrollNext,
|
||||||
|
canScrollPrev,
|
||||||
|
canScrollNext,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
onKeyDownCapture={handleKeyDown}
|
||||||
|
className={cn("relative", className)}
|
||||||
|
role="region"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</CarouselContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Carousel.displayName = "Carousel"
|
||||||
|
|
||||||
|
const CarouselContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { carouselRef, orientation } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={carouselRef} className="overflow-hidden">
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex",
|
||||||
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselContent.displayName = "CarouselContent"
|
||||||
|
|
||||||
|
const CarouselItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { orientation } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="slide"
|
||||||
|
className={cn(
|
||||||
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselItem.displayName = "CarouselItem"
|
||||||
|
|
||||||
|
const CarouselPrevious = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
React.ComponentProps<typeof Button>
|
||||||
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||||
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant={variant}
|
||||||
|
size={size}
|
||||||
|
className={cn(
|
||||||
|
"absolute h-8 w-8 rounded-full",
|
||||||
|
orientation === "horizontal"
|
||||||
|
? "-left-12 top-1/2 -translate-y-1/2"
|
||||||
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={!canScrollPrev}
|
||||||
|
onClick={scrollPrev}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Previous slide</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselPrevious.displayName = "CarouselPrevious"
|
||||||
|
|
||||||
|
const CarouselNext = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
React.ComponentProps<typeof Button>
|
||||||
|
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||||
|
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant={variant}
|
||||||
|
size={size}
|
||||||
|
className={cn(
|
||||||
|
"absolute h-8 w-8 rounded-full",
|
||||||
|
orientation === "horizontal"
|
||||||
|
? "-right-12 top-1/2 -translate-y-1/2"
|
||||||
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={!canScrollNext}
|
||||||
|
onClick={scrollNext}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ArrowRightIcon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Next slide</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CarouselNext.displayName = "CarouselNext"
|
||||||
|
|
||||||
|
export {
|
||||||
|
type CarouselApi,
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselPrevious,
|
||||||
|
CarouselNext,
|
||||||
|
}
|
||||||
370
docs/components/ui/chart.tsx
Normal file
370
docs/components/ui/chart.tsx
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as RechartsPrimitive from "recharts"
|
||||||
|
import {
|
||||||
|
NameType,
|
||||||
|
Payload,
|
||||||
|
ValueType,
|
||||||
|
} from "recharts/types/component/DefaultTooltipContent"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
|
const THEMES = { light: "", dark: ".dark" } as const
|
||||||
|
|
||||||
|
export type ChartConfig = {
|
||||||
|
[k in string]: {
|
||||||
|
label?: React.ReactNode
|
||||||
|
icon?: React.ComponentType
|
||||||
|
} & (
|
||||||
|
| { color?: string; theme?: never }
|
||||||
|
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartContextProps = {
|
||||||
|
config: ChartConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||||
|
|
||||||
|
function useChart() {
|
||||||
|
const context = React.useContext(ChartContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useChart must be used within a <ChartContainer />")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartContainer = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentProps<"div"> & {
|
||||||
|
config: ChartConfig
|
||||||
|
children: React.ComponentProps<
|
||||||
|
typeof RechartsPrimitive.ResponsiveContainer
|
||||||
|
>["children"]
|
||||||
|
}
|
||||||
|
>(({ id, className, children, config, ...props }, ref) => {
|
||||||
|
const uniqueId = React.useId()
|
||||||
|
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartContext.Provider value={{ config }}>
|
||||||
|
<div
|
||||||
|
data-chart={chartId}
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChartStyle id={chartId} config={config} />
|
||||||
|
<RechartsPrimitive.ResponsiveContainer>
|
||||||
|
{children}
|
||||||
|
</RechartsPrimitive.ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</ChartContext.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
ChartContainer.displayName = "Chart"
|
||||||
|
|
||||||
|
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||||
|
const colorConfig = Object.entries(config).filter(
|
||||||
|
([_, config]) => config.theme || config.color
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!colorConfig.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<style
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Object.entries(THEMES)
|
||||||
|
.map(
|
||||||
|
([theme, prefix]) => `
|
||||||
|
${prefix} [data-chart=${id}] {
|
||||||
|
${colorConfig
|
||||||
|
.map(([key, itemConfig]) => {
|
||||||
|
const color =
|
||||||
|
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||||
|
itemConfig.color
|
||||||
|
return color ? ` --color-${key}: ${color};` : null
|
||||||
|
})
|
||||||
|
.join("\n")}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("\n"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||||
|
|
||||||
|
const ChartTooltipContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||||
|
React.ComponentProps<"div"> & {
|
||||||
|
hideLabel?: boolean
|
||||||
|
hideIndicator?: boolean
|
||||||
|
indicator?: "line" | "dot" | "dashed"
|
||||||
|
nameKey?: string
|
||||||
|
labelKey?: string
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
active,
|
||||||
|
payload,
|
||||||
|
className,
|
||||||
|
indicator = "dot",
|
||||||
|
hideLabel = false,
|
||||||
|
hideIndicator = false,
|
||||||
|
label,
|
||||||
|
labelFormatter,
|
||||||
|
labelClassName,
|
||||||
|
formatter,
|
||||||
|
color,
|
||||||
|
nameKey,
|
||||||
|
labelKey,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { config } = useChart()
|
||||||
|
|
||||||
|
const tooltipLabel = React.useMemo(() => {
|
||||||
|
if (hideLabel || !payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item] = payload
|
||||||
|
const key = `${labelKey || item.dataKey || item.name || "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const value =
|
||||||
|
!labelKey && typeof label === "string"
|
||||||
|
? config[label as keyof typeof config]?.label || label
|
||||||
|
: itemConfig?.label
|
||||||
|
|
||||||
|
if (labelFormatter) {
|
||||||
|
return (
|
||||||
|
<div className={cn("font-medium", labelClassName)}>
|
||||||
|
{labelFormatter(value, payload)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||||
|
}, [
|
||||||
|
label,
|
||||||
|
labelFormatter,
|
||||||
|
payload,
|
||||||
|
hideLabel,
|
||||||
|
labelClassName,
|
||||||
|
config,
|
||||||
|
labelKey,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!active || !payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!nestLabel ? tooltipLabel : null}
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
{payload.map((item, index) => {
|
||||||
|
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const indicatorColor = color || item.payload.fill || item.color
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.dataKey}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||||
|
indicator === "dot" && "items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formatter && item?.value !== undefined && item.name ? (
|
||||||
|
formatter(item.value, item.name, item, index, item.payload)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{itemConfig?.icon ? (
|
||||||
|
<itemConfig.icon />
|
||||||
|
) : (
|
||||||
|
!hideIndicator && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||||
|
{
|
||||||
|
"h-2.5 w-2.5": indicator === "dot",
|
||||||
|
"w-1": indicator === "line",
|
||||||
|
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||||
|
indicator === "dashed",
|
||||||
|
"my-0.5": nestLabel && indicator === "dashed",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--color-bg": indicatorColor,
|
||||||
|
"--color-border": indicatorColor,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 justify-between leading-none",
|
||||||
|
nestLabel ? "items-end" : "items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
{nestLabel ? tooltipLabel : null}
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{itemConfig?.label || item.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{item.value && (
|
||||||
|
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||||
|
{item.value.toLocaleString()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChartTooltipContent.displayName = "ChartTooltip"
|
||||||
|
|
||||||
|
const ChartLegend = RechartsPrimitive.Legend
|
||||||
|
|
||||||
|
const ChartLegendContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentProps<"div"> &
|
||||||
|
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||||
|
hideIcon?: boolean
|
||||||
|
nameKey?: string
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { config } = useChart()
|
||||||
|
|
||||||
|
if (!payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center gap-4",
|
||||||
|
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{payload.map((item) => {
|
||||||
|
const key = `${nameKey || item.dataKey || "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.value}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{itemConfig?.icon && !hideIcon ? (
|
||||||
|
<itemConfig.icon />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{itemConfig?.label}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChartLegendContent.displayName = "ChartLegend"
|
||||||
|
|
||||||
|
// Helper to extract item config from a payload.
|
||||||
|
function getPayloadConfigFromPayload(
|
||||||
|
config: ChartConfig,
|
||||||
|
payload: unknown,
|
||||||
|
key: string
|
||||||
|
) {
|
||||||
|
if (typeof payload !== "object" || payload === null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloadPayload =
|
||||||
|
"payload" in payload &&
|
||||||
|
typeof payload.payload === "object" &&
|
||||||
|
payload.payload !== null
|
||||||
|
? payload.payload
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
let configLabelKey: string = key
|
||||||
|
|
||||||
|
if (
|
||||||
|
key in payload &&
|
||||||
|
typeof payload[key as keyof typeof payload] === "string"
|
||||||
|
) {
|
||||||
|
configLabelKey = payload[key as keyof typeof payload] as string
|
||||||
|
} else if (
|
||||||
|
payloadPayload &&
|
||||||
|
key in payloadPayload &&
|
||||||
|
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||||
|
) {
|
||||||
|
configLabelKey = payloadPayload[
|
||||||
|
key as keyof typeof payloadPayload
|
||||||
|
] as string
|
||||||
|
}
|
||||||
|
|
||||||
|
return configLabelKey in config
|
||||||
|
? config[configLabelKey]
|
||||||
|
: config[key as keyof typeof config]
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartStyle,
|
||||||
|
}
|
||||||
30
docs/components/ui/checkbox.tsx
Normal file
30
docs/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { CheckIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
||||||
11
docs/components/ui/collapsible.tsx
Normal file
11
docs/components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||||
|
|
||||||
|
const Collapsible = CollapsiblePrimitive.Root
|
||||||
|
|
||||||
|
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
||||||
|
|
||||||
|
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
||||||
|
|
||||||
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||||
155
docs/components/ui/command.tsx
Normal file
155
docs/components/ui/command.tsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
|
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
|
||||||
|
const Command = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Command.displayName = CommandPrimitive.displayName
|
||||||
|
|
||||||
|
interface CommandDialogProps extends DialogProps {}
|
||||||
|
|
||||||
|
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="overflow-hidden p-0">
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||||
|
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||||
|
>((props, ref) => (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
ref={ref}
|
||||||
|
className="py-6 text-center text-sm"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||||
|
|
||||||
|
const CommandSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const CommandShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CommandShortcut.displayName = "CommandShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
}
|
||||||
204
docs/components/ui/context-menu.tsx
Normal file
204
docs/components/ui/context-menu.tsx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotFilledIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const ContextMenu = ContextMenuPrimitive.Root
|
||||||
|
|
||||||
|
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
||||||
|
|
||||||
|
const ContextMenuGroup = ContextMenuPrimitive.Group
|
||||||
|
|
||||||
|
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
||||||
|
|
||||||
|
const ContextMenuSub = ContextMenuPrimitive.Sub
|
||||||
|
|
||||||
|
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const ContextMenuSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||||
|
</ContextMenuPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const ContextMenuSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const ContextMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Portal>
|
||||||
|
<ContextMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</ContextMenuPrimitive.Portal>
|
||||||
|
))
|
||||||
|
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const ContextMenuItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const ContextMenuCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<ContextMenuPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</ContextMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</ContextMenuPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
ContextMenuCheckboxItem.displayName =
|
||||||
|
ContextMenuPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const ContextMenuRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<ContextMenuPrimitive.ItemIndicator>
|
||||||
|
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||||
|
</ContextMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</ContextMenuPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const ContextMenuLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const ContextMenuSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const ContextMenuShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuCheckboxItem,
|
||||||
|
ContextMenuRadioItem,
|
||||||
|
ContextMenuLabel,
|
||||||
|
ContextMenuSeparator,
|
||||||
|
ContextMenuShortcut,
|
||||||
|
ContextMenuGroup,
|
||||||
|
ContextMenuPortal,
|
||||||
|
ContextMenuSub,
|
||||||
|
ContextMenuSubContent,
|
||||||
|
ContextMenuSubTrigger,
|
||||||
|
ContextMenuRadioGroup,
|
||||||
|
}
|
||||||
122
docs/components/ui/dialog.tsx
Normal file
122
docs/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const DialogPortal = DialogPrimitive.Portal
|
||||||
|
|
||||||
|
const DialogClose = DialogPrimitive.Close
|
||||||
|
|
||||||
|
const DialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<Cross2Icon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
))
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogHeader.displayName = "DialogHeader"
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogFooter.displayName = "DialogFooter"
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
}
|
||||||
118
docs/components/ui/drawer.tsx
Normal file
118
docs/components/ui/drawer.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Drawer = ({
|
||||||
|
shouldScaleBackground = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||||
|
<DrawerPrimitive.Root
|
||||||
|
shouldScaleBackground={shouldScaleBackground}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Drawer.displayName = "Drawer"
|
||||||
|
|
||||||
|
const DrawerTrigger = DrawerPrimitive.Trigger
|
||||||
|
|
||||||
|
const DrawerPortal = DrawerPrimitive.Portal
|
||||||
|
|
||||||
|
const DrawerClose = DrawerPrimitive.Close
|
||||||
|
|
||||||
|
const DrawerOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DrawerContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
{children}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
|
))
|
||||||
|
DrawerContent.displayName = "DrawerContent"
|
||||||
|
|
||||||
|
const DrawerHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DrawerHeader.displayName = "DrawerHeader"
|
||||||
|
|
||||||
|
const DrawerFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DrawerFooter.displayName = "DrawerFooter"
|
||||||
|
|
||||||
|
const DrawerTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DrawerDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerPortal,
|
||||||
|
DrawerOverlay,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerDescription,
|
||||||
|
}
|
||||||
205
docs/components/ui/dropdown-menu.tsx
Normal file
205
docs/components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotFilledIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||||
|
|
||||||
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||||
|
|
||||||
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||||
|
|
||||||
|
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||||
|
|
||||||
|
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||||
|
|
||||||
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||||
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
DropdownMenuSubTrigger.displayName =
|
||||||
|
DropdownMenuPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuSubContent.displayName =
|
||||||
|
DropdownMenuPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const DropdownMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Portal>
|
||||||
|
<DropdownMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
|
))
|
||||||
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DropdownMenuItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
DropdownMenuCheckboxItem.displayName =
|
||||||
|
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const DropdownMenuRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const DropdownMenuLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const DropdownMenuSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const DropdownMenuShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
}
|
||||||
67
docs/components/ui/fade-in.tsx
Normal file
67
docs/components/ui/fade-in.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AnimatePresence as PrimitiveAnimatePresence,
|
||||||
|
motion,
|
||||||
|
useReducedMotion,
|
||||||
|
} from "framer-motion";
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
const FadeInStaggerContext = createContext(false);
|
||||||
|
|
||||||
|
const viewport = { once: true, margin: "0px 0px -200px" };
|
||||||
|
|
||||||
|
export const FadeIn = (
|
||||||
|
props: React.ComponentPropsWithoutRef<typeof motion.div> & {
|
||||||
|
fromTopToBottom?: boolean;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const shouldReduceMotion = useReducedMotion();
|
||||||
|
const isInStaggerGroup = useContext(FadeInStaggerContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
variants={{
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: shouldReduceMotion ? 0 : props.fromTopToBottom ? -24 : 24,
|
||||||
|
},
|
||||||
|
visible: { opacity: 1, y: 0 },
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
{...(isInStaggerGroup
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
initial: "hidden",
|
||||||
|
whileInView: "visible",
|
||||||
|
viewport,
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FadeInStagger = ({
|
||||||
|
faster = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentPropsWithoutRef<typeof motion.div> & {
|
||||||
|
faster?: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<FadeInStaggerContext.Provider value={true}>
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={viewport}
|
||||||
|
transition={{ staggerChildren: faster ? 0.12 : 0.2 }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</FadeInStaggerContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AnimatePresence = (
|
||||||
|
props: React.ComponentPropsWithoutRef<typeof PrimitiveAnimatePresence>,
|
||||||
|
) => {
|
||||||
|
return <PrimitiveAnimatePresence {...props} />;
|
||||||
|
};
|
||||||
178
docs/components/ui/form.tsx
Normal file
178
docs/components/ui/form.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
ControllerProps,
|
||||||
|
FieldPath,
|
||||||
|
FieldValues,
|
||||||
|
FormProvider,
|
||||||
|
useFormContext,
|
||||||
|
} from "react-hook-form"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
|
const Form = FormProvider
|
||||||
|
|
||||||
|
type FormFieldContextValue<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
> = {
|
||||||
|
name: TName
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
|
{} as FormFieldContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormField = <
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
>({
|
||||||
|
...props
|
||||||
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
|
return (
|
||||||
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
|
<Controller {...props} />
|
||||||
|
</FormFieldContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useFormField = () => {
|
||||||
|
const fieldContext = React.useContext(FormFieldContext)
|
||||||
|
const itemContext = React.useContext(FormItemContext)
|
||||||
|
const { getFieldState, formState } = useFormContext()
|
||||||
|
|
||||||
|
const fieldState = getFieldState(fieldContext.name, formState)
|
||||||
|
|
||||||
|
if (!fieldContext) {
|
||||||
|
throw new Error("useFormField should be used within <FormField>")
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = itemContext
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: fieldContext.name,
|
||||||
|
formItemId: `${id}-form-item`,
|
||||||
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
|
formMessageId: `${id}-form-item-message`,
|
||||||
|
...fieldState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormItemContextValue = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
|
{} as FormItemContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormItem = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const id = React.useId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ id }}>
|
||||||
|
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormItem.displayName = "FormItem"
|
||||||
|
|
||||||
|
const FormLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { error, formItemId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(error && "text-destructive", className)}
|
||||||
|
htmlFor={formItemId}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormLabel.displayName = "FormLabel"
|
||||||
|
|
||||||
|
const FormControl = React.forwardRef<
|
||||||
|
React.ElementRef<typeof Slot>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof Slot>
|
||||||
|
>(({ ...props }, ref) => {
|
||||||
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot
|
||||||
|
ref={ref}
|
||||||
|
id={formItemId}
|
||||||
|
aria-describedby={
|
||||||
|
!error
|
||||||
|
? `${formDescriptionId}`
|
||||||
|
: `${formDescriptionId} ${formMessageId}`
|
||||||
|
}
|
||||||
|
aria-invalid={!!error}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormControl.displayName = "FormControl"
|
||||||
|
|
||||||
|
const FormDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { formDescriptionId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formDescriptionId}
|
||||||
|
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormDescription.displayName = "FormDescription"
|
||||||
|
|
||||||
|
const FormMessage = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, children, ...props }, ref) => {
|
||||||
|
const { error, formMessageId } = useFormField()
|
||||||
|
const body = error ? String(error?.message) : children
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formMessageId}
|
||||||
|
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormMessage.displayName = "FormMessage"
|
||||||
|
|
||||||
|
export {
|
||||||
|
useFormField,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormMessage,
|
||||||
|
FormField,
|
||||||
|
}
|
||||||
29
docs/components/ui/hover-card.tsx
Normal file
29
docs/components/ui/hover-card.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const HoverCard = HoverCardPrimitive.Root
|
||||||
|
|
||||||
|
const HoverCardTrigger = HoverCardPrimitive.Trigger
|
||||||
|
|
||||||
|
const HoverCardContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<HoverCardPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
||||||
71
docs/components/ui/input-otp.tsx
Normal file
71
docs/components/ui/input-otp.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { DashIcon } from "@radix-ui/react-icons"
|
||||||
|
import { OTPInput, OTPInputContext } from "input-otp"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const InputOTP = React.forwardRef<
|
||||||
|
React.ElementRef<typeof OTPInput>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof OTPInput>
|
||||||
|
>(({ className, containerClassName, ...props }, ref) => (
|
||||||
|
<OTPInput
|
||||||
|
ref={ref}
|
||||||
|
containerClassName={cn(
|
||||||
|
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
||||||
|
containerClassName
|
||||||
|
)}
|
||||||
|
className={cn("disabled:cursor-not-allowed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
InputOTP.displayName = "InputOTP"
|
||||||
|
|
||||||
|
const InputOTPGroup = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("flex items-center", className)} {...props} />
|
||||||
|
))
|
||||||
|
InputOTPGroup.displayName = "InputOTPGroup"
|
||||||
|
|
||||||
|
const InputOTPSlot = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div"> & { index: number }
|
||||||
|
>(({ index, className, ...props }, ref) => {
|
||||||
|
const inputOTPContext = React.useContext(OTPInputContext)
|
||||||
|
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
||||||
|
isActive && "z-10 ring-1 ring-ring",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
{hasFakeCaret && (
|
||||||
|
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
InputOTPSlot.displayName = "InputOTPSlot"
|
||||||
|
|
||||||
|
const InputOTPSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ ...props }, ref) => (
|
||||||
|
<div ref={ref} role="separator" {...props}>
|
||||||
|
<DashIcon />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
InputOTPSeparator.displayName = "InputOTPSeparator"
|
||||||
|
|
||||||
|
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
||||||
25
docs/components/ui/input.tsx
Normal file
25
docs/components/ui/input.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface InputProps
|
||||||
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
export { Input }
|
||||||
26
docs/components/ui/label.tsx
Normal file
26
docs/components/ui/label.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
||||||
240
docs/components/ui/menubar.tsx
Normal file
240
docs/components/ui/menubar.tsx
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotFilledIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const MenubarMenu = MenubarPrimitive.Menu
|
||||||
|
|
||||||
|
const MenubarGroup = MenubarPrimitive.Group
|
||||||
|
|
||||||
|
const MenubarPortal = MenubarPrimitive.Portal
|
||||||
|
|
||||||
|
const MenubarSub = MenubarPrimitive.Sub
|
||||||
|
|
||||||
|
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const Menubar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Menubar.displayName = MenubarPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const MenubarTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const MenubarSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||||
|
</MenubarPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const MenubarSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const MenubarContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<MenubarPrimitive.Portal>
|
||||||
|
<MenubarPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</MenubarPrimitive.Portal>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
MenubarContent.displayName = MenubarPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const MenubarItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarItem.displayName = MenubarPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const MenubarCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<MenubarPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</MenubarPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</MenubarPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const MenubarRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<MenubarPrimitive.ItemIndicator>
|
||||||
|
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||||
|
</MenubarPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</MenubarPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const MenubarLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const MenubarSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const MenubarShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MenubarShortcut.displayname = "MenubarShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Menubar,
|
||||||
|
MenubarMenu,
|
||||||
|
MenubarTrigger,
|
||||||
|
MenubarContent,
|
||||||
|
MenubarItem,
|
||||||
|
MenubarSeparator,
|
||||||
|
MenubarLabel,
|
||||||
|
MenubarCheckboxItem,
|
||||||
|
MenubarRadioGroup,
|
||||||
|
MenubarRadioItem,
|
||||||
|
MenubarPortal,
|
||||||
|
MenubarSubContent,
|
||||||
|
MenubarSubTrigger,
|
||||||
|
MenubarGroup,
|
||||||
|
MenubarSub,
|
||||||
|
MenubarShortcut,
|
||||||
|
}
|
||||||
128
docs/components/ui/navigation-menu.tsx
Normal file
128
docs/components/ui/navigation-menu.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||||
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const NavigationMenu = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<NavigationMenuViewport />
|
||||||
|
</NavigationMenuPrimitive.Root>
|
||||||
|
))
|
||||||
|
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const NavigationMenuList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||||
|
|
||||||
|
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||||
|
|
||||||
|
const navigationMenuTriggerStyle = cva(
|
||||||
|
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||||
|
)
|
||||||
|
|
||||||
|
const NavigationMenuTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}{" "}
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</NavigationMenuPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const NavigationMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||||
|
|
||||||
|
const NavigationMenuViewport = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||||
|
<NavigationMenuPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
NavigationMenuViewport.displayName =
|
||||||
|
NavigationMenuPrimitive.Viewport.displayName
|
||||||
|
|
||||||
|
const NavigationMenuIndicator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Indicator
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||||
|
</NavigationMenuPrimitive.Indicator>
|
||||||
|
))
|
||||||
|
NavigationMenuIndicator.displayName =
|
||||||
|
NavigationMenuPrimitive.Indicator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
navigationMenuTriggerStyle,
|
||||||
|
NavigationMenu,
|
||||||
|
NavigationMenuList,
|
||||||
|
NavigationMenuItem,
|
||||||
|
NavigationMenuContent,
|
||||||
|
NavigationMenuTrigger,
|
||||||
|
NavigationMenuLink,
|
||||||
|
NavigationMenuIndicator,
|
||||||
|
NavigationMenuViewport,
|
||||||
|
}
|
||||||
121
docs/components/ui/pagination.tsx
Normal file
121
docs/components/ui/pagination.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotsHorizontalIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Pagination.displayName = "Pagination"
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<"ul">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
PaginationContent.displayName = "PaginationContent"
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn("", className)} {...props} />
|
||||||
|
))
|
||||||
|
PaginationItem.displayName = "PaginationItem"
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean
|
||||||
|
} & Pick<ButtonProps, "size"> &
|
||||||
|
React.ComponentProps<"a">
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
PaginationLink.displayName = "PaginationLink"
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationPrevious.displayName = "PaginationPrevious"
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRightIcon className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationNext.displayName = "PaginationNext"
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<DotsHorizontalIcon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationEllipsis,
|
||||||
|
}
|
||||||
33
docs/components/ui/popover.tsx
Normal file
33
docs/components/ui/popover.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
|
const PopoverAnchor = PopoverPrimitive.Anchor
|
||||||
|
|
||||||
|
const PopoverContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
))
|
||||||
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||||
28
docs/components/ui/progress.tsx
Normal file
28
docs/components/ui/progress.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
))
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Progress }
|
||||||
44
docs/components/ui/radio-group.tsx
Normal file
44
docs/components/ui/radio-group.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { CheckIcon } from "@radix-ui/react-icons"
|
||||||
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const RadioGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
className={cn("grid gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const RadioGroupItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||||
|
<CheckIcon className="h-3.5 w-3.5 fill-primary" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem }
|
||||||
45
docs/components/ui/resizable.tsx
Normal file
45
docs/components/ui/resizable.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { DragHandleDots2Icon } from "@radix-ui/react-icons"
|
||||||
|
import * as ResizablePrimitive from "react-resizable-panels"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const ResizablePanelGroup = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
||||||
|
<ResizablePrimitive.PanelGroup
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ResizablePanel = ResizablePrimitive.Panel
|
||||||
|
|
||||||
|
const ResizableHandle = ({
|
||||||
|
withHandle,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||||
|
withHandle?: boolean
|
||||||
|
}) => (
|
||||||
|
<ResizablePrimitive.PanelResizeHandle
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{withHandle && (
|
||||||
|
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
||||||
|
<DragHandleDots2Icon className="h-2.5 w-2.5" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ResizablePrimitive.PanelResizeHandle>
|
||||||
|
)
|
||||||
|
|
||||||
|
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
||||||
48
docs/components/ui/scroll-area.tsx
Normal file
48
docs/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const ScrollArea = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn("relative overflow-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
))
|
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const ScrollBar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
ref={ref}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"flex touch-none select-none transition-colors",
|
||||||
|
orientation === "vertical" &&
|
||||||
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
|
orientation === "horizontal" &&
|
||||||
|
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
))
|
||||||
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar }
|
||||||
164
docs/components/ui/select.tsx
Normal file
164
docs/components/ui/select.tsx
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
CaretSortIcon,
|
||||||
|
CheckIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronUpIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUpIcon />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
))
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
))
|
||||||
|
SelectScrollDownButton.displayName =
|
||||||
|
SelectPrimitive.ScrollDownButton.displayName
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
))
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
))
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
}
|
||||||
31
docs/components/ui/separator.tsx
Normal file
31
docs/components/ui/separator.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Separator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 bg-border",
|
||||||
|
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Separator }
|
||||||
140
docs/components/ui/sheet.tsx
Normal file
140
docs/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close
|
||||||
|
|
||||||
|
const SheetPortal = SheetPrimitive.Portal
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||||
|
bottom:
|
||||||
|
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||||
|
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||||
|
right:
|
||||||
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SheetContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
|
SheetContentProps
|
||||||
|
>(({ side = "right", className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(sheetVariants({ side }), className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||||
|
<Cross2Icon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
{children}
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
))
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SheetHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetHeader.displayName = "SheetHeader"
|
||||||
|
|
||||||
|
const SheetFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetFooter.displayName = "SheetFooter"
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
}
|
||||||
15
docs/components/ui/skeleton.tsx
Normal file
15
docs/components/ui/skeleton.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Skeleton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("animate-pulse rounded-md bg-primary/10", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton }
|
||||||
28
docs/components/ui/slider.tsx
Normal file
28
docs/components/ui/slider.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Slider = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SliderPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full touch-none select-none items-center",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||||
|
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||||
|
</SliderPrimitive.Track>
|
||||||
|
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||||
|
</SliderPrimitive.Root>
|
||||||
|
))
|
||||||
|
Slider.displayName = SliderPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Slider }
|
||||||
31
docs/components/ui/sonner.tsx
Normal file
31
docs/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
import { Toaster as Sonner } from "sonner"
|
||||||
|
|
||||||
|
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = "system" } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps["theme"]}
|
||||||
|
className="toaster group"
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast:
|
||||||
|
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||||
|
description: "group-[.toast]:text-muted-foreground",
|
||||||
|
actionButton:
|
||||||
|
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||||
|
cancelButton:
|
||||||
|
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toaster }
|
||||||
29
docs/components/ui/switch.tsx
Normal file
29
docs/components/ui/switch.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
))
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||||
|
|
||||||
|
export { Switch }
|
||||||
120
docs/components/ui/table.tsx
Normal file
120
docs/components/ui/table.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Table = React.forwardRef<
|
||||||
|
HTMLTableElement,
|
||||||
|
React.HTMLAttributes<HTMLTableElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="relative w-full overflow-auto">
|
||||||
|
<table
|
||||||
|
ref={ref}
|
||||||
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
Table.displayName = "Table"
|
||||||
|
|
||||||
|
const TableHeader = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||||
|
))
|
||||||
|
TableHeader.displayName = "TableHeader"
|
||||||
|
|
||||||
|
const TableBody = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tbody
|
||||||
|
ref={ref}
|
||||||
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableBody.displayName = "TableBody"
|
||||||
|
|
||||||
|
const TableFooter = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tfoot
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableFooter.displayName = "TableFooter"
|
||||||
|
|
||||||
|
const TableRow = React.forwardRef<
|
||||||
|
HTMLTableRowElement,
|
||||||
|
React.HTMLAttributes<HTMLTableRowElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tr
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableRow.displayName = "TableRow"
|
||||||
|
|
||||||
|
const TableHead = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<th
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableHead.displayName = "TableHead"
|
||||||
|
|
||||||
|
const TableCell = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<td
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableCell.displayName = "TableCell"
|
||||||
|
|
||||||
|
const TableCaption = React.forwardRef<
|
||||||
|
HTMLTableCaptionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<caption
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableCaption.displayName = "TableCaption"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
TableCaption,
|
||||||
|
}
|
||||||
55
docs/components/ui/tabs.tsx
Normal file
55
docs/components/ui/tabs.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
24
docs/components/ui/textarea.tsx
Normal file
24
docs/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface TextareaProps
|
||||||
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
||||||
129
docs/components/ui/toast.tsx
Normal file
129
docs/components/ui/toast.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||||
|
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const ToastProvider = ToastPrimitives.Provider
|
||||||
|
|
||||||
|
const ToastViewport = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ToastPrimitives.Viewport
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||||
|
|
||||||
|
const toastVariants = cva(
|
||||||
|
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "border bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Toast = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||||
|
VariantProps<typeof toastVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<ToastPrimitives.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(toastVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Toast.displayName = ToastPrimitives.Root.displayName
|
||||||
|
|
||||||
|
const ToastAction = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToastPrimitives.Action>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ToastPrimitives.Action
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ToastAction.displayName = ToastPrimitives.Action.displayName
|
||||||
|
|
||||||
|
const ToastClose = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToastPrimitives.Close>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ToastPrimitives.Close
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
toast-close=""
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Cross2Icon className="h-4 w-4" />
|
||||||
|
</ToastPrimitives.Close>
|
||||||
|
))
|
||||||
|
ToastClose.displayName = ToastPrimitives.Close.displayName
|
||||||
|
|
||||||
|
const ToastTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ToastPrimitives.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
||||||
|
|
||||||
|
const ToastDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ToastPrimitives.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm opacity-90", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
||||||
|
|
||||||
|
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
||||||
|
|
||||||
|
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
||||||
|
|
||||||
|
export {
|
||||||
|
type ToastProps,
|
||||||
|
type ToastActionElement,
|
||||||
|
ToastProvider,
|
||||||
|
ToastViewport,
|
||||||
|
Toast,
|
||||||
|
ToastTitle,
|
||||||
|
ToastDescription,
|
||||||
|
ToastClose,
|
||||||
|
ToastAction,
|
||||||
|
}
|
||||||
35
docs/components/ui/toaster.tsx
Normal file
35
docs/components/ui/toaster.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useToast } from "@/hooks/use-toast"
|
||||||
|
import {
|
||||||
|
Toast,
|
||||||
|
ToastClose,
|
||||||
|
ToastDescription,
|
||||||
|
ToastProvider,
|
||||||
|
ToastTitle,
|
||||||
|
ToastViewport,
|
||||||
|
} from "@/components/ui/toast"
|
||||||
|
|
||||||
|
export function Toaster() {
|
||||||
|
const { toasts } = useToast()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastProvider>
|
||||||
|
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||||
|
return (
|
||||||
|
<Toast key={id} {...props}>
|
||||||
|
<div className="grid gap-1">
|
||||||
|
{title && <ToastTitle>{title}</ToastTitle>}
|
||||||
|
{description && (
|
||||||
|
<ToastDescription>{description}</ToastDescription>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{action}
|
||||||
|
<ToastClose />
|
||||||
|
</Toast>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<ToastViewport />
|
||||||
|
</ToastProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
61
docs/components/ui/toggle-group.tsx
Normal file
61
docs/components/ui/toggle-group.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||||
|
import { type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { toggleVariants } from "@/components/ui/toggle"
|
||||||
|
|
||||||
|
const ToggleGroupContext = React.createContext<
|
||||||
|
VariantProps<typeof toggleVariants>
|
||||||
|
>({
|
||||||
|
size: "default",
|
||||||
|
variant: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToggleGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||||
|
VariantProps<typeof toggleVariants>
|
||||||
|
>(({ className, variant, size, children, ...props }, ref) => (
|
||||||
|
<ToggleGroupPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center justify-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||||
|
{children}
|
||||||
|
</ToggleGroupContext.Provider>
|
||||||
|
</ToggleGroupPrimitive.Root>
|
||||||
|
))
|
||||||
|
|
||||||
|
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const ToggleGroupItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||||
|
VariantProps<typeof toggleVariants>
|
||||||
|
>(({ className, children, variant, size, ...props }, ref) => {
|
||||||
|
const context = React.useContext(ToggleGroupContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleGroupPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
toggleVariants({
|
||||||
|
variant: context.variant || variant,
|
||||||
|
size: context.size || size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ToggleGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
||||||
|
|
||||||
|
export { ToggleGroup, ToggleGroupItem }
|
||||||
45
docs/components/ui/toggle.tsx
Normal file
45
docs/components/ui/toggle.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const toggleVariants = cva(
|
||||||
|
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-transparent",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-3",
|
||||||
|
sm: "h-8 px-2",
|
||||||
|
lg: "h-10 px-3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Toggle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||||
|
VariantProps<typeof toggleVariants>
|
||||||
|
>(({ className, variant, size, ...props }, ref) => (
|
||||||
|
<TogglePrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(toggleVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
Toggle.displayName = TogglePrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Toggle, toggleVariants }
|
||||||
30
docs/components/ui/tooltip.tsx
Normal file
30
docs/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
371
docs/content/docs/basic-usage.mdx
Normal file
371
docs/content/docs/basic-usage.mdx
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
---
|
||||||
|
title: Basic Usage
|
||||||
|
description: Usage
|
||||||
|
---
|
||||||
|
|
||||||
|
## Signin and Signup with Email
|
||||||
|
|
||||||
|
To use signin and signup with email, you need to configure the email and password authenticator. You can do this by adding the following code to your `auth.ts` file:
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
emailAndPassword: { // [!code highlight]
|
||||||
|
enabled: true // [!code highlight]
|
||||||
|
} // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signup
|
||||||
|
|
||||||
|
To signup a user, you can use the `signUp.email` function provided by the client. The `signUp` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `email`: The email address of the user.
|
||||||
|
- `password`: The password of the user.
|
||||||
|
- `name`: The name of the user.
|
||||||
|
- `image`: The image of the user. (optional)
|
||||||
|
- `callbackUrl`: The url to redirect to after the user has signed up. (optional)
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signup = async () => {
|
||||||
|
const data = await client.signUp.email({
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password1234",
|
||||||
|
name: "test",
|
||||||
|
image: "https://example.com/image.png",
|
||||||
|
callbackUrl: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The function returns a promise that resolves an object with `data` and `error` properties. The `data` property contains the user object that was created, and the `error` property contains any error that occurred during the signup process.
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
If you want to use username instead of email, you can use <Link href="/docs/plugins/username">username Plugin</Link>.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Signin
|
||||||
|
|
||||||
|
To signin a user, you can use the `signIn.email` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `email`: The email address of the user.
|
||||||
|
- `password`: The password of the user.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.email({
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password1234"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Signin and Signup with Social Providers
|
||||||
|
|
||||||
|
Better Auth supports multiple social providers, including Google, Github, Apple, Discord, and more. To use a social provider, you need to configure the ones you need in the `socialProvider` option on your `auth` object.
|
||||||
|
|
||||||
|
### Configure Social Providers
|
||||||
|
|
||||||
|
To configure social providers, you need to import the provider you want to use and pass it to the `socialProvider` option. For example, to configure the Github provider, you can use the following code:
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
//@ts-expect-error
|
||||||
|
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID as string
|
||||||
|
//@ts-expect-error
|
||||||
|
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET as string
|
||||||
|
|
||||||
|
// ---cut---
|
||||||
|
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { github } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
github({ // [!code highlight]
|
||||||
|
clientId: GITHUB_CLIENT_ID, // [!code highlight]
|
||||||
|
clientSecret: GITHUB_CLIENT_SECRET, // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
See the <Link href="/docs/providers">Provider</Link> section for more information on how to configure each provider.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Signin with social providers
|
||||||
|
|
||||||
|
```ts title="signin.ts" twoslash
|
||||||
|
// @filename: client.ts
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
export const client = createAuthClient()
|
||||||
|
|
||||||
|
// ---cut---
|
||||||
|
// @filename: signin.ts
|
||||||
|
import { client } from "./client"
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "github"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Session
|
||||||
|
|
||||||
|
Once a user is signed in, you'll want to access their session. Better auth allows you easily to access the session data from the server and client side.
|
||||||
|
|
||||||
|
### Client Side
|
||||||
|
|
||||||
|
the client providers a `useSession` hook or a `session` object that you can use to access the session data. Each framework client implements the session getter in a reactive way. So if there are actions that affect the session (like signing out), it'll be reflected in the client.
|
||||||
|
|
||||||
|
<Tabs items={["React", "Vue","Svelte", "Solid"]} defaultValue="React">
|
||||||
|
<Tab value="React">
|
||||||
|
```tsx title="user.tsx"
|
||||||
|
//make sure you're using the react client
|
||||||
|
import { createAuthClient } from "better-auth/react"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
export function User(){
|
||||||
|
const session = client.useSession()
|
||||||
|
returns (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
session ? (
|
||||||
|
<div>
|
||||||
|
<button onClick={async () => {
|
||||||
|
await client.signOut()
|
||||||
|
}}>
|
||||||
|
Signout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button onClick={async () => {
|
||||||
|
await client.signIn.social({
|
||||||
|
provider: "github",
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
Continue with github
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value="Vue">
|
||||||
|
```vue title="user.vue"
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button v-if="!client.useSession().value" @click="() => client.signIn.social({
|
||||||
|
provider: 'github'
|
||||||
|
})">
|
||||||
|
Continue with github
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<pre>{{ client.useSession().value }}</pre>
|
||||||
|
<button v-if="client.useSession().value" @click="client.signOut()">
|
||||||
|
Sign out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value="Svelte">
|
||||||
|
```svelte title="user.svelte"
|
||||||
|
<script lang="ts">
|
||||||
|
import { client } from "$lib/client";
|
||||||
|
const session = client.$session;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style="display: flex; flex-direction: column; gap: 10px; border-radius: 10px; border: 1px solid #4B453F; padding: 20px; margin-top: 10px;"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{#if $session}
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{$session?.user.name}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{$session?.user.email}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
on:click={async () => {
|
||||||
|
await client.signOut();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Signout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
on:click={async () => {
|
||||||
|
await client.signIn.social({
|
||||||
|
provider: "github",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Continue with github
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab value="Solid">
|
||||||
|
```tsx title="user.tsx"
|
||||||
|
import { client } from "~/lib/client";
|
||||||
|
import { Show } from 'solid-js';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const session = client.useSession()
|
||||||
|
return (
|
||||||
|
<Show
|
||||||
|
when={session()}
|
||||||
|
fallback={<button onClick={toggle}>Log in</button>}
|
||||||
|
>
|
||||||
|
<button onClick={toggle}>Log out</button>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### Server Side
|
||||||
|
|
||||||
|
The server provides a `session` object that you can use to access the session data.
|
||||||
|
|
||||||
|
|
||||||
|
```ts title="server.ts" twoslash
|
||||||
|
// @filename: auth.ts
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
//...rest of the options
|
||||||
|
})
|
||||||
|
// ---cut-start---
|
||||||
|
// @filename: server.ts
|
||||||
|
// ---cut-end---
|
||||||
|
|
||||||
|
// somewhere in your server code
|
||||||
|
import { api } from "./auth"
|
||||||
|
async function addToCart(request: Request){
|
||||||
|
const session = await api.getSession({
|
||||||
|
headers: request.headers, //it requies a header to be passed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
For next js on RSC and server actions you can use import `headers` from `next/headers` and pass it to the `getSession` function.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Two Factor
|
||||||
|
|
||||||
|
### Introduction to plugins
|
||||||
|
|
||||||
|
One of the unique features of better auth is a plugins ecosystem. It allows you to add complex auth realted functionilty with small lines of code. Better auth come with many 1st party plugins, but you can also create your own plugins. To see a list of plugins, you can check the <Link href="/plugins">plugins list</Link> and if you want to create your own plugin, you can check the <Link href="/docs/plugins">plugin documentation</Link>.
|
||||||
|
|
||||||
|
### Server Configuration
|
||||||
|
|
||||||
|
To add a plugin, you need to import the plugin and pass it to the `plugins` option of the auth instance. For example, to add two facor authentication, you can use the following code:
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { twoFactor } from "better-auth/plugins"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
plugins: [ // [!code highlight]
|
||||||
|
twoFactor({ // [!code highlight]
|
||||||
|
issuer: "my-app" // [!code highlight]
|
||||||
|
}) // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
now two factor related routes and method will be available on the server.
|
||||||
|
|
||||||
|
### Migrate Database
|
||||||
|
|
||||||
|
once you have added the plugin, you need to migrate your database to add the necessary tables and fields. You can do this by running the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx better-auth migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Configuration
|
||||||
|
|
||||||
|
Once we're done with the server, we need to add the plugin to the client. To do this, you need to import the plugin and pass it to the `authPlugins` option of the auth client. For example, to add two facor authentication, you can use the following code:
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient, twoFactorClient } from "better-auth/client"
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
authPlugins: [ // [!code highlight]
|
||||||
|
twoFactorClient({ // [!code highlight]
|
||||||
|
twoFactorPage: "/two-factor" // [!code highlight]
|
||||||
|
}) // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
now two factor related methods will be available on the client.
|
||||||
|
|
||||||
|
```ts title="profile.ts" twoslash
|
||||||
|
// @filename: client.ts
|
||||||
|
import { createAuthClient, twoFactorClient } from "better-auth/client"
|
||||||
|
|
||||||
|
export const client = createAuthClient({
|
||||||
|
authPlugins: [ // [!code highlight]
|
||||||
|
twoFactorClient({ // [!code highlight]
|
||||||
|
twoFactorPage: "/two-factor" // [!code highlight]
|
||||||
|
}) // [!code highlight]
|
||||||
|
] // [!code highlight]
|
||||||
|
})
|
||||||
|
|
||||||
|
// ---cut---
|
||||||
|
|
||||||
|
// @filename: profile.ts
|
||||||
|
// ---cut---
|
||||||
|
import { client } from "./client"
|
||||||
|
|
||||||
|
const enableTwoFactor = async() => {
|
||||||
|
const data = await client.twoFactor.enable() // this will enable two factor authentication for the signed in user
|
||||||
|
}
|
||||||
|
```
|
||||||
32
docs/content/docs/email-password/configuration.mdx
Normal file
32
docs/content/docs/email-password/configuration.mdx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Email and Password Configuration
|
||||||
|
description: Configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
<TypeTable
|
||||||
|
type={{
|
||||||
|
enabled: {
|
||||||
|
description:
|
||||||
|
'Enable email and password authentication',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
minPasswordLength: {
|
||||||
|
description: 'The minimum length of the password.',
|
||||||
|
type: 'number',
|
||||||
|
default: 8,
|
||||||
|
},
|
||||||
|
maxPasswordLength: {
|
||||||
|
description: 'The maximum length of the password.',
|
||||||
|
type: 'number',
|
||||||
|
default: 32,
|
||||||
|
},
|
||||||
|
sendResetPasswordToken: {
|
||||||
|
description: 'send reset password email. It takes a functions that takes two parameters: token and user.',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
|
sendVerificationEmail: {
|
||||||
|
description: 'send verification email. It takes a functions that takes two parameters: email and url.',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
70
docs/content/docs/email-password/password-reset.mdx
Normal file
70
docs/content/docs/email-password/password-reset.mdx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: Password Reset
|
||||||
|
description: Password Reset
|
||||||
|
---
|
||||||
|
|
||||||
|
to reset a password first you need to provider `sendResetPasswordToken` function to the email and password authenticator. The `sendResetPasswordToken` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `user`: The user object.
|
||||||
|
- `token`: The token that was generated.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
async function sendResetEmail(email: string, url: string){
|
||||||
|
// send email to user
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
emailAndPassword: { // [!code highlight]
|
||||||
|
enabled: true, // [!code highlight]
|
||||||
|
async sendResetPasswordToken(token, user) { // [!code highlight]
|
||||||
|
// send email to user // [!code highlight]
|
||||||
|
const url = `https://example.com/reset-password?token=${token}` // [!code highlight]
|
||||||
|
await sendResetEmail(user.email, url) //your function to send email to user // [!code highlight]
|
||||||
|
}, // [!code highlight]
|
||||||
|
} // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
once you configured your server you can call `forgetPassword` function to send reset password link to user.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
// ---cut---
|
||||||
|
const forgetPassword = async () => {
|
||||||
|
const data = await client.forgetPassword({
|
||||||
|
email: "test@example.com",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When user click on the link in the email he will be redirected to the reset password page. You can add the reset password page to your app. Then you can use `resetPassword` function to reset the password. It takes an object with the following properties:
|
||||||
|
|
||||||
|
- `token`: The token that was generated.
|
||||||
|
- `newPassword`: The new password of the user.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
|
||||||
|
const client = createAuthClient()
|
||||||
|
function useSearchParams() {
|
||||||
|
return {
|
||||||
|
get: (key: string) => "" as string | null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
const token = useSearchParams().get("token") // get token from url
|
||||||
|
const resetPassword = async () => {
|
||||||
|
if(!token) return
|
||||||
|
const data = await client.resetPassword({
|
||||||
|
token: token,
|
||||||
|
newPassword: "password1234",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
127
docs/content/docs/email-password/sign-in-and-sign-up.mdx
Normal file
127
docs/content/docs/email-password/sign-in-and-sign-up.mdx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
---
|
||||||
|
title: Email & Password
|
||||||
|
description: Email & Password
|
||||||
|
---
|
||||||
|
|
||||||
|
Email and password authentication is a common authentication method used by many applications. It involves users entering their email and password to log in. Better Auth provides a built-in email and password authenticator that you can use to authenticate users.
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
If you want to use username as an identifier you can use <Link href="/docs/plugins/username">username plugin</Link>. It wraps the email and password authenticator and adds username support.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Signup and Signin with Email
|
||||||
|
|
||||||
|
To use email and password authentication, you need to configure the email and password authenticator. You can do this by adding the following code to your better auth instance:
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
emailAndPassword: { // [!code highlight]
|
||||||
|
enabled: true // [!code highlight]
|
||||||
|
} // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
If it's not enabled, it'll not allow you to sign in or sign up with email and password.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Signup
|
||||||
|
|
||||||
|
To signup a user, you can use the `signUp.email` function provided by the client. The `signUp` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `email`: The email address of the user.
|
||||||
|
- `password`: The password of the user. It should be at least 8 characters long and max 32 by default.
|
||||||
|
- `name`: The name of the user.
|
||||||
|
- `image`: The image of the user. (optional)
|
||||||
|
- `callbackURL`: The url to redirect to after the user has signed up. (optional)
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
/**
|
||||||
|
* Make sure to import the client for your framework
|
||||||
|
*/
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signup = async () => {
|
||||||
|
const data = await client.signUp.email({
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password1234",
|
||||||
|
name: "test",
|
||||||
|
image: "https://example.com/image.png",
|
||||||
|
callbackURL: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The function returns a promise that resolves an object with `data` and `error` properties. The `data` property contains the user object that was created, and the `error` property contains any error that occurred during the signup process.
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
Hover over the `data` object to see the shape of the response.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
### Signin
|
||||||
|
|
||||||
|
To signin a user, you can use the `signIn.email` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
|
||||||
|
- `email`: The email address of the user.
|
||||||
|
- `password`: The password of the user.
|
||||||
|
- `callbackURL`: The url to redirect to after the user has signed in. (optional)
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.email({
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password1234",
|
||||||
|
callbackURL: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Email Verification
|
||||||
|
|
||||||
|
To enable email verification, you need to configure the email and password authenticator. You can do this by adding the following code to your better auth instance:
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
// ---cut-start---
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite"
|
||||||
|
},
|
||||||
|
// ---cut-end---
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
async sendVerificationEmail(email, url){
|
||||||
|
// send email to user.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
on the client side you can use `sendVerificationEmail` function to send verification link to user.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
// ---cut---
|
||||||
|
const verifyEmail = async () => {
|
||||||
|
const data = await client.sendVerificationEmail({
|
||||||
|
email: "test@example.com",
|
||||||
|
callbackURL: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
132
docs/content/docs/installation.mdx
Normal file
132
docs/content/docs/installation.mdx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
description: Installation
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Install better-auth
|
||||||
|
start by installing better-auth package. You need this both on the server and client
|
||||||
|
|
||||||
|
```package-install
|
||||||
|
better-auth
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Environment Variables
|
||||||
|
Better auth requries a secret. This is a random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
|
||||||
|
```txt title=".env.local"
|
||||||
|
BETTER_AUTH_SECRET=
|
||||||
|
```
|
||||||
|
<GenerateSecret/>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Create Server
|
||||||
|
better auth requries a file called `auth.ts` or `auth.config.ts` to be present in the root directory or one of the following directories:
|
||||||
|
- `lib/`
|
||||||
|
- `utils/`
|
||||||
|
|
||||||
|
any of these directories can also be nested under `src/` directory.
|
||||||
|
|
||||||
|
```ts twoslash title="auth.ts"
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite", // or "mysql", "postgresql"
|
||||||
|
url: "./db.sqlite", // path to your database
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
<Callout type="info">
|
||||||
|
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
|
||||||
|
</Callout>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Providers
|
||||||
|
better auth comes with email and password authenticator and social sign-on providers. You can configure them in the auth object.
|
||||||
|
```ts
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { github } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [
|
||||||
|
github({
|
||||||
|
clientId: process.env.GITHUB_CLIENT_ID as string,
|
||||||
|
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Migrate Database
|
||||||
|
better auth comes with a cli tool to migrate your database. You can run the following command to migrate your database.
|
||||||
|
```bash
|
||||||
|
npx better-auth migrate
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Mount Handler
|
||||||
|
After creating an instance, mount the handler on the appropriate server route so it can listen and respond to authentication requests. Ensure this file is placed in your framework's catch-all directory, where the base path is `/api/auth`.
|
||||||
|
|
||||||
|
<Tabs items={["next-js", "svelte-kit", "nuxt", "solid-start", "hono"]} defaultValue="react">
|
||||||
|
<Tab value="next-js">
|
||||||
|
```ts title="api/[...auth]/route.ts"
|
||||||
|
import { auth } from "~/utils/auth.config"
|
||||||
|
import { toNextJSHandler } from "better-auth/next-js"
|
||||||
|
|
||||||
|
export const { GET, POST } = toNextJSHandler(auth)
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Create Client
|
||||||
|
better auth client comes with a support for differnt frameworks including React, Vue, Svelte, Preact, and Solid. You can also use it with vanilla javascript.
|
||||||
|
<Tabs items={["vanilla","react", "vue", "svelte", "solid"]} defaultValue="react">
|
||||||
|
<Tab value="vanilla">
|
||||||
|
```ts
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="react">
|
||||||
|
```ts
|
||||||
|
import { createAuthClient } from "better-auth/react"
|
||||||
|
const client = createAuthClient()
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="vue">
|
||||||
|
```ts
|
||||||
|
import { createAuthClient } from "better-auth/vue"
|
||||||
|
const client = createAuthClient()
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="svelte">
|
||||||
|
```ts
|
||||||
|
import { createAuthClient } from "better-auth/svelte"
|
||||||
|
const client = createAuthClient()
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
<Tab value="solid">
|
||||||
|
```ts
|
||||||
|
import { createAuthClient } from "better-auth/solid"
|
||||||
|
const client = createAuthClient()
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### That's it!
|
||||||
|
🎉 That's it! You're now ready to use better-auth in your application.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
10
docs/content/docs/introduction.mdx
Normal file
10
docs/content/docs/introduction.mdx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Introduction
|
||||||
|
description: Introduction to Better Auth.
|
||||||
|
---
|
||||||
|
|
||||||
|
Better Auth is comprehnsive, batteries included, framework-agnostic and extensible auth library and auth ecosystem for typescript.
|
||||||
|
|
||||||
|
## Why Better Auth?
|
||||||
|
|
||||||
|
Auth libraries in the TypeScript ecosystem typically handle basic authentication but often require significant additional code for any additional functionality. Better Auth addresses this gap by providing a comprehensive set of features out of the box and supporting easy extension through plugins for any additional auth-related requirements. It’s designed to be framework-agnostic, allowing integration with any framework backend or frontend, and it's built with typesaftey in mind.
|
||||||
13
docs/content/docs/meta.json
Normal file
13
docs/content/docs/meta.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"title:": "guide",
|
||||||
|
"root": true,
|
||||||
|
"pages": [
|
||||||
|
"introduction",
|
||||||
|
"installation",
|
||||||
|
"basic-usage",
|
||||||
|
"email-password/sign-in-and-sign-up",
|
||||||
|
"email-password/password-reset",
|
||||||
|
"email-password/configuration",
|
||||||
|
"social-sign-on/apple"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
docs/content/docs/plugins.mdx
Normal file
5
docs/content/docs/plugins.mdx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: Plugins
|
||||||
|
description: Plugins
|
||||||
|
---
|
||||||
|
|
||||||
67
docs/content/docs/providers/apple.mdx
Normal file
67
docs/content/docs/providers/apple.mdx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: Apple
|
||||||
|
description: Apple
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your OAuth credentials
|
||||||
|
To use Apple sign in, you need a client ID and client secret. You can get them from the [Apple Developer Portal](https://developer.apple.com/account/resources/authkeys/list).
|
||||||
|
|
||||||
|
Apple requires a little harder setup to get a client secret. You can use the guide below to get your client secret.
|
||||||
|
<Link href="https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret">
|
||||||
|
Creating a client secret
|
||||||
|
</Link>
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
APPLE_CLIENT_ID: "" as string,
|
||||||
|
APPLE_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { apple } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
apple({ // [!code highlight]
|
||||||
|
clientId: process.env.APPLE_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.APPLE_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Apple
|
||||||
|
To signin with Apple, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `apple`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "apple"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/discord.mdx
Normal file
63
docs/content/docs/providers/discord.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Discord
|
||||||
|
description: Discord Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your Discord credentials
|
||||||
|
To use Discord sign in, you need a client ID and client secret. You can get them from the [Discord Developer Portal](https://discord.com/developers/applications).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
DISCORD_CLIENT_ID: "" as string,
|
||||||
|
DISCORD_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { discord } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
discord({ // [!code highlight]
|
||||||
|
clientId: process.env.DISCORD_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.DISCORD_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Discord
|
||||||
|
To signin with Discord, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `discord`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "discord"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/facebook.mdx
Normal file
63
docs/content/docs/providers/facebook.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Facebook
|
||||||
|
description: Facebook Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your Facebook credentials
|
||||||
|
To use Facebook sign in, you need a client ID and client secret. You can get them from the [Facebook Developer Portal](https://developers.facebook.com/).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
FACEBOOK_CLIENT_ID: "" as string,
|
||||||
|
FACEBOOK_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { facebook } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
facebook({ // [!code highlight]
|
||||||
|
clientId: process.env.FACEBOOK_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Facebook
|
||||||
|
To signin with Facebook, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `facebook`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "facebook"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/github.mdx
Normal file
63
docs/content/docs/providers/github.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Github
|
||||||
|
description: Github Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your Github credentials
|
||||||
|
To use Github sign in, you need a client ID and client secret. You can get them from the [Github Developer Portal](https://github.com/settings/developers).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
GITHUB_CLIENT_ID: "" as string,
|
||||||
|
GITHUB_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { github } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
github({ // [!code highlight]
|
||||||
|
clientId: process.env.GITHUB_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Github
|
||||||
|
To signin with Gitjub, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `github`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "github"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/google.mdx
Normal file
63
docs/content/docs/providers/google.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Google
|
||||||
|
description: Google Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your Google credentials
|
||||||
|
To use Google as a social provider, you need to get your Google credentials. You can get them by creating a new project in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
GOOGLE_CLIENT_ID: "" as string,
|
||||||
|
GOOGLE_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { google } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
google({ // [!code highlight]
|
||||||
|
clientId: process.env.GOOGLE_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Google
|
||||||
|
To signin with Google, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `google`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "google"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/spotify.mdx
Normal file
63
docs/content/docs/providers/spotify.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Spotify
|
||||||
|
description: Spotify Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your Spotify Credentials
|
||||||
|
To use Spotify sign in, you need a client ID and client secret. You can get them from the [Spotify Developer Portal](https://developer.spotify.com/dashboard/applications).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
SPOTIFY_CLIENT_ID: "" as string,
|
||||||
|
SPOTIFY_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { spotify } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
spotify({ // [!code highlight]
|
||||||
|
clientId: process.env.SPOTIFY_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.SPOTIFY_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Spotify
|
||||||
|
To signin with Spotify, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `spotify`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash /
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "spotify"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/twitch.mdx
Normal file
63
docs/content/docs/providers/twitch.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Twitch
|
||||||
|
description: Twitch Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your Twitch Credentials
|
||||||
|
To use Twitch sign in, you need a client ID and client secret. You can get them from the [Twitch Developer Portal](https://dev.twitch.tv/console/apps).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
TWITCH_CLIENT_ID: "" as string,
|
||||||
|
TWITCH_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { twitch } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
socialProvider: [ // [!code highlight]
|
||||||
|
twitch({ // [!code highlight]
|
||||||
|
clientId: process.env.TWITCH_CLIENT_ID as string, // [!code highlight]
|
||||||
|
clientSecret: process.env.TWITCH_CLIENT_SECRET as string, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Twitch
|
||||||
|
To signin with Twitch, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `twitch`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "twitch"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
63
docs/content/docs/providers/twitter.mdx
Normal file
63
docs/content/docs/providers/twitter.mdx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Twitter (X)
|
||||||
|
description: Twitter Provider
|
||||||
|
---
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### Get your X(Twitter) Credentials
|
||||||
|
Get your Twitter credentials from the [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard).
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### Configure the provider
|
||||||
|
To configure the provider, you need to import the provider and pass it to the `socialProvider` option of the auth instance.
|
||||||
|
|
||||||
|
```ts title="auth.ts" twoslash
|
||||||
|
const process = {
|
||||||
|
env: {
|
||||||
|
TWITTER_CLIENT_ID: "" as string,
|
||||||
|
TWITTER_CLIENT_SECRET: "" as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---cut---
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
|
import { twitter } from "better-auth/social-providers"
|
||||||
|
|
||||||
|
export const { api, handler } = await betterAuth({
|
||||||
|
database: {
|
||||||
|
provider: "sqlite",
|
||||||
|
url: "./db.sqlite",
|
||||||
|
},
|
||||||
|
ocialProvider: [ // [!code highlight]
|
||||||
|
twitter({ // [!code highlight]
|
||||||
|
clientId: process.env.TWITTER_CLIENT_ID, // [!code highlight]
|
||||||
|
clientSecret: process.env.TWITTER_CLIENT_SECRET, // [!code highlight]
|
||||||
|
/** // [!code highlight]
|
||||||
|
* this is optional. // [!code highlight]
|
||||||
|
* only needed if you passed a custom basePath from `/api/auth` to `betterAuth` // [!code highlight]
|
||||||
|
* or if you're using a custom domain. // [!code highlight]
|
||||||
|
*/ // [!code highlight]
|
||||||
|
redirectURI: "https://example.com/api/auth/callback/apple", // [!code highlight]
|
||||||
|
}), // [!code highlight]
|
||||||
|
], // [!code highlight]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### Signin with Twitter
|
||||||
|
To signin with Twitter, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
|
||||||
|
- `provider`: The provider to use. It should be set to `twitter`.
|
||||||
|
|
||||||
|
```ts title="client.ts" twoslash
|
||||||
|
import { createAuthClient } from "better-auth/client"
|
||||||
|
const client = createAuthClient()
|
||||||
|
|
||||||
|
const signin = async () => {
|
||||||
|
const data = await client.signIn.social({
|
||||||
|
provider: "twitter"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
17
docs/content/docs/test.mdx
Normal file
17
docs/content/docs/test.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: Components
|
||||||
|
description: Components
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Block
|
||||||
|
|
||||||
|
```js
|
||||||
|
console.log('Hello World');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cards
|
||||||
|
|
||||||
|
<Cards>
|
||||||
|
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
|
||||||
|
<Card title="Learn more about Fumadocs" href="https://fumadocs.vercel.app" />
|
||||||
|
</Cards>
|
||||||
194
docs/hooks/use-toast.ts
Normal file
194
docs/hooks/use-toast.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
// Inspired by react-hot-toast library
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ToastActionElement,
|
||||||
|
ToastProps,
|
||||||
|
} from "@/components/ui/toast"
|
||||||
|
|
||||||
|
const TOAST_LIMIT = 1
|
||||||
|
const TOAST_REMOVE_DELAY = 1000000
|
||||||
|
|
||||||
|
type ToasterToast = ToastProps & {
|
||||||
|
id: string
|
||||||
|
title?: React.ReactNode
|
||||||
|
description?: React.ReactNode
|
||||||
|
action?: ToastActionElement
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionTypes = {
|
||||||
|
ADD_TOAST: "ADD_TOAST",
|
||||||
|
UPDATE_TOAST: "UPDATE_TOAST",
|
||||||
|
DISMISS_TOAST: "DISMISS_TOAST",
|
||||||
|
REMOVE_TOAST: "REMOVE_TOAST",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
function genId() {
|
||||||
|
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
||||||
|
return count.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionType = typeof actionTypes
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| {
|
||||||
|
type: ActionType["ADD_TOAST"]
|
||||||
|
toast: ToasterToast
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType["UPDATE_TOAST"]
|
||||||
|
toast: Partial<ToasterToast>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType["DISMISS_TOAST"]
|
||||||
|
toastId?: ToasterToast["id"]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType["REMOVE_TOAST"]
|
||||||
|
toastId?: ToasterToast["id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
toasts: ToasterToast[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||||
|
|
||||||
|
const addToRemoveQueue = (toastId: string) => {
|
||||||
|
if (toastTimeouts.has(toastId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
toastTimeouts.delete(toastId)
|
||||||
|
dispatch({
|
||||||
|
type: "REMOVE_TOAST",
|
||||||
|
toastId: toastId,
|
||||||
|
})
|
||||||
|
}, TOAST_REMOVE_DELAY)
|
||||||
|
|
||||||
|
toastTimeouts.set(toastId, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reducer = (state: State, action: Action): State => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "ADD_TOAST":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||||
|
}
|
||||||
|
|
||||||
|
case "UPDATE_TOAST":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
toasts: state.toasts.map((t) =>
|
||||||
|
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
case "DISMISS_TOAST": {
|
||||||
|
const { toastId } = action
|
||||||
|
|
||||||
|
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||||
|
// but I'll keep it here for simplicity
|
||||||
|
if (toastId) {
|
||||||
|
addToRemoveQueue(toastId)
|
||||||
|
} else {
|
||||||
|
state.toasts.forEach((toast) => {
|
||||||
|
addToRemoveQueue(toast.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
toasts: state.toasts.map((t) =>
|
||||||
|
t.id === toastId || toastId === undefined
|
||||||
|
? {
|
||||||
|
...t,
|
||||||
|
open: false,
|
||||||
|
}
|
||||||
|
: t
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "REMOVE_TOAST":
|
||||||
|
if (action.toastId === undefined) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
toasts: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners: Array<(state: State) => void> = []
|
||||||
|
|
||||||
|
let memoryState: State = { toasts: [] }
|
||||||
|
|
||||||
|
function dispatch(action: Action) {
|
||||||
|
memoryState = reducer(memoryState, action)
|
||||||
|
listeners.forEach((listener) => {
|
||||||
|
listener(memoryState)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Toast = Omit<ToasterToast, "id">
|
||||||
|
|
||||||
|
function toast({ ...props }: Toast) {
|
||||||
|
const id = genId()
|
||||||
|
|
||||||
|
const update = (props: ToasterToast) =>
|
||||||
|
dispatch({
|
||||||
|
type: "UPDATE_TOAST",
|
||||||
|
toast: { ...props, id },
|
||||||
|
})
|
||||||
|
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "ADD_TOAST",
|
||||||
|
toast: {
|
||||||
|
...props,
|
||||||
|
id,
|
||||||
|
open: true,
|
||||||
|
onOpenChange: (open) => {
|
||||||
|
if (!open) dismiss()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
dismiss,
|
||||||
|
update,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function useToast() {
|
||||||
|
const [state, setState] = React.useState<State>(memoryState)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
listeners.push(setState)
|
||||||
|
return () => {
|
||||||
|
const index = listeners.indexOf(setState)
|
||||||
|
if (index > -1) {
|
||||||
|
listeners.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [state])
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
toast,
|
||||||
|
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useToast, toast }
|
||||||
6
docs/lib/utils.ts
Normal file
6
docs/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
30
docs/mdx-components.tsx
Normal file
30
docs/mdx-components.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { MDXComponents } from 'mdx/types';
|
||||||
|
import defaultComponents from 'fumadocs-ui/mdx';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { cn } from './lib/utils';
|
||||||
|
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||||
|
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||||
|
import { GenerateSecret } from './components/generate-secret';
|
||||||
|
import { Popup, PopupContent, PopupTrigger } from "fumadocs-ui/twoslash/popup";
|
||||||
|
import { TypeTable } from 'fumadocs-ui/components/type-table';
|
||||||
|
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||||
|
return {
|
||||||
|
...defaultComponents,
|
||||||
|
...components,
|
||||||
|
Link: ({ className, ...props }: React.ComponentProps<typeof Link>) => (
|
||||||
|
<Link
|
||||||
|
className={cn("font-medium underline underline-offset-4", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
Step,
|
||||||
|
Steps,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
GenerateSecret,
|
||||||
|
Popup,
|
||||||
|
PopupTrigger,
|
||||||
|
PopupContent,
|
||||||
|
TypeTable,
|
||||||
|
};
|
||||||
|
}
|
||||||
48
docs/next.config.mjs
Normal file
48
docs/next.config.mjs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import createMDX from 'fumadocs-mdx/config';
|
||||||
|
import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
|
||||||
|
import { remarkInstall } from "fumadocs-docgen";
|
||||||
|
import { transformerTwoslash } from "fumadocs-twoslash"
|
||||||
|
import { getParsedCommandLineOfConfigFile, ModuleResolutionKind } from 'typescript';
|
||||||
|
|
||||||
|
const withMDX = createMDX({
|
||||||
|
mdxOptions: {
|
||||||
|
rehypeCodeOptions: {
|
||||||
|
transformers: [
|
||||||
|
...rehypeCodeDefaultOptions.transformers,
|
||||||
|
transformerTwoslash({
|
||||||
|
twoslashOptions: {
|
||||||
|
compilerOptions: {
|
||||||
|
moduleResolution: ModuleResolutionKind.Bundler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
remarkPlugins: [
|
||||||
|
[
|
||||||
|
remarkInstall,
|
||||||
|
{
|
||||||
|
persist: {
|
||||||
|
id: "persist-install",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const config = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
redirects() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/docs",
|
||||||
|
destination: "/docs/introduction",
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withMDX(config);
|
||||||
78
docs/package.json
Normal file
78
docs/package.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"name": "docs",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build",
|
||||||
|
"dev": "next dev",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^3.9.0",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||||
|
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
||||||
|
"@radix-ui/react-avatar": "^1.1.0",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.1",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.0",
|
||||||
|
"@radix-ui/react-context-menu": "^2.2.1",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
|
"@radix-ui/react-hover-card": "^1.1.1",
|
||||||
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
|
"@radix-ui/react-menubar": "^1.1.1",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||||
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
|
"@radix-ui/react-radio-group": "^1.2.0",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||||
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
|
"@radix-ui/react-slider": "^1.2.0",
|
||||||
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
|
"@radix-ui/react-switch": "^1.1.0",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.0",
|
||||||
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
|
"better-auth": "workspace:0.0.2-beta.8",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "1.0.0",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
|
"embla-carousel-react": "^8.2.0",
|
||||||
|
"fumadocs-core": "13.4.1",
|
||||||
|
"fumadocs-docgen": "^1.1.0",
|
||||||
|
"fumadocs-mdx": "9.0.4",
|
||||||
|
"fumadocs-twoslash": "^1.1.2",
|
||||||
|
"fumadocs-ui": "13.4.1",
|
||||||
|
"input-otp": "^1.2.4",
|
||||||
|
"lucide-react": "^0.435.0",
|
||||||
|
"next": "^14.2.5",
|
||||||
|
"next-themes": "^0.3.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-day-picker": "8.10.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-hook-form": "^7.52.2",
|
||||||
|
"react-resizable-panels": "^2.1.2",
|
||||||
|
"recharts": "^2.12.7",
|
||||||
|
"sonner": "^1.5.0",
|
||||||
|
"tailwind-merge": "^2.5.0",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"vaul": "^0.9.1",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/mdx": "^2.0.13",
|
||||||
|
"@types/node": "22.3.0",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.41",
|
||||||
|
"tailwindcss": "^3.4.10",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
docs/postcss.config.js
Normal file
6
docs/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
8
docs/public/logo.svg
Normal file
8
docs/public/logo.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="200" height="200" fill="#D9D9D9"/>
|
||||||
|
<line x1="21.5" y1="2.18557e-08" x2="21.5" y2="200" stroke="#C4C4C4"/>
|
||||||
|
<line x1="173.5" y1="2.18557e-08" x2="173.5" y2="200" stroke="#C4C4C4"/>
|
||||||
|
<line x1="200" y1="176.5" x2="-4.37114e-08" y2="176.5" stroke="#C4C4C4"/>
|
||||||
|
<line x1="200" y1="24.5" x2="-4.37114e-08" y2="24.5" stroke="#C4C4C4"/>
|
||||||
|
<path d="M64.4545 135V65.1818H88.8636C93.7273 65.1818 97.7386 66.0227 100.898 67.7045C104.057 69.3636 106.409 71.6023 107.955 74.4205C109.5 77.2159 110.273 80.3182 110.273 83.7273C110.273 86.7273 109.739 89.2045 108.67 91.1591C107.625 93.1136 106.239 94.6591 104.511 95.7955C102.807 96.9318 100.955 97.7727 98.9545 98.3182V99C101.091 99.1364 103.239 99.8864 105.398 101.25C107.557 102.614 109.364 104.568 110.818 107.114C112.273 109.659 113 112.773 113 116.455C113 119.955 112.205 123.102 110.614 125.898C109.023 128.693 106.511 130.909 103.08 132.545C99.6477 134.182 95.1818 135 89.6818 135H64.4545ZM72.9091 127.5H89.6818C95.2045 127.5 99.125 126.432 101.443 124.295C103.784 122.136 104.955 119.523 104.955 116.455C104.955 114.091 104.352 111.909 103.148 109.909C101.943 107.886 100.227 106.273 98 105.068C95.7727 103.841 93.1364 103.227 90.0909 103.227H72.9091V127.5ZM72.9091 95.8636H88.5909C91.1364 95.8636 93.4318 95.3636 95.4773 94.3636C97.5455 93.3636 99.1818 91.9545 100.386 90.1364C101.614 88.3182 102.227 86.1818 102.227 83.7273C102.227 80.6591 101.159 78.0568 99.0227 75.9205C96.8864 73.7614 93.5 72.6818 88.8636 72.6818H72.9091V95.8636ZM131.665 135.545C129.983 135.545 128.54 134.943 127.335 133.739C126.131 132.534 125.528 131.091 125.528 129.409C125.528 127.727 126.131 126.284 127.335 125.08C128.54 123.875 129.983 123.273 131.665 123.273C133.347 123.273 134.79 123.875 135.994 125.08C137.199 126.284 137.801 127.727 137.801 129.409C137.801 130.523 137.517 131.545 136.949 132.477C136.403 133.409 135.665 134.159 134.733 134.727C133.824 135.273 132.801 135.545 131.665 135.545Z" fill="#302208"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
88
docs/tailwind.config.js
Normal file
88
docs/tailwind.config.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { createPreset } from 'fumadocs-ui/tailwind-plugin';
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
darkMode: ['class'],
|
||||||
|
content: [
|
||||||
|
'./components/**/*.{ts,tsx}',
|
||||||
|
'./app/**/*.{ts,tsx}',
|
||||||
|
'./content/**/*.{md,mdx}',
|
||||||
|
'./mdx-components.{ts,tsx}',
|
||||||
|
'./node_modules/fumadocs-ui/dist/**/*.js',
|
||||||
|
],
|
||||||
|
presets: [createPreset()],
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: 'hsl(var(--background))',
|
||||||
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
|
},
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
'accordion-down': {
|
||||||
|
from: {
|
||||||
|
height: '0'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
height: 'var(--radix-accordion-content-height)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'accordion-up': {
|
||||||
|
from: {
|
||||||
|
height: 'var(--radix-accordion-content-height)'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
height: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
|
'accordion-up': 'accordion-up 0.2s ease-out'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
28
docs/tsconfig.json
Normal file
28
docs/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"target": "ESNext",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "Preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@simplewebauthn/browser": "^10.0.0",
|
"@simplewebauthn/browser": "^10.0.0",
|
||||||
"@simplewebauthn/server": "^10.0.1",
|
"@simplewebauthn/server": "^10.0.1",
|
||||||
"arctic": "^1.9.2",
|
"arctic": "2.0.0-next.5",
|
||||||
"better-call": "^0.1.38",
|
"better-call": "^0.1.38",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
"mysql2": "^3.11.0",
|
"mysql2": "^3.11.0",
|
||||||
"nanostores": "^0.11.2",
|
"nanostores": "^0.11.2",
|
||||||
|
"oauth4webapi": "^2.12.0",
|
||||||
"ora": "^8.0.1",
|
"ora": "^8.0.1",
|
||||||
"oslo": "^1.2.1",
|
"oslo": "^1.2.1",
|
||||||
"pg": "^8.12.0",
|
"pg": "^8.12.0",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const callbackOAuth = createAuthEndpoint(
|
|||||||
);
|
);
|
||||||
throw new APIError("NOT_FOUND");
|
throw new APIError("NOT_FOUND");
|
||||||
}
|
}
|
||||||
const tokens = await provider.provider.validateAuthorizationCode(
|
const tokens = await provider.validateAuthorizationCode(
|
||||||
c.query.code,
|
c.query.code,
|
||||||
c.query.code_verifier || "",
|
c.query.code_verifier || "",
|
||||||
);
|
);
|
||||||
@@ -37,7 +37,7 @@ export const callbackOAuth = createAuthEndpoint(
|
|||||||
c.context.logger.error("Code verification failed");
|
c.context.logger.error("Code verification failed");
|
||||||
throw new APIError("UNAUTHORIZED");
|
throw new APIError("UNAUTHORIZED");
|
||||||
}
|
}
|
||||||
const user = await provider.userInfo.getUserInfo(tokens);
|
const user = await provider.getUserInfo(tokens).then((res) => res?.user);
|
||||||
const id = generateId();
|
const id = generateId();
|
||||||
const data = userSchema.safeParse({
|
const data = userSchema.safeParse({
|
||||||
...user,
|
...user,
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ export const signInOAuth = createAuthEndpoint(
|
|||||||
c.context.secret,
|
c.context.secret,
|
||||||
cookie.pkCodeVerifier.options,
|
cookie.pkCodeVerifier.options,
|
||||||
);
|
);
|
||||||
const url = await provider.provider.createAuthorizationURL(
|
const url = provider.createAuthorizationURL({
|
||||||
state.state,
|
state: state.state,
|
||||||
codeVerifier,
|
codeVerifier,
|
||||||
);
|
});
|
||||||
return {
|
return {
|
||||||
url: url.toString(),
|
url: url.toString(),
|
||||||
state: state.state,
|
state: state.state,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const signUpEmail = createAuthEndpoint(
|
|||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
image: z.string().optional(),
|
image: z.string().optional(),
|
||||||
callbackUrl: z.string().optional(),
|
callbackURL: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
@@ -83,9 +83,9 @@ export const signUpEmail = createAuthEndpoint(
|
|||||||
session,
|
session,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: ctx.body.callbackUrl
|
body: ctx.body.callbackURL
|
||||||
? {
|
? {
|
||||||
url: ctx.body.callbackUrl,
|
url: ctx.body.callbackURL,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user