@object-ui/plugin-list 17.7.0 → 18.0.0
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/CHANGELOG.md +1012 -0
- package/README.md +305 -35
- package/dist/ListEmptyState.d.ts +72 -0
- package/dist/ListEmptyState.d.ts.map +1 -0
- package/dist/ListView.d.ts +127 -18
- package/dist/ListView.d.ts.map +1 -1
- package/dist/ListViewBlock.d.ts.map +1 -1
- package/dist/ListViewCompact.d.ts +77 -0
- package/dist/ListViewCompact.d.ts.map +1 -0
- package/dist/ObjectGallery.d.ts.map +1 -1
- package/dist/UserFilters.d.ts +14 -1
- package/dist/UserFilters.d.ts.map +1 -1
- package/dist/ViewSwitcher.d.ts +28 -20
- package/dist/ViewSwitcher.d.ts.map +1 -1
- package/dist/calendarWindow.d.ts +102 -0
- package/dist/calendarWindow.d.ts.map +1 -0
- package/dist/components/TabBar.d.ts.map +1 -1
- package/dist/components/ViewSettingsPopover.d.ts +53 -16
- package/dist/components/ViewSettingsPopover.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2404 -1521
- package/dist/index.umd.cjs +2 -2
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -20,6 +20,15 @@ The toolbar and cell renderers are tuned for low visual noise on dense tables:
|
|
|
20
20
|
- **Unified toolbar row**: view tabs (`schema.tabs`), user filters and tool
|
|
21
21
|
buttons share a single bordered row. The previous stacked rows (`tabs` /
|
|
22
22
|
`description` / `toolbar`) are collapsed into one separator line.
|
|
23
|
+
- **Four tool buttons by default**: the right side of the row is Filter, Sort,
|
|
24
|
+
Display and Search. **Display** holds the rest — a Layout row (the
|
|
25
|
+
visualization switcher, when the list offers more than one), Group, Row
|
|
26
|
+
color, Hide fields, Density, a session-only Row numbers switch and an Edit
|
|
27
|
+
inline switch, with Reset to view default, Refresh and Export in its footer.
|
|
28
|
+
Its trigger counts the settings that differ from the view's own. Each
|
|
29
|
+
control saves what its own toolbar button saves; the panel stores nothing
|
|
30
|
+
of its own. A view that sets `compactToolbar: false` gets one toolbar
|
|
31
|
+
button per control instead (objectui#12423).
|
|
23
32
|
- **Flat user-filter pills**: `userFilters` (dropdown mode) render as ghost
|
|
24
33
|
text + count. Active state is shown via `text-foreground font-medium`
|
|
25
34
|
rather than a filled / bordered pill.
|
|
@@ -108,7 +117,12 @@ import { ListView } from '@object-ui/plugin-list';
|
|
|
108
117
|
```
|
|
109
118
|
|
|
110
119
|
When both are present, `grouping` wins. End users can also add or remove
|
|
111
|
-
grouping fields at runtime via the Group toolbar button.
|
|
120
|
+
grouping fields at runtime via the Group toolbar button. Every such edit, in
|
|
121
|
+
the Display panel's Group section or in the wide toolbar's Group panel
|
|
122
|
+
(Clear included), fires `onGroupingChange` with the spec `GroupingConfig`, or
|
|
123
|
+
with `undefined` when the grouping is cleared. A changed `schema.grouping` that
|
|
124
|
+
the list re-reads does not fire it: that value came from the host
|
|
125
|
+
(objectui#11860).
|
|
112
126
|
|
|
113
127
|
A grouped **grid** is grouped on the server: over a data source that answers
|
|
114
128
|
the group header query (`dataSource.queryGroupHeaders`), `ListView` hands the
|
|
@@ -129,6 +143,11 @@ still grouped in the browser, exactly.
|
|
|
129
143
|
|
|
130
144
|
### With Multiple View Types
|
|
131
145
|
|
|
146
|
+
Each view type's configuration is a block of that name at the top level of the
|
|
147
|
+
schema: `kanban`, `calendar`, `gallery`, `timeline`, `gantt`, `map`, `chart`,
|
|
148
|
+
`tree`. Each block is `@objectstack/spec`'s own list-view block, and an unknown
|
|
149
|
+
key in it is refused by name.
|
|
150
|
+
|
|
132
151
|
```tsx
|
|
133
152
|
import { ListView } from '@object-ui/plugin-list';
|
|
134
153
|
|
|
@@ -138,25 +157,32 @@ import { ListView } from '@object-ui/plugin-list';
|
|
|
138
157
|
objectName: 'deals',
|
|
139
158
|
viewType: 'kanban',
|
|
140
159
|
columns: ['name', 'amount', 'stage', 'close_date'],
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
kanban: {
|
|
161
|
+
groupByField: 'stage',
|
|
162
|
+
columns: ['name', 'amount'],
|
|
163
|
+
titleField: 'name',
|
|
164
|
+
},
|
|
165
|
+
calendar: {
|
|
166
|
+
startDateField: 'close_date',
|
|
167
|
+
titleField: 'name',
|
|
168
|
+
},
|
|
169
|
+
// A chart binds a semantic-layer dataset and selects its dimensions and
|
|
170
|
+
// measures by name (ADR-0021).
|
|
171
|
+
chart: {
|
|
172
|
+
chartType: 'bar',
|
|
173
|
+
dataset: 'deal_pipeline',
|
|
174
|
+
dimensions: ['stage'],
|
|
175
|
+
values: ['total_amount'],
|
|
176
|
+
},
|
|
156
177
|
}}
|
|
157
178
|
/>
|
|
158
179
|
```
|
|
159
180
|
|
|
181
|
+
A stored view may also carry the same blocks in a legacy `options` bag
|
|
182
|
+
(`options.kanban`, …), which the platform's view write door judges key by key
|
|
183
|
+
with the same block schemas. `ListView` reads it under the top-level block, which
|
|
184
|
+
wins per key. Author the top-level blocks.
|
|
185
|
+
|
|
160
186
|
### With Callbacks
|
|
161
187
|
|
|
162
188
|
```tsx
|
|
@@ -172,6 +198,57 @@ import { ListView } from '@object-ui/plugin-list';
|
|
|
172
198
|
onSearchChange={(search) => console.log('Search:', search)}
|
|
173
199
|
onSortChange={(sort) => console.log('Sort:', sort)}
|
|
174
200
|
onFilterChange={(filters) => console.log('Filters:', filters)}
|
|
201
|
+
onGroupingChange={(grouping) => console.log('Grouping:', grouping)}
|
|
202
|
+
/>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Host slot: `toolbarLeading`
|
|
206
|
+
|
|
207
|
+
A host can draw its own node at the start of the list toolbar row, before the
|
|
208
|
+
user-filter chips, with the `toolbarLeading` prop. The node sits in a
|
|
209
|
+
`min-w-0 flex-1` wrapper, so it gets the width the right-hand controls leave.
|
|
210
|
+
This is a React prop for the host app, not a schema key: list metadata cannot
|
|
211
|
+
set it. Without it the toolbar row is unchanged. The console uses it to put its
|
|
212
|
+
saved-view tabs on the same row as Filter, Sort, Display and Search.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { ListView } from '@object-ui/plugin-list';
|
|
216
|
+
|
|
217
|
+
function MyViewTabs() {
|
|
218
|
+
return <nav aria-label="Views">…</nav>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
<ListView
|
|
222
|
+
schema={{ type: 'list-view', objectName: 'tasks', columns: ['title', 'status'] }}
|
|
223
|
+
toolbarLeading={<MyViewTabs />}
|
|
224
|
+
/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Host prop: `viewDefaults`
|
|
228
|
+
|
|
229
|
+
A host that stores the user's own hidden fields or density and lays that
|
|
230
|
+
preference over `hiddenFields` / `rowHeight` in `schema` passes the view's own
|
|
231
|
+
values in `viewDefaults`. The Display control's trigger count and its "Reset to
|
|
232
|
+
view default" then compare these two settings against the view, not against
|
|
233
|
+
`schema`, and the reset writes the view's values back through the doors the
|
|
234
|
+
controls write through (`onHiddenFieldsChange`, `schema.onDensityChange`), so
|
|
235
|
+
the stored preference follows. This is a React prop for the host app, not a
|
|
236
|
+
schema key: list metadata cannot set it.
|
|
237
|
+
|
|
238
|
+
When it is passed, its members are the whole baseline: an absent `hiddenFields`
|
|
239
|
+
means the view hides nothing, and an absent `rowHeight` means the default
|
|
240
|
+
density. Without it nothing changes: `schema` is taken as the view's own. The
|
|
241
|
+
console passes it on the object list page, where it persists both settings per
|
|
242
|
+
user.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { ListView } from '@object-ui/plugin-list';
|
|
246
|
+
|
|
247
|
+
// The view hides `amount`; the user has since hidden `stage` as well.
|
|
248
|
+
<ListView
|
|
249
|
+
schema={{ type: 'list-view', objectName: 'deals', hiddenFields: ['amount', 'stage'] }}
|
|
250
|
+
viewDefaults={{ hiddenFields: ['amount'] }}
|
|
251
|
+
onHiddenFieldsChange={(hidden) => console.log('Hidden:', hidden)}
|
|
175
252
|
/>
|
|
176
253
|
```
|
|
177
254
|
|
|
@@ -198,12 +275,11 @@ const view: ListViewSchema = {
|
|
|
198
275
|
columns: ['title', 'status', 'assignee'],
|
|
199
276
|
filters: [['status', '=', 'open']],
|
|
200
277
|
sort: [{ field: 'title', order: 'asc' }],
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
},
|
|
278
|
+
// One block per view type, at the top level. A grid has no block of its own:
|
|
279
|
+
// its settings are the top-level keys above.
|
|
280
|
+
kanban: { groupByField: 'status', columns: ['assignee'], titleField: 'title' },
|
|
281
|
+
calendar: { startDateField: 'due_date', titleField: 'title' },
|
|
282
|
+
chart: { chartType: 'bar', dataset: 'task_status', dimensions: ['status'], values: ['total_amount'] },
|
|
207
283
|
};
|
|
208
284
|
|
|
209
285
|
// `columns` also accepts ListColumn objects in place of the field-name strings.
|
|
@@ -229,6 +305,47 @@ const viewTypes: Record<NonNullable<ListViewSchema['viewType']>, string> = {
|
|
|
229
305
|
export { view, richColumns, viewTypes };
|
|
230
306
|
```
|
|
231
307
|
|
|
308
|
+
## Empty state
|
|
309
|
+
|
|
310
|
+
When the `grid` or `gallery` visualization has no record to show, the view
|
|
311
|
+
draws its `emptyState` (the spec's `EmptyState`): a Lucide `icon`, a `title`, a
|
|
312
|
+
`message` and one `link: { label, url }` the user follows, such as the how-to
|
|
313
|
+
page that explains what belongs in the list (objectui#12270). A member left out
|
|
314
|
+
keeps the localized default copy; `title`, `message` and the link's `label`
|
|
315
|
+
each take a string or an inline locale map, resolved against the display
|
|
316
|
+
locale.
|
|
317
|
+
|
|
318
|
+
The link is followed as a `type: 'url'` action through the action runner, so it
|
|
319
|
+
opens what such an action opens: an in-app path (`/…` or `./…`) in place, an
|
|
320
|
+
`http(s)://` address as the runner opens an external target, and `mailto:` in
|
|
321
|
+
place. The runner refuses anything else at the click and reports it. The link
|
|
322
|
+
is drawn beside the add-record action when the view's `addRecord` block offers
|
|
323
|
+
one; the two are separate, and either, both or neither may be drawn.
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import type { ListViewSchema } from '@object-ui/types';
|
|
327
|
+
|
|
328
|
+
const emptyTasks: ListViewSchema = {
|
|
329
|
+
type: 'list-view',
|
|
330
|
+
objectName: 'tasks',
|
|
331
|
+
viewType: 'grid',
|
|
332
|
+
columns: ['title'],
|
|
333
|
+
emptyState: {
|
|
334
|
+
title: 'No open tasks',
|
|
335
|
+
message: 'Tasks assigned to you appear here.',
|
|
336
|
+
icon: 'inbox',
|
|
337
|
+
link: { label: 'How tasks are assigned', url: '/docs/tasks' },
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export { emptyTasks };
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
A list page (`interfaceConfig` of a `type: 'list'` page) may declare its own
|
|
345
|
+
`emptyState` of the same shape. The page's own block wins, whole, over the one
|
|
346
|
+
its source view declares, on every visualization; the source view's is the
|
|
347
|
+
fallback when the page declares none.
|
|
348
|
+
|
|
232
349
|
## Page size and fetch batch
|
|
233
350
|
|
|
234
351
|
A list view asks the server for one window of records. When
|
|
@@ -239,22 +356,132 @@ the view pages:
|
|
|
239
356
|
grid's pager turns it. Its size is the default `@objectstack/spec` declares
|
|
240
357
|
for `pagination.pageSize`; the list reads it from the spec rather than
|
|
241
358
|
keeping a number of its own.
|
|
242
|
-
- **Every other view does not page** — kanban,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
359
|
+
- **Every other view does not page** — kanban, gallery and the rest, and a
|
|
360
|
+
grouped grid. The window is one fetch batch of **100** records, and records
|
|
361
|
+
past it are not reachable; the record-count bar says so when the batch comes
|
|
362
|
+
back full. It is a fetch size, not a page size, so it does not follow the
|
|
363
|
+
spec's display default.
|
|
364
|
+
- **The calendar view fetches the days it shows.** With a start date bound
|
|
365
|
+
(`calendar.startDateField`), the window is the calendar's visible days on
|
|
366
|
+
that field (and on `calendar.endDateField` when one is bound, so a span that
|
|
367
|
+
runs into the month is fetched), plus the records with no start date, which
|
|
368
|
+
the calendar lists as unscheduled. The list walks that window in steps of the
|
|
369
|
+
same fetch batch of 100 until it is exhausted, so every record of the month
|
|
370
|
+
is drawn, and stops at the platform's non-grid ceiling of 2,000 records with
|
|
371
|
+
a note under the calendar naming both numbers. Moving to a month the fetched
|
|
372
|
+
window does not cover fetches that month. The calendar reports the days it
|
|
373
|
+
draws through its `onVisibleRangeChange` callback (objectui#12081).
|
|
374
|
+
|
|
375
|
+
A declared `pagination.pageSize` sizes the window on every view but the
|
|
376
|
+
calendar, as before. With no declared size, switching between the grid view and
|
|
377
|
+
another view changes the window, so the list fetches again.
|
|
378
|
+
|
|
379
|
+
## On a page — `data` and `type`
|
|
380
|
+
|
|
381
|
+
A `list-view` node on a page is written in `@objectstack/spec`'s list view
|
|
382
|
+
spelling: `data` says where its records come from, and `type` says which kind of
|
|
383
|
+
list it is. On a metadata page both sit in the node's `properties`. The node's
|
|
384
|
+
own `type` stays `list-view`, because it is the key the page renderer looks the
|
|
385
|
+
component up by:
|
|
247
386
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"type": "list-view",
|
|
390
|
+
"properties": {
|
|
391
|
+
"data": { "provider": "object", "object": "deal" },
|
|
392
|
+
"columns": ["name", "amount"],
|
|
393
|
+
"kanban": { "groupByField": "stage" }
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
That node draws a grid of `deal` records: without `properties.type` the list is
|
|
399
|
+
a grid, the spec's default kind. Add `"type": "kanban"` to the same `properties`
|
|
400
|
+
and it draws a kanban over `deal`, grouped by `stage`. A `type` that names no
|
|
401
|
+
kind this list draws is a grid too. `data` may also hand the list its rows
|
|
402
|
+
(`{ "provider": "value", "items": [...] }`).
|
|
403
|
+
|
|
404
|
+
A page node's own `objectName` and `viewType` are **not read**. The page
|
|
405
|
+
renderer removes them before the list sees the node, and logs one warning per key
|
|
406
|
+
naming what to write instead. A node that names its object only as `objectName`
|
|
407
|
+
therefore names no object, and the list draws a hint in its place: "No object
|
|
408
|
+
named: set data.object or dataSource.object." Converting page nodes stored in
|
|
409
|
+
the old spelling is the platform's job (an ADR-0087 conversion in
|
|
410
|
+
`@objectstack/spec`); this renderer does not read them back. `ListView`'s own
|
|
411
|
+
React props (the `ListViewSchema` examples above) are a separate surface and
|
|
412
|
+
still spell the two `objectName` and `viewType`.
|
|
413
|
+
|
|
414
|
+
`objectui validate` judges a page `list-view` node in this spelling. Its record
|
|
415
|
+
source is `properties.data` or a bound `dataSource.object`, and a node with
|
|
416
|
+
neither is refused at `properties.data`. `objectName` and `viewType` are refused
|
|
417
|
+
by name, on the node or in `properties`, each naming what to write instead
|
|
418
|
+
(`properties.data`, `properties.type`). A `properties.type` that names no kind a
|
|
419
|
+
list view draws is refused, and so is a list view prop such as `columns` written
|
|
420
|
+
on the node itself rather than in `properties`.
|
|
421
|
+
|
|
422
|
+
## On a page — `requiredPermissions`
|
|
423
|
+
|
|
424
|
+
One more key of the spec's list view row acts on a page `list-view` node. It
|
|
425
|
+
sits in `properties`:
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"type": "list-view",
|
|
430
|
+
"properties": {
|
|
431
|
+
"data": { "provider": "object", "object": "deal" },
|
|
432
|
+
"columns": ["name", "amount"],
|
|
433
|
+
"requiredPermissions": ["sales.view_pipeline"]
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**`requiredPermissions`** names the capabilities the viewer must **all** hold,
|
|
439
|
+
the names permission sets grant through `systemPermissions`. It gates the node
|
|
440
|
+
the way it gates the record blocks (`record:details`, `record:highlights`,
|
|
441
|
+
`record:related_list`). A viewer missing one sees the notice "Insufficient
|
|
442
|
+
permissions to view list." (`role="status"`) in place of the list, and no
|
|
443
|
+
records are queried. An empty list, or no key, is no gate. A permission
|
|
444
|
+
provider that reports no capabilities at all leaves the list shown; the server
|
|
445
|
+
stays the authority. Only the page node reads this key: the object list page
|
|
446
|
+
draws `ListView` itself, and the server gates the views it serves.
|
|
447
|
+
|
|
448
|
+
`addRecord` draws an Add button. `position` places it: `"top"` (the toolbar),
|
|
449
|
+
`"bottom"` or `"both"`. `mode` and `formView` are not read.
|
|
450
|
+
|
|
451
|
+
The spec row declares `enabled` default `true` and `position` default
|
|
452
|
+
`"bottom"`. The renderer does not apply those defaults: it draws the button only
|
|
453
|
+
for an explicit `enabled: true`, and places an absent `position` at the top.
|
|
454
|
+
objectui#13099 owns that gap; until it lands, write both keys.
|
|
455
|
+
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"type": "list-view",
|
|
459
|
+
"properties": {
|
|
460
|
+
"data": { "provider": "object", "object": "deal" },
|
|
461
|
+
"addRecord": { "enabled": true, "position": "top" }
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
On a page node the click calls the `onCreate` a `RelatedRecordActionsContext`
|
|
467
|
+
provider (`@object-ui/react`) serves for the listed object, resolved with
|
|
468
|
+
`{ objectName }` and no `relationshipField`. The console's page host serves the
|
|
469
|
+
create the object list's New opens: the record-form overlay, or the full-page
|
|
470
|
+
create route for an object whose `editMode` is `page`. With no such provider,
|
|
471
|
+
for a viewer who may not create, or for a create the object greys, the button
|
|
472
|
+
is not drawn, rather than drawn with no action. A host that passes `ListView`'s
|
|
473
|
+
own `onAddRecord` prop keeps it; the console's object list passes its header
|
|
474
|
+
New's create.
|
|
475
|
+
|
|
476
|
+
`sharing` does nothing on a page node yet: it draws a Share button with no
|
|
477
|
+
action, because the spec declares none for it. It is not a published input.
|
|
251
478
|
|
|
252
479
|
## Page binding — `dataSource` (referencing a saved view by name)
|
|
253
480
|
|
|
254
|
-
On a metadata page, a `list-view` component can bind its data through the
|
|
255
|
-
per-element data source (`PageComponentSchema.dataSource`,
|
|
256
|
-
`ElementDataSourceSchema`) instead of
|
|
257
|
-
view's configuration
|
|
481
|
+
On a metadata page, a `list-view` component can also bind its data through the
|
|
482
|
+
spec's per-element data source (`PageComponentSchema.dataSource`,
|
|
483
|
+
`ElementDataSourceSchema`) instead of naming its object in `data` and inlining
|
|
484
|
+
the view's configuration. A bound `dataSource.object` outranks `data`:
|
|
258
485
|
|
|
259
486
|
```json
|
|
260
487
|
{
|
|
@@ -274,7 +501,8 @@ copy of a view's configuration in sync with the view itself. Both the short key
|
|
|
274
501
|
this placement, and they beat the component's own same-named key. Values that
|
|
275
502
|
come from the named view are a *baseline*: a key written on the component itself
|
|
276
503
|
is more specific than the view it points at, so the component's key wins
|
|
277
|
-
(an empty `columns: []` counts as "not authored")
|
|
504
|
+
(an empty `columns: []` counts as "not authored"), and a kind written as
|
|
505
|
+
`properties.type` beats the view's kind. `filter` is the exception —
|
|
278
506
|
the spec calls `dataSource.filter` "additional filter criteria", so the
|
|
279
507
|
component's filter, the view's filter and the binding's filter all AND together.
|
|
280
508
|
A binding can narrow what the view selects, never widen it.
|
|
@@ -285,6 +513,48 @@ actual views, and issues no query. It deliberately does not fall back to the
|
|
|
285
513
|
object's default view: that would turn a typo into a silently *wider* answer on a
|
|
286
514
|
page that still looks like it works.
|
|
287
515
|
|
|
516
|
+
## Compact posture in a narrow page region — `compact`
|
|
517
|
+
|
|
518
|
+
`ListViewProps` has an optional host prop, `compact`. It is a React prop, not a
|
|
519
|
+
schema key: nothing in `@objectstack/spec` declares a posture, and an author
|
|
520
|
+
never writes one. The `list-view` page block sets it. It reads
|
|
521
|
+
`usePageRegionPosture()` from `@object-ui/components` and passes
|
|
522
|
+
`compact` when the answer is `'compact'`, which means the page region the block
|
|
523
|
+
sits in is narrower than half the page (an `aside` or `sidebar` rail beside
|
|
524
|
+
`main`).
|
|
525
|
+
|
|
526
|
+
With `compact`, the list draws:
|
|
527
|
+
|
|
528
|
+
- a header with the list's title (its `label`, else the object's label) and
|
|
529
|
+
the record count;
|
|
530
|
+
- one row per record: the view's first visible column, with the second visible
|
|
531
|
+
column muted on a line below; a click on a row opens the record the way a grid
|
|
532
|
+
row does.
|
|
533
|
+
|
|
534
|
+
It does not draw the toolbar, the view tabs, the `#` row-number column, the
|
|
535
|
+
pager or the record-count bar.
|
|
536
|
+
|
|
537
|
+
The prop applies only to a paged grid, the view whose rows `ListView` fetches
|
|
538
|
+
a page of. A board, a calendar or a grouped grid keeps its own rendering. When
|
|
539
|
+
`compact` is absent (wide regions, unknown widths, the object list page and
|
|
540
|
+
every other host), the list renders exactly as before.
|
|
541
|
+
|
|
542
|
+
### "View all"
|
|
543
|
+
|
|
544
|
+
Under its rows the compact list draws a "View all" link (`list.viewAll`) to the
|
|
545
|
+
rest of the list. The host builds the destination: the `list-view` block asks
|
|
546
|
+
the host's `listHref` / `openList` on `RelatedRecordActionsContext`
|
|
547
|
+
(`@object-ui/react`) for the object it reads, naming the saved view the block
|
|
548
|
+
binds with `dataSource.view` when it binds one. Without a bound view the link
|
|
549
|
+
goes to the object's own list. The block's own `filter`, `columns` and `sort`
|
|
550
|
+
are never handed over, so they never reach the URL; the header count stays the
|
|
551
|
+
answer to what the block shows.
|
|
552
|
+
|
|
553
|
+
The link is a real anchor: a plain left click navigates in the app through
|
|
554
|
+
`openList`, and a modifier click (a new tab) follows the href. A host that
|
|
555
|
+
publishes no `listHref`, or answers `null`, gets no link. An empty list draws
|
|
556
|
+
no link either.
|
|
557
|
+
|
|
288
558
|
## Sorting (and why relational columns are not offered)
|
|
289
559
|
|
|
290
560
|
The toolbar sort becomes a server `$orderby` on the **flat field name**, so the
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { LucideIcon } from 'lucide-react';
|
|
2
|
+
import { ListViewSchema } from '@object-ui/types';
|
|
3
|
+
/**
|
|
4
|
+
* ObjectUI
|
|
5
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The empty state a list draws when it has no records (objectui#12217).
|
|
12
|
+
*
|
|
13
|
+
* ONE component, drawn by the two surfaces that show a list of records as
|
|
14
|
+
* rows or cards: `ListView` for its `grid` and `gallery` visualizations, and
|
|
15
|
+
* `ObjectGallery` when it is rendered without a `ListView` around it (an
|
|
16
|
+
* authored `object-gallery` node, `ObjectView`'s own non-grid path). It used to
|
|
17
|
+
* live inline in `ListView`'s grid branch, and `ObjectGallery` drew its own
|
|
18
|
+
* hard-coded English line instead, ignoring both the view's authored
|
|
19
|
+
* `emptyState` and the locale.
|
|
20
|
+
*
|
|
21
|
+
* Module-internal: not exported from the package barrel. The copy is the
|
|
22
|
+
* `list.*` keys the grid already read; ⛔ no second string table. Each caller
|
|
23
|
+
* hands over the translation function and display locale it already holds, the
|
|
24
|
+
* authored members it has (an authored icon NAME already resolved through the
|
|
25
|
+
* icon seam), and what it knows about the query that came back empty; a caller
|
|
26
|
+
* that has no search box or user filters (a standalone gallery) says `false`.
|
|
27
|
+
*/
|
|
28
|
+
import * as React from 'react';
|
|
29
|
+
/**
|
|
30
|
+
* Whether a DECLARED filter (the view's own `filter`, or a standalone node's)
|
|
31
|
+
* narrows the query at all: a non-empty condition array or a non-empty
|
|
32
|
+
* filter-node object. An absent filter, `[]` and `{}` narrow nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function hasDeclaredFilter(filter: unknown): boolean;
|
|
35
|
+
/** The authored `emptyState` (the spec's `EmptyStateSchema`). */
|
|
36
|
+
type AuthoredEmptyState = NonNullable<ListViewSchema['emptyState']>;
|
|
37
|
+
export interface ListEmptyStateProps {
|
|
38
|
+
/** The authored `emptyState.title` (`I18nLabel`), resolved here against `displayLocale`. */
|
|
39
|
+
title?: AuthoredEmptyState['title'];
|
|
40
|
+
/** The authored `emptyState.message` (`I18nLabel`), resolved here against `displayLocale`. */
|
|
41
|
+
message?: AuthoredEmptyState['message'];
|
|
42
|
+
/**
|
|
43
|
+
* The authored `emptyState.icon`, already resolved by the caller through the
|
|
44
|
+
* ONE icon seam (`resolveIcon`, objectui#5935); `null` or absent draws the
|
|
45
|
+
* `Inbox` glyph.
|
|
46
|
+
*/
|
|
47
|
+
icon?: LucideIcon | null;
|
|
48
|
+
/**
|
|
49
|
+
* The authored `emptyState.link`, `{ label, url }` (objectui#12270), drawn by
|
|
50
|
+
* the one shared `EmptyStateLink` beside the add-record action. The spec
|
|
51
|
+
* keeps the two apart: the link is the empty state's own next step, the
|
|
52
|
+
* add-record entry point is the view's `addRecord` block, so either, both or
|
|
53
|
+
* neither may be drawn.
|
|
54
|
+
*/
|
|
55
|
+
link?: AuthoredEmptyState['link'];
|
|
56
|
+
/**
|
|
57
|
+
* The USER applied a query: the search box, the user-filter chips, the
|
|
58
|
+
* filter panel. Only then is the user told to adjust or clear their filters.
|
|
59
|
+
*/
|
|
60
|
+
hasUserQuery: boolean;
|
|
61
|
+
/** The declared filter narrows the query (see {@link hasDeclaredFilter}). */
|
|
62
|
+
hasBaseFilter: boolean;
|
|
63
|
+
/** Draws the add-record action when supplied; omitted, no action is drawn. */
|
|
64
|
+
onAddRecord?: () => void;
|
|
65
|
+
/** The caller's `list.*` translation function. */
|
|
66
|
+
t: (key: string) => string;
|
|
67
|
+
/** The locale an authored `I18nLabel` map is resolved against. */
|
|
68
|
+
displayLocale: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function ListEmptyState({ title: authoredTitle, message: authoredMessage, icon, link, hasUserQuery, hasBaseFilter, onAddRecord, t, displayLocale, }: ListEmptyStateProps): React.JSX.Element;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=ListEmptyState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListEmptyState.d.ts","sourceRoot":"","sources":["../src/ListEmptyState.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKvD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAI1D;AAED,iEAAiE;AACjE,KAAK,kBAAkB,GAAG,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,4FAA4F;IAC5F,KAAK,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACpC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACxC;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAClC;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,6EAA6E;IAC7E,aAAa,EAAE,OAAO,CAAC;IACvB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,kDAAkD;IAClD,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3B,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,EACxB,IAAI,EACJ,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,CAAC,EACD,aAAa,GACd,EAAE,mBAAmB,qBAgErB"}
|