uifolio 0.0.0-stage → 0.4.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Barronakn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,305 @@
1
- # Temporary Holding Version
1
+ # UIfolio
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/uifolio.svg)](https://www.npmjs.com/package/uifolio)
4
+ [![license](https://img.shields.io/npm/l/uifolio.svg)](https://www.npmjs.com/package/uifolio)
5
+ [![node](https://img.shields.io/node/v/uifolio.svg)](https://www.npmjs.com/package/uifolio)
6
+
7
+ Ma librairie de composants et animations : le code vit dans le projet, se copie et se modifie.
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.
12
+
13
+ ## Démarrage
14
+
15
+ ```bash
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
20
+ npm run lint # oxlint
21
+ npm test # tests du CLI (copie, couleur, manifeste npm)
22
+ ```
23
+
24
+ Routes (hash) :
25
+
26
+ - `#/` — page d'accueil (identité UIFolio)
27
+ - `#/docs` — introduction, installation, thème
28
+ - `#/components` — catalogue documenté
29
+ - `#/components/video` — playground VideoPlayer
30
+ - `#/components/calendar`, `/time`, `/audio`, `/stories`, `/editor`, `/upload`,
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
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.
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`).
46
+
47
+ ## Utiliser les composants dans un autre projet (CLI)
48
+
49
+ Comme shadcn : tu installes les fichiers dans **ton** projet, avec leurs helpers,
50
+ et l'alias est réécrit automatiquement.
51
+
52
+ ```bash
53
+ # depuis n'importe quel projet, via le paquet npm
54
+ npx uifolio@latest add calendar --color "#10B981" --radius 6
55
+ pnpm dlx uifolio@latest add video-player otp
56
+
57
+ # depuis la racine du projet qui contient UIfolio
58
+ npm run uifolio -- list
59
+ npm run uifolio -- init --cwd ../mon-projet
60
+ npm run uifolio -- add video-player calendar otp --cwd ../mon-projet
61
+
62
+ # ou directement, exécutable via « bin »
63
+ node cli/uifolio.mjs add otp --cwd ../mon-projet
64
+ node cli/uifolio.mjs add file-upload --alias "#lib" --dry-run # simulate
65
+ ```
66
+
67
+ | Commande | Rôle |
68
+ | --- | --- |
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. :
84
+
85
+ ```bash
86
+ npm install clsx tailwind-merge lucide-react hls.js
87
+ ```
88
+
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
93
+
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
100
+ `background`, `foreground`, `card`, `muted`, `border`, `primary`, `accent`, `ring`.
101
+
102
+ ## Upload réel
103
+
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.
107
+
108
+ La démo utilise `createUploader()` (`src/lib/uploader.ts`) :
109
+
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).
114
+
115
+ Côté serveur, le plugin `upload-plugin.ts` (branché dans `vite.config.ts`) :
116
+
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.
123
+
124
+ Configuration dans `.env` — voir `.env.example`.
125
+
126
+ Brancher son propre uploader :
127
+
128
+ ```tsx
129
+ const upload = createUploader({ signEndpoint: '/api/upload/sign' })
130
+
131
+ // ou ton propre backend :
132
+ const upload = async (file: File, onProgress: (v: number) => void) => {
133
+ const form = new FormData()
134
+ form.append('file', file)
135
+ const res = await fetch('/api/ton-endpoint', { method: 'POST', body: form })
136
+ const { url } = await res.json()
137
+ return url
138
+ }
139
+
140
+ <FileUpload accept="image/*" uploader={upload} onChange={setFiles} />
141
+ ```
142
+
143
+ ## Stack
144
+
145
+ - 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
151
+
152
+ ## Composants
153
+
154
+ | Composant | Fichier | Ce qu'il gère |
155
+ | --- | --- | --- |
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`,
169
+ `src/lib/video.ts`, `src/lib/vtt.ts`, `src/lib/date.ts`, `src/lib/uploader.ts`, `src/lib/color.ts`,
170
+ `src/lib/use-theme.ts`, `src/lib/use-hotkeys.ts`, `src/lib/use-reduced-motion.ts`.
171
+
172
+ ## VideoPlayer
173
+
174
+ ```tsx
175
+ <VideoPlayer
176
+ sources={file ?? '/video.mp4'}
177
+ aspectRatio="16/9"
178
+ fit="cover"
179
+ controls="full" // full | minimal | none
180
+ accent="#facc15"
181
+ autoPlay
182
+ muted
183
+ loop
184
+ lazy
185
+ subtitles={[
186
+ { src: '/fr.vtt', label: 'Français', lang: 'fr', default: true },
187
+ { src: '/en.vtt', label: 'English', lang: 'en' },
188
+ ]}
189
+ subtitleClassName="bottom-20"
190
+ overlay={(state) => <MonBadge playing={state.playing} />}
191
+ />
192
+ ```
193
+
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`,
200
+ `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.
207
+
208
+ ## Calendar / TimePicker
209
+
210
+ ```tsx
211
+ const [date, setDate] = useState<CalendarValue>(null)
212
+ <Calendar mode="single" value={date} onChange={setDate} min={min} max={max} />
213
+ <Calendar mode="range" value={range} onChange={setRange} />
214
+ <Calendar disabled={(day) => day.getDay() === 0} />
215
+
216
+ <TimePicker value={time} onChange={setTime} withSeconds step={15} hour12 />
217
+ ```
218
+
219
+ La saisie manuelle accepte `jj/mm/aaaa`, `jj-mm-aaaa` et `aaaa-mm-jj`.
220
+
221
+ ## Audio / Stories
222
+
223
+ ```tsx
224
+ <AudioPlayer src="/audio.wav" title="Titre" cover="/cover.svg" accent="#facc15" />
225
+ <AudioPlayer variant="compact" src="/audio.wav" title="Titre" />
226
+
227
+ <Stories items={stories} open={open} onOpenChange={setOpen} duration={5000} />
228
+ ```
229
+
230
+ Une story peut être une image, une vidéo ou un slide texte avec son propre fond et sa durée.
231
+
232
+ ## Éditeur / Upload / Raccourcis / Panneaux / OTP
233
+
234
+ ```tsx
235
+ <RichTextEditor value={html} onChange={setHtml} toolbar />
236
+
237
+ <FileUpload accept="image/*,video/*" maxFiles={3} uploader={upload} onChange={setFiles} />
238
+
239
+ <CommandMenu items={items} open={open} onOpenChange={setOpen} />
240
+ useHotkeys({ 'mod+k': () => setOpen(true) })
241
+
242
+ <ResizablePanelGroup direction="horizontal">
243
+ <ResizablePanel defaultSize={34} minSize={20}>…</ResizablePanel>
244
+ <ResizableHandle />
245
+ <ResizablePanel defaultSize={66} minSize={25}>…</ResizablePanel>
246
+ </ResizablePanelGroup>
247
+
248
+ <OtpInput length={4} value={code} onChange={setCode} onComplete={valider} />
249
+ ```
250
+
251
+ L'OTP aligne de 4 à 8 champs, forme un cercle à la validation puis affiche une coche.
252
+ L'animation respecte `prefers-reduced-motion`.
253
+
254
+ ## Animations
255
+
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.
262
+
263
+ ## Publier le paquet
264
+
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`.
268
+
269
+ ```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
273
+ ```
274
+
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.
277
+
278
+ Ensuite `npx uifolio@latest add <composant>` et `pnpm dlx uifolio@latest add <composant>`
279
+ fonctionnent.
280
+
281
+ ### Publication automatique (GitHub Actions)
282
+
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.
288
+
289
+ ```bash
290
+ npm version patch # 0.3.0 → 0.3.1, commit + tag v0.3.1
291
+ git push --follow-tags # déclenche le workflow
292
+ ```
293
+
294
+ Prérequis, une seule fois :
295
+
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.
302
+
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 ».