@conduction/nextcloud-vue 2.56.0-beta.45 → 2.56.0-beta.47

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 (149) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +57 -4
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  8. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +20 -6
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -2
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +10 -39
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +5 -3
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  22. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +6 -4
  23. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
  26. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
  28. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  30. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
  31. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
  33. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  34. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
  35. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
  36. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
  37. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
  38. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
  39. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
  40. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +39 -10
  42. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +418 -399
  44. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
  46. package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
  47. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +1 -1
  48. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +156 -3
  49. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +168 -52
  51. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js +9 -0
  53. package/dist/esm/components/CnModuleCard/CnModuleCard.vue.js.map +1 -0
  54. package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js +83 -0
  55. package/dist/esm/components/CnModuleCard/CnModuleCard.vue2.js.map +1 -0
  56. package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js +119 -0
  57. package/dist/esm/components/CnModuleCard/CnModuleCard.vue3.js.map +1 -0
  58. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  59. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
  60. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
  62. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
  64. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
  65. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
  66. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
  67. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
  68. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
  69. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
  70. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
  71. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
  72. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  73. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -2
  74. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  76. package/dist/esm/components/CnTextWidget/CnTextWidget.vue.js +1 -1
  77. package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js +63 -4
  78. package/dist/esm/components/CnTextWidget/CnTextWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js +19 -2
  80. package/dist/esm/components/CnTextWidget/CnTextWidget.vue3.js.map +1 -1
  81. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
  82. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
  83. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
  84. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
  85. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
  86. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
  87. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  88. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +12 -0
  89. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  90. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  91. package/dist/esm/l10n/en.json.js +45 -1
  92. package/dist/esm/l10n/en.json.js.map +1 -1
  93. package/dist/esm/l10n/nl.json.js +44 -1
  94. package/dist/esm/l10n/nl.json.js.map +1 -1
  95. package/dist/esm/library-style.js +1 -1
  96. package/dist/esm/nextcloud-vue.css +1182 -328
  97. package/dist/esm/utils/boardDate.js +35 -1
  98. package/dist/esm/utils/boardDate.js.map +1 -1
  99. package/dist/esm/utils/featureCatalog.js +135 -0
  100. package/dist/esm/utils/featureCatalog.js.map +1 -0
  101. package/dist/esm/utils/fillTemplate.js +84 -4
  102. package/dist/esm/utils/fillTemplate.js.map +1 -1
  103. package/dist/esm/utils/headerMeta.js +62 -12
  104. package/dist/esm/utils/headerMeta.js.map +1 -1
  105. package/dist/esm/utils/roadmapIssues.js +86 -0
  106. package/dist/esm/utils/roadmapIssues.js.map +1 -0
  107. package/dist/esm/utils/userDisplayName.js +47 -0
  108. package/dist/esm/utils/userDisplayName.js.map +1 -0
  109. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  110. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  111. package/dist/nextcloud-vue.cjs.js +15618 -13097
  112. package/dist/nextcloud-vue.cjs.js.map +2 -2
  113. package/dist/nextcloud-vue.css +1182 -328
  114. package/l10n/en.json +45 -1
  115. package/l10n/nl.json +44 -1
  116. package/package.json +1 -1
  117. package/src/components/CnAppRoot/CnAppRoot.md +2 -0
  118. package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
  119. package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
  120. package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
  121. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -6
  122. package/src/components/CnDataTable/CnDataTable.vue +10 -39
  123. package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
  124. package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
  125. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
  126. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
  127. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
  128. package/src/components/CnFormDialog/CnFormDialog.md +1 -0
  129. package/src/components/CnFormDialog/CnFormDialog.vue +619 -562
  130. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +338 -5
  131. package/src/components/CnModuleCard/CnModuleCard.vue +258 -0
  132. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
  133. package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
  134. package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
  135. package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -2
  136. package/src/components/CnTextWidget/CnTextWidget.vue +89 -4
  137. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
  138. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +12 -0
  139. package/src/css/dialog.css +47 -0
  140. package/src/css/look-board-detail.css +8 -0
  141. package/src/css/look-board.css +33 -0
  142. package/src/schemas/app-manifest-v2.schema.json +67 -4
  143. package/src/utils/boardDate.js +34 -0
  144. package/src/utils/featureCatalog.js +132 -0
  145. package/src/utils/fillTemplate.js +83 -3
  146. package/src/utils/headerMeta.js +62 -12
  147. package/src/utils/roadmapIssues.js +82 -0
  148. package/src/utils/userDisplayName.js +54 -0
  149. package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json CHANGED
@@ -499,6 +499,7 @@
499
499
  "NC Activity is currently unavailable.": "NC Activity is currently unavailable.",
500
500
  "Could not load activity.": "Could not load activity.",
501
501
  "Today": "Today",
502
+ "Today, {date}": "Today, {date}",
502
503
  "Last 8 hours": "Last 8 hours",
503
504
  "Last 24 hours": "Last 24 hours",
504
505
  "Last 7 days": "Last 7 days",
@@ -1473,7 +1474,50 @@
1473
1474
  "Link related object": "Link related object",
1474
1475
  "{type} parts": "{type} parts",
1475
1476
  "Cannot save: this record has no id, so the edit would create a duplicate instead of updating it.": "Cannot save: this record has no id, so the edit would create a duplicate instead of updating it.",
1476
- "Save failed": "Save failed"
1477
+ "Save failed": "Save failed",
1478
+ "Modules": "Modules",
1479
+ "In the menu": "In the menu",
1480
+ "Off": "Off",
1481
+ "Pages": "Pages",
1482
+ "Open {module}": "Open {module}",
1483
+ "{list} and {last}": "{list} and {last}",
1484
+ "%n page: {pages}": [
1485
+ "%n page: {pages}",
1486
+ "%n pages: {pages}"
1487
+ ],
1488
+ "%n page, including {pages}": [
1489
+ "%n page, including {pages}",
1490
+ "%n pages, including {pages}"
1491
+ ],
1492
+ "{on} of %n is in the menu": [
1493
+ "{on} of %n is in the menu",
1494
+ "{on} of %n are in the menu"
1495
+ ],
1496
+ "Put it in the menu:": "Put it in the menu:",
1497
+ "Admin settings": "Admin settings",
1498
+ "Support this app": "Support this app",
1499
+ "No features with this status": "No features with this status",
1500
+ "Learn more about {title}": "Learn more about {title}",
1501
+ "Learn more": "Learn more",
1502
+ "Next on the roadmap": "Next on the roadmap",
1503
+ "Coming next": "Coming next",
1504
+ "The most requested items from the issue tracker.": "The most requested items from the issue tracker.",
1505
+ "Opened {date}": "Opened {date}",
1506
+ "See the whole roadmap": "See the whole roadmap",
1507
+ "Features and roadmap": "Features and roadmap",
1508
+ "Beta": "Beta",
1509
+ "Planned": "Planned",
1510
+ "{status}, {expected}": "{status}, {expected}",
1511
+ "Build on this app": "Build on this app",
1512
+ "AI skills": "AI skills",
1513
+ "%n feature": [
1514
+ "%n feature",
1515
+ "%n features"
1516
+ ],
1517
+ "Your account": "Your account",
1518
+ "Only for you. Colleagues keep their own settings.": "Only for you. Colleagues keep their own settings.",
1519
+ "Sections of the settings": "Sections of the settings",
1520
+ "Changes are saved straight away": "Changes are saved straight away"
1477
1521
  },
1478
1522
  "plurals": {
1479
1523
  "{count} selected": [
package/l10n/nl.json CHANGED
@@ -521,6 +521,7 @@
521
521
  "NC Activity is currently unavailable.": "NC Activiteit is momenteel niet beschikbaar.",
522
522
  "Could not load activity.": "Kon activiteit niet laden.",
523
523
  "Today": "Vandaag",
524
+ "Today, {date}": "Vandaag, {date}",
524
525
  "Last 8 hours": "Laatste 8 uur",
525
526
  "Last 24 hours": "Laatste 24 uur",
526
527
  "Last 7 days": "Laatste 7 dagen",
@@ -1660,7 +1661,49 @@
1660
1661
  "Link related object": "Gerelateerd object koppelen",
1661
1662
  "{type} parts": "Onderdelen van {type}",
1662
1663
  "Cannot save: this record has no id, so the edit would create a duplicate instead of updating it.": "Opslaan kan niet: dit record heeft geen id, dus de wijziging zou een kopie maken in plaats van het record bij te werken.",
1663
- "Save failed": "Opslaan is mislukt"
1664
+ "Save failed": "Opslaan is mislukt",
1665
+ "Modules": "Modules",
1666
+ "In the menu": "In het menu",
1667
+ "Off": "Uit",
1668
+ "Pages": "Pagina's",
1669
+ "Open {module}": "{module} openen",
1670
+ "{list} and {last}": "{list} en {last}",
1671
+ "%n page: {pages}": [
1672
+ "%n pagina: {pages}",
1673
+ "%n pagina's: {pages}"
1674
+ ],
1675
+ "%n page, including {pages}": [
1676
+ "%n pagina, waaronder {pages}",
1677
+ "%n pagina's, waaronder {pages}"
1678
+ ],
1679
+ "{on} of %n is in the menu": [
1680
+ "{on} van %n staat in het menu",
1681
+ "{on} van %n staan in het menu"
1682
+ ],
1683
+ "Put it in the menu:": "In het menu zetten:",
1684
+ "Support this app": "Steun deze app",
1685
+ "No features with this status": "Geen functies met deze status",
1686
+ "Learn more about {title}": "Lees meer over {title}",
1687
+ "Learn more": "Lees meer",
1688
+ "Next on the roadmap": "Hierna op de roadmap",
1689
+ "Coming next": "Hierna",
1690
+ "The most requested items from the issue tracker.": "De meest gevraagde items uit de issuetracker.",
1691
+ "Opened {date}": "Geopend {date}",
1692
+ "See the whole roadmap": "Bekijk de hele roadmap",
1693
+ "Features and roadmap": "Functies en roadmap",
1694
+ "Beta": "Beta",
1695
+ "Planned": "Gepland",
1696
+ "{status}, {expected}": "{status}, {expected}",
1697
+ "Build on this app": "Bouw verder op deze app",
1698
+ "AI skills": "AI-vaardigheden",
1699
+ "%n feature": [
1700
+ "%n functie",
1701
+ "%n functies"
1702
+ ],
1703
+ "Your account": "Uw account",
1704
+ "Only for you. Colleagues keep their own settings.": "Alleen voor jou. Collega's houden hun eigen instellingen.",
1705
+ "Sections of the settings": "Onderdelen van de instellingen",
1706
+ "Changes are saved straight away": "Wijzigingen worden meteen bewaard"
1664
1707
  },
1665
1708
  "plurals": {
1666
1709
  "{count} selected": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.56.0-beta.45",
3
+ "version": "2.56.0-beta.47",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -85,6 +85,8 @@ With loading state, custom components, translate, permissions, and custom page t
85
85
  | `permissions` | Array | No | `[]` | Permission strings for the current user; forwarded to `CnAppNav` for menu filtering |
86
86
  | `pageTypes` | Object | No | `null` | Page-type registry map (`type → component`). Falls back to the library's `defaultPageTypes`. Extend with `{ ...defaultPageTypes, report: MyReportPage }` |
87
87
  | `userSettingsTitle` | String | No | `''` | Title shown at the top of the hosted `NcAppSettingsDialog`. Empty (the default) resolves to "User settings" from the library catalogue ("Persoonlijke instellingen" in Dutch), then the app's `translate`. Override per app to brand the modal (e.g. `'Decidiq preferences'`). |
88
+ | `userSettingsEyebrow` | String | No | `''` | Board look: the line above the user-settings title. Empty uses "Your account" ("Uw account"). Goes through the app's `translate`. |
89
+ | `userSettingsSubtitle` | String | No | `''` | Board look: the sentence under the user-settings title. Empty uses "Only for you. Colleagues keep their own settings." Goes through the app's `translate`. |
88
90
  | `userSettingsSections` | Array | No | `null` | The user-settings sections to draw, in order: `app` (the app's slot content), `notifications`, `credentials`, `walkthrough`, `integrations`. A key left out is not drawn. `null` keeps the defaults: every section after `#user-settings-sections` content, or only the `#user-settings` content and the integrations section. |
89
91
  | `adminSettingsTitle` | String | No | `''` | Title shown at the top of the admin-settings `NcAppSettingsDialog`. Empty (the default) resolves to `translate('Administration')`. Override per app (e.g. `'Pipelinq administration'`). |
90
92
  | `requiresApps` | Array | No | `['openregister']` | App ids that MUST be installed for the host app to function. Checked against the OCS capabilities API on mount. When any required app is missing, CnAppRoot renders the empty state from the `or-missing` slot (default: `<NcEmptyContent>`) instead of the renderer. Pass `[]` to opt out (e.g. launchpad, the docs/styleguide app) — useful for any consumer that does not depend on OpenRegister. |
@@ -550,10 +550,9 @@
550
550
  integrations section are drawn, unless `userSettingsSections`
551
551
  names more.
552
552
  -->
553
- <NcAppSettingsDialog
554
- :open="userSettingsOpen"
555
- :showNavigation="true"
556
- :name="resolvedUserSettingsTitle"
553
+ <component
554
+ :is="resolvedLook === 'board' ? 'CnUserSettingsDialog' : 'NcAppSettingsDialog'"
555
+ v-bind="userSettingsDialogProps"
557
556
  @update:open="userSettingsOpen = $event">
558
557
  <template v-for="section in userSettingsSectionOrder()" :key="section">
559
558
  <template v-if="section === 'app'">
@@ -573,6 +572,9 @@
573
572
  <NcAppSettingsSection v-else-if="section === 'credentials' && userSettingsOpen"
574
573
  id="credentials"
575
574
  :name="libraryCopy('Credentials')">
575
+ <template v-if="resolvedLook === 'board'" #icon>
576
+ <KeyOutline :size="18" />
577
+ </template>
576
578
  <CnCredentials
577
579
  scope="personal"
578
580
  :appId="appId"
@@ -587,6 +589,9 @@
587
589
  <NcAppSettingsSection v-else-if="section === 'walkthrough' && walkthroughEnabled"
588
590
  id="cn-walkthrough"
589
591
  :name="restartWalkthroughSectionName">
592
+ <template v-if="resolvedLook === 'board'" #icon>
593
+ <Play :size="18" />
594
+ </template>
590
595
  <p class="cn-app-root__walkthrough-hint">
591
596
  {{ restartWalkthroughHint }}
592
597
  </p>
@@ -628,6 +633,9 @@
628
633
  <NcAppSettingsSection v-else-if="section === 'integrations' && userSettingsOpen && integrationEntries.length"
629
634
  id="cn-integrations"
630
635
  :name="translate('Integrations')">
636
+ <template v-if="resolvedLook === 'board'" #icon>
637
+ <OpenInNew :size="18" />
638
+ </template>
631
639
  <p class="cn-app-root__integrations-hint">
632
640
  {{ libraryCopy('These open in another app.') }}
633
641
  </p>
@@ -653,7 +661,7 @@
653
661
  </ul>
654
662
  </NcAppSettingsSection>
655
663
  </template>
656
- </NcAppSettingsDialog>
664
+ </component>
657
665
 
658
666
  <!--
659
667
  ADR-079 Step 2: the generic admin-settings modal has been REMOVED.
@@ -693,6 +701,7 @@ import { generateUrl } from '@nextcloud/router'
693
701
  import { NcAppContent, NcAppSettingsDialog, NcAppSettingsSection, NcButton, NcContent, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
694
702
  import { computed, reactive, shallowRef, watch } from 'vue'
695
703
  import DatabaseSearchOutline from 'vue-material-design-icons/DatabaseSearchOutline.vue'
704
+ import KeyOutline from 'vue-material-design-icons/KeyOutline.vue'
696
705
  import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
697
706
  import Play from 'vue-material-design-icons/Play.vue'
698
707
  import Restart from 'vue-material-design-icons/Restart.vue'
@@ -709,6 +718,7 @@ import CnObjectSidebar from '../CnObjectSidebar/CnObjectSidebar.vue'
709
718
  import CnSetupWizard from '../CnSetupWizard/CnSetupWizard.vue'
710
719
  import CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'
711
720
  import CnTenantBadge from '../CnTenantBadge/CnTenantBadge.vue'
721
+ import CnUserSettingsDialog from '../CnUserSettingsDialog/CnUserSettingsDialog.vue'
712
722
  import CnWalkthrough from '../CnWalkthrough/CnWalkthrough.vue'
713
723
  import { DEFAULT_CHAT_APP_ID } from '../../composables/aiChatConfig.js'
714
724
  import { useAppInstaller } from '../../composables/useAppInstaller.js'
@@ -818,6 +828,7 @@ export default {
818
828
  NcLoadingIcon,
819
829
  NcNoteCard,
820
830
  DatabaseSearchOutline,
831
+ KeyOutline,
821
832
  OpenInNew,
822
833
  Play,
823
834
  Restart,
@@ -832,6 +843,7 @@ export default {
832
843
  CnObjectSidebar,
833
844
  CnSupportDialog,
834
845
  CnNotificationPreferences,
846
+ CnUserSettingsDialog,
835
847
  CnCredentials,
836
848
  CnTenantBadge,
837
849
  },
@@ -1648,6 +1660,29 @@ export default {
1648
1660
  default: '',
1649
1661
  },
1650
1662
 
1663
+ /**
1664
+ * Board look: the line above the user-settings title. Empty uses
1665
+ * the library's "Your account".
1666
+ *
1667
+ * @type {string}
1668
+ */
1669
+ userSettingsEyebrow: {
1670
+ type: String,
1671
+ default: '',
1672
+ },
1673
+
1674
+ /**
1675
+ * Board look: the sentence under the user-settings title. Empty
1676
+ * uses the library's "Only for you. Colleagues keep their own
1677
+ * settings."
1678
+ *
1679
+ * @type {string}
1680
+ */
1681
+ userSettingsSubtitle: {
1682
+ type: String,
1683
+ default: '',
1684
+ },
1685
+
1651
1686
  /**
1652
1687
  * The sections of the user-settings modal, in the order they are
1653
1688
  * drawn. Keys: `app` (the content of the `#user-settings-sections`
@@ -3065,6 +3100,32 @@ export default {
3065
3100
  return this.userSettingsTitle || this.libraryCopy('User settings')
3066
3101
  },
3067
3102
 
3103
+ /**
3104
+ * The props of the user-settings dialog: `NcAppSettingsDialog`'s, or
3105
+ * under the board look CnUserSettingsDialog's (eyebrow, subtitle and
3106
+ * the app's name for the footer note).
3107
+ *
3108
+ * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look
3109
+ * @return {object}
3110
+ */
3111
+ userSettingsDialogProps() {
3112
+ if (this.resolvedLook !== 'board') {
3113
+ return { open: this.userSettingsOpen, showNavigation: true, name: this.resolvedUserSettingsTitle }
3114
+ }
3115
+ const props = {
3116
+ open: this.userSettingsOpen,
3117
+ title: this.resolvedUserSettingsTitle,
3118
+ appName: this.appDisplayName || (this.manifest && this.manifest.name) || this.appId || '',
3119
+ }
3120
+ if (this.userSettingsEyebrow) {
3121
+ props.eyebrow = this.translate(this.userSettingsEyebrow)
3122
+ }
3123
+ if (this.userSettingsSubtitle) {
3124
+ props.subtitle = this.translate(this.userSettingsSubtitle)
3125
+ }
3126
+ return props
3127
+ },
3128
+
3068
3129
  /**
3069
3130
  * Title for the admin-settings modal. Prop override, else the
3070
3131
  * translated "Administration". Mirrors `resolvedUserSettingsTitle`.
@@ -61,3 +61,30 @@ four lines `description-lines` shows by default:
61
61
  ]" />
62
62
  </div>
63
63
  ```
64
+
65
+ One option per row with `layout="rows"`, an option that cannot be picked yet
66
+ (`disabled` with a `disabledReason`), and fields for the chosen option right
67
+ under it through the `option-actions` slot (scope `{ option, selected }`):
68
+
69
+ ```vue
70
+ <div style="max-width: 560px;">
71
+ <CnChoiceCards
72
+ label="Next step"
73
+ layout="rows"
74
+ :model-value="'decide'"
75
+ :options="[
76
+ { value: 'decide', label: 'Take a decision',
77
+ description: 'Record the decision and inform the applicant.' },
78
+ { value: 'advice', label: 'Ask for advice',
79
+ description: 'Send the case to the advisory committee.' },
80
+ { value: 'close', label: 'Close the case', disabled: true,
81
+ disabledReason: 'A decision has to be taken first.' },
82
+ ]">
83
+ <template #option-actions="{ option, selected }">
84
+ <p v-if="selected && option.value === 'decide'" style="margin: 0 0 0 32px;">Decision fields go here.</p>
85
+ </template>
86
+ </CnChoiceCards>
87
+ </div>
88
+ ```
89
+
90
+ `columns` fixes the card grid's column count; `:columns="1"` stacks the cards in one column.
@@ -14,6 +14,45 @@
14
14
  {{ emptyText }}
15
15
  </NcNoteCard>
16
16
 
17
+ <!-- Rows: one full-width option per row, the input on the left and the
18
+ title over a muted description, no card chrome. -->
19
+ <div v-else-if="layout === 'rows'" class="cn-choice-cards__rows" data-testid="cn-choice-cards-rows">
20
+ <div
21
+ v-for="option in normalizedOptions"
22
+ :key="String(option.value)"
23
+ class="cn-choice-cards__cell">
24
+ <label
25
+ class="cn-choice-cards__row"
26
+ :class="{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option) }">
27
+ <input
28
+ class="cn-choice-cards__row-input"
29
+ :type="multiple ? 'checkbox' : 'radio'"
30
+ :name="groupName"
31
+ :value="String(option.value)"
32
+ :checked="isSelected(option)"
33
+ :disabled="isDisabled(option)"
34
+ :aria-describedby="option.disabled && option.disabledReason ? reasonId(option) : null"
35
+ @change="onToggle(option)">
36
+ <CnIcon v-if="option.icon"
37
+ :name="option.icon"
38
+ :size="20"
39
+ class="cn-choice-cards__row-icon" />
40
+ <span class="cn-choice-cards__row-text">
41
+ <span class="cn-choice-cards__row-title">{{ option.label }}</span>
42
+ <span v-if="option.description" class="cn-choice-cards__row-description">{{ option.description }}</span>
43
+ <span
44
+ v-if="option.disabled && option.disabledReason"
45
+ :id="reasonId(option)"
46
+ class="cn-choice-cards__reason"
47
+ data-testid="cn-choice-cards-reason">{{ option.disabledReason }}</span>
48
+ </span>
49
+ </label>
50
+ <div v-if="$slots['option-actions']" class="cn-choice-cards__actions">
51
+ <slot name="option-actions" :option="option" :selected="isSelected(option)" />
52
+ </div>
53
+ </div>
54
+ </div>
55
+
17
56
  <div v-else class="cn-choice-cards__grid" :style="gridStyle">
18
57
  <!-- One cell per option: the card's label, and below it whatever the
19
58
  `#option-actions` slot adds. The slot sits OUTSIDE the label on
@@ -37,7 +76,8 @@
37
76
  :name="groupName"
38
77
  :value="String(option.value)"
39
78
  :checked="isSelected(option)"
40
- :disabled="disabled"
79
+ :disabled="isDisabled(option)"
80
+ :aria-describedby="option.disabled && option.disabledReason ? reasonId(option) : null"
41
81
  @change="onToggle(option)">
42
82
  <CnCard
43
83
  class="cn-choice-cards__card"
@@ -53,6 +93,11 @@
53
93
  <CnIcon :name="option.icon" :size="20" />
54
94
  </template>
55
95
  </CnCard>
96
+ <span
97
+ v-if="option.disabled && option.disabledReason"
98
+ :id="reasonId(option)"
99
+ class="cn-choice-cards__reason cn-choice-cards__reason--card"
100
+ data-testid="cn-choice-cards-reason">{{ option.disabledReason }}</span>
56
101
  </label>
57
102
  <div v-if="$slots['option-actions']" class="cn-choice-cards__actions">
58
103
  <!-- @slot option-actions Content below one card, outside its label
@@ -109,7 +154,9 @@ export default {
109
154
  props: {
110
155
  /**
111
156
  * The options to offer. Each: `{ value, label, description?, stats?,
112
- * tags?, icon? }` — `icon` is a PascalCase MDI name resolved by CnIcon.
157
+ * tags?, icon?, disabled?, disabledReason? }` — `icon` is a PascalCase
158
+ * MDI name resolved by CnIcon; a `disabled` option is greyed out and
159
+ * cannot be picked, with `disabledReason` shown under it.
113
160
  */
114
161
  options: {
115
162
  type: Array,
@@ -156,6 +203,23 @@ export default {
156
203
  default: () => t('nextcloud-vue', 'Nothing to choose from here.'),
157
204
  },
158
205
 
206
+ /**
207
+ * `cards`: a grid of cards with stats and tags. `rows`: one option per
208
+ * row, full width, the input on the left and the title over a muted
209
+ * description; with `multiple` and one option it is a checkbox card.
210
+ */
211
+ layout: {
212
+ type: String,
213
+ default: 'cards',
214
+ validator: (value) => ['cards', 'rows'].includes(value),
215
+ },
216
+
217
+ /** Columns of the card grid; 0 lets the grid fit as many as the width allows. */
218
+ columns: {
219
+ type: Number,
220
+ default: 0,
221
+ },
222
+
159
223
  /** Lines of description shown before clamping. */
160
224
  descriptionLines: {
161
225
  type: Number,
@@ -226,6 +290,9 @@ export default {
226
290
  * @return {object} A `gridTemplateColumns` override, or `{}`.
227
291
  */
228
292
  gridStyle() {
293
+ if (this.columns > 0) {
294
+ return { gridTemplateColumns: `repeat(${this.columns}, minmax(0, 1fr))` }
295
+ }
229
296
  const count = this.normalizedOptions.length
230
297
  if (count > 0 && count <= 2) {
231
298
  return { gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }
@@ -244,6 +311,26 @@ export default {
244
311
  * @param {object} option The normalised option.
245
312
  * @return {boolean} True when selected.
246
313
  */
314
+ /**
315
+ * Whether an option cannot be picked: the whole group is disabled, or the option is.
316
+ *
317
+ * @param {object} option The normalised option.
318
+ * @return {boolean} True when disabled.
319
+ */
320
+ isDisabled(option) {
321
+ return this.disabled || option.disabled === true
322
+ },
323
+
324
+ /**
325
+ * Id of an option's disabled reason, for `aria-describedby`.
326
+ *
327
+ * @param {object} option The normalised option.
328
+ * @return {string} The id.
329
+ */
330
+ reasonId(option) {
331
+ return `${this.groupName}-reason-${String(option.value).replace(/[^A-Za-z0-9_-]/g, '-')}`
332
+ },
333
+
247
334
  isSelected(option) {
248
335
  return this.selectedValues.some((v) => String(v) === String(option.value))
249
336
  },
@@ -255,7 +342,7 @@ export default {
255
342
  * @return {void}
256
343
  */
257
344
  onToggle(option) {
258
- if (this.disabled) {
345
+ if (this.isDisabled(option)) {
259
346
  return
260
347
  }
261
348
  if (!this.multiple) {
@@ -368,4 +455,82 @@ export default {
368
455
  .cn-choice-cards__input:disabled + :deep(.cn-card) {
369
456
  opacity: 0.6;
370
457
  }
458
+
459
+ .cn-choice-cards__rows {
460
+ display: flex;
461
+ flex-direction: column;
462
+ gap: 8px;
463
+ }
464
+
465
+ .cn-choice-cards__row {
466
+ display: flex;
467
+ align-items: flex-start;
468
+ gap: 12px;
469
+ padding: 14px 16px;
470
+ border: 1px solid var(--color-border);
471
+ border-radius: var(--border-radius-large, 8px);
472
+ background: var(--color-main-background);
473
+ cursor: pointer;
474
+ }
475
+
476
+ .cn-choice-cards__row:hover {
477
+ border-color: var(--color-border-dark);
478
+ }
479
+
480
+ .cn-choice-cards__row--selected,
481
+ .cn-choice-cards__row--selected:hover {
482
+ border-color: var(--color-primary-element);
483
+ /* The second pixel of the 2px selected border, without moving the content. */
484
+ box-shadow: inset 0 0 0 1px var(--color-primary-element);
485
+ background: var(--color-primary-element-light);
486
+ }
487
+
488
+ .cn-choice-cards__row--disabled {
489
+ cursor: default;
490
+ opacity: 0.6;
491
+ }
492
+
493
+ .cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {
494
+ outline: 2px solid var(--color-primary-element);
495
+ outline-offset: 2px;
496
+ }
497
+
498
+ .cn-choice-cards__row-input {
499
+ flex: none;
500
+ margin: 2px 0 0;
501
+ accent-color: var(--color-primary-element);
502
+ cursor: inherit;
503
+ }
504
+
505
+ .cn-choice-cards__row-icon {
506
+ flex: none;
507
+ }
508
+
509
+ .cn-choice-cards__row-text {
510
+ display: flex;
511
+ flex-direction: column;
512
+ gap: 2px;
513
+ min-width: 0;
514
+ }
515
+
516
+ .cn-choice-cards__row-title {
517
+ font-size: 15px;
518
+ font-weight: 600;
519
+ }
520
+
521
+ .cn-choice-cards__row-description {
522
+ font-size: 13px;
523
+ color: var(--color-text-maxcontrast);
524
+ }
525
+
526
+ .cn-choice-cards__reason {
527
+ font-size: 13px;
528
+ color: var(--color-text-maxcontrast);
529
+ font-style: italic;
530
+ }
531
+
532
+ .cn-choice-cards__reason--card {
533
+ display: block;
534
+ margin-block-start: 4px;
535
+ }
371
536
  </style>
@@ -13,6 +13,7 @@
13
13
  <div
14
14
  class="cn-dashboard-page"
15
15
  :class="{ 'cn-dashboard-page--board': isBoardLook }"
16
+ :style="{ '--cn-dashboard-grid-margin': `${gridMargin}px` }"
16
17
  data-testid="cn-dashboard-page">
17
18
  <!-- Header. `showHeader: false` drops the whole row (title, description,
18
19
  header actions, edit toggle) for a page that opens with its own
@@ -1252,12 +1253,16 @@ export default {
1252
1253
  * October 2026", `"full"` uses the whole display name. The page
1253
1254
  * `description`, when set, follows after another middle dot. Off by
1254
1255
  * default: the subtitle is the description alone, as before.
1256
+ * `"date"` leads with the date alone, and `{ label }` puts the
1257
+ * translated label where the greeting would be ("Customer contact ·
1258
+ * Wednesday 7 October 2026 · ...").
1255
1259
  *
1256
1260
  * @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
1257
- * @type {boolean|string}
1261
+ * @spec openspec/changes/dashboard-greeting-label/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-lead-with-a-label-or-the-date
1262
+ * @type {boolean|string|object}
1258
1263
  */
1259
1264
  greeting: {
1260
- type: [Boolean, String],
1265
+ type: [Boolean, String, Object],
1261
1266
  default: false,
1262
1267
  },
1263
1268
 
@@ -1992,6 +1997,10 @@ export default {
1992
1997
  * @return {string}
1993
1998
  */
1994
1999
  greetingLine() {
2000
+ if (this.greeting && typeof this.greeting === 'object') {
2001
+ const label = typeof this.greeting.label === 'string' ? this.greeting.label.trim() : ''
2002
+ return label ? this.effectiveTranslate(label) : ''
2003
+ }
1995
2004
  if (this.greeting !== true && this.greeting !== 'first' && this.greeting !== 'full') {
1996
2005
  return ''
1997
2006
  }
@@ -2014,17 +2023,20 @@ export default {
2014
2023
  },
2015
2024
 
2016
2025
  /**
2017
- * The header subtitle: with `greeting` on, the greeting, today's date
2018
- * written out and the description, joined by a middle dot; else the
2019
- * description alone, as before.
2026
+ * The header subtitle: with `greeting` on, the greeting (or the
2027
+ * label), today's date written out and the description, joined by a
2028
+ * middle dot; with `greeting: "date"` the date leads, capitalised;
2029
+ * else the description alone, as before.
2020
2030
  *
2021
2031
  * @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
2032
+ * @spec openspec/changes/dashboard-greeting-label/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-lead-with-a-label-or-the-date
2022
2033
  * @return {string}
2023
2034
  */
2024
2035
  subtitleText() {
2025
2036
  const description = this.description ? this.resolvedDescription : ''
2037
+ const dateOnly = this.greeting === 'date'
2026
2038
  const greeting = this.greetingLine
2027
- if (greeting === '') {
2039
+ if (greeting === '' && !dateOnly) {
2028
2040
  return description
2029
2041
  }
2030
2042
  const options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }
@@ -2034,6 +2046,9 @@ export default {
2034
2046
  } catch {
2035
2047
  date = new Intl.DateTimeFormat(undefined, options).format(new Date())
2036
2048
  }
2049
+ if (dateOnly) {
2050
+ date = date.charAt(0).toLocaleUpperCase() + date.slice(1)
2051
+ }
2037
2052
  return [greeting, date, description].filter((part) => part !== '').join(' · ')
2038
2053
  },
2039
2054
 
@@ -4899,6 +4914,15 @@ export default {
4899
4914
  margin-bottom: var(--cn-board-section-gap, 16px);
4900
4915
  }
4901
4916
 
4917
+ /* Board look: GridStack insets every card by the grid margin on each side, so
4918
+ the cards started a margin to the right of the KPI row and the header. The
4919
+ grid reaches out by that margin, so the card edges line up with the page's
4920
+ (dashboard-grid-flush). */
4921
+ .cn-dashboard-page--board :deep(.cn-dashboard-grid) {
4922
+ width: auto;
4923
+ margin-inline: calc(-1 * var(--cn-dashboard-grid-margin, 12px));
4924
+ }
4925
+
4902
4926
  .cn-dashboard-page__kpi-cell {
4903
4927
  display: flex;
4904
4928
  min-width: 0;
@@ -5129,6 +5153,21 @@ export default {
5129
5153
  .cn-dashboard-page--board .cn-dashboard-page__kpi-cell {
5130
5154
  min-height: 44px;
5131
5155
  }
5156
+
5157
+ /* The widget Actions menu shows its icon only, so the card title keeps
5158
+ the room; the name stays for assistive tech. */
5159
+ .cn-dashboard-page--board :deep(.cn-widget-wrapper__actions .action-item__menutoggle) {
5160
+ min-width: 44px;
5161
+ }
5162
+
5163
+ .cn-dashboard-page--board :deep(.cn-widget-wrapper__actions .action-item__menutoggle .button-vue__text) {
5164
+ position: absolute;
5165
+ width: 1px;
5166
+ height: 1px;
5167
+ overflow: hidden;
5168
+ clip-path: inset(50%);
5169
+ white-space: nowrap;
5170
+ }
5132
5171
  }
5133
5172
 
5134
5173
  /* What a registry widget reports for its card header (`header` event): the