@livestorm/ui-vue 1.1.1 → 1.1.2
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/.env +1 -0
- package/.eslintignore +4 -0
- package/.eslintrc.js +47 -0
- package/.pkg +11 -0
- package/babel.config.js +5 -0
- package/build/Dockerfile +31 -0
- package/ci/ci.yml +54 -0
- package/components-vite.js +29 -0
- package/components-webpack.js +29 -0
- package/components.d.ts +4 -0
- package/index.d.ts +3 -0
- package/index.js +24 -0
- package/package.json +1 -4
- package/plugins.js +17 -0
- package/postcss.config.js +1 -0
- package/src/components/BaseAutocomplete/BaseAutocomplete.spec.js +506 -0
- package/src/components/BaseAutocomplete/BaseAutocomplete.stories.js +184 -0
- package/src/components/BaseAutocomplete/BaseAutocomplete.vue +431 -0
- package/src/components/BaseBanner/BaseBanner.spec.js +32 -0
- package/src/components/BaseBanner/BaseBanner.stories.js +133 -0
- package/src/components/BaseBanner/BaseBanner.vue +229 -0
- package/src/components/BaseButton/BaseButton.spec.js +98 -0
- package/src/components/BaseButton/BaseButton.stories.js +291 -0
- package/src/components/BaseButton/BaseButton.vue +193 -0
- package/src/components/BaseButton/__snapshots__/BaseButton.spec.js.snap +37 -0
- package/src/components/BaseButton/__snapshots__/BaseButton.spec.js.snap.old +31 -0
- package/src/components/BaseCheckbox/BaseCheckbox.stories.js +82 -0
- package/src/components/BaseCheckbox/BaseCheckbox.vue +161 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenu.stories.js +84 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenu.vue +251 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuButton.vue +26 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuDivider.vue +10 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuItem.vue +206 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuSearch.vue +49 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuTitle.vue +26 -0
- package/src/components/BaseModal/BaseModal.stories.js +143 -0
- package/src/components/BaseModal/BaseModal.vue +187 -0
- package/src/components/BaseModal/BaseModalContent.vue +164 -0
- package/src/components/BaseModal/BaseModalContentOld.vue +242 -0
- package/src/components/BaseRating/BaseRating.spec.js +46 -0
- package/src/components/BaseRating/BaseRating.stories.js +46 -0
- package/src/components/BaseRating/BaseRating.vue +119 -0
- package/src/components/BaseSelect/BaseSelect.stories.js +177 -0
- package/src/components/BaseSelect/BaseSelect.vue +443 -0
- package/src/components/BaseSelect/BaseSelectGroup.vue +34 -0
- package/src/components/BaseSelect/BaseSelectOption.vue +219 -0
- package/src/components/BaseSwitch/BaseSwitch.stories.js +44 -0
- package/src/components/BaseSwitch/BaseSwitch.vue +114 -0
- package/src/components/BaseTag/BaseTag.stories.js +123 -0
- package/src/components/BaseTag/BaseTag.vue +244 -0
- package/src/css/body.postcss +29 -0
- package/src/css/button.postcss +354 -0
- package/src/css/checkbox-radio.postcss +152 -0
- package/src/css/icon.postcss +9 -0
- package/src/css/loader.postcss +120 -0
- package/src/css/popper.postcss +143 -0
- package/src/css/tailwind-utilities.postcss +1 -0
- package/src/css/tailwind.postcss +2 -0
- package/src/css/transitions.postcss +66 -0
- package/src/mixins/DynamicAction.js +59 -0
- package/src/utils/breakpoints.js +8 -0
- package/src/utils/bus.js +3 -0
- package/src/utils/clipboard.js +38 -0
- package/src/utils/devices.js +134 -0
- package/src/utils/elements.js +25 -0
- package/src/utils/index.js +8 -0
- package/src/utils/responsive.js +61 -0
- package/src/utils/router.js +94 -0
- package/src/utils/slot.js +27 -0
- package/src/utils/stack-view.js +116 -0
- package/src/utils/uid.js +5 -0
- package/style-postcss.d.ts +3 -0
- package/style-postcss.js +2 -0
- package/style.js +12 -0
- package/tailwind.config.base.cjs +88 -0
- package/tailwind.config.js +9 -0
- package/tsconfig.check.json +13 -0
- package/tsconfig.json +66 -0
- package/vite.config.js +43 -0
- package/vitest.setup.js +26 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, toRefs, useSlots, onMounted } from 'vue'
|
|
3
|
+
import { onClickOutside } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
const emit = defineEmits(['update:modelValue', 'update:search'])
|
|
6
|
+
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
modelValue: {
|
|
9
|
+
type: [String, Array, Object],
|
|
10
|
+
default: null,
|
|
11
|
+
},
|
|
12
|
+
size: {
|
|
13
|
+
type: String,
|
|
14
|
+
validator: (value) => ['small', 'medium', 'large'].includes(value),
|
|
15
|
+
default: 'medium',
|
|
16
|
+
},
|
|
17
|
+
placeholder: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: null,
|
|
20
|
+
},
|
|
21
|
+
prependIcon: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: null,
|
|
24
|
+
},
|
|
25
|
+
items: {
|
|
26
|
+
type: Array,
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
multiple: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
hideSelected: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
noFilter: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
error: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
hideNoData: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
autofocus: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
noDataText: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: null,
|
|
56
|
+
},
|
|
57
|
+
clearable: {
|
|
58
|
+
type: Boolean,
|
|
59
|
+
default: false,
|
|
60
|
+
},
|
|
61
|
+
creatable: {
|
|
62
|
+
type: Boolean,
|
|
63
|
+
default: false,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const { items, modelValue, multiple, hideSelected, noFilter, hideNoData, autofocus, creatable } = toRefs(props)
|
|
68
|
+
|
|
69
|
+
const slots = useSlots()
|
|
70
|
+
|
|
71
|
+
const hasFocus = ref(false)
|
|
72
|
+
const selection = computed(() => multiple.value ? (modelValue.value?.map(item => normalizeItem(item, true)) || []) : normalizeItem(modelValue.value, true))
|
|
73
|
+
const search = ref(multiple.value ? null : itemText(selection.value))
|
|
74
|
+
const normalizedItems = computed(() => items.value?.map(normalizeItem) || [])
|
|
75
|
+
const searching = ref(false)
|
|
76
|
+
const itemsKeyboardNavigationIndex = ref(null)
|
|
77
|
+
const tagsKeyboardNavigationIndex = ref(null)
|
|
78
|
+
|
|
79
|
+
const input = ref(null)
|
|
80
|
+
const autocompleteWrapper = ref(null)
|
|
81
|
+
const dropdown = ref(null)
|
|
82
|
+
|
|
83
|
+
const filteredItems = computed(() => {
|
|
84
|
+
const filteredFromSearch = !noFilter.value && searching.value && search.value
|
|
85
|
+
? normalizedItems.value.filter(item => itemText(item).toLowerCase().includes(search.value.toLowerCase()))
|
|
86
|
+
: normalizedItems.value
|
|
87
|
+
const filteredFromSelected = hideSelected.value ? filteredFromSearch.filter(item => !isSelectedItem(item)) : filteredFromSearch
|
|
88
|
+
return filteredFromSelected
|
|
89
|
+
})
|
|
90
|
+
const hasTags = computed(() => multiple.value && selection.value.length > 0)
|
|
91
|
+
const showPopperContent = computed(() => {
|
|
92
|
+
if (hideNoData.value && filteredItems.value.length === 0 && (!creatable.value || !search.value)) return false
|
|
93
|
+
return hasFocus.value
|
|
94
|
+
})
|
|
95
|
+
const canCreateItem = computed(() => creatable.value && search.value && searching.value && !normalizedItems.value.some(item => itemValue(item) === search.value))
|
|
96
|
+
|
|
97
|
+
function normalizeItem (item) {
|
|
98
|
+
const isItemObject = item !== null && typeof item === 'object'
|
|
99
|
+
return {
|
|
100
|
+
...(isItemObject ? item : null),
|
|
101
|
+
value: isItemObject ? item.value : item,
|
|
102
|
+
text: isItemObject ? item.text : item,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getItemProps (item) {
|
|
107
|
+
const itemProps = normalizedItems.value.find(nItem => itemValue(nItem) === itemValue(item))
|
|
108
|
+
return itemProps ?? null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function createItem (item) {
|
|
112
|
+
const newItem = normalizeItem(item)
|
|
113
|
+
addSelectedItem(newItem)
|
|
114
|
+
|
|
115
|
+
if (multiple.value) {
|
|
116
|
+
normalizedItems.value.push(newItem)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function addSelectedItem (item) {
|
|
121
|
+
if (isSelectedItem(item)) {
|
|
122
|
+
removeSelectedItem(item)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (multiple.value) {
|
|
127
|
+
search.value = null
|
|
128
|
+
tagsKeyboardNavigationIndex.value = null
|
|
129
|
+
input.value.focus()
|
|
130
|
+
emit('update:modelValue', [...selection.value.map(item => item.value), itemValue(item)])
|
|
131
|
+
} else {
|
|
132
|
+
hasFocus.value = false
|
|
133
|
+
search.value = itemText(item)
|
|
134
|
+
emit('update:modelValue', selection.value)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
searching.value = false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function removeSelectedItem (item) {
|
|
141
|
+
if (multiple.value) {
|
|
142
|
+
emit('update:modelValue', selection.value.filter(value => itemValue(value) !== itemValue(item)).map(item => itemValue(item)))
|
|
143
|
+
input.value.focus()
|
|
144
|
+
} else {
|
|
145
|
+
hasFocus.value = false
|
|
146
|
+
search.value = null
|
|
147
|
+
emit('update:modelValue', null)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isSelectedItem (item) {
|
|
152
|
+
if (!selection.value) return false
|
|
153
|
+
return multiple.value
|
|
154
|
+
? selection.value.some(value => itemValue(value) === itemValue(item))
|
|
155
|
+
: itemValue(item) === itemValue(selection.value)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isKeyboardTargetItem (index) {
|
|
159
|
+
if (itemsKeyboardNavigationIndex.value === null) return false
|
|
160
|
+
return (itemsKeyboardNavigationIndex.value % filteredItems.value.length) === index
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isKeyboardTargetTag (index) {
|
|
164
|
+
if (tagsKeyboardNavigationIndex.value === null || !multiple.value) return false
|
|
165
|
+
return (tagsKeyboardNavigationIndex.value % selection.value.length) === index
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function onUserFocus (e) {
|
|
169
|
+
hasFocus.value = true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function onUserSearch () {
|
|
173
|
+
searching.value = true
|
|
174
|
+
itemsKeyboardNavigationIndex.value = null
|
|
175
|
+
tagsKeyboardNavigationIndex.value = null
|
|
176
|
+
|
|
177
|
+
if (!multiple.value) {
|
|
178
|
+
emit('update:modelValue', null)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
emit('update:search', search.value)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function onPopperHide () {
|
|
185
|
+
hasFocus.value = false
|
|
186
|
+
searching.value = false
|
|
187
|
+
itemsKeyboardNavigationIndex.value = null
|
|
188
|
+
tagsKeyboardNavigationIndex.value = null
|
|
189
|
+
input.value.blur()
|
|
190
|
+
if (multiple.value) {
|
|
191
|
+
search.value = null
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
*
|
|
197
|
+
* @param {('down'|'up'|'enter')} action
|
|
198
|
+
*/
|
|
199
|
+
function onUserKeyboardNavigateItems (action) {
|
|
200
|
+
if (action === 'up') {
|
|
201
|
+
itemsKeyboardNavigationIndex.value = itemsKeyboardNavigationIndex.value <= 0
|
|
202
|
+
? filteredItems.value.length - 1
|
|
203
|
+
: itemsKeyboardNavigationIndex.value - 1
|
|
204
|
+
} else if (action === 'down') {
|
|
205
|
+
itemsKeyboardNavigationIndex.value ??= -1
|
|
206
|
+
itemsKeyboardNavigationIndex.value++
|
|
207
|
+
} else if (action === 'enter') {
|
|
208
|
+
if (itemsKeyboardNavigationIndex.value !== null) {
|
|
209
|
+
const itemToSelect = filteredItems.value[itemsKeyboardNavigationIndex.value % filteredItems.value.length]
|
|
210
|
+
if (itemToSelect) {
|
|
211
|
+
addSelectedItem(itemToSelect)
|
|
212
|
+
}
|
|
213
|
+
} else if (canCreateItem.value) {
|
|
214
|
+
createItem(search.value)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
*
|
|
221
|
+
* @param {('left'|'right'|'delete')} action
|
|
222
|
+
*/
|
|
223
|
+
function onUserKeyboardNavigateTags (action) {
|
|
224
|
+
if (!!search.value || !multiple.value) return
|
|
225
|
+
tagsKeyboardNavigationIndex.value ??= -1
|
|
226
|
+
|
|
227
|
+
if (action === 'left') {
|
|
228
|
+
tagsKeyboardNavigationIndex.value = tagsKeyboardNavigationIndex.value <= 0
|
|
229
|
+
? selection.value.length - 1
|
|
230
|
+
: tagsKeyboardNavigationIndex.value - 1
|
|
231
|
+
} else if (action === 'right') {
|
|
232
|
+
tagsKeyboardNavigationIndex.value++
|
|
233
|
+
} else if (action === 'delete') {
|
|
234
|
+
if (tagsKeyboardNavigationIndex.value === -1) {
|
|
235
|
+
tagsKeyboardNavigationIndex.value = selection.value.length - 1
|
|
236
|
+
} else {
|
|
237
|
+
removeSelectedItem(selection.value[tagsKeyboardNavigationIndex.value % selection.value.length])
|
|
238
|
+
tagsKeyboardNavigationIndex.value = tagsKeyboardNavigationIndex.value - 1
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function onUserClickChevron () {
|
|
244
|
+
if (hasFocus.value) {
|
|
245
|
+
hasFocus.value = false
|
|
246
|
+
} else {
|
|
247
|
+
input.value.focus()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function onUserClickClear () {
|
|
252
|
+
if (multiple.value) {
|
|
253
|
+
emit('update:modelValue', [])
|
|
254
|
+
} else {
|
|
255
|
+
emit('update:modelValue', null)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
search.value = null
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function itemValue (item) {
|
|
262
|
+
return item.value
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function itemText (item) {
|
|
266
|
+
return item.text
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
onMounted(() => {
|
|
270
|
+
if (autofocus.value) {
|
|
271
|
+
input.value?.focus()
|
|
272
|
+
|
|
273
|
+
// If removed, unit tests fail
|
|
274
|
+
hasFocus.value = true
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
onClickOutside(autocompleteWrapper, () => {
|
|
279
|
+
hasFocus.value = false
|
|
280
|
+
}, {
|
|
281
|
+
ignore: [computed(() => dropdown.value.$refs.popperContent.$el)],
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
</script>
|
|
285
|
+
|
|
286
|
+
<template>
|
|
287
|
+
<VDropdown
|
|
288
|
+
ref="dropdown"
|
|
289
|
+
:shown="showPopperContent"
|
|
290
|
+
:triggers="[]"
|
|
291
|
+
auto-size
|
|
292
|
+
popper-class="no-padding"
|
|
293
|
+
no-auto-focus
|
|
294
|
+
:auto-hide="false"
|
|
295
|
+
@hide="onPopperHide"
|
|
296
|
+
>
|
|
297
|
+
<div
|
|
298
|
+
ref="autocompleteWrapper"
|
|
299
|
+
class="base-input base-form-style stretchable flex rounded-8 items-center"
|
|
300
|
+
:class="[`base-input-${size}`, {
|
|
301
|
+
error,
|
|
302
|
+
'!border-livestorm-600': showPopperContent,
|
|
303
|
+
}]"
|
|
304
|
+
>
|
|
305
|
+
<div
|
|
306
|
+
v-if="prependIcon"
|
|
307
|
+
:class="prependIcon"
|
|
308
|
+
class="w-4 h-4 mr-3"
|
|
309
|
+
/>
|
|
310
|
+
<div
|
|
311
|
+
class="flex flex-1 flex-wrap items-center"
|
|
312
|
+
:class="{
|
|
313
|
+
'mb-1': size == 'medium' && hasTags,
|
|
314
|
+
'mb-2': size === 'large' && hasTags
|
|
315
|
+
}"
|
|
316
|
+
>
|
|
317
|
+
<template v-if="hasTags">
|
|
318
|
+
<BaseTag
|
|
319
|
+
v-for="(item, index) in selection"
|
|
320
|
+
:key="itemValue(getItemProps(item))"
|
|
321
|
+
deletable
|
|
322
|
+
:theme="isKeyboardTargetTag(index) ? 'dark-grey' : 'light-grey'"
|
|
323
|
+
class="mr-1 self-baseline"
|
|
324
|
+
:class="{
|
|
325
|
+
'mt-1': size !== 'large',
|
|
326
|
+
'mt-2': size === 'large'
|
|
327
|
+
}"
|
|
328
|
+
:size="size === 'small' ? 'xsmall': 'small'"
|
|
329
|
+
@delete="removeSelectedItem(item)"
|
|
330
|
+
>
|
|
331
|
+
<div v-if="slots.tag">
|
|
332
|
+
<slot
|
|
333
|
+
name="tag"
|
|
334
|
+
v-bind="{
|
|
335
|
+
item: getItemProps(item),
|
|
336
|
+
index,
|
|
337
|
+
}"
|
|
338
|
+
/>
|
|
339
|
+
</div>
|
|
340
|
+
<template v-else>
|
|
341
|
+
{{ itemText(getItemProps(item)) }}
|
|
342
|
+
</template>
|
|
343
|
+
</BaseTag>
|
|
344
|
+
</template>
|
|
345
|
+
<input
|
|
346
|
+
ref="input"
|
|
347
|
+
v-model="search"
|
|
348
|
+
type="text"
|
|
349
|
+
data-testid="base-autocomplete-input"
|
|
350
|
+
class="flex-1 h-6 min-w-0 focus:min-w-[64px]"
|
|
351
|
+
:class="{
|
|
352
|
+
'mt-1': hasTags && size === 'medium',
|
|
353
|
+
'mt-2': hasTags && size === 'large'
|
|
354
|
+
}"
|
|
355
|
+
:placeholder="selection?.length ? null : placeholder"
|
|
356
|
+
@focus="onUserFocus"
|
|
357
|
+
@input="onUserSearch"
|
|
358
|
+
@keydown.up="onUserKeyboardNavigateItems('up')"
|
|
359
|
+
@keydown.down="onUserKeyboardNavigateItems('down')"
|
|
360
|
+
@keydown.enter="onUserKeyboardNavigateItems('enter')"
|
|
361
|
+
@keydown.left="onUserKeyboardNavigateTags('left')"
|
|
362
|
+
@keydown.right="onUserKeyboardNavigateTags('right')"
|
|
363
|
+
@keydown.delete="onUserKeyboardNavigateTags('delete')"
|
|
364
|
+
>
|
|
365
|
+
</div>
|
|
366
|
+
<div
|
|
367
|
+
v-if="clearable && (search?.length > 0 || selection?.length > 0)"
|
|
368
|
+
class="i-ls-x cursor-pointer w-4 h-4 ml-3"
|
|
369
|
+
data-testid="base-autocomplete-clear"
|
|
370
|
+
@click="onUserClickClear"
|
|
371
|
+
/>
|
|
372
|
+
<div
|
|
373
|
+
v-if="filteredItems.length > 0"
|
|
374
|
+
:class="hasFocus ? 'i-ls-chevron-up' : 'i-ls-chevron-down'"
|
|
375
|
+
class="cursor-text w-4 h-4 ml-3"
|
|
376
|
+
@click="onUserClickChevron"
|
|
377
|
+
/>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<template #popper>
|
|
381
|
+
<div
|
|
382
|
+
class="bg-white"
|
|
383
|
+
>
|
|
384
|
+
<div
|
|
385
|
+
v-if="canCreateItem"
|
|
386
|
+
data-testid="base-autocomplete-create-item"
|
|
387
|
+
class="empty:hidden item text-livestorm-600 text-14-semibold"
|
|
388
|
+
:class="{
|
|
389
|
+
active: itemsKeyboardNavigationIndex === null
|
|
390
|
+
}"
|
|
391
|
+
@click="createItem(search)"
|
|
392
|
+
>
|
|
393
|
+
{{ i18n.t('base-autocomplete-create-item', { item: search }) }}
|
|
394
|
+
</div>
|
|
395
|
+
<div
|
|
396
|
+
v-for="(item, index) of filteredItems"
|
|
397
|
+
:key="itemValue(item)"
|
|
398
|
+
class="item"
|
|
399
|
+
data-testid="base-autocomplete-item"
|
|
400
|
+
:class="{
|
|
401
|
+
'selected': isSelectedItem(item),
|
|
402
|
+
'active': isKeyboardTargetItem(index),
|
|
403
|
+
}"
|
|
404
|
+
@click="addSelectedItem(item)"
|
|
405
|
+
>
|
|
406
|
+
{{ itemText(item) }}
|
|
407
|
+
</div>
|
|
408
|
+
<div
|
|
409
|
+
v-if="hideSelected && noDataText && filteredItems.length === 0"
|
|
410
|
+
class="px-4 py-2"
|
|
411
|
+
>
|
|
412
|
+
{{ noDataText }}
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
</template>
|
|
416
|
+
</VDropdown>
|
|
417
|
+
</template>
|
|
418
|
+
|
|
419
|
+
<style lang="postcss" scoped>
|
|
420
|
+
.item {
|
|
421
|
+
@apply px-4 py-2 cursor-pointer hover:bg-grey-100;
|
|
422
|
+
|
|
423
|
+
&.selected {
|
|
424
|
+
@apply bg-grey-100;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
&.active {
|
|
428
|
+
@apply bg-grey-300;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import BaseBanner from './BaseBanner.vue'
|
|
3
|
+
|
|
4
|
+
describe('BaseBanner', () => {
|
|
5
|
+
/** @type {import('@vue/test-utils').VueWrapper} */
|
|
6
|
+
let wrapper
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
wrapper = mount(BaseBanner, {
|
|
10
|
+
props: {
|
|
11
|
+
type: 'error',
|
|
12
|
+
},
|
|
13
|
+
slots: {
|
|
14
|
+
default: 'Main content',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('It should include info class when type prop change', async () => {
|
|
20
|
+
await wrapper.setProps({ type: 'info' })
|
|
21
|
+
expect(wrapper.find('[data-testid="base-banner"]').attributes().class.includes('info')).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('It should render slot content', () => {
|
|
25
|
+
expect(wrapper.find('[data-testid="base-banner"]').text()).toContain('Main content')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('It should render the correct icon when type change', async () => {
|
|
29
|
+
await wrapper.setProps({ type: 'warning' })
|
|
30
|
+
expect(wrapper.exists('.i-ls-alert-triangle')).toBe(true)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import BaseBanner from './BaseBanner.vue'
|
|
2
|
+
import { icons } from '@/../../frontend/.storybook/icons'
|
|
3
|
+
import { withDesign } from 'storybook-addon-designs'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'common/vue/components/BaseBanner',
|
|
7
|
+
component: BaseBanner,
|
|
8
|
+
decorators: [withDesign],
|
|
9
|
+
argTypes: {
|
|
10
|
+
type: {
|
|
11
|
+
control: {
|
|
12
|
+
type: 'select',
|
|
13
|
+
},
|
|
14
|
+
options: ['info', 'success', 'warning', 'alert'],
|
|
15
|
+
},
|
|
16
|
+
display: {
|
|
17
|
+
control: {
|
|
18
|
+
type: 'select',
|
|
19
|
+
},
|
|
20
|
+
options: ['inline', 'block'],
|
|
21
|
+
},
|
|
22
|
+
buttonIcon: {
|
|
23
|
+
control: {
|
|
24
|
+
type: 'select',
|
|
25
|
+
},
|
|
26
|
+
options: icons,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
parameters: {
|
|
30
|
+
design: {
|
|
31
|
+
type: 'figma',
|
|
32
|
+
url: 'https://www.figma.com/file/JY03TRyhauKw0jjoHFFtmMHV/0.-Design-system---Fondation?node-id=7846%3A13012',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Template = (args) => ({
|
|
38
|
+
setup: () => ({ args }),
|
|
39
|
+
template: `
|
|
40
|
+
<BaseBanner v-bind="args">
|
|
41
|
+
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
|
42
|
+
</BaseBanner>
|
|
43
|
+
`,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const WithDescriptionTemplate = (args) => ({
|
|
47
|
+
setup: () => ({ args }),
|
|
48
|
+
template: `
|
|
49
|
+
<BaseBanner v-bind="args">
|
|
50
|
+
Lorem ipsum dolor sit amet, consetetur sadipscing elitr
|
|
51
|
+
<template #description>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</template>
|
|
52
|
+
</BaseBanner>
|
|
53
|
+
`,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const WithButtonsSlotTemplate = (args) => ({
|
|
57
|
+
setup: () => ({ args }),
|
|
58
|
+
template: `
|
|
59
|
+
<BaseBanner v-bind="args">
|
|
60
|
+
Lorem ipsum dolor sit amet, consetetur sadipscing elitr
|
|
61
|
+
<template #description>At vero eos et accusam et justo duo <a href="#">dolores et ea rebum</a>. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</template>
|
|
62
|
+
<template #buttons>
|
|
63
|
+
<div class="flex">
|
|
64
|
+
<BaseButton
|
|
65
|
+
class="base-button-secondary mr-2"
|
|
66
|
+
href="#"
|
|
67
|
+
icon-right="i-ls-external-link"
|
|
68
|
+
target="_blank"
|
|
69
|
+
>
|
|
70
|
+
Read documentation
|
|
71
|
+
</BaseButton>
|
|
72
|
+
<BaseButton
|
|
73
|
+
class="base-button-primary"
|
|
74
|
+
>
|
|
75
|
+
Contact us
|
|
76
|
+
</BaseButton>
|
|
77
|
+
</div>
|
|
78
|
+
</template>
|
|
79
|
+
</BaseBanner>
|
|
80
|
+
`,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export const Info = Template.bind({})
|
|
84
|
+
Info.args = {
|
|
85
|
+
type: 'info',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const Success = Template.bind({})
|
|
89
|
+
Success.args = {
|
|
90
|
+
type: 'success',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const Warning = Template.bind({})
|
|
94
|
+
Warning.args = {
|
|
95
|
+
type: 'warning',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const Alert = Template.bind({})
|
|
99
|
+
Alert.args = {
|
|
100
|
+
type: 'alert',
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const WithDescription = WithDescriptionTemplate.bind({})
|
|
104
|
+
WithDescription.args = {
|
|
105
|
+
type: 'success',
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const WithOneCta = WithDescriptionTemplate.bind({})
|
|
109
|
+
WithOneCta.args = {
|
|
110
|
+
type: 'success',
|
|
111
|
+
buttonLabel: 'Call to action',
|
|
112
|
+
buttonIcon: 'i-ls-share',
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const WithTwoCta = WithButtonsSlotTemplate.bind({})
|
|
116
|
+
WithTwoCta.args = {
|
|
117
|
+
type: 'info',
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const Closable = WithDescriptionTemplate.bind({})
|
|
121
|
+
Closable.args = {
|
|
122
|
+
type: 'success',
|
|
123
|
+
buttonLabel: 'Call to action',
|
|
124
|
+
closable: true,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const BannerMode = WithDescriptionTemplate.bind({})
|
|
128
|
+
BannerMode.args = {
|
|
129
|
+
type: 'success',
|
|
130
|
+
buttonLabel: 'Call to action',
|
|
131
|
+
closable: true,
|
|
132
|
+
bannerMode: true,
|
|
133
|
+
}
|