@xen-orchestra/web-core 0.0.1

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 (91) hide show
  1. package/lib/assets/css/_colors.pcss +125 -0
  2. package/lib/assets/css/_context.pcss +99 -0
  3. package/lib/assets/css/_fonts.pcss +6 -0
  4. package/lib/assets/css/_reset.pcss +42 -0
  5. package/lib/assets/css/_shadows.pcss +36 -0
  6. package/lib/assets/css/_typography.pcss +6 -0
  7. package/lib/assets/css/base.pcss +91 -0
  8. package/lib/assets/css/typography/_legacy.pcss +123 -0
  9. package/lib/assets/css/typography/_letter-spacing.pcss +27 -0
  10. package/lib/assets/css/typography/_line-height.pcss +19 -0
  11. package/lib/assets/css/typography/_size.pcss +95 -0
  12. package/lib/assets/css/typography/_style.pcss +34 -0
  13. package/lib/assets/css/typography/_weight.pcss +57 -0
  14. package/lib/assets/user.png +0 -0
  15. package/lib/components/PowerStateIcon.vue +46 -0
  16. package/lib/components/StatusPill.vue +32 -0
  17. package/lib/components/UiCounter.vue +89 -0
  18. package/lib/components/UiSpinner.vue +48 -0
  19. package/lib/components/UiTag.vue +97 -0
  20. package/lib/components/button/ButtonGroup.vue +33 -0
  21. package/lib/components/button/ButtonIcon.vue +199 -0
  22. package/lib/components/button/UiButton.vue +207 -0
  23. package/lib/components/chip/ChipIcon.vue +29 -0
  24. package/lib/components/chip/ChipRemoveIcon.vue +13 -0
  25. package/lib/components/chip/UiChip.vue +138 -0
  26. package/lib/components/dropdown/DropdownItem.vue +192 -0
  27. package/lib/components/dropdown/DropdownList.vue +31 -0
  28. package/lib/components/dropdown/DropdownTitle.vue +65 -0
  29. package/lib/components/icon/ComplexIcon.vue +51 -0
  30. package/lib/components/icon/ObjectIcon.vue +243 -0
  31. package/lib/components/icon/UiIcon.vue +47 -0
  32. package/lib/components/icon/VmIcon.vue +30 -0
  33. package/lib/components/layout/LayoutSidebar.vue +100 -0
  34. package/lib/components/menu/MenuItem.vue +82 -0
  35. package/lib/components/menu/MenuList.vue +104 -0
  36. package/lib/components/menu/MenuSeparator.vue +27 -0
  37. package/lib/components/menu/MenuTrigger.vue +52 -0
  38. package/lib/components/tab/TabItem.vue +100 -0
  39. package/lib/components/tab/TabList.vue +32 -0
  40. package/lib/components/tooltip/TooltipItem.vue +80 -0
  41. package/lib/components/tooltip/TooltipList.vue +15 -0
  42. package/lib/components/tree/TreeItem.vue +34 -0
  43. package/lib/components/tree/TreeItemError.vue +13 -0
  44. package/lib/components/tree/TreeItemLabel.vue +128 -0
  45. package/lib/components/tree/TreeLine.vue +51 -0
  46. package/lib/components/tree/TreeList.vue +14 -0
  47. package/lib/components/tree/TreeLoadingItem.vue +64 -0
  48. package/lib/components/user/UserLink.vue +72 -0
  49. package/lib/components/user/UserLogo.vue +57 -0
  50. package/lib/composables/context.composable.ts +34 -0
  51. package/lib/composables/tree/branch-definition.ts +17 -0
  52. package/lib/composables/tree/branch.ts +143 -0
  53. package/lib/composables/tree/build-nodes.ts +20 -0
  54. package/lib/composables/tree/define-branch.ts +23 -0
  55. package/lib/composables/tree/define-leaf.ts +16 -0
  56. package/lib/composables/tree/define-tree.ts +65 -0
  57. package/lib/composables/tree/leaf-definition.ts +8 -0
  58. package/lib/composables/tree/leaf.ts +34 -0
  59. package/lib/composables/tree/tree-node-base.ts +103 -0
  60. package/lib/composables/tree/tree-node-definition-base.ts +12 -0
  61. package/lib/composables/tree/types.ts +92 -0
  62. package/lib/composables/tree-filter.composable.ts +12 -0
  63. package/lib/composables/tree.composable.ts +85 -0
  64. package/lib/context.ts +10 -0
  65. package/lib/directives/tooltip.directive.md +117 -0
  66. package/lib/directives/tooltip.directive.ts +52 -0
  67. package/lib/i18n.ts +158 -0
  68. package/lib/layouts/CoreLayout.vue +182 -0
  69. package/lib/locales/de.json +6 -0
  70. package/lib/locales/en.json +15 -0
  71. package/lib/locales/fr.json +15 -0
  72. package/lib/stores/panel.store.ts +12 -0
  73. package/lib/stores/sidebar.store.ts +63 -0
  74. package/lib/stores/tooltip.store.ts +74 -0
  75. package/lib/stores/ui.store.ts +34 -0
  76. package/lib/types/button.type.ts +3 -0
  77. package/lib/types/color.type.ts +5 -0
  78. package/lib/types/object-icon.type.ts +43 -0
  79. package/lib/types/power-state.type.ts +1 -0
  80. package/lib/types/size.type.ts +3 -0
  81. package/lib/types/subscribable-store.type.ts +21 -0
  82. package/lib/types/utility.type.ts +1 -0
  83. package/lib/utils/create-subscribable-store-context.util.ts +66 -0
  84. package/lib/utils/has-ellipsis.util.ts +11 -0
  85. package/lib/utils/if-else.utils.ts +27 -0
  86. package/lib/utils/injection-keys.util.ts +17 -0
  87. package/lib/utils/sort-by-name-label.util.ts +6 -0
  88. package/lib/utils/to-array.utils.ts +9 -0
  89. package/lib/utils/unique-id.util.ts +8 -0
  90. package/package.json +45 -0
  91. package/tsconfig.json +12 -0
@@ -0,0 +1,57 @@
1
+ .typo {
2
+ /* BLACK (900) */
3
+
4
+ &.h1-black,
5
+ &.h2-black,
6
+ &.h3-black,
7
+ &.h4-black,
8
+ &.h5-black,
9
+ &.h6-black,
10
+ &.h7-black {
11
+ font-weight: 900;
12
+ }
13
+
14
+ /* SEMI-BOLD (600) */
15
+
16
+ &.h3-semi-bold,
17
+ &.h4-semi-bold,
18
+ &.h5-semi-bold,
19
+ &.h6-semi-bold,
20
+ &.h7-semi-bold,
21
+ &.p4-semi-bold,
22
+ &.c1-semi-bold,
23
+ &.c2-semi-bold,
24
+ &.c3-semi-bold,
25
+ &.c4-semi-bold,
26
+ &.c5-semi-bold {
27
+ font-weight: 600;
28
+ }
29
+
30
+ /* MEDIUM (500) */
31
+
32
+ &.h3-medium,
33
+ &.h4-medium,
34
+ &.h5-medium,
35
+ &.h6-medium,
36
+ &.h7-medium,
37
+ &.p1-medium,
38
+ &.p2-medium,
39
+ &.p3-medium,
40
+ &.p4-medium {
41
+ font-weight: 500;
42
+ }
43
+
44
+ /* REGULAR (400) */
45
+
46
+ &.p1-regular,
47
+ &.p2-regular,
48
+ &.p3-regular,
49
+ &.p4-regular,
50
+ &.c1-regular,
51
+ &.c2-regular,
52
+ &.c3-regular,
53
+ &.c4-regular,
54
+ &.c5-regular {
55
+ font-weight: 400;
56
+ }
57
+ }
Binary file
@@ -0,0 +1,46 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <UiIcon :class="className" :icon="icon" class="power-state-icon" />
4
+ </template>
5
+
6
+ <script lang="ts" setup>
7
+ import UiIcon from '@core/components/icon/UiIcon.vue'
8
+ import type { POWER_STATE } from '@core/types/power-state.type'
9
+ import { faMoon, faPause, faPlay, faStop } from '@fortawesome/free-solid-svg-icons'
10
+ import { computed } from 'vue'
11
+
12
+ const props = defineProps<{
13
+ state: POWER_STATE
14
+ }>()
15
+
16
+ const icons = {
17
+ running: faPlay,
18
+ paused: faPause,
19
+ suspended: faMoon,
20
+ halted: faStop,
21
+ }
22
+
23
+ const icon = computed(() => icons[props.state])
24
+
25
+ const className = computed(() => `state-${props.state}`)
26
+ </script>
27
+
28
+ <style lang="postcss" scoped>
29
+ .power-state-icon {
30
+ &.state-suspended {
31
+ color: var(--color-purple-d60);
32
+ }
33
+
34
+ &.state-running {
35
+ color: var(--color-green-base);
36
+ }
37
+
38
+ &.state-paused {
39
+ color: var(--color-purple-l40);
40
+ }
41
+
42
+ &.state-halted {
43
+ color: var(--color-red-base);
44
+ }
45
+ }
46
+ </style>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <UiIcon :color :icon class="status-pill" />
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import UiIcon from '@core/components/icon/UiIcon.vue'
7
+ import { computed } from 'vue'
8
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
9
+ import { faCheckCircle, faCircleMinus, faCircleXmark } from '@fortawesome/free-solid-svg-icons'
10
+ import type { Color } from '@core/types/color.type'
11
+
12
+ type Props = {
13
+ state: 'success' | 'partial' | 'failure'
14
+ }
15
+
16
+ const props = defineProps<Props>()
17
+
18
+ const states: Record<Props['state'], { icon: IconDefinition; color: Color }> = {
19
+ success: { icon: faCheckCircle, color: 'success' },
20
+ partial: { icon: faCircleMinus, color: 'warning' },
21
+ failure: { icon: faCircleXmark, color: 'error' },
22
+ }
23
+
24
+ const icon = computed(() => states[props.state].icon)
25
+ const color = computed(() => states[props.state].color)
26
+ </script>
27
+
28
+ <style lang="postcss" scoped>
29
+ .status-pill {
30
+ font-size: 1rem;
31
+ }
32
+ </style>
@@ -0,0 +1,89 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <span :class="classNames" class="ui-counter typo">
4
+ <span class="inner">{{ value }}</span>
5
+ </span>
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import type { CounterColor } from '@core/types/color.type'
10
+ import type { CounterSize } from '@core/types/size.type'
11
+ import { computed } from 'vue'
12
+
13
+ const props = withDefaults(
14
+ defineProps<{
15
+ value: number | string
16
+ color?: CounterColor
17
+ size?: CounterSize
18
+ }>(),
19
+ { size: 'small' }
20
+ )
21
+
22
+ const fontClasses = {
23
+ small: 'p4-semi-bold',
24
+ medium: 'p1-medium',
25
+ }
26
+
27
+ const classNames = computed(() => {
28
+ return [props.color, props.size, fontClasses[props.size]]
29
+ })
30
+ </script>
31
+
32
+ <style lang="postcss" scoped>
33
+ /* COLOR VARIANTS */
34
+ .ui-counter {
35
+ --background-color: var(--color-grey-300);
36
+
37
+ &.info {
38
+ --background-color: var(--color-purple-base);
39
+ }
40
+
41
+ &.success {
42
+ --background-color: var(--color-green-base);
43
+ }
44
+
45
+ &.warning {
46
+ --background-color: var(--color-orange-base);
47
+ }
48
+
49
+ &:is(.error, .danger) {
50
+ --background-color: var(--color-red-base);
51
+ }
52
+
53
+ &.black {
54
+ --background-color: var(--color-grey-100);
55
+ }
56
+ }
57
+
58
+ /* SIZE VARIANTS */
59
+ .ui-counter {
60
+ &.small {
61
+ --height: 1.5rem;
62
+ --padding: 0 0.4rem;
63
+ }
64
+
65
+ &.medium {
66
+ --height: 2.4rem;
67
+ --padding: 0 0.6rem;
68
+ }
69
+ }
70
+
71
+ /* IMPLEMENTATION */
72
+ .ui-counter {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ vertical-align: middle;
77
+ text-transform: lowercase;
78
+ color: var(--color-grey-600);
79
+ height: var(--height);
80
+ min-width: var(--height);
81
+ padding: var(--padding);
82
+ background-color: var(--background-color);
83
+ border-radius: calc(var(--height) / 2);
84
+
85
+ .inner {
86
+ line-height: 0;
87
+ }
88
+ }
89
+ </style>
@@ -0,0 +1,48 @@
1
+ <!-- Adapted from https://www.benmvp.com/blog/how-to-create-circle-svg-gradient-loading-spinner/ -->
2
+
3
+ <!-- v1.0 -->
4
+ <template>
5
+ <svg class="ui-spinner" fill="none" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
6
+ <defs>
7
+ <linearGradient :id="secondHalfId">
8
+ <stop offset="0%" stop-color="currentColor" stop-opacity="0" />
9
+ <stop offset="100%" stop-color="currentColor" stop-opacity="0.5" />
10
+ </linearGradient>
11
+ <linearGradient :id="firstHalfId">
12
+ <stop offset="0%" stop-color="currentColor" stop-opacity="1" />
13
+ <stop offset="100%" stop-color="currentColor" stop-opacity="0.5" />
14
+ </linearGradient>
15
+ </defs>
16
+
17
+ <g stroke-width="40">
18
+ <path :stroke="`url(#${secondHalfId})`" d="M 30 200 A 170 170 180 0 1 370 200" />
19
+ <path :stroke="`url(#${firstHalfId})`" d="M 370 200 A 170 170 0 0 1 30 200" />
20
+ <path d="M 30 200 A 170 170 180 0 1 30 200" stroke="currentColor" stroke-linecap="round" />
21
+ </g>
22
+ </svg>
23
+ </template>
24
+
25
+ <script lang="ts" setup>
26
+ import { uniqueId } from '@core/utils/unique-id.util'
27
+
28
+ const firstHalfId = uniqueId('spinner-first-half-')
29
+ const secondHalfId = uniqueId('spinner-second-half-')
30
+ </script>
31
+
32
+ <style lang="postcss" scoped>
33
+ .ui-spinner {
34
+ width: 1.2em;
35
+ height: 1.2em;
36
+ animation: rotate 1s linear infinite;
37
+ }
38
+
39
+ @keyframes rotate {
40
+ from {
41
+ transform: rotate(0deg);
42
+ }
43
+
44
+ to {
45
+ transform: rotate(360deg);
46
+ }
47
+ }
48
+ </style>
@@ -0,0 +1,97 @@
1
+ <!-- v1.2 -->
2
+ <template>
3
+ <span :class="[color, { light }]" class="ui-tag typo p3-regular">
4
+ <slot name="icon">
5
+ <UiIcon :icon fixed-width />
6
+ </slot>
7
+ <span class="content"><slot /></span>
8
+ </span>
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ import UiIcon from '@core/components/icon/UiIcon.vue'
13
+ import type { TagColor } from '@core/types/color.type'
14
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
15
+
16
+ withDefaults(
17
+ defineProps<{
18
+ color?: TagColor
19
+ light?: boolean
20
+ icon?: IconDefinition
21
+ }>(),
22
+ { color: 'grey' }
23
+ )
24
+ </script>
25
+
26
+ <style lang="postcss" scoped>
27
+ /* COLOR VARIANTS */
28
+ .ui-tag {
29
+ --color: var(--color-grey-600);
30
+
31
+ &.light {
32
+ --color: var(--color-grey-100);
33
+ }
34
+ }
35
+
36
+ /* BACKGROUND COLOR VARIANTS */
37
+ .ui-tag {
38
+ &.grey {
39
+ --background-color: var(--color-grey-300);
40
+
41
+ &.light {
42
+ --background-color: var(--background-color-secondary);
43
+ }
44
+ }
45
+
46
+ &.info {
47
+ --background-color: var(--color-purple-l20);
48
+
49
+ &.light {
50
+ --background-color: var(--background-color-purple-10);
51
+ }
52
+ }
53
+
54
+ &.success {
55
+ --background-color: var(--color-green-l20);
56
+
57
+ &.light {
58
+ --background-color: var(--background-color-green-10);
59
+ }
60
+ }
61
+
62
+ &.warning {
63
+ --background-color: var(--color-orange-l20);
64
+
65
+ &.light {
66
+ --background-color: var(--background-color-orange-10);
67
+ }
68
+ }
69
+
70
+ &:is(.error, .danger) {
71
+ --background-color: var(--color-red-l20);
72
+
73
+ &.light {
74
+ --background-color: var(--background-color-red-10);
75
+ }
76
+ }
77
+ }
78
+
79
+ /* IMPLEMENTATION */
80
+ .ui-tag {
81
+ display: inline-flex;
82
+ justify-content: center;
83
+ align-items: center;
84
+ gap: 0.8rem;
85
+ white-space: nowrap;
86
+ color: var(--color);
87
+ background-color: var(--background-color);
88
+ padding: 0.2rem 0.8rem;
89
+ border-radius: 0.4rem;
90
+ vertical-align: middle;
91
+
92
+ .content {
93
+ overflow: hidden;
94
+ text-overflow: ellipsis;
95
+ }
96
+ }
97
+ </style>
@@ -0,0 +1,33 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <div class="button-group">
4
+ <div class="line"><slot /></div>
5
+ <div v-if="slots.tertiary" class="line">
6
+ <slot name="tertiary" />
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ const slots = defineSlots<{
13
+ default: () => void
14
+ tertiary?: () => void
15
+ }>()
16
+ </script>
17
+
18
+ <style lang="postcss" scoped>
19
+ .button-group {
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+ justify-content: center;
24
+ gap: 1.6rem;
25
+
26
+ .line {
27
+ display: flex;
28
+ justify-content: center;
29
+ align-items: center;
30
+ gap: 2.4rem;
31
+ }
32
+ }
33
+ </style>
@@ -0,0 +1,199 @@
1
+ <!-- v1.0 -->
2
+ <template>
3
+ <button :class="[color, size, { disabled, active }]" :disabled class="button-icon" type="button">
4
+ <UiIcon :icon class="icon" />
5
+ <span v-if="dot" class="dot" />
6
+ </button>
7
+ </template>
8
+
9
+ <script lang="ts" setup>
10
+ import UiIcon from '@core/components/icon/UiIcon.vue'
11
+ import type { Color } from '@core/types/color.type'
12
+ import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
13
+
14
+ withDefaults(
15
+ defineProps<{
16
+ icon: IconDefinition
17
+ size?: 'small' | 'medium' | 'large'
18
+ color?: Color
19
+ disabled?: boolean
20
+ active?: boolean
21
+ dot?: boolean
22
+ }>(),
23
+ { color: 'info', size: 'medium' }
24
+ )
25
+ </script>
26
+
27
+ <style lang="postcss" scoped>
28
+ /* COLOR VARIANTS */
29
+ .button-icon {
30
+ &.info {
31
+ & {
32
+ --color: var(--color-purple-base);
33
+ --background-color: transparent;
34
+ --dot-color: var(--color-red-base);
35
+ }
36
+
37
+ &:is(.active, .selected) {
38
+ --color: var(--color-purple-base);
39
+ --background-color: var(--background-color-purple-10);
40
+ }
41
+
42
+ &:is(:hover, .hover, :focus-visible) {
43
+ --color: var(--color-purple-d20);
44
+ --background-color: var(--background-color-purple-20);
45
+ }
46
+
47
+ &:is(:active, .pressed) {
48
+ --color: var(--color-purple-d40);
49
+ --background-color: var(--background-color-purple-30);
50
+ }
51
+
52
+ &:is(:disabled, .disabled) {
53
+ --color: var(--color-grey-400);
54
+ --background-color: transparent;
55
+ }
56
+ }
57
+
58
+ &.success {
59
+ & {
60
+ --color: var(--color-green-base);
61
+ --background-color: transparent;
62
+ --dot-color: var(--color-red-base);
63
+ }
64
+
65
+ &:is(.active, .selected) {
66
+ --color: var(--color-green-base);
67
+ --background-color: var(--background-color-green-10);
68
+ }
69
+
70
+ &:is(:hover, .hover, :focus-visible) {
71
+ --color: var(--color-green-d20);
72
+ --background-color: var(--background-color-green-20);
73
+ }
74
+
75
+ &:is(:active, .pressed) {
76
+ --color: var(--color-green-d40);
77
+ --background-color: var(--background-color-green-30);
78
+ }
79
+
80
+ &:is(:disabled, .disabled) {
81
+ --color: var(--color-green-l60);
82
+ --background-color: transparent;
83
+ }
84
+ }
85
+
86
+ &.warning {
87
+ & {
88
+ --color: var(--color-orange-base);
89
+ --background-color: transparent;
90
+ --dot-color: var(--color-red-base);
91
+ }
92
+
93
+ &:is(.active, .selected) {
94
+ --color: var(--color-orange-base);
95
+ --background-color: var(--background-color-orange-10);
96
+ }
97
+
98
+ &:is(:hover, .hover, :focus-visible) {
99
+ --color: var(--color-orange-d20);
100
+ --background-color: var(--background-color-orange-20);
101
+ }
102
+
103
+ &:is(:active, .pressed) {
104
+ --color: var(--color-orange-d40);
105
+ --background-color: var(--background-color-orange-30);
106
+ }
107
+
108
+ &:is(:disabled, .disabled) {
109
+ --color: var(--color-orange-l60);
110
+ --background-color: transparent;
111
+ }
112
+ }
113
+
114
+ &:is(.danger, .error) {
115
+ & {
116
+ --color: var(--color-red-base);
117
+ --background-color: transparent;
118
+ --dot-color: var(--color-orange-base);
119
+ }
120
+
121
+ &:is(.active, .selected) {
122
+ --color: var(--color-red-base);
123
+ --background-color: var(--background-color-red-10);
124
+ }
125
+
126
+ &:is(:hover, .hover, :focus-visible) {
127
+ --color: var(--color-red-d20);
128
+ --background-color: var(--background-color-red-20);
129
+ }
130
+
131
+ &:is(:active, .pressed) {
132
+ --color: var(--color-red-d40);
133
+ --background-color: var(--background-color-red-30);
134
+ }
135
+
136
+ &:is(:disabled, .disabled) {
137
+ --color: var(--color-red-l60);
138
+ --background-color: transparent;
139
+ }
140
+ }
141
+ }
142
+
143
+ /* SIZE VARIANTS */
144
+ .button-icon {
145
+ &.small {
146
+ --size: 1.6rem;
147
+ --font-size: 1.2rem;
148
+ --dot-size: 0.4rem;
149
+ --dot-offset: 0.2rem;
150
+ }
151
+
152
+ &.medium {
153
+ --size: 2.4rem;
154
+ --font-size: 1.6rem;
155
+ --dot-size: 0.5rem;
156
+ --dot-offset: 0.4rem;
157
+ }
158
+
159
+ &.large {
160
+ --size: 4rem;
161
+ --font-size: 2.4rem;
162
+ --dot-size: 0.8rem;
163
+ --dot-offset: 0.8rem;
164
+ }
165
+ }
166
+
167
+ /* IMPLEMENTATION */
168
+ .button-icon {
169
+ display: inline-flex;
170
+ justify-content: center;
171
+ align-items: center;
172
+ border: none;
173
+ padding: 0;
174
+ border-radius: 0.2rem;
175
+ width: var(--size);
176
+ height: var(--size);
177
+ font-size: var(--font-size);
178
+ color: var(--color);
179
+ background-color: var(--background-color);
180
+ position: relative;
181
+ cursor: pointer;
182
+ outline: none;
183
+
184
+ &:is(:disabled, .disabled) {
185
+ cursor: not-allowed;
186
+ }
187
+
188
+ .dot {
189
+ position: absolute;
190
+ display: block;
191
+ width: var(--dot-size);
192
+ height: var(--dot-size);
193
+ border-radius: 50%;
194
+ background-color: var(--dot-color);
195
+ top: var(--dot-offset);
196
+ right: var(--dot-offset);
197
+ }
198
+ }
199
+ </style>