Compare commits
469 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fdc87ed8f1 | |||
| 0ab9c297a5 | |||
| 04d6fcc1e1 | |||
| bf18ac0ab0 | |||
| bbe9605f6f | |||
| bd34c194cb | |||
| c0aa707315 | |||
| b6fa448202 | |||
| 2e141be0fc | |||
| 93b5e3e68f | |||
| 39dac5065f | |||
| a16a313f71 | |||
| 82560ddc20 | |||
| 83f3cae6f0 | |||
| 99df4a5575 | |||
| d33c211a01 | |||
| 21ee89ada4 | |||
| dcedb1c6c0 | |||
| d5cc65983e | |||
| 3cb150b090 | |||
| 305ae59086 | |||
| 8fe8930def | |||
| 8730bdbfb9 | |||
| 3ee47e9481 | |||
| 4e649acd60 | |||
| 4768d70a8a | |||
| c7bff7ed09 | |||
| 3035d2a645 | |||
| 0750037385 | |||
| 8c4d61ee65 | |||
| 1a3ccb112e | |||
| 118172d27f | |||
| 69f1d85360 | |||
| f0d042c85b | |||
| 550830c232 | |||
| 6be12b4e58 | |||
| 33aa37ed7d | |||
| 43910fd75d | |||
| 5efcc86a63 | |||
| 17b61a5a41 | |||
| 5dae5c31b7 | |||
| 96deea7f31 | |||
| 7632e6a464 | |||
| 23e2a2e3ff | |||
| b19512dffc | |||
| 11c6fc820b | |||
| 1a8231851d | |||
| 1dfb83b1aa | |||
| 93cb1ae8ba | |||
| 631b168e05 | |||
| af8a43c93a | |||
| 875d12f514 | |||
| a9a2ef67ea | |||
| 5caf3ed6e7 | |||
| 580ec704f2 | |||
| a9918ae555 | |||
| 4b3f4bfab8 | |||
| fed3b1581b | |||
| 9b2f0aab32 | |||
| 818a6bd2ff | |||
| 94a43347a1 | |||
| 16d793c588 | |||
| 0322e5bd8d | |||
| cfe76a9ab3 | |||
| ff6014317b | |||
| 64220f2e27 | |||
| e75414abfc | |||
| d6fae42dc3 | |||
| 0db1245a6b | |||
| 91c0a9ec31 | |||
| 7670c37685 | |||
| ae41c5629c | |||
| 3e7fa05fce | |||
| 6833640c47 | |||
| 18541c45de | |||
| 45bb0dd0d2 | |||
| db0aad2c37 | |||
| d55ca12125 | |||
| ad2cada94b | |||
| f9545f513b | |||
| c75d83dc7c | |||
| 91236e5533 | |||
| 1356be248e | |||
| 7e8be92cd5 | |||
| c3fda4a3a5 | |||
| dfc66cfd50 | |||
| 0ef1252e1f | |||
| cf56b3a53e | |||
| 592f402ffd | |||
| 1dc77fc473 | |||
| 09cd772d74 | |||
| 1c69e10e6a | |||
| 1af18e4c91 | |||
| a3709609c5 | |||
| 70a79faee0 | |||
| 07cb7dae9d | |||
| 9a1fb9c4a1 | |||
| a3f7ed7f61 | |||
| 5302fd2779 | |||
| ccd4e02b6a | |||
| f6593097b6 | |||
| 230cd22bff | |||
| f4367f1755 | |||
| 7b7447f9a0 | |||
| d12d8404ec | |||
| d1b12173d5 | |||
| f3bf13da59 | |||
| f236b75f55 | |||
| 2434231f9b | |||
| b8b39e69a1 | |||
| f8158dfd8e | |||
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 | |||
| 4abc163a59 | |||
| 5bb9cf1e7c | |||
| a0cdf408a1 | |||
| 8467506915 | |||
| 704255774d | |||
| 10bb7b1cca | |||
| 7f82af3d3b | |||
| d7c729c035 | |||
| 51fa904f9b | |||
| 6191e55745 | |||
| c6e93c266a | |||
| bcf7e5b5ab | |||
| 466f122cc4 | |||
| 539484ea25 | |||
| db1cee25bf | |||
| 96502b18d0 | |||
| c491ece8c5 | |||
| c76464bf8a | |||
| 08b75b1a79 | |||
| 90da869277 | |||
| 80c4affaa8 | |||
| aa087193fe | |||
| af7e51f69b | |||
| 1b62d8ddc2 | |||
| 0839579733 | |||
| 7f9bf08f76 | |||
| 79ec1c6922 | |||
| c68246df83 | |||
| abc0ad7120 | |||
| 4356ca1cff | |||
| d6215d009c | |||
| 0b501e95a5 | |||
| f157ff37e0 | |||
| 11d425faa9 | |||
| 5fc68a1926 | |||
| ec4577c187 | |||
| 8052de8ad5 | |||
| 2fda959cd6 | |||
| 05fa8a5ae2 | |||
| a341b41e9a | |||
| a2e0a4d78a | |||
| 4fb1d6077c | |||
| 53ab927544 | |||
| 897cc2daf2 | |||
| 73ff31daf5 | |||
| c387a9e17e | |||
| 1f92931076 | |||
| d3105a1532 | |||
| 7f50f31837 | |||
| 4c3e90ea4b | |||
| 8d80fc4aa9 | |||
| 78a04ffcf1 | |||
| bcb2c91823 | |||
| 9058f209dd | |||
| 1f172d583e | |||
| 439b4757df | |||
| 9f8809bd51 | |||
| 4e326650cc | |||
| e27be27692 | |||
| 88eb2a1897 | |||
| 8c84ef73c8 | |||
| 859e49f16c | |||
| 14ba8b0a23 | |||
| d7c70f75f3 | |||
| 2760e3ea08 | |||
| f2f041fe55 | |||
| 9ec9700ed4 | |||
| c6b047fc02 | |||
| 6727264e39 | |||
| d1a5fe234d | |||
| e84c7cd276 | |||
| 087813fab9 | |||
| 3a4e7398c6 | |||
| 1630fd7e71 | |||
| 4aa99beaff | |||
| eaa0ba28d8 | |||
| 3f3e1120b2 | |||
| 89d7a2c740 | |||
| 56c31f0204 | |||
| 338cfb5be2 | |||
| 43f6fe664b | |||
| 209c9fb7f7 | |||
| a562f67e8d | |||
| ef0fe5a425 | |||
| b50902d23e | |||
| 55490579ec | |||
| 957d08701f | |||
| 61e1aed35d | |||
| 2034f0ef5f | |||
| fcb92c7f0b | |||
| 28ea1ca1df | |||
| ebc1a458a9 | |||
| df92ef5296 | |||
| 253e0a8378 | |||
| a9fbe4eddd | |||
| cfccc638c6 | |||
| 9f1460cc26 | |||
| f0a4541420 | |||
| d0ce482680 | |||
| 40e2734a4e | |||
| 8552796343 | |||
| 6d72ffb876 | |||
| 65faaf0183 | |||
| 7f7c2e8d6f | |||
| 3be491baa7 | |||
| 48b974ae58 | |||
| c394898fcd | |||
| f22249f3e9 | |||
| 120cec52e9 | |||
| 535cc6b3d9 | |||
| 18a060e2bf | |||
| 7b8165cee6 | |||
| 6331eb64e6 | |||
| 3c38b551f0 | |||
| 8e761347f2 | |||
| e2d44e0e6a | |||
| 607da92c71 | |||
| ae0427604e | |||
| 2aa981c581 | |||
| 5d8ec82502 | |||
| fc9bb52c41 | |||
| 3224e4138f | |||
| 7ea28c4fd3 | |||
| acb5567f6a | |||
| cc877c7c11 | |||
| 87bdddf7cf | |||
| d939b64d64 | |||
| 3f64f71947 | |||
| aaae7db24d | |||
| 48c3934b6e | |||
| 258b4af0b1 | |||
| edfbd03f13 | |||
| 9cb0f62da7 | |||
| 9591960e31 | |||
| 132d8ebe8f | |||
| 4020073aca | |||
| 5025692220 | |||
| d04a4c4103 | |||
| 7e6dea68f4 | |||
| a046833414 | |||
| cfa59ca8bf | |||
| d470bd58f2 | |||
| ea82d2b1df | |||
| 93b44088a9 | |||
| 625c091fdc | |||
| e0c0447fb6 | |||
| b67a8e8537 | |||
| c9f1b02954 | |||
| 6b3bfca1f6 | |||
| 15e568594d | |||
| 089f41ee89 | |||
| 636c68e532 | |||
| 6d8a04dfb2 | |||
| 01d5aad8aa | |||
| 002733cd99 | |||
| d7a086dfb1 | |||
| d60c7dcd9b | |||
| 58baf8aa82 | |||
| 0a8504d9f2 | |||
| 934a83c0e2 | |||
| ea65ad042f | |||
| 3544cd2943 | |||
| e593707ce7 | |||
| 6dd0fc7410 | |||
| faf7bf29e2 | |||
| c5b5f6aefe | |||
| 8e48eec01d | |||
| e3c5cc882f | |||
| dfb1bebc6b | |||
| a40367f813 | |||
| 1a1b62698c | |||
| b36dc0b897 | |||
| a9eacf21f4 | |||
| 23d84b25be | |||
| 6c7cc62371 | |||
| 588326cc57 | |||
| ce654bf7a2 | |||
| a3bcb76105 | |||
| 4045ebc24e | |||
| 2be3710462 | |||
| fe3185c6dc | |||
| f10b813e1e | |||
| c67d4199a0 | |||
| d0a6c59c62 | |||
| 60f622542f | |||
| e7fcd64b3a | |||
| 3772e7307e | |||
| e853db164d | |||
| 509283adab | |||
| db56b7f2dc | |||
| acd663d79f | |||
| 3636268a71 | |||
| 6402f42d01 | |||
| 71ac19cbb9 | |||
| d99389a398 | |||
| 024c386af4 | |||
| d982c9c24b | |||
| 480973318d | |||
| a497937106 | |||
| 0d33150f30 | |||
| 94667b1454 | |||
| 2cbcd8ad70 | |||
| 9f40a55907 | |||
| 51e01ec142 | |||
| 63fc2b9855 | |||
| edd8a02423 | |||
| a0a81b514c | |||
| 92820804f8 | |||
| e7ea7ed1c4 | |||
| 7471c90f49 | |||
| f545a9c469 | |||
| 30073c226d | |||
| 1b1e91b6c0 | |||
| 71d7ccf27d | |||
| 3ca24ec87f | |||
| b2940d9fa8 | |||
| a7b7f0eac2 | |||
| 4859916edd | |||
| ad3e19e965 | |||
| 03dead1083 | |||
| 341fba6ca9 | |||
| 58c4890122 | |||
| af42e5ef21 | |||
| 5abff0384c | |||
| cc915560b8 | |||
| 9835a4c0d1 | |||
| 90782ae710 | |||
| 75c790dd6e | |||
| 8604858a61 | |||
| d1b6162d3a | |||
| a445644b47 | |||
| ebb4434506 | |||
| 36a53662a0 | |||
| 569d95dc9c | |||
| bc4c4e03f0 | |||
| 170c5ea098 | |||
| 3fd5f166e2 | |||
| a2285a78a1 | |||
| 1ae7f72de3 | |||
| 19782a2dbe | |||
| b755e658f5 | |||
| cc0279b964 | |||
| 099da549b5 | |||
| 92a76032eb | |||
| cd17eebf96 | |||
| 1726312d34 | |||
| 444d1479b0 | |||
| 2949ea4dfa | |||
| 8ab292b2f9 | |||
| 9d8bfd9826 | |||
| 8a6685a6d0 | |||
| 78990d3c08 | |||
| 227662489b | |||
| 5ce410f233 | |||
| 67d4e1bcac | |||
| 3e6aa540fa | |||
| b412371287 | |||
| f62fc0b2b9 | |||
| 35d8154649 | |||
| 6e794e9500 | |||
| e4d4a75b88 | |||
| bc7ffc2246 | |||
| 74e2f4e873 | |||
| d9d62a91f7 | |||
| 77b34fbb7b | |||
| d3040aa63f | |||
| 672e4b69d7 | |||
| 9cd90ba62a | |||
| b6d0bb13ef | |||
| 7401a59346 | |||
| 622ae93d68 | |||
| b4207feb7e | |||
| 332fdb70ca | |||
| b9abfb64c0 | |||
| 3ca8861d85 | |||
| 0c93aab13f | |||
| 208b49cfc8 | |||
| db0a2d9d1d | |||
| c123e93433 | |||
| 35557d296d | |||
| d0d6550d7e | |||
| 38d9be2bb0 | |||
| fcb33d208c | |||
| f831ad6c7a | |||
| 3bfed5fabc | |||
| e1ce3ba2fd | |||
| a31f048ffd | |||
| 9c707466db | |||
| 4ff899729b | |||
| a8345d3509 | |||
| 1c3d6e2d78 | |||
| dde5ae7827 | |||
| aeef19cda3 | |||
| adec05c6f8 | |||
| 2c1e8f7db8 | |||
| 2c1e8f0cc2 | |||
| 5d434aeb68 | |||
| 54e7d3a397 | |||
| 7937291281 | |||
| 777f932266 | |||
| f37cb701b3 | |||
| 6498905377 | |||
| ed6c75fe37 | |||
| f759960641 | |||
| 75e8e51464 | |||
| 75ee140525 | |||
| 8d09637a7a | |||
| 62bfd35fbd | |||
| 091c94d609 | |||
| a72656cb4b | |||
| b3ef9f0ee6 | |||
| 0e4debdb8a | |||
| 476fae0e70 | |||
| 3de9d1be66 | |||
| 00916f188d | |||
| fff5f92893 | |||
| 8b796fc1f4 | |||
| 5ac679785b | |||
| 38b6cc8c65 | |||
| abb2d658c6 | |||
| 3587c085b9 | |||
| 581a7d79b3 | |||
| e21b80a5d8 | |||
| e0fc19ecaf | |||
| 0be0ce4c15 | |||
| 792358ea9c | |||
| 26c0c2488d | |||
| 70b4b9c28f | |||
| 8b4db423b4 | |||
| 7d65ede581 | |||
| 5b11f68fbb | |||
| 0d33e6887b | |||
| 5bb620a590 | |||
| 469386388b | |||
| 8728499f90 | |||
| 2e82dde2c3 |
@@ -0,0 +1,7 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
@@ -1,103 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"reportUnusedDisableDirectives": true,
|
||||
"ignorePatterns": ["**/build", "**/dist"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "import"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/stylistic",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"prettier",
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true,
|
||||
},
|
||||
"parserOptions": {
|
||||
"tsconfigRootDir": ".",
|
||||
"project": true,
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2020,
|
||||
},
|
||||
"settings": {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts", ".tsx"],
|
||||
},
|
||||
"import/resolver": {
|
||||
"typescript": true,
|
||||
},
|
||||
"react": {
|
||||
"version": "detect",
|
||||
},
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/ban-types": "error",
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{ "prefer": "type-imports" },
|
||||
],
|
||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "typeParameter",
|
||||
"format": ["PascalCase"],
|
||||
"leadingUnderscore": "forbid",
|
||||
"trailingUnderscore": "forbid",
|
||||
"custom": {
|
||||
"regex": "^(T|T[A-Z][A-Za-z]+)$",
|
||||
"match": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-empty-function": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/no-inferrable-types": [
|
||||
"error",
|
||||
{ "ignoreParameters": true },
|
||||
],
|
||||
"import/default": "error",
|
||||
"import/export": "error",
|
||||
"import/namespace": "error",
|
||||
"import/newline-after-import": "error",
|
||||
"import/no-cycle": "error",
|
||||
"import/no-duplicates": "off",
|
||||
"import/no-named-as-default-member": "error",
|
||||
"import/no-unused-modules": "error",
|
||||
"import/order": [
|
||||
"off",
|
||||
{
|
||||
"groups": [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index",
|
||||
"object",
|
||||
"type",
|
||||
],
|
||||
},
|
||||
],
|
||||
"no-case-declarations": "error",
|
||||
"no-empty": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-shadow": "error",
|
||||
"no-undef": "off",
|
||||
"sort-imports": "off",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
examples/** linguist-documentation
|
||||
*.css linguist-detectable=false
|
||||
*.scss linguist-detectable=false
|
||||
*.js linguist-detectable=false
|
||||
*.jsx linguist-detectable=true
|
||||
*.tsx linguist-detectable=true
|
||||
*.ts linguist-detectable=true
|
||||
*.rs linguist-detectable=true
|
||||
*.html linguist-detectable=false
|
||||
*.sh linguist-detectable=false
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Bug report
|
||||
description: File a bug report
|
||||
title: '[Bug]: '
|
||||
labels: ['bug']
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behaviour
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: Steps to reproduce the behavior (code, example repository)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||
render: shell
|
||||
placeholder: System, Binaries, Browsers
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-info-rust
|
||||
attributes:
|
||||
label: System info (Rust)
|
||||
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||
render: shell
|
||||
placeholder: 'rustc, cargo and tuono binary version'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the bug here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: '[Feature request]: '
|
||||
labels: ['enhancement']
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is.
|
||||
placeholder: I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Install NodeJS Dependencies
|
||||
description: This is a composite GitHub Action that sets up pnpm, node and installs the project's dependencies.
|
||||
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'Explicit node version. Otherwise fallback reading `.nvmrc`. Use in conjunction with matrix'
|
||||
required: false
|
||||
registry-url:
|
||||
description: 'https://github.com/actions/setup-node?tab=readme-ov-file#usage'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js (via input)
|
||||
if: ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
registry-url: ${{ inputs.registry-url }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Setup Node.js (via .nvmrc)
|
||||
if: ${{ !inputs.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: ${{ inputs.registry-url }}
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -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',
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
<!--
|
||||
👋 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
|
||||
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
@@ -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
|
||||
@@ -0,0 +1,69 @@
|
||||
name: Documentation Website CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
# match
|
||||
# - all files inside `apps/documentation` and `.github`
|
||||
# - all files in the root expect `Cargo.toml`
|
||||
paths:
|
||||
- '.github/**'
|
||||
- '*'
|
||||
- '!Cargo.toml'
|
||||
- 'apps/documentation/**'
|
||||
pull_request:
|
||||
# same as `on.push.paths`
|
||||
paths:
|
||||
- '.github/**'
|
||||
- '*'
|
||||
- '!Cargo.toml'
|
||||
- 'apps/documentation/**'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Check build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.17.5
|
||||
|
||||
- name: Build project
|
||||
working-directory: ./apps/documentation
|
||||
run: tuono build --static
|
||||
|
||||
code_check:
|
||||
name: Check format, lint and types
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm docs:format:check
|
||||
|
||||
- name: Lint
|
||||
run: pnpm docs:lint
|
||||
|
||||
- name: Types
|
||||
run: pnpm docs:types
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Deploy documentation website on AWS S3
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths:
|
||||
- '.github/**'
|
||||
- 'apps/documentation/**'
|
||||
|
||||
jobs:
|
||||
deploy-documentation:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.17.5
|
||||
|
||||
- name: Build project
|
||||
working-directory: ./apps/documentation
|
||||
run: tuono build --static
|
||||
|
||||
- name: Deploy
|
||||
uses: reggionick/s3-deploy@v4
|
||||
with:
|
||||
folder: apps/documentation/out/static
|
||||
bucket: tuono-documentation
|
||||
bucket-region: eu-west-3
|
||||
dist-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
|
||||
invalidation: /
|
||||
delete-removed: true
|
||||
no-cache: true
|
||||
private: true
|
||||
files-to-include: '{.*/**,**}'
|
||||
@@ -0,0 +1,27 @@
|
||||
name: 'PR Labeler'
|
||||
|
||||
# @see https://github.com/actions/labeler
|
||||
# @see .github/labeler.yml
|
||||
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: 'Manage labels'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
# Add checkout step to load labeler.yml locally,
|
||||
# rather than from an API request
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
@@ -0,0 +1,61 @@
|
||||
name: 'PR Title Checker'
|
||||
|
||||
# This workflow ensures that PR titles adhere to a pattern
|
||||
# similar to conventional commits standard
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
check_pr_title:
|
||||
name: 'PR Title Checker'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check type
|
||||
id: type
|
||||
# @warning Keep in sync with apps/documentation/src/routes/documentation/contributing/pull-requests.mdx
|
||||
run: |
|
||||
VALID_COMMIT_TYPES="chore|ci|docs|feat|fix|refactor|test"
|
||||
REGEX="^(${VALID_COMMIT_TYPES})(\(.*\))?!?: .*"
|
||||
|
||||
if ! [[ $PR_TITLE =~ $REGEX ]]; then
|
||||
echo "::error title=Type::The title has an incorrect type. Valid types are ${VALID_COMMIT_TYPES}"
|
||||
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::notice title=Type::The title is using a valid type"
|
||||
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check length
|
||||
id: length
|
||||
run: |
|
||||
MAX_LENGTH=100
|
||||
LENGTH=${#PR_TITLE}
|
||||
|
||||
if [ $LENGTH -gt $MAX_LENGTH ]; then
|
||||
echo "::error title=Length::The title is longer than $MAX_LENGTH characters"
|
||||
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::notice title=Length::The title length is within the maximum value of $MAX_LENGTH characters"
|
||||
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check result
|
||||
env:
|
||||
FAILURE: ${{ contains(join(steps.*.outputs.result, ','), 'fail') }}
|
||||
run: |
|
||||
echo "Failure: $FAILURE"
|
||||
if [ "$FAILURE" = "false" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,103 +1,92 @@
|
||||
name: Publish on npm and crates
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish-on-npm-dry-run:
|
||||
publish_npm_dry_run:
|
||||
name: NPM publish (dry run)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Install turbo
|
||||
run: pnpm install turbo --global
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Publish
|
||||
run: pnpm publish --no-git-checks --dry-run --filter ./packages/tuono
|
||||
run: pnpm publish -r --no-git-checks --dry-run
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish-on-crates-dry-run:
|
||||
publish_crates_dry_run:
|
||||
name: Crates publish (dry run)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: katyo/publish-crates@v2
|
||||
with:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable'
|
||||
|
||||
- uses: katyo/publish-crates@v2
|
||||
with:
|
||||
path: './crates/tuono'
|
||||
dry-run: true
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
check-repo: true
|
||||
|
||||
publish-on-npm:
|
||||
needs: [publish-on-crates-dry-run, publish-on-npm-dry-run]
|
||||
publish_npm:
|
||||
name: NPM publish
|
||||
needs: [publish_npm_dry_run, publish_crates_dry_run]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Clone root README.md on tuono package
|
||||
run: cp README.md packages/tuono/README.md
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Install turbo
|
||||
run: pnpm install turbo --global
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Publish
|
||||
run: pnpm publish --no-git-checks --filter ./packages/tuono
|
||||
run: pnpm publish -r --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish-on-crates:
|
||||
needs: [publish-on-crates-dry-run, publish-on-npm-dry-run]
|
||||
publish_crates:
|
||||
name: Crates publish
|
||||
needs: [publish_npm_dry_run, publish_crates_dry_run]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: katyo/publish-crates@v2
|
||||
with:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable'
|
||||
|
||||
- uses: katyo/publish-crates@v2
|
||||
with:
|
||||
path: './crates/tuono'
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
check-repo: true
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Repo root CI
|
||||
|
||||
# All workflows are triggered based on specific paths,
|
||||
# the root repository is something that is not applicable to rust nor typescript
|
||||
# so it has been moved in a standalone workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
format:
|
||||
name: Check format
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
|
||||
- name: 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
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and test crates on ${{ matrix.os }} with rust ${{ matrix.toolchain }}
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os:
|
||||
- 'ubuntu-latest'
|
||||
- 'macos-latest'
|
||||
- 'windows-latest'
|
||||
toolchain:
|
||||
- 'stable'
|
||||
|
||||
# Test `beta` and `nightly` toolchains only on `ubuntu`
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#expanding-or-adding-matrix-configurations
|
||||
include:
|
||||
- toolchain: 'beta'
|
||||
os: 'ubuntu-latest'
|
||||
- toolchain: 'nightly'
|
||||
os: 'ubuntu-latest'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
|
||||
- run: cargo build --verbose
|
||||
- run: cargo test --verbose
|
||||
|
||||
lint_and_fmt:
|
||||
name: Check format and lint rust crates
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- 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
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Spell Checker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
spell_checking:
|
||||
name: LanguageTool
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Keep this value in order to show the error comments
|
||||
# also on edited files.
|
||||
fetch-depth: 2
|
||||
|
||||
# If the workflow is triggered by a push use check reporter
|
||||
#
|
||||
# If the workflow is triggered by a PR coming from the
|
||||
# same repository, the author has write access so we can
|
||||
# use pr-review reporter since the GITHUB_TOKEN will have review
|
||||
# write permissions
|
||||
#
|
||||
# Otherwise use annotation reporter that do not require any
|
||||
# particular permission
|
||||
#
|
||||
# Why this?
|
||||
# https://github.com/Valerioageno/tuono/pull/137#pullrequestreview-2455455272
|
||||
- name: Set reporter
|
||||
id: reporter
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
FROM_MAIN_REPO: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
|
||||
run: |
|
||||
echo "EVENT_NAME: $EVENT_NAME FROM_MAIN_REPO: $FROM_MAIN_REPO"
|
||||
if [ "$EVENT_NAME" == 'push' ]; then
|
||||
echo "name=github-check" >> $GITHUB_OUTPUT
|
||||
elif [ "$FROM_MAIN_REPO" == 'true' ]; then
|
||||
echo "name=github-pr-review" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "name=github-pr-annotations" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Review dog languagetool
|
||||
uses: reviewdog/action-languagetool@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
|
||||
reporter: ${{ steps.reporter.outputs.name }}
|
||||
# Change reporter level if you need.
|
||||
level: 'warning'
|
||||
patterns: '**/*.md **/*.txt **/*.mdx'
|
||||
# enabled_categories: "STYLE,PLAIN_ENGLISH,WHITESPACE_RULE,EN_QUOTES,DASH_RULE,WORD_CONTAINS_UNDERSCORE,UPPERCASE_SENTENCE_START,ARROWS,COMMA_PARENTHESIS_WHITESPACE,UNLIKELY_OPENING_PUNCTUATION,SENTENCE_WHITESPACE,EN_UNPAIRED_BRACKETS,PHRASE_REPETITION,PUNCTUATION_PARAGRAPH_END"
|
||||
disabled_categories: ''
|
||||
disabled_rules: ''
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Typescript CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Check build and test
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os:
|
||||
- 'ubuntu-latest'
|
||||
- 'macos-latest'
|
||||
- 'windows-latest'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
|
||||
- name: Build project
|
||||
run: pnpm build
|
||||
|
||||
- name: Test project
|
||||
run: pnpm test
|
||||
|
||||
lint_and_fmt:
|
||||
name: Check format and lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install NodeJS Dependencies
|
||||
uses: ./.github/actions/install-node-dependencies
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- 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
|
||||
@@ -1,12 +1,14 @@
|
||||
## Node related ignores
|
||||
|
||||
node_modules/
|
||||
dist/
|
||||
.turbo/
|
||||
|
||||
*/**/routeTree.gen.ts
|
||||
|
||||
pnpm-lock.yaml
|
||||
examples/*/pnpm-lock.yaml
|
||||
|
||||
examples/playground/
|
||||
vite.config.ts.timestamp*
|
||||
|
||||
## Rust related ignores
|
||||
|
||||
@@ -24,3 +26,6 @@ examples/playground/
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Files, generated by the code editors
|
||||
.vscode/
|
||||
@@ -1,2 +1,8 @@
|
||||
dist/
|
||||
pnpm-lock.yaml
|
||||
|
||||
dist
|
||||
.tuono
|
||||
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
* @Valerioageno @marcalexiei
|
||||
|
||||
# Rust
|
||||
/crates/ @Valerioageno
|
||||
/Cargo.toml @Valerioageno
|
||||
|
||||
# Misc
|
||||
/.github/ @marcalexiei
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We, as members, contributors, and leaders, pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best, not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
Advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct that could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned with this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
An individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
valerioageno@yahoo.it.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -0,0 +1,26 @@
|
||||

|
||||

|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
# How to Contribute to Tuono
|
||||
|
||||
## Contributions
|
||||
|
||||
Any feature contribution or suggestion is strongly appreciated.
|
||||
|
||||
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project. You can pick any task that you see in the `ready` column that is not already assigned.
|
||||
|
||||
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
|
||||
|
||||
Consider [opening a new issue](https://github.com/tuono-labs/tuono/issues/new/choose) if you find a bug.
|
||||
|
||||
## Bugs
|
||||
|
||||
**Did you find a bug?**
|
||||
|
||||
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/tuono-labs/tuono/issues).
|
||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/tuono-labs/tuono/issues/new/choose). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
|
||||
|
||||
**Did you write a patch that fixes a bug?**
|
||||
|
||||
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
|
||||
@@ -1,12 +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 = [
|
||||
"examples/playground",
|
||||
"examples/tuono",
|
||||
"examples/tutorial"
|
||||
"apps/documentation",
|
||||
"examples/with-mdx",
|
||||
"examples/tuono-app",
|
||||
"examples/tuono-tutorial",
|
||||
"examples/with-tailwind",
|
||||
]
|
||||
|
||||
@@ -1,75 +1,62 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/Valerioageno/tuono/main/assets/logo.png" width="200px">
|
||||
</p>
|
||||
<h1 align="center">Tuono<br>The react/rust fullstack framework</h1>
|
||||
<p align="center">
|
||||
⚠️ This project is under heavy development. API might drastically change ⚠️
|
||||
</p>
|
||||
[Documentation](https://tuono.dev) | [Tutorial](https://tuono.dev/documentation/tutorial) |
|
||||
[Discord](https://discord.com/invite/khQzPa654B) | [✨Contributing](https://tuono.dev/documentation/contributing)
|
||||
|
||||
# Tuono
|
||||
|
||||
Tuono (Italian word for "thunder", pronounced /2 Oh No/).
|
||||
<img align="right" src="https://raw.githubusercontent.com/tuono-labs/tuono/main/assets/logo.png" width="160px">
|
||||
|
||||

|
||||

|
||||
|
||||
**Tuono is a full-stack web framework for building React applications using Rust as the backend with
|
||||
a strong focus on usability and performance.**
|
||||
|
||||
Tuono (Italian word for "thunder", pronounced /2 Oh No/).
|
||||
Why Tuono? Just a badass name.
|
||||
|
||||
> If you want to see how this project actually works check the [tutorial](https://github.com/Valerioageno/tuono/blob/main/docs/tutorial.md) page.
|
||||
If you have experience with [Next.js](https://nextjs.org/) you will feel home here.
|
||||
|
||||
## Introduction
|
||||
Some of its features are:
|
||||
|
||||
NodeJs/Deno/Bun are the only tools that make a React app fullstack right? (no)
|
||||
- 🟦 Native Typescript
|
||||
- 🌐 Next.js like Routing
|
||||
- 🍭 CSS/SCSS modules
|
||||
- 🧬 Server Side Rendering
|
||||
- 🔥 Hot Module Reload
|
||||
|
||||
Tuono wants to prove that it's possible creating fully fledged react applications without the need to host them on a JS runtime server leveraging the best of the two worlds:
|
||||
super powered server and amazing development experience.
|
||||
## Documentation
|
||||
|
||||
## Requirements
|
||||
|
||||
- rust
|
||||
- cargo
|
||||
- node
|
||||
- pnpm (other package managers support will be added soon)
|
||||
The [documentation](https://tuono.dev/) is available on
|
||||
[tuono.dev](https://tuono.dev/).
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
Available on MacOS, Linux and Windows.
|
||||
|
||||
```sh
|
||||
cargo install tuono
|
||||
```
|
||||
|
||||
## Create a new project
|
||||
## Getting started
|
||||
|
||||
```
|
||||
tuono new [NAME]
|
||||
Create a new project with:
|
||||
|
||||
```sh
|
||||
tuono new [PROJECT_NAME]
|
||||
```
|
||||
|
||||
## Development
|
||||
> You can optionally pass the --template (or -t) flag to directly start from a [template](https://github.com/tuono-labs/tuono/tree/main/examples)
|
||||
|
||||
```
|
||||
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
|
||||
|
||||
```sh
|
||||
tuono dev
|
||||
```
|
||||
## Features
|
||||
|
||||
- [x] FS routing
|
||||
- [x] Hot Module Reload
|
||||
- [x] CSS modules
|
||||
- [x] Rust based server side rendering
|
||||
- [x] Multi thread backend
|
||||
- [x] Development environment
|
||||
- [ ] Create custom APIs
|
||||
- [ ] Image optimization
|
||||
- [ ] Build optimization
|
||||
- [ ] Server streamed content
|
||||
## Contributing
|
||||
|
||||
> 💡 Any suggestion or improvement is strongly appreciated
|
||||
|
||||
## Folder structure
|
||||
|
||||
```
|
||||
| public/
|
||||
- src/
|
||||
| routes/
|
||||
| styles/
|
||||
| package.json
|
||||
| Cargo.toml
|
||||
| .gitignore
|
||||
| tsconfig.json
|
||||
```
|
||||
Any help or suggestion will be appreciated and encouraged.
|
||||
Check the [✨Contributing](https://tuono.dev/documentation/contributing) page
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Stadalone apps folder
|
||||
@@ -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
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "tuono"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "tuono"
|
||||
path = ".tuono/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tuono_lib = "0.17.5"
|
||||
glob = "0.3.1"
|
||||
time = { version = "0.3", features = ["macros"] }
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
@@ -0,0 +1,7 @@
|
||||
# Tuono starter
|
||||
|
||||
This is the starter tuono project. To download it run in your terminal:
|
||||
|
||||
```shell
|
||||
$ tuono new [NAME]
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "documentation",
|
||||
"description": "The react/rust fullstack framework documentation",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write --ignore-unknown .",
|
||||
"format:check": "prettier --check --ignore-unknown .",
|
||||
"types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/code-highlight": "7.16.1",
|
||||
"@mantine/core": "7.16.1",
|
||||
"@mantine/hooks": "7.16.1",
|
||||
"@mdx-js/react": "3.1.0",
|
||||
"@tabler/icons-react": "3.28.1",
|
||||
"clsx": "2.1.1",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"tuono": "npm:tuono@0.17.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/rollup": "3.1.0",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"postcss": "8.5.1",
|
||||
"postcss-preset-mantine": "1.17.0",
|
||||
"postcss-simple-vars": "7.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 550 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"polyfills": [
|
||||
{
|
||||
"identifier": "HTMLElement",
|
||||
"import": {
|
||||
"package": "@lit-labs/ssr-dom-shim",
|
||||
"name": "HTMLElement"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identifier": "customElements",
|
||||
"import": {
|
||||
"package": "@lit-labs/ssr-dom-shim",
|
||||
"name": "customElements"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -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,24 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Button } from '@mantine/core'
|
||||
import { IconEdit } from '@tabler/icons-react'
|
||||
import { useRouter } from 'tuono'
|
||||
|
||||
const GITHUB_URL =
|
||||
'https://github.com/tuono-labs/tuono/tree/main/apps/documentation/src/routes'
|
||||
|
||||
export default function EditPage(): JSX.Element {
|
||||
const { pathname } = useRouter()
|
||||
return (
|
||||
<Button
|
||||
p={0}
|
||||
mt={60}
|
||||
component="a"
|
||||
variant="transparent"
|
||||
leftSection={<IconEdit />}
|
||||
target="_blank"
|
||||
href={GITHUB_URL.concat(pathname).concat('.mdx')}
|
||||
>
|
||||
Edit page
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import EditPage from './EditPage'
|
||||
|
||||
export default EditPage
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { JSX } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
Container,
|
||||
CopyButton,
|
||||
Group,
|
||||
rem,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core'
|
||||
import { IconCopy, IconCheck } from '@tabler/icons-react'
|
||||
import { Link } from 'tuono'
|
||||
|
||||
export default function Hero(): JSX.Element {
|
||||
return (
|
||||
<Container size={1100} my={100}>
|
||||
<Center>
|
||||
<Title order={1}>Superfast fullstack react framework</Title>
|
||||
</Center>
|
||||
<Center mt={50}>
|
||||
<Text fz="18px">
|
||||
The technologies we love seamlessly working together to unleash the
|
||||
<strong> highest web performance</strong> ever met on React
|
||||
</Text>
|
||||
</Center>
|
||||
<Center mt={50}>
|
||||
<Group>
|
||||
<Button component={Link} href="/documentation" size="lg">
|
||||
Get Started
|
||||
</Button>
|
||||
<CopyButton value="cargo install tuono">
|
||||
{({ copied, copy }) => (
|
||||
<Button
|
||||
onClick={copy}
|
||||
size="lg"
|
||||
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
||||
color="gray"
|
||||
leftSection="$ cargo install tuono"
|
||||
rightSection={
|
||||
copied ? (
|
||||
<IconCheck style={{ width: rem(20) }} />
|
||||
) : (
|
||||
<IconCopy style={{ width: rem(20) }} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</CopyButton>
|
||||
</Group>
|
||||
</Center>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import Hero from './Hero'
|
||||
|
||||
export default Hero
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Text, type TextProps } from '@mantine/core'
|
||||
|
||||
export default function MdxBold(props: TextProps): JSX.Element {
|
||||
return <Text component="span" fw={700} {...props} />
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxBold from './MdxBold'
|
||||
|
||||
export default MdxBold
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { JSX, HTMLAttributes } from 'react'
|
||||
import { Code } from '@mantine/core'
|
||||
|
||||
export default function MdxCode(
|
||||
props: HTMLAttributes<HTMLPreElement>,
|
||||
): JSX.Element {
|
||||
return <Code {...props} style={{ fontSize: 'inherit' }} />
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxCode from './MdxCode'
|
||||
|
||||
export default MdxCode
|
||||
@@ -0,0 +1,36 @@
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxLink from './MdxLink'
|
||||
|
||||
export default MdxLink
|
||||
@@ -0,0 +1,5 @@
|
||||
.pre {
|
||||
code {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { JSX } from 'react'
|
||||
import { CodeHighlight } from '@mantine/code-highlight'
|
||||
|
||||
import styles from './MdxPre.module.css'
|
||||
|
||||
interface PreProps {
|
||||
children: {
|
||||
props: {
|
||||
children: string
|
||||
className?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function MdxPre({ children }: PreProps): JSX.Element {
|
||||
return (
|
||||
<CodeHighlight
|
||||
className={styles.pre}
|
||||
code={children.props.children || ''}
|
||||
style={{ borderRadius: 8, fontWeight: 100 }}
|
||||
language={children.props.className?.replace('language-', '')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MdxPre from './MdxPre'
|
||||
|
||||
export default MdxPre
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { JSX, ReactNode } from 'react'
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
|
||||
import MdxLink from './MdxLink'
|
||||
import MdxPre from './MdxPre'
|
||||
import MdxQuote from './MdxQuote'
|
||||
import MdxCode from './MdxCode'
|
||||
import { h } from './MdxTitle'
|
||||
import MdxBold from './MdxBold'
|
||||
import MdxTable from './MdxTable'
|
||||
|
||||
interface MdxProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function MdxProvider({
|
||||
children,
|
||||
}: MdxProviderProps): JSX.Element {
|
||||
return (
|
||||
<MDXProvider
|
||||
components={{
|
||||
a: MdxLink,
|
||||
pre: MdxPre,
|
||||
blockquote: MdxQuote,
|
||||
code: MdxCode,
|
||||
h1: h(1),
|
||||
h2: h(2),
|
||||
h3: h(3),
|
||||
h4: h(4),
|
||||
h5: h(5),
|
||||
h6: h(6),
|
||||
strong: MdxBold,
|
||||
table: MdxTable,
|
||||
thead: MdxTable.Thead,
|
||||
tbody: MdxTable.Tbody,
|
||||
tr: MdxTable.Tr,
|
||||
th: MdxTable.Th,
|
||||
td: MdxTable.Td,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MDXProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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,47 @@
|
||||
import type { ElementType, JSX, ReactNode } from 'react'
|
||||
import { Title, type TitleProps } from '@mantine/core'
|
||||
|
||||
export default function MdxTitle(props: TitleProps): JSX.Element {
|
||||
return (
|
||||
<Title
|
||||
data-heading={props.children}
|
||||
data-order={props.order}
|
||||
style={{ scrollMargin: 70 }}
|
||||
{...props}
|
||||
id={idGen(props.children)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function idGen(children: ReactNode): string {
|
||||
if (Array.isArray(children)) {
|
||||
const result = children
|
||||
.map((child) => {
|
||||
if (typeof child === 'string') {
|
||||
return child
|
||||
}
|
||||
if (typeof child === 'object' && child !== null && 'props' in child) {
|
||||
const childWithProps = child as { props?: { children?: ReactNode } }
|
||||
return typeof childWithProps.props?.children === 'string'
|
||||
? childWithProps.props.children
|
||||
: ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
.join('')
|
||||
|
||||
return result.toLowerCase().replace(/\s+/g, '-')
|
||||
}
|
||||
|
||||
return typeof children === 'string'
|
||||
? children.toLowerCase().replace(/\s+/g, '-')
|
||||
: ''
|
||||
}
|
||||
|
||||
export const h = (order: 1 | 2 | 3 | 4 | 5 | 6): ElementType<TitleProps> => {
|
||||
function render(props: TitleProps): JSX.Element {
|
||||
return <MdxTitle order={order} {...props} />
|
||||
}
|
||||
render.displayName = 'H'
|
||||
return render
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface MetaTagsProps {
|
||||
title: string
|
||||
description?: string
|
||||
type?: 'website' | 'article' | 'book' | 'video.movie' | 'music.song'
|
||||
canonical: string
|
||||
}
|
||||
|
||||
export default function MetaTags({
|
||||
title,
|
||||
description,
|
||||
type = 'article',
|
||||
canonical,
|
||||
}: MetaTagsProps): ReactNode {
|
||||
return (
|
||||
<>
|
||||
<title>{title}</title>
|
||||
<link rel="canonical" href={canonical} />
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
description ??
|
||||
'The technologies we love seamlessly working together to unleash the highest web performance ever met on React'
|
||||
}
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="ReactJs, Typescript, Rust, JavaScript, Fullstack framework, Web development"
|
||||
/>
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:type" content={type} />
|
||||
<meta property="og:image" content="https://tuono.dev/og-cover.png" />
|
||||
<meta property="og:url" content={canonical} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:site_name" content="Tuono" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@tuono_dev" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import MetaTags from './MetaTags'
|
||||
|
||||
export default MetaTags
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { JSX } from 'react'
|
||||
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
|
||||
import NavbarActions from './NavbarActions'
|
||||
|
||||
interface NavbarProps {
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
export default function Navbar({ toggle }: NavbarProps): JSX.Element {
|
||||
return (
|
||||
<AppShell.Header p="sm">
|
||||
<Flex justify="space-between">
|
||||
<Button
|
||||
component={Link}
|
||||
href="/"
|
||||
variant="transparent"
|
||||
p={0}
|
||||
fz={28}
|
||||
hiddenFrom="sm"
|
||||
>
|
||||
Tuono
|
||||
</Button>
|
||||
<Box />
|
||||
<Flex align="center" gap={8}>
|
||||
<NavbarActions />
|
||||
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</AppShell.Header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Flex, ActionIcon, Group } from '@mantine/core'
|
||||
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
|
||||
|
||||
import ThemeBtn from '../ThemeBtn'
|
||||
|
||||
export default function NavbarActions(): JSX.Element {
|
||||
return (
|
||||
<Flex gap={8}>
|
||||
<Group gap={8}>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Check the project on github"
|
||||
href="https://github.com/tuono-labs/tuono"
|
||||
target="_blank"
|
||||
component="a"
|
||||
>
|
||||
<IconBrandGithub />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Join the Tuono's community on Discord!"
|
||||
href="https://discord.com/invite/khQzPa654B"
|
||||
target="_blank"
|
||||
component="a"
|
||||
>
|
||||
<IconBrandDiscord />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<ThemeBtn />
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import Navbar from './Navbar'
|
||||
|
||||
export default Navbar
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
||||
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
||||
import { Link } from 'tuono'
|
||||
|
||||
interface NavigationButtonData {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface NavigationButtonsProps {
|
||||
prev?: NavigationButtonData
|
||||
next?: NavigationButtonData
|
||||
}
|
||||
|
||||
export default function NavigationButtons({
|
||||
prev,
|
||||
next,
|
||||
}: NavigationButtonsProps): JSX.Element {
|
||||
return (
|
||||
<Flex mt={50} gap={10}>
|
||||
{prev && <NavigationBtn type="prev" {...prev} />}
|
||||
{next && <NavigationBtn type="next" {...next} />}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
interface NavigationBtnProps extends NavigationButtonData {
|
||||
type: 'next' | 'prev'
|
||||
}
|
||||
|
||||
const NavigationBtn = ({
|
||||
type,
|
||||
title,
|
||||
href,
|
||||
}: NavigationBtnProps): JSX.Element => {
|
||||
const heading = type === 'next' ? 'Next' : 'Previous'
|
||||
const textAlign = type === 'next' ? 'left' : 'right'
|
||||
const variant = type === 'next' ? 'filled' : 'outline'
|
||||
|
||||
return (
|
||||
<Button
|
||||
component={Link}
|
||||
fullWidth
|
||||
variant={variant}
|
||||
href={href}
|
||||
justify="space-between"
|
||||
h="auto"
|
||||
rightSection={type === 'next' && <IconArrowRight />}
|
||||
leftSection={type === 'prev' && <IconArrowLeft />}
|
||||
p="20"
|
||||
>
|
||||
<Box>
|
||||
<Title component="span" display="block" order={4} style={{ textAlign }}>
|
||||
{heading}
|
||||
</Title>
|
||||
<Text component="span" display="block" style={{ textAlign }}>
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import NavigationButtons from './NavigationButtons'
|
||||
|
||||
export default NavigationButtons
|
||||
@@ -0,0 +1,111 @@
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import {
|
||||
AppShell,
|
||||
Badge,
|
||||
Flex,
|
||||
Divider,
|
||||
Title,
|
||||
Button,
|
||||
ScrollArea,
|
||||
Text,
|
||||
} from '@mantine/core'
|
||||
|
||||
import { IconX } from '@tabler/icons-react'
|
||||
import { useMediaQuery } from '@mantine/hooks'
|
||||
|
||||
import { sidebarElements } from './sidebarElements'
|
||||
import SidebarLink from './SidebarLink'
|
||||
|
||||
interface SidebarProps {
|
||||
close: () => void
|
||||
}
|
||||
|
||||
function SidebarHeader({ close }: SidebarProps): JSX.Element {
|
||||
const isSm = useMediaQuery('(min-width: 48em)')
|
||||
return (
|
||||
<AppShell.Section>
|
||||
<Flex mb={20} w="100%" justify="space-between">
|
||||
<Flex
|
||||
gap={10}
|
||||
align="center"
|
||||
w={isSm ? '100%' : 'auto'}
|
||||
justify="center"
|
||||
>
|
||||
<Title order={3}>Tuono</Title>
|
||||
<Badge mt={4} size="xs" variant="outline">
|
||||
Docs
|
||||
</Badge>
|
||||
</Flex>
|
||||
<Button onClick={close} hiddenFrom="sm" variant="transparent" p="0">
|
||||
<IconX />
|
||||
</Button>
|
||||
</Flex>
|
||||
</AppShell.Section>
|
||||
)
|
||||
}
|
||||
|
||||
function SidebarElements({ close }: SidebarProps): JSX.Element {
|
||||
return (
|
||||
<AppShell.Section component={ScrollArea}>
|
||||
{sidebarElements.map((el, i) => {
|
||||
if (el.type === 'divider') {
|
||||
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
|
||||
}
|
||||
|
||||
if (el.type === 'title') {
|
||||
return (
|
||||
<Text
|
||||
key={`${el.type}-${i}`}
|
||||
size="xs"
|
||||
fw={700}
|
||||
fz={12}
|
||||
pl={12}
|
||||
py={5}
|
||||
>
|
||||
{el.label}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
if (el.children?.length) {
|
||||
return (
|
||||
<SidebarLink
|
||||
key={`${el.type}-${i}`}
|
||||
href={el.href}
|
||||
label={el.label}
|
||||
leftSection={el.leftIcon}
|
||||
>
|
||||
{el.children.map((child, index) => (
|
||||
<SidebarLink
|
||||
href={child.href}
|
||||
label={child.label}
|
||||
key={index}
|
||||
onClick={close}
|
||||
/>
|
||||
))}
|
||||
</SidebarLink>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarLink
|
||||
key={`${el.type}-${i}`}
|
||||
href={el.href}
|
||||
label={el.label}
|
||||
onClick={close}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</AppShell.Section>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Sidebar({ close }: SidebarProps): JSX.Element {
|
||||
return (
|
||||
<AppShell.Navbar p="md">
|
||||
<SidebarHeader close={close} />
|
||||
<SidebarElements close={close} />
|
||||
</AppShell.Navbar>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.link {
|
||||
border-radius: 8px;
|
||||
margin-top: 0.25rem;
|
||||
line-height: 1rem;
|
||||
font-weight: 500;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.link span {
|
||||
font-size: 14px !important;
|
||||
color: var(--mantine-color-sidebar-gray);
|
||||
}
|
||||
|
||||
.link:hover span,
|
||||
.active span {
|
||||
color: var(--mantine-color-sidebar-text-hover);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { JSX, ReactNode } from 'react'
|
||||
import { NavLink, type NavLinkProps } from '@mantine/core'
|
||||
import clsx from 'clsx'
|
||||
import { Link, useRouter } from 'tuono'
|
||||
import { IconChevronRight } from '@tabler/icons-react'
|
||||
|
||||
import styles from './SidebarLink.module.css'
|
||||
|
||||
interface SidebarLinkProps {
|
||||
label: string
|
||||
href: string
|
||||
onClick?: () => void
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export default function SidebarLink(
|
||||
props: SidebarLinkProps & NavLinkProps,
|
||||
): JSX.Element {
|
||||
const { pathname } = useRouter()
|
||||
|
||||
const isActive = pathname === props.href
|
||||
|
||||
const internalProps = {
|
||||
active: isActive,
|
||||
className: clsx(styles.link, isActive && styles.active),
|
||||
rightSection: props.children && (
|
||||
<IconChevronRight
|
||||
size="1.2rem"
|
||||
stroke={1.5}
|
||||
className="mantine-rotate-rtl"
|
||||
/>
|
||||
),
|
||||
autoContrast: true,
|
||||
...props,
|
||||
}
|
||||
|
||||
if (props.href.startsWith('#')) {
|
||||
return <NavLink component="button" {...internalProps} />
|
||||
}
|
||||
|
||||
return <NavLink component={Link} {...internalProps} />
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import Sidebar from './Sidebar'
|
||||
|
||||
export default Sidebar
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface SidebarElementBase<T extends string> {
|
||||
type: T
|
||||
}
|
||||
|
||||
type SidebarDivider = SidebarElementBase<'divider'>
|
||||
|
||||
interface SidebarLink extends SidebarElementBase<'element'> {
|
||||
label: string
|
||||
href: string
|
||||
children?: Array<SidebarLink>
|
||||
leftIcon?: ReactNode
|
||||
}
|
||||
|
||||
interface SidebarTitle extends SidebarElementBase<'title'> {
|
||||
label: string
|
||||
}
|
||||
|
||||
type SidebarElement = SidebarDivider | SidebarLink | SidebarTitle
|
||||
|
||||
export const sidebarElements: Array<SidebarElement> = [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Home',
|
||||
href: '/',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Getting started',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Installation',
|
||||
href: '/documentation/installation',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'How is tuono different?',
|
||||
href: '/documentation/how-is-tuono-different',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Tutorial',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial',
|
||||
label: 'Intro',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/development-setup',
|
||||
label: 'Development setup',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/api-fetching',
|
||||
label: 'API fetching',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/components',
|
||||
label: 'Components',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/dynamic-routes',
|
||||
label: 'Dynamic routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/error-handling',
|
||||
label: 'Error handling',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/seo',
|
||||
label: 'SEO and meta tags',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/redirections',
|
||||
label: 'Redirections',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/production',
|
||||
label: 'Production build',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
href: '/documentation/tutorial/conclusion',
|
||||
label: 'Conclusion',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{ type: 'title', label: 'Overview' },
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Routing',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Defining routes',
|
||||
href: '/documentation/routing/defining-routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Dynamic routes',
|
||||
href: '/documentation/routing/dynamic-routes',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Link and navigation',
|
||||
href: '/documentation/routing/link-and-navigation',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Pages',
|
||||
href: '/documentation/routing/pages',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Loading state',
|
||||
href: '/documentation/routing/loading-state',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Layouts',
|
||||
href: '/documentation/routing/layouts',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Redirecting',
|
||||
href: '/documentation/routing/redirecting',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Rendering',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Server side rendering',
|
||||
href: '/documentation/rendering/server-side-rendering',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Static site rendering',
|
||||
href: '/documentation/rendering/static-site-rendering',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Styles',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'CSS modules',
|
||||
href: '/documentation/styles/css-modules',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Tailwind CSS',
|
||||
href: '/documentation/styles/tailwind-css',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Integrations',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'MDX',
|
||||
href: '/documentation/integrations/mdx',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Core concepts',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Multithreading',
|
||||
href: '/documentation/core-concepts/multithreading',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{ type: 'title', label: 'API reference' },
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Components',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Link',
|
||||
href: '/documentation/components/link',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Hooks',
|
||||
href: '#focus',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'useRouter',
|
||||
href: '/documentation/hooks/use-router',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'CLI',
|
||||
href: '/documentation/cli',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Application state',
|
||||
href: '/documentation/application-state',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Configuration',
|
||||
href: '/documentation/configuration',
|
||||
},
|
||||
|
||||
{ type: 'divider' },
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Contributing',
|
||||
href: '#focus',
|
||||
leftIcon: '✨',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Guildelines',
|
||||
href: '/documentation/contributing',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Local development',
|
||||
href: '/documentation/contributing/local-development',
|
||||
},
|
||||
{
|
||||
type: 'element',
|
||||
label: 'Pull requests',
|
||||
href: '/documentation/contributing/pull-requests',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,107 @@
|
||||
.wrapper {
|
||||
position: sticky;
|
||||
top: var(--mantine-spacing-xl);
|
||||
padding-top: 55px;
|
||||
flex: 0 0 calc(250px - 20px);
|
||||
min-width: 220px;
|
||||
max-width: 220px;
|
||||
height: 100vh;
|
||||
|
||||
/* var(--mantine-breakpoint-md) = 62em */
|
||||
@media (max-width: 62em) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.link {
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
display: block;
|
||||
border-left: 1px solid transparent;
|
||||
padding: 8px var(--mantine-spacing-md);
|
||||
margin-left: -1px;
|
||||
padding-left: calc(1 * var(--mantine-spacing-lg));
|
||||
border-top-right-radius: var(--mantine-radius-sm);
|
||||
border-bottom-right-radius: var(--mantine-radius-sm);
|
||||
|
||||
@mixin light {
|
||||
color: var(--mantine-color-gray-7);
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
color: var(--mantine-color-dark-1);
|
||||
}
|
||||
|
||||
@mixin rtl {
|
||||
border-left: 0;
|
||||
border-right: 1px solid transparent;
|
||||
margin-left: 0;
|
||||
margin-right: -1px;
|
||||
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--mantine-color-violet-1);
|
||||
|
||||
@mixin light {
|
||||
color: var(--mantine-color-black);
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
color: var(--mantine-color-violet-1);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-active] {
|
||||
border-color: var(--mantine-color-violet-5);
|
||||
|
||||
@mixin light {
|
||||
color: var(--mantine-color-violet-8);
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
color: var(--mantine-color-violet-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: var(--mantine-spacing-md);
|
||||
|
||||
@mixin rtl {
|
||||
margin-left: 0;
|
||||
margin-right: var(--mantine-spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
border-left: 1px solid;
|
||||
|
||||
@mixin light {
|
||||
border-color: var(--mantine-color-gray-3) !important;
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
border-color: var(--mantine-color-dark-4) !important;
|
||||
}
|
||||
|
||||
@mixin rtl {
|
||||
border-left: 0;
|
||||
border-right: 1px solid;
|
||||
|
||||
@mixin light {
|
||||
border-color: var(--mantine-color-gray-3) !important;
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
border-color: var(--mantine-color-dark-4) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||
*/
|
||||
export interface Heading {
|
||||
depth: number
|
||||
content: string
|
||||
id: string
|
||||
getNode: () => HTMLHeadingElement
|
||||
}
|
||||
|
||||
function getCleanedText(element: HTMLElement): string {
|
||||
const clone = element.cloneNode(true) as HTMLElement
|
||||
|
||||
clone.querySelectorAll('code, pre').forEach((codeBlock) => {
|
||||
const textNode = document.createTextNode(codeBlock.textContent || '')
|
||||
codeBlock.replaceWith(textNode)
|
||||
})
|
||||
|
||||
return clone.textContent?.trim() || ''
|
||||
}
|
||||
|
||||
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
||||
const result: Array<Heading> = []
|
||||
|
||||
for (const heading of headings) {
|
||||
const depth = parseInt(heading.getAttribute('data-order') || '1', 10)
|
||||
|
||||
if (depth <= 3 && heading.id) {
|
||||
result.push({
|
||||
depth,
|
||||
content: getCleanedText(heading),
|
||||
id: heading.id,
|
||||
getNode: () =>
|
||||
document.getElementById(heading.id) as HTMLHeadingElement,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function getHeadings(): Array<Heading> {
|
||||
const root = document.getElementById('mdx-root')
|
||||
|
||||
if (!root) {
|
||||
return []
|
||||
}
|
||||
|
||||
return getHeadingsData(Array.from(root.querySelectorAll('[data-heading]')))
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||
*/
|
||||
import { TableOfContents } from './TableOfContents'
|
||||
|
||||
export default TableOfContents
|
||||
@@ -0,0 +1,24 @@
|
||||
.icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
@mixin dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@mixin light {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.light {
|
||||
@mixin light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { JSX } from 'react'
|
||||
import {
|
||||
ActionIcon,
|
||||
useMantineColorScheme,
|
||||
useComputedColorScheme,
|
||||
} from '@mantine/core'
|
||||
import { IconSun, IconMoon } from '@tabler/icons-react'
|
||||
import cx from 'clsx'
|
||||
|
||||
import classes from './ThemeBtn.module.css'
|
||||
|
||||
export default function ThemeBtn(): JSX.Element {
|
||||
const { setColorScheme } = useMantineColorScheme()
|
||||
const computedColorScheme = useComputedColorScheme('light', {
|
||||
getInitialValueInEffect: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
||||
}}
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
>
|
||||
<IconSun className={cx(classes.icon, classes.light)} stroke={1.5} />
|
||||
<IconMoon className={cx(classes.icon, classes.dark)} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import ThemeBtn from './ThemeBtn'
|
||||
|
||||
export default ThemeBtn
|
||||
@@ -0,0 +1,5 @@
|
||||
// declaration.d.ts
|
||||
declare module '*.css' {
|
||||
const CSSModule: Record<string, string>
|
||||
export default CSSModule
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import type { ReactNode, JSX } from 'react'
|
||||
import { TuonoScripts } from 'tuono'
|
||||
|
||||
import {
|
||||
ColorSchemeScript,
|
||||
createTheme,
|
||||
MantineProvider,
|
||||
AppShell,
|
||||
mantineHtmlProps,
|
||||
type CSSVariablesResolver,
|
||||
} from '@mantine/core'
|
||||
import { useDisclosure } from '@mantine/hooks'
|
||||
|
||||
import MdxWrapper from '@/components/MdxWrapper'
|
||||
import Navbar from '@/components/Navbar'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
|
||||
import '@mantine/core/styles.css'
|
||||
import '@mantine/code-highlight/styles.css'
|
||||
|
||||
interface RootRouteProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const theme = createTheme({
|
||||
primaryColor: 'violet',
|
||||
primaryShade: { light: 6, dark: 9 },
|
||||
fontFamily: 'Inter',
|
||||
respectReducedMotion: true,
|
||||
radius: {
|
||||
xs: '8px',
|
||||
lg: '8px',
|
||||
xl: '8px',
|
||||
md: '8px',
|
||||
sm: '8px',
|
||||
},
|
||||
fontSizes: {
|
||||
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||
xs: '14px',
|
||||
sm: '14px',
|
||||
},
|
||||
colors: {
|
||||
dark: [
|
||||
'#d5d7e0',
|
||||
'#acaebf',
|
||||
'#8c8fa3',
|
||||
'#666980',
|
||||
'#4d4f66',
|
||||
'#34354a',
|
||||
'#2b2c3d',
|
||||
'#1d1e30',
|
||||
'#0c0d21',
|
||||
'#01010a',
|
||||
],
|
||||
},
|
||||
headings: {
|
||||
sizes: {
|
||||
h1: {
|
||||
fontSize: '48px',
|
||||
},
|
||||
},
|
||||
},
|
||||
other: {
|
||||
sidebarGrayLight: '#495057',
|
||||
sidebarGrayDark: '#adb5bd',
|
||||
sidebarTextHoverLight: '#212529',
|
||||
sidebarTextHoverDark: '#f8f9fa',
|
||||
},
|
||||
})
|
||||
|
||||
const resolver: CSSVariablesResolver = (th) => ({
|
||||
variables: {},
|
||||
light: {
|
||||
'--mantine-color-sidebar-gray': th.other.sidebarGrayLight as string,
|
||||
'--mantine-color-sidebar-text-hover': th.other
|
||||
.sidebarTextHoverLight as string,
|
||||
},
|
||||
dark: {
|
||||
'--mantine-color-sidebar-gray': th.other.sidebarGrayDark as string,
|
||||
'--mantine-color-sidebar-text-hover': th.other
|
||||
.sidebarTextHoverDark as string,
|
||||
},
|
||||
})
|
||||
|
||||
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
|
||||
const [opened, { toggle }] = useDisclosure()
|
||||
|
||||
return (
|
||||
<html lang="en" {...mantineHtmlProps}>
|
||||
<head>
|
||||
<meta charSet="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<ColorSchemeScript />
|
||||
</head>
|
||||
<body>
|
||||
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
|
||||
<AppShell
|
||||
layout="alt"
|
||||
header={{ height: 60 }}
|
||||
navbar={{
|
||||
width: 300,
|
||||
breakpoint: 'sm',
|
||||
collapsed: { mobile: !opened },
|
||||
}}
|
||||
>
|
||||
<Navbar toggle={toggle} />
|
||||
<Sidebar close={toggle} />
|
||||
<AppShell.Main pt={0} px="auto">
|
||||
<MdxWrapper>{children}</MdxWrapper>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
</MantineProvider>
|
||||
<TuonoScripts />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Application state"
|
||||
canonical="https://tuono.dev/documentation/application-state"
|
||||
description="Learn how to add features to your Tuono application"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Application state' }]} />
|
||||
|
||||
# Application state
|
||||
|
||||
The main reason Tuono is fast is that it loads just the features that are needed for the project.
|
||||
|
||||
To define them, you need to fill the `ApplicationState` struct in the `./src/app.rs` file, and
|
||||
they will be automatically available in all the handlers you will define across the application.
|
||||
|
||||
```rs
|
||||
// src/app.rs
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApplicationState {
|
||||
pub website_name: String,
|
||||
pub base_path: String
|
||||
}
|
||||
|
||||
pub fn main() -> ApplicationState {
|
||||
let website_name = "tuono".to_string();
|
||||
let base_path = "http://localhost:3000".to_string();
|
||||
|
||||
ApplicationState {
|
||||
website_name,
|
||||
base_path
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> For the sake of simplicity here we just enable a `String` but you can add Database connections or
|
||||
> HTTP clients (take a look [here](/documentation/tutorial/api-fetching) for an example with an HTTP client).
|
||||
|
||||
Now the `ApplicationState` is available on all the handlers as following:
|
||||
|
||||
```rs
|
||||
// src/routes/index.rs
|
||||
|
||||
#[tuono_lib::handler]
|
||||
// The first argument always is the Request
|
||||
// The ApplicationState arguments are optional. You can use just the ones you need
|
||||
// to use in the handler (with no specific order).
|
||||
async fn my_handler(req: Request, website_name: String) -> Response {
|
||||
...
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - CLI"
|
||||
canonical="https://tuono.dev/documentation/cli"
|
||||
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
|
||||
|
||||
# CLI
|
||||
|
||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
||||
|
||||
> ☝️ Check the [installation](/documentation/installation) page if you haven't installed the
|
||||
> `tuono` CLI yet.
|
||||
|
||||
To list all the available commands, run `tuono -h`.
|
||||
|
||||
```bash
|
||||
The React/Rust full-stack framework
|
||||
|
||||
Usage: tuono <COMMAND>
|
||||
|
||||
Commands:
|
||||
dev Start the development environment
|
||||
build Build the production assets
|
||||
new Scaffold a new project
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
To create a new project, run `tuono new [NAME]` (optionally you can pass the --template (or -t) flag - check the examples' folder).
|
||||
|
||||
Then to run the local development environment run inside the project folder `tuono dev`
|
||||
|
||||
Finally, when the project will be ready to be deployed just run `tuono build` to create the final React assets and to set the server project in the production mode.
|
||||
|
||||
Now to execute it, just run `cargo run --release`.
|
||||
@@ -0,0 +1,39 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Link"
|
||||
canonical="https://tuono.dev/documentation/components/link"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
|
||||
|
||||
# Link
|
||||
|
||||
Tuono provides a `Link` component for clientside navigation between pages of your app. `Link` prefetches pages from any links above the fold, allowing for faster navigations without a full reload.
|
||||
|
||||
```typescript jsx
|
||||
import { Link } from 'tuono'
|
||||
|
||||
export default function Page() {
|
||||
return <Link href="/page2">My Other Page</Link>
|
||||
}
|
||||
```
|
||||
|
||||
For navigating to pages outside of your app (other websites), you should use the `<a>` tag.
|
||||
|
||||
> Additional considerations:
|
||||
>
|
||||
> - Any `Link` in the viewport is prefetched by default when it appears within the viewport.
|
||||
> - The corresponding data for server-rendered routes is fetched only when the `Link` is clicked.
|
||||
|
||||
## Reference
|
||||
|
||||
The `Link` component behaves similarly to the `<a>` HTML tag, with a few additional features controlled by the following props:
|
||||
|
||||
| Prop | Type | Default value | Description |
|
||||
| --------- | ------------------- | ------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `href` | `string` (Required) | - | Relative path to the page in your application. Anchor names (links with hashtags) are also valid. |
|
||||
| `preload` | `boolean` | `true` | Whether to preload the page if the link is within the viewport. |
|
||||
| `scroll` | `boolean` | `true` | Whether to preserve scroll position between navigations. |
|
||||
@@ -0,0 +1,76 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - tuono.config.ts"
|
||||
canonical="https://tuono.dev/documentation/configuration"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Configuration' }]} />
|
||||
|
||||
# Configuration
|
||||
|
||||
Tuono can be configured using the `tuono.config.ts` file,
|
||||
which allows you to customize various aspects of the build process and development environment.
|
||||
|
||||
The config file is written in TypeScript,
|
||||
so you can take advantage of type checking and auto-suggestions provided by the `TuonoConfig` type,
|
||||
which is exported from `tuono/config`.
|
||||
|
||||
```ts
|
||||
import type { TuonoConfig } from 'tuono/config'
|
||||
|
||||
const config: TuonoConfig = {
|
||||
/* typesafe config */
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
## vite
|
||||
|
||||
Tuono leverages Vite, a modern and fast build tool and development server for web applications.
|
||||
|
||||
As a result, **some** Vite configuration properties can be set in the `tuono.config.ts` file under the `vite` property.
|
||||
|
||||
### vite.alias
|
||||
|
||||
Useful for configuring custom source alias paths during imports
|
||||
|
||||
- Type:
|
||||
- `Record<string, string>`
|
||||
- `Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>`
|
||||
- [Vite documentation: resolve.alias](https://vite.dev/config/shared-options#resolve-alias)
|
||||
|
||||
### vite.optimizeDeps
|
||||
|
||||
Vite's dependency optimizer used only during dev
|
||||
|
||||
- [Vite documentation: Dep Optimization Options](https://vite.dev/config/dep-optimization-options)
|
||||
|
||||
### vite.plugins
|
||||
|
||||
Useful for extending vite's functionality
|
||||
|
||||
- Type: `Array<Plugin | Array<Plugin> | Promise<Plugin | Array<Plugin>>`
|
||||
- [Vite documentation: plugins](https://vite.dev/guide/using-plugins)
|
||||
|
||||
## server
|
||||
|
||||
The following options configure the server.
|
||||
|
||||
## server.host
|
||||
|
||||
- Type: `string`
|
||||
- Default: `'localhost'`
|
||||
|
||||
The hostname or IP address of the server.
|
||||
It can be a domain name (e.g., `"example.com"`) or an IP address (e.g., `"127.0.0.1"`)
|
||||
|
||||
## server.port
|
||||
|
||||
- Type: `number`
|
||||
- Default: `3000`
|
||||
|
||||
The port number where the server will listen for incoming requests.
|
||||
@@ -0,0 +1,48 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Contributing"
|
||||
canonical="https://tuono.dev/documentation/contributing"
|
||||
description="The project is massive - if you like it, do consider contributing!"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
||||
|
||||
# Contributing
|
||||
|
||||
## TL;DR
|
||||
|
||||
The project is massive - if you like it, do consider contributing!
|
||||
|
||||
## Getting started
|
||||
|
||||
The `tuono` project can mostly be split into the following subdomains:
|
||||
|
||||
- The CLI
|
||||
- The Rust backend
|
||||
- The React frontend
|
||||
- The documentation website (which is written with tuono 🚀)
|
||||
|
||||
To check what the knowledge requirements are for each domain, check the
|
||||
[requirements](#requirements) section below.
|
||||
|
||||
Currently, I'm keeping a private dashboard to prioritize new features and bug fixes, but if you
|
||||
want to propose something, please open a new issue on Github or reach out to me using
|
||||
my email address [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it). I'm also available
|
||||
on Twitter (X) DMs `@valerioageno`, [Linkedin](https://www.linkedin.com/in/valerioageno)
|
||||
and discord `@__v__v__`.
|
||||
|
||||
## Requirements
|
||||
|
||||
It's not strictly required to know both React (& typescript) and Rust (even though it
|
||||
would be a great nice to have).
|
||||
|
||||
Without taking into account specific cases, we can mostly split the domain requirements by:
|
||||
|
||||
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
|
||||
- The Backend needs just `Rust`
|
||||
- The Frontend needs just `React` & `Typescript`
|
||||
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
||||
code is markdown).
|
||||
@@ -0,0 +1,56 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Contributing"
|
||||
canonical="https://tuono.dev/documentation/contributing"
|
||||
description="The project is massive - if you like it, do consider contributing!"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
||||
|
||||
# Contributing
|
||||
|
||||
## TL;DR
|
||||
|
||||
The project is massive - if you like it, do consider contributing!
|
||||
|
||||
## Getting started
|
||||
|
||||
The `tuono` project can mostly be split into the following subdomains:
|
||||
|
||||
- The CLI
|
||||
- The Rust backend
|
||||
- The React frontend
|
||||
- The documentation website (which is written with tuono 🚀)
|
||||
|
||||
To check what the knowledge requirements are for each domain, check the
|
||||
[requirements](#requirements) section below.
|
||||
|
||||
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project.
|
||||
You can pick any task that you see in the `ready` column that is not already assigned.
|
||||
|
||||
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
|
||||
|
||||
## Requirements
|
||||
|
||||
It's not strictly required to know both React (& typescript) and Rust (even though it
|
||||
would be a great nice to have).
|
||||
|
||||
Without taking into account specific cases, we can mostly split the domain requirements by:
|
||||
|
||||
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
|
||||
- The Backend needs just `Rust`
|
||||
- The Frontend needs just `React` & `Typescript`
|
||||
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
||||
code is markdown).
|
||||
|
||||
import NavigationButtons from '../../../components/NavigationButtons'
|
||||
|
||||
<NavigationButtons
|
||||
next={{
|
||||
title: 'Local development',
|
||||
href: '/documentation/contributing/local-development',
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,220 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Contributing - Local development"
|
||||
canonical="https://tuono.dev/documentation/contributing/local-development"
|
||||
description="Contribute to Tuono. Learn here how to setup the repository for local development"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: '✨ Contributing', href: '/documentation/contributing' },
|
||||
{ label: 'Local development' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Local development
|
||||
|
||||
Thanks for your interest! In this page, you find the instructions to set up `tuono` on your local environment!
|
||||
|
||||
## Setup
|
||||
|
||||
### Fork and clone repository
|
||||
|
||||
After [forking the repo on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo):
|
||||
|
||||
```sh
|
||||
git clone https://github.com/<your-name-here>/tuono
|
||||
cd tuono
|
||||
```
|
||||
|
||||
For the next steps, you can either set up the environment directly on your computer or use a ready-to-use Docker image (go to the [Docker setup](#docker-setup) part).
|
||||
|
||||
### Local setup
|
||||
|
||||
#### Rust tool chain
|
||||
|
||||
Install the Rust programming language tool chain (`rust` and `cargo`).
|
||||
Follows instructions in the official [docs](https://rustup.rs/)
|
||||
|
||||
#### Node.js — runtime
|
||||
|
||||
Install `Node.js`.
|
||||
You can follow the instructions from the [Node official site](https://nodejs.org/en/download/package-manager)
|
||||
|
||||
> 💡 This project has a `.nvmrc` file to specify the node version used in development.
|
||||
>
|
||||
> Consider to use [nvm](https://github.com/nvm-sh/nvm) so you can run
|
||||
>
|
||||
> ```sh
|
||||
> nvm use
|
||||
> ```
|
||||
>
|
||||
> to simply pick up the correct version!
|
||||
|
||||
#### Node.js — package manager
|
||||
|
||||
We use [`pnpm`](https://pnpm.io) as Node.js package manager.
|
||||
|
||||
You can see which version of yarn we use by checking the `packageManager` field in the root `package.json`.
|
||||
|
||||
#### Pre-flight checks
|
||||
|
||||
To check that everything is working properly, run:
|
||||
|
||||
```sh
|
||||
pnpm run check-all
|
||||
cargo build
|
||||
```
|
||||
|
||||
### Docker setup
|
||||
|
||||
#### Introduction
|
||||
|
||||
Docker takes care of configuring the development environment, so you don’t need to worry about installing specific versions of packages like Node.js or pnpm. The Docker image handles this for you!
|
||||
Using Docker, **the only operations you’ll need to manage on your host machine are related to git**. (don't use pnpm, cargo, ... from your host machine)
|
||||
Some IDEs (such as vscode with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)) allow you to connect directly to the Docker container.
|
||||
|
||||
**Before proceeding with this guide, make sure that the `node_modules` and `target` directories are either absent or empty in the subprojects of Tuono from your host machine where you want to contribute.**
|
||||
This ensures a clean and consistent environment when using Docker.
|
||||
|
||||
#### Build the Tuono's Docker Container
|
||||
|
||||
First, ensure Docker is installed on your machine by following the instructions on the [Docker official site](https://docs.docker.com/engine/install/).
|
||||
|
||||
Once Docker is installed, use the following command to build the image into a container named tuono-source-container:
|
||||
|
||||
```sh
|
||||
# Note: Execute this command in the project root
|
||||
docker compose -f docker/compose.yml up --build -d
|
||||
```
|
||||
|
||||
To verify that everything is working as expected, run the following command:
|
||||
|
||||
```sh
|
||||
docker images && docker ps -a --size
|
||||
```
|
||||
|
||||
Or on Windows:
|
||||
|
||||
```powershell
|
||||
docker images; docker ps -a --size
|
||||
```
|
||||
|
||||
You should see the image named tuono-source-image and the container tuono-source-container. The container's status should be "up".
|
||||
|
||||
Use the following command to connect to the container:
|
||||
|
||||
```sh
|
||||
docker exec -it tuono-source-container /bin/bash
|
||||
```
|
||||
|
||||
## Tuono development
|
||||
|
||||
1. Start tuono frontend build using
|
||||
|
||||
```sh
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
2. In another terminal run
|
||||
|
||||
```sh
|
||||
cargo build
|
||||
```
|
||||
|
||||
To automatically rebuild crates on code change, consider using `cargo-watch` crate
|
||||
|
||||
```sh
|
||||
cargo watch -x build -w crates/
|
||||
```
|
||||
|
||||
3. You can now use the binary inside `/target/debug/tuono` in another folder on your local machine
|
||||
|
||||
> Consider adding an alias to your shell setup file
|
||||
>
|
||||
> ```sh
|
||||
> alias t="/path-to-repo/target/debug/tuono"
|
||||
> ```
|
||||
|
||||
## Documentation development
|
||||
|
||||
1. Change the current working directory to the documentation folder:
|
||||
|
||||
```sh
|
||||
cd apps/documentation
|
||||
```
|
||||
|
||||
2. Run
|
||||
|
||||
```sh
|
||||
tuono dev
|
||||
```
|
||||
|
||||
3. Open the localhost URL.
|
||||
|
||||
> On the documentation remember that `tuono` `npm` package is installed from the registry and
|
||||
> it is not linked to the repository.
|
||||
|
||||
## Validate your changes
|
||||
|
||||
The following checks are all run on pull requests automatically.
|
||||
|
||||
You can also perform them locally.
|
||||
|
||||
## Formatting
|
||||
|
||||
For the typescript part you can run:
|
||||
|
||||
```sh
|
||||
pnpm run format:check && pnpm run -w repo:root:format:check
|
||||
```
|
||||
|
||||
For the rust codebase run:
|
||||
|
||||
```sh
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
For the typescript part you can run:
|
||||
|
||||
```sh
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
For the rust codebase run:
|
||||
|
||||
```sh
|
||||
cargo clippy -- -D warnings
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
For the typescript part you can run:
|
||||
|
||||
```sh
|
||||
pnpm run test
|
||||
```
|
||||
|
||||
For the rust codebase run:
|
||||
|
||||
```sh
|
||||
cargo test
|
||||
```
|
||||
|
||||
import NavigationButtons from '../../../components/NavigationButtons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Guidelines',
|
||||
href: '/documentation/contributing',
|
||||
}}
|
||||
next={{
|
||||
title: 'Pull requests',
|
||||
href: '/documentation/contributing/pull-requests',
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,110 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Opening a pull request"
|
||||
canonical="https://tuono.dev/documentation/contributing/pull-requests"
|
||||
description="Contribute to Tuono. Learn here how to open a pull request."
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: '✨ Contributing', href: '/documentation/contributing' },
|
||||
{ label: 'Pull Requests' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Pull Requests
|
||||
|
||||
Once your changes are ready, you can create a PR!
|
||||
|
||||
If you are not familiar with GitHub pull requests,
|
||||
you can check the [official documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
|
||||
|
||||
---
|
||||
|
||||
- Please do:
|
||||
|
||||
- Fill the template in full
|
||||
|
||||
- If you are fixing an issue, bear in mind to link the issue itself in the PR description.
|
||||
- If you are adding a new feature, describe the intended use case that the feature fulfills.
|
||||
|
||||
- Ensure your code is valid by running the test on you local machine.
|
||||
Refer to ["Validate your changes" section](/documentation/contributing/local-development)
|
||||
|
||||
- Please **don't**:
|
||||
|
||||
- Do not use force push after marking the PR ready for review.
|
||||
|
||||
**Reasoning:** GitHub cannot track changes across force pushes,
|
||||
which slows down our ability to perform incremental reviews.
|
||||
|
||||
## Title
|
||||
|
||||
The PR title must match the following format:
|
||||
|
||||
```text
|
||||
<type>[optional scope]: <short description>
|
||||
```
|
||||
|
||||
You can find commit examples in the [recent commits on main branch](https://github.com/tuono-labs/tuono/commits/main/)
|
||||
|
||||
Here are some examples:
|
||||
|
||||
- feat: add support for react 19
|
||||
- ci: create install-node-dependencies action
|
||||
- feat(packages/tuono-router): use React.Context to pass data
|
||||
- test(packages/tuono): messageChannel - use vi.fn
|
||||
|
||||
> We do not care about the number, or style of commits in your branch history,
|
||||
> because we squash merge every PR into `main`.
|
||||
>
|
||||
> Feel free to commit in whatever style you feel comfortable with.
|
||||
|
||||
### type
|
||||
|
||||
Must be one of the following
|
||||
|
||||
{/* @warning Keep in sync with .github/workflows/pr-title-checker.yml */}
|
||||
|
||||
- `docs` - if you only change documentation, and not shipped code
|
||||
- `feat` - any new functionality additions
|
||||
- `fix` - any bug fixes that don't add new functionality
|
||||
- `refactor` - a code change that neither fixes a bug or adds a feature
|
||||
- `test` - if you only change tests, and not shipped code
|
||||
- `ci` - if you change something related to continuos integration
|
||||
- `chore` - anything else (e.g.: release)
|
||||
|
||||
### optional scope
|
||||
|
||||
A scope may be provided to a commit's type,
|
||||
to provide additional contextual information and is contained within parenthesis
|
||||
E.g.: `fix(crates/tuono): remove cargo warnings`
|
||||
|
||||
### short description
|
||||
|
||||
A succinct title for the PR.
|
||||
(The title max length is set to 100 characters)
|
||||
|
||||
## Addressing review feedback
|
||||
|
||||
Once we've reviewed your PR, we’ll provide feedback on any changes that need attention.
|
||||
If you feel a suggested change isn’t correct, feel free to discuss it with us in the comments.
|
||||
|
||||
Please [post your comments](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/commenting-on-a-pull-request#adding-comments-to-a-pull-request)
|
||||
as line comments when possible so they can be threaded.
|
||||
|
||||
After addressing all feedback, whether through code changes or by starting a follow-up discussion,
|
||||
please [re-request a review](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews#re-requesting-a-review)
|
||||
from each maintainer whose feedback you've addressed.
|
||||
|
||||
import NavigationButtons from '../../../components/NavigationButtons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Local development',
|
||||
href: '/documentation/contributing/local-development',
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,14 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - Multithreading"
|
||||
canonical="https://tuono.dev/documentation/core-concepts/multithreading"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Core concepts' }]} />
|
||||
|
||||
# Multithreading
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,57 @@
|
||||
import MetaTags from '@/components/MetaTags'
|
||||
|
||||
<MetaTags
|
||||
title="Tuono - useRouter"
|
||||
canonical="https://tuono.dev/documentation/hooks/use-router"
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '@/components/Breadcrumbs'
|
||||
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'useRouter' }]} />
|
||||
|
||||
# useRouter
|
||||
|
||||
The `useRouter` hook provides access to various data about the current route, as well as methods to navigate between pages, as seen in the below example:
|
||||
|
||||
```tsx
|
||||
import { JSX } from 'react'
|
||||
import { useRouter } from 'tuono'
|
||||
|
||||
export default function IndexPage(): JSX.Element {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>pathname: {router.pathname}</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
router.push('/foo')
|
||||
}}
|
||||
>
|
||||
My link
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## `useRouter` result
|
||||
|
||||
- `pathname`: `string` - Returns the current path name
|
||||
- `query`: `Record<string, string>` - This object contains all the query params of the current route
|
||||
|
||||
The following methods are included inside `router`:
|
||||
|
||||
### `router.push`
|
||||
|
||||
Handles client side page navigation quickly. Useful for internal links, or for when you need to change url in some JS.
|
||||
|
||||
```ts
|
||||
router.push(path, options)
|
||||
```
|
||||
|
||||
| Name | Type | Default value | Description |
|
||||
| ---------------- | ------------------- | ------------- | ----------------------------------------------------------------- |
|
||||
| `path` | `string` (Required) | - | The path of the page you want to navigate to. |
|
||||
| `options` | `PushOptions` | - | Optional config object for additional control. |
|
||||
| `options.scroll` | `boolean` | `true` | If `false` the scroll offset will be kept across page navigation. |
|
||||