@conduction/nextcloud-vue 2.74.0 → 2.75.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.
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +19 -14
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +2 -2
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +77 -26
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +22 -14
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +23 -4
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js +9 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js.map +1 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js +107 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js +79 -0
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -0
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js +4 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js.map +1 -1
- package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +14 -7
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +32 -0
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +6 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- 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/CnQuickFilterBar/CnQuickFilterBar.vue2.js +18 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +2 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +16 -0
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +2 -2
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/directives/cnSelectAria.js +55 -0
- package/dist/esm/directives/cnSelectAria.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +66 -1
- package/dist/esm/utils/cnFetch.js +17 -2
- package/dist/esm/utils/cnFetch.js.map +1 -1
- package/dist/esm/utils/resolveBrand.js +38 -0
- package/dist/esm/utils/resolveBrand.js.map +1 -0
- 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 +8775 -8327
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +66 -1
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +21 -16
- package/src/components/CnAppRoot/CnAppRoot.vue +53 -0
- package/src/components/CnBannerWidget/CnBannerWidget.vue +23 -4
- package/src/components/CnBrandBar/CnBrandBar.vue +230 -0
- package/src/components/CnEnvironmentBanner/CnEnvironmentBanner.vue +4 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +10 -0
- package/src/components/CnFormPage/CnFormPage.vue +33 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +2 -2
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +20 -4
- package/src/components/CnStatsBlock/CnStatsBlock.vue +18 -2
- package/src/css/form-field.css +1 -1
- package/src/directives/cnSelectAria.js +54 -0
- package/src/schemas/app-manifest-v2.schema.json +6 -1
- package/src/utils/cnFetch.js +16 -1
- package/src/utils/resolveBrand.js +37 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
<component
|
|
218
218
|
:is="resolveFieldRender(field).tag"
|
|
219
219
|
v-if="resolveFieldRender(field)"
|
|
220
|
+
v-cn-select-aria="selectAria(field)"
|
|
220
221
|
v-bind="fieldProps(field)"
|
|
221
222
|
v-on="resolveFieldRender(field).listeners">
|
|
222
223
|
<!-- NcCheckboxRadioSwitch puts its label in the slot -->
|
|
@@ -398,6 +399,7 @@ import CnStepper from '../CnStepper/CnStepper.vue'
|
|
|
398
399
|
import { cnRenderFormField } from '../../composables/cnFormFieldRenderer.js'
|
|
399
400
|
import { draftIndicatorText, draftKey, formDraftMixin, readDraft } from '../../composables/useFormDraft.js'
|
|
400
401
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
402
|
+
import { cnSelectAria } from '../../directives/cnSelectAria.js'
|
|
401
403
|
import { loadCurrentUserProfile } from '../../utils/currentUserProfile.js'
|
|
402
404
|
import { computeAssignments, resolveFieldDefaults } from '../../utils/formAssign.js'
|
|
403
405
|
import { validateFieldValue } from '../../utils/formValidation.js'
|
|
@@ -441,6 +443,8 @@ function resolveParams(url, params) {
|
|
|
441
443
|
export default {
|
|
442
444
|
name: 'CnFormPage',
|
|
443
445
|
|
|
446
|
+
directives: { cnSelectAria },
|
|
447
|
+
|
|
444
448
|
components: {
|
|
445
449
|
CnFormErrorSummary,
|
|
446
450
|
CnFormField,
|
|
@@ -1308,6 +1312,35 @@ export default {
|
|
|
1308
1312
|
return props
|
|
1309
1313
|
},
|
|
1310
1314
|
|
|
1315
|
+
/**
|
|
1316
|
+
* The accessibility attributes of an enum field's NcSelect. NcSelect
|
|
1317
|
+
* forwards no attributes to its input, so these go through
|
|
1318
|
+
* `v-cn-select-aria` onto the combobox input instead of `fieldProps`.
|
|
1319
|
+
* The error element exists for an enum in both looks (NcSelect has no
|
|
1320
|
+
* native error text), so an invalid value lists it before the hint.
|
|
1321
|
+
*
|
|
1322
|
+
* @param {object} field The formField shape.
|
|
1323
|
+
* @return {object|null} The attributes, or null for any other control.
|
|
1324
|
+
*/
|
|
1325
|
+
selectAria(field) {
|
|
1326
|
+
const render = this.resolveFieldRender(field)
|
|
1327
|
+
if (!render || render.kind !== 'enum') {
|
|
1328
|
+
return null
|
|
1329
|
+
}
|
|
1330
|
+
const ids = []
|
|
1331
|
+
if (this.fieldErrors[field.key]) {
|
|
1332
|
+
ids.push(this.errorIdFor(field))
|
|
1333
|
+
}
|
|
1334
|
+
if (field.help) {
|
|
1335
|
+
ids.push(this.helpIdFor(field))
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
'aria-invalid': this.fieldErrors[field.key] ? 'true' : null,
|
|
1339
|
+
'aria-describedby': ids.length > 0 ? ids.join(' ') : null,
|
|
1340
|
+
'aria-required': this.showsBoardHead(field) && this.isFieldRequired(field) ? 'true' : null,
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
|
|
1311
1344
|
/**
|
|
1312
1345
|
* Cancel (board look footer): go to `cancelRoute` when set, and tell the
|
|
1313
1346
|
* host.
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
:activeFilterChips="activeFilterChips"
|
|
83
83
|
:showBuildiqButton="!buildiqInHeader"
|
|
84
84
|
:bulkNoun="boardBulkNoun"
|
|
85
|
-
:bulkHint="bulkHint"
|
|
85
|
+
:bulkHint="bulkHint ? cnTranslate(bulkHint) : bulkHint"
|
|
86
86
|
:pagination="effectivePagination"
|
|
87
87
|
:objectCount="effectiveObjects.length"
|
|
88
88
|
:selectable="selectable"
|
|
@@ -848,7 +848,7 @@
|
|
|
848
848
|
<CnPagination
|
|
849
849
|
v-if="effectivePagination && (effectivePagination.pages > 1 || (isBoardLook && effectivePagination.total > 0))"
|
|
850
850
|
:variant="isBoardLook ? 'board' : ''"
|
|
851
|
-
:footerNote="footerNote"
|
|
851
|
+
:footerNote="footerNote ? cnTranslate(footerNote) : footerNote"
|
|
852
852
|
:currentPage="effectivePagination.page || 1"
|
|
853
853
|
:totalPages="effectivePagination.pages || 1"
|
|
854
854
|
:totalItems="effectivePagination.total || 0"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
:name="entry.tab.icon"
|
|
47
47
|
:size="16"
|
|
48
48
|
class="cn-quick-filter-bar__icon" />
|
|
49
|
-
<span class="cn-quick-filter-bar__label">{{ entry.tab.label }}</span>
|
|
49
|
+
<span class="cn-quick-filter-bar__label">{{ tl(entry.tab.label) }}</span>
|
|
50
50
|
<span
|
|
51
51
|
v-if="countOf(entry) !== null"
|
|
52
52
|
class="cn-quick-filter-bar__count"
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
:name="entry.tab.icon"
|
|
103
103
|
:size="16"
|
|
104
104
|
class="cn-quick-filter-bar__icon" />
|
|
105
|
-
<span class="cn-quick-filter-bar__label">{{ entry.tab.label }}</span>
|
|
105
|
+
<span class="cn-quick-filter-bar__label">{{ tl(entry.tab.label) }}</span>
|
|
106
106
|
<span
|
|
107
107
|
v-if="countOf(entry) !== null"
|
|
108
108
|
class="cn-quick-filter-bar__count"
|
|
@@ -152,6 +152,11 @@ export default {
|
|
|
152
152
|
inject: {
|
|
153
153
|
/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */
|
|
154
154
|
cnLook: { default: 'nextcloud' },
|
|
155
|
+
/**
|
|
156
|
+
* The host's label lookup (CnAppRoot provides it); identity without one, so a
|
|
157
|
+
* label that is not a key renders as written.
|
|
158
|
+
*/
|
|
159
|
+
cnTranslate: { default: () => (key) => key },
|
|
155
160
|
},
|
|
156
161
|
|
|
157
162
|
// NO `model: { prop, event }` OPTION.
|
|
@@ -336,7 +341,7 @@ export default {
|
|
|
336
341
|
overflowLabel() {
|
|
337
342
|
const active = this.activeOverflowEntries
|
|
338
343
|
if (active.length === 1) {
|
|
339
|
-
return active[0].tab.label
|
|
344
|
+
return this.tl(active[0].tab.label)
|
|
340
345
|
}
|
|
341
346
|
if (active.length > 1) {
|
|
342
347
|
return t('nextcloud-vue', '{count} filters', { count: active.length })
|
|
@@ -370,7 +375,7 @@ export default {
|
|
|
370
375
|
.map((tab, index) => {
|
|
371
376
|
const count = this.countOf({ tab, index })
|
|
372
377
|
return {
|
|
373
|
-
label: count === null ? tab.label : `${tab.label} (${count})`,
|
|
378
|
+
label: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,
|
|
374
379
|
icon: tab.icon,
|
|
375
380
|
index,
|
|
376
381
|
_empty: this.isEmptyFilter(tab),
|
|
@@ -394,6 +399,17 @@ export default {
|
|
|
394
399
|
},
|
|
395
400
|
|
|
396
401
|
methods: {
|
|
402
|
+
/**
|
|
403
|
+
* A manifest string through the host's label lookup (`cnTranslate`), so a key
|
|
404
|
+
* such as "Mine" reads in the user's language.
|
|
405
|
+
*
|
|
406
|
+
* @param {string} text The text as written in the manifest.
|
|
407
|
+
* @return {string} The translated text.
|
|
408
|
+
*/
|
|
409
|
+
tl(text) {
|
|
410
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
411
|
+
},
|
|
412
|
+
|
|
397
413
|
t,
|
|
398
414
|
|
|
399
415
|
/**
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
</slot>
|
|
40
40
|
</span>
|
|
41
41
|
<h4 class="cn-kpi-card__title" :title="title || undefined">
|
|
42
|
-
{{ title
|
|
42
|
+
{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}
|
|
43
43
|
</h4>
|
|
44
44
|
</div>
|
|
45
45
|
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
<!-- @binding {string} formatted The default localized count string. -->
|
|
55
55
|
<slot name="value" :count="count" :formatted="formattedCount">{{ formattedCount }}</slot>
|
|
56
56
|
</span>
|
|
57
|
-
<span class="cn-kpi-card__label cn-stats-block__count-label">{{ countLabel }}</span>
|
|
57
|
+
<span class="cn-kpi-card__label cn-stats-block__count-label">{{ countLabel ? tl(countLabel) : countLabel }}</span>
|
|
58
58
|
</div>
|
|
59
59
|
<div v-else-if="loading" class="cn-stats-block__loading">
|
|
60
60
|
<NcLoadingIcon :size="16" />
|
|
@@ -164,6 +164,11 @@ export default {
|
|
|
164
164
|
* @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile
|
|
165
165
|
*/
|
|
166
166
|
cnLook: { default: 'nextcloud' },
|
|
167
|
+
/**
|
|
168
|
+
* The host's label lookup (CnAppRoot provides it); identity without one, so a
|
|
169
|
+
* label that is not a key renders as written.
|
|
170
|
+
*/
|
|
171
|
+
cnTranslate: { default: () => (key) => key },
|
|
167
172
|
},
|
|
168
173
|
|
|
169
174
|
props: {
|
|
@@ -453,6 +458,17 @@ export default {
|
|
|
453
458
|
},
|
|
454
459
|
|
|
455
460
|
methods: {
|
|
461
|
+
/**
|
|
462
|
+
* A manifest string through the host's label lookup (`cnTranslate`), so a key
|
|
463
|
+
* such as "Mine" reads in the user's language.
|
|
464
|
+
*
|
|
465
|
+
* @param {string} text The text as written in the manifest.
|
|
466
|
+
* @return {string} The translated text.
|
|
467
|
+
*/
|
|
468
|
+
tl(text) {
|
|
469
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
470
|
+
},
|
|
471
|
+
|
|
456
472
|
formatBreakdownLabel(key) {
|
|
457
473
|
return key.charAt(0).toUpperCase() + key.slice(1) + ':'
|
|
458
474
|
},
|
package/src/css/form-field.css
CHANGED
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/* An invalid field: 2px error border on the control, 4px edge on the group. */
|
|
91
|
-
.cn-look-board .cn-form-field--invalid input,
|
|
91
|
+
.cn-look-board .cn-form-field--invalid input:not([type='checkbox']):not([type='radio']),
|
|
92
92
|
.cn-look-board .cn-form-field--invalid textarea,
|
|
93
93
|
.cn-look-board .cn-form-field--invalid .select {
|
|
94
94
|
border: var(--cn-field-error-border) solid var(--color-error);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* `v-cn-select-aria`: puts `aria-invalid`, `aria-describedby` and
|
|
6
|
+
* `aria-required` on the search input of an NcSelect.
|
|
7
|
+
*
|
|
8
|
+
* NcSelect forwards no attributes to its input: an attribute bound on it lands
|
|
9
|
+
* on the wrapping `.v-select` div, where a screen reader never reads it. The
|
|
10
|
+
* input is the combobox, so the attributes belong there. This directive sets
|
|
11
|
+
* them on that input after every render, and removes the ones that go away
|
|
12
|
+
* (a field that becomes valid loses `aria-invalid`).
|
|
13
|
+
*
|
|
14
|
+
* Usage: `<NcSelect v-cn-select-aria="{ 'aria-invalid': 'true', 'aria-describedby': 'err-id' }" />`
|
|
15
|
+
* A `null` or `undefined` value does nothing, so a caller can bind it
|
|
16
|
+
* unconditionally.
|
|
17
|
+
*
|
|
18
|
+
* @spec openspec/changes/screens-form-parity/tasks.md
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const ARIA_ATTRIBUTES = ['aria-invalid', 'aria-describedby', 'aria-required']
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply the bound attributes to the combobox input inside the element.
|
|
25
|
+
*
|
|
26
|
+
* @param {HTMLElement} el The element the directive sits on (the select root).
|
|
27
|
+
* @param {{value: object|null|undefined}} binding The directive binding.
|
|
28
|
+
* @return {void}
|
|
29
|
+
*/
|
|
30
|
+
function apply(el, binding) {
|
|
31
|
+
const attributes = binding.value
|
|
32
|
+
if (!attributes || typeof el.querySelector !== 'function') {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
const input = el.querySelector('input.vs__search') || el.querySelector('input')
|
|
36
|
+
if (!input) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
for (const name of ARIA_ATTRIBUTES) {
|
|
40
|
+
const value = attributes[name]
|
|
41
|
+
if (value === undefined || value === null || value === false) {
|
|
42
|
+
input.removeAttribute(name)
|
|
43
|
+
} else {
|
|
44
|
+
input.setAttribute(name, String(value))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const cnSelectAria = {
|
|
50
|
+
mounted: apply,
|
|
51
|
+
updated: apply,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default cnSelectAria
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
|
|
4
4
|
"title": "Conduction App Manifest v2",
|
|
5
5
|
"description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.73.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -326,6 +326,11 @@
|
|
|
326
326
|
"alt": {
|
|
327
327
|
"type": "string",
|
|
328
328
|
"description": "i18n key / alternative text for the logo. Used only when no `name` is set."
|
|
329
|
+
},
|
|
330
|
+
"placement": {
|
|
331
|
+
"type": "string",
|
|
332
|
+
"enum": ["header", "nav"],
|
|
333
|
+
"description": "Where the brand block is drawn (screens-brand-block-top-bar). Under `look: \"board\"` the default is `header`: a 237px block at the start of Nextcloud's own header (#header), before the app menu, drawn by CnAppRoot; an app without CnAppNav has it too. Nextcloud offers no extension point there, so when `#header` is absent the block falls back to the navigation. `nav` keeps the block at the top of the app navigation. Ignored without the board look, where the navigation draws the block."
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
},
|
package/src/utils/cnFetch.js
CHANGED
|
@@ -59,11 +59,26 @@ export function configureCnFetch(config = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
transport = {
|
|
61
61
|
host,
|
|
62
|
-
baseUrl: typeof config.baseUrl === 'string' ? config.baseUrl
|
|
62
|
+
baseUrl: typeof config.baseUrl === 'string' ? stripTrailingSlashes(config.baseUrl) : '',
|
|
63
63
|
credential: config.credential || null,
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Drop trailing `/` characters in one linear pass (a `/\/+$/` pattern
|
|
69
|
+
* backtracks quadratically on a long run of slashes).
|
|
70
|
+
*
|
|
71
|
+
* @param {string} value The string to trim.
|
|
72
|
+
* @return {string} The string without trailing slashes.
|
|
73
|
+
*/
|
|
74
|
+
export function stripTrailingSlashes(value) {
|
|
75
|
+
let end = value.length
|
|
76
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
77
|
+
end--
|
|
78
|
+
}
|
|
79
|
+
return value.slice(0, end)
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
/**
|
|
68
83
|
* The bearer credential, resolved.
|
|
69
84
|
*
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Reads a declared brand (`nav.brand`, or the `brand` prop of CnAppNav and
|
|
6
|
+
* CnAppRoot) into the one shape the brand block and the brand bar draw.
|
|
7
|
+
*
|
|
8
|
+
* @spec openspec/changes/screens-brand-block-top-bar/tasks.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a declared brand.
|
|
13
|
+
*
|
|
14
|
+
* @param {object|null|undefined} declared The declared brand.
|
|
15
|
+
* @param {(text: string) => string} translate Label lookup for name, caption and alt.
|
|
16
|
+
* @return {{logo: string, emblem: (string|boolean), name: string, caption: string, alt: string, placement: string}|null}
|
|
17
|
+
* The brand, or null when nothing is declared that can be drawn.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveBrand(declared, translate) {
|
|
20
|
+
if (!declared || typeof declared !== 'object') {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
const text = (value) => (typeof value === 'string' && value !== '' ? translate(value) : '')
|
|
24
|
+
const brand = {
|
|
25
|
+
logo: typeof declared.logo === 'string' ? declared.logo : '',
|
|
26
|
+
// A URL, or `true` for the theme's emblem (--nldesign-emblem-url).
|
|
27
|
+
emblem: declared.emblem === true ? true : (typeof declared.emblem === 'string' ? declared.emblem : ''),
|
|
28
|
+
name: text(declared.name),
|
|
29
|
+
caption: text(declared.caption),
|
|
30
|
+
alt: text(declared.alt),
|
|
31
|
+
// '' means "not declared": the caller decides the default from the look.
|
|
32
|
+
placement: declared.placement === 'nav' || declared.placement === 'header' ? declared.placement : '',
|
|
33
|
+
}
|
|
34
|
+
return (brand.logo || brand.emblem || brand.name || brand.caption) ? brand : null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default resolveBrand
|