Fifteen pre-built TUI components
TextInput, TextArea, ItemList, Table, Tree, Viewport, FilePicker, Progress, Spinner, Cursor, Help, Key, Paginator, Stopwatch, Timer. Everything you'd hand-roll on day one of a TUI project, ready to drop into your Model.
composer require sugarcraft/sugar-bits
use SugarCraft\Bits\TextInput\TextInput;
use SugarCraft\Bits\Spinner\Spinner;
use SugarCraft\Core\{Cmd, Model, Msg, Program};
use SugarCraft\Core\Msg\KeyMsg;
use SugarCraft\Core\KeyType;
final class Search implements Model {
public function __construct(
public readonly TextInput $ti,
public readonly Spinner $sp,
) {}
public function init(): ?\Closure { return $this->sp->tick(); }
public function update(Msg $msg): array {
if ($msg instanceof KeyMsg && $msg->type === KeyType::Enter) {
return [$this, Cmd::quit()];
}
[$ti, $cmd] = $this->ti->update($msg);
[$sp, $cmd2] = $this->sp->update($msg);
return [new self($ti, $sp), Cmd::batch($cmd, $cmd2)];
}
public function view(): string {
return $this->sp->view().' '.$this->ti->view();
}
}
Progress + spring-physics AnimatedProgress driven by HoneyBounce.ValidateOn timing control, keystroke restrict pattern filter, line numbers, soft-wrap, dynamic-height growth (mirrors upstream bubbles #910).styleFunc(row, col) => Style (mirrors bubbles #246), multi-column sort with withSort() / thenSortBy() / clearSort(), filtering with withFilterable() / withFilter() / withFilterPredicate() (default: case-insensitive substring match on all visible columns), and pagination with withPageSize() / getPaginator() / withPage() / nextPage() / prevPage() / pageFirst() / pageLast() โ integrates with sort and filter.g / G jump (mirrors bubbles #233).| Class | Method | Description |
|---|---|---|
| TextInput | new() | Create a new text input instance |
| TextInput | update(Msg) | Handle input events |
| TextInput | withValidateOn(ValidateOn) | Set validation timing โ None / Blur / Change / Submit |
| TextInput | withRestrict(string $pattern) | PCRE regex filter โ only matching keystrokes are accepted |
| ValidateOn | enum | Cases: None / Blur / Change / Submit โ controls when validation fires |
| TextArea | new() | Create a multi-line text area |
| Spinner | new(Style) | Create spinner with given style |
| Spinner | tick() | Return tick command for animation |
| Table | new(headers, rows) | Create data table |
| Table | withSort(string $column, SortDirection $dir = Asc) | Set primary sort column and direction โ clears prior sort chain |
| Table | thenSortBy(string $column, SortDirection $dir = Asc) | Add secondary (or further) sort tiebreaker |
| Table | clearSort() | Remove all sort criteria, restoring insertion order |
| Table | getSortState(): SortState | Return current sort criteria (readonly) |
| Table | withFilterable(bool) | Enable or disable the filter feature |
| Table | withFilter(string $query) | Set filter query string โ default: case-insensitive substring match on all visible columns |
| Table | withFilterPredicate(?Closure(list<string>): bool) | Custom filter callable โ null restores the default |
| Table | getFilterable(): bool | Return whether filtering is enabled |
| Table | getFilter(): string | Return the current filter query string |
| Table | getFilterPredicate(): ?Closure | Return the current custom filter predicate |
| SortDirection | enum | Cases: Asc / Desc โ use toggle() to flip direction |
| SortState | DTO | Immutable list of (column index, SortDirection) pairs |
| Table | withPageSize(int $size) | Set rows per page โ 0 disables pagination, โฅ1 enables it |
| Table | withPage(int $page) | Navigate to a zero-based page (clamps to valid range) |
| Table | nextPage() | Advance one page โ returns new Table |
| Table | prevPage() | Retreat one page โ returns new Table |
| Table | pageFirst() | Jump to the first page โ returns new Table |
| Table | pageLast() | Jump to the last page โ returns new Table |
| Table | getPageSize(): int | Return rows per page (0 = pagination disabled) |
| Table | getCurrentPage(): int | Return the current zero-based page |
| Table | getTotalPages(): int | Return total page count (1 when pagination is disabled) |
| Table | getPaginator(): Paginator | Return a Paginator wired to the table's current page state |
| Tree | new(root) | Create tree view |
| Viewport | new(content) | Create scrollable viewport |
| Progress | new(ratio) | Create progress bar |
| FilePicker | new(directory) | Create file picker |
| ItemList | new(items) | Create selectable item list |
VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.