mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 06:39:09 +03:00
Merge branch 'master' into chore/remove-jwt-secret-from-db
This commit is contained in:
3283 files changed
+250667
-164830
No files matched your search
+11
-1
@@ -1,10 +1,20 @@
|
||||
/packages/shared-data/pricing.ts @roryw10 @kevcodez
|
||||
/packages/shared-data/plans.ts @roryw10 @kevcodez
|
||||
/packages/common/telemetry-constants.ts @4L3k51 @loong @pamelachia
|
||||
|
||||
/apps/studio/ @supabase/Dashboard
|
||||
|
||||
# temporary until we've ironed out all the problems with sync workflow
|
||||
/apps/docs/content/troubleshooting/ @charislam
|
||||
|
||||
/apps/www/ @supabase/marketing
|
||||
/apps/www/public/images/blog @supabase/marketing
|
||||
/apps/www/lib/redirects.js
|
||||
|
||||
/docker/ @supabase/cli
|
||||
/docker/ @supabase/dev-workflows
|
||||
|
||||
/apps/studio/next.config.js @supabase/security
|
||||
/apps/studio/components/interfaces/Billing/Payment @supabase/security
|
||||
/apps/studio/components/interfaces/Organization/BillingSettings/ @supabase/security
|
||||
/apps/studio/components/interfaces/Organization/Documents/ @supabase/security
|
||||
/apps/studio/pages/new/index.tsx @supabase/security
|
||||
@@ -33,14 +33,18 @@ jobs:
|
||||
with:
|
||||
sparse-checkout: |
|
||||
packages
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
- name: Type check
|
||||
run: npm run typecheck
|
||||
run: pnpm run typecheck
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
run: pnpm run test
|
||||
@@ -42,4 +42,31 @@ jobs:
|
||||
run: |
|
||||
echo "Applying triage label for new issue"
|
||||
gh issue edit "$NUMBER" --add-label "$LABELS"
|
||||
|
||||
spam-detection:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Check GitHub Issue for spam
|
||||
env:
|
||||
POST_URL: ${{ secrets.POST_URL }}
|
||||
BEARER_TOKEN: ${{ secrets.BEARER_TOKEN }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
run: |
|
||||
RESPONSE=$(curl -s -X POST "$POST_URL" \
|
||||
-H "Authorization: Bearer $BEARER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"issue_id": $NUMBER}')
|
||||
echo "spam_response=$RESPONSE" >> $GITHUB_OUTPUT
|
||||
- name: Use spam detector output to label issue
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
LABEL: flagged
|
||||
run: |
|
||||
IS_SPAM=$(echo "$SPAM_RESPONSE" | jq -r '.spam')
|
||||
if [ "$IS_SPAM" == "true" ]; then
|
||||
echo "Applying flagged label for new issue suspected of spam"
|
||||
gh issue edit "$NUMBER" --add-label "$LABEL"
|
||||
fi
|
||||
@@ -18,20 +18,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'pull_request' && (github.event.label.name == 'autofix') }}
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: apps
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install required dependencies
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run Prettier in fix mode
|
||||
run: npm run format
|
||||
run: pnpm run format
|
||||
|
||||
- name: Commit changes and push to existing branch
|
||||
run: |
|
||||
@@ -39,7 +44,7 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
exclude: |
|
||||
*.css
|
||||
**/package.json
|
||||
**/package-lock.json
|
||||
**/pnpm-lock.yaml
|
||||
./.git/*
|
||||
*.ipynb
|
||||
./i18n/README.*.md
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Config (Preview)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "supabase/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
wait:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
status: ${{ steps.check.outputs.conclusion }}
|
||||
steps:
|
||||
- uses: fountainhead/action-wait-for-check@v1.2.0
|
||||
id: check
|
||||
with:
|
||||
checkName: Supabase
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
apply:
|
||||
needs:
|
||||
- wait
|
||||
if: ${{ needs.wait.outputs.status == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
TF_VAR_linked_project: xguihxuzqibwxjnimxev
|
||||
TF_VAR_git_branch: ${{ github.head_ref }}
|
||||
TF_CLI_ARGS_apply: -target=supabase_settings.preview
|
||||
defaults:
|
||||
run:
|
||||
working-directory: supabase/remotes
|
||||
outputs:
|
||||
db_user: ${{ steps.branch.outputs.user }}
|
||||
db_pass: ${{ steps.branch.outputs.password }}
|
||||
db_host: ${{ steps.branch.outputs.host }}
|
||||
db_port: ${{ steps.branch.outputs.port }}
|
||||
jwt_secret: ${{ steps.branch.outputs.jwt_secret }}
|
||||
ref: ${{ steps.branch.outputs.id }}
|
||||
status: ${{ steps.branch.outputs.status }}
|
||||
version: ${{ steps.branch.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_wrapper: false
|
||||
|
||||
- run: terraform init
|
||||
- run: terraform apply -auto-approve -no-color
|
||||
- id: branch
|
||||
run: |
|
||||
terraform output -json branch_database \
|
||||
| jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" \
|
||||
>> "$GITHUB_OUTPUT"
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Config (Production)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "supabase/remotes/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
apply:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
TF_VAR_linked_project: xguihxuzqibwxjnimxev
|
||||
defaults:
|
||||
run:
|
||||
working-directory: supabase/remotes
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_wrapper: false
|
||||
|
||||
- run: terraform init
|
||||
- run: terraform apply -auto-approve -no-color
|
||||
@@ -26,20 +26,25 @@ jobs:
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Download dependencies
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Update last-changed dates
|
||||
working-directory: ./apps/docs
|
||||
if: ${{ !inputs.reset }}
|
||||
run: npm run last-changed
|
||||
run: pnpm run last-changed
|
||||
|
||||
- name: Reset last-changed dates
|
||||
working-directory: ./apps/docs
|
||||
if: ${{ inputs.reset }}
|
||||
run: npm run last-changed:reset
|
||||
run: pnpm run last-changed:reset
|
||||
@@ -0,0 +1,87 @@
|
||||
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:
|
||||
supa-mdx-lint:
|
||||
name: supa-mdx-lint
|
||||
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: 0be447eba738c3fd56698eff8ad01fa56c2391d7
|
||||
- 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 0be447eba738c3fd56698eff8ad01fa56c2391d7
|
||||
- name: install reviewdog
|
||||
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'
|
||||
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/" || 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'
|
||||
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
|
||||
}
|
||||
@@ -11,31 +11,36 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Change to apps/docs/spec directory and run make command
|
||||
working-directory: apps/docs/spec
|
||||
run: make download.api.v1 dereference.api.v1 generate.sections.api.v1 format
|
||||
- name: Install deps
|
||||
run: pnpm i
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'feat: update mgmt api docs'
|
||||
title: 'feat: update mgmt api docs'
|
||||
body: 'This PR updates mgmt api docs automatically.'
|
||||
branch: 'gha/auto-update-mgmt-api-docs'
|
||||
base: 'master'
|
||||
- name: Change to apps/docs/spec directory and run make command
|
||||
working-directory: apps/docs/spec
|
||||
run: make download.api.v1 dereference.api.v1 generate.sections.api.v1 format
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'feat: update mgmt api docs'
|
||||
title: 'feat: update mgmt api docs'
|
||||
body: 'This PR updates mgmt api docs automatically.'
|
||||
branch: 'gha/auto-update-mgmt-api-docs'
|
||||
base: 'master'
|
||||
@@ -0,0 +1,56 @@
|
||||
name: '[Docs] Sync troubleshooting guides to GitHub Discussions'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'apps/docs/content/troubleshooting/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-troubleshooting:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SEARCH_GITHUB_APP_ID: ${{ secrets.SEARCH_GITHUB_APP_ID }}
|
||||
SEARCH_GITHUB_APP_INSTALLATION_ID: ${{ secrets.SEARCH_GITHUB_APP_INSTALLATION_ID }}
|
||||
SEARCH_GITHUB_APP_PRIVATE_KEY: ${{ secrets.SEARCH_GITHUB_APP_PRIVATE_KEY }}
|
||||
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SEARCH_SUPABASE_URL }}
|
||||
SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
|
||||
- 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 dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Run Troubleshooting script
|
||||
run: pnpm run -F docs troubleshooting:sync
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
with:
|
||||
commit-message: '[bot] sync troubleshooting guides to db'
|
||||
title: '[bot] sync troubleshooting guides to db'
|
||||
author: 'github-docs-sync-bot <github-docs-sync-bot@supabase.com>'
|
||||
branch: 'bot/docs-sync-troubleshooting'
|
||||
branch-suffix: 'random'
|
||||
@@ -22,14 +22,19 @@ jobs:
|
||||
apps/docs
|
||||
packages
|
||||
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm --prefix="apps/docs" run test:smoke
|
||||
run: pnpm --prefix="apps/docs" run test:smoke
|
||||
@@ -23,14 +23,19 @@ jobs:
|
||||
examples
|
||||
packages
|
||||
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:docs
|
||||
run: pnpm run test:docs
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Comment on external PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment-on-external-pr:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const commentBody = `
|
||||
Thanks for contributing to Supabase! ❤️ Our team will review your PR.
|
||||
|
||||
A few tips for a smoother review process:
|
||||
- If you have a local version of the repo, run \`pnpm run format\` to make sure formatting checks pass.
|
||||
- Once we've reviewed your PR, please don't trivially merge master (don't click \`Update branch\` if there are no merge conflicts to be fixed). This invalidates any pre-merge checks we've run.
|
||||
`;
|
||||
github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: commentBody
|
||||
});
|
||||
@@ -11,15 +11,21 @@ jobs:
|
||||
name: runner / vale
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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: apps/docs/content
|
||||
# [Charis] Temporary because we still have to go through giant backlog of existing errors
|
||||
# Don't want people to worry about this (just want them looking at the specific PR comments)
|
||||
# fail_on_error: true
|
||||
files: '${{ steps.changed-files.outputs.CHANGED_FILES }}'
|
||||
separator: ','
|
||||
@@ -18,17 +18,21 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: apps
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps
|
||||
cache: 'pnpm'
|
||||
- name: Download dependencies
|
||||
run: |
|
||||
npm ci
|
||||
pnpm i
|
||||
- name: Run prettier
|
||||
run: |-
|
||||
npm run test:prettier
|
||||
pnpm run test:prettier
|
||||
|
||||
# i18n is not a node package, so we handle that one separately
|
||||
format-i18n:
|
||||
@@ -39,19 +43,21 @@ jobs:
|
||||
with:
|
||||
sparse-checkout: |
|
||||
i18n
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps
|
||||
cache: 'pnpm'
|
||||
- name: Download dependencies
|
||||
run: |
|
||||
rm package.json
|
||||
rm package-lock.json
|
||||
npm i prettier@4.0.0-alpha.8 prettier-plugin-sql-cst
|
||||
pnpm i
|
||||
- name: Run prettier
|
||||
run: |-
|
||||
npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'
|
||||
pnpm exec prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'
|
||||
|
||||
format-sql:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -62,13 +68,19 @@ jobs:
|
||||
sparse-checkout: |
|
||||
apps/docs/pages
|
||||
apps/docs/content
|
||||
# Installing all dependencies takes up to three minutes, hacking around to only installing prettier+deps
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
- name: Download dependencies
|
||||
run: |
|
||||
rm package.json
|
||||
rm package-lock.json
|
||||
npm i prettier@4.0.0-alpha.8 prettier-plugin-sql-cst
|
||||
pnpm i
|
||||
- name: Run prettier
|
||||
run: |-
|
||||
# Check mdx files which contain sql code blocks
|
||||
grep -lr '```sql' apps/docs/{pages,content}/**/*.mdx | xargs npx prettier -c
|
||||
grep -lr '```sql' apps/docs/{pages,content}/**/*.mdx | xargs pnpm exec prettier -c
|
||||
@@ -38,13 +38,18 @@ jobs:
|
||||
apps/www/.env.local.example
|
||||
supabase
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Download dependencies
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
# Need the miscellaneous use API, which is available publicly (by design) in www
|
||||
- name: Copy environment variables
|
||||
@@ -53,9 +58,9 @@ jobs:
|
||||
- name: Update embeddings
|
||||
working-directory: ./apps/docs
|
||||
if: ${{ !inputs.refresh }}
|
||||
run: npm run embeddings
|
||||
run: pnpm run embeddings
|
||||
|
||||
- name: Refresh embeddings
|
||||
working-directory: ./apps/docs
|
||||
if: ${{ inputs.refresh }}
|
||||
run: npm run embeddings:refresh
|
||||
run: pnpm run embeddings:refresh
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Self-Host Tests Smoke
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ['master']
|
||||
paths:
|
||||
- 'docker/**/*'
|
||||
|
||||
# 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: |
|
||||
docker/
|
||||
- name: Run docker-compose up
|
||||
# Ensure all services can be started and healthy with default config
|
||||
run: cd docker && cp .env.example .env && docker compose up --wait
|
||||
@@ -1,15 +1,17 @@
|
||||
name: Playwright Tests
|
||||
name: Studio E2E Tests
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'apps/studio/**'
|
||||
- 'package-lock.json'
|
||||
- 'tests/studio-tests/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'apps/studio/**'
|
||||
- 'package-lock.json'
|
||||
- 'tests/studio-tests/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
# Cancel old builds on new commit for same workflow + branch/PR
|
||||
concurrency:
|
||||
@@ -23,23 +25,29 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
- uses: supabase/setup-cli@v1
|
||||
with:
|
||||
version: latest
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
run: pnpm -C tests/studio-tests exec playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:playwright
|
||||
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: playwright-tests/playwright-report/
|
||||
path: tests/studio-tests/playwright-report/
|
||||
retention-days: 7
|
||||
@@ -8,12 +8,12 @@ on:
|
||||
branches: [master, studio]
|
||||
paths:
|
||||
- 'apps/studio/**'
|
||||
- 'package-lock.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
pull_request:
|
||||
branches: [master, studio]
|
||||
paths:
|
||||
- 'apps/studio/**'
|
||||
- 'package-lock.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
# Cancel old builds on new commit for same workflow + branch/PR
|
||||
concurrency:
|
||||
@@ -31,17 +31,21 @@ jobs:
|
||||
sparse-checkout: |
|
||||
apps/studio
|
||||
packages
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
working-directory: ./
|
||||
- name: Run Tests
|
||||
env:
|
||||
# Default is 2 GB, increase to have less frequent OOM errors
|
||||
NODE_OPTIONS: '--max_old_space_size=3072'
|
||||
run: npm run test:studio
|
||||
run: pnpm run test:studio
|
||||
working-directory: ./
|
||||
@@ -19,17 +19,22 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run TypeScript type check
|
||||
run: npx turbo run typecheck
|
||||
run: pnpm exec turbo run typecheck
|
||||
|
||||
- name: Run Lint
|
||||
run: npx turbo run lint
|
||||
run: pnpm exec turbo run lint
|
||||
@@ -21,14 +21,19 @@ jobs:
|
||||
sparse-checkout: |
|
||||
packages
|
||||
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:ui-patterns
|
||||
run: pnpm run test:ui-patterns
|
||||
@@ -21,14 +21,19 @@ jobs:
|
||||
sparse-checkout: |
|
||||
packages
|
||||
|
||||
- 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: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test:ui
|
||||
run: pnpm run test:ui
|
||||
+5
-3
@@ -7,12 +7,14 @@ apps/**/out
|
||||
# prettier-plugin-sql-cst only supports sqlite syntax
|
||||
**/supabase/migrations/*.sql
|
||||
apps/www/schema.sql
|
||||
apps/www/public/images/*
|
||||
apps/docs/**/generated/*
|
||||
apps/docs/examples
|
||||
examples/slack-clone/nextjs-slack-clone/full-schema.sql
|
||||
# ignore files with custom js formatting
|
||||
apps/docs/pages/guides/auth/*.mdx
|
||||
apps/docs/pages/guides/integrations/*.mdx
|
||||
apps/studio/public
|
||||
apps/**/.turbo
|
||||
apps/docs/CONTRIBUTING.md
|
||||
apps/design-system/__registry__
|
||||
apps/design-system/__registry__
|
||||
packages/icons/__registry__
|
||||
packages/icons/src/icons/*.ts
|
||||
@@ -6,7 +6,6 @@
|
||||
"printWidth": 100,
|
||||
"endOfLine": "lf",
|
||||
"sqlKeywordCase": "lower",
|
||||
"pluginSearchDirs": false,
|
||||
"plugins": ["prettier-plugin-sql-cst"],
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
+8
-11
@@ -38,7 +38,8 @@ You will need to install and configure the following dependencies on your machin
|
||||
|
||||
- [Git](http://git-scm.com/)
|
||||
- [Node.js v20.x (LTS)](http://nodejs.org)
|
||||
- [npm](https://www.npmjs.com/) version 10.x.x or higher
|
||||
- [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)
|
||||
|
||||
## Local development
|
||||
@@ -69,7 +70,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab
|
||||
1. Install the dependencies in the root of the repo.
|
||||
|
||||
```sh
|
||||
npm install # install dependencies
|
||||
pnpm install # install dependencies
|
||||
```
|
||||
|
||||
2. Copy the example `.env.local.example` to `.env.local`
|
||||
@@ -80,7 +81,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab
|
||||
|
||||
3. After that you can run the apps simultaneously with the following.
|
||||
```sh
|
||||
npm run dev # start all the applications
|
||||
pnpm dev # start all the applications
|
||||
```
|
||||
|
||||
Then visit, and edit, any of the following sites:
|
||||
@@ -96,7 +97,7 @@ Then visit, and edit, any of the following sites:
|
||||
You can run any of the sites individually by using the scope name. For example:
|
||||
|
||||
```sh
|
||||
npm run dev:www
|
||||
pnpm dev:www
|
||||
```
|
||||
|
||||
Note: Particularly for `www` make sure you have copied `apps/www/.env.local.example` to `apps/www/.env.local`
|
||||
@@ -114,17 +115,13 @@ The monorepo has a set of shared components under `/packages`:
|
||||
|
||||
#### Installing packages
|
||||
|
||||
Installing a package with NPM workspaces requires you to add the `-w` flag to tell NPM which workspace you want to install into. Do not install dependencies in their local folder, install them from the route using the `-w` flag.
|
||||
|
||||
The format is: `npm install <package name> -w=<workspace to install in>`.
|
||||
Installing a package in a specific workspace requires you to move to the workspace and then run the install command.
|
||||
|
||||
For example:
|
||||
|
||||
- `npm install react -w common`: installs into `./packages/common`
|
||||
- `npm install react -w www`: installs into `./apps/www`
|
||||
- `npm install react -w studio`: installs into `./apps/studio`
|
||||
1. `cd apps/studio`: move to the `studio` workspace
|
||||
2. `pnpm add react`: installs `react` into `studio` workspace
|
||||
|
||||
You do not need to install `devDependencies` in each workspace. These can all be installed in the root package.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
- [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
|
||||
- [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.
|
||||
|
||||
|
||||
@@ -654,6 +654,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"dialog-centered-off": {
|
||||
name: "dialog-centered-off",
|
||||
type: "components:example",
|
||||
registryDependencies: ["dialog","button"],
|
||||
component: React.lazy(() => import("@/registry/default/example/dialog-centered-off")),
|
||||
source: "",
|
||||
files: ["registry/default/example/dialog-centered-off.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"drawer-demo": {
|
||||
name: "drawer-demo",
|
||||
type: "components:example",
|
||||
@@ -1787,6 +1798,72 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-demo": {
|
||||
name: "inner-side-menu-demo",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-demo")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-demo.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-static-titles": {
|
||||
name: "inner-side-menu-static-titles",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-static-titles")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-static-titles.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-loading": {
|
||||
name: "inner-side-menu-loading",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-loading")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-loading.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-multiple-sections": {
|
||||
name: "inner-side-menu-multiple-sections",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-multiple-sections")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-multiple-sections.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-empty": {
|
||||
name: "inner-side-menu-empty",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-empty")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-empty.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-with-search": {
|
||||
name: "inner-side-menu-with-search",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-with-search")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-with-search.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-demo": {
|
||||
name: "multi-select-demo",
|
||||
type: "components:example",
|
||||
@@ -1798,13 +1875,79 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-form": {
|
||||
name: "multi-select-form",
|
||||
"multi-select-disabled": {
|
||||
name: "multi-select-disabled",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-form")),
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-disabled")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-form.tsx"],
|
||||
files: ["registry/default/example/multi-select-disabled.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-badge-limit-wrap": {
|
||||
name: "multi-select-badge-limit-wrap",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit-wrap")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-badge-limit-wrap.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-badge-limit": {
|
||||
name: "multi-select-badge-limit",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-badge-limit.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-combobox": {
|
||||
name: "multi-select-combobox",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-combobox")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-combobox.tsx"],
|
||||
category: "undefined",
|
||||
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",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-deletable-badge")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-deletable-badge.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-inline-search-input": {
|
||||
name: "multi-select-inline-search-input",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-inline-search-input")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-inline-search-input.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
@@ -2029,6 +2172,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"logs-bar-chart": {
|
||||
name: "logs-bar-chart",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/logs-bar-chart")),
|
||||
source: "",
|
||||
files: ["registry/default/example/logs-bar-chart.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"chart-area-axes": {
|
||||
name: "chart-area-axes",
|
||||
type: "components:block",
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -102,6 +102,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/info-tooltip',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Inner Side Menu',
|
||||
href: '/docs/fragments/inner-side-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Form Item Layout',
|
||||
href: '/docs/fragments/form-item-layout',
|
||||
@@ -112,6 +117,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/multi-select',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Logs Bar Chart',
|
||||
href: '/docs/fragments/logs-bar-chart',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -339,6 +349,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/components/slider',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Sidebar',
|
||||
href: '/docs/components/sidebar',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Sonner',
|
||||
href: '/docs/components/sonner',
|
||||
|
||||
@@ -4,6 +4,14 @@ description: Latest updates and announcements.
|
||||
toc: false
|
||||
---
|
||||
|
||||
## 18th Oct 2024 - Update multi-select
|
||||
|
||||
Improved Multi Select with combobox, badge limit, and more examples.
|
||||
|
||||
- [Multi Select](/design-system/components/multi-select)
|
||||
|
||||
[PR](https://github.com/supabase/supabase/pull/29695)
|
||||
|
||||
## 12th June 2024 - Button styles updated
|
||||
|
||||
- [`<Button />`](/design-system/docs/components/button) styles updated
|
||||
|
||||
@@ -13,44 +13,6 @@ source:
|
||||
|
||||
<ComponentPreview name="dialog-demo" peekCode wide />
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="cli">CLI</TabsTrigger>
|
||||
<TabsTrigger value="manual">Manual</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="cli">
|
||||
|
||||
```bash
|
||||
npx shadcn-ui@latest add dialog
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="manual">
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Install the following dependencies:</Step>
|
||||
|
||||
```bash
|
||||
npm install @radix-ui/react-dialog
|
||||
```
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="dialog" />
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
@@ -85,6 +47,23 @@ import {
|
||||
|
||||
<ComponentPreview name="dialog-close-button" />
|
||||
|
||||
### Centered behaviour
|
||||
|
||||
You can control whether the dialog is centered by passing `centered={false}` to the `DialogContent` component.
|
||||
|
||||
```tsx {3}
|
||||
<Dialog>
|
||||
<ContextMenuTrigger>Click here</ContextMenuTrigger>
|
||||
<DialogContent centered={false}>
|
||||
{/*
|
||||
* Content in here
|
||||
*/}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
<ComponentPreview name="dialog-centered-off" />
|
||||
|
||||
## Notes
|
||||
|
||||
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or
|
||||
@@ -93,7 +72,7 @@ To activate the `Dialog` component from within a `Context Menu` or `Dropdown Men
|
||||
```tsx {14-25}
|
||||
<Dialog>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>Right click</ContextMenuTrigger>
|
||||
<ContextMenuTrigger>Dialog not centered</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem>Open</ContextMenuItem>
|
||||
<ContextMenuItem>Download</ContextMenuItem>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Inner Side Menu
|
||||
description: InnerSideMenu is a component that provides a collapsible side menu with multiple sections.
|
||||
component: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="inner-side-menu-demo" peekCode wide />
|
||||
|
||||
## Examples
|
||||
|
||||
{/* ### Multiple sections */}
|
||||
|
||||
{/* <ComponentPreview name="inner-side-menu-multiple-sections" peekCode /> */}
|
||||
|
||||
### Collapsible menus
|
||||
|
||||
Inner Side Menu uses the `<InnerSideMenuCollapsible />` component to wrap the menu items.
|
||||
This is a wrapper component around the `<Collapsible />` component.
|
||||
|
||||
```jsx {10-12,16-17}
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
function app() {
|
||||
return (
|
||||
<InnerSideMenuCollapsible key={category} defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title={category} />
|
||||
<InnerSideMenuCollapsibleContent>
|
||||
<InnerSideMenuItem href="/dashboard">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/team">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/settings">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Static titles
|
||||
|
||||
If you don't need the Collapsible features, you can instead use `<InnerSideBarTitle />`.
|
||||
This doesn't require the use of the `<InnerSideMenuCollapsible />` parent component wrapped around it.
|
||||
|
||||
You can wrap the `<InnerSideBarItem />` conponents however you like. Below we've put a `<div/ >` around them.
|
||||
|
||||
```jsx {1}
|
||||
<InnerSideBarTitle>Projects</InnerSideBarTitle>
|
||||
<div className="mt-2">
|
||||
<InnerSideMenuItem href="/dashboard">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/team">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/settings">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
```
|
||||
|
||||
<ComponentPreview name="inner-side-menu-static-titles" />
|
||||
|
||||
### Loading state
|
||||
|
||||
Use `<InnerSideMenuItemLoading />` to show a loading state for menu items.
|
||||
|
||||
This item is a wrapper around the `<Skeleton />` component, and also includes some y-padding so the items don't layout shift when shifting to loaded state and to look visually separated in a list.
|
||||
|
||||
<ComponentPreview name="inner-side-menu-loading" />
|
||||
|
||||
### With search
|
||||
|
||||
Use the following components to add a search input to the menu.
|
||||
|
||||
```jsx
|
||||
<InnerSideBarFilters>
|
||||
<InnerSideBarFilterSearchInput
|
||||
name="search-input"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
aria-labelledby="Search items"
|
||||
/>
|
||||
</InnerSideBarFilters>
|
||||
```
|
||||
|
||||
There is also a Filter Dropdown component that can be used to sort the menu items.
|
||||
This can be used inside `<InnerSideBarFilterSearchInput />`.
|
||||
|
||||
```jsx {9-16}
|
||||
<InnerSideBarFilters>
|
||||
<InnerSideBarFilterSearchInput
|
||||
name="search-input"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
aria-labelledby="Search items"
|
||||
>
|
||||
<InnerSideBarFilterSortDropdown value={sort} onValueChange={(value) => setSort(value)}>
|
||||
<InnerSideBarFilterSortDropdownItem value="alphabetical">
|
||||
Sort Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
<InnerSideBarFilterSortDropdownItem value="reverse">
|
||||
Sort Reverse Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
</InnerSideBarFilterSortDropdown>
|
||||
</InnerSideBarFilterSearchInput>
|
||||
</InnerSideBarFilters>
|
||||
```
|
||||
|
||||
<ComponentPreview name="inner-side-menu-with-search" />
|
||||
|
||||
### Empty list state
|
||||
|
||||
Use `<InnerSideMenuEmptyState />` to show an empty state.
|
||||
|
||||
Use the `actions` prop to add any actions like a `<Button />`.
|
||||
|
||||
```jsx {4-7}
|
||||
<InnerSideBarEmptyPanel
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
actions={
|
||||
<Button type="default" onClick={createAction}>
|
||||
Create Function
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
You can also use the prop `illustration` to pass in a custom illustration, it accets any React Node.
|
||||
|
||||
```jsx {4-7}
|
||||
<InnerSideBarEmptyPanel
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
illustration={
|
||||
<figure>
|
||||
<svg>../</svg>
|
||||
</figure>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
<ComponentPreview name="inner-side-menu-empty" />
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Logs Bar Chart
|
||||
description: A stacked bar chart that displays logs errors and successes.
|
||||
fragment: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="logs-bar-chart" peekCode wide />
|
||||
|
||||
## Usage
|
||||
|
||||
This component is used to display the bar chart in logs pages.
|
||||
|
||||
It requires that the `data` prop is an array of objects with the following shape:
|
||||
|
||||
```ts
|
||||
type LogsBarChartDatum = {
|
||||
timestamp: string
|
||||
ok_count: number
|
||||
error_count: number
|
||||
}
|
||||
```
|
||||
@@ -12,31 +12,69 @@ fragment: true
|
||||
```tsx
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
} from 'ui-patterns/multi-select'
|
||||
```
|
||||
|
||||
```tsx
|
||||
<MultiSelector values={value} onValuesChange={setValue}>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select items" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="1">Item 1</MultiSelectorItem>
|
||||
<MultiSelectorItem value="2">Item 2</MultiSelectorItem>
|
||||
<MultiSelectorItem value="3">Item 3</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
<MultiSelector
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
selected={selected}
|
||||
onSelectedChange={onSelectedChange}
|
||||
>
|
||||
<MultiSelectorTrigger className="w-72" label="Fruits" />
|
||||
<MultiSelectorContent sameWidthAsTrigger>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Form
|
||||
### Disabled
|
||||
|
||||
<ComponentPreview name="multi-select-form" />
|
||||
<ComponentPreview name="multi-select-disabled" />
|
||||
|
||||
### Badge Combo Box
|
||||
|
||||
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"`.
|
||||
`badgeLimit` prop on the `MultiSelectorTrigger` component can be used to limit the number of badges displayed.
|
||||
|
||||
<ComponentPreview name="multi-select-badge-limit" />
|
||||
|
||||
### Badge Limit="wrap"
|
||||
|
||||
`badgeLimit` prop can also be "wrap" to wrap the badges to the next line.
|
||||
|
||||
<ComponentPreview name="multi-select-badge-limit-wrap" />
|
||||
|
||||
### Deletable Badge
|
||||
|
||||
Add the `deletableBadge` prop on the `MultiSelectorTrigger` component to enable badge removal directly from the badge.
|
||||
|
||||
<ComponentPreview name="multi-select-deletable-badge" />
|
||||
|
||||
### Inline search input
|
||||
|
||||
Use `mode="inline-combobox"` on the `MultiSelectorTrigger` component to add an inline search input.
|
||||
|
||||
<ComponentPreview name="multi-select-inline-search-input" />
|
||||
@@ -186,9 +186,3 @@ text-border-control
|
||||
| `500` |
|
||||
| `DEFAULT` |
|
||||
| `600` |
|
||||
|
||||
## Storybook
|
||||
|
||||
The current colors are also auto documented in Storybook
|
||||
|
||||
[Storybook Colors](https://ui-storybook-pre-release.vercel.app/)
|
||||
@@ -4,25 +4,26 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "next dev --port 3003",
|
||||
"build": "npm run content:build && npm run build:registry && next build",
|
||||
"build": "pnpm run content:build && pnpm run build:registry && next build",
|
||||
"build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"content:build": "contentlayer2 build",
|
||||
"clean": "rimraf node_modules .next .turbo",
|
||||
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"contentlayer2": "0.4.6",
|
||||
"eslint-config-supabase": "*",
|
||||
"fs": "^0.0.1-security",
|
||||
"eslint-config-supabase": "workspace:*",
|
||||
"icons": "workspace:*",
|
||||
"jotai": "^2.8.0",
|
||||
"lodash.template": "4.5.0",
|
||||
"lucide-react": "*",
|
||||
"markdown-wasm": "^1.2.0",
|
||||
"next": "^14.2.10",
|
||||
"next": "catalog:",
|
||||
"next-contentlayer2": "0.4.6",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -33,32 +34,32 @@
|
||||
"react-wrap-balancer": "^1.1.0",
|
||||
"recharts": "^2.12.7",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-pretty-code": "^0.6.0",
|
||||
"rehype-pretty-code": "^0.9.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark": "^14.0.3",
|
||||
"remark-code-import": "^1.2.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sonner": "^1.5.0",
|
||||
"ts-morph": "^22.0.0",
|
||||
"ui": "*",
|
||||
"ui-patterns": "*",
|
||||
"ui": "workspace:*",
|
||||
"ui-patterns": "workspace:*",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shikijs/compat": "^1.1.7",
|
||||
"@types/lodash.template": "4.5.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18.2.65",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"config": "*",
|
||||
"config": "workspace:^",
|
||||
"mdast-util-toc": "^6.1.1",
|
||||
"postcss": "^8",
|
||||
"rimraf": "^4.1.3",
|
||||
"shiki": "^1.1.7",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"tsconfig": "*",
|
||||
"tsconfig": "workspace:*",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "~5.5.0",
|
||||
"unist-builder": "3.0.0"
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Button, DialogSection, DialogSectionSeparator } from 'ui'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from 'ui'
|
||||
import { Input_Shadcn_ } from 'ui'
|
||||
import { Label_Shadcn_ } from 'ui'
|
||||
|
||||
export default function DialogDemo() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="default">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]" centered={false}>
|
||||
<DialogHeader padding={'small'}>
|
||||
<DialogTitle>This dialog is not centered.</DialogTitle>
|
||||
<DialogDescription>This dialog is not centered.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection className="space-y-4" padding={'small'}>
|
||||
<div>
|
||||
<Label_Shadcn_ htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_ id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div>
|
||||
<Label_Shadcn_ htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_ id="username" defaultValue="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</DialogSection>
|
||||
<DialogFooter padding={'small'}>
|
||||
<Button htmlType="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuDemo() {
|
||||
return (
|
||||
<Card className="min-w-60 bg-dash-sidebar py-4 flex flex-col gap-6 h-full">
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Projects">Hello</InnerSideMenuCollapsibleTrigger>
|
||||
<InnerSideMenuCollapsibleContent className="mt-2">
|
||||
<InnerSideMenuItem href="/">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Projects">Hello</InnerSideMenuCollapsibleTrigger>
|
||||
<InnerSideMenuCollapsibleContent className="mt-2">
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Projects">Hello</InnerSideMenuCollapsibleTrigger>
|
||||
<InnerSideMenuCollapsibleContent className="mt-2">
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideBarShimmeringLoaders,
|
||||
InnerSideBarEmptyPanel,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
import { Button, Card } from 'ui'
|
||||
import { Heart, Pointer } from 'lucide-react'
|
||||
|
||||
export default function InnerSideMenuEmpty() {
|
||||
const [isLoading, setIsLoading] = React.useState(true)
|
||||
const [hasItems, setHasItems] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
// Simulate loading
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 2000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card className="w-64 py-4 flex flex-col gap-4 bg-dash-sidebar">
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideBarEmptyPanel
|
||||
className="mx-2"
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
illustration={<div className="text-4xl">🚀</div>}
|
||||
actions={
|
||||
<Button type="default" onClick={() => setHasItems(true)}>
|
||||
Create Function
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideBarEmptyPanel
|
||||
className="mx-2"
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
illustration={
|
||||
<figure className="relative mb-3">
|
||||
<div className="h-6 w-6 bg-surface-100 border rounded-md flex items-center justify-center">
|
||||
<Heart className="text-light" size={13} />
|
||||
</div>
|
||||
<Pointer
|
||||
className="absolute -right-[6px] -bottom-2 text-lighter"
|
||||
strokeWidth={1.5}
|
||||
size={16}
|
||||
/>
|
||||
</figure>
|
||||
}
|
||||
actions={
|
||||
<Button type="default" onClick={() => setHasItems(true)}>
|
||||
Create Function
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuItemLoading,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuLoading() {
|
||||
return (
|
||||
<Card className="w-64 py-4 flex flex-col gap-4 bg-dash-sidebar">
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuMultipleSections() {
|
||||
return (
|
||||
<div className="w-64">
|
||||
<InnerSideMenuCollapsible>
|
||||
<InnerSideMenuCollapsibleTrigger title="Development" />
|
||||
<InnerSideMenuCollapsibleContent>
|
||||
<InnerSideMenuItem href="#" isActive>
|
||||
API
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="#">Database</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible>
|
||||
<InnerSideMenuCollapsibleTrigger title="Analytics" />
|
||||
<InnerSideMenuCollapsibleContent>
|
||||
<InnerSideMenuItem href="#">Reports</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="#">Usage</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
InnerSideBarTitle,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuBasic() {
|
||||
return (
|
||||
<Card className="min-w-60 bg-dash-sidebar py-4 flex flex-col gap-6 h-full">
|
||||
<div className="px-2">
|
||||
<InnerSideBarTitle className="mb-2">Projects</InnerSideBarTitle>
|
||||
<InnerSideMenuItem href="/">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
<InnerSideMenuSeparator />
|
||||
<div className="px-2">
|
||||
<InnerSideBarTitle className="mb-2">Projects</InnerSideBarTitle>
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<InnerSideBarTitle className="mb-2">Projects</InnerSideBarTitle>
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideBarFilters,
|
||||
InnerSideBarFilterSearchInput,
|
||||
InnerSideBarFilterSortDropdown,
|
||||
InnerSideBarFilterSortDropdownItem,
|
||||
InnerSideBarEmptyPanel,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuWithSearch() {
|
||||
const [sort, setSort] = useState('alphabetical')
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
|
||||
const foodCategories = {
|
||||
Fruits: ['Apple', 'Banana', 'Orange', 'Strawberry'],
|
||||
Vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Tomato'],
|
||||
Grains: ['Rice', 'Wheat', 'Oats', 'Quinoa'],
|
||||
Proteins: ['Chicken', 'Beef', 'Fish', 'Tofu'],
|
||||
}
|
||||
|
||||
const filteredAndSortedCategories = useMemo(() => {
|
||||
return Object.entries(foodCategories).reduce(
|
||||
(acc, [category, items]) => {
|
||||
let filteredItems = items.filter((item) =>
|
||||
item.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
|
||||
// Sort the filtered items
|
||||
filteredItems.sort((a, b) => {
|
||||
if (sort === 'alphabetical') {
|
||||
return a.localeCompare(b)
|
||||
} else {
|
||||
return b.localeCompare(a)
|
||||
}
|
||||
})
|
||||
|
||||
acc[category] = filteredItems
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, string[]>
|
||||
)
|
||||
}, [foodCategories, searchTerm, sort])
|
||||
|
||||
return (
|
||||
<Card className="w-64 py-4 flex flex-col gap-4 bg-dash-sidebar">
|
||||
<InnerSideBarFilters>
|
||||
<InnerSideBarFilterSearchInput
|
||||
name="search-input"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
aria-labelledby="Search items"
|
||||
>
|
||||
<InnerSideBarFilterSortDropdown value={sort} onValueChange={(value) => setSort(value)}>
|
||||
<InnerSideBarFilterSortDropdownItem value="alphabetical">
|
||||
Sort Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
<InnerSideBarFilterSortDropdownItem value="reverse">
|
||||
Sort Reverse Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
</InnerSideBarFilterSortDropdown>
|
||||
</InnerSideBarFilterSearchInput>
|
||||
</InnerSideBarFilters>
|
||||
|
||||
{Object.entries(filteredAndSortedCategories).map(([category, items]) => (
|
||||
<InnerSideMenuCollapsible key={category} defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title={category} />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
{items.length > 0 ? (
|
||||
items.map((item) => (
|
||||
<InnerSideMenuItem key={item} href="#">
|
||||
{item}
|
||||
</InnerSideMenuItem>
|
||||
))
|
||||
) : (
|
||||
<InnerSideBarEmptyPanel
|
||||
className="mx-2"
|
||||
title={`No ${category.toLowerCase()} found`}
|
||||
description={`Your search did not return any results in ${category}`}
|
||||
/>
|
||||
)}
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
export default function LogsBarChartDemo() {
|
||||
const data = Array.from({ length: 100 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart
|
||||
|
||||
return {
|
||||
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()
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<LogsBarChart data={data} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([
|
||||
'Apple',
|
||||
'Banana',
|
||||
'Cherry',
|
||||
'Date',
|
||||
'Elderberrie',
|
||||
])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
persistLabel
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
deletableBadge={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Minus, Plus } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button } from 'ui'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(['Apple', 'Banana', 'Cherry'])
|
||||
const [limit, setLimit] = useState(2)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="tiny" type="default" onClick={() => setLimit(limit - 1)} disabled={limit < 1}>
|
||||
<Minus size={12} />
|
||||
</Button>
|
||||
<span className="text-sm text-foreground/90 peer-checked:line-through font-semibold hover:cursor-pointer">
|
||||
Limit: {limit}
|
||||
</span>
|
||||
<Button size="tiny" type="default" onClick={() => setLimit(limit + 1)}>
|
||||
<Plus size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" badgeLimit={limit} />
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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,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>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(['Apple', 'Banana'])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
deletableBadge
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -2,25 +2,42 @@ import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [value, setValue] = useState<string[]>([])
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([])
|
||||
|
||||
const fruits = [
|
||||
{ value: 'Apple', isDisabled: false },
|
||||
{ value: 'Banana', isDisabled: false },
|
||||
{ value: 'Cherry', isDisabled: false },
|
||||
{ value: 'Date', isDisabled: false },
|
||||
{ value: 'Elderberrie', isDisabled: false },
|
||||
{ value: 'Fig', isDisabled: false },
|
||||
{ value: 'Grape', isDisabled: false },
|
||||
{ value: 'Kiwi', isDisabled: true },
|
||||
{ value: 'Mango', isDisabled: false },
|
||||
{ value: 'Strawberry', isDisabled: false },
|
||||
]
|
||||
|
||||
return (
|
||||
<MultiSelector values={value} onValuesChange={setValue} size="small">
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select items" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="1">Item 1</MultiSelectorItem>
|
||||
<MultiSelectorItem value="2">Item 2</MultiSelectorItem>
|
||||
<MultiSelectorItem value="3">Item 3</MultiSelectorItem>
|
||||
{fruits.map(({ value, isDisabled }) => (
|
||||
<MultiSelectorItem key={value} value={value} disabled={isDisabled}>
|
||||
{value}
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues} disabled={true}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" />
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import Image from 'next/image'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import z from 'zod'
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form_Shadcn_,
|
||||
FormDescription_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormItem_Shadcn_,
|
||||
FormLabel_Shadcn_,
|
||||
FormMessage_Shadcn_,
|
||||
} from 'ui'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
const form = z.object({
|
||||
value: z.array(z.string()).nonempty('Please select at least one person'),
|
||||
})
|
||||
|
||||
type Form = z.infer<typeof form>
|
||||
|
||||
const users = [
|
||||
{
|
||||
name: 'ThePrimeagen',
|
||||
picture: 'https://pbs.twimg.com/profile_images/1759330620160049152/2i_wkOoK_400x400.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Shadcn',
|
||||
picture: 'https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Theo',
|
||||
picture: 'https://pbs.twimg.com/profile_images/1605762947686375425/lsoGWWty_400x400.jpg',
|
||||
},
|
||||
]
|
||||
|
||||
const MultiSelectZod = () => {
|
||||
const multiForm = useForm<Form>({
|
||||
resolver: zodResolver(form),
|
||||
defaultValues: form.parse({ value: [users[0].name] }),
|
||||
})
|
||||
|
||||
const onSubmit = (data: Form) => {
|
||||
toast('Form submitted ', { description: JSON.stringify(data, null, 2) })
|
||||
}
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...multiForm}>
|
||||
<form onSubmit={multiForm.handleSubmit(onSubmit)} className="space-y-3 grid gap-3 w-full">
|
||||
<FormField_Shadcn_
|
||||
control={multiForm.control}
|
||||
name="value"
|
||||
render={({ field }) => (
|
||||
<FormItem_Shadcn_ className="w-full">
|
||||
<FormLabel_Shadcn_>Invite people</FormLabel_Shadcn_>
|
||||
<MultiSelector onValuesChange={field.onChange} values={field.value} size={'small'}>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select people to invite" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
{users.map((user) => (
|
||||
<MultiSelectorItem key={user.name} value={user.name}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Image
|
||||
src={user.picture}
|
||||
alt={user.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
<span>{user.name}</span>
|
||||
</div>
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
<FormDescription_Shadcn_>
|
||||
Select people to invite to this event
|
||||
</FormDescription_Shadcn_>
|
||||
<FormMessage_Shadcn_ />
|
||||
</FormItem_Shadcn_>
|
||||
)}
|
||||
/>
|
||||
<Button type="secondary" size="medium" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export default MultiSelectZod
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(['Apple'])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
mode="inline-combobox"
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
deletableBadge
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -97,7 +97,6 @@ export default function TreeViewDemo() {
|
||||
name={element.name}
|
||||
isEditing={element.metadata?.isEditing === true}
|
||||
onEditSubmit={(value) => {
|
||||
console.log('value', value)
|
||||
let updatedTreeData = { ...treeData }
|
||||
const findNode = (node: any) => {
|
||||
if (node.id === element.id) {
|
||||
|
||||
@@ -431,6 +431,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['dialog', 'button'],
|
||||
files: ['example/dialog-close-button.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'dialog-centered-off',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['dialog', 'button'],
|
||||
files: ['example/dialog-centered-off.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'drawer-demo',
|
||||
type: 'components:example',
|
||||
@@ -1030,15 +1036,75 @@ export const examples: Registry = [
|
||||
type: 'components:example',
|
||||
files: ['example/info-tooltip-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-static-titles',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-static-titles.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-loading',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-loading.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-multiple-sections',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-multiple-sections.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-empty',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-empty.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-with-search',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-with-search.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-form',
|
||||
name: 'multi-select-disabled',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-form.tsx'],
|
||||
files: ['example/multi-select-disabled.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-badge-limit-wrap',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-badge-limit-wrap.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-badge-limit',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-badge-limit.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-combobox',
|
||||
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',
|
||||
files: ['example/multi-select-deletable-badge.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-inline-search-input',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-inline-search-input.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'tree-view-demo',
|
||||
@@ -1140,4 +1206,9 @@ export const examples: Registry = [
|
||||
type: 'docs:example',
|
||||
files: ['example/expanding-textarea-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'logs-bar-chart',
|
||||
type: 'components:example',
|
||||
files: ['example/logs-bar-chart.tsx'],
|
||||
},
|
||||
]
|
||||
@@ -9,6 +9,7 @@ module.exports = config({
|
||||
//
|
||||
'./../../packages/ui/src/**/*.{tsx,ts,js}',
|
||||
'./../../packages/ui-patterns/**/*.{tsx,ts,js}',
|
||||
'!./../../packages/ui-patterns/node_modules/**/*', // ignore node_modules rule
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# IMPORTANT: This file is checked into version control.
|
||||
# Only publicly available environment variables should be listed here.
|
||||
# Do not add secrets here. Use .env.local instead.
|
||||
|
||||
NEXT_PUBLIC_MISC_URL="https://obuldanrptloktxcffvn.supabase.co"
|
||||
NEXT_PUBLIC_MISC_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTg2MTQ2ODUsImV4cCI6MjAzNDE5MDY4NX0.NFt49g6DFkc1X5khCzN5p01iAVo2TMxlx88cY1V0E2M"
|
||||
@@ -26,3 +26,6 @@ yarn-error.log*
|
||||
|
||||
# Sitemap
|
||||
public/sitemap.xml
|
||||
|
||||
# Copied examples folder
|
||||
/examples/
|
||||
@@ -77,7 +77,7 @@ You can usually identify a federated or reference doc because it uses a Next.js
|
||||
Example spec file import:
|
||||
|
||||
```js
|
||||
import specFile from '~/spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' }
|
||||
import specFile from '~/spec/transforms/analytics_v0_openapi_deparsed.json' with { type: 'json' }
|
||||
```
|
||||
|
||||
Example repo definition:
|
||||
|
||||
@@ -14,7 +14,7 @@ For a complete run-down on how all of our tools work together, see the main DEVE
|
||||
|
||||
## Local setup
|
||||
|
||||
[supabase.com/docs](https://supabase.com/docs) is a Next.JS site. You can get setup by following the same steps for all of our other Next.JS projects:
|
||||
[supabase.com/docs](https://supabase.com/docs) is a Next.js site. You can get setup by following the same steps for all of our other Next.js projects:
|
||||
|
||||
1. Follow the steps outlined in the Local Development section of the main [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
|
||||
2. If you work at Supabase, run `dev:secrets:pull` to pull down the internal environment variables. If you're a community member, create a `.env` file and add this line to it: `NEXT_PUBLIC_IS_PLATFORM=false`
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ We use documentation specifications which can be used to generate human-readable
|
||||
- CLISpec (custom to Supabase): for CLI commands and usage.
|
||||
|
||||
The benefit of using custom specifications is that we can generate many other types from a strict schema (eg, HTML and manpages).
|
||||
It also means that we can switch to any documentation system we want. On this site we use Next.JS, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool.
|
||||
It also means that we can switch to any documentation system we want. On this site we use Next.js, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import OpenAI from 'openai'
|
||||
export const runtime = 'edge'
|
||||
/* To avoid OpenAI errors, restrict to the Vercel Edge Function regions that
|
||||
overlap with the OpenAI API regions.
|
||||
|
||||
|
||||
Reference for Vercel regions: https://vercel.com/docs/edge-network/regions#region-list
|
||||
Reference for OpenAI regions: https://help.openai.com/en/articles/5347006-openai-api-supported-countries-and-territories
|
||||
*/
|
||||
|
||||
@@ -167,7 +167,7 @@ const PI = 3.14
|
||||
```
|
||||
````
|
||||
|
||||
Of, you can use the `<$CodeSample />` component to include code samples from a source code file.
|
||||
Or, you can use the `<$CodeSample />` component to include code samples from a source code file.
|
||||
|
||||
If the file is within the `supabase/supabase` repo's `examples` directory:
|
||||
|
||||
@@ -183,7 +183,7 @@ meta="display/path.js"
|
||||
/>
|
||||
```
|
||||
|
||||
If the file is within some other GitHub repo (note that the repo must be public):
|
||||
If the file is within some other GitHub repo:
|
||||
|
||||
```mdx
|
||||
<$CodeSample
|
||||
@@ -197,6 +197,8 @@ meta="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).
|
||||
|
||||
### Icons
|
||||
|
||||
The following icons are available. They can be styled with [Tailwind](https://tailwindcss.com/) classes:
|
||||
|
||||
@@ -12,7 +12,7 @@ export default async function ContributingPage() {
|
||||
const content = await readFile(contentFile, 'utf-8')
|
||||
|
||||
return (
|
||||
<SidebarSkeleton>
|
||||
<SidebarSkeleton hideSideNav>
|
||||
<div className="px-8 py-16">
|
||||
<article className="prose mx-auto">
|
||||
<MDXProviderGuides>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
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,16 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { type PropsWithChildren } from 'react'
|
||||
|
||||
import { getMenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu.utils'
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
const GuidesLayout = ({ children }: PropsWithChildren) => {
|
||||
const pathname = usePathname()
|
||||
const menuId = getMenuId(pathname)
|
||||
|
||||
return <Layout menuId={menuId}>{children}</Layout>
|
||||
}
|
||||
|
||||
export default GuidesLayout
|
||||
@@ -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>
|
||||
}
|
||||
+6
-3
@@ -1,15 +1,17 @@
|
||||
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import type { SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { relative } from 'path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
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'
|
||||
@@ -84,7 +86,8 @@ const getContent = async ({ slug }: Params) => {
|
||||
`https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
|
||||
)
|
||||
|
||||
const content = await response.text()
|
||||
let content = await response.text()
|
||||
content = removeRedundantH1(content)
|
||||
|
||||
return {
|
||||
pathname: `/guides/ai/python/${slug}` satisfies `/${string}`,
|
||||
@@ -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.
+8
-21
@@ -1,14 +1,15 @@
|
||||
import matter from 'gray-matter'
|
||||
import { type Heading } from 'mdast'
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { toMarkdown } from 'mdast-util-to-markdown'
|
||||
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join, relative } from 'node:path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import emoji from 'remark-emoji'
|
||||
|
||||
import { genGuideMeta, genGuidesStaticParams } from '~/features/docs/GuidesMdx.utils'
|
||||
import {
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
removeRedundantH1,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { GUIDES_DIRECTORY, isValidGuideFrontmatter } from '~/lib/docs'
|
||||
@@ -16,7 +17,8 @@ import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLin
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
@@ -184,22 +186,7 @@ const getContent = async (params: Params) => {
|
||||
const rawContent = await response.text()
|
||||
|
||||
const { content: contentWithoutFrontmatter } = matter(rawContent)
|
||||
|
||||
// This is the more robust way of doing it, but problems with the rewritten
|
||||
// Markdown and handling of tables this way, so saving it for later.
|
||||
//
|
||||
// const mdxTree = fromMarkdown(contentWithoutFrontmatter)
|
||||
// const maybeH1 = mdxTree.children[0]
|
||||
// if (maybeH1 && maybeH1.type === 'heading' && (maybeH1 as Heading).depth === 1) {
|
||||
// mdxTree.children.shift()
|
||||
// }
|
||||
// content = toMarkdown(mdxTree)
|
||||
|
||||
content = contentWithoutFrontmatter
|
||||
if (meta.title) {
|
||||
const h1Regex = new RegExp(`(?:^|\n)# ${meta.title}\n+`)
|
||||
content = content.replace(h1Regex, '')
|
||||
}
|
||||
content = removeRedundantH1(contentWithoutFrontmatter)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -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 }
|
||||
+4
-2
@@ -3,7 +3,7 @@ import { notFound } from 'next/navigation'
|
||||
import { relative } from 'node:path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
@@ -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'
|
||||
@@ -83,7 +85,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
`https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
|
||||
)
|
||||
|
||||
const content = await response.text()
|
||||
const content = removeRedundantH1(await response.text())
|
||||
|
||||
return {
|
||||
pathname: `/guides/cli/github-action/${slug}` satisfies `/${string}`,
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function DeploymentLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
+8
-2
@@ -3,7 +3,7 @@ import { type SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import { notFound } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
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'
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// Each external docs page is mapped to a local page
|
||||
const pageMap = [
|
||||
{
|
||||
@@ -119,7 +121,11 @@ const getContent = async ({ slug }: Params) => {
|
||||
let rawContent = await response.text()
|
||||
// Strip out HTML comments
|
||||
rawContent = rawContent.replace(/<!--.*?-->/, '')
|
||||
const { content, data } = matter(rawContent)
|
||||
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)) {
|
||||
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 }
|
||||
@@ -0,0 +1,98 @@
|
||||
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'
|
||||
import { readdir, readFile, stat } from 'node:fs/promises'
|
||||
import { basename, extname, join } from 'node:path'
|
||||
import { cache } from 'react'
|
||||
import { visit, EXIT } from 'unist-util-visit'
|
||||
|
||||
import { EXAMPLES_DIRECTORY } from '~/lib/docs'
|
||||
|
||||
const PROMPTS_DIRECTORY = join(EXAMPLES_DIRECTORY, 'prompts')
|
||||
|
||||
function parseMarkdown(markdown: string) {
|
||||
const { content: withoutFrontmatter } = matter(markdown)
|
||||
const mdast = fromMarkdown(withoutFrontmatter)
|
||||
|
||||
let heading = ''
|
||||
visit(mdast, 'heading', (node: Heading) => {
|
||||
if (node.depth === 1) {
|
||||
if ('value' in node.children[0]) {
|
||||
heading = node.children[0].value
|
||||
}
|
||||
return EXIT
|
||||
}
|
||||
})
|
||||
|
||||
const codeBlock: Code = {
|
||||
type: 'code',
|
||||
lang: 'markdown',
|
||||
value: markdown,
|
||||
}
|
||||
const root: Root = {
|
||||
type: 'root',
|
||||
children: [codeBlock],
|
||||
}
|
||||
const content = toMarkdown(root)
|
||||
|
||||
return { heading, content }
|
||||
}
|
||||
|
||||
async function getAiPromptsImpl() {
|
||||
const directoryContents = await readdir(PROMPTS_DIRECTORY)
|
||||
|
||||
const prompts = directoryContents
|
||||
.filter(async (file) => {
|
||||
if (extname(file) !== '.md') {
|
||||
return false
|
||||
}
|
||||
|
||||
const fileStats = await stat(join(PROMPTS_DIRECTORY, file))
|
||||
const isFile = fileStats.isFile()
|
||||
return isFile
|
||||
})
|
||||
.map(async (filename) => {
|
||||
const rawContent = await readFile(join(PROMPTS_DIRECTORY, filename), 'utf-8')
|
||||
const { heading, content } = parseMarkdown(rawContent)
|
||||
|
||||
return {
|
||||
filename: basename(filename, '.md'),
|
||||
heading,
|
||||
content,
|
||||
}
|
||||
})
|
||||
|
||||
return (await Promise.all(prompts)).sort((a, b) => b.filename.localeCompare(a.filename))
|
||||
}
|
||||
export const getAiPrompts = cache(getAiPromptsImpl)
|
||||
|
||||
async function getAiPromptImpl(prompt: string) {
|
||||
const filePath = join(PROMPTS_DIRECTORY, `${prompt}.md`)
|
||||
try {
|
||||
const rawContent = await readFile(filePath, 'utf-8')
|
||||
const { heading, content } = parseMarkdown(rawContent)
|
||||
return { heading, content }
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch prompt from repo: %o', err)
|
||||
}
|
||||
}
|
||||
export const getAiPrompt = cache(getAiPromptImpl)
|
||||
|
||||
export async function generateAiPromptMetadata({ params: { slug } }: { params: { slug: string } }) {
|
||||
const prompt = await getAiPrompt(slug)
|
||||
|
||||
return {
|
||||
title: `AI Prompt: ${prompt.heading} | Supabase Docs`,
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateAiPromptsStaticParams() {
|
||||
const prompts = await getAiPrompts()
|
||||
|
||||
return prompts.map((prompt) => {
|
||||
return {
|
||||
slug: prompt.filename,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
import { GlassPanel } from 'ui-patterns'
|
||||
|
||||
import { getAiPrompts } from './AiPrompts.utils'
|
||||
|
||||
export async function AiPromptsIndex() {
|
||||
const prompts = await getAiPrompts()
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-6 not-prose">
|
||||
{prompts.map((prompt) => (
|
||||
<Link
|
||||
key={prompt.filename}
|
||||
href={`/guides/getting-started/ai-prompts/${prompt.filename}`}
|
||||
passHref
|
||||
>
|
||||
<GlassPanel
|
||||
key={prompt.filename}
|
||||
title={prompt.heading ?? prompt.filename.replaceAll('-', ' ')}
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import {
|
||||
generateAiPromptMetadata,
|
||||
generateAiPromptsStaticParams,
|
||||
getAiPrompt,
|
||||
} from './AiPrompts.utils'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
export default async function AiPromptsPage({ params: { slug } }: { params: { slug: string } }) {
|
||||
let { heading, content } = await getAiPrompt(slug)
|
||||
content = `
|
||||
## How to use
|
||||
|
||||
Copy the prompt to a file in your repo.
|
||||
|
||||
Use the "include file" feature from your AI tool to include the prompt when chatting with your AI assistant. For example, with GitHub Copilot, use \`#<filename>\`, in Cursor, use \`@Files\`, and in Zed, use \`/file\`.
|
||||
|
||||
## Prompt
|
||||
|
||||
${content}
|
||||
`.trim()
|
||||
|
||||
return (
|
||||
<GuideTemplate
|
||||
meta={{ title: `AI Prompt: ${heading}` }}
|
||||
content={content}
|
||||
editLink={newEditLink(`supabase/supabase/blob/master/examples/prompts/${slug}.md`)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const generateMetadata = generateAiPromptMetadata
|
||||
export const generateStaticParams = generateAiPromptsStaticParams
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
+4
-2
@@ -5,12 +5,14 @@ import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { fetchRevalidatePerDay_TEMP_TESTING } from '~/features/helpers.fetch'
|
||||
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'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'pg_graphql'
|
||||
@@ -133,7 +135,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
|
||||
const editLink = newEditLink(`${org}/${repo}/blob/${branch}/${docsDir}/${remoteFile}`)
|
||||
|
||||
const response = await fetchRevalidatePerDay(
|
||||
const response = await fetchRevalidatePerDay_TEMP_TESTING(
|
||||
`https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
|
||||
)
|
||||
|
||||
@@ -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 }
|
||||
Loaded 100 of 3283 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user