@magnet-cms/plugin-playground 2.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/dist/backend/index.cjs +1023 -0
- package/dist/backend/index.d.cts +10 -0
- package/dist/backend/index.d.ts +10 -0
- package/dist/backend/index.js +1 -0
- package/dist/chunk-WY4YMBWZ.js +1044 -0
- package/dist/frontend/bundle.iife.js +2163 -0
- package/dist/frontend/bundle.iife.js.map +1 -0
- package/dist/index.cjs +1135 -0
- package/dist/index.d.cts +36 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +76 -0
- package/package.json +81 -0
- package/src/frontend/index.ts +110 -0
- package/src/frontend/pages/Playground/Editor/AddFieldDialog.tsx +187 -0
- package/src/frontend/pages/Playground/Editor/CodePreview.tsx +59 -0
- package/src/frontend/pages/Playground/Editor/FieldCard.tsx +161 -0
- package/src/frontend/pages/Playground/Editor/FieldList.tsx +121 -0
- package/src/frontend/pages/Playground/Editor/FieldSettingsPanel.tsx +652 -0
- package/src/frontend/pages/Playground/Editor/RelationConfigModal.tsx +292 -0
- package/src/frontend/pages/Playground/Editor/SchemaList.tsx +76 -0
- package/src/frontend/pages/Playground/Editor/SchemaOptionsDialog.tsx +109 -0
- package/src/frontend/pages/Playground/Editor/index.tsx +322 -0
- package/src/frontend/pages/Playground/constants/field-types.ts +384 -0
- package/src/frontend/pages/Playground/hooks/useSchemaBuilder.ts +280 -0
- package/src/frontend/pages/Playground/index.tsx +19 -0
- package/src/frontend/pages/Playground/types/builder.types.ts +191 -0
- package/src/frontend/pages/Playground/utils/code-generator.ts +319 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { useAdmin } from '@magnet-cms/admin'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogDescription,
|
|
7
|
+
DialogFooter,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
Input,
|
|
11
|
+
Label,
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
} from '@magnet-cms/ui/components'
|
|
18
|
+
import { cn } from '@magnet-cms/ui/lib'
|
|
19
|
+
import { useState } from 'react'
|
|
20
|
+
import type { RelationConfig } from '../types/builder.types'
|
|
21
|
+
|
|
22
|
+
interface RelationConfigModalProps {
|
|
23
|
+
open: boolean
|
|
24
|
+
onOpenChange: (open: boolean) => void
|
|
25
|
+
currentSchema: string
|
|
26
|
+
relationConfig: RelationConfig | undefined
|
|
27
|
+
onSave: (config: RelationConfig) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type RelationType = 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany'
|
|
31
|
+
|
|
32
|
+
interface RelationTypeOption {
|
|
33
|
+
value: RelationType
|
|
34
|
+
label: string
|
|
35
|
+
description: string
|
|
36
|
+
diagram: {
|
|
37
|
+
leftMultiple: boolean
|
|
38
|
+
rightMultiple: boolean
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const RELATION_TYPE_OPTIONS: RelationTypeOption[] = [
|
|
43
|
+
{
|
|
44
|
+
value: 'oneToOne',
|
|
45
|
+
label: 'One to One',
|
|
46
|
+
description: 'Each record relates to exactly one other record',
|
|
47
|
+
diagram: { leftMultiple: false, rightMultiple: false },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: 'oneToMany',
|
|
51
|
+
label: 'One to Many',
|
|
52
|
+
description: 'One record can relate to many other records',
|
|
53
|
+
diagram: { leftMultiple: false, rightMultiple: true },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
value: 'manyToOne',
|
|
57
|
+
label: 'Many to One',
|
|
58
|
+
description: 'Many records can relate to one other record',
|
|
59
|
+
diagram: { leftMultiple: true, rightMultiple: false },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
value: 'manyToMany',
|
|
63
|
+
label: 'Many to Many',
|
|
64
|
+
description: 'Many records can relate to many other records',
|
|
65
|
+
diagram: { leftMultiple: true, rightMultiple: true },
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
function RelationDiagram({
|
|
70
|
+
leftLabel,
|
|
71
|
+
rightLabel,
|
|
72
|
+
leftMultiple,
|
|
73
|
+
rightMultiple,
|
|
74
|
+
small = false,
|
|
75
|
+
}: {
|
|
76
|
+
leftLabel: string
|
|
77
|
+
rightLabel: string
|
|
78
|
+
leftMultiple: boolean
|
|
79
|
+
rightMultiple: boolean
|
|
80
|
+
small?: boolean
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<div className="flex items-center justify-center gap-2">
|
|
84
|
+
{/* Left side */}
|
|
85
|
+
<div className="flex items-center gap-1">
|
|
86
|
+
{leftMultiple && (
|
|
87
|
+
<div
|
|
88
|
+
className={cn(
|
|
89
|
+
'bg-background border rounded font-medium shadow-sm opacity-50 -mr-1',
|
|
90
|
+
small ? 'px-1.5 py-0.5 text-[10px]' : 'px-2 py-1 text-xs',
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
{leftLabel}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
<div
|
|
97
|
+
className={cn(
|
|
98
|
+
'bg-background border rounded font-medium shadow-sm relative z-10',
|
|
99
|
+
small ? 'px-1.5 py-0.5 text-[10px]' : 'px-2 py-1 text-xs',
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{leftLabel}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Connection line */}
|
|
107
|
+
<div className="flex items-center">
|
|
108
|
+
<div
|
|
109
|
+
className={cn(
|
|
110
|
+
'bg-muted-foreground/60 rounded-full',
|
|
111
|
+
small ? 'w-1.5 h-1.5' : 'w-2 h-2',
|
|
112
|
+
)}
|
|
113
|
+
/>
|
|
114
|
+
<div
|
|
115
|
+
className={cn('h-px bg-muted-foreground/60', small ? 'w-6' : 'w-10')}
|
|
116
|
+
/>
|
|
117
|
+
<div
|
|
118
|
+
className={cn(
|
|
119
|
+
'border-r-2 border-t-2 border-b-2 border-muted-foreground/60 rotate-45',
|
|
120
|
+
small ? 'w-1.5 h-1.5 -ml-1' : 'w-2 h-2 -ml-1.5',
|
|
121
|
+
)}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Right side */}
|
|
126
|
+
<div className="flex items-center gap-1">
|
|
127
|
+
<div
|
|
128
|
+
className={cn(
|
|
129
|
+
'bg-background border rounded font-medium shadow-sm relative z-10',
|
|
130
|
+
small ? 'px-1.5 py-0.5 text-[10px]' : 'px-2 py-1 text-xs',
|
|
131
|
+
)}
|
|
132
|
+
>
|
|
133
|
+
{rightLabel}
|
|
134
|
+
</div>
|
|
135
|
+
{rightMultiple && (
|
|
136
|
+
<div
|
|
137
|
+
className={cn(
|
|
138
|
+
'bg-background border rounded font-medium shadow-sm opacity-50 -ml-1',
|
|
139
|
+
small ? 'px-1.5 py-0.5 text-[10px]' : 'px-2 py-1 text-xs',
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
{rightLabel}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function RelationConfigModal({
|
|
151
|
+
open,
|
|
152
|
+
onOpenChange,
|
|
153
|
+
currentSchema,
|
|
154
|
+
relationConfig,
|
|
155
|
+
onSave,
|
|
156
|
+
}: RelationConfigModalProps) {
|
|
157
|
+
const { schemas } = useAdmin()
|
|
158
|
+
const [config, setConfig] = useState<RelationConfig>({
|
|
159
|
+
targetSchema: relationConfig?.targetSchema || '',
|
|
160
|
+
relationType: relationConfig?.relationType || 'manyToOne',
|
|
161
|
+
inverseSide: relationConfig?.inverseSide,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const handleSave = () => {
|
|
165
|
+
onSave(config)
|
|
166
|
+
onOpenChange(false)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const selectedTypeOption = RELATION_TYPE_OPTIONS.find(
|
|
170
|
+
(t) => t.value === config.relationType,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
175
|
+
<DialogContent className="sm:max-w-lg">
|
|
176
|
+
<DialogHeader>
|
|
177
|
+
<DialogTitle>Configure Relation</DialogTitle>
|
|
178
|
+
<DialogDescription>
|
|
179
|
+
Define how this field relates to another schema
|
|
180
|
+
</DialogDescription>
|
|
181
|
+
</DialogHeader>
|
|
182
|
+
|
|
183
|
+
<div className="space-y-6 py-4">
|
|
184
|
+
{/* Relation Type Picker */}
|
|
185
|
+
<div className="space-y-3">
|
|
186
|
+
<Label className="text-sm font-medium">Relation Type</Label>
|
|
187
|
+
<div className="grid grid-cols-2 gap-3">
|
|
188
|
+
{RELATION_TYPE_OPTIONS.map((option) => (
|
|
189
|
+
<button
|
|
190
|
+
key={option.value}
|
|
191
|
+
type="button"
|
|
192
|
+
onClick={() =>
|
|
193
|
+
setConfig((c) => ({ ...c, relationType: option.value }))
|
|
194
|
+
}
|
|
195
|
+
className={cn(
|
|
196
|
+
'p-3 rounded-lg border text-left transition-all',
|
|
197
|
+
config.relationType === option.value
|
|
198
|
+
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
|
199
|
+
: 'border-border hover:border-muted-foreground/50 hover:bg-muted/50',
|
|
200
|
+
)}
|
|
201
|
+
>
|
|
202
|
+
<div className="mb-2">
|
|
203
|
+
<RelationDiagram
|
|
204
|
+
leftLabel={currentSchema || 'A'}
|
|
205
|
+
rightLabel={config.targetSchema || 'B'}
|
|
206
|
+
leftMultiple={option.diagram.leftMultiple}
|
|
207
|
+
rightMultiple={option.diagram.rightMultiple}
|
|
208
|
+
small
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
<p className="text-sm font-medium">{option.label}</p>
|
|
212
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
213
|
+
{option.description}
|
|
214
|
+
</p>
|
|
215
|
+
</button>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Preview */}
|
|
221
|
+
{selectedTypeOption && (
|
|
222
|
+
<div className="p-4 bg-muted/30 rounded-lg border">
|
|
223
|
+
<p className="text-xs text-muted-foreground mb-3 text-center">
|
|
224
|
+
Preview
|
|
225
|
+
</p>
|
|
226
|
+
<RelationDiagram
|
|
227
|
+
leftLabel={currentSchema || 'Current'}
|
|
228
|
+
rightLabel={config.targetSchema || 'Target'}
|
|
229
|
+
leftMultiple={selectedTypeOption.diagram.leftMultiple}
|
|
230
|
+
rightMultiple={selectedTypeOption.diagram.rightMultiple}
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
{/* Target Schema */}
|
|
236
|
+
<div className="space-y-2">
|
|
237
|
+
<Label className="text-sm font-medium">Related Collection</Label>
|
|
238
|
+
<Select
|
|
239
|
+
value={config.targetSchema}
|
|
240
|
+
onValueChange={(value) =>
|
|
241
|
+
setConfig((c) => ({ ...c, targetSchema: value }))
|
|
242
|
+
}
|
|
243
|
+
>
|
|
244
|
+
<SelectTrigger>
|
|
245
|
+
<SelectValue placeholder="Select a schema..." />
|
|
246
|
+
</SelectTrigger>
|
|
247
|
+
<SelectContent>
|
|
248
|
+
{schemas?.map((schema) => (
|
|
249
|
+
<SelectItem key={schema} value={schema}>
|
|
250
|
+
{schema}
|
|
251
|
+
</SelectItem>
|
|
252
|
+
))}
|
|
253
|
+
</SelectContent>
|
|
254
|
+
</Select>
|
|
255
|
+
<p className="text-xs text-muted-foreground">
|
|
256
|
+
The schema this field will reference
|
|
257
|
+
</p>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
{/* Inverse Field Name */}
|
|
261
|
+
<div className="space-y-2">
|
|
262
|
+
<Label className="text-sm font-medium">
|
|
263
|
+
Inverse Field Name{' '}
|
|
264
|
+
<span className="text-muted-foreground font-normal">
|
|
265
|
+
(optional)
|
|
266
|
+
</span>
|
|
267
|
+
</Label>
|
|
268
|
+
<Input
|
|
269
|
+
value={config.inverseSide || ''}
|
|
270
|
+
onChange={(e) =>
|
|
271
|
+
setConfig((c) => ({ ...c, inverseSide: e.target.value }))
|
|
272
|
+
}
|
|
273
|
+
placeholder={`e.g., ${currentSchema?.toLowerCase() || 'items'}`}
|
|
274
|
+
/>
|
|
275
|
+
<p className="text-xs text-muted-foreground">
|
|
276
|
+
Field name on the related schema for bidirectional access
|
|
277
|
+
</p>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<DialogFooter>
|
|
282
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
283
|
+
Cancel
|
|
284
|
+
</Button>
|
|
285
|
+
<Button onClick={handleSave} disabled={!config.targetSchema}>
|
|
286
|
+
Save Configuration
|
|
287
|
+
</Button>
|
|
288
|
+
</DialogFooter>
|
|
289
|
+
</DialogContent>
|
|
290
|
+
</Dialog>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useAdmin } from '@magnet-cms/admin'
|
|
2
|
+
import { Button, Spinner } from '@magnet-cms/ui/components'
|
|
3
|
+
import { names } from '@magnet-cms/utils'
|
|
4
|
+
import { Database, Plus } from 'lucide-react'
|
|
5
|
+
import { useNavigate, useParams } from 'react-router-dom'
|
|
6
|
+
|
|
7
|
+
export function SchemaList() {
|
|
8
|
+
const navigate = useNavigate()
|
|
9
|
+
const { schemaName } = useParams<{ schemaName: string }>()
|
|
10
|
+
const { schemas, isLoading, error } = useAdmin()
|
|
11
|
+
|
|
12
|
+
if (isLoading) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex justify-center items-center h-32">
|
|
15
|
+
<Spinner className="h-5 w-5" />
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (error) {
|
|
21
|
+
return <div className="p-3 text-xs text-red-600">Error loading schemas</div>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isNewSchema = schemaName === 'new'
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="flex flex-col h-full">
|
|
28
|
+
{/* Header */}
|
|
29
|
+
<div className="p-3 border-b">
|
|
30
|
+
<Button
|
|
31
|
+
size="sm"
|
|
32
|
+
className="w-full"
|
|
33
|
+
variant={isNewSchema ? 'default' : 'outline'}
|
|
34
|
+
onClick={() => navigate('/playground/new')}
|
|
35
|
+
>
|
|
36
|
+
<Plus className="h-3.5 w-3.5 mr-1.5" />
|
|
37
|
+
New Schema
|
|
38
|
+
</Button>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Schema List */}
|
|
42
|
+
<div className="flex-1 overflow-y-auto">
|
|
43
|
+
{!schemas || schemas.length === 0 ? (
|
|
44
|
+
<div className="p-4 text-center text-muted-foreground">
|
|
45
|
+
<Database className="mx-auto h-8 w-8 mb-2 opacity-50" />
|
|
46
|
+
<p className="text-xs">No schemas yet</p>
|
|
47
|
+
</div>
|
|
48
|
+
) : (
|
|
49
|
+
<div className="py-1">
|
|
50
|
+
{schemas.map((schema) => {
|
|
51
|
+
const name = names(schema)
|
|
52
|
+
const isSelected = schemaName === name.key
|
|
53
|
+
return (
|
|
54
|
+
<button
|
|
55
|
+
key={schema}
|
|
56
|
+
type="button"
|
|
57
|
+
onClick={() => navigate(`/playground/${name.key}`)}
|
|
58
|
+
className={`w-full text-left px-3 py-2 text-sm transition-colors ${
|
|
59
|
+
isSelected
|
|
60
|
+
? 'bg-accent text-accent-foreground'
|
|
61
|
+
: 'hover:bg-muted/50 text-muted-foreground hover:text-foreground'
|
|
62
|
+
}`}
|
|
63
|
+
>
|
|
64
|
+
<div className="font-medium truncate">{name.title}</div>
|
|
65
|
+
<div className="text-xs opacity-70 font-mono truncate">
|
|
66
|
+
{name.key}
|
|
67
|
+
</div>
|
|
68
|
+
</button>
|
|
69
|
+
)
|
|
70
|
+
})}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useDialog } from '@magnet-cms/admin'
|
|
2
|
+
import { Button, Switch } from '@magnet-cms/ui/components'
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { useSchemaBuilder } from '../hooks/useSchemaBuilder'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Schema Options Content - rendered inside the dialog
|
|
8
|
+
*/
|
|
9
|
+
function SchemaOptionsContent({ onClose }: { onClose: () => void }) {
|
|
10
|
+
const { state, updateSchema } = useSchemaBuilder()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-4">
|
|
14
|
+
<div className="flex items-center justify-between">
|
|
15
|
+
<div className="space-y-0.5">
|
|
16
|
+
<label
|
|
17
|
+
htmlFor="versioning"
|
|
18
|
+
className="text-sm font-medium cursor-pointer"
|
|
19
|
+
>
|
|
20
|
+
Enable Versioning
|
|
21
|
+
</label>
|
|
22
|
+
<p className="text-xs text-muted-foreground">
|
|
23
|
+
Track content changes with version history
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
<Switch
|
|
27
|
+
id="versioning"
|
|
28
|
+
checked={state.schema.versioning}
|
|
29
|
+
onCheckedChange={(checked) => updateSchema({ versioning: checked })}
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="flex items-center justify-between">
|
|
34
|
+
<div className="space-y-0.5">
|
|
35
|
+
<label htmlFor="i18n" className="text-sm font-medium cursor-pointer">
|
|
36
|
+
Enable i18n
|
|
37
|
+
</label>
|
|
38
|
+
<p className="text-xs text-muted-foreground">
|
|
39
|
+
Support multiple languages for content
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
<Switch
|
|
43
|
+
id="i18n"
|
|
44
|
+
checked={state.schema.i18n}
|
|
45
|
+
onCheckedChange={(checked) => updateSchema({ i18n: checked })}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="flex justify-end pt-2">
|
|
50
|
+
<Button variant="outline" onClick={onClose}>
|
|
51
|
+
Close
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Hook to show schema options dialog
|
|
60
|
+
*/
|
|
61
|
+
export function useSchemaOptionsDialog() {
|
|
62
|
+
const { showDialog, closeDialog } = useDialog()
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
open: () => {
|
|
66
|
+
showDialog({
|
|
67
|
+
title: 'Schema Options',
|
|
68
|
+
description:
|
|
69
|
+
'Configure schema-level settings for versioning and internationalization.',
|
|
70
|
+
size: 'md',
|
|
71
|
+
content: <SchemaOptionsContent onClose={closeDialog} />,
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
close: closeDialog,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Legacy component wrapper for backwards compatibility
|
|
80
|
+
*/
|
|
81
|
+
interface SchemaOptionsDialogProps {
|
|
82
|
+
open: boolean
|
|
83
|
+
onOpenChange: (open: boolean) => void
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function SchemaOptionsDialog({
|
|
87
|
+
open,
|
|
88
|
+
onOpenChange,
|
|
89
|
+
}: SchemaOptionsDialogProps) {
|
|
90
|
+
const schemaOptions = useSchemaOptionsDialog()
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (open) {
|
|
94
|
+
schemaOptions.open()
|
|
95
|
+
}
|
|
96
|
+
}, [open, schemaOptions])
|
|
97
|
+
|
|
98
|
+
// When dialog closes via the service, notify parent
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
// This is a simplified approach - the dialog service handles closing
|
|
101
|
+
return () => {
|
|
102
|
+
if (open) {
|
|
103
|
+
onOpenChange(false)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, [open, onOpenChange])
|
|
107
|
+
|
|
108
|
+
return null // Dialog is rendered by DialogProvider in admin
|
|
109
|
+
}
|