design-system-next 2.26.3 → 2.26.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/design-system-next.es.d.ts +144 -186
- package/dist/design-system-next.es.js +7057 -6955
- 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-item/list-item.ts +8 -1
- package/src/components/list/list-item/list-item.vue +12 -1
- package/src/components/list/list-item/use-list-item.ts +5 -1
- package/src/components/list/list.ts +14 -2
- package/src/components/list/list.vue +38 -7
- package/src/components/list/use-list.ts +42 -2
- package/src/components/select/select-multiple/select-multiple.ts +12 -0
- package/src/components/select/select-multiple/select-multiple.vue +3 -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.7",
|
|
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
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { PropType, ExtractPropTypes } from 'vue';
|
|
2
2
|
import type { MenuListType } from '../list';
|
|
3
|
-
|
|
4
3
|
export const listItemPropTypes = {
|
|
5
4
|
item: {
|
|
6
5
|
type: Object as PropType<MenuListType>,
|
|
@@ -50,6 +49,14 @@ export const listItemPropTypes = {
|
|
|
50
49
|
type: Boolean,
|
|
51
50
|
default: false,
|
|
52
51
|
},
|
|
52
|
+
avatarVariant: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: '',
|
|
55
|
+
},
|
|
56
|
+
avatarSource: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
export const listItemEmitTypes = {
|
|
@@ -46,6 +46,16 @@
|
|
|
46
46
|
>
|
|
47
47
|
<Icon class="spr-text-xl" :icon="iconName" />
|
|
48
48
|
</span>
|
|
49
|
+
|
|
50
|
+
<span v-if="hasAvatar">
|
|
51
|
+
<spr-avatar
|
|
52
|
+
size="sm"
|
|
53
|
+
:initial="props.avatarSource || listItem.text"
|
|
54
|
+
:variant="props.avatarVariant"
|
|
55
|
+
:src="props.avatarSource"
|
|
56
|
+
/>
|
|
57
|
+
</span>
|
|
58
|
+
|
|
49
59
|
<div
|
|
50
60
|
:class="[
|
|
51
61
|
'spr-flex spr-flex-auto spr-flex-col spr-justify-start',
|
|
@@ -106,6 +116,7 @@ import { Icon } from '@iconify/vue';
|
|
|
106
116
|
import SprCheckbox from '@/components/checkbox/checkbox.vue';
|
|
107
117
|
import SprRadio from '@/components/radio/radio.vue';
|
|
108
118
|
import SprLozenge from '@/components/lozenge/lozenge.vue';
|
|
119
|
+
import SprAvatar from '@/components/avatar/avatar.vue';
|
|
109
120
|
|
|
110
121
|
import { LOZENGE_TONE } from '@/components/lozenge/lozenge';
|
|
111
122
|
|
|
@@ -115,5 +126,5 @@ import { useListItem } from './use-list-item';
|
|
|
115
126
|
const props = defineProps(listItemPropTypes);
|
|
116
127
|
const emit = defineEmits(listItemEmitTypes);
|
|
117
128
|
|
|
118
|
-
const { listItem, hasIcon, iconName, iconClasses, hasSublevels, showLozengeMode } = useListItem(props, emit);
|
|
129
|
+
const { listItem, hasIcon, iconName, iconClasses, hasSublevels, showLozengeMode, hasAvatar } = useListItem(props, emit);
|
|
119
130
|
</script>
|
|
@@ -14,13 +14,16 @@ export function useListItem(
|
|
|
14
14
|
iconClasses: ComputedRef<string>;
|
|
15
15
|
hasSublevels: ComputedRef<boolean>;
|
|
16
16
|
showLozengeMode: ComputedRef<boolean>;
|
|
17
|
+
hasAvatar: ComputedRef<boolean>;
|
|
17
18
|
} {
|
|
18
|
-
const { item, itemIcon, itemIconTone, itemIconFill, lozenge } = toRefs(props);
|
|
19
|
+
const { item, itemIcon, itemIconTone, itemIconFill, lozenge, avatarVariant } = toRefs(props);
|
|
19
20
|
|
|
20
21
|
const listItem = computed(() => item?.value);
|
|
21
22
|
|
|
22
23
|
const hasIcon = computed(() => !!(itemIcon.value || item?.value!.icon));
|
|
23
24
|
|
|
25
|
+
const hasAvatar = computed(() => !!(avatarVariant.value && !hasIcon.value));
|
|
26
|
+
|
|
24
27
|
const iconName = computed(() => itemIcon.value || item?.value!.icon || '');
|
|
25
28
|
|
|
26
29
|
const iconClasses = computed(() => {
|
|
@@ -64,6 +67,7 @@ export function useListItem(
|
|
|
64
67
|
return {
|
|
65
68
|
listItem,
|
|
66
69
|
hasIcon,
|
|
70
|
+
hasAvatar,
|
|
67
71
|
iconName,
|
|
68
72
|
iconClasses,
|
|
69
73
|
hasSublevels,
|
|
@@ -125,8 +125,20 @@ 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
|
+
},
|
|
134
|
+
avatarVariant: {
|
|
135
|
+
type: String,
|
|
136
|
+
default: '',
|
|
137
|
+
},
|
|
138
|
+
avatarSource: {
|
|
139
|
+
type: String,
|
|
140
|
+
default: '',
|
|
141
|
+
},
|
|
130
142
|
};
|
|
131
143
|
|
|
132
144
|
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
|
|
|
@@ -46,6 +70,8 @@
|
|
|
46
70
|
:item-icon-fill="props.itemIconFill"
|
|
47
71
|
:disabled-unselected-items="props.disabledUnselectedItems"
|
|
48
72
|
:radio-list="props.radioList"
|
|
73
|
+
:avatar-variant="props.avatarVariant"
|
|
74
|
+
:avatar-source="props.avatarSource"
|
|
49
75
|
@select="handleSelectedItem(item)"
|
|
50
76
|
/>
|
|
51
77
|
<div v-if="props.infiniteScrollLoader" class="spr-flex spr-items-center spr-justify-center spr-p-2">
|
|
@@ -74,6 +100,8 @@
|
|
|
74
100
|
:item-icon-fill="props.itemIconFill"
|
|
75
101
|
:disabled-unselected-items="props.disabledUnselectedItems"
|
|
76
102
|
:radio-list="props.radioList"
|
|
103
|
+
:avatar-variant="props.avatarVariant"
|
|
104
|
+
:avatar-source="props.avatarSource"
|
|
77
105
|
@select="handleSelectedItem(item)"
|
|
78
106
|
/>
|
|
79
107
|
<div v-if="props.infiniteScrollLoader" class="spr-flex spr-items-center spr-justify-center spr-p-2">
|
|
@@ -103,6 +131,7 @@
|
|
|
103
131
|
import { Icon } from '@iconify/vue';
|
|
104
132
|
|
|
105
133
|
import SprInputSearch from '@/components/input/input-search/input-search.vue';
|
|
134
|
+
import SprButton from '@/components/button/button.vue';
|
|
106
135
|
import ListItem from './list-item/list-item.vue';
|
|
107
136
|
|
|
108
137
|
import { listPropTypes, listEmitTypes } from './list';
|
|
@@ -119,9 +148,11 @@ const {
|
|
|
119
148
|
localizedMenuList,
|
|
120
149
|
groupedMenuList,
|
|
121
150
|
hasGroupedItems,
|
|
151
|
+
hasSelectedItems,
|
|
122
152
|
isItemSelected,
|
|
123
153
|
getListItemClasses,
|
|
124
154
|
handleSelectedItem,
|
|
155
|
+
handleSelectAll,
|
|
125
156
|
handleSearchKeyup,
|
|
126
157
|
} = useList(props, emit);
|
|
127
158
|
</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
|
};
|
|
@@ -209,6 +209,18 @@ export const multiSelectPropTypes = {
|
|
|
209
209
|
type: Boolean,
|
|
210
210
|
default: false,
|
|
211
211
|
},
|
|
212
|
+
allowSelectAll: {
|
|
213
|
+
type: Boolean,
|
|
214
|
+
default: false,
|
|
215
|
+
},
|
|
216
|
+
avatarVariant: {
|
|
217
|
+
type: String,
|
|
218
|
+
default: '',
|
|
219
|
+
},
|
|
220
|
+
avatarSource: {
|
|
221
|
+
type: String,
|
|
222
|
+
default: '',
|
|
223
|
+
},
|
|
212
224
|
};
|
|
213
225
|
|
|
214
226
|
export const multiSelectEmitTypes = {
|
|
@@ -165,6 +165,9 @@
|
|
|
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"
|
|
169
|
+
:avatar-variant="props.avatarVariant"
|
|
170
|
+
:avatar-source="props.avatarSource"
|
|
168
171
|
multi-select
|
|
169
172
|
@update:model-value="handleMultiSelectedItem"
|
|
170
173
|
@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
|