@datametria/vue-components 2.0.1 → 2.1.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.
@@ -0,0 +1,230 @@
1
+ <template>
2
+ <aside
3
+ :class="sidebarClasses"
4
+ role="complementary"
5
+ :aria-label="ariaLabel"
6
+ :aria-expanded="isOpen"
7
+ >
8
+ <div class="dm-sidebar__header" v-if="$slots.header">
9
+ <slot name="header"></slot>
10
+ <button
11
+ v-if="collapsible"
12
+ class="dm-sidebar__toggle"
13
+ @click="toggle"
14
+ :aria-label="isOpen ? 'Fechar sidebar' : 'Abrir sidebar'"
15
+ >
16
+ <span class="dm-sidebar__toggle-icon" :class="{ 'dm-sidebar__toggle-icon--open': isOpen }"></span>
17
+ </button>
18
+ </div>
19
+
20
+ <nav class="dm-sidebar__content">
21
+ <slot></slot>
22
+ </nav>
23
+
24
+ <div class="dm-sidebar__footer" v-if="$slots.footer">
25
+ <slot name="footer"></slot>
26
+ </div>
27
+ </aside>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { ref, computed, watch } from 'vue'
32
+
33
+ interface Props {
34
+ position?: 'left' | 'right'
35
+ variant?: 'light' | 'dark' | 'primary'
36
+ width?: string
37
+ collapsible?: boolean
38
+ defaultOpen?: boolean
39
+ ariaLabel?: string
40
+ }
41
+
42
+ const props = withDefaults(defineProps<Props>(), {
43
+ position: 'left',
44
+ variant: 'light',
45
+ width: '280px',
46
+ collapsible: false,
47
+ defaultOpen: true,
48
+ ariaLabel: 'Sidebar navigation'
49
+ })
50
+
51
+ const emit = defineEmits<{
52
+ toggle: [isOpen: boolean]
53
+ open: []
54
+ close: []
55
+ }>()
56
+
57
+ const isOpen = ref(props.defaultOpen)
58
+
59
+ const sidebarClasses = computed(() => [
60
+ 'dm-sidebar',
61
+ `dm-sidebar--${props.position}`,
62
+ `dm-sidebar--${props.variant}`,
63
+ { 'dm-sidebar--collapsed': !isOpen.value && props.collapsible }
64
+ ])
65
+
66
+ const toggle = () => {
67
+ isOpen.value = !isOpen.value
68
+ emit('toggle', isOpen.value)
69
+ if (isOpen.value) {
70
+ emit('open')
71
+ } else {
72
+ emit('close')
73
+ }
74
+ }
75
+
76
+ watch(() => props.defaultOpen, (newVal) => {
77
+ isOpen.value = newVal
78
+ })
79
+
80
+ defineExpose({ isOpen, toggle })
81
+ </script>
82
+
83
+ <style scoped>
84
+ .dm-sidebar {
85
+ height: 100vh;
86
+ background: var(--dm-neutral-50, #ffffff);
87
+ border-right: 1px solid var(--dm-neutral-200, #e5e7eb);
88
+ display: flex;
89
+ flex-direction: column;
90
+ transition: width 0.3s ease;
91
+ width: v-bind(width);
92
+ position: fixed;
93
+ top: 0;
94
+ z-index: 90;
95
+ }
96
+
97
+ .dm-sidebar--left {
98
+ left: 0;
99
+ }
100
+
101
+ .dm-sidebar--right {
102
+ right: 0;
103
+ border-right: none;
104
+ border-left: 1px solid var(--dm-neutral-200, #e5e7eb);
105
+ }
106
+
107
+ .dm-sidebar--dark {
108
+ background: var(--dm-neutral-900, #111827);
109
+ border-color: var(--dm-neutral-800, #1f2937);
110
+ color: var(--dm-neutral-50, #ffffff);
111
+ }
112
+
113
+ .dm-sidebar--primary {
114
+ background: var(--dm-primary, #0072CE);
115
+ border-color: color-mix(in srgb, var(--dm-primary, #0072CE) 90%, black);
116
+ color: var(--dm-neutral-50, #ffffff);
117
+ }
118
+
119
+ .dm-sidebar--collapsed {
120
+ width: 64px;
121
+ }
122
+
123
+ .dm-sidebar__header {
124
+ padding: var(--dm-spacing-4, 1rem);
125
+ border-bottom: 1px solid var(--dm-neutral-200, #e5e7eb);
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: space-between;
129
+ min-height: 64px;
130
+ }
131
+
132
+ .dm-sidebar--dark .dm-sidebar__header {
133
+ border-color: var(--dm-neutral-800, #1f2937);
134
+ }
135
+
136
+ .dm-sidebar--primary .dm-sidebar__header {
137
+ border-color: color-mix(in srgb, var(--dm-primary, #0072CE) 90%, black);
138
+ }
139
+
140
+ .dm-sidebar__toggle {
141
+ width: 32px;
142
+ height: 32px;
143
+ padding: 0;
144
+ border: none;
145
+ background: transparent;
146
+ cursor: pointer;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ border-radius: var(--dm-radius-md, 0.375rem);
151
+ transition: background 0.2s ease;
152
+ }
153
+
154
+ .dm-sidebar__toggle:hover {
155
+ background: var(--dm-neutral-100, #f3f4f6);
156
+ }
157
+
158
+ .dm-sidebar--dark .dm-sidebar__toggle:hover {
159
+ background: var(--dm-neutral-800, #1f2937);
160
+ }
161
+
162
+ .dm-sidebar__toggle-icon {
163
+ width: 16px;
164
+ height: 2px;
165
+ background: var(--dm-neutral-900, #111827);
166
+ position: relative;
167
+ transition: transform 0.3s ease;
168
+ }
169
+
170
+ .dm-sidebar--dark .dm-sidebar__toggle-icon,
171
+ .dm-sidebar--primary .dm-sidebar__toggle-icon {
172
+ background: var(--dm-neutral-50, #ffffff);
173
+ }
174
+
175
+ .dm-sidebar__toggle-icon::before,
176
+ .dm-sidebar__toggle-icon::after {
177
+ content: '';
178
+ position: absolute;
179
+ width: 16px;
180
+ height: 2px;
181
+ background: inherit;
182
+ transition: transform 0.3s ease;
183
+ }
184
+
185
+ .dm-sidebar__toggle-icon::before {
186
+ top: -5px;
187
+ }
188
+
189
+ .dm-sidebar__toggle-icon::after {
190
+ bottom: -5px;
191
+ }
192
+
193
+ .dm-sidebar__toggle-icon--open {
194
+ transform: rotate(180deg);
195
+ }
196
+
197
+ .dm-sidebar__content {
198
+ flex: 1;
199
+ overflow-y: auto;
200
+ padding: var(--dm-spacing-4, 1rem);
201
+ }
202
+
203
+ .dm-sidebar__footer {
204
+ padding: var(--dm-spacing-4, 1rem);
205
+ border-top: 1px solid var(--dm-neutral-200, #e5e7eb);
206
+ }
207
+
208
+ .dm-sidebar--dark .dm-sidebar__footer {
209
+ border-color: var(--dm-neutral-800, #1f2937);
210
+ }
211
+
212
+ .dm-sidebar--primary .dm-sidebar__footer {
213
+ border-color: color-mix(in srgb, var(--dm-primary, #0072CE) 90%, black);
214
+ }
215
+
216
+ @media (max-width: 768px) {
217
+ .dm-sidebar {
218
+ transform: translateX(0);
219
+ transition: transform 0.3s ease;
220
+ }
221
+
222
+ .dm-sidebar--left.dm-sidebar--collapsed {
223
+ transform: translateX(-100%);
224
+ }
225
+
226
+ .dm-sidebar--right.dm-sidebar--collapsed {
227
+ transform: translateX(100%);
228
+ }
229
+ }
230
+ </style>
@@ -1,24 +1,29 @@
1
1
  <template>
2
- <div class="dm-tabs">
3
- <div class="dm-tabs__header" role="tablist" :aria-label="ariaLabel">
2
+ <div :class="tabsClasses">
3
+ <div class="dm-tabs__header" role="tablist" :aria-label="ariaLabel" :aria-orientation="orientation">
4
4
  <button
5
5
  v-for="(tab, index) in tabs"
6
6
  :key="index"
7
7
  :id="`tab-${index}`"
8
8
  class="dm-tabs__tab"
9
- :class="{ 'dm-tabs__tab--active': activeTab === index }"
9
+ :class="{ 'dm-tabs__tab--active': activeTab === index, 'dm-tabs__tab--disabled': typeof tab === 'object' && 'disabled' in tab && tab.disabled }"
10
10
  role="tab"
11
11
  :aria-selected="activeTab === index"
12
12
  :aria-controls="`panel-${index}`"
13
+ :aria-disabled="typeof tab === 'object' && 'disabled' in tab ? tab.disabled : false"
13
14
  :tabindex="activeTab === index ? 0 : -1"
15
+ :disabled="typeof tab === 'object' && 'disabled' in tab ? tab.disabled : false"
14
16
  @click="selectTab(index)"
15
17
  @keydown="handleKeydown($event, index)"
16
18
  >
17
- {{ tab }}
19
+ <span v-if="typeof tab === 'object' && 'icon' in tab && tab.icon" class="dm-tabs__icon">{{ tab.icon }}</span>
20
+ <span class="dm-tabs__label">{{ typeof tab === 'string' ? tab : tab.label }}</span>
21
+ <span v-if="typeof tab === 'object' && 'badge' in tab && tab.badge" class="dm-tabs__badge">{{ tab.badge }}</span>
18
22
  </button>
19
23
  <div
24
+ v-if="showIndicator"
20
25
  class="dm-tabs__indicator"
21
- :style="{ transform: `translateX(${activeTab * 100}%)` }"
26
+ :style="indicatorStyle"
22
27
  ></div>
23
28
  </div>
24
29
  <div class="dm-tabs__panels">
@@ -39,21 +44,35 @@
39
44
  </template>
40
45
 
41
46
  <script setup lang="ts">
42
- import { ref, watch } from 'vue'
47
+ import { ref, watch, computed } from 'vue'
48
+
49
+ interface Tab {
50
+ label: string
51
+ icon?: string
52
+ badge?: string | number
53
+ disabled?: boolean
54
+ }
43
55
 
44
56
  interface Props {
45
- tabs: string[]
57
+ tabs: (string | Tab)[]
46
58
  modelValue?: number
59
+ variant?: 'default' | 'pills' | 'underline'
60
+ orientation?: 'horizontal' | 'vertical'
61
+ showIndicator?: boolean
47
62
  ariaLabel?: string
48
63
  }
49
64
 
50
65
  const props = withDefaults(defineProps<Props>(), {
51
66
  modelValue: 0,
67
+ variant: 'default',
68
+ orientation: 'horizontal',
69
+ showIndicator: true,
52
70
  ariaLabel: 'Tabs'
53
71
  })
54
72
 
55
73
  const emit = defineEmits<{
56
74
  'update:modelValue': [index: number]
75
+ change: [index: number]
57
76
  }>()
58
77
 
59
78
  const activeTab = ref(props.modelValue)
@@ -62,20 +81,59 @@ watch(() => props.modelValue, (newValue) => {
62
81
  activeTab.value = newValue
63
82
  })
64
83
 
84
+ const tabsClasses = computed(() => [
85
+ 'dm-tabs',
86
+ `dm-tabs--${props.variant}`,
87
+ `dm-tabs--${props.orientation}`
88
+ ])
89
+
90
+ const indicatorStyle = computed(() => {
91
+ const count = props.tabs.length
92
+ const position = props.orientation === 'horizontal'
93
+ ? `translateX(${activeTab.value * 100}%)`
94
+ : `translateY(${activeTab.value * 100}%)`
95
+
96
+ return {
97
+ transform: position,
98
+ width: props.orientation === 'horizontal' ? `${100 / count}%` : '100%',
99
+ height: props.orientation === 'vertical' ? `${100 / count}%` : '2px'
100
+ }
101
+ })
102
+
65
103
  const selectTab = (index: number) => {
104
+ const tab = props.tabs[index]
105
+ const isDisabled = typeof tab === 'object' && 'disabled' in tab && tab.disabled
106
+
107
+ if (isDisabled) return
108
+
66
109
  activeTab.value = index
67
110
  emit('update:modelValue', index)
111
+ emit('change', index)
68
112
  }
69
113
 
70
114
  const handleKeydown = (event: KeyboardEvent, index: number) => {
71
115
  let newIndex = index
72
116
 
117
+ const isHorizontal = props.orientation === 'horizontal'
118
+
73
119
  switch (event.key) {
74
120
  case 'ArrowLeft':
121
+ if (!isHorizontal) return
75
122
  event.preventDefault()
76
123
  newIndex = index > 0 ? index - 1 : props.tabs.length - 1
77
124
  break
78
125
  case 'ArrowRight':
126
+ if (!isHorizontal) return
127
+ event.preventDefault()
128
+ newIndex = index < props.tabs.length - 1 ? index + 1 : 0
129
+ break
130
+ case 'ArrowUp':
131
+ if (isHorizontal) return
132
+ event.preventDefault()
133
+ newIndex = index > 0 ? index - 1 : props.tabs.length - 1
134
+ break
135
+ case 'ArrowDown':
136
+ if (isHorizontal) return
79
137
  event.preventDefault()
80
138
  newIndex = index < props.tabs.length - 1 ? index + 1 : 0
81
139
  break
@@ -102,10 +160,24 @@ const handleKeydown = (event: KeyboardEvent, index: number) => {
102
160
  flex-direction: column;
103
161
  }
104
162
 
163
+ .dm-tabs--vertical {
164
+ flex-direction: row;
165
+ }
166
+
167
+ .dm-tabs--vertical .dm-tabs__header {
168
+ flex-direction: column;
169
+ border-bottom: none;
170
+ border-right: 2px solid var(--dm-neutral-200, #e5e7eb);
171
+ }
172
+
173
+ .dm-tabs--vertical .dm-tabs__tab {
174
+ text-align: left;
175
+ }
176
+
105
177
  .dm-tabs__header {
106
178
  display: flex;
107
179
  position: relative;
108
- border-bottom: 2px solid var(--dm-gray-200);
180
+ border-bottom: 2px solid var(--dm-neutral-200, #e5e7eb);
109
181
  overflow-x: auto;
110
182
  scrollbar-width: none;
111
183
  }
@@ -117,43 +189,95 @@ const handleKeydown = (event: KeyboardEvent, index: number) => {
117
189
  .dm-tabs__tab {
118
190
  flex: 1;
119
191
  min-width: max-content;
120
- padding: var(--dm-space-3) var(--dm-space-4);
192
+ padding: var(--dm-spacing-3, 0.75rem) var(--dm-spacing-4, 1rem);
121
193
  border: none;
122
194
  background: transparent;
123
- color: var(--dm-text-secondary);
124
- font-size: var(--dm-text-base);
195
+ color: var(--dm-neutral-600, #6b7280);
196
+ font-size: 1rem;
125
197
  font-weight: 500;
126
198
  cursor: pointer;
127
- transition: var(--dm-transition);
199
+ transition: all 0.2s ease;
128
200
  position: relative;
129
201
  white-space: nowrap;
202
+ display: flex;
203
+ align-items: center;
204
+ gap: var(--dm-spacing-2, 0.5rem);
130
205
  }
131
206
 
132
- .dm-tabs__tab:hover {
133
- color: var(--dm-text-primary);
207
+ .dm-tabs__tab--disabled {
208
+ opacity: 0.5;
209
+ cursor: not-allowed;
210
+ }
211
+
212
+ .dm-tabs__icon {
213
+ font-size: 1.25rem;
214
+ }
215
+
216
+ .dm-tabs__badge {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ min-width: 20px;
221
+ height: 20px;
222
+ padding: 0 6px;
223
+ background: var(--dm-primary, #0072CE);
224
+ color: white;
225
+ font-size: 0.75rem;
226
+ font-weight: 600;
227
+ border-radius: 10px;
228
+ }
229
+
230
+ .dm-tabs__tab:hover:not(:disabled) {
231
+ color: var(--dm-neutral-900, #111827);
134
232
  }
135
233
 
136
234
  .dm-tabs__tab:focus-visible {
137
- outline: var(--dm-focus-ring);
235
+ outline: 2px solid var(--dm-primary, #0072CE);
138
236
  outline-offset: -2px;
237
+ border-radius: 4px;
139
238
  }
140
239
 
141
240
  .dm-tabs__tab--active {
142
- color: var(--dm-primary);
241
+ color: var(--dm-primary, #0072CE);
143
242
  }
144
243
 
145
244
  .dm-tabs__indicator {
146
245
  position: absolute;
147
246
  bottom: -2px;
148
247
  left: 0;
149
- width: calc(100% / var(--tab-count, 1));
150
- height: 2px;
151
- background: var(--dm-primary);
248
+ background: var(--dm-primary, #0072CE);
152
249
  transition: transform 0.3s ease;
153
250
  }
154
251
 
252
+ .dm-tabs--vertical .dm-tabs__indicator {
253
+ bottom: auto;
254
+ left: auto;
255
+ right: -2px;
256
+ top: 0;
257
+ width: 2px;
258
+ }
259
+
260
+ .dm-tabs--pills .dm-tabs__header {
261
+ border-bottom: none;
262
+ gap: var(--dm-spacing-2, 0.5rem);
263
+ }
264
+
265
+ .dm-tabs--pills .dm-tabs__tab {
266
+ border-radius: var(--dm-radius-md, 0.375rem);
267
+ }
268
+
269
+ .dm-tabs--pills .dm-tabs__tab--active {
270
+ background: var(--dm-primary, #0072CE);
271
+ color: white;
272
+ }
273
+
274
+ .dm-tabs--pills .dm-tabs__indicator {
275
+ display: none;
276
+ }
277
+
155
278
  .dm-tabs__panels {
156
- padding: var(--dm-space-4);
279
+ padding: var(--dm-spacing-4, 1rem);
280
+ flex: 1;
157
281
  }
158
282
 
159
283
  .dm-tabs__panel {
@@ -164,17 +288,14 @@ const handleKeydown = (event: KeyboardEvent, index: number) => {
164
288
  display: block;
165
289
  }
166
290
 
167
- @media (prefers-color-scheme: dark) {
168
- .dm-tabs__header {
169
- border-bottom-color: var(--dm-gray-700);
170
- }
171
-
291
+ @media (max-width: 768px) {
172
292
  .dm-tabs__tab {
173
- color: var(--dm-gray-400);
293
+ padding: var(--dm-spacing-2, 0.5rem) var(--dm-spacing-3, 0.75rem);
294
+ font-size: 0.875rem;
174
295
  }
175
-
176
- .dm-tabs__tab:hover {
177
- color: var(--dm-white);
296
+
297
+ .dm-tabs__icon {
298
+ font-size: 1rem;
178
299
  }
179
300
  }
180
301
  </style>
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaFloatingBar from '../DatametriaFloatingBar.vue'
4
+
5
+ describe('DatametriaFloatingBar', () => {
6
+ it('renders correctly', () => {
7
+ const wrapper = mount(DatametriaFloatingBar)
8
+ expect(wrapper.find('.dm-floating-bar').exists()).toBe(true)
9
+ })
10
+
11
+ it('applies position classes', () => {
12
+ const positions = ['top', 'bottom', 'left', 'right'] as const
13
+ positions.forEach(position => {
14
+ const wrapper = mount(DatametriaFloatingBar, { props: { position } })
15
+ expect(wrapper.find(`.dm-floating-bar--${position}`).exists()).toBe(true)
16
+ })
17
+ })
18
+
19
+ it('applies variant classes', () => {
20
+ const variants = ['light', 'dark', 'primary'] as const
21
+ variants.forEach(variant => {
22
+ const wrapper = mount(DatametriaFloatingBar, { props: { variant } })
23
+ expect(wrapper.find(`.dm-floating-bar--${variant}`).exists()).toBe(true)
24
+ })
25
+ })
26
+
27
+ it('applies shadow class when shadow is true', () => {
28
+ const wrapper = mount(DatametriaFloatingBar, { props: { shadow: true } })
29
+ expect(wrapper.find('.dm-floating-bar--shadow').exists()).toBe(true)
30
+ })
31
+
32
+ it('does not apply shadow class when shadow is false', () => {
33
+ const wrapper = mount(DatametriaFloatingBar, { props: { shadow: false } })
34
+ expect(wrapper.find('.dm-floating-bar--shadow').exists()).toBe(false)
35
+ })
36
+
37
+ it('applies rounded class when rounded is true', () => {
38
+ const wrapper = mount(DatametriaFloatingBar, { props: { rounded: true } })
39
+ expect(wrapper.find('.dm-floating-bar--rounded').exists()).toBe(true)
40
+ })
41
+
42
+ it('does not apply rounded class when rounded is false', () => {
43
+ const wrapper = mount(DatametriaFloatingBar, { props: { rounded: false } })
44
+ expect(wrapper.find('.dm-floating-bar--rounded').exists()).toBe(false)
45
+ })
46
+
47
+ it('renders slot content', () => {
48
+ const wrapper = mount(DatametriaFloatingBar, {
49
+ slots: { default: '<button class="test-button">Action</button>' }
50
+ })
51
+ expect(wrapper.find('.test-button').exists()).toBe(true)
52
+ expect(wrapper.text()).toContain('Action')
53
+ })
54
+
55
+ it('applies correct position style for top', () => {
56
+ const wrapper = mount(DatametriaFloatingBar, {
57
+ props: { position: 'top', offset: '20px' }
58
+ })
59
+ const style = wrapper.find('.dm-floating-bar').attributes('style')
60
+ expect(style).toContain('top: 20px')
61
+ })
62
+
63
+ it('applies correct position style for bottom', () => {
64
+ const wrapper = mount(DatametriaFloatingBar, {
65
+ props: { position: 'bottom', offset: '24px' }
66
+ })
67
+ const style = wrapper.find('.dm-floating-bar').attributes('style')
68
+ expect(style).toContain('bottom: 24px')
69
+ })
70
+
71
+ it('applies correct position style for left', () => {
72
+ const wrapper = mount(DatametriaFloatingBar, {
73
+ props: { position: 'left', offset: '32px' }
74
+ })
75
+ const style = wrapper.find('.dm-floating-bar').attributes('style')
76
+ expect(style).toContain('left: 32px')
77
+ })
78
+
79
+ it('applies correct position style for right', () => {
80
+ const wrapper = mount(DatametriaFloatingBar, {
81
+ props: { position: 'right', offset: '40px' }
82
+ })
83
+ const style = wrapper.find('.dm-floating-bar').attributes('style')
84
+ expect(style).toContain('right: 40px')
85
+ })
86
+
87
+ it('uses default offset when not provided', () => {
88
+ const wrapper = mount(DatametriaFloatingBar, { props: { position: 'bottom' } })
89
+ const style = wrapper.find('.dm-floating-bar').attributes('style')
90
+ expect(style).toContain('bottom: 16px')
91
+ })
92
+
93
+ it('has correct aria attributes', () => {
94
+ const wrapper = mount(DatametriaFloatingBar, {
95
+ props: { ariaLabel: 'Floating toolbar' }
96
+ })
97
+ const bar = wrapper.find('.dm-floating-bar')
98
+ expect(bar.attributes('role')).toBe('toolbar')
99
+ expect(bar.attributes('aria-label')).toBe('Floating toolbar')
100
+ })
101
+
102
+ it('renders content wrapper', () => {
103
+ const wrapper = mount(DatametriaFloatingBar)
104
+ expect(wrapper.find('.dm-floating-bar__content').exists()).toBe(true)
105
+ })
106
+
107
+ it('applies all classes correctly', () => {
108
+ const wrapper = mount(DatametriaFloatingBar, {
109
+ props: {
110
+ position: 'top',
111
+ variant: 'primary',
112
+ shadow: true,
113
+ rounded: true
114
+ }
115
+ })
116
+ const bar = wrapper.find('.dm-floating-bar')
117
+ expect(bar.classes()).toContain('dm-floating-bar--top')
118
+ expect(bar.classes()).toContain('dm-floating-bar--primary')
119
+ expect(bar.classes()).toContain('dm-floating-bar--shadow')
120
+ expect(bar.classes()).toContain('dm-floating-bar--rounded')
121
+ })
122
+
123
+ it('renders multiple action buttons', () => {
124
+ const wrapper = mount(DatametriaFloatingBar, {
125
+ slots: {
126
+ default: `
127
+ <button class="btn-1">Action 1</button>
128
+ <button class="btn-2">Action 2</button>
129
+ <button class="btn-3">Action 3</button>
130
+ `
131
+ }
132
+ })
133
+ expect(wrapper.find('.btn-1').exists()).toBe(true)
134
+ expect(wrapper.find('.btn-2').exists()).toBe(true)
135
+ expect(wrapper.find('.btn-3').exists()).toBe(true)
136
+ })
137
+ })