design-system-next 2.26.2 → 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.
@@ -1,60 +1,60 @@
1
- import type { PropType, ExtractPropTypes } from 'vue';
2
- import type { MenuListType } from '../list';
3
-
4
- export const listItemPropTypes = {
5
- item: {
6
- type: Object as PropType<MenuListType>,
7
- required: true,
8
- },
9
- isSelected: {
10
- type: Boolean,
11
- required: true,
12
- },
13
- classes: {
14
- type: [String, Array, Object] as PropType<string | string[] | Record<string, boolean>>,
15
- required: true,
16
- },
17
- multiSelect: {
18
- type: Boolean,
19
- default: false,
20
- },
21
- lozenge: {
22
- type: Boolean,
23
- default: false,
24
- },
25
- ladderized: {
26
- type: Boolean,
27
- default: false,
28
- },
29
- noCheck: {
30
- type: Boolean,
31
- default: false,
32
- },
33
- itemIcon: {
34
- type: String,
35
- default: '',
36
- },
37
- itemIconTone: {
38
- type: String,
39
- default: 'plain',
40
- },
41
- itemIconFill: {
42
- type: Boolean,
43
- default: false,
44
- },
45
- disabledUnselectedItems: {
46
- type: Boolean,
47
- default: false,
48
- },
49
- radioList: {
50
- type: Boolean,
51
- default: false,
52
- },
53
- };
54
-
55
- export const listItemEmitTypes = {
56
- select: () => true,
57
- };
58
-
59
- export type ListItemPropTypes = ExtractPropTypes<typeof listItemPropTypes>;
60
- export type ListItemEmitTypes = typeof listItemEmitTypes;
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+ import type { MenuListType } from '../list';
3
+
4
+ export const listItemPropTypes = {
5
+ item: {
6
+ type: Object as PropType<MenuListType>,
7
+ required: true,
8
+ },
9
+ isSelected: {
10
+ type: Boolean,
11
+ required: true,
12
+ },
13
+ classes: {
14
+ type: [String, Array, Object] as PropType<string | string[] | Record<string, boolean>>,
15
+ required: true,
16
+ },
17
+ multiSelect: {
18
+ type: Boolean,
19
+ default: false,
20
+ },
21
+ lozenge: {
22
+ type: Boolean,
23
+ default: false,
24
+ },
25
+ ladderized: {
26
+ type: Boolean,
27
+ default: false,
28
+ },
29
+ noCheck: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ itemIcon: {
34
+ type: String,
35
+ default: '',
36
+ },
37
+ itemIconTone: {
38
+ type: String,
39
+ default: 'plain',
40
+ },
41
+ itemIconFill: {
42
+ type: Boolean,
43
+ default: false,
44
+ },
45
+ disabledUnselectedItems: {
46
+ type: Boolean,
47
+ default: false,
48
+ },
49
+ radioList: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ };
54
+
55
+ export const listItemEmitTypes = {
56
+ select: () => true,
57
+ };
58
+
59
+ export type ListItemPropTypes = ExtractPropTypes<typeof listItemPropTypes>;
60
+ export type ListItemEmitTypes = typeof listItemEmitTypes;
@@ -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(() => ({
@@ -375,7 +383,7 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
375
383
  const getListItemClasses = (item: MenuListType) => ({
376
384
  [listClasses.value.listItemClasses]: !item.disabled && !(disabledUnselectedItems.value && !isItemSelected(item)),
377
385
  'spr-background-color-single-active': isItemSelected(item) && !item.disabled && !noCheck.value,
378
- 'spr-cursor-not-allowed spr-flex spr-items-center spr-gap-1.5 spr-rounded-lg':
386
+ 'spr-cursor-not-allowed spr-flex spr-items-center spr-justify-between spr-gap-1.5 spr-rounded-lg':
379
387
  item.disabled || (disabledUnselectedItems.value && !isItemSelected(item)),
380
388
  'spr-p-size-spacing-3xs': !props.lozenge,
381
389
  'spr-py-size-spacing-3xs spr-px-size-spacing-4xs': props.lozenge,
@@ -527,13 +535,13 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
527
535
  } else {
528
536
  handleSingleSelect(item);
529
537
  }
530
- }
538
+ }
531
539
  };
532
540
 
533
541
  const handleDeselect = (item: MenuListType) => {
534
542
  if (selectedItems.value.length === 0 || !isItemSelected(item)) {
535
- selectedItems.value = [item];
536
- emit('get-single-selected-item', item);
543
+ selectedItems.value = [item];
544
+ emit('get-single-selected-item', item);
537
545
  } else {
538
546
  selectedItems.value = [];
539
547
  emit('get-single-deselected-item', item);
@@ -542,8 +550,8 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
542
550
 
543
551
  const handleSingleSelect = (item: MenuListType) => {
544
552
  selectedItems.value = [item];
545
- if (item.onClickFn) item.onClickFn();
546
- emit('get-single-selected-item', item);
553
+ if (item.onClickFn) item.onClickFn();
554
+ emit('get-single-selected-item', item);
547
555
  };
548
556
  // #endregion - Helper Methods
549
557
 
@@ -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
  };
@@ -1,65 +1,74 @@
1
- import type { PropType, ExtractPropTypes } from 'vue';
2
-
3
- export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
4
-
5
- export interface RadioOption {
6
- text: string;
7
- value: string | number | boolean;
8
- disabled?: boolean;
9
- }
10
-
11
- export const radioGroupedPropTypes = {
12
- id: {
13
- type: String,
14
- required: true,
15
- },
16
- modelValue: {
17
- type: [String, Number, Boolean],
18
- },
19
- name: {
20
- type: String,
21
- required: true,
22
- },
23
- options: {
24
- type: Array as PropType<RadioOption[]>,
25
- required: true,
26
- default: () => [],
27
- },
28
- disabled: {
29
- type: Boolean,
30
- default: false,
31
- },
32
- description: {
33
- type: String,
34
- },
35
- bordered: {
36
- type: Boolean,
37
- default: false,
38
- },
39
- displayHelper: {
40
- type: Boolean,
41
- default: false,
42
- },
43
- helperIcon: {
44
- type: String,
45
- default: null,
46
- },
47
- helperText: {
48
- type: String,
49
- default: '',
50
- },
51
- error: {
52
- type: Boolean,
53
- default: false,
54
- },
55
- horizontalAlign: {
56
- type: String as PropType<'left' | 'center' | 'right'>,
57
- default: 'left',
58
- },
59
- };
60
-
61
- export const radioGroupedEmitTypes = ['update:modelValue'];
62
-
63
- export type RadioGroupedPropTypes = ExtractPropTypes<typeof radioGroupedPropTypes>;
64
-
65
- export type RadioGroupedEmitTypes = typeof radioGroupedEmitTypes;
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+
3
+ export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
4
+
5
+ export interface RadioOption {
6
+ text: string;
7
+ value: string | number | boolean;
8
+ disabled?: boolean;
9
+ description?: string;
10
+ }
11
+
12
+ export const radioGroupedPropTypes = {
13
+ id: {
14
+ type: String,
15
+ required: true,
16
+ },
17
+ modelValue: {
18
+ type: [String, Number, Boolean],
19
+ },
20
+ name: {
21
+ type: String,
22
+ required: true,
23
+ },
24
+ options: {
25
+ type: Array as PropType<RadioOption[]>,
26
+ required: true,
27
+ default: () => [],
28
+ },
29
+ disabled: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ description: {
34
+ type: String,
35
+ },
36
+ fullWidth: {
37
+ type: Boolean,
38
+ default: false,
39
+ },
40
+ bordered: {
41
+ type: Boolean,
42
+ default: false,
43
+ },
44
+ displayHelper: {
45
+ type: Boolean,
46
+ default: false,
47
+ },
48
+ helperIcon: {
49
+ type: String,
50
+ default: null,
51
+ },
52
+ helperText: {
53
+ type: String,
54
+ default: '',
55
+ },
56
+ error: {
57
+ type: Boolean,
58
+ default: false,
59
+ },
60
+ horizontalAlign: {
61
+ type: String as PropType<'left' | 'center' | 'right'>,
62
+ default: 'left',
63
+ },
64
+ choiceBox: {
65
+ type: Boolean,
66
+ default: false,
67
+ },
68
+ };
69
+
70
+ export const radioGroupedEmitTypes = ['update:modelValue'];
71
+
72
+ export type RadioGroupedPropTypes = ExtractPropTypes<typeof radioGroupedPropTypes>;
73
+
74
+ export type RadioGroupedEmitTypes = typeof radioGroupedEmitTypes;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div :class="['spr-relative']">
2
+ <div :class="['spr-relative', { 'spr-w-full': props.choiceBox || props.fullWidth }]">
3
3
  <div :class="radioGroupedClasses.containerClasses">
4
4
  <spr-radio
5
5
  v-for="(option, index) in renderOptions()"
@@ -9,6 +9,9 @@
9
9
  :name="props.name"
10
10
  :value="option.value"
11
11
  :disabled="isOptionDisabled(option)"
12
+ :choice-box="props.choiceBox"
13
+ :full-width="props.fullWidth || props.choiceBox"
14
+ :description="option.description"
12
15
  >
13
16
  {{ option.text }}
14
17
  </spr-radio>
@@ -1,62 +1,63 @@
1
- import { toRefs, computed, ComputedRef } from 'vue';
2
- import { useVModel } from '@vueuse/core';
3
-
4
- import classNames from 'classnames';
5
-
6
- import type { SetupContext } from 'vue';
7
- import type { RadioGroupedPropTypes, RadioGroupedEmitTypes, RadioOption } from './radio-grouped';
8
-
9
- interface RadioGroupedClasses {
10
- containerClasses: string;
11
- helperClasses: string;
12
- }
13
-
14
- export const useRadioGrouped = (props: RadioGroupedPropTypes, emit: SetupContext<RadioGroupedEmitTypes>['emit']) => {
15
- const { disabled, horizontalAlign, displayHelper, error } = toRefs(props);
16
-
17
- const radioGroupedClasses: ComputedRef<RadioGroupedClasses> = computed(() => {
18
- const alignmentMap = {
19
- left: 'spr-justify-start',
20
- center: 'spr-justify-center',
21
- right: 'spr-justify-end',
22
- };
23
-
24
- const containerClasses = classNames('spr-flex spr-flex-col spr-gap-2', {
25
- [alignmentMap[horizontalAlign.value as keyof typeof alignmentMap]]: true,
26
- });
27
-
28
- const helperClasses = classNames(
29
- 'spr-flex spr-items-center spr-gap-1 spr-mt-size-spacing-2xs spr-body-sm-regular',
30
- {
31
- 'spr-text-mushroom-600': !error.value,
32
- 'spr-text-color-danger-base': error.value,
33
- },
34
- );
35
-
36
- return {
37
- containerClasses,
38
- helperClasses,
39
- };
40
- });
41
-
42
- const proxyValue = useVModel(props, 'modelValue', emit);
43
-
44
- const renderOptions = (): RadioOption[] => {
45
- return props.options || [];
46
- };
47
-
48
- const isOptionDisabled = (option: RadioOption): boolean => {
49
- return disabled.value || (option.disabled ?? false);
50
- };
51
-
52
- return {
53
- radioGroupedClasses,
54
- proxyValue,
55
- renderOptions,
56
- isOptionDisabled,
57
- disabled,
58
- displayHelper,
59
- horizontalAlign,
60
- error,
61
- };
62
- };
1
+ import { toRefs, computed, ComputedRef } from 'vue';
2
+ import { useVModel } from '@vueuse/core';
3
+
4
+ import classNames from 'classnames';
5
+
6
+ import type { SetupContext } from 'vue';
7
+ import type { RadioGroupedPropTypes, RadioGroupedEmitTypes, RadioOption } from './radio-grouped';
8
+
9
+ interface RadioGroupedClasses {
10
+ containerClasses: string;
11
+ helperClasses: string;
12
+ }
13
+
14
+ export const useRadioGrouped = (props: RadioGroupedPropTypes, emit: SetupContext<RadioGroupedEmitTypes>['emit']) => {
15
+ const { disabled, horizontalAlign, displayHelper, error, choiceBox } = toRefs(props);
16
+
17
+ const radioGroupedClasses: ComputedRef<RadioGroupedClasses> = computed(() => {
18
+ const alignmentMap = {
19
+ left: 'spr-justify-start',
20
+ center: 'spr-justify-center',
21
+ right: 'spr-justify-end',
22
+ };
23
+
24
+ const containerClasses = classNames('spr-flex spr-flex-col spr-gap-2', {
25
+ [alignmentMap[horizontalAlign.value as keyof typeof alignmentMap]]: true,
26
+ });
27
+
28
+ const helperClasses = classNames(
29
+ 'spr-flex spr-items-center spr-gap-1 spr-mt-size-spacing-2xs spr-body-sm-regular',
30
+ {
31
+ 'spr-text-mushroom-600': !error.value,
32
+ 'spr-text-color-danger-base': error.value,
33
+ },
34
+ );
35
+
36
+ return {
37
+ containerClasses,
38
+ helperClasses,
39
+ };
40
+ });
41
+
42
+ const proxyValue = useVModel(props, 'modelValue', emit);
43
+
44
+ const renderOptions = (): RadioOption[] => {
45
+ return props.options || [];
46
+ };
47
+
48
+ const isOptionDisabled = (option: RadioOption): boolean => {
49
+ return disabled.value || (option.disabled ?? false);
50
+ };
51
+
52
+ return {
53
+ radioGroupedClasses,
54
+ proxyValue,
55
+ renderOptions,
56
+ isOptionDisabled,
57
+ disabled,
58
+ displayHelper,
59
+ horizontalAlign,
60
+ error,
61
+ choiceBox,
62
+ };
63
+ };
@@ -33,6 +33,10 @@ export const radioPropTypes = {
33
33
  type: Boolean,
34
34
  default: false,
35
35
  },
36
+ choiceBox: {
37
+ type: Boolean,
38
+ default: false,
39
+ },
36
40
  };
37
41
 
38
42
  export const radioEmitTypes = ['update:modelValue'];
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <div :class="['spr-relative', { 'spr-w-full': props.fullWidth }]">
2
+ <div
3
+ :class="radioClasses.baseClasses"
4
+ @click="radioRef?.click()"
5
+ @mouseenter="isHovered = true"
6
+ @mouseleave="isHovered = false"
7
+ >
3
8
  <input
4
9
  :id="props.id"
5
10
  ref="radioRef"
@@ -8,7 +13,7 @@
8
13
  :name="props.name"
9
14
  :value="props.value"
10
15
  :disabled="props.disabled"
11
- :class="radioClasses.baseClasses"
16
+ :class="radioClasses.baseInputClasses"
12
17
  />
13
18
  <label
14
19
  ref="radioRef"
@@ -25,8 +30,9 @@
25
30
  'spr-text-xs spr-font-normal spr-leading-4 spr-text-mushroom-600',
26
31
  { 'spr-text-color-disabled': props.disabled },
27
32
  ]"
28
- >{{ props.description }}</span
29
33
  >
34
+ {{ props.description }}
35
+ </span>
30
36
  </div>
31
37
  </label>
32
38
  </div>
@@ -42,7 +48,7 @@ const props = defineProps(radioPropTypes);
42
48
  const emit = defineEmits(radioEmitTypes);
43
49
  const slots = useSlots();
44
50
 
45
- const { proxyValue, radioRef, radioClasses } = useRadioButton(props, emit, slots);
51
+ const { proxyValue, radioRef, radioClasses, isHovered } = useRadioButton(props, emit, slots);
46
52
  </script>
47
53
 
48
54
  <style scoped>