Skip to content

Commit f24540f

Browse files
authored
feat: add FormItem and labelsAside support to autoResponsive mode (#8729)
- Added support for <vaadin-form-item> elements with labels positioned above inputs by default when used with autoResponsive (which is opposite to its default behavior with responsiveSteps). - Introduced a labelsAside property, which allows <vaadin-form-item> labels to be displayed next to inputs when there is enough space in the container.
1 parent af47011 commit f24540f

21 files changed

+539
-44
lines changed

dev/form-layout-auto-responsive.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,37 @@ <h1>autoResponsive inside Dialog + expandColumns</h1>
191191
></vaadin-dialog>
192192
<vaadin-button @click="${this.openPrevSiblingDialog}">Open</vaadin-button>
193193
194+
<h1>autoResponsive inside Dialog + labelsAside</h1>
195+
<vaadin-dialog
196+
${dialogRenderer(
197+
(dialog) => html`
198+
<vaadin-form-layout auto-responsive auto-rows max-columns="2" labels-aside>
199+
<vaadin-form-item>
200+
<label slot="label"> First name </label>
201+
<vaadin-text-field></vaadin-text-field>
202+
</vaadin-form-item>
203+
<vaadin-form-item>
204+
<label slot="label"> Last name </label>
205+
<vaadin-text-field></vaadin-text-field>
206+
</vaadin-form-item>
207+
<vaadin-form-item colspan="2">
208+
<label slot="label"> Username </label>
209+
<vaadin-text-field colspan="2"></vaadin-text-field>
210+
</vaadin-form-item>
211+
<vaadin-form-item>
212+
<label slot="label"> Password </label>
213+
<vaadin-password-field></vaadin-password-field>
214+
</vaadin-form-item>
215+
<vaadin-form-item>
216+
<label slot="label"> Confirm </label>
217+
<vaadin-password-field></vaadin-password-field>
218+
</vaadin-form-item>
219+
</vaadin-form-layout>
220+
`,
221+
)}
222+
></vaadin-dialog>
223+
<vaadin-button @click="${this.openPrevSiblingDialog}">Open</vaadin-button>
224+
194225
<h1>autoResponsive inside Dialog 80%</h1>
195226
<vaadin-dialog
196227
width="80%"

packages/form-layout/src/vaadin-form-layout-mixin.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@ export declare class FormLayoutMixinClass {
108108
*/
109109
autoRows: boolean;
110110

111+
/**
112+
* When enabled with `autoResponsive`, `<vaadin-form-item>` prefers positioning
113+
* labels beside the fields. If the layout is too narrow to fit a single column
114+
* with side labels, they revert to their default position above the fields.
115+
*
116+
* To customize the label width and the gap between the label and the field,
117+
* use the following CSS properties:
118+
*
119+
* - `--vaadin-form-layout-label-width`
120+
* - `--vaadin-form-layout-label-spacing`
121+
*
122+
* @attr {boolean} labels-aside
123+
*/
124+
labelsAside: boolean;
125+
111126
/**
112127
* Update the layout.
113128
*/

packages/form-layout/src/vaadin-form-layout-mixin.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,26 @@ export const FormLayoutMixin = (superClass) =>
146146
reflectToAttribute: true,
147147
},
148148

149+
/**
150+
* When enabled with `autoResponsive`, `<vaadin-form-item>` prefers positioning
151+
* labels beside the fields. If the layout is too narrow to fit a single column
152+
* with side labels, they revert to their default position above the fields.
153+
*
154+
* To customize the label width and the gap between the label and the field,
155+
* use the following CSS properties:
156+
*
157+
* - `--vaadin-form-layout-label-width`
158+
* - `--vaadin-form-layout-label-spacing`
159+
*
160+
* @attr {boolean} labels-aside
161+
*/
162+
labelsAside: {
163+
type: Boolean,
164+
sync: true,
165+
value: false,
166+
reflectToAttribute: true,
167+
},
168+
149169
/**
150170
* Current number of columns in the layout
151171
* @private
@@ -392,8 +412,10 @@ export const FormLayoutMixin = (superClass) =>
392412

393413
/** @private */
394414
__updateCSSGridLayout() {
395-
let resetColumn = false;
415+
const fitsLabelsAside = this.offsetWidth >= this.__columnWidthWithLabelsAside;
416+
this.$.layout.toggleAttribute('fits-labels-aside', this.labelsAside && fitsLabelsAside);
396417

418+
let resetColumn = false;
397419
[...this.children]
398420
.flatMap((child) => {
399421
return child.localName === 'vaadin-form-row' ? [...child.children] : child;
@@ -448,4 +470,10 @@ export const FormLayoutMixin = (superClass) =>
448470
this.style.removeProperty('--_max-columns');
449471
}
450472
}
473+
474+
/** @private */
475+
get __columnWidthWithLabelsAside() {
476+
const { backgroundPositionY } = getComputedStyle(this.$.layout, '::before');
477+
return parseFloat(backgroundPositionY);
478+
}
451479
};

packages/form-layout/src/vaadin-form-layout-styles.js

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,25 @@ export const formLayoutStyles = css`
4545
}
4646
4747
:host([auto-responsive]) {
48-
--_column-gap: var(--vaadin-form-layout-column-spacing);
49-
--_max-total-gap-width: calc((var(--_max-columns) - 1) * var(--_column-gap));
50-
--_max-total-col-width: calc(var(--_max-columns) * var(--_column-width));
48+
--_column-width-labels-above: var(--_column-width);
49+
--_column-width-labels-aside: calc(
50+
var(--_column-width) + var(--vaadin-form-layout-label-width) + var(--vaadin-form-layout-label-spacing)
51+
);
5152
5253
display: flex;
53-
min-width: var(--_column-width);
54+
min-width: var(--_column-width-labels-above);
5455
}
5556
5657
:host([auto-responsive]) #layout {
58+
--_grid-column-gap: var(--vaadin-form-layout-column-spacing);
59+
--_grid-column-width: var(--_column-width-labels-above);
60+
--_grid-column-max-total-gap: calc((var(--_max-columns) - 1) * var(--_grid-column-gap));
61+
--_grid-column-max-total-width: calc(var(--_max-columns) * var(--_column-width-labels-above));
62+
5763
display: grid;
58-
grid-template-columns: repeat(auto-fit, var(--_column-width));
64+
grid-template-columns: repeat(auto-fit, var(--_grid-column-width));
5965
justify-items: start;
60-
gap: var(--vaadin-form-layout-row-spacing) var(--_column-gap);
66+
gap: var(--vaadin-form-layout-row-spacing) var(--_grid-column-gap);
6167
6268
/*
6369
To prevent the layout from exceeding the column limit defined by --_max-columns,
@@ -71,7 +77,7 @@ export const formLayoutStyles = css`
7177
number of columns inside <vaadin-overlay>, which creates a new stacking context
7278
without a predefined width.
7379
*/
74-
width: calc(var(--_max-total-col-width) + var(--_max-total-gap-width));
80+
width: calc(var(--_grid-column-max-total-width) + var(--_grid-column-max-total-gap));
7581
7682
/*
7783
Firefox requires min-width on both :host and #layout to allow the layout
@@ -80,13 +86,33 @@ export const formLayoutStyles = css`
8086
min-width: inherit;
8187
}
8288
89+
:host([auto-responsive]) #layout::before {
90+
background-position-y: var(--_column-width-labels-aside);
91+
}
92+
8393
:host([auto-responsive]) #layout ::slotted(*) {
94+
--_form-item-labels-above: initial; /* true */
95+
--_form-item-labels-aside: ' '; /* false */
96+
8497
grid-column-start: 1;
8598
}
8699
87100
:host([auto-responsive][auto-rows]) #layout ::slotted(*) {
88101
grid-column-start: var(--_column-start, auto);
89102
}
103+
104+
:host([auto-responsive][labels-aside]) #layout {
105+
--_grid-column-max-total-width: calc(var(--_max-columns) * var(--_column-width-labels-aside));
106+
}
107+
108+
:host([auto-responsive][labels-aside]) #layout[fits-labels-aside] {
109+
--_grid-column-width: var(--_column-width-labels-aside);
110+
}
111+
112+
:host([auto-responsive][labels-aside]) #layout[fits-labels-aside] ::slotted(*) {
113+
--_form-item-labels-above: ' '; /* false */
114+
--_form-item-labels-aside: initial; /* true */
115+
}
90116
`;
91117

92118
export const formRowStyles = css`
@@ -109,30 +135,33 @@ export const formRowStyles = css`
109135

110136
export const formItemStyles = css`
111137
:host {
138+
--_form-item-labels-above: ' '; /* false */
139+
--_form-item-labels-aside: initial; /* true */
140+
112141
display: inline-flex;
113-
flex-direction: row;
114-
align-items: baseline;
142+
align-items: var(--_form-item-labels-aside, baseline);
143+
flex-flow: var(--_form-item-labels-above, column) nowrap;
144+
justify-self: stretch;
115145
margin: calc(0.5 * var(--vaadin-form-item-row-spacing, var(--vaadin-form-layout-row-spacing, 1em))) 0;
116146
}
117147
118148
:host([label-position='top']) {
119-
flex-direction: column;
120-
align-items: stretch;
149+
--_form-item-labels-above: initial; /* true */
150+
--_form-item-labels-aside: ' '; /* false */
121151
}
122152
123153
:host([hidden]) {
124154
display: none !important;
125155
}
126156
127157
#label {
128-
width: var(--vaadin-form-item-label-width, var(--vaadin-form-layout-label-width, 8em));
158+
width: var(
159+
--_form-item-labels-aside,
160+
var(--vaadin-form-item-label-width, var(--vaadin-form-layout-label-width, 8em))
161+
);
129162
flex: 0 0 auto;
130163
}
131164
132-
:host([label-position='top']) #label {
133-
width: auto;
134-
}
135-
136165
#spacing {
137166
width: var(--vaadin-form-item-label-spacing, var(--vaadin-form-layout-label-spacing, 1em));
138167
flex: 0 0 auto;

packages/form-layout/test/dom/__snapshots__/form-layout.test.snap.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ snapshots["vaadin-form-layout auto-responsive basic host columnWidth"] =
4040
`;
4141
/* end snapshot vaadin-form-layout auto-responsive basic host columnWidth */
4242

43+
snapshots["vaadin-form-layout auto-responsive basic host labelsAside"] =
44+
`<vaadin-form-layout
45+
auto-responsive=""
46+
labels-aside=""
47+
style="--_column-width: 13em; --_max-columns: 10;"
48+
>
49+
<input placeholder="First name">
50+
<input placeholder="Last name">
51+
<input placeholder="Email">
52+
<input placeholder="Phone">
53+
</vaadin-form-layout>
54+
`;
55+
/* end snapshot vaadin-form-layout auto-responsive basic host labelsAside */
56+
4357
snapshots["vaadin-form-layout auto-responsive basic shadow default"] =
4458
`<div id="layout">
4559
<slot id="slot">
@@ -48,6 +62,25 @@ snapshots["vaadin-form-layout auto-responsive basic shadow default"] =
4862
`;
4963
/* end snapshot vaadin-form-layout auto-responsive basic shadow default */
5064

65+
snapshots["vaadin-form-layout auto-responsive basic shadow labelsAside in narrow container"] =
66+
`<div id="layout">
67+
<slot id="slot">
68+
</slot>
69+
</div>
70+
`;
71+
/* end snapshot vaadin-form-layout auto-responsive basic shadow labelsAside in narrow container */
72+
73+
snapshots["vaadin-form-layout auto-responsive basic shadow labelsAside in wide container"] =
74+
`<div
75+
fits-labels-aside=""
76+
id="layout"
77+
>
78+
<slot id="slot">
79+
</slot>
80+
</div>
81+
`;
82+
/* end snapshot vaadin-form-layout auto-responsive basic shadow labelsAside in wide container */
83+
5184
snapshots["vaadin-form-layout auto-responsive autoRows default"] =
5285
`<vaadin-form-layout
5386
auto-responsive=""

packages/form-layout/test/dom/form-layout.test.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
2+
import { fixtureSync, nextFrame, nextResize } from '@vaadin/testing-helpers';
33
import '../../src/vaadin-form-layout.js';
44

55
describe('vaadin-form-layout', () => {
@@ -33,12 +33,32 @@ describe('vaadin-form-layout', () => {
3333
layout.columnWidth = '15em';
3434
await expect(layout).dom.to.equalSnapshot();
3535
});
36+
37+
it('labelsAside', async () => {
38+
layout.labelsAside = true;
39+
await nextResize(layout);
40+
await expect(layout).dom.to.equalSnapshot();
41+
});
3642
});
3743

3844
describe('shadow', () => {
3945
it('default', async () => {
4046
await expect(layout).shadowDom.to.equalSnapshot();
4147
});
48+
49+
it('labelsAside in narrow container', async () => {
50+
layout.style.width = `calc(${layout.columnWidth} + 6em)`;
51+
layout.labelsAside = true;
52+
await nextResize(layout);
53+
await expect(layout).shadowDom.to.equalSnapshot();
54+
});
55+
56+
it('labelsAside in wide container', async () => {
57+
layout.style.width = '40em';
58+
layout.labelsAside = true;
59+
await nextResize(layout);
60+
await expect(layout).shadowDom.to.equalSnapshot();
61+
});
4262
});
4363
});
4464

packages/form-layout/test/form-item.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('form-item', () => {
7575
item.setAttribute('label-position', 'top');
7676

7777
expect(getComputedStyle(item).getPropertyValue('flex-direction')).to.equal('column');
78-
expect(getComputedStyle(item).getPropertyValue('align-items')).to.equal('stretch');
78+
expect(getComputedStyle(item).getPropertyValue('align-items')).to.equal('normal');
7979
});
8080
});
8181

0 commit comments

Comments
 (0)