- Conversa: chat empurrado por SSE, indicador de "pensando", gesto da resposta com repetir, voz com aviso claro quando o contexto nao e seguro e leitura em voz alta so de respostas novas - Memoria unificada: lembrancas da conversa + rotina/preferencias em abas; apagar tudo com confirmacao inline (sem confirm nativo) - Reacoes: biblioteca com miniaturas estaticas (sem iframe por cartao), busca, mostrar no celular, feedback, editar em <dialog>, duplicar, ativar e excluir com Desfazer no toast; criar com IA; diario; grade de gestos com estrela para favoritos; uma unica previa lateral - treinamento recarrega por evento SSE ao terminar, sem polling - corrige icones encolhidos em botoes .pequeno.icone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
1.8 KiB
Svelte
65 lines
1.8 KiB
Svelte
<script lang="ts" generics="T extends string">
|
|
let { abas, ativa = $bindable(), rotulo }: { abas: { id: T; titulo: string; contador?: number }[]; ativa: T; rotulo: string } =
|
|
$props();
|
|
|
|
function tecla(e: KeyboardEvent, i: number) {
|
|
const d = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;
|
|
if (!d) return;
|
|
const prox = abas[(i + d + abas.length) % abas.length];
|
|
ativa = prox.id;
|
|
(e.currentTarget as HTMLElement).parentElement?.querySelector<HTMLElement>(`[data-aba="${prox.id}"]`)?.focus();
|
|
}
|
|
</script>
|
|
|
|
<div class="abas" role="tablist" aria-label={rotulo}>
|
|
{#each abas as aba, i (aba.id)}
|
|
<button
|
|
role="tab"
|
|
data-aba={aba.id}
|
|
aria-selected={ativa === aba.id}
|
|
tabindex={ativa === aba.id ? 0 : -1}
|
|
onclick={() => (ativa = aba.id)}
|
|
onkeydown={(e) => tecla(e, i)}
|
|
>
|
|
{aba.titulo}
|
|
{#if aba.contador !== undefined}<span class="contador">{aba.contador}</span>{/if}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<style>
|
|
.abas {
|
|
display: flex;
|
|
gap: var(--e-1);
|
|
border-bottom: 1px solid var(--borda);
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
}
|
|
button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--e-2);
|
|
padding: var(--e-2) var(--e-3);
|
|
min-height: 40px;
|
|
border: 0;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -1px;
|
|
background: transparent;
|
|
color: var(--texto-3);
|
|
font: 500 var(--t-sm) var(--fonte);
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
button[aria-selected='true'] {
|
|
color: var(--texto);
|
|
border-bottom-color: var(--destaque);
|
|
}
|
|
.contador {
|
|
font-size: var(--t-xs);
|
|
padding: 0 6px;
|
|
border-radius: var(--raio-pill);
|
|
background: var(--superficie-3);
|
|
color: var(--texto-2);
|
|
}
|
|
</style>
|