@xen-orchestra/web-core 0.55.0 → 0.56.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 (52) hide show
  1. package/lib/components/card-object-title/VtsCardObjectTitle.vue +36 -0
  2. package/lib/components/code-snippet/VtsCodeSnippet.vue +11 -5
  3. package/lib/components/drawer/VtsDrawer.vue +5 -1
  4. package/lib/components/drawer/VtsDrawerButton.vue +4 -3
  5. package/lib/components/drawer/VtsDrawerConfirmButton.vue +4 -2
  6. package/lib/components/icon/VtsIcon.vue +3 -1
  7. package/lib/components/layout/VtsContentSidePanel.vue +41 -0
  8. package/lib/components/menu/VtsActionsMenu.vue +37 -3
  9. package/lib/components/operation-card/VtsOperationCard.vue +2 -1
  10. package/lib/components/panel/VtsPanel.vue +80 -0
  11. package/lib/components/panel/VtsSidePanel.vue +140 -0
  12. package/lib/components/select/VtsSelect.vue +1 -0
  13. package/lib/components/state-hero/VtsStateHero.vue +9 -17
  14. package/lib/components/table/VtsTable.vue +2 -1
  15. package/lib/components/table/cells/VtsActionCell.vue +7 -0
  16. package/lib/components/table/cells/VtsLinkCell.vue +3 -1
  17. package/lib/components/tree/VtsTreeItem.vue +9 -3
  18. package/lib/components/tree/VtsTreeList.vue +6 -3
  19. package/lib/components/tree/VtsTreeLoadingItem.vue +3 -3
  20. package/lib/components/ui/head-bar/UiHeadBar.vue +1 -1
  21. package/lib/components/ui/input/UiInput.vue +3 -1
  22. package/lib/components/ui/link/UiLink.vue +3 -1
  23. package/lib/components/ui/panel/UiPanel.vue +7 -24
  24. package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +1 -1
  25. package/lib/composables/table-state.composable.ts +1 -1
  26. package/lib/icons/action-icons.ts +14 -0
  27. package/lib/locales/cs.json +56 -1
  28. package/lib/locales/de.json +3 -2
  29. package/lib/locales/en.json +22 -2
  30. package/lib/locales/es.json +11 -1
  31. package/lib/locales/fa.json +0 -1
  32. package/lib/locales/fr.json +22 -2
  33. package/lib/locales/it.json +0 -1
  34. package/lib/locales/ko.json +2 -0
  35. package/lib/locales/nl.json +2 -1
  36. package/lib/locales/pt-BR.json +0 -1
  37. package/lib/locales/pt.json +0 -1
  38. package/lib/locales/ru.json +0 -1
  39. package/lib/locales/sk.json +56 -1
  40. package/lib/locales/sv.json +3 -2
  41. package/lib/locales/uk.json +0 -1
  42. package/lib/locales/zh-Hans.json +79 -4
  43. package/lib/packages/remote-resource/define-remote-resource.ts +64 -29
  44. package/lib/packages/tree/types.ts +6 -0
  45. package/lib/packages/tree/use-tree.ts +38 -37
  46. package/lib/stores/panel.store.ts +56 -5
  47. package/lib/tables/column-definitions/action-column.ts +21 -9
  48. package/lib/tables/column-sets/vm-columns.ts +2 -3
  49. package/lib/types/state-hero.type.ts +19 -0
  50. package/lib/types/vue-virtual-scroller.d.ts +8 -2
  51. package/lib/utils/injection-keys.util.ts +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <div class="vts-card-object-title">
3
+ <UiLink v-if="label" size="medium" :to :href :icon>
4
+ {{ label }}
5
+ </UiLink>
6
+ <VtsCodeSnippet class="id" :content="id" copy />
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import VtsCodeSnippet from '@core/components/code-snippet/VtsCodeSnippet.vue'
12
+ import UiLink from '@core/components/ui/link/UiLink.vue'
13
+ import type { IconName } from '@core/icons/index.ts'
14
+ import type { RouteLocationRaw } from 'vue-router'
15
+
16
+ defineProps<{
17
+ id: string
18
+ label?: string
19
+ icon?: IconName
20
+ to?: RouteLocationRaw
21
+ href?: string
22
+ }>()
23
+ </script>
24
+
25
+ <style scoped lang="postcss">
26
+ .vts-card-object-title {
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: start;
30
+ gap: 0.8rem;
31
+
32
+ .id {
33
+ width: 100%;
34
+ }
35
+ }
36
+ </style>
@@ -1,8 +1,10 @@
1
1
  <template>
2
2
  <div class="vts-code-snippet">
3
- <slot>
4
- {{ content }}
5
- </slot>
3
+ <div v-tooltip="{ placement: 'bottom' }" class="content text-ellipsis">
4
+ <slot>
5
+ {{ content }}
6
+ </slot>
7
+ </div>
6
8
  <div v-if="copy || slots.addons" class="addons">
7
9
  <slot name="addons">
8
10
  <VtsCopyButton v-if="copy && content" :value="String(content)" />
@@ -13,6 +15,7 @@
13
15
 
14
16
  <script lang="ts" setup>
15
17
  import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
18
+ import { vTooltip } from '@core/directives/tooltip.directive.ts'
16
19
 
17
20
  defineProps<{
18
21
  content?: string | number
@@ -27,13 +30,16 @@ const slots = defineSlots<{
27
30
 
28
31
  <style lang="postcss" scoped>
29
32
  .vts-code-snippet {
30
- font-family: 'Courier New', Courier, monospace;
31
33
  font-size: 1.4rem;
32
- color: var(--color-neutral-txt-primary);
33
34
  display: flex;
34
35
  align-items: center;
35
36
  gap: 0.8rem;
36
37
 
38
+ .content {
39
+ color: var(--color-neutral-txt-primary);
40
+ font-family: 'Courier New', Courier, monospace;
41
+ }
42
+
37
43
  .addons {
38
44
  margin-inline-start: auto;
39
45
  display: flex;
@@ -24,7 +24,11 @@ import { IK_DRAWER } from '@core/packages/drawer/types.ts'
24
24
  import { useMagicKeys, whenever } from '@vueuse/core'
25
25
  import { computed, inject, onMounted, ref } from 'vue'
26
26
 
27
- const { dismissible, isOpen, current } = defineProps<{
27
+ const {
28
+ dismissible,
29
+ isOpen = undefined,
30
+ current,
31
+ } = defineProps<{
28
32
  dismissible?: boolean
29
33
  isOpen?: boolean
30
34
  current?: boolean
@@ -1,16 +1,17 @@
1
1
  <template>
2
- <UiButton accent="brand" :busy="drawer?.isBusy.value" :variant size="medium">
2
+ <UiButton :accent :busy="drawer?.isBusy.value" :variant size="medium">
3
3
  <slot />
4
4
  </UiButton>
5
5
  </template>
6
6
 
7
7
  <script lang="ts" setup>
8
- import UiButton, { type ButtonVariant } from '@core/components/ui/button/UiButton.vue'
8
+ import UiButton, { type ButtonAccent, type ButtonVariant } from '@core/components/ui/button/UiButton.vue'
9
9
  import { IK_DRAWER } from '@core/packages/drawer/types.ts'
10
10
  import { inject } from 'vue'
11
11
 
12
- defineProps<{
12
+ const { accent = 'brand' } = defineProps<{
13
13
  variant: ButtonVariant
14
+ accent?: ButtonAccent
14
15
  }>()
15
16
 
16
17
  defineSlots<{
@@ -1,16 +1,18 @@
1
1
  <template>
2
- <VtsDrawerButton variant="primary" @click="handleClick()">
2
+ <VtsDrawerButton :accent variant="primary" @click="handleClick()">
3
3
  <slot />
4
4
  </VtsDrawerButton>
5
5
  </template>
6
6
 
7
7
  <script lang="ts" setup>
8
8
  import VtsDrawerButton from '@core/components/drawer/VtsDrawerButton.vue'
9
+ import type { ButtonAccent } from '@core/components/ui/button/UiButton.vue'
9
10
  import { IK_DRAWER } from '@core/packages/drawer/types.ts'
10
11
  import { inject } from 'vue'
11
12
 
12
- const { onClick } = defineProps<{
13
+ const { onClick, accent = 'brand' } = defineProps<{
13
14
  onClick?: () => void
15
+ accent?: ButtonAccent
14
16
  }>()
15
17
 
16
18
  defineSlots<{
@@ -1,10 +1,11 @@
1
1
  <template>
2
- <UiLoader v-if="busy" />
2
+ <UiLoader v-if="busy" v-tooltip="busyTooltip ?? false" />
3
3
  <DisplayIcon v-else-if="icon" class="vts-icon" :class="className" :icon />
4
4
  </template>
5
5
 
6
6
  <script lang="ts" setup>
7
7
  import UiLoader from '@core/components/ui/loader/UiLoader.vue'
8
+ import { vTooltip } from '@core/directives/tooltip.directive'
8
9
  import { type IconName, icons } from '@core/icons'
9
10
  import { DisplayIcon } from '@core/packages/icon'
10
11
  import { toVariants } from '@core/utils/to-variants.util.ts'
@@ -16,6 +17,7 @@ const { size, name } = defineProps<{
16
17
  size: IconSize
17
18
  name: IconName | undefined
18
19
  busy?: boolean
20
+ busyTooltip?: string
19
21
  }>()
20
22
 
21
23
  const className = computed(() =>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div
3
+ class="vts-content-side-panel"
4
+ :class="{
5
+ mobile: uiStore.isSmall,
6
+ 'has-panel-column': hasPanelColumn,
7
+ }"
8
+ >
9
+ <slot />
10
+ </div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { usePanelStore } from '@core/stores/panel.store'
15
+ import { useUiStore } from '@core/stores/ui.store'
16
+ import { computed } from 'vue'
17
+
18
+ const panelStore = usePanelStore()
19
+ const uiStore = useUiStore()
20
+
21
+ const hasPanelColumn = computed(() => (panelStore.isLocked || panelStore.isExpanded) && !uiStore.isSmall)
22
+ </script>
23
+
24
+ <style scoped lang="postcss">
25
+ .vts-content-side-panel {
26
+ --side-panel-width: 40rem;
27
+
28
+ &:not(.mobile) {
29
+ display: grid;
30
+ grid-template-columns: minmax(0, 1fr) 0;
31
+ align-items: start;
32
+ overflow-x: clip;
33
+ min-height: min-content;
34
+ transition: grid-template-columns 0.25s;
35
+
36
+ &.has-panel-column {
37
+ grid-template-columns: minmax(0, 1fr) var(--side-panel-width);
38
+ }
39
+ }
40
+ }
41
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <MenuList placement="bottom-end">
2
+ <MenuList placement="bottom-start">
3
3
  <template #trigger="{ open }">
4
4
  <UiButtonIcon
5
5
  v-tooltip="{
@@ -12,6 +12,7 @@
12
12
  @click="open($event)"
13
13
  />
14
14
  </template>
15
+ <slot>
15
16
  <MenuItem
16
17
  v-for="(action, index) of actions"
17
18
  :key="index"
@@ -22,7 +23,21 @@
22
23
  >
23
24
  {{ action.label }}
24
25
  <i v-if="action.hint">{{ action.hint }}</i>
26
+ <template v-if="isGroupAction(action)" #submenu>
27
+ <MenuItem
28
+ v-for="(child, childIndex) of action.children"
29
+ :key="childIndex"
30
+ :icon="child.icon"
31
+ :disabled="child.disabled"
32
+ :busy="child.busy"
33
+ :on-click="child.onClick"
34
+ >
35
+ {{ child.label }}
36
+ <i v-if="child.hint">{{ child.hint }}</i>
37
+ </MenuItem>
38
+ </template>
25
39
  </MenuItem>
40
+ </slot>
26
41
  </MenuList>
27
42
  </template>
28
43
 
@@ -40,14 +55,33 @@ const { size = 'small', actions = [] } = defineProps<{
40
55
  actions?: ActionItem[]
41
56
  }>()
42
57
 
58
+ defineSlots<{
59
+ default(): any
60
+ }>()
61
+
43
62
  const { t } = useI18n()
44
63
 
45
- export type ActionItem = {
64
+ type BaseActionItem = {
46
65
  label: string
47
66
  hint?: string
48
67
  icon?: IconName
49
- onClick: () => any
50
68
  disabled?: boolean
51
69
  busy?: boolean
52
70
  }
71
+
72
+ export type LeafActionItem = BaseActionItem & {
73
+ onClick: () => unknown
74
+ children?: never
75
+ }
76
+
77
+ export type GroupActionItem = BaseActionItem & {
78
+ onClick?: never
79
+ children: LeafActionItem[]
80
+ }
81
+
82
+ export type ActionItem = LeafActionItem | GroupActionItem
83
+
84
+ function isGroupAction(action: ActionItem): action is GroupActionItem {
85
+ return 'children' in action
86
+ }
53
87
  </script>
@@ -12,9 +12,10 @@
12
12
  </template>
13
13
 
14
14
  <script lang="ts" setup>
15
- import VtsStateHero, { type StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
15
+ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
16
16
  import UiCard from '@core/components/ui/card/UiCard.vue'
17
17
  import { useUiStore } from '@core/stores/ui.store.ts'
18
+ import type { StateHeroType } from '@core/types/state-hero.type.ts'
18
19
 
19
20
  defineProps<{
20
21
  title: string
@@ -0,0 +1,80 @@
1
+ <!-- v5 -->
2
+ <template>
3
+ <UiPanel :error>
4
+ <template v-if="needsHeader" #header>
5
+ <slot v-if="slots.header" name="header" />
6
+ <div v-if="slots.actions || slots['more-actions']" class="action-buttons">
7
+ <template v-if="slots.actions">
8
+ <slot name="actions" />
9
+ </template>
10
+ <MenuList v-if="slots['more-actions']" placement="bottom-end">
11
+ <template #trigger="{ open }">
12
+ <UiButtonIcon icon="action:more-actions" accent="brand" size="medium" @click="open($event)" />
13
+ </template>
14
+ <slot name="more-actions" />
15
+ </MenuList>
16
+ </div>
17
+ <div v-if="slots['corner-actions'] || closable" class="corner-actions">
18
+ <slot v-if="slots['corner-actions']" name="corner-actions" />
19
+ <UiButtonIcon
20
+ v-if="closable"
21
+ v-tooltip="t('action:close')"
22
+ size="small"
23
+ variant="tertiary"
24
+ accent="brand"
25
+ :icon="closeIcon"
26
+ @click="emit('close')"
27
+ />
28
+ </div>
29
+ </template>
30
+ <slot />
31
+ </UiPanel>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import MenuList from '@core/components/menu/MenuList.vue'
36
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
37
+ import UiPanel from '@core/components/ui/panel/UiPanel.vue'
38
+ import { vTooltip } from '@core/directives/tooltip.directive'
39
+ import type { IconName } from '@core/icons'
40
+ import { computed } from 'vue'
41
+ import { useI18n } from 'vue-i18n'
42
+
43
+ const { closable, closeIcon = 'action:close-cancel-clear' } = defineProps<{
44
+ error?: boolean
45
+ closable?: boolean
46
+ closeIcon?: IconName
47
+ }>()
48
+
49
+ const emit = defineEmits<{
50
+ close: []
51
+ }>()
52
+
53
+ const slots = defineSlots<{
54
+ default(): any
55
+ header?(): any
56
+ actions?(): any
57
+ 'more-actions'?(): any
58
+ 'corner-actions'?(): any
59
+ }>()
60
+
61
+ const { t } = useI18n()
62
+
63
+ const needsHeader = computed(
64
+ () => slots.header || slots.actions || slots['more-actions'] || slots['corner-actions'] || closable
65
+ )
66
+ </script>
67
+
68
+ <style scoped lang="postcss">
69
+ .action-buttons {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 1.6rem;
73
+ }
74
+ .corner-actions {
75
+ display: flex;
76
+ align-items: center;
77
+ margin-inline-start: auto;
78
+ gap: 0.8rem;
79
+ }
80
+ </style>
@@ -0,0 +1,140 @@
1
+ <!-- v5 -->
2
+ <template>
3
+ <div class="vts-side-panel-sticky" :class="{ mobile: uiStore.isSmall }">
4
+ <VtsPanel
5
+ :closable="hasSelection"
6
+ :close-icon="uiStore.isSmall ? 'fa:angle-left' : 'action:close-cancel-clear'"
7
+ class="vts-side-panel"
8
+ :class="{
9
+ locked: panelStore.isLocked && !uiStore.isSmall,
10
+ mobile: uiStore.isSmall,
11
+ }"
12
+ @close="handleClose"
13
+ >
14
+ <template v-if="slots.actions" #actions>
15
+ <slot name="actions" />
16
+ </template>
17
+
18
+ <template v-if="slots['more-actions']" #more-actions>
19
+ <slot name="more-actions" />
20
+ </template>
21
+
22
+ <template v-if="slots['corner-actions'] || !uiStore.isSmall" #corner-actions>
23
+ <slot v-if="slots['corner-actions']" name="corner-actions" />
24
+ <UiButtonIcon
25
+ v-if="!uiStore.isSmall"
26
+ v-tooltip="{
27
+ content: panelStore.isLocked ? t('action:sidebar-unlock') : t('action:sidebar-lock'),
28
+ placement: 'left',
29
+ }"
30
+ accent="brand"
31
+ size="small"
32
+ :icon="panelStore.isLocked ? 'action:pin-panel-hide' : 'action:pin-panel'"
33
+ @click="panelStore.toggleLock()"
34
+ />
35
+ </template>
36
+
37
+ <VtsStateHero v-if="!hasSelection" format="panel" type="no-selection" size="medium" />
38
+ <slot v-else />
39
+ </VtsPanel>
40
+ </div>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ import VtsPanel from '@core/components/panel/VtsPanel.vue'
45
+ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
46
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
47
+ import { vTooltip } from '@core/directives/tooltip.directive'
48
+ import { usePanelStore } from '@core/stores/panel.store'
49
+ import { useUiStore } from '@core/stores/ui.store'
50
+ import { watch } from 'vue'
51
+ import { useI18n } from 'vue-i18n'
52
+
53
+ const { hasSelection } = defineProps<{
54
+ hasSelection?: boolean
55
+ }>()
56
+
57
+ const emit = defineEmits<{
58
+ close: []
59
+ }>()
60
+
61
+ const slots = defineSlots<{
62
+ default(): any
63
+ actions?(): any
64
+ 'more-actions'?(): any
65
+ 'corner-actions'?(): any
66
+ }>()
67
+
68
+ const panelStore = usePanelStore()
69
+ const uiStore = useUiStore()
70
+
71
+ const { t } = useI18n()
72
+
73
+ watch(
74
+ [() => hasSelection, () => panelStore.isLocked, () => uiStore.isSmall],
75
+ ([hasSelection]) => {
76
+ if (hasSelection === undefined) {
77
+ return
78
+ }
79
+
80
+ panelStore.syncWithSelection(hasSelection)
81
+ },
82
+ { immediate: true }
83
+ )
84
+
85
+ function handleClose() {
86
+ if (panelStore.syncsOpenStateWithSelection) {
87
+ panelStore.collapse()
88
+ }
89
+
90
+ emit('close')
91
+ }
92
+ </script>
93
+
94
+ <style scoped lang="postcss">
95
+ .vts-side-panel-sticky:not(.mobile) {
96
+ position: sticky;
97
+ top: 0;
98
+ align-self: start;
99
+ }
100
+
101
+ .vts-side-panel {
102
+ width: var(--side-panel-width, 40rem);
103
+ transition: transform 0.25s;
104
+
105
+ &:not(.mobile) {
106
+ --panel-viewport-height: calc(100dvh - 5.6rem);
107
+ --panel-min-height: calc(100dvh - 16.6rem);
108
+
109
+ min-height: var(--panel-min-height);
110
+ max-height: var(--panel-viewport-height);
111
+ overflow: hidden;
112
+ transform: translateX(v-bind('panelStore.cssHorizontalOffset'));
113
+
114
+ :deep(> .content) {
115
+ flex: 1 1 auto;
116
+ min-height: 0;
117
+ overflow-y: auto;
118
+ }
119
+ }
120
+
121
+ &.mobile {
122
+ z-index: 1010;
123
+ position: fixed;
124
+ width: 100dvw;
125
+ height: 100dvh;
126
+ inset: 0;
127
+ transform: translateX(v-bind('panelStore.cssHorizontalOffset'));
128
+
129
+ :deep(.corner-actions) {
130
+ margin-inline-start: 0;
131
+ margin-inline-end: auto;
132
+ order: -1;
133
+ }
134
+
135
+ :deep(> .content) {
136
+ overflow: auto;
137
+ }
138
+ }
139
+ }
140
+ </style>
@@ -24,6 +24,7 @@
24
24
  :placeholder="searchPlaceholder"
25
25
  right-icon="fa:magnifying-glass"
26
26
  accent="brand"
27
+ detached
27
28
  />
28
29
  </div>
29
30
  </template>
@@ -9,32 +9,24 @@
9
9
  <div v-if="success">{{ t('all-good!') }}</div>
10
10
  <slot />
11
11
  </div>
12
+ <div v-else-if="type === 'no-selection'" :class="typoClass" class="content">
13
+ <I18nT keypath="select-to-see-details" scope="global" tag="p">
14
+ <template #icon>
15
+ <VtsIcon name="fa:eye" size="current" />
16
+ </template>
17
+ </I18nT>
18
+ </div>
12
19
  </div>
13
20
  </template>
14
21
 
15
22
  <script lang="ts" setup>
16
23
  import UiLoader from '@core/components/ui/loader/UiLoader.vue'
17
24
  import { useUiStore } from '@core/stores/ui.store'
25
+ import type { StateHeroFormat, StateHeroSize, StateHeroType } from '@core/types/state-hero.type.ts'
18
26
  import { toVariants } from '@core/utils/to-variants.util.ts'
19
27
  import { computed } from 'vue'
20
28
  import { useI18n } from 'vue-i18n'
21
-
22
- export type StateHeroFormat = 'page' | 'card' | 'panel' | 'table'
23
-
24
- export type StateHeroSize = 'extra-small' | 'small' | 'medium' | 'large'
25
-
26
- export type StateHeroType =
27
- | 'busy'
28
- | 'no-result'
29
- | 'under-construction'
30
- | 'no-data'
31
- | 'no-selection'
32
- | 'error'
33
- | 'not-found'
34
- | 'offline'
35
- | 'all-good'
36
- | 'all-done'
37
- | 'creating'
29
+ import VtsIcon from '../icon/VtsIcon.vue'
38
30
 
39
31
  const { format, type, size, horizontal } = defineProps<{
40
32
  format: StateHeroFormat
@@ -16,9 +16,10 @@
16
16
  </template>
17
17
 
18
18
  <script lang="ts" setup>
19
- import VtsStateHero, { type StateHeroSize, type StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
19
+ import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
20
20
  import UiTablePagination from '@core/components/ui/table-pagination/UiTablePagination.vue'
21
21
  import type { PaginationBindings } from '@core/composables/pagination.composable'
22
+ import type { StateHeroType, StateHeroSize } from '@core/types/state-hero.type.ts'
22
23
  import { hasEllipsis } from '@core/utils/has-ellipsis.util'
23
24
  import { toVariants } from '@core/utils/to-variants.util'
24
25
  import { useResizeObserver, useScroll } from '@vueuse/core'
@@ -9,6 +9,9 @@
9
9
  @click="emit('click')"
10
10
  />
11
11
  <VtsActionsMenu v-if="actions.length" :actions />
12
+ <VtsActionsMenu v-else>
13
+ <slot />
14
+ </VtsActionsMenu>
12
15
  </div>
13
16
  </UiTableCell>
14
17
  </template>
@@ -34,6 +37,10 @@ const {
34
37
  const emit = defineEmits<{
35
38
  click: []
36
39
  }>()
40
+
41
+ defineSlots<{
42
+ default(): any
43
+ }>()
37
44
  </script>
38
45
 
39
46
  <style scoped lang="postcss">
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <UiTableCell>
3
3
  <div class="vts-link-cell">
4
- <UiLink size="medium" :icon :to :href :target class="link">
4
+ <UiLink size="medium" :icon :busy :busy-tooltip :to :href :target class="link">
5
5
  <slot />
6
6
  <VtsIcon v-if="rightIcon" v-tooltip="rightIcon.tooltip ?? false" :name="rightIcon.icon" size="medium" />
7
7
  </UiLink>
@@ -21,6 +21,8 @@ import type { IconName } from '@core/icons'
21
21
 
22
22
  export type VtsLinkCellProps = LinkOptions & {
23
23
  icon?: IconName
24
+ busy?: boolean
25
+ busyTooltip?: string
24
26
  rightIcon?: {
25
27
  icon: IconName
26
28
  tooltip?: string
@@ -15,6 +15,7 @@ import { onBeforeMount, onBeforeUpdate, provide, ref, toRef, useSlots } from 'vu
15
15
  const props = defineProps<{
16
16
  nodeId?: TreeNodeId
17
17
  expanded?: boolean
18
+ hasChildren?: boolean
18
19
  }>()
19
20
 
20
21
  defineSlots<{
@@ -24,11 +25,16 @@ defineSlots<{
24
25
 
25
26
  const sidebar = useSidebarStore()
26
27
  const uiStore = useUiStore()
27
- const hasChildren = ref(false)
28
+ const hasChildrenState = ref(false)
28
29
 
29
30
  const updateHasChildren = () => {
31
+ if (props.hasChildren !== undefined) {
32
+ hasChildrenState.value = props.hasChildren
33
+ return
34
+ }
35
+
30
36
  const { sublist } = useSlots()
31
- hasChildren.value = sublist !== undefined
37
+ hasChildrenState.value = sublist !== undefined
32
38
  }
33
39
 
34
40
  const handleClick = () => {
@@ -40,6 +46,6 @@ const handleClick = () => {
40
46
  onBeforeMount(() => updateHasChildren())
41
47
  onBeforeUpdate(() => updateHasChildren())
42
48
 
43
- provide(IK_TREE_ITEM_HAS_CHILDREN, hasChildren)
49
+ provide(IK_TREE_ITEM_HAS_CHILDREN, hasChildrenState)
44
50
  provide(IK_TREE_ITEM_EXPANDED, toRef(props, 'expanded'))
45
51
  </script>
@@ -6,12 +6,15 @@
6
6
 
7
7
  <script lang="ts" setup>
8
8
  import { IK_TREE_LIST_DEPTH } from '@core/utils/injection-keys.util'
9
- import { inject, provide } from 'vue'
9
+ import { computed, inject, provide, ref } from 'vue'
10
10
 
11
11
  defineSlots<{
12
12
  default(): any
13
13
  }>()
14
14
 
15
- const depth = inject(IK_TREE_LIST_DEPTH, 0)
16
- provide(IK_TREE_LIST_DEPTH, depth + 1)
15
+ const depth = inject(IK_TREE_LIST_DEPTH, ref(0))
16
+ provide(
17
+ IK_TREE_LIST_DEPTH,
18
+ computed(() => depth.value + 1)
19
+ )
17
20
  </script>