@nuasite/cms 0.40.0 → 0.42.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/editor.js +13711 -13225
- package/package.json +1 -1
- package/src/collection-scanner.ts +247 -59
- package/src/content-config-ast.ts +172 -27
- package/src/editor/components/collections-browser.tsx +20 -4
- package/src/editor/components/fields.tsx +313 -52
- package/src/editor/components/frontmatter-fields.tsx +83 -3
- package/src/editor/components/frontmatter-sidebar.tsx +1 -0
- package/src/editor/components/markdown-editor-overlay.tsx +1 -1
- package/src/editor/components/markdown-inline-editor.tsx +50 -0
- package/src/editor/components/toolbar.tsx +17 -2
- package/src/editor/milkdown-utils.ts +9 -2
- package/src/editor/styled-list-plugin.ts +233 -0
- package/src/editor/types.ts +3 -0
- package/src/field-types.ts +15 -0
- package/src/handlers/markdown-ops.ts +75 -1
- package/src/html-processor.ts +22 -7
- package/src/index.ts +9 -2
- package/src/rehype-cms-marker.ts +2 -2
- package/src/types.ts +9 -0
|
@@ -20,11 +20,17 @@ export interface ParsedField {
|
|
|
20
20
|
reference?: ParsedReference
|
|
21
21
|
/** True when the field is `image()` from an Astro callback schema, which routes through `astro:assets`. */
|
|
22
22
|
astroImage?: boolean
|
|
23
|
+
/** Element type for `array` fields */
|
|
24
|
+
itemType?: FieldType
|
|
25
|
+
/** Nested fields for `object` fields, or per-item fields for `array` of objects */
|
|
26
|
+
fields?: ParsedField[]
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export interface ParsedCollection {
|
|
26
30
|
name: string
|
|
27
31
|
fields: ParsedField[]
|
|
32
|
+
loaderPattern?: string
|
|
33
|
+
loaderBase?: string
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export type ParsedConfig = Map<string, ParsedCollection>
|
|
@@ -33,6 +39,7 @@ const FIELD_HELPER_TYPES = new Set([
|
|
|
33
39
|
'text',
|
|
34
40
|
'number',
|
|
35
41
|
'image',
|
|
42
|
+
'file',
|
|
36
43
|
'url',
|
|
37
44
|
'email',
|
|
38
45
|
'tel',
|
|
@@ -40,6 +47,8 @@ const FIELD_HELPER_TYPES = new Set([
|
|
|
40
47
|
'date',
|
|
41
48
|
'datetime',
|
|
42
49
|
'time',
|
|
50
|
+
'year',
|
|
51
|
+
'month',
|
|
43
52
|
'textarea',
|
|
44
53
|
])
|
|
45
54
|
|
|
@@ -56,6 +65,26 @@ const VALID_HINT_KEYS = new Set([
|
|
|
56
65
|
|
|
57
66
|
const WRAPPER_METHODS = new Set(['optional', 'nullable', 'nullish', 'default'])
|
|
58
67
|
|
|
68
|
+
/** Map of top-level `const <name> = <expr>` bindings within a single config file. */
|
|
69
|
+
type Bindings = Map<string, t.Node>
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Follow `Identifier` references through same-file `const` bindings until reaching
|
|
73
|
+
* a non-Identifier node. Cycle-safe via the visited set. Returns the original node
|
|
74
|
+
* unchanged when the identifier is unbound or already visited.
|
|
75
|
+
*/
|
|
76
|
+
function resolveExpression(node: t.Node, bindings: Bindings, visited: Set<string> = new Set()): t.Node {
|
|
77
|
+
let current: t.Node = node
|
|
78
|
+
while (current.type === 'Identifier') {
|
|
79
|
+
if (visited.has(current.name)) return current
|
|
80
|
+
visited.add(current.name)
|
|
81
|
+
const next = bindings.get(current.name)
|
|
82
|
+
if (!next) return current
|
|
83
|
+
current = next
|
|
84
|
+
}
|
|
85
|
+
return current
|
|
86
|
+
}
|
|
87
|
+
|
|
59
88
|
/** Cached parse result keyed by absolute path; invalidated by mtime. */
|
|
60
89
|
const parseCache = new Map<string, { mtimeMs: number; parsed: ParsedConfig }>()
|
|
61
90
|
|
|
@@ -95,10 +124,14 @@ export function parseConfigSource(source: string, sourcePath?: string): ParsedCo
|
|
|
95
124
|
const ast = parseFrontmatter(source, sourcePath) as unknown as t.File | null
|
|
96
125
|
if (!ast) return result
|
|
97
126
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
127
|
+
// Single pass: collect every top-level `const X = <expr>` binding (so we can
|
|
128
|
+
// later resolve Identifier references like `cs: TestimonialTranslation`),
|
|
129
|
+
// while also picking out `defineCollection({...})` calls and the
|
|
130
|
+
// `export const collections = { name: X, ... }` mapping.
|
|
131
|
+
const bindings: Bindings = new Map()
|
|
100
132
|
const collectionDecls = new Map<string, t.ObjectExpression>()
|
|
101
133
|
const exportMap = new Map<string, string>() // varName → collectionName
|
|
134
|
+
const inlineCollections = new Map<string, t.ObjectExpression>() // collectionName → defineCollection arg (inline form)
|
|
102
135
|
|
|
103
136
|
for (const stmt of ast.program.body) {
|
|
104
137
|
const varDecl = stmt.type === 'ExportNamedDeclaration' && stmt.declaration?.type === 'VariableDeclaration'
|
|
@@ -112,6 +145,8 @@ export function parseConfigSource(source: string, sourcePath?: string): ParsedCo
|
|
|
112
145
|
if (decl.id.type !== 'Identifier') continue
|
|
113
146
|
if (!decl.init) continue
|
|
114
147
|
|
|
148
|
+
bindings.set(decl.id.name, decl.init)
|
|
149
|
+
|
|
115
150
|
if (decl.id.name === 'collections' && decl.init.type === 'ObjectExpression') {
|
|
116
151
|
for (const prop of decl.init.properties) {
|
|
117
152
|
if (prop.type !== 'ObjectProperty') continue
|
|
@@ -119,6 +154,12 @@ export function parseConfigSource(source: string, sourcePath?: string): ParsedCo
|
|
|
119
154
|
if (!key) continue
|
|
120
155
|
if (prop.value.type === 'Identifier') {
|
|
121
156
|
exportMap.set(prop.value.name, key)
|
|
157
|
+
} else if (prop.value.type === 'CallExpression' && isDefineCollectionCallee(prop.value.callee)) {
|
|
158
|
+
// Inline form: `collections = { name: defineCollection({...}) }`
|
|
159
|
+
const inlineArg = prop.value.arguments[0]
|
|
160
|
+
if (inlineArg?.type === 'ObjectExpression') {
|
|
161
|
+
inlineCollections.set(key, inlineArg)
|
|
162
|
+
}
|
|
122
163
|
}
|
|
123
164
|
}
|
|
124
165
|
continue
|
|
@@ -133,23 +174,57 @@ export function parseConfigSource(source: string, sourcePath?: string): ParsedCo
|
|
|
133
174
|
}
|
|
134
175
|
}
|
|
135
176
|
|
|
177
|
+
// Unify both styles: inline `name: defineCollection({...})` and the
|
|
178
|
+
// `const x = defineCollection({...}); collections = { name: x }` reference form.
|
|
179
|
+
const collectionObjects = new Map<string, t.ObjectExpression>(inlineCollections)
|
|
136
180
|
for (const [varName, collectionName] of exportMap) {
|
|
137
181
|
const decl = collectionDecls.get(varName)
|
|
138
|
-
if (
|
|
182
|
+
if (decl) collectionObjects.set(collectionName, decl)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const [collectionName, decl] of collectionObjects) {
|
|
186
|
+
const loaderProperty = decl.properties.find(
|
|
187
|
+
p =>
|
|
188
|
+
p.type === 'ObjectProperty'
|
|
189
|
+
&& propertyKeyName(p.key) === 'loader',
|
|
190
|
+
) as t.ObjectProperty | undefined
|
|
191
|
+
const loaderOptions = loaderProperty ? extractGlobLoaderOptions(loaderProperty.value, bindings) : {}
|
|
192
|
+
const loaderPattern = loaderOptions.pattern
|
|
193
|
+
const loaderBase = loaderOptions.base
|
|
139
194
|
|
|
140
195
|
const schemaProperty = decl.properties.find(
|
|
141
196
|
p =>
|
|
142
197
|
p.type === 'ObjectProperty'
|
|
143
198
|
&& propertyKeyName(p.key) === 'schema',
|
|
144
199
|
) as t.ObjectProperty | undefined
|
|
145
|
-
if (!schemaProperty)
|
|
200
|
+
if (!schemaProperty) {
|
|
201
|
+
if (!loaderPattern) continue
|
|
202
|
+
result.set(collectionName, {
|
|
203
|
+
name: collectionName,
|
|
204
|
+
fields: [],
|
|
205
|
+
loaderPattern,
|
|
206
|
+
loaderBase,
|
|
207
|
+
})
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
146
210
|
|
|
147
|
-
const schemaObject = unwrapSchemaToObject(schemaProperty.value)
|
|
148
|
-
if (!schemaObject)
|
|
211
|
+
const schemaObject = unwrapSchemaToObject(schemaProperty.value, bindings)
|
|
212
|
+
if (!schemaObject) {
|
|
213
|
+
if (!loaderPattern) continue
|
|
214
|
+
result.set(collectionName, {
|
|
215
|
+
name: collectionName,
|
|
216
|
+
fields: [],
|
|
217
|
+
loaderPattern,
|
|
218
|
+
loaderBase,
|
|
219
|
+
})
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
149
222
|
|
|
150
223
|
result.set(collectionName, {
|
|
151
224
|
name: collectionName,
|
|
152
|
-
fields: parseSchemaFields(schemaObject),
|
|
225
|
+
fields: parseSchemaFields(schemaObject, bindings),
|
|
226
|
+
loaderPattern,
|
|
227
|
+
loaderBase,
|
|
153
228
|
})
|
|
154
229
|
}
|
|
155
230
|
|
|
@@ -166,26 +241,64 @@ function propertyKeyName(key: t.Node): string | null {
|
|
|
166
241
|
return null
|
|
167
242
|
}
|
|
168
243
|
|
|
244
|
+
function extractGlobLoaderOptions(node: t.Node, bindings: Bindings): { pattern?: string; base?: string } {
|
|
245
|
+
const resolved = resolveExpression(node, bindings)
|
|
246
|
+
if (resolved.type !== 'CallExpression') return {}
|
|
247
|
+
if (!isGlobCallee(resolved.callee)) return {}
|
|
248
|
+
|
|
249
|
+
const arg = resolved.arguments[0]
|
|
250
|
+
if (!arg) return {}
|
|
251
|
+
const options = resolveExpression(arg, bindings)
|
|
252
|
+
if (options.type !== 'ObjectExpression') return {}
|
|
253
|
+
|
|
254
|
+
const result: { pattern?: string; base?: string } = {}
|
|
255
|
+
for (const prop of options.properties) {
|
|
256
|
+
if (prop.type !== 'ObjectProperty') continue
|
|
257
|
+
const key = propertyKeyName(prop.key)
|
|
258
|
+
if (key !== 'pattern' && key !== 'base') continue
|
|
259
|
+
const value = extractStaticString(prop.value, bindings)
|
|
260
|
+
if (value !== undefined) result[key] = value
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return result
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function extractStaticString(node: t.Node, bindings: Bindings): string | undefined {
|
|
267
|
+
const resolved = resolveExpression(node, bindings)
|
|
268
|
+
if (resolved.type === 'StringLiteral') return resolved.value
|
|
269
|
+
if (resolved.type === 'TemplateLiteral' && resolved.expressions.length === 0) {
|
|
270
|
+
return resolved.quasis[0]?.value.cooked ?? resolved.quasis[0]?.value.raw
|
|
271
|
+
}
|
|
272
|
+
return undefined
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function isGlobCallee(callee: t.Node): boolean {
|
|
276
|
+
return callee.type === 'Identifier' && callee.name === 'glob'
|
|
277
|
+
}
|
|
278
|
+
|
|
169
279
|
/**
|
|
170
280
|
* Unwrap a `schema:` value down to the top-level (z|n).object({ ... }) ObjectExpression.
|
|
171
|
-
* Handles direct calls
|
|
281
|
+
* Handles direct calls, the Astro callback form `({ image }) => z.object({...})`,
|
|
282
|
+
* and same-file variable references like `schema: BlogSchema`.
|
|
172
283
|
*/
|
|
173
|
-
function unwrapSchemaToObject(node: t.Node): t.ObjectExpression | null {
|
|
174
|
-
|
|
175
|
-
|
|
284
|
+
function unwrapSchemaToObject(node: t.Node, bindings: Bindings): t.ObjectExpression | null {
|
|
285
|
+
const resolved = resolveExpression(node, bindings)
|
|
286
|
+
|
|
287
|
+
if (resolved.type === 'ArrowFunctionExpression' || resolved.type === 'FunctionExpression') {
|
|
288
|
+
const body = resolved.body
|
|
176
289
|
if (body.type === 'BlockStatement') {
|
|
177
290
|
for (const stmt of body.body) {
|
|
178
291
|
if (stmt.type === 'ReturnStatement' && stmt.argument) {
|
|
179
|
-
return unwrapSchemaToObject(stmt.argument)
|
|
292
|
+
return unwrapSchemaToObject(stmt.argument, bindings)
|
|
180
293
|
}
|
|
181
294
|
}
|
|
182
295
|
return null
|
|
183
296
|
}
|
|
184
|
-
return unwrapSchemaToObject(body)
|
|
297
|
+
return unwrapSchemaToObject(body, bindings)
|
|
185
298
|
}
|
|
186
299
|
|
|
187
|
-
if (
|
|
188
|
-
const callee =
|
|
300
|
+
if (resolved.type === 'CallExpression') {
|
|
301
|
+
const callee = resolved.callee
|
|
189
302
|
if (
|
|
190
303
|
callee.type === 'MemberExpression'
|
|
191
304
|
&& callee.object.type === 'Identifier'
|
|
@@ -193,15 +306,17 @@ function unwrapSchemaToObject(node: t.Node): t.ObjectExpression | null {
|
|
|
193
306
|
&& callee.property.type === 'Identifier'
|
|
194
307
|
&& callee.property.name === 'object'
|
|
195
308
|
) {
|
|
196
|
-
const arg =
|
|
197
|
-
if (arg
|
|
309
|
+
const arg = resolved.arguments[0]
|
|
310
|
+
if (!arg) return null
|
|
311
|
+
const resolvedArg = resolveExpression(arg, bindings)
|
|
312
|
+
if (resolvedArg.type === 'ObjectExpression') return resolvedArg
|
|
198
313
|
}
|
|
199
314
|
}
|
|
200
315
|
|
|
201
316
|
return null
|
|
202
317
|
}
|
|
203
318
|
|
|
204
|
-
function parseSchemaFields(schemaObject: t.ObjectExpression): ParsedField[] {
|
|
319
|
+
function parseSchemaFields(schemaObject: t.ObjectExpression, bindings: Bindings): ParsedField[] {
|
|
205
320
|
const fields: ParsedField[] = []
|
|
206
321
|
for (const prop of schemaObject.properties) {
|
|
207
322
|
if (prop.type !== 'ObjectProperty') continue
|
|
@@ -209,7 +324,7 @@ function parseSchemaFields(schemaObject: t.ObjectExpression): ParsedField[] {
|
|
|
209
324
|
if (!name) continue
|
|
210
325
|
|
|
211
326
|
const field: ParsedField = { name, required: true }
|
|
212
|
-
analyzeFieldExpression(prop.value, field)
|
|
327
|
+
analyzeFieldExpression(prop.value, field, bindings)
|
|
213
328
|
fields.push(field)
|
|
214
329
|
}
|
|
215
330
|
return fields
|
|
@@ -219,14 +334,18 @@ function parseSchemaFields(schemaObject: t.ObjectExpression): ParsedField[] {
|
|
|
219
334
|
* Walk a field's value expression. Each layer is either a wrapper method call
|
|
220
335
|
* (`.optional()`, `.default()`, `.nullable()`, `.nullish()`, `.orderBy(...)`)
|
|
221
336
|
* or the base call (`n.image()`, `image()`, `z.enum([...])`, `n.array(reference(...))`).
|
|
337
|
+
*
|
|
338
|
+
* Resolves same-file `Identifier` references against `bindings` at each layer so
|
|
339
|
+
* patterns like `cs: TestimonialTranslation` and `en: TestimonialTranslation.optional()`
|
|
340
|
+
* are followed back to their defining call.
|
|
222
341
|
*/
|
|
223
|
-
function analyzeFieldExpression(node: t.Node, field: ParsedField): void {
|
|
224
|
-
let current: t.Node | null = node
|
|
342
|
+
function analyzeFieldExpression(node: t.Node, field: ParsedField, bindings: Bindings): void {
|
|
343
|
+
let current: t.Node | null = resolveExpression(node, bindings)
|
|
225
344
|
while (current) {
|
|
226
345
|
if (current.type !== 'CallExpression') return
|
|
227
346
|
|
|
228
347
|
if (isBaseCall(current)) {
|
|
229
|
-
analyzeBaseCall(current, field)
|
|
348
|
+
analyzeBaseCall(current, field, bindings)
|
|
230
349
|
return
|
|
231
350
|
}
|
|
232
351
|
|
|
@@ -242,7 +361,7 @@ function analyzeFieldExpression(node: t.Node, field: ParsedField): void {
|
|
|
242
361
|
field.orderBy = { direction }
|
|
243
362
|
}
|
|
244
363
|
|
|
245
|
-
current = current.callee.object
|
|
364
|
+
current = resolveExpression(current.callee.object, bindings)
|
|
246
365
|
}
|
|
247
366
|
}
|
|
248
367
|
|
|
@@ -262,7 +381,7 @@ function isBaseCall(node: t.CallExpression): boolean {
|
|
|
262
381
|
return false
|
|
263
382
|
}
|
|
264
383
|
|
|
265
|
-
function analyzeBaseCall(node: t.CallExpression, field: ParsedField): void {
|
|
384
|
+
function analyzeBaseCall(node: t.CallExpression, field: ParsedField, bindings: Bindings): void {
|
|
266
385
|
const callee = node.callee
|
|
267
386
|
|
|
268
387
|
// Bare image() / reference() from the schema callback form
|
|
@@ -314,11 +433,26 @@ function analyzeBaseCall(node: t.CallExpression, field: ParsedField): void {
|
|
|
314
433
|
return
|
|
315
434
|
}
|
|
316
435
|
|
|
317
|
-
// (z|n).
|
|
436
|
+
// (z|n).object({...}) → nested object field
|
|
437
|
+
if ((ns === 'z' || ns === 'n') && fn === 'object') {
|
|
438
|
+
const arg = node.arguments[0]
|
|
439
|
+
if (!arg) return
|
|
440
|
+
const resolved = resolveExpression(arg, bindings)
|
|
441
|
+
if (resolved.type === 'ObjectExpression') {
|
|
442
|
+
field.type = 'object'
|
|
443
|
+
field.fields = parseSchemaFields(resolved, bindings)
|
|
444
|
+
}
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// (z|n).array(<inner>) → array; inspect the element type
|
|
318
449
|
if ((ns === 'z' || ns === 'n') && fn === 'array') {
|
|
319
|
-
const
|
|
450
|
+
const innerRaw = node.arguments[0]
|
|
451
|
+
if (!innerRaw) return
|
|
452
|
+
const inner = resolveExpression(innerRaw, bindings)
|
|
453
|
+
// Array of references: keep the existing flat shape so detectReferenceFields can wire it up.
|
|
320
454
|
if (
|
|
321
|
-
inner
|
|
455
|
+
inner.type === 'CallExpression'
|
|
322
456
|
&& inner.callee.type === 'Identifier'
|
|
323
457
|
&& inner.callee.name === 'reference'
|
|
324
458
|
) {
|
|
@@ -326,7 +460,18 @@ function analyzeBaseCall(node: t.CallExpression, field: ParsedField): void {
|
|
|
326
460
|
if (target?.type === 'StringLiteral') {
|
|
327
461
|
field.reference = { target: target.value, isArray: true }
|
|
328
462
|
}
|
|
463
|
+
return
|
|
329
464
|
}
|
|
465
|
+
// Array of anything else: analyze the inner expression and lift its type/fields.
|
|
466
|
+
// Note: nested arrays (`n.array(n.array(...))`) collapse here — `itemType` records
|
|
467
|
+
// only the outer element type, the inner element shape is lost. No editor flow
|
|
468
|
+
// currently renders nested arrays, so we don't carry a recursive `itemDefinition`
|
|
469
|
+
// yet; add one when editor support arrives.
|
|
470
|
+
const innerField: ParsedField = { name: '__item__', required: true }
|
|
471
|
+
analyzeFieldExpression(inner, innerField, bindings)
|
|
472
|
+
field.type = 'array'
|
|
473
|
+
if (innerField.type) field.itemType = innerField.type
|
|
474
|
+
if (innerField.fields) field.fields = innerField.fields
|
|
330
475
|
return
|
|
331
476
|
}
|
|
332
477
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { signal } from '@preact/signals'
|
|
2
2
|
import { useMemo, useState } from 'preact/hooks'
|
|
3
3
|
import { useSearchFilter } from '../hooks/useSearchFilter'
|
|
4
|
+
import { cn } from '../lib/cn'
|
|
4
5
|
import { deleteMarkdownPage } from '../markdown-api'
|
|
5
6
|
import {
|
|
6
7
|
closeCollectionsBrowser,
|
|
@@ -241,20 +242,35 @@ export function CollectionsBrowser() {
|
|
|
241
242
|
)
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
// Collection list
|
|
245
|
+
// Collection list — group nested (child) collections under their parent
|
|
246
|
+
const ordered: Array<{ col: typeof collections[number]; nested: boolean }> = []
|
|
247
|
+
for (const col of collections.filter(c => !c.parentCollection)) {
|
|
248
|
+
ordered.push({ col, nested: false })
|
|
249
|
+
for (const child of collections.filter(c => c.parentCollection === col.name)) {
|
|
250
|
+
ordered.push({ col: child, nested: true })
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Append any orphaned children whose parent isn't present, so nothing is hidden.
|
|
254
|
+
for (const col of collections.filter(c => c.parentCollection && !collections.some(p => p.name === c.parentCollection))) {
|
|
255
|
+
ordered.push({ col, nested: false })
|
|
256
|
+
}
|
|
257
|
+
|
|
245
258
|
return (
|
|
246
259
|
<ModalBackdrop onClose={handleClose} extraClass="flex flex-col max-h-[80vh]">
|
|
247
260
|
<ModalHeader title="Collections" onClose={handleClose} />
|
|
248
261
|
<div class="p-5 space-y-2 overflow-y-auto flex-1 min-h-0">
|
|
249
|
-
{
|
|
262
|
+
{ordered.map(({ col, nested }) => (
|
|
250
263
|
<button
|
|
251
264
|
key={col.name}
|
|
252
265
|
type="button"
|
|
253
266
|
onClick={() => selectBrowserCollection(col.name)}
|
|
254
|
-
class=
|
|
267
|
+
class={cn(
|
|
268
|
+
'group w-full flex items-center gap-4 p-4 bg-white/5 hover:bg-white/10 rounded-cms-lg border border-white/10 hover:border-white/20 transition-colors text-left cursor-pointer',
|
|
269
|
+
nested && 'ml-8 w-[calc(100%-2rem)] border-l-2 border-l-cms-primary/40',
|
|
270
|
+
)}
|
|
255
271
|
data-cms-ui
|
|
256
272
|
>
|
|
257
|
-
<div class=
|
|
273
|
+
<div class={cn('shrink-0 bg-cms-primary/20 rounded-cms-sm flex items-center justify-center', nested ? 'w-8 h-8' : 'w-10 h-10')}>
|
|
258
274
|
<CollectionIcon />
|
|
259
275
|
</div>
|
|
260
276
|
<div class="flex-1 min-w-0">
|