@utopia-studio-design/design-system-cli 0.4.1 → 0.5.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.
Files changed (125) hide show
  1. package/README.md +23 -0
  2. package/bin/utopia-ds-mcp.mjs +9 -2
  3. package/bin/utopia-ds.mjs +106 -8
  4. package/data/blocks/activity-feed/ActivityFeedBlock.tsx +255 -0
  5. package/data/blocks/activity-feed/README.md +16 -0
  6. package/data/blocks/activity-feed/activity-feed-contract.ts +96 -0
  7. package/data/blocks/activity-feed/activity-feed-fixture.ts +22 -0
  8. package/data/blocks/activity-feed/activity-feed.css +154 -0
  9. package/data/blocks/agent-status/AgentStatusBlock.tsx +313 -0
  10. package/data/blocks/agent-status/README.md +15 -0
  11. package/data/blocks/agent-status/agent-status-contract.ts +133 -0
  12. package/data/blocks/agent-status/agent-status-fixture.ts +79 -0
  13. package/data/blocks/agent-status/agent-status.css +216 -0
  14. package/data/blocks/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  15. package/data/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  16. package/data/blocks/analytics-dashboard/README.md +17 -0
  17. package/data/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
  18. package/data/blocks/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  19. package/data/blocks/calendar-application/README.md +20 -0
  20. package/data/blocks/calendar-application/calendar-application.css +216 -0
  21. package/data/blocks/calendar-application/calendar-localization.ts +90 -0
  22. package/data/blocks/calendar-application/calendar-panel-contract.ts +72 -0
  23. package/data/blocks/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  24. package/data/blocks/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  25. package/data/blocks/dashboard-composer/README.md +16 -0
  26. package/data/blocks/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  27. package/data/blocks/dashboard-composer/dashboard-composer.css +328 -0
  28. package/data/blocks/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  29. package/data/blocks/database-workspace/README.md +9 -0
  30. package/data/blocks/database-workspace/database-workspace.css +107 -0
  31. package/data/blocks/interaction-workbench/InteractionWorkbench.tsx +271 -0
  32. package/data/blocks/interaction-workbench/README.md +9 -0
  33. package/data/blocks/interaction-workbench/interaction-workbench.css +226 -0
  34. package/data/docs/calendar.md +164 -0
  35. package/data/docs/charts.md +95 -0
  36. package/data/docs/composition-registry.md +174 -0
  37. package/data/docs/dashboard-composer.md +94 -0
  38. package/data/docs/database.md +68 -0
  39. package/data/docs/examples/dashboard-layout.v1.json +65 -0
  40. package/data/docs/foundations.md +1 -1
  41. package/data/docs/interaction-foundation.md +109 -0
  42. package/data/docs/quick-start-ai.md +23 -1
  43. package/data/manifests/blocks.json +392 -0
  44. package/data/manifests/catalog.json +7 -4
  45. package/data/manifests/components.json +179 -0
  46. package/data/manifests/composition-registry.json +427 -0
  47. package/data/manifests/motion-profiles.json +1 -1
  48. package/data/manifests/patterns.json +113 -1
  49. package/data/manifests/templates.json +346 -0
  50. package/data/manifests/theme-renacore.json +142 -0
  51. package/data/manifests/themes.json +314 -28
  52. package/data/templates/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  53. package/data/templates/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  54. package/data/templates/analytics-dashboard/README.md +6 -0
  55. package/data/templates/analytics-dashboard/analytics-dashboard.css +92 -0
  56. package/data/templates/analytics-dashboard/index.html +13 -0
  57. package/data/templates/analytics-dashboard/main.tsx +196 -0
  58. package/data/templates/analytics-dashboard/styles.css +120 -0
  59. package/data/templates/analytics-dashboard/template.manifest.json +17 -0
  60. package/data/templates/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  61. package/data/templates/calendar-application/README.md +10 -0
  62. package/data/templates/calendar-application/calendar-application.css +216 -0
  63. package/data/templates/calendar-application/calendar-localization.ts +90 -0
  64. package/data/templates/calendar-application/index.html +14 -0
  65. package/data/templates/calendar-application/main.tsx +409 -0
  66. package/data/templates/calendar-application/styles.css +248 -0
  67. package/data/templates/calendar-application/template.manifest.json +27 -0
  68. package/data/templates/composition-registry-lab/README.md +9 -0
  69. package/data/templates/composition-registry-lab/index.html +14 -0
  70. package/data/templates/composition-registry-lab/main.tsx +217 -0
  71. package/data/templates/composition-registry-lab/sample-layout.json +63 -0
  72. package/data/templates/composition-registry-lab/styles.css +343 -0
  73. package/data/templates/composition-registry-lab/template.manifest.json +34 -0
  74. package/data/templates/dashboard-composer/ActivityFeedBlock.tsx +255 -0
  75. package/data/templates/dashboard-composer/AgentStatusBlock.tsx +313 -0
  76. package/data/templates/dashboard-composer/AnalyticsDashboardBlocks.tsx +107 -0
  77. package/data/templates/dashboard-composer/AnalyticsMetricBlocks.tsx +87 -0
  78. package/data/templates/dashboard-composer/CalendarApplicationBlocks.tsx +519 -0
  79. package/data/templates/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  80. package/data/templates/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  81. package/data/templates/dashboard-composer/DatabaseWorkspaceBlocks.tsx +240 -0
  82. package/data/templates/dashboard-composer/README.md +53 -0
  83. package/data/templates/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  84. package/data/templates/dashboard-composer/activity-feed-contract.ts +96 -0
  85. package/data/templates/dashboard-composer/activity-feed-fixture.ts +22 -0
  86. package/data/templates/dashboard-composer/activity-feed.css +154 -0
  87. package/data/templates/dashboard-composer/agent-status-contract.ts +133 -0
  88. package/data/templates/dashboard-composer/agent-status-fixture.ts +79 -0
  89. package/data/templates/dashboard-composer/agent-status-lab/index.html +19 -0
  90. package/data/templates/dashboard-composer/agent-status-lab/main.tsx +151 -0
  91. package/data/templates/dashboard-composer/agent-status-lab/styles.css +139 -0
  92. package/data/templates/dashboard-composer/agent-status.css +216 -0
  93. package/data/templates/dashboard-composer/analytics-dashboard.css +92 -0
  94. package/data/templates/dashboard-composer/calendar-application.css +216 -0
  95. package/data/templates/dashboard-composer/calendar-localization.ts +90 -0
  96. package/data/templates/dashboard-composer/calendar-panel-contract.ts +72 -0
  97. package/data/templates/dashboard-composer/dashboard-composer.css +328 -0
  98. package/data/templates/dashboard-composer/database-workspace.css +107 -0
  99. package/data/templates/dashboard-composer/index.html +14 -0
  100. package/data/templates/dashboard-composer/main.tsx +550 -0
  101. package/data/templates/dashboard-composer/sample-layout.json +147 -0
  102. package/data/templates/dashboard-composer/styles.css +198 -0
  103. package/data/templates/dashboard-composer/template.manifest.json +34 -0
  104. package/data/templates/dashboard-composer/visual-editor/PuckEditorSurface.tsx +132 -0
  105. package/data/templates/dashboard-composer/visual-editor/VisualEditorApp.tsx +343 -0
  106. package/data/templates/dashboard-composer/visual-editor/index.html +17 -0
  107. package/data/templates/dashboard-composer/visual-editor/main.tsx +95 -0
  108. package/data/templates/dashboard-composer/visual-editor/styles.css +310 -0
  109. package/data/templates/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  110. package/data/templates/database-workspace/README.md +5 -0
  111. package/data/templates/database-workspace/database-workspace.css +107 -0
  112. package/data/templates/database-workspace/index.html +14 -0
  113. package/data/templates/database-workspace/main.tsx +247 -0
  114. package/data/templates/database-workspace/styles.css +121 -0
  115. package/data/templates/database-workspace/template.manifest.json +19 -0
  116. package/data/templates/interaction-lab/InteractionWorkbench.tsx +271 -0
  117. package/data/templates/interaction-lab/README.md +7 -0
  118. package/data/templates/interaction-lab/index.html +14 -0
  119. package/data/templates/interaction-lab/interaction-workbench.css +226 -0
  120. package/data/templates/interaction-lab/main.tsx +104 -0
  121. package/data/templates/interaction-lab/styles.css +224 -0
  122. package/data/templates/interaction-lab/template.manifest.json +36 -0
  123. package/lib/api.mjs +72 -5
  124. package/lib/composition-runtime.mjs +382 -0
  125. package/package.json +4 -1
@@ -0,0 +1,409 @@
1
+ import { StrictMode, useEffect, useMemo, useRef, useState } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { CalendarBlank, Moon, Sun, TextAlignLeft, TextAlignRight } from '@phosphor-icons/react'
4
+ import { Button } from '@utopia-studio-design/design-system/Button'
5
+ import { IconButton } from '@utopia-studio-design/design-system/IconButton'
6
+ import { PhosphorIcon } from '@utopia-studio-design/design-system/PhosphorIcon'
7
+ import type {
8
+ SchedulerEvent,
9
+ SchedulerEventEditHandler,
10
+ SchedulerMutationAdapter,
11
+ SchedulerResource,
12
+ SchedulerState,
13
+ SchedulerView,
14
+ } from '@utopia-studio-design/design-system/Calendar'
15
+ import { getSchedulerLocaleWeekInfo } from '@utopia-studio-design/design-system/Calendar'
16
+ import {
17
+ AgendaPanel,
18
+ CalendarSidebar,
19
+ EventEditorDrawer,
20
+ SchedulerShell,
21
+ TimezoneSelector,
22
+ } from './CalendarApplicationBlocks'
23
+ import {
24
+ calendarLocaleOptions,
25
+ getCalendarEditorLabels,
26
+ getCalendarSchedulerLabels,
27
+ getCalendarSidebarLabels,
28
+ isArabicCalendarLocale,
29
+ type CalendarLocale,
30
+ } from './calendar-localization'
31
+ import '@utopia-studio-design/design-system/core.css'
32
+ import '@utopia-studio-design/design-system/themes/utopia-default.css'
33
+ import './calendar-application.css'
34
+ import './styles.css'
35
+
36
+ const anchorDate = new Date(2026, 2, 9, 12)
37
+ const resources: SchedulerResource[] = [
38
+ { id: 'product', label: 'Product team', timezone: 'America/New_York' },
39
+ { id: 'customer', label: 'Customer calls', timezone: 'Europe/London' },
40
+ { id: 'focus', label: 'Focus time', timezone: 'Asia/Seoul' },
41
+ ]
42
+ const initialEvents: SchedulerEvent[] = [
43
+ {
44
+ id: 'release-window',
45
+ title: 'Release window',
46
+ start: '2026-03-09T04:00:00.000Z',
47
+ end: '2026-03-10T04:00:00.000Z',
48
+ allDay: true,
49
+ resourceId: 'product',
50
+ tone: 'accent',
51
+ },
52
+ {
53
+ id: 'design-review',
54
+ title: 'Design review',
55
+ start: '2026-03-09T13:00:00.000Z',
56
+ end: '2026-03-09T14:00:00.000Z',
57
+ resourceId: 'product',
58
+ tone: 'positive',
59
+ },
60
+ {
61
+ id: 'standup',
62
+ title: 'Delivery stand-up',
63
+ start: '2026-03-09T13:30:00.000Z',
64
+ end: '2026-03-09T14:15:00.000Z',
65
+ resourceId: 'product',
66
+ tone: 'warning',
67
+ },
68
+ {
69
+ id: 'focus-series',
70
+ title: 'Daily focus block',
71
+ start: '2026-03-09T15:00:00.000Z',
72
+ end: '2026-03-09T15:30:00.000Z',
73
+ resourceId: 'focus',
74
+ recurrence: { frequency: 'daily', count: 5 },
75
+ },
76
+ {
77
+ id: 'customer-call',
78
+ title: 'Customer onboarding',
79
+ start: '2026-03-09T22:30:00.000Z',
80
+ end: '2026-03-09T23:15:00.000Z',
81
+ resourceId: 'customer',
82
+ },
83
+ {
84
+ id: 'unavailable-lunch',
85
+ title: 'Unavailable · lunch',
86
+ start: '2026-03-10T16:00:00.000Z',
87
+ end: '2026-03-10T17:00:00.000Z',
88
+ resourceId: 'product',
89
+ unavailable: true,
90
+ tone: 'negative',
91
+ },
92
+ ]
93
+
94
+ const arabicEventTitles: Record<string, string> = {
95
+ 'release-window': 'نافذة الإصدار',
96
+ 'design-review': 'مراجعة التصميم',
97
+ standup: 'اجتماع متابعة التسليم',
98
+ 'focus-series': 'وقت التركيز اليومي',
99
+ 'customer-call': 'تهيئة العميل',
100
+ 'unavailable-lunch': 'غير متاح · الغداء',
101
+ }
102
+
103
+ const arabicResourceLabels: Record<string, string> = {
104
+ product: 'فريق المنتج',
105
+ customer: 'مكالمات العملاء',
106
+ focus: 'وقت التركيز',
107
+ }
108
+
109
+ const timezoneOptions = ['America/New_York', 'Europe/London', 'Asia/Seoul', 'UTC']
110
+ function localeFromUrl(): CalendarLocale {
111
+ const candidate = new URLSearchParams(window.location.search).get('locale')
112
+ return calendarLocaleOptions.includes(candidate as CalendarLocale) ? candidate as CalendarLocale : 'en-US'
113
+ }
114
+
115
+ function usesAutomaticSchedulerDirection() {
116
+ return new URLSearchParams(window.location.search).get('direction') === 'auto'
117
+ }
118
+
119
+ function viewFromUrl(): SchedulerView {
120
+ const candidate = new URLSearchParams(window.location.search).get('view')
121
+ if (candidate && ['month', 'week', 'day', 'agenda'].includes(candidate)) return candidate as SchedulerView
122
+ return window.matchMedia('(max-width: 44rem)').matches ? 'day' : 'week'
123
+ }
124
+
125
+ function App() {
126
+ const initialLocale = localeFromUrl()
127
+ const [events, setEvents] = useState(() => initialLocale.startsWith('ar')
128
+ ? initialEvents.map((event) => ({ ...event, title: arabicEventTitles[event.id] ?? event.title }))
129
+ : initialEvents)
130
+ const [date, setDate] = useState(anchorDate)
131
+ const [view, setView] = useState<SchedulerView>(viewFromUrl)
132
+ const [timezone, setTimezone] = useState('America/New_York')
133
+ const [locale, setLocale] = useState<CalendarLocale>(initialLocale)
134
+ const [direction, setDirection] = useState<'ltr' | 'rtl'>(initialLocale.startsWith('ar') ? 'rtl' : 'ltr')
135
+ const [dark, setDark] = useState(false)
136
+ const [state, setState] = useState<SchedulerState>('ready')
137
+ const [canEdit, setCanEdit] = useState(true)
138
+ const [qaControlsOpen, setQaControlsOpen] = useState(false)
139
+ const [selectedResources, setSelectedResources] = useState(resources.map((resource) => resource.id))
140
+ const [editorEvent, setEditorEvent] = useState<SchedulerEvent | null>(null)
141
+ const editorTriggerRef = useRef<HTMLElement | null>(null)
142
+ const automaticSchedulerDirection = usesAutomaticSchedulerDirection()
143
+ const failNext = useRef(false)
144
+ const isArabic = isArabicCalendarLocale(locale)
145
+ const weekInfo = useMemo(() => getSchedulerLocaleWeekInfo(locale), [locale])
146
+ const schedulerLabels = getCalendarSchedulerLabels(locale)
147
+ const displayResources = useMemo(() => resources.map((resource) => ({
148
+ ...resource,
149
+ label: isArabic ? arabicResourceLabels[resource.id] ?? resource.label : resource.label,
150
+ })), [isArabic])
151
+ const visibleEvents = useMemo(
152
+ () => events.filter((event) => !event.resourceId || selectedResources.includes(event.resourceId)),
153
+ [events, selectedResources],
154
+ )
155
+ const adapter = useMemo<SchedulerMutationAdapter>(() => ({
156
+ async commit() {
157
+ const shouldFail = failNext.current
158
+ failNext.current = false
159
+ await new Promise((resolve) => window.setTimeout(resolve, 80))
160
+ if (shouldFail) throw new Error('Fixture calendar mutation rejected')
161
+ },
162
+ }), [])
163
+
164
+ useEffect(() => {
165
+ const url = new URL(window.location.href)
166
+ url.searchParams.set('view', view)
167
+ url.searchParams.set('locale', locale)
168
+ window.history.replaceState({}, '', url)
169
+ }, [locale, view])
170
+ useEffect(() => {
171
+ document.documentElement.dir = direction
172
+ document.documentElement.lang = locale
173
+ document.title = isArabic
174
+ ? 'تطبيق التقويم — نظام تصميم Ceramic'
175
+ : 'Calendar Application — Ceramic Design System'
176
+ const skipLink = document.querySelector<HTMLAnchorElement>('.skip-link')
177
+ if (skipLink) skipLink.textContent = isArabic ? 'الانتقال إلى تطبيق التقويم' : 'Skip to calendar application'
178
+ return () => {
179
+ document.documentElement.dir = 'ltr'
180
+ document.documentElement.lang = 'en'
181
+ }
182
+ }, [direction, isArabic, locale])
183
+
184
+ const createDraft = (selectedDate: Date, trigger?: HTMLElement) => {
185
+ editorTriggerRef.current = trigger ?? null
186
+ const start = new Date(selectedDate)
187
+ start.setHours(10, 0, 0, 0)
188
+ const end = new Date(start.valueOf() + 30 * 60_000)
189
+ setEditorEvent({
190
+ id: `event-${events.length + 1}`,
191
+ title: isArabic ? 'حدث جديد' : 'New event',
192
+ start: start.toISOString(),
193
+ end: end.toISOString(),
194
+ resourceId: selectedResources[0] ?? resources[0].id,
195
+ })
196
+ }
197
+ const editEvent: SchedulerEventEditHandler = (event, trigger) => {
198
+ editorTriggerRef.current = trigger ?? null
199
+ setEditorEvent(event)
200
+ }
201
+
202
+ const saveEvent = async (draft: SchedulerEvent, scope: 'occurrence' | 'series') => {
203
+ const sourceId = draft.recurrenceId ?? draft.id
204
+ const exists = events.some((event) => event.id === sourceId)
205
+ await adapter.commit(
206
+ { type: exists ? 'update' : 'create', event: draft },
207
+ new AbortController().signal,
208
+ )
209
+ if (draft.recurrenceId && scope === 'occurrence' && draft.occurrenceStart) {
210
+ setEvents((current) => [
211
+ ...current.map((event) => event.id === draft.recurrenceId
212
+ ? { ...event, recurrenceExceptions: [...(event.recurrenceExceptions ?? []), draft.occurrenceStart!] }
213
+ : event),
214
+ { ...draft, id: `${draft.recurrenceId}-override-${draft.occurrenceStart}`, recurrenceId: undefined, recurrence: undefined },
215
+ ])
216
+ } else {
217
+ setEvents((current) => current.some((event) => event.id === sourceId)
218
+ ? current.map((event) => event.id === sourceId ? { ...draft, id: sourceId, recurrenceId: undefined } : event)
219
+ : [...current, draft])
220
+ }
221
+ setEditorEvent(null)
222
+ }
223
+
224
+ return (
225
+ <main
226
+ className="calendar-template"
227
+ data-color-mode={dark ? 'dark' : 'light'}
228
+ data-theme="utopia-default"
229
+ dir={direction}
230
+ id="main-content"
231
+ >
232
+ <header className="calendar-appbar">
233
+ <div className="calendar-appbar-brand">
234
+ <span className="calendar-appbar-mark">
235
+ <PhosphorIcon decorative icon={CalendarBlank} weight="fill" />
236
+ </span>
237
+ <div>
238
+ <h1>{isArabic ? 'التقويم' : 'Calendar'}</h1>
239
+ <p>{new Intl.DateTimeFormat(locale, {
240
+ weekday: 'long',
241
+ month: 'long',
242
+ day: 'numeric',
243
+ }).format(date)}</p>
244
+ </div>
245
+ </div>
246
+ <div className="calendar-appbar-actions">
247
+ <TimezoneSelector
248
+ className="calendar-appbar-locale"
249
+ label={isArabic ? 'اللغة والمنطقة' : 'Language and region'}
250
+ timezones={[...calendarLocaleOptions]}
251
+ value={locale}
252
+ onValueChange={(value) => {
253
+ const nextLocale = value as CalendarLocale
254
+ setLocale(nextLocale)
255
+ setDirection(nextLocale.startsWith('ar') ? 'rtl' : 'ltr')
256
+ }}
257
+ />
258
+ <TimezoneSelector
259
+ className="calendar-appbar-timezone"
260
+ label={isArabic ? 'المنطقة الزمنية' : 'Display timezone'}
261
+ timezones={timezoneOptions}
262
+ value={timezone}
263
+ onValueChange={setTimezone}
264
+ />
265
+ <IconButton
266
+ label={direction === 'ltr'
267
+ ? (isArabic ? 'تبديل التقويم إلى اليمين' : 'Switch scheduler to RTL')
268
+ : (isArabic ? 'تبديل التقويم إلى اليسار' : 'Switch scheduler to LTR')}
269
+ size="sm"
270
+ variant="ghost"
271
+ onClick={() => setDirection((value) => value === 'ltr' ? 'rtl' : 'ltr')}
272
+ >
273
+ <PhosphorIcon
274
+ decorative
275
+ icon={direction === 'ltr' ? TextAlignRight : TextAlignLeft}
276
+ size="sm"
277
+ />
278
+ </IconButton>
279
+ <IconButton
280
+ label={dark
281
+ ? (isArabic ? 'التبديل إلى الوضع الفاتح' : 'Switch to light')
282
+ : (isArabic ? 'التبديل إلى الوضع الداكن' : 'Switch to dark')}
283
+ size="sm"
284
+ variant="ghost"
285
+ onClick={() => setDark((value) => !value)}
286
+ >
287
+ <PhosphorIcon decorative icon={dark ? Sun : Moon} size="sm" />
288
+ </IconButton>
289
+ </div>
290
+ </header>
291
+
292
+ <section className="calendar-application-shell">
293
+ <CalendarSidebar
294
+ date={date}
295
+ labels={getCalendarSidebarLabels(locale)}
296
+ locale={locale}
297
+ resources={displayResources}
298
+ selectedResourceIds={selectedResources}
299
+ weekStartsOn={weekInfo.firstDay}
300
+ onDateChange={setDate}
301
+ onResourceToggle={(resourceId) => setSelectedResources((current) => (
302
+ current.includes(resourceId) ? current.filter((id) => id !== resourceId) : [...current, resourceId]
303
+ ))}
304
+ />
305
+ <SchedulerShell
306
+ adapter={adapter}
307
+ availability={[
308
+ { resourceId: 'product', timezone: 'America/New_York', weekday: 2, startMinute: 720, endMinute: 780, available: false },
309
+ ]}
310
+ date={date}
311
+ calendarSystem="gregory"
312
+ direction={automaticSchedulerDirection ? undefined : direction}
313
+ errorMessage={isArabic ? 'تعذر تحميل أحداث التقويم.' : undefined}
314
+ events={visibleEvents}
315
+ labels={schedulerLabels}
316
+ locale={locale}
317
+ permissions={{
318
+ create: canEdit,
319
+ edit: canEdit,
320
+ move: canEdit,
321
+ resize: canEdit,
322
+ duplicate: canEdit,
323
+ delete: canEdit,
324
+ }}
325
+ resources={displayResources}
326
+ secondaryCalendar={isArabic ? 'islamic-umalqura' : undefined}
327
+ showAgendaAlternative={false}
328
+ state={state}
329
+ timezone={timezone}
330
+ view={view}
331
+ weekendDays={weekInfo.weekend}
332
+ weekStartsOn={weekInfo.firstDay}
333
+ onDateChange={setDate}
334
+ onEventCreate={createDraft}
335
+ onEventEdit={editEvent}
336
+ onEventsChange={(nextVisible) => {
337
+ const visibleIds = new Set(visibleEvents.map((event) => event.id))
338
+ setEvents((current) => [...current.filter((event) => !visibleIds.has(event.id)), ...nextVisible])
339
+ }}
340
+ onViewChange={setView}
341
+ />
342
+ {view !== 'agenda' ? (
343
+ <AgendaPanel
344
+ calendarSystem="gregory"
345
+ date={date}
346
+ events={visibleEvents}
347
+ labels={schedulerLabels}
348
+ locale={locale}
349
+ timezone={timezone}
350
+ weekendDays={weekInfo.weekend}
351
+ weekStartsOn={weekInfo.firstDay}
352
+ onEventEdit={editEvent}
353
+ />
354
+ ) : null}
355
+ </section>
356
+
357
+ <details
358
+ className="calendar-fixture-controls"
359
+ open={qaControlsOpen}
360
+ onToggle={(event) => setQaControlsOpen(event.currentTarget.open)}
361
+ >
362
+ <summary>{isArabic ? 'حالات الاختبار' : 'Test states'}</summary>
363
+ <div className="calendar-fixture-controls-content">
364
+ <div>
365
+ <span>{isArabic ? 'عناصر تحكم اختبار الجودة' : 'QA fixture controls'}</span>
366
+ <div>
367
+ {(['ready', 'loading', 'empty', 'error'] as SchedulerState[]).map((item) => (
368
+ <Button
369
+ aria-pressed={state === item}
370
+ key={item}
371
+ size="sm"
372
+ variant={state === item ? 'secondary' : 'ghost'}
373
+ onClick={() => setState(item)}
374
+ >
375
+ {item}
376
+ </Button>
377
+ ))}
378
+ </div>
379
+ </div>
380
+ <Button aria-pressed={!canEdit} size="sm" variant="outline" onClick={() => setCanEdit((value) => !value)}>
381
+ {canEdit
382
+ ? (isArabic ? 'اختبار صلاحيات القراءة فقط' : 'Test read-only permissions')
383
+ : (isArabic ? 'تمكين تحرير الأحداث' : 'Enable event editing')}
384
+ </Button>
385
+ <Button size="sm" variant="outline" onClick={() => { failNext.current = true }}>
386
+ {isArabic ? 'إفشال الحفظ التالي' : 'Fail next save'}
387
+ </Button>
388
+ </div>
389
+ </details>
390
+
391
+ <EventEditorDrawer
392
+ canDelete={Boolean(editorEvent && events.some((event) => event.id === (editorEvent.recurrenceId ?? editorEvent.id)))}
393
+ dir={direction}
394
+ event={editorEvent}
395
+ labels={getCalendarEditorLabels(locale)}
396
+ open={Boolean(editorEvent)}
397
+ returnFocusRef={editorTriggerRef}
398
+ onClose={() => setEditorEvent(null)}
399
+ onDelete={(event) => {
400
+ setEvents((current) => current.filter((item) => item.id !== (event.recurrenceId ?? event.id)))
401
+ setEditorEvent(null)
402
+ }}
403
+ onSave={saveEvent}
404
+ />
405
+ </main>
406
+ )
407
+ }
408
+
409
+ createRoot(document.getElementById('root')!).render(<StrictMode><App /></StrictMode>)
@@ -0,0 +1,248 @@
1
+ * { box-sizing: border-box; }
2
+
3
+ html,
4
+ body,
5
+ #root {
6
+ min-block-size: 100%;
7
+ margin: 0;
8
+ }
9
+
10
+ body {
11
+ background: var(--background);
12
+ color: var(--foreground);
13
+ font-family: var(--font-sans);
14
+ }
15
+
16
+ .skip-link {
17
+ position: fixed;
18
+ inset-block-start: var(--space-2);
19
+ inset-inline-start: var(--space-2);
20
+ z-index: var(--z-modal);
21
+ translate: 0 -200%;
22
+ }
23
+
24
+ .skip-link:focus { translate: 0; }
25
+
26
+ .calendar-template {
27
+ min-block-size: 100dvh;
28
+ padding-inline: var(--page-gutter);
29
+ padding-block-end: var(--space-4);
30
+ background: var(--background);
31
+ color: var(--foreground);
32
+ }
33
+
34
+ .calendar-template > * {
35
+ max-inline-size: var(--container-wide);
36
+ margin-inline: auto;
37
+ }
38
+
39
+ .calendar-appbar {
40
+ display: flex;
41
+ min-block-size: 5rem;
42
+ align-items: center;
43
+ justify-content: space-between;
44
+ gap: var(--space-4);
45
+ border-block-end: var(--border-width-hairline) solid var(--border);
46
+ }
47
+
48
+ .calendar-appbar-brand,
49
+ .calendar-appbar-actions {
50
+ display: flex;
51
+ align-items: center;
52
+ }
53
+
54
+ .calendar-appbar-brand {
55
+ gap: var(--space-3);
56
+ min-inline-size: 0;
57
+ }
58
+
59
+ .calendar-appbar-mark {
60
+ display: inline-grid;
61
+ flex: 0 0 auto;
62
+ inline-size: 2.5rem;
63
+ block-size: 2.5rem;
64
+ place-items: center;
65
+ border-radius: var(--radius-control);
66
+ background: var(--primary);
67
+ color: var(--primary-foreground);
68
+ }
69
+
70
+ .calendar-appbar-brand > div {
71
+ display: grid;
72
+ gap: var(--space-1);
73
+ min-inline-size: 0;
74
+ }
75
+
76
+ .calendar-appbar :is(h1, p) { margin: 0; }
77
+
78
+ .calendar-appbar h1 {
79
+ font-size: var(--font-size-h4);
80
+ line-height: var(--line-height-heading);
81
+ letter-spacing: var(--tracking-heading);
82
+ }
83
+
84
+ .calendar-appbar p {
85
+ color: var(--muted-foreground);
86
+ font-size: var(--font-size-small);
87
+ }
88
+
89
+ .calendar-appbar-actions {
90
+ gap: var(--space-2);
91
+ }
92
+
93
+ .calendar-appbar-timezone,
94
+ .calendar-appbar-locale {
95
+ display: grid;
96
+ grid-template-columns: auto minmax(10rem, 1fr);
97
+ align-items: center;
98
+ gap: var(--space-2);
99
+ }
100
+
101
+ .calendar-appbar-timezone .uds-field-label,
102
+ .calendar-appbar-locale .uds-field-label {
103
+ color: var(--muted-foreground);
104
+ font-size: var(--font-size-small);
105
+ }
106
+
107
+ .calendar-appbar-timezone .uds-select-trigger,
108
+ .calendar-appbar-locale .uds-select-trigger {
109
+ min-inline-size: 0;
110
+ inline-size: 100%;
111
+ max-inline-size: 100%;
112
+ min-block-size: var(--button-height-sm);
113
+ }
114
+
115
+ .calendar-appbar-actions .uds-select-trigger > span {
116
+ min-inline-size: 0;
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+
122
+ .calendar-application-shell {
123
+ display: grid;
124
+ grid-template-columns: 15rem minmax(0, 1fr) 17rem;
125
+ align-items: stretch;
126
+ min-inline-size: 0;
127
+ min-block-size: calc(100dvh - 8rem);
128
+ margin-block-start: var(--space-4);
129
+ overflow: hidden;
130
+ border: var(--border-width-hairline) solid var(--border);
131
+ border-radius: var(--radius-surface);
132
+ background: var(--surface);
133
+ }
134
+
135
+ .calendar-application-shell > * { min-inline-size: 0; }
136
+
137
+ .calendar-fixture-controls {
138
+ margin-block-start: var(--space-3);
139
+ color: var(--muted-foreground);
140
+ }
141
+
142
+ .calendar-fixture-controls > summary {
143
+ inline-size: fit-content;
144
+ cursor: pointer;
145
+ font-size: var(--font-size-small);
146
+ font-weight: var(--font-weight-medium);
147
+ }
148
+
149
+ .calendar-fixture-controls-content,
150
+ .calendar-fixture-controls-content > div > div {
151
+ display: flex;
152
+ flex-wrap: wrap;
153
+ align-items: center;
154
+ gap: var(--space-2);
155
+ }
156
+
157
+ .calendar-fixture-controls-content {
158
+ justify-content: space-between;
159
+ margin-block-start: var(--space-2);
160
+ padding: var(--space-3);
161
+ border: var(--border-width-hairline) dashed var(--border);
162
+ border-radius: var(--radius-control);
163
+ background: var(--surface);
164
+ }
165
+
166
+ .calendar-fixture-controls-content > div {
167
+ display: grid;
168
+ gap: var(--space-2);
169
+ }
170
+
171
+ .calendar-fixture-controls-content > div > span {
172
+ font-size: var(--font-size-eyebrow);
173
+ font-weight: var(--font-weight-semibold);
174
+ letter-spacing: var(--tracking-eyebrow);
175
+ text-transform: uppercase;
176
+ }
177
+
178
+ @media (max-width: 72rem) {
179
+ .calendar-application-shell {
180
+ grid-template-columns: 14rem minmax(0, 1fr);
181
+ }
182
+
183
+ .ceramic-agenda-panel {
184
+ display: none;
185
+ }
186
+ }
187
+
188
+ @media (max-width: 52rem) {
189
+ .calendar-appbar {
190
+ align-items: flex-start;
191
+ flex-direction: column;
192
+ padding-block: var(--space-3);
193
+ }
194
+
195
+ .calendar-appbar-actions {
196
+ inline-size: 100%;
197
+ }
198
+
199
+ .calendar-appbar-timezone,
200
+ .calendar-appbar-locale {
201
+ flex: 1 1 auto;
202
+ }
203
+ }
204
+
205
+ @media (max-width: 44rem) {
206
+ .calendar-template {
207
+ padding-inline: var(--space-3);
208
+ }
209
+
210
+ .calendar-appbar {
211
+ min-block-size: auto;
212
+ }
213
+
214
+ .calendar-appbar-actions {
215
+ display: grid;
216
+ grid-template-columns: repeat(2, minmax(0, 1fr)) repeat(2, auto);
217
+ align-items: start;
218
+ }
219
+
220
+ .calendar-appbar-timezone,
221
+ .calendar-appbar-locale {
222
+ grid-template-columns: 1fr;
223
+ }
224
+
225
+ .calendar-appbar-timezone .uds-field-label,
226
+ .calendar-appbar-locale .uds-field-label {
227
+ position: absolute;
228
+ inline-size: 1px;
229
+ block-size: 1px;
230
+ overflow: hidden;
231
+ clip-path: inset(50%);
232
+ }
233
+
234
+ .calendar-application-shell {
235
+ grid-template-columns: minmax(0, 1fr);
236
+ min-block-size: calc(100dvh - 10rem);
237
+ margin-block-start: var(--space-3);
238
+ }
239
+
240
+ .ceramic-calendar-sidebar {
241
+ display: none;
242
+ }
243
+
244
+ .calendar-fixture-controls-content {
245
+ align-items: stretch;
246
+ flex-direction: column;
247
+ }
248
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "calendar-application",
3
+ "title": "Calendar Application",
4
+ "status": "available",
5
+ "route": "/templates/calendar-application/",
6
+ "entry": "templates/calendar-application/main.tsx",
7
+ "blocks": [
8
+ "scheduler-shell",
9
+ "calendar-sidebar",
10
+ "event-editor-drawer",
11
+ "agenda-panel",
12
+ "timezone-selector"
13
+ ],
14
+ "views": ["month", "week", "day", "agenda"],
15
+ "states": ["loading", "empty", "error", "ready", "conflict"],
16
+ "themes": ["utopia-default"],
17
+ "directions": ["ltr", "rtl"],
18
+ "locales": ["en-US", "ar-SA", "ar-EG", "ar-AE"],
19
+ "calendarSystems": ["gregory", "islamic-umalqura-secondary"],
20
+ "regionalWeekFixtures": ["ar-SA", "ar-EG", "ar-AE"],
21
+ "foundation": ["ceramic-calendar", "ceramic-button", "ceramic-button-group", "ceramic-select", "ceramic-forms"],
22
+ "packageDependencies": {
23
+ "@phosphor-icons/react": "^2.1.10"
24
+ },
25
+ "responsiveMinimum": "320px",
26
+ "timezoneFixtures": ["America/New_York", "Europe/London", "Asia/Seoul", "UTC"]
27
+ }
@@ -0,0 +1,9 @@
1
+ # Ceramic Composition Registry Lab
2
+
3
+ Use this runnable template to review the boundary between Ceramic block
4
+ definitions, versioned Layout JSON, agents, Studio runtime, and optional visual
5
+ editors such as Puck.
6
+
7
+ This is a registry inspector, not a drag-and-drop editor. Planned blocks remain
8
+ visible as roadmap definitions but cannot render in production until their
9
+ reviewed block implementation is registered as available.