87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className,
93 | )}
94 | {...props}
95 | />
96 | ));
97 | TableCell.displayName = "TableCell";
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ));
109 | TableCaption.displayName = "TableCaption";
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | };
121 |
--------------------------------------------------------------------------------
/apps/extension/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TabsPrimitive from "@radix-ui/react-tabs";
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 |
--------------------------------------------------------------------------------
/apps/extension/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider;
9 |
10 | const Tooltip = TooltipPrimitive.Root;
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger;
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ));
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
31 |
--------------------------------------------------------------------------------
/apps/extension/src/config/docs-config.ts:
--------------------------------------------------------------------------------
1 | export type DocsConfig = {
2 | title: string;
3 | path?: string;
4 | pages?: DocsConfig[];
5 | };
6 |
7 | export const Pages: DocsConfig[] = [
8 | {
9 | title: "Docs",
10 | path: "/docs/introduction",
11 | },
12 | {
13 | title: "Components",
14 | path: "/components",
15 | },
16 | ];
17 |
18 | export const docsConfig: DocsConfig[] = [
19 | {
20 | title: "Getting Started",
21 | pages: [
22 | {
23 | title: "Introduction",
24 | path: "/docs/introduction",
25 | },
26 | {
27 | title: "Installation",
28 | path: "/docs/installation",
29 | },
30 | {
31 | title: "Changelog",
32 | path: "/docs/changelog",
33 | },
34 | ],
35 | },
36 | {
37 | title: "Components",
38 | pages: [
39 | {
40 | title: "Tree view",
41 | path: "/docs/tree-view",
42 | },
43 | {
44 | title: "Carousel",
45 | path: "/docs/carousel",
46 | },
47 | {
48 | title: "Multi select",
49 | path: "/docs/multi-select",
50 | },
51 | {
52 | title: "Breadcrumb",
53 | path: "/docs/breadcrumb",
54 | },
55 | {
56 | title: "Otp input",
57 | path: "/docs/otp-input",
58 | },
59 | {
60 | title: "Smart DateTime input",
61 | path: "/docs/smart-datetime-input",
62 | },
63 | {
64 | title: "Datetime picker",
65 | path: "/docs/datetime-picker",
66 | },
67 | {
68 | title: "Tags Input",
69 | path: "/docs/tags-input",
70 | },
71 | {
72 | title: "File upload",
73 | path: "/docs/file-upload",
74 | },
75 | ],
76 | },
77 | ];
78 |
--------------------------------------------------------------------------------
/apps/extension/src/config/site-config.ts:
--------------------------------------------------------------------------------
1 | export const siteConfig = {
2 | name: "Shadcn Extension",
3 | url: "https://shadcn-extension-landing.vercel.app/",
4 | ogImage: "https://shadcn-extension-landing.vercel.app/og.png",
5 | description:
6 | "Discover new possibilities with the extended Shadcn UI library. More components, more layouts, more creativity.",
7 | links: {
8 | twitter: "https://twitter.com/BylkaYf",
9 | github: "https://git.new/extension",
10 | docs: "/docs/introduction",
11 | components: "/components",
12 | },
13 | };
14 |
15 | export type SiteConfig = typeof siteConfig;
16 |
--------------------------------------------------------------------------------
/apps/extension/src/env.ts:
--------------------------------------------------------------------------------
1 | import { createEnv } from "@t3-oss/env-nextjs";
2 | import { z } from "zod";
3 |
4 | export const env = createEnv({
5 | client: {
6 | NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1),
7 | NEXT_PUBLIC_POSTHOG_HOST: z.string().min(1),
8 | },
9 | experimental__runtimeEnv: {
10 | NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
11 | NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/apps/extension/src/hooks/use-active-section.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const useActiveSection = (items: string[]) => {
4 | const [activeId, setActiveId] = React.useState(null);
5 |
6 | React.useEffect(() => {
7 | const observer = new IntersectionObserver(
8 | (entries) => {
9 | entries.forEach((entry) => {
10 | if (entry.isIntersecting) {
11 | setActiveId(entry.target.id);
12 | }
13 | });
14 | },
15 | { rootMargin: `0% 0% -80% 0%` },
16 | );
17 |
18 | items?.forEach((id) => {
19 | const element = document.getElementById(id);
20 | if (element) {
21 | observer.observe(element);
22 | }
23 | });
24 |
25 | return () => {
26 | items?.forEach((id) => {
27 | const element = document.getElementById(id);
28 | if (element) {
29 | observer.unobserve(element);
30 | }
31 | });
32 | };
33 | }, [items]);
34 |
35 | return activeId ?? undefined;
36 | };
37 |
--------------------------------------------------------------------------------
/apps/extension/src/hooks/use-debounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export function useDebounce(value: T, delay?: number): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const timer = setTimeout(() => setDebouncedValue(value), delay ?? 500);
8 |
9 | return () => clearTimeout(timer);
10 | }, [value, delay]);
11 |
12 | return debouncedValue;
13 | }
14 |
--------------------------------------------------------------------------------
/apps/extension/src/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | export function useMediaQuery(query: string) {
4 | const [value, setValue] = React.useState(true);
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/extension/src/hooks/use-mounted.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export const useMounted = () => {
4 | const [mounted, setMounted] = useState(false);
5 | useEffect(() => setMounted(true), []);
6 | return mounted;
7 | };
8 |
--------------------------------------------------------------------------------
/apps/extension/src/lib/element-parser.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | interface Dependency {
4 | importedItems: string[];
5 | modulePath: string;
6 | }
7 |
8 | function extractDependencies(fileContent: string): Dependency[] {
9 | const regex = /import\s+\{(.*?)\}\s+from\s+"(.*?)"/gs;
10 | const dependencies: Dependency[] = [];
11 |
12 | let match: RegExpExecArray | null;
13 | while ((match = regex.exec(fileContent)) !== null) {
14 | const importedItems = match[1]
15 | .split(",")
16 | .map((item) => item.trim())
17 | .filter((item) => item !== "");
18 | const modulePath = match[2];
19 |
20 | dependencies.push({
21 | importedItems,
22 | modulePath,
23 | });
24 | }
25 |
26 | return dependencies;
27 | }
28 |
29 | function extractComponentContent(fileContent: string): string {
30 | // Remove the "use client" line
31 | const contentWithoutUseClient = fileContent.replace(
32 | /"use client"\s*;\s*/,
33 | "",
34 | );
35 |
36 | // Extract the component content
37 | const componentRegex =
38 | /const\s+(\w+)\s*=\s*\(\s*\)\s*=>\s*{([\s\S]*?)}\s*;\s*export\s+default\s+\w+\s*;/g;
39 | let match = componentRegex.exec(contentWithoutUseClient);
40 | if (match !== null) {
41 | return (
42 | "const " +
43 | match[1] +
44 | " = () => {" +
45 | match[2] +
46 | "};\n\nrender(<" +
47 | match[1] +
48 | " />)"
49 | );
50 | }
51 |
52 | return "";
53 | }
54 |
55 | export const readFieContent = (name: string) => {
56 | const correctName = name.toLowerCase().split(" ").join("-") + "-demo";
57 | const filePath = `src/registry/default/example/${correctName}.tsx`;
58 | const file = fs.readFileSync(filePath, "utf-8");
59 | return file;
60 | };
61 |
62 | export const getComponentDependencies = (name: string) => {
63 | const fileContent = readFieContent(name);
64 | const dependencies = extractDependencies(fileContent);
65 |
66 | return dependencies;
67 | };
68 |
69 | export const getComponentContent = (name: string) => {
70 | const fileContent = readFieContent(name);
71 | const componentContent = extractComponentContent(fileContent);
72 | return componentContent;
73 | };
74 |
--------------------------------------------------------------------------------
/apps/extension/src/lib/events.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const eventSchema = z.object({
4 | name: z.enum([
5 | "copy_npm_command",
6 | "copy_usage_import_code",
7 | "copy_usage_code",
8 | "copy_primitive_code",
9 | "copy_theme_code",
10 | ]),
11 | // declare type AllowedPropertyValues = string | number | boolean | null
12 | properties: z
13 | .record(z.union([z.string(), z.number(), z.boolean(), z.null()]))
14 | .optional(),
15 | });
16 |
17 | export type Event = z.infer;
18 |
--------------------------------------------------------------------------------
/apps/extension/src/lib/rehype-installation-command.ts:
--------------------------------------------------------------------------------
1 | import { UnistNode, UnistTree } from "../types/unist";
2 | import { visit } from "unist-util-visit";
3 |
4 | export function rehypeNpmCommand() {
5 | return (tree: UnistTree) => {
6 | visit(tree, (node: UnistNode) => {
7 | if (node.type !== "element" || node?.tagName !== "pre") {
8 | return;
9 | }
10 |
11 | // npm install.
12 | if (node.properties?.["__rawString__"]?.startsWith("npm install")) {
13 | const npmCommand = node.properties?.["__rawString__"];
14 | node.properties["__npmCommand__"] = npmCommand;
15 | node.properties["__yarnCommand__"] = npmCommand.replace(
16 | "npm install",
17 | "yarn add",
18 | );
19 | node.properties["__pnpmCommand__"] = npmCommand.replace(
20 | "npm install",
21 | "pnpm add",
22 | );
23 | node.properties["__bunCommand__"] = npmCommand.replace(
24 | "npm install",
25 | "bun add",
26 | );
27 | }
28 |
29 | // npx create.
30 | if (node.properties?.["__rawString__"]?.startsWith("npx create-")) {
31 | const npmCommand = node.properties?.["__rawString__"];
32 | node.properties["__npmCommand__"] = npmCommand;
33 | node.properties["__yarnCommand__"] = npmCommand.replace(
34 | "npx create-",
35 | "yarn create ",
36 | );
37 | node.properties["__pnpmCommand__"] = npmCommand.replace(
38 | "npx create-",
39 | "pnpm create ",
40 | );
41 | node.properties["__bunCommand__"] = npmCommand.replace(
42 | "npx",
43 | "bunx --bun",
44 | );
45 | }
46 |
47 | // npx.
48 | if (
49 | node.properties?.["__rawString__"]?.startsWith("npx") &&
50 | !node.properties?.["__rawString__"]?.startsWith("npx create-")
51 | ) {
52 | const npmCommand = node.properties?.["__rawString__"];
53 | node.properties["__npmCommand__"] = npmCommand;
54 | node.properties["__yarnCommand__"] = npmCommand;
55 | node.properties["__pnpmCommand__"] = npmCommand.replace(
56 | "npx",
57 | "pnpm dlx",
58 | );
59 | node.properties["__bunCommand__"] = npmCommand.replace(
60 | "npx",
61 | "bunx --bun",
62 | );
63 | }
64 | });
65 | };
66 | }
67 |
--------------------------------------------------------------------------------
/apps/extension/src/lib/toc.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | // TODO: I'll fix this later.
3 |
4 | import { toc } from "mdast-util-toc";
5 | import { remark } from "remark";
6 | import { visit } from "unist-util-visit";
7 | import { TreeViewElement } from "@/registry/default/extension/tree-view-api";
8 |
9 | const textTypes = ["text", "emphasis", "strong", "inlineCode"];
10 |
11 | function flattenNode(node) {
12 | const p = [];
13 | visit(node, (node) => {
14 | if (!textTypes.includes(node.type)) return;
15 | p.push(node.value);
16 | });
17 | return p.join(``);
18 | }
19 |
20 | function getItems(node, current): TreeViewElement[] {
21 | if (!node) {
22 | return {};
23 | }
24 |
25 | if (node.type === "paragraph") {
26 | visit(node, (item) => {
27 | if (item.type === "link") {
28 | current.id = item.url;
29 | current.name = flattenNode(node);
30 | current.isSelectable = true;
31 | }
32 |
33 | if (item.type === "text") {
34 | current.name = flattenNode(node);
35 | current.isSelectable = true;
36 | }
37 | });
38 |
39 | return current;
40 | }
41 |
42 | if (node.type === "list") {
43 | current.children = node.children.map((i) => getItems(i, {}));
44 |
45 | return current;
46 | } else if (node.type === "listItem") {
47 | const heading = getItems(node.children[0], {});
48 |
49 | if (node.children.length > 1) {
50 | getItems(node.children[1], heading);
51 | }
52 |
53 | return heading;
54 | }
55 |
56 | return {};
57 | }
58 |
59 | const getToc = () => (node, file) => {
60 | const table = toc(node);
61 | const items = getItems(table.map, {});
62 |
63 | file.data = items;
64 | };
65 |
66 | export type TableOfContents = TreeViewElement;
67 |
68 | export async function getTableOfContents(
69 | content: string,
70 | ): Promise {
71 | const result = await remark().use(getToc).process(content);
72 |
73 | return result.data;
74 | }
75 |
--------------------------------------------------------------------------------
/apps/extension/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 |
8 | export const isMacOs = () => {
9 | if (typeof window === "undefined") return false;
10 | return window.navigator.userAgent.includes("Mac");
11 | };
12 |
13 | export function formatDate(input: string | number): string {
14 | const date = new Date(input);
15 | return date.toLocaleDateString("en-US", {
16 | month: "long",
17 | day: "numeric",
18 | year: "numeric",
19 | });
20 | }
21 |
22 | export const EXCLUDED_FILES = [
23 | "/public/index.html",
24 | "/package.json",
25 | "/styles.css",
26 | "/tsconfig.json",
27 | ];
28 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/breadcrumb-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | BreadCrumb,
5 | BreadCrumbItem,
6 | BreadCrumbSeparator,
7 | } from "@/registry/default/extension/breadcrumb";
8 | import Link from "next/link";
9 |
10 | const BreadCrumbTest = () => {
11 | return (
12 |
17 |
18 | Home
19 |
20 |
21 |
22 | Settings
23 |
24 |
25 |
26 | Account
27 |
28 |
29 | );
30 | };
31 |
32 | export default BreadCrumbTest;
33 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/breadcrumb/breadcrumb-active.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | BreadCrumb,
5 | BreadCrumbItem,
6 | BreadCrumbSeparator,
7 | } from "@/registry/default/extension/breadcrumb";
8 | import Link from "next/link";
9 | import { useSearchParams } from "next/navigation";
10 |
11 | const Pages = [
12 | {
13 | title: "Home",
14 | path: "home",
15 | },
16 | {
17 | title: "Settings",
18 | path: "settings",
19 | },
20 | {
21 | title: "Account",
22 | path: "account",
23 | },
24 | ];
25 |
26 | const BreadCrumbTest = () => {
27 | const searchParams = useSearchParams();
28 | const path = searchParams.get("path");
29 | return (
30 |
35 | {Pages.map((page, index) => {
36 | const isActive = path === page.path;
37 | return (
38 |
39 |
47 |
48 | {page.title}
49 |
50 |
51 | {index !== Pages.length - 1 && }
52 |
53 | );
54 | })}
55 |
56 | );
57 | };
58 |
59 | export default BreadCrumbTest;
60 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/breadcrumb/breadcrumb-orientation.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BreadCrumb,
3 | BreadCrumbItem,
4 | BreadCrumbSeparator,
5 | } from "@/registry/default/extension/breadcrumb";
6 | import Link from "next/link";
7 |
8 | const Pages = [
9 | {
10 | title: "Home",
11 | path: "home",
12 | },
13 | {
14 | title: "Settings",
15 | path: "settings",
16 | },
17 | {
18 | title: "Account",
19 | path: "account",
20 | },
21 | ];
22 |
23 | const BreadcrumbOrientation = () => {
24 | return (
25 |
30 | {Pages.map((page, index) => {
31 | return (
32 |
36 |
37 |
38 | {page.title}
39 |
40 |
41 | {index !== Pages.length - 1 && }
42 |
43 | );
44 | })}
45 |
46 | );
47 | };
48 |
49 | export default BreadcrumbOrientation;
50 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/breadcrumb/breadcrumb-popover.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BreadCrumb,
3 | BreadCrumbItem,
4 | BreadCrumbSeparator,
5 | BreadCrumbPopover,
6 | BreadCrumbTrigger,
7 | BreadCrumbContent,
8 | BreadCrumbEllipsis,
9 | } from "@/registry/default/extension/breadcrumb";
10 | import Link from "next/link";
11 |
12 | const BreadCrumbTest = () => {
13 | return (
14 |
19 |
20 | Home
21 |
22 |
23 |
24 | Dashboard
25 |
26 |
27 |
28 |
29 |
33 | open rest links
34 |
35 |
36 |
37 | Settings
38 |
39 |
40 | Account
41 |
42 |
43 |
44 |
45 |
46 | Payments
47 |
48 |
49 | );
50 | };
51 |
52 | export default BreadCrumbTest;
53 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/breadcrumb/breadcrumb-separator.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BreadCrumb,
3 | BreadCrumbItem,
4 | BreadCrumbSeparator,
5 | } from "@/registry/default/extension/breadcrumb";
6 | import { Slash } from "lucide-react";
7 | import Link from "next/link";
8 |
9 | const BreadCrumbTest = () => {
10 | return (
11 |
16 |
17 | Home
18 |
19 |
20 |
21 |
22 |
23 | Settings
24 |
25 |
26 |
27 |
28 |
29 | Account
30 |
31 |
32 | );
33 | };
34 |
35 | export default BreadCrumbTest;
36 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/breadcrumb/breadcrumb-variants.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Select,
5 | SelectContent,
6 | SelectItem,
7 | SelectTrigger,
8 | SelectValue,
9 | } from "@/components/ui/select";
10 | import {
11 | BreadCrumb,
12 | BreadCrumbItem,
13 | BreadCrumbSeparator,
14 | } from "@/registry/default/extension/breadcrumb";
15 | import Link from "next/link";
16 | import { useState } from "react";
17 |
18 | const OPTIONS = ["ghost", "outline", "link", "default", "destructive"];
19 |
20 | const BreadCrumbVariantPicker = ({
21 | variant,
22 | setVariant,
23 | }: {
24 | variant: string;
25 | setVariant: (variant: string) => void;
26 | }) => {
27 | return (
28 |
29 |
47 |
48 | );
49 | };
50 |
51 | const BreadCrumbTest = () => {
52 | const [variant, setVariant] = useState("ghost");
53 | return (
54 | <>
55 |
60 |
61 | Home
62 |
63 |
64 |
65 | Settings
66 |
67 |
68 |
69 | Account
70 |
71 |
72 |
73 | >
74 | );
75 | };
76 |
77 | export default BreadCrumbTest;
78 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/carousel-demo.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Carousel,
3 | CarouselMainContainer,
4 | CarouselThumbsContainer,
5 | SliderMainItem,
6 | SliderThumbItem,
7 | } from "@/registry/default/extension/carousel";
8 |
9 | const CarouselExample = () => {
10 | return (
11 |
12 |
13 |
14 | {Array.from({ length: 10 }).map((_, index) => (
15 |
19 | Slide {index + 1}
20 |
21 | ))}
22 |
23 |
24 |
25 | {Array.from({ length: 10 }).map((_, index) => (
26 |
31 |
32 | Slide {index + 1}
33 |
34 |
35 | ))}
36 |
37 |
38 | );
39 | };
40 |
41 | export default CarouselExample;
42 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/carousel/carousel-indicator.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Carousel,
3 | CarouselIndicator,
4 | CarouselMainContainer,
5 | CarouselNext,
6 | CarouselPrevious,
7 | CarouselThumbsContainer,
8 | SliderMainItem,
9 | } from "@/registry/default/extension/carousel";
10 |
11 | const CarouselIndicatorExample = () => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | {Array.from({ length: 5 }).map((_, index) => (
19 |
20 |
21 | Slide {index + 1}
22 |
23 |
24 | ))}
25 |
26 |
27 |
28 | {Array.from({ length: 5 }).map((_, index) => (
29 |
30 | ))}
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default CarouselIndicatorExample;
39 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/carousel/carousel-orientation.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Carousel,
3 | CarouselMainContainer,
4 | CarouselNext,
5 | CarouselPrevious,
6 | SliderMainItem,
7 | CarouselThumbsContainer,
8 | SliderThumbItem,
9 | } from "@/registry/default/extension/carousel";
10 |
11 | const CarouselOrientation = () => {
12 | return (
13 |
14 |
15 |
16 |
17 | {Array.from({ length: 5 }).map((_, index) => (
18 |
19 |
20 | Slide {index + 1}
21 |
22 |
23 | ))}
24 |
25 |
26 | {Array.from({ length: 5 }).map((_, index) => (
27 |
28 |
29 | Slide {index + 1}
30 | {" "}
31 |
32 | ))}
33 |
34 |
35 | );
36 | };
37 |
38 | export default CarouselOrientation;
39 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/carousel/carousel-plugin.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Carousel,
3 | CarouselMainContainer,
4 | SliderMainItem,
5 | } from "@/registry/default/extension/carousel";
6 | import AutoScroll from "embla-carousel-auto-scroll";
7 |
8 | const CarouselOrientation = () => {
9 | return (
10 |
20 |
21 | {Array.from({ length: 5 }).map((_, index) => (
22 |
23 |
24 | Slide {index + 1}
25 |
26 |
27 | ))}
28 |
29 |
30 | );
31 | };
32 |
33 | export default CarouselOrientation;
34 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/carousel/carousel-rtl-support.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Select,
5 | SelectContent,
6 | SelectItem,
7 | SelectTrigger,
8 | SelectValue,
9 | } from "@/components/ui/select";
10 | import {
11 | Carousel,
12 | CarouselMainContainer,
13 | CarouselNext,
14 | CarouselPrevious,
15 | SliderMainItem,
16 | CarouselThumbsContainer,
17 | SliderThumbItem,
18 | CarouselIndicator,
19 | } from "@/registry/default/extension/carousel";
20 | import { useState } from "react";
21 |
22 | const CarouselOrientation = () => {
23 | const [direction, setDirection] = useState("ltr");
24 | return (
25 | <>
26 |
27 |
28 |
29 |
30 | {Array.from({ length: 5 }).map((_, index) => (
31 |
32 |
33 | Slide {index + 1}
34 |
35 |
36 | ))}
37 |
38 |
39 |
40 | {Array.from({ length: 5 }).map((_, index) => (
41 |
42 | ))}
43 |
44 |
45 |
46 |
47 | >
48 | );
49 | };
50 |
51 | export default CarouselOrientation;
52 |
53 | type DirectionType = "rtl" | "ltr";
54 |
55 | const OPTIONS = ["rtl", "ltr"];
56 |
57 | const SelectDirection = ({
58 | direction,
59 | setDirection,
60 | }: {
61 | direction: DirectionType;
62 | setDirection: (direction: DirectionType) => void;
63 | }) => {
64 | return (
65 |
66 |
88 |
89 | );
90 | };
91 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/datetime-picker-demo.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DatetimePicker } from "../extension/datetime-picker";
3 |
4 | export const DatetimePickerDemo = () => {
5 | return (
6 |
12 | );
13 | };
14 |
15 | export default DatetimePickerDemo;
16 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/datetime-picker/datetime-picker-zod.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { useForm, Controller } from "react-hook-form";
5 | import z from "zod";
6 | import { zodResolver } from "@hookform/resolvers/zod";
7 | import { toast } from "sonner";
8 | import { Calendar } from "lucide-react";
9 |
10 | import {
11 | Form,
12 | FormControl,
13 | FormField,
14 | FormItem,
15 | FormLabel,
16 | FormMessage,
17 | } from "@/components/ui/form";
18 | import { Button } from "@/components/ui/button";
19 | import { DatetimePicker } from "../../extension/datetime-picker";
20 |
21 | const formSchema = z.object({
22 | datetime: z.date().optional(),
23 | });
24 |
25 | type Form = z.infer;
26 |
27 | const DateTimePickerZod = () => {
28 | const [_, setFormData] = useState
97 |
98 | );
99 | };
100 |
101 | export default DateTimePickerZod;
102 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/file-upload-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import {
5 | FileUploader,
6 | FileUploaderContent,
7 | FileUploaderItem,
8 | FileInput,
9 | } from "@/registry/default/extension/file-upload";
10 | import { Paperclip } from "lucide-react";
11 |
12 | const FileSvgDraw = () => {
13 | return (
14 | <>
15 |
30 |
31 | Click to upload
32 | or drag and drop
33 |
34 |
35 | SVG, PNG, JPG or GIF
36 |
37 | >
38 | );
39 | };
40 |
41 | const FileUploaderTest = () => {
42 | const [files, setFiles] = useState(null);
43 |
44 | const dropZoneConfig = {
45 | maxFiles: 5,
46 | maxSize: 1024 * 1024 * 4,
47 | multiple: true,
48 | };
49 |
50 | return (
51 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {files &&
64 | files.length > 0 &&
65 | files.map((file, i) => (
66 |
67 |
68 | {file.name}
69 |
70 | ))}
71 |
72 |
73 | );
74 | };
75 |
76 | export default FileUploaderTest;
77 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/file-upload/file-upload-dropzone.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | FileUploader,
5 | FileInput,
6 | FileUploaderContent,
7 | FileUploaderItem,
8 | } from "@/registry/default/extension/file-upload";
9 | import Image from "next/image";
10 | import { useState } from "react";
11 | import { DropzoneOptions } from "react-dropzone";
12 |
13 | const FileUploadDropzone = () => {
14 | const [files, setFiles] = useState([]);
15 |
16 | const dropzone = {
17 | accept: {
18 | "image/*": [".jpg", ".jpeg", ".png"],
19 | },
20 | multiple: true,
21 | maxFiles: 4,
22 | maxSize: 1 * 1024 * 1024,
23 | } satisfies DropzoneOptions;
24 |
25 | return (
26 |
31 |
32 |
33 | Drop files here
34 |
35 |
36 |
37 | {files?.map((file, i) => (
38 |
44 |
51 |
52 | ))}
53 |
54 |
55 | );
56 | };
57 |
58 | export default FileUploadDropzone;
59 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/multi-select-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState } from "react";
3 | import {
4 | MultiSelector,
5 | MultiSelectorTrigger,
6 | MultiSelectorInput,
7 | MultiSelectorContent,
8 | MultiSelectorList,
9 | MultiSelectorItem,
10 | } from "@/registry/default/extension/multi-select";
11 |
12 | const options = [
13 | { label: "React", value: "react" },
14 | { label: "Vue", value: "vue" },
15 | { label: "Svelte", value: "svelte" },
16 | ];
17 |
18 | const MultiSelectTest = () => {
19 | const [value, setValue] = useState([]);
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 | {options.map((option, i) => (
28 |
29 | {option.label}
30 |
31 | ))}
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default MultiSelectTest;
39 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/multi-select/multi-select-state.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | MultiSelector,
5 | MultiSelectorContent,
6 | MultiSelectorInput,
7 | MultiSelectorItem,
8 | MultiSelectorList,
9 | MultiSelectorTrigger,
10 | } from "@/registry/default/extension/multi-select";
11 | import { useState } from "react";
12 |
13 | const MultiSelectState = () => {
14 | const [value, setValue] = useState([]);
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | Item 1
24 | Item 2
25 | Item 3
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default MultiSelectState;
33 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/multi-select/multi-select-zod.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | MultiSelector,
5 | MultiSelectorContent,
6 | MultiSelectorInput,
7 | MultiSelectorItem,
8 | MultiSelectorList,
9 | MultiSelectorTrigger,
10 | } from "@/registry/default/extension/multi-select";
11 | import {
12 | Form,
13 | FormDescription,
14 | FormField,
15 | FormItem,
16 | FormLabel,
17 | FormMessage,
18 | } from "@/components/ui/form";
19 | import z from "zod";
20 | import { zodResolver } from "@hookform/resolvers/zod";
21 | import { useForm } from "react-hook-form";
22 | import { toast } from "sonner";
23 | import { Button } from "@/components/ui/button";
24 | import Image from "next/image";
25 |
26 | const form = z.object({
27 | value: z.array(z.string()).nonempty("Please select at least one person"),
28 | });
29 |
30 | type Form = z.infer;
31 |
32 | const users = [
33 | {
34 | name: "ThePrimeagen",
35 | },
36 | {
37 | name: "Shadcn",
38 | },
39 | {
40 | name: "Theo",
41 | },
42 | ];
43 |
44 | const MultiSelectZod = () => {
45 | const multiForm = useForm
92 |
93 | );
94 | };
95 |
96 | export default MultiSelectZod;
97 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/otp-input-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useForm } from "react-hook-form";
4 | import { toast } from "sonner";
5 | import {
6 | Form,
7 | FormField,
8 | FormItem,
9 | FormControl,
10 | FormMessage,
11 | } from "@/components/ui/form";
12 | import { Button } from "@/components/ui/button";
13 | import { OtpStyledInput } from "@/registry/default/extension/otp-input";
14 |
15 | const OtpTest = () => {
16 | const form = useForm({
17 | defaultValues: {
18 | otp: "",
19 | },
20 | });
21 |
22 | const onSubmit = (data: any) => {
23 | console.log(data);
24 | toast.success(`Success , Your Otp code is : ${data.otp}`);
25 | };
26 | return (
27 |
28 |
29 |
30 | OTP verification
31 |
32 | Enter the 5-digit code sent to your email address or phone number
33 |
34 |
35 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default OtpTest;
64 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/otp-input/otp-input-zod.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { OtpStyledInput } from "@/registry/default/extension/otp-input";
4 | import {
5 | Form,
6 | FormControl,
7 | FormField,
8 | FormItem,
9 | FormLabel,
10 | FormMessage,
11 | } from "@/components/ui/form";
12 | import z from "zod";
13 | import { zodResolver } from "@hookform/resolvers/zod";
14 | import { useForm } from "react-hook-form";
15 | import { toast } from "sonner";
16 | import { Button } from "@/components/ui/button";
17 | import { EyeClosedIcon, EyeOpenIcon } from "@radix-ui/react-icons";
18 | import { useState } from "react";
19 |
20 | const INPUT_NUM = 4;
21 |
22 | const form = z.object({
23 | otp: z.string().min(INPUT_NUM, "Password confirmation is required"),
24 | });
25 |
26 | type Form = z.infer;
27 |
28 | enum OtpInputType {
29 | password = "password",
30 | text = "text",
31 | }
32 |
33 | const OTPInputZod = () => {
34 | const [isPassword, setIsPassword] = useState(
35 | OtpInputType.password,
36 | );
37 | const multiForm = useForm
99 |
100 | );
101 | };
102 |
103 | export default OTPInputZod;
104 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/smart-datetime-input-demo.tsx:
--------------------------------------------------------------------------------
1 | import { SmartDatetimeInput } from "@/registry/default/extension/smart-datetime-input";
2 |
3 | const SmartDateTimeInputDemo = () => {
4 | return date < new Date()} />;
5 | };
6 |
7 | export default SmartDateTimeInputDemo;
8 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tags-input-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { TagsInput } from "@/registry/default/extension/tags-input";
5 |
6 | const TagsInputDemo = () => {
7 | const [value, setValue] = useState([]);
8 | return (
9 |
15 | );
16 | };
17 |
18 | export default TagsInputDemo;
19 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tags-input/tags-input-state.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { TagsInput } from "@/registry/default/extension/tags-input";
5 |
6 | const TagsInputSate = () => {
7 | const [value, setValue] = useState([]);
8 | return (
9 |
15 | );
16 | };
17 |
18 | export default TagsInputSate;
19 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tags-input/tags-input-zod.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/components/ui/button";
4 | import { Form, FormField, FormItem } from "@/components/ui/form";
5 | import { TagsInput } from "@/registry/default/extension/tags-input";
6 | import { zodResolver } from "@hookform/resolvers/zod";
7 | import { useForm } from "react-hook-form";
8 | import { toast } from "sonner";
9 | import z from "zod";
10 |
11 | const form = z.object({
12 | value: z.array(z.string()).nonempty("Please at least one item"),
13 | });
14 |
15 | type Form = z.infer;
16 |
17 | const TagsInputZod = () => {
18 | const tagsForm = useForm
52 |
53 | );
54 | };
55 |
56 | export default TagsInputZod;
57 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tree-view-demo.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Tree,
5 | Folder,
6 | File,
7 | CollapseButton,
8 | } from "@/registry/default/extension/tree-view-api";
9 |
10 | const TreeFileTest = () => {
11 | const elements = [
12 | {
13 | id: "1",
14 | isSelectable: true,
15 | name: "src",
16 | children: [
17 | {
18 | id: "2",
19 | isSelectable: true,
20 | name: "app.tsx",
21 | },
22 | {
23 | id: "3",
24 | isSelectable: true,
25 | name: "components",
26 | children: [
27 | {
28 | id: "20",
29 | isSelectable: true,
30 | name: "pages",
31 | children: [
32 | {
33 | id: "21",
34 | isSelectable: true,
35 | name: "interface.ts",
36 | },
37 | ],
38 | },
39 | ],
40 | },
41 | {
42 | id: "6",
43 | isSelectable: true,
44 | name: "ui",
45 | children: [
46 | {
47 | id: "7",
48 | isSelectable: true,
49 | name: "carousel.tsx",
50 | },
51 | ],
52 | },
53 | ],
54 | },
55 | ];
56 | return (
57 |
62 |
63 |
64 | app.tsx
65 |
66 |
67 |
68 |
69 | interface.ts
70 |
71 |
72 |
73 |
74 |
75 | carousel.tsx
76 |
77 |
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default TreeFileTest;
85 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tree-view/tree-view-builtin-expand.tsx:
--------------------------------------------------------------------------------
1 | import { TreeView } from "@/registry/default/extension/tree-view";
2 |
3 | const TreeViewBuiltinExpand = () => {
4 | const elements = [
5 | {
6 | id: "1",
7 | name: "Element 1",
8 | children: [
9 | {
10 | id: "1.1",
11 | name: "Element 1.1",
12 | children: [
13 | {
14 | id: "1.1.1",
15 | name: "Element 1.1.1",
16 | },
17 | {
18 | id: "1.1.2",
19 | name: "Element 1.1.2",
20 | },
21 | ],
22 | },
23 | {
24 | id: "1.2",
25 | name: "Element 1.2",
26 | },
27 | ],
28 | },
29 | {
30 | id: "2",
31 | name: "Element 2",
32 | children: [
33 | {
34 | id: "2.1",
35 | name: "Element 2.1",
36 | },
37 | {
38 | id: "2.2",
39 | name: "Element 2.2",
40 | },
41 | ],
42 | },
43 | {
44 | id: "3",
45 | name: "Element 3",
46 | },
47 | ];
48 |
49 | return (
50 |
51 | );
52 | };
53 |
54 | export default TreeViewBuiltinExpand;
55 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tree-view/tree-view-builtin-indicator.tsx:
--------------------------------------------------------------------------------
1 | import { TreeView } from "@/registry/default/extension/tree-view";
2 |
3 | const TreeViewBuiltInComponent = () => {
4 | const elements = [
5 | {
6 | id: "1",
7 | name: "Element 1",
8 | children: [
9 | {
10 | id: "1.1",
11 | name: "Element 1.1",
12 | children: [
13 | {
14 | id: "1.1.1",
15 | name: "Element 1.1.1",
16 | },
17 | {
18 | id: "1.1.2",
19 | name: "Element 1.1.2",
20 | },
21 | ],
22 | },
23 | {
24 | id: "1.2",
25 | name: "Element 1.2",
26 | },
27 | ],
28 | },
29 | {
30 | id: "2",
31 | name: "Element 2",
32 | children: [
33 | {
34 | id: "2.1",
35 | name: "Element 2.1",
36 | },
37 | {
38 | id: "2.2",
39 | name: "Element 2.2",
40 | },
41 | ],
42 | },
43 | {
44 | id: "3",
45 | name: "Element 3",
46 | },
47 | ];
48 |
49 | return ;
50 | };
51 |
52 | export default TreeViewBuiltInComponent;
53 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tree-view/tree-view-builtin-select.tsx:
--------------------------------------------------------------------------------
1 | import { TreeView } from "@/registry/default/extension/tree-view";
2 |
3 | const TreeViewBuiltinSelect = () => {
4 | const elements = [
5 | {
6 | id: "1",
7 | name: "Element 1",
8 | children: [
9 | {
10 | id: "1.1",
11 | name: "Element 1.1",
12 | children: [
13 | {
14 | id: "1.1.1",
15 | name: "Element 1.1.1",
16 | },
17 | {
18 | id: "1.1.2",
19 | name: "Element 1.1.2",
20 | },
21 | ],
22 | },
23 | {
24 | id: "1.2",
25 | name: "Element 1.2",
26 | },
27 | ],
28 | },
29 | {
30 | id: "2",
31 | name: "Element 2",
32 | children: [
33 | {
34 | id: "2.1",
35 | name: "Element 2.1",
36 | children: [
37 | {
38 | id: "2.1.1",
39 | name: "Element 2.1.1",
40 | },
41 | {
42 | id: "2.1.2",
43 | name: "Element 2.1.2",
44 | },
45 | ],
46 | },
47 | {
48 | id: "2.2",
49 | name: "Element 2.2",
50 | },
51 | ],
52 | },
53 | {
54 | id: "3",
55 | name: "Element 3",
56 | },
57 | ];
58 |
59 | return (
60 |
65 | );
66 | };
67 |
68 | export default TreeViewBuiltinSelect;
69 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/example/tree-view/tree-view-guide.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Tree,
3 | TreeViewElement,
4 | File,
5 | Folder,
6 | CollapseButton,
7 | } from "@/registry/default/extension/tree-view-api";
8 |
9 | type TOCProps = {
10 | toc: TreeViewElement[];
11 | };
12 |
13 | const TOC = ({ toc }: TOCProps) => {
14 | return (
15 |
16 | {toc.map((element, _) => (
17 |
18 | ))}
19 |
20 |
21 | );
22 | };
23 |
24 | type TreeItemProps = {
25 | elements: TreeViewElement[];
26 | };
27 |
28 | export const TreeItem = ({ elements }: TreeItemProps) => {
29 | return (
30 |
31 | {elements.map((element) => (
32 | -
33 | {element.children && element.children?.length > 0 ? (
34 |
40 |
45 |
46 | ) : (
47 |
52 | {element?.name}
53 |
54 | )}
55 |
56 | ))}
57 |
58 | );
59 | };
60 |
61 | const TOCWrapper = () => {
62 | const toc = [
63 | {
64 | id: "1",
65 | name: "components",
66 | children: [
67 | {
68 | id: "2",
69 | name: "extension",
70 | children: [
71 | {
72 | id: "3",
73 | name: "tree-view.tsx",
74 | },
75 | {
76 | id: "4",
77 | name: "tree-view-api.tsx",
78 | },
79 | ],
80 | },
81 | {
82 | id: "5",
83 | name: "dashboard-tree.tsx",
84 | },
85 | ],
86 | },
87 | {
88 | id: "6",
89 | name: "pages",
90 | children: [
91 | {
92 | id: "7",
93 | name: "page.tsx",
94 | },
95 | {
96 | id: "8",
97 | name: "page-guide.tsx",
98 | },
99 | ],
100 | },
101 | {
102 | id: "18",
103 | name: "env.ts",
104 | },
105 | ];
106 | return ;
107 | };
108 |
109 | export default TOCWrapper;
110 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/extension/otp-input.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Input } from "@/components/ui/input";
3 | import { cn } from "@/lib/utils";
4 | import OtpInput, { OTPInputProps } from "react-otp-input";
5 |
6 | type OtpOptions = Omit;
7 |
8 | type OtpStyledInputProps = {
9 | className?: string;
10 | } & OtpOptions;
11 |
12 | /**
13 | * Otp input Docs: {@link: https://shadcn-extension.vercel.app/docs/otp-input}
14 | */
15 |
16 | export const OtpStyledInput = ({
17 | className,
18 | ...props
19 | }: OtpStyledInputProps) => {
20 | return (
21 | (
24 |
28 | )}
29 | containerStyle={`flex justify-center items-center flex-wrap text-2xl font-bold ${
30 | props.renderSeparator ? "gap-1" : "gap-x-3 gap-y-2"
31 | }`}
32 | />
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/default/extension/tree-view.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@/lib/utils";
4 | import React, { forwardRef, useCallback, useRef } from "react";
5 | import useResizeObserver from "use-resize-observer";
6 | import { useVirtualizer } from "@tanstack/react-virtual";
7 | import {
8 | Tree,
9 | Folder,
10 | File,
11 | CollapseButton,
12 | TreeViewElement,
13 | } from "./tree-view-api";
14 |
15 | // TODO: Add the ability to add custom icons
16 |
17 | interface TreeViewComponentProps extends React.HTMLAttributes {}
18 |
19 | type TreeViewProps = {
20 | initialSelectedId?: string;
21 | elements: TreeViewElement[];
22 | indicator?: boolean;
23 | } & (
24 | | {
25 | initialExpendedItems?: string[];
26 | expandAll?: false;
27 | }
28 | | {
29 | initialExpendedItems?: undefined;
30 | expandAll: true;
31 | }
32 | ) &
33 | TreeViewComponentProps;
34 |
35 | /**
36 | * Tree View Docs: {@link: https://shadcn-extension.vercel.app/docs/tree-view}
37 | */
38 |
39 | export const TreeView = ({
40 | elements,
41 | className,
42 | initialSelectedId,
43 | initialExpendedItems,
44 | expandAll,
45 | indicator = false,
46 | }: TreeViewProps) => {
47 | const containerRef = useRef(null);
48 |
49 | const { getVirtualItems, getTotalSize } = useVirtualizer({
50 | count: elements.length,
51 | getScrollElement: () => containerRef.current,
52 | estimateSize: useCallback(() => 40, []),
53 | overscan: 5,
54 | });
55 |
56 | const { height = getTotalSize(), width } = useResizeObserver({
57 | ref: containerRef,
58 | });
59 | return (
60 |
67 |
74 | {getVirtualItems().map((element) => (
75 |
81 | ))}
82 |
83 | Expand All
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | TreeView.displayName = "TreeView";
91 |
92 | export const TreeItem = forwardRef<
93 | HTMLUListElement,
94 | {
95 | elements?: TreeViewElement[];
96 | indicator?: boolean;
97 | } & React.HTMLAttributes
98 | >(({ className, elements, indicator, ...props }, ref) => {
99 | return (
100 |
101 | {elements &&
102 | elements.map((element) => (
103 | -
104 | {element.children && element.children?.length > 0 ? (
105 |
110 |
116 |
117 | ) : (
118 |
124 | {element?.name}
125 |
126 | )}
127 |
128 | ))}
129 |
130 | );
131 | });
132 |
133 | TreeItem.displayName = "TreeItem";
134 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const registrySchema = z.array(
4 | z.object({
5 | name: z.string(),
6 | dependencies: z.array(z.string()).optional(),
7 | devDependencies: z.array(z.string()).optional(),
8 | registryDependencies: z.array(z.string()).optional(),
9 | uiDependencies: z.array(z.string()).optional(),
10 | files: z.array(z.string()),
11 | type: z.enum([
12 | "components:extension",
13 | "components:demo",
14 | "components:example",
15 | ]),
16 | }),
17 | );
18 |
19 | export type Registry = z.infer;
20 |
--------------------------------------------------------------------------------
/apps/extension/src/registry/styles.ts:
--------------------------------------------------------------------------------
1 | export const styles = [
2 | {
3 | name: "default",
4 | label: "Default",
5 | },
6 | ] as const;
7 |
8 | export type Style = (typeof styles)[number];
9 |
--------------------------------------------------------------------------------
/apps/extension/src/script/registry-builder.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import fs from "fs";
3 | import path, { basename } from "path";
4 | import { rimraf } from "rimraf";
5 |
6 | import { registry } from "../registry/components";
7 | import { registrySchema } from "../registry/schema";
8 | import { styles } from "../registry/styles";
9 |
10 | console.log("📝 Writing registry index...");
11 |
12 | const registryPath = path.join(process.cwd(), "src/__registry__");
13 |
14 | const result = registrySchema.safeParse(registry);
15 |
16 | if (!result.success) {
17 | console.error(result.error);
18 | process.exit(1);
19 | }
20 |
21 | // ----------------------------------------------------------------------------
22 | // Build __registry__/index.tsx.
23 | // ----------------------------------------------------------------------------
24 | let index = `// @ts-nocheck
25 | // This file is autogenerated by scripts/registry-builder.ts
26 | // Do not edit this file directly.
27 | import * as React from "react"
28 |
29 | export const Index: Record = {
30 | `;
31 | console.log(process.cwd());
32 |
33 | for (const style of styles) {
34 | index += ` "${style.name}": {`;
35 |
36 | // Build style index.
37 | for (const item of result.data) {
38 | // if (item.type === "components:ui") {
39 | // continue
40 | // }
41 |
42 | const resolveFiles = item.files.map(
43 | (file) => `src/registry/${style.name}/${file}`,
44 | );
45 |
46 | const type = item.type.split(":")[1];
47 | index += `
48 | "${item.name}": {
49 | name: "${item.name}",
50 | type: "${item.type}",
51 | registryDependencies: ${JSON.stringify(item.registryDependencies)},
52 | component: React.lazy(() => import("@/registry/${style.name}/${
53 | item.files[0]
54 | }")),
55 | files: [${resolveFiles.map((file) => `"${file}"`)}],
56 | },`;
57 | }
58 |
59 | index += `
60 | },`;
61 | }
62 |
63 | index += `
64 | }
65 | `;
66 |
67 | if (!fs.existsSync(registryPath)) {
68 | fs.mkdirSync(registryPath);
69 | }
70 | // Write style index.
71 | rimraf.sync(path.join(process.cwd(), "src/__registry__/index.tsx"));
72 | fs.writeFileSync(path.join(process.cwd(), "src/__registry__/index.tsx"), index);
73 |
74 | // write the registry to public dir
75 | const names = result.data.filter(
76 | (item) => item.type === "components:extension",
77 | );
78 | console.log("📝 Building index...");
79 | const registryJson = JSON.stringify(names, null, 2);
80 | console.log("📝 getting registry path registry...");
81 | const publicRegistryPath = path.join(process.cwd(), "public/registry");
82 | if (!fs.existsSync(publicRegistryPath)) {
83 | console.log("📝 Creating registry directory...");
84 | fs.mkdirSync(publicRegistryPath);
85 | }
86 |
87 | console.log("📝 Writing registry...");
88 |
89 | // check if file exists
90 | const registryFilePath = path.join(publicRegistryPath, "index.json");
91 | if (fs.existsSync(registryFilePath)) {
92 | console.log("📝 Removing existing registry file...");
93 | fs.unlinkSync(registryFilePath);
94 | }
95 |
96 | fs.writeFileSync(registryFilePath, registryJson);
97 |
98 | console.log("✅ Done!");
99 |
--------------------------------------------------------------------------------
/apps/extension/src/script/tsconfig.scripts.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "module": "ES6",
6 | "esModuleInterop": true,
7 | "isolatedModules": false
8 | },
9 | "include": [".contentlayer/generated", "scripts/**/*.ts"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/apps/extension/src/types/unist.ts:
--------------------------------------------------------------------------------
1 | import { Node } from "unist";
2 |
3 | export interface UnistNode extends Node {
4 | type: string;
5 | name?: string;
6 | tagName?: string;
7 | value?: string;
8 | properties?: {
9 | __rawString__?: string;
10 | __className__?: string;
11 | __event__?: string;
12 | [key: string]: unknown;
13 | } & NpmCommands;
14 | attributes?: {
15 | name: string;
16 | value: unknown;
17 | type?: string;
18 | }[];
19 | children?: UnistNode[];
20 | }
21 |
22 | export interface UnistTree extends Node {
23 | children: UnistNode[];
24 | }
25 |
26 | export interface NpmCommands {
27 | __npmCommand__?: string;
28 | __yarnCommand__?: string;
29 | __pnpmCommand__?: string;
30 | __bunCommand__?: string;
31 | }
32 |
--------------------------------------------------------------------------------
/apps/extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/base.json",
3 | "compilerOptions": {
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "baseUrl": ".",
6 | "allowJs": true,
7 | "noEmit": true,
8 | "module": "esnext",
9 | "moduleResolution": "bundler",
10 | "jsx": "preserve",
11 | "incremental": true,
12 | "noUnusedLocals": false,
13 | "plugins": [
14 | {
15 | "name": "next"
16 | }
17 | ],
18 | "paths": {
19 | "@/*": ["./src/*"],
20 | "contentlayer/generated": ["./.contentlayer/generated"],
21 | "content-collections": ["./.content-collections/generated"],
22 | "@ui/*": ["./src/components/ui/*"]
23 | }
24 | },
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx",
29 | ".next/types/**/*.ts",
30 | "velite.config.ts",
31 | ".contentlayer/generated",
32 | "contentlayer.config.js",
33 | "next.config.mts"
34 | ],
35 | "exclude": ["./src/script/registry-builder.ts"]
36 | }
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "turboapp",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo build",
6 | "dev": "turbo dev",
7 | "lint": "turbo lint",
8 | "pub:release": "turbo pub:release",
9 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
10 | },
11 | "devDependencies": {
12 | "@repo/eslint-config": "workspace:*",
13 | "@repo/typescript-config": "workspace:*",
14 | "prettier": "^3.2.5",
15 | "turbo": "^2.0.14"
16 | },
17 | "engines": {
18 | "node": ">=18"
19 | },
20 | "packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
21 | }
22 |
--------------------------------------------------------------------------------
/packages/cli/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | .gitpod.yml
--------------------------------------------------------------------------------
/packages/cli/LICENSE.md:
--------------------------------------------------------------------------------
1 | ## MIT License
2 |
3 | Copyright (c) 2024 shadcn extension
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 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # Shadcn-Extension
2 |
3 | A CLI (inspired by shadcn-ui) for adding components to your project, making it easier to integrate and manage UI components within your codebase.
4 |
5 | ## Installation
6 |
7 | To get started with the `shadcn-Extension` CLI, ensure you have `npx` installed. This tool allows you to run packages directly from npm without globally installing them.
8 |
9 | > ## NOTE
10 | >
11 | > ALL TYPES PACKAGES FOR NOW NEEDS TO BE INSTALLED MANUALLY
12 |
13 | ## Usage
14 |
15 | ### Initializing a New Project
16 |
17 | Use the `init` command to initialize dependencies for a new project. This command sets up everything you need, including installing necessary dependencies, adding the `cn` utility, configuring `tailwind.config.js`, and setting up CSS variables.
18 |
19 | 1. **Initialize Dependencies**
20 |
21 | Run the following command to initialize the project:
22 |
23 | ```bash
24 | npx shadcn-ui init
25 | ```
26 |
27 | 2. **Initialize Shadcn-Extension CLI**
28 |
29 | Next, set up your project with the shadcn-extension CLI:
30 |
31 | ```bash
32 | npx @shadx/cli init
33 | ```
34 |
35 | ### Adding Components
36 |
37 | Use the `add` command to add components to your project. This command installs the required dependencies and integrates the specified component into your project.
38 |
39 | 1. **Add a Specific Component**
40 |
41 | To add a specific component, specify the component name:
42 |
43 | ```bash
44 | npx @shadx/cli add [component]
45 | ```
46 |
47 | **Example:**
48 |
49 | Adding a `tree-view` component:
50 |
51 | ```bash
52 | npx @shadx/cli add tree-view
53 | ```
54 |
55 | 2. **View Available Components**
56 |
57 | If you want to see a list of all available components, run the `add` command without any arguments:
58 |
59 | ```bash
60 | npx @shadx/cli add
61 | ```
62 |
63 | This will display a list of components that you can add to your project.
64 |
65 | ## Full Documentation
66 |
67 | For detailed documentation, including installation guides, component usage, and more, visit the [shadcn-Extension Documentation](https://shadcn-extension.vercel.app/docs/installation).
68 |
69 | ## License
70 |
71 | This project is licensed under the [MIT license](https://github.com/BelkacemYerfa/shadcn-extension/blob/master/packages/cli/LICENSE.md).
72 |
73 | ## Contributing
74 |
75 | Contributions are welcome! If you have suggestions for improvements, please open an issue or submit a pull request on the [GitHub repository](https://github.com/BelkacemYerfa/shadcn-extension).
76 |
--------------------------------------------------------------------------------
/packages/cli/commitlint.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@shadx/cli",
3 | "version": "1.0.3",
4 | "description": "A CLI tool to generate UI components for your project",
5 | "publishConfig": {
6 | "access": "public"
7 | },
8 | "author": {
9 | "name": "shadx",
10 | "url": "https://github.com/BelkacemYerfa/shadcn-extension"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/BelkacemYerfa/shadcn-extension.git",
15 | "directory": "packages/cli"
16 | },
17 | "license": "MIT",
18 | "type": "module",
19 | "exports": "./dist/index.js",
20 | "bin": "./dist/index.js",
21 | "engines": {
22 | "node": ">=14.16"
23 | },
24 | "paths": {
25 | "@/*": [
26 | "./*"
27 | ]
28 | },
29 | "files": [
30 | "dist"
31 | ],
32 | "keywords": [
33 | "shadcn",
34 | "shadcn-extension",
35 | "shadcn-cli",
36 | "shadcn-ui",
37 | "extension-cli",
38 | "extension-ui",
39 | "extension",
40 | "cli",
41 | "ui",
42 | "react",
43 | "typescript",
44 | "nextjs",
45 | "tailwindcss"
46 | ],
47 | "scripts": {
48 | "pub:release":"pnpm publish --access public",
49 | "cli": "node dist/index.js",
50 | "init": "node dist/index.js init",
51 | "build": "tsup src/index.ts --format esm --clean --minify --metafile",
52 | "dev": "tsup src/index.ts --format esm --watch --clean --onSuccess \"node dist/index.js\"",
53 | "test": "vitest",
54 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
55 | },
56 | "dependencies": {
57 | "@antfu/ni": "^0.21.12",
58 | "chalk": "^5.3.0",
59 | "chalk-animation": "^2.0.3",
60 | "commander": "^9.5.0",
61 | "execa": "^7.2.0",
62 | "figlet": "^1.7.0",
63 | "https-proxy-agent": "^7.0.4",
64 | "node-fetch": "^3.3.2",
65 | "ora": "^6.3.1",
66 | "prompts": "^2.4.2",
67 | "semver": "^7.6.0",
68 | "zod": "^3.22.4",
69 | "glob": "^10.4.1"
70 | },
71 | "devDependencies": {
72 | "@commitlint/cli": "^17.4.1",
73 | "@commitlint/config-conventional": "^17.4.0",
74 | "@trivago/prettier-plugin-sort-imports": "^4.0.0",
75 | "@types/chalk-animation": "^1.6.3",
76 | "@types/figlet": "^1.5.5",
77 | "@types/node": "^18.11.18",
78 | "@types/prompts": "^2.4.9",
79 | "@types/semver": "^7.5.8",
80 | "@types/glob": "^8.1.0",
81 | "@typescript-eslint/eslint-plugin": "^5.48.1",
82 | "@typescript-eslint/parser": "^5.48.1",
83 | "eslint": "^8.31.0",
84 | "eslint-config-prettier": "^8.6.0",
85 | "eslint-plugin-prettier": "^4.2.1",
86 | "husky": "^8.0.3",
87 | "lint-staged": "^13.1.0",
88 | "prettier": "^2.8.2",
89 | "tsup": "^6.5.0",
90 | "type-fest": "^3.5.1",
91 | "typescript": "^4.9.4",
92 | "vitest": "^0.27.1"
93 | },
94 | "lint-staged": {
95 | "*.{js,jsx,ts,tsx}": [
96 | "eslint --fix",
97 | "prettier --write"
98 | ],
99 | "*.{md,mdx,yml,json}": [
100 | "prettier --write"
101 | ]
102 | }
103 | }
--------------------------------------------------------------------------------
/packages/cli/src/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from 'vitest';
2 |
3 | describe('awesome node CLI app', () => {
4 | test('Should works!', () => {
5 | expect(1 + 2).toBe(3);
6 | });
7 | });
--------------------------------------------------------------------------------
/packages/cli/src/commands/ascii-logo.ts:
--------------------------------------------------------------------------------
1 | export const ascii_logo = `
2 | %^^^^^
3 | .^^$-. @^^^^^^^^^^^
4 | ^^^^^^^^^^^^^^^^^^^^^^^^^
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^
6 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7 | #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.
8 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.
9 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 | @^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^%
12 | %^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^%
13 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 | |::^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]%
16 | @:::::::::+^^^^^^^^^^^^^^^^^^^^^^^^^^^^:~::~~~:;}[
17 | '"*#;:::::::::]^^^^^^^^^^^^^^^^^^^^$ ---
18 | '"^#[:::::::::[^^^^^^^^^^^^$
19 | '"*#|::::::::::*^^*%
20 | '"^#@::::~;%'
21 | `;
22 | export const ascii_logo_tease = `
23 | %^^^^^
24 | .^^$-. @^^^^^^^^^^^
25 | ^^^^^^^^^^^^^^^^^^^^^^^^^
26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^
27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28 | #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.
29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.
30 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32 | @^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^%
33 | %^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^%
34 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36 | |::^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^]%
37 | @:::::::::+^^^^^^^^^^^^^^^^^^^^^^^^^^^^:~::~~~:;}[
38 | '"*#;:::::::::]^^^^^^^^^^^^^^^^^^^^$ ---
39 | '"^#[:::::::::[^^^^^^^^^^^^$
40 | '"*#|::::::::::*^^*%
41 | '"^#@::::~;%'
42 |
43 | shadcn-extension!
44 | https://shadcn-extension.vercel.app/
45 | `;
46 |
--------------------------------------------------------------------------------
/packages/cli/src/commands/hello-world.ts:
--------------------------------------------------------------------------------
1 | import { ascii_logo_tease } from "./ascii-logo";
2 | import { Command } from "commander";
3 | import chalkAnimation from "chalk-animation"
4 |
5 | export const helloWorldCommand = new Command()
6 | .name("hello-world")
7 | .description("an ester-egg!")
8 | .argument("[components...]", "the components to add")
9 | .action(() => {
10 | const animation = chalkAnimation.rainbow(ascii_logo_tease);
11 | setTimeout(() => {
12 | animation.stop();
13 | }, 5000);
14 | });
15 |
--------------------------------------------------------------------------------
/packages/cli/src/index.ts:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | import { helloWorldCommand } from "./commands/hello-world.js";
3 | import { add } from "./commands/add.js";
4 | import { init } from "./commands/init.js";
5 | import { packageJSON } from "@/utils/package-json.js";
6 | import { Command } from "commander";
7 |
8 | (async () => {
9 | const program = new Command();
10 |
11 | program
12 | .name(">")
13 | .description("⚡️ raphael-08/ui.")
14 | .version(
15 | packageJSON.version,
16 | "-v, --version",
17 | "display the version number"
18 | );
19 |
20 | program
21 | .addCommand(init)
22 | .addCommand(add)
23 | .addCommand(helloWorldCommand);
24 | program.parse();
25 | })();
26 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/get-json.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import path from "path";
3 |
4 | export const DEFAULT_EXTENSION_PATH = "@/components/ui/extension";
5 |
6 | export const COMPONENTS_JSON_PATH = path.join(process.cwd(), "components.json");
7 | export function parseComponentsJson() {
8 | if (fs.existsSync(COMPONENTS_JSON_PATH)) {
9 | return JSON.parse(fs.readFileSync(COMPONENTS_JSON_PATH, "utf-8"));
10 | } else {
11 | return {};
12 | }
13 | }
14 |
15 | const TSCONFIG_JSON_PATH = path.join(process.cwd(), "tsconfig.json");
16 | export function parseTsconfigJson() {
17 | if (fs.existsSync(TSCONFIG_JSON_PATH)) {
18 | return JSON.parse(fs.readFileSync(TSCONFIG_JSON_PATH, "utf-8"));
19 | } else {
20 | return {};
21 | }
22 | }
23 |
24 | export function hasSrcPath(): boolean {
25 | try {
26 | const tsconfig = parseTsconfigJson();
27 | const paths = tsconfig.compilerOptions?.paths || {};
28 | return !!paths["@/*"] && paths["@/*"][0] === "./src/*";
29 | } catch (error) {
30 | console.error("Error parsing tsconfig:", error);
31 | return false;
32 | }
33 | }
34 |
35 | export const mkdir_components = (path: string) => {
36 | fs.mkdir(path, { recursive: true }, (err) => {
37 | if (err) {
38 | console.error("Error creating directory:", err);
39 | }
40 | });
41 | };
42 |
43 | export const decide = {
44 | true: path.join(process.cwd(), "/src", DEFAULT_EXTENSION_PATH.replace("@", "")),
45 | false: path.join(process.cwd(), DEFAULT_EXTENSION_PATH.replace("@", "")),
46 | };
47 |
48 | export const srcPath = hasSrcPath() ? "true" : "false";
49 | export const componentPath = decide[srcPath];
--------------------------------------------------------------------------------
/packages/cli/src/utils/get-package-manager.ts:
--------------------------------------------------------------------------------
1 | import { detect } from "@antfu/ni"
2 |
3 | export async function getPackageManager(
4 | targetDir: string
5 | ): Promise<"yarn" | "pnpm" | "bun" | "npm"> {
6 | const packageManager = await detect({ programmatic: true, cwd: targetDir })
7 |
8 | if (packageManager === "yarn@berry") return "yarn"
9 | if (packageManager === "pnpm@6") return "pnpm"
10 | if (packageManager === "bun") return "bun"
11 |
12 | return packageManager ?? "npm"
13 | }
14 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/logger.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 |
3 | export const logger = {
4 | info: (...text: unknown[]) => {
5 | console.log(chalk.cyan(...text));
6 | },
7 | warn: (...text: unknown[]) => {
8 | console.log(chalk.yellow(...text));
9 | },
10 | succeed: (...text: unknown[]) => {
11 | console.log(chalk.green(...text));
12 | },
13 | error: (...text: unknown[]) => {
14 | console.log(chalk.red(...text));
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/package-json.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 | import type { PackageJson } from "type-fest";
5 |
6 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
7 |
8 | export const packageJSON = {
9 | getContent() {
10 | const packageJsonPath = path.resolve(__dirname, "../", "package.json");
11 | const packageJsonContent = JSON.parse(
12 | fs.readFileSync(packageJsonPath, "utf-8")
13 | ) as PackageJson;
14 | return packageJsonContent;
15 | },
16 |
17 | /**
18 | * Get package version.
19 | */
20 | get version() {
21 | const packageJsonContent = this.getContent();
22 | const { version } = packageJsonContent;
23 | return version || "0.0.0";
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/registry/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const registrySchema = z.object({
4 | name: z.string(),
5 | dependencies: z.array(z.string()).optional(),
6 | devDependencies: z.array(z.string()).optional(),
7 | registryDependencies: z.array(z.string()).optional(),
8 | uiDependencies: z.array(z.string()).optional(),
9 | files: z.array(z.string()),
10 | type: z.enum([
11 | "components:extension",
12 | "components:demo",
13 | "components:example",
14 | ]),
15 | });
16 |
17 | export const registryIndexSchema = z.array(registrySchema);
18 |
19 | export type Registry = z.infer;
--------------------------------------------------------------------------------
/packages/cli/src/utils/render-title.ts:
--------------------------------------------------------------------------------
1 | import figlet from "figlet";
2 |
3 | export function renderTitle(type: string) {
4 | const text = figlet.textSync(type, {
5 | font: "Small",
6 | });
7 | console.log(`\n${text}\n`);
8 | }
9 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/base.json",
3 | "compilerOptions": {
4 | "moduleResolution": "node",
5 | "lib": ["ES2022"],
6 | "allowJs": false,
7 | "baseUrl": "./",
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | }
11 | },
12 | "include": ["src"],
13 | "exclude": ["_dist"]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/cli/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {},
5 | });
--------------------------------------------------------------------------------
/packages/eslint-config/README.md:
--------------------------------------------------------------------------------
1 | # `@turbo/eslint-config`
2 |
3 | Collection of internal eslint configurations.
4 |
--------------------------------------------------------------------------------
/packages/eslint-config/library.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("node:path");
2 |
3 | const project = resolve(process.cwd(), "tsconfig.json");
4 |
5 | /** @type {import("eslint").Linter.Config} */
6 | module.exports = {
7 | extends: ["eslint:recommended", "prettier", "eslint-config-turbo"],
8 | plugins: ["only-warn"],
9 | globals: {
10 | React: true,
11 | JSX: true,
12 | },
13 | env: {
14 | node: true,
15 | },
16 | settings: {
17 | "import/resolver": {
18 | typescript: {
19 | project,
20 | },
21 | },
22 | },
23 | ignorePatterns: [
24 | // Ignore dotfiles
25 | ".*.js",
26 | "node_modules/",
27 | "dist/",
28 | ],
29 | overrides: [
30 | {
31 | files: ["*.js?(x)", "*.ts?(x)"],
32 | },
33 | ],
34 | };
35 |
--------------------------------------------------------------------------------
/packages/eslint-config/next.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("node:path");
2 |
3 | const project = resolve(process.cwd(), "tsconfig.json");
4 |
5 | /** @type {import("eslint").Linter.Config} */
6 | module.exports = {
7 | extends: [
8 | "eslint:recommended",
9 | "prettier",
10 | require.resolve("@vercel/style-guide/eslint/next"),
11 | "eslint-config-turbo",
12 | ],
13 | globals: {
14 | React: true,
15 | JSX: true,
16 | },
17 | env: {
18 | node: true,
19 | browser: true,
20 | },
21 | plugins: ["only-warn"],
22 | settings: {
23 | "import/resolver": {
24 | typescript: {
25 | project,
26 | },
27 | },
28 | },
29 | ignorePatterns: [
30 | // Ignore dotfiles
31 | ".*.js",
32 | "node_modules/",
33 | ],
34 | overrides: [{ files: ["*.js?(x)", "*.ts?(x)"] }],
35 | };
36 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/eslint-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "files": [
6 | "library.js",
7 | "next.js",
8 | "react-internal.js"
9 | ],
10 | "devDependencies": {
11 | "@vercel/style-guide": "^5.2.0",
12 | "eslint-config-turbo": "^1.12.4",
13 | "eslint-config-prettier": "^9.1.0",
14 | "eslint-plugin-only-warn": "^1.1.0",
15 | "@typescript-eslint/parser": "^7.1.0",
16 | "@typescript-eslint/eslint-plugin": "^7.1.0",
17 | "typescript": "^5.3.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/eslint-config/react-internal.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require("node:path");
2 |
3 | const project = resolve(process.cwd(), "tsconfig.json");
4 |
5 | /*
6 | * This is a custom ESLint configuration for use with
7 | * internal (bundled by their consumer) libraries
8 | * that utilize React.
9 | */
10 |
11 | /** @type {import("eslint").Linter.Config} */
12 | module.exports = {
13 | extends: ["eslint:recommended", "prettier", "eslint-config-turbo"],
14 | plugins: ["only-warn"],
15 | globals: {
16 | React: true,
17 | JSX: true,
18 | },
19 | env: {
20 | browser: true,
21 | },
22 | settings: {
23 | "import/resolver": {
24 | typescript: {
25 | project,
26 | },
27 | },
28 | },
29 | ignorePatterns: [
30 | // Ignore dotfiles
31 | ".*.js",
32 | "node_modules/",
33 | "dist/",
34 | ],
35 | overrides: [
36 | // Force ESLint to detect .tsx files
37 | { files: ["*.js?(x)", "*.ts?(x)"] },
38 | ],
39 | };
40 |
--------------------------------------------------------------------------------
/packages/typescript-config/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "skipLibCheck": true,
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "resolveJsonModule": true,
7 | "isolatedModules": true,
8 | "target": "ES2022",
9 | "module": "ES2022",
10 | "moduleResolution": "Node",
11 | "sourceMap": true,
12 | "experimentalDecorators": true,
13 | "removeComments": false,
14 | "forceConsistentCasingInFileNames": true,
15 | "allowSyntheticDefaultImports": true,
16 | "noUnusedLocals": true,
17 | "useUnknownInCatchVariables": false,
18 | "useDefineForClassFields": true
19 | },
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/typescript-config/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/typescript-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/typescript-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/typescript-config/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 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/ui/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import("eslint").Linter.Config} */
2 | module.exports = {
3 | root: true,
4 | extends: ["@repo/eslint-config/react-internal.js"],
5 | parser: "@typescript-eslint/parser",
6 | parserOptions: {
7 | project: "./tsconfig.lint.json",
8 | tsconfigRootDir: __dirname,
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/ui",
3 | "version": "0.0.0",
4 | "private": true,
5 | "exports": {
6 | "./button": "./src/button.tsx",
7 | "./card": "./src/card.tsx",
8 | "./code": "./src/code.tsx"
9 | },
10 | "scripts": {
11 | "lint": "eslint . --max-warnings 0",
12 | "generate:component": "turbo gen react-component"
13 | },
14 | "devDependencies": {
15 | "@repo/eslint-config": "workspace:*",
16 | "@repo/typescript-config": "workspace:*",
17 | "@turbo/gen": "^1.12.4",
18 | "@types/node": "^20.11.24",
19 | "@types/eslint": "^8.56.5",
20 | "@types/react": "^18.2.61",
21 | "@types/react-dom": "^18.2.19",
22 | "eslint": "^8.57.0",
23 | "react": "^18.2.0",
24 | "typescript": "^5.3.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/ui/src/button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ReactNode } from "react";
4 |
5 | interface ButtonProps {
6 | children: ReactNode;
7 | className?: string;
8 | appName: string;
9 | }
10 |
11 | export const Button = ({ children, className, appName }: ButtonProps) => {
12 | return (
13 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/packages/ui/src/card.tsx:
--------------------------------------------------------------------------------
1 | export function Card({
2 | className,
3 | title,
4 | children,
5 | href,
6 | }: {
7 | className?: string;
8 | title: string;
9 | children: React.ReactNode;
10 | href: string;
11 | }): JSX.Element {
12 | return (
13 |
19 |
20 | {title} ->
21 |
22 | {children}
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/packages/ui/src/code.tsx:
--------------------------------------------------------------------------------
1 | export function Code({
2 | children,
3 | className,
4 | }: {
5 | children: React.ReactNode;
6 | className?: string;
7 | }): JSX.Element {
8 | return {children} ;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.lint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": ["src", "turbo"],
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/ui/turbo/generators/config.ts:
--------------------------------------------------------------------------------
1 | import type { PlopTypes } from "@turbo/gen";
2 |
3 | // Learn more about Turborepo Generators at https://turbo.build/repo/docs/core-concepts/monorepos/code-generation
4 |
5 | export default function generator(plop: PlopTypes.NodePlopAPI): void {
6 | // A simple generator to add a new React component to the internal UI library
7 | plop.setGenerator("react-component", {
8 | description: "Adds a new react component",
9 | prompts: [
10 | {
11 | type: "input",
12 | name: "name",
13 | message: "What is the name of the component?",
14 | },
15 | ],
16 | actions: [
17 | {
18 | type: "add",
19 | path: "src/{{kebabCase name}}.tsx",
20 | templateFile: "templates/component.hbs",
21 | },
22 | {
23 | type: "append",
24 | path: "package.json",
25 | pattern: /"exports": {(?)/g,
26 | template: '"./{{kebabCase name}}": "./src/{{kebabCase name}}.tsx",',
27 | },
28 | ],
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/packages/ui/turbo/generators/templates/component.hbs:
--------------------------------------------------------------------------------
1 | export const {{ pascalCase name }} = ({ children }: { children: React.ReactNode }) => {
2 | return (
3 |
4 | {{ pascalCase name }} Component
5 | {children}
6 |
7 | );
8 | };
9 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "apps/*"
3 | - "packages/*"
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/base.json"
3 | }
4 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": [
4 | "**/.env.*local"
5 | ],
6 | "tasks": {
7 | "build": {
8 | "dependsOn": [
9 | "^build"
10 | ],
11 | "outputs": [
12 | ".next/**",
13 | "!.next/cache/**"
14 | ]
15 | },
16 | "lint": {
17 | "dependsOn": [
18 | "^lint"
19 | ]
20 | },
21 | "dev": {
22 | "cache": false,
23 | "persistent": true
24 | },
25 | "pub:release" : {
26 | "dependsOn": [
27 | "^build"
28 | ],
29 | "outputs": []
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
|