Make sure that the user doesn't select more than eight maybe buttons in the pre-select

This commit is contained in:
2026-03-31 15:30:42 +02:00
parent 17db4962c4
commit 2d7f1d8b1c
2 changed files with 61 additions and 13 deletions

View File

@@ -18,15 +18,41 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
// Array of option values that should be individually disabled.
// An already-selected option is never disabled via this prop.
disabledOptions: {
type: Array,
default: () => [],
},
// Tooltip text shown when hovering over a disabled option.
disabledTooltip: {
type: String,
default: '',
},
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
/**
* Returns true when an option should be treated as disabled.
* An option that is already selected (checked) is never individually disabled
* via disabledOptions — it remains selectable so the user can keep it.
*/
const isOptionDisabled = (value) => {
if (props.disabled) return true
if (props.modelValue === value) return false
return props.disabledOptions.includes(value)
}
const handleChange = (value) => { const handleChange = (value) => {
// Safety guard: do not emit for disabled options
if (isOptionDisabled(value)) return
emit('update:modelValue', value) emit('update:modelValue', value)
} }
const getSegmentClasses = (index) => { const getSegmentClasses = (option, index) => {
const optionDisabled = isOptionDisabled(option.value)
const classes = [ const classes = [
'inline-flex', 'inline-flex',
'items-center', 'items-center',
@@ -40,11 +66,6 @@ const getSegmentClasses = (index) => {
'duration-200', 'duration-200',
'bg-white/5', 'bg-white/5',
'text-gray-400', 'text-gray-400',
'cursor-pointer',
'hover:bg-white/10',
'hover:text-gray-200',
'peer-checked:hover:bg-primary-dark',
'peer-checked:hover:text-gray-900',
'peer-checked:bg-primary', 'peer-checked:bg-primary',
'peer-checked:text-gray-900', 'peer-checked:text-gray-900',
'peer-checked:font-semibold', 'peer-checked:font-semibold',
@@ -54,16 +75,25 @@ const getSegmentClasses = (index) => {
'peer-focus-visible:ring-offset-surface', 'peer-focus-visible:ring-offset-surface',
] ]
if (optionDisabled) {
// Disabled: visually subtle, no pointer, no hover effects
classes.push('opacity-35', 'cursor-not-allowed')
} else {
// Enabled: pointer cursor and hover effects
classes.push(
'cursor-pointer',
'hover:bg-white/10',
'hover:text-gray-200',
'peer-checked:hover:bg-primary-dark',
'peer-checked:hover:text-gray-900',
)
}
// All except last: add divider // All except last: add divider
if (index < props.options.length - 1) { if (index < props.options.length - 1) {
classes.push('border-r', 'border-white/10') classes.push('border-r', 'border-white/10')
} }
// Disabled state
if (props.disabled) {
classes.push('disabled:opacity-50', 'disabled:cursor-not-allowed')
}
return classes.join(' ') return classes.join(' ')
} }
</script> </script>
@@ -80,12 +110,12 @@ const getSegmentClasses = (index) => {
:name="name" :name="name"
:value="option.value" :value="option.value"
:checked="modelValue === option.value" :checked="modelValue === option.value"
:disabled="disabled" :disabled="isOptionDisabled(option.value)"
:data-cy="option.value === 'not_applicable' ? 'na' : option.value" :data-cy="option.value === 'not_applicable' ? 'na' : option.value"
@change="handleChange(option.value)" @change="handleChange(option.value)"
class="sr-only peer" class="sr-only peer"
/> />
<span :class="getSegmentClasses(index)"> <span :class="getSegmentClasses(option, index)" :title="isOptionDisabled(option.value) ? disabledTooltip : ''">
{{ option.label }} {{ option.label }}
</span> </span>
</label> </label>

View File

@@ -2,12 +2,15 @@
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { Head, useForm } from '@inertiajs/vue3' import { Head, useForm } from '@inertiajs/vue3'
import { ArrowLeftIcon } from '@heroicons/vue/24/outline' import { ArrowLeftIcon } from '@heroicons/vue/24/outline'
import { ExclamationTriangleIcon } from '@heroicons/vue/20/solid'
import AppLayout from '@/Layouts/AppLayout.vue' import AppLayout from '@/Layouts/AppLayout.vue'
import AppButton from '@/Components/AppButton.vue' import AppButton from '@/Components/AppButton.vue'
import RadioButtonGroup from '@/Components/RadioButtonGroup.vue' import RadioButtonGroup from '@/Components/RadioButtonGroup.vue'
defineOptions({ layout: AppLayout }) defineOptions({ layout: AppLayout })
const MAX_MAYBE_ANSWERS = 8
const props = defineProps({ const props = defineProps({
screening: { screening: {
type: Object, type: Object,
@@ -49,6 +52,14 @@ const allAnswered = computed(() => {
return Object.values(form.answers).every(v => v !== null) return Object.values(form.answers).every(v => v !== null)
}) })
const unknownCount = computed(() => {
return Object.values(form.answers).filter(v => v === 'unknown').length
})
const maybeLimitReached = computed(() => {
return unknownCount.value >= MAX_MAYBE_ANSWERS
})
const bottomBackButtonRef = ref(null) const bottomBackButtonRef = ref(null)
const showStickyBack = ref(false) const showStickyBack = ref(false)
@@ -92,6 +103,11 @@ onUnmounted(() => {
<h1 class="text-3xl font-bold text-white mb-2">Pre-Screening Questions</h1> <h1 class="text-3xl font-bold text-white mb-2">Pre-Screening Questions</h1>
<p class="text-gray-400 mb-8">Please answer all questions to proceed.</p> <p class="text-gray-400 mb-8">Please answer all questions to proceed.</p>
<p v-if="maybeLimitReached" class="text-amber-400/80 text-sm mb-6 flex items-center gap-2">
<ExclamationTriangleIcon class="h-4 w-4 shrink-0" />
You've used all {{ MAX_MAYBE_ANSWERS }} "I don't know" answers. Please choose Yes or No for the remaining questions.
</p>
<div class="space-y-4 mb-8"> <div class="space-y-4 mb-8">
<div <div
v-for="(question, index) in questions" v-for="(question, index) in questions"
@@ -111,6 +127,8 @@ onUnmounted(() => {
{ value: 'unknown', label: 'I don\'t know' }, { value: 'unknown', label: 'I don\'t know' },
{ value: 'no', label: 'No' }, { value: 'no', label: 'No' },
]" ]"
:disabled-options="maybeLimitReached && form.answers[index + 1] !== 'unknown' ? ['unknown'] : []"
disabled-tooltip="You can only select &quot;I don't know&quot; for a maximum of 8 questions"
/> />
<p v-if="form.errors[`answers.${index + 1}`]" class="text-red-500 text-sm mt-1"> <p v-if="form.errors[`answers.${index + 1}`]" class="text-red-500 text-sm mt-1">
{{ form.errors[`answers.${index + 1}`] }} {{ form.errors[`answers.${index + 1}`] }}