Compare commits

...

175 Commits

Author SHA1 Message Date
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
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
Valerio Ageno 166e61bab4 feat: update version to v0.16.4 2024-12-18 14:14:51 +01:00
Mustafa Zaki Assagaf 93cd8bd8da fix: catch-all paths replace double underscore with single underscore (#229) 2024-12-18 14:06:14 +01:00
Marco Pasqualetti f5c1641af2 chore(apps/documentation): remove tsconfig.node.json (#228) 2024-12-18 09:54:52 +01:00
Mustafa Zaki Assagaf 62ffed0624 docs: added website link to creates documentation (#230) 2024-12-18 09:13:03 +01:00
Marco Pasqualetti 8d0a4a7417 refactor(packages/lazy-fn-vite-plugin): add filter to avoid processing unrelated files (#227) 2024-12-18 09:11:30 +01:00
Mustafa Zaki Assagaf b1b274ea12 feat: update tuono macro handler naming to avoid naming collisions (#225) 2024-12-17 16:47:23 +01:00
Valerio Ageno fc38865965 fix: README.md 2024-12-16 13:57:25 +01:00
Valerio Ageno 911adaaecb feat: update version to v0.16.3 2024-12-16 08:08:27 +01:00
Marco Pasqualetti 7626ed8d1d fix(packages/lazy-fn-vite-plugin): make dynamic function detection stricter (#223) 2024-12-16 08:03:37 +01:00
Valerio Ageno 6135fd7e1b docs: add dynamic routes page (#219)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2024-12-15 13:39:06 +01:00
Valerio Ageno 14bea36356 fix: update lazyLoadComponent fn name to __tuono__internal__lazyLoadComponent (#222) 2024-12-15 09:22:19 +01:00
411 changed files with 8380 additions and 10002 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
+15 -45
View File
@@ -1,6 +1,7 @@
name: Bug report
description: File a bug report
title: '[Bug]: '
type: Bug
labels: ['bug']
body:
@@ -32,55 +33,24 @@ body:
description: If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: input
id: tuono-version
- type: textarea
id: system-info
attributes:
label: Tuono version
placeholder: '[e.g. 0.4.0]'
label: System Info
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
render: shell
placeholder: System, Binaries, Browsers
validations:
required: false
- type: input
id: os-version
required: true
- type: textarea
id: system-info-rust
attributes:
label: OS
placeholder: '[e.g. MacOS, Windows]'
label: System info (Rust)
description: Output of `rustc --version && cargo --version && tuono --version`
render: shell
placeholder: 'rustc, cargo and tuono binary version'
validations:
required: false
- type: input
id: browser-version
attributes:
label: Browser
placeholder: '[e.g. chrome, safari]'
validations:
required: false
- type: input
id: node-version
attributes:
label: Node version
placeholder: '[e.g. 20.0.0]'
validations:
required: false
- type: input
id: rust-version
attributes:
label: Rust version
placeholder: '[e.g. 1.79.0]'
validations:
required: false
- type: input
id: create-version
attributes:
label: Crate version
placeholder: '[e.g. 1.78.0]'
validations:
required: false
- type: input
id: node-package-manager-version
attributes:
label: Node Package Manger version
placeholder: '[e.g. pnpm: 9.5.0] '
validations:
required: false
required: true
- type: textarea
id: additional-context
attributes:
+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:
+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
you're trying to solve? If a new feature is being added, describe the intended
use case that feature fulfills.
Explain the context and why you're making that change.
What is the problem you're trying to solve?
If a new feature is being added,
describe the intended use case that feature fulfills.
Bug fixes and new features should include tests.
Contributors guide: https://github.com/Valerioageno/tuono/blob/main/CONTRIBUTING.md
-->
+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:
branches:
- 'main'
# match
# - all files inside `apps/documentation` and `.github`
# - all files in the root expect `Cargo.toml`
paths:
# ⬇ Keep paths in sync with `typescript.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `typescript.yml`
- 'apps/documentation/**'
pull_request:
# same as `on.push.paths`
paths:
# ⬇ Keep paths in sync with `typescript.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `typescript.yml`
- 'apps/documentation/**'
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build:
name: Check build
@@ -36,8 +40,16 @@ jobs:
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
# ⬇️ Keep in sync with `.github/workflows/deploy-documentation.yml`
- name: Read tuono version used by the documentation
id: get_tuono_version
run: |
VERSION=$(pnpm ls tuono --filter=documentation | grep "tuono " | sed -E 's/.* ([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Install tuono
run: cargo install tuono@0.15.0
run: cargo install tuono@${{ steps.get_tuono_version.outputs.version }}
# ⬆️
- name: Build project
working-directory: ./apps/documentation
@@ -1,4 +1,4 @@
name: Deploy documentation website on AWS S3
name: Documentation Website Deploy on AWS S3
on:
push:
@@ -26,8 +26,16 @@ jobs:
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
# ⬇️ Keep in sync with `.github/workflows/ci-documentation.yml`
- name: Read tuono version used by the documentation
id: get_tuono_version
run: |
VERSION=$(pnpm ls tuono --filter=documentation | grep "tuono " | sed -E 's/.* ([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Install tuono
run: cargo install tuono@0.16.0
run: cargo install tuono@${{ steps.get_tuono_version.outputs.version }}
# ⬆️
- name: Build project
working-directory: ./apps/documentation
+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:
branches:
- 'main'
paths:
- '.github/**'
- '*'
pull_request:
paths:
- '.github/**'
- '*'
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build-and-test:
format:
name: Check format
runs-on: ubuntu-latest
timeout-minutes: 15
@@ -31,3 +29,23 @@ jobs:
- name: Test project
run: pnpm repo:root:format:check
ci_ok:
name: Repo root CI OK
runs-on: ubuntu-latest
timeout-minutes: 1
if: always()
needs: [format]
env:
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
steps:
- name: Check for failure or cancelled jobs result
shell: bash
run: |
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
exit 0
else
exit 1
fi
@@ -4,15 +4,11 @@ on:
push:
branches:
- 'main'
paths:
- '.github/**'
- 'Cargo.toml'
- 'crates/**'
pull_request:
paths:
- '.github/**'
- 'Cargo.toml'
- 'crates/**'
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
@@ -40,6 +36,7 @@ jobs:
os: 'ubuntu-latest'
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- name: Checkout code
@@ -56,6 +53,7 @@ jobs:
lint_and_fmt:
name: Check format and lint rust crates
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
@@ -66,3 +64,23 @@ jobs:
- run: cargo fmt --all -- --check
- run: cargo clippy -- -D warnings
ci_ok:
name: Rust CI OK
runs-on: ubuntu-latest
timeout-minutes: 1
if: always()
needs: [build_and_test, lint_and_fmt]
env:
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
steps:
- name: Check for failure or cancelled jobs result
shell: bash
run: |
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
exit 0
else
exit 1
fi
+4
View File
@@ -7,6 +7,10 @@ on:
pull_request:
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
spell_checking:
name: LanguageTool
@@ -4,26 +4,15 @@ on:
push:
branches:
- 'main'
paths:
# ⬇ Keep paths in sync with `ci-documentation.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `ci-documentation.yml`
- 'packages/**'
pull_request:
paths:
# ⬇ Keep paths in sync with `ci-documentation.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `ci-documentation.yml`
- 'packages/**'
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build_and_test:
name: Check build and test
timeout-minutes: 15
strategy:
fail-fast: true
@@ -34,6 +23,7 @@ jobs:
- 'windows-latest'
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- name: Checkout code
@@ -68,3 +58,23 @@ jobs:
- name: Types
run: pnpm types
ci_ok:
name: Typescript CI OK
runs-on: ubuntu-latest
timeout-minutes: 1
if: always()
needs: [build_and_test, lint_and_fmt]
env:
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
steps:
- name: Check for failure or cancelled jobs result
shell: bash
run: |
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
exit 0
else
exit 1
fi
+2
View File
@@ -8,6 +8,8 @@ dist/
examples/*/pnpm-lock.yaml
vite.config.ts.timestamp*
## Rust related ignores
# Generated by Cargo
+1 -1
View File
@@ -1 +1 @@
22.11.0
22.13.1
+8 -1
View File
@@ -1 +1,8 @@
pnpm-lock.yaml
pnpm-lock.yaml
dist
.tuono
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?**
- Open a new GitHub pull request with the patch.
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number, if applicable.
- The pull requests must pass all the CI pipelines
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
+9 -8
View File
@@ -1,14 +1,15 @@
[workspace]
resolver = "2"
members = [
"crates/tuono",
"crates/tuono_lib",
"crates/tuono_lib_macros",
"crates/tuono",
"crates/tuono_internal",
"crates/tuono_lib",
"crates/tuono_lib_macros"
]
exclude = [
"apps/documentation",
"examples/with-mdx",
"examples/tuono-app",
"examples/tuono-tutorial",
"benches/tuono"
"apps/documentation",
"examples/with-mdx",
"examples/tuono-app",
"examples/tuono-tutorial",
"examples/with-tailwind",
]
+16 -33
View File
@@ -24,51 +24,34 @@ Some of its features are:
- 🧬 Server Side Rendering
- 🔥 Hot Module Reload
## 📖 Documentation
## Documentation
The [documentation](https://tuono.dev/documentation) is available on
The [documentation](https://tuono.dev/) is available on
[tuono.dev](https://tuono.dev/).
## Introduction
## Installation
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
Available on MacOS, Linux and Windows.
Tuono is a full-stack React framework with the server side written in Rust.
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
React is still React - it is just superpowered.
**Rust is a hard language, then writing server-side code is hard as well, right? (no again)**
Tuono provides a collection of utilities to handle the server side code seamlessly with the React code.
Each server-side route is managed in a separate file alongside the React route. The routing is handled
by Tuono based on the files defined within the `./src/routes` directory.
**How is Tuono different from Next.js?**
The Tuono API tries to stick as much as possible to the Next.js one (or at least takes a huge inspiration
from it). The major difference is the backend system. While Next.js relies entirely on Node/Deno/Bun,
Tuono runs the server without any intermediary runtime. This enables impressive performance improvements
(check the benchmarks [here](https://github.com/tuono-labs/tuono/tree/main/benches)).
```sh
cargo install tuono
```
## Getting started
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
To download it is required [cargo](https://doc.rust-lang.org/cargo/), which is the [rust](https://www.rust-lang.org/)
package manager.
Create a new project with:
Then run `cargo install tuono`.
```sh
tuono new [PROJECT_NAME]
```
To list all the available commands, run `tuono -h`
> You can optionally pass the --template (or -t) flag to directly start from a [template](https://github.com/tuono-labs/tuono/tree/main/examples)
To create a new project, run `tuono new [NAME]` (optionally, you can pass the `--template` (or `-t`) flag - check the
[examples](https://github.com/tuono-labs/tuono/tree/main/examples) folder).
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
Then, to run the local development environment, install the JS dependencies (`npm install`) and run inside the project folder `tuono dev`
Finally, when the project is ready to be deployed, just run `tuono build` to create the final React assets
and to set the server project in `production` mode.
Now to execute it just run `cargo run --release`.
```sh
tuono dev
```
## Contributing
+4
View File
@@ -0,0 +1,4 @@
VITE_PUBLIC_POSTHOG_KEY=phc_wqyze0qQlWutAwL5RL1Bv83D8bdySCkhcFw9MkTVuI8
VITE_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
VITE_ENV=development
VITE_ENABLE_POSTHOG=false
+4
View File
@@ -0,0 +1,4 @@
VITE_PUBLIC_POSTHOG_KEY=phc_wqyze0qQlWutAwL5RL1Bv83D8bdySCkhcFw9MkTVuI8
VITE_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
VITE_ENV=production
VITE_ENABLE_POSTHOG=true
+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"
[dependencies]
tuono_lib = "0.15.0"
tuono_lib = "0.17.6"
glob = "0.3.1"
time = { version = "0.3", features = ["macros"] }
serde = { version = "1.0.202", features = ["derive"] }
-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]
```
+13 -11
View File
@@ -6,26 +6,28 @@
"type": "module",
"scripts": {
"lint": "eslint .",
"format": "prettier -u --write --ignore-unknown .",
"format": "prettier --write --ignore-unknown .",
"format:check": "prettier --check --ignore-unknown .",
"types": "tsc --noEmit"
},
"dependencies": {
"@mantine/code-highlight": "7.14.2",
"@mantine/core": "7.14.2",
"@mantine/hooks": "7.14.2",
"@mantine/code-highlight": "7.16.1",
"@mantine/core": "7.16.1",
"@mantine/hooks": "7.16.1",
"@mdx-js/react": "3.1.0",
"@tabler/icons-react": "3.22.0",
"@tabler/icons-react": "3.28.1",
"clsx": "2.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"tuono": "npm:tuono@0.16.0"
"posthog-js": "^1.215.5",
"react": "19.0.0",
"react-dom": "19.0.0",
"remark-gfm": "4.0.0",
"tuono": "npm:tuono@0.17.6"
},
"devDependencies": {
"@mdx-js/rollup": "3.1.0",
"@types/react": "18.3.13",
"@types/react-dom": "18.3.0",
"postcss": "8.4.49",
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"postcss": "8.5.1",
"postcss-preset-mantine": "1.17.0",
"postcss-simple-vars": "7.0.1"
}
Binary file not shown.
@@ -0,0 +1,118 @@
import type { ReactNode, JSX } from 'react'
import { createTheme, MantineProvider, AppShell } from '@mantine/core'
import type { CSSVariablesResolver } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import PageWithTOC from '@/components/PageWithTOC'
import Navbar from '@/components/Navbar'
import Sidebar from '@/components/Sidebar'
import MdxProvider from '@/components/MdxProvider'
import '@mantine/core/styles.css'
import '@mantine/code-highlight/styles.css'
import Footer from '@/components/Footer'
interface RootRouteProps {
children: ReactNode
}
const theme = createTheme({
primaryColor: 'violet',
primaryShade: { light: 6, dark: 9 },
fontFamily: 'Inter',
fontFamilyMonospace: 'Menlo',
respectReducedMotion: true,
radius: {
xs: '4px',
sm: '4px',
lg: '8px',
xl: '8px',
md: '8px',
},
fontSizes: {
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
xs: '14px',
sm: '14px',
},
colors: {
dark: [
'#d5d7e0',
'#acaebf',
'#8c8fa3',
'#666980',
'#4d4f66',
'#34354a',
'#2b2c3d',
'#1d1e30',
'#0c0d21',
'#01010a',
],
},
headings: {
sizes: {
h1: {
fontSize: '48px',
},
},
},
other: {
sidebarGrayLight: '#495057',
sidebarGrayDark: '#adb5bd',
sidebarTextHoverLight: '#212529',
sidebarTextHoverDark: '#f8f9fa',
},
})
const resolver: CSSVariablesResolver = (th) => {
const {
sidebarGrayLight,
sidebarTextHoverLight,
sidebarGrayDark,
sidebarTextHoverDark,
} = th.other as Record<string, string>
return {
variables: {},
light: {
'--mantine-color-footer-bg': th.colors.gray[1],
'--mantine-color-sidebar-gray': sidebarGrayLight,
'--mantine-color-sidebar-text-hover': sidebarTextHoverLight,
'--mantine-color-quote-border': th.colors.violet[1],
},
dark: {
'--mantine-color-footer-bg': th.colors.dark[6],
'--mantine-color-sidebar-gray': sidebarGrayDark,
'--mantine-color-sidebar-text-hover': sidebarTextHoverDark,
'--mantine-color-quote-border': th.colors.violet[9],
},
}
}
export default function App({ children }: RootRouteProps): JSX.Element {
const [opened, { toggle }] = useDisclosure()
return (
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
<AppShell
layout="alt"
header={{ height: 60 }}
navbar={{
width: 300,
breakpoint: 'sm',
collapsed: { mobile: !opened },
}}
>
<Navbar toggle={toggle} />
<Sidebar close={toggle} />
<AppShell.Main pt={0} px="auto">
<MdxProvider>
<PageWithTOC>{children}</PageWithTOC>
</MdxProvider>
</AppShell.Main>
<Footer />
</AppShell>
</MantineProvider>
)
}
@@ -0,0 +1,3 @@
import App from './App'
export default App
@@ -0,0 +1,87 @@
import { useMemo, type JSX } from 'react'
import { Breadcrumbs as MantineBreadcrumbs, Button } from '@mantine/core'
import { Link } from 'tuono'
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
interface BreadcrumbData {
href?: string
label: string
}
interface BreadcrumbsProps {
breadcrumbs: Array<BreadcrumbData>
}
export default function Breadcrumbs({
breadcrumbs = [],
}: BreadcrumbsProps): JSX.Element {
const ldJson = useMemo(() => {
const _ldJson = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'Tuono - The React/Rust fullstack framework',
item: 'https://tuono.dev' as string | undefined,
},
],
}
_ldJson.itemListElement.push(
...breadcrumbs.map((br, i) => ({
'@type': 'ListItem' as const,
position: i + 3,
name: br.label,
item: br.href ? `https://tuono.dev${br.href}` : undefined,
})),
)
return _ldJson
}, [breadcrumbs])
return (
<>
<script type="application/ld+json">
{JSON.stringify(ldJson, null, 2)}
</script>
<MantineBreadcrumbs
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
mb="md"
mt="md"
>
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
<IconBolt />
</Button>
{breadcrumbs.map((br) => (
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
))}
</MantineBreadcrumbs>
</>
)
}
interface BreadcrumbElementProps {
href?: string
label: string
}
export function BreadcrumbElement({
href,
label,
}: BreadcrumbElementProps): JSX.Element {
if (href) {
return (
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
{label}
</Button>
)
}
return (
<Button component="span" variant="light" radius="xl">
{label}
</Button>
)
}
@@ -0,0 +1,3 @@
import Breadcrumbs from './Breadcrumbs'
export default Breadcrumbs
@@ -8,15 +8,15 @@ const GITHUB_URL =
export default function EditPage(): JSX.Element {
const { pathname } = useRouter()
return (
<Button
p={0}
mt={60}
component="a"
variant="transparent"
leftSection={<IconEdit />}
target="_blank"
href={GITHUB_URL.concat(pathname).concat('.mdx')}
href={`${GITHUB_URL}${pathname}.mdx`}
>
Edit page
</Button>
@@ -0,0 +1,3 @@
import EditPage from './EditPage'
export default EditPage
@@ -0,0 +1,182 @@
import type { JSX } from 'react'
import { Link } from 'tuono'
import {
Container,
Box,
AppShell,
Title,
Divider,
Flex,
Text,
Anchor,
} from '@mantine/core'
import type { MantineSize } from '@mantine/core'
interface LinkData {
title: string
url: string
}
interface FooterLinkListProps {
title: string
links: Array<LinkData>
hiddenFrom?: MantineSize
}
const aboutLinks: Array<LinkData> = [
{ title: 'Contribute', url: '/documentation/contributing' },
{
title: 'GitHub releases',
url: 'https://github.com/tuono-labs/tuono/releases',
},
]
const communityLinks: Array<LinkData> = [
{ title: 'Chat on Discord', url: 'https://discord.com/invite/khQzPa654B' },
{ title: 'Follow on X', url: 'https://twitter.com/valerioageno' },
{ title: 'Follow on GitHub', url: 'https://github.com/tuono-labs' },
]
const legalLinks: Array<LinkData> = [
{ title: 'Privacy Policy', url: '/policies/privacy' },
]
function FooterLink({ link }: { link: LinkData }): JSX.Element {
const component = link.url.startsWith('http') ? undefined : Link
const target = link.url.startsWith('http') ? '_blank' : undefined
return (
<Box>
<Anchor
component={component}
href={link.url}
key={link.url}
fz={14}
target={target}
>
{link.title}
</Anchor>
</Box>
)
}
function FooterLinkList({
title,
links,
hiddenFrom,
}: FooterLinkListProps): JSX.Element {
return (
<Box hiddenFrom={hiddenFrom}>
<Title order={5} mb={8}>
{title}
</Title>
{links.map((link) => (
<FooterLink link={link} key={link.url} />
))}
</Box>
)
}
function FooterTitle(): JSX.Element {
const size = 44
return (
<Box mb={24}>
<Flex gap={12} align="center" h={size} mb={8}>
<img src="/logo.svg" alt="Tuono" width={size} height={size} />
<Title order={2} fz={size}>
Tuono
</Title>
</Flex>
<Text fz={12}>Simply the fastest full-stack web framework </Text>
</Box>
)
}
function TocPlaceholder(): JSX.Element {
return (
<Box
w={220}
miw={220}
data-id="table-of-content-placeholder"
visibleFrom="lg"
/>
)
}
function FooterOutro(): JSX.Element {
const fontSize = 14
return (
<Container
size={1000}
w="100%"
display="flex"
style={{ gap: 12, justifyContent: 'space-between' }}
>
<Box w="100%">
<Text fz={fontSize}>
Built by{' '}
<Anchor fz={fontSize} href="https://github.com/Valerioageno">
Valerio Ageno
</Anchor>{' '}
and{' '}
<Anchor
fz={fontSize}
href="https://github.com/tuono-labs/tuono/graphs/contributors"
>
these awesome people
</Anchor>
</Text>
</Box>
<Anchor
fz={fontSize}
w={150}
component={Link}
href="/policies/privacy"
ta="right"
visibleFrom="xs"
>
Privacy Policy
</Anchor>
<TocPlaceholder />
</Container>
)
}
export default function Footer(): JSX.Element {
return (
<AppShell.Footer id="app-footer" withBorder={false} zIndex={0} p={0}>
<Box m={0} pt={52} pb={8} w="100%" bg="var(--mantine-color-footer-bg)">
<Container
size={1000}
w="100%"
display="flex"
style={{ gap: 12, justifyContent: 'space-between' }}
mb={24}
>
<Flex
justify="space-between"
w="100%"
direction={{ base: 'column', xs: 'row' }}
>
<FooterTitle />
<Flex
gap={{ base: 12, md: 32 }}
justify="space-between"
direction={{ base: 'column', xs: 'row' }}
>
<FooterLinkList title="About" links={aboutLinks} />
<FooterLinkList title="Community" links={communityLinks} />
<FooterLinkList
title="Legal"
links={legalLinks}
hiddenFrom="xs"
/>
</Flex>
</Flex>
<TocPlaceholder />
</Container>
<Divider mb={8} mt={28} />
<FooterOutro />
</Box>
</AppShell.Footer>
)
}
@@ -0,0 +1,3 @@
import Footer from './Footer'
export default Footer
@@ -36,7 +36,7 @@ export default function Hero(): JSX.Element {
size="lg"
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
color="gray"
leftSection="$ cargo install tuono"
leftSection="cargo install tuono"
rightSection={
copied ? (
<IconCheck style={{ width: rem(20) }} />
@@ -0,0 +1,3 @@
import Hero from './Hero'
export default Hero
@@ -2,5 +2,5 @@ import type { JSX } from 'react'
import { Text, type TextProps } from '@mantine/core'
export default function MdxBold(props: TextProps): JSX.Element {
return <Text fw={700} {...props} />
return <Text component="span" fw={700} {...props} />
}
@@ -0,0 +1,3 @@
import MdxBold from './MdxBold'
export default MdxBold
@@ -0,0 +1,3 @@
import MdxCode from './MdxCode'
export default MdxCode
@@ -0,0 +1,44 @@
import type { JSX, AnchorHTMLAttributes, ReactNode } from 'react'
import { Anchor } from '@mantine/core'
import { Link } from 'tuono'
import { IconExternalLink } from '@tabler/icons-react'
interface MdxLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
children: ReactNode
}
export default function MdxLink(props: MdxLinkProps): JSX.Element {
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
return (
<Anchor
{...props}
target="_blank"
variant="transparent"
display="inline"
style={{ fontWeight: 400 }}
p={0}
>
{props.children}
<IconExternalLink
size="16px"
style={{
marginLeft: '2px',
display: 'inline-block',
transform: 'translateY(3px)',
}}
/>
</Anchor>
)
}
return (
<Anchor
component={Link}
{...props}
target="_blank"
variant="transparent"
display="inline"
style={{ fontWeight: 400 }}
p={0}
/>
)
}
@@ -0,0 +1,3 @@
import MdxLink from './MdxLink'
export default MdxLink
@@ -0,0 +1,5 @@
.text {
code {
font-size: 0.9rem !important;
}
}
@@ -0,0 +1,19 @@
import type { JSX, HTMLAttributes } from 'react'
import { Text } from '@mantine/core'
import styles from './MdxParagraph.module.css'
export default function MdxParagraph(
props: HTMLAttributes<HTMLParagraphElement>,
): JSX.Element {
return (
<Text
{...props}
className={styles.text}
inherit
size="md"
my={20}
fw={300}
/>
)
}
@@ -0,0 +1,3 @@
import MdxParagraph from './MdxParagraph'
export default MdxParagraph
@@ -1,8 +1,6 @@
import type { JSX } from 'react'
import { CodeHighlight } from '@mantine/code-highlight'
import styles from './mdx-pre.module.css'
interface PreProps {
children: {
props: {
@@ -15,10 +13,10 @@ interface PreProps {
export default function MdxPre({ children }: PreProps): JSX.Element {
return (
<CodeHighlight
className={styles.pre}
code={children.props.children || ''}
style={{ borderRadius: 8, fontWeight: 100 }}
language={children.props.className?.replace('language-', '')}
my={16}
/>
)
}
@@ -0,0 +1,3 @@
import MdxPre from './MdxPre'
export default MdxPre
@@ -1,12 +1,16 @@
import type { JSX, ReactNode } from 'react'
import { MDXProvider } from '@mdx-js/react'
import { Divider } from '@mantine/core'
import MdxLink from './mdx-link'
import MdxPre from './mdx-pre'
import MdxQuote from './mdx-quote'
import MdxCode from './mdx-code'
import { h } from './mdx-title'
import MdxBold from './mdx-bold/mdx-bold'
import MdxLink from './MdxLink'
import MdxPre from './MdxPre'
import MdxQuote from './MdxQuote'
import MdxCode from './MdxCode'
import { h } from './MdxTitle'
import MdxBold from './MdxBold'
import MdxTable from './MdxTable'
import MdxUl from './MdxUl'
import MdxParagraph from './MdxParagraph'
interface MdxProviderProps {
children: ReactNode
@@ -19,7 +23,6 @@ export default function MdxProvider({
<MDXProvider
components={{
a: MdxLink,
// @ts-expect-error: useless finding the correct props types
pre: MdxPre,
blockquote: MdxQuote,
code: MdxCode,
@@ -30,6 +33,15 @@ export default function MdxProvider({
h5: h(5),
h6: h(6),
strong: MdxBold,
table: MdxTable,
thead: MdxTable.Thead,
tbody: MdxTable.Tbody,
tr: MdxTable.Tr,
th: MdxTable.Th,
td: MdxTable.Td,
p: MdxParagraph,
ul: MdxUl,
hr: Divider,
}}
>
{children}
@@ -0,0 +1,18 @@
import type { JSX, HTMLAttributes } from 'react'
import { Blockquote } from '@mantine/core'
export default function MdxQuote(
props: HTMLAttributes<HTMLQuoteElement>,
): JSX.Element {
return (
<Blockquote
py={1}
px={20}
my={16}
iconSize={28}
{...props}
bd="solid 1px var(--mantine-color-quote-border)"
style={{ borderRadius: 8 }}
/>
)
}
@@ -0,0 +1,3 @@
import MdxQuote from './MdxQuote'
export default MdxQuote
@@ -0,0 +1,27 @@
import type { DetailedHTMLProps, TableHTMLAttributes, JSX } from 'react'
import { Table, ScrollArea } from '@mantine/core'
type MdxTableProps = DetailedHTMLProps<
TableHTMLAttributes<HTMLTableElement>,
HTMLTableElement
>
function MdxTable(props: MdxTableProps): JSX.Element {
const { children, ...rest } = props
return (
<ScrollArea type="auto">
<Table highlightOnHover {...rest}>
{children}
</Table>
</ScrollArea>
)
}
MdxTable.Thead = Table.Thead
MdxTable.Tbody = Table.Tbody
MdxTable.Tr = Table.Tr
MdxTable.Th = Table.Th
MdxTable.Td = Table.Td
MdxTable.Caption = Table.Caption
export default MdxTable
@@ -0,0 +1,3 @@
import MdxTable from './MdxTable'
export default MdxTable
@@ -0,0 +1,111 @@
import type { ElementType, JSX, MouseEventHandler, ReactNode } from 'react'
import { useCallback } from 'react'
import { Title, Anchor, Box } from '@mantine/core'
import type { TitleProps } from '@mantine/core'
import { IconLink } from '@tabler/icons-react'
import { useHover } from '@mantine/hooks'
export default function MdxTitle({
children,
order,
...rest
}: TitleProps): JSX.Element {
const headingId = getIdFrom(children)
const { hovered, ref } = useHover<HTMLHeadingElement>()
const handleAnchorClick: MouseEventHandler<HTMLAnchorElement> = useCallback(
(e) => {
e.preventDefault()
if (ref.current) {
history.pushState(
null,
'',
`#${(ref.current.firstChild as HTMLHeadingElement).id} `,
)
ref.current.scrollIntoView({
behavior: 'instant',
block: 'start',
})
}
},
[ref],
)
return (
<Box
ref={ref}
data-order={order}
style={{
scrollMargin: 80,
marginTop: order === 1 ? 0 : 20,
}}
mb={24}
{...rest}
>
<Title
data-heading={headingId}
id={headingId}
data-order={order}
display="inline"
order={order}
style={{
scrollMargin: 80,
marginTop: order === 1 ? 0 : 20,
}}
mb={24}
{...rest}
>
{children}
{hovered && order !== 1 && (
<Anchor
onClick={handleAnchorClick}
display="inline-flex"
ml={8}
mb={2}
style={{ alignItems: 'center', verticalAlign: 'middle' }}
>
<IconLink width={20} height={20} />
</Anchor>
)}
</Title>
</Box>
)
}
function getIdFrom(children: ReactNode): string {
const getTextContent = (node: ReactNode): string => {
if (typeof node === 'string') return node
if (typeof node === 'object' && node !== null && 'props' in node) {
const child = node as { props?: { children?: ReactNode } }
return getTextContent(child.props?.children)
}
return ''
}
const textContent = Array.isArray(children)
? children.map(getTextContent).join('')
: getTextContent(children)
return (
textContent
// normalize cause tuono build --static to hang
// @see https://github.com/tuono-labs/tuono/issues/468
// .normalize('NFKD')// separate accented characters into their base form and diacritical marks
.replace(/[\u0300-\u036f]/g, '') // remove all the accents
.trim()
.toLowerCase()
.replace(/\./g, '-') // some titles (configuration) contain keypath, so replace dots with hyphens
.replace(/[^a-z0-9 -]/g, '') // remove non-alphanumeric characters
.replace(/\s+/g, '-') // replace spaces with hyphens
.replace(/-+/g, '-') // remove consecutive hyphens
)
}
export const h = (order: 1 | 2 | 3 | 4 | 5 | 6): ElementType<TitleProps> => {
function render(props: TitleProps): JSX.Element {
return <MdxTitle order={order} {...props} />
}
render.displayName = 'H'
return render
}
@@ -0,0 +1,5 @@
import MdxTitle, { h } from './MdxTitle'
export default MdxTitle
export { h }
@@ -0,0 +1,5 @@
.list {
code {
font-size: 0.9rem !important;
}
}
@@ -0,0 +1,20 @@
import type { HTMLAttributes, JSX } from 'react'
import { List } from '@mantine/core'
import styles from './MdxUl.module.css'
export default function MdxUl(
props: HTMLAttributes<HTMLUListElement>,
): JSX.Element {
return (
<List
{...props}
className={styles.list}
my={12}
size="md"
style={{ listStylePosition: 'outside' }}
fw={300}
withPadding
/>
)
}
@@ -0,0 +1,3 @@
import MdxUl from './MdxUl'
export default MdxUl
@@ -0,0 +1,3 @@
import MdxProvider from './MdxProvider'
export default MdxProvider
@@ -1,5 +1,4 @@
import type { ReactNode } from 'react'
import { Head } from 'tuono'
interface MetaTagsProps {
title: string
@@ -15,7 +14,7 @@ export default function MetaTags({
canonical,
}: MetaTagsProps): ReactNode {
return (
<Head>
<>
<title>{title}</title>
<link rel="canonical" href={canonical} />
<meta
@@ -41,6 +40,6 @@ export default function MetaTags({
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
</Head>
</>
)
}
@@ -0,0 +1,3 @@
import MetaTags from './MetaTags'
export default MetaTags
@@ -2,7 +2,7 @@ import type { JSX } from 'react'
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
import { Link } from 'tuono'
import Actions from './actions'
import NavbarActions from './NavbarActions'
interface NavbarProps {
toggle: () => void
@@ -24,7 +24,7 @@ export default function Navbar({ toggle }: NavbarProps): JSX.Element {
</Button>
<Box />
<Flex align="center" gap={8}>
<Actions />
<NavbarActions />
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
</Flex>
</Flex>
@@ -2,9 +2,9 @@ import type { JSX } from 'react'
import { Flex, ActionIcon, Group } from '@mantine/core'
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
import ThemeBtn from '../theme-btn'
import ThemeBtn from '../ThemeBtn'
export default function Actions(): JSX.Element {
export default function NavbarActions(): JSX.Element {
return (
<Flex gap={8}>
<Group gap={8}>
@@ -0,0 +1,3 @@
import Navbar from './Navbar'
export default Navbar
@@ -1,16 +1,16 @@
import type { JSX } from 'react'
import { Box, Button, Text, Title, Flex } from '@mantine/core'
import { Link } from 'tuono'
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
import { Link } from 'tuono'
interface NavigationButton {
interface NavigationButtonData {
href: string
title: string
}
interface NavigationButtonsProps {
prev?: NavigationButton
next?: NavigationButton
prev?: NavigationButtonData
next?: NavigationButtonData
}
export default function NavigationButtons({
@@ -25,7 +25,7 @@ export default function NavigationButtons({
)
}
interface NavigationButtonProps extends NavigationButton {
interface NavigationBtnProps extends NavigationButtonData {
type: 'next' | 'prev'
}
@@ -33,16 +33,15 @@ const NavigationBtn = ({
type,
title,
href,
}: NavigationButtonProps): JSX.Element => {
}: NavigationBtnProps): JSX.Element => {
const heading = type === 'next' ? 'Next' : 'Previous'
const textAlign = type === 'next' ? 'left' : 'right'
const variant = type === 'next' ? 'filled' : 'outline'
return (
<Button
component={Link}
fullWidth
variant={variant}
variant="outline"
href={href}
justify="space-between"
h="auto"
@@ -0,0 +1,3 @@
import NavigationButtons from './NavigationButtons'
export default NavigationButtons
@@ -0,0 +1,9 @@
.wrapper {
width: calc(100% - 220px);
word-wrap: break-word;
/* var(--mantine-breakpoint-md) = 62em */
@media (max-width: 62em) {
width: 100%;
}
}
@@ -0,0 +1,62 @@
import type { JSX, ReactNode } from 'react'
import { useEffect, useRef, useState } from 'react'
import { Box, Container } from '@mantine/core'
import { useViewportSize } from '@mantine/hooks'
import TableOfContents from '@/components/TableOfContents'
import classes from './PageWithTOC.module.css'
interface PageWithTOCProps {
children: ReactNode
}
export function PageWithTOC({ children }: PageWithTOCProps): JSX.Element {
const { width } = useViewportSize()
const [footerHeight, setFooterHeight] = useState<number>(0)
const mainRef = useRef<HTMLDivElement>(null)
const height = `calc(100% - ${footerHeight}px)`
useEffect(() => {
const footer = document.getElementById('app-footer')
if (footer) {
setFooterHeight(footer.clientHeight)
}
}, [
setFooterHeight,
// Reload on window width resize
width,
])
return (
<>
<Box
style={{ zIndex: 10, height }}
pos="relative"
ref={mainRef}
mb={footerHeight}
bg="var(--mantine-color-body)"
>
<Container
size={1000}
w="100%"
display="flex"
style={{ gap: 12, justifyContent: 'space-between' }}
>
<Box
id="mdx-root"
component="article"
mt="xl"
py={36}
className={classes.wrapper}
>
{children}
</Box>
<TableOfContents />
</Container>
</Box>
</>
)
}
@@ -0,0 +1,3 @@
import { PageWithTOC } from './PageWithTOC'
export default PageWithTOC
@@ -0,0 +1,20 @@
import { useEffect } from 'react'
import { usePostHog } from 'posthog-js/react'
import { useRouter } from 'tuono'
export default function PostHogPageView(): null {
const { pathname } = useRouter()
const posthog = usePostHog()
// Track pageviews
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (pathname && posthog) {
const url = window.origin + pathname
posthog.capture('$pageview', { $current_url: url })
}
}, [pathname, posthog])
return null
}
@@ -0,0 +1,31 @@
import type { JSX, ReactNode } from 'react'
import { useEffect } from 'react'
import { PostHogProvider as PostHogLibraryProvider } from 'posthog-js/react'
import posthogJs from 'posthog-js'
interface PostHogProviderProps {
children: ReactNode
}
export default function PostHogProvider({
children,
}: PostHogProviderProps): JSX.Element {
useEffect(() => {
if (import.meta.env.VITE_ENABLE_POSTHOG === 'true') {
posthogJs.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
persistence: 'memory', // Cookieless tracking
disable_persistence: true, // Disable persistence
loaded: (ph) => {
if (import.meta.env.VITE_ENV === 'development') ph.debug()
},
})
}
}, [])
return (
<PostHogLibraryProvider client={posthogJs}>
{children}
</PostHogLibraryProvider>
)
}
@@ -0,0 +1,4 @@
import PostHogProvider from './PostHogProvider'
import PostHogPageView from './PostHogPageView'
export { PostHogProvider, PostHogPageView }
@@ -14,8 +14,8 @@ import {
import { IconX } from '@tabler/icons-react'
import { useMediaQuery } from '@mantine/hooks'
import { sidebarElements } from './config'
import SidebarLink from './sidebar-link'
import { sidebarElements } from './sidebarElements'
import SidebarLink from './SidebarLink'
interface SidebarProps {
close: () => void
@@ -50,26 +50,30 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
<AppShell.Section component={ScrollArea}>
{sidebarElements.map((el, i) => {
if (el.type === 'divider') {
return <Divider my="md" mx={10} />
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
}
if (el.type === 'title') {
return (
<Text
key={`${el.type}-${i}`}
size="xs"
fw={700}
fz={12}
pl={12}
py={5}
children={el.label}
/>
>
{el.label}
</Text>
)
}
if (el.children?.length) {
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
key={i}
leftSection={el.leftIcon}
>
{el.children.map((child, index) => (
@@ -83,12 +87,13 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
</SidebarLink>
)
}
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
onClick={close}
key={i}
/>
)
})}
@@ -4,7 +4,7 @@ import clsx from 'clsx'
import { Link, useRouter } from 'tuono'
import { IconChevronRight } from '@tabler/icons-react'
import styles from './sidebar-link.module.css'
import styles from './SidebarLink.module.css'
interface SidebarLinkProps {
label: string
@@ -0,0 +1,3 @@
import Sidebar from './Sidebar'
export default Sidebar
@@ -33,12 +33,12 @@ export const sidebarElements: Array<SidebarElement> = [
{
type: 'element',
label: 'Installation',
href: '/documentation/installation',
href: '/documentation/getting-started/installation',
},
{
type: 'element',
label: 'How is tuono different?',
href: '/documentation/how-is-tuono-different',
label: 'Setup project',
href: '/documentation/getting-started/setup-project',
},
],
},
@@ -108,24 +108,9 @@ export const sidebarElements: Array<SidebarElement> = [
children: [
{
type: 'element',
label: 'Defining routes',
label: 'Pages and layout',
href: '/documentation/routing/defining-routes',
},
{
type: 'element',
label: 'Link and navigation',
href: '/documentation/routing/link-and-navigation',
},
{
type: 'element',
label: 'Pages',
href: '/documentation/routing/pages',
},
{
type: 'element',
label: 'Loading state',
href: '/documentation/routing/loading-state',
},
{
type: 'element',
label: 'Dynamic routes',
@@ -133,13 +118,8 @@ export const sidebarElements: Array<SidebarElement> = [
},
{
type: 'element',
label: 'Layouts',
href: '/documentation/routing/layouts',
},
{
type: 'element',
label: 'Redirecting',
href: '/documentation/routing/redirecting',
label: 'Link and navigation',
href: '/documentation/routing/link-and-navigation',
},
],
},
@@ -150,13 +130,13 @@ export const sidebarElements: Array<SidebarElement> = [
children: [
{
type: 'element',
label: 'Server side rendering',
label: 'Server side rendering (SSR)',
href: '/documentation/rendering/server-side-rendering',
},
{
type: 'element',
label: 'Static site rendering',
href: '/documentation/rendering/static-site-rendering',
label: 'Static site generation (SSG)',
href: '/documentation/rendering/static-site-generation',
},
],
},
@@ -170,6 +150,11 @@ export const sidebarElements: Array<SidebarElement> = [
label: 'CSS modules',
href: '/documentation/styles/css-modules',
},
{
type: 'element',
label: 'Tailwind CSS',
href: '/documentation/styles/tailwind-css',
},
],
},
{
@@ -184,18 +169,6 @@ export const sidebarElements: Array<SidebarElement> = [
},
],
},
{
type: 'element',
label: 'Core concepts',
href: '#focus',
children: [
{
type: 'element',
label: 'Multithreading',
href: '/documentation/core-concepts/multithreading',
},
],
},
{ type: 'divider' },
{ type: 'title', label: 'API reference' },
{
@@ -203,11 +176,6 @@ export const sidebarElements: Array<SidebarElement> = [
label: 'Components',
href: '#focus',
children: [
{
type: 'element',
label: 'Head',
href: '/documentation/components/head',
},
{
type: 'element',
label: 'Link',
@@ -217,13 +185,35 @@ export const sidebarElements: Array<SidebarElement> = [
},
{
type: 'element',
label: 'Hooks',
label: 'Functions',
href: '#focus',
children: [
{
type: 'element',
label: 'useRouter',
href: '/documentation/hooks/use-router',
href: '/documentation/functions/use-router',
},
{
type: 'element',
label: 'dynamic',
href: '/documentation/functions/dynamic',
},
],
},
{
type: 'element',
label: 'Server utilities',
href: '#focus',
children: [
{
type: 'element',
label: 'Response',
href: '/documentation/server-utilities/response',
},
{
type: 'element',
label: 'Request',
href: '/documentation/server-utilities/request',
},
],
},
@@ -239,7 +229,7 @@ export const sidebarElements: Array<SidebarElement> = [
},
{
type: 'element',
label: 'tuono.config.ts',
label: 'Configuration',
href: '/documentation/configuration',
},
@@ -260,6 +250,11 @@ export const sidebarElements: Array<SidebarElement> = [
label: 'Local development',
href: '/documentation/contributing/local-development',
},
{
type: 'element',
label: 'Pull requests',
href: '/documentation/contributing/pull-requests',
},
],
},
]
@@ -1,47 +1,26 @@
/*
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
*/
.wrapper {
padding-left: var(--mantine-spacing-md);
position: sticky;
top: var(--mantine-spacing-xl);
right: 0;
padding-top: 55px;
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
width: 200px;
flex: 0 0 calc(250px - 20px);
min-width: 220px;
max-width: 220px;
height: 100vh;
@mixin rtl {
padding-left: 0;
padding-right: var(--mantine-spacing-md);
right: auto;
left: 0;
}
&[data-with-tabs] {
padding-top: 0;
top: calc(var(--mantine-spacing-xl) + 60px);
}
}
.inner {
padding-bottom: var(--mantine-spacing-xl);
padding-left: var(--mantine-spacing-md);
display: flex;
flex-direction: column;
justify-content: space-between;
@mixin rtl {
padding-left: 0;
padding-right: var(--mantine-spacing-md);
/* var(--mantine-breakpoint-md) = 62em */
@media (max-width: 62em) {
display: none;
}
}
.link {
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
display: block;
border-left: 1px solid transparent;
padding: 8px var(--mantine-spacing-md);
margin-left: -1px;
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
border-top-right-radius: var(--mantine-radius-sm);
border-bottom-right-radius: var(--mantine-radius-sm);
@@ -58,10 +37,19 @@
border-right: 1px solid transparent;
margin-left: 0;
margin-right: -1px;
border-top-left-radius: var(--mantine-radius-sm);
border-bottom-left-radius: var(--mantine-radius-sm);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
}
&:hover {
border-color: var(--mantine-color-violet-1);
@mixin light {
color: var(--mantine-color-black);
}
@mixin dark {
color: var(--mantine-color-violet-1);
}
}
&[data-active] {
@@ -69,20 +57,18 @@
@mixin light {
color: var(--mantine-color-violet-8);
background-color: var(--mantine-color-violet-0);
}
@mixin dark {
color: var(--mantine-color-violet-1);
background-color: #534a6d;
}
}
}
.header {
.inner {
display: flex;
align-items: center;
margin-bottom: var(--mantine-spacing-md);
flex-direction: column;
justify-content: space-between;
}
.title {
@@ -0,0 +1,122 @@
import type { JSX, MouseEventHandler } from 'react'
import { useRef, useState, useEffect } from 'react'
import { useRouter } from 'tuono'
import { Box, ScrollArea, Text } from '@mantine/core'
import { getHeadings } from './getHeadings'
import type { Heading } from './getHeadings'
import classes from './TableOfContents.module.css'
export function TableOfContents(): JSX.Element | null {
const [activeHeadingIndex, setActiveHeadingIndex] = useState<number | null>(
null,
)
const [headings, setHeadings] = useState<Array<Heading>>([])
const headingsRef = useRef<Array<HTMLElement>>([])
const observerRef = useRef<IntersectionObserver | null>(null)
const router = useRouter()
useEffect(() => {
const _headings = getHeadings()
setHeadings(_headings)
headingsRef.current = _headings.map((heading) => heading.getNode())
if (observerRef.current) {
observerRef.current.disconnect()
}
const observer = new IntersectionObserver(
(entries) => {
const visibleEntries = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
if (visibleEntries.length > 0) {
setActiveHeadingIndex(
_headings.findIndex((h) => h.id === visibleEntries[0].target.id),
)
}
},
{
rootMargin: '-50px 0px -80% 0px',
threshold: [0.1, 0.5, 1.0],
},
)
headingsRef.current.forEach((node) => {
observer.observe(node)
})
observerRef.current = observer
const handleHashChange = (): void => {
setTimeout(() => {
observerRef.current?.disconnect()
observerRef.current = observer
headingsRef.current.forEach((node) => {
observer.observe(node)
})
}, 300)
}
window.addEventListener('hashchange', handleHashChange)
return (): void => {
observer.disconnect()
window.removeEventListener('hashchange', handleHashChange)
}
}, [router.pathname])
const handleHeadingClick: MouseEventHandler<HTMLAnchorElement> = (
event,
): void => {
event.preventDefault()
const target = event.target as HTMLAnchorElement
const href = target.getAttribute('href') as string
const targetHeading = document.querySelector(href)
if (!targetHeading) return
history.pushState(null, '', href)
targetHeading.scrollIntoView({ behavior: 'instant', block: 'start' })
}
// Table of contents will not be displayed if the page has one or fewer headings.
if (headings.length <= 1) {
return null
}
return (
<Box component="nav" className={classes.wrapper}>
<ScrollArea style={{ height: 'calc(100% - 60px)' }}>
<div className={classes.inner}>
<div>
<Text className={classes.title} mb={8}>
On this page
</Text>
<div className={classes.items}>
{headings.slice(1).map((heading, index) => (
<Text
key={heading.id}
component="a"
fz="sm"
w="fit-content"
py={4}
className={classes.link}
mod={{ active: activeHeadingIndex === index + 1 }}
href={`#${heading.id}`}
onClick={handleHeadingClick}
style={{
paddingLeft: `calc(${heading.order - 1} * var(--mantine-spacing-md))`,
}}
>
{heading.content}
</Text>
))}
</div>
</div>
</div>
</ScrollArea>
</Box>
)
}
@@ -2,20 +2,33 @@
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
*/
export interface Heading {
depth: number
order: number
content: string
id: string
getNode: () => HTMLHeadingElement
}
function getCleanedText(element: HTMLElement): string {
const clone = element.cloneNode(true) as HTMLElement
clone.querySelectorAll('code, pre').forEach((codeBlock) => {
const textNode = document.createTextNode(codeBlock.textContent || '')
codeBlock.replaceWith(textNode)
})
return clone.textContent?.trim() || ''
}
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
const result: Array<Heading> = []
for (const heading of headings) {
if (heading.id) {
const order = parseInt(heading.getAttribute('data-order') || '1', 10)
if (order <= 3 && heading.id) {
result.push({
depth: parseInt(heading.getAttribute('data-order') || '1', 10),
content: heading.getAttribute('data-heading') || '',
order,
content: getCleanedText(heading),
id: heading.id,
getNode: () =>
document.getElementById(heading.id) as HTMLHeadingElement,
@@ -28,7 +41,6 @@ function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
export function getHeadings(): Array<Heading> {
const root = document.getElementById('mdx-root')
console.log(root)
if (!root) {
return []
@@ -1,6 +1,6 @@
/*
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
*/
import { TableOfContents } from './table-of-content'
import { TableOfContents } from './TableOfContents'
export default TableOfContents
@@ -7,7 +7,7 @@ import {
import { IconSun, IconMoon } from '@tabler/icons-react'
import cx from 'clsx'
import classes from './theme-btn.module.css'
import classes from './ThemeBtn.module.css'
export default function ThemeBtn(): JSX.Element {
const { setColorScheme } = useMantineColorScheme()
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
return (
<ActionIcon
onClick={() =>
onClick={() => {
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
}
}}
variant="default"
size="lg"
aria-label="Toggle color scheme"
@@ -0,0 +1,3 @@
import ThemeBtn from './ThemeBtn'
export default ThemeBtn
@@ -1,84 +0,0 @@
import type { JSX } from 'react'
import { Breadcrumbs, Button } from '@mantine/core'
import { Link, Head } from 'tuono'
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
interface Breadcrumb {
href?: string
label: string
}
interface BreadcrumbsProps {
breadcrumbs: Array<Breadcrumb>
}
export default function TuonoBreadcrumbs({
breadcrumbs = [],
}: BreadcrumbsProps): JSX.Element {
return (
<>
<Head>
<script type="application/ld+json">
{`{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Tuono - The React/Rust fullstack framework",
"item": "https://tuono.dev"
}${breadcrumbs.length > 0 ? ',' : ''}
${breadcrumbs
.map((br, i) =>
JSON.stringify({
'@type': 'ListItem',
position: i + 3,
name: br.label,
item: br.href ? `https://tuono.dev${br.href}` : undefined,
}),
)
.join(',')}]
}
`}
</script>
</Head>
<Breadcrumbs
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
mb="md"
mt="md"
>
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
<IconBolt />
</Button>
{breadcrumbs.map((br) => (
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
))}
</Breadcrumbs>
</>
)
}
interface BreadcrumbElementProps {
href?: string
label: string
}
export function BreadcrumbElement({
href,
label,
}: BreadcrumbElementProps): JSX.Element {
if (href) {
return (
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
{label}
</Button>
)
}
return (
<Button component="span" variant="light" radius="xl">
{label}
</Button>
)
}
@@ -1,5 +0,0 @@
import Breadcrumbs, { BreadcrumbElement } from './breadcrumbs'
export default Breadcrumbs
export { BreadcrumbElement as Element }
@@ -1,3 +0,0 @@
import EditPage from './edit-page'
export default EditPage
@@ -1,3 +0,0 @@
import Hero from './hero'
export default Hero
@@ -1,3 +0,0 @@
import MdxProvider from './mdx-provider'
export default MdxProvider
@@ -1,3 +0,0 @@
import MdxCode from './mdx-code'
export default MdxCode
@@ -1,3 +0,0 @@
import MdxLink from './mdx-link'
export default MdxLink
@@ -1,36 +0,0 @@
import type { JSX, AnchorHTMLAttributes } from 'react'
import { Button } from '@mantine/core'
import { Link } from 'tuono'
import { IconExternalLink } from '@tabler/icons-react'
export default function MdxLink(
props: AnchorHTMLAttributes<HTMLAnchorElement>,
): JSX.Element {
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
return (
<Button
component="a"
{...props}
target="_blank"
rightSection={
<IconExternalLink size="16px" style={{ marginLeft: -5 }} />
}
variant="transparent"
style={{ height: '20px' }}
mt={-2}
p={0}
/>
)
}
return (
<Button
component={Link}
{...props}
target="_blank"
variant="transparent"
style={{ height: '20px' }}
mt={-2}
p={0}
/>
)
}
@@ -1,3 +0,0 @@
import MdxPre from './mdx-pre'
export default MdxPre
@@ -1,5 +0,0 @@
.pre {
code {
font-size: 14px;
}
}
@@ -1,3 +0,0 @@
import MdxQuote from './mdx-quote'
export default MdxQuote
@@ -1,21 +0,0 @@
import type { JSX, HTMLAttributes } from 'react'
import { Blockquote, Space } from '@mantine/core'
export default function MdxQuote(
props: HTMLAttributes<HTMLQuoteElement>,
): JSX.Element {
return (
<>
<Blockquote
color="violet"
py={1}
px={20}
mt={30}
iconSize={30}
{...props}
style={{ borderRadius: 8 }}
/>
<Space h="md" />
</>
)
}
@@ -1,5 +0,0 @@
import MdxTitle, { h } from './mdx-title'
export default MdxTitle
export { h }
@@ -1,21 +0,0 @@
import type { JSX } from 'react'
import { Title, type TitleProps } from '@mantine/core'
export default function MdxTitle(props: TitleProps): JSX.Element {
return (
<Title
data-heading={props.children}
data-order={props.order}
mt={20}
{...props}
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id={String(props.children ?? '')
.toLowerCase()
.replaceAll(' ', '-')}
/>
)
}
export const h =
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />

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