Also improves how buttons are shown with the missing files.
This commit is contained in:
@@ -13,7 +13,7 @@ ### Root Level
|
|||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
- `docs/theming-templating-vue.md` - Design tokens, Tailwind config, layout, shared Vue components, icon and scoring color standards
|
- `docs/theming-templating-vue.md` - Design tokens, Tailwind config, layout, shared Vue components, RadioButtonGroup pill buttons, icon and scoring color standards
|
||||||
|
|
||||||
### Agents
|
### Agents
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ ### Color Palette
|
|||||||
| Token | Hex | RGB | Tailwind Class | Usage |
|
| Token | Hex | RGB | Tailwind Class | Usage |
|
||||||
|-------------|-----------|------------------|-----------------|---------------------------------------------|
|
|-------------|-----------|------------------|-----------------|---------------------------------------------|
|
||||||
| Primary | `#d1ec51` | 209, 236, 81 | `bg-primary`, `text-primary` | Buttons (default), accents, highlights |
|
| Primary | `#d1ec51` | 209, 236, 81 | `bg-primary`, `text-primary` | Buttons (default), accents, highlights |
|
||||||
|
| Primary Dark | `#b5d136` | 181, 209, 54 | `bg-primary-dark`, `text-primary-dark` | Selected/hover state for pill buttons, ~15% darker primary |
|
||||||
| Secondary | `#00b7b3` | 0, 183, 179 | `bg-secondary`, `text-secondary` | Button hover states, secondary accents |
|
| Secondary | `#00b7b3` | 0, 183, 179 | `bg-secondary`, `text-secondary` | Button hover states, secondary accents |
|
||||||
| Background | `#2b303a` | 43, 48, 58 | `bg-surface` | Page background, card backgrounds |
|
| Background | `#2b303a` | 43, 48, 58 | `bg-surface` | Page background, card backgrounds |
|
||||||
| Text | `#ffffff` | 255, 255, 255 | `text-white` | Primary body text on dark background |
|
| Text | `#ffffff` | 255, 255, 255 | `text-white` | Primary body text on dark background |
|
||||||
@@ -96,6 +97,23 @@ ### QuestionCard
|
|||||||
- `has_na` -- show N/A button
|
- `has_na` -- show N/A button
|
||||||
- `details` -- show a text input for additional notes
|
- `details` -- show a text input for additional notes
|
||||||
|
|
||||||
|
### RadioButtonGroup
|
||||||
|
|
||||||
|
Pill-shaped button group that replaces native radio buttons. Options appear as connected segments with rounded outer edges.
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|--------------|-------------------------------|---------|--------------------------------|
|
||||||
|
| `modelValue` | `String \| null` | `null` | Selected value (v-model) |
|
||||||
|
| `options` | `Array<{value, label}>` | required | Options to render |
|
||||||
|
| `name` | `String` | required | HTML radio group name |
|
||||||
|
| `disabled` | `Boolean` | `false` | Disables all options |
|
||||||
|
|
||||||
|
Default state: `bg-primary` with `text-gray-900`.
|
||||||
|
Selected & hover state: `bg-primary-dark`.
|
||||||
|
Keyboard focus: visible ring using `ring-primary-dark`.
|
||||||
|
|
||||||
|
Used in `QuestionCard` (3-option: Yes/No/N/A) and `Screening/Show` (2-option: Yes/No).
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
|
|
||||||
Heroicons is the only icon library. No other icon packages.
|
Heroicons is the only icon library. No other icon packages.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ @theme {
|
|||||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||||
|
|
||||||
--color-primary: #d1ec51;
|
--color-primary: #d1ec51;
|
||||||
|
--color-primary-dark: #b5d136;
|
||||||
--color-secondary: #00b7b3;
|
--color-secondary: #00b7b3;
|
||||||
--color-surface: #2b303a;
|
--color-surface: #2b303a;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import RadioButtonGroup from '@/Components/RadioButtonGroup.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
question: {
|
question: {
|
||||||
@@ -39,6 +40,14 @@ const isTextOnly = computed(() => {
|
|||||||
return !hasRadioButtons.value && props.question.details
|
return !hasRadioButtons.value && props.question.details
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const availableOptions = computed(() => {
|
||||||
|
const opts = []
|
||||||
|
if (props.question.has_yes) opts.push({ value: 'yes', label: 'Yes' })
|
||||||
|
if (props.question.has_no) opts.push({ value: 'no', label: 'No' })
|
||||||
|
if (props.question.has_na) opts.push({ value: 'not_applicable', label: 'N/A' })
|
||||||
|
return opts
|
||||||
|
})
|
||||||
|
|
||||||
const updateValue = (value) => {
|
const updateValue = (value) => {
|
||||||
emit('update:modelValue', { ...props.modelValue, value })
|
emit('update:modelValue', { ...props.modelValue, value })
|
||||||
}
|
}
|
||||||
@@ -65,41 +74,13 @@ const updateTextValue = (event) => {
|
|||||||
|
|
||||||
<!-- Radio button question -->
|
<!-- Radio button question -->
|
||||||
<div v-if="hasRadioButtons">
|
<div v-if="hasRadioButtons">
|
||||||
<div class="flex flex-wrap gap-4 mb-3">
|
<RadioButtonGroup
|
||||||
<label v-if="question.has_yes" class="flex items-center gap-2 cursor-pointer">
|
:modelValue="modelValue.value"
|
||||||
<input
|
@update:modelValue="updateValue($event)"
|
||||||
type="radio"
|
:name="`question-${question.id}`"
|
||||||
:name="`question-${question.id}`"
|
:options="availableOptions"
|
||||||
value="yes"
|
class="mb-3"
|
||||||
:checked="modelValue.value === 'yes'"
|
/>
|
||||||
@change="updateValue('yes')"
|
|
||||||
class="w-4 h-4 text-primary bg-surface border-gray-600 focus:ring-primary focus:ring-offset-surface"
|
|
||||||
/>
|
|
||||||
<span class="text-white">Yes</span>
|
|
||||||
</label>
|
|
||||||
<label v-if="question.has_no" class="flex items-center gap-2 cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
:name="`question-${question.id}`"
|
|
||||||
value="no"
|
|
||||||
:checked="modelValue.value === 'no'"
|
|
||||||
@change="updateValue('no')"
|
|
||||||
class="w-4 h-4 text-primary bg-surface border-gray-600 focus:ring-primary focus:ring-offset-surface"
|
|
||||||
/>
|
|
||||||
<span class="text-white">No</span>
|
|
||||||
</label>
|
|
||||||
<label v-if="question.has_na" class="flex items-center gap-2 cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
:name="`question-${question.id}`"
|
|
||||||
value="not_applicable"
|
|
||||||
:checked="modelValue.value === 'not_applicable'"
|
|
||||||
@change="updateValue('not_applicable')"
|
|
||||||
class="w-4 h-4 text-primary bg-surface border-gray-600 focus:ring-primary focus:ring-offset-surface"
|
|
||||||
/>
|
|
||||||
<span class="text-white">N/A</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Details textarea (conditional) -->
|
<!-- Details textarea (conditional) -->
|
||||||
<div v-if="showDetails" class="mt-2">
|
<div v-if="showDetails" class="mt-2">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|||||||
import { Head, useForm } from '@inertiajs/vue3'
|
import { Head, useForm } from '@inertiajs/vue3'
|
||||||
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'
|
||||||
|
|
||||||
defineOptions({ layout: AppLayout })
|
defineOptions({ layout: AppLayout })
|
||||||
|
|
||||||
@@ -54,30 +55,14 @@ const allAnswered = computed(() => {
|
|||||||
<span class="text-gray-400 font-mono text-sm mt-1 shrink-0">{{ index + 1 }}.</span>
|
<span class="text-gray-400 font-mono text-sm mt-1 shrink-0">{{ index + 1 }}.</span>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-white mb-3">{{ question }}</p>
|
<p class="text-white mb-3">{{ question }}</p>
|
||||||
<div class="flex gap-4">
|
<RadioButtonGroup
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
v-model="form.answers[index + 1]"
|
||||||
<input
|
:name="`question-${index + 1}`"
|
||||||
type="radio"
|
:options="[
|
||||||
:name="`question-${index + 1}`"
|
{ value: 'yes', label: 'Yes' },
|
||||||
value="yes"
|
{ value: 'no', label: 'No' },
|
||||||
v-model="form.answers[index + 1]"
|
]"
|
||||||
class="w-4 h-4 text-primary bg-surface border-gray-600 focus:ring-primary focus:ring-offset-surface"
|
/>
|
||||||
data-cy="yes"
|
|
||||||
/>
|
|
||||||
<span class="text-white">Yes</span>
|
|
||||||
</label>
|
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
:name="`question-${index + 1}`"
|
|
||||||
value="no"
|
|
||||||
v-model="form.answers[index + 1]"
|
|
||||||
class="w-4 h-4 text-primary bg-surface border-gray-600 focus:ring-primary focus:ring-offset-surface"
|
|
||||||
data-cy="no"
|
|
||||||
/>
|
|
||||||
<span class="text-white">No</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<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}`] }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user