mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-27 13:52:47 -07:00
Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d86c97979b | |||
| 4e266522b6 | |||
| a58a1a4da9 | |||
| 22c1dd04c2 | |||
| 74e34dbbf9 | |||
| 4f38de3dea | |||
| 9d97f2d345 | |||
| 938cf3d8c6 | |||
| e1443274e3 | |||
| b9f8c0312f | |||
| 674a817131 | |||
| 2cbe53f7b7 | |||
| f3033ef0d2 | |||
| 0a0fb8c7f8 | |||
| f72a1a9e5c | |||
| fb29a26246 | |||
| 79d4aff758 | |||
| 7ea8414d6c | |||
| 1a3c9a7fbe | |||
| cecf2ba8cc | |||
| eaaf2513d7 | |||
| fa4d435320 | |||
| 2aff72942c | |||
| 11666fc422 | |||
| aef456cbf2 | |||
| 97b9d71caa | |||
| 5fa38b9506 | |||
| 98e50da575 | |||
| c8cf0771d4 | |||
| 1c6e78ad57 | |||
| 2b6f8c16ce | |||
| 2403ef3d64 | |||
| 003c0766cf | |||
| 9d7e59d793 | |||
| 43f03adedf | |||
| 211c6401e9 | |||
| 01b90b327e | |||
| ad0b09c3df | |||
| 362e1be58d | |||
| eb9039daa3 | |||
| ff7433f434 | |||
| a4e207688a | |||
| fdc87ed8f1 | |||
| 0ab9c297a5 | |||
| 04d6fcc1e1 | |||
| bf18ac0ab0 | |||
| bbe9605f6f | |||
| bd34c194cb | |||
| c0aa707315 | |||
| b6fa448202 | |||
| 2e141be0fc | |||
| 93b5e3e68f | |||
| 39dac5065f | |||
| a16a313f71 | |||
| 82560ddc20 | |||
| 83f3cae6f0 | |||
| 99df4a5575 | |||
| d33c211a01 | |||
| 21ee89ada4 | |||
| dcedb1c6c0 | |||
| d5cc65983e | |||
| 3cb150b090 | |||
| 305ae59086 | |||
| 8fe8930def | |||
| 8730bdbfb9 | |||
| 3ee47e9481 | |||
| 4e649acd60 | |||
| 4768d70a8a | |||
| c7bff7ed09 | |||
| 3035d2a645 | |||
| 0750037385 | |||
| 8c4d61ee65 | |||
| 1a3ccb112e | |||
| 118172d27f | |||
| 69f1d85360 | |||
| f0d042c85b | |||
| 550830c232 | |||
| 6be12b4e58 | |||
| 33aa37ed7d | |||
| 43910fd75d | |||
| 5efcc86a63 | |||
| 17b61a5a41 | |||
| 5dae5c31b7 | |||
| 96deea7f31 | |||
| 7632e6a464 | |||
| 23e2a2e3ff | |||
| b19512dffc | |||
| 11c6fc820b | |||
| 1a8231851d | |||
| 1dfb83b1aa | |||
| 93cb1ae8ba | |||
| 631b168e05 | |||
| af8a43c93a | |||
| 875d12f514 | |||
| a9a2ef67ea | |||
| 5caf3ed6e7 | |||
| 580ec704f2 | |||
| a9918ae555 | |||
| 4b3f4bfab8 | |||
| fed3b1581b | |||
| 9b2f0aab32 | |||
| 818a6bd2ff | |||
| 94a43347a1 | |||
| 16d793c588 | |||
| 0322e5bd8d | |||
| cfe76a9ab3 | |||
| ff6014317b | |||
| 64220f2e27 | |||
| e75414abfc | |||
| d6fae42dc3 | |||
| 0db1245a6b | |||
| 91c0a9ec31 | |||
| 7670c37685 | |||
| ae41c5629c | |||
| 3e7fa05fce | |||
| 6833640c47 | |||
| 18541c45de | |||
| 45bb0dd0d2 | |||
| db0aad2c37 | |||
| d55ca12125 | |||
| ad2cada94b | |||
| f9545f513b | |||
| c75d83dc7c | |||
| 91236e5533 | |||
| 1356be248e | |||
| 7e8be92cd5 | |||
| c3fda4a3a5 | |||
| dfc66cfd50 | |||
| 0ef1252e1f | |||
| cf56b3a53e | |||
| 592f402ffd | |||
| 1dc77fc473 | |||
| 09cd772d74 | |||
| 1c69e10e6a | |||
| 1af18e4c91 | |||
| a3709609c5 | |||
| 70a79faee0 | |||
| 07cb7dae9d | |||
| 9a1fb9c4a1 | |||
| a3f7ed7f61 | |||
| 5302fd2779 | |||
| ccd4e02b6a | |||
| f6593097b6 | |||
| 230cd22bff | |||
| f4367f1755 | |||
| 7b7447f9a0 | |||
| d12d8404ec | |||
| d1b12173d5 | |||
| f3bf13da59 | |||
| f236b75f55 | |||
| 2434231f9b | |||
| b8b39e69a1 | |||
| f8158dfd8e | |||
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 |
@@ -0,0 +1,7 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Bug report
|
||||
description: File a bug report
|
||||
title: '[Bug]: '
|
||||
type: Bug
|
||||
labels: ['bug']
|
||||
|
||||
body:
|
||||
@@ -32,55 +33,24 @@ body:
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: tuono-version
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: Tuono version
|
||||
placeholder: '[e.g. 0.4.0]'
|
||||
label: System Info
|
||||
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||
render: shell
|
||||
placeholder: System, Binaries, Browsers
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: os-version
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-info-rust
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: '[e.g. MacOS, Windows]'
|
||||
label: System info (Rust)
|
||||
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||
render: shell
|
||||
placeholder: 'rustc, cargo and tuono binary version'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser
|
||||
placeholder: '[e.g. chrome, safari]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Node version
|
||||
placeholder: '[e.g. 20.0.0]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: rust-version
|
||||
attributes:
|
||||
label: Rust version
|
||||
placeholder: '[e.g. 1.79.0]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: create-version
|
||||
attributes:
|
||||
label: Crate version
|
||||
placeholder: '[e.g. 1.78.0]'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: node-package-manager-version
|
||||
attributes:
|
||||
label: Node Package Manger version
|
||||
placeholder: '[e.g. pnpm: 9.5.0] '
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ask a question / Get support
|
||||
url: https://discord.com/invite/khQzPa654B
|
||||
about: Ask questions and discuss with other community members
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: '[Feature request]: '
|
||||
type: Feature
|
||||
labels: ['enhancement']
|
||||
|
||||
body:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# @see https://github.com/actions/labeler
|
||||
|
||||
rust:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['crates/**', 'Cargo.toml']
|
||||
|
||||
typescript:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
[
|
||||
'packages/**',
|
||||
'package.json',
|
||||
'pnpm-*.yaml',
|
||||
'eslint.config.js',
|
||||
'tsconfig.json',
|
||||
]
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['apps/documentation/**']
|
||||
|
||||
'CI/CD':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['.github/**']
|
||||
|
||||
'repo maintenance':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
[
|
||||
'.npmrc',
|
||||
'.nvmrc',
|
||||
'.prettierrc',
|
||||
'.prettierignore',
|
||||
'renovate.json',
|
||||
'turbo.json',
|
||||
]
|
||||
@@ -1,13 +1,24 @@
|
||||
## Context & Description
|
||||
<!--
|
||||
👋 Thank you for your Pull Request 🙏
|
||||
-->
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] I have read [Contributing > Pull requests](https://tuono.dev/documentation/contributing/pull-requests)
|
||||
|
||||
### Related issue
|
||||
|
||||
Fixes #0
|
||||
|
||||
<!-- Replace the content with "None" if you haven't an issue to link -->
|
||||
|
||||
### Overview
|
||||
|
||||
<!--
|
||||
Thank you for your Pull Request.
|
||||
|
||||
Explain the context and why you're making that change. What is the problem
|
||||
you're trying to solve? If a new feature is being added, describe the intended
|
||||
use case that feature fulfills.
|
||||
Explain the context and why you're making that change.
|
||||
What is the problem you're trying to solve?
|
||||
If a new feature is being added,
|
||||
describe the intended use case that feature fulfills.
|
||||
|
||||
Bug fixes and new features should include tests.
|
||||
|
||||
Contributors guide: https://github.com/Valerioageno/tuono/blob/main/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Docker CI
|
||||
|
||||
# If we need support more OS refer to
|
||||
# https://github.com/tuono-labs/tuono/pull/332#issuecomment-2614439692
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and Test Docker Image on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Run Docker Compose
|
||||
run: |
|
||||
docker compose -f docker/compose.yml up --build -d
|
||||
|
||||
- name: Check if the container is running
|
||||
run: |
|
||||
docker ps --filter "name=tuono-source-container" --format "table {{.Names}}\t{{.Status}}"
|
||||
if ! docker ps --filter "name=tuono-source-container" --format "{{.Status}}" | grep -q "Up"; then
|
||||
echo "Error: Container 'tuono-source-container' is not running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check commands inside container
|
||||
run: |
|
||||
docker exec tuono-source-container pnpm --version || { echo "Error: pnpm is not available."; exit 1; }
|
||||
docker exec tuono-source-container tuono --version || { echo "Error: tuono is not available."; exit 1; }
|
||||
|
||||
- name: Run cargo test
|
||||
run: docker exec tuono-source-container cargo test
|
||||
|
||||
- name: Install node dependencies in each tuono's sub-projets
|
||||
run: docker exec tuono-source-container bash -c 'for dir in /tuono/packages/*; do [ -d "$dir" ] && (cd "$dir" && yes | pnpm install --frozen-lockfile); done'
|
||||
|
||||
- name: Run pnpm test
|
||||
run: docker exec tuono-source-container pnpm test
|
||||
|
||||
- name: Tear Down Docker Compose
|
||||
if: always()
|
||||
run: |
|
||||
docker compose -f docker/compose.yml down --volumes
|
||||
@@ -4,22 +4,26 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
# match
|
||||
# - all files inside `apps/documentation` and `.github`
|
||||
# - all files in the root expect `Cargo.toml`
|
||||
paths:
|
||||
# ⬇ Keep paths in sync with `typescript.yml`
|
||||
- '.github/**'
|
||||
- '*'
|
||||
- '!Cargo.toml'
|
||||
# ⬆ Keep paths in sync with `typescript.yml`
|
||||
- 'apps/documentation/**'
|
||||
pull_request:
|
||||
# same as `on.push.paths`
|
||||
paths:
|
||||
# ⬇ Keep paths in sync with `typescript.yml`
|
||||
- '.github/**'
|
||||
- '*'
|
||||
- '!Cargo.toml'
|
||||
# ⬆ Keep paths in sync with `typescript.yml`
|
||||
- 'apps/documentation/**'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Check build
|
||||
@@ -36,8 +40,16 @@ jobs:
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
# ⬇️ Keep in sync with `.github/workflows/deploy-documentation.yml`
|
||||
- name: Read tuono version used by the documentation
|
||||
id: get_tuono_version
|
||||
run: |
|
||||
VERSION=$(pnpm ls tuono --filter=documentation | grep "tuono " | sed -E 's/.* ([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.15.0
|
||||
run: cargo install tuono@${{ steps.get_tuono_version.outputs.version }}
|
||||
# ⬆️
|
||||
|
||||
- name: Build project
|
||||
working-directory: ./apps/documentation
|
||||
+10
-2
@@ -1,4 +1,4 @@
|
||||
name: Deploy documentation website on AWS S3
|
||||
name: Documentation Website Deploy on AWS S3
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -26,8 +26,16 @@ jobs:
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
# ⬇️ Keep in sync with `.github/workflows/ci-documentation.yml`
|
||||
- name: Read tuono version used by the documentation
|
||||
id: get_tuono_version
|
||||
run: |
|
||||
VERSION=$(pnpm ls tuono --filter=documentation | grep "tuono " | sed -E 's/.* ([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.16.0
|
||||
run: cargo install tuono@${{ steps.get_tuono_version.outputs.version }}
|
||||
# ⬆️
|
||||
|
||||
- name: Build project
|
||||
working-directory: ./apps/documentation
|
||||
@@ -0,0 +1,27 @@
|
||||
name: 'PR Labeler'
|
||||
|
||||
# @see https://github.com/actions/labeler
|
||||
# @see .github/labeler.yml
|
||||
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: 'Manage labels'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
# Add checkout step to load labeler.yml locally,
|
||||
# rather than from an API request
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
@@ -0,0 +1,61 @@
|
||||
name: 'PR Title Checker'
|
||||
|
||||
# This workflow ensures that PR titles adhere to a pattern
|
||||
# similar to conventional commits standard
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
check_pr_title:
|
||||
name: 'PR Title Checker'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check type
|
||||
id: type
|
||||
# @warning Keep in sync with apps/documentation/src/routes/documentation/contributing/pull-requests.mdx
|
||||
run: |
|
||||
VALID_COMMIT_TYPES="chore|ci|docs|feat|fix|refactor|test"
|
||||
REGEX="^(${VALID_COMMIT_TYPES})(\(.*\))?!?: .*"
|
||||
|
||||
if ! [[ $PR_TITLE =~ $REGEX ]]; then
|
||||
echo "::error title=Type::The title has an incorrect type. Valid types are ${VALID_COMMIT_TYPES}"
|
||||
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::notice title=Type::The title is using a valid type"
|
||||
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check length
|
||||
id: length
|
||||
run: |
|
||||
MAX_LENGTH=100
|
||||
LENGTH=${#PR_TITLE}
|
||||
|
||||
if [ $LENGTH -gt $MAX_LENGTH ]; then
|
||||
echo "::error title=Length::The title is longer than $MAX_LENGTH characters"
|
||||
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::notice title=Length::The title length is within the maximum value of $MAX_LENGTH characters"
|
||||
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check result
|
||||
env:
|
||||
FAILURE: ${{ contains(join(steps.*.outputs.result, ','), 'fail') }}
|
||||
run: |
|
||||
echo "Failure: $FAILURE"
|
||||
if [ "$FAILURE" = "false" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -8,16 +8,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/**'
|
||||
- '*'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/**'
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
format:
|
||||
name: Check format
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
@@ -31,3 +29,23 @@ jobs:
|
||||
|
||||
- name: Test project
|
||||
run: pnpm repo:root:format:check
|
||||
|
||||
ci_ok:
|
||||
name: Repo root CI OK
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
if: always()
|
||||
needs: [format]
|
||||
env:
|
||||
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
|
||||
steps:
|
||||
- name: Check for failure or cancelled jobs result
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
|
||||
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -4,15 +4,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/**'
|
||||
- 'Cargo.toml'
|
||||
- 'crates/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/**'
|
||||
- 'Cargo.toml'
|
||||
- 'crates/**'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -40,6 +36,7 @@ jobs:
|
||||
os: 'ubuntu-latest'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -56,6 +53,7 @@ jobs:
|
||||
lint_and_fmt:
|
||||
name: Check format and lint rust crates
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -66,3 +64,23 @@ jobs:
|
||||
|
||||
- run: cargo fmt --all -- --check
|
||||
- run: cargo clippy -- -D warnings
|
||||
|
||||
ci_ok:
|
||||
name: Rust CI OK
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
if: always()
|
||||
needs: [build_and_test, lint_and_fmt]
|
||||
env:
|
||||
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
|
||||
steps:
|
||||
- name: Check for failure or cancelled jobs result
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
|
||||
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -7,6 +7,10 @@ on:
|
||||
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
spell_checking:
|
||||
name: LanguageTool
|
||||
|
||||
@@ -4,26 +4,15 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
# ⬇ Keep paths in sync with `ci-documentation.yml`
|
||||
- '.github/**'
|
||||
- '*'
|
||||
- '!Cargo.toml'
|
||||
# ⬆ Keep paths in sync with `ci-documentation.yml`
|
||||
- 'packages/**'
|
||||
pull_request:
|
||||
paths:
|
||||
# ⬇ Keep paths in sync with `ci-documentation.yml`
|
||||
- '.github/**'
|
||||
- '*'
|
||||
- '!Cargo.toml'
|
||||
# ⬆ Keep paths in sync with `ci-documentation.yml`
|
||||
- 'packages/**'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Check build and test
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -34,6 +23,7 @@ jobs:
|
||||
- 'windows-latest'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -68,3 +58,23 @@ jobs:
|
||||
|
||||
- name: Types
|
||||
run: pnpm types
|
||||
|
||||
ci_ok:
|
||||
name: Typescript CI OK
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
if: always()
|
||||
needs: [build_and_test, lint_and_fmt]
|
||||
env:
|
||||
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
|
||||
steps:
|
||||
- name: Check for failure or cancelled jobs result
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
|
||||
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -8,6 +8,8 @@ dist/
|
||||
|
||||
examples/*/pnpm-lock.yaml
|
||||
|
||||
vite.config.ts.timestamp*
|
||||
|
||||
## Rust related ignores
|
||||
|
||||
# Generated by Cargo
|
||||
|
||||
+8
-1
@@ -1 +1,8 @@
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
|
||||
dist
|
||||
.tuono
|
||||
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
* @Valerioageno @marcalexiei
|
||||
|
||||
# Rust
|
||||
/crates/ @Valerioageno
|
||||
/Cargo.toml @Valerioageno
|
||||
|
||||
# Misc
|
||||
/.github/ @marcalexiei
|
||||
|
||||
+1
-3
@@ -23,6 +23,4 @@ Consider [opening a new issue](https://github.com/tuono-labs/tuono/issues/new/ch
|
||||
|
||||
**Did you write a patch that fixes a bug?**
|
||||
|
||||
- Open a new GitHub pull request with the patch.
|
||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number, if applicable.
|
||||
- The pull requests must pass all the CI pipelines
|
||||
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
|
||||
|
||||
+9
-8
@@ -1,14 +1,15 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/tuono",
|
||||
"crates/tuono_lib",
|
||||
"crates/tuono_lib_macros",
|
||||
"crates/tuono",
|
||||
"crates/tuono_internal",
|
||||
"crates/tuono_lib",
|
||||
"crates/tuono_lib_macros"
|
||||
]
|
||||
exclude = [
|
||||
"apps/documentation",
|
||||
"examples/with-mdx",
|
||||
"examples/tuono-app",
|
||||
"examples/tuono-tutorial",
|
||||
"benches/tuono"
|
||||
"apps/documentation",
|
||||
"examples/with-mdx",
|
||||
"examples/tuono-app",
|
||||
"examples/tuono-tutorial",
|
||||
"examples/with-tailwind",
|
||||
]
|
||||
|
||||
@@ -24,51 +24,34 @@ Some of its features are:
|
||||
- 🧬 Server Side Rendering
|
||||
- 🔥 Hot Module Reload
|
||||
|
||||
## 📖 Documentation
|
||||
## Documentation
|
||||
|
||||
The [documentation](https://tuono.dev/documentation) is available on
|
||||
The [documentation](https://tuono.dev/) is available on
|
||||
[tuono.dev](https://tuono.dev/).
|
||||
|
||||
## Introduction
|
||||
## Installation
|
||||
|
||||
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
|
||||
Available on MacOS, Linux and Windows.
|
||||
|
||||
Tuono is a full-stack React framework with the server side written in Rust.
|
||||
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
|
||||
React is still React - it is just superpowered.
|
||||
|
||||
**Rust is a hard language, then writing server-side code is hard as well, right? (no again)**
|
||||
|
||||
Tuono provides a collection of utilities to handle the server side code seamlessly with the React code.
|
||||
Each server-side route is managed in a separate file alongside the React route. The routing is handled
|
||||
by Tuono based on the files defined within the `./src/routes` directory.
|
||||
|
||||
**How is Tuono different from Next.js?**
|
||||
|
||||
The Tuono API tries to stick as much as possible to the Next.js one (or at least takes a huge inspiration
|
||||
from it). The major difference is the backend system. While Next.js relies entirely on Node/Deno/Bun,
|
||||
Tuono runs the server without any intermediary runtime. This enables impressive performance improvements
|
||||
(check the benchmarks [here](https://github.com/tuono-labs/tuono/tree/main/benches)).
|
||||
```sh
|
||||
cargo install tuono
|
||||
```
|
||||
|
||||
## Getting started
|
||||
|
||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
||||
To download it is required [cargo](https://doc.rust-lang.org/cargo/), which is the [rust](https://www.rust-lang.org/)
|
||||
package manager.
|
||||
Create a new project with:
|
||||
|
||||
Then run `cargo install tuono`.
|
||||
```sh
|
||||
tuono new [PROJECT_NAME]
|
||||
```
|
||||
|
||||
To list all the available commands, run `tuono -h`
|
||||
> You can optionally pass the --template (or -t) flag to directly start from a [template](https://github.com/tuono-labs/tuono/tree/main/examples)
|
||||
|
||||
To create a new project, run `tuono new [NAME]` (optionally, you can pass the `--template` (or `-t`) flag - check the
|
||||
[examples](https://github.com/tuono-labs/tuono/tree/main/examples) folder).
|
||||
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
|
||||
|
||||
Then, to run the local development environment, install the JS dependencies (`npm install`) and run inside the project folder `tuono dev`
|
||||
|
||||
Finally, when the project is ready to be deployed, just run `tuono build` to create the final React assets
|
||||
and to set the server project in `production` mode.
|
||||
|
||||
Now to execute it just run `cargo run --release`.
|
||||
```sh
|
||||
tuono dev
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_wqyze0qQlWutAwL5RL1Bv83D8bdySCkhcFw9MkTVuI8
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
|
||||
VITE_ENV=development
|
||||
VITE_ENABLE_POSTHOG=false
|
||||
@@ -0,0 +1,4 @@
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_wqyze0qQlWutAwL5RL1Bv83D8bdySCkhcFw9MkTVuI8
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
|
||||
VITE_ENV=production
|
||||
VITE_ENABLE_POSTHOG=true
|
||||
@@ -0,0 +1,17 @@
|
||||
# Documentation Contributing
|
||||
|
||||
## Update `tuono` version in `apps/documentation`
|
||||
|
||||
Update `tuono` version in the following files
|
||||
|
||||
- `.github/workflows/ci-documentation.yml`
|
||||
- `.github/workflows/deploy-documentation.yml`
|
||||
- `apps/documentation/Cargo.toml`
|
||||
- `apps/documentation/package.json`
|
||||
|
||||
[Refer to this PR for additional information](https://github.com/tuono-labs/tuono/pull/236/files)
|
||||
|
||||
In the future we might:
|
||||
|
||||
- move the documentation in a separate repo
|
||||
- make a script to perform this operation
|
||||
@@ -8,8 +8,7 @@ name = "tuono"
|
||||
path = ".tuono/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tuono_lib = "0.15.0"
|
||||
tuono_lib = "0.17.6"
|
||||
glob = "0.3.1"
|
||||
time = { version = "0.3", features = ["macros"] }
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Tuono starter
|
||||
|
||||
This is the starter tuono project. To download it run in your terminal:
|
||||
|
||||
```shell
|
||||
$ tuono new [NAME]
|
||||
```
|
||||
@@ -6,26 +6,28 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"format": "prettier -u --write --ignore-unknown .",
|
||||
"format": "prettier --write --ignore-unknown .",
|
||||
"format:check": "prettier --check --ignore-unknown .",
|
||||
"types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/code-highlight": "7.14.2",
|
||||
"@mantine/core": "7.14.2",
|
||||
"@mantine/hooks": "7.14.2",
|
||||
"@mantine/code-highlight": "7.16.1",
|
||||
"@mantine/core": "7.16.1",
|
||||
"@mantine/hooks": "7.16.1",
|
||||
"@mdx-js/react": "3.1.0",
|
||||
"@tabler/icons-react": "3.22.0",
|
||||
"@tabler/icons-react": "3.28.1",
|
||||
"clsx": "2.1.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"tuono": "npm:tuono@0.16.0"
|
||||
"posthog-js": "^1.215.5",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"tuono": "npm:tuono@0.17.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/rollup": "3.1.0",
|
||||
"@types/react": "18.3.13",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"postcss": "8.4.49",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"postcss": "8.5.1",
|
||||
"postcss-preset-mantine": "1.17.0",
|
||||
"postcss-simple-vars": "7.0.1"
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,118 @@
|
||||
import type { ReactNode, JSX } from 'react'
|
||||
|
||||
import { createTheme, MantineProvider, AppShell } from '@mantine/core'
|
||||
|
||||
import type { CSSVariablesResolver } from '@mantine/core'
|
||||
import { useDisclosure } from '@mantine/hooks'
|
||||
|
||||
import PageWithTOC from '@/components/PageWithTOC'
|
||||
import Navbar from '@/components/Navbar'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
import MdxProvider from '@/components/MdxProvider'
|
||||
|
||||
import '@mantine/core/styles.css'
|
||||
import '@mantine/code-highlight/styles.css'
|
||||
import Footer from '@/components/Footer'
|
||||
|
||||
interface RootRouteProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const theme = createTheme({
|
||||
primaryColor: 'violet',
|
||||
primaryShade: { light: 6, dark: 9 },
|
||||
fontFamily: 'Inter',
|
||||
fontFamilyMonospace: 'Menlo',
|
||||
respectReducedMotion: true,
|
||||
radius: {
|
||||
xs: '4px',
|
||||
sm: '4px',
|
||||
lg: '8px',
|
||||
xl: '8px',
|
||||
md: '8px',
|
||||
},
|
||||
fontSizes: {
|
||||
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||
xs: '14px',
|
||||
sm: '14px',
|
||||
},
|
||||
colors: {
|
||||
dark: [
|
||||
'#d5d7e0',
|
||||
'#acaebf',
|
||||
'#8c8fa3',
|
||||
'#666980',
|
||||
'#4d4f66',
|
||||
'#34354a',
|
||||
'#2b2c3d',
|
||||
'#1d1e30',
|
||||
'#0c0d21',
|
||||
'#01010a',
|
||||
],
|
||||
},
|
||||
headings: {
|
||||
sizes: {
|
||||
h1: {
|
||||
fontSize: '48px',
|
||||
},
|
||||
},
|
||||
},
|
||||
other: {
|
||||
sidebarGrayLight: '#495057',
|
||||
sidebarGrayDark: '#adb5bd',
|
||||
sidebarTextHoverLight: '#212529',
|
||||
sidebarTextHoverDark: '#f8f9fa',
|
||||
},
|
||||
})
|
||||
|
||||
const resolver: CSSVariablesResolver = (th) => {
|
||||
const {
|
||||
sidebarGrayLight,
|
||||
sidebarTextHoverLight,
|
||||
sidebarGrayDark,
|
||||
sidebarTextHoverDark,
|
||||
} = th.other as Record<string, string>
|
||||
|
||||
return {
|
||||
variables: {},
|
||||
light: {
|
||||
'--mantine-color-footer-bg': th.colors.gray[1],
|
||||
'--mantine-color-sidebar-gray': sidebarGrayLight,
|
||||
'--mantine-color-sidebar-text-hover': sidebarTextHoverLight,
|
||||
'--mantine-color-quote-border': th.colors.violet[1],
|
||||
},
|
||||
dark: {
|
||||
'--mantine-color-footer-bg': th.colors.dark[6],
|
||||
'--mantine-color-sidebar-gray': sidebarGrayDark,
|
||||
'--mantine-color-sidebar-text-hover': sidebarTextHoverDark,
|
||||
'--mantine-color-quote-border': th.colors.violet[9],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default function App({ children }: RootRouteProps): JSX.Element {
|
||||
const [opened, { toggle }] = useDisclosure()
|
||||
|
||||
return (
|
||||
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
|
||||
<AppShell
|
||||
layout="alt"
|
||||
header={{ height: 60 }}
|
||||
navbar={{
|
||||
width: 300,
|
||||
breakpoint: 'sm',
|
||||
collapsed: { mobile: !opened },
|
||||
}}
|
||||
>
|
||||
<Navbar toggle={toggle} />
|
||||
<Sidebar close={toggle} />
|
||||
<AppShell.Main pt={0} px="auto">
|
||||
<MdxProvider>
|
||||
<PageWithTOC>{children}</PageWithTOC>
|
||||
</MdxProvider>
|
||||
</AppShell.Main>
|
||||
<Footer />
|
||||
</AppShell>
|
||||
</MantineProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import App from './App'
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useMemo, type JSX } from 'react'
|
||||
import { Breadcrumbs as MantineBreadcrumbs, Button } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
|
||||
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
||||
|
||||
interface BreadcrumbData {
|
||||
href?: string
|
||||
label: string
|
||||
}
|
||||
interface BreadcrumbsProps {
|
||||
breadcrumbs: Array<BreadcrumbData>
|
||||
}
|
||||
|
||||
export default function Breadcrumbs({
|
||||
breadcrumbs = [],
|
||||
}: BreadcrumbsProps): JSX.Element {
|
||||
const ldJson = useMemo(() => {
|
||||
const _ldJson = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'Tuono - The React/Rust fullstack framework',
|
||||
item: 'https://tuono.dev' as string | undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
_ldJson.itemListElement.push(
|
||||
...breadcrumbs.map((br, i) => ({
|
||||
'@type': 'ListItem' as const,
|
||||
position: i + 3,
|
||||
name: br.label,
|
||||
item: br.href ? `https://tuono.dev${br.href}` : undefined,
|
||||
})),
|
||||
)
|
||||
|
||||
return _ldJson
|
||||
}, [breadcrumbs])
|
||||
|
||||
return (
|
||||
<>
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(ldJson, null, 2)}
|
||||
</script>
|
||||
<MantineBreadcrumbs
|
||||
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
||||
mb="md"
|
||||
mt="md"
|
||||
>
|
||||
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
|
||||
<IconBolt />
|
||||
</Button>
|
||||
{breadcrumbs.map((br) => (
|
||||
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
||||
))}
|
||||
</MantineBreadcrumbs>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface BreadcrumbElementProps {
|
||||
href?: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export function BreadcrumbElement({
|
||||
href,
|
||||
label,
|
||||
}: BreadcrumbElementProps): JSX.Element {
|
||||
if (href) {
|
||||
return (
|
||||
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
|
||||
{label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button component="span" variant="light" radius="xl">
|
||||
{label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import Breadcrumbs from './Breadcrumbs'
|
||||
|
||||
export default Breadcrumbs
|
||||
+2
-2
@@ -8,15 +8,15 @@ const GITHUB_URL =
|
||||
|
||||
export default function EditPage(): JSX.Element {
|
||||
const { pathname } = useRouter()
|
||||
|
||||
return (
|
||||
<Button
|
||||
p={0}
|
||||
mt={60}
|
||||
component="a"
|
||||
variant="transparent"
|
||||
leftSection={<IconEdit />}
|
||||
target="_blank"
|
||||
href={GITHUB_URL.concat(pathname).concat('.mdx')}
|
||||
href={`${GITHUB_URL}${pathname}.mdx`}
|
||||
>
|
||||
Edit page
|
||||
</Button>
|
||||
@@ -0,0 +1,3 @@
|
||||
import EditPage from './EditPage'
|
||||
|
||||
export default EditPage
|
||||
@@ -0,0 +1,182 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Link } from 'tuono'
|
||||
import {
|
||||
Container,
|
||||
Box,
|
||||
AppShell,
|
||||
Title,
|
||||
Divider,
|
||||
Flex,
|
||||
Text,
|
||||
Anchor,
|
||||
} from '@mantine/core'
|
||||
import type { MantineSize } from '@mantine/core'
|
||||
|
||||
interface LinkData {
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface FooterLinkListProps {
|
||||
title: string
|
||||
links: Array<LinkData>
|
||||
hiddenFrom?: MantineSize
|
||||
}
|
||||
|
||||
const aboutLinks: Array<LinkData> = [
|
||||
{ title: 'Contribute', url: '/documentation/contributing' },
|
||||
{
|
||||
title: 'GitHub releases',
|
||||
url: 'https://github.com/tuono-labs/tuono/releases',
|
||||
},
|
||||
]
|
||||
|
||||
const communityLinks: Array<LinkData> = [
|
||||
{ title: 'Chat on Discord', url: 'https://discord.com/invite/khQzPa654B' },
|
||||
{ title: 'Follow on X', url: 'https://twitter.com/valerioageno' },
|
||||
{ title: 'Follow on GitHub', url: 'https://github.com/tuono-labs' },
|
||||
]
|
||||
|
||||
const legalLinks: Array<LinkData> = [
|
||||
{ title: 'Privacy Policy', url: '/policies/privacy' },
|
||||
]
|
||||
|
||||
function FooterLink({ link }: { link: LinkData }): JSX.Element {
|
||||
const component = link.url.startsWith('http') ? undefined : Link
|
||||
const target = link.url.startsWith('http') ? '_blank' : undefined
|
||||
return (
|
||||
<Box>
|
||||
<Anchor
|
||||
component={component}
|
||||
href={link.url}
|
||||
key={link.url}
|
||||
fz={14}
|
||||
target={target}
|
||||
>
|
||||
{link.title}
|
||||
</Anchor>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterLinkList({
|
||||
title,
|
||||
links,
|
||||
hiddenFrom,
|
||||
}: FooterLinkListProps): JSX.Element {
|
||||
return (
|
||||
<Box hiddenFrom={hiddenFrom}>
|
||||
<Title order={5} mb={8}>
|
||||
{title}
|
||||
</Title>
|
||||
{links.map((link) => (
|
||||
<FooterLink link={link} key={link.url} />
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterTitle(): JSX.Element {
|
||||
const size = 44
|
||||
return (
|
||||
<Box mb={24}>
|
||||
<Flex gap={12} align="center" h={size} mb={8}>
|
||||
<img src="/logo.svg" alt="Tuono" width={size} height={size} />
|
||||
<Title order={2} fz={size}>
|
||||
Tuono
|
||||
</Title>
|
||||
</Flex>
|
||||
<Text fz={12}>Simply the fastest full-stack web framework ⚡</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function TocPlaceholder(): JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
w={220}
|
||||
miw={220}
|
||||
data-id="table-of-content-placeholder"
|
||||
visibleFrom="lg"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterOutro(): JSX.Element {
|
||||
const fontSize = 14
|
||||
return (
|
||||
<Container
|
||||
size={1000}
|
||||
w="100%"
|
||||
display="flex"
|
||||
style={{ gap: 12, justifyContent: 'space-between' }}
|
||||
>
|
||||
<Box w="100%">
|
||||
<Text fz={fontSize}>
|
||||
Built by{' '}
|
||||
<Anchor fz={fontSize} href="https://github.com/Valerioageno">
|
||||
Valerio Ageno
|
||||
</Anchor>{' '}
|
||||
and{' '}
|
||||
<Anchor
|
||||
fz={fontSize}
|
||||
href="https://github.com/tuono-labs/tuono/graphs/contributors"
|
||||
>
|
||||
these awesome people
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Box>
|
||||
<Anchor
|
||||
fz={fontSize}
|
||||
w={150}
|
||||
component={Link}
|
||||
href="/policies/privacy"
|
||||
ta="right"
|
||||
visibleFrom="xs"
|
||||
>
|
||||
Privacy Policy
|
||||
</Anchor>
|
||||
<TocPlaceholder />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Footer(): JSX.Element {
|
||||
return (
|
||||
<AppShell.Footer id="app-footer" withBorder={false} zIndex={0} p={0}>
|
||||
<Box m={0} pt={52} pb={8} w="100%" bg="var(--mantine-color-footer-bg)">
|
||||
<Container
|
||||
size={1000}
|
||||
w="100%"
|
||||
display="flex"
|
||||
style={{ gap: 12, justifyContent: 'space-between' }}
|
||||
mb={24}
|
||||
>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
direction={{ base: 'column', xs: 'row' }}
|
||||
>
|
||||
<FooterTitle />
|
||||
<Flex
|
||||
gap={{ base: 12, md: 32 }}
|
||||
justify="space-between"
|
||||
direction={{ base: 'column', xs: 'row' }}
|
||||
>
|
||||
<FooterLinkList title="About" links={aboutLinks} />
|
||||
<FooterLinkList title="Community" links={communityLinks} />
|
||||
<FooterLinkList
|
||||
title="Legal"
|
||||
links={legalLinks}
|
||||
hiddenFrom="xs"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<TocPlaceholder />
|
||||
</Container>
|
||||
<Divider mb={8} mt={28} />
|
||||
<FooterOutro />
|
||||
</Box>
|
||||
</AppShell.Footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import Footer from './Footer'
|
||||
|
||||
export default Footer
|
||||
+1
-1
@@ -36,7 +36,7 @@ export default function Hero(): JSX.Element {
|
||||
size="lg"
|
||||
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
||||
color="gray"
|
||||
leftSection="$ cargo install tuono"
|
||||
leftSection="cargo install tuono"
|
||||
rightSection={
|
||||
copied ? (
|
||||
<IconCheck style={{ width: rem(20) }} />
|
||||
@@ -0,0 +1,3 @@
|
||||
import Hero from './Hero'
|
||||
|
||||
export default Hero
|
||||
+1
-1
@@ -2,5 +2,5 @@ import type { JSX } from 'react'
|
||||
import { Text, type TextProps } from '@mantine/core'
|
||||
|
||||
export default function MdxBold(props: TextProps): JSX.Element {
|
||||
return <Text fw={700} {...props} />
|
||||
return <Text component="span" fw={700} {...props} />
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxBold from './MdxBold'
|
||||
|
||||
export default MdxBold
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxCode from './MdxCode'
|
||||
|
||||
export default MdxCode
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { JSX, AnchorHTMLAttributes, ReactNode } from 'react'
|
||||
import { Anchor } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
import { IconExternalLink } from '@tabler/icons-react'
|
||||
|
||||
interface MdxLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function MdxLink(props: MdxLinkProps): JSX.Element {
|
||||
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
|
||||
return (
|
||||
<Anchor
|
||||
{...props}
|
||||
target="_blank"
|
||||
variant="transparent"
|
||||
display="inline"
|
||||
style={{ fontWeight: 400 }}
|
||||
p={0}
|
||||
>
|
||||
{props.children}
|
||||
<IconExternalLink
|
||||
size="16px"
|
||||
style={{
|
||||
marginLeft: '2px',
|
||||
display: 'inline-block',
|
||||
transform: 'translateY(3px)',
|
||||
}}
|
||||
/>
|
||||
</Anchor>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Anchor
|
||||
component={Link}
|
||||
{...props}
|
||||
target="_blank"
|
||||
variant="transparent"
|
||||
display="inline"
|
||||
style={{ fontWeight: 400 }}
|
||||
p={0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxLink from './MdxLink'
|
||||
|
||||
export default MdxLink
|
||||
@@ -0,0 +1,5 @@
|
||||
.text {
|
||||
code {
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { JSX, HTMLAttributes } from 'react'
|
||||
import { Text } from '@mantine/core'
|
||||
|
||||
import styles from './MdxParagraph.module.css'
|
||||
|
||||
export default function MdxParagraph(
|
||||
props: HTMLAttributes<HTMLParagraphElement>,
|
||||
): JSX.Element {
|
||||
return (
|
||||
<Text
|
||||
{...props}
|
||||
className={styles.text}
|
||||
inherit
|
||||
size="md"
|
||||
my={20}
|
||||
fw={300}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxParagraph from './MdxParagraph'
|
||||
|
||||
export default MdxParagraph
|
||||
+1
-3
@@ -1,8 +1,6 @@
|
||||
import type { JSX } from 'react'
|
||||
import { CodeHighlight } from '@mantine/code-highlight'
|
||||
|
||||
import styles from './mdx-pre.module.css'
|
||||
|
||||
interface PreProps {
|
||||
children: {
|
||||
props: {
|
||||
@@ -15,10 +13,10 @@ interface PreProps {
|
||||
export default function MdxPre({ children }: PreProps): JSX.Element {
|
||||
return (
|
||||
<CodeHighlight
|
||||
className={styles.pre}
|
||||
code={children.props.children || ''}
|
||||
style={{ borderRadius: 8, fontWeight: 100 }}
|
||||
language={children.props.className?.replace('language-', '')}
|
||||
my={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxPre from './MdxPre'
|
||||
|
||||
export default MdxPre
|
||||
+19
-7
@@ -1,12 +1,16 @@
|
||||
import type { JSX, ReactNode } from 'react'
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
import { Divider } from '@mantine/core'
|
||||
|
||||
import MdxLink from './mdx-link'
|
||||
import MdxPre from './mdx-pre'
|
||||
import MdxQuote from './mdx-quote'
|
||||
import MdxCode from './mdx-code'
|
||||
import { h } from './mdx-title'
|
||||
import MdxBold from './mdx-bold/mdx-bold'
|
||||
import MdxLink from './MdxLink'
|
||||
import MdxPre from './MdxPre'
|
||||
import MdxQuote from './MdxQuote'
|
||||
import MdxCode from './MdxCode'
|
||||
import { h } from './MdxTitle'
|
||||
import MdxBold from './MdxBold'
|
||||
import MdxTable from './MdxTable'
|
||||
import MdxUl from './MdxUl'
|
||||
import MdxParagraph from './MdxParagraph'
|
||||
|
||||
interface MdxProviderProps {
|
||||
children: ReactNode
|
||||
@@ -19,7 +23,6 @@ export default function MdxProvider({
|
||||
<MDXProvider
|
||||
components={{
|
||||
a: MdxLink,
|
||||
// @ts-expect-error: useless finding the correct props types
|
||||
pre: MdxPre,
|
||||
blockquote: MdxQuote,
|
||||
code: MdxCode,
|
||||
@@ -30,6 +33,15 @@ export default function MdxProvider({
|
||||
h5: h(5),
|
||||
h6: h(6),
|
||||
strong: MdxBold,
|
||||
table: MdxTable,
|
||||
thead: MdxTable.Thead,
|
||||
tbody: MdxTable.Tbody,
|
||||
tr: MdxTable.Tr,
|
||||
th: MdxTable.Th,
|
||||
td: MdxTable.Td,
|
||||
p: MdxParagraph,
|
||||
ul: MdxUl,
|
||||
hr: Divider,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { JSX, HTMLAttributes } from 'react'
|
||||
import { Blockquote } from '@mantine/core'
|
||||
|
||||
export default function MdxQuote(
|
||||
props: HTMLAttributes<HTMLQuoteElement>,
|
||||
): JSX.Element {
|
||||
return (
|
||||
<Blockquote
|
||||
py={1}
|
||||
px={20}
|
||||
my={16}
|
||||
iconSize={28}
|
||||
{...props}
|
||||
bd="solid 1px var(--mantine-color-quote-border)"
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxQuote from './MdxQuote'
|
||||
|
||||
export default MdxQuote
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { DetailedHTMLProps, TableHTMLAttributes, JSX } from 'react'
|
||||
import { Table, ScrollArea } from '@mantine/core'
|
||||
|
||||
type MdxTableProps = DetailedHTMLProps<
|
||||
TableHTMLAttributes<HTMLTableElement>,
|
||||
HTMLTableElement
|
||||
>
|
||||
|
||||
function MdxTable(props: MdxTableProps): JSX.Element {
|
||||
const { children, ...rest } = props
|
||||
return (
|
||||
<ScrollArea type="auto">
|
||||
<Table highlightOnHover {...rest}>
|
||||
{children}
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
MdxTable.Thead = Table.Thead
|
||||
MdxTable.Tbody = Table.Tbody
|
||||
MdxTable.Tr = Table.Tr
|
||||
MdxTable.Th = Table.Th
|
||||
MdxTable.Td = Table.Td
|
||||
MdxTable.Caption = Table.Caption
|
||||
|
||||
export default MdxTable
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxTable from './MdxTable'
|
||||
|
||||
export default MdxTable
|
||||
@@ -0,0 +1,111 @@
|
||||
import type { ElementType, JSX, MouseEventHandler, ReactNode } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { Title, Anchor, Box } from '@mantine/core'
|
||||
import type { TitleProps } from '@mantine/core'
|
||||
import { IconLink } from '@tabler/icons-react'
|
||||
import { useHover } from '@mantine/hooks'
|
||||
|
||||
export default function MdxTitle({
|
||||
children,
|
||||
order,
|
||||
...rest
|
||||
}: TitleProps): JSX.Element {
|
||||
const headingId = getIdFrom(children)
|
||||
const { hovered, ref } = useHover<HTMLHeadingElement>()
|
||||
|
||||
const handleAnchorClick: MouseEventHandler<HTMLAnchorElement> = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault()
|
||||
if (ref.current) {
|
||||
history.pushState(
|
||||
null,
|
||||
'',
|
||||
`#${(ref.current.firstChild as HTMLHeadingElement).id} `,
|
||||
)
|
||||
ref.current.scrollIntoView({
|
||||
behavior: 'instant',
|
||||
block: 'start',
|
||||
})
|
||||
}
|
||||
},
|
||||
[ref],
|
||||
)
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
data-order={order}
|
||||
style={{
|
||||
scrollMargin: 80,
|
||||
marginTop: order === 1 ? 0 : 20,
|
||||
}}
|
||||
mb={24}
|
||||
{...rest}
|
||||
>
|
||||
<Title
|
||||
data-heading={headingId}
|
||||
id={headingId}
|
||||
data-order={order}
|
||||
display="inline"
|
||||
order={order}
|
||||
style={{
|
||||
scrollMargin: 80,
|
||||
marginTop: order === 1 ? 0 : 20,
|
||||
}}
|
||||
mb={24}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{hovered && order !== 1 && (
|
||||
<Anchor
|
||||
onClick={handleAnchorClick}
|
||||
display="inline-flex"
|
||||
ml={8}
|
||||
mb={2}
|
||||
style={{ alignItems: 'center', verticalAlign: 'middle' }}
|
||||
>
|
||||
<IconLink width={20} height={20} />
|
||||
</Anchor>
|
||||
)}
|
||||
</Title>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function getIdFrom(children: ReactNode): string {
|
||||
const getTextContent = (node: ReactNode): string => {
|
||||
if (typeof node === 'string') return node
|
||||
if (typeof node === 'object' && node !== null && 'props' in node) {
|
||||
const child = node as { props?: { children?: ReactNode } }
|
||||
return getTextContent(child.props?.children)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const textContent = Array.isArray(children)
|
||||
? children.map(getTextContent).join('')
|
||||
: getTextContent(children)
|
||||
|
||||
return (
|
||||
textContent
|
||||
// normalize cause tuono build --static to hang
|
||||
// @see https://github.com/tuono-labs/tuono/issues/468
|
||||
// .normalize('NFKD')// separate accented characters into their base form and diacritical marks
|
||||
.replace(/[\u0300-\u036f]/g, '') // remove all the accents
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\./g, '-') // some titles (configuration) contain keypath, so replace dots with hyphens
|
||||
.replace(/[^a-z0-9 -]/g, '') // remove non-alphanumeric characters
|
||||
.replace(/\s+/g, '-') // replace spaces with hyphens
|
||||
.replace(/-+/g, '-') // remove consecutive hyphens
|
||||
)
|
||||
}
|
||||
|
||||
export const h = (order: 1 | 2 | 3 | 4 | 5 | 6): ElementType<TitleProps> => {
|
||||
function render(props: TitleProps): JSX.Element {
|
||||
return <MdxTitle order={order} {...props} />
|
||||
}
|
||||
render.displayName = 'H'
|
||||
|
||||
return render
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import MdxTitle, { h } from './MdxTitle'
|
||||
|
||||
export default MdxTitle
|
||||
|
||||
export { h }
|
||||
@@ -0,0 +1,5 @@
|
||||
.list {
|
||||
code {
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { HTMLAttributes, JSX } from 'react'
|
||||
import { List } from '@mantine/core'
|
||||
|
||||
import styles from './MdxUl.module.css'
|
||||
|
||||
export default function MdxUl(
|
||||
props: HTMLAttributes<HTMLUListElement>,
|
||||
): JSX.Element {
|
||||
return (
|
||||
<List
|
||||
{...props}
|
||||
className={styles.list}
|
||||
my={12}
|
||||
size="md"
|
||||
style={{ listStylePosition: 'outside' }}
|
||||
fw={300}
|
||||
withPadding
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxUl from './MdxUl'
|
||||
|
||||
export default MdxUl
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxProvider from './MdxProvider'
|
||||
|
||||
export default MdxProvider
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Head } from 'tuono'
|
||||
|
||||
interface MetaTagsProps {
|
||||
title: string
|
||||
@@ -15,7 +14,7 @@ export default function MetaTags({
|
||||
canonical,
|
||||
}: MetaTagsProps): ReactNode {
|
||||
return (
|
||||
<Head>
|
||||
<>
|
||||
<title>{title}</title>
|
||||
<link rel="canonical" href={canonical} />
|
||||
<meta
|
||||
@@ -41,6 +40,6 @@ export default function MetaTags({
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
|
||||
</Head>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MetaTags from './MetaTags'
|
||||
|
||||
export default MetaTags
|
||||
+2
-2
@@ -2,7 +2,7 @@ import type { JSX } from 'react'
|
||||
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
|
||||
import Actions from './actions'
|
||||
import NavbarActions from './NavbarActions'
|
||||
|
||||
interface NavbarProps {
|
||||
toggle: () => void
|
||||
@@ -24,7 +24,7 @@ export default function Navbar({ toggle }: NavbarProps): JSX.Element {
|
||||
</Button>
|
||||
<Box />
|
||||
<Flex align="center" gap={8}>
|
||||
<Actions />
|
||||
<NavbarActions />
|
||||
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
+2
-2
@@ -2,9 +2,9 @@ import type { JSX } from 'react'
|
||||
import { Flex, ActionIcon, Group } from '@mantine/core'
|
||||
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
|
||||
|
||||
import ThemeBtn from '../theme-btn'
|
||||
import ThemeBtn from '../ThemeBtn'
|
||||
|
||||
export default function Actions(): JSX.Element {
|
||||
export default function NavbarActions(): JSX.Element {
|
||||
return (
|
||||
<Flex gap={8}>
|
||||
<Group gap={8}>
|
||||
@@ -0,0 +1,3 @@
|
||||
import Navbar from './Navbar'
|
||||
|
||||
export default Navbar
|
||||
+7
-8
@@ -1,16 +1,16 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
||||
import { Link } from 'tuono'
|
||||
|
||||
interface NavigationButton {
|
||||
interface NavigationButtonData {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface NavigationButtonsProps {
|
||||
prev?: NavigationButton
|
||||
next?: NavigationButton
|
||||
prev?: NavigationButtonData
|
||||
next?: NavigationButtonData
|
||||
}
|
||||
|
||||
export default function NavigationButtons({
|
||||
@@ -25,7 +25,7 @@ export default function NavigationButtons({
|
||||
)
|
||||
}
|
||||
|
||||
interface NavigationButtonProps extends NavigationButton {
|
||||
interface NavigationBtnProps extends NavigationButtonData {
|
||||
type: 'next' | 'prev'
|
||||
}
|
||||
|
||||
@@ -33,16 +33,15 @@ const NavigationBtn = ({
|
||||
type,
|
||||
title,
|
||||
href,
|
||||
}: NavigationButtonProps): JSX.Element => {
|
||||
}: NavigationBtnProps): JSX.Element => {
|
||||
const heading = type === 'next' ? 'Next' : 'Previous'
|
||||
const textAlign = type === 'next' ? 'left' : 'right'
|
||||
const variant = type === 'next' ? 'filled' : 'outline'
|
||||
|
||||
return (
|
||||
<Button
|
||||
component={Link}
|
||||
fullWidth
|
||||
variant={variant}
|
||||
variant="outline"
|
||||
href={href}
|
||||
justify="space-between"
|
||||
h="auto"
|
||||
@@ -0,0 +1,3 @@
|
||||
import NavigationButtons from './NavigationButtons'
|
||||
|
||||
export default NavigationButtons
|
||||
@@ -0,0 +1,9 @@
|
||||
.wrapper {
|
||||
width: calc(100% - 220px);
|
||||
word-wrap: break-word;
|
||||
|
||||
/* var(--mantine-breakpoint-md) = 62em */
|
||||
@media (max-width: 62em) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { JSX, ReactNode } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Box, Container } from '@mantine/core'
|
||||
import { useViewportSize } from '@mantine/hooks'
|
||||
|
||||
import TableOfContents from '@/components/TableOfContents'
|
||||
|
||||
import classes from './PageWithTOC.module.css'
|
||||
|
||||
interface PageWithTOCProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function PageWithTOC({ children }: PageWithTOCProps): JSX.Element {
|
||||
const { width } = useViewportSize()
|
||||
const [footerHeight, setFooterHeight] = useState<number>(0)
|
||||
const mainRef = useRef<HTMLDivElement>(null)
|
||||
const height = `calc(100% - ${footerHeight}px)`
|
||||
|
||||
useEffect(() => {
|
||||
const footer = document.getElementById('app-footer')
|
||||
|
||||
if (footer) {
|
||||
setFooterHeight(footer.clientHeight)
|
||||
}
|
||||
}, [
|
||||
setFooterHeight,
|
||||
// Reload on window width resize
|
||||
width,
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
style={{ zIndex: 10, height }}
|
||||
pos="relative"
|
||||
ref={mainRef}
|
||||
mb={footerHeight}
|
||||
bg="var(--mantine-color-body)"
|
||||
>
|
||||
<Container
|
||||
size={1000}
|
||||
w="100%"
|
||||
display="flex"
|
||||
style={{ gap: 12, justifyContent: 'space-between' }}
|
||||
>
|
||||
<Box
|
||||
id="mdx-root"
|
||||
component="article"
|
||||
mt="xl"
|
||||
py={36}
|
||||
className={classes.wrapper}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
<TableOfContents />
|
||||
</Container>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { PageWithTOC } from './PageWithTOC'
|
||||
|
||||
export default PageWithTOC
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useEffect } from 'react'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { useRouter } from 'tuono'
|
||||
|
||||
export default function PostHogPageView(): null {
|
||||
const { pathname } = useRouter()
|
||||
const posthog = usePostHog()
|
||||
|
||||
// Track pageviews
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (pathname && posthog) {
|
||||
const url = window.origin + pathname
|
||||
|
||||
posthog.capture('$pageview', { $current_url: url })
|
||||
}
|
||||
}, [pathname, posthog])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { JSX, ReactNode } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { PostHogProvider as PostHogLibraryProvider } from 'posthog-js/react'
|
||||
import posthogJs from 'posthog-js'
|
||||
|
||||
interface PostHogProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function PostHogProvider({
|
||||
children,
|
||||
}: PostHogProviderProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (import.meta.env.VITE_ENABLE_POSTHOG === 'true') {
|
||||
posthogJs.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
|
||||
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
|
||||
persistence: 'memory', // Cookieless tracking
|
||||
disable_persistence: true, // Disable persistence
|
||||
loaded: (ph) => {
|
||||
if (import.meta.env.VITE_ENV === 'development') ph.debug()
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PostHogLibraryProvider client={posthogJs}>
|
||||
{children}
|
||||
</PostHogLibraryProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import PostHogProvider from './PostHogProvider'
|
||||
import PostHogPageView from './PostHogPageView'
|
||||
|
||||
export { PostHogProvider, PostHogPageView }
|
||||
+12
-7
@@ -14,8 +14,8 @@ import {
|
||||
import { IconX } from '@tabler/icons-react'
|
||||
import { useMediaQuery } from '@mantine/hooks'
|
||||
|
||||
import { sidebarElements } from './config'
|
||||
import SidebarLink from './sidebar-link'
|
||||
import { sidebarElements } from './sidebarElements'
|
||||
import SidebarLink from './SidebarLink'
|
||||
|
||||
interface SidebarProps {
|
||||
close: () => void
|
||||
@@ -50,26 +50,30 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
|
||||
<AppShell.Section component={ScrollArea}>
|
||||
{sidebarElements.map((el, i) => {
|
||||
if (el.type === 'divider') {
|
||||
return <Divider my="md" mx={10} />
|
||||
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
|
||||
}
|
||||
|
||||
if (el.type === 'title') {
|
||||
return (
|
||||
<Text
|
||||
key={`${el.type}-${i}`}
|
||||
size="xs"
|
||||
fw={700}
|
||||
fz={12}
|
||||
pl={12}
|
||||
py={5}
|
||||
children={el.label}
|
||||
/>
|
||||
>
|
||||
{el.label}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
if (el.children?.length) {
|
||||
return (
|
||||
<SidebarLink
|
||||
key={`${el.type}-${i}`}
|
||||
href={el.href}
|
||||
label={el.label}
|
||||
key={i}
|
||||
leftSection={el.leftIcon}
|
||||
>
|
||||
{el.children.map((child, index) => (
|
||||
@@ -83,12 +87,13 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
|
||||
</SidebarLink>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarLink
|
||||
key={`${el.type}-${i}`}
|
||||
href={el.href}
|
||||
label={el.label}
|
||||
onClick={close}
|
||||
key={i}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
+1
-1
@@ -4,7 +4,7 @@ import clsx from 'clsx'
|
||||
import { Link, useRouter } from 'tuono'
|
||||
import { IconChevronRight } from '@tabler/icons-react'
|
||||
|
||||
import styles from './sidebar-link.module.css'
|
||||
import styles from './SidebarLink.module.css'
|
||||
|
||||
interface SidebarLinkProps {
|
||||
label: string
|
||||
@@ -0,0 +1,3 @@
|
||||
import Sidebar from './Sidebar'
|
||||
|
||||
export default Sidebar
|
||||
+44
-49
@@ -33,12 +33,12 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Installation',
|
||||
href: '/documentation/installation',
|
||||
href: '/documentation/getting-started/installation',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'How is tuono different?',
|
||||
href: '/documentation/how-is-tuono-different',
|
||||
label: 'Setup project',
|
||||
href: '/documentation/getting-started/setup-project',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -108,24 +108,9 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Defining routes',
|
||||
label: 'Pages and layout',
|
||||
href: '/documentation/routing/defining-routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Link and navigation',
|
||||
href: '/documentation/routing/link-and-navigation',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Pages',
|
||||
href: '/documentation/routing/pages',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Loading state',
|
||||
href: '/documentation/routing/loading-state',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Dynamic routes',
|
||||
@@ -133,13 +118,8 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Layouts',
|
||||
href: '/documentation/routing/layouts',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Redirecting',
|
||||
href: '/documentation/routing/redirecting',
|
||||
label: 'Link and navigation',
|
||||
href: '/documentation/routing/link-and-navigation',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -150,13 +130,13 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Server side rendering',
|
||||
label: 'Server side rendering (SSR)',
|
||||
href: '/documentation/rendering/server-side-rendering',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Static site rendering',
|
||||
href: '/documentation/rendering/static-site-rendering',
|
||||
label: 'Static site generation (SSG)',
|
||||
href: '/documentation/rendering/static-site-generation',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -170,6 +150,11 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
label: 'CSS modules',
|
||||
href: '/documentation/styles/css-modules',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Tailwind CSS',
|
||||
href: '/documentation/styles/tailwind-css',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -184,18 +169,6 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Core concepts',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Multithreading',
|
||||
href: '/documentation/core-concepts/multithreading',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{ type: 'title', label: 'API reference' },
|
||||
{
|
||||
@@ -203,11 +176,6 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
label: 'Components',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Head',
|
||||
href: '/documentation/components/head',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Link',
|
||||
@@ -217,13 +185,35 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Hooks',
|
||||
label: 'Functions',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'useRouter',
|
||||
href: '/documentation/hooks/use-router',
|
||||
href: '/documentation/functions/use-router',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'dynamic',
|
||||
href: '/documentation/functions/dynamic',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Server utilities',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Response',
|
||||
href: '/documentation/server-utilities/response',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Request',
|
||||
href: '/documentation/server-utilities/request',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -239,7 +229,7 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'tuono.config.ts',
|
||||
label: 'Configuration',
|
||||
href: '/documentation/configuration',
|
||||
},
|
||||
|
||||
@@ -260,6 +250,11 @@ export const sidebarElements: Array<SidebarElement> = [
|
||||
label: 'Local development',
|
||||
href: '/documentation/contributing/local-development',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Pull requests',
|
||||
href: '/documentation/contributing/pull-requests',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
+26
-40
@@ -1,47 +1,26 @@
|
||||
/*
|
||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||
*/
|
||||
.wrapper {
|
||||
padding-left: var(--mantine-spacing-md);
|
||||
position: sticky;
|
||||
top: var(--mantine-spacing-xl);
|
||||
right: 0;
|
||||
padding-top: 55px;
|
||||
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
|
||||
width: 200px;
|
||||
flex: 0 0 calc(250px - 20px);
|
||||
min-width: 220px;
|
||||
max-width: 220px;
|
||||
height: 100vh;
|
||||
|
||||
@mixin rtl {
|
||||
padding-left: 0;
|
||||
padding-right: var(--mantine-spacing-md);
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&[data-with-tabs] {
|
||||
padding-top: 0;
|
||||
top: calc(var(--mantine-spacing-xl) + 60px);
|
||||
}
|
||||
}
|
||||
|
||||
.inner {
|
||||
padding-bottom: var(--mantine-spacing-xl);
|
||||
padding-left: var(--mantine-spacing-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
@mixin rtl {
|
||||
padding-left: 0;
|
||||
padding-right: var(--mantine-spacing-md);
|
||||
/* var(--mantine-breakpoint-md) = 62em */
|
||||
@media (max-width: 62em) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
display: block;
|
||||
border-left: 1px solid transparent;
|
||||
padding: 8px var(--mantine-spacing-md);
|
||||
margin-left: -1px;
|
||||
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
|
||||
border-top-right-radius: var(--mantine-radius-sm);
|
||||
border-bottom-right-radius: var(--mantine-radius-sm);
|
||||
|
||||
@@ -58,10 +37,19 @@
|
||||
border-right: 1px solid transparent;
|
||||
margin-left: 0;
|
||||
margin-right: -1px;
|
||||
border-top-left-radius: var(--mantine-radius-sm);
|
||||
border-bottom-left-radius: var(--mantine-radius-sm);
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--mantine-color-violet-1);
|
||||
|
||||
@mixin light {
|
||||
color: var(--mantine-color-black);
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
color: var(--mantine-color-violet-1);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-active] {
|
||||
@@ -69,20 +57,18 @@
|
||||
|
||||
@mixin light {
|
||||
color: var(--mantine-color-violet-8);
|
||||
background-color: var(--mantine-color-violet-0);
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
color: var(--mantine-color-violet-1);
|
||||
background-color: #534a6d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
.inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: var(--mantine-spacing-md);
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { JSX, MouseEventHandler } from 'react'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRouter } from 'tuono'
|
||||
import { Box, ScrollArea, Text } from '@mantine/core'
|
||||
|
||||
import { getHeadings } from './getHeadings'
|
||||
import type { Heading } from './getHeadings'
|
||||
|
||||
import classes from './TableOfContents.module.css'
|
||||
|
||||
export function TableOfContents(): JSX.Element | null {
|
||||
const [activeHeadingIndex, setActiveHeadingIndex] = useState<number | null>(
|
||||
null,
|
||||
)
|
||||
const [headings, setHeadings] = useState<Array<Heading>>([])
|
||||
const headingsRef = useRef<Array<HTMLElement>>([])
|
||||
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
const _headings = getHeadings()
|
||||
setHeadings(_headings)
|
||||
headingsRef.current = _headings.map((heading) => heading.getNode())
|
||||
|
||||
if (observerRef.current) {
|
||||
observerRef.current.disconnect()
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visibleEntries = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
|
||||
|
||||
if (visibleEntries.length > 0) {
|
||||
setActiveHeadingIndex(
|
||||
_headings.findIndex((h) => h.id === visibleEntries[0].target.id),
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
rootMargin: '-50px 0px -80% 0px',
|
||||
threshold: [0.1, 0.5, 1.0],
|
||||
},
|
||||
)
|
||||
|
||||
headingsRef.current.forEach((node) => {
|
||||
observer.observe(node)
|
||||
})
|
||||
observerRef.current = observer
|
||||
|
||||
const handleHashChange = (): void => {
|
||||
setTimeout(() => {
|
||||
observerRef.current?.disconnect()
|
||||
observerRef.current = observer
|
||||
headingsRef.current.forEach((node) => {
|
||||
observer.observe(node)
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', handleHashChange)
|
||||
|
||||
return (): void => {
|
||||
observer.disconnect()
|
||||
window.removeEventListener('hashchange', handleHashChange)
|
||||
}
|
||||
}, [router.pathname])
|
||||
|
||||
const handleHeadingClick: MouseEventHandler<HTMLAnchorElement> = (
|
||||
event,
|
||||
): void => {
|
||||
event.preventDefault()
|
||||
const target = event.target as HTMLAnchorElement
|
||||
const href = target.getAttribute('href') as string
|
||||
const targetHeading = document.querySelector(href)
|
||||
|
||||
if (!targetHeading) return
|
||||
|
||||
history.pushState(null, '', href)
|
||||
targetHeading.scrollIntoView({ behavior: 'instant', block: 'start' })
|
||||
}
|
||||
|
||||
// Table of contents will not be displayed if the page has one or fewer headings.
|
||||
if (headings.length <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Box component="nav" className={classes.wrapper}>
|
||||
<ScrollArea style={{ height: 'calc(100% - 60px)' }}>
|
||||
<div className={classes.inner}>
|
||||
<div>
|
||||
<Text className={classes.title} mb={8}>
|
||||
On this page
|
||||
</Text>
|
||||
<div className={classes.items}>
|
||||
{headings.slice(1).map((heading, index) => (
|
||||
<Text
|
||||
key={heading.id}
|
||||
component="a"
|
||||
fz="sm"
|
||||
w="fit-content"
|
||||
py={4}
|
||||
className={classes.link}
|
||||
mod={{ active: activeHeadingIndex === index + 1 }}
|
||||
href={`#${heading.id}`}
|
||||
onClick={handleHeadingClick}
|
||||
style={{
|
||||
paddingLeft: `calc(${heading.order - 1} * var(--mantine-spacing-md))`,
|
||||
}}
|
||||
>
|
||||
{heading.content}
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
+17
-5
@@ -2,20 +2,33 @@
|
||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||
*/
|
||||
export interface Heading {
|
||||
depth: number
|
||||
order: number
|
||||
content: string
|
||||
id: string
|
||||
getNode: () => HTMLHeadingElement
|
||||
}
|
||||
|
||||
function getCleanedText(element: HTMLElement): string {
|
||||
const clone = element.cloneNode(true) as HTMLElement
|
||||
|
||||
clone.querySelectorAll('code, pre').forEach((codeBlock) => {
|
||||
const textNode = document.createTextNode(codeBlock.textContent || '')
|
||||
codeBlock.replaceWith(textNode)
|
||||
})
|
||||
|
||||
return clone.textContent?.trim() || ''
|
||||
}
|
||||
|
||||
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
||||
const result: Array<Heading> = []
|
||||
|
||||
for (const heading of headings) {
|
||||
if (heading.id) {
|
||||
const order = parseInt(heading.getAttribute('data-order') || '1', 10)
|
||||
|
||||
if (order <= 3 && heading.id) {
|
||||
result.push({
|
||||
depth: parseInt(heading.getAttribute('data-order') || '1', 10),
|
||||
content: heading.getAttribute('data-heading') || '',
|
||||
order,
|
||||
content: getCleanedText(heading),
|
||||
id: heading.id,
|
||||
getNode: () =>
|
||||
document.getElementById(heading.id) as HTMLHeadingElement,
|
||||
@@ -28,7 +41,6 @@ function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
||||
|
||||
export function getHeadings(): Array<Heading> {
|
||||
const root = document.getElementById('mdx-root')
|
||||
console.log(root)
|
||||
|
||||
if (!root) {
|
||||
return []
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||
*/
|
||||
import { TableOfContents } from './table-of-content'
|
||||
import { TableOfContents } from './TableOfContents'
|
||||
|
||||
export default TableOfContents
|
||||
+3
-3
@@ -7,7 +7,7 @@ import {
|
||||
import { IconSun, IconMoon } from '@tabler/icons-react'
|
||||
import cx from 'clsx'
|
||||
|
||||
import classes from './theme-btn.module.css'
|
||||
import classes from './ThemeBtn.module.css'
|
||||
|
||||
export default function ThemeBtn(): JSX.Element {
|
||||
const { setColorScheme } = useMantineColorScheme()
|
||||
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
||||
}
|
||||
}}
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
@@ -0,0 +1,3 @@
|
||||
import ThemeBtn from './ThemeBtn'
|
||||
|
||||
export default ThemeBtn
|
||||
@@ -1,84 +0,0 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Breadcrumbs, Button } from '@mantine/core'
|
||||
import { Link, Head } from 'tuono'
|
||||
|
||||
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
||||
|
||||
interface Breadcrumb {
|
||||
href?: string
|
||||
label: string
|
||||
}
|
||||
interface BreadcrumbsProps {
|
||||
breadcrumbs: Array<Breadcrumb>
|
||||
}
|
||||
|
||||
export default function TuonoBreadcrumbs({
|
||||
breadcrumbs = [],
|
||||
}: BreadcrumbsProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<script type="application/ld+json">
|
||||
{`{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Tuono - The React/Rust fullstack framework",
|
||||
"item": "https://tuono.dev"
|
||||
}${breadcrumbs.length > 0 ? ',' : ''}
|
||||
${breadcrumbs
|
||||
.map((br, i) =>
|
||||
JSON.stringify({
|
||||
'@type': 'ListItem',
|
||||
position: i + 3,
|
||||
name: br.label,
|
||||
item: br.href ? `https://tuono.dev${br.href}` : undefined,
|
||||
}),
|
||||
)
|
||||
.join(',')}]
|
||||
}
|
||||
`}
|
||||
</script>
|
||||
</Head>
|
||||
<Breadcrumbs
|
||||
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
||||
mb="md"
|
||||
mt="md"
|
||||
>
|
||||
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
|
||||
<IconBolt />
|
||||
</Button>
|
||||
{breadcrumbs.map((br) => (
|
||||
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface BreadcrumbElementProps {
|
||||
href?: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export function BreadcrumbElement({
|
||||
href,
|
||||
label,
|
||||
}: BreadcrumbElementProps): JSX.Element {
|
||||
if (href) {
|
||||
return (
|
||||
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
|
||||
{label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button component="span" variant="light" radius="xl">
|
||||
{label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import Breadcrumbs, { BreadcrumbElement } from './breadcrumbs'
|
||||
|
||||
export default Breadcrumbs
|
||||
|
||||
export { BreadcrumbElement as Element }
|
||||
@@ -1,3 +0,0 @@
|
||||
import EditPage from './edit-page'
|
||||
|
||||
export default EditPage
|
||||
@@ -1,3 +0,0 @@
|
||||
import Hero from './hero'
|
||||
|
||||
export default Hero
|
||||
@@ -1,3 +0,0 @@
|
||||
import MdxProvider from './mdx-provider'
|
||||
|
||||
export default MdxProvider
|
||||
@@ -1,3 +0,0 @@
|
||||
import MdxCode from './mdx-code'
|
||||
|
||||
export default MdxCode
|
||||
@@ -1,3 +0,0 @@
|
||||
import MdxLink from './mdx-link'
|
||||
|
||||
export default MdxLink
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { JSX, AnchorHTMLAttributes } from 'react'
|
||||
import { Button } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
import { IconExternalLink } from '@tabler/icons-react'
|
||||
|
||||
export default function MdxLink(
|
||||
props: AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
): JSX.Element {
|
||||
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
{...props}
|
||||
target="_blank"
|
||||
rightSection={
|
||||
<IconExternalLink size="16px" style={{ marginLeft: -5 }} />
|
||||
}
|
||||
variant="transparent"
|
||||
style={{ height: '20px' }}
|
||||
mt={-2}
|
||||
p={0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
component={Link}
|
||||
{...props}
|
||||
target="_blank"
|
||||
variant="transparent"
|
||||
style={{ height: '20px' }}
|
||||
mt={-2}
|
||||
p={0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import MdxPre from './mdx-pre'
|
||||
|
||||
export default MdxPre
|
||||
@@ -1,5 +0,0 @@
|
||||
.pre {
|
||||
code {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import MdxQuote from './mdx-quote'
|
||||
|
||||
export default MdxQuote
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { JSX, HTMLAttributes } from 'react'
|
||||
import { Blockquote, Space } from '@mantine/core'
|
||||
|
||||
export default function MdxQuote(
|
||||
props: HTMLAttributes<HTMLQuoteElement>,
|
||||
): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Blockquote
|
||||
color="violet"
|
||||
py={1}
|
||||
px={20}
|
||||
mt={30}
|
||||
iconSize={30}
|
||||
{...props}
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
<Space h="md" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import MdxTitle, { h } from './mdx-title'
|
||||
|
||||
export default MdxTitle
|
||||
|
||||
export { h }
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Title, type TitleProps } from '@mantine/core'
|
||||
|
||||
export default function MdxTitle(props: TitleProps): JSX.Element {
|
||||
return (
|
||||
<Title
|
||||
data-heading={props.children}
|
||||
data-order={props.order}
|
||||
mt={20}
|
||||
{...props}
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
id={String(props.children ?? '')
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const h =
|
||||
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
|
||||
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user