design-system-next 2.26.3 → 2.26.6
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.es.d.ts +144 -186
- package/dist/design-system-next.es.js +5746 -5683
- package/dist/design-system-next.es.js.gz +0 -0
- package/dist/design-system-next.umd.js +11 -11
- package/dist/design-system-next.umd.js.gz +0 -0
- package/package.json +1 -2
- package/src/components/calendar/calendar.vue +16 -2
- package/src/components/calendar/use-calendar.ts +4 -1
- package/src/components/list/list.ts +6 -2
- package/src/components/list/list.vue +34 -7
- package/src/components/list/use-list.ts +42 -2
- package/src/components/select/select-multiple/select-multiple.ts +4 -0
- package/src/components/select/select-multiple/select-multiple.vue +1 -0
- package/src/components/select/select-multiple/use-select-multiple.ts +12 -1
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design-system-next",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.26.
|
|
4
|
+
"version": "2.26.6",
|
|
5
5
|
"main": "./dist/design-system-next.umd.js",
|
|
6
6
|
"module": "./dist/design-system-next.es.js",
|
|
7
7
|
"types": "./dist/design-system-next.es.d.ts",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"docs:preview": "vitepress preview docs",
|
|
47
47
|
"test:ui": "npx playwright test --ui --config=playwright-ct.config.ts",
|
|
48
48
|
"test:components": "npx playwright test --config=playwright-ct.config.ts --reporter=line",
|
|
49
|
-
"test:component": "npx playwright test --config=playwright-ct.config.ts --reporter=line",
|
|
50
49
|
"clean": "rm -rf node_modules dist coverage package-lock.json && npm install"
|
|
51
50
|
},
|
|
52
51
|
"dependencies": {
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
<div class="spr-heading-xs">{{ weekRangeDisplay }}</div>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<
|
|
18
|
+
<div class="spr-flex spr-items-center spr-justify-center spr-gap-size-spacing-3xs">
|
|
19
|
+
<spr-button id="calendar-today" variant="secondary" size="large" @click="goToToday"> Today </spr-button>
|
|
20
|
+
<slot name="headerActions" />
|
|
21
|
+
</div>
|
|
19
22
|
</div>
|
|
20
23
|
<!-- Filters -->
|
|
21
24
|
<slot name="filter" />
|
|
@@ -159,9 +162,20 @@
|
|
|
159
162
|
</div>
|
|
160
163
|
</section>
|
|
161
164
|
|
|
165
|
+
<section>
|
|
166
|
+
<slot
|
|
167
|
+
name="fixedCell"
|
|
168
|
+
:details="{
|
|
169
|
+
employeeId: employee.id,
|
|
170
|
+
date: formatDate(date, dateFormat),
|
|
171
|
+
shift: employee.schedule[formatDate(date, dateFormat)],
|
|
172
|
+
}"
|
|
173
|
+
/>
|
|
174
|
+
</section>
|
|
175
|
+
|
|
162
176
|
<section v-if="showCustomSlot(index, employee.id)">
|
|
163
177
|
<slot
|
|
164
|
-
name="
|
|
178
|
+
name="hoverCell"
|
|
165
179
|
:details="{
|
|
166
180
|
employeeId: employee.id,
|
|
167
181
|
date: formatDate(date, dateFormat),
|
|
@@ -119,7 +119,10 @@ export const useCalendar = (props: CalendarPropTypes, emit: SetupContext<Calenda
|
|
|
119
119
|
|
|
120
120
|
const showCustomSlot = (index: number, employeeId: number) => {
|
|
121
121
|
return (
|
|
122
|
-
state.hoveredCell.value === index &&
|
|
122
|
+
state.hoveredCell.value === index &&
|
|
123
|
+
state.isHover.value &&
|
|
124
|
+
state.employeeId.value === employeeId &&
|
|
125
|
+
slots.hoverCell
|
|
123
126
|
);
|
|
124
127
|
};
|
|
125
128
|
|
|
@@ -125,8 +125,12 @@ export const listPropTypes = {
|
|
|
125
125
|
},
|
|
126
126
|
allowDeselect: {
|
|
127
127
|
type: Boolean,
|
|
128
|
-
default: false
|
|
129
|
-
}
|
|
128
|
+
default: false,
|
|
129
|
+
},
|
|
130
|
+
allowSelectAll: {
|
|
131
|
+
type: Boolean,
|
|
132
|
+
default: false,
|
|
133
|
+
},
|
|
130
134
|
};
|
|
131
135
|
|
|
132
136
|
export const listEmitTypes = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="spr-font-main">
|
|
3
3
|
<!-- Header Section -->
|
|
4
|
-
<template
|
|
4
|
+
<template
|
|
5
|
+
v-if="props.searchableMenu || props.displayListItemSelected || (props.multiSelect && props.allowSelectAll)"
|
|
6
|
+
>
|
|
5
7
|
<div :class="listClasses.headerClasses" :style="stickyOffsetStyle">
|
|
6
8
|
<spr-input-search
|
|
7
9
|
v-if="props.searchableMenu"
|
|
@@ -10,12 +12,34 @@
|
|
|
10
12
|
autocomplete="off"
|
|
11
13
|
@keyup="handleSearchKeyup"
|
|
12
14
|
/>
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
<slot name="list-controls">
|
|
16
|
+
<div
|
|
17
|
+
v-if="
|
|
18
|
+
props.supportingDisplayText ||
|
|
19
|
+
props.displayListItemSelected ||
|
|
20
|
+
(props.multiSelect && props.allowSelectAll)
|
|
21
|
+
"
|
|
22
|
+
:class="listClasses.listControlsClasses"
|
|
23
|
+
>
|
|
24
|
+
<span
|
|
25
|
+
v-if="props.supportingDisplayText || props.displayListItemSelected"
|
|
26
|
+
class="spr-label-sm-medium spr-text-color-base spr-block"
|
|
27
|
+
>
|
|
28
|
+
{{ props.supportingDisplayText || `${selectedItems.length} Selected` }}
|
|
29
|
+
</span>
|
|
30
|
+
|
|
31
|
+
<spr-button
|
|
32
|
+
v-if="props.multiSelect && props.allowSelectAll"
|
|
33
|
+
id="select-all-button"
|
|
34
|
+
:class="{ 'spr-ml-auto': true }"
|
|
35
|
+
variant="secondary"
|
|
36
|
+
size="small"
|
|
37
|
+
@click="handleSelectAll"
|
|
38
|
+
>
|
|
39
|
+
{{ hasSelectedItems ? 'Unselect All' : 'Select All' }}
|
|
40
|
+
</spr-button>
|
|
41
|
+
</div>
|
|
42
|
+
</slot>
|
|
19
43
|
</div>
|
|
20
44
|
</template>
|
|
21
45
|
|
|
@@ -103,6 +127,7 @@
|
|
|
103
127
|
import { Icon } from '@iconify/vue';
|
|
104
128
|
|
|
105
129
|
import SprInputSearch from '@/components/input/input-search/input-search.vue';
|
|
130
|
+
import SprButton from '@/components/button/button.vue';
|
|
106
131
|
import ListItem from './list-item/list-item.vue';
|
|
107
132
|
|
|
108
133
|
import { listPropTypes, listEmitTypes } from './list';
|
|
@@ -119,9 +144,11 @@ const {
|
|
|
119
144
|
localizedMenuList,
|
|
120
145
|
groupedMenuList,
|
|
121
146
|
hasGroupedItems,
|
|
147
|
+
hasSelectedItems,
|
|
122
148
|
isItemSelected,
|
|
123
149
|
getListItemClasses,
|
|
124
150
|
handleSelectedItem,
|
|
151
|
+
handleSelectAll,
|
|
125
152
|
handleSearchKeyup,
|
|
126
153
|
} = useList(props, emit);
|
|
127
154
|
</script>
|
|
@@ -9,6 +9,8 @@ import type { ListPropTypes, ListEmitTypes, MenuListType, GroupedMenuListType }
|
|
|
9
9
|
interface ListClasses {
|
|
10
10
|
headerClasses: string;
|
|
11
11
|
listItemClasses: string;
|
|
12
|
+
borderClasses: string;
|
|
13
|
+
listControlsClasses: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>['emit']) => {
|
|
@@ -23,13 +25,17 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
23
25
|
disabledUnselectedItems,
|
|
24
26
|
stickySearchOffset,
|
|
25
27
|
allowDeselect,
|
|
28
|
+
allowSelectAll,
|
|
26
29
|
} = toRefs(props);
|
|
27
30
|
|
|
28
31
|
const listClasses: ComputedRef<ListClasses> = computed(() => {
|
|
32
|
+
const borderClasses = classNames('spr-border-color-weak spr-border spr-border-solid');
|
|
33
|
+
|
|
29
34
|
const headerClasses = classNames(
|
|
30
35
|
'spr-sticky spr-z-20',
|
|
31
36
|
'spr-grid spr-gap-3 spr-bg-white-50 spr-px-size-spacing-3xs spr-py-size-spacing-2xs',
|
|
32
|
-
|
|
37
|
+
borderClasses,
|
|
38
|
+
'spr-border-x-0 spr-border-b spr-border-t-0',
|
|
33
39
|
);
|
|
34
40
|
|
|
35
41
|
const listItemClasses = classNames(
|
|
@@ -39,7 +45,9 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
39
45
|
'active:spr-background-color-single-active active:spr-scale-[.98]',
|
|
40
46
|
);
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
const listControlsClasses = classNames('spr-flex spr-w-full spr-items-center');
|
|
49
|
+
|
|
50
|
+
return { headerClasses, listItemClasses, borderClasses, listControlsClasses };
|
|
43
51
|
});
|
|
44
52
|
|
|
45
53
|
const stickyOffsetStyle = computed(() => ({
|
|
@@ -640,6 +648,36 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
640
648
|
event.preventDefault();
|
|
641
649
|
};
|
|
642
650
|
|
|
651
|
+
// Computed property to check if any items are selected
|
|
652
|
+
const hasSelectedItems = computed(() => {
|
|
653
|
+
if (!multiSelect.value || !allowSelectAll.value) return false;
|
|
654
|
+
return selectedItems.value.length > 0;
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Function to handle select/unselect all items
|
|
658
|
+
const handleSelectAll = () => {
|
|
659
|
+
if (!multiSelect.value || !allowSelectAll.value) return;
|
|
660
|
+
|
|
661
|
+
const currentItems = hasGroupedItems.value
|
|
662
|
+
? groupedMenuList.value.flatMap((group) => group.items)
|
|
663
|
+
: localizedMenuList.value;
|
|
664
|
+
|
|
665
|
+
// Filter out disabled items
|
|
666
|
+
const enabledItems = currentItems.filter((item) => !item.disabled);
|
|
667
|
+
|
|
668
|
+
if (hasSelectedItems.value) {
|
|
669
|
+
// If any items are selected, unselect all items completely
|
|
670
|
+
selectedItems.value = [];
|
|
671
|
+
// Also clear any preserved or API selected items
|
|
672
|
+
apiSelectedList.value = [];
|
|
673
|
+
emit('update:modelValue', []);
|
|
674
|
+
} else {
|
|
675
|
+
// If no items are selected, select all enabled items
|
|
676
|
+
selectedItems.value = [...enabledItems];
|
|
677
|
+
emit('update:modelValue', [...enabledItems]);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
643
681
|
return {
|
|
644
682
|
listClasses,
|
|
645
683
|
stickyOffsetStyle,
|
|
@@ -650,10 +688,12 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
650
688
|
apiSelectedList,
|
|
651
689
|
isParentMenu,
|
|
652
690
|
hasGroupedItems,
|
|
691
|
+
hasSelectedItems,
|
|
653
692
|
isItemSelected,
|
|
654
693
|
getListItemClasses,
|
|
655
694
|
handleSearch,
|
|
656
695
|
handleSelectedItem,
|
|
696
|
+
handleSelectAll,
|
|
657
697
|
trackNewlySelectedItems,
|
|
658
698
|
handleSearchKeyup,
|
|
659
699
|
};
|
|
@@ -165,6 +165,7 @@
|
|
|
165
165
|
:display-list-item-selected="props.displayListItemSelected"
|
|
166
166
|
:disabled-local-search="props.disabledLocalSearch"
|
|
167
167
|
:disabled-unselected-items="props.disabledUnselectedItems"
|
|
168
|
+
:allow-select-all="props.allowSelectAll"
|
|
168
169
|
multi-select
|
|
169
170
|
@update:model-value="handleMultiSelectedItem"
|
|
170
171
|
@get-single-selected-item="emit('get-single-selected-item', $event)"
|
|
@@ -268,8 +268,9 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
// Always keep multiSelectedListItems in sync with selected values
|
|
271
|
+
const seenValues = new Set<string>();
|
|
271
272
|
multiSelectedListItems.value = multiSelectOptions.value.filter((item) => {
|
|
272
|
-
|
|
273
|
+
const isMatch = values.some((val) => {
|
|
273
274
|
let itemVal = item.value;
|
|
274
275
|
let valToCompare = val;
|
|
275
276
|
|
|
@@ -292,6 +293,16 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
292
293
|
}
|
|
293
294
|
return itemVal == valToCompare;
|
|
294
295
|
});
|
|
296
|
+
|
|
297
|
+
if (isMatch) {
|
|
298
|
+
const itemKey = typeof item.value === 'object' ? JSON.stringify(item.value) : String(item.value);
|
|
299
|
+
if (seenValues.has(itemKey)) {
|
|
300
|
+
return false; // Skip duplicates
|
|
301
|
+
}
|
|
302
|
+
seenValues.add(itemKey);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return isMatch;
|
|
295
306
|
});
|
|
296
307
|
|
|
297
308
|
// Determine input text based on whether count-only mode is enabled
|