Skip to content
This repository was archived by the owner on Sep 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/components/Checkbox/Checkbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ const props = defineProps<CheckboxProps>()
const emits = defineEmits<CheckboxEmits>()
const slots = defineSlots<CheckboxSlots>()

const model = defineModel<boolean | 'indeterminate'>({ default: undefined })
const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined })

const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))

const { id: _id, size } = useFormField<CheckboxProps>(props)
const { id: _id, size, color, name, disabled, ariaAttrs } = useFormField<CheckboxProps>(props)
const id = _id.value ?? useId()

const ui = computed(() => checkbox({
size: size.value,
color: props.color,
color: color.value,
required: props.required,
disabled: props.disabled,
checked: Boolean(model.value ?? props.defaultValue),
disabled: disabled.value,
checked: Boolean(modelValue.value ?? props.defaultValue),
}))

function onUpdate(value: any) {
Expand All @@ -61,13 +61,14 @@ function onUpdate(value: any) {
}
</script>

<!-- eslint-disable vue/no-template-shadow -->
<template>
<Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
<div :class="ui.container({ class: props.ui?.container })">
<CheckboxRoot
:id="id"
v-bind="rootProps"
v-model="model"
v-bind="{ ...rootProps, ...ariaAttrs }"
v-model="modelValue"
:name="name"
:disabled="disabled"
:class="ui.base({ class: props.ui?.base })"
Expand Down
2 changes: 1 addition & 1 deletion src/components/FormField/FormField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('form-field', () => {
const sizes = Object.keys(theme.variants.size) as any

it.each<[string, RenderOptions<typeof FormField>]>([
// Props
// Props
['with label and description', { props: { label: 'Username', description: 'Enter your username' } }],
['with required', { props: { label: 'Username', required: true } }],
['with help', { props: { help: 'Username must be unique' } }],
Expand Down
28 changes: 21 additions & 7 deletions src/components/FormField/FormField.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<script lang="ts">
import type { FormFieldInjectedOptions } from '@/ui/keys/form-field.ts'
import type { FormFieldInjectedOptions } from '@/ui/keys/form-field'
import type { VariantProps } from 'tailwind-variants'
import { formFieldInjectionKey, inputIdInjectionKey } from '@/ui/keys/form-field.ts'
import { formFieldInjectionKey, inputIdInjectionKey } from '@/ui/keys/form-field'
import theme from '@/ui/theme/form-field'
import { Label, Primitive } from 'reka-ui'
import { tv } from 'tailwind-variants'
import { computed, provide, ref, useId } from 'vue'

Expand All @@ -13,13 +12,16 @@ type FormFieldVariants = VariantProps<typeof formField>

export interface FormFieldProps {
as?: any
name?: string
label?: string
description?: string
help?: string
error?: string
error?: string | boolean
hint?: string
size?: FormFieldVariants['size']
required?: boolean
eagerValidation?: boolean
validateOnInputDelay?: number
class?: any
ui?: Partial<typeof formField.slots>
}
Expand All @@ -43,12 +45,24 @@ const ui = computed(() => formField({
required: props.required,
}))

const error = computed(() => props.error)

const id = ref(useId())
// Copies id's initial value to bind aria-attributes such as aria-describedby.
// This is required for the RadioGroup component which unsets the id value.
const ariaId = id.value

provide(inputIdInjectionKey, id)

provide(formFieldInjectionKey, computed(() => ({
error: error.value,
name: props.name,
size: props.size,
eagerValidation: props.eagerValidation,
validateOnInputDelay: props.validateOnInputDelay,
hint: props.hint,
description: props.description,
ariaId,
}) as FormFieldInjectedOptions<FormFieldProps>))
</script>

Expand All @@ -61,14 +75,14 @@ provide(formFieldInjectionKey, computed(() => ({
{{ label }}
</slot>
</Label>
<span v-if="hint || !!slots.hint" :class="ui.hint({ class: props.ui?.hint })">
<span v-if="hint || !!slots.hint" :id="`${ariaId}-hint`" :class="ui.hint({ class: props.ui?.hint })">
<slot name="hint" :hint="hint">
{{ hint }}
</slot>
</span>
</div>

<p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<p v-if="description || !!slots.description" :id="`${ariaId}-description`" :class="ui.description({ class: props.ui?.description })">
<slot name="description" :description="description">
{{ description }}
</slot>
Expand All @@ -78,7 +92,7 @@ provide(formFieldInjectionKey, computed(() => ({
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
<slot :error="error" />

<p v-if="(typeof error === 'string' && error) || !!slots.error" :class="ui.error({ class: props.ui?.error })">
<p v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
<slot name="error" :error="error">
{{ error }}
</slot>
Expand Down
Loading