166 lines
3.8 KiB
Vue
166 lines
3.8 KiB
Vue
<template>
|
|
<label :class="labelClasses" :for="effectiveId">
|
|
<input
|
|
type="radio"
|
|
:id="effectiveId"
|
|
:name="name"
|
|
:value="value"
|
|
:checked="isChecked"
|
|
:disabled="disabled"
|
|
@change="onChange"
|
|
/>
|
|
<span class="checkmark radio-mark"></span>
|
|
<span v-if="label" class="radio-text-label">{{ label }}</span>
|
|
</label>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, computed, PropType } from 'vue';
|
|
|
|
export default defineComponent({
|
|
name: 'VRadio',
|
|
props: {
|
|
modelValue: {
|
|
type: [String, Number] as PropType<string | number | null>, // Allow null for when nothing is selected
|
|
required: true,
|
|
},
|
|
value: {
|
|
type: [String, Number] as PropType<string | number>,
|
|
required: true,
|
|
},
|
|
label: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
id: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
name: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
},
|
|
emits: ['update:modelValue'],
|
|
setup(props, { emit }) {
|
|
const effectiveId = computed(() => {
|
|
return props.id || `vradio-${props.name}-${props.value}`;
|
|
});
|
|
|
|
const labelClasses = computed(() => [
|
|
'radio-label',
|
|
{ 'disabled': props.disabled },
|
|
{ 'checked': isChecked.value }, // For potential styling of the label itself when checked
|
|
]);
|
|
|
|
const isChecked = computed(() => {
|
|
return props.modelValue === props.value;
|
|
});
|
|
|
|
const onChange = () => {
|
|
if (!props.disabled) {
|
|
emit('update:modelValue', props.value);
|
|
}
|
|
};
|
|
|
|
return {
|
|
effectiveId,
|
|
labelClasses,
|
|
isChecked,
|
|
onChange,
|
|
};
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
// Styles are very similar to VCheckbox, with adjustments for radio appearance (circle)
|
|
.radio-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
position: relative;
|
|
user-select: none;
|
|
padding-left: 28px; // Space for the custom radio mark
|
|
min-height: 20px;
|
|
font-size: 1rem;
|
|
margin-right: 10px; // Spacing between radio buttons in a group
|
|
|
|
input[type="radio"] {
|
|
position: absolute;
|
|
opacity: 0;
|
|
cursor: pointer;
|
|
height: 0;
|
|
width: 0;
|
|
}
|
|
|
|
.checkmark {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
transform: translateY(-50%);
|
|
height: 20px;
|
|
width: 20px;
|
|
background-color: #fff;
|
|
border: 1px solid #adb5bd;
|
|
border-radius: 50%; // Makes it a circle for radio
|
|
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
|
|
|
|
// Radio mark's inner dot (hidden when not checked)
|
|
&.radio-mark:after {
|
|
content: "";
|
|
position: absolute;
|
|
display: none;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 10px; // Size of the inner dot
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: white;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
}
|
|
|
|
input[type="radio"]:checked ~ .checkmark {
|
|
background-color: #007bff;
|
|
border-color: #007bff;
|
|
}
|
|
|
|
input[type="radio"]:checked ~ .checkmark.radio-mark:after {
|
|
display: block;
|
|
}
|
|
|
|
input[type="radio"]:focus ~ .checkmark {
|
|
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
|
}
|
|
|
|
&.disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.7;
|
|
|
|
input[type="radio"]:disabled ~ .checkmark {
|
|
background-color: #e9ecef;
|
|
border-color: #ced4da;
|
|
}
|
|
|
|
input[type="radio"]:disabled:checked ~ .checkmark {
|
|
background-color: #7badec; // Lighter primary for disabled checked
|
|
border-color: #7badec;
|
|
}
|
|
|
|
input[type="radio"]:disabled:checked ~ .checkmark.radio-mark:after {
|
|
background: #e9ecef; // Match disabled background or a lighter contrast
|
|
}
|
|
}
|
|
|
|
.radio-text-label {
|
|
// margin-left: 0.5rem; // Similar to checkbox, handled by padding-left on root
|
|
vertical-align: middle;
|
|
}
|
|
}
|
|
</style>
|