rl-core-front 0.19.9 → 0.19.10

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,6 +1,6 @@
1
1
  {
2
2
  "name": "rl-core-front",
3
- "version": "0.19.9",
3
+ "version": "0.19.10",
4
4
  "description": "Telas e componentes Next.js do core: login com 2FA, usuários, RBAC, auditoria, logs e listagens com filtro dinâmico",
5
5
  "author": "Rodrigo Liberti",
6
6
  "license": "MIT",
@@ -549,12 +549,19 @@ export function AppShell({
549
549
  <Menu className="h-5 w-5" />
550
550
  </Button>
551
551
 
552
- <div className="flex items-center gap-2">
552
+ {/* A marca leva para a raiz do projeto: é o gesto que todo site tem,
553
+ e sem ele o caminho de volta ao início é achar "Painel" no menu. */}
554
+ <button
555
+ type="button"
556
+ onClick={() => go("/")}
557
+ aria-label={brand.name || t("app.name")}
558
+ className="flex cursor-pointer items-center gap-2 rounded-lg px-1.5 py-1 transition-colors hover:bg-accent"
559
+ >
553
560
  {brand.mark}
554
561
  <span className="hidden text-lg font-extrabold sm:block">
555
562
  {brand.name}
556
563
  </span>
557
- </div>
564
+ </button>
558
565
 
559
566
  <div className="flex-1" />
560
567
 
@@ -623,10 +630,15 @@ export function AppShell({
623
630
  onClick={() => setMobileOpen(false)}
624
631
  />
625
632
  <aside className="absolute left-0 top-0 flex h-full w-64 flex-col border-r border-border bg-card">
626
- <div className="flex h-16 items-center gap-2 border-b border-border px-4">
633
+ <button
634
+ type="button"
635
+ onClick={() => go("/")}
636
+ aria-label={brand.name || t("app.name")}
637
+ className="flex h-16 w-full cursor-pointer items-center gap-2 border-b border-border px-4 text-left transition-colors hover:bg-accent"
638
+ >
627
639
  {brand.mark}
628
640
  <span className="text-lg font-extrabold">{brand.name}</span>
629
- </div>
641
+ </button>
630
642
  <NavContent
631
643
  mini={false}
632
644
  nav={nav}
@@ -89,7 +89,23 @@ export function useTabState<T extends string>(
89
89
  if (!hydrated) {
90
90
  return;
91
91
  }
92
- if (tab !== undefined && readUrlParam(param) !== tab) {
92
+
93
+ /*
94
+ A URL de **agora**, e não a do render que criou este efeito: chegando por
95
+ um link, o Next troca a querystring no commit, depois que esta tela já
96
+ renderizou lendo a URL anterior. Aí `tab` ainda é o do armazenamento, e
97
+ escrevê-lo apagaria justamente a aba que o link pediu — a tela abria na
98
+ aba de ontem com a URL reescrita para ela, e só um F5 acertava.
99
+
100
+ Aba válida na URL manda, sempre: não há o que espelhar.
101
+ */
102
+ const atual = readUrlParam(param);
103
+
104
+ if (atual !== null && (known.current as readonly string[]).includes(atual)) {
105
+ return;
106
+ }
107
+
108
+ if (tab !== undefined) {
93
109
  writeUrlTab(tab);
94
110
  }
95
111
  }, [hydrated, tab, param, writeUrlTab]);
@@ -5,7 +5,51 @@ import { useCallback, useSyncExternalStore } from "react";
5
5
  /** Quem lê a querystring, para saber que ela mudou por aqui. */
6
6
  const listeners = new Set<() => void>();
7
7
 
8
+ const notify = (): void => {
9
+ for (const listener of listeners) {
10
+ listener();
11
+ }
12
+ };
13
+
14
+ /** A instrumentação do histórico é instalada uma vez só, no primeiro leitor. */
15
+ let watchingHistory = false;
16
+
17
+ /**
18
+ * Faz `pushState` e `replaceState` avisarem quem lê a querystring.
19
+ *
20
+ * Nenhum dos dois dispara evento — `popstate` é só do "voltar" do navegador —,
21
+ * e **a navegação do Next é `pushState`**: o App Router troca a URL num efeito
22
+ * (`HistoryUpdater`) depois que a tela nova já renderizou. Sem este aviso,
23
+ * quem abriu a tela por um link com `?tab=` lia a URL *anterior*, caía no
24
+ * valor guardado e ficava nele — a URL dizia uma aba e a tela mostrava outra,
25
+ * e só um F5 acertava.
26
+ *
27
+ * Instrumentar o histórico, e não `useSearchParams` do Next: aquele não
28
+ * enxerga o `replaceState` que este arquivo faz (o `?filter=`, o `?month=`),
29
+ * e obrigaria todo consumidor a nascer dentro de um `<Suspense>`.
30
+ */
31
+ const watchHistory = (): void => {
32
+ if (watchingHistory || typeof window === "undefined") {
33
+ return;
34
+ }
35
+
36
+ watchingHistory = true;
37
+
38
+ for (const method of ["pushState", "replaceState"] as const) {
39
+ const original = window.history[method];
40
+
41
+ window.history[method] = function patched(
42
+ this: History,
43
+ ...args: Parameters<History["pushState"]>
44
+ ): void {
45
+ original.apply(this, args);
46
+ notify();
47
+ };
48
+ }
49
+ };
50
+
8
51
  const subscribe = (onChange: () => void): (() => void) => {
52
+ watchHistory();
9
53
  listeners.add(onChange);
10
54
  // O "voltar" do navegador também troca o valor, e esse não passa por aqui.
11
55
  window.addEventListener("popstate", onChange);
@@ -28,6 +72,12 @@ export const readUrlParam = (name: string): string | null =>
28
72
  * da tela.
29
73
  */
30
74
  export const writeUrlParam = (name: string, value: string | null): void => {
75
+ // Escrever pode acontecer antes de qualquer leitor montar — e é o
76
+ // `replaceState` instrumentado que avisa. Sem esta linha, o aviso dependeria
77
+ // da ordem em que as coisas montaram, que é o tipo de laço que funciona até
78
+ // o dia em que alguém escreve primeiro.
79
+ watchHistory();
80
+
31
81
  const params = new URLSearchParams(window.location.search);
32
82
 
33
83
  if (value === null) {
@@ -43,11 +93,8 @@ export const writeUrlParam = (name: string, value: string | null): void => {
43
93
  query ? `${window.location.pathname}?${query}` : window.location.pathname,
44
94
  );
45
95
 
46
- // `replaceState` não dispara evento nenhum: sem este aviso, quem lê o
47
- // parâmetro ficaria com o valor antigo até o próximo render por outro motivo.
48
- for (const listener of listeners) {
49
- listener();
50
- }
96
+ // O aviso vem do `replaceState` instrumentado (`watchHistory`), que avisa
97
+ // por qualquer caminho — inclusive pelas trocas de URL do próprio Next.
51
98
  };
52
99
 
53
100
  /**