@actuate-media/cms-admin 0.49.0 → 0.49.3

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 (72) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +10 -11
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/modal.render.test.d.ts +2 -0
  6. package/dist/__tests__/components/modal.render.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/modal.render.test.js +37 -0
  8. package/dist/__tests__/components/modal.render.test.js.map +1 -0
  9. package/dist/actuate-admin.css +1 -1
  10. package/dist/components/FolderTree.js +19 -19
  11. package/dist/components/FolderTree.js.map +1 -1
  12. package/dist/components/MediaPickerModal.d.ts +1 -1
  13. package/dist/components/MediaPickerModal.d.ts.map +1 -1
  14. package/dist/components/MediaPickerModal.js +12 -11
  15. package/dist/components/MediaPickerModal.js.map +1 -1
  16. package/dist/components/SchedulePublishDialog.d.ts +1 -1
  17. package/dist/components/SchedulePublishDialog.d.ts.map +1 -1
  18. package/dist/components/SchedulePublishDialog.js +6 -4
  19. package/dist/components/SchedulePublishDialog.js.map +1 -1
  20. package/dist/components/SharePreviewLinkDialog.d.ts +1 -1
  21. package/dist/components/SharePreviewLinkDialog.d.ts.map +1 -1
  22. package/dist/components/SharePreviewLinkDialog.js +10 -9
  23. package/dist/components/SharePreviewLinkDialog.js.map +1 -1
  24. package/dist/components/VersionHistory.d.ts +1 -1
  25. package/dist/components/VersionHistory.d.ts.map +1 -1
  26. package/dist/components/VersionHistory.js +14 -14
  27. package/dist/components/VersionHistory.js.map +1 -1
  28. package/dist/components/ui/Modal.d.ts +17 -1
  29. package/dist/components/ui/Modal.d.ts.map +1 -1
  30. package/dist/components/ui/Modal.js +16 -15
  31. package/dist/components/ui/Modal.js.map +1 -1
  32. package/dist/fields/BlockBuilderField.d.ts.map +1 -1
  33. package/dist/fields/BlockBuilderField.js +6 -4
  34. package/dist/fields/BlockBuilderField.js.map +1 -1
  35. package/dist/views/MediaBrowser.d.ts.map +1 -1
  36. package/dist/views/MediaBrowser.js +16 -16
  37. package/dist/views/MediaBrowser.js.map +1 -1
  38. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  39. package/dist/views/Pages/PagesListView.js +3 -3
  40. package/dist/views/Pages/PagesListView.js.map +1 -1
  41. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  42. package/dist/views/Posts/PostsListView.js +3 -3
  43. package/dist/views/Posts/PostsListView.js.map +1 -1
  44. package/dist/views/Redirects.d.ts.map +1 -1
  45. package/dist/views/Redirects.js +7 -7
  46. package/dist/views/Redirects.js.map +1 -1
  47. package/dist/views/ScriptTagEditor.js +4 -4
  48. package/dist/views/ScriptTagEditor.js.map +1 -1
  49. package/dist/views/ScriptTags.d.ts.map +1 -1
  50. package/dist/views/ScriptTags.js +5 -5
  51. package/dist/views/ScriptTags.js.map +1 -1
  52. package/dist/views/page-builder/AIGenerateDialog.d.ts +1 -1
  53. package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -1
  54. package/dist/views/page-builder/AIGenerateDialog.js +23 -23
  55. package/dist/views/page-builder/AIGenerateDialog.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/AdminRoot.tsx +35 -30
  58. package/src/__tests__/components/modal.render.test.tsx +57 -0
  59. package/src/components/FolderTree.tsx +32 -32
  60. package/src/components/MediaPickerModal.tsx +143 -119
  61. package/src/components/SchedulePublishDialog.tsx +118 -93
  62. package/src/components/SharePreviewLinkDialog.tsx +115 -93
  63. package/src/components/VersionHistory.tsx +98 -82
  64. package/src/components/ui/Modal.tsx +48 -39
  65. package/src/fields/BlockBuilderField.tsx +26 -19
  66. package/src/views/MediaBrowser.tsx +128 -120
  67. package/src/views/Pages/PagesListView.tsx +123 -115
  68. package/src/views/Posts/PostsListView.tsx +114 -110
  69. package/src/views/Redirects.tsx +48 -44
  70. package/src/views/ScriptTagEditor.tsx +40 -40
  71. package/src/views/ScriptTags.tsx +34 -30
  72. package/src/views/page-builder/AIGenerateDialog.tsx +347 -335
@@ -398,123 +398,131 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
398
398
 
399
399
  {/* Table card */}
400
400
  <div className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
401
- <table className="w-full table-fixed" role="table" aria-label="Pages">
402
- <colgroup>
403
- <col style={{ width: '40px' }} />
404
- <col />
405
- <col style={{ width: '200px' }} />
406
- <col style={{ width: '170px' }} />
407
- <col style={{ width: '120px' }} />
408
- <col style={{ width: '130px' }} />
409
- <col style={{ width: '80px' }} />
410
- </colgroup>
411
- <thead className="border-border bg-muted/40 border-b">
412
- <tr className="text-muted-foreground text-xs">
413
- <th scope="col" className="px-3 py-2.5 text-left">
414
- <input
415
- type="checkbox"
416
- aria-label="Select all pages on this page"
417
- checked={allOnPageSelected}
418
- onChange={(e) => toggleAllOnPage(e.target.checked)}
419
- className="border-border accent-primary rounded"
420
- />
421
- </th>
422
- <th scope="col" aria-sort={ariaSortFor('title')} className="px-3 py-2.5 text-left">
423
- <SortHeader
424
- label="Title"
425
- field="title"
426
- current={sortBy}
427
- dir={dir}
428
- onSort={handleSort}
429
- />
430
- </th>
431
- <th scope="col" className="px-3 py-2.5 text-left">
432
- <span className="text-xs font-medium tracking-wider uppercase">Tags</span>
433
- </th>
434
- <th scope="col" aria-sort={ariaSortFor('author')} className="px-3 py-2.5 text-left">
435
- <SortHeader
436
- label="Author"
437
- field="author"
438
- current={sortBy}
439
- dir={dir}
440
- onSort={handleSort}
441
- />
442
- </th>
443
- <th scope="col" aria-sort={ariaSortFor('status')} className="px-3 py-2.5 text-left">
444
- <SortHeader
445
- label="Status"
446
- field="status"
447
- current={sortBy}
448
- dir={dir}
449
- onSort={handleSort}
450
- />
451
- </th>
452
- <th scope="col" aria-sort={ariaSortFor('modified')} className="px-3 py-2.5 text-left">
453
- <SortHeader
454
- label="Last Modified"
455
- field="modified"
456
- current={sortBy}
457
- dir={dir}
458
- onSort={handleSort}
459
- />
460
- </th>
461
- <th scope="col" aria-sort={ariaSortFor('seo')} className="px-3 py-2.5 text-left">
462
- <SortHeader
463
- label="SEO"
464
- field="seo"
465
- current={sortBy}
466
- dir={dir}
467
- onSort={handleSort}
401
+ {/* Horizontal scroll on narrow viewports: the fixed columns keep their
402
+ width and the table scrolls sideways instead of crushing content. */}
403
+ <div className="overflow-x-auto">
404
+ <table className="w-full min-w-[800px] table-fixed" role="table" aria-label="Pages">
405
+ <colgroup>
406
+ <col style={{ width: '40px' }} />
407
+ <col />
408
+ <col style={{ width: '200px' }} />
409
+ <col style={{ width: '170px' }} />
410
+ <col style={{ width: '120px' }} />
411
+ <col style={{ width: '130px' }} />
412
+ <col style={{ width: '80px' }} />
413
+ </colgroup>
414
+ <thead className="border-border bg-muted/40 border-b">
415
+ <tr className="text-muted-foreground text-xs">
416
+ <th scope="col" className="px-3 py-2.5 text-left">
417
+ <input
418
+ type="checkbox"
419
+ aria-label="Select all pages on this page"
420
+ checked={allOnPageSelected}
421
+ onChange={(e) => toggleAllOnPage(e.target.checked)}
422
+ className="border-border accent-primary rounded"
423
+ />
424
+ </th>
425
+ <th scope="col" aria-sort={ariaSortFor('title')} className="px-3 py-2.5 text-left">
426
+ <SortHeader
427
+ label="Title"
428
+ field="title"
429
+ current={sortBy}
430
+ dir={dir}
431
+ onSort={handleSort}
432
+ />
433
+ </th>
434
+ <th scope="col" className="px-3 py-2.5 text-left">
435
+ <span className="text-xs font-medium tracking-wider uppercase">Tags</span>
436
+ </th>
437
+ <th scope="col" aria-sort={ariaSortFor('author')} className="px-3 py-2.5 text-left">
438
+ <SortHeader
439
+ label="Author"
440
+ field="author"
441
+ current={sortBy}
442
+ dir={dir}
443
+ onSort={handleSort}
444
+ />
445
+ </th>
446
+ <th scope="col" aria-sort={ariaSortFor('status')} className="px-3 py-2.5 text-left">
447
+ <SortHeader
448
+ label="Status"
449
+ field="status"
450
+ current={sortBy}
451
+ dir={dir}
452
+ onSort={handleSort}
453
+ />
454
+ </th>
455
+ <th
456
+ scope="col"
457
+ aria-sort={ariaSortFor('modified')}
458
+ className="px-3 py-2.5 text-left"
459
+ >
460
+ <SortHeader
461
+ label="Last Modified"
462
+ field="modified"
463
+ current={sortBy}
464
+ dir={dir}
465
+ onSort={handleSort}
466
+ />
467
+ </th>
468
+ <th scope="col" aria-sort={ariaSortFor('seo')} className="px-3 py-2.5 text-left">
469
+ <SortHeader
470
+ label="SEO"
471
+ field="seo"
472
+ current={sortBy}
473
+ dir={dir}
474
+ onSort={handleSort}
475
+ />
476
+ </th>
477
+ </tr>
478
+ </thead>
479
+ <tbody>
480
+ {loading && pages.length === 0 && <LoadingRows />}
481
+ {!loading && error && (
482
+ <tr>
483
+ <td colSpan={7} className="px-3 py-12 text-center">
484
+ <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
485
+ <button
486
+ onClick={() => setRefreshToken((t) => t + 1)}
487
+ className="text-primary text-sm hover:underline"
488
+ >
489
+ Try again
490
+ </button>
491
+ </td>
492
+ </tr>
493
+ )}
494
+ {!loading && !error && pages.length === 0 && (
495
+ <EmptyRow
496
+ filtersActive={filtersAreActive}
497
+ onClear={clearFilters}
498
+ onNewPage={() => onNavigate('/pages/new')}
468
499
  />
469
- </th>
470
- </tr>
471
- </thead>
472
- <tbody>
473
- {loading && pages.length === 0 && <LoadingRows />}
474
- {!loading && error && (
475
- <tr>
476
- <td colSpan={7} className="px-3 py-12 text-center">
477
- <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
478
- <button
479
- onClick={() => setRefreshToken((t) => t + 1)}
480
- className="text-primary text-sm hover:underline"
500
+ )}
501
+ {!loading &&
502
+ !error &&
503
+ displayGroups.map((group) => (
504
+ <GroupBlock
505
+ key={group.key}
506
+ showHeader={grouped}
507
+ label={group.label}
508
+ color={group.color}
509
+ count={group.pages.length}
481
510
  >
482
- Try again
483
- </button>
484
- </td>
485
- </tr>
486
- )}
487
- {!loading && !error && pages.length === 0 && (
488
- <EmptyRow
489
- filtersActive={filtersAreActive}
490
- onClear={clearFilters}
491
- onNewPage={() => onNavigate('/pages/new')}
492
- />
493
- )}
494
- {!loading &&
495
- !error &&
496
- displayGroups.map((group) => (
497
- <GroupBlock
498
- key={group.key}
499
- showHeader={grouped}
500
- label={group.label}
501
- color={group.color}
502
- count={group.pages.length}
503
- >
504
- {group.pages.map((p) => (
505
- <PageRow
506
- key={p.id}
507
- page={p}
508
- selected={selected.has(p.id)}
509
- resolveTag={(slug) => tagBySlug.get(slug)}
510
- onSelect={() => toggleOne(p.id)}
511
- onOpen={() => onNavigate(`/pages/${p.id}/edit`)}
512
- />
513
- ))}
514
- </GroupBlock>
515
- ))}
516
- </tbody>
517
- </table>
511
+ {group.pages.map((p) => (
512
+ <PageRow
513
+ key={p.id}
514
+ page={p}
515
+ selected={selected.has(p.id)}
516
+ resolveTag={(slug) => tagBySlug.get(slug)}
517
+ onSelect={() => toggleOne(p.id)}
518
+ onOpen={() => onNavigate(`/pages/${p.id}/edit`)}
519
+ />
520
+ ))}
521
+ </GroupBlock>
522
+ ))}
523
+ </tbody>
524
+ </table>
525
+ </div>
518
526
 
519
527
  {/* Pagination only for the flat (ungrouped) view. */}
520
528
  {!grouped && (
@@ -257,118 +257,122 @@ export function PostsListView({ onNavigate }: PostsListViewProps) {
257
257
 
258
258
  {/* Table card */}
259
259
  <div className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
260
- <table className="w-full table-fixed" role="table" aria-label="Posts">
261
- <colgroup>
262
- <col style={{ width: '40px' }} />
263
- <col />
264
- <col style={{ width: '140px' }} />
265
- <col style={{ width: '180px' }} />
266
- <col style={{ width: '130px' }} />
267
- <col style={{ width: '120px' }} />
268
- <col style={{ width: '90px' }} />
269
- </colgroup>
270
- <thead className="border-border bg-muted/40 border-b">
271
- <tr className="text-muted-foreground text-xs">
272
- <th scope="col" className="px-3 py-2.5 text-left">
273
- <input
274
- type="checkbox"
275
- aria-label="Select all posts on this page"
276
- checked={allOnPageSelected}
277
- onChange={(e) => toggleAllOnPage(e.target.checked)}
278
- className="border-border accent-primary rounded"
279
- />
280
- </th>
281
- <th scope="col" className="px-3 py-2.5 text-left">
282
- <SortHeader
283
- label="Title"
284
- field="title"
285
- current={sortBy}
286
- dir={dir}
287
- onSort={handleSort}
288
- />
289
- </th>
290
- <th scope="col" className="px-3 py-2.5 text-left">
291
- <SortHeader
292
- label="Type"
293
- field="type"
294
- current={sortBy}
295
- dir={dir}
296
- onSort={handleSort}
297
- />
298
- </th>
299
- <th scope="col" className="px-3 py-2.5 text-left">
300
- <SortHeader
301
- label="Author"
302
- field="author"
303
- current={sortBy}
304
- dir={dir}
305
- onSort={handleSort}
306
- />
307
- </th>
308
- <th scope="col" className="px-3 py-2.5 text-left">
309
- <SortHeader
310
- label="Status"
311
- field="status"
312
- current={sortBy}
313
- dir={dir}
314
- onSort={handleSort}
315
- />
316
- </th>
317
- <th scope="col" className="px-3 py-2.5 text-left">
318
- <SortHeader
319
- label="Date"
320
- field="date"
321
- current={sortBy}
322
- dir={dir}
323
- onSort={handleSort}
324
- />
325
- </th>
326
- <th scope="col" className="px-3 py-2.5 text-left">
327
- <SortHeader
328
- label="SEO"
329
- field="seo"
330
- current={sortBy}
331
- dir={dir}
332
- onSort={handleSort}
333
- />
334
- </th>
335
- </tr>
336
- </thead>
337
- <tbody>
338
- {loading && posts.length === 0 && <LoadingRows />}
339
- {!loading && error && (
340
- <tr>
341
- <td colSpan={7} className="px-3 py-12 text-center">
342
- <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
343
- <button
344
- onClick={() => setRefreshToken((t) => t + 1)}
345
- className="text-primary text-sm hover:underline"
346
- >
347
- Try again
348
- </button>
349
- </td>
260
+ {/* Horizontal scroll on narrow viewports: the fixed columns keep their
261
+ width and the table scrolls sideways instead of crushing content. */}
262
+ <div className="overflow-x-auto">
263
+ <table className="w-full min-w-[760px] table-fixed" role="table" aria-label="Posts">
264
+ <colgroup>
265
+ <col style={{ width: '40px' }} />
266
+ <col />
267
+ <col style={{ width: '140px' }} />
268
+ <col style={{ width: '180px' }} />
269
+ <col style={{ width: '130px' }} />
270
+ <col style={{ width: '120px' }} />
271
+ <col style={{ width: '90px' }} />
272
+ </colgroup>
273
+ <thead className="border-border bg-muted/40 border-b">
274
+ <tr className="text-muted-foreground text-xs">
275
+ <th scope="col" className="px-3 py-2.5 text-left">
276
+ <input
277
+ type="checkbox"
278
+ aria-label="Select all posts on this page"
279
+ checked={allOnPageSelected}
280
+ onChange={(e) => toggleAllOnPage(e.target.checked)}
281
+ className="border-border accent-primary rounded"
282
+ />
283
+ </th>
284
+ <th scope="col" className="px-3 py-2.5 text-left">
285
+ <SortHeader
286
+ label="Title"
287
+ field="title"
288
+ current={sortBy}
289
+ dir={dir}
290
+ onSort={handleSort}
291
+ />
292
+ </th>
293
+ <th scope="col" className="px-3 py-2.5 text-left">
294
+ <SortHeader
295
+ label="Type"
296
+ field="type"
297
+ current={sortBy}
298
+ dir={dir}
299
+ onSort={handleSort}
300
+ />
301
+ </th>
302
+ <th scope="col" className="px-3 py-2.5 text-left">
303
+ <SortHeader
304
+ label="Author"
305
+ field="author"
306
+ current={sortBy}
307
+ dir={dir}
308
+ onSort={handleSort}
309
+ />
310
+ </th>
311
+ <th scope="col" className="px-3 py-2.5 text-left">
312
+ <SortHeader
313
+ label="Status"
314
+ field="status"
315
+ current={sortBy}
316
+ dir={dir}
317
+ onSort={handleSort}
318
+ />
319
+ </th>
320
+ <th scope="col" className="px-3 py-2.5 text-left">
321
+ <SortHeader
322
+ label="Date"
323
+ field="date"
324
+ current={sortBy}
325
+ dir={dir}
326
+ onSort={handleSort}
327
+ />
328
+ </th>
329
+ <th scope="col" className="px-3 py-2.5 text-left">
330
+ <SortHeader
331
+ label="SEO"
332
+ field="seo"
333
+ current={sortBy}
334
+ dir={dir}
335
+ onSort={handleSort}
336
+ />
337
+ </th>
350
338
  </tr>
351
- )}
352
- {!loading && !error && posts.length === 0 && (
353
- <EmptyRow
354
- filtersActive={filtersAreActive}
355
- onClear={clearFilters}
356
- onNewPost={() => onNavigate('/posts/new')}
357
- />
358
- )}
359
- {!loading &&
360
- !error &&
361
- posts.map((p) => (
362
- <PostRow
363
- key={p.id}
364
- post={p}
365
- selected={selected.has(p.id)}
366
- onSelect={() => toggleOne(p.id)}
367
- onOpen={() => onNavigate(`/posts/${p.postTypeId}/${p.id}/edit`)}
339
+ </thead>
340
+ <tbody>
341
+ {loading && posts.length === 0 && <LoadingRows />}
342
+ {!loading && error && (
343
+ <tr>
344
+ <td colSpan={7} className="px-3 py-12 text-center">
345
+ <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
346
+ <button
347
+ onClick={() => setRefreshToken((t) => t + 1)}
348
+ className="text-primary text-sm hover:underline"
349
+ >
350
+ Try again
351
+ </button>
352
+ </td>
353
+ </tr>
354
+ )}
355
+ {!loading && !error && posts.length === 0 && (
356
+ <EmptyRow
357
+ filtersActive={filtersAreActive}
358
+ onClear={clearFilters}
359
+ onNewPost={() => onNavigate('/posts/new')}
368
360
  />
369
- ))}
370
- </tbody>
371
- </table>
361
+ )}
362
+ {!loading &&
363
+ !error &&
364
+ posts.map((p) => (
365
+ <PostRow
366
+ key={p.id}
367
+ post={p}
368
+ selected={selected.has(p.id)}
369
+ onSelect={() => toggleOne(p.id)}
370
+ onOpen={() => onNavigate(`/posts/${p.postTypeId}/${p.id}/edit`)}
371
+ />
372
+ ))}
373
+ </tbody>
374
+ </table>
375
+ </div>
372
376
 
373
377
  {/* Pagination footer */}
374
378
  <PaginationFooter