Documentation
Footer Renderer
Replace the default pagination footer with your own UI via footerRenderer.
Custom pagination footer
Pass footerRenderer with enablePagination. It replaces the default footer. Use hasPrevPage / hasNextPage to disable controls at the ends.
React TSX
Copy
const FooterBar = ({currentPage, totalPages, startRow, endRow, totalRows,hasPrevPage, hasNextPage, onPrevPage, onNextPage,}) => (<div style={{ display: "flex", justifyContent: "space-between", padding: 12 }}><span>Showing {startRow}-{endRow} of {totalRows}</span><div><button disabled={!hasPrevPage} onClick={onPrevPage}>Prev</button><span> {currentPage} / {totalPages} </span><button disabled={!hasNextPage} onClick={() => void onNextPage()}>Next</button></div></div>);<SimpleTablecolumns={columns}rows={rows}enablePaginationrowsPerPage={10}footerRenderer={FooterBar}/>
Angularfooter-bar.component.ts — @Input() currentPage, totalPages, …
Copy
// Template: Prev / page / Next wired to onPrevPage / onNextPage<simple-table[columns]="columns"[rows]="rows"[enablePagination]="true"[rowsPerPage]="10"[footerRenderer]="FooterBarComponent"></simple-table>
Vue SFC
Copy
import { h } from "vue";const FooterBar = (fp) =>h("div", { style: { display: "flex", justifyContent: "space-between", padding: "12px" } }, [h("span", `Showing ${fp.startRow}-${fp.endRow} of ${fp.totalRows}`),h("div", [h("button", { disabled: !fp.hasPrevPage, onClick: fp.onPrevPage }, "Prev"),h("span", ` ${fp.currentPage} / ${fp.totalPages} `),h("button", {disabled: !fp.hasNextPage,onClick: () => { void fp.onNextPage(); },}, "Next"),]),]);<SimpleTable:columns="columns":rows="rows":enable-pagination="true":rows-per-page="10":footer-renderer="FooterBar"/>
Svelte
Copy
<!-- FooterBar.svelte --><script lang="ts">import type { FooterRendererProps } from "@simple-table/svelte";let {currentPage, totalPages, startRow, endRow, totalRows,hasPrevPage, hasNextPage, onPrevPage, onNextPage,}: FooterRendererProps = $props();</script><div style="display:flex;justify-content:space-between;padding:12px"><span>Showing {startRow}-{endRow} of {totalRows}</span><div><button disabled={!hasPrevPage} onclick={onPrevPage}>Prev</button><span> {currentPage} / {totalPages} </span><button disabled={!hasNextPage} onclick={() => void onNextPage()}>Next</button></div></div><SimpleTable{columns}{rows}enablePagination={true}rowsPerPage={10}footerRenderer={FooterBar}/>
Solid TSX
Copy
const FooterBar = (props) => (<div style={{ display: "flex", "justify-content": "space-between", padding: "12px" }}><span>Showing {props.startRow}-{props.endRow} of {props.totalRows}</span><div><button disabled={!props.hasPrevPage} onClick={props.onPrevPage}>Prev</button><span> {props.currentPage} / {props.totalPages} </span><button disabled={!props.hasNextPage} onClick={() => void props.onNextPage()}>Next</button></div></div>);<SimpleTablecolumns={columns}rows={rows()}enablePaginationrowsPerPage={10}footerRenderer={FooterBar}/>
TypeScript
Copy
const footerRenderer = ({currentPage, totalPages, startRow, endRow, totalRows,hasPrevPage, hasNextPage, onPrevPage, onNextPage,}) => {const el = document.createElement("div");el.style.cssText = "display:flex;justify-content:space-between;padding:12px";el.innerHTML = `<span>Showing ${startRow}-${endRow} of ${totalRows}</span><div><button ${!hasPrevPage ? "disabled" : ""} data-prev>Prev</button><span> ${currentPage} / ${totalPages} </span><button ${!hasNextPage ? "disabled" : ""} data-next>Next</button></div>`;el.querySelector("[data-prev]")?.addEventListener("click", onPrevPage);el.querySelector("[data-next]")?.addEventListener("click", () => { void onNextPage(); });return el;};new SimpleTableVanilla(container, {columns,rows,enablePagination: true,rowsPerPage: 10,footerRenderer,});
Footer above the table
Set footerPosition="top" for the default or custom footer. Defaults to "bottom".
React TSX
Copy
<SimpleTablecolumns={columns}rows={rows}enablePaginationfooterPosition="top"footerRenderer={FooterBar}/>
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"[enablePagination]="true"footerPosition="top"[footerRenderer]="FooterBarComponent"></simple-table>
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows":enable-pagination="true"footer-position="top":footer-renderer="FooterBar"/>
Svelte
Copy
<SimpleTable{columns}{rows}enablePagination={true}footerPosition="top"footerRenderer={FooterBar}/>
Solid TSX
Copy
<SimpleTablecolumns={columns}rows={rows()}enablePaginationfooterPosition="top"footerRenderer={FooterBar}/>
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,enablePagination: true,footerPosition: "top",footerRenderer,});
Notes
Page numbers are 1-based. onNextPage is async. For pagination setup without a custom footer, see Pagination.
Example
Custom footer with page info and navigation. Use Code or StackBlitz for the full example.
React TSX
Copy
1import {SimpleTable} from "@simple-table/react";import type { Theme, FooterRendererProps } from "@simple-table/react";2import { footerRendererConfig } from "./footer-renderer.demo-data";3import "@simple-table/react/styles.css";45function getFooterColors(theme?: Theme) {6 const isModernBlack = theme === "modern-black";7 const isModernDark = theme === "modern-dark";8 const isDark = theme === "dark" || isModernDark || isModernBlack;9 const isModernLight = theme === "modern-light";10 const isLight = theme === "light" || isModernLight;1112 if (isModernBlack)13 return {14 background: "#141414", border: "#262626", text: "#a3a3a3",15 buttonBg: "#1c1c1c", buttonBorder: "#262626", buttonActive: "#3b82f6",16 buttonText: "#fafafa", buttonDisabled: "#737373"17 };18 if (isModernDark)19 return {20 background: "#1f2937", border: "#374151", text: "#d1d5db",21 buttonBg: "#374151", buttonBorder: "#4b5563", buttonActive: "#3b82f6",22 buttonText: "#d1d5db", buttonDisabled: "#6b7280"23 };24 if (isDark)25 return {26 background: "#1f2937", border: "#374151", text: "#e5e7eb",27 buttonBg: "#374151", buttonBorder: "#4b5563", buttonActive: "#3b82f6",28 buttonText: "#d1d5db", buttonDisabled: "#6b7280"29 };30 if (isLight)31 return {32 background: "white", border: "#f3f4f6", text: "#6b7280",33 buttonBg: "white", buttonBorder: "#e5e7eb", buttonActive: "#3b82f6",34 buttonText: "#374151", buttonDisabled: "#d1d5db"35 };36 return {37 background: "#f8fafc", border: "#e2e8f0", text: "#475569",38 buttonBg: "white", buttonBorder: "#e2e8f0", buttonActive: "#3b82f6",39 buttonText: "#64748b", buttonDisabled: "#cbd5e1"40 };41}4243const FooterRendererDemo = ({44 height = "400px",45 theme46}: {47 height?: string | number;48 theme?: Theme;49}) => {50 const c = getFooterColors(theme);5152 return (53 <SimpleTable54 columns={footerRendererConfig.headers}55 rows={footerRendererConfig.rows}56 enablePagination={true}57 rowsPerPage={10}58 height={height}59 theme={theme}60 getRowId={({ row }) => row.id}61 footerRenderer={({62 currentPage,63 startRow,64 endRow,65 totalRows,66 totalPages,67 hasPrevPage,68 hasNextPage,69 onPrevPage,70 onNextPage,71 onPageChange72 }: FooterRendererProps) => (73 <div74 style={{75 display: "flex",76 alignItems: "center",77 justifyContent: "space-between",78 padding: "16px 20px",79 backgroundColor: c.background,80 borderTop: `2px solid ${c.border}`81 }}82 >83 <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>84 <span style={{ fontSize: "14px", fontWeight: 600, color: c.text }}>85 Showing {startRow}-{endRow} of {totalRows} items86 </span>87 </div>8889 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>90 <button91 onClick={onPrevPage}92 disabled={!hasPrevPage}93 style={{94 padding: "8px 16px", fontSize: "14px", fontWeight: 500,95 color: hasPrevPage ? c.buttonActive : c.buttonDisabled,96 backgroundColor: c.buttonBg, border: `1px solid ${c.buttonBorder}`,97 borderRadius: "6px", cursor: hasPrevPage ? "pointer" : "not-allowed",98 transition: "all 0.2s"99 }}100 >101 Previous102 </button>103104 <div style={{ display: "flex", gap: "4px" }}>105 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (106 <button107 key={page}108 onClick={() => onPageChange(page)}109 style={{110 padding: "8px 12px", fontSize: "14px", fontWeight: 500,111 color: currentPage === page ? "white" : c.buttonText,112 backgroundColor: currentPage === page ? c.buttonActive : c.buttonBg,113 border: `1px solid ${c.buttonBorder}`, borderRadius: "6px",114 cursor: "pointer", transition: "all 0.2s", minWidth: "40px"115 }}116 >117 {page}118 </button>119 ))}120 </div>121122 <button123 onClick={onNextPage}124 disabled={!hasNextPage}125 style={{126 padding: "8px 16px", fontSize: "14px", fontWeight: 500,127 color: hasNextPage ? c.buttonActive : c.buttonDisabled,128 backgroundColor: c.buttonBg, border: `1px solid ${c.buttonBorder}`,129 borderRadius: "6px", cursor: hasNextPage ? "pointer" : "not-allowed",130 transition: "all 0.2s"131 }}132 >133 Next134 </button>135 </div>136 </div>137 )}138 />139 );140};141142export default FooterRendererDemo;
Angularfooter-demo-theme-context.ts
Copy
1import { signal } from "@angular/core";2import type { Theme } from "@simple-table/angular";34export const footerDemoThemeContext = signal<Theme | undefined>(undefined);567// footer-pagination.component.ts8import { Component, computed, Input } from "@angular/core";9import type { FooterRendererProps } from "@simple-table/angular";10import type { Theme } from "@simple-table/angular";11import { footerDemoThemeContext } from "./footer-demo-theme-context";1213function palette(theme?: Theme) {14 switch (theme) {15 case "modern-black":16 return {17 background: "#141414",18 border: "#262626",19 text: "#a3a3a3",20 buttonBg: "#1c1c1c",21 buttonBorder: "#262626",22 buttonActive: "#3b82f6",23 buttonText: "#fafafa",24 buttonDisabled: "#737373",25 };26 case "modern-dark":27 case "dark":28 return {29 background: "#1f2937",30 border: "#374151",31 text: "#d1d5db",32 buttonBg: "#374151",33 buttonBorder: "#4b5563",34 buttonActive: "#3b82f6",35 buttonText: "#d1d5db",36 buttonDisabled: "#6b7280",37 };38 case "light":39 case "modern-light":40 return {41 background: "white",42 border: "#f3f4f6",43 text: "#6b7280",44 buttonBg: "white",45 buttonBorder: "#e5e7eb",46 buttonActive: "#3b82f6",47 buttonText: "#374151",48 buttonDisabled: "#d1d5db",49 };50 default:51 return {52 background: "#f8fafc",53 border: "#e2e8f0",54 text: "#475569",55 buttonBg: "white",56 buttonBorder: "#e2e8f0",57 buttonActive: "#3b82f6",58 buttonText: "#64748b",59 buttonDisabled: "#cbd5e1",60 };61 }62}6364@Component({65 standalone: true,66 selector: "demo-footer-pagination",67 template: `68 <div69 style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;"70 [style.background-color]="colors().background"71 [style.border-top]="'2px solid ' + colors().border"72 >73 <span style="font-size:14px;font-weight:600;" [style.color]="colors().text">74 Showing {{ startRow }}–{{ endRow }} of {{ totalRows }} items75 </span>76 <div style="display:flex;align-items:center;gap:8px;">77 <button78 type="button"79 [disabled]="!hasPrevPage"80 (click)="onPrevPage()"81 [style.padding]="'8px 16px'"82 [style.font-size]="'14px'"83 [style.font-weight]="'500'"84 [style.color]="btnStylePrev().color"85 [style.background-color]="btnStylePrev().bg"86 [style.border]="'1px solid ' + colors().buttonBorder"87 [style.border-radius]="'6px'"88 [style.cursor]="hasPrevPage ? 'pointer' : 'not-allowed'"89 [style.min-width]="'40px'"90 >91 Previous92 </button>93 <div style="display:flex;gap:4px;">94 @for (p of pageNumbers(); track p) {95 <button96 type="button"97 (click)="onPageChange(p)"98 [style.padding]="'8px 12px'"99 [style.font-size]="'14px'"100 [style.font-weight]="'500'"101 [style.color]="pageBtnStyle(p).color"102 [style.background-color]="pageBtnStyle(p).bg"103 [style.border]="'1px solid ' + colors().buttonBorder"104 [style.border-radius]="'6px'"105 style="cursor:pointer;min-width:40px;"106 >107 {{ p }}108 </button>109 }110 </div>111 <button112 type="button"113 [disabled]="!hasNextPage"114 (click)="onNext()"115 [style.padding]="'8px 16px'"116 [style.font-size]="'14px'"117 [style.font-weight]="'500'"118 [style.color]="btnStyleNext().color"119 [style.background-color]="btnStyleNext().bg"120 [style.border]="'1px solid ' + colors().buttonBorder"121 [style.border-radius]="'6px'"122 [style.cursor]="hasNextPage ? 'pointer' : 'not-allowed'"123 [style.min-width]="'40px'"124 >125 Next126 </button>127 </div>128 </div>129 `,130})131export class FooterPaginationComponent {132 @Input({ required: true }) currentPage!: FooterRendererProps["currentPage"];133 @Input({ required: true }) endRow!: FooterRendererProps["endRow"];134 @Input({ required: true }) hasNextPage!: FooterRendererProps["hasNextPage"];135 @Input({ required: true }) hasPrevPage!: FooterRendererProps["hasPrevPage"];136 @Input({ required: true }) onNextPage!: FooterRendererProps["onNextPage"];137 @Input({ required: true }) onPageChange!: FooterRendererProps["onPageChange"];138 @Input({ required: true }) onPrevPage!: FooterRendererProps["onPrevPage"];139 @Input({ required: true }) rowsPerPage!: FooterRendererProps["rowsPerPage"];140 @Input({ required: true }) startRow!: FooterRendererProps["startRow"];141 @Input({ required: true }) totalPages!: FooterRendererProps["totalPages"];142 @Input({ required: true }) totalRows!: FooterRendererProps["totalRows"];143144 readonly colors = computed(() => palette(footerDemoThemeContext()));145146 pageNumbers(): number[] {147 return Array.from({ length: this.totalPages }, (_, i) => i + 1);148 }149150 btnStylePrev(): { color: string; bg: string } {151 const c = this.colors();152 return {153 color: !this.hasPrevPage ? c.buttonDisabled : c.buttonActive,154 bg: c.buttonBg,155 };156 }157158 btnStyleNext(): { color: string; bg: string } {159 const c = this.colors();160 return {161 color: !this.hasNextPage ? c.buttonDisabled : c.buttonActive,162 bg: c.buttonBg,163 };164 }165166 pageBtnStyle(p: number): { color: string; bg: string } {167 const c = this.colors();168 const active = p === this.currentPage;169 return {170 color: active ? "white" : c.buttonActive,171 bg: active ? c.buttonActive : c.buttonBg,172 };173 }174175 onNext(): void {176 void this.onNextPage();177 }178}179180181// footer-renderer-demo.component.ts182import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from "@angular/core";183import { SimpleTableComponent } from "@simple-table/angular";184import type { AngularColumnDef, GetRowIdParams, Theme } from "@simple-table/angular";185import { footerDemoThemeContext } from "./footer-demo-theme-context";186import { FooterPaginationComponent } from "./footer-pagination.component";187import { footerRendererConfig } from "./footer-renderer.demo-data";188import "@simple-table/angular/styles.css";189import type { CatalogProduct } from "./footer-renderer.demo-data";190191@Component({192 selector: "footer-renderer-demo",193 standalone: true,194 imports: [SimpleTableComponent],195 template: `196 <simple-table197 [getRowId]="getRowId"198 [rows]="rows"199 [columns]="headers"200 [footerRenderer]="footerRenderer"201 [enablePagination]="true"202 [rowsPerPage]="10"203 [hideFooter]="false"204 [height]="height"205 [theme]="theme"206 ></simple-table>207 `,208})209export class FooterRendererDemoComponent implements OnInit, OnChanges, OnDestroy {210 @Input() height: string | number = "400px";211 @Input() theme?: Theme;212213 readonly rows: CatalogProduct[] = footerRendererConfig.rows;214 readonly headers: AngularColumnDef<CatalogProduct>[] = footerRendererConfig.headers;215 readonly footerRenderer = FooterPaginationComponent;216217 ngOnInit(): void {218 footerDemoThemeContext.set(this.theme);219 }220221 ngOnChanges(changes: SimpleChanges): void {222 if (changes["theme"]) {223 footerDemoThemeContext.set(this.theme);224 }225 }226227 ngOnDestroy(): void {228 footerDemoThemeContext.set(undefined);229 }230}231232233// footer-renderer.demo-data.ts234// Self-contained demo table setup for this example.235import type { AngularColumnDef } from "@simple-table/angular";236237export interface CatalogProduct {238 id: number;239 product: string;240 category: string;241 price: number;242 stock: number;243 status: string;244}245246export const footerRendererHeaders: AngularColumnDef<CatalogProduct>[] = [247 { accessor: "id", label: "ID", width: 60, type: "number" },248 { accessor: "product", label: "Product Name", width: 220, type: "string" },249 { accessor: "category", label: "Category", width: 150, type: "string" },250 { accessor: "price", label: "Price", width: 100, type: "number" },251 { accessor: "stock", label: "Stock", width: 100, type: "number" },252 { accessor: "status", label: "Status", width: "1fr", type: "string" },253];254255export const footerRendererData: CatalogProduct[] = [256 { id: 1, product: "MacBook Pro 16-inch M3 Max", category: "Laptops", price: 3499, stock: 28, status: "In Stock" },257 { id: 2, product: "Dell XPS 15 OLED Touchscreen", category: "Laptops", price: 2299, stock: 42, status: "In Stock" },258 { id: 3, product: "ThinkPad X1 Carbon Gen 11", category: "Laptops", price: 1899, stock: 35, status: "In Stock" },259 { id: 4, product: "HP Spectre x360 Convertible", category: "Laptops", price: 1649, stock: 51, status: "In Stock" },260 { id: 5, product: "ASUS ROG Strix Gaming Laptop", category: "Laptops", price: 2199, stock: 19, status: "In Stock" },261 { id: 6, product: "Logitech MX Master 3S Wireless", category: "Accessories", price: 99, stock: 342, status: "In Stock" },262 { id: 7, product: "Apple Magic Mouse Black", category: "Accessories", price: 89, stock: 218, status: "In Stock" },263 { id: 8, product: "Razer DeathAdder V3 Pro", category: "Accessories", price: 149, stock: 167, status: "In Stock" },264 { id: 9, product: "Microsoft Surface Precision Mouse", category: "Accessories", price: 79, stock: 203, status: "In Stock" },265 { id: 10, product: "Corsair K95 RGB Platinum XT", category: "Keyboards", price: 199, stock: 89, status: "In Stock" },266 { id: 11, product: "Keychron Q1 Pro Mechanical", category: "Keyboards", price: 189, stock: 134, status: "In Stock" },267 { id: 12, product: "Ducky One 3 TKL RGB", category: "Keyboards", price: 159, stock: 76, status: "In Stock" },268 { id: 13, product: "Leopold FC900R PD Cherry MX", category: "Keyboards", price: 169, stock: 54, status: "In Stock" },269 { id: 14, product: "LG UltraGear 27-inch 4K 144Hz", category: "Monitors", price: 799, stock: 31, status: "In Stock" },270 { id: 15, product: "Samsung Odyssey G9 Curved", category: "Monitors", price: 1299, stock: 18, status: "In Stock" },271 { id: 16, product: "Dell UltraSharp U2723DE 27in", category: "Monitors", price: 649, stock: 47, status: "In Stock" },272 { id: 17, product: "BenQ PD3220U Designer 32in", category: "Monitors", price: 1099, stock: 23, status: "In Stock" },273 { id: 18, product: "ASUS ProArt Display PA279CRV", category: "Monitors", price: 549, stock: 39, status: "In Stock" },274 { id: 19, product: "Sony WH-1000XM5 Noise Cancelling", category: "Audio", price: 399, stock: 127, status: "In Stock" },275 { id: 20, product: "Bose QuietComfort Ultra", category: "Audio", price: 429, stock: 98, status: "In Stock" },276 { id: 21, product: "Apple AirPods Max Space Gray", category: "Audio", price: 549, stock: 82, status: "In Stock" },277 { id: 22, product: "Sennheiser Momentum 4 Wireless", category: "Audio", price: 349, stock: 104, status: "In Stock" },278 { id: 23, product: "Logitech C920 HD Pro Webcam", category: "Video", price: 79, stock: 245, status: "In Stock" },279 { id: 24, product: "Elgato Facecam Pro 4K60", category: "Video", price: 299, stock: 67, status: "In Stock" },280 { id: 25, product: "Razer Kiyo Pro Ultra 4K", category: "Video", price: 329, stock: 41, status: "In Stock" },281 { id: 26, product: "Herman Miller Aeron Ergonomic", category: "Furniture", price: 1695, stock: 12, status: "Low Stock" },282 { id: 27, product: "Steelcase Leap V2 Office Chair", category: "Furniture", price: 1299, stock: 15, status: "In Stock" },283 { id: 28, product: "Autonomous SmartDesk Pro", category: "Furniture", price: 899, stock: 28, status: "In Stock" },284 { id: 29, product: "Uplift V2 Standing Desk Frame", category: "Furniture", price: 749, stock: 34, status: "In Stock" },285 { id: 30, product: "FlexiSpot E7 Plus Adjustable", category: "Furniture", price: 649, stock: 45, status: "In Stock" },286 { id: 31, product: "Anker PowerCore 20000mAh", category: "Power", price: 49, stock: 412, status: "In Stock" },287 { id: 32, product: "RAVPower 60W USB-C Charger", category: "Power", price: 39, stock: 387, status: "In Stock" },288 { id: 33, product: "Belkin BoostCharge Pro 3-in-1", category: "Power", price: 149, stock: 156, status: "In Stock" },289 { id: 34, product: "Samsung T7 Shield 2TB SSD", category: "Storage", price: 199, stock: 234, status: "In Stock" },290 { id: 35, product: "SanDisk Extreme Pro 1TB Portable", category: "Storage", price: 159, stock: 189, status: "In Stock" },291 { id: 36, product: "WD My Passport 5TB External", category: "Storage", price: 139, stock: 276, status: "In Stock" },292 { id: 37, product: "Crucial X9 Pro 4TB Rugged", category: "Storage", price: 289, stock: 143, status: "In Stock" },293 { id: 38, product: "CalDigit TS4 Thunderbolt 4 Dock", category: "Hubs & Docks", price: 399, stock: 52, status: "In Stock" },294 { id: 39, product: "Anker 577 Thunderbolt Docking", category: "Hubs & Docks", price: 299, stock: 78, status: "In Stock" },295 { id: 40, product: "HyperDrive Gen2 16-Port USB-C", category: "Hubs & Docks", price: 249, stock: 91, status: "In Stock" },296 { id: 41, product: "Blue Yeti X Professional USB", category: "Audio", price: 169, stock: 123, status: "In Stock" },297 { id: 42, product: "Shure MV7 Podcast Microphone", category: "Audio", price: 249, stock: 87, status: "In Stock" },298 { id: 43, product: "Elgato Wave:3 Premium USB", category: "Audio", price: 159, stock: 104, status: "In Stock" },299 { id: 44, product: "Rode NT-USB Mini Studio", category: "Audio", price: 99, stock: 167, status: "In Stock" },300 { id: 45, product: "Audio-Technica AT2020USB+", category: "Audio", price: 149, stock: 145, status: "In Stock" },301];302303export const footerRendererConfig = {304 headers: footerRendererHeaders,305 rows: footerRendererData,306 tableProps: {307 enablePagination: true,308 rowsPerPage: 10,309 },310};311
Vue SFC
Copy
1<script setup lang="ts">2import { defineComponent, h } from "vue";3import { SimpleTable } from "@simple-table/vue";4import type { Theme, FooterRendererProps, GetRowIdParams } from "@simple-table/vue";5import { footerRendererConfig } from "./footer-renderer.demo-data";6import type { CatalogProduct } from "./footer-renderer.demo-data";7import "@simple-table/vue/styles.css";89const getRowId = ({ row }: GetRowIdParams<CatalogProduct>) => row.id;1011const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {12 height: "400px",13});1415function getFooterColors(theme?: Theme) {16 switch (theme) {17 case "modern-black":18 return {19 background: "#141414",20 border: "#262626",21 text: "#a3a3a3",22 buttonBg: "#1c1c1c",23 buttonBorder: "#262626",24 buttonActive: "#3b82f6",25 buttonText: "#fafafa",26 buttonDisabled: "#737373",27 };28 case "modern-dark":29 case "dark":30 return {31 background: "#1f2937",32 border: "#374151",33 text: "#d1d5db",34 buttonBg: "#374151",35 buttonBorder: "#4b5563",36 buttonActive: "#3b82f6",37 buttonText: "#d1d5db",38 buttonDisabled: "#6b7280",39 };40 case "light":41 case "modern-light":42 return {43 background: "white",44 border: "#f3f4f6",45 text: "#6b7280",46 buttonBg: "white",47 buttonBorder: "#e5e7eb",48 buttonActive: "#3b82f6",49 buttonText: "#374151",50 buttonDisabled: "#d1d5db",51 };52 default:53 return {54 background: "#f8fafc",55 border: "#e2e8f0",56 text: "#475569",57 buttonBg: "white",58 buttonBorder: "#e2e8f0",59 buttonActive: "#3b82f6",60 buttonText: "#64748b",61 buttonDisabled: "#cbd5e1",62 };63 }64}6566const FooterBar = defineComponent({67 name: "FooterBar",68 setup(fp: FooterRendererProps) {69 return () => {70 const c = getFooterColors(props.theme);71 const btnStyle = (disabled: boolean, active = false) => ({72 padding: "8px 16px",73 fontSize: "14px",74 fontWeight: "500",75 color: active ? "white" : disabled ? c.buttonDisabled : c.buttonActive,76 backgroundColor: active ? c.buttonActive : c.buttonBg,77 border: `1px solid ${c.buttonBorder}`,78 borderRadius: "6px",79 cursor: disabled ? ("not-allowed" as const) : ("pointer" as const),80 transition: "all 0.2s",81 minWidth: "40px",82 });83 return h(84 "div",85 {86 style: {87 display: "flex",88 alignItems: "center",89 justifyContent: "space-between",90 padding: "16px 20px",91 backgroundColor: c.background,92 borderTop: `2px solid ${c.border}`,93 },94 },95 [96 h(97 "span",98 { style: { fontSize: "14px", fontWeight: "600", color: c.text } },99 `Showing ${fp.startRow}–${fp.endRow} of ${fp.totalRows} items`,100 ),101 h(102 "div",103 { style: { display: "flex", alignItems: "center", gap: "8px" } },104 [105 h("button", {106 style: btnStyle(!fp.hasPrevPage),107 disabled: !fp.hasPrevPage,108 onClick: fp.onPrevPage,109 }, "Previous"),110 h(111 "div",112 { style: { display: "flex", gap: "4px" } },113 Array.from({ length: fp.totalPages }, (_, i) => i + 1).map((page) =>114 h("button", {115 key: page,116 style: { ...btnStyle(false, page === fp.currentPage), padding: "8px 12px" },117 onClick: () => fp.onPageChange(page),118 }, String(page)),119 ),120 ),121 h("button", {122 style: btnStyle(!fp.hasNextPage),123 disabled: !fp.hasNextPage,124 onClick: () => {125 void fp.onNextPage();126 },127 }, "Next"),128 ],129 ),130 ],131 );132 };133 },134});135</script>136137<template>138 <SimpleTable139 :columns="footerRendererConfig.headers"140 :rows="footerRendererConfig.rows"141 :get-row-id="getRowId"142 :footer-renderer="FooterBar"143 :enable-pagination="true"144 :rows-per-page="10"145 :hide-footer="false"146 :height="props.height"147 :theme="props.theme"148 />149</template>
SvelteFooterDemoBar.svelte
Copy
1<script lang="ts">2 import { get } from "svelte/store";3 import type { FooterRendererProps, Theme } from "@simple-table/svelte";4 import { footerDemoTheme } from "./footer-demo-theme";56 let fp: FooterRendererProps = $props();78 function getFooterColors(t?: Theme) {9 switch (t) {10 case "modern-black":11 return {12 background: "#141414",13 border: "#262626",14 text: "#a3a3a3",15 buttonBg: "#1c1c1c",16 buttonBorder: "#262626",17 buttonActive: "#3b82f6",18 buttonDisabled: "#737373",19 };20 case "modern-dark":21 case "dark":22 return {23 background: "#1f2937",24 border: "#374151",25 text: "#d1d5db",26 buttonBg: "#374151",27 buttonBorder: "#4b5563",28 buttonActive: "#3b82f6",29 buttonDisabled: "#6b7280",30 };31 case "light":32 case "modern-light":33 return {34 background: "white",35 border: "#f3f4f6",36 text: "#6b7280",37 buttonBg: "white",38 buttonBorder: "#e5e7eb",39 buttonActive: "#3b82f6",40 buttonDisabled: "#d1d5db",41 };42 default:43 return {44 background: "#f8fafc",45 border: "#e2e8f0",46 text: "#475569",47 buttonBg: "white",48 buttonBorder: "#e2e8f0",49 buttonActive: "#3b82f6",50 buttonDisabled: "#cbd5e1",51 };52 }53 }5455 let c = $state(getFooterColors(get(footerDemoTheme)));56 $effect(() => {57 c = getFooterColors($footerDemoTheme);58 });5960 function btnStyle(disabled: boolean, active = false): string {61 const color = active ? "white" : disabled ? c.buttonDisabled : c.buttonActive;62 const bg = active ? c.buttonActive : c.buttonBg;63 return [64 `padding:8px 16px`,65 `font-size:14px`,66 `font-weight:500`,67 `color:${color}`,68 `background-color:${bg}`,69 `border:1px solid ${c.buttonBorder}`,70 `border-radius:6px`,71 `cursor:${disabled ? "not-allowed" : "pointer"}`,72 `transition:all 0.2s`,73 `min-width:40px`,74 ].join(";");75 }76</script>7778<div79 style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background-color:{c.background};border-top:2px solid {c.border};"80>81 <span style="font-size:14px;font-weight:600;color:{c.text};">82 Showing {fp.startRow}–{fp.endRow} of {fp.totalRows} items83 </span>84 <div style="display:flex;align-items:center;gap:8px;">85 <button type="button" style={btnStyle(!fp.hasPrevPage)} disabled={!fp.hasPrevPage} onclick={() => fp.onPrevPage()}>86 Previous87 </button>88 <div style="display:flex;gap:4px;">89 {#each Array.from({ length: fp.totalPages }, (_, i) => i + 1) as page (page)}90 <button91 type="button"92 style="{btnStyle(false, page === fp.currentPage)};padding:8px 12px;"93 onclick={() => fp.onPageChange(page)}94 >95 {page}96 </button>97 {/each}98 </div>99 <button100 type="button"101 style={btnStyle(!fp.hasNextPage)}102 disabled={!fp.hasNextPage}103 onclick={() => void fp.onNextPage()}104 >105 Next106 </button>107 </div>108</div>109110111// FooterRendererDemo.svelte112<script lang="ts">113 import { SimpleTable } from "@simple-table/svelte";114 import type { Theme, GetRowIdParams } from "@simple-table/svelte";115 import { footerRendererConfig } from "./footer-renderer.demo-data";116 import type { CatalogProduct } from "./footer-renderer.demo-data";117 import { footerDemoTheme } from "./footer-demo-theme";118 import FooterDemoBar from "./FooterDemoBar.svelte";119 import "@simple-table/svelte/styles.css";120121 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();122123 const getRowId = ({ row }: GetRowIdParams<CatalogProduct>) => row.id;124125 $effect.pre(() => {126 footerDemoTheme.set(theme);127 });128</script>129130<SimpleTable131 columns={footerRendererConfig.headers}132 rows={footerRendererConfig.rows}133 {getRowId}134 footerRenderer={FooterDemoBar}135 enablePagination={true}136 rowsPerPage={10}137 hideFooter={false}138 {height}139 {theme}140/>141
Solid TSX
Copy
1import {SimpleTable} from "@simple-table/solid";import type { Theme, FooterRendererProps } from "@simple-table/solid";2import { footerRendererConfig } from "./footer-renderer.demo-data";3import { For } from "solid-js";4import "@simple-table/solid/styles.css";56function getFooterColors(theme?: Theme) {7 const isModernBlack = theme === "modern-black";8 const isModernDark = theme === "modern-dark";9 const isDark = theme === "dark" || isModernDark || isModernBlack;10 const isModernLight = theme === "modern-light";11 const isLight = theme === "light" || isModernLight;1213 if (isModernBlack)14 return {15 background: "#141414", border: "#262626", text: "#a3a3a3",16 buttonBg: "#1c1c1c", buttonBorder: "#262626", buttonActive: "#3b82f6",17 buttonText: "#fafafa", buttonDisabled: "#737373",18 };19 if (isModernDark)20 return {21 background: "#1f2937", border: "#374151", text: "#d1d5db",22 buttonBg: "#374151", buttonBorder: "#4b5563", buttonActive: "#3b82f6",23 buttonText: "#d1d5db", buttonDisabled: "#6b7280",24 };25 if (isDark)26 return {27 background: "#1f2937", border: "#374151", text: "#e5e7eb",28 buttonBg: "#374151", buttonBorder: "#4b5563", buttonActive: "#3b82f6",29 buttonText: "#d1d5db", buttonDisabled: "#6b7280",30 };31 if (isLight)32 return {33 background: "white", border: "#f3f4f6", text: "#6b7280",34 buttonBg: "white", buttonBorder: "#e5e7eb", buttonActive: "#3b82f6",35 buttonText: "#374151", buttonDisabled: "#d1d5db",36 };37 return {38 background: "#f8fafc", border: "#e2e8f0", text: "#475569",39 buttonBg: "white", buttonBorder: "#e2e8f0", buttonActive: "#3b82f6",40 buttonText: "#64748b", buttonDisabled: "#cbd5e1",41 };42}4344export default function FooterRendererDemo(props: { height?: string | number; theme?: Theme }) {45 const c = getFooterColors(props.theme);46 const pages = (totalPages: number) => Array.from({ length: totalPages }, (_, i) => i + 1);4748 return (49 <SimpleTable50 columns={footerRendererConfig.headers}51 getRowId={({ row }) => row.id}52 rows={footerRendererConfig.rows}53 enablePagination={true}54 rowsPerPage={10}55 height={props.height ?? "400px"}56 theme={props.theme}57 footerRenderer={(fp: FooterRendererProps) => (58 <div59 style={{60 display: "flex",61 "align-items": "center",62 "justify-content": "space-between",63 padding: "16px 20px",64 "background-color": c.background,65 "border-top": `2px solid ${c.border}`,66 }}67 >68 <div style={{ display: "flex", "align-items": "center", gap: "12px" }}>69 <span style={{ "font-size": "14px", "font-weight": "600", color: c.text }}>70 Showing {fp.startRow}-{fp.endRow} of {fp.totalRows} items71 </span>72 </div>7374 <div style={{ display: "flex", "align-items": "center", gap: "8px" }}>75 <button76 onClick={fp.onPrevPage}77 disabled={!fp.hasPrevPage}78 style={{79 padding: "8px 16px", "font-size": "14px", "font-weight": "500",80 color: fp.hasPrevPage ? c.buttonActive : c.buttonDisabled,81 "background-color": c.buttonBg, border: `1px solid ${c.buttonBorder}`,82 "border-radius": "6px",83 cursor: fp.hasPrevPage ? "pointer" : "not-allowed",84 transition: "all 0.2s",85 }}86 >87 Previous88 </button>8990 <div style={{ display: "flex", gap: "4px" }}>91 <For each={pages(fp.totalPages)}>92 {(page) => (93 <button94 onClick={() => fp.onPageChange(page)}95 style={{96 padding: "8px 12px", "font-size": "14px", "font-weight": "500",97 color: fp.currentPage === page ? "white" : c.buttonText,98 "background-color": fp.currentPage === page ? c.buttonActive : c.buttonBg,99 border: `1px solid ${c.buttonBorder}`, "border-radius": "6px",100 cursor: "pointer", transition: "all 0.2s", "min-width": "40px",101 }}102 >103 {page}104 </button>105 )}106 </For>107 </div>108109 <button110 onClick={() => fp.onNextPage()}111 disabled={!fp.hasNextPage}112 style={{113 padding: "8px 16px", "font-size": "14px", "font-weight": "500",114 color: fp.hasNextPage ? c.buttonActive : c.buttonDisabled,115 "background-color": c.buttonBg, border: `1px solid ${c.buttonBorder}`,116 "border-radius": "6px",117 cursor: fp.hasNextPage ? "pointer" : "not-allowed",118 transition: "all 0.2s",119 }}120 >121 Next122 </button>123 </div>124 </div>125 )}126 />127 );128}
TypeScriptFooterRendererDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { CatalogProduct } from "./footer-renderer.demo-data";3import type { Theme, FooterRendererProps, GetRowIdParams } from "simple-table-core";4import { footerRendererConfig } from "./footer-renderer.demo-data";5import "simple-table-core/styles.css";67function getFooterColors(theme?: Theme) {8 switch (theme) {9 case "modern-black":10 return {11 background: "#141414",12 border: "#262626",13 text: "#a3a3a3",14 buttonBg: "#1c1c1c",15 buttonBorder: "#262626",16 buttonActive: "#3b82f6",17 buttonText: "#fafafa",18 buttonDisabled: "#737373",19 };20 case "modern-dark":21 case "dark":22 return {23 background: "#1f2937",24 border: "#374151",25 text: "#d1d5db",26 buttonBg: "#374151",27 buttonBorder: "#4b5563",28 buttonActive: "#3b82f6",29 buttonText: "#d1d5db",30 buttonDisabled: "#6b7280",31 };32 case "light":33 case "modern-light":34 return {35 background: "white",36 border: "#f3f4f6",37 text: "#6b7280",38 buttonBg: "white",39 buttonBorder: "#e5e7eb",40 buttonActive: "#3b82f6",41 buttonText: "#374151",42 buttonDisabled: "#d1d5db",43 };44 default:45 return {46 background: "#f8fafc",47 border: "#e2e8f0",48 text: "#475569",49 buttonBg: "white",50 buttonBorder: "#e2e8f0",51 buttonActive: "#3b82f6",52 buttonText: "#64748b",53 buttonDisabled: "#cbd5e1",54 };55 }56}5758function createFooter(props: FooterRendererProps, theme?: Theme): HTMLElement {59 const c = getFooterColors(theme);6061 const wrapper = document.createElement("div");62 Object.assign(wrapper.style, {63 display: "flex",64 alignItems: "center",65 justifyContent: "space-between",66 padding: "16px 20px",67 backgroundColor: c.background,68 borderTop: `2px solid ${c.border}`,69 });7071 const info = document.createElement("span");72 Object.assign(info.style, { fontSize: "14px", fontWeight: "600", color: c.text });73 info.textContent = `Showing ${props.startRow}–${props.endRow} of ${props.totalRows} items`;74 wrapper.appendChild(info);7576 const controls = document.createElement("div");77 Object.assign(controls.style, { display: "flex", alignItems: "center", gap: "8px" });7879 function makeBtn(label: string, onClick: () => void, disabled: boolean, active = false) {80 const btn = document.createElement("button");81 btn.textContent = label;82 Object.assign(btn.style, {83 padding: "8px 16px",84 fontSize: "14px",85 fontWeight: "500",86 color: active ? "white" : disabled ? c.buttonDisabled : c.buttonActive,87 backgroundColor: active ? c.buttonActive : c.buttonBg,88 border: `1px solid ${c.buttonBorder}`,89 borderRadius: "6px",90 cursor: disabled ? "not-allowed" : "pointer",91 transition: "all 0.2s",92 minWidth: "40px",93 });94 btn.disabled = disabled;95 if (!disabled) btn.addEventListener("click", onClick);96 return btn;97 }9899 controls.appendChild(makeBtn("Previous", props.onPrevPage, !props.hasPrevPage));100101 const pages = document.createElement("div");102 Object.assign(pages.style, { display: "flex", gap: "4px" });103 for (let p = 1; p <= props.totalPages; p++) {104 const isActive = p === props.currentPage;105 const btn = makeBtn(String(p), () => props.onPageChange(p), false, isActive);106 btn.style.padding = "8px 12px";107 pages.appendChild(btn);108 }109 controls.appendChild(pages);110111 controls.appendChild(makeBtn("Next", () => props.onNextPage(), !props.hasNextPage));112113 wrapper.appendChild(controls);114 return wrapper;115}116117118const getRowId = ({ row }: GetRowIdParams<CatalogProduct>) => row.id;119export function renderFooterRendererDemo(120 container: HTMLElement,121 options?: { height?: string | number; theme?: Theme }122): SimpleTableVanilla<CatalogProduct> {123 const theme = options?.theme;124 const table = new SimpleTableVanilla(container, {125 getRowId,126 columns: [...footerRendererConfig.headers],127 rows: footerRendererConfig.rows,128 height: options?.height ?? "400px",129 theme,130 enablePagination: true,131 rowsPerPage: 10,132 footerRenderer: (props) => createFooter(props, theme),133 hideFooter: false,134 });135 return table;136}137138139// footer-renderer.demo-data.ts140// Self-contained demo table setup for this example.141import type { ColumnDef } from "simple-table-core";142143export interface CatalogProduct {144 id: number;145 product: string;146 category: string;147 price: number;148 stock: number;149 status: string;150}151152export const footerRendererHeaders: ColumnDef<CatalogProduct>[] = [153 { accessor: "id", label: "ID", width: 60, type: "number" },154 { accessor: "product", label: "Product Name", width: 220, type: "string" },155 { accessor: "category", label: "Category", width: 150, type: "string" },156 { accessor: "price", label: "Price", width: 100, type: "number" },157 { accessor: "stock", label: "Stock", width: 100, type: "number" },158 { accessor: "status", label: "Status", width: "1fr", type: "string" },159];160161export const footerRendererData: CatalogProduct[] = [162 { id: 1, product: "MacBook Pro 16-inch M3 Max", category: "Laptops", price: 3499, stock: 28, status: "In Stock" },163 { id: 2, product: "Dell XPS 15 OLED Touchscreen", category: "Laptops", price: 2299, stock: 42, status: "In Stock" },164 { id: 3, product: "ThinkPad X1 Carbon Gen 11", category: "Laptops", price: 1899, stock: 35, status: "In Stock" },165 { id: 4, product: "HP Spectre x360 Convertible", category: "Laptops", price: 1649, stock: 51, status: "In Stock" },166 { id: 5, product: "ASUS ROG Strix Gaming Laptop", category: "Laptops", price: 2199, stock: 19, status: "In Stock" },167 { id: 6, product: "Logitech MX Master 3S Wireless", category: "Accessories", price: 99, stock: 342, status: "In Stock" },168 { id: 7, product: "Apple Magic Mouse Black", category: "Accessories", price: 89, stock: 218, status: "In Stock" },169 { id: 8, product: "Razer DeathAdder V3 Pro", category: "Accessories", price: 149, stock: 167, status: "In Stock" },170 { id: 9, product: "Microsoft Surface Precision Mouse", category: "Accessories", price: 79, stock: 203, status: "In Stock" },171 { id: 10, product: "Corsair K95 RGB Platinum XT", category: "Keyboards", price: 199, stock: 89, status: "In Stock" },172 { id: 11, product: "Keychron Q1 Pro Mechanical", category: "Keyboards", price: 189, stock: 134, status: "In Stock" },173 { id: 12, product: "Ducky One 3 TKL RGB", category: "Keyboards", price: 159, stock: 76, status: "In Stock" },174 { id: 13, product: "Leopold FC900R PD Cherry MX", category: "Keyboards", price: 169, stock: 54, status: "In Stock" },175 { id: 14, product: "LG UltraGear 27-inch 4K 144Hz", category: "Monitors", price: 799, stock: 31, status: "In Stock" },176 { id: 15, product: "Samsung Odyssey G9 Curved", category: "Monitors", price: 1299, stock: 18, status: "In Stock" },177 { id: 16, product: "Dell UltraSharp U2723DE 27in", category: "Monitors", price: 649, stock: 47, status: "In Stock" },178 { id: 17, product: "BenQ PD3220U Designer 32in", category: "Monitors", price: 1099, stock: 23, status: "In Stock" },179 { id: 18, product: "ASUS ProArt Display PA279CRV", category: "Monitors", price: 549, stock: 39, status: "In Stock" },180 { id: 19, product: "Sony WH-1000XM5 Noise Cancelling", category: "Audio", price: 399, stock: 127, status: "In Stock" },181 { id: 20, product: "Bose QuietComfort Ultra", category: "Audio", price: 429, stock: 98, status: "In Stock" },182 { id: 21, product: "Apple AirPods Max Space Gray", category: "Audio", price: 549, stock: 82, status: "In Stock" },183 { id: 22, product: "Sennheiser Momentum 4 Wireless", category: "Audio", price: 349, stock: 104, status: "In Stock" },184 { id: 23, product: "Logitech C920 HD Pro Webcam", category: "Video", price: 79, stock: 245, status: "In Stock" },185 { id: 24, product: "Elgato Facecam Pro 4K60", category: "Video", price: 299, stock: 67, status: "In Stock" },186 { id: 25, product: "Razer Kiyo Pro Ultra 4K", category: "Video", price: 329, stock: 41, status: "In Stock" },187 { id: 26, product: "Herman Miller Aeron Ergonomic", category: "Furniture", price: 1695, stock: 12, status: "Low Stock" },188 { id: 27, product: "Steelcase Leap V2 Office Chair", category: "Furniture", price: 1299, stock: 15, status: "In Stock" },189 { id: 28, product: "Autonomous SmartDesk Pro", category: "Furniture", price: 899, stock: 28, status: "In Stock" },190 { id: 29, product: "Uplift V2 Standing Desk Frame", category: "Furniture", price: 749, stock: 34, status: "In Stock" },191 { id: 30, product: "FlexiSpot E7 Plus Adjustable", category: "Furniture", price: 649, stock: 45, status: "In Stock" },192 { id: 31, product: "Anker PowerCore 20000mAh", category: "Power", price: 49, stock: 412, status: "In Stock" },193 { id: 32, product: "RAVPower 60W USB-C Charger", category: "Power", price: 39, stock: 387, status: "In Stock" },194 { id: 33, product: "Belkin BoostCharge Pro 3-in-1", category: "Power", price: 149, stock: 156, status: "In Stock" },195 { id: 34, product: "Samsung T7 Shield 2TB SSD", category: "Storage", price: 199, stock: 234, status: "In Stock" },196 { id: 35, product: "SanDisk Extreme Pro 1TB Portable", category: "Storage", price: 159, stock: 189, status: "In Stock" },197 { id: 36, product: "WD My Passport 5TB External", category: "Storage", price: 139, stock: 276, status: "In Stock" },198 { id: 37, product: "Crucial X9 Pro 4TB Rugged", category: "Storage", price: 289, stock: 143, status: "In Stock" },199 { id: 38, product: "CalDigit TS4 Thunderbolt 4 Dock", category: "Hubs & Docks", price: 399, stock: 52, status: "In Stock" },200 { id: 39, product: "Anker 577 Thunderbolt Docking", category: "Hubs & Docks", price: 299, stock: 78, status: "In Stock" },201 { id: 40, product: "HyperDrive Gen2 16-Port USB-C", category: "Hubs & Docks", price: 249, stock: 91, status: "In Stock" },202 { id: 41, product: "Blue Yeti X Professional USB", category: "Audio", price: 169, stock: 123, status: "In Stock" },203 { id: 42, product: "Shure MV7 Podcast Microphone", category: "Audio", price: 249, stock: 87, status: "In Stock" },204 { id: 43, product: "Elgato Wave:3 Premium USB", category: "Audio", price: 159, stock: 104, status: "In Stock" },205 { id: 44, product: "Rode NT-USB Mini Studio", category: "Audio", price: 99, stock: 167, status: "In Stock" },206 { id: 45, product: "Audio-Technica AT2020USB+", category: "Audio", price: 149, stock: 145, status: "In Stock" },207];208209export const footerRendererConfig = {210 headers: footerRendererHeaders,211 rows: footerRendererData,212 tableProps: {213 enablePagination: true,214 rowsPerPage: 10,215 },216};217
Props
Footer Renderer Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
footerRenderer | Optional | Custom footer that replaces the default. Receives pagination state and navigation handlers. | |
footerPosition"top" | "bottom" | Optional | Place the footer above ("top") or below ("bottom", default) the table body. | |
footerRenderKeystring | number | Optional | Cache key for custom footers that read external state. Change it when that state changes so the footer refreshes even if pagination inputs are unchanged. |
Renderer arguments
FooterRendererProps
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
currentPagenumber | Required | The current page number (1-based index). | |
startRownumber | Required | The starting row number for the current page (1-based index). | |
endRownumber | Required | The ending row number for the current page (1-based index). | |
totalRowsnumber | Required | The total number of rows in the table. | |
totalPagesnumber | Required | The total number of pages based on rowsPerPage. | |
rowsPerPagenumber | Required | The number of rows displayed per page. | |
hasPrevPageboolean | Required | Boolean indicating if there is a previous page available. | |
hasNextPageboolean | Required | Boolean indicating if there is a next page available. | |
onPrevPage() => void | Required | Function to navigate to the previous page. | |
onNextPage() => Promise<void> | Required | Async function to navigate to the next page. | |
onPageChange(page: number) => void | Required | Function to navigate to a specific page number. | |
prevIconReactNode | Optional | Optional custom icon for the previous page button. | |
nextIconReactNode | Optional | Optional custom icon for the next page button. |