4d8685e

← Histórico

fix(busca)barra de progresso enquanto o índice carrega

4d8685e+636Ver no GitHub

src/components/SearchPanel/SearchPanel.module.cssmodificado

@@ -1,8 +1,6 @@
 .panel {
-  /* 320 em vez de 280: a 280 o rodapé do celular não comportava o quinto
-     item, e a árvore truncava nomes como professor-particular.tsx. */
-  width: min(320px, 90vw);
-  min-width: min(320px, 90vw);
+  width: min(280px, 85vw);
+  min-width: min(280px, 85vw);
   height: 100%;
   overflow-y: auto;
   background-color: var(--background-primary);
@@ -42,6 +40,50 @@
   font-size: var(--font-size-sm);
 }
 
+/* Barra indeterminada do VS Code: uma faixa que atravessa em vez de girar. */
+.progress {
+  position: relative;
+  height: 2px;
+  margin-top: 0.5rem;
+  overflow: hidden;
+}
+
+.progress::before {
+  content: "";
+  position: absolute;
+  inset: 0;
+  width: 40%;
+  border-radius: 1px;
+  background-color: var(--primary-color);
+  /* Fora do estado ativo o rastro some, mas a faixa segue ocupando o espaço. */
+  opacity: 0;
+}
+
+.progressActive::before {
+  opacity: 1;
+  /* Anima só transform: fica na thread de composição e não força layout. */
+  animation: slide 1.4s ease-in-out infinite;
+}
+
+@keyframes slide {
+  from {
+    transform: translateX(-100%);
+  }
+  to {
+    transform: translateX(350%);
+  }
+}
+
+/* Movimento repetitivo é gatilho para quem tem sensibilidade vestibular; a
+   WCAG 2.3.3 pede respeitar a preferência do sistema. */
+@media (prefers-reduced-motion: reduce) {
+  .progressActive::before {
+    width: 100%;
+    opacity: 0.35;
+    animation: none;
+  }
+}
+
 .status {
   margin: 0.5rem 0 0.75rem;
   font-size: var(--font-size-xs);
@@ -118,7 +160,7 @@
   text-decoration: underline;
 }
 
-@media (max-width: 1067px) {
+@media (max-width: 768px) {
   .panel {
     height: auto;
     flex: 1;

src/components/SearchPanel/SearchPanel.tsxmodificado

@@ -89,6 +89,7 @@ const SearchPanel = ({ onCloseMenu }: SearchPanelProps) => {
 
   const term = normalize(query.trim());
   const ready = term.length >= 2;
+  const loading = !entries && !failed;
 
   const found = useMemo(() => {
     if (!ready || !entries) {
@@ -111,6 +112,10 @@ const SearchPanel = ({ onCloseMenu }: SearchPanelProps) => {
     if (failed) {
       return "Não foi possível carregar o índice do site.";
     }
+    /** Sem isto o painel dizia "0 resultados" enquanto o índice não chegava. */
+    if (loading) {
+      return "Carregando o índice do site.";
+    }
     if (!ready) {
       return "Digite ao menos dois caracteres.";
     }
@@ -136,7 +141,17 @@ const SearchPanel = ({ onCloseMenu }: SearchPanelProps) => {
         onChange={(event) => setQuery(event.target.value)}
       />
 
-      <p className={styles.status} aria-live="polite">
+      {/* Sempre no DOM, mesmo parada: a faixa reserva os 2px e evita que o
+          conteúdo pule quando o índice chega. */}
+      <div
+        className={`${styles.progress} ${loading ? styles.progressActive : ""}`}
+        aria-hidden="true"
+      />
+
+      {/* Enquanto carrega, o aviso vira só texto para leitor de tela — quem
+          enxerga já tem a barra. O elemento não sai do DOM porque região
+          aria-live trocada na hora do anúncio não é lida de forma confiável. */}
+      <p className={loading ? styles.srOnly : styles.status} aria-live="polite">
         {status()}
       </p>