@xen-orchestra/web-core 0.0.3 → 0.0.5

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 (53) hide show
  1. package/env.d.ts +0 -1
  2. package/lib/assets/css/_typography.pcss +1 -0
  3. package/lib/assets/css/typography/_utils.pcss +6 -0
  4. package/lib/components/LegendTitle.vue +9 -7
  5. package/lib/components/UiTag.vue +3 -7
  6. package/lib/components/backdrop/Backdrop.vue +11 -0
  7. package/lib/components/cell-object/CellObject.vue +54 -0
  8. package/lib/components/cell-text/CellText.vue +40 -0
  9. package/lib/components/chip/UiChip.vue +3 -4
  10. package/lib/components/console/RemoteConsole.vue +1 -1
  11. package/lib/components/divider/Divider.vue +25 -0
  12. package/lib/components/{DonutChart.vue → donut-chart/DonutChart.vue} +28 -24
  13. package/lib/components/donut-chart-with-legend/DonutChartWithLegend.vue +27 -0
  14. package/lib/components/dropdown/DropdownItem.vue +1 -4
  15. package/lib/components/head-bar/HeadBar.vue +78 -0
  16. package/lib/components/input/UiInput.vue +133 -0
  17. package/lib/components/legend/LegendGroup.vue +44 -0
  18. package/lib/components/{UiLegend.vue → legend/LegendItem.vue} +35 -17
  19. package/lib/components/legend/LegendList.vue +19 -0
  20. package/lib/components/object-link/ObjectLink.vue +87 -0
  21. package/lib/components/search-bar/SearchBar.vue +60 -0
  22. package/lib/components/stacked-bar/StackedBarSegment.vue +2 -8
  23. package/lib/components/state-hero/ComingSoonHero.vue +6 -2
  24. package/lib/components/state-hero/LoadingHero.vue +8 -2
  25. package/lib/components/state-hero/ObjectNotFoundHero.vue +1 -1
  26. package/lib/components/state-hero/StateHero.vue +27 -9
  27. package/lib/components/tab/TabList.vue +1 -0
  28. package/lib/components/table/UiTable.vue +6 -0
  29. package/lib/components/task/QuickTaskButton.vue +62 -0
  30. package/lib/components/task/QuickTaskItem.vue +91 -0
  31. package/lib/components/task/QuickTaskList.vue +48 -0
  32. package/lib/components/task/QuickTaskPanel.vue +65 -0
  33. package/lib/components/task/QuickTaskTabBar.vue +46 -0
  34. package/lib/components/tree/TreeItem.vue +8 -8
  35. package/lib/components/tree/TreeItemLabel.vue +28 -16
  36. package/lib/composables/item-counter.composable.md +25 -0
  37. package/lib/composables/item-counter.composable.ts +32 -0
  38. package/lib/composables/tab-list.composable.ts +33 -0
  39. package/lib/composables/tree/branch.ts +5 -5
  40. package/lib/i18n.ts +53 -0
  41. package/lib/layouts/CoreLayout.vue +2 -11
  42. package/lib/locales/de.json +7 -1
  43. package/lib/locales/en.json +23 -1
  44. package/lib/locales/fa.json +46 -0
  45. package/lib/locales/fr.json +23 -1
  46. package/lib/types/novnc.d.ts +342 -0
  47. package/lib/types/tab.type.ts +17 -0
  48. package/lib/types/task.type.ts +13 -0
  49. package/lib/types/utility.type.ts +1 -1
  50. package/lib/utils/if-else.utils.ts +3 -3
  51. package/lib/utils/open-url.utils.ts +3 -0
  52. package/package.json +3 -3
  53. package/lib/components/UiSeparator.vue +0 -11
@@ -1,67 +1,85 @@
1
1
  <!-- v1.0 -->
2
2
  <template>
3
- <li :class="color" class="ui-legend">
3
+ <li :class="color" class="legend-item">
4
4
  <UiIcon :icon="faCircle" class="circle-icon" />
5
5
  <span class="label typo p3-regular"><slot /></span>
6
6
  <UiIcon v-if="tooltip" v-tooltip="tooltip" :icon="faCircleInfo" class="tooltip-icon" color="info" />
7
-
8
- <span class="value-and-unit typo c3-semi-bold">{{ value }} {{ unit }}</span>
7
+ <span v-if="valueLabel" class="value-and-unit typo c3-semi-bold">{{ valueLabel }}</span>
9
8
  </li>
10
9
  </template>
11
10
 
12
- <script setup lang="ts">
11
+ <script lang="ts" setup>
13
12
  import UiIcon from '@core/components/icon/UiIcon.vue'
14
13
  import { vTooltip } from '@core/directives/tooltip.directive'
15
- import { faCircleInfo, faCircle } from '@fortawesome/free-solid-svg-icons'
14
+ import { faCircle, faCircleInfo } from '@fortawesome/free-solid-svg-icons'
15
+ import { computed } from 'vue'
16
16
 
17
- type LegendColor = 'default' | 'success' | 'warning' | 'error' | 'disabled' | 'dark-blue'
17
+ export type LegendItemColor = 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'disabled'
18
18
 
19
- defineProps<{
20
- color: LegendColor
19
+ export type LegendItemProps = {
20
+ color: LegendItemColor
21
21
  value?: number
22
22
  unit?: string
23
23
  tooltip?: string
24
+ }
25
+
26
+ const props = defineProps<LegendItemProps>()
27
+
28
+ defineSlots<{
29
+ default(): void
24
30
  }>()
31
+
32
+ const valueLabel = computed(() => [props.value, props.unit].join(' ').trim())
25
33
  </script>
26
34
 
27
35
  <style lang="postcss" scoped>
28
36
  /* COLOR VARIANTS */
29
- .ui-legend {
37
+ .legend-item {
38
+ &.primary {
39
+ --circle-color: var(--color-purple-base);
40
+ }
41
+
42
+ &.secondary {
43
+ --circle-color: var(--color-grey-100);
44
+ }
45
+
30
46
  &.success {
31
47
  --circle-color: var(--color-green-base);
32
48
  }
49
+
33
50
  &.warning {
34
51
  --circle-color: var(--color-orange-base);
35
52
  }
36
- &.error {
53
+
54
+ &.danger {
37
55
  --circle-color: var(--color-red-base);
38
56
  }
39
- &.default {
40
- --circle-color: var(--color-purple-base);
41
- }
57
+
42
58
  &.disabled {
43
59
  --circle-color: var(--color-grey-300);
44
60
  }
45
- &.dark-blue {
46
- --circle-color: var(--color-grey-100);
47
- }
48
61
  }
62
+
49
63
  /* IMPLEMENTATION */
50
- .ui-legend {
64
+ .legend-item {
51
65
  display: flex;
52
66
  align-items: center;
53
67
  gap: 0.8rem;
54
68
  }
69
+
55
70
  .circle-icon {
56
71
  font-size: 0.8rem;
57
72
  color: var(--circle-color);
58
73
  }
74
+
59
75
  .tooltip-icon {
60
76
  font-size: 1.2rem;
61
77
  }
78
+
62
79
  .label {
63
80
  color: var(--color-grey-000);
64
81
  }
82
+
65
83
  .value-and-unit {
66
84
  color: var(--color-grey-300);
67
85
  }
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <ul class="legend-list">
3
+ <slot />
4
+ </ul>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ defineSlots<{
9
+ default(): void
10
+ }>()
11
+ </script>
12
+
13
+ <style lang="postcss" scoped>
14
+ .legend-list {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 0.4rem;
18
+ }
19
+ </style>
@@ -0,0 +1,87 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <RouterLink v-if="route && !disabled" :to="route" class="object-link is-link typo p3-medium">
4
+ <span class="icon">
5
+ <slot name="icon">
6
+ <UiIcon :icon />
7
+ </slot>
8
+ </span>
9
+ <span v-tooltip class="content text-ellipsis">
10
+ <slot />
11
+ </span>
12
+ </RouterLink>
13
+ <span v-else :class="{ disabled }" class="object-link typo p3-medium">
14
+ <span class="icon">
15
+ <slot name="icon">
16
+ <UiIcon :icon />
17
+ </slot>
18
+ </span>
19
+ <slot />
20
+ </span>
21
+ </template>
22
+
23
+ <script lang="ts" setup>
24
+ import UiIcon from '@core/components/icon/UiIcon.vue'
25
+ import { vTooltip } from '@core/directives/tooltip.directive'
26
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
27
+ import { type RouteLocationRaw } from 'vue-router'
28
+
29
+ defineProps<{
30
+ route?: RouteLocationRaw
31
+ disabled?: boolean
32
+ icon?: IconDefinition
33
+ }>()
34
+
35
+ defineSlots<{
36
+ default: () => any
37
+ icon: () => any
38
+ }>()
39
+ </script>
40
+
41
+ <style lang="postcss" scoped>
42
+ /* COLOR VARIANTS */
43
+ .object-link {
44
+ --color: var(--color-purple-base);
45
+ --border-color: var(--color-purple-base);
46
+
47
+ &.is-link {
48
+ &:is(:hover, .hover, :focus-visible) {
49
+ --color: var(--color-purple-d20);
50
+ --border-color: var(--color-purple-d20);
51
+ }
52
+
53
+ &:is(:active, .pressed) {
54
+ --color: var(--color-purple-d40);
55
+ --border-color: var(--color-purple-d40);
56
+ }
57
+ }
58
+
59
+ &.disabled {
60
+ --color: var(--color-grey-400);
61
+ --border-color: var(--color-grey-400);
62
+ }
63
+ }
64
+
65
+ /* IMPLEMENTATION */
66
+ .object-link {
67
+ display: flex;
68
+ min-width: 0;
69
+ align-items: center;
70
+ color: var(--color);
71
+ gap: 1rem;
72
+ border-top: 0.1rem solid transparent;
73
+ border-bottom: 0.1rem solid var(--border-color);
74
+ line-height: 1;
75
+ padding-block: 0.5rem;
76
+ text-decoration: none;
77
+
78
+ &.disabled {
79
+ cursor: not-allowed;
80
+ }
81
+ }
82
+
83
+ .icon {
84
+ color: var(--color-grey-100);
85
+ font-size: 0.8rem;
86
+ }
87
+ </style>
@@ -0,0 +1,60 @@
1
+ <!-- v1.1 -->
2
+ <template>
3
+ <form class="search-bar" @submit.prevent="emit('search', value)">
4
+ <label v-if="uiStore.isDesktop" :for="id" class="typo p2-regular label">
5
+ {{ $t('core.search-bar.label') }}
6
+ </label>
7
+ <UiInput
8
+ :id
9
+ v-model="value"
10
+ :aria-label="uiStore.isMobile ? $t('core.search-bar.label') : undefined"
11
+ :icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
12
+ :placeholder="$t('core.search-bar.placeholder')"
13
+ />
14
+ <template v-if="uiStore.isDesktop">
15
+ <UiButton type="submit">{{ $t('core.search') }}</UiButton>
16
+ <Divider type="stretch" />
17
+ <UiButton v-tooltip="$t('coming-soon')" level="secondary" :left-icon="faFilter" disabled>
18
+ {{ $t('core.search-bar.use-query-builder') }}
19
+ </UiButton>
20
+ </template>
21
+ <template v-else>
22
+ <ButtonIcon type="submit" :icon="faMagnifyingGlass" />
23
+ <ButtonIcon disabled :icon="faFilter" />
24
+ </template>
25
+ </form>
26
+ </template>
27
+
28
+ <script lang="ts" setup>
29
+ import ButtonIcon from '@core/components/button/ButtonIcon.vue'
30
+ import UiButton from '@core/components/button/UiButton.vue'
31
+ import Divider from '@core/components/divider/Divider.vue'
32
+ import UiInput from '@core/components/input/UiInput.vue'
33
+ import { vTooltip } from '@core/directives/tooltip.directive'
34
+ import { useUiStore } from '@core/stores/ui.store'
35
+ import { uniqueId } from '@core/utils/unique-id.util'
36
+ import { faFilter, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'
37
+ import { ref } from 'vue'
38
+
39
+ const emit = defineEmits<{
40
+ search: [value: string]
41
+ }>()
42
+
43
+ const id = uniqueId('search-input-')
44
+
45
+ const uiStore = useUiStore()
46
+
47
+ const value = ref<string>('')
48
+ </script>
49
+
50
+ <style lang="postcss" scoped>
51
+ .search-bar {
52
+ display: flex;
53
+ gap: 1.6rem;
54
+ align-items: center;
55
+ }
56
+
57
+ .label {
58
+ color: var(--color-grey-200);
59
+ }
60
+ </style>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <div
3
- v-tooltip="{ selector: '.ellipsis' }"
3
+ v-tooltip="{ selector: '.text-ellipsis' }"
4
4
  :class="color"
5
5
  :style="{ width: percentage + '%' }"
6
6
  class="stacked-bar-segment typo c4-semi-bold"
7
7
  >
8
- <div ref="ellipsisElement" :class="{ hidden }" class="ellipsis">
8
+ <div ref="ellipsisElement" :class="{ hidden }" class="text-ellipsis">
9
9
  {{ $n(percentage / 100, 'percent') }}
10
10
  </div>
11
11
  </div>
@@ -61,12 +61,6 @@ useResizeObserver(ellipsisElement, ([entry]) => {
61
61
  background-color: var(--background-color);
62
62
  }
63
63
 
64
- .ellipsis {
65
- overflow: hidden;
66
- white-space: nowrap;
67
- min-width: 0;
68
- }
69
-
70
64
  .hidden {
71
65
  visibility: hidden;
72
66
  }
@@ -1,9 +1,13 @@
1
1
  <template>
2
- <StateHero image="under-construction" class="coming-soon-hero">
2
+ <StateHero :type class="coming-soon-hero" image="under-construction">
3
3
  {{ $t('coming-soon') }}
4
4
  </StateHero>
5
5
  </template>
6
6
 
7
7
  <script lang="ts" setup>
8
- import StateHero from '@core/components/state-hero/StateHero.vue'
8
+ import StateHero, { type StateHeroType } from '@core/components/state-hero/StateHero.vue'
9
+
10
+ defineProps<{
11
+ type: StateHeroType
12
+ }>()
9
13
  </script>
@@ -1,9 +1,15 @@
1
1
  <template>
2
- <StateHero busy class="loading-hero">
2
+ <StateHero v-if="!disabled" :type busy class="loading-hero">
3
3
  {{ $t('loading-in-progress') }}
4
4
  </StateHero>
5
+ <slot v-else />
5
6
  </template>
6
7
 
7
8
  <script lang="ts" setup>
8
- import StateHero from '@core/components/state-hero/StateHero.vue'
9
+ import StateHero, { type StateHeroType } from '@core/components/state-hero/StateHero.vue'
10
+
11
+ defineProps<{
12
+ type: StateHeroType
13
+ disabled?: boolean
14
+ }>()
9
15
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <StateHero image="no-result" class="object-not-found-hero">
2
+ <StateHero class="object-not-found-hero" image="no-result" type="page">
3
3
  {{ $t('object-not-found', { id }) }}
4
4
  </StateHero>
5
5
  </template>
@@ -1,8 +1,8 @@
1
1
  <template>
2
- <div class="state-hero">
2
+ <div :class="type" class="state-hero">
3
3
  <UiSpinner v-if="busy" class="spinner" />
4
4
  <img v-else-if="imageSrc" :src="imageSrc" alt="" class="image" />
5
- <p v-if="$slots.default" class="typo h2-black text">
5
+ <p v-if="$slots.default" :class="typoClass" class="typo text">
6
6
  <slot />
7
7
  </p>
8
8
  </div>
@@ -12,42 +12,60 @@
12
12
  import UiSpinner from '@core/components/UiSpinner.vue'
13
13
  import { computed } from 'vue'
14
14
 
15
+ export type StateHeroType = 'page' | 'card'
16
+
15
17
  const props = defineProps<{
18
+ type: StateHeroType
16
19
  busy?: boolean
17
20
  image?: 'no-result' | 'under-construction' // TODO: 'offline' | 'no-data' | 'not-found' | 'all-good' | 'all-done' | 'error'
18
21
  }>()
19
22
 
23
+ const typoClass = computed(() => (props.type === 'page' ? 'h2-black' : 'h4-medium'))
24
+
20
25
  const imageSrc = computed(() => {
21
- try {
22
- return new URL(`../../assets/${props.image}.svg`, import.meta.url).href
23
- } catch {
26
+ if (!props.image) {
24
27
  return undefined
25
28
  }
29
+
30
+ return new URL(`../../assets/${props.image}.svg`, import.meta.url).href
26
31
  })
27
32
  </script>
28
33
 
29
34
  <style lang="postcss" scoped>
35
+ .state-hero {
36
+ &.page {
37
+ --image-width: 90%;
38
+ --spinner-size: 10rem;
39
+ --gap: 8.2rem;
40
+ }
41
+
42
+ &.card {
43
+ --image-width: 70%;
44
+ --spinner-size: 6rem;
45
+ --gap: 2rem;
46
+ }
47
+ }
48
+
30
49
  .state-hero {
31
50
  flex: 1;
32
51
  display: flex;
33
52
  flex-direction: column;
34
53
  align-items: center;
35
54
  justify-content: center;
36
- gap: 4.2rem;
55
+ gap: var(--gap);
37
56
  }
38
57
 
39
58
  .image {
40
- width: 90%;
59
+ width: var(--image-width);
41
60
  max-width: 55rem;
42
61
  }
43
62
 
44
63
  .spinner {
45
64
  color: var(--color-purple-base);
46
- font-size: 10rem;
65
+ font-size: var(--spinner-size);
47
66
  }
48
67
 
49
68
  .text {
50
69
  color: var(--color-purple-base);
51
- margin-top: 4rem;
52
70
  }
53
71
  </style>
@@ -28,5 +28,6 @@ useContext(DisabledContext, () => props.disabled)
28
28
  background-color: var(--background-color-primary);
29
29
  max-width: 100%;
30
30
  overflow: auto;
31
+ flex-shrink: 0;
31
32
  }
32
33
  </style>
@@ -22,6 +22,8 @@ provide('tableName', props.name)
22
22
  background-color: var(--background-color-primary);
23
23
  line-height: 2.4rem;
24
24
  color: var(--color-grey-200);
25
+ border-collapse: collapse;
26
+ table-layout: fixed;
25
27
 
26
28
  :deep(th),
27
29
  :deep(td) {
@@ -49,6 +51,10 @@ provide('tableName', props.name)
49
51
  :deep(td) {
50
52
  border-right: 0.1rem solid var(--color-grey-500);
51
53
 
54
+ &:first-child {
55
+ border-left: none;
56
+ }
57
+
52
58
  &:last-child {
53
59
  border-right: none;
54
60
  }
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <ButtonIcon
3
+ ref="buttonRef"
4
+ v-tooltip="{ content: $t('tasks.quick-view'), placement: 'bottom-end' }"
5
+ :dot="hasNewTask"
6
+ :icon="faBarsProgress"
7
+ size="large"
8
+ @click="isPanelOpen = true"
9
+ />
10
+ <Teleport v-if="isPanelOpen" to="body">
11
+ <Backdrop @click="isPanelOpen = false" />
12
+ <QuickTaskPanel ref="panelRef" :loading :tasks />
13
+ </Teleport>
14
+ </template>
15
+
16
+ <script lang="ts" setup>
17
+ import Backdrop from '@core/components/backdrop/Backdrop.vue'
18
+ import ButtonIcon from '@core/components/button/ButtonIcon.vue'
19
+ import QuickTaskPanel from '@core/components/task/QuickTaskPanel.vue'
20
+ import { vTooltip } from '@core/directives/tooltip.directive'
21
+ import type { Task } from '@core/types/task.type'
22
+ import { faBarsProgress } from '@fortawesome/free-solid-svg-icons'
23
+ import { unrefElement, watchArray, whenever } from '@vueuse/core'
24
+ import placementJs from 'placement.js'
25
+ import { computed, nextTick, ref } from 'vue'
26
+
27
+ const props = defineProps<{
28
+ tasks: Task[]
29
+ loading?: boolean
30
+ }>()
31
+
32
+ const ids = computed(() => props.tasks.map(task => task.id))
33
+
34
+ const isPanelOpen = ref(false)
35
+ const hasNewTask = ref(false)
36
+
37
+ watchArray(ids, (_newList, _oldList, addedIds) => {
38
+ if (addedIds.length > 0 && !isPanelOpen.value) {
39
+ hasNewTask.value = true
40
+ }
41
+ })
42
+
43
+ const buttonRef = ref<HTMLButtonElement | null>(null)
44
+ const panelRef = ref<HTMLDivElement | null>(null)
45
+
46
+ whenever(isPanelOpen, async () => {
47
+ await nextTick()
48
+
49
+ const button = unrefElement(buttonRef)
50
+ const panel = unrefElement(panelRef)
51
+
52
+ if (!button || !panel) {
53
+ return
54
+ }
55
+
56
+ placementJs(button, panel, {
57
+ placement: 'bottom-end',
58
+ })
59
+
60
+ hasNewTask.value = false
61
+ })
62
+ </script>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <li class="vts-quick-task-item">
3
+ <div v-if="hasSubTasks" class="toggle" @click="toggleExpand()">
4
+ <ButtonIcon :icon="isExpanded ? faAngleDown : faAngleRight" size="small" />
5
+ </div>
6
+ <div class="content">
7
+ <div class="typo p1-medium">
8
+ {{ task.name }}
9
+ </div>
10
+ <div class="informations">
11
+ <div class="line-1">
12
+ <UiTag v-if="task.tag" color="grey">{{ task.tag }}</UiTag>
13
+ <div v-if="hasSubTasks" class="subtasks">
14
+ <UiIcon :icon="faCircleNotch" />
15
+ <span class="typo p4-medium">{{ $t('tasks.n-subtasks', { n: subTasksCount }) }}</span>
16
+ </div>
17
+ </div>
18
+ <div v-if="task.start" class="line-2 typo p4-regular">
19
+ {{ $d(task.start, 'datetime_short') }}
20
+ <template v-if="task.end">
21
+ <UiIcon :icon="faArrowRight" />
22
+ {{ $d(new Date(task.end), 'datetime_short') }}
23
+ </template>
24
+ </div>
25
+ </div>
26
+ <QuickTaskList v-if="hasSubTasks && isExpanded" :tasks="subTasks" sublist />
27
+ </div>
28
+ </li>
29
+ </template>
30
+
31
+ <script lang="ts" setup>
32
+ import ButtonIcon from '@core/components/button/ButtonIcon.vue'
33
+ import UiIcon from '@core/components/icon/UiIcon.vue'
34
+ import QuickTaskList from '@core/components/task/QuickTaskList.vue'
35
+ import UiTag from '@core/components/UiTag.vue'
36
+ import type { Task } from '@core/types/task.type'
37
+ import { faAngleDown, faAngleRight, faArrowRight, faCircleNotch } from '@fortawesome/free-solid-svg-icons'
38
+ import { useToggle } from '@vueuse/core'
39
+ import { computed } from 'vue'
40
+
41
+ const props = defineProps<{
42
+ task: Task
43
+ }>()
44
+
45
+ const [isExpanded, toggleExpand] = useToggle()
46
+
47
+ const subTasks = computed(() => props.task.subtasks ?? [])
48
+ const subTasksCount = computed(() => subTasks.value.length)
49
+ const hasSubTasks = computed(() => subTasksCount.value > 0)
50
+ </script>
51
+
52
+ <style lang="postcss" scoped>
53
+ .vts-quick-task-item {
54
+ display: flex;
55
+
56
+ &:not(:last-child) {
57
+ border-bottom: 0.1rem solid var(--color-grey-500);
58
+ }
59
+ }
60
+
61
+ .toggle {
62
+ padding: 0.4rem 0;
63
+ }
64
+
65
+ .content {
66
+ flex: 1;
67
+ padding: 0.4rem 0.4rem 0.4rem 0.8rem;
68
+ }
69
+
70
+ .informations {
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: 0.4rem;
74
+ }
75
+
76
+ .line-1 {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 0.2rem 0.8rem;
80
+ }
81
+
82
+ .line-2 {
83
+ color: var(--color-grey-200);
84
+ }
85
+
86
+ .subtasks {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 0.4rem;
90
+ }
91
+ </style>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <ul :class="{ sublist }" class="vts-quick-task-list">
3
+ <li v-if="loading">
4
+ <div class="loading">
5
+ <UiSpinner />
6
+ <div>{{ $t('loading-in-progress') }}</div>
7
+ </div>
8
+ </li>
9
+ <template v-else>
10
+ <li v-if="tasks.length === 0" class="typo p1-medium">{{ $t('tasks.no-tasks') }}</li>
11
+ <QuickTaskItem v-for="task of tasks" :key="task.id" :task />
12
+ </template>
13
+ </ul>
14
+ </template>
15
+
16
+ <script lang="ts" setup>
17
+ import QuickTaskItem from '@core/components/task/QuickTaskItem.vue'
18
+ import UiSpinner from '@core/components/UiSpinner.vue'
19
+ import type { Task } from '@core/types/task.type'
20
+
21
+ defineProps<{
22
+ tasks: Task[]
23
+ sublist?: boolean
24
+ loading?: boolean
25
+ }>()
26
+ </script>
27
+
28
+ <style lang="postcss" scoped>
29
+ .vts-quick-task-list {
30
+ display: flex;
31
+ flex-direction: column;
32
+ background-color: var(--background-color-primary);
33
+ padding: 1rem 0;
34
+
35
+ &:not(.sublist) {
36
+ padding: 1.6rem 2rem;
37
+ max-height: 40rem;
38
+ overflow: auto;
39
+ }
40
+ }
41
+
42
+ .loading {
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ gap: 1rem;
47
+ }
48
+ </style>