@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
package/env.d.ts CHANGED
@@ -1,2 +1 @@
1
- /// <reference types="novnc__novnc" />
2
1
  /// <reference types="vite/client" />
@@ -4,3 +4,4 @@
4
4
  @import 'typography/_line-height.pcss';
5
5
  @import 'typography/_letter-spacing.pcss';
6
6
  @import 'typography/_legacy.pcss';
7
+ @import 'typography/_utils.pcss';
@@ -0,0 +1,6 @@
1
+ .text-ellipsis {
2
+ overflow: hidden;
3
+ white-space: nowrap;
4
+ text-overflow: ellipsis;
5
+ min-width: 0;
6
+ }
@@ -2,18 +2,24 @@
2
2
  <template>
3
3
  <div class="legend-title typo c3-semi-bold">
4
4
  <slot />
5
- <UiIcon v-tooltip="iconTooltip ?? false" :icon color="info" class="tooltip-icon" />
5
+ <UiIcon v-tooltip="iconTooltip ?? false" :icon color="info" />
6
6
  </div>
7
7
  </template>
8
8
 
9
- <script setup lang="ts">
9
+ <script lang="ts" setup>
10
10
  import UiIcon from '@core/components/icon/UiIcon.vue'
11
11
  import { vTooltip } from '@core/directives/tooltip.directive'
12
12
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
13
13
 
14
- defineProps<{
14
+ export type LegendTitleProps = {
15
15
  iconTooltip?: string
16
16
  icon?: IconDefinition
17
+ }
18
+
19
+ defineProps<LegendTitleProps>()
20
+
21
+ defineSlots<{
22
+ default(): void
17
23
  }>()
18
24
  </script>
19
25
 
@@ -24,8 +30,4 @@ defineProps<{
24
30
  gap: 0.8rem;
25
31
  align-items: center;
26
32
  }
27
-
28
- .tooltip-icon {
29
- font-size: 1.2rem;
30
- }
31
33
  </style>
@@ -4,7 +4,7 @@
4
4
  <slot name="icon">
5
5
  <UiIcon :icon fixed-width />
6
6
  </slot>
7
- <span class="content"><slot /></span>
7
+ <span class="text-ellipsis"><slot /></span>
8
8
  </span>
9
9
  </template>
10
10
 
@@ -78,7 +78,7 @@ withDefaults(
78
78
 
79
79
  /* IMPLEMENTATION */
80
80
  .ui-tag {
81
- display: inline-flex;
81
+ display: flex;
82
82
  justify-content: center;
83
83
  align-items: center;
84
84
  gap: 0.8rem;
@@ -88,10 +88,6 @@ withDefaults(
88
88
  padding: 0.2rem 0.8rem;
89
89
  border-radius: 0.4rem;
90
90
  vertical-align: middle;
91
-
92
- .content {
93
- overflow: hidden;
94
- text-overflow: ellipsis;
95
- }
91
+ min-width: 0;
96
92
  }
97
93
  </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="vts-backdrop" />
3
+ </template>
4
+
5
+ <style lang="postcss" scoped>
6
+ .vts-backdrop {
7
+ position: fixed;
8
+ inset: 0;
9
+ z-index: 1000;
10
+ }
11
+ </style>
@@ -0,0 +1,54 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <td class="cell-object">
4
+ <div class="data">
5
+ <slot />
6
+ <template v-if="id !== undefined">
7
+ <span v-tooltip class="id typo p4-regular-italic text-ellipsis">
8
+ {{ id }}
9
+ </span>
10
+ <UiButton
11
+ v-if="isSupported && copiableId"
12
+ :left-icon="faCopy"
13
+ level="secondary"
14
+ size="extra-small"
15
+ :color="copied ? 'success' : 'info'"
16
+ @click="copy(id)"
17
+ >
18
+ {{ copied ? $t('core.copied') : $t('core.copy-id') }}
19
+ </UiButton>
20
+ </template>
21
+ </div>
22
+ </td>
23
+ </template>
24
+
25
+ <script lang="ts" setup>
26
+ import UiButton from '@core/components/button/UiButton.vue'
27
+ import { vTooltip } from '@core/directives/tooltip.directive'
28
+ import { faCopy } from '@fortawesome/free-solid-svg-icons'
29
+ import { useClipboard } from '@vueuse/core'
30
+
31
+ defineProps<{
32
+ id?: string
33
+ copiableId?: boolean
34
+ }>()
35
+
36
+ const { isSupported, copy, copied } = useClipboard()
37
+ </script>
38
+
39
+ <style lang="postcss" scoped>
40
+ .cell-object {
41
+ padding: 0.8rem;
42
+ border: 0.1rem solid var(--color-grey-500);
43
+ }
44
+
45
+ .data {
46
+ display: flex;
47
+ gap: 1.6rem;
48
+ align-items: center;
49
+ }
50
+
51
+ .id {
52
+ color: var(--color-grey-300);
53
+ }
54
+ </style>
@@ -0,0 +1,40 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <td class="cell-text">
4
+ <div class="data typo p2-regular">
5
+ <span v-tooltip class="text-ellipsis">
6
+ <slot />
7
+ </span>
8
+ <span v-if="slots.secondary" class="info typo p4-regular">
9
+ <slot name="secondary" />
10
+ </span>
11
+ </div>
12
+ </td>
13
+ </template>
14
+
15
+ <script lang="ts" setup>
16
+ import { vTooltip } from '@core/directives/tooltip.directive'
17
+
18
+ const slots = defineSlots<{
19
+ default: () => any
20
+ secondary?: () => any
21
+ }>()
22
+ </script>
23
+
24
+ <style lang="postcss" scoped>
25
+ .cell-text {
26
+ padding: 0.8rem;
27
+ border: 0.1rem solid var(--color-grey-500);
28
+ color: var(--color-grey-000);
29
+ }
30
+
31
+ .data {
32
+ display: flex;
33
+ gap: 1.6rem;
34
+ align-items: center;
35
+ }
36
+
37
+ .info {
38
+ color: var(--color-grey-300);
39
+ }
40
+ </style>
@@ -2,7 +2,7 @@
2
2
  <template>
3
3
  <span :class="[color, { disabled }]" class="ui-chip typo p3-regular" @click="emit('edit')">
4
4
  <ChipIcon :color :disabled :icon />
5
- <span class="content">
5
+ <span class="content text-ellipsis">
6
6
  <slot />
7
7
  </span>
8
8
  <ChipRemoveIcon v-if="!disabled" :color @click.stop="emit('remove')" />
@@ -113,7 +113,7 @@ const emit = defineEmits<{
113
113
 
114
114
  /* IMPLEMENTATION */
115
115
  .ui-chip {
116
- display: inline-flex;
116
+ display: flex;
117
117
  align-items: center;
118
118
  gap: 0.8rem;
119
119
  padding: 0.4rem 0.8rem;
@@ -124,6 +124,7 @@ const emit = defineEmits<{
124
124
  min-height: 2.4rem;
125
125
  vertical-align: middle;
126
126
  white-space: nowrap;
127
+ min-width: 0;
127
128
 
128
129
  &.disabled {
129
130
  pointer-events: none;
@@ -131,8 +132,6 @@ const emit = defineEmits<{
131
132
 
132
133
  .content {
133
134
  line-height: 1.6rem;
134
- overflow: hidden;
135
- text-overflow: ellipsis;
136
135
  }
137
136
  }
138
137
  </style>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <script lang="ts" setup>
8
8
  import { useUiStore } from '@core/stores/ui.store'
9
- import VncClient from '@novnc/novnc/core/rfb'
9
+ import VncClient from '@novnc/novnc/lib/rfb'
10
10
  import { promiseTimeout } from '@vueuse/shared'
11
11
  import { fibonacci } from 'iterable-backoff'
12
12
  import { onBeforeUnmount, ref, watchEffect } from 'vue'
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div class="vts-divider" :class="type" />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ defineProps<{
7
+ type: 'stretch' | 'tab'
8
+ }>()
9
+ </script>
10
+
11
+ <style scoped lang="postcss">
12
+ .vts-divider {
13
+ border-left: 0.1rem solid var(--color-grey-500);
14
+ border-bottom: 0.1rem solid var(--color-grey-500);
15
+
16
+ &.tab {
17
+ height: 2.4rem;
18
+ align-self: center;
19
+ }
20
+
21
+ &.stretch {
22
+ align-self: stretch;
23
+ }
24
+ }
25
+ </style>
@@ -17,51 +17,47 @@
17
17
  </svg>
18
18
  </template>
19
19
 
20
- <script setup lang="ts">
20
+ <script lang="ts" setup>
21
21
  import UiIcon from '@core/components/icon/UiIcon.vue'
22
22
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
23
23
  import { computed } from 'vue'
24
24
 
25
- type Segment = {
25
+ export type DonutSegmentColor = 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'disabled'
26
+
27
+ export type DonutSegment = {
26
28
  value: number
27
- color: 'success' | 'warning' | 'error' | 'unknown'
29
+ color: DonutSegmentColor
28
30
  }
29
31
 
30
- type ComputedSegment = {
31
- color: 'success' | 'warning' | 'error' | 'unknown'
32
- percent: number
33
- offset: number
32
+ export type DonutChartProps = {
33
+ segments: DonutSegment[]
34
+ icon?: IconDefinition
34
35
  }
35
36
 
36
- const props = defineProps<{
37
- segments: Segment[]
38
- maxValue?: number
39
- icon?: IconDefinition
40
- }>()
37
+ const props = defineProps<DonutChartProps>()
41
38
 
42
39
  const circumference = Math.PI * 80
43
40
 
44
- const totalValue = computed(() => {
45
- const sumOfValues = props.segments.reduce((acc, segment) => acc + segment.value, 0)
46
- return Math.max(props.maxValue ?? 0, sumOfValues)
47
- })
41
+ const totalValue = computed(() => props.segments.reduce((total, segment) => total + segment.value, 0))
48
42
 
49
- const computedSegments = computed<ComputedSegment[]>(() => {
43
+ const computedSegments = computed(() => {
50
44
  let nextOffset = circumference / 4
45
+
51
46
  return props.segments.map(segment => {
47
+ const offset = nextOffset
52
48
  const percent = (segment.value / totalValue.value) * circumference
53
- const currentSegment = {
49
+ nextOffset -= percent
50
+
51
+ return {
54
52
  color: segment.color,
55
53
  percent,
56
- offset: nextOffset,
54
+ offset,
57
55
  }
58
- nextOffset -= percent
59
- return currentSegment
60
56
  })
61
57
  })
62
58
  </script>
63
59
 
64
- <style scoped lang="postcss">
60
+ <style lang="postcss" scoped>
65
61
  .donut-chart {
66
62
  width: 100px;
67
63
  height: 100px;
@@ -73,6 +69,14 @@ const computedSegments = computed<ComputedSegment[]>(() => {
73
69
  fill: transparent;
74
70
  --stroke-color: var(--color-grey-100);
75
71
 
72
+ &.primary {
73
+ --stroke-color: var(--color-purple-base);
74
+ }
75
+
76
+ &.secondary {
77
+ --stroke-color: var(--color-grey-100);
78
+ }
79
+
76
80
  &.success {
77
81
  --stroke-color: var(--color-green-base);
78
82
  }
@@ -81,11 +85,11 @@ const computedSegments = computed<ComputedSegment[]>(() => {
81
85
  --stroke-color: var(--color-orange-base);
82
86
  }
83
87
 
84
- &.error {
88
+ &.danger {
85
89
  --stroke-color: var(--color-red-base);
86
90
  }
87
91
 
88
- &.unknown {
92
+ &.disabled {
89
93
  --stroke-color: var(--color-grey-400);
90
94
  }
91
95
  }
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <div class="donut-chart-with-legend">
3
+ <DonutChart :icon :segments />
4
+ <LegendGroup :items="segments" :title />
5
+ </div>
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import DonutChart, { type DonutChartProps } from '@core/components/donut-chart/DonutChart.vue'
10
+ import LegendGroup, { type LegendGroupProps } from '@core/components/legend/LegendGroup.vue'
11
+
12
+ export type DonutChartWithLegendProps = {
13
+ segments: (DonutChartProps['segments'][number] & LegendGroupProps['items'][number])[]
14
+ title?: LegendGroupProps['title']
15
+ icon?: DonutChartProps['icon']
16
+ }
17
+
18
+ defineProps<DonutChartWithLegendProps>()
19
+ </script>
20
+
21
+ <style lang="postcss" scoped>
22
+ .donut-chart-with-legend {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 3.2rem;
26
+ }
27
+ </style>
@@ -4,7 +4,7 @@
4
4
  <slot name="icon">
5
5
  <UiIcon :icon />
6
6
  </slot>
7
- <div class="label p2 medium">
7
+ <div class="label p2 medium text-ellipsis">
8
8
  <slot />
9
9
  </div>
10
10
  <div v-if="info" class="info-text p3 italic">{{ info }}</div>
@@ -181,9 +181,6 @@ const checkbox = inject(
181
181
 
182
182
  .label {
183
183
  margin-right: auto;
184
- white-space: nowrap;
185
- overflow: hidden;
186
- text-overflow: ellipsis;
187
184
  }
188
185
 
189
186
  .info-text {
@@ -0,0 +1,78 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <div class="head-bar">
4
+ <div class="label-wrapper">
5
+ <span v-if="slots.icon || icon" class="icon">
6
+ <slot name="icon">
7
+ <UiIcon :icon />
8
+ </slot>
9
+ </span>
10
+ <h4 v-tooltip class="typo h4-medium label text-ellipsis">
11
+ <slot />
12
+ </h4>
13
+ </div>
14
+ <div v-if="slots.status" class="status typo c3-regular">
15
+ <slot name="status" />
16
+ </div>
17
+ <div v-if="slots.actions" class="actions">
18
+ <slot name="actions" />
19
+ </div>
20
+ </div>
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
+
28
+ defineProps<{
29
+ icon?: IconDefinition
30
+ }>()
31
+
32
+ const slots = defineSlots<{
33
+ default: () => any
34
+ icon?: () => any
35
+ status?: () => any
36
+ actions?: () => any
37
+ }>()
38
+ </script>
39
+
40
+ <style lang="postcss" scoped>
41
+ .head-bar {
42
+ padding: 0.8rem 1.6rem;
43
+ display: flex;
44
+ gap: 4.8rem;
45
+ align-items: center;
46
+ border-bottom: 0.1rem solid var(--color-grey-500);
47
+ background-color: var(--background-color-primary);
48
+ }
49
+
50
+ .label-wrapper {
51
+ display: flex;
52
+ gap: 1.6rem;
53
+ align-items: center;
54
+ min-width: 0;
55
+ }
56
+
57
+ .label {
58
+ color: var(--color-grey-100);
59
+ }
60
+
61
+ .icon {
62
+ font-size: 2.4rem;
63
+ }
64
+
65
+ .status {
66
+ color: var(--color-grey-200);
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 1.6rem;
70
+ }
71
+
72
+ .actions {
73
+ margin-inline-start: auto;
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 0.8rem;
77
+ }
78
+ </style>
@@ -0,0 +1,133 @@
1
+ <!-- WIP -->
2
+ <template>
3
+ <div class="ui-input">
4
+ <UiIcon :icon class="before" />
5
+ <input :id v-model.trim="modelValue" class="typo p1-regular input" type="search" v-bind="$attrs" />
6
+ <UiIcon v-if="!$attrs.disabled && modelValue" :icon="faXmark" class="after" color="info" @click="modelValue = ''" />
7
+ </div>
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import UiIcon from '@core/components/icon/UiIcon.vue'
12
+ import { uniqueId } from '@core/utils/unique-id.util'
13
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
14
+ import { faXmark } from '@fortawesome/free-solid-svg-icons'
15
+ import { computed } from 'vue'
16
+
17
+ defineOptions({
18
+ inheritAttrs: false,
19
+ })
20
+
21
+ defineProps<{
22
+ icon?: IconDefinition
23
+ }>()
24
+
25
+ const modelValue = defineModel<string>({ required: true })
26
+
27
+ const id = computed(() => uniqueId('input-'))
28
+ </script>
29
+
30
+ <style lang="postcss" scoped>
31
+ /* COLOR VARIANTS */
32
+ .input {
33
+ & {
34
+ --border-color: var(--color-grey-500);
35
+ --background-color: var(--background-color-primary);
36
+ }
37
+
38
+ &:is(:hover, :focus-visible) {
39
+ --border-color: var(--color-purple-d20);
40
+ --background-color: var(--background-color-primary);
41
+ }
42
+
43
+ &:focus {
44
+ --border-color: var(--color-purple-base);
45
+ --background-color: var(--background-color-primary);
46
+ }
47
+
48
+ &:active {
49
+ --border-color: var(--color-purple-d40);
50
+ --background-color: var(--background-color-primary);
51
+ }
52
+
53
+ &:disabled {
54
+ --border-color: var(--color-grey-500);
55
+ --background-color: var(--background-color-secondary);
56
+ }
57
+ }
58
+
59
+ /* BORDER VARIANTS */
60
+ .input {
61
+ --border-width: 0.1rem;
62
+
63
+ &:is(:hover, :focus-visible) {
64
+ --border-width: 0.1rem;
65
+ }
66
+
67
+ &:focus {
68
+ --border-width: 0.2rem;
69
+ }
70
+
71
+ &:active {
72
+ --border-width: 0.1rem;
73
+ }
74
+
75
+ &:disabled {
76
+ --border-width: 0.1rem;
77
+ }
78
+ }
79
+
80
+ /* IMPLEMENTATION */
81
+ .ui-input {
82
+ position: relative;
83
+
84
+ .before + .input {
85
+ padding-inline-start: 4.8rem;
86
+ }
87
+ }
88
+
89
+ .input {
90
+ border: var(--border-width) solid var(--border-color);
91
+ border-radius: 0.8rem;
92
+ outline: none;
93
+ width: 100%;
94
+ height: 4rem;
95
+ padding: 0.8rem 1.6rem;
96
+ color: var(--color-grey-000);
97
+ background-color: var(--background-color);
98
+
99
+ &:has(+ .after) {
100
+ padding-inline-end: 4.8rem;
101
+ }
102
+
103
+ &:disabled {
104
+ cursor: not-allowed;
105
+ }
106
+
107
+ &::placeholder {
108
+ color: var(--color-grey-300);
109
+ }
110
+
111
+ &[type='search']::-webkit-search-cancel-button {
112
+ display: none;
113
+ }
114
+ }
115
+
116
+ .before,
117
+ .after {
118
+ position: absolute;
119
+ inset-block: 1.2rem;
120
+ }
121
+
122
+ .before {
123
+ color: var(--color-grey-400);
124
+ inset-inline-start: 1.6rem;
125
+ pointer-events: none;
126
+ z-index: 1;
127
+ }
128
+
129
+ .after {
130
+ inset-inline-end: 1.6rem;
131
+ cursor: pointer;
132
+ }
133
+ </style>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <div class="legend-group">
3
+ <LegendTitle v-if="title" :icon="title.icon" :icon-tooltip="title.iconTooltip">
4
+ {{ title.label }}
5
+ </LegendTitle>
6
+ <LegendList class="list">
7
+ <LegendItem
8
+ v-for="item in items"
9
+ :key="item.label"
10
+ :color="item.color"
11
+ :tooltip="item.tooltip"
12
+ :unit="item.unit"
13
+ :value="item.value"
14
+ >
15
+ {{ item.label }}
16
+ </LegendItem>
17
+ </LegendList>
18
+ </div>
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ import LegendItem, { type LegendItemProps } from '@core/components/legend/LegendItem.vue'
23
+ import LegendList from '@core/components/legend/LegendList.vue'
24
+ import LegendTitle, { type LegendTitleProps } from '@core/components/LegendTitle.vue'
25
+
26
+ export type LegendGroupProps = {
27
+ items: (LegendItemProps & { label: string })[]
28
+ title?: LegendTitleProps & { label: string }
29
+ }
30
+
31
+ defineProps<LegendGroupProps>()
32
+ </script>
33
+
34
+ <style lang="postcss" scoped>
35
+ .legend-group {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 0.8rem;
39
+ }
40
+
41
+ .list {
42
+ padding-left: 1.6rem;
43
+ }
44
+ </style>