vue-chat-kit 0.3.10 → 0.3.11

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 (79) hide show
  1. package/dist/vue-chat-kit.css +1 -1
  2. package/dist/vue-chat-kit.es.js +4426 -2819
  3. package/dist/vue-chat-kit.umd.js +1 -1
  4. package/package.json +1 -1
  5. package/src/components/AvatarCrop.vue +16 -127
  6. package/src/components/ChatPanel.vue +491 -2675
  7. package/src/components/EmojiPicker.vue +2 -73
  8. package/src/components/chat/ChatWindow.vue +177 -0
  9. package/src/components/chat/ContentList.vue +300 -0
  10. package/src/components/chat/ContextMenu.vue +32 -0
  11. package/src/components/chat/GroupSidebar.vue +284 -0
  12. package/src/components/chat/MainArea.vue +87 -0
  13. package/src/components/chat/Sidebar.vue +52 -0
  14. package/src/components/chat/dialogs/AddFriendDialog.vue +62 -0
  15. package/src/components/chat/dialogs/CreateGroupDialog.vue +86 -0
  16. package/src/components/chat/dialogs/GroupDetailDialog.vue +132 -0
  17. package/src/components/ui/Button.vue +190 -0
  18. package/src/components/ui/Dialog.vue +194 -0
  19. package/src/components/ui/Empty.vue +66 -0
  20. package/src/components/ui/Input.vue +166 -0
  21. package/src/components/ui/Message.vue +186 -0
  22. package/src/components/ui/MessageBox.vue +143 -0
  23. package/src/components/ui/MessageManager.vue +92 -0
  24. package/src/components/ui/Switch.vue +65 -0
  25. package/src/components/ui/Tag.vue +68 -0
  26. package/src/components/ui/icons/ArrowDown.vue +5 -0
  27. package/src/components/ui/icons/ArrowRight.vue +5 -0
  28. package/src/components/ui/icons/Bell.vue +6 -0
  29. package/src/components/ui/icons/Camera.vue +6 -0
  30. package/src/components/ui/icons/ChatDotRound.vue +5 -0
  31. package/src/components/ui/icons/Check.vue +5 -0
  32. package/src/components/ui/icons/CircleCheck.vue +6 -0
  33. package/src/components/ui/icons/Clock.vue +6 -0
  34. package/src/components/ui/icons/Close.vue +5 -0
  35. package/src/components/ui/icons/Delete.vue +8 -0
  36. package/src/components/ui/icons/Edit.vue +6 -0
  37. package/src/components/ui/icons/Folder.vue +5 -0
  38. package/src/components/ui/icons/Minus.vue +5 -0
  39. package/src/components/ui/icons/Monitor.vue +7 -0
  40. package/src/components/ui/icons/Moon.vue +5 -0
  41. package/src/components/ui/icons/Picture.vue +7 -0
  42. package/src/components/ui/icons/Plus.vue +5 -0
  43. package/src/components/ui/icons/Search.vue +6 -0
  44. package/src/components/ui/icons/Setting.vue +6 -0
  45. package/src/components/ui/icons/Sunny.vue +6 -0
  46. package/src/components/ui/icons/User.vue +6 -0
  47. package/src/components/ui/icons/UserFilled.vue +6 -0
  48. package/src/components/ui/icons/Warning.vue +5 -0
  49. package/src/components/ui/icons/index.js +24 -0
  50. package/src/components/ui/index.js +10 -0
  51. package/src/composables/useFriendChat.js +10 -14
  52. package/src/composables/useGroupChat.js +140 -48
  53. package/src/composables/useMessage.js +21 -0
  54. package/src/composables/useMessageBox.js +98 -0
  55. package/src/composables/useTheme.js +140 -0
  56. package/src/config/index.js +1 -0
  57. package/src/const/index.js +1 -0
  58. package/src/const/theme.js +19 -0
  59. package/src/core/api.js +13 -2
  60. package/src/index.js +5 -5
  61. package/src/styles/_base.scss +38 -0
  62. package/src/styles/_variables.scss +43 -0
  63. package/src/styles/components/_add-friend-dialog.scss +45 -0
  64. package/src/styles/components/_avatar-crop.scss +120 -0
  65. package/src/styles/components/_chat-panel.scss +546 -0
  66. package/src/styles/components/_chat-window.scss +239 -0
  67. package/src/styles/components/_content-list.scss +260 -0
  68. package/src/styles/components/_context-menu.scss +35 -0
  69. package/src/styles/components/_create-group-dialog.scss +78 -0
  70. package/src/styles/components/_dialogs.scss +226 -0
  71. package/src/styles/components/_emoji-picker.scss +74 -0
  72. package/src/styles/components/_group-detail-dialog.scss +110 -0
  73. package/src/styles/components/_group-sidebar.scss +278 -0
  74. package/src/styles/components/_main-area.scss +94 -0
  75. package/src/styles/components/_sidebar.scss +83 -0
  76. package/src/styles/index.scss +18 -0
  77. package/src/styles/themes/_dark.scss +68 -0
  78. package/src/styles/themes/_index.scss +7 -0
  79. package/src/styles/themes/_light.scss +69 -0
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <Dialog
3
+ :model-value="visible"
4
+ @update:model-value="$emit('update:visible', $event)"
5
+ :title="currentSelectGroup?.name"
6
+ width="500px"
7
+ >
8
+ <div class="group-detail-content">
9
+ <div class="group-info-section">
10
+ <div class="group-info-item">
11
+ <span class="info-label">群ID</span>
12
+ <span class="info-value">{{ currentSelectGroup?.groupId }}</span>
13
+ </div>
14
+ <div class="group-info-item">
15
+ <span class="info-label">群主</span>
16
+ <span class="info-value">{{ groupOwnerUsername || currentSelectGroup?.owner }}</span>
17
+ </div>
18
+ <div class="group-info-item">
19
+ <span class="info-label">成员数</span>
20
+ <span class="info-value">{{ groupMemberList.length }}</span>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="group-actions-section" v-if="isGroupOwner">
25
+ <div class="section-header">
26
+ <span class="section-title">群管理</span>
27
+ </div>
28
+ <div class="group-actions-list">
29
+ <Button type="primary" size="small" @click="$emit('edit-info')">
30
+ <template #icon><Edit /></template>编辑群信息
31
+ </Button>
32
+ <Button type="danger" size="small" @click="$emit('delete')">
33
+ <template #icon><Delete /></template>解散群聊
34
+ </Button>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="group-members-section">
39
+ <div class="section-header">
40
+ <span class="section-title">群成员</span>
41
+ <Button size="small" @click="$emit('invite')">
42
+ <template #icon><Plus /></template>邀请成员
43
+ </Button>
44
+ </div>
45
+ <div class="group-members-list">
46
+ <Empty v-if="groupMemberList.length === 0" description="暂无成员" />
47
+ <div
48
+ v-else
49
+ v-for="member in groupMemberList"
50
+ :key="member.id || member.username"
51
+ class="group-member-item"
52
+ >
53
+ <img
54
+ :src="member.avatar ? `${config.api.baseUrl}${member.avatar}` : `https://api.dicebear.com/7.x/avataaars/svg?seed=${member.username}`"
55
+ :alt="member.username"
56
+ class="group-member-avatar"
57
+ />
58
+ <div class="group-member-info">
59
+ <span class="group-member-name">{{ member.memberNick || member.username }}</span>
60
+ <span v-if="member.memberType === 0 || member.username === groupOwnerUsername || member.username === currentSelectGroup?.owner" class="group-member-badge">群主</span>
61
+ </div>
62
+ <div class="group-member-actions" v-if="isGroupOwner && member.username !== myUsername">
63
+ <Button size="small" link @click="$emit('edit-member-nick', member)">
64
+ 改昵称
65
+ </Button>
66
+ <Button size="small" link type="danger" @click="$emit('remove-member', member.username)">
67
+ 移除
68
+ </Button>
69
+ <Button size="small" link type="warning" @click="$emit('transfer-owner', member.username)">
70
+ 转让
71
+ </Button>
72
+ </div>
73
+ <div class="group-member-actions" v-else-if="member.username === myUsername">
74
+ <Button size="small" link @click="$emit('edit-member-nick', member)">
75
+ 改昵称
76
+ </Button>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <template #footer>
83
+ <Button @click="$emit('update:visible', false)">关闭</Button>
84
+ <Button type="danger" @click="$emit('quit')">退出群聊</Button>
85
+ </template>
86
+ </Dialog>
87
+ </template>
88
+
89
+ <script setup>
90
+ import { computed } from 'vue'
91
+ import { Edit, Delete, Plus } from '../../ui/icons/index.js'
92
+ import { Dialog, Empty, Button } from '../../ui/index.js'
93
+
94
+ const props = defineProps({
95
+ visible: Boolean,
96
+ currentSelectGroup: Object,
97
+ groupMemberList: Array,
98
+ myUsername: String,
99
+ config: Object,
100
+ groupOwnerUsername: String
101
+ })
102
+
103
+ // 在组件内部直接计算是否群主
104
+ const isGroupOwner = computed(() => {
105
+ console.log('[GroupDetailDialog] 检查群主身份:', {
106
+ myUsername: props.myUsername,
107
+ groupMemberCount: props.groupMemberList?.length
108
+ })
109
+
110
+ if (!props.groupMemberList || props.groupMemberList.length === 0) {
111
+ console.log('[GroupDetailDialog] 无成员列表为空')
112
+ return false
113
+ }
114
+
115
+ const currentUserInGroup = props.groupMemberList.find(
116
+ m => m.username === props.myUsername
117
+ )
118
+
119
+ console.log('[GroupDetailDialog] 当前用户在群成员:', currentUserInGroup)
120
+
121
+ const isOwner = currentUserInGroup?.memberType === 0
122
+
123
+ console.log('[GroupDetailDialog] 是否群主:', isOwner)
124
+ return isOwner
125
+ })
126
+
127
+ defineEmits(['update:visible', 'edit-info', 'delete', 'invite', 'edit-member-nick', 'remove-member', 'transfer-owner', 'quit'])
128
+ </script>
129
+
130
+ <style scoped lang="scss">
131
+ @use '../../../styles/components/group-detail-dialog';
132
+ </style>
@@ -0,0 +1,190 @@
1
+ <template>
2
+ <button
3
+ :class="[
4
+ 'vc-button',
5
+ `vc-button--${type}`,
6
+ `vc-button--${size}`,
7
+ {
8
+ 'vc-button--disabled': disabled || loading,
9
+ 'vc-button--loading': loading
10
+ }
11
+ ]"
12
+ :disabled="disabled || loading"
13
+ @click="handleClick"
14
+ >
15
+ <span v-if="loading" class="vc-button__loading-icon">
16
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
17
+ <path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 10.84 21.78 9.73 21.37 8.72C21.12 8.14 21.41 7.44 21.99 7.19C22.57 6.94 23.27 7.23 23.52 7.81C24.15 9.26 24.5 10.86 24.5 12.5C24.5 19.07 19.07 24.5 12.5 24.5C5.93 24.5 0.5 19.07 0.5 12.5C0.5 5.93 5.93 0.5 12.5 0.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
18
+ </svg>
19
+ </span>
20
+ <span v-if="$slots.icon" class="vc-button__icon">
21
+ <slot name="icon"></slot>
22
+ </span>
23
+ <span v-if="$slots.default" class="vc-button__text">
24
+ <slot></slot>
25
+ </span>
26
+ </button>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { computed } from 'vue'
31
+
32
+ const props = defineProps({
33
+ type: {
34
+ type: String,
35
+ default: 'default',
36
+ validator: (val) => ['default', 'primary', 'success', 'warning', 'danger', 'link'].includes(val)
37
+ },
38
+ size: {
39
+ type: String,
40
+ default: 'default',
41
+ validator: (val) => ['small', 'default', 'large'].includes(val)
42
+ },
43
+ disabled: {
44
+ type: Boolean,
45
+ default: false
46
+ },
47
+ loading: {
48
+ type: Boolean,
49
+ default: false
50
+ }
51
+ })
52
+
53
+ const emit = defineEmits(['click'])
54
+
55
+ const handleClick = (e) => {
56
+ if (!props.disabled && !props.loading) {
57
+ emit('click', e)
58
+ }
59
+ }
60
+ </script>
61
+
62
+ <style scoped lang="scss">
63
+ .vc-button {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ gap: var(--vck-space-xs);
68
+ padding: var(--vck-space-sm) var(--vck-space);
69
+ font-size: 14px;
70
+ font-weight: 500;
71
+ border: 1px solid var(--vck-border);
72
+ border-radius: var(--vck-radius);
73
+ background-color: var(--vck-bg);
74
+ color: var(--vck-text-primary);
75
+ cursor: pointer;
76
+ transition: all var(--vck-transition-fast);
77
+ line-height: 1;
78
+ user-select: none;
79
+
80
+ &:hover:not(.vc-button--disabled) {
81
+ border-color: var(--vck-primary);
82
+ color: var(--vck-primary);
83
+ }
84
+
85
+ &:active:not(.vc-button--disabled) {
86
+ opacity: 0.8;
87
+ }
88
+
89
+ // Types
90
+ &--primary {
91
+ background-color: var(--vck-primary);
92
+ border-color: var(--vck-primary);
93
+ color: var(--vck-text-white);
94
+
95
+ &:hover:not(.vc-button--disabled) {
96
+ background-color: var(--vck-primary-hover);
97
+ border-color: var(--vck-primary-hover);
98
+ color: var(--vck-text-white);
99
+ }
100
+ }
101
+
102
+ &--success {
103
+ background-color: var(--vck-success);
104
+ border-color: var(--vck-success);
105
+ color: var(--vck-text-white);
106
+ }
107
+
108
+ &--warning {
109
+ background-color: var(--vck-warning);
110
+ border-color: var(--vck-warning);
111
+ color: var(--vck-text-white);
112
+ }
113
+
114
+ &--danger {
115
+ background-color: var(--vck-danger);
116
+ border-color: var(--vck-danger);
117
+ color: var(--vck-text-white);
118
+ }
119
+
120
+ &--link {
121
+ background-color: transparent;
122
+ border-color: transparent;
123
+ padding: var(--vck-space-xs);
124
+ color: var(--vck-primary);
125
+
126
+ &:hover:not(.vc-button--disabled) {
127
+ color: var(--vck-primary-hover);
128
+ background-color: transparent;
129
+ }
130
+
131
+ &.vc-button--danger {
132
+ color: var(--vck-danger);
133
+
134
+ &:hover:not(.vc-button--disabled) {
135
+ color: var(--vck-danger-hover);
136
+ }
137
+ }
138
+
139
+ &.vc-button--warning {
140
+ color: var(--vck-warning);
141
+
142
+ &:hover:not(.vc-button--disabled) {
143
+ color: var(--vck-warning-hover);
144
+ }
145
+ }
146
+ }
147
+
148
+ // Sizes
149
+ &--small {
150
+ padding: var(--vck-space-xs) var(--vck-space-sm);
151
+ font-size: 12px;
152
+ }
153
+
154
+ &--large {
155
+ padding: var(--vck-space) var(--vck-space-md);
156
+ font-size: 16px;
157
+ }
158
+
159
+ // Disabled
160
+ &--disabled {
161
+ opacity: 0.5;
162
+ cursor: not-allowed;
163
+ }
164
+
165
+ // Loading
166
+ &--loading {
167
+ pointer-events: none;
168
+ }
169
+
170
+ &__loading-icon {
171
+ animation: spin 0.8s linear infinite;
172
+ display: inline-flex;
173
+ width: 14px;
174
+ height: 14px;
175
+
176
+ @keyframes spin {
177
+ from {
178
+ transform: rotate(0deg);
179
+ }
180
+ to {
181
+ transform: rotate(360deg);
182
+ }
183
+ }
184
+ }
185
+
186
+ &__text {
187
+ display: inline-flex;
188
+ }
189
+ }
190
+ </style>
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <transition name="vc-dialog-fade">
4
+ <div v-if="visible" class="vc-dialog__wrapper" @click.self="handleMaskClick">
5
+ <transition name="vc-dialog-zoom">
6
+ <div v-if="visible" class="vc-dialog" :style="dialogStyle">
7
+ <div v-if="showClose || $slots.header" class="vc-dialog__header">
8
+ <span v-if="$slots.header" class="vc-dialog__title">
9
+ <slot name="header">{{ title }}</slot>
10
+ </span>
11
+ <span v-else-if="title" class="vc-dialog__title">
12
+ {{ title }}
13
+ </span>
14
+ <button
15
+ v-if="showClose"
16
+ type="button"
17
+ class="vc-dialog__headerbtn"
18
+ @click="handleClose"
19
+ >
20
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="vc-dialog__close">
21
+ <path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
22
+ </svg>
23
+ </button>
24
+ </div>
25
+ <div class="vc-dialog__body">
26
+ <slot></slot>
27
+ </div>
28
+ <div v-if="$slots.footer" class="vc-dialog__footer">
29
+ <slot name="footer"></slot>
30
+ </div>
31
+ </div>
32
+ </transition>
33
+ </div>
34
+ </transition>
35
+ </Teleport>
36
+ </template>
37
+
38
+ <script setup>
39
+ import { computed } from 'vue'
40
+
41
+ const props = defineProps({
42
+ modelValue: {
43
+ type: Boolean,
44
+ default: false
45
+ },
46
+ title: {
47
+ type: String,
48
+ default: ''
49
+ },
50
+ width: {
51
+ type: [String, Number],
52
+ default: '50%'
53
+ },
54
+ top: {
55
+ type: [String, Number],
56
+ default: '15vh'
57
+ },
58
+ showClose: {
59
+ type: Boolean,
60
+ default: true
61
+ },
62
+ closeOnClickModal: {
63
+ type: Boolean,
64
+ default: true
65
+ }
66
+ })
67
+
68
+ const emit = defineEmits(['update:modelValue', 'close'])
69
+
70
+ const visible = computed({
71
+ get: () => props.modelValue,
72
+ set: (val) => emit('update:modelValue', val)
73
+ })
74
+
75
+ const dialogStyle = computed(() => ({
76
+ width: typeof props.width === 'number' ? `${props.width}px` : props.width,
77
+ marginTop: typeof props.top === 'number' ? `${props.top}px` : props.top
78
+ }))
79
+
80
+ const handleClose = () => {
81
+ visible.value = false
82
+ emit('close')
83
+ }
84
+
85
+ const handleMaskClick = () => {
86
+ if (props.closeOnClickModal) {
87
+ handleClose()
88
+ }
89
+ }
90
+ </script>
91
+
92
+ <style scoped lang="scss">
93
+ .vc-dialog__wrapper {
94
+ position: fixed;
95
+ top: 0;
96
+ right: 0;
97
+ bottom: 0;
98
+ left: 0;
99
+ overflow: auto;
100
+ margin: 0;
101
+ z-index: 2000;
102
+ background-color: rgba(0, 0, 0, 0.5);
103
+ display: flex;
104
+ align-items: flex-start;
105
+ justify-content: center;
106
+ }
107
+
108
+ .vc-dialog {
109
+ position: relative;
110
+ margin: 0 auto;
111
+ background-color: var(--vck-bg);
112
+ border-radius: var(--vck-radius-md);
113
+ box-shadow: var(--vck-shadow);
114
+ overflow: hidden;
115
+ backface-visibility: hidden;
116
+
117
+ &__header {
118
+ padding: var(--vck-space-md) var(--vck-space-lg);
119
+ border-bottom: 1px solid var(--vck-border);
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: space-between;
123
+ }
124
+
125
+ &__title {
126
+ font-size: 18px;
127
+ font-weight: 600;
128
+ color: var(--vck-text-primary);
129
+ line-height: 1;
130
+ }
131
+
132
+ &__headerbtn {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ width: 24px;
137
+ height: 24px;
138
+ padding: 0;
139
+ margin: 0;
140
+ border: none;
141
+ background: none;
142
+ cursor: pointer;
143
+ color: var(--vck-text-muted);
144
+ transition: color var(--vck-transition-fast);
145
+
146
+ &:hover {
147
+ color: var(--vck-text-primary);
148
+ }
149
+ }
150
+
151
+ &__close {
152
+ width: 16px;
153
+ height: 16px;
154
+ }
155
+
156
+ &__body {
157
+ padding: var(--vck-space-lg);
158
+ color: var(--vck-text-primary);
159
+ font-size: 14px;
160
+ line-height: 1.6;
161
+ }
162
+
163
+ &__footer {
164
+ padding: var(--vck-space-sm) var(--vck-space-lg) var(--vck-space-lg);
165
+ border-top: 1px solid var(--vck-border);
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: flex-end;
169
+ gap: var(--vck-space-sm);
170
+ }
171
+ }
172
+
173
+ // Transitions
174
+ .vc-dialog-fade-enter-active,
175
+ .vc-dialog-fade-leave-active {
176
+ transition: opacity var(--vck-transition-fast);
177
+ }
178
+
179
+ .vc-dialog-fade-enter-from,
180
+ .vc-dialog-fade-leave-to {
181
+ opacity: 0;
182
+ }
183
+
184
+ .vc-dialog-zoom-enter-active,
185
+ .vc-dialog-zoom-leave-active {
186
+ transition: transform var(--vck-transition-fast), opacity var(--vck-transition-fast);
187
+ }
188
+
189
+ .vc-dialog-zoom-enter-from,
190
+ .vc-dialog-zoom-leave-to {
191
+ opacity: 0;
192
+ transform: scale(0.95);
193
+ }
194
+ </style>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <div class="vc-empty">
3
+ <div v-if="$slots.image" class="vc-empty__image">
4
+ <slot name="image"></slot>
5
+ </div>
6
+ <div v-else class="vc-empty__image">
7
+ <svg viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
8
+ <rect x="10" y="10" width="100" height="80" rx="8" fill="var(--vck-bg-panel)" stroke="var(--vck-border)"/>
9
+ <path d="M30 40H90M30 55H70" stroke="var(--vck-text-muted)" stroke-width="2" stroke-linecap="round"/>
10
+ </svg>
11
+ </div>
12
+ <div v-if="$slots.default" class="vc-empty__description">
13
+ <slot>{{ description }}</slot>
14
+ </div>
15
+ <div v-else-if="description" class="vc-empty__description">
16
+ {{ description }}
17
+ </div>
18
+ <div v-if="$slots.bottom" class="vc-empty__bottom">
19
+ <slot name="bottom"></slot>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup>
25
+ const props = defineProps({
26
+ description: {
27
+ type: String,
28
+ default: '暂无数据'
29
+ }
30
+ })
31
+ </script>
32
+
33
+ <style scoped lang="scss">
34
+ .vc-empty {
35
+ display: flex;
36
+ flex-direction: column;
37
+ align-items: center;
38
+ justify-content: center;
39
+ padding: var(--vck-space-lg);
40
+ text-align: center;
41
+
42
+ &__image {
43
+ width: 120px;
44
+ height: 80px;
45
+ margin-bottom: var(--vck-space-md);
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+
50
+ svg {
51
+ width: 100%;
52
+ height: 100%;
53
+ }
54
+ }
55
+
56
+ &__description {
57
+ font-size: 14px;
58
+ color: var(--vck-text-muted);
59
+ line-height: 1.5;
60
+ }
61
+
62
+ &__bottom {
63
+ margin-top: var(--vck-space-md);
64
+ }
65
+ }
66
+ </style>