@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.
- package/dist/design-system.css +1 -1
- package/dist/design-system.js +26585 -272
- package/dist/design-system.js.map +1 -1
- package/dist/lib/js/components/Buttons/IconButton/IconButton.vue.d.ts +4 -0
- package/dist/lib/js/components/Cards/Card/Card.vue.d.ts +2 -0
- package/dist/lib/js/components/Cards/CardExpandable/CardExpandable.vue.d.ts +13 -0
- package/dist/lib/js/components/Chip/Chip.vue.d.ts +8 -0
- package/dist/lib/js/components/DatePickers/DateBox/DateBox.vue.d.ts +4 -0
- package/dist/lib/js/components/DatePickers/DatePicker/DatePicker.vue.d.ts +4 -0
- package/dist/lib/js/components/DatePickers/DateRangePicker/DateRangePicker.vue.d.ts +4 -0
- package/dist/lib/js/components/Drawer/DrawerHeader/DrawerHeader.vue.d.ts +44 -14
- package/dist/lib/js/components/Drawer/DrawerListItem/DrawerListItem.vue.d.ts +4 -0
- package/dist/lib/js/components/Drawer/DrawerSection/DrawerSection.vue.d.ts +12 -0
- package/dist/lib/js/components/Form/Checkbox/Checkbox.consts.d.ts +18 -5
- package/dist/lib/js/components/Form/Checkbox/Checkbox.vue.d.ts +31 -461
- package/dist/lib/js/components/Form/Checkbox/index.d.ts +2 -0
- package/dist/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.consts.d.ts +9 -0
- package/dist/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.types.d.ts +12 -0
- package/dist/lib/js/components/Form/CheckboxGroupField/index.d.ts +1 -0
- package/dist/lib/js/components/Form/FormControlLabel/FormControlLabel.consts.d.ts +13 -0
- package/dist/lib/js/components/Form/FormControlLabel/FormControlLabel.vue.d.ts +28 -0
- package/dist/lib/js/components/Form/FormField/FormField.types.d.ts +1 -0
- package/dist/lib/js/components/Form/FormField/FormField.utils.d.ts +2 -2
- package/dist/lib/js/components/Form/InputField/useInputFieldWithinForm.d.ts +1 -1
- package/dist/lib/js/components/Form/RadioButton/RadioButton.vue.d.ts +4 -0
- package/dist/lib/js/components/Headers/OverlayHeader/OverlayHeader.vue.d.ts +8 -0
- package/dist/lib/js/components/Headers/SectionHeader/SectionHeader.vue.d.ts +8 -0
- package/dist/lib/js/components/Icons/Icon/Icon.consts.d.ts +4 -0
- package/dist/lib/js/components/Modal/Modal.vue.d.ts +4 -0
- package/dist/lib/js/components/Modals/Modal/Modal.vue.d.ts +8 -0
- package/dist/lib/js/components/Modals/ModalDialog/ModalDialog.vue.d.ts +8 -0
- package/dist/lib/js/components/Outline/OutlineItem/OutlineItem.vue.d.ts +4 -0
- package/dist/lib/js/components/Pagination/Pagination.vue.d.ts +12 -0
- package/dist/lib/js/components/ProgressBar/ProgressBar.vue.d.ts +4 -0
- package/dist/lib/js/components/ProgressDonutChart/ProgressDonutChart.vue.d.ts +4 -0
- package/dist/lib/js/components/RichList/BasicRichListItem/BasicRichListItem.vue.d.ts +69 -469
- package/dist/lib/js/components/RichList/RichListItem/RichListItem.vue.d.ts +69 -469
- package/dist/lib/js/components/SelectList/SelectListItem/SelectListItem.vue.d.ts +4 -0
- package/dist/lib/js/components/SelectList/SelectListItemToggle/SelectListItemToggle.vue.d.ts +4 -0
- package/dist/lib/js/components/SelectionTile/SelectionTile.vue.d.ts +29 -1533
- package/dist/lib/js/components/Statuses/AccessStatus/AccessStatus.vue.d.ts +4 -0
- package/dist/lib/js/components/Statuses/BlockadeStatus/BlockadeStatus.vue.d.ts +4 -0
- package/dist/lib/js/components/SurveyQuestions/SurveyQuestionOpenEnded/SurveyQuestionOpenEnded.vue.d.ts +21 -0
- package/dist/lib/js/components/SurveyQuestions/SurveyQuestionScale/SurveyQuestionScale.vue.d.ts +21 -0
- package/dist/lib/js/components/Switch/Switch.vue.d.ts +4 -0
- package/dist/lib/js/components/Tile/Tile.sb.shared.d.ts +4 -0
- package/dist/lib/js/components/Toast/Toast.vue.d.ts +9 -0
- package/dist/lib/js/components/Toggles/ToggleButton/ToggleButton.vue.d.ts +4 -0
- package/dist/lib/js/composables/useFormFieldWithinForm.d.ts +7 -0
- package/dist/lib/js/icons/fontawesome.d.ts +4 -0
- package/dist/storybook/localhost:8080/node_modules/.vite/deps/@bethinkpl_design-system.js?v=62a0baa6 +7919 -0
- package/lib/js/components/Cards/Card/Card.spec.ts +23 -0
- package/lib/js/components/Cards/Card/Card.stories.ts +1 -0
- package/lib/js/components/Cards/Card/Card.vue +21 -4
- package/lib/js/components/Drawer/DrawerHeader/DrawerHeader.vue +13 -13
- package/lib/js/components/Form/Checkbox/Checkbox.consts.ts +27 -10
- package/lib/js/components/Form/Checkbox/Checkbox.spec.ts +294 -0
- package/lib/js/components/Form/Checkbox/Checkbox.stories.ts +60 -19
- package/lib/js/components/Form/Checkbox/Checkbox.vue +272 -55
- package/lib/js/components/Form/Checkbox/index.ts +2 -0
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.consts.ts +11 -0
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.spec.ts +268 -0
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.stories.ts +92 -0
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.types.ts +13 -0
- package/lib/js/components/Form/CheckboxGroupField/CheckboxGroupField.vue +97 -0
- package/lib/js/components/Form/CheckboxGroupField/index.ts +1 -0
- package/lib/js/components/Form/Form.stories.ts +67 -0
- package/lib/js/components/Form/FormControlLabel/FormControlLabel.consts.ts +16 -0
- package/lib/js/components/Form/FormControlLabel/FormControlLabel.vue +61 -0
- package/lib/js/components/Form/FormField/FormField.types.ts +2 -1
- package/lib/js/components/Form/FormField/FormField.utils.ts +14 -10
- package/lib/js/components/Form/FormField/FormField.vue +3 -2
- package/lib/js/components/Form/InputField/InputField.vue +1 -7
- package/lib/js/components/Form/InputField/useInputFieldWithinForm.ts +4 -29
- package/lib/js/components/RichList/RichListItem/RichListItem.vue +11 -8
- package/lib/js/components/SelectionTile/SelectionTile.vue +64 -95
- package/lib/js/composables/useFormFieldWithinForm.ts +26 -0
- package/lib/js/composables/useLegacyI18n.ts +0 -1
- package/lib/js/icons/fontawesome.ts +8 -0
- package/lib/styles/settings/_animations.scss +3 -0
- 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({
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
<div
|
|
46
46
|
v-if="$slots.content"
|
|
47
47
|
class="ds-card__content"
|
|
48
|
-
:class="{
|
|
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
|
-
|
|
124
|
-
padding: $space-s
|
|
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:
|
|
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
|
|
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-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
7
|
-
|
|
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 =
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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 {
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
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 =
|
|
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 =
|
|
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/
|
|
87
|
+
url: 'https://www.figma.com/design/izQdYyiBR1GQgFkaOIfIJI/LMS---DS-Components?node-id=7269-127863&m=dev',
|
|
47
88
|
},
|
|
48
89
|
};
|