@object-ui/plugin-view 0.3.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ObjectQL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # @object-ui/plugin-view
2
+
3
+ Object View plugin for Object UI - Unified component for displaying and managing ObjectQL data with automatic form and grid generation.
4
+
5
+ ## Features
6
+
7
+ - **Automatic Views** - Generate views from ObjectQL schemas
8
+ - **Form Generation** - Auto-generate forms from object definitions
9
+ - **Grid Generation** - Auto-generate data grids
10
+ - **CRUD Operations** - Built-in create, read, update, delete
11
+ - **Field Mapping** - Automatic field type detection
12
+ - **Validation** - Schema-based validation
13
+ - **ObjectQL Integration** - Native ObjectStack support
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @object-ui/plugin-view
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Automatic Registration (Side-Effect Import)
24
+
25
+ ```typescript
26
+ // In your app entry point (e.g., App.tsx or main.tsx)
27
+ import '@object-ui/plugin-view';
28
+
29
+ // Now you can use view types in your schemas
30
+ const schema = {
31
+ type: 'object-view',
32
+ object: 'users',
33
+ viewMode: 'grid'
34
+ };
35
+ ```
36
+
37
+ ### Manual Registration
38
+
39
+ ```typescript
40
+ import { viewComponents } from '@object-ui/plugin-view';
41
+ import { ComponentRegistry } from '@object-ui/core';
42
+
43
+ // Register view components
44
+ Object.entries(viewComponents).forEach(([type, component]) => {
45
+ ComponentRegistry.register(type, component);
46
+ });
47
+ ```
48
+
49
+ ## Schema API
50
+
51
+ ### ObjectView
52
+
53
+ Unified view component for ObjectQL objects:
54
+
55
+ ```typescript
56
+ {
57
+ type: 'object-view',
58
+ object: string, // ObjectQL object name
59
+ viewMode?: 'grid' | 'form' | 'detail',
60
+ fields?: string[], // Fields to display
61
+ dataSource?: DataSource,
62
+ onCreate?: (data) => void,
63
+ onUpdate?: (id, data) => void,
64
+ onDelete?: (id) => void,
65
+ className?: string
66
+ }
67
+ ```
68
+
69
+ ## Examples
70
+
71
+ ### Grid View
72
+
73
+ Display objects in a data grid:
74
+
75
+ ```typescript
76
+ const schema = {
77
+ type: 'object-view',
78
+ object: 'users',
79
+ viewMode: 'grid',
80
+ fields: ['name', 'email', 'role', 'created_at'],
81
+ dataSource: myDataSource
82
+ };
83
+ ```
84
+
85
+ ### Form View
86
+
87
+ Create or edit objects with a form:
88
+
89
+ ```typescript
90
+ const schema = {
91
+ type: 'object-view',
92
+ object: 'users',
93
+ viewMode: 'form',
94
+ mode: 'create',
95
+ fields: ['name', 'email', 'role'],
96
+ onSubmit: (data) => {
97
+ console.log('Form submitted:', data);
98
+ }
99
+ };
100
+ ```
101
+
102
+ ### Detail View
103
+
104
+ Display a single object's details:
105
+
106
+ ```typescript
107
+ const schema = {
108
+ type: 'object-view',
109
+ object: 'users',
110
+ viewMode: 'detail',
111
+ recordId: '123',
112
+ fields: ['name', 'email', 'role', 'bio', 'created_at']
113
+ };
114
+ ```
115
+
116
+ ## CRUD Operations
117
+
118
+ ### Create
119
+
120
+ ```typescript
121
+ const schema = {
122
+ type: 'object-view',
123
+ object: 'products',
124
+ viewMode: 'form',
125
+ mode: 'create',
126
+ onCreate: async (data) => {
127
+ const newProduct = await dataSource.create('products', data);
128
+ console.log('Created:', newProduct);
129
+ }
130
+ };
131
+ ```
132
+
133
+ ### Read/List
134
+
135
+ ```typescript
136
+ const schema = {
137
+ type: 'object-view',
138
+ object: 'products',
139
+ viewMode: 'grid',
140
+ pagination: true,
141
+ searchable: true,
142
+ filters: {
143
+ category: 'electronics'
144
+ }
145
+ };
146
+ ```
147
+
148
+ ### Update
149
+
150
+ ```typescript
151
+ const schema = {
152
+ type: 'object-view',
153
+ object: 'products',
154
+ viewMode: 'form',
155
+ mode: 'edit',
156
+ recordId: '123',
157
+ onUpdate: async (id, data) => {
158
+ await dataSource.update('products', id, data);
159
+ console.log('Updated product:', id);
160
+ }
161
+ };
162
+ ```
163
+
164
+ ### Delete
165
+
166
+ ```typescript
167
+ const schema = {
168
+ type: 'object-view',
169
+ object: 'products',
170
+ viewMode: 'grid',
171
+ enableDelete: true,
172
+ onDelete: async (id) => {
173
+ await dataSource.delete('products', id);
174
+ console.log('Deleted product:', id);
175
+ }
176
+ };
177
+ ```
178
+
179
+ ## Integration with ObjectQL
180
+
181
+ The plugin works seamlessly with ObjectStack:
182
+
183
+ ```typescript
184
+ import { createObjectStackAdapter } from '@object-ui/data-objectstack';
185
+
186
+ const dataSource = createObjectStackAdapter({
187
+ baseUrl: 'https://api.example.com',
188
+ token: 'your-auth-token'
189
+ });
190
+
191
+ const schema = {
192
+ type: 'object-view',
193
+ object: 'contacts',
194
+ viewMode: 'grid',
195
+ dataSource,
196
+ fields: ['first_name', 'last_name', 'email', 'company'],
197
+ searchable: true,
198
+ sortable: true,
199
+ pagination: {
200
+ pageSize: 25
201
+ }
202
+ };
203
+ ```
204
+
205
+ ## Field Configuration
206
+
207
+ Customize field display and behavior:
208
+
209
+ ```typescript
210
+ const schema = {
211
+ type: 'object-view',
212
+ object: 'users',
213
+ viewMode: 'form',
214
+ fieldConfig: {
215
+ name: {
216
+ label: 'Full Name',
217
+ required: true,
218
+ placeholder: 'Enter name'
219
+ },
220
+ email: {
221
+ label: 'Email Address',
222
+ type: 'email',
223
+ required: true,
224
+ validation: [
225
+ { type: 'email', message: 'Invalid email format' }
226
+ ]
227
+ },
228
+ role: {
229
+ label: 'User Role',
230
+ type: 'select',
231
+ options: [
232
+ { label: 'Admin', value: 'admin' },
233
+ { label: 'User', value: 'user' },
234
+ { label: 'Guest', value: 'guest' }
235
+ ]
236
+ }
237
+ }
238
+ };
239
+ ```
240
+
241
+ ## Advanced Features
242
+
243
+ ### Nested Objects
244
+
245
+ ```typescript
246
+ const schema = {
247
+ type: 'object-view',
248
+ object: 'orders',
249
+ viewMode: 'detail',
250
+ fields: ['order_number', 'customer.name', 'items', 'total'],
251
+ nestedFields: {
252
+ items: {
253
+ type: 'object-grid',
254
+ object: 'order_items',
255
+ fields: ['product.name', 'quantity', 'price']
256
+ }
257
+ }
258
+ };
259
+ ```
260
+
261
+ ### Tabs View
262
+
263
+ ```typescript
264
+ const schema = {
265
+ type: 'object-view',
266
+ object: 'users',
267
+ viewMode: 'tabs',
268
+ tabs: [
269
+ { label: 'Details', fields: ['name', 'email', 'bio'] },
270
+ { label: 'Settings', fields: ['theme', 'notifications', 'timezone'] },
271
+ { label: 'Activity', type: 'object-grid', object: 'user_activities' }
272
+ ]
273
+ };
274
+ ```
275
+
276
+ ## TypeScript Support
277
+
278
+ ```typescript
279
+ import type { ObjectViewSchema } from '@object-ui/plugin-view';
280
+
281
+ const userView: ObjectViewSchema = {
282
+ type: 'object-view',
283
+ object: 'users',
284
+ viewMode: 'grid',
285
+ fields: ['name', 'email', 'role']
286
+ };
287
+ ```
288
+
289
+ ## License
290
+
291
+ MIT
@@ -0,0 +1,2 @@
1
+ export * from './plugin-view/src/index'
2
+ export {}
package/dist/index.js ADDED
@@ -0,0 +1,475 @@
1
+ import ne, { useState as E, useEffect as ae, useCallback as v } from "react";
2
+ import { ComponentRegistry as le } from "@object-ui/core";
3
+ import { ObjectGrid as se } from "@object-ui/plugin-grid";
4
+ import { ObjectForm as K } from "@object-ui/plugin-form";
5
+ import { Input as ie, Button as Z, Drawer as ce, DrawerContent as ue, DrawerHeader as fe, DrawerTitle as de, DrawerDescription as me, Dialog as be, DialogContent as pe, DialogHeader as ve, DialogTitle as xe, DialogDescription as je } from "@object-ui/components";
6
+ import { Search as we, Filter as _e, X as Re, RefreshCw as Ee, Plus as ge } from "lucide-react";
7
+ var z = { exports: {} }, C = {};
8
+ var ee;
9
+ function he() {
10
+ if (ee) return C;
11
+ ee = 1;
12
+ var r = /* @__PURE__ */ Symbol.for("react.transitional.element"), d = /* @__PURE__ */ Symbol.for("react.fragment");
13
+ function R(x, c, u) {
14
+ var f = null;
15
+ if (u !== void 0 && (f = "" + u), c.key !== void 0 && (f = "" + c.key), "key" in c) {
16
+ u = {};
17
+ for (var b in c)
18
+ b !== "key" && (u[b] = c[b]);
19
+ } else u = c;
20
+ return c = u.ref, {
21
+ $$typeof: r,
22
+ type: x,
23
+ key: f,
24
+ ref: c !== void 0 ? c : null,
25
+ props: u
26
+ };
27
+ }
28
+ return C.Fragment = d, C.jsx = R, C.jsxs = R, C;
29
+ }
30
+ var A = {};
31
+ var re;
32
+ function Ne() {
33
+ return re || (re = 1, process.env.NODE_ENV !== "production" && (function() {
34
+ function r(e) {
35
+ if (e == null) return null;
36
+ if (typeof e == "function")
37
+ return e.$$typeof === X ? null : e.displayName || e.name || null;
38
+ if (typeof e == "string") return e;
39
+ switch (e) {
40
+ case _:
41
+ return "Fragment";
42
+ case p:
43
+ return "Profiler";
44
+ case m:
45
+ return "StrictMode";
46
+ case U:
47
+ return "Suspense";
48
+ case q:
49
+ return "SuspenseList";
50
+ case G:
51
+ return "Activity";
52
+ }
53
+ if (typeof e == "object")
54
+ switch (typeof e.tag == "number" && console.error(
55
+ "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
56
+ ), e.$$typeof) {
57
+ case L:
58
+ return "Portal";
59
+ case B:
60
+ return e.displayName || "Context";
61
+ case W:
62
+ return (e._context.displayName || "Context") + ".Consumer";
63
+ case $:
64
+ var t = e.render;
65
+ return e = e.displayName, e || (e = t.displayName || t.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
66
+ case J:
67
+ return t = e.displayName || null, t !== null ? t : r(e.type) || "Memo";
68
+ case T:
69
+ t = e._payload, e = e._init;
70
+ try {
71
+ return r(e(t));
72
+ } catch {
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ function d(e) {
78
+ return "" + e;
79
+ }
80
+ function R(e) {
81
+ try {
82
+ d(e);
83
+ var t = !1;
84
+ } catch {
85
+ t = !0;
86
+ }
87
+ if (t) {
88
+ t = console;
89
+ var a = t.error, l = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
90
+ return a.call(
91
+ t,
92
+ "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
93
+ l
94
+ ), d(e);
95
+ }
96
+ }
97
+ function x(e) {
98
+ if (e === _) return "<>";
99
+ if (typeof e == "object" && e !== null && e.$$typeof === T)
100
+ return "<...>";
101
+ try {
102
+ var t = r(e);
103
+ return t ? "<" + t + ">" : "<...>";
104
+ } catch {
105
+ return "<...>";
106
+ }
107
+ }
108
+ function c() {
109
+ var e = y.A;
110
+ return e === null ? null : e.getOwner();
111
+ }
112
+ function u() {
113
+ return Error("react-stack-top-frame");
114
+ }
115
+ function f(e) {
116
+ if (S.call(e, "key")) {
117
+ var t = Object.getOwnPropertyDescriptor(e, "key").get;
118
+ if (t && t.isReactWarning) return !1;
119
+ }
120
+ return e.key !== void 0;
121
+ }
122
+ function b(e, t) {
123
+ function a() {
124
+ Y || (Y = !0, console.error(
125
+ "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
126
+ t
127
+ ));
128
+ }
129
+ a.isReactWarning = !0, Object.defineProperty(e, "key", {
130
+ get: a,
131
+ configurable: !0
132
+ });
133
+ }
134
+ function h() {
135
+ var e = r(this.type);
136
+ return M[e] || (M[e] = !0, console.error(
137
+ "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
138
+ )), e = this.props.ref, e !== void 0 ? e : null;
139
+ }
140
+ function N(e, t, a, l, V, H) {
141
+ var s = a.ref;
142
+ return e = {
143
+ $$typeof: D,
144
+ type: e,
145
+ key: t,
146
+ props: a,
147
+ _owner: l
148
+ }, (s !== void 0 ? s : null) !== null ? Object.defineProperty(e, "ref", {
149
+ enumerable: !1,
150
+ get: h
151
+ }) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
152
+ configurable: !1,
153
+ enumerable: !1,
154
+ writable: !0,
155
+ value: 0
156
+ }), Object.defineProperty(e, "_debugInfo", {
157
+ configurable: !1,
158
+ enumerable: !1,
159
+ writable: !0,
160
+ value: null
161
+ }), Object.defineProperty(e, "_debugStack", {
162
+ configurable: !1,
163
+ enumerable: !1,
164
+ writable: !0,
165
+ value: V
166
+ }), Object.defineProperty(e, "_debugTask", {
167
+ configurable: !1,
168
+ enumerable: !1,
169
+ writable: !0,
170
+ value: H
171
+ }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
172
+ }
173
+ function j(e, t, a, l, V, H) {
174
+ var s = t.children;
175
+ if (s !== void 0)
176
+ if (l)
177
+ if (I(s)) {
178
+ for (l = 0; l < s.length; l++)
179
+ P(s[l]);
180
+ Object.freeze && Object.freeze(s);
181
+ } else
182
+ console.error(
183
+ "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
184
+ );
185
+ else P(s);
186
+ if (S.call(t, "key")) {
187
+ s = r(e);
188
+ var g = Object.keys(t).filter(function(oe) {
189
+ return oe !== "key";
190
+ });
191
+ l = 0 < g.length ? "{key: someKey, " + g.join(": ..., ") + ": ...}" : "{key: someKey}", O[s + l] || (g = 0 < g.length ? "{" + g.join(": ..., ") + ": ...}" : "{}", console.error(
192
+ `A props object containing a "key" prop is being spread into JSX:
193
+ let props = %s;
194
+ <%s {...props} />
195
+ React keys must be passed directly to JSX without using spread:
196
+ let props = %s;
197
+ <%s key={someKey} {...props} />`,
198
+ l,
199
+ s,
200
+ g,
201
+ s
202
+ ), O[s + l] = !0);
203
+ }
204
+ if (s = null, a !== void 0 && (R(a), s = "" + a), f(t) && (R(t.key), s = "" + t.key), "key" in t) {
205
+ a = {};
206
+ for (var Q in t)
207
+ Q !== "key" && (a[Q] = t[Q]);
208
+ } else a = t;
209
+ return s && b(
210
+ a,
211
+ typeof e == "function" ? e.displayName || e.name || "Unknown" : e
212
+ ), N(
213
+ e,
214
+ s,
215
+ a,
216
+ c(),
217
+ V,
218
+ H
219
+ );
220
+ }
221
+ function P(e) {
222
+ F(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === T && (e._payload.status === "fulfilled" ? F(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
223
+ }
224
+ function F(e) {
225
+ return typeof e == "object" && e !== null && e.$$typeof === D;
226
+ }
227
+ var w = ne, D = /* @__PURE__ */ Symbol.for("react.transitional.element"), L = /* @__PURE__ */ Symbol.for("react.portal"), _ = /* @__PURE__ */ Symbol.for("react.fragment"), m = /* @__PURE__ */ Symbol.for("react.strict_mode"), p = /* @__PURE__ */ Symbol.for("react.profiler"), W = /* @__PURE__ */ Symbol.for("react.consumer"), B = /* @__PURE__ */ Symbol.for("react.context"), $ = /* @__PURE__ */ Symbol.for("react.forward_ref"), U = /* @__PURE__ */ Symbol.for("react.suspense"), q = /* @__PURE__ */ Symbol.for("react.suspense_list"), J = /* @__PURE__ */ Symbol.for("react.memo"), T = /* @__PURE__ */ Symbol.for("react.lazy"), G = /* @__PURE__ */ Symbol.for("react.activity"), X = /* @__PURE__ */ Symbol.for("react.client.reference"), y = w.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, S = Object.prototype.hasOwnProperty, I = Array.isArray, k = console.createTask ? console.createTask : function() {
228
+ return null;
229
+ };
230
+ w = {
231
+ react_stack_bottom_frame: function(e) {
232
+ return e();
233
+ }
234
+ };
235
+ var Y, M = {}, n = w.react_stack_bottom_frame.bind(
236
+ w,
237
+ u
238
+ )(), i = k(x(u)), O = {};
239
+ A.Fragment = _, A.jsx = function(e, t, a) {
240
+ var l = 1e4 > y.recentlyCreatedOwnerStacks++;
241
+ return j(
242
+ e,
243
+ t,
244
+ a,
245
+ !1,
246
+ l ? Error("react-stack-top-frame") : n,
247
+ l ? k(x(e)) : i
248
+ );
249
+ }, A.jsxs = function(e, t, a) {
250
+ var l = 1e4 > y.recentlyCreatedOwnerStacks++;
251
+ return j(
252
+ e,
253
+ t,
254
+ a,
255
+ !0,
256
+ l ? Error("react-stack-top-frame") : n,
257
+ l ? k(x(e)) : i
258
+ );
259
+ };
260
+ })()), A;
261
+ }
262
+ var te;
263
+ function Te() {
264
+ return te || (te = 1, process.env.NODE_ENV === "production" ? z.exports = he() : z.exports = Ne()), z.exports;
265
+ }
266
+ var o = Te();
267
+ const ye = ({
268
+ schema: r,
269
+ dataSource: d,
270
+ className: R
271
+ }) => {
272
+ const [x, c] = E(null), [u, f] = E(!1), [b, h] = E("create"), [N, j] = E(null), [P, F] = E(""), [w, D] = E(!1), [L, _] = E(0);
273
+ ae(() => {
274
+ const n = async () => {
275
+ try {
276
+ const i = await d.getObjectSchema(r.objectName);
277
+ c(i);
278
+ } catch (i) {
279
+ console.error("Failed to fetch object schema:", i);
280
+ }
281
+ };
282
+ r.objectName && d && n();
283
+ }, [r.objectName, d]);
284
+ const m = r.layout || "drawer", p = r.operations || r.table?.operations || {
285
+ create: !0,
286
+ read: !0,
287
+ update: !0,
288
+ delete: !0
289
+ }, W = v(() => {
290
+ m === "page" && r.onNavigate ? r.onNavigate("new", "edit") : (h("create"), j(null), f(!0));
291
+ }, [m, r]), B = v((n) => {
292
+ if (m === "page" && r.onNavigate) {
293
+ const i = n._id || n.id;
294
+ r.onNavigate(i, "edit");
295
+ } else
296
+ h("edit"), j(n), f(!0);
297
+ }, [m, r]), $ = v((n) => {
298
+ if (m === "page" && r.onNavigate) {
299
+ const i = n._id || n.id;
300
+ r.onNavigate(i, "view");
301
+ } else
302
+ h("view"), j(n), f(!0);
303
+ }, [m, r]), U = v((n) => {
304
+ p.read !== !1 && $(n);
305
+ }, [p.read, $]), q = v((n) => {
306
+ _((i) => i + 1);
307
+ }, []), J = v((n) => {
308
+ _((i) => i + 1);
309
+ }, []), T = v(() => {
310
+ f(!1), j(null), _((n) => n + 1);
311
+ }, []), G = v(() => {
312
+ f(!1), j(null);
313
+ }, []), X = v(() => {
314
+ _((n) => n + 1);
315
+ }, []), y = {
316
+ type: "object-grid",
317
+ objectName: r.objectName,
318
+ title: r.table?.title,
319
+ description: r.table?.description,
320
+ fields: r.table?.fields,
321
+ columns: r.table?.columns,
322
+ operations: {
323
+ ...p,
324
+ create: !1
325
+ // Create is handled by the view's create button
326
+ },
327
+ defaultFilters: r.table?.defaultFilters,
328
+ defaultSort: r.table?.defaultSort,
329
+ pageSize: r.table?.pageSize,
330
+ selectable: r.table?.selectable,
331
+ className: r.table?.className
332
+ }, S = () => {
333
+ const n = N ? N._id || N.id : void 0;
334
+ return {
335
+ type: "object-form",
336
+ objectName: r.objectName,
337
+ mode: b,
338
+ recordId: n,
339
+ title: r.form?.title,
340
+ description: r.form?.description,
341
+ fields: r.form?.fields,
342
+ customFields: r.form?.customFields,
343
+ groups: r.form?.groups,
344
+ layout: r.form?.layout,
345
+ columns: r.form?.columns,
346
+ showSubmit: r.form?.showSubmit,
347
+ submitText: r.form?.submitText,
348
+ showCancel: r.form?.showCancel,
349
+ cancelText: r.form?.cancelText,
350
+ showReset: r.form?.showReset,
351
+ initialValues: r.form?.initialValues,
352
+ readOnly: r.form?.readOnly || b === "view",
353
+ className: r.form?.className,
354
+ onSuccess: T,
355
+ onCancel: G
356
+ };
357
+ }, I = () => {
358
+ if (r.form?.title) return r.form.title;
359
+ const n = x?.label || r.objectName;
360
+ switch (b) {
361
+ case "create":
362
+ return `Create ${n}`;
363
+ case "edit":
364
+ return `Edit ${n}`;
365
+ case "view":
366
+ return `View ${n}`;
367
+ default:
368
+ return n;
369
+ }
370
+ }, k = () => /* @__PURE__ */ o.jsx(ce, { open: u, onOpenChange: f, direction: "right", children: /* @__PURE__ */ o.jsxs(ue, { className: "w-full sm:max-w-2xl", children: [
371
+ /* @__PURE__ */ o.jsxs(fe, { children: [
372
+ /* @__PURE__ */ o.jsx(de, { children: I() }),
373
+ r.form?.description && /* @__PURE__ */ o.jsx(me, { children: r.form.description })
374
+ ] }),
375
+ /* @__PURE__ */ o.jsx("div", { className: "flex-1 overflow-y-auto px-4 pb-4", children: /* @__PURE__ */ o.jsx(
376
+ K,
377
+ {
378
+ schema: S(),
379
+ dataSource: d
380
+ }
381
+ ) })
382
+ ] }) }), Y = () => /* @__PURE__ */ o.jsx(be, { open: u, onOpenChange: f, children: /* @__PURE__ */ o.jsxs(pe, { className: "max-w-2xl max-h-[90vh] overflow-y-auto", children: [
383
+ /* @__PURE__ */ o.jsxs(ve, { children: [
384
+ /* @__PURE__ */ o.jsx(xe, { children: I() }),
385
+ r.form?.description && /* @__PURE__ */ o.jsx(je, { children: r.form.description })
386
+ ] }),
387
+ /* @__PURE__ */ o.jsx(
388
+ K,
389
+ {
390
+ schema: S(),
391
+ dataSource: d
392
+ }
393
+ )
394
+ ] }) }), M = () => {
395
+ const n = r.showSearch !== !1, i = r.showFilters !== !1, O = r.showCreate !== !1 && p.create !== !1, e = r.showRefresh !== !1;
396
+ return !n && !i && !O && !e ? null : /* @__PURE__ */ o.jsxs("div", { className: "flex flex-col gap-4 mb-4", children: [
397
+ /* @__PURE__ */ o.jsxs("div", { className: "flex items-center justify-between gap-4", children: [
398
+ /* @__PURE__ */ o.jsx("div", { className: "flex-1 max-w-md", children: n && /* @__PURE__ */ o.jsxs("div", { className: "relative", children: [
399
+ /* @__PURE__ */ o.jsx(we, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
400
+ /* @__PURE__ */ o.jsx(
401
+ ie,
402
+ {
403
+ type: "search",
404
+ placeholder: `Search ${x?.label || r.objectName}...`,
405
+ value: P,
406
+ onChange: (t) => F(t.target.value),
407
+ className: "pl-9"
408
+ }
409
+ )
410
+ ] }) }),
411
+ /* @__PURE__ */ o.jsxs("div", { className: "flex items-center gap-2", children: [
412
+ i && /* @__PURE__ */ o.jsxs(
413
+ Z,
414
+ {
415
+ variant: "outline",
416
+ size: "sm",
417
+ onClick: () => D(!w),
418
+ children: [
419
+ /* @__PURE__ */ o.jsx(_e, { className: "h-4 w-4" }),
420
+ "Filters",
421
+ w && /* @__PURE__ */ o.jsx(Re, { className: "h-3 w-3 ml-1" })
422
+ ]
423
+ }
424
+ ),
425
+ e && /* @__PURE__ */ o.jsx(
426
+ Z,
427
+ {
428
+ variant: "outline",
429
+ size: "sm",
430
+ onClick: X,
431
+ children: /* @__PURE__ */ o.jsx(Ee, { className: "h-4 w-4" })
432
+ }
433
+ ),
434
+ O && /* @__PURE__ */ o.jsxs(
435
+ Z,
436
+ {
437
+ size: "sm",
438
+ onClick: W,
439
+ children: [
440
+ /* @__PURE__ */ o.jsx(ge, { className: "h-4 w-4" }),
441
+ "Create"
442
+ ]
443
+ }
444
+ )
445
+ ] })
446
+ ] }),
447
+ w && /* @__PURE__ */ o.jsx("div", { className: "p-4 border rounded-md bg-muted/50", children: /* @__PURE__ */ o.jsx("p", { className: "text-sm text-muted-foreground", children: "Filter functionality will be integrated with FilterBuilder component" }) })
448
+ ] });
449
+ };
450
+ return /* @__PURE__ */ o.jsxs("div", { className: R, children: [
451
+ (r.title || r.description) && /* @__PURE__ */ o.jsxs("div", { className: "mb-6", children: [
452
+ r.title && /* @__PURE__ */ o.jsx("h2", { className: "text-2xl font-bold tracking-tight", children: r.title }),
453
+ r.description && /* @__PURE__ */ o.jsx("p", { className: "text-muted-foreground mt-1", children: r.description })
454
+ ] }),
455
+ M(),
456
+ /* @__PURE__ */ o.jsx(
457
+ se,
458
+ {
459
+ schema: y,
460
+ dataSource: d,
461
+ onRowClick: U,
462
+ onEdit: p.update !== !1 ? B : void 0,
463
+ onDelete: p.delete !== !1 ? q : void 0,
464
+ onBulkDelete: p.delete !== !1 ? J : void 0
465
+ },
466
+ L
467
+ ),
468
+ m === "drawer" && k(),
469
+ m === "modal" && Y()
470
+ ] });
471
+ }, Se = ({ schema: r }) => /* @__PURE__ */ o.jsx(ye, { schema: r, dataSource: null });
472
+ le.register("object-view", Se);
473
+ export {
474
+ ye as ObjectView
475
+ };
@@ -0,0 +1,6 @@
1
+ (function(m,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react"),require("@object-ui/core"),require("@object-ui/plugin-grid"),require("@object-ui/plugin-form"),require("@object-ui/components"),require("lucide-react")):typeof define=="function"&&define.amd?define(["exports","react","@object-ui/core","@object-ui/plugin-grid","@object-ui/plugin-form","@object-ui/components","lucide-react"],a):(m=typeof globalThis<"u"?globalThis:m||self,a(m.ObjectUIPluginView={},m.React,m.core,m.pluginGrid,m.pluginForm,m.components,m.lucideReact))})(this,(function(m,a,ae,le,ee,u,T){"use strict";var D={exports:{}},N={};var te;function se(){if(te)return N;te=1;var t=Symbol.for("react.transitional.element"),p=Symbol.for("react.fragment");function g(w,f,d){var b=null;if(d!==void 0&&(b=""+d),f.key!==void 0&&(b=""+f.key),"key"in f){d={};for(var v in f)v!=="key"&&(d[v]=f[v])}else d=f;return f=d.ref,{$$typeof:t,type:w,key:b,ref:f!==void 0?f:null,props:d}}return N.Fragment=p,N.jsx=g,N.jsxs=g,N}var S={};var re;function ie(){return re||(re=1,process.env.NODE_ENV!=="production"&&(function(){function t(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===K?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case R:return"Fragment";case x:return"Profiler";case j:return"StrictMode";case X:return"Suspense";case H:return"SuspenseList";case Z:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case W:return"Portal";case J:return e.displayName||"Context";case G:return(e._context.displayName||"Context")+".Consumer";case M:var r=e.render;return e=e.displayName,e||(e=r.displayName||r.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Q:return r=e.displayName||null,r!==null?r:t(e.type)||"Memo";case O:r=e._payload,e=e._init;try{return t(e(r))}catch{}}return null}function p(e){return""+e}function g(e){try{p(e);var r=!1}catch{r=!0}if(r){r=console;var l=r.error,s=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return l.call(r,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",s),p(e)}}function w(e){if(e===R)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===O)return"<...>";try{var r=t(e);return r?"<"+r+">":"<...>"}catch{return"<...>"}}function f(){var e=C.A;return e===null?null:e.getOwner()}function d(){return Error("react-stack-top-frame")}function b(e){if(P.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function v(e,r){function l(){B||(B=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",r))}l.isReactWarning=!0,Object.defineProperty(e,"key",{get:l,configurable:!0})}function k(){var e=t(this.type);return L[e]||(L[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function y(e,r,l,s,U,q){var i=l.ref;return e={$$typeof:V,type:e,key:r,props:l,_owner:s},(i!==void 0?i:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:k}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:U}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:q}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function _(e,r,l,s,U,q){var i=r.children;if(i!==void 0)if(s)if(z(i)){for(s=0;s<i.length;s++)I(i[s]);Object.freeze&&Object.freeze(i)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else I(i);if(P.call(r,"key")){i=t(e);var h=Object.keys(r).filter(function(fe){return fe!=="key"});s=0<h.length?"{key: someKey, "+h.join(": ..., ")+": ...}":"{key: someKey}",F[i+s]||(h=0<h.length?"{"+h.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
2
+ let props = %s;
3
+ <%s {...props} />
4
+ React keys must be passed directly to JSX without using spread:
5
+ let props = %s;
6
+ <%s key={someKey} {...props} />`,s,i,h,i),F[i+s]=!0)}if(i=null,l!==void 0&&(g(l),i=""+l),b(r)&&(g(r.key),i=""+r.key),"key"in r){l={};for(var $ in r)$!=="key"&&(l[$]=r[$])}else l=r;return i&&v(l,typeof e=="function"?e.displayName||e.name||"Unknown":e),y(e,i,l,f(),U,q)}function I(e){Y(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===O&&(e._payload.status==="fulfilled"?Y(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function Y(e){return typeof e=="object"&&e!==null&&e.$$typeof===V}var E=a,V=Symbol.for("react.transitional.element"),W=Symbol.for("react.portal"),R=Symbol.for("react.fragment"),j=Symbol.for("react.strict_mode"),x=Symbol.for("react.profiler"),G=Symbol.for("react.consumer"),J=Symbol.for("react.context"),M=Symbol.for("react.forward_ref"),X=Symbol.for("react.suspense"),H=Symbol.for("react.suspense_list"),Q=Symbol.for("react.memo"),O=Symbol.for("react.lazy"),Z=Symbol.for("react.activity"),K=Symbol.for("react.client.reference"),C=E.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,P=Object.prototype.hasOwnProperty,z=Array.isArray,A=console.createTask?console.createTask:function(){return null};E={react_stack_bottom_frame:function(e){return e()}};var B,L={},n=E.react_stack_bottom_frame.bind(E,d)(),c=A(w(d)),F={};S.Fragment=R,S.jsx=function(e,r,l){var s=1e4>C.recentlyCreatedOwnerStacks++;return _(e,r,l,!1,s?Error("react-stack-top-frame"):n,s?A(w(e)):c)},S.jsxs=function(e,r,l){var s=1e4>C.recentlyCreatedOwnerStacks++;return _(e,r,l,!0,s?Error("react-stack-top-frame"):n,s?A(w(e)):c)}})()),S}var oe;function ce(){return oe||(oe=1,process.env.NODE_ENV==="production"?D.exports=se():D.exports=ie()),D.exports}var o=ce();const ne=({schema:t,dataSource:p,className:g})=>{const[w,f]=a.useState(null),[d,b]=a.useState(!1),[v,k]=a.useState("create"),[y,_]=a.useState(null),[I,Y]=a.useState(""),[E,V]=a.useState(!1),[W,R]=a.useState(0);a.useEffect(()=>{const n=async()=>{try{const c=await p.getObjectSchema(t.objectName);f(c)}catch(c){console.error("Failed to fetch object schema:",c)}};t.objectName&&p&&n()},[t.objectName,p]);const j=t.layout||"drawer",x=t.operations||t.table?.operations||{create:!0,read:!0,update:!0,delete:!0},G=a.useCallback(()=>{j==="page"&&t.onNavigate?t.onNavigate("new","edit"):(k("create"),_(null),b(!0))},[j,t]),J=a.useCallback(n=>{if(j==="page"&&t.onNavigate){const c=n._id||n.id;t.onNavigate(c,"edit")}else k("edit"),_(n),b(!0)},[j,t]),M=a.useCallback(n=>{if(j==="page"&&t.onNavigate){const c=n._id||n.id;t.onNavigate(c,"view")}else k("view"),_(n),b(!0)},[j,t]),X=a.useCallback(n=>{x.read!==!1&&M(n)},[x.read,M]),H=a.useCallback(n=>{R(c=>c+1)},[]),Q=a.useCallback(n=>{R(c=>c+1)},[]),O=a.useCallback(()=>{b(!1),_(null),R(n=>n+1)},[]),Z=a.useCallback(()=>{b(!1),_(null)},[]),K=a.useCallback(()=>{R(n=>n+1)},[]),C={type:"object-grid",objectName:t.objectName,title:t.table?.title,description:t.table?.description,fields:t.table?.fields,columns:t.table?.columns,operations:{...x,create:!1},defaultFilters:t.table?.defaultFilters,defaultSort:t.table?.defaultSort,pageSize:t.table?.pageSize,selectable:t.table?.selectable,className:t.table?.className},P=()=>{const n=y?y._id||y.id:void 0;return{type:"object-form",objectName:t.objectName,mode:v,recordId:n,title:t.form?.title,description:t.form?.description,fields:t.form?.fields,customFields:t.form?.customFields,groups:t.form?.groups,layout:t.form?.layout,columns:t.form?.columns,showSubmit:t.form?.showSubmit,submitText:t.form?.submitText,showCancel:t.form?.showCancel,cancelText:t.form?.cancelText,showReset:t.form?.showReset,initialValues:t.form?.initialValues,readOnly:t.form?.readOnly||v==="view",className:t.form?.className,onSuccess:O,onCancel:Z}},z=()=>{if(t.form?.title)return t.form.title;const n=w?.label||t.objectName;switch(v){case"create":return`Create ${n}`;case"edit":return`Edit ${n}`;case"view":return`View ${n}`;default:return n}},A=()=>o.jsx(u.Drawer,{open:d,onOpenChange:b,direction:"right",children:o.jsxs(u.DrawerContent,{className:"w-full sm:max-w-2xl",children:[o.jsxs(u.DrawerHeader,{children:[o.jsx(u.DrawerTitle,{children:z()}),t.form?.description&&o.jsx(u.DrawerDescription,{children:t.form.description})]}),o.jsx("div",{className:"flex-1 overflow-y-auto px-4 pb-4",children:o.jsx(ee.ObjectForm,{schema:P(),dataSource:p})})]})}),B=()=>o.jsx(u.Dialog,{open:d,onOpenChange:b,children:o.jsxs(u.DialogContent,{className:"max-w-2xl max-h-[90vh] overflow-y-auto",children:[o.jsxs(u.DialogHeader,{children:[o.jsx(u.DialogTitle,{children:z()}),t.form?.description&&o.jsx(u.DialogDescription,{children:t.form.description})]}),o.jsx(ee.ObjectForm,{schema:P(),dataSource:p})]})}),L=()=>{const n=t.showSearch!==!1,c=t.showFilters!==!1,F=t.showCreate!==!1&&x.create!==!1,e=t.showRefresh!==!1;return!n&&!c&&!F&&!e?null:o.jsxs("div",{className:"flex flex-col gap-4 mb-4",children:[o.jsxs("div",{className:"flex items-center justify-between gap-4",children:[o.jsx("div",{className:"flex-1 max-w-md",children:n&&o.jsxs("div",{className:"relative",children:[o.jsx(T.Search,{className:"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"}),o.jsx(u.Input,{type:"search",placeholder:`Search ${w?.label||t.objectName}...`,value:I,onChange:r=>Y(r.target.value),className:"pl-9"})]})}),o.jsxs("div",{className:"flex items-center gap-2",children:[c&&o.jsxs(u.Button,{variant:"outline",size:"sm",onClick:()=>V(!E),children:[o.jsx(T.Filter,{className:"h-4 w-4"}),"Filters",E&&o.jsx(T.X,{className:"h-3 w-3 ml-1"})]}),e&&o.jsx(u.Button,{variant:"outline",size:"sm",onClick:K,children:o.jsx(T.RefreshCw,{className:"h-4 w-4"})}),F&&o.jsxs(u.Button,{size:"sm",onClick:G,children:[o.jsx(T.Plus,{className:"h-4 w-4"}),"Create"]})]})]}),E&&o.jsx("div",{className:"p-4 border rounded-md bg-muted/50",children:o.jsx("p",{className:"text-sm text-muted-foreground",children:"Filter functionality will be integrated with FilterBuilder component"})})]})};return o.jsxs("div",{className:g,children:[(t.title||t.description)&&o.jsxs("div",{className:"mb-6",children:[t.title&&o.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:t.title}),t.description&&o.jsx("p",{className:"text-muted-foreground mt-1",children:t.description})]}),L(),o.jsx(le.ObjectGrid,{schema:C,dataSource:p,onRowClick:X,onEdit:x.update!==!1?J:void 0,onDelete:x.delete!==!1?H:void 0,onBulkDelete:x.delete!==!1?Q:void 0},W),j==="drawer"&&A(),j==="modal"&&B()]})},ue=({schema:t})=>o.jsx(ne,{schema:t,dataSource:null});ae.ComponentRegistry.register("object-view",ue),m.ObjectView=ne,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,36 @@
1
+ import { default as React } from 'react';
2
+ import { ObjectViewSchema, DataSource } from '../../types/src';
3
+ export interface ObjectViewProps {
4
+ /**
5
+ * The schema configuration for the view
6
+ */
7
+ schema: ObjectViewSchema;
8
+ /**
9
+ * Data source (ObjectQL or ObjectStack adapter)
10
+ */
11
+ dataSource: DataSource;
12
+ /**
13
+ * Additional CSS class
14
+ */
15
+ className?: string;
16
+ }
17
+ /**
18
+ * ObjectView Component
19
+ *
20
+ * Renders a complete object management interface with table and forms.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <ObjectView
25
+ * schema={{
26
+ * type: 'object-view',
27
+ * objectName: 'users',
28
+ * layout: 'drawer',
29
+ * showSearch: true,
30
+ * showFilters: true
31
+ * }}
32
+ * dataSource={dataSource}
33
+ * />
34
+ * ```
35
+ */
36
+ export declare const ObjectView: React.FC<ObjectViewProps>;
@@ -0,0 +1,3 @@
1
+ import { ObjectView } from './ObjectView';
2
+ export { ObjectView };
3
+ export type { ObjectViewProps } from './ObjectView';
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@object-ui/plugin-view",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Object View plugin for Object UI",
7
+ "main": "dist/index.umd.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.umd.cjs"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "lucide-react": "^0.563.0",
19
+ "@object-ui/components": "0.3.0",
20
+ "@object-ui/plugin-form": "0.3.0",
21
+ "@object-ui/core": "0.3.0",
22
+ "@object-ui/plugin-grid": "0.3.0",
23
+ "@object-ui/react": "0.3.0",
24
+ "@object-ui/types": "0.3.0"
25
+ },
26
+ "peerDependencies": {
27
+ "react": "^18.0.0 || ^19.0.0",
28
+ "react-dom": "^18.0.0 || ^19.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@vitejs/plugin-react": "^4.2.1",
32
+ "typescript": "^5.9.3",
33
+ "vite": "^7.3.1",
34
+ "vite-plugin-dts": "^4.5.4"
35
+ },
36
+ "scripts": {
37
+ "build": "vite build",
38
+ "test": "vitest run",
39
+ "lint": "eslint ."
40
+ }
41
+ }
@@ -0,0 +1,409 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ /**
10
+ * ObjectView Component
11
+ *
12
+ * A complete object management interface that combines ObjectGrid and ObjectForm.
13
+ * Provides list view with integrated search, filters, and create/edit operations.
14
+ */
15
+
16
+ import React, { useEffect, useState, useCallback } from 'react';
17
+ import type { ObjectViewSchema, ObjectGridSchema, ObjectFormSchema, DataSource } from '@object-ui/types';
18
+ import { ObjectGrid } from '@object-ui/plugin-grid';
19
+ import { ObjectForm } from '@object-ui/plugin-form';
20
+ import {
21
+ Dialog,
22
+ DialogContent,
23
+ DialogHeader,
24
+ DialogTitle,
25
+ DialogDescription,
26
+ Drawer,
27
+ DrawerContent,
28
+ DrawerHeader,
29
+ DrawerTitle,
30
+ DrawerDescription,
31
+ Button,
32
+ Input,
33
+ } from '@object-ui/components';
34
+ import { Plus, Search, RefreshCw, Filter, X } from 'lucide-react';
35
+
36
+ export interface ObjectViewProps {
37
+ /**
38
+ * The schema configuration for the view
39
+ */
40
+ schema: ObjectViewSchema;
41
+
42
+ /**
43
+ * Data source (ObjectQL or ObjectStack adapter)
44
+ */
45
+ dataSource: DataSource;
46
+
47
+ /**
48
+ * Additional CSS class
49
+ */
50
+ className?: string;
51
+ }
52
+
53
+ type FormMode = 'create' | 'edit' | 'view';
54
+
55
+ /**
56
+ * ObjectView Component
57
+ *
58
+ * Renders a complete object management interface with table and forms.
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * <ObjectView
63
+ * schema={{
64
+ * type: 'object-view',
65
+ * objectName: 'users',
66
+ * layout: 'drawer',
67
+ * showSearch: true,
68
+ * showFilters: true
69
+ * }}
70
+ * dataSource={dataSource}
71
+ * />
72
+ * ```
73
+ */
74
+ export const ObjectView: React.FC<ObjectViewProps> = ({
75
+ schema,
76
+ dataSource,
77
+ className,
78
+ }) => {
79
+ const [objectSchema, setObjectSchema] = useState<Record<string, unknown> | null>(null);
80
+ const [isFormOpen, setIsFormOpen] = useState(false);
81
+ const [formMode, setFormMode] = useState<FormMode>('create');
82
+ const [selectedRecord, setSelectedRecord] = useState<Record<string, unknown> | null>(null);
83
+ const [searchQuery, setSearchQuery] = useState('');
84
+ const [showFilters, setShowFilters] = useState(false);
85
+ const [refreshKey, setRefreshKey] = useState(0);
86
+
87
+ // Fetch object schema from ObjectQL/ObjectStack
88
+ useEffect(() => {
89
+ const fetchObjectSchema = async () => {
90
+ try {
91
+ const schemaData = await dataSource.getObjectSchema(schema.objectName);
92
+ setObjectSchema(schemaData);
93
+ } catch (err) {
94
+ console.error('Failed to fetch object schema:', err);
95
+ }
96
+ };
97
+
98
+ if (schema.objectName && dataSource) {
99
+ fetchObjectSchema();
100
+ }
101
+ }, [schema.objectName, dataSource]);
102
+
103
+ // Determine layout mode
104
+ const layout = schema.layout || 'drawer';
105
+
106
+ // Determine enabled operations
107
+ const operations = schema.operations || schema.table?.operations || {
108
+ create: true,
109
+ read: true,
110
+ update: true,
111
+ delete: true,
112
+ };
113
+
114
+ // Handle create action
115
+ const handleCreate = useCallback(() => {
116
+ if (layout === 'page' && schema.onNavigate) {
117
+ schema.onNavigate('new', 'edit');
118
+ } else {
119
+ setFormMode('create');
120
+ setSelectedRecord(null);
121
+ setIsFormOpen(true);
122
+ }
123
+ }, [layout, schema]);
124
+
125
+ // Handle edit action
126
+ const handleEdit = useCallback((record: Record<string, unknown>) => {
127
+ if (layout === 'page' && schema.onNavigate) {
128
+ const recordId = record._id || record.id;
129
+ schema.onNavigate(recordId as string | number, 'edit');
130
+ } else {
131
+ setFormMode('edit');
132
+ setSelectedRecord(record);
133
+ setIsFormOpen(true);
134
+ }
135
+ }, [layout, schema]);
136
+
137
+ // Handle view action
138
+ const handleView = useCallback((record: Record<string, unknown>) => {
139
+ if (layout === 'page' && schema.onNavigate) {
140
+ const recordId = record._id || record.id;
141
+ schema.onNavigate(recordId as string | number, 'view');
142
+ } else {
143
+ setFormMode('view');
144
+ setSelectedRecord(record);
145
+ setIsFormOpen(true);
146
+ }
147
+ }, [layout, schema]);
148
+
149
+ // Handle row click
150
+ const handleRowClick = useCallback((record: Record<string, unknown>) => {
151
+ if (operations.read !== false) {
152
+ handleView(record);
153
+ }
154
+ }, [operations.read, handleView]);
155
+
156
+ // Handle delete action
157
+ const handleDelete = useCallback((_record: Record<string, unknown>) => {
158
+ // Trigger table refresh after delete
159
+ setRefreshKey(prev => prev + 1);
160
+ }, []);
161
+
162
+ // Handle bulk delete action
163
+ const handleBulkDelete = useCallback((_records: Record<string, unknown>[]) => {
164
+ // Trigger table refresh after bulk delete
165
+ setRefreshKey(prev => prev + 1);
166
+ }, []);
167
+
168
+ // Handle form submission
169
+ const handleFormSuccess = useCallback(() => {
170
+ // Close the form
171
+ setIsFormOpen(false);
172
+ setSelectedRecord(null);
173
+
174
+ // Trigger table refresh
175
+ setRefreshKey(prev => prev + 1);
176
+ }, []);
177
+
178
+ // Handle form cancellation
179
+ const handleFormCancel = useCallback(() => {
180
+ setIsFormOpen(false);
181
+ setSelectedRecord(null);
182
+ }, []);
183
+
184
+ // Handle refresh
185
+ const handleRefresh = useCallback(() => {
186
+ setRefreshKey(prev => prev + 1);
187
+ }, []);
188
+
189
+ // Build grid schema
190
+ const gridSchema: ObjectGridSchema = {
191
+ type: 'object-grid',
192
+ objectName: schema.objectName,
193
+ title: schema.table?.title,
194
+ description: schema.table?.description,
195
+ fields: schema.table?.fields,
196
+ columns: schema.table?.columns,
197
+ operations: {
198
+ ...operations,
199
+ create: false, // Create is handled by the view's create button
200
+ },
201
+ defaultFilters: schema.table?.defaultFilters,
202
+ defaultSort: schema.table?.defaultSort,
203
+ pageSize: schema.table?.pageSize,
204
+ selectable: schema.table?.selectable,
205
+ className: schema.table?.className,
206
+ };
207
+
208
+ // Build form schema
209
+ const buildFormSchema = (): ObjectFormSchema => {
210
+ const recordId = selectedRecord ? (selectedRecord._id || selectedRecord.id) as string | number | undefined : undefined;
211
+
212
+ return {
213
+ type: 'object-form',
214
+ objectName: schema.objectName,
215
+ mode: formMode,
216
+ recordId,
217
+ title: schema.form?.title,
218
+ description: schema.form?.description,
219
+ fields: schema.form?.fields,
220
+ customFields: schema.form?.customFields,
221
+ groups: schema.form?.groups,
222
+ layout: schema.form?.layout,
223
+ columns: schema.form?.columns,
224
+ showSubmit: schema.form?.showSubmit,
225
+ submitText: schema.form?.submitText,
226
+ showCancel: schema.form?.showCancel,
227
+ cancelText: schema.form?.cancelText,
228
+ showReset: schema.form?.showReset,
229
+ initialValues: schema.form?.initialValues,
230
+ readOnly: schema.form?.readOnly || formMode === 'view',
231
+ className: schema.form?.className,
232
+ onSuccess: handleFormSuccess,
233
+ onCancel: handleFormCancel,
234
+ };
235
+ };
236
+
237
+ // Get form title based on mode
238
+ const getFormTitle = (): string => {
239
+ if (schema.form?.title) return schema.form.title;
240
+
241
+ const objectLabel = (objectSchema?.label as string) || schema.objectName;
242
+
243
+ switch (formMode) {
244
+ case 'create':
245
+ return `Create ${objectLabel}`;
246
+ case 'edit':
247
+ return `Edit ${objectLabel}`;
248
+ case 'view':
249
+ return `View ${objectLabel}`;
250
+ default:
251
+ return objectLabel;
252
+ }
253
+ };
254
+
255
+ // Render the form in a drawer
256
+ const renderDrawerForm = () => (
257
+ <Drawer open={isFormOpen} onOpenChange={setIsFormOpen} direction="right">
258
+ <DrawerContent className="w-full sm:max-w-2xl">
259
+ <DrawerHeader>
260
+ <DrawerTitle>{getFormTitle()}</DrawerTitle>
261
+ {schema.form?.description && (
262
+ <DrawerDescription>{schema.form.description}</DrawerDescription>
263
+ )}
264
+ </DrawerHeader>
265
+ <div className="flex-1 overflow-y-auto px-4 pb-4">
266
+ <ObjectForm
267
+ schema={buildFormSchema()}
268
+ dataSource={dataSource}
269
+ />
270
+ </div>
271
+ </DrawerContent>
272
+ </Drawer>
273
+ );
274
+
275
+ // Render the form in a modal
276
+ const renderModalForm = () => (
277
+ <Dialog open={isFormOpen} onOpenChange={setIsFormOpen}>
278
+ <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
279
+ <DialogHeader>
280
+ <DialogTitle>{getFormTitle()}</DialogTitle>
281
+ {schema.form?.description && (
282
+ <DialogDescription>{schema.form.description}</DialogDescription>
283
+ )}
284
+ </DialogHeader>
285
+ <ObjectForm
286
+ schema={buildFormSchema()}
287
+ dataSource={dataSource}
288
+ />
289
+ </DialogContent>
290
+ </Dialog>
291
+ );
292
+
293
+ // Render toolbar
294
+ const renderToolbar = () => {
295
+ const showSearchBox = schema.showSearch !== false;
296
+ const showFiltersButton = schema.showFilters !== false;
297
+ const showCreateButton = schema.showCreate !== false && operations.create !== false;
298
+ const showRefreshButton = schema.showRefresh !== false;
299
+
300
+ // Don't render toolbar if no elements are shown
301
+ if (!showSearchBox && !showFiltersButton && !showCreateButton && !showRefreshButton) {
302
+ return null;
303
+ }
304
+
305
+ return (
306
+ <div className="flex flex-col gap-4 mb-4">
307
+ {/* Main toolbar row */}
308
+ <div className="flex items-center justify-between gap-4">
309
+ {/* Left side: Search */}
310
+ <div className="flex-1 max-w-md">
311
+ {showSearchBox && (
312
+ <div className="relative">
313
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
314
+ <Input
315
+ type="search"
316
+ placeholder={`Search ${objectSchema?.label || schema.objectName}...`}
317
+ value={searchQuery}
318
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
319
+ className="pl-9"
320
+ />
321
+ </div>
322
+ )}
323
+ </div>
324
+
325
+ {/* Right side: Actions */}
326
+ <div className="flex items-center gap-2">
327
+ {showFiltersButton && (
328
+ <Button
329
+ variant="outline"
330
+ size="sm"
331
+ onClick={() => setShowFilters(!showFilters)}
332
+ >
333
+ <Filter className="h-4 w-4" />
334
+ Filters
335
+ {showFilters && (
336
+ <X className="h-3 w-3 ml-1" />
337
+ )}
338
+ </Button>
339
+ )}
340
+
341
+ {showRefreshButton && (
342
+ <Button
343
+ variant="outline"
344
+ size="sm"
345
+ onClick={handleRefresh}
346
+ >
347
+ <RefreshCw className="h-4 w-4" />
348
+ </Button>
349
+ )}
350
+
351
+ {showCreateButton && (
352
+ <Button
353
+ size="sm"
354
+ onClick={handleCreate}
355
+ >
356
+ <Plus className="h-4 w-4" />
357
+ Create
358
+ </Button>
359
+ )}
360
+ </div>
361
+ </div>
362
+
363
+ {/* Filter panel (shown when filters are active) */}
364
+ {showFilters && (
365
+ <div className="p-4 border rounded-md bg-muted/50">
366
+ <p className="text-sm text-muted-foreground">
367
+ Filter functionality will be integrated with FilterBuilder component
368
+ </p>
369
+ {/* TODO: Integrate FilterBuilder component here */}
370
+ </div>
371
+ )}
372
+ </div>
373
+ );
374
+ };
375
+
376
+ return (
377
+ <div className={className}>
378
+ {/* Title and description */}
379
+ {(schema.title || schema.description) && (
380
+ <div className="mb-6">
381
+ {schema.title && (
382
+ <h2 className="text-2xl font-bold tracking-tight">{schema.title}</h2>
383
+ )}
384
+ {schema.description && (
385
+ <p className="text-muted-foreground mt-1">{schema.description}</p>
386
+ )}
387
+ </div>
388
+ )}
389
+
390
+ {/* Toolbar */}
391
+ {renderToolbar()}
392
+
393
+ {/* Grid */}
394
+ <ObjectGrid
395
+ key={refreshKey}
396
+ schema={gridSchema}
397
+ dataSource={dataSource}
398
+ onRowClick={handleRowClick}
399
+ onEdit={operations.update !== false ? handleEdit : undefined}
400
+ onDelete={operations.delete !== false ? handleDelete : undefined}
401
+ onBulkDelete={operations.delete !== false ? handleBulkDelete : undefined}
402
+ />
403
+
404
+ {/* Form (drawer or modal) */}
405
+ {layout === 'drawer' && renderDrawerForm()}
406
+ {layout === 'modal' && renderModalForm()}
407
+ </div>
408
+ );
409
+ };
package/src/index.tsx ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import React from 'react';
10
+ import { ComponentRegistry } from '@object-ui/core';
11
+ import { ObjectView } from './ObjectView';
12
+
13
+ export { ObjectView };
14
+ export type { ObjectViewProps } from './ObjectView';
15
+
16
+ // Register object-view component
17
+ const ObjectViewRenderer: React.FC<{ schema: any }> = ({ schema }) => {
18
+ return <ObjectView schema={schema} dataSource={null as any} />;
19
+ };
20
+
21
+ ComponentRegistry.register('object-view', ObjectViewRenderer);
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "jsx": "react-jsx"
6
+ },
7
+ "include": ["src"]
8
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import dts from 'vite-plugin-dts';
4
+ import { resolve } from 'path';
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ react(),
9
+ dts({
10
+ insertTypesEntry: true,
11
+ include: ['src'],
12
+ }),
13
+ ],
14
+ build: {
15
+ lib: {
16
+ entry: resolve(__dirname, 'src/index.tsx'),
17
+ name: 'ObjectUIPluginView',
18
+ fileName: 'index',
19
+ },
20
+ rollupOptions: {
21
+ external: [
22
+ 'react',
23
+ 'react-dom',
24
+ '@object-ui/components',
25
+ '@object-ui/core',
26
+ '@object-ui/plugin-form',
27
+ '@object-ui/plugin-grid',
28
+ '@object-ui/react',
29
+ '@object-ui/types',
30
+ 'lucide-react'
31
+ ],
32
+ output: {
33
+ globals: {
34
+ react: 'React',
35
+ 'react-dom': 'ReactDOM',
36
+ },
37
+ },
38
+ },
39
+ },
40
+ });