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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "uifolio",
3
- "version": "0.5.1",
4
- "description": "CLI qui copie des composants React (calendrier, lecteurs vidéo/audio, OTP, éditeur, upload) et leurs helpers dans ton projet, avec des tokens Tailwind CSS 4.",
3
+ "version": "0.5.3",
4
+ "description": "CLI that copies React components (calendar, video/audio players, OTP, editor, upload) and their helpers into your project, with Tailwind CSS 4 tokens.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Barronakn <jeanbarronalokpon@gmail.com>",
@@ -199,7 +199,7 @@ export function Calendar({
199
199
  <>
200
200
  <label className="flex items-center gap-2 rounded-xl border bg-background/40 px-2.5 py-1.5 focus-within:ring-2 focus-within:ring-primary/40">
201
201
  <CalendarDays className="size-3.5 shrink-0 text-muted-foreground" />
202
- <span className="sr-only">Début (jj/mm/aaaa)</span>
202
+ <span className="sr-only">Start (MM/DD/YYYY)</span>
203
203
  <input
204
204
  value={fromDraft}
205
205
  onChange={(event) => setFromDraft(event.target.value)}
@@ -210,14 +210,14 @@ export function Calendar({
210
210
  applyRange(fromDraft, toDraft)
211
211
  }
212
212
  }}
213
- placeholder="du jj/mm/aaaa"
213
+ placeholder="from MM/DD/YYYY"
214
214
  inputMode="numeric"
215
215
  className="w-full bg-transparent text-xs tabular-nums outline-none placeholder:text-muted-foreground/60"
216
216
  />
217
217
  </label>
218
218
  <label className="flex items-center gap-2 rounded-xl border bg-background/40 px-2.5 py-1.5 focus-within:ring-2 focus-within:ring-primary/40">
219
219
  <CalendarDays className="size-3.5 shrink-0 text-muted-foreground" />
220
- <span className="sr-only">Fin (jj/mm/aaaa)</span>
220
+ <span className="sr-only">End (MM/DD/YYYY)</span>
221
221
  <input
222
222
  value={toDraft}
223
223
  onChange={(event) => setToDraft(event.target.value)}
@@ -228,7 +228,7 @@ export function Calendar({
228
228
  applyRange(fromDraft, toDraft)
229
229
  }
230
230
  }}
231
- placeholder="au jj/mm/aaaa"
231
+ placeholder="to MM/DD/YYYY"
232
232
  inputMode="numeric"
233
233
  className="w-full bg-transparent text-xs tabular-nums outline-none placeholder:text-muted-foreground/60"
234
234
  />
@@ -242,7 +242,7 @@ export function Calendar({
242
242
  <button
243
243
  type="button"
244
244
  onClick={() => setVisibleMonth((month) => addMonths(month, -1))}
245
- aria-label="Mois précédent"
245
+ aria-label="Previous month"
246
246
  className="inline-flex size-7 items-center justify-center rounded-lg text-muted-foreground transition hover:bg-muted hover:text-foreground"
247
247
  >
248
248
  <ChevronLeft className="size-4" />
@@ -296,7 +296,7 @@ export function ColorPicker({
296
296
  type="button"
297
297
  onClick={toggle}
298
298
  disabled={disabled}
299
- aria-label="Ouvrir le sélecteur de couleur"
299
+ aria-label="Open the color picker"
300
300
  aria-haspopup="dialog"
301
301
  aria-expanded={isOpen}
302
302
  aria-controls={panelId}
@@ -336,7 +336,7 @@ export function ColorPicker({
336
336
  }}
337
337
  spellCheck={false}
338
338
  aria-invalid={invalid}
339
- aria-label="Valeur de la couleur"
339
+ aria-label="Color value"
340
340
  placeholder={displayFormat === 'hex' ? '#10B981' : displayFormat === 'rgb' ? 'rgb(16, 185, 129)' : 'hsl(160, 84%, 39%)'}
341
341
  className={cn(
342
342
  'h-8 w-full rounded-sm border bg-background pl-10 pr-2 font-mono text-[10.5px] text-foreground outline-none transition placeholder:normal-case placeholder:text-muted-foreground/70 focus:border-primary/60 focus:ring-2 focus:ring-primary/15 disabled:cursor-not-allowed',
@@ -349,8 +349,8 @@ export function ColorPicker({
349
349
  type="button"
350
350
  onClick={toggle}
351
351
  disabled={disabled}
352
- aria-label="Personnaliser la couleur"
353
- title="Personnaliser la couleur"
352
+ aria-label="Customize the color"
353
+ title="Customize the color"
354
354
  className={cn(
355
355
  'grid size-8 shrink-0 place-items-center rounded-sm border transition outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed',
356
356
  isOpen
@@ -372,7 +372,7 @@ export function ColorPicker({
372
372
  ref={panelRef}
373
373
  id={panelId}
374
374
  role="dialog"
375
- aria-label="Sélecteur de couleur"
375
+ aria-label="Color picker"
376
376
  tabIndex={-1}
377
377
  style={{
378
378
  position: 'fixed',
@@ -386,7 +386,7 @@ export function ColorPicker({
386
386
  >
387
387
  <div className="flex items-start justify-between gap-3 border-b px-3.5 py-3">
388
388
  <div className="min-w-0">
389
- <p className="text-[11px] font-semibold">Sélecteur de couleur</p>
389
+ <p className="text-[11px] font-semibold">Color picker</p>
390
390
  <p className="mt-0.5 truncate font-mono text-[9.5px] uppercase tracking-wide text-muted-foreground">
391
391
  {formatColor(current, 'hex', showAlpha)}
392
392
  </p>
@@ -394,7 +394,7 @@ export function ColorPicker({
394
394
  <button
395
395
  type="button"
396
396
  onClick={close}
397
- aria-label="Fermer le sélecteur de couleur"
397
+ aria-label="Close the color picker"
398
398
  className="grid size-6 shrink-0 place-items-center rounded-sm text-muted-foreground transition hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
399
399
  >
400
400
  <X className="size-3.5" />
@@ -404,7 +404,7 @@ export function ColorPicker({
404
404
  <div className="space-y-3 p-3.5">
405
405
  <div
406
406
  role="group"
407
- aria-label="Saturation et luminosité"
407
+ aria-label="Saturation and luminance"
408
408
  tabIndex={disabled ? -1 : 0}
409
409
  onPointerDown={(event) => {
410
410
  if (disabled) return
@@ -530,8 +530,8 @@ export function ColorPicker({
530
530
  type="button"
531
531
  disabled={disabled}
532
532
  onClick={pickFromScreen}
533
- title="Prélever une couleur à l’écran"
534
- aria-label="Prélever une couleur à l’écran"
533
+ title="Pick a color from the screen"
534
+ aria-label="Pick a color from the screen"
535
535
  className="inline-flex items-center gap-1.5 rounded-sm border border-border px-2 py-1 text-[9px] font-semibold uppercase tracking-[0.1em] text-muted-foreground transition hover:border-primary/50 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
536
536
  >
537
537
  <Pipette className="size-3" />
@@ -614,7 +614,7 @@ export function ColorPicker({
614
614
 
615
615
  {checkerAlpha && (
616
616
  <p className="text-[9px] leading-relaxed text-muted-foreground">
617
- La transparence est incluse dans la valeur exportée.
617
+ The opacity is included in the exported value.
618
618
  </p>
619
619
  )}
620
620
  </div>
@@ -34,7 +34,7 @@ export function CommandMenu({
34
34
  defaultOpen = false,
35
35
  onOpenChange,
36
36
  placeholder = 'Rechercher une commande…',
37
- emptyText = 'Aucun résultat.',
37
+ emptyText = 'No result.',
38
38
  emptyState,
39
39
  className,
40
40
  }: Readonly<CommandMenuProps>) {
@@ -14,7 +14,7 @@ export interface UploadItem {
14
14
  progress: number
15
15
  status: UploadStatus
16
16
  error?: string
17
- /** URL publique renvoyée par l'uploader (upload réel). */
17
+ /** Public URL returned by the uploader (real upload). */
18
18
  url?: string
19
19
  }
20
20
 
@@ -23,7 +23,7 @@ export interface FileUploadProps {
23
23
  multiple?: boolean
24
24
  maxSizeMb?: number
25
25
  maxFiles?: number
26
- /** Renvoie l'URL publique du fichier (upload réel) ou void. */
26
+ /** Returns the public file URL (real upload) or void. */
27
27
  uploader?: (file: File, onProgress: (value: number) => void) => Promise<string | void>
28
28
  onChange?: (items: UploadItem[]) => void
29
29
  className?: string
@@ -142,11 +142,11 @@ export function FileUpload({
142
142
  for (const file of files) {
143
143
  if (accepted.length >= slots) break
144
144
  if (file.size > maxSizeMb * 1024 * 1024) {
145
- errors.push(`${file.name} dépasse ${maxSizeMb} Mo`)
145
+ errors.push(`${file.name} exceeds ${maxSizeMb} MB`)
146
146
  continue
147
147
  }
148
148
  if (accept && !matchesAccept(file, accept)) {
149
- errors.push(`${file.name} n'est pas un type accepté`)
149
+ errors.push(`${file.name} is not an accepted type`)
150
150
  continue
151
151
  }
152
152
  accepted.push(file)
@@ -218,10 +218,10 @@ export function FileUpload({
218
218
  <Upload className="size-5" />
219
219
  </span>
220
220
  <span className="text-sm font-medium">
221
- {dragging ? 'Dépose ici' : 'Glisse tes fichiers ou clique'}
221
+ {dragging ? 'Drop here' : 'Drag your files or click'}
222
222
  </span>
223
223
  <span className="max-w-xs text-[11px] leading-relaxed text-muted-foreground">
224
- {accept ? accept.replaceAll(',', ' · ') : 'Tous types'} — jusqu'à {maxSizeMb} Mo
224
+ {accept ? accept.replaceAll(',', ' · ') : 'All types'} — up to {maxSizeMb} MB
225
225
  {multiple ? ` · ${maxFiles} fichiers` : ''}
226
226
  </span>
227
227
  <input
@@ -302,9 +302,9 @@ export function FileUpload({
302
302
  href={item.url}
303
303
  target={item.url ? '_blank' : undefined}
304
304
  rel="noreferrer"
305
- title={item.url ?? 'Envoyé'}
305
+ title={item.url ?? 'Uploaded'}
306
306
  className="grid size-7 shrink-0 place-items-center rounded-md text-emerald-400 transition hover:bg-emerald-400/10"
307
- aria-label={item.url ? `Ouvrir ${item.file.name}` : 'Envoyé'}
307
+ aria-label={item.url ? `Open ${item.file.name}` : 'Uploaded'}
308
308
  >
309
309
  <Check className="size-4" />
310
310
  </a>
@@ -313,7 +313,7 @@ export function FileUpload({
313
313
  <button
314
314
  type="button"
315
315
  onClick={() => retry(item)}
316
- aria-label="Réessayer"
316
+ aria-label="Retry"
317
317
  className="grid size-7 place-items-center rounded-md text-muted-foreground transition hover:bg-muted hover:text-foreground"
318
318
  >
319
319
  <RotateCcw className="size-3.5" />
@@ -158,7 +158,7 @@ export function IconGlyph({
158
158
  const [prevIcon, setPrevIcon] = useState(icon)
159
159
  const loadRef = useRef<Promise<void> | null>(null)
160
160
 
161
- // Ajustement pendant le rendu : une icône déjà en cache s'applique sans effet.
161
+ // Render-time adjustment: an icon already cached is applied with no effect.
162
162
  if (prevIcon !== icon) {
163
163
  setPrevIcon(icon)
164
164
  setData(iconCache.get(icon) ?? null)
@@ -184,7 +184,7 @@ export function IconPicker({
184
184
  value,
185
185
  color = 'var(--primary)',
186
186
  onChange,
187
- placeholder = 'Choisir une icône',
187
+ placeholder = 'Choose an icon',
188
188
  className,
189
189
  }: Readonly<IconPickerProps>) {
190
190
  const [open, setOpen] = useState(false)
@@ -206,7 +206,7 @@ export function IconPicker({
206
206
  `https://api.iconify.design/search?query=${encodeURIComponent(q)}&limit=96&prefixes=${SEARCH_PREFIXES}`,
207
207
  { signal: controller.signal },
208
208
  )
209
- if (!res.ok) throw new Error('Erreur lors de la recherche des icônes')
209
+ if (!res.ok) throw new Error('Error while searching icons')
210
210
  const data: IconSearchResponse = await res.json()
211
211
  setIcons(data.icons ?? [])
212
212
  } catch (err) {
@@ -270,12 +270,12 @@ export function IconPicker({
270
270
  {/* Picker */}
271
271
  {open && (
272
272
  <div className="absolute left-0 top-full z-50 mt-2 w-full overflow-hidden rounded-xl border border-border bg-card shadow-xl">
273
- {/* En-tête */}
273
+ {/* Header */}
274
274
  <div className="flex items-center justify-between border-b border-border px-4 py-3">
275
275
  <div>
276
- <p className="text-sm font-medium text-foreground">Choisir une icône</p>
276
+ <p className="text-sm font-medium text-foreground">Choose an icon</p>
277
277
  <p className="text-xs text-muted-foreground">
278
- Recherchez parmi des milliers d'icônes
278
+ Search among thousands of icons
279
279
  </p>
280
280
  </div>
281
281
  <button
@@ -305,7 +305,7 @@ export function IconPicker({
305
305
  </div>
306
306
  </div>
307
307
 
308
- {/* Liste d'icônes */}
308
+ {/* Icon list */}
309
309
  <div className="max-h-36 overflow-y-auto p-3">
310
310
  {loading ? (
311
311
  <div className="grid grid-cols-[repeat(auto-fill,minmax(2.5rem,1fr))] gap-2">
@@ -349,7 +349,7 @@ export function IconPicker({
349
349
  <div className="flex flex-col items-center justify-center py-6 text-center">
350
350
  <Search size={26} className="mb-2 text-muted-foreground/50" />
351
351
  <p className="text-sm font-medium text-muted-foreground">
352
- Aucune icône trouvée
352
+ No icon found
353
353
  </p>
354
354
  <p className="mt-1 text-xs text-muted-foreground/70">
355
355
  Essayez un autre terme
@@ -183,7 +183,7 @@ export function ResizableHandle({ className }: Readonly<{ className?: string }>)
183
183
  }}
184
184
  className={cn('group/handle relative shrink-0 outline-none', className)}
185
185
  >
186
- <span className="sr-only">Utilise les flèches pour redimensionner</span>
186
+ <span className="sr-only">Use the arrows to resize</span>
187
187
  </div>
188
188
  )
189
189
  }
@@ -143,14 +143,14 @@ export function RichTextEditor({
143
143
  <Italic className="size-3.5" />
144
144
  </ToolButton>
145
145
  <ToolButton
146
- label="Souligné"
146
+ label="Underline"
147
147
  active={active.underline}
148
148
  onClick={() => exec('underline')}
149
149
  >
150
150
  <Underline className="size-3.5" />
151
151
  </ToolButton>
152
152
  <ToolButton
153
- label="Barré"
153
+ label="Strikethrough"
154
154
  active={active.strikeThrough}
155
155
  onClick={() => exec('strikeThrough')}
156
156
  >
@@ -182,14 +182,14 @@ export function RichTextEditor({
182
182
  <Divider />
183
183
 
184
184
  <ToolButton
185
- label="Liste à puces"
185
+ label="Bullet list"
186
186
  active={active.insertUnorderedList}
187
187
  onClick={() => exec('insertUnorderedList')}
188
188
  >
189
189
  <List className="size-3.5" />
190
190
  </ToolButton>
191
191
  <ToolButton
192
- label="Liste numérotée"
192
+ label="Numbered list"
193
193
  active={active.insertOrderedList}
194
194
  onClick={() => exec('insertOrderedList')}
195
195
  >
@@ -204,7 +204,7 @@ export function RichTextEditor({
204
204
  <ToolButton label="Annuler" onClick={() => exec('undo')}>
205
205
  <Undo2 className="size-3.5" />
206
206
  </ToolButton>
207
- <ToolButton label="Rétablir" onClick={() => exec('redo')}>
207
+ <ToolButton label="Redo" onClick={() => exec('redo')}>
208
208
  <Redo2 className="size-3.5" />
209
209
  </ToolButton>
210
210
  <ToolButton label="Effacer la mise en forme" onClick={() => exec('removeFormat')}>
@@ -51,7 +51,7 @@ export function Stories({
51
51
  const setOpen = useCallback(
52
52
  (next: boolean) => {
53
53
  if (next) {
54
- // Réinitialise la lecture à l'ouverture plutôt que par un effet.
54
+ // Restarts playback at open rather than via an effect.
55
55
  elapsedRef.current = 0
56
56
  setPaused(false)
57
57
  }
@@ -204,7 +204,7 @@ export function Stories({
204
204
  )}
205
205
  <div className="min-w-0 flex-1">
206
206
  <p className="truncate text-xs font-medium text-white">{item.title ?? 'Stories'}</p>
207
- <p className="text-[10px] text-white/55">à l'instant</p>
207
+ <p className="text-[10px] text-white/55">now</p>
208
208
  </div>
209
209
  <button
210
210
  type="button"