Skip to content

Commit

Permalink
feat(Form): replace with UI5 Web Component (#5925)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The `Form` component has been replaced with the `ui5-form` UI5 Web Component, please visit our [Migration Guide](https://sap.github.io/ui5-webcomponents-react/main/?path=/docs/migration-guide--docs) for more details.

---------

Co-authored-by: Lukas Harbarth <lukas.harbarth@sap.com>
  • Loading branch information
MarcusNotheis and Lukas742 committed Jun 19, 2024
1 parent 8348998 commit 1e246ee
Show file tree
Hide file tree
Showing 27 changed files with 881 additions and 2,051 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- uses: preactjs/compressed-size-action@2.6.0
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
pattern: 'packages/**/dist/**/*.js'
pattern: 'packages/**/dist/**/*.{js,css}'
compression: 'gzip'
clean-script: 'clean:remove-modules'

Expand Down
5 changes: 5 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
user-select: none;
}

.formAlignLabelStart::part(label) {
padding-block-start: 0.25rem;
align-self: start;
}

/* TODO remove this workaround as soon as https://github.com/storybookjs/storybook/issues/20497 is fixed */
.docs-story > div > div[scale] {
min-height: 20px;
Expand Down
59 changes: 59 additions & 0 deletions docs/MigrationGuide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,63 @@ function MyComponent() {
}
```

### Form

The `Form` component has been replaced with the `ui5-form` Web Component.
You can use the new `Form` component as a feature complete replacement of the old Form component with the important differences to mention:

1. You can't mix `FormGroup`s and `FormItem`s as children of the Form. Either only use `FormItem`s or only `FormGroup`s with `FormItem`s inside.
2. Additional HTML elements between `Form / FormGroup / FormItem` are not allowed. You can still use custom React components if they render a `FormGroup` or `FormItem` as most outer element (or a fragment).

```tsx
// v1
import { Form, FormGroup, FormItem } from '@ui5/webcomponents-react';

function MyComponent() {
return (
<Form
backgroundDesign="Solid"
titleText="My Form"
labelSpanS={1}
labelSpanM={2}
labelSpanL={3}
labelSpanXL={4}
columnsS={1}
columnsM={2}
columnsL={3}
columnsXL={4}
as={'form'}
>
<FormGroup titleText="My Form Group" as="h5">
<FormItem label={'MyLabel'}>{/* FormItem Content */}</FormItem>
</FormGroup>
</Form>
);
}

// v2
import { Form, FormGroup, FormItem, Label } from '@ui5/webcomponents-react';

function MyComponent() {
return (
// `backgroundDesign` and `as` have been removed without replacement
<Form
// `titleText` has been renamed to `headerText`
headerText="My Form"
// the `columnsX` props have been merged into the `layout` string
layout="S1 M2 L3 XL4"
// the `labelSpanX` props have been merged into the `labelSpan` string
labelSpan="S1 M2 L3 XL4"
>
{/* `titleText` has been renamed to `headerText`, `as` has been removed */}
<FormGroup headerText="My Form Group">
{/* the `label` prop has been renamed to a `labelContent` slot.
It doesn't support strings anymore, it's recommended to use the `Label` component in this slot. */}
<FormItem labelContent={<Label>MyLabel</Label>}>{/* FormItem Content */}</FormItem>
</FormGroup>
</Form>
);
}
```

<Footer />
27 changes: 27 additions & 0 deletions packages/cli/src/scripts/codemod/transforms/v2/codemodConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,33 @@
},
"FilterItem": {},
"FilterItemOption": {},
"Form": {
"comment": "merge labelSpan and columns props",
"changedProps": {
"titleText": "headerText",
"labelSpanS": "labelSpan",
"labelSpanM": "labelSpan",
"labelSpanL": "labelSpan",
"labelSpanXL": "labelSpan",
"columnsS": "layout",
"columnsM": "layout",
"columnsL": "layout",
"columnsXL": "layout"
},
"removedProps": ["backgroundDesign", "as"]
},
"FormGroup": {
"changedProps": {
"titleText": "headerText"
},
"removedProps": ["as"]
},
"FormItem": {
"comment": "if label is string, convert it to Label Component",
"changedProps": {
"label": "labelContent"
}
},
"GroupHeaderListItem": {
"newComponent": "ListItemGroup"
},
Expand Down
167 changes: 122 additions & 45 deletions packages/cli/src/scripts/codemod/transforms/v2/main.cts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { API, Collection, FileInfo, JSCodeshift, Options } from 'jscodeshift';
import type { API, ASTPath, Collection, FileInfo, JSCodeshift, JSXElement, Options } from 'jscodeshift';

const config = require('./codemodConfig.json');

Expand Down Expand Up @@ -45,6 +45,32 @@ function addWebComponentsReactImport(j: JSCodeshift, root: Collection, importNam
}
}

function extractValueFromProp(
j: JSCodeshift,
el: ASTPath<JSXElement>,
componentName: string,
propName: string
): string | null {
const prop = j(el).find(j.JSXAttribute, { name: { name: propName } });

if (prop.size()) {
const s = prop.get();
const stringLiteral = prop.find(j.StringLiteral);
const numericLiteral = prop.find(j.NumericLiteral);
prop.remove();

if (stringLiteral.size() > 0) {
return stringLiteral.get().value.value;
} else if (numericLiteral.size() > 0) {
return numericLiteral.get().value.value;
} else {
console.warn(`Unable to read value for prop '${propName}' (${componentName}). Please check the code manually.`);
return null;
}
}
return null;
}

export default function transform(file: FileInfo, api: API, options?: Options): string | undefined {
const j = api.jscodeshift;
const root = j(file.source);
Expand Down Expand Up @@ -82,52 +108,21 @@ export default function transform(file: FileInfo, api: API, options?: Options):

if (componentName === 'Carousel') {
jsxElements.forEach((el) => {
const itemsPerPageS = j(el).find(j.JSXAttribute, { name: { name: 'itemsPerPageS' } });
const itemsPerPageM = j(el).find(j.JSXAttribute, { name: { name: 'itemsPerPageM' } });
const itemsPerPageL = j(el).find(j.JSXAttribute, { name: { name: 'itemsPerPageL' } });

const sizeValues: string[] = [];

if (itemsPerPageS.size()) {
const s = itemsPerPageS.get();
const stringLiteral = itemsPerPageS.find(j.StringLiteral);
const numericLiteral = itemsPerPageS.find(j.NumericLiteral);

if (stringLiteral.size() > 0) {
sizeValues.push(`S${stringLiteral.get().value.value}`);
} else if (numericLiteral.size() > 0) {
sizeValues.push(`S${numericLiteral.get().value.value}`);
} else {
console.warn(`Unable to read value for prop 'itemsPerPageS' (Carousel). Please check the code manually.`);
}
}

if (itemsPerPageM.size()) {
const stringLiteral = itemsPerPageM.find(j.StringLiteral);
const numericLiteral = itemsPerPageM.find(j.NumericLiteral);
if (stringLiteral.size() > 0) {
sizeValues.push(`M${stringLiteral.get().value.value}`);
} else if (numericLiteral.size() > 0) {
sizeValues.push(`M${numericLiteral.get().value.value}`);
} else {
console.warn(`Unable to read value for prop 'itemsPerPageM' (Carousel). Please check the code manually.`);
}
}

if (itemsPerPageL.size()) {
const stringLiteral = itemsPerPageL.find(j.StringLiteral);
const numericLiteral = itemsPerPageL.find(j.NumericLiteral);
if (stringLiteral.size() > 0) {
sizeValues.push(`L${stringLiteral.get().value.value}`);
} else if (numericLiteral.size() > 0) {
sizeValues.push(`L${numericLiteral.get().value.value}`);
} else {
console.warn(`Unable to read value for prop 'itemsPerPageL' (Carousel). Please check the code manually.`);
}
}
const sizeValues: string[] = [
['S', 'itemsPerPageS'],
['M', 'itemsPerPageM'],
['L', 'itemsPerPageL']
]
.map(([key, prop]) => {
const val = extractValueFromProp(j, el, componentName, prop);
if (val != null) {
return `${key}${val}`;
}
return '';
})
.filter((val) => val.length > 0);

if (sizeValues.length > 0) {
[itemsPerPageS, itemsPerPageM, itemsPerPageL].forEach((e) => e.remove());
j(el)
.find(j.JSXOpeningElement)
.get()
Expand All @@ -139,6 +134,88 @@ export default function transform(file: FileInfo, api: API, options?: Options):
});
}

if (componentName === 'Form') {
jsxElements.forEach((el) => {
const labelSpan: string[] = [
['S', 'labelSpanS'],
['M', 'labelSpanM'],
['L', 'labelSpanL'],
['XL', 'labelSpanXL']
]
.map(([key, prop]) => {
const val = extractValueFromProp(j, el, componentName, prop);
if (val != null) {
return `${key}${val}`;
}
return '';
})
.filter((val) => val.length > 0);

if (labelSpan.length > 0) {
j(el)
.find(j.JSXOpeningElement)
.get()
.value.attributes.push(j.jsxAttribute(j.jsxIdentifier('labelSpan'), j.stringLiteral(labelSpan.join(' '))));
isDirty = true;
}

const layout: string[] = [
['S', 'columnsS'],
['M', 'columnsM'],
['L', 'columnsL'],
['XL', 'columnsXL']
]
.map(([key, prop]) => {
const val = extractValueFromProp(j, el, componentName, prop);
if (val != null) {
return `${key}${val}`;
}
return '';
})
.filter((val) => val.length > 0);

if (layout.length > 0) {
j(el)
.find(j.JSXOpeningElement)
.get()
.value.attributes.push(j.jsxAttribute(j.jsxIdentifier('layout'), j.stringLiteral(layout.join(' '))));
isDirty = true;
}
});
}

if (componentName === 'FormItem') {
jsxElements.forEach((el) => {
const label = j(el).find(j.JSXAttribute, { name: { name: 'label' } });
if (label.size()) {
const labelNode = label.get();
let value: string | undefined;
if (labelNode.value.value.type === 'StringLiteral') {
value = labelNode.value.value.value;
}
if (
labelNode.value.value.type === 'JSXExpressionContainer' &&
labelNode.value.value.expression.type === 'StringLiteral'
) {
value = labelNode.value.value.expression.value;
}

if (value) {
addWebComponentsReactImport(j, root, 'Label');
const labelComponent = j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier('Label'), [], false),
j.jsxClosingElement(j.jsxIdentifier('Label')),
[j.jsxText(value)]
);
label.replaceWith(
j.jsxAttribute(j.jsxIdentifier('labelContent'), j.jsxExpressionContainer(labelComponent))
);
isDirty = true;
}
}
});
}

if (componentName === 'Icon') {
jsxElements.forEach((el) => {
const interactive = j(el).find(j.JSXAttribute, { name: { name: 'interactive' } });
Expand Down
Loading

0 comments on commit 1e246ee

Please sign in to comment.