odoro 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +73 -0
  2. package/dist/build-KI7IEVRJ.js +4 -0
  3. package/dist/{chunk-R6PMAMXA.js → chunk-2EE5QUCW.js} +16 -3
  4. package/dist/{chunk-CJIUP7A3.js → chunk-FJYUQALD.js} +1 -1
  5. package/dist/cli.js +4 -4
  6. package/dist/{create-LTT3SAWT.js → create-VUSKHXBN.js} +109 -65
  7. package/dist/index.js +2 -2
  8. package/dist/{package-LK46RV2I.js → package-2JOBLFXE.js} +3 -2
  9. package/dist/registry/index.d.ts +2 -2
  10. package/dist/{server-ATGFRVSW.js → server-DXOQAPVX.js} +1 -1
  11. package/package.json +3 -2
  12. package/templates/react-ts/_variantes/avec-moteur/src/{sections/Fond.tsx → fond.tsx} +13 -13
  13. package/templates/react-ts/_variantes/sans-libs/src/App.tsx +363 -23
  14. package/templates/{react-ts-server/_variantes/sans-libs/client/src/sections/Fond.tsx → react-ts/_variantes/sans-libs/src/fond.tsx} +1 -1
  15. package/templates/react-ts/_variantes/sans-libs/src/styles.css +115 -38
  16. package/templates/react-ts/_variantes/sans-routeur/src/App.tsx +427 -22
  17. package/templates/react-ts/index.html +11 -0
  18. package/templates/react-ts/public/favicon.svg +2 -3
  19. package/templates/react-ts/src/App.tsx +585 -57
  20. package/templates/{react-ts-server/client/src/sections/Fond.tsx → react-ts/src/fond.tsx} +9 -9
  21. package/templates/react-ts/src/router.tsx +72 -0
  22. package/templates/react-ts-server/_variantes/avec-moteur/client/src/{sections/Fond.tsx → fond.tsx} +13 -13
  23. package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +363 -23
  24. package/templates/{react-ts/_variantes/sans-libs/src/sections/Fond.tsx → react-ts-server/_variantes/sans-libs/client/src/fond.tsx} +1 -1
  25. package/templates/react-ts-server/_variantes/sans-libs/client/src/styles.css +115 -38
  26. package/templates/react-ts-server/_variantes/sans-routeur/client/src/App.tsx +427 -22
  27. package/templates/react-ts-server/client/index.html +11 -0
  28. package/templates/react-ts-server/client/public/favicon.svg +2 -3
  29. package/templates/react-ts-server/client/src/App.tsx +585 -57
  30. package/templates/{react-ts/src/sections/Fond.tsx → react-ts-server/client/src/fond.tsx} +9 -9
  31. package/templates/react-ts-server/client/src/router.tsx +72 -0
  32. package/dist/build-GIPMPYJ3.js +0 -4
  33. package/templates/react-ts/_variantes/sans-libs/src/composants/Marque.tsx +0 -37
  34. package/templates/react-ts/_variantes/sans-libs/src/sections/Cloture.tsx +0 -60
  35. package/templates/react-ts/_variantes/sans-libs/src/sections/Hero.tsx +0 -92
  36. package/templates/react-ts/_variantes/sans-libs/src/sections/Piliers.tsx +0 -44
  37. package/templates/react-ts/src/composants/Marque.tsx +0 -50
  38. package/templates/react-ts/src/routes/About.tsx +0 -43
  39. package/templates/react-ts/src/routes/Home.tsx +0 -28
  40. package/templates/react-ts/src/routes/NotFound.tsx +0 -14
  41. package/templates/react-ts/src/sections/Cloture.tsx +0 -69
  42. package/templates/react-ts/src/sections/Hero.tsx +0 -119
  43. package/templates/react-ts/src/sections/Piliers.tsx +0 -68
  44. package/templates/react-ts-server/_variantes/sans-libs/client/src/composants/Marque.tsx +0 -37
  45. package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Cloture.tsx +0 -60
  46. package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Hero.tsx +0 -92
  47. package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Piliers.tsx +0 -44
  48. package/templates/react-ts-server/client/src/composants/Marque.tsx +0 -50
  49. package/templates/react-ts-server/client/src/routes/About.tsx +0 -43
  50. package/templates/react-ts-server/client/src/routes/Home.tsx +0 -28
  51. package/templates/react-ts-server/client/src/routes/NotFound.tsx +0 -14
  52. package/templates/react-ts-server/client/src/sections/Cloture.tsx +0 -69
  53. package/templates/react-ts-server/client/src/sections/Hero.tsx +0 -119
  54. package/templates/react-ts-server/client/src/sections/Piliers.tsx +0 -68
@@ -1,22 +1,22 @@
1
1
  /**
2
2
  * Le fond decoratif de la page.
3
3
  *
4
- * ## Ce qu'il est, et ce qu'il remplace
4
+ * ## Pourquoi ce n'est pas dans `App.tsx`
5
5
  *
6
- * Deux nappes de couleur de marque, tres diluees, posees en haut de page. Rien
7
- * ne bouge : c'est un degrade, pas une animation — il ne coute aucune image par
8
- * seconde et se dessine avant le premier octet de JavaScript.
6
+ * C'est le seul morceau de la page qui depend de ce qui a ete coche a la
7
+ * creation : avec le moteur, ce fichier est remplace par une version qui ouvre
8
+ * une surface WebGL animee ; sans lui, c'est le degrade ci-dessous.
9
9
  *
10
- * Si le moteur a ete retenu a la creation, ce fichier est remplace par une
11
- * version qui ouvre une surface WebGL animee. Le reste de la page ne sait pas
12
- * d'ou vient son fond : elle place `<Fond />` et n'en demande pas plus.
10
+ * Le garder a part evite d'avoir deux `App.tsx` a tenir — un par cas — qui
11
+ * divergeraient au premier changement de texte. `App.tsx` place `<Fond />` et
12
+ * n'en demande pas plus.
13
13
  *
14
14
  * @module
15
15
  */
16
16
 
17
17
  import type { ReactElement } from 'react'
18
18
 
19
- /** Deux nappes de marque, diluees, derriere le contenu. */
19
+ /** Deux nappes de marque, diluees, derriere le haut de la page. */
20
20
  export function Fond(): ReactElement {
21
21
  return (
22
22
  <div
@@ -31,7 +31,7 @@ export function Fond(): ReactElement {
31
31
  }}
32
32
  />
33
33
  <div
34
- className="o-absolute o-right-[-10rem] o-top-[6rem] o-size-[30rem] o-rounded-full o-blur-3xl o-opacity-20 dark:o-opacity-20"
34
+ className="o-absolute o-right-[-10rem] o-top-[6rem] o-size-[30rem] o-rounded-full o-blur-3xl o-opacity-20"
35
35
  style={{
36
36
  background:
37
37
  'radial-gradient(closest-side, var(--o-palette-brand-400), transparent)',
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Le routeur du projet.
3
+ *
4
+ * ## Pourquoi il vit seul, dans son fichier
5
+ *
6
+ * C'est le seul endroit qui nomme la dependance de routage. Changer de routeur,
7
+ * ajouter une route, en proteger une derriere une authentification : tout se
8
+ * lit ici, et `App.tsx` n'a rien a en savoir au-dela de la ligne qui l'importe.
9
+ *
10
+ * ## Pourquoi les pages sont passees, et non importees
11
+ *
12
+ * Ce fichier pourrait importer les pages depuis `App.tsx`. Les deux modules
13
+ * s'importeraient alors l'un l'autre : cela fonctionne, mais l'ordre
14
+ * d'evaluation devient une question a laquelle personne ne veut repondre le
15
+ * jour ou quelque chose s'execute au chargement du module.
16
+ *
17
+ * Les pages arrivent donc en proprietes. La dependance ne va que dans un sens —
18
+ * `App.tsx` connait le routeur, le routeur ne connait que React — et la table
19
+ * des routes reste lisible d'un coup d'oeil.
20
+ *
21
+ * @module
22
+ */
23
+
24
+ import { Outlet, Route, Router, Routes } from '@odoro-cli/libs/router'
25
+ import type { ReactElement, ReactNode } from 'react'
26
+
27
+ export { Link, useLocation } from '@odoro-cli/libs/router'
28
+
29
+ /** Les pages que le routeur place. */
30
+ export interface RouteurProps {
31
+ /** L'enveloppe commune : navigation, contenu, pied de page. */
32
+ readonly enveloppe: (contenu: ReactNode) => ReactElement
33
+ /** La page d'accueil. */
34
+ readonly accueil: ReactElement
35
+ /** La page « A propos ». */
36
+ readonly apropos: ReactElement
37
+ /** Ce qui s'affiche quand aucune route ne correspond. */
38
+ readonly introuvable: ReactElement
39
+ }
40
+
41
+ /**
42
+ * La table des routes.
43
+ *
44
+ * @example
45
+ * <Routeur
46
+ * enveloppe={(contenu) => <Coquille>{contenu}</Coquille>}
47
+ * accueil={<Accueil />}
48
+ * apropos={<APropos />}
49
+ * introuvable={<Introuvable />}
50
+ * />
51
+ */
52
+ export function Routeur({
53
+ enveloppe,
54
+ accueil,
55
+ apropos,
56
+ introuvable,
57
+ }: RouteurProps): ReactElement {
58
+ return (
59
+ <Router>
60
+ <Routes>
61
+ {/* `Outlet` marque l'endroit ou la page courante se rend : c'est ce
62
+ qui permet a la navigation et au pied de page de ne pas etre
63
+ remontes a chaque changement de route. */}
64
+ <Route path="/" element={enveloppe(<Outlet />)}>
65
+ <Route index element={accueil} />
66
+ <Route path="a-propos" element={apropos} />
67
+ <Route path="*" element={introuvable} />
68
+ </Route>
69
+ </Routes>
70
+ </Router>
71
+ )
72
+ }
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- export { buildProject, reportBuild } from './chunk-CJIUP7A3.js';
3
- import './chunk-R6PMAMXA.js';
4
- import './chunk-JMEHF3KN.js';
@@ -1,37 +0,0 @@
1
- /**
2
- * La marque : le signe et le mot.
3
- *
4
- * Meme signe que dans la version complete, dessine en SVG avec `currentColor`
5
- * pour prendre la couleur de son parent. Les bibliotheques n'ayant pas ete
6
- * retenues, la teinte vient d'une variable declaree dans `styles.css`.
7
- *
8
- * @module
9
- */
10
-
11
- import type { ReactElement } from 'react'
12
-
13
- /** Le signe seul, a la taille de la police qui l'entoure. */
14
- export function Signe({ className = '' }: { readonly className?: string }): ReactElement {
15
- return (
16
- <svg viewBox="0 0 100 100" fill="none" aria-hidden="true" className={`signe ${className}`}>
17
- <path
18
- d="M20.25 20.25H50a29.75 29.75 0 1 1-29.75 29.75Z"
19
- stroke="currentColor"
20
- strokeWidth="10.5"
21
- strokeLinejoin="miter"
22
- />
23
- </svg>
24
- )
25
- }
26
-
27
- /** Le signe et le mot. */
28
- export function Marque(): ReactElement {
29
- return (
30
- <span className="marque">
31
- <span className="marque-signe">
32
- <Signe />
33
- </span>
34
- Odoro
35
- </span>
36
- )
37
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * La cloture : une commande, et la porte de sortie vers la documentation.
3
- *
4
- * @module
5
- */
6
-
7
- import { useState, type ReactElement } from 'react'
8
-
9
- /** La commande que l'on copie le plus souvent. */
10
- const COMMANDE = 'npm i @odoro-cli/libs'
11
-
12
- /** La section de cloture. */
13
- export function Cloture(): ReactElement {
14
- const [copie, setCopie] = useState(false)
15
-
16
- return (
17
- <section className="section section-centre">
18
- <div className="contenu-etroit">
19
- <h2 className="cloture-titre">Ajoutez le systeme de style quand vous voudrez.</h2>
20
- <p className="cloture-chapeau">
21
- Les jetons, les utilitaires et les composants vivent dans un paquet a part.
22
- Ce projet tourne sans eux, et les accueille sans rien casser.
23
- </p>
24
-
25
- <div className="commande">
26
- <code>{COMMANDE}</code>
27
- <button
28
- type="button"
29
- className="bouton bouton-fantome"
30
- onClick={() => {
31
- // Le presse-papiers peut etre refuse — hors contexte sur, ou
32
- // permission retiree. L'echec ne casse pas la page : le libelle
33
- // ne change simplement pas.
34
- void navigator.clipboard
35
- ?.writeText(COMMANDE)
36
- .then(() => {
37
- setCopie(true)
38
- setTimeout(() => {
39
- setCopie(false)
40
- }, 1600)
41
- })
42
- .catch(() => undefined)
43
- }}
44
- >
45
- {copie ? 'Copie' : 'Copier'}
46
- </button>
47
- </div>
48
-
49
- <a
50
- href="https://odoro.dev/docs/installation"
51
- target="_blank"
52
- rel="noreferrer"
53
- className="lien-discret"
54
- >
55
- Lire la marche a suivre
56
- </a>
57
- </div>
58
- </section>
59
- )
60
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * Le hero : la promesse, et la fenetre du produit.
3
- *
4
- * Meme dessin que la version avec bibliotheques — titre serre, pastilles de
5
- * faits, fenetre de navigateur — en CSS ordinaire. Ce qui manque ici est
6
- * l'animation d'entree : elle venait de `@odoro-cli/libs/motion`, et il n'y a
7
- * pas lieu de la reecrire a la main dans un projet de depart.
8
- *
9
- * @module
10
- */
11
-
12
- import type { ReactElement } from 'react'
13
-
14
- import { Signe } from '@/composants/Marque'
15
-
16
- /** Trois faits, pas trois slogans. */
17
- const FAITS = ['Zero configuration', 'TypeScript strict', 'Rechargement a chaud']
18
-
19
- /** Ce que la fenetre montre. */
20
- const EXTRAIT = `export function App() {
21
- return (
22
- <h1 className="titre">
23
- Votre premiere page.
24
- </h1>
25
- )
26
- }`
27
-
28
- /** Une fenetre de navigateur miniature. */
29
- function Fenetre(): ReactElement {
30
- return (
31
- <div className="fenetre">
32
- <div className="fenetre-barre">
33
- <span className="fenetre-pastilles" aria-hidden="true">
34
- <span />
35
- <span />
36
- <span />
37
- </span>
38
- <span className="fenetre-chemin">src/App.tsx</span>
39
- </div>
40
- <pre className="fenetre-code">{EXTRAIT}</pre>
41
- </div>
42
- )
43
- }
44
-
45
- /** Le hero de la page d'accueil. */
46
- export function Hero(): ReactElement {
47
- return (
48
- <section className="hero">
49
- <div className="contenu">
50
- <span className="pastille-marque">
51
- <Signe />
52
- Genere par odoro create
53
- </span>
54
-
55
- <h1 className="hero-titre">
56
- Votre premiere page, <span className="accent">deja vivante</span>.
57
- </h1>
58
-
59
- <p className="hero-chapeau">
60
- Le serveur de developpement, la compilation et le rechargement a chaud
61
- viennent de <code>odoro</code>. Le systeme de style est le votre.
62
- </p>
63
-
64
- <div className="hero-actions">
65
- <a href="#piliers" className="bouton bouton-primaire">
66
- Voir ce qui est inclus
67
- </a>
68
- <a
69
- href="https://odoro.dev/docs"
70
- target="_blank"
71
- rel="noreferrer"
72
- className="bouton bouton-secondaire"
73
- >
74
- Documentation
75
- </a>
76
- </div>
77
-
78
- <div className="faits">
79
- {FAITS.map((fait) => (
80
- <span key={fait} className="fait">
81
- {fait}
82
- </span>
83
- ))}
84
- </div>
85
-
86
- <div className="hero-fenetre">
87
- <Fenetre />
88
- </div>
89
- </div>
90
- </section>
91
- )
92
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * Trois piliers : ce que le projet a deja sous la main.
3
- *
4
- * @module
5
- */
6
-
7
- import type { ReactElement } from 'react'
8
-
9
- /** Un pilier : un titre, une phrase. */
10
- const PILIERS: readonly { readonly titre: string; readonly texte: string }[] = [
11
- {
12
- titre: 'Compilation',
13
- texte:
14
- 'Un serveur de developpement a rechargement a chaud, et une compilation de production.',
15
- },
16
- {
17
- titre: 'TypeScript',
18
- texte: 'Types stricts, alias de chemins, et verification sans emission.',
19
- },
20
- {
21
- titre: 'A vous de jouer',
22
- texte: 'Aucun systeme de style impose : le votre prend toute la place.',
23
- },
24
- ]
25
-
26
- /** La section des piliers. */
27
- export function Piliers(): ReactElement {
28
- return (
29
- <section id="piliers" className="section">
30
- <div className="contenu">
31
- <h2 className="sur-titre">Ce qui est deja la</h2>
32
- <div className="grille">
33
- {PILIERS.map((pilier, index) => (
34
- <article key={pilier.titre} className="carte">
35
- <span className="carte-numero">{String(index + 1).padStart(2, '0')}</span>
36
- <h3>{pilier.titre}</h3>
37
- <p>{pilier.texte}</p>
38
- </article>
39
- ))}
40
- </div>
41
- </div>
42
- </section>
43
- )
44
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * La marque : le signe et le mot.
3
- *
4
- * ## Pourquoi le signe est dessine ici, et non charge
5
- *
6
- * Un fichier dans `public/` serait une image de plus a telecharger, et surtout
7
- * une image d'une seule couleur. Le signe est dessine en SVG dans le flux, avec
8
- * `currentColor` : il prend la couleur de son parent, donc la teinte de marque
9
- * en clair comme en sombre, sans qu'on ait deux fichiers a tenir.
10
- *
11
- * @module
12
- */
13
-
14
- import type { ReactElement } from 'react'
15
-
16
- /** Le signe seul, a la taille de la police qui l'entoure. */
17
- export function Signe({ className = '' }: { readonly className?: string }): ReactElement {
18
- return (
19
- <svg
20
- viewBox="0 0 100 100"
21
- fill="none"
22
- aria-hidden="true"
23
- className={className}
24
- // La taille est en style, et non en classe : elle suit la police qui
25
- // entoure le signe, et une classe utilitaire a valeur arbitraire n'est
26
- // emise que si le compilateur l'a vue passer. Absente, elle laisserait
27
- // un SVG sans dimensions — donc invisible, sans rien pour le signaler.
28
- style={{ width: '1.15em', height: '1.15em', flexShrink: 0 }}
29
- >
30
- <path
31
- d="M20.25 20.25H50a29.75 29.75 0 1 1-29.75 29.75Z"
32
- stroke="currentColor"
33
- strokeWidth="10.5"
34
- strokeLinejoin="miter"
35
- />
36
- </svg>
37
- )
38
- }
39
-
40
- /** Le signe et le mot, tels qu'ils vont dans une barre de navigation. */
41
- export function Marque(): ReactElement {
42
- return (
43
- <span className="o-inline-flex o-items-center o-gap-2 o-font-semibold o-tracking-tight">
44
- <span className="o-text-brand-500">
45
- <Signe />
46
- </span>
47
- Odoro
48
- </span>
49
- )
50
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * Une seconde page, pour montrer le routeur a l'oeuvre.
3
- *
4
- * @module
5
- */
6
-
7
- import { Reveal } from '@odoro-cli/libs/motion'
8
- import type { ReactElement } from 'react'
9
-
10
- /** Ce que le projet embarque, tel qu'il a ete cree. */
11
- const REPERES: readonly { readonly cle: string; readonly valeur: string }[] = [
12
- { cle: 'Moteur', valeur: 'odoro — serveur de developpement et compilation' },
13
- { cle: 'Styles', valeur: '@odoro-cli/libs — jetons, utilitaires, composants' },
14
- { cle: 'Registre', valeur: 'register.odoro.dev — copie dans votre projet' },
15
- ]
16
-
17
- /** Page « A propos ». */
18
- export function About(): ReactElement {
19
- return (
20
- <section className="o-px-6 o-py-24">
21
- <div className="o-mx-auto o-w-full o-max-w-3xl">
22
- <Reveal>
23
- <h1 className="o-text-4xl o-font-bold o-tracking-tight">A propos</h1>
24
- <p className="o-mt-4 o-text-zinc-500 dark:o-text-zinc-400">
25
- Cette page existe pour montrer le routeur : la navigation ne recharge
26
- rien, et la transition est celle du navigateur.
27
- </p>
28
-
29
- <dl className="o-mt-10 o-divide-y o-divide-zinc-200 dark:o-divide-zinc-800 o-border-t o-border-zinc-200 dark:o-border-zinc-800">
30
- {REPERES.map((repere) => (
31
- <div key={repere.cle} className="o-flex o-flex-col md:o-flex-row o-gap-1 md:o-gap-6 o-py-4">
32
- <dt className="o-w-32 o-shrink-0 o-text-sm o-font-medium">{repere.cle}</dt>
33
- <dd className="o-m-0 o-text-sm o-text-zinc-500 dark:o-text-zinc-400">
34
- {repere.valeur}
35
- </dd>
36
- </div>
37
- ))}
38
- </dl>
39
- </Reveal>
40
- </div>
41
- </section>
42
- )
43
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * La page d'accueil : le fond, puis les sections.
3
- *
4
- * Elle ne fait que les mettre bout a bout. Chaque section vit dans son propre
5
- * fichier, si bien qu'en retirer une, en ajouter une ou en changer l'ordre se
6
- * lit ici en une ligne.
7
- *
8
- * @module
9
- */
10
-
11
- import type { ReactElement } from 'react'
12
-
13
- import { Cloture } from '@/sections/Cloture'
14
- import { Fond } from '@/sections/Fond'
15
- import { Hero } from '@/sections/Hero'
16
- import { Piliers } from '@/sections/Piliers'
17
-
18
- /** Page d'accueil. */
19
- export function Home(): ReactElement {
20
- return (
21
- <>
22
- <Fond />
23
- <Hero />
24
- <Piliers />
25
- <Cloture />
26
- </>
27
- )
28
- }
@@ -1,14 +0,0 @@
1
- import { Link } from '@odoro-cli/libs/router'
2
-
3
- /** Page affichee pour toute route inconnue. */
4
- export function NotFound() {
5
- return (
6
- <div className="o-flex o-flex-col o-gap-4" role="alert">
7
- <h1 className="o-text-3xl o-font-bold">404</h1>
8
- <p className="o-text-zinc-500 dark:o-text-zinc-400">Cette page n existe pas.</p>
9
- <p>
10
- <Link to="/">Retour a l accueil</Link>
11
- </p>
12
- </div>
13
- )
14
- }
@@ -1,69 +0,0 @@
1
- /**
2
- * La cloture : une commande, et la porte de sortie vers la documentation.
3
- *
4
- * Une page d'accueil qui s'arrete sur sa derniere carte laisse le visiteur
5
- * sans geste suivant. Celle-ci se termine par ce qu'il y a a taper.
6
- *
7
- * @module
8
- */
9
-
10
- import { Reveal } from '@odoro-cli/libs/motion'
11
- import { buttonClasses } from '@odoro-cli/libs/ui'
12
- import { useState, type ReactElement } from 'react'
13
-
14
- /** La commande que l'on copie le plus souvent. */
15
- const COMMANDE = 'odoro add text/count-up'
16
-
17
- /** La section de cloture. */
18
- export function Cloture(): ReactElement {
19
- const [copie, setCopie] = useState(false)
20
-
21
- return (
22
- <section className="o-border-t o-border-zinc-200 dark:o-border-zinc-800 o-px-6 o-py-24">
23
- <div className="o-mx-auto o-w-full o-max-w-3xl o-text-center">
24
- <Reveal>
25
- <h2 className="o-text-3xl o-font-bold o-tracking-tight md:o-text-4xl">
26
- Ajoutez un composant, sans quitter le terminal.
27
- </h2>
28
- <p className="o-mx-auto o-mt-4 o-max-w-prose o-text-zinc-500 dark:o-text-zinc-400">
29
- Le registre copie le code dans votre projet. Il vous appartient : on le
30
- lit, on le modifie, il ne se met pas a jour dans votre dos.
31
- </p>
32
-
33
- <div className="o-mx-auto o-mt-8 o-flex o-max-w-md o-items-center o-gap-2 o-rounded-xl o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-bg-zinc-50 dark:o-bg-zinc-900 o-p-2 o-pl-4">
34
- <code className="o-flex-1 o-text-left o-font-mono o-text-sm">{COMMANDE}</code>
35
- <button
36
- type="button"
37
- className={buttonClasses({ tone: 'ghost', size: 'sm' })}
38
- onClick={() => {
39
- // Le presse-papiers peut etre refuse — hors contexte sur, ou
40
- // permission retiree. L'echec ne doit pas casser la page : le
41
- // libelle ne change simplement pas.
42
- void navigator.clipboard
43
- ?.writeText(COMMANDE)
44
- .then(() => {
45
- setCopie(true)
46
- setTimeout(() => {
47
- setCopie(false)
48
- }, 1600)
49
- })
50
- .catch(() => undefined)
51
- }}
52
- >
53
- {copie ? 'Copie' : 'Copier'}
54
- </button>
55
- </div>
56
-
57
- <a
58
- href="https://odoro.dev/docs/registry"
59
- target="_blank"
60
- rel="noreferrer"
61
- className="o-mt-6 o-inline-block o-text-sm o-text-zinc-500 dark:o-text-zinc-400 hover:o-text-brand-500"
62
- >
63
- Parcourir le catalogue
64
- </a>
65
- </Reveal>
66
- </div>
67
- </section>
68
- )
69
- }