diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 92882cec376..f506d37595e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,6 @@ /packages/shared-data/pricing.ts @roryw10 @kevcodez /packages/shared-data/plans.ts @roryw10 @kevcodez +/packages/common/telemetry-constants.ts @4L3k51 @supabase/growth-eng /apps/studio/ @supabase/Dashboard @@ -17,5 +18,3 @@ /apps/studio/components/interfaces/Organization/BillingSettings/ @supabase/security /apps/studio/components/interfaces/Organization/Documents/ @supabase/security /apps/studio/pages/new/index.tsx @supabase/security - -/apps/studio/lib/constants/telemetry.ts @4L3k51 @loong @pamelachia \ No newline at end of file diff --git a/.github/workflows/ai-tests.yml b/.github/workflows/ai-tests.yml index 90f94476155..64314d44fe7 100644 --- a/.github/workflows/ai-tests.yml +++ b/.github/workflows/ai-tests.yml @@ -36,7 +36,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/autofix_linters.yml b/.github/workflows/autofix_linters.yml index 650d5776a82..1664da4be70 100644 --- a/.github/workflows/autofix_linters.yml +++ b/.github/workflows/autofix_linters.yml @@ -5,9 +5,6 @@ on: types: - labeled -permissions: - contents: write - # Cancel old builds on new commit for same workflow + branch/PR concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -18,20 +15,25 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' && (github.event.label.name == 'autofix') }} steps: + - name: Calculate number of commits + run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}" + - uses: actions/checkout@v4 with: - sparse-checkout: apps ref: ${{ github.head_ref }} + token: ${{ secrets.PAT_AUTOFIX }} + fetch-depth: ${{ env.PR_FETCH_DEPTH }} + sparse-checkout: | + packages + apps - uses: pnpm/action-setup@v4 name: Install pnpm - with: - version: 9.10.0 - run_install: false - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' + cache: pnpm - name: Install required dependencies run: pnpm i @@ -40,15 +42,8 @@ jobs: run: pnpm run format - name: Commit changes and push to existing branch - run: | - git config --global user.name 'github-tidy-bot' - git config --global user.email 'github-tidy-bot@supabase.com' - if [[ `git status --porcelain` ]]; then - echo "[bot] Changes detected, committing." - - echo "[bot] Running in non-squash mode." - git commit -am "ci: Autofix updates from GitHub workflow" - git push - else - echo "[bot] No changes detected, nothing to commit." - fi + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "ci: Autofix updates from GitHub workflow" + commit_user_name: "kevcodez" + commit_user_email: "k.grueneberg1994@gmail.com" diff --git a/.github/workflows/docs-last-changed.yml b/.github/workflows/docs-last-changed.yml index d8d88c53af5..94967a403f2 100644 --- a/.github/workflows/docs-last-changed.yml +++ b/.github/workflows/docs-last-changed.yml @@ -29,7 +29,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Setup node diff --git a/.github/workflows/docs-lint-v2-scheduled.yml b/.github/workflows/docs-lint-v2-scheduled.yml new file mode 100644 index 00000000000..bdb8209632e --- /dev/null +++ b/.github/workflows/docs-lint-v2-scheduled.yml @@ -0,0 +1,64 @@ +name: '[Docs] Lint v2 (scheduled)' +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + +permissions: + contents: write + pull-requests: write + +jobs: + lint-all: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + sparse-checkout: | + supa-mdx-lint.config.toml + supa-mdx-lint + apps/docs/content + - name: cache cargo + id: cache-cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: e5609278486869b545fda23b5732727b6e70a1c1 + - name: install linter + if: steps.cache-cargo.outputs.cache-hit != 'true' + run: cargo install --locked --git https://github.com/supabase-community/supa-mdx-lint --rev e5609278486869b545fda23b5732727b6e70a1c1 + - name: run linter + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + supa-mdx-lint apps/docs/content || { + echo "Linter failed, attempting to fix errors..." + git config --global user.name 'github-docs-bot' + git config --global user.email 'github-docs-bot@supabase.com' + BRANCH_NAME="bot/docs-lint-fixes" + EXISTING_BRANCH=$(git ls-remote --heads origin $BRANCH_NAME) + if [ -n "$EXISTING_BRANCH" ]; then + git checkout $BRANCH_NAME + else + git checkout -b $BRANCH_NAME + fi + supa-mdx-lint apps/docs/content --fix || FIX_FAILED=1 + git add . + git commit -m '[bot] fix lint errors' || true + git push origin $BRANCH_NAME + if [ -z "$EXISTING_BRANCH" ]; then + gh pr create --title '[bot] fix lint errors' --body 'This PR fixes lint errors in the documentation.' --head $BRANCH_NAME + fi + if [ "${FIX_FAILED:-0}" -eq 1 ]; then + echo "Fix did not correct all errors." + exit 1 + fi + } diff --git a/.github/workflows/docs-lint-v2.yml b/.github/workflows/docs-lint-v2.yml index 5ec6e0d3127..a8abcd74eef 100644 --- a/.github/workflows/docs-lint-v2.yml +++ b/.github/workflows/docs-lint-v2.yml @@ -1,20 +1,11 @@ name: '[Docs] Lint v2' on: pull_request: - paths: - - '.github/workflows/docs-lint-v2.yml' - - 'supa-mdx-lint.config.toml' - - 'apps/docs/content/**' - push: - branches: - - master - workflow_dispatch: env: CARGO_NET_GIT_FETCH_WITH_CLI: true permissions: - contents: write pull-requests: write jobs: @@ -22,15 +13,24 @@ jobs: name: supa-mdx-lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 sparse-checkout: | supa-mdx-lint.config.toml supa-mdx-lint apps/docs/content + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 #v3.0.2 + id: filter + with: + filters: | + docs: + - 'apps/docs/content/**' + - 'supa-mdx-lint/**' + - 'supa-mdx-lint.config.toml' - name: cache cargo id: cache-cargo + if: steps.filter.outputs.docs == 'true' uses: actions/cache@v4 with: path: | @@ -38,50 +38,44 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - key: 6435a4cd1eeea7c2bbd343731de7e8a5127cb2d1 + key: e5609278486869b545fda23b5732727b6e70a1c1 - name: install linter - if: steps.cache-cargo.outputs.cache-hit != 'true' - run: cargo install --locked --git https://github.com/supabase-community/supa-mdx-lint --rev 6435a4cd1eeea7c2bbd343731de7e8a5127cb2d1 + if: steps.filter.outputs.docs == 'true' && steps.cache-cargo.outputs.cache-hit != 'true' + run: cargo install --locked --git https://github.com/supabase-community/supa-mdx-lint --rev e5609278486869b545fda23b5732727b6e70a1c1 - name: install reviewdog + if: steps.filter.outputs.docs == 'true' uses: reviewdog/action-setup@3f401fe1d58fe77e10d665ab713057375e39b887 # v1.3.0 with: reviewdog_version: v0.20.2 - - name: run linter on PR - if: github.event_name == 'pull_request' + - name: run linter (internal) + if: steps.filter.outputs.docs == 'true' && github.event.pull_request.head.repo.full_name == github.repository env: BASE_REF: ${{ github.base_ref }} REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -o pipefail git diff --name-only origin/$BASE_REF HEAD \ - | { grep -E "^apps/docs/content/guides/" || test $? = 1; } \ + | { grep -E "^apps/docs/content/" || test $? = 1; } \ | xargs -r supa-mdx-lint --format rdf \ - | reviewdog -f=rdjsonl -reporter=github-pr-review - - name: run linter on push or workflow dispatch - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + | reviewdog -f=rdjsonl -reporter=github-pr-review -tee + - name: run linter (external) + if: steps.filter.outputs.docs == 'true' && github.event.pull_request.head.repo.full_name != github.repository env: + BASE_REF: ${{ github.base_ref }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ github.head_ref }} run: | - supa-mdx-lint apps/docs/content || { - echo "Linter failed, attempting to fix errors..." - git config --global user.name 'github-docs-bot' - git config --global user.email 'github-docs-bot@supabase.com' - BRANCH_NAME="bot/docs-lint-fixes" - EXISTING_BRANCH=$(git ls-remote --heads origin $BRANCH_NAME) - if [ -n "$EXISTING_BRANCH" ]; then - git checkout $BRANCH_NAME - else - git checkout -b $BRANCH_NAME - fi - supa-mdx-lint apps/docs/content --fix || FIX_FAILED=1 - git add . - git commit -m '[bot] fix lint errors' || true - git push origin $BRANCH_NAME - if [ -z "$EXISTING_BRANCH" ]; then - gh pr create --title '[bot] fix lint errors' --body 'This PR fixes lint errors in the documentation.' --head $BRANCH_NAME - fi - if [ "${FIX_FAILED:-0}" -eq 1 ]; then - echo "Fix did not correct all errors." - exit 1 - fi + set -o pipefail + run_lints() { + git diff --name-only origin/$BASE_REF HEAD \ + | { grep -E "^apps/docs/content/" || test $? = 1; } \ + | xargs -rx -n 1000000000 supa-mdx-lint --format markdown } + set +e + LINT_RESULTS=$(run_lints) + LINT_EXIT_CODE=$? + set -e + if [[ $LINT_EXIT_CODE -ne 0 ]]; then + gh pr comment $BRANCH_NAME --body "$LINT_RESULTS" + exit 1 + fi diff --git a/.github/workflows/docs-mgmt-api-update.yml b/.github/workflows/docs-mgmt-api-update.yml index 47508dc49c2..f395be7acad 100644 --- a/.github/workflows/docs-mgmt-api-update.yml +++ b/.github/workflows/docs-mgmt-api-update.yml @@ -20,7 +20,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js diff --git a/.github/workflows/docs-tests-smoke.yml b/.github/workflows/docs-tests-smoke.yml index 930406127c1..c5a38916a46 100644 --- a/.github/workflows/docs-tests-smoke.yml +++ b/.github/workflows/docs-tests-smoke.yml @@ -25,7 +25,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js diff --git a/.github/workflows/docs-tests.yml b/.github/workflows/docs-tests.yml index e1de7081e50..f9a36f3cf20 100644 --- a/.github/workflows/docs-tests.yml +++ b/.github/workflows/docs-tests.yml @@ -26,7 +26,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml deleted file mode 100644 index a652acb37c3..00000000000 --- a/.github/workflows/lint-prose.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: reviewdog -on: - pull_request: - paths: - - '.vale.ini' - - 'vale/**' - - 'apps/docs/content/**' - -jobs: - vale: - name: runner / vale - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - sparse-checkout: | - .vale.ini - vale - apps/docs/content - - name: Get changed files - id: changed-files - env: - BASE_REF: ${{ github.base_ref }} - SHA: ${{ github.sha }} - run: | - git fetch origin $BASE_REF - echo "CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/$BASE_REF $SHA | grep '^apps/docs/content/' | tr '\n' ',' | sed 's/,$//')" >> $GITHUB_OUTPUT - - uses: errata-ai/vale-action@reviewdog - with: - files: '${{ steps.changed-files.outputs.CHANGED_FILES }}' - separator: ',' diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml index afec7f34149..0e5f03d3fd7 100644 --- a/.github/workflows/og_images.yml +++ b/.github/workflows/og_images.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup the Supabase CLI uses: supabase/setup-cli@v1 diff --git a/.github/workflows/pg-meta-tests.yml b/.github/workflows/pg-meta-tests.yml new file mode 100644 index 00000000000..2392fc049c7 --- /dev/null +++ b/.github/workflows/pg-meta-tests.yml @@ -0,0 +1,50 @@ +name: PG Meta Tests + +on: + push: + branches: ['master'] + paths: + - 'packages/pg-meta/**/*' + pull_request: + branches: ['master'] + paths: + - 'packages/pg-meta/**/*' + +# Cancel old builds on new commit for same workflow + branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + packages/pg-meta + packages/tsconfig + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install deps + run: pnpm i + + - name: Run tests + run: pnpm --filter=@supabase/pg-meta run test + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + directory: packages/pg-meta/coverage + flags: pg-meta diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 0886ee2337a..9381cd0e98e 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -21,7 +21,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Setup node uses: actions/setup-node@v4 @@ -47,7 +46,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Setup node uses: actions/setup-node@v4 @@ -73,7 +71,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Setup node uses: actions/setup-node@v4 diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml index 70148e80480..60385047ea4 100644 --- a/.github/workflows/search.yml +++ b/.github/workflows/search.yml @@ -41,7 +41,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Setup node diff --git a/.github/workflows/local-studio-tests.yml b/.github/workflows/studio-e2e-tests.yml similarity index 76% rename from .github/workflows/local-studio-tests.yml rename to .github/workflows/studio-e2e-tests.yml index 9c64715b1e0..8ae25f750fe 100644 --- a/.github/workflows/local-studio-tests.yml +++ b/.github/workflows/studio-e2e-tests.yml @@ -1,16 +1,18 @@ -name: Local Studio Tests +name: Studio E2E Tests on: push: branches: [master] paths: + - 'packages/pg-meta/**/*' - 'apps/studio/**' - - 'tests/local-studio-tests/**' + - 'tests/studio-tests/**' - 'pnpm-lock.yaml' pull_request: branches: [master] paths: + - 'packages/pg-meta/**/*' - 'apps/studio/**' - - 'tests/local-studio-tests/**' + - 'tests/studio-tests/**' - 'pnpm-lock.yaml' # Cancel old builds on new commit for same workflow + branch/PR @@ -28,7 +30,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js uses: actions/setup-node@v4 @@ -36,17 +37,19 @@ jobs: node-version-file: '.nvmrc' cache: 'pnpm' - uses: supabase/setup-cli@v1 + with: + version: latest - name: Install dependencies run: pnpm i - name: Install Playwright Browsers - run: pnpm -C tests/local-studio-tests exec playwright install --with-deps + run: pnpm -C tests/studio-tests exec playwright install --with-deps - name: Run Playwright tests - run: pnpm test:local-studio + run: pnpm test:e2e:studio-local # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. # continue-on-error: true - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report - path: tests/local-studio-tests/playwright-report/ + path: tests/studio-tests/playwright-report/ retention-days: 7 diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-unit-tests.yml similarity index 98% rename from .github/workflows/studio-tests.yml rename to .github/workflows/studio-unit-tests.yml index 7390691e03d..0f41b9e49cf 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-unit-tests.yml @@ -34,7 +34,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 42e5e2c0cb5..76e474371a1 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -22,7 +22,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js diff --git a/.github/workflows/ui-patterns-tests.yml b/.github/workflows/ui-patterns-tests.yml index 2209729286c..1f5b93696d8 100644 --- a/.github/workflows/ui-patterns-tests.yml +++ b/.github/workflows/ui-patterns-tests.yml @@ -24,7 +24,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index ba4861cfb9b..80b75b974cd 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -24,7 +24,6 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 9.10.0 run_install: false - name: Use Node.js diff --git a/.npmrc b/.npmrc index 4fd021952d5..19c081e2d4f 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,3 @@ -engine-strict=true \ No newline at end of file +engine-strict=true +update-notifier=false +@jsr:registry=https://npm.jsr.io diff --git a/.prettierignore b/.prettierignore index 4c3f5a35591..9904b212c22 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,7 +9,7 @@ apps/**/out apps/www/schema.sql apps/www/public/images/* apps/docs/**/generated/* -apps/docs/examples +apps/docs/examples/* examples/slack-clone/nextjs-slack-clone/full-schema.sql # ignore files with custom js formatting apps/studio/public @@ -18,3 +18,8 @@ apps/docs/CONTRIBUTING.md apps/design-system/__registry__ packages/icons/__registry__ packages/icons/src/icons/*.ts +apps/ui-library/__registry__ +apps/ui-library/public/r +apps/**/.contentlayer +# invalid JSON file +packages/ui/src/components/Form/examples/PhoneProvidersSchema.json diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 26f40136c82..e1687622715 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -12,7 +12,7 @@ - [Shared components](#shared-components) - [Installing packages](#installing-packages) - [Running Docker for Supabase Studio](#running-docker-for-supabase-studio) - - [Prerequsites](#prerequsites) + - [Prerequisites](#prerequisites) - [Get Started](#get-started) - [Create a pull request](#create-a-pull-request) - [Issue assignment](#issue-assignment) @@ -36,8 +36,8 @@ To ensure a positive and inclusive environment, please read our [code of conduct You will need to install and configure the following dependencies on your machine to build [Supabase](https://supabase.com): -- [Git](http://git-scm.com/) -- [Node.js v20.x (LTS)](http://nodejs.org) +- [Git](https://git-scm.com/) +- [Node.js v20.x (LTS)](https://nodejs.org) - [pnpm](https://pnpm.io/) version 9.x.x or higher - [make](https://www.gnu.org/software/make/) or the equivalent to `build-essentials` for your OS - [Docker](https://docs.docker.com/get-docker/) (to run studio locally) @@ -129,7 +129,7 @@ For example: To run Studio locally, you'll need to setup Docker in addition to your NextJS frontend. -#### Prerequsites +#### Prerequisites First, make sure you have the Docker installed on your device. You can download and install it from [here](https://docs.docker.com/get-docker/). diff --git a/README.md b/README.md index 437878278b6..2d770087762 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and - [Postgres](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance. - [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients. -- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API -- [GoTrue](https://github.com/supabase/gotrue) is a JWT based API for managing users and issuing JWT tokens. -- [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions. -- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API +- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API. +- [GoTrue](https://github.com/supabase/gotrue) is a JWT-based authentication API that simplifies user sign-ups, logins, and session management in your applications. +- [Storage](https://github.com/supabase/storage-api) a RESTful API for managing files in S3, with Postgres handling permissions. +- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API. - [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc. - [Kong](https://github.com/Kong/kong) is a cloud-native API gateway. diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 8d55cc7b07a..d4554da3efa 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -1864,6 +1864,28 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "toc-demo": { + name: "toc-demo", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/toc-demo")), + source: "", + files: ["registry/default/example/toc-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "toc-single-demo": { + name: "toc-single-demo", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/toc-single-demo")), + source: "", + files: ["registry/default/example/toc-single-demo.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "multi-select-demo": { name: "multi-select-demo", type: "components:example", @@ -1919,6 +1941,17 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "multi-select-combobox-creatable": { + name: "multi-select-combobox-creatable", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-combobox-creatable")), + source: "", + files: ["registry/default/example/multi-select-combobox-creatable.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "multi-select-deletable-badge": { name: "multi-select-deletable-badge", type: "components:example", diff --git a/apps/design-system/app/Providers.tsx b/apps/design-system/app/Providers.tsx index be3bdf78611..87bac1f1e2f 100644 --- a/apps/design-system/app/Providers.tsx +++ b/apps/design-system/app/Providers.tsx @@ -1,17 +1,16 @@ 'use client' -import * as React from 'react' import { Provider as JotaiProvider } from 'jotai' import { ThemeProvider as NextThemesProvider } from 'next-themes' import { ThemeProviderProps } from 'next-themes/dist/types' -import { TooltipProvider_Shadcn_ } from 'ui' +import { TooltipProvider } from 'ui' export function ThemeProvider({ children, ...props }: ThemeProviderProps) { return ( - {children} + {children} ) diff --git a/apps/design-system/components/source-panel.tsx b/apps/design-system/components/source-panel.tsx index 92151a863c8..a40d922771d 100644 --- a/apps/design-system/components/source-panel.tsx +++ b/apps/design-system/components/source-panel.tsx @@ -29,9 +29,9 @@ const SourcePanel = forwardRef & y2="208" fill="none" stroke="currentColor" - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="16" + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="16" > & y2="192" fill="none" stroke="currentColor" - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="16" + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="16" > shadcn/ui diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 471a9c89158..1031738338e 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -122,6 +122,11 @@ export const docsConfig: DocsConfig = { href: '/docs/fragments/logs-bar-chart', items: [], }, + { + title: 'Table of Contents (TOC)', + href: '/docs/fragments/toc', + items: [], + }, ], }, { @@ -349,6 +354,11 @@ export const docsConfig: DocsConfig = { href: '/docs/components/slider', items: [], }, + { + title: 'Sidebar', + href: '/docs/components/sidebar', + items: [], + }, { title: 'Sonner', href: '/docs/components/sonner', diff --git a/apps/design-system/content/docs/components/sidebar.mdx b/apps/design-system/content/docs/components/sidebar.mdx new file mode 100644 index 00000000000..f8706d0f4ae --- /dev/null +++ b/apps/design-system/content/docs/components/sidebar.mdx @@ -0,0 +1,1372 @@ +--- +title: Sidebar +description: A composable, themeable and customizable sidebar component. +component: true +--- + +
+ +
+ A sidebar that collapses to icons. +
+
+ +Sidebars are one of the most complex components to build. They are central +to any application and often contain a lot of moving parts. + +I don't like building sidebars. So I built 30+ of them. All kinds of +configurations. Then I extracted the core components into `sidebar.tsx`. + +We now have a solid foundation to build on top of. Composable. Themeable. +Customizable. + +[Browse the Blocks Library](/blocks). + +## Installation + + + + + CLI + Manual + + + + + +Run the following command to install `sidebar.tsx` + +```bash +npx shadcn@latest add sidebar +``` + +Add the following colors to your CSS file + +The command above should install the colors for you. If not, copy and paste the following in your CSS file. + +We'll go over the colors later in the [theming section](/docs/components/sidebar#theming). + +```css title="app/globals.css" +@layer base { + :root { + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + + .dark { + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} +``` + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + +Add the following colors to your CSS file + +We'll go over the colors later in the [theming section](/docs/components/sidebar#theming). + +```css title="app/globals.css" +@layer base { + :root { + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + + .dark { + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} +``` + +Add the Sidebar tailwind config into `tailwind.config.js` + +Add the following object in the `theme.extend.colors` section of your `tailwind.config.js` file +this config enable sidebar related style utilities like bg-sidebar + +```javascript title="tailwind.config.js" +// ... +sidebar: { + DEFAULT: 'hsl(var(--sidebar-background))', + foreground: 'hsl(var(--sidebar-foreground))', + primary: 'hsl(var(--sidebar-primary))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + accent: 'hsl(var(--sidebar-accent))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', + border: 'hsl(var(--sidebar-border))', + ring: 'hsl(var(--sidebar-ring))', +}, +// ... +``` + + + + + + + +## Structure + +A `Sidebar` component is composed of the following parts: + +- `SidebarProvider` - Handles collapsible state. +- `Sidebar` - The sidebar container. +- `SidebarHeader` and `SidebarFooter` - Sticky at the top and bottom of the sidebar. +- `SidebarContent` - Scrollable content. +- `SidebarGroup` - Section within the `SidebarContent`. +- `SidebarTrigger` - Trigger for the `Sidebar`. + +Sidebar Structure +Sidebar Structure + +## Usage + +```tsx showLineNumbers title="app/layout.tsx" +import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' +import { AppSidebar } from '@/components/app-sidebar' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + +
+ + {children} +
+
+ ) +} +``` + +```tsx showLineNumbers title="components/app-sidebar.tsx" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarHeader, +} from '@/components/ui/sidebar' + +export function AppSidebar() { + return ( + + + + + + + + + ) +} +``` + +## Your First Sidebar + +Let's start with the most basic sidebar. A collapsible sidebar with a menu. + + + +Add a `SidebarProvider` and `SidebarTrigger` at the root of your application. + +```tsx showLineNumbers title="app/layout.tsx" +import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' +import { AppSidebar } from '@/components/app-sidebar' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + +
+ + {children} +
+
+ ) +} +``` + +Create a new sidebar component at `components/app-sidebar.tsx`. + +```tsx showLineNumbers title="components/app-sidebar.tsx" +import { Sidebar, SidebarContent } from '@/components/ui/sidebar' + +export function AppSidebar() { + return ( + + + + ) +} +``` + +Now, let's add a `SidebarMenu` to the sidebar. + +We'll use the `SidebarMenu` component in a `SidebarGroup`. + +```tsx showLineNumbers title="components/app-sidebar.tsx" +import { Calendar, Home, Inbox, Search, Settings } from 'lucide-react' + +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from '@/components/ui/sidebar' + +// Menu items. +const items = [ + { + title: 'Home', + url: '#', + icon: Home, + }, + { + title: 'Inbox', + url: '#', + icon: Inbox, + }, + { + title: 'Calendar', + url: '#', + icon: Calendar, + }, + { + title: 'Search', + url: '#', + icon: Search, + }, + { + title: 'Settings', + url: '#', + icon: Settings, + }, +] + +export function AppSidebar() { + return ( + + + + Application + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + ) +} +``` + +You've created your first sidebar. + +
+ +
Your first sidebar.
+
+ +
+ +## Components + +The components in `sidebar.tsx` are built to be composable i.e you build your sidebar by putting the provided components together. They also compose well with other shadcn/ui components such as `DropdownMenu`, `Collapsible` or `Dialog` etc. + +**If you need to change the code in `sidebar.tsx`, you are encouraged to do so. The code is yours. Use `sidebar.tsx` as a starting point and build your own.** + +In the next sections, we'll go over each component and how to use them. + +## SidebarProvider + +The `SidebarProvider` component is used to provide the sidebar context to the `Sidebar` component. You should always wrap your application in a `SidebarProvider` component. + +### Props + +| Name | Type | Description | +| -------------- | ------------------------- | -------------------------------------------- | +| `defaultOpen` | `boolean` | Default open state of the sidebar. | +| `open` | `boolean` | Open state of the sidebar (controlled). | +| `onOpenChange` | `(open: boolean) => void` | Sets open state of the sidebar (controlled). | + +### Width + +If you have a single sidebar in your application, you can use the `SIDEBAR_WIDTH` and `SIDEBAR_WIDTH_MOBILE` variables in `sidebar.tsx` to set the width of the sidebar. + +```tsx showLineNumbers title="components/ui/sidebar.tsx" +const SIDEBAR_WIDTH = '16rem' +const SIDEBAR_WIDTH_MOBILE = '18rem' +``` + +For multiple sidebars in your application, you can use the `style` prop to set the width of the sidebar. + +To set the width of the sidebar, you can use the `--sidebar-width` and `--sidebar-width-mobile` CSS variables in the `style` prop. + +```tsx showLineNumbers title="components/ui/sidebar.tsx" + + + +``` + +This will handle the width of the sidebar but also the layout spacing. + +### Keyboard Shortcut + +The `SIDEBAR_KEYBOARD_SHORTCUT` variable is used to set the keyboard shortcut used to open and close the sidebar. + +To trigger the sidebar, you use the `cmd+b` keyboard shortcut on Mac and `ctrl+b` on Windows. + +You can change the keyboard shortcut by updating the `SIDEBAR_KEYBOARD_SHORTCUT` variable. + +```tsx showLineNumbers title="components/ui/sidebar.tsx" +const SIDEBAR_KEYBOARD_SHORTCUT = 'b' +``` + +### Persisted State + +The `SidebarProvider` supports persisting the sidebar state across page reloads and server-side rendering. It uses cookies to store the current state of the sidebar. When the sidebar state changes, a default cookie named `sidebar_state` is set with the current open/closed state. This cookie is then read on subsequent page loads to restore the sidebar state. + +To persist sidebar state in Next.js, set up your `SidebarProvider` in `app/layout.tsx` like this: + +```tsx showLineNumbers title="app/layout.tsx" +import { cookies } from 'next/headers' + +import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' +import { AppSidebar } from '@/components/app-sidebar' + +export async function Layout({ children }: { children: React.ReactNode }) { + const cookieStore = await cookies() + const defaultOpen = cookieStore.get('sidebar_state')?.value === 'true' + + return ( + + +
+ + {children} +
+
+ ) +} +``` + +You can change the name of the cookie by updating the `SIDEBAR_COOKIE_NAME` variable in `sidebar.tsx`. + +```tsx showLineNumbers title="components/ui/sidebar.tsx" +const SIDEBAR_COOKIE_NAME = 'sidebar_state' +``` + +## Sidebar + +The main `Sidebar` component used to render a collapsible sidebar. + +```tsx showLineNumbers +import { Sidebar } from '@/components/ui/sidebar' + +export function AppSidebar() { + return +} +``` + +### Props + +| Property | Type | Description | +| ------------- | --------------------------------- | --------------------------------- | +| `side` | `left` or `right` | The side of the sidebar. | +| `variant` | `sidebar`, `floating`, or `inset` | The variant of the sidebar. | +| `collapsible` | `offcanvas`, `icon`, or `none` | Collapsible state of the sidebar. | + +### side + +Use the `side` prop to change the side of the sidebar. + +Available options are `left` and `right`. + +```tsx showLineNumbers +import { Sidebar } from '@/components/ui/sidebar' + +export function AppSidebar() { + return +} +``` + +### variant + +Use the `variant` prop to change the variant of the sidebar. + +Available options are `sidebar`, `floating` and `inset`. + +```tsx showLineNumbers +import { Sidebar } from '@/components/ui/sidebar' + +export function AppSidebar() { + return +} +``` + + + **Note:** If you use the `inset` variant, remember to wrap your main content in a `SidebarInset` + component. + + +```tsx showLineNumbers + + + +
{children}
+
+
+``` + +### collapsible + +Use the `collapsible` prop to make the sidebar collapsible. + +Available options are `offcanvas`, `icon` and `none`. + +```tsx showLineNumbers +import { Sidebar } from '@/components/ui/sidebar' + +export function AppSidebar() { + return +} +``` + +| Prop | Description | +| ----------- | ------------------------------------------------------------ | +| `offcanvas` | A collapsible sidebar that slides in from the left or right. | +| `icon` | A sidebar that collapses to icons. | +| `none` | A non-collapsible sidebar. | + +## useSidebar + +The `useSidebar` hook is used to control the sidebar. + +```tsx showLineNumbers +import { useSidebar } from '@/components/ui/sidebar' + +export function AppSidebar() { + const { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } = useSidebar() +} +``` + +| Property | Type | Description | +| --------------- | ------------------------- | --------------------------------------------- | +| `state` | `expanded` or `collapsed` | The current state of the sidebar. | +| `open` | `boolean` | Whether the sidebar is open. | +| `setOpen` | `(open: boolean) => void` | Sets the open state of the sidebar. | +| `openMobile` | `boolean` | Whether the sidebar is open on mobile. | +| `setOpenMobile` | `(open: boolean) => void` | Sets the open state of the sidebar on mobile. | +| `isMobile` | `boolean` | Whether the sidebar is on mobile. | +| `toggleSidebar` | `() => void` | Toggles the sidebar. Desktop and mobile. | + +## SidebarHeader + +Use the `SidebarHeader` component to add a sticky header to the sidebar. + +The following example adds a `` to the `SidebarHeader`. + +
+ +
+ A sidebar header with a dropdown menu. +
+
+ +```tsx showLineNumbers title="components/app-sidebar.tsx" + + + + + + + + Select Workspace + + + + + + Acme Inc + + + Acme Corp. + + + + + + + +``` + +## SidebarFooter + +Use the `SidebarFooter` component to add a sticky footer to the sidebar. + +The following example adds a `` to the `SidebarFooter`. + +
+ +
+ A sidebar footer with a dropdown menu. +
+
+ +```tsx showLineNumbers title="components/app-sidebar.tsx" +export function AppSidebar() { + return ( + + + + + + + + + + + Username + + + + + + Account + + + Billing + + + Sign out + + + + + + + + + ) +} +``` + +## SidebarContent + +The `SidebarContent` component is used to wrap the content of the sidebar. This is where you add your `SidebarGroup` components. It is scrollable. + +```tsx showLineNumbers +import { Sidebar, SidebarContent } from '@/components/ui/sidebar' + +export function AppSidebar() { + return ( + + + + + + + ) +} +``` + +## SidebarGroup + +Use the `SidebarGroup` component to create a section within the sidebar. + +A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an optional `SidebarGroupAction`. + +
+ +
A sidebar group.
+
+ +```tsx showLineNumbers +import { Sidebar, SidebarContent, SidebarGroup } from '@/components/ui/sidebar' + +export function AppSidebar() { + return ( + + + + Application + + Add Project + + + + + + ) +} +``` + +## Collapsible SidebarGroup + +To make a `SidebarGroup` collapsible, wrap it in a `Collapsible`. + +
+ +
+ A collapsible sidebar group. +
+
+ +```tsx showLineNumbers +export function AppSidebar() { + return ( + + + + + Help + + + + + + + + + ) +} +``` + + + **Note:** We wrap the `CollapsibleTrigger` in a `SidebarGroupLabel` to render a button. + + +## SidebarGroupAction + +Use the `SidebarGroupAction` component to add an action button to the `SidebarGroup`. + +```tsx showLineNumbers {5-7} +export function AppSidebar() { + return ( + + Projects + + Add Project + + + + ) +} +``` + +
+ +
+ A sidebar group with an action button. +
+
+ +## SidebarMenu + +The `SidebarMenu` component is used for building a menu within a `SidebarGroup`. + +A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton`, `` and `` components. + +Sidebar Menu +Sidebar Menu + +Here's an example of a `SidebarMenu` component rendering a list of projects. + +
+ +
+ A sidebar menu with a list of projects. +
+
+ +```tsx showLineNumbers + + + + Projects + + + {projects.map((project) => ( + + + + + {project.name} + + + + ))} + + + + + +``` + +## SidebarMenuButton + +The `SidebarMenuButton` component is used to render a menu button within a `SidebarMenuItem`. + +### Link or Anchor + +By default, the `SidebarMenuButton` renders a button but you can use the `asChild` prop to render a different component such as a `Link` or an `a` tag. + +```tsx showLineNumbers + + Home + +``` + +### Icon and Label + +You can render an icon and a truncated label inside the button. Remember to wrap the label in a ``. + +```tsx showLineNumbers + + + + Home + + +``` + +### isActive + +Use the `isActive` prop to mark a menu item as active. + +```tsx showLineNumbers + + Home + +``` + +## SidebarMenuAction + +The `SidebarMenuAction` component is used to render a menu action within a `SidebarMenuItem`. + +This button works independently of the `SidebarMenuButton` i.e you can have the `` as a clickable link and the `` as a button. + +```tsx showLineNumbers + + + + + Home + + + + Add Project + + +``` + +### DropdownMenu + +Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`. + +
+ +
+ A sidebar menu action with a dropdown menu. +
+
+ +```tsx showLineNumbers + + + + + Home + + + + + + + + + + + Edit Project + + + Delete Project + + + + +``` + +## SidebarMenuSub + +The `SidebarMenuSub` component is used to render a submenu within a `SidebarMenu`. + +Use `` and `` to render a submenu item. + +
+ +
+ A sidebar menu with a submenu. +
+
+ +```tsx showLineNumbers + + + + + + + + + + + +``` + +## Collapsible SidebarMenu + +To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` components in a `Collapsible`. + +
+ +
A collapsible sidebar menu.
+
+ +```tsx showLineNumbers + + + + + + + + + + + + + + +``` + +## SidebarMenuBadge + +The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenuItem`. + +
+ +
+ A sidebar menu with a badge. +
+
+ +```tsx showLineNumbers + + + 24 + +``` + +## SidebarMenuSkeleton + +The `SidebarMenuSkeleton` component is used to render a skeleton for a `SidebarMenu`. You can use this to show a loading state when using React Server Components, SWR or react-query. + +```tsx showLineNumbers +function NavProjectsSkeleton() { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) +} +``` + +## SidebarSeparator + +The `SidebarSeparator` component is used to render a separator within a `Sidebar`. + +```tsx showLineNumbers + + + + + + + + + +``` + +## SidebarTrigger + +Use the `SidebarTrigger` component to render a button that toggles the sidebar. + +The `SidebarTrigger` component must be used within a `SidebarProvider`. + +```tsx showLineNumbers + + +
+ +
+
+``` + +### Custom Trigger + +To create a custom trigger, you can use the `useSidebar` hook. + +```tsx showLineNumbers +import { useSidebar } from '@/components/ui/sidebar' + +export function CustomTrigger() { + const { toggleSidebar } = useSidebar() + + return +} +``` + +## SidebarRail + +The `SidebarRail` component is used to render a rail within a `Sidebar`. This rail can be used to toggle the sidebar. + +```tsx showLineNumbers + + + + + + + + +``` + +## Data Fetching + +### React Server Components + +Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components. + +
+ +
+ A sidebar menu using React Server Components. +
+
+ +```tsx showLineNumbers {6} title="Skeleton to show loading state." +function NavProjectsSkeleton() { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) +} +``` + +```tsx showLineNumbers {2} title="Server component fetching data." +async function NavProjects() { + const projects = await fetchProjects() + + return ( + + {projects.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +```tsx showLineNumbers {8-10} title="Usage with React Suspense." +function AppSidebar() { + return ( + + + + Projects + + }> + + + + + + + ) +} +``` + +### SWR and React Query + +You can use the same approach with [SWR](https://swr.vercel.app/) or [react-query](https://tanstack.com/query/latest/docs/framework/react/overview). + +```tsx showLineNumbers title="SWR" +function NavProjects() { + const { data, isLoading } = useSWR("/api/projects", fetcher) + + if (isLoading) { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) + } + + if (!data) { + return ... + } + + return ( + + {data.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +```tsx showLineNumbers title="React Query" +function NavProjects() { + const { data, isLoading } = useQuery() + + if (isLoading) { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) + } + + if (!data) { + return ... + } + + return ( + + {data.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +## Controlled Sidebar + +Use the `open` and `onOpenChange` props to control the sidebar. + +
+ +
A controlled sidebar.
+
+ +```tsx showLineNumbers +export function AppSidebar() { + const [open, setOpen] = React.useState(false) + + return ( + + + + ) +} +``` + +## Theming + +We use the following CSS variables to theme the sidebar. + +```css +@layer base { + :root { + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + + .dark { + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 0 0% 98%; + --sidebar-primary-foreground: 240 5.9% 10%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} +``` + +**We intentionally use different variables for the sidebar and the rest of the application** to make it easy to have a sidebar that is styled differently from the rest of the application. Think a sidebar with a darker shade from the main application. + +## Styling + +Here are some tips for styling the sidebar based on different states. + +- **Styling an element based on the sidebar collapsible state.** The following will hide the `SidebarGroup` when the sidebar is in `icon` mode. + +```tsx + + + + + +``` + +- **Styling a menu action based on the menu button active state.** The following will force the menu action to be visible when the menu button is active. + +```tsx + + + + +``` + +You can find more tips on using states for styling in this [Twitter thread](https://x.com/shadcn/status/1842329158879420864). + +## Changelog + +### 2024-10-30 Cookie handling in setOpen + +- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in ``. + +Update the `setOpen` callback in `` as follows: + +```tsx showLineNumbers +const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === 'function' ? value(open) : value + if (setOpenProp) { + setOpenProp(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProp, open] +) +``` + +### 2024-10-21 Fixed `text-sidebar-foreground` + +- [#5491](https://github.com/shadcn-ui/ui/pull/5491) - Moved `text-sidebar-foreground` from `` to `` component. + +### 2024-10-20 Typo in `useSidebar` hook. + +Fixed typo in `useSidebar` hook. + +```diff showLineNumbers title="sidebar.tsx" +- throw new Error("useSidebar must be used within a Sidebar.") ++ throw new Error("useSidebar must be used within a SidebarProvider.") +``` diff --git a/apps/design-system/content/docs/fragments/multi-select.mdx b/apps/design-system/content/docs/fragments/multi-select.mdx index 8425bb4a988..1c3d40faf46 100644 --- a/apps/design-system/content/docs/fragments/multi-select.mdx +++ b/apps/design-system/content/docs/fragments/multi-select.mdx @@ -47,6 +47,13 @@ Use `MultiSelectorInput`to add a search input. +### Badge Combo Box with creatable selector + +creatable: `boolean` +`creatable` prop on the `MultiSelectorList` component can be used to add a new option if no items match + + + ### Badge Limit badgeLimit: `number` | `"wrap"`. diff --git a/apps/design-system/content/docs/fragments/toc.mdx b/apps/design-system/content/docs/fragments/toc.mdx new file mode 100644 index 00000000000..998a1ad9eb1 --- /dev/null +++ b/apps/design-system/content/docs/fragments/toc.mdx @@ -0,0 +1,18 @@ +--- +title: Table of Contents (TOC) +description: List of page anchors for the current page. +component: true +fragment: true +--- + +## Usage + +Highlight one or more elements of the TOC if in view. + + + +## Single=true + +Highlight one element of the TOC at a time. + + diff --git a/apps/design-system/package.json b/apps/design-system/package.json index 8a5e56ce5a2..a33cdffb605 100644 --- a/apps/design-system/package.json +++ b/apps/design-system/package.json @@ -6,8 +6,9 @@ "scripts": { "preinstall": "npx only-allow pnpm", "dev": "next dev --port 3003", - "build": "pnpm run content:build && pnpm run build:registry && next build", + "build": "pnpm run content:build && pnpm run build:registry && pnpm run build:llms && next build", "build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache", + "build:llms": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-llms-txt.ts", "start": "next start", "lint": "next lint", "content:build": "contentlayer2 build", @@ -59,7 +60,7 @@ "shiki": "^1.1.7", "tailwindcss": "^3.3.0", "tsconfig": "workspace:*", - "tsx": "^4.7.0", + "tsx": "^4.19.3", "typescript": "~5.5.0", "unist-builder": "3.0.0" } diff --git a/apps/design-system/public/llms.txt b/apps/design-system/public/llms.txt new file mode 100644 index 00000000000..6d50fe9e7f0 --- /dev/null +++ b/apps/design-system/public/llms.txt @@ -0,0 +1,154 @@ +# Supabase Design System +Last updated: 2025-03-05T12:59:50.911Z + +## Overview +The Design System used in Supabase Studio. + +## Docs +- [Changelog](https://supabase-design-system.vercel.app/design-system/docs/changelog) + - Latest updates and announcements. +- [Color usage](https://supabase-design-system.vercel.app/design-system/docs/color-usage) + - Colors system breakdown with best practise +- [Accordion](https://supabase-design-system.vercel.app/design-system/docs/components/accordion) + - A vertically stacked set of interactive headings that each reveal a section of content. +- [Alert](https://supabase-design-system.vercel.app/design-system/docs/components/alert copy) + - Displays a callout for user attention. +- [Alert Dialog](https://supabase-design-system.vercel.app/design-system/docs/components/alert-dialog) + - A modal dialog that interrupts the user with important content and expects a response. +- [Alert](https://supabase-design-system.vercel.app/design-system/docs/components/alert) + - Displays a callout for user attention. +- [Aspect Ratio](https://supabase-design-system.vercel.app/design-system/docs/components/aspect-ratio) + - Displays content within a desired ratio. +- [Atom components](https://supabase-design-system.vercel.app/design-system/docs/components/atom-components) +- [Avatar](https://supabase-design-system.vercel.app/design-system/docs/components/avatar) + - An image element with a fallback for representing the user. +- [Badge](https://supabase-design-system.vercel.app/design-system/docs/components/badge) + - Displays a badge or a component that looks like a badge. +- [Breadcrumb](https://supabase-design-system.vercel.app/design-system/docs/components/breadcrumb) + - Displays the path to the current resource using a hierarchy of links. +- [Button](https://supabase-design-system.vercel.app/design-system/docs/components/button) + - Displays a button or a component that looks like a button. +- [Calendar](https://supabase-design-system.vercel.app/design-system/docs/components/calendar) + - A date field component that allows users to enter and edit date. +- [Card](https://supabase-design-system.vercel.app/design-system/docs/components/card) + - Displays a card with header, content, and footer. +- [Carousel](https://supabase-design-system.vercel.app/design-system/docs/components/carousel) + - A carousel with motion and swipe built using Embla. +- [Chart](https://supabase-design-system.vercel.app/design-system/docs/components/chart) + - Beautiful charts. Built using Recharts. Copy and paste into your apps. +- [Checkbox](https://supabase-design-system.vercel.app/design-system/docs/components/checkbox) + - A control that allows the user to toggle between checked and not checked. +- [Collapsible](https://supabase-design-system.vercel.app/design-system/docs/components/collapsible) + - An interactive component which expands/collapses a panel. +- [Combobox](https://supabase-design-system.vercel.app/design-system/docs/components/combobox) + - Autocomplete input and command palette with a list of suggestions. +- [Command](https://supabase-design-system.vercel.app/design-system/docs/components/command) + - Fast, composable, unstyled command menu for React. +- [Command Menu (cmdk)](https://supabase-design-system.vercel.app/design-system/docs/components/commandmenu) + - A central command menu that acts as a control center with searchable actions. +- [Context Menu](https://supabase-design-system.vercel.app/design-system/docs/components/context-menu) + - Displays a menu to the user — such as a set of actions or functions — triggered by a button. +- [Data Table](https://supabase-design-system.vercel.app/design-system/docs/components/data-table) + - Powerful table and datagrids built using TanStack Table. +- [Date Picker](https://supabase-design-system.vercel.app/design-system/docs/components/date-picker) + - A date picker component with range and presets. +- [Dialog](https://supabase-design-system.vercel.app/design-system/docs/components/dialog) + - A window overlaid on either the primary window or another dialog window, rendering the content underneath inert. +- [Drawer](https://supabase-design-system.vercel.app/design-system/docs/components/drawer) + - A drawer component for React. +- [Dropdown Menu](https://supabase-design-system.vercel.app/design-system/docs/components/dropdown-menu) + - Displays a menu to the user — such as a set of actions or functions — triggered by a button. +- [Expanding Textarea](https://supabase-design-system.vercel.app/design-system/docs/components/expanding-textarea) + - Displays a textarea which autoexpands (or shrinks) based on its content. +- [React Hook Form](https://supabase-design-system.vercel.app/design-system/docs/components/form) + - Building forms with React Hook Form and Zod. +- [Fragment components](https://supabase-design-system.vercel.app/design-system/docs/components/fragment-components) + - '' +- [Hover Card](https://supabase-design-system.vercel.app/design-system/docs/components/hover-card) + - For sighted users to preview content available behind a link. +- [Input OTP](https://supabase-design-system.vercel.app/design-system/docs/components/input-otp) + - Accessible one-time password component with copy paste functionality. +- [Input](https://supabase-design-system.vercel.app/design-system/docs/components/input) + - Displays a form input field or a component that looks like an input field. +- [Label](https://supabase-design-system.vercel.app/design-system/docs/components/label) + - Renders an accessible label associated with controls. +- [Menubar](https://supabase-design-system.vercel.app/design-system/docs/components/menubar) + - A visually persistent menu common in desktop applications that provides quick access to a consistent set of commands. +- [Navigation Menu](https://supabase-design-system.vercel.app/design-system/docs/components/navigation-menu) + - A collection of links for navigating websites. +- [Pagination](https://supabase-design-system.vercel.app/design-system/docs/components/pagination) + - Pagination with page navigation, next and previous links. +- [Popover](https://supabase-design-system.vercel.app/design-system/docs/components/popover) + - Displays rich content in a portal, triggered by a button. +- [Progress](https://supabase-design-system.vercel.app/design-system/docs/components/progress) + - Displays an indicator showing the completion progress of a task, typically displayed as a progress bar. +- [Radio Group Card](https://supabase-design-system.vercel.app/design-system/docs/components/radio-group-card) + - A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time. +- [Radio Group Stacked](https://supabase-design-system.vercel.app/design-system/docs/components/radio-group-stacked) + - A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time. +- [Radio Group](https://supabase-design-system.vercel.app/design-system/docs/components/radio-group) + - A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time. +- [Resizable](https://supabase-design-system.vercel.app/design-system/docs/components/resizable) + - Accessible resizable panel groups and layouts with keyboard support. +- [Scroll-area](https://supabase-design-system.vercel.app/design-system/docs/components/scroll-area) + - Augments native scroll functionality for custom, cross-browser styling. +- [Select](https://supabase-design-system.vercel.app/design-system/docs/components/select) + - Displays a list of options for the user to pick from—triggered by a button. +- [Separator](https://supabase-design-system.vercel.app/design-system/docs/components/separator) + - Visually or semantically separates content. +- [Sheet](https://supabase-design-system.vercel.app/design-system/docs/components/sheet) + - Extends the Dialog component to display content that complements the main content of the screen. +- [Sidebar](https://supabase-design-system.vercel.app/design-system/docs/components/sidebar) + - A composable, themeable and customizable sidebar component. +- [Skeleton](https://supabase-design-system.vercel.app/design-system/docs/components/skeleton) + - Use to show a placeholder while content is loading. +- [Slider](https://supabase-design-system.vercel.app/design-system/docs/components/slider) + - An input where the user selects a value from within a given range. +- [Sonner](https://supabase-design-system.vercel.app/design-system/docs/components/sonner) + - An opinionated toast component for React. +- [Switch](https://supabase-design-system.vercel.app/design-system/docs/components/switch) + - A control that allows the user to toggle between checked and not checked. +- [Table](https://supabase-design-system.vercel.app/design-system/docs/components/table) + - A responsive table component. +- [Tabs](https://supabase-design-system.vercel.app/design-system/docs/components/tabs) + - A set of layered sections of content—known as tab panels—that are displayed one at a time. +- [Textarea](https://supabase-design-system.vercel.app/design-system/docs/components/textarea) + - Displays a form textarea or a component that looks like a textarea. +- [Toggle Group](https://supabase-design-system.vercel.app/design-system/docs/components/toggle-group) + - A set of two-state buttons that can be toggled on or off. +- [Toggle](https://supabase-design-system.vercel.app/design-system/docs/components/toggle) + - A two-state button that can be either on or off. +- [Tooltip](https://supabase-design-system.vercel.app/design-system/docs/components/tooltip) + - A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it. +- [Tree View](https://supabase-design-system.vercel.app/design-system/docs/components/tree-view) + - A tree view that assembles all the functionalities of the Accordion component to create a tree view. +- [Typography](https://supabase-design-system.vercel.app/design-system/docs/components/typography) + - Styles for headings, paragraphs, lists...etc +- [Figma](https://supabase-design-system.vercel.app/design-system/docs/figma) + - Every component recreated in Figma. With customizable props, typography and icons. +- [Admonition](https://supabase-design-system.vercel.app/design-system/docs/fragments/admonition) + - Displays a callout for user attention. +- [Assistant Chat](https://supabase-design-system.vercel.app/design-system/docs/fragments/assistant-chat) + - Bespoke Assistant chat text area, with support for a commands Popover. +- [Form Item Layout](https://supabase-design-system.vercel.app/design-system/docs/fragments/form-item-layout) + - A helper component that provides a layout for form items. +- [Info Tooltip](https://supabase-design-system.vercel.app/design-system/docs/fragments/info-tooltip) + - InfoTooltip provides a tooltip with an information icon. +- [Inner Side Menu](https://supabase-design-system.vercel.app/design-system/docs/fragments/inner-side-menu) + - InnerSideMenu is a component that provides a collapsible side menu with multiple sections. +- [Logs Bar Chart](https://supabase-design-system.vercel.app/design-system/docs/fragments/logs-bar-chart) + - A stacked bar chart that displays logs errors and successes. +- [Modal](https://supabase-design-system.vercel.app/design-system/docs/fragments/modal) + - A window overlaid on either the primary window or another dialog window, rendering the content underneath inert. +- [Multi Select](https://supabase-design-system.vercel.app/design-system/docs/fragments/multi-select) + - Multiple selection component. +- [Text Confirm Dialog](https://supabase-design-system.vercel.app/design-system/docs/fragments/text-confirm-dialog) + - A modal dialog that interrupts the user with important content and expects a response. +- [Icons](https://supabase-design-system.vercel.app/design-system/docs/icons) + - Icons system breakdown. Copy values of Icons. +- [Introduction](https://supabase-design-system.vercel.app/design-system/docs/index) + - Components and patterns that you can copy and paste into Supabase apps. Accessible. Customizable. Open Source. +- [Tailwind Classes](https://supabase-design-system.vercel.app/design-system/docs/tailwind-classes) + - Icons system breakdown. Copy values of Icons. +- [Themes](https://supabase-design-system.vercel.app/design-system/docs/theming) + - Themes used in Supabase diff --git a/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx b/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx index f2e06033516..b1c29126099 100644 --- a/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx +++ b/apps/design-system/registry/default/example/combobox-dropdown-menu.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { Calendar, MoreHorizontal, Tags, Trash, User } from 'lucide-react' -import { Button, CommandList_Shadcn_ } from 'ui' +import { Button, CommandList_Shadcn_, ScrollArea } from 'ui' import { Command_Shadcn_, CommandEmpty_Shadcn_, @@ -75,23 +75,25 @@ export default function ComboboxDropdownMenu() { - - No label found. - - {labels.map((label) => ( - { - setLabel(value) - setOpen(false) - }} - > - {label} - - ))} - - + + + No label found. + + {labels.map((label) => ( + { + setLabel(value) + setOpen(false) + }} + > + {label} + + ))} + + + diff --git a/apps/design-system/registry/default/example/logs-bar-chart.tsx b/apps/design-system/registry/default/example/logs-bar-chart.tsx index b477e491133..e639f355f1a 100644 --- a/apps/design-system/registry/default/example/logs-bar-chart.tsx +++ b/apps/design-system/registry/default/example/logs-bar-chart.tsx @@ -9,6 +9,7 @@ export default function LogsBarChartDemo() { timestamp: date.toISOString(), ok_count: Math.floor(Math.random() * 100), // Random value 0-99 error_count: Math.floor(Math.random() * 50), // Random value 0-50 + warning_count: Math.floor(Math.random() * 50), // Random value 0-50 } }).reverse() diff --git a/apps/design-system/registry/default/example/multi-select-combobox-creatable.tsx b/apps/design-system/registry/default/example/multi-select-combobox-creatable.tsx new file mode 100644 index 00000000000..5b8ddf96c71 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-combobox-creatable.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorInput, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([]) + + return ( + + + + + + Mango + Date + Apple + Elderberrie + Fig + Grape + Banana + Kiwi + Strawberry + Cherry + + + + ) +} diff --git a/apps/design-system/registry/default/example/toc-demo.tsx b/apps/design-system/registry/default/example/toc-demo.tsx new file mode 100644 index 00000000000..2de1b7fdfeb --- /dev/null +++ b/apps/design-system/registry/default/example/toc-demo.tsx @@ -0,0 +1,227 @@ +'use client' +import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react' +import { Toc, TOCItems, TOCScrollArea } from 'ui-patterns' + +export default function MultiSelectDemo() { + return ( + +
+
+
+

Getting Started with Cloud Computing

+ +

+ Introduction + +

+

+ Cloud computing has revolutionized how we build and deploy applications. This guide + will walk you through the fundamental concepts and best practices. +

+ +

+ Key Concepts + +

+

+ Before diving deep into cloud services, it's important to understand the basic + building blocks that make cloud computing possible. +

+ +

+ Infrastructure as a Service (IaaS) + +

+

+ IaaS provides virtualized computing resources over the internet. This includes virtual + machines, storage, and networking. +

+ +

+ Platform as a Service (PaaS) + +

+

+ PaaS delivers a platform allowing customers to develop, run, and manage applications + without dealing with infrastructure. +

+ +

+ Best Practices + +

+

+ Following cloud computing best practices ensures optimal performance, security, and + cost-effectiveness. +

+ +

+ Security Considerations + +

+

+ Security should be your top priority when working with cloud services. Implement + proper authentication, encryption, and access controls. +

+ +

+ Cost Optimization + +

+

+ Learn how to optimize your cloud spending through resource planning, monitoring, and + implementing cost-saving strategies. +

+ +

+ Conclusion + +

+

+ Cloud computing continues to evolve, offering new possibilities for businesses and + developers alike. Stay updated with the latest trends and best practices. +

+
+
+ +
+
+ ) +} + +const TocComponent = () => { + const { toc } = useTocAnchors() + + return ( + +

+ On this page +

+ + + +
+ ) +} + +import { type AnchorProviderProps, AnchorProvider } from 'ui-patterns' + +interface TOCHeader { + id?: string + text: string + link: string + level: number +} + +const TocAnchorsContext = createContext(undefined) + +const useTocAnchors = () => { + const context = useContext(TocAnchorsContext) + if (!context) { + throw new Error('useTocAnchors must be used within an TocAnchorsContext') + } + return context +} + +const TocAnchorsProvider = ({ children }: PropsWithChildren) => { + const [tocList, setTocList] = useState([]) + + const toc = tocList.map((item) => ({ + title: item.text, + url: item.link, + depth: item.level, + })) + + useEffect(() => { + /** + * Because we're directly querying the DOM, needs the setTimeout so the DOM + * update will happen first. + */ + const timeoutHandle = setTimeout(() => { + const headings = Array.from( + document.querySelector('#example-toc-demo')?.querySelectorAll('h2, h3') ?? [] + ) + + const newHeadings = headings + .filter((heading) => heading.id) + .map((heading) => { + const text = heading?.textContent?.replace('#', '') + const link = heading.querySelector('a')?.getAttribute('href') + if (!link) return null + + const level = heading.tagName === 'H2' ? 2 : 3 + + return { text, link, level } as Partial + }) + .filter((x): x is TOCHeader => !!x && !!x.text && !!x.link && !!x.level) + + setTocList(newHeadings) + }, 100) + + return () => clearTimeout(timeoutHandle) + /** + * window.location.href needed to recalculate toc when page changes, + * `useSubscribeTocRerender` above will trigger the rerender + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [typeof window !== 'undefined' && window.location.href]) + + return ( + + + {children} + + + ) +} diff --git a/apps/design-system/registry/default/example/toc-single-demo.tsx b/apps/design-system/registry/default/example/toc-single-demo.tsx new file mode 100644 index 00000000000..d7fe367d0ce --- /dev/null +++ b/apps/design-system/registry/default/example/toc-single-demo.tsx @@ -0,0 +1,227 @@ +'use client' +import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react' +import { Toc, TOCItems, TOCScrollArea } from 'ui-patterns' + +export default function MultiSelectDemo() { + return ( + +
+
+
+

Getting Started with Cloud Computing

+ +

+ Introduction + +

+

+ Cloud computing has revolutionized how we build and deploy applications. This guide + will walk you through the fundamental concepts and best practices. +

+ +

+ Key Concepts + +

+

+ Before diving deep into cloud services, it's important to understand the basic + building blocks that make cloud computing possible. +

+ +

+ Infrastructure as a Service (IaaS) + +

+

+ IaaS provides virtualized computing resources over the internet. This includes virtual + machines, storage, and networking. +

+ +

+ Platform as a Service (PaaS) + +

+

+ PaaS delivers a platform allowing customers to develop, run, and manage applications + without dealing with infrastructure. +

+ +

+ Best Practices + +

+

+ Following cloud computing best practices ensures optimal performance, security, and + cost-effectiveness. +

+ +

+ Security Considerations + +

+

+ Security should be your top priority when working with cloud services. Implement + proper authentication, encryption, and access controls. +

+ +

+ Cost Optimization + +

+

+ Learn how to optimize your cloud spending through resource planning, monitoring, and + implementing cost-saving strategies. +

+ +

+ Conclusion + +

+

+ Cloud computing continues to evolve, offering new possibilities for businesses and + developers alike. Stay updated with the latest trends and best practices. +

+
+
+ +
+
+ ) +} + +const TocComponent = () => { + const { toc } = useTocAnchors() + + return ( + +

+ On this page +

+ + + +
+ ) +} + +import { type AnchorProviderProps, AnchorProvider } from 'ui-patterns' + +interface TOCHeader { + id?: string + text: string + link: string + level: number +} + +const TocAnchorsContext = createContext(undefined) + +const useTocAnchors = () => { + const context = useContext(TocAnchorsContext) + if (!context) { + throw new Error('useTocAnchors must be used within an TocAnchorsContext') + } + return context +} + +const TocAnchorsProvider = ({ children }: PropsWithChildren) => { + const [tocList, setTocList] = useState([]) + + const toc = tocList.map((item) => ({ + title: item.text, + url: item.link, + depth: item.level, + })) + + useEffect(() => { + /** + * Because we're directly querying the DOM, needs the setTimeout so the DOM + * update will happen first. + */ + const timeoutHandle = setTimeout(() => { + const headings = Array.from( + document.querySelector('#example-toc-single-demo')?.querySelectorAll('h2, h3') ?? [] + ) + + const newHeadings = headings + .filter((heading) => heading.id) + .map((heading) => { + const text = heading?.textContent?.replace('#', '') + const link = heading.querySelector('a')?.getAttribute('href') + if (!link) return null + + const level = heading.tagName === 'H2' ? 2 : 3 + + return { text, link, level } as Partial + }) + .filter((x): x is TOCHeader => !!x && !!x.text && !!x.link && !!x.level) + + setTocList(newHeadings) + }, 100) + + return () => clearTimeout(timeoutHandle) + /** + * window.location.href needed to recalculate toc when page changes, + * `useSubscribeTocRerender` above will trigger the rerender + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [typeof window !== 'undefined' && window.location.href]) + + return ( + + + {children} + + + ) +} diff --git a/apps/design-system/registry/default/example/tooltip-demo.tsx b/apps/design-system/registry/default/example/tooltip-demo.tsx index 5584ba3f170..f347278c438 100644 --- a/apps/design-system/registry/default/example/tooltip-demo.tsx +++ b/apps/design-system/registry/default/example/tooltip-demo.tsx @@ -1,22 +1,16 @@ -import { Button } from 'ui' -import { - Tooltip_Shadcn_, - TooltipContent_Shadcn_, - TooltipProvider_Shadcn_, - TooltipTrigger_Shadcn_, -} from 'ui' +import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui' export default function TooltipDemo() { return ( - - - + + + - - + +

Add to library

-
-
-
+ + + ) } diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index 58d7c1dd508..2198f2527af 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -1066,6 +1066,16 @@ export const examples: Registry = [ type: 'components:example', files: ['example/inner-side-menu-with-search.tsx'], }, + { + name: 'toc-demo', + type: 'components:example', + files: ['example/toc-demo.tsx'], + }, + { + name: 'toc-single-demo', + type: 'components:example', + files: ['example/toc-single-demo.tsx'], + }, { name: 'multi-select-demo', type: 'components:example', @@ -1091,6 +1101,11 @@ export const examples: Registry = [ type: 'components:example', files: ['example/multi-select-combobox.tsx'], }, + { + name: 'multi-select-combobox-creatable', + type: 'components:example', + files: ['example/multi-select-combobox-creatable.tsx'], + }, { name: 'multi-select-deletable-badge', type: 'components:example', diff --git a/apps/design-system/scripts/build-llms-txt.ts b/apps/design-system/scripts/build-llms-txt.ts new file mode 100644 index 00000000000..85d19ba5089 --- /dev/null +++ b/apps/design-system/scripts/build-llms-txt.ts @@ -0,0 +1,106 @@ +import { registry } from '@/registry/registry' +import fs from 'fs' +import path from 'path' + +const BASE_URL = 'https://supabase-design-system.vercel.app/design-system/docs' + +interface DocMeta { + title: string + description?: string + path: string +} + +console.log('🤖 Building llms.txt') + +// Function to extract frontmatter from MDX files +function extractFrontmatter(content: string): { title?: string; description?: string } { + const frontmatterRegex = /---\n([\s\S]*?)\n---/ + const match = content.match(frontmatterRegex) + if (!match) return {} + + const frontmatter = match[1] + const titleMatch = frontmatter.match(/title:\s*(.*)/) + const descriptionMatch = frontmatter.match(/description:\s*(.*)/) + + return { + title: titleMatch?.[1], + description: descriptionMatch?.[1], + } +} + +// Function to recursively get all MDX files +function getMdxFiles(dir: string): string[] { + const files: string[] = [] + const entries = fs.readdirSync(dir, { withFileTypes: true }) + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name) + if (entry.isDirectory()) { + files.push(...getMdxFiles(fullPath)) + } else if (entry.name.endsWith('.mdx')) { + files.push(fullPath) + } + } + + return files +} + +// Function to get all MDX files and their metadata +function getDocFiles(): DocMeta[] { + const docsDir = path.join(process.cwd(), 'content/docs') + const mdxFiles = getMdxFiles(docsDir) + + const docs: DocMeta[] = [] + + for (const fullPath of mdxFiles) { + const content = fs.readFileSync(fullPath, 'utf-8') + const { title, description } = extractFrontmatter(content) + + if (title) { + // Get relative path and convert to URL path + const relativePath = path.relative(docsDir, fullPath) + const urlPath = relativePath + .replace(/\.mdx$/, '') + .replace(/\/index$/, '') + .replace(/\\/g, '/') + + docs.push({ + title, + description, + path: urlPath, + }) + } + } + + return docs +} + +// Generate the llms.txt content +const docs = getDocFiles() +let content = `# Supabase Design System +Last updated: ${new Date().toISOString()} + +## Overview +The Design System used in Supabase Studio. + +## Docs +` + +// Add documentation links +for (const doc of docs) { + const url = `${BASE_URL}/${doc.path}` + content += `- [${doc.title}](${url})` + if (doc.description) { + content += `\n - ${doc.description}` + } + content += '\n' +} + +// Write the file +const publicDir = path.join(process.cwd(), 'public') +if (!fs.existsSync(publicDir)) { + fs.mkdirSync(publicDir, { recursive: true }) +} + +fs.writeFileSync(path.join(publicDir, 'llms.txt'), content) +console.log('✅ Generated llms.txt in public directory') diff --git a/apps/design-system/tailwind.config.js b/apps/design-system/tailwind.config.js index 5a512287d82..5be9a0d5a1d 100644 --- a/apps/design-system/tailwind.config.js +++ b/apps/design-system/tailwind.config.js @@ -8,7 +8,8 @@ module.exports = config({ // purge styles from grid library // './../../packages/ui/src/**/*.{tsx,ts,js}', - './../../packages/ui-patterns/!(node_modules)/**/*.{tsx,ts,js}', + './../../packages/ui-patterns/**/*.{tsx,ts,js}', + '!./../../packages/ui-patterns/node_modules/**/*', // ignore node_modules rule ], theme: { extend: { diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index d3ed2ff3c4d..6eeb1dafe26 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -26,6 +26,9 @@ yarn-error.log* # Sitemap public/sitemap.xml +# llms.txt (generated) +public/llms.txt +public/llms/ # Copied examples folder /examples/ diff --git a/apps/docs/app/contributing/ContributingToC.tsx b/apps/docs/app/contributing/ContributingToC.tsx index e1d8423f89a..ea2a953f15f 100644 --- a/apps/docs/app/contributing/ContributingToC.tsx +++ b/apps/docs/app/contributing/ContributingToC.tsx @@ -5,7 +5,8 @@ import type { HTMLAttributes } from 'react' import { useEffect, useState } from 'react' import { useBreakpoint } from 'common' -import { cn, Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_ } from 'ui' +import { cn, Separator, Sheet, SheetContent, SheetHeader, SheetTrigger } from 'ui' +import { Feedback } from '~/components/Feedback' interface TocItem extends HTMLAttributes { label: string @@ -18,7 +19,7 @@ export function ContributingToc({ className }: { className?: string }) { useEffect(() => { const headings = [ - ...document.querySelectorAll('article.prose > h2,h3'), + ...document.querySelectorAll('article.prose > h2,h3:not(#feedback-title)'), ] as Array const tocItems = headings .filter((heading) => !!heading.id) @@ -30,20 +31,16 @@ export function ContributingToc({ className }: { className?: string }) { }, []) return mobileToc ? ( - + ) : (