@utopia-studio-design/design-system-cli 0.4.1 → 0.6.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/README.md +23 -0
- package/bin/utopia-ds-mcp.mjs +9 -2
- package/bin/utopia-ds.mjs +106 -8
- package/data/blocks/activity-feed/ActivityFeedBlock.tsx +255 -0
- package/data/blocks/activity-feed/README.md +16 -0
- package/data/blocks/activity-feed/activity-feed-contract.ts +96 -0
- package/data/blocks/activity-feed/activity-feed-fixture.ts +22 -0
- package/data/blocks/activity-feed/activity-feed.css +154 -0
- package/data/blocks/agent-status/AgentStatusBlock.tsx +313 -0
- package/data/blocks/agent-status/README.md +15 -0
- package/data/blocks/agent-status/agent-status-contract.ts +133 -0
- package/data/blocks/agent-status/agent-status-fixture.ts +79 -0
- package/data/blocks/agent-status/agent-status.css +216 -0
- package/data/blocks/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
- package/data/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
- package/data/blocks/analytics-dashboard/README.md +17 -0
- package/data/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
- package/data/blocks/calendar-application/CalendarApplicationBlocks.tsx +519 -0
- package/data/blocks/calendar-application/README.md +20 -0
- package/data/blocks/calendar-application/calendar-application.css +216 -0
- package/data/blocks/calendar-application/calendar-localization.ts +90 -0
- package/data/blocks/calendar-application/calendar-panel-contract.ts +72 -0
- package/data/blocks/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
- package/data/blocks/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
- package/data/blocks/dashboard-composer/README.md +16 -0
- package/data/blocks/dashboard-composer/RegistryDataBlocks.tsx +238 -0
- package/data/blocks/dashboard-composer/dashboard-composer.css +328 -0
- package/data/blocks/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
- package/data/blocks/database-workspace/README.md +9 -0
- package/data/blocks/database-workspace/database-workspace.css +107 -0
- package/data/blocks/interaction-workbench/InteractionWorkbench.tsx +271 -0
- package/data/blocks/interaction-workbench/README.md +9 -0
- package/data/blocks/interaction-workbench/interaction-workbench.css +226 -0
- package/data/docs/calendar.md +164 -0
- package/data/docs/charts.md +95 -0
- package/data/docs/composition-registry.md +174 -0
- package/data/docs/dashboard-composer.md +94 -0
- package/data/docs/database.md +68 -0
- package/data/docs/examples/dashboard-layout.v1.json +65 -0
- package/data/docs/foundations.md +1 -1
- package/data/docs/interaction-foundation.md +109 -0
- package/data/docs/quick-start-ai.md +23 -1
- package/data/manifests/blocks.json +392 -0
- package/data/manifests/catalog.json +7 -4
- package/data/manifests/components.json +179 -0
- package/data/manifests/composition-registry.json +427 -0
- package/data/manifests/motion-profiles.json +1 -1
- package/data/manifests/patterns.json +113 -1
- package/data/manifests/templates.json +346 -0
- package/data/manifests/theme-renacore.json +142 -0
- package/data/manifests/theme-vyapti.json +125 -0
- package/data/manifests/themes.json +400 -28
- package/data/templates/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
- package/data/templates/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
- package/data/templates/analytics-dashboard/README.md +6 -0
- package/data/templates/analytics-dashboard/analytics-dashboard.css +92 -0
- package/data/templates/analytics-dashboard/index.html +13 -0
- package/data/templates/analytics-dashboard/main.tsx +196 -0
- package/data/templates/analytics-dashboard/styles.css +120 -0
- package/data/templates/analytics-dashboard/template.manifest.json +17 -0
- package/data/templates/calendar-application/CalendarApplicationBlocks.tsx +519 -0
- package/data/templates/calendar-application/README.md +10 -0
- package/data/templates/calendar-application/calendar-application.css +216 -0
- package/data/templates/calendar-application/calendar-localization.ts +90 -0
- package/data/templates/calendar-application/index.html +14 -0
- package/data/templates/calendar-application/main.tsx +409 -0
- package/data/templates/calendar-application/styles.css +248 -0
- package/data/templates/calendar-application/template.manifest.json +27 -0
- package/data/templates/composition-registry-lab/README.md +9 -0
- package/data/templates/composition-registry-lab/index.html +14 -0
- package/data/templates/composition-registry-lab/main.tsx +217 -0
- package/data/templates/composition-registry-lab/sample-layout.json +63 -0
- package/data/templates/composition-registry-lab/styles.css +343 -0
- package/data/templates/composition-registry-lab/template.manifest.json +34 -0
- package/data/templates/dashboard-composer/ActivityFeedBlock.tsx +255 -0
- package/data/templates/dashboard-composer/AgentStatusBlock.tsx +313 -0
- package/data/templates/dashboard-composer/AnalyticsDashboardBlocks.tsx +107 -0
- package/data/templates/dashboard-composer/AnalyticsMetricBlocks.tsx +87 -0
- package/data/templates/dashboard-composer/CalendarApplicationBlocks.tsx +519 -0
- package/data/templates/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
- package/data/templates/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
- package/data/templates/dashboard-composer/DatabaseWorkspaceBlocks.tsx +240 -0
- package/data/templates/dashboard-composer/README.md +53 -0
- package/data/templates/dashboard-composer/RegistryDataBlocks.tsx +238 -0
- package/data/templates/dashboard-composer/activity-feed-contract.ts +96 -0
- package/data/templates/dashboard-composer/activity-feed-fixture.ts +22 -0
- package/data/templates/dashboard-composer/activity-feed.css +154 -0
- package/data/templates/dashboard-composer/agent-status-contract.ts +133 -0
- package/data/templates/dashboard-composer/agent-status-fixture.ts +79 -0
- package/data/templates/dashboard-composer/agent-status-lab/index.html +19 -0
- package/data/templates/dashboard-composer/agent-status-lab/main.tsx +151 -0
- package/data/templates/dashboard-composer/agent-status-lab/styles.css +139 -0
- package/data/templates/dashboard-composer/agent-status.css +216 -0
- package/data/templates/dashboard-composer/analytics-dashboard.css +92 -0
- package/data/templates/dashboard-composer/calendar-application.css +216 -0
- package/data/templates/dashboard-composer/calendar-localization.ts +90 -0
- package/data/templates/dashboard-composer/calendar-panel-contract.ts +72 -0
- package/data/templates/dashboard-composer/dashboard-composer.css +328 -0
- package/data/templates/dashboard-composer/database-workspace.css +107 -0
- package/data/templates/dashboard-composer/index.html +14 -0
- package/data/templates/dashboard-composer/main.tsx +550 -0
- package/data/templates/dashboard-composer/sample-layout.json +147 -0
- package/data/templates/dashboard-composer/styles.css +198 -0
- package/data/templates/dashboard-composer/template.manifest.json +34 -0
- package/data/templates/dashboard-composer/visual-editor/PuckEditorSurface.tsx +132 -0
- package/data/templates/dashboard-composer/visual-editor/VisualEditorApp.tsx +343 -0
- package/data/templates/dashboard-composer/visual-editor/index.html +17 -0
- package/data/templates/dashboard-composer/visual-editor/main.tsx +95 -0
- package/data/templates/dashboard-composer/visual-editor/styles.css +310 -0
- package/data/templates/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
- package/data/templates/database-workspace/README.md +5 -0
- package/data/templates/database-workspace/database-workspace.css +107 -0
- package/data/templates/database-workspace/index.html +14 -0
- package/data/templates/database-workspace/main.tsx +247 -0
- package/data/templates/database-workspace/styles.css +121 -0
- package/data/templates/database-workspace/template.manifest.json +19 -0
- package/data/templates/interaction-lab/InteractionWorkbench.tsx +271 -0
- package/data/templates/interaction-lab/README.md +7 -0
- package/data/templates/interaction-lab/index.html +14 -0
- package/data/templates/interaction-lab/interaction-workbench.css +226 -0
- package/data/templates/interaction-lab/main.tsx +104 -0
- package/data/templates/interaction-lab/styles.css +224 -0
- package/data/templates/interaction-lab/template.manifest.json +36 -0
- package/lib/api.mjs +72 -5
- package/lib/composition-runtime.mjs +382 -0
- package/package.json +4 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
Suspense,
|
|
4
|
+
createContext,
|
|
5
|
+
lazy,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
type ErrorInfo,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import type { Data } from '@puckeditor/core'
|
|
14
|
+
import { Badge } from '@utopia-studio-design/design-system/Badge'
|
|
15
|
+
import { Button } from '@utopia-studio-design/design-system/Button'
|
|
16
|
+
import type { SchedulerEvent } from '@utopia-studio-design/design-system/Calendar'
|
|
17
|
+
import type {
|
|
18
|
+
CeramicLayoutBlock,
|
|
19
|
+
CeramicLayoutDocument,
|
|
20
|
+
} from '@utopia-studio-design/design-system/Composition'
|
|
21
|
+
import { AgentStatusBlock } from '../AgentStatusBlock'
|
|
22
|
+
import { resolveAgentStatusConfiguration } from '../agent-status-contract'
|
|
23
|
+
import { getAgentStatusFixture } from '../agent-status-fixture'
|
|
24
|
+
import { ActivityFeedBlock } from '../ActivityFeedBlock'
|
|
25
|
+
import { resolveActivityFeedConfiguration } from '../activity-feed-contract'
|
|
26
|
+
import { getActivityFeedFixture } from '../activity-feed-fixture'
|
|
27
|
+
import {
|
|
28
|
+
KPIGrid,
|
|
29
|
+
type MetricDefinition,
|
|
30
|
+
} from '../AnalyticsMetricBlocks'
|
|
31
|
+
import { SchedulerShell } from '../CalendarApplicationBlocks'
|
|
32
|
+
import { resolveCalendarPanelConfiguration } from '../calendar-panel-contract'
|
|
33
|
+
import {
|
|
34
|
+
TodoListBlock,
|
|
35
|
+
type CeramicBlockRenderer,
|
|
36
|
+
type TodoFilter,
|
|
37
|
+
type TodoItem,
|
|
38
|
+
} from '../DashboardComposerBlocks'
|
|
39
|
+
import {
|
|
40
|
+
ChartPanelRegistryBlock,
|
|
41
|
+
IssueTableRegistryBlock,
|
|
42
|
+
} from '../RegistryDataBlocks'
|
|
43
|
+
import {
|
|
44
|
+
ceramicLayoutToPuckData,
|
|
45
|
+
createCeramicPuckConfig,
|
|
46
|
+
puckDataToCeramicLayout,
|
|
47
|
+
} from '../CeramicPuckAdapter'
|
|
48
|
+
import '../agent-status.css'
|
|
49
|
+
import '../activity-feed.css'
|
|
50
|
+
import '../analytics-dashboard.css'
|
|
51
|
+
import '../calendar-application.css'
|
|
52
|
+
import '../dashboard-composer.css'
|
|
53
|
+
import initialLayoutDocument from '../sample-layout.json'
|
|
54
|
+
|
|
55
|
+
const PuckEditorSurface = lazy(() => import('./PuckEditorSurface'))
|
|
56
|
+
|
|
57
|
+
const initialLayout = initialLayoutDocument as CeramicLayoutDocument
|
|
58
|
+
const anchorDate = new Date(2026, 2, 9, 12)
|
|
59
|
+
|
|
60
|
+
const initialTasks: TodoItem[] = [
|
|
61
|
+
{ id: 'puck-todo-1', title: 'Review the visual editor boundary', completed: true, dueLabel: 'Today', priority: 'high' },
|
|
62
|
+
{ id: 'puck-todo-2', title: 'Rearrange the Calendar block', completed: false, dueLabel: 'Today', priority: 'medium' },
|
|
63
|
+
{ id: 'puck-todo-3', title: 'Publish validated Layout JSON', completed: false, dueLabel: 'Tomorrow', priority: 'low' },
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
const events: SchedulerEvent[] = [
|
|
67
|
+
{
|
|
68
|
+
id: 'puck-review',
|
|
69
|
+
title: 'Puck adapter review',
|
|
70
|
+
start: '2026-03-09T01:00:00.000Z',
|
|
71
|
+
end: '2026-03-09T02:00:00.000Z',
|
|
72
|
+
tone: 'accent',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'registry-check',
|
|
76
|
+
title: 'Registry contract check',
|
|
77
|
+
start: '2026-03-10T04:00:00.000Z',
|
|
78
|
+
end: '2026-03-10T05:00:00.000Z',
|
|
79
|
+
tone: 'positive',
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
const arabicEventTitles: Record<string, string> = {
|
|
84
|
+
'puck-review': 'مراجعة محرر Puck',
|
|
85
|
+
'registry-check': 'فحص عقد السجل',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const metrics: MetricDefinition[] = [
|
|
89
|
+
{ id: 'open', label: 'Open tasks', value: 2, change: -33, changeLabel: 'vs yesterday' },
|
|
90
|
+
{ id: 'completed', label: 'Completed', value: 8, change: 14, changeLabel: 'this week' },
|
|
91
|
+
{ id: 'focus', label: 'Focus hours', value: 12.5, change: 6, changeLabel: 'this week' },
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
interface EditorFixtureData {
|
|
95
|
+
compact: boolean
|
|
96
|
+
tasks: TodoItem[]
|
|
97
|
+
setTasks: (tasks: TodoItem[]) => void
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const EditorFixtureContext = createContext<EditorFixtureData | null>(null)
|
|
101
|
+
|
|
102
|
+
function useEditorFixture() {
|
|
103
|
+
const value = useContext(EditorFixtureContext)
|
|
104
|
+
if (!value) throw new Error('Puck block renderer must be used inside EditorFixtureContext.')
|
|
105
|
+
return value
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function TodoRenderer({ block }: { block: CeramicLayoutBlock }) {
|
|
109
|
+
const { tasks, setTasks } = useEditorFixture()
|
|
110
|
+
return (
|
|
111
|
+
<TodoListBlock
|
|
112
|
+
density={(block.props.density as 'compact' | 'default' | 'comfortable') ?? 'default'}
|
|
113
|
+
filter={(block.props.filter as TodoFilter) ?? 'all'}
|
|
114
|
+
items={tasks}
|
|
115
|
+
showCompleted={block.props.showCompleted !== false}
|
|
116
|
+
title={(block.props.title as string) ?? 'Today’s tasks'}
|
|
117
|
+
onItemsChange={setTasks}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function CalendarRenderer({
|
|
123
|
+
block,
|
|
124
|
+
direction,
|
|
125
|
+
}: {
|
|
126
|
+
block: CeramicLayoutBlock
|
|
127
|
+
direction: 'ltr' | 'rtl'
|
|
128
|
+
}) {
|
|
129
|
+
const { compact } = useEditorFixture()
|
|
130
|
+
const configuration = resolveCalendarPanelConfiguration(block.props, { compact, direction })
|
|
131
|
+
const displayEvents = configuration.locale.startsWith('ar')
|
|
132
|
+
? events.map((event) => ({
|
|
133
|
+
...event,
|
|
134
|
+
title: arabicEventTitles[event.id] ?? event.title,
|
|
135
|
+
}))
|
|
136
|
+
: events
|
|
137
|
+
return (
|
|
138
|
+
<SchedulerShell
|
|
139
|
+
{...configuration}
|
|
140
|
+
date={anchorDate}
|
|
141
|
+
events={displayEvents}
|
|
142
|
+
permissions={{ create: false, edit: false, move: false, resize: false, duplicate: false, delete: false }}
|
|
143
|
+
onDateChange={() => undefined}
|
|
144
|
+
onEventsChange={() => undefined}
|
|
145
|
+
onViewChange={() => undefined}
|
|
146
|
+
/>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function MetricsRenderer({ block }: { block: CeramicLayoutBlock }) {
|
|
151
|
+
const metricIds = new Set((block.props.metrics as string[]) ?? metrics.map((metric) => metric.id))
|
|
152
|
+
return (
|
|
153
|
+
<KPIGrid
|
|
154
|
+
density={(block.props.density as 'compact' | 'default' | 'comfortable') ?? 'compact'}
|
|
155
|
+
metrics={metrics.filter((metric) => metricIds.has(metric.id))}
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function ActivityRenderer({
|
|
161
|
+
block,
|
|
162
|
+
direction,
|
|
163
|
+
}: {
|
|
164
|
+
block: CeramicLayoutBlock
|
|
165
|
+
direction: 'ltr' | 'rtl'
|
|
166
|
+
}) {
|
|
167
|
+
const configuration = resolveActivityFeedConfiguration(block.props, { direction })
|
|
168
|
+
return (
|
|
169
|
+
<ActivityFeedBlock
|
|
170
|
+
{...configuration}
|
|
171
|
+
items={getActivityFeedFixture(configuration.locale)}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function AgentStatusRenderer({
|
|
177
|
+
block,
|
|
178
|
+
direction,
|
|
179
|
+
}: {
|
|
180
|
+
block: CeramicLayoutBlock
|
|
181
|
+
direction: 'ltr' | 'rtl'
|
|
182
|
+
}) {
|
|
183
|
+
const configuration = resolveAgentStatusConfiguration(block.props, { direction })
|
|
184
|
+
return (
|
|
185
|
+
<AgentStatusBlock
|
|
186
|
+
{...configuration}
|
|
187
|
+
items={getAgentStatusFixture(configuration.locale)}
|
|
188
|
+
/>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const renderers: Record<string, CeramicBlockRenderer> = {
|
|
193
|
+
'agent-status': AgentStatusRenderer,
|
|
194
|
+
'activity-feed': ActivityRenderer,
|
|
195
|
+
'calendar-panel': CalendarRenderer,
|
|
196
|
+
'chart-panel': ChartPanelRegistryBlock,
|
|
197
|
+
'issue-table': IssueTableRegistryBlock,
|
|
198
|
+
'metric-grid': MetricsRenderer,
|
|
199
|
+
'todo-list': TodoRenderer,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
class EditorLoadBoundary extends Component<
|
|
203
|
+
{ children: ReactNode },
|
|
204
|
+
{ failed: boolean }
|
|
205
|
+
> {
|
|
206
|
+
state = { failed: false }
|
|
207
|
+
|
|
208
|
+
static getDerivedStateFromError() {
|
|
209
|
+
return { failed: true }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
213
|
+
console.error('The optional Puck editor failed to load.', error, info)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
render() {
|
|
217
|
+
if (this.state.failed) {
|
|
218
|
+
return (
|
|
219
|
+
<div className="ceramic-puck-editor-state" role="alert">
|
|
220
|
+
<strong>Visual editor could not load.</strong>
|
|
221
|
+
<p>The Ceramic layout is safe. Reload this optional editing layer to try again.</p>
|
|
222
|
+
<Button onClick={() => window.location.reload()}>Retry loading editor</Button>
|
|
223
|
+
</div>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return this.props.children
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default function VisualEditorApp() {
|
|
232
|
+
const [layout, setLayout] = useState<CeramicLayoutDocument>(() => structuredClone(initialLayout))
|
|
233
|
+
const [puckData, setPuckData] = useState<Data>(() => ceramicLayoutToPuckData(initialLayout))
|
|
234
|
+
const [tasks, setTasks] = useState(initialTasks)
|
|
235
|
+
const [direction, setDirection] = useState<'ltr' | 'rtl'>('ltr')
|
|
236
|
+
const [dark, setDark] = useState(false)
|
|
237
|
+
const [compact, setCompact] = useState(() => window.matchMedia('(max-width: 48rem)').matches)
|
|
238
|
+
const [feedback, setFeedback] = useState('Registry connected. Reorder a block or edit a registered field.')
|
|
239
|
+
const [valid, setValid] = useState(true)
|
|
240
|
+
const config = useMemo(
|
|
241
|
+
() => createCeramicPuckConfig({
|
|
242
|
+
colorMode: dark ? 'dark' : 'light',
|
|
243
|
+
direction,
|
|
244
|
+
layout,
|
|
245
|
+
renderers,
|
|
246
|
+
}),
|
|
247
|
+
[dark, direction, layout],
|
|
248
|
+
)
|
|
249
|
+
const fixture = useMemo(() => ({ compact, tasks, setTasks }), [compact, tasks])
|
|
250
|
+
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
const media = window.matchMedia('(max-width: 48rem)')
|
|
253
|
+
const handleChange = () => setCompact(media.matches)
|
|
254
|
+
media.addEventListener('change', handleChange)
|
|
255
|
+
return () => media.removeEventListener('change', handleChange)
|
|
256
|
+
}, [])
|
|
257
|
+
|
|
258
|
+
const inspectDraft = (data: Data) => {
|
|
259
|
+
setPuckData(data)
|
|
260
|
+
const result = puckDataToCeramicLayout(data, layout)
|
|
261
|
+
setValid(result.ok)
|
|
262
|
+
setFeedback(
|
|
263
|
+
result.ok
|
|
264
|
+
? 'Draft is valid Ceramic Layout JSON. Publish to apply it to this demo session.'
|
|
265
|
+
: `Draft rejected: ${result.issues[0] ?? 'Unknown registry violation.'}`,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const publish = (data: Data) => {
|
|
270
|
+
const result = puckDataToCeramicLayout(data, layout)
|
|
271
|
+
if (!result.ok || !result.layout) {
|
|
272
|
+
setValid(false)
|
|
273
|
+
setFeedback(`Publish rejected: ${result.issues[0] ?? 'Unknown registry violation.'}`)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
setLayout(result.layout)
|
|
277
|
+
setPuckData(ceramicLayoutToPuckData(result.layout))
|
|
278
|
+
setValid(true)
|
|
279
|
+
setFeedback('Published to this demo session after Ceramic mutation validation.')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<EditorFixtureContext.Provider value={fixture}>
|
|
284
|
+
<main
|
|
285
|
+
className="ceramic-puck-editor"
|
|
286
|
+
data-color-mode={dark ? 'dark' : 'light'}
|
|
287
|
+
data-preview-direction={direction}
|
|
288
|
+
data-theme="utopia-default"
|
|
289
|
+
dir="ltr"
|
|
290
|
+
>
|
|
291
|
+
<header className="ceramic-puck-editor-header">
|
|
292
|
+
<div>
|
|
293
|
+
<span>Optional editing layer</span>
|
|
294
|
+
<h1>Ceramic blocks, arranged with Puck</h1>
|
|
295
|
+
<p>Puck edits only registered semantic fields. Ceramic still validates the published Layout JSON.</p>
|
|
296
|
+
</div>
|
|
297
|
+
<div aria-label="Visual editor controls">
|
|
298
|
+
<Button asChild variant="outline">
|
|
299
|
+
<a href="/templates/dashboard-composer/">Back to runtime</a>
|
|
300
|
+
</Button>
|
|
301
|
+
<Button variant="outline" onClick={() => setDirection((value) => value === 'ltr' ? 'rtl' : 'ltr')}>
|
|
302
|
+
{direction === 'ltr' ? 'Switch canvas to RTL' : 'Switch canvas to LTR'}
|
|
303
|
+
</Button>
|
|
304
|
+
<Button variant="outline" onClick={() => setDark((value) => !value)}>
|
|
305
|
+
{dark ? 'Switch to light' : 'Switch to dark'}
|
|
306
|
+
</Button>
|
|
307
|
+
</div>
|
|
308
|
+
<output aria-live="polite" data-valid={valid}>
|
|
309
|
+
<Badge variant={valid ? 'secondary' : 'destructive'}>
|
|
310
|
+
{valid ? 'Registry valid' : 'Registry rejected'}
|
|
311
|
+
</Badge>
|
|
312
|
+
<span>{feedback}</span>
|
|
313
|
+
</output>
|
|
314
|
+
</header>
|
|
315
|
+
|
|
316
|
+
<section aria-label="Puck visual editor" className="ceramic-puck-editor-shell">
|
|
317
|
+
<EditorLoadBoundary>
|
|
318
|
+
<Suspense
|
|
319
|
+
fallback={(
|
|
320
|
+
<div className="ceramic-puck-editor-state" role="status" aria-live="polite">
|
|
321
|
+
<strong>Loading visual editor…</strong>
|
|
322
|
+
<p>The Ceramic shell is ready while the optional editing layer loads.</p>
|
|
323
|
+
</div>
|
|
324
|
+
)}
|
|
325
|
+
>
|
|
326
|
+
<PuckEditorSurface
|
|
327
|
+
config={config}
|
|
328
|
+
data={puckData}
|
|
329
|
+
onChange={inspectDraft}
|
|
330
|
+
onPublish={publish}
|
|
331
|
+
/>
|
|
332
|
+
</Suspense>
|
|
333
|
+
</EditorLoadBoundary>
|
|
334
|
+
</section>
|
|
335
|
+
|
|
336
|
+
<details className="ceramic-puck-json">
|
|
337
|
+
<summary>Inspect the last validated Layout JSON</summary>
|
|
338
|
+
<pre>{JSON.stringify(layout, null, 2)}</pre>
|
|
339
|
+
</details>
|
|
340
|
+
</main>
|
|
341
|
+
</EditorFixtureContext.Provider>
|
|
342
|
+
)
|
|
343
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="A Puck visual editor generated from the Ceramic Composition Registry."
|
|
9
|
+
/>
|
|
10
|
+
<link rel="icon" href="data:," />
|
|
11
|
+
<title>Ceramic Visual Editor — Dashboard Composer</title>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="./main.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
StrictMode,
|
|
4
|
+
Suspense,
|
|
5
|
+
lazy,
|
|
6
|
+
type ErrorInfo,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from 'react'
|
|
9
|
+
import { createRoot } from 'react-dom/client'
|
|
10
|
+
import '@utopia-studio-design/design-system/core.css'
|
|
11
|
+
import '@utopia-studio-design/design-system/themes/utopia-default.css'
|
|
12
|
+
import './styles.css'
|
|
13
|
+
|
|
14
|
+
const VisualEditorApp = lazy(() => import('./VisualEditorApp'))
|
|
15
|
+
|
|
16
|
+
function EditorBootShell({
|
|
17
|
+
failed = false,
|
|
18
|
+
}: {
|
|
19
|
+
failed?: boolean
|
|
20
|
+
}) {
|
|
21
|
+
return (
|
|
22
|
+
<main
|
|
23
|
+
className="ceramic-puck-editor"
|
|
24
|
+
data-color-mode="light"
|
|
25
|
+
data-preview-direction="ltr"
|
|
26
|
+
data-theme="utopia-default"
|
|
27
|
+
dir="ltr"
|
|
28
|
+
>
|
|
29
|
+
<header className="ceramic-puck-editor-header">
|
|
30
|
+
<div>
|
|
31
|
+
<span>Optional editing layer</span>
|
|
32
|
+
<h1>Ceramic blocks, arranged with Puck</h1>
|
|
33
|
+
<p>Puck edits only registered semantic fields. Ceramic still validates the published Layout JSON.</p>
|
|
34
|
+
</div>
|
|
35
|
+
</header>
|
|
36
|
+
<section aria-label="Puck visual editor" className="ceramic-puck-editor-shell">
|
|
37
|
+
<div
|
|
38
|
+
className="ceramic-puck-editor-state"
|
|
39
|
+
role={failed ? 'alert' : 'status'}
|
|
40
|
+
aria-live="polite"
|
|
41
|
+
>
|
|
42
|
+
<strong>{failed ? 'Visual editor could not start.' : 'Preparing Ceramic editor…'}</strong>
|
|
43
|
+
<p>
|
|
44
|
+
{failed
|
|
45
|
+
? 'The saved layout is safe. Reload the optional editing route to try again.'
|
|
46
|
+
: 'The page shell is ready while registered blocks and editing controls load.'}
|
|
47
|
+
</p>
|
|
48
|
+
{failed ? (
|
|
49
|
+
<button
|
|
50
|
+
className="ceramic-puck-retry"
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() => window.location.reload()}
|
|
53
|
+
>
|
|
54
|
+
Retry loading editor
|
|
55
|
+
</button>
|
|
56
|
+
) : null}
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
</main>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class EditorBootBoundary extends Component<
|
|
64
|
+
{ children: ReactNode },
|
|
65
|
+
{ failed: boolean }
|
|
66
|
+
> {
|
|
67
|
+
state = { failed: false }
|
|
68
|
+
|
|
69
|
+
static getDerivedStateFromError() {
|
|
70
|
+
return { failed: true }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
74
|
+
console.error('The Ceramic visual editor runtime failed to load.', error, info)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
return this.state.failed ? <EditorBootShell failed /> : this.props.children
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const root = createRoot(document.getElementById('root')!)
|
|
83
|
+
root.render(
|
|
84
|
+
<StrictMode>
|
|
85
|
+
<EditorBootBoundary>
|
|
86
|
+
<Suspense fallback={<EditorBootShell />}>
|
|
87
|
+
<VisualEditorApp />
|
|
88
|
+
</Suspense>
|
|
89
|
+
</EditorBootBoundary>
|
|
90
|
+
</StrictMode>,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if (import.meta.hot) {
|
|
94
|
+
import.meta.hot.dispose(() => root.unmount())
|
|
95
|
+
}
|