mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 14:19:11 +03:00
Merge branch 'master' into hf/deprecate-slack-provider
This commit is contained in:
2519 files changed
+260731
-172446
No files matched your search
+1
-2
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -29,7 +29,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: ','
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
engine-strict=true
|
||||
engine-strict=true
|
||||
update-notifier=false
|
||||
@jsr:registry=https://npm.jsr.io
|
||||
+6
-1
@@ -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
|
||||
+4
-4
@@ -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/).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1864,6 +1864,28 @@ export const Index: Record<string, any> = {
|
||||
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<string, any> = {
|
||||
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",
|
||||
|
||||
@@ -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 (
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
<TooltipProvider_Shadcn_ delayDuration={0}>{children}</TooltipProvider_Shadcn_>
|
||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
||||
</NextThemesProvider>
|
||||
</JotaiProvider>
|
||||
)
|
||||
|
||||
@@ -29,9 +29,9 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
|
||||
y2="208"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="16"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="16"
|
||||
></line>
|
||||
<line
|
||||
x1="192"
|
||||
@@ -40,9 +40,9 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
|
||||
y2="192"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="16"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="16"
|
||||
></line>
|
||||
</svg>
|
||||
<span className="hidden font-bold sm:inline-block">shadcn/ui</span>
|
||||
|
||||
@@ -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',
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -47,6 +47,13 @@ Use `MultiSelectorInput`to add a search input.
|
||||
|
||||
<ComponentPreview name="multi-select-combobox" />
|
||||
|
||||
### 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
|
||||
|
||||
<ComponentPreview name="multi-select-combobox-creatable" />
|
||||
|
||||
### Badge Limit
|
||||
|
||||
badgeLimit: `number` | `"wrap"`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
<ComponentPreview name="toc-demo" description="" peekCode wide />
|
||||
|
||||
## Single=true
|
||||
|
||||
Highlight one element of the TOC at a time.
|
||||
|
||||
<ComponentPreview name="toc-single-demo" description="" peekCode wide />
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
<DropdownMenuSubContent className="p-0">
|
||||
<Command_Shadcn_>
|
||||
<CommandInput_Shadcn_ placeholder="Filter label..." autoFocus={true} />
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No label found.</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{labels.map((label) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={label}
|
||||
value={label}
|
||||
onSelect={(value) => {
|
||||
setLabel(value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
<ScrollArea className="h-20">
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No label found.</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{labels.map((label) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={label}
|
||||
value={label}
|
||||
onSelect={(value) => {
|
||||
setLabel(value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</ScrollArea>
|
||||
</Command_Shadcn_>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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<string[]>([])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" badgeLimit="wrap" />
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorInput placeholder="Search fruits" showResetIcon />
|
||||
<MultiSelectorList creatable>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<TocAnchorsProvider>
|
||||
<div
|
||||
id="example-toc-demo"
|
||||
className="p-4 md:p-8 grid md:grid-cols-4 gap-4 max-h-[600px] overflow-y-auto"
|
||||
>
|
||||
<div className="md:col-span-3">
|
||||
<div className="prose max-w-none">
|
||||
<h1>Getting Started with Cloud Computing</h1>
|
||||
|
||||
<h2 id="introduction">
|
||||
Introduction
|
||||
<a
|
||||
href="#introduction"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Cloud computing has revolutionized how we build and deploy applications. This guide
|
||||
will walk you through the fundamental concepts and best practices.
|
||||
</p>
|
||||
|
||||
<h2 id="key-concepts">
|
||||
Key Concepts
|
||||
<a
|
||||
href="#key-concepts"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Before diving deep into cloud services, it's important to understand the basic
|
||||
building blocks that make cloud computing possible.
|
||||
</p>
|
||||
|
||||
<h3 id="iaas">
|
||||
Infrastructure as a Service (IaaS)
|
||||
<a
|
||||
href="#iaas"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
IaaS provides virtualized computing resources over the internet. This includes virtual
|
||||
machines, storage, and networking.
|
||||
</p>
|
||||
|
||||
<h3 id="paas">
|
||||
Platform as a Service (PaaS)
|
||||
<a
|
||||
href="#paas"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
PaaS delivers a platform allowing customers to develop, run, and manage applications
|
||||
without dealing with infrastructure.
|
||||
</p>
|
||||
|
||||
<h2 id="best-practices">
|
||||
Best Practices
|
||||
<a
|
||||
href="#best-practices"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Following cloud computing best practices ensures optimal performance, security, and
|
||||
cost-effectiveness.
|
||||
</p>
|
||||
|
||||
<h3 id="security">
|
||||
Security Considerations
|
||||
<a
|
||||
href="#security"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Security should be your top priority when working with cloud services. Implement
|
||||
proper authentication, encryption, and access controls.
|
||||
</p>
|
||||
|
||||
<h3 id="cost-optimization">
|
||||
Cost Optimization
|
||||
<a
|
||||
href="#cost-optimization"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Learn how to optimize your cloud spending through resource planning, monitoring, and
|
||||
implementing cost-saving strategies.
|
||||
</p>
|
||||
|
||||
<h2 id="conclusion">
|
||||
Conclusion
|
||||
<a
|
||||
href="#conclusion"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Cloud computing continues to evolve, offering new possibilities for businesses and
|
||||
developers alike. Stay updated with the latest trends and best practices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<TocComponent />
|
||||
</div>
|
||||
</TocAnchorsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const TocComponent = () => {
|
||||
const { toc } = useTocAnchors()
|
||||
|
||||
return (
|
||||
<Toc className="sticky top-0 border-l">
|
||||
<h3 className="inline-flex items-center gap-1.5 font-mono text-xs uppercase text-foreground pl-[calc(1.5rem+6px)]">
|
||||
On this page
|
||||
</h3>
|
||||
<TOCScrollArea className="-ml-[2px]">
|
||||
<TOCItems items={toc} />
|
||||
</TOCScrollArea>
|
||||
</Toc>
|
||||
)
|
||||
}
|
||||
|
||||
import { type AnchorProviderProps, AnchorProvider } from 'ui-patterns'
|
||||
|
||||
interface TOCHeader {
|
||||
id?: string
|
||||
text: string
|
||||
link: string
|
||||
level: number
|
||||
}
|
||||
|
||||
const TocAnchorsContext = createContext<AnchorProviderProps | undefined>(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<TOCHeader[]>([])
|
||||
|
||||
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<TOCHeader>
|
||||
})
|
||||
.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 (
|
||||
<TocAnchorsContext.Provider value={{ toc }}>
|
||||
<AnchorProvider toc={toc} single={false}>
|
||||
{children}
|
||||
</AnchorProvider>
|
||||
</TocAnchorsContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<TocAnchorsProvider>
|
||||
<div
|
||||
id="example-toc-single-demo"
|
||||
className="p-4 md:p-8 grid md:grid-cols-4 gap-4 max-h-[600px] overflow-y-auto"
|
||||
>
|
||||
<div className="md:col-span-3">
|
||||
<div className="prose max-w-none">
|
||||
<h1>Getting Started with Cloud Computing</h1>
|
||||
|
||||
<h2 id="single-introduction">
|
||||
Introduction
|
||||
<a
|
||||
href="#single-introduction"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Cloud computing has revolutionized how we build and deploy applications. This guide
|
||||
will walk you through the fundamental concepts and best practices.
|
||||
</p>
|
||||
|
||||
<h2 id="single-key-concepts">
|
||||
Key Concepts
|
||||
<a
|
||||
href="#single-key-concepts"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Before diving deep into cloud services, it's important to understand the basic
|
||||
building blocks that make cloud computing possible.
|
||||
</p>
|
||||
|
||||
<h3 id="single-iaas">
|
||||
Infrastructure as a Service (IaaS)
|
||||
<a
|
||||
href="#single-iaas"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
IaaS provides virtualized computing resources over the internet. This includes virtual
|
||||
machines, storage, and networking.
|
||||
</p>
|
||||
|
||||
<h3 id="single-paas">
|
||||
Platform as a Service (PaaS)
|
||||
<a
|
||||
href="#single-paas"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
PaaS delivers a platform allowing customers to develop, run, and manage applications
|
||||
without dealing with infrastructure.
|
||||
</p>
|
||||
|
||||
<h2 id="single-best-practices">
|
||||
Best Practices
|
||||
<a
|
||||
href="#single-best-practices"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Following cloud computing best practices ensures optimal performance, security, and
|
||||
cost-effectiveness.
|
||||
</p>
|
||||
|
||||
<h3 id="single-security">
|
||||
Security Considerations
|
||||
<a
|
||||
href="#single-security"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Security should be your top priority when working with cloud services. Implement
|
||||
proper authentication, encryption, and access controls.
|
||||
</p>
|
||||
|
||||
<h3 id="single-cost-optimization">
|
||||
Cost Optimization
|
||||
<a
|
||||
href="#single-cost-optimization"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Learn how to optimize your cloud spending through resource planning, monitoring, and
|
||||
implementing cost-saving strategies.
|
||||
</p>
|
||||
|
||||
<h2 id="single-conclusion">
|
||||
Conclusion
|
||||
<a
|
||||
href="#single-conclusion"
|
||||
aria-hidden="true"
|
||||
className="ml-2 opacity-0 group-hover:opacity-100 transition"
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
</h2>
|
||||
<p>
|
||||
Cloud computing continues to evolve, offering new possibilities for businesses and
|
||||
developers alike. Stay updated with the latest trends and best practices.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<TocComponent />
|
||||
</div>
|
||||
</TocAnchorsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const TocComponent = () => {
|
||||
const { toc } = useTocAnchors()
|
||||
|
||||
return (
|
||||
<Toc className="sticky top-0 border-l">
|
||||
<h3 className="inline-flex items-center gap-1.5 font-mono text-xs uppercase text-foreground pl-[calc(1.5rem+6px)]">
|
||||
On this page
|
||||
</h3>
|
||||
<TOCScrollArea className="-ml-[2px]">
|
||||
<TOCItems items={toc} />
|
||||
</TOCScrollArea>
|
||||
</Toc>
|
||||
)
|
||||
}
|
||||
|
||||
import { type AnchorProviderProps, AnchorProvider } from 'ui-patterns'
|
||||
|
||||
interface TOCHeader {
|
||||
id?: string
|
||||
text: string
|
||||
link: string
|
||||
level: number
|
||||
}
|
||||
|
||||
const TocAnchorsContext = createContext<AnchorProviderProps | undefined>(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<TOCHeader[]>([])
|
||||
|
||||
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<TOCHeader>
|
||||
})
|
||||
.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 (
|
||||
<TocAnchorsContext.Provider value={{ toc }}>
|
||||
<AnchorProvider toc={toc} single={true}>
|
||||
{children}
|
||||
</AnchorProvider>
|
||||
</TocAnchorsContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<TooltipProvider_Shadcn_>
|
||||
<Tooltip_Shadcn_>
|
||||
<TooltipTrigger_Shadcn_ asChild>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button type="outline">Hover</Button>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
<TooltipContent_Shadcn_>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Add to library</p>
|
||||
</TooltipContent_Shadcn_>
|
||||
</Tooltip_Shadcn_>
|
||||
</TooltipProvider_Shadcn_>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
@@ -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: {
|
||||
|
||||
@@ -26,6 +26,9 @@ yarn-error.log*
|
||||
|
||||
# Sitemap
|
||||
public/sitemap.xml
|
||||
# llms.txt (generated)
|
||||
public/llms.txt
|
||||
public/llms/
|
||||
|
||||
# Copied examples folder
|
||||
/examples/
|
||||
@@ -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<HTMLElement> {
|
||||
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<HTMLHeadingElement>
|
||||
const tocItems = headings
|
||||
.filter((heading) => !!heading.id)
|
||||
@@ -30,20 +31,16 @@ export function ContributingToc({ className }: { className?: string }) {
|
||||
}, [])
|
||||
|
||||
return mobileToc ? (
|
||||
<MobileToc
|
||||
items={tocItems}
|
||||
className={cn(
|
||||
'[--local-top-spacing:2rem]',
|
||||
'fixed top-[calc(var(--header-height)+var(--local-top-spacing))] right-8'
|
||||
)}
|
||||
/>
|
||||
<MobileToc items={tocItems} />
|
||||
) : (
|
||||
<TocBase
|
||||
items={tocItems}
|
||||
className={cn(
|
||||
'[--local-top-spacing:5rem]',
|
||||
'fixed top-[calc(var(--header-height)+var(--local-top-spacing))] right-8 w-36',
|
||||
'xl:border-l border-foreground-muted'
|
||||
'border-l thin-scrollbar overflow-y-auto px-2 hidden lg:block',
|
||||
'col-span-3 self-start sticky',
|
||||
'top-[calc(var(--header-height)+1px+2rem)] max-h-[calc(100vh-var(--header-height)-3rem)]',
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
@@ -52,31 +49,74 @@ export function ContributingToc({ className }: { className?: string }) {
|
||||
function MobileToc({ items, className }: { items: Array<TocItem>; className?: string }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const onHashChanged = () => setOpen(false)
|
||||
|
||||
window.addEventListener('hashchange', onHashChanged)
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', onHashChanged)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger_Shadcn_ className={cn('border rounded p-2', className)}>
|
||||
<Menu />
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger
|
||||
className={cn(
|
||||
'fixed z-0 inset-0 top-auto w-full rounded-t-lg border border-b-0 p-4 bg-studio flex items-center gap-2 text-foreground-light text-sm',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Menu size={16} strokeWidth={1.5} className="text-foreground-light" />
|
||||
<span className="sr-only">
|
||||
{open ? 'Close table of contents' : 'Open table of contents'}
|
||||
</span>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ align="end" className="w-48">
|
||||
<TocBase items={items} />
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
<span>On this page</span>
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
side="bottom"
|
||||
size="lg"
|
||||
className={cn(
|
||||
'w-full flex flex-col gap-0 p-0 rounded-t-lg overflow-hidden',
|
||||
!open && 'top-[calc(100vh-100px)]'
|
||||
)}
|
||||
>
|
||||
<SheetHeader className="py-0 px-4">
|
||||
<SheetTrigger
|
||||
className={cn(
|
||||
'w-full py-4 flex items-center gap-2 text-foreground-light text-sm',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Menu size={16} strokeWidth={1.5} className="text-foreground-light" />
|
||||
<span className="sr-only">
|
||||
{open ? 'Close table of contents' : 'Open table of contents'}
|
||||
</span>
|
||||
<span>On this page</span>
|
||||
</SheetTrigger>
|
||||
</SheetHeader>
|
||||
<div className="w-full flex-1 p-4 pb-8 overflow-y-auto thin-scrollbar">
|
||||
<TocBase items={items} />
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
|
||||
function TocBase({ items, className }: { items: Array<TocItem>; className?: string }) {
|
||||
return (
|
||||
<nav aria-label="Table of contents" className={cn('px-4 text-foreground-light', className)}>
|
||||
<ul className="flex flex-col gap-1">
|
||||
<nav aria-label="Table of contents" className={cn('text-foreground-lighter', className)}>
|
||||
<span className="hidden lg:block font-mono text-xs uppercase text-foreground px-5 mb-6">
|
||||
On this page
|
||||
</span>
|
||||
<ul className="toc-menu list-none lg:pl-5 text-[0.8rem] grid gap-2">
|
||||
{items.map((item) => (
|
||||
<li key={item.anchor} className="overflow-hidden truncate">
|
||||
<a href={`#${item.anchor}`}>{item.label}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Separator className="lg:w-[calc(100%-2rem)] lg:ml-5 my-4 lg:my-8" />
|
||||
<Feedback className="pl-0 lg:pl-5" />
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -171,15 +171,16 @@ Or, you can use the `<$CodeSample />` component to include code samples from a s
|
||||
|
||||
If the file is within the `supabase/supabase` repo's `examples` directory:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```mdx
|
||||
<$CodeSample
|
||||
path="/relative/path/from/examples/directory.js"
|
||||
{/_ Array of [start, end] line numbers to include.
|
||||
{/* Array of [start, end] line numbers to include.
|
||||
Line numbers are 1-indexed and inclusive.
|
||||
-1 indicates the final line. _/}
|
||||
-1 indicates the final line. */}
|
||||
lines={[[1, 3], [5, -1]]}
|
||||
{/* Optional, displays as a file name on the code block */}
|
||||
meta="display/path.js"
|
||||
meta="name=display/path.js"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -193,12 +194,92 @@ repo="cli"
|
||||
commit="1623aa9b95ec90e21c5bae5a0d50dcf272abe92f"
|
||||
path="/relative/path/from/root.js"
|
||||
lines={[[1, 3], [5, -1]]}
|
||||
meta="display/path.js"
|
||||
meta="name=display/path.js"
|
||||
/>
|
||||
```
|
||||
|
||||
The repo must be public, the org must be on the allow list, and the commit must be an immutable SHA (not a mutable tag or branch name).
|
||||
|
||||
#### Multi-file code samples
|
||||
|
||||
Multi-file code samples use the `<$CodeTabs>` annotation:
|
||||
|
||||
````mdx
|
||||
<$CodeTabs>
|
||||
|
||||
```js name=a.js
|
||||
console.log('Hello, world!')
|
||||
```
|
||||
|
||||
```js name=b.js
|
||||
console.log('Goodbye, world!')
|
||||
```
|
||||
|
||||
</$CodeTabs>
|
||||
````
|
||||
|
||||
This produces:
|
||||
|
||||
<$CodeTabs>
|
||||
|
||||
```js name=a.js
|
||||
console.log('Hello, world!')
|
||||
```
|
||||
|
||||
```js name=b.js
|
||||
console.log('Goodbye, world!')
|
||||
```
|
||||
|
||||
</$CodeTabs>
|
||||
|
||||
If you want to give a file name for your code sample, you can also use `<$CodeTabs>` with only one code block.
|
||||
|
||||
#### TypeScript type hints
|
||||
|
||||
JavaScript and TypeScript code blocks include type hints:
|
||||
|
||||
````mdx
|
||||
```js
|
||||
let hello = 'Hello, world!'
|
||||
```
|
||||
````
|
||||
|
||||
Hover over `hello` to see the hint.
|
||||
|
||||
```js
|
||||
let hello = 'Hello, world!'
|
||||
```
|
||||
|
||||
The entire code block must be a valid compilation target for TypeScript. That is, if you have undefined variables or missing imports, type hints won't be shown. You can add extra statements above the cut for variable definitions, etc.
|
||||
|
||||
You can also import the `supabase-js` library here:
|
||||
|
||||
````mdx
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
const supabase = createClient('dummy', 'client')
|
||||
|
||||
// ---cut---
|
||||
const result = supabase.auth.signInWithPassword({
|
||||
email: 'test@example.com',
|
||||
password: 'test1234',
|
||||
})
|
||||
```
|
||||
````
|
||||
|
||||
Note the hidden statements above the cut. Hover over `signInWithPassword` to see the hint:
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
const supabase = createClient('dummy', 'client')
|
||||
|
||||
// ---cut---
|
||||
const result = supabase.auth.signInWithPassword({
|
||||
email: 'test@example.com',
|
||||
password: 'test1234',
|
||||
})
|
||||
```
|
||||
|
||||
### Icons
|
||||
|
||||
The following icons are available. They can be styled with [Tailwind](https://tailwindcss.com/) classes:
|
||||
@@ -388,6 +469,14 @@ void main() async {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Info Tooltip
|
||||
|
||||
The <InfoTooltip tooltipContent="This is an info tooltip">InfoTooltip</InfoTooltip> component is used to add more context to a word or phrase via tooltip.
|
||||
|
||||
```mdx
|
||||
<InfoTooltip tooltipContent="The open source Firebase alternative.">Supabase</InfoTooltip>
|
||||
```
|
||||
|
||||
## Partials
|
||||
|
||||
We incorporate content reuse in the docs to avoid duplication. If you find yourself writing the same content over and over, you can put it in a partial instead. Here are some examples of commonly used partials:
|
||||
@@ -407,10 +496,10 @@ We incorporate content reuse in the docs to avoid duplication. If you find yours
|
||||
>
|
||||
|
||||
```mdx
|
||||
<DatabaseSetup />
|
||||
<$Partial path="database_setup.mdx" />
|
||||
```
|
||||
|
||||
<DatabaseSetup />
|
||||
<$Partial path="database_setup.mdx" />
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
@@ -422,10 +511,10 @@ We incorporate content reuse in the docs to avoid duplication. If you find yours
|
||||
>
|
||||
|
||||
```mdx
|
||||
<CreateClientSnippet />
|
||||
<$Partial path="create_client_snippet.mdx" />
|
||||
```
|
||||
|
||||
<CreateClientSnippet />
|
||||
<$Partial path="create_client_snippet.mdx" />
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
@@ -434,8 +523,18 @@ We incorporate content reuse in the docs to avoid duplication. If you find yours
|
||||
|
||||
To make a new partial:
|
||||
|
||||
1. Make a new MDX file in `apps/docs/components/MDX`.
|
||||
1. Make a new MDX file in `apps/docs/content/_partials`.
|
||||
1. Write your reusable content.
|
||||
1. Inside `apps/docs/components/MDX/partials.tsx`, import and re-export your partial.
|
||||
1. Inside `apps/docs/features/docs/mdx.shared.tsx`, import your partial and include it in the `components` object.
|
||||
1. You can now use your partial inside any other MDX file by using: `<YourPartialName />`.
|
||||
1. You can now use your partial inside any other MDX file by using: `<$Partial path="path/from/_partials/directory.mdx" />`.
|
||||
|
||||
If you need compile-time variable replacement, you can define variables that get replaced with strings.
|
||||
|
||||
```mdx
|
||||
<$Partial path="path/to/file.mdx" variables={{ "substitute": "this" }}>
|
||||
```
|
||||
|
||||
```mdx
|
||||
Here is the partial text to {{ .substitute }}.
|
||||
```
|
||||
|
||||
Only string replacements are possible at this time. For more complex use cases, consider making a custom component.
|
||||
@@ -3,24 +3,29 @@ import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { ContributingToc } from '~/app/contributing/ContributingToC'
|
||||
import { MDXProviderGuides } from '~/features/docs/GuidesMdx.client'
|
||||
import { MDXRemoteBase } from '~/features/docs/MdxBase'
|
||||
import { LayoutMainContent } from '~/layouts/DefaultLayout'
|
||||
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
|
||||
import Breadcrumbs from '~/components/Breadcrumbs'
|
||||
|
||||
export default async function ContributingPage() {
|
||||
const contentFile = join(dirname(fileURLToPath(import.meta.url)), 'content.mdx')
|
||||
const content = await readFile(contentFile, 'utf-8')
|
||||
|
||||
return (
|
||||
<SidebarSkeleton hideSideNav>
|
||||
<div className="px-8 py-16">
|
||||
<article className="prose mx-auto">
|
||||
<MDXProviderGuides>
|
||||
<SidebarSkeleton>
|
||||
<LayoutMainContent className="pb-0 grid grid-cols-12 relative gap-4">
|
||||
<div className="col-span-12 lg:col-span-9">
|
||||
<Breadcrumbs className="mb-2 col-span-full" />
|
||||
<article
|
||||
id="contributing"
|
||||
className="prose max-w-none relative transition-all ease-out duration-100"
|
||||
>
|
||||
<MDXRemoteBase source={content} />
|
||||
</MDXProviderGuides>
|
||||
</article>
|
||||
</article>
|
||||
</div>
|
||||
<ContributingToc />
|
||||
</div>
|
||||
</LayoutMainContent>
|
||||
</SidebarSkeleton>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug: string[] }
|
||||
|
||||
const GuidePage = async ({ params }: { params: Params }) => {
|
||||
const data = await getGuidesMarkdown(params)
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams()
|
||||
const generateMetadata = genGuideMeta(getGuidesMarkdown)
|
||||
|
||||
export default GuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -1,146 +0,0 @@
|
||||
import matter from 'gray-matter'
|
||||
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import { notFound } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { isValidGuideFrontmatter } from '~/lib/docs'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
import {
|
||||
terraformDocsBranch,
|
||||
terraformDocsDocsDir,
|
||||
terraformDocsOrg,
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
// Each external docs page is mapped to a local page
|
||||
const pageMap = [
|
||||
{
|
||||
remoteFile: 'README.md',
|
||||
meta: {
|
||||
title: 'Terraform Provider',
|
||||
},
|
||||
useRoot: true,
|
||||
},
|
||||
{
|
||||
slug: 'tutorial',
|
||||
remoteFile: 'tutorial.md',
|
||||
meta: {
|
||||
title: 'Using the Supabase Terraform Provider',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
interface Params {
|
||||
slug?: string[]
|
||||
}
|
||||
|
||||
const TerraformDocs = async ({ params }: { params: Params }) => {
|
||||
const { meta, ...data } = await getContent(params)
|
||||
|
||||
const options = {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkMkDocsAdmonition, remarkPyMdownTabs, [removeTitle, meta.title]],
|
||||
rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
|
||||
},
|
||||
} as SerializeOptions
|
||||
|
||||
return <GuideTemplate mdxOptions={options} meta={meta} {...data} />
|
||||
}
|
||||
|
||||
/**
|
||||
* The GitHub repo uses relative links, which don't lead to the right locations
|
||||
* in docs.
|
||||
*
|
||||
* @param url The original link, as written in the Markdown file
|
||||
* @returns The rewritten link
|
||||
*/
|
||||
const urlTransform: UrlTransformFunction = (url: string) => {
|
||||
try {
|
||||
const placeholderHostname = 'placeholder'
|
||||
const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
|
||||
|
||||
// Don't modify a url with a FQDN or a url that's only a hash
|
||||
if (hostname !== placeholderHostname || pathname === '/') {
|
||||
return url
|
||||
}
|
||||
|
||||
const getBasename = (pathname: string) =>
|
||||
pathname.endsWith('.md') ? pathname.replace(/\.md$/, '') : pathname
|
||||
const stripLeadingPrefix = (pathname: string) => pathname.replace(/^\//, '')
|
||||
const stripLeadingDocs = (pathname: string) => pathname.replace(/^docs\//, '')
|
||||
|
||||
const relativePath = stripLeadingPrefix(getBasename(pathname))
|
||||
|
||||
const page = pageMap.find(
|
||||
({ remoteFile, useRoot }) =>
|
||||
(useRoot && `${relativePath}.md` === remoteFile) ||
|
||||
(!useRoot && `${stripLeadingDocs(relativePath)}.md` === remoteFile)
|
||||
)
|
||||
|
||||
if (page) {
|
||||
return 'terraform' + `/${page.slug}` + hash
|
||||
}
|
||||
|
||||
// If we don't have this page in our docs, link to GitHub repo
|
||||
return `https://github.com/${terraformDocsOrg}/${terraformDocsRepo}/blob/${terraformDocsBranch}${pathname}${hash}`
|
||||
} catch (err) {
|
||||
console.error('Error transforming markdown URL', err)
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch markdown from external repo
|
||||
*/
|
||||
const getContent = async ({ slug }: Params) => {
|
||||
const [requestedSlug] = slug ?? []
|
||||
const page = pageMap.find((page) => page.slug === requestedSlug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const { meta, remoteFile, useRoot } = page
|
||||
|
||||
const editLink = newEditLink(
|
||||
`${terraformDocsOrg}/${terraformDocsRepo}/blob/${terraformDocsBranch}/${useRoot ? '' : `${terraformDocsDocsDir}/`}${remoteFile}`
|
||||
)
|
||||
|
||||
let response = await fetchRevalidatePerDay(
|
||||
`https://raw.githubusercontent.com/${terraformDocsOrg}/${terraformDocsRepo}/${terraformDocsBranch}/${useRoot ? '' : `${terraformDocsDocsDir}/`}${remoteFile}`
|
||||
)
|
||||
|
||||
let rawContent = await response.text()
|
||||
// Strip out HTML comments
|
||||
rawContent = rawContent.replace(/<!--.*?-->/, '')
|
||||
let { content, data } = matter(rawContent)
|
||||
|
||||
// Remove the title from the content so it isn't duplicated in the final display
|
||||
content = removeRedundantH1(content)
|
||||
|
||||
Object.assign(meta, data)
|
||||
|
||||
if (!isValidGuideFrontmatter(meta)) {
|
||||
throw Error('Guide frontmatter is invalid.')
|
||||
}
|
||||
|
||||
return {
|
||||
pathname:
|
||||
`/guides/platform/terraform${slug?.length ? `/${slug.join('/')}` : ''}` satisfies `/${string}`,
|
||||
meta,
|
||||
content,
|
||||
editLink,
|
||||
}
|
||||
}
|
||||
|
||||
const generateStaticParams = async () => pageMap.map(({ slug }) => ({ slug: slug ? [slug] : [] }))
|
||||
const generateMetadata = genGuideMeta(getContent)
|
||||
|
||||
export default TerraformDocs
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -1,61 +0,0 @@
|
||||
import { cache, type PropsWithChildren } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
|
||||
import { NavMenuSection } from '~/components/Navigation/Navigation.types'
|
||||
import Layout from '~/layouts/guides'
|
||||
import { supabaseMisc } from '~/lib/supabaseMisc'
|
||||
import { getAiPrompts } from './getting-started/ai-prompts/[slug]/AiPrompts.utils'
|
||||
|
||||
// Revalidate occasionally to pick up changes to partners
|
||||
// 60 seconds/minute * 60 minutes/hour * 24 hours/day
|
||||
// export const revalidate = 86_400
|
||||
|
||||
const GuidesLayout = async ({ children }: PropsWithChildren) => {
|
||||
const [partnerNavItems, promptNavItems] = await Promise.all([getPartners(), getPrompts()])
|
||||
|
||||
const additionalNavItems =
|
||||
partnerNavItems.length > 0 || promptNavItems.length > 0
|
||||
? { integrations: partnerNavItems, prompts: promptNavItems }
|
||||
: undefined
|
||||
|
||||
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
async function getPrompts() {
|
||||
const prompts = await getAiPrompts()
|
||||
return prompts.map(
|
||||
(prompt) =>
|
||||
({
|
||||
name: prompt.heading,
|
||||
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
}
|
||||
|
||||
const getPartners = cache(getPartnersImpl)
|
||||
async function getPartnersImpl() {
|
||||
if (!IS_PLATFORM) return []
|
||||
|
||||
const { data, error } = await supabaseMisc()
|
||||
.from('partners')
|
||||
.select('slug, title')
|
||||
.eq('approved', true)
|
||||
.eq('type', 'technology')
|
||||
.order('title')
|
||||
if (error) {
|
||||
console.error(new Error('Error fetching partners', { cause: error }))
|
||||
}
|
||||
|
||||
const partnerNavItems = (data ?? []).map(
|
||||
(partner) =>
|
||||
({
|
||||
name: partner.title,
|
||||
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
|
||||
return partnerNavItems
|
||||
}
|
||||
|
||||
export default GuidesLayout
|
||||
@@ -1,106 +0,0 @@
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { CodeBlock } from 'ui'
|
||||
import { Heading } from 'ui/src/components/CustomHTMLElements'
|
||||
import { type TOCHeader } from '~/components/GuidesTableOfContents'
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import type { Parameter } from '~/lib/refGenerator/refTypes'
|
||||
import specFile from '~/spec/cli_v1_config.yaml' with { type: 'yml' }
|
||||
|
||||
const meta = {
|
||||
title: 'Supabase CLI config',
|
||||
}
|
||||
|
||||
const generateMetadata = genGuideMeta(() => ({
|
||||
pathname: '/guides/cli/config',
|
||||
meta,
|
||||
}))
|
||||
|
||||
const tocList: TOCHeader[] = []
|
||||
const content = specFile.info.tags.map((tag: { id: string; title: string }, id: number) => {
|
||||
tocList.push({ id: `${id}`, text: tag.title, link: `${tag.id}-config`, level: 2 })
|
||||
return (
|
||||
<div>
|
||||
<Heading tag="h2">{tag.title} Config</Heading>
|
||||
{specFile.parameters
|
||||
.filter((param: Parameter) => param.tags && param.tags[0] === tag.id)
|
||||
.map((parameter: Parameter, id: number) => {
|
||||
tocList.push({ id: `${id}`, text: parameter.id, link: `#${parameter.id}`, level: 3 })
|
||||
return <Info parameter={parameter} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const Config = () => {
|
||||
const editLink = newEditLink('supabase/supabase/blob/master/apps/docs/spec/cli_v1_config.yaml')
|
||||
|
||||
return (
|
||||
<GuideTemplate meta={meta} editLink={editLink}>
|
||||
<ReactMarkdown>{specFile.info.description}</ReactMarkdown>
|
||||
<div>{content}</div>
|
||||
</GuideTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
function Info({ parameter }: { parameter: Parameter }) {
|
||||
return (
|
||||
<div className="mt-8">
|
||||
<div>
|
||||
<Heading tag="h3" parseAnchors={false} customAnchor={parameter.id}>
|
||||
<code>{parameter.title}</code>
|
||||
</Heading>
|
||||
|
||||
<div className="border-b pb-8" key={parameter.id}>
|
||||
<div className=" mb-16">
|
||||
<div>
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">Name</th>
|
||||
<th className="text-left">Default</th>
|
||||
<th className="text-left">Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{parameter.id}</td>
|
||||
<td>{parameter.default ? parameter.default.toString() : 'None'}</td>
|
||||
<td>{parameter.required !== undefined && parameter.required.toString()}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="mb-4 scroll-mt-16 mt-0 ">
|
||||
<p className="text-sm font-bold uppercase">Description</p>
|
||||
<ReactMarkdown>{parameter.description}</ReactMarkdown>
|
||||
</div>
|
||||
{parameter.usage && (
|
||||
<div className="mb-4 scroll-mt-16 mt-0 ">
|
||||
<p className="text-sm font-bold uppercase">Usage</p>
|
||||
<CodeBlock className="useless-code-block-class" language="py">
|
||||
{parameter.usage}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
)}
|
||||
{parameter.links && (
|
||||
<div>
|
||||
<p className="text-sm font-bold uppercase">See also</p>
|
||||
<ul>
|
||||
{parameter.links.map((link) => (
|
||||
<li key={link.link}>
|
||||
<a href={link.link}>{link.name}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Config
|
||||
export { generateMetadata }
|
||||
@@ -1,410 +0,0 @@
|
||||
import { codeBlock } from 'common-tags'
|
||||
import { Check, PlusCircle } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
import {
|
||||
CodeBlock,
|
||||
Heading,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
||||
import {
|
||||
terraformDocsBranch,
|
||||
terraformDocsDocsDir,
|
||||
terraformDocsOrg,
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
const meta = {
|
||||
title: 'Terraform Provider reference',
|
||||
subtitle: 'Resources and data sources available through the Terraform Provider',
|
||||
}
|
||||
|
||||
const generateMetadata = genGuideMeta(() => ({
|
||||
pathname: '/guides/platform/terraform/reference',
|
||||
meta,
|
||||
}))
|
||||
|
||||
function ProviderSettings({ schema }: { schema: any }) {
|
||||
const attributes = schema.block.attributes
|
||||
|
||||
const example = codeBlock`
|
||||
provider "supabase" {
|
||||
${Object.keys(attributes).map(
|
||||
(attribute) =>
|
||||
`${attribute} = ${attributes[attribute].type === 'string' ? `""` : '<value>'}`
|
||||
)}
|
||||
}
|
||||
`
|
||||
|
||||
return (
|
||||
<section aria-labelledby="provider-settings" className="prose max-w-none">
|
||||
<Heading tag="h2">Provider settings</Heading>
|
||||
<p>
|
||||
Use these settings to configure your Supabase provider and authenticate to your Supabase
|
||||
project.
|
||||
</p>
|
||||
<Heading tag="h3">Example usage</Heading>
|
||||
<CodeBlock className="not-prose">{example}</CodeBlock>
|
||||
<Heading tag="h3">Details</Heading>
|
||||
{/* extra div because width restriction doesn't work on table itself */}
|
||||
<div className="w-full overflow-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Optional</th>
|
||||
<th>Sensitive</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(schema.block.attributes).map((attribute) => (
|
||||
<tr key={attribute}>
|
||||
<td>{attribute}</td>
|
||||
<td>
|
||||
<ReactMarkdown>{attributes[attribute].description}</ReactMarkdown>
|
||||
</td>
|
||||
<td>{attributes[attribute].type}</td>
|
||||
<td className="align-middle">
|
||||
{attributes[attribute].optional && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{attributes[attribute].sensitive && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function Resources({ schema }: { schema: any }) {
|
||||
return (
|
||||
<section aria-labelledby="resources" className="prose max-w-none">
|
||||
<Heading tag="h2">Resources</Heading>
|
||||
<p>You can configure these resources using the Supabase Terraform provider:</p>
|
||||
<Tabs>
|
||||
{Object.keys(schema).map((resource) => (
|
||||
<TabPanel id={resource} label={resource}>
|
||||
<Heading tag="h4">Example usage</Heading>
|
||||
<CodeBlock className="not-prose">{codeBlock`
|
||||
resource "${resource}" "<label>" {
|
||||
${Object.keys(schema[resource].block.attributes)
|
||||
.filter((attribute) => !schema[resource].block.attributes[attribute].computed)
|
||||
.map(
|
||||
(attribute) =>
|
||||
`${attribute} = ${
|
||||
schema[resource].block.attributes[attribute].type === 'string'
|
||||
? `""`
|
||||
: '<value>'
|
||||
}`
|
||||
)}
|
||||
}
|
||||
`}</CodeBlock>
|
||||
<Heading tag="h4">Details</Heading>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Optional</th>
|
||||
<th>Read-only</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(schema[resource].block.attributes).map((attribute) => (
|
||||
<tr key={attribute}>
|
||||
<td>
|
||||
<code>{attribute}</code>
|
||||
</td>
|
||||
<td>
|
||||
<ReactMarkdown>
|
||||
{schema[resource].block.attributes[attribute].description}
|
||||
</ReactMarkdown>
|
||||
</td>
|
||||
<td>
|
||||
{schema[resource].block.attributes[attribute].type ?? (
|
||||
<Popover_Shadcn_>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<button className="flex items-center justify-between gap-2">
|
||||
Nested type
|
||||
<PlusCircle size={14} className="shrink-0" />
|
||||
</button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="max-h-[50vh] overflow-auto">
|
||||
<ul>
|
||||
{Object.keys(
|
||||
schema[resource].block.attributes[attribute].nested_type.attributes
|
||||
).map((nestedAttribute) => (
|
||||
<li key={nestedAttribute}>
|
||||
{nestedAttribute}
|
||||
<ul>
|
||||
<li>
|
||||
<ReactMarkdown className="*:!m-0">
|
||||
{
|
||||
schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].description
|
||||
}
|
||||
</ReactMarkdown>
|
||||
</li>
|
||||
<li>
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].type ?? 'nested type'}
|
||||
</li>
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].required && <li>Required</li>}
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].optional && <li>Optional</li>}
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].computed && <li>Read-only</li>}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[resource].block.attributes[attribute].required && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[resource].block.attributes[attribute].optional && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[resource].block.attributes[attribute].computed && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function DataSources({ schema }: { schema: any }) {
|
||||
return (
|
||||
<section aria-labelledby="data-sources" className="prose max-w-none">
|
||||
<Heading tag="h2">Data sources</Heading>
|
||||
<p>You can read these resources using the Supabase Terraform provider:</p>
|
||||
<Tabs>
|
||||
{Object.keys(schema).map((dataSource) => (
|
||||
<TabPanel id={dataSource} label={dataSource}>
|
||||
<Heading tag="h4">Example usage</Heading>
|
||||
<CodeBlock className="not-prose">{codeBlock`
|
||||
resource "${dataSource}" "all" {
|
||||
${Object.keys(schema[dataSource].block.attributes)
|
||||
.filter(
|
||||
(attribute) => !schema[dataSource].block.attributes[attribute].computed
|
||||
)
|
||||
.map(
|
||||
(attribute) =>
|
||||
`${attribute} = ${
|
||||
schema[dataSource].block.attributes[attribute].type === 'string'
|
||||
? `""`
|
||||
: '<value>'
|
||||
}`
|
||||
)}
|
||||
}
|
||||
`}</CodeBlock>
|
||||
<Heading tag="h4">Details</Heading>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Optional</th>
|
||||
<th>Read-only</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(schema[dataSource].block.attributes).map((attribute) => (
|
||||
<tr key={attribute}>
|
||||
<td>
|
||||
<code>{attribute}</code>
|
||||
</td>
|
||||
<td>
|
||||
<ReactMarkdown>
|
||||
{schema[dataSource].block.attributes[attribute].description}
|
||||
</ReactMarkdown>
|
||||
</td>
|
||||
<td>
|
||||
{schema[dataSource].block.attributes[attribute].type ?? (
|
||||
<Popover_Shadcn_>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<button className="flex items-center justify-between gap-2">
|
||||
Nested type
|
||||
<PlusCircle size={14} />
|
||||
</button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="max-h-[50vh] overflow-auto">
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.nesting_mode === 'set' && 'Array of:'}
|
||||
<ul>
|
||||
{Object.keys(
|
||||
schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes
|
||||
).map((nestedAttribute) => (
|
||||
<li key={nestedAttribute}>
|
||||
{nestedAttribute}
|
||||
<ul>
|
||||
<li>
|
||||
<ReactMarkdown className="*:!m-0">
|
||||
{
|
||||
schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].description
|
||||
}
|
||||
</ReactMarkdown>
|
||||
</li>
|
||||
<li>
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].type ?? 'nested type'}
|
||||
</li>
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].required && <li>Required</li>}
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].optional && <li>Optional</li>}
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].computed && <li>Read-only</li>}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[dataSource].block.attributes[attribute].required && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[dataSource].block.attributes[attribute].optional && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[dataSource].block.attributes[attribute].computed && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const TerraformReferencePage = async () => {
|
||||
const { schema } = await getSchema()
|
||||
|
||||
const editLink = newEditLink('supabase/terraform-provider-supabase')
|
||||
|
||||
return (
|
||||
<GuideTemplate meta={meta} editLink={editLink}>
|
||||
The Terraform Provider provices access to{' '}
|
||||
<Link
|
||||
href="https://developer.hashicorp.com/terraform/language/resources"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
resources
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
href="https://developer.hashicorp.com/terraform/language/data-sources"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
data sources
|
||||
</Link>
|
||||
. Resources are infrastructure objects, such as a Supabase project, that you can declaratively
|
||||
configure. Data sources are sources of information about your Supabase instances.
|
||||
<ProviderSettings
|
||||
schema={schema.provider_schemas['registry.terraform.io/supabase/supabase'].provider}
|
||||
/>
|
||||
<Resources
|
||||
schema={schema.provider_schemas['registry.terraform.io/supabase/supabase'].resource_schemas}
|
||||
/>
|
||||
<DataSources
|
||||
schema={
|
||||
schema.provider_schemas['registry.terraform.io/supabase/supabase'].data_source_schemas
|
||||
}
|
||||
/>
|
||||
</GuideTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON schema from external repo
|
||||
*/
|
||||
const getSchema = async () => {
|
||||
let response = await fetchRevalidatePerDay(
|
||||
`https://raw.githubusercontent.com/${terraformDocsOrg}/${terraformDocsRepo}/${terraformDocsBranch}/${terraformDocsDocsDir}/schema.json`
|
||||
)
|
||||
if (!response.ok) throw Error('Failed to fetch Terraform JSON schema from GitHub')
|
||||
|
||||
const schema = await response.json()
|
||||
|
||||
return {
|
||||
schema,
|
||||
}
|
||||
}
|
||||
|
||||
export default TerraformReferencePage
|
||||
export { generateMetadata }
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Information on where to fetch docs content
|
||||
*/
|
||||
const terraformDocsOrg = 'supabase'
|
||||
const terraformDocsRepo = 'terraform-provider-supabase'
|
||||
const terraformDocsBranch = 'v1.1.3'
|
||||
const terraformDocsDocsDir = 'docs'
|
||||
|
||||
export { terraformDocsOrg, terraformDocsRepo, terraformDocsBranch, terraformDocsDocsDir }
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const AiGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['ai', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('ai')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['ai', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default AiGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function AiLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
+2
@@ -10,6 +10,8 @@ import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLin
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'vecs'
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const ApiGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['api', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('api')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['api', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default ApiGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function ApiLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const AuthGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['auth', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('auth')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['auth', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default AuthGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const CronGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['cron', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('cron')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['cron', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default CronGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function CronLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const DatabaseGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['database', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('database')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['database', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default DatabaseGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
+16
@@ -18,6 +18,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'wrappers'
|
||||
@@ -48,6 +50,13 @@ const pageMap = [
|
||||
},
|
||||
remoteFile: 'bigquery.md',
|
||||
},
|
||||
{
|
||||
slug: 'clerk',
|
||||
meta: {
|
||||
title: 'Clerk',
|
||||
},
|
||||
remoteFile: 'clerk.md',
|
||||
},
|
||||
{
|
||||
slug: 'clickhouse',
|
||||
meta: {
|
||||
@@ -83,6 +92,13 @@ const pageMap = [
|
||||
},
|
||||
remoteFile: 'mssql.md',
|
||||
},
|
||||
{
|
||||
slug: 'notion',
|
||||
meta: {
|
||||
title: 'Notion',
|
||||
},
|
||||
remoteFile: 'notion.md',
|
||||
},
|
||||
{
|
||||
slug: 'paddle',
|
||||
meta: {
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function DatabaseLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const DeploymentGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['deployment', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('deployment')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['deployment', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default DeploymentGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
+2
@@ -11,6 +11,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'setup-cli'
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function DeploymentLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
+2
@@ -18,6 +18,8 @@ import {
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// Each external docs page is mapped to a local page
|
||||
const pageMap = [
|
||||
{
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const FunctionsGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['functions', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('functions')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['functions', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default FunctionsGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function FunctionsLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const GettingStartedGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['getting-started', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('getting-started')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['getting-started', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default GettingStartedGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
+3
-1
@@ -1,3 +1,4 @@
|
||||
import matter from 'gray-matter'
|
||||
import type { Code, Heading, Root } from 'mdast'
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { toMarkdown } from 'mdast-util-to-markdown'
|
||||
@@ -11,7 +12,8 @@ import { EXAMPLES_DIRECTORY } from '~/lib/docs'
|
||||
const PROMPTS_DIRECTORY = join(EXAMPLES_DIRECTORY, 'prompts')
|
||||
|
||||
function parseMarkdown(markdown: string) {
|
||||
const mdast = fromMarkdown(markdown)
|
||||
const { content: withoutFrontmatter } = matter(markdown)
|
||||
const mdast = fromMarkdown(withoutFrontmatter)
|
||||
|
||||
let heading = ''
|
||||
visit(mdast, 'heading', (node: Heading) => {
|
||||
File renamed without changes.
+1
-1
@@ -23,7 +23,7 @@ ${content}
|
||||
|
||||
return (
|
||||
<GuideTemplate
|
||||
meta={{ title: `API Prompt: ${heading}` }}
|
||||
meta={{ title: `AI Prompt: ${heading}` }}
|
||||
content={content}
|
||||
editLink={newEditLink(`supabase/supabase/blob/master/examples/prompts/${slug}.md`)}
|
||||
/>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { type NavMenuSection } from '~/components/Navigation/Navigation.types'
|
||||
import Layout from '~/layouts/guides'
|
||||
import { getAiPrompts } from '../getting-started/ai-prompts/[slug]/AiPrompts.utils'
|
||||
|
||||
export default async function GettingStartedLayout({ children }: { children: React.ReactNode }) {
|
||||
const additionalNavItems = { prompts: await getPrompts() }
|
||||
|
||||
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
async function getPrompts() {
|
||||
const prompts = await getAiPrompts()
|
||||
return prompts.map(
|
||||
(prompt) =>
|
||||
({
|
||||
name: prompt.heading,
|
||||
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
}
|
||||
+2
@@ -11,6 +11,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'pg_graphql'
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function GraphQlLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const IntegrationsGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['integrations', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('integrations')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['integrations', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default IntegrationsGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
import { type NavMenuSection } from '~/components/Navigation/Navigation.types'
|
||||
import Layout from '~/layouts/guides'
|
||||
import { supabaseMisc } from '~/lib/supabaseMisc'
|
||||
|
||||
export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) {
|
||||
const additionalNavItems = { integrations: await getPartners() }
|
||||
|
||||
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
// Will need to turn on revalidation later, just turning it off for now so we
|
||||
// can slowly turn things back on while monitoring usage
|
||||
const getPartners = unstable_cache(getPartnersImpl, [], { revalidate: false })
|
||||
async function getPartnersImpl() {
|
||||
if (!IS_PLATFORM) return []
|
||||
|
||||
const { data, error } = await supabaseMisc()
|
||||
.from('partners')
|
||||
.select('slug, title')
|
||||
.eq('approved', true)
|
||||
.eq('type', 'technology')
|
||||
.order('title')
|
||||
if (error) {
|
||||
console.error(new Error('Error fetching partners', { cause: error }))
|
||||
}
|
||||
|
||||
const partnerNavItems = (data ?? []).map(
|
||||
(partner) =>
|
||||
({
|
||||
name: partner.title,
|
||||
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
|
||||
return partnerNavItems
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const LocalDevelopmentGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['local-development', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('local-development')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['local-development', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default LocalDevelopmentGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function LocalDevelopmentLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const PlatformGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['platform', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('platform')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['platform', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default PlatformGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function PlatformLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const QueuesGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['queues', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('queues')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['queues', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default QueuesGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function QueuesLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const RealtimeGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['realtime', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('realtime')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['realtime', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default RealtimeGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function RealtimeLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const ResourcesGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['resources', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('resources')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['resources', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default ResourcesGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function ResourcesLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const SecurityGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['security', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('security')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['security', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default SecurityGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function SecurityLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const SelfHostingGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['self-hosting', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('self-hosting')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['self-hosting', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default SelfHostingGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function SelfHostingLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
Loaded 100 of 2519 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user