@xen-orchestra/web-core 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/backdrop/VtsBackdrop.vue +1 -1
- package/lib/components/column/VtsColumn.vue +21 -0
- package/lib/components/columns/VtsColumns.vue +38 -0
- package/lib/components/copy-button/VtsCopyButton.vue +29 -0
- package/lib/components/enabled-state/VtsEnabledState.vue +23 -0
- package/lib/components/icon/VtsIcon.vue +9 -1
- package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -0
- package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
- package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +1 -1
- package/lib/components/quick-info-row/VtsQuickInfoRow.vue +26 -7
- package/lib/components/relative-time/VtsRelativeTime.vue +18 -0
- package/lib/components/select/VtsOption.vue +24 -0
- package/lib/components/select/VtsSelect.vue +96 -0
- package/lib/components/state-hero/VtsLoadingHero.vue +45 -4
- package/lib/components/tree/VtsTreeItem.vue +11 -1
- package/lib/components/ui/alert/UiAlert.vue +105 -0
- package/lib/components/ui/circle-progress-bar/UiCircleProgressBar.vue +212 -0
- package/lib/components/ui/dropdown/UiDropdownList.vue +10 -2
- package/lib/components/ui/head-bar/UiHeadBar.vue +2 -2
- package/lib/components/ui/info/UiInfo.vue +5 -3
- package/lib/components/ui/input/UiInput.vue +126 -109
- package/lib/composables/chart-theme.composable.ts +3 -3
- package/lib/composables/relative-time.composable.ts +1 -1
- package/lib/i18n.ts +4 -0
- package/lib/locales/cs.json +65 -18
- package/lib/locales/en.json +65 -1
- package/lib/locales/es.json +60 -13
- package/lib/locales/fa.json +59 -12
- package/lib/locales/fr.json +67 -3
- package/lib/locales/it.json +145 -7
- package/lib/locales/nl.json +502 -0
- package/lib/locales/ru.json +91 -1
- package/lib/locales/sv.json +75 -19
- package/lib/packages/collection/README.md +172 -0
- package/lib/packages/collection/create-collection.ts +74 -0
- package/lib/packages/collection/create-item.ts +39 -0
- package/lib/packages/collection/guess-item-id.ts +26 -0
- package/lib/packages/collection/index.ts +2 -0
- package/lib/packages/collection/types.ts +57 -0
- package/lib/packages/collection/use-collection.ts +47 -0
- package/lib/packages/collection/use-flag-registry.ts +64 -0
- package/lib/packages/form-select/README.md +96 -0
- package/lib/packages/form-select/index.ts +2 -0
- package/lib/packages/form-select/types.ts +75 -0
- package/lib/packages/form-select/use-form-option-controller.ts +50 -0
- package/lib/packages/form-select/use-form-select-controller.ts +205 -0
- package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +157 -0
- package/lib/packages/form-select/use-form-select.ts +193 -0
- package/lib/stores/sidebar.store.ts +14 -1
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vts-column">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
defineSlots<{
|
|
9
|
+
default(): any
|
|
10
|
+
}>()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<style lang="postcss" scoped>
|
|
14
|
+
.vts-column {
|
|
15
|
+
flex: 1;
|
|
16
|
+
flex-basis: 0;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 0.8rem;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vts-columns" :class="{ mobile: uiStore.isMobile }">
|
|
3
|
+
<component :is="nodes[index - 1] ?? VtsColumn" v-for="index of columns" :key="index" />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script lang="ts" setup>
|
|
8
|
+
import VtsColumn from '@core/components/column/VtsColumn.vue'
|
|
9
|
+
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
10
|
+
import { computed } from 'vue'
|
|
11
|
+
|
|
12
|
+
const { columns: _columns = 2 } = defineProps<{
|
|
13
|
+
columns?: number
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
const slots = defineSlots<{
|
|
17
|
+
default(): any
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const nodes = computed(() => slots.default())
|
|
21
|
+
|
|
22
|
+
const columns = computed(() => Math.max(_columns, nodes.value.length))
|
|
23
|
+
|
|
24
|
+
const uiStore = useUiStore()
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<style lang="postcss" scoped>
|
|
28
|
+
.vts-columns {
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 0.8rem;
|
|
31
|
+
padding: 0.8rem;
|
|
32
|
+
flex-direction: row;
|
|
33
|
+
|
|
34
|
+
&.mobile {
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiButtonIcon v-tooltip="copied && $t('core.copied')" :icon size="medium" accent="brand" @click="copyToClipboard()" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
7
|
+
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
8
|
+
import { faCheckCircle, faCopy } from '@fortawesome/free-solid-svg-icons'
|
|
9
|
+
import { useClipboard, useTimeoutFn } from '@vueuse/core'
|
|
10
|
+
import { ref } from 'vue'
|
|
11
|
+
|
|
12
|
+
const { value } = defineProps<{
|
|
13
|
+
value: string
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
const { copy, copied } = useClipboard()
|
|
17
|
+
|
|
18
|
+
const icon = ref(faCopy)
|
|
19
|
+
|
|
20
|
+
const { start: changeIcon } = useTimeoutFn(() => {
|
|
21
|
+
icon.value = faCopy
|
|
22
|
+
}, 1_500) // 1.5s is time to toltips is visible
|
|
23
|
+
|
|
24
|
+
function copyToClipboard() {
|
|
25
|
+
copy(value)
|
|
26
|
+
icon.value = faCheckCircle
|
|
27
|
+
changeIcon()
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiInfo :accent="state.accent">
|
|
3
|
+
{{ state.label }}
|
|
4
|
+
</UiInfo>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import UiInfo, { type InfoAccent } from '@core/components/ui/info/UiInfo.vue'
|
|
9
|
+
import { computed } from 'vue'
|
|
10
|
+
import { useI18n } from 'vue-i18n'
|
|
11
|
+
|
|
12
|
+
type StatesMap = { label: string; accent: InfoAccent }
|
|
13
|
+
|
|
14
|
+
const { enabled } = defineProps<{
|
|
15
|
+
enabled: boolean
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const { t } = useI18n()
|
|
19
|
+
|
|
20
|
+
const state = computed<StatesMap>(() =>
|
|
21
|
+
enabled ? { label: t('enabled'), accent: 'success' } : { label: t('disabled'), accent: 'muted' }
|
|
22
|
+
)
|
|
23
|
+
</script>
|
|
@@ -13,7 +13,7 @@ import UiLoader from '@core/components/ui/loader/UiLoader.vue'
|
|
|
13
13
|
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
|
|
14
14
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
|
15
15
|
|
|
16
|
-
export type IconAccent = 'current' | 'brand' | 'info' | 'success' | 'warning' | 'danger'
|
|
16
|
+
export type IconAccent = 'current' | 'brand' | 'info' | 'success' | 'warning' | 'danger' | 'muted'
|
|
17
17
|
|
|
18
18
|
defineProps<{
|
|
19
19
|
accent: IconAccent
|
|
@@ -88,5 +88,13 @@ defineProps<{
|
|
|
88
88
|
color: var(--color-danger-txt-item);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
&.muted {
|
|
93
|
+
color: var(--color-neutral-background-disabled);
|
|
94
|
+
|
|
95
|
+
.overlay-icon {
|
|
96
|
+
color: var(--color-neutral-txt-secondary);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
91
99
|
}
|
|
92
100
|
</style>
|
|
@@ -58,7 +58,7 @@ const ui = useUiStore()
|
|
|
58
58
|
background-color: var(--color-neutral-background-secondary);
|
|
59
59
|
border-right: 0.1rem solid var(--color-neutral-border);
|
|
60
60
|
width: v-bind('sidebar.cssWidth');
|
|
61
|
-
z-index:
|
|
61
|
+
z-index: 1010;
|
|
62
62
|
transition:
|
|
63
63
|
margin-left 0.25s,
|
|
64
64
|
transform 0.25s;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="vts-quick-info-row">
|
|
3
|
-
<span class="typo-body-
|
|
2
|
+
<div class="vts-quick-info-row" :class="{ mobile: uiStore.isMobile }">
|
|
3
|
+
<span v-tooltip class="typo-body-regular label text-ellipsis">
|
|
4
4
|
<slot name="label">
|
|
5
5
|
{{ label }}
|
|
6
6
|
</slot>
|
|
7
7
|
</span>
|
|
8
|
-
<span
|
|
8
|
+
<span class="typo-body-regular value">
|
|
9
9
|
<slot name="value">
|
|
10
10
|
{{ value }}
|
|
11
11
|
</slot>
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<script lang="ts" setup>
|
|
17
17
|
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
18
|
+
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
18
19
|
|
|
19
20
|
defineProps<{
|
|
20
21
|
label?: string
|
|
@@ -25,16 +26,34 @@ defineSlots<{
|
|
|
25
26
|
label?(): any
|
|
26
27
|
value?(): any
|
|
27
28
|
}>()
|
|
29
|
+
|
|
30
|
+
const uiStore = useUiStore()
|
|
28
31
|
</script>
|
|
29
32
|
|
|
30
33
|
<style lang="postcss" scoped>
|
|
31
34
|
.vts-quick-info-row {
|
|
32
35
|
display: flex;
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
gap: 2.4rem;
|
|
37
|
+
|
|
38
|
+
&.mobile {
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: 0.8rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.label {
|
|
44
|
+
flex-shrink: 0;
|
|
45
|
+
color: var(--color-neutral-txt-secondary);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.value {
|
|
49
|
+
color: var(--color-neutral-txt-primary);
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 0.8rem;
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
&:empty::before {
|
|
55
|
+
content: '-';
|
|
56
|
+
}
|
|
38
57
|
}
|
|
39
58
|
}
|
|
40
59
|
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :title="date.toLocaleString()">{{ relativeTime }}</span>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import useRelativeTime from '@core/composables/relative-time.composable'
|
|
7
|
+
import { parseDateTime } from '@core/utils/time.util'
|
|
8
|
+
import { useNow } from '@vueuse/core'
|
|
9
|
+
import { computed } from 'vue'
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
date: Date | number | string
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const date = computed(() => new Date(parseDateTime(props.date)))
|
|
16
|
+
const now = useNow({ interval: 1000 })
|
|
17
|
+
const relativeTime = useRelativeTime(date, now)
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiDropdown
|
|
3
|
+
ref="elementRef"
|
|
4
|
+
:checkbox="option.properties.multiple"
|
|
5
|
+
:disabled="option.properties.disabled"
|
|
6
|
+
:hover="option.flags.active"
|
|
7
|
+
:selected="option.flags.selected"
|
|
8
|
+
accent="normal"
|
|
9
|
+
>
|
|
10
|
+
<slot>{{ option.properties.label }}</slot>
|
|
11
|
+
</UiDropdown>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script generic="TOption extends FormOption" lang="ts" setup>
|
|
15
|
+
import UiDropdown from '@core/components/ui/dropdown/UiDropdown.vue'
|
|
16
|
+
import type { FormOption } from '@core/packages/form-select/types.ts'
|
|
17
|
+
import { useFormOptionController } from '@core/packages/form-select/use-form-option-controller.ts'
|
|
18
|
+
|
|
19
|
+
const { option } = defineProps<{
|
|
20
|
+
option: TOption
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const { elementRef } = useFormOptionController(() => option)
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="className" class="vts-select">
|
|
3
|
+
<VtsBackdrop v-if="isOpen" />
|
|
4
|
+
|
|
5
|
+
<UiInput
|
|
6
|
+
ref="triggerRef"
|
|
7
|
+
:accent
|
|
8
|
+
:model-value="selectedLabel"
|
|
9
|
+
:placeholder
|
|
10
|
+
:required
|
|
11
|
+
:right-icon="faAngleDown"
|
|
12
|
+
readonly
|
|
13
|
+
/>
|
|
14
|
+
|
|
15
|
+
<UiDropdownList v-if="isOpen" ref="dropdownRef" :style="floatingStyles" class="dropdown-list">
|
|
16
|
+
<template v-if="searchTerm !== undefined" #before>
|
|
17
|
+
<div class="search-container">
|
|
18
|
+
<UiInput
|
|
19
|
+
ref="searchRef"
|
|
20
|
+
v-model="searchTerm"
|
|
21
|
+
:placeholder="searchPlaceholder"
|
|
22
|
+
:right-icon="faMagnifyingGlass"
|
|
23
|
+
accent="brand"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
<UiDropdown v-if="loading || options.length === 0" accent="normal" disabled>
|
|
28
|
+
{{ loading ? t('loading-in-progress') : t('no-results') }}
|
|
29
|
+
</UiDropdown>
|
|
30
|
+
<template v-for="option of options" :key="option.id">
|
|
31
|
+
<slot :option>
|
|
32
|
+
<VtsOption :option />
|
|
33
|
+
</slot>
|
|
34
|
+
</template>
|
|
35
|
+
</UiDropdownList>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script generic="TOption extends FormOption" lang="ts" setup>
|
|
40
|
+
import VtsBackdrop from '@core/components/backdrop/VtsBackdrop.vue'
|
|
41
|
+
import VtsOption from '@core/components/select/VtsOption.vue'
|
|
42
|
+
import UiDropdown from '@core/components/ui/dropdown/UiDropdown.vue'
|
|
43
|
+
import UiDropdownList from '@core/components/ui/dropdown/UiDropdownList.vue'
|
|
44
|
+
import UiInput from '@core/components/ui/input/UiInput.vue'
|
|
45
|
+
import type { FormOption } from '@core/packages/form-select/types.ts'
|
|
46
|
+
import { useFormSelectController } from '@core/packages/form-select/use-form-select-controller.ts'
|
|
47
|
+
import { toVariants } from '@core/utils/to-variants.util.ts'
|
|
48
|
+
import { faAngleDown, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'
|
|
49
|
+
import { computed } from 'vue'
|
|
50
|
+
import { useI18n } from 'vue-i18n'
|
|
51
|
+
|
|
52
|
+
const { accent, options, selectedLabel } = defineProps<{
|
|
53
|
+
accent: 'brand' | 'warning' | 'danger'
|
|
54
|
+
options: TOption[]
|
|
55
|
+
selectedLabel: string
|
|
56
|
+
required?: boolean
|
|
57
|
+
placeholder?: string
|
|
58
|
+
searchPlaceholder?: string
|
|
59
|
+
loading?: boolean
|
|
60
|
+
}>()
|
|
61
|
+
|
|
62
|
+
const searchTerm = defineModel<string>('search')
|
|
63
|
+
|
|
64
|
+
defineSlots<{
|
|
65
|
+
default(props: { option: TOption }): any
|
|
66
|
+
}>()
|
|
67
|
+
|
|
68
|
+
const { t } = useI18n()
|
|
69
|
+
|
|
70
|
+
const { triggerRef, dropdownRef, searchRef, isOpen, floatingStyles } = useFormSelectController({
|
|
71
|
+
options: () => options,
|
|
72
|
+
searchTerm,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const className = computed(() => toVariants({ accent }))
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<style lang="postcss" scoped>
|
|
79
|
+
.vts-select {
|
|
80
|
+
.ui-input:deep(input) {
|
|
81
|
+
cursor: default;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dropdown-list {
|
|
85
|
+
min-width: 40rem;
|
|
86
|
+
max-height: 36.2rem; /* 8 Dropdown items */
|
|
87
|
+
overflow: auto;
|
|
88
|
+
z-index: 1020;
|
|
89
|
+
|
|
90
|
+
.search-container {
|
|
91
|
+
background-color: var(--color-neutral-background-primary);
|
|
92
|
+
padding: 0.4rem;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
@@ -1,13 +1,54 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
<div class="vts-loading-hero">
|
|
3
|
+
<VtsStateHero :type busy>
|
|
4
|
+
{{ $t('loading-in-progress') }}
|
|
5
|
+
</VtsStateHero>
|
|
6
|
+
<div v-if="slots.title || slots.text" class="content">
|
|
7
|
+
<div v-if="slots.title" class="title" :class="className">
|
|
8
|
+
<slot name="title" />
|
|
9
|
+
</div>
|
|
10
|
+
<div v-if="slots.text" class="text typo-body-bold">
|
|
11
|
+
<slot name="text" />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
5
15
|
</template>
|
|
6
16
|
|
|
7
17
|
<script lang="ts" setup>
|
|
8
18
|
import VtsStateHero, { type StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
|
|
19
|
+
import { computed } from 'vue'
|
|
9
20
|
|
|
10
|
-
defineProps<{
|
|
21
|
+
const { type } = defineProps<{
|
|
11
22
|
type: StateHeroType
|
|
12
23
|
}>()
|
|
24
|
+
|
|
25
|
+
const slots = defineSlots<{
|
|
26
|
+
title?(): any
|
|
27
|
+
text?(): any
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const className = computed(() => (type === 'page' ? 'typo-h1' : 'typo-h2'))
|
|
13
31
|
</script>
|
|
32
|
+
|
|
33
|
+
<style lang="postcss" scoped>
|
|
34
|
+
.vts-loading-hero {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: 1.6rem;
|
|
38
|
+
|
|
39
|
+
.content {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 2.4rem;
|
|
43
|
+
text-align: center;
|
|
44
|
+
|
|
45
|
+
.title {
|
|
46
|
+
color: var(--color-neutral-txt-primary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.text {
|
|
50
|
+
color: var(--color-neutral-txt-secondary);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<li class="vts-tree-item">
|
|
2
|
+
<li class="vts-tree-item" @click="handleClick()">
|
|
3
3
|
<slot />
|
|
4
4
|
<slot v-if="expanded" name="sublist" />
|
|
5
5
|
</li>
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
8
|
<script lang="ts" setup>
|
|
9
|
+
import { useSidebarStore } from '@core/stores/sidebar.store'
|
|
10
|
+
import { useUiStore } from '@core/stores/ui.store'
|
|
9
11
|
import { IK_TREE_ITEM_EXPANDED, IK_TREE_ITEM_HAS_CHILDREN } from '@core/utils/injection-keys.util'
|
|
10
12
|
import { onBeforeMount, onBeforeUpdate, provide, ref, toRef, useSlots } from 'vue'
|
|
11
13
|
|
|
@@ -18,6 +20,8 @@ defineSlots<{
|
|
|
18
20
|
sublist?(): any
|
|
19
21
|
}>()
|
|
20
22
|
|
|
23
|
+
const sidebar = useSidebarStore()
|
|
24
|
+
const uiStore = useUiStore()
|
|
21
25
|
const hasChildren = ref(false)
|
|
22
26
|
|
|
23
27
|
const updateHasChildren = () => {
|
|
@@ -25,6 +29,12 @@ const updateHasChildren = () => {
|
|
|
25
29
|
hasChildren.value = sublist !== undefined
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
const handleClick = () => {
|
|
33
|
+
if (uiStore.isMobile) {
|
|
34
|
+
sidebar.toggleExpand(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
onBeforeMount(() => updateHasChildren())
|
|
29
39
|
onBeforeUpdate(() => updateHasChildren())
|
|
30
40
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<!-- v3 -->
|
|
2
|
+
<template>
|
|
3
|
+
<div :class="toVariants({ accent })" class="ui-alert">
|
|
4
|
+
<div class="content">
|
|
5
|
+
<VtsIcon class="information-icon" :accent :icon="faCircle" :overlay-icon="icon" />
|
|
6
|
+
<div class="alert typo-body-regular-small">
|
|
7
|
+
<div>
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
<div v-if="slots.description">
|
|
11
|
+
<slot name="description" />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<UiButtonIcon
|
|
15
|
+
v-if="close"
|
|
16
|
+
class="close-button"
|
|
17
|
+
:icon="faXmark"
|
|
18
|
+
accent="brand"
|
|
19
|
+
size="medium"
|
|
20
|
+
@click="emit('close')"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
|
28
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
29
|
+
import { toVariants } from '@core/utils/to-variants.util'
|
|
30
|
+
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
|
|
31
|
+
import { faCheck, faCircle, faExclamation, faInfo, faXmark } from '@fortawesome/free-solid-svg-icons'
|
|
32
|
+
import { computed } from 'vue'
|
|
33
|
+
|
|
34
|
+
type AlertAccent = 'info' | 'success' | 'warning' | 'danger'
|
|
35
|
+
|
|
36
|
+
const { accent } = defineProps<{
|
|
37
|
+
accent: AlertAccent
|
|
38
|
+
close?: boolean
|
|
39
|
+
}>()
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits<{
|
|
42
|
+
close: []
|
|
43
|
+
}>()
|
|
44
|
+
|
|
45
|
+
const slots = defineSlots<{
|
|
46
|
+
default(): any
|
|
47
|
+
description?(): any
|
|
48
|
+
}>()
|
|
49
|
+
|
|
50
|
+
const iconByAccent: Record<AlertAccent, IconDefinition> = {
|
|
51
|
+
info: faInfo,
|
|
52
|
+
success: faCheck,
|
|
53
|
+
warning: faExclamation,
|
|
54
|
+
danger: faXmark,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const icon = computed(() => iconByAccent[accent])
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<style scoped lang="postcss">
|
|
61
|
+
.ui-alert {
|
|
62
|
+
padding: 1.6rem;
|
|
63
|
+
border: 0.1rem solid;
|
|
64
|
+
border-radius: 0.4rem;
|
|
65
|
+
|
|
66
|
+
.content {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: flex-start;
|
|
69
|
+
gap: 1.6rem;
|
|
70
|
+
|
|
71
|
+
.information-icon {
|
|
72
|
+
font-size: 2.7rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.alert {
|
|
76
|
+
align-self: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.close-button {
|
|
80
|
+
margin-inline-start: auto;
|
|
81
|
+
flex-shrink: 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&.accent--info {
|
|
86
|
+
background-color: var(--color-info-background-selected);
|
|
87
|
+
border-color: var(--color-info-item-base);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&.accent--success {
|
|
91
|
+
background-color: var(--color-success-background-selected);
|
|
92
|
+
border-color: var(--color-success-item-base);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&.accent--warning {
|
|
96
|
+
background-color: var(--color-warning-background-selected);
|
|
97
|
+
border-color: var(--color-warning-item-base);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&.accent--danger {
|
|
101
|
+
background-color: var(--color-danger-background-selected);
|
|
102
|
+
border-color: var(--color-danger-item-base);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</style>
|