design-system-next 2.19.0 → 2.19.3

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.
@@ -8,7 +8,10 @@
8
8
  @click="handleClick"
9
9
  @keyup.enter="handleClick"
10
10
  >
11
- <template v-if="variant === 'tag'">
11
+ <template v-if="slots.default">
12
+ <slot />
13
+ </template>
14
+ <template v-else-if="variant === 'tag'">
12
15
  <span v-if="hasIcon" class="chips-icon spr-inline-flex spr-items-center spr-leading-[0]">
13
16
  <slot name="icon">
14
17
  <Icon :icon="getIcon" class="spr-font-size-300" />
@@ -27,7 +27,7 @@ export type RestDayType = 'su' | 'mo' | 'tu' | 'we' | 'th' | 'fr' | 'sa';
27
27
  export type DatePickerMode = typeof DATE_PICKER_MODES[number];
28
28
 
29
29
  // Define props with JSDoc comments for documentation
30
- export const reusableCalendarPropTypes = {
30
+ export const dateCalendarPickerPropTypes = {
31
31
  /**
32
32
  * @description The selected date value (v-model)
33
33
  */
@@ -109,7 +109,7 @@ export const reusableCalendarPropTypes = {
109
109
  };
110
110
 
111
111
  // Define emits with type validation
112
- export const reusableCalendarEmitTypes = {
112
+ export const dateCalendarPickerEmitTypes = {
113
113
  'update:modelValue': (value: string): value is string => typeof value === 'string',
114
114
  'update:month': (month: number): month is number => typeof month === 'number',
115
115
  'update:year': (year: number): year is number => typeof year === 'number',
@@ -117,5 +117,5 @@ export const reusableCalendarEmitTypes = {
117
117
  };
118
118
 
119
119
  // Export types for use in other files
120
- export type ReusableCalendarPropTypes = ExtractPropTypes<typeof reusableCalendarPropTypes>;
121
- export type ReusableCalendarEmitTypes = typeof reusableCalendarEmitTypes;
120
+ export type DateCalendarPickerPropTypes = ExtractPropTypes<typeof dateCalendarPickerPropTypes>;
121
+ export type DateCalendarPickerEmitTypes = typeof dateCalendarPickerEmitTypes;
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
- ref="reusableCalendarRef"
4
- :class="reusableCalendarClasses"
3
+ ref="dateCalendarPickerRef"
4
+ :class="dateCalendarPickerClasses"
5
5
  >
6
6
  <div
7
7
  :class="[
@@ -132,13 +132,13 @@ import DatePickerCalendarTab from '../tabs/DatePickerCalendarTab.vue';
132
132
  import DatePickerMonthTab from '../tabs/DatePickerMonthTab.vue';
133
133
  import DatePickerYearTab from '../tabs/DatePickerYearTab.vue';
134
134
 
135
- import { useReusableCalendar } from './use-reusable-calendar';
136
- import { reusableCalendarEmitTypes, reusableCalendarPropTypes } from './reusable-calendar';
135
+ import { useDateCalendarPicker } from './use-date-calendar-picker';
136
+ import { dateCalendarPickerEmitTypes, dateCalendarPickerPropTypes } from './date-calendar-picker';
137
137
 
138
- const props = defineProps(reusableCalendarPropTypes);
139
- const emit = defineEmits(reusableCalendarEmitTypes);
138
+ const props = defineProps(dateCalendarPickerPropTypes);
139
+ const emit = defineEmits(dateCalendarPickerEmitTypes);
140
140
 
141
- const reusableCalendarRef = ref<HTMLElement | null>(null);
141
+ const dateCalendarPickerRef = ref<HTMLElement | null>(null);
142
142
 
143
143
  // Use the composable for all logic
144
144
  const {
@@ -176,12 +176,12 @@ const {
176
176
  handleMonthTabMonthUpdateWrapper,
177
177
  handleYearTabYearUpdateWrapper,
178
178
  handleYearTabCurrentPageUpdateWrapper,
179
- } = useReusableCalendar(props, emit);
179
+ } = useDateCalendarPicker(props, emit);
180
180
 
181
181
  // Compute CSS classes using classNames utility
182
- const reusableCalendarClasses = computed(() => {
182
+ const dateCalendarPickerClasses = computed(() => {
183
183
  return classNames(
184
- 'reusable-calendar-container spr-bg-white spr-rounded-lg spr-shadow-lg spr-border spr-border-solid spr-border-mushroom-200 min-w-[320px]',
184
+ 'date-calendar-picker-container spr-bg-white spr-rounded-lg spr-shadow-lg spr-border spr-border-solid spr-border-mushroom-200 min-w-[320px]',
185
185
  {
186
186
  'spr-disabled': props.disabled,
187
187
  'spr-readonly': props.readonly,
@@ -3,11 +3,11 @@ import { toRefs } from 'vue';
3
3
  import dayjs from 'dayjs';
4
4
 
5
5
  import type { SetupContext } from 'vue';
6
- import type { ReusableCalendarEmitTypes, ReusableCalendarPropTypes } from './reusable-calendar';
6
+ import type { DateCalendarPickerEmitTypes, DateCalendarPickerPropTypes } from './date-calendar-picker';
7
7
 
8
- export const useReusableCalendar = (
9
- props: ReusableCalendarPropTypes,
10
- emit: SetupContext<ReusableCalendarEmitTypes>['emit']
8
+ export const useDateCalendarPicker = (
9
+ props: DateCalendarPickerPropTypes,
10
+ emit: SetupContext<DateCalendarPickerEmitTypes>['emit']
11
11
  ) => {
12
12
  // Extract reactive props
13
13
  const {
@@ -1,8 +1,6 @@
1
1
  import { onBeforeMount, ref, toRefs, watch } from 'vue';
2
- import { useVModel } from '@vueuse/core';
3
-
2
+ import { useVModel, watchDeep } from '@vueuse/core';
4
3
  import { LadderizedListPropTypes, LadderizedListEmitTypes } from './ladderized-list';
5
-
6
4
  import type { SetupContext } from 'vue';
7
5
  import type { MenuListType } from '../list';
8
6
 
@@ -21,6 +19,8 @@ export const useLadderizedList = (
21
19
  const activeList = ref<MenuListType[]>(menuList.value); // List of items to display in the active level
22
20
  const searchText = ref('');
23
21
 
22
+ const modelValueIsCustom = ref(false);
23
+
24
24
  // Recursive filter function for ladderized options
25
25
  const filterOptionsRecursive = (items: MenuListType[], search: string): MenuListType[] => {
26
26
  if (!search) return items;
@@ -86,6 +86,12 @@ export const useLadderizedList = (
86
86
  const handleSelectedListItem = (item: MenuListType) => {
87
87
  transitionName.value = 'slide-left';
88
88
 
89
+ if (modelValueIsCustom.value) {
90
+ console.log("Custom");
91
+ ladderizedListOutput.value.shift();
92
+ modelValueIsCustom.value = false;
93
+ };
94
+
89
95
  // If searching, reconstruct full path as array of option objects
90
96
  if (searchText.value) {
91
97
  const path = findOptionPath(menuList.value, String(item.value));
@@ -111,7 +117,7 @@ export const useLadderizedList = (
111
117
  replaceItemInOutput(item);
112
118
  }
113
119
 
114
- if (item.sublevel && item.sublevel.length > 0) updateLevel(item);
120
+ if (item.sublevel && item.sublevel.length > 0) updateLevel(item); // FIXME: This causes activeLevel one less than expected. Currently not critical since most forms are 2 levels deep.
115
121
  emit('update:modelValue', ladderizedListOutput.value);
116
122
  };
117
123
 
@@ -224,9 +230,14 @@ export const useLadderizedList = (
224
230
 
225
231
  prevList.value = activeList.value;
226
232
  activeList.value = item.sublevel ?? prevList.value;
227
- activeLevel.value += item.sublevel ? 1 : 0;
233
+ activeLevel.value += item.sublevel ? 1 : 0; // FIXME: This causes activeLevel one less than expected. Currently not critical since most forms are 2 levels deep.
228
234
  } else {
229
- // If no item found, skip the for loop
235
+ // If no item found, rest the values to initial state
236
+ selectedListItem.value = [];
237
+ prevList.value = [];
238
+ activeList.value = menuList.value;
239
+ activeLevel.value = 0;
240
+ modelValueIsCustom.value = true;
230
241
  return;
231
242
  }
232
243
  });
@@ -241,8 +252,8 @@ export const useLadderizedList = (
241
252
  };
242
253
 
243
254
  // Watch for modelValue changes and reset selectedListItem if cleared
244
- watch(
245
- () => props.modelValue,
255
+ watchDeep(
256
+ ladderizedListOutput,
246
257
  (newVal) => {
247
258
  if (!newVal || (Array.isArray(newVal) && newVal.length === 0)) {
248
259
  selectedListItem.value = [];
@@ -91,10 +91,6 @@ export const listPropTypes = {
91
91
  type: [String, Number] as PropType<string | number>,
92
92
  default: 0,
93
93
  },
94
- displayListItemSelected: {
95
- type: Boolean,
96
- default: false,
97
- },
98
94
  itemIcon: {
99
95
  type: String,
100
96
  default: '',
@@ -1,30 +1,21 @@
1
1
  <template>
2
2
  <div class="spr-font-main">
3
- <template v-if="props.searchableMenu || props.displayListItemSelected">
4
- <div
5
- :class="[
6
- 'spr-sticky spr-z-20',
7
- 'spr-grid spr-gap-3 spr-bg-white-50 spr-px-size-spacing-3xs spr-py-size-spacing-2xs',
8
- 'spr-border-color-weak spr-border spr-border-x-0 spr-border-b spr-border-t-0 spr-border-solid',
9
- ]"
10
- :style="{
11
- top:
12
- typeof props.stickySearchOffset === 'number'
13
- ? props.stickySearchOffset + 'px'
14
- : String(props.stickySearchOffset),
15
- }"
16
- >
17
- <spr-input-search
18
- v-if="props.searchableMenu"
19
- v-model="searchText"
20
- :placeholder="props.searchableMenuPlaceholder"
21
- autocomplete="off"
22
- />
23
- <span v-if="props.displayListItemSelected" class="spr-label-sm-medium spr-text-color-base spr-block">
24
- {{ selectedItems.length }} Selected
25
- </span>
26
- </div>
27
- </template>
3
+ <div
4
+ v-if="props.searchableMenu"
5
+ :class="[
6
+ 'spr-sticky spr-z-20',
7
+ 'spr-grid spr-gap-3 spr-bg-white-50 spr-p-size-spacing-3xs',
8
+ 'spr-border-color-weak spr-border spr-border-x-0 spr-border-b spr-border-t-0 spr-border-solid',
9
+ ]"
10
+ :style="{
11
+ top:
12
+ typeof props.stickySearchOffset === 'number'
13
+ ? props.stickySearchOffset + 'px'
14
+ : String(props.stickySearchOffset),
15
+ }"
16
+ >
17
+ <spr-input-search v-model="searchText" :placeholder="props.searchableMenuPlaceholder" autocomplete="off" />
18
+ </div>
28
19
 
29
20
  <div class="spr-p-size-spacing-3xs">
30
21
  <template v-if="props.groupItemsBy">
@@ -72,16 +63,13 @@
72
63
  { 'spr-text-color-disabled': item.disabled },
73
64
  ]"
74
65
  >
75
- <span class="spr-text-left spr-text-xs" style="word-break: break-word">
76
- {{ item.text }}
77
- </span>
66
+ <span class="spr-text-left spr-text-xs">{{ item.text }}</span>
78
67
  <span
79
68
  v-if="item.subtext"
80
69
  :class="[
81
70
  'spr-body-xs-regular spr-text-color-base spr-text-left',
82
71
  { 'spr-text-color-disabled': item.disabled },
83
72
  ]"
84
- style="word-break: break-word"
85
73
  >
86
74
  {{ item.subtext }}
87
75
  </span>
@@ -165,16 +153,13 @@
165
153
  { 'spr-text-color-disabled': item.disabled },
166
154
  ]"
167
155
  >
168
- <span class="spr-text-left spr-text-xs" style="word-break: break-word">
169
- {{ item.text }}
170
- </span>
156
+ <span class="spr-text-left spr-text-xs">{{ item.text }}</span>
171
157
  <span
172
158
  v-if="item.subtext"
173
159
  :class="[
174
160
  'spr-body-xs-regular spr-text-color-base spr-text-left',
175
161
  { 'spr-text-color-disabled': item.disabled },
176
162
  ]"
177
- style="word-break: break-word"
178
163
  >
179
164
  {{ item.subtext }}
180
165
  </span>
@@ -240,13 +225,6 @@ import { useList } from './use-list';
240
225
  const props = defineProps(listPropTypes);
241
226
  const emit = defineEmits(listEmitTypes);
242
227
 
243
- const {
244
- selectedItems,
245
- searchText,
246
- localizedMenuList,
247
- groupedMenuList,
248
- isItemSelected,
249
- getListItemClasses,
250
- handleSelectedItem,
251
- } = useList(props, emit);
228
+ const { searchText, localizedMenuList, groupedMenuList, isItemSelected, getListItemClasses, handleSelectedItem } =
229
+ useList(props, emit);
252
230
  </script>
@@ -572,7 +572,6 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
572
572
  });
573
573
 
574
574
  return {
575
- selectedItems,
576
575
  searchText,
577
576
  listClasses,
578
577
  localizedMenuList,
@@ -144,12 +144,16 @@ export const selectLadderizedPropTypes = {
144
144
  type: Boolean,
145
145
  default: false,
146
146
  },
147
+ writableInputText: {
148
+ type: Boolean,
149
+ default: false,
150
+ }
147
151
  };
148
152
 
149
153
  export const selectLadderizedEmitTypes = {
150
154
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
151
155
  'update:modelValue': (_value: unknown) => true,
152
- 'popper-state': Boolean,
156
+ 'popper-state': () => true,
153
157
  };
154
158
 
155
159
  export type SelectLadderizedEmitFn = (event: string, ...args: unknown[]) => void;
@@ -37,10 +37,11 @@
37
37
  :helper-text="props.helperText"
38
38
  :helper-icon="props.helperIcon"
39
39
  :display-helper="props.displayHelper"
40
- readonly
40
+ :readonly="!props.writableInputText"
41
41
  :active="props.active"
42
42
  :disabled="props.disabled"
43
43
  :error="props.error"
44
+ @blur="handleInputChange"
44
45
  >
45
46
  <template #icon>
46
47
  <div
@@ -121,5 +122,6 @@ const {
121
122
  inputText,
122
123
  handleSelectedLadderizedItem,
123
124
  handleClear,
125
+ handleInputChange,
124
126
  } = useSelectLadderized(props, emit as SelectLadderizedEmitFn);
125
127
  </script>
@@ -1,5 +1,5 @@
1
1
  import { ref, toRefs, computed, watch } from 'vue';
2
- import { useVModel, onClickOutside } from '@vueuse/core';
2
+ import { useVModel, onClickOutside, watchDeep } from '@vueuse/core';
3
3
 
4
4
  import type { SelectLadderizedPropTypes } from './select-ladderized';
5
5
 
@@ -17,6 +17,7 @@ export const useSelectLadderized = (
17
17
  supportingLabelClasses: 'spr-body-sm-regular spr-text-color-supporting',
18
18
  }));
19
19
 
20
+ // Wrapper for input field
20
21
  const ladderizedSelectState = ref<HTMLDivElement | null>(null);
21
22
 
22
23
  // Popper Variables
@@ -31,6 +32,7 @@ export const useSelectLadderized = (
31
32
  // Input Variables
32
33
  const inputText = ref<string>('');
33
34
  const wasCleared = ref<boolean>(false);
35
+ const isCustomInput = ref<boolean>(false);
34
36
 
35
37
  const isLeafNode = (item: MenuListType): boolean => {
36
38
  return !item.sublevel || item.sublevel.length === 0;
@@ -57,6 +59,7 @@ export const useSelectLadderized = (
57
59
 
58
60
  const handleSelectedLadderizedItem = (selectedItems: string[], selectedItem?: MenuListType) => {
59
61
  wasCleared.value = false;
62
+ isCustomInput.value = false;
60
63
 
61
64
  // If the selectedItems is a single value (leaf from search), reconstruct the full path
62
65
  let fullPath: string[] | null = null;
@@ -157,15 +160,17 @@ export const useSelectLadderized = (
157
160
  };
158
161
 
159
162
  // Watch for changes in modelValue to update inputText
160
- watch(
161
- () => ladderizedSelectModel.value,
163
+ watchDeep(
164
+ ladderizedSelectModel,
162
165
  (newVal) => {
166
+ if (isCustomInput.value) return;
167
+
163
168
  if (wasCleared.value) {
164
169
  inputText.value = '';
165
170
  wasCleared.value = false;
166
171
 
167
172
  return;
168
- }
173
+ };
169
174
 
170
175
  if (Array.isArray(newVal) && newVal.length > 0) {
171
176
  // Treat the array as a single path for ladderized select
@@ -194,6 +199,13 @@ export const useSelectLadderized = (
194
199
  emit('popper-state', newState);
195
200
  });
196
201
 
202
+ const handleInputChange = () => {
203
+ if (!props.writableInputText) return;
204
+ wasCleared.value = false;
205
+ isCustomInput.value = true;
206
+ ladderizedSelectModel.value = [inputText.value];
207
+ };
208
+
197
209
  // Close only when clicking completely outside both the popper and the trigger wrapper.
198
210
  onClickOutside(ladderizedSelectPopperRef, (event) => {
199
211
  const triggerWrapper = ladderizedSelectState.value;
@@ -216,5 +228,6 @@ export const useSelectLadderized = (
216
228
  inputText,
217
229
  handleSelectedLadderizedItem,
218
230
  handleClear,
231
+ handleInputChange,
219
232
  };
220
233
  };
@@ -127,10 +127,6 @@ export const multiSelectPropTypes = {
127
127
  type: Boolean,
128
128
  default: false,
129
129
  },
130
- displayListItemSelected: {
131
- type: Boolean,
132
- default: false,
133
- },
134
130
  displayHelper: {
135
131
  type: Boolean,
136
132
  default: false,
@@ -49,7 +49,9 @@
49
49
  class="spr-text-color-supporting spr-px-3"
50
50
  :aria-label="`${multiSelectedListItems.length} selected options`"
51
51
  >
52
- {{ multiSelectedListItems.length }} item{{ multiSelectedListItems.length === 1 ? '' : 's' }}
52
+ {{ multiSelectedListItems.length }} item{{
53
+ multiSelectedListItems.length === 1 ? '' : 's'
54
+ }}
53
55
  selected
54
56
  </span>
55
57
  </template>
@@ -162,9 +164,8 @@
162
164
  :loading="props.loading"
163
165
  :item-icon="props.itemIcon"
164
166
  :lozenge="props.lozenge"
165
- :display-list-item-selected="props.displayListItemSelected"
166
- :disabled-local-search="props.disabledLocalSearch"
167
167
  multi-select
168
+ :disabled-local-search="props.disabledLocalSearch"
168
169
  @update:model-value="handleMultiSelectedItem"
169
170
  />
170
171
  </div>