@kidecms/core 0.1.7 → 0.1.9

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.
@@ -78,7 +78,13 @@ export default function DocumentActions({
78
78
 
79
79
  const canDuplicate = !!(showDuplicate && collectionSlug && documentId);
80
80
  const hasActions =
81
- canDuplicate || showUnpublish || showDiscardDraft || showDelete || showSchedule || showCancelSchedule || versions.length > 0;
81
+ canDuplicate ||
82
+ showUnpublish ||
83
+ showDiscardDraft ||
84
+ showDelete ||
85
+ showSchedule ||
86
+ showCancelSchedule ||
87
+ versions.length > 0;
82
88
  if (!hasActions) return null;
83
89
 
84
90
  const duplicate = async () => {
@@ -68,7 +68,7 @@ const isTreeEditor =
68
68
  const builtInComponents = new Set(["radio", "taxonomy-select", "menu-items", "taxonomy-terms"]);
69
69
  const CustomComponent =
70
70
  field.admin?.component && !builtInComponents.has(field.admin.component)
71
- ? customFields[field.admin.component] ?? null
71
+ ? (customFields[field.admin.component] ?? null)
72
72
  : null;
73
73
 
74
74
  // Build serializable block types metadata for BlockEditor
@@ -122,276 +122,239 @@ const blockTypesMeta =
122
122
 
123
123
  {
124
124
  CustomComponent && (
125
- <CustomComponent
126
- client:load
127
- name={name}
128
- field={field}
129
- value={serializedValue}
130
- readOnly={readOnly}
131
- />
125
+ <CustomComponent client:load name={name} field={field} value={serializedValue} readOnly={readOnly} />
132
126
  )
133
127
  }
134
128
 
135
- {!CustomComponent && (
136
- <Fragment>
137
-
138
129
  {
139
- field.type === "image" && !readOnly && (
140
- <ImagePicker client:load name={name} value={serializedValue} placeholder={placeholder} />
141
- )
142
- }
130
+ !CustomComponent && (
131
+ <Fragment>
132
+ {field.type === "image" && !readOnly && (
133
+ <ImagePicker client:load name={name} value={serializedValue} placeholder={placeholder} />
134
+ )}
143
135
 
144
- {
145
- field.type === "image" && readOnly && (
146
- <Input
147
- className={controlClass}
148
- type="text"
149
- id={name}
150
- name={name}
151
- defaultValue={serializedValue}
152
- placeholder={placeholder}
153
- readOnly={readOnly}
154
- />
155
- )
156
- }
136
+ {field.type === "image" && readOnly && (
137
+ <Input
138
+ className={controlClass}
139
+ type="text"
140
+ id={name}
141
+ name={name}
142
+ defaultValue={serializedValue}
143
+ placeholder={placeholder}
144
+ readOnly={readOnly}
145
+ />
146
+ )}
157
147
 
158
- {
159
- field.type === "text" && field.admin?.component === "taxonomy-select" && !readOnly && (
160
- <TaxonomySelect client:load name={name} value={serializedValue} taxonomySlug={field.admin?.placeholder ?? ""} />
161
- )
162
- }
148
+ {field.type === "text" && field.admin?.component === "taxonomy-select" && !readOnly && (
149
+ <TaxonomySelect
150
+ client:load
151
+ name={name}
152
+ value={serializedValue}
153
+ taxonomySlug={field.admin?.placeholder ?? ""}
154
+ />
155
+ )}
163
156
 
164
- {
165
- (field.type === "text" || field.type === "email" || field.type === "date") &&
166
- field.admin?.component !== "taxonomy-select" &&
167
- (field.type === "text" && field.admin?.rows ? (
168
- <Textarea
169
- className={controlClass}
170
- id={name}
171
- name={name}
172
- rows={field.admin.rows}
173
- defaultValue={serializedValue}
174
- placeholder={placeholder}
175
- readOnly={readOnly}
176
- required={field.required}
177
- />
178
- ) : (
179
- <Input
180
- className={controlClass}
181
- type={field.type === "email" ? "email" : field.type === "date" ? "date" : "text"}
182
- id={name}
183
- name={name}
184
- defaultValue={serializedValue}
185
- placeholder={placeholder}
186
- readOnly={readOnly}
187
- required={field.required}
188
- />
189
- ))
190
- }
157
+ {(field.type === "text" || field.type === "email" || field.type === "date") &&
158
+ field.admin?.component !== "taxonomy-select" &&
159
+ (field.type === "text" && field.admin?.rows ? (
160
+ <Textarea
161
+ className={controlClass}
162
+ id={name}
163
+ name={name}
164
+ rows={field.admin.rows}
165
+ defaultValue={serializedValue}
166
+ placeholder={placeholder}
167
+ readOnly={readOnly}
168
+ required={field.required}
169
+ />
170
+ ) : (
171
+ <Input
172
+ className={controlClass}
173
+ type={field.type === "email" ? "email" : field.type === "date" ? "date" : "text"}
174
+ id={name}
175
+ name={name}
176
+ defaultValue={serializedValue}
177
+ placeholder={placeholder}
178
+ readOnly={readOnly}
179
+ required={field.required}
180
+ />
181
+ ))}
191
182
 
192
- {
193
- field.type === "slug" && (
194
- <SlugField
195
- client:load
196
- name={name}
197
- value={serializedValue}
198
- from={field.from}
199
- readOnly={readOnly}
200
- required={field.required}
201
- placeholder={placeholder || "auto-generated-slug"}
202
- className={controlClass}
203
- />
204
- )
205
- }
183
+ {field.type === "slug" && (
184
+ <SlugField
185
+ client:load
186
+ name={name}
187
+ value={serializedValue}
188
+ from={field.from}
189
+ readOnly={readOnly}
190
+ required={field.required}
191
+ placeholder={placeholder || "auto-generated-slug"}
192
+ className={controlClass}
193
+ />
194
+ )}
206
195
 
207
- {
208
- field.type === "number" && (
209
- <Input
210
- className={controlClass}
211
- type="number"
212
- id={name}
213
- name={name}
214
- defaultValue={serializedValue}
215
- inputMode={inputMode}
216
- readOnly={readOnly}
217
- required={field.required}
218
- />
219
- )
220
- }
196
+ {field.type === "number" && (
197
+ <Input
198
+ className={controlClass}
199
+ type="number"
200
+ id={name}
201
+ name={name}
202
+ defaultValue={serializedValue}
203
+ inputMode={inputMode}
204
+ readOnly={readOnly}
205
+ required={field.required}
206
+ />
207
+ )}
221
208
 
222
- {field.type === "boolean" && <CheckboxField client:load name={name} checked={Boolean(value)} disabled={readOnly} />}
209
+ {field.type === "boolean" && (
210
+ <CheckboxField client:load name={name} checked={Boolean(value)} disabled={readOnly} />
211
+ )}
223
212
 
224
- {
225
- field.type === "select" && field.admin?.component !== "radio" && (
226
- <SelectField
227
- client:load
228
- name={name}
229
- value={String(value ?? "")}
230
- placeholder="Select an option"
231
- disabled={readOnly}
232
- items={field.options.map((option) => ({ value: option, label: option }))}
233
- />
234
- )
235
- }
213
+ {field.type === "select" && field.admin?.component !== "radio" && (
214
+ <SelectField
215
+ client:load
216
+ name={name}
217
+ value={String(value ?? "")}
218
+ placeholder="Select an option"
219
+ disabled={readOnly}
220
+ items={field.options.map((option) => ({ value: option, label: option }))}
221
+ />
222
+ )}
236
223
 
237
- {
238
- field.type === "select" && field.admin?.component === "radio" && (
239
- <div class="flex flex-wrap gap-x-5 gap-y-2 has-disabled:opacity-50">
240
- {field.options.map((option) => (
241
- <label class="hover:text-foreground flex items-center gap-2 text-sm transition-colors has-disabled:cursor-not-allowed has-disabled:hover:text-current">
242
- <input
243
- class="border-input hover:border-primary/60 checked:border-primary checked:hover:border-primary focus-visible:ring-ring/50 disabled:hover:border-input size-4 shrink-0 appearance-none rounded-full border transition-all checked:border-[5px] focus-visible:ring-3 focus-visible:outline-none disabled:cursor-not-allowed"
244
- type="radio"
245
- name={name}
246
- value={option}
247
- checked={String(value ?? "") === option}
248
- disabled={readOnly}
249
- />
250
- <span class="select-none">{option}</span>
251
- </label>
252
- ))}
253
- </div>
254
- )
255
- }
224
+ {field.type === "select" && field.admin?.component === "radio" && (
225
+ <div class="flex flex-wrap gap-x-5 gap-y-2 has-disabled:opacity-50">
226
+ {field.options.map((option) => (
227
+ <label class="hover:text-foreground flex items-center gap-2 text-sm transition-colors has-disabled:cursor-not-allowed has-disabled:hover:text-current">
228
+ <input
229
+ class="border-input hover:border-primary/60 checked:border-primary checked:hover:border-primary focus-visible:ring-ring/50 disabled:hover:border-input size-4 shrink-0 appearance-none rounded-full border transition-all checked:border-[5px] focus-visible:ring-3 focus-visible:outline-none disabled:cursor-not-allowed"
230
+ type="radio"
231
+ name={name}
232
+ value={option}
233
+ checked={String(value ?? "") === option}
234
+ disabled={readOnly}
235
+ />
236
+ <span class="select-none">{option}</span>
237
+ </label>
238
+ ))}
239
+ </div>
240
+ )}
256
241
 
257
- {
258
- field.type === "relation" && !readOnly && relationMeta && (
259
- <RelationField
260
- client:load
261
- name={name}
262
- value={Array.isArray(value) ? JSON.stringify(value) : String(value ?? "")}
263
- hasMany={relationMeta.hasMany}
264
- options={relationOptions}
265
- collectionSlug={relationMeta.collectionSlug}
266
- collectionLabel={relationMeta.collectionLabel}
267
- labelField={relationMeta.labelField}
268
- />
269
- )
270
- }
242
+ {field.type === "relation" && !readOnly && relationMeta && (
243
+ <RelationField
244
+ client:load
245
+ name={name}
246
+ value={Array.isArray(value) ? JSON.stringify(value) : String(value ?? "")}
247
+ hasMany={relationMeta.hasMany}
248
+ options={relationOptions}
249
+ collectionSlug={relationMeta.collectionSlug}
250
+ collectionLabel={relationMeta.collectionLabel}
251
+ labelField={relationMeta.labelField}
252
+ />
253
+ )}
271
254
 
272
- {
273
- field.type === "relation" && (readOnly || !relationMeta) && (
274
- <SelectField
275
- client:load
276
- name={name}
277
- value={String(value ?? "")}
278
- placeholder="Select a document"
279
- disabled={readOnly}
280
- items={relationOptions}
281
- />
282
- )
283
- }
255
+ {field.type === "relation" && (readOnly || !relationMeta) && (
256
+ <SelectField
257
+ client:load
258
+ name={name}
259
+ value={String(value ?? "")}
260
+ placeholder="Select a document"
261
+ disabled={readOnly}
262
+ items={relationOptions}
263
+ />
264
+ )}
284
265
 
285
- {
286
- field.type === "richText" && !readOnly && (
287
- <RichTextEditor client:load name={name} initialValue={value ? JSON.stringify(value) : ""} rows={rows} />
288
- )
289
- }
266
+ {field.type === "richText" && !readOnly && (
267
+ <RichTextEditor client:load name={name} initialValue={value ? JSON.stringify(value) : ""} rows={rows} />
268
+ )}
290
269
 
291
- {
292
- field.type === "richText" && readOnly && (
293
- <Textarea
294
- className={controlClass}
295
- id={name}
296
- name={name}
297
- rows={rows}
298
- readOnly={readOnly}
299
- defaultValue={serializedValue}
300
- />
301
- )
302
- }
270
+ {field.type === "richText" && readOnly && (
271
+ <Textarea
272
+ className={controlClass}
273
+ id={name}
274
+ name={name}
275
+ rows={rows}
276
+ readOnly={readOnly}
277
+ defaultValue={serializedValue}
278
+ />
279
+ )}
303
280
 
304
- {
305
- field.type === "json" && field.admin?.component === "menu-items" && !readOnly && (
306
- <TreeItemsEditor
307
- client:load
308
- name={name}
309
- value={serializedValue}
310
- variant="menu"
311
- label={label}
312
- linkOptions={menuLinkOptions}
313
- />
314
- )
315
- }
281
+ {field.type === "json" && field.admin?.component === "menu-items" && !readOnly && (
282
+ <TreeItemsEditor
283
+ client:load
284
+ name={name}
285
+ value={serializedValue}
286
+ variant="menu"
287
+ label={label}
288
+ linkOptions={menuLinkOptions}
289
+ />
290
+ )}
316
291
 
317
- {
318
- field.type === "json" && field.admin?.component === "taxonomy-terms" && !readOnly && (
319
- <TreeItemsEditor client:load name={name} value={serializedValue} variant="taxonomy" label={label} />
320
- )
321
- }
292
+ {field.type === "json" && field.admin?.component === "taxonomy-terms" && !readOnly && (
293
+ <TreeItemsEditor client:load name={name} value={serializedValue} variant="taxonomy" label={label} />
294
+ )}
322
295
 
323
- {
324
- field.type === "json" &&
325
- (field.admin?.component === "menu-items" || field.admin?.component === "taxonomy-terms") &&
326
- readOnly && (
327
- <Textarea
328
- className={controlClass}
329
- id={name}
330
- name={name}
331
- rows={rows}
332
- readOnly={readOnly}
333
- defaultValue={serializedValue}
334
- />
335
- )
336
- }
296
+ {field.type === "json" &&
297
+ (field.admin?.component === "menu-items" || field.admin?.component === "taxonomy-terms") &&
298
+ readOnly && (
299
+ <Textarea
300
+ className={controlClass}
301
+ id={name}
302
+ name={name}
303
+ rows={rows}
304
+ readOnly={readOnly}
305
+ defaultValue={serializedValue}
306
+ />
307
+ )}
337
308
 
338
- {
339
- field.type === "blocks" && !readOnly && (
340
- <BlockEditor
341
- client:load
342
- name={name}
343
- value={serializedValue}
344
- types={blockTypesMeta}
345
- blockRelationOptions={blockRelationOptions}
346
- />
347
- )
348
- }
309
+ {field.type === "blocks" && !readOnly && (
310
+ <BlockEditor
311
+ client:load
312
+ name={name}
313
+ value={serializedValue}
314
+ types={blockTypesMeta}
315
+ blockRelationOptions={blockRelationOptions}
316
+ />
317
+ )}
349
318
 
350
- {
351
- field.type === "blocks" && readOnly && (
352
- <Textarea
353
- className={controlClass}
354
- id={name}
355
- name={name}
356
- rows={rows}
357
- readOnly={readOnly}
358
- defaultValue={serializedValue}
359
- />
360
- )
361
- }
319
+ {field.type === "blocks" && readOnly && (
320
+ <Textarea
321
+ className={controlClass}
322
+ id={name}
323
+ name={name}
324
+ rows={rows}
325
+ readOnly={readOnly}
326
+ defaultValue={serializedValue}
327
+ />
328
+ )}
362
329
 
363
- {
364
- field.type === "array" && "of" in field && field.of?.type === "text" && (
365
- <Input
366
- className={controlClass}
367
- id={name}
368
- name={name}
369
- placeholder={placeholder || "item1, item2, item3"}
370
- readOnly={readOnly}
371
- required={field.required}
372
- defaultValue={serializedValue}
373
- />
374
- )
375
- }
330
+ {field.type === "array" && "of" in field && field.of?.type === "text" && (
331
+ <Input
332
+ className={controlClass}
333
+ id={name}
334
+ name={name}
335
+ placeholder={placeholder || "item1, item2, item3"}
336
+ readOnly={readOnly}
337
+ required={field.required}
338
+ defaultValue={serializedValue}
339
+ />
340
+ )}
376
341
 
377
- {
378
- ((field.type === "array" && !("of" in field && field.of?.type === "text")) ||
379
- (field.type === "json" &&
380
- field.admin?.component !== "menu-items" &&
381
- field.admin?.component !== "taxonomy-terms")) && (
382
- <Textarea
383
- className={controlClass}
384
- id={name}
385
- name={name}
386
- rows={rows}
387
- placeholder={placeholder}
388
- readOnly={readOnly}
389
- required={field.required}
390
- defaultValue={serializedValue}
391
- />
342
+ {((field.type === "array" && !("of" in field && field.of?.type === "text")) ||
343
+ (field.type === "json" &&
344
+ field.admin?.component !== "menu-items" &&
345
+ field.admin?.component !== "taxonomy-terms")) && (
346
+ <Textarea
347
+ className={controlClass}
348
+ id={name}
349
+ name={name}
350
+ rows={rows}
351
+ placeholder={placeholder}
352
+ readOnly={readOnly}
353
+ required={field.required}
354
+ defaultValue={serializedValue}
355
+ />
356
+ )}
357
+ </Fragment>
392
358
  )
393
359
  }
394
-
395
- </Fragment>
396
- )}
397
360
  </div>
@@ -180,7 +180,7 @@ const ToolbarButton = ({
180
180
  disabled={disabled}
181
181
  title={title}
182
182
  className={cn(
183
- "focus-visible:ring-ring/50 focus-visible:border-ring inline-flex size-8 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-2 disabled:opacity-50 disabled:hover:bg-transparent disabled:hover:text-muted-foreground",
183
+ "focus-visible:ring-ring/50 focus-visible:border-ring disabled:hover:text-muted-foreground inline-flex size-8 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-2 disabled:opacity-50 disabled:hover:bg-transparent",
184
184
  active ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
185
185
  )}
186
186
  >
@@ -623,7 +623,7 @@ export default function RichTextEditor({ name, initialValue, rows = 10, onChange
623
623
  disabled={!editor}
624
624
  title={markdownMode ? "Switch to editor" : "Switch to Markdown"}
625
625
  >
626
- <span className="text-xs font-semibold leading-none">MD</span>
626
+ <span className="text-xs leading-none font-semibold">MD</span>
627
627
  </ToolbarButton>
628
628
  </div>
629
629
 
@@ -37,10 +37,36 @@ import {
37
37
  } from "lucide-react";
38
38
 
39
39
  const navIconMap: Record<string, any> = {
40
- BarChart, Bell, Bookmark, Calendar, Clock, Database, FileText, FolderTree,
41
- Globe, Home, Image, Key, Layers, LayoutGrid, Link, Lock, Mail,
42
- Menu, MessageSquare, Package, Palette, PencilRuler, Search, Settings,
43
- Shield, Star, Tag, Terminal, Users, Zap,
40
+ BarChart,
41
+ Bell,
42
+ Bookmark,
43
+ Calendar,
44
+ Clock,
45
+ Database,
46
+ FileText,
47
+ FolderTree,
48
+ Globe,
49
+ Home,
50
+ Image,
51
+ Key,
52
+ Layers,
53
+ LayoutGrid,
54
+ Link,
55
+ Lock,
56
+ Mail,
57
+ Menu,
58
+ MessageSquare,
59
+ Package,
60
+ Palette,
61
+ PencilRuler,
62
+ Search,
63
+ Settings,
64
+ Shield,
65
+ Star,
66
+ Tag,
67
+ Terminal,
68
+ Users,
69
+ Zap,
44
70
  };
45
71
 
46
72
  import MobileSidebar from "../components/MobileSidebar";
@@ -90,7 +116,15 @@ const singletonSlugs = new Set(collections.filter((c) => c.singleton).map((c) =>
90
116
  const isActiveSingles =
91
117
  activeCollection === "singles" || (activeCollection !== null && singletonSlugs.has(activeCollection));
92
118
 
93
- type NavItem = { href: string; label: string; singularLabel?: string; Icon: any; active: boolean; newHref?: string; weight: number };
119
+ type NavItem = {
120
+ href: string;
121
+ label: string;
122
+ singularLabel?: string;
123
+ Icon: any;
124
+ active: boolean;
125
+ newHref?: string;
126
+ weight: number;
127
+ };
94
128
  const navItems: NavItem[] = [];
95
129
 
96
130
  // Built-in nav items with default weights (spaced by 10 for easy interleaving)
@@ -210,31 +244,31 @@ navItems.sort((a, b) => a.weight - b.weight);
210
244
  <nav class="flex-1 space-y-1 overflow-y-auto px-3 py-3">
211
245
  {navItems.map((item) => (
212
246
  <div
247
+ class={cn(
248
+ "group relative rounded-lg",
249
+ item.active ? "bg-foreground/10 dark:bg-accent" : "hover:bg-foreground/5 dark:hover:bg-accent/60",
250
+ )}
251
+ >
252
+ <a
253
+ href={item.href}
213
254
  class={cn(
214
- "group relative rounded-lg",
215
- item.active ? "bg-foreground/10 dark:bg-accent" : "hover:bg-foreground/5 dark:hover:bg-accent/60",
255
+ "flex items-center gap-3 px-3 py-2.5 text-sm",
256
+ item.newHref && "pr-8",
257
+ item.active ? "text-accent-foreground" : "text-foreground/70 group-hover:text-foreground",
216
258
  )}
217
259
  >
260
+ <item.Icon className="size-4 shrink-0 stroke-1" />
261
+ <span>{item.label}</span>
262
+ </a>
263
+ {item.newHref && (
218
264
  <a
219
- href={item.href}
220
- class={cn(
221
- "flex items-center gap-3 px-3 py-2.5 text-sm",
222
- item.newHref && "pr-8",
223
- item.active ? "text-accent-foreground" : "text-foreground/70 group-hover:text-foreground",
224
- )}
265
+ href={item.newHref}
266
+ title={`Add new ${(item.singularLabel ?? item.label).toLowerCase()}`}
267
+ class="text-foreground/40 hover:text-foreground/70 absolute top-1/2 right-1.5 hidden -translate-y-1/2 rounded p-1 group-hover:block"
225
268
  >
226
- <item.Icon className="size-4 shrink-0 stroke-1" />
227
- <span>{item.label}</span>
269
+ <Plus className="size-3.5" />
228
270
  </a>
229
- {item.newHref && (
230
- <a
231
- href={item.newHref}
232
- title={`Add new ${(item.singularLabel ?? item.label).toLowerCase()}`}
233
- class="text-foreground/40 hover:text-foreground/70 absolute top-1/2 right-1.5 hidden -translate-y-1/2 rounded p-1 group-hover:block"
234
- >
235
- <Plus className="size-3.5" />
236
- </a>
237
- )}
271
+ )}
238
272
  </div>
239
273
  ))}
240
274
  </nav>