@datametria/vue-components 2.1.1 → 2.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.
- package/README.md +11 -3
- package/dist/index.es.js +1814 -1735
- package/dist/index.umd.js +9 -9
- package/dist/src/types/index.d.ts +5 -0
- package/dist/vue-components.css +1 -1
- package/package.json +1 -1
- package/src/components/DatametriaSortableTable.vue +204 -14
- package/src/types/index.ts +2 -0
|
@@ -56,15 +56,68 @@
|
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
58
|
<!-- Column Filter -->
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
<div v-if="filterable && column.filterable !== false" @click.stop>
|
|
60
|
+
<!-- Select Filter -->
|
|
61
|
+
<select
|
|
62
|
+
v-if="column.filterType === 'select'"
|
|
63
|
+
v-model="columnFilters[column.key]"
|
|
64
|
+
:aria-label="`Filtrar por ${column.label}`"
|
|
65
|
+
class="datametria-sortable-table__filter-input"
|
|
66
|
+
@keydown.stop
|
|
67
|
+
>
|
|
68
|
+
<option value="">Todos</option>
|
|
69
|
+
<option
|
|
70
|
+
v-for="option in getFilterOptions(column)"
|
|
71
|
+
:key="option.value"
|
|
72
|
+
:value="option.value"
|
|
73
|
+
>
|
|
74
|
+
{{ option.label }}
|
|
75
|
+
</option>
|
|
76
|
+
</select>
|
|
77
|
+
|
|
78
|
+
<!-- Multi-Select Filter -->
|
|
79
|
+
<div
|
|
80
|
+
v-else-if="column.filterType === 'multiselect'"
|
|
81
|
+
class="datametria-sortable-table__multiselect"
|
|
82
|
+
>
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
class="datametria-sortable-table__multiselect-trigger"
|
|
86
|
+
@click="toggleMultiselect(column.key)"
|
|
87
|
+
>
|
|
88
|
+
{{ getMultiselectLabel(column) }}
|
|
89
|
+
</button>
|
|
90
|
+
<div
|
|
91
|
+
v-if="activeMultiselect === column.key"
|
|
92
|
+
class="datametria-sortable-table__multiselect-dropdown"
|
|
93
|
+
>
|
|
94
|
+
<label
|
|
95
|
+
v-for="option in getFilterOptions(column)"
|
|
96
|
+
:key="option.value"
|
|
97
|
+
class="datametria-sortable-table__multiselect-option"
|
|
98
|
+
>
|
|
99
|
+
<input
|
|
100
|
+
type="checkbox"
|
|
101
|
+
:value="option.value"
|
|
102
|
+
:checked="isMultiselectChecked(column.key, option.value)"
|
|
103
|
+
@change="toggleMultiselectOption(column.key, option.value)"
|
|
104
|
+
/>
|
|
105
|
+
{{ option.label }}
|
|
106
|
+
</label>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Text/Date Filter -->
|
|
111
|
+
<input
|
|
112
|
+
v-else
|
|
113
|
+
v-model="columnFilters[column.key]"
|
|
114
|
+
:type="column.filterType || getFilterInputType(column)"
|
|
115
|
+
:placeholder="`Filtrar ${column.label}...`"
|
|
116
|
+
:aria-label="`Filtrar por ${column.label}`"
|
|
117
|
+
class="datametria-sortable-table__filter-input"
|
|
118
|
+
@keydown.stop
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
68
121
|
</th>
|
|
69
122
|
</tr>
|
|
70
123
|
</thead>
|
|
@@ -204,6 +257,72 @@ const emit = defineEmits<{
|
|
|
204
257
|
'selection-change': [selectedIds: (string | number)[]]
|
|
205
258
|
}>()
|
|
206
259
|
|
|
260
|
+
// Detect filter input type based on column data
|
|
261
|
+
const getFilterInputType = (column: any) => {
|
|
262
|
+
if (!props.data || props.data.length === 0) return 'text'
|
|
263
|
+
|
|
264
|
+
const sampleValue = props.data[0][column.key]
|
|
265
|
+
|
|
266
|
+
// Check if it's a date
|
|
267
|
+
if (sampleValue instanceof Date) return 'date'
|
|
268
|
+
if (typeof sampleValue === 'string' && !isNaN(Date.parse(sampleValue))) {
|
|
269
|
+
// Check if it looks like a date string (YYYY-MM-DD, DD/MM/YYYY, etc)
|
|
270
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}|\d{2}\/\d{2}\/\d{4}/
|
|
271
|
+
if (datePattern.test(sampleValue)) return 'date'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return 'text'
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Get filter options (auto-generate if 'auto')
|
|
278
|
+
const getFilterOptions = (column: any) => {
|
|
279
|
+
if (column.filterOptions === 'auto') {
|
|
280
|
+
const uniqueValues = new Set<string>()
|
|
281
|
+
props.data.forEach(row => {
|
|
282
|
+
const value = row[column.key]
|
|
283
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
284
|
+
uniqueValues.add(String(value))
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
return Array.from(uniqueValues)
|
|
288
|
+
.sort()
|
|
289
|
+
.map(value => ({ value, label: value }))
|
|
290
|
+
}
|
|
291
|
+
return column.filterOptions || []
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Multi-select methods
|
|
295
|
+
const toggleMultiselect = (key: string) => {
|
|
296
|
+
activeMultiselect.value = activeMultiselect.value === key ? null : key
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const getMultiselectLabel = (column: any) => {
|
|
300
|
+
const selected = columnFilters.value[column.key] as string[] || []
|
|
301
|
+
if (selected.length === 0) return 'Todos'
|
|
302
|
+
if (selected.length === 1) {
|
|
303
|
+
const options = getFilterOptions(column)
|
|
304
|
+
const option = options.find((o: any) => o.value === selected[0])
|
|
305
|
+
return option?.label || selected[0]
|
|
306
|
+
}
|
|
307
|
+
return `${selected.length} selecionados`
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const isMultiselectChecked = (key: string, value: string) => {
|
|
311
|
+
const selected = columnFilters.value[key] as string[] || []
|
|
312
|
+
return selected.includes(value)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const toggleMultiselectOption = (key: string, value: string) => {
|
|
316
|
+
const selected = (columnFilters.value[key] as string[]) || []
|
|
317
|
+
const index = selected.indexOf(value)
|
|
318
|
+
|
|
319
|
+
if (index > -1) {
|
|
320
|
+
columnFilters.value[key] = selected.filter(v => v !== value)
|
|
321
|
+
} else {
|
|
322
|
+
columnFilters.value[key] = [...selected, value]
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
207
326
|
// Search
|
|
208
327
|
const searchQuery = ref('')
|
|
209
328
|
|
|
@@ -212,7 +331,8 @@ const sortKey = ref<string>('')
|
|
|
212
331
|
const sortOrder = ref<'asc' | 'desc'>('asc')
|
|
213
332
|
|
|
214
333
|
// Filtering
|
|
215
|
-
const columnFilters = ref<Record<string, string>>({})
|
|
334
|
+
const columnFilters = ref<Record<string, string | string[]>>({})
|
|
335
|
+
const activeMultiselect = ref<string | null>(null)
|
|
216
336
|
|
|
217
337
|
// Selection
|
|
218
338
|
const selectedRows = ref<Set<string | number>>(new Set())
|
|
@@ -238,10 +358,19 @@ const filteredData = computed(() => {
|
|
|
238
358
|
// Column filters
|
|
239
359
|
Object.entries(columnFilters.value).forEach(([key, filterValue]) => {
|
|
240
360
|
if (filterValue) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
361
|
+
// Multi-select filter
|
|
362
|
+
if (Array.isArray(filterValue) && filterValue.length > 0) {
|
|
363
|
+
result = result.filter(row =>
|
|
364
|
+
filterValue.includes(String(row[key]))
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
// Text/Date/Select filter
|
|
368
|
+
else if (typeof filterValue === 'string') {
|
|
369
|
+
const query = filterValue.toLowerCase()
|
|
370
|
+
result = result.filter(row =>
|
|
371
|
+
String(row[key]).toLowerCase().includes(query)
|
|
372
|
+
)
|
|
373
|
+
}
|
|
245
374
|
}
|
|
246
375
|
})
|
|
247
376
|
|
|
@@ -438,6 +567,67 @@ watch(totalPages, (newTotal) => {
|
|
|
438
567
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 20%, transparent);
|
|
439
568
|
}
|
|
440
569
|
|
|
570
|
+
.datametria-sortable-table__multiselect {
|
|
571
|
+
position: relative;
|
|
572
|
+
margin-top: var(--dm-spacing-2, 0.5rem);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.datametria-sortable-table__multiselect-trigger {
|
|
576
|
+
width: 100%;
|
|
577
|
+
padding: var(--dm-spacing-2, 0.5rem);
|
|
578
|
+
border: 1px solid var(--dm-neutral-200, #e5e7eb);
|
|
579
|
+
border-radius: var(--dm-radius-md, 0.375rem);
|
|
580
|
+
font-size: var(--dm-font-size-sm, 0.875rem);
|
|
581
|
+
font-weight: 400;
|
|
582
|
+
text-align: left;
|
|
583
|
+
background: var(--dm-neutral-50, #ffffff);
|
|
584
|
+
cursor: pointer;
|
|
585
|
+
transition: all 0.2s ease;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.datametria-sortable-table__multiselect-trigger:hover {
|
|
589
|
+
border-color: var(--dm-primary, #0072CE);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.datametria-sortable-table__multiselect-trigger:focus {
|
|
593
|
+
outline: none;
|
|
594
|
+
border-color: var(--dm-primary, #0072CE);
|
|
595
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-primary, #0072CE) 20%, transparent);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.datametria-sortable-table__multiselect-dropdown {
|
|
599
|
+
position: absolute;
|
|
600
|
+
top: 100%;
|
|
601
|
+
left: 0;
|
|
602
|
+
right: 0;
|
|
603
|
+
margin-top: var(--dm-spacing-1, 0.25rem);
|
|
604
|
+
max-height: 200px;
|
|
605
|
+
overflow-y: auto;
|
|
606
|
+
background: var(--dm-neutral-50, #ffffff);
|
|
607
|
+
border: 1px solid var(--dm-neutral-200, #e5e7eb);
|
|
608
|
+
border-radius: var(--dm-radius-md, 0.375rem);
|
|
609
|
+
box-shadow: var(--dm-shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));
|
|
610
|
+
z-index: 10;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.datametria-sortable-table__multiselect-option {
|
|
614
|
+
display: flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
gap: var(--dm-spacing-2, 0.5rem);
|
|
617
|
+
padding: var(--dm-spacing-2, 0.5rem) var(--dm-spacing-3, 0.75rem);
|
|
618
|
+
font-size: var(--dm-font-size-sm, 0.875rem);
|
|
619
|
+
cursor: pointer;
|
|
620
|
+
transition: background-color 0.2s ease;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.datametria-sortable-table__multiselect-option:hover {
|
|
624
|
+
background: var(--dm-neutral-100, #f3f4f6);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.datametria-sortable-table__multiselect-option input[type="checkbox"] {
|
|
628
|
+
margin: 0;
|
|
629
|
+
}
|
|
630
|
+
|
|
441
631
|
.datametria-sortable-table__tbody {
|
|
442
632
|
background: var(--dm-neutral-50, #ffffff);
|
|
443
633
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface TableColumn {
|
|
|
39
39
|
width?: string
|
|
40
40
|
sortable?: boolean
|
|
41
41
|
filterable?: boolean
|
|
42
|
+
filterType?: 'text' | 'date' | 'select' | 'multiselect'
|
|
43
|
+
filterOptions?: Array<{ value: string; label: string }> | 'auto'
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export interface SortableTableProps {
|