@conduction/nextcloud-vue 2.120.0 → 2.122.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 (79) hide show
  1. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +53 -1
  2. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  3. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +546 -523
  4. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  5. package/dist/esm/components/CnIndexPage/useNamedSource.js +9 -0
  6. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  7. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  8. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -0
  9. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  10. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js +9 -0
  11. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js.map +1 -0
  12. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js +214 -0
  13. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js.map +1 -0
  14. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js +125 -0
  15. package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js.map +1 -0
  16. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js +9 -0
  17. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js.map +1 -0
  18. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js +163 -0
  19. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js.map +1 -0
  20. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js +131 -0
  21. package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js.map +1 -0
  22. package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js +127 -0
  23. package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js.map +1 -0
  24. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js +9 -0
  25. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js.map +1 -0
  26. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js +82 -0
  27. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js.map +1 -0
  28. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js +61 -0
  29. package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js.map +1 -0
  30. package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js +60 -0
  31. package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js.map +1 -0
  32. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js +10 -0
  33. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js.map +1 -0
  34. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js +226 -0
  35. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js.map +1 -0
  36. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js +126 -0
  37. package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js.map +1 -0
  38. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +42 -32
  39. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  40. package/dist/esm/composables/indexSources.js +116 -16
  41. package/dist/esm/composables/indexSources.js.map +1 -1
  42. package/dist/esm/index.js +4 -0
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/l10n/en.json.js +42 -1
  45. package/dist/esm/l10n/en.json.js.map +1 -1
  46. package/dist/esm/l10n/nl.json.js +42 -1
  47. package/dist/esm/l10n/nl.json.js.map +1 -1
  48. package/dist/esm/library-style.js +1 -1
  49. package/dist/esm/nextcloud-vue.css +309 -13
  50. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  51. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  52. package/dist/nextcloud-vue.cjs.js +14136 -12578
  53. package/dist/nextcloud-vue.cjs.js.map +2 -2
  54. package/dist/nextcloud-vue.css +309 -13
  55. package/l10n/en.json +42 -1
  56. package/l10n/nl.json +42 -1
  57. package/package.json +1 -1
  58. package/src/components/CnIndexPage/CnIndexPage.vue +74 -3
  59. package/src/components/CnIndexPage/useNamedSource.js +9 -0
  60. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +1 -0
  61. package/src/components/CnReadinessChecklist/CnReadinessChecklist.vue +335 -0
  62. package/src/components/CnReadinessChecklist/index.js +9 -0
  63. package/src/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue +300 -0
  64. package/src/components/CnSchemaRulesWidget/index.js +10 -0
  65. package/src/components/CnSchemaRulesWidget/schemaRules.js +123 -0
  66. package/src/components/CnSearchIndexStatus/CnSearchIndexStatus.vue +160 -0
  67. package/src/components/CnSearchIndexStatus/index.js +10 -0
  68. package/src/components/CnSearchIndexStatus/searchIndexStatus.js +56 -0
  69. package/src/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue +328 -0
  70. package/src/components/CnShippedConfigurationSection/index.js +9 -0
  71. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +10 -0
  72. package/src/components/index.js +4 -0
  73. package/src/composables/indexSources.js +116 -16
  74. package/src/css/index-page.css +7 -0
  75. package/src/css/look-board-detail.css +27 -0
  76. package/src/css/look-board-index.css +108 -12
  77. package/src/css/look-board.css +27 -1
  78. package/src/index.js +4 -0
  79. package/src/utils/libraryWidgetKeys.js +1 -0
@@ -37,6 +37,7 @@ import CnRelatedObjectsWidgetForm from '../CnRelatedObjectsWidgetForm/CnRelatedO
37
37
  import CnRetentionWidget from '../CnRetentionWidget/CnRetentionWidget.vue'
38
38
  import CnSavedViewWidget2 from '../CnSavedViewWidget/CnSavedViewWidget.vue'
39
39
  import CnSavedViewWidgetForm2 from '../CnSavedViewWidgetForm/CnSavedViewWidgetForm.vue'
40
+ import CnSchemaRulesWidget from '../CnSchemaRulesWidget/CnSchemaRulesWidget.vue'
40
41
  import CnStackedBarWidget from '../CnStackedBarWidget/CnStackedBarWidget.vue'
41
42
  import CnStackedBarWidgetForm from '../CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue'
42
43
  import CnStagesWidget from '../CnStagesWidget/CnStagesWidget.vue'
@@ -311,6 +312,15 @@ registerDashboardWidget('retention', {
311
312
  ownsTitle: true,
312
313
  })
313
314
 
315
+ registerDashboardWidget('schema-rules', {
316
+ renderer: CnSchemaRulesWidget,
317
+ form: null,
318
+ defaultContent: { register: '', schema: '', objectId: '' },
319
+ displayName: 'Schema rules',
320
+ icon: 'ScaleBalance',
321
+ surfaces: ['detail-page'],
322
+ })
323
+
314
324
  registerDashboardWidget('plan', {
315
325
  renderer: CnPlanWidget,
316
326
  form: null,
@@ -464,3 +464,7 @@ export { CnBrandStripe } from './CnBrandStripe/index.js'
464
464
  export { CnDuplicateWarning } from './CnDuplicateWarning/index.js'
465
465
  export { CnEndpointNotice } from './CnEndpointNotice/index.js'
466
466
  export { CnCountTablesReport } from './CnCountTablesReport/index.js'
467
+ export { CnShippedConfigurationSection } from './CnShippedConfigurationSection/index.js'
468
+ export { CnSearchIndexStatus } from './CnSearchIndexStatus/index.js'
469
+ export { CnSchemaRulesWidget } from './CnSchemaRulesWidget/index.js'
470
+ export { CnReadinessChecklist } from './CnReadinessChecklist/index.js'
@@ -737,6 +737,14 @@ function readRowsPath(body, path) {
737
737
  return String(path).split('.').reduce((value, key) => (value && typeof value === 'object' ? value[key] : undefined), body)
738
738
  }
739
739
 
740
+ /**
741
+ * The `sourceConfig` keys the endpoint source reads itself; every other key
742
+ * goes along as a query parameter.
743
+ *
744
+ * @type {Array<string>}
745
+ */
746
+ const ENDPOINT_SOURCE_KEYS = ['url', 'rowsPath', 'totalPath', 'searchParam', 'searchFields', 'notePath', 'noteText', 'refusedText']
747
+
740
748
  /**
741
749
  * The endpoint source: rows from an app's own endpoint
742
750
  * (`entitySource: "endpoint"`), for a list that is not OpenRegister objects,
@@ -759,25 +767,46 @@ function readRowsPath(body, path) {
759
767
  * request with that tab's filter, its total read at `sourceConfig.totalPath`
760
768
  * (`total` by default), else the number of rows the answer holds.
761
769
  *
770
+ * Three more keys shape what the page says around the rows:
771
+ * - `searchParam`: the page's search box sends its term as this parameter
772
+ * (`sender`), so the endpoint does the searching.
773
+ * - `notePath` and `noteText`: a value read off the answer (`filterOrder`) and
774
+ * the sentence it is said in ("Filters run in this order: {order}", its one
775
+ * placeholder taking the value); a list
776
+ * is joined with commas. Shown above the rows.
777
+ * - `refusedText`: what a refused load (403) says when the endpoint gave no
778
+ * `message` of its own. A refusal is not an empty list, so the page shows it
779
+ * instead of the rows, and a failed load is not one either.
780
+ * - `searchFields`: the filters the endpoint takes, in the same shape a
781
+ * registered source declares them (`{ status: { param: 'status', single:
782
+ * true } }`), so a filter strip or sidebar on the page narrows the
783
+ * endpoint's query.
784
+ *
785
+ * @param {object} [declared] The page's `sourceConfig`, for `searchParam` and `searchFields`.
786
+ * @param {object} [context] The page's helpers; `translate` is the app's catalogue.
762
787
  * @return {object} The source adapter.
763
788
  * @spec openspec/changes/index-endpoint-source/specs/index-page/spec.md#requirement-an-index-page-can-list-rows-from-an-app-endpoint
764
789
  */
765
- function endpointSource() {
766
- const state = reactive({ rows: [], loading: false })
790
+ function endpointSource(declared = {}, context = {}) {
791
+ const state = reactive({ rows: [], loading: false, refusal: '', failed: false, note: '' })
792
+ const translate = typeof context.translate === 'function' ? context.translate : (text) => text
793
+ const searchParam = typeof declared.searchParam === 'string' && declared.searchParam !== '' ? declared.searchParam : null
794
+ const searchFields = declared.searchFields && typeof declared.searchFields === 'object' && !Array.isArray(declared.searchFields) ? declared.searchFields : null
767
795
  let seq = 0
768
796
  let warned = false
769
797
 
770
798
  /**
771
- * The query parameters for a config: every key but the url and the paths,
772
- * empty values left out, a list joined by commas.
799
+ * The query parameters for a config: every key but the source's own
800
+ * (`url`, the paths, the texts), empty values left out, a list joined by
801
+ * commas.
773
802
  *
774
- * @param {object} rest The config without `url`, `rowsPath` and `totalPath`.
803
+ * @param {object} config The config.
775
804
  * @return {object} The parameters.
776
805
  */
777
- const paramsOf = (rest) => {
806
+ const paramsOf = (config) => {
778
807
  const params = {}
779
- for (const [key, value] of Object.entries(rest)) {
780
- if (value === undefined || value === null || value === '') {
808
+ for (const [key, value] of Object.entries(config)) {
809
+ if (ENDPOINT_SOURCE_KEYS.includes(key) || value === undefined || value === null || value === '') {
781
810
  continue
782
811
  }
783
812
  params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value)
@@ -797,15 +826,61 @@ function endpointSource() {
797
826
  return typeof url === 'string' && url !== '' && !Object.hasOwn(config, 'entitySource')
798
827
  }
799
828
 
829
+ /**
830
+ * The note read off an answer, in the page's sentence.
831
+ *
832
+ * @param {object} body The response body.
833
+ * @param {string} [path] Where the value sits.
834
+ * @param {string} [text] The sentence, with one `{placeholder}` where the value goes.
835
+ * @return {string} The note, or '' when there is nothing to say.
836
+ */
837
+ const noteOf = (body, path, text) => {
838
+ if (!path || !body || typeof body !== 'object' || Array.isArray(body)) {
839
+ return ''
840
+ }
841
+ const raw = readRowsPath(body, path)
842
+ const value = Array.isArray(raw) ? raw.filter((v) => v !== null && v !== undefined && v !== '').join(', ') : (raw === null || raw === undefined ? '' : String(raw))
843
+ if (value === '') {
844
+ return ''
845
+ }
846
+ return text ? translate(text).replace(/\{\w+\}/, value) : value
847
+ }
848
+
849
+ /**
850
+ * The sentence a refused load is said in: the endpoint's own, else the
851
+ * page's, else the library's.
852
+ *
853
+ * @param {object} body The refusal's body.
854
+ * @param {string} [text] The page's `refusedText`.
855
+ * @return {string} The sentence.
856
+ */
857
+ const refusalOf = (body, text) => {
858
+ if (body && typeof body.message === 'string' && body.message.trim() !== '') {
859
+ return body.message
860
+ }
861
+ return text ? translate(text) : t('nextcloud-vue', 'You may not read this list.')
862
+ }
863
+
800
864
  return {
865
+ /**
866
+ * The parameter the page's search term is sent as, or null when this
867
+ * endpoint takes no search.
868
+ */
869
+ searchParam,
870
+
871
+ /**
872
+ * The page filters this endpoint takes, and the parameter each becomes.
873
+ */
874
+ searchFields,
875
+
801
876
  /**
802
877
  * Fetch the rows.
803
878
  *
804
879
  * @param {object} config The page's source config with the tab filter merged in.
805
- * @return {Promise<void>} Resolves when loaded; a failed request leaves no rows and rethrows.
880
+ * @return {Promise<void>} Resolves when loaded or refused; a failed request leaves no rows and rethrows.
806
881
  */
807
882
  load: async (config) => {
808
- const { url, rowsPath, totalPath, ...rest } = config || {}
883
+ const { url, rowsPath, notePath, noteText, refusedText } = config || {}
809
884
  const mine = ++seq
810
885
  if (!requestable(config)) {
811
886
  state.rows = []
@@ -817,7 +892,7 @@ function endpointSource() {
817
892
  }
818
893
  return
819
894
  }
820
- const params = paramsOf(rest)
895
+ const params = paramsOf(config)
821
896
  state.loading = true
822
897
  try {
823
898
  const { default: axios } = await import('@nextcloud/axios')
@@ -826,12 +901,22 @@ function endpointSource() {
826
901
  const rows = Array.isArray(body) ? body : readRowsPath(body, rowsPath || 'results')
827
902
  if (mine === seq) {
828
903
  state.rows = Array.isArray(rows) ? rows : []
904
+ state.refusal = ''
905
+ state.failed = false
906
+ state.note = noteOf(body, notePath, noteText)
829
907
  }
830
908
  } catch (error) {
831
- if (mine === seq) {
832
- state.rows = []
909
+ if (mine !== seq) {
910
+ return
911
+ }
912
+ state.rows = []
913
+ state.note = ''
914
+ const refused = error && error.response && error.response.status === 403
915
+ state.refusal = refused ? refusalOf(error.response.data, refusedText) : ''
916
+ state.failed = !refused
917
+ if (!refused) {
918
+ throw error
833
919
  }
834
- throw error
835
920
  } finally {
836
921
  if (mine === seq) {
837
922
  state.loading = false
@@ -850,9 +935,9 @@ function endpointSource() {
850
935
  if (!requestable(config)) {
851
936
  return Number.NaN
852
937
  }
853
- const { url, rowsPath, totalPath, ...rest } = config
938
+ const { url, rowsPath, totalPath } = config
854
939
  const { default: axios } = await import('@nextcloud/axios')
855
- const res = await axios.get(generateUrl(url), { params: paramsOf(rest) })
940
+ const res = await axios.get(generateUrl(url), { params: paramsOf(config) })
856
941
  const body = res ? res.data : null
857
942
  const total = body && typeof body === 'object' && !Array.isArray(body) ? readRowsPath(body, totalPath || 'total') : undefined
858
943
  if (typeof total === 'number' && Number.isFinite(total)) {
@@ -871,6 +956,21 @@ function endpointSource() {
871
956
  * @return {boolean} Whether a load is in flight.
872
957
  */
873
958
  loading: () => state.loading,
959
+
960
+ /**
961
+ * @return {string} The sentence the last load was refused with, or ''.
962
+ */
963
+ refusal: () => state.refusal,
964
+
965
+ /**
966
+ * @return {boolean} Whether the last load failed for another reason than a refusal.
967
+ */
968
+ failed: () => state.failed,
969
+
970
+ /**
971
+ * @return {string} The note read off the last answer, or ''.
972
+ */
973
+ note: () => state.note,
874
974
  }
875
975
  }
876
976
 
@@ -222,6 +222,13 @@
222
222
  color: var(--color-text-maxcontrast);
223
223
  }
224
224
 
225
+ /* A note the named source read off its answer, above the rows. */
226
+ .cn-index-page__source-note {
227
+ margin: 0 0 8px;
228
+ color: var(--color-text-maxcontrast);
229
+ font-size: 0.9em;
230
+ }
231
+
225
232
  .cn-index-page__scope-heading {
226
233
  display: flex;
227
234
  flex-direction: column;
@@ -974,3 +974,30 @@
974
974
  .cn-look-board .cn-tasks-widget .cn-tasks-widget__cell-actions .button-vue .button-vue__text.button-vue__text {
975
975
  padding-inline: 0;
976
976
  }
977
+
978
+ /* ---- Grouped cards (CnGroupedCardsWidget, DqZaaktype "Fasen en taken") --- */
979
+ /* Groups 14px apart, each padded 14px with a 10px radius and a bold title; a
980
+ card in a group is padded 9px 12px with the 8px control radius, its title
981
+ 15px. The doubled class outranks the widget's scoped rules.
982
+ @spec openspec/changes/board-grouped-cards/specs/board-grouped-cards/spec.md#requirement-board-grouped-cards-take-the-boards-group-and-card-shape */
983
+ .cn-look-board .cn-grouped-cards__grid.cn-grouped-cards__grid {
984
+ gap: 14px;
985
+ }
986
+
987
+ .cn-look-board .cn-grouped-cards__group.cn-grouped-cards__group {
988
+ padding: 14px;
989
+ border-radius: 10px;
990
+ }
991
+
992
+ .cn-look-board .cn-grouped-cards__group-title.cn-grouped-cards__group-title {
993
+ font-weight: 700;
994
+ }
995
+
996
+ .cn-look-board .cn-grouped-cards__item.cn-grouped-cards__item {
997
+ padding: 9px 12px;
998
+ border-radius: var(--cn-board-control-radius, 8px);
999
+ }
1000
+
1001
+ .cn-look-board .cn-grouped-cards__item-title.cn-grouped-cards__item-title {
1002
+ font-size: 15px;
1003
+ }
@@ -634,16 +634,36 @@
634
634
  width: 18%;
635
635
  }
636
636
 
637
- /* The board's data cells sit 12px apart (PqTickets), the first one 18px from
638
- the selection column; the selection and menu columns keep 16px. */
639
- .cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table th:not(.cn-table-col--checkbox):not(.cn-table-col--actions):not(.cn-table-col--icon),
640
- .cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table td:not(.cn-table-col--checkbox):not(.cn-table-col--actions):not(.cn-table-col--icon) {
637
+ /* An index table pads every cell 16px at the sides (DqZaken). The plain-title
638
+ table (`rowTitle: "plain"`: PqTickets, OcPublicaties) sets its data cells
639
+ 12px apart and its title column 18px from the selection column; the
640
+ selection and menu columns keep 16px. The title column is left out of the
641
+ 12px rule, which would otherwise outrank the 18px one.
642
+ @spec openspec/changes/board-table-density/specs/board-table-density/spec.md#requirement-a-board-table-pads-its-cells-as-the-boards-draw-them */
643
+ .cn-look-board .cn-index-page__main--table .cn-table-container--board.cn-table-container--title-plain .cn-data-table th:not(.cn-table-col--checkbox):not(.cn-table-col--actions):not(.cn-table-col--icon):not(.cn-table-head--title),
644
+ .cn-look-board .cn-index-page__main--table .cn-table-container--board.cn-table-container--title-plain .cn-data-table td:not(.cn-table-col--checkbox):not(.cn-table-col--actions):not(.cn-table-col--icon):not(.cn-table-col--title) {
641
645
  padding-inline: 12px;
642
646
  }
643
647
 
644
- .cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table th.cn-table-head--title,
645
- .cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table td.cn-table-col--title {
646
- padding-inline-start: 18px;
648
+ .cn-look-board .cn-index-page__main--table .cn-table-container--board.cn-table-container--title-plain .cn-data-table th.cn-table-head--title,
649
+ .cn-look-board .cn-index-page__main--table .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title {
650
+ padding-inline: 18px 12px;
651
+ }
652
+
653
+ /* A table that is not an index page's main list sits in a card and is the
654
+ compact one: 10px 12px in the header, 11px 12px in the cells
655
+ (DqInstellingen, DqVthInstellingen, DqZaaktype, DqTermijnen). The selection
656
+ column keeps its 16px, so it stays 60px wide. */
657
+ .cn-look-board .cn-table-container--board:not(.cn-index-page__main--table > *) .cn-data-table th {
658
+ padding: 10px 12px;
659
+ }
660
+
661
+ .cn-look-board .cn-table-container--board:not(.cn-index-page__main--table > *) .cn-data-table td {
662
+ padding: 11px 12px;
663
+ }
664
+
665
+ .cn-look-board .cn-table-container--board:not(.cn-index-page__main--table > *) .cn-data-table .cn-table-col--checkbox {
666
+ padding-inline: 16px;
647
667
  }
648
668
 
649
669
  .cn-look-board .cn-index-page__main--table .cn-table-container--board .cn-data-table td > .cn-cell-renderer {
@@ -873,12 +893,15 @@
873
893
  }
874
894
 
875
895
  /* A page link is a <button>: Nextcloud's `button` rule sets it bold, the
876
- boards set the links at the regular weight and only the current page at 600. */
896
+ boards set the links at the regular weight and only the current page at 600.
897
+ The boards size a link's content box at 34px, so a bordered link is 36px
898
+ and the borderless current page 34px (DqZaken "2", "Volgende", "1").
899
+ @spec openspec/changes/board-pagination-links/specs/board-pagination-links/spec.md#requirement-a-board-page-link-is-sized-as-the-boards-draw-it */
877
900
  .cn-look-board .cn-pagination--board .cn-pagination__board-link {
878
901
  box-sizing: border-box;
879
- min-width: 34px;
880
- height: 34px;
881
- min-height: 34px;
902
+ min-width: 36px;
903
+ height: 36px;
904
+ min-height: 36px;
882
905
  margin: 0;
883
906
  padding: 0 8px;
884
907
  border: 1px solid var(--color-border);
@@ -903,7 +926,10 @@
903
926
  }
904
927
 
905
928
  .cn-look-board .cn-pagination--board .cn-pagination__board-link--current {
906
- border-color: var(--color-primary-element);
929
+ min-width: 34px;
930
+ height: 34px;
931
+ min-height: 34px;
932
+ border: 0;
907
933
  background: var(--color-primary-element);
908
934
  color: var(--color-primary-element-text);
909
935
  font-weight: 600;
@@ -1406,3 +1432,73 @@
1406
1432
  border-bottom: none;
1407
1433
  padding-block: 2px;
1408
1434
  }
1435
+
1436
+ /* ---- The index side panel (CnIndexSidebar, board IndexZijpaneel) -------- */
1437
+ /* The panel's title is 20px bold without the schema icon, and its tabs are
1438
+ one row of 40px labels, 14px weight 600 in the soft ink, the open one in
1439
+ the deep primary ink and underlined 2px in the primary colour on the
1440
+ strip's rule (DqFilterBeheren). NcAppSidebar stacks the icon over the
1441
+ label in a 54px tile with a highlight bar; the board does neither. The
1442
+ header rule repeats NcAppSidebar's own chain to outrank it.
1443
+ @spec openspec/changes/board-index-sidebar-tabs/specs/board-index-sidebar-tabs/spec.md#requirement-the-board-index-side-panel-draws-the-boards-header-and-tab-row */
1444
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-header__desc .app-sidebar-header__name-container .app-sidebar-header__mainname-container h2.app-sidebar-header__mainname {
1445
+ font-size: 20px;
1446
+ font-weight: 700;
1447
+ line-height: 1.2;
1448
+ min-height: 0;
1449
+ }
1450
+
1451
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-header__figure:has(> .cn-index-sidebar__header-icon) {
1452
+ display: none;
1453
+ }
1454
+
1455
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__nav[role="tablist"] {
1456
+ justify-content: flex-start;
1457
+ gap: 4px;
1458
+ margin: 0;
1459
+ padding: 14px 12px 0;
1460
+ border-bottom: 1px solid var(--color-border);
1461
+ }
1462
+
1463
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__highlight {
1464
+ display: none;
1465
+ }
1466
+
1467
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__tab[role="tab"] {
1468
+ flex: 0 0 auto;
1469
+ flex-direction: row;
1470
+ align-items: center;
1471
+ gap: 6px;
1472
+ box-sizing: border-box;
1473
+ height: 40px;
1474
+ min-height: 40px;
1475
+ margin: 0 0 -1px;
1476
+ padding: 0 10px;
1477
+ border: 0;
1478
+ border-bottom: 2px solid transparent;
1479
+ border-radius: 0;
1480
+ background: transparent;
1481
+ color: var(--cn-board-text-soft);
1482
+ font-size: 14px;
1483
+ font-weight: 600;
1484
+ }
1485
+
1486
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__tab[role="tab"] * {
1487
+ font-weight: inherit;
1488
+ }
1489
+
1490
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__tab[role="tab"]::after {
1491
+ content: none;
1492
+ }
1493
+
1494
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__tab[role="tab"]:hover {
1495
+ background: transparent;
1496
+ border-bottom-color: var(--color-border-dark);
1497
+ color: var(--color-main-text);
1498
+ }
1499
+
1500
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__tab[role="tab"][aria-selected="true"] {
1501
+ background: transparent;
1502
+ border-bottom-color: var(--color-primary-element);
1503
+ color: var(--color-primary-element-light-text);
1504
+ }
@@ -228,9 +228,15 @@
228
228
  look sets 600 on each button, so every NcButton on a board page draws the
229
229
  board's weight without a rule per surface. Breadcrumb buttons keep the
230
230
  crumb weight.
231
- @spec openspec/changes/board-button-weight/specs/board-button-weight/spec.md#requirement-a-board-button-draws-at-600 */
231
+ The boards also draw a button's text at 14px, against Nextcloud's 15px.
232
+ NcButton reads its size from --default-font-size, so the look sets that
233
+ variable on the button: a surface that sets its own `font-size` (the next
234
+ step card's 15px) keeps it.
235
+ @spec openspec/changes/board-button-weight/specs/board-button-weight/spec.md#requirement-a-board-button-draws-at-600
236
+ @spec openspec/changes/board-control-text/specs/board-control-text/spec.md#requirement-a-board-button-draws-its-text-at-14px */
232
237
  .cn-look-board .button-vue:not(.cn-look-nextcloud *):not(.vue-crumb *) {
233
238
  --font-weight-element: 600;
239
+ --default-font-size: 14px;
234
240
  }
235
241
 
236
242
  /* ---- A button with an icon and a label --------------------------------- */
@@ -393,6 +399,26 @@
393
399
  background: var(--color-background-hover);
394
400
  }
395
401
 
402
+ /* ---- A secondary button --------------------------------------------------- */
403
+ /* The boards draw every secondary button neutral: the main background, a 1px
404
+ control border, the main text, the hover ground on hover (DqFlow "Testen
405
+ met een zaak", DqFunctionaliteiten "Documentatie", DqTaak "Afronden").
406
+ NcButton draws its secondary from the primary-light variables, on a 2px
407
+ bottom edge; the look points those variables at the neutral colours on the
408
+ button itself. The selector weighs (0,3,0): it outranks NcButton's border
409
+ (0,2,0), and a surface rule of the same weight later in the cascade (a
410
+ dialog's cancel) still decides for its surface.
411
+ @spec openspec/changes/board-secondary-neutral/specs/board-secondary-neutral/spec.md#requirement-a-board-secondary-button-is-neutral */
412
+ .cn-look-board .button-vue.button-vue--secondary:where(:not(.cn-look-nextcloud *):not(.vue-crumb *)),
413
+ .cn-look-board .button-vue.button-vue--vue-secondary:where(:not(.cn-look-nextcloud *):not(.vue-crumb *)) {
414
+ --color-primary-element-light: var(--color-main-background);
415
+ --color-primary-element-light-hover: var(--color-background-hover);
416
+ --color-primary-element-light-text: var(--color-main-text);
417
+ --nldesign-color-on-surface: var(--color-main-text);
418
+ border: 1px solid var(--cn-board-control-border);
419
+ color: var(--color-main-text);
420
+ }
421
+
396
422
  /* ---- Header action buttons (CnActionButtons) ----------------------------- */
397
423
  /* The board draws a page's secondary actions as neutral 40px buttons: the
398
424
  main background, a grey border, the main text at 600 (DqZaak "Bericht",
package/src/index.js CHANGED
@@ -275,6 +275,7 @@ export {
275
275
  CnQuicklinksWidget,
276
276
  CnQuicklinksWidgetForm,
277
277
  CnRadioCardsWidget,
278
+ CnReadinessChecklist,
278
279
  CnRecordListWidget,
279
280
  CnRedactionEditor,
280
281
  CnReferencePreview,
@@ -304,6 +305,8 @@ export {
304
305
  CnSavedViewWidgetForm,
305
306
  CnSaveViewDialog,
306
307
  CnSchemaFormDialog,
308
+ CnSchemaRulesWidget,
309
+ CnSearchIndexStatus,
307
310
  CnSearchPage,
308
311
  CnSearchSelect,
309
312
  CnSectionsWidget,
@@ -315,6 +318,7 @@ export {
315
318
  CnSettingsPage,
316
319
  CnSettingsSection,
317
320
  CnSetupWizard,
321
+ CnShippedConfigurationSection,
318
322
  CnSignatureCapture,
319
323
  CnSpendAnalyticsWidget,
320
324
  CnSpendAnalyticsWidgetForm,
@@ -151,6 +151,7 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
151
151
  'retention',
152
152
  'saved-view',
153
153
  'saved-views',
154
+ 'schema-rules',
154
155
  'sections',
155
156
  'settings-form',
156
157
  'spend-analytics',