@conduction/nextcloud-vue 2.104.0 → 2.106.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 (224) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  6. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
  7. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
  9. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  15. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  16. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
  17. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +112 -5
  25. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
  28. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
  29. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
  30. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
  35. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  42. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
  43. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  46. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
  47. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
  49. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  51. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
  52. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
  54. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  56. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
  57. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  60. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
  61. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  62. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
  63. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +208 -46
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
  72. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  73. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +25 -6
  74. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  75. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
  76. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
  78. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  79. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  80. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
  81. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
  83. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  84. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
  85. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
  86. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
  87. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
  88. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
  89. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
  90. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  91. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
  92. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
  94. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  95. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  96. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
  97. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  98. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
  99. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  100. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  101. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
  102. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  103. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  105. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
  106. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
  107. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  108. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
  109. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  111. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  112. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
  113. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  114. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  115. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  116. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
  117. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  118. package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
  119. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  120. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  121. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  122. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
  123. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
  125. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  126. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
  127. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
  129. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
  131. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  133. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
  134. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  135. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
  136. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  137. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  138. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  139. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  140. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  141. package/dist/esm/index.js +2 -0
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/l10n/en.json.js +24 -1
  144. package/dist/esm/l10n/en.json.js.map +1 -1
  145. package/dist/esm/l10n/nl.json.js +24 -1
  146. package/dist/esm/l10n/nl.json.js.map +1 -1
  147. package/dist/esm/library-style.js +1 -1
  148. package/dist/esm/nextcloud-vue.css +1369 -748
  149. package/dist/esm/utils/dueText.js +18 -3
  150. package/dist/esm/utils/dueText.js.map +1 -1
  151. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  152. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  153. package/dist/esm/utils/nextcloudApps.js +45 -0
  154. package/dist/esm/utils/nextcloudApps.js.map +1 -0
  155. package/dist/esm/utils/scopeListLayout.js +9 -2
  156. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  157. package/dist/esm/utils/sortOrder.js +11 -1
  158. package/dist/esm/utils/sortOrder.js.map +1 -1
  159. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  160. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  161. package/dist/esm/utils/visibleWhen.js +12 -4
  162. package/dist/esm/utils/visibleWhen.js.map +1 -1
  163. package/dist/nextcloud-vue.cjs.js +8965 -6497
  164. package/dist/nextcloud-vue.cjs.js.map +2 -2
  165. package/dist/nextcloud-vue.css +1369 -748
  166. package/l10n/en.json +24 -1
  167. package/l10n/nl.json +24 -1
  168. package/package.json +1 -1
  169. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
  170. package/src/components/CnCardGrid/CnCardGrid.md +2 -0
  171. package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
  172. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
  173. package/src/components/CnChartWidget/CnChartWidget.md +2 -0
  174. package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
  175. package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
  176. package/src/components/CnDetailPage/CnDetailPage.md +3 -2
  177. package/src/components/CnDetailPage/CnDetailPage.vue +112 -5
  178. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
  179. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
  180. package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
  181. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
  182. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
  183. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
  184. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
  185. package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
  186. package/src/components/CnIndexPage/CnIndexPage.md +8 -5
  187. package/src/components/CnIndexPage/CnIndexPage.vue +236 -54
  188. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
  189. package/src/components/CnIndexPage/useSelfFetchList.js +25 -6
  190. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  191. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
  192. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
  193. package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
  194. package/src/components/CnNoticeWidget/index.js +12 -0
  195. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  196. package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
  197. package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
  198. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
  199. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
  200. package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
  201. package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
  202. package/src/components/CnTabs/CnTabs.vue +11 -0
  203. package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
  204. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
  205. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
  206. package/src/components/CnTeamWorkloadWidget/index.js +14 -0
  207. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
  208. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  209. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  210. package/src/components/index.js +2 -0
  211. package/src/css/dialog.css +20 -2
  212. package/src/css/index-page.css +14 -2
  213. package/src/css/look-board-detail.css +125 -8
  214. package/src/css/look-board-index.css +100 -4
  215. package/src/css/look-board.css +122 -14
  216. package/src/index.js +2 -1
  217. package/src/schemas/app-manifest-v2.schema.json +110 -8
  218. package/src/utils/dueText.js +18 -3
  219. package/src/utils/libraryWidgetKeys.js +2 -0
  220. package/src/utils/nextcloudApps.js +50 -0
  221. package/src/utils/scopeListLayout.js +9 -2
  222. package/src/utils/sortOrder.js +10 -0
  223. package/src/utils/validateManifestV2.compiled.js +3 -3
  224. package/src/utils/visibleWhen.js +12 -4
@@ -33,6 +33,31 @@
33
33
  {{ noticeText }}
34
34
  </p>
35
35
  </div>
36
+ <!-- table: one row per record under a header, one cell per `columns`
37
+ entry; a cell past its `alertAbove` reads bold in the danger colour. -->
38
+ <table v-else-if="presentation === 'table'" class="cn-record-list__table" data-testid="cn-record-list-table">
39
+ <thead>
40
+ <tr>
41
+ <th
42
+ v-for="col in tableColumns"
43
+ :key="col.key"
44
+ scope="col"
45
+ :class="{ 'cn-record-list__cell--end': col.align === 'end' }">
46
+ {{ col.label }}
47
+ </th>
48
+ </tr>
49
+ </thead>
50
+ <tbody>
51
+ <tr v-for="item in tableRows" :key="item.key" data-testid="cn-record-list-item">
52
+ <td
53
+ v-for="cell in item.cells"
54
+ :key="cell.key"
55
+ :class="{ 'cn-record-list__cell--end': cell.align === 'end', 'cn-record-list__cell--alert': cell.alert }">
56
+ {{ cell.text }}
57
+ </td>
58
+ </tr>
59
+ </tbody>
60
+ </table>
36
61
  <ul v-else class="cn-record-list__list">
37
62
  <li
38
63
  v-for="item in items"
@@ -48,6 +73,11 @@
48
73
  class="cn-record-list__avatar"
49
74
  aria-hidden="true"
50
75
  data-testid="cn-record-list-avatar">{{ item.initials }}</span>
76
+ <span v-if="item.dot"
77
+ class="cn-record-list__dot"
78
+ :style="{ background: item.dot }"
79
+ aria-hidden="true"
80
+ data-testid="cn-record-list-dot" />
51
81
  <span class="cn-record-list__main">
52
82
  <component
53
83
  :is="item.tag"
@@ -59,6 +89,7 @@
59
89
  </component>
60
90
  <span v-if="item.meta" class="cn-record-list__meta">{{ item.meta }}</span>
61
91
  </span>
92
+ <span v-if="item.flag" class="cn-record-list__flag" data-testid="cn-record-list-flag">{{ item.flag }}</span>
62
93
  <span v-if="item.pill || item.badges.length > 0" class="cn-record-list__pills">
63
94
  <CnStatusBadge
64
95
  v-if="item.pill"
@@ -76,7 +107,7 @@
76
107
  <span
77
108
  v-if="item.due"
78
109
  class="cn-record-list__due cn-record-list__due--column"
79
- :class="`cn-record-list__due--${item.due.tone}`"
110
+ :class="[`cn-record-list__due--${item.due.tone}`, { 'cn-record-list__due--soon': item.due.soon }]"
80
111
  data-testid="cn-record-list-due">{{ item.due.text }}</span>
81
112
  <span v-if="item.trailing" class="cn-record-list__trailing">{{ item.trailing }}</span>
82
113
  <component
@@ -99,11 +130,17 @@
99
130
  class="cn-record-list__avatar"
100
131
  aria-hidden="true"
101
132
  data-testid="cn-record-list-avatar">{{ item.initials }}</span>
133
+ <span v-if="item.dot"
134
+ class="cn-record-list__dot"
135
+ :style="{ background: item.dot }"
136
+ aria-hidden="true"
137
+ data-testid="cn-record-list-dot" />
102
138
  <span class="cn-record-list__main">
103
139
  <span v-if="item.title" class="cn-record-list__title">{{ item.title }}</span>
104
140
  <span v-if="item.meta" class="cn-record-list__meta">{{ item.meta }}</span>
105
141
  </span>
106
- <span v-if="item.pill || item.badges.length > 0 || item.due || item.trailing" class="cn-record-list__aside">
142
+ <span v-if="item.pill || item.badges.length > 0 || item.due || item.trailing || item.flag" class="cn-record-list__aside">
143
+ <span v-if="item.flag" class="cn-record-list__flag" data-testid="cn-record-list-flag">{{ item.flag }}</span>
107
144
  <CnStatusBadge
108
145
  v-if="item.pill"
109
146
  :label="item.pill.label"
@@ -119,7 +156,7 @@
119
156
  <span
120
157
  v-if="item.due"
121
158
  class="cn-record-list__due"
122
- :class="`cn-record-list__due--${item.due.tone}`"
159
+ :class="[`cn-record-list__due--${item.due.tone}`, { 'cn-record-list__due--soon': item.due.soon }]"
123
160
  data-testid="cn-record-list-due">{{ item.due.text }}</span>
124
161
  <span v-if="item.trailing" class="cn-record-list__trailing">{{ item.trailing }}</span>
125
162
  </span>
@@ -201,6 +238,9 @@
201
238
  </span>
202
239
  </li>
203
240
  </ul>
241
+ <p v-if="footnote && !error" class="cn-record-list__footnote" data-testid="cn-record-list-footnote">
242
+ {{ footnote }}
243
+ </p>
204
244
  <!-- `footerAction`: one action under the list, also under the empty
205
245
  text ("Beroep registreren"). With `disabledWhen` it greys out and
206
246
  says why (`disabledReason`: "Kan zodra de beslissing is
@@ -255,7 +295,7 @@ const PAGE_REFRESH_CHANNEL = 'cn:page:refresh'
255
295
 
256
296
  /** Gives each list's footer reason its own element id. */
257
297
  let footerSeq = 0
258
- const PRESENTATIONS = ['rows', 'feed', 'inline', 'cards', 'notice']
298
+ const PRESENTATIONS = ['rows', 'feed', 'inline', 'cards', 'notice', 'table']
259
299
 
260
300
  /** The tones a `notice` list takes. */
261
301
  const NOTICE_VARIANTS = ['info', 'warning', 'error']
@@ -343,6 +383,9 @@ export default {
343
383
  * `presentation: "notice"` draws the rows as sentences in one box toned by
344
384
  * `variant` (`info`, `warning` or `error`), after an optional `lead`
345
385
  * (`{total}` filled in).
386
+ * `dot` (a field, or `{ field, colorMap? }`) draws a colour dot before
387
+ * the title; `flags` (`[{ field, label, value? }]`) draws the labels
388
+ * of the flags a row has ("Start", "End") at its end.
346
389
  * `footerAction` (`{ label, icon?, type, ..., disabledWhen?,
347
390
  * disabledReason? }`) ends the card with one action, also under the
348
391
  * empty text; while the visibleWhen condition `disabledWhen` holds the
@@ -579,10 +622,55 @@ export default {
579
622
  due: this.dueOf(row),
580
623
  icon: this.iconOf(row),
581
624
  bar: this.barOf(row, barScale),
625
+ dot: this.dotOf(row),
626
+ flag: this.flagOf(row),
582
627
  }
583
628
  })
584
629
  },
585
630
 
631
+ /**
632
+ * The `table` presentation's columns: `{ label, value, align?, alertAbove? }`,
633
+ * `value` a template over the row.
634
+ *
635
+ * @return {Array<object>} The columns with a key and a translated label.
636
+ * @spec openspec/changes/record-list-table/specs/record-list-table/spec.md#requirement-a-record-list-can-draw-its-rows-as-a-small-table
637
+ */
638
+ tableColumns() {
639
+ const columns = Array.isArray(this.content?.columns) ? this.content.columns : []
640
+ return columns
641
+ .filter((col) => col && typeof col === 'object' && typeof col.value === 'string')
642
+ .map((col, index) => ({ ...col, key: `col-${index}`, label: typeof col.label === 'string' ? this.translate(col.label) : '' }))
643
+ },
644
+
645
+ /**
646
+ * The `table` presentation's rows: one cell per column.
647
+ *
648
+ * @return {Array<object>} `{ key, cells: [{ key, text, align, alert }] }`.
649
+ * @spec openspec/changes/record-list-table/specs/record-list-table/spec.md#requirement-a-record-list-can-draw-its-rows-as-a-small-table
650
+ */
651
+ tableRows() {
652
+ return this.items.map((item) => ({
653
+ key: item.key,
654
+ cells: this.tableColumns.map((col) => {
655
+ const text = this.fill(col.value, item.row)
656
+ const limit = Number(col.alertAbove)
657
+ const value = Number(text)
658
+ return {
659
+ key: col.key,
660
+ text,
661
+ align: col.align,
662
+ alert: col.alertAbove !== undefined && Number.isFinite(limit) && text !== '' && Number.isFinite(value) && value > limit,
663
+ }
664
+ }),
665
+ }))
666
+ },
667
+
668
+ /** @return {string} The note under the list (`footnote`), translated. */
669
+ footnote() {
670
+ const note = this.content?.footnote
671
+ return typeof note === 'string' && note !== '' ? this.translate(note) : ''
672
+ },
673
+
586
674
  /**
587
675
  * The largest `bar.valueField` over the rows shown, the scale of a
588
676
  * `bar` without `maxField`. 0 without a `bar`.
@@ -699,6 +787,50 @@ export default {
699
787
  return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
700
788
  },
701
789
 
790
+ /**
791
+ * A row's colour dot (`dot`: a field name, or `{ field, colorMap? }`):
792
+ * the field's colour, or its value through `colorMap`. Only a hex
793
+ * colour, an `rgb()`/`hsl()` colour or a `var(--…)` token is drawn.
794
+ *
795
+ * @spec openspec/changes/record-list-dot-flags/specs/record-list/spec.md#requirement-a-record-list-row-can-carry-a-colour-dot-and-flag-text
796
+ * @param {object} row The row.
797
+ * @return {string} The colour, or ''.
798
+ */
799
+ dotOf(row) {
800
+ const dot = this.content?.dot
801
+ const def = typeof dot === 'string' ? { field: dot } : dot
802
+ if (!def || typeof def !== 'object' || typeof def.field !== 'string' || def.field === '') {
803
+ return ''
804
+ }
805
+ const raw = readPath(row, def.field)
806
+ if (raw === null || raw === undefined || raw === '') {
807
+ return ''
808
+ }
809
+ const mapped = def.colorMap && typeof def.colorMap === 'object' && typeof def.colorMap[String(raw)] === 'string' ? def.colorMap[String(raw)] : String(raw)
810
+ return /^(#[0-9a-f]{3,8}|(rgb|rgba|hsl|hsla)\([^;{}]*\)|var\(--[a-z0-9-]+(,[^;{}]*)?\))$/i.test(mapped.trim()) ? mapped.trim() : ''
811
+ },
812
+
813
+ /**
814
+ * A row's flag text (`flags`: `[{ field, label, value? }]`): the
815
+ * labels of the flags whose field is set (or equals `value`), through
816
+ * the host translate and joined with " · " ("Start", "End").
817
+ *
818
+ * @spec openspec/changes/record-list-dot-flags/specs/record-list/spec.md#requirement-a-record-list-row-can-carry-a-colour-dot-and-flag-text
819
+ * @param {object} row The row.
820
+ * @return {string} The text, or ''.
821
+ */
822
+ flagOf(row) {
823
+ const flags = Array.isArray(this.content?.flags) ? this.content.flags : []
824
+ return flags
825
+ .filter((flag) => flag && typeof flag.field === 'string' && typeof flag.label === 'string' && flag.label !== '')
826
+ .filter((flag) => {
827
+ const raw = readPath(row, flag.field)
828
+ return flag.value !== undefined ? String(raw) === String(flag.value) : raw === true || raw === 'true' || raw === 1
829
+ })
830
+ .map((flag) => this.translate(flag.label))
831
+ .join(' · ')
832
+ },
833
+
702
834
  /**
703
835
  * Settle whether the footer action is disabled: `disabledWhen` is a
704
836
  * visibleWhen condition read against the page's record (`field`), the
@@ -1408,6 +1540,19 @@ export default {
1408
1540
  text-decoration: underline;
1409
1541
  }
1410
1542
 
1543
+ .cn-record-list__dot {
1544
+ flex: 0 0 14px;
1545
+ width: 14px;
1546
+ height: 14px;
1547
+ border-radius: 50%;
1548
+ }
1549
+
1550
+ .cn-record-list__flag {
1551
+ color: var(--color-text-maxcontrast);
1552
+ font-size: 14px;
1553
+ white-space: nowrap;
1554
+ }
1555
+
1411
1556
  .cn-record-list__avatar {
1412
1557
  align-items: center;
1413
1558
  background: var(--color-background-hover);
@@ -1462,6 +1607,47 @@ export default {
1462
1607
  font-weight: 600;
1463
1608
  }
1464
1609
 
1610
+ .cn-record-list__table {
1611
+ width: 100%;
1612
+ border-collapse: collapse;
1613
+ font-size: 14px;
1614
+ }
1615
+
1616
+ .cn-record-list__table th {
1617
+ padding: 6px 0;
1618
+ border-bottom: 1px solid var(--color-border);
1619
+ color: var(--color-text-maxcontrast);
1620
+ font-weight: 600;
1621
+ text-align: start;
1622
+ }
1623
+
1624
+ .cn-record-list__table td {
1625
+ padding: 8px 0;
1626
+ border-bottom: 1px solid var(--color-border);
1627
+ }
1628
+
1629
+ .cn-record-list__table .cn-record-list__cell--end {
1630
+ padding-inline-start: 12px;
1631
+ text-align: end;
1632
+ font-variant-numeric: tabular-nums;
1633
+ }
1634
+
1635
+ .cn-record-list__table .cn-record-list__cell--alert {
1636
+ color: var(--color-text-error, var(--color-error-text));
1637
+ font-weight: 700;
1638
+ }
1639
+
1640
+ .cn-record-list__footnote {
1641
+ margin: 8px 0 0;
1642
+ color: var(--color-text-maxcontrast);
1643
+ font-size: 13px;
1644
+ }
1645
+
1646
+ .cn-record-list__due--soon {
1647
+ color: var(--color-text-warning, var(--color-warning-text));
1648
+ font-weight: 600;
1649
+ }
1650
+
1465
1651
  .cn-record-list__due--empty {
1466
1652
  color: var(--color-text-maxcontrast);
1467
1653
  }
@@ -42,7 +42,53 @@
42
42
  <div class="cn-related-objects-widget">
43
43
  <!-- Tabbed self-fetch mode -->
44
44
  <template v-if="useTabs">
45
- <div v-if="tabStripVisible" class="cn-related-objects-widget__tabs" role="tablist">
45
+ <!-- `layout: "sections"`: every group under its own heading in one
46
+ list, a heading action beside it (DqZaakGerelateerd). -->
47
+ <template v-if="layout === 'sections'">
48
+ <section v-for="group in visibleGroups"
49
+ :key="`section-${group.key}`"
50
+ class="cn-related-objects-widget__section"
51
+ :aria-labelledby="`${sectionIdBase}-${group.key}`"
52
+ data-testid="cn-related-section">
53
+ <div class="cn-related-objects-widget__section-head">
54
+ <h4 :id="`${sectionIdBase}-${group.key}`" class="cn-related-objects-widget__section-title">
55
+ {{ group.label }}
56
+ </h4>
57
+ <button v-if="groupAction(group)"
58
+ type="button"
59
+ class="cn-related-objects-widget__section-action"
60
+ data-testid="cn-related-section-action"
61
+ @click="onGroupAction(group)">
62
+ <CnIcon v-if="groupAction(group).icon" :name="groupAction(group).icon" :size="16" />
63
+ {{ groupAction(group).label }}
64
+ </button>
65
+ </div>
66
+ <ul class="cn-related-objects-widget__list">
67
+ <li v-for="(item, i) in group.items" :key="`item-${group.key}-${item.id || i}`">
68
+ <a v-if="groupHref(group, item)"
69
+ class="cn-related-objects-widget__row"
70
+ :href="groupHref(group, item)"
71
+ target="_blank"
72
+ rel="noopener noreferrer">
73
+ <CnIcon :name="group.icon" :size="20" class="cn-related-objects-widget__icon" />
74
+ <span class="cn-related-objects-widget__label">{{ item.label }}</span>
75
+ <span v-if="item.meta" class="cn-related-objects-widget__meta">{{ item.meta }}</span>
76
+ </a>
77
+ <div v-else
78
+ class="cn-related-objects-widget__row"
79
+ tabindex="0"
80
+ role="button"
81
+ @click="onSelectGroupItem(group, item)"
82
+ @keydown.enter="onSelectGroupItem(group, item)">
83
+ <CnIcon :name="group.icon" :size="20" class="cn-related-objects-widget__icon" />
84
+ <span class="cn-related-objects-widget__label">{{ item.label }}</span>
85
+ <span v-if="item.meta" class="cn-related-objects-widget__meta">{{ item.meta }}</span>
86
+ </div>
87
+ </li>
88
+ </ul>
89
+ </section>
90
+ </template>
91
+ <div v-if="layout !== 'sections' && tabStripVisible" class="cn-related-objects-widget__tabs" role="tablist">
46
92
  <button v-for="group in visibleGroups"
47
93
  :key="`tab-${group.key}`"
48
94
  type="button"
@@ -57,7 +103,7 @@
57
103
  </button>
58
104
  </div>
59
105
 
60
- <section v-if="activeGroup" class="cn-related-objects-widget__panel" role="tabpanel">
106
+ <section v-if="layout !== 'sections' && activeGroup" class="cn-related-objects-widget__panel" role="tabpanel">
61
107
  <ul class="cn-related-objects-widget__list">
62
108
  <li v-for="(item, i) in activeGroup.items"
63
109
  :key="`item-${activeGroup.key}-${item.id || i}`">
@@ -445,6 +491,8 @@ export default {
445
491
  * reads in the app's language. Without it a title reads as stored.
446
492
  */
447
493
  cnTranslate: { default: () => (key) => key },
494
+ /** The page's pre-bound action dispatcher (CnPageRenderer), for a group's heading action. */
495
+ cnDispatchAction: { default: null },
448
496
  },
449
497
 
450
498
  props: {
@@ -507,15 +555,46 @@ export default {
507
555
 
508
556
  /**
509
557
  * Render mode. `'tabs'` (default) self-fetches from OpenRegister and
510
- * renders a tab per non-empty group. `'list'` forces the deprecated
558
+ * renders a tab per non-empty group. `'sections'` renders every group
559
+ * under its own heading in one list. `'list'` forces the deprecated
511
560
  * store-action list path.
512
561
  *
513
- * @type {'tabs'|'list'}
562
+ * @type {'tabs'|'list'|'sections'}
514
563
  */
515
564
  layout: {
516
565
  type: String,
517
566
  default: 'tabs',
518
- validator: (value) => ['tabs', 'list'].includes(value),
567
+ validator: (value) => ['tabs', 'list', 'sections'].includes(value),
568
+ },
569
+
570
+ /**
571
+ * Split the Objects group into one group per relation type, labelled
572
+ * with the label OpenRegister answers for the direction
573
+ * (`relation.displayLabel` on each row: "Onderwerp van deze zaak",
574
+ * "Wacht op"), through the host translate. A row without one stays in
575
+ * Objects.
576
+ *
577
+ * @spec openspec/changes/related-groups-by-relation/specs/related-objects/spec.md#requirement-related-objects-can-be-grouped-by-relation-type-under-headings
578
+ */
579
+ groupByRelation: {
580
+ type: Boolean,
581
+ default: false,
582
+ },
583
+
584
+ /**
585
+ * An action on a group's heading in `layout: "sections"`, keyed by the
586
+ * group key (`objects`, `files`, an `extraSections` key, or
587
+ * `relation:<label>` for a relation group): `{ label, icon?, ...action }`
588
+ * ("Vervolg plannen"). An `extraSections` entry may carry its own
589
+ * `action`. Pressing it dispatches the action through the page and
590
+ * emits `group-action`.
591
+ *
592
+ * @type {Record<string, object>}
593
+ * @spec openspec/changes/related-groups-by-relation/specs/related-objects/spec.md#requirement-related-objects-can-be-grouped-by-relation-type-under-headings
594
+ */
595
+ groupActions: {
596
+ type: Object,
597
+ default: () => ({}),
519
598
  },
520
599
 
521
600
  /** Include `/contracts` relations in the Objects group (opt-in). */
@@ -688,6 +767,7 @@ export default {
688
767
 
689
768
  emits: [
690
769
  'add',
770
+ 'group-action',
691
771
  'file-uploaded',
692
772
  'note-added',
693
773
  'open-integration',
@@ -724,6 +804,8 @@ export default {
724
804
  groups: [],
725
805
  /** Active tab key. */
726
806
  activeKey: '',
807
+ /** Prefix for the section heading ids in `layout: "sections"`. */
808
+ sectionIdBase: `cn-related-${Math.random().toString(36).slice(2, 9)}`,
727
809
  /** Keys of the tabbed sections whose request is still in flight (`objects`, `files`, `relations`). */
728
810
  pendingSections: [],
729
811
  /** Tabbed sections whose request failed: `{ key, label }`. */
@@ -774,7 +856,7 @@ export default {
774
856
 
775
857
  /** True when the tabbed self-fetch path is usable. */
776
858
  useTabs() {
777
- return this.layout === 'tabs'
859
+ return (this.layout === 'tabs' || this.layout === 'sections')
778
860
  && Boolean(this.resolvedRegister && this.resolvedSchema && this.resolvedId)
779
861
  },
780
862
 
@@ -809,6 +891,7 @@ export default {
809
891
  items,
810
892
  total: items.length,
811
893
  isExtra: true,
894
+ action: section.action && typeof section.action === 'object' ? section.action : null,
812
895
  }
813
896
  })
814
897
  },
@@ -824,7 +907,8 @@ export default {
824
907
  const allow = Array.isArray(this.includeGroups) ? this.includeGroups : []
825
908
  const own = this.groups.filter((group) => (group.total > 0 || group.items.length > 0)
826
909
  && (allow.length === 0 || allow.includes(group.key)))
827
- return [...own, ...this.extraGroups]
910
+ const split = this.groupByRelation ? own.flatMap((group) => (group.key === 'objects' ? this.splitByRelation(group) : [group])) : own
911
+ return [...split, ...this.extraGroups]
828
912
  },
829
913
 
830
914
  /**
@@ -892,7 +976,7 @@ export default {
892
976
  */
893
977
  activeGroupHrefs() {
894
978
  const group = this.activeGroup
895
- if (!group || group.isExtra || group.key === 'objects') {
979
+ if (!group || group.isExtra || group.key === 'objects' || String(group.key).startsWith('relation:')) {
896
980
  return []
897
981
  }
898
982
  return group.items.map((item) => this.resolveItemHref(group.key, item.raw || {}))
@@ -1195,6 +1279,83 @@ export default {
1195
1279
  this.$emit('select-file', raw)
1196
1280
  },
1197
1281
 
1282
+ /**
1283
+ * The Objects group split by relation type: one group per
1284
+ * `relation.displayLabel`, in the order they first appear; rows
1285
+ * without a label stay in an Objects group.
1286
+ *
1287
+ * @spec openspec/changes/related-groups-by-relation/specs/related-objects/spec.md#requirement-related-objects-can-be-grouped-by-relation-type-under-headings
1288
+ * @param {object} group The Objects group.
1289
+ * @return {Array<object>} The groups.
1290
+ */
1291
+ splitByRelation(group) {
1292
+ const byLabel = new Map()
1293
+ for (const item of group.items) {
1294
+ const relation = item.raw && item.raw.relation
1295
+ const label = relation && typeof relation.displayLabel === 'string' && relation.displayLabel !== '' ? relation.displayLabel : ''
1296
+ const key = label === '' ? 'objects' : `relation:${label}`
1297
+ if (!byLabel.has(key)) {
1298
+ byLabel.set(key, { ...group, key, label: label === '' ? group.label : this.cnTranslate(label), relationLabel: label, items: [], total: 0 })
1299
+ }
1300
+ const target = byLabel.get(key)
1301
+ target.items.push(item)
1302
+ target.total = target.items.length
1303
+ }
1304
+ return [...byLabel.values()]
1305
+ },
1306
+
1307
+ /**
1308
+ * The heading action of a group: `groupActions[group.key]`, else the
1309
+ * extra section's own `action`; null without one or without a label.
1310
+ *
1311
+ * @param {object} group The group.
1312
+ * @return {{label: string, icon: string, action: object}|null}
1313
+ */
1314
+ groupAction(group) {
1315
+ const declared = (this.groupActions && this.groupActions[group.key]) || group.action || null
1316
+ if (!declared || typeof declared !== 'object' || typeof declared.label !== 'string' || declared.label === '') {
1317
+ return null
1318
+ }
1319
+ return { label: this.cnTranslate(declared.label), icon: typeof declared.icon === 'string' ? declared.icon : '', action: declared }
1320
+ },
1321
+
1322
+ /**
1323
+ * Run a group's heading action through the page's dispatcher, and say
1324
+ * so with `group-action`.
1325
+ *
1326
+ * @spec openspec/changes/related-groups-by-relation/specs/related-objects/spec.md#requirement-related-objects-can-be-grouped-by-relation-type-under-headings
1327
+ * @param {object} group The group.
1328
+ * @return {void}
1329
+ */
1330
+ onGroupAction(group) {
1331
+ const resolved = this.groupAction(group)
1332
+ if (!resolved) {
1333
+ return
1334
+ }
1335
+ if (typeof this.cnDispatchAction === 'function' && resolved.action.type) {
1336
+ this.cnDispatchAction(resolved.action)
1337
+ }
1338
+ /**
1339
+ * @event group-action A group's heading action was pressed. Payload: `{ group, action }`, the group key and the declared action.
1340
+ */
1341
+ this.$emit('group-action', { group: group.key, action: resolved.action })
1342
+ },
1343
+
1344
+ /**
1345
+ * The owning-app link of one row in a group, '' where the row routes
1346
+ * through the host (related objects, host sections).
1347
+ *
1348
+ * @param {object} group The group.
1349
+ * @param {object} item The row.
1350
+ * @return {string}
1351
+ */
1352
+ groupHref(group, item) {
1353
+ if (!group || group.isExtra || group.key === 'objects' || String(group.key).startsWith('relation:')) {
1354
+ return ''
1355
+ }
1356
+ return this.resolveItemHref(group.key, item.raw || {})
1357
+ },
1358
+
1198
1359
  /**
1199
1360
  * Handle a click on a tabbed-path row that has no owning-app link
1200
1361
  * (rows with one render as a real `<a>`): emit a host-routed event.
@@ -1213,7 +1374,7 @@ export default {
1213
1374
  this.onSelectExtra(group.key, item.raw)
1214
1375
  return
1215
1376
  }
1216
- if (group.key === 'objects') {
1377
+ if (group.key === 'objects' || String(group.key).startsWith('relation:')) {
1217
1378
  this.onSelectObject(item.raw)
1218
1379
  return
1219
1380
  }
@@ -1860,6 +2021,45 @@ export default {
1860
2021
  </script>
1861
2022
 
1862
2023
  <style scoped>
2024
+ .cn-related-objects-widget__section + .cn-related-objects-widget__section {
2025
+ margin-top: 16px;
2026
+ }
2027
+
2028
+ .cn-related-objects-widget__section-head {
2029
+ display: flex;
2030
+ align-items: center;
2031
+ justify-content: space-between;
2032
+ gap: 12px;
2033
+ padding-bottom: 8px;
2034
+ border-bottom: 1px solid var(--color-border);
2035
+ }
2036
+
2037
+ .cn-related-objects-widget__section-title {
2038
+ margin: 0;
2039
+ font-size: 15px;
2040
+ font-weight: 700;
2041
+ }
2042
+
2043
+ .cn-related-objects-widget__section-action {
2044
+ display: inline-flex;
2045
+ align-items: center;
2046
+ gap: 6px;
2047
+ height: 34px;
2048
+ padding: 0 12px;
2049
+ border: none;
2050
+ border-radius: 17px;
2051
+ background: var(--color-primary-element-light);
2052
+ color: var(--color-primary-element-light-text);
2053
+ font-size: 13px;
2054
+ font-weight: 600;
2055
+ cursor: pointer;
2056
+ }
2057
+
2058
+ .cn-related-objects-widget__section-action:focus-visible {
2059
+ outline: 2px solid var(--color-main-text);
2060
+ outline-offset: 2px;
2061
+ }
2062
+
1863
2063
  .cn-related-objects-widget__group {
1864
2064
  padding: calc(2 * var(--default-grid-baseline, 4px)) 0;
1865
2065
  }
@@ -18,7 +18,10 @@
18
18
  class="cn-settings-field__image"
19
19
  role="group"
20
20
  :aria-labelledby="`${inputId}-label`">
21
- <span :id="`${inputId}-label`" class="cn-settings-field__image-label">{{ label }}</span>
21
+ <!-- `hideLabel` keeps the name for a screen reader only. -->
22
+ <span :id="`${inputId}-label`"
23
+ class="cn-settings-field__image-label"
24
+ :class="{ 'hidden-visually': field.hideLabel === true }">{{ label }}</span>
22
25
  <div class="cn-settings-field__image-row">
23
26
  <span class="cn-settings-field__image-tile" :data-testid="`cn-settings-field-image-${field.key}`">
24
27
  <img v-if="stringValue" :src="stringValue" :alt="t('nextcloud-vue', 'Current {name}', { name: label })">
@@ -126,7 +126,15 @@
126
126
  <NcNoteCard v-if="isChoiceDisabled(step)" type="warning">
127
127
  {{ dependsOnHint(step) }}
128
128
  </NcNoteCard>
129
- <p v-if="stepNote(step)" class="cn-setup-step__note" data-testid="cn-setup-step-note">
129
+ <!-- `noteStyle: "card"` sets the note as an info card (DqInstellen). -->
130
+ <NcNoteCard
131
+ v-if="stepNote(step) && step.noteStyle === 'card'"
132
+ type="info"
133
+ class="cn-setup-step__note-card"
134
+ data-testid="cn-setup-step-note">
135
+ {{ stepNote(step) }}
136
+ </NcNoteCard>
137
+ <p v-else-if="stepNote(step)" class="cn-setup-step__note" data-testid="cn-setup-step-note">
130
138
  {{ stepNote(step) }}
131
139
  </p>
132
140
  </template>
@@ -947,7 +947,16 @@ export default defineComponent({
947
947
  overflow: visible;
948
948
  }
949
949
 
950
+ /* `margin: 0`: a tab is a <button>, and Nextcloud's core stylesheet gives
951
+ every plain button a 3px margin, which put the tabs 7px apart and 3px low
952
+ (DqInstellingen: 4px apart, on the strip's top). The nldesign theme sets a
953
+ button's weight, corner and border width with `!important` through its
954
+ button tokens, which drew every tab bold with a 1px underline; the tab
955
+ hands its own values to those tokens. */
950
956
  .cn-tabs--underline .cn-tabs__nav-item {
957
+ --nldesign-component-button-font-weight: 500;
958
+ --nldesign-component-button-border-radius: 0;
959
+ --nldesign-component-button-border-width: 0 0 3px;
951
960
  background-color: transparent;
952
961
  border: 0;
953
962
  border-bottom: 3px solid transparent;
@@ -956,6 +965,7 @@ export default defineComponent({
956
965
  font-size: 14px;
957
966
  font-weight: 500;
958
967
  gap: 8px;
968
+ margin: 0;
959
969
  min-height: 44px;
960
970
  padding: 0 9px;
961
971
  }
@@ -975,6 +985,7 @@ export default defineComponent({
975
985
  .cn-tabs--underline .cn-tabs__nav-item--active,
976
986
  .cn-tabs--underline .cn-tabs__nav-item--active:hover,
977
987
  .cn-tabs--underline .cn-tabs__nav-item--active:focus-visible {
988
+ --nldesign-component-button-font-weight: 700;
978
989
  border-bottom: 3px solid var(--color-primary-element);
979
990
  color: var(--color-main-text);
980
991
  font-weight: 700;