@conduction/nextcloud-vue 2.143.0 → 2.145.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 (165) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +7 -2
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +1 -0
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  7. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +55 -2
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  11. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +16 -0
  12. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -1
  14. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  15. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  16. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  17. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
  18. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +1 -1
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +3 -3
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -1
  24. package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
  25. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
  26. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
  27. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
  28. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
  29. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  30. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +2 -2
  31. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  34. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +2 -1
  35. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +9 -4
  38. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnMailTab/CnMailTab.vue.js +1 -1
  41. package/dist/esm/components/CnMailTab/CnMailTab.vue2.js.map +1 -1
  42. package/dist/esm/components/CnMailTab/CnMailTab.vue3.js +2 -2
  43. package/dist/esm/components/CnMailTab/CnMailTab.vue3.js.map +1 -1
  44. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  45. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
  46. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  47. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
  48. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  49. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
  50. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
  51. package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue3.js.map +1 -1
  52. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
  53. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
  54. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
  55. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
  56. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  58. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +73 -12
  59. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +165 -126
  61. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  63. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  64. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +12 -0
  65. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  66. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  67. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +14 -0
  68. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  69. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +2 -1
  70. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  71. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
  72. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
  73. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  74. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
  75. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  76. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  77. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -8
  78. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  79. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  80. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue.js +1 -1
  81. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +10 -2
  82. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  83. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  84. package/dist/esm/composables/indexSources.js +35 -9
  85. package/dist/esm/composables/indexSources.js.map +1 -1
  86. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  87. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
  88. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  89. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
  90. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  91. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  92. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
  93. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  94. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  95. package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
  96. package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
  97. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  98. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
  99. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  100. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
  101. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  102. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  103. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
  104. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  105. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  106. package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
  107. package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
  108. package/dist/esm/l10n/en.json.js +6 -0
  109. package/dist/esm/l10n/en.json.js.map +1 -1
  110. package/dist/esm/l10n/nl.json.js +8 -0
  111. package/dist/esm/l10n/nl.json.js.map +1 -1
  112. package/dist/esm/library-style.js +1 -1
  113. package/dist/esm/nextcloud-vue.css +1505 -637
  114. package/dist/esm/utils/builtInFormatters.js +18 -1
  115. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  116. package/dist/esm/utils/fetchFilterCounts.js +5 -2
  117. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  118. package/dist/esm/utils/localHeaderRows.js +95 -0
  119. package/dist/esm/utils/localHeaderRows.js.map +1 -0
  120. package/dist/esm/utils/nextcloudDirectory.js +19 -6
  121. package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
  122. package/dist/nextcloud-vue.cjs.js +1848 -924
  123. package/dist/nextcloud-vue.cjs.js.map +2 -2
  124. package/dist/nextcloud-vue.css +1505 -637
  125. package/l10n/en.json +6 -0
  126. package/l10n/nl.json +8 -0
  127. package/package.json +1 -1
  128. package/src/components/CnActionDialog/CnActionDialog.vue +36 -4
  129. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +55 -2
  130. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +18 -0
  131. package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
  132. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +2 -1
  133. package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
  134. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +2 -2
  135. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +15 -1
  136. package/src/components/CnIndexPage/CnIndexPage.vue +9 -4
  137. package/src/components/CnMailTab/CnMailTab.vue +6 -4
  138. package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
  139. package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +4 -1
  140. package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
  141. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +131 -20
  142. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
  143. package/src/components/CnSettingsField/CnSettingsField.vue +15 -0
  144. package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
  145. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -8
  146. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +10 -2
  147. package/src/composables/indexSources.js +35 -9
  148. package/src/css/dialog.css +25 -10
  149. package/src/css/kpi-card.css +3 -0
  150. package/src/css/look-board-detail.css +513 -0
  151. package/src/css/look-board-index.css +52 -4
  152. package/src/css/look-board.css +163 -8
  153. package/src/css/table.css +8 -1
  154. package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
  155. package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
  156. package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
  157. package/src/integrations/builtin/calendar/eventStatus.js +63 -0
  158. package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
  159. package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
  160. package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
  161. package/src/integrations/builtin/deck/deckCardParts.js +56 -0
  162. package/src/utils/builtInFormatters.js +17 -0
  163. package/src/utils/fetchFilterCounts.js +5 -2
  164. package/src/utils/localHeaderRows.js +91 -0
  165. package/src/utils/nextcloudDirectory.js +19 -6
@@ -77,7 +77,7 @@
77
77
  grid-column: 1 / -1;
78
78
  grid-row: 2;
79
79
  justify-self: start;
80
- margin-block: 6px 3px;
80
+ margin-block: 6px 0;
81
81
  }
82
82
 
83
83
  .cn-look-board.cn-index-page--sidebar-open .cn-page-header .cn-header-trail,
@@ -90,6 +90,13 @@
90
90
  grid-row: 4;
91
91
  }
92
92
 
93
+ /* Without a breadcrumb trail the count line takes the trail's row, so no
94
+ empty row adds a second gap (DqZakenSorteren: 58px under the title). */
95
+ .cn-look-board.cn-index-page--sidebar-open .cn-page-header:not(:has(.cn-header-trail)) .cn-page-header__text > .cn-page-header__description,
96
+ .cn-look-board .cn-index-page--sidebar-open .cn-page-header:not(:has(.cn-header-trail)) .cn-page-header__text > .cn-page-header__description {
97
+ grid-row: 3;
98
+ }
99
+
93
100
  /* The header buttons keep the board's button under a theme that styles every
94
101
  secondary or primary button with `!important` (the nldesign theme: a
95
102
  primary border, 8px 16px padding): Download and the Actions menu are 40px,
@@ -190,6 +197,15 @@
190
197
  gap: 8px;
191
198
  }
192
199
 
200
+ /* With the side panel open the boards keep Filter on row 1 after Save view
201
+ and wrap the view switch alone to row 2 (DqFilterBeheren, DqZakenSorteren):
202
+ the unit dissolves into the bar's flex row.
203
+ @spec openspec/changes/board-filter-row-one-beside-side-panel/specs/board-filter-row-one-beside-side-panel/spec.md */
204
+ .cn-look-board.cn-index-page--sidebar-open .cn-actions-bar--board .cn-actions-bar__view-controls,
205
+ .cn-look-board .cn-index-page--sidebar-open .cn-actions-bar--board .cn-actions-bar__view-controls {
206
+ display: contents;
207
+ }
208
+
193
209
  /* `quickFilterPlacement: "toolbar"` (DqTeamwachtrij): the chips take the room
194
210
  the Filter button and the view switch leave on row 1, so the three stay on
195
211
  one row in the column beside a side panel; the chips that do not fit move
@@ -909,16 +925,21 @@
909
925
  `#content[class*="app-"]`, an id selector no class rule outranks, so a plain
910
926
  `content-box` here was ignored and the cell left 12px for the checkbox (the
911
927
  header's checkbox showed as a stray bracket). */
912
- /* The checkbox starts 20px in, as the boards draw a native checkbox (16px of
913
- cell padding and its 4px margin; DqZoekresultaten), in the same 60px. */
928
+ /* The cell pads 16px as the boards do and the checkbox starts 4px further in,
929
+ a native checkbox's margin (DqZoekresultaten, DqPostvak, DqContracten), in
930
+ the same 60px. */
914
931
  .cn-look-board .cn-table-container--board .cn-data-table .cn-table-col--checkbox {
915
932
  box-sizing: border-box !important;
916
933
  width: 60px;
917
934
  min-width: 60px;
918
- padding-inline: 20px 12px;
935
+ padding-inline: 16px;
919
936
  text-align: start;
920
937
  }
921
938
 
939
+ .cn-look-board .cn-table-container--board .cn-data-table .cn-table-col--checkbox .checkbox-radio-switch {
940
+ margin-inline-start: 4px;
941
+ }
942
+
922
943
  .cn-look-board .cn-table-container--board .cn-data-table .checkbox-radio-switch {
923
944
  --icon-size: 18px;
924
945
  }
@@ -991,6 +1012,20 @@
991
1012
  text-decoration: none;
992
1013
  }
993
1014
 
1015
+ /* In such a plain log an icon-and-name cell is regular too (DqZaakContact
1016
+ Kanaal "Mijn Zuiddrecht"); elsewhere the icon label stays 600.
1017
+ @spec openspec/changes/board-log-table-icon-label-regular/specs/board-log-table-icon-label-regular/spec.md#requirement-a-plain-logs-icon-label-cell-is-regular */
1018
+ .cn-look-board .cn-table-container--board.cn-table-container--title-text .cn-cell-renderer__icon-label:not(.cn-look-nextcloud *) {
1019
+ font-weight: 400;
1020
+ }
1021
+
1022
+ /* In such a plain log a wrapping prose cell runs at line height 1.45, as
1023
+ DqZaakContact draws the contact summary (rows 61.6 high, not 57).
1024
+ @spec openspec/changes/board-log-table-prose-line-height/specs/board-log-table-prose-line-height/spec.md#requirement-a-plain-logs-prose-cell-runs-at-line-height-1-45 */
1025
+ .cn-look-board .cn-table-container--board.cn-table-container--title-text .cn-data-table td.cn-cell--wrap:not(.cn-look-nextcloud *) {
1026
+ line-height: 1.45;
1027
+ }
1028
+
994
1029
  /* `rowTitle: "strong"` (DqZaaktypeStatussen "Statussen", "Resultaten"): the
995
1030
  name column of a card's list, the cell's 14px at weight 600 in the main
996
1031
  ink, no underline; the row stays a link.
@@ -1414,6 +1449,19 @@
1414
1449
  padding-top: 0;
1415
1450
  }
1416
1451
 
1452
+ /* The selection box sits flush where the boards draw it: NcCheckbox pads its
1453
+ content 7px on each side (DqAanMijToegewezen card head, DqTeamwachtrij rows,
1454
+ whose box starts 18px in).
1455
+ @spec openspec/changes/board-selection-box-flush/specs/board-selection-box-flush/spec.md#requirement-a-cards-and-a-rows-selection-box-sits-where-the-board-draws-it */
1456
+ .cn-look-board .cn-object-card__record-head > .cn-object-card__checkbox .checkbox-radio-switch__content,
1457
+ .cn-look-board .cn-object-row__checkbox .checkbox-radio-switch__content {
1458
+ padding-inline: 0;
1459
+ }
1460
+
1461
+ .cn-look-board .cn-object-row__checkbox {
1462
+ margin-inline-start: 6px;
1463
+ }
1464
+
1417
1465
  .cn-look-board .cn-object-card--board .cn-object-card__record-head > .cn-object-card__checkbox ~ .cn-object-card__record-pill {
1418
1466
  margin-inline-start: auto;
1419
1467
  }
@@ -329,9 +329,11 @@
329
329
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-banner-widget__kicker {
330
330
  --nldesign-component-paragraph-font-size: 13px;
331
331
  --nldesign-component-paragraph-font-weight: 700;
332
+ --nldesign-component-paragraph-line-height: normal;
332
333
  font-size: 13px;
333
334
  font-weight: 700;
334
335
  letter-spacing: 0;
336
+ line-height: normal;
335
337
  text-transform: none;
336
338
  }
337
339
 
@@ -662,12 +664,17 @@
662
664
  }
663
665
 
664
666
  /* Two buttons in a card's title row stand 8px apart (DqZaakContact "Live
665
- gesprek", DqZaakEmail "Mail koppelen" / "Mail opstellen").
667
+ gesprek", DqZaakEmail "Mail koppelen" / "Mail opstellen"), also in an
668
+ integration card's CnDetailCard header (DqZaakTaken "Afspraken", "Kaarten").
666
669
  @spec openspec/changes/board-card-header-actions-gap/specs/board-card-header-actions-gap/spec.md#requirement-a-board-cards-title-row-buttons-stand-8px-apart */
667
670
  .cn-look-board .cn-widget-wrapper__actions:not(.cn-look-nextcloud *) {
668
671
  gap: 8px;
669
672
  }
670
673
 
674
+ .cn-look-board .cn-detail-card__header-right:not(.cn-look-nextcloud *) {
675
+ gap: 8px;
676
+ }
677
+
671
678
  /* A detail page marked `cardButtons: "regular"` draws them at the 40px
672
679
  control height: the case page's tab cards (DqZaakContact "Live gesprek",
673
680
  DqZaakHistorie, DqZaakBetrokkenen), where setting and case-type pages keep 34.
@@ -1465,6 +1472,90 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1465
1472
  text-decoration: underline;
1466
1473
  }
1467
1474
 
1475
+ /* A plain table an app draws in a settings card (dossiq's ZGW mapping, StUF
1476
+ endpoints and audit log, AWB terms) reads as the board's settings tables:
1477
+ 14px, a near-white head with muted 600 labels, 10px 12px cells, soft rules.
1478
+ The theme paints every th through its table header tokens, so the head
1479
+ hands those tokens the board's values too.
1480
+ @spec openspec/changes/board-settings-plain-tables/specs/settings-board-look/spec.md */
1481
+ .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) {
1482
+ width: 100%;
1483
+ border-collapse: collapse;
1484
+ font-size: 14px;
1485
+ line-height: 1.2;
1486
+ }
1487
+
1488
+ .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) th {
1489
+ --nldesign-component-table-header-background-color: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
1490
+ --nldesign-component-table-header-color: var(--color-text-maxcontrast);
1491
+ --nldesign-component-table-header-font-weight: 600;
1492
+ padding: 10px 12px;
1493
+ border-bottom: 1px solid var(--color-border);
1494
+ background: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
1495
+ color: var(--color-text-maxcontrast);
1496
+ font-size: 14px;
1497
+ font-weight: 600;
1498
+ line-height: 1.2;
1499
+ text-align: start;
1500
+ }
1501
+
1502
+ /* The working calendar's month reads as the board draws it: the month name
1503
+ first, at the content edge, with the previous and next month buttons after
1504
+ it, and the buttons add no height to the line, so the month sits 20px under
1505
+ the card title (DqInstellingenAlgemeen "Werkdagen en feestdagen": the
1506
+ arrow put the month 30px in and 28px down).
1507
+ @spec openspec/changes/working-calendar-board-head/specs/settings-board-look/spec.md */
1508
+ .cn-look-board .cn-working-calendar__title:not(.cn-look-nextcloud *) {
1509
+ order: -1;
1510
+ min-width: 0;
1511
+ margin-inline-end: 4px;
1512
+ line-height: 1.2;
1513
+ text-align: start;
1514
+ }
1515
+
1516
+ .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) td {
1517
+ padding: 10px 12px;
1518
+ border-bottom: 1px solid var(--color-background-dark);
1519
+ color: var(--color-main-text);
1520
+ font-size: 14px;
1521
+ line-height: 1.2;
1522
+ vertical-align: middle;
1523
+ }
1524
+
1525
+ /* A strong cell's name in a settings card reads 15px bold over its muted
1526
+ second line, as the boards draw a connection's name (DqInstellingen
1527
+ "Koppelingen": <strong> 15px), not the index tables' 14px 500.
1528
+ @spec openspec/changes/board-settings-strong-cell-name/specs/settings-board-look/spec.md */
1529
+ .cn-look-board .settings-section .cn-data-table td.cn-cell--strong:not(.cn-look-nextcloud *) {
1530
+ font-weight: 400;
1531
+ }
1532
+
1533
+ .cn-look-board .settings-section .cn-data-table td.cn-cell--strong:not(.cn-look-nextcloud *) > .cn-cell-renderer {
1534
+ font-size: 15px;
1535
+ font-weight: 700;
1536
+ }
1537
+
1538
+ .cn-look-board .cn-working-calendar__head:not(.cn-look-nextcloud *) > .button-vue:not(.cn-working-calendar__add) {
1539
+ margin-block: -8px;
1540
+ }
1541
+
1542
+ /* In a one-column settings card a single-line field is at most 560px wide,
1543
+ its hint under it, as the boards draw a field on its own row
1544
+ (DqBeheerGeavanceerd "Financiële integratie", "Storeregister"); a text
1545
+ area, a note or a switch still spans the card.
1546
+ @spec openspec/changes/board-settings-single-field-width/specs/settings-board-look/spec.md */
1547
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--text,
1548
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--password,
1549
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--secret,
1550
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--number,
1551
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--email,
1552
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--url,
1553
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--select,
1554
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--date,
1555
+ .cn-look-board .cn-settings-fields__grid--1:not(.cn-look-nextcloud *) > .cn-settings-field--datetime-local {
1556
+ max-width: 560px;
1557
+ }
1558
+
1468
1559
  /* Section actions sit at the end of the title row. The row is a grid, so a
1469
1560
  long title wraps beside the buttons instead of running under them. A
1470
1561
  half-width card keeps them there (DqVthInstellingen "Inspectiechecklists",
@@ -1513,6 +1604,42 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1513
1604
  align-self: center;
1514
1605
  }
1515
1606
 
1607
+ /* Without a subtitle the title centres on the title-row buttons, as the boards
1608
+ draw the card head (DqVthInstellingen "Inspectiechecklists",
1609
+ DqInstellingenAlgemeen "Werkdagen en feestdagen": title 27.5px down, live 21).
1610
+ @spec openspec/changes/board-settings-title-centred-and-link-flush/specs/settings-board-look/spec.md */
1611
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(> .cn-settings-section__actions):not(:has(> .settings-section__desc)) > .settings-section__name {
1612
+ align-self: center;
1613
+ }
1614
+
1615
+ /* A switch or radio in a board settings card starts at the card's content
1616
+ edge, its label 12px (switch) or 10px (radio) after the control, as the
1617
+ boards draw them (DqBeheerinstellingen "AI-ondersteunde verwerking").
1618
+ NcCheckboxRadioSwitch pads its content 9px on each side, which set the
1619
+ control 9px in from the fields above and below it.
1620
+ @spec openspec/changes/board-settings-switch-radio-flush/specs/settings-board-look/spec.md */
1621
+ .cn-look-board .cn-settings-fields:not(.cn-look-nextcloud *) .checkbox-content {
1622
+ padding-inline: 0;
1623
+ }
1624
+
1625
+ .cn-look-board .cn-settings-fields:not(.cn-look-nextcloud *) .checkbox-content-switch {
1626
+ gap: 12px;
1627
+ }
1628
+
1629
+ .cn-look-board .cn-settings-fields:not(.cn-look-nextcloud *) .checkbox-content-radio {
1630
+ gap: 10px;
1631
+ }
1632
+
1633
+ /* The "Uitleg tonen" toggle is a text link flush with the card's right edge
1634
+ (DqBeheerinstellingen Zaak-e-mail), not a padded tertiary button.
1635
+ @spec openspec/changes/board-settings-title-centred-and-link-flush/specs/settings-board-look/spec.md */
1636
+ .cn-look-board .cn-settings-fields__hints-toggle:not(.cn-look-nextcloud *) .button-vue {
1637
+ --button-padding: 0px;
1638
+ --button-padding-default: 0px;
1639
+ padding-inline: 0;
1640
+ min-width: 0;
1641
+ }
1642
+
1516
1643
  @container (max-width: 400px) {
1517
1644
  .cn-look-board .settings-section:not(.cn-look-nextcloud *) > .cn-settings-section__actions,
1518
1645
  .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(> .settings-section__desc) > .cn-settings-section__actions {
@@ -1576,8 +1703,12 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1576
1703
  max-width: none;
1577
1704
  }
1578
1705
 
1579
- .cn-look-board .settings-section.cn-settings-section--wide:not(.cn-look-nextcloud *) .settings-section__name,
1580
- .cn-look-board .settings-section.cn-settings-section--wide:not(.cn-look-nextcloud *) .settings-section__desc {
1706
+ /* A board card's title and subtitle run the card's width, wide or not
1707
+ (DqBeheerGeavanceerd "Eerste keer", "StUF-ZKN-endpoints": the subtitle
1708
+ wrapped at NcSettingsSection's 900px measure, the board's does not).
1709
+ @spec openspec/changes/board-settings-desc-full-width/specs/settings-board-look/spec.md */
1710
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *) .settings-section__name,
1711
+ .cn-look-board .settings-section:not(.cn-look-nextcloud *) .settings-section__desc {
1581
1712
  max-width: none;
1582
1713
  }
1583
1714
 
@@ -2136,7 +2267,7 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2136
2267
  12px follows it: the boards' card is title, 6px, rule, row (DqMijnTeam
2137
2268
  "Vastgelopen zaken", DqMijnWerk "Zaken die u volgt").
2138
2269
  @spec openspec/changes/board-card-title-above-ruled-list/specs/board-card-title-above-ruled-list/spec.md#requirement-a-ruled-list-starts-6px-under-the-card-title */
2139
- .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard):not(.cn-widget-wrapper--borderless):has(> .cn-widget-wrapper__content .cn-record-list--rule-above, > .cn-widget-wrapper__content .cn-tasks-widget__list, > .cn-widget-wrapper__content .cn-decision-list__rows) .cn-widget-wrapper__header {
2270
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard):not(.cn-widget-wrapper--borderless):has(> .cn-widget-wrapper__content .cn-record-list--rule-above, > .cn-widget-wrapper__content .cn-tasks-widget__list, > .cn-widget-wrapper__content .cn-decision-list__rows, > .cn-widget-wrapper__content .cn-nc-widget-widget__body--vertical) .cn-widget-wrapper__header {
2140
2271
  padding-bottom: 6px;
2141
2272
  }
2142
2273
 
@@ -2672,13 +2803,13 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2672
2803
 
2673
2804
  /* A vertical NC widget is the board's activity list (DqMijnWerk "Recente
2674
2805
  activiteit"): hairline rows 12px above and below the text, a 10px dot in
2675
- the icon's place, the title 14px in the main ink and wrapping, the first
2676
- hairline 12px under the title. Thematiq underlines links with a heavier
2677
- selector, hence the !important.
2806
+ the icon's place, the title 14px in the main ink and wrapping. Thematiq underlines links with a heavier
2807
+ selector, hence the !important. The first hairline stands 6px under the
2808
+ title, as on every Dq list card (design-system #432).
2678
2809
  @spec openspec/changes/board-nc-widget-activity-rows/specs/board-nc-widget-activity-rows/spec.md#requirement-a-board-dashboard-nc-widget-draws-its-items-as-activity-rows */
2679
2810
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-nc-widget-widget__body--vertical {
2680
2811
  gap: 0;
2681
- padding: 2px var(--cn-board-dashboard-card-inset) 10px;
2812
+ padding: 0 var(--cn-board-dashboard-card-inset) 10px;
2682
2813
  }
2683
2814
 
2684
2815
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-nc-widget-widget__body--vertical a.cn-nc-widget-widget__item--vertical {
@@ -3112,6 +3243,25 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3112
3243
  line-height: normal;
3113
3244
  }
3114
3245
 
3246
+ /* A bar list in a framed section of a sections card is the board's small
3247
+ list: 13px rows 8px apart, a 150px label column, a 10px bar and a 48px
3248
+ value column (DqDoorlooptijd "Naleving per zaaktype").
3249
+ @spec openspec/changes/board-section-bar-list-compact/specs/board-section-bar-list-compact/spec.md#requirement-a-bar-list-in-a-sections-card-section-is-compact */
3250
+ .cn-look-board .cn-sections-widget__section .cn-chart-widget__bar-list:not(.cn-look-nextcloud *) {
3251
+ gap: 8px;
3252
+ }
3253
+
3254
+ .cn-look-board .cn-sections-widget__section .cn-chart-widget__bar-list:not(.cn-look-nextcloud *) .cn-chart-widget__bar-line {
3255
+ grid-template-columns: 150px minmax(0, 1fr) 48px;
3256
+ gap: 10px;
3257
+ font-size: 13px;
3258
+ }
3259
+
3260
+ .cn-look-board .cn-sections-widget__section .cn-chart-widget__bar-list:not(.cn-look-nextcloud *) .cn-chart-widget__bar-track {
3261
+ height: 10px;
3262
+ border-radius: 5px;
3263
+ }
3264
+
3115
3265
  /* A bar list with the value at the bar's end (DqProcesanalyse "Doorlooptijd
3116
3266
  per status") sets its rows in 13px, the label in the secondary text, the
3117
3267
  value in the main ink at 600, rows 34px apart.
@@ -3157,6 +3307,11 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3157
3307
  line-height: 1.2;
3158
3308
  }
3159
3309
 
3310
+ /* An option's title bold, as the dialog boards draw it (a <strong>). */
3311
+ .cn-look-board .cn-choice-cards .cn-choice-cards__row-title:not(.cn-look-nextcloud *) {
3312
+ font-weight: 700;
3313
+ }
3314
+
3160
3315
  .cn-look-board .cn-choice-cards .cn-choice-cards__row-icon:not(.cn-look-nextcloud *) {
3161
3316
  align-self: flex-start;
3162
3317
  }
package/src/css/table.css CHANGED
@@ -310,7 +310,7 @@
310
310
  .cn-data-table--fit td.cn-table-col--grow.cn-cell--wrap {
311
311
  min-width: 8em;
312
312
  max-width: none;
313
- overflow-wrap: anywhere;
313
+ overflow-wrap: break-word;
314
314
  white-space: normal;
315
315
  }
316
316
 
@@ -359,6 +359,13 @@
359
359
  font-weight: 500;
360
360
  }
361
361
 
362
+ /* A lead cell's value reads 15px, a person's name over their function
363
+ (DqAfdelingen). */
364
+ .cn-data-table td.cn-cell--lead > .cn-cell-renderer,
365
+ .cn-data-table td.cn-cell--lead .cn-table-row__link > .cn-cell-renderer {
366
+ font-size: 15px;
367
+ }
368
+
362
369
  .cn-data-table td.cn-cell--muted {
363
370
  color: var(--color-text-maxcontrast);
364
371
  }
@@ -83,22 +83,42 @@
83
83
  v-for="ev in displayedEvents"
84
84
  :key="rowKey(ev)"
85
85
  class="cn-calendar-card__row">
86
- <div class="cn-calendar-card__row-icon">
87
- <CalendarClock v-if="isUpcoming(ev)" :size="20" class="cn-calendar-card__icon--upcoming" />
88
- <CalendarCheck v-else :size="20" class="cn-calendar-card__icon--past" />
89
- </div>
90
- <div class="cn-calendar-card__row-main">
91
- <span class="cn-calendar-card__title">{{ ev.summary || untitledLabel }}</span>
92
- <div class="cn-calendar-card__meta">
93
- <span v-if="ev.dtstart" class="cn-calendar-card__when">{{ formatShort(ev.dtstart) }}</span>
94
- <span v-if="ev.location" class="cn-calendar-card__location">· {{ ev.location }}</span>
86
+ <!-- The board look draws a meeting as a ruled row: its title, one
87
+ meta line (when, where, who) and its status at the end. -->
88
+ <template v-if="boardRows">
89
+ <div class="cn-calendar-card__row-main">
90
+ <span class="cn-calendar-card__title">{{ ev.summary || untitledLabel }}</span>
91
+ <span class="cn-calendar-card__meta" data-testid="cn-calendar-card-meta">{{ boardMetaOf(ev) }}</span>
95
92
  </div>
96
- </div>
93
+ <span v-if="statusOf(ev)"
94
+ class="cn-status-badge"
95
+ :class="`cn-status-badge--${statusVariantOf(ev)}`"
96
+ data-testid="cn-calendar-card-status">{{ statusOf(ev) }}</span>
97
+ </template>
98
+ <template v-else>
99
+ <div class="cn-calendar-card__row-icon">
100
+ <CalendarClock v-if="isUpcoming(ev)" :size="20" class="cn-calendar-card__icon--upcoming" />
101
+ <CalendarCheck v-else :size="20" class="cn-calendar-card__icon--past" />
102
+ </div>
103
+ <div class="cn-calendar-card__row-main">
104
+ <span class="cn-calendar-card__title">{{ ev.summary || untitledLabel }}</span>
105
+ <div class="cn-calendar-card__meta">
106
+ <span v-if="ev.dtstart" class="cn-calendar-card__when">{{ formatShort(ev.dtstart) }}</span>
107
+ <span v-if="ev.location" class="cn-calendar-card__location">· {{ ev.location }}</span>
108
+ </div>
109
+ </div>
110
+ </template>
97
111
  </div>
112
+ <a v-if="boardRows"
113
+ class="cn-calendar-card__open"
114
+ :href="calendarAppUrl"
115
+ target="_blank"
116
+ rel="noopener"
117
+ data-testid="cn-calendar-card-open">{{ openInCalendarLabel }}</a>
98
118
  </div>
99
119
 
100
120
  <!-- Footer: detail surface gets "Open in NC Calendar" CTA -->
101
- <template v-if="surface === 'detail-page'" #footer>
121
+ <template v-if="surface === 'detail-page' && !isBoard" #footer>
102
122
  <a
103
123
  class="cn-calendar-card__cta"
104
124
  :href="calendarAppUrl"
@@ -144,9 +164,12 @@ import CnCalendarEventCreate from '../../../components/CnCalendarEventCreate/CnC
144
164
  import CnCalendarEventPicker from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.vue'
145
165
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
146
166
  import CnIcon from '../../../components/CnIcon/CnIcon.vue'
167
+ import { normalizeLook } from '../../../composables/useLook.js'
168
+ import { formatBoardDate, formatTimeOfDay } from '../../../utils/boardDate.js'
147
169
  import { buildHeaders, prefixUrl } from '../../../utils/index.js'
148
170
  import { userLocale } from '../../../utils/userLocale.js'
149
171
  import { linkCalendarEvent } from './calendarEventLink.js'
172
+ import { eventAttendeeNames, eventStatusLabel, eventStatusVariant } from './eventStatus.js'
150
173
 
151
174
  /**
152
175
  * CnCalendarCard — surface-aware compact widget for the calendar
@@ -158,6 +181,10 @@ export default {
158
181
 
159
182
  components: { CnCalendarEventCreate, CnCalendarEventPicker, CnDetailCard, CnIcon, LinkVariant, NcButton, NcLoadingIcon, Plus, CalendarClock, CalendarCheck },
160
183
 
184
+ inject: {
185
+ cnLook: { default: 'nextcloud' },
186
+ },
187
+
161
188
  props: {
162
189
  /** OpenRegister register id (slug or uuid). Optional for user-dashboard. */
163
190
  register: { type: String, default: '' },
@@ -233,6 +260,16 @@ export default {
233
260
  *
234
261
  * @return {Array<string>}
235
262
  */
263
+ /** @return {boolean} The board look draws ruled rows. */
264
+ isBoard() {
265
+ return normalizeLook(this.cnLook) === 'board'
266
+ },
267
+
268
+ /** @return {boolean} The meetings are ruled rows: the board look on a detail page. */
269
+ boardRows() {
270
+ return this.isBoard && this.surface === 'detail-page'
271
+ },
272
+
236
273
  shownHeaderActions() {
237
274
  if (this.surface !== 'detail-page' || !this.objectId || !Array.isArray(this.headerActions)) {
238
275
  return []
@@ -292,6 +329,34 @@ export default {
292
329
  },
293
330
 
294
331
  methods: {
332
+ /**
333
+ * A meeting's meta line in the board look: "14 okt, 10:00 tot 10:30",
334
+ * its place and its attendees, separated by middots.
335
+ *
336
+ * @param {object} ev The event.
337
+ * @return {string}
338
+ */
339
+ boardMetaOf(ev) {
340
+ const start = ev.dtstart ? new Date(ev.dtstart) : null
341
+ const end = ev.dtend ? new Date(ev.dtend) : null
342
+ let when = ''
343
+ if (start && !Number.isNaN(start.getTime())) {
344
+ when = `${formatBoardDate(start)}, ${formatTimeOfDay(start)}`
345
+ if (end && !Number.isNaN(end.getTime())) {
346
+ when = t('nextcloud-vue', '{start} until {end}', { start: when, end: formatTimeOfDay(end) })
347
+ }
348
+ }
349
+ return [when, ev.location, ...eventAttendeeNames(ev)].filter(Boolean).join(' · ')
350
+ },
351
+
352
+ statusOf(ev) {
353
+ return eventStatusLabel(ev)
354
+ },
355
+
356
+ statusVariantOf(ev) {
357
+ return eventStatusVariant(ev)
358
+ },
359
+
295
360
  /**
296
361
  * A meeting was made on this object: close the form, read the list again.
297
362
  *
@@ -242,6 +242,7 @@ import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
242
242
  import { buildHeaders, prefixUrl } from '../../../utils/index.js'
243
243
  import { userLocale } from '../../../utils/userLocale.js'
244
244
  import { linkCalendarEvent } from './calendarEventLink.js'
245
+ import { eventStatusLabel, eventStatusVariant } from './eventStatus.js'
245
246
 
246
247
  const MAX_AVATARS = 3
247
248
 
@@ -542,31 +543,11 @@ export default {
542
543
  },
543
544
 
544
545
  statusLabel(ev) {
545
- const status = (ev.status || '').toString().toLowerCase()
546
- if (status === 'confirmed') {
547
- return t('nextcloud-vue', 'Confirmed')
548
- }
549
- if (status === 'tentative') {
550
- return t('nextcloud-vue', 'Tentative')
551
- }
552
- if (status === 'cancelled') {
553
- return t('nextcloud-vue', 'Cancelled')
554
- }
555
- return ''
546
+ return eventStatusLabel(ev)
556
547
  },
557
548
 
558
549
  statusVariant(ev) {
559
- const status = (ev.status || '').toString().toLowerCase()
560
- if (status === 'confirmed') {
561
- return 'success'
562
- }
563
- if (status === 'tentative') {
564
- return 'warning'
565
- }
566
- if (status === 'cancelled') {
567
- return 'error'
568
- }
569
- return 'default'
550
+ return eventStatusVariant(ev)
570
551
  },
571
552
 
572
553
  attendeeList(ev) {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * CnCalendarCard in the board look on a detail page (DqZaakTaken
3
+ * "Afspraken"): each meeting is a ruled row with its title, one meta line
4
+ * (when, where, who) and its status at the end, and "Open in Calendar" is a
5
+ * link under the rows rather than a card footer.
6
+ *
7
+ * @spec openspec/changes/board-deck-calendar-rows/specs/integrations/spec.md
8
+ */
9
+
10
+ const { flushPromises, mount } = require('@vue/test-utils')
11
+
12
+ jest.mock('@nextcloud/router', () => ({ generateUrl: (path) => path }))
13
+
14
+ const CnCalendarCard = require('../CnCalendarCard.vue').default
15
+
16
+ const EVENT = { id: 'e1', summary: 'Toelichting stukken', dtstart: '2026-10-14T10:00:00', dtend: '2026-10-14T10:30:00', location: 'stadskantoor, balie 3', attendees: [{ name: 'Sanne de Vries' }], status: 'TENTATIVE' }
17
+
18
+ function mountCard(look) {
19
+ global.fetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ results: [EVENT] }) })
20
+ return mount(CnCalendarCard, {
21
+ props: { register: 'reg', schema: 'case', objectId: 'c-1', surface: 'detail-page' },
22
+ global: { provide: { cnLook: look }, stubs: { NcLoadingIcon: true } },
23
+ })
24
+ }
25
+
26
+ describe('CnCalendarCard board rows', () => {
27
+ afterEach(() => {
28
+ delete global.fetch
29
+ })
30
+
31
+ it('draws the meeting as a row with its meta line and status, and the link under the rows', async () => {
32
+ const w = mountCard('board')
33
+ await flushPromises()
34
+ const row = w.find('.cn-calendar-card__row')
35
+ expect(row.find('.cn-calendar-card__row-icon').exists()).toBe(false)
36
+ const meta = row.find('[data-testid="cn-calendar-card-meta"]').text()
37
+ expect(meta).toContain('stadskantoor, balie 3')
38
+ expect(meta).toContain('Sanne de Vries')
39
+ expect(row.find('[data-testid="cn-calendar-card-status"]').text()).toBe('Tentative')
40
+ expect(w.find('[data-testid="cn-calendar-card-open"]').exists()).toBe(true)
41
+ expect(w.find('.cn-detail-card__footer').exists()).toBe(false)
42
+ w.unmount()
43
+ })
44
+
45
+ it('keeps the icon rows and the footer in the Nextcloud look', async () => {
46
+ const w = mountCard('nextcloud')
47
+ await flushPromises()
48
+ expect(w.find('.cn-calendar-card__row-icon').exists()).toBe(true)
49
+ expect(w.find('.cn-detail-card__footer').exists()).toBe(true)
50
+ w.unmount()
51
+ })
52
+ })
@@ -0,0 +1,63 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * A calendar event's status as a label and a badge tone, shared by the
6
+ * calendar tab and the calendar card.
7
+ */
8
+
9
+ import { translate as t } from '@nextcloud/l10n'
10
+
11
+ /**
12
+ * The event's status in words, or '' when it has none the reader needs.
13
+ *
14
+ * @param {object} ev The event.
15
+ * @return {string}
16
+ */
17
+ export function eventStatusLabel(ev) {
18
+ const status = String(ev?.status || '').toLowerCase()
19
+ if (status === 'confirmed') {
20
+ return t('nextcloud-vue', 'Confirmed')
21
+ }
22
+ if (status === 'tentative') {
23
+ return t('nextcloud-vue', 'Tentative')
24
+ }
25
+ if (status === 'cancelled') {
26
+ return t('nextcloud-vue', 'Cancelled')
27
+ }
28
+ return ''
29
+ }
30
+
31
+ /**
32
+ * The badge tone of the event's status.
33
+ *
34
+ * @param {object} ev The event.
35
+ * @return {string} `success`, `warning`, `error` or `default`.
36
+ */
37
+ export function eventStatusVariant(ev) {
38
+ const status = String(ev?.status || '').toLowerCase()
39
+ if (status === 'confirmed') {
40
+ return 'success'
41
+ }
42
+ if (status === 'tentative') {
43
+ return 'warning'
44
+ }
45
+ if (status === 'cancelled') {
46
+ return 'error'
47
+ }
48
+ return 'default'
49
+ }
50
+
51
+ /**
52
+ * The names of the event's attendees.
53
+ *
54
+ * @param {object} ev The event.
55
+ * @return {Array<string>}
56
+ */
57
+ export function eventAttendeeNames(ev) {
58
+ const raw = ev?.attendees || ev?.participants || []
59
+ if (!Array.isArray(raw)) {
60
+ return []
61
+ }
62
+ return raw.map((att) => (typeof att === 'string' ? att : (att.name || att.displayName || att.cn || att.email || ''))).filter(Boolean)
63
+ }