mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-29 13:52:46 -07:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f219ab83f8 | |||
| 88ab0538d9 | |||
| 90ee21b577 | |||
| 79f4d96742 | |||
| 61707ffb96 | |||
| 38c1d68ad0 | |||
| 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 |
@@ -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
|
||||||
@@ -32,55 +32,24 @@ body:
|
|||||||
description: If applicable, add screenshots to help explain your problem.
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: textarea
|
||||||
id: tuono-version
|
id: system-info
|
||||||
attributes:
|
attributes:
|
||||||
label: Tuono version
|
label: System Info
|
||||||
placeholder: '[e.g. 0.4.0]'
|
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||||
|
render: shell
|
||||||
|
placeholder: System, Binaries, Browsers
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: input
|
- type: textarea
|
||||||
id: os-version
|
id: system-info-rust
|
||||||
attributes:
|
attributes:
|
||||||
label: OS
|
label: System info (Rust)
|
||||||
placeholder: '[e.g. MacOS, Windows]'
|
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||||
|
render: shell
|
||||||
|
placeholder: 'rustc, cargo and tuono binary version'
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- 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
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional-context
|
id: additional-context
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -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
|
Explain the context and why you're making that change.
|
||||||
you're trying to solve? If a new feature is being added, describe the intended
|
What is the problem you're trying to solve?
|
||||||
use case that feature fulfills.
|
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:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
# match
|
||||||
|
# - all files inside `apps/documentation` and `.github`
|
||||||
|
# - all files in the root expect `Cargo.toml`
|
||||||
paths:
|
paths:
|
||||||
# ⬇ Keep paths in sync with `typescript.yml`
|
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '*'
|
- '*'
|
||||||
- '!Cargo.toml'
|
- '!Cargo.toml'
|
||||||
# ⬆ Keep paths in sync with `typescript.yml`
|
|
||||||
- 'apps/documentation/**'
|
- 'apps/documentation/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
# same as `on.push.paths`
|
||||||
paths:
|
paths:
|
||||||
# ⬇ Keep paths in sync with `typescript.yml`
|
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '*'
|
- '*'
|
||||||
- '!Cargo.toml'
|
- '!Cargo.toml'
|
||||||
# ⬆ Keep paths in sync with `typescript.yml`
|
|
||||||
- 'apps/documentation/**'
|
- 'apps/documentation/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Check build
|
name: Check build
|
||||||
@@ -37,7 +41,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install tuono
|
- name: Install tuono
|
||||||
run: cargo install tuono@0.15.0
|
run: cargo install tuono@0.17.6
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
working-directory: ./apps/documentation
|
working-directory: ./apps/documentation
|
||||||
+1
-1
@@ -27,7 +27,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install tuono
|
- name: Install tuono
|
||||||
run: cargo install tuono@0.16.0
|
run: cargo install tuono@0.17.6
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
working-directory: ./apps/documentation
|
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:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- '*'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/**'
|
concurrency:
|
||||||
- '*'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
format:
|
||||||
name: Check format
|
name: Check format
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
@@ -31,3 +29,23 @@ jobs:
|
|||||||
|
|
||||||
- name: Test project
|
- name: Test project
|
||||||
run: pnpm repo:root:format:check
|
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:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'Cargo.toml'
|
|
||||||
- 'crates/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/**'
|
concurrency:
|
||||||
- 'Cargo.toml'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
- 'crates/**'
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@@ -40,6 +36,7 @@ jobs:
|
|||||||
os: 'ubuntu-latest'
|
os: 'ubuntu-latest'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -56,6 +53,7 @@ jobs:
|
|||||||
lint_and_fmt:
|
lint_and_fmt:
|
||||||
name: Check format and lint rust crates
|
name: Check format and lint rust crates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -66,3 +64,23 @@ jobs:
|
|||||||
|
|
||||||
- run: cargo fmt --all -- --check
|
- run: cargo fmt --all -- --check
|
||||||
- run: cargo clippy -- -D warnings
|
- 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:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
spell_checking:
|
spell_checking:
|
||||||
name: LanguageTool
|
name: LanguageTool
|
||||||
|
|||||||
@@ -4,26 +4,15 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
paths:
|
|
||||||
# ⬇ Keep paths in sync with `ci-documentation.yml`
|
|
||||||
- '.github/**'
|
|
||||||
- '*'
|
|
||||||
- '!Cargo.toml'
|
|
||||||
# ⬆ Keep paths in sync with `ci-documentation.yml`
|
|
||||||
- 'packages/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
# ⬇ Keep paths in sync with `ci-documentation.yml`
|
concurrency:
|
||||||
- '.github/**'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
- '*'
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
- '!Cargo.toml'
|
|
||||||
# ⬆ Keep paths in sync with `ci-documentation.yml`
|
|
||||||
- 'packages/**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_test:
|
build_and_test:
|
||||||
name: Check build and test
|
name: Check build and test
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
@@ -34,6 +23,7 @@ jobs:
|
|||||||
- 'windows-latest'
|
- 'windows-latest'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -68,3 +58,23 @@ jobs:
|
|||||||
|
|
||||||
- name: Types
|
- name: Types
|
||||||
run: pnpm 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
|
examples/*/pnpm-lock.yaml
|
||||||
|
|
||||||
|
vite.config.ts.timestamp*
|
||||||
|
|
||||||
## Rust related ignores
|
## Rust related ignores
|
||||||
|
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
|
|||||||
+3
-1
@@ -3,4 +3,6 @@ pnpm-lock.yaml
|
|||||||
dist
|
dist
|
||||||
.tuono
|
.tuono
|
||||||
|
|
||||||
packages/lazy-fn-vite-plugin/tests/sources/*
|
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?**
|
**Did you write a patch that fixes a bug?**
|
||||||
|
|
||||||
- Open a new GitHub pull request with the patch.
|
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
- 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
|
|
||||||
|
|||||||
+9
-8
@@ -1,14 +1,15 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/tuono",
|
"crates/tuono",
|
||||||
"crates/tuono_lib",
|
"crates/tuono_internal",
|
||||||
"crates/tuono_lib_macros",
|
"crates/tuono_lib",
|
||||||
|
"crates/tuono_lib_macros"
|
||||||
]
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
"apps/documentation",
|
"apps/documentation",
|
||||||
"examples/with-mdx",
|
"examples/with-mdx",
|
||||||
"examples/tuono-app",
|
"examples/tuono-app",
|
||||||
"examples/tuono-tutorial",
|
"examples/tuono-tutorial",
|
||||||
"benches/tuono"
|
"examples/with-tailwind",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -24,51 +24,34 @@ Some of its features are:
|
|||||||
- 🧬 Server Side Rendering
|
- 🧬 Server Side Rendering
|
||||||
- 🔥 Hot Module Reload
|
- 🔥 Hot Module Reload
|
||||||
|
|
||||||
## 📖 Documentation
|
## Documentation
|
||||||
|
|
||||||
The [documentation](https://tuono.dev/) is available on
|
The [documentation](https://tuono.dev/) is available on
|
||||||
[tuono.dev](https://tuono.dev/).
|
[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.
|
```sh
|
||||||
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
|
cargo install tuono
|
||||||
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)).
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
Create a new project with:
|
||||||
To download it is required [cargo](https://doc.rust-lang.org/cargo/), which is the [rust](https://www.rust-lang.org/)
|
|
||||||
package manager.
|
|
||||||
|
|
||||||
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
|
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
|
||||||
[examples](https://github.com/tuono-labs/tuono/tree/main/examples) folder).
|
|
||||||
|
|
||||||
Then, to run the local development environment, install the JS dependencies (`npm install`) and run inside the project folder `tuono dev`
|
```sh
|
||||||
|
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`.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
path = ".tuono/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tuono_lib = "0.16.2"
|
tuono_lib = "0.17.6"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
time = { version = "0.3", features = ["macros"] }
|
time = { version = "0.3", features = ["macros"] }
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
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]
|
|
||||||
```
|
|
||||||
@@ -11,21 +11,22 @@
|
|||||||
"types": "tsc --noEmit"
|
"types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/code-highlight": "7.14.2",
|
"@mantine/code-highlight": "7.16.1",
|
||||||
"@mantine/core": "7.14.2",
|
"@mantine/core": "7.16.1",
|
||||||
"@mantine/hooks": "7.14.2",
|
"@mantine/hooks": "7.16.1",
|
||||||
"@mdx-js/react": "3.1.0",
|
"@mdx-js/react": "3.1.0",
|
||||||
"@tabler/icons-react": "3.22.0",
|
"@tabler/icons-react": "3.28.1",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"react": "18.3.1",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.0.0",
|
||||||
"tuono": "npm:tuono@0.16.2"
|
"remark-gfm": "4.0.0",
|
||||||
|
"tuono": "npm:tuono@0.17.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mdx-js/rollup": "3.1.0",
|
"@mdx-js/rollup": "3.1.0",
|
||||||
"@types/react": "18.3.13",
|
"@types/react": "19.0.8",
|
||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "19.0.3",
|
||||||
"postcss": "8.4.49",
|
"postcss": "8.5.1",
|
||||||
"postcss-preset-mantine": "1.17.0",
|
"postcss-preset-mantine": "1.17.0",
|
||||||
"postcss-simple-vars": "7.0.1"
|
"postcss-simple-vars": "7.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import EditPage from './EditPage'
|
||||||
|
|
||||||
|
export default EditPage
|
||||||
+1
-1
@@ -36,7 +36,7 @@ export default function Hero(): JSX.Element {
|
|||||||
size="lg"
|
size="lg"
|
||||||
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
||||||
color="gray"
|
color="gray"
|
||||||
leftSection="$ cargo install tuono"
|
leftSection="cargo install tuono"
|
||||||
rightSection={
|
rightSection={
|
||||||
copied ? (
|
copied ? (
|
||||||
<IconCheck style={{ width: rem(20) }} />
|
<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'
|
import { Text, type TextProps } from '@mantine/core'
|
||||||
|
|
||||||
export default function MdxBold(props: TextProps): JSX.Element {
|
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,3 @@
|
|||||||
|
import MdxLink from './MdxLink'
|
||||||
|
|
||||||
|
export default MdxLink
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX } from 'react'
|
||||||
import { CodeHighlight } from '@mantine/code-highlight'
|
import { CodeHighlight } from '@mantine/code-highlight'
|
||||||
|
|
||||||
import styles from './mdx-pre.module.css'
|
import styles from './MdxPre.module.css'
|
||||||
|
|
||||||
interface PreProps {
|
interface PreProps {
|
||||||
children: {
|
children: {
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxPre from './MdxPre'
|
||||||
|
|
||||||
|
export default MdxPre
|
||||||
+13
-7
@@ -1,12 +1,13 @@
|
|||||||
import type { JSX, ReactNode } from 'react'
|
import type { JSX, ReactNode } from 'react'
|
||||||
import { MDXProvider } from '@mdx-js/react'
|
import { MDXProvider } from '@mdx-js/react'
|
||||||
|
|
||||||
import MdxLink from './mdx-link'
|
import MdxLink from './MdxLink'
|
||||||
import MdxPre from './mdx-pre'
|
import MdxPre from './MdxPre'
|
||||||
import MdxQuote from './mdx-quote'
|
import MdxQuote from './MdxQuote'
|
||||||
import MdxCode from './mdx-code'
|
import MdxCode from './MdxCode'
|
||||||
import { h } from './mdx-title'
|
import { h } from './MdxTitle'
|
||||||
import MdxBold from './mdx-bold/mdx-bold'
|
import MdxBold from './MdxBold'
|
||||||
|
import MdxTable from './MdxTable'
|
||||||
|
|
||||||
interface MdxProviderProps {
|
interface MdxProviderProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -19,7 +20,6 @@ export default function MdxProvider({
|
|||||||
<MDXProvider
|
<MDXProvider
|
||||||
components={{
|
components={{
|
||||||
a: MdxLink,
|
a: MdxLink,
|
||||||
// @ts-expect-error: useless finding the correct props types
|
|
||||||
pre: MdxPre,
|
pre: MdxPre,
|
||||||
blockquote: MdxQuote,
|
blockquote: MdxQuote,
|
||||||
code: MdxCode,
|
code: MdxCode,
|
||||||
@@ -30,6 +30,12 @@ export default function MdxProvider({
|
|||||||
h5: h(5),
|
h5: h(5),
|
||||||
h6: h(6),
|
h6: h(6),
|
||||||
strong: MdxBold,
|
strong: MdxBold,
|
||||||
|
table: MdxTable,
|
||||||
|
thead: MdxTable.Thead,
|
||||||
|
tbody: MdxTable.Tbody,
|
||||||
|
tr: MdxTable.Tr,
|
||||||
|
th: MdxTable.Th,
|
||||||
|
td: MdxTable.Td,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -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,50 @@
|
|||||||
|
import type { ElementType, JSX, ReactNode } from 'react'
|
||||||
|
import { Title, type TitleProps } from '@mantine/core'
|
||||||
|
|
||||||
|
export default function MdxTitle(props: TitleProps): JSX.Element {
|
||||||
|
const headingId = getIdFrom(props.children)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Title
|
||||||
|
data-heading={headingId}
|
||||||
|
data-order={props.order}
|
||||||
|
style={{ scrollMargin: 70 }}
|
||||||
|
{...props}
|
||||||
|
id={headingId}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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('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,3 @@
|
|||||||
|
import MdxProvider from './MdxProvider'
|
||||||
|
|
||||||
|
export default MdxProvider
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { JSX, ReactNode } from 'react'
|
||||||
|
import { Box, Container } from '@mantine/core'
|
||||||
|
|
||||||
|
import TableOfContents from '@/components/TableOfContents'
|
||||||
|
|
||||||
|
import EditPage from '../EditPage'
|
||||||
|
import MdxProvider from '../MdxProvider'
|
||||||
|
|
||||||
|
interface MdxWrapperProps {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MdxWrapper({ children }: MdxWrapperProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Container size={1000} w="100%" display="flex" style={{ gap: 12 }}>
|
||||||
|
<Box
|
||||||
|
id="mdx-root"
|
||||||
|
component="article"
|
||||||
|
mt="xl"
|
||||||
|
px={16}
|
||||||
|
py={36}
|
||||||
|
style={{ maxWidth: '100%' }}
|
||||||
|
>
|
||||||
|
<MdxProvider>{children}</MdxProvider>
|
||||||
|
<EditPage />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<TableOfContents />
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { MdxWrapper } from './MdxWrapper'
|
||||||
|
|
||||||
|
export default MdxWrapper
|
||||||
+2
-3
@@ -1,5 +1,4 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { Head } from 'tuono'
|
|
||||||
|
|
||||||
interface MetaTagsProps {
|
interface MetaTagsProps {
|
||||||
title: string
|
title: string
|
||||||
@@ -15,7 +14,7 @@ export default function MetaTags({
|
|||||||
canonical,
|
canonical,
|
||||||
}: MetaTagsProps): ReactNode {
|
}: MetaTagsProps): ReactNode {
|
||||||
return (
|
return (
|
||||||
<Head>
|
<>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="canonical" href={canonical} />
|
<link rel="canonical" href={canonical} />
|
||||||
<meta
|
<meta
|
||||||
@@ -41,6 +40,6 @@ export default function MetaTags({
|
|||||||
<meta name="twitter:title" content={title} />
|
<meta name="twitter:title" content={title} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
|
<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 { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
|
||||||
import { Link } from 'tuono'
|
import { Link } from 'tuono'
|
||||||
|
|
||||||
import Actions from './actions'
|
import NavbarActions from './NavbarActions'
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
toggle: () => void
|
toggle: () => void
|
||||||
@@ -24,7 +24,7 @@ export default function Navbar({ toggle }: NavbarProps): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
<Box />
|
<Box />
|
||||||
<Flex align="center" gap={8}>
|
<Flex align="center" gap={8}>
|
||||||
<Actions />
|
<NavbarActions />
|
||||||
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
|
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
+2
-2
@@ -2,9 +2,9 @@ import type { JSX } from 'react'
|
|||||||
import { Flex, ActionIcon, Group } from '@mantine/core'
|
import { Flex, ActionIcon, Group } from '@mantine/core'
|
||||||
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
|
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 (
|
return (
|
||||||
<Flex gap={8}>
|
<Flex gap={8}>
|
||||||
<Group gap={8}>
|
<Group gap={8}>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Navbar from './Navbar'
|
||||||
|
|
||||||
|
export default Navbar
|
||||||
+6
-6
@@ -1,16 +1,16 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX } from 'react'
|
||||||
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
||||||
import { Link } from 'tuono'
|
|
||||||
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
||||||
|
import { Link } from 'tuono'
|
||||||
|
|
||||||
interface NavigationButton {
|
interface NavigationButtonData {
|
||||||
href: string
|
href: string
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavigationButtonsProps {
|
interface NavigationButtonsProps {
|
||||||
prev?: NavigationButton
|
prev?: NavigationButtonData
|
||||||
next?: NavigationButton
|
next?: NavigationButtonData
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NavigationButtons({
|
export default function NavigationButtons({
|
||||||
@@ -25,7 +25,7 @@ export default function NavigationButtons({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavigationButtonProps extends NavigationButton {
|
interface NavigationBtnProps extends NavigationButtonData {
|
||||||
type: 'next' | 'prev'
|
type: 'next' | 'prev'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ const NavigationBtn = ({
|
|||||||
type,
|
type,
|
||||||
title,
|
title,
|
||||||
href,
|
href,
|
||||||
}: NavigationButtonProps): JSX.Element => {
|
}: NavigationBtnProps): JSX.Element => {
|
||||||
const heading = type === 'next' ? 'Next' : 'Previous'
|
const heading = type === 'next' ? 'Next' : 'Previous'
|
||||||
const textAlign = type === 'next' ? 'left' : 'right'
|
const textAlign = type === 'next' ? 'left' : 'right'
|
||||||
const variant = type === 'next' ? 'filled' : 'outline'
|
const variant = type === 'next' ? 'filled' : 'outline'
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import NavigationButtons from './NavigationButtons'
|
||||||
|
|
||||||
|
export default NavigationButtons
|
||||||
+12
-7
@@ -14,8 +14,8 @@ import {
|
|||||||
import { IconX } from '@tabler/icons-react'
|
import { IconX } from '@tabler/icons-react'
|
||||||
import { useMediaQuery } from '@mantine/hooks'
|
import { useMediaQuery } from '@mantine/hooks'
|
||||||
|
|
||||||
import { sidebarElements } from './config'
|
import { sidebarElements } from './sidebarElements'
|
||||||
import SidebarLink from './sidebar-link'
|
import SidebarLink from './SidebarLink'
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
close: () => void
|
close: () => void
|
||||||
@@ -50,26 +50,30 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
|
|||||||
<AppShell.Section component={ScrollArea}>
|
<AppShell.Section component={ScrollArea}>
|
||||||
{sidebarElements.map((el, i) => {
|
{sidebarElements.map((el, i) => {
|
||||||
if (el.type === 'divider') {
|
if (el.type === 'divider') {
|
||||||
return <Divider my="md" mx={10} />
|
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.type === 'title') {
|
if (el.type === 'title') {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
fw={700}
|
fw={700}
|
||||||
fz={12}
|
fz={12}
|
||||||
pl={12}
|
pl={12}
|
||||||
py={5}
|
py={5}
|
||||||
children={el.label}
|
>
|
||||||
/>
|
{el.label}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.children?.length) {
|
if (el.children?.length) {
|
||||||
return (
|
return (
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
href={el.href}
|
href={el.href}
|
||||||
label={el.label}
|
label={el.label}
|
||||||
key={i}
|
|
||||||
leftSection={el.leftIcon}
|
leftSection={el.leftIcon}
|
||||||
>
|
>
|
||||||
{el.children.map((child, index) => (
|
{el.children.map((child, index) => (
|
||||||
@@ -83,12 +87,13 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
|
|||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
href={el.href}
|
href={el.href}
|
||||||
label={el.label}
|
label={el.label}
|
||||||
onClick={close}
|
onClick={close}
|
||||||
key={i}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import clsx from 'clsx'
|
|||||||
import { Link, useRouter } from 'tuono'
|
import { Link, useRouter } from 'tuono'
|
||||||
import { IconChevronRight } from '@tabler/icons-react'
|
import { IconChevronRight } from '@tabler/icons-react'
|
||||||
|
|
||||||
import styles from './sidebar-link.module.css'
|
import styles from './SidebarLink.module.css'
|
||||||
|
|
||||||
interface SidebarLinkProps {
|
interface SidebarLinkProps {
|
||||||
label: string
|
label: string
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Sidebar from './Sidebar'
|
||||||
|
|
||||||
|
export default Sidebar
|
||||||
+11
-6
@@ -170,6 +170,11 @@ export const sidebarElements: Array<SidebarElement> = [
|
|||||||
label: 'CSS modules',
|
label: 'CSS modules',
|
||||||
href: '/documentation/styles/css-modules',
|
href: '/documentation/styles/css-modules',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Tailwind CSS',
|
||||||
|
href: '/documentation/styles/tailwind-css',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -203,11 +208,6 @@ export const sidebarElements: Array<SidebarElement> = [
|
|||||||
label: 'Components',
|
label: 'Components',
|
||||||
href: '#focus',
|
href: '#focus',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
type: 'element',
|
|
||||||
label: 'Head',
|
|
||||||
href: '/documentation/components/head',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'element',
|
type: 'element',
|
||||||
label: 'Link',
|
label: 'Link',
|
||||||
@@ -239,7 +239,7 @@ export const sidebarElements: Array<SidebarElement> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'element',
|
type: 'element',
|
||||||
label: 'tuono.config.ts',
|
label: 'Configuration',
|
||||||
href: '/documentation/configuration',
|
href: '/documentation/configuration',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -260,6 +260,11 @@ export const sidebarElements: Array<SidebarElement> = [
|
|||||||
label: 'Local development',
|
label: 'Local development',
|
||||||
href: '/documentation/contributing/local-development',
|
href: '/documentation/contributing/local-development',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Pull requests',
|
||||||
|
href: '/documentation/contributing/pull-requests',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
+24
-37
@@ -1,47 +1,33 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: var(--mantine-spacing-xl);
|
top: var(--mantine-spacing-xl);
|
||||||
right: 0;
|
|
||||||
padding-top: 55px;
|
padding-top: 55px;
|
||||||
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
|
flex: 0 0 calc(250px - 20px);
|
||||||
width: 200px;
|
min-width: 220px;
|
||||||
|
max-width: 220px;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
@mixin rtl {
|
/* var(--mantine-breakpoint-md) = 62em */
|
||||||
padding-left: 0;
|
@media (max-width: 62em) {
|
||||||
padding-right: var(--mantine-spacing-md);
|
display: none;
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-with-tabs] {
|
|
||||||
padding-top: 0;
|
|
||||||
top: calc(var(--mantine-spacing-xl) + 60px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
padding-bottom: var(--mantine-spacing-xl);
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
display: block;
|
display: block;
|
||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
padding: 8px var(--mantine-spacing-md);
|
padding: 8px var(--mantine-spacing-md);
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
|
padding-left: calc(1 * var(--mantine-spacing-lg));
|
||||||
border-top-right-radius: var(--mantine-radius-sm);
|
border-top-right-radius: var(--mantine-radius-sm);
|
||||||
border-bottom-right-radius: var(--mantine-radius-sm);
|
border-bottom-right-radius: var(--mantine-radius-sm);
|
||||||
|
|
||||||
@@ -58,10 +44,19 @@
|
|||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
border-top-left-radius: var(--mantine-radius-sm);
|
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
|
||||||
border-bottom-left-radius: var(--mantine-radius-sm);
|
}
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
&: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] {
|
&[data-active] {
|
||||||
@@ -69,22 +64,14 @@
|
|||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
color: var(--mantine-color-violet-8);
|
color: var(--mantine-color-violet-8);
|
||||||
background-color: var(--mantine-color-violet-0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin dark {
|
@mixin dark {
|
||||||
color: var(--mantine-color-violet-1);
|
color: var(--mantine-color-violet-1);
|
||||||
background-color: #534a6d;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-left: var(--mantine-spacing-md);
|
margin-left: var(--mantine-spacing-md);
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import type { JSX, MouseEvent } from 'react'
|
||||||
|
import { useRef, useState, useEffect } from 'react'
|
||||||
|
import { useRouter } from 'tuono'
|
||||||
|
import { Box, Text } from '@mantine/core'
|
||||||
|
|
||||||
|
import { getHeadings, 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 = (
|
||||||
|
event: MouseEvent<HTMLAnchorElement>,
|
||||||
|
id: string,
|
||||||
|
): void => {
|
||||||
|
event.preventDefault()
|
||||||
|
const element = document.getElementById(id)
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: 'instant',
|
||||||
|
block: 'start',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid to show it in case of a TODO page
|
||||||
|
if (headings.length === 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box component="nav" className={classes.wrapper}>
|
||||||
|
<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"
|
||||||
|
px={8}
|
||||||
|
w="fit-content"
|
||||||
|
py={4}
|
||||||
|
className={classes.link}
|
||||||
|
mod={{ active: activeHeadingIndex === index + 1 }}
|
||||||
|
href={`#${heading.id}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleHeadingClick(e, heading.id)
|
||||||
|
}}
|
||||||
|
__vars={{ '--toc-link-offset': `${heading.depth - 1}` }}
|
||||||
|
>
|
||||||
|
{heading.content}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
+16
-4
@@ -8,14 +8,27 @@ export interface Heading {
|
|||||||
getNode: () => HTMLHeadingElement
|
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> {
|
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
||||||
const result: Array<Heading> = []
|
const result: Array<Heading> = []
|
||||||
|
|
||||||
for (const heading of headings) {
|
for (const heading of headings) {
|
||||||
if (heading.id) {
|
const depth = parseInt(heading.getAttribute('data-order') || '1', 10)
|
||||||
|
|
||||||
|
if (depth <= 3 && heading.id) {
|
||||||
result.push({
|
result.push({
|
||||||
depth: parseInt(heading.getAttribute('data-order') || '1', 10),
|
depth,
|
||||||
content: heading.getAttribute('data-heading') || '',
|
content: getCleanedText(heading),
|
||||||
id: heading.id,
|
id: heading.id,
|
||||||
getNode: () =>
|
getNode: () =>
|
||||||
document.getElementById(heading.id) as HTMLHeadingElement,
|
document.getElementById(heading.id) as HTMLHeadingElement,
|
||||||
@@ -28,7 +41,6 @@ function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
|||||||
|
|
||||||
export function getHeadings(): Array<Heading> {
|
export function getHeadings(): Array<Heading> {
|
||||||
const root = document.getElementById('mdx-root')
|
const root = document.getElementById('mdx-root')
|
||||||
console.log(root)
|
|
||||||
|
|
||||||
if (!root) {
|
if (!root) {
|
||||||
return []
|
return []
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
* 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
|
export default TableOfContents
|
||||||
+3
-3
@@ -7,7 +7,7 @@ import {
|
|||||||
import { IconSun, IconMoon } from '@tabler/icons-react'
|
import { IconSun, IconMoon } from '@tabler/icons-react'
|
||||||
import cx from 'clsx'
|
import cx from 'clsx'
|
||||||
|
|
||||||
import classes from './theme-btn.module.css'
|
import classes from './ThemeBtn.module.css'
|
||||||
|
|
||||||
export default function ThemeBtn(): JSX.Element {
|
export default function ThemeBtn(): JSX.Element {
|
||||||
const { setColorScheme } = useMantineColorScheme()
|
const { setColorScheme } = useMantineColorScheme()
|
||||||
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
||||||
}
|
}}
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
aria-label="Toggle color scheme"
|
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,3 +0,0 @@
|
|||||||
import MdxPre from './mdx-pre'
|
|
||||||
|
|
||||||
export default MdxPre
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxQuote from './mdx-quote'
|
|
||||||
|
|
||||||
export default MdxQuote
|
|
||||||
@@ -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} />
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MetaTags from './meta-tags'
|
|
||||||
|
|
||||||
export default MetaTags
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Navbar from './navbar'
|
|
||||||
|
|
||||||
export default Navbar
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import NavigationButtons from './navigation-buttons'
|
|
||||||
|
|
||||||
export default NavigationButtons
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Sidebar from './sidebar'
|
|
||||||
|
|
||||||
export default Sidebar
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
import type { JSX } from 'react'
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import { useRouter, Link } from 'tuono'
|
|
||||||
import { IconList } from '@tabler/icons-react'
|
|
||||||
import { Box, rem, ScrollArea, Text } from '@mantine/core'
|
|
||||||
|
|
||||||
import { getHeadings, type Heading } from './get-headings'
|
|
||||||
import classes from './table-of-content.module.css'
|
|
||||||
|
|
||||||
interface TableOfContentsProps {
|
|
||||||
withTabs: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveElement(rects: Array<DOMRect>): number {
|
|
||||||
if (rects.length === 0) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
const closest = rects.reduce(
|
|
||||||
(acc, item, index) => {
|
|
||||||
if (Math.abs(acc.position) < Math.abs(item.y)) {
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
index,
|
|
||||||
position: item.y,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ index: 0, position: rects[0].y },
|
|
||||||
)
|
|
||||||
|
|
||||||
return closest.index
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TableOfContents({
|
|
||||||
withTabs,
|
|
||||||
}: TableOfContentsProps): JSX.Element | null {
|
|
||||||
const [active, setActive] = useState(0)
|
|
||||||
const [headings, setHeadings] = useState<Array<Heading>>([])
|
|
||||||
const headingsRef = useRef<Array<Heading>>([])
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const filteredHeadings = headings.filter((heading) => heading.depth > 1)
|
|
||||||
|
|
||||||
const handleScroll = (): void => {
|
|
||||||
setActive(
|
|
||||||
getActiveElement(
|
|
||||||
headingsRef.current.map((d) => d.getNode().getBoundingClientRect()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const _headings = getHeadings()
|
|
||||||
headingsRef.current = _headings
|
|
||||||
setHeadings(_headings)
|
|
||||||
setActive(
|
|
||||||
getActiveElement(
|
|
||||||
_headings.map((d) => d.getNode().getBoundingClientRect()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return (): void => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [router.pathname])
|
|
||||||
|
|
||||||
if (filteredHeadings.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = filteredHeadings.map((heading, index) => (
|
|
||||||
<Text
|
|
||||||
key={heading.id}
|
|
||||||
component={Link}
|
|
||||||
fz="sm"
|
|
||||||
p={10}
|
|
||||||
className={classes.link}
|
|
||||||
mod={{ active: active === index }}
|
|
||||||
href={`#${heading.id}`}
|
|
||||||
__vars={{ '--toc-link-offset': `${heading.depth - 1}` }}
|
|
||||||
>
|
|
||||||
{heading.content}
|
|
||||||
</Text>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
mod={{ 'with-tabs': withTabs }}
|
|
||||||
className={classes.wrapper}
|
|
||||||
>
|
|
||||||
<div className={classes.inner}>
|
|
||||||
<div>
|
|
||||||
<div className={classes.header}>
|
|
||||||
<IconList
|
|
||||||
style={{ width: rem(20), height: rem(20) }}
|
|
||||||
stroke={1.5}
|
|
||||||
/>
|
|
||||||
<Text className={classes.title}>Table of contents</Text>
|
|
||||||
</div>
|
|
||||||
<ScrollArea.Autosize
|
|
||||||
mah={`calc(100vh - ${rem(140)})`}
|
|
||||||
type="never"
|
|
||||||
offsetScrollbars
|
|
||||||
>
|
|
||||||
<div className={classes.items}>{items}</div>
|
|
||||||
</ScrollArea.Autosize>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import ThemeBtn from './theme-btn'
|
|
||||||
|
|
||||||
export default ThemeBtn
|
|
||||||
+33
-34
@@ -1,20 +1,19 @@
|
|||||||
import type { ReactNode, JSX } from 'react'
|
import type { ReactNode, JSX } from 'react'
|
||||||
|
import { TuonoScripts } from 'tuono'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ColorSchemeScript,
|
ColorSchemeScript,
|
||||||
createTheme,
|
createTheme,
|
||||||
MantineProvider,
|
MantineProvider,
|
||||||
AppShell,
|
AppShell,
|
||||||
Container,
|
mantineHtmlProps,
|
||||||
|
type CSSVariablesResolver,
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
import type { CSSVariablesResolver } from '@mantine/core'
|
|
||||||
import { useDisclosure } from '@mantine/hooks'
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
import { Head } from 'tuono'
|
|
||||||
|
|
||||||
import EditPage from '@/components/edit-page'
|
import MdxWrapper from '@/components/MdxWrapper'
|
||||||
import MdxProvider from '@/components/mdx-provider'
|
import Navbar from '@/components/Navbar'
|
||||||
import Navbar from '@/components/navbar'
|
import Sidebar from '@/components/Sidebar'
|
||||||
import Sidebar from '@/components/sidebar'
|
|
||||||
|
|
||||||
import '@mantine/core/styles.css'
|
import '@mantine/core/styles.css'
|
||||||
import '@mantine/code-highlight/styles.css'
|
import '@mantine/code-highlight/styles.css'
|
||||||
@@ -37,8 +36,8 @@ const theme = createTheme({
|
|||||||
},
|
},
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
xs: '16px',
|
xs: '14px',
|
||||||
sm: '16px',
|
sm: '14px',
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
dark: [
|
dark: [
|
||||||
@@ -87,8 +86,8 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
|
|||||||
const [opened, { toggle }] = useDisclosure()
|
const [opened, { toggle }] = useDisclosure()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<html lang="en" {...mantineHtmlProps}>
|
||||||
<Head>
|
<head>
|
||||||
<meta charSet="UTF-8" />
|
<meta charSet="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link
|
<link
|
||||||
@@ -109,28 +108,28 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
|
|||||||
href="/favicon-16x16.png"
|
href="/favicon-16x16.png"
|
||||||
/>
|
/>
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
</Head>
|
<ColorSchemeScript />
|
||||||
<ColorSchemeScript />
|
</head>
|
||||||
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
|
<body>
|
||||||
<AppShell
|
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
|
||||||
layout="alt"
|
<AppShell
|
||||||
header={{ height: 60 }}
|
layout="alt"
|
||||||
navbar={{
|
header={{ height: 60 }}
|
||||||
width: 300,
|
navbar={{
|
||||||
breakpoint: 'sm',
|
width: 300,
|
||||||
collapsed: { mobile: !opened },
|
breakpoint: 'sm',
|
||||||
}}
|
collapsed: { mobile: !opened },
|
||||||
>
|
}}
|
||||||
<Navbar toggle={toggle} />
|
>
|
||||||
<Sidebar close={toggle} />
|
<Navbar toggle={toggle} />
|
||||||
<AppShell.Main>
|
<Sidebar close={toggle} />
|
||||||
<Container id="mdx-root" component="article" size="md" p={20}>
|
<AppShell.Main pt={0} px="auto">
|
||||||
<MdxProvider>{children}</MdxProvider>
|
<MdxWrapper>{children}</MdxWrapper>
|
||||||
<EditPage />
|
</AppShell.Main>
|
||||||
</Container>
|
</AppShell>
|
||||||
</AppShell.Main>
|
</MantineProvider>
|
||||||
</AppShell>
|
<TuonoScripts />
|
||||||
</MantineProvider>
|
</body>
|
||||||
</>
|
</html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Application state"
|
title="Tuono - Application state"
|
||||||
@@ -6,7 +6,7 @@ import MetaTags from '@/components/meta-tags'
|
|||||||
description="Learn how to add features to your Tuono application"
|
description="Learn how to add features to your Tuono application"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Application state' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'Application state' }]} />
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - CLI"
|
title="Tuono - CLI"
|
||||||
@@ -6,7 +6,7 @@ import MetaTags from '@/components/meta-tags'
|
|||||||
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
|
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ Tuono is the CLI that provides all the needed commands to handle the full-stack
|
|||||||
|
|
||||||
To list all the available commands, run `tuono -h`.
|
To list all the available commands, run `tuono -h`.
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
The React/Rust full-stack framework
|
The React/Rust full-stack framework
|
||||||
|
|
||||||
Usage: tuono <COMMAND>
|
Usage: tuono <COMMAND>
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Head"
|
|
||||||
canonical="https://tuono.dev/documentation/components/head"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
|
|
||||||
|
|
||||||
# Head
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -1,14 +1,39 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Link"
|
title="Tuono - Link"
|
||||||
canonical="https://tuono.dev/documentation/components/link"
|
canonical="https://tuono.dev/documentation/components/link"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
|
||||||
|
|
||||||
# Link
|
# Link
|
||||||
|
|
||||||
TODO
|
Tuono provides a `Link` component for clientside navigation between pages of your app. `Link` prefetches pages from any links above the fold, allowing for faster navigations without a full reload.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Link } from 'tuono'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Link href="/page2">My Other Page</Link>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For navigating to pages outside of your app (other websites), you should use the `<a>` tag.
|
||||||
|
|
||||||
|
> Additional considerations:
|
||||||
|
>
|
||||||
|
> - Any `Link` in the viewport is prefetched by default when it appears within the viewport.
|
||||||
|
> - The corresponding data for server-rendered routes is fetched only when the `Link` is clicked.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
The `Link` component behaves similarly to the `<a>` HTML tag, with a few additional features controlled by the following props:
|
||||||
|
|
||||||
|
| Prop | Type | Default value | Description |
|
||||||
|
| --------- | ------------------- | ------------- | ------------------------------------------------------------------------------------------------- |
|
||||||
|
| `href` | `string` (Required) | - | Relative path to the page in your application. Anchor names (links with hashtags) are also valid. |
|
||||||
|
| `preload` | `boolean` | `true` | Whether to preload the page if the link is within the viewport. |
|
||||||
|
| `scroll` | `boolean` | `true` | Whether to preserve scroll position between navigations. |
|
||||||
|
|||||||
@@ -1,14 +1,76 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - tuono.config.ts"
|
title="Tuono - tuono.config.ts"
|
||||||
canonical="https://tuono.dev/documentation/configuration"
|
canonical="https://tuono.dev/documentation/configuration"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Configuration' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'Configuration' }]} />
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
TODO
|
Tuono can be configured using the `tuono.config.ts` file,
|
||||||
|
which allows you to customize various aspects of the build process and development environment.
|
||||||
|
|
||||||
|
The config file is written in TypeScript,
|
||||||
|
so you can take advantage of type checking and auto-suggestions provided by the `TuonoConfig` type,
|
||||||
|
which is exported from `tuono/config`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { TuonoConfig } from 'tuono/config'
|
||||||
|
|
||||||
|
const config: TuonoConfig = {
|
||||||
|
/* typesafe config */
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
|
```
|
||||||
|
|
||||||
|
## vite
|
||||||
|
|
||||||
|
Tuono leverages Vite, a modern and fast build tool and development server for web applications.
|
||||||
|
|
||||||
|
As a result, **some** Vite configuration properties can be set in the `tuono.config.ts` file under the `vite` property.
|
||||||
|
|
||||||
|
### vite.alias
|
||||||
|
|
||||||
|
Useful for configuring custom source alias paths during imports
|
||||||
|
|
||||||
|
- Type:
|
||||||
|
- `Record<string, string>`
|
||||||
|
- `Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>`
|
||||||
|
- [Vite documentation: resolve.alias](https://vite.dev/config/shared-options#resolve-alias)
|
||||||
|
|
||||||
|
### vite.optimizeDeps
|
||||||
|
|
||||||
|
Vite's dependency optimizer used only during dev
|
||||||
|
|
||||||
|
- [Vite documentation: Dep Optimization Options](https://vite.dev/config/dep-optimization-options)
|
||||||
|
|
||||||
|
### vite.plugins
|
||||||
|
|
||||||
|
Useful for extending vite's functionality
|
||||||
|
|
||||||
|
- Type: `Array<Plugin | Array<Plugin> | Promise<Plugin | Array<Plugin>>`
|
||||||
|
- [Vite documentation: plugins](https://vite.dev/guide/using-plugins)
|
||||||
|
|
||||||
|
## server
|
||||||
|
|
||||||
|
The following options configure the server.
|
||||||
|
|
||||||
|
## server.host
|
||||||
|
|
||||||
|
- Type: `string`
|
||||||
|
- Default: `'localhost'`
|
||||||
|
|
||||||
|
The hostname or IP address of the server.
|
||||||
|
It can be a domain name (e.g., `"example.com"`) or an IP address (e.g., `"127.0.0.1"`)
|
||||||
|
|
||||||
|
## server.port
|
||||||
|
|
||||||
|
- Type: `number`
|
||||||
|
- Default: `3000`
|
||||||
|
|
||||||
|
The port number where the server will listen for incoming requests.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Contributing"
|
title="Tuono - Contributing"
|
||||||
@@ -6,7 +6,7 @@ import MetaTags from '@/components/meta-tags'
|
|||||||
description="The project is massive - if you like it, do consider contributing!"
|
description="The project is massive - if you like it, do consider contributing!"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Contributing"
|
title="Tuono - Contributing"
|
||||||
@@ -6,7 +6,7 @@ import MetaTags from '@/components/meta-tags'
|
|||||||
description="The project is massive - if you like it, do consider contributing!"
|
description="The project is massive - if you like it, do consider contributing!"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ Without taking into account specific cases, we can mostly split the domain requi
|
|||||||
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
||||||
code is markdown).
|
code is markdown).
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
import NavigationButtons from '../../../components/NavigationButtons'
|
||||||
|
|
||||||
<NavigationButtons
|
<NavigationButtons
|
||||||
next={{
|
next={{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Contributing - Local development"
|
title="Tuono - Contributing - Local development"
|
||||||
@@ -6,7 +6,7 @@ import MetaTags from '@/components/meta-tags'
|
|||||||
description="Contribute to Tuono. Learn here how to setup the repository for local development"
|
description="Contribute to Tuono. Learn here how to setup the repository for local development"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
@@ -30,12 +30,16 @@ git clone https://github.com/<your-name-here>/tuono
|
|||||||
cd tuono
|
cd tuono
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rust tool chain
|
For the next steps, you can either set up the environment directly on your computer or use a ready-to-use Docker image (go to the [Docker setup](#docker-setup) part).
|
||||||
|
|
||||||
|
### Local setup
|
||||||
|
|
||||||
|
#### Rust tool chain
|
||||||
|
|
||||||
Install the Rust programming language tool chain (`rust` and `cargo`).
|
Install the Rust programming language tool chain (`rust` and `cargo`).
|
||||||
Follows instructions in the official [docs](https://rustup.rs/)
|
Follows instructions in the official [docs](https://rustup.rs/)
|
||||||
|
|
||||||
### Node.js — runtime
|
#### Node.js — runtime
|
||||||
|
|
||||||
Install `Node.js`.
|
Install `Node.js`.
|
||||||
You can follow the instructions from the [Node official site](https://nodejs.org/en/download/package-manager)
|
You can follow the instructions from the [Node official site](https://nodejs.org/en/download/package-manager)
|
||||||
@@ -50,27 +54,69 @@ You can follow the instructions from the [Node official site](https://nodejs.org
|
|||||||
>
|
>
|
||||||
> to simply pick up the correct version!
|
> to simply pick up the correct version!
|
||||||
|
|
||||||
### Node.js — package manager
|
#### Node.js — package manager
|
||||||
|
|
||||||
We use [`pnpm`](https://pnpm.io) as Node.js package manager.
|
We use [`pnpm`](https://pnpm.io) as Node.js package manager.
|
||||||
|
|
||||||
You can see which version of yarn we use by checking the `packageManager` field in the root `package.json`.
|
You can see which version of yarn we use by checking the `packageManager` field in the root `package.json`.
|
||||||
|
|
||||||
### Pre-flight checks
|
#### Pre-flight checks
|
||||||
|
|
||||||
To check that everything is working properly, run:
|
To check that everything is working properly, run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
turbo run check-all
|
pnpm run check-all
|
||||||
cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Docker setup
|
||||||
|
|
||||||
|
#### Introduction
|
||||||
|
|
||||||
|
Docker takes care of configuring the development environment, so you don’t need to worry about installing specific versions of packages like Node.js or pnpm. The Docker image handles this for you!
|
||||||
|
Using Docker, **the only operations you’ll need to manage on your host machine are related to git**. (don't use pnpm, cargo, ... from your host machine)
|
||||||
|
Some IDEs (such as vscode with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)) allow you to connect directly to the Docker container.
|
||||||
|
|
||||||
|
**Before proceeding with this guide, make sure that the `node_modules` and `target` directories are either absent or empty in the subprojects of Tuono from your host machine where you want to contribute.**
|
||||||
|
This ensures a clean and consistent environment when using Docker.
|
||||||
|
|
||||||
|
#### Build the Tuono's Docker Container
|
||||||
|
|
||||||
|
First, ensure Docker is installed on your machine by following the instructions on the [Docker official site](https://docs.docker.com/engine/install/).
|
||||||
|
|
||||||
|
Once Docker is installed, use the following command to build the image into a container named tuono-source-container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Note: Execute this command in the project root
|
||||||
|
docker compose -f docker/compose.yml up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To verify that everything is working as expected, run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker images && docker ps -a --size
|
||||||
|
```
|
||||||
|
|
||||||
|
Or on Windows:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
docker images; docker ps -a --size
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the image named tuono-source-image and the container tuono-source-container. The container's status should be "up".
|
||||||
|
|
||||||
|
Use the following command to connect to the container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker exec -it tuono-source-container /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
## Tuono development
|
## Tuono development
|
||||||
|
|
||||||
1. Start tuono frontend build using
|
1. Start tuono frontend build using
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
turbo run dev
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
2. In another terminal run
|
2. In another terminal run
|
||||||
@@ -111,3 +157,64 @@ cargo build
|
|||||||
|
|
||||||
> On the documentation remember that `tuono` `npm` package is installed from the registry and
|
> On the documentation remember that `tuono` `npm` package is installed from the registry and
|
||||||
> it is not linked to the repository.
|
> it is not linked to the repository.
|
||||||
|
|
||||||
|
## Validate your changes
|
||||||
|
|
||||||
|
The following checks are all run on pull requests automatically.
|
||||||
|
|
||||||
|
You can also perform them locally.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
For the typescript part you can run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run format:check && pnpm run -w repo:root:format:check
|
||||||
|
```
|
||||||
|
|
||||||
|
For the rust codebase run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
For the typescript part you can run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
For the rust codebase run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo clippy -- -D warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
For the typescript part you can run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
For the rust codebase run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
import NavigationButtons from '../../../components/NavigationButtons'
|
||||||
|
|
||||||
|
<NavigationButtons
|
||||||
|
prev={{
|
||||||
|
title: 'Guidelines',
|
||||||
|
href: '/documentation/contributing',
|
||||||
|
}}
|
||||||
|
next={{
|
||||||
|
title: 'Pull requests',
|
||||||
|
href: '/documentation/contributing/pull-requests',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
|
<MetaTags
|
||||||
|
title="Tuono - Opening a pull request"
|
||||||
|
canonical="https://tuono.dev/documentation/contributing/pull-requests"
|
||||||
|
description="Contribute to Tuono. Learn here how to open a pull request."
|
||||||
|
/>
|
||||||
|
|
||||||
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
|
<Breadcrumbs
|
||||||
|
breadcrumbs={[
|
||||||
|
{ label: '✨ Contributing', href: '/documentation/contributing' },
|
||||||
|
{ label: 'Pull Requests' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
# Pull Requests
|
||||||
|
|
||||||
|
Once your changes are ready, you can create a PR!
|
||||||
|
|
||||||
|
If you are not familiar with GitHub pull requests,
|
||||||
|
you can check the [official documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- Please do:
|
||||||
|
|
||||||
|
- Fill the template in full
|
||||||
|
|
||||||
|
- If you are fixing an issue, bear in mind to link the issue itself in the PR description.
|
||||||
|
- If you are adding a new feature, describe the intended use case that the feature fulfills.
|
||||||
|
|
||||||
|
- Ensure your code is valid by running the test on you local machine.
|
||||||
|
Refer to ["Validate your changes" section](/documentation/contributing/local-development)
|
||||||
|
|
||||||
|
- Please **don't**:
|
||||||
|
|
||||||
|
- Do not use force push after marking the PR ready for review.
|
||||||
|
|
||||||
|
**Reasoning:** GitHub cannot track changes across force pushes,
|
||||||
|
which slows down our ability to perform incremental reviews.
|
||||||
|
|
||||||
|
## Title
|
||||||
|
|
||||||
|
The PR title must match the following format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<type>[optional scope]: <short description>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find commit examples in the [recent commits on main branch](https://github.com/tuono-labs/tuono/commits/main/)
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
|
||||||
|
- feat: add support for react 19
|
||||||
|
- ci: create install-node-dependencies action
|
||||||
|
- feat(packages/tuono-router): use React.Context to pass data
|
||||||
|
- test(packages/tuono): messageChannel - use vi.fn
|
||||||
|
|
||||||
|
> We do not care about the number, or style of commits in your branch history,
|
||||||
|
> because we squash merge every PR into `main`.
|
||||||
|
>
|
||||||
|
> Feel free to commit in whatever style you feel comfortable with.
|
||||||
|
|
||||||
|
### type
|
||||||
|
|
||||||
|
Must be one of the following
|
||||||
|
|
||||||
|
{/* @warning Keep in sync with .github/workflows/pr-title-checker.yml */}
|
||||||
|
|
||||||
|
- `docs` - if you only change documentation, and not shipped code
|
||||||
|
- `feat` - any new functionality additions
|
||||||
|
- `fix` - any bug fixes that don't add new functionality
|
||||||
|
- `refactor` - a code change that neither fixes a bug or adds a feature
|
||||||
|
- `test` - if you only change tests, and not shipped code
|
||||||
|
- `ci` - if you change something related to continuos integration
|
||||||
|
- `chore` - anything else (e.g.: release)
|
||||||
|
|
||||||
|
### optional scope
|
||||||
|
|
||||||
|
A scope may be provided to a commit's type,
|
||||||
|
to provide additional contextual information and is contained within parenthesis
|
||||||
|
E.g.: `fix(crates/tuono): remove cargo warnings`
|
||||||
|
|
||||||
|
### short description
|
||||||
|
|
||||||
|
A succinct title for the PR.
|
||||||
|
(The title max length is set to 100 characters)
|
||||||
|
|
||||||
|
## Addressing review feedback
|
||||||
|
|
||||||
|
Once we've reviewed your PR, we’ll provide feedback on any changes that need attention.
|
||||||
|
If you feel a suggested change isn’t correct, feel free to discuss it with us in the comments.
|
||||||
|
|
||||||
|
Please [post your comments](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#adding-comments-to-a-pull-request)
|
||||||
|
as line comments when possible so they can be threaded.
|
||||||
|
|
||||||
|
After addressing all feedback, whether through code changes or by starting a follow-up discussion,
|
||||||
|
please [re-request a review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review)
|
||||||
|
from each maintainer whose feedback you've addressed.
|
||||||
|
|
||||||
|
import NavigationButtons from '../../../components/NavigationButtons'
|
||||||
|
|
||||||
|
<NavigationButtons
|
||||||
|
prev={{
|
||||||
|
title: 'Local development',
|
||||||
|
href: '/documentation/contributing/local-development',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Multithreading"
|
title="Tuono - Multithreading"
|
||||||
canonical="https://tuono.dev/documentation/core-concepts/multithreading"
|
canonical="https://tuono.dev/documentation/core-concepts/multithreading"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Core concepts' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'Core concepts' }]} />
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,57 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - useRouter"
|
title="Tuono - useRouter"
|
||||||
canonical="https://tuono.dev/documentation/hooks/use-router"
|
canonical="https://tuono.dev/documentation/hooks/use-router"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Hooks' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'useRouter' }]} />
|
||||||
|
|
||||||
# useRouter
|
# useRouter
|
||||||
|
|
||||||
TODO
|
The `useRouter` hook provides access to various data about the current route, as well as methods to navigate between pages, as seen in the below example:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { JSX } from 'react'
|
||||||
|
import { useRouter } from 'tuono'
|
||||||
|
|
||||||
|
export default function IndexPage(): JSX.Element {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>pathname: {router.pathname}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/foo')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
My link
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `useRouter` result
|
||||||
|
|
||||||
|
- `pathname`: `string` - Returns the current path name
|
||||||
|
- `query`: `Record<string, string>` - This object contains all the query params of the current route
|
||||||
|
|
||||||
|
The following methods are included inside `router`:
|
||||||
|
|
||||||
|
### `router.push`
|
||||||
|
|
||||||
|
Handles client side page navigation quickly. Useful for internal links, or for when you need to change url in some JS.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
router.push(path, options)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Name | Type | Default value | Description |
|
||||||
|
| ---------------- | ------------------- | ------------- | ----------------------------------------------------------------- |
|
||||||
|
| `path` | `string` (Required) | - | The path of the page you want to navigate to. |
|
||||||
|
| `options` | `PushOptions` | - | Optional config object for additional control. |
|
||||||
|
| `options.scroll` | `boolean` | `true` | If `false` the scroll offset will be kept across page navigation. |
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - tuono.config.ts"
|
title="Tuono - tuono.config.ts"
|
||||||
canonical="https://tuono.dev/documentation/how-is-tuono-different"
|
canonical="https://tuono.dev/documentation/how-is-tuono-different"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'How is tuono different?' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'How is tuono different?' }]} />
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - Installation"
|
title="Tuono - Installation"
|
||||||
@@ -8,6 +6,8 @@ import MetaTags from '@/components/meta-tags'
|
|||||||
description="Tuono is a development environment built in rust and typescript that outputs a website written in both the languages"
|
description="Tuono is a development environment built in rust and typescript that outputs a website written in both the languages"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Installation' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'Installation' }]} />
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
@@ -28,19 +28,19 @@ languages. Since then, you need the following tools installed on your computer t
|
|||||||
|
|
||||||
The tuono `CLI` is hosted on [crates.io](https://crates.io/crates/tuono) to download and install it, just run it on a terminal:
|
The tuono `CLI` is hosted on [crates.io](https://crates.io/crates/tuono) to download and install it, just run it on a terminal:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
cargo install tuono
|
cargo install tuono
|
||||||
```
|
```
|
||||||
|
|
||||||
To check that it is correctly installed, run:
|
To check that it is correctly installed, run:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
tuono --version
|
tuono --version
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `tuono -h` to see all the available commands.
|
Run `tuono -h` to see all the available commands.
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
The React/Rust full-stack framework
|
The React/Rust full-stack framework
|
||||||
|
|
||||||
Usage: tuono <COMMAND>
|
Usage: tuono <COMMAND>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
import MetaTags from '@/components/MetaTags'
|
||||||
|
|
||||||
<MetaTags
|
<MetaTags
|
||||||
title="Tuono - MDX"
|
title="Tuono - MDX"
|
||||||
canonical="https://tuono.dev/documentation/integrations/mdx"
|
canonical="https://tuono.dev/documentation/integrations/mdx"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Integrations' }]} />
|
<Breadcrumbs breadcrumbs={[{ label: 'Integrations' }]} />
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user