feat(frontend): add FormCheckbox primitive component

This commit is contained in:
kolaente 2026-04-17 14:36:28 +02:00 committed by kolaente
parent 740546ee5a
commit 59e7c9bce3
2 changed files with 83 additions and 0 deletions

View File

@ -0,0 +1,42 @@
import {describe, it, expect} from 'vitest'
import {mount} from '@vue/test-utils'
import FormCheckbox from './FormCheckbox.vue'
describe('FormCheckbox', () => {
it('renders a Bulma-classed checkbox label', () => {
const wrapper = mount(FormCheckbox, {props: {label: 'Enable thing'}})
const label = wrapper.find('label.checkbox')
expect(label.exists()).toBe(true)
expect(label.text()).toContain('Enable thing')
expect(label.find('input[type="checkbox"]').exists()).toBe(true)
})
it('supports v-model (boolean)', async () => {
const wrapper = mount(FormCheckbox, {
props: {
label: 'Toggle',
modelValue: false,
'onUpdate:modelValue': (val: boolean) => wrapper.setProps({modelValue: val}),
},
})
const input = wrapper.find('input[type="checkbox"]')
expect((input.element as HTMLInputElement).checked).toBe(false)
await input.setValue(true)
expect(wrapper.props('modelValue')).toBe(true)
})
it('applies disabled', () => {
const wrapper = mount(FormCheckbox, {
props: {label: 'X', disabled: true},
})
expect(wrapper.find('input').attributes('disabled')).toBe('')
})
it('renders slot content instead of label prop when slot is provided', () => {
const wrapper = mount(FormCheckbox, {
slots: {default: '<span>Custom <b>content</b></span>'},
})
expect(wrapper.find('label.checkbox').html()).toContain('<b>content</b>')
})
})

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
interface Props {
modelValue?: boolean
label?: string
disabled?: boolean
}
defineProps<Props>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
function handleChange(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).checked)
}
</script>
<template>
<label class="checkbox">
<input
type="checkbox"
:checked="modelValue"
:disabled="disabled || undefined"
@change="handleChange"
>
<slot>{{ label }}</slot>
</label>
</template>
<style lang="scss" scoped>
label.checkbox {
display: flex;
align-items: center;
gap: .5rem;
inline-size: fit-content;
&:not(:last-child) {
margin-block-end: .75rem;
}
}
</style>