PHP port of Evertras/bubble-table — customizable interactive table component for terminal UIs.
- Column definitions: unique key, title, width (fixed or flexible), optional style
- Row data: key-value map (
RowData), arbitrary values rendered viafmt.Sprintf("%v") - Styled cells:
StyledCellwraps value + ANSI style, overrides row/column/base styles - Row styles: zebra striping, bold rows, per-row ANSI styling
- Selection: single-row cursor, up/down navigation
- Pagination: page size, page navigation, auto footer
- Sorting: asc/desc, multi-column sort, numeric + string sort
- Filtering: filter by column text
- Frozen columns: pin columns from the left
- Horizontal scroll: max width with overflow, frozen columns stay visible
- Missing data indicator: configurable placeholder for absent cells
- Border styling:
withBorder(Border $border)— consume anySugarCraft\Sprinkles\Borderfamily (normal/rounded/thick/double/block/ascii/hidden/markdownBorder) +withBorderStyle(string $ansiStyle)for ANSI color/styling on default border - Viewport virtualization: render only visible rows via
withViewportHeight()+withScrollY() - Column width modes:
ColumnWidthenum — Fixed, Percent, Dynamic, Content - Cell text wrapping:
WrapModeenum — None, WordWrap, Character - Multi-line row rendering:
withMultilineMode(bool $multiline)— when enabled, rows expand to the maximum height of any cell; when disabled (default), cells are clamped to one line (backward compatible)
composer require sugarcraft/sugar-tableuse SugarCraft\Table\{Column, Row, RowData, Table};
$t = Table::withColumns([
Column::new('id', 'ID', 5),
Column::new('name', 'Name', 20),
Column::new('city', 'City', 15),
])->withRows([
Row::new(RowData::from(['id' => '1', 'name' => 'Alice', 'city' => 'NYC'])),
Row::new(RowData::from(['id' => '2', 'name' => 'Bob', 'city' => 'LA'])),
Row::new(RowData::from(['id' => '3', 'name' => 'Carol', 'city' => 'CHI'])),
]);
echo $t->View();Column::new($key, $title, $width) // key, display title, fixed width
->withFlexibleWidth($flex) // flexible width share
->withMaxWidth($max) // horizontal scroll cap
->withStyle('1;34') // ANSI SGR style
->withFilterable() // enable built-in filter
->withAlignLeft() // left-align (default is right)
->withColumnWidth($mode, $value) // ColumnWidth::Fixed|Percent|Dynamic|Content
->withWrapMode(WrapMode::None) // WrapMode::None|WordWrap|CharacterColumnWidth specifies how a column's width is computed:
| Case | Description |
|---|---|
Fixed |
Fixed character count (uses Column.width) |
Percent |
Percentage of total table width (uses Column.percentValue, 0–100) |
Dynamic |
Min-width from content, max from table |
Content |
Exactly fit content, compress if needed |
use SugarCraft\Table\ColumnWidth;
// Fixed 10-character column
col = Column::new('id', 'ID', 10)
->withColumnWidth(ColumnWidth::Fixed, 0);
// 25% of table width
col = Column::new('name', 'Name', 20)
->withColumnWidth(ColumnWidth::Percent, 25.0);
// Dynamic (content or flex share, whichever is larger)
col = Column::new('city', 'City', 15)
->withColumnWidth(ColumnWidth::Dynamic, 0);
// Content-based (exact fit)
col = Column::new('email', 'Email', 30)
->withColumnWidth(ColumnWidth::Content, 0);WrapMode controls how cell text is wrapped:
| Case | Description |
|---|---|
None |
Truncate at column width |
WordWrap |
Break at word boundaries |
Character |
Break at any character, no padding on last line |
use SugarCraft\Table\{Column, WrapMode};
$col = Column::new('desc', 'Description', 20)
->withWrapMode(WrapMode::WordWrap); // or Character or NoneRow::new($rowData)
->withStyle('1') // bold entire row
->withZebra() // alternating style
// Styled cell (overrides row+column style)
StyledCell::new('value', '31;1') // red bold cell$t = $t->SortBy('name', ascending: true);
$t = $t->Filter('name', 'alice'); // filter column by text
$t = $t->SelectNext(); // move cursor down
$t = $t->SelectPrevious(); // move cursor up
$t = $t->CurrentRow(); // get selected RowData$t = $t->withPageSize(25) // 25 rows per page
->withPage(2); // show page 2
echo $t->PageFooter(); // 'Page 2 of 4' (i18n-aware)Render only a visible slice of rows for large datasets:
$t = Table::withColumns([...])
->withRows($bigDataset)
->withViewportHeight(15) // show 15 rows at a time
->withScrollY(30); // start at row 30
echo $t->View(); // renders rows 30-44
$currentScroll = $t->scrollY(); // get current scroll offsetThe table automatically slices the visible row range from the filtered+sorted view.
scrollY() returns the current vertical scroll offset.
Compute actual column widths from ColumnWidth enum values:
$widths = $t->computeColumnWidths(80);
// Returns: [5, 20, 15, 40] (colIndex => character width)
foreach ($t->Columns() as $i => $col) {
printf("%s => %d chars\n", $col->title, $widths[$i]);
}Uses multi-pass computation:
- Pass 1: Collect Fixed/Percent widths, count Dynamic/Content columns
- Pass 2: Distribute remaining space among Dynamic/Content columns
- Dynamic:
max(contentWidth, flexShare) - Content: exact content width (min 1)
All translatable strings live in
lang/en.phpunder the'table'namespace.
Available keys (lang/en.php):
| Key | Default string | Parameters |
|---|---|---|
page_of |
Page {page} of {total} |
{page}, {total} |
no_data |
No data |
— |
showing_rows |
Showing {from} to {to} of {total} rows |
{from}, {to}, {total} |
sort |
Sort |
— |
filter |
Filter |
— |
To add a locale, copy lang/en.php to lang/<code>.php and translate the
values. The lookup chain follows SugarCraft\Core\I18n\T:
exact locale → base language → en → raw key.
Adding new translatable strings:
// In any source file:
use SugarCraft\Table\Lang;
$label = Lang::t('sort'); // 'Sort'
$pager = Lang::t('page_of', ['page' => 2, 'total' => 4]); // 'Page 2 of 4'Customize the table border using the SugarCraft\Sprinkles\Border family:
use SugarCraft\Table\Table;
use SugarCraft\Sprinkles\Border;
$t = Table::withColumns([...])
->withRows([...])
->withBorder(Border::rounded()); // ─ │ ╭ ╮ ╰ ╯
// ->withBorder(Border::thick()) // ━ ┃ ┏ ┓ ┗ ┛
// ->withBorder(Border::double()) // ═ ║ ╔ ╗ ╚ ╝
// ->withBorder(Border::ascii()) // - | + + + +
// ->withBorder(Border::hidden()) // all spaces
// ->withBorder(Border::markdownBorder()) // | - | ...
// Or style the default border with ANSI colors:
$t = $t->withBorderStyle('1;32'); // bold greenAvailable border factories: Border::normal(), rounded(), thick(), double(), block(), ascii(), hidden(), markdownBorder().
Enable multi-line row rendering to display tall cell content:
$t = Table::withColumns([...])
->withRows([...])
->withMultilineMode(true); // rows expand to max cell height
// ->withMultilineMode(false); // default, clamps to single lineWhen enabled, each row's height equals the maximum number of lines across all its cells after text wrapping. renderRowLines() iterates all cell lines to build the full row. When disabled (the default), cells are clamped to one line for backward compatibility.