Skip to content

Commit a800f18

Browse files
authored
Merge pull request #108 from cloudblue/feat/LITE-31805
LITE-31805: Fix important QoL issues in Card and inputs
2 parents f0a1b96 + 23f0d3a commit a800f18

File tree

10 files changed

+125
-24
lines changed

10 files changed

+125
-24
lines changed

components/src/stories/Card.stories.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@ export const Component = {
2222
},
2323
template: `
2424
<ui-card v-bind="args" style="width: 400px">
25-
<div slot="default">
25+
<div>
2626
{{ args.content }}
2727
</div>
28-
<div slot="actions">
29-
<button>Action!</button>
30-
</div>
3128
</ui-card>`,
3229
}),
3330

@@ -37,3 +34,27 @@ export const Component = {
3734
content: 'Card Content',
3835
},
3936
};
37+
38+
export const ComponentWithAllSlots = {
39+
render: (args) => ({
40+
setup() {
41+
return { args };
42+
},
43+
template: `
44+
<ui-card v-bind="args" style="width: 400px">
45+
<div>
46+
{{ args.content }}
47+
</div>
48+
<a slot="title" style="" href="#">Custom title, link</a>
49+
<p slot="subtitle">My <span style="font-weight:bold">custom</span> subtitle :)</p>
50+
<div slot="actions">
51+
<button>Action!</button>
52+
</div>
53+
</ui-card>`,
54+
}),
55+
56+
args: {
57+
title: 'Card title',
58+
content: 'Card Content',
59+
},
60+
};

components/src/widgets/card/widget.vue

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22
<div class="c-card">
33
<div class="c-card__header">
44
<div class="c-card__title-container">
5-
<p
6-
v-if="title"
7-
class="c-card__title"
8-
>
9-
{{ title }}
10-
</p>
11-
<p
12-
v-if="subtitle"
13-
class="c-card__subtitle"
14-
>
15-
{{ subtitle }}
16-
</p>
5+
<div class="c-card__title">
6+
<slot name="title">
7+
<p v-if="title">{{ title }}</p>
8+
</slot>
9+
</div>
10+
<div class="c-card__subtitle">
11+
<slot name="subtitle">
12+
<p v-if="subtitle">{{ subtitle }}</p>
13+
</slot>
14+
</div>
1715
</div>
1816
<div class="c-card__actions">
1917
<slot name="actions" />
@@ -53,26 +51,38 @@ export default {
5351
text-decoration: inherit;
5452
color: inherit;
5553
56-
&__header{
54+
&__header {
5755
display: flex;
58-
margin-bottom: 26px;
56+
margin-bottom: 24px;
5957
align-items: start;
6058
flex-grow: 1;
6159
justify-content: space-between;
6260
}
6361
6462
&__title {
65-
line-height: 25px;
66-
font-size: 20px;
63+
line-height: 24px;
64+
font-size: 18px;
65+
font-weight: 500;
6766
margin: 0;
67+
68+
p,
69+
& ::slotted(p) {
70+
margin: 0;
71+
}
6872
}
6973
7074
&__subtitle {
75+
margin-top: 8px;
7176
font-size: 14px;
77+
font-weight: 400;
7278
line-height: 20px;
7379
color: #707070;
74-
margin: 0;
7580
white-space: pre-wrap;
81+
82+
p,
83+
& ::slotted(p) {
84+
margin: 0;
85+
}
7686
}
7787
7888
&__title-container {

components/src/widgets/menu/widget.spec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ describe('Menu component', () => {
1919

2020
expect(wrapper.vm.showMenu).toBe(false);
2121
});
22+
23+
it('does not toggle menu if disabled is true', async () => {
24+
const wrapper = mount(Menu);
25+
wrapper.vm.showMenu = true;
26+
await wrapper.setProps({ disabled: true });
27+
wrapper.vm.toggle(wrapper.vm.showMenu);
28+
29+
expect(wrapper.vm.showMenu).toBe(true);
30+
});
2231
});
2332

2433
describe('#handleClickOutside', () => {

components/src/widgets/menu/widget.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ const props = defineProps({
4242
type: Boolean,
4343
default: false,
4444
},
45+
disabled: {
46+
type: Boolean,
47+
default: false,
48+
},
4549
});
4650
4751
const emit = defineEmits(['opened', 'closed']);
@@ -56,6 +60,8 @@ const alignmentClass = computed(() =>
5660
const fullWidthClass = computed(() => (props.fullWidth ? 'menu-content_full-width' : ''));
5761
5862
const toggle = () => {
63+
if (props.disabled) return;
64+
5965
showMenu.value = !showMenu.value;
6066
emit(showMenu.value ? 'opened' : 'closed');
6167
};

components/src/widgets/select/widget.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ describe('Select', () => {
4040
expect(wrapper.get('.select-input__label').text()).toEqual('My select');
4141
});
4242

43+
it('adds the disabled class if disabled is true', async () => {
44+
await wrapper.setProps({
45+
disabled: true,
46+
});
47+
48+
expect(wrapper.get('.select-input__selected').classes()).toContain(
49+
'select-input__selected_disabled',
50+
);
51+
});
52+
4353
it('renders a complex array of objects', async () => {
4454
await wrapper.setProps({
4555
options: [

components/src/widgets/select/widget.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
</div>
1515
<ui-menu
1616
v-bind="menuProps"
17+
:disabled="disabled"
1718
@opened="isFocused = true"
1819
@closed="isFocused = false"
1920
>
2021
<div
2122
slot="trigger"
2223
class="select-input__selected"
24+
:class="{ 'select-input__selected_disabled': disabled }"
2325
>
2426
<slot name="selected">
2527
<span v-if="model">{{ getDisplayText(selectedOption) }}</span>
@@ -134,6 +136,10 @@ const props = defineProps({
134136
type: String,
135137
default: 'Nothing was found',
136138
},
139+
disabled: {
140+
type: Boolean,
141+
default: false,
142+
},
137143
});
138144
139145
const emit = defineEmits(['valueChange']);
@@ -206,6 +212,12 @@ watch(
206212
border-color: #FF6A6A;
207213
outline: 1px solid #FF6A6A;
208214
}
215+
216+
&_disabled {
217+
border-style: dashed;
218+
cursor: default;
219+
color: #BDBDBD;
220+
}
209221
}
210222
211223
&__menu {

components/src/widgets/textarea/widget.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919
v-model="localValue"
2020
class="textarea-field__input"
2121
:class="inputClasses"
22+
:disabled="disabled"
2223
:placeholder="placeholder"
2324
:readonly="props.readonly"
2425
:rows="rows"
2526
name="textarea"
2627
@focus="setFocus"
2728
@input.stop
28-
></textarea>
29+
/>
2930
<div
3031
v-if="hint || !isValid"
3132
class="textarea-field__hint"
@@ -92,6 +93,10 @@ const props = defineProps({
9293
type: Array,
9394
default: () => [],
9495
},
96+
disabled: {
97+
type: Boolean,
98+
default: false,
99+
},
95100
});
96101
97102
const localValue = ref('');
@@ -117,6 +122,8 @@ const removeFocus = () => {
117122
};
118123
119124
const setFocus = () => {
125+
if (props.disabled) return;
126+
120127
txtarea.value.focus();
121128
isFocused.value = true;
122129
};
@@ -125,12 +132,14 @@ const computedClasses = computed(() => ({
125132
'textarea-field_focused': isFocused.value,
126133
'textarea-field_invalid': !isValid.value,
127134
'textarea-field_optional': !props.required,
135+
'textarea-field_disabled': props.disabled,
128136
}));
129137
130138
const inputClasses = computed(() => ({
131139
'textarea-field__input_no-resize': props.autoGrow,
132140
'textarea-field__input_no-border': props.noBorder,
133141
'textarea-field__input_monospace': props.monospace,
142+
'textarea-field__input_disabled': props.disabled,
134143
}));
135144
136145
onMounted(() => {
@@ -188,6 +197,13 @@ watch(localValue, async (newValue) => {
188197
&_monospace {
189198
font-family: monospace;
190199
}
200+
201+
&[disabled],
202+
:disabled,
203+
&_disabled {
204+
border-style: dashed;
205+
cursor: default;
206+
}
191207
}
192208
193209
&__label {

components/src/widgets/textfield/widget.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ describe('Textfield widget', () => {
6060

6161
expect(wrapper.get('.text-field label').text()).toEqual('My input');
6262
});
63+
64+
it('adds the disabled class if disabled is true', () => {
65+
wrapper = mount(Textfield, {
66+
props: { disabled: true },
67+
});
68+
69+
expect(wrapper.get('.text-field').classes()).toContain('text-field_disabled');
70+
});
6371
});
6472

6573
describe('validation', () => {

components/src/widgets/textfield/widget.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
v-model="localValue"
2222
class="text-field__input"
2323
:class="{ 'text-field__input_right': suffix }"
24+
:autocomplete="browserAutocomplete ? 'on' : 'off'"
25+
:disabled="disabled"
2426
:placeholder="placeholder"
2527
name="textfield"
2628
type="text"
27-
:autocomplete="browserAutocomplete ? 'on' : 'off'"
2829
@focus="setFocus"
2930
@input.stop
3031
/>
@@ -92,6 +93,10 @@ const props = defineProps({
9293
type: Boolean,
9394
default: true,
9495
},
96+
disabled: {
97+
type: Boolean,
98+
default: false,
99+
},
95100
});
96101
97102
const emit = defineEmits(['input']);
@@ -107,10 +112,13 @@ const computedClasses = computed(() => ({
107112
'text-field_focused': isFocused.value,
108113
'text-field_invalid': !isValid.value,
109114
'text-field_no-borders': props.noBorders,
115+
'text-field_disabled': props.disabled,
110116
}));
111117
112118
const removeFocus = () => (isFocused.value = false);
113119
const setFocus = () => {
120+
if (props.disabled) return;
121+
114122
isFocused.value = true;
115123
inputEl.value.focus();
116124
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"test:coverage": "vitest run --coverage.enabled",
2020
"test:watch": "vitest watch",
2121
"storybook": "storybook dev -p 6006",
22-
"build-storybook": "storybook build"
22+
"build-storybook": "storybook build",
23+
"precommit": "npm run test && npm run lint:fix && npm run format && npm run build"
2324
},
2425
"dependencies": {
2526
"@cloudblueconnect/material-svg": "^1.0.43",

0 commit comments

Comments
 (0)