@datametria/vue-components 1.2.0 → 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.
Files changed (103) hide show
  1. package/README.md +554 -657
  2. package/dist/index.es.js +2570 -1433
  3. package/dist/index.umd.js +10 -10
  4. package/dist/vue-components.css +1 -1
  5. package/package.json +102 -98
  6. package/src/components/DatametriaAlert.vue +137 -137
  7. package/src/components/DatametriaAutocomplete.vue +184 -138
  8. package/src/components/DatametriaAvatar.vue +177 -33
  9. package/src/components/DatametriaBadge.vue +98 -98
  10. package/src/components/DatametriaBreadcrumb.vue +21 -21
  11. package/src/components/DatametriaButton.vue +177 -165
  12. package/src/components/DatametriaCard.vue +12 -12
  13. package/src/components/DatametriaCheckbox.vue +8 -8
  14. package/src/components/DatametriaChip.vue +145 -149
  15. package/src/components/DatametriaContainer.vue +4 -4
  16. package/src/components/DatametriaDatePicker.vue +686 -68
  17. package/src/components/DatametriaDivider.vue +13 -13
  18. package/src/components/DatametriaFileUpload.vue +272 -140
  19. package/src/components/DatametriaFloatingBar.vue +126 -0
  20. package/src/components/DatametriaGrid.vue +3 -3
  21. package/src/components/DatametriaInput.vue +15 -15
  22. package/src/components/DatametriaMenu.vue +604 -619
  23. package/src/components/DatametriaModal.vue +16 -16
  24. package/src/components/DatametriaNavbar.vue +230 -252
  25. package/src/components/DatametriaPasswordInput.vue +430 -0
  26. package/src/components/DatametriaProgress.vue +18 -18
  27. package/src/components/DatametriaRadio.vue +20 -20
  28. package/src/components/DatametriaSelect.vue +15 -15
  29. package/src/components/DatametriaSidebar.vue +230 -0
  30. package/src/components/DatametriaSkeleton.vue +243 -239
  31. package/src/components/DatametriaSlider.vue +395 -407
  32. package/src/components/DatametriaSortableTable.vue +585 -0
  33. package/src/components/DatametriaSpinner.vue +7 -7
  34. package/src/components/DatametriaSwitch.vue +16 -16
  35. package/src/components/DatametriaTable.vue +14 -14
  36. package/src/components/DatametriaTabs.vue +150 -29
  37. package/src/components/DatametriaTextarea.vue +28 -28
  38. package/src/components/DatametriaTimePicker.vue +285 -285
  39. package/src/components/DatametriaToast.vue +176 -176
  40. package/src/components/DatametriaTooltip.vue +408 -408
  41. package/src/components/__tests__/DatametriaAlert.test.js +35 -35
  42. package/src/components/__tests__/DatametriaAlert.test.ts +190 -0
  43. package/src/components/__tests__/DatametriaAutocomplete.test.ts +180 -0
  44. package/src/components/__tests__/DatametriaAvatar.test.ts +152 -0
  45. package/src/components/__tests__/DatametriaBadge.test.js +29 -29
  46. package/src/components/__tests__/DatametriaBadge.test.ts +167 -0
  47. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +75 -0
  48. package/src/components/__tests__/DatametriaButton.test.js +30 -30
  49. package/src/components/__tests__/DatametriaButton.test.ts +283 -0
  50. package/src/components/__tests__/DatametriaCard.test.ts +201 -0
  51. package/src/components/__tests__/DatametriaCheckbox.test.ts +47 -0
  52. package/src/components/__tests__/DatametriaChip.test.js +38 -38
  53. package/src/components/__tests__/DatametriaContainer.test.ts +52 -0
  54. package/src/components/__tests__/DatametriaDatePicker.test.ts +234 -0
  55. package/src/components/__tests__/DatametriaDivider.test.ts +54 -0
  56. package/src/components/__tests__/DatametriaFileUpload.test.ts +291 -0
  57. package/src/components/__tests__/DatametriaFloatingBar.test.ts +137 -0
  58. package/src/components/__tests__/DatametriaGrid.test.ts +31 -0
  59. package/src/components/__tests__/DatametriaInput.test.ts +72 -0
  60. package/src/components/__tests__/DatametriaMenu.test.ts +366 -0
  61. package/src/components/__tests__/DatametriaModal.test.ts +86 -0
  62. package/src/components/__tests__/DatametriaNavbar.test.js +48 -48
  63. package/src/components/__tests__/DatametriaNavbar.test.ts +203 -0
  64. package/src/components/__tests__/DatametriaPasswordInput.test.js +305 -0
  65. package/src/components/__tests__/DatametriaProgress.test.ts +90 -0
  66. package/src/components/__tests__/DatametriaRadio.test.ts +77 -0
  67. package/src/components/__tests__/DatametriaSelect.test.ts +77 -0
  68. package/src/components/__tests__/DatametriaSidebar.test.ts +169 -0
  69. package/src/components/__tests__/DatametriaSlider.test.ts +261 -0
  70. package/src/components/__tests__/DatametriaSortableTable.test.js +168 -0
  71. package/src/components/__tests__/DatametriaSpinner.test.ts +156 -0
  72. package/src/components/__tests__/DatametriaSwitch.test.ts +64 -0
  73. package/src/components/__tests__/DatametriaTable.test.ts +97 -0
  74. package/src/components/__tests__/DatametriaTabs.test.ts +232 -0
  75. package/src/components/__tests__/DatametriaTextarea.test.ts +66 -0
  76. package/src/components/__tests__/DatametriaToast.test.js +48 -48
  77. package/src/components/__tests__/DatametriaToast.test.ts +99 -0
  78. package/src/composables/useAccessibilityScale.ts +94 -94
  79. package/src/composables/useBreakpoints.ts +82 -82
  80. package/src/composables/useHapticFeedback.ts +439 -439
  81. package/src/composables/useRipple.ts +218 -218
  82. package/src/index.ts +70 -61
  83. package/src/stories/Variants.stories.js +95 -95
  84. package/src/styles/design-tokens.css +623 -623
  85. package/src/theme/ThemeProvider.vue +96 -0
  86. package/src/theme/__tests__/ThemeProvider.test.ts +208 -0
  87. package/src/theme/__tests__/constants.test.ts +31 -0
  88. package/src/theme/__tests__/presets.test.ts +166 -0
  89. package/src/theme/__tests__/tokens.test.ts +155 -0
  90. package/src/theme/__tests__/types.test.ts +153 -0
  91. package/src/theme/__tests__/useTheme.test.ts +146 -0
  92. package/src/theme/constants.ts +14 -0
  93. package/src/theme/index.ts +12 -0
  94. package/src/theme/presets/datametria.ts +94 -0
  95. package/src/theme/presets/default.ts +94 -0
  96. package/src/theme/presets/index.ts +8 -0
  97. package/src/theme/tokens/colors.ts +28 -0
  98. package/src/theme/tokens/index.ts +47 -0
  99. package/src/theme/tokens/spacing.ts +21 -0
  100. package/src/theme/tokens/typography.ts +35 -0
  101. package/src/theme/types.ts +111 -0
  102. package/src/theme/useTheme.ts +28 -0
  103. package/src/types/index.ts +19 -0
@@ -28,13 +28,13 @@ withDefaults(defineProps<Props>(), {
28
28
  .dm-divider {
29
29
  display: flex;
30
30
  align-items: center;
31
- color: var(--dm-text-secondary);
32
- font-size: var(--dm-text-sm);
31
+ color: var(--dm-neutral-600, #4b5563);
32
+ font-size: var(--dm-font-size-sm, 0.875rem);
33
33
  }
34
34
 
35
35
  .dm-divider--horizontal {
36
36
  width: 100%;
37
- margin: var(--dm-space-4) 0;
37
+ margin: var(--dm-spacing-4, 1rem) 0;
38
38
  }
39
39
 
40
40
  .dm-divider--horizontal::before,
@@ -42,17 +42,17 @@ withDefaults(defineProps<Props>(), {
42
42
  content: '';
43
43
  flex: 1;
44
44
  height: 1px;
45
- background: var(--dm-gray-300);
45
+ background: var(--dm-neutral-300, #d1d5db);
46
46
  }
47
47
 
48
48
  .dm-divider--horizontal .dm-divider__label {
49
- padding: 0 var(--dm-space-3);
49
+ padding: 0 var(--dm-spacing-3, 0.75rem);
50
50
  }
51
51
 
52
52
  .dm-divider--vertical {
53
53
  height: 100%;
54
54
  flex-direction: column;
55
- margin: 0 var(--dm-space-4);
55
+ margin: 0 var(--dm-spacing-4, 1rem);
56
56
  }
57
57
 
58
58
  .dm-divider--vertical::before,
@@ -60,23 +60,23 @@ withDefaults(defineProps<Props>(), {
60
60
  content: '';
61
61
  flex: 1;
62
62
  width: 1px;
63
- background: var(--dm-gray-300);
63
+ background: var(--dm-neutral-300, #d1d5db);
64
64
  }
65
65
 
66
66
  .dm-divider--vertical .dm-divider__label {
67
- padding: var(--dm-space-3) 0;
67
+ padding: var(--dm-spacing-3, 0.75rem) 0;
68
68
  }
69
69
 
70
70
  .dm-divider--dashed::before,
71
71
  .dm-divider--dashed::after {
72
72
  background: none;
73
- border-top: 1px dashed var(--dm-gray-300);
73
+ border-top: 1px dashed var(--dm-neutral-300, #d1d5db);
74
74
  }
75
75
 
76
76
  .dm-divider--vertical.dm-divider--dashed::before,
77
77
  .dm-divider--vertical.dm-divider--dashed::after {
78
78
  border-top: none;
79
- border-left: 1px dashed var(--dm-gray-300);
79
+ border-left: 1px dashed var(--dm-neutral-300, #d1d5db);
80
80
  }
81
81
 
82
82
  @media (prefers-color-scheme: dark) {
@@ -84,17 +84,17 @@ withDefaults(defineProps<Props>(), {
84
84
  .dm-divider--horizontal::after,
85
85
  .dm-divider--vertical::before,
86
86
  .dm-divider--vertical::after {
87
- background: var(--dm-gray-700);
87
+ background: var(--dm-neutral-700, #374151);
88
88
  }
89
89
 
90
90
  .dm-divider--dashed::before,
91
91
  .dm-divider--dashed::after {
92
- border-color: var(--dm-gray-700);
92
+ border-color: var(--dm-neutral-700, #374151);
93
93
  }
94
94
 
95
95
  .dm-divider--vertical.dm-divider--dashed::before,
96
96
  .dm-divider--vertical.dm-divider--dashed::after {
97
- border-color: var(--dm-gray-700);
97
+ border-color: var(--dm-neutral-700, #374151);
98
98
  }
99
99
  }
100
100
  </style>
@@ -1,169 +1,266 @@
1
1
  <template>
2
- <div class="dm-file-upload">
3
- <label v-if="label" class="dm-file-upload__label">
4
- {{ label }}
5
- <span v-if="required" class="dm-file-upload__required">*</span>
6
- </label>
2
+ <div class="dm-file-upload" :class="{ 'dm-file-upload--disabled': disabled, 'dm-file-upload--loading': loading }">
7
3
  <div
8
- class="dm-file-upload__dropzone"
9
- :class="{ 'dm-file-upload--dragging': isDragOver, 'dm-file-upload__dropzone--dragover': isDragOver, 'dm-file-upload__dropzone--error': error }"
10
- @dragover.prevent="handleDragOver"
11
- @dragleave.prevent="handleDragLeave"
12
- @drop.prevent="handleDrop"
4
+ class="dm-file-upload__area"
5
+ :class="{ 'dm-file-upload__area--dragover': isDragOver }"
13
6
  @click="triggerFileInput"
7
+ @drop="handleDrop"
8
+ @dragover="handleDragOver"
9
+ @dragenter="handleDragEnter"
10
+ @dragleave="handleDragLeave"
14
11
  >
15
12
  <input
16
- ref="fileInput"
13
+ ref="fileInputRef"
17
14
  type="file"
18
15
  class="dm-file-upload__input"
19
16
  :accept="accept"
20
17
  :multiple="multiple"
21
18
  :disabled="disabled"
22
- :required="required"
23
- @change="handleFileChange"
19
+ @change="handleFileSelect"
24
20
  />
25
- <div class="dm-file-upload__content">
26
- <svg class="dm-file-upload__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
27
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
28
- </svg>
29
- <p class="dm-file-upload__text">
30
- <span class="dm-file-upload__text--primary">Clique para selecionar</span>
31
- ou arraste arquivos aqui
32
- </p>
33
- <p v-if="accept" class="dm-file-upload__hint">{{ accept }}</p>
21
+
22
+ <div v-if="loading" class="dm-file-upload__spinner"></div>
23
+
24
+ <div v-else class="dm-file-upload__content">
25
+ <div class="dm-file-upload__icon">
26
+ <svg viewBox="0 0 24 24" fill="currentColor">
27
+ <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
28
+ </svg>
29
+ </div>
30
+
31
+ <div class="dm-file-upload__text">
32
+ <p class="dm-file-upload__primary-text">
33
+ {{ uploadText || 'Click to upload or drag and drop' }}
34
+ </p>
35
+ <p class="dm-file-upload__secondary-text">
36
+ {{ accept ? `Supported formats: ${accept}` : 'All file types supported' }}
37
+ </p>
38
+ </div>
34
39
  </div>
35
40
  </div>
36
- <div v-if="selectedFiles.length" class="dm-file-upload__files">
37
- <div v-for="(file, index) in selectedFiles" :key="index" class="dm-file-upload__file">
38
- <span class="dm-file-upload__filename">{{ file.name }}</span>
39
- <span class="dm-file-upload__filesize">{{ formatFileSize(file.size) }}</span>
41
+
42
+ <div v-if="progress !== undefined" class="dm-file-upload__progress">
43
+ <div class="dm-file-upload__progress-track">
44
+ <div
45
+ class="dm-file-upload__progress-bar"
46
+ :style="{ width: `${progress}%` }"
47
+ ></div>
48
+ </div>
49
+ <span class="dm-file-upload__progress-text">{{ progress }}%</span>
50
+ </div>
51
+
52
+ <div v-if="selectedFiles.length > 0" class="dm-file-upload__files">
53
+ <div
54
+ v-for="(file, index) in selectedFiles"
55
+ :key="`${file.name}-${index}`"
56
+ class="dm-file-upload__file"
57
+ >
58
+ <div class="dm-file-upload__file-info">
59
+ <span class="dm-file-upload__file-name">{{ file.name }}</span>
60
+ <span class="dm-file-upload__file-size">{{ formatFileSize(file.size) }}</span>
61
+ </div>
40
62
  <button
41
63
  type="button"
42
64
  class="dm-file-upload__remove"
43
65
  @click="removeFile(index)"
44
- aria-label="Remover arquivo"
45
- >×</button>
66
+ >
67
+ ×
68
+ </button>
46
69
  </div>
47
70
  </div>
48
- <p v-if="error" class="dm-file-upload__error">{{ error }}</p>
71
+
72
+ <div v-if="computedError" class="dm-file-upload__error">
73
+ {{ computedError }}
74
+ </div>
49
75
  </div>
50
76
  </template>
51
77
 
52
78
  <script setup lang="ts">
53
- import { ref } from 'vue'
79
+ import { ref, computed } from 'vue'
54
80
 
55
81
  interface Props {
56
- modelValue?: File[]
57
- label?: string
82
+ modelValue?: File | File[]
58
83
  accept?: string
59
84
  multiple?: boolean
85
+ maxSize?: number // in bytes
86
+ maxFiles?: number
60
87
  disabled?: boolean
61
- required?: boolean
88
+ loading?: boolean
89
+ progress?: number
90
+ uploadText?: string
62
91
  error?: string
63
92
  }
64
93
 
65
94
  const props = withDefaults(defineProps<Props>(), {
66
- modelValue: () => [],
67
95
  multiple: false,
68
96
  disabled: false,
69
- required: false
97
+ loading: false
70
98
  })
71
99
 
72
100
  const emit = defineEmits<{
73
- 'update:modelValue': [files: File[]]
101
+ 'update:modelValue': [value: File | File[]]
102
+ 'file-added': [file: File]
103
+ 'file-removed': [file: File, index: number]
74
104
  }>()
75
105
 
76
- const fileInput = ref<HTMLInputElement>()
77
- const selectedFiles = ref<File[]>(props.modelValue)
106
+ const fileInputRef = ref<HTMLInputElement>()
78
107
  const isDragOver = ref(false)
108
+ const selectedFiles = ref<File[]>([])
109
+ const validationError = ref('')
110
+
111
+ const computedError = computed(() => props.error || validationError.value)
79
112
 
80
113
  const triggerFileInput = () => {
81
- if (!props.disabled) {
82
- fileInput.value?.click()
114
+ if (!props.disabled && fileInputRef.value) {
115
+ fileInputRef.value.click()
83
116
  }
84
117
  }
85
118
 
86
- const handleFileChange = (event: Event) => {
87
- const files = Array.from((event.target as HTMLInputElement).files || [])
88
- updateFiles(files)
119
+ const handleFileSelect = (event: Event) => {
120
+ const target = event.target as HTMLInputElement
121
+ if (target.files) {
122
+ processFiles(Array.from(target.files))
123
+ }
89
124
  }
90
125
 
91
- const handleDragOver = () => {
92
- if (!props.disabled) {
93
- isDragOver.value = true
126
+ const handleDrop = (event: DragEvent) => {
127
+ event.preventDefault()
128
+ isDragOver.value = false
129
+
130
+ if (props.disabled) return
131
+
132
+ const files = event.dataTransfer?.files
133
+ if (files) {
134
+ processFiles(Array.from(files))
94
135
  }
95
136
  }
96
137
 
97
- const handleDragLeave = () => {
98
- isDragOver.value = false
138
+ const handleDragOver = (event: DragEvent) => {
139
+ event.preventDefault()
99
140
  }
100
141
 
101
- const handleDrop = (event: DragEvent) => {
102
- isDragOver.value = false
142
+ const handleDragEnter = (event: DragEvent) => {
143
+ event.preventDefault()
103
144
  if (!props.disabled) {
104
- const files = Array.from(event.dataTransfer?.files || [])
105
- updateFiles(files)
145
+ isDragOver.value = true
106
146
  }
107
147
  }
108
148
 
109
- const updateFiles = (files: File[]) => {
110
- selectedFiles.value = props.multiple ? [...selectedFiles.value, ...files] : files
111
- emit('update:modelValue', selectedFiles.value)
149
+ const handleDragLeave = (event: DragEvent) => {
150
+ event.preventDefault()
151
+ // Only set to false if we're leaving the drop area entirely
152
+ if (!(event.currentTarget as Element)?.contains(event.relatedTarget as Node)) {
153
+ isDragOver.value = false
154
+ }
155
+ }
156
+
157
+ const processFiles = (files: File[]) => {
158
+ validationError.value = ''
159
+
160
+ // Validate file types
161
+ if (props.accept) {
162
+ const acceptedTypes = props.accept.split(',').map(type => type.trim())
163
+ const invalidFiles = files.filter(file => {
164
+ return !acceptedTypes.some(type => {
165
+ if (type.startsWith('.')) {
166
+ return file.name.toLowerCase().endsWith(type.toLowerCase())
167
+ }
168
+ return file.type.match(type.replace('*', '.*'))
169
+ })
170
+ })
171
+
172
+ if (invalidFiles.length > 0) {
173
+ validationError.value = `Invalid file type(s): ${invalidFiles.map(f => f.name).join(', ')}`
174
+ return
175
+ }
176
+ }
177
+
178
+ // Validate file sizes
179
+ if (props.maxSize) {
180
+ const oversizedFiles = files.filter(file => file.size > props.maxSize!)
181
+ if (oversizedFiles.length > 0) {
182
+ validationError.value = `File(s) too large: ${oversizedFiles.map(f => f.name).join(', ')}`
183
+ return
184
+ }
185
+ }
186
+
187
+ // Validate number of files
188
+ if (props.multiple) {
189
+ const totalFiles = selectedFiles.value.length + files.length
190
+ if (props.maxFiles && totalFiles > props.maxFiles) {
191
+ validationError.value = `Maximum ${props.maxFiles} files allowed`
192
+ return
193
+ }
194
+
195
+ selectedFiles.value.push(...files)
196
+ emit('update:modelValue', selectedFiles.value)
197
+ } else {
198
+ selectedFiles.value = [files[0]]
199
+ emit('update:modelValue', files[0])
200
+ }
201
+
202
+ // Emit file-added events
203
+ files.forEach(file => {
204
+ emit('file-added', file)
205
+ })
112
206
  }
113
207
 
114
208
  const removeFile = (index: number) => {
209
+ const removedFile = selectedFiles.value[index]
115
210
  selectedFiles.value.splice(index, 1)
116
- emit('update:modelValue', selectedFiles.value)
211
+
212
+ if (props.multiple) {
213
+ emit('update:modelValue', selectedFiles.value)
214
+ } else {
215
+ emit('update:modelValue', selectedFiles.value[0] || null)
216
+ }
217
+
218
+ emit('file-removed', removedFile, index)
117
219
  }
118
220
 
119
221
  const formatFileSize = (bytes: number): string => {
120
- if (bytes === 0) return '0 B'
222
+ if (bytes === 0) return '0 Bytes'
223
+
121
224
  const k = 1024
122
- const sizes = ['B', 'KB', 'MB', 'GB']
225
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
123
226
  const i = Math.floor(Math.log(bytes) / Math.log(k))
124
- return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
227
+
228
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
125
229
  }
126
230
  </script>
127
231
 
128
232
  <style scoped>
129
233
  .dm-file-upload {
130
- display: flex;
131
- flex-direction: column;
132
- gap: var(--dm-space-2);
234
+ width: 100%;
133
235
  }
134
236
 
135
- .dm-file-upload__label {
136
- color: var(--dm-text-primary);
137
- font-size: var(--dm-text-sm);
138
- font-weight: 500;
237
+ .dm-file-upload--disabled {
238
+ opacity: 0.6;
239
+ pointer-events: none;
139
240
  }
140
241
 
141
- .dm-file-upload__required {
142
- color: var(--dm-error);
242
+ .dm-file-upload--loading {
243
+ opacity: 0.8;
143
244
  }
144
245
 
145
- .dm-file-upload__dropzone {
146
- border: 2px dashed var(--dm-gray-300);
147
- border-radius: var(--dm-radius);
148
- padding: var(--dm-space-6);
246
+ .dm-file-upload__area {
247
+ border: 2px dashed var(--dm-neutral-300, #d1d5db);
248
+ border-radius: var(--dm-radius-lg, 0.5rem);
249
+ padding: 2rem;
149
250
  text-align: center;
150
251
  cursor: pointer;
151
- transition: var(--dm-transition);
152
- background: var(--dm-white);
153
- }
154
-
155
- .dm-file-upload__dropzone:hover {
156
- border-color: var(--dm-primary);
157
- background: var(--dm-gray-50);
252
+ transition: all 0.2s;
253
+ background-color: var(--dm-neutral-50, #f9fafb);
158
254
  }
159
255
 
160
- .dm-file-upload__dropzone--dragover {
161
- border-color: var(--dm-primary);
162
- background: var(--dm-primary-light, rgba(0, 114, 206, 0.1));
256
+ .dm-file-upload__area:hover {
257
+ border-color: var(--dm-primary, #0072ce);
258
+ background-color: var(--dm-neutral-100, #f3f4f6);
163
259
  }
164
260
 
165
- .dm-file-upload__dropzone--error {
166
- border-color: var(--dm-error);
261
+ .dm-file-upload__area--dragover {
262
+ border-color: var(--dm-primary, #0072ce);
263
+ background-color: rgba(0, 114, 206, 0.05);
167
264
  }
168
265
 
169
266
  .dm-file-upload__input {
@@ -174,95 +271,130 @@ const formatFileSize = (bytes: number): string => {
174
271
  display: flex;
175
272
  flex-direction: column;
176
273
  align-items: center;
177
- gap: var(--dm-space-2);
274
+ gap: 1rem;
178
275
  }
179
276
 
180
277
  .dm-file-upload__icon {
181
- width: 48px;
182
- height: 48px;
183
- color: var(--dm-gray-400);
278
+ width: 3rem;
279
+ height: 3rem;
280
+ color: var(--dm-neutral-400, #9ca3af);
281
+ }
282
+
283
+ .dm-file-upload__icon svg {
284
+ width: 100%;
285
+ height: 100%;
184
286
  }
185
287
 
186
288
  .dm-file-upload__text {
187
- color: var(--dm-text-secondary);
188
- font-size: var(--dm-text-sm);
189
- margin: 0;
289
+ text-align: center;
190
290
  }
191
291
 
192
- .dm-file-upload__text--primary {
193
- color: var(--dm-primary);
292
+ .dm-file-upload__primary-text {
293
+ font-size: 1.125rem;
194
294
  font-weight: 500;
295
+ color: var(--dm-neutral-700, #374151);
296
+ margin: 0 0 0.5rem 0;
195
297
  }
196
298
 
197
- .dm-file-upload__hint {
198
- color: var(--dm-gray-500);
199
- font-size: var(--dm-text-xs);
299
+ .dm-file-upload__secondary-text {
300
+ font-size: 0.875rem;
301
+ color: var(--dm-neutral-500, #6b7280);
200
302
  margin: 0;
201
303
  }
202
304
 
203
- .dm-file-upload__files {
204
- display: flex;
205
- flex-direction: column;
206
- gap: var(--dm-space-2);
305
+ .dm-file-upload__spinner {
306
+ width: 2rem;
307
+ height: 2rem;
308
+ border: 3px solid var(--dm-neutral-300, #d1d5db);
309
+ border-top: 3px solid var(--dm-primary, #0072ce);
310
+ border-radius: 50%;
311
+ animation: spin 1s linear infinite;
207
312
  }
208
313
 
209
- .dm-file-upload__file {
314
+ @keyframes spin {
315
+ 0% { transform: rotate(0deg); }
316
+ 100% { transform: rotate(360deg); }
317
+ }
318
+
319
+ .dm-file-upload__progress {
320
+ margin-top: 1rem;
210
321
  display: flex;
211
322
  align-items: center;
212
- gap: var(--dm-space-2);
213
- padding: var(--dm-space-2) var(--dm-space-3);
214
- background: var(--dm-gray-50);
215
- border-radius: var(--dm-radius);
323
+ gap: 1rem;
216
324
  }
217
325
 
218
- .dm-file-upload__filename {
326
+ .dm-file-upload__progress-track {
219
327
  flex: 1;
220
- font-size: var(--dm-text-sm);
221
- color: var(--dm-text-primary);
328
+ height: 0.5rem;
329
+ background-color: var(--dm-neutral-200, #e5e7eb);
330
+ border-radius: var(--dm-radius-full, 9999px);
222
331
  overflow: hidden;
223
- text-overflow: ellipsis;
224
- white-space: nowrap;
225
332
  }
226
333
 
227
- .dm-file-upload__filesize {
228
- font-size: var(--dm-text-xs);
229
- color: var(--dm-gray-500);
334
+ .dm-file-upload__progress-bar {
335
+ height: 100%;
336
+ background-color: var(--dm-primary, #0072ce);
337
+ transition: width 0.3s ease;
338
+ }
339
+
340
+ .dm-file-upload__progress-text {
341
+ font-size: 0.875rem;
342
+ font-weight: 500;
343
+ color: var(--dm-neutral-600, #4b5563);
344
+ min-width: 3rem;
345
+ }
346
+
347
+ .dm-file-upload__files {
348
+ margin-top: 1rem;
349
+ space-y: 0.5rem;
350
+ }
351
+
352
+ .dm-file-upload__file {
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: space-between;
356
+ padding: 0.75rem;
357
+ background-color: var(--dm-neutral-50, #f9fafb);
358
+ border: 1px solid var(--dm-neutral-200, #e5e7eb);
359
+ border-radius: var(--dm-radius-md, 0.375rem);
360
+ margin-bottom: 0.5rem;
361
+ }
362
+
363
+ .dm-file-upload__file-info {
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 0.25rem;
367
+ }
368
+
369
+ .dm-file-upload__file-name {
370
+ font-weight: 500;
371
+ color: var(--dm-neutral-700, #374151);
372
+ }
373
+
374
+ .dm-file-upload__file-size {
375
+ font-size: 0.875rem;
376
+ color: var(--dm-neutral-500, #6b7280);
230
377
  }
231
378
 
232
379
  .dm-file-upload__remove {
233
- width: 24px;
234
- height: 24px;
380
+ background: none;
235
381
  border: none;
236
- background: transparent;
237
- color: var(--dm-gray-500);
238
- font-size: 24px;
239
- line-height: 1;
382
+ color: var(--dm-error, #ef4444);
240
383
  cursor: pointer;
241
- transition: var(--dm-transition);
384
+ font-size: 1.25rem;
385
+ padding: 0.25rem;
386
+ border-radius: var(--dm-radius-sm, 0.25rem);
387
+ transition: background-color 0.2s;
242
388
  }
243
389
 
244
390
  .dm-file-upload__remove:hover {
245
- color: var(--dm-error);
391
+ background-color: var(--dm-error, #ef4444);
392
+ color: white;
246
393
  }
247
394
 
248
395
  .dm-file-upload__error {
249
- color: var(--dm-error);
250
- font-size: var(--dm-text-sm);
251
- margin: 0;
252
- }
253
-
254
- @media (prefers-color-scheme: dark) {
255
- .dm-file-upload__dropzone {
256
- background: var(--dm-gray-800);
257
- border-color: var(--dm-gray-600);
258
- }
259
-
260
- .dm-file-upload__dropzone:hover {
261
- background: var(--dm-gray-700);
262
- }
263
-
264
- .dm-file-upload__file {
265
- background: var(--dm-gray-700);
266
- }
396
+ margin-top: 0.5rem;
397
+ color: var(--dm-error, #ef4444);
398
+ font-size: 0.875rem;
267
399
  }
268
- </style>
400
+ </style>