Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-form): add new emptySpan property #10194

Merged
merged 6 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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