@chrono-os/image-editor-react 0.2.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/README.md ADDED
@@ -0,0 +1,424 @@
1
+ # @chrono-os/image-editor-react
2
+
3
+ ![npm](https://img.shields.io/npm/v/@chrono-os/image-editor-react)
4
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
5
+
6
+ Editor de imagens com crop visual + preview de badge. Inclui `<ImagePicker>` controlado (preview + modal de edição com crop draggable, snap nas bordas, zoom, filtros saturação/sépia, cor de fundo, espelhar), `<ImageCropPicker>` standalone (sem o wrapper), `<BadgeOverlay>` reutilizável e os editores de badge `<BadgeEditorCard>` / `<BadgePartControls>` / `<SliderRow>` (admin).
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ yarn add @chrono-os/image-editor-react
12
+ ```
13
+
14
+ Publicado em `registry.npmjs.org` público — sem auth pra consumir.
15
+
16
+ Peer dependencies: `react@>=18` `react-dom@>=18`.
17
+
18
+ ## Setup tema (recomendado)
19
+
20
+ Importar o CSS de tokens no layout root (Next.js App Router):
21
+
22
+ ```ts
23
+ // app/layout.tsx
24
+ import '@chrono-os/image-editor-react/theme.css'
25
+ ```
26
+
27
+ E sobrescrever as CSS vars no `globals.css` do consumer (ou em qualquer ancestor com classe `.image-editor-root`):
28
+
29
+ ```css
30
+ :root {
31
+ --ie-accent: #C9A961; /* default electric blue #556FFF */
32
+ --ie-accent-hover: #A88842;
33
+ --ie-border: #d4d4d8;
34
+ --ie-bg: #fafafa;
35
+ --ie-text: #404040;
36
+ --ie-text-muted: #737373;
37
+ --ie-danger: #dc2626;
38
+ }
39
+ ```
40
+
41
+ Os componentes usam `text-[color:var(--ie-accent,#556FFF)]`/`bg-[color:var(--ie-accent,#556FFF)]` (Tailwind arbitrary values), então qualquer wrapper aplicando essas vars é suficiente.
42
+
43
+ ## Setup Tailwind (opcional)
44
+
45
+ Funciona com Tailwind 3+ sem config extra — as classes usadas são todas core utilities + arbitrary values. Se o consumer tiver `purge`/`content` configurado, garantir que o `node_modules/@chrono-os/image-editor-react/dist/**/*.{js,cjs}` está incluído:
46
+
47
+ ```ts
48
+ // tailwind.config.ts
49
+ export default {
50
+ content: [
51
+ './app/**/*.{ts,tsx}',
52
+ './node_modules/@chrono-os/image-editor-react/dist/**/*.{js,cjs}',
53
+ ],
54
+ // ...
55
+ }
56
+ ```
57
+
58
+ ## Exemplo: `<ImagePicker>` completo
59
+
60
+ ```tsx
61
+ 'use client'
62
+
63
+ import { useState } from 'react'
64
+ import {
65
+ ImagePicker,
66
+ type UploadAdapter,
67
+ type CropValue,
68
+ type BadgeData,
69
+ type ImagePreset,
70
+ } from '@chrono-os/image-editor-react'
71
+ import '@chrono-os/image-editor-react/theme.css'
72
+
73
+ const uploadAdapter: UploadAdapter = {
74
+ list: async () => {
75
+ const r = await fetch('/api/admin/uploads', { credentials: 'include' })
76
+ if (!r.ok) throw new Error('Falha ao listar')
77
+ const data = await r.json()
78
+ return data.items
79
+ },
80
+ upload: async (file) => {
81
+ const form = new FormData()
82
+ form.append('file', file, file.name)
83
+ const r = await fetch('/api/admin/uploads', {
84
+ method: 'POST',
85
+ body: form,
86
+ credentials: 'include',
87
+ })
88
+ if (!r.ok) throw new Error('Upload falhou')
89
+ return r.json()
90
+ },
91
+ delete: async (filename) => {
92
+ const r = await fetch(
93
+ `/api/admin/uploads/${encodeURIComponent(filename)}`,
94
+ { method: 'DELETE', credentials: 'include' },
95
+ )
96
+ return r.ok
97
+ },
98
+ }
99
+
100
+ const presets: ImagePreset[] = [
101
+ { label: 'Hero Naírio', url: '/branding/hero-nairio.webp' },
102
+ { label: 'Background ABS', url: '/branding/abs-pattern.webp' },
103
+ ]
104
+
105
+ export function HeroImageField() {
106
+ const [url, setUrl] = useState('')
107
+ const [crop, setCrop] = useState<CropValue>({})
108
+ const badge: BadgeData = {
109
+ numero: '201',
110
+ label: 'advogado',
111
+ numeroEnabled: true,
112
+ labelEnabled: true,
113
+ }
114
+
115
+ return (
116
+ <ImagePicker
117
+ label="Foto do hero"
118
+ hint="Recomendado: 1200×1600px (3:4)"
119
+ value={url}
120
+ onChange={(next) => setUrl(next)}
121
+ aspect="3/4"
122
+ crop={crop}
123
+ onCropChange={setCrop}
124
+ badge={badge}
125
+ uploadAdapter={uploadAdapter}
126
+ presets={presets}
127
+ />
128
+ )
129
+ }
130
+ ```
131
+
132
+ ## Exemplo: `<BadgeOverlay>` standalone
133
+
134
+ Pra renderizar só o badge sobreposto (sem o picker — útil em previews de listagem, cards, etc):
135
+
136
+ ```tsx
137
+ import { BadgeOverlay } from '@chrono-os/image-editor-react/badge'
138
+
139
+ export function HeroCard({ imageUrl }: { imageUrl: string }) {
140
+ return (
141
+ <div
142
+ className="relative aspect-[3/4] overflow-hidden rounded-lg"
143
+ style={{ containerType: 'inline-size' }}
144
+ >
145
+ <img src={imageUrl} alt="" className="h-full w-full object-cover" />
146
+ <BadgeOverlay
147
+ badge={{
148
+ numero: '201',
149
+ label: 'advogado',
150
+ numeroEnabled: true,
151
+ labelEnabled: true,
152
+ }}
153
+ />
154
+ </div>
155
+ )
156
+ }
157
+ ```
158
+
159
+ > **Importante:** o pai do `<BadgeOverlay>` precisa de `containerType: 'inline-size'` no CSS pra que os `cqw` (container query width) funcionem proporcionalmente em qualquer tamanho.
160
+
161
+ ## Exemplo: `<BadgeEditorCard>` no `extraCards`
162
+
163
+ Renderiza um subcard completo de edição de parte do badge (título + checkbox "Exibir" + texto + cor + sliders). Encaixa direto no `extraCards` do `<ImagePicker>`:
164
+
165
+ ```tsx
166
+ 'use client'
167
+
168
+ import {
169
+ ImagePicker,
170
+ BadgeEditorCard,
171
+ type BadgePartState,
172
+ type RecentColorsAdapter,
173
+ } from '@chrono-os/image-editor-react'
174
+
175
+ // Adapter opcional pra integrar com sistema de cores recentes do consumer.
176
+ const recentColors: RecentColorsAdapter = {
177
+ colors: { text: ['#13203B', '#C9A961', '#FFFFFF'] },
178
+ addColor: (hex, kind) => {
179
+ // persistir no DB do consumer
180
+ },
181
+ }
182
+
183
+ export function HeroBadgeFields({
184
+ badge,
185
+ onBadgeChange,
186
+ }: {
187
+ badge: { numero?: BadgePartState; label?: BadgePartState }
188
+ onBadgeChange: (next: typeof badge) => void
189
+ }) {
190
+ return (
191
+ <ImagePicker
192
+ // ...props do ImagePicker
193
+ value="/img/hero.webp"
194
+ onChange={() => {}}
195
+ uploadAdapter={uploadAdapter}
196
+ extraCards={[
197
+ <BadgeEditorCard
198
+ key="numero"
199
+ title="Badge — Número"
200
+ enabledLabel="Exibir número"
201
+ value={badge.numero ?? {}}
202
+ onChange={(next) => onBadgeChange({ ...badge, numero: next })}
203
+ defaultColor="#C9A961"
204
+ defaultPosX={6}
205
+ defaultPosY={84}
206
+ recentColors={recentColors}
207
+ />,
208
+ <BadgeEditorCard
209
+ key="label"
210
+ title="Badge — Label"
211
+ enabledLabel="Exibir label"
212
+ value={badge.label ?? {}}
213
+ onChange={(next) => onBadgeChange({ ...badge, label: next })}
214
+ defaultColor="#FFFFFF"
215
+ defaultPosX={36}
216
+ defaultPosY={88}
217
+ recentColors={recentColors}
218
+ />,
219
+ ]}
220
+ />
221
+ )
222
+ }
223
+ ```
224
+
225
+ A interface `BadgePartState` é:
226
+
227
+ ```ts
228
+ export interface BadgePartState {
229
+ enabled?: boolean // undefined === true (exibe), false === oculto
230
+ text?: string
231
+ color?: string // #RRGGBB
232
+ size?: number // % do default (50-500)
233
+ offsetX?: number // % horizontal (0-100)
234
+ offsetY?: number // % vertical (0-100)
235
+ }
236
+ ```
237
+
238
+ ## Exemplo: `<BadgePartControls>` standalone
239
+
240
+ Se você já tem o subcard externo (título + checkbox + texto) e quer só os controles compactos de cor + sliders:
241
+
242
+ ```tsx
243
+ 'use client'
244
+
245
+ import { BadgePartControls } from '@chrono-os/image-editor-react'
246
+
247
+ export function MyBadgeControls({ partState, onPatch }) {
248
+ return (
249
+ <BadgePartControls
250
+ color={partState.color}
251
+ defaultColor="#C9A961"
252
+ size={partState.size}
253
+ offsetX={partState.offsetX}
254
+ offsetY={partState.offsetY}
255
+ defaultPosX={6}
256
+ defaultPosY={84}
257
+ onColorChange={(v) => onPatch({ color: v })}
258
+ onSizeChange={(v) => onPatch({ size: v })}
259
+ onOffsetXChange={(v) => onPatch({ offsetX: v })}
260
+ onOffsetYChange={(v) => onPatch({ offsetY: v })}
261
+ />
262
+ )
263
+ }
264
+ ```
265
+
266
+ ## Exemplo: `<ImageCropPicker>` standalone
267
+
268
+ Pra quem só quer o editor de crop sem o wrapper de upload/preview:
269
+
270
+ ```tsx
271
+ 'use client'
272
+
273
+ import { useState } from 'react'
274
+ import { ImageCropPicker, type CropValue } from '@chrono-os/image-editor-react'
275
+
276
+ export function CropOnly({ imageUrl }: { imageUrl: string }) {
277
+ const [crop, setCrop] = useState<CropValue>({})
278
+ return (
279
+ <ImageCropPicker
280
+ imageUrl={imageUrl}
281
+ targetAspect={3 / 4}
282
+ value={crop}
283
+ onChange={setCrop}
284
+ maxHeight={520}
285
+ />
286
+ )
287
+ }
288
+ ```
289
+
290
+ ## Props do `<ImagePicker>`
291
+
292
+ | Prop | Tipo | Default | Descrição |
293
+ |---|---|---|---|
294
+ | `value` | `string` | — | URL atual da imagem (controlado). |
295
+ | `onChange` | `(url: string, aspectRatio?: number) => void` | — | Chamado quando o consumer escolhe nova imagem. |
296
+ | `uploadAdapter` | `UploadAdapter` | — | Adapter contra o backend (list/upload/delete/resolveUrl). |
297
+ | `label` | `string` | — | Rótulo do field acima do preview. |
298
+ | `hint` | `string` | — | Texto de ajuda abaixo do label. |
299
+ | `aspect` | `'1/1'\|'3/4'\|'16/9'\|'4/3'` | `'3/4'` | Aspect ratio do frame de destino. |
300
+ | `crop` | `CropValue` | `{}` | Valor de crop (position/scale/mirror/filters/backdrop). |
301
+ | `onCropChange` | `(crop: CropValue) => void` | — | Chamado quando o usuário ajusta o crop no modal. |
302
+ | `badge` | `BadgeData` | — | Badge sobreposto na preview (número + label). |
303
+ | `extraCards` | `ReactNode[]` | — | Cards extras ao lado direito do preview (slots de UI custom). |
304
+ | `presets` | `ImagePreset[]` | — | Atalhos de imagem curados (mostrados na aba "Imagens"). |
305
+
306
+ ## Interface `UploadAdapter`
307
+
308
+ ```ts
309
+ export interface UploadAdapter {
310
+ /** Lista uploads existentes (ordem decrescente por createdAt no consumer). */
311
+ list: () => Promise<UploadItem[]>
312
+ /** Sobe um arquivo e devolve o item criado. */
313
+ upload: (file: File) => Promise<UploadItem>
314
+ /** Remove um upload por filename. Retorna true em sucesso. */
315
+ delete: (filename: string) => Promise<boolean>
316
+ /**
317
+ * Resolve uma URL armazenada (relativa ou absoluta) pra URL absoluta usada
318
+ * no <img src>. Default = identity. Útil quando backend serve /uploads/*
319
+ * em domínio diferente sem rewrite.
320
+ */
321
+ resolveUrl?: (url: string) => string
322
+ }
323
+
324
+ export interface UploadItem {
325
+ filename: string
326
+ url: string
327
+ sizeBytes: number
328
+ createdAt?: string
329
+ width?: number
330
+ height?: number
331
+ }
332
+ ```
333
+
334
+ ## Interface `CropValue`
335
+
336
+ ```ts
337
+ export interface CropValue {
338
+ /** CSS object-position style: "50% 50%" ou "center top". */
339
+ position?: string
340
+ /** Zoom. 1 = cover normal, >1 = zoom in, <1 = zoom out (mostra backdrop). */
341
+ scale?: number
342
+ mirror?: boolean
343
+ grayscale?: number
344
+ saturation?: number
345
+ sepia?: number
346
+ sepiaColor?: string
347
+ /** Cor de fundo do container (#RRGGBB ou 'transparent'). */
348
+ backdropColor?: string
349
+ /** Opacidade da cor de backdrop 0-100. */
350
+ backdropOpacity?: number
351
+ /** Translate offset adicional (frame fora da imagem). */
352
+ overflowX?: number
353
+ overflowY?: number
354
+ }
355
+ ```
356
+
357
+ ## Interface `BadgeData`
358
+
359
+ ```ts
360
+ export interface BadgeData {
361
+ numero?: string
362
+ label?: string
363
+ numeroEnabled?: boolean
364
+ labelEnabled?: boolean
365
+ numeroColor?: string
366
+ numeroSize?: number // 50-500% (default 100)
367
+ numeroOffsetX?: number // %, default 6
368
+ numeroOffsetY?: number // %, default 84
369
+ labelColor?: string
370
+ labelSize?: number // 50-500% (default 100)
371
+ labelOffsetX?: number // %, default 36
372
+ labelOffsetY?: number // %, default 88
373
+ }
374
+ ```
375
+
376
+ ## Props do `<BadgeEditorCard>`
377
+
378
+ | Prop | Tipo | Default | Descrição |
379
+ |---|---|---|---|
380
+ | `title` | `string` | — | Título do subcard (ex `"Badge — Número"`). |
381
+ | `value` | `BadgePartState` | — | Estado controlado (enabled/text/color/size/offsetX/offsetY). |
382
+ | `onChange` | `(next: BadgePartState) => void` | — | Recebe o estado completo a cada edição. |
383
+ | `defaultColor` | `string` | — | Hex mostrado quando `value.color` é undefined. |
384
+ | `defaultPosX` | `number` | — | % horizontal exibida no slider quando undefined. |
385
+ | `defaultPosY` | `number` | — | % vertical exibida no slider quando undefined. |
386
+ | `enabledLabel` | `string` | `'Exibir'` | Label da checkbox. |
387
+ | `textPlaceholder` | `string` | — | Placeholder do field texto. |
388
+ | `recentColors` | `RecentColorsAdapter` | — | Adapter pra cores recentes (sumir swatches se ausente). |
389
+ | `recentKind` | `string` | `'text'` | Chave de cor pra agrupar recentes (do adapter). |
390
+ | `seedColors` | `string[]` | `['#FFFFFF', '#000000']` | Sementes quando recentes acabam. |
391
+ | `sizeMin` | `number` | `50` | Mínimo do slider de Tamanho (%). |
392
+ | `sizeMax` | `number` | `500` | Máximo do slider de Tamanho (%). |
393
+
394
+ ## Props do `<BadgePartControls>`
395
+
396
+ | Prop | Tipo | Default | Descrição |
397
+ |---|---|---|---|
398
+ | `color` | `string \| undefined` | — | Hex atual (undefined cai pra `defaultColor`). |
399
+ | `defaultColor` | `string` | — | Hex placeholder quando `color` é undefined. |
400
+ | `size` | `number \| undefined` | — | Tamanho atual (%). |
401
+ | `offsetX` | `number \| undefined` | — | Posição X atual (%). |
402
+ | `offsetY` | `number \| undefined` | — | Posição Y atual (%). |
403
+ | `defaultPosX` | `number` | — | Posição X exibida quando `offsetX` é undefined. |
404
+ | `defaultPosY` | `number` | — | Posição Y exibida quando `offsetY` é undefined. |
405
+ | `onColorChange` | `(v?: string) => void` | — | Emit. Recebe undefined em reset. |
406
+ | `onSizeChange` | `(v?: number) => void` | — | Emit. undefined em reset. |
407
+ | `onOffsetXChange` | `(v?: number) => void` | — | idem. |
408
+ | `onOffsetYChange` | `(v?: number) => void` | — | idem. |
409
+ | `recentColors` | `RecentColorsAdapter` | — | Adapter opcional de cores recentes. |
410
+ | `recentKind` | `string` | `'text'` | Chave do adapter. |
411
+ | `seedColors` | `string[]` | `['#FFFFFF', '#000000']` | Sementes neutras. |
412
+ | `sizeMin` | `number` | `50` | Mínimo do slider Tamanho. |
413
+ | `sizeMax` | `number` | `500` | Máximo do slider Tamanho. |
414
+
415
+ ## Roadmap
416
+
417
+ - Drag-and-drop de arquivos no card de uploads
418
+ - Mais aspect ratios (`2/3`, `9/16`)
419
+ - Animações suaves na troca de modal/preview
420
+ - Adapter de recentes (cores usadas anteriormente nos badges)
421
+
422
+ ## License
423
+
424
+ MIT
@@ -0,0 +1,130 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Tipos compartilhados do `@chrono-os/image-editor-react`.
6
+ */
7
+
8
+ /**
9
+ * Valor do crop aplicado à imagem. Persistido pelo consumer no schema
10
+ * (geralmente JSONB).
11
+ */
12
+ interface CropValue {
13
+ position?: string;
14
+ /** Zoom. 1 = cover normal, >1 = zoom in, <1 = zoom out (mostra backdrop). */
15
+ scale?: number;
16
+ mirror?: boolean;
17
+ grayscale?: number;
18
+ saturation?: number;
19
+ sepia?: number;
20
+ sepiaColor?: string;
21
+ /**
22
+ * Cor de fundo do container (visível em PNG transparente ou zoom out).
23
+ * Pode ser `#RRGGBB` ou `"transparent"`. Quando undefined, consumer usa
24
+ * o default dele.
25
+ */
26
+ backdropColor?: string;
27
+ /** Opacidade da cor de backdrop 0-100 (default 100). */
28
+ backdropOpacity?: number;
29
+ /** Translate offset additional to focal — em target frac signed. Permite frame fora da imagem. */
30
+ overflowX?: number;
31
+ overflowY?: number;
32
+ }
33
+ /** Item de upload listado/criado pelo `UploadAdapter`. */
34
+ interface UploadItem {
35
+ filename: string;
36
+ url: string;
37
+ sizeBytes: number;
38
+ createdAt?: string;
39
+ width?: number;
40
+ height?: number;
41
+ }
42
+ /**
43
+ * Resultado do upload (subset extendido do `UploadItem` retornado pelo
44
+ * `upload()`). Compatível com `UploadItem`.
45
+ */
46
+ type UploadedImage = UploadItem;
47
+ /**
48
+ * Adapter de uploads — o consumer implementa essa interface contra o backend
49
+ * dele (REST, GraphQL, mock, etc.) e passa para `<ImagePicker>` via prop.
50
+ *
51
+ * Erros propagam (rejeitam a promise). `ImagePicker` mostra mensagem amigável.
52
+ */
53
+ interface UploadAdapter {
54
+ /** Lista uploads existentes (ordem decrescente por createdAt no consumer). */
55
+ list: () => Promise<UploadItem[]>;
56
+ /** Sobe um arquivo e devolve o item criado. */
57
+ upload: (file: File) => Promise<UploadItem>;
58
+ /** Remove um upload por filename. Retorna true em sucesso. */
59
+ delete: (filename: string) => Promise<boolean>;
60
+ /**
61
+ * Resolve uma URL armazenada (relativa ou absoluta) pra URL absoluta usada
62
+ * no `<img src>`. Default = identity (string passa direto).
63
+ * Útil quando o backend serve `/uploads/*` e o frontend roda em domínio
64
+ * diferente sem rewrite — consumer prefixa com baseUrl.
65
+ */
66
+ resolveUrl?: (url: string) => string;
67
+ }
68
+ /** Preset de imagem do site (atalhos curados — opcional). */
69
+ interface ImagePreset {
70
+ label: string;
71
+ url: string;
72
+ }
73
+ /**
74
+ * Adapter de cores recentes — opcional. Quando ausente, `BadgePartControls`
75
+ * (consumer) não mostra o swatch.
76
+ */
77
+ interface RecentColorsAdapter {
78
+ colors: {
79
+ [kind: string]: string[];
80
+ };
81
+ addColor: (hex: string, kind: string) => void;
82
+ seed?: {
83
+ [kind: string]: string[];
84
+ };
85
+ }
86
+ /**
87
+ * Dados do badge sobreposto à imagem. Renderer real fica no consumer (ex
88
+ * @chrono-os/lp-react); aqui só usamos pra preview visual no editor.
89
+ */
90
+ interface BadgeData {
91
+ numero?: string;
92
+ label?: string;
93
+ numeroEnabled?: boolean;
94
+ labelEnabled?: boolean;
95
+ numeroColor?: string;
96
+ numeroSize?: number;
97
+ numeroOffsetX?: number;
98
+ numeroOffsetY?: number;
99
+ labelColor?: string;
100
+ labelSize?: number;
101
+ labelOffsetX?: number;
102
+ labelOffsetY?: number;
103
+ }
104
+ type ImagePickerAspect = '1/1' | '3/4' | '16/9' | '4/3';
105
+ /** Props do `<ImagePicker>` controlado. */
106
+ interface ImagePickerProps {
107
+ value: string;
108
+ onChange: (url: string, aspectRatio?: number) => void;
109
+ /** Adapter pra upload/list/delete contra o backend do consumer. */
110
+ uploadAdapter: UploadAdapter;
111
+ label?: string;
112
+ hint?: string;
113
+ aspect?: ImagePickerAspect;
114
+ crop?: CropValue;
115
+ onCropChange?: (crop: CropValue) => void;
116
+ /** Badge opcional (número + label) sobreposto na imagem no preview. */
117
+ badge?: BadgeData;
118
+ /** Slots opcionais — cada um vira um subcard ao lado direito do preview. */
119
+ extraCards?: ReactNode[];
120
+ /** Presets de imagem do site (mostrados na aba "Imagens" abaixo dos uploads). */
121
+ presets?: ImagePreset[];
122
+ }
123
+
124
+ interface BadgeOverlayProps {
125
+ badge?: BadgeData;
126
+ size?: 'sm' | 'md';
127
+ }
128
+ declare function BadgeOverlay({ badge }: BadgeOverlayProps): react_jsx_runtime.JSX.Element | null;
129
+
130
+ export { type BadgeData as B, type CropValue as C, type ImagePickerAspect as I, type RecentColorsAdapter as R, type UploadAdapter as U, BadgeOverlay as a, type ImagePickerProps as b, type ImagePreset as c, type UploadItem as d, type UploadedImage as e };
@@ -0,0 +1,130 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Tipos compartilhados do `@chrono-os/image-editor-react`.
6
+ */
7
+
8
+ /**
9
+ * Valor do crop aplicado à imagem. Persistido pelo consumer no schema
10
+ * (geralmente JSONB).
11
+ */
12
+ interface CropValue {
13
+ position?: string;
14
+ /** Zoom. 1 = cover normal, >1 = zoom in, <1 = zoom out (mostra backdrop). */
15
+ scale?: number;
16
+ mirror?: boolean;
17
+ grayscale?: number;
18
+ saturation?: number;
19
+ sepia?: number;
20
+ sepiaColor?: string;
21
+ /**
22
+ * Cor de fundo do container (visível em PNG transparente ou zoom out).
23
+ * Pode ser `#RRGGBB` ou `"transparent"`. Quando undefined, consumer usa
24
+ * o default dele.
25
+ */
26
+ backdropColor?: string;
27
+ /** Opacidade da cor de backdrop 0-100 (default 100). */
28
+ backdropOpacity?: number;
29
+ /** Translate offset additional to focal — em target frac signed. Permite frame fora da imagem. */
30
+ overflowX?: number;
31
+ overflowY?: number;
32
+ }
33
+ /** Item de upload listado/criado pelo `UploadAdapter`. */
34
+ interface UploadItem {
35
+ filename: string;
36
+ url: string;
37
+ sizeBytes: number;
38
+ createdAt?: string;
39
+ width?: number;
40
+ height?: number;
41
+ }
42
+ /**
43
+ * Resultado do upload (subset extendido do `UploadItem` retornado pelo
44
+ * `upload()`). Compatível com `UploadItem`.
45
+ */
46
+ type UploadedImage = UploadItem;
47
+ /**
48
+ * Adapter de uploads — o consumer implementa essa interface contra o backend
49
+ * dele (REST, GraphQL, mock, etc.) e passa para `<ImagePicker>` via prop.
50
+ *
51
+ * Erros propagam (rejeitam a promise). `ImagePicker` mostra mensagem amigável.
52
+ */
53
+ interface UploadAdapter {
54
+ /** Lista uploads existentes (ordem decrescente por createdAt no consumer). */
55
+ list: () => Promise<UploadItem[]>;
56
+ /** Sobe um arquivo e devolve o item criado. */
57
+ upload: (file: File) => Promise<UploadItem>;
58
+ /** Remove um upload por filename. Retorna true em sucesso. */
59
+ delete: (filename: string) => Promise<boolean>;
60
+ /**
61
+ * Resolve uma URL armazenada (relativa ou absoluta) pra URL absoluta usada
62
+ * no `<img src>`. Default = identity (string passa direto).
63
+ * Útil quando o backend serve `/uploads/*` e o frontend roda em domínio
64
+ * diferente sem rewrite — consumer prefixa com baseUrl.
65
+ */
66
+ resolveUrl?: (url: string) => string;
67
+ }
68
+ /** Preset de imagem do site (atalhos curados — opcional). */
69
+ interface ImagePreset {
70
+ label: string;
71
+ url: string;
72
+ }
73
+ /**
74
+ * Adapter de cores recentes — opcional. Quando ausente, `BadgePartControls`
75
+ * (consumer) não mostra o swatch.
76
+ */
77
+ interface RecentColorsAdapter {
78
+ colors: {
79
+ [kind: string]: string[];
80
+ };
81
+ addColor: (hex: string, kind: string) => void;
82
+ seed?: {
83
+ [kind: string]: string[];
84
+ };
85
+ }
86
+ /**
87
+ * Dados do badge sobreposto à imagem. Renderer real fica no consumer (ex
88
+ * @chrono-os/lp-react); aqui só usamos pra preview visual no editor.
89
+ */
90
+ interface BadgeData {
91
+ numero?: string;
92
+ label?: string;
93
+ numeroEnabled?: boolean;
94
+ labelEnabled?: boolean;
95
+ numeroColor?: string;
96
+ numeroSize?: number;
97
+ numeroOffsetX?: number;
98
+ numeroOffsetY?: number;
99
+ labelColor?: string;
100
+ labelSize?: number;
101
+ labelOffsetX?: number;
102
+ labelOffsetY?: number;
103
+ }
104
+ type ImagePickerAspect = '1/1' | '3/4' | '16/9' | '4/3';
105
+ /** Props do `<ImagePicker>` controlado. */
106
+ interface ImagePickerProps {
107
+ value: string;
108
+ onChange: (url: string, aspectRatio?: number) => void;
109
+ /** Adapter pra upload/list/delete contra o backend do consumer. */
110
+ uploadAdapter: UploadAdapter;
111
+ label?: string;
112
+ hint?: string;
113
+ aspect?: ImagePickerAspect;
114
+ crop?: CropValue;
115
+ onCropChange?: (crop: CropValue) => void;
116
+ /** Badge opcional (número + label) sobreposto na imagem no preview. */
117
+ badge?: BadgeData;
118
+ /** Slots opcionais — cada um vira um subcard ao lado direito do preview. */
119
+ extraCards?: ReactNode[];
120
+ /** Presets de imagem do site (mostrados na aba "Imagens" abaixo dos uploads). */
121
+ presets?: ImagePreset[];
122
+ }
123
+
124
+ interface BadgeOverlayProps {
125
+ badge?: BadgeData;
126
+ size?: 'sm' | 'md';
127
+ }
128
+ declare function BadgeOverlay({ badge }: BadgeOverlayProps): react_jsx_runtime.JSX.Element | null;
129
+
130
+ export { type BadgeData as B, type CropValue as C, type ImagePickerAspect as I, type RecentColorsAdapter as R, type UploadAdapter as U, BadgeOverlay as a, type ImagePickerProps as b, type ImagePreset as c, type UploadItem as d, type UploadedImage as e };