İçeriğe geç
Reusable List

Referans / Şablonlar

Şablonlar

Basit bir listeden tüm durumları yöneten bir sayfaya kadar dört farklı kompozisyon katmanı. Projedeki her örnek, bunların bir kısmını veya tamamını kullanır.

TL;DR

Her liste sayfası ortak sayfa yapısı için ListPageShell, server tarafında render edilen navigasyon için ise PaginationControls kullanır. Çoğu örnek, sayfaya özgü satır ve kartları göstermek için ResultsView kullanırken Sorunlar bilinçli olarak kendi seçilebilir tablosunu kullanır. Sayfaya özgü araç çubuğu ise ortak URL durumu üzerinden arama, filtreleme, sıralama ve görünüm kontrollerini bir araya getirir.

Seçim yalnızca ilgili sayfanın geçici satır durumuna ihtiyaç duyduğu yerlerde eklenir. Yüklenme, boş sonuç, filtre sonrası boş sonuç ve hata durumları ise her tam liste sayfasının doğrudan bir parçasıdır. Şablonlar farklı yeteneklere sahip olsa da ayrı mimariler oluşturmak yerine aynı ortak çekirdeği kullanır.

Kompozisyon katmanları

Her katman, bir öncekinin üzerine src/features/list-page/ içinden küçük bir yapı taşı grubu ekler.

Temel Liste

ListPageShell, ResultsView ve PaginationControls ile oluşturulur. toolbar zorunlu bir prop olsa da temel kullanımda null veya minimum içerik verilebilir. Sonraki katmanlar aynı alanlara yeni yetenekler ekler.

  • ListPageShell
  • ResultsView
  • PaginationControls
Paketler örneği bunun üzerine arama ve iki filtre ekler
Önizleme: arama veya filtre kontrolü olmadan paket adı ve sürümünü gösteren üç bağımlılık satırı.

Arama ve Filtreler

Sayfaya özgü araç çubuğu; SearchField, SingleSelectFilter veya MultiSelectFilter, SortMenu ve ViewSwitcher bileşenlerini bir araya getirir ve useListQueryState'i tek noktadan kullanır. Debounce edilmiş arama mevcut URL girdisini günceller; filtre, sıralama, görünüm ve sayfalama değişiklikleri ise tarayıcı geçmişine yeni bir giriş ekler. Böylece geri tuşu ve paylaşılan bağlantılar doğru liste durumunu geri yükleyebilir.

  • SearchField
  • SingleSelectFilter
  • MultiSelectFilter
  • SortMenu
  • ViewSwitcher
  • useListQueryState
Bileşenler, tekli ve çoklu seçim filtrelerini birlikte kullanır
Önizleme: arama alanı, etkin kategori filtresi ve durum bilgisi içeren iki bileşen satırı.

Seçim ve Toplu İşlemler

Satır seçimi bilinçli olarak URL dışında tutulur; çünkü paylaşılabilir liste durumunun değil, geçici sayfa durumunun bir parçasıdır. Bir veya daha fazla satır seçildiğinde SelectionToolbar görünür ve sayfaya özgü toplu işlemleri sunar. ResultsView tablo başlıklarını, tümünü seçme davranışını ve indeterminate seçim durumunu modellemediği için bu örnek kendi seçilebilir tablosunu render eder.

  • useSelection
  • SelectionToolbar
Bu yapıya ihtiyaç duyan tek örnek Sorunlar'dır
Önizleme: iki seçili sorun satırının üzerinde, "2 seçildi" bilgisi ve Durumu değiştir işlemi bulunan araç çubuğu.

Sistem Durumları

Her örnekte ListSkeleton, ListErrorState ve useDemoErrorRecovery kullanan loading.tsx ve error.tsx dosyaları bulunur. Results bileşeni ise ListEmptyState ile hem boş sonucu hem de filtre sonrası boş sonucu ele alır. Route Handler'lar, bu durumların ek bir araca ihtiyaç olmadan görüntülenebilmesi için demoState query parametresini destekler.

  • ListSkeleton
  • ListEmptyState
  • ListErrorState
  • useDemoErrorRecovery
Dağıtımlar URL'sine ?demoState=error eklemeyi deneyin
Önizleme: farklı genişliklerde üç yüklenme iskeleti.

Bir sayfa bunları nasıl bir araya getirir?

Gerçek örneklerle aynı yapıyı kullanan kurgusal bir Widgets sayfası: Server Component URL'yi bir kez ayrıştırır ve ortak sayfa yapısını render eder. Küçük bir Client Component araç çubuğu ise etkileşimli kontrolleri yönetir ve değişiklikleri aynı URL durumuna yazar.

Server Component — src/app/examples/widgets/page.tsx
export default async function WidgetsPage(
  props: PageProps<"/examples/widgets">,
) {
  const searchParams = toSearchParams(await props.searchParams);
  const demoState = parseDemoState(searchParams);
  const query = parseListQuery<WidgetSortKey, WidgetFilterKey>(
    searchParams,
    WIDGET_LIST_QUERY_CONFIG,
  );
  const { records, total, page } = await queryWidgets(query, demoState);

  const buildHref = (target: number) =>
    "/examples/widgets" +
    buildListQueryString(
      { ...query, page: target },
      WIDGET_LIST_QUERY_CONFIG,
    );

  if (page !== query.page) {
    redirect(buildHref(page));
  }

  return (
    <ListPageShell
      title="Widgets"
      toolbar={<WidgetsToolbar />}
      pagination={
        <PaginationControls
          page={page}
          pageSize={WIDGET_LIST_QUERY_CONFIG.pageSize}
          total={total}
          buildHref={buildHref}
          itemLabel="widgets"
        />
      }
    >
      <ResultsView
        view={query.view}
        items={records}
        getItemKey={(record) => record.id}
        renderListItem={(record) => <WidgetListRow record={record} />}
        renderGridItem={(record) => <WidgetGridCard record={record} />}
        listAriaLabel="Widgets"
      />
    </ListPageShell>
  );
}
Client Component — src/features/widgets-example/widgets-toolbar.tsx
"use client";

export function WidgetsToolbar() {
  const { query, setSearch, setSort, setView, setSingleFilter } =
    useListQueryState<WidgetSortKey, WidgetFilterKey>(
      WIDGET_LIST_QUERY_CONFIG,
    );

  return (
    <div className="flex flex-wrap items-center gap-2.5">
      <SearchField
        label="Search widgets"
        placeholder="Search widgets…"
        value={query.search}
        onChange={setSearch}
      />
      <SingleSelectFilter
        label="Status"
        options={STATUS_OPTIONS}
        value={query.filters.status[0]}
        onChange={(value) => setSingleFilter("status", value)}
      />
      <div className="flex-1" />
      <SortMenu options={SORT_OPTIONS} value={query.sort} onChange={setSort} />
      <ViewSwitcher value={query.view} onChange={setView} />
    </div>
  );
}