@conduction/nextcloud-vue 2.100.0 → 2.101.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 (127) hide show
  1. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  2. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  3. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  4. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  5. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  6. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  7. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +51 -7
  8. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  11. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  12. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  13. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  14. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  15. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  16. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  17. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  18. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  19. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  20. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  22. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  23. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  24. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  25. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  26. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  27. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  28. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  29. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  30. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  31. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +251 -71
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
  37. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  38. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  39. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  40. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  42. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  43. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  44. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  47. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  48. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  49. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  50. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  51. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  52. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  53. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  54. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  55. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  56. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  57. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  58. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  59. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  60. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  61. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  62. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  63. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  64. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +35 -2
  65. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  66. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  67. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  68. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  69. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  70. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  71. package/dist/esm/composables/indexSources.js +167 -20
  72. package/dist/esm/composables/indexSources.js.map +1 -1
  73. package/dist/esm/composables/useEndpointSource.js +28 -6
  74. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  75. package/dist/esm/index.js +1 -0
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/l10n/en.json.js +37 -1
  78. package/dist/esm/l10n/en.json.js.map +1 -1
  79. package/dist/esm/l10n/nl.json.js +35 -1
  80. package/dist/esm/l10n/nl.json.js.map +1 -1
  81. package/dist/esm/library-style.js +1 -1
  82. package/dist/esm/nextcloud-vue.css +769 -251
  83. package/dist/esm/utils/languageMapText.js +50 -0
  84. package/dist/esm/utils/languageMapText.js.map +1 -0
  85. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  86. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  87. package/dist/esm/utils/mapList.js +71 -0
  88. package/dist/esm/utils/mapList.js.map +1 -0
  89. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  90. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  91. package/dist/nextcloud-vue.cjs.js +8977 -6843
  92. package/dist/nextcloud-vue.cjs.js.map +2 -2
  93. package/dist/nextcloud-vue.css +769 -251
  94. package/l10n/en.json +37 -1
  95. package/l10n/nl.json +35 -1
  96. package/package.json +1 -1
  97. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  98. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  99. package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
  100. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  101. package/src/components/CnExportsWidget/index.js +7 -0
  102. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  103. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  104. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  105. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  106. package/src/components/CnIndexPage/CnIndexPage.vue +240 -4
  107. package/src/components/CnIndexPage/useNamedSource.js +4 -1
  108. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  109. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  110. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  111. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  112. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  113. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  114. package/src/components/CnSetupWizard/CnSetupWizard.vue +35 -2
  115. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  116. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  117. package/src/components/index.js +3 -0
  118. package/src/composables/indexSources.js +166 -19
  119. package/src/composables/useEndpointSource.js +28 -6
  120. package/src/css/index-page.css +117 -0
  121. package/src/css/look-board.css +44 -0
  122. package/src/index.js +1 -0
  123. package/src/schemas/app-manifest-v2.schema.json +31 -1
  124. package/src/utils/languageMapText.js +46 -0
  125. package/src/utils/libraryWidgetKeys.js +3 -0
  126. package/src/utils/mapList.js +67 -0
  127. package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json CHANGED
@@ -1700,7 +1700,27 @@
1700
1700
  "Run test": "Run test",
1701
1701
  "Test with a record": "Test with a record",
1702
1702
  "admin": "admin",
1703
- "Wizard": "Wizard"
1703
+ "Wizard": "Wizard",
1704
+ "{done} of {total} reviewed": "{done} of {total} reviewed",
1705
+ "{open} still to review": "{open} still to review",
1706
+ "{action}: {name}": "{action}: {name}",
1707
+ "On the map": "On the map",
1708
+ "Show them in the table": "Show them in the table",
1709
+ "Linked": "Linked",
1710
+ "Now": "Now",
1711
+ "Tell me above a number": "Tell me above a number",
1712
+ "Threshold": "Threshold",
1713
+ "A message to you when there are {threshold} or more": "A message to you when there are {threshold} or more",
1714
+ "Save this search or open a saved one to set an alert on it.": "Save this search or open a saved one to set an alert on it.",
1715
+ "The alert could not be saved.": "The alert could not be saved.",
1716
+ "Your exports could not be loaded.": "Your exports could not be loaded.",
1717
+ "You have no exports here.": "You have no exports here.",
1718
+ "Expires today": "Expires today",
1719
+ "Download": "Download",
1720
+ "Made {date}": "Made {date}",
1721
+ "expires {date}": "expires {date}",
1722
+ "Loading …": "Loading …",
1723
+ "Reorder {item}: drag, or press the up and down arrow keys": "Reorder {item}: drag, or press the up and down arrow keys"
1704
1724
  },
1705
1725
  "plurals": {
1706
1726
  "{count} selected": [
@@ -1798,6 +1818,22 @@
1798
1818
  "Showing %n flow": [
1799
1819
  "Showing %n flow",
1800
1820
  "Showing %n flows"
1821
+ ],
1822
+ "%n without an address": [
1823
+ "%n without an address",
1824
+ "%n without an address"
1825
+ ],
1826
+ "%n with an address on the map": [
1827
+ "%n with an address on the map",
1828
+ "%n with an address on the map"
1829
+ ],
1830
+ "%n result": [
1831
+ "%n result",
1832
+ "%n results"
1833
+ ],
1834
+ "downloaded %n time": [
1835
+ "downloaded %n time",
1836
+ "downloaded %n times"
1801
1837
  ]
1802
1838
  }
1803
1839
  }
package/l10n/nl.json CHANGED
@@ -1915,7 +1915,25 @@
1915
1915
  "Run test": "Test uitvoeren",
1916
1916
  "Test with a record": "Testen met een record",
1917
1917
  "admin": "beheerder",
1918
- "Wizard": "Assistent"
1918
+ "Wizard": "Assistent",
1919
+ "{done} of {total} reviewed": "{done} van {total} beoordeeld",
1920
+ "{open} still to review": "Nog {open} te beoordelen",
1921
+ "{action}: {name}": "{action}: {name}",
1922
+ "On the map": "Op de kaart",
1923
+ "Show them in the table": "Toon ze in de tabel",
1924
+ "Linked": "Gekoppeld",
1925
+ "Now": "Nu",
1926
+ "Tell me above a number": "Melden boven een aantal",
1927
+ "Threshold": "Grens",
1928
+ "A message to you when there are {threshold} or more": "Bericht aan u als het er {threshold} of meer zijn",
1929
+ "Save this search or open a saved one to set an alert on it.": "Sla deze zoekopdracht op of open een opgeslagen zoekopdracht om er een melding op te zetten.",
1930
+ "The alert could not be saved.": "De melding kon niet worden opgeslagen.",
1931
+ "Your exports could not be loaded.": "Uw exports konden niet worden geladen.",
1932
+ "You have no exports here.": "U heeft hier geen exports.",
1933
+ "Expires today": "Verloopt vandaag",
1934
+ "Made {date}": "Gemaakt {date}",
1935
+ "expires {date}": "verloopt {date}",
1936
+ "Reorder {item}: drag, or press the up and down arrow keys": "{item} verplaatsen: slepen, of de pijltoetsen omhoog en omlaag"
1919
1937
  },
1920
1938
  "plurals": {
1921
1939
  "{count} selected": [
@@ -2013,6 +2031,22 @@
2013
2031
  "Showing %n flow": [
2014
2032
  "%n flow",
2015
2033
  "%n flows"
2034
+ ],
2035
+ "%n without an address": [
2036
+ "%n zonder adres",
2037
+ "%n zonder adres"
2038
+ ],
2039
+ "%n with an address on the map": [
2040
+ "%n met een adres op de kaart",
2041
+ "%n met een adres op de kaart"
2042
+ ],
2043
+ "%n result": [
2044
+ "%n resultaat",
2045
+ "%n resultaten"
2046
+ ],
2047
+ "downloaded %n time": [
2048
+ "%n keer gedownload",
2049
+ "%n keer gedownload"
2016
2050
  ]
2017
2051
  }
2018
2052
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.100.0",
3
+ "version": "2.101.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -87,4 +87,43 @@ under it through the `option-actions` slot (scope `{ option, selected }`):
87
87
  </div>
88
88
  ```
89
89
 
90
+ The chosen option's own fields can also sit inside its frame, under its text,
91
+ through the `option-details` slot (scope `{ option }`): rendered only for the
92
+ selected option, and outside the radio's label.
93
+
94
+ ```vue
95
+ <div style="max-width: 560px;">
96
+ <CnChoiceCards
97
+ label="Next step"
98
+ layout="rows"
99
+ :model-value="'suspend'"
100
+ :options="[
101
+ { value: 'suspend', label: 'Suspend', description: 'The term stops until the answer is in.' },
102
+ { value: 'abort', label: 'Abort' },
103
+ ]">
104
+ <template #option-details="{ option }">
105
+ <p v-if="option.value === 'suspend'" style="margin: 0;">Reason and return date go here.</p>
106
+ </template>
107
+ </CnChoiceCards>
108
+ </div>
109
+ ```
110
+
111
+ A recorded choice shown back with `readonly`: every option at full contrast, the
112
+ chosen one marked, nothing pickable. `disabled` greys the options out instead.
113
+
114
+ ```vue
115
+ <div style="max-width: 560px;">
116
+ <CnChoiceCards
117
+ label="Outcome"
118
+ layout="rows"
119
+ :readonly="true"
120
+ :model-value="'partial'"
121
+ :options="[
122
+ { value: 'public', label: 'Made public' },
123
+ { value: 'partial', label: 'Partly made public' },
124
+ { value: 'none', label: 'Not made public' },
125
+ ]" />
126
+ </div>
127
+ ```
128
+
90
129
  `columns` fixes the card grid's column count; `:columns="1"` stacks the cards in one column.
@@ -20,17 +20,18 @@
20
20
  <div
21
21
  v-for="option in normalizedOptions"
22
22
  :key="String(option.value)"
23
- class="cn-choice-cards__cell">
23
+ class="cn-choice-cards__cell"
24
+ :class="{ 'cn-choice-cards__cell--with-details': hasDetails(option) }">
24
25
  <label
25
26
  class="cn-choice-cards__row"
26
- :class="{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option) }">
27
+ :class="{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option), 'cn-choice-cards__row--readonly': readonly }">
27
28
  <input
28
29
  class="cn-choice-cards__row-input"
29
30
  :type="multiple ? 'checkbox' : 'radio'"
30
31
  :name="groupName"
31
32
  :value="String(option.value)"
32
33
  :checked="isSelected(option)"
33
- :disabled="isDisabled(option)"
34
+ :disabled="isDisabled(option) || readonly"
34
35
  :aria-describedby="option.disabled && option.disabledReason ? reasonId(option) : null"
35
36
  @change="onToggle(option)">
36
37
  <CnIcon v-if="option.icon"
@@ -47,6 +48,16 @@
47
48
  data-testid="cn-choice-cards-reason">{{ option.disabledReason }}</span>
48
49
  </span>
49
50
  </label>
51
+ <div v-if="hasDetails(option)"
52
+ class="cn-choice-cards__details"
53
+ :data-testid="`cn-choice-cards-details-${option.value}`">
54
+ <!-- @slot option-details The chosen option's own fields, drawn
55
+ inside the selected row's frame under its text (DqVolgendeStap:
56
+ Opschorten's reason, days and return date). Rendered only for a
57
+ selected option, outside the label so a field is not part of
58
+ the radio's label. Scope: `{ option }`. -->
59
+ <slot name="option-details" :option="option" />
60
+ </div>
50
61
  <div v-if="$slots['option-actions']" class="cn-choice-cards__actions">
51
62
  <slot name="option-actions" :option="option" :selected="isSelected(option)" />
52
63
  </div>
@@ -64,7 +75,7 @@
64
75
  class="cn-choice-cards__cell">
65
76
  <label
66
77
  class="cn-choice-cards__option"
67
- :class="{ 'cn-choice-cards__option--selected': isSelected(option) }">
78
+ :class="{ 'cn-choice-cards__option--selected': isSelected(option), 'cn-choice-cards__option--disabled': isDisabled(option) }">
68
79
  <!-- A REAL, VISIBLE input, not a styled div with a click handler.
69
80
  It carries the checked state to assistive technology and to
70
81
  Windows high-contrast mode for free, and it keeps the
@@ -76,7 +87,7 @@
76
87
  :name="groupName"
77
88
  :value="String(option.value)"
78
89
  :checked="isSelected(option)"
79
- :disabled="isDisabled(option)"
90
+ :disabled="isDisabled(option) || readonly"
80
91
  :aria-describedby="option.disabled && option.disabledReason ? reasonId(option) : null"
81
92
  @change="onToggle(option)">
82
93
  <CnCard
@@ -191,6 +202,16 @@ export default {
191
202
  default: false,
192
203
  },
193
204
 
205
+ /**
206
+ * Show the choice without letting it change: every option at full
207
+ * contrast, the selected one marked, nothing pickable (DqBesluit's
208
+ * recorded outcome). `disabled` greys the options out instead.
209
+ */
210
+ readonly: {
211
+ type: Boolean,
212
+ default: false,
213
+ },
214
+
194
215
  /** Show a spinner instead of the grid while options are being fetched. */
195
216
  loading: {
196
217
  type: Boolean,
@@ -311,6 +332,17 @@ export default {
311
332
  * @param {object} option The normalised option.
312
333
  * @return {boolean} True when selected.
313
334
  */
335
+ /**
336
+ * Whether a rows option draws the `option-details` slot in its frame:
337
+ * the slot is given and the option is the chosen one.
338
+ *
339
+ * @param {object} option The option.
340
+ * @return {boolean}
341
+ */
342
+ hasDetails(option) {
343
+ return Boolean(this.$slots['option-details']) && this.isSelected(option)
344
+ },
345
+
314
346
  /**
315
347
  * Whether an option cannot be picked: the whole group is disabled, or the option is.
316
348
  *
@@ -342,7 +374,7 @@ export default {
342
374
  * @return {void}
343
375
  */
344
376
  onToggle(option) {
345
- if (this.isDisabled(option)) {
377
+ if (this.readonly || this.isDisabled(option)) {
346
378
  return
347
379
  }
348
380
  if (!this.multiple) {
@@ -452,7 +484,7 @@ export default {
452
484
  cursor: default;
453
485
  }
454
486
 
455
- .cn-choice-cards__input:disabled + :deep(.cn-card) {
487
+ .cn-choice-cards__option--disabled .cn-choice-cards__input + :deep(.cn-card) {
456
488
  opacity: 0.6;
457
489
  }
458
490
 
@@ -462,11 +494,12 @@ export default {
462
494
  gap: 8px;
463
495
  }
464
496
 
497
+ /* 12px 14px, as the boards draw an option row (DqVolgendeStap, DqBesluit). */
465
498
  .cn-choice-cards__row {
466
499
  display: flex;
467
500
  align-items: flex-start;
468
501
  gap: 12px;
469
- padding: 14px 16px;
502
+ padding: 12px 14px;
470
503
  border: 1px solid var(--color-border);
471
504
  border-radius: var(--border-radius-large, 8px);
472
505
  background: var(--color-main-background);
@@ -485,9 +518,54 @@ export default {
485
518
  background: var(--color-primary-element-light);
486
519
  }
487
520
 
488
- .cn-choice-cards__row--disabled {
521
+ /* An option this record does not allow: the grey ground and the muted ink,
522
+ still legible (DqVolgendeStap), not faded. */
523
+ .cn-choice-cards__row--disabled,
524
+ .cn-choice-cards__row--disabled:hover {
525
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
526
+ border-color: var(--color-border);
527
+ background: var(--color-background-hover);
528
+ color: var(--color-text-maxcontrast);
489
529
  cursor: default;
490
- opacity: 0.6;
530
+ }
531
+
532
+ /* The title takes the muted ink itself: a theme that colours every label
533
+ with `!important` (thematiq) would otherwise hand it the label colour. */
534
+ .cn-choice-cards__row--disabled .cn-choice-cards__row-title {
535
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
536
+ color: var(--color-text-maxcontrast);
537
+ }
538
+
539
+ /* The chosen option's fields share the selected row's frame: the cell draws
540
+ the 2px primary border and the ground, the row inside it draws none, and
541
+ the fields sit under the text, indented to it (DqVolgendeStap). */
542
+ .cn-choice-cards__cell--with-details {
543
+ border: 1px solid var(--color-primary-element);
544
+ border-radius: var(--border-radius-large, 8px);
545
+ box-shadow: inset 0 0 0 1px var(--color-primary-element);
546
+ background: var(--color-primary-element-light);
547
+ }
548
+
549
+ .cn-choice-cards__cell--with-details .cn-choice-cards__row,
550
+ .cn-choice-cards__cell--with-details .cn-choice-cards__row:hover {
551
+ border-color: transparent;
552
+ box-shadow: none;
553
+ background: transparent;
554
+ }
555
+
556
+ .cn-choice-cards__details {
557
+ display: flex;
558
+ flex-direction: column;
559
+ gap: 14px;
560
+ padding: 0 14px 14px 44px;
561
+ }
562
+
563
+ .cn-choice-cards__row--readonly {
564
+ cursor: default;
565
+ }
566
+
567
+ .cn-choice-cards__row--readonly:not(.cn-choice-cards__row--selected):hover {
568
+ border-color: var(--color-border);
491
569
  }
492
570
 
493
571
  .cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {
@@ -518,15 +596,14 @@ export default {
518
596
  font-weight: 600;
519
597
  }
520
598
 
521
- .cn-choice-cards__row-description {
522
- font-size: 13px;
523
- color: var(--color-text-maxcontrast);
524
- }
525
-
599
+ /* Regular weight on the span itself: a theme that sets every label bold
600
+ with `!important` (thematiq) reaches a span that only inherits. */
601
+ .cn-choice-cards__row-description,
526
602
  .cn-choice-cards__reason {
603
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
527
604
  font-size: 13px;
605
+ font-weight: 400;
528
606
  color: var(--color-text-maxcontrast);
529
- font-style: italic;
530
607
  }
531
608
 
532
609
  .cn-choice-cards__reason--card {
@@ -42,12 +42,13 @@
42
42
  group ends the description line ("Week 41 · alle teams ·
43
43
  [Deze week | Dit kwartaal | Dit jaar]"). -->
44
44
  <div
45
- v-if="datePeriodInDescription"
45
+ v-if="datePeriodInDescription || descriptionPageFilters.length > 0"
46
46
  class="cn-dashboard-page__description cn-dashboard-page__description--with-period"
47
47
  data-testid="cn-dashboard-page-subtitle">
48
48
  <span v-if="subtitleText">{{ subtitleText }}</span>
49
49
  <span v-if="subtitleText" aria-hidden="true">&middot;</span>
50
50
  <CnSegmentedControl
51
+ v-if="datePeriodInDescription"
51
52
  size="compact"
52
53
  mode="toggle"
53
54
  :options="segmentedOptions"
@@ -55,6 +56,20 @@
55
56
  :ariaLabel="datePeriodLabel"
56
57
  data-testid="cn-dashboard-page-date-segmented"
57
58
  @update:modelValue="onSegmentPick" />
59
+ <!-- A page filter with `control: "segmented"` and
60
+ `placement: "description"` (DqProcesanalyse
61
+ "Gemiddeld | Mediaan"): a segmented group on the
62
+ description line that writes the workspace context. -->
63
+ <CnSegmentedControl
64
+ v-for="pf in descriptionPageFilters"
65
+ :key="pf.key"
66
+ size="compact"
67
+ mode="toggle"
68
+ :options="pageFilterSegments(pf)"
69
+ :modelValue="workspaceContext[pf.key]"
70
+ :ariaLabel="pf.label ? effectiveTranslate(pf.label) : pf.key"
71
+ :data-testid="'cn-page-filter-' + pf.key"
72
+ @update:modelValue="onPageFilterChange(pf, $event)" />
58
73
  </div>
59
74
  <p v-else-if="subtitleText" class="cn-dashboard-page__description" data-testid="cn-dashboard-page-subtitle">
60
75
  {{ subtitleText }}
@@ -316,23 +331,33 @@
316
331
  `@page.<key>` / `@workspace.<key>` token (e.g. an endpoint KPI's URL
317
332
  interpolating a chosen period). -->
318
333
  <div
319
- v-if="pageFilters && pageFilters.length"
334
+ v-if="rowPageFilters.length > 0"
320
335
  class="cn-dashboard-page__page-filters"
321
336
  data-testid="cn-dashboard-page-page-filters">
322
- <label
323
- v-for="pf in pageFilters"
324
- :key="pf.key"
325
- class="cn-dashboard-page__page-filter">
326
- <span v-if="pf.label" class="cn-dashboard-page__page-filter-label">{{ pf.label }}</span>
327
- <NcSelect
328
- :modelValue="selectedPageFilterOption(pf)"
329
- :options="pf.options || []"
330
- :clearable="false"
331
- :inputLabel="pf.label || pf.key"
332
- label="label"
337
+ <template v-for="pf in rowPageFilters" :key="pf.key">
338
+ <CnSegmentedControl
339
+ v-if="pf.control === 'segmented'"
340
+ size="compact"
341
+ mode="toggle"
342
+ :options="pageFilterSegments(pf)"
343
+ :modelValue="workspaceContext[pf.key]"
344
+ :ariaLabel="pf.label ? effectiveTranslate(pf.label) : pf.key"
333
345
  :data-testid="'cn-page-filter-' + pf.key"
334
346
  @update:modelValue="onPageFilterChange(pf, $event)" />
335
- </label>
347
+ <label
348
+ v-else
349
+ class="cn-dashboard-page__page-filter">
350
+ <span v-if="pf.label" class="cn-dashboard-page__page-filter-label">{{ pf.label }}</span>
351
+ <NcSelect
352
+ :modelValue="selectedPageFilterOption(pf)"
353
+ :options="pf.options || []"
354
+ :clearable="false"
355
+ :inputLabel="pf.label || pf.key"
356
+ label="label"
357
+ :data-testid="'cn-page-filter-' + pf.key"
358
+ @update:modelValue="onPageFilterChange(pf, $event)" />
359
+ </label>
360
+ </template>
336
361
  </div>
337
362
 
338
363
  <!-- Declarative in-body sections, `placement: "before-grid"` — host-app
@@ -1678,13 +1703,18 @@ export default {
1678
1703
  * read it via a `@page.<key>` / `@workspace.<key>` token — e.g. a period
1679
1704
  * selector that every endpoint-bound KPI's URL interpolates.
1680
1705
  *
1681
- * Each entry: `{ key, label?, type?: 'select', options: [{ value, label }],
1682
- * default? }`. `key` is the workspace-context key written on change;
1683
- * `default` (or the first option) seeds it on mount. Only `'select'` is
1684
- * supported today. Empty (the default) renders no controls and leaves the
1685
- * context untouched.
1706
+ * Each entry: `{ key, label?, type?: 'select', control?, placement?,
1707
+ * options: [{ value, label }], default? }`. `key` is the workspace-context
1708
+ * key written on change; `default` (or the first option) seeds it on
1709
+ * mount. The control is a select, or with `control: 'segmented'` a
1710
+ * compact segmented group; `placement: 'description'` puts a segmented
1711
+ * group on the header's description line, after the period group
1712
+ * (DqProcesanalyse "Gemiddeld | Mediaan"). An endpoint widget can read
1713
+ * the value in its `responsePath` too (`phaseDays.@workspace.measure`).
1714
+ * Empty (the default) renders no controls and leaves the context
1715
+ * untouched.
1686
1716
  *
1687
- * @type {Array<{key: string, label?: string, type?: string, options: Array<{value: (string|number), label: string}>, default?: (string|number)}>}
1717
+ * @type {Array<{key: string, label?: string, type?: string, control?: ('select'|'segmented'), placement?: ('row'|'description'), options: Array<{value: (string|number), label: string}>, default?: (string|number)}>}
1688
1718
  */
1689
1719
  pageFilters: {
1690
1720
  type: Array,
@@ -2343,6 +2373,33 @@ export default {
2343
2373
  && this.dateRange.placement === 'description' && !this.hasCustomPreset
2344
2374
  },
2345
2375
 
2376
+ /**
2377
+ * The page filters drawn as a segmented group on the description
2378
+ * line (`control: "segmented"`, `placement: "description"`).
2379
+ * Without a header row they fall back to the filter row.
2380
+ *
2381
+ * @spec openspec/changes/dashboard-board-termijnen/specs/dashboard-page/spec.md#requirement-a-page-filter-as-a-segmented-group-on-the-description-line
2382
+ * @return {Array<object>}
2383
+ */
2384
+ descriptionPageFilters() {
2385
+ if (!this.showHeader) {
2386
+ return []
2387
+ }
2388
+ return (this.pageFilters || []).filter((pf) => pf && pf.key && pf.control === 'segmented' && pf.placement === 'description')
2389
+ },
2390
+
2391
+ /**
2392
+ * The page filters drawn in the filter row under the header: every
2393
+ * filter the description line does not draw.
2394
+ *
2395
+ * @spec openspec/changes/dashboard-board-termijnen/specs/dashboard-page/spec.md#requirement-a-page-filter-as-a-segmented-group-on-the-description-line
2396
+ * @return {Array<object>}
2397
+ */
2398
+ rowPageFilters() {
2399
+ const inDescription = this.descriptionPageFilters
2400
+ return (this.pageFilters || []).filter((pf) => pf && pf.key && !inDescription.includes(pf))
2401
+ },
2402
+
2346
2403
  /**
2347
2404
  * The preset list passed to the picker. Falls back to the
2348
2405
  * `DEFAULT_DATE_RANGE_PRESETS` constant when the consumer
@@ -2795,6 +2852,18 @@ export default {
2795
2852
  return (pf.options || []).find((o) => o.value === current) || null
2796
2853
  },
2797
2854
 
2855
+ /**
2856
+ * A page filter's options as segments, labels through the host
2857
+ * translate.
2858
+ *
2859
+ * @spec openspec/changes/dashboard-board-termijnen/specs/dashboard-page/spec.md#requirement-a-page-filter-as-a-segmented-group-on-the-description-line
2860
+ * @param {object} pf The page-filter descriptor.
2861
+ * @return {Array<{value: (string|number), label: string}>}
2862
+ */
2863
+ pageFilterSegments(pf) {
2864
+ return (pf.options || []).map((o) => ({ value: o.value, label: o.label ? this.effectiveTranslate(o.label) : String(o.value) }))
2865
+ },
2866
+
2798
2867
  /**
2799
2868
  * Write a page filter's new selection into the reactive workspace
2800
2869
  * context so every `@page.<key>` / `@workspace.<key>` token re-resolves.