@conduction/nextcloud-vue 2.62.0 → 2.63.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 (154) hide show
  1. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
  2. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
  3. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
  4. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
  6. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +271 -32
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  14. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  15. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  16. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  21. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  22. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  23. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  24. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  25. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +216 -8
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +118 -50
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  30. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  34. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  35. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  36. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  37. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  38. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  40. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  41. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  46. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  49. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
  50. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  51. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  52. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  53. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  54. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  55. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  56. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  57. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  58. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  59. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  60. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  61. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  62. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  63. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  64. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  65. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  66. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  67. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  68. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  69. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  70. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  71. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  72. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  73. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  74. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  75. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  76. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  77. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  78. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  79. package/dist/esm/composables/useDependencyCheck.js +65 -0
  80. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  81. package/dist/esm/composables/useWalkthrough.js +218 -6
  82. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  83. package/dist/esm/index.js +2 -0
  84. package/dist/esm/index.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +43 -0
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +43 -0
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/nextcloud-vue.css +449 -0
  90. package/dist/esm/store/useObjectStore.js +8 -0
  91. package/dist/esm/store/useObjectStore.js.map +1 -1
  92. package/dist/esm/utils/columnFilters.js +311 -0
  93. package/dist/esm/utils/columnFilters.js.map +1 -0
  94. package/dist/esm/utils/groupAutocomplete.js +123 -0
  95. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  96. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  97. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  98. package/dist/esm/utils/pickerOptions.js +198 -0
  99. package/dist/esm/utils/pickerOptions.js.map +1 -0
  100. package/dist/esm/utils/schema.js +83 -7
  101. package/dist/esm/utils/schema.js.map +1 -1
  102. package/dist/esm/utils/timelineEvents.js +174 -0
  103. package/dist/esm/utils/timelineEvents.js.map +1 -0
  104. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  105. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  106. package/dist/esm/utils/walkthroughSignals.js +88 -0
  107. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  108. package/dist/nextcloud-vue.cjs.js +9369 -5193
  109. package/dist/nextcloud-vue.cjs.js.map +2 -2
  110. package/dist/nextcloud-vue.css +449 -0
  111. package/l10n/en.json +43 -0
  112. package/l10n/nl.json +43 -0
  113. package/package.json +1 -1
  114. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  115. package/src/components/CnAdminActionCard/index.js +4 -0
  116. package/src/components/CnAppNav/CnAppNav.vue +271 -32
  117. package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
  118. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  119. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -2
  120. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  121. package/src/components/CnDataTable/CnDataTable.md +3 -0
  122. package/src/components/CnDataTable/CnDataTable.vue +260 -4
  123. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  124. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  125. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  126. package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
  127. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  128. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
  129. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  130. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  131. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  132. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  133. package/src/components/CnTimelineWidget/index.js +4 -0
  134. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  135. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  136. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  137. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  138. package/src/components/index.js +2 -0
  139. package/src/composables/useDependencyCheck.js +61 -0
  140. package/src/composables/useWalkthrough.js +217 -5
  141. package/src/css/dashboard.css +49 -0
  142. package/src/css/detail-page.css +19 -0
  143. package/src/css/table.css +81 -0
  144. package/src/index.js +2 -0
  145. package/src/schemas/app-manifest-v2.schema.json +23 -1
  146. package/src/store/useObjectStore.js +8 -0
  147. package/src/utils/columnFilters.js +324 -0
  148. package/src/utils/groupAutocomplete.js +119 -0
  149. package/src/utils/libraryWidgetKeys.js +2 -0
  150. package/src/utils/pickerOptions.js +194 -0
  151. package/src/utils/schema.js +83 -7
  152. package/src/utils/timelineEvents.js +171 -0
  153. package/src/utils/validateManifestV2.compiled.js +2 -2
  154. package/src/utils/walkthroughSignals.js +94 -0
@@ -509,7 +509,7 @@
509
509
  :resume="walkthroughResume"
510
510
  :translate="translate"
511
511
  @complete="onWalkthroughComplete"
512
- @dismiss="onWalkthroughComplete" />
512
+ @progress="onWalkthroughProgress" />
513
513
  </slot>
514
514
  <!--
515
515
  User-settings modal. Always mounted so descendants can
@@ -554,12 +554,27 @@
554
554
  <p class="cn-app-root__walkthrough-hint">
555
555
  {{ restartWalkthroughHint }}
556
556
  </p>
557
- <NcButton variant="secondary" @click="restartWalkthroughFromSettings">
558
- <template #icon>
559
- <Restart :size="20" />
560
- </template>
561
- {{ restartWalkthroughLabel }}
562
- </NcButton>
557
+ <div class="cn-app-root__walkthrough-actions">
558
+ <NcButton
559
+ v-if="walkthroughProgressValue"
560
+ variant="primary"
561
+ data-testid="cn-walkthrough-continue"
562
+ @click="continueWalkthroughFromSettings">
563
+ <template #icon>
564
+ <Play :size="20" />
565
+ </template>
566
+ {{ continueWalkthroughLabel }}
567
+ </NcButton>
568
+ <NcButton
569
+ variant="secondary"
570
+ data-testid="cn-walkthrough-restart"
571
+ @click="restartWalkthroughFromSettings">
572
+ <template #icon>
573
+ <Restart :size="20" />
574
+ </template>
575
+ {{ walkthroughProgressValue ? startOverWalkthroughLabel : restartWalkthroughLabel }}
576
+ </NcButton>
577
+ </div>
563
578
  </NcAppSettingsSection>
564
579
  </slot>
565
580
  <!--
@@ -638,11 +653,13 @@
638
653
  import { getCurrentUser } from '@nextcloud/auth'
639
654
  import axios from '@nextcloud/axios'
640
655
  import { loadState } from '@nextcloud/initial-state'
656
+ import { translate as t } from '@nextcloud/l10n'
641
657
  import { generateUrl } from '@nextcloud/router'
642
658
  import { NcAppContent, NcAppSettingsDialog, NcAppSettingsSection, NcButton, NcContent, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
643
659
  import { computed, reactive, shallowRef, watch } from 'vue'
644
660
  import DatabaseSearchOutline from 'vue-material-design-icons/DatabaseSearchOutline.vue'
645
661
  import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
662
+ import Play from 'vue-material-design-icons/Play.vue'
646
663
  import Restart from 'vue-material-design-icons/Restart.vue'
647
664
  import CnAiCompanion from '../CnAiCompanion/CnAiCompanion.vue'
648
665
  import CnAppLoading from '../CnAppLoading/CnAppLoading.vue'
@@ -667,9 +684,12 @@ import { useSupportDialog } from '../../composables/useSupportDialog.js'
667
684
  import { provideTenantContext } from '../../composables/useTenantContext.js'
668
685
  import { useUserPreferences } from '../../composables/useUserPreferences.js'
669
686
  import {
687
+ loadWalkthroughProgress,
670
688
  loadWalkthroughSeenVersion,
671
689
  normaliseSeenVersion,
690
+ persistWalkthroughProgress,
672
691
  persistWalkthroughSeenVersion,
692
+ readLocalWalkthroughProgress,
673
693
  readLocalWalkthroughSeenVersion,
674
694
  useWalkthrough,
675
695
  } from '../../composables/useWalkthrough.js'
@@ -749,6 +769,7 @@ export default {
749
769
  NcNoteCard,
750
770
  DatabaseSearchOutline,
751
771
  OpenInNew,
772
+ Play,
752
773
  Restart,
753
774
  CnAppNav,
754
775
  CnAppLoading,
@@ -1776,6 +1797,15 @@ export default {
1776
1797
  * @type {string}
1777
1798
  */
1778
1799
  walkthroughSeenVersionValue: readLocalWalkthroughSeenVersion(this.appId),
1800
+ /**
1801
+ * Where the user is in an unfinished tour (`{ tourId, stepId,
1802
+ * index, version }`), or null. Seeded from the local mirror, then
1803
+ * replaced by the per-user `<completionConfigKey>-progress`
1804
+ * preference. The tour resumes at this step on the next visit.
1805
+ *
1806
+ * @type {object|null}
1807
+ */
1808
+ walkthroughProgressValue: readLocalWalkthroughProgress(this.appId),
1779
1809
  /**
1780
1810
  * Whether `walkthroughSeenVersionValue` is settled. Starts `true`
1781
1811
  * when the manifest declares no `completionConfigKey` (the local
@@ -2529,13 +2559,15 @@ export default {
2529
2559
  try {
2530
2560
  const p = new URLSearchParams(window.location.search)
2531
2561
  const tourId = p.get('cn_resume_tour')
2532
- if (!tourId) {
2533
- return null
2562
+ if (tourId) {
2563
+ return { tourId, stepId: p.get('cn_resume_step') || '' }
2534
2564
  }
2535
- return { tourId, stepId: p.get('cn_resume_step') || '' }
2536
2565
  } catch {
2537
- return null
2566
+ // No URL token; fall through to the remembered progress.
2538
2567
  }
2568
+ // An unfinished tour continues at the step the user reached.
2569
+ const progress = this.walkthroughProgressValue
2570
+ return progress ? { tourId: progress.tourId, stepId: progress.stepId } : null
2539
2571
  },
2540
2572
 
2541
2573
  phase() {
@@ -2778,6 +2810,24 @@ export default {
2778
2810
  return this.translate('Restart walkthrough')
2779
2811
  },
2780
2812
 
2813
+ /**
2814
+ * Label for continuing an unfinished tour at the remembered step.
2815
+ *
2816
+ * @return {string}
2817
+ */
2818
+ continueWalkthroughLabel() {
2819
+ return t('nextcloud-vue', 'Continue where you left off')
2820
+ },
2821
+
2822
+ /**
2823
+ * Label for restarting an unfinished tour from the first step.
2824
+ *
2825
+ * @return {string}
2826
+ */
2827
+ startOverWalkthroughLabel() {
2828
+ return t('nextcloud-vue', 'Start over')
2829
+ },
2830
+
2781
2831
  /**
2782
2832
  * Resolve the active modal's Vue component from the registry.
2783
2833
  * Returns null when no modal is open or the key no longer resolves.
@@ -3281,10 +3331,12 @@ export default {
3281
3331
  return
3282
3332
  }
3283
3333
  try {
3284
- this.walkthroughSeenVersionValue = await loadWalkthroughSeenVersion(
3285
- this.appId,
3286
- this.walkthroughConfigKey,
3287
- )
3334
+ const [seen, progress] = await Promise.all([
3335
+ loadWalkthroughSeenVersion(this.appId, this.walkthroughConfigKey),
3336
+ loadWalkthroughProgress(this.appId, this.walkthroughConfigKey),
3337
+ ])
3338
+ this.walkthroughSeenVersionValue = seen
3339
+ this.walkthroughProgressValue = progress
3288
3340
  } finally {
3289
3341
  this.walkthroughSeenResolved = true
3290
3342
  }
@@ -3308,12 +3360,68 @@ export default {
3308
3360
  // Fire-and-forget: the loader never rejects and the local mirror is
3309
3361
  // already written, so a failed PUT must not surface as an error.
3310
3362
  persistWalkthroughSeenVersion(this.appId, this.walkthroughConfigKey, v)
3363
+ // A finished or skipped tour has nothing to continue.
3364
+ if (this.walkthroughProgressValue) {
3365
+ this.walkthroughProgressValue = null
3366
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, null)
3367
+ }
3311
3368
  /**
3312
3369
  * @event walkthrough-complete Emitted when the walkthrough finishes or is dismissed.
3313
3370
  */
3314
3371
  this.$emit('walkthrough-complete')
3315
3372
  },
3316
3373
 
3374
+ /**
3375
+ * Remember where the user is in the tour, so a reload or a restart
3376
+ * entry continues there (local mirror + per-user preference).
3377
+ *
3378
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
3379
+ * @param {object} progress `{ tourId, stepId, index }` from CnWalkthrough.
3380
+ * @return {void}
3381
+ */
3382
+ onWalkthroughProgress(progress) {
3383
+ if (!progress || !progress.tourId) {
3384
+ return
3385
+ }
3386
+ // The first step of a fresh tour is where a restart lands anyway,
3387
+ // so it costs no request.
3388
+ if (progress.index === 0 && !this.walkthroughProgressValue) {
3389
+ return
3390
+ }
3391
+ const value = {
3392
+ tourId: progress.tourId,
3393
+ stepId: progress.stepId || '',
3394
+ index: progress.index || 0,
3395
+ version: String((this.manifest && this.manifest.version) || ''),
3396
+ }
3397
+ this.walkthroughProgressValue = value
3398
+ persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
3399
+ },
3400
+
3401
+ /**
3402
+ * Continue an unfinished tour from the user-settings dialog: show a
3403
+ * paused tour again, or start the remembered tour at its step.
3404
+ *
3405
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
3406
+ * @return {void}
3407
+ */
3408
+ continueWalkthroughFromSettings() {
3409
+ this.userSettingsOpen = false
3410
+ if (!this.walkthroughEnabled) {
3411
+ return
3412
+ }
3413
+ setTimeout(() => {
3414
+ const wt = useWalkthrough(this.appId, this.manifest)
3415
+ if (wt.resumePaused()) {
3416
+ return
3417
+ }
3418
+ const progress = this.walkthroughProgressValue
3419
+ if (progress) {
3420
+ wt.resumeAt(progress.tourId, progress.stepId)
3421
+ }
3422
+ }, 50)
3423
+ },
3424
+
3317
3425
  /**
3318
3426
  * Replay the product tour from the user-settings dialog. Closes the
3319
3427
  * dialog first, then restarts the first declared tour on the next tick
@@ -3646,6 +3754,12 @@ export default {
3646
3754
  color: var(--color-text-maxcontrast);
3647
3755
  }
3648
3756
 
3757
+ .cn-app-root__walkthrough-actions {
3758
+ display: flex;
3759
+ flex-wrap: wrap;
3760
+ gap: 8px;
3761
+ }
3762
+
3649
3763
  .cn-app-root__integrations-hint {
3650
3764
  margin-bottom: 12px;
3651
3765
  color: var(--color-text-maxcontrast);
@@ -15,39 +15,51 @@
15
15
  </NcNoteCard>
16
16
 
17
17
  <div v-else class="cn-choice-cards__grid" :style="gridStyle">
18
- <label
18
+ <!-- One cell per option: the card's label, and below it whatever the
19
+ `#option-actions` slot adds. The slot sits OUTSIDE the label on
20
+ purpose: a button inside a label is interactive content inside
21
+ interactive content, and a click on it would toggle the radio. -->
22
+ <div
19
23
  v-for="option in normalizedOptions"
20
24
  :key="String(option.value)"
21
- class="cn-choice-cards__option"
22
- :class="{ 'cn-choice-cards__option--selected': isSelected(option) }">
23
- <!-- A REAL, VISIBLE input, not a styled div with a click handler.
24
- It carries the checked state to assistive technology and to
25
- Windows high-contrast mode for free, and it keeps the
26
- selection perceivable without colour (WCAG 1.4.1) — the
27
- card's active border alone would not be. -->
28
- <input
29
- class="cn-choice-cards__input"
30
- :type="multiple ? 'checkbox' : 'radio'"
31
- :name="groupName"
32
- :value="String(option.value)"
33
- :checked="isSelected(option)"
34
- :disabled="disabled"
35
- @change="onToggle(option)">
36
- <CnCard
37
- class="cn-choice-cards__card"
38
- :title="option.label"
39
- titleTag="span"
40
- :description="option.description"
41
- :stats="option.stats || []"
42
- :tags="option.tags || []"
43
- :active="isSelected(option)"
44
- activeVariant="primary"
45
- :descriptionLines="descriptionLines">
46
- <template v-if="option.icon" #icon>
47
- <CnIcon :name="option.icon" :size="20" />
48
- </template>
49
- </CnCard>
50
- </label>
25
+ class="cn-choice-cards__cell">
26
+ <label
27
+ class="cn-choice-cards__option"
28
+ :class="{ 'cn-choice-cards__option--selected': isSelected(option) }">
29
+ <!-- A REAL, VISIBLE input, not a styled div with a click handler.
30
+ It carries the checked state to assistive technology and to
31
+ Windows high-contrast mode for free, and it keeps the
32
+ selection perceivable without colour (WCAG 1.4.1) — the
33
+ card's active border alone would not be. -->
34
+ <input
35
+ class="cn-choice-cards__input"
36
+ :type="multiple ? 'checkbox' : 'radio'"
37
+ :name="groupName"
38
+ :value="String(option.value)"
39
+ :checked="isSelected(option)"
40
+ :disabled="disabled"
41
+ @change="onToggle(option)">
42
+ <CnCard
43
+ class="cn-choice-cards__card"
44
+ :title="option.label"
45
+ titleTag="span"
46
+ :description="option.description"
47
+ :stats="option.stats || []"
48
+ :tags="option.tags || []"
49
+ :active="isSelected(option)"
50
+ activeVariant="primary"
51
+ :descriptionLines="descriptionLines">
52
+ <template v-if="option.icon" #icon>
53
+ <CnIcon :name="option.icon" :size="20" />
54
+ </template>
55
+ </CnCard>
56
+ </label>
57
+ <div v-if="$slots['option-actions']" class="cn-choice-cards__actions">
58
+ <!-- @slot option-actions Content below one card, outside its label
59
+ (e.g. a per-option Load button). Scope: `{ option, selected }`. -->
60
+ <slot name="option-actions" :option="option" :selected="isSelected(option)" />
61
+ </div>
62
+ </div>
51
63
  </div>
52
64
  </fieldset>
53
65
  </template>
@@ -298,6 +310,19 @@ export default {
298
310
  padding: 12px;
299
311
  }
300
312
 
313
+ .cn-choice-cards__cell {
314
+ display: flex;
315
+ flex-direction: column;
316
+ gap: 8px;
317
+ min-inline-size: 0;
318
+ }
319
+
320
+ .cn-choice-cards__actions {
321
+ display: flex;
322
+ flex-direction: column;
323
+ gap: 4px;
324
+ }
325
+
301
326
  .cn-choice-cards__option {
302
327
  position: relative;
303
328
  display: block;
@@ -282,9 +282,9 @@
282
282
  <NcEmptyContent
283
283
  :name="installAppLabel(missingRequiredApp(item))"
284
284
  :description="t('nextcloud-vue', 'This widget shows data from another app that isn\'t installed yet.')"
285
- class="cn-dashboard-page__requires-app">
285
+ class="cn-dashboard-page__requires-app cn-requires-app">
286
286
  <template #icon>
287
- <Download :size="32" />
287
+ <Download :size="24" />
288
288
  </template>
289
289
  <template #action>
290
290
  <NcButton