Data List Page
The standard enterprise listing page — search, filter, sortable table, pagination, row actions, and bulk selection.
Components used​
Layout · Header · DataTable · Search · FilterBar · Badge · Button · Modal
Code​
- HTML · @primus/ui-core
- React
- Angular
<div style="background:var(--background);width:100%;padding:1.25rem;box-sizing:border-box">
<!-- Page header -->
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem">
<div>
<h1 style="margin:0;font-size:1.25rem;font-weight:700">Tenants</h1>
<p style="margin:0;font-size:0.8rem;color:var(--muted-foreground)">142 total · 8 added this month</p>
</div>
<div class="hstack">
<button class="outline small">Export CSV</button>
<button class="small">+ Add Tenant</button>
</div>
</div>
<!-- Filter bar -->
<div class="hstack" style="flex-wrap:wrap;margin-bottom:1rem">
<input type="search" placeholder="Search tenants..." style="max-width:240px" />
<select style="max-width:140px">
<option>All Plans</option>
<option>Starter</option>
<option>Pro</option>
<option>Enterprise</option>
</select>
<select style="max-width:140px">
<option>All Status</option>
<option>Active</option>
<option>Trial</option>
<option>Suspended</option>
</select>
<button class="ghost small" style="margin-inline-start:auto;color:var(--muted-foreground)">Clear filters</button>
</div>
<!-- Table -->
<div class="card" style="padding:0;overflow:hidden">
<table style="width:100%;font-size:0.8rem;border-collapse:collapse">
<thead>
<tr style="border-bottom:1px solid var(--border)">
<th style="padding:0.625rem 1rem;text-align:left;width:32px"><input type="checkbox" /></th>
<th style="padding:0.625rem 0.5rem;text-align:left;font-weight:500;color:var(--muted-foreground);cursor:pointer">Tenant ↕</th>
<th style="padding:0.625rem 0.5rem;text-align:left;font-weight:500;color:var(--muted-foreground)">Plan</th>
<th style="padding:0.625rem 0.5rem;text-align:left;font-weight:500;color:var(--muted-foreground)">Status</th>
<th style="padding:0.625rem 0.5rem;text-align:left;font-weight:500;color:var(--muted-foreground)">Users</th>
<th style="padding:0.625rem 0.5rem;text-align:right;font-weight:500;color:var(--muted-foreground)">MRR</th>
<th style="padding:0.625rem 1rem;text-align:right;font-weight:500;color:var(--muted-foreground)">Actions</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:0.625rem 1rem"><input type="checkbox" /></td>
<td style="padding:0.625rem 0.5rem;font-weight:500">Acme Corp</td>
<td style="padding:0.625rem 0.5rem">Enterprise</td>
<td style="padding:0.625rem 0.5rem"><span class="badge success" style="font-size:0.7rem">Active</span></td>
<td style="padding:0.625rem 0.5rem">247</td>
<td style="padding:0.625rem 0.5rem;text-align:right">$12,000</td>
<td style="padding:0.625rem 1rem;text-align:right">
<button class="ghost small">Edit</button>
<button class="ghost small" style="color:var(--danger)">Suspend</button>
</td>
</tr>
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:0.625rem 1rem"><input type="checkbox" /></td>
<td style="padding:0.625rem 0.5rem;font-weight:500">Globex Inc</td>
<td style="padding:0.625rem 0.5rem">Pro</td>
<td style="padding:0.625rem 0.5rem"><span class="badge warning" style="font-size:0.7rem">Trial</span></td>
<td style="padding:0.625rem 0.5rem">14</td>
<td style="padding:0.625rem 0.5rem;text-align:right">$499</td>
<td style="padding:0.625rem 1rem;text-align:right">
<button class="ghost small">Edit</button>
<button class="ghost small" style="color:var(--danger)">Suspend</button>
</td>
</tr>
<tr>
<td style="padding:0.625rem 1rem"><input type="checkbox" /></td>
<td style="padding:0.625rem 0.5rem;font-weight:500">Initech</td>
<td style="padding:0.625rem 0.5rem">Starter</td>
<td style="padding:0.625rem 0.5rem"><span class="badge danger" style="font-size:0.7rem">Suspended</span></td>
<td style="padding:0.625rem 0.5rem">3</td>
<td style="padding:0.625rem 0.5rem;text-align:right">—</td>
<td style="padding:0.625rem 1rem;text-align:right">
<button class="ghost small">Edit</button>
<button class="ghost small" style="color:var(--success)">Activate</button>
</td>
</tr>
</tbody>
</table>
<!-- Pagination -->
<div style="padding:0.75rem 1rem;border-top:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;font-size:0.8rem">
<span style="color:var(--muted-foreground)">Showing 1–10 of 142</span>
<div class="hstack">
<button class="outline small" disabled>Previous</button>
<button class="outline small">Next</button>
</div>
</div>
</div>
</div>
import {
PrimusLayout, PrimusHeader,
PrimusDataTable, PrimusSearch, PrimusFilterBar,
PrimusButton, PrimusExportMenu, Badge,
} from 'primus-react-ui';
const columns = [
{ key: 'name', header: 'Tenant', sortable: true },
{ key: 'plan', header: 'Plan' },
{ key: 'status', header: 'Status',
render: row => <Badge variant={row.statusVariant}>{row.status}</Badge> },
{ key: 'users', header: 'Users' },
{ key: 'mrr', header: 'MRR' },
];
export function TenantsListPage() {
const [filters, setFilters] = useState({});
const [search, setSearch] = useState('');
return (
<PrimusLayout>
<PrimusHeader title="Tenants" subtitle="142 total · 8 added this month">
<PrimusExportMenu formats={['CSV']} onExport={handleExport} />
<PrimusButton onClick={() => navigate('/tenants/new')}>+ Add Tenant</PrimusButton>
</PrimusHeader>
<PrimusSearch placeholder="Search tenants..."
debounce={300} onSearch={setSearch} />
<PrimusFilterBar
filters={[
{ key: 'plan', label: 'Plan', type: 'select', options: ['Starter','Pro','Enterprise'] },
{ key: 'status', label: 'Status', type: 'select', options: ['Active','Trial','Suspended'] },
]}
activeFilters={filters}
onChange={setFilters}
/>
<PrimusDataTable
columns={columns}
data={tenants}
rowKey="id"
selectable
paginated
pageSize={10}
onRowClick={row => navigate('/tenants/' + row.id)}
/>
</PrimusLayout>
);
}
<primus-layout>
<primus-header title="Tenants" subtitle="142 total · 8 added this month">
<div header-actions>
<primus-export-menu [formats]="['CSV']" (exportTriggered)="onExport($event)"></primus-export-menu>
<primus-button (clicked)="openNew()">+ Add Tenant</primus-button>
</div>
</primus-header>
<primus-search placeholder="Search tenants..." [debounce]="300"
(search)="onSearch($event)">
</primus-search>
<primus-filter-bar [filters]="filterConfig" [activeFilters]="activeFilters"
(filtersChange)="onFilter($event)">
</primus-filter-bar>
<primus-data-table
[columns]="columns" [data]="tenants"
rowKey="id" [selectable]="true"
[paginated]="true" [pageSize]="10"
(onRowClick)="openDetail($event)">
</primus-data-table>
</primus-layout>