mdxui

DirectorySite Specialization

Directory site type with listings, search, filters, and map views

DirectorySite Specialization

The DirectorySite type is optimized for listings, catalogs, and searchable directories. It extends the base Site component with specialized components for search, filtering, map views, and listing management.

Naming Convention

DirectorySite follows the ${SiteType}${Component} pattern:

DirectoryHeader = Header + search and filters
SearchBar = Search + directory-specific features
FilterPanel = Panel + faceted filtering
ListingGrid = Grid + directory listings
ListingCard = Card + listing preview
ListingDetail = Detail + full listing view

All directory components are the base component enhanced with directory site-specific capabilities.

Type Definition

import type { DirectorySite } from 'mdxui'

interface DirectorySite extends BaseSite {
  type: 'directory'
  directory?: {
    itemsPerPage?: number     // Items per page (default: 20)
    filters?: boolean         // Enable filtering (default: true)
    sorting?: boolean         // Enable sorting (default: true)
    map?: boolean            // Enable map view (default: false)
    search?: {
      enabled?: boolean       // Enable search (default: true)
      placeholder?: string    // Search placeholder text
    }
  }
}

Usage

Basic Example

import { Site, DirectoryHeader, SearchBar, ListingGrid } from '@mdxui/beacon'

<Site
  type="directory"
  name="Find Restaurants"
  directory={{
    itemsPerPage: 20,
    filters: true,
    sorting: true,
    map: true
  }}
>
  <DirectoryHeader />
  <SearchBar placeholder="Search restaurants..." />
  <ListingGrid listings={restaurants} />
</Site>

With Map View

<Site
  type="directory"
  name="Store Locator"
  directory={{
    map: true,
    search: {
      enabled: true,
      placeholder: "Find stores near you"
    }
  }}
>
  <MapView locations={stores} />
  <ListingGrid listings={stores} />
</Site>

Required Components

DirectorySite requires these specialized components:

DirectoryHeader

Required: Yes

Directory header with search and filter controls.

<DirectoryHeader
  logo={<Logo />}
  search={true}
  filterToggle={true}
  viewToggle={true}  // Grid/List/Map view switcher
  navigation={[
    { label: "Home", href: "/" },
    { label: "Directory", href: "/directory" },
    { label: "Submit", href: "/submit" }
  ]}
/>

Required: Yes

Search interface with autocomplete and suggestions.

<SearchBar
  placeholder="Search by name, category, or location..."
  autocomplete={true}
  suggestions={recentSearches}
  onSearch={(query) => {
    // Handle search
  }}
  filters={activeFilters}
/>

ListingGrid

Required: Yes

Grid of directory listings.

<ListingGrid
  listings={[
    {
      id: "1",
      title: "Joe's Pizza",
      category: "Restaurant",
      location: "New York, NY",
      image: "/listings/joes-pizza.jpg",
      rating: 4.5,
      reviews: 120,
      verified: true
    }
  ]}
  layout="grid"  // or "list"
  itemsPerPage={20}
/>

Optional Components

FilterPanel

Faceted filter sidebar or panel.

<FilterPanel
  filters={[
    {
      type: "category",
      label: "Category",
      options: [
        { value: "restaurant", label: "Restaurants", count: 42 },
        { value: "cafe", label: "Cafes", count: 28 }
      ]
    },
    {
      type: "location",
      label: "Location",
      options: [
        { value: "new-york", label: "New York", count: 35 },
        { value: "los-angeles", label: "Los Angeles", count: 25 }
      ]
    },
    {
      type: "rating",
      label: "Rating",
      options: [
        { value: "4+", label: "4+ Stars", count: 30 },
        { value: "3+", label: "3+ Stars", count: 55 }
      ]
    }
  ]}
  selected={selectedFilters}
  onChange={(filters) => {
    // Apply filters
  }}
/>

ListingCard

Individual listing card for grid/list view.

<ListingCard
  title="Joe's Pizza"
  category="Restaurant"
  location="New York, NY"
  image="/listings/joes-pizza.jpg"
  rating={4.5}
  reviews={120}
  verified={true}
  badges={["Popular", "Open Now"]}
  href="/listings/joes-pizza"
/>

ListingDetail

Detailed listing page with full information.

<ListingDetail
  listing={{
    title: "Joe's Pizza",
    category: "Restaurant",
    description: "Authentic New York pizza since 1975",
    location: {
      address: "123 Main St, New York, NY 10001",
      lat: 40.7589,
      lng: -73.9851
    },
    hours: {
      monday: "11:00 AM - 10:00 PM",
      tuesday: "11:00 AM - 10:00 PM",
      // ...
    },
    contact: {
      phone: "(555) 123-4567",
      email: "info@joespizza.com",
      website: "https://joespizza.com"
    },
    images: ["/img1.jpg", "/img2.jpg"],
    rating: 4.5,
    reviews: reviewsData,
    verified: true
  }}
/>

MapView

Map-based listing view with markers.

<MapView
  listings={listings}
  center={{ lat: 40.7589, lng: -73.9851 }}
  zoom={12}
  onMarkerClick={(listing) => {
    // Show listing details
  }}
  cluster={true}  // Cluster nearby markers
  heatmap={false}
/>

Pagination

Pagination controls for listing navigation.

<Pagination
  currentPage={1}
  totalPages={10}
  itemsPerPage={20}
  totalItems={200}
  onPageChange={(page) => {
    // Load page
  }}
/>

Component Specializations

DirectoryHeader

Base Header component + directory features.

// Enhanced with:
- Full-text search bar
- Filter toggle button
- View mode switcher (grid/list/map)
- Sort dropdown
- Results count display

SearchBar

Search component with directory-specific features.

// Enhanced with:
- Autocomplete suggestions
- Search by category, location, name
- Recent searches
- Popular searches
- Search filters integration
- Geolocation search

FilterPanel

Filter sidebar with faceted search.

// Enhanced with:
- Multi-select filters
- Filter counts
- Clear filters button
- Active filter chips
- Hierarchical categories
- Range sliders (price, distance)

ListingGrid

Grid layout with sorting and filtering.

// Enhanced with:
- Grid/List view toggle
- Sort by: relevance, rating, distance, newest
- Infinite scroll or pagination
- Featured listings
- Sponsored listings
- Empty state handling

Search and Filtering

Search Types

<SearchBar
  searchTypes={[
    { type: "name", label: "Search by name" },
    { type: "category", label: "Search by category" },
    { type: "location", label: "Search by location" }
  ]}
  onSearch={(query, type) => {
    // Handle typed search
  }}
/>

Faceted Filtering

<FilterPanel
  filters={[
    {
      type: "category",
      label: "Category",
      multiple: true,
      options: categories
    },
    {
      type: "price",
      label: "Price Range",
      component: "range-slider",
      min: 0,
      max: 100
    },
    {
      type: "distance",
      label: "Distance",
      component: "range-slider",
      min: 0,
      max: 50,
      unit: "miles"
    }
  ]}
/>

Active Filters

<ActiveFilters
  filters={[
    { type: "category", value: "restaurant", label: "Restaurants" },
    { type: "rating", value: "4+", label: "4+ Stars" }
  ]}
  onRemove={(filter) => {
    // Remove filter
  }}
  onClearAll={() => {
    // Clear all filters
  }}
/>

Sorting Options

Sort Dropdown

<SortDropdown
  options={[
    { value: "relevance", label: "Most Relevant" },
    { value: "rating", label: "Highest Rated" },
    { value: "distance", label: "Nearest" },
    { value: "newest", label: "Newest" },
    { value: "popular", label: "Most Popular" }
  ]}
  selected="relevance"
  onChange={(sort) => {
    // Apply sorting
  }}
/>

Map Integration

Map View

<MapView
  listings={listings}
  provider="mapbox"  // or "google", "openstreetmap"
  apiKey="YOUR_API_KEY"
  center={{ lat: 40.7589, lng: -73.9851 }}
  zoom={12}
  markers={listings.map(listing => ({
    id: listing.id,
    position: listing.location,
    popup: <ListingCardMini listing={listing} />
  }))}
  onMarkerClick={(listing) => {
    // Show details
  }}
  cluster={true}
  clusterRadius={50}
/>
<SearchBar
  geolocation={true}
  onGeolocation={(coords) => {
    // Search near user location
    searchNear(coords.lat, coords.lng)
  }}
/>

Listing Details

Contact Information

<ContactInfo
  phone="(555) 123-4567"
  email="info@example.com"
  website="https://example.com"
  social={{
    facebook: "https://facebook.com/example",
    instagram: "https://instagram.com/example"
  }}
/>

Hours of Operation

<Hours
  schedule={{
    monday: "9:00 AM - 5:00 PM",
    tuesday: "9:00 AM - 5:00 PM",
    wednesday: "9:00 AM - 5:00 PM",
    thursday: "9:00 AM - 5:00 PM",
    friday: "9:00 AM - 5:00 PM",
    saturday: "10:00 AM - 3:00 PM",
    sunday: "Closed"
  }}
  timezone="America/New_York"
  currentStatus="open"  // or "closed"
/>

Reviews and Ratings

<Reviews
  averageRating={4.5}
  totalReviews={120}
  distribution={{
    5: 80,
    4: 25,
    3: 10,
    2: 3,
    1: 2
  }}
  reviews={[
    {
      author: "John Doe",
      rating: 5,
      date: "2024-01-15",
      text: "Great place! Highly recommend.",
      verified: true
    }
  ]}
  allowSubmit={true}
/>

Best Practices

Directory Structure

  • Clear categories: Logical, non-overlapping categories
  • Rich listings: Complete information for every listing
  • Quality images: High-quality photos for all listings
  • Verification: Badge verified/official listings
  • Regular updates: Keep listings current and accurate

Search Experience

  • Fast search: Sub-100ms search results
  • Autocomplete: Suggest as user types
  • Typo tolerance: Handle misspellings
  • Location-aware: Default to user's location
  • Remember searches: Save recent searches

Filter Design

  • Show counts: Display result count for each filter
  • Clear hierarchy: Organize filters logically
  • Mobile-friendly: Drawer or bottom sheet on mobile
  • Persistent state: Remember filter selections
  • Easy reset: Clear all filters with one click

Real-World Example

Complete directory site:

import {
  Site,
  DirectoryHeader,
  SearchBar,
  FilterPanel,
  ListingGrid,
  ListingCard,
  ListingDetail,
  MapView,
  Pagination
} from '@mdxui/beacon'

<Site
  type="directory"
  name="Restaurant Finder"
  domain="restaurantfinder.com"
  directory={{
    itemsPerPage: 20,
    filters: true,
    sorting: true,
    map: true,
    search: {
      enabled: true,
      placeholder: "Search restaurants, cuisine, or location..."
    }
  }}
>
  <DirectoryHeader
    logo={<Logo />}
    search={true}
    filterToggle={true}
    viewToggle={true}
  />

  <div className="directory-layout">
    {/* Filter Sidebar */}
    <FilterPanel
      filters={[
        {
          type: "category",
          label: "Cuisine",
          options: cuisineTypes
        },
        {
          type: "price",
          label: "Price Range",
          component: "range-slider",
          min: 1,
          max: 4
        },
        {
          type: "rating",
          label: "Rating",
          options: ratingOptions
        },
        {
          type: "features",
          label: "Features",
          options: [
            { value: "delivery", label: "Delivery Available" },
            { value: "outdoor", label: "Outdoor Seating" },
            { value: "parking", label: "Parking Available" }
          ]
        }
      ]}
      selected={activeFilters}
      onChange={handleFilterChange}
    />

    {/* Main Content */}
    <main>
      <SearchBar
        placeholder="Search restaurants..."
        autocomplete={true}
        geolocation={true}
        onSearch={handleSearch}
      />

      <div className="results-header">
        <p>{totalResults} restaurants found</p>
        <SortDropdown
          options={sortOptions}
          selected={currentSort}
          onChange={handleSort}
        />
        <ViewToggle
          views={["grid", "list", "map"]}
          current={currentView}
          onChange={setView}
        />
      </div>

      {currentView === "map" ? (
        <MapView
          listings={listings}
          provider="mapbox"
          center={mapCenter}
          zoom={12}
          cluster={true}
        />
      ) : (
        <>
          <ListingGrid
            listings={listings}
            layout={currentView}
            itemsPerPage={20}
          />
          <Pagination
            currentPage={currentPage}
            totalPages={totalPages}
            onPageChange={handlePageChange}
          />
        </>
      )}
    </main>
  </div>

  {/* Listing Detail Page */}
  <ListingDetail
    listing={currentListing}
    showMap={true}
    showReviews={true}
    showSimilar={true}
  />
</Site>

TypeScript

Full type definition:

import { z } from 'zod'
import { BaseSiteSchema } from 'mdxui/zod'

export const DirectorySiteSchema = BaseSiteSchema.extend({
  type: z.literal('directory'),
  directory: z.object({
    itemsPerPage: z.number().default(20),
    filters: z.boolean().default(true),
    sorting: z.boolean().default(true),
    map: z.boolean().default(false),
    search: z.object({
      enabled: z.boolean().default(true),
      placeholder: z.string().optional(),
    }).optional(),
  }).optional(),
})

export type DirectorySite = z.infer<typeof DirectorySiteSchema>

On this page