@datametria/vue-components 1.1.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.
- package/ACCESSIBILITY.md +78 -0
- package/DESIGN-SYSTEM.md +70 -0
- package/LICENSE +21 -0
- package/PROGRESS.md +327 -0
- package/README.md +473 -0
- package/dist/index.es.js +1405 -0
- package/dist/index.umd.js +1 -0
- package/dist/vue-components.css +1 -0
- package/package.json +98 -0
- package/src/components/DatametriaAlert.vue +123 -0
- package/src/components/DatametriaAutocomplete.vue +292 -0
- package/src/components/DatametriaAvatar.vue +99 -0
- package/src/components/DatametriaBadge.vue +90 -0
- package/src/components/DatametriaBreadcrumb.vue +144 -0
- package/src/components/DatametriaButton.vue +157 -0
- package/src/components/DatametriaCard.vue +72 -0
- package/src/components/DatametriaCheckbox.vue +82 -0
- package/src/components/DatametriaChip.vue +149 -0
- package/src/components/DatametriaContainer.vue +57 -0
- package/src/components/DatametriaDatePicker.vue +140 -0
- package/src/components/DatametriaDivider.vue +100 -0
- package/src/components/DatametriaFileUpload.vue +268 -0
- package/src/components/DatametriaGrid.vue +44 -0
- package/src/components/DatametriaInput.vue +102 -0
- package/src/components/DatametriaModal.vue +135 -0
- package/src/components/DatametriaNavbar.vue +227 -0
- package/src/components/DatametriaProgress.vue +113 -0
- package/src/components/DatametriaRadio.vue +138 -0
- package/src/components/DatametriaSelect.vue +112 -0
- package/src/components/DatametriaSpinner.vue +112 -0
- package/src/components/DatametriaSwitch.vue +137 -0
- package/src/components/DatametriaTable.vue +105 -0
- package/src/components/DatametriaTabs.vue +180 -0
- package/src/components/DatametriaTextarea.vue +159 -0
- package/src/components/DatametriaToast.vue +163 -0
- package/src/composables/useAPI.ts +78 -0
- package/src/composables/useClipboard.ts +42 -0
- package/src/composables/useDebounce.ts +16 -0
- package/src/composables/useLocalStorage.ts +26 -0
- package/src/composables/useTheme.ts +66 -0
- package/src/composables/useValidation.ts +39 -0
- package/src/index.ts +52 -0
- package/src/styles/design-tokens.css +31 -0
- package/src/types/index.ts +34 -0
|
@@ -0,0 +1,163 @@
|
|
|
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>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface ApiConfig {
|
|
4
|
+
baseURL?: string
|
|
5
|
+
timeout?: number
|
|
6
|
+
headers?: Record<string, string>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useAPI(config: ApiConfig = {}) {
|
|
10
|
+
const loading = ref(false)
|
|
11
|
+
const error = ref<string | null>(null)
|
|
12
|
+
const data = ref<any>(null)
|
|
13
|
+
|
|
14
|
+
const request = async (url: string, options: RequestInit = {}) => {
|
|
15
|
+
loading.value = true
|
|
16
|
+
error.value = null
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const fullUrl = config.baseURL ? `${config.baseURL}${url}` : url
|
|
20
|
+
|
|
21
|
+
const response = await fetch(fullUrl, {
|
|
22
|
+
...options,
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
...config.headers,
|
|
26
|
+
...options.headers
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = await response.json()
|
|
35
|
+
data.value = result
|
|
36
|
+
return result
|
|
37
|
+
} catch (err) {
|
|
38
|
+
error.value = (err as Error).message || 'Request failed'
|
|
39
|
+
throw err
|
|
40
|
+
} finally {
|
|
41
|
+
loading.value = false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const get = (url: string, options?: RequestInit) => {
|
|
46
|
+
return request(url, { ...options, method: 'GET' })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const post = (url: string, body: any, options?: RequestInit) => {
|
|
50
|
+
return request(url, {
|
|
51
|
+
...options,
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: JSON.stringify(body)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const put = (url: string, body: any, options?: RequestInit) => {
|
|
58
|
+
return request(url, {
|
|
59
|
+
...options,
|
|
60
|
+
method: 'PUT',
|
|
61
|
+
body: JSON.stringify(body)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const del = (url: string, options?: RequestInit) => {
|
|
66
|
+
return request(url, { ...options, method: 'DELETE' })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
loading,
|
|
71
|
+
error,
|
|
72
|
+
data,
|
|
73
|
+
get,
|
|
74
|
+
post,
|
|
75
|
+
put,
|
|
76
|
+
delete: del
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useClipboard() {
|
|
4
|
+
const copied = ref(false)
|
|
5
|
+
const error = ref<string | null>(null)
|
|
6
|
+
|
|
7
|
+
const copy = async (text: string): Promise<boolean> => {
|
|
8
|
+
try {
|
|
9
|
+
await navigator.clipboard.writeText(text)
|
|
10
|
+
copied.value = true
|
|
11
|
+
error.value = null
|
|
12
|
+
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
copied.value = false
|
|
15
|
+
}, 2000)
|
|
16
|
+
|
|
17
|
+
return true
|
|
18
|
+
} catch (err) {
|
|
19
|
+
error.value = (err as Error).message || 'Copy failed'
|
|
20
|
+
copied.value = false
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const read = async (): Promise<string> => {
|
|
26
|
+
try {
|
|
27
|
+
const text = await navigator.clipboard.readText()
|
|
28
|
+
error.value = null
|
|
29
|
+
return text
|
|
30
|
+
} catch (err) {
|
|
31
|
+
error.value = (err as Error).message || 'Read failed'
|
|
32
|
+
return ''
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
copied,
|
|
38
|
+
error,
|
|
39
|
+
copy,
|
|
40
|
+
read
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ref, watch, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useDebounce<T>(value: Ref<T>, delay: number = 300): Ref<T> {
|
|
4
|
+
const debouncedValue = ref<T>(value.value) as Ref<T>
|
|
5
|
+
let timeout: ReturnType<typeof setTimeout> | null = null
|
|
6
|
+
|
|
7
|
+
watch(value, (newValue) => {
|
|
8
|
+
if (timeout) clearTimeout(timeout)
|
|
9
|
+
|
|
10
|
+
timeout = setTimeout(() => {
|
|
11
|
+
debouncedValue.value = newValue
|
|
12
|
+
}, delay)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return debouncedValue
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ref, watch, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useLocalStorage<T>(key: string, defaultValue: T): Ref<T> {
|
|
4
|
+
const storedValue = localStorage.getItem(key)
|
|
5
|
+
let parsedValue = defaultValue
|
|
6
|
+
|
|
7
|
+
if (storedValue) {
|
|
8
|
+
try {
|
|
9
|
+
parsedValue = JSON.parse(storedValue)
|
|
10
|
+
} catch {
|
|
11
|
+
parsedValue = defaultValue
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const data = ref<T>(parsedValue) as Ref<T>
|
|
16
|
+
|
|
17
|
+
watch(
|
|
18
|
+
data,
|
|
19
|
+
(newValue) => {
|
|
20
|
+
localStorage.setItem(key, JSON.stringify(newValue))
|
|
21
|
+
},
|
|
22
|
+
{ deep: true }
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ref, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
let isDark = ref(false)
|
|
4
|
+
let initialized = false
|
|
5
|
+
|
|
6
|
+
// Reset function for testing
|
|
7
|
+
export function resetTheme() {
|
|
8
|
+
isDark = ref(false)
|
|
9
|
+
initialized = false
|
|
10
|
+
document.documentElement.classList.remove('dark')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useTheme() {
|
|
14
|
+
const toggleTheme = () => {
|
|
15
|
+
isDark.value = !isDark.value
|
|
16
|
+
document.documentElement.classList.toggle('dark', isDark.value)
|
|
17
|
+
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const setTheme = (theme: 'light' | 'dark') => {
|
|
21
|
+
isDark.value = theme === 'dark'
|
|
22
|
+
document.documentElement.classList.toggle('dark', isDark.value)
|
|
23
|
+
localStorage.setItem('theme', theme)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const initTheme = () => {
|
|
27
|
+
initialized = true
|
|
28
|
+
|
|
29
|
+
const savedTheme = localStorage.getItem('theme')
|
|
30
|
+
if (savedTheme) {
|
|
31
|
+
setTheme(savedTheme as 'light' | 'dark')
|
|
32
|
+
} else if (typeof window !== 'undefined' && window.matchMedia) {
|
|
33
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
34
|
+
setTheme(prefersDark ? 'dark' : 'light')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Listen for system preference changes
|
|
38
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
39
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
40
|
+
if (!localStorage.getItem('theme')) {
|
|
41
|
+
setTheme(e.matches ? 'dark' : 'light')
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Auto-initialize on first use
|
|
48
|
+
if (!initialized) {
|
|
49
|
+
initTheme()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const currentTheme = computed(() => isDark.value ? 'dark' : 'light')
|
|
53
|
+
|
|
54
|
+
const setDark = () => setTheme('dark')
|
|
55
|
+
const setLight = () => setTheme('light')
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
isDark,
|
|
59
|
+
currentTheme,
|
|
60
|
+
toggle: toggleTheme,
|
|
61
|
+
setTheme,
|
|
62
|
+
setDark,
|
|
63
|
+
setLight,
|
|
64
|
+
initTheme
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Simple validators that return boolean
|
|
2
|
+
export const required = (value: any): boolean => {
|
|
3
|
+
return !!value
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const email = (value: any): boolean => {
|
|
7
|
+
if (!value) return true
|
|
8
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const minLength = (min: number) => (value: any): boolean => {
|
|
12
|
+
if (!value) return true
|
|
13
|
+
return value.length >= min
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const maxLength = (max: number) => (value: any): boolean => {
|
|
17
|
+
if (!value) return true
|
|
18
|
+
return value.length <= max
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const pattern = (regex: RegExp) => (value: any): boolean => {
|
|
22
|
+
if (!value) return true
|
|
23
|
+
return regex.test(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const custom = (validator: (value: any) => boolean) => (value: any): boolean => {
|
|
27
|
+
return validator(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useValidation() {
|
|
31
|
+
return {
|
|
32
|
+
required,
|
|
33
|
+
email,
|
|
34
|
+
minLength,
|
|
35
|
+
maxLength,
|
|
36
|
+
pattern,
|
|
37
|
+
custom
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Components - Form
|
|
2
|
+
export { default as DatametriaButton } from './components/DatametriaButton.vue'
|
|
3
|
+
export { default as DatametriaInput } from './components/DatametriaInput.vue'
|
|
4
|
+
export { default as DatametriaSelect } from './components/DatametriaSelect.vue'
|
|
5
|
+
export { default as DatametriaCheckbox } from './components/DatametriaCheckbox.vue'
|
|
6
|
+
export { default as DatametriaRadio } from './components/DatametriaRadio.vue'
|
|
7
|
+
export { default as DatametriaSwitch } from './components/DatametriaSwitch.vue'
|
|
8
|
+
export { default as DatametriaTextarea } from './components/DatametriaTextarea.vue'
|
|
9
|
+
export { default as DatametriaDatePicker } from './components/DatametriaDatePicker.vue'
|
|
10
|
+
export { default as DatametriaFileUpload } from './components/DatametriaFileUpload.vue'
|
|
11
|
+
export { default as DatametriaAutocomplete } from './components/DatametriaAutocomplete.vue'
|
|
12
|
+
|
|
13
|
+
// Components - Layout
|
|
14
|
+
export { default as DatametriaCard } from './components/DatametriaCard.vue'
|
|
15
|
+
export { default as DatametriaModal } from './components/DatametriaModal.vue'
|
|
16
|
+
export { default as DatametriaContainer } from './components/DatametriaContainer.vue'
|
|
17
|
+
export { default as DatametriaGrid } from './components/DatametriaGrid.vue'
|
|
18
|
+
export { default as DatametriaDivider } from './components/DatametriaDivider.vue'
|
|
19
|
+
|
|
20
|
+
// Components - Feedback
|
|
21
|
+
export { default as DatametriaAlert } from './components/DatametriaAlert.vue'
|
|
22
|
+
export { default as DatametriaToast } from './components/DatametriaToast.vue'
|
|
23
|
+
export { default as DatametriaProgress } from './components/DatametriaProgress.vue'
|
|
24
|
+
export { default as DatametriaSpinner } from './components/DatametriaSpinner.vue'
|
|
25
|
+
|
|
26
|
+
// Components - Data Display
|
|
27
|
+
export { default as DatametriaTable } from './components/DatametriaTable.vue'
|
|
28
|
+
export { default as DatametriaAvatar } from './components/DatametriaAvatar.vue'
|
|
29
|
+
export { default as DatametriaBadge } from './components/DatametriaBadge.vue'
|
|
30
|
+
export { default as DatametriaChip } from './components/DatametriaChip.vue'
|
|
31
|
+
|
|
32
|
+
// Components - Navigation
|
|
33
|
+
export { default as DatametriaNavbar } from './components/DatametriaNavbar.vue'
|
|
34
|
+
export { default as DatametriaBreadcrumb } from './components/DatametriaBreadcrumb.vue'
|
|
35
|
+
export { default as DatametriaTabs } from './components/DatametriaTabs.vue'
|
|
36
|
+
|
|
37
|
+
// Composables - Core
|
|
38
|
+
export { useTheme } from './composables/useTheme'
|
|
39
|
+
export { useValidation, required, email, minLength, maxLength, pattern, custom } from './composables/useValidation'
|
|
40
|
+
export { useAPI } from './composables/useAPI'
|
|
41
|
+
|
|
42
|
+
// Composables - Utility
|
|
43
|
+
export { useLocalStorage } from './composables/useLocalStorage'
|
|
44
|
+
export { useDebounce } from './composables/useDebounce'
|
|
45
|
+
export { useClipboard } from './composables/useClipboard'
|
|
46
|
+
|
|
47
|
+
// Types
|
|
48
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'outline'
|
|
49
|
+
export type ButtonSize = 'sm' | 'md' | 'lg'
|
|
50
|
+
export type AlertVariant = 'info' | 'success' | 'warning' | 'error'
|
|
51
|
+
export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info'
|
|
52
|
+
export type BadgeSize = 'sm' | 'md' | 'lg'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--dm-primary: #0072CE;
|
|
3
|
+
--dm-secondary: #4B0078;
|
|
4
|
+
--dm-success: #10b981;
|
|
5
|
+
--dm-error: #ef4444;
|
|
6
|
+
--dm-warning: #f59e0b;
|
|
7
|
+
|
|
8
|
+
--dm-gray-50: #f9fafb;
|
|
9
|
+
--dm-gray-100: #f3f4f6;
|
|
10
|
+
--dm-gray-700: #374151;
|
|
11
|
+
--dm-gray-900: #111827;
|
|
12
|
+
|
|
13
|
+
--dm-space-2: 0.5rem;
|
|
14
|
+
--dm-space-3: 0.75rem;
|
|
15
|
+
--dm-space-4: 1rem;
|
|
16
|
+
|
|
17
|
+
--dm-text-sm: 0.875rem;
|
|
18
|
+
--dm-text-base: 1rem;
|
|
19
|
+
--dm-text-lg: 1.125rem;
|
|
20
|
+
|
|
21
|
+
--dm-radius: 0.375rem;
|
|
22
|
+
--dm-transition: 200ms ease-in-out;
|
|
23
|
+
--dm-focus-ring: 0 0 0 3px rgba(0, 114, 206, 0.1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@media (max-width: 640px) {
|
|
27
|
+
:root {
|
|
28
|
+
--dm-text-sm: 0.8rem;
|
|
29
|
+
--dm-text-base: 0.9rem;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export enum ButtonVariant {
|
|
2
|
+
PRIMARY = 'primary',
|
|
3
|
+
SECONDARY = 'secondary',
|
|
4
|
+
OUTLINE = 'outline',
|
|
5
|
+
GHOST = 'ghost'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export enum ButtonSize {
|
|
9
|
+
SM = 'sm',
|
|
10
|
+
MD = 'md',
|
|
11
|
+
LG = 'lg'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ButtonProps {
|
|
15
|
+
variant?: ButtonVariant
|
|
16
|
+
size?: ButtonSize
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
loading?: boolean
|
|
19
|
+
fullWidth?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface InputProps {
|
|
23
|
+
modelValue?: string | number
|
|
24
|
+
label?: string
|
|
25
|
+
placeholder?: string
|
|
26
|
+
errorMessage?: string
|
|
27
|
+
disabled?: boolean
|
|
28
|
+
required?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CardProps {
|
|
32
|
+
title?: string
|
|
33
|
+
padding?: boolean
|
|
34
|
+
}
|