feat: put the library view mode in the url via shallow routing
This commit is contained in:
@@ -12,7 +12,9 @@
|
||||
import FilterButton from './filter-button.svelte';
|
||||
import SortButton from './sort-button.svelte';
|
||||
import ViewToggle from './view-toggle.svelte';
|
||||
import PresetChips from './preset-chips.svelte';
|
||||
import BookTable from './book-table.svelte';
|
||||
import BookRows from './book-rows.svelte';
|
||||
import BatchOperationsToolbar from './batch-operations-toolbar.svelte';
|
||||
import { getBookCollectionState } from '$lib/state/bookCollection.svelte';
|
||||
import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
|
||||
@@ -22,7 +24,6 @@
|
||||
const bookCollection = getBookCollectionState();
|
||||
const bookOps = getBookOperationsState();
|
||||
|
||||
let view = $state('grid');
|
||||
let sentinel = $state<HTMLElement>();
|
||||
let scrollContainer = $state<HTMLElement | null>(null);
|
||||
|
||||
@@ -66,7 +67,14 @@
|
||||
<div class="top-0 z-1 mb-4 flex h-12 w-full rounded-lg border bg-sidebar px-5">
|
||||
<div class="flex w-full items-center py-2">
|
||||
{#if !selectionState.selectionModeActive}
|
||||
<ViewToggle bind:view />
|
||||
<ViewToggle
|
||||
value={bookCollection.view}
|
||||
onValueChange={(next) => bookCollection.setView(next)}
|
||||
/>
|
||||
|
||||
<div class="mx-4 min-w-0 flex-1">
|
||||
<PresetChips />
|
||||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<SortButton />
|
||||
@@ -92,8 +100,10 @@
|
||||
</div>
|
||||
{:else if bookCollection.books.length > 0}
|
||||
<ScrollArea bind:viewportRef={scrollContainer} orientation="both" class="h-[calc(100vh-11rem)] w-full px-5 pb-5">
|
||||
{#if view === 'grid'}
|
||||
{#if bookCollection.view === 'grid'}
|
||||
<BookGrid books={bookCollection.books} />
|
||||
{:else if bookCollection.view === 'list'}
|
||||
<BookRows books={bookCollection.books} />
|
||||
{:else}
|
||||
<BookTable books={bookCollection.books} />
|
||||
{/if}
|
||||
|
||||
@@ -1,40 +1,54 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Tooltip from '$lib/components/ui/tooltip/index';
|
||||
import * as ToggleGroup from '$lib/components/ui/toggle-group/index';
|
||||
import { BOOK_VIEWS, type BookView } from '$lib/state/bookCollection.svelte';
|
||||
|
||||
import { List, LayoutGrid } from '@lucide/svelte';
|
||||
import { List, LayoutGrid, Table } from '@lucide/svelte';
|
||||
|
||||
let { view = $bindable(), class: className = '' } = $props();
|
||||
/**
|
||||
* Controlled rather than bound: the parent writes the URL when the view
|
||||
* changes, and a callback keeps that write next to the intent instead of
|
||||
* needing an effect that also fires on the initial assignment.
|
||||
*/
|
||||
let {
|
||||
value,
|
||||
onValueChange,
|
||||
class: className = ''
|
||||
}: {
|
||||
value: BookView;
|
||||
onValueChange: (view: BookView) => void;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
const ICONS = { grid: LayoutGrid, list: List, table: Table };
|
||||
const LABELS = { grid: 'Grid view', list: 'List view', table: 'Table view' };
|
||||
</script>
|
||||
|
||||
<ToggleGroup.Root type="single" bind:value={view} class={className}>
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<ToggleGroup.Item value="grid" aria-label="Toggle grid view" {...props}>
|
||||
<LayoutGrid class="size-4" />
|
||||
</ToggleGroup.Item>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>Grid view</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<ToggleGroup.Item value="list" aria-label="Toggle list view" {...props}>
|
||||
<List class="size-4" />
|
||||
</ToggleGroup.Item>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<p>List view</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
<ToggleGroup.Root
|
||||
type="single"
|
||||
{value}
|
||||
onValueChange={(next) => {
|
||||
// The group emits '' when the active item is pressed again; keep the
|
||||
// current view rather than leaving the browser with nothing to render.
|
||||
if (next) onValueChange(next as BookView);
|
||||
}}
|
||||
class={className}
|
||||
>
|
||||
{#each BOOK_VIEWS as view (view)}
|
||||
{@const Icon = ICONS[view]}
|
||||
<Tooltip.Provider>
|
||||
<Tooltip.Root ignoreNonKeyboardFocus>
|
||||
<Tooltip.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<ToggleGroup.Item value={view} aria-label={LABELS[view]} {...props}>
|
||||
<Icon class="size-4" />
|
||||
</ToggleGroup.Item>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<p>{LABELS[view]}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</Tooltip.Provider>
|
||||
{/each}
|
||||
</ToggleGroup.Root>
|
||||
|
||||
Reference in New Issue
Block a user