diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..5e13055 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "shadcn-ui/ui" }], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["www", "**-template"] +} diff --git a/.changeset/mean-badgers-grab.md b/.changeset/mean-badgers-grab.md new file mode 100644 index 0000000..95f7f8b --- /dev/null +++ b/.changeset/mean-badgers-grab.md @@ -0,0 +1,5 @@ +--- +"shadcn-ui": patch +--- + +fix code style diff --git a/.changeset/smart-pants-smile.md b/.changeset/smart-pants-smile.md new file mode 100644 index 0000000..b353ce7 --- /dev/null +++ b/.changeset/smart-pants-smile.md @@ -0,0 +1,5 @@ +--- +"shadcn-ui": patch +--- + +use jsconfig for non-ts projects diff --git a/.changeset/sweet-worms-fix.md b/.changeset/sweet-worms-fix.md new file mode 100644 index 0000000..deaf083 --- /dev/null +++ b/.changeset/sweet-worms-fix.md @@ -0,0 +1,5 @@ +--- +"shadcn-ui": minor +--- + +add support for tailwind.config.ts diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 0000000..c30e5a9 --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["@commitlint/config-conventional"] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ae10a5c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..9a48847 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/eslintrc", + "root": true, + "extends": [ + "next/core-web-vitals", + "turbo", + "prettier", + "plugin:tailwindcss/recommended" + ], + "plugins": ["tailwindcss"], + "rules": { + "@next/next/no-html-link-for-pages": "off", + "tailwindcss/no-custom-classname": "off", + "tailwindcss/classnames-order": "error" + }, + "settings": { + "tailwindcss": { + "callees": ["cn", "cva"], + "config": "tailwind.config.cjs" + }, + "next": { + "rootDir": ["apps/*/"] + } + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "parser": "@typescript-eslint/parser" + } + ] +} diff --git a/.github/changeset-version.js b/.github/changeset-version.js new file mode 100644 index 0000000..e1b4cef --- /dev/null +++ b/.github/changeset-version.js @@ -0,0 +1,12 @@ +// ORIGINALLY FROM CLOUDFLARE WRANGLER: +// https://github.com/cloudflare/wrangler2/blob/main/.github/changeset-version.js + +import { exec } from "child_process" + +// This script is used by the `release.yml` workflow to update the version of the packages being released. +// The standard step is only to run `changeset version` but this does not update the package-lock.json file. +// So we also run `npm install`, which does this update. +// This is a workaround until this is handled automatically by `changeset version`. +// See https://github.com/changesets/changesets/issues/421. +exec("npx changeset version") +exec("npm install") diff --git a/.github/version-script-beta.js b/.github/version-script-beta.js new file mode 100644 index 0000000..4dd5ea2 --- /dev/null +++ b/.github/version-script-beta.js @@ -0,0 +1,21 @@ +// ORIGINALLY FROM CLOUDFLARE WRANGLER: +// https://github.com/cloudflare/wrangler2/blob/main/.github/version-script.js + +import { exec } from "child_process" +import fs from "fs" + +const pkgJsonPath = "packages/cli/package.json" +try { + const pkg = JSON.parse(fs.readFileSync(pkgJsonPath)) + exec("git rev-parse --short HEAD", (err, stdout) => { + if (err) { + console.log(err) + process.exit(1) + } + pkg.version = "0.0.0-beta." + stdout.trim() + fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, "\t") + "\n") + }) +} catch (error) { + console.error(error) + process.exit(1) +} diff --git a/.github/version-script-next.js b/.github/version-script-next.js new file mode 100644 index 0000000..c18a7af --- /dev/null +++ b/.github/version-script-next.js @@ -0,0 +1,21 @@ +// ORIGINALLY FROM CLOUDFLARE WRANGLER: +// https://github.com/cloudflare/wrangler2/blob/main/.github/version-script.js + +import { exec } from "child_process" +import fs from "fs" + +const pkgJsonPath = "packages/cli/package.json" +try { + const pkg = JSON.parse(fs.readFileSync(pkgJsonPath)) + exec("git rev-parse --short HEAD", (err, stdout) => { + if (err) { + console.log(err) + process.exit(1) + } + pkg.version = "0.0.0-next." + stdout.trim() + fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, "\t") + "\n") + }) +} catch (error) { + console.error(error) + process.exit(1) +} diff --git a/.github/workflows/code-check.yml b/.github/workflows/code-check.yml new file mode 100644 index 0000000..5024553 --- /dev/null +++ b/.github/workflows/code-check.yml @@ -0,0 +1,116 @@ +name: Code check + +on: + pull_request: + branches: ["*"] + +jobs: + lint: + runs-on: ubuntu-latest + name: pnpm lint + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - uses: pnpm/action-setup@v2.2.4 + name: Install pnpm + id: pnpm-install + with: + version: 8.6.1 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies + run: pnpm install + + - run: pnpm lint + + format: + runs-on: ubuntu-latest + name: pnpm format:check + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - uses: pnpm/action-setup@v2.2.4 + name: Install pnpm + id: pnpm-install + with: + version: 8.6.1 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - run: pnpm format:check + + tsc: + runs-on: ubuntu-latest + name: pnpm typecheck + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - uses: pnpm/action-setup@v2.2.4 + name: Install pnpm + id: pnpm-install + with: + version: 8.6.1 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies + run: pnpm install + + - run: pnpm typecheck diff --git a/.github/workflows/prerelease-comment.yml b/.github/workflows/prerelease-comment.yml new file mode 100644 index 0000000..ad21a54 --- /dev/null +++ b/.github/workflows/prerelease-comment.yml @@ -0,0 +1,65 @@ +# Adapted from create-t3-app. +name: Write Beta Release comment + +on: + workflow_run: + workflows: ["Release - Beta"] + types: + - completed + +jobs: + comment: + if: | + github.repository_owner == 'shadcn-ui' && + ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + name: Write comment to the PR + steps: + - name: "Comment on PR" + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + + for (const artifact of allArtifacts.data.artifacts) { + // Extract the PR number and package version from the artifact name + const match = /^npm-package-shadcn-ui@(.*?)-pr-(\d+)/.exec(artifact.name); + + if (match) { + require("fs").appendFileSync( + process.env.GITHUB_ENV, + `\nBETA_PACKAGE_VERSION=${match[1]}` + + `\nWORKFLOW_RUN_PR=${match[2]}` + + `\nWORKFLOW_RUN_ID=${context.payload.workflow_run.id}` + ); + break; + } + } + + - name: "Comment on PR with Link" + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ env.WORKFLOW_RUN_PR }} + message: | + A new prerelease is available for testing: + + ```sh + npx shadcn-ui@${{ env.BETA_PACKAGE_VERSION }} + ``` + + - name: "Remove the autorelease label once published" + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: '${{ env.WORKFLOW_RUN_PR }}', + name: '🚀 autorelease', + }); diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml new file mode 100644 index 0000000..ea41485 --- /dev/null +++ b/.github/workflows/prerelease.yml @@ -0,0 +1,60 @@ +# Adapted from create-t3-app. + +name: Release - Beta + +on: + pull_request: + types: [labeled] + branches: + - main +jobs: + prerelease: + if: | + github.repository_owner == 'shadcn-ui' && + contains(github.event.pull_request.labels.*.name, '🚀 autorelease') + name: Build & Publish a beta release to NPM + runs-on: ubuntu-latest + environment: Preview + + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Use PNPM + uses: pnpm/action-setup@v2.2.4 + with: + version: 8.6.1 + + - name: Use Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "pnpm" + + - name: Install NPM Dependencies + run: pnpm install + + - name: Modify package.json version + run: node .github/version-script-beta.js + + - name: Authenticate to NPM + run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/cli/.npmrc + env: + NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} + + - name: Publish Beta to NPM + run: pnpm pub:beta + + - name: get-npm-version + id: package-version + uses: martinbeentjes/npm-get-version-action@main + with: + path: packages/cli + + - name: Upload packaged artifact + uses: actions/upload-artifact@v2 + with: + name: npm-package-shadcn-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name + path: packages/cli/dist/index.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7ec4840 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +# Adapted from create-t3-app. + +name: Release + +on: + push: + branches: + - main + +jobs: + release: + if: ${{ github.repository_owner == 'shadcn-ui' }} + name: Create a PR for release workflow + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Use PNPM + uses: pnpm/action-setup@v2.2.4 + with: + version: 8.6.1 + + - name: Use Node.js 18 + uses: actions/setup-node@v3 + with: + version: 8.6.1 + node-version: 18 + cache: "pnpm" + + - name: Install NPM Dependencies + run: pnpm install + + # - name: Check for errors + # run: pnpm check + + - name: Build the package + run: pnpm build:cli + + - name: Create Version PR or Publish to NPM + id: changesets + uses: changesets/action@v1.4.1 + with: + commit: "chore(release): version packages" + title: "chore(release): version packages" + version: node .github/changeset-version.js + publish: npx changeset publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} + NODE_ENV: "production" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0534967 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: Test + +on: + pull_request: + branches: ["*"] + +jobs: + test: + runs-on: ubuntu-latest + name: pnpm test + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - uses: pnpm/action-setup@v2.2.4 + name: Install pnpm + id: pnpm-install + with: + version: 8.6.1 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies + run: pnpm install + + - run: pnpm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24c1ac8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# next.js +.next/ +out/ +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo + +.contentlayer +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..4974c35 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx commitlint --edit $1 diff --git a/.kodiak.toml b/.kodiak.toml new file mode 100644 index 0000000..bbfdb22 --- /dev/null +++ b/.kodiak.toml @@ -0,0 +1,18 @@ +# .kodiak.toml +version = 1 + +[merge] +automerge_label = "automerge" +require_automerge_label = true +method = "squash" +delete_branch_on_merge = true +optimistic_updates = false +prioritize_ready_to_merge = true +notify_on_conflict = true + +[merge.message] +title = "pull_request_title" +body = "pull_request_body" +include_pr_number = true +body_type = "markdown" +strip_html_comments = true diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..3e775ef --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +auto-install-peers=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..50e4b92 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v16.18.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b226983 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +dist +node_modules +.next +build +.contentlayer +apps/www/pages/api/registry.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2de0393 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "eslint.workingDirectories": [ + { "pattern": "apps/*/" }, + { "pattern": "packages/*/" } + ], + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f45807d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,175 @@ +# Contributing + +Thanks for your interest in contributing to ui.shadcn.com. We're happy to have you here. + +Please take a moment to review this document before submitting your first pull request. We also strongly recommend that you check for open issues and pull requests to see if someone else is working on something similar. + +If you need any help, feel free to reach out to [@shadcn](https://twitter.com/shadcn). + +## About this repository + +This repository is a monorepo. + +- We use [pnpm](https://pnpm.io) and [`workspaces`](https://pnpm.io/workspaces) for development. +- We use [Turborepo](https://turbo.build/repo) as our build system. +- We use [changesets](https://github.com/changesets/changesets) for managing releases. + +## Structure + +This repository is structured as follows: + +``` +apps +└── www + ├── app + ├── components + ├── content + └── registry + ├── default + │ ├── example + │ └── ui + └── new-york + ├── example + └── ui +packages +└── cli +``` + +| Path | Description | +| --------------------- | ---------------------------------------- | +| `apps/www/app` | The Next.js application for the website. | +| `apps/www/components` | The React components for the website. | +| `apps/www/content` | The content for the website. | +| `apps/www/registry` | The registry for the components. | +| `packages/cli` | The `shadcn-ui` package. | + +## Development + +### Fork this repo + +You can fork this repo by clicking the fork button in the top right corner of this page. + +### Clone on your local machine + +```bash +git clone https://github.com/your-username/ui.git +``` + +### Navigate to project directory + +```bash +cd ui +``` + +### Create a new Branch + +```bash +git checkout -b my-new-branch +``` + +### Install dependencies + +```bash +pnpm install +``` + +### Run a workspace + +You can use the `pnpm --filter=[WORKSPACE]` command to start the development process for a workspace. + +#### Examples + +1. To run the `ui.shadcn.com` website: + +```bash +pnpm --filter=www dev +``` + +2. To run the `shadcn-ui` package: + +```bash +pnpm --filter=shadcn-ui dev +``` + +## Documentation + +The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command: + +```bash +pnpm --filter=www dev +``` + +Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory. + +## Components + +We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles. + +```bash +apps +└── www + └── registry + ├── default + │ ├── example + │ └── ui + └── new-york + ├── example + └── ui +``` + +When adding or modifying components, please ensure that: + +1. You make the changes for every style. +2. You update the documentation. +3. You run `pnpm build:registry` to update the registry. + +## Commit Convention + +Before you create a Pull Request, please check whether your commits comply with +the commit conventions used in this repository. + +When you create a commit we kindly ask you to follow the convention +`category(scope or module): message` in your commit message while using one of +the following categories: + +- `feat / feature`: all changes that introduce completely new code or new + features +- `fix`: changes that fix a bug (ideally you will additionally reference an + issue if present) +- `refactor`: any code related change that is not a fix nor a feature +- `docs`: changing existing or creating new documentation (i.e. README, docs for + usage of a lib or cli usage) +- `build`: all changes regarding the build of the software, changes to + dependencies or the addition of new dependencies +- `test`: all changes regarding tests (adding new tests or changing existing + ones) +- `ci`: all changes regarding the configuration of continuous integration (i.e. + github actions, ci system) +- `chore`: all changes to the repository that do not fit into any of the above + categories + + e.g. `feat(components): add new prop to the avatar component` + +If you are interested in the detailed specification you can visit +https://www.conventionalcommits.org/ or check out the +[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines). + +## Requests for new components + +If you have a request for a new component, please open a discussion on GitHub. We'll be happy to help you out. + +## CLI + +The `shadcn-ui` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli). + +Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes. + +## Testing + +Tests are written using [Vitest](https://vitest.dev). You can run all the tests from the root of the repository. + +```bash +pnpm test +``` + +Please ensure that the tests are passing when submitting a pull request. If you're adding new features, please include tests. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..fad4d88 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 shadcn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..afd00aa --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# TurboETH BUIDL + +Handcrafted Web3 components that you can copy and paste into your WAGMI apps. Free. Open Source. **Use this to build your own component library**. + +## Documentation + +Visit http://buidl.turboeth/docs to view the documentation. + +## Contributing + +Please read the [contributing guide](/CONTRIBUTING.md). + +## License + +Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md). diff --git a/apps/www/.env.example b/apps/www/.env.example new file mode 100644 index 0000000..9ea50f4 --- /dev/null +++ b/apps/www/.env.example @@ -0,0 +1,4 @@ +# ----------------------------------------------------------------------------- +# App +# ----------------------------------------------------------------------------- +NEXT_PUBLIC_APP_URL=http://localhost:3001 diff --git a/apps/www/.gitignore b/apps/www/.gitignore new file mode 100644 index 0000000..a5c8936 --- /dev/null +++ b/apps/www/.gitignore @@ -0,0 +1,2 @@ +.vscode +.env \ No newline at end of file diff --git a/apps/www/.prettierignore b/apps/www/.prettierignore new file mode 100644 index 0000000..ccf2b0b --- /dev/null +++ b/apps/www/.prettierignore @@ -0,0 +1,6 @@ +dist +node_modules +.next +build +.contentlayer +__registry__/index.tsx \ No newline at end of file diff --git a/apps/www/__registry__/.autogenerated b/apps/www/__registry__/.autogenerated new file mode 100644 index 0000000..0055c2a --- /dev/null +++ b/apps/www/__registry__/.autogenerated @@ -0,0 +1 @@ +// The content of this directory is autogenerated by the registry server. diff --git a/apps/www/__registry__/.gitkeep b/apps/www/__registry__/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/www/__registry__/README.md b/apps/www/__registry__/README.md new file mode 100644 index 0000000..5621cea --- /dev/null +++ b/apps/www/__registry__/README.md @@ -0,0 +1 @@ +> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx new file mode 100644 index 0000000..8bc03fe --- /dev/null +++ b/apps/www/__registry__/index.tsx @@ -0,0 +1,233 @@ +// @ts-nocheck +// This file is autogenerated by scripts/build-registry.ts +// Do not edit this file directly. +import * as React from "react" + +export const Index: Record = { + "default": { + "address": { + name: "address", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/address")), + files: ["registry/default/buidl/address.tsx"], + }, + "blockie": { + name: "blockie", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/blockie")), + files: ["registry/default/buidl/blockie.tsx"], + }, + "ens-avatar": { + name: "ens-avatar", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/ens-avatar")), + files: ["registry/default/buidl/ens-avatar.tsx"], + }, + "ens-name": { + name: "ens-name", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/ens-name")), + files: ["registry/default/buidl/ens-name.tsx"], + }, + "wallet-connect": { + name: "wallet-connect", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/wallet-connect")), + files: ["registry/default/buidl/wallet-connect.tsx"], + }, + "wallet-manage": { + name: "wallet-manage", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/wallet-manage")), + files: ["registry/default/buidl/wallet-manage.tsx"], + }, + "network-manage": { + name: "network-manage", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/network-manage")), + files: ["registry/default/buidl/network-manage.tsx"], + }, + "erc20-balance": { + name: "erc20-balance", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/erc20/erc20-balance")), + files: ["registry/default/buidl/erc20/erc20-balance.tsx"], + }, + "erc20-image": { + name: "erc20-image", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/erc20/erc20-image")), + files: ["registry/default/buidl/erc20/erc20-image.tsx"], + }, + "erc20-name": { + name: "erc20-name", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/erc20/erc20-name")), + files: ["registry/default/buidl/erc20/erc20-name.tsx"], + }, + "erc20-symbol": { + name: "erc20-symbol", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/erc20/erc20-symbol")), + files: ["registry/default/buidl/erc20/erc20-symbol.tsx"], + }, + "erc20-select": { + name: "erc20-select", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/erc20/erc20-select")), + files: ["registry/default/buidl/erc20/erc20-select.tsx"], + }, + "erc20-select-and-amount": { + name: "erc20-select-and-amount", + type: "components:buidl", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/buidl/erc20/erc20-select-and-amount")), + files: ["registry/default/buidl/erc20/erc20-select-and-amount.tsx"], + }, + "accordion": { + name: "accordion", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/accordion")), + files: ["registry/default/ui/accordion.tsx"], + }, + "address-demo": { + name: "address-demo", + type: "components:example", + registryDependencies: ["address"], + component: React.lazy(() => import("@/registry/default/example/address-demo")), + files: ["registry/default/example/address-demo.tsx"], + }, + "blockie-demo": { + name: "blockie-demo", + type: "components:example", + registryDependencies: ["blockie"], + component: React.lazy(() => import("@/registry/default/example/blockie-demo")), + files: ["registry/default/example/blockie-demo.tsx"], + }, + "ens-avatar-demo": { + name: "ens-avatar-demo", + type: "components:example", + registryDependencies: ["ens-avatar"], + component: React.lazy(() => import("@/registry/default/example/ens-avatar-demo")), + files: ["registry/default/example/ens-avatar-demo.tsx"], + }, + "ens-name-demo": { + name: "ens-name-demo", + type: "components:example", + registryDependencies: ["ens-name"], + component: React.lazy(() => import("@/registry/default/example/ens-name-demo")), + files: ["registry/default/example/ens-name-demo.tsx"], + }, + "account-address-demo": { + name: "account-address-demo", + type: "components:example", + registryDependencies: ["account-address"], + component: React.lazy(() => import("@/registry/default/example/account-address-demo")), + files: ["registry/default/example/account-address-demo.tsx"], + }, + "account-blockie-demo": { + name: "account-blockie-demo", + type: "components:example", + registryDependencies: ["account-blockie"], + component: React.lazy(() => import("@/registry/default/example/account-blockie-demo")), + files: ["registry/default/example/account-blockie-demo.tsx"], + }, + "account-ens-avatar-demo": { + name: "account-ens-avatar-demo", + type: "components:example", + registryDependencies: ["account-ens-avatar"], + component: React.lazy(() => import("@/registry/default/example/account-ens-avatar-demo")), + files: ["registry/default/example/account-ens-avatar-demo.tsx"], + }, + "account-ens-name-demo": { + name: "account-ens-name-demo", + type: "components:example", + registryDependencies: ["account-ens-name"], + component: React.lazy(() => import("@/registry/default/example/account-ens-name-demo")), + files: ["registry/default/example/account-ens-name-demo.tsx"], + }, + "wallet-connect-demo": { + name: "wallet-connect-demo", + type: "components:example", + registryDependencies: ["wallet-connect"], + component: React.lazy(() => import("@/registry/default/example/wallet-connect-demo")), + files: ["registry/default/example/wallet-connect-demo.tsx"], + }, + "wallet-manage-demo": { + name: "wallet-manage-demo", + type: "components:example", + registryDependencies: ["wallet-manage"], + component: React.lazy(() => import("@/registry/default/example/wallet-manage-demo")), + files: ["registry/default/example/wallet-manage-demo.tsx"], + }, + "network-manage-demo": { + name: "network-manage-demo", + type: "components:example", + registryDependencies: ["network-manage"], + component: React.lazy(() => import("@/registry/default/example/network-manage-demo")), + files: ["registry/default/example/network-manage-demo.tsx"], + }, + "erc20-balance-demo": { + name: "erc20-balance-demo", + type: "components:example", + registryDependencies: ["erc20-balance"], + component: React.lazy(() => import("@/registry/default/example/erc20-balance-demo")), + files: ["registry/default/example/erc20-balance-demo.tsx"], + }, + "erc20-image-demo": { + name: "erc20-image-demo", + type: "components:example", + registryDependencies: ["erc20-image"], + component: React.lazy(() => import("@/registry/default/example/erc20-image-demo")), + files: ["registry/default/example/erc20-image-demo.tsx"], + }, + "erc20-name-demo": { + name: "erc20-name-demo", + type: "components:example", + registryDependencies: ["erc20-name"], + component: React.lazy(() => import("@/registry/default/example/erc20-name-demo")), + files: ["registry/default/example/erc20-name-demo.tsx"], + }, + "erc20-symbol-demo": { + name: "erc20-symbol-demo", + type: "components:example", + registryDependencies: ["erc20-symbol"], + component: React.lazy(() => import("@/registry/default/example/erc20-symbol-demo")), + files: ["registry/default/example/erc20-symbol-demo.tsx"], + }, + "erc20-select-demo": { + name: "erc20-select-demo", + type: "components:example", + registryDependencies: ["erc20-select"], + component: React.lazy(() => import("@/registry/default/example/erc20-select-demo")), + files: ["registry/default/example/erc20-select-demo.tsx"], + }, + "erc20-select-and-amount-demo": { + name: "erc20-select-and-amount-demo", + type: "components:example", + registryDependencies: ["erc20-select-and-amount"], + component: React.lazy(() => import("@/registry/default/example/erc20-select-and-amount-demo")), + files: ["registry/default/example/erc20-select-and-amount-demo.tsx"], + }, + "accordion-demo": { + name: "accordion-demo", + type: "components:example", + registryDependencies: ["accordion"], + component: React.lazy(() => import("@/registry/default/example/accordion-demo")), + files: ["registry/default/example/accordion-demo.tsx"], + }, + }, +} diff --git a/apps/www/app/docs/[[...slug]]/page.tsx b/apps/www/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..7f9f4f2 --- /dev/null +++ b/apps/www/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,152 @@ +import { notFound } from "next/navigation" +import { allDocs } from "contentlayer/generated" + +import "@/styles/mdx.css" +import type { Metadata } from "next" +import Link from "next/link" +import { ChevronRightIcon } from "@radix-ui/react-icons" +import Balancer from "react-wrap-balancer" + +import { siteConfig } from "@/config/site" +import { getTableOfContents } from "@/lib/toc" +import { absoluteUrl, cn } from "@/lib/utils" +import { Icons } from "@/components/icons" +import { Mdx } from "@/components/mdx-components" +import { DocsPager } from "@/components/pager" +import { DashboardTableOfContents } from "@/components/toc" +import { badgeVariants } from "@/registry/default/ui/badge" +import { ScrollArea } from "@/registry/default/ui/scroll-area" + +interface DocPageProps { + params: { + slug: string[] + } +} + +async function getDocFromParams({ params }: DocPageProps) { + const slug = params.slug?.join("/") || "" + const doc = allDocs.find((doc) => doc.slugAsParams === slug) + + if (!doc) { + return null + } + + return doc +} + +export async function generateMetadata({ + params, +}: DocPageProps): Promise { + const doc = await getDocFromParams({ params }) + + if (!doc) { + return {} + } + + return { + title: doc.title, + description: doc.description, + openGraph: { + title: doc.title, + description: doc.description, + type: "article", + url: absoluteUrl(doc.slug), + images: [ + { + url: siteConfig.ogImage, + width: 1200, + height: 630, + alt: siteConfig.name, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: doc.title, + description: doc.description, + images: [siteConfig.ogImage], + creator: "@DistrctFi", + }, + } +} + +export async function generateStaticParams(): Promise< + DocPageProps["params"][] +> { + return allDocs.map((doc) => ({ + slug: doc.slugAsParams.split("/"), + })) +} + +export default async function DocPage({ params }: DocPageProps) { + const doc = await getDocFromParams({ params }) + + if (!doc) { + notFound() + } + + const toc = await getTableOfContents(doc.body.raw) + + return ( +
+
+
+
+ Docs +
+ +
{doc.title}
+
+
+

+ {doc.title} +

+ {doc.description && ( +

+ {doc.description} +

+ )} +
+ {doc.wagmi ? ( +
+ {doc.wagmi?.link && ( + + WAGMI + + )} + {doc.wagmi?.api && ( + + API Reference + + )} +
+ ) : null} +
+ +
+ +
+ {doc.toc && ( +
+
+ +
+ +
+
+
+
+ )} +
+ ) +} diff --git a/apps/www/app/docs/layout.tsx b/apps/www/app/docs/layout.tsx new file mode 100644 index 0000000..409bfd1 --- /dev/null +++ b/apps/www/app/docs/layout.tsx @@ -0,0 +1,22 @@ +import { docsConfig } from "@/config/docs" +import { DocsSidebarNav } from "@/components/sidebar-nav" +import { ScrollArea } from "@/registry/default/ui/scroll-area" + +interface DocsLayoutProps { + children: React.ReactNode +} + +export default function DocsLayout({ children }: DocsLayoutProps) { + return ( +
+
+ + {children} +
+
+ ) +} diff --git a/apps/www/app/examples/authentication/components/user-auth-form.tsx b/apps/www/app/examples/authentication/components/user-auth-form.tsx new file mode 100644 index 0000000..fa39d53 --- /dev/null +++ b/apps/www/app/examples/authentication/components/user-auth-form.tsx @@ -0,0 +1,71 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" +import { Icons } from "@/components/icons" +import { Button } from "@/registry/default/ui/button" +import { Input } from "@/registry/default/ui/input" +import { Label } from "@/registry/default/ui/label" + +interface UserAuthFormProps extends React.HTMLAttributes {} + +export function UserAuthForm({ className, ...props }: UserAuthFormProps) { + const [isLoading, setIsLoading] = React.useState(false) + + async function onSubmit(event: React.SyntheticEvent) { + event.preventDefault() + setIsLoading(true) + + setTimeout(() => { + setIsLoading(false) + }, 3000) + } + + return ( +
+
+
+
+ + +
+ +
+
+
+
+ +
+
+ + Or continue with + +
+
+ +
+ ) +} diff --git a/apps/www/app/examples/authentication/page.tsx b/apps/www/app/examples/authentication/page.tsx new file mode 100644 index 0000000..969175a --- /dev/null +++ b/apps/www/app/examples/authentication/page.tsx @@ -0,0 +1,104 @@ +import { Metadata } from "next" +import Image from "next/image" +import Link from "next/link" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/registry/default/ui/button" +import { UserAuthForm } from "@/app/examples/authentication/components/user-auth-form" + +export const metadata: Metadata = { + title: "Authentication", + description: "Authentication forms built using the components.", +} + +export default function AuthenticationPage() { + return ( + <> +
+ Authentication + Authentication +
+
+ + Login + +
+
+
+ + + + Acme Inc +
+
+
+

+ “This library has saved me countless hours of work and + helped me deliver stunning designs to my clients faster than + ever before.” +

+
Sofia Davis
+
+
+
+
+
+
+

+ Create an account +

+

+ Enter your email below to create your account +

+
+ +

+ By clicking continue, you agree to our{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + + . +

+
+
+
+ + ) +} diff --git a/apps/www/app/examples/layout.tsx b/apps/www/app/examples/layout.tsx new file mode 100644 index 0000000..6ee61c9 --- /dev/null +++ b/apps/www/app/examples/layout.tsx @@ -0,0 +1,75 @@ +import { Metadata } from "next" +import Link from "next/link" +import { ArrowRightIcon } from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" +import { ExamplesNav } from "@/components/examples-nav" +import { + PageHeader, + PageHeaderDescription, + PageHeaderHeading, +} from "@/components/page-header" +import { buttonVariants } from "@/registry/default/ui/button" +import { Separator } from "@/registry/default/ui/separator" + +export const metadata: Metadata = { + title: "Examples", + description: "Check out some examples app built using the components.", +} + +interface ExamplesLayoutProps { + children: React.ReactNode +} + +export default function ExamplesLayout({ children }: ExamplesLayoutProps) { + return ( + <> +
+ + + 🎉 {" "} + Style, a new CLI and more. + + Introducing Style, a new CLI and more. + + + + + Check out some examples. + + Examples + + Dashboard, cards, authentication. Some examples built using the + components. Use this as a guide to build your own. + +
+ + Get Started + + + Components + +
+
+
+ +
+ {children} +
+
+
+ + ) +} diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx new file mode 100644 index 0000000..2ef0509 --- /dev/null +++ b/apps/www/app/layout.tsx @@ -0,0 +1,115 @@ +import "@/styles/globals.css" +import { Metadata } from "next" + +import { siteConfig } from "@/config/site" +import { fontSans } from "@/lib/fonts" +import { cn } from "@/lib/utils" +import { Analytics } from "@/components/analytics" +import { ThemeProvider } from "@/components/providers" +import { RainbowKitInitializedProvider } from "@/components/providers/rainbow-kit-provider" +import { WagmiProvider } from "@/components/providers/wagmi-provider" +import { SiteFooter } from "@/components/site-footer" +import { SiteHeader } from "@/components/site-header" +import { TailwindIndicator } from "@/components/tailwind-indicator" +import { ThemeSwitcher } from "@/components/theme-switcher" +import { + Toaster as DefaultToaster, + Toaster as NewYorkToaster, +} from "@/registry/default/ui/toaster" + +export const metadata: Metadata = { + title: { + default: siteConfig.name, + template: `%s - ${siteConfig.name}`, + }, + description: siteConfig.description, + keywords: [ + "Next.js", + "React", + "Tailwind CSS", + "Server Components", + "Radix UI", + ], + authors: [ + { + name: "DistrictLabs", + url: "https://districtLabs.com", + }, + ], + creator: "DistrictLabs", + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], + openGraph: { + type: "website", + locale: "en_US", + url: siteConfig.url, + title: siteConfig.name, + description: siteConfig.description, + siteName: siteConfig.name, + images: [ + { + url: siteConfig.ogImage, + width: 1200, + height: 630, + alt: siteConfig.name, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: siteConfig.name, + description: siteConfig.description, + images: [siteConfig.ogImage], + creator: "@districtLabs", + }, + icons: { + icon: "/favicon.ico", + shortcut: "/favicon-16x16.png", + apple: "/apple-touch-icon.png", + }, + manifest: `${siteConfig.url}/site.webmanifest`, +} + +interface RootLayoutProps { + children: React.ReactNode +} + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + <> + + + + + + +
+ +
{children}
+ +
+
+
+ +
+ + + + + + + + ) +} diff --git a/apps/www/app/page.tsx b/apps/www/app/page.tsx new file mode 100644 index 0000000..e598594 --- /dev/null +++ b/apps/www/app/page.tsx @@ -0,0 +1,77 @@ +import Image from "next/image" +import Link from "next/link" +import { ArrowRightIcon } from "@radix-ui/react-icons" + +import { siteConfig } from "@/config/site" +import { cn } from "@/lib/utils" +import { ExamplesNav } from "@/components/examples-nav" +import { Icons } from "@/components/icons" +import { + PageHeader, + PageHeaderDescription, + PageHeaderHeading, +} from "@/components/page-header" +import { buttonVariants } from "@/registry/default/ui/button" +import { Separator } from "@/registry/default/ui/separator" + +import AuthenticationPage from "./examples/authentication/page" + +export default function IndexPage() { + return ( +
+ + + ⚡️ {" "} + Build Web3 in Turbo Mode. + Build Web3 in Turbo Mode + + + A Flexible Web3 Component Library + + Handcrafted Web3 components that you can copy and paste into your + WAGMI apps.{" "} + Save time and BUIDL faster. + +
+ + Get Started + + + + GitHub + +
+
+ +
+ Dashboard + Dashboard +
+
+
+ +
+
+
+ ) +} diff --git a/apps/www/app/themes/page.tsx b/apps/www/app/themes/page.tsx new file mode 100644 index 0000000..d50f7b7 --- /dev/null +++ b/apps/www/app/themes/page.tsx @@ -0,0 +1,38 @@ +import { Metadata } from "next" + +import "public/registry/themes.css" +import { + PageHeader, + PageHeaderDescription, + PageHeaderHeading, +} from "@/components/page-header" +import { ThemeCustomizer } from "@/components/theme-customizer" +import { ThemeWrapper } from "@/components/theme-wrapper" +import { ThemesTabs } from "@/app/themes/tabs" + +export const metadata: Metadata = { + title: "Themes", + description: "Hand-picked themes that you can copy and paste into your apps.", +} + +export default function ThemesPage() { + return ( +
+ + + Make it yours. + + Hand-picked themes that you can copy and paste into your apps. + + +
+ +
+
+ +
+ ) +} diff --git a/apps/www/app/themes/tabs.tsx b/apps/www/app/themes/tabs.tsx new file mode 100644 index 0000000..d9fad57 --- /dev/null +++ b/apps/www/app/themes/tabs.tsx @@ -0,0 +1,72 @@ +"use client" + +import * as React from "react" + +import { useConfig } from "@/hooks/use-config" +import { ThemeWrapper } from "@/components/theme-wrapper" +import CardsDefault from "@/registry/default/example/cards" +import CardsNewYork from "@/registry/default/example/cards" +import { Skeleton } from "@/registry/default/ui/skeleton" + +export function ThemesTabs() { + const [mounted, setMounted] = React.useState(false) + const [config] = useConfig() + + React.useEffect(() => { + setMounted(true) + }, []) + + return ( +
+ {!mounted ? ( +
+
+ +
+ +
+ +
+
+ +
+
+
+
+ + + +
+
+ + +
+ +
+
+
+
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+ ) : ( + + {config.style === "default" && } + + )} +
+ ) +} diff --git a/apps/www/assets/fonts/CalSans-SemiBold.ttf b/apps/www/assets/fonts/CalSans-SemiBold.ttf new file mode 100644 index 0000000..4a2950a Binary files /dev/null and b/apps/www/assets/fonts/CalSans-SemiBold.ttf differ diff --git a/apps/www/assets/fonts/CalSans-SemiBold.woff b/apps/www/assets/fonts/CalSans-SemiBold.woff new file mode 100644 index 0000000..da45991 Binary files /dev/null and b/apps/www/assets/fonts/CalSans-SemiBold.woff differ diff --git a/apps/www/assets/fonts/CalSans-SemiBold.woff2 b/apps/www/assets/fonts/CalSans-SemiBold.woff2 new file mode 100644 index 0000000..36d71b7 Binary files /dev/null and b/apps/www/assets/fonts/CalSans-SemiBold.woff2 differ diff --git a/apps/www/assets/fonts/Inter-Bold.ttf b/apps/www/assets/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..8e82c70 Binary files /dev/null and b/apps/www/assets/fonts/Inter-Bold.ttf differ diff --git a/apps/www/assets/fonts/Inter-Regular.ttf b/apps/www/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000..8d4eebf Binary files /dev/null and b/apps/www/assets/fonts/Inter-Regular.ttf differ diff --git a/apps/www/components/analytics.tsx b/apps/www/components/analytics.tsx new file mode 100644 index 0000000..164e9b7 --- /dev/null +++ b/apps/www/components/analytics.tsx @@ -0,0 +1,7 @@ +"use client" + +import { Analytics as VercelAnalytics } from "@vercel/analytics/react" + +export function Analytics() { + return +} diff --git a/apps/www/components/callout.tsx b/apps/www/components/callout.tsx new file mode 100644 index 0000000..aaba504 --- /dev/null +++ b/apps/www/components/callout.tsx @@ -0,0 +1,21 @@ +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/registry/default/ui/alert" + +interface CalloutProps { + icon?: string + title?: string + children?: React.ReactNode +} + +export function Callout({ title, children, icon, ...props }: CalloutProps) { + return ( + + {icon && {icon}} + {title && {title}} + {children} + + ) +} diff --git a/apps/www/components/code-block-wrapper.tsx b/apps/www/components/code-block-wrapper.tsx new file mode 100644 index 0000000..0ecc128 --- /dev/null +++ b/apps/www/components/code-block-wrapper.tsx @@ -0,0 +1,56 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" +import { Button } from "@/registry/default/ui/button" +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/registry/default/ui/collapsible" + +interface CodeBlockProps extends React.HTMLAttributes { + expandButtonTitle?: string +} + +export function CodeBlockWrapper({ + expandButtonTitle = "View Code", + className, + children, + ...props +}: CodeBlockProps) { + const [isOpened, setIsOpened] = React.useState(false) + + return ( + +
+ +
+ {children} +
+
+
+ + + +
+
+
+ ) +} diff --git a/apps/www/components/command-menu.tsx b/apps/www/components/command-menu.tsx new file mode 100644 index 0000000..3aac155 --- /dev/null +++ b/apps/www/components/command-menu.tsx @@ -0,0 +1,123 @@ +"use client" + +import * as React from "react" +import { useRouter } from "next/navigation" +import { DialogProps } from "@radix-ui/react-alert-dialog" +import { + CircleIcon, + FileIcon, + LaptopIcon, + MoonIcon, + SunIcon, +} from "@radix-ui/react-icons" +import { useTheme } from "next-themes" + +import { docsConfig } from "@/config/docs" +import { cn } from "@/lib/utils" +import { Button } from "@/registry/default/ui/button" +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/registry/default/ui/command" + +export function CommandMenu({ ...props }: DialogProps) { + const router = useRouter() + const [open, setOpen] = React.useState(false) + const { setTheme } = useTheme() + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + setOpen((open) => !open) + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + const runCommand = React.useCallback((command: () => unknown) => { + setOpen(false) + command() + }, []) + + return ( + <> + + + + + No results found. + + {docsConfig.mainNav + .filter((navitem) => !navitem.external) + .map((navItem) => ( + { + runCommand(() => router.push(navItem.href as string)) + }} + > + + {navItem.title} + + ))} + + {docsConfig.sidebarNav.map((group) => ( + + {group.items.map((navItem) => ( + { + runCommand(() => router.push(navItem.href as string)) + }} + > +
+ +
+ {navItem.title} +
+ ))} +
+ ))} + + + runCommand(() => setTheme("light"))}> + + Light + + runCommand(() => setTheme("dark"))}> + + Dark + + runCommand(() => setTheme("system"))}> + + System + + +
+
+ + ) +} diff --git a/apps/www/components/component-card.tsx b/apps/www/components/component-card.tsx new file mode 100644 index 0000000..413b388 --- /dev/null +++ b/apps/www/components/component-card.tsx @@ -0,0 +1,21 @@ +import React from "react" + +import { cn } from "@/lib/utils" +import { AspectRatio } from "@/registry/default/ui/aspect-ratio" + +export function ComponentCard({ + className, + ...props +}: React.HTMLAttributes) { + return ( + +
+ + ) +} diff --git a/apps/www/components/component-example.tsx b/apps/www/components/component-example.tsx new file mode 100644 index 0000000..a0bc83a --- /dev/null +++ b/apps/www/components/component-example.tsx @@ -0,0 +1,107 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" +import { CopyButton, CopyWithClassNames } from "@/components/copy-button" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/registry/default/ui/tabs" + +interface ComponentExampleProps extends React.HTMLAttributes { + extractClassname?: boolean + extractedClassNames?: string + align?: "center" | "start" | "end" + src?: string +} + +export function ComponentExample({ + children, + className, + extractClassname, + extractedClassNames, + align = "center", + src: _, + ...props +}: ComponentExampleProps) { + const [Example, Code, ...Children] = React.Children.toArray( + children + ) as React.ReactElement[] + + const codeString = React.useMemo(() => { + if ( + typeof Code?.props["data-rehype-pretty-code-fragment"] !== "undefined" + ) { + const [, Button] = React.Children.toArray( + Code.props.children + ) as React.ReactElement[] + return Button?.props?.value || Button?.props?.__rawString__ || null + } + }, [Code]) + + return ( +
+ +
+ + + Preview + + + Code + + + {extractedClassNames ? ( + + ) : ( + codeString && ( + + ) + )} +
+ +
+ {Example} +
+
+ +
+
+ {Code} +
+ {Children?.length ? ( +
+ {Children} +
+ ) : null} +
+
+
+
+ ) +} diff --git a/apps/www/components/component-preview.tsx b/apps/www/components/component-preview.tsx new file mode 100644 index 0000000..d5a31bc --- /dev/null +++ b/apps/www/components/component-preview.tsx @@ -0,0 +1,139 @@ +"use client" + +import * as React from "react" +import { Index } from "@/__registry__" + +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" +import { CopyButton, CopyWithClassNames } from "@/components/copy-button" +import { Icons } from "@/components/icons" +import { StyleSwitcher } from "@/components/style-switcher" +import { ThemeWrapper } from "@/components/theme-wrapper" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/registry/default/ui/tabs" +import { styles } from "@/registry/styles" + +interface ComponentPreviewProps extends React.HTMLAttributes { + name: string + extractClassname?: boolean + extractedClassNames?: string + align?: "center" | "start" | "end" +} + +export function ComponentPreview({ + name, + children, + className, + extractClassname, + extractedClassNames, + align = "center", + ...props +}: ComponentPreviewProps) { + const [config] = useConfig() + const index = styles.findIndex((style) => style.name === config.style) + + const Codes = React.Children.toArray(children) as React.ReactElement[] + const Code = Codes[index] + + const Preview = React.useMemo(() => { + const Component = Index[config.style][name]?.component + + if (!Component) { + return ( +

+ Component{" "} + + {name} + {" "} + not found in registry. +

+ ) + } + + return + }, [name, config.style]) + + const codeString = React.useMemo(() => { + if ( + typeof Code?.props["data-rehype-pretty-code-fragment"] !== "undefined" + ) { + const [, Button] = React.Children.toArray( + Code.props.children + ) as React.ReactElement[] + return Button?.props?.value || Button?.props?.__rawString__ || null + } + }, [Code]) + + return ( +
+ +
+ + + Preview + + + Code + + +
+ +
+ + {extractedClassNames ? ( + + ) : ( + codeString && + )} +
+ +
+ + + Loading... +
+ } + > + {Preview} + +
+ + + +
+
+ {Code} +
+
+
+ +
+ ) +} diff --git a/apps/www/components/component-source.tsx b/apps/www/components/component-source.tsx new file mode 100644 index 0000000..3115038 --- /dev/null +++ b/apps/www/components/component-source.tsx @@ -0,0 +1,25 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" +import { CodeBlockWrapper } from "@/components/code-block-wrapper" + +interface ComponentSourceProps extends React.HTMLAttributes { + src: string +} + +export function ComponentSource({ + children, + className, + ...props +}: ComponentSourceProps) { + return ( + + {children} + + ) +} diff --git a/apps/www/components/copy-button.tsx b/apps/www/components/copy-button.tsx new file mode 100644 index 0000000..d10a669 --- /dev/null +++ b/apps/www/components/copy-button.tsx @@ -0,0 +1,210 @@ +"use client" + +import * as React from "react" +import { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu" +import { CheckIcon, CopyIcon } from "@radix-ui/react-icons" +import { NpmCommands } from "types/unist" + +import { Event, trackEvent } from "@/lib/events" +import { cn } from "@/lib/utils" +import { Button } from "@/registry/default/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/registry/default/ui/dropdown-menu" + +interface CopyButtonProps extends React.HTMLAttributes { + value: string + src?: string + event?: Event["name"] +} + +export async function copyToClipboardWithMeta(value: string, event?: Event) { + navigator.clipboard.writeText(value) + if (event) { + trackEvent(event) + } +} + +export function CopyButton({ + value, + className, + src, + event, + ...props +}: CopyButtonProps) { + const [hasCopied, setHasCopied] = React.useState(false) + + React.useEffect(() => { + setTimeout(() => { + setHasCopied(false) + }, 2000) + }, [hasCopied]) + + return ( + + ) +} + +interface CopyWithClassNamesProps extends DropdownMenuTriggerProps { + value: string + classNames: string + className?: string +} + +export function CopyWithClassNames({ + value, + classNames, + className, + ...props +}: CopyWithClassNamesProps) { + const [hasCopied, setHasCopied] = React.useState(false) + + React.useEffect(() => { + setTimeout(() => { + setHasCopied(false) + }, 2000) + }, [hasCopied]) + + const copyToClipboard = React.useCallback((value: string) => { + copyToClipboardWithMeta(value) + setHasCopied(true) + }, []) + + return ( + + + + + + copyToClipboard(value)}> + Component + + copyToClipboard(classNames)}> + Classname + + + + ) +} + +interface CopyNpmCommandButtonProps extends DropdownMenuTriggerProps { + commands: Required +} + +export function CopyNpmCommandButton({ + commands, + className, + ...props +}: CopyNpmCommandButtonProps) { + const [hasCopied, setHasCopied] = React.useState(false) + + React.useEffect(() => { + setTimeout(() => { + setHasCopied(false) + }, 2000) + }, [hasCopied]) + + const copyCommand = React.useCallback( + (value: string, pm: "npm" | "pnpm" | "yarn" | "bun") => { + copyToClipboardWithMeta(value, { + name: "copy_npm_command", + properties: { + command: value, + pm, + }, + }) + setHasCopied(true) + }, + [] + ) + + return ( + + + + + + copyCommand(commands.__npmCommand__, "npm")} + > + npm + + copyCommand(commands.__yarnCommand__, "yarn")} + > + yarn + + copyCommand(commands.__pnpmCommand__, "pnpm")} + > + pnpm + + copyCommand(commands.__bunCommand__, "bun")} + > + bun + + + + ) +} diff --git a/apps/www/components/drawer.tsx b/apps/www/components/drawer.tsx new file mode 100644 index 0000000..4fab78d --- /dev/null +++ b/apps/www/components/drawer.tsx @@ -0,0 +1,31 @@ +"use client" + +import { forwardRef } from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerContent = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +export { DrawerTrigger, DrawerContent } diff --git a/apps/www/components/examples-nav.tsx b/apps/www/components/examples-nav.tsx new file mode 100644 index 0000000..a1870f8 --- /dev/null +++ b/apps/www/components/examples-nav.tsx @@ -0,0 +1,73 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { ArrowRightIcon } from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" +import { ScrollArea, ScrollBar } from "@/registry/default/ui/scroll-area" + +const examples = [ + { + name: "Authentication", + href: "/examples/authentication", + code: "https://github.com/shadcn/ui/tree/main/apps/www/app/examples/authentication", + }, +] + +interface ExamplesNavProps extends React.HTMLAttributes {} + +export function ExamplesNav({ className, ...props }: ExamplesNavProps) { + const pathname = usePathname() + + return ( +
+ +
+ {examples.map((example) => ( + + {example.name} + + ))} +
+ +
+ +
+ ) +} + +interface ExampleCodeLinkProps { + pathname: string | null +} + +export function ExampleCodeLink({ pathname }: ExampleCodeLinkProps) { + const example = examples.find((example) => pathname?.startsWith(example.href)) + + if (!example?.code) { + return null + } + + return ( + + View code + + + ) +} diff --git a/apps/www/components/framework-docs.tsx b/apps/www/components/framework-docs.tsx new file mode 100644 index 0000000..2bebd69 --- /dev/null +++ b/apps/www/components/framework-docs.tsx @@ -0,0 +1,22 @@ +"use client" + +import * as React from "react" +import { allDocs } from "contentlayer/generated" + +import { Mdx } from "./mdx-components" + +interface FrameworkDocsProps extends React.HTMLAttributes { + data: string +} + +export function FrameworkDocs({ ...props }: FrameworkDocsProps) { + const frameworkDoc = allDocs.find( + (doc) => doc.slug === `/docs/installation/${props.data}` + ) + + if (!frameworkDoc) { + return null + } + + return +} diff --git a/apps/www/components/icons.tsx b/apps/www/components/icons.tsx new file mode 100644 index 0000000..1e82e6b --- /dev/null +++ b/apps/www/components/icons.tsx @@ -0,0 +1,156 @@ +type IconProps = React.HTMLAttributes + +export const Icons = { + logo: (props: IconProps) => ( + + + + ), + twitter: (props: IconProps) => ( + + + + ), + gitHub: (props: IconProps) => ( + + + + ), + wagmi: (props: IconProps) => ( + + wagmi logo + + + ), + radix: (props: IconProps) => ( + + + + + + ), + aria: (props: IconProps) => ( + + + + ), + npm: (props: IconProps) => ( + + + + ), + yarn: (props: IconProps) => ( + + + + ), + pnpm: (props: IconProps) => ( + + + + ), + react: (props: IconProps) => ( + + + + ), + tailwind: (props: IconProps) => ( + + + + ), + google: (props: IconProps) => ( + + + + ), + apple: (props: IconProps) => ( + + + + ), + paypal: (props: IconProps) => ( + + + + ), + spinner: (props: IconProps) => ( + + + + ), +} diff --git a/apps/www/components/main-nav.tsx b/apps/www/components/main-nav.tsx new file mode 100644 index 0000000..5dbf52f --- /dev/null +++ b/apps/www/components/main-nav.tsx @@ -0,0 +1,77 @@ +"use client" + +import * as React from "react" +import Link from "next/link" +import { usePathname } from "next/navigation" + +import { siteConfig } from "@/config/site" +import { cn } from "@/lib/utils" +import { Icons } from "@/components/icons" +import { Badge } from "@/registry/default/ui/badge" + +export function MainNav() { + const pathname = usePathname() + + return ( +
+ + + + {siteConfig.name} + + + +
+ ) +} diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx new file mode 100644 index 0000000..6203676 --- /dev/null +++ b/apps/www/components/mdx-components.tsx @@ -0,0 +1,332 @@ +"use client" + +import * as React from "react" +import Image from "next/image" +import Link from "next/link" +import { useMDXComponent } from "next-contentlayer/hooks" +import { NpmCommands } from "types/unist" + +import { Event } from "@/lib/events" +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" +import { Callout } from "@/components/callout" +import { CodeBlockWrapper } from "@/components/code-block-wrapper" +import { ComponentExample } from "@/components/component-example" +import { ComponentPreview } from "@/components/component-preview" +import { ComponentSource } from "@/components/component-source" +import { CopyButton, CopyNpmCommandButton } from "@/components/copy-button" +import { FrameworkDocs } from "@/components/framework-docs" +import { StyleWrapper } from "@/components/style-wrapper" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/registry/default/ui/accordion" +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/registry/default/ui/alert" +import { AspectRatio } from "@/registry/default/ui/aspect-ratio" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/registry/default/ui/tabs" +import { Style } from "@/registry/styles" + +const components = { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Alert, + AlertTitle, + AlertDescription, + h1: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + h2: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + h3: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + h4: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + h5: ({ className, ...props }: React.HTMLAttributes) => ( +
+ ), + h6: ({ className, ...props }: React.HTMLAttributes) => ( +
+ ), + a: ({ className, ...props }: React.HTMLAttributes) => ( + + ), + p: ({ className, ...props }: React.HTMLAttributes) => ( +

+ ), + ul: ({ className, ...props }: React.HTMLAttributes) => ( +

    + ), + ol: ({ className, ...props }: React.HTMLAttributes) => ( +
      + ), + li: ({ className, ...props }: React.HTMLAttributes) => ( +
    1. + ), + blockquote: ({ className, ...props }: React.HTMLAttributes) => ( +
      + ), + img: ({ + className, + alt, + ...props + }: React.ImgHTMLAttributes) => ( + // eslint-disable-next-line @next/next/no-img-element + {alt} + ), + hr: ({ ...props }: React.HTMLAttributes) => ( +
      + ), + table: ({ className, ...props }: React.HTMLAttributes) => ( +
      + + + ), + tr: ({ className, ...props }: React.HTMLAttributes) => ( + + ), + th: ({ className, ...props }: React.HTMLAttributes) => ( +
      + ), + td: ({ className, ...props }: React.HTMLAttributes) => ( + + ), + pre: ({ + className, + __rawString__, + __npmCommand__, + __yarnCommand__, + __pnpmCommand__, + __bunCommand__, + __withMeta__, + __src__, + __event__, + __style__, + ...props + }: React.HTMLAttributes & { + __style__?: Style["name"] + __rawString__?: string + __withMeta__?: boolean + __src__?: string + __event__?: Event["name"] + } & NpmCommands) => { + return ( + +
      +        {__rawString__ && !__npmCommand__ && (
      +          
      +        )}
      +        {__npmCommand__ &&
      +          __yarnCommand__ &&
      +          __pnpmCommand__ &&
      +          __bunCommand__ && (
      +            
      +          )}
      +      
      +    )
      +  },
      +  code: ({ className, ...props }: React.HTMLAttributes) => (
      +    
      +  ),
      +  Image,
      +  Callout,
      +  ComponentPreview,
      +  ComponentExample,
      +  ComponentSource,
      +  AspectRatio,
      +  CodeBlockWrapper: ({ ...props }) => (
      +    
      +  ),
      +  Step: ({ className, ...props }: React.ComponentProps<"h3">) => (
      +    

      + ), + Steps: ({ ...props }) => ( +
      + ), + Tabs: ({ className, ...props }: React.ComponentProps) => ( + + ), + TabsList: ({ + className, + ...props + }: React.ComponentProps) => ( + + ), + TabsTrigger: ({ + className, + ...props + }: React.ComponentProps) => ( + + ), + TabsContent: ({ + className, + ...props + }: React.ComponentProps) => ( + + ), + FrameworkDocs: ({ + className, + ...props + }: React.ComponentProps) => ( + + ), + Link: ({ className, ...props }: React.ComponentProps) => ( + + ), + LinkedCard: ({ className, ...props }: React.ComponentProps) => ( + + ), +} + +interface MdxProps { + code: string +} + +export function Mdx({ code }: MdxProps) { + const [config] = useConfig() + const Component = useMDXComponent(code, { + style: config.style, + }) + + return ( +
      + +
      + ) +} diff --git a/apps/www/components/mobile-nav.tsx b/apps/www/components/mobile-nav.tsx new file mode 100644 index 0000000..d5f9bdd --- /dev/null +++ b/apps/www/components/mobile-nav.tsx @@ -0,0 +1,111 @@ +"use client" + +import * as React from "react" +import Link, { LinkProps } from "next/link" +import { useRouter } from "next/navigation" +import { ViewVerticalIcon } from "@radix-ui/react-icons" + +import { docsConfig } from "@/config/docs" +import { siteConfig } from "@/config/site" +import { cn } from "@/lib/utils" +import { Icons } from "@/components/icons" +import { Button } from "@/registry/default/ui/button" +import { ScrollArea } from "@/registry/default/ui/scroll-area" +import { Sheet, SheetContent, SheetTrigger } from "@/registry/default/ui/sheet" + +export function MobileNav() { + const [open, setOpen] = React.useState(false) + + return ( + + + + + + + + {siteConfig.name} + + +
      + {docsConfig.mainNav?.map( + (item) => + item.href && ( + + {item.title} + + ) + )} +
      +
      + {docsConfig.sidebarNav.map((item, index) => ( +
      +

      {item.title}

      + {item?.items?.length && + item.items.map((item) => ( + + {!item.disabled && + (item.href ? ( + + {item.title} + + ) : ( + item.title + ))} + + ))} +
      + ))} +
      +
      +
      +
      + ) +} + +interface MobileLinkProps extends LinkProps { + onOpenChange?: (open: boolean) => void + children: React.ReactNode + className?: string +} + +function MobileLink({ + href, + onOpenChange, + className, + children, + ...props +}: MobileLinkProps) { + const router = useRouter() + return ( + { + router.push(href.toString()) + onOpenChange?.(false) + }} + className={cn(className)} + {...props} + > + {children} + + ) +} diff --git a/apps/www/components/mode-toggle.tsx b/apps/www/components/mode-toggle.tsx new file mode 100644 index 0000000..d1ac491 --- /dev/null +++ b/apps/www/components/mode-toggle.tsx @@ -0,0 +1,40 @@ +"use client" + +import * as React from "react" +import { MoonIcon, SunIcon } from "@radix-ui/react-icons" +import { useTheme } from "next-themes" + +import { Button } from "@/registry/default/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/registry/default/ui/dropdown-menu" + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ) +} diff --git a/apps/www/components/page-header.tsx b/apps/www/components/page-header.tsx new file mode 100644 index 0000000..db2f703 --- /dev/null +++ b/apps/www/components/page-header.tsx @@ -0,0 +1,53 @@ +import Balance from "react-wrap-balancer" + +import { cn } from "@/lib/utils" + +function PageHeader({ + className, + children, + ...props +}: React.HTMLAttributes) { + return ( +
      + {children} +
      + ) +} + +function PageHeaderHeading({ + className, + ...props +}: React.HTMLAttributes) { + return ( +

      + ) +} + +function PageHeaderDescription({ + className, + ...props +}: React.HTMLAttributes) { + return ( + + ) +} + +export { PageHeader, PageHeaderHeading, PageHeaderDescription } diff --git a/apps/www/components/pager.tsx b/apps/www/components/pager.tsx new file mode 100644 index 0000000..daad42d --- /dev/null +++ b/apps/www/components/pager.tsx @@ -0,0 +1,67 @@ +import Link from "next/link" +import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" +import { Doc } from "contentlayer/generated" +import { NavItem, NavItemWithChildren } from "types/nav" + +import { docsConfig } from "@/config/docs" +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/registry/default/ui/button" + +interface DocsPagerProps { + doc: Doc +} + +export function DocsPager({ doc }: DocsPagerProps) { + const pager = getPagerForDoc(doc) + + if (!pager) { + return null + } + + return ( +
      + {pager?.prev?.href && ( + + + {pager.prev.title} + + )} + {pager?.next?.href && ( + + {pager.next.title} + + + )} +
      + ) +} + +export function getPagerForDoc(doc: Doc) { + const flattenedLinks = [null, ...flatten(docsConfig.sidebarNav), null] + const activeIndex = flattenedLinks.findIndex( + (link) => doc.slug === link?.href + ) + const prev = activeIndex !== 0 ? flattenedLinks[activeIndex - 1] : null + const next = + activeIndex !== flattenedLinks.length - 1 + ? flattenedLinks[activeIndex + 1] + : null + return { + prev, + next, + } +} + +export function flatten(links: NavItemWithChildren[]): NavItem[] { + return links + .reduce((flat, link) => { + return flat.concat(link.items?.length ? flatten(link.items) : link) + }, []) + .filter((link) => !link?.disabled) +} diff --git a/apps/www/components/promo-video.tsx b/apps/www/components/promo-video.tsx new file mode 100644 index 0000000..ab76403 --- /dev/null +++ b/apps/www/components/promo-video.tsx @@ -0,0 +1,19 @@ +"use client" + +import { AspectRatio } from "@/registry/default/ui/aspect-ratio" + +export function PromoVideo() { + return ( + + + + ) +} diff --git a/apps/www/components/providers.tsx b/apps/www/components/providers.tsx new file mode 100644 index 0000000..f869cb4 --- /dev/null +++ b/apps/www/components/providers.tsx @@ -0,0 +1,15 @@ +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import { ThemeProviderProps } from "next-themes/dist/types" + +import { TooltipProvider } from "@/registry/default/ui/tooltip" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return ( + + {children} + + ) +} diff --git a/apps/www/components/providers/rainbow-kit-provider.tsx b/apps/www/components/providers/rainbow-kit-provider.tsx new file mode 100644 index 0000000..77c46b8 --- /dev/null +++ b/apps/www/components/providers/rainbow-kit-provider.tsx @@ -0,0 +1,15 @@ +"use client" + +import "@rainbow-me/rainbowkit/styles.css" +import { ReactNode } from "react" +import { RainbowKitProvider } from "@rainbow-me/rainbowkit" + +import { chains, publicClient, webSocketPublicClient } from "@/config/networks" + +export function RainbowKitInitializedProvider({ + children, +}: { + children: ReactNode +}) { + return {children} +} diff --git a/apps/www/components/providers/wagmi-provider.tsx b/apps/www/components/providers/wagmi-provider.tsx new file mode 100644 index 0000000..c25dc68 --- /dev/null +++ b/apps/www/components/providers/wagmi-provider.tsx @@ -0,0 +1,18 @@ +"use client" + +import { ReactNode } from "react" +import { WagmiConfig, createConfig } from "wagmi" + +import { connectors } from "@/config/connectors" +import { publicClient, webSocketPublicClient } from "@/config/networks" + +const wagmiConfig = createConfig({ + autoConnect: true, + connectors: connectors, + publicClient, + webSocketPublicClient, +}) + +export function WagmiProvider({ children }: { children: ReactNode }) { + return {children} +} diff --git a/apps/www/components/sidebar-nav.tsx b/apps/www/components/sidebar-nav.tsx new file mode 100644 index 0000000..f84f9bc --- /dev/null +++ b/apps/www/components/sidebar-nav.tsx @@ -0,0 +1,84 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { SidebarNavItem } from "types/nav" + +import { cn } from "@/lib/utils" + +export interface DocsSidebarNavProps { + items: SidebarNavItem[] +} + +export function DocsSidebarNav({ items }: DocsSidebarNavProps) { + const pathname = usePathname() + + return items.length ? ( +
      + {items.map((item, index) => ( +
      +

      + {item.title} +

      + {item?.items?.length && ( + + )} +
      + ))} +
      + ) : null +} + +interface DocsSidebarNavItemsProps { + items: SidebarNavItem[] + pathname: string | null +} + +export function DocsSidebarNavItems({ + items, + pathname, +}: DocsSidebarNavItemsProps) { + return items?.length ? ( +
      + {items.map((item, index) => + item.href && !item.disabled ? ( + + {item.title} + {item.label && ( + + {item.label} + + )} + + ) : ( + + {item.title} + {item.label && ( + + {item.label} + + )} + + ) + )} +
      + ) : null +} diff --git a/apps/www/components/site-footer.tsx b/apps/www/components/site-footer.tsx new file mode 100644 index 0000000..b481a25 --- /dev/null +++ b/apps/www/components/site-footer.tsx @@ -0,0 +1,31 @@ +import { siteConfig } from "@/config/site" + +export function SiteFooter() { + return ( + + ) +} diff --git a/apps/www/components/site-header.tsx b/apps/www/components/site-header.tsx new file mode 100644 index 0000000..7703835 --- /dev/null +++ b/apps/www/components/site-header.tsx @@ -0,0 +1,63 @@ +import Link from "next/link" + +import { siteConfig } from "@/config/site" +import { cn } from "@/lib/utils" +import { CommandMenu } from "@/components/command-menu" +import { Icons } from "@/components/icons" +import { MainNav } from "@/components/main-nav" +import { MobileNav } from "@/components/mobile-nav" +import { ModeToggle } from "@/components/mode-toggle" +import { buttonVariants } from "@/registry/default/ui/button" + +export function SiteHeader() { + return ( +
      +
      + + +
      +
      + +
      + +
      +
      +
      + ) +} diff --git a/apps/www/components/style-switcher.tsx b/apps/www/components/style-switcher.tsx new file mode 100644 index 0000000..252d402 --- /dev/null +++ b/apps/www/components/style-switcher.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import { type SelectTriggerProps } from "@radix-ui/react-select" + +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/registry/default/ui/select" +import { Style, styles } from "@/registry/styles" + +export function StyleSwitcher({ className }: SelectTriggerProps) { + const [config, setConfig] = useConfig() + + return ( + + ) +} diff --git a/apps/www/components/style-wrapper.tsx b/apps/www/components/style-wrapper.tsx new file mode 100644 index 0000000..c53352f --- /dev/null +++ b/apps/www/components/style-wrapper.tsx @@ -0,0 +1,20 @@ +"use client" + +import * as React from "react" + +import { useConfig } from "@/hooks/use-config" +import { Style } from "@/registry/styles" + +interface StyleWrapperProps extends React.HTMLAttributes { + styleName?: Style["name"] +} + +export function StyleWrapper({ styleName, children }: StyleWrapperProps) { + const [config] = useConfig() + + if (!styleName || config.style === styleName) { + return <>{children} + } + + return null +} diff --git a/apps/www/components/tailwind-indicator.tsx b/apps/www/components/tailwind-indicator.tsx new file mode 100644 index 0000000..535e074 --- /dev/null +++ b/apps/www/components/tailwind-indicator.tsx @@ -0,0 +1,14 @@ +export function TailwindIndicator() { + if (process.env.NODE_ENV === "production") return null + + return ( +
      +
      xs
      +
      sm
      +
      md
      +
      lg
      +
      xl
      +
      2xl
      +
      + ) +} diff --git a/apps/www/components/theme-component.tsx b/apps/www/components/theme-component.tsx new file mode 100644 index 0000000..3d56c38 --- /dev/null +++ b/apps/www/components/theme-component.tsx @@ -0,0 +1,52 @@ +"use client" + +import * as React from "react" +import { Index } from "@/__registry__" + +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" +import { Icons } from "@/components/icons" + +interface ThemeComponentProps extends React.HTMLAttributes { + name: string + extractClassname?: boolean + extractedClassNames?: string + align?: "center" | "start" | "end" +} + +export function ThemeComponent({ name, ...props }: ThemeComponentProps) { + const [config] = useConfig() + + const Preview = React.useMemo(() => { + const Component = Index[config.style][name]?.component + + if (!Component) { + return ( +

      + Component{" "} + + {name} + {" "} + not found in registry. +

      + ) + } + + return + }, [name, config.style]) + + return ( +
      + + + Loading... +
      + } + > + {Preview} + +

      + ) +} diff --git a/apps/www/components/theme-customizer.tsx b/apps/www/components/theme-customizer.tsx new file mode 100644 index 0000000..d1a0d88 --- /dev/null +++ b/apps/www/components/theme-customizer.tsx @@ -0,0 +1,619 @@ +"use client" + +import * as React from "react" +import { + CheckIcon, + CopyIcon, + InfoCircledIcon, + MoonIcon, + ResetIcon, + SunIcon, +} from "@radix-ui/react-icons" +import template from "lodash.template" +import { Paintbrush } from "lucide-react" +import { useTheme } from "next-themes" + +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" +import { copyToClipboardWithMeta } from "@/components/copy-button" +import { DrawerContent, DrawerTrigger } from "@/components/drawer" +import { ThemeWrapper } from "@/components/theme-wrapper" +import { Button } from "@/registry/default/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/registry/default/ui/dialog" +import { Label } from "@/registry/default/ui/label" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/registry/default/ui/popover" +import { Skeleton } from "@/registry/default/ui/skeleton" +import { Theme, themes } from "@/registry/themes" + +import "@/styles/mdx.css" +import { Drawer } from "vaul" + +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/registry/default/ui/tooltip" + +export function ThemeCustomizer() { + const [config, setConfig] = useConfig() + const { resolvedTheme: mode } = useTheme() + const [mounted, setMounted] = React.useState(false) + + React.useEffect(() => { + setMounted(true) + }, []) + + return ( +
      + + + + + + + + +
      +
      + {mounted ? ( + <> + {["zinc", "rose", "blue", "green", "orange"].map((color) => { + const theme = themes.find((theme) => theme.name === color) + const isActive = config.theme === color + + if (!theme) { + return null + } + + return ( + + + + + + {theme.label} + + + ) + })} + + ) : ( +
      + + + + + +
      + )} +
      + + + + + + + + +
      + +
      + ) +} + +function Customizer() { + const [mounted, setMounted] = React.useState(false) + const { setTheme: setMode, resolvedTheme: mode } = useTheme() + const [config, setConfig] = useConfig() + + React.useEffect(() => { + setMounted(true) + }, []) + + return ( + +
      +
      +
      + Customize +
      +
      + Pick a style and color for your components. +
      +
      + +
      +
      +
      +
      + + + + + About styles + + +

      + What is the difference between the New York and Default style? +

      +

      + A style comes with its own set of components, animations, + icons and more. +

      +

      + The Default style has + larger inputs, uses lucide-react for icons and + tailwindcss-animate for animations. +

      +

      + The New York style ships + with smaller buttons and cards with shadows. It uses icons + from Radix Icons. +

      +
      +
      +
      +
      + +
      +
      +
      + +
      + {themes.map((theme) => { + const isActive = config.theme === theme.name + + return mounted ? ( + + ) : ( + + ) + })} +
      +
      +
      + +
      + {["0", "0.3", "0.5", "0.75", "1.0"].map((value) => { + return ( + + ) + })} +
      +
      +
      + +
      + {mounted ? ( + <> + + + + ) : ( + <> + + + + )} +
      +
      +
      +
      + ) +} + +function CopyCodeButton() { + const [config] = useConfig() + const activeTheme = themes.find((theme) => theme.name === config.theme) + const [hasCopied, setHasCopied] = React.useState(false) + + React.useEffect(() => { + setTimeout(() => { + setHasCopied(false) + }, 2000) + }, [hasCopied]) + + return ( + <> + {activeTheme && ( + + )} + + + + + + + Theme + + Copy and paste the following code into your CSS file. + + + + + {activeTheme && ( + + )} + + + + + ) +} + +function CustomizerCode() { + const [config] = useConfig() + const activeTheme = themes.find((theme) => theme.name === config.theme) + + return ( + +
      +
      +          
      +            @layer base {
      +              :root {
      +            
      +                  --background:{" "}
      +              {activeTheme?.cssVars.light["background"]};
      +            
      +            
      +                  --foreground:{" "}
      +              {activeTheme?.cssVars.light["foreground"]};
      +            
      +            {[
      +              "card",
      +              "popover",
      +              "primary",
      +              "secondary",
      +              "muted",
      +              "accent",
      +              "destructive",
      +            ].map((prefix) => (
      +              <>
      +                
      +                      --{prefix}:{" "}
      +                  {
      +                    activeTheme?.cssVars.light[
      +                      prefix as keyof typeof activeTheme.cssVars.light
      +                    ]
      +                  }
      +                  ;
      +                
      +                
      +                      --{prefix}-foreground:{" "}
      +                  {
      +                    activeTheme?.cssVars.light[
      +                      `${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
      +                    ]
      +                  }
      +                  ;
      +                
      +              
      +            ))}
      +            
      +                  --border:{" "}
      +              {activeTheme?.cssVars.light["border"]};
      +            
      +            
      +                  --input:{" "}
      +              {activeTheme?.cssVars.light["input"]};
      +            
      +            
      +                  --ring:{" "}
      +              {activeTheme?.cssVars.light["ring"]};
      +            
      +            
      +                  --radius: {config.radius}rem;
      +            
      +              }
      +             
      +              .dark {
      +            
      +                  --background:{" "}
      +              {activeTheme?.cssVars.dark["background"]};
      +            
      +            
      +                  --foreground:{" "}
      +              {activeTheme?.cssVars.dark["foreground"]};
      +            
      +            {[
      +              "card",
      +              "popover",
      +              "primary",
      +              "secondary",
      +              "muted",
      +              "accent",
      +              "destructive",
      +            ].map((prefix) => (
      +              <>
      +                
      +                      --{prefix}:{" "}
      +                  {
      +                    activeTheme?.cssVars.dark[
      +                      prefix as keyof typeof activeTheme.cssVars.dark
      +                    ]
      +                  }
      +                  ;
      +                
      +                
      +                      --{prefix}-foreground:{" "}
      +                  {
      +                    activeTheme?.cssVars.dark[
      +                      `${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
      +                    ]
      +                  }
      +                  ;
      +                
      +              
      +            ))}
      +            
      +                  --border:{" "}
      +              {activeTheme?.cssVars.dark["border"]};
      +            
      +            
      +                  --input:{" "}
      +              {activeTheme?.cssVars.dark["input"]};
      +            
      +            
      +                  --ring:{" "}
      +              {activeTheme?.cssVars.dark["ring"]};
      +            
      +              }
      +            }
      +          
      +        
      +
      +
      + ) +} + +function getThemeCode(theme: Theme, radius: number) { + if (!theme) { + return "" + } + + return template(BASE_STYLES_WITH_VARIABLES)({ + colors: theme.cssVars, + radius, + }) +} + +const BASE_STYLES_WITH_VARIABLES = ` +@layer base { + :root { + --background: <%- colors.light["background"] %>; + --foreground: <%- colors.light["foreground"] %>; + --card: <%- colors.light["card"] %>; + --card-foreground: <%- colors.light["card-foreground"] %>; + --popover: <%- colors.light["popover"] %>; + --popover-foreground: <%- colors.light["popover-foreground"] %>; + --primary: <%- colors.light["primary"] %>; + --primary-foreground: <%- colors.light["primary-foreground"] %>; + --secondary: <%- colors.light["secondary"] %>; + --secondary-foreground: <%- colors.light["secondary-foreground"] %>; + --muted: <%- colors.light["muted"] %>; + --muted-foreground: <%- colors.light["muted-foreground"] %>; + --accent: <%- colors.light["accent"] %>; + --accent-foreground: <%- colors.light["accent-foreground"] %>; + --destructive: <%- colors.light["destructive"] %>; + --destructive-foreground: <%- colors.light["destructive-foreground"] %>; + --border: <%- colors.light["border"] %>; + --input: <%- colors.light["input"] %>; + --ring: <%- colors.light["ring"] %>; + --radius: <%- radius %>rem; + } + + .dark { + --background: <%- colors.dark["background"] %>; + --foreground: <%- colors.dark["foreground"] %>; + --card: <%- colors.dark["card"] %>; + --card-foreground: <%- colors.dark["card-foreground"] %>; + --popover: <%- colors.dark["popover"] %>; + --popover-foreground: <%- colors.dark["popover-foreground"] %>; + --primary: <%- colors.dark["primary"] %>; + --primary-foreground: <%- colors.dark["primary-foreground"] %>; + --secondary: <%- colors.dark["secondary"] %>; + --secondary-foreground: <%- colors.dark["secondary-foreground"] %>; + --muted: <%- colors.dark["muted"] %>; + --muted-foreground: <%- colors.dark["muted-foreground"] %>; + --accent: <%- colors.dark["accent"] %>; + --accent-foreground: <%- colors.dark["accent-foreground"] %>; + --destructive: <%- colors.dark["destructive"] %>; + --destructive-foreground: <%- colors.dark["destructive-foreground"] %>; + --border: <%- colors.dark["border"] %>; + --input: <%- colors.dark["input"] %>; + --ring: <%- colors.dark["ring"] %>; + } +} +` diff --git a/apps/www/components/theme-switcher.tsx b/apps/www/components/theme-switcher.tsx new file mode 100644 index 0000000..ed5951f --- /dev/null +++ b/apps/www/components/theme-switcher.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import { useSelectedLayoutSegment } from "next/navigation" + +import { useConfig } from "@/hooks/use-config" + +export function ThemeSwitcher() { + const [config] = useConfig() + const segment = useSelectedLayoutSegment() + + React.useEffect(() => { + document.body.classList.forEach((className) => { + if (className.match(/^theme.*/)) { + document.body.classList.remove(className) + } + }) + + const theme = segment === "themes" ? config.theme : null + if (theme) { + return document.body.classList.add(`theme-${theme}`) + } + }, [segment, config]) + + return null +} diff --git a/apps/www/components/theme-wrapper.tsx b/apps/www/components/theme-wrapper.tsx new file mode 100644 index 0000000..d4ac140 --- /dev/null +++ b/apps/www/components/theme-wrapper.tsx @@ -0,0 +1,33 @@ +"use client" + +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" + +interface ThemeWrapperProps extends React.ComponentProps<"div"> { + defaultTheme?: string +} + +export function ThemeWrapper({ + defaultTheme, + children, + className, +}: ThemeWrapperProps) { + const [config] = useConfig() + + return ( +
      + {children} +
      + ) +} diff --git a/apps/www/components/toc.tsx b/apps/www/components/toc.tsx new file mode 100644 index 0000000..29838f0 --- /dev/null +++ b/apps/www/components/toc.tsx @@ -0,0 +1,107 @@ +// @ts-nocheck +"use client" + +import * as React from "react" + +import { TableOfContents } from "@/lib/toc" +import { cn } from "@/lib/utils" +import { useMounted } from "@/hooks/use-mounted" + +interface TocProps { + toc: TableOfContents +} + +export function DashboardTableOfContents({ toc }: TocProps) { + const itemIds = React.useMemo( + () => + toc.items + ? toc.items + .flatMap((item) => [item.url, item?.items?.map((item) => item.url)]) + .flat() + .filter(Boolean) + .map((id) => id?.split("#")[1]) + : [], + [toc] + ) + const activeHeading = useActiveItem(itemIds) + const mounted = useMounted() + + if (!toc?.items || !mounted) { + return null + } + + return ( +
      +

      On This Page

      + +
      + ) +} + +function useActiveItem(itemIds: string[]) { + const [activeId, setActiveId] = React.useState(null) + + React.useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(entry.target.id) + } + }) + }, + { rootMargin: `0% 0% -80% 0%` } + ) + + itemIds?.forEach((id) => { + const element = document.getElementById(id) + if (element) { + observer.observe(element) + } + }) + + return () => { + itemIds?.forEach((id) => { + const element = document.getElementById(id) + if (element) { + observer.unobserve(element) + } + }) + } + }, [itemIds]) + + return activeId +} + +interface TreeProps { + tree: TableOfContents + level?: number + activeItem?: string +} + +function Tree({ tree, level = 1, activeItem }: TreeProps) { + return tree?.items?.length && level < 3 ? ( +
        + {tree.items.map((item, index) => { + return ( +
      • + + {item.title} + + {item.items?.length ? ( + + ) : null} +
      • + ) + })} +
      + ) : null +} diff --git a/apps/www/config/connectors.ts b/apps/www/config/connectors.ts new file mode 100644 index 0000000..4ade616 --- /dev/null +++ b/apps/www/config/connectors.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +import { CoinbaseWalletConnector } from "@wagmi/core/connectors/coinbaseWallet" +import { InjectedConnector } from "@wagmi/core/connectors/injected" +import { MetaMaskConnector } from "@wagmi/core/connectors/metaMask" + +export const connectors = [ + new MetaMaskConnector(), + new CoinbaseWalletConnector({ + options: { + appName: "wagmi.sh", + jsonRpcUrl: "https://eth-mainnet.alchemyapi.io/v2/yourAlchemyId", + }, + }), +] diff --git a/apps/www/config/constants.ts b/apps/www/config/constants.ts new file mode 100644 index 0000000..dcdcc12 --- /dev/null +++ b/apps/www/config/constants.ts @@ -0,0 +1,5 @@ +export const ADDRESS_ZERO = + "0x0000000000000000000000000000000000000000" as `0x${string}` + +export const ADDRESS_EXAMPLE = + "0x761d584f1C2d43cBc3F42ECd739701a36dFFAa31" as `0x${string}` diff --git a/apps/www/config/docs.ts b/apps/www/config/docs.ts new file mode 100644 index 0000000..840e214 --- /dev/null +++ b/apps/www/config/docs.ts @@ -0,0 +1,178 @@ +import { MainNavItem, SidebarNavItem } from "types/nav" + +interface DocsConfig { + mainNav: MainNavItem[] + sidebarNav: SidebarNavItem[] +} + +export const docsConfig: DocsConfig = { + mainNav: [ + { + title: "Documentation", + href: "/docs", + }, + { + title: "Components", + href: "/docs/components/accordion", + }, + { + title: "Themes", + href: "/themes", + }, + { + title: "Examples", + href: "/examples", + }, + { + title: "Figma", + href: "/docs/figma", + }, + { + title: "GitHub", + href: "https://github.com/turboeth/buidl", + external: true, + }, + { + title: "Twitter", + href: "https://twitter.com/districtFi", + external: true, + }, + ], + sidebarNav: [ + { + title: "Getting Started", + items: [ + { + title: "Introduction", + href: "/docs", + items: [], + }, + ], + }, + { + title: "Installation", + items: [ + { + title: "Manual", + href: "/docs/installation/manual", + items: [], + }, + ], + }, + { + title: "Wallet/Network", + items: [ + { + title: "Wallet Connect", + href: "/docs/components/wallet-connect", + items: [], + }, + { + title: "Wallet Manage", + href: "/docs/components/wallet-manage", + items: [], + }, + { + title: "Network Manage", + href: "/docs/components/network-manage", + items: [], + }, + ], + }, + { + title: "Primitives", + items: [ + { + title: "Address", + href: "/docs/components/address", + items: [], + }, + { + title: "Blockie", + href: "/docs/components/blockie", + items: [], + }, + { + title: "ENS Avatar", + href: "/docs/components/ens-avatar", + items: [], + }, + { + title: "ENS Name", + href: "/docs/components/ens-name", + items: [], + }, + ], + }, + { + title: "Account", + items: [ + { + title: "Account Address", + href: "/docs/components/account-address", + items: [], + }, + { + title: "Account Blockie", + href: "/docs/components/account-blockie", + items: [], + }, + { + title: "Account ENS Avatar", + href: "/docs/components/account-ens-avatar", + items: [], + }, + { + title: "Account ENS Name", + href: "/docs/components/account-ens-name", + items: [], + }, + ], + }, + { + title: "ERC20", + items: [ + { + title: "ERC20 Balance", + href: "/docs/components/erc20-balance", + items: [], + }, + { + title: "ERC20 Image", + href: "/docs/components/erc20-image", + items: [], + }, + { + title: "ERC20 Name", + href: "/docs/components/erc20-name", + items: [], + }, + { + title: "ERC20 Symbol", + href: "/docs/components/erc20-symbol", + items: [], + }, + { + title: "ERC20 Select", + href: "/docs/components/erc20-select", + items: [], + }, + { + title: "ERC20 Select & Amount", + href: "/docs/components/erc20-select-and-amount", + items: [], + }, + ], + }, + // { + // title: "ERC721", + // items: [ + // { + // title: "ERC721 Name", + // href: "/docs/components/erc721-name", + // items: [], + // }, + // ], + // }, + ], +} diff --git a/apps/www/config/networks.ts b/apps/www/config/networks.ts new file mode 100644 index 0000000..9b4763e --- /dev/null +++ b/apps/www/config/networks.ts @@ -0,0 +1,68 @@ +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +// Networks +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +import { env } from "@/env.mjs" +import { Chain, ChainProviderFn, configureChains } from "wagmi" +import { + goerli as goerliNoIcon, + mainnet, + optimism, + optimismGoerli, + sepolia as sepoliaNoIcon, +} from "wagmi/chains" +import { alchemyProvider } from "wagmi/providers/alchemy" +import { infuraProvider } from "wagmi/providers/infura" +import { publicProvider } from "wagmi/providers/public" + +const goerli = { + ...goerliNoIcon, + iconUrl: "/icons/NetworkEthereumTest.svg", +} +const sepolia = { + ...sepoliaNoIcon, + iconUrl: "/icons/NetworkEthereumTest.svg", +} + +export const ETH_CHAINS_TEST = [goerli, sepolia, optimismGoerli] + +export const ETH_CHAINS_PROD = [mainnet, optimism] +export const ETH_CHAINS_DEV = + env.NEXT_PUBLIC_PROD_NETWORKS_DEV === "true" + ? [...ETH_CHAINS_PROD, ...ETH_CHAINS_TEST] + : [...ETH_CHAINS_TEST] + +export const CHAINS: Chain[] = + process.env.NODE_ENV === "production" ? ETH_CHAINS_PROD : ETH_CHAINS_DEV + +const PROVIDERS: ChainProviderFn[] = [] + +if (env.NEXT_PUBLIC_ALCHEMY_API_KEY) { + if (!env.NEXT_PUBLIC_ALCHEMY_API_KEY) + throw new Error("NEXT_PUBLIC_ALCHEMY_API_KEY is not defined") + PROVIDERS.push( + alchemyProvider({ + apiKey: env.NEXT_PUBLIC_ALCHEMY_API_KEY, + }) + ) +} + +if (env.NEXT_PUBLIC_INFURA_API_KEY) { + if (!env.NEXT_PUBLIC_INFURA_API_KEY) + throw new Error("NEXT_PUBLIC_INFURA_API_KEY is not defined") + PROVIDERS.push( + infuraProvider({ + apiKey: env.NEXT_PUBLIC_INFURA_API_KEY, + }) + ) +} + +// Fallback to public provider +// Only include public provider if no other providers are available. +if (PROVIDERS.length === 0 || env.NEXT_PUBLIC_USE_PUBLIC_PROVIDER === "true") { + PROVIDERS.push(publicProvider()) +} + +export const { chains, publicClient, webSocketPublicClient } = configureChains( + CHAINS, + PROVIDERS +) diff --git a/apps/www/config/site.ts b/apps/www/config/site.ts new file mode 100644 index 0000000..af58af5 --- /dev/null +++ b/apps/www/config/site.ts @@ -0,0 +1,13 @@ +export const siteConfig = { + name: "turboeth/buidl", + url: "https://buidl.turboeth.com", + ogImage: "https://buidl.turboeth.com/og.jpg", + description: + "Beautifully designed components built with Radix UI and Tailwind CSS.", + links: { + twitter: "https://twitter.com/districtfi", + github: "https://github.com/turboeth/buidl", + }, +} + +export type SiteConfig = typeof siteConfig diff --git a/apps/www/content/docs/about.mdx b/apps/www/content/docs/about.mdx new file mode 100644 index 0000000..ab52318 --- /dev/null +++ b/apps/www/content/docs/about.mdx @@ -0,0 +1,20 @@ +--- +title: About +description: Powered by amazing open source projects. +--- + +## About + +[ui.shadcn.com](https://ui.shadcn.com) is a project by [shadcn](https://shadcn.com). + +## Credits + +- [Radix UI](https://radix-ui.com) - For the primitives. +- [Vercel](https://vercel.com) - Where I host all my projects. +- [Shu Ding](https://shud.in) - The typography style is adapted from his work on Nextra. +- [Cal](https://cal.com) - Where I copied the styles for the first component: the `Button`. +- [cmdk](https://cmdk.paco.me) - For the `` component. + +## License + +MIT © [shadcn](https://shadcn.com) diff --git a/apps/www/content/docs/changelog.mdx b/apps/www/content/docs/changelog.mdx new file mode 100644 index 0000000..7f76738 --- /dev/null +++ b/apps/www/content/docs/changelog.mdx @@ -0,0 +1,11 @@ +--- +title: Changelog +description: Latest updates and announcements. +toc: false +--- + +## November 27th - Alpha Launch + +Launching the TurboETH BUIDL component library! + +This is the first release of the TurboETH BUIDL component library. We are excited to share this with the community and look forward to your feedback. diff --git a/apps/www/content/docs/components-json.mdx b/apps/www/content/docs/components-json.mdx new file mode 100644 index 0000000..87b5f75 --- /dev/null +++ b/apps/www/content/docs/components-json.mdx @@ -0,0 +1,163 @@ +--- +title: components.json +description: Configuration for your project. +--- + +The `components.json` file holds configuration for your project. + +We use it to understand how your project is set up and how to generate components customized for your project. + + + Note: The `components.json` file is optional and **only required if you're + using the CLI** to add components to your project. If you're using the copy + and paste method, you don't need this file. + + +You can create a `components.json` file in your project by running the following command: + +```bash +npx shadcn-ui@latest init +``` + +See the CLI section for more information. + +## $schema + +You can see the JSON Schema for `components.json` [here](https://ui.shadcn.com/schema.json). + +```json title="components.json" +{ + "$schema": "https://ui.shadcn.com/schema.json" +} +``` + +## style + +The style for your components. **This cannot be changed after initialization.** + +```json title="components.json" +{ + "style": "default" | "new-york" +} +``` + + + +## tailwind + +Configuration to help the CLI understand how Tailwind CSS is set up in your project. + +See the installation section for how to set up Tailwind CSS. + +### tailwind.config + +Path to where your `tailwind.config.js` file is located. + +```json title="components.json" +{ + "tailwind": { + "config": "tailwind.config.js" | "tailwind.config.ts" + } +} +``` + +### tailwind.css + +Path to the CSS file that imports Tailwind CSS into your project. + +```json title="components.json" +{ + "tailwind": { + "css": "styles/global.css" + } +} +``` + +### tailwind.baseColor + +This is used to generate the default color palette for your components. **This cannot be changed after initialization.** + +```json title="components.json" +{ + "tailwind": { + "baseColor": "gray" | "neutral" | "slate" | "stone" | "zinc" + } +} +``` + +### tailwind.cssVariables + +You can choose between using CSS variables or Tailwind CSS utility classes for theming. + +To use utility classes for theming set `tailwind.cssVariables` to `false`. For CSS variables, set `tailwind.cssVariables` to `true`. + +```json title="components.json" +{ + "tailwind": { + "cssVariables": `true` | `false` + } +} +``` + +For more information, see the theming docs. + +**This cannot be changed after initialization.** To switch between CSS variables and utility classes, you'll have to delete and re-install your components. + +## rsc + +Whether or not to enable support for React Server Components. + +The CLI automatically adds a `use client` directive to client components when set to `true`. + +```json title="components.json" +{ + "rsc": `true` | `false` +} +``` + +## tsx + +Choose between TypeScript or JavaScript components. + +Setting this option to `false` allows components to be added as JavaScript with the `.jsx` file extension. + +```json title="components.json" +{ + "tsx": `true` | `false` +} +``` + +## aliases + +The CLI uses these values and the `paths` config from your `tsconfig.json` or `jsconfig.json` file to place generated components in the correct location. + +Path aliases have to be set up in your `tsconfig.json` or `jsconfig.json` file. + + + **Important:** If you're using the `src` directory, make sure it is included + under `paths` in your `tsconfig.json` or `jsconfig.json` file. + + +### aliases.utils + +Import alias for your utility functions. + +```json title="components.json" +{ + "aliases": { + "utils": "@/lib/utils" + } +} +``` + +### aliases.components + +Import alias for your components. + +```json title="components.json" +{ + "aliases": { + "components": "@/components" + } +} +``` diff --git a/apps/www/content/docs/components/accordion.mdx b/apps/www/content/docs/components/accordion.mdx new file mode 100644 index 0000000..6db148f --- /dev/null +++ b/apps/www/content/docs/components/accordion.mdx @@ -0,0 +1,136 @@ +--- +title: Accordion +description: A vertically stacked set of interactive headings that each reveal a section of content. +component: true +radix: + link: https://www.radix-ui.com/docs/primitives/components/accordion + api: https://www.radix-ui.com/docs/primitives/components/accordion#api-reference +--- + + + +## Installation + + + + + CLI + Manual + + + + + + +Run the following command: + +```bash +npx shadcn-ui@latest add accordion +``` + +Update `tailwind.config.js` + +Add the following animations to your `tailwind.config.js` file: + +```js title="tailwind.config.js" {5-18} +/** @type {import('tailwindcss').Config} */ +module.exports = { + theme: { + extend: { + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, +} +``` + + + + + + + + + +Install the following dependencies: + +```bash +npm install @radix-ui/react-accordion +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + +Update `tailwind.config.js` + +Add the following animations to your `tailwind.config.js` file: + +```js title="tailwind.config.js" {5-18} +/** @type {import('tailwindcss').Config} */ +module.exports = { + theme: { + extend: { + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, +} +``` + + + + + + + +## Usage + +```tsx +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" +``` + +```tsx + + + Is it accessible? + + Yes. It adheres to the WAI-ARIA design pattern. + + + +``` diff --git a/apps/www/content/docs/components/account-address.mdx b/apps/www/content/docs/components/account-address.mdx new file mode 100644 index 0000000..fb5bd7c --- /dev/null +++ b/apps/www/content/docs/components/account-address.mdx @@ -0,0 +1,59 @@ +--- +title: Address +description: Display connected account address. +component: true +wagmi: + link: https://wagmi.sh/react/hooks/useNetwork +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { AccountAddress } from "@/components/buidl/account-address" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/account-blockie.mdx b/apps/www/content/docs/components/account-blockie.mdx new file mode 100644 index 0000000..26acce9 --- /dev/null +++ b/apps/www/content/docs/components/account-blockie.mdx @@ -0,0 +1,58 @@ +--- +title: Account Blockie +description: Display connected account blockie identicon. +component: true +radix: + link: https://www.radix-ui.com/docs/primitives/components/accordion + api: https://www.radix-ui.com/docs/primitives/components/accordion#api-reference +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + + + + + + + +## Usage + +```tsx +import { AccountBlockie } from "@/components/buidl/account-blockie" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/account-ens-avatar.mdx b/apps/www/content/docs/components/account-ens-avatar.mdx new file mode 100644 index 0000000..5d4e069 --- /dev/null +++ b/apps/www/content/docs/components/account-ens-avatar.mdx @@ -0,0 +1,59 @@ +--- +title: Account ENS Avatar +description: Display the connected account ENS avatar. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { AccountEnsAvatar } from "@/components/buidl/account-ens-avatar" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/account-ens-name.mdx b/apps/www/content/docs/components/account-ens-name.mdx new file mode 100644 index 0000000..3be91a6 --- /dev/null +++ b/apps/www/content/docs/components/account-ens-name.mdx @@ -0,0 +1,59 @@ +--- +title: Account ENS Name +description: Display the connected account ENS name. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { AccountEnsAvatar } from "@/components/buidl/account-ens-name" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/address.mdx b/apps/www/content/docs/components/address.mdx new file mode 100644 index 0000000..e0b058d --- /dev/null +++ b/apps/www/content/docs/components/address.mdx @@ -0,0 +1,59 @@ +--- +title: Address +description: Manage the display of an address. +component: true +wagmi: + link: https://wagmi.sh/react/hooks/useNetwork +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Address } from "@/components/buidl/address" +``` + +```tsx +
      +``` diff --git a/apps/www/content/docs/components/blockie.mdx b/apps/www/content/docs/components/blockie.mdx new file mode 100644 index 0000000..e9012f0 --- /dev/null +++ b/apps/www/content/docs/components/blockie.mdx @@ -0,0 +1,64 @@ +--- +title: Blockie +description: Display an account blockie identicon. +component: true +radix: + link: https://www.radix-ui.com/docs/primitives/components/accordion + api: https://www.radix-ui.com/docs/primitives/components/accordion#api-reference +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Install the following dependencies: + +```bash +pnpm add ethereum-blockies-base64 +``` + + + + + + + +## Usage + +```tsx +import { Blockie } from "@/components/buidl/blockie" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/ens-avatar.mdx b/apps/www/content/docs/components/ens-avatar.mdx new file mode 100644 index 0000000..b4336ad --- /dev/null +++ b/apps/www/content/docs/components/ens-avatar.mdx @@ -0,0 +1,59 @@ +--- +title: ENS Avatar +description: Display an account ENS avatar. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { EnsAvatar } from "@/components/buidl/ens-avatar" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/ens-name.mdx b/apps/www/content/docs/components/ens-name.mdx new file mode 100644 index 0000000..82cc2fa --- /dev/null +++ b/apps/www/content/docs/components/ens-name.mdx @@ -0,0 +1,59 @@ +--- +title: ENS Name +description: Display an account ENS domain name. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { EnsName } from "@/components/buidl/ens-name" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/ens-symbol.mdx b/apps/www/content/docs/components/ens-symbol.mdx new file mode 100644 index 0000000..d95440b --- /dev/null +++ b/apps/www/content/docs/components/ens-symbol.mdx @@ -0,0 +1,59 @@ +--- +title: ENS Symbol +description: Display an account ENS domain name. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { EnsName } from "@/components/buidl/ens-name" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/erc20-balance.mdx b/apps/www/content/docs/components/erc20-balance.mdx new file mode 100644 index 0000000..334ab7d --- /dev/null +++ b/apps/www/content/docs/components/erc20-balance.mdx @@ -0,0 +1,60 @@ +--- +title: ERC20 Balance +description: Display the balance of an ERC20 token for a given address. +component: true +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Erc20Balance } from "@/components/buidl/erc20-balance" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/erc20-image.mdx b/apps/www/content/docs/components/erc20-image.mdx new file mode 100644 index 0000000..b350035 --- /dev/null +++ b/apps/www/content/docs/components/erc20-image.mdx @@ -0,0 +1,57 @@ +--- +title: ERC20 Image +description: Display the icon of an ERC20 token +component: true +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Erc20Name } from "@/components/buidl/erc20-image" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/erc20-name.mdx b/apps/www/content/docs/components/erc20-name.mdx new file mode 100644 index 0000000..5edbdf2 --- /dev/null +++ b/apps/www/content/docs/components/erc20-name.mdx @@ -0,0 +1,57 @@ +--- +title: ERC20 Name +description: Display the name of an ERC20 token +component: true +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Erc20Name } from "@/components/buidl/erc20-name" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/erc20-select-and-amount.mdx b/apps/www/content/docs/components/erc20-select-and-amount.mdx new file mode 100644 index 0000000..67ba22a --- /dev/null +++ b/apps/www/content/docs/components/erc20-select-and-amount.mdx @@ -0,0 +1,57 @@ +--- +title: ERC20 Select & Amount +description: Select an ERC20 token and amount from a list of tokens. +component: true +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Erc20Symbol } from "@/components/buidl/erc20-select-and-amount" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/erc20-select.mdx b/apps/www/content/docs/components/erc20-select.mdx new file mode 100644 index 0000000..8a34107 --- /dev/null +++ b/apps/www/content/docs/components/erc20-select.mdx @@ -0,0 +1,57 @@ +--- +title: ERC20 Select +description: Select an ERC20 token from a list of tokens. +component: true +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Erc20Symbol } from "@/components/buidl/erc20-select" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/erc20-symbol.mdx b/apps/www/content/docs/components/erc20-symbol.mdx new file mode 100644 index 0000000..ed53222 --- /dev/null +++ b/apps/www/content/docs/components/erc20-symbol.mdx @@ -0,0 +1,57 @@ +--- +title: ENS Symbol +description: Display the symbol of an ERC20 token +component: true +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { Erc20Symbol } from "@/components/buidl/erc20-symbol" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/network-manage.mdx b/apps/www/content/docs/components/network-manage.mdx new file mode 100644 index 0000000..7b10352 --- /dev/null +++ b/apps/www/content/docs/components/network-manage.mdx @@ -0,0 +1,59 @@ +--- +title: Network Manage +description: Manage your wallet's network connections. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { NetworkManage } from "@/components/buidl/network-manage" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/wallet-connect.mdx b/apps/www/content/docs/components/wallet-connect.mdx new file mode 100644 index 0000000..fe4551d --- /dev/null +++ b/apps/www/content/docs/components/wallet-connect.mdx @@ -0,0 +1,59 @@ +--- +title: Wallet Connect +description: Connect to a wallet provider or service. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { EnsName } from "@/components/buidl/wallet-connect" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/components/wallet-manage.mdx b/apps/www/content/docs/components/wallet-manage.mdx new file mode 100644 index 0000000..0a90bcf --- /dev/null +++ b/apps/www/content/docs/components/wallet-manage.mdx @@ -0,0 +1,59 @@ +--- +title: Wallet Manage +description: Manage your wallet connection and display basic details. +component: true +radix: + link: https://wagmi.sh/react/hooks/useEnsAvatar +--- + + + +## Installation + + + + + Manual + + CLI (Coming Soon) + + + + + + + +Coming soon... + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Usage + +```tsx +import { WalletManage } from "@/components/buidl/wallet-manage" +``` + +```tsx + +``` diff --git a/apps/www/content/docs/dark-mode/astro.mdx b/apps/www/content/docs/dark-mode/astro.mdx new file mode 100644 index 0000000..01e4ffe --- /dev/null +++ b/apps/www/content/docs/dark-mode/astro.mdx @@ -0,0 +1,121 @@ +--- +title: Astro +description: Adding dark mode to your astro app. +--- + +## Dark mode + + + +### Create an inline theme script + +```astro title="src/pages/index.astro" +--- +import '../styles/globals.css' +--- + + + + + +

      Astro

      + + + +``` + +### Add a mode toggle + +```tsx title="src/components/ModeToggle.tsx" +import * as React from "react" +import { Moon, Sun } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +export function ModeToggle() { + const [theme, setThemeState] = React.useState< + "theme-light" | "dark" | "system" + >("theme-light") + + React.useEffect(() => { + const isDarkMode = document.documentElement.classList.contains("dark") + setThemeState(isDarkMode ? "dark" : "theme-light") + }, []) + + React.useEffect(() => { + const isDark = + theme === "dark" || + (theme === "system" && + window.matchMedia("(prefers-color-scheme: dark)").matches) + document.documentElement.classList[isDark ? "add" : "remove"]("dark") + }, [theme]) + + return ( + + + + + + setThemeState("theme-light")}> + Light + + setThemeState("dark")}> + Dark + + setThemeState("system")}> + System + + + + ) +} +``` + +### Display the mode toggle + +Place a mode toggle on your site to toggle between light and dark mode. + +```astro title="src/pages/index.astro" +--- +import '../styles/globals.css' +import { ModeToggle } from '@/components/ModeToggle'; +--- + + + + + +

      Astro

      + + + +``` + +
      diff --git a/apps/www/content/docs/dark-mode/index.mdx b/apps/www/content/docs/dark-mode/index.mdx new file mode 100644 index 0000000..79b9298 --- /dev/null +++ b/apps/www/content/docs/dark-mode/index.mdx @@ -0,0 +1,63 @@ +--- +title: Dark Mode +description: Adding dark mode to your site. +--- + +
      + + + Next.js + + +

      Next.js

      +
      + + + Vite + + +

      Vite

      +
      + + + + + +

      Astro

      +
      + + + Remix + + +

      Remix

      +
      +
      + +## Other frameworks + +I'm looking for help writing guides for other frameworks. Help me write guides for Remix, Astro and Vite by [opening an PR](https://github.com/shadcn/ui). diff --git a/apps/www/content/docs/dark-mode/next.mdx b/apps/www/content/docs/dark-mode/next.mdx new file mode 100644 index 0000000..175014c --- /dev/null +++ b/apps/www/content/docs/dark-mode/next.mdx @@ -0,0 +1,66 @@ +--- +title: Next.js +description: Adding dark mode to your next app. +--- + +## Dark mode + + + +### Install next-themes + +Start by installing `next-themes`: + +```bash +npm install next-themes +``` + +### Create a theme provider + +```tsx title="components/theme-provider.tsx" +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import { type ThemeProviderProps } from "next-themes/dist/types" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} +``` + +### Wrap your root layout + +Add the `ThemeProvider` to your root layout. + +```tsx {1,9-11} title="app/layout.tsx" +import { ThemeProvider } from "@/components/theme-provider" + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + <> + + + + + {children} + + + + + ) +} +``` + +### Add a mode toggle + +Place a mode toggle on your site to toggle between light and dark mode. + + + + diff --git a/apps/www/content/docs/dark-mode/remix.mdx b/apps/www/content/docs/dark-mode/remix.mdx new file mode 100644 index 0000000..2b523c5 --- /dev/null +++ b/apps/www/content/docs/dark-mode/remix.mdx @@ -0,0 +1,159 @@ +--- +title: Remix +description: Adding dark mode to your remix app. +--- + +## Dark mode + + + +### Modify your tailwind.css file + +Add `:root[class~="dark"]` to your tailwind.css file. This will allow you to use the `dark` class on your html element to apply dark mode styles. + +```css {2} title="app/tailwind.css" +.dark, +:root[class~="dark"] { + ...; +} +``` + +### Install remix-themes + +Start by installing `remix-themes`: + +```bash +npm install remix-themes +``` + +### Create a session storage and theme session resolver + +```tsx title="app/sessions.server.tsx" +import { createThemeSessionResolver } from "remix-themes" + +// You can default to 'development' if process.env.NODE_ENV is not set +const isProduction = process.env.NODE_ENV === "production" + +const sessionStorage = createCookieSessionStorage({ + cookie: { + name: "theme", + path: "/", + httpOnly: true, + sameSite: "lax", + secrets: ["s3cr3t"], + // Set domain and secure only if in production + ...(isProduction + ? { domain: "your-production-domain.com", secure: true } + : {}), + }, +}) + +export const themeSessionResolver = createThemeSessionResolver(sessionStorage) +``` + +### Set up Remix Themes + +Add the `ThemeProvider` to your root layout. + +```tsx {1-3,6-11,15-22,25-26,28,33} title="app/root.tsx" +import clsx from "clsx" +import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from "remix-themes" + +import { themeSessionResolver } from "./sessions.server" + +// Return the theme from the session storage using the loader +export async function loader({ request }: LoaderFunctionArgs) { + const { getTheme } = await themeSessionResolver(request) + return { + theme: getTheme(), + } +} +// Wrap your app with ThemeProvider. +// `specifiedTheme` is the stored theme in the session storage. +// `themeAction` is the action name that's used to change the theme in the session storage. +export default function AppWithProviders() { + const data = useLoaderData() + return ( + + + + ) +} + +export function App() { + const data = useLoaderData() + const [theme] = useTheme() + return ( + + + + + + + + + + + + + + + + ) +} +``` + +### Add an action route + +Create a file in `/routes/action.set-theme.ts`. Ensure that you pass the filename to the ThemeProvider component. This route it's used to store the preferred theme in the session storage when the user changes it. + +```tsx title="app/routes/action.set-theme.ts" +import { createThemeAction } from "remix-themes" + +import { themeSessionResolver } from "./sessions.server" + +export const action = createThemeAction(themeSessionResolver) +``` + +### Add a mode toggle + +Place a mode toggle on your site to toggle between light and dark mode. + +```tsx title="components/mode-toggle.tsx" +import { Moon, Sun } from "lucide-react" +import { Theme, useTheme } from "remix-themes" + +import { Button } from "./ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "./ui/dropdown-menu" + +export function ModeToggle() { + const [, setTheme] = useTheme() + + return ( + + + + + + setTheme(Theme.LIGHT)}> + Light + + setTheme(Theme.DARK)}> + Dark + + + + ) +} +``` + + diff --git a/apps/www/content/docs/dark-mode/vite.mdx b/apps/www/content/docs/dark-mode/vite.mdx new file mode 100644 index 0000000..4617f42 --- /dev/null +++ b/apps/www/content/docs/dark-mode/vite.mdx @@ -0,0 +1,150 @@ +--- +title: Vite +description: Adding dark mode to your vite app. +--- + +## Dark mode + + + +### Create a theme provider + +```tsx title="components/theme-provider.tsx" +import { createContext, useContext, useEffect, useState } from "react" + +type Theme = "dark" | "light" | "system" + +type ThemeProviderProps = { + children: React.ReactNode + defaultTheme?: Theme + storageKey?: string +} + +type ThemeProviderState = { + theme: Theme + setTheme: (theme: Theme) => void +} + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +} + +const ThemeProviderContext = createContext(initialState) + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme + ) + + useEffect(() => { + const root = window.document.documentElement + + root.classList.remove("light", "dark") + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light" + + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme) + setTheme(theme) + }, + } + + return ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext) + + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider") + + return context +} +``` + +### Wrap your root layout + +Add the `ThemeProvider` to your root layout. + +```tsx {1,5-7} title="App.tsx" +import { ThemeProvider } from "@/components/theme-provider" + +function App() { + return ( + + {children} + + ) +} + +export default App +``` + +### Add a mode toggle + +Place a mode toggle on your site to toggle between light and dark mode. + +```tsx title="components/mode-toggle.tsx" +import { Moon, Sun } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { useTheme } from "@/components/theme-provider" + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ) +} +``` + + diff --git a/apps/www/content/docs/index.mdx b/apps/www/content/docs/index.mdx new file mode 100644 index 0000000..644b096 --- /dev/null +++ b/apps/www/content/docs/index.mdx @@ -0,0 +1,67 @@ +--- +title: Introduction +description: Re-usable components built using Radix UI and Tailwind CSS. +--- + +This is **NOT** a component library. It's a collection of re-usable components that you can copy and paste into your apps. + +**What do you mean by not a component library?** + +I mean you do not install it as a dependency. It is not available or distributed via npm. + +Pick the components you need. Copy and paste the code into your project and customize to your needs. The code is yours. + +_Use this as a reference to build your own component libraries._ + +## FAQ + + + + + + Why copy/paste and not packaged as a dependency? + + +The idea behind this is to give you ownership and control over the code, allowing you to decide how the components are built and styled. + +Start with some sensible defaults, then customize the components to your needs. + +One of the drawback of packaging the components in an npm package is that the style is coupled with the implementation. _The design of your components should be separate from their implementation._ + + + + + + + Do you plan to publish it as an npm package? + + + No. I have no plans to publish it as an npm package. + + + + + +Which frameworks are supported? + + + +You can use any framework that supports React. [Next.js](https://ui.shadcn.com/docs/installation/next), [Astro](https://ui.shadcn.com/docs/installation/astro), [Remix](https://ui.shadcn.com/docs/installation/remix), [Gatsby](https://ui.shadcn.com/docs/installation/gatsby) etc. + + + + + + + Can I use this in my project? + + +Yes. Free to use for personal and commercial projects. No attribution required. + +But hey, let me know if you do. I'd love to see what you build. + + + + + + diff --git a/apps/www/content/docs/installation/astro.mdx b/apps/www/content/docs/installation/astro.mdx new file mode 100644 index 0000000..311bfba --- /dev/null +++ b/apps/www/content/docs/installation/astro.mdx @@ -0,0 +1,165 @@ +--- +title: Astro +description: Install and configure Astro. +--- + + + +### Create project + +Start by creating a new Astro project: + +```bash +npm create astro@latest +``` + +### Configure your Astro project + +You will be asked a few questions to configure your project: + +```txt showLineNumbers +- Where should we create your new project? +./your-app-name +- How would you like to start your new project? +Choose a starter template (or Empty) +- Install dependencies? +Yes +- Do you plan to write TypeScript? +Yes +- How strict should TypeScript be? +Strict +- Initialize a new git repository? (optional) +Yes/No +``` + +### Add React to your project + +Install React using the Astro CLI: + +```bash +npx astro add react +``` + + + +Answer `Yes` to all the question prompted by the CLI when installing React. + + + +### Add Tailwind CSS to your project + +Install Tailwind CSS using the Astro CLI: + +```bash +npx astro add tailwind +``` + + + +Answer `Yes` to all the question prompted by the CLI when installing Tailwind CSS. + + + +### Edit tsconfig.json file + +Add the following code to the `tsconfig.json` file to resolve paths: + +```ts {4-9} showLineNumbers +{ + "compilerOptions": { + // ... + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + } + // ... + } +} +``` + +### Run the CLI + +Run the `shadcn-ui` init command to setup your project: + +```bash +npx shadcn-ui@latest init +``` + +### Configure components.json + +You will be asked a few questions to configure `components.json`: + +```txt showLineNumbers +Would you like to use TypeScript (recommended)? no / yes +Which style would you like to use? › Default +Which color would you like to use as base color? › Slate +Where is your global CSS file? › › ./src/styles/globals.css +Do you want to use CSS variables for colors? › no / yes +Where is your tailwind.config.js located? › tailwind.config.mjs +Configure the import alias for components: › @/components +Configure the import alias for utils: › @/lib/utils +Are you using React Server Components? › no +``` + +### Import the globals.css file + +Import the `globals.css` file in the `src/pages/index.astro` file: + +```ts {2} showLineNumbers +--- +import '@/styles/globals.css' +--- +``` + +### Update astro tailwind config + +To prevent serving the Tailwind base styles twice, we need to tell Astro not to apply the base styles, since we already include them in our own `globals.css` file. To do this, set the `applyBaseStyles` config option for the tailwind plugin in `astro.config.mjs` to `false`. + +```ts {3-5} showLineNumbers +export default defineConfig({ + integrations: [ + tailwind({ + applyBaseStyles: false, + }), + ], +}) +``` + +### Update tailwind.config.mjs + +When running `npx shadcn-ui@latest init`, your tailwind config for content will be overwritten. To fix this, change the `module.exports` to `export default` and the `content` section with the code below to your `tailwind.config.mjs` file: + +```js {1-4} showLineNumbers +export default { + content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], +} +``` + +### That's it + +You can now start adding components to your project. + +```bash +npx shadcn-ui@latest add button +``` + +The command above will add the `Button` component to your project. You can then import it like this: + +```astro {2,10} showLineNumbers +--- +import { Button } from "@/components/ui/button" +--- + + + + Astro + + + + + +``` + + diff --git a/apps/www/content/docs/installation/gatsby.mdx b/apps/www/content/docs/installation/gatsby.mdx new file mode 100644 index 0000000..184e0f9 --- /dev/null +++ b/apps/www/content/docs/installation/gatsby.mdx @@ -0,0 +1,120 @@ +--- +title: Gatsby +description: Install and configure Gatsby. +--- + + + +### Create project + +Start by creating a new Gatsby project using `create-gatsby`: + +```bash +npm init gatsby +``` + +### Configure your Gatsby project to use TypeScript and Tailwind CSS + +You will be asked a few questions to configure your project: + +```txt showLineNumbers +✔ What would you like to call your site? +· your-app-name +✔ What would you like to name the folder where your site will be created? +· your-app-name +✔ Will you be using JavaScript or TypeScript? +· TypeScript +✔ Will you be using a CMS? +· Choose whatever you want +✔ Would you like to install a styling system? +· Tailwind CSS +✔ Would you like to install additional features with other plugins? +· Choose whatever you want +✔ Shall we do this? (Y/n) · Yes +``` + +### Edit tsconfig.json file + +Add the following code to the `tsconfig.json` file to resolve paths: + +```ts {4-9} showLineNumbers +{ + "compilerOptions": { + // ... + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + } + // ... + } +} +``` + +### Create gatsby-node.ts file + +Create a `gatsby-node.ts` file at the root of your project if it doesn’t already exist, and add the code below to the `gatsby-node` file so your app can resolve paths: + +```ts +import * as path from "path" + +export const onCreateWebpackConfig = ({ actions }) => { + actions.setWebpackConfig({ + resolve: { + alias: { + "@/components": path.resolve(__dirname, "src/components"), + "@/lib/utils": path.resolve(__dirname, "src/lib/utils"), + }, + }, + }) +} +``` + +### Run the CLI + +Run the `shadcn-ui` init command to setup your project: + +```bash +npx shadcn-ui@latest init +``` + +### Configure components.json + +You will be asked a few questions to configure `components.json`: + +```txt showLineNumbers +Would you like to use TypeScript (recommended)? no / yes +Which style would you like to use? › Default +Which color would you like to use as base color? › Slate +Where is your global CSS file? › › ./src/styles/globals.css +Do you want to use CSS variables for colors? › no / yes +Where is your tailwind.config.js located? › tailwind.config.js +Configure the import alias for components: › @/components +Configure the import alias for utils: › @/lib/utils +Are you using React Server Components? › no +``` + +### That's it + +You can now start adding components to your project. + +```bash +npx shadcn-ui@latest add button +``` + +The command above will add the `Button` component to your project. You can then import it like this: + +```tsx {1,6} showLineNumbers +import { Button } from "@/components/ui/button" + +export default function Home() { + return ( +
      + +
      + ) +} +``` + +
      diff --git a/apps/www/content/docs/installation/index.mdx b/apps/www/content/docs/installation/index.mdx new file mode 100644 index 0000000..6a5653b --- /dev/null +++ b/apps/www/content/docs/installation/index.mdx @@ -0,0 +1,140 @@ +--- +title: Installation +description: How to install dependencies and structure your app. +--- + +## Frameworks + +
      + + + Next.js + + +

      Next.js

      +
      + + + Vite + + +

      Vite

      +
      + + + Remix + + +

      Remix

      +
      + + + Gatsby + + +

      Gatsby

      +
      + + + Astro + + +

      Astro

      +
      + + + + +

      Laravel

      +
      + + + React + + +

      Manual

      +
      +
      + +## TypeScript + +This project and the components are written in TypeScript. We recommend using TypeScript for your project as well. + +However we provide a JavaScript version of the components as well. The JavaScript version is available via the [cli](/docs/cli). + +To opt-out of TypeScript, you can use the `tsx` flag in your `components.json` file. + +```json {10} title="components.json" +{ + "style": "default", + "tailwind": { + "config": "tailwind.config.js", + "css": "src/app/globals.css", + "baseColor": "zinc", + "cssVariables": true + }, + "rsc": false, + "tsx": false, + "aliases": { + "utils": "~/lib/utils", + "components": "~/components" + } +} +``` + +To configure import aliases, you can use the following `jsconfig.json`: + +```json {4} title="jsconfig.json" +{ + "compilerOptions": { + "paths": { + "@/*": ["./*"] + } + } +} +``` diff --git a/apps/www/content/docs/installation/laravel.mdx b/apps/www/content/docs/installation/laravel.mdx new file mode 100644 index 0000000..e2ef0bf --- /dev/null +++ b/apps/www/content/docs/installation/laravel.mdx @@ -0,0 +1,153 @@ +--- +title: Laravel +description: Install and configure Laravel with Inertia +--- + + + +### Create project + +Start by creating a new Laravel project with Inertia and React using the laravel installer `laravel new my-app`: + +```bash +laravel new my-app --typescript --breeze --stack=react --git --no-interaction +``` + +### Run the CLI + +Run the `shadcn-ui` init command to setup your project: + +```bash +npx shadcn-ui@latest init +``` + +### Configure components.json + +You will be asked a few questions to configure `components.json`: + +```txt showLineNumbers +Would you like to use TypeScript (recommended)? no / yes +Which style would you like to use? › Default +Which color would you like to use as base color? › Slate +Where is your global CSS file? › resources/css/app.css +Do you want to use CSS variables for colors? › no / yes +Where is your tailwind.config.js located? › tailwind.config.js +Configure the import alias for components: › @/Components +Configure the import alias for utils: › @/lib/utils +Are you using React Server Components? › no / yes +``` + +### Update tailwind.config.js + +The `shadcn-ui` CLI will automatically overwrite your `tailwind.config.js`. Update it to look like this: + +```js +import forms from "@tailwindcss/forms" +import defaultTheme from "tailwindcss/defaultTheme" + +/** @type {import('tailwindcss').Config} */ +export default { + darkMode: "class", + content: [ + "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php", + "./storage/framework/views/*.php", + "./resources/views/**/*.blade.php", + "./resources/js/**/*.tsx", + ], + + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: ["Figtree", ...defaultTheme.fontFamily.sans], + }, + keyframes: { + "accordion-down": { + from: { height: 0 }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: 0 }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + + plugins: [forms, require("tailwindcss-animate")], +} +``` + +### That's it + +You can now start adding components to your project. + +```bash +npx shadcn-ui@latest add button +``` + +The command above will add the `Button` component to your project. You can then import it like this: + +```tsx {1,6} showLineNumbers +import { Button } from "@/Components/ui/button" + +export default function Home() { + return ( +
      + +
      + ) +} +``` + +
      diff --git a/apps/www/content/docs/installation/manual.mdx b/apps/www/content/docs/installation/manual.mdx new file mode 100644 index 0000000..4625b2c --- /dev/null +++ b/apps/www/content/docs/installation/manual.mdx @@ -0,0 +1,243 @@ +--- +title: Manual Installation +description: Add dependencies to your project manually. +--- + + + +### Add Tailwind CSS + +Components are styled using Tailwind CSS. You need to install Tailwind CSS in your project. + +[Follow the Tailwind CSS installation instructions to get started.](https://tailwindcss.com/docs/installation) + +### Add dependencies + +Add the following dependencies to your project: + +```bash +npm install tailwindcss-animate class-variance-authority clsx tailwind-merge +``` + +### Add icon library + +If you're using the `default` style, install `lucide-react`: + +```bash +npm install lucide-react +``` + +If you're using the `new-york` style, install `@radix-ui/react-icons`: + +```bash +npm install @radix-ui/react-icons +``` + +### Configure path aliases + +I use the `@` alias. This is how I configure it in tsconfig.json: + +```json {3-6} title="tsconfig.json" +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + } +} +``` + +The `@` alias is a preference. You can use other aliases if you want. + +**If you use a different alias such as ~, you'll need to update import statements when adding components.** + +### Configure tailwind.config.js + +Here's what my `tailwind.config.js` file looks like: + +```js title="tailwind.config.js" +const { fontFamily } = require("tailwindcss/defaultTheme") + +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: ["var(--font-sans)", ...fontFamily.sans], + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} +``` + +### Configure styles + +Add the following to your styles/globals.css file. You can learn more about using CSS variables for theming in the [theming section](/docs/theming). + +```css title="globals.css" +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 47.4% 11.2%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + + --card: 0 0% 100%; + --card-foreground: 222.2 47.4% 11.2%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 100% 50%; + --destructive-foreground: 210 40% 98%; + + --ring: 215 20.2% 65.1%; + + --radius: 0.5rem; + } + + .dark { + --background: 224 71% 4%; + --foreground: 213 31% 91%; + + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + + --accent: 216 34% 17%; + --accent-foreground: 210 40% 98%; + + --popover: 224 71% 4%; + --popover-foreground: 215 20.2% 65.1%; + + --border: 216 34% 17%; + --input: 216 34% 17%; + + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + + --secondary: 222.2 47.4% 11.2%; + --secondary-foreground: 210 40% 98%; + + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + + --ring: 216 34% 17%; + + --radius: 0.5rem; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + } +} +``` + +### Add a cn helper + +I use a `cn` helper to make it easier to conditionally add Tailwind CSS classes. Here's how I define it in `lib/utils.ts`: + +```ts title="lib/utils.ts" +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +### That's it + +You can now start adding components to your project. + + diff --git a/apps/www/content/docs/installation/next.mdx b/apps/www/content/docs/installation/next.mdx new file mode 100644 index 0000000..0e4375b --- /dev/null +++ b/apps/www/content/docs/installation/next.mdx @@ -0,0 +1,151 @@ +--- +title: Next.js +description: Install and configure Next.js. +--- + + + +### Create project + +Start by creating a new Next.js project using `create-next-app`: + +```bash +npx create-next-app@latest my-app --typescript --tailwind --eslint +``` + +### Run the CLI + +Run the `shadcn-ui` init command to setup your project: + +```bash +npx shadcn-ui@latest init +``` + +### Configure components.json + +You will be asked a few questions to configure `components.json`: + +```txt showLineNumbers +Would you like to use TypeScript (recommended)? no / yes +Which style would you like to use? › Default +Which color would you like to use as base color? › Slate +Where is your global CSS file? › › app/globals.css +Do you want to use CSS variables for colors? › no / yes +Where is your tailwind.config.js located? › tailwind.config.js +Configure the import alias for components: › @/components +Configure the import alias for utils: › @/lib/utils +Are you using React Server Components? › no / yes +``` + +### Fonts + +I use [Inter](https://rsms.me/inter/) as the default font. Inter is not required. You can replace it with any other font. + +Here's how I configure Inter for Next.js: + +**1. Import the font in the root layout:** + +```js showLineNumbers title=app/layout.tsx {2,5-8,16-17} +import "@/styles/globals.css" +import { Inter as FontSans } from "next/font/google" + +import { cn } from "../@/lib/utils" + +export const fontSans = FontSans({ + subsets: ["latin"], + variable: "--font-sans", +}) + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + + + + ... + + + ) +} +``` + +**2. Configure `theme.extend.fontFamily` in `tailwind.config.js`** + +```js showLineNumbers title=tailwind.config.js {9-11} +const { fontFamily } = require("tailwindcss/defaultTheme") + +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"], + theme: { + extend: { + fontFamily: { + sans: ["var(--font-sans)", ...fontFamily.sans], + }, + }, + }, +} +``` + +### App structure + +Here's how I structure my Next.js apps. You can use this as a reference: + +```txt {6-10,14-15} +. +├── app +│ ├── layout.tsx +│ └── page.tsx +├── components +│ ├── ui +│ │ ├── alert-dialog.tsx +│ │ ├── button.tsx +│ │ ├── dropdown-menu.tsx +│ │ └── ... +│ ├── main-nav.tsx +│ ├── page-header.tsx +│ └── ... +├── lib +│ └── utils.ts +├── styles +│ └── globals.css +├── next.config.js +├── package.json +├── postcss.config.js +├── tailwind.config.js +└── tsconfig.json +``` + +- I place the UI components in the `components/ui` folder. +- The rest of the components such as `` and `` are placed in the `components` folder. +- The `lib` folder contains all the utility functions. I have a `utils.ts` where I define the `cn` helper. +- The `styles` folder contains the global CSS. + +### That's it + +You can now start adding components to your project. + +```bash +npx shadcn-ui@latest add button +``` + +The command above will add the `Button` component to your project. You can then import it like this: + +```tsx {1,6} showLineNumbers +import { Button } from "@/components/ui/button" + +export default function Home() { + return ( +
      + +
      + ) +} +``` + +
      diff --git a/apps/www/content/docs/installation/remix.mdx b/apps/www/content/docs/installation/remix.mdx new file mode 100644 index 0000000..a2e8965 --- /dev/null +++ b/apps/www/content/docs/installation/remix.mdx @@ -0,0 +1,117 @@ +--- +title: Remix +description: Install and configure Remix. +--- + + + +### Create project + +Start by creating a new Remix project using `create-remix`: + +```bash +npx create-remix@latest my-app +``` + +### Run the CLI + +Run the `shadcn-ui` init command to setup your project: + +```bash +npx shadcn-ui@latest init +``` + +### Configure components.json + +You will be asked a few questions to configure `components.json`: + +```txt showLineNumbers +Would you like to use TypeScript (recommended)? no / yes +Which style would you like to use? › Default +Which color would you like to use as base color? › Slate +Where is your global CSS file? › app/tailwind.css +Do you want to use CSS variables for colors? › no / yes +Where is your tailwind.config.js located? › tailwind.config.js +Configure the import alias for components: › ~/components +Configure the import alias for utils: › ~/lib/utils +Are you using React Server Components? › no +``` + +### App structure + + + +**Note**: This app structure is only a suggestion. You can place the files wherever you want. + + + +- Place the UI components in the `app/components/ui` folder. +- Your own components can be placed in the `app/components` folder. +- The `app/lib` folder contains all the utility functions. We have a `utils.ts` where we define the `cn` helper. +- The `app/tailwind.css` file contains the global CSS. + +### Install Tailwind CSS + +```bash +npm add -D tailwindcss@latest autoprefixer@latest +``` + +Then we create a `postcss.config.js` file: + +```js +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} +``` + +And finally we add the following to our `remix.config.js` file: + +```js {4-5} +/** @type {import('@remix-run/dev').AppConfig} */ +export default { + ... + tailwind: true, + postcss: true, + ... +}; +``` + +### Add `tailwind.css` to your app + +In your `app/root.tsx` file, import the `tailwind.css` file: + +```js {1, 4} +import styles from "./tailwind.css" + +export const links: LinksFunction = () => [ + { rel: "stylesheet", href: styles }, + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), +] +``` + +### That's it + +You can now start adding components to your project. + +```bash +npx shadcn-ui@latest add button +``` + +The command above will add the `Button` component to your project. You can then import it like this: + +```tsx {1,6} showLineNumbers +import { Button } from "~/components/ui/button" + +export default function Home() { + return ( +
      + +
      + ) +} +``` + +
      diff --git a/apps/www/content/docs/installation/vite.mdx b/apps/www/content/docs/installation/vite.mdx new file mode 100644 index 0000000..eddceef --- /dev/null +++ b/apps/www/content/docs/installation/vite.mdx @@ -0,0 +1,115 @@ +--- +title: Vite +description: Install and configure Vite. +--- + + + +### Create project + +Start by creating a new React project using `vite`: + +```bash +npm create vite@latest +``` + +### Add Tailwind and its configuration + +Install `tailwindcss` and its peer dependencies, then generate your `tailwind.config.js` and `postcss.config.js` files: + +```bash +npm install -D tailwindcss postcss autoprefixer + +npx tailwindcss init -p +``` + +### Edit tsconfig.json file + +Add the following code to the `tsconfig.json` file to resolve paths: + +```ts {4-9} showLineNumbers +{ + "compilerOptions": { + // ... + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + } + // ... + } +} +``` + +### Update vite.config.ts + +Add the following code to the vite.config.ts so your app can resolve paths without error + +```bash +# (so you can import "path" without error) +npm i -D @types/node +``` + +```typescript +import path from "path" +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +}) +``` + +### Run the CLI + +Run the `shadcn-ui` init command to setup your project: + +```bash +npx shadcn-ui@latest init +``` + +### Configure components.json + +You will be asked a few questions to configure `components.json`: + +```txt showLineNumbers +Would you like to use TypeScript (recommended)? no / yes +Which style would you like to use? › Default +Which color would you like to use as base color? › Slate +Where is your global CSS file? › › src/index.css +Do you want to use CSS variables for colors? › no / yes +Where is your tailwind.config.js located? › tailwind.config.js +Configure the import alias for components: › @/components +Configure the import alias for utils: › @/lib/utils +Are you using React Server Components? › no / yes (no) +``` + +### That's it + +You can now start adding components to your project. + +```bash +npx shadcn-ui@latest add button +``` + +The command above will add the `Button` component to your project. You can then import it like this: + +```tsx {1,6} showLineNumbers +import { Button } from "@/components/ui/button" + +export default function Home() { + return ( +
      + +
      + ) +} +``` + +
      diff --git a/apps/www/content/docs/theming.mdx b/apps/www/content/docs/theming.mdx new file mode 100644 index 0000000..bb168de --- /dev/null +++ b/apps/www/content/docs/theming.mdx @@ -0,0 +1,190 @@ +--- +title: Theming +description: Using CSS Variables or Tailwind CSS for theming. +--- + +You can choose between using CSS variables or Tailwind CSS utility classes for theming. + +## Utility classes + +```tsx /bg-zinc-950/ /text-zinc-50/ /dark:bg-white/ /dark:text-zinc-950/ +
      +``` + +To use utility classes for theming set `tailwind.cssVariables` to `false` in your `components.json` file. + +```json {8} title="components.json" +{ + "style": "default", + "rsc": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": false + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} +``` + +## CSS Variables + +```tsx /bg-background/ /text-foreground/ +
      +``` + +To use CSS variables for theming set `tailwind.cssVariables` to `true` in your `components.json` file. + +```json {8} title="components.json" +{ + "style": "default", + "rsc": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} +``` + +### Convention + +We use a simple `background` and `foreground` convention for colors. The `background` variable is used for the background color of the component and the `foreground` variable is used for the text color. + + + +The `background` suffix is omitted when the variable is used for the background color of the component. + + + +Given the following CSS variables: + +```css +--primary: 222.2 47.4% 11.2%; +--primary-foreground: 210 40% 98%; +``` + +The `background` color of the following component will be `hsl(var(--primary))` and the `foreground` color will be `hsl(var(--primary-foreground))`. + +```tsx +
      Hello
      +``` + + + +**CSS variables must be defined without color space function**. See the [Tailwind CSS documentation](https://tailwindcss.com/docs/customizing-colors#using-css-variables) for more information. + + + +### List of variables + +Here's the list of variables available for customization: + + + +```css title="Default background color of ...etc" +--background: 0 0% 100%; +--foreground: 222.2 47.4% 11.2%; +``` + +```css title="Muted backgrounds such as , and " +--muted: 210 40% 96.1%; +--muted-foreground: 215.4 16.3% 46.9%; +``` + +```css title="Background color for " +--card: 0 0% 100%; +--card-foreground: 222.2 47.4% 11.2%; +``` + +```css title="Background color for popovers such as , , " +--popover: 0 0% 100%; +--popover-foreground: 222.2 47.4% 11.2%; +``` + +```css title="Default border color" +--border: 214.3 31.8% 91.4%; +``` + +```css title="Border color for inputs such as ,