@weni/unnnic-system 3.12.3-alpha.2 → 3.12.3-alpha.4

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 (32) hide show
  1. package/dist/components/Disclaimer/Disclaimer.vue.d.ts +6 -3
  2. package/dist/components/Disclaimer/Disclaimer.vue.d.ts.map +1 -1
  3. package/dist/components/Disclaimer/types.d.ts +6 -3
  4. package/dist/components/Disclaimer/types.d.ts.map +1 -1
  5. package/dist/components/MultiSelect/index.vue.d.ts.map +1 -1
  6. package/dist/components/Select/index.vue.d.ts.map +1 -1
  7. package/dist/components/index.d.ts +6 -0
  8. package/dist/components/index.d.ts.map +1 -1
  9. package/dist/components/ui/popover/PopoverContent.vue.d.ts.map +1 -1
  10. package/dist/{es-d8dee0fd.mjs → es-e4e4f53d.mjs} +1 -1
  11. package/dist/{index-97fc812f.mjs → index-492e2532.mjs} +3595 -3742
  12. package/dist/locales/en.json.d.ts +2 -1
  13. package/dist/locales/es.json.d.ts +2 -1
  14. package/dist/locales/pt_br.json.d.ts +2 -1
  15. package/dist/{pt-br-11081c69.mjs → pt-br-ac2463c3.mjs} +1 -1
  16. package/dist/style.css +1 -1
  17. package/dist/unnnic.mjs +1 -1
  18. package/dist/unnnic.umd.js +19 -19
  19. package/package.json +1 -1
  20. package/src/components/Disclaimer/Disclaimer.vue +136 -42
  21. package/src/components/Disclaimer/__tests__/Disclaimer.spec.js +70 -45
  22. package/src/components/Disclaimer/types.ts +12 -3
  23. package/src/components/MultiSelect/__tests__/__snapshots__/MultiSelect.spec.js.snap +12 -6
  24. package/src/components/MultiSelect/index.vue +1 -1
  25. package/src/components/Select/__tests__/Select.spec.js +8 -8
  26. package/src/components/Select/__tests__/__snapshots__/Select.spec.js.snap +12 -6
  27. package/src/components/Select/index.vue +45 -9
  28. package/src/components/ui/popover/PopoverContent.vue +15 -0
  29. package/src/locales/en.json +2 -1
  30. package/src/locales/es.json +2 -1
  31. package/src/locales/pt_br.json +2 -1
  32. package/src/stories/Disclaimer.stories.js +53 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weni/unnnic-system",
3
- "version": "3.12.3-alpha.2",
3
+ "version": "3.12.3-alpha.4",
4
4
  "type": "commonjs",
5
5
  "files": [
6
6
  "dist",
@@ -1,71 +1,165 @@
1
1
  <template>
2
- <section class="unnnic-disclaimer">
2
+ <section
3
+ :class="['unnnic-disclaimer', `unnnic-disclaimer--${type}`]"
4
+ data-testid="disclaimer"
5
+ >
3
6
  <UnnnicIcon
4
7
  class="unnnic-disclaimer__icon"
5
- size="avatar-nano"
6
- :icon="icon"
7
- :scheme="iconColor"
8
+ size="ant"
9
+ :icon="variant.icon"
10
+ :scheme="variant.scheme"
8
11
  data-testid="disclaimer-icon"
9
12
  />
10
- <p
11
- class="unnnic-disclaimer__text"
12
- data-testid="disclaimer-text"
13
- v-html="text"
14
- />
13
+
14
+ <section class="unnnic-disclaimer__content">
15
+ <p
16
+ v-if="title"
17
+ class="unnnic-disclaimer__title"
18
+ data-testid="disclaimer-title"
19
+ >
20
+ {{ title }}
21
+ </p>
22
+ <p
23
+ v-if="description"
24
+ class="unnnic-disclaimer__description"
25
+ data-testid="disclaimer-description"
26
+ >
27
+ {{ description }}
28
+ </p>
29
+ </section>
15
30
  </section>
16
31
  </template>
17
32
 
18
33
  <script setup lang="ts">
19
- import icons from '../../utils/iconList';
20
- import colors from '../../utils/colorsList';
34
+ import { computed } from 'vue';
35
+
21
36
  import UnnnicIcon from '../Icon.vue';
22
- import type { DisclaimerProps } from './types';
37
+ import type { SchemeColor } from '@/types/scheme-colors';
38
+ import type { DisclaimerType, DisclaimerProps } from './types';
23
39
 
24
40
  defineOptions({
25
41
  name: 'UnnnicDisclaimer',
26
42
  });
27
43
 
28
- export type { DisclaimerProps };
29
-
30
44
  const props = withDefaults(defineProps<DisclaimerProps>(), {
31
- icon: 'alert-circle-1-1',
32
- iconColor: 'neutral-darkest',
45
+ title: 'Disclaimer',
46
+ description: 'The quick brown fox jumps over the lazy dog',
47
+ type: 'informational',
48
+ icon: undefined,
49
+ iconColor: undefined,
33
50
  });
34
51
 
35
- const validateIcon = (value: string): boolean => {
36
- return icons.includes(value);
37
- };
52
+ // This is a temporary solution to ensure backwards compatibility with the older version of the component.
53
+ // TODO: It should be removed once the older version is discontinued.
54
+ const variantCompatibleWithOlderVersion = computed((): DisclaimerType => {
55
+ if (props.icon) {
56
+ if (props.icon.includes('alert-circle')) return 'attention';
57
+ if (props.icon === 'info') return 'informational';
58
+ if (props.icon === 'error') return 'error';
59
+ return 'neutral';
60
+ }
38
61
 
39
- const validateIconColor = (value: string): boolean => {
40
- return colors.includes(value);
41
- };
62
+ if (props.iconColor) {
63
+ if (props.iconColor.includes('yellow')) return 'attention';
64
+ if (props.iconColor.includes('red')) return 'error';
65
+ return 'neutral';
66
+ }
42
67
 
43
- if (!validateIcon(props.icon as string)) {
44
- console.warn(`Invalid icon prop: ${props.icon}`);
45
- }
68
+ return props.type;
69
+ });
46
70
 
47
- if (!validateIconColor(props.iconColor as string)) {
48
- console.warn(`Invalid iconColor prop: ${props.iconColor}`);
49
- }
71
+ const variant = computed(() => {
72
+ const variants: Record<
73
+ DisclaimerType,
74
+ { icon: string; scheme: SchemeColor }
75
+ > = {
76
+ informational: {
77
+ icon: 'info',
78
+ scheme: 'blue-500',
79
+ },
80
+ success: {
81
+ icon: 'check_circle',
82
+ scheme: 'green-500',
83
+ },
84
+ attention: {
85
+ icon: 'error',
86
+ scheme: 'yellow-500',
87
+ },
88
+ error: {
89
+ icon: 'cancel',
90
+ scheme: 'red-500',
91
+ },
92
+ neutral: {
93
+ icon: 'info',
94
+ scheme: 'gray-400',
95
+ },
96
+ };
97
+
98
+ return variants[variantCompatibleWithOlderVersion.value || props.type];
99
+ });
50
100
  </script>
51
101
 
52
- <style lang="scss" scoped>
102
+ <style scoped lang="scss">
53
103
  @use '@/assets/scss/unnnic' as *;
104
+
54
105
  .unnnic-disclaimer {
55
- display: inline-flex;
56
- align-items: center;
57
- gap: $unnnic-spacing-xs;
58
- padding: $unnnic-spacing-sm;
59
- border-radius: $unnnic-border-radius-sm;
60
- border: 1px solid $unnnic-color-neutral-soft;
61
- background: $unnnic-color-background-lightest;
62
- .unnnic-disclaimer__text {
106
+ $border-width: 1px;
107
+
108
+ display: flex;
109
+ gap: $unnnic-space-2;
110
+
111
+ width: 100%;
112
+ min-height: 53px;
113
+ padding: calc($unnnic-space-4 - $border-width) $unnnic-space-4;
114
+ box-sizing: border-box;
115
+
116
+ border: $border-width solid $unnnic-color-border-base;
117
+ border-radius: $unnnic-radius-2;
118
+
119
+ background-color: $unnnic-color-bg-soft;
120
+
121
+ color: $unnnic-color-fg-emphasized;
122
+
123
+ &__content {
124
+ display: flex;
125
+ flex-direction: column;
126
+ justify-content: center;
127
+ gap: $unnnic-space-1;
128
+ }
129
+
130
+ &__title {
131
+ margin: 0;
132
+ font: $unnnic-font-action;
133
+ }
134
+
135
+ &__description {
63
136
  margin: 0;
64
- font-family: $unnnic-font-family-secondary;
65
- font-size: $unnnic-font-size-body-gt;
66
- line-height: $unnnic-line-height-large * 1.375;
67
- font-weight: $unnnic-font-weight-regular;
68
- color: $unnnic-color-neutral-dark;
137
+ font: $unnnic-font-caption-2;
138
+ }
139
+
140
+ &--informational {
141
+ background-color: $unnnic-color-bg-info;
142
+ border-color: $unnnic-color-border-info;
143
+ }
144
+
145
+ &--success {
146
+ background-color: $unnnic-color-bg-success;
147
+ border-color: $unnnic-color-border-success;
148
+ }
149
+
150
+ &--attention {
151
+ background-color: $unnnic-color-bg-warning;
152
+ border-color: $unnnic-color-border-warning;
153
+ }
154
+
155
+ &--error {
156
+ background-color: $unnnic-color-bg-critical;
157
+ border-color: $unnnic-color-border-critical;
158
+ }
159
+
160
+ &--neutral {
161
+ background-color: $unnnic-color-bg-soft;
162
+ border-color: $unnnic-color-border-base;
69
163
  }
70
164
  }
71
165
  </style>
@@ -1,72 +1,97 @@
1
1
  import { mount } from '@vue/test-utils';
2
- import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { describe, it, expect } from 'vitest';
3
3
 
4
4
  import Disclaimer from '../Disclaimer.vue';
5
5
  import Icon from '../../Icon.vue';
6
6
 
7
+ const mountComponent = (props = {}) =>
8
+ mount(Disclaimer, {
9
+ props,
10
+ global: {
11
+ components: { UnnnicIcon: Icon },
12
+ },
13
+ });
14
+
7
15
  describe('Disclaimer', () => {
8
- let wrapper;
16
+ it('renders default title and description', () => {
17
+ const wrapper = mountComponent();
9
18
 
10
- beforeEach(() => {
11
- wrapper = mount(Disclaimer, {
12
- props: { text: 'Test Disclaimer Text' },
13
- global: { components: { UnnnicIcon: Icon } },
14
- });
19
+ expect(wrapper.find('[data-testid="disclaimer-title"]').text()).toBe(
20
+ 'Disclaimer',
21
+ );
22
+ expect(wrapper.find('[data-testid="disclaimer-description"]').text()).toBe(
23
+ 'The quick brown fox jumps over the lazy dog',
24
+ );
15
25
  });
16
26
 
17
- it('renders the text prop correctly', () => {
18
- expect(wrapper.find('[data-testid="disclaimer-text"]').text()).toBe(
19
- 'Test Disclaimer Text',
27
+ it('hides title when no title is provided', () => {
28
+ const wrapper = mountComponent({ title: '' });
29
+
30
+ expect(wrapper.find('[data-testid="disclaimer-title"]').exists()).toBe(
31
+ false,
20
32
  );
21
33
  });
22
34
 
23
- it('renders the UnnnicIcon component with correct icon and color', async () => {
24
- const icon = 'alert-circle-1-1';
25
- const iconColor = 'neutral-darkest';
35
+ it('hides description when no description is provided', () => {
36
+ const wrapper = mountComponent({ description: '' });
37
+
38
+ expect(
39
+ wrapper.find('[data-testid="disclaimer-description"]').exists(),
40
+ ).toBe(false);
41
+ });
26
42
 
27
- await wrapper.setProps({ icon, iconColor });
43
+ it.each([
44
+ ['informational', 'info', 'blue-500'],
45
+ ['success', 'check_circle', 'green-500'],
46
+ ['attention', 'error', 'yellow-500'],
47
+ ['error', 'cancel', 'red-500'],
48
+ ['neutral', 'info', 'gray-400'],
49
+ ])('applies variant %s styles', (type, icon, scheme) => {
50
+ const wrapper = mountComponent({ type });
51
+
52
+ expect(wrapper.classes()).toContain(`unnnic-disclaimer--${type}`);
28
53
 
29
54
  const iconComponent = wrapper.findComponent(
30
55
  '[data-testid="disclaimer-icon"]',
31
56
  );
32
- expect(iconComponent.exists()).toBe(true);
57
+
33
58
  expect(iconComponent.props('icon')).toBe(icon);
34
- expect(iconComponent.props('scheme')).toBe(iconColor);
59
+ expect(iconComponent.props('scheme')).toBe(scheme);
35
60
  });
36
61
 
37
- it('renders with default icon and color if not provided', () => {
38
- const iconComponent = wrapper.findComponent(
39
- '[data-testid="disclaimer-icon"]',
40
- );
41
- expect(iconComponent.props('icon')).toBe('alert-circle-1-1');
42
- expect(iconComponent.props('scheme')).toBe('neutral-darkest');
43
- });
62
+ describe('legacy compatibility', () => {
63
+ it.each([
64
+ ['alert-circle-1-1', 'error', 'yellow-500'],
65
+ ['info', 'info', 'blue-500'],
66
+ ['error', 'cancel', 'red-500'],
67
+ ['custom-icon', 'info', 'gray-400'],
68
+ ])('maps icon prop "%s" to variant', (icon, expectedIcon, scheme) => {
69
+ const wrapper = mountComponent({ icon });
44
70
 
45
- it('validates the icon prop correctly', () => {
46
- const wrapperValid = mount(Disclaimer, {
47
- props: { text: 'Test', icon: 'alert-circle-1-1' },
48
- global: { components: { UnnnicIcon: Icon } },
49
- });
50
- expect(wrapperValid.exists()).toBe(true);
71
+ const iconComponent = wrapper.findComponent(
72
+ '[data-testid="disclaimer-icon"]',
73
+ );
51
74
 
52
- const wrapperInvalid = mount(Disclaimer, {
53
- props: { text: 'Test', icon: 'invalid-icon' },
54
- global: { components: { UnnnicIcon: Icon } },
75
+ expect(iconComponent.props('icon')).toBe(expectedIcon);
76
+ expect(iconComponent.props('scheme')).toBe(scheme);
55
77
  });
56
- expect(wrapperInvalid.exists()).toBe(true);
57
- });
58
78
 
59
- it('validates the iconColor prop correctly', () => {
60
- const wrapperValid = mount(Disclaimer, {
61
- props: { text: 'Test', iconColor: 'neutral-darkest' },
62
- global: { components: { UnnnicIcon: Icon } },
63
- });
64
- expect(wrapperValid.exists()).toBe(true);
79
+ it.each([
80
+ ['feedback-yellow', 'error', 'yellow-500'],
81
+ ['feedback-red', 'cancel', 'red-500'],
82
+ ['feedback-blue', 'info', 'gray-400'],
83
+ ])(
84
+ 'maps iconColor prop "%s" to variant',
85
+ (iconColor, expectedIcon, scheme) => {
86
+ const wrapper = mountComponent({ iconColor });
65
87
 
66
- const wrapperInvalid = mount(Disclaimer, {
67
- props: { text: 'Test', iconColor: 'invalid-color' },
68
- global: { components: { UnnnicIcon: Icon } },
69
- });
70
- expect(wrapperInvalid.exists()).toBe(true);
88
+ const iconComponent = wrapper.findComponent(
89
+ '[data-testid="disclaimer-icon"]',
90
+ );
91
+
92
+ expect(iconComponent.props('icon')).toBe(expectedIcon);
93
+ expect(iconComponent.props('scheme')).toBe(scheme);
94
+ },
95
+ );
71
96
  });
72
97
  });
@@ -1,7 +1,16 @@
1
1
  import type { SchemeColor } from '@/types/scheme-colors';
2
2
 
3
+ export type DisclaimerType =
4
+ | 'informational'
5
+ | 'success'
6
+ | 'attention'
7
+ | 'error'
8
+ | 'neutral';
9
+
3
10
  export interface DisclaimerProps {
4
- text: string;
5
- icon?: string;
6
- iconColor?: SchemeColor;
11
+ title?: string;
12
+ description?: string;
13
+ type?: DisclaimerType;
14
+ icon?: string | undefined;
15
+ iconColor?: SchemeColor | undefined;
7
16
  }
@@ -4,9 +4,11 @@ exports[`UnnnicMultiSelect.vue > snapshot testing > matches snapshot with defaul
4
4
  "<div data-v-03c7fb50="" class="unnnic-multi-select"><button data-v-9d52eef8="" data-v-03c7fb50="" class="unnnic-popover-trigger" id="reka-popover-trigger-v-0" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="" data-state="closed">
5
5
  <section data-v-9f8d6c86="" data-v-d890ad85="" data-v-03c7fb50="" class="unnnic-form-element unnnic-form md unnnic-multi-select__input" data-testid="form-element">
6
6
  <!--v-if-->
7
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-multi-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-multi-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right input--has-clear-icon unnnic-multi-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="true" type="text" readonly="" value="">
7
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-multi-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-multi-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right unnnic-multi-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="false" type="text" readonly="" value="">
8
8
  <!--v-if-->
9
- <section data-v-a0d36167="" class="icon-right-container"><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-clear" data-testid="material-icon" translate="no">close</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span></section>
9
+ <section data-v-a0d36167="" class="icon-right-container">
10
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span>
11
+ </section>
10
12
  </div>
11
13
  <!--v-if-->
12
14
  </section>
@@ -20,9 +22,11 @@ exports[`UnnnicMultiSelect.vue > snapshot testing > matches snapshot with disabl
20
22
  "<div data-v-03c7fb50="" class="unnnic-multi-select"><button data-v-9d52eef8="" data-v-03c7fb50="" class="unnnic-popover-trigger" id="reka-popover-trigger-v-0" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="" data-state="closed">
21
23
  <section data-v-9f8d6c86="" data-v-d890ad85="" data-v-03c7fb50="" class="unnnic-form-element unnnic-form-element--disabled unnnic-form md unnnic-multi-select__input" data-testid="form-element">
22
24
  <!--v-if-->
23
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-multi-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-multi-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right input--has-clear-icon unnnic-multi-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="true" type="text" readonly="" value="" disabled="">
25
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-multi-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-multi-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right unnnic-multi-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="false" type="text" readonly="" value="" disabled="">
24
26
  <!--v-if-->
25
- <section data-v-a0d36167="" class="icon-right-container"><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-muted unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-clear" data-testid="material-icon" translate="no">close</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-muted unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span></section>
27
+ <section data-v-a0d36167="" class="icon-right-container">
28
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-muted unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span>
29
+ </section>
26
30
  </div>
27
31
  <!--v-if-->
28
32
  </section>
@@ -52,9 +56,11 @@ exports[`UnnnicMultiSelect.vue > snapshot testing > matches snapshot with search
52
56
  "<div data-v-03c7fb50="" class="unnnic-multi-select"><button data-v-9d52eef8="" data-v-03c7fb50="" class="unnnic-popover-trigger" id="reka-popover-trigger-v-0" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="" data-state="closed">
53
57
  <section data-v-9f8d6c86="" data-v-d890ad85="" data-v-03c7fb50="" class="unnnic-form-element unnnic-form md unnnic-multi-select__input" data-testid="form-element">
54
58
  <!--v-if-->
55
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-multi-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-multi-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right input--has-clear-icon unnnic-multi-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="true" type="text" readonly="" value="">
59
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-multi-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-multi-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right unnnic-multi-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="false" type="text" readonly="" value="">
56
60
  <!--v-if-->
57
- <section data-v-a0d36167="" class="icon-right-container"><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-clear" data-testid="material-icon" translate="no">close</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span></section>
61
+ <section data-v-a0d36167="" class="icon-right-container">
62
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span>
63
+ </section>
58
64
  </div>
59
65
  <!--v-if-->
60
66
  </section>
@@ -17,7 +17,7 @@
17
17
  :message="props.message"
18
18
  :iconRight="openPopover ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"
19
19
  :disabled="props.disabled"
20
- showClear
20
+ :showClear="!!selectedItems.length"
21
21
  @clear="emit('update:modelValue', [])"
22
22
  />
23
23
  </PopoverTrigger>
@@ -245,20 +245,20 @@ describe('UnnnicSelect.vue', () => {
245
245
  });
246
246
 
247
247
  describe('computed properties', () => {
248
- test('calculatedMaxHeight returns correct value', () => {
249
- const maxHeight = wrapper.vm.calculatedMaxHeight;
248
+ test('calculatedPopoverHeight returns correct value', () => {
249
+ const maxHeight = wrapper.vm.calculatedPopoverHeight;
250
250
  expect(maxHeight).toBe('235px');
251
251
  });
252
252
 
253
- test('calculatedMaxHeight includes search height when enabled', async () => {
253
+ test('calculatedPopoverHeight includes search height when enabled', async () => {
254
254
  await wrapper.setProps({ enableSearch: true });
255
- const maxHeight = wrapper.vm.calculatedMaxHeight;
256
- expect(maxHeight).toBe('289px');
255
+ const maxHeight = wrapper.vm.calculatedPopoverHeight;
256
+ expect(maxHeight).toBe('282px');
257
257
  });
258
258
 
259
- test('calculatedMaxHeight returns unset when no options', async () => {
259
+ test('calculatedPopoverHeight returns unset when no options', async () => {
260
260
  await wrapper.setProps({ options: [] });
261
- const maxHeight = wrapper.vm.calculatedMaxHeight;
261
+ const maxHeight = wrapper.vm.calculatedPopoverHeight;
262
262
  expect(maxHeight).toBe('unset');
263
263
  });
264
264
 
@@ -360,7 +360,7 @@ describe('UnnnicSelect.vue', () => {
360
360
  test('handles empty options array', async () => {
361
361
  await wrapper.setProps({ options: [] });
362
362
  expect(wrapper.vm.filteredOptions).toEqual([]);
363
- expect(wrapper.vm.calculatedMaxHeight).toBe('unset');
363
+ expect(wrapper.vm.calculatedPopoverHeight).toBe('unset');
364
364
  });
365
365
 
366
366
  test('handles null modelValue', async () => {
@@ -4,9 +4,11 @@ exports[`UnnnicSelect.vue > snapshot testing > matches snapshot with default pro
4
4
  "<div data-v-6077efb7="" class="unnnic-select"><button data-v-9d52eef8="" data-v-6077efb7="" class="unnnic-popover-trigger" id="reka-popover-trigger-v-0" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="" data-state="closed">
5
5
  <section data-v-9f8d6c86="" data-v-d890ad85="" data-v-6077efb7="" class="unnnic-form-element unnnic-form md unnnic-select__input" data-testid="form-element">
6
6
  <!--v-if-->
7
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right input--has-clear-icon unnnic-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="true" type="text" readonly="" value="">
7
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right unnnic-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="false" type="text" readonly="" value="">
8
8
  <!--v-if-->
9
- <section data-v-a0d36167="" class="icon-right-container"><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-clear" data-testid="material-icon" translate="no">close</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span></section>
9
+ <section data-v-a0d36167="" class="icon-right-container">
10
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span>
11
+ </section>
10
12
  </div>
11
13
  <!--v-if-->
12
14
  </section>
@@ -20,9 +22,11 @@ exports[`UnnnicSelect.vue > snapshot testing > matches snapshot with disabled st
20
22
  "<div data-v-6077efb7="" class="unnnic-select"><button data-v-9d52eef8="" data-v-6077efb7="" class="unnnic-popover-trigger" id="reka-popover-trigger-v-0" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="" data-state="closed">
21
23
  <section data-v-9f8d6c86="" data-v-d890ad85="" data-v-6077efb7="" class="unnnic-form-element unnnic-form-element--disabled unnnic-form md unnnic-select__input" data-testid="form-element">
22
24
  <!--v-if-->
23
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right input--has-clear-icon unnnic-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="true" type="text" readonly="" value="" disabled="">
25
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right unnnic-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="false" type="text" readonly="" value="" disabled="">
24
26
  <!--v-if-->
25
- <section data-v-a0d36167="" class="icon-right-container"><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-muted unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-clear" data-testid="material-icon" translate="no">close</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-muted unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span></section>
27
+ <section data-v-a0d36167="" class="icon-right-container">
28
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-muted unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span>
29
+ </section>
26
30
  </div>
27
31
  <!--v-if-->
28
32
  </section>
@@ -36,9 +40,11 @@ exports[`UnnnicSelect.vue > snapshot testing > matches snapshot with search enab
36
40
  "<div data-v-6077efb7="" class="unnnic-select"><button data-v-9d52eef8="" data-v-6077efb7="" class="unnnic-popover-trigger" id="reka-popover-trigger-v-0" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="" data-state="closed">
37
41
  <section data-v-9f8d6c86="" data-v-d890ad85="" data-v-6077efb7="" class="unnnic-form-element unnnic-form md unnnic-select__input" data-testid="form-element">
38
42
  <!--v-if-->
39
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right input--has-clear-icon unnnic-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="true" type="text" readonly="" value="">
43
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-select__input unnnic-form-input" hascloudycolor="false" mask=""><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-select__input unnnic-form-input input-itself input size-md normal input--has-icon-right unnnic-select__input unnnic-form-input input-itself" hascloudycolor="false" placeholder="" iconleft="" iconright="keyboard_arrow_down" iconleftclickable="false" iconrightclickable="false" showclear="false" type="text" readonly="" value="">
40
44
  <!--v-if-->
41
- <section data-v-a0d36167="" class="icon-right-container"><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-clear" data-testid="material-icon" translate="no">close</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span></section>
45
+ <section data-v-a0d36167="" class="icon-right-container">
46
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant icon-right" data-testid="material-icon" translate="no">keyboard_arrow_down</span>
47
+ </section>
42
48
  </div>
43
49
  <!--v-if-->
44
50
  </section>
@@ -20,16 +20,14 @@
20
20
  :message="props.message"
21
21
  :iconRight="openPopover ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"
22
22
  :disabled="props.disabled"
23
- showClear
23
+ :showClear="!!selectedItem"
24
24
  @clear="emit('update:modelValue', '')"
25
25
  />
26
26
  </PopoverTrigger>
27
27
  <PopoverContent
28
28
  align="start"
29
- :style="{
30
- maxHeight: calculatedMaxHeight,
31
- overflow: 'auto',
32
- }"
29
+ :class="'h-full'"
30
+ :style="popoverContentCustomStyles"
33
31
  >
34
32
  <div class="unnnic-select__content">
35
33
  <UnnnicInput
@@ -40,8 +38,15 @@
40
38
  iconLeft="search"
41
39
  @update:model-value="handleSearch"
42
40
  />
41
+ <p
42
+ v-if="filteredOptions.length === 0"
43
+ class="unnnic-select__content-no-results"
44
+ >
45
+ {{ $t('without_results') }}
46
+ </p>
43
47
  <PopoverOption
44
48
  v-for="(option, index) in filteredOptions"
49
+ v-else
45
50
  :key="option[props.itemValue]"
46
51
  :data-option-index="index"
47
52
  data-testid="select-option"
@@ -69,6 +74,7 @@ import {
69
74
  PopoverContent,
70
75
  PopoverOption,
71
76
  } from '../ui/popover/index';
77
+
72
78
  import UnnnicI18n from '../../mixins/i18n';
73
79
 
74
80
  defineOptions({
@@ -141,6 +147,7 @@ watch(openPopover, () => {
141
147
  const handleKeyDown = (event) => {
142
148
  const { key } = event;
143
149
  const validKeys = ['ArrowUp', 'ArrowDown', 'Enter'];
150
+
144
151
  if (validKeys.includes(key)) {
145
152
  event.preventDefault();
146
153
  if (key === 'ArrowUp') {
@@ -153,7 +160,7 @@ const handleKeyDown = (event) => {
153
160
  focusedOptionIndex.value++;
154
161
  scrollToOption(focusedOptionIndex.value);
155
162
  }
156
- if (key === 'Enter') {
163
+ if (key === 'Enter' && focusedOptionIndex.value !== -1) {
157
164
  handleSelectOption(filteredOptions.value[focusedOptionIndex.value]);
158
165
  }
159
166
  }
@@ -174,15 +181,32 @@ const scrollToOption = (
174
181
  });
175
182
  };
176
183
 
177
- const calculatedMaxHeight = computed(() => {
184
+ const calculatedPopoverHeight = computed(() => {
178
185
  if (!props.options || props.options.length === 0) return 'unset';
179
186
  const popoverPadding = 32;
180
187
  const popoverGap = 4;
181
188
  // 37 = 21px (height) + 16px (padding)
182
189
  const fieldsHeight = 37 * props.optionsLines;
190
+ const gapsCompensation = props.enableSearch ? 1 : 2;
191
+
183
192
  const size =
184
- fieldsHeight + popoverPadding + (popoverGap * props.optionsLines - 2);
185
- return `${props.enableSearch ? size + 54 : size}px`;
193
+ fieldsHeight +
194
+ popoverPadding +
195
+ (popoverGap * props.optionsLines - gapsCompensation);
196
+
197
+ return `${props.enableSearch ? size + 45 + 1 : size}px`;
198
+ });
199
+
200
+ const popoverContentCustomStyles = computed(() => {
201
+ const emptyFilteredOptions = filteredOptions.value?.length === 0;
202
+ return {
203
+ overflow: 'auto',
204
+ display: 'flex',
205
+ flexDirection: 'column',
206
+ minHeight: calculatedPopoverHeight.value,
207
+ maxHeight: emptyFilteredOptions ? 'unset' : calculatedPopoverHeight.value,
208
+ height: emptyFilteredOptions ? calculatedPopoverHeight.value : 'unset',
209
+ };
186
210
  });
187
211
 
188
212
  const selectedItem = computed(() => {
@@ -256,6 +280,18 @@ const filteredOptions = computed(() => {
256
280
  padding: 0;
257
281
  margin: 0;
258
282
  gap: $unnnic-space-1;
283
+
284
+ height: -webkit-fill-available;
285
+
286
+ &-no-results {
287
+ margin: 0;
288
+ display: flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ height: 100%;
292
+ font: $unnnic-font-emphasis;
293
+ color: $unnnic-color-fg-muted;
294
+ }
259
295
  }
260
296
  }
261
297
  </style>