diff --git a/resources/js/Components/RadioButtonGroup.vue b/resources/js/Components/RadioButtonGroup.vue index a1931bd..bf8b96a 100644 --- a/resources/js/Components/RadioButtonGroup.vue +++ b/resources/js/Components/RadioButtonGroup.vue @@ -18,15 +18,41 @@ const props = defineProps({ type: Boolean, 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']) +/** + * 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) => { + // Safety guard: do not emit for disabled options + if (isOptionDisabled(value)) return emit('update:modelValue', value) } -const getSegmentClasses = (index) => { +const getSegmentClasses = (option, index) => { + const optionDisabled = isOptionDisabled(option.value) + const classes = [ 'inline-flex', 'items-center', @@ -40,11 +66,6 @@ const getSegmentClasses = (index) => { 'duration-200', 'bg-white/5', '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:text-gray-900', 'peer-checked:font-semibold', @@ -54,16 +75,25 @@ const getSegmentClasses = (index) => { '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 if (index < props.options.length - 1) { classes.push('border-r', 'border-white/10') } - // Disabled state - if (props.disabled) { - classes.push('disabled:opacity-50', 'disabled:cursor-not-allowed') - } - return classes.join(' ') } @@ -80,12 +110,12 @@ const getSegmentClasses = (index) => { :name="name" :value="option.value" :checked="modelValue === option.value" - :disabled="disabled" + :disabled="isOptionDisabled(option.value)" :data-cy="option.value === 'not_applicable' ? 'na' : option.value" @change="handleChange(option.value)" class="sr-only peer" /> - + {{ option.label }} diff --git a/resources/js/Pages/Screening/Show.vue b/resources/js/Pages/Screening/Show.vue index c1a44f8..427599a 100644 --- a/resources/js/Pages/Screening/Show.vue +++ b/resources/js/Pages/Screening/Show.vue @@ -2,12 +2,15 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { Head, useForm } from '@inertiajs/vue3' import { ArrowLeftIcon } from '@heroicons/vue/24/outline' +import { ExclamationTriangleIcon } from '@heroicons/vue/20/solid' import AppLayout from '@/Layouts/AppLayout.vue' import AppButton from '@/Components/AppButton.vue' import RadioButtonGroup from '@/Components/RadioButtonGroup.vue' defineOptions({ layout: AppLayout }) +const MAX_MAYBE_ANSWERS = 8 + const props = defineProps({ screening: { type: Object, @@ -49,6 +52,14 @@ const allAnswered = computed(() => { 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 showStickyBack = ref(false) @@ -92,6 +103,11 @@ onUnmounted(() => {

Pre-Screening Questions

Please answer all questions to proceed.

+

+ + You've used all {{ MAX_MAYBE_ANSWERS }} "I don't know" answers. Please choose Yes or No for the remaining questions. +

+
{ { value: 'unknown', label: 'I don\'t know' }, { value: 'no', label: 'No' }, ]" + :disabled-options="maybeLimitReached && form.answers[index + 1] !== 'unknown' ? ['unknown'] : []" + disabled-tooltip="You can only select "I don't know" for a maximum of 8 questions" />

{{ form.errors[`answers.${index + 1}`] }}