@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
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
]"
|
|
26
26
|
v-bind="linkAttrs">
|
|
27
27
|
<div
|
|
28
|
-
v-if="resolvedIcon"
|
|
28
|
+
v-if="resolvedIcon && !isStacked"
|
|
29
29
|
class="cn-kpi-card__icon cn-stat-widget__icon"
|
|
30
30
|
:style="iconCircleStyle">
|
|
31
31
|
<CnWidgetIcon :name="resolvedIcon" :size="24" />
|
|
@@ -94,10 +94,23 @@
|
|
|
94
94
|
{{ formattedTrend }}
|
|
95
95
|
</span>
|
|
96
96
|
</template>
|
|
97
|
-
<span
|
|
97
|
+
<span
|
|
98
|
+
v-if="!isStacked && !displayLoading && !displayError && resolvedCaption"
|
|
99
|
+
class="cn-kpi-card__label cn-stat-widget__caption"
|
|
100
|
+
:class="captionVariantClass"
|
|
101
|
+
data-testid="cn-stat-widget-caption">
|
|
98
102
|
{{ resolvedCaption }}
|
|
99
103
|
</span>
|
|
100
104
|
</div>
|
|
105
|
+
<!-- Stacked (`content.layout: "stacked"`): the caption is a line of
|
|
106
|
+
its own under the number, as the board's "3 new this week". -->
|
|
107
|
+
<span
|
|
108
|
+
v-if="isStacked && !displayLoading && !displayError && resolvedCaption"
|
|
109
|
+
class="cn-kpi-card__label cn-stat-widget__caption cn-kpi-card__caption-line"
|
|
110
|
+
:class="captionVariantClass"
|
|
111
|
+
data-testid="cn-stat-widget-caption">
|
|
112
|
+
{{ resolvedCaption }}
|
|
113
|
+
</span>
|
|
101
114
|
</div>
|
|
102
115
|
</component>
|
|
103
116
|
</template>
|
|
@@ -461,7 +474,7 @@ export default {
|
|
|
461
474
|
* counted between LOCAL CALENDAR DAYS, not as elapsed milliseconds, so
|
|
462
475
|
* the time of day on a deadline never moves the answer.
|
|
463
476
|
*
|
|
464
|
-
* @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string}>}}
|
|
477
|
+
* @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, captionVariant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), captionVariantWhen?: Array<{op: string, value: unknown, variant: string}>, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string, caption?: string, captionVariant?: string}>}}
|
|
465
478
|
*/
|
|
466
479
|
content: {
|
|
467
480
|
type: Object,
|
|
@@ -605,7 +618,10 @@ export default {
|
|
|
605
618
|
* @return {string}
|
|
606
619
|
*/
|
|
607
620
|
resolvedCaption() {
|
|
608
|
-
const
|
|
621
|
+
const override = this.activeOverride
|
|
622
|
+
const caption = (override && typeof override.caption === 'string' && override.caption !== '')
|
|
623
|
+
? override.caption
|
|
624
|
+
: this.content.caption
|
|
609
625
|
if (!caption) {
|
|
610
626
|
return ''
|
|
611
627
|
}
|
|
@@ -615,11 +631,58 @@ export default {
|
|
|
615
631
|
}
|
|
616
632
|
const payload = this.endpointMode ? this.epData : null
|
|
617
633
|
return translated.replace(/\{([A-Za-z0-9_.]+)\}/g, (whole, path) => {
|
|
634
|
+
// `{value}` is the tile's own number, so a caption can read
|
|
635
|
+
// "{value} due today" on a register-counted tile that has no
|
|
636
|
+
// payload to read fields from.
|
|
637
|
+
if (path === 'value' && (payload === null || getByPath(payload, path) === undefined)) {
|
|
638
|
+
return this.formattedValue === undefined || this.formattedValue === null ? '' : String(this.formattedValue)
|
|
639
|
+
}
|
|
618
640
|
const v = getByPath(payload, path)
|
|
619
641
|
return (v === undefined || v === null) ? '' : String(v)
|
|
620
642
|
}).replace(/\s{2,}/g, ' ').trim()
|
|
621
643
|
},
|
|
622
644
|
|
|
645
|
+
/**
|
|
646
|
+
* The first `content.captionVariantWhen` rule the tile's value
|
|
647
|
+
* matches (`[{ op, value, variant }]`, the `variantWhen` shape), or
|
|
648
|
+
* null. Colours the caption on the value without recolouring the
|
|
649
|
+
* tile: "1 due today" turns red while the number stays ink.
|
|
650
|
+
*
|
|
651
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stat-tile-colours-its-caption-by-rule
|
|
652
|
+
* @return {object|null}
|
|
653
|
+
*/
|
|
654
|
+
activeCaptionRule() {
|
|
655
|
+
const rules = this.content.captionVariantWhen
|
|
656
|
+
if (!Array.isArray(rules) || rules.length === 0) {
|
|
657
|
+
return null
|
|
658
|
+
}
|
|
659
|
+
const current = this.displayValue
|
|
660
|
+
if (current === null || current === undefined) {
|
|
661
|
+
return null
|
|
662
|
+
}
|
|
663
|
+
return rules.find((r) => r && this.matchesRule(current, r)) || null
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* The caption's colour class: the matching override's
|
|
668
|
+
* `captionVariant`, else `content.captionVariant`, as
|
|
669
|
+
* `cn-kpi-card__label--<variant>`. '' for `default` or an unknown
|
|
670
|
+
* variant, so an uncoloured caption is what it always was.
|
|
671
|
+
*
|
|
672
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stat-tile-colours-its-caption-by-rule
|
|
673
|
+
* @return {string}
|
|
674
|
+
*/
|
|
675
|
+
captionVariantClass() {
|
|
676
|
+
const override = this.activeOverride
|
|
677
|
+
const variant = (override && override.captionVariant)
|
|
678
|
+
|| (this.activeCaptionRule && this.activeCaptionRule.variant)
|
|
679
|
+
|| this.content.captionVariant
|
|
680
|
+
if (!variant || variant === 'default' || !Object.hasOwn(VARIANT_COLORS, variant)) {
|
|
681
|
+
return ''
|
|
682
|
+
}
|
|
683
|
+
return `cn-kpi-card__label--${variant === 'danger' ? 'error' : variant}`
|
|
684
|
+
},
|
|
685
|
+
|
|
623
686
|
/**
|
|
624
687
|
* The unwrapped detail-page object context for token resolution, or null
|
|
625
688
|
* on surfaces (dashboards) that don't provide one.
|
|
@@ -1188,12 +1251,31 @@ export default {
|
|
|
1188
1251
|
/**
|
|
1189
1252
|
* Card orientation. Horizontal (icon beside the number) is the
|
|
1190
1253
|
* canonical KPI card; `content.layout: 'vertical'` stacks the icon
|
|
1191
|
-
* above a centred number for a tile taller than it is wide
|
|
1254
|
+
* above a centred number for a tile taller than it is wide;
|
|
1255
|
+
* `content.layout: 'stacked'` is the board look (see `isStacked`).
|
|
1192
1256
|
*
|
|
1193
|
-
* @
|
|
1257
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-stat-tile-can-take-the-stacked-board-look
|
|
1258
|
+
* @return {'horizontal'|'vertical'|'stacked'}
|
|
1194
1259
|
*/
|
|
1195
1260
|
cardLayout() {
|
|
1196
|
-
|
|
1261
|
+
const layout = (this.content || {}).layout
|
|
1262
|
+
if (layout === 'vertical' || layout === 'stacked') {
|
|
1263
|
+
return layout
|
|
1264
|
+
}
|
|
1265
|
+
return 'horizontal'
|
|
1266
|
+
},
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Whether the tile takes the stacked board look (`content.layout:
|
|
1270
|
+
* "stacked"`): a plain muted label, the number at 34px in the text
|
|
1271
|
+
* colour, the caption on a line of its own, and no icon circle. Off by
|
|
1272
|
+
* default.
|
|
1273
|
+
*
|
|
1274
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-stat-tile-can-take-the-stacked-board-look
|
|
1275
|
+
* @return {boolean}
|
|
1276
|
+
*/
|
|
1277
|
+
isStacked() {
|
|
1278
|
+
return this.cardLayout === 'stacked'
|
|
1197
1279
|
},
|
|
1198
1280
|
|
|
1199
1281
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<CnTabs
|
|
9
9
|
:aria-label="stripLabel"
|
|
10
10
|
:moreLabel="moreLabel"
|
|
11
|
+
:variant="stripVariant"
|
|
11
12
|
class="cn-tabs-widget__tabs"
|
|
12
13
|
@update:activeIndex="onTabChange">
|
|
13
14
|
<!-- One Actions menu for the whole widget, bound to whichever child
|
|
@@ -246,9 +247,10 @@ export default {
|
|
|
246
247
|
*
|
|
247
248
|
* `maxVisibleTabs` caps the strip: later tabs go under "More".
|
|
248
249
|
* `hideEmpty: true` moves a tab whose count is 0 there too.
|
|
249
|
-
* `moreLabel` names that menu.
|
|
250
|
+
* `moreLabel` names that menu. `variant: "segmented"` draws the strip
|
|
251
|
+
* as a pill switch (CnTabs' segmented variant); `line` is the default.
|
|
250
252
|
*
|
|
251
|
-
* @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string }}
|
|
253
|
+
* @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
|
|
252
254
|
*/
|
|
253
255
|
content: {
|
|
254
256
|
type: Object,
|
|
@@ -464,6 +466,17 @@ export default {
|
|
|
464
466
|
return this.activeTab?.label || ''
|
|
465
467
|
},
|
|
466
468
|
|
|
469
|
+
/**
|
|
470
|
+
* The strip's look: `segmented` when the content asks for it, else
|
|
471
|
+
* CnTabs' default `line`. An unknown value falls back to `line`.
|
|
472
|
+
*
|
|
473
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-tabs-widget-renders-a-segmented-strip
|
|
474
|
+
* @return {('line'|'segmented')} The CnTabs variant.
|
|
475
|
+
*/
|
|
476
|
+
stripVariant() {
|
|
477
|
+
return this.content?.variant === 'segmented' ? 'segmented' : 'line'
|
|
478
|
+
},
|
|
479
|
+
|
|
467
480
|
/**
|
|
468
481
|
* Accessible name for the tab strip.
|
|
469
482
|
*
|
|
@@ -163,3 +163,7 @@ Request a feature / Report a bug / Documentation render on every widget; the sha
|
|
|
163
163
|
## High contrast
|
|
164
164
|
|
|
165
165
|
`high-contrast` declares that this widget wants a high contrast treatment. The flag reaches the theme; the component picks no colour. See [the reference page](../../../docs/components/cn-widget-wrapper.md#high-contrast).
|
|
166
|
+
|
|
167
|
+
### A text link in the header (`headerLink`)
|
|
168
|
+
|
|
169
|
+
`headerLink` (`{ label, route?, params?, query?, href? }`) draws one text link in the header before the overflow menu, such as "All deadlines".
|
|
@@ -69,6 +69,17 @@
|
|
|
69
69
|
It used to gate both, so a caller who switched the menu off
|
|
70
70
|
also lost the Save button for an inline edit, silently. -->
|
|
71
71
|
<div v-if="actionsAreVisible()" class="cn-widget-wrapper__actions">
|
|
72
|
+
<!-- One text link in the header (`headerLink`): "All deadlines",
|
|
73
|
+
"To the board". A route is a router link, an href a plain
|
|
74
|
+
anchor. Sits before the slot and the overflow menu. -->
|
|
75
|
+
<component
|
|
76
|
+
:is="headerLinkTag"
|
|
77
|
+
v-if="headerLinkTag"
|
|
78
|
+
class="cn-widget-wrapper__header-link"
|
|
79
|
+
data-testid="cn-widget-wrapper-header-link"
|
|
80
|
+
v-bind="headerLinkAttrs">
|
|
81
|
+
{{ headerLinkLabel }}
|
|
82
|
+
</component>
|
|
72
83
|
<!-- @slot actions Custom action buttons rendered before the
|
|
73
84
|
built-in overflow menu. -->
|
|
74
85
|
<slot name="actions" />
|
|
@@ -351,6 +362,22 @@ export default {
|
|
|
351
362
|
default: () => [],
|
|
352
363
|
},
|
|
353
364
|
|
|
365
|
+
/**
|
|
366
|
+
* One text link in the header's actions group, before the overflow
|
|
367
|
+
* menu: `{ label, route?, params?, query?, href? }`. A `route` is a
|
|
368
|
+
* named router link (needs a router), an `href` a plain anchor. The
|
|
369
|
+
* label goes through the host translate function. Null (the
|
|
370
|
+
* default) draws nothing. Manifest: `headerLink` on the widget
|
|
371
|
+
* definition or its layout entry.
|
|
372
|
+
*
|
|
373
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
|
|
374
|
+
* @type {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
|
|
375
|
+
*/
|
|
376
|
+
headerLink: {
|
|
377
|
+
type: Object,
|
|
378
|
+
default: null,
|
|
379
|
+
},
|
|
380
|
+
|
|
354
381
|
/**
|
|
355
382
|
* Whether the header's overflow action menu (Refresh / Documentation /
|
|
356
383
|
* Request-a-feature + any `#action-items`) renders. Shown by default;
|
|
@@ -634,6 +661,62 @@ export default {
|
|
|
634
661
|
return this.translate ?? this.cnTranslate
|
|
635
662
|
},
|
|
636
663
|
|
|
664
|
+
/**
|
|
665
|
+
* The header link's tag: `router-link` for a route (when a router is
|
|
666
|
+
* mounted), `a` for an href, '' when there is no usable link.
|
|
667
|
+
*
|
|
668
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
|
|
669
|
+
* @return {string}
|
|
670
|
+
*/
|
|
671
|
+
headerLinkTag() {
|
|
672
|
+
const link = this.headerLink
|
|
673
|
+
if (!link || typeof link.label !== 'string' || link.label === '') {
|
|
674
|
+
return ''
|
|
675
|
+
}
|
|
676
|
+
if (link.route && this.$router) {
|
|
677
|
+
return 'router-link'
|
|
678
|
+
}
|
|
679
|
+
if (typeof link.href === 'string' && link.href !== '') {
|
|
680
|
+
return 'a'
|
|
681
|
+
}
|
|
682
|
+
return ''
|
|
683
|
+
},
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* The header link's attributes for its tag.
|
|
687
|
+
*
|
|
688
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
|
|
689
|
+
* @return {object}
|
|
690
|
+
*/
|
|
691
|
+
headerLinkAttrs() {
|
|
692
|
+
const link = this.headerLink || {}
|
|
693
|
+
if (this.headerLinkTag === 'router-link') {
|
|
694
|
+
const to = { name: link.route }
|
|
695
|
+
if (link.params && typeof link.params === 'object') {
|
|
696
|
+
to.params = link.params
|
|
697
|
+
}
|
|
698
|
+
if (link.query && typeof link.query === 'object') {
|
|
699
|
+
to.query = link.query
|
|
700
|
+
}
|
|
701
|
+
return { to }
|
|
702
|
+
}
|
|
703
|
+
if (this.headerLinkTag === 'a') {
|
|
704
|
+
return { href: link.href }
|
|
705
|
+
}
|
|
706
|
+
return {}
|
|
707
|
+
},
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* The header link's text, through the host translate function.
|
|
711
|
+
*
|
|
712
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
|
|
713
|
+
* @return {string}
|
|
714
|
+
*/
|
|
715
|
+
headerLinkLabel() {
|
|
716
|
+
const label = this.headerLink && this.headerLink.label
|
|
717
|
+
return label ? this.effectiveTranslate(label) : ''
|
|
718
|
+
},
|
|
719
|
+
|
|
637
720
|
/**
|
|
638
721
|
* The RENDERED widget title — `displayTitle` run through the host
|
|
639
722
|
* translate function. Kept separate from `displayTitle` on purpose:
|
|
@@ -877,7 +960,7 @@ export default {
|
|
|
877
960
|
* @return {boolean} True when the group would hold a visible control.
|
|
878
961
|
*/
|
|
879
962
|
actionsAreVisible() {
|
|
880
|
-
return this.showActions || this.actionsSlotRenders()
|
|
963
|
+
return this.showActions || this.actionsSlotRenders() || Boolean(this.headerLinkTag)
|
|
881
964
|
},
|
|
882
965
|
|
|
883
966
|
/**
|
|
@@ -1127,6 +1210,29 @@ export default {
|
|
|
1127
1210
|
flex-shrink: 0;
|
|
1128
1211
|
}
|
|
1129
1212
|
|
|
1213
|
+
/* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size
|
|
1214
|
+
and --cn-widget-header-link-weight. */
|
|
1215
|
+
.cn-widget-wrapper__header-link {
|
|
1216
|
+
align-self: center;
|
|
1217
|
+
padding: 0 calc(1 * var(--default-grid-baseline));
|
|
1218
|
+
color: var(--color-primary-element);
|
|
1219
|
+
font-size: var(--cn-widget-header-link-size, 14px);
|
|
1220
|
+
font-weight: var(--cn-widget-header-link-weight, 600);
|
|
1221
|
+
text-decoration: none;
|
|
1222
|
+
white-space: nowrap;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.cn-widget-wrapper__header-link:hover,
|
|
1226
|
+
.cn-widget-wrapper__header-link:focus-visible {
|
|
1227
|
+
text-decoration: underline;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.cn-widget-wrapper__header-link:focus-visible {
|
|
1231
|
+
outline: 2px solid var(--color-primary-element);
|
|
1232
|
+
outline-offset: 2px;
|
|
1233
|
+
border-radius: var(--border-radius-small, 4px);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1130
1236
|
/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on
|
|
1131
1237
|
the header from titleIconStyle, and defaults to the theme colour if a
|
|
1132
1238
|
wrapper is ever rendered without it. Material-design-icon components paint
|
package/src/css/detail-page.css
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
position: relative;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/* The breadcrumb line above the header (`config.breadcrumb`). */
|
|
12
|
+
.cn-detail-page__breadcrumbs {
|
|
13
|
+
margin-bottom: calc(2 * var(--default-grid-baseline));
|
|
14
|
+
color: var(--color-text-maxcontrast);
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
/* Header */
|
|
12
18
|
.cn-detail-page__header {
|
|
13
19
|
display: flex;
|
|
@@ -33,6 +39,28 @@
|
|
|
33
39
|
min-width: 0;
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
/* A widget in the header (`config.headerWidget`) takes a row of its own under
|
|
43
|
+
the title and the actions. */
|
|
44
|
+
.cn-detail-page__header--with-widget {
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
row-gap: var(--cn-detail-header-row-gap, 22px);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.cn-detail-page__header-widget {
|
|
50
|
+
flex: 1 0 100%;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* The header as a card (`config.headerCard`): the board's case header, a
|
|
55
|
+
bordered box on the surface colour. The 56px inset that clears the
|
|
56
|
+
navigation toggle is not needed inside a card that starts below it. */
|
|
57
|
+
.cn-detail-page__header--card {
|
|
58
|
+
padding: var(--cn-detail-header-card-padding, 22px 24px);
|
|
59
|
+
border: 1px solid var(--color-border);
|
|
60
|
+
border-radius: var(--cn-detail-header-card-radius, 12px);
|
|
61
|
+
background: var(--color-main-background);
|
|
62
|
+
}
|
|
63
|
+
|
|
36
64
|
.cn-detail-page__header-left {
|
|
37
65
|
display: flex;
|
|
38
66
|
align-items: center;
|
|
@@ -175,7 +203,7 @@
|
|
|
175
203
|
|
|
176
204
|
.cn-detail-page__stats-table thead th {
|
|
177
205
|
padding: calc(1.5 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline));
|
|
178
|
-
text-align:
|
|
206
|
+
text-align: start;
|
|
179
207
|
font-weight: 600;
|
|
180
208
|
font-size: 0.9em;
|
|
181
209
|
color: var(--color-text-maxcontrast);
|
|
@@ -200,7 +228,7 @@
|
|
|
200
228
|
}
|
|
201
229
|
|
|
202
230
|
.cn-detail-page__stats-cell--indented {
|
|
203
|
-
padding-
|
|
231
|
+
padding-inline-start: calc(5 * var(--default-grid-baseline)) !important;
|
|
204
232
|
}
|
|
205
233
|
|
|
206
234
|
.cn-detail-page__stats-cell--center {
|
|
@@ -208,7 +236,7 @@
|
|
|
208
236
|
}
|
|
209
237
|
|
|
210
238
|
.cn-detail-page__stats-cell--right {
|
|
211
|
-
text-align:
|
|
239
|
+
text-align: end;
|
|
212
240
|
}
|
|
213
241
|
|
|
214
242
|
/* Body & content */
|
|
@@ -342,7 +370,7 @@
|
|
|
342
370
|
|
|
343
371
|
.cn-detail-page__sidebar-toggle {
|
|
344
372
|
position: fixed;
|
|
345
|
-
|
|
373
|
+
inset-inline-end: calc(5 * var(--default-grid-baseline));
|
|
346
374
|
top: 80px;
|
|
347
375
|
z-index: 10;
|
|
348
376
|
}
|
|
@@ -453,3 +481,8 @@
|
|
|
453
481
|
font-size: 0.9em;
|
|
454
482
|
white-space: nowrap;
|
|
455
483
|
}
|
|
484
|
+
|
|
485
|
+
/* A page view the switch shows with nothing to draw (view-switch-containers). */
|
|
486
|
+
.cn-detail-page .cn-page-view-region--empty {
|
|
487
|
+
padding: 40px 20px;
|
|
488
|
+
}
|
package/src/css/kpi-card.css
CHANGED
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
padding: var(--cn-kpi-padding-flat, 8px);
|
|
134
134
|
box-sizing: border-box;
|
|
135
135
|
overflow: hidden;
|
|
136
|
-
text-align:
|
|
136
|
+
text-align: start;
|
|
137
137
|
text-decoration: none;
|
|
138
138
|
color: inherit;
|
|
139
139
|
background: transparent;
|
|
@@ -248,6 +248,29 @@
|
|
|
248
248
|
white-space: nowrap;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/* A caption coloured by a rule (`captionVariant` / `overrides[].captionVariant`):
|
|
252
|
+
"1 due today" in the error colour. Foreground tokens, which stay readable in
|
|
253
|
+
dark mode. */
|
|
254
|
+
.cn-kpi-card__label--primary {
|
|
255
|
+
color: var(--color-primary-element);
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.cn-kpi-card__label--success {
|
|
260
|
+
color: var(--color-text-success, var(--color-success-text));
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.cn-kpi-card__label--warning {
|
|
265
|
+
color: var(--color-element-warning, var(--color-warning-text));
|
|
266
|
+
font-weight: 600;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.cn-kpi-card__label--error {
|
|
270
|
+
color: var(--color-text-error, var(--color-error-text));
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
}
|
|
273
|
+
|
|
251
274
|
/* Semantic variants. Each re-points the ONE accent token, so the icon tint
|
|
252
275
|
and the number move together by construction — there is no second place to
|
|
253
276
|
forget. The inline KPI below shares the same modifiers for the same reason:
|
|
@@ -344,6 +367,53 @@
|
|
|
344
367
|
font-size: var(--cn-kpi-value-size-stacked, 2rem);
|
|
345
368
|
}
|
|
346
369
|
|
|
370
|
+
/* Stacked: the board's tile (`content.layout: 'stacked'`). A plain muted
|
|
371
|
+
label, the number at 34px/700 in the text colour, and the caption on a line
|
|
372
|
+
of its own; CnStatWidget draws no icon circle in this layout. The padding
|
|
373
|
+
adds up with a dashboard card-fit cell's 8px 14px to the board's 18px 20px.
|
|
374
|
+
Every value is a token so a theme can move it. */
|
|
375
|
+
.cn-kpi-card--stacked {
|
|
376
|
+
flex-direction: column;
|
|
377
|
+
align-items: stretch;
|
|
378
|
+
justify-content: center;
|
|
379
|
+
gap: 0;
|
|
380
|
+
padding: var(--cn-kpi-stacked-padding, 10px 6px);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.cn-kpi-card--stacked .cn-kpi-card__body {
|
|
384
|
+
gap: var(--cn-kpi-stacked-gap, 6px);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.cn-kpi-card--stacked .cn-kpi-card__title {
|
|
388
|
+
font-size: var(--cn-kpi-stacked-title-size, 14px);
|
|
389
|
+
font-weight: var(--cn-kpi-stacked-title-weight, 400);
|
|
390
|
+
color: var(--color-text-maxcontrast);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.cn-kpi-card--stacked .cn-kpi-card__value {
|
|
394
|
+
font-size: var(--cn-kpi-stacked-value-size, 34px);
|
|
395
|
+
font-weight: var(--cn-kpi-value-weight, 700);
|
|
396
|
+
line-height: 1;
|
|
397
|
+
letter-spacing: -0.01em;
|
|
398
|
+
color: var(--cn-kpi-stacked-value-color, var(--color-main-text));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.cn-kpi-card--stacked .cn-kpi-card__caption-line {
|
|
402
|
+
flex: 0 0 auto;
|
|
403
|
+
max-width: 100%;
|
|
404
|
+
font-size: var(--cn-kpi-label-size, 13px);
|
|
405
|
+
font-weight: 500;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* A linked stacked tile reads as a tile, not as underlined link text. The
|
|
409
|
+
NL Design theme underlines every `a` with `!important`, so the opt-out
|
|
410
|
+
needs it too; it only applies to this opt-in layout. */
|
|
411
|
+
a.cn-kpi-card--stacked,
|
|
412
|
+
a.cn-kpi-card--stacked:hover,
|
|
413
|
+
a.cn-kpi-card--stacked:focus-visible {
|
|
414
|
+
text-decoration: none !important;
|
|
415
|
+
}
|
|
416
|
+
|
|
347
417
|
/* Filled: the card draws its own grey box, for a KPI that is NOT already
|
|
348
418
|
inside a wrapper that draws one. Opt-in via `content.flat: false`. */
|
|
349
419
|
.cn-kpi-card--filled {
|
package/src/css/table.css
CHANGED
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
.cn-data-table th,
|
|
156
156
|
.cn-data-table td {
|
|
157
157
|
padding: calc(3 * var(--default-grid-baseline));
|
|
158
|
-
text-align:
|
|
158
|
+
text-align: start;
|
|
159
159
|
border-bottom: 1px solid var(--color-border);
|
|
160
160
|
vertical-align: middle;
|
|
161
161
|
max-width: var(--cn-table-cell-max-width, 22rem);
|
|
@@ -174,6 +174,18 @@
|
|
|
174
174
|
white-space: normal;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/* The secondary line of a cell (`columns[].secondary`): muted, smaller, on its
|
|
178
|
+
own line under the value. Theme hooks: --cn-table-secondary-size and
|
|
179
|
+
--cn-table-secondary-color. */
|
|
180
|
+
.cn-data-table .cn-table-cell__secondary {
|
|
181
|
+
display: block;
|
|
182
|
+
color: var(--cn-table-secondary-color, var(--color-text-maxcontrast));
|
|
183
|
+
font-size: var(--cn-table-secondary-size, 0.9em);
|
|
184
|
+
line-height: 1.35;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
text-overflow: ellipsis;
|
|
187
|
+
}
|
|
188
|
+
|
|
177
189
|
/* Back to wrapping, for a column holding prose rather than a label:
|
|
178
190
|
`columns: [{ key: 'description', cellClass: 'cn-cell--wrap' }]`. */
|
|
179
191
|
.cn-data-table th.cn-cell--wrap,
|
|
@@ -235,7 +247,7 @@
|
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
.cn-data-table td.cn-cell--end {
|
|
238
|
-
text-align:
|
|
250
|
+
text-align: end;
|
|
239
251
|
white-space: nowrap;
|
|
240
252
|
}
|
|
241
253
|
|
|
@@ -273,7 +285,7 @@
|
|
|
273
285
|
}
|
|
274
286
|
|
|
275
287
|
.cn-table-sort-indicator {
|
|
276
|
-
margin-
|
|
288
|
+
margin-inline-start: var(--default-grid-baseline);
|
|
277
289
|
opacity: 0.6;
|
|
278
290
|
}
|
|
279
291
|
|
|
@@ -383,7 +395,7 @@
|
|
|
383
395
|
justify-content: center;
|
|
384
396
|
min-width: 14px;
|
|
385
397
|
height: 14px;
|
|
386
|
-
margin-
|
|
398
|
+
margin-inline-start: 2px;
|
|
387
399
|
padding: 0 3px;
|
|
388
400
|
border-radius: 7px;
|
|
389
401
|
background-color: var(--color-primary-element);
|
|
@@ -448,7 +460,7 @@
|
|
|
448
460
|
|
|
449
461
|
.cn-table-col--icon {
|
|
450
462
|
width: 40px;
|
|
451
|
-
padding-
|
|
463
|
+
padding-inline-end: 0;
|
|
452
464
|
text-align: center;
|
|
453
465
|
color: var(--color-text-maxcontrast);
|
|
454
466
|
}
|
|
@@ -511,7 +523,7 @@
|
|
|
511
523
|
|
|
512
524
|
.viewTable { width: 100%; border-collapse: collapse; background-color: var(--color-main-background); }
|
|
513
525
|
|
|
514
|
-
.viewTable th, .viewTable td { padding: calc(3 * var(--default-grid-baseline)); text-align:
|
|
526
|
+
.viewTable th, .viewTable td { padding: calc(3 * var(--default-grid-baseline)); text-align: start; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
|
|
515
527
|
|
|
516
528
|
.viewTable th { background: var(--color-background-hover); font-weight: 500; color: var(--color-text-maxcontrast); background-color: var(--color-background-dark); }
|
|
517
529
|
|