@asteby/metacore-runtime-react 12.0.0 → 13.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 +20 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +6 -2
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +7 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/model-action-toolbar.d.ts +27 -0
- package/dist/model-action-toolbar.d.ts.map +1 -0
- package/dist/model-action-toolbar.js +88 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/dynamic-crud-page.tsx +11 -1
- package/src/dynamic-table.tsx +7 -1
- package/src/index.ts +6 -0
- package/src/model-action-toolbar.tsx +154 -0
- package/src/types.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 13.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7ea7caa: Acciones con `placement` (`row` | `table` | `create`) y nuevo primitivo `<ModelActionToolbar>`.
|
|
8
|
+
|
|
9
|
+
`ActionMetadata`/`ActionDefinition` ganan `placement`, espejando `manifest/v3` Action.placement del kernel (v0.30.0):
|
|
10
|
+
- `row` (default) — acción por fila dentro de `<DynamicTable>` (comportamiento actual).
|
|
11
|
+
- `table` — botón en la toolbar de la página, sin contexto de record.
|
|
12
|
+
- `create` — botón en la toolbar que **reemplaza** el botón "crear" genérico, para addons que traen una experiencia de creación custom (p.ej. un asiento contable con líneas débito/crédito).
|
|
13
|
+
|
|
14
|
+
`<ModelActionToolbar>` (+ hook `useModelActions`) es el primitivo genérico que renderiza esos triggers de nivel página y monta el `ActionModalDispatcher` (record vacío para `create`). Resuelve tanto modales federados custom (vía el action registry) como el form declarativo genérico. `DynamicCRUDPage` lo consume internamente y suprime su botón crear cuando existe una acción `create`; `DynamicTable` excluye los placements `table`/`create` de la columna de acciones por fila. Los hosts ya no reimplementan el plumbing de botones de acción — montan `<ModelActionToolbar>` y listo.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [7ea7caa]
|
|
19
|
+
- Updated dependencies [3b40ed5]
|
|
20
|
+
- @asteby/metacore-sdk@3.1.0
|
|
21
|
+
- @asteby/metacore-ui@2.1.0
|
|
22
|
+
|
|
3
23
|
## 12.0.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAYd,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CA+L1D"}
|
|
@@ -32,6 +32,7 @@ import { DynamicRecordDialog } from './dialogs/dynamic-record';
|
|
|
32
32
|
import { ExportDialog } from './dialogs/export';
|
|
33
33
|
import { ImportDialog } from './dialogs/import';
|
|
34
34
|
import { getModelExtension } from './model-extension-registry';
|
|
35
|
+
import { ModelActionToolbar } from './model-action-toolbar';
|
|
35
36
|
const defaultStrings = {
|
|
36
37
|
refresh: 'Refresh',
|
|
37
38
|
export: 'Export',
|
|
@@ -84,7 +85,10 @@ export function DynamicCRUDPage(props) {
|
|
|
84
85
|
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
85
86
|
}, [title]);
|
|
86
87
|
const enableCRUD = metadata?.enableCRUDActions ?? false;
|
|
87
|
-
|
|
88
|
+
// A "create"-placement action ships a custom create experience — it
|
|
89
|
+
// replaces the generic create button (the ModelActionToolbar renders it).
|
|
90
|
+
const hasCreateAction = metadata?.actions?.some((a) => a.placement === 'create') ?? false;
|
|
91
|
+
const effectiveHideCreate = hideCreate || ext?.hideCreate || hasCreateAction;
|
|
88
92
|
const effectiveHideExport = hideExport || ext?.hideExport;
|
|
89
93
|
const effectiveHideImport = hideImport || ext?.hideImport;
|
|
90
94
|
// Refresh defaults to hidden in the page header — <DynamicTable> ships
|
|
@@ -106,5 +110,5 @@ export function DynamicCRUDPage(props) {
|
|
|
106
110
|
const titleCls = classes?.title ?? 'text-2xl font-bold tracking-tight';
|
|
107
111
|
const toolbarCls = classes?.toolbar ?? 'flex items-center gap-2';
|
|
108
112
|
const tableWrapperCls = classes?.tableWrapper ?? 'flex-1 min-h-0';
|
|
109
|
-
return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, showCreate && (_jsxs("button", { type: 'button', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey }, model) })] }), showCreate && (_jsx(DynamicRecordDialog, { open: openCreate, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
|
|
113
|
+
return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, _jsx(ModelActionToolbar, { model: model, endpoint: dataEndpoint, actions: metadata?.actions, onChange: handleRefresh }), showCreate && (_jsxs("button", { type: 'button', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey }, model) })] }), showCreate && (_jsx(DynamicRecordDialog, { open: openCreate, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
|
|
110
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CAqtBnB"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -532,7 +532,13 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
|
|
|
532
532
|
const columns = useMemo(() => {
|
|
533
533
|
if (!metadata)
|
|
534
534
|
return [];
|
|
535
|
-
|
|
535
|
+
// Row-action column only renders per-row actions. Table-level placements
|
|
536
|
+
// ("table"/"create") are surfaced by <ModelActionToolbar> at the page
|
|
537
|
+
// level, so strip them here to avoid a meaningless per-row button.
|
|
538
|
+
const rowMetadata = metadata.actions?.some((a) => a.placement === 'table' || a.placement === 'create')
|
|
539
|
+
? { ...metadata, actions: metadata.actions.filter((a) => !a.placement || a.placement === 'row') }
|
|
540
|
+
: metadata;
|
|
541
|
+
const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs);
|
|
536
542
|
const filteredBase = baseColumns.filter((col) => !hiddenColumns.includes(col.id));
|
|
537
543
|
const actionsCol = filteredBase.find((c) => c.id === 'actions');
|
|
538
544
|
const otherCols = filteredBase.filter((c) => c.id !== 'actions');
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './options-context';
|
|
|
3
3
|
export * from './dynamic-table';
|
|
4
4
|
export * from './dynamic-form';
|
|
5
5
|
export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
|
|
6
|
+
export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
|
|
6
7
|
export * from './addon-loader';
|
|
7
8
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
|
|
8
9
|
export * from './slot';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './options-context';
|
|
|
8
8
|
export * from './dynamic-table';
|
|
9
9
|
export * from './dynamic-form';
|
|
10
10
|
export { ActionModalDispatcher, } from './action-modal-dispatcher';
|
|
11
|
+
export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
|
|
11
12
|
export * from './addon-loader';
|
|
12
13
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
|
|
13
14
|
export * from './slot';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ActionDefinition } from './types';
|
|
2
|
+
export type ActionPlacement = 'row' | 'table' | 'create';
|
|
3
|
+
export interface ModelActionToolbarProps {
|
|
4
|
+
/** Model key as registered on the backend (e.g. "JournalEntry"). */
|
|
5
|
+
model: string;
|
|
6
|
+
/** Data endpoint passed to the dispatcher. Defaults to `/data/<model>/me`. */
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Pre-fetched action definitions. When omitted the toolbar reads them from
|
|
10
|
+
* the metadata cache, falling back to `/metadata/table/<model>`. Pass this
|
|
11
|
+
* when the host page already holds the metadata to avoid a second fetch.
|
|
12
|
+
*/
|
|
13
|
+
actions?: ActionDefinition[];
|
|
14
|
+
/** Which placements to render. Defaults to `['table', 'create']`. */
|
|
15
|
+
placements?: ActionPlacement[];
|
|
16
|
+
/** Fired after an action's modal reports success. */
|
|
17
|
+
onChange?: () => void;
|
|
18
|
+
/** Extra classes on the button row container. */
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the model's actions matching the requested placements. Reads from the
|
|
23
|
+
* `actions` prop when provided, else the metadata cache, else fetches once.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useModelActions(model: string, placements?: ActionPlacement[], provided?: ActionDefinition[]): ActionDefinition[];
|
|
26
|
+
export declare function ModelActionToolbar({ model, endpoint, actions, placements, onChange, className, }: ModelActionToolbarProps): import("react/jsx-runtime").JSX.Element | null;
|
|
27
|
+
//# sourceMappingURL=model-action-toolbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-action-toolbar.d.ts","sourceRoot":"","sources":["../src/model-action-toolbar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,gBAAgB,EAAiC,MAAM,SAAS,CAAA;AAE9E,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACpC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAmBD;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,eAAe,EAAuB,EAClD,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAC9B,gBAAgB,EAAE,CA2BpB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,UAA+B,EAC/B,QAAQ,EACR,SAAS,GACZ,EAAE,uBAAuB,kDA4CzB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// ModelActionToolbar — renders page-level (toolbar) triggers for a model's
|
|
3
|
+
// declarative actions and owns the modal dispatch for them.
|
|
4
|
+
//
|
|
5
|
+
// A model's actions carry a `placement` hint (see manifest/v3 Action.placement):
|
|
6
|
+
// "row" (default) — rendered per-row inside <DynamicTable>'s action column.
|
|
7
|
+
// "table" — a plain toolbar button (no record context).
|
|
8
|
+
// "create" — a primary toolbar button that replaces the generic
|
|
9
|
+
// "create" button, for addons shipping a custom create
|
|
10
|
+
// experience (e.g. a journal entry with debit/credit lines).
|
|
11
|
+
//
|
|
12
|
+
// This component renders the buttons for the placements it's asked to surface
|
|
13
|
+
// (default: table + create) and mounts the <ActionModalDispatcher>, which
|
|
14
|
+
// resolves either a custom federated modal (registered via the action registry)
|
|
15
|
+
// or the generic declarative form. For create-style actions there is no record
|
|
16
|
+
// yet, so the dispatcher receives an empty record `{}`.
|
|
17
|
+
//
|
|
18
|
+
// It is the single generic primitive every host consumes — DynamicCRUDPage uses
|
|
19
|
+
// it internally, and bespoke host pages (e.g. ops `/m/$model`) mount it directly
|
|
20
|
+
// next to their own toolbar. Hosts never reimplement action-button plumbing.
|
|
21
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
22
|
+
import { Button } from '@asteby/metacore-ui/primitives';
|
|
23
|
+
import { useApi } from './api-context';
|
|
24
|
+
import { useMetadataCache } from './metadata-cache';
|
|
25
|
+
import { DynamicIcon } from './dynamic-icon';
|
|
26
|
+
import { ActionModalDispatcher } from './action-modal-dispatcher';
|
|
27
|
+
const DEFAULT_PLACEMENTS = ['table', 'create'];
|
|
28
|
+
function toActionMetadata(a) {
|
|
29
|
+
return {
|
|
30
|
+
key: a.key,
|
|
31
|
+
label: a.label,
|
|
32
|
+
icon: a.icon || 'Zap',
|
|
33
|
+
color: a.color,
|
|
34
|
+
confirm: a.confirm,
|
|
35
|
+
confirmMessage: a.confirmMessage,
|
|
36
|
+
fields: a.fields,
|
|
37
|
+
requiresState: a.requiresState,
|
|
38
|
+
executable: a.executable,
|
|
39
|
+
placement: a.placement,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the model's actions matching the requested placements. Reads from the
|
|
44
|
+
* `actions` prop when provided, else the metadata cache, else fetches once.
|
|
45
|
+
*/
|
|
46
|
+
export function useModelActions(model, placements = DEFAULT_PLACEMENTS, provided) {
|
|
47
|
+
const api = useApi();
|
|
48
|
+
const cached = useMetadataCache((s) => s.getMetadata(model));
|
|
49
|
+
const [fetched, setFetched] = useState(null);
|
|
50
|
+
const haveSource = provided != null || cached != null;
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (haveSource)
|
|
53
|
+
return;
|
|
54
|
+
let cancelled = false;
|
|
55
|
+
api
|
|
56
|
+
.get(`/metadata/table/${model}`)
|
|
57
|
+
.then((res) => {
|
|
58
|
+
if (!cancelled)
|
|
59
|
+
setFetched((res.data?.data ?? res.data));
|
|
60
|
+
})
|
|
61
|
+
.catch(() => {
|
|
62
|
+
if (!cancelled)
|
|
63
|
+
setFetched(null);
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, [model, haveSource, api]);
|
|
69
|
+
const all = provided ?? cached?.actions ?? fetched?.actions ?? [];
|
|
70
|
+
return useMemo(() => all.filter((a) => placements.includes((a.placement ?? 'row'))), [all, placements]);
|
|
71
|
+
}
|
|
72
|
+
export function ModelActionToolbar({ model, endpoint, actions, placements = DEFAULT_PLACEMENTS, onChange, className, }) {
|
|
73
|
+
const surfaced = useModelActions(model, placements, actions);
|
|
74
|
+
const [active, setActive] = useState(null);
|
|
75
|
+
const dataEndpoint = endpoint ?? `/data/${model}/me`;
|
|
76
|
+
if (surfaced.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: className ?? 'flex items-center gap-2', children: surfaced.map((a) => {
|
|
79
|
+
const isCreate = (a.placement ?? 'row') === 'create';
|
|
80
|
+
return (_jsxs(Button, { variant: isCreate ? 'default' : 'outline', onClick: () => setActive(toActionMetadata(a)), style: a.color && !isCreate ? { borderColor: a.color, color: a.color } : undefined, children: [_jsx(DynamicIcon, { name: a.icon || (isCreate ? 'Plus' : 'Zap'), className: "mr-2 h-4 w-4" }), a.label] }, a.key));
|
|
81
|
+
}) }), active && (_jsx(ActionModalDispatcher, { open: !!active, onOpenChange: (open) => {
|
|
82
|
+
if (!open)
|
|
83
|
+
setActive(null);
|
|
84
|
+
}, action: active, model: model, record: {}, endpoint: dataEndpoint, onSuccess: () => {
|
|
85
|
+
setActive(null);
|
|
86
|
+
onChange?.();
|
|
87
|
+
} }))] }));
|
|
88
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -147,6 +147,13 @@ export interface ActionDefinition {
|
|
|
147
147
|
fields?: ActionFieldDef[];
|
|
148
148
|
requiresState?: string[];
|
|
149
149
|
executable?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Where the host surfaces the trigger. Mirrors manifest/v3 Action.placement.
|
|
152
|
+
* "row" (default) — per-row table action.
|
|
153
|
+
* "table" — page toolbar button (no record context).
|
|
154
|
+
* "create" — toolbar button that replaces the generic create button.
|
|
155
|
+
*/
|
|
156
|
+
placement?: 'row' | 'table' | 'create';
|
|
150
157
|
}
|
|
151
158
|
export interface ApiResponse<T> {
|
|
152
159
|
success: boolean;
|
|
@@ -173,5 +180,6 @@ export interface ActionMetadata {
|
|
|
173
180
|
fields?: ActionFieldDef[];
|
|
174
181
|
requiresState?: string[];
|
|
175
182
|
executable?: boolean;
|
|
183
|
+
placement?: 'row' | 'table' | 'create';
|
|
176
184
|
}
|
|
177
185
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"lucide-react": ">=0.460",
|
|
33
33
|
"date-fns": ">=3",
|
|
34
34
|
"react-day-picker": ">=8",
|
|
35
|
-
"@asteby/metacore-sdk": "^3.
|
|
36
|
-
"@asteby/metacore-ui": "^2.0
|
|
35
|
+
"@asteby/metacore-sdk": "^3.1.0",
|
|
36
|
+
"@asteby/metacore-ui": "^2.1.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@tanstack/react-router": {
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"typescript": "^6.0.0",
|
|
61
61
|
"vitest": "^4.0.0",
|
|
62
62
|
"zustand": "^5.0.0",
|
|
63
|
-
"@asteby/metacore-sdk": "3.
|
|
64
|
-
"@asteby/metacore-ui": "2.0
|
|
63
|
+
"@asteby/metacore-sdk": "3.1.0",
|
|
64
|
+
"@asteby/metacore-ui": "2.1.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsc -p tsconfig.json",
|
|
@@ -36,6 +36,7 @@ import { DynamicRecordDialog } from './dialogs/dynamic-record'
|
|
|
36
36
|
import { ExportDialog } from './dialogs/export'
|
|
37
37
|
import { ImportDialog } from './dialogs/import'
|
|
38
38
|
import { getModelExtension } from './model-extension-registry'
|
|
39
|
+
import { ModelActionToolbar } from './model-action-toolbar'
|
|
39
40
|
import type { TableMetadata } from './types'
|
|
40
41
|
|
|
41
42
|
export interface DynamicCRUDPageStrings {
|
|
@@ -153,7 +154,10 @@ export function DynamicCRUDPage(props: DynamicCRUDPageProps) {
|
|
|
153
154
|
}, [title])
|
|
154
155
|
|
|
155
156
|
const enableCRUD = metadata?.enableCRUDActions ?? false
|
|
156
|
-
|
|
157
|
+
// A "create"-placement action ships a custom create experience — it
|
|
158
|
+
// replaces the generic create button (the ModelActionToolbar renders it).
|
|
159
|
+
const hasCreateAction = metadata?.actions?.some((a) => a.placement === 'create') ?? false
|
|
160
|
+
const effectiveHideCreate = hideCreate || ext?.hideCreate || hasCreateAction
|
|
157
161
|
const effectiveHideExport = hideExport || ext?.hideExport
|
|
158
162
|
const effectiveHideImport = hideImport || ext?.hideImport
|
|
159
163
|
// Refresh defaults to hidden in the page header — <DynamicTable> ships
|
|
@@ -222,6 +226,12 @@ export function DynamicCRUDPage(props: DynamicCRUDPageProps) {
|
|
|
222
226
|
)}
|
|
223
227
|
{ext?.toolbarExtras && <ext.toolbarExtras model={model} onRefresh={handleRefresh} />}
|
|
224
228
|
{toolbarExtras}
|
|
229
|
+
<ModelActionToolbar
|
|
230
|
+
model={model}
|
|
231
|
+
endpoint={dataEndpoint}
|
|
232
|
+
actions={metadata?.actions}
|
|
233
|
+
onChange={handleRefresh}
|
|
234
|
+
/>
|
|
225
235
|
{showCreate && (
|
|
226
236
|
<button
|
|
227
237
|
type='button'
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -569,7 +569,13 @@ export function DynamicTable({
|
|
|
569
569
|
|
|
570
570
|
const columns = useMemo(() => {
|
|
571
571
|
if (!metadata) return []
|
|
572
|
-
|
|
572
|
+
// Row-action column only renders per-row actions. Table-level placements
|
|
573
|
+
// ("table"/"create") are surfaced by <ModelActionToolbar> at the page
|
|
574
|
+
// level, so strip them here to avoid a meaningless per-row button.
|
|
575
|
+
const rowMetadata = metadata.actions?.some((a) => a.placement === 'table' || a.placement === 'create')
|
|
576
|
+
? { ...metadata, actions: metadata.actions.filter((a) => !a.placement || a.placement === 'row') }
|
|
577
|
+
: metadata
|
|
578
|
+
const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs)
|
|
573
579
|
const filteredBase = baseColumns.filter((col: ColumnDef<any>) => !hiddenColumns.includes(col.id as string))
|
|
574
580
|
const actionsCol = filteredBase.find((c: ColumnDef<any>) => c.id === 'actions')
|
|
575
581
|
const otherCols = filteredBase.filter((c: ColumnDef<any>) => c.id !== 'actions')
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,12 @@ export {
|
|
|
11
11
|
ActionModalDispatcher,
|
|
12
12
|
type ActionModalProps,
|
|
13
13
|
} from './action-modal-dispatcher'
|
|
14
|
+
export {
|
|
15
|
+
ModelActionToolbar,
|
|
16
|
+
useModelActions,
|
|
17
|
+
type ModelActionToolbarProps,
|
|
18
|
+
type ActionPlacement,
|
|
19
|
+
} from './model-action-toolbar'
|
|
14
20
|
export * from './addon-loader'
|
|
15
21
|
export {
|
|
16
22
|
AddonLayoutProvider,
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// ModelActionToolbar — renders page-level (toolbar) triggers for a model's
|
|
2
|
+
// declarative actions and owns the modal dispatch for them.
|
|
3
|
+
//
|
|
4
|
+
// A model's actions carry a `placement` hint (see manifest/v3 Action.placement):
|
|
5
|
+
// "row" (default) — rendered per-row inside <DynamicTable>'s action column.
|
|
6
|
+
// "table" — a plain toolbar button (no record context).
|
|
7
|
+
// "create" — a primary toolbar button that replaces the generic
|
|
8
|
+
// "create" button, for addons shipping a custom create
|
|
9
|
+
// experience (e.g. a journal entry with debit/credit lines).
|
|
10
|
+
//
|
|
11
|
+
// This component renders the buttons for the placements it's asked to surface
|
|
12
|
+
// (default: table + create) and mounts the <ActionModalDispatcher>, which
|
|
13
|
+
// resolves either a custom federated modal (registered via the action registry)
|
|
14
|
+
// or the generic declarative form. For create-style actions there is no record
|
|
15
|
+
// yet, so the dispatcher receives an empty record `{}`.
|
|
16
|
+
//
|
|
17
|
+
// It is the single generic primitive every host consumes — DynamicCRUDPage uses
|
|
18
|
+
// it internally, and bespoke host pages (e.g. ops `/m/$model`) mount it directly
|
|
19
|
+
// next to their own toolbar. Hosts never reimplement action-button plumbing.
|
|
20
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
21
|
+
import { Button } from '@asteby/metacore-ui/primitives'
|
|
22
|
+
import { useApi } from './api-context'
|
|
23
|
+
import { useMetadataCache } from './metadata-cache'
|
|
24
|
+
import { DynamicIcon } from './dynamic-icon'
|
|
25
|
+
import { ActionModalDispatcher } from './action-modal-dispatcher'
|
|
26
|
+
import type { ActionDefinition, ActionMetadata, TableMetadata } from './types'
|
|
27
|
+
|
|
28
|
+
export type ActionPlacement = 'row' | 'table' | 'create'
|
|
29
|
+
|
|
30
|
+
export interface ModelActionToolbarProps {
|
|
31
|
+
/** Model key as registered on the backend (e.g. "JournalEntry"). */
|
|
32
|
+
model: string
|
|
33
|
+
/** Data endpoint passed to the dispatcher. Defaults to `/data/<model>/me`. */
|
|
34
|
+
endpoint?: string
|
|
35
|
+
/**
|
|
36
|
+
* Pre-fetched action definitions. When omitted the toolbar reads them from
|
|
37
|
+
* the metadata cache, falling back to `/metadata/table/<model>`. Pass this
|
|
38
|
+
* when the host page already holds the metadata to avoid a second fetch.
|
|
39
|
+
*/
|
|
40
|
+
actions?: ActionDefinition[]
|
|
41
|
+
/** Which placements to render. Defaults to `['table', 'create']`. */
|
|
42
|
+
placements?: ActionPlacement[]
|
|
43
|
+
/** Fired after an action's modal reports success. */
|
|
44
|
+
onChange?: () => void
|
|
45
|
+
/** Extra classes on the button row container. */
|
|
46
|
+
className?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DEFAULT_PLACEMENTS: ActionPlacement[] = ['table', 'create']
|
|
50
|
+
|
|
51
|
+
function toActionMetadata(a: ActionDefinition): ActionMetadata {
|
|
52
|
+
return {
|
|
53
|
+
key: a.key,
|
|
54
|
+
label: a.label,
|
|
55
|
+
icon: a.icon || 'Zap',
|
|
56
|
+
color: a.color,
|
|
57
|
+
confirm: a.confirm,
|
|
58
|
+
confirmMessage: a.confirmMessage,
|
|
59
|
+
fields: a.fields,
|
|
60
|
+
requiresState: a.requiresState,
|
|
61
|
+
executable: a.executable,
|
|
62
|
+
placement: a.placement,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns the model's actions matching the requested placements. Reads from the
|
|
68
|
+
* `actions` prop when provided, else the metadata cache, else fetches once.
|
|
69
|
+
*/
|
|
70
|
+
export function useModelActions(
|
|
71
|
+
model: string,
|
|
72
|
+
placements: ActionPlacement[] = DEFAULT_PLACEMENTS,
|
|
73
|
+
provided?: ActionDefinition[],
|
|
74
|
+
): ActionDefinition[] {
|
|
75
|
+
const api = useApi()
|
|
76
|
+
const cached = useMetadataCache((s) => s.getMetadata(model))
|
|
77
|
+
const [fetched, setFetched] = useState<TableMetadata | null>(null)
|
|
78
|
+
|
|
79
|
+
const haveSource = provided != null || cached != null
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (haveSource) return
|
|
82
|
+
let cancelled = false
|
|
83
|
+
api
|
|
84
|
+
.get(`/metadata/table/${model}`)
|
|
85
|
+
.then((res) => {
|
|
86
|
+
if (!cancelled) setFetched((res.data?.data ?? res.data) as TableMetadata)
|
|
87
|
+
})
|
|
88
|
+
.catch(() => {
|
|
89
|
+
if (!cancelled) setFetched(null)
|
|
90
|
+
})
|
|
91
|
+
return () => {
|
|
92
|
+
cancelled = true
|
|
93
|
+
}
|
|
94
|
+
}, [model, haveSource, api])
|
|
95
|
+
|
|
96
|
+
const all = provided ?? cached?.actions ?? fetched?.actions ?? []
|
|
97
|
+
return useMemo(
|
|
98
|
+
() => all.filter((a) => placements.includes((a.placement ?? 'row') as ActionPlacement)),
|
|
99
|
+
[all, placements],
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function ModelActionToolbar({
|
|
104
|
+
model,
|
|
105
|
+
endpoint,
|
|
106
|
+
actions,
|
|
107
|
+
placements = DEFAULT_PLACEMENTS,
|
|
108
|
+
onChange,
|
|
109
|
+
className,
|
|
110
|
+
}: ModelActionToolbarProps) {
|
|
111
|
+
const surfaced = useModelActions(model, placements, actions)
|
|
112
|
+
const [active, setActive] = useState<ActionMetadata | null>(null)
|
|
113
|
+
const dataEndpoint = endpoint ?? `/data/${model}/me`
|
|
114
|
+
|
|
115
|
+
if (surfaced.length === 0) return null
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<div className={className ?? 'flex items-center gap-2'}>
|
|
120
|
+
{surfaced.map((a) => {
|
|
121
|
+
const isCreate = (a.placement ?? 'row') === 'create'
|
|
122
|
+
return (
|
|
123
|
+
<Button
|
|
124
|
+
key={a.key}
|
|
125
|
+
variant={isCreate ? 'default' : 'outline'}
|
|
126
|
+
onClick={() => setActive(toActionMetadata(a))}
|
|
127
|
+
style={a.color && !isCreate ? { borderColor: a.color, color: a.color } : undefined}
|
|
128
|
+
>
|
|
129
|
+
<DynamicIcon name={a.icon || (isCreate ? 'Plus' : 'Zap')} className="mr-2 h-4 w-4" />
|
|
130
|
+
{a.label}
|
|
131
|
+
</Button>
|
|
132
|
+
)
|
|
133
|
+
})}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{active && (
|
|
137
|
+
<ActionModalDispatcher
|
|
138
|
+
open={!!active}
|
|
139
|
+
onOpenChange={(open) => {
|
|
140
|
+
if (!open) setActive(null)
|
|
141
|
+
}}
|
|
142
|
+
action={active}
|
|
143
|
+
model={model}
|
|
144
|
+
record={{}}
|
|
145
|
+
endpoint={dataEndpoint}
|
|
146
|
+
onSuccess={() => {
|
|
147
|
+
setActive(null)
|
|
148
|
+
onChange?.()
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
</>
|
|
153
|
+
)
|
|
154
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -162,6 +162,13 @@ export interface ActionDefinition {
|
|
|
162
162
|
fields?: ActionFieldDef[]
|
|
163
163
|
requiresState?: string[]
|
|
164
164
|
executable?: boolean
|
|
165
|
+
/**
|
|
166
|
+
* Where the host surfaces the trigger. Mirrors manifest/v3 Action.placement.
|
|
167
|
+
* "row" (default) — per-row table action.
|
|
168
|
+
* "table" — page toolbar button (no record context).
|
|
169
|
+
* "create" — toolbar button that replaces the generic create button.
|
|
170
|
+
*/
|
|
171
|
+
placement?: 'row' | 'table' | 'create'
|
|
165
172
|
}
|
|
166
173
|
|
|
167
174
|
export interface ApiResponse<T> {
|
|
@@ -194,4 +201,5 @@ export interface ActionMetadata {
|
|
|
194
201
|
fields?: ActionFieldDef[]
|
|
195
202
|
requiresState?: string[]
|
|
196
203
|
executable?: boolean
|
|
204
|
+
placement?: 'row' | 'table' | 'create'
|
|
197
205
|
}
|