Skip to main content

Filter System Components

The filter system is a comprehensive set of components, hooks, and utilities that power the directory listing filtering experience. It supports multi-select tags and categories, text search, location-based filtering, URL synchronization, and sort controls.

Architecture Overview

template/components/filters/
components/
active-filters/ # Displays currently applied filters with remove actions
categories/ # Category list, section, and item components
controls/ # Search input, sort dropdown, filter bar
location/ # Near Me, City, Country, Radius filters
pagination/ # Standard and infinite scroll pagination
tags/ # Tag list, section, and item components
context/
filter-context.tsx # React Context provider for filter state
location-distance-context.tsx # Context for item distance data
hooks/
use-filter-state.ts # Core state management hook
use-filter-url-sync.ts # URL synchronization hook
use-sticky-header.ts # Scroll-based sticky behavior
use-tag-visibility.ts # Show more/less tag toggling
constants.ts # Configuration constants
filter-url-parser.tsx # Client-side URL-to-state parser
index.ts # Barrel exports
types.ts # TypeScript type definitions
utils/
style-utils.ts # CSS class helpers
tag-utils.ts # Tag data transformation helpers
text-utils.ts # Text truncation and formatting

Core Types

The filter system defines a central FilterContextType interface that all components consume:

PropertyTypeDescription
searchTermstringCurrent text search query
selectedTagsTagId[]Array of selected tag identifiers
selectedCategoriesCategoryId[]Array of selected category identifiers
sortBySortOptionCurrent sort mode
isFiltersLoadingbooleanLoading indicator during filter updates
locationFilterLocationFilterStateActive location filter state
selectedTagTagId | nullSingle selected tag for navigation mode
selectedCategoryCategoryId | nullSingle selected category for navigation mode

Sort Options

Defined in constants.ts, the five sort options are:

ConstantValueLabel Key
POPULARITY'popularity'POPULARITY
NAME_ASC'name-asc'NAME_A_Z
NAME_DESC'name-desc'NAME_Z_A
DATE_DESC'date-desc'NEWEST
DATE_ASC'date-asc'OLDEST

Location Filter State

interface LocationFilterState {
nearMe?: NearMeCoordinates; // { latitude, longitude, radius }
city?: string;
country?: string;
sortByDistance?: boolean;
}

FilterProvider

The FilterProvider component wraps any page section that needs filter capabilities. It initializes state from route parameters and provides context to all child components.

import { FilterProvider } from '@/components/filters';

<FilterProvider
initialTag="open-source"
initialCategory="productivity"
initialSortBy="popularity"
>
<Tags tags={tags} />
<Categories categories={categories} total={total} />
<SharedCard items={items} {...props} />
</FilterProvider>

FilterProvider Props

PropTypeDefaultDescription
childrenReactNoderequiredChild components
initialTagstring | nullnullPre-selected tag from route
initialCategorystring | nullnullPre-selected category from route
initialSortBystring'popularity'Initial sort order

Filter Components

Categories

The category system provides two rendering modes:

ModeBehavior
"navigation"Links to /categories/[slug] routes
"filter"Toggles category selection in filter context
<Categories total={218} categories={categories} />

CategoriesListProps

PropTypeDefaultDescription
categoriesCategory[]requiredArray of category objects
mode"navigation" | "filter""navigation"Rendering behavior
selectedCategoriesstring[][]Currently selected IDs
onCategoryToggle(id: string) => void-Toggle callback for filter mode

Tags

Tags support sticky header behavior, visibility limiting, and both navigation and filter modes.

<Tags
tags={tags}
enableSticky={true}
maxVisibleTags={8}
mode="filter"
total={218}
/>

TagsProps

PropTypeDefaultDescription
tagsTag[]requiredArray of tag objects
enableStickybooleanfalseEnable sticky scroll behavior
maxVisibleTagsnumber8Tags shown before "Show More"
mode"navigation" | "filter""navigation"Rendering behavior
totalnumber-Total item count for display

ActiveFilters

Displays all currently applied filters as removable chips, with a "Clear All" action.

<ActiveFilters
searchTerm={searchTerm}
selectedTags={selectedTags}
selectedCategories={selectedCategories}
sortBy={sortBy}
availableTags={tags}
availableCategories={categories}
clearAllFilters={clearAllFilters}
{...setters}
/>

FilterControls

Combines search input and sort dropdown into a single toolbar row.

Location Components

The location subdirectory provides five specialized components:

ComponentFilePurpose
LocationFilterLocationFilter.tsxParent orchestrator for all location UI
NearMeButtonNearMeButton.tsxGeolocation trigger using browser API
RadiusSliderRadiusSlider.tsxDistance radius adjustment (km)
CityFilterCityFilter.tsxCity name autocomplete filter
CountryFilterCountryFilter.tsxCountry selection filter

Pagination

<Paginate
basePath="/directory"
initialPage={1}
total={218}
paginationType="standard" // or "infinite"
/>

PaginateProps

PropTypeDefaultDescription
basePathstringrequiredBase URL path for page links
initialPagenumberrequiredStarting page number
totalnumberrequiredTotal item count
paginationType"standard" | "infinite""standard"Pagination strategy
onLoadMore(page: number) => Promise<void>-Infinite scroll callback

Hooks

useFilterState

The primary state management hook. Returns all filter state values and their setters, including tag/category toggle actions, location filter controls, and URL synchronization.

Key actions provided:

ActionSignatureDescription
toggleSelectedTag(tagId: TagId) => voidAdd or remove a tag
toggleSelectedCategory(categoryId: CategoryId) => voidSelect one category (exclusive)
clearAllFilters() => voidReset all filters and URL
setNearMe(coords | null) => voidEnable/disable geolocation filter
setLocationCity(city | null) => voidSet city filter
setLocationCountry(country | null) => voidSet country filter

useFilterURLSync

Synchronizes filter state to URL query parameters using window.history.replaceState. Uses 300ms debouncing to avoid excessive history entries. Skips URL updates on /categories/[slug] and /tags/[slug] routes where the path already encodes the filter.

URL parameters managed: tags, categories, q, near_lat, near_lng, radius, city, country.

useStickyHeader

Monitors scroll position using a RAF-throttled listener. Returns isSticky boolean when scroll exceeds the configured threshold (default 250px).

useTagVisibility

Controls "Show More / Show Less" toggling for tag lists. Limits visible tags to maxVisibleTags (default 8) and provides toggle state.

FilterURLParser

A client component wrapped in Suspense that reads URL search parameters and route path segments on navigation, then updates filter context state accordingly. It handles:

  • Query parameter parsing (?tags=a,b&categories=c)
  • Path segment parsing (/tags/[tag], /categories/[category])
  • Location parameter parsing (?near_lat=...&near_lng=...&radius=...)
  • Search query parsing (?q=search+term)
  • Transition detection to prevent race conditions

Configuration Constants

Defined in FILTER_CONSTANTS:

ConstantValuePurpose
MAX_VISIBLE_TAGS8Default visible tag limit
TEXT_TRUNCATE_LENGTH20Character limit for tag text
SCROLL_THRESHOLD250Pixels before sticky activates
SCROLL_DURATION600Smooth scroll animation (ms)
TOOLTIP_DELAY300Tooltip show delay (ms)
TRANSITION_DURATION300CSS transition timing (ms)
MOBILE_BREAKPOINT'md'Responsive breakpoint

Integration Example

import {
FilterProvider,
Tags,
Categories,
FilterControls,
ActiveFilters,
Paginate,
useFilters,
} from '@/components/filters';

export function DirectoryPage({ items, categories, tags, total }) {
return (
<FilterProvider>
<FilterURLParser />
<Categories total={total} categories={categories} />
<Tags tags={tags} enableSticky maxVisibleTags={10} mode="filter" />
<FilterControls {...useFilters()} />
<ActiveFilters {...useFilters()} availableTags={tags} availableCategories={categories} />
<SharedCard items={items} categories={categories} tags={tags} />
<Paginate basePath="/directory" initialPage={1} total={total} />
</FilterProvider>
);
}