mitlist/fe/src/components/valerie/VRadio.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>