@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.
Files changed (50) hide show
  1. package/lib/components/backdrop/VtsBackdrop.vue +1 -1
  2. package/lib/components/column/VtsColumn.vue +21 -0
  3. package/lib/components/columns/VtsColumns.vue +38 -0
  4. package/lib/components/copy-button/VtsCopyButton.vue +29 -0
  5. package/lib/components/enabled-state/VtsEnabledState.vue +23 -0
  6. package/lib/components/icon/VtsIcon.vue +9 -1
  7. package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -0
  8. package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
  9. package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +1 -1
  10. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +26 -7
  11. package/lib/components/relative-time/VtsRelativeTime.vue +18 -0
  12. package/lib/components/select/VtsOption.vue +24 -0
  13. package/lib/components/select/VtsSelect.vue +96 -0
  14. package/lib/components/state-hero/VtsLoadingHero.vue +45 -4
  15. package/lib/components/tree/VtsTreeItem.vue +11 -1
  16. package/lib/components/ui/alert/UiAlert.vue +105 -0
  17. package/lib/components/ui/circle-progress-bar/UiCircleProgressBar.vue +212 -0
  18. package/lib/components/ui/dropdown/UiDropdownList.vue +10 -2
  19. package/lib/components/ui/head-bar/UiHeadBar.vue +2 -2
  20. package/lib/components/ui/info/UiInfo.vue +5 -3
  21. package/lib/components/ui/input/UiInput.vue +126 -109
  22. package/lib/composables/chart-theme.composable.ts +3 -3
  23. package/lib/composables/relative-time.composable.ts +1 -1
  24. package/lib/i18n.ts +4 -0
  25. package/lib/locales/cs.json +65 -18
  26. package/lib/locales/en.json +65 -1
  27. package/lib/locales/es.json +60 -13
  28. package/lib/locales/fa.json +59 -12
  29. package/lib/locales/fr.json +67 -3
  30. package/lib/locales/it.json +145 -7
  31. package/lib/locales/nl.json +502 -0
  32. package/lib/locales/ru.json +91 -1
  33. package/lib/locales/sv.json +75 -19
  34. package/lib/packages/collection/README.md +172 -0
  35. package/lib/packages/collection/create-collection.ts +74 -0
  36. package/lib/packages/collection/create-item.ts +39 -0
  37. package/lib/packages/collection/guess-item-id.ts +26 -0
  38. package/lib/packages/collection/index.ts +2 -0
  39. package/lib/packages/collection/types.ts +57 -0
  40. package/lib/packages/collection/use-collection.ts +47 -0
  41. package/lib/packages/collection/use-flag-registry.ts +64 -0
  42. package/lib/packages/form-select/README.md +96 -0
  43. package/lib/packages/form-select/index.ts +2 -0
  44. package/lib/packages/form-select/types.ts +75 -0
  45. package/lib/packages/form-select/use-form-option-controller.ts +50 -0
  46. package/lib/packages/form-select/use-form-select-controller.ts +205 -0
  47. package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +157 -0
  48. package/lib/packages/form-select/use-form-select.ts +193 -0
  49. package/lib/stores/sidebar.store.ts +14 -1
  50. package/package.json +1 -1
@@ -6,6 +6,6 @@
6
6
  .vts-backdrop {
7
7
  position: fixed;
8
8
  inset: 0;
9
- z-index: 1000;
9
+ z-index: 1010;
10
10
  }
11
11
  </style>
@@ -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>
@@ -68,6 +68,7 @@ const labelAccent = useMapper<InfoAccent, LabelAccent>(
68
68
  success: 'neutral',
69
69
  warning: 'warning',
70
70
  danger: 'danger',
71
+ muted: 'neutral',
71
72
  },
72
73
  'neutral'
73
74
  )
@@ -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: 1001;
61
+ z-index: 1010;
62
62
  transition:
63
63
  margin-left 0.25s,
64
64
  transform 0.25s;
@@ -8,6 +8,6 @@
8
8
  .vts-quick-info-column {
9
9
  display: flex;
10
10
  flex-direction: column;
11
- gap: 0.8rem;
11
+ gap: 1.6rem;
12
12
  }
13
13
  </style>
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <div class="vts-quick-info-row">
3
- <span class="typo-body-bold">
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 v-tooltip class="typo-body-regular value text-ellipsis">
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
- align-items: center;
34
- gap: 1rem;
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
- .value:empty::before {
37
- content: '-';
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
- <VtsStateHero :type busy>
3
- {{ $t('loading-in-progress') }}
4
- </VtsStateHero>
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>