@weni/unnnic-system 3.2.9-alpha.1 → 3.2.9-alpha.11

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 (72) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/components/ChartFunnel/DefaultFunnel/ChartDefaultFunnelBase.vue.d.ts.map +1 -1
  3. package/dist/components/ChartFunnel/SvgFunnel/ChartFunnelTwoRows.vue.d.ts +43 -0
  4. package/dist/components/ChartFunnel/SvgFunnel/ChartFunnelTwoRows.vue.d.ts.map +1 -0
  5. package/dist/components/Chip/Chip.vue.d.ts.map +1 -1
  6. package/dist/components/DataTable/index.vue.d.ts +7 -0
  7. package/dist/components/DataTable/index.vue.d.ts.map +1 -1
  8. package/dist/components/DateFilter/DateFilter.vue.d.ts +87 -95
  9. package/dist/components/Input/BaseInput.vue.d.ts +22 -0
  10. package/dist/components/Input/BaseInput.vue.d.ts.map +1 -1
  11. package/dist/components/Input/Input.vue.d.ts +87 -95
  12. package/dist/components/Input/Input.vue.d.ts.map +1 -1
  13. package/dist/components/Input/TextInput.vue.d.ts +52 -1
  14. package/dist/components/Input/TextInput.vue.d.ts.map +1 -1
  15. package/dist/components/InputDatePicker/InputDatePicker.vue.d.ts +87 -95
  16. package/dist/components/InputNext/InputNext.vue.d.ts +1 -1
  17. package/dist/components/Label/Label.vue.d.ts +2 -2
  18. package/dist/components/Label/Label.vue.d.ts.map +1 -1
  19. package/dist/components/ModalNext/ModalNext.vue.d.ts +87 -95
  20. package/dist/components/SelectSmart/SelectSmart.vue.d.ts +52 -1
  21. package/dist/components/SelectTime/index.vue.d.ts +52 -1
  22. package/dist/components/Tab/Tab.vue.d.ts +11 -0
  23. package/dist/components/index.d.ts +895 -937
  24. package/dist/components/index.d.ts.map +1 -1
  25. package/dist/{es-2735a8fb.js → es-dcef703d.js} +1 -1
  26. package/dist/{index-e012fa52.js → index-6ddf8580.js} +4673 -4567
  27. package/dist/locales/en.json.d.ts +2 -1
  28. package/dist/locales/es.json.d.ts +2 -1
  29. package/dist/locales/pt_br.json.d.ts +2 -1
  30. package/dist/{pt-br-f38a8b9c.js → pt-br-485ef253.js} +1 -1
  31. package/dist/style.css +1 -1
  32. package/dist/unnnic.js +1 -1
  33. package/dist/unnnic.umd.cjs +39 -39
  34. package/package.json +1 -1
  35. package/src/assets/scss/scheme-colors.scss +115 -238
  36. package/src/components/Alert/__tests__/__snapshots__/Alert.spec.js.snap +11 -7
  37. package/src/components/Alert/__tests__/__snapshots__/Version1dot1.spec.js.snap +2 -2
  38. package/src/components/ChartFunnel/ChartFunnel.vue +4 -0
  39. package/src/components/ChartFunnel/DefaultFunnel/ChartDefaultFunnelBase.vue +8 -2
  40. package/src/components/ChartFunnel/SvgFunnel/ChartFunnelBaseRow.vue +1 -1
  41. package/src/components/ChartFunnel/SvgFunnel/ChartFunnelTwoRows.vue +64 -0
  42. package/src/components/Chip/Chip.vue +3 -2
  43. package/src/components/DataTable/index.vue +25 -10
  44. package/src/components/Input/BaseInput.vue +21 -2
  45. package/src/components/Input/Input.scss +2 -1
  46. package/src/components/Input/Input.vue +26 -30
  47. package/src/components/Input/TextInput.vue +59 -22
  48. package/src/components/Input/__test__/__snapshots__/Input.spec.js.snap +7 -3
  49. package/src/components/Input/__test__/__snapshots__/TextInput.spec.js.snap +7 -1
  50. package/src/components/Label/Label.vue +2 -2
  51. package/src/components/Popover/__tests__/Popover.spec.js +147 -0
  52. package/src/components/Popover/__tests__/__snapshots__/Popover.spec.js.snap +8 -0
  53. package/src/components/Popover/index.vue +146 -0
  54. package/src/components/Select/SelectOption.vue +65 -0
  55. package/src/components/Select/__tests__/Select.spec.js +412 -0
  56. package/src/components/Select/__tests__/SelectItem.spec.js +330 -0
  57. package/src/components/Select/__tests__/SelectOption.spec.js +174 -0
  58. package/src/components/Select/__tests__/__snapshots__/Select.spec.js.snap +97 -0
  59. package/src/components/Select/__tests__/__snapshots__/SelectItem.spec.js.snap +15 -0
  60. package/src/components/Select/__tests__/__snapshots__/SelectOption.spec.js.snap +25 -0
  61. package/src/components/Select/index.vue +245 -0
  62. package/src/components/Tab/Tab.vue +37 -23
  63. package/src/components/Tab/__test__/__snapshots__/Tab.spec.js.snap +1 -1
  64. package/src/locales/en.json +2 -1
  65. package/src/locales/es.json +2 -1
  66. package/src/locales/pt_br.json +2 -1
  67. package/src/stories/ChartFunnel.stories.js +19 -0
  68. package/src/stories/DataTable.stories.js +60 -0
  69. package/src/stories/Input.stories.js +6 -0
  70. package/src/stories/Popover.stories.js +39 -0
  71. package/src/stories/Select.stories.js +91 -0
  72. package/src/stories/Tab.stories.js +11 -4
@@ -16,7 +16,7 @@
16
16
  {
17
17
  'unnnic-data-table__header-cell--clickable': header.isSortable,
18
18
  'unnnic-data-table__header-cell--sorting':
19
- sort.header === header.title && sort.order !== '',
19
+ sortState.header === header.title && sortState.order !== '',
20
20
  },
21
21
  ]"
22
22
  @click.stop="handleClickHeader(header)"
@@ -31,12 +31,12 @@
31
31
  </template>
32
32
  <template v-if="header.isSortable">
33
33
  <IconArrowsDefault
34
- v-if="sort.header !== header.title"
34
+ v-if="sortState.header !== header.title"
35
35
  class="order-default-icon"
36
36
  data-testid="arrow-default-icon"
37
37
  />
38
38
  <Icon
39
- v-else-if="sort.order === 'asc'"
39
+ v-else-if="sortState.order === 'asc'"
40
40
  clickable
41
41
  size="ant"
42
42
  :icon="'switch_left'"
@@ -44,7 +44,7 @@
44
44
  data-testid="arrow-asc-icon"
45
45
  />
46
46
  <Icon
47
- v-else-if="sort.order === 'desc'"
47
+ v-else-if="sortState.order === 'desc'"
48
48
  clickable
49
49
  size="ant"
50
50
  :icon="'switch_left'"
@@ -183,6 +183,12 @@ type DataTableItem = {
183
183
  [key: string]: any;
184
184
  };
185
185
 
186
+ type SortState = {
187
+ header: string;
188
+ itemKey: string;
189
+ order: string;
190
+ };
191
+
186
192
  interface Props {
187
193
  headers: DataTableHeader[];
188
194
  items: DataTableItem[];
@@ -198,6 +204,7 @@ interface Props {
198
204
  pageTotal?: number;
199
205
  pageInterval?: number;
200
206
  locale?: string;
207
+ sort?: SortState;
201
208
  }
202
209
 
203
210
  defineOptions({
@@ -225,6 +232,7 @@ const props = withDefaults(defineProps<Props>(), {
225
232
  pageTotal: 0,
226
233
  pageInterval: 5,
227
234
  locale: 'en',
235
+ sort: undefined,
228
236
  });
229
237
 
230
238
  const defaultTranslations = {
@@ -251,12 +259,16 @@ const headersItemsKeys: ComputedRef<string[]> = computed(() => {
251
259
  return props.headers.map((header) => header.itemKey);
252
260
  });
253
261
 
254
- const sort = ref({
262
+ const internalSort = ref({
255
263
  header: '',
256
264
  itemKey: '',
257
265
  order: '',
258
266
  });
259
267
 
268
+ const sortState = computed(() => {
269
+ return props.sort !== undefined ? props.sort : internalSort.value;
270
+ });
271
+
260
272
  const getHeaderColumnSize = (header: DataTableHeader): string => {
261
273
  return typeof header.size === 'number'
262
274
  ? `${header.size || 1}fr`
@@ -271,9 +283,12 @@ const gridTemplateColumns: ComputedRef<string> = computed(() => {
271
283
  return columnSizes.join(' ');
272
284
  });
273
285
 
274
- const handleSort = (header: typeof sort.value, order: string) => {
275
- sort.value = { ...header, order };
276
- emit('update:sort', sort.value);
286
+ const handleSort = (header: SortState, order: string) => {
287
+ if (props.sort === undefined) {
288
+ internalSort.value = { ...header, order };
289
+ }
290
+
291
+ emit('update:sort', { ...header, order });
277
292
  };
278
293
 
279
294
  const handleClickHeader = (header: DataTableHeader) => {
@@ -286,9 +301,9 @@ const handleClickHeader = (header: DataTableHeader) => {
286
301
  };
287
302
 
288
303
  const nextSort =
289
- header.title !== sort.value.header
304
+ header.title !== sortState.value.header
290
305
  ? 'asc'
291
- : nextSortOrderMapper[sort.value.order];
306
+ : nextSortOrderMapper[sortState.value.order];
292
307
 
293
308
  handleSort(
294
309
  nextSort === ''
@@ -4,16 +4,18 @@
4
4
  v-mask="mask"
5
5
  v-bind="attributes"
6
6
  :value="fullySanitize(modelValue)"
7
- :class="classes"
7
+ :class="[classes, { focus: forceActiveStatus }]"
8
8
  :type="nativeType"
9
+ :readonly="readonly"
9
10
  />
10
11
  <input
11
12
  v-else
12
13
  v-bind="attributes"
13
14
  :value="fullySanitize(modelValue)"
14
- :class="classes"
15
+ :class="[classes, { focus: forceActiveStatus }]"
15
16
  :type="nativeType"
16
17
  :maxlength="maxlength"
18
+ :readonly="readonly"
17
19
  />
18
20
  </template>
19
21
 
@@ -49,10 +51,19 @@ export default {
49
51
  },
50
52
  hasIconLeft: Boolean,
51
53
  hasIconRight: Boolean,
54
+ hasClearIcon: Boolean,
52
55
  maxlength: {
53
56
  type: Number,
54
57
  default: null,
55
58
  },
59
+ readonly: {
60
+ type: Boolean,
61
+ default: false,
62
+ },
63
+ forceActiveStatus: {
64
+ type: Boolean,
65
+ default: false,
66
+ },
56
67
  },
57
68
  emits: ['update:modelValue'],
58
69
  data() {
@@ -77,6 +88,7 @@ export default {
77
88
  {
78
89
  'input--has-icon-left': this.hasIconLeft,
79
90
  'input--has-icon-right': this.hasIconRight,
91
+ 'input--has-clear-icon': this.hasClearIcon,
80
92
  },
81
93
  ];
82
94
  },
@@ -115,6 +127,13 @@ export default {
115
127
 
116
128
  &.input--has-icon-right {
117
129
  padding-right: $unnnic-space-10;
130
+ &.input--has-clear-icon {
131
+ padding-right: $unnnic-space-10 + $unnnic-space-6;
132
+ }
133
+ }
134
+
135
+ &.input--has-clear-icon {
136
+ padding-right: $unnnic-space-10;
118
137
  }
119
138
 
120
139
  &.error {
@@ -9,7 +9,8 @@
9
9
  caret-color: $unnnic-color-fg-muted;
10
10
  font: $unnnic-font-body;
11
11
 
12
- &:focus {
12
+ &:focus,
13
+ &.focus {
13
14
  border-color: $unnnic-color-border-active;
14
15
  }
15
16
 
@@ -6,30 +6,19 @@
6
6
  >
7
7
  <slot name="label" />
8
8
  </p>
9
- <section
9
+
10
+ <UnnnicLabel
10
11
  v-else-if="label"
11
12
  class="unnnic-form__label"
12
- >
13
- <p>
14
- {{ fullySanitize(label) }}
15
- </p>
16
- <UnnnicToolTip
17
- v-if="tooltip"
18
- enabled
19
- :text="tooltip"
20
- >
21
- <UnnnicIcon
22
- icon="help"
23
- size="sm"
24
- scheme="fg-base"
25
- />
26
- </UnnnicToolTip>
27
- </section>
13
+ :label="label"
14
+ :tooltip="tooltip"
15
+ />
28
16
 
29
17
  <TextInput
30
18
  v-bind="$attrs"
31
19
  v-model="val"
32
20
  class="unnnic-form-input"
21
+ :forceActiveStatus="forceActiveStatus"
33
22
  :placeholder="placeholder"
34
23
  :iconLeft="iconLeft"
35
24
  :iconRight="iconRight"
@@ -42,6 +31,9 @@
42
31
  :nativeType="nativeType"
43
32
  :maxlength="maxlength"
44
33
  :disabled="disabled"
34
+ :readonly="readonly"
35
+ :showClear="showClear"
36
+ @clear="$emit('clear')"
45
37
  />
46
38
 
47
39
  <section class="unnnic-form__hints-container">
@@ -69,12 +61,11 @@
69
61
  <script>
70
62
  import { fullySanitize } from '../../utils/sanitize';
71
63
  import TextInput from './TextInput.vue';
72
- import UnnnicToolTip from '../ToolTip/ToolTip.vue';
73
- import UnnnicIcon from '../Icon.vue';
64
+ import UnnnicLabel from '../Label/Label.vue';
74
65
 
75
66
  export default {
76
67
  name: 'UnnnicInput',
77
- components: { TextInput, UnnnicToolTip, UnnnicIcon },
68
+ components: { TextInput, UnnnicLabel },
78
69
  props: {
79
70
  placeholder: {
80
71
  type: String,
@@ -155,8 +146,22 @@ export default {
155
146
  type: Boolean,
156
147
  default: false,
157
148
  },
149
+ readonly: {
150
+ type: Boolean,
151
+ default: false,
152
+ },
153
+ forceActiveStatus: {
154
+ type: Boolean,
155
+ default: false,
156
+ },
157
+ showClear: {
158
+ type: Boolean,
159
+ default: false,
160
+ },
158
161
  },
159
- emits: ['update:modelValue'],
162
+
163
+ emits: ['update:modelValue', 'clear'],
164
+
160
165
  data() {
161
166
  return {
162
167
  val: this.modelValue,
@@ -203,16 +208,7 @@ export default {
203
208
  }
204
209
 
205
210
  &__label {
206
- font: $unnnic-font-body;
207
- color: $unnnic-color-neutral-cloudy;
208
211
  margin-bottom: $unnnic-space-1;
209
- display: flex;
210
- align-items: center;
211
- gap: $unnnic-space-2;
212
-
213
- :deep(.unnnic-tooltip) {
214
- display: flex;
215
- }
216
212
  }
217
213
 
218
214
  &__hints-container {
@@ -12,7 +12,10 @@
12
12
  class="input-itself"
13
13
  :hasIconLeft="!!iconLeft"
14
14
  :hasIconRight="!!iconRight || allowTogglePassword"
15
+ :hasClearIcon="showClear"
15
16
  :maxlength="maxlength"
17
+ :readonly="readonly"
18
+ :forceActiveStatus="forceActiveStatus"
16
19
  @focus="onFocus"
17
20
  @blur="onBlur"
18
21
  />
@@ -27,18 +30,28 @@
27
30
  @click="onIconLeftClick"
28
31
  />
29
32
 
30
- <UnnnicIcon
31
- v-if="iconRightSvg"
32
- :scheme="iconScheme"
33
- :icon="iconRightSvg"
34
- size="ant"
35
- :clickable="iconRightClickable || allowTogglePassword"
36
- :class="[
37
- 'icon-right',
38
- { clickable: iconRightClickable || allowTogglePassword },
39
- ]"
40
- @click="onIconRightClick"
41
- />
33
+ <section class="icon-right-container">
34
+ <UnnnicIcon
35
+ v-if="showClear"
36
+ class="icon-clear"
37
+ :scheme="iconScheme"
38
+ icon="close"
39
+ size="ant"
40
+ clickable
41
+ @click.stop="onClearClick"
42
+ />
43
+
44
+ <UnnnicIcon
45
+ v-if="iconRightSvg"
46
+ :scheme="iconScheme"
47
+ :icon="iconRightSvg"
48
+ size="ant"
49
+ :clickable="iconRightClickable || allowTogglePassword"
50
+ class="icon-right"
51
+ :class="{ clickable: iconRightClickable || allowTogglePassword }"
52
+ @click="onIconRightClick"
53
+ />
54
+ </section>
42
55
  </div>
43
56
  </template>
44
57
 
@@ -107,8 +120,20 @@ export default {
107
120
  type: Boolean,
108
121
  default: false,
109
122
  },
123
+ readonly: {
124
+ type: Boolean,
125
+ default: false,
126
+ },
127
+ forceActiveStatus: {
128
+ type: Boolean,
129
+ default: false,
130
+ },
131
+ showClear: {
132
+ type: Boolean,
133
+ default: false,
134
+ },
110
135
  },
111
- emits: ['icon-left-click', 'icon-right-click'],
136
+ emits: ['icon-left-click', 'icon-right-click', 'clear'],
112
137
  data() {
113
138
  return {
114
139
  isFocused: false,
@@ -137,7 +162,7 @@ export default {
137
162
  return 'fg-muted';
138
163
  }
139
164
 
140
- if (this.modelValue || this.isFocused) {
165
+ if (this.modelValue || this.isFocused || this.forceActiveStatus) {
141
166
  return 'color-gray-700';
142
167
  }
143
168
 
@@ -170,6 +195,10 @@ export default {
170
195
  if (this.iconLeftClickable) this.$emit('icon-left-click');
171
196
  },
172
197
 
198
+ onClearClick() {
199
+ this.$emit('clear');
200
+ },
201
+
173
202
  onIconRightClick() {
174
203
  if (this.attributes.disabled) return;
175
204
  if (this.allowTogglePassword) this.showPassword = !this.showPassword;
@@ -187,25 +216,33 @@ export default {
187
216
  }
188
217
 
189
218
  .icon {
190
- &-left,
191
- &-right {
192
- &:not(.clickable) {
193
- pointer-events: none;
194
- }
195
- }
196
-
197
219
  &-left {
198
220
  position: absolute;
199
221
  top: 50%;
200
222
  transform: translateY(-50%);
201
223
  left: $unnnic-space-4;
224
+
225
+ &:not(.clickable) {
226
+ pointer-events: none;
227
+ }
202
228
  }
203
229
 
204
- &-right {
230
+ &-right-container {
205
231
  position: absolute;
206
232
  top: 50%;
207
233
  transform: translateY(-50%);
208
234
  right: $unnnic-space-4;
235
+
236
+ display: flex;
237
+ align-items: center;
238
+ gap: $unnnic-space-2;
239
+
240
+ .icon-clear {
241
+ cursor: pointer;
242
+ }
243
+ .icon-right:not(.clickable) {
244
+ pointer-events: none;
245
+ }
209
246
  }
210
247
  }
211
248
  </style>
@@ -2,11 +2,15 @@
2
2
 
3
3
  exports[`Input.vue > matches the snapshot 1`] = `
4
4
  "<div data-v-d890ad85="" class="unnnic-form md">
5
- <section data-v-d890ad85="" class="unnnic-form__label">
6
- <p data-v-d890ad85="">Sample Label</p>
5
+ <section data-v-7f222291="" data-v-d890ad85="" class="unnnic-label unnnic-form__label">
6
+ <p data-v-7f222291="" class="unnnic-label__label">Sample Label</p>
7
7
  <!--v-if-->
8
8
  </section>
9
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-form-input" mask="####-####"><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-form-input input-itself input size-md normal input--has-icon-left input--has-icon-right unnnic-form-input input-itself" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" hascloudycolor="false" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span></div>
9
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-form-input" mask="####-####"><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-form-input input-itself input size-md normal input--has-icon-left input--has-icon-right unnnic-form-input input-itself" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" hascloudycolor="false" showclear="false" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span>
10
+ <section data-v-a0d36167="" class="icon-right-container">
11
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span>
12
+ </section>
13
+ </div>
10
14
  <section data-v-d890ad85="" class="unnnic-form__hints-container">
11
15
  <section data-v-d890ad85="" class="unnnic-form__message-container">
12
16
  <p data-v-d890ad85="" class="unnnic-form__message">Error message</p>
@@ -1,3 +1,9 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`TextInput.vue > matches the snapshot 1`] = `"<div data-v-a0d36167="" class="text-input size--md"><input data-v-86533b41="" data-v-a0d36167="" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" allowtogglepassword="false" hascloudycolor="false" class="input-itself input size-md normal input--has-icon-left input--has-icon-right input-itself" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span></div>"`;
3
+ exports[`TextInput.vue > matches the snapshot 1`] = `
4
+ "<div data-v-a0d36167="" class="text-input size--md"><input data-v-86533b41="" data-v-a0d36167="" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" allowtogglepassword="false" hascloudycolor="false" showclear="false" class="input-itself input size-md normal input--has-icon-left input--has-icon-right input-itself" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span>
5
+ <section data-v-a0d36167="" class="icon-right-container">
6
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span>
7
+ </section>
8
+ </div>"
9
+ `;
@@ -26,13 +26,13 @@ defineOptions({
26
26
  name: 'UnnnicLabel',
27
27
  });
28
28
 
29
- interface Props {
29
+ export interface LabelProps {
30
30
  label?: string;
31
31
  tooltip?: string;
32
32
  useHtmlTooltip?: boolean;
33
33
  }
34
34
 
35
- const props = withDefaults(defineProps<Props>(), {
35
+ const props = withDefaults(defineProps<LabelProps>(), {
36
36
  label: '',
37
37
  tooltip: '',
38
38
  useHtmlTooltip: false,
@@ -0,0 +1,147 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { beforeEach, describe, expect, afterEach, test, vi } from 'vitest';
3
+ import UnnnicPopover from '@/components/Popover/index.vue';
4
+
5
+ vi.mock('@vueuse/core', () => ({
6
+ onClickOutside: vi.fn(),
7
+ useResizeObserver: vi.fn(),
8
+ }));
9
+
10
+ describe('UnnnicPopover.vue', () => {
11
+ let wrapper;
12
+
13
+ const defaultSlots = {
14
+ trigger: '<button data-testid="trigger-button">Click me</button>',
15
+ content: '<div data-testid="popover-content">Popover content</div>',
16
+ };
17
+
18
+ const mountWrapper = (props) => {
19
+ return mount(UnnnicPopover, {
20
+ slots: defaultSlots,
21
+ props: {
22
+ ...props,
23
+ },
24
+ });
25
+ };
26
+
27
+ beforeEach(() => {
28
+ wrapper = mountWrapper();
29
+ });
30
+
31
+ afterEach(() => {
32
+ wrapper?.unmount();
33
+ });
34
+
35
+ test('renders correctly', () => {
36
+ expect(wrapper.exists()).toBe(true);
37
+ expect(wrapper.find('.unnnic-popover').exists()).toBe(true);
38
+ });
39
+
40
+ test('renders trigger slot', () => {
41
+ const trigger = wrapper.find('[data-testid="popover-trigger"]');
42
+ const triggerButton = wrapper.find('[data-testid="trigger-button"]');
43
+
44
+ expect(trigger.exists()).toBe(true);
45
+ expect(triggerButton.exists()).toBe(true);
46
+ expect(triggerButton.text()).toBe('Click me');
47
+ });
48
+
49
+ test('renders content slot inside balloon', async () => {
50
+ wrapper.vm.open = true;
51
+ await wrapper.vm.$nextTick();
52
+ const balloon = wrapper.find('[data-testid="popover-balloon"]');
53
+ const content = wrapper.find('[data-testid="popover-content"]');
54
+
55
+ expect(balloon.exists()).toBe(true);
56
+ expect(content.exists()).toBe(true);
57
+ expect(content.text()).toBe('Popover content');
58
+ });
59
+
60
+ test('balloon is hidden by default', () => {
61
+ const balloon = wrapper.find('[data-testid="popover-balloon"]');
62
+ expect(balloon.exists()).toBe(false);
63
+ });
64
+
65
+ test('toggles balloon visibility when trigger is clicked', async () => {
66
+ const trigger = wrapper.find('[data-testid="popover-trigger"]');
67
+
68
+ let balloon = wrapper.find('[data-testid="popover-balloon"]');
69
+
70
+ expect(balloon.exists()).toBe(false);
71
+
72
+ await trigger.trigger('click');
73
+ await wrapper.vm.$nextTick();
74
+
75
+ balloon = wrapper.find('[data-testid="popover-balloon"]');
76
+
77
+ expect(balloon.exists()).toBe(true);
78
+ });
79
+
80
+ test('uses modelValue when provided', async () => {
81
+ const wrapper = mountWrapper({ modelValue: true });
82
+ const balloon = wrapper.find('[data-testid="popover-balloon"]');
83
+ expect(balloon.isVisible()).toBe(true);
84
+ });
85
+
86
+ test('emits update:modelValue when open state changes', async () => {
87
+ await wrapper.setProps({ modelValue: false });
88
+
89
+ const trigger = wrapper.find('[data-testid="popover-trigger"]');
90
+ await trigger.trigger('click');
91
+
92
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
93
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([true]);
94
+ });
95
+
96
+ test('does not emit update:modelValue when modelValue is undefined', async () => {
97
+ const trigger = wrapper.find('[data-testid="popover-trigger"]');
98
+ await trigger.trigger('click');
99
+
100
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy();
101
+ });
102
+
103
+ test('exposes open ref', () => {
104
+ expect(wrapper.vm.open).toBeDefined();
105
+ expect(typeof wrapper.vm.open).toBe('boolean');
106
+ });
107
+
108
+ test('open ref can be controlled programmatically', async () => {
109
+ wrapper.vm.open = true;
110
+ await wrapper.vm.$nextTick();
111
+
112
+ const balloon = wrapper.find('[data-testid="popover-balloon"]');
113
+ expect(balloon.isVisible()).toBe(true);
114
+ });
115
+
116
+ test('persistent prop prevents closing on outside click', async () => {
117
+ await wrapper.setProps({ persistent: true });
118
+
119
+ const { onClickOutside } = await import('@vueuse/core');
120
+ const mockOnClickOutside = vi.mocked(onClickOutside);
121
+
122
+ const callback = mockOnClickOutside.mock.calls[0][1];
123
+
124
+ wrapper.vm.open = true;
125
+ await wrapper.vm.$nextTick();
126
+
127
+ callback();
128
+
129
+ const balloon = wrapper.find('[data-testid="popover-balloon"]');
130
+ expect(balloon.isVisible()).toBe(true);
131
+ });
132
+
133
+ test('applies correct CSS classes', async () => {
134
+ wrapper.vm.open = true;
135
+ await wrapper.vm.$nextTick();
136
+
137
+ const popover = wrapper.find('.unnnic-popover');
138
+ const balloon = wrapper.find('.unnnic-popover__balloon');
139
+
140
+ expect(popover.exists()).toBe(true);
141
+ expect(balloon.exists()).toBe(true);
142
+ });
143
+
144
+ test('matches the snapshot', () => {
145
+ expect(wrapper.html()).toMatchSnapshot();
146
+ });
147
+ });
@@ -0,0 +1,8 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`UnnnicPopover.vue > matches the snapshot 1`] = `
4
+ "<section data-v-5a3125ac="" class="unnnic-popover">
5
+ <div data-v-5a3125ac="" class="unnnic-popover__trigger" data-testid="popover-trigger"><button data-testid="trigger-button">Click me</button></div>
6
+ <!--v-if-->
7
+ </section>"
8
+ `;