@conduction/nextcloud-vue 2.56.0-beta.6 → 2.56.0-beta.7
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/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +39 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +224 -9
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +54 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +54 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +931 -4
- package/dist/esm/utils/dateVariant.js +124 -0
- package/dist/esm/utils/dateVariant.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +260 -0
- package/dist/esm/utils/detailActionModel.js.map +1 -0
- package/dist/esm/utils/dueRule.js +84 -0
- package/dist/esm/utils/dueRule.js.map +1 -0
- package/dist/esm/utils/fetchAggregate.js +37 -1
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +186 -0
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +5 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/readPath.js +30 -0
- package/dist/esm/utils/readPath.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 +14970 -8991
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +931 -4
- package/l10n/en.json +54 -1
- package/l10n/nl.json +54 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +3 -0
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
- package/src/components/CnAppNav/CnAppNav.md +26 -0
- package/src/components/CnAppNav/CnAppNav.vue +104 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
- package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
- package/src/components/CnBoardView/CnBoardView.vue +77 -0
- package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
- package/src/components/CnBrandStripe/index.js +4 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
- package/src/components/CnConversationThread/CnConversationThread.md +51 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
- package/src/components/CnConversationThread/index.js +4 -0
- package/src/components/CnDetailPage/CnDetailPage.md +16 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +683 -48
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
- package/src/components/CnDocumentReviewList/index.js +4 -0
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
- package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
- package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
- package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
- package/src/components/CnNextStepCard/index.js +4 -0
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
- package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
- package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
- package/src/components/CnSegmentedControl/index.js +4 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
- package/src/components/CnStackedBarWidget/index.js +14 -0
- package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
- package/src/components/CnStackedBarWidgetForm/index.js +4 -0
- package/src/components/CnTabs/CnTab.vue +35 -0
- package/src/components/CnTabs/CnTabs.vue +244 -10
- package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
- package/src/components/CnWeekStripWidget/index.js +14 -0
- package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
- package/src/components/CnWeekStripWidgetForm/index.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
- package/src/components/index.js +11 -0
- package/src/css/detail-page.css +47 -0
- package/src/index.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +389 -1
- package/src/schemas/app-manifest.schema.json +2 -1
- package/src/utils/dateVariant.js +120 -0
- package/src/utils/detailActionModel.js +256 -0
- package/src/utils/dueRule.js +94 -0
- package/src/utils/fetchAggregate.js +36 -0
- package/src/utils/fetchFilterCounts.js +182 -0
- package/src/utils/index.js +1 -0
- package/src/utils/libraryWidgetKeys.js +5 -0
- package/src/utils/readPath.js +27 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
:size="16"
|
|
45
45
|
class="cn-quick-filter-bar__icon" />
|
|
46
46
|
<span class="cn-quick-filter-bar__label">{{ entry.tab.label }}</span>
|
|
47
|
+
<span
|
|
48
|
+
v-if="countOf(entry) !== null"
|
|
49
|
+
class="cn-quick-filter-bar__count"
|
|
50
|
+
data-testid="cn-quick-filter-count">{{ ' ' + countOf(entry) }}</span>
|
|
47
51
|
</button>
|
|
48
52
|
</div>
|
|
49
53
|
<!-- The overflow is a CHIP and not a toolbar button: it is one of the
|
|
@@ -96,6 +100,10 @@
|
|
|
96
100
|
:size="16"
|
|
97
101
|
class="cn-quick-filter-bar__icon" />
|
|
98
102
|
<span class="cn-quick-filter-bar__label">{{ entry.tab.label }}</span>
|
|
103
|
+
<span
|
|
104
|
+
v-if="countOf(entry) !== null"
|
|
105
|
+
class="cn-quick-filter-bar__count"
|
|
106
|
+
data-testid="cn-quick-filter-count">{{ ' ' + countOf(entry) }}</span>
|
|
99
107
|
</button>
|
|
100
108
|
</div>
|
|
101
109
|
</div>
|
|
@@ -231,6 +239,19 @@ export default {
|
|
|
231
239
|
default: 0,
|
|
232
240
|
validator: (v) => Number.isFinite(v) && v >= 0,
|
|
233
241
|
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* How many records each tab matches, keyed by tab index (an array in
|
|
245
|
+
* tab order works too). A tab with a number here shows it after its
|
|
246
|
+
* label; a tab without one shows no count. A tab can also carry its
|
|
247
|
+
* own `count`. `null` (the default) shows no counts at all.
|
|
248
|
+
*
|
|
249
|
+
* @type {{[index: number]: number}|Array<number|null>|null}
|
|
250
|
+
*/
|
|
251
|
+
counts: {
|
|
252
|
+
type: [Object, Array],
|
|
253
|
+
default: null,
|
|
254
|
+
},
|
|
234
255
|
},
|
|
235
256
|
|
|
236
257
|
emits: ['update:active-index', 'update:selected-indices'],
|
|
@@ -324,10 +345,19 @@ export default {
|
|
|
324
345
|
* omitted — an empty selection already means "all".
|
|
325
346
|
*
|
|
326
347
|
* @return {Array<{label: string, index: number, icon: string}>}
|
|
348
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
327
349
|
*/
|
|
328
350
|
dropdownOptions() {
|
|
329
351
|
return this.tabs
|
|
330
|
-
.map((tab, index) =>
|
|
352
|
+
.map((tab, index) => {
|
|
353
|
+
const count = this.countOf({ tab, index })
|
|
354
|
+
return {
|
|
355
|
+
label: count === null ? tab.label : `${tab.label} (${count})`,
|
|
356
|
+
icon: tab.icon,
|
|
357
|
+
index,
|
|
358
|
+
_empty: this.isEmptyFilter(tab),
|
|
359
|
+
}
|
|
360
|
+
})
|
|
331
361
|
.filter((o) => !o._empty)
|
|
332
362
|
},
|
|
333
363
|
|
|
@@ -346,6 +376,20 @@ export default {
|
|
|
346
376
|
},
|
|
347
377
|
|
|
348
378
|
methods: {
|
|
379
|
+
/**
|
|
380
|
+
* The count to show on a tab, or null for none: `counts[index]`
|
|
381
|
+
* first, else the tab's own `count`.
|
|
382
|
+
*
|
|
383
|
+
* @param {{tab: object, index: number}} entry The tab and its index.
|
|
384
|
+
* @return {number|null}
|
|
385
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
386
|
+
*/
|
|
387
|
+
countOf(entry) {
|
|
388
|
+
const fromProp = this.counts ? this.counts[entry.index] : undefined
|
|
389
|
+
const value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count
|
|
390
|
+
return (typeof value === 'number' && Number.isFinite(value)) ? value : null
|
|
391
|
+
},
|
|
392
|
+
|
|
349
393
|
/**
|
|
350
394
|
* Is tab `i` rendered active in chips mode (handles both single
|
|
351
395
|
* `activeIndex` and `multiple` `selectedIndices`).
|
|
@@ -501,6 +545,21 @@ export default {
|
|
|
501
545
|
line-height: 1;
|
|
502
546
|
}
|
|
503
547
|
|
|
548
|
+
/* Inherits the chip's own text colour, so it stays readable on the active
|
|
549
|
+
(primary) fill as well as on the resting one. The count's text starts with
|
|
550
|
+
a space: the chip is a flex row, so the space is not drawn, but it keeps a
|
|
551
|
+
screen reader from reading label and count as one word. */
|
|
552
|
+
.cn-quick-filter-bar__count {
|
|
553
|
+
min-width: 1.5em;
|
|
554
|
+
padding: 2px 6px;
|
|
555
|
+
border: 1px solid currentColor;
|
|
556
|
+
border-radius: var(--border-radius-pill, 10px);
|
|
557
|
+
font-size: 0.85em;
|
|
558
|
+
font-weight: 600;
|
|
559
|
+
line-height: 1;
|
|
560
|
+
text-align: center;
|
|
561
|
+
}
|
|
562
|
+
|
|
504
563
|
/* NcPopover wraps its trigger in a plain `<div class="v-popper">`, which as a
|
|
505
564
|
block would drop the chip onto its own line under the strip. */
|
|
506
565
|
.cn-quick-filter-bar > :deep(.v-popper) {
|
|
@@ -182,6 +182,18 @@ export default {
|
|
|
182
182
|
type: Number,
|
|
183
183
|
default: 3,
|
|
184
184
|
},
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* How many records each view matches, keyed by view id (or slug). A
|
|
188
|
+
* view with a number here shows it after its name; a view can also
|
|
189
|
+
* carry its own `count`. `null` (the default) shows no counts.
|
|
190
|
+
*
|
|
191
|
+
* @type {{[viewId: string]: number}|null}
|
|
192
|
+
*/
|
|
193
|
+
counts: {
|
|
194
|
+
type: Object,
|
|
195
|
+
default: null,
|
|
196
|
+
},
|
|
185
197
|
},
|
|
186
198
|
|
|
187
199
|
emits: ['apply', 'delete-request', 'save-request'],
|
|
@@ -245,9 +257,42 @@ export default {
|
|
|
245
257
|
*
|
|
246
258
|
* @param {object} row The row from buildViewTree().
|
|
247
259
|
* @return {string} The name.
|
|
260
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
248
261
|
*/
|
|
249
262
|
rowName(row) {
|
|
250
|
-
return `${'\u2007'.repeat(row.depth * 2)}${row.view
|
|
263
|
+
return `${'\u2007'.repeat(row.depth * 2)}${this.nameWithCount(row.view)}`
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The number of records a view matches, or null when none is known:
|
|
268
|
+
* `counts[id]` (or `counts[slug]`) first, else the view's own `count`.
|
|
269
|
+
*
|
|
270
|
+
* @param {object} view The saved view.
|
|
271
|
+
* @return {number|null}
|
|
272
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
273
|
+
*/
|
|
274
|
+
countOf(view) {
|
|
275
|
+
const counts = this.counts || {}
|
|
276
|
+
let value = counts[view.id]
|
|
277
|
+
if (value === undefined || value === null) {
|
|
278
|
+
value = counts[view.slug]
|
|
279
|
+
}
|
|
280
|
+
if (value === undefined || value === null) {
|
|
281
|
+
value = view.count
|
|
282
|
+
}
|
|
283
|
+
return (typeof value === 'number' && Number.isFinite(value)) ? value : null
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* A view's name, followed by its count when one is known.
|
|
288
|
+
*
|
|
289
|
+
* @param {object} view The saved view.
|
|
290
|
+
* @return {string}
|
|
291
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
292
|
+
*/
|
|
293
|
+
nameWithCount(view) {
|
|
294
|
+
const count = this.countOf(view)
|
|
295
|
+
return count === null ? view.name : `${view.name} (${count})`
|
|
251
296
|
},
|
|
252
297
|
|
|
253
298
|
/**
|
|
@@ -259,11 +304,13 @@ export default {
|
|
|
259
304
|
*
|
|
260
305
|
* @param {object} row The row from buildViewTree().
|
|
261
306
|
* @return {string} The accessible label.
|
|
307
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
262
308
|
*/
|
|
263
309
|
rowLabel(row) {
|
|
310
|
+
const viewName = this.nameWithCount(row.view)
|
|
264
311
|
const name = row.depth === 0
|
|
265
|
-
?
|
|
266
|
-
: t('nextcloud-vue', '{name}, level {level}', { name:
|
|
312
|
+
? viewName
|
|
313
|
+
: t('nextcloud-vue', '{name}, level {level}', { name: viewName, level: row.depth + 1 })
|
|
267
314
|
|
|
268
315
|
if (row.orphaned !== true) {
|
|
269
316
|
return name
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
A switch between a few views of the same thing. It is a radio group: the arrow keys move the choice.
|
|
2
|
+
|
|
3
|
+
```vue
|
|
4
|
+
<template>
|
|
5
|
+
<div>
|
|
6
|
+
<CnSegmentedControl
|
|
7
|
+
v-model="view"
|
|
8
|
+
aria-label="View"
|
|
9
|
+
:options="[
|
|
10
|
+
{ value: 'mine', label: 'My work' },
|
|
11
|
+
{ value: 'team', label: 'My team', count: 12 },
|
|
12
|
+
]" />
|
|
13
|
+
<p>Chosen: {{ view }}</p>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
export default {
|
|
19
|
+
data() { return { view: 'mine' } },
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Stretched, with a disabled option:
|
|
25
|
+
|
|
26
|
+
```vue
|
|
27
|
+
<template>
|
|
28
|
+
<CnSegmentedControl
|
|
29
|
+
v-model="period"
|
|
30
|
+
stretch
|
|
31
|
+
aria-label="Period"
|
|
32
|
+
:options="[
|
|
33
|
+
{ value: 'week', label: 'Week' },
|
|
34
|
+
{ value: 'month', label: 'Month' },
|
|
35
|
+
{ value: 'year', label: 'Year', disabled: true },
|
|
36
|
+
]" />
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
export default {
|
|
41
|
+
data() { return { period: 'week' } },
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Props: `options` lists the choices, `modelValue` is the chosen value (use `v-model`), `aria-label` names the group (or point `aria-labelledby` at a visible heading), and `stretch` fills the width.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div
|
|
7
|
+
class="cn-segmented-control"
|
|
8
|
+
:class="{ 'cn-segmented-control--stretch': stretch }"
|
|
9
|
+
role="radiogroup"
|
|
10
|
+
:aria-label="ariaLabel || null"
|
|
11
|
+
:aria-labelledby="ariaLabelledby || null"
|
|
12
|
+
@keydown="onKeydown">
|
|
13
|
+
<button
|
|
14
|
+
v-for="(option, index) in normalizedOptions"
|
|
15
|
+
:key="option.key"
|
|
16
|
+
type="button"
|
|
17
|
+
role="radio"
|
|
18
|
+
class="cn-segmented-control__option"
|
|
19
|
+
:class="{ 'cn-segmented-control__option--checked': isChecked(option) }"
|
|
20
|
+
:aria-checked="isChecked(option) ? 'true' : 'false'"
|
|
21
|
+
:tabindex="index === focusableIndex ? 0 : -1"
|
|
22
|
+
:disabled="option.disabled || null"
|
|
23
|
+
:data-value="option.key"
|
|
24
|
+
@click="select(option)">
|
|
25
|
+
<!-- @slot option Replaces the content of one option. -->
|
|
26
|
+
<!-- @binding {object} option The option: `{ value, label, disabled, count }`. -->
|
|
27
|
+
<!-- @binding {boolean} checked Whether this option is the chosen one. -->
|
|
28
|
+
<slot name="option" :option="option" :checked="isChecked(option)">
|
|
29
|
+
<span class="cn-segmented-control__label">{{ option.label }}</span>
|
|
30
|
+
<!-- The leading space is not drawn in this flex row; it keeps a
|
|
31
|
+
screen reader from reading label and count as one word. -->
|
|
32
|
+
<span v-if="option.count !== null" class="cn-segmented-control__count">{{ ' ' + option.count }}</span>
|
|
33
|
+
</slot>
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
/**
|
|
40
|
+
* CnSegmentedControl is a switch between a few views of the same thing, such
|
|
41
|
+
* as "My work / My team". One option is always the chosen one.
|
|
42
|
+
*
|
|
43
|
+
* It is a radio group: screen readers announce the group and which option is
|
|
44
|
+
* checked, the arrow keys move the choice, and Tab enters and leaves the group
|
|
45
|
+
* in one step. Give it a name with `aria-label` (or `aria-labelledby`).
|
|
46
|
+
*
|
|
47
|
+
* ```vue
|
|
48
|
+
* <CnSegmentedControl
|
|
49
|
+
* v-model="view"
|
|
50
|
+
* aria-label="View"
|
|
51
|
+
* :options="[
|
|
52
|
+
* { value: 'mine', label: 'My work' },
|
|
53
|
+
* { value: 'team', label: 'My team' },
|
|
54
|
+
* ]" />
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* To switch between panels of content, use `CnTabs` with `variant="segmented"`
|
|
58
|
+
* instead: that keeps the tab and panel semantics and looks the same.
|
|
59
|
+
*/
|
|
60
|
+
export default {
|
|
61
|
+
name: 'CnSegmentedControl',
|
|
62
|
+
|
|
63
|
+
props: {
|
|
64
|
+
/**
|
|
65
|
+
* The options. Each is `{ value, label, disabled?, count? }`, or a
|
|
66
|
+
* plain string that is both the value and the label. `count` shows a
|
|
67
|
+
* number after the label.
|
|
68
|
+
*
|
|
69
|
+
* @type {Array<string|{value: (string|number|boolean), label: string, disabled?: boolean, count?: number}>}
|
|
70
|
+
*/
|
|
71
|
+
options: {
|
|
72
|
+
type: Array,
|
|
73
|
+
required: true,
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The value of the chosen option (v-model).
|
|
78
|
+
*
|
|
79
|
+
* @type {string|number|boolean|null}
|
|
80
|
+
*/
|
|
81
|
+
modelValue: {
|
|
82
|
+
type: [Boolean, String, Number],
|
|
83
|
+
default: null,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/** Accessible name of the group, read out when focus enters it. */
|
|
87
|
+
ariaLabel: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: '',
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/** Id of an element that names the group, as an alternative to `ariaLabel`. */
|
|
93
|
+
ariaLabelledby: {
|
|
94
|
+
type: String,
|
|
95
|
+
default: '',
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/** Stretch the control to the full width, with options of equal width. */
|
|
99
|
+
stretch: {
|
|
100
|
+
type: Boolean,
|
|
101
|
+
default: false,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
emits: [
|
|
106
|
+
/**
|
|
107
|
+
* Emitted with the value of the newly chosen option.
|
|
108
|
+
*
|
|
109
|
+
* @event update:modelValue
|
|
110
|
+
* @type {string|number|boolean}
|
|
111
|
+
*/
|
|
112
|
+
'update:modelValue',
|
|
113
|
+
],
|
|
114
|
+
|
|
115
|
+
computed: {
|
|
116
|
+
/**
|
|
117
|
+
* The options in one shape.
|
|
118
|
+
*
|
|
119
|
+
* @return {Array<{key: string, value: (string|number|boolean), label: string, disabled: boolean, count: (number|null)}>}
|
|
120
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
|
|
121
|
+
*/
|
|
122
|
+
normalizedOptions() {
|
|
123
|
+
return (Array.isArray(this.options) ? this.options : [])
|
|
124
|
+
.filter((option) => option !== null && option !== undefined)
|
|
125
|
+
.map((option) => {
|
|
126
|
+
if (typeof option !== 'object') {
|
|
127
|
+
return { key: String(option), value: option, label: String(option), disabled: false, count: null }
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
key: String(option.value),
|
|
131
|
+
value: option.value,
|
|
132
|
+
label: String(option.label ?? option.value),
|
|
133
|
+
disabled: option.disabled === true,
|
|
134
|
+
count: Number.isFinite(option.count) ? option.count : null,
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The option Tab lands on: the chosen one, else the first enabled one.
|
|
141
|
+
*
|
|
142
|
+
* @return {number} Its index, or -1 when no option can take focus.
|
|
143
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
|
|
144
|
+
*/
|
|
145
|
+
focusableIndex() {
|
|
146
|
+
const checked = this.normalizedOptions.findIndex((option) => this.isChecked(option) && !option.disabled)
|
|
147
|
+
if (checked !== -1) {
|
|
148
|
+
return checked
|
|
149
|
+
}
|
|
150
|
+
return this.normalizedOptions.findIndex((option) => !option.disabled)
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
methods: {
|
|
155
|
+
/**
|
|
156
|
+
* Whether an option is the chosen one.
|
|
157
|
+
*
|
|
158
|
+
* @param {{value: (string|number|boolean)}} option The option.
|
|
159
|
+
* @return {boolean}
|
|
160
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
|
|
161
|
+
*/
|
|
162
|
+
isChecked(option) {
|
|
163
|
+
return this.modelValue !== null && String(option.value) === String(this.modelValue)
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Choose an option.
|
|
168
|
+
*
|
|
169
|
+
* @param {{value: (string|number|boolean), disabled: boolean}} option The option.
|
|
170
|
+
* @return {void}
|
|
171
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
|
|
172
|
+
*/
|
|
173
|
+
select(option) {
|
|
174
|
+
if (option.disabled || this.isChecked(option)) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
this.$emit('update:modelValue', option.value)
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Arrow keys, Home and End move the choice, as in a native radio
|
|
182
|
+
* group. Disabled options are passed over and the ends wrap around.
|
|
183
|
+
*
|
|
184
|
+
* @param {KeyboardEvent} event The key press.
|
|
185
|
+
* @return {void}
|
|
186
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
|
|
187
|
+
*/
|
|
188
|
+
onKeydown(event) {
|
|
189
|
+
const enabled = this.normalizedOptions
|
|
190
|
+
.map((option, index) => ({ option, index }))
|
|
191
|
+
.filter((entry) => !entry.option.disabled)
|
|
192
|
+
if (enabled.length === 0) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
const buttons = Array.from(this.$el.querySelectorAll('[role="radio"]'))
|
|
196
|
+
const focused = buttons.indexOf(document.activeElement)
|
|
197
|
+
const from = focused !== -1 ? focused : this.focusableIndex
|
|
198
|
+
let position = enabled.findIndex((entry) => entry.index === from)
|
|
199
|
+
if (position === -1) {
|
|
200
|
+
position = 0
|
|
201
|
+
}
|
|
202
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
203
|
+
position = (position + 1) % enabled.length
|
|
204
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
205
|
+
position = (position - 1 + enabled.length) % enabled.length
|
|
206
|
+
} else if (event.key === 'Home') {
|
|
207
|
+
position = 0
|
|
208
|
+
} else if (event.key === 'End') {
|
|
209
|
+
position = enabled.length - 1
|
|
210
|
+
} else {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
event.preventDefault()
|
|
214
|
+
const target = enabled[position]
|
|
215
|
+
this.select(target.option)
|
|
216
|
+
if (buttons[target.index] && typeof buttons[target.index].focus === 'function') {
|
|
217
|
+
buttons[target.index].focus()
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<style scoped>
|
|
225
|
+
.cn-segmented-control {
|
|
226
|
+
display: inline-flex;
|
|
227
|
+
gap: 4px;
|
|
228
|
+
max-width: 100%;
|
|
229
|
+
padding: 4px;
|
|
230
|
+
border-radius: var(--border-radius-container, var(--border-radius-large, 10px));
|
|
231
|
+
background-color: var(--color-background-dark);
|
|
232
|
+
overflow-x: auto;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.cn-segmented-control--stretch {
|
|
236
|
+
display: flex;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.cn-segmented-control--stretch .cn-segmented-control__option {
|
|
240
|
+
flex: 1 1 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* The parent class outscores Nextcloud's server rule that gives every plain
|
|
244
|
+
`button` a margin and a border. */
|
|
245
|
+
.cn-segmented-control .cn-segmented-control__option {
|
|
246
|
+
display: inline-flex;
|
|
247
|
+
align-items: center;
|
|
248
|
+
justify-content: center;
|
|
249
|
+
gap: 8px;
|
|
250
|
+
box-sizing: border-box;
|
|
251
|
+
min-height: 36px;
|
|
252
|
+
margin: 0;
|
|
253
|
+
padding: 0 16px;
|
|
254
|
+
border: 1px solid transparent;
|
|
255
|
+
border-radius: var(--border-radius-element, var(--border-radius-large, 8px));
|
|
256
|
+
background-color: transparent;
|
|
257
|
+
/* Full-contrast label on every option: the choice is carried by the
|
|
258
|
+
raised surface, not by dimming the others. */
|
|
259
|
+
color: var(--color-main-text);
|
|
260
|
+
font: inherit;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
cursor: pointer;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.cn-segmented-control .cn-segmented-control__option:hover {
|
|
267
|
+
background-color: var(--color-background-hover);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.cn-segmented-control .cn-segmented-control__option:focus-visible {
|
|
271
|
+
outline: 2px solid var(--color-primary-element);
|
|
272
|
+
outline-offset: 2px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* The border survives forced-colours mode, which drops background and shadow. */
|
|
276
|
+
.cn-segmented-control .cn-segmented-control__option--checked,
|
|
277
|
+
.cn-segmented-control .cn-segmented-control__option--checked:hover {
|
|
278
|
+
border-color: var(--color-border-dark);
|
|
279
|
+
background-color: var(--color-main-background);
|
|
280
|
+
box-shadow: 0 1px 2px var(--color-box-shadow);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.cn-segmented-control .cn-segmented-control__option[disabled] {
|
|
284
|
+
cursor: default;
|
|
285
|
+
opacity: 0.5;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.cn-segmented-control__count {
|
|
289
|
+
font-weight: 400;
|
|
290
|
+
}
|
|
291
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
One bar cut into segments, with a legend that carries the numbers. This example uses static segments; in an app they usually come from an OpenRegister `source` with `groupBy`.
|
|
2
|
+
|
|
3
|
+
```vue
|
|
4
|
+
<CnStackedBarWidget
|
|
5
|
+
:content="{
|
|
6
|
+
segments: [
|
|
7
|
+
{ label: 'Received', value: 3 },
|
|
8
|
+
{ label: 'In progress', value: 6 },
|
|
9
|
+
{ label: 'Decision', value: 3 },
|
|
10
|
+
{ label: 'Publish', value: 2 },
|
|
11
|
+
],
|
|
12
|
+
}" />
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Props: `content` holds the configuration (see the component description) and `translate` is an optional translate function for the labels in it.
|