design-system-next 2.9.7 → 2.9.10
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/design-system-next.js +6672 -6381
- package/dist/design-system-next.js.gz +0 -0
- package/dist/main.css +1 -1
- package/dist/main.css.gz +0 -0
- package/dist/package.json.d.ts +1 -1
- package/package.json +1 -1
- package/src/App.vue +1 -80
- package/src/assets/styles/tailwind.css +10 -0
- package/src/components/avatar/avatar.ts +4 -0
- package/src/components/avatar/avatar.vue +5 -1
- package/src/components/avatar/use-avatar.ts +2 -1
- package/src/components/calendar/calendar.ts +9 -0
- package/src/components/calendar/calendar.vue +69 -24
- package/src/components/calendar/use-calendar.ts +6 -4
- package/src/components/calendar-cell/calendar-cell.ts +4 -0
- package/src/components/calendar-cell/calendar-cell.vue +3 -1
- package/src/components/calendar-cell/use-calendar-cell.ts +3 -2
- package/src/components/card/card.ts +5 -0
- package/src/components/card/card.vue +1 -1
- package/src/components/card/use-card.ts +8 -1
- package/src/components/filter/filter.ts +12 -0
- package/src/components/filter/filter.vue +23 -13
- package/src/components/filter/use-filter.ts +49 -19
- package/src/components/lozenge/lozenge.ts +4 -0
- package/src/components/lozenge/lozenge.vue +3 -2
- package/src/components/lozenge/use-lozenge.ts +12 -5
- package/src/components/select/select-ladderized/use-select-ladderized.ts +164 -164
- package/src/components/sidenav/sidenav.ts +47 -7
- package/src/components/sidenav/sidenav.vue +6 -6
- package/src/components/sidenav/use-sidenav.ts +97 -3
|
@@ -5,18 +5,24 @@ import classNames from 'classnames';
|
|
|
5
5
|
import type { LozengePropTypes } from './lozenge';
|
|
6
6
|
|
|
7
7
|
interface LozengeClasses {
|
|
8
|
+
wrapperClasses: string;
|
|
8
9
|
baseClasses: string;
|
|
9
10
|
toneClasses: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const useLozenge = (props: LozengePropTypes) => {
|
|
13
|
-
const { tone, fill } = toRefs(props);
|
|
14
|
+
const { tone, fill, loading } = toRefs(props);
|
|
14
15
|
|
|
15
16
|
const lozengeClasses: ComputedRef<LozengeClasses> = computed(() => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
17
|
+
const wrapperClasses = classNames({
|
|
18
|
+
'spr-h-fit spr-w-fit': !loading.value,
|
|
19
|
+
'spr-flex spr-w-full': loading.value,
|
|
20
|
+
});
|
|
21
|
+
const baseClasses = classNames({
|
|
22
|
+
'spr-flex spr-flex-wrap spr-rounded-md': !fill.value,
|
|
23
|
+
'spr-flex spr-flex-wrap': fill.value,
|
|
24
|
+
'spr-skeletal-loader spr-flex spr-h-6 spr-w-full spr-rounded-md': loading.value,
|
|
25
|
+
});
|
|
20
26
|
|
|
21
27
|
const toneClasses = classNames(
|
|
22
28
|
'spr-label-xs-medium spr-inline-flex spr-items-center spr-gap-size-spacing-6xs spr-rounded-md spr-border spr-border-solid spr-p-size-spacing-5xs spr-text-xs spr-uppercase',
|
|
@@ -48,6 +54,7 @@ export const useLozenge = (props: LozengePropTypes) => {
|
|
|
48
54
|
);
|
|
49
55
|
|
|
50
56
|
return {
|
|
57
|
+
wrapperClasses,
|
|
51
58
|
baseClasses,
|
|
52
59
|
toneClasses,
|
|
53
60
|
};
|
|
@@ -1,164 +1,164 @@
|
|
|
1
|
-
import { ref, toRefs, computed, watch } from 'vue';
|
|
2
|
-
import { useVModel, useDebounceFn, onClickOutside } from '@vueuse/core';
|
|
3
|
-
|
|
4
|
-
import type { SelectLadderizedPropTypes } from './select-ladderized';
|
|
5
|
-
|
|
6
|
-
import type { MenuListType } from '@/components/list/list';
|
|
7
|
-
|
|
8
|
-
export const useSelectLadderized = (
|
|
9
|
-
props: SelectLadderizedPropTypes,
|
|
10
|
-
emit: (event: string, ...args: unknown[]) => void,
|
|
11
|
-
) => {
|
|
12
|
-
const { options, disabled } = toRefs(props);
|
|
13
|
-
|
|
14
|
-
const ladderizedClasses = computed(() => ({
|
|
15
|
-
baseClasses: 'spr-flex spr-flex-col spr-gap-size-spacing-4xs',
|
|
16
|
-
labelClasses: 'spr-body-sm-regular spr-text-color-strong spr-block',
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
// Popper Variables
|
|
20
|
-
const ladderizedSelectPopperState = ref(false);
|
|
21
|
-
const ladderizedSelectRef = ref(null);
|
|
22
|
-
const isLadderizedSelectPopperDisabled = computed(() => disabled.value);
|
|
23
|
-
|
|
24
|
-
// Ladderized Select Model
|
|
25
|
-
const ladderizedSelectModel = useVModel(props, 'modelValue', emit);
|
|
26
|
-
const ladderizedSelectOptions = computed(() => options.value);
|
|
27
|
-
|
|
28
|
-
// Input Variables
|
|
29
|
-
const inputText = ref<string>('');
|
|
30
|
-
const isSearching = ref(false);
|
|
31
|
-
const wasCleared = ref(false);
|
|
32
|
-
|
|
33
|
-
const isLeafNode = (item: MenuListType): boolean => {
|
|
34
|
-
return !item.sublevel || item.sublevel.length === 0;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Helper to find the path to a selected value in the menu tree
|
|
38
|
-
const findPathToValue = (items: MenuListType[], value: string | number, path: string[] = []): string[] | null => {
|
|
39
|
-
for (const item of items) {
|
|
40
|
-
const newPath = [...path, item.text];
|
|
41
|
-
|
|
42
|
-
if (item.value === value) {
|
|
43
|
-
return newPath;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (item.sublevel) {
|
|
47
|
-
const result = findPathToValue(item.sublevel, value, newPath);
|
|
48
|
-
|
|
49
|
-
if (result) return result;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return null;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const handleSelectedLadderizedItem = (selectedItems: string[], selectedItem?: MenuListType) => {
|
|
57
|
-
wasCleared.value = false;
|
|
58
|
-
ladderizedSelectModel.value = selectedItems;
|
|
59
|
-
|
|
60
|
-
let itemToCheck = selectedItem;
|
|
61
|
-
|
|
62
|
-
// Fallback: if selectedItem is not provided, try to find it from the value
|
|
63
|
-
if (!itemToCheck && selectedItems.length > 0) {
|
|
64
|
-
const findItemByValue = (items: MenuListType[], value: string | number): MenuListType | undefined => {
|
|
65
|
-
for (const item of items) {
|
|
66
|
-
if (item.value === value) return item;
|
|
67
|
-
|
|
68
|
-
if (item.sublevel) {
|
|
69
|
-
const found = findItemByValue(item.sublevel, value);
|
|
70
|
-
|
|
71
|
-
if (found) return found;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return undefined;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
itemToCheck = findItemByValue(ladderizedSelectOptions.value, selectedItems[selectedItems.length - 1]);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (itemToCheck) {
|
|
82
|
-
const path = findPathToValue(ladderizedSelectOptions.value, itemToCheck.value);
|
|
83
|
-
|
|
84
|
-
inputText.value = path ? path.join(' > ') : itemToCheck.text || '';
|
|
85
|
-
|
|
86
|
-
if (isLeafNode(itemToCheck)) {
|
|
87
|
-
ladderizedSelectPopperState.value = false;
|
|
88
|
-
}
|
|
89
|
-
} else if (selectedItems.length === 0 && !wasCleared.value) {
|
|
90
|
-
inputText.value = '';
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const handleSearch = () => {
|
|
95
|
-
isSearching.value = true;
|
|
96
|
-
|
|
97
|
-
debouncedEmitSearch();
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const debouncedEmitSearch = useDebounceFn(() => {
|
|
101
|
-
// Optionally emit search event here if needed
|
|
102
|
-
}, 300);
|
|
103
|
-
|
|
104
|
-
const handleClear = () => {
|
|
105
|
-
wasCleared.value = true;
|
|
106
|
-
|
|
107
|
-
inputText.value = '';
|
|
108
|
-
|
|
109
|
-
emit('update:modelValue', []);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const handleOptionsToggle = () => {
|
|
113
|
-
ladderizedSelectPopperState.value = true;
|
|
114
|
-
|
|
115
|
-
isSearching.value = false;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Watch for changes in modelValue to update inputText
|
|
119
|
-
watch(
|
|
120
|
-
() => ladderizedSelectModel.value,
|
|
121
|
-
(newVal) => {
|
|
122
|
-
if (wasCleared.value) {
|
|
123
|
-
inputText.value = '';
|
|
124
|
-
wasCleared.value = false;
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (Array.isArray(newVal) && newVal.length > 0) {
|
|
129
|
-
// Treat the array as a single path for ladderized select
|
|
130
|
-
let currentLevel = ladderizedSelectOptions.value;
|
|
131
|
-
|
|
132
|
-
const pathTexts: string[] = [];
|
|
133
|
-
|
|
134
|
-
for (const value of newVal) {
|
|
135
|
-
const found = currentLevel.find((item) => item.value === value);
|
|
136
|
-
if (!found) break;
|
|
137
|
-
pathTexts.push(found.text);
|
|
138
|
-
currentLevel = found.sublevel || [];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
inputText.value = pathTexts.join(' > ');
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
{ immediate: true },
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
onClickOutside(ladderizedSelectRef, () => {
|
|
148
|
-
ladderizedSelectPopperState.value = false;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
ladderizedClasses,
|
|
153
|
-
ladderizedSelectPopperState,
|
|
154
|
-
ladderizedSelectRef,
|
|
155
|
-
ladderizedSelectOptions,
|
|
156
|
-
isLadderizedSelectPopperDisabled,
|
|
157
|
-
ladderizedSelectModel,
|
|
158
|
-
inputText,
|
|
159
|
-
handleSelectedLadderizedItem,
|
|
160
|
-
handleSearch,
|
|
161
|
-
handleClear,
|
|
162
|
-
handleOptionsToggle,
|
|
163
|
-
};
|
|
164
|
-
};
|
|
1
|
+
import { ref, toRefs, computed, watch } from 'vue';
|
|
2
|
+
import { useVModel, useDebounceFn, onClickOutside } from '@vueuse/core';
|
|
3
|
+
|
|
4
|
+
import type { SelectLadderizedPropTypes } from './select-ladderized';
|
|
5
|
+
|
|
6
|
+
import type { MenuListType } from '@/components/list/list';
|
|
7
|
+
|
|
8
|
+
export const useSelectLadderized = (
|
|
9
|
+
props: SelectLadderizedPropTypes,
|
|
10
|
+
emit: (event: string, ...args: unknown[]) => void,
|
|
11
|
+
) => {
|
|
12
|
+
const { options, disabled } = toRefs(props);
|
|
13
|
+
|
|
14
|
+
const ladderizedClasses = computed(() => ({
|
|
15
|
+
baseClasses: 'spr-flex spr-flex-col spr-gap-size-spacing-4xs',
|
|
16
|
+
labelClasses: 'spr-body-sm-regular spr-text-color-strong spr-block',
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Popper Variables
|
|
20
|
+
const ladderizedSelectPopperState = ref(false);
|
|
21
|
+
const ladderizedSelectRef = ref(null);
|
|
22
|
+
const isLadderizedSelectPopperDisabled = computed(() => disabled.value);
|
|
23
|
+
|
|
24
|
+
// Ladderized Select Model
|
|
25
|
+
const ladderizedSelectModel = useVModel(props, 'modelValue', emit);
|
|
26
|
+
const ladderizedSelectOptions = computed(() => options.value);
|
|
27
|
+
|
|
28
|
+
// Input Variables
|
|
29
|
+
const inputText = ref<string>('');
|
|
30
|
+
const isSearching = ref(false);
|
|
31
|
+
const wasCleared = ref(false);
|
|
32
|
+
|
|
33
|
+
const isLeafNode = (item: MenuListType): boolean => {
|
|
34
|
+
return !item.sublevel || item.sublevel.length === 0;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Helper to find the path to a selected value in the menu tree
|
|
38
|
+
const findPathToValue = (items: MenuListType[], value: string | number, path: string[] = []): string[] | null => {
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
const newPath = [...path, item.text];
|
|
41
|
+
|
|
42
|
+
if (item.value === value) {
|
|
43
|
+
return newPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (item.sublevel) {
|
|
47
|
+
const result = findPathToValue(item.sublevel, value, newPath);
|
|
48
|
+
|
|
49
|
+
if (result) return result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleSelectedLadderizedItem = (selectedItems: string[], selectedItem?: MenuListType) => {
|
|
57
|
+
wasCleared.value = false;
|
|
58
|
+
ladderizedSelectModel.value = selectedItems;
|
|
59
|
+
|
|
60
|
+
let itemToCheck = selectedItem;
|
|
61
|
+
|
|
62
|
+
// Fallback: if selectedItem is not provided, try to find it from the value
|
|
63
|
+
if (!itemToCheck && selectedItems.length > 0) {
|
|
64
|
+
const findItemByValue = (items: MenuListType[], value: string | number): MenuListType | undefined => {
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
if (item.value === value) return item;
|
|
67
|
+
|
|
68
|
+
if (item.sublevel) {
|
|
69
|
+
const found = findItemByValue(item.sublevel, value);
|
|
70
|
+
|
|
71
|
+
if (found) return found;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return undefined;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
itemToCheck = findItemByValue(ladderizedSelectOptions.value, selectedItems[selectedItems.length - 1]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (itemToCheck) {
|
|
82
|
+
const path = findPathToValue(ladderizedSelectOptions.value, itemToCheck.value);
|
|
83
|
+
|
|
84
|
+
inputText.value = path ? path.join(' > ') : itemToCheck.text || '';
|
|
85
|
+
|
|
86
|
+
if (isLeafNode(itemToCheck)) {
|
|
87
|
+
ladderizedSelectPopperState.value = false;
|
|
88
|
+
}
|
|
89
|
+
} else if (selectedItems.length === 0 && !wasCleared.value) {
|
|
90
|
+
inputText.value = '';
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleSearch = () => {
|
|
95
|
+
isSearching.value = true;
|
|
96
|
+
|
|
97
|
+
debouncedEmitSearch();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const debouncedEmitSearch = useDebounceFn(() => {
|
|
101
|
+
// Optionally emit search event here if needed
|
|
102
|
+
}, 300);
|
|
103
|
+
|
|
104
|
+
const handleClear = () => {
|
|
105
|
+
wasCleared.value = true;
|
|
106
|
+
|
|
107
|
+
inputText.value = '';
|
|
108
|
+
|
|
109
|
+
emit('update:modelValue', []);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleOptionsToggle = () => {
|
|
113
|
+
ladderizedSelectPopperState.value = true;
|
|
114
|
+
|
|
115
|
+
isSearching.value = false;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Watch for changes in modelValue to update inputText
|
|
119
|
+
watch(
|
|
120
|
+
() => ladderizedSelectModel.value,
|
|
121
|
+
(newVal) => {
|
|
122
|
+
if (wasCleared.value) {
|
|
123
|
+
inputText.value = '';
|
|
124
|
+
wasCleared.value = false;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (Array.isArray(newVal) && newVal.length > 0) {
|
|
129
|
+
// Treat the array as a single path for ladderized select
|
|
130
|
+
let currentLevel = ladderizedSelectOptions.value;
|
|
131
|
+
|
|
132
|
+
const pathTexts: string[] = [];
|
|
133
|
+
|
|
134
|
+
for (const value of newVal) {
|
|
135
|
+
const found = currentLevel.find((item) => item.value === value);
|
|
136
|
+
if (!found) break;
|
|
137
|
+
pathTexts.push(found.text);
|
|
138
|
+
currentLevel = found.sublevel || [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
inputText.value = pathTexts.join(' > ');
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{ immediate: true },
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
onClickOutside(ladderizedSelectRef, () => {
|
|
148
|
+
ladderizedSelectPopperState.value = false;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
ladderizedClasses,
|
|
153
|
+
ladderizedSelectPopperState,
|
|
154
|
+
ladderizedSelectRef,
|
|
155
|
+
ladderizedSelectOptions,
|
|
156
|
+
isLadderizedSelectPopperDisabled,
|
|
157
|
+
ladderizedSelectModel,
|
|
158
|
+
inputText,
|
|
159
|
+
handleSelectedLadderizedItem,
|
|
160
|
+
handleSearch,
|
|
161
|
+
handleClear,
|
|
162
|
+
handleOptionsToggle,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
@@ -5,6 +5,7 @@ type QuickAction = {
|
|
|
5
5
|
items: QuickActionItem[];
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
type QuickActionItem = {
|
|
9
10
|
title: string;
|
|
10
11
|
description: string;
|
|
@@ -14,23 +15,29 @@ type QuickActionItem = {
|
|
|
14
15
|
hidden: boolean;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
type NavLinks = {
|
|
18
|
+
export type NavLinks = {
|
|
18
19
|
top: { parentLinks: ParentLinkItem[] }[];
|
|
19
20
|
bottom: { parentLinks: ParentLinkItem[] }[];
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
type
|
|
23
|
+
export type NavAPILinks = {
|
|
24
|
+
top: { parentLinks: NavItem[] }[];
|
|
25
|
+
bottom: { parentLinks: NavItem[] }[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ParentLinkItem = {
|
|
23
29
|
title: string;
|
|
24
30
|
icon: string;
|
|
25
|
-
link
|
|
26
|
-
redirect
|
|
31
|
+
link?: string;
|
|
32
|
+
redirect?: Redirect;
|
|
27
33
|
menuLinks: MenuLink[];
|
|
28
|
-
|
|
34
|
+
submenuLinks?: SubmenuLink[];
|
|
35
|
+
hidden?: boolean;
|
|
29
36
|
};
|
|
30
37
|
|
|
31
38
|
type MenuLink = {
|
|
32
39
|
menuHeading: string;
|
|
33
|
-
items: MenuLinkItem[];
|
|
40
|
+
items: MenuLinkItem[] | ParentLinkItem[];
|
|
34
41
|
};
|
|
35
42
|
|
|
36
43
|
type MenuLinkItem = {
|
|
@@ -42,7 +49,7 @@ type MenuLinkItem = {
|
|
|
42
49
|
|
|
43
50
|
type SubmenuLink = {
|
|
44
51
|
subMenuHeading: string;
|
|
45
|
-
items: SubmenuLinkItem[];
|
|
52
|
+
items: SubmenuLinkItem[] | ParentLinkItem[];
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
type SubmenuLinkItem = {
|
|
@@ -70,6 +77,34 @@ type UserMenuItem = {
|
|
|
70
77
|
hidden: boolean;
|
|
71
78
|
redirect: Redirect;
|
|
72
79
|
};
|
|
80
|
+
export interface NavItem {
|
|
81
|
+
groupId: string;
|
|
82
|
+
label: string;
|
|
83
|
+
icon?: string | null;
|
|
84
|
+
url?: string | null;
|
|
85
|
+
isNewTab?: boolean;
|
|
86
|
+
children?: NavItem[] | null;
|
|
87
|
+
groupName?: string | null;
|
|
88
|
+
hidden?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface MappedNavItem {
|
|
92
|
+
title: string;
|
|
93
|
+
icon?: string;
|
|
94
|
+
redirect?: {
|
|
95
|
+
openInNewTab: boolean;
|
|
96
|
+
isAbsoluteURL: boolean;
|
|
97
|
+
link: string;
|
|
98
|
+
};
|
|
99
|
+
menuLinks?: {
|
|
100
|
+
menuHeading: string;
|
|
101
|
+
items: MappedNavItem[];
|
|
102
|
+
}[];
|
|
103
|
+
submenuLinks?: {
|
|
104
|
+
subMenuHeading: string;
|
|
105
|
+
items: MappedNavItem[];
|
|
106
|
+
}[];
|
|
107
|
+
}
|
|
73
108
|
|
|
74
109
|
export const sidenavPropTypes = {
|
|
75
110
|
quickActions: {
|
|
@@ -120,6 +155,11 @@ export const sidenavPropTypes = {
|
|
|
120
155
|
validator: (value: unknown) => typeof value === 'boolean',
|
|
121
156
|
default: false,
|
|
122
157
|
},
|
|
158
|
+
isNavApi: {
|
|
159
|
+
type: Boolean,
|
|
160
|
+
validator: (value: unknown) => typeof value === 'boolean',
|
|
161
|
+
default: false,
|
|
162
|
+
}
|
|
123
163
|
};
|
|
124
164
|
|
|
125
165
|
export const sidenavEmitTypes = {
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
141
|
<!-- Grouped Nav Links -->
|
|
142
|
-
<template v-for="(navLink, navLinkIndex) in
|
|
142
|
+
<template v-for="(navLink, navLinkIndex) in navLinks.top" :key="navLinkIndex">
|
|
143
143
|
<template v-for="(parentLink, parentLinkIndex) in navLink.parentLinks" :key="parentLinkIndex">
|
|
144
144
|
<!-- Parent link with menu links -->
|
|
145
145
|
<template v-if="parentLink.menuLinks && parentLink.menuLinks.length > 0">
|
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
</template>
|
|
353
353
|
</template>
|
|
354
354
|
<div
|
|
355
|
-
v-if="
|
|
355
|
+
v-if="navLinks.top.length > 0 && navLinkIndex < navLinks.top.length - 1"
|
|
356
356
|
class="spr-background-color-hover spr-h-[2px] spr-w-full"
|
|
357
357
|
></div>
|
|
358
358
|
</template>
|
|
@@ -360,11 +360,11 @@
|
|
|
360
360
|
|
|
361
361
|
<!-- Bottom Section -->
|
|
362
362
|
<div
|
|
363
|
-
v-if="
|
|
363
|
+
v-if="navLinks.bottom && navLinks.bottom.length > 0"
|
|
364
364
|
class="spr-grid spr-justify-center spr-gap-2 spr-px-3 spr-pb-4 spr-pt-0"
|
|
365
365
|
>
|
|
366
366
|
<!-- Grouped Nav Links -->
|
|
367
|
-
<template v-for="(navLink, navLinkIndex) in
|
|
367
|
+
<template v-for="(navLink, navLinkIndex) in navLinks.bottom" :key="navLinkIndex">
|
|
368
368
|
<template v-for="(parentLink, parentLinkIndex) in navLink.parentLinks" :key="parentLinkIndex">
|
|
369
369
|
<!-- Parent link with menu links -->
|
|
370
370
|
<template v-if="parentLink.menuLinks && parentLink.menuLinks.length > 0">
|
|
@@ -573,7 +573,7 @@
|
|
|
573
573
|
</template>
|
|
574
574
|
</template>
|
|
575
575
|
<div
|
|
576
|
-
v-if="
|
|
576
|
+
v-if="navLinks.bottom.length > 0 && navLinkIndex < navLinks.bottom.length - 1"
|
|
577
577
|
class="spr-background-color-hover spr-h-[2px] spr-w-full"
|
|
578
578
|
></div>
|
|
579
579
|
</template>
|
|
@@ -767,7 +767,7 @@ import SprBadge from '../badge/badge.vue';
|
|
|
767
767
|
const props = defineProps(sidenavPropTypes);
|
|
768
768
|
const emit = defineEmits(sidenavEmitTypes);
|
|
769
769
|
|
|
770
|
-
const { isQuckActionMenuVisible, isUserMenuVisible, userProfileError, getUserInitials, handleRedirect, generateId } = useSidenav(
|
|
770
|
+
const { navLinks, isQuckActionMenuVisible, isUserMenuVisible, userProfileError, getUserInitials, handleRedirect, generateId } = useSidenav(
|
|
771
771
|
props,
|
|
772
772
|
emit,
|
|
773
773
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ref } from 'vue';
|
|
1
|
+
import { ref, onMounted } from 'vue';
|
|
2
2
|
import type { SetupContext } from 'vue';
|
|
3
3
|
|
|
4
|
-
import type { SidenavPropTypes, SidenavEmitTypes } from './sidenav';
|
|
4
|
+
import type { SidenavPropTypes, SidenavEmitTypes, ParentLinkItem, NavLinks, NavItem } from './sidenav';
|
|
5
5
|
|
|
6
6
|
interface ObjectItem {
|
|
7
7
|
redirect: {
|
|
@@ -17,6 +17,7 @@ interface ObjectItem {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const useSidenav = (props: SidenavPropTypes, emit: SetupContext<SidenavEmitTypes>['emit']) => {
|
|
20
|
+
const navLinks = ref<NavLinks>(props.navLinks);
|
|
20
21
|
const isQuckActionMenuVisible = ref(false);
|
|
21
22
|
|
|
22
23
|
const isUserMenuVisible = ref(false);
|
|
@@ -67,13 +68,106 @@ export const useSidenav = (props: SidenavPropTypes, emit: SetupContext<SidenavEm
|
|
|
67
68
|
const generateId = (...titles: string[]): string => {
|
|
68
69
|
return titles.map(transformToCamelCaseId).join('_');
|
|
69
70
|
}
|
|
71
|
+
const confirmIfOwnDomain = (url: string) => {
|
|
72
|
+
const domain = window.location.href;
|
|
73
|
+
const urlHostname = new URL(url).hostname;
|
|
74
|
+
const isOwnDomain = domain === urlHostname || window.location.hostname === 'localhost'
|
|
75
|
+
return isOwnDomain;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const getPathFromUrl = (url: string): string => {
|
|
79
|
+
const parsedUrl = new URL(url);
|
|
80
|
+
return parsedUrl ? parsedUrl.pathname : '';
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const navLinkCondition = (link: NavItem) => {
|
|
85
|
+
if (confirmIfOwnDomain(link.url as string)) {
|
|
86
|
+
return getPathFromUrl(link.url as string);
|
|
87
|
+
} else {
|
|
88
|
+
return link.url;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const groupByGroupId = (items: NavItem[]) => {
|
|
93
|
+
const groups: Record<string, NavItem[]> = {};
|
|
94
|
+
items.forEach(item => {
|
|
95
|
+
if (!groups[item.groupId]) {
|
|
96
|
+
groups[item.groupId] = [];
|
|
97
|
+
}
|
|
98
|
+
groups[item.groupId].push(item);
|
|
99
|
+
});
|
|
100
|
+
return Object.values(groups).map(group => ({ parentLinks: group.map(mapItemToNav) }));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const mapItemToNav = (item: NavItem): ParentLinkItem => {
|
|
104
|
+
return {
|
|
105
|
+
title: item.label,
|
|
106
|
+
icon: item.icon || "",
|
|
107
|
+
redirect: item.url
|
|
108
|
+
? {
|
|
109
|
+
openInNewTab: item.isNewTab || false,
|
|
110
|
+
isAbsoluteURL: !confirmIfOwnDomain(item.url),
|
|
111
|
+
link: navLinkCondition(item) || "",
|
|
112
|
+
}
|
|
113
|
+
: undefined,
|
|
114
|
+
menuLinks: item.children && item.children.length > 0
|
|
115
|
+
? [{
|
|
116
|
+
menuHeading: '',
|
|
117
|
+
items: item.children.map(child => mapItemToNav(child))
|
|
118
|
+
}]
|
|
119
|
+
: [],
|
|
120
|
+
submenuLinks: item.children && item.children.length > 0 && !item.children.some(c => c.children)
|
|
121
|
+
? [{
|
|
122
|
+
subMenuHeading: '',
|
|
123
|
+
items: item.children.map(child => mapItemToNav(child)),
|
|
124
|
+
}]
|
|
125
|
+
: [],
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Helper function to extract valid NavItems from an array of objects
|
|
130
|
+
const extractValidNavItems = <T extends Record<string, unknown>>(items: T[]): NavItem[] => {
|
|
131
|
+
return items.filter((item): item is NavItem & T => (
|
|
132
|
+
item !== null &&
|
|
133
|
+
'groupId' in item &&
|
|
134
|
+
'label' in item &&
|
|
135
|
+
typeof item.groupId === 'string' &&
|
|
136
|
+
typeof item.label === 'string'
|
|
137
|
+
)) as NavItem[];
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const transformedNavItems = async (apiData: NavLinks) => {
|
|
141
|
+
// Output type matches navLinks ref type
|
|
142
|
+
const transformedData: NavLinks = { top: [], bottom: [] };
|
|
143
|
+
|
|
144
|
+
if (apiData.top && Array.isArray(apiData.top)) {
|
|
145
|
+
const validTopItems = extractValidNavItems(apiData.top);
|
|
146
|
+
transformedData.top = groupByGroupId(validTopItems);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (apiData.bottom && Array.isArray(apiData.bottom)) {
|
|
150
|
+
const validBottomItems = extractValidNavItems(apiData.bottom);
|
|
151
|
+
transformedData.bottom = groupByGroupId(validBottomItems);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return transformedData;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
onMounted(async () => {
|
|
158
|
+
if (props.isNavApi) {
|
|
159
|
+
navLinks.value = await transformedNavItems(props.navLinks);
|
|
160
|
+
}
|
|
161
|
+
})
|
|
70
162
|
|
|
71
163
|
return {
|
|
164
|
+
navLinks,
|
|
72
165
|
isQuckActionMenuVisible,
|
|
73
166
|
isUserMenuVisible,
|
|
74
167
|
userProfileError,
|
|
75
168
|
getUserInitials,
|
|
76
169
|
handleRedirect,
|
|
77
|
-
generateId
|
|
170
|
+
generateId,
|
|
171
|
+
transformedNavItems,
|
|
78
172
|
};
|
|
79
173
|
};
|