design-system-next 2.8.3 → 2.9.1
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 +8916 -8687
- 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/components/banner/banner.ts +20 -0
- package/src/components/banner/banner.vue +28 -0
- package/src/components/banner/use-banner.ts +96 -0
- package/src/components/calendar/calendar.ts +4 -31
- package/src/components/calendar/calendar.vue +186 -223
- package/src/components/calendar/use-calendar.ts +72 -38
- package/src/components/calendar-cell/use-calendar-cell.ts +0 -1
- package/src/components/list/use-list.ts +10 -6
- package/src/components/select/select-ladderized/use-select-ladderized.ts +164 -164
- package/src/components/select/use-select.ts +7 -3
- package/src/components/stepper/step/step.ts +8 -0
- package/src/components/stepper/step/step.vue +3 -1
- package/src/components/stepper/step/use-step.ts +22 -10
- package/src/components/stepper/stepper.ts +9 -0
- package/src/components/stepper/stepper.vue +1 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { computed, SetupContext, toRefs, ref, watch } from 'vue';
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
|
+
import isBetween from 'dayjs/plugin/isBetween';
|
|
3
4
|
import classNames from 'classnames';
|
|
5
|
+
import { useInfiniteScroll } from '@vueuse/core';
|
|
4
6
|
import { useVModel } from '@vueuse/core';
|
|
5
7
|
|
|
8
|
+
dayjs.extend(isBetween);
|
|
9
|
+
|
|
6
10
|
import type { CalendarPropTypes, CalendarEmitTypes, SelectedShift } from './calendar';
|
|
7
11
|
|
|
8
12
|
export const useCalendar = (props: CalendarPropTypes, emit: SetupContext<CalendarEmitTypes>['emit']) => {
|
|
9
|
-
const { initialDate,
|
|
13
|
+
const { initialDate, hideAddButton } = toRefs(props);
|
|
10
14
|
|
|
11
15
|
const state = {
|
|
12
16
|
dateFormat: ref('YYYY-MM-DD'),
|
|
@@ -21,13 +25,20 @@ export const useCalendar = (props: CalendarPropTypes, emit: SetupContext<Calenda
|
|
|
21
25
|
isHover: ref<boolean>(false),
|
|
22
26
|
hoveredCell: ref<number>(),
|
|
23
27
|
employeeId: ref<number>(),
|
|
28
|
+
sort: ref<string>(''),
|
|
29
|
+
tableBodyRef: ref<HTMLElement | null>(null),
|
|
24
30
|
};
|
|
25
31
|
|
|
26
32
|
const searchEmployee = useVModel(props, 'search', emit);
|
|
27
33
|
const selectedCell = useVModel(props, 'selectedCell', emit);
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
34
|
+
|
|
35
|
+
const getSortIcon = computed(() => {
|
|
36
|
+
if (!state.sort.value) {
|
|
37
|
+
return 'ph:caret-up-down-light';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return state.sort.value === 'asc' ? 'ph:arrow-up' : 'ph:arrow-down';
|
|
41
|
+
});
|
|
31
42
|
|
|
32
43
|
const startDate = computed(() => state.currentDate.value.startOf('week'));
|
|
33
44
|
|
|
@@ -63,64 +74,81 @@ export const useCalendar = (props: CalendarPropTypes, emit: SetupContext<Calenda
|
|
|
63
74
|
state.currentDate.value = state.currentDate.value.add(1, 'week');
|
|
64
75
|
};
|
|
65
76
|
|
|
77
|
+
const getFirstAndLastDayOfWeek = () => {
|
|
78
|
+
if (!state.currentDate.value) return;
|
|
79
|
+
|
|
80
|
+
const firstDayOfWeek = state.currentDate.value.startOf('week');
|
|
81
|
+
const lastDayOfWeek = state.currentDate.value.endOf('week');
|
|
82
|
+
|
|
83
|
+
emit('update:firstLastDayOfWeek', {
|
|
84
|
+
firstDay: firstDayOfWeek.format(state.dateFormat.value),
|
|
85
|
+
lastDay: lastDayOfWeek.format(state.dateFormat.value),
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
66
89
|
const goToToday = () => {
|
|
67
|
-
|
|
90
|
+
const today = dayjs();
|
|
91
|
+
const currentWeekStart = state.currentDate.value.startOf('week');
|
|
92
|
+
const currentWeekEnd = state.currentDate.value.endOf('week');
|
|
93
|
+
|
|
94
|
+
// Only update if today is not within the current week
|
|
95
|
+
if (!today.isBetween(currentWeekStart, currentWeekEnd, 'day', '[]')) {
|
|
96
|
+
state.currentDate.value = today;
|
|
97
|
+
}
|
|
68
98
|
};
|
|
69
99
|
|
|
70
|
-
const
|
|
100
|
+
const onCellClick = (selected: SelectedShift) => {
|
|
71
101
|
selectedCell.value = selected;
|
|
102
|
+
emit('onCellClick', selected);
|
|
72
103
|
};
|
|
73
104
|
|
|
74
105
|
const handleHover = (isHover: boolean, index: number, employeeId: number) => {
|
|
75
106
|
state.isHover.value = isHover;
|
|
76
|
-
state.hoveredCell.value = isHover ? index :
|
|
107
|
+
state.hoveredCell.value = isHover ? index : undefined;
|
|
77
108
|
state.employeeId.value = employeeId;
|
|
78
109
|
};
|
|
79
110
|
|
|
80
111
|
const showAddShift = (index: number, employeeId: number) => {
|
|
81
|
-
return
|
|
112
|
+
return (
|
|
113
|
+
state.hoveredCell.value === index &&
|
|
114
|
+
state.isHover.value &&
|
|
115
|
+
state.employeeId.value === employeeId &&
|
|
116
|
+
!hideAddButton.value
|
|
117
|
+
);
|
|
82
118
|
};
|
|
83
119
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
state.selectedCompany.value = foundCompany?.text ?? '';
|
|
88
|
-
selectedCompany.value = selected;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (filter === 'department') {
|
|
92
|
-
const foundCompany = departmentOptions.value.find((item) => item.value === selected);
|
|
93
|
-
state.selectedDepartment.value = foundCompany?.text ?? '';
|
|
94
|
-
selectedDepartment.value = selected;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (filter === 'branch') {
|
|
98
|
-
const foundCompany = branchOptions.value.find((item) => item.value === selected);
|
|
99
|
-
state.selectedBranch.value = foundCompany?.text ?? '';
|
|
100
|
-
selectedBranch.value = selected;
|
|
101
|
-
}
|
|
120
|
+
const handleSorting = () => {
|
|
121
|
+
state.sort.value = state.sort.value === 'desc' ? 'asc' : 'desc';
|
|
122
|
+
emit('update:sort', state.sort.value);
|
|
102
123
|
};
|
|
103
124
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
useInfiniteScroll(
|
|
126
|
+
state.tableBodyRef,
|
|
127
|
+
() => {
|
|
128
|
+
emit('loadMore');
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
distance: 50,
|
|
132
|
+
direction: 'bottom',
|
|
133
|
+
},
|
|
134
|
+
);
|
|
108
135
|
|
|
109
136
|
const getCalendarClasses = computed(() => {
|
|
110
137
|
const borderClasses = classNames(' spr-border spr-border-color-weak spr-border-solid');
|
|
111
138
|
const headerWrapper = classNames(
|
|
112
|
-
'spr-bg-color-weak spr-flex spr-w-full spr-items-center spr-justify-between spr-
|
|
139
|
+
'spr-bg-color-weak spr-flex spr-w-full spr-items-center spr-justify-between spr-p-size-spacing-sm',
|
|
113
140
|
);
|
|
114
141
|
|
|
115
142
|
const contentWrapper = classNames('spr-divide-color-weak spr-divide-x-0 spr-divide-y spr-divide-solid');
|
|
116
|
-
const calendarFilter = classNames('spr-grid spr-grid-cols-4 spr-gap-size-spacing-2xs spr-p-size-spacing-xs');
|
|
117
143
|
const calendarTable = classNames(
|
|
118
|
-
'spr-table spr-w-full spr-table-fixed spr-border-collapse spr-border-spacing-0 spr-
|
|
144
|
+
' spr-overflow-y-auto spr-h-full spr-table spr-w-full spr-table-fixed spr-border-collapse spr-border-spacing-0 spr-rounded-border',
|
|
119
145
|
);
|
|
120
146
|
const tableHeaderEmployeeName = classNames(
|
|
121
|
-
'spr-
|
|
147
|
+
'spr-sticky spr-left-0 spr-z-20 spr-background-color spr-body-xs-regular-medium spr-p-size-spacing-xs spr-text-left spr-overflow-hidden spr-h-full',
|
|
148
|
+
);
|
|
149
|
+
const tableHeader = classNames(
|
|
150
|
+
'spr-background-color spr-border-x-0 spr-border-y-0 spr-border-l spr-p-size-spacing-sm spr-text-center',
|
|
122
151
|
);
|
|
123
|
-
const tableHeader = classNames('spr-border-x-0 spr-border-y spr-border-l spr-p-size-spacing-sm spr-text-center');
|
|
124
152
|
const headerContent = classNames(
|
|
125
153
|
'spr-flex spr-flex-row spr-w-full spr-items-center spr-gap-size-spacing-3xs lg:spr-flex-col spr-overflow-hidden',
|
|
126
154
|
);
|
|
@@ -128,11 +156,16 @@ export const useCalendar = (props: CalendarPropTypes, emit: SetupContext<Calenda
|
|
|
128
156
|
'spr-font-size-400 spr-line-height-500 spr-letter-spacing-dense spr-flex spr-h-size-spacing-md spr-w-size-spacing-md spr-items-center spr-justify-center spr-rounded-full spr-font-main spr-font-normal',
|
|
129
157
|
);
|
|
130
158
|
|
|
159
|
+
getFirstAndLastDayOfWeek();
|
|
160
|
+
|
|
161
|
+
watch(state.searchTerm, (value, oldValue) => {
|
|
162
|
+
if (value === oldValue) return; // Prevent unnecessary updates
|
|
163
|
+
searchEmployee.value = value;
|
|
164
|
+
});
|
|
131
165
|
return {
|
|
132
166
|
borderClasses,
|
|
133
167
|
headerWrapper,
|
|
134
168
|
contentWrapper,
|
|
135
|
-
calendarFilter,
|
|
136
169
|
calendarTable,
|
|
137
170
|
tableHeaderEmployeeName,
|
|
138
171
|
tableHeader,
|
|
@@ -145,16 +178,17 @@ export const useCalendar = (props: CalendarPropTypes, emit: SetupContext<Calenda
|
|
|
145
178
|
weekDates,
|
|
146
179
|
weekRangeDisplay,
|
|
147
180
|
getCalendarClasses,
|
|
181
|
+
getSortIcon,
|
|
148
182
|
|
|
149
183
|
formatDate,
|
|
150
184
|
isToday,
|
|
151
185
|
prevWeek,
|
|
152
186
|
nextWeek,
|
|
153
187
|
goToToday,
|
|
154
|
-
|
|
188
|
+
onCellClick,
|
|
155
189
|
handleHover,
|
|
156
190
|
showAddShift,
|
|
157
|
-
|
|
191
|
+
handleSorting,
|
|
158
192
|
|
|
159
193
|
...state,
|
|
160
194
|
};
|
|
@@ -358,13 +358,17 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
358
358
|
};
|
|
359
359
|
// #endregion - Helper Methods
|
|
360
360
|
|
|
361
|
-
watch(
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
watch(
|
|
362
|
+
menuList,
|
|
363
|
+
() => {
|
|
364
|
+
localizedMenuList.value = [];
|
|
365
|
+
groupedMenuList.value = [];
|
|
364
366
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
setMenuList();
|
|
368
|
+
setPreSelectedItems();
|
|
369
|
+
},
|
|
370
|
+
{ deep: true },
|
|
371
|
+
);
|
|
368
372
|
|
|
369
373
|
onMounted(() => {
|
|
370
374
|
setMenuList();
|
|
@@ -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
|
+
};
|
|
@@ -147,9 +147,13 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
|
|
|
147
147
|
return selectOptions.value.filter((item) => item.text?.toString().toLowerCase().includes(query));
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
watch(
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
watch(
|
|
151
|
+
options,
|
|
152
|
+
() => {
|
|
153
|
+
processOptions();
|
|
154
|
+
},
|
|
155
|
+
{ deep: true },
|
|
156
|
+
);
|
|
153
157
|
|
|
154
158
|
// Search handler: always emit search-string, but only filter locally if local search is enabled
|
|
155
159
|
const handleSearch = () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtractPropTypes, PropType } from 'vue';
|
|
2
|
+
import { STEPPER_TYPE } from '../stepper';
|
|
2
3
|
|
|
3
4
|
export const STEP_STATES = ['pending', 'active', 'completed'] as const;
|
|
4
5
|
|
|
@@ -29,6 +30,13 @@ export const stepPropTypes = {
|
|
|
29
30
|
validator: (value: (typeof STEP_STATES)[number]) => STEP_STATES.includes(value),
|
|
30
31
|
default: 'pending',
|
|
31
32
|
},
|
|
33
|
+
/**
|
|
34
|
+
* @description Display type of stepper if displayed as compact (no solid bg color) or solid
|
|
35
|
+
*/
|
|
36
|
+
type: {
|
|
37
|
+
type: String as PropType<(typeof STEPPER_TYPE)[number]>,
|
|
38
|
+
validator: (value: (typeof STEPPER_TYPE)[number]) => STEPPER_TYPE.includes(value),
|
|
39
|
+
}
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
export const stepEmitTypes = {
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
<div :class="stepClasses.baseClass">
|
|
3
3
|
<!-- STEP NUMBER -->
|
|
4
4
|
<div :class="stepClasses.badgeClass">
|
|
5
|
-
<
|
|
5
|
+
<icon v-if="props.status === 'completed' && props.type === 'solid'" icon="ph:check-bold" width="14px" height="14px" class="spr-text-color-inverted-strong"/>
|
|
6
|
+
<span v-else :class="stepClasses.numberClass">
|
|
6
7
|
{{ props.number }}
|
|
7
8
|
</span>
|
|
8
9
|
</div>
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
<script setup lang="ts">
|
|
24
25
|
import { stepEmitTypes, stepPropTypes } from '@/components/stepper/step/step';
|
|
25
26
|
import { useStep } from '@/components/stepper/step/use-step';
|
|
27
|
+
import { Icon } from '@iconify/vue';
|
|
26
28
|
|
|
27
29
|
const props = defineProps(stepPropTypes);
|
|
28
30
|
const emit = defineEmits(stepEmitTypes);
|
|
@@ -16,19 +16,29 @@ interface StepClasses {
|
|
|
16
16
|
export const useStep = (props: StepPropTypes, emit: SetupContext<StepEmitTypes>['emit']) => {
|
|
17
17
|
|
|
18
18
|
const stepClasses: ComputedRef<StepClasses> = computed(() => {
|
|
19
|
-
const baseClass = classNames(
|
|
19
|
+
const baseClass = classNames(
|
|
20
|
+
'spr-flex spr-gap-2 spr-items-top',
|
|
21
|
+
{
|
|
22
|
+
'spr-p-size-spacing-3xs spr-rounded-border-radius-lg': props.type === 'solid',
|
|
23
|
+
'spr-background-color-brand-weak': props.status === 'active' && props.type === 'solid',
|
|
24
|
+
'spr-opacity-60': props.status === 'completed' && props.type === 'solid'
|
|
25
|
+
}
|
|
26
|
+
);
|
|
20
27
|
|
|
21
28
|
// Usage of prop.status ensures reactivity instead of destructuring props
|
|
22
29
|
const badgeClass = classNames(
|
|
23
|
-
'spr-flex spr-items-center spr-justify-center spr-rounded-border-radius-full spr-h-6 spr-w-6 spr-border spr-border-solid
|
|
30
|
+
'spr-flex spr-items-center spr-justify-center spr-rounded-border-radius-full spr-h-6 spr-w-6 spr-border spr-border-solid',
|
|
24
31
|
{
|
|
25
|
-
'spr-
|
|
32
|
+
'spr-border-2': props.type !== 'solid',
|
|
33
|
+
'spr-border-1': props.type === 'solid',
|
|
34
|
+
'spr-background-color-brand-base spr-border-color-brand-base': props.status === 'active',
|
|
26
35
|
'spr-border-mushroom-300': props.status === 'pending',
|
|
27
36
|
'spr-border-kangkong-700': props.status === 'completed',
|
|
37
|
+
'spr-background-color-brand-base': props.status === 'completed' && props.type === 'solid'
|
|
28
38
|
}
|
|
29
39
|
);
|
|
30
40
|
|
|
31
|
-
const numberClass = classNames('spr-
|
|
41
|
+
const numberClass = classNames('spr-body-md-regular-medium',
|
|
32
42
|
{
|
|
33
43
|
'spr-text-white-50': props.status === 'active',
|
|
34
44
|
'spr-text-mushroom-600': props.status === 'pending',
|
|
@@ -36,18 +46,20 @@ export const useStep = (props: StepPropTypes, emit: SetupContext<StepEmitTypes>[
|
|
|
36
46
|
}
|
|
37
47
|
);
|
|
38
48
|
|
|
39
|
-
const textContainerClass = classNames('spr-flex spr-flex-col spr-
|
|
49
|
+
const textContainerClass = classNames('spr-flex spr-flex-col spr-mt-[2px]');
|
|
40
50
|
|
|
41
|
-
const labelClass = classNames('spr-
|
|
51
|
+
const labelClass = classNames('spr-body-md-regular spr-whitespace-nowrap',
|
|
42
52
|
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
'spr-text-kangkong-700 !spr-body-md-regular-medium': props.status === 'active' && props.type === 'compact',
|
|
54
|
+
'spr-text-mushroom-600': props.status === 'pending',
|
|
55
|
+
'spr-text-mushroom-950': props.status === 'completed',
|
|
46
56
|
}
|
|
47
57
|
);
|
|
48
58
|
|
|
49
59
|
// Absolute since the sample in the figma is a hanging text for description
|
|
50
|
-
const descriptionClass = classNames(
|
|
60
|
+
const descriptionClass = classNames(
|
|
61
|
+
'spr-body-sm-regular spr-text-color-supporting spr-whitespace-nowrap',
|
|
62
|
+
);
|
|
51
63
|
|
|
52
64
|
return {
|
|
53
65
|
baseClass,
|