@xen-orchestra/web-core 0.4.0 → 0.6.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 (132) hide show
  1. package/lib/assets/css/_colors.pcss +56 -24
  2. package/lib/assets/css/_context.pcss +9 -9
  3. package/lib/assets/css/base.pcss +6 -6
  4. package/lib/assets/error.svg +375 -0
  5. package/lib/assets/no-selection.svg +70 -0
  6. package/lib/components/backup-item/VtsBackupItem.vue +47 -0
  7. package/lib/components/backup-state/{BackupState.vue → VtsBackupState.vue} +4 -4
  8. package/lib/components/{button/ButtonGroup.vue → button-group/VtsButtonGroup.vue} +7 -6
  9. package/lib/components/cell-object/{CellObject.vue → VtsCellObject.vue} +14 -14
  10. package/lib/components/cell-text/{CellText.vue → VtsCellText.vue} +11 -11
  11. package/lib/components/console/VtsActionsConsole.vue +67 -0
  12. package/lib/components/console/VtsClipboardConsole.vue +38 -0
  13. package/lib/components/console/VtsLayoutConsole.vue +40 -0
  14. package/lib/components/console/{RemoteConsole.vue → VtsRemoteConsole.vue} +14 -17
  15. package/lib/components/donut-chart-with-legend/{DonutChartWithLegend.vue → VtsDonutChartWithLegend.vue} +6 -6
  16. package/lib/components/dropdown/DropdownItem.vue +8 -14
  17. package/lib/components/dropdown/DropdownTitle.vue +3 -3
  18. package/lib/components/icon/VtsIcon.vue +38 -42
  19. package/lib/components/layout/{LayoutSidebar.vue → VtsLayoutSidebar.vue} +31 -30
  20. package/lib/components/legend-group/VtsLegendGroup.vue +44 -0
  21. package/lib/components/{legend/LegendList.vue → legend-list/VtsLegendList.vue} +2 -2
  22. package/lib/components/menu/MenuTrigger.vue +2 -2
  23. package/lib/components/stacked-bar-with-legend/{StackedBarWithLegend.vue → VtsStackedBarWithLegend.vue} +14 -14
  24. package/lib/components/state-hero/VtsComingSoonHero.vue +13 -0
  25. package/lib/components/state-hero/VtsLoadingHero.vue +15 -0
  26. package/lib/components/state-hero/VtsNoDataHero.vue +11 -0
  27. package/lib/components/state-hero/VtsObjectNotFoundHero.vue +13 -0
  28. package/lib/components/state-hero/VtsStateHero.vue +117 -0
  29. package/lib/components/tab/TabItem.vue +6 -6
  30. package/lib/components/table/ColumnTitle.vue +7 -7
  31. package/lib/components/table/{UiTable.vue → VtsTable.vue} +7 -3
  32. package/lib/components/task/{QuickTaskButton.vue → VtsQuickTaskButton.vue} +8 -7
  33. package/lib/components/task/{QuickTaskList.vue → VtsQuickTaskList.vue} +10 -11
  34. package/lib/components/task/{QuickTaskTabBar.vue → VtsQuickTaskTabBar.vue} +9 -13
  35. package/lib/components/{tooltip/TooltipList.vue → tooltip-list/VtsTooltipList.vue} +2 -3
  36. package/lib/components/tree/{TreeItem.vue → VtsTreeItem.vue} +3 -4
  37. package/lib/components/tree/VtsTreeItemError.vue +18 -0
  38. package/lib/components/tree/{TreeLine.vue → VtsTreeLine.vue} +9 -11
  39. package/lib/components/tree/{TreeList.vue → VtsTreeList.vue} +5 -2
  40. package/lib/components/tree/VtsTreeLoadingItem.vue +61 -0
  41. package/lib/components/ui/account-menu-button/UiAccountMenuButton.vue +64 -0
  42. package/lib/components/ui/actions-title/UiActionsTitle.vue +2 -2
  43. package/lib/components/ui/button/UiButton.vue +532 -0
  44. package/lib/components/ui/button-icon/UiButtonIcon.vue +248 -0
  45. package/lib/components/{UiCard.vue → ui/card/UiCard.vue} +8 -6
  46. package/lib/components/ui/card-numbers/UiCardNumbers.vue +103 -0
  47. package/lib/components/ui/card-subtitle/UiCardSubtitle.vue +24 -0
  48. package/lib/components/ui/card-title/UiCardTitle.vue +56 -0
  49. package/lib/components/ui/checkbox/UiCheckbox.vue +410 -0
  50. package/lib/components/ui/checkbox-group/UiCheckboxGroup.vue +57 -0
  51. package/lib/components/ui/chip/ChipIcon.vue +21 -0
  52. package/lib/components/ui/chip/ChipRemoveIcon.vue +13 -0
  53. package/lib/components/ui/chip/UiChip.vue +135 -0
  54. package/lib/components/{icon/ComplexIcon.vue → ui/complex-icon/UiComplexIcon.vue} +21 -27
  55. package/lib/components/ui/counter/UiCounter.vue +134 -0
  56. package/lib/components/{donut-chart/DonutChart.vue → ui/donut-chart/UiDonutChart.vue} +28 -30
  57. package/lib/components/{head-bar/HeadBar.vue → ui/head-bar/UiHeadBar.vue} +31 -31
  58. package/lib/components/{info/VtsInfo.vue → ui/info/UiInfo.vue} +13 -11
  59. package/lib/components/{input → ui/input}/UiInput.vue +9 -7
  60. package/lib/components/ui/input/UiTextarea.vue +120 -0
  61. package/lib/components/{input/VtsLabel.vue → ui/label/UiLabel.vue} +25 -25
  62. package/lib/components/ui/legend/UiLegend.vue +98 -0
  63. package/lib/components/{LegendTitle.vue → ui/legend-title/UiLegendTitle.vue} +4 -4
  64. package/lib/components/{UiSpinner.vue → ui/loader/UiLoader.vue} +3 -3
  65. package/lib/components/{icon/ObjectIcon.vue → ui/object-icon/UiObjectIcon.vue} +44 -36
  66. package/lib/components/ui/object-link/UiObjectLink.vue +83 -0
  67. package/lib/components/ui/panel/UiPanel.vue +57 -0
  68. package/lib/components/{query-search-bar/QuerySearchBar.vue → ui/query-search-bar/UiQuerySearchBar.vue} +16 -16
  69. package/lib/components/{task/QuickTaskItem.vue → ui/quick-task-item/UiQuickTaskItem.vue} +46 -34
  70. package/lib/components/{task/QuickTaskPanel.vue → ui/quick-task-panel/UiQuickTaskPanel.vue} +8 -7
  71. package/lib/components/ui/radio-button/UiRadioButton.vue +196 -0
  72. package/lib/components/ui/radio-button-group/UiRadioButtonGroup.vue +56 -0
  73. package/lib/components/{stacked-bar → ui/stacked-bar}/StackedBarSegment.vue +23 -26
  74. package/lib/components/{stacked-bar/StackedBar.vue → ui/stacked-bar/UiStackedBar.vue} +6 -6
  75. package/lib/components/ui/table-actions/UiTableActions.vue +46 -0
  76. package/lib/components/ui/tag/UiTag.vue +118 -0
  77. package/lib/components/ui/title/UiTitle.vue +2 -2
  78. package/lib/components/ui/toaster/UiToaster.vue +100 -0
  79. package/lib/components/ui/toggle/UiToggle.vue +117 -0
  80. package/lib/components/{tooltip/TooltipItem.vue → ui/tooltip/UiTooltip.vue} +15 -15
  81. package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +64 -0
  82. package/lib/components/{tree/TreeItemLabel.vue → ui/tree-item-label/UiTreeItemLabel.vue} +60 -59
  83. package/lib/components/ui/user-link/UiUserLink.vue +76 -0
  84. package/lib/components/ui/user-logo/UiUserLogo.vue +50 -0
  85. package/lib/composables/route-query.composable.md +136 -0
  86. package/lib/composables/table.composable.md +159 -0
  87. package/lib/composables/tree/define-tree.ts +1 -1
  88. package/lib/composables/tree/tree-node-base.ts +6 -6
  89. package/lib/composables/tree.composable.md +536 -0
  90. package/lib/i18n.ts +4 -0
  91. package/lib/layouts/CoreLayout.vue +8 -6
  92. package/lib/locales/cs.json +76 -0
  93. package/lib/locales/de.json +5 -2
  94. package/lib/locales/en.json +14 -3
  95. package/lib/locales/fa.json +5 -2
  96. package/lib/locales/fr.json +14 -3
  97. package/lib/types/color.type.ts +0 -2
  98. package/lib/types/subscribable-store.type.ts +2 -2
  99. package/lib/utils/create-subscribable-store-context.util.ts +1 -1
  100. package/lib/utils/if-else.utils.md +23 -0
  101. package/lib/utils/if-else.utils.ts +2 -2
  102. package/lib/utils/to-variants.util.md +62 -0
  103. package/package.json +7 -7
  104. package/lib/components/CardNumbers.vue +0 -101
  105. package/lib/components/PowerStateIcon.vue +0 -46
  106. package/lib/components/UiTag.vue +0 -101
  107. package/lib/components/backup-item/BackupItem.vue +0 -40
  108. package/lib/components/button/ButtonIcon.vue +0 -220
  109. package/lib/components/button/UiButton.vue +0 -470
  110. package/lib/components/card/CardSubtitle.vue +0 -24
  111. package/lib/components/card/CardTitle.vue +0 -57
  112. package/lib/components/chip/ChipIcon.vue +0 -30
  113. package/lib/components/chip/ChipRemoveIcon.vue +0 -13
  114. package/lib/components/chip/UiChip.vue +0 -137
  115. package/lib/components/counter/VtsCounter.vue +0 -147
  116. package/lib/components/legend/LegendGroup.vue +0 -44
  117. package/lib/components/legend/LegendItem.vue +0 -86
  118. package/lib/components/object-link/ObjectLink.vue +0 -87
  119. package/lib/components/state-hero/ComingSoonHero.vue +0 -13
  120. package/lib/components/state-hero/LoadingHero.vue +0 -15
  121. package/lib/components/state-hero/NoDataHero.vue +0 -11
  122. package/lib/components/state-hero/ObjectNotFoundHero.vue +0 -13
  123. package/lib/components/state-hero/StateHero.vue +0 -74
  124. package/lib/components/tree/TreeItemError.vue +0 -13
  125. package/lib/components/tree/TreeLoadingItem.vue +0 -60
  126. package/lib/components/user/UserLink.vue +0 -72
  127. package/lib/components/user/UserLogo.vue +0 -57
  128. package/lib/types/backup.type.ts +0 -11
  129. package/lib/types/size.type.ts +0 -1
  130. package/lib/types/task.type.ts +0 -13
  131. /package/lib/components/backdrop/{Backdrop.vue → VtsBackdrop.vue} +0 -0
  132. /package/lib/components/divider/{Divider.vue → VtsDivider.vue} +0 -0
@@ -0,0 +1,117 @@
1
+ <!-- v2 -->
2
+ <template>
3
+ <label class="ui-toggle typo c2-semi-bold">
4
+ <slot />
5
+ <span class="toggle-container">
6
+ <input v-model="checked" :disabled="isDisabled || busy" class="input" type="checkbox" />
7
+ <span :class="{ checked }" class="toggle-icon">
8
+ <UiLoader :class="{ visible: busy }" class="spinner" />
9
+ </span>
10
+ </span>
11
+ </label>
12
+ </template>
13
+
14
+ <script lang="ts" setup>
15
+ import UiLoader from '@core/components/ui/loader/UiLoader.vue'
16
+ import { useContext } from '@core/composables/context.composable'
17
+ import { DisabledContext } from '@core/context'
18
+
19
+ const props = withDefaults(
20
+ defineProps<{
21
+ disabled?: boolean
22
+ busy?: boolean
23
+ }>(),
24
+ { disabled: undefined }
25
+ )
26
+
27
+ const checked = defineModel<boolean>()
28
+
29
+ defineSlots<{
30
+ default(): any
31
+ }>()
32
+
33
+ const isDisabled = useContext(DisabledContext, () => props.disabled)
34
+ </script>
35
+
36
+ <style lang="postcss" scoped>
37
+ .ui-toggle {
38
+ display: inline-flex;
39
+ gap: 1.6rem;
40
+ align-items: center;
41
+
42
+ .toggle-container {
43
+ --transition-timing: 0.25s ease-in-out;
44
+ --border-color: var(--color-neutral-txt-secondary);
45
+ height: 2rem;
46
+ width: 4rem;
47
+ background-color: var(--color-neutral-background-primary);
48
+ border: 0.1rem solid var(--border-color);
49
+ border-radius: 1rem;
50
+ transition:
51
+ background-color var(--transition-timing),
52
+ border-color var(--transition-timing);
53
+
54
+ &:has(input:disabled) {
55
+ --border-color: var(--color-neutral-border);
56
+ background-color: var(--color-neutral-background-disabled);
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ &:has(input:checked) {
61
+ background-color: var(--color-success-item-base);
62
+ }
63
+
64
+ &:has(input:checked:disabled) {
65
+ background-color: var(--color-success-item-disabled);
66
+ }
67
+
68
+ &:has(input:focus-visible) {
69
+ outline: none;
70
+
71
+ &::after {
72
+ position: absolute;
73
+ content: '';
74
+ inset: -0.5rem;
75
+ border: 0.2rem solid var(--color-normal-txt-base);
76
+ border-radius: 0.4rem;
77
+ }
78
+ }
79
+ }
80
+
81
+ .input {
82
+ position: absolute;
83
+ pointer-events: none;
84
+ opacity: 0;
85
+ }
86
+
87
+ .toggle-icon {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ font-size: 1.4rem;
92
+ width: 2rem;
93
+ height: 2rem;
94
+ margin: -0.1rem 0 0 -0.1rem;
95
+ color: var(--color-info-txt-base);
96
+ background-color: var(--color-neutral-background-primary);
97
+ border: 0.1rem solid var(--border-color);
98
+ border-radius: 1rem;
99
+ transition:
100
+ transform var(--transition-timing),
101
+ border-color var(--transition-timing);
102
+
103
+ &.checked {
104
+ transform: translateX(2rem);
105
+ }
106
+ }
107
+
108
+ .spinner {
109
+ opacity: 0;
110
+ transition: opacity var(--transition-timing);
111
+
112
+ &.visible {
113
+ opacity: 1;
114
+ }
115
+ }
116
+ }
117
+ </style>
@@ -1,6 +1,6 @@
1
- <!-- v1.0 -->
1
+ <!-- v2 -->
2
2
  <template>
3
- <div v-if="!isDisabled" ref="tooltipElement" class="app-tooltip typo p3-regular">
3
+ <div v-if="!isDisabled" ref="tooltipElement" class="ui-tooltip typo p3-regular">
4
4
  {{ content }}
5
5
  </div>
6
6
  </template>
@@ -57,7 +57,7 @@ watch(content, () => updatePlacement(), { flush: 'post' })
57
57
  </script>
58
58
 
59
59
  <style lang="postcss" scoped>
60
- .app-tooltip {
60
+ .ui-tooltip {
61
61
  position: relative;
62
62
  display: inline-flex;
63
63
  padding: 0.4rem 0.8rem;
@@ -66,21 +66,21 @@ watch(content, () => updatePlacement(), { flush: 'post' })
66
66
  border-radius: 0.4rem;
67
67
  background-color: var(--color-neutral-txt-primary);
68
68
  z-index: 999999;
69
- }
70
69
 
71
- [data-placement^='top'] {
72
- margin-bottom: 0.7rem;
73
- }
70
+ [data-placement^='top'] {
71
+ margin-bottom: 0.7rem;
72
+ }
74
73
 
75
- [data-placement^='right'] {
76
- margin-left: 0.7rem;
77
- }
74
+ [data-placement^='right'] {
75
+ margin-left: 0.7rem;
76
+ }
78
77
 
79
- [data-placement^='bottom'] {
80
- margin-top: 0.7rem;
81
- }
78
+ [data-placement^='bottom'] {
79
+ margin-top: 0.7rem;
80
+ }
82
81
 
83
- [data-placement^='left'] {
84
- margin-right: 0.7rem;
82
+ [data-placement^='left'] {
83
+ margin-right: 0.7rem;
84
+ }
85
85
  }
86
86
  </style>
@@ -0,0 +1,64 @@
1
+ <!-- v2 -->
2
+ <template>
3
+ <div class="ui-top-bottom-table">
4
+ <div class="content">
5
+ <span class="typo p3-regular label">
6
+ {{ $t('core.select.n-selected-of', { count: selectedItems, total: totalItems }) }}
7
+ </span>
8
+
9
+ <UiButton
10
+ :disabled="selectedItems === totalItems"
11
+ accent="info"
12
+ size="small"
13
+ variant="tertiary"
14
+ @click="emit('toggleSelectAll', true)"
15
+ >
16
+ {{ $t('core.select.all') }}
17
+ </UiButton>
18
+ <UiButton
19
+ :disabled="selectedItems === 0"
20
+ accent="info"
21
+ size="small"
22
+ variant="tertiary"
23
+ @click="emit('toggleSelectAll', false)"
24
+ >
25
+ {{ $t('core.select.unselect') }}
26
+ </UiButton>
27
+ </div>
28
+ <slot />
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import UiButton from '@core/components/ui/button/UiButton.vue'
34
+
35
+ defineProps<{
36
+ selectedItems: number
37
+ totalItems: number
38
+ }>()
39
+
40
+ const emit = defineEmits<{
41
+ toggleSelectAll: [value: boolean]
42
+ }>()
43
+
44
+ defineSlots<{
45
+ default(): any
46
+ }>()
47
+ </script>
48
+
49
+ <style scoped lang="postcss">
50
+ .ui-top-bottom-table {
51
+ display: flex;
52
+ justify-content: space-between;
53
+
54
+ .content {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 0.8rem;
58
+ }
59
+
60
+ .label {
61
+ color: var(--color-neutral-txt-secondary);
62
+ }
63
+ }
64
+ </style>
@@ -1,23 +1,24 @@
1
- <!-- v1.1 -->
1
+ <!-- v2 -->
2
2
  <template>
3
3
  <RouterLink v-slot="{ isExactActive, isActive, href, navigate }" :to="route" custom>
4
4
  <div
5
5
  :class="isExactActive ? 'exact-active' : active || isActive ? 'active' : undefined"
6
- class="tree-item-label"
7
- v-bind="$attrs"
6
+ class="ui-tree-item-label"
7
+ v-bind="attrs"
8
8
  >
9
9
  <template v-if="depth > 1">
10
- <TreeLine
10
+ <VtsTreeLine
11
11
  v-for="i in depth - 1"
12
12
  :key="i"
13
13
  :half-height="(!hasToggle && i === depth - 1) || !isExpanded"
14
14
  :right="i === depth - 1"
15
15
  />
16
16
  </template>
17
- <ButtonIcon
17
+ <UiButtonIcon
18
18
  v-if="hasToggle"
19
19
  v-tooltip="isExpanded ? $t('core.close') : $t('core.open')"
20
20
  class="toggle"
21
+ accent="info"
21
22
  :icon="isExpanded ? faAngleDown : faAngleRight"
22
23
  size="small"
23
24
  :target-scale="{ x: 1.5, y: 2 }"
@@ -26,7 +27,7 @@
26
27
  <div v-else class="h-line" />
27
28
  <a v-tooltip="{ selector: '.text' }" :href class="link typo p2-medium" @click="navigate">
28
29
  <slot name="icon">
29
- <VtsIcon :icon accent="current" class="icon" />
30
+ <UiIcon :icon accent="current" class="icon" />
30
31
  </slot>
31
32
  <div class="text text-ellipsis">
32
33
  <slot />
@@ -38,14 +39,14 @@
38
39
  </template>
39
40
 
40
41
  <script lang="ts" setup>
41
- import ButtonIcon from '@core/components/button/ButtonIcon.vue'
42
- import VtsIcon from '@core/components/icon/VtsIcon.vue'
43
- import TreeLine from '@core/components/tree/TreeLine.vue'
42
+ import UiIcon from '@core/components/icon/VtsIcon.vue'
43
+ import VtsTreeLine from '@core/components/tree/VtsTreeLine.vue'
44
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
44
45
  import { vTooltip } from '@core/directives/tooltip.directive'
45
46
  import { IK_TREE_ITEM_EXPANDED, IK_TREE_ITEM_HAS_CHILDREN, IK_TREE_LIST_DEPTH } from '@core/utils/injection-keys.util'
46
47
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
47
48
  import { faAngleDown, faAngleRight } from '@fortawesome/free-solid-svg-icons'
48
- import { inject, ref } from 'vue'
49
+ import { inject, ref, useAttrs } from 'vue'
49
50
  import type { RouteLocationRaw } from 'vue-router'
50
51
 
51
52
  defineOptions({
@@ -53,8 +54,8 @@ defineOptions({
53
54
  })
54
55
 
55
56
  defineProps<{
56
- icon?: IconDefinition
57
57
  route: RouteLocationRaw
58
+ icon?: IconDefinition
58
59
  active?: boolean
59
60
  }>()
60
61
 
@@ -62,6 +63,14 @@ const emit = defineEmits<{
62
63
  toggle: []
63
64
  }>()
64
65
 
66
+ defineSlots<{
67
+ default(): any
68
+ icon?(): any
69
+ addons?(): any
70
+ }>()
71
+
72
+ const attrs = useAttrs()
73
+
65
74
  const hasToggle = inject(IK_TREE_ITEM_HAS_CHILDREN, ref(false))
66
75
 
67
76
  const isExpanded = inject(IK_TREE_ITEM_EXPANDED, ref(true))
@@ -70,65 +79,57 @@ const depth = inject(IK_TREE_LIST_DEPTH, 0)
70
79
  </script>
71
80
 
72
81
  <style lang="postcss" scoped>
73
- /* COLOR VARIANTS */
74
- .tree-item-label {
75
- --color: var(--color-neutral-txt-primary);
76
- --background-color: var(--color-neutral-background-primary);
77
-
78
- &:is(.exact-active, .active) {
79
- --color: var(--color-neutral-txt-primary);
80
- --background-color: var(--color-normal-background-selected);
81
- }
82
-
83
- &:hover {
84
- --color: var(--color-neutral-txt-primary);
85
- --background-color: var(--color-normal-background-hover);
86
- }
87
-
88
- &:active {
89
- --color: var(--color-neutral-txt-primary);
90
- --background-color: var(--color-normal-background-active);
91
- }
92
- }
93
-
94
- /* IMPLEMENTATION */
95
- .tree-item-label {
82
+ .ui-tree-item-label {
96
83
  display: flex;
97
84
  align-items: center;
98
- color: var(--color);
99
- background-color: var(--background-color);
85
+ color: var(--color-neutral-txt-primary);
86
+ background-color: var(--color-neutral-background-primary);
100
87
  border-radius: 0.8rem;
101
88
  gap: 0.4rem;
102
89
  padding: 0 0.8rem;
103
90
  margin-bottom: 0.2rem;
104
- }
105
91
 
106
- .link {
107
- display: flex;
108
- align-items: center;
109
- flex: 1;
110
- min-width: 0;
111
- padding: 0.8rem 0;
112
- text-decoration: none;
113
- color: inherit;
114
- gap: 1.2rem;
92
+ .link {
93
+ display: flex;
94
+ align-items: center;
95
+ flex: 1;
96
+ min-width: 0;
97
+ padding: 0.8rem 0;
98
+ text-decoration: none;
99
+ color: inherit;
100
+ gap: 1.2rem;
101
+
102
+ &:hover {
103
+ color: var(--color-neutral-txt-primary);
104
+ }
105
+ }
115
106
 
116
- &:hover {
117
- color: var(--color-neutral-txt-primary);
107
+ .text {
108
+ padding-inline-end: 0.4rem;
118
109
  }
119
- }
120
110
 
121
- .text {
122
- padding-inline-end: 0.4rem;
123
- }
111
+ .icon {
112
+ font-size: 1.6rem;
113
+ }
124
114
 
125
- .icon {
126
- font-size: 1.6rem;
127
- }
115
+ .h-line {
116
+ width: 2rem;
117
+ border-bottom: 0.1rem solid var(--color-info-txt-base);
118
+ margin-left: -0.4rem;
119
+ }
120
+
121
+ /* INTERACTION VARIANTS */
128
122
 
129
- .h-line {
130
- width: 2rem;
131
- border-bottom: 0.1rem solid var(--color-normal-txt-base);
132
- margin-left: -0.4rem;
123
+ &:is(.exact-active, .active) {
124
+ background-color: var(--color-info-background-selected);
125
+ }
126
+
127
+ &:hover {
128
+ background-color: var(--color-info-background-hover);
129
+ }
130
+
131
+ &:active {
132
+ background-color: var(--color-info-background-active);
133
+ }
133
134
  }
134
135
  </style>
@@ -0,0 +1,76 @@
1
+ <!-- v3 -->
2
+ <template>
3
+ <RouterLink v-if="route && !disabled" :to="route" class="ui-user-link is-link typo p3-regular-underline">
4
+ <UiUserLogo size="extra-small" class="logo" />
5
+ {{ username }}
6
+ </RouterLink>
7
+ <span v-else :class="toVariants({ disabled })" class="ui-user-link typo p3-regular-underline">
8
+ <UiUserLogo size="extra-small" class="logo" />
9
+ {{ username }}
10
+ </span>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import UiUserLogo from '@core/components/ui/user-logo/UiUserLogo.vue'
15
+ import { toVariants } from '@core/utils/to-variants.util'
16
+ import { type RouteLocationRaw } from 'vue-router'
17
+
18
+ defineProps<{
19
+ username: string
20
+ route?: RouteLocationRaw
21
+ disabled?: boolean
22
+ }>()
23
+ </script>
24
+
25
+ <style lang="postcss" scoped>
26
+ .ui-user-link {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ color: var(--color-info-txt-base);
30
+ gap: 0.8rem;
31
+
32
+ &.disabled {
33
+ cursor: not-allowed;
34
+ }
35
+
36
+ /* INTERACTION VARIANTS */
37
+
38
+ &.is-link {
39
+ &:hover {
40
+ color: var(--color-info-txt-hover);
41
+
42
+ .logo {
43
+ border-color: var(--color-info-txt-hover);
44
+ }
45
+ }
46
+
47
+ &:active {
48
+ color: var(--color-info-txt-active);
49
+
50
+ .logo {
51
+ border-color: var(--color-info-txt-active);
52
+ }
53
+ }
54
+
55
+ &:focus-visible {
56
+ outline: none;
57
+
58
+ &::before {
59
+ content: '';
60
+ position: absolute;
61
+ inset: -0.6rem;
62
+ border: 0.2rem solid var(--color-info-txt-base);
63
+ border-radius: 0.4rem;
64
+ }
65
+ }
66
+ }
67
+
68
+ &.disabled {
69
+ color: var(--color-neutral-txt-secondary);
70
+
71
+ .logo {
72
+ border-color: var(--color-neutral-txt-secondary);
73
+ }
74
+ }
75
+ }
76
+ </style>
@@ -0,0 +1,50 @@
1
+ <!-- v2 -->
2
+ <template>
3
+ <span :class="toVariants({ size })" class="ui-user-logo" />
4
+ </template>
5
+
6
+ <script lang="ts" setup>
7
+ import { toVariants } from '@core/utils/to-variants.util'
8
+
9
+ export type UserLogoSize = 'extra-small' | 'small' | 'medium'
10
+
11
+ defineProps<{
12
+ size: UserLogoSize
13
+ }>()
14
+ </script>
15
+
16
+ <style lang="postcss" scoped>
17
+ .ui-user-logo {
18
+ display: block;
19
+ background: var(--color-neutral-txt-primary) url('../../../assets/user.png') no-repeat;
20
+ border-style: solid;
21
+ border-color: var(--color-info-item-base);
22
+ border-radius: 20rem;
23
+
24
+ /* SIZE VARIANTS */
25
+
26
+ &.size--extra-small {
27
+ width: 1.6rem;
28
+ height: 1.6rem;
29
+ border-width: 0.1rem;
30
+ background-position: -0.85rem -0.55rem;
31
+ background-size: 2.88rem;
32
+ }
33
+
34
+ &.size--small {
35
+ width: 2.4rem;
36
+ height: 2.4rem;
37
+ border-width: 0.1rem;
38
+ background-position: -1.3rem -0.8rem;
39
+ background-size: 4.32rem;
40
+ }
41
+
42
+ &.size--medium {
43
+ width: 4rem;
44
+ height: 4rem;
45
+ border-width: 0.2rem;
46
+ background-position: -2.2rem -1.4rem;
47
+ background-size: 7.2rem;
48
+ }
49
+ }
50
+ </style>
@@ -0,0 +1,136 @@
1
+ # useRouteQuery Composable
2
+
3
+ `useRouteQuery` is a composable that synchronizes a route query parameter with a reactive reference.
4
+
5
+ It can optionally be configured with a default query value and transformers
6
+ to convert the query string to a custom data type.
7
+
8
+ ## Usage
9
+
10
+ ### Basic Usage
11
+
12
+ By default, the data type is `string`.
13
+
14
+ ```typescript
15
+ const search = useRouteQuery('search')
16
+ ```
17
+
18
+ In this example, if URL contains `?search=hello`, then `search.value` will be `'hello'`.
19
+
20
+ If you do `search.value = 'world'`, then the URL will be replaced with `?search=world`.
21
+
22
+ Resetting `search.value` to `''` will remove the query parameter from the URL.
23
+
24
+ ### Default query value
25
+
26
+ You can provide a default value for the query parameter if it is not present in the URL.
27
+
28
+ ```typescript
29
+ const search = useRouteQuery('search', { defaultQuery: 'hello' })
30
+ ```
31
+
32
+ In this case, if the URL doesn’t contain the `search` query parameter, then `search.value` will be `'hello'`.
33
+
34
+ Setting `search.value = 'world'` will update the URL to `?search=world`.
35
+
36
+ Setting `search.value = ''` will update the URL to `?search=` (because it's not the default value).
37
+
38
+ Resetting `search.value` to `'hello'` will remove the query parameter from the URL.
39
+
40
+ ### Advanced usage with custom data type (aka. Transformers)
41
+
42
+ As said before, the default data type is `string`.
43
+
44
+ That means that if the query is `?count=42`, then `query.value` will be the string `'42'`.
45
+
46
+ You can use `toData`/`toQuery` transformers to convert the query string to a custom data type and back.
47
+
48
+ ```typescript
49
+ const count = useRouteQuery('count', {
50
+ toData: query => parseInt(query, 10),
51
+ toQuery: data => data.toString(),
52
+ defaultQuery: '0', // If you don't provide a `defaultQuery`, then make sure that `toData` can handle the case of an empty string
53
+ })
54
+ ```
55
+
56
+ Now if the URL contains `?count=42`, then `count.value` will be the number `42`.
57
+
58
+ Setting `count.value = 0` will remove the query parameter from the URL.
59
+
60
+ ### Working with objects, arrays, Set, Map, and boolean
61
+
62
+ The `useRouteQuery` composable can handle any data type,
63
+ but has some specific actions for `object`, `array`, `Set` and `Map` and `boolean`.
64
+
65
+ It will then return an extended `computed` containing some actions to manipulate the data.
66
+
67
+ #### Array
68
+
69
+ Provides `add(value)`, `set(index, value)` and `delete(index)` methods.
70
+
71
+ ```typescript
72
+ const array = useRouteQuery('array', {
73
+ toData: query => (query ? query.split(',') : []),
74
+ toQuery: data => data.join(','),
75
+ })
76
+
77
+ array.add('hello') // ?array=hello
78
+ array.add('world') // ?array=hello,world
79
+ array.add('hello') // ?array=hello,world,hello
80
+ array.set(1, 'hi') // ?array=hello,hi,hello
81
+ array.delete(2) // ?array=hello,hi
82
+ ```
83
+
84
+ #### Set
85
+
86
+ Provides `add(value)`, `delete(value)`, and `toggle(value, state?: boolean)` methods.
87
+
88
+ ```typescript
89
+ const set = useRouteQuery('set', {
90
+ toData: query => new Set(query ? query.split(',') : []),
91
+ toQuery: data => Array.from(data).join(','),
92
+ })
93
+
94
+ set.toggle('hello') // ?set=hello
95
+ set.toggle('hello') // ?
96
+ set.toggle('hello', true) // ?set=hello
97
+ set.toggle('hello', true) // ?set=hello
98
+ set.toggle('hello', false) // ?
99
+
100
+ set.add('hello') // ?set=hello
101
+ set.add('world') // ?set=hello,world
102
+ set.add('world') // ?set=hello,world
103
+ set.delete('hello') // ?set=world
104
+ ```
105
+
106
+ #### Map or Object
107
+
108
+ Provides `set(key, value)` and `delete(key)` methods.
109
+
110
+ ```typescript
111
+ const map = useRouteQuery('map', {
112
+ toData: query => convertQueryToMap(query),
113
+ toQuery: data => convertMapToQuery(data),
114
+ })
115
+
116
+ map.set('hello', 'world') // ?map=hello:world
117
+ map.set('hi', 'earth') // ?map=hello:world,hi:earth
118
+ map.delete('hello') // ?map=hi:earth
119
+ ```
120
+
121
+ #### Boolean
122
+
123
+ Provides `toggle(value?: boolean)` method.
124
+
125
+ ```typescript
126
+ const bool = useRouteQuery('bool', {
127
+ toData: query => query === '1',
128
+ toQuery: data => (data ? '1' : '0'),
129
+ })
130
+
131
+ bool.toggle() // ?bool=1
132
+ bool.toggle() // ?
133
+ bool.toggle(true) // ?bool=1
134
+ bool.toggle(true) // ?bool=1
135
+ bool.toggle(false) // ?
136
+ ```