diff --git a/package-lock.json b/package-lock.json index d61e6db..4d02c62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,6 +124,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2972,6 +2973,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2982,6 +2984,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2998,6 +3001,7 @@ "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.1", @@ -3028,6 +3032,7 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -3502,6 +3507,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3854,6 +3860,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -4658,6 +4665,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4718,6 +4726,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4844,6 +4853,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6998,6 +7008,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.5.tgz", "integrity": "sha512-OQVdBPtpBfq7HxFN0kOVb7rXXOSIkt5lTzDJDGRBcOyVvNRIWFauMqi1gIHd1pszq1542vMOGY0HP4CaiALfkA==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.5", "@swc/helpers": "0.5.15", @@ -7394,6 +7405,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -7551,6 +7563,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7560,6 +7573,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -7572,6 +7586,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz", "integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7587,7 +7602,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-masked-text": { "version": "1.0.5", @@ -7600,6 +7616,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -7718,7 +7735,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -8478,6 +8496,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8489,7 +8508,8 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-8.1.2.tgz", "integrity": "sha512-KITxHEEHRlxC5xOnxA123eAJ67NgsWxNphtItWt9TRu07DiTZrWIqJeIKRX9euE51/l3kJO4WQiqoBXKTJJGsA==", - "license": "GPL-2.0-or-later" + "license": "GPL-2.0-or-later", + "peer": true }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -8643,6 +8663,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8708,6 +8729,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -8994,6 +9016,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/packages/administrativo/components/GGramatica/GGramaticaForm.tsx b/src/packages/administrativo/components/GGramatica/GGramaticaForm.tsx index 25bfa17..b9710c3 100644 --- a/src/packages/administrativo/components/GGramatica/GGramaticaForm.tsx +++ b/src/packages/administrativo/components/GGramatica/GGramaticaForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { diff --git a/src/packages/administrativo/components/GGramatica/GGramaticaIndex.tsx b/src/packages/administrativo/components/GGramatica/GGramaticaIndex.tsx index be6e005..e4be82b 100644 --- a/src/packages/administrativo/components/GGramatica/GGramaticaIndex.tsx +++ b/src/packages/administrativo/components/GGramatica/GGramaticaIndex.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState, useCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useGGramaticaDeleteHook } from '@/packages/administrativo/hooks/GGramatica/useGGramaticaDeleteHook'; diff --git a/src/packages/administrativo/components/GGramatica/GGramaticaTable.tsx b/src/packages/administrativo/components/GGramatica/GGramaticaTable.tsx index 648ccaf..58295e9 100644 --- a/src/packages/administrativo/components/GGramatica/GGramaticaTable.tsx +++ b/src/packages/administrativo/components/GGramatica/GGramaticaTable.tsx @@ -2,8 +2,8 @@ import { DataTable } from '@/shared/components/dataTable/DataTable'; -import GGramaticaColumns from './GGramaticaColumns'; import GGramaticaTableInterface from '../../interfaces/GGramatica/GGramaticaTableInterface'; +import GGramaticaColumns from './GGramaticaColumns'; /** * Componente principal da tabela de Naturezas diff --git a/src/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoList.tsx b/src/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoList.tsx index 3490cfa..3bd40f8 100644 --- a/src/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoList.tsx +++ b/src/packages/servicos/components/TServicoItemPedido/TServicoItemPedidoList.tsx @@ -15,7 +15,7 @@ export default function TServicoItemPedidoList({ items }: TServicoItemPedidoList {/* Altura máxima + scroll vertical */}
- {items.map((item) => ( + {items?.map((item) => (
('pedido'); + const sections: StepSection[] = [ + { key: 'pedido', id: 'selectPedido', icon: , title: 'Pedido', description: 'Dados gerais do pedido.' }, + { key: 'servicoPedidoItem', id: 'selectServicoPedidoItem', icon: , title: 'Itens', description: 'Itens/serviços do pedido.' }, + { key: 'payment', id: 'selectPayment', icon: , title: 'Pagamento', description: 'Forma e dados de pagamento.' }, + ]; + + const ref = React.useRef(null); // submit e error do RHF - function onSubmit(values: any) { - console.log('Submit:', values); - } + const onSave = useCallback( + + async (formData: TServicoPedidoInterface) => { + + console.log(formData); + + // Coloca o botão em estado de loading + setButtonIsLoading(true); + + // Aguarda salvar o registro + const response = await saveTServicoPedido(formData); + + // Verifica se pode redirecionar o usuário + if (response.servico_pedido_id > 0) { + + // Redirecionamento de página + router.replace('/servicos/balcao/detalhes/' + response.servico_pedido_id) + } + + // Remove o botão em estado de loading + setButtonIsLoading(false); + + }, + [saveTServicoPedido, TServicoPedido], + ); + function onError(error: any) { console.log('Erro no formulário:', error); } - // === Bloqueio do scroll-spy durante rolagem programática === - const spyLockedRef = React.useRef(false); - const spyTimerRef = React.useRef(null); - - const lockSpy = React.useCallback(() => { - spyLockedRef.current = true; - if (spyTimerRef.current) window.clearTimeout(spyTimerRef.current); - spyTimerRef.current = window.setTimeout(() => { - spyLockedRef.current = false; - }, SPY_LOCK_MS) as unknown as number; - }, []); - - const scrollToSection = React.useCallback( - (id: string) => { - const el = document.getElementById(id); - if (!el) return; - lockSpy(); - const y = el.getBoundingClientRect().top + window.scrollY - SCROLL_OFFSET; - window.scrollTo({ top: y, behavior: 'smooth' }); - }, - [lockSpy] - ); - - // Scroll spy - React.useEffect(() => { - const handler = () => { - if (spyLockedRef.current) return; - - let current: StepKey = 'pedido'; - let best = Number.POSITIVE_INFINITY; - - for (const { key, id } of SECTION_ORDER) { - const el = document.getElementById(id); - if (!el) continue; - const dist = Math.abs(el.getBoundingClientRect().top - SCROLL_OFFSET - 8); - if (dist < best) { - best = dist; - current = key; - } - } - if (current !== active) setActive(current); - }; - - handler(); - window.addEventListener('scroll', handler, { passive: true }); - return () => window.removeEventListener('scroll', handler); - }, [active]); - - // Atualiza o formulário quando recebe dados para edição - React.useEffect(() => { - ResetFormIfData(form, data); - }, [data, form]); - - const dataItens = [ - { - 'emolumento': 4.99, - 'fundesp': 1.21, - 'taxa_judiciaria': 0, - 'valor_iss': 0.25, - 'valor': 6.45, - 'servico': 'Autenticação Original', - 'tabela': 'Autenticação por página' - }, - { - 'emolumento': 3.50, - 'fundesp': 0.85, - 'taxa_judiciaria': 0, - 'valor_iss': 0.18, - 'valor': 4.53, - 'servico': 'Autenticação de Cópia', - 'tabela': 'Autenticação por página' - }, - { - 'emolumento': 5.90, - 'fundesp': 1.20, - 'taxa_judiciaria': 0, - 'valor_iss': 0.30, - 'valor': 7.40, - 'servico': 'Reconhecimento de Firma (Semelhança)', - 'tabela': 'Reconhecimento de firma' - }, - { - 'emolumento': 8.50, - 'fundesp': 1.75, - 'taxa_judiciaria': 0, - 'valor_iss': 0.43, - 'valor': 10.68, - 'servico': 'Reconhecimento de Firma (Autenticidade)', - 'tabela': 'Reconhecimento de firma' - }, - { - 'emolumento': 12.00, - 'fundesp': 2.50, - 'taxa_judiciaria': 0, - 'valor_iss': 0.60, - 'valor': 15.10, - 'servico': 'Abertura de Firma', - 'tabela': 'Ficha de assinatura' - }, - { - 'emolumento': 18.00, - 'fundesp': 3.60, - 'taxa_judiciaria': 0, - 'valor_iss': 0.90, - 'valor': 22.50, - 'servico': 'Procuração Particular', - 'tabela': 'Procuração por folha' - }, - { - 'emolumento': 35.00, - 'fundesp': 7.00, - 'taxa_judiciaria': 0, - 'valor_iss': 1.75, - 'valor': 43.75, - 'servico': 'Procuração Pública', - 'tabela': 'Procuração (tabela geral)' - }, - { - 'emolumento': 20.00, - 'fundesp': 4.00, - 'taxa_judiciaria': 0, - 'valor_iss': 1.00, - 'valor': 25.00, - 'servico': 'Certidão Simples', - 'tabela': 'Certidões' - }, - { - 'emolumento': 45.00, - 'fundesp': 9.00, - 'taxa_judiciaria': 0, - 'valor_iss': 2.25, - 'valor': 56.25, - 'servico': 'Certidão em Inteiro Teor', - 'tabela': 'Certidões' - }, - { - 'emolumento': 80.00, - 'fundesp': 16.00, - 'taxa_judiciaria': 0, - 'valor_iss': 4.00, - 'valor': 100.00, - 'servico': 'Ata Notarial', - 'tabela': 'Ata por página' - }, - { - 'emolumento': 7.50, - 'fundesp': 1.50, - 'taxa_judiciaria': 0, - 'valor_iss': 0.38, - 'valor': 9.38, - 'servico': 'Testemunha em Documento', - 'tabela': 'Outras declarações' - }, - { - 'emolumento': 6.20, - 'fundesp': 1.24, - 'taxa_judiciaria': 0, - 'valor_iss': 0.31, - 'valor': 7.75, - 'servico': 'Autenticação de Documento Eletrônico', - 'tabela': 'Autenticação digital' - }, - { - 'emolumento': 25.00, - 'fundesp': 5.00, - 'taxa_judiciaria': 0, - 'valor_iss': 1.25, - 'valor': 31.25, - 'servico': 'Reconhecimento de Sinal Público', - 'tabela': 'Sinal público' - }, - { - 'emolumento': 10.00, - 'fundesp': 2.00, - 'taxa_judiciaria': 0, - 'valor_iss': 0.50, - 'valor': 12.50, - 'servico': 'Certificação de Cópia Digital', - 'tabela': 'Certificação digital' - }, - { - 'emolumento': 9.90, - 'fundesp': 1.98, - 'taxa_judiciaria': 0, - 'valor_iss': 0.50, - 'valor': 12.38, - 'servico': 'Arquivamento de Documento', - 'tabela': 'Arquivamento' - } - ]; - - return (

Pedido

- +
{/* Sidebar - Sticky */}
@@ -516,48 +324,4 @@ export default function TServicoPedidoForm() {
); -} - -/* ---------- Components auxiliares ---------- */ - -function StepLink({ - active, - onClick, - icon, - title, - description, -}: { - active?: boolean; - onClick?: () => void; - icon: React.ReactNode; - title: string; - description: string; -}) { - return ( - - ); -} +} \ No newline at end of file diff --git a/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormHook.ts b/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormHook.ts index 697c882..fc60cba 100644 --- a/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormHook.ts +++ b/src/packages/servicos/hooks/TServicoPedido/useTServicoPedidoFormHook.ts @@ -8,6 +8,19 @@ export function useTServicoPedidoFormHook(defaults?: Partial; \ No newline at end of file diff --git a/src/shared/components/response/ResponseContext.tsx b/src/shared/components/response/ResponseContext.tsx index e11a004..81d404d 100644 --- a/src/shared/components/response/ResponseContext.tsx +++ b/src/shared/components/response/ResponseContext.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { createContext, useContext, useState, ReactNode } from 'react'; +import React, { createContext, ReactNode, useContext, useState } from 'react'; interface ResponseState { message?: string; diff --git a/src/shared/components/step/stepNavigator.tsx b/src/shared/components/step/stepNavigator.tsx new file mode 100644 index 0000000..a6f55d1 --- /dev/null +++ b/src/shared/components/step/stepNavigator.tsx @@ -0,0 +1,256 @@ +'use client'; + +import * as React from 'react'; + +import { cn } from '@/lib/utils'; // ajuste o caminho conforme seu projeto + +/** ========================= + * Constantes (com defaults) + * ========================= */ +const DEFAULT_SCROLL_OFFSET = 16; +const DEFAULT_SPY_LOCK_MS = 600; + +/** =========== + * Tipos + * =========== */ +export type StepKey = string; + +export type StepSection = { + /** chave lógica do step (ex.: 'pedido') */ + key: StepKey; + /** id do elemento alvo no DOM (ex.: 'selectPedido') */ + id: string; + /** ícone (lucide ou outro) */ + icon: React.ReactNode; + /** título do step */ + title: string; + /** descrição curta do step */ + description?: string; +}; + +export type StepNavigatorProps = { + /** Lista de seções em ordem */ + sections: StepSection[]; + /** Ativo controlado externamente */ + active?: StepKey; + /** Ativo padrão (modo não-controlado) */ + defaultActive?: StepKey; + /** Callback ao mudar de seção (por scroll ou clique) */ + onChange?: (key: StepKey) => void; + + /** Offset do topo ao calcular posicionamento (ex.: header fixo) */ + scrollOffset?: number; + /** Tempo de bloqueio do spy após scroll programático */ + spyLockMs?: number; + + /** Classe extra no container */ + className?: string; + /** Se `false`, desabilita o scroll-spy */ + enableScrollSpy?: boolean; +}; + +/** ================= + * Hook: useScrollSpy + * ================= */ +function useScrollSpy(opts: { + sections: StepSection[]; + active: StepKey | undefined; + setActive: (key: StepKey) => void; + scrollOffset: number; + enable: boolean; + lockMs: number; +}) { + const { sections, active, setActive, scrollOffset, enable, lockMs } = opts; + + // bloqueio do spy durante rolagem programática + const spyLockedRef = React.useRef(false); + const spyTimerRef = React.useRef(null); + + const lockSpy = React.useCallback(() => { + spyLockedRef.current = true; + if (spyTimerRef.current) window.clearTimeout(spyTimerRef.current); + spyTimerRef.current = window.setTimeout(() => { + spyLockedRef.current = false; + }, lockMs) as unknown as number; + }, [lockMs]); + + const scrollToId = React.useCallback( + (id: string) => { + const el = document.getElementById(id); + if (!el) return; + lockSpy(); + const y = el.getBoundingClientRect().top + window.scrollY - scrollOffset; + window.scrollTo({ top: y, behavior: 'smooth' }); + }, + [lockSpy, scrollOffset] + ); + + React.useEffect(() => { + if (!enable) return; + + const handler = () => { + if (spyLockedRef.current) return; + + let current: StepKey | undefined = undefined; + let best = Number.POSITIVE_INFINITY; + + for (const { key, id } of sections) { + const el = document.getElementById(id); + if (!el) continue; + const dist = Math.abs(el.getBoundingClientRect().top - scrollOffset - 8); + if (dist < best) { + best = dist; + current = key; + } + } + + if (current && current !== active) { + setActive(current); + } + }; + + handler(); // avalia logo ao montar + window.addEventListener('scroll', handler, { passive: true }); + return () => window.removeEventListener('scroll', handler); + }, [enable, sections, active, scrollOffset, setActive]); + + return { scrollToId, lockSpy }; +} + +/** ================== + * Subcomponente: StepLink + * ================== */ +function StepLink({ + active, + onClick, + icon, + title, + description, +}: { + active?: boolean; + onClick?: () => void; + icon: React.ReactNode; + title: string; + description?: string; +}) { + return ( + + ); +} + +/** =================== + * Componente principal + * =================== */ +export type StepNavigatorRef = { + /** Rola até a seção pela key ou id */ + scrollTo: (target: StepKey | { id: string }) => void; +}; + +export const StepNavigator = React.forwardRef( + ( + { + sections, + active: activeProp, + defaultActive, + onChange, + scrollOffset = DEFAULT_SCROLL_OFFSET, + spyLockMs = DEFAULT_SPY_LOCK_MS, + className, + enableScrollSpy = true, + }, + ref + ) => { + // estado controlado/ não-controlado + const [activeState, setActiveState] = React.useState( + activeProp ?? defaultActive ?? sections[0]?.key + ); + + // sincroniza quando for controlado + React.useEffect(() => { + if (activeProp !== undefined) setActiveState(activeProp); + }, [activeProp]); + + const setActive = React.useCallback( + (key: StepKey) => { + if (activeProp === undefined) { + setActiveState(key); + } + onChange?.(key); + }, + [activeProp, onChange] + ); + + const { scrollToId, lockSpy } = useScrollSpy({ + sections, + active: activeState, + setActive, + scrollOffset, + enable: enableScrollSpy, + lockMs: spyLockMs, + }); + + const scrollTo = React.useCallback( + (target: StepKey | { id: string }) => { + const id = + typeof target === 'string' + ? sections.find((s) => s.key === target)?.id + : target.id; + if (!id) return; + scrollToId(id); + }, + [sections, scrollToId] + ); + + React.useImperativeHandle(ref, () => ({ scrollTo }), [scrollTo]); + + return ( + + ); + } +); +StepNavigator.displayName = 'StepNavigator'; \ No newline at end of file