@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
@@ -55,6 +55,29 @@ describe('Card', () => {
55
55
  expect(component.text()).toContain(footer);
56
56
  });
57
57
 
58
+ it('should render content slot with padding by default', () => {
59
+ const content = 'Wpłynąlem na suchego przestwór oceanu';
60
+ const component = createComponent({
61
+ slots: {
62
+ content: () => [h('span', content)],
63
+ },
64
+ });
65
+
66
+ expect(component.find('.ds-card__content').classes()).toContain('-ds-withPadding');
67
+ });
68
+
69
+ it('should render content slot without padding if contentHasPadding is false', () => {
70
+ const content = 'Wpłynąlem na suchego przestwór oceanu';
71
+ const component = createComponent({
72
+ props: { contentHasPadding: false },
73
+ slots: {
74
+ content: () => [h('span', content)],
75
+ },
76
+ });
77
+
78
+ expect(component.find('.ds-card__content').classes()).not.toContain('-ds-withPadding');
79
+ });
80
+
58
81
  it('should render header slot with padding', () => {
59
82
  const header = 'Wpłynąlem na suchego przestwór oceanu';
60
83
  const component = createComponent({
@@ -39,6 +39,7 @@ const args = {
39
39
  header: 'header slot',
40
40
  content: 'content slot that supports <b>HTML markup</b>',
41
41
  footer: 'footer slot',
42
+ contentHasPadding: true,
42
43
  headerHasPadding: false,
43
44
  footerHasPadding: false,
44
45
  paddingSize: CARD_PADDING_SIZES.SMALL,
@@ -45,7 +45,10 @@
45
45
  <div
46
46
  v-if="$slots.content"
47
47
  class="ds-card__content"
48
- :class="{ '-ds-scrollable': isContentScrollable }"
48
+ :class="{
49
+ '-ds-scrollable': isContentScrollable,
50
+ '-ds-withPadding': contentHasPadding,
51
+ }"
49
52
  >
50
53
  <slot name="content" />
51
54
  </div>
@@ -118,10 +121,14 @@
118
121
  }
119
122
 
120
123
  &__content {
121
- padding: $space-s;
124
+ padding: $space-s 0;
122
125
 
123
- #{$root}.-ds-paddingLarge & {
124
- padding: $space-s $space-l;
126
+ &.-ds-withPadding {
127
+ padding: $space-s;
128
+
129
+ #{$root}.-ds-paddingLarge & {
130
+ padding: $space-s $space-l;
131
+ }
125
132
  }
126
133
 
127
134
  &.-ds-scrollable {
@@ -157,6 +164,13 @@
157
164
  border-top-left-radius: $card-border-radius;
158
165
  border-top-right-radius: 0;
159
166
  }
167
+
168
+ .-ds-leftBorder & {
169
+ height: 100%;
170
+ left: 0;
171
+ position: absolute;
172
+ top: 0;
173
+ }
160
174
  }
161
175
  }
162
176
  </style>
@@ -180,6 +194,8 @@ import {
180
194
  } from './Card.consts';
181
195
 
182
196
  const {
197
+ // only contentHasPadding is true by default for backward compatibility
198
+ contentHasPadding = true,
183
199
  headerHasPadding = false,
184
200
  footerHasPadding = false,
185
201
  paddingSize = CARD_PADDING_SIZES.SMALL,
@@ -195,6 +211,7 @@ const {
195
211
  isFlat = false,
196
212
  isContentScrollable = false,
197
213
  } = defineProps<{
214
+ contentHasPadding?: boolean;
198
215
  headerHasPadding?: boolean;
199
216
  footerHasPadding?: boolean;
200
217
  paddingSize?: CardPaddingSize;
@@ -56,6 +56,7 @@
56
56
  </div>
57
57
  <icon-button
58
58
  v-if="isClosable"
59
+ :color="ICON_COLORS.NEUTRAL"
59
60
  :icon="ICONS.FA_XMARK"
60
61
  :size="ICON_BUTTON_SIZES.MEDIUM"
61
62
  :touchable="false"
@@ -71,7 +72,7 @@
71
72
  @import '../../../../styles/settings/typography/tokens';
72
73
  @import '../../../../styles/settings/colors/tokens';
73
74
 
74
- $minimal-drawer-header-height: 82px;
75
+ $minimal-drawer-header-height: 58px;
75
76
 
76
77
  .ds-drawerHeader {
77
78
  display: flex;
@@ -127,7 +128,7 @@ $minimal-drawer-header-height: 82px;
127
128
  }
128
129
 
129
130
  &__titleText {
130
- @include heading-s-default-bold-uppercase;
131
+ @include heading-s-default-bold;
131
132
 
132
133
  &.-ds-neutralStrong {
133
134
  color: $color-neutral-text-strong;
@@ -150,7 +151,7 @@ $minimal-drawer-header-height: 82px;
150
151
  display: flex;
151
152
  justify-content: space-between;
152
153
  min-height: $minimal-drawer-header-height;
153
- padding: $space-m $space-xs;
154
+ padding: $space-xs;
154
155
  }
155
156
 
156
157
  &__actions {
@@ -168,7 +169,7 @@ import IconButton from '../../Buttons/IconButton/IconButton.vue';
168
169
  import Chip from '../../Chip/Chip.vue';
169
170
  import Icon from '../../Icons/Icon/Icon.vue';
170
171
  import { BUTTON_TYPES } from '../../Buttons/Button';
171
- import { ICON_SIZES, ICONS } from '../../Icons/Icon';
172
+ import { ICON_COLORS, ICON_SIZES, ICONS } from '../../Icons/Icon';
172
173
  import { DIVIDER_PROMINENCES, DIVIDER_SIZES } from '../../Divider';
173
174
  import { ICON_BUTTON_SIZES } from '../../Buttons/IconButton';
174
175
  import { DRAWER_HEADER_TITLE_COLORS, DrawerHeaderTitleColor } from './DrawerHeader.consts';
@@ -239,16 +240,15 @@ export default defineComponent({
239
240
  setup() {
240
241
  const { t } = useLegacyI18n();
241
242
 
242
- return { t };
243
- },
244
- data() {
245
243
  return {
246
- BUTTON_TYPES: Object.freeze(BUTTON_TYPES),
247
- DIVIDER_SIZES: Object.freeze(DIVIDER_SIZES),
248
- DIVIDER_PROMINENCES: Object.freeze(DIVIDER_PROMINENCES),
249
- ICONS: Object.freeze(ICONS),
250
- ICON_BUTTON_SIZES: Object.freeze(ICON_BUTTON_SIZES),
251
- ICON_SIZES: Object.freeze(ICON_SIZES),
244
+ t,
245
+ BUTTON_TYPES,
246
+ DIVIDER_SIZES,
247
+ DIVIDER_PROMINENCES,
248
+ ICONS,
249
+ ICON_BUTTON_SIZES,
250
+ ICON_SIZES,
251
+ ICON_COLORS,
252
252
  };
253
253
  },
254
254
  });
@@ -1,15 +1,32 @@
1
- import {
2
- SELECTION_CONTROL_SIZE,
3
- SELECTION_CONTROL_STATE,
4
- } from '../SelectionControl/SelectionControl.consts';
1
+ import { Value } from '../../../utils/type.utils';
5
2
 
6
- export const CHECKBOX_SIZE = {
7
- ...SELECTION_CONTROL_SIZE,
3
+ export const CHECKBOX_SIZES = {
4
+ X_SMALL: 'x-small',
5
+ SMALL: 'small',
6
+ MEDIUM: 'medium',
8
7
  } as const;
9
8
 
10
- export type CheckboxSize = (typeof CHECKBOX_SIZE)[keyof typeof CHECKBOX_SIZE];
11
- export const CHECKBOX_STATE = {
12
- ...SELECTION_CONTROL_STATE,
9
+ export type CheckboxSize = Value<typeof CHECKBOX_SIZES>;
10
+
11
+ export const CHECKBOX_STATES = {
12
+ DEFAULT: 'default',
13
+ DISABLED: 'disabled',
14
+ ERROR: 'error',
15
+ } as const;
16
+
17
+ export type CheckboxState = Value<typeof CHECKBOX_STATES>;
18
+
19
+ export const CHECKBOX_VALUES = {
20
+ CHECKED: true,
21
+ UNCHECKED: false,
22
+ INDETERMINATE: 'indeterminate',
23
+ } as const;
24
+
25
+ export type CheckboxValue = Value<typeof CHECKBOX_VALUES>;
26
+
27
+ export const CHECKBOX_ELEVATIONS = {
28
+ NONE: 'none',
29
+ X_SMALL: 'x-small',
13
30
  } as const;
14
31
 
15
- export type CheckboxState = (typeof CHECKBOX_STATE)[keyof typeof CHECKBOX_STATE];
32
+ export type CheckboxElevation = Value<typeof CHECKBOX_ELEVATIONS>;
@@ -0,0 +1,294 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import { h } from 'vue';
4
+ import Checkbox from './Checkbox.vue';
5
+ import { ComponentProps, ComponentSlots } from 'vue-component-type-helpers';
6
+ import {
7
+ CHECKBOX_SIZES,
8
+ CHECKBOX_STATES,
9
+ CHECKBOX_VALUES,
10
+ CHECKBOX_ELEVATIONS,
11
+ } from './Checkbox.consts';
12
+ import { ICON_SIZES, ICONS } from '../../Icons/Icon';
13
+ import Icon from '../../Icons/Icon/Icon.vue';
14
+
15
+ function setup(
16
+ props: Partial<ComponentProps<typeof Checkbox>> = {},
17
+ slots: Partial<ComponentSlots<typeof Checkbox>> = {},
18
+ ) {
19
+ return mount(Checkbox, {
20
+ props: {
21
+ value: 'test',
22
+ ...props,
23
+ },
24
+ // @ts-expect-error - it looks like a bug in vue-component-type-helpers or vue-test-utils
25
+ slots,
26
+ attachTo: 'body',
27
+ });
28
+ }
29
+
30
+ describe('Checkbox', () => {
31
+ it('should render', () => {
32
+ const wrapper = setup();
33
+
34
+ expect(wrapper.exists()).toBe(true);
35
+ expect(wrapper.find('.ds-checkbox').exists()).toBe(true);
36
+ expect(wrapper.find('.ds-checkbox__root').exists()).toBe(true);
37
+ expect(wrapper.find('.ds-checkbox__indicator').exists()).toBe(true);
38
+ });
39
+
40
+ it('should render with label', () => {
41
+ const wrapper = setup(
42
+ {},
43
+ {
44
+ default: () => [h('span', 'Test Label')],
45
+ },
46
+ );
47
+
48
+ expect(wrapper.find('.ds-formControlLabel').exists()).toBe(true);
49
+ expect(wrapper.find('.ds-formControlLabel').text()).toBe('Test Label');
50
+ });
51
+
52
+ it('should not render label when no slot content provided', () => {
53
+ const wrapper = setup();
54
+
55
+ expect(wrapper.find('.ds-checkbox__label').exists()).toBe(false);
56
+ });
57
+
58
+ describe('sizes', () => {
59
+ it.each([
60
+ {
61
+ size: CHECKBOX_SIZES.X_SMALL,
62
+ expectedClass: '-ds-x-small',
63
+ expectedIconSize: ICON_SIZES.XX_SMALL,
64
+ },
65
+ {
66
+ size: CHECKBOX_SIZES.SMALL,
67
+ expectedClass: '-ds-small',
68
+ expectedIconSize: ICON_SIZES.X_SMALL,
69
+ },
70
+ {
71
+ size: CHECKBOX_SIZES.MEDIUM,
72
+ expectedClass: '-ds-medium',
73
+ expectedIconSize: ICON_SIZES.SMALL,
74
+ },
75
+ ])('should render $size size correctly', ({ size, expectedClass, expectedIconSize }) => {
76
+ const wrapper = setup({ size });
77
+
78
+ expect(wrapper.find('.ds-checkbox').classes()).toContain(expectedClass);
79
+
80
+ // Check that icon has correct size
81
+ const iconComponent = wrapper.findComponent({ name: 'Icon' });
82
+ if (iconComponent.exists()) {
83
+ expect(iconComponent.props('size')).toBe(expectedIconSize);
84
+ }
85
+ });
86
+
87
+ it('should default to small size', () => {
88
+ const wrapper = setup();
89
+
90
+ expect(wrapper.find('.ds-checkbox').classes()).toContain('-ds-small');
91
+ });
92
+ });
93
+
94
+ describe('states', () => {
95
+ it.each([
96
+ {
97
+ state: CHECKBOX_STATES.DISABLED,
98
+ expectedClass: '-ds-disabled',
99
+ },
100
+ {
101
+ state: CHECKBOX_STATES.ERROR,
102
+ expectedClass: '-ds-error',
103
+ },
104
+ {
105
+ state: CHECKBOX_STATES.DEFAULT,
106
+ expectedClass: null,
107
+ },
108
+ ])('should render $state state correctly', ({ state, expectedClass }) => {
109
+ const wrapper = setup({ state });
110
+
111
+ if (expectedClass) {
112
+ expect(wrapper.find('.ds-checkbox').classes()).toContain(expectedClass);
113
+ } else {
114
+ expect(wrapper.find('.ds-checkbox').classes()).not.toContain('-ds-disabled');
115
+ expect(wrapper.find('.ds-checkbox').classes()).not.toContain('-ds-error');
116
+ }
117
+ });
118
+
119
+ it('should disable checkbox when state is disabled', () => {
120
+ const wrapper = setup({ state: CHECKBOX_STATES.DISABLED });
121
+
122
+ const checkboxRoot = wrapper.find('[role="checkbox"]');
123
+ expect(checkboxRoot.attributes('disabled')).toBeDefined();
124
+ });
125
+
126
+ it('should default to default state', () => {
127
+ const wrapper = setup();
128
+
129
+ expect(wrapper.find('.ds-checkbox').classes()).not.toContain('-ds-disabled');
130
+ expect(wrapper.find('.ds-checkbox').classes()).not.toContain('-ds-error');
131
+ });
132
+ });
133
+
134
+ describe('elevations', () => {
135
+ it.each([
136
+ {
137
+ elevation: CHECKBOX_ELEVATIONS.X_SMALL,
138
+ expectedClass: '-ds-elevation',
139
+ },
140
+ {
141
+ elevation: CHECKBOX_ELEVATIONS.NONE,
142
+ expectedClass: null,
143
+ },
144
+ ])('should render $elevation elevation correctly', ({ elevation, expectedClass }) => {
145
+ const wrapper = setup({ elevation });
146
+
147
+ if (expectedClass) {
148
+ expect(wrapper.find('.ds-checkbox').classes()).toContain(expectedClass);
149
+ } else {
150
+ expect(wrapper.find('.ds-checkbox').classes()).not.toContain('-ds-elevation');
151
+ }
152
+ });
153
+
154
+ it('should default to x-small elevation', () => {
155
+ const wrapper = setup();
156
+
157
+ expect(wrapper.find('.ds-checkbox').classes()).toContain('-ds-elevation');
158
+ });
159
+ });
160
+
161
+ describe('checkbox values', () => {
162
+ it('should display checked icon when value is checked', () => {
163
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.CHECKED });
164
+
165
+ const icon = wrapper.findComponent(Icon);
166
+
167
+ expect(icon.props('icon')).toBe(ICONS.FAD_SQUARE_CHECK);
168
+ });
169
+
170
+ it('should display checked icon when value is unchecked', () => {
171
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.UNCHECKED });
172
+
173
+ const icon = wrapper.findComponent(Icon);
174
+
175
+ expect(icon.props('icon')).toBe(ICONS.FAD_SQUARE);
176
+ });
177
+
178
+ it('should display checked icon when value is indeterminate', () => {
179
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.INDETERMINATE });
180
+
181
+ const icon = wrapper.findComponent(Icon);
182
+
183
+ expect(icon.props('icon')).toBe(ICONS.FAD_SQUARE_MINUS);
184
+ });
185
+ });
186
+
187
+ describe('user interactions', () => {
188
+ it('should emit update:modelValue when clicked', async () => {
189
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.UNCHECKED });
190
+
191
+ await wrapper.find('label').trigger('click');
192
+
193
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
194
+ expect(wrapper.emitted('update:modelValue')?.[0]?.[0]).toBe(CHECKBOX_VALUES.CHECKED);
195
+ });
196
+
197
+ it('should toggle from checked to unchecked when clicked', async () => {
198
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.CHECKED });
199
+
200
+ await wrapper.find('label').trigger('click');
201
+
202
+ expect(wrapper.emitted('update:modelValue')?.[0]?.[0]).toBe(CHECKBOX_VALUES.UNCHECKED);
203
+ });
204
+
205
+ it('should toggle from indeterminate to checked when clicked', async () => {
206
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.INDETERMINATE });
207
+
208
+ await wrapper.find('label').trigger('click');
209
+
210
+ expect(wrapper.emitted('update:modelValue')?.[0]?.[0]).toBe(CHECKBOX_VALUES.CHECKED);
211
+ });
212
+
213
+ it('should not emit when disabled and clicked', async () => {
214
+ const wrapper = setup({
215
+ modelValue: CHECKBOX_VALUES.UNCHECKED,
216
+ state: CHECKBOX_STATES.DISABLED,
217
+ });
218
+
219
+ await wrapper.find('label').trigger('click');
220
+
221
+ // Should not emit because checkbox is disabled
222
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy();
223
+ });
224
+ });
225
+
226
+ describe('accessibility', () => {
227
+ it('should have proper ARIA attributes', () => {
228
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.CHECKED });
229
+
230
+ const checkboxRoot = wrapper.find('[role="checkbox"]');
231
+ expect(checkboxRoot.attributes('role')).toBe('checkbox');
232
+ expect(checkboxRoot.attributes('aria-checked')).toBe('true');
233
+ });
234
+
235
+ it('should have aria-checked="false" when unchecked', () => {
236
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.UNCHECKED });
237
+
238
+ const checkboxRoot = wrapper.find('[role="checkbox"]');
239
+ expect(checkboxRoot.attributes('aria-checked')).toBe('false');
240
+ });
241
+
242
+ it('should have aria-checked="mixed" when indeterminate', () => {
243
+ const wrapper = setup({ modelValue: CHECKBOX_VALUES.INDETERMINATE });
244
+
245
+ const checkboxRoot = wrapper.find('[role="checkbox"]');
246
+ expect(checkboxRoot.attributes('aria-checked')).toBe('mixed');
247
+ });
248
+
249
+ it('should be focusable when not disabled', () => {
250
+ const wrapper = setup();
251
+
252
+ const checkboxRoot = wrapper.find('[role="checkbox"]');
253
+
254
+ expect(checkboxRoot.attributes('type')).toBe('button');
255
+ expect(checkboxRoot.attributes('disabled')).toBeUndefined();
256
+ });
257
+
258
+ it('should not be focusable when disabled', () => {
259
+ const wrapper = setup({ state: CHECKBOX_STATES.DISABLED });
260
+
261
+ const checkboxRoot = wrapper.find('[role="checkbox"]');
262
+ expect(checkboxRoot.attributes('disabled')).toBeDefined();
263
+ });
264
+ });
265
+
266
+ describe('styling classes', () => {
267
+ it('should apply multiple modifier classes correctly', () => {
268
+ const wrapper = setup({
269
+ size: CHECKBOX_SIZES.MEDIUM,
270
+ state: CHECKBOX_STATES.ERROR,
271
+ elevation: CHECKBOX_ELEVATIONS.X_SMALL,
272
+ });
273
+
274
+ const checkbox = wrapper.find('.ds-checkbox');
275
+ expect(checkbox.classes()).toContain('-ds-medium');
276
+ expect(checkbox.classes()).toContain('-ds-error');
277
+ expect(checkbox.classes()).toContain('-ds-elevation');
278
+ });
279
+ });
280
+
281
+ it('should work with v-model pattern', async () => {
282
+ const modelValue = CHECKBOX_VALUES.UNCHECKED;
283
+ const onUpdate = vi.fn();
284
+
285
+ const wrapper = setup({
286
+ modelValue,
287
+ 'onUpdate:modelValue': onUpdate,
288
+ });
289
+
290
+ await wrapper.find('label').trigger('click');
291
+
292
+ expect(onUpdate).toHaveBeenCalledWith(CHECKBOX_VALUES.CHECKED);
293
+ });
294
+ });
@@ -1,10 +1,16 @@
1
1
  import Checkbox from './Checkbox.vue';
2
2
 
3
3
  import { Meta, StoryFn } from '@storybook/vue3';
4
- import { args, argTypes, template } from '../SelectionControl/SelectionControl.sb.shared';
5
- import { CHECKBOX_SIZE, CHECKBOX_STATE } from './Checkbox.consts';
6
- import { useArgs } from '@storybook/preview-api';
4
+ import {
5
+ CHECKBOX_SIZES,
6
+ CHECKBOX_STATES,
7
+ CHECKBOX_VALUES,
8
+ CHECKBOX_ELEVATIONS,
9
+ } from './Checkbox.consts';
7
10
  import { withActions } from '@storybook/addon-actions/decorator';
11
+ import { computed } from 'vue';
12
+ import Banner from '../../Banner';
13
+ import { useArgs } from '@storybook/preview-api';
8
14
 
9
15
  export default {
10
16
  title: 'Components/Form/Checkbox',
@@ -16,33 +22,68 @@ const StoryTemplate: StoryFn<typeof Checkbox> = (args) => {
16
22
  const [_, updateArgs] = useArgs();
17
23
 
18
24
  return {
19
- components: { Checkbox },
25
+ components: { Checkbox, Banner },
20
26
  setup() {
21
- return { args };
22
- },
23
- methods: {
24
- onIsSelectedUpdated(isSelected) {
25
- updateArgs({
26
- isSelected,
27
- });
28
- },
27
+ const props = computed(() => {
28
+ const { default: defaultSlot, modelValue, ...rest } = args;
29
+
30
+ return rest;
31
+ });
32
+
33
+ const defaultSlot = computed(() => args.default);
34
+ const modelValue = computed(() => args.modelValue);
35
+
36
+ return { defaultSlot, props, modelValue, updateArgs };
29
37
  },
30
- template: template('checkbox'),
38
+ template: `
39
+ <Checkbox
40
+ v-bind="props"
41
+ :model-value="modelValue"
42
+ @update:model-value="(value) => updateArgs({ modelValue: value })"
43
+ >
44
+ <span v-if="defaultSlot" v-html="defaultSlot" />
45
+ </Checkbox>
46
+ <Banner color="danger" title="Uwaga! Mogą wystąpić problemy z pisaniem testów jednostkowych korzystających z tego komponentu. Unikaj jego używania. A jeśli jest rok 2026 i wciąż widzisz ten komunikat — nakrzycz na Karola!" title-in-color />
47
+ `,
31
48
  };
32
49
  };
33
50
 
34
51
  export const Interactive = StoryTemplate.bind({});
35
52
 
36
- Interactive.argTypes = argTypes(CHECKBOX_SIZE, CHECKBOX_STATE);
53
+ Interactive.argTypes = {
54
+ size: {
55
+ control: 'select',
56
+ options: Object.values(CHECKBOX_SIZES),
57
+ },
58
+ modelValue: {
59
+ control: 'select',
60
+ options: Object.values(CHECKBOX_VALUES),
61
+ },
62
+ state: {
63
+ control: 'select',
64
+ options: Object.values(CHECKBOX_STATES),
65
+ },
66
+ elevation: {
67
+ control: 'select',
68
+ options: Object.values(CHECKBOX_ELEVATIONS),
69
+ },
70
+ default: {
71
+ control: 'text',
72
+ },
73
+ };
37
74
 
38
- Interactive.args = args(CHECKBOX_SIZE, CHECKBOX_STATE);
75
+ Interactive.args = {
76
+ default: 'Example label',
77
+ modelValue: false,
78
+ size: CHECKBOX_SIZES.SMALL,
79
+ state: CHECKBOX_STATES.DEFAULT,
80
+ elevation: CHECKBOX_ELEVATIONS.X_SMALL,
81
+ value: 'example',
82
+ };
39
83
 
40
84
  Interactive.parameters = {
41
- actions: {
42
- handles: ['click', 'toggle'],
43
- },
44
85
  design: {
45
86
  type: 'figma',
46
- url: 'https://www.figma.com/file/izQdYyiBR1GQgFkaOIfIJI/LMS---DS-Components?type=design&node-id=1552-34962&t=Ui6dF84wekRpqsXb-0',
87
+ url: 'https://www.figma.com/design/izQdYyiBR1GQgFkaOIfIJI/LMS---DS-Components?node-id=7269-127863&m=dev',
47
88
  },
48
89
  };