@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
@@ -1,41 +1,41 @@
1
1
  <!-- v5 -->
2
2
  <template>
3
- <div class="ui-input" :class="toVariants({ accent })">
4
- <UiLabel v-if="slots.default || label" :accent="labelAccent" :required :icon="labelIcon" :href :for="id">
5
- <slot>{{ label }}</slot>
6
- </UiLabel>
7
- <div>
8
- <VtsIcon :icon accent="current" class="before" />
9
- <input
10
- :id
11
- v-model.trim="modelValue"
12
- class="typo-body-regular input text-ellipsis"
13
- :type
14
- :disabled
15
- v-bind="attrs"
16
- />
17
- <VtsIcon
18
- v-if="!attrs.disabled && modelValue && clearable"
19
- :icon="faXmark"
20
- class="after"
21
- accent="brand"
22
- @click="modelValue = ''"
23
- />
24
- </div>
25
- <UiInfo v-if="slots.info || info" :accent="accent === 'brand' ? 'info' : accent">
26
- <slot name="info">{{ info }}</slot>
27
- </UiInfo>
3
+ <div :class="toVariants({ accent, disabled })" class="ui-input" @click.self="focus()">
4
+ <VtsIcon :icon accent="current" class="left-icon" />
5
+ <input
6
+ :id="wrapperController?.id ?? id"
7
+ ref="inputRef"
8
+ v-model.trim="modelValue"
9
+ :disabled
10
+ :required
11
+ :type
12
+ class="typo-body-regular input text-ellipsis"
13
+ v-bind="attrs"
14
+ />
15
+ <UiButtonIcon
16
+ v-if="!disabled && modelValue && clearable"
17
+ :icon="faXmark"
18
+ :target-scale="1.6"
19
+ accent="brand"
20
+ size="small"
21
+ @click="clear()"
22
+ />
23
+ <slot v-if="slots['right-icon'] || rightIcon" name="right-icon">
24
+ <VtsIcon :icon="rightIcon" accent="current" class="right-icon" />
25
+ </slot>
28
26
  </div>
29
27
  </template>
30
28
 
31
29
  <script lang="ts" setup>
32
30
  import VtsIcon from '@core/components/icon/VtsIcon.vue'
33
- import UiInfo from '@core/components/ui/info/UiInfo.vue'
34
- import UiLabel from '@core/components/ui/label/UiLabel.vue'
31
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
32
+ import type { LabelAccent } from '@core/components/ui/label/UiLabel.vue'
33
+ import { useMapper } from '@core/composables/mapper.composable.ts'
34
+ import { IK_INPUT_WRAPPER_CONTROLLER } from '@core/utils/injection-keys.util'
35
35
  import { toVariants } from '@core/utils/to-variants.util'
36
36
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
37
37
  import { faXmark } from '@fortawesome/free-solid-svg-icons'
38
- import { computed, useAttrs, useId } from 'vue'
38
+ import { inject, ref, useAttrs, watchEffect } from 'vue'
39
39
 
40
40
  type InputAccent = 'brand' | 'warning' | 'danger'
41
41
  type InputType = 'text' | 'number' | 'password' | 'search'
@@ -44,135 +44,152 @@ defineOptions({
44
44
  inheritAttrs: false,
45
45
  })
46
46
 
47
- const { accent, id = useId() } = defineProps<{
47
+ const {
48
+ accent: _accent,
49
+ required,
50
+ disabled,
51
+ id,
52
+ } = defineProps<{
48
53
  accent: InputAccent
49
- label?: string
50
- info?: string
51
54
  id?: string
52
- disabled?: boolean
53
- clearable?: boolean
54
- href?: string
55
55
  required?: boolean
56
- labelIcon?: IconDefinition
57
- icon?: IconDefinition
56
+ disabled?: boolean
58
57
  type?: InputType
58
+ icon?: IconDefinition
59
+ rightIcon?: IconDefinition
60
+ clearable?: boolean
59
61
  }>()
60
62
 
61
63
  const modelValue = defineModel<string | number>({ required: true })
62
64
 
63
65
  const slots = defineSlots<{
64
- default?(): any
65
- info?(): any
66
+ 'right-icon'?(): any
66
67
  }>()
67
68
 
68
69
  const attrs = useAttrs()
69
70
 
70
- const labelAccent = computed(() => (accent === 'brand' ? 'neutral' : accent))
71
+ const inputRef = ref<HTMLInputElement>()
72
+
73
+ const wrapperController = inject(IK_INPUT_WRAPPER_CONTROLLER, undefined)
74
+
75
+ const accent = useMapper<LabelAccent, InputAccent>(
76
+ () => wrapperController?.labelAccent,
77
+ {
78
+ neutral: _accent,
79
+ warning: 'warning',
80
+ danger: 'danger',
81
+ },
82
+ () => _accent
83
+ )
84
+
85
+ if (wrapperController) {
86
+ watchEffect(() => {
87
+ wrapperController.required = required
88
+ })
89
+ }
90
+
91
+ function focus() {
92
+ inputRef.value?.focus()
93
+ }
94
+
95
+ function clear() {
96
+ modelValue.value = ''
97
+ focus()
98
+ }
99
+
100
+ defineExpose({ focus })
71
101
  </script>
72
102
 
73
103
  <style lang="postcss" scoped>
74
- /* IMPLEMENTATION */
75
104
  .ui-input {
76
- position: relative;
77
105
  display: flex;
78
- flex-direction: column;
79
- gap: 0.4rem;
80
- flex: 1;
106
+ align-items: center;
107
+ gap: 1.6rem;
108
+ border-radius: 0.4rem;
109
+ border-width: 0.1rem;
110
+ border-style: solid;
111
+ background-color: var(--color-neutral-background-primary);
112
+ color: var(--color-neutral-txt-primary);
113
+ height: 4rem;
114
+ width: 100%;
115
+ padding-inline: 1.6rem;
116
+
117
+ .left-icon,
118
+ .right-icon {
119
+ pointer-events: none;
120
+ color: var(--color-neutral-txt-secondary);
121
+ }
122
+
123
+ &:not(.disabled) .right-icon {
124
+ color: var(--color-brand-item-base);
125
+ }
81
126
 
82
127
  .input {
83
- border-radius: 0.4rem;
84
- border-width: 0.1rem;
85
- border-style: solid;
86
- background-color: var(--color-neutral-background-primary);
87
- color: var(--color-neutral-txt-primary);
88
- height: 4rem;
128
+ background-color: transparent;
129
+ border: none;
89
130
  outline: none;
90
- width: 100%;
91
- padding-block: 0.8rem;
92
- padding-inline: 1.6rem;
131
+ flex: 1;
93
132
 
94
133
  &::placeholder {
95
134
  color: var(--color-neutral-txt-secondary);
96
135
  }
97
-
98
- &:focus {
99
- border-width: 0.2rem;
100
- }
101
-
102
- &:has(+ .after) {
103
- padding-inline-end: 4.8rem;
104
- }
105
136
  }
106
137
 
107
138
  /* VARIANT */
108
139
 
109
140
  &.accent--brand {
110
- .input {
111
- border-color: var(--color-neutral-border);
141
+ border-color: var(--color-neutral-border);
112
142
 
113
- &:hover {
114
- border-color: var(--color-brand-item-hover);
115
- }
143
+ &:focus-within {
144
+ border-color: var(--color-brand-item-base);
145
+ }
116
146
 
117
- &:focus {
118
- border-color: var(--color-brand-item-base);
119
- }
147
+ &:hover {
148
+ border-color: var(--color-brand-item-hover);
149
+ }
120
150
 
121
- &:active {
122
- border-color: var(--color-brand-item-active);
123
- }
151
+ &:has(input:active) {
152
+ border-color: var(--color-brand-item-active);
153
+ }
124
154
 
125
- &:disabled {
126
- border-color: var(--color-neutral-border);
127
- background-color: var(--color-neutral-background-disabled);
128
- }
155
+ &:has(input:disabled) {
156
+ border-color: var(--color-neutral-border);
157
+ background-color: var(--color-neutral-background-disabled);
129
158
  }
130
159
  }
131
160
 
132
161
  &.accent--warning {
133
- .input {
134
- border-color: var(--color-warning-item-base);
162
+ border-color: var(--color-warning-item-base);
135
163
 
136
- &:disabled {
137
- border-color: var(--color-neutral-border);
138
- background-color: var(--color-neutral-background-disabled);
139
- }
164
+ &:hover {
165
+ border-color: var(--color-warning-item-hover);
140
166
  }
141
- }
142
167
 
143
- &.accent--danger {
144
- .input {
145
- border-color: var(--color-danger-item-base);
168
+ &:has(input:active) {
169
+ border-color: var(--color-warning-item-active);
170
+ }
146
171
 
147
- &:disabled {
148
- border-color: var(--color-neutral-border);
149
- background-color: var(--color-neutral-background-disabled);
150
- }
172
+ &:has(input:disabled) {
173
+ border-color: var(--color-neutral-border);
174
+ background-color: var(--color-neutral-background-disabled);
151
175
  }
152
176
  }
153
177
 
154
- /* ICON POSITION */
155
-
156
- .before + .input {
157
- padding-inline-start: 4.8rem;
158
- }
178
+ &.accent--danger {
179
+ border-color: var(--color-danger-item-base);
159
180
 
160
- .before,
161
- .after {
162
- position: absolute;
163
- inset-block: 1.2rem;
164
- }
181
+ &:hover {
182
+ border-color: var(--color-danger-item-hover);
183
+ }
165
184
 
166
- .before {
167
- color: var(--color-neutral-txt-secondary);
168
- inset-inline-start: 1.6rem;
169
- pointer-events: none;
170
- z-index: 1;
171
- }
185
+ &:has(input:active) {
186
+ border-color: var(--color-danger-item-active);
187
+ }
172
188
 
173
- .after {
174
- inset-inline-end: 1.6rem;
175
- cursor: pointer;
189
+ &:has(input:disabled) {
190
+ border-color: var(--color-neutral-border);
191
+ background-color: var(--color-neutral-background-disabled);
192
+ }
176
193
  }
177
194
  }
178
195
  </style>
@@ -22,7 +22,7 @@ const { accent, value, disabled } = defineProps<{
22
22
  disabled?: boolean
23
23
  }>()
24
24
 
25
- const model = defineModel<boolean>()
25
+ const model = defineModel<string>()
26
26
 
27
27
  defineSlots<{
28
28
  default(): any
@@ -11,7 +11,7 @@
11
11
  <slot name="description" />
12
12
  </div>
13
13
  </div>
14
- <UiButtonIcon class="close-icon" :icon="faXmark" accent="brand" size="medium" />
14
+ <UiButtonIcon class="close-icon" :icon="faXmark" accent="brand" size="medium" @click="emit('close')" />
15
15
  </div>
16
16
  <div v-if="slots.actions" class="actions">
17
17
  <slot name="actions" />
@@ -24,7 +24,7 @@ import VtsIcon from '@core/components/icon/VtsIcon.vue'
24
24
  import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
25
25
  import { toVariants } from '@core/utils/to-variants.util'
26
26
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
27
- import { faXmark, faCheck, faCircle, faInfo, faExclamation } from '@fortawesome/free-solid-svg-icons'
27
+ import { faCheck, faCircle, faExclamation, faInfo, faXmark } from '@fortawesome/free-solid-svg-icons'
28
28
  import { computed } from 'vue'
29
29
 
30
30
  type ToasterAccent = 'info' | 'success' | 'warning' | 'danger'
@@ -33,6 +33,10 @@ const props = defineProps<{
33
33
  accent: ToasterAccent
34
34
  }>()
35
35
 
36
+ const emit = defineEmits<{
37
+ close: []
38
+ }>()
39
+
36
40
  const slots = defineSlots<{
37
41
  default(): any
38
42
  description?(): any
@@ -1,10 +1,10 @@
1
1
  import { useUiStore } from '@core/stores/ui.store'
2
- import { storeToRefs } from 'pinia'
2
+ import { storeToRefs, type Pinia } from 'pinia'
3
3
  import { computed, provide, ref, watch } from 'vue'
4
4
  import { THEME_KEY } from 'vue-echarts'
5
5
 
6
- export const useChartTheme = () => {
7
- const { colorMode } = storeToRefs(useUiStore())
6
+ export const useChartTheme = (pinia?: Pinia) => {
7
+ const { colorMode } = storeToRefs(useUiStore(pinia))
8
8
 
9
9
  const style = window.getComputedStyle(window.document.documentElement)
10
10
 
@@ -12,8 +12,8 @@ export const useChartTheme = () => {
12
12
  background: style.getPropertyValue('--color-neutral-background-primary'),
13
13
  text: style.getPropertyValue('--color-neutral-txt-secondary'),
14
14
  splitLine: style.getPropertyValue('--color-neutral-border'),
15
- primary: style.getPropertyValue('--color-normal-txt-base'),
16
- secondary: style.getPropertyValue('--color-warning-txt-base'),
15
+ primary: style.getPropertyValue('--color-brand-item-base'),
16
+ secondary: style.getPropertyValue('--color-warning-item-base'),
17
17
  })
18
18
 
19
19
  const colors = ref(getColors())
@@ -0,0 +1,18 @@
1
+ # useRelativeTime composable
2
+
3
+ ## Usage
4
+
5
+ ```ts
6
+ const relativeTime = useRelativeTime(fromDate, toDate)
7
+
8
+ console.log(relativeTime.value) // 3 days 27 minutes 10 seconds ago
9
+ ```
10
+
11
+ # Reactivity
12
+
13
+ Both arguments can be `Ref`
14
+
15
+ ```ts
16
+ const now = useNow()
17
+ const relativeTime = useRelativeTime(fromDate, now) // Value will be updated each time `now` changes
18
+ ```
@@ -0,0 +1,66 @@
1
+ import type { MaybeRef } from '@vueuse/core'
2
+ import { computed, unref } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+
5
+ export default function useRelativeTime(fromDate: MaybeRef<Date>, toDate: MaybeRef<Date>) {
6
+ const { t } = useI18n()
7
+ const fromTime = computed(() => unref(fromDate).getTime())
8
+ const toTime = computed(() => unref(toDate).getTime())
9
+ const isPast = computed(() => toTime.value > fromTime.value)
10
+ const diff = computed(() => Math.abs(toTime.value - fromTime.value))
11
+
12
+ return computed(() => {
13
+ if (diff.value < 10000) {
14
+ return t('relative-time.now')
15
+ }
16
+
17
+ const years = Math.floor(diff.value / 31556952000)
18
+ let timeLeft = diff.value % 31556952000
19
+
20
+ const months = Math.floor(timeLeft / 2629746000)
21
+ timeLeft = timeLeft % 2629746000
22
+
23
+ const days = Math.floor(timeLeft / 86400000)
24
+ timeLeft = timeLeft % 86400000
25
+
26
+ const hours = Math.floor(timeLeft / 3600000)
27
+ timeLeft = timeLeft % 3600000
28
+
29
+ const minutes = Math.floor(timeLeft / 60000)
30
+ timeLeft = timeLeft % 60000
31
+ const seconds = Math.floor(timeLeft / 1000)
32
+
33
+ const parts = []
34
+
35
+ if (years > 0) {
36
+ parts.push(t('relative-time.year', { n: years }))
37
+ }
38
+
39
+ if (months > 0) {
40
+ const n = days >= 15 ? months + 1 : months
41
+ parts.push(t('relative-time.month', { n }))
42
+ }
43
+
44
+ if (years === 0 && months === 0) {
45
+ if (days > 0) {
46
+ parts.push(t('relative-time.day', { n: days }))
47
+ }
48
+
49
+ if (days <= 1 && hours > 0) {
50
+ parts.push(t('relative-time.hour', { n: hours }))
51
+ }
52
+
53
+ if (days === 0 && minutes > 0) {
54
+ parts.push(t('relative-time.minute', { n: minutes }))
55
+ }
56
+
57
+ if (days === 0 && seconds > 0) {
58
+ parts.push(t('relative-time.second', { n: seconds }))
59
+ }
60
+ }
61
+
62
+ return t(isPast.value ? 'relative-time.past' : 'relative-time.future', {
63
+ str: parts.join(' '),
64
+ })
65
+ })
66
+ }
package/lib/i18n.ts CHANGED
@@ -37,6 +37,22 @@ export const locales: Locales = {
37
37
  code: 'sv',
38
38
  name: 'Svenska',
39
39
  },
40
+ it: {
41
+ code: 'it',
42
+ name: 'Italiano',
43
+ },
44
+ ru: {
45
+ code: 'ru',
46
+ name: 'Русский',
47
+ },
48
+ uk: {
49
+ code: 'uk',
50
+ name: 'Українська',
51
+ },
52
+ nl: {
53
+ code: 'nl',
54
+ name: 'Nederlands',
55
+ },
40
56
  }
41
57
 
42
58
  export default createI18n({