@vc-shell/framework 1.2.2 → 1.2.3
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/core/composables/useMenuService/index.ts +10 -2
- package/core/services/menu-service.ts +40 -1
- package/core/types/index.ts +47 -0
- package/dist/core/composables/useMenuService/index.d.ts +2 -2
- package/dist/core/composables/useMenuService/index.d.ts.map +1 -1
- package/dist/core/services/menu-service.d.ts +24 -1
- package/dist/core/services/menu-service.d.ts.map +1 -1
- package/dist/core/types/index.d.ts +34 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/framework.js +5258 -5108
- package/dist/index.css +1 -1
- package/dist/shared/composables/index.d.ts +1 -0
- package/dist/shared/composables/index.d.ts.map +1 -1
- package/dist/shared/composables/useTableSelection.d.ts +57 -0
- package/dist/shared/composables/useTableSelection.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/components/atoms/vc-badge/vc-badge.vue.d.ts +3 -0
- package/dist/ui/components/atoms/vc-badge/vc-badge.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/useBadge.d.ts +18 -0
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/useBadge.d.ts.map +1 -0
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts +4 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts +4 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue.d.ts.map +1 -1
- package/package.json +5 -5
- package/shared/composables/index.ts +1 -0
- package/shared/composables/useTableSelection.ts +145 -0
- package/ui/components/atoms/vc-badge/vc-badge.vue +59 -0
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/useBadge.ts +80 -0
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue +53 -10
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue +10 -1
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +3 -0
|
@@ -9,6 +9,8 @@ export interface Props {
|
|
|
9
9
|
customPosition?: boolean;
|
|
10
10
|
top?: string;
|
|
11
11
|
right?: string;
|
|
12
|
+
/** When true, renders badge as inline element without absolute positioning (no slot content) */
|
|
13
|
+
inline?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export interface Emits {
|
|
14
16
|
(event: "click"): void;
|
|
@@ -30,6 +32,7 @@ declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {},
|
|
|
30
32
|
customPosition: boolean;
|
|
31
33
|
top: string;
|
|
32
34
|
right: string;
|
|
35
|
+
inline: boolean;
|
|
33
36
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
34
37
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
35
38
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vc-badge.vue.d.ts","sourceRoot":"","sources":["../../../../../ui/components/atoms/vc-badge/vc-badge.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vc-badge.vue.d.ts","sourceRoot":"","sources":["../../../../../ui/components/atoms/vc-badge/vc-badge.vue"],"names":[],"mappings":"AA8SA,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IAC9E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,KAAK;IACpB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACxB;AAgBD,KAAK,WAAW,GAAG;IACjB;;SAEK;IACL,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;CAC9B,CAAC;AAyIF,QAAA,MAAM,eAAe;;;;;UA1KZ,GAAG,GAAG,GAAG;WACR,OAAO;aACL,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW;oBAC5D,OAAO;SAClB,MAAM;WACJ,MAAM;YAEL,OAAO;6EA2KhB,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ComputedRef } from "vue";
|
|
2
|
+
import type { MenuItemBadgeConfig } from "./../../../../../../../../../core/types";
|
|
3
|
+
export interface ResolvedBadge {
|
|
4
|
+
content: string | number | undefined;
|
|
5
|
+
variant: "primary" | "success" | "warning" | "danger" | "info" | "secondary";
|
|
6
|
+
isDot: boolean;
|
|
7
|
+
isVisible: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Composable to resolve badge configuration for menu items.
|
|
11
|
+
* Supports direct badge config, routeId lookup, or groupId lookup.
|
|
12
|
+
*
|
|
13
|
+
* @param badgeConfig - Direct badge configuration (takes priority)
|
|
14
|
+
* @param routeId - Route ID to lookup badge from registry
|
|
15
|
+
* @param groupId - Group ID to lookup badge from registry
|
|
16
|
+
*/
|
|
17
|
+
export declare function useBadge(badgeConfig: MenuItemBadgeConfig | undefined, routeId?: string, groupId?: string): ComputedRef<ResolvedBadge>;
|
|
18
|
+
//# sourceMappingURL=useBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBadge.d.ts","sourceRoot":"","sources":["../../../../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/useBadge.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,WAAW,EAAY,MAAM,KAAK,CAAC;AACzE,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,yCAAyC,CAAC;AAGlG,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IAC7E,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,WAAW,EAAE,mBAAmB,GAAG,SAAS,EAC5C,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,WAAW,CAAC,aAAa,CAAC,CAwD5B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component } from "vue";
|
|
2
|
-
import { MenuItem } from "./../../../../../../../../../core/types";
|
|
2
|
+
import { MenuItem, MenuItemBadgeConfig } from "./../../../../../../../../../core/types";
|
|
3
3
|
export interface Props {
|
|
4
4
|
children?: MenuItem[];
|
|
5
5
|
sticky?: boolean;
|
|
@@ -8,6 +8,9 @@ export interface Props {
|
|
|
8
8
|
url?: string;
|
|
9
9
|
expand?: boolean;
|
|
10
10
|
id?: string | number;
|
|
11
|
+
badge?: MenuItemBadgeConfig;
|
|
12
|
+
routeId?: string;
|
|
13
|
+
groupId?: string;
|
|
11
14
|
}
|
|
12
15
|
export interface Emits {
|
|
13
16
|
(event: "onClick", item?: MenuItem): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vc-app-menu-link.vue.d.ts","sourceRoot":"","sources":["../../../../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"vc-app-menu-link.vue.d.ts","sourceRoot":"","sources":["../../../../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue"],"names":[],"mappings":"AAAA,OAgcO,EAAmC,SAAS,EAAmB,MAAM,KAAK,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAKxF,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,KAAK;IACpB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC3C;;;;;;YAbU,OAAO;;AA+YlB,wBAQG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MenuItem } from "../../../../../../../../core/types";
|
|
1
|
+
import { MenuItem, MenuItemBadgeConfig } from "../../../../../../../../core/types";
|
|
2
2
|
import type { Component } from "vue";
|
|
3
3
|
export interface Props {
|
|
4
4
|
sticky?: boolean;
|
|
@@ -9,6 +9,9 @@ export interface Props {
|
|
|
9
9
|
children?: MenuItem[];
|
|
10
10
|
expand?: boolean;
|
|
11
11
|
id?: string | number;
|
|
12
|
+
badge?: MenuItemBadgeConfig;
|
|
13
|
+
routeId?: string;
|
|
14
|
+
groupId?: string;
|
|
12
15
|
}
|
|
13
16
|
export interface Emits {
|
|
14
17
|
(event: "click", item?: MenuItem): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vc-app-menu-item.vue.d.ts","sourceRoot":"","sources":["../../../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vc-app-menu-item.vue.d.ts","sourceRoot":"","sources":["../../../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue"],"names":[],"mappings":"AAyEA,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AACrC,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,KAAK;IACpB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACzC;;;;;;cAVY,QAAQ,EAAE;YALZ,OAAO;;AA6JlB,wBAQG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vc-app-menu.vue.d.ts","sourceRoot":"","sources":["../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vc-app-menu.vue.d.ts","sourceRoot":"","sources":["../../../../../../../ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue"],"names":[],"mappings":"AAoPA,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAIxD,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,KAAK;IACpB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CAChC;;;;;;;;aANU,MAAM,GAAG,SAAS;;AAkK7B,wBAQG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/framework",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/framework.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -90,9 +90,9 @@
|
|
|
90
90
|
"@fullhuman/postcss-purgecss": "^7.0.2",
|
|
91
91
|
"@laynezh/vite-plugin-lib-assets": "v1.1.0",
|
|
92
92
|
"@types/dompurify": "^3.0.5",
|
|
93
|
-
"@vc-shell/api-client-generator": "1.2.
|
|
94
|
-
"@vc-shell/config-generator": "1.2.
|
|
95
|
-
"@vc-shell/ts-config": "1.2.
|
|
93
|
+
"@vc-shell/api-client-generator": "1.2.3",
|
|
94
|
+
"@vc-shell/config-generator": "1.2.3",
|
|
95
|
+
"@vc-shell/ts-config": "1.2.3",
|
|
96
96
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
97
97
|
"@vue/test-utils": "^2.4.5",
|
|
98
98
|
"cypress-signalr-mock": "^1.5.0",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"access": "public",
|
|
113
113
|
"registry": "https://registry.npmjs.org/"
|
|
114
114
|
},
|
|
115
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "6270dab6a5a8149c8380d17d55b77531bad13531"
|
|
116
116
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { ref, computed, Ref, ComputedRef } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface UseTableSelectionOptions<T> {
|
|
4
|
+
/**
|
|
5
|
+
* The field to use for extracting IDs from items.
|
|
6
|
+
* Can be a key of T or a function that extracts the ID.
|
|
7
|
+
* @default 'id'
|
|
8
|
+
*/
|
|
9
|
+
idField?: keyof T | ((item: T) => string | undefined);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UseTableSelectionReturn<T> {
|
|
13
|
+
/**
|
|
14
|
+
* Array of currently selected item objects.
|
|
15
|
+
*/
|
|
16
|
+
selectedItems: Ref<T[]>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Computed array of IDs extracted from selected items.
|
|
20
|
+
*/
|
|
21
|
+
selectedIds: ComputedRef<string[]>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether "select all" across pagination is active.
|
|
25
|
+
*/
|
|
26
|
+
allSelected: Ref<boolean>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Number of currently selected items.
|
|
30
|
+
*/
|
|
31
|
+
selectionCount: ComputedRef<number>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether any items are currently selected.
|
|
35
|
+
*/
|
|
36
|
+
hasSelection: ComputedRef<boolean>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handler for VcTable's @selection-changed event.
|
|
40
|
+
*/
|
|
41
|
+
handleSelectionChange: (items: T[]) => void;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handler for VcTable's @select:all event.
|
|
45
|
+
*/
|
|
46
|
+
handleSelectAll: (selected: boolean) => void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Clears all selection state.
|
|
50
|
+
*/
|
|
51
|
+
resetSelection: () => void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Checks if a specific item is selected.
|
|
55
|
+
*/
|
|
56
|
+
isSelected: (item: T) => boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Programmatically select items.
|
|
60
|
+
*/
|
|
61
|
+
selectItems: (items: T[]) => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deselect items by their IDs.
|
|
65
|
+
*/
|
|
66
|
+
deselectByIds: (ids: string[]) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function useTableSelection<T extends object>(
|
|
70
|
+
options?: UseTableSelectionOptions<T>
|
|
71
|
+
): UseTableSelectionReturn<T> {
|
|
72
|
+
const idField = options?.idField ?? ("id" as keyof T);
|
|
73
|
+
|
|
74
|
+
const getItemId = (item: T): string | undefined => {
|
|
75
|
+
if (typeof idField === "function") {
|
|
76
|
+
return idField(item);
|
|
77
|
+
}
|
|
78
|
+
const value = item[idField as keyof T];
|
|
79
|
+
return typeof value === "string" ? value : undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const selectedItems = ref<T[]>([]) as Ref<T[]>;
|
|
83
|
+
const allSelected = ref(false);
|
|
84
|
+
|
|
85
|
+
const selectedIds = computed<string[]>(() => {
|
|
86
|
+
return selectedItems.value.flatMap((item) => {
|
|
87
|
+
const id = getItemId(item);
|
|
88
|
+
return id ? [id] : [];
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const selectionCount = computed(() => selectedItems.value.length);
|
|
93
|
+
const hasSelection = computed(() => selectedItems.value.length > 0);
|
|
94
|
+
const selectedIdSet = computed(() => new Set(selectedIds.value));
|
|
95
|
+
|
|
96
|
+
const handleSelectionChange = (items: T[]): void => {
|
|
97
|
+
selectedItems.value = items;
|
|
98
|
+
if (items.length === 0) {
|
|
99
|
+
allSelected.value = false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleSelectAll = (selected: boolean): void => {
|
|
104
|
+
allSelected.value = selected;
|
|
105
|
+
if (!selected) {
|
|
106
|
+
selectedItems.value = [];
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const resetSelection = (): void => {
|
|
111
|
+
selectedItems.value = [];
|
|
112
|
+
allSelected.value = false;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isSelected = (item: T): boolean => {
|
|
116
|
+
const id = getItemId(item);
|
|
117
|
+
return id ? selectedIdSet.value.has(id) : false;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const selectItems = (items: T[]): void => {
|
|
121
|
+
selectedItems.value = items;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const deselectByIds = (ids: string[]): void => {
|
|
125
|
+
const idsToRemove = new Set(ids);
|
|
126
|
+
selectedItems.value = selectedItems.value.filter((item) => {
|
|
127
|
+
const id = getItemId(item);
|
|
128
|
+
return id ? !idsToRemove.has(id) : true;
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
selectedItems,
|
|
134
|
+
selectedIds,
|
|
135
|
+
allSelected,
|
|
136
|
+
selectionCount,
|
|
137
|
+
hasSelection,
|
|
138
|
+
handleSelectionChange,
|
|
139
|
+
handleSelectAll,
|
|
140
|
+
resetSelection,
|
|
141
|
+
isSelected,
|
|
142
|
+
selectItems,
|
|
143
|
+
deselectByIds,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<!-- Inline mode: render badge element directly without wrapper -->
|
|
2
3
|
<div
|
|
4
|
+
v-if="inline && (typeof content !== 'undefined' || isDot)"
|
|
5
|
+
ref="badge"
|
|
6
|
+
class="vc-badge__badge vc-badge__badge--inline"
|
|
7
|
+
:class="[
|
|
8
|
+
`vc-badge__badge--${variant}`,
|
|
9
|
+
{
|
|
10
|
+
'vc-badge__badge--active': active,
|
|
11
|
+
'vc-badge__badge--clickable': clickable,
|
|
12
|
+
'vc-badge__badge--disabled': disabled,
|
|
13
|
+
'vc-badge__badge--content-long': String(content).length > 1,
|
|
14
|
+
'vc-badge__badge--content-very-long': String(content).length > 2,
|
|
15
|
+
'vc-badge__badge--dot': isDot,
|
|
16
|
+
'vc-badge__badge--inline-small': size === 's',
|
|
17
|
+
'vc-badge__badge--inline-medium': size === 'm',
|
|
18
|
+
},
|
|
19
|
+
]"
|
|
20
|
+
@click="onClick"
|
|
21
|
+
>
|
|
22
|
+
<span
|
|
23
|
+
v-if="!isDot"
|
|
24
|
+
class="vc-badge__text"
|
|
25
|
+
>{{ content }}</span
|
|
26
|
+
>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- Standard mode: wrapper with slot and positioned badge -->
|
|
30
|
+
<div
|
|
31
|
+
v-else
|
|
3
32
|
ref="badgeContainer"
|
|
4
33
|
class="vc-badge"
|
|
5
34
|
:class="{
|
|
@@ -53,6 +82,8 @@ export interface Props {
|
|
|
53
82
|
customPosition?: boolean;
|
|
54
83
|
top?: string;
|
|
55
84
|
right?: string;
|
|
85
|
+
/** When true, renders badge as inline element without absolute positioning (no slot content) */
|
|
86
|
+
inline?: boolean;
|
|
56
87
|
}
|
|
57
88
|
|
|
58
89
|
export interface Emits {
|
|
@@ -66,6 +97,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
66
97
|
customPosition: false,
|
|
67
98
|
top: undefined,
|
|
68
99
|
right: undefined,
|
|
100
|
+
inline: false,
|
|
69
101
|
});
|
|
70
102
|
|
|
71
103
|
const emit = defineEmits<Emits>();
|
|
@@ -236,4 +268,31 @@ $sizes: small, medium;
|
|
|
236
268
|
.vc-badge__badge--disabled {
|
|
237
269
|
@apply tw-cursor-not-allowed tw-bg-[color:var(--badge-background-color-disabled)] tw-text-[color:var(--badge-text-color-disabled)] tw-border-[color:var(--badge-border-color-disabled)] hover:tw-bg-[color:var(--badge-background-color-disabled)] hover:tw-text-[color:var(--badge-text-color-disabled)] hover:tw-border-[color:var(--badge-border-color-disabled)];
|
|
238
270
|
}
|
|
271
|
+
|
|
272
|
+
// Inline mode - relative positioning, no wrapper
|
|
273
|
+
.vc-badge__badge--inline {
|
|
274
|
+
@apply tw-relative tw-top-0 tw-right-0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.vc-badge__badge--inline-small {
|
|
278
|
+
height: var(--badge-size-small);
|
|
279
|
+
min-width: var(--badge-size-small);
|
|
280
|
+
|
|
281
|
+
&.vc-badge__badge--dot {
|
|
282
|
+
height: var(--badge-dot-size-small);
|
|
283
|
+
min-width: var(--badge-dot-size-small);
|
|
284
|
+
width: var(--badge-dot-size-small);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.vc-badge__badge--inline-medium {
|
|
289
|
+
height: var(--badge-size-medium);
|
|
290
|
+
min-width: var(--badge-size-medium);
|
|
291
|
+
|
|
292
|
+
&.vc-badge__badge--dot {
|
|
293
|
+
height: var(--badge-dot-size-medium);
|
|
294
|
+
min-width: var(--badge-dot-size-medium);
|
|
295
|
+
width: var(--badge-dot-size-medium);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
239
298
|
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { computed, unref, isRef, type ComputedRef, type Ref } from "vue";
|
|
2
|
+
import type { MenuItemBadgeConfig, MenuItemBadge } from "./../../../../../../../../../core/types";
|
|
3
|
+
import { getMenuBadges } from "./../../../../../../../../../core/services/menu-service";
|
|
4
|
+
|
|
5
|
+
export interface ResolvedBadge {
|
|
6
|
+
content: string | number | undefined;
|
|
7
|
+
variant: "primary" | "success" | "warning" | "danger" | "info" | "secondary";
|
|
8
|
+
isDot: boolean;
|
|
9
|
+
isVisible: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Composable to resolve badge configuration for menu items.
|
|
14
|
+
* Supports direct badge config, routeId lookup, or groupId lookup.
|
|
15
|
+
*
|
|
16
|
+
* @param badgeConfig - Direct badge configuration (takes priority)
|
|
17
|
+
* @param routeId - Route ID to lookup badge from registry
|
|
18
|
+
* @param groupId - Group ID to lookup badge from registry
|
|
19
|
+
*/
|
|
20
|
+
export function useBadge(
|
|
21
|
+
badgeConfig: MenuItemBadgeConfig | undefined,
|
|
22
|
+
routeId?: string,
|
|
23
|
+
groupId?: string,
|
|
24
|
+
): ComputedRef<ResolvedBadge> {
|
|
25
|
+
const menuBadges = getMenuBadges();
|
|
26
|
+
|
|
27
|
+
return computed(() => {
|
|
28
|
+
// Priority: direct badge config > badge from registry by routeId > badge from registry by groupId
|
|
29
|
+
let config: MenuItemBadgeConfig | undefined = badgeConfig;
|
|
30
|
+
if (!config && routeId) {
|
|
31
|
+
config = menuBadges.value.get(routeId);
|
|
32
|
+
}
|
|
33
|
+
if (!config && groupId) {
|
|
34
|
+
config = menuBadges.value.get(groupId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!config) {
|
|
38
|
+
return { content: undefined, variant: "primary", isDot: false, isVisible: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Normalize shorthand to full config object
|
|
42
|
+
let badge: MenuItemBadge;
|
|
43
|
+
if (typeof config === "object" && !isRef(config) && "content" in config) {
|
|
44
|
+
badge = config as MenuItemBadge;
|
|
45
|
+
} else {
|
|
46
|
+
badge = { content: config as MenuItemBadge["content"] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resolve reactive content
|
|
50
|
+
let rawContent: string | number | undefined = undefined;
|
|
51
|
+
if (typeof badge.content === "function") {
|
|
52
|
+
rawContent = badge.content();
|
|
53
|
+
} else if (isRef(badge.content)) {
|
|
54
|
+
rawContent = unref(badge.content as Ref<string | number | undefined>);
|
|
55
|
+
} else {
|
|
56
|
+
rawContent = badge.content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Truncate values > 99
|
|
60
|
+
let displayContent: string | number | undefined = rawContent;
|
|
61
|
+
if (typeof rawContent === "number" && rawContent > 99) {
|
|
62
|
+
displayContent = "99+";
|
|
63
|
+
} else if (typeof rawContent === "string") {
|
|
64
|
+
const numValue = parseInt(rawContent, 10);
|
|
65
|
+
if (!isNaN(numValue) && numValue > 99) {
|
|
66
|
+
displayContent = "99+";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const isDot = badge.isDot ?? false;
|
|
71
|
+
const isVisible = isDot || (displayContent != null && displayContent !== "" && displayContent !== 0);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
content: displayContent,
|
|
75
|
+
variant: badge.variant ?? "primary",
|
|
76
|
+
isDot,
|
|
77
|
+
isVisible,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -45,6 +45,15 @@
|
|
|
45
45
|
<div class="vc-app-menu-link__title-truncate">
|
|
46
46
|
{{ $t(title ?? "") }}
|
|
47
47
|
</div>
|
|
48
|
+
<VcBadge
|
|
49
|
+
v-if="resolvedBadge.isVisible"
|
|
50
|
+
:content="resolvedBadge.isDot ? undefined : resolvedBadge.content"
|
|
51
|
+
:variant="resolvedBadge.variant"
|
|
52
|
+
:is-dot="resolvedBadge.isDot"
|
|
53
|
+
size="s"
|
|
54
|
+
inline
|
|
55
|
+
class="vc-app-menu-link__badge"
|
|
56
|
+
/>
|
|
48
57
|
<div
|
|
49
58
|
v-if="(!!children?.length && expand) || false"
|
|
50
59
|
class="vc-app-menu-link__title-icon"
|
|
@@ -119,15 +128,28 @@
|
|
|
119
128
|
{{ twoLettersTitle(nested.title) }}
|
|
120
129
|
</div>
|
|
121
130
|
<Transition name="opacity">
|
|
122
|
-
<
|
|
131
|
+
<div
|
|
123
132
|
v-show="expand"
|
|
124
|
-
class="vc-app-menu-link__child-item-
|
|
125
|
-
:class="{
|
|
126
|
-
'vc-app-menu-link__child-item-title--no-icon': !nested.icon,
|
|
127
|
-
}"
|
|
133
|
+
class="vc-app-menu-link__child-item-content"
|
|
128
134
|
>
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
<p
|
|
136
|
+
class="vc-app-menu-link__child-item-title"
|
|
137
|
+
:class="{
|
|
138
|
+
'vc-app-menu-link__child-item-title--no-icon': !nested.icon,
|
|
139
|
+
}"
|
|
140
|
+
>
|
|
141
|
+
{{ $t(nested.title) }}
|
|
142
|
+
</p>
|
|
143
|
+
<VcBadge
|
|
144
|
+
v-if="getChildBadge(nested).isVisible"
|
|
145
|
+
:content="getChildBadge(nested).isDot ? undefined : getChildBadge(nested).content"
|
|
146
|
+
:variant="getChildBadge(nested).variant"
|
|
147
|
+
:is-dot="getChildBadge(nested).isDot"
|
|
148
|
+
size="s"
|
|
149
|
+
inline
|
|
150
|
+
class="vc-app-menu-link__badge"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
131
153
|
</Transition>
|
|
132
154
|
</div>
|
|
133
155
|
</div>
|
|
@@ -139,9 +161,10 @@
|
|
|
139
161
|
|
|
140
162
|
<script lang="ts" setup>
|
|
141
163
|
import { ref, watch, computed, onMounted, Component, MaybeRef, unref } from "vue";
|
|
142
|
-
import { MenuItem } from "./../../../../../../../../../core/types";
|
|
143
|
-
import { VcIcon } from "./../../../../../../../";
|
|
164
|
+
import { MenuItem, MenuItemBadgeConfig } from "./../../../../../../../../../core/types";
|
|
165
|
+
import { VcIcon, VcBadge } from "./../../../../../../../";
|
|
144
166
|
import { useRoute } from "vue-router";
|
|
167
|
+
import { useBadge } from "./useBadge";
|
|
145
168
|
|
|
146
169
|
export interface Props {
|
|
147
170
|
children?: MenuItem[];
|
|
@@ -151,6 +174,9 @@ export interface Props {
|
|
|
151
174
|
url?: string;
|
|
152
175
|
expand?: boolean;
|
|
153
176
|
id?: string | number;
|
|
177
|
+
badge?: MenuItemBadgeConfig;
|
|
178
|
+
routeId?: string;
|
|
179
|
+
groupId?: string;
|
|
154
180
|
}
|
|
155
181
|
|
|
156
182
|
export interface Emits {
|
|
@@ -163,6 +189,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
163
189
|
|
|
164
190
|
const emit = defineEmits<Emits>();
|
|
165
191
|
|
|
192
|
+
// Badge resolution for parent menu item
|
|
193
|
+
const resolvedBadge = useBadge(props.badge, props.routeId, props.groupId);
|
|
194
|
+
|
|
195
|
+
// Helper to get resolved badge for child items
|
|
196
|
+
const getChildBadge = (child: MenuItem) => {
|
|
197
|
+
return useBadge(child.badge, child.routeId, child.groupId).value;
|
|
198
|
+
};
|
|
199
|
+
|
|
166
200
|
const isOpened = ref(false);
|
|
167
201
|
const route = useRoute();
|
|
168
202
|
const params = Object.fromEntries(Object.entries(route.params).filter(([key]) => key !== "pathMatch"));
|
|
@@ -346,7 +380,7 @@ watch(isOpened, (newValue) => {
|
|
|
346
380
|
|
|
347
381
|
&__child-item {
|
|
348
382
|
@apply tw-cursor-pointer tw-min-w-0 tw-flex tw-h-[var(--app-menu-item-height)]
|
|
349
|
-
tw-items-center tw-transition-[padding] tw-duration-150 tw-w-full tw-py-2 tw-
|
|
383
|
+
tw-items-center tw-transition-[padding] tw-duration-150 tw-w-full tw-py-2 tw-pl-3 tw-pr-2
|
|
350
384
|
tw-text-[color:var(--app-menu-item-title-color)] tw-text-sm
|
|
351
385
|
hover:tw-bg-[var(--app-menu-item-background-color-hover)]
|
|
352
386
|
hover:tw-text-[color:var(--app-menu-item-title-color-active)] tw-gap-5;
|
|
@@ -389,6 +423,15 @@ watch(isOpened, (newValue) => {
|
|
|
389
423
|
&__icon-collapsed {
|
|
390
424
|
@apply tw-p-0 tw-m-0;
|
|
391
425
|
}
|
|
426
|
+
|
|
427
|
+
// Badge styles for menu items - positioned after text
|
|
428
|
+
&__badge {
|
|
429
|
+
@apply tw-shrink-0 tw-ml-auto;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
&__child-item-content {
|
|
433
|
+
@apply tw-flex tw-items-center tw-w-full tw-min-w-0;
|
|
434
|
+
}
|
|
392
435
|
}
|
|
393
436
|
|
|
394
437
|
.opacity-enter-active,
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
:title="title ?? ''"
|
|
14
14
|
:url="url"
|
|
15
15
|
:expand="expand"
|
|
16
|
+
:badge="badge"
|
|
17
|
+
:route-id="routeId"
|
|
18
|
+
:group-id="groupId"
|
|
16
19
|
@on-click="$emit('click')"
|
|
17
20
|
/>
|
|
18
21
|
</router-link>
|
|
@@ -26,6 +29,9 @@
|
|
|
26
29
|
:icon="icon ?? ''"
|
|
27
30
|
:title="title ?? ''"
|
|
28
31
|
:expand="expand"
|
|
32
|
+
:badge="badge"
|
|
33
|
+
:route-id="routeId"
|
|
34
|
+
:group-id="groupId"
|
|
29
35
|
@on-click="$emit('click', $event)"
|
|
30
36
|
/>
|
|
31
37
|
</template>
|
|
@@ -34,7 +40,7 @@
|
|
|
34
40
|
|
|
35
41
|
<script lang="ts" setup>
|
|
36
42
|
import VcAppMenuLink from "./_internal/vc-app-menu-link.vue";
|
|
37
|
-
import { MenuItem } from "../../../../../../../../core/types";
|
|
43
|
+
import { MenuItem, MenuItemBadgeConfig } from "../../../../../../../../core/types";
|
|
38
44
|
import type { Component } from "vue";
|
|
39
45
|
export interface Props {
|
|
40
46
|
sticky?: boolean;
|
|
@@ -45,6 +51,9 @@ export interface Props {
|
|
|
45
51
|
children?: MenuItem[];
|
|
46
52
|
expand?: boolean;
|
|
47
53
|
id?: string | number;
|
|
54
|
+
badge?: MenuItemBadgeConfig;
|
|
55
|
+
routeId?: string;
|
|
56
|
+
groupId?: string;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export interface Emits {
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
:title="item.title as string"
|
|
37
37
|
:children="item.children"
|
|
38
38
|
:expand="$isDesktop.value ? isExpanded || isHoverExpanded : true"
|
|
39
|
+
:badge="item.badge"
|
|
40
|
+
:route-id="item.routeId"
|
|
41
|
+
:group-id="item.groupId"
|
|
39
42
|
@click="handleMenuItemClick(item, $event)"
|
|
40
43
|
/>
|
|
41
44
|
</div>
|