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

[material-ui][TableSortLabel] Introduce slots and slotProps #42157

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 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
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,25 @@ Here's how to migrate:
},
```

## TableSortLabel

Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#table-sort-label-props) below to migrate the code as described in the following sections:

```bash
npx @mui/codemod@next deprecations/table-sort-label-props <path>
```

### IconComponent

The TableSortLabel's `IconComponent` was deprecated in favor of `slots.icon`:

```diff
<TableSortLabel
- IconComponent={IconComponent}
+ slots={{ icon: IconComponent }}
/>
```

## StepLabel

Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#step-label-props) below to migrate the code as described in the following sections:
Expand Down
29 changes: 22 additions & 7 deletions docs/pages/material-ui/api/table-sort-label.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@
"default": "'asc'"
},
"hideSortIcon": { "type": { "name": "bool" }, "default": "false" },
"IconComponent": { "type": { "name": "elementType" }, "default": "ArrowDownwardIcon" },
"IconComponent": {
"type": { "name": "elementType" },
"default": "ArrowDownwardIcon",
"deprecated": true,
"deprecationInfo": "Use <code>slots.icon</code> instead. This prop will be removed in v7. See <a href=\"/material-ui/migration/migrating-from-deprecated-apis/\">Migrating from deprecated APIs</a> for more details."
},
"slotProps": {
"type": { "name": "shape", "description": "{ icon?: func<br>&#124;&nbsp;object }" },
"default": "{}"
},
"slots": {
"type": { "name": "shape", "description": "{ icon?: elementType }" },
"default": "{}"
},
"sx": {
"type": {
"name": "union",
Expand All @@ -22,19 +35,21 @@
"import TableSortLabel from '@mui/material/TableSortLabel';",
"import { TableSortLabel } from '@mui/material';"
],
"slots": [
{
"name": "icon",
"description": "Sort icon to use.",
"default": "ArrowDownwardIcon",
"class": "MuiTableSortLabel-icon"
}
],
"classes": [
{
"key": "active",
"className": "Mui-active",
"description": "State class applied to the root element if `active={true}`.",
"isGlobal": true
},
{
"key": "icon",
"className": "MuiTableSortLabel-icon",
"description": "Styles applied to the icon component.",
"isGlobal": false
},
{
"key": "iconDirectionAsc",
"className": "MuiTableSortLabel-iconDirectionAsc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"direction": { "description": "The current sort direction." },
"hideSortIcon": { "description": "Hide sort icon when active is false." },
"IconComponent": { "description": "Sort icon to use." },
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." },
"sx": {
"description": "The system prop that allows defining system overrides as well as additional CSS styles."
}
Expand All @@ -19,7 +21,6 @@
"nodeName": "the root element",
"conditions": "<code>active={true}</code>"
},
"icon": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the icon component" },
"iconDirectionAsc": {
"description": "Styles applied to {{nodeName}} if {{conditions}}.",
"nodeName": "the icon component",
Expand All @@ -31,5 +32,6 @@
"conditions": "<code>direction=\"desc\"</code>"
},
"root": { "description": "Styles applied to the root element." }
}
},
"slotDescriptions": { "icon": "Sort icon to use." }
}
23 changes: 23 additions & 0 deletions packages/mui-codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,29 @@ npx @mui/codemod@latest deprecations/step-label-props <path>

```

#### `table-sort-label-props`

```diff
<TableSortLabel
- IconComponent={IconComponent}
+ slots={{ icon: IconComponent }}
/>
```

```diff
MuiTableSortLabel: {
defaultProps: {
- IconComponent:IconComponent
+ slots:{ icon: IconComponent }
},
},
```

```bash
npx @mui/codemod@next deprecations/table-sort-label-props <path>

```

#### `step-connector-classes`

JS transforms:
Expand Down
2 changes: 2 additions & 0 deletions packages/mui-codemod/src/deprecations/all/deprecations-all.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import transformAccordionProps from '../accordion-props';
import transformTableSortLabelProps from '../table-sort-label-props';
import transformFormControlLabelProps from '../form-control-label-props';
import transformAvatarProps from '../avatar-props';
import transformDividerProps from '../divider-props';
Expand All @@ -20,6 +21,7 @@ import transformSpeedDialProps from '../speed-dial-props';
*/
export default function deprecationsAll(file, api, options) {
file.source = transformAccordionProps(file, api, options);
file.source = transformTableSortLabelProps(file, api, options);
file.source = transformFormControlLabelProps(file, api, options);
file.source = transformAvatarProps(file, api, options);
file.source = transformDividerProps(file, api, options);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './table-sort-label-props';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import replaceComponentsWithSlots from '../utils/replaceComponentsWithSlots';
import movePropIntoSlots from '../utils/movePropIntoSlots';

/**
* @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;

replaceComponentsWithSlots(j, { root, componentName: 'TableSortLabel' });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required?


movePropIntoSlots(j, {
root,
componentName: 'TableSortLabel',
propName: 'IconComponent',
slotName: 'icon',
});

return root.toSource(printOptions);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import path from 'path';
import { expect } from 'chai';
import { jscodeshift } from '../../../testUtils';
import transform from './table-sort-label-props';
import readFile from '../../util/readFile';

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

describe('@mui/codemod', () => {
describe('deprecations', () => {
describe('table-sort-label-props', () => {
it('transforms props as needed', () => {
const actual = transform({ source: read('./test-cases/actual.js') }, { jscodeshift }, {});

const expected = read('./test-cases/expected.js');
expect(actual).to.equal(expected, 'The transformed version should be correct');
});

it('should be idempotent', () => {
const actual = transform({ source: read('./test-cases/expected.js') }, { jscodeshift }, {});

const expected = read('./test-cases/expected.js');
expect(actual).to.equal(expected, 'The transformed version should be correct');
});
});

describe('[theme] table-sort-label-props', () => {
it('transforms props as needed', () => {
const actual = transform(
{ source: read('./test-cases/theme.actual.js') },
{ jscodeshift },
{ printOptions: { trailingComma: true } },
);

const expected = read('./test-cases/theme.expected.js');
expect(actual).to.equal(expected, 'The transformed version should be correct');
});

it('should be idempotent', () => {
const actual = transform(
{ source: read('./test-cases/theme.expected.js') },
{ jscodeshift },
{},
);

const expected = read('./test-cases/theme.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,3 @@
import TableSortLabel from '@mui/material/TableSortLabel';

<TableSortLabel IconComponent={IconComponent} />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import TableSortLabel from '@mui/material/TableSortLabel';

<TableSortLabel slots={{
icon: IconComponent
}} />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn({
MuiTableSortLabel: {
defaultProps: {
IconComponent: IconComponent,
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fn({
MuiTableSortLabel: {
defaultProps: {
slots: {
icon: IconComponent,
},
},
},
});
25 changes: 24 additions & 1 deletion packages/mui-material/src/TableSortLabel/TableSortLabel.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,28 @@ import { Theme } from '..';
import { ExtendButtonBase, ExtendButtonBaseTypeMap } from '../ButtonBase';
import { OverrideProps } from '../OverridableComponent';
import { TableSortLabelClasses } from './tableSortLabelClasses';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';

export interface TableSortLabelOwnProps {
export interface TableSortLabelSlots {
/**
* Sort icon to use.
* @default ArrowDownwardIcon
*/
icon?: React.ElementType;
}

export type TableSortLabelSlotsAndSlotProps = CreateSlotsAndSlotProps<
TableSortLabelSlots,
{
icon: SlotProps<
React.ElementType<React.SVGAttributes<SVGSVGElement>>,
{},
TableSortLabelOwnerState
>;
}
>;

export interface TableSortLabelOwnProps extends TableSortLabelSlotsAndSlotProps {
/**
* If `true`, the label will have the active styling (should be true for the sorted column).
* @default false
Expand All @@ -32,6 +52,7 @@ export interface TableSortLabelOwnProps {
/**
* Sort icon to use.
* @default ArrowDownwardIcon
* @deprecated Use `slots.icon` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
IconComponent?: React.JSXElementConstructor<{
className: string;
Expand Down Expand Up @@ -71,4 +92,6 @@ export type TableSortLabelProps<
component?: React.ElementType;
};

export interface TableSortLabelOwnerState extends TableSortLabelProps {}

export default TableSortLabel;
35 changes: 30 additions & 5 deletions packages/mui-material/src/TableSortLabel/TableSortLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import capitalize from '../utils/capitalize';
import tableSortLabelClasses, { getTableSortLabelUtilityClass } from './tableSortLabelClasses';
import useSlot from '../utils/useSlot';

const useUtilityClasses = (ownerState) => {
const { classes, direction, active } = ownerState;
Expand Down Expand Up @@ -102,6 +103,8 @@ const TableSortLabel = React.forwardRef(function TableSortLabel(inProps, ref) {
direction = 'asc',
hideSortIcon = false,
IconComponent = ArrowDownwardIcon,
slots = {},
slotProps = {},
...other
} = props;

Expand All @@ -115,6 +118,17 @@ const TableSortLabel = React.forwardRef(function TableSortLabel(inProps, ref) {

const classes = useUtilityClasses(ownerState);

const externalForwardedProps = {
slots,
slotProps,
};

const [IconSlot, iconProps] = useSlot('icon', {
elementType: IconComponent,
externalForwardedProps,
ownerState,
});

return (
<TableSortLabelRoot
className={clsx(classes.root, className)}
Expand All @@ -126,11 +140,7 @@ const TableSortLabel = React.forwardRef(function TableSortLabel(inProps, ref) {
>
{children}
{hideSortIcon && !active ? null : (
<TableSortLabelIcon
as={IconComponent}
className={clsx(classes.icon)}
ownerState={ownerState}
/>
<TableSortLabelIcon as={IconSlot} className={clsx(classes.icon)} {...iconProps} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be:

  const externalForwardedProps = {
    slots: {
        icon: IconComponent,
        ...slots
    },
    slotProps,
  };

  const [IconSlot, iconProps] = useSlot('icon', {
    elementType: TableSortLabelIcon,
    externalForwardedProps,
    ownerState,
    className: classes.icon
  });

// ...
  <IconSlot {...iconProps} />

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@sai6855 sai6855 May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure?

By going with above approach TableSortLabelIcon won't be in consideration by below logic and it's styles won't be applied

const elementType = slots[name] || initialElementType;

slots[name] (here: IconComponent) preceeds over initialElementType ( here: TableSortLabelIcon)

Copy link
Member

@DiegoAndai DiegoAndai May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see what's happening. There are some *Component props that are used as

<DefaultComponent
    as={ComponentProp}
/>

But others are used as

const { ComponentProp: DefaultComponent } = props

// ...

<ComponentProp />

Which is different.

If I'm not mistaken, the convention for the slot pattern is to work as the latter: When you provide a slot, it replaces the default completely. Is this correct @siriwatknp?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other components with the as behavior:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume codemod should also do same?

Yes, but let's wait for confirmation

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wait for @siriwatknp's response, but my proposal would be:

  • Deprecate IconComponent and keep providing it through as, so we keep the same functionality.
  • Implement slots.icon so that it replaces the slot completely, following the pattern
  • In the deprecation message, the correct replacement for IconComponent would be slotProps.icon.as

Agree with the first two. I don't think the last is needed. Currently, we don't document as prop for Material UI, only component prop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the last is needed. Currently, we don't document as prop for Material UI, only component prop.

But what should we say the replacement for IconComponent is? Options:

  1. We could say the replacement is slots.icon but it's not the same (replaces entire slot)
  2. We could implement the icon prop which behaves as IconComponent does right now. I think it's a common-enough case to change the icon to justify a dedicated prop.

@siriwatknp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @DiegoAndai @siriwatknp any discussion happened on this PR front ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay @sai6855. I think this is a special case (the same for other IconComponent props in the codebase). Let's not deprecate.

)}
</TableSortLabelRoot>
);
Expand Down Expand Up @@ -171,8 +181,23 @@ TableSortLabel.propTypes /* remove-proptypes */ = {
/**
* Sort icon to use.
* @default ArrowDownwardIcon
* @deprecated Use `slots.icon` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
IconComponent: PropTypes.elementType,
/**
* The props used for each slot inside.
* @default {}
*/
slotProps: PropTypes.shape({
icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
}),
/**
* The components used for each slot inside.
* @default {}
*/
slots: PropTypes.shape({
icon: PropTypes.elementType,
}),
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
Expand Down
Loading