@almadar/std 16.148.0 → 16.149.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/behaviors/lolo/ui/core/atoms/std-app-search.lolo +1 -1
- package/behaviors/lolo/ui/core/atoms/std-billable-hour.lolo +86 -41
- package/behaviors/lolo/ui/core/atoms/std-board.lolo +29 -16
- package/behaviors/lolo/ui/core/atoms/std-browse.lolo +187 -83
- package/behaviors/lolo/ui/core/atoms/std-dashboard-grid-embedded.lolo +8 -0
- package/behaviors/lolo/ui/core/atoms/std-file-store.lolo +1 -1
- package/behaviors/lolo/ui/core/atoms/std-stats.lolo +14 -10
- package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -29
- package/behaviors/registry/factory-signatures.json +9 -13
- package/behaviors/registry/ui/core/atoms/std-app-search.orb +9 -1
- package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
- package/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
- package/behaviors/registry/ui/core/atoms/std-browse.orb +377 -119
- package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
- package/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
- package/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
- package/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
- package/dist/behaviors/behaviors-embeddings.json +475 -475
- package/dist/behaviors/behaviors-registry.json +10 -12
- package/dist/behaviors/knob-embeddings.json +1 -1
- package/dist/behaviors/registry/factory-signatures.json +9 -13
- package/dist/behaviors/registry/ui/core/atoms/std-app-search.orb +9 -1
- package/dist/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
- package/dist/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
- package/dist/behaviors/registry/ui/core/atoms/std-browse.orb +377 -119
- package/dist/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
- package/dist/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
- package/dist/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
- package/dist/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
- package/dist/behaviors-embeddings.json +475 -475
- package/dist/behaviors-registry.json +10 -12
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +9 -13
- package/package.json +8 -1
|
@@ -113,7 +113,7 @@ orbital AppSearchOrbital {
|
|
|
113
113
|
type: typography
|
|
114
114
|
variant: caption
|
|
115
115
|
color: muted
|
|
116
|
-
content: "Matching
|
|
116
|
+
content: (str/concat (str/concat "Matching “" @entity.searchTerm) "”")
|
|
117
117
|
}, { type: divider }, {
|
|
118
118
|
type: data-grid
|
|
119
119
|
entity: @payload.data
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
app std-billable-hour "1.0.0"
|
|
2
2
|
"std-billable-hour — generic billable-hour timesheet primitive for legal, agency, and consulting domains. Lists entries in `browsing`; opens entry detail in `viewing_single` with Invoice / Write Off actions."
|
|
3
|
+
type ItemAction = { label : string!, event : string, navigatesTo : string, variant : string, icon : string }
|
|
4
|
+
type ColumnSpec = { key : string!, field : string, header : string, width : string, align : string, className : string, weight : string, format : string, icon : string }
|
|
3
5
|
orbital BillableHourOrbital {
|
|
6
|
+
uses TableView from "std/behaviors/ui-table-view"
|
|
7
|
+
|
|
4
8
|
type BillableHourLoaded = Event { data : [BillableHour] } "Fired when the billable-hour collection finishes loading"
|
|
5
9
|
|
|
6
10
|
type BillableHourLoadFailed = Event { error : string, code : string } "Fired when the billable-hour collection fails to load"
|
|
@@ -19,6 +23,20 @@ orbital BillableHourOrbital {
|
|
|
19
23
|
invoiceStatus : "unbilled" | "invoiced" | "paid" | "written_off" @description "The current billing status of the hour entry." @synonyms "status, billing status, payment status" = unbilled
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
trait DenseHoursTable = TableView.traits.TableViewRender -> BillableHour {
|
|
27
|
+
config {
|
|
28
|
+
columns: @config.columns
|
|
29
|
+
emptyMessage: "No time entries"
|
|
30
|
+
itemActions: @config.itemActions
|
|
31
|
+
look: @config.tableLook
|
|
32
|
+
maxInlineActions: 2
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
listens {
|
|
36
|
+
BillableHourTimesheet.BillableHourLoaded -> TableViewLoaded
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
trait BillableHourTimesheet -> BillableHour [interaction, collection] {
|
|
23
41
|
initial: loading
|
|
24
42
|
|
|
@@ -53,46 +71,11 @@ orbital BillableHourOrbital {
|
|
|
53
71
|
gap: sm
|
|
54
72
|
type: stack
|
|
55
73
|
}, { type: divider }, {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
variant: caption
|
|
62
|
-
}, {
|
|
63
|
-
label: Matter
|
|
64
|
-
name: matterId
|
|
65
|
-
variant: caption
|
|
66
|
-
}, {
|
|
67
|
-
label: Date
|
|
68
|
-
name: workDate
|
|
69
|
-
variant: caption
|
|
70
|
-
}, {
|
|
71
|
-
label: Hours
|
|
72
|
-
name: hours
|
|
73
|
-
variant: caption
|
|
74
|
-
}, {
|
|
75
|
-
label: Rate
|
|
76
|
-
name: rate
|
|
77
|
-
variant: caption
|
|
78
|
-
}, {
|
|
79
|
-
label: Status
|
|
80
|
-
name: invoiceStatus
|
|
81
|
-
variant: badge
|
|
82
|
-
}]
|
|
83
|
-
gap: sm
|
|
84
|
-
itemActions: [{
|
|
85
|
-
event: OPEN_HOURS
|
|
86
|
-
icon: arrow-right
|
|
87
|
-
label: Open
|
|
88
|
-
variant: primary
|
|
89
|
-
}, {
|
|
90
|
-
event: WRITE_OFF_HOURS
|
|
91
|
-
label: "Write Off"
|
|
92
|
-
variant: danger
|
|
93
|
-
}]
|
|
94
|
-
look: @config.tableLook
|
|
95
|
-
type: data-grid
|
|
74
|
+
children: [@trait.DenseHoursTable]
|
|
75
|
+
className: overflow-x-auto
|
|
76
|
+
direction: vertical
|
|
77
|
+
gap: none
|
|
78
|
+
type: stack
|
|
96
79
|
}]
|
|
97
80
|
direction: vertical
|
|
98
81
|
gap: md
|
|
@@ -107,6 +90,11 @@ orbital BillableHourOrbital {
|
|
|
107
90
|
}
|
|
108
91
|
|
|
109
92
|
state browsing {
|
|
93
|
+
INIT -> loading
|
|
94
|
+
(fetch BillableHour {
|
|
95
|
+
emit: { failure: BillableHourLoadFailed, success: BillableHourLoaded }
|
|
96
|
+
})
|
|
97
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
110
98
|
OPEN_HOURS -> viewing_single
|
|
111
99
|
(set @entity.id ?row.id)
|
|
112
100
|
(set @entity.workerId ?row.workerId)
|
|
@@ -398,10 +386,67 @@ orbital BillableHourOrbital {
|
|
|
398
386
|
@label "Section title"
|
|
399
387
|
@description "Heading shown above the time-entries list"
|
|
400
388
|
@tier "presentation"
|
|
401
|
-
tableLook : "dense" | "spacious" | "striped" | "borderless" | "
|
|
389
|
+
tableLook : "dense" | "spacious" | "striped" | "borderless" | "bordered" = dense
|
|
402
390
|
@label "Table look"
|
|
403
391
|
@description "Layer 2 visual treatment for the data table rendered by this atom."
|
|
404
392
|
@tier "presentation"
|
|
393
|
+
columns : [ColumnSpec] = [{
|
|
394
|
+
align: left
|
|
395
|
+
field: workerName
|
|
396
|
+
header: Worker
|
|
397
|
+
key: workerName
|
|
398
|
+
width: "minmax(8rem, 1fr)"
|
|
399
|
+
}, {
|
|
400
|
+
align: left
|
|
401
|
+
field: matterId
|
|
402
|
+
header: Matter
|
|
403
|
+
key: matterId
|
|
404
|
+
width: "8rem"
|
|
405
|
+
}, {
|
|
406
|
+
align: left
|
|
407
|
+
field: workDate
|
|
408
|
+
format: date
|
|
409
|
+
header: Date
|
|
410
|
+
key: workDate
|
|
411
|
+
width: "8rem"
|
|
412
|
+
}, {
|
|
413
|
+
align: right
|
|
414
|
+
field: hours
|
|
415
|
+
format: number
|
|
416
|
+
header: Hours
|
|
417
|
+
key: hours
|
|
418
|
+
width: "6rem"
|
|
419
|
+
}, {
|
|
420
|
+
align: right
|
|
421
|
+
field: rate
|
|
422
|
+
format: currency
|
|
423
|
+
header: Rate
|
|
424
|
+
key: rate
|
|
425
|
+
width: "7rem"
|
|
426
|
+
}, {
|
|
427
|
+
align: center
|
|
428
|
+
field: invoiceStatus
|
|
429
|
+
format: badge
|
|
430
|
+
header: Status
|
|
431
|
+
key: invoiceStatus
|
|
432
|
+
width: "8rem"
|
|
433
|
+
}]
|
|
434
|
+
@label "Table columns"
|
|
435
|
+
@description "ColumnSpec list for the dense hours ledger table."
|
|
436
|
+
@tier "presentation"
|
|
437
|
+
itemActions : [ItemAction] = [{
|
|
438
|
+
event: OPEN_HOURS
|
|
439
|
+
icon: arrow-right
|
|
440
|
+
label: Open
|
|
441
|
+
variant: ghost
|
|
442
|
+
}, {
|
|
443
|
+
event: WRITE_OFF_HOURS
|
|
444
|
+
label: "Write Off"
|
|
445
|
+
variant: danger
|
|
446
|
+
}]
|
|
447
|
+
@label "Row actions"
|
|
448
|
+
@description "Per-row actions rendered by the hours table."
|
|
449
|
+
@tier "presentation"
|
|
405
450
|
statLook : "elevated" | "flat" | "progress-backed" | "gauge" | "sparkline" = elevated
|
|
406
451
|
@label "Stat display look"
|
|
407
452
|
@description "Layer 2 visual treatment for stat / KPI cards."
|
|
@@ -28,6 +28,7 @@ orbital BoardOrbital {
|
|
|
28
28
|
currentDescription : string @intrinsic = ""
|
|
29
29
|
currentStage : string @intrinsic = ""
|
|
30
30
|
currentNotes : string @intrinsic = ""
|
|
31
|
+
currentRow : object @intrinsic = {}
|
|
31
32
|
errorMessage : string @intrinsic = ""
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -97,6 +98,7 @@ orbital BoardOrbital {
|
|
|
97
98
|
(set @entity.currentDescription ?description)
|
|
98
99
|
(set @entity.currentStage ?stage)
|
|
99
100
|
(set @entity.currentNotes ?row.notes)
|
|
101
|
+
(set @entity.currentRow ?row)
|
|
100
102
|
(render-ui main {
|
|
101
103
|
children: [{
|
|
102
104
|
align: center
|
|
@@ -164,20 +166,7 @@ orbital BoardOrbital {
|
|
|
164
166
|
})
|
|
165
167
|
type: data-list
|
|
166
168
|
variant: default
|
|
167
|
-
},
|
|
168
|
-
align: center
|
|
169
|
-
children: [{
|
|
170
|
-
action: DELETE_CARD
|
|
171
|
-
actionPayload: { id: @entity.currentId }
|
|
172
|
-
icon: trash-2
|
|
173
|
-
label: Delete
|
|
174
|
-
type: button
|
|
175
|
-
variant: danger
|
|
176
|
-
}]
|
|
177
|
-
direction: horizontal
|
|
178
|
-
gap: sm
|
|
179
|
-
type: stack
|
|
180
|
-
}]
|
|
169
|
+
}, @config.cardActionsContent]
|
|
181
170
|
direction: vertical
|
|
182
171
|
gap: md
|
|
183
172
|
type: stack
|
|
@@ -341,6 +330,13 @@ orbital BoardOrbital {
|
|
|
341
330
|
@description "Signals removal of a card from the board."
|
|
342
331
|
@synonyms "remove, delete, discard"
|
|
343
332
|
@tier "presentation"
|
|
333
|
+
EDIT_CARD -> external {
|
|
334
|
+
id : string!
|
|
335
|
+
row : object
|
|
336
|
+
}
|
|
337
|
+
@description "Signals a request to edit the currently open card. The board renders no edit form itself — consumers override cardActionsContent with an Edit button and wire this emit to their edit modal/form trait."
|
|
338
|
+
@synonyms "edit, modify, update, change"
|
|
339
|
+
@tier "domain"
|
|
344
340
|
BoardItemsLoaded {
|
|
345
341
|
data : [ObjectSpec]
|
|
346
342
|
}
|
|
@@ -516,6 +512,23 @@ orbital BoardOrbital {
|
|
|
516
512
|
@label "Board look"
|
|
517
513
|
@description "Layer 3 body layout: default N-column data-grid ('columns'), or a Trello-style wide-column kanban with sticky column headers ('kanban-classic')."
|
|
518
514
|
@tier "presentation"
|
|
515
|
+
cardActionsContent : render-ui = {
|
|
516
|
+
align: center
|
|
517
|
+
children: [{
|
|
518
|
+
action: DELETE_CARD
|
|
519
|
+
actionPayload: { id: @entity.currentId }
|
|
520
|
+
icon: trash-2
|
|
521
|
+
label: Delete
|
|
522
|
+
type: button
|
|
523
|
+
variant: danger
|
|
524
|
+
}]
|
|
525
|
+
direction: horizontal
|
|
526
|
+
gap: sm
|
|
527
|
+
type: stack
|
|
528
|
+
}
|
|
529
|
+
@label "Card panel actions"
|
|
530
|
+
@description "Actions row of the open-card panel. Default is the Delete button only. Override to add consumer-wired actions — e.g. an Edit button with action: EDIT_CARD and actionPayload: { id: @entity.currentId, row: @entity.currentRow }, listened to by the organism's edit modal. Never leave a button here without a listener."
|
|
531
|
+
@tier "presentation"
|
|
519
532
|
bodyContent : render-ui = {
|
|
520
533
|
children: [{
|
|
521
534
|
align: center
|
|
@@ -702,7 +715,7 @@ orbital BoardOrbital {
|
|
|
702
715
|
type: stack
|
|
703
716
|
}]
|
|
704
717
|
className: "cursor-grab hover:shadow-lg transition-shadow"
|
|
705
|
-
look:
|
|
718
|
+
look: @config.cardLook
|
|
706
719
|
padding: sm
|
|
707
720
|
type: card
|
|
708
721
|
})
|
|
@@ -734,7 +747,7 @@ orbital BoardOrbital {
|
|
|
734
747
|
type: stack
|
|
735
748
|
}
|
|
736
749
|
@label "Kanban classic body content tree"
|
|
737
|
-
@description "Render-ui SExpr for boardLook='kanban-classic' — Trello-style wide elevated columns with sticky headers, dense cards with a status badge
|
|
750
|
+
@description "Render-ui SExpr for boardLook='kanban-classic' — Trello-style wide elevated columns with sticky headers, dense cards with a status badge; per-item cards honor config.cardLook (the column wrapper stays elevated — it is column chrome, not a card)."
|
|
738
751
|
@tier "internal"
|
|
739
752
|
}
|
|
740
753
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
app std-browse "2.3.0"
|
|
2
|
-
"std-browse as a Function — single primitive: data-grid. Owns the fetch + REFETCH (query/filter/page) state machine. Consumers configure fields/imageField/itemActions/cols/gap/variant; the atom renders the grid. Each `fields[]` entry accepts `{ name, label, variant }` plus optional `referenceEntity` + `displayField` to render a foreign-key column as the joined entity's display value (data-grid resolves @list[referenceEntity] at render time when both keys are present). `browseLook` (table default / dense / feed / gallery) selects one of
|
|
2
|
+
"std-browse as a Function — single primitive: data-grid. Owns the fetch + REFETCH (query/filter/page) state machine. Consumers configure fields/imageField/itemActions/cols/gap/variant; the atom renders the grid. Each `fields[]` entry accepts `{ name, label, variant }` plus optional `referenceEntity` + `displayField` to render a foreign-key column as the joined entity's display value (data-grid resolves @list[referenceEntity] at render time when both keys are present). `browseLook` (table default / dense / feed / gallery / master-detail / triage) selects one of six render bodies; dense/feed/gallery were folded in from std-browse-dense/-feed/-gallery (Phase 5.B); master-detail (split record view driven by the guarded VIEW/CLOSE self-transitions) and triage (inbox rows) were folded in 2026-07-26. Consumers retarget by setting browseLook."
|
|
3
3
|
type FieldSpec = { name : string!, label : string, variant : string, icon : string, format : string, key : string, header : string }
|
|
4
4
|
type ItemAction = { label : string!, event : string, navigatesTo : string, variant : string, icon : string }
|
|
5
5
|
type ColumnSpec = { key : string!, field : string, header : string, width : string, align : string, className : string, weight : string, format : string, icon : string }
|
|
@@ -20,6 +20,7 @@ orbital BrowseItemOrbital {
|
|
|
20
20
|
uses Typography from "std/behaviors/ui-typography"
|
|
21
21
|
uses DataGrid from "std/behaviors/ui-data-grid"
|
|
22
22
|
uses TableView from "std/behaviors/ui-table-view"
|
|
23
|
+
uses DataList from "std/behaviors/ui-data-list"
|
|
23
24
|
|
|
24
25
|
trait DataGrid1 = DataGrid.traits.DataGridRender -> BrowseItem {
|
|
25
26
|
config {
|
|
@@ -28,6 +29,7 @@ orbital BrowseItemOrbital {
|
|
|
28
29
|
gap: @config.gap
|
|
29
30
|
imageField: @config.imageField
|
|
30
31
|
itemActions: @config.itemActions
|
|
32
|
+
maxInlineActions: @config.maxInlineActions
|
|
31
33
|
pageSize: @config.displayPageSize
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -44,7 +46,7 @@ orbital BrowseItemOrbital {
|
|
|
44
46
|
look: dense
|
|
45
47
|
maxInlineActions: @config.maxInlineActions
|
|
46
48
|
pageSize: @config.displayPageSize
|
|
47
|
-
selectable:
|
|
49
|
+
selectable: @config.selectable
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
listens {
|
|
@@ -52,6 +54,20 @@ orbital BrowseItemOrbital {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
trait MasterListView = DataList.traits.DataListRender -> BrowseItem {
|
|
58
|
+
config {
|
|
59
|
+
fields: @config.fields
|
|
60
|
+
itemActions: @config.itemActions
|
|
61
|
+
itemClickEvent: VIEW
|
|
62
|
+
maxInlineActions: @config.maxInlineActions
|
|
63
|
+
variant: compact
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
listens {
|
|
67
|
+
BrowseItemBrowse.BrowseItemLoaded -> DataListLoaded
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
trait BrowseItemBrowse -> @rebindable BrowseItem [interaction, collection] {
|
|
56
72
|
initial: loading
|
|
57
73
|
|
|
@@ -62,7 +78,7 @@ orbital BrowseItemOrbital {
|
|
|
62
78
|
limit: @config.pageSize
|
|
63
79
|
offset: 0
|
|
64
80
|
})
|
|
65
|
-
(render-ui main {
|
|
81
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
66
82
|
BrowseItemLoaded -> browsing
|
|
67
83
|
(if ["=", @config.browseLook, dense]
|
|
68
84
|
(render-ui main @config.denseBodyContent)
|
|
@@ -70,9 +86,13 @@ orbital BrowseItemOrbital {
|
|
|
70
86
|
(render-ui main @config.feedBodyContent)
|
|
71
87
|
(if ["=", @config.browseLook, gallery]
|
|
72
88
|
(render-ui main @config.galleryBodyContent)
|
|
73
|
-
(
|
|
89
|
+
(if ["=", @config.browseLook, master-detail]
|
|
90
|
+
(render-ui main @config.masterDetailBodyContent)
|
|
91
|
+
(if ["=", @config.browseLook, triage]
|
|
92
|
+
(render-ui main @config.triageBodyContent)
|
|
93
|
+
(render-ui main @config.bodyContent))))))
|
|
74
94
|
BrowseItemLoadFailed -> error
|
|
75
|
-
(render-ui main {
|
|
95
|
+
(render-ui main { message: ?error, title: "Couldn't load this list", type: alert, variant: error })
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
state browsing {
|
|
@@ -82,7 +102,7 @@ orbital BrowseItemOrbital {
|
|
|
82
102
|
limit: @config.pageSize
|
|
83
103
|
offset: 0
|
|
84
104
|
})
|
|
85
|
-
(render-ui main {
|
|
105
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
86
106
|
;; REFETCH_* stays in `browsing` and DOES NOT replace the data-grid
|
|
87
107
|
;; with a spinner. Replacing during search-as-you-type unmounts the
|
|
88
108
|
;; grid every keystroke and causes a visible flicker. The fetch fires
|
|
@@ -109,6 +129,14 @@ orbital BrowseItemOrbital {
|
|
|
109
129
|
limit: @config.pageSize
|
|
110
130
|
offset: ["*", ["-", ?page, 1], @config.pageSize]
|
|
111
131
|
})
|
|
132
|
+
;; master-detail only: opening a row re-renders the split view with the
|
|
133
|
+
;; clicked row's detail (payload ?row feeds the panel). Guard keeps every
|
|
134
|
+
;; other look's machine untouched — VIEW still reaches external listeners
|
|
135
|
+
;; either way, and no effect here re-emits the trigger.
|
|
136
|
+
VIEW -> browsing when ["=", @config.browseLook, "master-detail"]
|
|
137
|
+
(render-ui main @config.masterDetailRecordBodyContent)
|
|
138
|
+
CLOSE -> browsing when ["=", @config.browseLook, "master-detail"]
|
|
139
|
+
(render-ui main @config.masterDetailBodyContent)
|
|
112
140
|
BrowseItemLoaded -> browsing
|
|
113
141
|
(if ["=", @config.browseLook, dense]
|
|
114
142
|
(render-ui main @config.denseBodyContent)
|
|
@@ -116,7 +144,11 @@ orbital BrowseItemOrbital {
|
|
|
116
144
|
(render-ui main @config.feedBodyContent)
|
|
117
145
|
(if ["=", @config.browseLook, gallery]
|
|
118
146
|
(render-ui main @config.galleryBodyContent)
|
|
119
|
-
(
|
|
147
|
+
(if ["=", @config.browseLook, master-detail]
|
|
148
|
+
(render-ui main @config.masterDetailBodyContent)
|
|
149
|
+
(if ["=", @config.browseLook, triage]
|
|
150
|
+
(render-ui main @config.triageBodyContent)
|
|
151
|
+
(render-ui main @config.bodyContent))))))
|
|
120
152
|
}
|
|
121
153
|
|
|
122
154
|
state error {
|
|
@@ -126,7 +158,7 @@ orbital BrowseItemOrbital {
|
|
|
126
158
|
limit: @config.pageSize
|
|
127
159
|
offset: 0
|
|
128
160
|
})
|
|
129
|
-
(render-ui main {
|
|
161
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
130
162
|
}
|
|
131
163
|
|
|
132
164
|
emits {
|
|
@@ -300,11 +332,16 @@ orbital BrowseItemOrbital {
|
|
|
300
332
|
@description "Dropdown filter facets by field. Each fires a refetch when the user changes the selection. Empty = no filter bar."
|
|
301
333
|
@synonyms "filter bar, faceted filters, field filters, column filters, dropdowns"
|
|
302
334
|
@tier "presentation"
|
|
303
|
-
maxInlineActions : number =
|
|
335
|
+
maxInlineActions : number = 3
|
|
304
336
|
@label "How many row actions to show before collapsing?"
|
|
305
337
|
@description "Actions beyond this count collapse into a '⋯' overflow menu. Set 2–3 for dense tables."
|
|
306
338
|
@synonyms "inline actions limit, overflow threshold, max buttons per row"
|
|
307
339
|
@tier "presentation"
|
|
340
|
+
selectable : boolean = false
|
|
341
|
+
@label "Show row selection checkboxes?"
|
|
342
|
+
@description "Adds a checkbox column with select-all to the dense table. Enable only when the surface offers bulk actions — checkboxes with nothing to act on are a dead affordance."
|
|
343
|
+
@synonyms "row checkboxes, multi-select, bulk select, selection column"
|
|
344
|
+
@tier "presentation"
|
|
308
345
|
bodySearch : boolean = true
|
|
309
346
|
@label "Show the list's own search box?"
|
|
310
347
|
@description "The dense/feed/gallery looks embed their own search box above the rows. Set false when the page already provides a search affordance (a toolbar Search molecule), so the surface shows one search box instead of two."
|
|
@@ -319,9 +356,14 @@ orbital BrowseItemOrbital {
|
|
|
319
356
|
@label "Render pattern"
|
|
320
357
|
@description "UI pattern used to render the collection. Used by the default bodyContent tree; Layer 3 variants override bodyContent directly instead and may ignore this knob."
|
|
321
358
|
@tier "internal"
|
|
322
|
-
browseLook : "table" | "dense" | "feed" | "gallery" = table
|
|
359
|
+
browseLook : "table" | "dense" | "feed" | "gallery" | "master-detail" | "triage" = table
|
|
323
360
|
@label "Browse look"
|
|
324
|
-
@description "Layer 2 visual treatment for the whole browse surface. table = the default data-grid (bodyContent); dense = spreadsheet-style
|
|
361
|
+
@description "Layer 2 visual treatment for the whole browse surface. table = the default data-grid (bodyContent); dense = spreadsheet-style table with toolbar search (denseBodyContent); feed = chronological card stream (feedBodyContent); gallery = image-first responsive card grid (galleryBodyContent); master-detail = record-work split view — dense list left, clicked record's detail right (masterDetailBodyContent); triage = inbox-register compact rows for queue work (triageBodyContent). Selecting a non-table look swaps which body tree renders after rows load; any body may still be overridden directly at the call site for a one-off layout."
|
|
362
|
+
@tier "presentation"
|
|
363
|
+
detailFields : [string] = [name, description, status, createdAt]
|
|
364
|
+
@label "Which fields appear in the master-detail record pane?"
|
|
365
|
+
@description "Field names shown in the detail panel when browseLook = master-detail; the first entry doubles as the pane title. Consumers overriding columns/fields should override this list to match their entity."
|
|
366
|
+
@synonyms "detail columns, record fields, detail pane fields"
|
|
325
367
|
@tier "presentation"
|
|
326
368
|
bodyContent : render-ui = {
|
|
327
369
|
children: [@trait.DataGrid1]
|
|
@@ -334,13 +376,13 @@ orbital BrowseItemOrbital {
|
|
|
334
376
|
denseBodyContent : render-ui = {
|
|
335
377
|
children: [(if @config.bodySearch {
|
|
336
378
|
children: [{
|
|
337
|
-
className: w-full
|
|
379
|
+
className: "w-full max-w-md"
|
|
338
380
|
clearable: true
|
|
339
381
|
event: REFETCH_QUERY
|
|
340
382
|
placeholder: @config.searchPlaceholder
|
|
341
383
|
type: search-input
|
|
342
384
|
}]
|
|
343
|
-
className:
|
|
385
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
344
386
|
direction: horizontal
|
|
345
387
|
gap: sm
|
|
346
388
|
type: stack
|
|
@@ -350,13 +392,14 @@ orbital BrowseItemOrbital {
|
|
|
350
392
|
type: stack
|
|
351
393
|
}), {
|
|
352
394
|
children: [@trait.DenseTableView]
|
|
353
|
-
className:
|
|
395
|
+
className: overflow-x-auto
|
|
354
396
|
direction: vertical
|
|
355
397
|
gap: none
|
|
356
398
|
type: stack
|
|
357
399
|
}]
|
|
400
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
358
401
|
direction: vertical
|
|
359
|
-
gap:
|
|
402
|
+
gap: none
|
|
360
403
|
type: stack
|
|
361
404
|
}
|
|
362
405
|
@label "Dense look body"
|
|
@@ -448,6 +491,7 @@ orbital BrowseItemOrbital {
|
|
|
448
491
|
}]
|
|
449
492
|
className: "rounded-lg bg-[var(--color-card)] border border-[var(--color-border)] shadow-sm transition-all duration-normal hover:shadow-main hover:border-[var(--color-border-strong)] hover:-translate-y-px"
|
|
450
493
|
look: flat-bordered
|
|
494
|
+
padding: none
|
|
451
495
|
type: card
|
|
452
496
|
})
|
|
453
497
|
type: data-list
|
|
@@ -471,7 +515,7 @@ orbital BrowseItemOrbital {
|
|
|
471
515
|
placeholder: @config.searchPlaceholder
|
|
472
516
|
type: search-input
|
|
473
517
|
}]
|
|
474
|
-
className: "sticky top-0 z-10
|
|
518
|
+
className: "sticky top-0 z-10 py-3 bg-[var(--color-surface)]/95 backdrop-blur"
|
|
475
519
|
direction: horizontal
|
|
476
520
|
gap: sm
|
|
477
521
|
type: stack
|
|
@@ -482,76 +526,12 @@ orbital BrowseItemOrbital {
|
|
|
482
526
|
}), {
|
|
483
527
|
className: w-full
|
|
484
528
|
entity: ?data
|
|
485
|
-
fields:
|
|
529
|
+
fields: @config.fields
|
|
486
530
|
gap: lg
|
|
531
|
+
imageField: @config.imageField
|
|
487
532
|
itemActions: @config.itemActions
|
|
488
533
|
maxInlineActions: @config.maxInlineActions
|
|
489
534
|
minCardWidth: 260
|
|
490
|
-
renderItem: (fn item {
|
|
491
|
-
children: [{
|
|
492
|
-
align: center
|
|
493
|
-
children: [{
|
|
494
|
-
className: "text-muted-foreground opacity-50 transition-transform duration-normal group-hover:scale-105"
|
|
495
|
-
name: image
|
|
496
|
-
size: lg
|
|
497
|
-
type: icon
|
|
498
|
-
}, {
|
|
499
|
-
className: "px-2 text-center truncate max-w-full"
|
|
500
|
-
color: muted
|
|
501
|
-
content: @config.imageLabelBinding
|
|
502
|
-
type: typography
|
|
503
|
-
variant: caption
|
|
504
|
-
}, {
|
|
505
|
-
align: center
|
|
506
|
-
children: [{
|
|
507
|
-
label: @config.priceBinding
|
|
508
|
-
type: badge
|
|
509
|
-
variant: primary
|
|
510
|
-
}]
|
|
511
|
-
className: "absolute top-2 right-2"
|
|
512
|
-
direction: horizontal
|
|
513
|
-
gap: xs
|
|
514
|
-
type: stack
|
|
515
|
-
}, {
|
|
516
|
-
align: center
|
|
517
|
-
children: [{
|
|
518
|
-
label: @config.badgeBinding
|
|
519
|
-
type: badge
|
|
520
|
-
variant: neutral
|
|
521
|
-
}]
|
|
522
|
-
className: "absolute top-2 left-2"
|
|
523
|
-
direction: horizontal
|
|
524
|
-
gap: xs
|
|
525
|
-
type: stack
|
|
526
|
-
}]
|
|
527
|
-
className: "relative aspect-square w-full bg-muted overflow-hidden"
|
|
528
|
-
direction: vertical
|
|
529
|
-
gap: xs
|
|
530
|
-
justify: center
|
|
531
|
-
type: stack
|
|
532
|
-
}, {
|
|
533
|
-
children: [{
|
|
534
|
-
className: truncate
|
|
535
|
-
content: @config.titleBinding
|
|
536
|
-
type: typography
|
|
537
|
-
variant: h4
|
|
538
|
-
}, {
|
|
539
|
-
className: line-clamp-2
|
|
540
|
-
color: muted
|
|
541
|
-
content: @config.descriptionBinding
|
|
542
|
-
type: typography
|
|
543
|
-
variant: caption
|
|
544
|
-
}]
|
|
545
|
-
className: p-card-md
|
|
546
|
-
direction: vertical
|
|
547
|
-
gap: xs
|
|
548
|
-
type: stack
|
|
549
|
-
}]
|
|
550
|
-
className: "group rounded-lg overflow-hidden bg-[var(--color-card)] border border-[var(--color-border)] shadow-sm hover:shadow-hover hover:-translate-y-0.5 transition-all duration-normal cursor-pointer"
|
|
551
|
-
direction: vertical
|
|
552
|
-
gap: sm
|
|
553
|
-
type: stack
|
|
554
|
-
})
|
|
555
535
|
type: data-grid
|
|
556
536
|
}]
|
|
557
537
|
className: w-full
|
|
@@ -560,7 +540,131 @@ orbital BrowseItemOrbital {
|
|
|
560
540
|
type: stack
|
|
561
541
|
}
|
|
562
542
|
@label "Gallery look body"
|
|
563
|
-
@description "Render-ui SExpr rendered after rows load when browseLook = gallery. Image-first responsive card grid
|
|
543
|
+
@description "Render-ui SExpr rendered after rows load when browseLook = gallery. Image-first responsive card grid (auto-fit minCardWidth) on the data-grid fields path: imageField renders the card photo when set (falsy-safe when empty), fields drive title/badges/meta. One tree, no renderItem lambda — lambdas do not survive inside (if) arms (2026-07-27)."
|
|
544
|
+
@tier "internal"
|
|
545
|
+
triageBodyContent : render-ui = {
|
|
546
|
+
children: [(if @config.bodySearch {
|
|
547
|
+
children: [{
|
|
548
|
+
className: "w-full max-w-md"
|
|
549
|
+
clearable: true
|
|
550
|
+
event: REFETCH_QUERY
|
|
551
|
+
placeholder: @config.searchPlaceholder
|
|
552
|
+
type: search-input
|
|
553
|
+
}]
|
|
554
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
555
|
+
direction: horizontal
|
|
556
|
+
gap: sm
|
|
557
|
+
type: stack
|
|
558
|
+
} {
|
|
559
|
+
children: []
|
|
560
|
+
gap: none
|
|
561
|
+
type: stack
|
|
562
|
+
}), {
|
|
563
|
+
children: [{
|
|
564
|
+
entity: ?data
|
|
565
|
+
fields: @config.fields
|
|
566
|
+
itemActions: @config.itemActions
|
|
567
|
+
maxInlineActions: @config.maxInlineActions
|
|
568
|
+
type: data-list
|
|
569
|
+
variant: compact
|
|
570
|
+
}]
|
|
571
|
+
className: overflow-x-auto
|
|
572
|
+
direction: vertical
|
|
573
|
+
gap: none
|
|
574
|
+
type: stack
|
|
575
|
+
}]
|
|
576
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
577
|
+
direction: vertical
|
|
578
|
+
gap: none
|
|
579
|
+
type: stack
|
|
580
|
+
}
|
|
581
|
+
@label "Triage look body"
|
|
582
|
+
@description "Render-ui SExpr rendered after rows load when browseLook = triage. Inbox-register queue: one surface with a toolbar search and compact list rows (title + badges + inline meta) built for keyboard-pace queue work; row actions collapse per maxInlineActions. Composes fields/itemActions/searchPlaceholder/bodySearch."
|
|
583
|
+
@tier "internal"
|
|
584
|
+
masterDetailBodyContent : render-ui = {
|
|
585
|
+
children: [{
|
|
586
|
+
children: [{
|
|
587
|
+
className: "w-full max-w-md"
|
|
588
|
+
clearable: true
|
|
589
|
+
event: REFETCH_QUERY
|
|
590
|
+
placeholder: @config.searchPlaceholder
|
|
591
|
+
type: search-input
|
|
592
|
+
}]
|
|
593
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
594
|
+
direction: horizontal
|
|
595
|
+
gap: sm
|
|
596
|
+
type: stack
|
|
597
|
+
}, {
|
|
598
|
+
detail: {
|
|
599
|
+
children: []
|
|
600
|
+
gap: none
|
|
601
|
+
type: stack
|
|
602
|
+
}
|
|
603
|
+
emptyDetail: {
|
|
604
|
+
description: "Select a record from the list to see its details."
|
|
605
|
+
icon: mouse-pointer-click
|
|
606
|
+
title: "Nothing selected"
|
|
607
|
+
type: empty-state
|
|
608
|
+
}
|
|
609
|
+
hasSelection: false
|
|
610
|
+
master: {
|
|
611
|
+
children: [@trait.MasterListView]
|
|
612
|
+
className: "overflow-y-auto"
|
|
613
|
+
direction: vertical
|
|
614
|
+
gap: none
|
|
615
|
+
type: stack
|
|
616
|
+
}
|
|
617
|
+
type: master-detail-layout
|
|
618
|
+
}]
|
|
619
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
620
|
+
direction: vertical
|
|
621
|
+
gap: none
|
|
622
|
+
type: stack
|
|
623
|
+
}
|
|
624
|
+
@label "Master-detail look body (no selection)"
|
|
625
|
+
@description "Render-ui SExpr rendered after rows load when browseLook = master-detail: record-work split view — toolbar search, dense list left, empty-state pane right until a row is opened. Opening a row renders masterDetailRecordBodyContent. The toolbar is always present in this look (bodySearch does not apply)."
|
|
626
|
+
@tier "internal"
|
|
627
|
+
masterDetailRecordBodyContent : render-ui = {
|
|
628
|
+
children: [{
|
|
629
|
+
children: [{
|
|
630
|
+
className: "w-full max-w-md"
|
|
631
|
+
clearable: true
|
|
632
|
+
event: REFETCH_QUERY
|
|
633
|
+
placeholder: @config.searchPlaceholder
|
|
634
|
+
type: search-input
|
|
635
|
+
}]
|
|
636
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
637
|
+
direction: horizontal
|
|
638
|
+
gap: sm
|
|
639
|
+
type: stack
|
|
640
|
+
}, {
|
|
641
|
+
detail: {
|
|
642
|
+
actions: [{
|
|
643
|
+
event: CLOSE
|
|
644
|
+
label: Close
|
|
645
|
+
}]
|
|
646
|
+
fieldNames: @config.detailFields
|
|
647
|
+
initialData: ?row
|
|
648
|
+
showActions: false
|
|
649
|
+
type: detail-panel
|
|
650
|
+
}
|
|
651
|
+
hasSelection: true
|
|
652
|
+
master: {
|
|
653
|
+
children: [@trait.MasterListView]
|
|
654
|
+
className: "overflow-y-auto"
|
|
655
|
+
direction: vertical
|
|
656
|
+
gap: none
|
|
657
|
+
type: stack
|
|
658
|
+
}
|
|
659
|
+
type: master-detail-layout
|
|
660
|
+
}]
|
|
661
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
662
|
+
direction: vertical
|
|
663
|
+
gap: none
|
|
664
|
+
type: stack
|
|
665
|
+
}
|
|
666
|
+
@label "Master-detail look body (record open)"
|
|
667
|
+
@description "Render-ui SExpr rendered by the VIEW transition when browseLook = master-detail: the same split view with the clicked record's detail panel on the right, fed by the VIEW payload row and detailFields."
|
|
564
668
|
@tier "internal"
|
|
565
669
|
}
|
|
566
670
|
}
|