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

[Avatar][joy] Remove imgProps prop and add Codemod script for migration #35859

Merged
merged 12 commits into from
Jan 23, 2023
27 changes: 27 additions & 0 deletions docs/data/joy/components/avatar/avatar.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,33 @@ githubLabel: 'component: avatar'

<p class="description">An avatar is a graphical representation of a user's identity.</p>

:::warning
**imgProps** prop has been removed in [`@mui/[email protected]`](https://github.com/mui/material-ui/releases/tag/v5.11.6).

## Migration
hbjORbj marked this conversation as resolved.
Show resolved Hide resolved

### Codemod

Run this [codemod](https://github.com/mui/material-ui/blob/master/packages/mui-codemod/README.md#joy-text-field-to-input) in your project's terminal:

```sh
npx @mui/codemod v5.0.0/joy-text-field-to-input <path>
```

It will go through all files under `<path>` and replace `<TextField />` with the `<Input />` composition.

### Manual

Remove `imgProps` prop by transferring its value into `slotProps.img`:

```diff
<Avatar
- imgProps={{ ['data-id']: 'imageId' }}
- slotProps={{ root: { ['data-id']: 'rootId' }}}
+ slotProps={{ root: { ['data-id']: 'rootId' }, img: { ['data-id']: 'imageId' } }}
/>
```

## Introduction

The Avatar component can be used to display graphical information about a user in places such as menus, tables, and chats.
Expand Down
18 changes: 18 additions & 0 deletions packages/mui-codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ npx @mui/codemod <transform> <path> --jscodeshift="--printOptions='{\"quote\":\"

### v5.0.0

#### `joy-avatar-remove-imgProps`

Remove `imgProps` prop by transferring its value into `slotProps.img`

This change only affects Joy UI Avatar component.

```diff
<Avatar
- imgProps={{ ['data-id']: 'imageId' }}
- slotProps={{ root: { ['data-id']: 'rootId' }}}
+ slotProps={{ root: { ['data-id']: 'rootId' }, img: { ['data-id']: 'imageId' } }}
/>
```

```sh
npx @mui/codemod v5.0.0/joy-avatar-remove-imgProps <path>
```

#### `joy-text-field-to-input`

Replace `<TextField>` with composition of `Input`
Expand Down
92 changes: 92 additions & 0 deletions packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @param {import('jscodeshift').FileInfo} file
* @param {import('jscodeshift').API} api
*/
export default function transformer(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);
const printOptions = options.printOptions;

root
.find(j.ImportDeclaration)
.filter(({ node }) => {
const sourceVal = node.source.value;

return [
'@mui/joy', // Process only Joy UI components
'@mui/joy/Avatar', // Filter default imports of components other than `Avatar`
].includes(sourceVal);
})
.forEach((path) => {
path.node.specifiers.forEach((elementNode) => {
if (
(elementNode.type === 'ImportSpecifier' && elementNode.imported?.name === 'Avatar') ||
elementNode.type === 'ImportDefaultSpecifier'
) {
// Process only Joy `Avatar` component
root.findJSXElements(elementNode.local.name).forEach((elementPath) => {
if (elementPath.node.type !== 'JSXElement') {
return;
}

const slotPropsAttributeNode = elementPath.node.openingElement.attributes.find(
(attributeNode) =>
attributeNode.type === 'JSXAttribute' &&
attributeNode.name.name === 'slotProps' &&
attributeNode.value.expression?.type === 'ObjectExpression',
);
const newAttributeNodes = [];
elementPath.node.openingElement.attributes.forEach((attributeNode) => {
if (attributeNode.type !== 'JSXAttribute') {
return;
}

if (attributeNode.name.name !== 'imgProps') {
newAttributeNodes.push(attributeNode);
return;
}

const val = attributeNode.value;
if (!val?.expression) {
return;
}

if (slotPropsAttributeNode) {
const imgObjInSlotProps = slotPropsAttributeNode.value.expression.properties.find(
(propNode) =>
propNode.key.name === 'img' && propNode.value.type === 'ObjectExpression',
);
if (imgObjInSlotProps) {
const newProperties = [
...imgObjInSlotProps.value.properties,
...attributeNode.value.expression.properties,
];
imgObjInSlotProps.value.properties = newProperties;
} else {
slotPropsAttributeNode.value.expression.properties.push(
j.objectProperty(j.identifier('img'), attributeNode.value),
);
}
} else {
newAttributeNodes.push(
j.jsxAttribute(
j.jsxIdentifier('slotProps'),
j.jsxExpressionContainer(
j.objectExpression([
j.objectProperty(j.identifier('img'), attributeNode.value.expression),
]),
),
),
);
}
});
elementPath.node.openingElement.attributes = newAttributeNodes;
});
}
});
});

const transformed = root.findJSXElements();

return transformed.toSource(printOptions);
}
29 changes: 29 additions & 0 deletions packages/mui-codemod/src/v5.0.0/joy-avatar-remove-imgProps.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import path from 'path';
import { expect } from 'chai';
import jscodeshift from 'jscodeshift';
import transform from './joy-avatar-remove-imgProps';
import readFile from '../util/readFile';

function read(fileName) {
return readFile(path.join(__dirname, fileName));
}

describe('@mui/codemod', () => {
describe('v5.0.0', () => {
describe('joy-avatar-remove-imgProps', () => {
it('transforms `imgProps` prop to `slotProps.img`', () => {
const actual = transform(
{
source: read('./joy-avatar-remove-imgProps.test/actual.js'),
path: require.resolve('./joy-rename-components-to-slots.test/actual.js'),
},
{ jscodeshift },
{},
);

const expected = read('./joy-avatar-remove-imgProps.test/expected.js');
expect(actual).to.equal(expected, 'The transformed version should be correct');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// the codemod should transform only Joy UI `Avatar`;
import { Avatar as JoyAvatar } from '@mui/joy';
import Avatar from '@mui/joy/Avatar';
import MaterialAvatar from '@mui/material/Avatar';

<div>
<JoyAvatar imgProps={{ ['aria-hidden']: true }} />
<Avatar
slotProps={{ root: { ['aria-hidden']: false }, img: { ['aria-label']: 'imgSlot' } }}
imgProps={{ ['aria-hidden']: true }}
/>
<MaterialAvatar imgProps={{ ['aria-hidden']: true }} />
</div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// the codemod should transform only Joy UI `Avatar`;
import { Avatar as JoyAvatar } from '@mui/joy';
import Avatar from '@mui/joy/Avatar';
import MaterialAvatar from '@mui/material/Avatar';

<div>
<JoyAvatar slotProps={{
img: { ['aria-hidden']: true }
}} />
<Avatar
slotProps={{ root: { ['aria-hidden']: false }, img: {
['aria-label']: 'imgSlot',
['aria-hidden']: true
} }} />
hbjORbj marked this conversation as resolved.
Show resolved Hide resolved
<MaterialAvatar imgProps={{ ['aria-hidden']: true }} />
</div>;
1 change: 0 additions & 1 deletion packages/mui-codemod/src/v5.0.0/joy-text-field-to-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export default function transformer(file, api, options) {
}
propNode.value.properties.forEach((prop) => {
const key = prop.key.value;
// const value = prop.value.value;
const newAttributeNode = j.jsxAttribute(
j.jsxIdentifier(key),
j.jsxExpressionContainer(prop.value),
Expand Down
8 changes: 0 additions & 8 deletions packages/mui-joy/src/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
color: colorProp = 'neutral',
size: sizeProp = 'md',
variant: variantProp = 'soft',
imgProps,
src,
srcSet,
children: childrenProp,
Expand Down Expand Up @@ -186,7 +185,6 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {
alt,
src,
srcSet,
...imgProps,
},
className: classes.img,
elementType: AvatarImg,
Expand All @@ -203,7 +201,6 @@ const Avatar = React.forwardRef(function Avatar(inProps, ref) {

// Use a hook instead of onError on the img element to support server-side rendering.
const loaded = useLoaded({
...imgProps,
...imageProps,
src,
srcSet,
Expand Down Expand Up @@ -248,11 +245,6 @@ Avatar.propTypes /* remove-proptypes */ = {
PropTypes.oneOf(['danger', 'info', 'neutral', 'primary', 'success', 'warning']),
PropTypes.string,
]),
/**
* [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) applied to the `img` element if the component is used to display an image.
* It can be used to listen for the loading error event.
*/
imgProps: PropTypes.object,
/**
* The size of the component.
* It accepts theme values between 'sm' and 'lg'.
Expand Down
7 changes: 0 additions & 7 deletions packages/mui-joy/src/Avatar/AvatarProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,6 @@ export interface AvatarTypeMap<P = {}, D extends React.ElementType = 'div'> {
* @default 'neutral'
*/
color?: OverridableStringUnion<ColorPaletteProp, AvatarPropsColorOverrides>;
/**
* [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) applied to the `img` element if the component is used to display an image.
* It can be used to listen for the loading error event.
*/
imgProps?: React.ImgHTMLAttributes<HTMLImageElement> & {
sx?: SxProps;
};
/**
* The size of the component.
* It accepts theme values between 'sm' and 'lg'.
Expand Down