@datametria/vue-components 1.1.3 → 1.2.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.
@@ -1,163 +1,176 @@
1
- <template>
2
- <Teleport to="body">
3
- <Transition name="dm-toast">
4
- <div
5
- v-if="isVisible"
6
- class="dm-toast"
7
- :class="`dm-toast--${variant}`"
8
- role="alert"
9
- :aria-live="variant === 'error' ? 'assertive' : 'polite'"
10
- >
11
- <div class="dm-toast__content">
12
- <span class="dm-toast__message">{{ message }}</span>
13
- <button
14
- v-if="closable"
15
- class="dm-toast__close"
16
- @click="close"
17
- aria-label="Fechar"
18
- >×</button>
19
- </div>
20
- </div>
21
- </Transition>
22
- </Teleport>
23
- </template>
24
-
25
- <script setup lang="ts">
26
- import { ref, watch, onMounted } from 'vue'
27
-
28
- interface Props {
29
- message: string
30
- variant?: 'success' | 'error' | 'warning' | 'info'
31
- duration?: number
32
- closable?: boolean
33
- modelValue?: boolean
34
- }
35
-
36
- const props = withDefaults(defineProps<Props>(), {
37
- variant: 'info',
38
- duration: 3000,
39
- closable: true,
40
- modelValue: false
41
- })
42
-
43
- const emit = defineEmits<{
44
- 'update:modelValue': [value: boolean]
45
- close: []
46
- }>()
47
-
48
- const isVisible = ref(props.modelValue)
49
- let timer: ReturnType<typeof setTimeout> | null = null
50
-
51
- watch(() => props.modelValue, (newValue) => {
52
- isVisible.value = newValue
53
- if (newValue && props.duration > 0) {
54
- startTimer()
55
- }
56
- })
57
-
58
- const startTimer = () => {
59
- if (timer) clearTimeout(timer)
60
- timer = setTimeout(() => {
61
- close()
62
- }, props.duration)
63
- }
64
-
65
- const close = () => {
66
- isVisible.value = false
67
- emit('update:modelValue', false)
68
- emit('close')
69
- if (timer) clearTimeout(timer)
70
- }
71
-
72
- onMounted(() => {
73
- if (isVisible.value && props.duration > 0) {
74
- startTimer()
75
- }
76
- })
77
- </script>
78
-
79
- <style scoped>
80
- .dm-toast {
81
- position: fixed;
82
- top: var(--dm-space-4);
83
- right: var(--dm-space-4);
84
- min-width: 300px;
85
- max-width: 500px;
86
- padding: var(--dm-space-4);
87
- border-radius: var(--dm-radius);
88
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
89
- z-index: 9999;
90
- }
91
-
92
- .dm-toast--success {
93
- background: var(--dm-success);
94
- color: var(--dm-white);
95
- }
96
-
97
- .dm-toast--error {
98
- background: var(--dm-error);
99
- color: var(--dm-white);
100
- }
101
-
102
- .dm-toast--warning {
103
- background: var(--dm-warning);
104
- color: var(--dm-gray-900);
105
- }
106
-
107
- .dm-toast--info {
108
- background: var(--dm-primary);
109
- color: var(--dm-white);
110
- }
111
-
112
- .dm-toast__content {
113
- display: flex;
114
- align-items: center;
115
- gap: var(--dm-space-3);
116
- }
117
-
118
- .dm-toast__message {
119
- flex: 1;
120
- font-size: var(--dm-text-sm);
121
- line-height: 1.5;
122
- }
123
-
124
- .dm-toast__close {
125
- width: 24px;
126
- height: 24px;
127
- border: none;
128
- background: transparent;
129
- color: inherit;
130
- font-size: 24px;
131
- line-height: 1;
132
- cursor: pointer;
133
- opacity: 0.8;
134
- transition: var(--dm-transition);
135
- }
136
-
137
- .dm-toast__close:hover {
138
- opacity: 1;
139
- }
140
-
141
- .dm-toast-enter-active,
142
- .dm-toast-leave-active {
143
- transition: all 0.3s ease;
144
- }
145
-
146
- .dm-toast-enter-from {
147
- opacity: 0;
148
- transform: translateX(100%);
149
- }
150
-
151
- .dm-toast-leave-to {
152
- opacity: 0;
153
- transform: translateY(-20px);
154
- }
155
-
156
- @media (max-width: 640px) {
157
- .dm-toast {
158
- left: var(--dm-space-4);
159
- right: var(--dm-space-4);
160
- min-width: auto;
161
- }
162
- }
163
- </style>
1
+ <template>
2
+ <Teleport to="body">
3
+ <Transition name="dm-toast">
4
+ <div
5
+ v-if="isVisible"
6
+ class="dm-toast"
7
+ :class="`dm-toast--${variant}`"
8
+ role="alert"
9
+ :aria-live="variant === 'error' ? 'assertive' : 'polite'"
10
+ >
11
+ <div class="dm-toast__content">
12
+ <span class="dm-toast__message">{{ message }}</span>
13
+ <button
14
+ v-if="closable"
15
+ class="dm-toast__close"
16
+ @click="close"
17
+ aria-label="Fechar"
18
+ >×</button>
19
+ </div>
20
+ </div>
21
+ </Transition>
22
+ </Teleport>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { ref, watch, onMounted } from 'vue'
27
+
28
+ interface Props {
29
+ message: string
30
+ variant?: 'success' | 'error' | 'warning' | 'info' | 'primary'
31
+ duration?: number
32
+ closable?: boolean
33
+ modelValue?: boolean
34
+ }
35
+
36
+ const props = withDefaults(defineProps<Props>(), {
37
+ variant: 'info',
38
+ duration: 3000,
39
+ closable: true,
40
+ modelValue: false
41
+ })
42
+
43
+ // Validação em desenvolvimento
44
+ if (process.env.NODE_ENV === 'development') {
45
+ const validVariants = ['success', 'error', 'warning', 'info', 'primary']
46
+ if (!validVariants.includes(props.variant)) {
47
+ console.warn(`[DatametriaToast] Invalid variant "${props.variant}". Valid options: ${validVariants.join(', ')}`)
48
+ }
49
+ }
50
+
51
+ const emit = defineEmits<{
52
+ 'update:modelValue': [value: boolean]
53
+ close: []
54
+ }>()
55
+
56
+ const isVisible = ref(props.modelValue)
57
+ let timer: ReturnType<typeof setTimeout> | null = null
58
+
59
+ watch(() => props.modelValue, (newValue) => {
60
+ isVisible.value = newValue
61
+ if (newValue && props.duration > 0) {
62
+ startTimer()
63
+ }
64
+ })
65
+
66
+ const startTimer = () => {
67
+ if (timer) clearTimeout(timer)
68
+ timer = setTimeout(() => {
69
+ close()
70
+ }, props.duration)
71
+ }
72
+
73
+ const close = () => {
74
+ isVisible.value = false
75
+ emit('update:modelValue', false)
76
+ emit('close')
77
+ if (timer) clearTimeout(timer)
78
+ }
79
+
80
+ onMounted(() => {
81
+ if (isVisible.value && props.duration > 0) {
82
+ startTimer()
83
+ }
84
+ })
85
+ </script>
86
+
87
+ <style scoped>
88
+ .dm-toast {
89
+ position: fixed;
90
+ top: var(--dm-space-4);
91
+ right: var(--dm-space-4);
92
+ min-width: 300px;
93
+ max-width: 500px;
94
+ padding: var(--dm-space-4);
95
+ border-radius: var(--dm-radius);
96
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
97
+ z-index: 9999;
98
+ }
99
+
100
+ .dm-toast--success {
101
+ background: var(--dm-success);
102
+ color: var(--dm-white);
103
+ }
104
+
105
+ .dm-toast--error {
106
+ background: var(--dm-error);
107
+ color: var(--dm-white);
108
+ }
109
+
110
+ .dm-toast--warning {
111
+ background: var(--dm-warning);
112
+ color: var(--dm-gray-900);
113
+ }
114
+
115
+ .dm-toast--primary {
116
+ background: var(--dm-primary);
117
+ color: var(--dm-white);
118
+ }
119
+
120
+ .dm-toast--info {
121
+ background: var(--dm-gray-600);
122
+ color: var(--dm-white);
123
+ }
124
+
125
+ .dm-toast__content {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: var(--dm-space-3);
129
+ }
130
+
131
+ .dm-toast__message {
132
+ flex: 1;
133
+ font-size: var(--dm-text-sm);
134
+ line-height: 1.5;
135
+ }
136
+
137
+ .dm-toast__close {
138
+ width: 24px;
139
+ height: 24px;
140
+ border: none;
141
+ background: transparent;
142
+ color: inherit;
143
+ font-size: 24px;
144
+ line-height: 1;
145
+ cursor: pointer;
146
+ opacity: 0.8;
147
+ transition: var(--dm-transition);
148
+ }
149
+
150
+ .dm-toast__close:hover {
151
+ opacity: 1;
152
+ }
153
+
154
+ .dm-toast-enter-active,
155
+ .dm-toast-leave-active {
156
+ transition: all 0.3s ease;
157
+ }
158
+
159
+ .dm-toast-enter-from {
160
+ opacity: 0;
161
+ transform: translateX(100%);
162
+ }
163
+
164
+ .dm-toast-leave-to {
165
+ opacity: 0;
166
+ transform: translateY(-20px);
167
+ }
168
+
169
+ @media (max-width: 640px) {
170
+ .dm-toast {
171
+ left: var(--dm-space-4);
172
+ right: var(--dm-space-4);
173
+ min-width: auto;
174
+ }
175
+ }
176
+ </style>
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaAlert from '../DatametriaAlert.vue'
4
+
5
+ describe('DatametriaAlert', () => {
6
+ it('renders with primary variant', () => {
7
+ const wrapper = mount(DatametriaAlert, {
8
+ props: {
9
+ variant: 'primary',
10
+ message: 'Test message'
11
+ }
12
+ })
13
+
14
+ expect(wrapper.classes()).toContain('datametria-alert--primary')
15
+ })
16
+
17
+ it('validates invalid variant in development', () => {
18
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
19
+ const originalEnv = process.env.NODE_ENV
20
+ process.env.NODE_ENV = 'development'
21
+
22
+ mount(DatametriaAlert, {
23
+ props: {
24
+ variant: 'invalid',
25
+ message: 'Test'
26
+ }
27
+ })
28
+
29
+ expect(consoleSpy).toHaveBeenCalledWith(
30
+ expect.stringContaining('[DatametriaAlert] Invalid variant "invalid"')
31
+ )
32
+
33
+ process.env.NODE_ENV = originalEnv
34
+ consoleSpy.mockRestore()
35
+ })
36
+ })
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaBadge from '../DatametriaBadge.vue'
4
+
5
+ describe('DatametriaBadge', () => {
6
+ it('renders with primary variant', () => {
7
+ const wrapper = mount(DatametriaBadge, {
8
+ props: { label: 'Test', variant: 'primary' }
9
+ })
10
+
11
+ expect(wrapper.classes()).toContain('dm-badge--primary')
12
+ })
13
+
14
+ it('validates invalid variant in development', () => {
15
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
16
+ const originalEnv = process.env.NODE_ENV
17
+ process.env.NODE_ENV = 'development'
18
+
19
+ mount(DatametriaBadge, {
20
+ props: { label: 'Test', variant: 'invalid' }
21
+ })
22
+
23
+ expect(consoleSpy).toHaveBeenCalledWith(
24
+ expect.stringContaining('[DatametriaBadge] Invalid variant "invalid"')
25
+ )
26
+
27
+ process.env.NODE_ENV = originalEnv
28
+ consoleSpy.mockRestore()
29
+ })
30
+ })
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaButton from '../DatametriaButton.vue'
4
+ import { ButtonVariant } from '../../types'
5
+
6
+ describe('DatametriaButton', () => {
7
+ it('validates invalid variant in development', () => {
8
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
9
+ const originalEnv = process.env.NODE_ENV
10
+ process.env.NODE_ENV = 'development'
11
+
12
+ mount(DatametriaButton, {
13
+ props: { variant: 'invalid' }
14
+ })
15
+
16
+ expect(consoleSpy).toHaveBeenCalledWith(
17
+ expect.stringContaining('[DatametriaButton] Invalid variant "invalid"')
18
+ )
19
+
20
+ process.env.NODE_ENV = originalEnv
21
+ consoleSpy.mockRestore()
22
+ })
23
+
24
+ it('renders with primary variant', () => {
25
+ const wrapper = mount(DatametriaButton, {
26
+ props: { variant: ButtonVariant.PRIMARY }
27
+ })
28
+
29
+ expect(wrapper.classes()).toContain('datametria-button--primary')
30
+ })
31
+ })
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaChip from '../DatametriaChip.vue'
4
+
5
+ describe('DatametriaChip', () => {
6
+ it('renders with primary variant by default', () => {
7
+ const wrapper = mount(DatametriaChip, {
8
+ props: { label: 'Test' }
9
+ })
10
+
11
+ expect(wrapper.classes()).toContain('dm-chip--primary')
12
+ })
13
+
14
+ it('validates invalid variant in development', () => {
15
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
16
+ const originalEnv = process.env.NODE_ENV
17
+ process.env.NODE_ENV = 'development'
18
+
19
+ mount(DatametriaChip, {
20
+ props: { label: 'Test', variant: 'invalid' }
21
+ })
22
+
23
+ expect(consoleSpy).toHaveBeenCalledWith(
24
+ expect.stringContaining('[DatametriaChip] Invalid variant "invalid"')
25
+ )
26
+
27
+ process.env.NODE_ENV = originalEnv
28
+ consoleSpy.mockRestore()
29
+ })
30
+
31
+ it('emits click event when clickable', async () => {
32
+ const wrapper = mount(DatametriaChip, {
33
+ props: { label: 'Test', clickable: true }
34
+ })
35
+
36
+ await wrapper.trigger('click')
37
+ expect(wrapper.emitted('click')).toBeTruthy()
38
+ })
39
+ })
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaNavbar from '../DatametriaNavbar.vue'
4
+
5
+ describe('DatametriaNavbar', () => {
6
+ it('renders with primary variant', () => {
7
+ const wrapper = mount(DatametriaNavbar, {
8
+ props: { variant: 'primary' }
9
+ })
10
+
11
+ expect(wrapper.classes()).toContain('dm-navbar--primary')
12
+ })
13
+
14
+ it('validates invalid variant in development', () => {
15
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
16
+ const originalEnv = process.env.NODE_ENV
17
+ process.env.NODE_ENV = 'development'
18
+
19
+ mount(DatametriaNavbar, {
20
+ props: { variant: 'invalid' }
21
+ })
22
+
23
+ expect(consoleSpy).toHaveBeenCalledWith(
24
+ expect.stringContaining('[DatametriaNavbar] Invalid variant "invalid"')
25
+ )
26
+
27
+ process.env.NODE_ENV = originalEnv
28
+ consoleSpy.mockRestore()
29
+ })
30
+
31
+ it('renders brand text', () => {
32
+ const wrapper = mount(DatametriaNavbar, {
33
+ props: { brand: 'DATAMETRIA' }
34
+ })
35
+
36
+ expect(wrapper.text()).toContain('DATAMETRIA')
37
+ })
38
+
39
+ it('toggles mobile menu', async () => {
40
+ const wrapper = mount(DatametriaNavbar, {
41
+ slots: { menu: '<div>Menu items</div>' }
42
+ })
43
+
44
+ const toggle = wrapper.find('.dm-navbar__toggle')
45
+ await toggle.trigger('click')
46
+
47
+ expect(wrapper.find('.dm-navbar__menu').classes()).toContain('dm-navbar__menu--open')
48
+ })
49
+ })
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaToast from '../DatametriaToast.vue'
4
+
5
+ describe('DatametriaToast', () => {
6
+ it('renders with primary variant', () => {
7
+ // Create body element for Teleport
8
+ const body = document.createElement('div')
9
+ document.body.appendChild(body)
10
+
11
+ const wrapper = mount(DatametriaToast, {
12
+ props: {
13
+ message: 'Test message',
14
+ variant: 'primary',
15
+ modelValue: true
16
+ },
17
+ attachTo: body
18
+ })
19
+
20
+ // Check if toast exists in document body
21
+ const toast = document.querySelector('.dm-toast')
22
+ expect(toast).toBeTruthy()
23
+ expect(toast.classList.contains('dm-toast--primary')).toBe(true)
24
+
25
+ wrapper.unmount()
26
+ document.body.removeChild(body)
27
+ })
28
+
29
+ it('validates invalid variant in development', () => {
30
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
31
+ const originalEnv = process.env.NODE_ENV
32
+ process.env.NODE_ENV = 'development'
33
+
34
+ mount(DatametriaToast, {
35
+ props: {
36
+ message: 'Test',
37
+ variant: 'invalid',
38
+ modelValue: true
39
+ }
40
+ })
41
+
42
+ expect(consoleSpy).toHaveBeenCalledWith(
43
+ expect.stringContaining('[DatametriaToast] Invalid variant "invalid"')
44
+ )
45
+
46
+ process.env.NODE_ENV = originalEnv
47
+ consoleSpy.mockRestore()
48
+ })
49
+ })
@@ -0,0 +1,96 @@
1
+ import DatametriaButton from '../components/DatametriaButton.vue'
2
+ import DatametriaAlert from '../components/DatametriaAlert.vue'
3
+ import DatametriaBadge from '../components/DatametriaBadge.vue'
4
+ import DatametriaChip from '../components/DatametriaChip.vue'
5
+ import DatametriaToast from '../components/DatametriaToast.vue'
6
+ import DatametriaNavbar from '../components/DatametriaNavbar.vue'
7
+
8
+ export default {
9
+ title: 'Design System/Variants',
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component: 'Showcase of all component variants with primary variant standardization'
14
+ }
15
+ }
16
+ }
17
+ }
18
+
19
+ export const AllVariants = () => ({
20
+ components: {
21
+ DatametriaButton,
22
+ DatametriaAlert,
23
+ DatametriaBadge,
24
+ DatametriaChip,
25
+ DatametriaToast,
26
+ DatametriaNavbar
27
+ },
28
+ template: `
29
+ <div style="padding: 2rem; space-y: 2rem;">
30
+ <section>
31
+ <h2>Button Variants</h2>
32
+ <div style="display: flex; gap: 1rem; margin: 1rem 0;">
33
+ <DatametriaButton variant="primary">Primary</DatametriaButton>
34
+ <DatametriaButton variant="secondary">Secondary</DatametriaButton>
35
+ <DatametriaButton variant="outline">Outline</DatametriaButton>
36
+ <DatametriaButton variant="ghost">Ghost</DatametriaButton>
37
+ </div>
38
+ </section>
39
+
40
+ <section>
41
+ <h2>Alert Variants</h2>
42
+ <div style="display: flex; flex-direction: column; gap: 1rem; margin: 1rem 0;">
43
+ <DatametriaAlert variant="primary" message="Primary alert message" />
44
+ <DatametriaAlert variant="success" message="Success alert message" />
45
+ <DatametriaAlert variant="error" message="Error alert message" />
46
+ <DatametriaAlert variant="warning" message="Warning alert message" />
47
+ <DatametriaAlert variant="info" message="Info alert message" />
48
+ </div>
49
+ </section>
50
+
51
+ <section>
52
+ <h2>Badge Variants</h2>
53
+ <div style="display: flex; gap: 1rem; margin: 1rem 0;">
54
+ <DatametriaBadge variant="primary" label="Primary" />
55
+ <DatametriaBadge variant="secondary" label="Secondary" />
56
+ <DatametriaBadge variant="success" label="Success" />
57
+ <DatametriaBadge variant="error" label="Error" />
58
+ <DatametriaBadge variant="warning" label="Warning" />
59
+ <DatametriaBadge variant="info" label="Info" />
60
+ </div>
61
+ </section>
62
+
63
+ <section>
64
+ <h2>Chip Variants</h2>
65
+ <div style="display: flex; gap: 1rem; margin: 1rem 0;">
66
+ <DatametriaChip variant="primary" label="Primary" />
67
+ <DatametriaChip variant="secondary" label="Secondary" />
68
+ <DatametriaChip variant="success" label="Success" />
69
+ <DatametriaChip variant="error" label="Error" />
70
+ <DatametriaChip variant="warning" label="Warning" />
71
+ </div>
72
+ </section>
73
+
74
+ <section>
75
+ <h2>Toast Variants</h2>
76
+ <div style="display: flex; flex-direction: column; gap: 1rem; margin: 1rem 0;">
77
+ <DatametriaToast variant="primary" message="Primary toast" :model-value="true" />
78
+ <DatametriaToast variant="success" message="Success toast" :model-value="true" />
79
+ <DatametriaToast variant="error" message="Error toast" :model-value="true" />
80
+ <DatametriaToast variant="warning" message="Warning toast" :model-value="true" />
81
+ <DatametriaToast variant="info" message="Info toast" :model-value="true" />
82
+ </div>
83
+ </section>
84
+
85
+ <section>
86
+ <h2>Navbar Variants</h2>
87
+ <div style="display: flex; flex-direction: column; gap: 1rem; margin: 1rem 0;">
88
+ <DatametriaNavbar variant="primary" brand="Primary Navbar" />
89
+ <DatametriaNavbar variant="light" brand="Light Navbar" />
90
+ <DatametriaNavbar variant="dark" brand="Dark Navbar" />
91
+ <DatametriaNavbar variant="transparent" brand="Transparent Navbar" />
92
+ </div>
93
+ </section>
94
+ </div>
95
+ `
96
+ })