Compare commits

...

89 Commits

Author SHA1 Message Date
Valerio Ageno bf77ed1794 chore: update version to v0.17.10 (#615) 2025-02-28 17:41:14 +01:00
Valerio Ageno e34c10bc34 fix: build script for creation of parent directories (#614) 2025-02-28 17:41:01 +01:00
Marco Pasqualetti 310579cf76 fix(examples/tuono-tutorial): use pokemon name as key instead of index (#613) 2025-02-28 14:17:26 +01:00
pveierland 1ba94238ed feat: add origin config option (#582)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-02-27 18:25:03 +01:00
Valerio Ageno 5f6bad637c chore: update version to v0.17.9 (#608) 2025-02-27 08:35:42 +01:00
Marco Pasqualetti 3063763667 feat(env): temporary revert frontend implementation (#609) 2025-02-27 08:35:06 +01:00
Valerio Ageno caf1a58a0b feat: update axum to v0.8.1 (#595) 2025-02-26 19:01:10 +01:00
Marco Pasqualetti 059f3d6476 chore(deps): update dependency prettier to v3.5.2 (#602) 2025-02-24 20:30:12 +01:00
renovate[bot] 303a177344 chore(deps): update dependency vitest to v3.0.7 (#601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 20:21:16 +01:00
renovate[bot] d26918fc2f chore(deps): update dependency happy-dom to v17.1.4 (#598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 20:15:27 +01:00
renovate[bot] 31f0a167e9 chore(deps): update dependency @types/node to v22.13.5 (#597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 20:04:09 +01:00
Jovan Milosevic d71185c9cb feat(packages/tuono-router): add replace method to router and replace prop to Link (#596) 2025-02-24 19:53:59 +01:00
Valerio Ageno 798f8f1bf8 feat: allow custom route with .txt extension (#594) 2025-02-24 12:18:14 +01:00
Marco Pasqualetti 06612f07b0 refactor(examples): uniform tsconfig.json files (#591) 2025-02-23 15:36:04 +01:00
Marco Pasqualetti 067535c2f6 test(packages/tuono): create-json-config test are not clearing writeFileSpy mock (#593) 2025-02-23 15:31:56 +01:00
Jacob Marshall d679982949 feat(env): setup env prefix and typescript for client-side (#584) 2025-02-23 14:17:23 +01:00
Jacob Marshall de5ac53cc3 feat: more detailed errors for Failed to read tuono.config.ts (#579) 2025-02-23 12:15:12 +01:00
Robert Lawson ca4a7b3466 test: add full integration test for tuono new CLI command (#411)
Co-authored-by: Valerio Ageno <valerioageno@yahoo.it>
2025-02-23 11:06:06 +01:00
Marco Pasqualetti 0a4d1e2995 feat: update vite to v6 and vitest v3 (#576) 2025-02-22 21:06:38 +01:00
Jacob Marshall d6fc18e2f6 chore(examples/with-tailwind): update tailwind to v4.0.6 (#577) 2025-02-17 20:58:42 +01:00
Valerio Ageno 8e04abbe42 feat: setup default tracing subscriber (#564) 2025-02-17 20:49:51 +01:00
Marco Pasqualetti 2905cfae3d chore: add devtools to labeler.yml (#575) 2025-02-17 18:07:56 +01:00
Jacob Marshall b632cf43ba refactor: apply RustRover lint suggestions (#569) 2025-02-17 08:40:03 +01:00
Jacob Marshall 06bc3700b7 fix: use snake_case for handler names (#565) 2025-02-16 18:53:40 +01:00
Marco Pasqualetti d4648afe01 chore: correct missing types task reference (#572) 2025-02-16 16:15:59 +01:00
renovate[bot] 0653349c08 chore(deps): update dependency turbo to v2.4.2 (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-16 15:49:29 +01:00
Marco Pasqualetti 12e0937174 chore: add internal vite config (#568) 2025-02-16 15:39:25 +01:00
Marco Pasqualetti d37b655add feat(crates/tuono): feat: add spinner to tuono build (#560) 2025-02-14 19:20:22 +01:00
Valerio Ageno e3b1c0e013 feat: create body extractor function (#561) 2025-02-14 17:39:37 +01:00
Valerio Ageno be77068c73 chore: update version to v0.17.8 (#559) 2025-02-12 18:34:06 +01:00
Marco Pasqualetti 907c92bedc chore: remove documentation (#556) 2025-02-12 17:01:35 +01:00
Jovan Milosevic 6794ab20d3 feat: Add e2e setup and tests (#535) 2025-02-12 08:57:18 +01:00
Valerio Ageno e00dad9200 feat: polyfill URLSearchParams on the server side (#555) 2025-02-12 08:35:36 +01:00
Valerio Ageno 9aa6aedee0 feat: add spinner to 'tuono dev' (#550) 2025-02-11 17:21:28 +01:00
renovate[bot] 0114867fe6 chore(deps): update dependency typescript-eslint to v8.24.0 (#551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 20:29:29 +01:00
renovate[bot] 4f58d42582 chore(deps): update devdependencies (eslint) to v9.20.0 (#546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 17:44:23 +01:00
Marco Pasqualetti 40add60fd8 chore(pnpm): dedupe dependencies (#547) 2025-02-10 17:31:42 +01:00
renovate[bot] 9a4bad95ed chore(deps): update dependency prettier to v3.5.0 (#545)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 09:15:42 +01:00
renovate[bot] 3e28aeea27 fix(deps): update dependency posthog-js to v1.215.7 (#543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 08:56:18 +01:00
renovate[bot] 58e48147b8 chore(deps): update dependency @types/node to v22.13.1 (#541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 08:36:19 +01:00
Marco Pasqualetti 68eda84053 test(packages/tuono-router): use screen instead of render matchers (#540) 2025-02-09 19:01:30 +01:00
Jacob Marshall 253f35dbbb fix: update link component to allow modifier+click to properly work, and write tests (#534) 2025-02-09 18:50:45 +01:00
Jacob Marshall 0f72506f95 feat: add css key to vite config (#518) 2025-02-08 16:01:26 +01:00
Marco Pasqualetti ebe1abe422 docs: fix broken links (#532) 2025-02-07 21:02:41 +01:00
Valerio Ageno f5a7480f69 docs: add API routes page (#528)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2025-02-07 19:07:58 +01:00
Marco Pasqualetti c0139fddc8 docs(sitemap.xml): exclude 404 page (#530) 2025-02-07 19:00:43 +01:00
Valerio Ageno 9e08a949bb docs: update tuono version to v0.17.7 (#524) 2025-02-07 18:50:46 +01:00
Valerio Ageno d86c97979b chore: bump version to v0.17.7 (#523) 2025-02-07 18:35:14 +01:00
Valerio Ageno 4e266522b6 chore: update ssr_rs to v0.8.1 (#529) 2025-02-07 18:15:16 +01:00
Marco Pasqualetti a58a1a4da9 chore(deps): update dependency turbo to v2.4.0 (#525) 2025-02-07 12:07:15 +01:00
Valerio Ageno 22c1dd04c2 docs: enable posthog analytics (#519) 2025-02-06 20:58:33 +01:00
Valerio Ageno 74e34dbbf9 fix: with-tailwind template download (#522) 2025-02-06 20:55:30 +01:00
renovate[bot] 4f38de3dea chore(deps): update devdependencies (eslint) (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 20:22:08 +01:00
Marco Pasqualetti 9d97f2d345 chore: refine issue templates (#508) 2025-02-06 16:21:13 +01:00
Valerio Ageno 938cf3d8c6 docs: add footer to doc (#512) 2025-02-06 15:03:39 +01:00
Valerio Ageno e1443274e3 docs: add privacy policy (#517) 2025-02-06 12:50:28 +01:00
Valerio Ageno b9f8c0312f docs: add mdx page (#514)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2025-02-06 12:48:49 +01:00
Marco Pasqualetti 674a817131 docs: add /styles/css-modules page (#513) 2025-02-06 12:41:59 +01:00
Valerio Ageno 2cbe53f7b7 docs: fix typo (#516) 2025-02-06 12:04:31 +01:00
Valerio Ageno f3033ef0d2 docs: fix quote blocks (#515) 2025-02-06 10:33:53 +01:00
Valerio Ageno 0a0fb8c7f8 docs: add Request page (#502)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2025-02-06 09:54:47 +01:00
Valerio Ageno f72a1a9e5c docs: improve readability (#510) 2025-02-05 18:33:09 +01:00
Valerio Ageno fb29a26246 docs: refine dynamic page hierarchy (#511) 2025-02-05 17:45:12 +01:00
Valerio Ageno 79d4aff758 docs: add TuonoScript component mention (#509)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2025-02-05 16:44:43 +01:00
Jacob Marshall 7ea8414d6c docs: write page for dynamic function (#503) 2025-02-05 15:01:49 +01:00
Marco Pasqualetti 1a3c9a7fbe feat: refine package.json version ranges (#507) 2025-02-05 09:28:18 +01:00
renovate[bot] cecf2ba8cc chore(deps): update dependency vitest to v2.1.9 [security] (#505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-05 06:42:15 +01:00
Valerio Ageno eaaf2513d7 docs: add SSG page (#498)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2025-02-04 15:58:34 +01:00
Valerio Ageno fa4d435320 docs: add Response enum page (#500)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2025-02-04 15:48:26 +01:00
Marco Pasqualetti 2aff72942c docs: add getting-started/setup-project (#488) 2025-02-04 09:18:03 +01:00
renovate[bot] 11666fc422 chore(deps): update dependency happy-dom to v16.8.1 (#494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 20:01:27 +01:00
renovate[bot] aef456cbf2 chore(deps): update dependency @testing-library/react to v16.2.0 (#490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 20:00:38 +01:00
Marco Pasqualetti 97b9d71caa docs: add custom 404 page (#486) 2025-02-03 19:57:14 +01:00
renovate[bot] 5fa38b9506 chore(deps): update dependency @types/node to v22.13.0 (#492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 18:16:22 +01:00
Marco Pasqualetti 98e50da575 docs: remove "How is tuono different?" and refine “Installation” (#480) 2025-02-03 09:44:13 +01:00
Marco Pasqualetti c8cf0771d4 test(packages/tuono-router): refine unit tests (#478) 2025-02-02 17:45:16 +01:00
Valerio Ageno 1c6e78ad57 docs: add SSR page (#473) 2025-02-02 09:39:50 +01:00
Valerio Ageno 2b6f8c16ce docs: remove valerioageno email reference (#477) 2025-02-02 09:22:16 +01:00
Jacob Marshall 2403ef3d64 docs: indent ToC based on heading levels (#475) 2025-02-02 09:04:30 +01:00
renovate[bot] 003c0766cf chore(deps): update node.js to v22.13.1 (#445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 18:00:08 +01:00
renovate[bot] 9d7e59d793 chore(deps): update dependency turbo to v2.3.4 (#430)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 17:51:22 +01:00
Valerio Ageno 43f03adedf fix: heading order (#471) 2025-01-30 21:20:34 +01:00
Valerio Ageno 211c6401e9 docs: refine UI (#466) 2025-01-30 21:04:15 +01:00
Jacob Marshall 01b90b327e docs: add headings containing only code to ToC (#467)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-30 08:48:07 +01:00
Jacob Marshall ad0b09c3df fix(docs): fix all line and link overflow issues (#426) 2025-01-29 18:42:53 +01:00
Marco Pasqualetti 362e1be58d ci: get documentation tuono version automatically (#462) 2025-01-29 17:32:35 +01:00
Marco Pasqualetti eb9039daa3 fix(crates/tuono_lib): print server url as valid URL to be clickable on CLI (#464) 2025-01-29 17:31:06 +01:00
Marco Pasqualetti ff7433f434 docs: use consistent snippet style across the codebase (#451)
Co-authored-by: wd <52734504+wdcodecn@users.noreply.github.com>
2025-01-29 08:33:55 +01:00
Marco Pasqualetti a4e207688a docs: update to tuono 0.17.6 (#461) 2025-01-29 07:20:05 +01:00
209 changed files with 2838 additions and 7387 deletions
+3
View File
@@ -5,3 +5,6 @@ end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 80
[*.rs]
indent_size = 4
+1
View File
@@ -1,6 +1,7 @@
name: Bug report
description: File a bug report
title: '[Bug]: '
type: Bug
labels: ['bug']
body:
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question / Get support
url: https://discord.com/invite/khQzPa654B
about: Ask questions and discuss with other community members
@@ -1,6 +1,7 @@
name: Feature request
description: Suggest an idea for this project
title: '[Feature request]: '
type: Feature
labels: ['enhancement']
body:
+2 -4
View File
@@ -8,6 +8,7 @@ typescript:
- changed-files:
- any-glob-to-any-file:
[
'devtools/**',
'packages/**',
'package.json',
'pnpm-*.yaml',
@@ -15,10 +16,6 @@ typescript:
'tsconfig.json',
]
documentation:
- changed-files:
- any-glob-to-any-file: ['apps/documentation/**']
'CI/CD':
- changed-files:
- any-glob-to-any-file: ['.github/**']
@@ -27,6 +24,7 @@ documentation:
- changed-files:
- any-glob-to-any-file:
[
'devtools/**',
'.npmrc',
'.nvmrc',
'.prettierrc',
-69
View File
@@ -1,69 +0,0 @@
name: Documentation Website CI
on:
push:
branches:
- 'main'
# match
# - all files inside `apps/documentation` and `.github`
# - all files in the root expect `Cargo.toml`
paths:
- '.github/**'
- '*'
- '!Cargo.toml'
- 'apps/documentation/**'
pull_request:
# same as `on.push.paths`
paths:
- '.github/**'
- '*'
- '!Cargo.toml'
- 'apps/documentation/**'
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build:
name: Check build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install NodeJS Dependencies
uses: ./.github/actions/install-node-dependencies
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.17.5
- name: Build project
working-directory: ./apps/documentation
run: tuono build --static
code_check:
name: Check format, lint and types
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install NodeJS Dependencies
uses: ./.github/actions/install-node-dependencies
- name: Check formatting
run: pnpm docs:format:check
- name: Lint
run: pnpm docs:lint
- name: Types
run: pnpm docs:types
@@ -1,47 +0,0 @@
name: Deploy documentation website on AWS S3
on:
push:
branches:
- 'main'
paths:
- '.github/**'
- 'apps/documentation/**'
jobs:
deploy-documentation:
name: Deploy
runs-on: ubuntu-latest
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install NodeJS Dependencies
uses: ./.github/actions/install-node-dependencies
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.17.5
- name: Build project
working-directory: ./apps/documentation
run: tuono build --static
- name: Deploy
uses: reggionick/s3-deploy@v4
with:
folder: apps/documentation/out/static
bucket: tuono-documentation
bucket-region: eu-west-3
dist-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
invalidation: /
delete-removed: true
no-cache: true
private: true
files-to-include: '{.*/**,**}'
+61
View File
@@ -0,0 +1,61 @@
name: E2E CI
on:
push:
branches:
- 'main'
pull_request:
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
e2e:
name: E2E Test (${{ matrix.os }})
strategy:
fail-fast: true
matrix:
os:
- 'ubuntu-latest'
- 'macos-latest'
- 'windows-latest'
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install NodeJS Dependencies
uses: ./.github/actions/install-node-dependencies
- name: Setup rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install Playwright Browsers
run: pnpm exec playwright install
- name: Run Playwright E2E tests
run: pnpm test:e2e
ci_ok:
name: E2E CI OK
runs-on: ubuntu-latest
timeout-minutes: 1
if: always()
needs: [e2e]
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
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Check type
id: type
# @warning Keep in sync with apps/documentation/src/routes/documentation/contributing/pull-requests.mdx
# @warning Keep in sync with https://github.com/tuono-labs/tuono-documentation/blob/main/src/routes/documentation/contributing/pull-requests.mdx
run: |
VALID_COMMIT_TYPES="chore|ci|docs|feat|fix|refactor|test"
REGEX="^(${VALID_COMMIT_TYPES})(\(.*\))?!?: .*"
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
uses: ./.github/actions/install-node-dependencies
- name: Test project
run: pnpm repo:root:format:check
run: pnpm repo:root:format
ci_ok:
name: Repo root CI OK
+4 -4
View File
@@ -51,13 +51,13 @@ jobs:
uses: ./.github/actions/install-node-dependencies
- name: Check formatting
run: pnpm format:check
run: pnpm run format
- name: Lint
run: pnpm lint
run: pnpm run lint
- name: Types
run: pnpm types
- name: Typecheck
run: pnpm run typecheck
ci_ok:
name: Typescript CI OK
+2 -1
View File
@@ -28,4 +28,5 @@ vite.config.ts.timestamp*
*.pdb
# Files, generated by the code editors
.vscode/
.vscode/
+1 -1
View File
@@ -1 +1 @@
22.13.0
22.13.1
+2
View File
@@ -6,3 +6,5 @@ dist
vite.config.ts.timestamp-*
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
e2e/fixtures/*/test-results
+1 -1
View File
@@ -7,9 +7,9 @@ members = [
"crates/tuono_lib_macros"
]
exclude = [
"apps/documentation",
"examples/with-mdx",
"examples/tuono-app",
"examples/tuono-tutorial",
"examples/with-tailwind",
"e2e/fixtures/base",
]
-1
View File
@@ -1 +0,0 @@
# Stadalone apps folder
-17
View File
@@ -1,17 +0,0 @@
# 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
-7
View File
@@ -1,7 +0,0 @@
# Tuono starter
This is the starter tuono project. To download it run in your terminal:
```shell
$ tuono new [NAME]
```
-33
View File
@@ -1,33 +0,0 @@
{
"name": "documentation",
"description": "The react/rust fullstack framework documentation",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"lint": "eslint .",
"format": "prettier --write --ignore-unknown .",
"format:check": "prettier --check --ignore-unknown .",
"types": "tsc --noEmit"
},
"dependencies": {
"@mantine/code-highlight": "7.16.1",
"@mantine/core": "7.16.1",
"@mantine/hooks": "7.16.1",
"@mdx-js/react": "3.1.0",
"@tabler/icons-react": "3.28.1",
"clsx": "2.1.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"remark-gfm": "4.0.0",
"tuono": "npm:tuono@0.17.5"
},
"devDependencies": {
"@mdx-js/rollup": "3.1.0",
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"postcss": "8.5.1",
"postcss-preset-mantine": "1.17.0",
"postcss-simple-vars": "7.0.1"
}
}
-14
View File
@@ -1,14 +0,0 @@
export default {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

@@ -1,18 +0,0 @@
{
"polyfills": [
{
"identifier": "HTMLElement",
"import": {
"package": "@lit-labs/ssr-dom-shim",
"name": "HTMLElement"
}
},
{
"identifier": "customElements",
"import": {
"package": "@lit-labs/ssr-dom-shim",
"name": "customElements"
}
}
]
}
@@ -1,19 +0,0 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
@@ -1,87 +0,0 @@
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>
)
}
@@ -1,3 +0,0 @@
import Breadcrumbs from './Breadcrumbs'
export default Breadcrumbs
@@ -1,24 +0,0 @@
import type { JSX } from 'react'
import { Button } from '@mantine/core'
import { IconEdit } from '@tabler/icons-react'
import { useRouter } from 'tuono'
const GITHUB_URL =
'https://github.com/tuono-labs/tuono/tree/main/apps/documentation/src/routes'
export default function EditPage(): JSX.Element {
const { pathname } = useRouter()
return (
<Button
p={0}
mt={60}
component="a"
variant="transparent"
leftSection={<IconEdit />}
target="_blank"
href={GITHUB_URL.concat(pathname).concat('.mdx')}
>
Edit page
</Button>
)
}
@@ -1,3 +0,0 @@
import EditPage from './EditPage'
export default EditPage
@@ -1,54 +0,0 @@
import type { JSX } from 'react'
import {
Button,
Center,
Container,
CopyButton,
Group,
rem,
Text,
Title,
} from '@mantine/core'
import { IconCopy, IconCheck } from '@tabler/icons-react'
import { Link } from 'tuono'
export default function Hero(): JSX.Element {
return (
<Container size={1100} my={100}>
<Center>
<Title order={1}>Superfast fullstack react framework</Title>
</Center>
<Center mt={50}>
<Text fz="18px">
The technologies we love seamlessly working together to unleash the
<strong> highest web performance</strong> ever met on React
</Text>
</Center>
<Center mt={50}>
<Group>
<Button component={Link} href="/documentation" size="lg">
Get Started
</Button>
<CopyButton value="cargo install tuono">
{({ copied, copy }) => (
<Button
onClick={copy}
size="lg"
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
color="gray"
leftSection="$ cargo install tuono"
rightSection={
copied ? (
<IconCheck style={{ width: rem(20) }} />
) : (
<IconCopy style={{ width: rem(20) }} />
)
}
/>
)}
</CopyButton>
</Group>
</Center>
</Container>
)
}
@@ -1,3 +0,0 @@
import Hero from './Hero'
export default Hero
@@ -1,6 +0,0 @@
import type { JSX } from 'react'
import { Text, type TextProps } from '@mantine/core'
export default function MdxBold(props: TextProps): JSX.Element {
return <Text component="span" fw={700} {...props} />
}
@@ -1,3 +0,0 @@
import MdxBold from './MdxBold'
export default MdxBold
@@ -1,8 +0,0 @@
import type { JSX, HTMLAttributes } from 'react'
import { Code } from '@mantine/core'
export default function MdxCode(
props: HTMLAttributes<HTMLPreElement>,
): JSX.Element {
return <Code {...props} style={{ fontSize: 'inherit' }} />
}
@@ -1,3 +0,0 @@
import MdxCode from './MdxCode'
export default MdxCode
@@ -1,36 +0,0 @@
import type { JSX, AnchorHTMLAttributes } from 'react'
import { Button } from '@mantine/core'
import { Link } from 'tuono'
import { IconExternalLink } from '@tabler/icons-react'
export default function MdxLink(
props: AnchorHTMLAttributes<HTMLAnchorElement>,
): JSX.Element {
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
return (
<Button
component="a"
{...props}
target="_blank"
rightSection={
<IconExternalLink size="16px" style={{ marginLeft: -5 }} />
}
variant="transparent"
style={{ height: '20px' }}
mt={-2}
p={0}
/>
)
}
return (
<Button
component={Link}
{...props}
target="_blank"
variant="transparent"
style={{ height: '20px' }}
mt={-2}
p={0}
/>
)
}
@@ -1,3 +0,0 @@
import MdxLink from './MdxLink'
export default MdxLink
@@ -1,5 +0,0 @@
.pre {
code {
font-size: 14px;
}
}
@@ -1,24 +0,0 @@
import type { JSX } from 'react'
import { CodeHighlight } from '@mantine/code-highlight'
import styles from './MdxPre.module.css'
interface PreProps {
children: {
props: {
children: string
className?: string
}
}
}
export default function MdxPre({ children }: PreProps): JSX.Element {
return (
<CodeHighlight
className={styles.pre}
code={children.props.children || ''}
style={{ borderRadius: 8, fontWeight: 100 }}
language={children.props.className?.replace('language-', '')}
/>
)
}
@@ -1,3 +0,0 @@
import MdxPre from './MdxPre'
export default MdxPre
@@ -1,44 +0,0 @@
import type { JSX, ReactNode } from 'react'
import { MDXProvider } from '@mdx-js/react'
import MdxLink from './MdxLink'
import MdxPre from './MdxPre'
import MdxQuote from './MdxQuote'
import MdxCode from './MdxCode'
import { h } from './MdxTitle'
import MdxBold from './MdxBold'
import MdxTable from './MdxTable'
interface MdxProviderProps {
children: ReactNode
}
export default function MdxProvider({
children,
}: MdxProviderProps): JSX.Element {
return (
<MDXProvider
components={{
a: MdxLink,
pre: MdxPre,
blockquote: MdxQuote,
code: MdxCode,
h1: h(1),
h2: h(2),
h3: h(3),
h4: h(4),
h5: h(5),
h6: h(6),
strong: MdxBold,
table: MdxTable,
thead: MdxTable.Thead,
tbody: MdxTable.Tbody,
tr: MdxTable.Tr,
th: MdxTable.Th,
td: MdxTable.Td,
}}
>
{children}
</MDXProvider>
)
}
@@ -1,21 +0,0 @@
import type { JSX, HTMLAttributes } from 'react'
import { Blockquote, Space } from '@mantine/core'
export default function MdxQuote(
props: HTMLAttributes<HTMLQuoteElement>,
): JSX.Element {
return (
<>
<Blockquote
color="violet"
py={1}
px={20}
mt={30}
iconSize={30}
{...props}
style={{ borderRadius: 8 }}
/>
<Space h="md" />
</>
)
}
@@ -1,3 +0,0 @@
import MdxQuote from './MdxQuote'
export default MdxQuote
@@ -1,27 +0,0 @@
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
@@ -1,3 +0,0 @@
import MdxTable from './MdxTable'
export default MdxTable
@@ -1,47 +0,0 @@
import type { ElementType, JSX, ReactNode } 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}
style={{ scrollMargin: 70 }}
{...props}
id={idGen(props.children)}
/>
)
}
function idGen(children: ReactNode): string {
if (Array.isArray(children)) {
const result = children
.map((child) => {
if (typeof child === 'string') {
return child
}
if (typeof child === 'object' && child !== null && 'props' in child) {
const childWithProps = child as { props?: { children?: ReactNode } }
return typeof childWithProps.props?.children === 'string'
? childWithProps.props.children
: ''
}
return ''
})
.join('')
return result.toLowerCase().replace(/\s+/g, '-')
}
return typeof children === 'string'
? children.toLowerCase().replace(/\s+/g, '-')
: ''
}
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
}
@@ -1,5 +0,0 @@
import MdxTitle, { h } from './MdxTitle'
export default MdxTitle
export { h }
@@ -1,3 +0,0 @@
import MdxProvider from './MdxProvider'
export default MdxProvider
@@ -1,32 +0,0 @@
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>
)
}
@@ -1,3 +0,0 @@
import { MdxWrapper } from './MdxWrapper'
export default MdxWrapper
@@ -1,45 +0,0 @@
import type { ReactNode } from 'react'
interface MetaTagsProps {
title: string
description?: string
type?: 'website' | 'article' | 'book' | 'video.movie' | 'music.song'
canonical: string
}
export default function MetaTags({
title,
description,
type = 'article',
canonical,
}: MetaTagsProps): ReactNode {
return (
<>
<title>{title}</title>
<link rel="canonical" href={canonical} />
<meta
name="description"
content={
description ??
'The technologies we love seamlessly working together to unleash the highest web performance ever met on React'
}
/>
<meta
name="keywords"
content="ReactJs, Typescript, Rust, JavaScript, Fullstack framework, Web development"
/>
<meta property="og:title" content={title} />
<meta property="og:type" content={type} />
<meta property="og:image" content="https://tuono.dev/og-cover.png" />
<meta property="og:url" content={canonical} />
<meta property="og:description" content={description} />
<meta property="og:site_name" content="Tuono" />
<meta property="og:locale" content="en_US" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@tuono_dev" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
</>
)
}
@@ -1,3 +0,0 @@
import MetaTags from './MetaTags'
export default MetaTags
@@ -1,33 +0,0 @@
import type { JSX } from 'react'
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
import { Link } from 'tuono'
import NavbarActions from './NavbarActions'
interface NavbarProps {
toggle: () => void
}
export default function Navbar({ toggle }: NavbarProps): JSX.Element {
return (
<AppShell.Header p="sm">
<Flex justify="space-between">
<Button
component={Link}
href="/"
variant="transparent"
p={0}
fz={28}
hiddenFrom="sm"
>
Tuono
</Button>
<Box />
<Flex align="center" gap={8}>
<NavbarActions />
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
</Flex>
</Flex>
</AppShell.Header>
)
}
@@ -1,35 +0,0 @@
import type { JSX } from 'react'
import { Flex, ActionIcon, Group } from '@mantine/core'
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
import ThemeBtn from '../ThemeBtn'
export default function NavbarActions(): JSX.Element {
return (
<Flex gap={8}>
<Group gap={8}>
<ActionIcon
variant="default"
size="lg"
aria-label="Check the project on github"
href="https://github.com/tuono-labs/tuono"
target="_blank"
component="a"
>
<IconBrandGithub />
</ActionIcon>
<ActionIcon
variant="default"
size="lg"
aria-label="Join the Tuono's community on Discord!"
href="https://discord.com/invite/khQzPa654B"
target="_blank"
component="a"
>
<IconBrandDiscord />
</ActionIcon>
</Group>
<ThemeBtn />
</Flex>
)
}
@@ -1,3 +0,0 @@
import Navbar from './Navbar'
export default Navbar
@@ -1,63 +0,0 @@
import type { JSX } from 'react'
import { Box, Button, Text, Title, Flex } from '@mantine/core'
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
import { Link } from 'tuono'
interface NavigationButtonData {
href: string
title: string
}
interface NavigationButtonsProps {
prev?: NavigationButtonData
next?: NavigationButtonData
}
export default function NavigationButtons({
prev,
next,
}: NavigationButtonsProps): JSX.Element {
return (
<Flex mt={50} gap={10}>
{prev && <NavigationBtn type="prev" {...prev} />}
{next && <NavigationBtn type="next" {...next} />}
</Flex>
)
}
interface NavigationBtnProps extends NavigationButtonData {
type: 'next' | 'prev'
}
const NavigationBtn = ({
type,
title,
href,
}: NavigationBtnProps): JSX.Element => {
const heading = type === 'next' ? 'Next' : 'Previous'
const textAlign = type === 'next' ? 'left' : 'right'
const variant = type === 'next' ? 'filled' : 'outline'
return (
<Button
component={Link}
fullWidth
variant={variant}
href={href}
justify="space-between"
h="auto"
rightSection={type === 'next' && <IconArrowRight />}
leftSection={type === 'prev' && <IconArrowLeft />}
p="20"
>
<Box>
<Title component="span" display="block" order={4} style={{ textAlign }}>
{heading}
</Title>
<Text component="span" display="block" style={{ textAlign }}>
{title}
</Text>
</Box>
</Button>
)
}
@@ -1,3 +0,0 @@
import NavigationButtons from './NavigationButtons'
export default NavigationButtons
@@ -1,111 +0,0 @@
import type { JSX } from 'react'
import {
AppShell,
Badge,
Flex,
Divider,
Title,
Button,
ScrollArea,
Text,
} from '@mantine/core'
import { IconX } from '@tabler/icons-react'
import { useMediaQuery } from '@mantine/hooks'
import { sidebarElements } from './sidebarElements'
import SidebarLink from './SidebarLink'
interface SidebarProps {
close: () => void
}
function SidebarHeader({ close }: SidebarProps): JSX.Element {
const isSm = useMediaQuery('(min-width: 48em)')
return (
<AppShell.Section>
<Flex mb={20} w="100%" justify="space-between">
<Flex
gap={10}
align="center"
w={isSm ? '100%' : 'auto'}
justify="center"
>
<Title order={3}>Tuono</Title>
<Badge mt={4} size="xs" variant="outline">
Docs
</Badge>
</Flex>
<Button onClick={close} hiddenFrom="sm" variant="transparent" p="0">
<IconX />
</Button>
</Flex>
</AppShell.Section>
)
}
function SidebarElements({ close }: SidebarProps): JSX.Element {
return (
<AppShell.Section component={ScrollArea}>
{sidebarElements.map((el, i) => {
if (el.type === 'divider') {
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
}
if (el.type === 'title') {
return (
<Text
key={`${el.type}-${i}`}
size="xs"
fw={700}
fz={12}
pl={12}
py={5}
>
{el.label}
</Text>
)
}
if (el.children?.length) {
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
leftSection={el.leftIcon}
>
{el.children.map((child, index) => (
<SidebarLink
href={child.href}
label={child.label}
key={index}
onClick={close}
/>
))}
</SidebarLink>
)
}
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
onClick={close}
/>
)
})}
</AppShell.Section>
)
}
export default function Sidebar({ close }: SidebarProps): JSX.Element {
return (
<AppShell.Navbar p="md">
<SidebarHeader close={close} />
<SidebarElements close={close} />
</AppShell.Navbar>
)
}
@@ -1,17 +0,0 @@
.link {
border-radius: 8px;
margin-top: 0.25rem;
line-height: 1rem;
font-weight: 500;
transition: 0.3s;
}
.link span {
font-size: 14px !important;
color: var(--mantine-color-sidebar-gray);
}
.link:hover span,
.active span {
color: var(--mantine-color-sidebar-text-hover);
}
@@ -1,42 +0,0 @@
import type { JSX, ReactNode } from 'react'
import { NavLink, type NavLinkProps } from '@mantine/core'
import clsx from 'clsx'
import { Link, useRouter } from 'tuono'
import { IconChevronRight } from '@tabler/icons-react'
import styles from './SidebarLink.module.css'
interface SidebarLinkProps {
label: string
href: string
onClick?: () => void
children?: ReactNode
}
export default function SidebarLink(
props: SidebarLinkProps & NavLinkProps,
): JSX.Element {
const { pathname } = useRouter()
const isActive = pathname === props.href
const internalProps = {
active: isActive,
className: clsx(styles.link, isActive && styles.active),
rightSection: props.children && (
<IconChevronRight
size="1.2rem"
stroke={1.5}
className="mantine-rotate-rtl"
/>
),
autoContrast: true,
...props,
}
if (props.href.startsWith('#')) {
return <NavLink component="button" {...internalProps} />
}
return <NavLink component={Link} {...internalProps} />
}
@@ -1,3 +0,0 @@
import Sidebar from './Sidebar'
export default Sidebar
@@ -1,270 +0,0 @@
import type { ReactNode } from 'react'
interface SidebarElementBase<T extends string> {
type: T
}
type SidebarDivider = SidebarElementBase<'divider'>
interface SidebarLink extends SidebarElementBase<'element'> {
label: string
href: string
children?: Array<SidebarLink>
leftIcon?: ReactNode
}
interface SidebarTitle extends SidebarElementBase<'title'> {
label: string
}
type SidebarElement = SidebarDivider | SidebarLink | SidebarTitle
export const sidebarElements: Array<SidebarElement> = [
{
type: 'element',
label: 'Home',
href: '/',
},
{
type: 'element',
label: 'Getting started',
href: '#focus',
children: [
{
type: 'element',
label: 'Installation',
href: '/documentation/installation',
},
{
type: 'element',
label: 'How is tuono different?',
href: '/documentation/how-is-tuono-different',
},
],
},
{
type: 'element',
label: 'Tutorial',
href: '#focus',
children: [
{
type: 'element',
href: '/documentation/tutorial',
label: 'Intro',
},
{
type: 'element',
href: '/documentation/tutorial/development-setup',
label: 'Development setup',
},
{
type: 'element',
href: '/documentation/tutorial/api-fetching',
label: 'API fetching',
},
{
type: 'element',
href: '/documentation/tutorial/components',
label: 'Components',
},
{
type: 'element',
href: '/documentation/tutorial/dynamic-routes',
label: 'Dynamic routes',
},
{
type: 'element',
href: '/documentation/tutorial/error-handling',
label: 'Error handling',
},
{
type: 'element',
href: '/documentation/tutorial/seo',
label: 'SEO and meta tags',
},
{
type: 'element',
href: '/documentation/tutorial/redirections',
label: 'Redirections',
},
{
type: 'element',
href: '/documentation/tutorial/production',
label: 'Production build',
},
{
type: 'element',
href: '/documentation/tutorial/conclusion',
label: 'Conclusion',
},
],
},
{ type: 'divider' },
{ type: 'title', label: 'Overview' },
{
type: 'element',
label: 'Routing',
href: '#focus',
children: [
{
type: 'element',
label: 'Defining routes',
href: '/documentation/routing/defining-routes',
},
{
type: 'element',
label: 'Dynamic routes',
href: '/documentation/routing/dynamic-routes',
},
{
type: 'element',
label: 'Link and navigation',
href: '/documentation/routing/link-and-navigation',
},
{
type: 'element',
label: 'Pages',
href: '/documentation/routing/pages',
},
{
type: 'element',
label: 'Loading state',
href: '/documentation/routing/loading-state',
},
{
type: 'element',
label: 'Layouts',
href: '/documentation/routing/layouts',
},
{
type: 'element',
label: 'Redirecting',
href: '/documentation/routing/redirecting',
},
],
},
{
type: 'element',
label: 'Rendering',
href: '#focus',
children: [
{
type: 'element',
label: 'Server side rendering',
href: '/documentation/rendering/server-side-rendering',
},
{
type: 'element',
label: 'Static site rendering',
href: '/documentation/rendering/static-site-rendering',
},
],
},
{
type: 'element',
label: 'Styles',
href: '#focus',
children: [
{
type: 'element',
label: 'CSS modules',
href: '/documentation/styles/css-modules',
},
{
type: 'element',
label: 'Tailwind CSS',
href: '/documentation/styles/tailwind-css',
},
],
},
{
type: 'element',
label: 'Integrations',
href: '#focus',
children: [
{
type: 'element',
label: 'MDX',
href: '/documentation/integrations/mdx',
},
],
},
{
type: 'element',
label: 'Core concepts',
href: '#focus',
children: [
{
type: 'element',
label: 'Multithreading',
href: '/documentation/core-concepts/multithreading',
},
],
},
{ type: 'divider' },
{ type: 'title', label: 'API reference' },
{
type: 'element',
label: 'Components',
href: '#focus',
children: [
{
type: 'element',
label: 'Link',
href: '/documentation/components/link',
},
],
},
{
type: 'element',
label: 'Hooks',
href: '#focus',
children: [
{
type: 'element',
label: 'useRouter',
href: '/documentation/hooks/use-router',
},
],
},
{
type: 'element',
label: 'CLI',
href: '/documentation/cli',
},
{
type: 'element',
label: 'Application state',
href: '/documentation/application-state',
},
{
type: 'element',
label: 'Configuration',
href: '/documentation/configuration',
},
{ type: 'divider' },
{
type: 'element',
label: 'Contributing',
href: '#focus',
leftIcon: '✨',
children: [
{
type: 'element',
label: 'Guildelines',
href: '/documentation/contributing',
},
{
type: 'element',
label: 'Local development',
href: '/documentation/contributing/local-development',
},
{
type: 'element',
label: 'Pull requests',
href: '/documentation/contributing/pull-requests',
},
],
},
]
@@ -1,107 +0,0 @@
.wrapper {
position: sticky;
top: var(--mantine-spacing-xl);
padding-top: 55px;
flex: 0 0 calc(250px - 20px);
min-width: 220px;
max-width: 220px;
height: 100vh;
/* var(--mantine-breakpoint-md) = 62em */
@media (max-width: 62em) {
display: none;
}
}
.inner {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.link {
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
display: block;
border-left: 1px solid transparent;
padding: 8px var(--mantine-spacing-md);
margin-left: -1px;
padding-left: calc(1 * var(--mantine-spacing-lg));
border-top-right-radius: var(--mantine-radius-sm);
border-bottom-right-radius: var(--mantine-radius-sm);
@mixin light {
color: var(--mantine-color-gray-7);
}
@mixin dark {
color: var(--mantine-color-dark-1);
}
@mixin rtl {
border-left: 0;
border-right: 1px solid transparent;
margin-left: 0;
margin-right: -1px;
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
}
&:hover {
border-color: var(--mantine-color-violet-1);
@mixin light {
color: var(--mantine-color-black);
}
@mixin dark {
color: var(--mantine-color-violet-1);
}
}
&[data-active] {
border-color: var(--mantine-color-violet-5);
@mixin light {
color: var(--mantine-color-violet-8);
}
@mixin dark {
color: var(--mantine-color-violet-1);
}
}
}
.title {
margin-left: var(--mantine-spacing-md);
@mixin rtl {
margin-left: 0;
margin-right: var(--mantine-spacing-md);
}
}
.items {
border-left: 1px solid;
@mixin light {
border-color: var(--mantine-color-gray-3) !important;
}
@mixin dark {
border-color: var(--mantine-color-dark-4) !important;
}
@mixin rtl {
border-left: 0;
border-right: 1px solid;
@mixin light {
border-color: var(--mantine-color-gray-3) !important;
}
@mixin dark {
border-color: var(--mantine-color-dark-4) !important;
}
}
}
@@ -1,119 +0,0 @@
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>
)
}
@@ -1,50 +0,0 @@
/**
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
*/
export interface Heading {
depth: number
content: string
id: string
getNode: () => HTMLHeadingElement
}
function getCleanedText(element: HTMLElement): string {
const clone = element.cloneNode(true) as HTMLElement
clone.querySelectorAll('code, pre').forEach((codeBlock) => {
const textNode = document.createTextNode(codeBlock.textContent || '')
codeBlock.replaceWith(textNode)
})
return clone.textContent?.trim() || ''
}
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
const result: Array<Heading> = []
for (const heading of headings) {
const depth = parseInt(heading.getAttribute('data-order') || '1', 10)
if (depth <= 3 && heading.id) {
result.push({
depth,
content: getCleanedText(heading),
id: heading.id,
getNode: () =>
document.getElementById(heading.id) as HTMLHeadingElement,
})
}
}
return result
}
export function getHeadings(): Array<Heading> {
const root = document.getElementById('mdx-root')
if (!root) {
return []
}
return getHeadingsData(Array.from(root.querySelectorAll('[data-heading]')))
}
@@ -1,6 +0,0 @@
/*
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
*/
import { TableOfContents } from './TableOfContents'
export default TableOfContents
@@ -1,24 +0,0 @@
.icon {
width: 22px;
height: 22px;
}
.dark {
@mixin dark {
display: none;
}
@mixin light {
display: block;
}
}
.light {
@mixin light {
display: none;
}
@mixin dark {
display: block;
}
}
@@ -1,31 +0,0 @@
import type { JSX } from 'react'
import {
ActionIcon,
useMantineColorScheme,
useComputedColorScheme,
} from '@mantine/core'
import { IconSun, IconMoon } from '@tabler/icons-react'
import cx from 'clsx'
import classes from './ThemeBtn.module.css'
export default function ThemeBtn(): JSX.Element {
const { setColorScheme } = useMantineColorScheme()
const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: true,
})
return (
<ActionIcon
onClick={() => {
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
}}
variant="default"
size="lg"
aria-label="Toggle color scheme"
>
<IconSun className={cx(classes.icon, classes.light)} stroke={1.5} />
<IconMoon className={cx(classes.icon, classes.dark)} stroke={1.5} />
</ActionIcon>
)
}
@@ -1,3 +0,0 @@
import ThemeBtn from './ThemeBtn'
export default ThemeBtn
-5
View File
@@ -1,5 +0,0 @@
// declaration.d.ts
declare module '*.css' {
const CSSModule: Record<string, string>
export default CSSModule
}
-135
View File
@@ -1,135 +0,0 @@
import type { ReactNode, JSX } from 'react'
import { TuonoScripts } from 'tuono'
import {
ColorSchemeScript,
createTheme,
MantineProvider,
AppShell,
mantineHtmlProps,
type CSSVariablesResolver,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import MdxWrapper from '@/components/MdxWrapper'
import Navbar from '@/components/Navbar'
import Sidebar from '@/components/Sidebar'
import '@mantine/core/styles.css'
import '@mantine/code-highlight/styles.css'
interface RootRouteProps {
children: ReactNode
}
const theme = createTheme({
primaryColor: 'violet',
primaryShade: { light: 6, dark: 9 },
fontFamily: 'Inter',
respectReducedMotion: true,
radius: {
xs: '8px',
lg: '8px',
xl: '8px',
md: '8px',
sm: '8px',
},
fontSizes: {
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
xs: '14px',
sm: '14px',
},
colors: {
dark: [
'#d5d7e0',
'#acaebf',
'#8c8fa3',
'#666980',
'#4d4f66',
'#34354a',
'#2b2c3d',
'#1d1e30',
'#0c0d21',
'#01010a',
],
},
headings: {
sizes: {
h1: {
fontSize: '48px',
},
},
},
other: {
sidebarGrayLight: '#495057',
sidebarGrayDark: '#adb5bd',
sidebarTextHoverLight: '#212529',
sidebarTextHoverDark: '#f8f9fa',
},
})
const resolver: CSSVariablesResolver = (th) => ({
variables: {},
light: {
'--mantine-color-sidebar-gray': th.other.sidebarGrayLight as string,
'--mantine-color-sidebar-text-hover': th.other
.sidebarTextHoverLight as string,
},
dark: {
'--mantine-color-sidebar-gray': th.other.sidebarGrayDark as string,
'--mantine-color-sidebar-text-hover': th.other
.sidebarTextHoverDark as string,
},
})
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
const [opened, { toggle }] = useDisclosure()
return (
<html lang="en" {...mantineHtmlProps}>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<ColorSchemeScript />
</head>
<body>
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
<AppShell
layout="alt"
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: 'sm',
collapsed: { mobile: !opened },
}}
>
<Navbar toggle={toggle} />
<Sidebar close={toggle} />
<AppShell.Main pt={0} px="auto">
<MdxWrapper>{children}</MdxWrapper>
</AppShell.Main>
</AppShell>
</MantineProvider>
<TuonoScripts />
</body>
</html>
)
}
@@ -1,55 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Application state"
canonical="https://tuono.dev/documentation/application-state"
description="Learn how to add features to your Tuono application"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Application state' }]} />
# Application state
The main reason Tuono is fast is that it loads just the features that are needed for the project.
To define them, you need to fill the `ApplicationState` struct in the `./src/app.rs` file, and
they will be automatically available in all the handlers you will define across the application.
```rs
// src/app.rs
#[derive(Clone)]
pub struct ApplicationState {
pub website_name: String,
pub base_path: String
}
pub fn main() -> ApplicationState {
let website_name = "tuono".to_string();
let base_path = "http://localhost:3000".to_string();
ApplicationState {
website_name,
base_path
}
}
```
> For the sake of simplicity here we just enable a `String` but you can add Database connections or
> HTTP clients (take a look [here](/documentation/tutorial/api-fetching) for an example with an HTTP client).
Now the `ApplicationState` is available on all the handlers as following:
```rs
// src/routes/index.rs
#[tuono_lib::handler]
// The first argument always is the Request
// The ApplicationState arguments are optional. You can use just the ones you need
// to use in the handler (with no specific order).
async fn my_handler(req: Request, website_name: String) -> Response {
...
}
```
@@ -1,44 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - CLI"
canonical="https://tuono.dev/documentation/cli"
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
# CLI
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
> ☝️ Check the [installation](/documentation/installation) page if you haven't installed the
> `tuono` CLI yet.
To list all the available commands, run `tuono -h`.
```bash
The React/Rust full-stack framework
Usage: tuono <COMMAND>
Commands:
dev Start the development environment
build Build the production assets
new Scaffold a new project
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
To create a new project, run `tuono new [NAME]` (optionally you can pass the --template (or -t) flag - check the examples' folder).
Then to run the local development environment run inside the project folder `tuono dev`
Finally, when the project will be ready to be deployed just run `tuono build` to create the final React assets and to set the server project in the production mode.
Now to execute it, just run `cargo run --release`.
@@ -1,39 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Link"
canonical="https://tuono.dev/documentation/components/link"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
# Link
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.
```typescript jsx
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,76 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - tuono.config.ts"
canonical="https://tuono.dev/documentation/configuration"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Configuration' }]} />
# Configuration
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,48 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Contributing"
canonical="https://tuono.dev/documentation/contributing"
description="The project is massive - if you like it, do consider contributing!"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
# Contributing
## TL;DR
The project is massive - if you like it, do consider contributing!
## Getting started
The `tuono` project can mostly be split into the following subdomains:
- The CLI
- The Rust backend
- The React frontend
- The documentation website (which is written with tuono 🚀)
To check what the knowledge requirements are for each domain, check the
[requirements](#requirements) section below.
Currently, I'm keeping a private dashboard to prioritize new features and bug fixes, but if you
want to propose something, please open a new issue on Github or reach out to me using
my email address [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it). I'm also available
on Twitter (X) DMs `@valerioageno`, [Linkedin](https://www.linkedin.com/in/valerioageno)
and discord `@__v__v__`.
## Requirements
It's not strictly required to know both React (& typescript) and Rust (even though it
would be a great nice to have).
Without taking into account specific cases, we can mostly split the domain requirements by:
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
- The Backend needs just `Rust`
- The Frontend needs just `React` & `Typescript`
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
code is markdown).
@@ -1,56 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Contributing"
canonical="https://tuono.dev/documentation/contributing"
description="The project is massive - if you like it, do consider contributing!"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
# Contributing
## TL;DR
The project is massive - if you like it, do consider contributing!
## Getting started
The `tuono` project can mostly be split into the following subdomains:
- The CLI
- The Rust backend
- The React frontend
- The documentation website (which is written with tuono 🚀)
To check what the knowledge requirements are for each domain, check the
[requirements](#requirements) section below.
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project.
You can pick any task that you see in the `ready` column that is not already assigned.
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
## Requirements
It's not strictly required to know both React (& typescript) and Rust (even though it
would be a great nice to have).
Without taking into account specific cases, we can mostly split the domain requirements by:
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
- The Backend needs just `Rust`
- The Frontend needs just `React` & `Typescript`
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
code is markdown).
import NavigationButtons from '../../../components/NavigationButtons'
<NavigationButtons
next={{
title: 'Local development',
href: '/documentation/contributing/local-development',
}}
/>
@@ -1,220 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Contributing - Local development"
canonical="https://tuono.dev/documentation/contributing/local-development"
description="Contribute to Tuono. Learn here how to setup the repository for local development"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs
breadcrumbs={[
{ label: '✨ Contributing', href: '/documentation/contributing' },
{ label: 'Local development' },
]}
/>
# Local development
Thanks for your interest! In this page, you find the instructions to set up `tuono` on your local environment!
## Setup
### Fork and clone repository
After [forking the repo on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo):
```sh
git clone https://github.com/<your-name-here>/tuono
cd tuono
```
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`).
Follows instructions in the official [docs](https://rustup.rs/)
#### Node.js — runtime
Install `Node.js`.
You can follow the instructions from the [Node official site](https://nodejs.org/en/download/package-manager)
> 💡 This project has a `.nvmrc` file to specify the node version used in development.
>
> Consider to use [nvm](https://github.com/nvm-sh/nvm) so you can run
>
> ```sh
> nvm use
> ```
>
> to simply pick up the correct version!
#### 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`.
#### Pre-flight checks
To check that everything is working properly, run:
```sh
pnpm run check-all
cargo build
```
### Docker setup
#### Introduction
Docker takes care of configuring the development environment, so you dont 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 youll 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
1. Start tuono frontend build using
```sh
pnpm run dev
```
2. In another terminal run
```sh
cargo build
```
To automatically rebuild crates on code change, consider using `cargo-watch` crate
```sh
cargo watch -x build -w crates/
```
3. You can now use the binary inside `/target/debug/tuono` in another folder on your local machine
> Consider adding an alias to your shell setup file
>
> ```sh
> alias t="/path-to-repo/target/debug/tuono"
> ```
## Documentation development
1. Change the current working directory to the documentation folder:
```sh
cd apps/documentation
```
2. Run
```sh
tuono dev
```
3. Open the localhost URL.
> On the documentation remember that `tuono` `npm` package is installed from the registry and
> 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',
}}
/>
@@ -1,110 +0,0 @@
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, well provide feedback on any changes that need attention.
If you feel a suggested change isnt 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,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Multithreading"
canonical="https://tuono.dev/documentation/core-concepts/multithreading"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Core concepts' }]} />
# Multithreading
TODO
@@ -1,57 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - useRouter"
canonical="https://tuono.dev/documentation/hooks/use-router"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'useRouter' }]} />
# useRouter
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,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - tuono.config.ts"
canonical="https://tuono.dev/documentation/how-is-tuono-different"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'How is tuono different?' }]} />
# How is tuono different?
TODO
@@ -1,57 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Installation"
canonical="https://tuono.dev/documentation/installation"
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' }]} />
# Installation
## Requirements
`Tuono` is a development environment built in rust and typescript that outputs a website written in both the
languages. Since then, you need the following tools installed on your computer to work with it:
- `Rust` - The Rust programming language tool chain (Go [here](https://rustup.rs/) for installing both `rust` and `cargo`)
- `Cargo` - The rust package manager
- `NodeJs` - The JavaScript runtime (Go [here](https://nodejs.org/en/download/package-manager) for installing both `nodejs` and `npm`)
- `npm` or `yarn` or `pnpm` - A javaScript package manager
> Node.js is needed just for the development environment. The final output will run on a Rust server.
## Installation
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
cargo install tuono
```
To check that it is correctly installed, run:
```bash
tuono --version
```
Run `tuono -h` to see all the available commands.
```bash
The React/Rust full-stack framework
Usage: tuono <COMMAND>
Commands:
dev Start the development environment
build Build the production assets
new Scaffold a new project
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - MDX"
canonical="https://tuono.dev/documentation/integrations/mdx"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Integrations' }]} />
# MDX
TODO
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Server side rendering"
canonical="https://tuono.dev/documentation/rendering/server-side-rendering"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Rendering' }]} />
# SSR
TODO
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Static site rendering"
canonical="https://tuono.dev/documentation/rendering/static-site-rendering"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Rendering' }]} />
# Static Site Rendering
TODO
@@ -1,101 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/defining-routes"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Defining routes' }]} />
# Defining routes
Tuono has a file-system based router built on the concept of routes.
When a file is added to the `src/routes` directory, it's automatically available as a route.
In Tuono, a page is a [React Component](https://react.dev/learn/your-first-component) exported from a `.tsx` file in the `src/routes` directory.
Each page is associated with a route based on its file name.
Every page is rendered server side by default.
Example:
If you create `src/routes/about.tsx` that exports a React component like:
```
export default function About() {
return <div>About</div>
}
```
The `About` component will be rendered when loading `/about` route.
## Index routes
The router will automatically route files named `index` to the root of the directory.
- `src/routes/index.tsx` → `/`
- `src/routes/blog/index.tsx`→ `/blog`
## Nested routes
You can also create a nested folder structure, and files will still be routed in the same way.
- `src/routes/blog/first-post.tsx` → `/blog/first-post`
- `src/routes/dashboard/settings/username.tsx` → `/dashboard/settings/username`
## Pages with Dynamic Routes
Tuono supports pages with dynamic routes segments.
For example, if you create a file called `src/routes/posts/[id].tsx`,
then it will be accessible at `posts/1`, `posts/2`, etc.
## The Root route (Layout components)
Tuono allows you to have a layout component to wrap all the routes included within the same folder.
To define such component you will have to create a `__layout.tsx` file (is allowed only a single `__layout` file per folder).
This file won't generate any route.
### Layout component for all routes
A file created in this location will wrap all project route.
```tsx
// src/routes/__layout.tsx
export default function RootLayout({ children }) {
return <main>{children}</main>
}
```
### Layout component for specific routes
A file created in this location will wrap only the routes defined within the `src/routes/posts` folder.
```tsx
// src/routes/posts/__layout.tsx
export default function PostLayout({ children }) {
return <article>{children}</article>
}
```
Referring to the two examples above consider that:
- `/posts/examples-post` → will be wrapped by:
```tsx
<RootLayout>
<PostLayout>
<ExamplePost />
</PostLayout>
</RootLayout>
```
- `/about` → will be wrapped by:
```tsx
<RootLayout>
<About />
</RootLayout>
```
@@ -1,61 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/dynamic-routes"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Dynamic routes' }]} />
# Dynamic routes
> 🚧 Only available for SSR apps. This feature will be soon available also for static generated apps.
When the exact segment names are not known in advance and you need to generate routes from dynamic data,
you can use dynamic segments, which are populated at request time.
## Convention
A dynamic segment can be created by wrapping a
file or a folder name in square brackets: `[segmentName]`.
The dynamic segment can be used in folders as well as file names:
- `src/routes/blog/[slug].tsx`
- `src/routes/blog/[slug]/index.tsx`
A path can contain multiple dynamic segments, allowing for more flexible routing:
- `src/routes/blog/[post]/[comment].tsx`
### Example
Assuming you're working on a blog with multiple posts,
you can use a dynamic segment to define the post page: `src/routes/blog/[slug].tsx`.
The `[slug]` is the dynamic segment which will be used to identify individual blog posts.
```tsx
import { useRouter } from 'tuono'
export default function Page() {
const router = useRouter()
return <p>Post: {router.pathname}</p>
}
```
## Catch all segments
Dynamic Segments can be extended to catch-all subsequent segments by adding
an ellipsis inside the brackets `[...segmentName]`.
Given the following `src/routes/shop/[...slug].tsx`,
the following paths will be matched:
- `/shop/clothes`
- `/shop/clothes/tops`
- `/shop`
- `/shop/clothes/tops/t-shirts`
- and so on.
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/intro"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Routing' }]} />
# Routing index
TODO
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/layouts"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Layouts' }]} />
# Layouts
Todo
@@ -1,81 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/link-and-navigation"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Link and navigation' }]} />
# Link and navigation
The Tuono router facilitates client-side route transitions between pages, enhancing the user experience by:
- Enabling smooth navigation in single-page applications (SPAs)
- Preventing page reloads during site navigation
Tuono provides two choices for navigation between pages of your app:
- The `Link` component - used in place of an `<a>` tag
- The `useRouter` hook - used to programmatically change routes, or to access data about the current route
Use the `<a>` tag for navigating to other websites.
## The `Link` component
Tuono delivers this experience to users with the `Link` component provided from Tuono exports:
```tsx
import { Link } from 'tuono'
export default function HomePage() {
return (
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About Us</Link>
</li>
<li>
<Link href="/blog/hello-world">Blog Post</Link>
</li>
</ul>
)
}
```
The example above uses multiple links. Each one maps a path (`href`) to a known route:
- `/` → `src/routes/index.tsx`
- `/about` → `src/routes/about.tsx`
- `/blog/hello-world` → `src/routes/blog/[slug].tsx`
> For more information about this component visit the [Link component API reference page](/documentation/components/link).
## The `useRouter` hook
The Link component is not suitable for programmatic navigation.
To handle this, the library provides the `useRouter` hook.
For example, after a user submits a form and the API request succeeds, they can be redirected to another page:
```tsx
import { useRouter } from 'tuono'
export default function GoToAboutPage() {
const router = useRouter()
return (
<ProfileForm
onUpdateSuccess={() => {
router.push('/profile')
}}
/>
)
}
```
> For more information about this hook visit the [useRouter API reference page](/documentation/hooks/use-router).
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/loading-state"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Loading state' }]} />
# Loading state
Todo
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/pages"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Pages' }]} />
# Pages
Todo
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/redirecting"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Redirecting' }]} />
# Redirecting
Todo
@@ -1,14 +0,0 @@
import MetaTags from '@/components/MetaTags'
<MetaTags
title="Tuono - CSS Modules"
canonical="https://tuono.dev/documentation/styles/css-modules"
/>
import Breadcrumbs from '@/components/Breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Styles' }]} />
# CSS Modules
TODO

Some files were not shown because too many files have changed in this diff Show More