Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 2x 2x 10x 10x 4x 4x 10x 10x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | <script setup lang="ts">
/**
* FzCheckboxGroup Component
*
* A container component for managing multiple related checkboxes as a group.
* Provides group-level labeling, help text, error handling, and accessibility features.
* Supports both flat checkbox lists and hierarchical parent-child structures.
*
* @component
* @example
* // Basic checkbox group
* <FzCheckboxGroup
* v-model="selectedOptions"
* label="Choose options"
* :options="[
* { label: 'Option 1', value: 'opt1' },
* { label: 'Option 2', value: 'opt2' }
* ]"
* />
*
* @example
* // Hierarchical checkbox group with children
* <FzCheckboxGroup
* v-model="selection"
* label="Select features"
* :options="[
* {
* label: 'All Features',
* value: 'all',
* children: [
* { label: 'Feature A', value: 'a' },
* { label: 'Feature B', value: 'b' }
* ]
* }
* ]"
* />
*/
import { computed, useSlots, watch } from "vue";
import { FzCheckboxGroupProps } from "./types";
import { generateGroupId } from "./utils";
import FzCheckboxGroupOption from "./components/FzCheckboxGroupOption.vue";
import ErrorAlert from "./components/ErrorAlert.vue";
const props = defineProps<FzCheckboxGroupProps>();
const slots = useSlots();
/**
* Deprecation warning for size prop.
* Watches the size prop and warns once on mount if it's provided.
* Using watch with immediate:true ensures the warning only fires once per component instance.
*/
watch(
() => props.size,
(size) => {
if (size !== undefined) {
console.warn(
'[FzCheckboxGroup] The "size" prop is deprecated and will be removed in a future version. Checkboxes now have a fixed size.'
);
}
},
{ immediate: true }
);
/** Unique identifier for the checkbox group, used for ARIA relationships */
const id: string = generateGroupId();
/** Dynamic classes for help text based on disabled state */
const computedHelpTextClass = computed<string[]>(() => [
"text-sm",
props.disabled ? "text-grey-400" : "text-grey-500",
]);
/**
* Two-way binding for selected checkbox values.
* Supports string, number, and boolean values.
* Always an array, even when empty.
*/
const model = defineModel<(string | number | boolean)[]>({
required: true,
default: [],
});
/** Base layout for the label element */
const staticLabeldClass: string = "flex flex-col";
/** Base layout for the root container */
const staticContainerClass: string = "flex flex-col gap-10";
/** Base layout for the checkboxes container */
const staticSlotContainerClass: string = "flex items-start";
/** Dynamic label classes based on spacing and disabled state */
const computedLabelClass = computed<string[]>(() => [
"text-base",
"gap-6",
props.disabled ? "text-grey-400" : "text-core-black",
]);
/** Dynamic container classes */
const computedContainerClass = computed<string[]>(() => ["text-base"]);
/**
* Dynamic classes for the checkbox container.
* Handles both horizontal and vertical layouts with appropriate spacing.
*/
const computedSlotContainerClass = computed<string[]>(() => [
"text-base",
props.horizontal ? "gap-16" : "gap-8",
props.horizontal ? "flex-row" : "flex-col",
]);
/**
* Computes the aria-describedby attribute value for the checkbox group.
* Combines help text and error message IDs when present.
*
* @returns Space-separated string of IDs, or undefined if no descriptions
*
* @example
* // Only help text
* "fz-checkbox-group-123-help"
*
* @example
* // Only error
* "fz-checkbox-group-123-error"
*
* @example
* // Both help and error
* "fz-checkbox-group-123-help fz-checkbox-group-123-error"
*/
const computedAriaDescribedby = computed<string | undefined>(() => {
const descriptions: string[] = [];
if (slots.help) {
descriptions.push(`${id}-help`);
}
if (props.error && slots.error) {
descriptions.push(`${id}-error`);
}
return descriptions.length > 0 ? descriptions.join(" ") : undefined;
});
</script>
<template>
<!-- Root container for the entire checkbox group -->
<div :class="[staticContainerClass, computedContainerClass]">
<!--
Group label with optional required indicator and help text
Connected to checkbox group via aria-labelledby
-->
<label :id="id + '-label'" :class="[staticLabeldClass, computedLabelClass]">
<!-- Main label text with required asterisk if applicable -->
<span>{{ label }}<span v-if="required"> *</span></span>
<!-- Optional help text slot for additional context -->
<p :id="id + '-help'" :class="computedHelpTextClass" v-if="$slots.help">
<slot name="help" />
</p>
</label>
<!--
Checkbox group container with ARIA group role
- role="group": Identifies this as a group of related form controls
- aria-labelledby: Links to the label element
- aria-describedby: Links to help text and/or error message for screen readers
- aria-required: Indicates if selection is mandatory
- aria-invalid: Indicates validation state
-->
<div
:class="[staticSlotContainerClass, computedSlotContainerClass]"
:id="id"
role="group"
:aria-labelledby="id + '-label'"
:aria-describedby="computedAriaDescribedby"
:aria-required="required ? 'true' : 'false'"
:aria-invalid="error ? 'true' : 'false'"
>
<!--
Render each checkbox option
Supports both simple checkboxes and parent-child hierarchies
Key uses value if available, falls back to label for uniqueness
-->
<FzCheckboxGroupOption
v-for="option in options"
:key="option.value ? option.value.toString() : option.label"
v-model="model"
:disabled="disabled"
v-bind="option"
:emphasis="emphasis"
:error="error"
/>
</div>
<!-- Error message display with accessible ARIA live region -->
<ErrorAlert v-if="error && $slots.error" :id="id + '-error'">
<slot name="error" />
</ErrorAlert>
</div>
</template>
|