Icons for payment methods
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
<script setup>
|
||||
import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
|
||||
import Icon from './Icon.vue'
|
||||
|
||||
// Generisches Custom-Dropdown, das je Option ein Icon + Label anzeigt (ein natives <select> kann
|
||||
// keine SVG-Icons rendern). Wiederverwendbar für Icon-/Rich-Selects.
|
||||
const props = defineProps({
|
||||
// Ausgewählter Wert (value einer Option).
|
||||
modelValue: {type: String, default: ''},
|
||||
// [{ value, label, icon? }]
|
||||
options: {type: Array, default: () => []},
|
||||
placeholder: {type: String, default: 'Auswählen…'},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const open = ref(false)
|
||||
const root = ref(null)
|
||||
|
||||
const selected = computed(() => props.options.find(o => o.value === props.modelValue) ?? null)
|
||||
|
||||
function toggle() {
|
||||
open.value = !open.value
|
||||
}
|
||||
|
||||
function select(option) {
|
||||
emit('update:modelValue', option.value)
|
||||
open.value = false
|
||||
}
|
||||
|
||||
function onClickOutside(event) {
|
||||
if (root.value && !root.value.contains(event.target)) {
|
||||
open.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onKeydown(event) {
|
||||
if (event.key === 'Escape' || event.key === 'Esc') {
|
||||
open.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onClickOutside)
|
||||
document.addEventListener('keydown', onKeydown)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', onClickOutside)
|
||||
document.removeEventListener('keydown', onKeydown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="root" class="rich-select">
|
||||
<button
|
||||
type="button"
|
||||
class="rich-select__trigger"
|
||||
:aria-expanded="open"
|
||||
@click="toggle"
|
||||
>
|
||||
<span class="rich-select__value">
|
||||
<template v-if="selected">
|
||||
<Icon v-if="selected.icon" :key="selected.value" :name="selected.icon"/>
|
||||
<span>{{ selected.label }}</span>
|
||||
</template>
|
||||
<span v-else class="rich-select__placeholder">{{ placeholder }}</span>
|
||||
</span>
|
||||
<span class="rich-select__caret" aria-hidden="true">▾</span>
|
||||
</button>
|
||||
|
||||
<ul v-if="open" class="rich-select__list" role="listbox">
|
||||
<li
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
class="rich-select__option"
|
||||
:class="{ 'rich-select__option--active': option.value === modelValue }"
|
||||
role="option"
|
||||
:aria-selected="option.value === modelValue"
|
||||
@click="select(option)"
|
||||
>
|
||||
<Icon v-if="option.icon" :name="option.icon"/>
|
||||
<span>{{ option.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rich-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rich-select__trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rich-select__trigger:hover {
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.rich-select__value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.rich-select__placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.rich-select__caret {
|
||||
color: #6b7280;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.rich-select__list {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
list-style: none;
|
||||
background: #fff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.rich-select__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rich-select__option:hover {
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.rich-select__option--active {
|
||||
background: #dbeafe;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user