157 lines
3.6 KiB
Vue
157 lines
3.6 KiB
Vue
<template>
|
|
<v-dialog v-model="dialogModel" max-width="800px" persistent>
|
|
<v-card class="dialog-card">
|
|
<v-card-title class="dialog-header">
|
|
<span class="headline-4">{{ title }}</span>
|
|
<v-btn icon variant="text" size="small" class="btn-close" @click="dialogModel = false">
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</v-card-title>
|
|
|
|
<v-divider />
|
|
|
|
<v-card-text class="dialog-content">
|
|
<v-text-field
|
|
v-model="searchModel"
|
|
:placeholder="searchPlaceholder"
|
|
density="compact"
|
|
hide-details
|
|
class="mb-4"
|
|
prepend-inner-icon="mdi-magnify"
|
|
/>
|
|
|
|
<v-row dense>
|
|
<v-col
|
|
v-for="item in filteredItems"
|
|
:key="item.id"
|
|
cols="6"
|
|
sm="4"
|
|
>
|
|
<v-card
|
|
class="selection-card"
|
|
@click="handleSelect(item)"
|
|
elevation="0"
|
|
>
|
|
<v-card-text class="text-center pa-3">
|
|
<div class="selection-name">{{ item.name }}</div>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue';
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: 'Pilih Item'
|
|
},
|
|
items: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
searchQuery: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
searchPlaceholder: {
|
|
type: String,
|
|
default: 'Cari...'
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['update:modelValue', 'update:searchQuery', 'select']);
|
|
|
|
const dialogModel = computed({
|
|
get: () => props.modelValue,
|
|
set: (value) => emit('update:modelValue', value)
|
|
});
|
|
|
|
const searchModel = computed({
|
|
get: () => props.searchQuery,
|
|
set: (value) => emit('update:searchQuery', value)
|
|
});
|
|
|
|
const filteredItems = computed(() => {
|
|
if (!searchModel.value) return props.items;
|
|
const search = searchModel.value.toLowerCase();
|
|
return props.items.filter(item =>
|
|
item.name.toLowerCase().includes(search)
|
|
);
|
|
});
|
|
|
|
const handleSelect = (item) => {
|
|
emit('select', item);
|
|
dialogModel.value = false;
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.dialog-card {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
.dialog-header {
|
|
background: linear-gradient(135deg, var(--color-secondary-600) 0%, var(--color-secondary-700) 100%);
|
|
color: var(--color-neutral-100);
|
|
padding: 20px 24px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.headline-4 {
|
|
font-size: 20px;
|
|
line-height: 28px;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
.btn-close {
|
|
color: var(--color-neutral-100) !important;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-close:hover {
|
|
background: rgba(255, 255, 255, 0.2) !important;
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.dialog-content {
|
|
padding: 24px !important;
|
|
background: var(--color-neutral-300);
|
|
}
|
|
|
|
.selection-card {
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
border: 1px solid var(--color-neutral-400);
|
|
background: var(--color-neutral-100);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.selection-card:hover {
|
|
border-color: var(--color-secondary-600);
|
|
background: var(--color-secondary-300);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(6, 113, 224, 0.15);
|
|
}
|
|
|
|
.selection-name {
|
|
font-size: 14px;
|
|
line-height: 20px;
|
|
font-weight: 600;
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
color: var(--color-neutral-900);
|
|
}
|
|
</style> |