Skip to content
Gx
GitHub

Data Table Page

A page frame for a server-driven data table.

<datatablepage.DataTablePage title="Invoices" description="Every invoice for this workspace.">
  <:filter>
    <input.Input name="q" type="search" placeholder="Search" attrs={gx.Attrs{{Key: "aria-label", Value: "Search"}}} />
  </:filter>
  <:table>
    <table.Table>
      <table.TableHeader>
        <table.TableRow>
          <table.TableHead>Invoice</table.TableHead>
          <table.TableHead>Status</table.TableHead>
          <table.TableHead class="text-right">Amount</table.TableHead>
        </table.TableRow>
      </table.TableHeader>
      <table.TableBody>
        <table.TableRow>
          <table.TableCell class="font-medium">INV-001</table.TableCell>
          <table.TableCell>Paid</table.TableCell>
          <table.TableCell class="text-right">$120.00</table.TableCell>
        </table.TableRow>
        <table.TableRow>
          <table.TableCell class="font-medium">INV-002</table.TableCell>
          <table.TableCell>Open</table.TableCell>
          <table.TableCell class="text-right">$80.00</table.TableCell>
        </table.TableRow>
        <table.TableRow>
          <table.TableCell class="font-medium">INV-003</table.TableCell>
          <table.TableCell>Paid</table.TableCell>
          <table.TableCell class="text-right">$310.00</table.TableCell>
        </table.TableRow>
      </table.TableBody>
    </table.Table>
  </:table>
  <:pagination>
    <pagination.Pagination>
      <pagination.PaginationContent>
        <pagination.PaginationItem>
          <pagination.PaginationPrevious href={gx.URL("/invoices?page=1")} />
        </pagination.PaginationItem>
        <pagination.PaginationItem>
          <pagination.PaginationLink href={gx.URL("/invoices?page=1")} active>1</pagination.PaginationLink>
        </pagination.PaginationItem>
        <pagination.PaginationItem>
          <pagination.PaginationLink href={gx.URL("/invoices?page=2")}>2</pagination.PaginationLink>
        </pagination.PaginationItem>
        <pagination.PaginationItem>
          <pagination.PaginationNext href={gx.URL("/invoices?page=2")} />
        </pagination.PaginationItem>
      </pagination.PaginationContent>
    </pagination.Pagination>
  </:pagination>
</datatablepage.DataTablePage>

Installation

Run the command in the app module.

Terminal
gx add data-table-page

The command also installs card, input, pagination and table.

The command writes these files.

File Path in the app
DataTablePage.gx ui/data-table-page/DataTablePage.gx
DataTablePage.fixtures.go ui/data-table-page/DataTablePage.fixtures.go

Install card, input, pagination and table first.

Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.

ui/data-table-page/DataTablePage.gx
package datatablepage

import "github.com/alternayte/gx/registry/card"
import "github.com/alternayte/gx/registry/input"

props {
  // Title is the heading of the page.
  Title       string
  // Description is the text below the title. An empty value renders no description.
  Description string = ""
  // Filter is the filter control shown in the header row. A nil value renders no filter.
  Filter      gx.Node = nil
  // Table is the data table. It renders in a card.
  Table       gx.Node
  // Pagination is the paging links shown below the table. A nil value renders no paging.
  Pagination  gx.Node = nil
}

<div class="mx-auto flex w-full max-w-5xl flex-col gap-6">
  <div class="flex flex-wrap items-end justify-between gap-4">
    <div class="flex flex-col gap-1">
      <h1 class="text-xl font-semibold">{p.Title}</h1>
      if p.Description != "" {
        <p class="text-sm text-muted-foreground">{p.Description}</p>
      }
    </div>
    if p.Filter != nil {
      <div class="w-56">
        {p.Filter}
      </div>
    }
  </div>
  <card.Card>
    {p.Table}
  </card.Card>
  if p.Pagination != nil {
    <div class="flex justify-center">{p.Pagination}</div>
  }
</div>
ui/data-table-page/DataTablePage.fixtures.go
package datatablepage

import (
	"github.com/alternayte/gx"
	"github.com/alternayte/gx/registry/input"
	"github.com/alternayte/gx/registry/pagination"
	"github.com/alternayte/gx/registry/table"
)

// row renders one invoice row of the fixture table.
func row(id, status, amount string) gx.Node {
	return table.TableRow(table.TableRowProps{Children: gx.Frag(
		table.TableCell(table.TableCellProps{Class: "font-medium", Children: gx.Text(id)}),
		table.TableCell(table.TableCellProps{Children: gx.Text(status)}),
		table.TableCell(table.TableCellProps{Class: "text-right", Children: gx.Text(amount)}),
	)})
}

var DataTablePageFixtures = gx.Fixtures[DataTablePageProps]{
	"Default": {
		Title:       "Invoices",
		Description: "Every invoice for this workspace.",
		Filter:      input.Input(input.InputProps{Name: "q", Type: "search", Placeholder: "Search", Attrs: gx.Attrs{{Key: "aria-label", Value: "Search"}}}),
		Table: table.Table(table.TableProps{Children: gx.Frag(
			table.TableHeader(table.TableHeaderProps{Children: table.TableRow(table.TableRowProps{Children: gx.Frag(
				table.TableHead(table.TableHeadProps{Children: gx.Text("Invoice")}),
				table.TableHead(table.TableHeadProps{Children: gx.Text("Status")}),
				table.TableHead(table.TableHeadProps{Class: "text-right", Children: gx.Text("Amount")}),
			)})}),
			table.TableBody(table.TableBodyProps{Children: gx.Frag(
				row("INV-001", "Paid", "$120.00"),
				row("INV-002", "Open", "$80.00"),
				row("INV-003", "Paid", "$310.00"),
			)}),
		)}),
		Pagination: pagination.Pagination(pagination.PaginationProps{Children: pagination.PaginationContent(pagination.PaginationContentProps{Children: gx.Frag(
			pagination.PaginationItem(pagination.PaginationItemProps{Children: pagination.PaginationPrevious(pagination.PaginationPreviousProps{Href: gx.URL("/invoices?page=1")})}),
			pagination.PaginationItem(pagination.PaginationItemProps{Children: pagination.PaginationLink(pagination.PaginationLinkProps{Href: gx.URL("/invoices?page=1"), Active: true, Children: gx.Text("1")})}),
			pagination.PaginationItem(pagination.PaginationItemProps{Children: pagination.PaginationLink(pagination.PaginationLinkProps{Href: gx.URL("/invoices?page=2"), Children: gx.Text("2")})}),
			pagination.PaginationItem(pagination.PaginationItemProps{Children: pagination.PaginationNext(pagination.PaginationNextProps{Href: gx.URL("/invoices?page=2")})}),
		)})}),
	},
}

The theme must define these tokens: --border, --card, --card-foreground, --muted-foreground.

Usage

<datatablepage.DataTablePage title="Invoices" filter={filterInput} table={table} pagination={paging} />

The block is copied source. The table and the paging links come from the app, so the sort, filter and page state stay typed in Go.

Examples

Default

<datatablepage.DataTablePage title="Invoices" description="Every invoice for this workspace.">
  <:filter>
    <input.Input name="q" type="search" placeholder="Search" attrs={gx.Attrs{{Key: "aria-label", Value: "Search"}}} />
  </:filter>
  <:table>
    <table.Table>
      <table.TableHeader>
        <table.TableRow>
          <table.TableHead>Invoice</table.TableHead>
          <table.TableHead>Status</table.TableHead>
          <table.TableHead class="text-right">Amount</table.TableHead>
        </table.TableRow>
      </table.TableHeader>
      <table.TableBody>
        <table.TableRow>
          <table.TableCell class="font-medium">INV-001</table.TableCell>
          <table.TableCell>Paid</table.TableCell>
          <table.TableCell class="text-right">$120.00</table.TableCell>
        </table.TableRow>
        <table.TableRow>
          <table.TableCell class="font-medium">INV-002</table.TableCell>
          <table.TableCell>Open</table.TableCell>
          <table.TableCell class="text-right">$80.00</table.TableCell>
        </table.TableRow>
        <table.TableRow>
          <table.TableCell class="font-medium">INV-003</table.TableCell>
          <table.TableCell>Paid</table.TableCell>
          <table.TableCell class="text-right">$310.00</table.TableCell>
        </table.TableRow>
      </table.TableBody>
    </table.Table>
  </:table>
  <:pagination>
    <pagination.Pagination>
      <pagination.PaginationContent>
        <pagination.PaginationItem>
          <pagination.PaginationPrevious href={gx.URL("/invoices?page=1")} />
        </pagination.PaginationItem>
        <pagination.PaginationItem>
          <pagination.PaginationLink href={gx.URL("/invoices?page=1")} active>1</pagination.PaginationLink>
        </pagination.PaginationItem>
        <pagination.PaginationItem>
          <pagination.PaginationLink href={gx.URL("/invoices?page=2")}>2</pagination.PaginationLink>
        </pagination.PaginationItem>
        <pagination.PaginationItem>
          <pagination.PaginationNext href={gx.URL("/invoices?page=2")} />
        </pagination.PaginationItem>
      </pagination.PaginationContent>
    </pagination.Pagination>
  </:pagination>
</datatablepage.DataTablePage>

API reference

A tag sets a prop by its name with a lower-case first letter: Class is class.

datatablepage.DataTablePage

Prop Type Default Description
Title string Required Title is the heading of the page.
Description string "" Description is the text below the title. An empty value renders no description.
Filter gx.Node nil Filter is the filter control shown in the header row. A nil value renders no filter.
Table gx.Node Required Table is the data table. It renders in a card.
Pagination gx.Node nil Pagination is the paging links shown below the table. A nil value renders no paging.

Do and do not

Do

  • Put the filter in the header row.
  • Keep the table in one card.

Don't

  • Do not page on the client.
  • Do not render more rows than the page size.

Keyboard

Key Action
Tab Moves to the filter, then the table links, then the paging links.
Enter Follows the focused sort or page link.