@conduction/nextcloud-vue 2.124.0 → 2.125.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 (66) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +120 -11
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +78 -32
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +8 -7
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  10. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +2 -1
  11. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -2
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  14. package/dist/esm/components/CnDialog/CnDialog.vue2.js +5 -0
  15. package/dist/esm/components/CnDialog/CnDialog.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDialog/CnDialog.vue3.js +1 -1
  17. package/dist/esm/components/CnDialog/CnDialog.vue3.js.map +1 -1
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +13 -0
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
  21. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  22. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  23. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +7 -2
  24. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  25. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  26. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  27. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +159 -10
  28. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +151 -90
  30. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +12 -0
  32. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  33. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
  34. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  35. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  36. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +39 -68
  37. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  38. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +70 -116
  39. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  40. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  41. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +19 -3
  42. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  43. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  44. package/dist/esm/l10n/en.json.js +8 -8
  45. package/dist/esm/l10n/nl.json.js +8 -8
  46. package/dist/esm/library-style.js +1 -1
  47. package/dist/esm/nextcloud-vue.css +316 -185
  48. package/dist/nextcloud-vue.cjs.js +1149 -808
  49. package/dist/nextcloud-vue.cjs.js.map +2 -2
  50. package/dist/nextcloud-vue.css +316 -185
  51. package/l10n/en.json +8 -8
  52. package/l10n/nl.json +8 -8
  53. package/package.json +1 -1
  54. package/src/components/CnActionDialog/CnActionDialog.vue +173 -14
  55. package/src/components/CnAppRoot/CnAppRoot.vue +8 -7
  56. package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -3
  57. package/src/components/CnDialog/CnDialog.vue +6 -1
  58. package/src/components/CnIndexPage/CnIndexPage.vue +14 -0
  59. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +7 -2
  60. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +237 -23
  61. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +13 -1
  62. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +147 -145
  63. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +19 -3
  64. package/src/css/dialog.css +23 -0
  65. package/src/css/look-board-index.css +5 -1
  66. package/src/css/look-board.css +19 -0
@@ -7,29 +7,26 @@
7
7
  class="cn-user-settings-dialog"
8
8
  :name="title"
9
9
  look="board"
10
- width="wizard"
11
- :eyebrow="eyebrow"
12
- :subtitle="subtitle"
10
+ headless
13
11
  :open="open"
14
- :style="eyebrowStyle"
12
+ :style="frameStyle"
15
13
  data-testid="cn-user-settings-dialog"
16
14
  @update:open="onUpdateOpen"
17
15
  @closing="close">
18
16
  <div class="cn-user-settings-dialog__body">
19
17
  <nav
20
- v-if="sections.length > 0"
21
18
  class="cn-user-settings-dialog__nav"
22
19
  :aria-label="navLabel"
23
20
  data-testid="cn-user-settings-nav">
21
+ <h2 class="cn-user-settings-dialog__title" data-testid="cn-user-settings-title">
22
+ {{ title }}
23
+ </h2>
24
24
  <!-- `nextcloudLinks`: Nextcloud's own personal settings first, as
25
25
  links, then one entry for this app holding its sections. -->
26
26
  <ul v-if="ncLinks.length > 0" class="cn-user-settings-dialog__nc-links" data-testid="cn-user-settings-nc-links">
27
27
  <li v-for="link in ncLinks" :key="link.key">
28
28
  <a :href="link.href" class="cn-user-settings-dialog__nav-link">
29
- <span v-if="link.icon" class="cn-user-settings-dialog__nav-icon" aria-hidden="true">
30
- <CnIcon :name="link.icon" :size="18" />
31
- </span>
32
- <span>{{ link.label }}</span>
29
+ <span class="cn-user-settings-dialog__nav-label">{{ link.label }}</span>
33
30
  </a>
34
31
  </li>
35
32
  </ul>
@@ -47,10 +44,7 @@
47
44
  :aria-current="section.id === selected ? 'true' : undefined"
48
45
  :data-testid="`cn-user-settings-nav-${section.id}`"
49
46
  @click.prevent="selectSection(section.id)">
50
- <span v-if="section.icon" class="cn-user-settings-dialog__nav-icon" aria-hidden="true">
51
- <CnVNodes :vnodes="section.icon" />
52
- </span>
53
- <span>{{ section.name }}</span>
47
+ <span class="cn-user-settings-dialog__nav-label">{{ section.name }}</span>
54
48
  </a>
55
49
  </li>
56
50
  </ul>
@@ -58,24 +52,22 @@
58
52
  <div class="cn-user-settings-dialog__sections">
59
53
  <!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->
60
54
  <slot />
61
- <!-- A section's `#icon` also stands before its heading. -->
62
- <template v-for="section in sections" :key="`heading-icon-${section.id}-${section.name}`">
63
- <Teleport v-if="section.icon && headingTargets.includes(section.id)" :to="`#settings-section_${section.id}--label`">
64
- <span class="cn-user-settings-dialog__heading-icon" aria-hidden="true" :data-testid="`cn-user-settings-heading-icon-${section.id}`">
65
- <CnVNodes :vnodes="section.icon" />
66
- </span>
67
- </Teleport>
68
- </template>
55
+ <p class="cn-user-settings-dialog__note" data-testid="cn-user-settings-note">
56
+ {{ footerNote }}
57
+ </p>
69
58
  </div>
70
- </div>
71
- <template #actions>
72
- <p class="cn-user-settings-dialog__note" data-testid="cn-user-settings-note">
73
- {{ footerNote }}
74
- </p>
75
- <NcButton variant="secondary" data-testid="cn-user-settings-close" @click="close">
76
- {{ closeLabel }}
59
+ <NcButton
60
+ class="cn-user-settings-dialog__close"
61
+ variant="tertiary"
62
+ :aria-label="closeLabel"
63
+ :title="closeLabel"
64
+ data-testid="cn-user-settings-close"
65
+ @click="close">
66
+ <template #icon>
67
+ <Close :size="20" />
68
+ </template>
77
69
  </NcButton>
78
- </template>
70
+ </div>
79
71
  </CnDialog>
80
72
  </template>
81
73
 
@@ -83,9 +75,9 @@
83
75
  import { translate as t } from '@nextcloud/l10n'
84
76
  import { generateUrl } from '@nextcloud/router'
85
77
  import { NcButton } from '@nextcloud/vue'
86
- import { cloneVNode, markRaw, ref } from 'vue'
78
+ import { ref } from 'vue'
79
+ import Close from 'vue-material-design-icons/Close.vue'
87
80
  import CnDialog from '../CnDialog/CnDialog.vue'
88
- import CnIcon from '../CnIcon/CnIcon.vue'
89
81
 
90
82
  /**
91
83
  * The key NcAppSettingsSection injects to register itself with its dialog
@@ -96,37 +88,28 @@ const REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')
96
88
  /** The key NcAppSettingsSection injects for the legacy design flag. */
97
89
  const LEGACY_DESIGN_KEY = Symbol.for('NcAppSettingsDialog:legacy')
98
90
 
99
- /** Renders a section's icon vnodes as they were registered. */
100
- const CnVNodes = {
101
- name: 'CnVNodes',
102
- props: { vnodes: { type: [Array, Object], default: null } },
103
- render() {
104
- // A section's icon is drawn twice (navigation and heading), so each draw gets its own copy.
105
- const list = Array.isArray(this.vnodes) ? this.vnodes : [this.vnodes]
106
- return list.map((vnode) => (vnode && typeof vnode === 'object' ? cloneVNode(vnode) : vnode))
107
- },
108
- }
109
-
110
91
  /**
111
- * CnUserSettingsDialog - the user settings dialog in the board look (board
112
- * PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the
113
- * sections with their icons, the current section (the others stay mounted
114
- * but hidden), and a footer with a note that changes save straight away and
115
- * a Close button.
92
+ * CnUserSettingsDialog - the user settings dialog in the board look, as the
93
+ * DqPersoonlijkeInstellingen board draws Nextcloud's own settings dialog:
94
+ * 900px wide, the title at the top of a tinted navigation column, every
95
+ * section stacked on a white pane that the navigation scrolls, a close icon
96
+ * in the corner, and no header or footer. Under the sections a line says
97
+ * that changes save straight away.
116
98
  *
117
99
  * The sections stay the app's: any `NcAppSettingsSection` in the default slot
118
100
  * registers itself with this dialog exactly as it does with
119
- * `NcAppSettingsDialog`, with its name, order and `#icon` slot. CnAppRoot
120
- * renders this dialog instead of `NcAppSettingsDialog` under the board look.
101
+ * `NcAppSettingsDialog`, with its name and order. CnAppRoot renders this
102
+ * dialog instead of `NcAppSettingsDialog` under the board look.
121
103
  *
122
104
  * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look
105
+ * @spec openspec/changes/user-settings-dialog-dq-board/specs/user-settings-dialog-dq-board/spec.md#requirement-the-user-settings-dialog-follows-the-dq-board
123
106
  *
124
107
  * @event update:open Emitted with `false` when the dialog closes.
125
108
  */
126
109
  export default {
127
110
  name: 'CnUserSettingsDialog',
128
111
 
129
- components: { CnDialog, CnIcon, CnVNodes, NcButton },
112
+ components: { Close, CnDialog, NcButton },
130
113
 
131
114
  provide() {
132
115
  return {
@@ -145,9 +128,19 @@ export default {
145
128
  open: { type: Boolean, default: false },
146
129
  /** Dialog title; also its accessible name. */
147
130
  title: { type: String, required: true },
148
- /** Context line above the title. */
131
+ /**
132
+ * Deprecated and ignored: the board draws no line above the title.
133
+ * Kept so a host that passes it stays valid.
134
+ *
135
+ * @deprecated
136
+ */
149
137
  eyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },
150
- /** Sentence under the title. */
138
+ /**
139
+ * Deprecated and ignored: the board draws no line under the title.
140
+ * Kept so a host that passes it stays valid.
141
+ *
142
+ * @deprecated
143
+ */
151
144
  subtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },
152
145
  /** The app's name, after the footer note and as the navigation entry holding its sections. */
153
146
  appName: { type: String, default: '' },
@@ -166,8 +159,6 @@ export default {
166
159
  return {
167
160
  sections: [],
168
161
  selected: '',
169
- /** Ids of the sections whose heading is in the page, for the heading icons. */
170
- headingTargets: [],
171
162
  picked: false,
172
163
  }
173
164
  },
@@ -190,9 +181,9 @@ export default {
190
181
  }))
191
182
  },
192
183
 
193
- /** @return {object} The board's eyebrow: muted, in sentence case. */
194
- eyebrowStyle() {
195
- return { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }
184
+ /** @return {object} The board's 900px width, over CnDialog's width roles. */
185
+ frameStyle() {
186
+ return { '--cn-dialog-width': '900px' }
196
187
  },
197
188
 
198
189
  /** @return {string} The navigation's accessible name. */
@@ -212,21 +203,6 @@ export default {
212
203
  },
213
204
  },
214
205
 
215
- watch: {
216
- sections() {
217
- this.$nextTick(() => {
218
- this.headingTargets = typeof document === 'undefined'
219
- ? []
220
- : this.sections.filter((section) => section.icon && document.getElementById(`settings-section_${section.id}--label`)).map((section) => section.id)
221
- this.showSelected()
222
- })
223
- },
224
-
225
- selected() {
226
- this.$nextTick(() => this.showSelected())
227
- },
228
- },
229
-
230
206
  methods: {
231
207
  /**
232
208
  * Add a section to the navigation, kept in `order`; a section
@@ -235,13 +211,14 @@ export default {
235
211
  *
236
212
  * @param {string} id The section id.
237
213
  * @param {string} name The section name.
238
- * @param {number} [order] The section's place.
239
- * @param {Array} [icon] The section's icon vnodes.
214
+ * @param {number} [order] The section's place. A section's icon, the
215
+ * fourth argument NcAppSettingsSection passes, is not drawn: the
216
+ * board lists the sections by name alone.
240
217
  */
241
- registerSection(id, name, order, icon) {
218
+ registerSection(id, name, order) {
242
219
  const rest = this.sections.filter((section) => section.id !== id)
243
220
  const place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1
244
- this.sections = [...rest, { id, name, order: place, icon: icon ? markRaw(icon) : null }].sort((a, b) => a.order - b.order)
221
+ this.sections = [...rest, { id, name, order: place }].sort((a, b) => a.order - b.order)
245
222
  if (!this.picked) {
246
223
  this.selected = this.sections[0].id
247
224
  }
@@ -260,7 +237,8 @@ export default {
260
237
  },
261
238
 
262
239
  /**
263
- * Mark a section current and scroll it into view.
240
+ * Mark a section current, scroll it to the top of the pane and focus
241
+ * its heading. Every section stays in the pane.
264
242
  *
265
243
  * @param {string} id The section id.
266
244
  */
@@ -268,33 +246,18 @@ export default {
268
246
  this.picked = true
269
247
  this.selected = id
270
248
  this.$nextTick(() => {
271
- this.showSelected()
249
+ const el = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null
250
+ if (el && typeof el.scrollIntoView === 'function') {
251
+ el.scrollIntoView({ block: 'start', behavior: 'smooth' })
252
+ }
272
253
  const heading = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}--label`) : null
273
254
  if (heading) {
274
255
  heading.setAttribute('tabindex', '-1')
275
- heading.focus()
256
+ heading.focus({ preventScroll: true })
276
257
  }
277
258
  })
278
259
  },
279
260
 
280
- /**
281
- * Show the current section and hide the others. They stay mounted,
282
- * so what a reader typed in one survives a switch.
283
- *
284
- * @spec openspec/changes/user-settings-one-section/specs/user-settings-board-dialog/spec.md#requirement-the-board-user-settings-dialog-shows-one-section-at-a-time
285
- */
286
- showSelected() {
287
- if (typeof document === 'undefined') {
288
- return
289
- }
290
- for (const section of this.sections) {
291
- const el = document.getElementById(`settings-section_${section.id}`)
292
- if (el) {
293
- el.hidden = section.id !== this.selected
294
- }
295
- }
296
- },
297
-
298
261
  /**
299
262
  * Follow NcDialog's own open state.
300
263
  *
@@ -318,16 +281,35 @@ export default {
318
281
  </script>
319
282
 
320
283
  <style scoped>
284
+ /* The DqPersoonlijkeInstellingen board: a tinted navigation column with the
285
+ title on top, the sections on a white pane beside it, and the close icon
286
+ in the corner. The dialog's frame (900px, its tint, no padding) is in
287
+ dialog.css, on the teleported container. */
321
288
  .cn-user-settings-dialog__body {
289
+ position: relative;
322
290
  display: flex;
323
- flex-wrap: wrap;
324
- gap: 0 24px;
291
+ min-height: 440px;
292
+ max-height: min(760px, calc(100vh - 112px));
325
293
  }
326
294
 
327
295
  .cn-user-settings-dialog__nav {
296
+ display: flex;
328
297
  flex: 0 0 200px;
298
+ flex-direction: column;
299
+ gap: 2px;
300
+ box-sizing: border-box;
329
301
  width: 200px;
330
- padding-top: 18px;
302
+ padding: 0 4px 12px;
303
+ overflow-y: auto;
304
+ }
305
+
306
+ .cn-user-settings-dialog__title {
307
+ margin: 0;
308
+ padding: 14px 10px 12px;
309
+ font-size: 20px;
310
+ font-weight: 700;
311
+ line-height: 1.25;
312
+ color: var(--color-main-text);
331
313
  }
332
314
 
333
315
  .cn-user-settings-dialog__nav ul {
@@ -344,19 +326,25 @@ export default {
344
326
  it (thematiq), and a bare `a` with `!important` beats any class. */
345
327
  .cn-user-settings-dialog__nav-link,
346
328
  .cn-user-settings-dialog__nav-link:visited {
329
+ position: relative;
347
330
  display: flex;
348
331
  align-items: center;
349
- gap: 10px;
350
- min-height: 40px;
351
- padding: 0 12px;
332
+ min-height: 36px;
333
+ padding: 0 10px;
352
334
  border-radius: var(--border-radius-element, 8px);
353
335
  font-size: 15px;
336
+ font-weight: 500;
354
337
  color: var(--color-main-text);
355
338
  text-decoration: none !important;
356
339
  }
357
340
 
341
+ .cn-user-settings-dialog__nav-label {
342
+ overflow: hidden;
343
+ text-overflow: ellipsis;
344
+ }
345
+
358
346
  .cn-user-settings-dialog__nav-link:hover {
359
- background: var(--color-background-hover);
347
+ background: color-mix(in srgb, var(--color-primary-element) 8%, transparent);
360
348
  text-decoration: none !important;
361
349
  }
362
350
 
@@ -365,75 +353,89 @@ export default {
365
353
  outline-offset: 2px;
366
354
  }
367
355
 
356
+ /* The open section: a stronger tint and a 3px bar at the left edge. */
368
357
  .cn-user-settings-dialog__nav-link--active,
369
358
  .cn-user-settings-dialog__nav-link--active:hover {
370
- background: var(--color-primary-element-light);
371
- color: var(--color-primary-element-light-text);
359
+ background: color-mix(in srgb, var(--color-primary-element) 16%, var(--color-primary-element-light));
360
+ }
361
+
362
+ .cn-user-settings-dialog__nav-link--active::before {
363
+ content: '';
364
+ position: absolute;
365
+ top: 6px;
366
+ bottom: 6px;
367
+ inset-inline-start: 0;
368
+ width: 3px;
369
+ border-radius: 2px;
370
+ background: var(--color-primary-element);
371
+ }
372
+
373
+ .cn-user-settings-dialog__group {
374
+ margin: 10px 0 2px;
375
+ padding: 0 10px;
376
+ font-size: 15px;
372
377
  font-weight: 600;
378
+ color: var(--color-main-text);
373
379
  }
374
380
 
375
- .cn-user-settings-dialog__nav-icon {
376
- display: inline-flex;
377
- flex: none;
378
- width: 18px;
379
- height: 18px;
380
- align-items: center;
381
- justify-content: center;
381
+ .cn-user-settings-dialog__group-sections {
382
+ padding-inline-start: 10px;
382
383
  }
383
384
 
384
385
  .cn-user-settings-dialog__sections {
385
386
  display: flex;
386
- flex: 1 1 380px;
387
+ flex: 1 1 auto;
387
388
  flex-direction: column;
389
+ gap: 32px;
390
+ box-sizing: border-box;
388
391
  min-width: 0;
392
+ padding: 20px 64px 20px 24px;
393
+ overflow-y: auto;
394
+ border-inline-start: 1px solid var(--color-border);
395
+ border-radius: 8px 0 0 8px;
396
+ background: var(--color-main-background);
389
397
  }
390
398
 
391
399
  .cn-user-settings-dialog__sections :deep(.app-settings-section) {
392
400
  margin: 0;
393
- padding: 18px 0;
394
- border-top: 1px solid var(--color-border);
401
+ padding: 0;
402
+ border: 0;
395
403
  }
396
404
 
397
405
  .cn-user-settings-dialog__sections :deep(.app-settings-section__name) {
398
- margin: 0 0 12px;
399
- font-size: 17px;
406
+ margin: 0 0 6px;
407
+ font-size: 20px;
400
408
  font-weight: 700;
401
- display: flex;
402
- align-items: center;
403
- gap: 10px;
409
+ line-height: 1.3;
404
410
  }
405
411
 
406
412
  .cn-user-settings-dialog__note {
407
- flex: 1 1 auto;
408
413
  margin: 0;
409
414
  font-size: 14px;
410
415
  color: var(--color-text-maxcontrast);
411
416
  }
412
417
 
413
- .cn-user-settings-dialog__nc-links {
414
- margin-bottom: 2px;
415
- }
416
-
417
- .cn-user-settings-dialog__group {
418
- margin: 10px 0 2px;
419
- padding: 0 12px;
420
- font-size: 15px;
421
- font-weight: 600;
422
- color: var(--color-main-text);
423
- }
424
-
425
- .cn-user-settings-dialog__group-sections {
426
- padding-inline-start: 12px;
427
- }
428
-
429
- .cn-user-settings-dialog__heading-icon {
430
- display: inline-flex;
431
- order: -1;
432
- color: var(--color-text-maxcontrast);
418
+ .cn-user-settings-dialog__close {
419
+ position: absolute;
420
+ top: 8px;
421
+ inset-inline-end: 8px;
433
422
  }
434
423
 
435
- /* One section at a time: the others are mounted but hidden. */
436
- .cn-user-settings-dialog__sections :deep(.app-settings-section[hidden]) {
437
- display: none;
424
+ @media (max-width: 600px) {
425
+ .cn-user-settings-dialog__body {
426
+ flex-direction: column;
427
+ }
428
+
429
+ .cn-user-settings-dialog__nav {
430
+ flex-basis: auto;
431
+ width: auto;
432
+ max-height: 40vh;
433
+ }
434
+
435
+ .cn-user-settings-dialog__sections {
436
+ padding: 20px 16px;
437
+ border-inline-start: 0;
438
+ border-radius: 8px 8px 0 0;
439
+ }
438
440
  }
439
441
  </style>
@@ -115,6 +115,7 @@ import { useObjectStore } from '../../store/useObjectStore.js'
115
115
  import { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatcher.js'
116
116
  import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
117
117
  import { pageFilter, pageSort } from '../../utils/pageControls.js'
118
+ import { readPath } from '../../utils/readPath.js'
118
119
  import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
119
120
  import { compileRowClass } from '../../utils/rowClassRules.js'
120
121
  import { fieldsFromSchema } from '../../utils/schema.js'
@@ -406,6 +407,19 @@ export default {
406
407
  default: '',
407
408
  },
408
409
 
410
+ /**
411
+ * The row field (a dot-path) holding the id `rowRoute` opens, for a row
412
+ * that links to another record (a payment row opening its case:
413
+ * `rowIdField: "case"`). Empty (the default) uses the row's own id; a
414
+ * row whose field is empty does not link.
415
+ *
416
+ * @spec openspec/changes/object-table-row-id-field/specs/object-table/spec.md#requirement-a-row-can-link-to-the-record-a-field-names
417
+ */
418
+ rowIdField: {
419
+ type: String,
420
+ default: '',
421
+ },
422
+
409
423
  /**
410
424
  * Client-side row cap, forwarded to CnDataTable (`0` = all rows).
411
425
  * This is what makes `viewAllRoute` reachable for `endpointSource`
@@ -791,7 +805,7 @@ export default {
791
805
  * @return {object}
792
806
  */
793
807
  innerProps() {
794
- const { title, documentationUrl, widgetId, hideWrapper, source, endpointSource, actions, rowRoute, rowClass, schema, exportCsv, exportFileName, ...rest } = this.$props
808
+ const { title, documentationUrl, widgetId, hideWrapper, source, endpointSource, actions, rowRoute, rowIdField, rowClass, schema, exportCsv, exportFileName, ...rest } = this.$props
795
809
  const inner = {}
796
810
  for (const [k, v] of Object.entries(rest)) {
797
811
  if (v !== undefined) {
@@ -854,8 +868,10 @@ export default {
854
868
  */
855
869
  rowRouteFn() {
856
870
  return (row) => {
857
- const id = row && (row.id || (row['@self'] && row['@self'].id))
858
- return id ? { name: this.rowRoute, params: { id: String(id) } } : null
871
+ const id = this.rowIdField
872
+ ? readPath(row, this.rowIdField)
873
+ : row && (row.id || (row['@self'] && row['@self'].id))
874
+ return id !== null && id !== undefined && id !== '' && typeof id !== 'object' ? { name: this.rowRoute, params: { id: String(id) } } : null
859
875
  }
860
876
  },
861
877
 
@@ -30,6 +30,14 @@
30
30
  background-color: color-mix(in srgb, var(--color-main-text) 45%, transparent);
31
31
  }
32
32
 
33
+ /* Any modal of a board-look app (an app's own NcDialog or NcModal, not
34
+ opened through CnDialog) dims the page the same way. A page that takes the
35
+ Nextcloud look keeps Nextcloud's backdrop.
36
+ @spec openspec/changes/board-modal-backdrop/specs/board-modal-backdrop/spec.md#requirement-every-modal-in-a-board-app-dims-the-page-as-the-boards-do */
37
+ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
38
+ background-color: color-mix(in srgb, var(--color-main-text) 45%, transparent);
39
+ }
40
+
33
41
  /* Width by role, capped at the viewport minus a 16px gutter each side. The
34
42
  shadow carries `!important`: the nldesign theme removes every
35
43
  `.modal-container`'s shadow with `!important` and offers no token for it. */
@@ -70,6 +78,21 @@
70
78
  border: 0;
71
79
  }
72
80
 
81
+ /* The user settings dialog (CnUserSettingsDialog, DqPersoonlijkeInstellingen
82
+ board): the frame takes the navigation's tint and the body fills it edge
83
+ to edge; the white pane and the navigation are drawn by the component.
84
+ @spec openspec/changes/user-settings-dialog-dq-board/specs/user-settings-dialog-dq-board/spec.md#requirement-the-user-settings-dialog-follows-the-dq-board */
85
+ .modal-mask.cn-dialog-board.cn-user-settings-dialog .modal-wrapper .modal-container {
86
+ overflow: hidden;
87
+ background: var(--color-primary-element-light);
88
+ }
89
+
90
+ .modal-mask.cn-dialog-board.cn-user-settings-dialog .dialog__content {
91
+ gap: 0;
92
+ padding: 0;
93
+ overflow: hidden;
94
+ }
95
+
73
96
  /* Header: padding 22px 24px 0. */
74
97
  .cn-dialog-header {
75
98
  display: flex;
@@ -972,9 +972,13 @@
972
972
  outline-offset: 2px;
973
973
  }
974
974
 
975
- /* Under the card grid the footer is a row of its own, not a card's edge. */
975
+ /* Under the card grid the footer is a row of its own, not a card's edge: no
976
+ hairline, 4px 0 (DqAanMijToegewezen, BqAgents, BqStore).
977
+ @spec openspec/changes/board-pagination-under-card-grid/specs/board-pagination-under-card-grid/spec.md#requirement-the-page-footer-under-a-card-grid-is-a-plain-row */
976
978
  .cn-look-board .cn-index-page__main:not(.cn-index-page__main--table) > .cn-pagination--board {
977
979
  margin-top: 8px;
980
+ padding: 4px 0;
981
+ border-top: 0;
978
982
  }
979
983
 
980
984
  /* ------------------------------------------------------------------- cards */
@@ -1602,6 +1602,25 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1602
1602
  font-weight: 600;
1603
1603
  }
1604
1604
 
1605
+ /* A compact list (`density: "compact"`) draws its rows 10px above and below,
1606
+ at least 56px tall, 6px between lines, the title at 500 (DqProcesanalyse
1607
+ "Waar zaken blijven hangen").
1608
+ @spec openspec/changes/record-list-density/specs/record-list/spec.md#requirement-a-record-list-can-draw-compact-rows */
1609
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--compact .cn-record-list__row {
1610
+ box-sizing: border-box;
1611
+ min-height: 56px;
1612
+ padding: 10px 0;
1613
+ gap: 6px 16px;
1614
+ }
1615
+
1616
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--compact .cn-record-list__title {
1617
+ font-weight: 500;
1618
+ }
1619
+
1620
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--compact .cn-record-list__main {
1621
+ gap: 2px;
1622
+ }
1623
+
1605
1624
  /* Hover: the whole row takes the hover ground, 8px beyond the text so the
1606
1625
  text does not move. */
1607
1626
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link {