uifolio 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -4,135 +4,149 @@
4
4
  [![license](https://img.shields.io/npm/l/uifolio.svg)](https://www.npmjs.com/package/uifolio)
5
5
  [![node](https://img.shields.io/node/v/uifolio.svg)](https://www.npmjs.com/package/uifolio)
6
6
 
7
- Ma librairie de composants et animations : le code vit dans le projet, se copie et se modifie.
7
+ My component and animation library: the code lives in your project, gets copied and edited.
8
8
 
9
- Le paquet npm `uifolio` contient la CLI, les 14 composants et leurs 9 helpers — rien d'autre.
10
- Le site de démonstration (Vite, animations, playgrounds) vit dans le dépôt du projet et ne
11
- fait pas partie du paquet.
9
+ The `uifolio` npm package contains the CLI, the 14 components and their 9 helpers — nothing else.
10
+ The demo site (Vite, animations, playgrounds) lives in the project repository and is not part
11
+ of the package.
12
12
 
13
- ## Démarrage
13
+ ## Getting started
14
14
 
15
15
  ```bash
16
16
  npm install
17
- npm run dev # serveur de démo (http://localhost:5173)
18
- npm run build # typecheck + build de production
19
- npm run preview # sert le build
17
+ npm run dev # demo server (http://localhost:5173)
18
+ npm run build # typecheck + production build
19
+ npm run preview # serve the build
20
20
  npm run lint # oxlint
21
- npm test # tests du CLI (copie, couleur, manifeste npm)
21
+ npm test # CLI tests (copy, color, npm manifest)
22
22
  ```
23
23
 
24
- Routes (hash) :
24
+ Routes (hash):
25
25
 
26
- - `#/` — page d'accueil (identité UIFolio)
27
- - `#/docs` — introduction, installation, thème
28
- - `#/components` — catalogue documenté
29
- - `#/components/video` — playground VideoPlayer
26
+ - `#/` — home page (UIFolio identity)
27
+ - `#/docs` — introduction, installation, theme
28
+ - `#/components` — documented catalog
29
+ - `#/components/video` — VideoPlayer playground
30
30
  - `#/components/calendar`, `/time`, `/audio`, `/stories`, `/editor`, `/upload`,
31
31
  `/shortcuts`, `/panels`, `/choice`, `/color-picker`, `/icon-picker`
32
- - `#/animations/otp` — démo OTP animée
33
- - `#/animations/accordeon-horizontal`, `/storytelling`, `/empilement`,
34
- `/texte-progressif`, `/images-fondues` — animations au scroll et fondus d’images
32
+ - `#/animations/otp` — animated OTP demo
33
+ - `#/animations/horizontal-accordion`, `/storytelling`, `/stacking`,
34
+ `/scroll-text`, `/image-crossfade` — scroll animations and crossfades
35
35
 
36
- Thème **clair/sombre** : bouton dans le header, choix persisté dans
37
- `localStorage (uifolio-theme)`, initialisation avant le premier rendu (aucun flash),
38
- défaut = préférence système.
36
+ **Light/dark** theme: button in the header, choice persisted in
37
+ `localStorage (uifolio-theme)`, initialized before the first paint (no flash),
38
+ default = system preference.
39
39
 
40
- **Personnalisation globale** : le panneau de droite (ou l'accordéon sur mobile)
41
- règle la couleur `--primary` et l'arrondi `--radius` pour tous les composants.
42
- Le sélecteur avancé propose saturation/luminosité, teinte, alpha, pipette, canaux
43
- RGB, presets et formats HEX/RGB/HSL/RGBA. Le choix est persisté dans
44
- `localStorage (uifolio:design)` et repris dans les commandes CLI copiées
45
- (`--color`, `--radius`).
40
+ **Global customization**: the right panel (or the accordion on mobile) sets the
41
+ `--primary` color and the `--radius` for every component. The advanced picker offers
42
+ saturation/lightness, hue, alpha, eyedropper, RGB channels, presets and HEX/RGB/HSL/RGBA
43
+ formats. The choice is persisted in `localStorage (uifolio:design)` and reused in the
44
+ copied CLI commands (`--color`, `--radius`).
46
45
 
47
- ## Utiliser les composants dans un autre projet (CLI)
46
+ ## Using the components in another project (CLI)
48
47
 
49
- Comme shadcn : tu installes les fichiers dans **ton** projet, avec leurs helpers,
50
- et l'alias est réécrit automatiquement.
48
+ Like shadcn: you install the files into **your** project, with their helpers,
49
+ and the alias is rewritten automatically.
51
50
 
52
51
  ```bash
53
- # depuis n'importe quel projet, via le paquet npm
52
+ # from any project, through the npm package
54
53
  npx uifolio@latest add calendar --color "#10B981" --radius 6
55
54
  pnpm dlx uifolio@latest add video-player otp
56
55
 
57
- # depuis la racine du projet qui contient UIfolio
56
+ # from the root of the project that contains UIfolio
58
57
  npm run uifolio -- list
59
- npm run uifolio -- init --cwd ../mon-projet
60
- npm run uifolio -- add video-player calendar otp --cwd ../mon-projet
58
+ npm run uifolio -- init --cwd ../my-project
59
+ npm run uifolio -- add video-player calendar otp --cwd ../my-project
61
60
 
62
- # ou directement, exécutable via « bin »
63
- node cli/uifolio.mjs add otp --cwd ../mon-projet
61
+ # or directly, through the « bin »
62
+ node cli/uifolio.mjs add otp --cwd ../my-project
64
63
  node cli/uifolio.mjs add file-upload --alias "#lib" --dry-run # simulate
65
64
  ```
66
65
 
67
- | Commande | Rôle |
66
+ | Command | Purpose |
68
67
  | --- | --- |
69
- | `uifolio list` | les composants disponibles |
70
- | `uifolio init` | écrit `uifolio.json` (dir, lib, alias) dans le projet cible |
71
- | `uifolio add <noms...>` | copie composant **+ dépendances transitives** (helpers `@/lib/*`) |
72
-
73
- Options : `--cwd <dir>`, `--dir <chemin>` (défaut `src/components/ui`),
74
- `--lib <chemin>` (défaut `src/lib`), `--alias <alias>` (défaut `@`),
75
- `--force` (écraser), `--dry-run` (simuler), `--color <hex>` et `--radius <px>`.
76
-
77
- `--color` écrit la couleur **directement dans les fichiers copiés** : les classes
78
- Tailwind (`bg-primary`, `border-primary/50`, `ring-primary/40`…) et les `var(--primary)`
79
- deviennent la valeur hex (`bg-[#10b981]`, `border-[#10b981]/50`…). Le composant garde donc
80
- la couleur choisie même si le projet cible ne définit pas `--primary`. `--radius` (seul)
81
- écrit `uifolio-theme.css` avec l'arrondi (et les tokens de couleur par défaut).
82
-
83
- À la fin, la CLI affiche les dépendances npm détectées, par ex. :
68
+ | `uifolio list` | the available components |
69
+ | `uifolio init` | writes `uifolio.json` (dir, lib, alias) in the target project |
70
+ | `uifolio add <names...>` | copies the component **+ transitive dependencies** (helpers `@/lib/*`) |
71
+
72
+ Options: `--cwd <dir>`, `--dir <path>` (default `src/components/ui`),
73
+ `--lib <path>` (default `src/lib`), `--alias <alias>` (default `@`),
74
+ `--page <path>` (created if missing), `--interactive` (force the questions),
75
+ `--force` (overwrite), `--dry-run` (simulate), `--color <hex>` and `--radius <px>`.
76
+
77
+ `add` does not stop at the copy. Without `--dir` or `--page`, the CLI asks for the
78
+ components folder, then:
79
+
80
+ 1. *Do you already have a file ready to receive the component (Calendar)?*
81
+ `y` → *In which file should the component (Calendar) be copied?* (format
82
+ `src/pages/calendar.tsx`): the import and `<Component />` are added without
83
+ duplication, and the file is created when missing;
84
+ 2. `n` → *Would you like to create a new folder and an import file?*
85
+ `y` → *Please specify the new path*: the folder and the file are created;
86
+ `n` → install only, no file is touched.
87
+
88
+ Answering `-` to a path question installs the component without creating or
89
+ modifying any page.
90
+
91
+ Colors go through **Tailwind CSS 4 tokens**: the CLI writes
92
+ `uifolio-theme.css` (`:root`, `.dark`, then `@theme inline`, which exposes
93
+ `--color-primary`, `--color-muted-foreground`, …) and imports it automatically
94
+ into the project's global CSS (the one with `@import 'tailwindcss'`). The
95
+ components therefore keep `bg-primary`, `border-primary/50`, `ring-primary/40`:
96
+ no hard-coded colors. `--color` changes `--primary` in the theme and `--radius`
97
+ adds `--radius` to it. When no global Tailwind CSS is found, the file is written
98
+ at the root and the import to add is printed.
99
+
100
+ At the end, the CLI prints the detected npm dependencies, for example:
84
101
 
85
102
  ```bash
86
103
  npm install clsx tailwind-merge lucide-react hls.js
87
104
  ```
88
105
 
89
- Le projet cible doit fournir les tokens du thème (`bg-background`, `border`, `text-muted-foreground`…) :
90
- copie les variables CSS de `src/index.css` (`:root`, `.dark`, `@theme`) si ton projet ne les a pas.
91
-
92
- ## Thèmes clair/sombre
106
+ ## Light/dark themes
93
107
 
94
- - Tokens CSS : `:root` (clair) et `.dark` (sombre) dans `src/index.css`,
95
- branchés à Tailwind via `@theme inline` + `@custom-variant dark`.
96
- - `useTheme()` (`src/lib/use-theme.ts`) : `{ theme, setTheme, toggle }`,
97
- persistance `localStorage('uifolio-theme')`.
98
- - `<ThemeToggle />` (`src/components/theme-toggle.tsx`) : bouton Sun/Moon.
99
- - Jamais de couleur figée dans les composants : tout passe par
108
+ - CSS tokens: `:root` (light) and `.dark` (dark) in `src/index.css`,
109
+ wired to Tailwind through `@theme inline` + `@custom-variant dark`.
110
+ - `useTheme()` (`src/lib/use-theme.ts`): `{ theme, setTheme, toggle }`,
111
+ persistence in `localStorage('uifolio-theme')`.
112
+ - `<ThemeToggle />` (`src/components/theme-toggle.tsx`): Sun/Moon button.
113
+ - No fixed color anywhere in the components: everything goes through
100
114
  `background`, `foreground`, `card`, `muted`, `border`, `primary`, `accent`, `ring`.
101
115
 
102
- ## Upload réel
116
+ ## Real upload
103
117
 
104
- Le `FileUpload` ne connaît pas ton backend : il appelle
105
- `uploader(file, onProgress) => Promise<url>` et affiche progression, succès et lien
106
- vers l'URL publique renvoyée.
118
+ `FileUpload` knows nothing about your backend: it calls
119
+ `uploader(file, onProgress) => Promise<url>` and shows progress, success and a link
120
+ to the returned public URL.
107
121
 
108
- La démo utilise `createUploader()` (`src/lib/uploader.ts`) :
122
+ The demo uses `createUploader()` (`src/lib/uploader.ts`):
109
123
 
110
- 1. `POST /api/upload/sign` → `{ key, uploadUrl, publicUrl }` (URL signée) ;
111
- 2. `PUT` du fichier en **XHR avec progression** ;
112
- 3. **reprise automatique** (2 retries, backoff) en réutilisant la même URL signée ;
113
- un 4xx ne réessaie pas (refus définitif).
124
+ 1. `POST /api/upload/sign` → `{ key, uploadUrl, publicUrl }` (signed URL);
125
+ 2. `PUT` of the file through **XHR with progress**;
126
+ 3. **automatic retry** (2 retries, backoff) reusing the same signed URL;
127
+ a 4xx does not retry (final refusal).
114
128
 
115
- Côté serveur, le plugin `upload-plugin.ts` (branché dans `vite.config.ts`) :
129
+ On the server side, the `upload-plugin.ts` plugin (wired in `vite.config.ts`):
116
130
 
117
- - **Mode local (défaut, zéro config)** : écrit dans `public/uploads/` et sert
118
- `/uploads/<key>` ;
119
- - **Mode S3 compatible** (AWS S3, Cloudflare R2, MinIO) : si `S3_ENDPOINT`,
120
- `S3_BUCKET`, `S3_ACCESS_KEY_ID` et `S3_SECRET_ACCESS_KEY` sont définis, le serveur
121
- renvoie une URL PUT signée **SigV4** (implémentée avec `node:crypto`, aucun SDK)
122
- et le navigateur envoie directement au bucket.
131
+ - **Local mode (default, zero config)**: writes into `public/uploads/` and serves
132
+ `/uploads/<key>`;
133
+ - **S3-compatible mode** (AWS S3, Cloudflare R2, MinIO): when `S3_ENDPOINT`,
134
+ `S3_BUCKET`, `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY` are set, the server
135
+ returns a **SigV4** signed PUT URL (implemented with `node:crypto`, no SDK)
136
+ and the browser uploads straight to the bucket.
123
137
 
124
- Configuration dans `.env` — voir `.env.example`.
138
+ Configuration in `.env` — see `.env.example`.
125
139
 
126
- Brancher son propre uploader :
140
+ Plugging in your own uploader:
127
141
 
128
142
  ```tsx
129
143
  const upload = createUploader({ signEndpoint: '/api/upload/sign' })
130
144
 
131
- // ou ton propre backend :
145
+ // or your own backend:
132
146
  const upload = async (file: File, onProgress: (v: number) => void) => {
133
147
  const form = new FormData()
134
148
  form.append('file', file)
135
- const res = await fetch('/api/ton-endpoint', { method: 'POST', body: form })
149
+ const res = await fetch('/api/your-endpoint', { method: 'POST', body: form })
136
150
  const { url } = await res.json()
137
151
  return url
138
152
  }
@@ -143,29 +157,29 @@ const upload = async (file: File, onProgress: (v: number) => void) => {
143
157
  ## Stack
144
158
 
145
159
  - Vite 8 · React 19 · TypeScript
146
- - Tailwind CSS 4 (tokens CSS, aucune config JS)
147
- - `lucide-react` pour les icônes, `clsx` + `tailwind-merge` pour `cn()`
148
- - `hls.js` importé **dynamiquement**, uniquement quand un flux `.m3u8` est lu
149
- - Aucune autre dépendance : éditeur, calendrier, time picker, panneaux, palette,
150
- OTP et signature SigV4 sont maison
160
+ - Tailwind CSS 4 (CSS tokens, no JS config)
161
+ - `lucide-react` for icons, `clsx` + `tailwind-merge` for `cn()`
162
+ - `hls.js` imported **dynamically**, only when a `.m3u8` stream is played
163
+ - No other dependency: editor, calendar, time picker, panels, palette,
164
+ OTP and SigV4 signing are home-made
151
165
 
152
- ## Composants
166
+ ## Components
153
167
 
154
- | Composant | Fichier | Ce qu'il gère |
168
+ | Component | File | What it handles |
155
169
  | --- | --- | --- |
156
- | VideoPlayer | `src/components/ui/video-player.tsx` | URL, File/Blob, qualités, HLS, YouTube/Vimeo, sous-titres VTT, contrôles et lecture |
157
- | Calendar | `src/components/ui/calendar.tsx` | date simple, plage start–end, saisie manuelle, min/max, jours désactivés |
158
- | TimePicker | `src/components/ui/time-picker.tsx` | 12/24 h, secondes, pas, saisie et flèches |
159
- | AudioPlayer | `src/components/ui/audio-player.tsx` | variantes carte/compact, seek, volume, vitesse, égaliseur |
160
- | Stories | `src/components/ui/stories.tsx` | viewer Instagram : images, vidéos, slides texte, pause, progression |
161
- | RichTextEditor | `src/components/ui/rich-text-editor.tsx` | WYSIWYG `contentEditable`, toolbar, HTML en sortie |
162
- | FileUpload | `src/components/ui/file-upload.tsx` | drag & drop, validation, aperçus, progression, URL publique, retrait |
163
- | CommandMenu | `src/components/ui/command-menu.tsx` | palette Ctrl/⌘ + K, recherche, clavier |
164
- | ResizablePanels | `src/components/ui/resizable.tsx` | groupes horizontal/vertical, poignées, min-size, clavier |
165
- | OtpInput | `src/components/ui/otp-input.tsx` | 4 champs, collage, rotate + coche de succès |
166
- | ColorPicker | `src/components/ui/color-picker.tsx` | panneau avancé, saturation/luminosité, teinte, alpha, pipette, canaux RGB, presets, HEX/RGB/HSL/RGBA |
167
-
168
- Helpers copiables (la CLI les suit automatiquement) : `src/lib/utils.ts`,
170
+ | VideoPlayer | `src/components/ui/video-player.tsx` | URL, File/Blob, qualities, HLS, YouTube/Vimeo, VTT subtitles, controls and playback |
171
+ | Calendar | `src/components/ui/calendar.tsx` | single date, start–end range, manual input, min/max, disabled days |
172
+ | TimePicker | `src/components/ui/time-picker.tsx` | 12/24 h, seconds, step, typing and arrow keys |
173
+ | AudioPlayer | `src/components/ui/audio-player.tsx` | card/compact variants, seek, volume, speed, equalizer |
174
+ | Stories | `src/components/ui/stories.tsx` | Instagram viewer: images, videos, text slides, pause, progress |
175
+ | RichTextEditor | `src/components/ui/rich-text-editor.tsx` | `contentEditable` WYSIWYG, toolbar, HTML output |
176
+ | FileUpload | `src/components/ui/file-upload.tsx` | drag & drop, validation, previews, progress, public URL, removal |
177
+ | CommandMenu | `src/components/ui/command-menu.tsx` | Ctrl/⌘ + K palette, search, keyboard |
178
+ | ResizablePanels | `src/components/ui/resizable.tsx` | horizontal/vertical groups, handles, min-size, keyboard |
179
+ | OtpInput | `src/components/ui/otp-input.tsx` | 4 fields, paste, rotate + success check |
180
+ | ColorPicker | `src/components/ui/color-picker.tsx` | advanced panel, saturation/lightness, hue, alpha, eyedropper, RGB channels, presets, HEX/RGB/HSL/RGBA |
181
+
182
+ Copyable helpers (the CLI follows them automatically): `src/lib/utils.ts`,
169
183
  `src/lib/video.ts`, `src/lib/vtt.ts`, `src/lib/date.ts`, `src/lib/uploader.ts`, `src/lib/color.ts`,
170
184
  `src/lib/use-theme.ts`, `src/lib/use-hotkeys.ts`, `src/lib/use-reduced-motion.ts`.
171
185
 
@@ -183,27 +197,27 @@ Helpers copiables (la CLI les suit automatiquement) : `src/lib/utils.ts`,
183
197
  loop
184
198
  lazy
185
199
  subtitles={[
186
- { src: '/fr.vtt', label: 'Français', lang: 'fr', default: true },
200
+ { src: '/fr.vtt', label: 'French', lang: 'fr', default: true },
187
201
  { src: '/en.vtt', label: 'English', lang: 'en' },
188
202
  ]}
189
203
  subtitleClassName="bottom-20"
190
- overlay={(state) => <MonBadge playing={state.playing} />}
204
+ overlay={(state) => <MyBadge playing={state.playing} />}
191
205
  />
192
206
  ```
193
207
 
194
- - Sources : URL directe, `File`/`Blob`, tableau de qualités (`{ src, quality }`), `.m3u8`,
195
- YouTube et Vimeo.
196
- - Pour les embeds, l'iframe n'est injectée qu'au clic (façade + miniature) et les contrôles
197
- natifs sont masqués : la barre du composant pilote le lecteur de la plateforme
198
- (progression, temps, volume, vitesse, lecture/pause) via `postMessage`.
199
- - `ref` expose `play`, `pause`, `seek`, `mute`, `setVolume`, `setPlaybackRate`,
208
+ - Sources: direct URL, `File`/`Blob`, quality list (`{ src, quality }`), `.m3u8`,
209
+ YouTube and Vimeo.
210
+ - For embeds, the iframe is only injected on click (facade + thumbnail) and native
211
+ controls are hidden: the component bar drives the platform player
212
+ (progress, time, volume, speed, play/pause) through `postMessage`.
213
+ - `ref` exposes `play`, `pause`, `seek`, `mute`, `setVolume`, `setPlaybackRate`,
200
214
  `requestFullscreen`, `getVideo`.
201
- - La barre de progression et le volume suivent l'arrondi global `--radius` (poignées
202
- comprises), comme le reste du catalogue.
203
- - Optimisations : chargement lazy, pause hors écran, `preload` maîtrisé, `hls.js` en chunk séparé,
204
- object URLs révoquées, changement de qualité sans perdre la position.
205
- - Sous-titres : parsing VTT tolérant, pistes fichier ou URL, style libre via
206
- `subtitleClassName`, menu CC.
215
+ - The progress bar and the volume follow the global `--radius` (handles
216
+ included), like the rest of the catalog.
217
+ - Optimizations: lazy loading, pause off-screen, controlled `preload`, `hls.js` in a separate
218
+ chunk, revoked object URLs, quality switch without losing the position.
219
+ - Subtitles: tolerant VTT parsing, file or URL tracks, free styling through
220
+ `subtitleClassName`, CC menu.
207
221
 
208
222
  ## Calendar / TimePicker
209
223
 
@@ -216,20 +230,20 @@ const [date, setDate] = useState<CalendarValue>(null)
216
230
  <TimePicker value={time} onChange={setTime} withSeconds step={15} hour12 />
217
231
  ```
218
232
 
219
- La saisie manuelle accepte `jj/mm/aaaa`, `jj-mm-aaaa` et `aaaa-mm-jj`.
233
+ Manual input accepts `dd/mm/yyyy`, `dd-mm-yyyy` and `yyyy-mm-dd`.
220
234
 
221
235
  ## Audio / Stories
222
236
 
223
237
  ```tsx
224
- <AudioPlayer src="/audio.wav" title="Titre" cover="/cover.svg" accent="#facc15" />
225
- <AudioPlayer variant="compact" src="/audio.wav" title="Titre" />
238
+ <AudioPlayer src="/audio.wav" title="Title" cover="/cover.svg" accent="#facc15" />
239
+ <AudioPlayer variant="compact" src="/audio.wav" title="Title" />
226
240
 
227
241
  <Stories items={stories} open={open} onOpenChange={setOpen} duration={5000} />
228
242
  ```
229
243
 
230
- Une story peut être une image, une vidéo ou un slide texte avec son propre fond et sa durée.
244
+ A story can be an image, a video or a text slide with its own background and duration.
231
245
 
232
- ## Éditeur / Upload / Raccourcis / Panneaux / OTP
246
+ ## Editor / Upload / Shortcuts / Panels / OTP
233
247
 
234
248
  ```tsx
235
249
  <RichTextEditor value={html} onChange={setHtml} toolbar />
@@ -245,61 +259,61 @@ useHotkeys({ 'mod+k': () => setOpen(true) })
245
259
  <ResizablePanel defaultSize={66} minSize={25}>…</ResizablePanel>
246
260
  </ResizablePanelGroup>
247
261
 
248
- <OtpInput length={4} value={code} onChange={setCode} onComplete={valider} />
262
+ <OtpInput length={4} value={code} onChange={setCode} onComplete={validate} />
249
263
  ```
250
264
 
251
- L'OTP aligne de 4 à 8 champs, forme un cercle à la validation puis affiche une coche.
252
- L'animation respecte `prefers-reduced-motion`.
265
+ The OTP aligns 4 to 8 fields, forms a circle on validation and then shows a check.
266
+ The animation respects `prefers-reduced-motion`.
253
267
 
254
268
  ## Animations
255
269
 
256
- Chaque animation a sa propre page documentée : `#/animations/otp` pour l'OTP, puis
257
- `#/animations/accordeon-horizontal` et `#/animations/storytelling` pour les cartes
258
- horizontales, et `#/animations/empilement` pour les cartes qui s'empilent au scroll avec
259
- une couleur de texte pilotée par la progression. Les couleurs suivent `--primary`. Le
260
- nombre de cartes se règle de 4 à 8 directement dans chaque aperçu. Ces démos autonomes ne
261
- sont pas installables via la CLI.
270
+ Every animation has its own documented page: `#/animations/otp` for the OTP, then
271
+ `#/animations/horizontal-accordion` and `#/animations/storytelling` for the
272
+ horizontal cards, and `#/animations/stacking` for the cards that stack on scroll with
273
+ a text color driven by progress. Colors follow `--primary`. The number of cards is set
274
+ from 4 to 8 right in each preview. These stand-alone demos are not installable
275
+ through the CLI.
262
276
 
263
- ## Publier le paquet
277
+ ## Publishing the package
264
278
 
265
- Le paquet publié ne contient que la CLI, les composants et les helpers (`files`) : aucune
266
- dépendance d'exécution, React et Tailwind restent en `devDependencies` pour ne pas les
267
- installer chez ceux qui lancent `npx uifolio`.
279
+ The published package only contains the CLI, the components and the helpers (`files`): no
280
+ runtime dependency, React and Tailwind stay in `devDependencies` so they are not installed
281
+ by people running `npx uifolio`.
268
282
 
269
283
  ```bash
270
- npm login # une fois par machine
271
- npm profile enable-2fa auth-and-writes # npm refuse de publier sans 2FA
272
- npm publish --otp 123456 # prepublishOnly vérifie cli/uifolio.mjs
284
+ npm login # once per machine
285
+ npm profile enable-2fa auth-and-writes # npm refuses to publish without 2FA
286
+ npm publish --otp 123456 # prepublishOnly checks cli/uifolio.mjs
273
287
  ```
274
288
 
275
- Un jeton *granular* avec « Bypass 2FA » fonctionne aussi, mais npm restreint
276
- progressivement ces jetons pour la publication : la 2FA + `--otp` est la voie sûre.
289
+ A *granular* token with “Bypass 2FA” also works, but npm is progressively restricting
290
+ these tokens for publishing: 2FA + `--otp` is the safe path.
277
291
 
278
- Ensuite `npx uifolio@latest add <composant>` et `pnpm dlx uifolio@latest add <composant>`
279
- fonctionnent.
292
+ Then `npx uifolio@latest add <component>` and `pnpm dlx uifolio@latest add <component>`
293
+ work.
280
294
 
281
- ### Publication automatique (GitHub Actions)
295
+ ### Automatic publishing (GitHub Actions)
282
296
 
283
- `.github/workflows/publish.yml` publie sur npm à chaque tag `v*` : il vérifie que le tag
284
- correspond à la version de `package.json`, installe avec `npm ci`, lance `oxlint`,
285
- `npm pack --dry-run` et `npm test`, puis publie via **trusted publishing** (OIDC, aucun
286
- token stocké). Un second job crée ensuite la **GitHub Release** du tag avec les notes
287
- générées à partir des commits.
297
+ `.github/workflows/publish.yml` publishes to npm on every `v*` tag: it checks that the tag
298
+ matches the `package.json` version, installs with `npm ci`, runs `oxlint`,
299
+ `npm pack --dry-run` and `npm test`, then publishes through **trusted publishing** (OIDC, no
300
+ stored token). A second job then creates the tag's **GitHub Release** with notes generated
301
+ from the commits.
288
302
 
289
303
  ```bash
290
304
  npm version patch # 0.3.0 → 0.3.1, commit + tag v0.3.1
291
- git push --follow-tags # déclenche le workflow
305
+ git push --follow-tags # triggers the workflow
292
306
  ```
293
307
 
294
- Prérequis, une seule fois :
308
+ Prerequisites, once:
295
309
 
296
- 1. première publication manuelle (ci-dessus) — le paquet doit exister sur npm ;
297
- 2. npmjs.com → `uifolio` → Settings → **Trusted publishing** → GitHub Actions :
298
- organisation `Barronakn`, dépôt `UIfolio`, workflow `publish.yml`,
299
- action « npm publish » autorisée ;
300
- 3. une configuration neuve doit réussir sa première publication **sous 2 jours**,
301
- sinon elle expire et il faut la recréer.
310
+ 1. first manual publication (above) — the package must exist on npm;
311
+ 2. npmjs.com → `uifolio` → Settings → **Trusted publishing** → GitHub Actions:
312
+ organization `Barronakn`, repository `UIfolio`, workflow `publish.yml`,
313
+ “npm publish” action allowed;
314
+ 3. a new configuration must complete its first publication **within 2 days**,
315
+ otherwise it expires and has to be recreated.
302
316
 
303
- Le workflow doit être présent sur le commit taggé. Après activation, tu peux durcir le
304
- paquet avec Settings → Publishing access → « Require two-factor authentication and
305
- disallow tokens ».
317
+ The workflow must be present on the tagged commit. Once enabled, you can harden the
318
+ package with Settings → Publishing access → “Require two-factor authentication and
319
+ disallow tokens”.