Passer au contenu principal

ListPanel

Implemented

Slot-based list host for the recurring header + toolbar + table + pagination pattern. Renders a token-driven header (title + actions slot), a renderTable slot for table ecosystem choice (ADR-033-compliant storefront DataTable OR DS AdminDataTable), optional toolbar/filter slot, density: default|compact for POSRegister-shaped layouts (cashier/scanner), inline loading skeleton and empty states, native button pagination. Used in storefront CRUD pages, dashboard tables, and POS register pattern (recipe cited in docs/components/organisms/listpanel.recipes.md).

Categoryorganisms
Sourcecomponents/organisms/ListPanel/ListPanel.tsx
AI HintComposable via renderTable slot. For POS cashier/scanner PWA use density=compact + FormModal for payments. See docs/components/organisms/listpanel.recipes.md for the POSRegister-shape recipe.

Slot-based list host for the recurring "header + toolbar + table + pagination" pattern. ListPanel owns the chrome (header, toolbar slot, loading/empty states, pagination); the table itself is caller-owned via the renderTable slot — the canonical composition hands a DataTable to it. `density="compact"` switches to the POS register shape (cashier/scanner). Grouped tables pass rowGroups through to the slot. See `listpanel.recipes.md` for the POSRegister recipe validated against 6 community Medusa POS sources.

ListPanel

Organism — Account & Admin family (Family 9)

Composes a header (title + actions slot), an optional toolbar slot, the renderTable slot (caller provides the table body — ADR-033 table ecosystem choice), the loading/empty state machine (inline skeleton dot and empty block — hand-rolled, not the Skeleton/EmptyState atoms), and native-button pagination that renders only when total > pageSize.

a. Regular density — DataTable composition

Tickets

toolbar slot (search, filters…)
Recent ticket orders
EventQty
Festival Jazz MTL4
OSM Symphonie n° 52
Lion d'Or Gala1
Just for Laughs3
FrancoFolies2

b. Compact density — POSRegister shape

Register — $128.00

  • T-shirt M× 1
  • Poster A2× 2
  • VIP pass× 1

c. Grouped — rowGroups via slots.rowGroups → DataTable

Sales by product family

Sales by product family
EventQty
Jazz & blues
Miles Davis Tribute2
Diana Krall Quartet4
Classical
Bach Brandenburg 32
Beethoven Symphony 91
World music
Afrobeat Night3
Celtic Folk Fest2

d. Loading — inline skeleton dot

Loading orders

e. Empty — inline empty block with emptyAction

Packages

No packages yet

f. Pagination-visible — total > pageSize

All rows

All rows (page 1 of 3)
NameQty
Row 11
Row 22
Row 33
Row 44
Row 55
Row 66
Row 77
Row 88
Row 99
Row 1010

This component supports light and dark mode.

Props

Prop
Type
Default
Description
title
string
—

Header title — also the region’s aria-label

actions
ReactNode
null

Slot for header buttons (e.g. + Create)

toolbar
ReactNode
null

Slot for filter/search controls — compose FilterChipGroup here

renderTable*
(slots) => ReactNode
—

Slot: caller provides the table body; ListPanel wraps with header, density padding, loading/empty wrappers, and pagination

rowGroups
ListPanelRowGroup<T>[]
—

Grouped-table support: labeled row clusters handed to the renderTable slot as slots.rowGroups (e.g. forwarded to DataTable’s rowGroups → th[scope="rowgroup"] headers); items derives by flattening the groups when not passed

density
"default" | "compact"
"default"

default = storefront spacing; compact = POSRegister-shaped (cashier/scanner register, receipt lists)

items
T[]
[]

Array of items for pagination/empty computation (derived from rowGroups when omitted)

loading
boolean
false

When true, renders an inline skeleton dot (animate-pulse) instead of the table

empty
boolean
items.length === 0

When true, renders an inline empty block instead of the table

emptyTitle
string
"No items"

Title in the inline empty block

emptyAction
ReactNode
null

CTA slot inside the inline empty block

page
number
1

Current page (1-indexed)

pageSize
number
25

Items per page (drives the Page X / Y computation)

total
number
items.length

Total items across all pages — pagination renders only when total > pageSize

onPageChange
(page: number) => void
—

Pagination callback (Prev/Next are no-ops without it)

Props

Preview

ListPanel

Prop
Type
Required
Default
Description
title
string
Optional
—

Header title

actions
ReactNode
Optional
null

Slot for header buttons (e.g. + Create)

toolbar
ReactNode
Optional
null

Slot for filter/search controls — compose FilterChipGroup here

renderTable
(state) => ReactNode
Required
—

Slot: caller provides the table body; ListPanel wraps with header, density padding, skeleton/empty wrappers, and pagination

density
default | compact
Optional
default

default = storefront spacing (table-card idiom); compact = POSRegister-shaped (cashier/scanner register, receipt lists)

items
T[]
Optional
[]

Array of items for pagination skeleton/empty computation

rowGroups
ListPanelRowGroup<T>[]
Optional
—

Grouped-table support: labeled row clusters passed through the renderTable slot (slots.rowGroups); items derive by flattening the groups when not passed

loading
boolean
Optional
false

When true, renders an inline skeleton dot (animate-pulse) instead of the table

empty
boolean
Optional
items.length === 0

When true, renders an inline empty block instead of the table

emptyTitle
string
Optional
"No items"

Title in the inline empty block (used when empty=true)

emptyAction
ReactNode
Optional
null

CTA slot inside the inline empty block

page
number
Optional
1

Current page (1-indexed)

pageSize
number
Optional
25

Total items per page

total
number
Optional
items.length

Total items across all pages

onPageChange
(page: number) => void
Optional
—

Pagination callback

Quick use:<ListPanel />

Accessibility

ARIA roleregion

behavior

1 rule

POSRegister recipe (listpanel.recipes.md)

pagination chrome only: totalPages=Math.ceil(total/pageSize), Page X / Y + Prev/Next when total > pageSize; item slicing is caller-owned via the renderTable slot

state

1 rule

ListPanel organism spec (orgplan D3)

empty+loading mutually exclusive

Usage

Import

tsxSnippet
import { ListPanel } from "@epasslive/design-system"

Example 1

tsxSnippet
<ListPanel title=Tickets renderTable={({items}) => <DataTable rows={items} />} />

Example 2

tsxSnippet
<ListPanel density=compact renderTable={({items}) => <RegisterTable rows={items} />} />

Example 3

tsxSnippet
<ListPanel rowGroups={groups} renderTable={({items, rowGroups}) => <DataTable rows={items} rowGroups={rowGroups} />} />

Anti-patterns

— Do not hand-roll header+Skeleton+EmptyState+pagination composition — ListPanel is the slot composition

— Do not put POSRegister as a wrapper organism over ListPanel — the recipe pattern keeps organisms neutral

Design System v4.1.0 | ePassLive