@rebasepro/cms-types 0.17.0-canary.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/LICENSE +21 -0
- package/dist/admin_collection.d.ts +650 -0
- package/dist/augment.d.ts +81 -0
- package/dist/collections.d.ts +282 -0
- package/dist/controllers/analytics_controller.d.ts +7 -0
- package/dist/controllers/auth.d.ts +111 -0
- package/dist/controllers/customization_controller.d.ts +69 -0
- package/dist/controllers/dialogs_controller.d.ts +36 -0
- package/dist/controllers/index.d.ts +10 -0
- package/dist/controllers/local_config_persistence.d.ts +20 -0
- package/dist/controllers/navigation.d.ts +248 -0
- package/dist/controllers/registry.d.ts +96 -0
- package/dist/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/controllers/side_panel_controller.d.ts +97 -0
- package/dist/controllers/snackbar.d.ts +45 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.es.js +154 -0
- package/dist/index.es.js.map +1 -0
- package/dist/react_component_ref.d.ts +43 -0
- package/dist/rebase_context.d.ts +68 -0
- package/dist/types/breadcrumbs.d.ts +15 -0
- package/dist/types/builders.d.ts +15 -0
- package/dist/types/collection_views.d.ts +105 -0
- package/dist/types/component_overrides.d.ts +196 -0
- package/dist/types/entity_actions.d.ts +112 -0
- package/dist/types/entity_display.d.ts +148 -0
- package/dist/types/entity_link_builder.d.ts +7 -0
- package/dist/types/entity_views.d.ts +115 -0
- package/dist/types/export_import.d.ts +21 -0
- package/dist/types/form_layout.d.ts +126 -0
- package/dist/types/formex.d.ts +40 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/locales.d.ts +4 -0
- package/dist/types/modify_collections.d.ts +5 -0
- package/dist/types/plugins.d.ts +277 -0
- package/dist/types/property_config.d.ts +74 -0
- package/dist/types/property_options.d.ts +255 -0
- package/dist/types/slots.d.ts +279 -0
- package/dist/types/translations.d.ts +989 -0
- package/dist/types/user_management_delegate.d.ts +22 -0
- package/package.json +103 -0
- package/src/admin_collection.ts +775 -0
- package/src/augment.ts +79 -0
- package/src/collections.ts +312 -0
- package/src/controllers/analytics_controller.tsx +57 -0
- package/src/controllers/auth.ts +122 -0
- package/src/controllers/customization_controller.tsx +81 -0
- package/src/controllers/dialogs_controller.tsx +37 -0
- package/src/controllers/index.ts +10 -0
- package/src/controllers/local_config_persistence.tsx +22 -0
- package/src/controllers/navigation.ts +288 -0
- package/src/controllers/registry.ts +114 -0
- package/src/controllers/side_dialogs_controller.tsx +82 -0
- package/src/controllers/side_panel_controller.tsx +112 -0
- package/src/controllers/snackbar.ts +51 -0
- package/src/index.ts +20 -0
- package/src/react_component_ref.ts +52 -0
- package/src/rebase_context.ts +81 -0
- package/src/types/breadcrumbs.ts +16 -0
- package/src/types/builders.ts +18 -0
- package/src/types/collection_views.tsx +125 -0
- package/src/types/component_overrides.ts +244 -0
- package/src/types/entity_actions.tsx +134 -0
- package/src/types/entity_display.ts +182 -0
- package/src/types/entity_link_builder.ts +8 -0
- package/src/types/entity_views.tsx +135 -0
- package/src/types/export_import.ts +26 -0
- package/src/types/form_layout.ts +137 -0
- package/src/types/formex.ts +45 -0
- package/src/types/index.ts +18 -0
- package/src/types/locales.ts +81 -0
- package/src/types/modify_collections.tsx +6 -0
- package/src/types/plugins.tsx +346 -0
- package/src/types/property_config.tsx +97 -0
- package/src/types/property_options.ts +300 -0
- package/src/types/slots.tsx +334 -0
- package/src/types/translations.ts +1104 -0
- package/src/types/user_management_delegate.ts +23 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { ComponentLike, ComponentRef, LazyComponentRef } from "@rebasepro/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `ComponentRef`, narrowed to real React types.
|
|
6
|
+
*
|
|
7
|
+
* Core's {@link ComponentRef} describes a component structurally
|
|
8
|
+
* ({@link ComponentLike}) so that `properties.ts` — and therefore the whole
|
|
9
|
+
* property model the backend reads — can live without React. The trade is that
|
|
10
|
+
* the return type is `unknown`, so a function returning something React cannot
|
|
11
|
+
* render type-checks there.
|
|
12
|
+
*
|
|
13
|
+
* Use this type wherever React genuinely exists: authoring a collection's admin
|
|
14
|
+
* options, and inside the admin packages. Assignments flow into core unchanged,
|
|
15
|
+
* because every member of this union is a member of that one.
|
|
16
|
+
*/
|
|
17
|
+
export type ReactComponentRef<P = any> =
|
|
18
|
+
| string
|
|
19
|
+
| LazyComponentRef<P>
|
|
20
|
+
| (() => Promise<{ default: React.ComponentType<P> }>)
|
|
21
|
+
| React.ComponentType<P>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The `ComponentLike` contract, as a signature the compiler has to keep true.
|
|
25
|
+
*
|
|
26
|
+
* The split rests on one claim: **every form a React component takes is
|
|
27
|
+
* assignable to `ComponentLike`** — function components, class components,
|
|
28
|
+
* `memo`, `forwardRef`. If that stopped holding, core's `ComponentRef` would
|
|
29
|
+
* quietly begin rejecting real components, and the failure would surface far away
|
|
30
|
+
* in whichever collection file happened to use the broken form.
|
|
31
|
+
*
|
|
32
|
+
* So the claim is not left to a test that someone has to run. This function's
|
|
33
|
+
* parameter and return types state it, and `pnpm typecheck` enforces it on every
|
|
34
|
+
* commit. It is also useful on its own: an explicit widening at the point where
|
|
35
|
+
* an authored component enters a collection config.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* import { MyField } from "./MyField";
|
|
39
|
+
* admin: { Field: asComponentRef(MyField) }
|
|
40
|
+
*/
|
|
41
|
+
export function asComponentRef<P>(component: React.ComponentType<P>): ComponentRef<P> {
|
|
42
|
+
return component;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The same contract in the other direction: a `ComponentLike` is only renderable
|
|
47
|
+
* once narrowed, and this is the single sanctioned place that narrowing is
|
|
48
|
+
* spelled out. `resolveComponentRef` in `@rebasepro/app` does the runtime half.
|
|
49
|
+
*/
|
|
50
|
+
export function asReactComponent<P>(component: ComponentLike<P>): React.ComponentType<P> {
|
|
51
|
+
return component as React.ComponentType<P>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { AnalyticsController } from "./controllers/analytics_controller";
|
|
2
|
+
import type { AuthController } from "./controllers/auth";
|
|
3
|
+
import type { UserConfigurationPersistence } from "./controllers/local_config_persistence";
|
|
4
|
+
import type { DatabaseAdmin } from "@rebasepro/types";
|
|
5
|
+
import type { RebaseCallContext } from "@rebasepro/types";
|
|
6
|
+
import type { User } from "@rebasepro/types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Context that includes the internal controllers and contexts used by the app.
|
|
10
|
+
* Some controllers and context included in this context can be accessed
|
|
11
|
+
* directly from their respective hooks.
|
|
12
|
+
* @group Hooks and utilities
|
|
13
|
+
* @see useRebaseContext
|
|
14
|
+
*/
|
|
15
|
+
export type RebaseContext<USER extends User = User, AuthControllerType extends AuthController<USER> = AuthController<USER>> = RebaseCallContext<USER> & {
|
|
16
|
+
|
|
17
|
+
authController: AuthControllerType;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Controller mapping strings to collections
|
|
21
|
+
*/
|
|
22
|
+
collectionRegistryController?: import("@rebasepro/types").CollectionRegistryController;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Controller for navigation state
|
|
26
|
+
*/
|
|
27
|
+
navigationStateController?: import("./controllers/navigation").NavigationStateController;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Controller for side dialogs (side sheets)
|
|
31
|
+
*/
|
|
32
|
+
sideDialogsController?: import("./controllers/side_dialogs_controller").SideDialogsController;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Controller to open the side panel displaying entity forms
|
|
36
|
+
*/
|
|
37
|
+
sidePanelController?: import("./controllers/side_panel_controller").SidePanelController;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Controller resolving URLs in the admin
|
|
41
|
+
*/
|
|
42
|
+
urlController?: import("./controllers/navigation").UrlController;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Controller to handle simple confirmation and alert dialogs
|
|
46
|
+
*/
|
|
47
|
+
dialogsController?: import("./controllers/dialogs_controller").DialogsController;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Controller for admin customization
|
|
51
|
+
*/
|
|
52
|
+
customizationController?: import("./controllers/customization_controller").CustomizationController;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Controller for effective role
|
|
56
|
+
*/
|
|
57
|
+
effectiveRoleController?: { effectiveRole: string | null, setEffectiveRole: (role: string | null) => void };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Use this controller to access data stored in the browser for the user
|
|
61
|
+
*/
|
|
62
|
+
userConfigPersistence?: UserConfigurationPersistence;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Callback to send analytics events
|
|
66
|
+
*/
|
|
67
|
+
analyticsController?: AnalyticsController;
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Administrative database operations (SQL, schema discovery).
|
|
72
|
+
* Only available in developer/admin contexts.
|
|
73
|
+
*/
|
|
74
|
+
databaseAdmin?: DatabaseAdmin;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Controller for snackbars
|
|
78
|
+
*/
|
|
79
|
+
snackbarController?: import("./controllers/snackbar").SnackbarController;
|
|
80
|
+
|
|
81
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface BreadcrumbEntry {
|
|
2
|
+
title: string;
|
|
3
|
+
url: string;
|
|
4
|
+
/**
|
|
5
|
+
* Stable identifier for this entry (e.g., collection path). Lets the
|
|
6
|
+
* provider tell a rebuilt-but-unchanged trail from a real navigation.
|
|
7
|
+
*/
|
|
8
|
+
id?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BreadcrumbsController {
|
|
12
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
13
|
+
set: (props: {
|
|
14
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
15
|
+
}) => void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AuthController } from "../controllers/auth";
|
|
2
|
+
import type { RebaseData } from "@rebasepro/types";
|
|
3
|
+
import type { User } from "@rebasepro/types";
|
|
4
|
+
|
|
5
|
+
import type { AppView } from "../controllers/navigation";
|
|
6
|
+
import type { AdminCollection } from "@rebasepro/cms-types";
|
|
7
|
+
|
|
8
|
+
export type CollectionConfigsBuilder<EC extends AdminCollection = AdminCollection> = (params: {
|
|
9
|
+
user: User | null,
|
|
10
|
+
authController: AuthController,
|
|
11
|
+
data: RebaseData
|
|
12
|
+
}) => EC[] | Promise<EC[]>;
|
|
13
|
+
|
|
14
|
+
export type AppViewsBuilder = (params: {
|
|
15
|
+
user: User | null,
|
|
16
|
+
authController: AuthController,
|
|
17
|
+
data: RebaseData
|
|
18
|
+
}) => AppView[] | Promise<AppView[]>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Entity, ComponentRef } from "@rebasepro/types";
|
|
3
|
+
import type { AdminCollection } from "@rebasepro/cms-types";
|
|
4
|
+
|
|
5
|
+
import type { CollectionSize, EntityTableController, SelectionController, ViewMode } from "../collections";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A custom rendering of *one* collection's rows, selectable from the same view
|
|
9
|
+
* switcher as list / table / cards / kanban.
|
|
10
|
+
*
|
|
11
|
+
* This is the middle scope of custom UI. The other two already exist and this
|
|
12
|
+
* is not a substitute for either:
|
|
13
|
+
*
|
|
14
|
+
* - one record → `entityViews` (the entity tab strip)
|
|
15
|
+
* - one collection's rows, rendered differently → **this**
|
|
16
|
+
* - a workflow spanning several collections → `AppView` (`views={[…]}`)
|
|
17
|
+
*
|
|
18
|
+
* A view mode is another rendering of the *same query*. The `Builder` is handed
|
|
19
|
+
* the live {@link EntityTableController}, so it inherits the collection's
|
|
20
|
+
* filters, search string, sort, pagination, role checks and entity side panel
|
|
21
|
+
* for free — which is the whole reason to declare one instead of an `AppView`.
|
|
22
|
+
*
|
|
23
|
+
* If your component ignores `tableController` and fetches tables of its own, it
|
|
24
|
+
* wants to be an `AppView`: the toolbar above it — search box, filters, the
|
|
25
|
+
* record count — would be describing a query the view does not render.
|
|
26
|
+
*
|
|
27
|
+
* @group Models
|
|
28
|
+
*/
|
|
29
|
+
export type CollectionCustomView<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
30
|
+
/**
|
|
31
|
+
* Identifies this view. It is what `defaultViewMode` and `enabledViews`
|
|
32
|
+
* name, and what the `__view` URL param carries, so it must not collide
|
|
33
|
+
* with a built-in mode ("list", "table", "cards", "kanban").
|
|
34
|
+
*
|
|
35
|
+
* Treat it as frozen once shipped: users have it persisted in their saved
|
|
36
|
+
* collection config and in bookmarked URLs.
|
|
37
|
+
*/
|
|
38
|
+
key: string;
|
|
39
|
+
|
|
40
|
+
/** Label shown in the view switcher. */
|
|
41
|
+
name: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Icon shown beside the name, as a `lucide-react` icon name (e.g. `"Map"`)
|
|
45
|
+
* or a rendered node. A name is what the collection editor can store.
|
|
46
|
+
*/
|
|
47
|
+
icon?: string | React.ReactNode;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The component that renders the rows.
|
|
51
|
+
*/
|
|
52
|
+
Builder: ComponentRef<CollectionCustomViewParams<M>>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* How clicking a record should present it, overriding what
|
|
56
|
+
* `resolveOpenEntityMode` would otherwise derive from the view mode.
|
|
57
|
+
* Defaults to `"side_panel"`, which is what the board uses: a custom view
|
|
58
|
+
* usually owns its whole surface and should keep it.
|
|
59
|
+
*/
|
|
60
|
+
openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether the size selector applies to this view. Off by default — most
|
|
64
|
+
* custom views have no notion of row height.
|
|
65
|
+
*/
|
|
66
|
+
sizeable?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* What a {@link CollectionCustomView}'s `Builder` receives.
|
|
71
|
+
*
|
|
72
|
+
* This is the same set the built-in view bindings are given, so a custom view
|
|
73
|
+
* starts from parity with them.
|
|
74
|
+
*
|
|
75
|
+
* @group Models
|
|
76
|
+
*/
|
|
77
|
+
export interface CollectionCustomViewParams<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
78
|
+
/** The collection being rendered, fully resolved. */
|
|
79
|
+
collection: AdminCollection<M>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The live query: rows, loading state, pagination, and the filter / sort /
|
|
83
|
+
* search state shared with the toolbar. Read rows from here rather than
|
|
84
|
+
* fetching — that is what keeps the toolbar honest.
|
|
85
|
+
*/
|
|
86
|
+
tableController: EntityTableController<M>;
|
|
87
|
+
|
|
88
|
+
/** Full path of the collection, e.g. `users/1234/addresses`. */
|
|
89
|
+
path: string;
|
|
90
|
+
|
|
91
|
+
/** Parent path segments, when this is a subcollection. */
|
|
92
|
+
parentCollectionSlugs?: string[];
|
|
93
|
+
parentEntityIds?: string[];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Open a record. Routes through the collection's resolved
|
|
97
|
+
* `openEntityMode`, so the side panel, split view and full-screen form all
|
|
98
|
+
* work without the view knowing which one it got.
|
|
99
|
+
*/
|
|
100
|
+
onEntityClick?: (entity: Entity<M>) => void;
|
|
101
|
+
|
|
102
|
+
/** Create a record. Undefined when the user may not create. */
|
|
103
|
+
onNewClick?: () => void;
|
|
104
|
+
|
|
105
|
+
/** Whether the current user may create records in this collection. */
|
|
106
|
+
canCreate?: boolean;
|
|
107
|
+
|
|
108
|
+
selectionController?: SelectionController<M>;
|
|
109
|
+
selectionEnabled?: boolean;
|
|
110
|
+
|
|
111
|
+
/** Records to draw as highlighted, e.g. the one open in the side panel. */
|
|
112
|
+
highlightedEntities?: Entity<M>[];
|
|
113
|
+
|
|
114
|
+
/** Records deleted in this session, for optimistic removal. */
|
|
115
|
+
deletedEntities?: Entity<M>[];
|
|
116
|
+
|
|
117
|
+
/** The shared empty state, so a custom view matches the built-ins. */
|
|
118
|
+
emptyComponent?: React.ReactNode;
|
|
119
|
+
|
|
120
|
+
/** Set when the view declared `sizeable`. */
|
|
121
|
+
size?: CollectionSize;
|
|
122
|
+
|
|
123
|
+
/** The mode this view was resolved to, for views that render several. */
|
|
124
|
+
viewMode?: ViewMode;
|
|
125
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import type { Property } from "@rebasepro/types";
|
|
3
|
+
import type { WhereFilterOp } from "@rebasepro/types";
|
|
4
|
+
|
|
5
|
+
// ── Scoped component name unions ──────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Components that can only be overridden at the **app level** via the
|
|
9
|
+
* `components` prop on `<Rebase>`.
|
|
10
|
+
*
|
|
11
|
+
* These are shell-level / global components that exist outside of any
|
|
12
|
+
* specific collection context.
|
|
13
|
+
*
|
|
14
|
+
* @group Component Overrides
|
|
15
|
+
*/
|
|
16
|
+
export type AppComponentName =
|
|
17
|
+
// ── Shell / Layout ──
|
|
18
|
+
| "Shell.AppBar"
|
|
19
|
+
| "Shell.Drawer"
|
|
20
|
+
| "Shell.DrawerNavigationItem"
|
|
21
|
+
| "Shell.DrawerNavigationGroup"
|
|
22
|
+
|
|
23
|
+
// ── Home Page ──
|
|
24
|
+
| "HomePage"
|
|
25
|
+
| "HomePage.CollectionCard"
|
|
26
|
+
|
|
27
|
+
// ── Auth ──
|
|
28
|
+
| "Auth.LoginView";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Components that can be overridden at the **collection level**
|
|
32
|
+
* (on an individual collection definition) or at the **app level**
|
|
33
|
+
* (as a default for all collections).
|
|
34
|
+
*
|
|
35
|
+
* When set at the app level, these act as defaults. When set on a
|
|
36
|
+
* specific collection, they override the app-level default for that
|
|
37
|
+
* collection only.
|
|
38
|
+
*
|
|
39
|
+
* @group Component Overrides
|
|
40
|
+
*/
|
|
41
|
+
export type CollectionComponentName =
|
|
42
|
+
// ── Collection View ──
|
|
43
|
+
| "Collection.View"
|
|
44
|
+
| "Collection.Table"
|
|
45
|
+
| "Collection.Card"
|
|
46
|
+
| "Collection.EmptyState"
|
|
47
|
+
| "Collection.Actions"
|
|
48
|
+
| "Collection.FilterField"
|
|
49
|
+
|
|
50
|
+
// ── Entity / Form ──
|
|
51
|
+
| "Entity.Form"
|
|
52
|
+
| "EditView.FormActions"
|
|
53
|
+
| "DetailView"
|
|
54
|
+
| "Entity.SidePanel"
|
|
55
|
+
| "EntityPreview"
|
|
56
|
+
| "Entity.MissingReference";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* All overridable component names across all scopes.
|
|
60
|
+
* @group Component Overrides
|
|
61
|
+
*/
|
|
62
|
+
export type OverridableComponentName = AppComponentName | CollectionComponentName;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Props received by a filter field component — whether it is a built-in
|
|
66
|
+
* per-type field, a property-level replacement (`property.ui.Filter`), or a
|
|
67
|
+
* `"Collection.FilterField"` override.
|
|
68
|
+
*
|
|
69
|
+
* The `operators` list is **already resolved**: it is the intersection of the
|
|
70
|
+
* engine's {@link DataSourceCapabilities.filterOperators}, the property-type
|
|
71
|
+
* defaults, and any `property.ui.filterOperators` narrowing. A custom field
|
|
72
|
+
* should only offer operators from this list — anything else may throw at
|
|
73
|
+
* query time on engines that cannot execute it.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* function MyStatusFilter({ value, setValue, operators }: FilterFieldBindingProps) {
|
|
78
|
+
* return (
|
|
79
|
+
* <select
|
|
80
|
+
* value={value?.[1] as string ?? ""}
|
|
81
|
+
* onChange={e => setValue(e.target.value ? ["==", e.target.value] : undefined)}>
|
|
82
|
+
* <option value="">Any</option>
|
|
83
|
+
* <option value="active">Active</option>
|
|
84
|
+
* <option value="archived">Archived</option>
|
|
85
|
+
* </select>
|
|
86
|
+
* );
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @group Component Overrides
|
|
91
|
+
*/
|
|
92
|
+
export interface FilterFieldBindingProps {
|
|
93
|
+
/** Key of the property being filtered (the column id). */
|
|
94
|
+
propertyKey: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The resolved property. For array properties this is the **item**
|
|
98
|
+
* property (`property.of`), with `isArray` set to true.
|
|
99
|
+
*/
|
|
100
|
+
property: Property;
|
|
101
|
+
|
|
102
|
+
/** True when the underlying property is an array of `property`. */
|
|
103
|
+
isArray: boolean;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Operators this field may offer, already narrowed by engine
|
|
107
|
+
* capabilities, property-type defaults, and `property.ui.filterOperators`.
|
|
108
|
+
*/
|
|
109
|
+
operators: readonly WhereFilterOp[];
|
|
110
|
+
|
|
111
|
+
/** Current filter condition for this property, if any. */
|
|
112
|
+
value?: [WhereFilterOp, unknown];
|
|
113
|
+
|
|
114
|
+
/** Set (or clear, with `undefined`) the filter condition. */
|
|
115
|
+
setValue: (value?: [WhereFilterOp, unknown]) => void;
|
|
116
|
+
|
|
117
|
+
/** Display title for the field (usually the property name). */
|
|
118
|
+
title?: string;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Coordination flags used by fields that open their own dialogs
|
|
122
|
+
* (e.g. the reference picker hides the parent filters dialog).
|
|
123
|
+
*/
|
|
124
|
+
hidden?: boolean;
|
|
125
|
+
setHidden?: (hidden: boolean) => void;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Override entry ────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A single component override entry.
|
|
132
|
+
*
|
|
133
|
+
* - **Eject mode** (default): Your component fully replaces the built-in one.
|
|
134
|
+
* It receives the same props as the original.
|
|
135
|
+
*
|
|
136
|
+
* - **Wrap mode** (`wrap: true`): Your component wraps the original. The
|
|
137
|
+
* built-in component is passed as `OriginalComponent` in props, so you can
|
|
138
|
+
* render it inside your custom layout/logic.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* // Eject — full replacement
|
|
143
|
+
* { Component: MyCustomAppBar }
|
|
144
|
+
*
|
|
145
|
+
* // Wrap — augment the original
|
|
146
|
+
* {
|
|
147
|
+
* Component: ({ OriginalComponent, ...props }) => (
|
|
148
|
+
* <div>
|
|
149
|
+
* <MyBanner />
|
|
150
|
+
* <OriginalComponent {...props} />
|
|
151
|
+
* </div>
|
|
152
|
+
* ),
|
|
153
|
+
* wrap: true
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @group Component Overrides
|
|
158
|
+
*/
|
|
159
|
+
export interface ComponentOverride<P = Record<string, unknown>> {
|
|
160
|
+
/**
|
|
161
|
+
* The replacement component. Receives the same props as the built-in
|
|
162
|
+
* component it replaces.
|
|
163
|
+
*
|
|
164
|
+
* When `wrap` is true, an additional `OriginalComponent` prop is injected
|
|
165
|
+
* containing the default component, allowing you to render it within
|
|
166
|
+
* your custom wrapper.
|
|
167
|
+
*/
|
|
168
|
+
Component: React.ComponentType<P>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* When true, the original default component is injected as the
|
|
172
|
+
* `OriginalComponent` prop into your Component, enabling the
|
|
173
|
+
* wrapping pattern (similar to Docusaurus's `--wrap` swizzle mode).
|
|
174
|
+
*
|
|
175
|
+
* When false or omitted, your component fully replaces the default
|
|
176
|
+
* (similar to Docusaurus's `--eject` swizzle mode).
|
|
177
|
+
*
|
|
178
|
+
* @default false
|
|
179
|
+
*/
|
|
180
|
+
wrap?: boolean;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Override maps by scope ────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Collection-scoped overrides. Only collection-level components
|
|
187
|
+
* can be overridden here.
|
|
188
|
+
*
|
|
189
|
+
* Set on a collection's `components` field to customize
|
|
190
|
+
* components for that specific collection.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```tsx
|
|
194
|
+
* const productsCollection = {
|
|
195
|
+
* name: "Products",
|
|
196
|
+
* slug: "products",
|
|
197
|
+
* components: {
|
|
198
|
+
* "Entity.Form": { Component: ProductForm },
|
|
199
|
+
* "Collection.EmptyState": { Component: ProductsEmptyState },
|
|
200
|
+
* "Collection.Card": { Component: ProductCard },
|
|
201
|
+
* }
|
|
202
|
+
* };
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* @group Component Overrides
|
|
206
|
+
*/
|
|
207
|
+
export type CollectionComponentOverrideMap = {
|
|
208
|
+
[K in CollectionComponentName]?: ComponentOverride;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* App-level overrides. Includes both app-only components (Shell, HomePage, Auth)
|
|
213
|
+
* and collection-level components (as defaults for all collections).
|
|
214
|
+
*
|
|
215
|
+
* Pass this to the `components` prop on `<Rebase>`.
|
|
216
|
+
*
|
|
217
|
+
* Collection-level components set here act as **defaults** — they apply to all
|
|
218
|
+
* collections unless a specific collection overrides them in its own
|
|
219
|
+
* `components`.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```tsx
|
|
223
|
+
* <Rebase
|
|
224
|
+
* client={client}
|
|
225
|
+
* components={{
|
|
226
|
+
* // App-level: only available here
|
|
227
|
+
* "Shell.AppBar": { Component: MyAppBar },
|
|
228
|
+
* "HomePage": { Component: MyDashboard },
|
|
229
|
+
*
|
|
230
|
+
* // Collection defaults: apply to ALL collections
|
|
231
|
+
* "EditView.FormActions": {
|
|
232
|
+
* Component: MyFormActions,
|
|
233
|
+
* wrap: true
|
|
234
|
+
* },
|
|
235
|
+
* "Collection.EmptyState": { Component: MyEmptyState },
|
|
236
|
+
* }}
|
|
237
|
+
* />
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @group Component Overrides
|
|
241
|
+
*/
|
|
242
|
+
export type ComponentOverrideMap = {
|
|
243
|
+
[K in OverridableComponentName]?: ComponentOverride;
|
|
244
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Entity } from "@rebasepro/types";
|
|
3
|
+
|
|
4
|
+
import type { SelectionController } from "../collections";
|
|
5
|
+
import type { FormContext } from "./entity_views";
|
|
6
|
+
import type { User } from "@rebasepro/types";
|
|
7
|
+
import type { RebaseContext } from "../rebase_context";
|
|
8
|
+
import type { SidePanelController } from "../controllers/side_panel_controller";
|
|
9
|
+
import type { AdminCollection } from "@rebasepro/cms-types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A entity action is a custom action that can be performed on a entity.
|
|
13
|
+
* They are displayed in the entity view and in the collection view.
|
|
14
|
+
*/
|
|
15
|
+
export interface EntityAction<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> {
|
|
16
|
+
/**
|
|
17
|
+
* Title of the action
|
|
18
|
+
*/
|
|
19
|
+
name: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Key of the action. You only need to provide this if you want to
|
|
23
|
+
* override the default actions, or if you are not passing the action
|
|
24
|
+
* directly to the `entityActions` prop of a collection.
|
|
25
|
+
* You can define your actions at the app level, in which case you
|
|
26
|
+
* must provide a key.
|
|
27
|
+
* The default actions are:
|
|
28
|
+
* - edit
|
|
29
|
+
* - delete
|
|
30
|
+
* - copy
|
|
31
|
+
*/
|
|
32
|
+
key?: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Icon of the action: a Lucide icon name (`"FileBarChart"`) or an element.
|
|
36
|
+
*
|
|
37
|
+
* The name form is what lets a collection declare one from the config package,
|
|
38
|
+
* which is plain `.ts` with no React dependency — an element would drag the
|
|
39
|
+
* whole UI layer into a backend that only loads the collection for its schema.
|
|
40
|
+
* It also matches how every other icon in a collection is written
|
|
41
|
+
* (`admin.icon`, `entityViews[].icon`), which were already strings while this
|
|
42
|
+
* one alone was not.
|
|
43
|
+
*/
|
|
44
|
+
icon?: React.ReactElement | string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Callback when the action is clicked
|
|
48
|
+
* @param props
|
|
49
|
+
*/
|
|
50
|
+
onClick(props: EntityActionClickProps<M, USER>): Promise<void> | void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional callback in case you want to disable the action
|
|
54
|
+
* @param props
|
|
55
|
+
*/
|
|
56
|
+
isEnabled?(props: EntityActionClickProps<M, USER>): boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* When true, this action is rendered inline on each row in the list view.
|
|
60
|
+
* By default, entity actions only appear in the table view and entity form.
|
|
61
|
+
* Use this for actions that should be easily accessible regardless of view mode.
|
|
62
|
+
*/
|
|
63
|
+
showActionsInListView?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Show this action collapsed in the menu of the collection view.
|
|
67
|
+
* Defaults to true
|
|
68
|
+
* If false, the action will be shown in the menu
|
|
69
|
+
*/
|
|
70
|
+
collapsed?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Show this action in the form, defaults to true
|
|
74
|
+
*/
|
|
75
|
+
includeInForm?: boolean;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type EntityActionClickProps<M extends Record<string, unknown>, USER extends User = User> = {
|
|
80
|
+
entity?: Entity<M>;
|
|
81
|
+
context?: RebaseContext<USER>;
|
|
82
|
+
|
|
83
|
+
path?: string;
|
|
84
|
+
collection?: AdminCollection<M>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Optional form context, present if the action is being called from a form.
|
|
88
|
+
* This allows you to access the form state and methods, including modifying the form values.
|
|
89
|
+
*/
|
|
90
|
+
formContext?: FormContext;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Present if this actions is being called from a side dialog only
|
|
94
|
+
*/
|
|
95
|
+
sidePanelController?: SidePanelController;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Is the action being called from the collection view or from the entity form view?
|
|
99
|
+
*/
|
|
100
|
+
view: "collection" | "form";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* If the action is rendered in the form, is it open in a side panel or full screen?
|
|
104
|
+
*/
|
|
105
|
+
openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Optional selection controller, present if the action is being called from a collection view
|
|
109
|
+
*/
|
|
110
|
+
selectionController?: SelectionController;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Optional highlight function to highlight the entity in the collection view
|
|
114
|
+
* @param entity
|
|
115
|
+
*/
|
|
116
|
+
highlightEntity?: (entity: Entity<Record<string, unknown>>) => void;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Optional unhighlight function to remove the highlight from the entity in the collection view
|
|
120
|
+
* @param entity
|
|
121
|
+
*/
|
|
122
|
+
unhighlightEntity?: (entity: Entity<Record<string, unknown>>) => void;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Optional function to navigate back (e.g. when deleting a entity or navigating from a form)
|
|
126
|
+
*/
|
|
127
|
+
navigateBack?: () => void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Callback to be called when the collection changes, e.g. after a entity is deleted or created.
|
|
131
|
+
*/
|
|
132
|
+
onCollectionChange?: () => void;
|
|
133
|
+
|
|
134
|
+
};
|