├── .eslintrc.js ├── .github └── workflows │ └── close-stale-issues.yml ├── .gitignore ├── .npmrc ├── LICENSE.md ├── README.md ├── apps └── extension │ ├── .env.sample │ ├── .eslintrc.json │ ├── .gitignore │ ├── .hintrc │ ├── .prettierignore │ ├── CONTRIBUTING.md │ ├── LICENSE.md │ ├── components.json │ ├── content-collections.ts │ ├── content │ └── docs │ │ ├── breadcrumb.mdx │ │ ├── carousel.mdx │ │ ├── changelog.mdx │ │ ├── datetime-picker.mdx │ │ ├── file-upload.mdx │ │ ├── installation.mdx │ │ ├── introduction.mdx │ │ ├── multi-select.mdx │ │ ├── otp-input.mdx │ │ ├── smart-datetime-input.mdx │ │ ├── tags-input.mdx │ │ └── tree-view.mdx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── next.svg │ ├── og.png │ ├── registry │ │ ├── breadcrumb.json │ │ ├── carousel.json │ │ ├── datetime-picker.json │ │ ├── file-upload.json │ │ ├── image-carousel-upload.json │ │ ├── index.json │ │ ├── multi-select.json │ │ ├── otp-input.json │ │ ├── smart-datetime-input.json │ │ ├── tags-input.json │ │ ├── tree-view-api.json │ │ └── tree-view.json │ └── vercel.svg │ ├── src │ ├── __registry__ │ │ ├── .autogenerated │ │ ├── READMe.md │ │ └── index.tsx │ ├── app │ │ ├── components │ │ │ └── page.tsx │ │ ├── docs │ │ │ ├── [[...slug]] │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── components │ │ ├── analytics.tsx │ │ ├── banner.tsx │ │ ├── callout.tsx │ │ ├── cards │ │ │ └── component-card.tsx │ │ ├── code-block-wrapper.tsx │ │ ├── component-preview.tsx │ │ ├── component-source.tsx │ │ ├── copy-button.tsx │ │ ├── doc-breadcrumb.tsx │ │ ├── drop-downs │ │ │ └── search-selector.tsx │ │ ├── icons.tsx │ │ ├── layouts │ │ │ ├── site-footer.tsx │ │ │ ├── site-header.tsx │ │ │ └── toc.tsx │ │ ├── loaders │ │ │ ├── editor-loader.tsx │ │ │ ├── playground-loader.tsx │ │ │ └── view-output-loader.tsx │ │ ├── mdx-component.tsx │ │ ├── pager.tsx │ │ ├── primitive-link.tsx │ │ ├── provider.tsx │ │ ├── search.tsx │ │ ├── side-bar.tsx │ │ ├── skip-nav.tsx │ │ ├── social-links.tsx │ │ ├── tables │ │ │ └── mdx-table.tsx │ │ ├── toggle-theme.tsx │ │ └── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── credenza.tsx │ │ │ ├── date-picker.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ └── tooltip.tsx │ ├── config │ │ ├── docs-config.ts │ │ └── site-config.ts │ ├── env.ts │ ├── hooks │ │ ├── use-active-section.ts │ │ ├── use-debounce.ts │ │ ├── use-media-query.ts │ │ └── use-mounted.ts │ ├── lib │ │ ├── editor-comp.ts │ │ ├── element-parser.ts │ │ ├── events.ts │ │ ├── rehype-component.ts │ │ ├── rehype-installation-command.ts │ │ ├── toc.ts │ │ └── utils.ts │ ├── registry │ │ ├── components.ts │ │ ├── default │ │ │ ├── example │ │ │ │ ├── breadcrumb-demo.tsx │ │ │ │ ├── breadcrumb │ │ │ │ │ ├── breadcrumb-active.tsx │ │ │ │ │ ├── breadcrumb-orientation.tsx │ │ │ │ │ ├── breadcrumb-popover.tsx │ │ │ │ │ ├── breadcrumb-separator.tsx │ │ │ │ │ └── breadcrumb-variants.tsx │ │ │ │ ├── carousel-demo.tsx │ │ │ │ ├── carousel │ │ │ │ │ ├── carousel-indicator.tsx │ │ │ │ │ ├── carousel-orientation.tsx │ │ │ │ │ ├── carousel-plugin.tsx │ │ │ │ │ └── carousel-rtl-support.tsx │ │ │ │ ├── datetime-picker-demo.tsx │ │ │ │ ├── datetime-picker │ │ │ │ │ └── datetime-picker-zod.tsx │ │ │ │ ├── file-upload-demo.tsx │ │ │ │ ├── file-upload │ │ │ │ │ ├── file-upload-dropzone.tsx │ │ │ │ │ └── file-upload-zod.tsx │ │ │ │ ├── image-carousel-upload-example.tsx │ │ │ │ ├── multi-select-demo.tsx │ │ │ │ ├── multi-select │ │ │ │ │ ├── multi-select-state.tsx │ │ │ │ │ └── multi-select-zod.tsx │ │ │ │ ├── otp-input-demo.tsx │ │ │ │ ├── otp-input │ │ │ │ │ └── otp-input-zod.tsx │ │ │ │ ├── smart-datetime-input-demo.tsx │ │ │ │ ├── smart-datetime-input │ │ │ │ │ └── smart-datetime-input-zod.tsx │ │ │ │ ├── tags-input-demo.tsx │ │ │ │ ├── tags-input │ │ │ │ │ ├── tags-input-state.tsx │ │ │ │ │ └── tags-input-zod.tsx │ │ │ │ ├── tree-view-demo.tsx │ │ │ │ └── tree-view │ │ │ │ │ ├── tree-view-builtin-expand.tsx │ │ │ │ │ ├── tree-view-builtin-indicator.tsx │ │ │ │ │ ├── tree-view-builtin-select.tsx │ │ │ │ │ └── tree-view-guide.tsx │ │ │ └── extension │ │ │ │ ├── breadcrumb.tsx │ │ │ │ ├── carousel.tsx │ │ │ │ ├── datetime-picker.tsx │ │ │ │ ├── file-upload.tsx │ │ │ │ ├── image-carousel-upload.tsx │ │ │ │ ├── multi-select.tsx │ │ │ │ ├── otp-input.tsx │ │ │ │ ├── smart-datetime-input.tsx │ │ │ │ ├── tags-input.tsx │ │ │ │ ├── tree-view-api.tsx │ │ │ │ └── tree-view.tsx │ │ ├── schema.ts │ │ └── styles.ts │ ├── script │ │ ├── registry-builder.ts │ │ └── tsconfig.scripts.json │ └── types │ │ └── unist.ts │ ├── tailwind.config.ts │ └── tsconfig.json ├── package.json ├── packages ├── cli │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── commitlint.config.cjs │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ ├── commands │ │ │ ├── add.ts │ │ │ ├── ascii-logo.ts │ │ │ ├── hello-world.ts │ │ │ └── init.ts │ │ ├── index.ts │ │ └── utils │ │ │ ├── get-json.ts │ │ │ ├── get-package-manager.ts │ │ │ ├── logger.ts │ │ │ ├── package-json.ts │ │ │ ├── registry │ │ │ ├── index.ts │ │ │ └── schema.ts │ │ │ └── render-title.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── eslint-config │ ├── README.md │ ├── library.js │ ├── next.js │ ├── package.json │ └── react-internal.js ├── typescript-config │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── .eslintrc.js │ ├── package.json │ ├── src │ ├── button.tsx │ ├── card.tsx │ └── code.tsx │ ├── tsconfig.json │ ├── tsconfig.lint.json │ └── turbo │ └── generators │ ├── config.ts │ └── templates │ └── component.hbs ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json └── turbo.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // This configuration only applies to the package manager root. 2 | /** @type {import("eslint").Linter.Config} */ 3 | module.exports = { 4 | ignorePatterns: ["apps/**", "packages/**"], 5 | extends: ["@repo/eslint-config/library.js"], 6 | parser: "@typescript-eslint/parser", 7 | parserOptions: { 8 | project: true, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /.github/workflows/close-stale-issues.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Runs at midnight every day 6 | 7 | jobs: 8 | close-stale-issues: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Close Stale Issues 12 | uses: actions/stale@v9.0.0 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | 40 | # local history 41 | .history 42 | 43 | # vscode 44 | .vscode -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BelkacemYerfa/shadcn-extension/5326f1afa62dca1afbd9f676a97c8967eef1e898/.npmrc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/extension/.env.sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_POSTHOG_HOST= 2 | NEXT_PUBLIC_POSTHOG_KEY= -------------------------------------------------------------------------------- /apps/extension/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /apps/extension/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | .contentlayer 40 | .content-collections 41 | tsconfig.tsbuildinfo 42 | -------------------------------------------------------------------------------- /apps/extension/.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "hints": { 6 | "no-inline-styles": "off" 7 | } 8 | } -------------------------------------------------------------------------------- /apps/extension/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | build 4 | .content-collections 5 | __registry__/index.tsx -------------------------------------------------------------------------------- /apps/extension/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 | -------------------------------------------------------------------------------- /apps/extension/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /apps/extension/content-collections.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, defineConfig } from "@content-collections/core"; 2 | import rehypePrettyCode from "rehype-pretty-code"; 3 | import rehypeSlug from "rehype-slug"; 4 | import { codeImport } from "remark-code-import"; 5 | import { compileMDX } from "@content-collections/mdx"; 6 | import remarkGfm from "remark-gfm"; 7 | import { visit } from "unist-util-visit"; 8 | import { rehypeComponent } from "./src/lib/rehype-component"; 9 | import { rehypeNpmCommand } from "./src/lib/rehype-installation-command"; 10 | import { z } from "zod"; 11 | 12 | const LinksProperties = z.object({ 13 | url: z.string(), 14 | title: z.string(), 15 | }); 16 | 17 | export const Doc = defineCollection({ 18 | name: "Doc", 19 | directory: `content/docs`, 20 | include: "**/*.mdx", 21 | schema: (z) => ({ 22 | title: z.string().min(1), 23 | description: z.string().min(1), 24 | published: z.boolean({}).optional(), 25 | links: z.array(LinksProperties).optional(), 26 | toc: z.boolean().default(true), 27 | }), 28 | transform: async (document, context) => { 29 | const body = await compileMDX(context, document, { 30 | remarkPlugins: [codeImport, remarkGfm], 31 | rehypePlugins: [ 32 | rehypeSlug, 33 | rehypeComponent, 34 | () => (tree) => { 35 | visit(tree, (node) => { 36 | if (node?.type === "element" && node?.tagName === "pre") { 37 | const [codeEl] = node.children; 38 | if (codeEl.tagName !== "code") { 39 | return; 40 | } 41 | if (codeEl.data?.meta) { 42 | // Extract event from meta and pass it down the tree. 43 | const regex = /event="([^"]*)"/; 44 | const match = codeEl.data?.meta.match(regex); 45 | if (match) { 46 | node.__event__ = match ? match[1] : null; 47 | codeEl.data.meta = codeEl.data.meta.replace(regex, ""); 48 | } 49 | } 50 | node.__rawString__ = codeEl.children?.[0].value; 51 | node.__src__ = node.properties?.__src__; 52 | node.__style__ = node.properties?.__style__; 53 | } 54 | }); 55 | }, 56 | () => (tree) => { 57 | visit(tree, (node) => { 58 | if (node?.type === "element" && node?.tagName === "figure") { 59 | if (!("data-rehype-pretty-code-figure" in node.properties)) { 60 | return; 61 | } 62 | 63 | const preElement = node.children.at(-1); 64 | if (preElement.tagName !== "pre") { 65 | return; 66 | } 67 | 68 | preElement.properties["__withMeta__"] = 69 | node.children.at(0).tagName === "div"; 70 | preElement.properties["__rawString__"] = node.__rawString__; 71 | 72 | if (node.__src__) { 73 | preElement.properties["__src__"] = node.__src__; 74 | } 75 | 76 | if (node.__event__) { 77 | preElement.properties["__event__"] = node.__event__; 78 | } 79 | 80 | if (node.__style__) { 81 | preElement.properties["__style__"] = node.__style__; 82 | } 83 | } 84 | }); 85 | }, 86 | rehypeNpmCommand, 87 | [ 88 | rehypePrettyCode, 89 | { 90 | theme: "one-dark-pro", 91 | }, 92 | ], 93 | ], 94 | }); 95 | return { 96 | ...document, 97 | slug: `${document._meta.fileName}`, 98 | slugAsParams: document._meta.fileName, 99 | body: { 100 | raw: document.content, 101 | code: body, 102 | }, 103 | }; 104 | }, 105 | }); 106 | 107 | export default defineConfig({ 108 | collections: [Doc], 109 | }); 110 | -------------------------------------------------------------------------------- /apps/extension/content/docs/changelog.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | description: All last changes in the project with announcements 4 | --- 5 | 6 | ## August 2024 - New Cli, and more 7 | 8 | ### Cli support 9 | 10 | One of the most requested features is the Cli, cause some component have big configuration process, for that we heard you and we made the cli, and with no more config, we use shadcn's config, a big thanks 11 | 12 | 17 | @Raphael 18 | 19 | 20 | #### Example 21 | 22 | This example showcases on how to add Smart Date time picker 23 | 24 | ```bash 25 | npx @shadx/cli add tree-view 26 | ``` 27 | 28 | ### New team member 29 | 30 | Thanks to his big help and support of building the cli, and making the configuration for the end user as easy as possible, we want to welcome 31 | @Raphael 32 | to the team, and really this is just the beginning he has some big plans to make the experience even better. 33 | 34 | ### New components 35 | 36 | As we saw in the community having good components to deal with time is kinda hard to build, for that we made 2 main component and you can use them in a lot of scenarios, see the examples, and one of them is smart. also they support both RTL and LTR stuff. 37 | 38 | ## April 2024 - RTL support, new member 39 | 40 | ### RTL support 41 | 42 | One of the most missing features is the RTL support for the components, so we provided the fix for that, so now every component in the registry gonna make the write adjusments for the ui and functionality based on the dir property. 43 | 44 | #### Example 45 | 46 | This example showcases the carousel component 47 | 48 | 49 | 50 | 51 | 52 | Good to know 53 | 54 | The carousel component has 2 ways for the direction one is using the `dir` 55 | prop , and the other one is to use `carouselOptions` as the embla carousel 56 | provides a{" "} 57 | 62 | Direction prop 63 | {" "} 64 | that suport both 65 | 66 | 67 | 68 | ### New team member 69 | 70 | This is project is growing up, and this is cool to see from the community, but a lot of parts are missing, so I want you all to welcome a new team member @Gaurang, together, we feel confident that we can deliver the tools you need to easily build accessible modern UIs faster than ever before. 71 | 72 | ## March 2024 - New Docs 73 | 74 | We are happy to announce that we have updated our documentation. We have added new features. We hope you will find it useful. If you have any questions or suggestions, please let us know. 75 | -------------------------------------------------------------------------------- /apps/extension/content/docs/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | description: Full installation guide for the project 4 | --- 5 | 6 | # Shadcn-Extension 7 | 8 | A CLI (inspired by and meant to work with shadcn-ui) for adding components to your project, making it easier to integrate and manage UI components within your codebase. 9 | 10 | ## Usage 11 | 12 | ### Initializing a New Project 13 | 14 | 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 as well as enabling shadcn-ui in your project. 15 | 16 | 1. **Initialize Dependencies** 17 | 18 | Run the following command to initialize the project: 19 | 20 | ```bash 21 | npx shadcn-ui init 22 | ``` 23 | 24 | 2. **Initialize Shadcn-Extension CLI** 25 | 26 | Next, set up your project with the shadcn-extension CLI: 27 | 28 | ```bash 29 | npx @shadx/cli init 30 | ``` 31 | 32 | ### Adding Components 33 | 34 | Use the `add` command to add components to your project. This command installs the required dependencies and integrates the specified component into your project. 35 | 36 | 1. **Add a Specific Component** 37 | 38 | To add a specific component, specify the component name: 39 | 40 | ```bash 41 | npx @shadx/cli add [component] 42 | ``` 43 | 44 | **Example:** 45 | 46 | Adding a `tree-view` component: 47 | 48 | ```bash 49 | npx @shadx/cli add tree-view 50 | ``` 51 | 52 | 2. **View Available Components** 53 | 54 | If you want to see a list of all available components, run the `add` command without any arguments: 55 | 56 | ```bash 57 | npx @shadx/cli add 58 | ``` 59 | 60 | This will display a list of components that you can add to your project. 61 | 62 | ## Full Documentation 63 | 64 | For detailed documentation, including installation guides, component usage, and more, visit the [shadcn-Extension Documentation](https://shadcn-extension.vercel.app/docs/installation). 65 | -------------------------------------------------------------------------------- /apps/extension/content/docs/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: Welcome to Shadcn extension documentation! This is a guide to help you get started with it. 4 | --- 5 | 6 | ## What is Shadcn extension? 7 | 8 | Shadcn extension is an extend for Shadcn/ui component library that provides a set of components for building web applications. It is built on top of Shadcn/ui. 9 | 10 | ## Capabilities 11 | 12 | Shadcn extension is a set of components that are built on top of Shadcn-ui . It extends the component that are provided by shadcn and use the one there to build some common component for the most web apps . It is built with the following goals in mind: 13 | 14 | - **Customizable**: Shadcn extension is designed to be highly customizable. It provides a set of components that can be easily customized to fit your needs. 15 | - **Easy to use**: Shadcn extension is designed to be easy to use. It provides a set of components that are easy to use and can be easily integrated into your web application. 16 | - **No need for installation**: Shadcn extension does not require any installation. just Copy&Paste the code and install the shadcn/ui component that it uses. 17 | 18 | ## Getting Started 19 | 20 | To get started with shadcn extension, you need to have a basic understanding of ReactJs/NextJs and Tailwinds .You also need to have a basic understanding of Shadcn ui. If you are new to Shadcn-ui, you can check out the Shadcn/ui. 21 | 22 | ## Installation 23 | 24 | To install shadcn extension, you need to configure the shadcn/ui component that it uses. You can do this by following the instructions in the Shadcn/ui. 25 | 26 | ## Usage 27 | 28 | To use Shadcn extension, you need to Copy&Paste the components that you want to use from the **shadcn extension** . You can then use these components in your web application. 29 | 30 | Example: 31 | 32 | ```tsx 33 | import React from "react"; 34 | import { BreadCrumb } from "@/component/extension/breadcrumb"; 35 | 36 | export const BreadCrumbTest = () => { 37 | return ( 38 | 39 | Home 40 | 41 | Settings 42 | 43 | Info 44 | 45 | ); 46 | }; 47 | ``` 48 | 49 | ## Contributing 50 | 51 | If you would like to contribute to Shadcn extension, you can do so by following the instructions in the [CONTRIBUTING.md](https://github.com/BelkacemYerfa/shadcn-extension/blob/master/apps/extension/CONTRIBUTING.md) guide. 52 | 53 | In the end , I want to give a special thanks to [shadcn](https://twitter.com/shadcn) for the amazing work that he did to build the shadcn/ui component, he's the one who inspired me to build this extension and I hope that it will help people shipping better web applications. 54 | -------------------------------------------------------------------------------- /apps/extension/content/docs/otp-input.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Otp Input 3 | description: A simple validation input for OTP 4 | links: 5 | - title: shadcn-ui 6 | url: https://ui.shadcn.com/docs/components/input 7 | - title: react-otp-input 8 | url: https://devfolioco.github.io/react-otp-input/ 9 | --- 10 | 11 | 12 | 13 | ## Installation 14 | 15 | 16 | 17 | 18 | Manual 19 | CLI 20 | 21 | 22 | 23 | 24 | 25 | 26 | Run the following command: 27 | 28 | ```bash 29 | npx shadcn@latest add input 30 | npm i react-otp-input 31 | ``` 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Run the following command 46 | 47 | ```bash 48 | npx @shadx/cli add otp-input 49 | ``` 50 | 51 | 52 | 53 | 54 | 55 | ## Usage 56 | 57 | ```tsx 58 | import { OtpStyledInput } from "@/components/extension/otp-input"; 59 | ``` 60 | 61 | ```tsx 62 | 68 | ``` 69 | 70 | ## Example 71 | 72 | ### Form 73 | 74 | ```tsx showLineNumbers {1 , 3 , 16-21 } 75 | "use client"; 76 | 77 | import { OtpStyledInput } from "@/components/extension/otp-input"; 78 | 79 | {...} 80 | 81 | const OTPInput = ()=>{ 82 | return ( 83 |
84 | {...} 85 | ( 89 | {...} 90 | 96 | {...} 97 | )} /> 98 | {...} 99 | 100 | ) 101 | } 102 | 103 | ``` 104 | 105 | 106 | -------------------------------------------------------------------------------- /apps/extension/next.config.mjs: -------------------------------------------------------------------------------- 1 | import { withContentCollections } from "@content-collections/next"; 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | reactStrictMode: true, 6 | images: { 7 | // Domains that are allowed to be used with next/image 8 | // doesn't work for cjs config , need ESM config 9 | domains: ["pbs.twimg.com"], 10 | }, 11 | redirects: async function () { 12 | return [ 13 | { 14 | source: "/docs", 15 | destination: "/docs/introduction", 16 | permanent: true, 17 | }, 18 | { 19 | source: "/docs/components", 20 | destination: "/components", 21 | permanent: true, 22 | }, 23 | ]; 24 | }, 25 | }; 26 | 27 | export default withContentCollections(nextConfig); 28 | -------------------------------------------------------------------------------- /apps/extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-extension", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbo", 7 | "build": "content-collections build && pnpm build:registry && next build && prettier --write \"**/*.{ts,tsx,mdx}\" --cache", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "build:test": "pnpm typecheck && pnpm build:registry && next build && next start -p 3001", 11 | "build:registry": "npx tsx --tsconfig ./src/script/tsconfig.scripts.json ./src/script/registry-builder.ts", 12 | "typecheck": "content-collections build && tsc --noEmit", 13 | "watch": "content-collections dev && tsc --noEmit" 14 | }, 15 | "dependencies": { 16 | "@content-collections/mdx": "^0.2.0", 17 | "@content-collections/next": "^0.2.4", 18 | "@hookform/resolvers": "^3.3.4", 19 | "@mdx-js/loader": "^3.0.1", 20 | "@radix-ui/react-accordion": "^1.1.2", 21 | "@radix-ui/react-aspect-ratio": "^1.0.3", 22 | "@radix-ui/react-collapsible": "^1.0.3", 23 | "@radix-ui/react-dialog": "^1.0.5", 24 | "@radix-ui/react-dropdown-menu": "^2.0.6", 25 | "@radix-ui/react-icons": "^1.3.0", 26 | "@radix-ui/react-label": "^2.0.2", 27 | "@radix-ui/react-popover": "^1.0.7", 28 | "@radix-ui/react-scroll-area": "^1.0.5", 29 | "@radix-ui/react-select": "^2.0.0", 30 | "@radix-ui/react-slot": "^1.0.2", 31 | "@radix-ui/react-tabs": "^1.0.4", 32 | "@radix-ui/react-tooltip": "^1.0.7", 33 | "@radix-ui/react-use-controllable-state": "^1.0.1", 34 | "@t3-oss/env-nextjs": "^0.9.2", 35 | "@tailwindcss/typography": "^0.5.10", 36 | "@tanstack/react-virtual": "^3.0.2", 37 | "@types/unist": "^3.0.2", 38 | "chrono-node": "^2.7.5", 39 | "class-variance-authority": "^0.7.0", 40 | "clsx": "^2.1.0", 41 | "cmdk": "^0.2.1", 42 | "date-fns": "^3.3.1", 43 | "embla-carousel": "^8.0.1", 44 | "embla-carousel-auto-scroll": "^8.0.1", 45 | "embla-carousel-react": "^8.0.1", 46 | "eslint-config-next": "^14.1.2", 47 | "lucide-react": "^0.365.0", 48 | "mdast-util-toc": "^7.0.0", 49 | "mini-svg-data-uri": "^1.4.4", 50 | "next": "^14.1.2", 51 | "next-themes": "^0.4.4", 52 | "posthog-js": "^1.116.5", 53 | "react": "^18", 54 | "react-day-picker": "^8.10.0", 55 | "react-dom": "^18", 56 | "react-dropzone": "^14.2.3", 57 | "react-hook-form": "^7.51.1", 58 | "react-otp-input": "^3.1.1", 59 | "react-resizable-panels": "^2.0.12", 60 | "react-wrap-balancer": "^1.1.0", 61 | "rehype-pretty-code": "^0.14.0", 62 | "rehype-slug": "^6.0.0", 63 | "remark": "^15.0.1", 64 | "remark-code-import": "^1.2.0", 65 | "remark-gfm": "^4.0.0", 66 | "rimraf": "^5.0.5", 67 | "sonner": "^1.3.1", 68 | "tailwind-merge": "^2.2.1", 69 | "tailwind-scrollbar": "^3.0.5", 70 | "tailwindcss-animate": "^1.0.7", 71 | "timescape": "^0.4.1", 72 | "tsc": "^2.0.4", 73 | "tsx": "^4.15.5", 74 | "unist-builder": "^4.0.0", 75 | "unist-util-visit": "^5.0.0", 76 | "use-resize-observer": "^9.1.0", 77 | "vaul": "^0.9.0", 78 | "zod": "^3.22.4" 79 | }, 80 | "devDependencies": { 81 | "@content-collections/cli": "^0.1.6", 82 | "@content-collections/core": "^0.8.0", 83 | "@types/node": "^20", 84 | "@types/react": "^18", 85 | "@types/react-dom": "^18", 86 | "autoprefixer": "^10.0.1", 87 | "eslint": "^8", 88 | "postcss": "^8", 89 | "tailwindcss": "^3.3.0", 90 | "typescript": "^5" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /apps/extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/extension/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/extension/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BelkacemYerfa/shadcn-extension/5326f1afa62dca1afbd9f676a97c8967eef1e898/apps/extension/public/og.png -------------------------------------------------------------------------------- /apps/extension/public/registry/index.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "tree-view", 4 | "dependencies": [ 5 | "@tanstack/react-virtual", 6 | "use-resize-observer" 7 | ], 8 | "registryDependencies": [ 9 | "tree-view-api" 10 | ], 11 | "files": [ 12 | "extension/tree-view.tsx" 13 | ], 14 | "type": "components:extension" 15 | }, 16 | { 17 | "name": "tree-view-api", 18 | "dependencies": [ 19 | "@radix-ui/react-accordion" 20 | ], 21 | "uiDependencies": [ 22 | "button", 23 | "scroll-area" 24 | ], 25 | "files": [ 26 | "extension/tree-view-api.tsx" 27 | ], 28 | "type": "components:extension" 29 | }, 30 | { 31 | "name": "file-upload", 32 | "dependencies": [ 33 | "react-dropzone", 34 | "sonner" 35 | ], 36 | "uiDependencies": [ 37 | "button", 38 | "input" 39 | ], 40 | "files": [ 41 | "extension/file-upload.tsx" 42 | ], 43 | "type": "components:extension" 44 | }, 45 | { 46 | "name": "multi-select", 47 | "dependencies": [ 48 | "command", 49 | "cmdk" 50 | ], 51 | "uiDependencies": [ 52 | "badge", 53 | "command" 54 | ], 55 | "files": [ 56 | "extension/multi-select.tsx" 57 | ], 58 | "type": "components:extension" 59 | }, 60 | { 61 | "name": "otp-input", 62 | "dependencies": [ 63 | "react-otp-input" 64 | ], 65 | "uiDependencies": [ 66 | "input" 67 | ], 68 | "files": [ 69 | "extension/otp-input.tsx" 70 | ], 71 | "type": "components:extension" 72 | }, 73 | { 74 | "name": "carousel", 75 | "dependencies": [ 76 | "embla-carousel-react", 77 | "embla-carousel", 78 | "@radix-ui/react-icons" 79 | ], 80 | "uiDependencies": [ 81 | "button" 82 | ], 83 | "files": [ 84 | "extension/carousel.tsx" 85 | ], 86 | "type": "components:extension" 87 | }, 88 | { 89 | "name": "breadcrumb", 90 | "dependencies": [ 91 | "@radix-ui/react-popover" 92 | ], 93 | "uiDependencies": [ 94 | "button", 95 | "popover" 96 | ], 97 | "files": [ 98 | "extension/breadcrumb.tsx" 99 | ], 100 | "type": "components:extension" 101 | }, 102 | { 103 | "name": "image-carousel-upload", 104 | "dependencies": [ 105 | "react-dropzone", 106 | "embla-carousel-react", 107 | "embla-carousel" 108 | ], 109 | "registryDependencies": [ 110 | "carousel" 111 | ], 112 | "uiDependencies": [ 113 | "input" 114 | ], 115 | "files": [ 116 | "extension/image-carousel-upload.tsx" 117 | ], 118 | "type": "components:extension" 119 | }, 120 | { 121 | "name": "smart-datetime-input", 122 | "dependencies": [ 123 | "chrono-node", 124 | "react-day-picker" 125 | ], 126 | "uiDependencies": [ 127 | "popover", 128 | "calendar", 129 | "input", 130 | "button", 131 | "scroll-area" 132 | ], 133 | "files": [ 134 | "extension/smart-datetime-input.tsx" 135 | ], 136 | "type": "components:extension" 137 | }, 138 | { 139 | "name": "datetime-picker", 140 | "dependencies": [ 141 | "timescape" 142 | ], 143 | "uiDependencies": [ 144 | "input" 145 | ], 146 | "files": [ 147 | "extension/datetime-picker.tsx" 148 | ], 149 | "type": "components:extension" 150 | }, 151 | { 152 | "name": "tags-input", 153 | "dependencies": [ 154 | "badge" 155 | ], 156 | "uiDependencies": [ 157 | "input", 158 | "badge" 159 | ], 160 | "files": [ 161 | "extension/tags-input.tsx" 162 | ], 163 | "type": "components:extension" 164 | } 165 | ] -------------------------------------------------------------------------------- /apps/extension/public/registry/otp-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otp-input", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "react-otp-input" 6 | ], 7 | "registryDependencies": [ 8 | "input" 9 | ], 10 | "files": [ 11 | { 12 | "path": "./registry/default/extension/otp-input.tsx", 13 | "type": "registry:ui", 14 | "content": "import React from \"react\";\nimport { Input } from \"@/components/ui/input\";\nimport { cn } from \"@/lib/utils\";\nimport OtpInput, { OTPInputProps } from \"react-otp-input\";\n\ntype OtpOptions = Omit;\n\ntype OtpStyledInputProps = {\n className?: string;\n} & OtpOptions;\n\n/**\n * Otp input Docs: {@link: https://shadcn-extension.vercel.app/docs/otp-input}\n */\n\nexport const OtpStyledInput = ({\n className,\n ...props\n}: OtpStyledInputProps) => {\n return (\n (\n \n )}\n containerStyle={`flex justify-center items-center flex-wrap text-2xl font-bold ${\n props.renderSeparator ? \"gap-1\" : \"gap-x-3 gap-y-2\"\n }`}\n />\n );\n};\n" 15 | } 16 | ], 17 | "docs": "https://shadcn-extension.vercel.app/docs/otp-input" 18 | } -------------------------------------------------------------------------------- /apps/extension/public/registry/tree-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-view", 3 | "type": "registry:ui", 4 | "dependencies": [ 5 | "@tanstack/react-virtual", 6 | "use-resize-observer" 7 | ], 8 | "registryDependencies": [ 9 | "https://shadcn-extension.vercel.app/registry/tree-view-api.json" 10 | ], 11 | "files": [ 12 | { 13 | "path": "./registry/default/extension/tree-view.tsx", 14 | "type": "registry:ui", 15 | "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport React, { forwardRef, useCallback, useRef } from \"react\";\nimport useResizeObserver from \"use-resize-observer\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport {\n Tree,\n Folder,\n File,\n CollapseButton,\n TreeViewElement,\n} from \"./tree-view-api\";\n\n// TODO: Add the ability to add custom icons\n\ninterface TreeViewComponentProps extends React.HTMLAttributes {}\n\ntype TreeViewProps = {\n initialSelectedId?: string;\n elements: TreeViewElement[];\n indicator?: boolean;\n} & (\n | {\n initialExpendedItems?: string[];\n expandAll?: false;\n }\n | {\n initialExpendedItems?: undefined;\n expandAll: true;\n }\n) &\n TreeViewComponentProps;\n\n/**\n * Tree View Docs: {@link: https://shadcn-extension.vercel.app/docs/tree-view}\n */\n\nexport const TreeView = ({\n elements,\n className,\n initialSelectedId,\n initialExpendedItems,\n expandAll,\n indicator = false,\n}: TreeViewProps) => {\n const containerRef = useRef(null);\n\n const { getVirtualItems, getTotalSize } = useVirtualizer({\n count: elements.length,\n getScrollElement: () => containerRef.current,\n estimateSize: useCallback(() => 40, []),\n overscan: 5,\n });\n\n const { height = getTotalSize(), width } = useResizeObserver({\n ref: containerRef,\n });\n return (\n \n \n {getVirtualItems().map((element) => (\n \n ))}\n \n Expand All\n \n \n \n );\n};\n\nTreeView.displayName = \"TreeView\";\n\nexport const TreeItem = forwardRef<\n HTMLUListElement,\n {\n elements?: TreeViewElement[];\n indicator?: boolean;\n } & React.HTMLAttributes\n>(({ className, elements, indicator, ...props }, ref) => {\n return (\n
    \n {elements &&\n elements.map((element) => (\n
  • \n {element.children && element.children?.length > 0 ? (\n \n \n \n ) : (\n \n {element?.name}\n \n )}\n
  • \n ))}\n
\n );\n});\n\nTreeItem.displayName = \"TreeItem\";\n" 16 | } 17 | ], 18 | "docs": "https://shadcn-extension.vercel.app/docs/tree-view" 19 | } -------------------------------------------------------------------------------- /apps/extension/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/extension/src/__registry__/.autogenerated: -------------------------------------------------------------------------------- 1 | // The content of this directory is autogenerated by the registry server. 2 | -------------------------------------------------------------------------------- /apps/extension/src/__registry__/READMe.md: -------------------------------------------------------------------------------- 1 | > Files inside this directory is autogenerated by `./script/build-registry.ts`. **Do not edit them manually.** 2 | -------------------------------------------------------------------------------- /apps/extension/src/app/components/page.tsx: -------------------------------------------------------------------------------- 1 | import { Index } from "@/__registry__"; 2 | import { ComponentCard } from "@/components/cards/component-card"; 3 | import { SiteFooter } from "@/components/layouts/site-footer"; 4 | import { type Metadata } from "next"; 5 | 6 | export const metadata: Metadata = { 7 | title: "Components", 8 | description: "Browse all the components available in the registry.", 9 | }; 10 | 11 | export default function ComponentsPage() { 12 | return ( 13 |
17 |
18 |
19 |

20 | Browse Components 21 |

22 |

23 | Navigate to all the components available in the registry. 24 |

25 |
26 |
27 | {Object.entries(Index).map(([_, value]) => { 28 | return Object.entries(value).map( 29 | ([key, newValue]: [key: string, newValue: any]) => { 30 | const componentName = key; 31 | return newValue.type === "components:demo" ? ( 32 | 33 | ) : null; 34 | }, 35 | ); 36 | })} 37 |
38 |
39 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /apps/extension/src/app/docs/[[...slug]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { allDocs as docs } from "content-collections"; 3 | import { Mdx } from "@/components/mdx-component"; 4 | import { notFound } from "next/navigation"; 5 | import { DocsPager } from "@/components/pager"; 6 | import { DocMainTOC, Toc } from "@/components/layouts/toc"; 7 | import { getTableOfContents } from "@/lib/toc"; 8 | import Balancer from "react-wrap-balancer"; 9 | import { cn } from "@/lib/utils"; 10 | import { DocsBreadcrumb } from "@/components/doc-breadcrumb"; 11 | import { siteConfig } from "@/config/site-config"; 12 | import { ScrollArea } from "@/components/ui/scroll-area"; 13 | import { PrimitiveLink } from "@/components/primitive-link"; 14 | 15 | type DocsPageProps = { 16 | params: { 17 | slug: string[]; 18 | }; 19 | }; 20 | 21 | async function getDocFromParams({ params }: DocsPageProps) { 22 | let slug = params.slug?.join("/") || ""; 23 | slug += ".mdx"; 24 | const doc = docs.find((doc) => doc.slugAsParams === slug); 25 | 26 | if (!doc) { 27 | return null; 28 | } 29 | 30 | return doc; 31 | } 32 | 33 | export async function generateMetadata({ 34 | params, 35 | }: DocsPageProps): Promise { 36 | const doc = await getDocFromParams({ params }); 37 | if (doc == null) return {}; 38 | return { 39 | title: doc.title, 40 | description: doc.description, 41 | openGraph: { 42 | type: "website", 43 | url: new URL(`/docs/${doc.slug}`, siteConfig.url).toString(), 44 | locale: "en_US", 45 | title: siteConfig.name, 46 | description: siteConfig.description, 47 | siteName: siteConfig.name, 48 | images: [ 49 | { 50 | url: siteConfig.ogImage, 51 | width: 1200, 52 | height: 630, 53 | alt: siteConfig.name, 54 | }, 55 | ], 56 | }, 57 | }; 58 | } 59 | export default async function CurrentSlugPage({ params }: DocsPageProps) { 60 | const doc = await getDocFromParams({ params }); 61 | 62 | if (!doc) notFound(); 63 | 64 | const toc = await getTableOfContents(doc.body.raw); 65 | return ( 66 |
70 |
71 |
72 | 73 | 74 |

75 | {doc.title} 76 |

77 | {doc.description && ( 78 |

79 | {doc.description} 80 |

81 | )} 82 | {doc.links && ( 83 |
84 | {doc.links.map((link) => ( 85 | 86 | {link.title} 87 | 88 | ))} 89 |
90 | )} 91 |
92 | 93 | 94 |
95 | {toc.children && ( 96 |
97 |
98 | 99 |
100 | 101 |
102 |
103 |
104 |
105 | )} 106 |
107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /apps/extension/src/app/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { siteConfig } from "@/config/site-config"; 3 | import { SideBar } from "@/components/side-bar"; 4 | import { ScrollArea } from "@/components/ui/scroll-area"; 5 | import { SiteFooter } from "@/components/layouts/site-footer"; 6 | 7 | export const metadata: Metadata = { 8 | title: "Docs - Shadcn extension", 9 | description: 10 | "Shadcn extension for Next.js with Tailwind CSS and TypeScript , sonner and vercel analytics , and more.", 11 | creator: "Belkacem Yerfa", 12 | metadataBase: new URL(siteConfig.url), 13 | openGraph: { 14 | type: "website", 15 | url: siteConfig.url, 16 | locale: "en_US", 17 | title: siteConfig.name, 18 | description: siteConfig.description, 19 | siteName: siteConfig.name, 20 | images: [ 21 | { 22 | url: siteConfig.ogImage, 23 | width: 1200, 24 | height: 630, 25 | alt: siteConfig.name, 26 | }, 27 | ], 28 | }, 29 | twitter: { 30 | card: "summary_large_image", 31 | title: siteConfig.name, 32 | description: siteConfig.description, 33 | images: [siteConfig.ogImage], 34 | creator: "@bylka207", 35 | }, 36 | }; 37 | 38 | export default function DocsLayout({ 39 | children, 40 | }: Readonly<{ 41 | children: React.ReactNode; 42 | }>) { 43 | return ( 44 | <> 45 |
46 | 51 | {children} 52 |
53 |
54 | 55 |
56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /apps/extension/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BelkacemYerfa/shadcn-extension/5326f1afa62dca1afbd9f676a97c8967eef1e898/apps/extension/src/app/favicon.ico -------------------------------------------------------------------------------- /apps/extension/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | import { Provider } from "@/components/provider"; 5 | import { Toaster } from "sonner"; 6 | import { SiteHeader } from "@/components/layouts/site-header"; 7 | import { CSPostHogProvider } from "@/components/analytics"; 8 | import { siteConfig } from "@/config/site-config"; 9 | import { cn } from "@/lib/utils"; 10 | import Link from "next/link"; 11 | import { buttonVariants } from "@/components/ui/button"; 12 | import { SkipNav } from "@/components/skip-nav"; 13 | 14 | const inter = Inter({ subsets: ["latin"] }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Shadcn extension", 18 | description: 19 | "Shadcn extension for Next.js with Tailwind CSS and TypeScript , sonner and vercel analytics , and more.", 20 | creator: "Belkacem Yerfa", 21 | metadataBase: new URL(siteConfig.url), 22 | openGraph: { 23 | type: "website", 24 | url: siteConfig.url, 25 | locale: "en_US", 26 | title: siteConfig.name, 27 | description: siteConfig.description, 28 | siteName: siteConfig.name, 29 | images: [ 30 | { 31 | url: siteConfig.ogImage, 32 | width: 1200, 33 | height: 630, 34 | alt: siteConfig.name, 35 | }, 36 | ], 37 | }, 38 | twitter: { 39 | card: "summary_large_image", 40 | title: siteConfig.name, 41 | description: siteConfig.description, 42 | images: [siteConfig.ogImage], 43 | creator: "@bylka207", 44 | }, 45 | }; 46 | 47 | export default function RootLayout({ 48 | children, 49 | }: Readonly<{ 50 | children: React.ReactNode; 51 | }>) { 52 | return ( 53 | 54 | 60 | 61 | 62 | 63 | 64 | {children} 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /apps/extension/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Icons } from "@/components/icons"; 2 | import { SiteFooter } from "@/components/layouts/site-footer"; 3 | import { buttonVariants } from "@/components/ui/button"; 4 | import { siteConfig } from "@/config/site-config"; 5 | import { cn } from "@/lib/utils"; 6 | import Link from "next/link"; 7 | import Balancer from "react-wrap-balancer"; 8 | 9 | export default async function Home() { 10 | return ( 11 |
12 |
13 | 22 | 23 | Discord community 24 | 25 |

32 | 33 | Extend your component 34 | library 35 | 36 |

37 | 44 | {siteConfig.description} 45 | 46 |
53 | 57 | Get Started 58 | 59 | 64 | GitHub 65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /apps/extension/src/components/analytics.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import posthog from "posthog-js"; 4 | import { PostHogProvider } from "posthog-js/react"; 5 | import { env } from "@/env"; 6 | 7 | if (typeof window !== "undefined") { 8 | if (process.env.NODE_ENV === "production") { 9 | posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, { 10 | api_host: env.NEXT_PUBLIC_POSTHOG_HOST, 11 | }); 12 | } 13 | } 14 | export function CSPostHogProvider({ children }: { children: React.ReactNode }) { 15 | if (process.env.NODE_ENV === "development") return <>{children}; 16 | return {children}; 17 | } 18 | -------------------------------------------------------------------------------- /apps/extension/src/components/banner.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { buttonVariants } from "@/components/ui/button"; 3 | import { cn } from "@/lib/utils"; 4 | import { siteConfig } from "@/config/site-config"; 5 | 6 | export const Banner = () => { 7 | return ( 8 |
9 | 🥳 New docs for {siteConfig.name} are live. 10 | 20 | Check them out 21 | 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /apps/extension/src/components/callout.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 2 | 3 | interface CalloutProps { 4 | icon?: string; 5 | title?: string; 6 | children?: React.ReactNode; 7 | } 8 | 9 | export function Callout({ title, children, icon, ...props }: CalloutProps) { 10 | return ( 11 | 12 | {icon && {icon}} 13 | {title && {title}} 14 | {children} 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/extension/src/components/cards/component-card.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Index } from "@/__registry__"; 4 | import { styles } from "@/registry/styles"; 5 | import { Card, CardContent, CardTitle } from "@ui/card"; 6 | import { MoveRight } from "lucide-react"; 7 | import Link from "next/link"; 8 | import { Suspense } from "react"; 9 | import { useMemo } from "react"; 10 | 11 | type ComponentCardProps = { 12 | name: string; 13 | }; 14 | 15 | export const ComponentCard = ({ name }: ComponentCardProps) => { 16 | const Preview = useMemo(() => { 17 | const Component = Index[styles[0].name][name]?.component; 18 | 19 | if (!Component) { 20 | return ( 21 |

22 | Component{" "} 23 | 24 | button 25 | {" "} 26 | not found in registry. 27 |

28 | ); 29 | } 30 | 31 | return ; 32 | }, [name]); 33 | 34 | const componentName = name.replace("demo", "").split("-").join(" "); 35 | 36 | return ( 37 | 38 | 39 | 47 | {componentName} 48 | 49 | 50 | 51 | 52 | 53 |
{Preview}
54 |
55 |
56 | ); 57 | }; 58 | 59 | const FocusArea = () => { 60 | return ( 61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /apps/extension/src/components/code-block-wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | import { Button } from "@/components/ui/button"; 7 | import { 8 | Collapsible, 9 | CollapsibleContent, 10 | CollapsibleTrigger, 11 | } from "@/components/ui/collapsible"; 12 | 13 | interface CodeBlockProps extends React.HTMLAttributes { 14 | expandButtonTitle?: string; 15 | } 16 | 17 | export function CodeBlockWrapper({ 18 | expandButtonTitle = "View Code", 19 | className, 20 | children, 21 | ...props 22 | }: CodeBlockProps) { 23 | const [isOpened, setIsOpened] = React.useState(false); 24 | 25 | return ( 26 | 27 |
28 | 32 |
38 | {children} 39 |
40 |
41 |
47 | 48 | 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /apps/extension/src/components/component-source.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | import { CodeBlockWrapper } from "@/components/code-block-wrapper"; 7 | 8 | interface ComponentSourceProps extends React.HTMLAttributes { 9 | src: string; 10 | } 11 | 12 | export function ComponentSource({ 13 | children, 14 | className, 15 | ...props 16 | }: ComponentSourceProps) { 17 | return ( 18 | 22 | {children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /apps/extension/src/components/doc-breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { 5 | BreadCrumb, 6 | BreadCrumbItem, 7 | BreadCrumbSeparator, 8 | } from "@/registry/default/extension/breadcrumb"; 9 | import Link from "next/link"; 10 | import { usePathname } from "next/navigation"; 11 | 12 | type DocsBreadcrumbProps = { 13 | slug: string[]; 14 | }; 15 | 16 | export const DocsBreadcrumb = ({ slug }: DocsBreadcrumbProps) => { 17 | const pathname = usePathname(); 18 | const pathItems = pathname.split("/"); 19 | pathItems.shift(); 20 | return ( 21 | 25 | {pathItems.map((path, index) => { 26 | const isActive = slug.join("/") === path; 27 | return ( 28 |
32 | 33 | 44 | {path.replaceAll("-", " ")} 45 | 46 | 47 | {index !== pathItems.length - 1 && ( 48 | 49 | )} 50 |
51 | ); 52 | })} 53 |
54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /apps/extension/src/components/drop-downs/search-selector.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; 5 | import { useRouter } from "next/navigation"; 6 | import { cn } from "@/lib/utils"; 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | Command, 10 | CommandEmpty, 11 | CommandGroup, 12 | CommandInput, 13 | CommandItem, 14 | } from "@/components/ui/command"; 15 | import { 16 | Popover, 17 | PopoverContent, 18 | PopoverTrigger, 19 | } from "@/components/ui/popover"; 20 | 21 | type ThemeComboboxProps = { 22 | value: string; 23 | onValueChange: (value: string) => void; 24 | options: { label: string; value: string }[]; 25 | placeholder?: string; 26 | noneResult?: string; 27 | createQuery: (params: Record) => string; 28 | } & ({ comp: true; theme?: undefined } | { comp?: undefined; theme: true }); 29 | 30 | export const PlaygroundSearchSelector = ({ 31 | value, 32 | onValueChange, 33 | options, 34 | placeholder = "Select ...", 35 | noneResult = "Doesn't exist.", 36 | createQuery, 37 | comp, 38 | }: ThemeComboboxProps) => { 39 | const router = useRouter(); 40 | const [open, setOpen] = React.useState(false); 41 | return ( 42 | 43 | 44 | 55 | 56 | 57 | 58 | 59 | {noneResult} 60 | 61 | {options.map((opt) => ( 62 | { 66 | onValueChange(opt.value); 67 | router.push( 68 | `?${createQuery({ 69 | [comp ? "comp" : "theme"]: opt.value, 70 | })}`, 71 | ); 72 | setOpen(false); 73 | }} 74 | > 75 | {opt.label} 76 | 82 | 83 | ))} 84 | 85 | 86 | 87 | 88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /apps/extension/src/components/layouts/site-footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { siteConfig } from "@/config/site-config"; 3 | import { SocialLinks } from "../social-links"; 4 | 5 | export const SiteFooter = () => { 6 | return ( 7 |
8 |

9 | 10 | Built by{" "} 11 | 17 | Yerfa Belkacem 18 | 19 | 20 |

21 | 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /apps/extension/src/components/layouts/site-header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { Icons } from "../icons"; 5 | import { Balancer } from "react-wrap-balancer"; 6 | import { siteConfig } from "@/config/site-config"; 7 | import { SocialLinks } from "../social-links"; 8 | import { ModeToggle } from "../toggle-theme"; 9 | import { SearchPopOver } from "../search"; 10 | import { Pages } from "@/config/docs-config"; 11 | import { usePathname } from "next/navigation"; 12 | import { cn } from "@/lib/utils"; 13 | import { Banner } from "@/components/banner"; 14 | import { useState } from "react"; 15 | import { Sheet, SheetContent, SheetTrigger } from "../ui/sheet"; 16 | import { Menu } from "lucide-react"; 17 | import { SideBar } from "../side-bar"; 18 | 19 | export const SiteHeader = () => { 20 | const pathname = usePathname(); 21 | return ( 22 |
23 | {pathname === "/" && <>} 24 |
25 | 63 |
64 |
65 | ); 66 | }; 67 | 68 | const MobileMenu = () => { 69 | const [open, setOpen] = useState(false); 70 | return ( 71 |
72 | 73 | 74 | 75 | menu 76 | 77 | 78 | 79 | 80 | 81 |
82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /apps/extension/src/components/loaders/editor-loader.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton"; 2 | 3 | export const EditorLoader = () => { 4 | return ( 5 |
6 |
7 |
8 | {Array.from({ length: 5 }).map((_, i) => ( 9 | 10 | ))} 11 |
12 |
13 |
14 | {Array.from({ length: 5 }).map((_, i) => ( 15 | 16 | ))} 17 |
18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /apps/extension/src/components/loaders/playground-loader.tsx: -------------------------------------------------------------------------------- 1 | import { EditorLoader } from "./editor-loader"; 2 | import { ViewLoader } from "./view-output-loader"; 3 | 4 | export const PlaygroundLoader = () => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /apps/extension/src/components/loaders/view-output-loader.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "../ui/skeleton"; 2 | 3 | export const ViewLoader = () => { 4 | return ( 5 |
6 |
7 | 8 | 9 |
10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /apps/extension/src/components/pager.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"; 3 | import { Doc } from "content-collections"; 4 | 5 | import { DocsConfig, docsConfig } from "@/config/docs-config"; 6 | import { cn } from "@/lib/utils"; 7 | import { buttonVariants } from "@/components/ui/button"; 8 | 9 | interface DocsPagerProps { 10 | doc: Doc; 11 | } 12 | 13 | export function DocsPager({ doc }: DocsPagerProps) { 14 | const pager = getPagerForDoc(doc); 15 | 16 | if (!pager) { 17 | return null; 18 | } 19 | 20 | return ( 21 |
22 | {pager?.prev?.path && ( 23 | 27 | 28 | {pager.prev.title} 29 | 30 | )} 31 | {pager?.next?.path && ( 32 | 36 | {pager.next.title} 37 | 38 | 39 | )} 40 |
41 | ); 42 | } 43 | 44 | export function getPagerForDoc(doc: Doc) { 45 | const flattenedLinks = [null, ...flatten(docsConfig), null]; 46 | const activeIndex = flattenedLinks.findIndex( 47 | (link) => doc.slug === link?.path, 48 | ); 49 | const prev = activeIndex !== 0 ? flattenedLinks[activeIndex - 1] : null; 50 | const next = 51 | activeIndex !== flattenedLinks.length - 1 52 | ? flattenedLinks[activeIndex + 1] 53 | : null; 54 | return { 55 | prev, 56 | next, 57 | }; 58 | } 59 | 60 | export type FlatDocsConfig = { 61 | title: string; 62 | path?: string; 63 | }; 64 | 65 | function flatten( 66 | links: DocsConfig[], 67 | parentPath: string = "", 68 | ): FlatDocsConfig[] { 69 | return links.reduce((acc, link) => { 70 | const currentPath = parentPath ? `${parentPath}/${link.title}` : link.title; 71 | const flatLink: FlatDocsConfig = { 72 | title: link.title, 73 | path: link.path, 74 | }; 75 | if (link.path) { 76 | acc.push(flatLink); 77 | } 78 | 79 | if (link.pages) { 80 | acc.push(...flatten(link.pages, currentPath)); 81 | } 82 | 83 | return acc; 84 | }, []); 85 | } 86 | -------------------------------------------------------------------------------- /apps/extension/src/components/primitive-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Icons } from "./icons"; 3 | import { badgeVariants } from "@ui/badge"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | export const extractDomain = async (link: string) => { 7 | const domain = new URL(link).hostname 8 | .replaceAll("www.", "") 9 | .split(".")[0] 10 | .replace(/-/g, " ") 11 | .split(" ") 12 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 13 | .join(""); 14 | return domain; 15 | }; 16 | 17 | interface PrimitiveLinkProps 18 | extends React.AnchorHTMLAttributes {} 19 | 20 | export const PrimitiveLink = async ({ 21 | href, 22 | className, 23 | children, 24 | ...props 25 | }: PrimitiveLinkProps) => { 26 | if (!href) return null; 27 | 28 | const currentDomain = await extractDomain(href); 29 | const Icon = Icons[currentDomain as keyof typeof Icons]; 30 | 31 | return ( 32 | 43 | 44 | {children} 45 | 46 | ); 47 | }; 48 | 49 | PrimitiveLink.displayName = "PrimitiveLink"; 50 | -------------------------------------------------------------------------------- /apps/extension/src/components/provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ThemeProvider } from "next-themes"; 4 | 5 | export const Provider = ({ children }: { children: React.ReactNode }) => { 6 | return {children}; 7 | }; 8 | -------------------------------------------------------------------------------- /apps/extension/src/components/side-bar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { docsConfig } from "@/config/docs-config"; 4 | import { cn } from "@/lib/utils"; 5 | import Link from "next/link"; 6 | import { buttonVariants } from "./ui/button"; 7 | import { usePathname } from "next/navigation"; 8 | 9 | interface SideBarProps { 10 | setOpen?: (open: boolean) => void; 11 | } 12 | 13 | export const SideBar = ({ setOpen }: SideBarProps) => { 14 | const pathname = usePathname(); 15 | return ( 16 |
17 | {docsConfig.map((section, _) => ( 18 |
19 |

{section.title}

20 |
21 | {section.pages && 22 | section.pages.map((page, _) => ( 23 | setOpen?.(false)} 27 | className={cn( 28 | buttonVariants({ 29 | variant: "ghost", 30 | }), 31 | "w-full justify-start px-2 h-8 transition-all", 32 | { 33 | "text-accent-foreground bg-accent": 34 | pathname === page.path, 35 | "text-muted-foreground": pathname !== page.path, 36 | }, 37 | )} 38 | > 39 | {page.title} 40 | 41 | ))} 42 |
43 |
44 | ))} 45 |
46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /apps/extension/src/components/skip-nav.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { buttonVariants } from "./ui/button"; 4 | import { cn } from "@/lib/utils"; 5 | export const SkipNav = () => { 6 | return ( 7 | 14 | Skip Navigation 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /apps/extension/src/components/social-links.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Icons } from "./icons"; 3 | import { ModeToggle } from "./toggle-theme"; 4 | import { siteConfig } from "@/config/site-config"; 5 | import { cn } from "@/lib/utils"; 6 | import { buttonVariants } from "./ui/button"; 7 | 8 | export const SocialLinks = ({ className }: { className: string }) => { 9 | return ( 10 |
11 | 21 | 22 | 23 | 33 | 34 | 35 |
36 | 37 |
38 |
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /apps/extension/src/components/toggle-theme.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { LaptopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons"; 4 | import { useTheme } from "next-themes"; 5 | import { Button } from "@/components/ui/button"; 6 | import { useMounted } from "@/hooks/use-mounted"; 7 | import { cn } from "@/lib/utils"; 8 | import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover"; 9 | 10 | const OptionMode = [ 11 | { 12 | value: "light", 13 | label: "Light", 14 | icon: SunIcon, 15 | }, 16 | { 17 | value: "dark", 18 | label: "Dark", 19 | icon: MoonIcon, 20 | }, 21 | { 22 | value: "system", 23 | label: "System", 24 | icon: LaptopIcon, 25 | }, 26 | ] satisfies { value: string; label: string; icon: any }[]; 27 | 28 | type ModeToggleProps = { 29 | isDesktop?: boolean; 30 | }; 31 | 32 | export function ModeToggle({ isDesktop = true }: ModeToggleProps) { 33 | const { theme, setTheme } = useTheme(); 34 | const mounted = useMounted(); 35 | return mounted ? ( 36 | isDesktop ? ( 37 | 38 | 39 | 44 | 45 | 46 | {OptionMode.map((option) => { 47 | const Icon = option.icon; 48 | return ( 49 | 61 | ); 62 | })} 63 | 64 | 65 | ) : ( 66 | 76 | ) 77 | ) : ( 78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"; 5 | import { ChevronDownIcon } from "@radix-ui/react-icons"; 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 | AccordionContent.displayName = AccordionPrimitive.Content.displayName; 56 | 57 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; 58 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cva, type VariantProps } from "class-variance-authority"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | info: "border-info/50 text-info/90 [&>svg]:text-info", 13 | warning: "border-warning/50 text-warning/90 [&>svg]:text-warning", 14 | destructive: 15 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 16 | }, 17 | }, 18 | defaultVariants: { 19 | variant: "default", 20 | }, 21 | }, 22 | ); 23 | 24 | const Alert = React.forwardRef< 25 | HTMLDivElement, 26 | React.HTMLAttributes & VariantProps 27 | >(({ className, variant, ...props }, ref) => ( 28 |
34 | )); 35 | Alert.displayName = "Alert"; 36 | 37 | const AlertTitle = React.forwardRef< 38 | HTMLParagraphElement, 39 | React.HTMLAttributes 40 | >(({ className, ...props }, ref) => ( 41 |
46 | )); 47 | AlertTitle.displayName = "AlertTitle"; 48 | 49 | const AlertDescription = React.forwardRef< 50 | HTMLParagraphElement, 51 | React.HTMLAttributes 52 | >(({ className, ...props }, ref) => ( 53 |
58 | )); 59 | AlertDescription.displayName = "AlertDescription"; 60 | 61 | export { Alert, AlertTitle, AlertDescription }; 62 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root; 6 | 7 | export { AspectRatio }; 8 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cva, type VariantProps } from "class-variance-authority"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow 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 shadow 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 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-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-sm 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/extension/src/components/ui/calendar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"; 5 | import { DayPicker } from "react-day-picker"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | import { buttonVariants } from "@/components/ui/button"; 9 | 10 | export type CalendarProps = React.ComponentProps; 11 | 12 | function Calendar({ 13 | className, 14 | classNames, 15 | showOutsideDays = true, 16 | ...props 17 | }: CalendarProps) { 18 | return ( 19 | .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" 43 | : "[&:has([aria-selected])]:rounded-md", 44 | ), 45 | day: cn( 46 | buttonVariants({ variant: "ghost" }), 47 | "h-8 w-8 p-0 font-normal aria-selected:opacity-100", 48 | ), 49 | day_range_start: "day-range-start", 50 | day_range_end: "day-range-end", 51 | day_selected: 52 | "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", 53 | day_today: "bg-accent text-accent-foreground", 54 | day_outside: 55 | "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", 56 | day_disabled: "text-muted-foreground opacity-50", 57 | day_range_middle: 58 | "aria-selected:bg-accent aria-selected:text-accent-foreground", 59 | day_hidden: "invisible", 60 | ...classNames, 61 | }} 62 | components={{ 63 | IconLeft: ({ ...props }) => , 64 | IconRight: ({ ...props }) => , 65 | }} 66 | {...props} 67 | /> 68 | ); 69 | } 70 | Calendar.displayName = "Calendar"; 71 | 72 | export { Calendar }; 73 | -------------------------------------------------------------------------------- /apps/extension/src/components/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 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

41 | )); 42 | CardTitle.displayName = "CardTitle"; 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 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/extension/src/components/ui/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, CollapsibleTrigger, CollapsibleContent }; 12 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/date-picker.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { format } from "date-fns"; 5 | import { Calendar as CalendarIcon } from "lucide-react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Calendar } from "@/components/ui/calendar"; 10 | import { 11 | Popover, 12 | PopoverContent, 13 | PopoverTrigger, 14 | } from "@/components/ui/popover"; 15 | 16 | export function DatePicker() { 17 | const [date, setDate] = React.useState(); 18 | 19 | return ( 20 | 21 | 22 | 32 | 33 | 34 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /apps/extension/src/components/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 "@/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/extension/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | }, 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | import { cva, type VariantProps } from "class-variance-authority"; 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/extension/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Popover = PopoverPrimitive.Root; 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger; 11 | 12 | const PopoverAnchor = PopoverPrimitive.Anchor; 13 | 14 | const PopoverContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 18 | 19 | 29 | 30 | )); 31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 32 | 33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; 34 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { DragHandleDots2Icon } from "@radix-ui/react-icons"; 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 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; 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 | -------------------------------------------------------------------------------- /apps/extension/src/components/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/extension/src/components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )); 17 | Table.displayName = "Table"; 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )); 25 | TableHeader.displayName = "TableHeader"; 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )); 37 | TableBody.displayName = "TableBody"; 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className, 48 | )} 49 | {...props} 50 | /> 51 | )); 52 | TableFooter.displayName = "TableFooter"; 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )); 67 | TableRow.displayName = "TableRow"; 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
[role=checkbox]]:translate-y-[2px]", 77 | className, 78 | )} 79 | {...props} 80 | /> 81 | )); 82 | TableHead.displayName = "TableHead"; 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 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
(null); 29 | const form = useForm({ 30 | resolver: zodResolver(formSchema), 31 | defaultValues: { 32 | datetime: new Date(), 33 | }, 34 | shouldUseNativeValidation: true, 35 | }); 36 | 37 | const onSubmit = (data: Form) => { 38 | if (!data.datetime) return; 39 | setFormData((prev) => ({ ...prev, ...data })); 40 | toast.success("Form submitted : " + JSON.stringify(data, null, 2)); 41 | form.reset(); 42 | }; 43 | 44 | return ( 45 | 46 | 50 | <> 51 |
52 | 53 |

54 | Let's get this on the books. 55 |

56 |
57 | { 61 | return ( 62 | 63 | <> 64 | 65 | 69 | Let's put it in the books 70 | 71 | 78 | 79 | 80 | 81 | 82 | ); 83 | }} 84 | /> 85 | 86 |
87 | 95 |
96 | 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 | {file.name} 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
({ 46 | resolver: zodResolver(form), 47 | defaultValues: form.parse({ value: [users[0].name] }), 48 | }); 49 | 50 | const onSubmit = (data: Form) => { 51 | toast.success("Form submitted : " + JSON.stringify(data, null, 2)); 52 | }; 53 | 54 | return ( 55 | 56 | 60 | ( 64 | 65 | Invite people 66 | 70 | 71 | 72 | 73 | 74 | 75 | {users.map((user) => ( 76 | 77 | {user.name} 78 | 79 | ))} 80 | 81 | 82 | 83 | 84 | Select people to invite to this event 85 | 86 | 87 | 88 | )} 89 | /> 90 | 91 | 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 |
36 | 37 | ( 41 | 42 | <> 43 | 44 | 49 | 50 | 51 | 52 | 53 | )} 54 | /> 55 | 56 | 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
({ 38 | resolver: zodResolver(form), 39 | defaultValues: { 40 | otp: "", 41 | }, 42 | }); 43 | 44 | const onSubmit = (data: Form) => { 45 | toast.success("Form submitted : " + JSON.stringify(data, null, 2)); 46 | }; 47 | 48 | return ( 49 | 50 | 54 | ( 58 | 59 | <> 60 | 61 | Enter your confirmation password 62 | 63 | 64 | 69 | 89 | 90 | 91 | 92 | 93 | )} 94 | /> 95 | 98 | 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
({ 19 | resolver: zodResolver(form), 20 | defaultValues: { 21 | value: [], 22 | }, 23 | }); 24 | 25 | const onSubmit = (data: Form) => { 26 | toast.success(JSON.stringify(data)); 27 | }; 28 | return ( 29 | 30 | 34 | ( 38 | 39 | 45 | 46 | )} 47 | /> 48 | 51 | 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 | --------------------------------------------------------------------------------