@xen-orchestra/web-core 0.17.0 → 0.19.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 (38) hide show
  1. package/lib/components/icon/VtsIcon.vue +9 -1
  2. package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -0
  3. package/lib/components/{charts/LinearChart.md → linear-chart/VtsLinearChart.md} +3 -3
  4. package/lib/components/{charts/LinearChart.vue → linear-chart/VtsLinearChart.vue} +12 -11
  5. package/lib/components/quick-info-card/VtsQuickInfoCard.vue +29 -0
  6. package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +13 -0
  7. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +40 -0
  8. package/lib/components/ui/alert/UiAlert.vue +105 -0
  9. package/lib/components/ui/card/UiCard.vue +12 -0
  10. package/lib/components/ui/circle-progress-bar/UiCircleProgressBar.vue +212 -0
  11. package/lib/components/ui/info/UiInfo.vue +5 -3
  12. package/lib/components/ui/input/UiInput.vue +126 -109
  13. package/lib/components/ui/radio-button/UiRadioButton.vue +1 -1
  14. package/lib/components/ui/toaster/UiToaster.vue +6 -2
  15. package/lib/composables/chart-theme.composable.ts +5 -5
  16. package/lib/composables/relative-time.composable.md +18 -0
  17. package/lib/composables/relative-time.composable.ts +66 -0
  18. package/lib/i18n.ts +16 -0
  19. package/lib/locales/cs.json +98 -18
  20. package/lib/locales/de.json +233 -24
  21. package/lib/locales/en.json +58 -2
  22. package/lib/locales/es.json +94 -14
  23. package/lib/locales/fa.json +259 -9
  24. package/lib/locales/fr.json +58 -2
  25. package/lib/locales/it.json +316 -0
  26. package/lib/locales/nl.json +503 -0
  27. package/lib/locales/ru.json +181 -0
  28. package/lib/locales/sv.json +92 -10
  29. package/lib/locales/uk.json +1 -0
  30. package/lib/packages/collection/README.md +167 -0
  31. package/lib/packages/collection/build-item.ts +45 -0
  32. package/lib/packages/collection/create-collection.ts +60 -0
  33. package/lib/packages/collection/index.ts +5 -0
  34. package/lib/packages/collection/types.ts +29 -0
  35. package/lib/packages/collection/use-collection.ts +15 -0
  36. package/lib/packages/collection/use-flag-registry.ts +47 -0
  37. package/lib/utils/time.util.ts +18 -0
  38. package/package.json +1 -1
@@ -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
  )
@@ -2,12 +2,12 @@
2
2
 
3
3
  ```vue
4
4
  <template>
5
- <LinearChart :data="data" :value-formatter="customValueFormatter" />
5
+ <VtsLinearChart :data :value-formatter="customValueFormatter" />
6
6
  </template>
7
7
 
8
8
  <script lang="ts" setup>
9
- import type { LinearChartData } from '@/types/chart'
10
- import LinearChart from '@/components/charts/LinearChart.vue'
9
+ import type { LinearChartData } from '@core/types/chart'
10
+ import VstLinearChart from '@core/components/linear-chart/VtsLinearChart.vue'
11
11
 
12
12
  const data: LinearChartData = [
13
13
  {
@@ -1,20 +1,23 @@
1
1
  <template>
2
- <VueCharts :option autoresize class="chart" />
2
+ <VueCharts :option autoresize class="vts-linear-chart" />
3
3
  </template>
4
4
 
5
5
  <script lang="ts" setup>
6
6
  import type { LinearChartData, ValueFormatter } from '@core/types/chart'
7
- import { IK_CHART_VALUE_FORMATTER } from '@core/utils/injection-keys.util'
8
7
  import { utcFormat } from 'd3-time-format'
9
8
  import type { EChartsOption } from 'echarts'
10
9
  import { LineChart } from 'echarts/charts'
11
10
  import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
12
11
  import { use } from 'echarts/core'
13
12
  import { CanvasRenderer } from 'echarts/renderers'
14
- import { computed, provide } from 'vue'
13
+ import { computed } from 'vue'
15
14
  import VueCharts from 'vue-echarts'
16
15
 
17
- const props = defineProps<{
16
+ const {
17
+ data,
18
+ valueFormatter: _valueFormatter,
19
+ maxValue,
20
+ } = defineProps<{
18
21
  data: LinearChartData
19
22
  valueFormatter?: ValueFormatter
20
23
  maxValue?: number
@@ -23,7 +26,7 @@ const props = defineProps<{
23
26
  const Y_AXIS_MAX_VALUE = 200
24
27
 
25
28
  const valueFormatter = computed<ValueFormatter>(() => {
26
- const formatter = props.valueFormatter
29
+ const formatter = _valueFormatter
27
30
 
28
31
  return value => {
29
32
  if (formatter === undefined) {
@@ -34,13 +37,11 @@ const valueFormatter = computed<ValueFormatter>(() => {
34
37
  }
35
38
  })
36
39
 
37
- provide(IK_CHART_VALUE_FORMATTER, valueFormatter)
38
-
39
40
  use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent])
40
41
 
41
42
  const option = computed<EChartsOption>(() => ({
42
43
  legend: {
43
- data: props.data.map(series => series.label),
44
+ data: data.map(series => series.label),
44
45
  },
45
46
  tooltip: {
46
47
  valueFormatter: v => valueFormatter.value(v as number),
@@ -58,9 +59,9 @@ const option = computed<EChartsOption>(() => ({
58
59
  axisLabel: {
59
60
  formatter: valueFormatter.value,
60
61
  },
61
- max: props.maxValue ?? Y_AXIS_MAX_VALUE,
62
+ max: maxValue ?? Y_AXIS_MAX_VALUE,
62
63
  },
63
- series: props.data.map((series, index) => ({
64
+ series: data.map((series, index) => ({
64
65
  type: 'line',
65
66
  name: series.label,
66
67
  zlevel: index + 1,
@@ -70,7 +71,7 @@ const option = computed<EChartsOption>(() => ({
70
71
  </script>
71
72
 
72
73
  <style lang="postcss" scoped>
73
- .chart {
74
+ .vts-linear-chart {
74
75
  width: 100%;
75
76
  height: 30rem;
76
77
  }
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <UiCard class="vts-quick-info-card">
3
+ <UiCardTitle>{{ $t('quick-info') }}</UiCardTitle>
4
+ <VtsLoadingHero v-if="loading" type="card" />
5
+ <div v-else class="info-container">
6
+ <slot />
7
+ </div>
8
+ </UiCard>
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
13
+ import UiCard from '@core/components/ui/card/UiCard.vue'
14
+ import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
15
+
16
+ defineProps<{
17
+ loading: boolean
18
+ }>()
19
+ </script>
20
+
21
+ <style lang="postcss" scoped>
22
+ .vts-quick-info-card {
23
+ .info-container {
24
+ display: grid;
25
+ grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
26
+ gap: 2.4rem;
27
+ }
28
+ }
29
+ </style>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div class="vts-quick-info-column">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style lang="postcss" scoped>
8
+ .vts-quick-info-column {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 0.8rem;
12
+ }
13
+ </style>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <div class="vts-quick-info-row">
3
+ <span class="typo-body-bold">
4
+ <slot name="label">
5
+ {{ label }}
6
+ </slot>
7
+ </span>
8
+ <span v-tooltip class="typo-body-regular value text-ellipsis">
9
+ <slot name="value">
10
+ {{ value }}
11
+ </slot>
12
+ </span>
13
+ </div>
14
+ </template>
15
+
16
+ <script lang="ts" setup>
17
+ import { vTooltip } from '@core/directives/tooltip.directive'
18
+
19
+ defineProps<{
20
+ label?: string
21
+ value?: string
22
+ }>()
23
+
24
+ defineSlots<{
25
+ label?(): any
26
+ value?(): any
27
+ }>()
28
+ </script>
29
+
30
+ <style lang="postcss" scoped>
31
+ .vts-quick-info-row {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 1rem;
35
+
36
+ .value:empty::before {
37
+ content: '-';
38
+ }
39
+ }
40
+ </style>
@@ -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>
@@ -24,6 +24,18 @@ defineSlots<{
24
24
  border: 0.1rem solid var(--color-neutral-border);
25
25
  border-radius: 0.8rem;
26
26
 
27
+ &:has(> .vts-linear-chart) {
28
+ padding-inline: 0;
29
+
30
+ > *:not(.vts-linear-chart) {
31
+ padding-inline: 2.4rem;
32
+ }
33
+
34
+ :deep(.vts-linear-chart) {
35
+ padding-inline-end: 2.4rem;
36
+ }
37
+ }
38
+
27
39
  &.horizontal {
28
40
  flex-direction: row;
29
41
  }
@@ -0,0 +1,212 @@
1
+ <!-- v4 -->
2
+ <template>
3
+ <div class="ui-circle-progress-bar" :class="className">
4
+ <svg
5
+ :width="circleSize"
6
+ :height="circleSize"
7
+ :viewBox="`0 0 ${circleSize} ${circleSize}`"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ >
10
+ <circle :r="radius" :cx="circleSize / 2" :cy="circleSize / 2" fill="transparent" class="background" />
11
+ <circle
12
+ :r="radius"
13
+ :cx="circleSize / 2"
14
+ :cy="circleSize / 2"
15
+ fill="transparent"
16
+ class="fill"
17
+ :class="{ success: isCompleteWithSuccess }"
18
+ />
19
+ </svg>
20
+ <div v-if="size !== 'extra-small'" class="overlay">
21
+ <VtsIcon v-if="isComplete" class="icon" :icon :accent="iconAccent" />
22
+ <span v-else>{{ percentValue }}</span>
23
+ </div>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import VtsIcon from '@core/components/icon/VtsIcon.vue'
29
+ import { toVariants } from '@core/utils/to-variants.util.ts'
30
+ import { faCheck, faExclamation } from '@fortawesome/free-solid-svg-icons'
31
+ import { useClamp, useMax } from '@vueuse/math'
32
+ import { computed } from 'vue'
33
+ import { useI18n } from 'vue-i18n'
34
+
35
+ const {
36
+ accent,
37
+ size,
38
+ value: _value,
39
+ maxValue = 100,
40
+ } = defineProps<{
41
+ accent: CircleProgressBarAccent
42
+ size: CircleProgressBarSize
43
+ value: number
44
+ maxValue?: number
45
+ }>()
46
+
47
+ const { n } = useI18n()
48
+
49
+ type CircleProgressBarAccent = 'info' | 'warning' | 'danger'
50
+ type CircleProgressBarSize = 'extra-small' | 'small' | 'medium' | 'large'
51
+
52
+ const circleSizeMap = {
53
+ 'extra-small': 16,
54
+ small: 40,
55
+ medium: 64,
56
+ large: 164,
57
+ }
58
+
59
+ const strokeWidthMap = {
60
+ 'extra-small': 2,
61
+ small: 4,
62
+ medium: 6,
63
+ large: 16,
64
+ }
65
+
66
+ const fontClassesMap = {
67
+ 'extra-small': undefined,
68
+ small: 'typo-body-bold-small',
69
+ medium: 'typo-h5',
70
+ large: 'typo-h3',
71
+ }
72
+
73
+ const circleSize = computed(() => circleSizeMap[size])
74
+ const fontClass = computed(() => fontClassesMap[size])
75
+ const strokeWidth = computed(() => strokeWidthMap[size])
76
+
77
+ const radius = computed(() => (circleSize.value - strokeWidth.value) / 2)
78
+ const circumference = computed(() => 2 * Math.PI * radius.value)
79
+
80
+ const value = useMax(0, () => _value)
81
+ const valuePercent = useClamp(() => (value.value / maxValue) * 100 || 0, 0, 100)
82
+
83
+ const isComplete = computed(() => valuePercent.value >= 100)
84
+ const isCompleteWithSuccess = computed(() => isComplete.value && accent === 'info')
85
+
86
+ const dashOffset = computed(() => {
87
+ if (valuePercent.value > 100) return
88
+ return circumference.value * (1 - valuePercent.value / 100)
89
+ })
90
+
91
+ const percentValue = computed(() => n(valuePercent.value / 100, 'percent'))
92
+
93
+ const iconAccent = computed(() => (isCompleteWithSuccess.value ? 'success' : accent))
94
+ const icon = computed(() => (accent === 'warning' || accent === 'danger' ? faExclamation : faCheck))
95
+
96
+ const className = computed(() => [
97
+ fontClass.value,
98
+ toVariants({
99
+ accent,
100
+ size,
101
+ }),
102
+ ])
103
+ </script>
104
+
105
+ <style lang="postcss" scoped>
106
+ .ui-circle-progress-bar {
107
+ position: relative;
108
+ display: inline-flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+
112
+ /* ACCENT */
113
+
114
+ &.accent--info {
115
+ color: var(--color-info-item-base);
116
+
117
+ .fill {
118
+ stroke: var(--color-info-item-base);
119
+ }
120
+
121
+ .background {
122
+ stroke: var(--color-info-background-selected);
123
+ }
124
+
125
+ .success {
126
+ stroke: var(--color-success-item-base);
127
+ }
128
+ }
129
+
130
+ &.accent--warning {
131
+ color: var(--color-warning-item-base);
132
+
133
+ .fill {
134
+ stroke: var(--color-warning-item-base);
135
+ }
136
+
137
+ .background {
138
+ stroke: var(--color-warning-background-selected);
139
+ }
140
+ }
141
+
142
+ &.accent--danger {
143
+ color: var(--color-danger-item-base);
144
+
145
+ .fill {
146
+ stroke: var(--color-danger-item-base);
147
+ }
148
+
149
+ .background {
150
+ stroke: var(--color-danger-background-selected);
151
+ }
152
+ }
153
+
154
+ /* SIZE */
155
+
156
+ &.size--extra-small {
157
+ .background,
158
+ .fill {
159
+ stroke-width: 2;
160
+ }
161
+ }
162
+
163
+ &.size--small {
164
+ .icon {
165
+ font-size: 1.6rem;
166
+ }
167
+
168
+ .background,
169
+ .fill {
170
+ stroke-width: 4;
171
+ }
172
+ }
173
+
174
+ &.size--medium {
175
+ .icon {
176
+ font-size: 3.2rem;
177
+ }
178
+
179
+ .background,
180
+ .fill {
181
+ stroke-width: 6;
182
+ }
183
+ }
184
+
185
+ &.size--large {
186
+ .icon {
187
+ font-size: 4.8rem;
188
+ }
189
+
190
+ .background,
191
+ .fill {
192
+ stroke-width: 16;
193
+ }
194
+ }
195
+
196
+ .fill {
197
+ stroke-linecap: butt;
198
+ transition: stroke-dashoffset 0.3s ease;
199
+ transform: rotate(-90deg);
200
+ transform-origin: center;
201
+ stroke-dasharray: v-bind(circumference);
202
+ stroke-dashoffset: v-bind(dashOffset);
203
+ }
204
+
205
+ .overlay {
206
+ position: absolute;
207
+ top: 50%;
208
+ left: 50%;
209
+ transform: translate(-50%, -50%);
210
+ }
211
+ }
212
+ </style>
@@ -1,8 +1,8 @@
1
- <!-- v2 -->
1
+ <!-- v4 -->
2
2
  <template>
3
3
  <div class="ui-info">
4
4
  <VtsIcon :accent class="icon" :icon="faCircle" :overlay-icon="icon" />
5
- <p v-tooltip="!wrap" class="typo-form-info" :class="{ 'text-ellipsis': !wrap }">
5
+ <p v-tooltip="!wrap" class="typo-body-regular-small" :class="{ 'text-ellipsis': !wrap }">
6
6
  <slot />
7
7
  </p>
8
8
  </div>
@@ -16,12 +16,13 @@ import {
16
16
  faCircle,
17
17
  faExclamation,
18
18
  faInfo,
19
+ faMinus,
19
20
  faXmark,
20
21
  type IconDefinition,
21
22
  } from '@fortawesome/free-solid-svg-icons'
22
23
  import { computed } from 'vue'
23
24
 
24
- export type InfoAccent = 'info' | 'success' | 'warning' | 'danger'
25
+ export type InfoAccent = 'info' | 'success' | 'warning' | 'danger' | 'muted'
25
26
 
26
27
  const { accent } = defineProps<{
27
28
  accent: InfoAccent
@@ -37,6 +38,7 @@ const iconByAccent: Record<InfoAccent, IconDefinition> = {
37
38
  success: faCheck,
38
39
  warning: faExclamation,
39
40
  danger: faXmark,
41
+ muted: faMinus,
40
42
  }
41
43
 
42
44
  const icon = computed(() => iconByAccent[accent])