braindrops.nicwands.com/shared/Radio.vue
2026-08-11 10:24:27 -04:00

116 lines
2.5 KiB
Vue

<template>
<div class="shared-radio-group" :class="{ 'is-disabled': disabled }">
<span v-if="label" class="group-label">{{ label }}</span>
<label
v-for="option in options"
:key="option.value ?? option"
class="shared-radio"
:class="{ 'is-checked': model === (option.value ?? option) }"
>
<input
type="radio"
:name="name"
:value="option.value ?? option"
:disabled="disabled"
v-model="model"
/>
<span class="dot-wrap">
<span class="dot" />
</span>
<span class="radio-label">{{ option.label ?? option }}</span>
</label>
</div>
</template>
<script setup>
const props = defineProps({
label: String,
name: String,
options: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
})
const model = defineModel()
</script>
<style lang="scss">
.shared-radio-group {
display: flex;
flex-direction: column;
gap: rs(8px);
.group-label {
@include label;
opacity: 0.45;
margin-bottom: rs(4px);
}
&.is-disabled {
opacity: 0.3;
pointer-events: none;
}
}
.shared-radio {
--t-duration: 200ms;
--t-ease: var(--ease-out-quad);
display: inline-flex;
align-items: center;
gap: rs(10px);
cursor: pointer;
user-select: none;
input[type='radio'] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
pointer-events: none;
}
.dot-wrap {
flex-shrink: 0;
width: rs(18px);
height: rs(18px);
border: rs(1px) solid var(--grey-400);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: border-color var(--t-duration) var(--t-ease);
}
.dot {
width: rs(8px);
height: rs(8px);
border-radius: 50%;
background: var(--theme-contrast);
transform: scale(0);
transition: transform var(--t-duration) var(--t-ease);
}
.radio-label {
@include p;
}
&:hover .dot-wrap {
border-color: var(--theme-contrast);
}
&.is-checked {
.dot-wrap {
border-color: var(--theme-contrast);
}
.dot {
transform: scale(1);
}
}
}
</style>