├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── apps
└── web
│ ├── app
│ ├── (home)
│ │ ├── blocks
│ │ │ ├── [...categories]
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── api
│ │ ├── og
│ │ │ └── route.tsx
│ │ └── search
│ │ │ └── route.ts
│ ├── blocks
│ │ └── preview
│ │ │ └── [blockId]
│ │ │ └── page.tsx
│ ├── docs
│ │ ├── [[...slug]]
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── favicon.ico
│ ├── layout.config.tsx
│ ├── layout.tsx
│ ├── opengraph-image.png
│ └── sitemap.ts
│ ├── components
│ ├── announcement.tsx
│ ├── block-display.tsx
│ ├── block-viewer.tsx
│ ├── blocks-nav.tsx
│ ├── cli-commands.tsx
│ ├── code-block.tsx
│ ├── content
│ │ ├── component-loader.tsx
│ │ ├── component-preview-collapse.tsx
│ │ ├── component-preview.tsx
│ │ ├── component-source.tsx
│ │ └── mdx-components.tsx
│ ├── copy-button.tsx
│ ├── hero.tsx
│ ├── showcase-code-viewer.tsx
│ ├── showcase-component.tsx
│ └── v0-button.tsx
│ ├── config
│ └── site.ts
│ ├── content
│ ├── blog
│ │ └── web-interface-guide.mdx
│ └── docs
│ │ ├── components
│ │ └── button.mdx
│ │ ├── index.mdx
│ │ ├── installation.mdx
│ │ ├── meta.json
│ │ ├── props.ts
│ │ └── texts
│ │ ├── text-loop.mdx
│ │ └── text-type.mdx
│ ├── hooks
│ ├── use-config.ts
│ ├── use-copy-to-clipboard.ts
│ └── use-media-query.ts
│ ├── lib
│ ├── blocks.ts
│ ├── fonts.ts
│ ├── metadata.ts
│ ├── registry.ts
│ ├── source.ts
│ └── utils.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── banner.png
│ ├── fonts
│ │ ├── Geist-Medium.otf
│ │ └── Redaction_35-Regular.woff2
│ ├── images
│ │ ├── 10x-deng.png
│ │ ├── brush-line.png
│ │ ├── currents-evangelion.jpg
│ │ └── lines-top.svg
│ ├── next.svg
│ ├── r
│ │ ├── action-search-input-demo.json
│ │ ├── button-color.json
│ │ ├── button-destructive.json
│ │ ├── button-outline.json
│ │ ├── button-primary.json
│ │ ├── button.json
│ │ ├── card.json
│ │ ├── checkbox.json
│ │ ├── family-wallets-demo.json
│ │ ├── icons.json
│ │ ├── input.json
│ │ ├── label.json
│ │ ├── login-03.json
│ │ ├── login-04.json
│ │ ├── login-05.json
│ │ ├── paper-fold-demo.json
│ │ ├── separator.json
│ │ ├── sheet.json
│ │ ├── sidebar.json
│ │ ├── skeleton.json
│ │ ├── stack.json
│ │ ├── text-loop-demo.json
│ │ ├── text-loop.json
│ │ ├── text-type-demo.json
│ │ ├── text-type.json
│ │ ├── tooltip.json
│ │ ├── use-debounce.json
│ │ ├── use-mobile.json
│ │ └── utils.json
│ └── vercel.svg
│ ├── registry.json
│ ├── registry
│ ├── blocks
│ │ ├── login-03
│ │ │ ├── components
│ │ │ │ └── login-form.tsx
│ │ │ └── page.tsx
│ │ ├── login-04
│ │ │ ├── components
│ │ │ │ └── login-form.tsx
│ │ │ └── page.tsx
│ │ └── login-05
│ │ │ ├── components
│ │ │ └── login-form.tsx
│ │ │ └── page.tsx
│ ├── components
│ │ ├── action-search-input-demo.tsx
│ │ ├── button-color.tsx
│ │ ├── button-destructive.tsx
│ │ ├── button-outline.tsx
│ │ ├── button-primary.tsx
│ │ ├── family-wallets-demo.tsx
│ │ ├── icons.tsx
│ │ ├── paper-fold-demo.tsx
│ │ ├── text-loop-demo.tsx
│ │ └── text-type-demo.tsx
│ ├── hooks
│ │ ├── use-debounce.ts
│ │ └── use-mobile.ts
│ ├── lib
│ │ └── utils.ts
│ └── ui
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── drawer.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── sidebar.tsx
│ │ ├── skeleton.tsx
│ │ ├── stack.tsx
│ │ ├── text-effect.tsx
│ │ ├── text-loop.tsx
│ │ ├── text-morph.tsx
│ │ ├── text-type.tsx
│ │ └── tooltip.tsx
│ ├── source.config.ts
│ ├── styles
│ └── globals.css
│ ├── tsconfig.json
│ └── types
│ └── component.ts
├── biome.json
├── bun.lockb
├── package.json
├── packages
├── tsconfig
│ ├── base.json
│ ├── nextjs.json
│ ├── package.json
│ └── react-library.json
└── ui
│ ├── package.json
│ ├── postcss.config.js
│ ├── src
│ ├── components
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── glass-button.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── page-header.tsx
│ │ ├── references.tsx
│ │ ├── resizable.tsx
│ │ ├── scroll-area.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── sidebar.tsx
│ │ ├── skeleton.tsx
│ │ ├── tabs.tsx
│ │ ├── toggle-group.tsx
│ │ ├── toggle.tsx
│ │ └── tooltip.tsx
│ ├── hooks
│ │ └── use-mobile.ts
│ ├── index.tsx
│ └── lib
│ │ └── utils.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── tsconfig.json
└── turbo.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | /.pnp
6 | .pnpm/
7 | .pnp.js
8 |
9 | ##output directories
10 | .map.ts
11 | dist
12 | .source
13 |
14 |
15 | # testing
16 | /coverage
17 |
18 | # next.js
19 | /.next/
20 | /out/
21 |
22 | # production
23 | build
24 |
25 | # misc
26 | .DS_Store
27 | *.pem
28 |
29 | # debug
30 | npm-debug.log*
31 | yarn-debug.log*
32 | yarn-error.log*
33 |
34 | # local env files
35 | .env
36 | .env*.local
37 |
38 | # vercel
39 | .vercel
40 |
41 | # typescriptuild
42 | *.tsbuildinfo
43 | next-env.d.ts
44 |
45 | ## nx
46 | .nx
47 | .nx/cache
48 | .nx/workspace-data
49 |
50 | # Next.js
51 | .next
52 | out
53 |
54 | # Turbo
55 | .turbo
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Nur Cahya Wibawa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UI/TOPIA
2 |
3 | Welcome to **UI/TOPIA**—a curated collection of fine UI components built with **[Motion](https://motion.dev/)** and **Tailwind CSS**.
4 | This project extend showcases experimental designs from [cahyawibawa.com](https://cahyawibawa.com).
5 |
6 |
7 | ## Documentation
8 |
9 | Explore the full documentation at [uitopia.xyz/docs](http://uitopia.xyz/docs) to get started.
10 |
11 |
12 | ## Contributing
13 |
14 | Contributions are always welcome!
15 | Whether you’re improving existing components or adding something new, we’d love to have your input.
16 | Check out our [Contributing Guide](/CONTRIBUTING.md) for details—it’s a simple process and a great way to share your creativity with the community.
17 |
18 |
19 | ## License
20 |
21 | This project is licensed under the [MIT License](/LICENSE).
22 |
23 |
24 | ## Stats
25 |
26 | 
27 |
--------------------------------------------------------------------------------
/apps/web/app/(home)/blocks/[...categories]/page.tsx:
--------------------------------------------------------------------------------
1 | import { BlockDisplay } from "@/components/block-display";
2 | import { getAllBlockIds, getBlockCategories } from "@/lib/blocks";
3 |
4 | export const dynamicParams = false;
5 |
6 | export async function generateStaticParams() {
7 | const categories = await getBlockCategories();
8 | return categories.map((category) => ({
9 | categories: [category.slug],
10 | }));
11 | }
12 |
13 | type Params = Promise<{ categories?: string[] }>;
14 | type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>;
15 |
16 | export default async function Page(props: {
17 | params: Params;
18 | searchParams: SearchParams;
19 | }) {
20 | const params = await props.params;
21 | const _searchParams = await props.searchParams;
22 |
23 | const blocks = await getAllBlockIds(
24 | ["registry:block"],
25 | params.categories ?? [],
26 | );
27 |
28 | return (
29 |
30 | {blocks.map((name) => (
31 |
32 |
33 |
34 | ))}
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/apps/web/app/(home)/blocks/layout.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { BlocksNav } from "@/components/blocks-nav";
3 | import { createMetadata } from "@/lib/metadata";
4 | import { cn } from "@/lib/utils";
5 | import { buttonVariants } from "@/registry/ui/button";
6 | import registry from "@/registry.json";
7 |
8 | import {
9 | PageActions,
10 | PageHeader,
11 | PageHeaderDescription,
12 | PageHeaderHeading,
13 | } from "@/uitopia/page-header";
14 |
15 | export const metadata = createMetadata({
16 | title: "Building Blocks for the Web",
17 | description:
18 | "Clean, modern building blocks. Copy and paste into your apps. Works with all React frameworks. Open Source. Free forever.",
19 | });
20 |
21 | // Extract unique categories from registry blocks only
22 | const categories = Array.from(
23 | new Set(
24 | registry.items
25 | .filter((item) => item.type === "registry:block")
26 | .flatMap((item) => item.categories || [])
27 | .filter(Boolean),
28 | ),
29 | ).map((category) => ({
30 | name: category.charAt(0).toUpperCase() + category.slice(1),
31 | slug: category.toLowerCase(),
32 | hidden: false,
33 | }));
34 |
35 | interface BlocksLayoutProps {
36 | children: React.ReactNode;
37 | }
38 |
39 | export default function BlocksLayout({ children }: BlocksLayoutProps) {
40 | return (
41 |
42 |
43 |
44 | Building blocks for the web
45 |
46 |
47 | Clean, modern building blocks. Copy and paste into your apps. Works
48 | with all React frameworks. Open Source. Free forever.
49 |
50 |
51 |
52 |
59 | Browse blocks
60 |
61 |
62 |
67 |
{children}
68 |
69 |
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/apps/web/app/(home)/blocks/page.tsx:
--------------------------------------------------------------------------------
1 | import { BlockDisplay } from "@/components/block-display";
2 |
3 | const FEATURED_BLOCKS = ["login-03", "login-05"];
4 |
5 | export default async function BlocksPage() {
6 | return (
7 |
8 | {FEATURED_BLOCKS.map((block) => (
9 |
13 |
14 |
15 | ))}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/apps/web/app/(home)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { HomeLayout } from "fumadocs-ui/layouts/home";
2 | import type { ReactNode } from "react";
3 | import { baseOptions } from "@/app/layout.config";
4 | import { siteConfig } from "@/config/site";
5 |
6 | export default function Layout({
7 | children,
8 | }: {
9 | children: ReactNode;
10 | }): React.ReactElement {
11 | return {children} ;
12 | }
13 |
14 | function _Footer(): React.ReactElement {
15 | const currentYear = new Date().getFullYear();
16 | return (
17 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/apps/web/app/(home)/page.tsx:
--------------------------------------------------------------------------------
1 | import { Hero } from "@/components/hero";
2 | import { ShowcaseComponent } from "@/components/showcase-component";
3 |
4 | export default function Home() {
5 | return (
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/app/api/og/route.tsx:
--------------------------------------------------------------------------------
1 | import { readFileSync } from "fs";
2 | import { ImageResponse } from "next/og";
3 | import type { NextRequest } from "next/server";
4 | import { join } from "path";
5 | import { Icons } from "@/registry/components/icons";
6 |
7 | export async function GET(req: NextRequest) {
8 | try {
9 | const { searchParams } = req.nextUrl;
10 | const title = searchParams.get("title");
11 | const description = searchParams.get("description");
12 |
13 | const fontPath = join(process.cwd(), "public", "fonts", "Geist-Medium.otf");
14 | const fontData = readFileSync(fontPath);
15 |
16 | return new ImageResponse(
17 |
30 |
39 |
40 |
46 | ui/topia
47 |
48 |
49 |
62 |
{title}
63 |
64 |
{description}
65 |
66 |
,
67 | {
68 | width: 1200,
69 | height: 630,
70 | fonts: [
71 | {
72 | name: "Geist",
73 | data: fontData,
74 | style: "normal",
75 | },
76 | ],
77 | },
78 | );
79 | } catch (error) {
80 | console.error("Failed to generate the image:", error);
81 | return new Response(`Failed to generate the image: ${error}`, {
82 | status: 500,
83 | });
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/apps/web/app/api/search/route.ts:
--------------------------------------------------------------------------------
1 | import { createFromSource } from "fumadocs-core/search/server";
2 | import { source } from "@/lib/source";
3 |
4 | export const { GET } = createFromSource(source);
5 |
--------------------------------------------------------------------------------
/apps/web/app/blocks/preview/[blockId]/page.tsx:
--------------------------------------------------------------------------------
1 | import { BlockDisplay } from "@/components/block-display";
2 |
3 | export const dynamic = "force-static";
4 | export const dynamicParams = false;
5 |
6 | type Params = Promise<{ blockId: string }>;
7 | type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>;
8 |
9 | export default async function Page(props: {
10 | params: Params;
11 | searchParams: SearchParams;
12 | }) {
13 | const params = await props.params;
14 | const _searchParams = await props.searchParams;
15 |
16 | return (
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/apps/web/app/docs/[[...slug]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { getPageTreePeers } from "fumadocs-core/server";
2 | import { Card, Cards } from "fumadocs-ui/components/card";
3 | import {
4 | DocsBody,
5 | DocsDescription,
6 | DocsPage,
7 | DocsTitle,
8 | } from "fumadocs-ui/page";
9 | import type { Metadata } from "next";
10 | import { notFound } from "next/navigation";
11 | import { useMDXComponents } from "@/components/content/mdx-components";
12 | import { siteConfig } from "@/config/site";
13 | import { createMetadata } from "@/lib/metadata";
14 | import { source } from "@/lib/source";
15 |
16 | export default async function Page(props: {
17 | params: Promise<{ slug?: string[] }>;
18 | }) {
19 | const params = await props.params;
20 | const page = source.getPage(params.slug);
21 |
22 | if (!page) notFound();
23 |
24 | const path = `apps/web/content/docs/${page.file.path}`;
25 | const MDX = page.data.body;
26 |
27 | return (
28 |
42 | {page.data.title}
43 |
44 | {page.data.description}
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | function _DocsCategory({ url }: { url: string }) {
54 | return (
55 |
56 | {getPageTreePeers(source.pageTree, url).map((peer) => (
57 |
58 | {peer.description}
59 |
60 | ))}
61 |
62 | );
63 | }
64 |
65 | export async function generateStaticParams() {
66 | return source.generateParams();
67 | }
68 |
69 | export async function generateMetadata(props: {
70 | params: Promise<{ slug?: string[] }>;
71 | }): Promise {
72 | const params = await props.params;
73 | const page = source.getPage(params.slug);
74 |
75 | if (page == null) notFound();
76 |
77 | const description = page.data.description ?? siteConfig.description;
78 |
79 | const imageParams = new URLSearchParams();
80 | imageParams.set("description", description);
81 |
82 | const image = {
83 | alt: "Banner",
84 | url: `/api/og?${imageParams.toString()}`,
85 | width: 1200,
86 | height: 630,
87 | };
88 |
89 | return createMetadata({
90 | title: page.data.title,
91 | description,
92 | openGraph: {
93 | url: `/docs/${page.slugs.join("/")}`,
94 | images: image,
95 | },
96 | twitter: {
97 | images: image,
98 | },
99 | });
100 | }
101 |
--------------------------------------------------------------------------------
/apps/web/app/docs/layout.tsx:
--------------------------------------------------------------------------------
1 | import { GithubInfo } from "fumadocs-ui/components/github-info";
2 | import { DocsLayout, type DocsLayoutProps } from "fumadocs-ui/layouts/docs";
3 | import type { ReactNode } from "react";
4 | import { baseOptions } from "@/app/layout.config";
5 | import { source } from "@/lib/source";
6 |
7 | const docsOptions: DocsLayoutProps = {
8 | ...baseOptions,
9 | tree: source.pageTree,
10 | links: [
11 | {
12 | type: "custom",
13 | children: ,
14 | },
15 | ],
16 | };
17 |
18 | export default function Layout({ children }: { children: ReactNode }) {
19 | return (
20 |
38 | {node.icon}
39 |
40 | ),
41 | };
42 | },
43 | },
44 | }}
45 | {...docsOptions}
46 | >
47 | {children}
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/apps/web/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/app/favicon.ico
--------------------------------------------------------------------------------
/apps/web/app/layout.config.tsx:
--------------------------------------------------------------------------------
1 | import type { LinkItemType } from "fumadocs-ui/layouts/docs";
2 | import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
3 | import { siteConfig } from "@/config/site";
4 | import { Icons } from "@/registry/components/icons";
5 |
6 | export const linkItems: LinkItemType[] = [
7 | {
8 | text: "Docs",
9 | icon: ,
10 | url: "/docs",
11 | },
12 | {
13 | text: "Blocks",
14 | icon: ,
15 | url: "/blocks",
16 | },
17 | {
18 | type: "icon",
19 | url: siteConfig.links.github,
20 | text: "GitHub",
21 | icon: (
22 |
23 |
24 |
25 | ),
26 | external: true,
27 | },
28 | ];
29 |
30 | export const baseOptions: BaseLayoutProps = {
31 | nav: {
32 | transparentMode: "top",
33 | title: (
34 |
35 |
36 |
37 | {siteConfig.name}
38 |
39 |
40 | beta
41 |
42 |
43 | ),
44 | },
45 | links: [...linkItems],
46 | };
47 |
--------------------------------------------------------------------------------
/apps/web/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from "@vercel/analytics/react";
2 | import { RootProvider } from "fumadocs-ui/provider";
3 | import type { Viewport } from "next";
4 | import { geistMono, geistSans, Redaction } from "@/lib/fonts";
5 | import { createMetadata } from "@/lib/metadata";
6 | import "styles/globals.css";
7 |
8 | export const metadata = createMetadata({});
9 |
10 | export const viewport: Viewport = {
11 | colorScheme: "dark light",
12 | themeColor: [
13 | { media: "(prefers-color-scheme: light)", color: "white" },
14 | { media: "(prefers-color-scheme: dark)", color: "black" },
15 | ],
16 | };
17 |
18 | interface RootLayoutProps {
19 | children: React.ReactNode;
20 | }
21 | export default function RootLayout({ children }: RootLayoutProps) {
22 | return (
23 |
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/apps/web/app/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/app/opengraph-image.png
--------------------------------------------------------------------------------
/apps/web/app/sitemap.ts:
--------------------------------------------------------------------------------
1 | import type { MetadataRoute } from "next";
2 | import { source } from "@/lib/source";
3 |
4 | export const revalidate = false;
5 |
6 | export default function sitemap(): MetadataRoute.Sitemap {
7 | const url = (path: string): string =>
8 | new URL(path, process.env.NEXT_PUBLIC_APP_URL).toString();
9 |
10 | return [
11 | {
12 | url: url("/"),
13 | changeFrequency: "monthly",
14 | priority: 1,
15 | },
16 | {
17 | url: url("/docs"),
18 | changeFrequency: "monthly",
19 | priority: 0.8,
20 | },
21 | ...source.getPages().map((page) => ({
22 | url: url(page.url),
23 | lastModified: page.data.lastModified
24 | ? new Date(page.data.lastModified)
25 | : undefined,
26 | changeFrequency: "weekly",
27 | priority: 0.5,
28 | })),
29 | ];
30 | }
31 |
--------------------------------------------------------------------------------
/apps/web/components/announcement.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowRight, Blocks } from "lucide-react";
2 | import Link from "next/link";
3 |
4 | export function Announcement() {
5 | return (
6 |
10 |
11 |
12 | New blocks components
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/components/block-display.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { BlockViewer } from "@/components/block-viewer";
3 | import { getComponentsByName } from "@/lib/registry";
4 |
5 | const LoadingFallback = () => (
6 |
7 |
8 | Loading component...
9 |
10 |
11 | );
12 |
13 | export async function BlockDisplay({ name }: { name: string }) {
14 | const item = await getCachedComponentItem(name);
15 |
16 | if (!item) {
17 | return (
18 |
19 |
20 | No component found with name: {name}
21 |
22 |
23 | );
24 | }
25 |
26 | // Use the meta data from the registry for height
27 | const meta = { iframeHeight: item.meta?.iframeHeight || "350px" };
28 |
29 | return (
30 |
31 | }>
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | const getCachedComponentItem = React.cache(async (name: string) => {
39 | return getComponentsByName(name);
40 | });
41 |
--------------------------------------------------------------------------------
/apps/web/components/blocks-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { usePathname } from "next/navigation";
5 | import { ScrollArea, ScrollBar } from "@/uitopia/scroll-area";
6 |
7 | interface Category {
8 | name: string;
9 | slug: string;
10 | hidden?: boolean;
11 | }
12 |
13 | interface BlocksNavProps {
14 | categories: Category[];
15 | }
16 |
17 | export function BlocksNav({ categories }: BlocksNavProps) {
18 | const pathname = usePathname();
19 |
20 | return (
21 |
22 |
23 |
24 |
28 | {categories.map((category) => (
29 |
34 | ))}
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | function BlocksNavLink({
43 | category,
44 | isActive,
45 | }: {
46 | category: Category;
47 | isActive: boolean;
48 | }) {
49 | if (category.hidden) {
50 | return null;
51 | }
52 |
53 | return (
54 |
59 | {category.name}
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/apps/web/components/cli-commands.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import CopyButton from "@/components/copy-button";
4 | import { useConfig } from "@/hooks/use-config";
5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/uitopia/tabs";
6 |
7 | export default function CliCommands({ name }: { name: string }) {
8 | const [config, setConfig] = useConfig();
9 | const packageManager = config.packageManager || "pnpm";
10 |
11 | const commands = {
12 | pnpm: `pnpm dlx shadcn@latest add https://uitopia.vercel.app/r/${name}.json`,
13 | npm: `npx shadcn@latest add https://uitopia.vercel.app/r/${name}.json`,
14 | yarn: `npx shadcn@latest add https://uitopia.vercel.app/r/${name}.json`,
15 | bun: `bunx --bun shadcn@latest add https://uitopia.vercel.app/r/${name}.json`,
16 | };
17 |
18 | return (
19 |
20 |
{
23 | setConfig({
24 | ...config,
25 | packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
26 | });
27 | }}
28 | className="rounded-lg bg-zinc-950 dark:bg-zinc-900"
29 | >
30 |
31 |
32 |
36 | pnpm
37 |
38 |
42 | npm
43 |
44 |
48 | yarn
49 |
50 |
54 | bun
55 |
56 |
57 |
58 | {Object.entries(commands).map(([pkg, command]) => (
59 |
60 |
61 | {command}
62 |
63 |
64 | ))}
65 |
66 |
69 |
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/apps/web/components/code-block.tsx:
--------------------------------------------------------------------------------
1 | import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock";
2 |
3 | interface CodeBlockProps {
4 | code: string;
5 | language?: string;
6 | }
7 |
8 | export function CodeBlock({ code, language = "tsx" }: CodeBlockProps) {
9 | return (
10 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/apps/web/components/content/component-loader.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { V0Button } from "@/components/v0-button";
3 | import { cn } from "@/lib/utils";
4 | import { Icons } from "@/registry/components/icons";
5 | import type {
6 | ComponentDisplayProps,
7 | ComponentLoaderProps,
8 | } from "@/types/component";
9 | import {
10 | Tooltip,
11 | TooltipContent,
12 | TooltipProvider,
13 | TooltipTrigger,
14 | } from "@/uitopia/tooltip";
15 |
16 | export function ComponentLoader({
17 | name,
18 | hasReTrigger = false,
19 | classNameComponentContainer,
20 | showV0Button = true,
21 | }: ComponentLoaderProps & { showV0Button?: boolean }) {
22 | const [Component, setComponent] = useState(null);
23 | const [reTriggerKey, setReTriggerKey] = useState(() => Date.now());
24 |
25 | useEffect(() => {
26 | async function loadComponent() {
27 | try {
28 | const importedComponent = await import(`@/registry/components/${name}`);
29 | setComponent(() => importedComponent.default);
30 | } catch (error) {
31 | console.error(`Failed to load component: ${name}`, error);
32 | }
33 | }
34 | loadComponent();
35 | }, [name]);
36 |
37 | const handleReTrigger = () => {
38 | setReTriggerKey(Date.now());
39 | };
40 |
41 | if (!Component) {
42 | return (
43 |
44 |
45 | Loading...
46 |
47 | );
48 | }
49 |
50 | return (
51 | }
53 | hasReTrigger={hasReTrigger}
54 | className={classNameComponentContainer}
55 | reTriggerKey={reTriggerKey}
56 | reTrigger={handleReTrigger}
57 | name={name}
58 | showV0Button={showV0Button}
59 | />
60 | );
61 | }
62 |
63 | function ComponentDisplay({
64 | component,
65 | hasReTrigger,
66 | className,
67 | reTriggerKey,
68 | reTrigger,
69 | name,
70 | showV0Button = true,
71 | }: ComponentDisplayProps & { showV0Button?: boolean }) {
72 | const renderComponent = () => {
73 | if (!React.isValidElement(component)) {
74 | return null;
75 | }
76 |
77 | return hasReTrigger
78 | ? React.cloneElement(component, { key: reTriggerKey })
79 | : component;
80 | };
81 |
82 | return (
83 |
89 |
90 | {showV0Button && (
91 |
96 | )}
97 |
98 |
99 |
100 | {hasReTrigger && (
101 |
107 |
108 |
109 | )}
110 |
111 | Refresh component
112 |
113 |
114 |
115 | {renderComponent()}
116 |
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/apps/web/components/content/component-preview-collapse.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import type { RegistryItem } from "shadcn/registry";
5 | import { CodeBlock } from "@/components/code-block";
6 | import { ComponentLoader } from "@/components/content/component-loader";
7 | import { convertRegistryPaths, getComponentsByName } from "@/lib/registry";
8 | import { cn } from "@/lib/utils";
9 | import { Icons } from "@/registry/components/icons";
10 | import type { ComponentPreviewProps } from "@/types/component";
11 | import {
12 | Collapsible,
13 | CollapsibleContent,
14 | CollapsibleTrigger,
15 | } from "@/uitopia/collapsible";
16 |
17 | export function ComponentCollapse({
18 | name,
19 | hasReTrigger = false,
20 | classNameComponentContainer,
21 | }: ComponentPreviewProps) {
22 | const [isOpen, setIsOpen] = useState(false);
23 | const [_component, setComponent] = useState(null);
24 | const [code, setCode] = useState(null);
25 | const [error, setError] = useState(null);
26 |
27 | useEffect(() => {
28 | const registryComponent = getComponentsByName(name);
29 | setComponent(registryComponent);
30 |
31 | async function loadComponentCode() {
32 | try {
33 | const response = await fetch(`/r/${name}.json`);
34 | const data = (await response.json()) as RegistryItem;
35 | const codeContent = data.files?.[0]?.content;
36 |
37 | if (!codeContent) {
38 | throw new Error("No code content found");
39 | }
40 |
41 | setCode(convertRegistryPaths(codeContent));
42 | setError(null);
43 | } catch (err) {
44 | console.error("Error loading component:", err);
45 | setCode(null);
46 | setError("Failed to load component code");
47 | }
48 | }
49 |
50 | loadComponentCode();
51 | }, [name]);
52 |
53 | return (
54 |
59 |
60 |
61 |
66 |
67 |
68 |
69 |
75 | {isOpen ? "Hide" : "Show"} code
76 |
77 |
78 |
79 |
80 |
81 | {error ? (
82 |
{error}
83 | ) : code ? (
84 |
85 | ) : (
86 |
87 | Loading code...
88 |
89 | )}
90 |
91 |
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/apps/web/components/content/component-preview.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import type { RegistryItem } from "shadcn/registry";
5 | import { CodeBlock } from "@/components/code-block";
6 | import { ComponentLoader } from "@/components/content/component-loader";
7 | import { convertRegistryPaths, getComponentsByName } from "@/lib/registry";
8 | import type { ComponentPreviewProps } from "@/types/component";
9 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/uitopia/tabs";
10 |
11 | export function ComponentPreview({
12 | name,
13 | hasReTrigger = false,
14 | classNameComponentContainer,
15 | }: ComponentPreviewProps) {
16 | const [activeTab, setActiveTab] = useState("preview");
17 | const [_component, setComponent] = useState(null);
18 | const [code, setCode] = useState(null);
19 | const [error, setError] = useState(null);
20 |
21 | useEffect(() => {
22 | const registryComponent = getComponentsByName(name);
23 | setComponent(registryComponent);
24 |
25 | async function loadComponentCode() {
26 | try {
27 | const response = await fetch(`/r/${name}.json`);
28 | const data = (await response.json()) as RegistryItem;
29 | const codeContent = data.files?.[0]?.content;
30 |
31 | if (!codeContent) {
32 | throw new Error("No code content found");
33 | }
34 |
35 | setCode(convertRegistryPaths(codeContent));
36 | setError(null);
37 | } catch (err) {
38 | console.error("Error loading component:", err);
39 | setCode(null);
40 | setError("Failed to load component code");
41 | }
42 | }
43 |
44 | loadComponentCode();
45 | }, [name]);
46 |
47 | return (
48 |
49 |
54 |
55 |
59 | Preview
60 |
61 |
65 | Code
66 |
67 |
68 |
69 |
70 |
75 |
76 |
77 |
78 | {error ? (
79 | {error}
80 | ) : code ? (
81 |
82 | ) : (
83 |
84 | Loading code...
85 |
86 | )}
87 |
88 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/apps/web/components/content/component-source.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import type { RegistryItem } from "shadcn/registry";
5 | import { CodeBlock } from "@/components/code-block";
6 | import { convertRegistryPaths, getComponentsByName } from "@/lib/registry";
7 | import { cn } from "@/lib/utils";
8 | import type { ComponentSourceProps } from "@/types/component";
9 | import { Button } from "@/uitopia/button";
10 | import {
11 | Collapsible,
12 | CollapsibleContent,
13 | CollapsibleTrigger,
14 | } from "@/uitopia/collapsible";
15 |
16 | export function ComponentSource({
17 | name,
18 | expandButtonTitle = "Expand",
19 | defaultExpanded = false,
20 | maxHeight = "550px",
21 | className,
22 | ...props
23 | }: ComponentSourceProps) {
24 | const [isOpened, setIsOpened] = useState(defaultExpanded);
25 | const [_component, setComponent] = useState(null);
26 | const [code, setCode] = useState(null);
27 | const [error, setError] = useState(null);
28 |
29 | useEffect(() => {
30 | const registryComponent = getComponentsByName(name);
31 | setComponent(registryComponent);
32 |
33 | async function loadComponentCode() {
34 | try {
35 | const response = await fetch(`/r/${name}.json`);
36 | const data = (await response.json()) as RegistryItem;
37 | const codeContent = data.files?.[0]?.content;
38 |
39 | if (!codeContent) {
40 | throw new Error("No code content found");
41 | }
42 |
43 | setCode(convertRegistryPaths(codeContent));
44 | setError(null);
45 | } catch (err) {
46 | console.error("Error loading component:", err);
47 | setCode(null);
48 | setError("Failed to load component code");
49 | }
50 | }
51 |
52 | loadComponentCode();
53 | }, [name]);
54 |
55 | return (
56 |
61 |
68 |
72 |
82 | {error ? (
83 |
{error}
84 | ) : code ? (
85 |
86 | ) : (
87 |
88 | Loading code...
89 |
90 | )}
91 |
92 |
93 |
94 |
100 |
101 |
102 | {isOpened ? "Collapse" : expandButtonTitle}
103 |
104 |
105 |
106 |
107 |
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/apps/web/components/copy-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { cn } from "@/lib/utils";
5 | import { Icons } from "@/registry/components/icons";
6 | import { Button } from "@/uitopia/button";
7 |
8 | interface CopyButtonProps {
9 | componentSource: string;
10 | className?: string;
11 | duration?: number;
12 | }
13 |
14 | function CopyButton({
15 | componentSource,
16 | className,
17 | duration = 2000,
18 | }: CopyButtonProps) {
19 | const [copied, setCopied] = useState(false);
20 |
21 | async function copy(text: string) {
22 | try {
23 | await navigator.clipboard.writeText(text);
24 | setCopied(true);
25 | setTimeout(() => setCopied(false), duration);
26 | return true;
27 | } catch (err) {
28 | console.error("Failed to copy text: ", err);
29 | return false;
30 | }
31 | }
32 |
33 | return (
34 |
35 | copy(componentSource)}
43 | aria-label={copied ? "Copied" : "Copy code"}
44 | >
45 | {copied ? (
46 |
47 | ) : (
48 |
49 | )}
50 |
51 |
52 | );
53 | }
54 |
55 | export default CopyButton;
56 |
--------------------------------------------------------------------------------
/apps/web/components/hero.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { siteConfig } from "@/config/site";
3 | import { cn } from "@/lib/utils";
4 | import { buttonVariants } from "@/uitopia/button";
5 | import {
6 | PageActions,
7 | PageHeader,
8 | PageHeaderDescription,
9 | PageHeaderHeading,
10 | } from "@/uitopia/page-header";
11 | import { Announcement } from "./announcement";
12 |
13 | export function Hero() {
14 | return (
15 |
16 |
17 |
18 | Make your interface delightful
19 | effortlessly.
20 |
21 |
22 | Collection set of beautifully designed motions components. Easy
23 | copy-paste. Customizable. Open Source.
24 |
25 |
26 |
27 |
34 | Explore Now
35 |
36 |
45 | GitHub
46 |
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/apps/web/components/showcase-code-viewer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type React from "react";
4 | import { Suspense } from "react";
5 | import type { RegistryItem } from "shadcn/registry";
6 | import ComponentCli from "@/components/cli-commands";
7 | import { V0Button } from "@/components/v0-button";
8 | import { useMediaQuery } from "@/hooks/use-media-query";
9 | import { cn } from "@/lib/utils";
10 | import { Icons } from "@/registry/components/icons";
11 | import {
12 | Drawer,
13 | DrawerContent,
14 | DrawerTitle,
15 | DrawerTrigger,
16 | } from "@/registry/ui/drawer";
17 | import { Button } from "@/uitopia/button";
18 | import { Sheet, SheetContent, SheetTitle, SheetTrigger } from "@/uitopia/sheet";
19 |
20 | interface CodeViewerProps {
21 | children: React.ReactNode;
22 | component: RegistryItem;
23 | }
24 |
25 | function Content({ children, component }: CodeViewerProps) {
26 | return (
27 |
28 |
29 |
Installation
30 |
31 |
32 |
33 |
34 |
Code
35 |
38 |
39 | {children}
40 |
41 |
42 | );
43 | }
44 |
45 | export function CodeViewer({ component, children }: CodeViewerProps) {
46 | const isDesktop = useMediaQuery("(min-width: 768px)");
47 |
48 | if (!isDesktop) {
49 | return (
50 |
51 |
52 |
57 | View Code
58 |
59 |
60 |
61 | Code
62 |
63 | {children}
64 |
65 |
66 |
67 | );
68 | }
69 |
70 | return (
71 |
72 |
73 |
86 | View Code
87 |
88 |
89 |
93 | Code
94 |
97 |
98 | Loading...
99 |
100 | }
101 | >
102 | {children}
103 |
104 |
105 |
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/apps/web/components/showcase-component.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import type { RegistryItem } from "shadcn/registry";
5 | import { CodeBlock } from "@/components/code-block";
6 | import { ComponentLoader } from "@/components/content/component-loader";
7 | import { convertRegistryPaths, getComponentsByName } from "@/lib/registry";
8 | import { cn } from "@/lib/utils";
9 | import { Badge } from "@/uitopia/badge";
10 | import { CodeViewer } from "./showcase-code-viewer";
11 |
12 | interface ShowcaseComponentProps {
13 | name: string;
14 | height?: string;
15 | showV0Button?: boolean;
16 | }
17 |
18 | export function ShowcaseComponent({
19 | name,
20 | height = "h-[400px] md:h-[540px]",
21 | showV0Button = true,
22 | }: ShowcaseComponentProps) {
23 | const [code, setCode] = useState(null);
24 | const [error, setError] = useState(null);
25 | const [component, setComponent] = useState(null);
26 |
27 | useEffect(() => {
28 | const registryComponent = getComponentsByName(name);
29 | setComponent(registryComponent);
30 |
31 | async function loadComponentCode() {
32 | try {
33 | const response = await fetch(`/r/${name}.json`);
34 | const data = (await response.json()) as RegistryItem;
35 | const codeContent = data.files?.[0]?.content;
36 |
37 | if (!codeContent) {
38 | throw new Error("No code content found");
39 | }
40 |
41 | setCode(convertRegistryPaths(codeContent));
42 | setError(null);
43 | } catch (err) {
44 | console.error("Error loading component:", err);
45 | setCode(null);
46 | setError("Failed to load component code");
47 | }
48 | }
49 |
50 | loadComponentCode();
51 | }, [name]);
52 |
53 | if (!component) {
54 | return null;
55 | }
56 |
57 | return (
58 |
59 | {/* Component Display */}
60 |
66 |
67 |
68 | {error ? (
69 | {error}
70 | ) : code ? (
71 |
72 | ) : (
73 |
74 | Loading code...
75 |
76 | )}
77 |
78 |
79 |
80 |
81 |
82 | {/* Component Preview */}
83 |
84 |
85 |
{component.title}
86 |
87 | {component.description}
88 |
89 |
90 |
91 | {component.categories?.map((category) => (
92 |
93 | {category}
94 |
95 | ))}
96 |
97 |
98 |
99 | );
100 | }
101 |
--------------------------------------------------------------------------------
/apps/web/components/v0-button.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { cn } from "@/lib/utils";
3 | import { buttonVariants } from "@/uitopia/button";
4 | import {
5 | Tooltip,
6 | TooltipContent,
7 | TooltipProvider,
8 | TooltipTrigger,
9 | } from "@/uitopia/tooltip";
10 |
11 | interface V0ButtonProps {
12 | componentSource: string;
13 | className?: string;
14 | variant?: "default" | "icon";
15 | }
16 |
17 | const V0Icon = () => (
18 |
24 |
28 |
32 |
33 | );
34 |
35 | export function V0Button({
36 | componentSource,
37 | className,
38 | variant = "default",
39 | }: V0ButtonProps) {
40 | const href = `https://v0.dev/chat/api/open?url=${encodeURIComponent(componentSource)}`;
41 |
42 | if (variant === "icon") {
43 | return (
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 | Open in v0
58 |
59 |
60 | );
61 | }
62 |
63 | return (
64 |
75 | Open in
76 |
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/apps/web/config/site.ts:
--------------------------------------------------------------------------------
1 | export const siteConfig = {
2 | name: "ui/topia",
3 | creator: "@kyuotaka",
4 | url: process.env.NEXT_PUBLIC_APP_URL ?? "https://uitopia.vercel.app",
5 | ogImage: `${process.env.NEXT_PUBLIC_APP_URL}/opengraph-image.png`,
6 | description:
7 | "ui/topia is, a growing collection of experimental components built with shadcn/ui, Motion and Tailwind CSS.",
8 | keywords: [
9 | "Next.js",
10 | "React",
11 | "Tailwind CSS",
12 | "UI Library",
13 | "UI Kit",
14 | "UI Components",
15 | "UI Elements",
16 | "Open Source",
17 | "shadcn/ui",
18 | ],
19 | links: {
20 | portfolio: "https://cahyawibawa.com/",
21 | github: "https://git.new/ui/topia",
22 | },
23 | };
24 |
25 | export type SiteConfig = typeof siteConfig;
26 |
--------------------------------------------------------------------------------
/apps/web/content/docs/components/button.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Button
3 | description: Custom shadcn/ui button.
4 | ---
5 |
6 | ### Color
7 |
8 |
12 |
13 | ### Primary
14 |
15 |
19 |
20 | ### Outline
21 |
22 |
26 |
27 | ### Destructive
28 |
29 |
33 |
--------------------------------------------------------------------------------
/apps/web/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | description: Your Perfect Destination for Elegant, Accessible, and Customizable UI Components
4 | icon: Album
5 | ---
6 |
7 | ## What is ui/topia?
8 |
9 | **ui/topia** isn’t just a component library (yet)—it’s my personal playground for:
10 |
11 | - Exploring interaction design with [Motion](https://motion.dev), formerly known as **Framer Motion**.
12 | - Recreating and collecting UI ideas that inspire me.
13 | - Refining documentation writing skills.
14 | - Working towards becoming a Design Engineer.
15 |
16 |
24 |
25 | ## Motivation
26 |
27 | **ui/topia** was created to be a space where unique and accessible UI components could inspire creativity. It’s designed to help designers and developers find ideas and solutions for their projects.
28 |
29 | This project draws inspiration from [shadcn/ui](https://github.com/shadcn/ui), [origin/ui](https://originui.com), [motion primitives](https://motion-primitives.com), [magic ui](https://magicui.design), and other well-known libraries.
30 |
31 | ## FAQ
32 |
33 |
34 |
35 | Definitely! **ui/topia** is free for personal and commercial projects.
36 | No credit is needed, but I’d love to see what you build, so feel free to share!
37 |
38 |
39 |
40 | Absolutely! Adding your work is a great way to showcase your creativity and help other developers.
41 | Check out the [CONTRIBUTING.md](https://github.com/cahyawibawa/ui-topia/blob/main/CONTRIBUTING.md) file for all the details. It’s got everything you need to get started.
42 |
43 |
44 |
45 | Not quite—at least not yet. For now, it’s more of a learning and experimentation playground.
46 |
47 |
48 |
49 | Start exploring **ui/topia** today and take your UI design to new heights!
50 |
--------------------------------------------------------------------------------
/apps/web/content/docs/installation.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Installation
3 | description: A simple guide to set up ui/topia with the required dependencies and structure.
4 | icon: PackagePlus
5 | ---
6 |
7 | This project is built with **Next.js**, using **TypeScript** and **Tailwind CSS** for all components. Some components also rely on [shadcn/ui](https://ui.shadcn.com/docs/installation) and [Motion](https://motion.dev).
8 |
9 | For the best development experience, I recommend using **TypeScript**, but it’s not mandatory.
10 |
11 |
12 |
13 | ### Install Tailwind CSS
14 |
15 | The components are styled using **Tailwind CSS**.
16 | Follow the official [installation guide](https://tailwindcss.com/docs/installation) to set it up in your project.
17 |
18 |
19 |
20 | ### Install Motion
21 |
22 | To use components with animations, you’ll need to add **Motion** to your project.
23 |
24 |
25 | ```bash
26 | npm install motion
27 | ```
28 |
29 | {/*
30 | ```bash tab="npm"
31 | npm install motion
32 | ```
33 | ```bash tab="pnpm"
34 | pnpm add motion
35 | ```
36 | ```bash tab="bun"
37 | bun add motion
38 | ```
39 | */}
40 |
41 |
42 |
43 | ### Add a Utility Helper
44 |
45 | To make it easier to conditionally apply Tailwind CSS classes, add this `cn` helper function to your project.
46 |
47 | ```ts title="lib/utils.ts"
48 | import { clsx, type ClassValue } from 'clsx'
49 | import { twMerge } from 'tailwind-merge'
50 |
51 | export function cn(...inputs: ClassValue[]) {
52 | return twMerge(clsx(inputs))
53 | }
54 | ```
55 |
56 |
57 |
58 | ### Folder Structure
59 |
60 | Here’s a recommended folder structure to organize your components and utilities:
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Feel free to adapt it based on your project’s needs.
74 |
75 |
76 |
77 | You're all set! You can now start enjoying the components.
78 |
--------------------------------------------------------------------------------
/apps/web/content/docs/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "ui",
3 | "root": true,
4 | "pages": [
5 | "---Getting Started---",
6 | "index",
7 | "installation",
8 | "---Components---",
9 | "...components",
10 | "---Text---",
11 | "...texts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/apps/web/content/docs/props.ts:
--------------------------------------------------------------------------------
1 | import type { TextEffectProps } from "@/registry/ui/text-effect";
2 | import type { TextLoopProps } from "@/registry/ui/text-loop";
3 | import type { TextTypeProps } from "@/registry/ui/text-type";
4 |
5 | export type TextLoop = TextLoopProps;
6 | export type TextEffect = TextEffectProps;
7 | export type TextType = TextTypeProps;
8 |
--------------------------------------------------------------------------------
/apps/web/content/docs/texts/text-loop.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Text Loop
3 | description: Text animation that transitions between multiple items, creating an engaging looping effect.
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 | ## Installation
17 |
18 |
19 |
20 |
21 | CLI
22 | Manual
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Install the following dependencies:
35 |
36 |
37 |
38 | Copy and paste the following code into your project.
39 |
40 |
41 |
42 | Update the import paths to match your project setup.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ## Props
51 |
52 |
53 |
54 | {/* void',
90 | default: '-',
91 | },
92 | }}
93 | /> */}
94 |
95 |
--------------------------------------------------------------------------------
/apps/web/content/docs/texts/text-type.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Typewriter
3 | description: Text animation that types out a text, one letter at a time.
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 | ## Installation
17 |
18 |
19 |
20 |
21 | CLI
22 | Manual
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Install the following dependencies:
35 |
36 |
37 |
38 | Copy and paste the following code into your project.
39 |
40 |
41 |
42 | Update the import paths to match your project setup.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ## Props
51 |
52 |
53 |
54 | {/* */}
124 |
125 |
--------------------------------------------------------------------------------
/apps/web/hooks/use-config.ts:
--------------------------------------------------------------------------------
1 | import { useAtom } from "jotai";
2 | import { atomWithStorage } from "jotai/utils";
3 |
4 | type Config = {
5 | packageManager: "npm" | "yarn" | "pnpm" | "bun";
6 | };
7 |
8 | const configAtom = atomWithStorage("config", {
9 | packageManager: "pnpm",
10 | });
11 |
12 | export function useConfig() {
13 | return useAtom(configAtom);
14 | }
15 |
--------------------------------------------------------------------------------
/apps/web/hooks/use-copy-to-clipboard.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 |
5 | export function useCopyToClipboard({
6 | timeout = 2000,
7 | onCopy,
8 | }: {
9 | timeout?: number;
10 | onCopy?: () => void;
11 | } = {}) {
12 | const [isCopied, setIsCopied] = React.useState(false);
13 |
14 | const copyToClipboard = (value: string) => {
15 | if (typeof window === "undefined" || !navigator.clipboard.writeText) {
16 | return;
17 | }
18 |
19 | if (!value) return;
20 |
21 | navigator.clipboard.writeText(value).then(() => {
22 | setIsCopied(true);
23 |
24 | if (onCopy) {
25 | onCopy();
26 | }
27 |
28 | setTimeout(() => {
29 | setIsCopied(false);
30 | }, timeout);
31 | }, console.error);
32 | };
33 |
34 | return { isCopied, copyToClipboard };
35 | }
36 |
--------------------------------------------------------------------------------
/apps/web/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | export function useMediaQuery(query: string) {
4 | const [value, setValue] = React.useState(false);
5 |
6 | React.useEffect(() => {
7 | function onChange(event: MediaQueryListEvent) {
8 | setValue(event.matches);
9 | }
10 |
11 | const result = matchMedia(query);
12 | result.addEventListener("change", onChange);
13 | setValue(result.matches);
14 |
15 | return () => result.removeEventListener("change", onChange);
16 | }, [query]);
17 |
18 | return value;
19 | }
20 |
--------------------------------------------------------------------------------
/apps/web/lib/blocks.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import type { RegistryItem } from "shadcn/registry";
4 | import { getComponents, getComponentsByName } from "./registry";
5 |
6 | export async function getAllBlockIds(
7 | types: RegistryItem["type"][] = ["registry:block"],
8 | categories: string[] = [],
9 | ): Promise {
10 | try {
11 | // Get all components from local registry
12 | const registry = getComponents();
13 |
14 | const blocks = registry
15 | .filter((item: RegistryItem) => types.includes(item.type))
16 | .map((item: RegistryItem) => item.name);
17 |
18 | // If no categories specified, return all blocks
19 | if (categories.length === 0) {
20 | return blocks;
21 | }
22 |
23 | // Filter blocks by category
24 | const filteredBlocks = [];
25 | for (const name of blocks) {
26 | const blockData = getComponentsByName(name);
27 |
28 | if (
29 | blockData?.categories?.some((category) =>
30 | categories.includes(category.toLowerCase()),
31 | )
32 | ) {
33 | filteredBlocks.push(name);
34 | }
35 | }
36 |
37 | return filteredBlocks;
38 | } catch (error) {
39 | console.error("Error getting blocks:", error);
40 | return [];
41 | }
42 | }
43 |
44 | export async function getBlockCategories(): Promise<
45 | Array<{ name: string; slug: string }>
46 | > {
47 | try {
48 | const registry = getComponents();
49 |
50 | const categories = new Set();
51 |
52 | // Get all block names first
53 | const blocks = registry.filter(
54 | (item: RegistryItem) => item.type === "registry:block",
55 | );
56 |
57 | // Collect categories from each block
58 | for (const block of blocks) {
59 | if (block.categories) {
60 | for (const category of block.categories) {
61 | categories.add(category.toLowerCase());
62 | }
63 | }
64 | }
65 |
66 | return Array.from(categories).map((category) => ({
67 | name: category.charAt(0).toUpperCase() + category.slice(1),
68 | slug: category,
69 | }));
70 | } catch (error) {
71 | console.error("Error getting categories:", error);
72 | return [];
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/apps/web/lib/fonts.ts:
--------------------------------------------------------------------------------
1 | import { Geist, Geist_Mono } from "next/font/google";
2 | import localFont from "next/font/local";
3 |
4 | export const geistSans = Geist({
5 | variable: "--font-geist-sans",
6 | subsets: ["latin"],
7 | });
8 |
9 | export const geistMono = Geist_Mono({
10 | variable: "--font-geist-mono",
11 | subsets: ["latin"],
12 | });
13 |
14 | export const Redaction = localFont({
15 | src: "../public/fonts/Redaction_35-Regular.woff2",
16 | variable: "--font-redaction",
17 | });
18 |
--------------------------------------------------------------------------------
/apps/web/lib/metadata.ts:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { siteConfig } from "@/config/site";
3 |
4 | export const defaultMetadata: Metadata = {
5 | title: {
6 | default: siteConfig.name,
7 | template: `%s - ${siteConfig.name}`,
8 | },
9 | description: siteConfig.description,
10 | metadataBase: process.env.NEXT_PUBLIC_APP_URL
11 | ? new URL(process.env.NEXT_PUBLIC_APP_URL)
12 | : undefined,
13 | keywords: siteConfig.keywords,
14 | };
15 |
16 | export const defaultSocialMetadata = {
17 | openGraph: {
18 | title: siteConfig.name,
19 | description: siteConfig.description,
20 | type: "website",
21 | images: ["/api/og"],
22 | url: siteConfig.url,
23 | siteName: siteConfig.name,
24 | },
25 | twitter: {
26 | card: "summary_large_image",
27 | creator: siteConfig.creator,
28 | title: siteConfig.name,
29 | description: siteConfig.description,
30 | images: ["/api/og"],
31 | },
32 | } satisfies Pick;
33 |
34 | export function createMetadata(override: Metadata): Metadata {
35 | return {
36 | ...defaultMetadata,
37 | ...override,
38 | openGraph: {
39 | ...defaultSocialMetadata.openGraph,
40 | title: override.title ?? defaultSocialMetadata.openGraph.title,
41 | description:
42 | override.description ?? defaultSocialMetadata.openGraph.description,
43 | ...override.openGraph,
44 | },
45 | twitter: {
46 | ...defaultSocialMetadata.twitter,
47 | title: override.title ?? defaultSocialMetadata.twitter.title,
48 | description:
49 | override.description ?? defaultSocialMetadata.twitter.description,
50 | ...override.twitter,
51 | },
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/apps/web/lib/registry.ts:
--------------------------------------------------------------------------------
1 | import type { RegistryItem } from "shadcn/registry";
2 | import registry from "@/registry.json";
3 |
4 | const components = registry.items as RegistryItem[];
5 |
6 | const PATH_MAPPINGS = {
7 | "@/uitopia/": "@/components/ui/",
8 | "@/registry/ui": "@/components/ui",
9 | "@/registry/hooks": "@/hooks",
10 | "@/registry/lib": "@/lib",
11 | } as const;
12 |
13 | export interface FileTree {
14 | name: string;
15 | path: string; // Original path from registry.json
16 | displayPath: string; // Transformed path for display (e.g., app/...)
17 | children?: FileTree[];
18 | }
19 |
20 | export function getComponents(): readonly RegistryItem[] {
21 | return components;
22 | }
23 |
24 | export function getComponentsByName(name: string): RegistryItem | null {
25 | return components.find((item) => item.name === name) ?? null;
26 | }
27 |
28 | export function convertRegistryPaths(content: string): string {
29 | let updatedContent = content;
30 |
31 | // 1. Handle specific block component imports
32 | const blockComponentRegex =
33 | /@\/registry\/blocks\/([\w-]+)\/components\/([\w-]+)/g;
34 | updatedContent = updatedContent.replace(
35 | blockComponentRegex,
36 | (_match, _blockName, componentName) => `@/components/${componentName}`,
37 | );
38 |
39 | // 2. Handle general registry path mappings
40 | updatedContent = Object.entries(PATH_MAPPINGS).reduce(
41 | (acc, [from, to]) => acc.replace(new RegExp(from, "g"), to),
42 | updatedContent, // Start with the already updated content
43 | );
44 |
45 | return updatedContent;
46 | }
47 |
48 | export function getFileTarget(file: { path: string; type?: string }) {
49 | if (!file.path) return "";
50 |
51 | const pathParts = file.path.split("/");
52 | const blocksIndex = pathParts.indexOf("blocks");
53 | if (blocksIndex === -1) return file.path;
54 |
55 | const componentName = pathParts[blocksIndex + 1];
56 | const fileName = pathParts[pathParts.length - 1];
57 | const isComponent = pathParts.includes("components");
58 |
59 | if (isComponent) {
60 | return `components/${fileName}`;
61 | }
62 |
63 | if (fileName === "page.tsx") {
64 | return `app/${componentName}/${fileName}`;
65 | }
66 |
67 | return fileName;
68 | }
69 |
70 | export function createFileTreeForRegistryItemFiles(
71 | files: Array<{ path: string; target?: string }>,
72 | ): FileTree[] {
73 | const root: FileTree[] = [];
74 |
75 | // First pass: collect all unique display paths and map them back to original paths
76 | const uniquePaths = new Map(); // Map
77 | for (const file of files) {
78 | const displayPath = file.target ?? getFileTarget(file);
79 | if (displayPath && !uniquePaths.has(displayPath)) {
80 | uniquePaths.set(displayPath, file.path);
81 | }
82 | }
83 |
84 | // Second pass: create tree structure using display paths
85 | for (const [displayPath, originalPath] of uniquePaths.entries()) {
86 | const parts = displayPath.split("/");
87 | let currentLevel = root;
88 |
89 | for (let i = 0; i < parts.length; i++) {
90 | const part = parts[i];
91 | if (!part) continue;
92 |
93 | const isFile = i === parts.length - 1;
94 | const existingNode = currentLevel.find((node) => node.name === part);
95 |
96 | if (existingNode) {
97 | if (isFile) {
98 | // This shouldn't happen if display paths are unique, but handle defensively
99 | existingNode.path = originalPath;
100 | existingNode.displayPath = displayPath;
101 | } else {
102 | // Ensure children array exists
103 | existingNode.children = existingNode.children || [];
104 | currentLevel = existingNode.children;
105 | }
106 | } else {
107 | const newNode: FileTree = {
108 | name: part,
109 | path: isFile ? originalPath : "", // Only files have a meaningful original path here
110 | displayPath: isFile ? displayPath : "", // Folders don't have a direct path
111 | children: isFile ? undefined : [],
112 | };
113 |
114 | currentLevel.push(newNode);
115 |
116 | if (!isFile) {
117 | currentLevel = newNode.children!;
118 | }
119 | }
120 | }
121 | }
122 |
123 | return root;
124 | }
125 |
--------------------------------------------------------------------------------
/apps/web/lib/source.ts:
--------------------------------------------------------------------------------
1 | import type { InferMetaType, InferPageType } from "fumadocs-core/source";
2 | import { loader } from "fumadocs-core/source";
3 | import { createMDXSource } from "fumadocs-mdx";
4 | import { icons } from "lucide-react";
5 | import { docs, meta } from "@/.source";
6 | import { create } from "@/registry/components/icons";
7 |
8 | export const source = loader({
9 | baseUrl: "/docs",
10 | source: createMDXSource(docs, meta),
11 | icon(icon) {
12 | if (icon && icon in icons)
13 | return create({ icon: icons[icon as keyof typeof icons] });
14 | },
15 | });
16 |
17 | export type Page = InferPageType;
18 | export type Meta = InferMetaType;
19 |
--------------------------------------------------------------------------------
/apps/web/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function absoluteUrl(path: string) {
9 | return `${process.env.NEXT_PUBLIC_APP_URL}${path}`;
10 | }
11 |
--------------------------------------------------------------------------------
/apps/web/next.config.ts:
--------------------------------------------------------------------------------
1 | import createBundleAnalyzer from "@next/bundle-analyzer";
2 | import { createMDX } from "fumadocs-mdx/next";
3 | import type { NextConfig } from "next";
4 |
5 | const withAnalyzer = createBundleAnalyzer({
6 | enabled: process.env.ANALYZE === "true",
7 | });
8 |
9 | const nextConfig: NextConfig = {
10 | outputFileTracingIncludes: {
11 | registry: ["./registry/**/*"],
12 | },
13 | };
14 |
15 | const withMDX = createMDX();
16 |
17 | export default withAnalyzer(withMDX(nextConfig));
18 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "next dev --turbo",
8 | "build": "next build",
9 | "lint": "next lint",
10 | "analyze": "ANALYZE=true next build",
11 | "clean": "git clean -xdf .next .turbo node_modules",
12 | "format": "biome format --write .",
13 | "check": "biome check --fix .",
14 | "shadcn": "bun x shadcn@latest add",
15 | "registry:build": "shadcn build"
16 | },
17 | "dependencies": {
18 | "@ui/topia": "workspace:*",
19 | "@vercel/analytics": "^1.4.1",
20 | "class-variance-authority": "^0.7.1",
21 | "clsx": "^2.1.1",
22 | "fumadocs-core": "^15.2.10",
23 | "fumadocs-docgen": "^2.0.0",
24 | "fumadocs-mdx": "^11.6.1",
25 | "fumadocs-twoslash": "^3.1.1",
26 | "fumadocs-typescript": "^4.0.3",
27 | "fumadocs-ui": "^15.2.10",
28 | "jotai": "^2.11.0",
29 | "motion": "^11.14.4",
30 | "next": "^15.3.1",
31 | "react": "^19.0.0",
32 | "react-dom": "^19.0.0",
33 | "react-wrap-balancer": "^1.1.0",
34 | "shadcn": "^2.4.0-canary.11",
35 | "sharp": "^0.33.3",
36 | "tailwind-merge": "^2.5.5",
37 | "tailwindcss-animate": "^1.0.7",
38 | "zod": "^3.23.8"
39 | },
40 | "devDependencies": {
41 | "@next/bundle-analyzer": "^15.1.4",
42 | "@tailwindcss/postcss": "^4.0.3",
43 | "@types/mdx": "^2.0.13",
44 | "@types/node": "^22.10.5",
45 | "@types/react": "^19.0.4",
46 | "@types/react-dom": "^19.0.2",
47 | "@uitopia/tsconfig": "workspace:*",
48 | "postcss": "^8.5.3",
49 | "tailwindcss": "^4.1.4",
50 | "typescript": "^5.7.3"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/apps/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | "@tailwindcss/postcss": {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/apps/web/public/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/public/banner.png
--------------------------------------------------------------------------------
/apps/web/public/fonts/Geist-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/public/fonts/Geist-Medium.otf
--------------------------------------------------------------------------------
/apps/web/public/fonts/Redaction_35-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/public/fonts/Redaction_35-Regular.woff2
--------------------------------------------------------------------------------
/apps/web/public/images/10x-deng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/public/images/10x-deng.png
--------------------------------------------------------------------------------
/apps/web/public/images/brush-line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/public/images/brush-line.png
--------------------------------------------------------------------------------
/apps/web/public/images/currents-evangelion.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/apps/web/public/images/currents-evangelion.jpg
--------------------------------------------------------------------------------
/apps/web/public/images/lines-top.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/apps/web/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/web/public/r/button-color.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "button-color",
4 | "type": "registry:component",
5 | "title": "Button color",
6 | "description": "custom shadcn/ui button.",
7 | "dependencies": [
8 | "@radix-ui/react-slot",
9 | "class-variance-authority",
10 | "clsx",
11 | "react",
12 | "tailwind-merge"
13 | ],
14 | "registryDependencies": [
15 | "https://uitopia.vercel.app/r/button.json",
16 | "https://uitopia.vercel.app/r/stack.json"
17 | ],
18 | "files": [
19 | {
20 | "path": "registry/components/button-color.tsx",
21 | "content": "\"use client\";\n\nimport { Button } from \"@/registry/ui/button\";\nimport { Stack } from \"@/registry/ui/stack\";\n\nexport default function ButtonColor() {\n return (\n \n \n Primary\n \n\n \n Primary\n \n\n \n Primary\n \n \n );\n}\n",
22 | "type": "registry:component",
23 | "target": "components/button-color.tsx"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/apps/web/public/r/button-destructive.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "button-destructive",
4 | "type": "registry:component",
5 | "title": "Button Destructive",
6 | "description": "custom shadcn/ui button.",
7 | "dependencies": [
8 | "@radix-ui/react-slot",
9 | "class-variance-authority",
10 | "clsx",
11 | "react",
12 | "tailwind-merge"
13 | ],
14 | "registryDependencies": [
15 | "https://uitopia.vercel.app/r/button.json",
16 | "https://uitopia.vercel.app/r/stack.json"
17 | ],
18 | "files": [
19 | {
20 | "path": "registry/components/button-destructive.tsx",
21 | "content": "\"use client\";\n\nimport { Button } from \"@/registry/ui/button\";\nimport { Stack } from \"@/registry/ui/stack\";\n\nexport default function ButtonDestructive() {\n return (\n \n \n Destructive\n \n\n \n \n Destructive\n \n \n\n \n \n Destructive\n \n \n \n );\n}\n",
22 | "type": "registry:component",
23 | "target": "components/button-destructive.tsx"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/apps/web/public/r/button-outline.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "button-outline",
4 | "type": "registry:component",
5 | "title": "Button Outline",
6 | "description": "custom shadcn/ui button.",
7 | "dependencies": [
8 | "@radix-ui/react-slot",
9 | "class-variance-authority",
10 | "clsx",
11 | "react",
12 | "tailwind-merge"
13 | ],
14 | "registryDependencies": [
15 | "https://uitopia.vercel.app/r/button.json",
16 | "https://uitopia.vercel.app/r/stack.json"
17 | ],
18 | "files": [
19 | {
20 | "path": "registry/components/button-outline.tsx",
21 | "content": "\"use client\";\n\nimport { Button } from \"@/registry/ui/button\";\nimport { Stack } from \"@/registry/ui/stack\";\n\nexport default function ButtonOutline() {\n return (\n \n \n Outline\n \n\n \n Outline\n \n\n \n Outline\n \n \n );\n}\n",
22 | "type": "registry:component",
23 | "target": "components/button-outline.tsx"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/apps/web/public/r/button-primary.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "button-primary",
4 | "type": "registry:component",
5 | "title": "Button Primary",
6 | "description": "custom shadcn/ui button.",
7 | "dependencies": [
8 | "@radix-ui/react-slot",
9 | "class-variance-authority",
10 | "clsx",
11 | "react",
12 | "tailwind-merge"
13 | ],
14 | "registryDependencies": [
15 | "https://uitopia.vercel.app/r/button.json",
16 | "https://uitopia.vercel.app/r/stack.json"
17 | ],
18 | "files": [
19 | {
20 | "path": "registry/components/button-primary.tsx",
21 | "content": "\"use client\";\n\nimport { Button } from \"@/registry/ui/button\";\nimport { Stack } from \"@/registry/ui/stack\";\n\nexport default function ButtonPrimary() {\n return (\n \n \n Primary\n \n\n \n \n Primary\n \n \n\n \n \n Primary\n \n \n \n );\n}\n",
22 | "type": "registry:component",
23 | "target": "components/button-primary.tsx"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/apps/web/public/r/button.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "button",
4 | "type": "registry:ui",
5 | "dependencies": [
6 | "@radix-ui/react-slot"
7 | ],
8 | "files": [
9 | {
10 | "path": "registry/ui/button.tsx",
11 | "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/registry/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90 ring-1 ring-inset ring-white/20 ring-offset-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes,\n VariantProps {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n \n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n",
12 | "type": "registry:ui",
13 | "target": "components/ui/button.tsx"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/apps/web/public/r/card.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "card",
4 | "type": "registry:ui",
5 | "files": [
6 | {
7 | "path": "registry/ui/card.tsx",
8 | "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardDescription,\n CardContent,\n};\n",
9 | "type": "registry:ui",
10 | "target": "components/ui/card.tsx"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/apps/web/public/r/checkbox.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "checkbox",
4 | "type": "registry:ui",
5 | "dependencies": [
6 | "@radix-ui/react-checkbox"
7 | ],
8 | "files": [
9 | {
10 | "path": "registry/ui/checkbox.tsx",
11 | "content": "\"use client\";\n\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { CheckIcon } from \"lucide-react\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Checkbox({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n \n \n \n \n );\n}\n\nexport { Checkbox };\n",
12 | "type": "registry:ui",
13 | "target": "components/ui/checkbox.tsx"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/apps/web/public/r/input.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "input",
4 | "type": "registry:ui",
5 | "files": [
6 | {
7 | "path": "registry/ui/input.tsx",
8 | "content": "import * as React from \"react\";\nimport { cn } from \"@/registry/lib/utils\";\n\nconst Input = React.forwardRef>(\n ({ className, type, ...props }, ref) => {\n return (\n \n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n",
9 | "type": "registry:ui",
10 | "target": "components/ui/input.tsx"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/apps/web/public/r/label.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "label",
4 | "type": "registry:ui",
5 | "dependencies": [
6 | "@radix-ui/react-label"
7 | ],
8 | "files": [
9 | {
10 | "path": "registry/ui/label.tsx",
11 | "content": "\"use client\";\n\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst labelVariants = cva(\n \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n);\n\nconst Label = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef &\n VariantProps\n>(({ className, ...props }, ref) => (\n \n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n",
12 | "type": "registry:ui",
13 | "target": "components/ui/label.tsx"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/apps/web/public/r/login-03.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "login-03",
4 | "type": "registry:block",
5 | "title": "Login 03",
6 | "description": "A simple login form.",
7 | "registryDependencies": [
8 | "https://uitopia.vercel.app/r/button.json",
9 | "https://uitopia.vercel.app/r/card.json",
10 | "https://uitopia.vercel.app/r/input.json",
11 | "https://uitopia.vercel.app/r/label.json"
12 | ],
13 | "files": [
14 | {
15 | "path": "registry/blocks/login-03/page.tsx",
16 | "content": "import { Login03 } from \"@/registry/blocks/login-03/components/login-form\";\n\nexport default function Page() {\n return (\n \n );\n}\n",
17 | "type": "registry:page",
18 | "target": "app/login-03/page.tsx"
19 | },
20 | {
21 | "path": "registry/blocks/login-03/components/login-form.tsx",
22 | "content": "import { cn } from \"@/lib/utils\";\nimport { Button } from \"@/registry/ui/button\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/registry/ui/card\";\nimport { Input } from \"@/registry/ui/input\";\nimport { Label } from \"@/registry/ui/label\";\n\nexport function Login03({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<\"div\">) {\n return (\n \n
\n \n Login \n \n Enter your email below to login to your account\n \n \n \n \n \n \n
\n );\n}\n",
23 | "type": "registry:component"
24 | }
25 | ],
26 | "meta": {
27 | "iframeHeight": "auto"
28 | },
29 | "categories": [
30 | "authentication"
31 | ]
32 | }
--------------------------------------------------------------------------------
/apps/web/public/r/paper-fold-demo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "paper-fold-demo",
4 | "type": "registry:component",
5 | "title": "Paper Fold",
6 | "description": "A paper folding animation.",
7 | "dependencies": [
8 | "@remixicon/react",
9 | "clsx",
10 | "tailwind-merge"
11 | ],
12 | "registryDependencies": [
13 | "https://uitopia.vercel.app/r/utils.json"
14 | ],
15 | "files": [
16 | {
17 | "path": "registry/components/paper-fold-demo.tsx",
18 | "content": "\"use client\";\n\nimport { RiTranslateAi } from \"@remixicon/react\";\n\nimport { cn } from \"@/registry/lib/utils\";\n\nconst StickerCard = ({\n icon: Icon,\n title,\n children,\n}: {\n icon: React.ElementType;\n title: string;\n children: React.ReactNode;\n}) => {\n return (\n \n );\n};\n\nconst cardData = [\n {\n icon: RiTranslateAi,\n title: \"Lost in translations\",\n description: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\",\n },\n //more data here\n];\n\nexport default function PaperFoldDemo() {\n return (\n \n {cardData.map((card, index) => (\n \n {card.description}\n \n ))}\n
\n );\n}\n",
19 | "type": "registry:component",
20 | "target": "components/paper-fold-demo.tsx"
21 | }
22 | ],
23 | "categories": [
24 | "react",
25 | "tailwindcss"
26 | ]
27 | }
--------------------------------------------------------------------------------
/apps/web/public/r/separator.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "separator",
4 | "type": "registry:ui",
5 | "dependencies": [
6 | "@radix-ui/react-separator"
7 | ],
8 | "files": [
9 | {
10 | "path": "registry/ui/separator.tsx",
11 | "content": "\"use client\";\n\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Separator = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(\n (\n { className, orientation = \"horizontal\", decorative = true, ...props },\n ref,\n ) => (\n \n ),\n);\nSeparator.displayName = SeparatorPrimitive.Root.displayName;\n\nexport { Separator };\n",
12 | "type": "registry:ui",
13 | "target": "components/ui/separator.tsx"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/apps/web/public/r/skeleton.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "skeleton",
4 | "type": "registry:ui",
5 | "files": [
6 | {
7 | "path": "registry/ui/skeleton.tsx",
8 | "content": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({\n className,\n ...props\n}: React.HTMLAttributes) {\n return (\n
\n );\n}\n\nexport { Skeleton };\n",
9 | "type": "registry:ui",
10 | "target": "components/ui/skeleton.tsx"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/apps/web/public/r/stack.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "stack",
4 | "type": "registry:ui",
5 | "dependencies": [
6 | "clsx",
7 | "tailwind-merge"
8 | ],
9 | "files": [
10 | {
11 | "path": "registry/ui/stack.tsx",
12 | "content": "import type { ComponentProps } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Direction = \"row\" | \"column\";\ntype ResponsiveDirection = {\n sm?: Direction;\n md?: Direction;\n};\n\ntype FlexAlignItems = \"stretch\" | \"start\" | \"end\" | \"center\";\ntype FlexJustifyContent =\n | \"stretch\"\n | \"start\"\n | \"end\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\"\n | \"center\";\n\ninterface StackProps extends ComponentProps<\"div\"> {\n children: React.ReactNode;\n direction?: ResponsiveDirection;\n gap?: number;\n padding?: number;\n grow?: boolean;\n shrink?: boolean;\n wrap?: boolean;\n align?: FlexAlignItems;\n justify?: FlexJustifyContent;\n className?: string;\n}\n\nexport function Stack({\n direction = { sm: \"column\", md: \"row\" },\n align = \"start\",\n justify = \"start\",\n wrap = false,\n shrink = false,\n grow = false,\n padding = 0,\n gap = 0,\n children,\n className,\n ...etc\n}: StackProps) {\n const directionClasses = [\n direction.sm === \"row\" ? \"flex-row\" : \"flex-col\",\n direction.md === \"row\" ? \"md:flex-row\" : \"md:flex-col\",\n ];\n\n const alignClasses = {\n stretch: \"items-stretch\",\n start: \"items-start\",\n end: \"items-end\",\n center: \"items-center\",\n }[align];\n\n const justifyClasses = {\n stretch: \"justify-stretch\",\n start: \"justify-start\",\n end: \"justify-end\",\n \"space-between\": \"justify-between\",\n \"space-around\": \"justify-around\",\n \"space-evenly\": \"justify-evenly\",\n center: \"justify-center\",\n }[justify];\n\n return (\n \n {children}\n
\n );\n}\n",
13 | "type": "registry:ui",
14 | "target": "components/ui/stack.tsx"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/apps/web/public/r/text-loop-demo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "text-loop-demo",
4 | "type": "registry:component",
5 | "title": "Text Loop",
6 | "description": "Text animation that transitions between multiple items, creating an engaging looping effect",
7 | "dependencies": [
8 | "clsx",
9 | "motion",
10 | "react",
11 | "tailwind-merge"
12 | ],
13 | "registryDependencies": [
14 | "https://uitopia.vercel.app/r/text-loop.json"
15 | ],
16 | "files": [
17 | {
18 | "path": "registry/components/text-loop-demo.tsx",
19 | "content": "\"use client\";\n\nimport TextLoop from \"@/registry/ui/text-loop\";\n\nexport default function TextLoopDemo() {\n return (\n \n Some days you're{\" \"}\n \n Healing \n Feeling \n Thriving \n Surviving \n \n \n );\n}\n",
20 | "type": "registry:component",
21 | "target": "components/text-loop-demo.tsx"
22 | }
23 | ],
24 | "categories": [
25 | "motion",
26 | "react",
27 | "tailwindcss"
28 | ]
29 | }
--------------------------------------------------------------------------------
/apps/web/public/r/text-loop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "text-loop",
4 | "type": "registry:component",
5 | "title": "Text Loop",
6 | "description": "text loop base",
7 | "dependencies": [
8 | "clsx",
9 | "motion",
10 | "react",
11 | "tailwind-merge"
12 | ],
13 | "files": [
14 | {
15 | "path": "registry/ui/text-loop.tsx",
16 | "content": "\"use client\";\nimport {\n AnimatePresence,\n motion,\n type Transition,\n type Variants,\n} from \"motion/react\";\nimport { Children, useEffect, useState } from \"react\";\nimport { cn } from \"@/registry/lib/utils\";\n\nexport type TextLoopProps = {\n children: React.ReactNode[];\n className?: string;\n interval?: number;\n transition?: Transition;\n variants?: Variants;\n onIndexChange?: (index: number) => void;\n};\n\nexport default function TextLoop({\n children,\n className,\n interval = 2,\n transition = { duration: 0.3 },\n variants,\n onIndexChange,\n}: TextLoopProps) {\n const [currentIndex, setCurrentIndex] = useState(0);\n const items = Children.toArray(children);\n\n useEffect(() => {\n const intervalMs = interval * 1000;\n\n const timer = setInterval(() => {\n setCurrentIndex((current) => {\n const next = (current + 1) % items.length;\n onIndexChange?.(next);\n return next;\n });\n }, intervalMs);\n return () => clearInterval(timer);\n }, [items.length, interval, onIndexChange]);\n\n const motionVariants: Variants = {\n initial: { y: 20, opacity: 0 },\n animate: { y: 0, opacity: 1 },\n exit: { y: -20, opacity: 0 },\n };\n\n return (\n \n
\n \n {items[currentIndex]}\n \n \n
\n );\n}\n",
17 | "type": "registry:ui",
18 | "target": "components/ui/text-loop.tsx"
19 | }
20 | ],
21 | "categories": [
22 | "motion",
23 | "react",
24 | "tailwindcss"
25 | ]
26 | }
--------------------------------------------------------------------------------
/apps/web/public/r/text-type-demo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "text-type-demo",
4 | "type": "registry:component",
5 | "title": "Typewriter",
6 | "description": "text type",
7 | "dependencies": [
8 | "clsx",
9 | "motion",
10 | "react",
11 | "tailwind-merge"
12 | ],
13 | "registryDependencies": [
14 | "https://uitopia.vercel.app/r/text-type.json"
15 | ],
16 | "files": [
17 | {
18 | "path": "registry/components/text-type-demo.tsx",
19 | "content": "\"use client\";\n\nimport TextType from \"@/registry/ui/text-type\";\n\nexport default function TextLoopDemo() {\n return (\n \n {\"Don't forget how \"} \n \n {\" you are.\"} \n \n );\n}\n",
20 | "type": "registry:component",
21 | "target": "components/text-type-demo.tsx"
22 | }
23 | ],
24 | "categories": [
25 | "motion",
26 | "react",
27 | "tailwindcss"
28 | ]
29 | }
--------------------------------------------------------------------------------
/apps/web/public/r/text-type.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "text-type",
4 | "type": "registry:component",
5 | "title": "Text Type",
6 | "description": "text type base",
7 | "dependencies": [
8 | "clsx",
9 | "motion",
10 | "react",
11 | "tailwind-merge"
12 | ],
13 | "files": [
14 | {
15 | "path": "registry/ui/text-type.tsx",
16 | "content": "\"use client\";\n\nimport { motion, type Variants } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\nimport { cn } from \"@/registry/lib/utils\";\n\nexport type TextTypeProps = {\n text: string | string[];\n speed?: number;\n initialDelay?: number;\n waitTime?: number;\n deleteSpeed?: number;\n loop?: boolean;\n className?: string;\n showCursor?: boolean;\n hideCursorOnType?: boolean;\n cursorChar?: string | React.ReactNode;\n cursorAnimationVariants?: {\n initial: Variants[\"initial\"];\n animate: Variants[\"animate\"];\n };\n cursorClassName?: string;\n};\n\nexport default function Typewriter({\n text,\n speed = 50,\n initialDelay = 0,\n waitTime = 2000,\n deleteSpeed = 30,\n loop = true,\n className,\n showCursor = true,\n hideCursorOnType = false,\n cursorChar = \"|\",\n cursorClassName = \"ml-0.5\",\n cursorAnimationVariants = {\n initial: { opacity: 0 },\n animate: {\n opacity: 1,\n transition: {\n duration: 0.01,\n repeat: Number.POSITIVE_INFINITY,\n repeatDelay: 0.4,\n repeatType: \"reverse\",\n },\n },\n },\n}: TextTypeProps) {\n const [displayText, setDisplayText] = useState(\"\");\n const [currentIndex, setCurrentIndex] = useState(0);\n const [isDeleting, setIsDeleting] = useState(false);\n const [currentTextIndex, setCurrentTextIndex] = useState(0);\n\n const texts = Array.isArray(text) ? text : [text];\n\n useEffect(() => {\n let timeout: NodeJS.Timeout;\n\n const currentText = texts[currentTextIndex];\n\n const startTyping = () => {\n if (isDeleting) {\n if (displayText === \"\") {\n setIsDeleting(false);\n if (currentTextIndex === texts.length - 1 && !loop) {\n return;\n }\n setCurrentTextIndex((prev) => (prev + 1) % texts.length);\n setCurrentIndex(0);\n timeout = setTimeout(() => {}, waitTime);\n } else {\n timeout = setTimeout(() => {\n setDisplayText((prev) => prev.slice(0, -1));\n }, deleteSpeed);\n }\n } else {\n if (currentText && currentIndex < currentText.length) {\n timeout = setTimeout(() => {\n setDisplayText((prev) => prev + currentText[currentIndex]);\n setCurrentIndex((prev) => prev + 1);\n }, speed);\n } else if (texts.length > 1) {\n timeout = setTimeout(() => {\n setIsDeleting(true);\n }, waitTime);\n }\n }\n };\n\n // Apply initial delay only at the start\n if (currentIndex === 0 && !isDeleting && displayText === \"\") {\n timeout = setTimeout(startTyping, initialDelay);\n } else {\n startTyping();\n }\n\n return () => clearTimeout(timeout);\n }, [\n currentIndex,\n displayText,\n isDeleting,\n speed,\n deleteSpeed,\n waitTime,\n texts,\n currentTextIndex,\n loop,\n initialDelay,\n ]);\n\n return (\n \n {displayText} \n {showCursor && (\n \n {cursorChar}\n \n )}\n
\n );\n}\n",
17 | "type": "registry:component",
18 | "target": "components/ui/text-type.tsx"
19 | }
20 | ],
21 | "categories": [
22 | "motion",
23 | "react",
24 | "tailwindcss"
25 | ]
26 | }
--------------------------------------------------------------------------------
/apps/web/public/r/tooltip.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "tooltip",
4 | "type": "registry:ui",
5 | "dependencies": [
6 | "@radix-ui/react-tooltip"
7 | ],
8 | "files": [
9 | {
10 | "path": "registry/ui/tooltip.tsx",
11 | "content": "\"use client\";\n\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nfunction Tooltip({\n ...props\n}: React.ComponentProps) {\n return (\n \n \n \n );\n}\n\nfunction TooltipTrigger({\n ...props\n}: React.ComponentProps) {\n return ;\n}\n\nfunction TooltipContent({\n className,\n sideOffset = 0,\n children,\n ...props\n}: React.ComponentProps) {\n return (\n \n \n {children}\n \n \n \n );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n",
12 | "type": "registry:ui",
13 | "target": "components/ui/tooltip.tsx"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/apps/web/public/r/use-debounce.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "use-debounce",
4 | "type": "registry:hook",
5 | "files": [
6 | {
7 | "path": "registry/hooks/use-debounce.ts",
8 | "content": "import { useEffect, useState } from \"react\";\n\nfunction useDebounce(value: T, delay = 500): T {\n const [debouncedValue, setDebouncedValue] = useState(value);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(timer);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n\nexport default useDebounce;\n",
9 | "type": "registry:hook"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/apps/web/public/r/use-mobile.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "use-mobile",
4 | "type": "registry:hook",
5 | "files": [
6 | {
7 | "path": "registry/hooks/use-mobile.ts",
8 | "content": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState(\n undefined,\n );\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n };\n mql.addEventListener(\"change\", onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return !!isMobile;\n}\n",
9 | "type": "registry:hook"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/apps/web/public/r/utils.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "utils",
4 | "type": "registry:lib",
5 | "dependencies": [
6 | "clsx",
7 | "tailwind-merge"
8 | ],
9 | "files": [
10 | {
11 | "path": "registry/lib/utils.ts",
12 | "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n",
13 | "type": "registry:lib",
14 | "target": "lib/utils.ts"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/apps/web/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/web/registry/blocks/login-03/components/login-form.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import { Button } from "@/registry/ui/button";
3 | import {
4 | Card,
5 | CardContent,
6 | CardDescription,
7 | CardHeader,
8 | CardTitle,
9 | } from "@/registry/ui/card";
10 | import { Input } from "@/registry/ui/input";
11 | import { Label } from "@/registry/ui/label";
12 |
13 | export function Login03({
14 | className,
15 | ...props
16 | }: React.ComponentPropsWithoutRef<"div">) {
17 | return (
18 |
19 |
20 |
21 | Login
22 |
23 | Enter your email below to login to your account
24 |
25 |
26 |
27 |
64 |
65 |
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/apps/web/registry/blocks/login-03/page.tsx:
--------------------------------------------------------------------------------
1 | import { Login03 } from "@/registry/blocks/login-03/components/login-form";
2 |
3 | export default function Page() {
4 | return (
5 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web/registry/blocks/login-04/page.tsx:
--------------------------------------------------------------------------------
1 | import { Login04 } from "@/registry/blocks/login-04/components/login-form";
2 |
3 | export default function Page() {
4 | return (
5 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web/registry/blocks/login-05/page.tsx:
--------------------------------------------------------------------------------
1 | import { Login05 } from "@/registry/blocks/login-05/components/login-form";
2 |
3 | export default function Page() {
4 | return (
5 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/apps/web/registry/components/button-color.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/registry/ui/button";
4 | import { Stack } from "@/registry/ui/stack";
5 |
6 | export default function ButtonColor() {
7 | return (
8 |
14 |
15 | Primary
16 |
17 |
18 |
19 | Primary
20 |
21 |
22 |
23 | Primary
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/apps/web/registry/components/button-destructive.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/registry/ui/button";
4 | import { Stack } from "@/registry/ui/stack";
5 |
6 | export default function ButtonDestructive() {
7 | return (
8 |
14 |
18 | Destructive
19 |
20 |
21 |
25 |
26 | Destructive
27 |
28 |
29 |
30 |
34 |
35 | Destructive
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/apps/web/registry/components/button-outline.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/registry/ui/button";
4 | import { Stack } from "@/registry/ui/stack";
5 |
6 | export default function ButtonOutline() {
7 | return (
8 |
14 |
18 | Outline
19 |
20 |
21 |
25 | Outline
26 |
27 |
28 |
32 | Outline
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/apps/web/registry/components/button-primary.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/registry/ui/button";
4 | import { Stack } from "@/registry/ui/stack";
5 |
6 | export default function ButtonPrimary() {
7 | return (
8 |
14 |
15 | Primary
16 |
17 |
18 |
19 |
20 | Primary
21 |
22 |
23 |
24 |
25 |
26 | Primary
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/apps/web/registry/components/paper-fold-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { RiTranslateAi } from "@remixicon/react";
4 |
5 | import { cn } from "@/registry/lib/utils";
6 |
7 | const StickerCard = ({
8 | icon: Icon,
9 | title,
10 | children,
11 | }: {
12 | icon: React.ElementType;
13 | title: string;
14 | children: React.ReactNode;
15 | }) => {
16 | return (
17 |
74 | );
75 | };
76 |
77 | const cardData = [
78 | {
79 | icon: RiTranslateAi,
80 | title: "Lost in translations",
81 | description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
82 | },
83 | //more data here
84 | ];
85 |
86 | export default function PaperFoldDemo() {
87 | return (
88 |
89 | {cardData.map((card, index) => (
90 |
91 | {card.description}
92 |
93 | ))}
94 |
95 | );
96 | }
97 |
--------------------------------------------------------------------------------
/apps/web/registry/components/text-loop-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import TextLoop from "@/registry/ui/text-loop";
4 |
5 | export default function TextLoopDemo() {
6 | return (
7 |
8 | Some days you're{" "}
9 |
39 | Healing
40 | Feeling
41 | Thriving
42 | Surviving
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/apps/web/registry/components/text-type-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import TextType from "@/registry/ui/text-type";
4 |
5 | export default function TextLoopDemo() {
6 | return (
7 |
8 | {"Don't forget how "}
9 |
30 | {" you are."}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/apps/web/registry/hooks/use-debounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | function useDebounce(value: T, delay = 500): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const timer = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, delay);
10 |
11 | return () => {
12 | clearTimeout(timer);
13 | };
14 | }, [value, delay]);
15 |
16 | return debouncedValue;
17 | }
18 |
19 | export default useDebounce;
20 |
--------------------------------------------------------------------------------
/apps/web/registry/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(
7 | undefined,
8 | );
9 |
10 | React.useEffect(() => {
11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12 | const onChange = () => {
13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14 | };
15 | mql.addEventListener("change", onChange);
16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17 | return () => mql.removeEventListener("change", onChange);
18 | }, []);
19 |
20 | return !!isMobile;
21 | }
22 |
--------------------------------------------------------------------------------
/apps/web/registry/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 | import * as React from "react";
4 |
5 | import { cn } from "@/registry/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90 ring-1 ring-inset ring-white/20 ring-offset-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | },
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | },
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ));
42 | CardTitle.displayName = "CardTitle";
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLDivElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ));
54 | CardDescription.displayName = "CardDescription";
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ));
62 | CardContent.displayName = "CardContent";
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ));
74 | CardFooter.displayName = "CardFooter";
75 |
76 | export {
77 | Card,
78 | CardHeader,
79 | CardFooter,
80 | CardTitle,
81 | CardDescription,
82 | CardContent,
83 | };
84 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4 | import { CheckIcon } from "lucide-react";
5 | import type * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | function Checkbox({
10 | className,
11 | ...props
12 | }: React.ComponentProps) {
13 | return (
14 |
22 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export { Checkbox };
33 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { Drawer as DrawerPrimitive } from "vaul";
5 |
6 | import { cn } from "@/registry/lib/utils";
7 |
8 | const Drawer = ({
9 | shouldScaleBackground = true,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
16 | );
17 | Drawer.displayName = "Drawer";
18 |
19 | const DrawerTrigger = DrawerPrimitive.Trigger;
20 |
21 | const DrawerPortal = DrawerPrimitive.Portal;
22 |
23 | const DrawerClose = DrawerPrimitive.Close;
24 |
25 | const DrawerOverlay = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
34 | ));
35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
36 |
37 | const DrawerContent = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, children, ...props }, ref) => (
41 |
42 |
43 |
51 |
52 | {children}
53 |
54 |
55 | ));
56 | DrawerContent.displayName = "DrawerContent";
57 |
58 | const DrawerHeader = ({
59 | className,
60 | ...props
61 | }: React.HTMLAttributes) => (
62 |
66 | );
67 | DrawerHeader.displayName = "DrawerHeader";
68 |
69 | const DrawerFooter = ({
70 | className,
71 | ...props
72 | }: React.HTMLAttributes) => (
73 |
77 | );
78 | DrawerFooter.displayName = "DrawerFooter";
79 |
80 | const DrawerTitle = React.forwardRef<
81 | React.ElementRef,
82 | React.ComponentPropsWithoutRef
83 | >(({ className, ...props }, ref) => (
84 |
92 | ));
93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
94 |
95 | const DrawerDescription = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, ...props }, ref) => (
99 |
104 | ));
105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
106 |
107 | export {
108 | Drawer,
109 | DrawerPortal,
110 | DrawerOverlay,
111 | DrawerTrigger,
112 | DrawerClose,
113 | DrawerContent,
114 | DrawerHeader,
115 | DrawerFooter,
116 | DrawerTitle,
117 | DrawerDescription,
118 | };
119 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cn } from "@/registry/lib/utils";
3 |
4 | const Input = React.forwardRef>(
5 | ({ className, type, ...props }, ref) => {
6 | return (
7 |
20 | );
21 | },
22 | );
23 | Input.displayName = "Input";
24 |
25 | export { Input };
26 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { cva, type VariantProps } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 |
26 | export { Label };
27 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref,
15 | ) => (
16 |
27 | ),
28 | );
29 | Separator.displayName = SeparatorPrimitive.Root.displayName;
30 |
31 | export { Separator };
32 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | );
13 | }
14 |
15 | export { Skeleton };
16 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/stack.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentProps } from "react";
2 | import { cn } from "@/lib/utils";
3 |
4 | type Direction = "row" | "column";
5 | type ResponsiveDirection = {
6 | sm?: Direction;
7 | md?: Direction;
8 | };
9 |
10 | type FlexAlignItems = "stretch" | "start" | "end" | "center";
11 | type FlexJustifyContent =
12 | | "stretch"
13 | | "start"
14 | | "end"
15 | | "space-between"
16 | | "space-around"
17 | | "space-evenly"
18 | | "center";
19 |
20 | interface StackProps extends ComponentProps<"div"> {
21 | children: React.ReactNode;
22 | direction?: ResponsiveDirection;
23 | gap?: number;
24 | padding?: number;
25 | grow?: boolean;
26 | shrink?: boolean;
27 | wrap?: boolean;
28 | align?: FlexAlignItems;
29 | justify?: FlexJustifyContent;
30 | className?: string;
31 | }
32 |
33 | export function Stack({
34 | direction = { sm: "column", md: "row" },
35 | align = "start",
36 | justify = "start",
37 | wrap = false,
38 | shrink = false,
39 | grow = false,
40 | padding = 0,
41 | gap = 0,
42 | children,
43 | className,
44 | ...etc
45 | }: StackProps) {
46 | const directionClasses = [
47 | direction.sm === "row" ? "flex-row" : "flex-col",
48 | direction.md === "row" ? "md:flex-row" : "md:flex-col",
49 | ];
50 |
51 | const alignClasses = {
52 | stretch: "items-stretch",
53 | start: "items-start",
54 | end: "items-end",
55 | center: "items-center",
56 | }[align];
57 |
58 | const justifyClasses = {
59 | stretch: "justify-stretch",
60 | start: "justify-start",
61 | end: "justify-end",
62 | "space-between": "justify-between",
63 | "space-around": "justify-around",
64 | "space-evenly": "justify-evenly",
65 | center: "justify-center",
66 | }[justify];
67 |
68 | return (
69 |
84 | {children}
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/text-loop.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | AnimatePresence,
4 | motion,
5 | type Transition,
6 | type Variants,
7 | } from "motion/react";
8 | import { Children, useEffect, useState } from "react";
9 | import { cn } from "@/registry/lib/utils";
10 |
11 | export type TextLoopProps = {
12 | children: React.ReactNode[];
13 | className?: string;
14 | interval?: number;
15 | transition?: Transition;
16 | variants?: Variants;
17 | onIndexChange?: (index: number) => void;
18 | };
19 |
20 | export default function TextLoop({
21 | children,
22 | className,
23 | interval = 2,
24 | transition = { duration: 0.3 },
25 | variants,
26 | onIndexChange,
27 | }: TextLoopProps) {
28 | const [currentIndex, setCurrentIndex] = useState(0);
29 | const items = Children.toArray(children);
30 |
31 | useEffect(() => {
32 | const intervalMs = interval * 1000;
33 |
34 | const timer = setInterval(() => {
35 | setCurrentIndex((current) => {
36 | const next = (current + 1) % items.length;
37 | onIndexChange?.(next);
38 | return next;
39 | });
40 | }, intervalMs);
41 | return () => clearInterval(timer);
42 | }, [items.length, interval, onIndexChange]);
43 |
44 | const motionVariants: Variants = {
45 | initial: { y: 20, opacity: 0 },
46 | animate: { y: 0, opacity: 1 },
47 | exit: { y: -20, opacity: 0 },
48 | };
49 |
50 | return (
51 |
52 |
53 |
61 | {items[currentIndex]}
62 |
63 |
64 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/text-morph.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { AnimatePresence, motion } from "motion/react";
3 | import { useId, useMemo } from "react";
4 | import { cn } from "@/registry/lib/utils";
5 |
6 | type TextMorphProps = {
7 | children: string;
8 | as?: React.ElementType;
9 | className?: string;
10 | style?: React.CSSProperties;
11 | };
12 |
13 | export function TextMorph({
14 | children,
15 | as: Component = "p",
16 | className,
17 | style,
18 | }: TextMorphProps) {
19 | const uniqueId = useId();
20 |
21 | const characters = useMemo(() => {
22 | const charCounts: Record = {};
23 |
24 | return children.split("").map((char, index) => {
25 | const lowerChar = char.toLowerCase();
26 | charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1;
27 |
28 | return {
29 | id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`,
30 | label: index === 0 ? char.toUpperCase() : lowerChar,
31 | };
32 | });
33 | }, [children, uniqueId]);
34 |
35 | return (
36 |
37 |
38 | {characters.map((character) => (
39 |
54 | {character.label}
55 |
56 | ))}
57 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/text-type.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { motion, type Variants } from "motion/react";
4 | import { useEffect, useState } from "react";
5 | import { cn } from "@/registry/lib/utils";
6 |
7 | export type TextTypeProps = {
8 | text: string | string[];
9 | speed?: number;
10 | initialDelay?: number;
11 | waitTime?: number;
12 | deleteSpeed?: number;
13 | loop?: boolean;
14 | className?: string;
15 | showCursor?: boolean;
16 | hideCursorOnType?: boolean;
17 | cursorChar?: string | React.ReactNode;
18 | cursorAnimationVariants?: {
19 | initial: Variants["initial"];
20 | animate: Variants["animate"];
21 | };
22 | cursorClassName?: string;
23 | };
24 |
25 | export default function Typewriter({
26 | text,
27 | speed = 50,
28 | initialDelay = 0,
29 | waitTime = 2000,
30 | deleteSpeed = 30,
31 | loop = true,
32 | className,
33 | showCursor = true,
34 | hideCursorOnType = false,
35 | cursorChar = "|",
36 | cursorClassName = "ml-0.5",
37 | cursorAnimationVariants = {
38 | initial: { opacity: 0 },
39 | animate: {
40 | opacity: 1,
41 | transition: {
42 | duration: 0.01,
43 | repeat: Number.POSITIVE_INFINITY,
44 | repeatDelay: 0.4,
45 | repeatType: "reverse",
46 | },
47 | },
48 | },
49 | }: TextTypeProps) {
50 | const [displayText, setDisplayText] = useState("");
51 | const [currentIndex, setCurrentIndex] = useState(0);
52 | const [isDeleting, setIsDeleting] = useState(false);
53 | const [currentTextIndex, setCurrentTextIndex] = useState(0);
54 |
55 | const texts = Array.isArray(text) ? text : [text];
56 |
57 | useEffect(() => {
58 | let timeout: NodeJS.Timeout;
59 |
60 | const currentText = texts[currentTextIndex];
61 |
62 | const startTyping = () => {
63 | if (isDeleting) {
64 | if (displayText === "") {
65 | setIsDeleting(false);
66 | if (currentTextIndex === texts.length - 1 && !loop) {
67 | return;
68 | }
69 | setCurrentTextIndex((prev) => (prev + 1) % texts.length);
70 | setCurrentIndex(0);
71 | timeout = setTimeout(() => {}, waitTime);
72 | } else {
73 | timeout = setTimeout(() => {
74 | setDisplayText((prev) => prev.slice(0, -1));
75 | }, deleteSpeed);
76 | }
77 | } else {
78 | if (currentText && currentIndex < currentText.length) {
79 | timeout = setTimeout(() => {
80 | setDisplayText((prev) => prev + currentText[currentIndex]);
81 | setCurrentIndex((prev) => prev + 1);
82 | }, speed);
83 | } else if (texts.length > 1) {
84 | timeout = setTimeout(() => {
85 | setIsDeleting(true);
86 | }, waitTime);
87 | }
88 | }
89 | };
90 |
91 | // Apply initial delay only at the start
92 | if (currentIndex === 0 && !isDeleting && displayText === "") {
93 | timeout = setTimeout(startTyping, initialDelay);
94 | } else {
95 | startTyping();
96 | }
97 |
98 | return () => clearTimeout(timeout);
99 | }, [
100 | currentIndex,
101 | displayText,
102 | isDeleting,
103 | speed,
104 | deleteSpeed,
105 | waitTime,
106 | texts,
107 | currentTextIndex,
108 | loop,
109 | initialDelay,
110 | ]);
111 |
112 | return (
113 |
114 | {displayText}
115 | {showCursor && (
116 |
129 | {cursorChar}
130 |
131 | )}
132 |
133 | );
134 | }
135 |
--------------------------------------------------------------------------------
/apps/web/registry/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4 | import type * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | );
19 | }
20 |
21 | function Tooltip({
22 | ...props
23 | }: React.ComponentProps) {
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | function TooltipTrigger({
32 | ...props
33 | }: React.ComponentProps) {
34 | return ;
35 | }
36 |
37 | function TooltipContent({
38 | className,
39 | sideOffset = 0,
40 | children,
41 | ...props
42 | }: React.ComponentProps) {
43 | return (
44 |
45 |
54 | {children}
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
62 |
--------------------------------------------------------------------------------
/apps/web/source.config.ts:
--------------------------------------------------------------------------------
1 | import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
2 | import { fileGenerator, remarkDocGen, remarkInstall } from "fumadocs-docgen";
3 | import {
4 | defineConfig,
5 | defineDocs,
6 | frontmatterSchema,
7 | } from "fumadocs-mdx/config";
8 | import { transformerTwoslash } from "fumadocs-twoslash";
9 |
10 | export const { docs, meta } = defineDocs({
11 | dir: "content/docs",
12 | docs: {
13 | schema: frontmatterSchema,
14 | },
15 | });
16 |
17 | export default defineConfig({
18 | lastModifiedTime: "git",
19 | mdxOptions: {
20 | rehypeCodeOptions: {
21 | inline: "tailing-curly-colon",
22 | themes: {
23 | light: "github-light",
24 | dark: "github-dark",
25 | },
26 | transformers: [
27 | ...(rehypeCodeDefaultOptions.transformers ?? []),
28 | transformerTwoslash(),
29 | {
30 | name: "transformers:remove-notation-escape",
31 | code(hast) {
32 | for (const line of hast.children) {
33 | if (line.type !== "element") continue;
34 |
35 | const lastSpan = line.children.findLast(
36 | (v) => v.type === "element",
37 | );
38 |
39 | const head = lastSpan?.children[0];
40 | if (head?.type !== "text") return;
41 |
42 | head.value = head.value.replace(/\[\\!code/g, "[!code");
43 | }
44 | },
45 | },
46 | ],
47 | },
48 | remarkPlugins: [
49 | [remarkInstall, { persist: { id: "package-manager" } }],
50 | [remarkDocGen, { generators: [fileGenerator()] }],
51 | ],
52 | },
53 | });
54 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@uitopia/tsconfig/nextjs.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["./*"],
7 | "@/uitopia/*": ["../../packages/ui/src/components/*"]
8 | },
9 | "plugins": [
10 | {
11 | "name": "next"
12 | }
13 | ],
14 | "target": "ES2017"
15 | },
16 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
17 | "exclude": ["node_modules"]
18 | }
19 |
--------------------------------------------------------------------------------
/apps/web/types/component.ts:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 |
3 | export interface ComponentLoaderProps {
4 | name: string;
5 | hasReTrigger?: boolean;
6 | classNameComponentContainer?: string;
7 | }
8 |
9 | export interface ComponentDisplayProps {
10 | component: ReactNode;
11 | hasReTrigger: boolean;
12 | className?: string;
13 | reTriggerKey: number;
14 | reTrigger: () => void;
15 | name: string;
16 | }
17 |
18 | export interface ComponentPreviewProps {
19 | name: string;
20 | hasReTrigger?: boolean;
21 | classNameComponentContainer?: string;
22 | }
23 |
24 | export interface ComponentSourceProps {
25 | name: string;
26 | expandButtonTitle?: string;
27 | defaultExpanded?: boolean;
28 | maxHeight?: string;
29 | className?: string;
30 | }
31 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/2.0.0-beta.3/schema.json",
3 | "vcs": {
4 | "enabled": false,
5 | "clientKind": "git",
6 | "useIgnoreFile": false
7 | },
8 | "files": {
9 | "ignoreUnknown": false,
10 | "includes": [
11 | "**",
12 | "!**/.next",
13 | "!**/.source",
14 | "!**/dist",
15 | "!**/node_modules",
16 | "!**/public"
17 | ]
18 | },
19 | "formatter": {
20 | "enabled": true,
21 | "formatWithErrors": false,
22 | "indentStyle": "space",
23 | "indentWidth": 2,
24 | "lineEnding": "lf",
25 | "lineWidth": 80,
26 | "attributePosition": "auto"
27 | },
28 | "assist": { "actions": { "source": { "organizeImports": "on" } } },
29 | "linter": {
30 | "enabled": true,
31 | "domains": {
32 | "next": "recommended"
33 | },
34 | "rules": {
35 | "recommended": true,
36 | "a11y": {
37 | "noSvgWithoutTitle": "off",
38 | "useKeyWithClickEvents": "off",
39 | "useButtonType": "off",
40 | "useAltText": "off",
41 | "useValidAnchor": "off"
42 | },
43 | "nursery": {
44 | "useSortedClasses": {
45 | "level": "error",
46 | "fix": "safe",
47 | "options": {}
48 | }
49 | },
50 | "style": {
51 | "noNonNullAssertion": "off",
52 | "useSelfClosingElements": "off",
53 | "useNodejsImportProtocol": "off",
54 | "useNumberNamespace": "off",
55 | "noParameterAssign": "error",
56 | "useAsConstAssertion": "error",
57 | "useDefaultParameterLast": "error",
58 | "useEnumInitializers": "error",
59 | "useConst": "error",
60 | "useSingleVarDeclarator": "error",
61 | "noUnusedTemplateLiteral": "error",
62 | "noInferrableTypes": "error",
63 | "noUselessElse": "error"
64 | },
65 | "correctness": {
66 | "useExhaustiveDependencies": "off"
67 | },
68 | "suspicious": {
69 | "noArrayIndexKey": "off",
70 | "noExplicitAny": "warn",
71 | "noRedeclare": "off"
72 | },
73 | "security": {
74 | "noDangerouslySetInnerHtml": "off"
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cahyawibawa/ui-topia/121ef6e0d07db0558849e06036235349ce7dc5ad/bun.lockb
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uitopia",
3 | "version": "1.0.0",
4 | "devDependencies": {
5 | "@biomejs/biome": "^2.0.0-beta.3",
6 | "@uitopia/tsconfig": "workspace:*",
7 | "turbo": "^2.5.0"
8 | },
9 | "engines": {
10 | "node": ">=20.14.0"
11 | },
12 | "license": "MIT",
13 | "packageManager": "bun@1.2.12",
14 | "private": true,
15 | "scripts": {
16 | "dev": "turbo run dev",
17 | "clean": "turbo run clean",
18 | "lint": "turbo run lint",
19 | "build": "turbo run build",
20 | "format": "biome format --write .",
21 | "check": "biome check --fix ."
22 | },
23 | "workspaces": [
24 | "apps/*",
25 | "packages/*"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/tsconfig/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "compilerOptions": {
5 | "esModuleInterop": true,
6 | "incremental": false,
7 | "isolatedModules": true,
8 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
9 | "module": "ESNext",
10 | "moduleDetection": "force",
11 | "moduleResolution": "Bundler",
12 | "noUncheckedIndexedAccess": true,
13 | "resolveJsonModule": true,
14 | "skipLibCheck": true,
15 | "strict": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/tsconfig/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "plugins": [{ "name": "next" }],
7 | "module": "ESNext",
8 | "moduleResolution": "Bundler",
9 | "allowJs": true,
10 | "jsx": "preserve",
11 | "noEmit": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/tsconfig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@uitopia/tsconfig",
3 | "version": "1.0.0",
4 | "private": true,
5 | "files": [
6 | "base.json",
7 | "nextjs.json",
8 | "react-library.json"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/tsconfig/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "react-jsx",
7 | "target": "ES6",
8 | "moduleResolution": "Node",
9 | "allowJs": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ui/topia",
3 | "version": "1.0.0",
4 | "description": "UI components for uitopia.vercel.app",
5 | "sideEffects": false,
6 | "main": "./dist/index.js",
7 | "module": "./dist/index.mjs",
8 | "types": "./dist/index.d.ts",
9 | "files": [
10 | "dist"
11 | ],
12 | "scripts": {
13 | "build": "tsup",
14 | "dev": "tsup --watch",
15 | "check-types": "tsc --noEmit",
16 | "shadcn": "bun x --bun shadcn@latest add"
17 | },
18 | "exports": {
19 | ".": {
20 | "types": "./dist/index.d.ts",
21 | "import": "./dist/index.mjs",
22 | "require": "./dist/index.js"
23 | }
24 | },
25 | "dependencies": {
26 | "@radix-ui/react-accordion": "^1.2.2",
27 | "@radix-ui/react-alert-dialog": "^1.1.4",
28 | "@radix-ui/react-checkbox": "^1.1.5",
29 | "@radix-ui/react-collapsible": "^1.1.0",
30 | "@radix-ui/react-dialog": "^1.1.5",
31 | "@radix-ui/react-dropdown-menu": "^2.1.4",
32 | "@radix-ui/react-icons": "^1.3.0",
33 | "@radix-ui/react-label": "^2.1.0",
34 | "@radix-ui/react-scroll-area": "^1.2.1",
35 | "@radix-ui/react-separator": "^1.1.3",
36 | "@radix-ui/react-slot": "^1.1.1",
37 | "@radix-ui/react-tabs": "^1.1.0",
38 | "@radix-ui/react-toggle-group": "^1.1.3",
39 | "@radix-ui/react-tooltip": "^1.1.4",
40 | "@remixicon/react": "^4.5.0",
41 | "class-variance-authority": "^0.7.0",
42 | "clsx": "^2.1.0",
43 | "lucide-react": "^0.468.0",
44 | "motion": "^11.15.0",
45 | "react": "^19.0.0",
46 | "react-dom": "^19.0.0",
47 | "react-icons": "^5.2.1",
48 | "react-resizable-panels": "^2.1.7",
49 | "tailwind-merge": "^2.2.0",
50 | "tailwindcss-animate": "^1.0.7",
51 | "usehooks-ts": "^3.1.0",
52 | "vaul": "^1.1.2",
53 | "zod": "^3.23.8"
54 | },
55 | "devDependencies": {
56 | "@tailwindcss/postcss": "^4.0.3",
57 | "@types/react": "^19.0.4",
58 | "@types/react-dom": "^19.0.2",
59 | "@uitopia/tsconfig": "workspace:*",
60 | "postcss": "^8.5.1",
61 | "tailwindcss": "^4.0.3",
62 | "tsup": "^8.3.6",
63 | "typescript": "^5.7.3"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | "@tailwindcss/postcss": {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/packages/ui/src/components/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AccordionPrimitive from "@radix-ui/react-accordion";
4 | import { ChevronDown } from "lucide-react";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Accordion = AccordionPrimitive.Root;
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ));
21 | AccordionItem.displayName = "AccordionItem";
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180",
32 | className,
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ));
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
52 | {children}
53 |
54 | ));
55 |
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName;
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
59 |
--------------------------------------------------------------------------------
/packages/ui/src/components/badge.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from "class-variance-authority";
2 | import type * as React from "react";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-normal transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | },
24 | );
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | );
34 | }
35 |
36 | export { Badge, badgeVariants };
37 |
--------------------------------------------------------------------------------
/packages/ui/src/components/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 | import * as React from "react";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90 ring-1 ring-inset ring-white/20 ring-offset-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | },
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | },
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/packages/ui/src/components/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ));
42 | CardTitle.displayName = "CardTitle";
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLDivElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ));
54 | CardDescription.displayName = "CardDescription";
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ));
62 | CardContent.displayName = "CardContent";
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ));
74 | CardFooter.displayName = "CardFooter";
75 |
76 | export {
77 | Card,
78 | CardHeader,
79 | CardFooter,
80 | CardTitle,
81 | CardDescription,
82 | CardContent,
83 | };
84 |
--------------------------------------------------------------------------------
/packages/ui/src/components/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4 | import { Check } from "lucide-react";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ));
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
29 |
30 | export { Checkbox };
31 |
--------------------------------------------------------------------------------
/packages/ui/src/components/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4 |
5 | const Collapsible = CollapsiblePrimitive.Root;
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
10 |
11 | export { Collapsible, CollapsibleContent, CollapsibleTrigger };
12 |
--------------------------------------------------------------------------------
/packages/ui/src/components/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cn } from "@/lib/utils";
3 |
4 | const Input = React.forwardRef>(
5 | ({ className, type, ...props }, ref) => {
6 | return (
7 |
20 | );
21 | },
22 | );
23 | Input.displayName = "Input";
24 |
25 | export { Input };
26 |
--------------------------------------------------------------------------------
/packages/ui/src/components/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { cva, type VariantProps } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 |
26 | export { Label };
27 |
--------------------------------------------------------------------------------
/packages/ui/src/components/page-header.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | function PageHeader({
4 | className,
5 | children,
6 | ...props
7 | }: React.HTMLAttributes) {
8 | return (
9 |
10 |
11 |
12 | {children}
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | function PageHeaderHeading({
20 | className,
21 | ...props
22 | }: React.HTMLAttributes) {
23 | return (
24 |
31 | );
32 | }
33 |
34 | function PageHeaderDescription({
35 | className,
36 | ...props
37 | }: React.HTMLAttributes) {
38 | return (
39 |
46 | );
47 | }
48 |
49 | function PageActions({
50 | className,
51 | ...props
52 | }: React.HTMLAttributes) {
53 | return (
54 |
61 | );
62 | }
63 |
64 | export { PageActions, PageHeader, PageHeaderDescription, PageHeaderHeading };
65 |
--------------------------------------------------------------------------------
/packages/ui/src/components/references.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import type React from "react";
3 | import { cn } from "@/lib/utils";
4 | import { ExternalLinkIcon } from "../../../../apps/web/registry/components/icons";
5 | import { badgeVariants } from "./badge";
6 |
7 | interface LinkItem {
8 | link: string;
9 | label: string;
10 | }
11 |
12 | export function References({ children }: React.PropsWithChildren) {
13 | return {children}
;
14 | }
15 |
16 | export function Reference({ label, link }: LinkItem) {
17 | return (
18 |
27 | {label}
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/packages/ui/src/components/resizable.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { GripVertical } from "lucide-react";
4 | import * as ResizablePrimitive from "react-resizable-panels";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const ResizablePanelGroup = ({
9 | className,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
19 | );
20 |
21 | const ResizablePanel = ResizablePrimitive.Panel;
22 |
23 | const ResizableHandle = ({
24 | withHandle,
25 | className,
26 | ...props
27 | }: React.ComponentProps & {
28 | withHandle?: boolean;
29 | }) => (
30 | div]:rotate-90",
33 | className,
34 | )}
35 | {...props}
36 | >
37 | {withHandle && (
38 |
39 |
40 |
41 | )}
42 |
43 | );
44 |
45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
46 |
--------------------------------------------------------------------------------
/packages/ui/src/components/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ));
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ));
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
47 |
48 | export { ScrollArea, ScrollBar };
49 |
--------------------------------------------------------------------------------
/packages/ui/src/components/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref,
15 | ) => (
16 |
27 | ),
28 | );
29 | Separator.displayName = SeparatorPrimitive.Root.displayName;
30 |
31 | export { Separator };
32 |
--------------------------------------------------------------------------------
/packages/ui/src/components/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | );
13 | }
14 |
15 | export { Skeleton };
16 |
--------------------------------------------------------------------------------
/packages/ui/src/components/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as TabsPrimitive from "@radix-ui/react-tabs";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Tabs = TabsPrimitive.Root;
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ));
23 | TabsList.displayName = TabsPrimitive.List.displayName;
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ));
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ));
53 | TabsContent.displayName = TabsPrimitive.Content.displayName;
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent };
56 |
--------------------------------------------------------------------------------
/packages/ui/src/components/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
4 | import type { VariantProps } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { toggleVariants } from "@/components/toggle";
8 | import { cn } from "@/lib/utils";
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | });
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ));
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext);
41 |
42 | return (
43 |
54 | {children}
55 |
56 | );
57 | });
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
60 |
61 | export { ToggleGroup, ToggleGroupItem };
62 |
--------------------------------------------------------------------------------
/packages/ui/src/components/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as TogglePrimitive from "@radix-ui/react-toggle";
4 | import { cva, type VariantProps } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-9 px-2 min-w-9",
20 | sm: "h-8 px-1.5 min-w-8",
21 | lg: "h-10 px-2.5 min-w-10",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | },
29 | );
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ));
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName;
44 |
45 | export { Toggle, toggleVariants };
46 |
--------------------------------------------------------------------------------
/packages/ui/src/components/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4 | import type * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | );
19 | }
20 |
21 | function Tooltip({
22 | ...props
23 | }: React.ComponentProps) {
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | function TooltipTrigger({
32 | ...props
33 | }: React.ComponentProps) {
34 | return ;
35 | }
36 |
37 | function TooltipContent({
38 | className,
39 | sideOffset = 0,
40 | children,
41 | ...props
42 | }: React.ComponentProps) {
43 | return (
44 |
45 |
54 | {children}
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
62 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(
7 | undefined,
8 | );
9 |
10 | React.useEffect(() => {
11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12 | const onChange = () => {
13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14 | };
15 | mql.addEventListener("change", onChange);
16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17 | return () => mql.removeEventListener("change", onChange);
18 | }, []);
19 |
20 | return !!isMobile;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | // base components shadcn/ui
2 | export * from "./components/accordion";
3 | export * from "./components/alert-dialog";
4 | export * from "./components/badge";
5 | export * from "./components/button";
6 | export * from "./components/card";
7 | export * from "./components/checkbox";
8 | export * from "./components/collapsible";
9 | export * from "./components/dialog";
10 | export * from "./components/dropdown-menu";
11 | export * from "./components/input";
12 | export * from "./components/label";
13 | // misc
14 | export * from "./components/page-header";
15 | export * from "./components/references";
16 | export * from "./components/resizable";
17 | export * from "./components/scroll-area";
18 | export * from "./components/separator";
19 | export * from "./components/sheet";
20 | export * from "./components/sidebar";
21 | export * from "./components/skeleton";
22 | export * from "./components/tabs";
23 | export * from "./components/toggle";
24 | export * from "./components/toggle-group";
25 | export * from "./components/tooltip";
26 |
--------------------------------------------------------------------------------
/packages/ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@uitopia/tsconfig/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "baseUrl": ".",
6 | "paths": {
7 | "@/*": ["./src/*"],
8 | "@/components/*": ["./src/components/*"],
9 | "@/examples/*": ["src/components/elements/texts/*"],
10 | "@/lib/*": ["./src/lib/*"]
11 | }
12 | },
13 | "exclude": ["dist", "node_modules"],
14 | "include": [".", "../../apps/web/registry/ui/sidebar.tsx"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/ui/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | entry: ["src/index.tsx"],
5 | format: ["cjs", "esm"],
6 | dts: true,
7 | splitting: false,
8 | sourcemap: true,
9 | clean: true,
10 | external: ["react", "react-dom"],
11 | esbuildOptions(options) {
12 | options.resolveExtensions = [".tsx", ".ts", ".jsx", ".js"];
13 | options.alias = {
14 | "@": "./src",
15 | "@/components": "./src/components",
16 | "@/lib": "./src/lib",
17 | "@/hooks": "./src/hooks",
18 | };
19 | options.banner = {
20 | js: '"use client"',
21 | };
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@uitopia/tsconfig/base.json"
3 | }
4 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
8 | "outputs": ["dist/**", ".next/**", "!.next/cache/**", ".source/**"]
9 | },
10 | "lint": {
11 | "dependsOn": ["^lint"]
12 | },
13 | "dev": {
14 | "cache": false,
15 | "persistent": true
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------