@kyro-cms/admin 0.9.0 → 0.9.2

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 (114) hide show
  1. package/dist/index.cjs +11715 -11292
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +67 -65
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +564 -0
  6. package/dist/index.d.ts +11 -10
  7. package/dist/index.js +11326 -10912
  8. package/dist/index.js.map +1 -1
  9. package/package.json +16 -12
  10. package/src/components/ActionBar.tsx +25 -161
  11. package/src/components/Admin.tsx +2 -4
  12. package/src/components/ApiKeysManager.tsx +5 -5
  13. package/src/components/AuditLogsPage.tsx +2 -13
  14. package/src/components/AutoForm.tsx +572 -461
  15. package/src/components/BrandingHub.tsx +7 -4
  16. package/src/components/CreateView.tsx +2 -0
  17. package/src/components/DetailView.tsx +52 -65
  18. package/src/components/DeveloperCenter.tsx +8 -6
  19. package/src/components/FieldRenderer.tsx +94 -19
  20. package/src/components/ListView.tsx +57 -216
  21. package/src/components/MediaGallery.tsx +334 -367
  22. package/src/components/PluginsManager.tsx +197 -70
  23. package/src/components/RestPlayground.tsx +59 -52
  24. package/src/components/SessionsManager.tsx +1 -1
  25. package/src/components/SettingsPage.tsx +22 -0
  26. package/src/components/Sidebar.astro +13 -41
  27. package/src/components/UserManagement.tsx +153 -15
  28. package/src/components/UserMenu.tsx +30 -4
  29. package/src/components/VersionHistoryPanel.tsx +112 -119
  30. package/src/components/WebhookManager.tsx +6 -4
  31. package/src/components/blocks/ArrayBlock.tsx +6 -23
  32. package/src/components/blocks/BlockEditModal.tsx +82 -309
  33. package/src/components/blocks/CardBlock.tsx +35 -0
  34. package/src/components/blocks/ChildBlocksTree.tsx +57 -31
  35. package/src/components/blocks/GenericBlock.tsx +44 -0
  36. package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
  37. package/src/components/blocks/HeroBlock.tsx +5 -14
  38. package/src/components/blocks/RichTextBlock.tsx +5 -5
  39. package/src/components/blocks/index.ts +5 -3
  40. package/src/components/fields/AccordionField.tsx +2 -2
  41. package/src/components/fields/ArrayField.tsx +1 -1
  42. package/src/components/fields/ArrayLayout.tsx +120 -29
  43. package/src/components/fields/BlocksField.tsx +433 -55
  44. package/src/components/fields/CardField.tsx +73 -0
  45. package/src/components/fields/CheckboxField.tsx +7 -3
  46. package/src/components/fields/DateField.tsx +4 -1
  47. package/src/components/fields/GroupLayout.tsx +2 -2
  48. package/src/components/fields/HeadingSubheadingField.tsx +43 -0
  49. package/src/components/fields/ListField.tsx +2 -2
  50. package/src/components/fields/NumberField.tsx +4 -1
  51. package/src/components/fields/RelationshipBlockField.tsx +2 -3
  52. package/src/components/fields/RelationshipField.tsx +155 -90
  53. package/src/components/fields/RichTextField.tsx +781 -0
  54. package/src/components/fields/SecretField.tsx +102 -0
  55. package/src/components/fields/SelectField.tsx +19 -6
  56. package/src/components/fields/TabsLayout.tsx +19 -9
  57. package/src/components/fields/TextField.tsx +4 -1
  58. package/src/components/fields/UploadField.tsx +122 -56
  59. package/src/components/fields/extensions/blockComponents.tsx +103 -174
  60. package/src/components/fields/extensions/blocksStore.ts +8 -1
  61. package/src/components/fields/index.ts +4 -2
  62. package/src/components/fix_imports.cjs +23 -0
  63. package/src/components/fix_imports2.cjs +19 -0
  64. package/src/components/replace_svgs.cjs +63 -0
  65. package/src/components/ui/Dropdown.tsx +7 -2
  66. package/src/components/ui/Modal.tsx +24 -27
  67. package/src/components/ui/PageHeader.tsx +5 -5
  68. package/src/components/ui/PromptModal.tsx +2 -10
  69. package/src/components/ui/SlidePanel.tsx +10 -13
  70. package/src/components/ui/SplitButton.tsx +107 -0
  71. package/src/components/ui/Toaster.tsx +0 -1
  72. package/src/components/ui/icons.tsx +110 -109
  73. package/src/components/users/UserDetail.tsx +79 -16
  74. package/src/components/users/UsersList.tsx +8 -85
  75. package/src/hooks/useAutoFormState.ts +187 -196
  76. package/src/hooks/useQueue.ts +60 -0
  77. package/src/integration.ts +148 -46
  78. package/src/kyro-cms.d.ts +7 -2
  79. package/src/layouts/AdminLayout.astro +22 -2
  80. package/src/layouts/AuthLayout.astro +67 -7
  81. package/src/lib/autoform-store.ts +90 -53
  82. package/src/lib/change-source.ts +9 -0
  83. package/src/lib/config.ts +104 -8
  84. package/src/lib/globals.ts +48 -11
  85. package/src/lib/normalize-upload-fields.ts +41 -0
  86. package/src/lib/paths.ts +2 -2
  87. package/src/lib/resolve-field-value.ts +110 -0
  88. package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
  89. package/src/lib/shim/use-sync-external-store.js +1 -0
  90. package/src/lib/stores/index.ts +1 -0
  91. package/src/lib/useResourceManager.ts +4 -4
  92. package/src/lib/vite-shim-plugin.ts +100 -0
  93. package/src/pages/[collection]/[id].astro +1 -1
  94. package/src/pages/auth/register.astro +5 -2
  95. package/src/pages/preview/[collection]/[id].astro +4 -4
  96. package/src/pages/settings/[slug].astro +2 -2
  97. package/src/styles/main.css +60 -54
  98. package/README.md +0 -46
  99. package/dist/EditorClient-Q23UXR37.cjs +0 -468
  100. package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
  101. package/dist/EditorClient-T5PASFNR.js +0 -466
  102. package/dist/EditorClient-T5PASFNR.js.map +0 -1
  103. package/dist/chunk-3BGDYKTD.cjs +0 -348
  104. package/dist/chunk-3BGDYKTD.cjs.map +0 -1
  105. package/dist/chunk-EEFXLQVT.js +0 -3
  106. package/dist/chunk-EEFXLQVT.js.map +0 -1
  107. package/src/components/blocks/ButtonBlock.tsx +0 -64
  108. package/src/components/blocks/ColumnsBlock.tsx +0 -55
  109. package/src/components/blocks/DividerBlock.tsx +0 -43
  110. package/src/components/blocks/LinkBlock.tsx +0 -65
  111. package/src/components/blocks/VStackBlock.tsx +0 -29
  112. package/src/components/fields/EditorClient.tsx +0 -535
  113. package/src/components/fields/PortableTextField.tsx +0 -155
  114. package/src/components/fields/PortableTextRenderer.tsx +0 -68
@@ -1,17 +1,20 @@
1
+ import { Search, Filter, Columns3, X, Trash2, Archive, ChevronUp, Edit2 } from "./ui/icons";
1
2
  import { useState, useEffect, useMemo, useCallback, useRef } from "react";
2
3
  import { Spinner } from "./ui/Spinner";
3
4
  import { Shimmer } from "./ui/Shimmer";
4
5
  import { Plus } from "./ui/icons";
5
6
  import { apiGet, apiDelete, withCacheBust } from "../lib/api";
6
7
 
7
- import { useAuthStore } from "../lib/stores";
8
+ import { useAuthStore, toast } from "../lib/stores";
8
9
  import { useUIStore } from "../lib/stores";
9
10
  import { adminPath as ADMIN_BASE } from "../lib/paths";
10
11
  import { PageHeader } from "./ui/PageHeader";
11
12
  import { Badge } from "./ui/Badge";
13
+ import { Pagination } from "./ui/Pagination";
12
14
 
13
15
 
14
16
  import type { CollectionConfig, Field } from "@kyro-cms/core";
17
+ import { resolveFieldValue } from "../lib/resolve-field-value";
15
18
 
16
19
  type FieldConfig = Field;
17
20
 
@@ -114,7 +117,7 @@ export function ListView({
114
117
  function flattenFields(fields: FieldConfig[]): FieldConfig[] {
115
118
  const result: FieldConfig[] = [];
116
119
  for (const field of fields || []) {
117
- if (!field.name || field.admin?.hidden || field.name === "id") continue;
120
+ if (!field.name || field.hidden === true || field.admin?.hidden || field.name === "id") continue;
118
121
  if (field.type === "tabs" && field.tabs) {
119
122
  for (const tab of field.tabs) {
120
123
  if (tab.fields) {
@@ -197,7 +200,21 @@ export function ListView({
197
200
  }, []);
198
201
 
199
202
  const displayFields = useMemo(
200
- () => allFields.filter((f): f is typeof f & { name: string } => !!f.name && visibleColumns.has(f.name)),
203
+ () => {
204
+ const fields = allFields.filter((f): f is typeof f & { name: string } => !!f.name && visibleColumns.has(f.name));
205
+ if (visibleColumns.has("status")) {
206
+ fields.push({
207
+ name: "status",
208
+ type: "select",
209
+ label: "Status",
210
+ options: [
211
+ { value: "draft", label: "Draft" },
212
+ { value: "published", label: "Published" },
213
+ ],
214
+ } as any);
215
+ }
216
+ return fields;
217
+ },
201
218
  [allFields, visibleColumns],
202
219
  );
203
220
 
@@ -211,21 +228,8 @@ export function ListView({
211
228
 
212
229
  function extractFieldValue(doc: any, field: FieldConfig): any {
213
230
  if (!field.name) return null;
214
- if (doc[field.name] !== undefined && doc[field.name] !== null) {
215
- return doc[field.name];
216
- }
217
- if (field.type === "group" && typeof doc[field.name] === "object") {
218
- const firstFieldName = field.fields?.[0]?.name;
219
- if (
220
- firstFieldName &&
221
- doc[field.name][firstFieldName] !== undefined
222
- ) {
223
- return doc[field.name][firstFieldName];
224
- }
225
- const firstKey = Object.keys(doc[field.name] || {})[0];
226
- if (firstKey) return doc[field.name][firstKey];
227
- }
228
- return null;
231
+ const val = resolveFieldValue(collection.fields as any, doc, field.name);
232
+ return val ?? null;
229
233
  }
230
234
 
231
235
  const fetchDocs = useCallback(async () => {
@@ -303,9 +307,10 @@ export function ListView({
303
307
  }
304
308
  setSelectedIds(new Set());
305
309
  fetchDocs();
310
+ toast.success("Documents deleted");
306
311
  } catch (error) {
307
312
  console.error("Bulk delete failed:", error);
308
- alert({ title: "Error", message: "Failed to delete some documents" });
313
+ toast.error("Failed to delete some documents");
309
314
  }
310
315
  }
311
316
  });
@@ -320,9 +325,10 @@ export function ListView({
320
325
  try {
321
326
  await apiDelete(`/api/${collectionSlug}/${id}`);
322
327
  fetchDocs();
328
+ toast.success("Document deleted");
323
329
  } catch (error) {
324
330
  console.error("Delete failed:", error);
325
- alert({ title: "Error", message: "Failed to delete document" });
331
+ toast.error("Failed to delete document");
326
332
  }
327
333
  }
328
334
  });
@@ -354,19 +360,7 @@ export function ListView({
354
360
  <div className="surface-tile p-4 flex flex-col lg:flex-row gap-4 items-start lg:items-center">
355
361
  {/* Search */}
356
362
  <div className="relative flex-1 max-w-md">
357
- <svg
358
- className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-secondary)]"
359
- fill="none"
360
- stroke="currentColor"
361
- viewBox="0 0 24 24"
362
- >
363
- <path
364
- strokeLinecap="round"
365
- strokeLinejoin="round"
366
- strokeWidth="2"
367
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
368
- />
369
- </svg>
363
+ <Search className="w-4 h-4" />
370
364
  <input
371
365
  type="text"
372
366
  placeholder="Search..."
@@ -386,19 +380,7 @@ export function ListView({
386
380
  : "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
387
381
  }`}
388
382
  >
389
- <svg
390
- className="w-4 h-4"
391
- fill="none"
392
- stroke="currentColor"
393
- viewBox="0 0 24 24"
394
- >
395
- <path
396
- strokeLinecap="round"
397
- strokeLinejoin="round"
398
- strokeWidth="2"
399
- d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
400
- />
401
- </svg>
383
+ <Filter className="w-4 h-4" />
402
384
  Filters
403
385
  {filters.length > 0 && (
404
386
  <span className="ml-1 px-1.5 py-0.5 bg-[var(--kyro-sidebar-text-active)] text-[var(--kyro-sidebar-active)] rounded-full text-xs">
@@ -414,19 +396,7 @@ export function ListView({
414
396
  onClick={() => setShowColumns(!showColumns)}
415
397
  className="flex items-center gap-2 px-4 py-2 rounded-xl font-bold text-sm bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-all"
416
398
  >
417
- <svg
418
- className="w-4 h-4"
419
- fill="none"
420
- stroke="currentColor"
421
- viewBox="0 0 24 24"
422
- >
423
- <path
424
- strokeLinecap="round"
425
- strokeLinejoin="round"
426
- strokeWidth="2"
427
- d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
428
- />
429
- </svg>
399
+ <Columns3 className="w-4 h-4" />
430
400
  Columns
431
401
  </button>
432
402
  {showColumns && (
@@ -486,19 +456,7 @@ export function ListView({
486
456
  onClick={addFilter}
487
457
  className="flex items-center gap-2 px-3 py-1.5 text-sm font-bold text-[var(--kyro-sidebar-active)] hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-all"
488
458
  >
489
- <svg
490
- className="w-4 h-4"
491
- fill="none"
492
- stroke="currentColor"
493
- viewBox="0 0 24 24"
494
- >
495
- <path
496
- strokeLinecap="round"
497
- strokeLinejoin="round"
498
- strokeWidth="2"
499
- d="M12 5v14M5 12h14"
500
- />
501
- </svg>
459
+ <Plus className="w-4 h-4" />
502
460
  Add Filter
503
461
  </button>
504
462
  </div>
@@ -548,19 +506,7 @@ export function ListView({
548
506
  onClick={() => removeFilter(index)}
549
507
  className="p-2 text-[var(--kyro-text-muted)] hover:text-red-500 transition-colors"
550
508
  >
551
- <svg
552
- className="w-4 h-4"
553
- fill="none"
554
- stroke="currentColor"
555
- viewBox="0 0 24 24"
556
- >
557
- <path
558
- strokeLinecap="round"
559
- strokeLinejoin="round"
560
- strokeWidth="2"
561
- d="M6 18L18 6M6 6l12 12"
562
- />
563
- </svg>
509
+ <X className="w-4 h-4" />
564
510
  </button>
565
511
  </div>
566
512
  ))}
@@ -586,19 +532,7 @@ export function ListView({
586
532
  onClick={handleBulkDelete}
587
533
  className="flex items-center gap-2 px-4 py-2 bg-red-500 text-white rounded-lg font-bold text-sm hover:bg-red-600 transition-all"
588
534
  >
589
- <svg
590
- className="w-4 h-4"
591
- fill="none"
592
- stroke="currentColor"
593
- viewBox="0 0 24 24"
594
- >
595
- <path
596
- strokeLinecap="round"
597
- strokeLinejoin="round"
598
- strokeWidth="2"
599
- d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
600
- />
601
- </svg>
535
+ <Trash2 className="w-4 h-4" />
602
536
  Delete Selected
603
537
  </button>
604
538
  )}
@@ -622,19 +556,7 @@ export function ListView({
622
556
  ) : docs.length === 0 ? (
623
557
  <div className="flex flex-col items-center justify-center py-16 px-8">
624
558
  <div className="w-16 h-16 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center mb-4">
625
- <svg
626
- className="w-8 h-8 text-[var(--kyro-text-muted)]"
627
- fill="none"
628
- stroke="currentColor"
629
- viewBox="0 0 24 24"
630
- >
631
- <path
632
- strokeLinecap="round"
633
- strokeLinejoin="round"
634
- strokeWidth="1.5"
635
- d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
636
- />
637
- </svg>
559
+ <Archive className="w-4 h-4" />
638
560
  </div>
639
561
  <p className="font-medium text-[var(--kyro-text-primary)] text-base">
640
562
  No documents found
@@ -650,19 +572,7 @@ export function ListView({
650
572
  onClick={handleCreate}
651
573
  className="mt-4 kyro-btn kyro-btn-md kyro-btn-primary shadow-md flex items-center gap-2"
652
574
  >
653
- <svg
654
- className="w-3.5 h-3.5"
655
- fill="none"
656
- stroke="currentColor"
657
- viewBox="0 0 24 24"
658
- >
659
- <path
660
- strokeLinecap="round"
661
- strokeLinejoin="round"
662
- strokeWidth="2.5"
663
- d="M12 5v14M5 12h14"
664
- />
665
- </svg>
575
+ <Plus className="w-4 h-4" />
666
576
  Create{" "}
667
577
  {String(collection.singularLabel || collection.label || collectionSlug)}
668
578
  </button>
@@ -693,19 +603,7 @@ export function ListView({
693
603
  {checkTabbedValue(displayFields, field.type) ??
694
604
  (field.label || field.name)}
695
605
  {sort && sort.field === field.name && (
696
- <svg
697
- className={`w-3 h-3 ${sort.direction === "desc" ? "rotate-180" : ""}`}
698
- fill="none"
699
- stroke="currentColor"
700
- viewBox="0 0 24 24"
701
- >
702
- <path
703
- strokeLinecap="round"
704
- strokeLinejoin="round"
705
- strokeWidth="2"
706
- d="M5 15l7-7 7 7"
707
- />
708
- </svg>
606
+ <ChevronUp className="w-4 h-4" />
709
607
  )}
710
608
  </div>
711
609
  </th>
@@ -775,19 +673,7 @@ export function ListView({
775
673
  className="flex items-center gap-2 px-3 py-1.5 hover:bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-all"
776
674
  title={canUpdate ? "Edit" : "View"}
777
675
  >
778
- <svg
779
- className="w-4 h-4"
780
- fill="none"
781
- stroke="currentColor"
782
- viewBox="0 0 24 24"
783
- >
784
- <path
785
- strokeLinecap="round"
786
- strokeLinejoin="round"
787
- strokeWidth="2"
788
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
789
- />
790
- </svg>
676
+ <Edit2 className="w-4 h-4" />
791
677
  </button>
792
678
  {canDelete && (
793
679
  <button
@@ -796,19 +682,7 @@ export function ListView({
796
682
  className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[var(--kyro-text-muted)] hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-500/10 transition-colors"
797
683
  title="Delete"
798
684
  >
799
- <svg
800
- className="w-4 h-4"
801
- fill="none"
802
- stroke="currentColor"
803
- viewBox="0 0 24 24"
804
- >
805
- <path
806
- strokeLinecap="round"
807
- strokeLinejoin="round"
808
- strokeWidth="2"
809
- d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
810
- />
811
- </svg>
685
+ <Trash2 className="w-4 h-4" />
812
686
  </button>
813
687
  )}
814
688
  </div>
@@ -822,59 +696,17 @@ export function ListView({
822
696
  </div>
823
697
 
824
698
  {/* Pagination */}
825
- {totalDocs > limit && (
826
- <div className="flex flex-col lg:flex-row items-center justify-between gap-4 px-2">
827
- <div className="flex items-center gap-4">
828
- <span className="text-sm text-[var(--kyro-text-secondary)] font-medium">
829
- Showing{" "}
830
- <span className="text-[var(--kyro-text-primary)] font-bold">
831
- {(page - 1) * limit + 1}
832
- </span>{" "}
833
- to{" "}
834
- <span className="text-[var(--kyro-text-primary)] font-bold">
835
- {Math.min(page * limit, totalDocs)}
836
- </span>{" "}
837
- of{" "}
838
- <span className="text-[var(--kyro-text-primary)] font-bold">
839
- {totalDocs}
840
- </span>
841
- </span>
842
- <select
843
- value={limit}
844
- onChange={(e) => {
845
- setLimit(Number(e.target.value));
846
- setPage(1);
847
- }}
848
- className="px-2 py-1 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)]"
849
- >
850
- <option value={10}>10 / page</option>
851
- <option value={25}>25 / page</option>
852
- <option value={50}>50 / page</option>
853
- <option value={100}>100 / page</option>
854
- </select>
855
- </div>
856
- <div className="flex gap-2">
857
- {page > 1 && (
858
- <button
859
- type="button"
860
- onClick={() => setPage(page - 1)}
861
- className="px-4 py-2 border border-[var(--kyro-border)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
862
- >
863
- ← Previous
864
- </button>
865
- )}
866
- {page < totalPages && (
867
- <button
868
- type="button"
869
- onClick={() => setPage(page + 1)}
870
- className="px-4 py-2 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-lg text-sm font-bold hover:opacity-90 transition-all"
871
- >
872
- Next →
873
- </button>
874
- )}
875
- </div>
876
- </div>
877
- )}
699
+ <Pagination
700
+ page={page}
701
+ totalPages={totalPages}
702
+ totalDocs={totalDocs}
703
+ limit={limit}
704
+ onPageChange={setPage}
705
+ onLimitChange={(newLimit) => {
706
+ setLimit(newLimit);
707
+ setPage(1);
708
+ }}
709
+ />
878
710
  </div>
879
711
  );
880
712
  }
@@ -891,6 +723,15 @@ function formatCellValue(value: any, type?: string): string {
891
723
  });
892
724
  }
893
725
 
726
+ if (Array.isArray(value)) {
727
+ return value.map(item => {
728
+ if (item && typeof item === "object") {
729
+ return item.title || item.name || item.email || item.filename || item.url || JSON.stringify(item).slice(0, 30);
730
+ }
731
+ return String(item ?? "").slice(0, 30);
732
+ }).filter(Boolean).join(", ");
733
+ }
734
+
894
735
  if (typeof value === "object") {
895
736
  if (value.title) return value.title;
896
737
  if (value.name) return value.name;