@conduction/nextcloud-vue 2.56.0-beta.11 → 2.56.0-beta.13
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.
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +253 -10
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +139 -46
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +87 -0
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +26 -24
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +154 -4
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +555 -480
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +37 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +12 -4
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +187 -19
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +215 -135
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +157 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +23 -5
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +5 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
- package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +74 -5
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +15 -8
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +5 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +400 -0
- package/dist/esm/mixins/pageViews.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +513 -81
- package/dist/esm/store/useObjectStore.js +118 -33
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +25 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +27 -3
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/schema.js +40 -2
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/schemaRefSlug.js +19 -3
- package/dist/esm/utils/schemaRefSlug.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8858 -6566
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +513 -81
- package/l10n/en.json +5 -0
- package/l10n/nl.json +5 -0
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +441 -14
- package/src/components/CnAppRoot/CnAppRoot.md +4 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +94 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +237 -12
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
- package/src/components/CnDataTable/CnDataTable.vue +45 -1
- package/src/components/CnDetailPage/CnDetailPage.md +8 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +263 -7
- package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +206 -9
- package/src/components/CnIndexPage/CnIndexPage.vue +32 -2
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +7 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +189 -2
- package/src/components/CnStagesWidget/stagesModel.js +5 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +89 -7
- package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
- package/src/css/detail-page.css +37 -4
- package/src/css/kpi-card.css +71 -1
- package/src/css/table.css +18 -6
- package/src/mixins/pageViews.js +396 -0
- package/src/schemas/app-manifest-v2.schema.json +162 -3
- package/src/store/useObjectStore.js +118 -33
- package/src/utils/actionsDispatcher.js +25 -2
- package/src/utils/columnFilters.js +29 -2
- package/src/utils/schema.js +40 -2
- package/src/utils/schemaRefSlug.js +19 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
package/l10n/en.json
CHANGED
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
"No integrations available yet.": "No integrations available yet.",
|
|
49
49
|
"This integration is not available.": "This integration is not available.",
|
|
50
50
|
"Integrations": "Integrations",
|
|
51
|
+
"View": "View",
|
|
52
|
+
"Views": "Views",
|
|
53
|
+
"current step": "current step",
|
|
51
54
|
"{app} not available": "{app} not available",
|
|
52
55
|
"{app} not configured": "{app} not configured",
|
|
53
56
|
"This integration is not available or not configured yet.": "This integration is not available or not configured yet.",
|
|
@@ -406,6 +409,7 @@
|
|
|
406
409
|
"Any": "Any",
|
|
407
410
|
"To": "To",
|
|
408
411
|
"Equals": "Equals",
|
|
412
|
+
"Contains": "Contains",
|
|
409
413
|
"Clear": "Clear",
|
|
410
414
|
"Apply": "Apply",
|
|
411
415
|
"This column cannot list its objects.": "This column cannot list its objects.",
|
|
@@ -1086,6 +1090,7 @@
|
|
|
1086
1090
|
"The actions of an attention card are declared in the manifest.": "The actions of an attention card are declared in the manifest.",
|
|
1087
1091
|
"An attention card needs a title": "An attention card needs a title",
|
|
1088
1092
|
"Good morning, {name}": "Good morning, {name}",
|
|
1093
|
+
"This view has no widgets yet.": "This view has no widgets yet.",
|
|
1089
1094
|
"Good morning": "Good morning",
|
|
1090
1095
|
"Good afternoon, {name}": "Good afternoon, {name}",
|
|
1091
1096
|
"Good afternoon": "Good afternoon",
|
package/l10n/nl.json
CHANGED
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
"No integrations available yet.": "Nog geen integraties beschikbaar.",
|
|
49
49
|
"This integration is not available.": "Deze integratie is niet beschikbaar.",
|
|
50
50
|
"Integrations": "Integraties",
|
|
51
|
+
"View": "Weergave",
|
|
52
|
+
"Views": "Weergaven",
|
|
53
|
+
"current step": "huidige stap",
|
|
51
54
|
"{app} not available": "{app} niet beschikbaar",
|
|
52
55
|
"{app} not configured": "{app} niet geconfigureerd",
|
|
53
56
|
"This integration is not available or not configured yet.": "Deze integratie is nog niet beschikbaar of niet geconfigureerd.",
|
|
@@ -406,6 +409,7 @@
|
|
|
406
409
|
"Any": "Alle",
|
|
407
410
|
"To": "Tot",
|
|
408
411
|
"Equals": "Is gelijk aan",
|
|
412
|
+
"Contains": "Bevat",
|
|
409
413
|
"Clear": "Wissen",
|
|
410
414
|
"Apply": "Toepassen",
|
|
411
415
|
"This column cannot list its objects.": "Deze kolom kan zijn objecten niet tonen.",
|
|
@@ -1130,6 +1134,7 @@
|
|
|
1130
1134
|
"The actions of an attention card are declared in the manifest.": "De acties van een aandachtskaart staan in het manifest.",
|
|
1131
1135
|
"An attention card needs a title": "Een aandachtskaart heeft een titel nodig",
|
|
1132
1136
|
"Good morning, {name}": "Goedemorgen, {name}",
|
|
1137
|
+
"This view has no widgets yet.": "Deze weergave heeft nog geen widgets.",
|
|
1133
1138
|
"Good morning": "Goedemorgen",
|
|
1134
1139
|
"Good afternoon, {name}": "Goedemiddag, {name}",
|
|
1135
1140
|
"Good afternoon": "Goedemiddag",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.56.0-beta.
|
|
3
|
+
"version": "2.56.0-beta.13",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -92,8 +92,23 @@
|
|
|
92
92
|
<!-- Decorative beside a name: the name already says whose
|
|
93
93
|
app this is, and an alt text would say it twice. A logo
|
|
94
94
|
on its own carries the name as its alt text. -->
|
|
95
|
+
<!-- An emblem (`nav.brand.emblem`) stands in for the logo:
|
|
96
|
+
the municipality's mark beside the app name, distinct
|
|
97
|
+
from the wordmark in the top bar. `true` draws the
|
|
98
|
+
theme's emblem from --nldesign-emblem-url. -->
|
|
95
99
|
<img
|
|
96
|
-
v-if="resolvedBrand.
|
|
100
|
+
v-if="typeof resolvedBrand.emblem === 'string' && resolvedBrand.emblem !== ''"
|
|
101
|
+
class="cn-app-nav__brand-emblem"
|
|
102
|
+
data-testid="cn-nav-brand-emblem"
|
|
103
|
+
:src="resolvedBrand.emblem"
|
|
104
|
+
:alt="resolvedBrand.name ? '' : resolvedBrand.alt">
|
|
105
|
+
<span
|
|
106
|
+
v-else-if="resolvedBrand.emblem === true"
|
|
107
|
+
class="cn-app-nav__brand-emblem cn-app-nav__brand-emblem--theme"
|
|
108
|
+
data-testid="cn-nav-brand-emblem"
|
|
109
|
+
aria-hidden="true" />
|
|
110
|
+
<img
|
|
111
|
+
v-else-if="resolvedBrand.logo"
|
|
97
112
|
class="cn-app-nav__brand-logo"
|
|
98
113
|
:src="resolvedBrand.logo"
|
|
99
114
|
:alt="resolvedBrand.name ? '' : resolvedBrand.alt">
|
|
@@ -118,17 +133,33 @@
|
|
|
118
133
|
OR nav root). Omitted entirely when neither the slot nor any
|
|
119
134
|
resolvable primaryAction is present. -->
|
|
120
135
|
<slot name="primary-action">
|
|
136
|
+
<!-- A primary action that carries an `action` runs a page action
|
|
137
|
+
(open-form opens the create dialog, navigate pushes a route)
|
|
138
|
+
through CnActionButtons, so "New case" can live in the
|
|
139
|
+
navigation without a create page. Drawn as one solid,
|
|
140
|
+
full-width primary button. -->
|
|
141
|
+
<div
|
|
142
|
+
v-if="activePrimaryAction && primaryDispatchEntry"
|
|
143
|
+
class="app-navigation-new cn-app-nav__primary-action cn-app-nav__primary-action--solid"
|
|
144
|
+
data-testid="cn-nav-primary-action">
|
|
145
|
+
<CnActionButtons
|
|
146
|
+
:actions="[primaryDispatchEntry]"
|
|
147
|
+
data-testid="cn-nav-primary-action-dispatch"
|
|
148
|
+
@created="onPrimaryActionCreated" />
|
|
149
|
+
</div>
|
|
121
150
|
<!-- A primary action with an href or route is a real link.
|
|
122
151
|
NcAppNavigationNew cannot render one, so this mirrors its
|
|
123
|
-
wrapper and button.
|
|
152
|
+
wrapper and button. `solid: true` takes this same solid,
|
|
153
|
+
full-width button for an action with neither. -->
|
|
124
154
|
<div
|
|
125
|
-
v-if="activePrimaryAction && primaryActionLink"
|
|
155
|
+
v-else-if="activePrimaryAction && (primaryActionLink || activePrimaryAction.solid === true)"
|
|
126
156
|
class="app-navigation-new cn-app-nav__primary-action"
|
|
157
|
+
:class="{ 'cn-app-nav__primary-action--solid': activePrimaryAction.solid === true }"
|
|
127
158
|
data-testid="cn-nav-primary-action">
|
|
128
159
|
<NcButton
|
|
129
160
|
variant="primary"
|
|
130
161
|
wide
|
|
131
|
-
v-bind="primaryActionLink"
|
|
162
|
+
v-bind="primaryActionLink || {}"
|
|
132
163
|
@click="onPrimaryActionClick">
|
|
133
164
|
<template #icon>
|
|
134
165
|
<component :is="primaryActionIconComponent" :size="20" />
|
|
@@ -215,7 +246,32 @@
|
|
|
215
246
|
</NcAppNavigationItem>
|
|
216
247
|
</template>
|
|
217
248
|
</template>
|
|
218
|
-
<template v-if="footerItems.length > 0 || showSettingsFoldout" #footer>
|
|
249
|
+
<template v-if="footerItems.length > 0 || showSettingsFoldout || resolvedCard || showsHelp" #footer>
|
|
250
|
+
<!--
|
|
251
|
+
@slot card
|
|
252
|
+
@description Replace the card above the footer entries
|
|
253
|
+
(`nav.card`). Default content: a title, a line of text and one
|
|
254
|
+
link. Renders nothing when neither the slot nor `nav.card` is set.
|
|
255
|
+
@binding {object|null} card The resolved card, or null.
|
|
256
|
+
-->
|
|
257
|
+
<slot name="card" :card="resolvedCard">
|
|
258
|
+
<div
|
|
259
|
+
v-if="resolvedCard"
|
|
260
|
+
class="cn-app-nav__card"
|
|
261
|
+
data-testid="cn-nav-card">
|
|
262
|
+
<strong class="cn-app-nav__card-title">{{ resolvedCard.title }}</strong>
|
|
263
|
+
<span v-if="resolvedCard.text" class="cn-app-nav__card-text">{{ resolvedCard.text }}</span>
|
|
264
|
+
<component
|
|
265
|
+
:is="resolvedCard.linkTag"
|
|
266
|
+
v-if="resolvedCard.linkTag"
|
|
267
|
+
class="cn-app-nav__card-link"
|
|
268
|
+
data-testid="cn-nav-card-link"
|
|
269
|
+
v-bind="resolvedCard.linkAttrs"
|
|
270
|
+
@click="onCardLinkClick">
|
|
271
|
+
{{ resolvedCard.linkLabel }}
|
|
272
|
+
</component>
|
|
273
|
+
</div>
|
|
274
|
+
</slot>
|
|
219
275
|
<!-- Footer-section entries (Documentation, Features & Roadmap,
|
|
220
276
|
About) live in NcAppNavigation's #footer slot — OUTSIDE the
|
|
221
277
|
scrollable list — so they stay visible above the settings
|
|
@@ -226,7 +282,24 @@
|
|
|
226
282
|
hover highlight are scoped to itself, so they survive being
|
|
227
283
|
slotted. The main list's inset comes from NcAppNavigation's
|
|
228
284
|
scope, which slot content does not carry. -->
|
|
229
|
-
<NcAppNavigationList
|
|
285
|
+
<NcAppNavigationList
|
|
286
|
+
v-if="footerItems.length > 0 || showsHelp"
|
|
287
|
+
class="cn-app-nav__footer-list"
|
|
288
|
+
:class="{ 'cn-app-nav__footer-list--after-settings': settingsFirst }">
|
|
289
|
+
<!-- The help entry (`nav.help`): a link to the app's own help
|
|
290
|
+
with a help icon, above the footer entries (or after them,
|
|
291
|
+
when a declared `nav.footer` names it last). -->
|
|
292
|
+
<NcAppNavigationItem
|
|
293
|
+
v-if="showsHelp && !helpAfterItems"
|
|
294
|
+
:name="resolvedHelp.label"
|
|
295
|
+
:to="resolvedHelp.to"
|
|
296
|
+
:href="resolvedHelp.href"
|
|
297
|
+
:target="resolvedHelp.href ? '_blank' : undefined"
|
|
298
|
+
data-testid="cn-nav-help">
|
|
299
|
+
<template #icon>
|
|
300
|
+
<HelpCircleOutline :size="20" />
|
|
301
|
+
</template>
|
|
302
|
+
</NcAppNavigationItem>
|
|
230
303
|
<NcAppNavigationItem
|
|
231
304
|
v-for="item in footerItems"
|
|
232
305
|
:key="item.id"
|
|
@@ -249,6 +322,17 @@
|
|
|
249
322
|
:active="isActive(item)" />
|
|
250
323
|
</template>
|
|
251
324
|
</NcAppNavigationItem>
|
|
325
|
+
<NcAppNavigationItem
|
|
326
|
+
v-if="showsHelp && helpAfterItems"
|
|
327
|
+
:name="resolvedHelp.label"
|
|
328
|
+
:to="resolvedHelp.to"
|
|
329
|
+
:href="resolvedHelp.href"
|
|
330
|
+
:target="resolvedHelp.href ? '_blank' : undefined"
|
|
331
|
+
data-testid="cn-nav-help">
|
|
332
|
+
<template #icon>
|
|
333
|
+
<HelpCircleOutline :size="20" />
|
|
334
|
+
</template>
|
|
335
|
+
</NcAppNavigationItem>
|
|
252
336
|
</NcAppNavigationList>
|
|
253
337
|
<!-- Settings foldout (section: "settings" items). NC-native
|
|
254
338
|
gear-icon button that slides open a panel; the first entry
|
|
@@ -257,6 +341,7 @@
|
|
|
257
341
|
<NcAppNavigationSettings
|
|
258
342
|
v-if="showSettingsFoldout"
|
|
259
343
|
:name="settingsFoldoutLabel"
|
|
344
|
+
:class="{ 'cn-app-nav__settings--first': settingsFirst }"
|
|
260
345
|
data-testid="cn-nav-settings">
|
|
261
346
|
<ul class="cn-app-nav__settings-list">
|
|
262
347
|
<NcAppNavigationItem
|
|
@@ -360,6 +445,7 @@ import CnMenuItemIcon from '../CnMenuWidget/CnMenuItemIcon.vue'
|
|
|
360
445
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
361
446
|
import { isSvgPath } from '../../utils/iconUtils.js'
|
|
362
447
|
import { passesContextPredicates } from '../../utils/visibleIfContext.js'
|
|
448
|
+
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
363
449
|
// The legacy `icon-*` → MDI bridge lives beside CnIcon now, so the menu EDITOR
|
|
364
450
|
// resolves a seeded `icon-comment` the same way this nav does. It used to be a
|
|
365
451
|
// map local to this file, which is why the nav drew a proper glyph and the
|
|
@@ -444,6 +530,7 @@ export default {
|
|
|
444
530
|
NcAppNavigationSettings,
|
|
445
531
|
NcButton,
|
|
446
532
|
NcCounterBubble,
|
|
533
|
+
CnActionButtons,
|
|
447
534
|
CnMenuItemIcon,
|
|
448
535
|
Cog,
|
|
449
536
|
HelpCircleOutline,
|
|
@@ -490,6 +577,16 @@ export default {
|
|
|
490
577
|
* @type {{ [register: string]: { [schema: string]: number } }}
|
|
491
578
|
*/
|
|
492
579
|
cnMenuCounts: { default: () => ({}) },
|
|
580
|
+
/**
|
|
581
|
+
* Provided by CnAppRoot — reactive `{ [menuItemId]: number }` totals
|
|
582
|
+
* for every menu entry whose `count` is an object
|
|
583
|
+
* (`{ register, schema, filter? }`): the total of that filtered
|
|
584
|
+
* list, fetched under the entry's own key so an index page's
|
|
585
|
+
* whole-schema total is left alone. Empty outside a CnAppRoot.
|
|
586
|
+
*
|
|
587
|
+
* @type {{ [itemId: string]: number }}
|
|
588
|
+
*/
|
|
589
|
+
cnMenuItemCounts: { default: () => ({}) },
|
|
493
590
|
},
|
|
494
591
|
|
|
495
592
|
props: {
|
|
@@ -613,7 +710,7 @@ export default {
|
|
|
613
710
|
},
|
|
614
711
|
},
|
|
615
712
|
|
|
616
|
-
emits: ['primary-action', 'primary-action-click'],
|
|
713
|
+
emits: ['primary-action', 'primary-action-click', 'primary-action-created', 'card-action'],
|
|
617
714
|
|
|
618
715
|
/**
|
|
619
716
|
* The navigation's own state: which groups are open, and which entry
|
|
@@ -674,11 +771,70 @@ export default {
|
|
|
674
771
|
const text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '')
|
|
675
772
|
const brand = {
|
|
676
773
|
logo: typeof declared.logo === 'string' ? declared.logo : '',
|
|
774
|
+
// A URL, or `true` for the theme's emblem (--nldesign-emblem-url).
|
|
775
|
+
emblem: declared.emblem === true ? true : (typeof declared.emblem === 'string' ? declared.emblem : ''),
|
|
677
776
|
name: text(declared.name),
|
|
678
777
|
caption: text(declared.caption),
|
|
679
778
|
alt: text(declared.alt),
|
|
680
779
|
}
|
|
681
|
-
return (brand.logo || brand.name || brand.caption) ? brand : null
|
|
780
|
+
return (brand.logo || brand.emblem || brand.name || brand.caption) ? brand : null
|
|
781
|
+
},
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* The card above the footer entries (`nav.card`): `{ title, text?,
|
|
785
|
+
* link?: { label, route?, params?, href?, action? } }`. Title, text
|
|
786
|
+
* and link label go through the translate function. The link is a
|
|
787
|
+
* router link for a `route` (with a router), an anchor for an
|
|
788
|
+
* `href`, and a button emitting `card-action` with the `action` id
|
|
789
|
+
* otherwise. Null without a title.
|
|
790
|
+
*
|
|
791
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
|
|
792
|
+
* @return {object|null}
|
|
793
|
+
*/
|
|
794
|
+
resolvedCard() {
|
|
795
|
+
const declared = this.effectiveManifest?.nav?.card
|
|
796
|
+
if (!declared || typeof declared !== 'object' || typeof declared.title !== 'string' || declared.title === '') {
|
|
797
|
+
return null
|
|
798
|
+
}
|
|
799
|
+
const text = (value) => (typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : '')
|
|
800
|
+
const card = { title: text(declared.title), text: text(declared.text), linkTag: '', linkAttrs: {}, linkLabel: '', action: '' }
|
|
801
|
+
const link = declared.link
|
|
802
|
+
if (link && typeof link === 'object' && typeof link.label === 'string' && link.label !== '') {
|
|
803
|
+
card.linkLabel = text(link.label)
|
|
804
|
+
if (link.route && this.$router) {
|
|
805
|
+
card.linkTag = 'router-link'
|
|
806
|
+
card.linkAttrs = { to: { name: link.route, ...(link.params && typeof link.params === 'object' ? { params: link.params } : {}) } }
|
|
807
|
+
} else if (typeof link.href === 'string' && link.href !== '') {
|
|
808
|
+
card.linkTag = 'a'
|
|
809
|
+
card.linkAttrs = { href: link.href }
|
|
810
|
+
} else if (typeof link.action === 'string' && link.action !== '') {
|
|
811
|
+
card.linkTag = 'button'
|
|
812
|
+
card.linkAttrs = { type: 'button' }
|
|
813
|
+
card.action = link.action
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return card
|
|
817
|
+
},
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* The help entry (`nav.help`): `{ label, route?, href? }` rendered as
|
|
821
|
+
* a footer entry with a help icon. Null without a label or a target.
|
|
822
|
+
*
|
|
823
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
|
|
824
|
+
* @return {{label: string, to?: object, href?: string}|null}
|
|
825
|
+
*/
|
|
826
|
+
resolvedHelp() {
|
|
827
|
+
const declared = this.effectiveManifest?.nav?.help
|
|
828
|
+
if (!declared || typeof declared !== 'object' || typeof declared.label !== 'string' || declared.label === '') {
|
|
829
|
+
return null
|
|
830
|
+
}
|
|
831
|
+
if (declared.route && this.$router) {
|
|
832
|
+
return { label: this.effectiveTranslate(declared.label), to: { name: declared.route }, href: undefined }
|
|
833
|
+
}
|
|
834
|
+
if (typeof declared.href === 'string' && declared.href !== '') {
|
|
835
|
+
return { label: this.effectiveTranslate(declared.label), to: undefined, href: declared.href }
|
|
836
|
+
}
|
|
837
|
+
return null
|
|
682
838
|
},
|
|
683
839
|
|
|
684
840
|
/**
|
|
@@ -699,20 +855,30 @@ export default {
|
|
|
699
855
|
* 2. `manifest.nav.primaryAction` as app-wide default
|
|
700
856
|
* 3. `null` — no primary-action button renders
|
|
701
857
|
*
|
|
702
|
-
* Page-scoped declarations always win over the nav-root default.
|
|
858
|
+
* Page-scoped declarations always win over the nav-root default. An
|
|
859
|
+
* action declaring `permission` or `visibleIf` is gated like a menu
|
|
860
|
+
* entry.
|
|
703
861
|
*
|
|
862
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
|
|
704
863
|
* @return {object|null}
|
|
705
864
|
*/
|
|
706
865
|
activePrimaryAction() {
|
|
707
866
|
const pages = this.effectiveManifest?.pages ?? []
|
|
708
867
|
const routeName = this.$route?.name
|
|
868
|
+
// The same `permission` / `visibleIf` gate a menu entry has: an
|
|
869
|
+
// action the reader may not take, or that a condition hides, is
|
|
870
|
+
// not drawn. A gated-out page action falls back to the nav one.
|
|
871
|
+
const gated = (action) => (action && this.passesPermission(action) && this.passesVisibleIf(action) ? action : null)
|
|
709
872
|
if (routeName) {
|
|
710
873
|
const page = pages.find((p) => p.id === routeName)
|
|
711
874
|
if (page && page.primaryAction) {
|
|
712
|
-
|
|
875
|
+
const pageAction = gated(page.primaryAction)
|
|
876
|
+
if (pageAction) {
|
|
877
|
+
return pageAction
|
|
878
|
+
}
|
|
713
879
|
}
|
|
714
880
|
}
|
|
715
|
-
return this.effectiveManifest?.nav?.primaryAction ?? null
|
|
881
|
+
return gated(this.effectiveManifest?.nav?.primaryAction ?? null)
|
|
716
882
|
},
|
|
717
883
|
|
|
718
884
|
/**
|
|
@@ -726,6 +892,31 @@ export default {
|
|
|
726
892
|
return this.mdiIconComponent(this.activePrimaryAction) ?? Plus
|
|
727
893
|
},
|
|
728
894
|
|
|
895
|
+
/**
|
|
896
|
+
* The CnActionButtons entry for a primary action that carries an
|
|
897
|
+
* `action` (`$defs/action`: open-form, navigate, api-call, ...):
|
|
898
|
+
* the action with the primary action's `id`, `label` and `icon`
|
|
899
|
+
* folded in and `variant: "primary"`. Null for a plain primary
|
|
900
|
+
* action, which keeps its link or button.
|
|
901
|
+
*
|
|
902
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
|
|
903
|
+
* @return {object|null}
|
|
904
|
+
*/
|
|
905
|
+
primaryDispatchEntry() {
|
|
906
|
+
const action = this.activePrimaryAction
|
|
907
|
+
const inner = action && action.action
|
|
908
|
+
if (!inner || typeof inner !== 'object' || typeof inner.type !== 'string') {
|
|
909
|
+
return null
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
icon: action.icon || 'Plus',
|
|
913
|
+
...inner,
|
|
914
|
+
id: inner.id || action.id || 'primary-action',
|
|
915
|
+
label: action.label,
|
|
916
|
+
variant: 'primary',
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
|
|
729
920
|
/**
|
|
730
921
|
* Link props for the primary action's button: an `href` opens in a
|
|
731
922
|
* new tab, a `route` is a router link. Null when the action is
|
|
@@ -783,17 +974,113 @@ export default {
|
|
|
783
974
|
* Documentation, Features & Roadmap, About.
|
|
784
975
|
*/
|
|
785
976
|
footerItems() {
|
|
786
|
-
|
|
977
|
+
const footer = this.visibleItems.filter((item) => item.section === 'footer')
|
|
978
|
+
const declared = this.declaredFooter
|
|
979
|
+
if (declared === null) {
|
|
980
|
+
return footer
|
|
981
|
+
}
|
|
982
|
+
// A declared footer (`nav.footer`) keeps only the entries it names,
|
|
983
|
+
// in the order it names them.
|
|
984
|
+
return declared
|
|
985
|
+
.map((id) => footer.find((item) => item.id === id))
|
|
986
|
+
.filter(Boolean)
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* The footer the manifest declares (`nav.footer`): an ordered list of
|
|
991
|
+
* menu entry ids plus the reserved ids `help` (the `nav.help` entry)
|
|
992
|
+
* and `settings` (the settings foldout). Null when the manifest
|
|
993
|
+
* declares none, which keeps today's footer: the help entry, then
|
|
994
|
+
* every `section: "footer"` entry, then the foldout.
|
|
995
|
+
*
|
|
996
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
|
|
997
|
+
* @return {Array<string>|null}
|
|
998
|
+
*/
|
|
999
|
+
declaredFooter() {
|
|
1000
|
+
const declared = this.effectiveManifest?.nav?.footer
|
|
1001
|
+
if (!Array.isArray(declared)) {
|
|
1002
|
+
return null
|
|
1003
|
+
}
|
|
1004
|
+
return declared.filter((id) => typeof id === 'string' && id !== '')
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Footer-section entries a declared footer leaves out. They move into
|
|
1009
|
+
* the settings foldout rather than disappearing, so a page such as
|
|
1010
|
+
* Store or Reports stays reachable. Empty without `nav.footer`.
|
|
1011
|
+
*
|
|
1012
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
|
|
1013
|
+
* @return {Array<object>}
|
|
1014
|
+
*/
|
|
1015
|
+
undeclaredFooterItems() {
|
|
1016
|
+
const declared = this.declaredFooter
|
|
1017
|
+
if (declared === null) {
|
|
1018
|
+
return []
|
|
1019
|
+
}
|
|
1020
|
+
return this.visibleItems.filter((item) => item.section === 'footer' && !declared.includes(item.id))
|
|
1021
|
+
},
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Whether the help entry renders: whenever it resolves, unless a
|
|
1025
|
+
* declared footer leaves `help` out.
|
|
1026
|
+
*
|
|
1027
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
|
|
1028
|
+
* @return {boolean}
|
|
1029
|
+
*/
|
|
1030
|
+
showsHelp() {
|
|
1031
|
+
if (!this.resolvedHelp) {
|
|
1032
|
+
return false
|
|
1033
|
+
}
|
|
1034
|
+
return this.declaredFooter === null || this.declaredFooter.includes('help')
|
|
1035
|
+
},
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Whether the help entry goes AFTER the footer entries. Only a declared
|
|
1039
|
+
* footer that names an entry before `help` puts it there; by default
|
|
1040
|
+
* it leads the list.
|
|
1041
|
+
*
|
|
1042
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
|
|
1043
|
+
* @return {boolean}
|
|
1044
|
+
*/
|
|
1045
|
+
helpAfterItems() {
|
|
1046
|
+
const declared = this.declaredFooter
|
|
1047
|
+
if (declared === null) {
|
|
1048
|
+
return false
|
|
1049
|
+
}
|
|
1050
|
+
const help = declared.indexOf('help')
|
|
1051
|
+
return this.footerItems.some((item) => declared.indexOf(item.id) < help)
|
|
1052
|
+
},
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Whether the settings foldout goes ABOVE the footer list: only when a
|
|
1056
|
+
* declared footer names `settings` before every other entry
|
|
1057
|
+
* (`["settings", "help"]`, the board's "Instellingen" then "Hulp en
|
|
1058
|
+
* uitleg"). Done with CSS `order` on the two siblings, so the markup
|
|
1059
|
+
* of both stays as it is.
|
|
1060
|
+
*
|
|
1061
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
|
|
1062
|
+
* @return {boolean}
|
|
1063
|
+
*/
|
|
1064
|
+
settingsFirst() {
|
|
1065
|
+
const declared = this.declaredFooter
|
|
1066
|
+
return declared !== null && declared.indexOf('settings') === 0
|
|
787
1067
|
},
|
|
788
1068
|
|
|
789
1069
|
/**
|
|
790
1070
|
* Items that render INSIDE the NcAppNavigationSettings foldout
|
|
791
1071
|
* (section: "settings"). The foldout is the NC-native gear-icon
|
|
792
1072
|
* button that slides a panel open; these entries are app-level
|
|
793
|
-
* configuration pages (Forms, Pipelines, Automations, …).
|
|
1073
|
+
* configuration pages (Forms, Pipelines, Automations, …). A declared
|
|
1074
|
+
* footer (`nav.footer`) adds the footer entries it leaves out.
|
|
1075
|
+
*
|
|
1076
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-the-navigation-footer-can-be-declared
|
|
1077
|
+
* @return {Array<object>}
|
|
794
1078
|
*/
|
|
795
1079
|
settingsItems() {
|
|
796
|
-
return
|
|
1080
|
+
return [
|
|
1081
|
+
...this.visibleItems.filter((item) => item.section === 'settings'),
|
|
1082
|
+
...this.undeclaredFooterItems,
|
|
1083
|
+
]
|
|
797
1084
|
},
|
|
798
1085
|
|
|
799
1086
|
/**
|
|
@@ -1590,6 +1877,9 @@ export default {
|
|
|
1590
1877
|
* `NcAppNavigationItem`).
|
|
1591
1878
|
*
|
|
1592
1879
|
* - Literal positive integer in `item.count` → return as-is.
|
|
1880
|
+
* - `item.count` is an object (`{ register, schema, filter? }`) →
|
|
1881
|
+
* `cnMenuItemCounts[item.id] ?? null`, the filtered total CnAppRoot
|
|
1882
|
+
* fetched for this entry.
|
|
1593
1883
|
* - `item.count === "auto"` → look up the entry's resolved page;
|
|
1594
1884
|
* when that page is `type: "index"` with a `register`/`schema`
|
|
1595
1885
|
* in its `config`, return
|
|
@@ -1613,6 +1903,11 @@ export default {
|
|
|
1613
1903
|
if (typeof raw === 'number') {
|
|
1614
1904
|
return raw > 0 ? raw : null
|
|
1615
1905
|
}
|
|
1906
|
+
if (raw && typeof raw === 'object') {
|
|
1907
|
+
// A filtered count: CnAppRoot fetched it per entry id.
|
|
1908
|
+
const value = this.cnMenuItemCounts?.[item.id]
|
|
1909
|
+
return (typeof value === 'number' && value > 0) ? value : null
|
|
1910
|
+
}
|
|
1616
1911
|
if (raw !== 'auto') {
|
|
1617
1912
|
return null
|
|
1618
1913
|
}
|
|
@@ -1923,6 +2218,39 @@ export default {
|
|
|
1923
2218
|
*
|
|
1924
2219
|
* @return {void}
|
|
1925
2220
|
*/
|
|
2221
|
+
/**
|
|
2222
|
+
* Re-emit the object an `open-form` primary action created, so the
|
|
2223
|
+
* host can refresh a list or open the record.
|
|
2224
|
+
*
|
|
2225
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-navigation-primary-action-runs-a-page-action
|
|
2226
|
+
* @param {object} created The created object.
|
|
2227
|
+
* @return {void}
|
|
2228
|
+
*/
|
|
2229
|
+
onPrimaryActionCreated(created) {
|
|
2230
|
+
/**
|
|
2231
|
+
* @event primary-action-created Emitted after an `open-form` primary action saves. Payload: the created object.
|
|
2232
|
+
*/
|
|
2233
|
+
this.$emit('primary-action-created', created)
|
|
2234
|
+
},
|
|
2235
|
+
|
|
2236
|
+
/**
|
|
2237
|
+
* A card link that is a button (an `action` id) emits `card-action`
|
|
2238
|
+
* with that id for the host to run; links and anchors navigate on
|
|
2239
|
+
* their own.
|
|
2240
|
+
*
|
|
2241
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-navigation-carries-a-card-and-a-help-entry
|
|
2242
|
+
* @return {void}
|
|
2243
|
+
*/
|
|
2244
|
+
onCardLinkClick() {
|
|
2245
|
+
const card = this.resolvedCard
|
|
2246
|
+
if (card && card.action) {
|
|
2247
|
+
/**
|
|
2248
|
+
* @event card-action Emitted when the nav card's link is an action button. Payload: the action id.
|
|
2249
|
+
*/
|
|
2250
|
+
this.$emit('card-action', card.action)
|
|
2251
|
+
}
|
|
2252
|
+
},
|
|
2253
|
+
|
|
1926
2254
|
onPrimaryActionClick() {
|
|
1927
2255
|
const action = this.activePrimaryAction
|
|
1928
2256
|
if (!action) {
|
|
@@ -1952,6 +2280,76 @@ export default {
|
|
|
1952
2280
|
</script>
|
|
1953
2281
|
|
|
1954
2282
|
<style>
|
|
2283
|
+
/* The emblem beside the app name (`nav.brand.emblem`). A theme sets
|
|
2284
|
+
--nldesign-emblem-url for `emblem: true`, and --cn-nav-emblem-size for both. */
|
|
2285
|
+
.cn-app-nav__brand-emblem {
|
|
2286
|
+
flex: none;
|
|
2287
|
+
height: var(--cn-nav-emblem-size, 34px);
|
|
2288
|
+
width: auto;
|
|
2289
|
+
object-fit: contain;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.cn-app-nav__brand-emblem--theme {
|
|
2293
|
+
display: inline-block;
|
|
2294
|
+
width: var(--cn-nav-emblem-size, 34px);
|
|
2295
|
+
background: var(--nldesign-emblem-url) center / contain no-repeat;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
/* The solid primary action (`solid: true`, or an action that dispatches):
|
|
2299
|
+
one full-width primary button, as the board draws "New case". */
|
|
2300
|
+
.cn-app-nav__primary-action--solid .cn-action-buttons,
|
|
2301
|
+
.cn-app-nav__primary-action--solid .cn-action-buttons .button-vue,
|
|
2302
|
+
.cn-app-nav__primary-action--solid .button-vue {
|
|
2303
|
+
width: 100%;
|
|
2304
|
+
justify-content: center;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
/* The card above the footer entries (`nav.card`). Theme hooks:
|
|
2308
|
+
--cn-nav-card-background and --cn-nav-card-radius. */
|
|
2309
|
+
.cn-app-nav__card {
|
|
2310
|
+
display: flex;
|
|
2311
|
+
flex-direction: column;
|
|
2312
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
2313
|
+
margin: calc(2 * var(--default-grid-baseline));
|
|
2314
|
+
padding: calc(4 * var(--default-grid-baseline));
|
|
2315
|
+
border-radius: var(--cn-nav-card-radius, var(--border-radius-large));
|
|
2316
|
+
background: var(--cn-nav-card-background, var(--color-background-hover));
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
.cn-app-nav__card-title {
|
|
2320
|
+
color: var(--color-main-text);
|
|
2321
|
+
font-weight: 700;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
.cn-app-nav__card-text {
|
|
2325
|
+
color: var(--color-text-maxcontrast);
|
|
2326
|
+
line-height: 1.4;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
.cn-app-nav__card-link {
|
|
2330
|
+
align-self: flex-start;
|
|
2331
|
+
margin: 0;
|
|
2332
|
+
padding: 0;
|
|
2333
|
+
border: 0;
|
|
2334
|
+
background: none;
|
|
2335
|
+
color: var(--color-primary-element);
|
|
2336
|
+
font: inherit;
|
|
2337
|
+
font-weight: 600;
|
|
2338
|
+
text-decoration: none;
|
|
2339
|
+
cursor: pointer;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
.cn-app-nav__card-link:hover,
|
|
2343
|
+
.cn-app-nav__card-link:focus-visible {
|
|
2344
|
+
text-decoration: underline;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
.cn-app-nav__card-link:focus-visible {
|
|
2348
|
+
outline: 2px solid var(--color-primary-element);
|
|
2349
|
+
outline-offset: 2px;
|
|
2350
|
+
border-radius: var(--border-radius-small, 4px);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
1955
2353
|
/* The brand block at the top of the navigation. */
|
|
1956
2354
|
.cn-app-nav__brand {
|
|
1957
2355
|
align-items: center;
|
|
@@ -2008,6 +2406,21 @@ export default {
|
|
|
2008
2406
|
padding: calc(var(--default-grid-baseline, 4px) * 2);
|
|
2009
2407
|
}
|
|
2010
2408
|
|
|
2409
|
+
/*
|
|
2410
|
+
* The primary action never shrinks out of sight. NcAppNavigation renders it in
|
|
2411
|
+
* `.app-navigation__body`, a flex child with `overflow-y: scroll` (so its
|
|
2412
|
+
* minimum height is 0) beside a list of `height: 100%` and the footer. With a
|
|
2413
|
+
* card and footer entries below the list, the column overflows and the body
|
|
2414
|
+
* gives up its share of the shrink: "New case" showed as a 24px sliver of a
|
|
2415
|
+
* 42px button. All three forms carry `.app-navigation-new` (the solid and
|
|
2416
|
+
* link wrappers mirror NcAppNavigationNew's class), and the rule is scoped to
|
|
2417
|
+
* this component's navigation, so a navigation without a primary action lays
|
|
2418
|
+
* out exactly as before.
|
|
2419
|
+
*/
|
|
2420
|
+
.app-navigation[data-testid="cn-nav"] .app-navigation__body:has(> .app-navigation-new) {
|
|
2421
|
+
flex-shrink: 0;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2011
2424
|
/*
|
|
2012
2425
|
* Footer-section items (section: "footer") render in NcAppNavigation's
|
|
2013
2426
|
* #footer slot, outside the scroll container, so they stay visible above
|
|
@@ -2031,6 +2444,20 @@ export default {
|
|
|
2031
2444
|
overflow: visible !important;
|
|
2032
2445
|
}
|
|
2033
2446
|
|
|
2447
|
+
/*
|
|
2448
|
+
* A declared footer that names `settings` first (`nav.footer`) puts the
|
|
2449
|
+
* foldout above the footer list. The two are siblings in NcAppNavigation's
|
|
2450
|
+
* flex column, so `order` moves them without changing either's markup; the
|
|
2451
|
+
* card above keeps order 0 and stays first.
|
|
2452
|
+
*/
|
|
2453
|
+
.cn-app-nav__settings--first {
|
|
2454
|
+
order: 1;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
.cn-app-nav__footer-list--after-settings {
|
|
2458
|
+
order: 2;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2034
2461
|
/*
|
|
2035
2462
|
* Align the settings foldout's items with the main/footer list items,
|
|
2036
2463
|
* whose icons sit at a 16px inset. The foldout's items live in a bare
|