@conduction/nextcloud-vue 2.104.0 → 2.105.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 (110) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  6. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
  7. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +59 -1
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  17. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  18. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
  19. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  20. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  22. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
  23. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
  25. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  27. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
  28. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  29. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
  30. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +54 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +3 -2
  35. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  36. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
  37. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
  39. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
  41. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  43. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  44. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
  45. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  46. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  47. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  48. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
  49. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  50. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  51. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
  52. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  53. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
  54. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  55. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  56. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  57. package/dist/esm/index.js +1 -0
  58. package/dist/esm/index.js.map +1 -1
  59. package/dist/esm/l10n/en.json.js +4 -1
  60. package/dist/esm/l10n/en.json.js.map +1 -1
  61. package/dist/esm/l10n/nl.json.js +4 -1
  62. package/dist/esm/l10n/nl.json.js.map +1 -1
  63. package/dist/esm/library-style.js +1 -1
  64. package/dist/esm/nextcloud-vue.css +566 -295
  65. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  66. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  67. package/dist/esm/utils/nextcloudApps.js +45 -0
  68. package/dist/esm/utils/nextcloudApps.js.map +1 -0
  69. package/dist/esm/utils/scopeListLayout.js +9 -2
  70. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  71. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  72. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  73. package/dist/esm/utils/visibleWhen.js +12 -4
  74. package/dist/esm/utils/visibleWhen.js.map +1 -1
  75. package/dist/nextcloud-vue.cjs.js +3054 -2517
  76. package/dist/nextcloud-vue.cjs.js.map +2 -2
  77. package/dist/nextcloud-vue.css +566 -295
  78. package/l10n/en.json +4 -1
  79. package/l10n/nl.json +4 -1
  80. package/package.json +1 -1
  81. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
  82. package/src/components/CnChartWidget/CnChartWidget.md +2 -0
  83. package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
  84. package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
  85. package/src/components/CnDetailPage/CnDetailPage.md +1 -0
  86. package/src/components/CnDetailPage/CnDetailPage.vue +59 -1
  87. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
  88. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
  89. package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
  90. package/src/components/CnIndexPage/CnIndexPage.md +1 -0
  91. package/src/components/CnIndexPage/CnIndexPage.vue +54 -1
  92. package/src/components/CnIndexPage/useSelfFetchList.js +3 -2
  93. package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
  94. package/src/components/CnNoticeWidget/index.js +12 -0
  95. package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
  96. package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
  97. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
  98. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  99. package/src/components/index.js +1 -0
  100. package/src/css/dialog.css +20 -2
  101. package/src/css/look-board-detail.css +77 -4
  102. package/src/css/look-board-index.css +36 -2
  103. package/src/css/look-board.css +97 -12
  104. package/src/index.js +1 -0
  105. package/src/schemas/app-manifest-v2.schema.json +36 -3
  106. package/src/utils/libraryWidgetKeys.js +1 -0
  107. package/src/utils/nextcloudApps.js +50 -0
  108. package/src/utils/scopeListLayout.js +9 -2
  109. package/src/utils/validateManifestV2.compiled.js +3 -3
  110. package/src/utils/visibleWhen.js +12 -4
@@ -1288,6 +1288,7 @@ import {
1288
1288
  import { reportBindingProblems } from '../../utils/diagnostics.js'
1289
1289
  import { fetchGroupedCounts } from '../../utils/fetchAggregate.js'
1290
1290
  import { fetchFilterCounts, fetchListTotal } from '../../utils/fetchFilterCounts.js'
1291
+ import { unwrapAppConfig } from '../../utils/formatMetric.js'
1291
1292
  import { buildExportUrl } from '../../utils/indexExportHelpers.js'
1292
1293
  import { lensUnavailableText } from '../../utils/lensAvailability.js'
1293
1294
  import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js'
@@ -1677,6 +1678,8 @@ export default {
1677
1678
  cnCustomComponents: { default: () => ({}) },
1678
1679
  /** The permissions the user holds, provided by CnAppRoot; for gated page tabs. */
1679
1680
  cnPermissions: { default: () => [] },
1681
+ /** The app config an ancestor provides, for `@config.<key>` in quick-filter labels. */
1682
+ cnAppConfig: { default: null },
1680
1683
  /** The app id CnAppRoot provides, for the plural form of `countSubtitle`. */
1681
1684
  cnAppId: { default: '' },
1682
1685
  /**
@@ -3133,6 +3136,19 @@ export default {
3133
3136
  default: true,
3134
3137
  },
3135
3138
 
3139
+ /**
3140
+ * The app config `@config.<key>` tokens resolve against, in quick-filter
3141
+ * filters and labels (`"Residence @config.municipality"`), as on the
3142
+ * dashboard and detail pages. Merged over a config an ancestor provides
3143
+ * on `cnAppConfig`. Manifest: `config.appConfig`.
3144
+ *
3145
+ * @type {object}
3146
+ */
3147
+ appConfig: {
3148
+ type: Object,
3149
+ default: () => ({}),
3150
+ },
3151
+
3136
3152
  /**
3137
3153
  * Keep the "Showing X of Y" counter visible beside the inline search field,
3138
3154
  * after any `#after-search` controls (only relevant with `inlineSearch`,
@@ -6678,11 +6694,21 @@ export default {
6678
6694
  effectiveQuickFilters() {
6679
6695
  const own = withPersonalLenses(this.presented('quickFilters') || this.generatedQuickFilters, this.personalLenses)
6680
6696
  if (own && own.length > 0) {
6681
- return own
6697
+ return own.map((tab) => this.withConfigLabels(tab))
6682
6698
  }
6683
6699
  return (this.isNamedSource && this.namedQuickFilters) || null
6684
6700
  },
6685
6701
 
6702
+ /**
6703
+ * The app config `@config.<key>` tokens resolve against: the `appConfig`
6704
+ * prop over a config an ancestor provides.
6705
+ *
6706
+ * @return {object} The config.
6707
+ */
6708
+ effectiveAppConfig() {
6709
+ return { ...unwrapAppConfig(this.cnAppConfig), ...(this.appConfig || {}) }
6710
+ },
6711
+
6686
6712
  /**
6687
6713
  * Whether the Add button renders. A named source that declares
6688
6714
  * `showAdd: false` suppresses it (a task is created by a flow, never
@@ -8766,6 +8792,33 @@ export default {
8766
8792
  this.$emit('sort-change', value)
8767
8793
  },
8768
8794
 
8795
+ /**
8796
+ * A quick filter with `@config.<key>` in its `label` or `activeLabel`
8797
+ * (`"Residence @config.municipality"`): the label translated first, then
8798
+ * each token replaced by its configured value. A key the config does
8799
+ * not hold leaves its token in place. Other tabs pass unchanged.
8800
+ *
8801
+ * @param {object} tab A quick-filter tab.
8802
+ * @return {object} The tab, with its labels resolved.
8803
+ */
8804
+ withConfigLabels(tab) {
8805
+ if (!tab || typeof tab !== 'object') {
8806
+ return tab
8807
+ }
8808
+ const hasToken = (value) => typeof value === 'string' && value.includes('@config.')
8809
+ if (!hasToken(tab.label) && !hasToken(tab.activeLabel)) {
8810
+ return tab
8811
+ }
8812
+ const config = this.effectiveAppConfig
8813
+ const fill = (value) => (hasToken(value)
8814
+ ? this.cnTranslate(value).replace(/@config\.([A-Za-z0-9_.-]*[A-Za-z0-9_])/g, (token, key) => {
8815
+ const found = this.getByPath(config, key)
8816
+ return found === undefined || found === null || found === '' ? token : String(found)
8817
+ })
8818
+ : value)
8819
+ return { ...tab, label: fill(tab.label), activeLabel: fill(tab.activeLabel) }
8820
+ },
8821
+
8769
8822
  /**
8770
8823
  * @param {{key: string, order: string, keys?: Array<{key: string, order: string}>}} payload Sort change from CnDataTable.
8771
8824
  * @return {void}
@@ -171,7 +171,8 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
171
171
  const objCtx = unwrapCtx(objectCtxRaw)
172
172
  const base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}
173
173
  base.workspace = unwrapCtx(workspaceCtxRaw) || {}
174
- base.config = unwrapCtx(appConfigRaw) || {}
174
+ // The page's own `appConfig` prop wins over a config an ancestor provides.
175
+ base.config = { ...(unwrapCtx(appConfigRaw) || {}), ...(props.appConfig || {}) }
175
176
  return base
176
177
  }
177
178
 
@@ -183,7 +184,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
183
184
  // never again. Stringified (like CnDeltaWidget's `sourceKey`) so the
184
185
  // watcher fires on real content changes only, not object identity.
185
186
  const workspaceSignature = computed(() => JSON.stringify(unwrapCtx(workspaceCtxRaw) || {}))
186
- const appConfigSignature = computed(() => JSON.stringify(unwrapCtx(appConfigRaw) || {}))
187
+ const appConfigSignature = computed(() => JSON.stringify({ ...(unwrapCtx(appConfigRaw) || {}), ...(props.appConfig || {}) }))
187
188
 
188
189
  // Pass register/schema in their positional id slots (not as a {register, schema} object as
189
190
  // second arg) — that previously made fetch URLs go to `/api/objects/undefined/[object Object]`.
@@ -0,0 +1,153 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <section
7
+ v-if="resolvedText || resolvedTitle"
8
+ class="cn-notice-widget"
9
+ :class="`cn-notice-widget--${resolvedVariant}`"
10
+ :aria-label="resolvedTitle || null"
11
+ role="note"
12
+ data-testid="cn-notice-widget">
13
+ <CnIcon
14
+ class="cn-notice-widget__icon"
15
+ :name="resolvedIcon"
16
+ :size="18"
17
+ aria-hidden="true" />
18
+ <p class="cn-notice-widget__text">
19
+ <strong v-if="resolvedTitle" class="cn-notice-widget__title" data-testid="cn-notice-widget-title">{{ resolvedTitle }}</strong>{{ resolvedTitle && resolvedText ? ' ' : '' }}{{ resolvedText }}
20
+ </p>
21
+ </section>
22
+ </template>
23
+
24
+ <script>
25
+ import { CnIcon } from '../CnIcon/index.js'
26
+
27
+ /** The tones a notice takes, and the icon each shows by default. */
28
+ const VARIANT_ICONS = Object.freeze({
29
+ info: 'InformationOutline',
30
+ warning: 'AlertOutline',
31
+ error: 'AlertCircleOutline',
32
+ success: 'CheckCircleOutline',
33
+ })
34
+
35
+ /**
36
+ * CnNoticeWidget — a toned notice box: an icon, a bold title and a line of
37
+ * text in one rounded box, as the boards draw a warning beside a record
38
+ * (DqSociaalDomein "Bijzondere persoonsgegevens: gezondheid. Elke inzage
39
+ * wordt gelogd met het doel."). Usable in a detail page's side column, its
40
+ * body grid and on a dashboard as widget type `notice`; the surface applies
41
+ * the widget's `visibleWhen`.
42
+ *
43
+ * `content`: `{ variant?, title?, text, icon? }`. `variant` is `info`
44
+ * (default), `warning`, `error` or `success`; `title` and `text` go through
45
+ * the host's translate; `icon` names a CnIcon instead of the variant's own.
46
+ *
47
+ * ```vue
48
+ * <CnNoticeWidget :content="{ variant: 'warning', title: 'Special category data: health.', text: 'Every view is logged with its purpose.' }" />
49
+ * ```
50
+ */
51
+ export default {
52
+ name: 'CnNoticeWidget',
53
+
54
+ components: { CnIcon },
55
+
56
+ inject: {
57
+ /** The host app's translate function (CnAppRoot `cnTranslate`). */
58
+ cnTranslate: { default: () => (key) => key },
59
+ },
60
+
61
+ props: {
62
+ /**
63
+ * The notice: `{ variant?, title?, text, icon? }`.
64
+ *
65
+ * @type {{variant?: string, title?: string, text?: string, icon?: string}}
66
+ */
67
+ content: {
68
+ type: Object,
69
+ default: () => ({}),
70
+ },
71
+ },
72
+
73
+ computed: {
74
+ /** @return {string} `info`, `warning`, `error` or `success`. */
75
+ resolvedVariant() {
76
+ const variant = this.content && this.content.variant === 'danger' ? 'error' : this.content && this.content.variant
77
+ return Object.hasOwn(VARIANT_ICONS, variant) ? variant : 'info'
78
+ },
79
+
80
+ /** @return {string} The icon name. */
81
+ resolvedIcon() {
82
+ const icon = this.content && this.content.icon
83
+ return typeof icon === 'string' && icon !== '' ? icon : VARIANT_ICONS[this.resolvedVariant]
84
+ },
85
+
86
+ /** @return {string} The title through the host translate. */
87
+ resolvedTitle() {
88
+ return this.translate(this.content && this.content.title)
89
+ },
90
+
91
+ /** @return {string} The text through the host translate. */
92
+ resolvedText() {
93
+ return this.translate(this.content && this.content.text)
94
+ },
95
+ },
96
+
97
+ methods: {
98
+ /**
99
+ * @param {unknown} text A manifest string.
100
+ * @return {string} Its translation, '' for anything but a non-empty string.
101
+ */
102
+ translate(text) {
103
+ if (typeof text !== 'string' || text === '') {
104
+ return ''
105
+ }
106
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
107
+ },
108
+ },
109
+ }
110
+ </script>
111
+
112
+ <style scoped>
113
+ .cn-notice-widget {
114
+ display: flex;
115
+ align-items: flex-start;
116
+ gap: 10px;
117
+ padding: 14px 16px;
118
+ border-radius: var(--cn-board-card-radius, 12px);
119
+ font-size: 14px;
120
+ line-height: 1.5;
121
+ background: color-mix(in srgb, var(--color-info, #0071ad) 10%, var(--color-main-background));
122
+ color: var(--color-main-text);
123
+ }
124
+
125
+ .cn-notice-widget--warning {
126
+ background: var(--color-warning-light, #fff4de);
127
+ color: var(--color-warning-text, #7a4f00);
128
+ }
129
+
130
+ .cn-notice-widget--error {
131
+ background: color-mix(in srgb, var(--color-error, #db0606) 10%, var(--color-main-background));
132
+ color: var(--color-error-text, var(--color-main-text));
133
+ }
134
+
135
+ .cn-notice-widget--success {
136
+ background: color-mix(in srgb, var(--color-success, #2d7b41) 10%, var(--color-main-background));
137
+ color: var(--color-success-text, var(--color-main-text));
138
+ }
139
+
140
+ .cn-notice-widget__icon {
141
+ flex: 0 0 auto;
142
+ margin-top: 2px;
143
+ }
144
+
145
+ .cn-notice-widget__text {
146
+ margin: 0;
147
+ overflow-wrap: anywhere;
148
+ }
149
+
150
+ .cn-notice-widget__title {
151
+ font-weight: 700;
152
+ }
153
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnNoticeWidget — a toned notice box (icon, bold title, text) for a side
6
+ * column, a detail body or a dashboard (widget type `notice`).
7
+ */
8
+
9
+ import CnNoticeWidget from './CnNoticeWidget.vue'
10
+
11
+ export { CnNoticeWidget }
12
+ export default CnNoticeWidget
@@ -18,7 +18,10 @@
18
18
  class="cn-settings-field__image"
19
19
  role="group"
20
20
  :aria-labelledby="`${inputId}-label`">
21
- <span :id="`${inputId}-label`" class="cn-settings-field__image-label">{{ label }}</span>
21
+ <!-- `hideLabel` keeps the name for a screen reader only. -->
22
+ <span :id="`${inputId}-label`"
23
+ class="cn-settings-field__image-label"
24
+ :class="{ 'hidden-visually': field.hideLabel === true }">{{ label }}</span>
22
25
  <div class="cn-settings-field__image-row">
23
26
  <span class="cn-settings-field__image-tile" :data-testid="`cn-settings-field-image-${field.key}`">
24
27
  <img v-if="stringValue" :src="stringValue" :alt="t('nextcloud-vue', 'Current {name}', { name: label })">
@@ -126,7 +126,15 @@
126
126
  <NcNoteCard v-if="isChoiceDisabled(step)" type="warning">
127
127
  {{ dependsOnHint(step) }}
128
128
  </NcNoteCard>
129
- <p v-if="stepNote(step)" class="cn-setup-step__note" data-testid="cn-setup-step-note">
129
+ <!-- `noteStyle: "card"` sets the note as an info card (DqInstellen). -->
130
+ <NcNoteCard
131
+ v-if="stepNote(step) && step.noteStyle === 'card'"
132
+ type="info"
133
+ class="cn-setup-step__note-card"
134
+ data-testid="cn-setup-step-note">
135
+ {{ stepNote(step) }}
136
+ </NcNoteCard>
137
+ <p v-else-if="stepNote(step)" class="cn-setup-step__note" data-testid="cn-setup-step-note">
130
138
  {{ stepNote(step) }}
131
139
  </p>
132
140
  </template>
@@ -35,13 +35,13 @@
35
35
  <div class="cn-version-info__details">
36
36
  <!-- Application Name (the board card can leave it to its description: `showAppName`) -->
37
37
  <div v-if="!isBoard || (showAppName && !compact)" class="cn-version-info__item" data-testid="cn-version-info-app-name">
38
- <span class="cn-version-info__label">{{ fieldLabels.appName }}:</span>
38
+ <span class="cn-version-info__label">{{ fieldLabels.appName }}{{ labelColon }}</span>
39
39
  <span class="cn-version-info__value">{{ appName }}</span>
40
40
  </div>
41
41
 
42
42
  <!-- Version -->
43
43
  <div class="cn-version-info__item">
44
- <span class="cn-version-info__label">{{ fieldLabels.version }}:</span>
44
+ <span class="cn-version-info__label">{{ fieldLabels.version }}{{ labelColon }}</span>
45
45
  <span class="cn-version-info__value">{{ appVersion }}</span>
46
46
  </div>
47
47
 
@@ -49,13 +49,13 @@
49
49
  which draws the row ("Unknown" until the app reports it) with the
50
50
  up-to-date state inline ("0.4.47, bijgewerkt"). -->
51
51
  <div v-if="configuredVersion || isBoard" class="cn-version-info__item" data-testid="cn-version-info-configured">
52
- <span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}:</span>
52
+ <span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}{{ labelColon }}</span>
53
53
  <span class="cn-version-info__value">{{ isBoard ? boardConfiguredText : configuredVersion }}</span>
54
54
  </div>
55
55
 
56
56
  <!-- Additional items -->
57
57
  <div v-for="item in additionalItems" :key="item.label" class="cn-version-info__item">
58
- <span class="cn-version-info__label">{{ item.label }}:</span>
58
+ <span class="cn-version-info__label">{{ item.label }}{{ labelColon }}</span>
59
59
  <span class="cn-version-info__value" :class="item.statusClass">{{ item.value }}</span>
60
60
  </div>
61
61
 
@@ -320,6 +320,17 @@ export default {
320
320
  },
321
321
 
322
322
  computed: {
323
+ /**
324
+ * The colon after each label: none on the compact board card, which
325
+ * sets each label beside its value ("Versie 0.4.47", DqInstellingenAlgemeen);
326
+ * one otherwise, PqBeheer's card included.
327
+ *
328
+ * @return {string} The colon, or ''.
329
+ */
330
+ labelColon() {
331
+ return this.isBoard && this.compact ? '' : ':'
332
+ },
333
+
323
334
  /**
324
335
  * The field labels: the app's, else the library's in the reader's language.
325
336
  *
@@ -21,6 +21,7 @@ import CnExportsWidget from '../CnExportsWidget/CnExportsWidget.vue'
21
21
  import CnHeaderWidget from '../CnHeaderWidget/CnHeaderWidget.vue'
22
22
  import CnIntegrationWidget from '../CnIntegrationWidget/CnIntegrationWidget.vue'
23
23
  import CnNavCardGrid from '../CnNavCardGrid/CnNavCardGrid.vue'
24
+ import CnNoticeWidget from '../CnNoticeWidget/CnNoticeWidget.vue'
24
25
  import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
25
26
  import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
26
27
  import CnObjectMetadataWidget from '../CnObjectMetadataWidget/CnObjectMetadataWidget.vue'
@@ -86,6 +87,7 @@ export const BUILT_IN_WIDGETS = {
86
87
  related: CnRelatedObjectsWidget,
87
88
  integration: CnIntegrationWidget,
88
89
  banner: CnBannerWidget,
90
+ notice: CnNoticeWidget,
89
91
  'audit-trail': CnAuditTrailWidget,
90
92
  timeline: CnTimelineWidget,
91
93
  header: CnHeaderWidget,
@@ -131,6 +131,7 @@ export { CnContactPicker } from './CnContactPicker/index.js'
131
131
  export { CnContactCreate } from './CnContactCreate/index.js'
132
132
  export { CnResourceSelect } from './CnResourceSelect/index.js'
133
133
  export { CnSearchSelect } from './CnSearchSelect/index.js'
134
+ export { CnNoticeWidget } from './CnNoticeWidget/index.js'
134
135
  export { CnPropertySourceField } from './CnPropertySourceField/index.js'
135
136
  export { CnDurationField } from './CnDurationField/index.js'
136
137
  export { CnFavouriteToggle } from './CnFavouriteToggle/index.js'
@@ -13,7 +13,11 @@
13
13
  * --cn-dialog-eyebrow-tracking letter spacing; 0 by default, 0.06em suits capitals
14
14
  */
15
15
 
16
+ /* The nldesign theme sets a dialog's radius with `!important` through its
17
+ dialog radius token, so the board's 12px goes to that token.
18
+ @spec openspec/changes/board-dialog-theme/specs/board-dialog-theme/spec.md#requirement-a-board-dialog-keeps-its-frame-and-inset-under-a-theme */
16
19
  .cn-dialog-board {
20
+ --nldesign-component-dialog-border-radius: var(--cn-board-card-radius, 12px);
17
21
  --cn-dialog-width: 640px;
18
22
  --cn-dialog-danger: var(--color-error);
19
23
  --cn-dialog-eyebrow-color: var(--color-primary-element-text, var(--color-main-text));
@@ -26,16 +30,26 @@
26
30
  background-color: color-mix(in srgb, var(--color-main-text) 45%, transparent);
27
31
  }
28
32
 
29
- /* Width by role, capped at the viewport minus a 16px gutter each side. */
33
+ /* Width by role, capped at the viewport minus a 16px gutter each side. The
34
+ shadow carries `!important`: the nldesign theme removes every
35
+ `.modal-container`'s shadow with `!important` and offers no token for it. */
30
36
  .modal-mask.cn-dialog-board .modal-wrapper--normal > .modal-container,
31
37
  .modal-mask.cn-dialog-board .modal-wrapper .modal-container {
32
38
  width: var(--cn-dialog-width);
33
39
  max-width: min(var(--cn-dialog-width), calc(100% - 32px));
34
40
  border-radius: var(--cn-board-card-radius, 12px);
35
- box-shadow: 0 12px 40px color-mix(in srgb, var(--color-main-text) 28%, transparent);
41
+ box-shadow: 0 12px 40px color-mix(in srgb, var(--color-main-text) 28%, transparent) !important;
36
42
  overflow: hidden;
37
43
  }
38
44
 
45
+ /* The board's header and body carry their own 24px inset. NcDialog pads its
46
+ container 4px at the top and 12px at the start, which put the title and
47
+ the fields 36px in; its rule outranked the one above through the scoped
48
+ `.dialog__modal[data-v-…]`, so this one names the class too. */
49
+ .modal-mask.cn-dialog-board.dialog__modal .modal-wrapper .modal-container {
50
+ padding: 0;
51
+ }
52
+
39
53
  /* The header is ours: NcModal's own close button and NcDialog's heading give
40
54
  way (the heading stays as the accessible name, visually hidden). The mask
41
55
  class raises the rule above NcButton's `.button-vue[data-v-…]`, whose
@@ -269,6 +283,10 @@
269
283
  border-radius: 16px 16px 0 0;
270
284
  }
271
285
 
286
+ .cn-dialog-board {
287
+ --nldesign-component-dialog-border-radius: 16px 16px 0 0;
288
+ }
289
+
272
290
  .modal-mask.cn-dialog-board .modal-container::before {
273
291
  content: '';
274
292
  display: block;
@@ -203,6 +203,58 @@
203
203
  width: auto;
204
204
  }
205
205
 
206
+ /* ---- The follow pill (DqZaak "Volgen") ---------------------------------- */
207
+ /* The board draws the follow control under the title as a 28px pill: 13px
208
+ text at 600 on the main background, a grey border, a 14px icon 6px from its
209
+ label. Live it was Nextcloud's 34px tertiary button, 15px bold in the
210
+ primary colour. The theme tokens carry the weight and radius past the
211
+ nldesign theme's `!important`; the follower count takes the same pill.
212
+ @spec openspec/changes/board-follow-pill/specs/board-follow-pill/spec.md#requirement-the-board-follow-control-is-a-pill */
213
+ .cn-look-board .cn-detail-page .cn-follow-toggle .button-vue {
214
+ --nldesign-component-button-font-weight: 600;
215
+ --nldesign-component-button-border-radius: 14px;
216
+ --nldesign-component-button-secondary-action-color: var(--color-main-text);
217
+ --nldesign-component-button-secondary-action-background-color: var(--color-main-background);
218
+ --nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
219
+ --nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
220
+ box-sizing: border-box;
221
+ height: 28px;
222
+ min-height: 28px;
223
+ min-width: 0;
224
+ padding: 0 10px;
225
+ border: 1px solid var(--color-border-dark);
226
+ border-radius: 14px;
227
+ background: var(--color-main-background);
228
+ color: var(--color-main-text);
229
+ font-size: 13px;
230
+ }
231
+
232
+ .cn-look-board .cn-detail-page .cn-follow-toggle .button-vue:hover {
233
+ background: var(--color-background-hover);
234
+ }
235
+
236
+ .cn-look-board .cn-detail-page .cn-follow-toggle .button-vue .button-vue__wrapper {
237
+ gap: 6px;
238
+ }
239
+
240
+ .cn-look-board .cn-detail-page .cn-follow-toggle .button-vue .button-vue__icon {
241
+ width: 14px;
242
+ min-width: 14px;
243
+ height: 14px;
244
+ min-height: 14px;
245
+ color: var(--color-main-text);
246
+ }
247
+
248
+ .cn-look-board .cn-detail-page .cn-follow-toggle .button-vue .button-vue__icon svg {
249
+ width: 14px;
250
+ height: 14px;
251
+ }
252
+
253
+ .cn-look-board .cn-detail-page .cn-follow-toggle .button-vue .button-vue__text {
254
+ margin: 0;
255
+ padding: 0;
256
+ }
257
+
206
258
  /* ---- Folder tabs ------------------------------------------------------- */
207
259
 
208
260
  /* The strip draws no line of its own (design-system pull request 147). The card under it
@@ -222,7 +274,7 @@
222
274
  }
223
275
 
224
276
  /* Tabs sit on the card's top edge; only the open tab reaches 1px further
225
- down (min-height 35px below), over the card's top border. Top-aligned so
277
+ down (min-height 38px below), over the card's top border. Top-aligned so
226
278
  the inactive tabs do not stretch to the open tab's height. */
227
279
  .cn-look-board .cn-tabs--board .cn-tabs__nav {
228
280
  align-items: flex-start;
@@ -256,8 +308,15 @@
256
308
 
257
309
  /* `margin: 0`: a tab is a <button>, and Nextcloud's core stylesheet gives
258
310
  every `button:not(.button-vue)` `margin: 3px 3px 3px 0`, which put the strip
259
- 3px low and the tabs 7px apart instead of 4 (DqZaak "Zaakonderdelen"). */
311
+ 3px low and the tabs 7px apart instead of 4 (DqZaak "Zaakonderdelen").
312
+ The nldesign theme sets every button's weight and radius with `!important`
313
+ through its button tokens, which drew every tab bold with four rounded
314
+ corners; the tab hands the board's values to those tokens. A tab is 37px
315
+ with or without a count (DqZaak), not 34px without one.
316
+ @spec openspec/changes/board-tabs-theme/specs/board-tabs-theme/spec.md#requirement-board-folder-tabs-keep-the-boards-weight-shape-and-height-under-a-theme */
260
317
  .cn-look-board .cn-tabs--board .cn-tabs__nav-item {
318
+ --nldesign-component-button-font-weight: 400;
319
+ --nldesign-component-button-border-radius: 8px 8px 0 0;
261
320
  background-color: var(--color-background-dark);
262
321
  border: 1px solid var(--color-border-dark);
263
322
  border-bottom: none;
@@ -265,17 +324,18 @@
265
324
  font-size: 14px;
266
325
  font-weight: 400;
267
326
  margin: 0;
268
- min-height: 34px;
327
+ min-height: 37px;
269
328
  padding: 8px 12px;
270
329
  }
271
330
 
272
331
  /* The open tab: the panel's colour, bold, and no coloured top edge. */
273
332
  .cn-look-board .cn-tabs--board .cn-tabs__nav-item.cn-tabs__nav-item--active {
333
+ --nldesign-component-button-font-weight: 700;
274
334
  background-color: var(--color-main-background);
275
335
  border-bottom: 1px solid var(--color-main-background);
276
336
  border-top: 1px solid var(--color-border-dark);
277
337
  font-weight: 700;
278
- min-height: 35px;
338
+ min-height: 38px;
279
339
  padding-bottom: 9px;
280
340
  padding-top: 8px;
281
341
  position: relative;
@@ -456,6 +516,19 @@
456
516
  padding: 10px 22px 20px;
457
517
  }
458
518
 
519
+ /* A stat tile in a side card (DqZaak "Termijn") sits on the card's inset as
520
+ the board draws it. The card-fit wrapper is flush, and the wrapper's own
521
+ flush rule loads after this sheet and outranked the side card's padding,
522
+ so the value sat 11px from the card's edge instead of 23px.
523
+ @spec openspec/changes/board-dashboard-widget-inset/specs/board-dashboard-widget-inset/spec.md#requirement-a-stat-tile-in-a-detail-side-card-keeps-the-cards-inset */
524
+ .cn-look-board .cn-detail-page__side .cn-detail-page__card-fit > .cn-widget-wrapper__content:has(> .cn-kpi-card--board) {
525
+ padding: 10px 22px 20px;
526
+ }
527
+
528
+ .cn-look-board .cn-detail-page__side .cn-detail-page__card-fit > .cn-widget-wrapper__content > .cn-kpi-card--board {
529
+ padding: 0;
530
+ }
531
+
459
532
  /* No Actions menu on a side card. */
460
533
  .cn-look-board .cn-detail-page__side .cn-widget-wrapper__actions {
461
534
  display: none;
@@ -163,8 +163,15 @@
163
163
  gap: 8px;
164
164
  }
165
165
 
166
+ /* A chip is a <button>: the nldesign theme sets every button's radius and
167
+ weight with `!important` through its button tokens, which drew the chips
168
+ as 8px boxes at 700 (DqTeamwachtrij), so the chip hands its pill radius and
169
+ 600 to those tokens.
170
+ @spec openspec/changes/board-chips-theme/specs/board-chips-theme/spec.md#requirement-board-chips-stay-pills-under-a-theme */
166
171
  .cn-look-board .cn-saved-views__chips .cn-saved-views__chip,
167
172
  .cn-look-board .cn-quick-filter-bar .cn-quick-filter-bar__tab {
173
+ --nldesign-component-button-border-radius: 19px;
174
+ --nldesign-component-button-font-weight: 600;
168
175
  display: inline-flex;
169
176
  align-items: center;
170
177
  gap: 8px;
@@ -267,12 +274,30 @@
267
274
  padding: 0;
268
275
  }
269
276
 
270
- /* Filter: a labelled secondary button, 36px, radius 8, with the active count. */
277
+ /* The toolbar's buttons are the board's neutral control (DqTeamwachtrij
278
+ "Filter"): the main text at 600 on the main background with a grey border.
279
+ The nldesign theme paints a secondary button in the primary colour with
280
+ `!important` through its button tokens, so the bar hands the board's values
281
+ to those tokens.
282
+ @spec openspec/changes/board-toolbar-buttons-theme/specs/board-toolbar-buttons-theme/spec.md#requirement-board-toolbar-buttons-are-neutral-under-a-theme */
283
+ .cn-look-board .cn-actions-bar:not(.cn-look-nextcloud *) {
284
+ --nldesign-component-button-font-weight: 600;
285
+ --nldesign-component-button-secondary-action-color: var(--color-main-text);
286
+ --nldesign-component-button-secondary-action-background-color: var(--color-main-background);
287
+ --nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
288
+ --nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
289
+ }
290
+
291
+ /* Filter: a labelled secondary button, 36px, radius 8, 12px at both sides
292
+ and 14px text, with the active count. NcButton with an icon pads its start
293
+ 4px and sets 15px. */
271
294
  .cn-look-board .cn-actions-bar__filter-button.button-vue,
272
295
  .cn-look-board .cn-actions-bar__filter-menu .button-vue {
273
296
  min-height: 36px;
274
297
  height: 36px;
298
+ padding: 0 12px;
275
299
  border-radius: 8px;
300
+ font-size: 14px;
276
301
  font-weight: 600;
277
302
  }
278
303
 
@@ -513,9 +538,18 @@
513
538
 
514
539
  /* The header row is near-white on the boards (#fafbfc), not the hover grey,
515
540
  its labels 14px muted. The ink also goes to --nldesign-color-on-surface,
516
- which a theme that forces the body ink onto every span (thematiq) reads. */
541
+ which a theme that forces the body ink onto every span (thematiq) reads.
542
+ The nldesign theme paints every `th` with `!important` through its table
543
+ header tokens, and the sort button in it through its button weight token
544
+ (bold), so the cell hands the board's ground, ink and weight to those
545
+ tokens too.
546
+ @spec openspec/changes/board-table-head-theme/specs/board-table-head-theme/spec.md#requirement-a-board-table-header-keeps-the-boards-ground-ink-and-weight-under-a-theme */
517
547
  .cn-look-board .cn-table-container--board .cn-data-table th {
518
548
  --nldesign-color-on-surface: var(--color-text-maxcontrast);
549
+ --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)));
550
+ --nldesign-component-table-header-color: var(--color-text-maxcontrast);
551
+ --nldesign-component-table-header-font-weight: 600;
552
+ --nldesign-component-button-font-weight: 600;
519
553
  padding: 12px 16px;
520
554
  border-bottom: 0;
521
555
  background: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));