Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
565b46f
docs: spec for components SP3 — TextInput and Checkbox
blove Sep 7, 2026
288e74b
docs: components SP3 implementation plan
blove Sep 7, 2026
6c698b3
refactor(react): the site vocabulary is PretableSite, and it names th…
blove Sep 7, 2026
16697c3
docs(react): three chrome fields, not seven
blove Sep 7, 2026
60d8a7a
feat(react): PretableCheckbox
blove Sep 7, 2026
c0d4c1f
feat(react): PretableTextInput
blove Sep 7, 2026
b0bf78c
test(react): the checkbox glyph test tells check from minus; label-fo…
blove Sep 7, 2026
d3a48d0
test(react): pin the TextInput and Checkbox type contracts
blove Sep 7, 2026
4c5fabf
feat(react): the TextInput and Checkbox slots
blove Sep 7, 2026
e0de95b
feat(ui): the kit text-input and checkbox rules; the site guard learn…
blove Sep 7, 2026
4ea30c6
docs(react): the slot ref docs claim only what holds
blove Sep 7, 2026
74609cf
refactor(react,ui): the fourteen fields and checkboxes render the kit
blove Sep 7, 2026
888751a
fix(react,ui): a disabled checkbox keeps its tick; option identity is…
blove Sep 7, 2026
2a0b69f
feat(react): export PretableTextInput, PretableCheckbox and their slo…
blove Sep 8, 2026
7f0d2d5
docs: TextInput and Checkbox on the Components page; the option attri…
blove Sep 8, 2026
6c9778f
test(website): the kit fields and checkboxes in a real browser; e2e d…
blove Sep 8, 2026
50cd413
docs: the Components page description fits the 155-character cap
blove Sep 8, 2026
0df8e5d
fix(website,docs): the checkbox driver's reason, the override sweep's…
blove Sep 8, 2026
0f83fb1
chore: changeset for components SP3
blove Sep 8, 2026
eebaf98
docs(react,ui,website): the veto contract, the warning's reach, and t…
blove Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .changeset/components-sp3-inputs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
"@pretable/react": minor
"@pretable/ui": patch
---

The kit's field and its checkbox, and the fourteen controls in the grid rebuilt
on them.

`PretableTextInput` is an `<input>` and `PretableCheckbox` a
`button[role="checkbox"]`, with `PretableTextInputProps`,
`PretableCheckboxProps`, `PretableTextInputComponent` and
`PretableCheckboxComponent` alongside them and `TextInput` and `Checkbox` slots
on `PretableComponents` — `components={{ TextInput, Checkbox }}` on
`<PretableSurface>` and `<Pretable>` replaces the grid's seven chrome text
fields and all seven of its checkboxes, the same way `Button`, `IconButton` and
`Select` already do. The fields are the filter dialog's value and its two range
bounds, the tool panel's three filter-row values, and the Columns search box;
the checkboxes are row select, header select-all, the boolean cell, the column
visibility toggle, "hide grouped columns", and the two enum checklists.

**`PretableButtonSite` is now `PretableSite`, and `PretableBuiltInButtonSite` is
`PretableBuiltInSite`.** The type names where any kit control sits, not where a
button does, and a field and a checkbox have sites too. The union gains ten
names: `filter-value`, `filter-row-value`, `tool-search`, `row-select`,
`row-select-all`, `bool-cell`, `tool-column-toggle`, `hide-grouped`,
`filter-choice` and `filter-row-choice`.

**Three checkboxes are no longer `<input type="checkbox">`.** The filter
dialog's enum checklist, the tool panel's filter-builder enum checklist and the
"hide grouped columns" switch are now `button[role="checkbox"]` carrying
`aria-checked`. A test keyed on `.checked`, on Playwright's `.check()` or on an
`input[type=checkbox]` selector must read `aria-checked` and click the button
instead; `toBeChecked()` still works, because it reads the ARIA state.

**A listbox option is marked `data-pretable-option-value`, not `data-value`.**
The select's open list is the one place where an attribute a consumer could
already have written against `0.17.0` has been renamed: a selector like
`[data-pretable-option][data-value="open"]` now matches nothing, and must read
`data-pretable-option-value` instead. (This branch's own end-to-end suite broke
on exactly that selector.) The new name says which option an element **is**, as
against the select trigger's `data-pretable-value`, which is what that picker
has committed.

**Attributes.** Every kit field carries `data-pretable-text-input` and every kit
checkbox `data-pretable-checkbox`, and each site keeps the `data-pretable-*`
attribute it already had. The two enum checklists gain
`data-pretable-filter-choice` and `data-pretable-filter-row-choice`, and each
choice carries `data-pretable-option-value` too. The filter builder's checklist
_wrapper_ is now `data-pretable-filter-row-set` — the field alongside it keeps
`data-pretable-filter-row-value`, which the wrapper used to borrow.

**Look.** The three ex-native checkboxes become the kit's 16px square drawn from
the `--pretable-checkbox-*` tokens, in place of the browser's ~13px default. The
Columns search box and every kit checkbox now take the product focus ring
instead of the user agent's — visibly on the header select-all and the column
visibility toggle, and on row select and the boolean cell too, though both are
`tabIndex={-1}` and so out of a `Tab`'s reach. Nothing else moves: the seven
fields and the four checkboxes that were already buttons keep their boxes, their
element type and their spacing exactly.

**Behaviour.** A kit checkbox's keyboard is the native button's — Space and
Enter both activate. A consumer `onClick` runs before the toggle and may
`preventDefault()` to veto it, which is how a shift-click range select keeps its
click without a second write. `checked` accepts `"mixed"` for the header
select-all's partial selection; it renders the minus glyph and toggles to
`true`.
68 changes: 65 additions & 3 deletions apps/website/app/fixtures/components/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import {
PretableSurface,
type PretableButtonComponent,
type PretableCheckboxComponent,
type PretableColumn,
type PretableIconButtonComponent,
type PretableSelectComponent,
type PretableTextInputComponent,
} from "@pretable/react";
import { forwardRef } from "react";

Expand All @@ -29,17 +31,32 @@ interface Row {
id: string;
name: string;
qty: number;
status: string;
}

const ROWS: Row[] = [
{ id: "a", name: "Alpha", qty: 1 },
{ id: "b", name: "Bravo", qty: 2 },
{ id: "c", name: "Charlie", qty: 3 },
{ id: "a", name: "Alpha", qty: 1, status: "open" },
{ id: "b", name: "Bravo", qty: 2, status: "closed" },
{ id: "c", name: "Charlie", qty: 3, status: "open" },
];

const COLUMNS: PretableColumn<Row>[] = [
{ id: "name", header: "Name", widthPx: 160, type: "text" },
{ id: "qty", header: "Qty", widthPx: 100, type: "number" },
// `enum` is what makes the Name funnel's sibling a CHECKLIST: an enum
// column's first operator is `isAnyOf`, whose value shape is `set`, which
// is the only shape that renders `site="filter-choice"` checkboxes. Without
// it the fixture could not reach that site at all.
{
id: "status",
header: "Status",
widthPx: 120,
type: "enum",
options: [
{ value: "open", label: "Open" },
{ value: "closed", label: "Closed" },
],
},
];

const FixtureButton: PretableButtonComponent = forwardRef(
Expand Down Expand Up @@ -103,6 +120,48 @@ const FixtureSelect: PretableSelectComponent = forwardRef(
},
);

/**
* A field replacement that is the native input and nothing else — the point
* being that the grid's own value semantics (the debounce on the filter
* operand, the search's live narrowing) live above the slot, so a bare input
* is enough to drive them. It records its `site` and nothing else.
*/
const FixtureTextInput: PretableTextInputComponent = forwardRef(
function FixtureTextInput({ site, ...props }, ref) {
return <input {...props} ref={ref} data-fixture-field={site ?? ""} />;
},
);

/**
* A checkbox replacement that keeps the kit's CONTRACT and none of its
* markup: no `role="checkbox"`, no `aria-checked`, just a button that records
* the state it was handed and re-implements the one behaviour the kit
* promises — consumer `onClick` first, `preventDefault()` vetoes the toggle.
* A test that finds the grid still selecting rows through this proves the
* grid drives the slot's props, not the kit's internals.
*/
const FixtureCheckbox: PretableCheckboxComponent = forwardRef(
function FixtureCheckbox(
{ site, checked, onCheckedChange, onClick, ...props },
ref,
) {
return (
<button
{...props}
ref={ref}
type="button"
data-fixture-checkbox={site ?? ""}
data-fixture-checked={String(checked)}
onClick={(event) => {
onClick?.(event);
if (event.defaultPrevented) return;
onCheckedChange(checked !== true);
}}
/>
);
},
);

export default function ComponentsFixturePage() {
return (
<main style={{ padding: 24 }}>
Expand All @@ -111,10 +170,13 @@ export default function ComponentsFixturePage() {
columns={COLUMNS}
components={{
Button: FixtureButton,
Checkbox: FixtureCheckbox,
IconButton: FixtureIconButton,
Select: FixtureSelect,
TextInput: FixtureTextInput,
}}
getRowId={(row) => row.id}
rowSelectionColumn={{ enabled: true, headerCheckbox: true }}
rows={ROWS}
toolPanel={{ defaultActiveSection: "columns" }}
viewportHeight={240}
Expand Down
Loading