@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.
Files changed (126) 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/theme-vyapti.json +125 -0
  52. package/data/manifests/themes.json +400 -28
  53. package/data/templates/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  54. package/data/templates/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  55. package/data/templates/analytics-dashboard/README.md +6 -0
  56. package/data/templates/analytics-dashboard/analytics-dashboard.css +92 -0
  57. package/data/templates/analytics-dashboard/index.html +13 -0
  58. package/data/templates/analytics-dashboard/main.tsx +196 -0
  59. package/data/templates/analytics-dashboard/styles.css +120 -0
  60. package/data/templates/analytics-dashboard/template.manifest.json +17 -0
  61. package/data/templates/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  62. package/data/templates/calendar-application/README.md +10 -0
  63. package/data/templates/calendar-application/calendar-application.css +216 -0
  64. package/data/templates/calendar-application/calendar-localization.ts +90 -0
  65. package/data/templates/calendar-application/index.html +14 -0
  66. package/data/templates/calendar-application/main.tsx +409 -0
  67. package/data/templates/calendar-application/styles.css +248 -0
  68. package/data/templates/calendar-application/template.manifest.json +27 -0
  69. package/data/templates/composition-registry-lab/README.md +9 -0
  70. package/data/templates/composition-registry-lab/index.html +14 -0
  71. package/data/templates/composition-registry-lab/main.tsx +217 -0
  72. package/data/templates/composition-registry-lab/sample-layout.json +63 -0
  73. package/data/templates/composition-registry-lab/styles.css +343 -0
  74. package/data/templates/composition-registry-lab/template.manifest.json +34 -0
  75. package/data/templates/dashboard-composer/ActivityFeedBlock.tsx +255 -0
  76. package/data/templates/dashboard-composer/AgentStatusBlock.tsx +313 -0
  77. package/data/templates/dashboard-composer/AnalyticsDashboardBlocks.tsx +107 -0
  78. package/data/templates/dashboard-composer/AnalyticsMetricBlocks.tsx +87 -0
  79. package/data/templates/dashboard-composer/CalendarApplicationBlocks.tsx +519 -0
  80. package/data/templates/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  81. package/data/templates/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  82. package/data/templates/dashboard-composer/DatabaseWorkspaceBlocks.tsx +240 -0
  83. package/data/templates/dashboard-composer/README.md +53 -0
  84. package/data/templates/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  85. package/data/templates/dashboard-composer/activity-feed-contract.ts +96 -0
  86. package/data/templates/dashboard-composer/activity-feed-fixture.ts +22 -0
  87. package/data/templates/dashboard-composer/activity-feed.css +154 -0
  88. package/data/templates/dashboard-composer/agent-status-contract.ts +133 -0
  89. package/data/templates/dashboard-composer/agent-status-fixture.ts +79 -0
  90. package/data/templates/dashboard-composer/agent-status-lab/index.html +19 -0
  91. package/data/templates/dashboard-composer/agent-status-lab/main.tsx +151 -0
  92. package/data/templates/dashboard-composer/agent-status-lab/styles.css +139 -0
  93. package/data/templates/dashboard-composer/agent-status.css +216 -0
  94. package/data/templates/dashboard-composer/analytics-dashboard.css +92 -0
  95. package/data/templates/dashboard-composer/calendar-application.css +216 -0
  96. package/data/templates/dashboard-composer/calendar-localization.ts +90 -0
  97. package/data/templates/dashboard-composer/calendar-panel-contract.ts +72 -0
  98. package/data/templates/dashboard-composer/dashboard-composer.css +328 -0
  99. package/data/templates/dashboard-composer/database-workspace.css +107 -0
  100. package/data/templates/dashboard-composer/index.html +14 -0
  101. package/data/templates/dashboard-composer/main.tsx +550 -0
  102. package/data/templates/dashboard-composer/sample-layout.json +147 -0
  103. package/data/templates/dashboard-composer/styles.css +198 -0
  104. package/data/templates/dashboard-composer/template.manifest.json +34 -0
  105. package/data/templates/dashboard-composer/visual-editor/PuckEditorSurface.tsx +132 -0
  106. package/data/templates/dashboard-composer/visual-editor/VisualEditorApp.tsx +343 -0
  107. package/data/templates/dashboard-composer/visual-editor/index.html +17 -0
  108. package/data/templates/dashboard-composer/visual-editor/main.tsx +95 -0
  109. package/data/templates/dashboard-composer/visual-editor/styles.css +310 -0
  110. package/data/templates/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  111. package/data/templates/database-workspace/README.md +5 -0
  112. package/data/templates/database-workspace/database-workspace.css +107 -0
  113. package/data/templates/database-workspace/index.html +14 -0
  114. package/data/templates/database-workspace/main.tsx +247 -0
  115. package/data/templates/database-workspace/styles.css +121 -0
  116. package/data/templates/database-workspace/template.manifest.json +19 -0
  117. package/data/templates/interaction-lab/InteractionWorkbench.tsx +271 -0
  118. package/data/templates/interaction-lab/README.md +7 -0
  119. package/data/templates/interaction-lab/index.html +14 -0
  120. package/data/templates/interaction-lab/interaction-workbench.css +226 -0
  121. package/data/templates/interaction-lab/main.tsx +104 -0
  122. package/data/templates/interaction-lab/styles.css +224 -0
  123. package/data/templates/interaction-lab/template.manifest.json +36 -0
  124. package/lib/api.mjs +72 -5
  125. package/lib/composition-runtime.mjs +382 -0
  126. package/package.json +4 -1
@@ -0,0 +1,328 @@
1
+ .ceramic-todo-list {
2
+ display: grid;
3
+ align-content: start;
4
+ gap: var(--space-3);
5
+ min-inline-size: 0;
6
+ block-size: 100%;
7
+ padding: var(--space-4);
8
+ background: var(--surface);
9
+ color: var(--foreground);
10
+ }
11
+
12
+ .ceramic-todo-list > header,
13
+ .ceramic-todo-items > li,
14
+ .ceramic-todo-items label,
15
+ .ceramic-todo-items > li > div,
16
+ .ceramic-todo-create,
17
+ .ceramic-dashboard-block-toolbar,
18
+ .ceramic-dashboard-block-toolbar > div {
19
+ display: flex;
20
+ align-items: center;
21
+ }
22
+
23
+ .ceramic-todo-list > header,
24
+ .ceramic-todo-items > li,
25
+ .ceramic-dashboard-block-toolbar {
26
+ justify-content: space-between;
27
+ }
28
+
29
+ .ceramic-todo-list > header {
30
+ flex-wrap: wrap;
31
+ gap: var(--space-3);
32
+ padding-block-end: var(--space-3);
33
+ border-block-end: var(--border-width-hairline) solid var(--border);
34
+ }
35
+
36
+ .ceramic-todo-list > header > div {
37
+ display: grid;
38
+ min-inline-size: 0;
39
+ gap: var(--space-1);
40
+ }
41
+
42
+ .ceramic-todo-list h2,
43
+ .ceramic-todo-list p {
44
+ margin: 0;
45
+ }
46
+
47
+ .ceramic-todo-list > header span {
48
+ color: var(--muted-foreground);
49
+ font-size: var(--font-size-eyebrow);
50
+ font-weight: var(--font-weight-semibold);
51
+ letter-spacing: var(--tracking-eyebrow);
52
+ text-transform: uppercase;
53
+ }
54
+
55
+ .ceramic-todo-items {
56
+ display: grid;
57
+ gap: var(--space-1);
58
+ margin: 0;
59
+ padding: 0;
60
+ list-style: none;
61
+ }
62
+
63
+ .ceramic-todo-items > li {
64
+ gap: var(--space-2);
65
+ min-inline-size: 0;
66
+ padding-block: var(--space-2);
67
+ border-block-end: var(--border-width-hairline) solid var(--border);
68
+ }
69
+
70
+ .ceramic-todo-list[data-density='compact'] .ceramic-todo-items > li {
71
+ padding-block: var(--space-1);
72
+ }
73
+
74
+ .ceramic-todo-list[data-density='comfortable'] .ceramic-todo-items > li {
75
+ padding-block: var(--space-3);
76
+ }
77
+
78
+ .ceramic-todo-items label {
79
+ gap: var(--space-2);
80
+ min-inline-size: 0;
81
+ }
82
+
83
+ .ceramic-todo-items label > span {
84
+ overflow-wrap: anywhere;
85
+ }
86
+
87
+ .ceramic-todo-items > li[data-completed='true'] label > span {
88
+ color: var(--muted-foreground);
89
+ text-decoration: line-through;
90
+ }
91
+
92
+ .ceramic-todo-items > li > div {
93
+ flex: 0 0 auto;
94
+ gap: var(--space-1);
95
+ color: var(--muted-foreground);
96
+ font-size: var(--font-size-small);
97
+ }
98
+
99
+ .ceramic-todo-state {
100
+ display: grid;
101
+ gap: var(--space-1);
102
+ min-block-size: 8rem;
103
+ align-content: center;
104
+ justify-items: start;
105
+ color: var(--muted-foreground);
106
+ }
107
+
108
+ .ceramic-todo-state strong {
109
+ color: var(--foreground);
110
+ }
111
+
112
+ .ceramic-todo-create {
113
+ gap: var(--space-2);
114
+ min-inline-size: 0;
115
+ margin-block-start: auto;
116
+ padding-block-start: var(--space-2);
117
+ }
118
+
119
+ .ceramic-todo-create .uds-input {
120
+ flex: 1 1 auto;
121
+ min-inline-size: 0;
122
+ inline-size: 100%;
123
+ }
124
+
125
+ .ceramic-todo-create .uds-button {
126
+ flex: 0 0 auto;
127
+ }
128
+
129
+ .ceramic-dashboard-renderer {
130
+ display: grid;
131
+ grid-template-columns: repeat(12, minmax(0, 1fr));
132
+ gap: var(--space-3);
133
+ min-inline-size: 0;
134
+ }
135
+
136
+ .ceramic-dashboard-block {
137
+ display: flex;
138
+ flex-direction: column;
139
+ grid-column: var(--ceramic-block-column) / span var(--ceramic-block-span);
140
+ min-inline-size: 0;
141
+ overflow: clip;
142
+ border: var(--border-width-hairline) solid var(--border);
143
+ background: var(--surface);
144
+ }
145
+
146
+ .ceramic-dashboard-block > :not(.ceramic-dashboard-block-toolbar) {
147
+ flex: 1 1 auto;
148
+ min-block-size: 0;
149
+ }
150
+
151
+ .ceramic-dashboard-block[data-locked='true'] {
152
+ border-color: var(--interaction-selection-border);
153
+ }
154
+
155
+ .ceramic-dashboard-block-toolbar {
156
+ gap: var(--space-2);
157
+ min-inline-size: 0;
158
+ padding: var(--space-2) var(--space-3);
159
+ border-block-end: var(--border-width-hairline) solid var(--border);
160
+ background: var(--muted);
161
+ }
162
+
163
+ .ceramic-dashboard-block-toolbar > div {
164
+ gap: var(--space-1);
165
+ min-inline-size: 0;
166
+ }
167
+
168
+ .ceramic-dashboard-block-toolbar strong {
169
+ overflow: hidden;
170
+ text-overflow: ellipsis;
171
+ white-space: nowrap;
172
+ }
173
+
174
+ .ceramic-dashboard-missing,
175
+ .ceramic-dashboard-error {
176
+ display: grid;
177
+ gap: var(--space-1);
178
+ padding: var(--space-4);
179
+ color: var(--muted-foreground);
180
+ }
181
+
182
+ .ceramic-dashboard-missing strong,
183
+ .ceramic-dashboard-error strong {
184
+ color: var(--foreground);
185
+ }
186
+
187
+ .ceramic-dashboard-block .uds-scheduler-root {
188
+ border: 0;
189
+ }
190
+
191
+ .ceramic-puck-canvas {
192
+ container-name: ceramic-puck-canvas;
193
+ container-type: inline-size;
194
+ min-block-size: 100%;
195
+ min-inline-size: 0;
196
+ padding: var(--space-4);
197
+ background: var(--background);
198
+ color: var(--foreground);
199
+ }
200
+
201
+ .ceramic-puck-canvas > [class*='_DropZone_'] {
202
+ display: grid;
203
+ grid-template-columns: repeat(12, minmax(0, 1fr));
204
+ align-items: start;
205
+ gap: var(--space-3);
206
+ min-inline-size: 0;
207
+ }
208
+
209
+ .ceramic-puck-block-preview {
210
+ display: flex;
211
+ flex-direction: column;
212
+ grid-column: span var(--ceramic-puck-span);
213
+ min-block-size: 20rem;
214
+ min-inline-size: 0;
215
+ overflow: clip;
216
+ border: var(--border-width-hairline) solid var(--border);
217
+ background: var(--surface);
218
+ }
219
+
220
+ .ceramic-puck-block-preview[data-block-type='calendar-panel'] {
221
+ block-size: 34rem;
222
+ min-block-size: 34rem;
223
+ }
224
+
225
+ .ceramic-puck-block-preview[data-block-type='todo-list'] {
226
+ container-type: inline-size;
227
+ }
228
+
229
+ .ceramic-puck-block-preview[data-block-type='activity-feed'] {
230
+ min-block-size: 24rem;
231
+ }
232
+
233
+ .ceramic-puck-block-preview[data-block-type='agent-status'] {
234
+ min-block-size: 30rem;
235
+ }
236
+
237
+ .ceramic-puck-block-preview[data-block-type='issue-table'],
238
+ .ceramic-puck-block-preview[data-block-type='chart-panel'] {
239
+ min-block-size: 34rem;
240
+ }
241
+
242
+ .ceramic-puck-block-preview[data-locked='true'] {
243
+ border-color: var(--interaction-selection-border);
244
+ }
245
+
246
+ .ceramic-puck-block-preview .uds-scheduler-root,
247
+ .ceramic-puck-block-preview .ceramic-kpi-grid {
248
+ border: 0;
249
+ }
250
+
251
+ .ceramic-issue-table-block,
252
+ .ceramic-chart-panel-block {
253
+ min-inline-size: 0;
254
+ max-inline-size: 100%;
255
+ block-size: 100%;
256
+ padding: var(--space-3);
257
+ background: var(--surface);
258
+ }
259
+
260
+ .ceramic-issue-table-block .uds-database-root {
261
+ block-size: 100%;
262
+ }
263
+
264
+ .ceramic-chart-panel-state {
265
+ display: grid;
266
+ place-content: center;
267
+ justify-items: start;
268
+ gap: var(--space-2);
269
+ min-block-size: 20rem;
270
+ padding: var(--space-4);
271
+ color: var(--muted-foreground);
272
+ }
273
+
274
+ .ceramic-chart-panel-state strong {
275
+ color: var(--foreground);
276
+ }
277
+
278
+ @container (max-width: 30rem) {
279
+ .ceramic-puck-block-preview .ceramic-todo-items > li {
280
+ align-items: start;
281
+ flex-direction: column;
282
+ }
283
+
284
+ .ceramic-puck-block-preview .ceramic-todo-items > li > div {
285
+ padding-inline-start: calc(var(--control-size-sm) + var(--space-2));
286
+ }
287
+ }
288
+
289
+ @container ceramic-puck-canvas (max-width: 42rem) {
290
+ .ceramic-puck-block-preview {
291
+ grid-column: 1 / -1;
292
+ }
293
+ }
294
+
295
+ @media (max-width: 48rem) {
296
+ .ceramic-dashboard-block {
297
+ grid-column: 1 / -1;
298
+ grid-row: auto;
299
+ }
300
+
301
+ .ceramic-puck-block-preview {
302
+ grid-column: 1 / -1;
303
+ }
304
+
305
+ .ceramic-dashboard-block-toolbar {
306
+ align-items: start;
307
+ }
308
+ }
309
+
310
+ @media (max-width: 28rem) {
311
+ .ceramic-dashboard-block-toolbar > div:first-child .uds-badge {
312
+ display: none;
313
+ }
314
+
315
+ .ceramic-dashboard-block-toolbar > div:first-child strong {
316
+ overflow: visible;
317
+ white-space: normal;
318
+ }
319
+
320
+ .ceramic-todo-items > li {
321
+ align-items: start;
322
+ flex-direction: column;
323
+ }
324
+
325
+ .ceramic-todo-items > li > div {
326
+ padding-inline-start: calc(var(--control-size-sm) + var(--space-2));
327
+ }
328
+ }
@@ -0,0 +1,240 @@
1
+ import * as React from 'react'
2
+ import { Badge } from '@utopia-studio-design/design-system/Badge'
3
+ import { Button } from '@utopia-studio-design/design-system/Button'
4
+ import {
5
+ DatabasePropertyCell,
6
+ DatabaseRoot,
7
+ type DatabaseMutationAdapter,
8
+ type DatabasePermissions,
9
+ type DatabaseQuery,
10
+ type DatabaseRecord,
11
+ type DatabaseSchema,
12
+ type DatabaseState,
13
+ type DatabaseView,
14
+ } from '@utopia-studio-design/design-system/Database'
15
+ import './database-workspace.css'
16
+
17
+ export interface DatabaseToolbarProps extends React.HTMLAttributes<HTMLElement> {
18
+ activeFilterCount?: number
19
+ activeSortCount?: number
20
+ groupLabel?: string
21
+ onCreate?: () => void
22
+ onFilter?: () => void
23
+ onSearchChange?: (value: string) => void
24
+ onSort?: () => void
25
+ searchValue?: string
26
+ }
27
+
28
+ export function DatabaseToolbar({
29
+ activeFilterCount = 0,
30
+ activeSortCount = 0,
31
+ groupLabel,
32
+ onCreate,
33
+ onFilter,
34
+ onSearchChange,
35
+ onSort,
36
+ searchValue = '',
37
+ ...props
38
+ }: DatabaseToolbarProps) {
39
+ return (
40
+ <header className="ceramic-database-toolbar" {...props}>
41
+ <label>
42
+ <span className="uds-visually-hidden">Search records</span>
43
+ <input
44
+ placeholder="Search records"
45
+ type="search"
46
+ value={searchValue}
47
+ onChange={(event) => onSearchChange?.(event.target.value)}
48
+ />
49
+ </label>
50
+ <div>
51
+ <Button size="sm" variant="outline" onClick={onFilter}>
52
+ Filter {activeFilterCount ? `(${activeFilterCount})` : ''}
53
+ </Button>
54
+ <Button size="sm" variant="outline" onClick={onSort}>
55
+ Sort {activeSortCount ? `(${activeSortCount})` : ''}
56
+ </Button>
57
+ {groupLabel ? <Badge variant="outline">Grouped by {groupLabel}</Badge> : null}
58
+ </div>
59
+ <Button size="sm" onClick={onCreate}>New record</Button>
60
+ </header>
61
+ )
62
+ }
63
+
64
+ export interface SavedViewsBarProps extends React.HTMLAttributes<HTMLElement> {
65
+ activeViewId: string
66
+ onViewChange?: (viewId: string) => void
67
+ views: DatabaseView[]
68
+ }
69
+
70
+ export function SavedViewsBar({
71
+ activeViewId,
72
+ onViewChange,
73
+ views,
74
+ ...props
75
+ }: SavedViewsBarProps) {
76
+ return (
77
+ <nav aria-label="Workspace views" className="ceramic-saved-views" {...props}>
78
+ {views.map((view) => (
79
+ <Button
80
+ aria-current={activeViewId === view.id ? 'page' : undefined}
81
+ key={view.id}
82
+ size="sm"
83
+ variant={activeViewId === view.id ? 'secondary' : 'ghost'}
84
+ onClick={() => onViewChange?.(view.id)}
85
+ >
86
+ {view.label}
87
+ </Button>
88
+ ))}
89
+ </nav>
90
+ )
91
+ }
92
+
93
+ export interface DatabaseTableBlockProps extends React.HTMLAttributes<HTMLElement> {
94
+ adapter?: DatabaseMutationAdapter
95
+ density?: 'compact' | 'default' | 'comfortable'
96
+ direction?: 'ltr' | 'rtl'
97
+ locale?: string
98
+ onRecordOpen?: (record: DatabaseRecord) => void
99
+ onCopyCell?: (text: string, recordId: string, propertyId: string) => void
100
+ onPasteCell?: (recordId: string, propertyId: string) => void
101
+ onRecordsChange?: (records: DatabaseRecord[]) => void
102
+ permissions?: DatabasePermissions
103
+ query?: DatabaseQuery
104
+ records: DatabaseRecord[]
105
+ schema: DatabaseSchema
106
+ state?: DatabaseState
107
+ views?: DatabaseView[]
108
+ }
109
+
110
+ export function DatabaseTableBlock({
111
+ adapter,
112
+ density,
113
+ direction,
114
+ locale,
115
+ onRecordOpen,
116
+ onCopyCell,
117
+ onPasteCell,
118
+ onRecordsChange,
119
+ permissions,
120
+ query,
121
+ records,
122
+ schema,
123
+ state,
124
+ views,
125
+ ...props
126
+ }: DatabaseTableBlockProps) {
127
+ return (
128
+ <DatabaseRoot
129
+ adapter={adapter}
130
+ density={density}
131
+ direction={direction}
132
+ locale={locale}
133
+ permissions={permissions}
134
+ query={query}
135
+ records={records}
136
+ schema={schema}
137
+ state={state}
138
+ views={views}
139
+ onRecordOpen={onRecordOpen}
140
+ onCopyCell={onCopyCell}
141
+ onPasteCell={onPasteCell}
142
+ onRecordsChange={onRecordsChange}
143
+ {...props}
144
+ />
145
+ )
146
+ }
147
+
148
+ export interface RecordDetailDrawerProps extends React.HTMLAttributes<HTMLElement> {
149
+ onClose?: () => void
150
+ open: boolean
151
+ record: DatabaseRecord | null
152
+ schema: DatabaseSchema
153
+ }
154
+
155
+ export function RecordDetailDrawer({
156
+ onClose,
157
+ open,
158
+ record,
159
+ schema,
160
+ ...props
161
+ }: RecordDetailDrawerProps) {
162
+ const drawerRef = React.useRef<HTMLElement>(null)
163
+ const closeRef = React.useRef<HTMLButtonElement>(null)
164
+ React.useLayoutEffect(() => {
165
+ if (!open || !record) return
166
+ const trigger = document.activeElement instanceof HTMLElement ? document.activeElement : null
167
+ closeRef.current?.focus()
168
+ return () => trigger?.focus()
169
+ }, [open, record])
170
+ if (!open || !record) return null
171
+ return (
172
+ <>
173
+ <div aria-hidden="true" className="ceramic-record-drawer-backdrop" onClick={onClose} />
174
+ <aside
175
+ aria-label="Record details"
176
+ aria-modal="true"
177
+ className="ceramic-record-drawer"
178
+ ref={drawerRef}
179
+ role="dialog"
180
+ onKeyDown={(event) => {
181
+ if (event.key === 'Escape') {
182
+ event.preventDefault()
183
+ onClose?.()
184
+ return
185
+ }
186
+ if (event.key !== 'Tab') return
187
+ const focusable = [...(drawerRef.current?.querySelectorAll<HTMLElement>(
188
+ 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
189
+ ) ?? [])]
190
+ if (focusable.length === 0) return
191
+ const first = focusable[0]
192
+ const last = focusable.at(-1)!
193
+ if (event.shiftKey && document.activeElement === first) {
194
+ event.preventDefault()
195
+ last.focus()
196
+ } else if (!event.shiftKey && document.activeElement === last) {
197
+ event.preventDefault()
198
+ first.focus()
199
+ }
200
+ }}
201
+ {...props}
202
+ >
203
+ <header>
204
+ <div>
205
+ <span>Record detail</span>
206
+ <h2>{String(record.values[schema.primaryPropertyId] ?? 'Untitled')}</h2>
207
+ </div>
208
+ <Button ref={closeRef} aria-label="Close record details" isIconOnly variant="ghost" onClick={onClose}>×</Button>
209
+ </header>
210
+ <dl>
211
+ {schema.properties.map((property) => (
212
+ <div key={property.id}>
213
+ <dt>{property.label}</dt>
214
+ <dd>
215
+ <DatabasePropertyCell property={property} value={record.values[property.id]} />
216
+ </dd>
217
+ </div>
218
+ ))}
219
+ </dl>
220
+ </aside>
221
+ </>
222
+ )
223
+ }
224
+
225
+ export interface BulkActionBarProps extends React.HTMLAttributes<HTMLElement> {
226
+ count: number
227
+ onClear?: () => void
228
+ onDelete?: () => void
229
+ }
230
+
231
+ export function BulkActionBar({ count, onClear, onDelete, ...props }: BulkActionBarProps) {
232
+ if (count === 0) return null
233
+ return (
234
+ <div aria-label="Selected record actions" className="ceramic-bulk-action-bar" role="toolbar" {...props}>
235
+ <strong>{count} selected</strong>
236
+ <Button size="sm" variant="ghost" onClick={onClear}>Clear</Button>
237
+ <Button size="sm" variant="destructive" onClick={onDelete}>Delete</Button>
238
+ </div>
239
+ )
240
+ }
@@ -0,0 +1,9 @@
1
+ # Database Workspace blocks
2
+
3
+ Copyable Ceramic blocks for a schema-driven Notion-style workspace:
4
+ `DatabaseToolbar`, `DatabaseTableBlock`, `SavedViewsBar`,
5
+ `RecordDetailDrawer`, and `BulkActionBar`.
6
+
7
+ Product code owns persistence, permissions, queries, and copy/paste data.
8
+ Ceramic owns the semantic grid, mobile record transformation, states, keyboard
9
+ model, tokens, and optimistic recovery contract.
@@ -0,0 +1,107 @@
1
+ .ceramic-database-toolbar {
2
+ display: grid;
3
+ grid-template-columns: minmax(12rem, 1fr) auto auto;
4
+ align-items: center;
5
+ gap: var(--space-3);
6
+ }
7
+
8
+ .ceramic-database-toolbar label,
9
+ .ceramic-database-toolbar input {
10
+ min-inline-size: 0;
11
+ inline-size: 100%;
12
+ }
13
+
14
+ .ceramic-database-toolbar input {
15
+ min-block-size: var(--button-height-sm);
16
+ border: var(--border-width-hairline) solid var(--input);
17
+ padding-inline: var(--space-3);
18
+ background: var(--background);
19
+ color: var(--foreground);
20
+ font: inherit;
21
+ }
22
+
23
+ .ceramic-database-toolbar > div,
24
+ .ceramic-saved-views {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ align-items: center;
28
+ gap: var(--space-1);
29
+ }
30
+
31
+ .ceramic-saved-views {
32
+ border-block-end: var(--border-width-hairline) solid var(--border);
33
+ }
34
+
35
+ .ceramic-record-drawer-backdrop {
36
+ position: fixed;
37
+ z-index: var(--z-overlay);
38
+ inset: 0;
39
+ background: var(--overlay);
40
+ }
41
+
42
+ .ceramic-record-drawer {
43
+ position: fixed;
44
+ inset-block: 0;
45
+ inset-inline-end: 0;
46
+ z-index: var(--z-modal);
47
+ inline-size: min(100%, 28rem);
48
+ overflow: auto;
49
+ padding: var(--space-5);
50
+ border-inline-start: var(--border-width-strong) solid var(--border);
51
+ background: var(--popover-surface);
52
+ color: var(--foreground);
53
+ }
54
+
55
+ .ceramic-record-drawer header {
56
+ display: flex;
57
+ align-items: start;
58
+ justify-content: space-between;
59
+ gap: var(--space-4);
60
+ }
61
+
62
+ .ceramic-record-drawer header div,
63
+ .ceramic-record-drawer dl {
64
+ display: grid;
65
+ gap: var(--space-2);
66
+ }
67
+
68
+ .ceramic-record-drawer :is(h2, dl, dd) {
69
+ margin: 0;
70
+ }
71
+
72
+ .ceramic-record-drawer header span,
73
+ .ceramic-record-drawer dt {
74
+ color: var(--muted-foreground);
75
+ font-size: var(--font-size-sm);
76
+ }
77
+
78
+ .ceramic-record-drawer dl > div {
79
+ display: grid;
80
+ gap: var(--space-1);
81
+ padding-block: var(--space-3);
82
+ border-block-end: var(--border-width-hairline) solid var(--border);
83
+ }
84
+
85
+ .ceramic-record-drawer .uds-database-property-cell {
86
+ padding-inline: 0;
87
+ }
88
+
89
+ .ceramic-bulk-action-bar {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: var(--space-2);
93
+ inline-size: fit-content;
94
+ padding: var(--space-2);
95
+ border: var(--border-width-strong) solid var(--border);
96
+ background: var(--surface-elevated);
97
+ }
98
+
99
+ @media (max-width: 44rem) {
100
+ .ceramic-database-toolbar {
101
+ grid-template-columns: 1fr;
102
+ }
103
+
104
+ .ceramic-database-toolbar > :last-child {
105
+ inline-size: 100%;
106
+ }
107
+ }