Skip to content

Commit

Permalink
feat(ui5-form): add new emptySpan property (#10194)
Browse files Browse the repository at this point in the history
Adds new emptySpan property that provides additional layout flexibility by defining empty space at the form item’s end.

Fixes: #9963
  • Loading branch information
ilhan007 authored Nov 23, 2024
1 parent 7059e09 commit 48b0cc8
Show file tree
Hide file tree
Showing 15 changed files with 1,104 additions and 327 deletions.
70 changes: 47 additions & 23 deletions packages/main/cypress/specs/Form.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "../../src/Text.js";
import "../../src/Input.js";

describe("General API", () => {
it("tests calculated state of Form with default layout and label-span", () => {
it("tests calculated state of Form with default layout, label-span and empty-span", () => {
cy.mount(html`<ui5-form class="addressForm" header-text="Default form">
<ui5-form-group header-text="Address">
<ui5-form-item>
Expand Down Expand Up @@ -36,28 +36,18 @@ describe("General API", () => {
.as("form");

cy.get("@form")
.should("have.prop", "columnsS", 1);

cy.get("@form")
.should("have.prop", "labelSpanS", 12);

cy.get("@form")
.should("have.prop", "columnsM", 1);

cy.get("@form")
.should("have.prop", "labelSpanM", 4);

cy.get("@form")
.should("have.prop", "columnsL", 2);

cy.get("@form")
.should("have.prop", "labelSpanL", 4);

cy.get("@form")
.should("have.prop", "columnsXl", 3);

cy.get("@form")
.should("have.prop", "labelSpanXl", 4);
.should("have.prop", "columnsS", 1)
.and("have.prop", "labelSpanS", 12)
.and("have.prop", "emptySpanS", 0)
.and("have.prop", "columnsM", 1)
.and("have.prop", "labelSpanM", 4)
.and("have.prop", "emptySpanM", 0)
.and("have.prop", "columnsL", 2)
.and("have.prop", "labelSpanL", 4)
.and("have.prop", "emptySpanL", 0)
.and("have.prop", "columnsXl", 3)
.and("have.prop", "emptySpanXl", 0)
.and("have.prop", "labelSpanXl", 4);
});

it("tests calculated state of Form with layout='S1 M2 L3 XL6' and label-span='S12 M4 L4 XL4'", () => {
Expand Down Expand Up @@ -175,6 +165,40 @@ describe("General API", () => {
.should("have.prop", "labelSpanXl", 12);
});

it("tests calculated state of Form empty-span='S0 M0 L1 XL1'", () => {
cy.mount(html`<ui5-form empty-span="L1 XL1">
<ui5-form-group header-text="Address">
<ui5-form-item>
<ui5-label slot="labelContent">Name</ui5-label>
<ui5-text>Red Point Stores</ui5-text>
</ui5-form-item>
</ui5-form-group>
<ui5-form-group id="testFormGroup2" header-text="Contact">
<ui5-form-item>
<ui5-label slot="labelContent">Twitter</ui5-label>
<ui5-text>@sap</ui5-text>
</ui5-form-item>
</ui5-form-group>
<ui5-form-group id="testFormGroup3" header-text="Other info">
<ui5-form-item>
<ui5-label slot="labelContent">Name</ui5-label>
<ui5-text>Red Point Stores</ui5-text>
</ui5-form-item>
</ui5-form-group>
</ui5-form>`);

cy.get("[ui5-form]")
.as("form");

cy.get("@form")
.should("have.prop", "emptySpanS", 0)
.and("have.prop", "emptySpanM", 0)
.and("have.prop", "emptySpanL", 1)
.and("have.prop", "emptySpanXl", 1);
});

it("tests calculated state of two FormGroups in layout='S1 M2 L3 XL4'", () => {
cy.mount(html`<ui5-form header-text="WebC :: Supplier 2gr (S1 M2 L3 XL4)" layout="S1 M2 L3 XL4">
<ui5-form-group id="testFormGroup4" header-text="Address">
Expand Down
108 changes: 99 additions & 9 deletions packages/main/src/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ const StepColumn = {
};

const breakpoints = ["S", "M", "L", "Xl"];

const MAX_FORM_ITEM_CELLS = 12;
const DEFAULT_FORM_ITEM_LAYOUT = "4fr 8fr 0fr";
const DEFAULT_FORM_ITEM_LAYOUT_S = "1fr";
/**
* Interface for components that can be slotted inside `ui5-form` as items.
* @public
* @experimental
* @since 2.0.0
*/
interface IFormItem extends UI5Element {
labelSpan: string
itemSpacing: `${FormItemSpacing}`;
readonly isGroup: boolean;
colsXl?: number;
Expand Down Expand Up @@ -130,6 +131,22 @@ type ItemsInfo = {
*
* **For example:** To always place the labels on top set: `labelSpan="S12 M12 L12 XL12"` property.
*
* ### Items Empty Span
*
* By default, a form item spans 12 cells, fully divided between its label and field, with no empty space at the end:
* - **Label:** occupies 4 cells.
* - **Field:** occupies 8 cells.
*
* The `emptySpan` property provides additional layout flexibility by defining empty space at the form item’s end.
*
* **For example:** Setting "S0 M0 L3 XL3" (or just "L3 XL3") adjusts the layout as follows:
* - **Label:** remains 4 cells.
* - **Field:** is reduced to 5 cells.
* - **Empty space:** 3 cells are added at the end.
*
* Greater values increase the empty space at the end of the form item, reducing the space available for the label and its field.
* However, setting `emptySpan` to 1 cell is recommended and typically sufficient to achieve a balanced layout.
*
* ### Navigation flow
*
* The Form component supports two layout options for keyboard navigation:
Expand Down Expand Up @@ -210,20 +227,38 @@ class Form extends UI5Element {
layout = "S1 M1 L2 XL3"

/**
* Defines the width proportion of the labels and fields of a FormItem by breakpoint.
* Defines the width proportion of the labels and fields of a form item by breakpoint.
*
* By default, the labels take 4/12 (or 1/3) of the form item in M,L and XL sizes,
* and 12/12 in S size, e.g in S the label is on top of its associated field.
*
* The supported values are between 1 and 12. Greater the number, more space the label will use.
*
* **Note:** If "12" is set, the label will be displayed on top of its assosiated field.
*
* @default "S12 M4 L4 XL4"
* @public
*/
@property()
labelSpan = "S12 M4 L4 XL4";

/**
* Defines the number of cells that are empty at the end of each form item, configurable by breakpoint.
*
* By default, a form item spans 12 cells, fully divided between its label (4 cells) and field (8 cells), with no empty space at the end.
* The `emptySpan` provides additional layout flexibility by defining empty space at the form item’s end.
*
* **Note:**
* - The maximum allowable empty space is 10 cells. At least 1 cell each must remain for the label and the field.
* - When `emptySpan` is specified (greater than 0), ensure that the combined value of `emptySpan` and `labelSpan` does not exceed 11. This guarantees a minimum of 1 cell for the field.
*
* @default "S0 M0 L0 XL0"
* @since 2.5.0
* @public
*/
@property()
emptySpan = "S0 M0 L0 XL0";

/**
* Defines the header text of the component.
*
Expand Down Expand Up @@ -279,28 +314,36 @@ class Form extends UI5Element {
columnsS = 1;
@property({ type: Number })
labelSpanS = 12
@property({ type: Number })
emptySpanS = 0

@property({ type: Number })
columnsM = 1;
@property({ type: Number })
labelSpanM = 4;
@property({ type: Number })
emptySpanM = 0

@property({ type: Number })
columnsL = 2;
@property({ type: Number })
labelSpanL = 4;
@property({ type: Number })
emptySpanL = 0

@property({ type: Number })
columnsXl = 3;
@property({ type: Number })
labelSpanXl = 4;
@property({ type: Number })
emptySpanXl = 0;

onBeforeRendering() {
// Parse the layout and set it to the FormGroups/FormItems.
this.setColumnLayout();

// Parse the labelSpan and set it to the FormGroups/FormItems.
this.setLabelSpan();
// Parse the labelSpan and emptySpan and set it to the FormGroups/FormItems.
this.setFormItemLayout();

// Define how many columns a group should take.
this.setGroupsColSpan();
Expand Down Expand Up @@ -328,7 +371,7 @@ class Form extends UI5Element {
});
}

setLabelSpan() {
parseFormItemSpan() {
this.labelSpan.split(" ").forEach((breakpoint: string) => {
if (breakpoint.startsWith("S")) {
this.labelSpanS = parseInt(breakpoint.slice(1));
Expand All @@ -341,12 +384,59 @@ class Form extends UI5Element {
}
});

this.items.forEach((item: IFormItem) => {
item.labelSpan = this.labelSpan;
item.itemSpacing = this.itemSpacing;
this.emptySpan.split(" ").forEach((breakpoint: string) => {
if (breakpoint.startsWith("S")) {
this.emptySpanS = parseInt(breakpoint.slice(1));
} else if (breakpoint.startsWith("M")) {
this.emptySpanM = parseInt(breakpoint.slice(1));
} else if (breakpoint.startsWith("L")) {
this.emptySpanL = parseInt(breakpoint.slice(1));
} else if (breakpoint.startsWith("XL")) {
this.emptySpanXl = parseInt(breakpoint.slice(2));
}
});
}

setFormItemLayout() {
this.parseFormItemSpan();

[
{
breakpoint: "S",
labelSpan: this.labelSpanS,
emptySpan: this.emptySpanS,
},
{
breakpoint: "M",
labelSpan: this.labelSpanM,
emptySpan: this.emptySpanM,
},
{
breakpoint: "L",
labelSpan: this.labelSpanL,
emptySpan: this.emptySpanL,
},
{
breakpoint: "XL",
labelSpan: this.labelSpanXl,
emptySpan: this.emptySpanXl,
},
].forEach(layout => {
if (this.isValidFormItemLayout(layout.labelSpan, layout.emptySpan)) {
const formItemLayout = layout.labelSpan === MAX_FORM_ITEM_CELLS ? `1fr` : `${layout.labelSpan}fr ${MAX_FORM_ITEM_CELLS - (layout.labelSpan + layout.emptySpan)}fr ${layout.emptySpan}fr`;
this.style.setProperty(getScopedVarName(`--ui5-form-item-layout-${layout.breakpoint}`), formItemLayout);
} else {
// eslint-disable-next-line
console.warn(`Form :: invalid usage of emptySpan and/or labelSpan in ${layout.breakpoint} size. The labelSpan must be <=12 and when emptySpace is used - their combined values must not exceed 11.`)
this.style.setProperty(getScopedVarName(`--ui5-form-item-layout-${layout.breakpoint}`), layout.breakpoint === "S" ? DEFAULT_FORM_ITEM_LAYOUT_S : DEFAULT_FORM_ITEM_LAYOUT);
}
});
}

isValidFormItemLayout(labelSpan: number, emptySpan: number) {
return emptySpan === 0 ? labelSpan <= MAX_FORM_ITEM_CELLS : labelSpan + emptySpan <= MAX_FORM_ITEM_CELLS - 1;
}

setFastNavGroup() {
if (this.hasGroupItems) {
this.removeAttribute("data-sap-ui-fastnavgroup");
Expand Down
4 changes: 0 additions & 4 deletions packages/main/src/FormGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,12 @@ class FormGroup extends UI5Element implements IFormItem {
@property()
itemSpacing: `${FormItemSpacing}` = "Normal";

@property()
labelSpan = "S12 M4 L4 XL4";

onBeforeRendering() {
this.processFormItems();
}

processFormItems() {
this.items.forEach((item: FormItem) => {
item.labelSpan = this.labelSpan;
item.itemSpacing = this.itemSpacing;
});
}
Expand Down
6 changes: 0 additions & 6 deletions packages/main/src/FormItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ class FormItem extends UI5Element implements IFormItem {
})
content!: Array<HTMLElement>;

/**
* @private
*/
@property()
labelSpan = "S12 M4 L4 XL4";

/**
* @private
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/themes/Form.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@import "./FormLayout.css";
@import "./FormLabelSpan.css";
@import "./FormItemSpan.css";

:host {
display: block;
Expand Down
59 changes: 59 additions & 0 deletions packages/main/src/themes/FormItemSpan.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* By default,
* - in M, L ans XL sizes the Form's labels take 4 cells out 12 of the whole width, e.g 4:8:0
* - in S size, it the Form's labels take the whole width 12/12 cells, e.g the label and the input are displayed vertically.
*
* The ratio can be changed via the labelSpan and the emptySpan properties.
*/

@container (max-width: 600px) {
.ui5-form-item,
.ui5-form-group {
--ui5-form-item-layout: var(--ui5-form-item-layout-S);
}

:host([label-span-s="12"]) .ui5-form-item,
:host([label-span-s="12"]) .ui5-form-group {
--ui5-form-item-label-justify: var(--ui5-form-item-label-justify-span12);
--ui5-form-item-label-padding: var(--ui5-form-item-label-padding-span12);
}
}

@container (width > 600px) and (width <= 1024px) {
.ui5-form-item,
.ui5-form-group {
--ui5-form-item-layout: var(--ui5-form-item-layout-M);
}

:host([label-span-m="12"]) .ui5-form-item,
:host([label-span-m="12"]) .ui5-form-group {
--ui5-form-item-label-justify: var(--ui5-form-item-label-justify-span12);
--ui5-form-item-label-padding: var(--ui5-form-item-label-padding-span12);
}
}

@container (width > 1024px) and (width <= 1440px) {
.ui5-form-item,
.ui5-form-group {
--ui5-form-item-layout: var(--ui5-form-item-layout-L);
}

:host([label-span-l="12"]) .ui5-form-item,
:host([label-span-l="12"]) .ui5-form-group {
--ui5-form-item-label-justify: var(--ui5-form-item-label-justify-span12);
--ui5-form-item-label-padding: var(--ui5-form-item-label-padding-span12);
}
}

@container (min-width: 1441px) {
.ui5-form-item,
.ui5-form-group {
--ui5-form-item-layout: var(--ui5-form-item-layout-XL);
}

:host([label-span-xl="12"]) .ui5-form-item,
:host([label-span-xl="12"]) .ui5-form-group {
--ui5-form-item-label-justify: var(--ui5-form-item-label-justify-span12);
--ui5-form-item-label-padding: var(--ui5-form-item-label-padding-span12);
}
}
Loading

0 comments on commit 48b0cc8

Please sign in to comment.