Surface saved views in the FilterBar mobile expanded body

Mobile users could not save, apply, or set default views on any
savedViewsKey surface because SavedViewsMenu only rendered in the
desktop controls row. Render it in the mobile chip row, show that row
when a surface has saved views even with zero menu filters, drop the
duplicate Reset the wider chip row introduced, and left-anchor the
dropdown below the sm breakpoint so it stays on screen at 375px.
This commit is contained in:
rcourtman 2026-07-08 09:52:56 +01:00
parent 9923e4b04f
commit 545087d614
3 changed files with 62 additions and 3 deletions

View file

@ -1,8 +1,19 @@
import { Route, Router } from '@solidjs/router';
import { cleanup, fireEvent, render, screen, within } from '@solidjs/testing-library';
import type { JSX } from 'solid-js';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { FilterBar } from './FilterBar';
import type { FilterDef } from './filterCatalog';
// SavedViewsMenu URL-backs view application (useNavigate/useLocation), so
// savedViewsKey renders must sit inside a Router context.
const renderInRouter = (component: () => JSX.Element) =>
render(() => (
<Router>
<Route path="/" component={component} />
</Router>
));
const search = {
value: () => '',
setValue: vi.fn(),
@ -93,4 +104,49 @@ describe('FilterBar', () => {
fireEvent.click(screen.getByRole('button', { name: 'Clear all' }));
expect(onClearAll).toHaveBeenCalledTimes(1);
});
it('shows saved views in the expanded mobile body when savedViewsKey is set', () => {
renderInRouter(() => (
<FilterBar
search={search}
filters={[inlineTypeFilter(), menuNodeFilter()]}
isMobile={() => true}
savedViewsKey="test-surface"
/>
));
expect(screen.queryByRole('button', { name: 'Saved views' })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Filters' }));
expect(screen.getByRole('button', { name: 'Saved views' })).toBeInTheDocument();
});
it('shows saved views on mobile even when every filter is inline', () => {
renderInRouter(() => (
<FilterBar
search={search}
filters={[inlineTypeFilter()]}
isMobile={() => true}
savedViewsKey="test-surface"
/>
));
fireEvent.click(screen.getByRole('button', { name: 'Filters' }));
expect(screen.getByRole('button', { name: 'Saved views' })).toBeInTheDocument();
expect(screen.queryByRole('combobox', { name: 'Filter' })).not.toBeInTheDocument();
});
it('renders a single clear-all on mobile when the saved-views row is shown', () => {
renderInRouter(() => (
<FilterBar
search={search}
filters={[inlineTypeFilter(vi.fn(), 'vm')]}
isMobile={() => true}
savedViewsKey="test-surface"
/>
));
fireEvent.click(screen.getByRole('button', { name: /^Filters/ }));
expect(screen.getAllByRole('button', { name: 'Clear all' })).toHaveLength(1);
});
});

View file

@ -88,7 +88,7 @@ export const FilterBar: Component<FilterBarProps> = (props) => {
const showMobileBody = () => props.isMobile() && mobileExpanded();
const showChipRow = () =>
showDesktopChipRow() ||
(showMobileBody() && (activeMenuFilters().length > 0 || hasMenuFilters()));
(showMobileBody() && (activeMenuFilters().length > 0 || hasMenuFilters() || hasSavedViews()));
const searchHistory = () => {
const key = props.search.historyKey;
@ -169,7 +169,7 @@ export const FilterBar: Component<FilterBarProps> = (props) => {
<Show when={showInlineRow()}>
<div class="flex flex-wrap items-center gap-2">
<For each={inlineFilters()}>{(filter) => <InlineFilterControl filter={filter} />}</For>
<Show when={hasClearableState() && activeMenuFilters().length === 0}>
<Show when={hasClearableState() && !showChipRow()}>
<FilterBarClearAllButton onClick={clearAll} />
</Show>
</div>
@ -182,6 +182,9 @@ export const FilterBar: Component<FilterBarProps> = (props) => {
<Show when={hasMenuFilters()}>
<AddFilterMenu filters={menuFilters()} />
</Show>
<Show when={props.savedViewsKey}>
{(key) => <SavedViewsMenu storageKey={key()} />}
</Show>
</Show>
<Show when={hasClearableState() && showMobileBody()}>
<FilterBarClearAllButton onClick={clearAll} />

View file

@ -91,7 +91,7 @@ export const SavedViewsMenu: Component<SavedViewsMenuProps> = (props) => {
<Show when={open()}>
<div
role="menu"
class="absolute right-0 top-[calc(100%+0.25rem)] z-50 w-64 max-w-[calc(100vw-2rem)] rounded-md border border-border bg-surface shadow-lg"
class="absolute left-0 top-[calc(100%+0.25rem)] z-50 w-64 max-w-[calc(100vw-2rem)] rounded-md border border-border bg-surface shadow-lg sm:left-auto sm:right-0"
>
<Show
when={savePromptOpen()}