@conduction/nextcloud-vue 2.140.0 → 2.142.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 (123) hide show
  1. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  2. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
  3. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +24 -3
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +3 -3
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +12 -0
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +57 -40
  14. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -3
  20. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -1
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  23. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  24. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +34 -3
  25. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  26. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -3
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  30. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +1 -1
  31. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +21 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +14 -0
  37. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  38. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
  39. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  40. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
  41. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  42. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  43. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
  44. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  49. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  51. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +3 -1
  52. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  53. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  54. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  55. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -1
  56. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +31 -1
  57. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  58. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  59. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  60. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
  61. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  62. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
  63. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
  65. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
  66. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
  67. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
  68. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
  69. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue.js +1 -1
  70. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js +52 -1
  71. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js.map +1 -1
  72. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js +119 -65
  73. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js.map +1 -1
  74. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  75. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +86 -4
  76. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  77. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +67 -5
  78. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  79. package/dist/esm/l10n/en.json.js +11 -1
  80. package/dist/esm/l10n/en.json.js.map +1 -1
  81. package/dist/esm/l10n/nl.json.js +12 -2
  82. package/dist/esm/l10n/nl.json.js.map +1 -1
  83. package/dist/esm/library-style.js +1 -1
  84. package/dist/esm/nextcloud-vue.css +1116 -686
  85. package/dist/esm/utils/buildViewTree.js +23 -8
  86. package/dist/esm/utils/buildViewTree.js.map +1 -1
  87. package/dist/esm/utils/groupSublines.js +120 -0
  88. package/dist/esm/utils/groupSublines.js.map +1 -0
  89. package/dist/nextcloud-vue.cjs.js +2023 -1239
  90. package/dist/nextcloud-vue.cjs.js.map +2 -2
  91. package/dist/nextcloud-vue.css +1116 -686
  92. package/l10n/en.json +11 -1
  93. package/l10n/nl.json +12 -2
  94. package/package.json +1 -1
  95. package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
  96. package/src/components/CnCellRenderer/CnCellRenderer.vue +26 -4
  97. package/src/components/CnChartWidget/CnChartWidget.md +1 -0
  98. package/src/components/CnChartWidget/CnChartWidget.vue +49 -4
  99. package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
  100. package/src/components/CnDataTable/CnDataTable.md +1 -0
  101. package/src/components/CnDataTable/CnDataTable.vue +28 -4
  102. package/src/components/CnFactsWidget/CnFactsWidget.vue +34 -3
  103. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +44 -4
  104. package/src/components/CnIndexPage/CnIndexPage.vue +22 -1
  105. package/src/components/CnIndexPage/useSelfFetchList.js +14 -0
  106. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
  107. package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
  108. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
  109. package/src/components/CnObjectSidebar/CnFilesTab.vue +4 -1
  110. package/src/components/CnPartyPicker/CnPartyPicker.vue +31 -1
  111. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
  112. package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +33 -9
  113. package/src/css/dialog.css +13 -1
  114. package/src/css/look-board-detail.css +107 -9
  115. package/src/css/look-board-index.css +143 -7
  116. package/src/css/look-board.css +109 -0
  117. package/src/css/table.css +12 -0
  118. package/src/integrations/builtin/contacts/CnContactsTab.vue +101 -26
  119. package/src/integrations/builtin/contacts/__tests__/CnContactsTabBoardRows.spec.js +67 -0
  120. package/src/integrations/builtin/deck/CnDeckCard.vue +121 -2
  121. package/src/integrations/builtin/deck/__tests__/CnDeckCardHeaderActions.spec.js +68 -0
  122. package/src/utils/buildViewTree.js +23 -8
  123. package/src/utils/groupSublines.js +125 -0
@@ -52,13 +52,11 @@
52
52
  grid-row: 2;
53
53
  }
54
54
 
55
- /* A list page's breadcrumb and note read 15px; the case page's row stays 14px. */
56
- .cn-look-board .cn-index-page .cn-page-header .cn-header-trail.cn-detail-page__header-row2 {
57
- font-size: 15px;
58
- }
59
-
60
- .cn-look-board .cn-index-page .cn-header-trail.cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb .button-vue.button-vue {
61
- font-size: 15px;
55
+ /* The note after the breadcrumb ("38 contracten bij 21 leveranciers") is 14px
56
+ muted on every Dq list board (37 of 37).
57
+ @spec openspec/changes/board-index-header-meta-14/specs/index-page/spec.md */
58
+ .cn-look-board .cn-index-page .cn-page-header .cn-header-trail .cn-detail-page__header-meta {
59
+ font-size: 14px;
62
60
  }
63
61
 
64
62
  .cn-look-board .cn-index-page .cn-page-header .cn-header-trail ~ .cn-page-header__description {
@@ -982,6 +980,17 @@
982
980
  text-decoration: none;
983
981
  }
984
982
 
983
+ /* `rowTitle: "strong"` (DqZaaktypeStatussen "Statussen", "Resultaten"): the
984
+ name column of a card's list, the cell's 14px at weight 600 in the main
985
+ ink, no underline; the row stays a link.
986
+ @spec openspec/changes/table-strong-title/specs/table-strong-title/spec.md#requirement-a-tables-title-column-can-read-as-a-strong-name */
987
+ .cn-look-board .cn-table-container--board.cn-table-container--title-strong .cn-data-table td.cn-table-col--title {
988
+ color: var(--color-main-text);
989
+ font-size: 14px;
990
+ font-weight: 600;
991
+ text-decoration: none;
992
+ }
993
+
985
994
  /* An avatar in the title column (PtAccounts): a 32px chip circle, 10px before
986
995
  the name, the e-mail under the name beside it. The name keeps the title's
987
996
  weight and size, and its underline: an inline-flex box is atomic, so the
@@ -999,6 +1008,19 @@
999
1008
  font-weight: 700;
1000
1009
  }
1001
1010
 
1011
+ /* `widgetProps.tone: "neutral"`: the grey person chip (DqContacten), 13px,
1012
+ 12px before the name.
1013
+ @spec openspec/changes/board-avatar-chip/specs/data-table/spec.md#requirement-a-board-avatar-cell-draws-the-initials-chip */
1014
+ .cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar-initials--neutral {
1015
+ background: var(--color-background-dark);
1016
+ color: var(--color-main-text);
1017
+ font-size: 13px;
1018
+ }
1019
+
1020
+ .cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar:has(.cn-cell-renderer__avatar-initials--neutral) {
1021
+ gap: 12px;
1022
+ }
1023
+
1002
1024
  .cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title .cn-cell-renderer__avatar-name {
1003
1025
  text-decoration: underline;
1004
1026
  text-underline-offset: 2px;
@@ -2101,3 +2123,117 @@
2101
2123
  .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > .cn-index-page__quick-filter-row {
2102
2124
  margin-bottom: calc(var(--cn-board-section-gap) + 12px);
2103
2125
  }
2126
+
2127
+ /* ---- The side panel's filters as checkbox lists (DqZakenSorteren) ------- */
2128
+ /* "Filters" with "Alles wissen" beside it; per field its 14px semibold label
2129
+ over rows of a checkbox, the value and its count in 13px muted at the end,
2130
+ 32px each.
2131
+ @spec openspec/changes/board-sidebar-filters-as-checkbox-lists/specs/index-sidebar/spec.md#requirement-the-board-look-draws-a-short-filter-as-a-checkbox-list-with-counts */
2132
+ .cn-look-board .cn-index-sidebar__filters-head {
2133
+ display: flex;
2134
+ align-items: center;
2135
+ justify-content: space-between;
2136
+ gap: 8px;
2137
+ margin-bottom: 14px;
2138
+ }
2139
+
2140
+ .cn-look-board .cn-index-sidebar__filters-head h3 {
2141
+ margin: 0;
2142
+ font-size: 15px;
2143
+ font-weight: 700;
2144
+ }
2145
+
2146
+ .cn-look-board .cn-index-sidebar__filters-clear {
2147
+ min-height: 0;
2148
+ margin: 0;
2149
+ padding: 0;
2150
+ border: 0;
2151
+ background: transparent;
2152
+ color: var(--color-primary-element);
2153
+ font-size: 14px;
2154
+ font-weight: 600;
2155
+ text-decoration: underline;
2156
+ cursor: pointer;
2157
+ }
2158
+
2159
+ .cn-look-board .cn-index-sidebar__filter-group:has(> .cn-index-sidebar__checklist) .cn-index-sidebar__filter-label {
2160
+ font-size: 14px;
2161
+ font-weight: 600;
2162
+ }
2163
+
2164
+ .cn-look-board .cn-index-sidebar__checklist {
2165
+ display: flex;
2166
+ flex-direction: column;
2167
+ gap: 4px;
2168
+ margin: 0;
2169
+ padding: 0;
2170
+ border: 0;
2171
+ }
2172
+
2173
+ .cn-look-board .cn-index-sidebar__checklist-row {
2174
+ display: flex;
2175
+ align-items: center;
2176
+ gap: 10px;
2177
+ min-height: 32px;
2178
+ font-size: 14px;
2179
+ cursor: pointer;
2180
+ }
2181
+
2182
+ .cn-look-board .cn-index-sidebar__checklist-row > input[type='checkbox'] {
2183
+ width: 18px;
2184
+ min-width: 18px;
2185
+ height: 18px;
2186
+ min-height: 18px;
2187
+ margin: 0;
2188
+ accent-color: var(--color-primary-element);
2189
+ }
2190
+
2191
+ .cn-look-board .cn-index-sidebar__checklist-label {
2192
+ flex: 1;
2193
+ min-width: 0;
2194
+ }
2195
+
2196
+ .cn-look-board .cn-index-sidebar__checklist-count {
2197
+ color: var(--color-text-maxcontrast);
2198
+ font-size: 13px;
2199
+ }
2200
+
2201
+ /* A table's leading icon on a 32px tile, radius 8, an 18px glyph, 12px
2202
+ before the title (DqOrganisaties, PqOrganisaties: teal; most boards: the
2203
+ light primary). The tile's tone is the page's `rowIconTone`.
2204
+ @spec openspec/changes/board-row-icon-tile/specs/data-table/spec.md#requirement-a-board-tables-leading-icon-sits-on-a-tile */
2205
+ .cn-look-board .cn-data-table td.cn-table-col--icon,
2206
+ .cn-look-board .cn-data-table th.cn-table-col--icon {
2207
+ width: 48px;
2208
+ padding-inline: 16px 0;
2209
+ }
2210
+
2211
+ .cn-look-board .cn-data-table td.cn-table-col--icon .material-design-icon {
2212
+ width: 32px;
2213
+ height: 32px;
2214
+ border-radius: 8px;
2215
+ justify-content: center;
2216
+ align-items: center;
2217
+ background: var(--color-primary-element-light);
2218
+ color: var(--color-primary-element-light-text);
2219
+ }
2220
+
2221
+ .cn-look-board .cn-data-table td.cn-table-col--icon-teal .material-design-icon {
2222
+ background: var(--cn-status-teal-bg);
2223
+ color: var(--cn-status-teal-text);
2224
+ }
2225
+
2226
+ .cn-look-board .cn-data-table td.cn-table-col--icon-neutral .material-design-icon {
2227
+ background: var(--color-background-dark);
2228
+ color: var(--color-main-text);
2229
+ }
2230
+
2231
+ .cn-look-board .cn-data-table td.cn-table-col--icon .material-design-icon svg {
2232
+ width: 18px;
2233
+ height: 18px;
2234
+ }
2235
+
2236
+ .cn-look-board .cn-data-table td.cn-table-col--icon + td,
2237
+ .cn-look-board .cn-data-table th.cn-table-col--icon + th {
2238
+ padding-inline-start: 12px;
2239
+ }
@@ -2173,6 +2173,26 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2173
2173
  line-height: normal;
2174
2174
  }
2175
2175
 
2176
+ /* A list in a side card draws a hairline over every row, the first too, so
2177
+ a line separates the title from the list (DqZaaktype*, DqTenant,
2178
+ DqSociaalDomein, DqTaak "Versies" and the like). Its row titles keep the
2179
+ main ink without an underline: the plain side rule marks them as links,
2180
+ and the board underlines on hover only. The footer note is 13px muted; the
2181
+ theme sets paragraphs through its tokens with `!important`, so the note
2182
+ hands it its size and ink through those tokens. */
2183
+ .cn-look-board .cn-detail-page__side .cn-record-list--rows:not(.cn-look-nextcloud *) .cn-record-list__item {
2184
+ border-top: 1px solid var(--cn-board-hairline);
2185
+ }
2186
+
2187
+ .cn-look-board .cn-detail-page__side .cn-record-list--rows:not(.cn-look-nextcloud *) .cn-record-list__row:not(:hover):not(:focus-visible) .cn-record-list__title {
2188
+ text-decoration: none;
2189
+ }
2190
+
2191
+ .cn-look-board .cn-record-list__footer-note:not(.cn-look-nextcloud *) {
2192
+ --nldesign-component-paragraph-font-size: 13px;
2193
+ --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
2194
+ }
2195
+
2176
2196
  /* An audit row is a board list row (DqHandhaving, DqTenant "Historie"): 10px
2177
2197
  above and below, a hairline over every row including the first, the head
2178
2198
  and the line under it at their normal line height, 2px apart: 56px, the
@@ -2412,6 +2432,29 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2412
2432
  gap: 2px;
2413
2433
  }
2414
2434
 
2435
+ /* A stacked list (`density: "stacked"`) keeps its rows unruled and unpadded
2436
+ in the board look too, 10px apart (DqDagAfsluiten "Morgen eerst").
2437
+ @spec openspec/changes/record-list-stacked-density/specs/record-list-stacked-density/spec.md#requirement-a-record-list-can-stack-its-rows */
2438
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--stacked .cn-record-list__row {
2439
+ min-height: 0;
2440
+ padding-block: 0;
2441
+ }
2442
+
2443
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--stacked.cn-record-list--rows .cn-record-list__item + .cn-record-list__item {
2444
+ border-top: 0;
2445
+ }
2446
+
2447
+ /* A dashboard card's task list and decision list rule above every row, the
2448
+ first one under the title included, as the record lists with ruleAbove do
2449
+ (DqMijnWerk "Mijn taken", "Mijn archiefbeoordelingen"); no rule under the
2450
+ last row.
2451
+ @spec openspec/changes/board-dashboard-lists-rule-above/specs/board-dashboard-lists-rule-above/spec.md#requirement-a-dashboard-task-or-decision-list-rules-above-every-row */
2452
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-tasks-widget__row,
2453
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-decision-list__row {
2454
+ border-block-end: 0;
2455
+ border-top: 1px solid var(--color-border);
2456
+ }
2457
+
2415
2458
  /* Hover: the whole row takes the hover ground, 8px beyond the text so the
2416
2459
  text does not move. */
2417
2460
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link {
@@ -2680,6 +2723,17 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2680
2723
  padding: 0;
2681
2724
  }
2682
2725
 
2726
+ /* A chart in a section of a sections widget (a framed section card, 14px
2727
+ 16px in) takes none of the dashboard card's inset either: its bars and plot
2728
+ start 17px in, not 39 (DqDoorlooptijd "Naleving": Naleving per zaaktype,
2729
+ Verdeling doorlooptijd, Maandelijkse SLA-trend, Doorstroom).
2730
+ @spec openspec/changes/board-sections-chart-single-inset/specs/board-sections-chart-single-inset/spec.md#requirement-a-chart-inside-a-dashboard-sections-card-is-inset-once */
2731
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-sections-widget__section .cn-chart-widget__html,
2732
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-sections-widget__section .cn-chart-widget__canvas,
2733
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-sections-widget__section .cn-chart-widget > .cn-chart-widget__footnote {
2734
+ padding: 0;
2735
+ }
2736
+
2683
2737
  /* ---- A plain sections widget on a dashboard card ------------------------ */
2684
2738
  /* The card is flush, because a sections widget usually draws its own cards;
2685
2739
  the plain one draws none, so its body takes the card's side inset and 20px
@@ -2805,6 +2859,27 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2805
2859
  font-weight: 600;
2806
2860
  }
2807
2861
 
2862
+ /* A toned cell takes the theme's status badge grounds, as the board's tones
2863
+ are (DqHandhaving: #E7F3EA, #FFF4DE, #FCEDEC); the darkest tone has its
2864
+ own token and otherwise deepens the error ground. Nextcloud's own mix of
2865
+ the status colours read a shade off each.
2866
+ @spec openspec/changes/board-matrix-tones/specs/board-matrix-tones/spec.md#requirement-a-board-matrix-cell-takes-the-themes-status-grounds */
2867
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) .cn-chart-widget__matrix-cell--success {
2868
+ background: var(--nldesign-component-status-badge-success-background-color, var(--color-success-light, color-mix(in srgb, var(--color-success) 15%, var(--color-main-background))));
2869
+ }
2870
+
2871
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) .cn-chart-widget__matrix-cell--warning {
2872
+ background: var(--nldesign-component-status-badge-warning-background-color, var(--color-warning-light, color-mix(in srgb, var(--color-warning) 15%, var(--color-main-background))));
2873
+ }
2874
+
2875
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) .cn-chart-widget__matrix-cell--error {
2876
+ background: var(--nldesign-component-status-badge-error-background-color, var(--color-error-light, color-mix(in srgb, var(--color-error) 12%, var(--color-main-background))));
2877
+ }
2878
+
2879
+ .cn-look-board .cn-chart-widget__matrix:not(.cn-look-nextcloud *) .cn-chart-widget__matrix-cell--danger {
2880
+ background: var(--nldesign-component-status-badge-danger-background-color, color-mix(in srgb, var(--color-error) 10%, var(--nldesign-component-status-badge-error-background-color, var(--color-main-background))));
2881
+ }
2882
+
2808
2883
  /* A cell without a tone ("geen") sits on the page ground in the muted ink,
2809
2884
  as the board draws it; toned and coloured cells keep their ground.
2810
2885
  @spec openspec/changes/board-matrix-plain-cells/specs/board-matrix-plain-cells/spec.md#requirement-a-board-matrix-cell-without-a-tone-sits-on-the-ground-in-the-muted-ink */
@@ -3002,6 +3077,23 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3002
3077
  line-height: normal;
3003
3078
  }
3004
3079
 
3080
+ /* A bar list with the value at the bar's end (DqProcesanalyse "Doorlooptijd
3081
+ per status") sets its rows in 13px, the label in the secondary text, the
3082
+ value in the main ink at 600, rows 34px apart.
3083
+ @spec openspec/changes/chart-bar-list-value-at-end/specs/chart-bar-list-value-at-end/spec.md#requirement-a-bar-list-can-put-each-value-at-its-bars-end */
3084
+ .cn-look-board .cn-chart-widget__bar-list--value-at-end:not(.cn-look-nextcloud *) {
3085
+ gap: 16px;
3086
+ }
3087
+
3088
+ .cn-look-board .cn-chart-widget__bar-list--value-at-end:not(.cn-look-nextcloud *) .cn-chart-widget__bar-line {
3089
+ font-size: 13px;
3090
+ }
3091
+
3092
+ .cn-look-board .cn-chart-widget__bar-list--value-at-end:not(.cn-look-nextcloud *) .cn-chart-widget__bar-label {
3093
+ --nldesign-color-on-surface: var(--cn-board-text-soft);
3094
+ color: var(--cn-board-text-soft);
3095
+ }
3096
+
3005
3097
  /* A dialog's options as the boards draw them (DqVolgendeStap, DqBesluit,
3006
3098
  DqDocumentGenereren, DqFilterBewerken): rounded at 10px, an idle option
3007
3099
  edged in the dark border grey, and an 18px radio. The chosen option keeps
@@ -3023,6 +3115,23 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3023
3115
  background: var(--cn-board-sunken-ground);
3024
3116
  }
3025
3117
 
3118
+ /* An option's two lines at line height 1.2 and its icon at the top of the
3119
+ row, as DqVolgendeStap draws them (rows 60 high, not 70). */
3120
+ .cn-look-board .cn-choice-cards .cn-choice-cards__row-title:not(.cn-look-nextcloud *),
3121
+ .cn-look-board .cn-choice-cards .cn-choice-cards__row-description:not(.cn-look-nextcloud *) {
3122
+ line-height: 1.2;
3123
+ }
3124
+
3125
+ .cn-look-board .cn-choice-cards .cn-choice-cards__row-icon:not(.cn-look-nextcloud *) {
3126
+ align-self: flex-start;
3127
+ }
3128
+
3129
+ /* The chosen option's fields start under its text: radio 18, icon 20 and
3130
+ two 12px gaps past the row's 14px inset. */
3131
+ .cn-look-board .cn-choice-cards .cn-choice-cards__cell--with-details:has(.cn-choice-cards__row-icon) .cn-choice-cards__details:not(.cn-look-nextcloud *) {
3132
+ padding-inline-start: 76px;
3133
+ }
3134
+
3026
3135
  /* A chosen option stays white under its primary edge, as every Dq dialog board
3027
3136
  draws it (design-system #371); the Nextcloud look keeps the tint.
3028
3137
  @spec openspec/changes/board-choice-row-chosen-white/specs/board-choice-row-chosen-white/spec.md#requirement-a-chosen-board-option-is-white */
package/src/css/table.css CHANGED
@@ -226,6 +226,18 @@
226
226
  overflow-wrap: anywhere;
227
227
  }
228
228
 
229
+ /* In a table fitted to its tile every column but the growing one asks for
230
+ as little width as its content allows (`width: 1%`). With
231
+ `overflow-wrap: anywhere` that is one character, so a wrapping head broke
232
+ letter by letter ("D o e l", DqDoorlooptijd "Prestaties per zaaktype",
233
+ 240px tall). Here a wrapping column breaks between words and only breaks
234
+ a word that is longer than the column.
235
+ @spec openspec/changes/fit-table-wrap-by-word/specs/fit-table-wrap-by-word/spec.md#requirement-a-wrapping-column-in-a-fitted-table-breaks-between-words */
236
+ .cn-data-table--fit th.cn-cell--wrap,
237
+ .cn-data-table--fit td.cn-cell--wrap {
238
+ overflow-wrap: break-word;
239
+ }
240
+
229
241
  /* Opt-in fixed layout (CnDataTable's `fixedLayout` prop).
230
242
 
231
243
  Under the default `table-layout: auto` a column's declared `width` is only a
@@ -87,7 +87,7 @@
87
87
  class="cn-contacts-tab__group">
88
88
  <h3 class="cn-contacts-tab__group-title">
89
89
  {{ group.label }}
90
- <span class="cn-contacts-tab__group-count">{{ group.items.length }}</span>
90
+ <span v-if="!isBoard" class="cn-contacts-tab__group-count">{{ group.items.length }}</span>
91
91
  </h3>
92
92
  <ul class="cn-contacts-tab__list">
93
93
  <li
@@ -97,7 +97,7 @@
97
97
  @click="openReverseLookup(contact)">
98
98
  <div class="cn-contacts-tab__avatar">
99
99
  <NcAvatar
100
- :size="40"
100
+ :size="isBoard ? 32 : 40"
101
101
  :displayName="contact.displayName || unknownLabel"
102
102
  :user="avatarSeed(contact)"
103
103
  :url="contact.avatarUrl || undefined"
@@ -112,7 +112,7 @@
112
112
  {{ contact.displayName || unknownLabel }}
113
113
  </span>
114
114
  <CnStatusBadge
115
- v-if="contact.role"
115
+ v-if="contact.role && !isBoard"
116
116
  :label="roleLabel(contact)"
117
117
  :variant="roleVariant(contact)"
118
118
  size="small" />
@@ -123,30 +123,54 @@
123
123
  size="small"
124
124
  data-testid="cn-contacts-tab-inactive" />
125
125
  </div>
126
- <div v-if="contact.email" class="cn-contacts-tab__sub">
127
- <Email :size="14" class="cn-contacts-tab__sub-icon" />
128
- <span class="cn-contacts-tab__sub-text">{{ contact.email }}</span>
129
- </div>
130
- <div v-if="contact.phone" class="cn-contacts-tab__sub">
131
- <Phone :size="14" class="cn-contacts-tab__sub-icon" />
132
- <span class="cn-contacts-tab__sub-text">{{ contact.phone }}</span>
133
- </div>
134
- <div v-if="contact.org" class="cn-contacts-tab__sub">
135
- <OfficeBuildingOutline :size="14" class="cn-contacts-tab__sub-icon" />
136
- <span class="cn-contacts-tab__sub-text">{{ contact.org }}</span>
137
- </div>
138
- <div
139
- v-if="periodOf(contact)"
140
- class="cn-contacts-tab__sub"
141
- data-testid="cn-contacts-tab-period">
142
- <CalendarRange :size="14" class="cn-contacts-tab__sub-icon" />
143
- <span class="cn-contacts-tab__sub-text">{{ periodOf(contact) }}</span>
144
- </div>
145
- <div v-if="contact.note" class="cn-contacts-tab__sub">
146
- <span class="cn-contacts-tab__sub-text">{{ contact.note }}</span>
147
- </div>
126
+ <!-- The board look sets the details on one line (DqZaakBetrokkenen). -->
127
+ <span v-if="isBoard && boardMetaOf(contact)"
128
+ class="cn-contacts-tab__meta"
129
+ data-testid="cn-contacts-tab-meta">{{ boardMetaOf(contact) }}</span>
130
+ <template v-if="!isBoard">
131
+ <div v-if="contact.email" class="cn-contacts-tab__sub">
132
+ <Email :size="14" class="cn-contacts-tab__sub-icon" />
133
+ <span class="cn-contacts-tab__sub-text">{{ contact.email }}</span>
134
+ </div>
135
+ <div v-if="contact.phone" class="cn-contacts-tab__sub">
136
+ <Phone :size="14" class="cn-contacts-tab__sub-icon" />
137
+ <span class="cn-contacts-tab__sub-text">{{ contact.phone }}</span>
138
+ </div>
139
+ <div v-if="contact.org" class="cn-contacts-tab__sub">
140
+ <OfficeBuildingOutline :size="14" class="cn-contacts-tab__sub-icon" />
141
+ <span class="cn-contacts-tab__sub-text">{{ contact.org }}</span>
142
+ </div>
143
+ <div
144
+ v-if="periodOf(contact)"
145
+ class="cn-contacts-tab__sub"
146
+ data-testid="cn-contacts-tab-period">
147
+ <CalendarRange :size="14" class="cn-contacts-tab__sub-icon" />
148
+ <span class="cn-contacts-tab__sub-text">{{ periodOf(contact) }}</span>
149
+ </div>
150
+ <div v-if="contact.note" class="cn-contacts-tab__sub">
151
+ <span class="cn-contacts-tab__sub-text">{{ contact.note }}</span>
152
+ </div>
153
+ </template>
148
154
  </div>
155
+ <template v-if="isBoard">
156
+ <span v-if="boardPeriodOf(contact)"
157
+ class="cn-contacts-tab__period"
158
+ data-testid="cn-contacts-tab-board-period">{{ boardPeriodOf(contact) }}</span>
159
+ <NcActions class="cn-contacts-tab__menu"
160
+ variant="secondary"
161
+ :aria-label="moreActionsLabel(contact)"
162
+ data-testid="cn-contacts-tab-menu"
163
+ @click.stop>
164
+ <NcActionButton :closeAfterClick="true" @click="unlink(contact)">
165
+ <template #icon>
166
+ <Close :size="20" />
167
+ </template>
168
+ {{ unlinkLabel }}
169
+ </NcActionButton>
170
+ </NcActions>
171
+ </template>
149
172
  <button
173
+ v-else
150
174
  type="button"
151
175
  class="cn-contacts-tab__unlink"
152
176
  :aria-label="unlinkLabel"
@@ -178,7 +202,7 @@
178
202
 
179
203
  <script>
180
204
  import { translate as t } from '@nextcloud/l10n'
181
- import { NcAvatar, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
205
+ import { NcActionButton, NcActions, NcAvatar, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
182
206
  import AccountMultipleOutline from 'vue-material-design-icons/AccountMultipleOutline.vue'
183
207
  import AccountPlus from 'vue-material-design-icons/AccountPlus.vue'
184
208
  import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
@@ -193,6 +217,7 @@ import CnContactPicker from '../../../components/CnContactPicker/CnContactPicker
193
217
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
194
218
  import CnWidgetHeaderActionButtons from '../../../components/CnWidgetWrapper/CnWidgetHeaderActionButtons.vue'
195
219
  import { normalizeLook } from '../../../composables/useLook.js'
220
+ import { formatBoardDate } from '../../../utils/boardDate.js'
196
221
  import { buildHeaders, prefixUrl } from '../../../utils/index.js'
197
222
  import { WIDGET_BODY_ACTIONS } from '../../../utils/widgetHeaderActions.js'
198
223
 
@@ -223,6 +248,8 @@ export default {
223
248
  name: 'CnContactsTab',
224
249
 
225
250
  components: {
251
+ NcActionButton,
252
+ NcActions,
226
253
  NcAvatar,
227
254
  NcButton,
228
255
  NcEmptyContent,
@@ -313,6 +340,11 @@ export default {
313
340
  },
314
341
 
315
342
  computed: {
343
+ /** @return {boolean} The board look draws the rows as DqZaakBetrokkenen does. */
344
+ isBoard() {
345
+ return normalizeLook(this.cnLook) === 'board'
346
+ },
347
+
316
348
  /**
317
349
  * The two header buttons in the order and weight of the look: the
318
350
  * board look draws Link contact then New contact, both outlined; the
@@ -605,6 +637,49 @@ export default {
605
637
  *
606
638
  * @return {string} The window.
607
639
  */
640
+ /**
641
+ * A person's details on one line for the board look: e-mail, phone,
642
+ * organisation and note, separated by middots.
643
+ *
644
+ * @param {object} contact The contact link.
645
+ * @return {string}
646
+ */
647
+ boardMetaOf(contact) {
648
+ return [contact?.email, contact?.phone, contact?.org, contact?.note].filter((part) => typeof part === 'string' && part !== '').join(' · ')
649
+ },
650
+
651
+ /**
652
+ * The validity window in the board's short dates: "Vanaf 3 okt",
653
+ * "Tot 31 okt" or "3 okt – 31 okt".
654
+ *
655
+ * @param {object} contact The contact link.
656
+ * @return {string}
657
+ */
658
+ boardPeriodOf(contact) {
659
+ const from = formatBoardDate(contact?.validFrom || '')
660
+ const until = formatBoardDate(contact?.validUntil || '')
661
+ if (from === '' && until === '') {
662
+ return ''
663
+ }
664
+ if (until === '') {
665
+ return t('nextcloud-vue', 'From {date}', { date: from })
666
+ }
667
+ if (from === '') {
668
+ return t('nextcloud-vue', 'Until {date}', { date: until })
669
+ }
670
+ return `${from} – ${until}`
671
+ },
672
+
673
+ /**
674
+ * The label of a person's actions menu.
675
+ *
676
+ * @param {object} contact The contact link.
677
+ * @return {string}
678
+ */
679
+ moreActionsLabel(contact) {
680
+ return t('nextcloud-vue', 'More actions for {name}', { name: contact?.displayName || this.unknownLabel })
681
+ },
682
+
608
683
  periodOf(contact) {
609
684
  const from = contact?.validFrom || ''
610
685
  const until = contact?.validUntil || ''
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SPDX-License-Identifier: EUPL-1.2
3
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
4
+ *
5
+ * CnContactsTab in the board look draws a person as DqZaakBetrokkenen
6
+ * "Partijen" does: no role badge under a group that already names the role
7
+ * (r26a showed a role id as a badge), no count beside the group heading, the
8
+ * contact details on one line, the period at the right in the board's short
9
+ * date ("Vanaf 3 okt"), and the remove action in a "…" menu.
10
+ *
11
+ * @spec openspec/changes/board-contacts-rows/specs/contacts/spec.md
12
+ */
13
+
14
+ const { flushPromises, mount } = require('@vue/test-utils')
15
+ const CnContactsTab = require('../CnContactsTab.vue').default
16
+ const { formatBoardDate } = require('../../../../utils/boardDate.js')
17
+
18
+ const ROWS = [{
19
+ id: 1,
20
+ contactUid: 'c1',
21
+ displayName: 'Sanne de Vries',
22
+ role: '3c0f5a00-0000-4000-a000-0000000c0001',
23
+ email: 'sanne@example.nl',
24
+ note: 'ingelogd met DigiD',
25
+ validFrom: '2026-10-03',
26
+ }]
27
+
28
+ function mountTab(look) {
29
+ return mount(CnContactsTab, {
30
+ propsData: { objectId: 'o1', register: 'r1', schema: 's1' },
31
+ global: { provide: { cnLook: look } },
32
+ })
33
+ }
34
+
35
+ describe('CnContactsTab board rows', () => {
36
+ beforeEach(() => {
37
+ global.fetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ results: ROWS, total: 1 }) })
38
+ })
39
+
40
+ afterEach(() => {
41
+ delete global.fetch
42
+ })
43
+
44
+ it('draws the board row in the board look', async () => {
45
+ const w = mountTab('board')
46
+ await flushPromises()
47
+ const item = w.find('.cn-contacts-tab__item')
48
+ expect(item.find('.cn-contacts-tab__group-count').exists()).toBe(false)
49
+ expect(w.find('.cn-contacts-tab__group-count').exists()).toBe(false)
50
+ expect(item.text()).not.toContain('3c0f5a00')
51
+ expect(item.find('[data-testid="cn-contacts-tab-meta"]').text()).toBe('sanne@example.nl · ingelogd met DigiD')
52
+ expect(item.find('[data-testid="cn-contacts-tab-board-period"]').text()).toBe(`From ${formatBoardDate('2026-10-03')}`)
53
+ expect(item.find('.cn-contacts-tab__unlink').exists()).toBe(false)
54
+ expect(item.find('[data-testid="cn-contacts-tab-menu"]').exists()).toBe(true)
55
+ w.unmount()
56
+ })
57
+
58
+ it('keeps the Nextcloud row in the Nextcloud look', async () => {
59
+ const w = mountTab('nextcloud')
60
+ await flushPromises()
61
+ const item = w.find('.cn-contacts-tab__item')
62
+ expect(w.find('.cn-contacts-tab__group-count').exists()).toBe(true)
63
+ expect(item.find('.cn-contacts-tab__unlink').exists()).toBe(true)
64
+ expect(item.find('[data-testid="cn-contacts-tab-meta"]').exists()).toBe(false)
65
+ w.unmount()
66
+ })
67
+ })