@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.
Files changed (44) hide show
  1. package/ACCESSIBILITY.md +78 -0
  2. package/DESIGN-SYSTEM.md +70 -0
  3. package/LICENSE +21 -0
  4. package/PROGRESS.md +327 -0
  5. package/README.md +473 -0
  6. package/dist/index.es.js +1405 -0
  7. package/dist/index.umd.js +1 -0
  8. package/dist/vue-components.css +1 -0
  9. package/package.json +98 -0
  10. package/src/components/DatametriaAlert.vue +123 -0
  11. package/src/components/DatametriaAutocomplete.vue +292 -0
  12. package/src/components/DatametriaAvatar.vue +99 -0
  13. package/src/components/DatametriaBadge.vue +90 -0
  14. package/src/components/DatametriaBreadcrumb.vue +144 -0
  15. package/src/components/DatametriaButton.vue +157 -0
  16. package/src/components/DatametriaCard.vue +72 -0
  17. package/src/components/DatametriaCheckbox.vue +82 -0
  18. package/src/components/DatametriaChip.vue +149 -0
  19. package/src/components/DatametriaContainer.vue +57 -0
  20. package/src/components/DatametriaDatePicker.vue +140 -0
  21. package/src/components/DatametriaDivider.vue +100 -0
  22. package/src/components/DatametriaFileUpload.vue +268 -0
  23. package/src/components/DatametriaGrid.vue +44 -0
  24. package/src/components/DatametriaInput.vue +102 -0
  25. package/src/components/DatametriaModal.vue +135 -0
  26. package/src/components/DatametriaNavbar.vue +227 -0
  27. package/src/components/DatametriaProgress.vue +113 -0
  28. package/src/components/DatametriaRadio.vue +138 -0
  29. package/src/components/DatametriaSelect.vue +112 -0
  30. package/src/components/DatametriaSpinner.vue +112 -0
  31. package/src/components/DatametriaSwitch.vue +137 -0
  32. package/src/components/DatametriaTable.vue +105 -0
  33. package/src/components/DatametriaTabs.vue +180 -0
  34. package/src/components/DatametriaTextarea.vue +159 -0
  35. package/src/components/DatametriaToast.vue +163 -0
  36. package/src/composables/useAPI.ts +78 -0
  37. package/src/composables/useClipboard.ts +42 -0
  38. package/src/composables/useDebounce.ts +16 -0
  39. package/src/composables/useLocalStorage.ts +26 -0
  40. package/src/composables/useTheme.ts +66 -0
  41. package/src/composables/useValidation.ts +39 -0
  42. package/src/index.ts +52 -0
  43. package/src/styles/design-tokens.css +31 -0
  44. package/src/types/index.ts +34 -0
@@ -0,0 +1,157 @@
1
+ <template>
2
+ <button
3
+ :class="buttonClasses"
4
+ :disabled="disabled || loading"
5
+ :type="type"
6
+ :aria-busy="loading"
7
+ :aria-disabled="disabled"
8
+ @click="$emit('click', $event)"
9
+ >
10
+ <span v-if="loading" class="spinner" role="status" aria-label="Carregando"></span>
11
+ <slot />
12
+ </button>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { computed } from 'vue'
17
+ import { ButtonVariant, ButtonSize } from '../types'
18
+
19
+ interface Props {
20
+ variant?: ButtonVariant
21
+ size?: ButtonSize
22
+ disabled?: boolean
23
+ loading?: boolean
24
+ fullWidth?: boolean
25
+ type?: 'button' | 'submit' | 'reset'
26
+ }
27
+
28
+ const props = withDefaults(defineProps<Props>(), {
29
+ variant: ButtonVariant.PRIMARY,
30
+ size: ButtonSize.MD,
31
+ disabled: false,
32
+ loading: false,
33
+ fullWidth: false,
34
+ type: 'button'
35
+ })
36
+
37
+ defineEmits<{
38
+ click: [event: MouseEvent]
39
+ }>()
40
+
41
+ const buttonClasses = computed(() => {
42
+ return [
43
+ 'datametria-button',
44
+ `datametria-button--${props.variant}`,
45
+ `datametria-button--${props.size}`,
46
+ {
47
+ 'datametria-button--full-width': props.fullWidth,
48
+ 'datametria-button--loading': props.loading,
49
+ 'datametria-button--disabled': props.disabled
50
+ }
51
+ ]
52
+ })
53
+ </script>
54
+
55
+ <style scoped>
56
+ .datametria-button {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ font-weight: 500;
61
+ border-radius: var(--dm-radius);
62
+ transition: var(--dm-transition);
63
+ cursor: pointer;
64
+ border: 1px solid transparent;
65
+ font-family: var(--dm-font-sans, -apple-system, sans-serif);
66
+ touch-action: manipulation;
67
+ -webkit-tap-highlight-color: transparent;
68
+ }
69
+
70
+ .datametria-button--primary {
71
+ background: var(--dm-primary);
72
+ color: white;
73
+ }
74
+
75
+ .datametria-button--primary:hover:not(:disabled) {
76
+ background: #005ba3;
77
+ }
78
+
79
+ .datametria-button--primary:focus-visible {
80
+ outline: none;
81
+ box-shadow: var(--dm-focus-ring);
82
+ }
83
+
84
+ .datametria-button--secondary {
85
+ background: var(--dm-secondary);
86
+ color: white;
87
+ }
88
+
89
+ .datametria-button--secondary:focus-visible {
90
+ outline: none;
91
+ box-shadow: 0 0 0 3px rgba(75, 0, 120, 0.1);
92
+ }
93
+
94
+ .datametria-button--outline {
95
+ background: transparent;
96
+ border-color: var(--dm-primary);
97
+ color: var(--dm-primary);
98
+ }
99
+
100
+ .datametria-button--outline:focus-visible {
101
+ outline: none;
102
+ box-shadow: var(--dm-focus-ring);
103
+ }
104
+
105
+ .datametria-button--ghost {
106
+ background: transparent;
107
+ color: #0072CE;
108
+ }
109
+
110
+ .datametria-button--sm {
111
+ padding: var(--dm-space-2) var(--dm-space-4);
112
+ font-size: var(--dm-text-sm);
113
+ min-height: 2rem;
114
+ }
115
+
116
+ .datametria-button--md {
117
+ padding: var(--dm-space-3) calc(var(--dm-space-4) * 1.5);
118
+ font-size: var(--dm-text-base);
119
+ min-height: 2.5rem;
120
+ }
121
+
122
+ .datametria-button--lg {
123
+ padding: var(--dm-space-4) calc(var(--dm-space-4) * 2);
124
+ font-size: var(--dm-text-lg);
125
+ min-height: 3rem;
126
+ }
127
+
128
+ @media (max-width: 640px) {
129
+ .datametria-button--sm { min-height: 2.25rem; }
130
+ .datametria-button--md { min-height: 2.75rem; }
131
+ .datametria-button--lg { min-height: 3.25rem; }
132
+ }
133
+
134
+ .datametria-button--full-width {
135
+ width: 100%;
136
+ }
137
+
138
+ .datametria-button--disabled,
139
+ .datametria-button:disabled {
140
+ opacity: 0.5;
141
+ cursor: not-allowed;
142
+ }
143
+
144
+ .spinner {
145
+ width: 1rem;
146
+ height: 1rem;
147
+ border: 2px solid currentColor;
148
+ border-top-color: transparent;
149
+ border-radius: 50%;
150
+ animation: spin 0.6s linear infinite;
151
+ margin-right: 0.5rem;
152
+ }
153
+
154
+ @keyframes spin {
155
+ to { transform: rotate(360deg); }
156
+ }
157
+ </style>
@@ -0,0 +1,72 @@
1
+ <template>
2
+ <div :class="cardClasses">
3
+ <div v-if="title || $slots.header" class="datametria-card__header">
4
+ <slot name="header">
5
+ <h3 class="datametria-card__title">{{ title }}</h3>
6
+ </slot>
7
+ </div>
8
+
9
+ <div class="datametria-card__content">
10
+ <slot />
11
+ </div>
12
+
13
+ <div v-if="$slots.footer" class="datametria-card__footer">
14
+ <slot name="footer" />
15
+ </div>
16
+ </div>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { computed } from 'vue'
21
+
22
+ interface Props {
23
+ title?: string
24
+ padding?: boolean
25
+ }
26
+
27
+ const props = withDefaults(defineProps<Props>(), {
28
+ padding: true
29
+ })
30
+
31
+ const cardClasses = computed(() => [
32
+ 'datametria-card',
33
+ {
34
+ 'datametria-card--no-padding': !props.padding
35
+ }
36
+ ])
37
+ </script>
38
+
39
+ <style scoped>
40
+ .datametria-card {
41
+ background: white;
42
+ border-radius: 0.5rem;
43
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
44
+ overflow: hidden;
45
+ }
46
+
47
+ .datametria-card__header {
48
+ padding: 1.5rem;
49
+ border-bottom: 1px solid #e5e7eb;
50
+ }
51
+
52
+ .datametria-card__title {
53
+ margin: 0;
54
+ font-size: 1.25rem;
55
+ font-weight: 600;
56
+ color: #111827;
57
+ }
58
+
59
+ .datametria-card__content {
60
+ padding: 1.5rem;
61
+ }
62
+
63
+ .datametria-card--no-padding .datametria-card__content {
64
+ padding: 0;
65
+ }
66
+
67
+ .datametria-card__footer {
68
+ padding: 1.5rem;
69
+ border-top: 1px solid #e5e7eb;
70
+ background: #f9fafb;
71
+ }
72
+ </style>
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <label class="datametria-checkbox">
3
+ <input
4
+ type="checkbox"
5
+ :checked="modelValue"
6
+ :disabled="disabled"
7
+ class="datametria-checkbox__input"
8
+ @change="$emit('update:modelValue', ($event.target as HTMLInputElement).checked)"
9
+ />
10
+ <span class="datametria-checkbox__checkmark"></span>
11
+ <span v-if="label" class="datametria-checkbox__label">{{ label }}</span>
12
+ </label>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ interface Props {
17
+ modelValue?: boolean
18
+ label?: string
19
+ disabled?: boolean
20
+ }
21
+
22
+ withDefaults(defineProps<Props>(), {
23
+ modelValue: false,
24
+ disabled: false
25
+ })
26
+
27
+ defineEmits<{
28
+ 'update:modelValue': [value: boolean]
29
+ }>()
30
+ </script>
31
+
32
+ <style scoped>
33
+ .datametria-checkbox {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: 0.5rem;
37
+ cursor: pointer;
38
+ user-select: none;
39
+ }
40
+
41
+ .datametria-checkbox__input {
42
+ position: absolute;
43
+ opacity: 0;
44
+ cursor: pointer;
45
+ }
46
+
47
+ .datametria-checkbox__checkmark {
48
+ position: relative;
49
+ width: 1.25rem;
50
+ height: 1.25rem;
51
+ border: 2px solid #d1d5db;
52
+ border-radius: 0.25rem;
53
+ transition: all 0.2s;
54
+ }
55
+
56
+ .datametria-checkbox__input:checked ~ .datametria-checkbox__checkmark {
57
+ background: #0072CE;
58
+ border-color: #0072CE;
59
+ }
60
+
61
+ .datametria-checkbox__input:checked ~ .datametria-checkbox__checkmark::after {
62
+ content: '';
63
+ position: absolute;
64
+ left: 0.375rem;
65
+ top: 0.125rem;
66
+ width: 0.375rem;
67
+ height: 0.625rem;
68
+ border: solid white;
69
+ border-width: 0 2px 2px 0;
70
+ transform: rotate(45deg);
71
+ }
72
+
73
+ .datametria-checkbox__input:disabled ~ .datametria-checkbox__checkmark {
74
+ background: #f3f4f6;
75
+ cursor: not-allowed;
76
+ }
77
+
78
+ .datametria-checkbox__label {
79
+ font-size: 0.875rem;
80
+ color: #374151;
81
+ }
82
+ </style>
@@ -0,0 +1,149 @@
1
+ <template>
2
+ <div
3
+ class="dm-chip"
4
+ :class="[`dm-chip--${variant}`, { 'dm-chip--clickable': clickable }]"
5
+ :role="clickable ? 'button' : undefined"
6
+ :tabindex="clickable ? 0 : undefined"
7
+ @click="handleClick"
8
+ @keydown.enter="handleClick"
9
+ @keydown.space.prevent="handleClick"
10
+ >
11
+ <span v-if="$slots.icon" class="dm-chip__icon">
12
+ <slot name="icon"></slot>
13
+ </span>
14
+ <span class="dm-chip__label">
15
+ <slot>{{ label }}</slot>
16
+ </span>
17
+ <button
18
+ v-if="closable"
19
+ class="dm-chip__close"
20
+ @click.stop="handleClose"
21
+ aria-label="Remover"
22
+ type="button"
23
+ >×</button>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ interface Props {
29
+ label?: string
30
+ variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'default'
31
+ closable?: boolean
32
+ clickable?: boolean
33
+ }
34
+
35
+ withDefaults(defineProps<Props>(), {
36
+ variant: 'default',
37
+ closable: false,
38
+ clickable: false
39
+ })
40
+
41
+ const emit = defineEmits<{
42
+ click: []
43
+ close: []
44
+ }>()
45
+
46
+ const handleClick = () => {
47
+ emit('click')
48
+ }
49
+
50
+ const handleClose = () => {
51
+ emit('close')
52
+ }
53
+ </script>
54
+
55
+ <style scoped>
56
+ .dm-chip {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: var(--dm-space-2);
60
+ padding: var(--dm-space-2) var(--dm-space-3);
61
+ border-radius: 16px;
62
+ font-size: var(--dm-text-sm);
63
+ font-weight: 500;
64
+ user-select: none;
65
+ transition: var(--dm-transition);
66
+ }
67
+
68
+ .dm-chip--clickable {
69
+ cursor: pointer;
70
+ }
71
+
72
+ .dm-chip--clickable:hover {
73
+ opacity: 0.8;
74
+ }
75
+
76
+ .dm-chip--clickable:focus-visible {
77
+ outline: var(--dm-focus-ring);
78
+ outline-offset: 2px;
79
+ }
80
+
81
+ .dm-chip--default {
82
+ background: var(--dm-gray-200);
83
+ color: var(--dm-gray-900);
84
+ }
85
+
86
+ .dm-chip--primary {
87
+ background: var(--dm-primary-light, rgba(0, 114, 206, 0.1));
88
+ color: var(--dm-primary);
89
+ }
90
+
91
+ .dm-chip--secondary {
92
+ background: var(--dm-secondary-light, rgba(75, 0, 120, 0.1));
93
+ color: var(--dm-secondary);
94
+ }
95
+
96
+ .dm-chip--success {
97
+ background: var(--dm-success-light, rgba(34, 197, 94, 0.1));
98
+ color: var(--dm-success);
99
+ }
100
+
101
+ .dm-chip--warning {
102
+ background: var(--dm-warning-light, rgba(251, 191, 36, 0.2));
103
+ color: var(--dm-warning-dark, #92400e);
104
+ }
105
+
106
+ .dm-chip--error {
107
+ background: var(--dm-error-light, rgba(239, 68, 68, 0.1));
108
+ color: var(--dm-error);
109
+ }
110
+
111
+ .dm-chip__icon {
112
+ display: flex;
113
+ align-items: center;
114
+ font-size: 16px;
115
+ }
116
+
117
+ .dm-chip__label {
118
+ line-height: 1.5;
119
+ }
120
+
121
+ .dm-chip__close {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ width: 20px;
126
+ height: 20px;
127
+ border: none;
128
+ background: transparent;
129
+ color: inherit;
130
+ font-size: 20px;
131
+ line-height: 1;
132
+ cursor: pointer;
133
+ opacity: 0.6;
134
+ transition: var(--dm-transition);
135
+ padding: 0;
136
+ margin: 0;
137
+ }
138
+
139
+ .dm-chip__close:hover {
140
+ opacity: 1;
141
+ }
142
+
143
+ @media (prefers-color-scheme: dark) {
144
+ .dm-chip--default {
145
+ background: var(--dm-gray-700);
146
+ color: var(--dm-white);
147
+ }
148
+ }
149
+ </style>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div
3
+ class="dm-container"
4
+ :class="[`dm-container--${size}`, { 'dm-container--fluid': fluid }]"
5
+ >
6
+ <slot></slot>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ interface Props {
12
+ size?: 'sm' | 'md' | 'lg' | 'xl'
13
+ fluid?: boolean
14
+ }
15
+
16
+ withDefaults(defineProps<Props>(), {
17
+ size: 'lg',
18
+ fluid: false
19
+ })
20
+ </script>
21
+
22
+ <style scoped>
23
+ .dm-container {
24
+ width: 100%;
25
+ margin-left: auto;
26
+ margin-right: auto;
27
+ padding-left: var(--dm-space-4);
28
+ padding-right: var(--dm-space-4);
29
+ }
30
+
31
+ .dm-container--sm {
32
+ max-width: 640px;
33
+ }
34
+
35
+ .dm-container--md {
36
+ max-width: 768px;
37
+ }
38
+
39
+ .dm-container--lg {
40
+ max-width: 1024px;
41
+ }
42
+
43
+ .dm-container--xl {
44
+ max-width: 1280px;
45
+ }
46
+
47
+ .dm-container--fluid {
48
+ max-width: 100%;
49
+ }
50
+
51
+ @media (max-width: 640px) {
52
+ .dm-container {
53
+ padding-left: var(--dm-space-3);
54
+ padding-right: var(--dm-space-3);
55
+ }
56
+ }
57
+ </style>
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <div class="dm-datepicker">
3
+ <label v-if="label" :for="inputId" class="dm-datepicker__label">
4
+ {{ label }}
5
+ <span v-if="required" class="dm-datepicker__required">*</span>
6
+ </label>
7
+ <div class="dm-datepicker__wrapper">
8
+ <input
9
+ :id="inputId"
10
+ type="date"
11
+ v-model="internalValue"
12
+ class="dm-datepicker__input"
13
+ :class="{ 'dm-datepicker__input--error': error }"
14
+ :disabled="disabled"
15
+ :required="required"
16
+ :min="min"
17
+ :max="max"
18
+ :aria-label="ariaLabel"
19
+ :aria-describedby="error ? `${inputId}-error` : undefined"
20
+ :aria-invalid="!!error"
21
+ @change="handleChange"
22
+ />
23
+ </div>
24
+ <p v-if="error" :id="`${inputId}-error`" class="dm-datepicker__error">{{ error }}</p>
25
+ </div>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ import { ref, watch } from 'vue'
30
+
31
+ interface Props {
32
+ modelValue?: string
33
+ label?: string
34
+ disabled?: boolean
35
+ required?: boolean
36
+ error?: string
37
+ min?: string
38
+ max?: string
39
+ ariaLabel?: string
40
+ }
41
+
42
+ const props = withDefaults(defineProps<Props>(), {
43
+ modelValue: '',
44
+ disabled: false,
45
+ required: false
46
+ })
47
+
48
+ const emit = defineEmits<{
49
+ 'update:modelValue': [value: string]
50
+ }>()
51
+
52
+ const inputId = `dm-datepicker-${Math.random().toString(36).substr(2, 9)}`
53
+ const internalValue = ref(props.modelValue)
54
+
55
+ watch(() => props.modelValue, (newValue) => {
56
+ internalValue.value = newValue
57
+ })
58
+
59
+ const handleChange = () => {
60
+ emit('update:modelValue', internalValue.value)
61
+ }
62
+ </script>
63
+
64
+ <style scoped>
65
+ .dm-datepicker {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: var(--dm-space-2);
69
+ }
70
+
71
+ .dm-datepicker__label {
72
+ color: var(--dm-text-primary);
73
+ font-size: var(--dm-text-sm);
74
+ font-weight: 500;
75
+ }
76
+
77
+ .dm-datepicker__required {
78
+ color: var(--dm-error);
79
+ }
80
+
81
+ .dm-datepicker__wrapper {
82
+ position: relative;
83
+ }
84
+
85
+ .dm-datepicker__input {
86
+ width: 100%;
87
+ min-height: 44px;
88
+ padding: var(--dm-space-3);
89
+ border: 1px solid var(--dm-gray-300);
90
+ border-radius: var(--dm-radius);
91
+ font-size: var(--dm-text-base);
92
+ color: var(--dm-text-primary);
93
+ background: var(--dm-white);
94
+ transition: var(--dm-transition);
95
+ font-family: inherit;
96
+ }
97
+
98
+ .dm-datepicker__input:hover:not(:disabled) {
99
+ border-color: var(--dm-gray-400);
100
+ }
101
+
102
+ .dm-datepicker__input:focus {
103
+ outline: var(--dm-focus-ring);
104
+ outline-offset: 0;
105
+ border-color: var(--dm-primary);
106
+ }
107
+
108
+ .dm-datepicker__input:disabled {
109
+ background: var(--dm-gray-100);
110
+ cursor: not-allowed;
111
+ opacity: 0.6;
112
+ }
113
+
114
+ .dm-datepicker__input--error {
115
+ border-color: var(--dm-error);
116
+ }
117
+
118
+ .dm-datepicker__input--error:focus {
119
+ outline-color: var(--dm-error);
120
+ }
121
+
122
+ .dm-datepicker__error {
123
+ color: var(--dm-error);
124
+ font-size: var(--dm-text-sm);
125
+ margin: 0;
126
+ }
127
+
128
+ @media (prefers-color-scheme: dark) {
129
+ .dm-datepicker__input {
130
+ background: var(--dm-gray-800);
131
+ border-color: var(--dm-gray-600);
132
+ color: var(--dm-white);
133
+ color-scheme: dark;
134
+ }
135
+
136
+ .dm-datepicker__input:disabled {
137
+ background: var(--dm-gray-900);
138
+ }
139
+ }
140
+ </style>