@datametria/vue-components 2.1.1 → 2.3.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.
Files changed (80) hide show
  1. package/README.md +34 -8
  2. package/dist/index.es.js +3520 -2211
  3. package/dist/index.umd.js +10 -10
  4. package/dist/src/components/DatametriaAutocomplete.vue.d.ts +14 -17
  5. package/dist/src/components/DatametriaBreadcrumb.vue.d.ts +39 -7
  6. package/dist/src/components/DatametriaCheckbox.vue.d.ts +35 -6
  7. package/dist/src/components/DatametriaCheckboxGroup.vue.d.ts +30 -0
  8. package/dist/src/components/DatametriaDataTable.vue.d.ts +64 -0
  9. package/dist/src/components/DatametriaDatePicker.vue.d.ts +15 -37
  10. package/dist/src/components/DatametriaDialog.vue.d.ts +71 -0
  11. package/dist/src/components/DatametriaEmpty.vue.d.ts +30 -0
  12. package/dist/src/components/DatametriaFloatingBar.vue.d.ts +2 -2
  13. package/dist/src/components/DatametriaForm.vue.d.ts +40 -0
  14. package/dist/src/components/DatametriaFormItem.vue.d.ts +28 -0
  15. package/dist/src/components/DatametriaGrid.vue.d.ts +1 -1
  16. package/dist/src/components/DatametriaInput.vue.d.ts +69 -10
  17. package/dist/src/components/DatametriaMenu.vue.d.ts +3 -3
  18. package/dist/src/components/DatametriaNavbar.vue.d.ts +2 -2
  19. package/dist/src/components/DatametriaPagination.vue.d.ts +29 -0
  20. package/dist/src/components/DatametriaPopconfirm.vue.d.ts +43 -0
  21. package/dist/src/components/DatametriaProgress.vue.d.ts +33 -8
  22. package/dist/src/components/DatametriaRadio.vue.d.ts +25 -6
  23. package/dist/src/components/DatametriaRadioGroup.vue.d.ts +29 -0
  24. package/dist/src/components/DatametriaResult.vue.d.ts +30 -0
  25. package/dist/src/components/DatametriaSelect.vue.d.ts +16 -11
  26. package/dist/src/components/DatametriaSidebar.vue.d.ts +3 -3
  27. package/dist/src/components/DatametriaSlider.vue.d.ts +3 -3
  28. package/dist/src/components/DatametriaSortableTable.vue.d.ts +1 -1
  29. package/dist/src/components/DatametriaSteps.vue.d.ts +45 -0
  30. package/dist/src/components/DatametriaSwitch.vue.d.ts +9 -4
  31. package/dist/src/components/DatametriaTabPane.vue.d.ts +28 -0
  32. package/dist/src/components/DatametriaTextarea.vue.d.ts +27 -8
  33. package/dist/src/components/DatametriaTimePicker.vue.d.ts +17 -25
  34. package/dist/src/components/DatametriaToast.vue.d.ts +1 -1
  35. package/dist/src/components/DatametriaTooltip.vue.d.ts +1 -1
  36. package/dist/src/components/DatametriaTree.vue.d.ts +31 -0
  37. package/dist/src/components/DatametriaTreeNode.vue.d.ts +17 -0
  38. package/dist/src/components/DatametriaUpload.vue.d.ts +64 -0
  39. package/dist/src/index.d.ts +14 -0
  40. package/dist/src/types/index.d.ts +5 -0
  41. package/dist/vue-components.css +1 -1
  42. package/package.json +4 -3
  43. package/src/components/DatametriaAutocomplete.vue +155 -260
  44. package/src/components/DatametriaBreadcrumb.vue +66 -80
  45. package/src/components/DatametriaCheckbox.vue +150 -37
  46. package/src/components/DatametriaCheckboxGroup.vue +43 -0
  47. package/src/components/DatametriaDataTable.vue +304 -0
  48. package/src/components/DatametriaDatePicker.vue +238 -614
  49. package/src/components/DatametriaDialog.vue +295 -0
  50. package/src/components/DatametriaDropdown.vue +352 -0
  51. package/src/components/DatametriaEmpty.vue +153 -0
  52. package/src/components/DatametriaForm.vue +160 -0
  53. package/src/components/DatametriaFormItem.vue +181 -0
  54. package/src/components/DatametriaInput.vue +226 -63
  55. package/src/components/DatametriaPagination.vue +373 -0
  56. package/src/components/DatametriaPopconfirm.vue +236 -0
  57. package/src/components/DatametriaProgress.vue +176 -63
  58. package/src/components/DatametriaRadio.vue +83 -72
  59. package/src/components/DatametriaRadioGroup.vue +42 -0
  60. package/src/components/DatametriaResult.vue +133 -0
  61. package/src/components/DatametriaSelect.vue +172 -67
  62. package/src/components/DatametriaSortableTable.vue +241 -20
  63. package/src/components/DatametriaSteps.vue +314 -0
  64. package/src/components/DatametriaSwitch.vue +86 -80
  65. package/src/components/DatametriaTabPane.vue +82 -0
  66. package/src/components/DatametriaTextarea.vue +140 -100
  67. package/src/components/DatametriaTimePicker.vue +231 -214
  68. package/src/components/DatametriaTree.vue +124 -0
  69. package/src/components/DatametriaTreeNode.vue +174 -0
  70. package/src/components/DatametriaUpload.vue +365 -0
  71. package/src/index.ts +25 -11
  72. package/src/types/index.ts +2 -0
  73. package/src/components/__tests__/DatametriaAutocomplete.test.ts +0 -180
  74. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +0 -75
  75. package/src/components/__tests__/DatametriaCheckbox.test.ts +0 -47
  76. package/src/components/__tests__/DatametriaDatePicker.test.ts +0 -234
  77. package/src/components/__tests__/DatametriaProgress.test.ts +0 -90
  78. package/src/components/__tests__/DatametriaRadio.test.ts +0 -77
  79. package/src/components/__tests__/DatametriaSwitch.test.ts +0 -64
  80. package/src/components/__tests__/DatametriaTextarea.test.ts +0 -66
@@ -1,33 +1,35 @@
1
1
  <template>
2
- <div class="dm-radio">
3
- <label class="dm-radio__label">
2
+ <label
3
+ class="datametria-radio"
4
+ :class="{
5
+ 'is-checked': isChecked,
6
+ 'is-disabled': disabled
7
+ }"
8
+ >
9
+ <span class="datametria-radio__input">
4
10
  <input
11
+ ref="inputRef"
5
12
  type="radio"
6
- class="dm-radio__input"
7
- :value="value"
8
- :checked="modelValue === value"
13
+ :checked="isChecked"
9
14
  :disabled="disabled"
10
- :name="name"
11
- :aria-label="ariaLabel"
12
- :aria-describedby="error ? `${name}-error` : undefined"
15
+ :value="modelValue"
13
16
  @change="handleChange"
14
17
  />
15
- <span class="dm-radio__checkmark"></span>
16
- <span v-if="label" class="dm-radio__text">{{ label }}</span>
17
- </label>
18
- <p v-if="error" :id="`${name}-error`" class="dm-radio__error">{{ error }}</p>
19
- </div>
18
+ <span class="datametria-radio__inner"></span>
19
+ </span>
20
+ <span v-if="label || $slots.default" class="datametria-radio__label">
21
+ <slot>{{ label }}</slot>
22
+ </span>
23
+ </label>
20
24
  </template>
21
25
 
22
26
  <script setup lang="ts">
27
+ import { ref, computed, inject } from 'vue'
28
+
23
29
  interface Props {
24
30
  modelValue?: string | number | boolean
25
- value: string | number | boolean
26
31
  label?: string
27
- name?: string
28
32
  disabled?: boolean
29
- error?: string
30
- ariaLabel?: string
31
33
  }
32
34
 
33
35
  const props = withDefaults(defineProps<Props>(), {
@@ -36,103 +38,112 @@ const props = withDefaults(defineProps<Props>(), {
36
38
 
37
39
  const emit = defineEmits<{
38
40
  'update:modelValue': [value: string | number | boolean]
41
+ change: [value: string | number | boolean]
39
42
  }>()
40
43
 
44
+ const inputRef = ref<HTMLInputElement>()
45
+ const radioGroup = inject<any>('radioGroup', null)
46
+
47
+ const isChecked = computed(() => {
48
+ if (radioGroup) {
49
+ return radioGroup.modelValue.value === props.modelValue
50
+ }
51
+ return false
52
+ })
53
+
41
54
  const handleChange = () => {
42
- if (!props.disabled) {
43
- emit('update:modelValue', props.value)
55
+ if (props.disabled) return
56
+
57
+ if (radioGroup) {
58
+ radioGroup.updateValue(props.modelValue)
44
59
  }
60
+
61
+ emit('update:modelValue', props.modelValue!)
62
+ emit('change', props.modelValue!)
45
63
  }
64
+
65
+ defineExpose({
66
+ inputRef
67
+ })
46
68
  </script>
47
69
 
48
70
  <style scoped>
49
- .dm-radio {
50
- display: flex;
51
- flex-direction: column;
52
- gap: var(--dm-spacing-1, 0.25rem);
53
- }
54
-
55
- .dm-radio__label {
71
+ .datametria-radio {
56
72
  display: inline-flex;
57
73
  align-items: center;
58
- gap: var(--dm-spacing-2, 0.5rem);
59
74
  cursor: pointer;
60
- min-height: 44px;
61
- padding: var(--dm-spacing-2, 0.5rem);
62
75
  user-select: none;
76
+ font-size: var(--dm-font-size-base, 14px);
77
+ color: var(--dm-text-primary, #333);
63
78
  }
64
79
 
65
- .dm-radio__label:has(.dm-radio__input:disabled) {
80
+ .datametria-radio.is-disabled {
66
81
  cursor: not-allowed;
67
82
  opacity: 0.5;
68
83
  }
69
84
 
70
- .dm-radio__input {
85
+ .datametria-radio__input {
86
+ position: relative;
87
+ display: inline-block;
88
+ width: 16px;
89
+ height: 16px;
90
+ }
91
+
92
+ .datametria-radio__input input {
71
93
  position: absolute;
72
94
  opacity: 0;
73
- width: 0;
74
- height: 0;
95
+ width: 100%;
96
+ height: 100%;
97
+ cursor: pointer;
98
+ z-index: 1;
75
99
  }
76
100
 
77
- .dm-radio__checkmark {
78
- position: relative;
79
- width: 20px;
80
- height: 20px;
81
- border: 2px solid var(--dm-neutral-400, #9ca3af);
101
+ .datametria-radio__input input:disabled {
102
+ cursor: not-allowed;
103
+ }
104
+
105
+ .datametria-radio__inner {
106
+ position: absolute;
107
+ top: 0;
108
+ left: 0;
109
+ width: 16px;
110
+ height: 16px;
111
+ border: 1px solid var(--dm-border-color, #dcdfe6);
82
112
  border-radius: 50%;
83
- background: white;
113
+ background-color: var(--dm-bg-color, #fff);
84
114
  transition: all 0.2s;
85
- flex-shrink: 0;
86
115
  }
87
116
 
88
- .dm-radio__input:checked + .dm-radio__checkmark {
89
- border-color: var(--dm-primary, #0072CE);
90
- background: var(--dm-primary, #0072CE);
117
+ .datametria-radio.is-checked .datametria-radio__inner {
118
+ border-color: var(--dm-color-primary, #0072ce);
91
119
  }
92
120
 
93
- .dm-radio__input:checked + .dm-radio__checkmark::after {
121
+ .datametria-radio.is-checked .datametria-radio__inner::after {
94
122
  content: '';
95
123
  position: absolute;
96
124
  top: 50%;
97
125
  left: 50%;
98
- transform: translate(-50%, -50%);
99
126
  width: 8px;
100
127
  height: 8px;
101
128
  border-radius: 50%;
102
- background: white;
103
- }
104
-
105
- .dm-radio__input:focus-visible + .dm-radio__checkmark {
106
- outline: 2px solid var(--dm-primary, #0072CE);
107
- outline-offset: 2px;
108
- }
109
-
110
- .dm-radio__input:disabled + .dm-radio__checkmark {
111
- background: var(--dm-neutral-100, #f3f4f6);
112
- border-color: var(--dm-neutral-300, #d1d5db);
113
- }
114
-
115
- .dm-radio__text {
116
- color: var(--dm-neutral-900, #111827);
117
- font-size: var(--dm-font-size-base, 1rem);
118
- line-height: 1.5;
129
+ background-color: var(--dm-color-primary, #0072ce);
130
+ transform: translate(-50%, -50%);
119
131
  }
120
132
 
121
- .dm-radio__error {
122
- color: var(--dm-error, #ef4444);
123
- font-size: var(--dm-font-size-sm, 0.875rem);
124
- margin: 0;
133
+ .datametria-radio__label {
134
+ margin-left: 8px;
135
+ line-height: 1;
125
136
  }
126
137
 
138
+ /* Dark mode */
127
139
  @media (prefers-color-scheme: dark) {
128
- .dm-radio__checkmark {
129
- background: var(--dm-neutral-800, #1f2937);
130
- border-color: var(--dm-neutral-600, #4b5563);
140
+ .datametria-radio {
141
+ color: var(--dm-text-primary-dark, #e0e0e0);
131
142
  }
132
143
 
133
- .dm-radio__input:disabled + .dm-radio__checkmark {
134
- background: var(--dm-neutral-900, #111827);
135
- border-color: var(--dm-neutral-700, #374151);
144
+ .datametria-radio__inner {
145
+ border-color: var(--dm-border-color-dark, #4a4a4a);
146
+ background-color: var(--dm-bg-color-dark, #1e1e1e);
136
147
  }
137
148
  }
138
149
  </style>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="datametria-radio-group" role="radiogroup">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { provide, computed } from 'vue'
9
+
10
+ interface Props {
11
+ modelValue?: string | number | boolean
12
+ disabled?: boolean
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ disabled: false
17
+ })
18
+
19
+ const emit = defineEmits<{
20
+ 'update:modelValue': [value: string | number | boolean]
21
+ change: [value: string | number | boolean]
22
+ }>()
23
+
24
+ const updateValue = (value: string | number | boolean) => {
25
+ emit('update:modelValue', value)
26
+ emit('change', value)
27
+ }
28
+
29
+ provide('radioGroup', {
30
+ modelValue: computed(() => props.modelValue),
31
+ disabled: computed(() => props.disabled),
32
+ updateValue
33
+ })
34
+ </script>
35
+
36
+ <style scoped>
37
+ .datametria-radio-group {
38
+ display: inline-flex;
39
+ flex-wrap: wrap;
40
+ gap: 12px;
41
+ }
42
+ </style>
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <div class="datametria-result" :class="classes">
3
+ <div class="datametria-result__icon">
4
+ <slot name="icon">
5
+ <svg v-if="status === 'success'" viewBox="0 0 48 48" fill="none">
6
+ <path d="M24 4C12.96 4 4 12.96 4 24s8.96 20 20 20 20-8.96 20-20S35.04 4 24 4zm-4 30L10 24l2.83-2.83L20 28.34l15.17-15.17L38 16 20 34z" fill="currentColor"/>
7
+ </svg>
8
+ <svg v-else-if="status === 'warning'" viewBox="0 0 48 48" fill="none">
9
+ <path d="M2 42h44L24 4 2 42zm24-6h-4v-4h4v4zm0-8h-4v-8h4v8z" fill="currentColor"/>
10
+ </svg>
11
+ <svg v-else-if="status === 'error'" viewBox="0 0 48 48" fill="none">
12
+ <path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm10 27.17L31.17 34 24 26.83 16.83 34 14 31.17 21.17 24 14 16.83 16.83 14 24 21.17 31.17 14 34 16.83 26.83 24 34 31.17z" fill="currentColor"/>
13
+ </svg>
14
+ <svg v-else viewBox="0 0 48 48" fill="none">
15
+ <path d="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm2 30h-4v-4h4v4zm0-8h-4V14h4v12z" fill="currentColor"/>
16
+ </svg>
17
+ </slot>
18
+ </div>
19
+
20
+ <div class="datametria-result__title">
21
+ <slot name="title">
22
+ <p>{{ title }}</p>
23
+ </slot>
24
+ </div>
25
+
26
+ <div v-if="subtitle || $slots.subtitle" class="datametria-result__subtitle">
27
+ <slot name="subtitle">
28
+ <p>{{ subtitle }}</p>
29
+ </slot>
30
+ </div>
31
+
32
+ <div v-if="$slots.extra" class="datametria-result__extra">
33
+ <slot name="extra" />
34
+ </div>
35
+ </div>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { computed } from 'vue'
40
+
41
+ type ResultStatus = 'success' | 'warning' | 'error' | 'info'
42
+
43
+ interface Props {
44
+ status?: ResultStatus
45
+ title?: string
46
+ subtitle?: string
47
+ }
48
+
49
+ const props = withDefaults(defineProps<Props>(), {
50
+ status: 'info',
51
+ title: '',
52
+ subtitle: ''
53
+ })
54
+
55
+ const classes = computed(() => ({
56
+ [`datametria-result--${props.status}`]: true
57
+ }))
58
+ </script>
59
+
60
+ <style scoped>
61
+ .datametria-result {
62
+ display: flex;
63
+ flex-direction: column;
64
+ align-items: center;
65
+ justify-content: center;
66
+ padding: 48px 32px;
67
+ text-align: center;
68
+ }
69
+
70
+ .datametria-result__icon {
71
+ width: 72px;
72
+ height: 72px;
73
+ margin-bottom: 24px;
74
+ }
75
+
76
+ .datametria-result__icon svg {
77
+ width: 100%;
78
+ height: 100%;
79
+ }
80
+
81
+ .datametria-result--success .datametria-result__icon {
82
+ color: var(--datametria-success-color, #67c23a);
83
+ }
84
+
85
+ .datametria-result--warning .datametria-result__icon {
86
+ color: var(--datametria-warning-color, #e6a23c);
87
+ }
88
+
89
+ .datametria-result--error .datametria-result__icon {
90
+ color: var(--datametria-error-color, #f56c6c);
91
+ }
92
+
93
+ .datametria-result--info .datametria-result__icon {
94
+ color: var(--datametria-info-color, #909399);
95
+ }
96
+
97
+ .datametria-result__title {
98
+ margin-bottom: 12px;
99
+ font-size: 20px;
100
+ font-weight: 500;
101
+ color: var(--datametria-text-primary, #303133);
102
+ line-height: 1.3;
103
+ }
104
+
105
+ .datametria-result__title p {
106
+ margin: 0;
107
+ }
108
+
109
+ .datametria-result__subtitle {
110
+ margin-bottom: 24px;
111
+ font-size: 14px;
112
+ color: var(--datametria-text-secondary, #606266);
113
+ line-height: 1.5;
114
+ }
115
+
116
+ .datametria-result__subtitle p {
117
+ margin: 0;
118
+ }
119
+
120
+ .datametria-result__extra {
121
+ margin-top: 8px;
122
+ }
123
+
124
+ @media (prefers-color-scheme: dark) {
125
+ .datametria-result__title {
126
+ color: var(--datametria-text-primary, #e5eaf3);
127
+ }
128
+
129
+ .datametria-result__subtitle {
130
+ color: var(--datametria-text-secondary, #a3a6ad);
131
+ }
132
+ }
133
+ </style>
@@ -1,112 +1,217 @@
1
1
  <template>
2
- <div class="datametria-select">
3
- <label v-if="label" :for="selectId" class="datametria-select__label">
4
- {{ label }}
5
- <span v-if="required" class="datametria-select__required">*</span>
6
- </label>
2
+ <div class="datametria-select" :class="{ 'is-disabled': disabled }">
3
+ <div class="datametria-select__trigger" @click="toggleDropdown">
4
+ <span v-if="displayValue" class="datametria-select__value">{{ displayValue }}</span>
5
+ <span v-else class="datametria-select__placeholder">{{ placeholder }}</span>
6
+ <span class="datametria-select__arrow">▼</span>
7
+ </div>
7
8
 
8
- <select
9
- :id="selectId"
10
- :value="modelValue"
11
- :disabled="disabled"
12
- :required="required"
13
- :class="selectClasses"
14
- @change="$emit('update:modelValue', ($event.target as HTMLSelectElement).value)"
15
- >
16
- <option v-if="placeholder" value="" disabled>{{ placeholder }}</option>
17
- <option v-for="option in options" :key="option.value" :value="option.value">
18
- {{ option.label }}
19
- </option>
20
- </select>
21
-
22
- <p v-if="errorMessage" class="datametria-select__error">{{ errorMessage }}</p>
9
+ <Teleport to="body">
10
+ <div v-if="isOpen" class="datametria-select__dropdown" :style="dropdownStyle">
11
+ <input
12
+ v-if="filterable"
13
+ v-model="filterText"
14
+ class="datametria-select__filter"
15
+ placeholder="Buscar..."
16
+ @click.stop
17
+ />
18
+ <div class="datametria-select__options">
19
+ <div
20
+ v-for="option in filteredOptions"
21
+ :key="option.value"
22
+ class="datametria-select__option"
23
+ :class="{ 'is-selected': isSelected(option.value) }"
24
+ @click="handleSelect(option)"
25
+ >
26
+ {{ option.label }}
27
+ </div>
28
+ <div v-if="filteredOptions.length === 0" class="datametria-select__empty">
29
+ Nenhum resultado
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </Teleport>
23
34
  </div>
24
35
  </template>
25
36
 
26
37
  <script setup lang="ts">
27
- import { computed } from 'vue'
38
+ import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
28
39
 
29
- interface Option {
30
- value: string | number
40
+ interface SelectOption {
31
41
  label: string
42
+ value: any
43
+ disabled?: boolean
32
44
  }
33
45
 
34
46
  interface Props {
35
- modelValue?: string | number
36
- options: Option[]
37
- label?: string
47
+ modelValue: any | any[]
48
+ options: SelectOption[]
38
49
  placeholder?: string
39
- errorMessage?: string
40
50
  disabled?: boolean
41
- required?: boolean
51
+ clearable?: boolean
52
+ filterable?: boolean
53
+ multiple?: boolean
42
54
  }
43
55
 
44
56
  const props = withDefaults(defineProps<Props>(), {
45
- modelValue: '',
57
+ placeholder: 'Selecione',
46
58
  disabled: false,
47
- required: false
59
+ clearable: false,
60
+ filterable: false,
61
+ multiple: false
48
62
  })
49
63
 
50
- defineEmits<{
51
- 'update:modelValue': [value: string]
64
+ const emit = defineEmits<{
65
+ 'update:modelValue': [value: any]
66
+ 'change': [value: any]
52
67
  }>()
53
68
 
54
- const selectId = computed(() => `select-${Math.random().toString(36).substr(2, 9)}`)
69
+ const isOpen = ref(false)
70
+ const filterText = ref('')
71
+ const dropdownStyle = ref({})
72
+
73
+ const displayValue = computed(() => {
74
+ if (props.multiple && Array.isArray(props.modelValue)) {
75
+ const labels = props.modelValue.map(v =>
76
+ props.options.find(o => o.value === v)?.label
77
+ ).filter(Boolean)
78
+ return labels.join(', ')
79
+ }
80
+ return props.options.find(o => o.value === props.modelValue)?.label || ''
81
+ })
82
+
83
+ const filteredOptions = computed(() => {
84
+ if (!props.filterable || !filterText.value) return props.options
85
+ return props.options.filter(o =>
86
+ o.label.toLowerCase().includes(filterText.value.toLowerCase())
87
+ )
88
+ })
89
+
90
+ const isSelected = (value: any) => {
91
+ if (props.multiple && Array.isArray(props.modelValue)) {
92
+ return props.modelValue.includes(value)
93
+ }
94
+ return props.modelValue === value
95
+ }
55
96
 
56
- const selectClasses = computed(() => [
57
- 'datametria-select__field',
58
- {
59
- 'datametria-select__field--error': props.errorMessage,
60
- 'datametria-select__field--disabled': props.disabled
97
+ const toggleDropdown = () => {
98
+ if (!props.disabled) {
99
+ isOpen.value = !isOpen.value
61
100
  }
62
- ])
101
+ }
102
+
103
+ const handleSelect = (option: SelectOption) => {
104
+ if (props.multiple) {
105
+ const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
106
+ const index = current.indexOf(option.value)
107
+ if (index > -1) {
108
+ current.splice(index, 1)
109
+ } else {
110
+ current.push(option.value)
111
+ }
112
+ emit('update:modelValue', current)
113
+ emit('change', current)
114
+ } else {
115
+ emit('update:modelValue', option.value)
116
+ emit('change', option.value)
117
+ isOpen.value = false
118
+ }
119
+ }
120
+
121
+ const handleClickOutside = (e: MouseEvent) => {
122
+ const target = e.target as HTMLElement
123
+ if (!target.closest('.datametria-select')) {
124
+ isOpen.value = false
125
+ }
126
+ }
127
+
128
+ onMounted(() => {
129
+ document.addEventListener('click', handleClickOutside)
130
+ })
131
+
132
+ onUnmounted(() => {
133
+ document.removeEventListener('click', handleClickOutside)
134
+ })
63
135
  </script>
64
136
 
65
137
  <style scoped>
66
138
  .datametria-select {
139
+ position: relative;
140
+ width: 100%;
141
+ }
142
+
143
+ .datametria-select__trigger {
67
144
  display: flex;
68
- flex-direction: column;
69
- gap: var(--dm-spacing-2, 0.5rem);
145
+ align-items: center;
146
+ justify-content: space-between;
147
+ padding: 8px 12px;
148
+ border: 1px solid var(--color-border, #d1d5db);
149
+ border-radius: 6px;
150
+ background: var(--color-background, #fff);
151
+ cursor: pointer;
152
+ transition: all 0.2s;
70
153
  }
71
154
 
72
- .datametria-select__label {
73
- font-size: var(--dm-font-size-sm, 0.875rem);
74
- font-weight: var(--dm-font-weight-medium, 500);
75
- color: var(--dm-neutral-700, #374151);
155
+ .datametria-select__trigger:hover {
156
+ border-color: var(--color-primary, #3b82f6);
76
157
  }
77
158
 
78
- .datametria-select__required {
79
- color: var(--dm-error, #ef4444);
159
+ .is-disabled .datametria-select__trigger {
160
+ opacity: 0.6;
161
+ cursor: not-allowed;
80
162
  }
81
163
 
82
- .datametria-select__field {
83
- padding: var(--dm-spacing-3, 0.75rem);
84
- border: 1px solid var(--dm-neutral-300, #d1d5db);
85
- border-radius: var(--dm-radius-md, 0.375rem);
86
- font-size: var(--dm-font-size-base, 1rem);
87
- background: white;
88
- cursor: pointer;
89
- transition: all 0.2s;
164
+ .datametria-select__placeholder {
165
+ color: var(--color-text-secondary, #9ca3af);
90
166
  }
91
167
 
92
- .datametria-select__field:focus {
168
+ .datametria-select__arrow {
169
+ font-size: 12px;
170
+ transition: transform 0.2s;
171
+ }
172
+
173
+ .datametria-select__dropdown {
174
+ position: fixed;
175
+ z-index: 2000;
176
+ background: var(--color-background, #fff);
177
+ border: 1px solid var(--color-border, #d1d5db);
178
+ border-radius: 6px;
179
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
180
+ max-height: 300px;
181
+ overflow: hidden;
182
+ }
183
+
184
+ .datametria-select__filter {
185
+ width: 100%;
186
+ padding: 8px 12px;
187
+ border: none;
188
+ border-bottom: 1px solid var(--color-border, #d1d5db);
93
189
  outline: none;
94
- border-color: var(--dm-primary, #0072CE);
95
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 10%, transparent);
96
190
  }
97
191
 
98
- .datametria-select__field--error {
99
- border-color: var(--dm-error, #ef4444);
192
+ .datametria-select__options {
193
+ max-height: 250px;
194
+ overflow-y: auto;
100
195
  }
101
196
 
102
- .datametria-select__field--disabled {
103
- background: var(--dm-neutral-100, #f3f4f6);
104
- cursor: not-allowed;
197
+ .datametria-select__option {
198
+ padding: 8px 12px;
199
+ cursor: pointer;
200
+ transition: background 0.2s;
201
+ }
202
+
203
+ .datametria-select__option:hover {
204
+ background: var(--color-background-hover, #f3f4f6);
205
+ }
206
+
207
+ .datametria-select__option.is-selected {
208
+ background: var(--color-primary-light, #dbeafe);
209
+ color: var(--color-primary, #3b82f6);
105
210
  }
106
211
 
107
- .datametria-select__error {
108
- font-size: var(--dm-font-size-sm, 0.875rem);
109
- color: var(--dm-error, #ef4444);
110
- margin: 0;
212
+ .datametria-select__empty {
213
+ padding: 16px;
214
+ text-align: center;
215
+ color: var(--color-text-secondary, #9ca3af);
111
216
  }
112
217
  </style>