@seedgrid/ai-toolkit 0.3.0 → 0.3.1

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.
@@ -1,629 +1,629 @@
1
- ---
2
- tags: [seedgrid, front, component, gadgets]
3
- fonte: front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets
4
- branch: main
5
- commit: ad30fc11de
6
- analisado-em: 2026-10-14
7
- ---
8
-
9
- # Exemplos — gadgets
10
-
11
- Exemplos copiados 1:1 dos `samples/*.tsx.sample` do showcase (rotas `/components/gadgets/*`, `/components/providers/*`, `/components/hooks/*`), salvo os marcados como **exemplo próprio**. Todos verificados com type-check TypeScript (`strict`) contra `packages/seedgrid-fe-components/src/index.ts` no commit `ad30fc11de` — sem erros. Classes utilitárias são Tailwind do app de exemplo.
12
-
13
- API: [[SeedGrid/SeedGrid-components/gadgets/API|API dos gadgets]] · Visão geral: [[SeedGrid/SeedGrid-components/gadgets/Visao-geral|Gadgets — visão geral]]
14
-
15
- ## SgCalendar
16
-
17
- ### Básico (não controlado)
18
-
19
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-calendar/samples/basico-mes-atual.tsx.sample`
20
-
21
- ```tsx
22
- "use client";
23
-
24
- import { SgCalendar } from "@seedgrid/fe-components";
25
-
26
- export default function Example() {
27
- return (
28
- <SgCalendar
29
- locale="pt-BR"
30
- defaultValue="2026-03-10"
31
- defaultViewDate="2026-03-01"
32
- cardTitle="Calendario base"
33
- cardProps={{ id: "showcase-calendar-basic", dragPersistKey: "showcase-calendar-basic" }}
34
- />
35
- );
36
- }
37
- ```
38
-
39
- ### Controlado, com limites e fins de semana desabilitados
40
-
41
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-calendar/samples/selecionado-controlado-limite-de-datas.tsx.sample`
42
-
43
- ```tsx
44
- "use client";
45
-
46
- import React from "react";
47
- import { SgCalendar } from "@seedgrid/fe-components";
48
-
49
- export default function Example() {
50
- const [selected, setSelected] = React.useState(new Date(2026, 2, 10));
51
- const minDate = React.useMemo(() => new Date(2026, 2, 5), []);
52
- const maxDate = React.useMemo(() => new Date(2026, 2, 25), []);
53
-
54
- return (
55
- <div className="space-y-2">
56
- <SgCalendar
57
- value={selected}
58
- onValueChange={setSelected}
59
- locale="pt-BR"
60
- minDate={minDate}
61
- maxDate={maxDate}
62
- isDateDisabled={(date) => date.getDay() === 0 || date.getDay() === 6}
63
- cardTitle="Agenda de visitas"
64
- cardProps={{ id: "showcase-calendar-range", dragPersistKey: "showcase-calendar-range" }}
65
- />
66
- <p className="text-sm text-muted-foreground">
67
- Selecionado: {selected.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" })}
68
- </p>
69
- </div>
70
- );
71
- }
72
- ```
73
-
74
- ## SgClock
75
-
76
- ### Digital em três tamanhos, alternando 12h/24h
77
-
78
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-clock/samples/digital.tsx.sample`
79
-
80
- ```tsx
81
- import * as React from "react";
82
- import { SgButton, SgClock } from "@seedgrid/fe-components";
83
-
84
- export default function Example(props: { initialServerTime?: string }) {
85
- const [format, setFormat] = React.useState<"12h" | "24h">("24h");
86
-
87
- return (
88
- <>
89
- <div className="mb-3 flex flex-wrap items-center gap-2">
90
- <SgButton onClick={() => setFormat((value) => (value === "24h" ? "12h" : "24h"))}>
91
- {format}
92
- </SgButton>
93
- </div>
94
-
95
- <div className="grid gap-4 md:grid-cols-3">
96
- <SgClock
97
- clockStyle="digital"
98
- size="sm"
99
- initialServerTime={props.initialServerTime ?? new Date().toISOString()}
100
- timezone="America/Sao_Paulo"
101
- format={format}
102
- digitalStyle="default"
103
- />
104
- <SgClock
105
- clockStyle="digital"
106
- size="md"
107
- initialServerTime={props.initialServerTime ?? new Date().toISOString()}
108
- timezone="America/Sao_Paulo"
109
- format={format}
110
- digitalStyle="default"
111
- />
112
- <SgClock
113
- clockStyle="digital"
114
- size="lg"
115
- initialServerTime={props.initialServerTime ?? new Date().toISOString()}
116
- timezone="America/Sao_Paulo"
117
- format={format}
118
- digitalStyle="default"
119
- />
120
- </div>
121
- </>
122
- );
123
- }
124
- ```
125
-
126
- ### Analógico com tema inline (sem provider nem registro)
127
-
128
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-clock/samples/tema-inline-sem-provider.tsx.sample`
129
-
130
- ```tsx
131
- import React from "react";
132
- import { SgClock } from "@seedgrid/fe-components";
133
-
134
- export default function Example(props: { initialServerTime?: string }) {
135
- return (
136
- <div className="flex items-center justify-center rounded-xl border border-border bg-background p-6">
137
- <SgClock
138
- clockStyle="analog"
139
- size={260}
140
- initialServerTime={props.initialServerTime ?? new Date().toISOString()}
141
- showSeconds
142
- secondHandMode="smooth"
143
- theme={{
144
- id: "inline-theme",
145
- label: "Inline Theme",
146
- render: () => (
147
- <>
148
- <circle cx="50" cy="50" r="48" className="fill-white dark:fill-neutral-950" />
149
- <circle cx="50" cy="50" r="47" className="fill-none stroke-neutral-300 dark:stroke-neutral-700" />
150
- <circle cx="50" cy="50" r="38" className="fill-none stroke-neutral-200 dark:stroke-neutral-800" />
151
- <circle cx="50" cy="50" r="6" className="fill-neutral-100 dark:fill-neutral-900" />
152
- <text x="50" y="56" textAnchor="middle" className="fill-neutral-400 text-[8px] font-semibold">
153
- SeedGrid
154
- </text>
155
- </>
156
- ),
157
- }}
158
- />
159
- </div>
160
- );
161
- }
162
- ```
163
-
164
- ### Fusos horários
165
-
166
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-clock/samples/timezone.tsx.sample`
167
-
168
- ```tsx
169
- import * as React from "react";
170
- import { SgButton, SgClock } from "@seedgrid/fe-components";
171
-
172
- export default function Example(props: { initialServerTime?: string }) {
173
- const [timezone, setTimezone] = React.useState("America/Sao_Paulo");
174
- const [format, setFormat] = React.useState<"12h" | "24h">("24h");
175
-
176
- return (
177
- <>
178
- <div className="flex flex-wrap gap-2">
179
- <SgButton onClick={() => setTimezone("America/Sao_Paulo")}>Sao Paulo</SgButton>
180
- <SgButton onClick={() => setTimezone("Europe/Lisbon")}>Lisboa</SgButton>
181
- <SgButton onClick={() => setTimezone("America/New_York")}>New York</SgButton>
182
- <SgButton onClick={() => setTimezone("Asia/Tokyo")}>Tokyo</SgButton>
183
- <SgButton onClick={() => setFormat((value) => (value === "24h" ? "12h" : "24h"))}>
184
- {format}
185
- </SgButton>
186
- </div>
187
-
188
- <div className="mt-4 grid gap-3 md:grid-cols-2">
189
- <SgClock
190
- clockStyle="digital"
191
- size="lg"
192
- initialServerTime={props.initialServerTime ?? new Date().toISOString()}
193
- timezone={timezone}
194
- format={format}
195
- showSeconds
196
- />
197
- <SgClock
198
- clockStyle="analog"
199
- size={220}
200
- initialServerTime={props.initialServerTime ?? new Date().toISOString()}
201
- themeId="seedgrid"
202
- timezone={timezone}
203
- showSeconds={false}
204
- />
205
- </div>
206
- </>
207
- );
208
- }
209
- ```
210
-
211
- Observação: o tema `seedgrid` usado acima só resolve se os temas embutidos tiverem sido registrados (o showcase registra em `sg-clock-showcase-client.tsx`). Ver o exemplo seguinte.
212
-
213
- ### Temas embutidos + provider + picker (exemplo próprio)
214
-
215
- Os temas de `sgClockThemesBuiltIn` precisam ser registrados; o `SgClockThemePicker` lista os temas do `SgClockThemeProvider`.
216
-
217
- ```tsx
218
- "use client";
219
-
220
- import React from "react";
221
- import {
222
- SgClock,
223
- SgClockThemePicker,
224
- SgClockThemeProvider,
225
- registerThemes,
226
- sgClockThemesBuiltIn
227
- } from "@seedgrid/fe-components";
228
-
229
- // Os temas embutidos NÃO são registrados automaticamente: registre uma vez no boot do app.
230
- registerThemes(sgClockThemesBuiltIn);
231
-
232
- export default function Example() {
233
- const [themeId, setThemeId] = React.useState("classic");
234
-
235
- return (
236
- <SgClockThemeProvider value={{ mode: "fallback", fallbackThemeId: "classic" }}>
237
- <SgClockThemePicker value={themeId} onChange={setThemeId} label="Tema" />
238
- <SgClock clockStyle="analog" size={240} themeId={themeId} />
239
- </SgClockThemeProvider>
240
- );
241
- }
242
- ```
243
-
244
- ## SgTimeProvider / useSgTime
245
-
246
- ### Uso básico
247
-
248
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/providers/sg-time-provider/samples/uso-basico.tsx.sample`
249
-
250
- ```tsx
251
- import { SgTimeProvider, useSgTime } from "@seedgrid/fe-components";
252
-
253
- function LiveClock() {
254
- const time = useSgTime();
255
- const date = new Date(time.nowMs());
256
- const pad = (n: number) => String(n).padStart(2, "0");
257
-
258
- return (
259
- <span>
260
- {pad(date.getHours())}:{pad(date.getMinutes())}:{pad(date.getSeconds())}
261
- </span>
262
- );
263
- }
264
-
265
- export default function App() {
266
- return (
267
- <SgTimeProvider initialServerTime={new Date().toISOString()}>
268
- <LiveClock />
269
- </SgTimeProvider>
270
- );
271
- }
272
- ```
273
-
274
- ## SgClockThemeProvider / useSgClockThemeResolver
275
-
276
- ### Resolvedor via hook (modo `fallback`)
277
-
278
- Só há temas a listar/resolver se algum tiver sido registrado (`registerThemes`) ou passado em `value.themes`; caso contrário `resolve("classic")` retorna `null`.
279
-
280
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/providers/sg-clock-theme-provider/samples/resolver-hook.tsx.sample`
281
-
282
- ```tsx
283
- "use client";
284
-
285
- import { SgClockThemeProvider, useSgClockThemeResolver } from "@seedgrid/fe-components";
286
-
287
- function TemasDisponiveis() {
288
- // Fora de um SgClockThemeProvider o hook devolve null: sem provider nao ha
289
- // resolvedor, e nao existe fallback implicito.
290
- const resolver = useSgClockThemeResolver();
291
- if (!resolver) return <div>Sem SgClockThemeProvider na arvore.</div>;
292
-
293
- const classic = resolver.resolve("classic");
294
- const inexistente = resolver.resolve("nao-existe");
295
-
296
- return (
297
- <div className="space-y-1 text-sm">
298
- <div>temas visíveis: {resolver.list().map((theme) => theme.id).join(", ")}</div>
299
- <div>resolve("classic"): {classic?.id ?? "null"}</div>
300
- {/* modo "fallback" (padrão) cai no fallbackThemeId; em "strict" isto seria null */}
301
- <div>resolve("nao-existe"): {inexistente?.id ?? "null"}</div>
302
- </div>
303
- );
304
- }
305
-
306
- export default function Example() {
307
- return (
308
- <SgClockThemeProvider value={{ mode: "fallback", fallbackThemeId: "classic" }}>
309
- <TemasDisponiveis />
310
- </SgClockThemeProvider>
311
- );
312
- }
313
- ```
314
-
315
- ## SgClockThemePreview
316
-
317
- ### Galeria dos temas embutidos (exemplo próprio)
318
-
319
- ```tsx
320
- import { SgClockThemePreview, sgClockThemesBuiltIn } from "@seedgrid/fe-components";
321
-
322
- export default function Example() {
323
- return (
324
- <div style={{ display: "flex", gap: 12 }}>
325
- {sgClockThemesBuiltIn.map((theme) => (
326
- <SgClockThemePreview key={theme.id} theme={theme} size={72} />
327
- ))}
328
- </div>
329
- );
330
- }
331
- ```
332
-
333
- ## SgLinearGauge
334
-
335
- ### Horizontal com faixas e ponteiro arrastável
336
-
337
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-linear-gauge/samples/default-horizontal.tsx.sample`
338
-
339
- ```tsx
340
- "use client";
341
-
342
- import React from "react";
343
- import { SgLinearGauge } from "@seedgrid/fe-components";
344
-
345
- export default function Example() {
346
- const [value, setValue] = React.useState(64);
347
-
348
- return (
349
- <SgLinearGauge
350
- min={0}
351
- max={100}
352
- value={value}
353
- onValueChange={setValue}
354
- ranges={[
355
- { start: 0, end: 30, color: "#22c55e" },
356
- { start: 30, end: 70, color: "#f59e0b" },
357
- { start: 70, end: 100, color: "#ef4444" }
358
- ]}
359
- showTicks
360
- showLabels
361
- majorTickCount={5}
362
- barPointer
363
- primaryPointerDraggable
364
- />
365
- );
366
- }
367
- ```
368
-
369
- ### Vertical com múltiplos ponteiros
370
-
371
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-linear-gauge/samples/vertical-multi-pointers.tsx.sample`
372
-
373
- ```tsx
374
- "use client";
375
-
376
- import React from "react";
377
- import { SgLinearGauge, type SgLinearGaugePointer } from "@seedgrid/fe-components";
378
-
379
- export default function Example() {
380
- const [temperature, setTemperature] = React.useState(28);
381
- const [target, setTarget] = React.useState(72);
382
- const pointers = React.useMemo<SgLinearGaugePointer[]>(
383
- () => [
384
- { id: "target", value: target, color: "hsl(var(--secondary))", shape: "diamond", size: 11, draggable: true },
385
- { id: "temp", value: temperature, color: "hsl(var(--destructive))", shape: "circle", size: 10, draggable: true }
386
- ],
387
- [target, temperature]
388
- );
389
-
390
- return (
391
- <SgLinearGauge
392
- min={-10}
393
- max={50}
394
- value={temperature}
395
- onValueChange={setTemperature}
396
- orientation="vertical"
397
- width={140}
398
- height={360}
399
- pointers={pointers}
400
- onPointerValueChange={(pointerId, next) => {
401
- if (pointerId === "target") setTarget(next);
402
- if (pointerId === "temp") setTemperature(next);
403
- }}
404
- ranges={[
405
- { start: -10, end: 15, color: "#38bdf8" },
406
- { start: 15, end: 30, color: "#22c55e" },
407
- { start: 30, end: 50, color: "#ef4444" }
408
- ]}
409
- majorTickCount={6}
410
- minorTicksPerInterval={1}
411
- labelFormatter={(value) => `${Math.round(value)}°`}
412
- />
413
- );
414
- }
415
- ```
416
-
417
- ## SgRadialGauge
418
-
419
- ### Estilo velocímetro com conteúdo central
420
-
421
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-radial-gauge/samples/speedometer-style.tsx.sample`
422
-
423
- ```tsx
424
- "use client";
425
-
426
- import React from "react";
427
- import { SgRadialGauge } from "@seedgrid/fe-components";
428
-
429
- export default function Example() {
430
- const [rpm, setRpm] = React.useState(62);
431
-
432
- return (
433
- <SgRadialGauge
434
- min={0}
435
- max={100}
436
- value={rpm}
437
- onValueChange={setRpm}
438
- startAngle={140}
439
- endAngle={40}
440
- ranges={[
441
- { start: 0, end: 40, color: "#22c55e", width: 14 },
442
- { start: 40, end: 75, color: "#f59e0b", width: 14 },
443
- { start: 75, end: 100, color: "#ef4444", width: 14 }
444
- ]}
445
- showTicks
446
- showLabels
447
- majorTickCount={5}
448
- minorTicksPerInterval={2}
449
- primaryPointerDraggable
450
- primaryPointerType="needle"
451
- primaryPointerColor="#2563eb"
452
- centerContent={<div className="text-center"><div className="text-xs uppercase text-muted-foreground">RPM</div><div className="text-xl font-semibold">{Math.round(rpm)}</div></div>}
453
- />
454
- );
455
- }
456
- ```
457
-
458
- ### Ponteiros extras (marker/range) e anotação
459
-
460
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-radial-gauge/samples/multi-pointers-annotation.tsx.sample`
461
-
462
- ```tsx
463
- "use client";
464
-
465
- import React from "react";
466
- import { SgRadialGauge, type SgRadialGaugePointer } from "@seedgrid/fe-components";
467
-
468
- export default function Example() {
469
- const [rpm, setRpm] = React.useState(62);
470
- const [target, setTarget] = React.useState(70);
471
- const [consumption, setConsumption] = React.useState(44);
472
- const pointers = React.useMemo<SgRadialGaugePointer[]>(
473
- () => [
474
- { id: "target", type: "marker", value: target, color: "#8b5cf6", size: 13, shape: "diamond", draggable: true, label: String(Math.round(target)) },
475
- { id: "consumption", type: "range", value: consumption, color: "#14b8a6", width: 10, draggable: true }
476
- ],
477
- [consumption, target]
478
- );
479
-
480
- return (
481
- <SgRadialGauge
482
- min={0}
483
- max={120}
484
- value={rpm}
485
- onValueChange={setRpm}
486
- startAngle={135}
487
- endAngle={45}
488
- radiusFactor={0.95}
489
- ranges={[
490
- { start: 0, end: 60, color: "#60a5fa", width: 12, opacity: 0.45 },
491
- { start: 60, end: 90, color: "#f59e0b", width: 12, opacity: 0.45 },
492
- { start: 90, end: 120, color: "#ef4444", width: 12, opacity: 0.45 }
493
- ]}
494
- pointers={pointers}
495
- onPointerValueChange={(id, next) => {
496
- if (id === "target") setTarget(next);
497
- if (id === "consumption") setConsumption(next);
498
- }}
499
- annotations={[{ id: "target-label", value: target, radiusFactor: 0.8, content: <span className="rounded bg-violet-600 px-2 py-1 text-[10px] font-semibold text-white">target</span> }]}
500
- primaryPointerDraggable
501
- />
502
- );
503
- }
504
- ```
505
-
506
- ## SgQRCode
507
-
508
- ### Interativo com logo e fallback
509
-
510
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-qr-code/samples/interactive-example.tsx.sample`
511
-
512
- ```tsx
513
- "use client";
514
-
515
- import React from "react";
516
- import { SgButton, SgQRCode } from "@seedgrid/fe-components";
517
-
518
- export default function Example() {
519
- const [value, setValue] = React.useState("https://seedgrid.com.br");
520
- const [logoSrc, setLogoSrc] = React.useState("/logo-seedgrid.svg");
521
- const [size, setSize] = React.useState(220);
522
-
523
- return (
524
- <div className="grid gap-6 md:grid-cols-[1fr_auto]">
525
- <div className="space-y-4">
526
- <label className="block space-y-1">
527
- <span className="text-sm font-medium">Valor do QR Code</span>
528
- <textarea
529
- value={value}
530
- onChange={(event) => setValue(event.target.value)}
531
- rows={3}
532
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-primary"
533
- placeholder="Digite texto, URL ou payload PIX"
534
- />
535
- </label>
536
-
537
- <label className="block space-y-1">
538
- <span className="text-sm font-medium">Logo central (URL)</span>
539
- <input
540
- type="text"
541
- value={logoSrc}
542
- onChange={(event) => setLogoSrc(event.target.value)}
543
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-primary"
544
- placeholder="https://exemplo.com/logo.png"
545
- />
546
- </label>
547
-
548
- <label className="block space-y-1">
549
- <span className="text-sm font-medium">Tamanho: {size}px</span>
550
- <input type="range" min={140} max={360} step={4} value={size} onChange={(event) => setSize(Number(event.target.value))} className="w-full" />
551
- </label>
552
-
553
- <div className="flex flex-wrap gap-2">
554
- <SgButton onClick={() => setValue("https://seedgrid.com.br")}>URL</SgButton>
555
- <SgButton severity="secondary" onClick={() => setValue("00020126580014BR.GOV.BCB.PIX0136contato@seedgrid.com.br5204000053039865802BR5922SEEDGRID TECNOLOGIA6009SAO PAULO62140510SEEDGRID1236304ABCD")}>
556
- Exemplo PIX
557
- </SgButton>
558
- <SgButton severity="warning" onClick={() => setLogoSrc("")}>Sem logo</SgButton>
559
- </div>
560
- </div>
561
-
562
- <div className="inline-flex h-fit rounded-xl border border-border bg-background p-4 shadow-sm">
563
- <SgQRCode
564
- value={value}
565
- size={size}
566
- logoSrc={logoSrc || undefined}
567
- logoAlt="Logo SeedGrid"
568
- emptyFallback={<span className="text-sm text-muted-foreground">Informe um valor</span>}
569
- />
570
- </div>
571
- </div>
572
- );
573
- }
574
- ```
575
-
576
- ## SgStringAnimator
577
-
578
- ### `autoStart` ao trocar o alvo
579
-
580
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-string-animator/samples/autostart-animacao-automatica.tsx.sample`
581
-
582
- ```tsx
583
- "use client";
584
-
585
- import React from "react";
586
- import { SgStringAnimator } from "@seedgrid/fe-components";
587
-
588
- const NAMES = ["LUCIANO", "MARTA", "PEDRO", "ANA", "SEEDGRID"];
589
-
590
- export default function Example() {
591
- const [index, setIndex] = React.useState(0);
592
- const [target, setTarget] = React.useState(NAMES[0] ?? "");
593
-
594
- React.useEffect(() => {
595
- const id = window.setInterval(() => {
596
- setIndex((prev) => {
597
- const next = (prev + 1) % NAMES.length;
598
- setTarget(NAMES[next] ?? "");
599
- return next;
600
- });
601
- }, 1800);
602
- return () => window.clearInterval(id);
603
- }, []);
604
-
605
- return <SgStringAnimator sourceString={NAMES[index] ?? ""} targetString={target} stringAnimatorStyle="roller3d" alignTo="left" fontSize={28} autoStart />;
606
- }
607
- ```
608
-
609
- ### Disparo imperativo via `ref` (números alinhados à direita)
610
-
611
- Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-string-animator/samples/roller-3d-numeros-right-aligned.tsx.sample`
612
-
613
- ```tsx
614
- "use client";
615
-
616
- import React from "react";
617
- import { SgStringAnimator, type SgStringAnimatorRef, SgButton } from "@seedgrid/fe-components";
618
-
619
- export default function Example() {
620
- const animRef = React.useRef<SgStringAnimatorRef>(null);
621
-
622
- return (
623
- <div className="flex flex-col gap-4">
624
- <SgStringAnimator ref={animRef} sourceString="42" targetString="1337" stringAnimatorStyle="roller3d" alignTo="right" fontSize={28} />
625
- <SgButton size="sm" onClick={() => animRef.current?.startAnimation()}>Animar</SgButton>
626
- </div>
627
- );
628
- }
629
- ```
1
+ ---
2
+ tags: [seedgrid, front, component, gadgets]
3
+ fonte: front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets
4
+ branch: main
5
+ commit: ad30fc11de
6
+ analisado-em: 2026-10-14
7
+ ---
8
+
9
+ # Exemplos — gadgets
10
+
11
+ Exemplos copiados 1:1 dos `samples/*.tsx.sample` do showcase (rotas `/components/gadgets/*`, `/components/providers/*`, `/components/hooks/*`), salvo os marcados como **exemplo próprio**. Todos verificados com type-check TypeScript (`strict`) contra `packages/seedgrid-fe-components/src/index.ts` no commit `ad30fc11de` — sem erros. Classes utilitárias são Tailwind do app de exemplo.
12
+
13
+ API: [[SeedGrid/SeedGrid-components/gadgets/API|API dos gadgets]] · Visão geral: [[SeedGrid/SeedGrid-components/gadgets/Visao-geral|Gadgets — visão geral]]
14
+
15
+ ## SgCalendar
16
+
17
+ ### Básico (não controlado)
18
+
19
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-calendar/samples/basico-mes-atual.tsx.sample`
20
+
21
+ ```tsx
22
+ "use client";
23
+
24
+ import { SgCalendar } from "@seedgrid/fe-components";
25
+
26
+ export default function Example() {
27
+ return (
28
+ <SgCalendar
29
+ locale="pt-BR"
30
+ defaultValue="2026-03-10"
31
+ defaultViewDate="2026-03-01"
32
+ cardTitle="Calendario base"
33
+ cardProps={{ id: "showcase-calendar-basic", dragPersistKey: "showcase-calendar-basic" }}
34
+ />
35
+ );
36
+ }
37
+ ```
38
+
39
+ ### Controlado, com limites e fins de semana desabilitados
40
+
41
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-calendar/samples/selecionado-controlado-limite-de-datas.tsx.sample`
42
+
43
+ ```tsx
44
+ "use client";
45
+
46
+ import React from "react";
47
+ import { SgCalendar } from "@seedgrid/fe-components";
48
+
49
+ export default function Example() {
50
+ const [selected, setSelected] = React.useState(new Date(2026, 2, 10));
51
+ const minDate = React.useMemo(() => new Date(2026, 2, 5), []);
52
+ const maxDate = React.useMemo(() => new Date(2026, 2, 25), []);
53
+
54
+ return (
55
+ <div className="space-y-2">
56
+ <SgCalendar
57
+ value={selected}
58
+ onValueChange={setSelected}
59
+ locale="pt-BR"
60
+ minDate={minDate}
61
+ maxDate={maxDate}
62
+ isDateDisabled={(date) => date.getDay() === 0 || date.getDay() === 6}
63
+ cardTitle="Agenda de visitas"
64
+ cardProps={{ id: "showcase-calendar-range", dragPersistKey: "showcase-calendar-range" }}
65
+ />
66
+ <p className="text-sm text-muted-foreground">
67
+ Selecionado: {selected.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" })}
68
+ </p>
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## SgClock
75
+
76
+ ### Digital em três tamanhos, alternando 12h/24h
77
+
78
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-clock/samples/digital.tsx.sample`
79
+
80
+ ```tsx
81
+ import * as React from "react";
82
+ import { SgButton, SgClock } from "@seedgrid/fe-components";
83
+
84
+ export default function Example(props: { initialServerTime?: string }) {
85
+ const [format, setFormat] = React.useState<"12h" | "24h">("24h");
86
+
87
+ return (
88
+ <>
89
+ <div className="mb-3 flex flex-wrap items-center gap-2">
90
+ <SgButton onClick={() => setFormat((value) => (value === "24h" ? "12h" : "24h"))}>
91
+ {format}
92
+ </SgButton>
93
+ </div>
94
+
95
+ <div className="grid gap-4 md:grid-cols-3">
96
+ <SgClock
97
+ clockStyle="digital"
98
+ size="sm"
99
+ initialServerTime={props.initialServerTime ?? new Date().toISOString()}
100
+ timezone="America/Sao_Paulo"
101
+ format={format}
102
+ digitalStyle="default"
103
+ />
104
+ <SgClock
105
+ clockStyle="digital"
106
+ size="md"
107
+ initialServerTime={props.initialServerTime ?? new Date().toISOString()}
108
+ timezone="America/Sao_Paulo"
109
+ format={format}
110
+ digitalStyle="default"
111
+ />
112
+ <SgClock
113
+ clockStyle="digital"
114
+ size="lg"
115
+ initialServerTime={props.initialServerTime ?? new Date().toISOString()}
116
+ timezone="America/Sao_Paulo"
117
+ format={format}
118
+ digitalStyle="default"
119
+ />
120
+ </div>
121
+ </>
122
+ );
123
+ }
124
+ ```
125
+
126
+ ### Analógico com tema inline (sem provider nem registro)
127
+
128
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-clock/samples/tema-inline-sem-provider.tsx.sample`
129
+
130
+ ```tsx
131
+ import React from "react";
132
+ import { SgClock } from "@seedgrid/fe-components";
133
+
134
+ export default function Example(props: { initialServerTime?: string }) {
135
+ return (
136
+ <div className="flex items-center justify-center rounded-xl border border-border bg-background p-6">
137
+ <SgClock
138
+ clockStyle="analog"
139
+ size={260}
140
+ initialServerTime={props.initialServerTime ?? new Date().toISOString()}
141
+ showSeconds
142
+ secondHandMode="smooth"
143
+ theme={{
144
+ id: "inline-theme",
145
+ label: "Inline Theme",
146
+ render: () => (
147
+ <>
148
+ <circle cx="50" cy="50" r="48" className="fill-white dark:fill-neutral-950" />
149
+ <circle cx="50" cy="50" r="47" className="fill-none stroke-neutral-300 dark:stroke-neutral-700" />
150
+ <circle cx="50" cy="50" r="38" className="fill-none stroke-neutral-200 dark:stroke-neutral-800" />
151
+ <circle cx="50" cy="50" r="6" className="fill-neutral-100 dark:fill-neutral-900" />
152
+ <text x="50" y="56" textAnchor="middle" className="fill-neutral-400 text-[8px] font-semibold">
153
+ SeedGrid
154
+ </text>
155
+ </>
156
+ ),
157
+ }}
158
+ />
159
+ </div>
160
+ );
161
+ }
162
+ ```
163
+
164
+ ### Fusos horários
165
+
166
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-clock/samples/timezone.tsx.sample`
167
+
168
+ ```tsx
169
+ import * as React from "react";
170
+ import { SgButton, SgClock } from "@seedgrid/fe-components";
171
+
172
+ export default function Example(props: { initialServerTime?: string }) {
173
+ const [timezone, setTimezone] = React.useState("America/Sao_Paulo");
174
+ const [format, setFormat] = React.useState<"12h" | "24h">("24h");
175
+
176
+ return (
177
+ <>
178
+ <div className="flex flex-wrap gap-2">
179
+ <SgButton onClick={() => setTimezone("America/Sao_Paulo")}>Sao Paulo</SgButton>
180
+ <SgButton onClick={() => setTimezone("Europe/Lisbon")}>Lisboa</SgButton>
181
+ <SgButton onClick={() => setTimezone("America/New_York")}>New York</SgButton>
182
+ <SgButton onClick={() => setTimezone("Asia/Tokyo")}>Tokyo</SgButton>
183
+ <SgButton onClick={() => setFormat((value) => (value === "24h" ? "12h" : "24h"))}>
184
+ {format}
185
+ </SgButton>
186
+ </div>
187
+
188
+ <div className="mt-4 grid gap-3 md:grid-cols-2">
189
+ <SgClock
190
+ clockStyle="digital"
191
+ size="lg"
192
+ initialServerTime={props.initialServerTime ?? new Date().toISOString()}
193
+ timezone={timezone}
194
+ format={format}
195
+ showSeconds
196
+ />
197
+ <SgClock
198
+ clockStyle="analog"
199
+ size={220}
200
+ initialServerTime={props.initialServerTime ?? new Date().toISOString()}
201
+ themeId="seedgrid"
202
+ timezone={timezone}
203
+ showSeconds={false}
204
+ />
205
+ </div>
206
+ </>
207
+ );
208
+ }
209
+ ```
210
+
211
+ Observação: o tema `seedgrid` usado acima só resolve se os temas embutidos tiverem sido registrados (o showcase registra em `sg-clock-showcase-client.tsx`). Ver o exemplo seguinte.
212
+
213
+ ### Temas embutidos + provider + picker (exemplo próprio)
214
+
215
+ Os temas de `sgClockThemesBuiltIn` precisam ser registrados; o `SgClockThemePicker` lista os temas do `SgClockThemeProvider`.
216
+
217
+ ```tsx
218
+ "use client";
219
+
220
+ import React from "react";
221
+ import {
222
+ SgClock,
223
+ SgClockThemePicker,
224
+ SgClockThemeProvider,
225
+ registerThemes,
226
+ sgClockThemesBuiltIn
227
+ } from "@seedgrid/fe-components";
228
+
229
+ // Os temas embutidos NÃO são registrados automaticamente: registre uma vez no boot do app.
230
+ registerThemes(sgClockThemesBuiltIn);
231
+
232
+ export default function Example() {
233
+ const [themeId, setThemeId] = React.useState("classic");
234
+
235
+ return (
236
+ <SgClockThemeProvider value={{ mode: "fallback", fallbackThemeId: "classic" }}>
237
+ <SgClockThemePicker value={themeId} onChange={setThemeId} label="Tema" />
238
+ <SgClock clockStyle="analog" size={240} themeId={themeId} />
239
+ </SgClockThemeProvider>
240
+ );
241
+ }
242
+ ```
243
+
244
+ ## SgTimeProvider / useSgTime
245
+
246
+ ### Uso básico
247
+
248
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/providers/sg-time-provider/samples/uso-basico.tsx.sample`
249
+
250
+ ```tsx
251
+ import { SgTimeProvider, useSgTime } from "@seedgrid/fe-components";
252
+
253
+ function LiveClock() {
254
+ const time = useSgTime();
255
+ const date = new Date(time.nowMs());
256
+ const pad = (n: number) => String(n).padStart(2, "0");
257
+
258
+ return (
259
+ <span>
260
+ {pad(date.getHours())}:{pad(date.getMinutes())}:{pad(date.getSeconds())}
261
+ </span>
262
+ );
263
+ }
264
+
265
+ export default function App() {
266
+ return (
267
+ <SgTimeProvider initialServerTime={new Date().toISOString()}>
268
+ <LiveClock />
269
+ </SgTimeProvider>
270
+ );
271
+ }
272
+ ```
273
+
274
+ ## SgClockThemeProvider / useSgClockThemeResolver
275
+
276
+ ### Resolvedor via hook (modo `fallback`)
277
+
278
+ Só há temas a listar/resolver se algum tiver sido registrado (`registerThemes`) ou passado em `value.themes`; caso contrário `resolve("classic")` retorna `null`.
279
+
280
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/providers/sg-clock-theme-provider/samples/resolver-hook.tsx.sample`
281
+
282
+ ```tsx
283
+ "use client";
284
+
285
+ import { SgClockThemeProvider, useSgClockThemeResolver } from "@seedgrid/fe-components";
286
+
287
+ function TemasDisponiveis() {
288
+ // Fora de um SgClockThemeProvider o hook devolve null: sem provider nao ha
289
+ // resolvedor, e nao existe fallback implicito.
290
+ const resolver = useSgClockThemeResolver();
291
+ if (!resolver) return <div>Sem SgClockThemeProvider na arvore.</div>;
292
+
293
+ const classic = resolver.resolve("classic");
294
+ const inexistente = resolver.resolve("nao-existe");
295
+
296
+ return (
297
+ <div className="space-y-1 text-sm">
298
+ <div>temas visíveis: {resolver.list().map((theme) => theme.id).join(", ")}</div>
299
+ <div>resolve("classic"): {classic?.id ?? "null"}</div>
300
+ {/* modo "fallback" (padrão) cai no fallbackThemeId; em "strict" isto seria null */}
301
+ <div>resolve("nao-existe"): {inexistente?.id ?? "null"}</div>
302
+ </div>
303
+ );
304
+ }
305
+
306
+ export default function Example() {
307
+ return (
308
+ <SgClockThemeProvider value={{ mode: "fallback", fallbackThemeId: "classic" }}>
309
+ <TemasDisponiveis />
310
+ </SgClockThemeProvider>
311
+ );
312
+ }
313
+ ```
314
+
315
+ ## SgClockThemePreview
316
+
317
+ ### Galeria dos temas embutidos (exemplo próprio)
318
+
319
+ ```tsx
320
+ import { SgClockThemePreview, sgClockThemesBuiltIn } from "@seedgrid/fe-components";
321
+
322
+ export default function Example() {
323
+ return (
324
+ <div style={{ display: "flex", gap: 12 }}>
325
+ {sgClockThemesBuiltIn.map((theme) => (
326
+ <SgClockThemePreview key={theme.id} theme={theme} size={72} />
327
+ ))}
328
+ </div>
329
+ );
330
+ }
331
+ ```
332
+
333
+ ## SgLinearGauge
334
+
335
+ ### Horizontal com faixas e ponteiro arrastável
336
+
337
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-linear-gauge/samples/default-horizontal.tsx.sample`
338
+
339
+ ```tsx
340
+ "use client";
341
+
342
+ import React from "react";
343
+ import { SgLinearGauge } from "@seedgrid/fe-components";
344
+
345
+ export default function Example() {
346
+ const [value, setValue] = React.useState(64);
347
+
348
+ return (
349
+ <SgLinearGauge
350
+ min={0}
351
+ max={100}
352
+ value={value}
353
+ onValueChange={setValue}
354
+ ranges={[
355
+ { start: 0, end: 30, color: "#22c55e" },
356
+ { start: 30, end: 70, color: "#f59e0b" },
357
+ { start: 70, end: 100, color: "#ef4444" }
358
+ ]}
359
+ showTicks
360
+ showLabels
361
+ majorTickCount={5}
362
+ barPointer
363
+ primaryPointerDraggable
364
+ />
365
+ );
366
+ }
367
+ ```
368
+
369
+ ### Vertical com múltiplos ponteiros
370
+
371
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-linear-gauge/samples/vertical-multi-pointers.tsx.sample`
372
+
373
+ ```tsx
374
+ "use client";
375
+
376
+ import React from "react";
377
+ import { SgLinearGauge, type SgLinearGaugePointer } from "@seedgrid/fe-components";
378
+
379
+ export default function Example() {
380
+ const [temperature, setTemperature] = React.useState(28);
381
+ const [target, setTarget] = React.useState(72);
382
+ const pointers = React.useMemo<SgLinearGaugePointer[]>(
383
+ () => [
384
+ { id: "target", value: target, color: "hsl(var(--secondary))", shape: "diamond", size: 11, draggable: true },
385
+ { id: "temp", value: temperature, color: "hsl(var(--destructive))", shape: "circle", size: 10, draggable: true }
386
+ ],
387
+ [target, temperature]
388
+ );
389
+
390
+ return (
391
+ <SgLinearGauge
392
+ min={-10}
393
+ max={50}
394
+ value={temperature}
395
+ onValueChange={setTemperature}
396
+ orientation="vertical"
397
+ width={140}
398
+ height={360}
399
+ pointers={pointers}
400
+ onPointerValueChange={(pointerId, next) => {
401
+ if (pointerId === "target") setTarget(next);
402
+ if (pointerId === "temp") setTemperature(next);
403
+ }}
404
+ ranges={[
405
+ { start: -10, end: 15, color: "#38bdf8" },
406
+ { start: 15, end: 30, color: "#22c55e" },
407
+ { start: 30, end: 50, color: "#ef4444" }
408
+ ]}
409
+ majorTickCount={6}
410
+ minorTicksPerInterval={1}
411
+ labelFormatter={(value) => `${Math.round(value)}°`}
412
+ />
413
+ );
414
+ }
415
+ ```
416
+
417
+ ## SgRadialGauge
418
+
419
+ ### Estilo velocímetro com conteúdo central
420
+
421
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-radial-gauge/samples/speedometer-style.tsx.sample`
422
+
423
+ ```tsx
424
+ "use client";
425
+
426
+ import React from "react";
427
+ import { SgRadialGauge } from "@seedgrid/fe-components";
428
+
429
+ export default function Example() {
430
+ const [rpm, setRpm] = React.useState(62);
431
+
432
+ return (
433
+ <SgRadialGauge
434
+ min={0}
435
+ max={100}
436
+ value={rpm}
437
+ onValueChange={setRpm}
438
+ startAngle={140}
439
+ endAngle={40}
440
+ ranges={[
441
+ { start: 0, end: 40, color: "#22c55e", width: 14 },
442
+ { start: 40, end: 75, color: "#f59e0b", width: 14 },
443
+ { start: 75, end: 100, color: "#ef4444", width: 14 }
444
+ ]}
445
+ showTicks
446
+ showLabels
447
+ majorTickCount={5}
448
+ minorTicksPerInterval={2}
449
+ primaryPointerDraggable
450
+ primaryPointerType="needle"
451
+ primaryPointerColor="#2563eb"
452
+ centerContent={<div className="text-center"><div className="text-xs uppercase text-muted-foreground">RPM</div><div className="text-xl font-semibold">{Math.round(rpm)}</div></div>}
453
+ />
454
+ );
455
+ }
456
+ ```
457
+
458
+ ### Ponteiros extras (marker/range) e anotação
459
+
460
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-radial-gauge/samples/multi-pointers-annotation.tsx.sample`
461
+
462
+ ```tsx
463
+ "use client";
464
+
465
+ import React from "react";
466
+ import { SgRadialGauge, type SgRadialGaugePointer } from "@seedgrid/fe-components";
467
+
468
+ export default function Example() {
469
+ const [rpm, setRpm] = React.useState(62);
470
+ const [target, setTarget] = React.useState(70);
471
+ const [consumption, setConsumption] = React.useState(44);
472
+ const pointers = React.useMemo<SgRadialGaugePointer[]>(
473
+ () => [
474
+ { id: "target", type: "marker", value: target, color: "#8b5cf6", size: 13, shape: "diamond", draggable: true, label: String(Math.round(target)) },
475
+ { id: "consumption", type: "range", value: consumption, color: "#14b8a6", width: 10, draggable: true }
476
+ ],
477
+ [consumption, target]
478
+ );
479
+
480
+ return (
481
+ <SgRadialGauge
482
+ min={0}
483
+ max={120}
484
+ value={rpm}
485
+ onValueChange={setRpm}
486
+ startAngle={135}
487
+ endAngle={45}
488
+ radiusFactor={0.95}
489
+ ranges={[
490
+ { start: 0, end: 60, color: "#60a5fa", width: 12, opacity: 0.45 },
491
+ { start: 60, end: 90, color: "#f59e0b", width: 12, opacity: 0.45 },
492
+ { start: 90, end: 120, color: "#ef4444", width: 12, opacity: 0.45 }
493
+ ]}
494
+ pointers={pointers}
495
+ onPointerValueChange={(id, next) => {
496
+ if (id === "target") setTarget(next);
497
+ if (id === "consumption") setConsumption(next);
498
+ }}
499
+ annotations={[{ id: "target-label", value: target, radiusFactor: 0.8, content: <span className="rounded bg-violet-600 px-2 py-1 text-[10px] font-semibold text-white">target</span> }]}
500
+ primaryPointerDraggable
501
+ />
502
+ );
503
+ }
504
+ ```
505
+
506
+ ## SgQRCode
507
+
508
+ ### Interativo com logo e fallback
509
+
510
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-qr-code/samples/interactive-example.tsx.sample`
511
+
512
+ ```tsx
513
+ "use client";
514
+
515
+ import React from "react";
516
+ import { SgButton, SgQRCode } from "@seedgrid/fe-components";
517
+
518
+ export default function Example() {
519
+ const [value, setValue] = React.useState("https://seedgrid.com.br");
520
+ const [logoSrc, setLogoSrc] = React.useState("/logo-seedgrid.svg");
521
+ const [size, setSize] = React.useState(220);
522
+
523
+ return (
524
+ <div className="grid gap-6 md:grid-cols-[1fr_auto]">
525
+ <div className="space-y-4">
526
+ <label className="block space-y-1">
527
+ <span className="text-sm font-medium">Valor do QR Code</span>
528
+ <textarea
529
+ value={value}
530
+ onChange={(event) => setValue(event.target.value)}
531
+ rows={3}
532
+ className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-primary"
533
+ placeholder="Digite texto, URL ou payload PIX"
534
+ />
535
+ </label>
536
+
537
+ <label className="block space-y-1">
538
+ <span className="text-sm font-medium">Logo central (URL)</span>
539
+ <input
540
+ type="text"
541
+ value={logoSrc}
542
+ onChange={(event) => setLogoSrc(event.target.value)}
543
+ className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-primary"
544
+ placeholder="https://exemplo.com/logo.png"
545
+ />
546
+ </label>
547
+
548
+ <label className="block space-y-1">
549
+ <span className="text-sm font-medium">Tamanho: {size}px</span>
550
+ <input type="range" min={140} max={360} step={4} value={size} onChange={(event) => setSize(Number(event.target.value))} className="w-full" />
551
+ </label>
552
+
553
+ <div className="flex flex-wrap gap-2">
554
+ <SgButton onClick={() => setValue("https://seedgrid.com.br")}>URL</SgButton>
555
+ <SgButton severity="secondary" onClick={() => setValue("00020126580014BR.GOV.BCB.PIX0136contato@seedgrid.com.br5204000053039865802BR5922SEEDGRID TECNOLOGIA6009SAO PAULO62140510SEEDGRID1236304ABCD")}>
556
+ Exemplo PIX
557
+ </SgButton>
558
+ <SgButton severity="warning" onClick={() => setLogoSrc("")}>Sem logo</SgButton>
559
+ </div>
560
+ </div>
561
+
562
+ <div className="inline-flex h-fit rounded-xl border border-border bg-background p-4 shadow-sm">
563
+ <SgQRCode
564
+ value={value}
565
+ size={size}
566
+ logoSrc={logoSrc || undefined}
567
+ logoAlt="Logo SeedGrid"
568
+ emptyFallback={<span className="text-sm text-muted-foreground">Informe um valor</span>}
569
+ />
570
+ </div>
571
+ </div>
572
+ );
573
+ }
574
+ ```
575
+
576
+ ## SgStringAnimator
577
+
578
+ ### `autoStart` ao trocar o alvo
579
+
580
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-string-animator/samples/autostart-animacao-automatica.tsx.sample`
581
+
582
+ ```tsx
583
+ "use client";
584
+
585
+ import React from "react";
586
+ import { SgStringAnimator } from "@seedgrid/fe-components";
587
+
588
+ const NAMES = ["LUCIANO", "MARTA", "PEDRO", "ANA", "SEEDGRID"];
589
+
590
+ export default function Example() {
591
+ const [index, setIndex] = React.useState(0);
592
+ const [target, setTarget] = React.useState(NAMES[0] ?? "");
593
+
594
+ React.useEffect(() => {
595
+ const id = window.setInterval(() => {
596
+ setIndex((prev) => {
597
+ const next = (prev + 1) % NAMES.length;
598
+ setTarget(NAMES[next] ?? "");
599
+ return next;
600
+ });
601
+ }, 1800);
602
+ return () => window.clearInterval(id);
603
+ }, []);
604
+
605
+ return <SgStringAnimator sourceString={NAMES[index] ?? ""} targetString={target} stringAnimatorStyle="roller3d" alignTo="left" fontSize={28} autoStart />;
606
+ }
607
+ ```
608
+
609
+ ### Disparo imperativo via `ref` (números alinhados à direita)
610
+
611
+ Fonte: `front/seedgrid-fe-components/apps/showcase/src/app/components/gadgets/sg-string-animator/samples/roller-3d-numeros-right-aligned.tsx.sample`
612
+
613
+ ```tsx
614
+ "use client";
615
+
616
+ import React from "react";
617
+ import { SgStringAnimator, type SgStringAnimatorRef, SgButton } from "@seedgrid/fe-components";
618
+
619
+ export default function Example() {
620
+ const animRef = React.useRef<SgStringAnimatorRef>(null);
621
+
622
+ return (
623
+ <div className="flex flex-col gap-4">
624
+ <SgStringAnimator ref={animRef} sourceString="42" targetString="1337" stringAnimatorStyle="roller3d" alignTo="right" fontSize={28} />
625
+ <SgButton size="sm" onClick={() => animRef.current?.startAnimation()}>Animar</SgButton>
626
+ </div>
627
+ );
628
+ }
629
+ ```