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.
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.3",
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
- <spr-button id="calendar-today" variant="secondary" size="large" @click="goToToday"> Today </spr-button>
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="cell"
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 && state.isHover.value && state.employeeId.value === employeeId && slots.cell
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 v-if="props.searchableMenu || props.displayListItemSelected">
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
- <span
14
- v-if="props.supportingDisplayText || props.displayListItemSelected"
15
- class="spr-label-sm-medium spr-text-color-base spr-block"
16
- >
17
- {{ props.supportingDisplayText || `${selectedItems.length} Selected` }}
18
- </span>
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
- 'spr-border-color-weak spr-border spr-border-x-0 spr-border-b spr-border-t-0 spr-border-solid',
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
- return { headerClasses, listItemClasses };
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,10 @@ export const multiSelectPropTypes = {
209
209
  type: Boolean,
210
210
  default: false,
211
211
  },
212
+ allowSelectAll: {
213
+ type: Boolean,
214
+ default: false,
215
+ },
212
216
  };
213
217
 
214
218
  export const multiSelectEmitTypes = {
@@ -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
- return values.some((val) => {
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