MelissaMenu: busca no topo do mm-side (estilo rail)
Input com pi-search a esquerda + botao limpar a direita. Quando query tem texto, substitui a lista de categorias por uma lista flat de sub-itens que casam (com nome da categoria a direita como breadcrumb). Click no resultado dispara clicarSubItem (mesma logica de navegacao) e limpa o termo. Empty state pra "nenhum resultado". Visual segue mm-aside: bg --m-bg-soft, border --m-border, focus border --p-primary-color. Hover dos resultados usa color-mix primary 12% (mesmo pattern do .mm-foot-item). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,31 @@ function categoryKeyFor(itemKey) {
|
||||
const selectedKey = ref(categoryKeyFor(props.secaoAtiva) || CATEGORIAS[0].key);
|
||||
const copiado = ref(false);
|
||||
|
||||
// Busca — quando query tem texto, substitui a lista de categorias
|
||||
// por uma lista flat de sub-itens que casam com o termo.
|
||||
const query = ref('');
|
||||
const searchResults = computed(() => {
|
||||
const q = query.value.trim().toLowerCase();
|
||||
if (!q) return [];
|
||||
const out = [];
|
||||
for (const cat of CATEGORIAS) {
|
||||
for (const group of cat.groups) {
|
||||
for (const item of group.items) {
|
||||
if (item.tipo === 'link-cadastro') continue;
|
||||
const label = (item.label || '').toLowerCase();
|
||||
if (label.includes(q)) {
|
||||
out.push({ catLabel: cat.label, item });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
function clicarResultado(item) {
|
||||
clicarSubItem(item);
|
||||
query.value = '';
|
||||
}
|
||||
|
||||
// Sincroniza o destaque com a sessao ativa — se o user troca de
|
||||
// "Lancamentos" pra "Agenda" sem fechar o menu, o selecionado
|
||||
// acompanha a sessao corrente.
|
||||
@@ -382,7 +407,48 @@ async function sair() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mm-side__list">
|
||||
<!-- Busca (estilo rail) — filtra sub-itens de todas as categorias -->
|
||||
<div class="mm-search">
|
||||
<i class="pi pi-search mm-search__icon" />
|
||||
<input
|
||||
v-model="query"
|
||||
type="text"
|
||||
inputmode="search"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
placeholder="Encontrar menu..."
|
||||
class="mm-search__input"
|
||||
/>
|
||||
<button
|
||||
v-if="query"
|
||||
class="mm-search__clear"
|
||||
type="button"
|
||||
aria-label="Limpar busca"
|
||||
@click="query = ''"
|
||||
>
|
||||
<i class="pi pi-times" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Resultados de busca (flat, com nome da categoria) -->
|
||||
<div v-if="query.trim()" class="mm-search__results">
|
||||
<button
|
||||
v-for="r in searchResults"
|
||||
:key="`${r.catLabel}_${r.item.key}`"
|
||||
class="mm-search__result"
|
||||
@click="clicarResultado(r.item)"
|
||||
>
|
||||
<i :class="r.item.icon" class="mm-search__result-icon" />
|
||||
<span class="mm-search__result-label">{{ r.item.label }}</span>
|
||||
<span class="mm-search__result-cat">{{ r.catLabel }}</span>
|
||||
</button>
|
||||
<div v-if="!searchResults.length" class="mm-search__empty">
|
||||
Nenhum resultado pra "{{ query }}"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista normal de categorias (so quando busca vazia) -->
|
||||
<div v-else class="mm-side__list">
|
||||
<button
|
||||
v-for="c in CATEGORIAS"
|
||||
:key="c.key"
|
||||
@@ -724,6 +790,121 @@ async function sair() {
|
||||
transition: background-color 140ms ease;
|
||||
}
|
||||
.mm-side__close:hover { background: var(--m-bg-soft-hover); }
|
||||
/* Busca no topo do mm-side — espelha o pattern do AppMenu rail */
|
||||
.mm-search {
|
||||
position: relative;
|
||||
margin: 0 8px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--m-bg-soft);
|
||||
border: 1px solid var(--m-border);
|
||||
border-radius: 10px;
|
||||
transition: border-color 140ms ease, background-color 140ms ease;
|
||||
}
|
||||
.mm-search:focus-within {
|
||||
border-color: var(--p-primary-color);
|
||||
background: var(--m-bg-medium);
|
||||
}
|
||||
.mm-search__icon {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
color: var(--m-text-muted);
|
||||
font-size: 0.85rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
.mm-search:focus-within .mm-search__icon { color: var(--p-primary-color); }
|
||||
.mm-search__input {
|
||||
width: 100%;
|
||||
padding: 8px 30px 8px 32px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--m-text);
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
outline: none;
|
||||
}
|
||||
.mm-search__input::placeholder { color: var(--m-text-faint); }
|
||||
.mm-search__clear {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--m-text-muted);
|
||||
cursor: pointer;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 0.7rem;
|
||||
transition: color 120ms ease, background-color 120ms ease;
|
||||
}
|
||||
.mm-search__clear:hover {
|
||||
background: var(--m-bg-soft-hover);
|
||||
color: var(--m-text);
|
||||
}
|
||||
|
||||
.mm-search__results {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 8px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--m-border-strong) transparent;
|
||||
}
|
||||
.mm-search__results::-webkit-scrollbar { width: 5px; }
|
||||
.mm-search__results::-webkit-scrollbar-thumb {
|
||||
background: var(--m-border-strong);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.mm-search__result {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: var(--m-text);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
transition: background-color 120ms ease, color 120ms ease;
|
||||
}
|
||||
.mm-search__result:hover {
|
||||
background: color-mix(in srgb, var(--p-primary-color) 12%, transparent);
|
||||
color: var(--p-primary-color);
|
||||
}
|
||||
.mm-search__result-icon {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
color: var(--p-primary-color);
|
||||
font-size: 0.85rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.mm-search__result-label {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mm-search__result-cat {
|
||||
font-size: 0.68rem;
|
||||
color: var(--m-text-muted);
|
||||
flex-shrink: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.mm-search__empty {
|
||||
padding: 16px 12px;
|
||||
color: var(--m-text-muted);
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.mm-side__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in New Issue
Block a user