@fiscozen/checkbox 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -0
- package/dist/checkbox.js +743 -569
- package/dist/checkbox.umd.cjs +2 -2
- package/dist/src/FzCheckboxCard.vue.d.ts +240 -0
- package/dist/src/FzCheckboxGroup.vue.d.ts +8 -2
- package/dist/src/__tests__/FzCheckboxCard.spec.d.ts +1 -0
- package/dist/src/common.d.ts +16 -5
- package/dist/src/index.d.ts +2 -1
- package/dist/src/types.d.ts +71 -2
- package/dist/style.css +1 -1
- package/package.json +7 -7
- package/src/FzCheckboxCard.vue +209 -0
- package/src/FzCheckboxGroup.vue +33 -10
- package/src/__tests__/FzCheckboxCard.spec.ts +423 -0
- package/src/__tests__/__snapshots__/FzCheckboxCard.spec.ts.snap +17 -0
- package/src/common.ts +19 -0
- package/src/index.ts +6 -1
- package/src/types.ts +86 -1
- package/tsconfig.tsbuildinfo +1 -1
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FzTooltipProps } from '@fiscozen/tooltip';
|
|
1
|
+
import { FzTooltipProps, FzTooltipStatus } from '@fiscozen/tooltip';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Props for the FzCheckbox component.
|
|
@@ -136,7 +136,7 @@ export type FzCheckboxGroupProps = {
|
|
|
136
136
|
* }
|
|
137
137
|
* ]
|
|
138
138
|
*/
|
|
139
|
-
options
|
|
139
|
+
options?: ParentCheckbox[];
|
|
140
140
|
/**
|
|
141
141
|
* Applies emphasis styling to all checkboxes in the group.
|
|
142
142
|
* Icons turn blue when checked.
|
|
@@ -190,3 +190,72 @@ export type ParentCheckbox = ChildCheckbox & {
|
|
|
190
190
|
* Inherits all FzCheckboxProps except 'size' which is controlled by the parent group.
|
|
191
191
|
*/
|
|
192
192
|
export type ChildCheckbox = Omit<FzCheckboxProps, "size">;
|
|
193
|
+
/**
|
|
194
|
+
* Shared props for all FzCheckboxCard variants.
|
|
195
|
+
*/
|
|
196
|
+
type FzCheckboxCardBaseProps = Omit<FzCheckboxProps, "standalone" | "indeterminate" | "ariaOwns" | "checkboxId" | "value" | "tooltip"> & {
|
|
197
|
+
/**
|
|
198
|
+
* Value associated with the card when used in array v-model.
|
|
199
|
+
* Falls back to label if not provided.
|
|
200
|
+
*/
|
|
201
|
+
value?: string | number;
|
|
202
|
+
/**
|
|
203
|
+
* Primary title text displayed in the card.
|
|
204
|
+
*/
|
|
205
|
+
title: string;
|
|
206
|
+
/**
|
|
207
|
+
* Optional secondary description text below the title.
|
|
208
|
+
*/
|
|
209
|
+
subtitle?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Alt text for the card image.
|
|
212
|
+
*/
|
|
213
|
+
imageAlt?: string;
|
|
214
|
+
/**
|
|
215
|
+
* Text to display in the tooltip.
|
|
216
|
+
*/
|
|
217
|
+
tooltip?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Status of the tooltip (determines color and icon).
|
|
220
|
+
*/
|
|
221
|
+
tooltipStatus?: FzTooltipStatus;
|
|
222
|
+
/**
|
|
223
|
+
* Controls whether the checkbox icon is shown inside the card.
|
|
224
|
+
*
|
|
225
|
+
* @default true
|
|
226
|
+
*/
|
|
227
|
+
hasCheckbox?: boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Group name for the checkbox, used for form submission.
|
|
230
|
+
*/
|
|
231
|
+
name?: string;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Horizontal card layout: image left, text right (compact).
|
|
235
|
+
* Image is optional.
|
|
236
|
+
*
|
|
237
|
+
* @default variant is 'horizontal' when omitted
|
|
238
|
+
*/
|
|
239
|
+
type FzCheckboxCardHorizontal = FzCheckboxCardBaseProps & {
|
|
240
|
+
variant?: "horizontal";
|
|
241
|
+
imageUrl?: string;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Vertical card layout: image top, text bottom (full-width image).
|
|
245
|
+
* Image is required — the vertical layout is designed around the image.
|
|
246
|
+
*/
|
|
247
|
+
type FzCheckboxCardVertical = FzCheckboxCardBaseProps & {
|
|
248
|
+
variant: "vertical";
|
|
249
|
+
imageUrl: string;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Props for the FzCheckboxCard component.
|
|
253
|
+
*
|
|
254
|
+
* A card-style checkbox with title, subtitle, optional image and tooltip.
|
|
255
|
+
* Uses a discriminated union on `variant` to enforce that the vertical layout
|
|
256
|
+
* always includes an image (since the layout is designed around it).
|
|
257
|
+
*
|
|
258
|
+
* Uses array v-model for multi-select.
|
|
259
|
+
*/
|
|
260
|
+
export type FzCheckboxCardProps = FzCheckboxCardHorizontal | FzCheckboxCardVertical;
|
|
261
|
+
export {};
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.fz-icon-button-wrapper[data-v-b4be112d]>button{gap:0!important;min-width:0!important}.fz-icon-button-wrapper--backoffice[data-v-b4be112d]>button{padding-left:5px;padding-right:5px}.fz-icon-button-wrapper--frontoffice[data-v-b4be112d]>button{padding-left:11px;padding-right:11px}.fz-container[data-v-8c40daeb]{display:flex}.fz-container--vertical[data-v-8c40daeb]{flex-direction:column}.fz-container--horizontal[data-v-8c40daeb]{flex-direction:row;flex-wrap:nowrap}.fz-container.align-items-start[data-v-8c40daeb]{align-items:flex-start}.fz-container.align-items-center[data-v-8c40daeb]{align-items:center}.fz-container.align-items-end[data-v-8c40daeb]{align-items:flex-end}.fz-container.align-items-stretch[data-v-8c40daeb]{align-items:stretch}.fz-container.align-items-baseline[data-v-8c40daeb]{align-items:baseline}.fz-container--horizontal.layout-expand-first[data-v-8c40daeb]>*:first-child{flex-grow:1}.fz-container--horizontal.layout-expand-all[data-v-8c40daeb]>*{flex-grow:1}.fz-container--horizontal.layout-space-between[data-v-8c40daeb]{justify-content:space-between}.fz-container--horizontal.layout-expand-last[data-v-8c40daeb]>*:last-child{flex-grow:1}.fz-container--vertical.gap-main-content-sm[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--main-content-sm, 32px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-main-content-base[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--main-content-base, 48px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-main-content-lg[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--main-content-lg, 64px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-none[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-none, 0px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-xs[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-xs, 8px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-sm[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-sm, 16px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-base[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-base, 24px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-lg[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-lg, 32px)) + var(--paragraph-gap, 8px))}.fz-container--horizontal[data-v-8c40daeb]>p+p{margin-top:0}.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(2),.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(2)~*{flex-basis:50%;flex-grow:0;flex-shrink:1}.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(3),.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(3)~*{flex-basis:33.333%;flex-grow:0;flex-shrink:1}.fz-button-group[data-v-79dd8b6f]>.fz-icon-button-wrapper{flex-basis:initial!important;flex-grow:0!important;flex-shrink:0!important}.fz__tooltip__text[data-v-94597a6c]{overflow-wrap:anywhere}.bg-semantic-info-50[data-v-f72b7e4a]{background-color:#f3f7ff}.bg-semantic-error-50[data-v-f72b7e4a]{background-color:#fef3f3}.bg-semantic-warning-50[data-v-f72b7e4a]{background-color:#fff8f3}.bg-semantic-success-50[data-v-f72b7e4a]{background-color:#f2f8f6}.fz-hidden-input[data-v-2b198cc9]{opacity:0;margin:0;height:0;border:0 none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.group[data-error]:hover:not([data-disabled]) .peer~label>div[data-v-2b198cc9]{color:var(--semantic-error-300)!important}.group[data-error]:hover:not([data-disabled]) .peer~label[data-v-2b198cc9]{color:var(--semantic-error-300)!important}.group[data-emphasis]:hover:not([data-disabled]):not([data-error]) .peer~label>div[data-v-2b198cc9]{color:var(--blue-600)!important}.group:hover:not([data-emphasis]):not([data-error]):not([data-disabled]) .peer:checked~label>div[data-v-2b198cc9]{color:var(--core-black)!important}.group:hover:not([data-emphasis]):not([data-error]):not([data-disabled]) .peer:indeterminate~label>div[data-v-2b198cc9]{color:var(--core-black)!important}.group:hover:not([data-emphasis]):not([data-error]):not([data-disabled]) .peer:not(:checked):not(:indeterminate)~label>div[data-v-2b198cc9]{color:var(--blue-600)!important}
|
|
1
|
+
.fz-icon-button-wrapper[data-v-b4be112d]>button{gap:0!important;min-width:0!important}.fz-icon-button-wrapper--backoffice[data-v-b4be112d]>button{padding-left:5px;padding-right:5px}.fz-icon-button-wrapper--frontoffice[data-v-b4be112d]>button{padding-left:11px;padding-right:11px}.fz-container[data-v-8c40daeb]{display:flex}.fz-container--vertical[data-v-8c40daeb]{flex-direction:column}.fz-container--horizontal[data-v-8c40daeb]{flex-direction:row;flex-wrap:nowrap}.fz-container.align-items-start[data-v-8c40daeb]{align-items:flex-start}.fz-container.align-items-center[data-v-8c40daeb]{align-items:center}.fz-container.align-items-end[data-v-8c40daeb]{align-items:flex-end}.fz-container.align-items-stretch[data-v-8c40daeb]{align-items:stretch}.fz-container.align-items-baseline[data-v-8c40daeb]{align-items:baseline}.fz-container--horizontal.layout-expand-first[data-v-8c40daeb]>*:first-child{flex-grow:1}.fz-container--horizontal.layout-expand-all[data-v-8c40daeb]>*{flex-grow:1}.fz-container--horizontal.layout-space-between[data-v-8c40daeb]{justify-content:space-between}.fz-container--horizontal.layout-expand-last[data-v-8c40daeb]>*:last-child{flex-grow:1}.fz-container--vertical.gap-main-content-sm[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--main-content-sm, 32px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-main-content-base[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--main-content-base, 48px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-main-content-lg[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--main-content-lg, 64px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-none[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-none, 0px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-xs[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-xs, 8px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-sm[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-sm, 16px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-base[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-base, 24px)) + var(--paragraph-gap, 8px))}.fz-container--vertical.gap-section-content-lg[data-v-8c40daeb]>p+p{margin-top:calc((0px - var(--section-content-lg, 32px)) + var(--paragraph-gap, 8px))}.fz-container--horizontal[data-v-8c40daeb]>p+p{margin-top:0}.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(2),.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(2)~*{flex-basis:50%;flex-grow:0;flex-shrink:1}.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(3),.fz-button-group[data-v-79dd8b6f]>*:nth-child(1):nth-last-child(3)~*{flex-basis:33.333%;flex-grow:0;flex-shrink:1}.fz-button-group[data-v-79dd8b6f]>.fz-icon-button-wrapper{flex-basis:initial!important;flex-grow:0!important;flex-shrink:0!important}.fz__tooltip__text[data-v-94597a6c]{overflow-wrap:anywhere}.bg-semantic-info-50[data-v-f72b7e4a]{background-color:#f3f7ff}.bg-semantic-error-50[data-v-f72b7e4a]{background-color:#fef3f3}.bg-semantic-warning-50[data-v-f72b7e4a]{background-color:#fff8f3}.bg-semantic-success-50[data-v-f72b7e4a]{background-color:#f2f8f6}.fz-hidden-input[data-v-2b198cc9]{opacity:0;margin:0;height:0;border:0 none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.group[data-error]:hover:not([data-disabled]) .peer~label>div[data-v-2b198cc9]{color:var(--semantic-error-300)!important}.group[data-error]:hover:not([data-disabled]) .peer~label[data-v-2b198cc9]{color:var(--semantic-error-300)!important}.group[data-emphasis]:hover:not([data-disabled]):not([data-error]) .peer~label>div[data-v-2b198cc9]{color:var(--blue-600)!important}.group:hover:not([data-emphasis]):not([data-error]):not([data-disabled]) .peer:checked~label>div[data-v-2b198cc9]{color:var(--core-black)!important}.group:hover:not([data-emphasis]):not([data-error]):not([data-disabled]) .peer:indeterminate~label>div[data-v-2b198cc9]{color:var(--core-black)!important}.group:hover:not([data-emphasis]):not([data-error]):not([data-disabled]) .peer:not(:checked):not(:indeterminate)~label>div[data-v-2b198cc9]{color:var(--blue-600)!important}.fz-hidden-input[data-v-d850ec45]{opacity:0;margin:0;height:0;border:0 none;-webkit-appearance:none;-moz-appearance:none;appearance:none}
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiscozen/checkbox",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Design System Checkbox and Checkbox Group component",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [],
|
|
8
8
|
"author": "Cristian Barraco",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fiscozen/
|
|
11
|
-
"@fiscozen/
|
|
12
|
-
"@fiscozen/
|
|
13
|
-
"@fiscozen/
|
|
14
|
-
"@fiscozen/tooltip": "1.0.2"
|
|
10
|
+
"@fiscozen/alert": "1.0.1",
|
|
11
|
+
"@fiscozen/badge": "1.0.1",
|
|
12
|
+
"@fiscozen/tooltip": "1.0.3",
|
|
13
|
+
"@fiscozen/composables": "1.0.1"
|
|
15
14
|
},
|
|
16
15
|
"peerDependencies": {
|
|
17
16
|
"tailwindcss": "^3.4.1",
|
|
18
|
-
"vue": "^3.4.13"
|
|
17
|
+
"vue": "^3.4.13",
|
|
18
|
+
"@fiscozen/icons": "^0.1.37"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@rushstack/eslint-patch": "^1.3.3",
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject } from "vue";
|
|
3
|
+
import { FzCheckboxCardProps } from "./types";
|
|
4
|
+
import { generateCheckboxId } from "./utils";
|
|
5
|
+
import { CHECKED_SET_KEY } from "./common";
|
|
6
|
+
import { FzIcon, type IconVariant } from "@fiscozen/icons";
|
|
7
|
+
import { FzTooltip } from "@fiscozen/tooltip";
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<FzCheckboxCardProps>(), {
|
|
10
|
+
variant: "horizontal",
|
|
11
|
+
hasCheckbox: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const computedValue = computed<string | number>(
|
|
15
|
+
() => props.value ?? props.label,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const id: string = generateCheckboxId();
|
|
19
|
+
|
|
20
|
+
const model = defineModel<(string | number | boolean)[]>({
|
|
21
|
+
required: true,
|
|
22
|
+
default: [],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const injected = inject(CHECKED_SET_KEY, null);
|
|
26
|
+
|
|
27
|
+
const isChecked = computed<boolean>(() => {
|
|
28
|
+
if (model.value == null) return false;
|
|
29
|
+
if (injected && injected.source.value === model.value) {
|
|
30
|
+
return injected.set.value.has(computedValue.value);
|
|
31
|
+
}
|
|
32
|
+
return model.value.includes(computedValue.value);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function handleChange() {
|
|
36
|
+
if (props.disabled) return;
|
|
37
|
+
const current = model.value ?? [];
|
|
38
|
+
if (isChecked.value) {
|
|
39
|
+
model.value = current.filter((v) => v !== computedValue.value);
|
|
40
|
+
} else {
|
|
41
|
+
model.value = [...current, computedValue.value];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const CHECKBOX_ICONS = Object.freeze({
|
|
46
|
+
CHECKED: "square-check",
|
|
47
|
+
UNCHECKED: "square",
|
|
48
|
+
} as const);
|
|
49
|
+
|
|
50
|
+
const CHECKBOX_ICON_VARIANTS = Object.freeze({
|
|
51
|
+
SOLID: "fas",
|
|
52
|
+
REGULAR: "far",
|
|
53
|
+
} as const);
|
|
54
|
+
|
|
55
|
+
const computedIconName = computed<string>(() =>
|
|
56
|
+
isChecked.value ? CHECKBOX_ICONS.CHECKED : CHECKBOX_ICONS.UNCHECKED,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const computedIconVariant = computed<IconVariant>(() =>
|
|
60
|
+
isChecked.value ? CHECKBOX_ICON_VARIANTS.SOLID : CHECKBOX_ICON_VARIANTS.REGULAR,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const computedIconColor = computed<string>(() => {
|
|
64
|
+
if (props.disabled) return "text-grey-300";
|
|
65
|
+
if (props.error) return "text-semantic-error-200";
|
|
66
|
+
if (props.emphasis) return "text-blue-500";
|
|
67
|
+
return isChecked.value ? "text-blue-500" : "text-grey-400";
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const showImage = computed(() => props.variant === "vertical" || !!props.imageUrl);
|
|
71
|
+
|
|
72
|
+
const staticInputClass: string = "w-0 h-0 peer fz-hidden-input";
|
|
73
|
+
|
|
74
|
+
const labelClass = computed(() => ({
|
|
75
|
+
"flex-col": props.variant === "vertical",
|
|
76
|
+
"flex-row": props.variant === "horizontal",
|
|
77
|
+
"pb-20": props.variant === "vertical" && (!isChecked.value || props.disabled),
|
|
78
|
+
"pb-12": props.variant === "horizontal" && (!isChecked.value || props.disabled),
|
|
79
|
+
"pb-[19px]": props.variant === "vertical" && isChecked.value && !props.disabled,
|
|
80
|
+
"pb-[11px]": props.variant === "horizontal" && isChecked.value && !props.disabled,
|
|
81
|
+
"pt-[11px]": isChecked.value && !props.disabled,
|
|
82
|
+
"gap-20": props.variant === "vertical",
|
|
83
|
+
"gap-12": props.variant === "horizontal",
|
|
84
|
+
"border-2 px-[11px] border-blue-500":
|
|
85
|
+
isChecked.value && !props.disabled,
|
|
86
|
+
"border-1 border-grey-300":
|
|
87
|
+
!isChecked.value || props.disabled,
|
|
88
|
+
"hover:bg-[#f9faff] peer-focus:outline peer-focus:bg-[#f9faff] peer-focus:outline-blue-200 peer-focus:outline-2 peer-checked:bg-[#f9faff]":
|
|
89
|
+
!props.disabled,
|
|
90
|
+
}));
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<div>
|
|
95
|
+
<input
|
|
96
|
+
type="checkbox"
|
|
97
|
+
:id="id"
|
|
98
|
+
:class="[staticInputClass]"
|
|
99
|
+
:value="computedValue"
|
|
100
|
+
:disabled="disabled"
|
|
101
|
+
:checked="isChecked"
|
|
102
|
+
:name="name"
|
|
103
|
+
:required="required"
|
|
104
|
+
tabindex="0"
|
|
105
|
+
:aria-checked="isChecked"
|
|
106
|
+
:aria-required="required ? 'true' : 'false'"
|
|
107
|
+
:aria-invalid="error ? 'true' : 'false'"
|
|
108
|
+
@change="handleChange"
|
|
109
|
+
/>
|
|
110
|
+
<label
|
|
111
|
+
:class="[
|
|
112
|
+
'relative flex block rounded-lg border-solid pt-12 px-12 cursor-pointer w-full',
|
|
113
|
+
labelClass,
|
|
114
|
+
]"
|
|
115
|
+
:for="id"
|
|
116
|
+
>
|
|
117
|
+
<FzIcon
|
|
118
|
+
v-if="hasCheckbox"
|
|
119
|
+
:name="computedIconName"
|
|
120
|
+
size="md"
|
|
121
|
+
:class="[
|
|
122
|
+
'shrink-0',
|
|
123
|
+
computedIconColor,
|
|
124
|
+
{
|
|
125
|
+
'absolute top-[23px] left-[23px]': variant === 'vertical',
|
|
126
|
+
'self-start': variant === 'horizontal',
|
|
127
|
+
},
|
|
128
|
+
]"
|
|
129
|
+
:variant="computedIconVariant"
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
/>
|
|
132
|
+
|
|
133
|
+
<picture
|
|
134
|
+
v-if="showImage"
|
|
135
|
+
:class="[
|
|
136
|
+
'rounded overflow-hidden',
|
|
137
|
+
{
|
|
138
|
+
'shrink-0 size-[58px]': variant === 'horizontal',
|
|
139
|
+
'w-full aspect-[16/9]': variant === 'vertical',
|
|
140
|
+
'opacity-30': props.disabled,
|
|
141
|
+
},
|
|
142
|
+
]"
|
|
143
|
+
:title="imageAlt || ''"
|
|
144
|
+
>
|
|
145
|
+
<img
|
|
146
|
+
:src="imageUrl"
|
|
147
|
+
:alt="imageAlt || ''"
|
|
148
|
+
class="object-cover h-full w-full"
|
|
149
|
+
/>
|
|
150
|
+
</picture>
|
|
151
|
+
|
|
152
|
+
<div
|
|
153
|
+
class="flex flex-row w-full justify-between min-w-0"
|
|
154
|
+
>
|
|
155
|
+
<div class="justify-center flex flex-col w-full grow-0 min-w-0 gap-4">
|
|
156
|
+
<p
|
|
157
|
+
:class="[
|
|
158
|
+
'font-medium break-words !m-0 !leading-[20px]',
|
|
159
|
+
{ 'text-grey-300': props.disabled },
|
|
160
|
+
]"
|
|
161
|
+
>
|
|
162
|
+
{{ title }}
|
|
163
|
+
</p>
|
|
164
|
+
<p
|
|
165
|
+
v-if="subtitle"
|
|
166
|
+
:class="[
|
|
167
|
+
'font-normal text-sm mt-4 break-words !m-0 !leading-[16px]',
|
|
168
|
+
{
|
|
169
|
+
'text-grey-300': props.disabled,
|
|
170
|
+
'text-grey-500': !props.disabled,
|
|
171
|
+
},
|
|
172
|
+
]"
|
|
173
|
+
>
|
|
174
|
+
{{ subtitle }}
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
<FzTooltip
|
|
178
|
+
v-if="tooltip"
|
|
179
|
+
:class="{
|
|
180
|
+
'self-center': props.variant === 'horizontal',
|
|
181
|
+
'self-start': props.variant === 'vertical',
|
|
182
|
+
'ml-8': props.variant === 'vertical',
|
|
183
|
+
'ml-12': props.variant === 'horizontal',
|
|
184
|
+
}"
|
|
185
|
+
:disabled="props.disabled"
|
|
186
|
+
:text="tooltip"
|
|
187
|
+
:status="tooltipStatus || 'neutral'"
|
|
188
|
+
>
|
|
189
|
+
<FzIcon
|
|
190
|
+
name="circle-info"
|
|
191
|
+
variant="far"
|
|
192
|
+
size="md"
|
|
193
|
+
class="text-semantic-info"
|
|
194
|
+
/>
|
|
195
|
+
</FzTooltip>
|
|
196
|
+
</div>
|
|
197
|
+
</label>
|
|
198
|
+
</div>
|
|
199
|
+
</template>
|
|
200
|
+
|
|
201
|
+
<style scoped>
|
|
202
|
+
.fz-hidden-input {
|
|
203
|
+
opacity: 0;
|
|
204
|
+
margin: 0;
|
|
205
|
+
height: 0;
|
|
206
|
+
border: 0 none;
|
|
207
|
+
appearance: none;
|
|
208
|
+
}
|
|
209
|
+
</style>
|
package/src/FzCheckboxGroup.vue
CHANGED
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
* ]"
|
|
36
36
|
* />
|
|
37
37
|
*/
|
|
38
|
-
import { computed, useSlots, watch } from "vue";
|
|
38
|
+
import { computed, provide, useSlots, watch } from "vue";
|
|
39
39
|
import { FzCheckboxGroupProps } from "./types";
|
|
40
40
|
import { generateGroupId } from "./utils";
|
|
41
|
+
import { CHECKED_SET_KEY } from "./common";
|
|
41
42
|
import FzCheckboxGroupOption from "./components/FzCheckboxGroupOption.vue";
|
|
42
43
|
import ErrorAlert from "./components/ErrorAlert.vue";
|
|
43
44
|
|
|
@@ -80,6 +81,9 @@ const model = defineModel<(string | number | boolean)[]>({
|
|
|
80
81
|
default: [],
|
|
81
82
|
});
|
|
82
83
|
|
|
84
|
+
const checkedSet = computed(() => new Set(model.value));
|
|
85
|
+
provide(CHECKED_SET_KEY, { source: model, set: checkedSet });
|
|
86
|
+
|
|
83
87
|
/** Base layout for the label element */
|
|
84
88
|
const staticLabeldClass: string = "flex flex-col";
|
|
85
89
|
|
|
@@ -109,6 +113,22 @@ const computedSlotContainerClass = computed<string[]>(() => [
|
|
|
109
113
|
props.horizontal ? "flex-row" : "flex-col",
|
|
110
114
|
]);
|
|
111
115
|
|
|
116
|
+
/** Whether the group has options to render (options-based mode vs slot-based mode) */
|
|
117
|
+
const hasOptions = computed<boolean>(
|
|
118
|
+
() => Array.isArray(props.options) && props.options.length > 0,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Props passed down to child components via scoped slot.
|
|
123
|
+
* Used in slot-based mode (e.g., when rendering FzCheckboxCard components).
|
|
124
|
+
*/
|
|
125
|
+
const controlledProps = computed(() => ({
|
|
126
|
+
disabled: props.disabled,
|
|
127
|
+
error: props.error,
|
|
128
|
+
emphasis: props.emphasis,
|
|
129
|
+
required: props.required,
|
|
130
|
+
}));
|
|
131
|
+
|
|
112
132
|
/**
|
|
113
133
|
* Computes the aria-describedby attribute value for the checkbox group.
|
|
114
134
|
* Combines help text and error message IDs when present.
|
|
@@ -181,15 +201,18 @@ const computedAriaDescribedby = computed<string | undefined>(() => {
|
|
|
181
201
|
Supports both simple checkboxes and parent-child hierarchies
|
|
182
202
|
Key uses value if available, falls back to label for uniqueness
|
|
183
203
|
-->
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
<template v-if="hasOptions">
|
|
205
|
+
<FzCheckboxGroupOption
|
|
206
|
+
v-for="option in options"
|
|
207
|
+
:key="option.value ? option.value.toString() : option.label"
|
|
208
|
+
v-model="model"
|
|
209
|
+
:disabled="disabled"
|
|
210
|
+
v-bind="option"
|
|
211
|
+
:emphasis="emphasis"
|
|
212
|
+
:error="error"
|
|
213
|
+
/>
|
|
214
|
+
</template>
|
|
215
|
+
<slot v-else :checkboxGroupProps="controlledProps" />
|
|
193
216
|
</div>
|
|
194
217
|
<!-- Error message display with accessible ARIA live region -->
|
|
195
218
|
<ErrorAlert v-if="error && $slots.error" :id="id + '-error'">
|