move database filters to collapsible row with toggle button
- add Filters button next to search input - show filter count badge when filters are active - auto-expand filters when URL has filter params
This commit is contained in:
parent
ce495a9145
commit
30c2539766
1 changed files with 155 additions and 63 deletions
|
|
@ -20,6 +20,8 @@
|
||||||
buildUrlFromFilters,
|
buildUrlFromFilters,
|
||||||
type ParsedFilters
|
type ParsedFilters
|
||||||
} from '$lib/utils/filterParams'
|
} from '$lib/utils/filterParams'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
|
|
||||||
import type { Snippet } from 'svelte'
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
|
|
@ -31,7 +33,13 @@
|
||||||
headerActions?: Snippet
|
headerActions?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const { resource, columns, pageSize: initialPageSize = 20, leftActions, headerActions }: Props = $props()
|
const {
|
||||||
|
resource,
|
||||||
|
columns,
|
||||||
|
pageSize: initialPageSize = 20,
|
||||||
|
leftActions,
|
||||||
|
headerActions
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
// Derive entity type from resource
|
// Derive entity type from resource
|
||||||
const entityType = $derived(
|
const entityType = $derived(
|
||||||
|
|
@ -70,6 +78,18 @@
|
||||||
let proficiencyFilters = $state<number[]>([])
|
let proficiencyFilters = $state<number[]>([])
|
||||||
let seasonFilters = $state<number[]>([])
|
let seasonFilters = $state<number[]>([])
|
||||||
|
|
||||||
|
// Filter visibility state
|
||||||
|
let showFilters = $state(false)
|
||||||
|
|
||||||
|
// Check if any filters are active (for button indicator)
|
||||||
|
const hasActiveFilters = $derived(
|
||||||
|
elementFilters.length > 0 ||
|
||||||
|
rarityFilters.length > 0 ||
|
||||||
|
seriesFilters.length > 0 ||
|
||||||
|
proficiencyFilters.length > 0 ||
|
||||||
|
seasonFilters.length > 0
|
||||||
|
)
|
||||||
|
|
||||||
// Handle filter changes from CollectionFilters component
|
// Handle filter changes from CollectionFilters component
|
||||||
function handleFiltersChange(filters: CollectionFilterState) {
|
function handleFiltersChange(filters: CollectionFilterState) {
|
||||||
// Convert series to string[] (weapon series are UUIDs, character series are numbers that need conversion)
|
// Convert series to string[] (weapon series are UUIDs, character series are numbers that need conversion)
|
||||||
|
|
@ -271,11 +291,7 @@
|
||||||
if (urlInitialized) return
|
if (urlInitialized) return
|
||||||
if (resource === 'weapons' && !weaponSeriesQuery.data) return // Wait for weapon series
|
if (resource === 'weapons' && !weaponSeriesQuery.data) return // Wait for weapon series
|
||||||
|
|
||||||
const parsed = parseFiltersFromUrl(
|
const parsed = parseFiltersFromUrl($page.url.searchParams, entityType, weaponSeriesQuery.data)
|
||||||
$page.url.searchParams,
|
|
||||||
entityType,
|
|
||||||
weaponSeriesQuery.data
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set filter state
|
// Set filter state
|
||||||
elementFilters = parsed.element
|
elementFilters = parsed.element
|
||||||
|
|
@ -315,6 +331,17 @@
|
||||||
lastSearchTerm = parsed.searchQuery
|
lastSearchTerm = parsed.searchQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show filters panel if any filters are active from URL
|
||||||
|
if (
|
||||||
|
parsed.element.length > 0 ||
|
||||||
|
parsed.rarity.length > 0 ||
|
||||||
|
parsed.proficiency.length > 0 ||
|
||||||
|
parsed.season.length > 0 ||
|
||||||
|
parsed.series.length > 0
|
||||||
|
) {
|
||||||
|
showFilters = true
|
||||||
|
}
|
||||||
|
|
||||||
urlInitialized = true
|
urlInitialized = true
|
||||||
loadData(parsed.page, false) // Don't update URL on initial load
|
loadData(parsed.page, false) // Don't update URL on initial load
|
||||||
}
|
}
|
||||||
|
|
@ -353,8 +380,42 @@
|
||||||
{@render leftActions()}
|
{@render leftActions()}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="controls-right">
|
||||||
|
{#if headerActions}
|
||||||
|
{@render headerActions()}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
onclick={() => (showFilters = !showFilters)}
|
||||||
|
class="filter-toggle {hasActiveFilters ? 'has-active' : ''}"
|
||||||
|
>
|
||||||
|
<Icon name="chevron-down-small" size={14} />
|
||||||
|
Filters
|
||||||
|
{#if hasActiveFilters}
|
||||||
|
<span class="filter-count">
|
||||||
|
{elementFilters.length +
|
||||||
|
rarityFilters.length +
|
||||||
|
seriesFilters.length +
|
||||||
|
proficiencyFilters.length +
|
||||||
|
seasonFilters.length}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<input type="text" placeholder="Search..." bind:value={searchTerm} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if showFilters}
|
||||||
|
<div class="filters-row">
|
||||||
<CollectionFilters
|
<CollectionFilters
|
||||||
entityType={resource === 'characters' ? 'character' : resource === 'summons' ? 'summon' : 'weapon'}
|
entityType={resource === 'characters'
|
||||||
|
? 'character'
|
||||||
|
: resource === 'summons'
|
||||||
|
? 'summon'
|
||||||
|
: 'weapon'}
|
||||||
bind:elementFilters
|
bind:elementFilters
|
||||||
bind:rarityFilters
|
bind:rarityFilters
|
||||||
bind:seriesFilters
|
bind:seriesFilters
|
||||||
|
|
@ -364,16 +425,9 @@
|
||||||
showSort={false}
|
showSort={false}
|
||||||
contained={false}
|
contained={false}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div class="controls-right">
|
|
||||||
{#if headerActions}
|
|
||||||
{@render headerActions()}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<input type="text" placeholder="Search..." bind:value={searchTerm} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid-wrapper" class:loading>
|
<div class="grid-wrapper" class:loading>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="loading-overlay">
|
<div class="loading-overlay">
|
||||||
|
|
@ -381,7 +435,14 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Grid {data} {columns} {init} {sortMarks} sizes={{ rowHeight: 80 }} class="database-grid-theme" />
|
<Grid
|
||||||
|
{data}
|
||||||
|
{columns}
|
||||||
|
{init}
|
||||||
|
{sortMarks}
|
||||||
|
sizes={{ rowHeight: 80 }}
|
||||||
|
class="database-grid-theme"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-footer">
|
<div class="grid-footer">
|
||||||
|
|
@ -433,26 +494,39 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: spacing.$unit;
|
padding: spacing.$unit;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
gap: spacing.$unit;
|
gap: spacing.$unit;
|
||||||
|
|
||||||
// CollectionFilters on the left
|
|
||||||
:global(.filters-container) {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
// Override filter trigger padding
|
|
||||||
:global([data-select-trigger]) {
|
|
||||||
padding-top: 7px;
|
|
||||||
padding-bottom: 7px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls-right {
|
.controls-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: spacing.$unit;
|
gap: spacing.$unit;
|
||||||
flex-shrink: 0;
|
margin-left: auto;
|
||||||
|
|
||||||
|
:global(.filter-toggle) {
|
||||||
|
gap: spacing.$unit-half;
|
||||||
|
|
||||||
|
:global(svg) {
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.has-active) {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 spacing.$unit-half;
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: typography.$medium;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: spacing.$unit spacing.$unit-2x;
|
padding: spacing.$unit spacing.$unit-2x;
|
||||||
|
|
@ -474,6 +548,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filters-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: spacing.$unit;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
|
||||||
|
:global(.filters-container) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
// Override filter trigger padding
|
||||||
|
:global([data-select-trigger]) {
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-wrapper {
|
.grid-wrapper {
|
||||||
|
|
@ -585,7 +678,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
:global(.wx-grid .wx-cell) {
|
:global(.wx-grid .wx-cell) {
|
||||||
padding: spacing.$unit * 0.5;
|
padding: spacing.$unit * 0.5;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue