uifolio 0.5.1 → 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,142 +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
- `--page <chemin>` (créé s'il n'existe pas), `--interactive` (forcer les questions),
76
- `--force` (écraser), `--dry-run` (simuler), `--color <hex>` et `--radius <px>`.
77
-
78
- `add` ne s'arrête pas à la copie : sans `--dir` ni `--page`, la CLI demande
79
- **dans quel dossier** copier les composants, puis **dans quel fichier** les
80
- utiliser. La page est créée si elle manque ; si elle existe, l'import et le
81
- `<Composant />` sont ajoutés dans son JSX (sans doublon). Réponds `-` à la
82
- seconde question pour installer sans toucher à une page.
83
-
84
- Les couleurs passent par des **tokens Tailwind CSS 4** : la CLI écrit
85
- `uifolio-theme.css` (`:root`, `.dark`, puis `@theme inline` qui expose
86
- `--color-primary`, `--color-muted-foreground`, …) et l'importe automatiquement
87
- dans le CSS global du projet (celui qui fait `@import 'tailwindcss'`). Les
88
- composants gardent donc `bg-primary`, `border-primary/50`, `ring-primary/40` :
89
- aucune couleur en dur, `--color` change `--primary` dans le thème et `--radius`
90
- y ajoute `--radius`. Sans CSS global Tailwind détecté, le fichier est écrit à la
91
- racine et l'import à ajouter est affiché.
92
-
93
- À 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:
94
101
 
95
102
  ```bash
96
103
  npm install clsx tailwind-merge lucide-react hls.js
97
104
  ```
98
105
 
99
- ## Thèmes clair/sombre
106
+ ## Light/dark themes
100
107
 
101
- - Tokens CSS : `:root` (clair) et `.dark` (sombre) dans `src/index.css`,
102
- branchés à Tailwind via `@theme inline` + `@custom-variant dark`.
103
- - `useTheme()` (`src/lib/use-theme.ts`) : `{ theme, setTheme, toggle }`,
104
- persistance `localStorage('uifolio-theme')`.
105
- - `<ThemeToggle />` (`src/components/theme-toggle.tsx`) : bouton Sun/Moon.
106
- - 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
107
114
  `background`, `foreground`, `card`, `muted`, `border`, `primary`, `accent`, `ring`.
108
115
 
109
- ## Upload réel
116
+ ## Real upload
110
117
 
111
- Le `FileUpload` ne connaît pas ton backend : il appelle
112
- `uploader(file, onProgress) => Promise<url>` et affiche progression, succès et lien
113
- 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.
114
121
 
115
- La démo utilise `createUploader()` (`src/lib/uploader.ts`) :
122
+ The demo uses `createUploader()` (`src/lib/uploader.ts`):
116
123
 
117
- 1. `POST /api/upload/sign` → `{ key, uploadUrl, publicUrl }` (URL signée) ;
118
- 2. `PUT` du fichier en **XHR avec progression** ;
119
- 3. **reprise automatique** (2 retries, backoff) en réutilisant la même URL signée ;
120
- 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).
121
128
 
122
- 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`):
123
130
 
124
- - **Mode local (défaut, zéro config)** : écrit dans `public/uploads/` et sert
125
- `/uploads/<key>` ;
126
- - **Mode S3 compatible** (AWS S3, Cloudflare R2, MinIO) : si `S3_ENDPOINT`,
127
- `S3_BUCKET`, `S3_ACCESS_KEY_ID` et `S3_SECRET_ACCESS_KEY` sont définis, le serveur
128
- renvoie une URL PUT signée **SigV4** (implémentée avec `node:crypto`, aucun SDK)
129
- 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.
130
137
 
131
- Configuration dans `.env` — voir `.env.example`.
138
+ Configuration in `.env` — see `.env.example`.
132
139
 
133
- Brancher son propre uploader :
140
+ Plugging in your own uploader:
134
141
 
135
142
  ```tsx
136
143
  const upload = createUploader({ signEndpoint: '/api/upload/sign' })
137
144
 
138
- // ou ton propre backend :
145
+ // or your own backend:
139
146
  const upload = async (file: File, onProgress: (v: number) => void) => {
140
147
  const form = new FormData()
141
148
  form.append('file', file)
142
- const res = await fetch('/api/ton-endpoint', { method: 'POST', body: form })
149
+ const res = await fetch('/api/your-endpoint', { method: 'POST', body: form })
143
150
  const { url } = await res.json()
144
151
  return url
145
152
  }
@@ -150,29 +157,29 @@ const upload = async (file: File, onProgress: (v: number) => void) => {
150
157
  ## Stack
151
158
 
152
159
  - Vite 8 · React 19 · TypeScript
153
- - Tailwind CSS 4 (tokens CSS, aucune config JS)
154
- - `lucide-react` pour les icônes, `clsx` + `tailwind-merge` pour `cn()`
155
- - `hls.js` importé **dynamiquement**, uniquement quand un flux `.m3u8` est lu
156
- - Aucune autre dépendance : éditeur, calendrier, time picker, panneaux, palette,
157
- 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
158
165
 
159
- ## Composants
166
+ ## Components
160
167
 
161
- | Composant | Fichier | Ce qu'il gère |
168
+ | Component | File | What it handles |
162
169
  | --- | --- | --- |
163
- | VideoPlayer | `src/components/ui/video-player.tsx` | URL, File/Blob, qualités, HLS, YouTube/Vimeo, sous-titres VTT, contrôles et lecture |
164
- | Calendar | `src/components/ui/calendar.tsx` | date simple, plage start–end, saisie manuelle, min/max, jours désactivés |
165
- | TimePicker | `src/components/ui/time-picker.tsx` | 12/24 h, secondes, pas, saisie et flèches |
166
- | AudioPlayer | `src/components/ui/audio-player.tsx` | variantes carte/compact, seek, volume, vitesse, égaliseur |
167
- | Stories | `src/components/ui/stories.tsx` | viewer Instagram : images, vidéos, slides texte, pause, progression |
168
- | RichTextEditor | `src/components/ui/rich-text-editor.tsx` | WYSIWYG `contentEditable`, toolbar, HTML en sortie |
169
- | FileUpload | `src/components/ui/file-upload.tsx` | drag & drop, validation, aperçus, progression, URL publique, retrait |
170
- | CommandMenu | `src/components/ui/command-menu.tsx` | palette Ctrl/⌘ + K, recherche, clavier |
171
- | ResizablePanels | `src/components/ui/resizable.tsx` | groupes horizontal/vertical, poignées, min-size, clavier |
172
- | OtpInput | `src/components/ui/otp-input.tsx` | 4 champs, collage, rotate + coche de succès |
173
- | ColorPicker | `src/components/ui/color-picker.tsx` | panneau avancé, saturation/luminosité, teinte, alpha, pipette, canaux RGB, presets, HEX/RGB/HSL/RGBA |
174
-
175
- 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`,
176
183
  `src/lib/video.ts`, `src/lib/vtt.ts`, `src/lib/date.ts`, `src/lib/uploader.ts`, `src/lib/color.ts`,
177
184
  `src/lib/use-theme.ts`, `src/lib/use-hotkeys.ts`, `src/lib/use-reduced-motion.ts`.
178
185
 
@@ -190,27 +197,27 @@ Helpers copiables (la CLI les suit automatiquement) : `src/lib/utils.ts`,
190
197
  loop
191
198
  lazy
192
199
  subtitles={[
193
- { src: '/fr.vtt', label: 'Français', lang: 'fr', default: true },
200
+ { src: '/fr.vtt', label: 'French', lang: 'fr', default: true },
194
201
  { src: '/en.vtt', label: 'English', lang: 'en' },
195
202
  ]}
196
203
  subtitleClassName="bottom-20"
197
- overlay={(state) => <MonBadge playing={state.playing} />}
204
+ overlay={(state) => <MyBadge playing={state.playing} />}
198
205
  />
199
206
  ```
200
207
 
201
- - Sources : URL directe, `File`/`Blob`, tableau de qualités (`{ src, quality }`), `.m3u8`,
202
- YouTube et Vimeo.
203
- - Pour les embeds, l'iframe n'est injectée qu'au clic (façade + miniature) et les contrôles
204
- natifs sont masqués : la barre du composant pilote le lecteur de la plateforme
205
- (progression, temps, volume, vitesse, lecture/pause) via `postMessage`.
206
- - `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`,
207
214
  `requestFullscreen`, `getVideo`.
208
- - La barre de progression et le volume suivent l'arrondi global `--radius` (poignées
209
- comprises), comme le reste du catalogue.
210
- - Optimisations : chargement lazy, pause hors écran, `preload` maîtrisé, `hls.js` en chunk séparé,
211
- object URLs révoquées, changement de qualité sans perdre la position.
212
- - Sous-titres : parsing VTT tolérant, pistes fichier ou URL, style libre via
213
- `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.
214
221
 
215
222
  ## Calendar / TimePicker
216
223
 
@@ -223,20 +230,20 @@ const [date, setDate] = useState<CalendarValue>(null)
223
230
  <TimePicker value={time} onChange={setTime} withSeconds step={15} hour12 />
224
231
  ```
225
232
 
226
- 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`.
227
234
 
228
235
  ## Audio / Stories
229
236
 
230
237
  ```tsx
231
- <AudioPlayer src="/audio.wav" title="Titre" cover="/cover.svg" accent="#facc15" />
232
- <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" />
233
240
 
234
241
  <Stories items={stories} open={open} onOpenChange={setOpen} duration={5000} />
235
242
  ```
236
243
 
237
- 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.
238
245
 
239
- ## Éditeur / Upload / Raccourcis / Panneaux / OTP
246
+ ## Editor / Upload / Shortcuts / Panels / OTP
240
247
 
241
248
  ```tsx
242
249
  <RichTextEditor value={html} onChange={setHtml} toolbar />
@@ -252,61 +259,61 @@ useHotkeys({ 'mod+k': () => setOpen(true) })
252
259
  <ResizablePanel defaultSize={66} minSize={25}>…</ResizablePanel>
253
260
  </ResizablePanelGroup>
254
261
 
255
- <OtpInput length={4} value={code} onChange={setCode} onComplete={valider} />
262
+ <OtpInput length={4} value={code} onChange={setCode} onComplete={validate} />
256
263
  ```
257
264
 
258
- L'OTP aligne de 4 à 8 champs, forme un cercle à la validation puis affiche une coche.
259
- 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`.
260
267
 
261
268
  ## Animations
262
269
 
263
- Chaque animation a sa propre page documentée : `#/animations/otp` pour l'OTP, puis
264
- `#/animations/accordeon-horizontal` et `#/animations/storytelling` pour les cartes
265
- horizontales, et `#/animations/empilement` pour les cartes qui s'empilent au scroll avec
266
- une couleur de texte pilotée par la progression. Les couleurs suivent `--primary`. Le
267
- nombre de cartes se règle de 4 à 8 directement dans chaque aperçu. Ces démos autonomes ne
268
- 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.
269
276
 
270
- ## Publier le paquet
277
+ ## Publishing the package
271
278
 
272
- Le paquet publié ne contient que la CLI, les composants et les helpers (`files`) : aucune
273
- dépendance d'exécution, React et Tailwind restent en `devDependencies` pour ne pas les
274
- 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`.
275
282
 
276
283
  ```bash
277
- npm login # une fois par machine
278
- npm profile enable-2fa auth-and-writes # npm refuse de publier sans 2FA
279
- 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
280
287
  ```
281
288
 
282
- Un jeton *granular* avec « Bypass 2FA » fonctionne aussi, mais npm restreint
283
- 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.
284
291
 
285
- Ensuite `npx uifolio@latest add <composant>` et `pnpm dlx uifolio@latest add <composant>`
286
- fonctionnent.
292
+ Then `npx uifolio@latest add <component>` and `pnpm dlx uifolio@latest add <component>`
293
+ work.
287
294
 
288
- ### Publication automatique (GitHub Actions)
295
+ ### Automatic publishing (GitHub Actions)
289
296
 
290
- `.github/workflows/publish.yml` publie sur npm à chaque tag `v*` : il vérifie que le tag
291
- correspond à la version de `package.json`, installe avec `npm ci`, lance `oxlint`,
292
- `npm pack --dry-run` et `npm test`, puis publie via **trusted publishing** (OIDC, aucun
293
- token stocké). Un second job crée ensuite la **GitHub Release** du tag avec les notes
294
- 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.
295
302
 
296
303
  ```bash
297
304
  npm version patch # 0.3.0 → 0.3.1, commit + tag v0.3.1
298
- git push --follow-tags # déclenche le workflow
305
+ git push --follow-tags # triggers the workflow
299
306
  ```
300
307
 
301
- Prérequis, une seule fois :
308
+ Prerequisites, once:
302
309
 
303
- 1. première publication manuelle (ci-dessus) — le paquet doit exister sur npm ;
304
- 2. npmjs.com → `uifolio` → Settings → **Trusted publishing** → GitHub Actions :
305
- organisation `Barronakn`, dépôt `UIfolio`, workflow `publish.yml`,
306
- action « npm publish » autorisée ;
307
- 3. une configuration neuve doit réussir sa première publication **sous 2 jours**,
308
- 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.
309
316
 
310
- Le workflow doit être présent sur le commit taggé. Après activation, tu peux durcir le
311
- paquet avec Settings → Publishing access → « Require two-factor authentication and
312
- 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”.