design-system-next 2.24.2 → 2.26.0

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.
Files changed (31) hide show
  1. package/dist/design-system-next.es.d.ts +152 -114
  2. package/dist/design-system-next.es.js +10058 -9379
  3. package/dist/design-system-next.es.js.gz +0 -0
  4. package/dist/design-system-next.umd.js +12 -12
  5. package/dist/design-system-next.umd.js.gz +0 -0
  6. package/dist/main.css +1 -1
  7. package/dist/main.css.gz +0 -0
  8. package/package.json +3 -2
  9. package/src/App.vue +1 -89
  10. package/src/components/date-picker/date-picker.ts +9 -12
  11. package/src/components/date-picker/date-picker.vue +1 -1
  12. package/src/components/date-picker/date-range-picker/date-range-picker.ts +9 -12
  13. package/src/components/date-picker/date-range-picker/use-date-range-picker.ts +64 -49
  14. package/src/components/date-picker/month-year-picker/month-year-picker.ts +134 -0
  15. package/src/components/date-picker/month-year-picker/month-year-picker.vue +233 -0
  16. package/src/components/date-picker/month-year-picker/use-month-year-picker.ts +603 -0
  17. package/src/components/date-picker/use-date-picker.ts +88 -147
  18. package/src/components/list/list-item/list-item.ts +60 -60
  19. package/src/components/list/list.ts +5 -0
  20. package/src/components/list/list.vue +2 -0
  21. package/src/components/list/use-list.ts +36 -3
  22. package/src/components/radio-grouped/radio-grouped.ts +65 -65
  23. package/src/components/radio-grouped/use-radio-grouped.ts +62 -62
  24. package/src/components/select/select-multiple/use-select-multiple.ts +7 -3
  25. package/src/components/select/use-select.ts +9 -5
  26. package/src/components/sidepanel/sidepanel.ts +7 -0
  27. package/src/components/sidepanel/sidepanel.vue +24 -4
  28. package/src/components/sidepanel/use-sidepanel.ts +4 -0
  29. package/src/components/table/table-header-dropdown/table-header-dropdown.vue +5 -1
  30. package/src/components/table/table.vue +14 -0
  31. package/src/components/table/use-table.ts +1 -2
@@ -1,65 +1,65 @@
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
+ }
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,62 +1,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 } = 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 } = 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
+ };
@@ -376,10 +376,14 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
376
376
  { deep: true },
377
377
  );
378
378
 
379
- watch(searchInput, () => {
380
- search.value = searchInput.value;
379
+ watch(searchInput, (newVal, oldVal) => {
380
+ search.value = newVal;
381
381
 
382
- emit('search-string', searchInput.value);
382
+ // Only emit search-string if value actually changed (not just modifier keys)
383
+ // Modifier key presses alone won't change the input value
384
+ if (newVal !== oldVal) {
385
+ emit('search-string', newVal);
386
+ }
383
387
  });
384
388
 
385
389
  watch(multiSelectPopperState, (newState) => {
@@ -148,11 +148,15 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
148
148
  return selectOptions.value.filter((item) => item.text?.toString().toLowerCase().includes(query));
149
149
  });
150
150
 
151
- // Search handler: always emit search-string, but only filter locally if local search is enabled
152
- const handleSearch = () => {
153
- isSearching.value = true;
154
-
155
- debouncedEmitSearch();
151
+ // Search handler: only emit search-string on regular keys and ENTER, ignore modifier-only keys
152
+ const handleSearch = (event: KeyboardEvent) => {
153
+ // Ignore pure modifier keys: Shift, Control, Alt, Meta (Command/Windows), CapsLock
154
+ const modifierOnlyKeys = ['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'];
155
+
156
+ if (!modifierOnlyKeys.includes(event.key)) {
157
+ isSearching.value = true;
158
+ debouncedEmitSearch();
159
+ }
156
160
  };
157
161
 
158
162
  const debouncedEmitSearch = useDebounceFn(() => {
@@ -20,6 +20,9 @@ export const sidepanelPropTypes = {
20
20
  type: String,
21
21
  default: 'Sidepanel Header',
22
22
  },
23
+ headerSubtitle: {
24
+ type: String
25
+ },
23
26
  /**
24
27
  * @description Specifies the size of the side panel.
25
28
  * Acceptable values are: `'sm'`, `'md'`, `'lg'`, `'xl'`.
@@ -108,6 +111,10 @@ export const sidepanelPropTypes = {
108
111
  footerNoTopBorder: {
109
112
  type: Boolean,
110
113
  default: false,
114
+ },
115
+ isLoading: {
116
+ type: Boolean,
117
+ default: false,
111
118
  }
112
119
  };
113
120
 
@@ -22,10 +22,25 @@
22
22
  >
23
23
  <template v-if="!props.hideHeader">
24
24
  <div v-if="!$slots.header" :class="sidepanelClasses.sidepanelHeaderClasses">
25
- <div id="sidepanel-title" :class="sidepanelClasses.sidepanelHeaderTitleClasses">
26
- {{ headerTitle }}
25
+ <div v-if="!isLoading" id="headers">
26
+ <div id="sidepanel-title" :class="sidepanelClasses.sidepanelHeaderTitleClasses">
27
+ {{ headerTitle }}
28
+ </div>
29
+ <div id="sidepanel-subtitle" :class="sidepanelClasses.sidepanelHeaderSubtitleClasses">
30
+ <slot name="subtitle">
31
+ <span class="spr-text-color-base">{{ headerSubtitle }}</span>
32
+ </slot>
33
+ </div>
27
34
  </div>
28
- <div class="spr-flex spr-items-center spr-gap-size-spacing-3xs">
35
+ <div v-else id="header-loaders" class="spr-w-full spr-flex spr-flex-col spr-gap-size-spacing-4xs">
36
+ <div class="spr-skeletal-loader spr-h-4 spr-w-[90%] spr-rounded-md"></div>
37
+ <div
38
+ v-if="headerSubtitle || $slots.subtitle"
39
+ class="spr-skeletal-loader spr-h-8 spr-w-[95%] spr-rounded-md"
40
+ ></div>
41
+ </div>
42
+
43
+ <div class="spr-flex spr-items-center spr-gap-size-spacing-3xs">
29
44
  <Icon
30
45
  v-if="props.isExpandable"
31
46
  :class="sidepanelClasses.sidepanelHeaderIconClasses"
@@ -33,7 +48,12 @@
33
48
  data-testid="expand-icon"
34
49
  @click="handlePanelExpansion"
35
50
  />
36
- <Icon :class="sidepanelClasses.sidepanelHeaderIconClasses" icon="ph:x" data-testid="x-icon" @click="handleClose" />
51
+ <Icon
52
+ :class="sidepanelClasses.sidepanelHeaderIconClasses"
53
+ icon="ph:x"
54
+ data-testid="x-icon"
55
+ @click="handleClose"
56
+ />
37
57
  </div>
38
58
  </div>
39
59
  <div v-else>
@@ -16,6 +16,7 @@ interface SidepanelClasses {
16
16
  sidepanelTransitionHiddenClasses: string;
17
17
  sidepanelTransitionVisibleClasses: string;
18
18
  backdropBaseClasses: string;
19
+ sidepanelHeaderSubtitleClasses: string;
19
20
  }
20
21
 
21
22
  export const useSidepanel = (props: SidepanelPropTypes, emit: SetupContext<SidepanelEmitTypes>['emit']) => {
@@ -43,6 +44,8 @@ export const useSidepanel = (props: SidepanelPropTypes, emit: SetupContext<Sidep
43
44
 
44
45
  const sidepanelHeaderTitleClasses = classNames('spr-subheading-xs');
45
46
 
47
+ const sidepanelHeaderSubtitleClasses = classNames('spr-text-200 spr-max-w-[95%]');
48
+
46
49
  const sidepanelHeaderIconClasses = classNames('spr-text-color-weak spr-h-5 spr-w-5 spr-cursor-pointer');
47
50
 
48
51
  const sidepanelContentClasses = classNames('spr-h-full spr-overflow-y-auto');
@@ -81,6 +84,7 @@ export const useSidepanel = (props: SidepanelPropTypes, emit: SetupContext<Sidep
81
84
  sidepanelTransitionHiddenClasses,
82
85
  sidepanelTransitionVisibleClasses,
83
86
  backdropBaseClasses,
87
+ sidepanelHeaderSubtitleClasses
84
88
  };
85
89
  });
86
90
 
@@ -25,7 +25,7 @@
25
25
  </template>
26
26
  <template #content>
27
27
  <div v-if="props.isSortable" class="spr-border-0 spr-border-b spr-border-solid spr-border-color-weak">
28
- <spr-list v-model="selectedSort" class="spr-capitalize spr-body-sm-regular spr-text-color-strong [&_svg]:spr-w-[16px] [&_svg]:spr-h-[16px] [&_svg.spr-text-color-brand-base]:spr-hidden" :menu-list="props.sortOptions"/>
28
+ <spr-list v-model="selectedSort" class="spr-capitalize spr-body-sm-regular spr-text-color-strong [&_svg]:spr-w-[16px] [&_svg]:spr-h-[16px] [&_svg.spr-text-color-brand-base]:spr-hidden" :menu-list="props.sortOptions" :allow-deselect="true"/>
29
29
  </div>
30
30
  <div v-if="props.header.filterList && props.header.filterList.length > 0" >
31
31
  <spr-list v-model="selectedFilters" class="spr-capitalize spr-body-sm-regular spr-text-color-strong [&_svg]:spr-w-[16px] [&_svg]:spr-h-[16px]" :menu-list="props.header.filterList" multi-select/>
@@ -81,4 +81,8 @@ const selectAll = () => {
81
81
  selectedFilters.value = props.header.filterList ? [...props.header.filterList] : [];
82
82
  emits('onSelectAll');
83
83
  };
84
+
85
+ defineExpose({
86
+ showDropdown
87
+ });
84
88
  </script>
@@ -45,9 +45,11 @@
45
45
  :class="[getTableClasses.headerClasses(header)]"
46
46
  :style="{ width: header?.width }"
47
47
  >
48
+ <div v-if="props.showHeaderFilter" class="spr-cursor-pointer hover:spr-background-color-hover active:spr-background-color-pressed spr-absolute spr-inset-0 spr-z-[2]" @click="handleHeaderDropdownClick(keyHeader)"></div>
48
49
  <!-- Header with Dropdown Filter -->
49
50
  <spr-table-header-dropdown
50
51
  v-if="props.showHeaderFilter"
52
+ :ref="(el: unknown) => setHeaderDropdownRef(el, keyHeader)"
51
53
  :id="`th-dropdown-${keyHeader}`"
52
54
  :header="header"
53
55
  :is-sortable="true"
@@ -238,6 +240,18 @@ import { useDraggableTableRows } from './use-draggable-table-rows';
238
240
  const props = defineProps(tablePropTypes);
239
241
  const emit = defineEmits(tableEmitTypes);
240
242
  const slots = useSlots();
243
+ const headerDropdownRefs = ref<Record<string, Element | null>>({});
244
+
245
+ const setHeaderDropdownRef = (el: unknown, key: string | number) => {
246
+ if (el) {
247
+ headerDropdownRefs.value[String(key)] = el as Element;
248
+ }
249
+ };
250
+
251
+ const handleHeaderDropdownClick = (keyHeader: string | number) => {
252
+ const dropdownRef = headerDropdownRefs.value[String(keyHeader)] as InstanceType<typeof SprTableHeaderDropdown> | null;
253
+ dropdownRef?.showDropdown();
254
+ };
241
255
 
242
256
  const sortableTBody = ref<HTMLElement | null>(null);
243
257
 
@@ -218,12 +218,11 @@ export const useTable = (props: TablePropTypes, emit: SetupContext<TableEmitType
218
218
  }
219
219
 
220
220
  return classNames(
221
- 'spr-min-h-12 spr-px-size-spacing-2xs spr-py-size-spacing-3xs',
221
+ 'spr-min-h-12 spr-px-size-spacing-2xs spr-py-size-spacing-3xs spr-relative',
222
222
  'spr-text-color-strong spr-font-size-100 spr-font-line-height-100 spr-font-letter-spacing-normal spr-text-start spr-font-medium spr-uppercase',
223
223
  'spr-border-color-weak spr-border-x-0 spr-border-y spr-border-solid',
224
224
  {
225
225
  'spr-border-t-0': !slots.default,
226
- 'spr-cursor-pointer hover:spr-background-color-hover active:spr-background-color-pressed': props.showHeaderFilter,
227
226
  },
228
227
  headerBackground,
229
228
  );