Compare commits

..

130 Commits

Author SHA1 Message Date
Jacob Marshall f219ab83f8 lint 2025-01-29 22:47:08 +00:00
Jacob Marshall 88ab0538d9 update id gen 2025-01-29 22:45:15 +00:00
Jacob Marshall 90ee21b577 remove useState, match all special chars 2025-01-29 21:59:52 +00:00
Jacob Marshall 79f4d96742 lint 2025-01-29 20:15:38 +00:00
Jacob Marshall 61707ffb96 implement suggestions 2025-01-29 20:13:57 +00:00
Jacob Marshall 38c1d68ad0 generate headings which only consist of code 2025-01-29 19:42:45 +00: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
Valerio Ageno fdc87ed8f1 chore: bump version to v0.17.6 (#453) 2025-01-28 19:08:36 +01:00
Valerio Ageno 0ab9c297a5 test: integration test to tuono_lib (#425) 2025-01-28 19:08:18 +01:00
Marco Pasqualetti 04d6fcc1e1 feat(examples/tuono-tutorial): rewrite tuono-tutorial (#449) 2025-01-28 19:07:36 +01:00
Marco Pasqualetti bf18ac0ab0 docs: add notes about pull request and validating changes (#427) 2025-01-28 18:53:31 +01:00
Marco Pasqualetti bbe9605f6f docs(configuration): add documentation for server option (#446)
Co-authored-by: Valerio Ageno <51341197+Valerioageno@users.noreply.github.com>
2025-01-28 08:53:49 +01:00
Marco Pasqualetti bd34c194cb ci: add concurrency flag for CI workflows (#448) 2025-01-28 08:30:33 +01:00
Marco Pasqualetti c0aa707315 docs(tutorial): correct api fetching example (#447) 2025-01-28 08:17:32 +01:00
Jacob Marshall b6fa448202 fix: check if project contains tuono.config.ts before running dev/build (#420) 2025-01-27 20:16:12 +01:00
Marco Pasqualetti 2e141be0fc test(packages/tuono-router): replace jsdom with happy-dom (#444) 2025-01-27 20:00:28 +01:00
Marco Pasqualetti 93b5e3e68f chore(deps): update dependency @types/react to v19.0.8 (#442) 2025-01-27 19:42:29 +01:00
spacecodeur 39dac5065f ci: add docker image test workflow (#332) 2025-01-27 19:29:08 +01:00
Marco Pasqualetti a16a313f71 ci(pr-title-checker): test type is marked as invalid (#432) 2025-01-27 01:11:11 +01:00
Jacob Marshall 82560ddc20 fix(docs): content overflow (#423) 2025-01-26 19:27:33 +01:00
Jacob Marshall 83f3cae6f0 docs: table of contents (#394)
Co-authored-by: Valerio Ageno <valerioageno@yahoo.it>
2025-01-26 16:36:18 +01:00
Marco Pasqualetti 99df4a5575 docs: update tuono to 0.17.5 (#416) 2025-01-26 15:02:49 +01:00
Valerio Ageno d33c211a01 chore: bump version to v0.17.5 (#417) 2025-01-25 21:16:36 +01:00
Marco Pasqualetti 21ee89ada4 fix(packages/tuono): build error when config contains vite plugins (#415) 2025-01-25 18:12:57 +01:00
Marco Pasqualetti dcedb1c6c0 refactor: create TuonoContext (#413) 2025-01-25 15:56:10 +01:00
Jacob Marshall d5cc65983e feat(crates/tuono): add --head flag to download most recent template (#408)
Co-authored-by: Valerio Ageno <51341197+Valerioageno@users.noreply.github.com>
2025-01-25 13:42:29 +01:00
Valerio Ageno 3cb150b090 chore: bump version 0.17.4 (#409) 2025-01-23 18:57:14 +01:00
Valerio Ageno 305ae59086 feat: pass config from server to client (#407) 2025-01-23 18:43:17 +01:00
Robert Lawson 8fe8930def feat: Improve error handling (#398) 2025-01-22 20:41:59 +01:00
Valerio Ageno 8730bdbfb9 refactor: streamline readme (#403) 2025-01-22 19:07:30 +01:00
Valerio Ageno 3ee47e9481 feat: load config in tuono CLI (#399) 2025-01-22 18:01:29 +01:00
Marco Pasqualetti 4e649acd60 ci(docs): use tuono 0.17.3 in docs workflows (#404) 2025-01-22 16:07:21 +01:00
Valerio Ageno 4768d70a8a docs: remove "Components > Head" entry from sidebar (#402)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-22 13:55:19 +01:00
Marco Pasqualetti c7bff7ed09 fix(deps): update dependency vite to v5.4.12 [security] (#401) 2025-01-22 08:32:23 +01:00
Valerio Ageno 3035d2a645 feat: create tuono_internal crate (#395) 2025-01-21 19:01:37 +01:00
renovate[bot] 0750037385 fix(deps): update mantine monorepo to v7.16.1 (#388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 09:49:18 +01:00
Marco Pasqualetti 8c4d61ee65 chore(deps): update dependency @types/react to v19.0.7 (#392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 09:09:37 +01:00
Valerio Ageno 1a3ccb112e feat: add host and port support to TuonoConfig (#366)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-20 21:00:47 +01:00
Jacob Marshall 118172d27f refactor: add TuonoEntryPoint (#374)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-20 20:17:49 +01:00
renovate[bot] 69f1d85360 chore(deps): update dependency @types/node to v22.10.7 (#375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 19:58:11 +01:00
Marco Pasqualetti f0d042c85b chore(packages/tuono): remove dynamic orphan files (#385) 2025-01-20 16:30:30 +01:00
Jacob Marshall 550830c232 chore: update documentation to tuono 0.17.x (#364) 2025-01-20 16:21:35 +01:00
Valerio Ageno 6be12b4e58 chore: bump version to v0.17.3 (#372) 2025-01-19 16:24:19 +01:00
Valerio Ageno 33aa37ed7d fix: create a component tree that matches between client and server to avoid useId output mismatch (#371)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-19 16:23:30 +01:00
Marco Pasqualetti 43910fd75d ci: add unique names on ci_ok jobs (#370) 2025-01-19 11:52:57 +01:00
LeNei 5efcc86a63 fix: propagate build error and check if js files exist (#297) 2025-01-19 09:35:11 +01:00
Valerio Ageno 17b61a5a41 chore: bump to v0.17.2 (#365) 2025-01-18 20:51:28 +01:00
Jacob Marshall 5dae5c31b7 feat: add a with-tailwind example (#359)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-18 20:44:45 +01:00
Marco Pasqualetti 96deea7f31 ci: redesign CI-related workflows to allow marking them as "Required” before merging a PR (#368) 2025-01-18 20:36:06 +01:00
Jacob Marshall 7632e6a464 fix: broken docs tables (#361) 2025-01-17 15:11:57 +01:00
Jacob Marshall 23e2a2e3ff docs(styles): add Tailwind CSS (#351)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-16 18:50:56 +01:00
Jacob Marshall b19512dffc docs: move from bullet points to tables for reference docs (#352)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-16 18:42:37 +01:00
Valerio Ageno 11c6fc820b chore: bump version to v0.17.1 (#357) 2025-01-16 12:34:59 +01:00
Valerio Ageno 1a8231851d feat: add support for client only lazy loading with dynamic (#331) 2025-01-16 12:34:06 +01:00
Valerio Ageno 1dfb83b1aa fix: revert "chore: bump version to v0.17.1" (#356) 2025-01-16 12:23:46 +01:00
Valerio Ageno 93cb1ae8ba chore: bump version to v0.17.1 (#354) 2025-01-15 19:24:34 +01:00
Jacob Marshall 631b168e05 docs: more cohesive documentation for linking and navigation, Link and useRouter (#335) 2025-01-15 19:17:35 +01:00
Marco Pasqualetti af8a43c93a docs: use PascalCase for file name (#350) 2025-01-15 16:59:36 +01:00
Valerio Ageno 875d12f514 docs(configuration): remove unclear doc (#349) 2025-01-15 13:02:48 +01:00
Jacob Marshall a9a2ef67ea fix(packages/tuono): config - correct vite.plugins type (#346) 2025-01-14 21:32:51 +01:00
Marco Pasqualetti 5caf3ed6e7 chore(renovate): fix schedule (#345) 2025-01-14 21:19:14 +01:00
Marco Pasqualetti 580ec704f2 chore: dedupe deps (#347) 2025-01-14 21:12:47 +01:00
Marco Pasqualetti a9918ae555 chore(renovate): change prConcurrentLimit to 5 (default 10) and schedule once per week (#340) 2025-01-14 20:43:25 +01:00
Marco Pasqualetti 4b3f4bfab8 docs(configuration): add types and reformat vite properties (#342) 2025-01-14 20:42:54 +01:00
Jacob Marshall fed3b1581b chore: remove RouterProvider from tuono exports (#337) 2025-01-14 16:43:16 +01:00
Jacob Marshall 9b2f0aab32 chore: refine useRouter return type and update docs (#339) 2025-01-14 16:42:27 +01:00
renovate[bot] 818a6bd2ff chore(deps): update dependency typescript to v5.7.3 (#309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 09:43:27 +01:00
renovate[bot] 94a43347a1 chore(deps): update dependency turbo to v2.3.3 (#308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 09:14:00 +01:00
renovate[bot] 16d793c588 chore(deps): update devdependencies (eslint) (#311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 08:56:10 +01:00
renovate[bot] 0322e5bd8d chore(deps): update dependency postcss to v8.5.0 (#334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 08:55:44 +01:00
renovate[bot] cfe76a9ab3 chore(deps): update dependency @types/node to v22.10.6 (#329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 08:45:25 +01:00
renovate[bot] ff6014317b chore(deps): update pnpm to v9.15.4 (#333)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 08:42:06 +01:00
PRAMOD A JINGADE 64220f2e27 docs(configuration): fill page content (#307)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-14 06:48:57 +01:00
Marco Pasqualetti e75414abfc fix(deps): revert update dependency @babel/types to v7.26.5 (#328) 2025-01-13 20:31:34 +01:00
Jacob Marshall d6fae42dc3 docs: Add useRouter Documentation (#325) 2025-01-13 18:41:31 +01:00
renovate[bot] 0db1245a6b fix(deps): update rust crate ssr_rs to 0.8.0 (#324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 18:26:50 +01:00
renovate[bot] 91c0a9ec31 fix(deps): update dependency @babel/types to v7.26.5 (#304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 15:44:21 +01:00
renovate[bot] 7670c37685 fix(deps): update dependency react-intersection-observer to v9.14.1 (#318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 15:18:24 +01:00
renovate[bot] ae41c5629c chore(deps): update rust crate serial_test to 0.10.0 (#314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 14:07:29 +01:00
renovate[bot] 3e7fa05fce chore(deps): update react monorepo (#301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 12:54:43 +01:00
renovate[bot] 6833640c47 chore(deps): update node.js to v22.13.0 (#312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 12:14:33 +01:00
renovate[bot] 18541c45de chore(deps): update pnpm to v9.15.3 (#313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 12:14:06 +01:00
Marco Pasqualetti 45bb0dd0d2 chore: add PR labeler (#310) 2025-01-13 08:58:26 +01:00
Marco Pasqualetti db0aad2c37 chore(deps): update dependency prettier to v3.4.2 (#306) 2025-01-13 05:04:50 +01:00
Valerio Ageno d55ca12125 fix: dev script (#298) 2025-01-12 18:47:21 +01:00
Valerio Ageno ad2cada94b chore: remove benches folder (#294)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-12 13:45:20 +01:00
spacecodeur f9545f513b chore: dockerization of the tuono repo! (#276)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2025-01-12 09:54:54 +01:00
Marco Pasqualetti c75d83dc7c ci: add pr title check to enforce a consistent git history (#290) 2025-01-05 10:41:23 +01:00
Valerio Ageno 91236e5533 fix: catch-all server side handler (#287) 2025-01-04 12:07:20 +01:00
Valerio Ageno 1356be248e Revert "fix: catch-all server side handler"
This reverts commit 7e8be92cd5.
2025-01-04 10:33:52 +01:00
Valerio Ageno 7e8be92cd5 fix: catch-all server side handler 2025-01-04 10:33:17 +01:00
Valerio Ageno c3fda4a3a5 doc: update tutorial seo page 2025-01-03 17:20:46 +01:00
Valerio Ageno dfc66cfd50 doc: update tutorial 2025-01-03 17:19:09 +01:00
Marco Pasqualetti 0ef1252e1f feat(packages/tuono-(router|fs-router-vite-plugin)): replace __route with __layout (#283) 2025-01-03 17:08:18 +01:00
Valerio Ageno cf56b3a53e Load template by tag (#284) 2025-01-03 17:08:10 +01:00
Marco Pasqualetti 592f402ffd feat: add support for react 19 (#264)
Co-authored-by: Valerio Ageno <valerioageno@yahoo.it>
2025-01-03 11:00:44 +01:00
renovate[bot] 1dc77fc473 fix(deps): update babel monorepo (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 13:36:04 +01:00
Marco Pasqualetti 09cd772d74 chore: refine eslint ignores and fix format script of tuono-fs-router-vite-plugin (#265) 2025-01-02 13:35:22 +01:00
Marco Pasqualetti 1c69e10e6a docs: update tuono to 0.16.9 (#280) 2025-01-02 10:57:39 +01:00
Marco Pasqualetti 1af18e4c91 chore(renovate): setup a package rule for eslint (#279) 2025-01-02 10:19:28 +01:00
renovate[bot] a3709609c5 chore(deps): update dependency @testing-library/react to v16.1.0 (#275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 10:11:38 +01:00
renovate[bot] 70a79faee0 fix(deps): update dependency @types/node to v22.10.3 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:37:33 +01:00
renovate[bot] 07cb7dae9d chore(deps): update dependency vitest to v2.1.8 (#269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:34:03 +01:00
renovate[bot] 9a1fb9c4a1 chore(deps): update dependency eslint-plugin-react to v7.37.3 (#267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:32:48 +01:00
renovate[bot] a3f7ed7f61 fix(deps): update dependency prettier to v3.4.2 (#273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:28:52 +01:00
renovate[bot] 5302fd2779 chore(deps): update react monorepo (#270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 08:57:01 +01:00
Marco Pasqualetti ccd4e02b6a docs(local-development): use pnpm instead of turbo (#277) 2025-01-02 08:43:41 +01:00
Valerio Ageno f6593097b6 feat: update version to v0.16.9 2024-12-24 16:20:55 +01:00
gravity 230cd22bff feat(crates/tuono): tuono new - download template by cli version and git tag (#261) 2024-12-24 16:03:29 +01:00
Valerio Ageno f4367f1755 feat: update version to v0.16.8 2024-12-24 11:57:21 +01:00
Marco Pasqualetti 7b7447f9a0 feat(packages/vite-plugins): use named export and consistent names across plugins (#260) 2024-12-24 11:41:58 +01:00
Valerio Ageno d12d8404ec feat(packages/tuono-router): replace zustand with React.Context (#256)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-24 11:41:45 +01:00
Valerio Ageno d1b12173d5 feat: update version to v0.16.7 2024-12-23 18:32:15 +01:00
Marco Pasqualetti f3bf13da59 refactor(packages/*): add tuono- prefix to all relevant folders (#255) 2024-12-23 18:28:06 +01:00
Marco Pasqualetti f236b75f55 refactor(packages/*): use @vitejs/plugin-react-swc and make all package dependencies explicit (#254) 2024-12-23 18:20:29 +01:00
Marco Pasqualetti 2434231f9b chore: add eslint-plugin-react (#251) 2024-12-23 11:43:04 +01:00
Valerio Ageno b8b39e69a1 Add message event polyfill (#250) 2024-12-22 21:35:08 +01:00
Jovan Milosevic f8158dfd8e chore(examples): Remove tsconfig.node.js file from examples (#245) 2024-12-22 20:12:19 +01:00
Valerio Ageno f1ab7df5ce feat: udpdate version to v0.16.6 2024-12-22 18:06:40 +01:00
Marco Pasqualetti 986de49052 refactor: add eslint-plugin-react-hooks (#248) 2024-12-22 18:03:03 +01:00
Marco Pasqualetti d09ca5548a chore: add .editorconfig (#249) 2024-12-22 17:52:03 +01:00
Marco Pasqualetti 77e22c40b6 refactor: enable typescript-eslint strict rules (#243) 2024-12-22 12:08:21 +01:00
Marco Pasqualetti de74647c43 test(packages/tuono): messageChannel - use vi.fn (#241) 2024-12-22 09:30:18 +01:00
Mustafa Zaki Assagaf 498601c21a feat: add MessageChannel polyfill to support react 19 (#238) 2024-12-21 16:55:04 +01:00
Marco Pasqualetti e99f06ddf5 chore(bug_report): make system-info fields required and add CLI command to retrieve them (#239) 2024-12-20 07:34:55 +01:00
Marco Pasqualetti f06af59168 chore: add CODEOWNERS (#237) 2024-12-19 08:22:16 +01:00
Valerio Ageno d50e3a7fc0 feat: update version to v0.16.5 2024-12-18 22:10:05 +01:00
Valerio Ageno 3e4e7ff1ab feat: update rendering from renderToString to renderToReadableStream (#233)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-18 21:58:19 +01:00
Marco Pasqualetti 21ff3c8655 docs: update tuono (#236) 2024-12-18 21:53:24 +01:00
372 changed files with 6554 additions and 9507 deletions
+7
View File
@@ -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
+14 -45
View File
@@ -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:
+36
View File
@@ -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',
]
+19 -8
View File
@@ -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
--> -->
+61
View File
@@ -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
@@ -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
+27
View File
@@ -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
+61
View File
@@ -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
+25 -7
View File
@@ -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
+4
View File
@@ -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
+2
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
22.11.0 22.13.0
+3 -1
View File
@@ -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/*
+9
View File
@@ -0,0 +1,9 @@
* @Valerioageno @marcalexiei
# Rust
/crates/ @Valerioageno
/Cargo.toml @Valerioageno
# Misc
/.github/ @marcalexiei
+1 -3
View File
@@ -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
View File
@@ -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",
] ]
+15 -32
View File
@@ -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
+17
View File
@@ -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
+1 -2
View File
@@ -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"] }
-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]
```
+11 -10
View File
@@ -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
@@ -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
@@ -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,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
@@ -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
@@ -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,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,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
@@ -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
@@ -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}
/> />
) )
})} })}
@@ -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
@@ -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',
},
], ],
}, },
] ]
@@ -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>
)
}
@@ -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,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
@@ -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
@@ -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 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 ## 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, 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,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