toga-ai 1.0.165 → 1.0.166
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/2.0/apps/toga-blox/INDEX.md +11 -0
- package/knowledge/2.0/apps/toga-blox/architecture.md +123 -0
- package/knowledge/2.0/apps/toga-blox/features/advanced-select.md +73 -0
- package/knowledge/2.0/apps/toga-blox/features/api-client.md +100 -0
- package/knowledge/2.0/apps/toga-blox/features/base-input.md +70 -0
- package/knowledge/2.0/apps/toga-blox/features/primary-table-templates.md +86 -0
- package/knowledge/2.0/apps/toga-blox/features/table-record-modal.md +69 -0
- package/knowledge/2.0/apps/toga-blox/features/table.md +141 -0
- package/knowledge/INDEX.md +1 -0
- package/knowledge/registry.json +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# toga-blox (TOGa Blox) — 2.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [TOGa Blox (toga-blox) Architecture](architecture.md) | `toga-blox` (npm package **`@agilant/toga-blox`**) is the **shared React + TypeScript component / template / utility library** reused across the 2.0 frontends — | toga-blox/package.json, toga-blox/src/components/index.ts, toga-blox/src/api/index.ts, toga-blox/src/templates/index.ts |
|
|
6
|
+
| [AdvancedSelect (virtualized single/multi select)](features/advanced-select.md) | `AdvancedSelect<T>` is a **fully controlled, virtualized** single/multi select built from scratch (not react-select) on **`@tanstack/react-virtual`**, for large | toga-blox/src/components/AdvancedSelect/AdvancedSelect.tsx, toga-blox/src/components/AdvancedSelect/AdvancedSelect.types.ts, toga-blox/src/components/AdvancedSelect/AdvancedSelect.module.css |
|
|
7
|
+
| [API client (axios wrapper, auth, table-data fetchers)](features/api-client.md) | `src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages auth (Bearer + refresh), serializes complex query options, and provid | toga-blox/src/api/index.ts, toga-blox/src/api/axiosInstance.ts, toga-blox/src/api/apiFunctions.ts, toga-blox/src/api/auth.ts, toga-blox/src/api/genericApi.ts, toga-blox/src/api/types.ts, toga-blox/src/api/tableData |
|
|
8
|
+
| [BaseInput (react-hook-form field factory)](features/base-input.md) | `BaseInput` is a **form-field factory** driven by react-hook-form. | toga-blox/src/components/BaseInput/BaseInput.tsx, toga-blox/src/components/BaseInput/BaseInput.types.ts, toga-blox/src/components/BaseInput/BaseInput.module.css, toga-blox/src/components/BaseInput/components |
|
|
9
|
+
| [Primary Table templates (server/client, sizing, virtualization)](features/primary-table-templates.md) | `src/templates/PrimaryTable/` is the **production, wired-up table** built on the [Table component](table.md). | toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx, toga-blox/src/templates/PrimaryTable/types.ts |
|
|
10
|
+
| [TableRecordModal (record-detail modal shell)](features/table-record-modal.md) | `TableRecordModal` is a **generic, presentational modal shell** for showing a single table record (row) in detail — typically opened from a table row click. | toga-blox/src/components/TableRecordModal/TableRecordModal.tsx, toga-blox/src/components/TableRecordModal/index.ts, toga-blox/src/components/TableRecordModal/tableRecordModal.module.css |
|
|
11
|
+
| [Table component (cells, action cells, filters & sorts, hooks, theming)](features/table.md) | The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block behind the [Primary Table templates](primary-table-templates.md). | toga-blox/src/components/Table/index.ts, toga-blox/src/components/Table/types.ts, toga-blox/src/components/Table/utils/buildTanstackColumns.tsx, toga-blox/src/components/Table/utils/resolveCellType.tsx, toga-blox/src/components/Table/components/cellTypes, toga-blox/src/components/Table/components/actionCells, toga-blox/src/components/Table/components/columnFiltersAndSorts, toga-blox/src/components/Table/hooks, toga-blox/src/components/Table/themeConfig |
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TOGa Blox (toga-blox) Architecture
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: architecture
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/package.json
|
|
13
|
+
- toga-blox/src/components/index.ts
|
|
14
|
+
- toga-blox/src/api/index.ts
|
|
15
|
+
- toga-blox/src/templates/index.ts
|
|
16
|
+
related:
|
|
17
|
+
- features/table.md
|
|
18
|
+
- features/primary-table-templates.md
|
|
19
|
+
- features/table-record-modal.md
|
|
20
|
+
- features/base-input.md
|
|
21
|
+
- features/advanced-select.md
|
|
22
|
+
- features/api-client.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
|
|
27
|
+
`toga-blox` (npm package **`@agilant/toga-blox`**) is the **shared React + TypeScript
|
|
28
|
+
component / template / utility library** reused across the 2.0 frontends — `toga2-view`,
|
|
29
|
+
`toga2-supply`, `toga25-supply`, `toga2-commerce`, and `toga2-hub`. It is **not a PHP
|
|
30
|
+
framework repo**: it has no `_underscore` backend code. It is filed under `2.0/` because
|
|
31
|
+
every consumer is a 2.0 app, but structurally it is a standalone front-end package
|
|
32
|
+
(`dependsOn: []`).
|
|
33
|
+
|
|
34
|
+
It ships: presentational + data-aware **components** (`src/components/`), opinionated
|
|
35
|
+
**templates** that wire components into ready-to-use screens (`src/templates/`, e.g.
|
|
36
|
+
`PrimaryTable`, `Login`, `AddNotes`), a thin **API client** over axios + React Query
|
|
37
|
+
(`src/api/`, `src/reactQuery/`), and **utilities** (`src/utils/`). It builds with `tsc`
|
|
38
|
+
to `dist/` (not bundled), develops via Storybook, and tests with Vitest.
|
|
39
|
+
|
|
40
|
+
**Critical rules:**
|
|
41
|
+
- **The API client is inert until initialized.** A consumer **must** call
|
|
42
|
+
`createAxiosInstance({ baseURL, onLogout })` then `setAxiosInstance(instance)` once at
|
|
43
|
+
startup, or every `apiGet/Post/Put/Delete` throws "no axios instance registered". See
|
|
44
|
+
[api-client](features/api-client.md).
|
|
45
|
+
- **`BaseInput` requires a react-hook-form `<FormProvider>` ancestor** — it calls
|
|
46
|
+
`useFormContext()` and throws outside one. See [base-input](features/base-input.md).
|
|
47
|
+
- This is a **published library** — a breaking change to any exported prop/type ripples to
|
|
48
|
+
all five consuming apps at their next `@agilant/toga-blox` bump. Treat exported component
|
|
49
|
+
props and `src/api` signatures as a public contract; bump and publish via the beta flow.
|
|
50
|
+
- Styling is **CSS Modules + CSS custom properties** (`var(--toga-*)`, `var(--baseInput-*)`,
|
|
51
|
+
etc.) injected by `themeConfig/ThemeProvider`. Skins/themes are selected by prop, colors by
|
|
52
|
+
CSS variables on `document.documentElement` — never hardcode palette values in components.
|
|
53
|
+
|
|
54
|
+
## Package & build
|
|
55
|
+
|
|
56
|
+
| Aspect | Detail |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Name / type | `@agilant/toga-blox`, published to npm (beta tag for pre-release) |
|
|
59
|
+
| Build | `npm run build` → `tsc` emit to `dist/` + copy `*.scss/*.css/*.module.css` + assets, then `scripts/fix-esm-imports.mjs` and `scripts/fix-asset-imports.mjs` post-process import paths |
|
|
60
|
+
| Dev | `npm run dev` (nodemon rebuild) and `npm run blox` (Storybook on :6006) |
|
|
61
|
+
| Test | `npm run test` (Vitest) |
|
|
62
|
+
| CSS | `npm run build-css` (Tailwind → `dist/main.css`); components also use CSS Modules |
|
|
63
|
+
|
|
64
|
+
Because the build is `tsc`-emit (not a bundler), **post-build scripts rewrite import
|
|
65
|
+
specifiers** so the emitted ESM resolves correctly and asset imports point at `dist/assets`.
|
|
66
|
+
A consumer installs the package and imports from `@agilant/toga-blox`.
|
|
67
|
+
|
|
68
|
+
## Layout
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
src/
|
|
72
|
+
├── components/ # the component catalog (Table, BaseInput, AdvancedSelect, TableRecordModal, …)
|
|
73
|
+
│ └── index.ts # public barrel — what consumers import
|
|
74
|
+
├── templates/ # opinionated, wired-up screens
|
|
75
|
+
│ ├── PrimaryTable/ # the production table template (server/client variants)
|
|
76
|
+
│ ├── Login/
|
|
77
|
+
│ └── AddNotes/
|
|
78
|
+
├── api/ # axios client, auth, generic CRUD, table-data fetchers
|
|
79
|
+
├── reactQuery/ # useApiQuery / useApiMutation wrappers + query helpers
|
|
80
|
+
└── utils/ # formatters (currency, phone), endpoint resolution, etc.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Components vs. templates.** A *component* (e.g. `BaseInput`, `AdvancedSelect`, the Table
|
|
84
|
+
primitives) is a reusable building block. A *template* (e.g. `PrimaryTable`) composes
|
|
85
|
+
components + hooks + the API client into a drop-in feature. The Table story is split across
|
|
86
|
+
both: low-level pieces and cell/filter components live in `src/components/Table/`, while the
|
|
87
|
+
fully-wired table lives in `src/templates/PrimaryTable/`. See
|
|
88
|
+
[primary-table-templates](features/primary-table-templates.md).
|
|
89
|
+
|
|
90
|
+
## Data layer
|
|
91
|
+
|
|
92
|
+
`src/api/` is a thin wrapper over **axios** that standardizes the **2.0 API envelope**
|
|
93
|
+
(`{ isSuccess, status, error, messages, data, meta, transactionId, ... }`), auto-injects a
|
|
94
|
+
Bearer token + a per-request `transactionId`, and retries once on 401 via a refresh-token
|
|
95
|
+
flow before logging out. `src/reactQuery/` wraps those helpers as `useApiQuery` /
|
|
96
|
+
`useApiMutation`. Table data fetching (`src/api/tableData/`) serializes table view metadata
|
|
97
|
+
(fields, joins, sorts, filters, pagination) into the API's query-string option format. Full
|
|
98
|
+
detail in [api-client](features/api-client.md).
|
|
99
|
+
|
|
100
|
+
## Theming
|
|
101
|
+
|
|
102
|
+
`src/components/Table/themeConfig/ThemeProvider.tsx` wraps the app and injects a client
|
|
103
|
+
`ClientTheme` (`{ id, name, vars }`) as CSS custom properties on `document.documentElement`.
|
|
104
|
+
Components reference those variables in CVA variants and CSS Modules. Table supports named
|
|
105
|
+
**skins** (`supply`, `desk`, `supply-nested`, `supply-modal-table`) selected by the `skin`
|
|
106
|
+
prop. Form inputs theme via `--baseInput-*` variables.
|
|
107
|
+
|
|
108
|
+
## Consumers
|
|
109
|
+
|
|
110
|
+
`toga2-view`, `toga2-supply`, `toga25-supply`, `toga2-commerce`, `toga2-hub`. These apps
|
|
111
|
+
talk to the 2.0 API (`api2` over `_underscore`); `toga-blox` is the shared presentation +
|
|
112
|
+
client layer they all build on. (These consumers are not registered as `dependsOn` here —
|
|
113
|
+
the dependency runs the other way, app → library; record it on the consuming app's doc when
|
|
114
|
+
relevant.)
|
|
115
|
+
|
|
116
|
+
## Related docs
|
|
117
|
+
|
|
118
|
+
- [Table component](features/table.md) — cell types, action cells, filters/sorts, hooks, utils, theming.
|
|
119
|
+
- [Primary Table templates](features/primary-table-templates.md) — the wired server/client table + measure-and-freeze sizing.
|
|
120
|
+
- [TableRecordModal](features/table-record-modal.md) — the record-detail modal shell.
|
|
121
|
+
- [BaseInput](features/base-input.md) — the react-hook-form field factory.
|
|
122
|
+
- [AdvancedSelect](features/advanced-select.md) — virtualized single/multi select.
|
|
123
|
+
- [API client](features/api-client.md) — axios wrapper, auth, table-data fetchers.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AdvancedSelect (virtualized single/multi select)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/components/AdvancedSelect/AdvancedSelect.tsx
|
|
13
|
+
- toga-blox/src/components/AdvancedSelect/AdvancedSelect.types.ts
|
|
14
|
+
- toga-blox/src/components/AdvancedSelect/AdvancedSelect.module.css
|
|
15
|
+
related:
|
|
16
|
+
- ../architecture.md
|
|
17
|
+
- base-input.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
`AdvancedSelect<T>` is a **fully controlled, virtualized** single/multi select built from
|
|
23
|
+
scratch (not react-select) on **`@tanstack/react-virtual`**, for large option sets. Features:
|
|
24
|
+
client- or server-side search, infinite pagination, keyboard navigation, chips (multi),
|
|
25
|
+
initials avatars, secondary labels, and error/dirty display. It is the select used by
|
|
26
|
+
[`BaseInput`](base-input.md) for `advancedSelect` / `advancedMultiSelect`.
|
|
27
|
+
|
|
28
|
+
## API (discriminated union by `mode`)
|
|
29
|
+
|
|
30
|
+
`AdvancedSelectProps<T> = AdvancedSelectSingleProps<T> | AdvancedSelectMultiProps<T>`.
|
|
31
|
+
|
|
32
|
+
Common (`AdvancedSelectBaseProps<T>`): `data: T[]`, `getOptionValue/getOptionLabel`
|
|
33
|
+
(required), `getOptionSecondaryLabel?`, `showAvatar?` + `getOptionAvatarText?`,
|
|
34
|
+
`findSpecificValue?` (async server search), `searchDebounceMs?` (350), `searchMinChars?` (3),
|
|
35
|
+
`hasMorePages?` + `isFetchingMore?` + `fetchMoreData?` (infinite scroll), `rowHeight?` (36),
|
|
36
|
+
`maxVisibleRows?` (8), `overscan?` (6), `errorMessage?`, `isFieldDirty?`, `isFieldError?`,
|
|
37
|
+
`disabled?`, `valueKey?`, `onOpen/onClose/onClearInput?`.
|
|
38
|
+
|
|
39
|
+
- **single**: `value: T | null | undefined`, `onSelect: (item: T | null, valueKey?) => void`.
|
|
40
|
+
- **multi**: `value: T[]`, `onSelect: (items: T[], valueKey?) => void`.
|
|
41
|
+
|
|
42
|
+
`AdvancedSelectOption<T> = { value, label, secondaryLabel?, item }`.
|
|
43
|
+
|
|
44
|
+
## Behavior
|
|
45
|
+
|
|
46
|
+
- **Data source**: `searchResults ?? data`. Mapped to internal options; selected values held
|
|
47
|
+
in a `Set` for O(1) lookup. **Selected items always float to the top** (stable order).
|
|
48
|
+
- **Client search** (no `findSpecificValue`): filters `data` by `label.includes(query)`.
|
|
49
|
+
**Server search**: when `input.length >= searchMinChars`, debounced call, previous request
|
|
50
|
+
**aborted via `AbortController`**; pagination is disabled while search results are active.
|
|
51
|
+
- **Infinite scroll**: when the virtualizer reaches the last row and `hasMorePages`, calls
|
|
52
|
+
`fetchMoreData()` (with a shimmer loader row).
|
|
53
|
+
- **Keyboard**: ArrowUp/Down move `activeIndex`, Enter selects, Escape closes, Backspace on an
|
|
54
|
+
empty multi input removes the last chip. Single-select closes on pick and clears the search
|
|
55
|
+
text; multi keeps the menu open and re-focuses the input.
|
|
56
|
+
- **Controlled**: selection state lives in the parent via `value`/`onSelect`; only UI state
|
|
57
|
+
(open, input text, activeIndex, searchResults) is local.
|
|
58
|
+
- CSS Modules + CSS variables (shares `--baseInput*` dirty/error styling); shimmer keyframe for
|
|
59
|
+
the loading row.
|
|
60
|
+
|
|
61
|
+
## Gotchas
|
|
62
|
+
|
|
63
|
+
- **`renderOption` prop is declared but unused** — option rendering is hardcoded (primary +
|
|
64
|
+
secondary label, check/checkbox, avatar). Treat custom rendering as not yet supported.
|
|
65
|
+
- Single-select with a `secondaryLabel` and menu closed shows a styled two-line value; the
|
|
66
|
+
native input collapses to width 0 (`.inputCollapsed`).
|
|
67
|
+
- Selected-float-to-top is **not** configurable.
|
|
68
|
+
- `mode` is checked at runtime (`mode === "multi"`) — mismatched value/onSelect types can slip
|
|
69
|
+
past TypeScript's union if cast.
|
|
70
|
+
- No built-in required validation or full ARIA listbox/option roles (aria-labels on buttons only).
|
|
71
|
+
|
|
72
|
+
## Change history
|
|
73
|
+
- 2026-06-23 — Documented AdvancedSelect: virtualization, client/server search with abort, infinite scroll, keyboard nav, single/multi modes, and the unused `renderOption` caveat (apeterson).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API client (axios wrapper, auth, table-data fetchers)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/api/index.ts
|
|
13
|
+
- toga-blox/src/api/axiosInstance.ts
|
|
14
|
+
- toga-blox/src/api/apiFunctions.ts
|
|
15
|
+
- toga-blox/src/api/auth.ts
|
|
16
|
+
- toga-blox/src/api/genericApi.ts
|
|
17
|
+
- toga-blox/src/api/types.ts
|
|
18
|
+
- toga-blox/src/api/tableData
|
|
19
|
+
related:
|
|
20
|
+
- ../architecture.md
|
|
21
|
+
- table.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
`src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages
|
|
27
|
+
auth (Bearer + refresh), serializes complex query options, and provides CRUD + table-data
|
|
28
|
+
fetchers. `src/reactQuery/` wraps it as `useApiQuery` / `useApiMutation`.
|
|
29
|
+
|
|
30
|
+
**The client is inert until initialized**: a consumer must call
|
|
31
|
+
`createAxiosInstance({ baseURL, onLogout? })` then `setAxiosInstance(instance)` once at
|
|
32
|
+
startup. Otherwise `apiGet/Post/Put/Delete` throw "no axios instance registered".
|
|
33
|
+
|
|
34
|
+
## Core helpers (`apiFunctions.ts`)
|
|
35
|
+
|
|
36
|
+
`apiGet<T>(route, options?, params?)`, `apiPost<T>(route, data, options?, params?)`,
|
|
37
|
+
`apiPut<T>(...)`, `apiDelete<T>(...)`. `options` is serialized to a query string via
|
|
38
|
+
`assembleOptions()` (reactQuery/queryHelpers): `fields` → comma list (nested → dot notation),
|
|
39
|
+
`where` → `(field:op:value,AND/OR,…)` (ops: like/contains/starts/ends/in/between/ge/le/gt/lt/
|
|
40
|
+
eq/ne/excludes), `join`/`ojoin` → `Table:a=b;…`, `sort` → `+field,-field`. `apiDelete` allows
|
|
41
|
+
status < 500.
|
|
42
|
+
|
|
43
|
+
## Axios instance (`axiosInstance.ts`)
|
|
44
|
+
|
|
45
|
+
`createAxiosInstance({ baseURL, onLogout })`: `Content-Type: application/json`, `timeout
|
|
46
|
+
180000` (3 min). Interceptors: (1) inject a per-request `transactionId` UUID; (2) inject
|
|
47
|
+
`Authorization: Bearer <accessToken>` from localStorage, falling back to `fetchPublicToken()`;
|
|
48
|
+
(3) on **401**, call `refreshAccessToken()` and retry once (`_retry` guard) — on failure call
|
|
49
|
+
`onLogout` (default `performLogout()`, which clears tokens and redirects to `/`).
|
|
50
|
+
|
|
51
|
+
## Auth (`auth.ts`)
|
|
52
|
+
|
|
53
|
+
`performLogout`, `fetchPublicToken(baseURL)` (`POST /auth/public`), `refreshAccessToken(baseURL)`
|
|
54
|
+
(`POST /auth/refresh` with refresh-token bearer), `validateEmailDomain(email)`,
|
|
55
|
+
`submitLoginPassword(email, password, fields)` (`POST /auth/login`),
|
|
56
|
+
`handleClientAuthentication(uuid)` (encrypted-uuid SAML/SSO). Tokens stored in localStorage
|
|
57
|
+
(`accessToken`, `refreshToken`, `user`). `AuthResponse = { data: { tokens{access,refresh},
|
|
58
|
+
user{firstName,lastName,uuid} }, messages? }`.
|
|
59
|
+
|
|
60
|
+
## Generic CRUD (`genericApi.ts`)
|
|
61
|
+
|
|
62
|
+
`getData(slug, fields?)` (auto-paginates at recordsPerPage 1000, returns flattened array;
|
|
63
|
+
slug → camelCase response key), `saveData(slug, payload, returnPayloadDepth?)`,
|
|
64
|
+
`updateData(slug, uuid, payload, returnPayloadDepth?)`, `deleteData(slug, uuid)`,
|
|
65
|
+
`deleteAddress(uuid)`, `fetchCoreParameters()`. `returnPayloadDepth` appends `?depth=-1` for
|
|
66
|
+
nested data.
|
|
67
|
+
|
|
68
|
+
## Table data (`tableData/`)
|
|
69
|
+
|
|
70
|
+
`getDataTableData(tableViewMeta, additionalData?, extraFields?, searchParams?, options?)` —
|
|
71
|
+
builds the request from `DataTableMetaProp`: fields (incl. hyperlink/imageUrl), pagination
|
|
72
|
+
(URL `${slug}_page` / `${slug}_recordsPerPage`), sorting (`${slug}_sort[i]=±field`), filtering
|
|
73
|
+
(`${slug}_${field}_<op>` → `where.and[]`), joins (`table@alias` syntax for duplicate tables),
|
|
74
|
+
plus `additionalData`, `_status`, and `apiWhereClause` — all AND-joined.
|
|
75
|
+
`getDataTableMeta(slug)` (`GET /table-views/meta`), `getPageMeta(page, app)`,
|
|
76
|
+
`getColumnFieldFilterOptions(recordRoute, options?, params?)`.
|
|
77
|
+
|
|
78
|
+
## Envelope & types (`types.ts`)
|
|
79
|
+
|
|
80
|
+
`ApiResponse<T> = { transactionId, timestamp, authority, audience{client,user,app}, isSuccess,
|
|
81
|
+
status, error, messages[], meta{depth}, data: T }`. Other types: `Options`, `OptionsType`,
|
|
82
|
+
`TableViewField` (slug vs `tableViewField` DB path, `type`, `isVisible/Editable/Sortable/
|
|
83
|
+
Filterable`, `hyperlinkField`, `imageUrlField`, `sticky`, `precision`, …), `DataTableMetaProp`,
|
|
84
|
+
`DataTableMetaFormatted`, `SamlData`. Base URL resolution via
|
|
85
|
+
`utils/getEndpointFromEnvironment.ts` (env `VITE_API` / `VITE_API_<HOST>`; pattern
|
|
86
|
+
`api.<env>.togahub.com`).
|
|
87
|
+
|
|
88
|
+
## Gotchas
|
|
89
|
+
|
|
90
|
+
- **Must `setAxiosInstance` before use** — otherwise every call throws.
|
|
91
|
+
- Always check `response.isSuccess` before reading `data`; `data` keys are **camelCased slugs**
|
|
92
|
+
(`/sales-orders` → `data.salesOrders`).
|
|
93
|
+
- `TableViewField.slug` (URL-safe) ≠ `tableViewField` (DB column path); `getDataTableData` maps
|
|
94
|
+
slug → DB field.
|
|
95
|
+
- Only **401** auto-retries; other network errors need React Query `retry`.
|
|
96
|
+
- 401 refresh needs a `refreshToken` in localStorage, else it logs out (hard redirect to `/`).
|
|
97
|
+
- `transactionId` is injected automatically (request tracing); `depth=-1` returns hydrated nesting.
|
|
98
|
+
|
|
99
|
+
## Change history
|
|
100
|
+
- 2026-06-23 — Documented the API client: axios init contract, auth/refresh interceptors, query-option serialization, generic CRUD, table-data fetchers, and the 2.0 response envelope (apeterson).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: BaseInput (react-hook-form field factory)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/components/BaseInput/BaseInput.tsx
|
|
13
|
+
- toga-blox/src/components/BaseInput/BaseInput.types.ts
|
|
14
|
+
- toga-blox/src/components/BaseInput/BaseInput.module.css
|
|
15
|
+
- toga-blox/src/components/BaseInput/components
|
|
16
|
+
related:
|
|
17
|
+
- ../architecture.md
|
|
18
|
+
- advanced-select.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
|
|
23
|
+
`BaseInput` is a **form-field factory** driven by react-hook-form. Given a `Field` config
|
|
24
|
+
object it renders the right input sub-component by `inputType` and wires validation, error
|
|
25
|
+
display, and dirty-state styling through `useFormContext()`. **It must be rendered inside a
|
|
26
|
+
react-hook-form `<FormProvider>`** or it throws.
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
Exported default: `BaseInput: React.FC<BaseInputTypes>`. Two config surfaces:
|
|
31
|
+
- **`field?: Field`** (BaseInput.types.ts) — the per-field config: `uuid`, `valueKey`,
|
|
32
|
+
`inputType`, `inputLabel`, `placeholder`, `isRequired`, `showRequiredIndicator`,
|
|
33
|
+
`characterLimit`, select data (`data`, `getOptionValue/Label/SecondaryLabel`,
|
|
34
|
+
`fetchMoreData`, `findSpecificValue`, `showAvatar`), etc.
|
|
35
|
+
- **`BaseInputTypes`** top-level props — many duplicate `Field` keys (act as
|
|
36
|
+
fallbacks/overrides) plus styling-class props and toggle-specific props
|
|
37
|
+
(`toggleTextPosition`, `toggleActiveLabel`, etc.). `valueKey` is the one required prop.
|
|
38
|
+
|
|
39
|
+
## Input types (switch on `inputType`)
|
|
40
|
+
|
|
41
|
+
`text` → `BaseTextInput` (icon, `hasTypeCheck` number/currency regex, phone/currency
|
|
42
|
+
formatters), `textArea` → `BaseTextareaInput` (char counter, max-height 5lh), `toggle` →
|
|
43
|
+
`BaseToggle` (wraps `ToggleButton`), `checkbox` → `BaseCheckbox` (SVG check), `radio` →
|
|
44
|
+
`BaseRadioInput` (SVG circles; options are `{value,label}[]`), `disabledSelect` →
|
|
45
|
+
`DisabledSelect` (read-only + lock icon), `advancedSelect` / `advancedMultiSelect` →
|
|
46
|
+
[`AdvancedSelect`](advanced-select.md) (single/multi). Legacy `BaseSelect`/`BaseMultiSelect`
|
|
47
|
+
(react-select) exist but are **not** used by the factory.
|
|
48
|
+
|
|
49
|
+
## Validation / state
|
|
50
|
+
|
|
51
|
+
- Validation via react-hook-form `Controller` rules: required → "Required field."; text
|
|
52
|
+
`hasTypeCheck` patterns: number `/^[0-9]*\.?[0-9]*$/`, currency
|
|
53
|
+
`/^(\d+|\d{1,3}(,\d{3})+)(\.\d{1,2})?$/`.
|
|
54
|
+
- Errors/dirty read from `formState.errors` / `dirtyFields`; UUID-scoped when
|
|
55
|
+
`requireUuidField`, else keyed by `valueKey`. `getNestedValue` walks dotted paths.
|
|
56
|
+
- `BaseErrorMessage` renders the message; dirty fields get `--baseInputDirty-*` border, errors
|
|
57
|
+
`--baseInputError-*`. CSS Modules + CSS variables (`--baseInput-*`); shared sub-components in
|
|
58
|
+
`components/` (`BaseErrorMessage`, `CharacterLimitMessage`, `Option`, `SingleValue`,
|
|
59
|
+
`DropDownIndicator`, `MultiValueRemove`).
|
|
60
|
+
|
|
61
|
+
## Gotchas
|
|
62
|
+
|
|
63
|
+
- **Requires `<FormProvider>`** (uses `useFormContext()`).
|
|
64
|
+
- `Field` keys and top-level props overlap; `Field` generally wins — don't set both expecting
|
|
65
|
+
the prop to override.
|
|
66
|
+
- Currency formats **on blur**; phone formats **on keystroke**.
|
|
67
|
+
- Missing `aria-required` / `aria-invalid` / `aria-describedby` — labels + visual asterisk only.
|
|
68
|
+
|
|
69
|
+
## Change history
|
|
70
|
+
- 2026-06-23 — Documented BaseInput as a react-hook-form field factory: input-type switch, validation/dirty handling, and the FormProvider requirement (apeterson).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Primary Table templates (server/client, sizing, virtualization)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx
|
|
13
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx
|
|
14
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx
|
|
15
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx
|
|
16
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx
|
|
17
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx
|
|
18
|
+
- toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx
|
|
19
|
+
- toga-blox/src/templates/PrimaryTable/types.ts
|
|
20
|
+
related:
|
|
21
|
+
- ../architecture.md
|
|
22
|
+
- table.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
|
|
27
|
+
`src/templates/PrimaryTable/` is the **production, wired-up table** built on the
|
|
28
|
+
[Table component](table.md). Three entry points:
|
|
29
|
+
|
|
30
|
+
- **`PrimaryTable`** — pure render component. Takes an already-built TanStack `table`
|
|
31
|
+
instance + `tableViewFieldMap` and renders header, body (virtualized), and pagination.
|
|
32
|
+
- **`PrimaryTableServerTemplate`** — wraps `useTableSetup()`; **consumer owns** sorting,
|
|
33
|
+
filtering, and pagination state (passes `handleSortingChange`, `handleColumnFiltersChange`,
|
|
34
|
+
etc.). Use for server-side data.
|
|
35
|
+
- **`PrimaryTableClientTemplate`** — wraps `useTableSetup()`; **owns state internally**
|
|
36
|
+
(sorting/filtering/pagination), `initialPageSize` default 15. Use for in-memory data.
|
|
37
|
+
|
|
38
|
+
> Note: although referred to as "primary table templates **within Table**," the files live
|
|
39
|
+
> at `src/templates/PrimaryTable/`, not under `src/components/Table/`.
|
|
40
|
+
|
|
41
|
+
## Key props (`PrimaryTableProps`, types.ts)
|
|
42
|
+
|
|
43
|
+
`table`, `tableViewFieldMap`, `skin` ("supply" | "desk" | "supply-nested" |
|
|
44
|
+
"supply-modal-table"), `onRowClick`, `paginationMeta` + `handlePageChange` +
|
|
45
|
+
`handleRecordsPerPageChange`, `isFetching`, `dirtyRows`, `activeRowUuid`, `tableSettings`
|
|
46
|
+
({ isPaginationEnabled, isInfiniteScrollingEnabled }), `rowHeight`, `containerHeight`
|
|
47
|
+
(default 750), `fetchNextPage` + `hasNextPage` (infinite scroll), `renderExpandedContent`,
|
|
48
|
+
`headerSpan` (multi-row header), `showColumnBorders`, `resizableColumns`, `shrinkColumns`,
|
|
49
|
+
`minColumnWidth` (number | per-column map).
|
|
50
|
+
|
|
51
|
+
## Column sizing: measure-and-freeze
|
|
52
|
+
|
|
53
|
+
Constants in `PrimaryTable.tsx`: `DEFAULT_MIN_COLUMN_WIDTH = 80`, `ABSOLUTE_MIN_COLUMN_WIDTH
|
|
54
|
+
= 40`, `MIN_RESIZE_WIDTH = 40` (and `MIN_COLUMN_WIDTH = 120` from `buildTanstackColumns`).
|
|
55
|
+
|
|
56
|
+
1. **Measure** — on first paint, measure header + widest body cell content per column
|
|
57
|
+
(`measureCellContentWidth`), store in `frozenWidths`; per-column floors in `contentFloors`.
|
|
58
|
+
2. **Freeze** — apply `table-layout: fixed` + a `<colgroup>` of frozen pixel widths so columns
|
|
59
|
+
don't reflow while virtualizing. Resets when the leaf column set changes. Empty tables stay
|
|
60
|
+
on auto-layout.
|
|
61
|
+
3. **Shrink-to-fit** — when `shrinkColumns` (modal tables), scale widths to container width
|
|
62
|
+
via `fitColumnsToContainer`, respecting `minColumnWidth` → `contentFloors` →
|
|
63
|
+
`DEFAULT_MIN_COLUMN_WIDTH`, clamped to `ABSOLUTE_MIN_COLUMN_WIDTH`; ResizeObserver re-fits.
|
|
64
|
+
4. **Resize** — when `resizableColumns`, a header drag handle mutates `frozenWidths` only
|
|
65
|
+
(visual, local, resets on column change).
|
|
66
|
+
|
|
67
|
+
## Rows & virtualization
|
|
68
|
+
|
|
69
|
+
`PrimaryTableHeaderCell` (label + `SortIcon` with multi-sort index + filter UI dispatch by
|
|
70
|
+
field type + resize handle + sticky edge divider), `PrimaryTableBodyCell` (`flexRender`,
|
|
71
|
+
sticky styling, action-column detection via `meta.isActionColumn`), `PrimaryTableRow` (dirty/
|
|
72
|
+
active/hover states; click handler ignores text-selection; renders expanded content row),
|
|
73
|
+
`PrimaryTableExpandableRow` (adds chevron toggle). Body uses a virtualizer with top/bottom
|
|
74
|
+
spacer rows computed from the virtualizer item `.start`/`.end` minus `scrollMargin`. The
|
|
75
|
+
`supply-modal-table` skin **disables virtualization** (renders all rows, min-height 300px).
|
|
76
|
+
Sticky columns use a `--sticky-offset` CSS variable summed from neighboring column widths.
|
|
77
|
+
|
|
78
|
+
## Gotchas
|
|
79
|
+
|
|
80
|
+
- Frozen widths measure only **mounted** rows; virtualized tables size from the first paint set.
|
|
81
|
+
- `headerSpan` adds a separate top row (`colSpan`); only the leaf header row maps to colgroup.
|
|
82
|
+
- Resize is non-persistent (local state, resets when columns change).
|
|
83
|
+
|
|
84
|
+
## Change history
|
|
85
|
+
- 2026-06-23 — Documented the three Primary Table entry points, measure-and-freeze sizing, virtualization, and sticky columns (apeterson).
|
|
86
|
+
- 2026-06-22 — Added `shrinkColumns` / `minColumnWidth` shrink-to-fit for modal tables; freeze logic uses natural content width (apeterson).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TableRecordModal (record-detail modal shell)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/components/TableRecordModal/TableRecordModal.tsx
|
|
13
|
+
- toga-blox/src/components/TableRecordModal/index.ts
|
|
14
|
+
- toga-blox/src/components/TableRecordModal/tableRecordModal.module.css
|
|
15
|
+
related:
|
|
16
|
+
- ../architecture.md
|
|
17
|
+
- table.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
`TableRecordModal` is a **generic, presentational modal shell** for showing a single table
|
|
23
|
+
record (row) in detail — typically opened from a table row click. It handles **positioning,
|
|
24
|
+
animation, backdrop, and lifecycle only**; the consumer supplies the record's field rendering
|
|
25
|
+
via `children`. It does **not** render fields, manage edit/view modes, or provide
|
|
26
|
+
accessibility/focus management — all of that is the consumer's responsibility.
|
|
27
|
+
|
|
28
|
+
## API (props)
|
|
29
|
+
|
|
30
|
+
`{ isOpen, onClose, position?, children, slideUpModalClassName?, variant?, isMaximized?,
|
|
31
|
+
style? }`:
|
|
32
|
+
|
|
33
|
+
- `isOpen: boolean`, `onClose: () => void` — controlled visibility.
|
|
34
|
+
- `position?: "right" | "left" | "bottom" | "top" | "center"` (default `"right"`) — each has
|
|
35
|
+
its own slide animation and backdrop opacity (right/left 0.2, top/bottom 0.4, center 0.5 +
|
|
36
|
+
blur).
|
|
37
|
+
- `children: ReactNode | ((onClose) => ReactElement)` — static content **or** a render-prop
|
|
38
|
+
function that receives `onClose` (avoids prop-drilling the close handler).
|
|
39
|
+
- `variant?: "default" | "expandable"` + `isMaximized?` — expandable variant toggles width
|
|
40
|
+
between minimized (57%) and full (100%); `isMaximized` is ignored for other variants.
|
|
41
|
+
- `slideUpModalClassName?` — arbitrary extra class merged onto the wrapper (misnomer: not
|
|
42
|
+
slide-up-specific). `style?` — inline override.
|
|
43
|
+
|
|
44
|
+
## Behavior & styling
|
|
45
|
+
|
|
46
|
+
- A single `useEffect` locks body scroll (`document.body.style.overflow = "hidden"`) while
|
|
47
|
+
open and restores it on close.
|
|
48
|
+
- Backdrop click **always** calls `onClose` (not suppressible).
|
|
49
|
+
- CSS Modules (`tableRecordModal.module.css`) with position keyframes (`slideFromRight/Left/
|
|
50
|
+
Top/Bottom`, `fadeScaleIn`, `fadeIn`) and CSS variables for dimensions
|
|
51
|
+
(`--tableRecordModal-right-width` default 480px, etc.).
|
|
52
|
+
- Shares the `TableTheme` className-override type from `Table/themeConfig/types.ts`.
|
|
53
|
+
|
|
54
|
+
## Relationship to Table
|
|
55
|
+
|
|
56
|
+
No direct import coupling. The consuming app wires it: on `PrimaryTable`'s `onRowClick(row)`
|
|
57
|
+
it opens the modal with `isOpen={!!selectedRow}` and renders the record inside `children`
|
|
58
|
+
(often using the same `tableViewFields` metadata to format fields). `utils/resolveModalType.tsx`
|
|
59
|
+
and `types.ts` are present but **empty/reserved** for a future field-type→renderer mapping.
|
|
60
|
+
|
|
61
|
+
## Gotchas
|
|
62
|
+
|
|
63
|
+
- **No portal** — renders inline in the React tree; a parent `overflow: hidden` can clip it.
|
|
64
|
+
- **No focus trap / ARIA** (`role="dialog"`, `aria-modal`) — add in consumer.
|
|
65
|
+
- **No modal stacking guard** — nested modals overwrite the saved body-overflow value.
|
|
66
|
+
- CSS-variable defaults are hardcoded fallbacks; set vars on the DOM before mount to change sizes.
|
|
67
|
+
|
|
68
|
+
## Change history
|
|
69
|
+
- 2026-06-23 — Documented TableRecordModal as a generic modal shell: positions, render-prop children, body-scroll lock, and consumer-owned field rendering / a11y (apeterson).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Table component (cells, action cells, filters & sorts, hooks, theming)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/components/Table/index.ts
|
|
13
|
+
- toga-blox/src/components/Table/types.ts
|
|
14
|
+
- toga-blox/src/components/Table/utils/buildTanstackColumns.tsx
|
|
15
|
+
- toga-blox/src/components/Table/utils/resolveCellType.tsx
|
|
16
|
+
- toga-blox/src/components/Table/components/cellTypes
|
|
17
|
+
- toga-blox/src/components/Table/components/actionCells
|
|
18
|
+
- toga-blox/src/components/Table/components/columnFiltersAndSorts
|
|
19
|
+
- toga-blox/src/components/Table/hooks
|
|
20
|
+
- toga-blox/src/components/Table/themeConfig
|
|
21
|
+
related:
|
|
22
|
+
- ../architecture.md
|
|
23
|
+
- primary-table-templates.md
|
|
24
|
+
- api-client.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Summary
|
|
28
|
+
|
|
29
|
+
The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block
|
|
30
|
+
behind the [Primary Table templates](primary-table-templates.md). It is organized in layers:
|
|
31
|
+
|
|
32
|
+
- **Primitives** — thin HTML wrappers (`Table`, `TableHead`, `TableBody`, `TableRow`,
|
|
33
|
+
`TableHeaderCell`, `TableCell`), exported from `src/components/Table/index.ts`.
|
|
34
|
+
- **Column building** — `buildTanstackColumns()` turns a `TableViewField[]` (API metadata)
|
|
35
|
+
into TanStack `ColumnDef[]`; `resolveCellType()` picks the right cell renderer per field type.
|
|
36
|
+
- **Cell types** (`components/cellTypes/`) — the renderers for each data type.
|
|
37
|
+
- **Action cells** (`components/actionCells/`) — row-level action buttons (chevron, download).
|
|
38
|
+
- **Filters & sorts** (`components/columnFiltersAndSorts/`) — per-column header filter/sort UI.
|
|
39
|
+
- **Hooks** (`hooks/`) — data fetching (paginated/infinite), edit state, metadata, filter options.
|
|
40
|
+
- **Theming** (`themeConfig/`) — `ThemeProvider` + skins + CSS variables.
|
|
41
|
+
|
|
42
|
+
## Column building & cell resolution
|
|
43
|
+
|
|
44
|
+
`utils/buildTanstackColumns.tsx` — `buildTanstackColumns({ tableViewFields, isEditable,
|
|
45
|
+
updateCell, typeValues?, skin?, onNavigate?, userSettings?, columnWidths?, columnTypeConfig? })`
|
|
46
|
+
returns `ColumnDef<any>[]`. Per visible field it sets `id = slug`, `header = label`,
|
|
47
|
+
`enableSorting/Filtering`, a custom `filterFn` (`columnFilterFn` supports modes
|
|
48
|
+
**startsWith, endsWith, exactly, includes, excludes**), an `accessorFn` that walks
|
|
49
|
+
dot-notation paths (`related.field`), and a `cell` that delegates to `resolveCellType()`.
|
|
50
|
+
|
|
51
|
+
**Column width precedence** (`buildTanstackColumns.tsx:112-123`): explicit `columnWidths`
|
|
52
|
+
(set as both `size` and `minSize`) → `columnTypeConfig`/`COLUMN_TYPE_CONFIG` → field `width`
|
|
53
|
+
→ default 200px size / `MIN_COLUMN_WIDTH` (=120, line 9) minSize. `COLUMN_TYPE_CONFIG`
|
|
54
|
+
defaults: DATETIME_CREATED 160, STATUS 140, URGENCY 100, CLIENT 180.
|
|
55
|
+
|
|
56
|
+
`utils/resolveCellType.tsx` maps `tableViewField.type` → a cell component and wraps it for
|
|
57
|
+
alignment (CVA `cellWrapperVariants`), expandable, and copyable behavior. Empty/null values
|
|
58
|
+
render `EmptyCell`. If `imageUrlField` is set, the URL is resolved separately and passed to
|
|
59
|
+
`ImageCell`.
|
|
60
|
+
|
|
61
|
+
## Cell types (`components/cellTypes/`)
|
|
62
|
+
|
|
63
|
+
| Component | Type | Renders |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `TableCell` / `TableHeaderCell` | generic | base `<td>` / `<th>` wrappers (forwardRef) |
|
|
66
|
+
| `DateCell` | DATE/DATETIME | dayjs-formatted time (top) + date (bottom); optional `timeZone` (dayjs.tz); default `MM/DD/YYYY` + `hh:mm A` |
|
|
67
|
+
| `CurrencyCell` | CURRENCY | `$` + `toLocaleString("en-US")` to `precision` |
|
|
68
|
+
| `NumberCell` | NUMBER | localized number, `maximumFractionDigits = precision` |
|
|
69
|
+
| `StatusCell` | STATUS | colored dot + label from `typeValues` (fallback `#CBD5E1`) |
|
|
70
|
+
| `StatusBadgeCell` | STATUS_BADGE | filled badge, background from `typeValues` |
|
|
71
|
+
| `UrgencyCell` | URGENCY | FontAwesome flag icon + label, colored |
|
|
72
|
+
| `BooleanCell` | BOOLEAN | "Yes"/"No" with green (`#2CB224`) / red (`#FF2E54`) dot |
|
|
73
|
+
| `EmptyCell` | — | em-dash placeholder |
|
|
74
|
+
| `ImageCell` | IMAGE | image (or gray placeholder) + label |
|
|
75
|
+
| `CopyableCell` | any | wraps content with copy-to-clipboard button (`buttonPosition` left/right) |
|
|
76
|
+
| `ClientCell` / `UserAvatarCell` | CLIENT/AVATAR | hash-colored initials avatar + name |
|
|
77
|
+
| `ExpandableCell` | text | truncates at `maxLength` (default 80) with show more/less |
|
|
78
|
+
| `EditableCell` | edit mode | type-specific inline input (text/number/currency/date/boolean); commits on blur |
|
|
79
|
+
|
|
80
|
+
## Action cells (`components/actionCells/`)
|
|
81
|
+
|
|
82
|
+
`ChevronActionCell` (row expand/collapse; rotates on `isActive`) and `DownloadActionCell`
|
|
83
|
+
(download button, `aria-label`, `stopPropagation`). Both take `{ row, onAction, isActive,
|
|
84
|
+
isHovered, skin, isDisabled }`. They are inserted as columns via `buildActionColumn()` +
|
|
85
|
+
`insertActionColumns()` (sorted by `position`: "start" → -∞, "end" → +∞, number → index).
|
|
86
|
+
|
|
87
|
+
## Column filters & sorts (`components/columnFiltersAndSorts/`)
|
|
88
|
+
|
|
89
|
+
Per-column header controls, each a popover trigger with `data-active` / `data-open`:
|
|
90
|
+
|
|
91
|
+
- **`HeaderFilterSearch`** — text filter, 5 modes (startsWith/endsWith/exactly/includes/
|
|
92
|
+
excludes) chosen from a **mode dropdown that excludes the currently selected mode**.
|
|
93
|
+
**Tag-based**: confirmed values become removable tags (`activeTags`); `isActive =
|
|
94
|
+
activeTags.length > 0`. The mode dropdown is portalled to `document.body` with fixed
|
|
95
|
+
positioning recomputed on scroll. The search icon (solid when active, regular otherwise)
|
|
96
|
+
is hidden while the input is focused; both the trigger button and the icon span carry
|
|
97
|
+
`data-active`. The clear button must not widen the input row (fixed-width menu).
|
|
98
|
+
- **`HeaderFilterRange`** — numeric, modes exactly/moreThan/lessThan + a range toggle
|
|
99
|
+
(min/max); emits `{ key, value }[]` (`eq`/`min`/`max`/`between`).
|
|
100
|
+
- **`HeaderFilterMultiselect`** — checkbox list with "Select All"; emits comma-joined slugs.
|
|
101
|
+
- **`HeaderFilterDate`** — calendar picker, modes exactly/before/after + range; converts
|
|
102
|
+
`MM/DD/YYYY` ↔ URL `YYYY-MM-DD`; calendar + mode dropdown both portalled.
|
|
103
|
+
- **`HeaderFilterButton`** — generic trigger: shows a numbered badge when active with a
|
|
104
|
+
`filterIndex`, otherwise the search icon (`data-active`).
|
|
105
|
+
- **`SortIcon`** — asc/desc/reset menu with a multi-sort index badge; button visibility keys
|
|
106
|
+
off current sort direction.
|
|
107
|
+
|
|
108
|
+
## Hooks (`hooks/`)
|
|
109
|
+
|
|
110
|
+
| Hook | Returns / does |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `useTableSetup` | builds columns via `buildTanstackColumns` + `insertActionColumns`; memoized `updateCell`; tracks hovered row uuid |
|
|
113
|
+
| `useTableData` | runs **either** paginated or infinite query by `isPaginationEnabled` (single source of truth); shared `["table-data", slug, …]` cache prefix; strips display-only URL params (`slug`, `cols`) so they don't refetch |
|
|
114
|
+
| `useTablePageData` | paginated-only variant |
|
|
115
|
+
| `useInfiniteTableData` | `useInfiniteQuery` + IntersectionObserver on a `sentinelRef` |
|
|
116
|
+
| `useTableInfiniteScroll` | virtualizer-based "near end" trigger (lower-level alternative) |
|
|
117
|
+
| `useTableEdit` | dirty-row tracking, dot-notation patch, `handleSave` calls `mutationFn` and updates the query cache, clears dirty + exits edit mode on success |
|
|
118
|
+
| `useFetchTablePageMeta` | fetches table meta + fans out filter-option queries per field |
|
|
119
|
+
| `useAssignTableFieldLabels` | merges page-level labels into field defs |
|
|
120
|
+
| `useTableFieldFilterOptions` | filter options for a single field |
|
|
121
|
+
|
|
122
|
+
## Theming (`themeConfig/`)
|
|
123
|
+
|
|
124
|
+
`ThemeProvider` injects a `ClientTheme { id, name, vars }` as CSS variables on
|
|
125
|
+
`document.documentElement`. `TableTheme` (types.ts) is a className-override map
|
|
126
|
+
(`wrapper`, `headerCell`, `bodyRow`, `stickyLeft/Right`, `filterPopover`, …). Skins:
|
|
127
|
+
`supply` (default), `desk`, `supply-nested` (compact, max-height 480px), `supply-modal-table`
|
|
128
|
+
(modal: fixed heights, virtualization disabled). Design tokens are `--primaryTable-*` in
|
|
129
|
+
`toga.module.css`.
|
|
130
|
+
|
|
131
|
+
## Gotchas
|
|
132
|
+
|
|
133
|
+
- `MIN_COLUMN_WIDTH = 120` is the shrink floor only for columns with **no** explicit width.
|
|
134
|
+
- The filter `includes` mode treats comma-separated values as OR.
|
|
135
|
+
- `EditableCell` currency formats **on blur**, phone formats **on keystroke**.
|
|
136
|
+
- Cell type config (`columnTypeConfig` prop) overrides the hardcoded `COLUMN_TYPE_CONFIG`.
|
|
137
|
+
|
|
138
|
+
## Change history
|
|
139
|
+
- 2026-06-23 — Documented the Table component layers, cell types, filters/sorts, hooks, and theming (apeterson).
|
|
140
|
+
- 2026-06-23 — `HeaderFilterSearch`: search icon hidden on input focus; `data-active` added to the icon span; clear button no longer widens the input (fixed-width menu); mode dropdown excludes the selected mode (apeterson).
|
|
141
|
+
- 2026-06-22 — Column sizing reworked to measure-and-freeze using natural content width; `MIN_COLUMN_WIDTH` floor (120px) applies only when no explicit width is set (apeterson).
|
package/knowledge/INDEX.md
CHANGED
|
@@ -28,6 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
28
28
|
- **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
29
29
|
- **toga2-commerce** (TOGa Commerce) — 2 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
30
30
|
- **toga25-supply** (TOGa 2.5 Supply) — 5 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
31
|
+
- **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
31
32
|
|
|
32
33
|
## standalone framework
|
|
33
34
|
|
package/knowledge/registry.json
CHANGED
package/package.json
CHANGED