fix(manager): favorites filter as wp-input box matching prototype

Replace chip-toggle approach with the correct prototype pattern: a
button.wp-input containing an inline wp-check span, styled identically to
the Category Select in the same 2-column filter grid. Clicking the whole
bordered box toggles the favorites filter. Uses span.wp-check (visual-only)
to avoid invalid button-in-button HTML nesting. Reverts intermediate chip
approach.
This commit is contained in:
DumiFlex
2026-04-27 23:41:22 +03:00
parent f51b752fd7
commit c246360b97
9 changed files with 100 additions and 25 deletions
+3 -5
View File
@@ -119,11 +119,9 @@ describe("ModuleListView.vue", () => {
await filtersBtn.trigger("click");
await flushPromises();
expect(wrap.find(".wp-filter-panel").exists()).toBe(true);
// Favorites quick-filter chip and extra-filter chips are both rendered.
const toggleChips = wrap.findAll(".wp-chip--toggle");
expect(toggleChips.length).toBeGreaterThanOrEqual(2);
expect(toggleChips.some((c) => c.text().includes("Favorites only"))).toBe(true);
expect(toggleChips.some((c) => c.text().includes("Favorited"))).toBe(true);
// Extra filter chip is rendered inside.
expect(wrap.find(".wp-chip--toggle").exists()).toBe(true);
expect(wrap.find(".wp-chip--toggle").text()).toContain("Favorited");
});
it("paginates 20 items into pages of 10 then 10 when pageSize=10", async () => {
-13
View File
@@ -354,19 +354,6 @@ defineExpose({
<Transition name="filter-collapse">
<div v-if="filtersOpen" class="wp-filter-panel">
<slot name="filter-panel" :filter="filter" :emit-fetch="emitFetch" />
<div v-if="showFavorite" class="wp-filter-panel__extra">
<span class="wp-filter-panel__extra-label">Quick filters</span>
<div class="wp-filter-panel__extra-chips">
<button
type="button"
class="wp-chip wp-chip--toggle"
:data-active="filter.favorites ? '' : null"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
★ Favorites only
</button>
</div>
</div>
<div v-if="extraFilters?.length" class="wp-filter-panel__extra">
<span class="wp-filter-panel__extra-label">Syntax</span>
<div class="wp-filter-panel__extra-chips">
+16 -1
View File
@@ -151,7 +151,22 @@ function templateParts(row: ModuleRow): TemplatePart[] {
@update:model-value="(v) => { filter.category = v as string | null; emitFetch(); }"
/>
</div>
<div class="wp-field">
<label class="wp-field__label">Favorites</label>
<button
type="button"
class="wp-input"
style="display:flex;align-items:center;gap:8px;justify-content:flex-start;"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
<span class="wp-check" :data-checked="filter.favorites ? 'true' : 'false'" aria-hidden="true">
<svg v-if="filter.favorites" viewBox="0 0 12 12" fill="none" style="display:block">
<path d="M3 6.2l2.2 2.2L9 4.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span>Favorites only</span>
</button>
</div>
<div class="wp-field wp-field--full">
<label class="wp-field__label">
Tags{{ filter.tags?.length ? ` (${filter.tags.length})` : "" }}
+16 -1
View File
@@ -187,7 +187,22 @@ function formatFactor(f: number): string {
@update:model-value="(v) => { filter.category = v as string | null; emitFetch(); }"
/>
</div>
<div class="wp-field">
<label class="wp-field__label">Favorites</label>
<button
type="button"
class="wp-input"
style="display:flex;align-items:center;gap:8px;justify-content:flex-start;"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
<span class="wp-check" :data-checked="filter.favorites ? 'true' : 'false'" aria-hidden="true">
<svg v-if="filter.favorites" viewBox="0 0 12 12" fill="none" style="display:block">
<path d="M3 6.2l2.2 2.2L9 4.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span>Favorites only</span>
</button>
</div>
<div class="wp-field wp-field--full">
<label class="wp-field__label">
Tags{{ filter.tags?.length ? ` (${filter.tags.length})` : "" }}
+16 -1
View File
@@ -151,7 +151,22 @@ function actView(a: DerivationAction | undefined): { verb: string; target: strin
@update:model-value="(v) => { filter.category = v as string | null; emitFetch(); }"
/>
</div>
<div class="wp-field">
<label class="wp-field__label">Favorites</label>
<button
type="button"
class="wp-input"
style="display:flex;align-items:center;gap:8px;justify-content:flex-start;"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
<span class="wp-check" :data-checked="filter.favorites ? 'true' : 'false'" aria-hidden="true">
<svg v-if="filter.favorites" viewBox="0 0 12 12" fill="none" style="display:block">
<path d="M3 6.2l2.2 2.2L9 4.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span>Favorites only</span>
</button>
</div>
<div class="wp-field wp-field--full">
<label class="wp-field__label">
Tags{{ filter.tags?.length ? ` (${filter.tags.length})` : "" }}
+16 -1
View File
@@ -127,7 +127,22 @@ function topValues(row: ModuleRow): NamedValue[] { return values(row).slice(0, 4
@update:model-value="(v) => { filter.category = v as string | null; emitFetch(); }"
/>
</div>
<div class="wp-field">
<label class="wp-field__label">Favorites</label>
<button
type="button"
class="wp-input"
style="display:flex;align-items:center;gap:8px;justify-content:flex-start;"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
<span class="wp-check" :data-checked="filter.favorites ? 'true' : 'false'" aria-hidden="true">
<svg v-if="filter.favorites" viewBox="0 0 12 12" fill="none" style="display:block">
<path d="M3 6.2l2.2 2.2L9 4.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span>Favorites only</span>
</button>
</div>
<div class="wp-field wp-field--full">
<label class="wp-field__label">
Tags{{ filter.tags?.length ? ` (${filter.tags.length})` : "" }}
+16 -1
View File
@@ -197,7 +197,22 @@ function pad2(n: number): string { return String(n + 1).padStart(2, "0"); }
@update:model-value="(v) => { filter.category = v as string | null; emitFetch(); }"
/>
</div>
<div class="wp-field">
<label class="wp-field__label">Favorites</label>
<button
type="button"
class="wp-input"
style="display:flex;align-items:center;gap:8px;justify-content:flex-start;"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
<span class="wp-check" :data-checked="filter.favorites ? 'true' : 'false'" aria-hidden="true">
<svg v-if="filter.favorites" viewBox="0 0 12 12" fill="none" style="display:block">
<path d="M3 6.2l2.2 2.2L9 4.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span>Favorites only</span>
</button>
</div>
<div class="wp-field wp-field--full">
<label class="wp-field__label">
Tags{{ filter.tags?.length ? ` (${filter.tags.length})` : "" }}
+16 -1
View File
@@ -187,7 +187,22 @@ function isValid(row: ModuleRow): boolean {
@update:model-value="(v) => { filter.category = v as string | null; emitFetch(); }"
/>
</div>
<div class="wp-field">
<label class="wp-field__label">Favorites</label>
<button
type="button"
class="wp-input"
style="display:flex;align-items:center;gap:8px;justify-content:flex-start;"
@click="filter.favorites = !filter.favorites; emitFetch()"
>
<span class="wp-check" :data-checked="filter.favorites ? 'true' : 'false'" aria-hidden="true">
<svg v-if="filter.favorites" viewBox="0 0 12 12" fill="none" style="display:block">
<path d="M3 6.2l2.2 2.2L9 4.4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span>Favorites only</span>
</button>
</div>
<div class="wp-field wp-field--full">
<label class="wp-field__label">
Tags{{ filter.tags?.length ? ` (${filter.tags.length})` : "" }}
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="icon" type="image/svg+xml" href="/wp/images/favicon.svg" />
<link rel="apple-touch-icon" href="/wp/images/apple-touch-icon.png" />
<link rel="manifest" href="/wp/images/site.webmanifest" />
<script type="module" crossorigin src="/wp/assets/manager-baYgQ7LG.js"></script>
<script type="module" crossorigin src="/wp/assets/manager-BWnFunYr.js"></script>
<link rel="stylesheet" crossorigin href="/wp/assets/manager-C6od6CgI.css">
</head>
<body>