@bethinkpl/design-system 30.1.2 → 30.3.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.
Files changed (81) hide show
  1. package/dist/design-system.css +1 -1
  2. package/dist/design-system.js +26585 -272
  3. package/dist/design-system.js.map +1 -1
  4. package/dist/lib/js/components/Buttons/IconButton/IconButton.vue.d.ts +4 -0
  5. package/dist/lib/js/components/Cards/Card/Card.vue.d.ts +2 -0
  6. package/dist/lib/js/components/Cards/CardExpandable/CardExpandable.vue.d.ts +13 -0
  7. package/dist/lib/js/components/Chip/Chip.vue.d.ts +8 -0
  8. package/dist/lib/js/components/DatePickers/DateBox/DateBox.vue.d.ts +4 -0
  9. package/dist/lib/js/components/DatePickers/DatePicker/DatePicker.vue.d.ts +4 -0
  10. package/dist/lib/js/components/DatePickers/DateRangePicker/DateRangePicker.vue.d.ts +4 -0
  11. package/dist/lib/js/components/Drawer/DrawerHeader/DrawerHeader.vue.d.ts +44 -14
  12. package/dist/lib/js/components/Drawer/DrawerListItem/DrawerListItem.vue.d.ts +4 -0
  13. package/dist/lib/js/components/Drawer/DrawerSection/DrawerSection.vue.d.ts +12 -0
  14. package/dist/lib/js/components/Form/Checkbox/Checkbox.consts.d.ts +18 -5
  15. package/dist/lib/js/components/Form/Checkbox/Checkbox.vue.d.ts +31 -461
  16. package/dist/lib/js/components/Form/Checkbox/index.d.ts +2 -0
  17. package/dist/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.consts.d.ts +9 -0
  18. package/dist/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.types.d.ts +12 -0
  19. package/dist/lib/js/components/Form/CheckboxGroupField/index.d.ts +1 -0
  20. package/dist/lib/js/components/Form/FormControlLabel/FormControlLabel.consts.d.ts +13 -0
  21. package/dist/lib/js/components/Form/FormControlLabel/FormControlLabel.vue.d.ts +28 -0
  22. package/dist/lib/js/components/Form/FormField/FormField.types.d.ts +1 -0
  23. package/dist/lib/js/components/Form/FormField/FormField.utils.d.ts +2 -2
  24. package/dist/lib/js/components/Form/InputField/useInputFieldWithinForm.d.ts +1 -1
  25. package/dist/lib/js/components/Form/RadioButton/RadioButton.vue.d.ts +4 -0
  26. package/dist/lib/js/components/Headers/OverlayHeader/OverlayHeader.vue.d.ts +8 -0
  27. package/dist/lib/js/components/Headers/SectionHeader/SectionHeader.vue.d.ts +8 -0
  28. package/dist/lib/js/components/Icons/Icon/Icon.consts.d.ts +4 -0
  29. package/dist/lib/js/components/Modal/Modal.vue.d.ts +4 -0
  30. package/dist/lib/js/components/Modals/Modal/Modal.vue.d.ts +8 -0
  31. package/dist/lib/js/components/Modals/ModalDialog/ModalDialog.vue.d.ts +8 -0
  32. package/dist/lib/js/components/Outline/OutlineItem/OutlineItem.vue.d.ts +4 -0
  33. package/dist/lib/js/components/Pagination/Pagination.vue.d.ts +12 -0
  34. package/dist/lib/js/components/ProgressBar/ProgressBar.vue.d.ts +4 -0
  35. package/dist/lib/js/components/ProgressDonutChart/ProgressDonutChart.vue.d.ts +4 -0
  36. package/dist/lib/js/components/RichList/BasicRichListItem/BasicRichListItem.vue.d.ts +69 -469
  37. package/dist/lib/js/components/RichList/RichListItem/RichListItem.vue.d.ts +69 -469
  38. package/dist/lib/js/components/SelectList/SelectListItem/SelectListItem.vue.d.ts +4 -0
  39. package/dist/lib/js/components/SelectList/SelectListItemToggle/SelectListItemToggle.vue.d.ts +4 -0
  40. package/dist/lib/js/components/SelectionTile/SelectionTile.vue.d.ts +29 -1533
  41. package/dist/lib/js/components/Statuses/AccessStatus/AccessStatus.vue.d.ts +4 -0
  42. package/dist/lib/js/components/Statuses/BlockadeStatus/BlockadeStatus.vue.d.ts +4 -0
  43. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionOpenEnded/SurveyQuestionOpenEnded.vue.d.ts +21 -0
  44. package/dist/lib/js/components/SurveyQuestions/SurveyQuestionScale/SurveyQuestionScale.vue.d.ts +21 -0
  45. package/dist/lib/js/components/Switch/Switch.vue.d.ts +4 -0
  46. package/dist/lib/js/components/Tile/Tile.sb.shared.d.ts +4 -0
  47. package/dist/lib/js/components/Toast/Toast.vue.d.ts +9 -0
  48. package/dist/lib/js/components/Toggles/ToggleButton/ToggleButton.vue.d.ts +4 -0
  49. package/dist/lib/js/composables/useFormFieldWithinForm.d.ts +7 -0
  50. package/dist/lib/js/icons/fontawesome.d.ts +4 -0
  51. package/dist/storybook/localhost:8080/node_modules/.vite/deps/@bethinkpl_design-system.js?v=62a0baa6 +7919 -0
  52. package/lib/js/components/Cards/Card/Card.spec.ts +23 -0
  53. package/lib/js/components/Cards/Card/Card.stories.ts +1 -0
  54. package/lib/js/components/Cards/Card/Card.vue +21 -4
  55. package/lib/js/components/Drawer/DrawerHeader/DrawerHeader.vue +13 -13
  56. package/lib/js/components/Form/Checkbox/Checkbox.consts.ts +27 -10
  57. package/lib/js/components/Form/Checkbox/Checkbox.spec.ts +294 -0
  58. package/lib/js/components/Form/Checkbox/Checkbox.stories.ts +60 -19
  59. package/lib/js/components/Form/Checkbox/Checkbox.vue +272 -55
  60. package/lib/js/components/Form/Checkbox/index.ts +2 -0
  61. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.consts.ts +11 -0
  62. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.spec.ts +268 -0
  63. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.stories.ts +92 -0
  64. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.types.ts +13 -0
  65. package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.vue +97 -0
  66. package/lib/js/components/Form/CheckboxGroupField/index.ts +1 -0
  67. package/lib/js/components/Form/Form.stories.ts +67 -0
  68. package/lib/js/components/Form/FormControlLabel/FormControlLabel.consts.ts +16 -0
  69. package/lib/js/components/Form/FormControlLabel/FormControlLabel.vue +61 -0
  70. package/lib/js/components/Form/FormField/FormField.types.ts +2 -1
  71. package/lib/js/components/Form/FormField/FormField.utils.ts +14 -10
  72. package/lib/js/components/Form/FormField/FormField.vue +3 -2
  73. package/lib/js/components/Form/InputField/InputField.vue +1 -7
  74. package/lib/js/components/Form/InputField/useInputFieldWithinForm.ts +4 -29
  75. package/lib/js/components/RichList/RichListItem/RichListItem.vue +11 -8
  76. package/lib/js/components/SelectionTile/SelectionTile.vue +64 -95
  77. package/lib/js/composables/useFormFieldWithinForm.ts +26 -0
  78. package/lib/js/composables/useLegacyI18n.ts +0 -1
  79. package/lib/js/icons/fontawesome.ts +8 -0
  80. package/lib/styles/settings/_animations.scss +3 -0
  81. package/package.json +7 -2
@@ -1,60 +1,277 @@
1
1
  <template>
2
- <selection-control
3
- :size="size"
4
- :label="label"
5
- :is-selected="isSelected"
6
- :selected-icon="ICONS.FA_SQUARE_CHECK_SOLID"
7
- :not-selected-icon="ICONS.FA_SQUARE"
8
- :state="state"
9
- :type="SELECTION_CONTROL_TYPE.CHECKBOX"
10
- @update:is-selected="$emit('update:is-selected', $event)"
11
- @input:focus="$emit('input:focus')"
12
- @input:blur="$emit('input:blur')"
13
- />
2
+ <label
3
+ :class="[
4
+ 'ds-checkbox',
5
+ {
6
+ '-ds-x-small': size === CHECKBOX_SIZES.X_SMALL,
7
+ '-ds-small': size === CHECKBOX_SIZES.SMALL,
8
+ '-ds-medium': size === CHECKBOX_SIZES.MEDIUM,
9
+ '-ds-elevation': elevation === CHECKBOX_ELEVATIONS.X_SMALL,
10
+ '-ds-disabled': state === CHECKBOX_STATES.DISABLED,
11
+ '-ds-error': state === CHECKBOX_STATES.ERROR,
12
+ },
13
+ ]"
14
+ >
15
+ <checkbox-root
16
+ v-slot="{ state: rekaState }"
17
+ v-model="modelValue"
18
+ :disabled="state === CHECKBOX_STATES.DISABLED"
19
+ class="ds-checkbox__root"
20
+ :value="value"
21
+ >
22
+ <!-- As we also use icon for unchecked state, we need to force mount the indicator -->
23
+ <checkbox-indicator :force-mount="true" class="ds-checkbox__indicator" as="div">
24
+ <ds-icon
25
+ v-if="rekaState === 'indeterminate'"
26
+ :icon="ICONS.FAD_SQUARE_MINUS"
27
+ :size="iconSize"
28
+ />
29
+ <ds-icon v-else-if="rekaState" :icon="ICONS.FAD_SQUARE_CHECK" :size="iconSize" />
30
+ <ds-icon v-else :icon="ICONS.FAD_SQUARE" :size="iconSize" />
31
+ </checkbox-indicator>
32
+ </checkbox-root>
33
+
34
+ <ds-form-control-label v-if="$slots.default" :size="size" :state="labelState">
35
+ <slot />
36
+ </ds-form-control-label>
37
+ </label>
14
38
  </template>
15
39
 
16
- <script lang="ts">
17
- import { defineComponent, PropType } from 'vue';
18
- import { CHECKBOX_SIZE, CHECKBOX_STATE, CheckboxSize, CheckboxState } from './Checkbox.consts';
19
- import SelectionControl from '../SelectionControl/SelectionControl.vue';
20
- import { ICONS } from '../../Icons/Icon';
21
- import { SELECTION_CONTROL_TYPE } from '../SelectionControl/SelectionControl.consts';
22
-
23
- export default defineComponent({
24
- name: 'Checkbox',
25
- components: { SelectionControl },
26
- props: {
27
- size: {
28
- type: String as PropType<CheckboxSize>,
29
- default: CHECKBOX_SIZE.SMALL,
30
- validator(size: CheckboxSize) {
31
- return Object.values(CHECKBOX_SIZE).includes(size);
32
- },
33
- },
34
- label: {
35
- type: String,
36
- default: null,
37
- },
38
- isSelected: {
39
- type: Boolean,
40
- default: false,
41
- },
42
- state: {
43
- type: String as PropType<CheckboxState>,
44
- default: CHECKBOX_STATE.DEFAULT,
45
- validator(state: CheckboxState) {
46
- return Object.values(CHECKBOX_STATE).includes(state);
47
- },
48
- },
49
- },
50
- // TODO fix me when touching this file
51
- // eslint-disable-next-line vue/require-emit-validator
52
- emits: ['update:is-selected', 'input:focus', 'input:blur'],
53
- data() {
54
- return {
55
- SELECTION_CONTROL_TYPE: Object.freeze(SELECTION_CONTROL_TYPE),
56
- ICONS: Object.freeze(ICONS),
57
- };
58
- },
40
+ <style scoped lang="scss">
41
+ @import '../../../../styles/settings/colors/tokens';
42
+ @import '../../../../styles/settings/spacings';
43
+ @import '../../../../styles/settings/typography/tokens';
44
+ @import '../../../../styles/settings/radiuses';
45
+ @import '../../../../styles/settings/animations';
46
+
47
+ .ds-checkbox {
48
+ $root: &;
49
+
50
+ align-items: flex-start;
51
+ display: inline-flex;
52
+
53
+ &.-ds-x-small {
54
+ gap: $space-4xs;
55
+ }
56
+
57
+ &.-ds-small {
58
+ gap: $space-2xs;
59
+ }
60
+
61
+ &.-ds-medium {
62
+ gap: $space-2xs;
63
+ }
64
+
65
+ &:not(.-ds-disabled) {
66
+ cursor: pointer;
67
+ }
68
+
69
+ &__root {
70
+ $checkbox-root-xs-space: $space-3xs;
71
+ $checkbox-root-s-space: $space-3xs;
72
+ $checkbox-root-m-space: $space-2xs;
73
+
74
+ --checkbox-circle-background-color-focused: #{$color-primary-background-ghost-focused};
75
+ --checkbox-circle-background-color-hovered: #{$color-primary-background-ghost-hovered};
76
+
77
+ background: none;
78
+ border: none;
79
+ line-height: 0;
80
+ margin: 0;
81
+ padding: 0;
82
+ position: relative;
83
+
84
+ &[data-state='unchecked'] {
85
+ --checkbox-circle-background-color-focused: #{$color-primary-background-ghost-focused};
86
+ --checkbox-circle-background-color-hovered: #{$color-neutral-background-ghost-hovered};
87
+ }
88
+
89
+ #{$root}.-ds-error & {
90
+ --checkbox-circle-background-color-focused: #{$color-danger-background-ghost-focused};
91
+ --checkbox-circle-background-color-hovered: #{$color-danger-background-ghost-hovered};
92
+ }
93
+
94
+ #{$root}.-ds-x-small & {
95
+ padding: $checkbox-root-xs-space 0;
96
+ }
97
+
98
+ #{$root}.-ds-small & {
99
+ padding: $checkbox-root-s-space 0;
100
+ }
101
+
102
+ #{$root}.-ds-medium & {
103
+ padding: $checkbox-root-m-space 0;
104
+ }
105
+
106
+ &:focus {
107
+ outline: none;
108
+ }
109
+
110
+ &::before {
111
+ aspect-ratio: 1;
112
+ border-radius: $radius-xl;
113
+ content: '';
114
+ display: block;
115
+ height: 100%;
116
+ position: absolute;
117
+ top: 0;
118
+ transform: scale(0);
119
+ transition: $default-cubic-bezier-transition;
120
+ width: auto;
121
+ }
122
+
123
+ #{$root}:hover:not(.-ds-disabled) &::before,
124
+ &:focus-visible::before {
125
+ transform: scale(1);
126
+ }
127
+
128
+ #{$root}.-ds-x-small &::before {
129
+ left: -$checkbox-root-xs-space;
130
+ }
131
+
132
+ #{$root}.-ds-small &::before {
133
+ left: -$checkbox-root-s-space;
134
+ }
135
+
136
+ #{$root}.-ds-medium &::before {
137
+ left: -$checkbox-root-m-space;
138
+ }
139
+
140
+ #{$root}:hover &::before {
141
+ background-color: var(--checkbox-circle-background-color-hovered);
142
+ }
143
+
144
+ &:focus-visible::before {
145
+ background-color: var(--checkbox-circle-background-color-focused);
146
+ }
147
+ }
148
+
149
+ &__indicator {
150
+ --checkbox-color: #{$color-primary-icon};
151
+ --checkbox-elevation-opacity: 0;
152
+
153
+ position: relative;
154
+
155
+ &[data-state='unchecked'] {
156
+ --checkbox-color: #{$color-neutral-icon};
157
+ --fa-primary-color: var(--checkbox-color);
158
+ --fa-primary-opacity: 1;
159
+ --fa-secondary-color: #{$color-default-background};
160
+ --fa-secondary-opacity: var(--checkbox-elevation-opacity);
161
+ }
162
+
163
+ &[data-state='checked'] {
164
+ --fa-primary-color: #{$color-default-background};
165
+ --fa-primary-opacity: 1;
166
+ --fa-secondary-color: var(--checkbox-color);
167
+ --fa-secondary-opacity: 1;
168
+ }
169
+
170
+ &[data-state='indeterminate'] {
171
+ --fa-primary-color: var(--checkbox-color);
172
+ --fa-primary-opacity: 1;
173
+ --fa-secondary-color: #{$color-default-background};
174
+ --fa-secondary-opacity: var(--checkbox-elevation-opacity);
175
+ }
176
+
177
+ #{$root}__root:focus-visible & {
178
+ --checkbox-color: #{$color-primary-icon};
179
+ }
180
+
181
+ #{$root}.-ds-error & {
182
+ --checkbox-color: #{$color-danger-icon};
183
+ }
184
+
185
+ #{$root}.-ds-disabled & {
186
+ --checkbox-color: #{$color-primary-icon-disabled};
187
+ }
188
+
189
+ #{$root}.-ds-disabled &[data-state='unchecked'] {
190
+ --checkbox-color: #{$color-neutral-icon-disabled};
191
+ }
192
+
193
+ #{$root}.-ds-elevation & {
194
+ --checkbox-elevation-opacity: 1;
195
+ }
196
+ }
197
+ }
198
+ </style>
199
+
200
+ <script setup lang="ts">
201
+ import { CheckboxIndicator, CheckboxRoot } from 'reka-ui';
202
+ import {
203
+ CHECKBOX_ELEVATIONS,
204
+ CHECKBOX_SIZES,
205
+ CHECKBOX_STATES,
206
+ CheckboxElevation,
207
+ CheckboxSize,
208
+ CheckboxState,
209
+ CheckboxValue,
210
+ } from './Checkbox.consts';
211
+ import DsIcon from '../../Icons/Icon/Icon.vue';
212
+ import { ICON_SIZES, ICONS } from '../../Icons/Icon';
213
+ import { computed, inject } from 'vue';
214
+ import { CHECKBOX_GROUP_INJECTION_KEY } from '../CheckboxGroupField/CheckboxGroupField.consts';
215
+ import DsFormControlLabel from '../FormControlLabel/FormControlLabel.vue';
216
+ import { FORM_CONTROL_STATE } from '../FormControlLabel/FormControlLabel.consts';
217
+
218
+ const props = defineProps<{
219
+ size?: CheckboxSize;
220
+ state?: CheckboxState;
221
+ elevation?: CheckboxElevation;
222
+ /**
223
+ * String value set when the checkbox is checked
224
+ */
225
+ value?: string;
226
+ }>();
227
+
228
+ const modelValue = defineModel<CheckboxValue>({
229
+ default: false,
230
+ });
231
+
232
+ defineSlots<{
233
+ default?(): any;
234
+ }>();
235
+
236
+ // Inject context from CheckboxGroupField
237
+ const groupContext = inject(CHECKBOX_GROUP_INJECTION_KEY, null);
238
+
239
+ // Use props if provided, otherwise fall back to injected values, then defaults
240
+ const size = computed(() => props.size ?? groupContext?.size.value ?? CHECKBOX_SIZES.SMALL);
241
+
242
+ const state = computed(() => props.state ?? groupContext?.state.value ?? CHECKBOX_STATES.DEFAULT);
243
+
244
+ const elevation = computed(
245
+ () => props.elevation ?? groupContext?.elevation.value ?? CHECKBOX_ELEVATIONS.X_SMALL,
246
+ );
247
+
248
+ const value = computed(() => {
249
+ if (groupContext && !props.value) {
250
+ throw new Error(
251
+ '[Checkbox]: When used inside a CheckboxGroupField, each Checkbox should have a unique "value" prop.',
252
+ );
253
+ }
254
+
255
+ return props.value ?? 'on';
256
+ });
257
+
258
+ const iconSize = computed(() => {
259
+ switch (size.value) {
260
+ case CHECKBOX_SIZES.X_SMALL:
261
+ return ICON_SIZES.XX_SMALL;
262
+ case CHECKBOX_SIZES.SMALL:
263
+ return ICON_SIZES.X_SMALL;
264
+ default:
265
+ case CHECKBOX_SIZES.MEDIUM:
266
+ return ICON_SIZES.SMALL;
267
+ }
268
+ });
269
+
270
+ const labelState = computed(() => {
271
+ if (state.value === CHECKBOX_STATES.DISABLED) {
272
+ return FORM_CONTROL_STATE.DISABLED;
273
+ }
274
+
275
+ return FORM_CONTROL_STATE.DEFAULT;
59
276
  });
60
277
  </script>
@@ -0,0 +1,2 @@
1
+ export { default } from './Checkbox.vue';
2
+ export * from './Checkbox.consts';
@@ -0,0 +1,11 @@
1
+ import type { InjectionKey, Ref } from 'vue';
2
+ import type { CheckboxSize, CheckboxState, CheckboxElevation } from '../Checkbox';
3
+
4
+ export interface CheckboxGroupContext {
5
+ size: Ref<CheckboxSize>;
6
+ state: Ref<CheckboxState>;
7
+ elevation: Ref<CheckboxElevation>;
8
+ }
9
+
10
+ export const CHECKBOX_GROUP_INJECTION_KEY: InjectionKey<CheckboxGroupContext> =
11
+ Symbol('checkboxGroup');
@@ -0,0 +1,268 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import { inject, defineComponent } from 'vue';
4
+ import { useForm } from 'vee-validate';
5
+ import { z } from 'zod';
6
+ import { toTypedSchema } from '@vee-validate/zod';
7
+ import CheckboxGroupField from './CheckboxGroupField.vue';
8
+ import FormField from '../FormField/FormField.vue';
9
+ import Checkbox from '../Checkbox/Checkbox.vue';
10
+ import { CHECKBOX_SIZES, CHECKBOX_STATES, CHECKBOX_ELEVATIONS } from '../Checkbox/Checkbox.consts';
11
+ import { FORM_FIELD_STATES } from '../FormField/FormField.consts';
12
+ import { CHECKBOX_GROUP_INJECTION_KEY } from './CheckboxGroupField.consts';
13
+ import { waitForExpectShort } from '../../../tests/helpers';
14
+
15
+ describe('CheckboxGroupField', () => {
16
+ it('renders FormField with correct props', () => {
17
+ const wrapper = mount(CheckboxGroupField, {
18
+ props: {
19
+ label: 'Test Label',
20
+ state: FORM_FIELD_STATES.ERROR,
21
+ hasRequiredIndicator: true,
22
+ messageText: 'Test message',
23
+ },
24
+ slots: {
25
+ field: '<div>Test content</div>',
26
+ },
27
+ });
28
+
29
+ const formField = wrapper.findComponent(FormField);
30
+ expect(formField.exists()).toBe(true);
31
+ expect(formField.props('label')).toBe('Test Label');
32
+ expect(formField.props('state')).toBe(FORM_FIELD_STATES.ERROR);
33
+ expect(formField.props('hasRequiredIndicator')).toBe(true);
34
+ expect(formField.props('messageText')).toBe('Test message');
35
+ });
36
+
37
+ it('provides context to child components', () => {
38
+ const TestChild = {
39
+ template: '<div>{{ size }}-{{ state }}-{{ elevation }}</div>',
40
+ setup() {
41
+ const context = inject(CHECKBOX_GROUP_INJECTION_KEY, null);
42
+ return {
43
+ size: context?.size,
44
+ state: context?.state,
45
+ elevation: context?.elevation,
46
+ };
47
+ },
48
+ };
49
+
50
+ const wrapper = mount(CheckboxGroupField, {
51
+ props: {
52
+ label: 'Test',
53
+ size: CHECKBOX_SIZES.MEDIUM,
54
+ state: CHECKBOX_STATES.ERROR,
55
+ elevation: CHECKBOX_ELEVATIONS.X_SMALL,
56
+ },
57
+ slots: {
58
+ field: TestChild,
59
+ },
60
+ });
61
+
62
+ expect(wrapper.text()).toContain('medium-error-x-small');
63
+ });
64
+
65
+ it('renders all slot content correctly', () => {
66
+ const wrapper = mount(CheckboxGroupField, {
67
+ props: {
68
+ label: 'Test Label',
69
+ },
70
+ slots: {
71
+ labelAside: '<span>Label aside</span>',
72
+ help: '<button>Help</button>',
73
+ field: '<div>Field content</div>',
74
+ message: '<span>Custom message</span>',
75
+ fieldStatus: '<span>Status</span>',
76
+ },
77
+ });
78
+
79
+ expect(wrapper.html()).toContain('Label aside');
80
+ expect(wrapper.html()).toContain('Help');
81
+ expect(wrapper.html()).toContain('Field content');
82
+ expect(wrapper.html()).toContain('Custom message');
83
+ expect(wrapper.html()).toContain('Status');
84
+ });
85
+
86
+ it('has correct accessibility attributes', () => {
87
+ const wrapper = mount(CheckboxGroupField, {
88
+ props: {
89
+ label: 'Test Label',
90
+ messageText: 'Test message',
91
+ },
92
+ slots: {
93
+ field: '<div>Content</div>',
94
+ },
95
+ });
96
+
97
+ const fieldGroup = wrapper.find('.ds-checkboxGroupField');
98
+ expect(fieldGroup.attributes('role')).toBe('group');
99
+ expect(fieldGroup.attributes('aria-describedby')).toBe(
100
+ wrapper.find('.ds-formFieldMessage').attributes('id'),
101
+ );
102
+ expect(fieldGroup.attributes('aria-labelledby')).toBe(
103
+ wrapper.find('.ds-formField__label').attributes('id'),
104
+ );
105
+ });
106
+
107
+ describe('vee-validate integration', () => {
108
+ it('integrates with vee-validate form context and reflects initial values', async () => {
109
+ // eslint-disable-next-line vue/one-component-per-file
110
+ const TestForm = defineComponent({
111
+ name: 'TestForm',
112
+ components: {
113
+ CheckboxGroupField,
114
+ Checkbox,
115
+ },
116
+ setup() {
117
+ useForm({
118
+ initialValues: {
119
+ preferences: ['option2'],
120
+ },
121
+ });
122
+
123
+ return {};
124
+ },
125
+ template: `
126
+ <form>
127
+ <CheckboxGroupField
128
+ name="preferences"
129
+ label="Select your preferences"
130
+ >
131
+ <template #field>
132
+ <Checkbox value="option1">Option 1</Checkbox>
133
+ <Checkbox value="option2">Option 2</Checkbox>
134
+ <Checkbox value="option3">Option 3</Checkbox>
135
+ </template>
136
+ </CheckboxGroupField>
137
+ </form>
138
+ `,
139
+ });
140
+
141
+ const wrapper = mount(TestForm);
142
+
143
+ const checkboxes = wrapper.findAll('button[role="checkbox"]');
144
+
145
+ expect(checkboxes[0]?.attributes('data-state')).toBe('unchecked');
146
+ expect(checkboxes[1]?.attributes('data-state')).toBe('checked');
147
+ expect(checkboxes[2]?.attributes('data-state')).toBe('unchecked');
148
+ });
149
+
150
+ it('displays validation errors on form submission with invalid data', async () => {
151
+ const validationSchema = toTypedSchema(
152
+ z.object({
153
+ preferences: z
154
+ .array(z.string())
155
+ .min(1, { message: 'Please select at least one option' }),
156
+ }),
157
+ );
158
+ const onSubmitHandler = vi.fn();
159
+
160
+ // eslint-disable-next-line vue/one-component-per-file
161
+ const TestForm = defineComponent({
162
+ name: 'TestForm',
163
+ components: {
164
+ CheckboxGroupField,
165
+ Checkbox,
166
+ },
167
+ setup() {
168
+ const { handleSubmit } = useForm({
169
+ validationSchema,
170
+ initialValues: {
171
+ preferences: [],
172
+ },
173
+ });
174
+
175
+ const onSubmit = handleSubmit(onSubmitHandler);
176
+
177
+ return {
178
+ onSubmit,
179
+ };
180
+ },
181
+ template: `
182
+ <form @submit="onSubmit">
183
+ <CheckboxGroupField
184
+ name="preferences"
185
+ label="Select your preferences"
186
+ >
187
+ <template #field>
188
+ <Checkbox value="option1">Option 1</Checkbox>
189
+ <Checkbox value="option2">Option 2</Checkbox>
190
+ </template>
191
+ </CheckboxGroupField>
192
+ <button type="submit">Submit</button>
193
+ </form>
194
+ `,
195
+ });
196
+
197
+ const wrapper = mount(TestForm);
198
+ const formField = wrapper.findComponent(FormField);
199
+
200
+ const group = wrapper.find('[role="group"]');
201
+ const describedby = group.attributes('aria-describedby');
202
+ const message = wrapper.find(`#${describedby}`);
203
+
204
+ // Initially, there should be no error message
205
+ expect(message.exists()).toBe(false);
206
+
207
+ const form = wrapper.find('form');
208
+ await form.trigger('submit');
209
+
210
+ await waitForExpectShort(() => {
211
+ const group = wrapper.find('[role="group"]');
212
+ const describedby = group.attributes('aria-describedby');
213
+ const message = wrapper.find(`#${describedby}`);
214
+ expect(message.text()).toBe('Please select at least one option');
215
+ expect(formField.props('state')).toBe(FORM_FIELD_STATES.ERROR);
216
+ expect(onSubmitHandler).not.toHaveBeenCalled();
217
+ });
218
+ });
219
+
220
+ it('works without vee-validate form context using v-model', async () => {
221
+ // eslint-disable-next-line vue/one-component-per-file
222
+ const TestComponent = defineComponent({
223
+ name: 'TestComponent',
224
+ components: {
225
+ CheckboxGroupField,
226
+ Checkbox,
227
+ },
228
+ data() {
229
+ return {
230
+ selectedValues: ['option1'],
231
+ };
232
+ },
233
+ template: `
234
+ <CheckboxGroupField
235
+ v-model="selectedValues"
236
+ label="Select your preferences"
237
+ >
238
+ <template #field>
239
+ <Checkbox value="option1">Option 1</Checkbox>
240
+ <Checkbox value="option2">Option 2</Checkbox>
241
+ </template>
242
+ </CheckboxGroupField>
243
+ `,
244
+ });
245
+
246
+ const wrapper = mount(TestComponent);
247
+
248
+ const checkboxes = wrapper.findAll('button[role="checkbox"]');
249
+
250
+ expect(checkboxes[0]?.attributes('data-state')).toBe('checked');
251
+ expect(checkboxes[1]?.attributes('data-state')).toBe('unchecked');
252
+ });
253
+
254
+ it('throws error when name is provided but no form context exists', () => {
255
+ expect(() => {
256
+ mount(CheckboxGroupField, {
257
+ props: {
258
+ name: 'preferences',
259
+ label: 'Test Label',
260
+ },
261
+ slots: {
262
+ field: '<div>test</div>',
263
+ },
264
+ });
265
+ }).toThrow();
266
+ });
267
+ });
268
+ });