@weni/unnnic-system 3.2.9-alpha.0 → 3.2.9-alpha.10
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.
- package/CHANGELOG.md +18 -0
- package/dist/components/Chip/Chip.vue.d.ts.map +1 -1
- package/dist/components/DataTable/index.vue.d.ts +7 -0
- package/dist/components/DataTable/index.vue.d.ts.map +1 -1
- package/dist/components/DateFilter/DateFilter.vue.d.ts +87 -95
- package/dist/components/Input/BaseInput.vue.d.ts +22 -0
- package/dist/components/Input/BaseInput.vue.d.ts.map +1 -1
- package/dist/components/Input/Input.vue.d.ts +87 -95
- package/dist/components/Input/Input.vue.d.ts.map +1 -1
- package/dist/components/Input/TextInput.vue.d.ts +52 -1
- package/dist/components/Input/TextInput.vue.d.ts.map +1 -1
- package/dist/components/InputDatePicker/InputDatePicker.vue.d.ts +87 -95
- package/dist/components/InputNext/InputNext.vue.d.ts +1 -1
- package/dist/components/Label/Label.vue.d.ts +2 -2
- package/dist/components/Label/Label.vue.d.ts.map +1 -1
- package/dist/components/ModalNext/ModalNext.vue.d.ts +87 -95
- package/dist/components/SelectSmart/SelectSmart.vue.d.ts +52 -1
- package/dist/components/SelectTime/index.vue.d.ts +52 -1
- package/dist/components/Tab/Tab.vue.d.ts +11 -0
- package/dist/components/index.d.ts +895 -937
- package/dist/components/index.d.ts.map +1 -1
- package/dist/{es-2735a8fb.js → es-3cbe331a.js} +1 -1
- package/dist/{index-e012fa52.js → index-2241773d.js} +4242 -4189
- package/dist/locales/en.json.d.ts +2 -1
- package/dist/locales/es.json.d.ts +2 -1
- package/dist/locales/pt_br.json.d.ts +2 -1
- package/dist/{pt-br-f38a8b9c.js → pt-br-9ddee0e9.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/unnnic.js +1 -1
- package/dist/unnnic.umd.cjs +39 -39
- package/package.json +1 -1
- package/src/assets/scss/scheme-colors.scss +115 -238
- package/src/components/Alert/__tests__/__snapshots__/Alert.spec.js.snap +11 -7
- package/src/components/Alert/__tests__/__snapshots__/Version1dot1.spec.js.snap +2 -2
- package/src/components/Chip/Chip.vue +3 -2
- package/src/components/DataTable/index.vue +25 -10
- package/src/components/Input/BaseInput.vue +21 -2
- package/src/components/Input/Input.scss +2 -1
- package/src/components/Input/Input.vue +26 -30
- package/src/components/Input/TextInput.vue +59 -22
- package/src/components/Input/__test__/__snapshots__/Input.spec.js.snap +7 -3
- package/src/components/Input/__test__/__snapshots__/TextInput.spec.js.snap +7 -1
- package/src/components/Label/Label.vue +2 -2
- package/src/components/Popover/__tests__/Popover.spec.js +147 -0
- package/src/components/Popover/__tests__/__snapshots__/Popover.spec.js.snap +8 -0
- package/src/components/Popover/index.vue +146 -0
- package/src/components/Select/SelectOption.vue +65 -0
- package/src/components/Select/__tests__/Select.spec.js +412 -0
- package/src/components/Select/__tests__/SelectItem.spec.js +330 -0
- package/src/components/Select/__tests__/SelectOption.spec.js +174 -0
- package/src/components/Select/__tests__/__snapshots__/Select.spec.js.snap +97 -0
- package/src/components/Select/__tests__/__snapshots__/SelectItem.spec.js.snap +15 -0
- package/src/components/Select/__tests__/__snapshots__/SelectOption.spec.js.snap +25 -0
- package/src/components/Select/index.vue +245 -0
- package/src/components/Tab/Tab.vue +37 -23
- package/src/components/Tab/__test__/__snapshots__/Tab.spec.js.snap +1 -1
- package/src/locales/en.json +2 -1
- package/src/locales/es.json +2 -1
- package/src/locales/pt_br.json +2 -1
- package/src/stories/DataTable.stories.js +60 -0
- package/src/stories/Input.stories.js +6 -0
- package/src/stories/Popover.stories.js +39 -0
- package/src/stories/Select.stories.js +91 -0
- package/src/stories/Tab.stories.js +11 -4
|
@@ -6,30 +6,19 @@
|
|
|
6
6
|
>
|
|
7
7
|
<slot name="label" />
|
|
8
8
|
</p>
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
<UnnnicLabel
|
|
10
11
|
v-else-if="label"
|
|
11
12
|
class="unnnic-form__label"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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-
|
|
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
|
|
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`] = `
|
|
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
|
|
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<
|
|
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
|
+
`;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="unnnic-popover"
|
|
4
|
+
ref="popover"
|
|
5
|
+
>
|
|
6
|
+
<div
|
|
7
|
+
class="unnnic-popover__trigger"
|
|
8
|
+
data-testid="popover-trigger"
|
|
9
|
+
@click="toggleOpen()"
|
|
10
|
+
>
|
|
11
|
+
<slot name="trigger" />
|
|
12
|
+
</div>
|
|
13
|
+
<div
|
|
14
|
+
v-if="open"
|
|
15
|
+
class="unnnic-popover__balloon"
|
|
16
|
+
data-testid="popover-balloon"
|
|
17
|
+
>
|
|
18
|
+
<slot name="content" />
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue';
|
|
25
|
+
import { onClickOutside, useResizeObserver } from '@vueuse/core';
|
|
26
|
+
|
|
27
|
+
const target = useTemplateRef<HTMLDivElement>('popover');
|
|
28
|
+
|
|
29
|
+
const popoverWidth = ref<string>('');
|
|
30
|
+
|
|
31
|
+
useResizeObserver(target, (entries) => {
|
|
32
|
+
const entry = entries[0];
|
|
33
|
+
const { width } = entry.contentRect;
|
|
34
|
+
popoverWidth.value = `${width}px`;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
onClickOutside(target, () => {
|
|
38
|
+
if (props.persistent) return;
|
|
39
|
+
open.value = false;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
defineOptions({
|
|
43
|
+
name: 'UnnnicPopover',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
interface PopoverBalloonProps {
|
|
47
|
+
width?: string;
|
|
48
|
+
height?: string;
|
|
49
|
+
maxHeight?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface PopoverProps {
|
|
53
|
+
modelValue?: boolean;
|
|
54
|
+
persistent?: boolean;
|
|
55
|
+
popoverBalloonProps?: PopoverBalloonProps;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const props = withDefaults(defineProps<PopoverProps>(), {
|
|
59
|
+
modelValue: undefined,
|
|
60
|
+
persistent: false,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const emit = defineEmits<{
|
|
64
|
+
'update:modelValue': [value: boolean];
|
|
65
|
+
}>();
|
|
66
|
+
|
|
67
|
+
const useModelValue = computed(() => props.modelValue !== undefined);
|
|
68
|
+
|
|
69
|
+
const open = ref<boolean>(
|
|
70
|
+
useModelValue.value ? Boolean(props.modelValue) : false,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const toggleOpen = () => {
|
|
74
|
+
open.value = !open.value;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const calculatedPopoverWidth = computed(() => {
|
|
78
|
+
return props.popoverBalloonProps?.width || popoverWidth.value;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const popoverHeight = computed(() => {
|
|
82
|
+
return props.popoverBalloonProps?.height || 'unset';
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const popoverMaxHeight = computed(() => {
|
|
86
|
+
return props.popoverBalloonProps?.maxHeight || 'unset';
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
onMounted(() => {
|
|
90
|
+
if (useModelValue.value) {
|
|
91
|
+
open.value = Boolean(props.modelValue);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
watch(open, (value) => {
|
|
96
|
+
if (useModelValue.value) {
|
|
97
|
+
emit('update:modelValue', value);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
watch(
|
|
102
|
+
() => props.modelValue,
|
|
103
|
+
(value) => {
|
|
104
|
+
open.value = !!value;
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<style lang="scss" scoped>
|
|
110
|
+
@use '@/assets/scss/unnnic' as *;
|
|
111
|
+
|
|
112
|
+
* {
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 0;
|
|
115
|
+
box-sizing: border-box;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.unnnic-popover {
|
|
119
|
+
&__balloon {
|
|
120
|
+
border-radius: $unnnic-radius-2;
|
|
121
|
+
padding: $unnnic-space-4;
|
|
122
|
+
background: $unnnic-color-bg-base;
|
|
123
|
+
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.16);
|
|
124
|
+
// margin-top: $unnnic-space-1;
|
|
125
|
+
position: fixed;
|
|
126
|
+
width: v-bind(calculatedPopoverWidth);
|
|
127
|
+
height: v-bind(popoverHeight);
|
|
128
|
+
max-height: v-bind(popoverMaxHeight);
|
|
129
|
+
overflow: auto;
|
|
130
|
+
|
|
131
|
+
&::-webkit-scrollbar {
|
|
132
|
+
width: $unnnic-spacing-inline-nano;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&::-webkit-scrollbar-thumb {
|
|
136
|
+
background: $unnnic-color-neutral-cleanest;
|
|
137
|
+
border-radius: $unnnic-border-radius-pill;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&::-webkit-scrollbar-track {
|
|
141
|
+
background: $unnnic-color-neutral-soft;
|
|
142
|
+
border-radius: $unnnic-border-radius-pill;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="[
|
|
4
|
+
'unnnic-select-option',
|
|
5
|
+
{
|
|
6
|
+
'unnnic-select-option--disabled': props.disabled,
|
|
7
|
+
'unnnic-select-option--active': props.active,
|
|
8
|
+
'unnnic-select-option--focused': props.focused,
|
|
9
|
+
},
|
|
10
|
+
]"
|
|
11
|
+
>
|
|
12
|
+
<p class="unnnic-select-option__label">{{ props.label }}</p>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
defineOptions({
|
|
18
|
+
name: 'UnnnicSelectOption',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
interface SelectOptionProps {
|
|
22
|
+
label: string;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
active?: boolean;
|
|
25
|
+
focused?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<SelectOptionProps>(), {
|
|
29
|
+
disabled: false,
|
|
30
|
+
active: false,
|
|
31
|
+
focused: false,
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style lang="scss" scoped>
|
|
36
|
+
@use '@/assets/scss/unnnic' as *;
|
|
37
|
+
* {
|
|
38
|
+
margin: 0;
|
|
39
|
+
padding: 0;
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.unnnic-select-option {
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
border-radius: $unnnic-radius-1;
|
|
46
|
+
padding: $unnnic-space-2 $unnnic-space-4;
|
|
47
|
+
font: $unnnic-font-emphasis;
|
|
48
|
+
|
|
49
|
+
&:hover:not(&--active):not(&--disabled),
|
|
50
|
+
&--focused {
|
|
51
|
+
background-color: $unnnic-color-bg-soft;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&--active {
|
|
55
|
+
background-color: $unnnic-color-bg-active;
|
|
56
|
+
color: $unnnic-color-fg-inverted;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&--disabled {
|
|
60
|
+
color: $unnnic-color-fg-muted;
|
|
61
|
+
background-color: $unnnic-color-bg-muted;
|
|
62
|
+
cursor: not-allowed;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</style>
|