Repository: mui/material-ui (TypeScript)
Date: 2018-09-25 21:07:08+00:00
Task type: Feature

Issue:
[StepIcon] Pass classes to icon component instead of simply render it.
That would be nice to provide StepIcon classes to the rendered icon when using custom icons.
It would allow to render the icon based on Step state.

<!-- Checked checkbox should look like this: [x] -->
- [x] This is not a v0.x issue. <!-- (v0.x is no longer maintained) -->
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior
Icon rendered by a `StepIcon` < `completed StepLabel` should have `completed` class. Same for `active` and `error` classes.

One way to do it would be to clone the icon to inject some props in it : 
```
 return React.cloneElement(icon, {
    className: classNames(classes.root, {
      [classes.active]: active,
      [classes.completed]: completed,
      [classes.error]: error
    })
  })
```

I'm still a bit new to react, so let me know if there is a better way to do this.

## Current Behavior
No class is passed to the render icon.

Right now the StepIcon function returns the icon without modification : https://github.com/mui-org/material-ui/blob/19c818842dba8208b1296173a8c7a7ddc6629c31/packages/material-ui/src/StepIcon/StepIcon.js#L62

## Examples
```
const MyStepIcon = step => ({ className }) => (
  <div className={className}>
    {step.icon}
  </div>
);
```
```
render() {
...
const IconStep = MyStepIcon(step);
...
return (
  <Step>
    <StepLabel
      icon={<IconStep />}
    >
      <span>{step.title}</span>
    </StepLabel>
  </Step>
...
```

## Context
I am using custom icons on each Step, and am trying to change the icon color for completed Step.



Issue discussion:
We could be adding a `StepIconComponent` property to the `StepLabel` component. This property would allow people to override this one:
https://github.com/mui-org/material-ui/blob/19c818842dba8208b1296173a8c7a7ddc6629c31/packages/material-ui/src/StepLabel/StepLabel.js#L105

@semos Do you want to work on it?
@oliviertassinari
If @semos can't work on it I can help.
Let me know.

Cheers

Solution patch:
diff --git a/packages/material-ui/src/StepLabel/StepLabel.d.ts b/packages/material-ui/src/StepLabel/StepLabel.d.ts
--- a/packages/material-ui/src/StepLabel/StepLabel.d.ts
+++ b/packages/material-ui/src/StepLabel/StepLabel.d.ts
@@ -16,6 +16,7 @@ export interface StepLabelProps
   last?: boolean;
   optional?: React.ReactNode;
   orientation?: Orientation;
+  StepIconComponent?: React.ReactType;
   StepIconProps?: Partial<StepIconProps>;
 }
 
diff --git a/packages/material-ui/src/StepLabel/StepLabel.js b/packages/material-ui/src/StepLabel/StepLabel.js
--- a/packages/material-ui/src/StepLabel/StepLabel.js
+++ b/packages/material-ui/src/StepLabel/StepLabel.js
@@ -78,10 +78,17 @@ function StepLabel(props) {
     last,
     optional,
     orientation,
+    StepIconComponent: StepIconComponentProp,
     StepIconProps,
     ...other
   } = props;
 
+  let StepIconComponent = StepIconComponentProp;
+
+  if (icon && !StepIconComponent) {
+    StepIconComponent = StepIcon;
+  }
+
   return (
     <span
       className={classNames(
@@ -96,13 +103,13 @@ function StepLabel(props) {
       )}
       {...other}
     >
-      {icon && (
+      {icon || StepIconComponent ? (
         <span
           className={classNames(classes.iconContainer, {
             [classes.alternativeLabel]: alternativeLabel,
           })}
         >
-          <StepIcon
+          <StepIconComponent
             completed={completed}
             active={active}
             error={error}
@@ -110,7 +117,7 @@ function StepLabel(props) {
             {...StepIconProps}
           />
         </span>
-      )}
+      ) : null}
       <span className={classes.labelContainer}>
         <Typography
           variant="body1"
@@ -184,6 +191,10 @@ StepLabel.propTypes = {
    * @ignore
    */
   orientation: PropTypes.oneOf(['horizontal', 'vertical']),
+  /**
+   * The component to render in place of the [`StepIcon`](/api/step-icon).
+   */
+  StepIconComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),
   /**
    * Properties applied to the [`StepIcon`](/api/step-icon) element.
    */
diff --git a/pages/api/step-label.md b/pages/api/step-label.md
--- a/pages/api/step-label.md
+++ b/pages/api/step-label.md
@@ -25,6 +25,7 @@ import StepLabel from '@material-ui/core/StepLabel';
 | <span class="prop-name">error</span> | <span class="prop-type">bool | <span class="prop-default">false</span> | Mark the step as failed. |
 | <span class="prop-name">icon</span> | <span class="prop-type">node |   | Override the default icon. |
 | <span class="prop-name">optional</span> | <span class="prop-type">node |   | The optional node to display. |
+| <span class="prop-name">StepIconComponent</span> | <span class="prop-type">union:&nbsp;string&nbsp;&#124;<br>&nbsp;func&nbsp;&#124;<br>&nbsp;object<br> |   | The component to render in place of the [`StepIcon`](/api/step-icon). |
 | <span class="prop-name">StepIconProps</span> | <span class="prop-type">object |   | Properties applied to the [`StepIcon`](/api/step-icon) element. |
 
 Any other properties supplied will be spread to the root element (native element).


Test patch:
diff --git a/packages/material-ui/src/StepLabel/StepLabel.test.js b/packages/material-ui/src/StepLabel/StepLabel.test.js
--- a/packages/material-ui/src/StepLabel/StepLabel.test.js
+++ b/packages/material-ui/src/StepLabel/StepLabel.test.js
@@ -59,6 +59,42 @@ describe('<StepLabel />', () => {
     });
   });
 
+  describe('prop: StepIconComponent', () => {
+    it('should render', () => {
+      const CustomizedIcon = () => <div />;
+      const wrapper = shallow(
+        <StepLabel
+          StepIconComponent={CustomizedIcon}
+          active
+          completed
+          StepIconProps={{ prop1: 'value1', prop2: 'value2' }}
+        >
+          Step One
+        </StepLabel>,
+      );
+      const stepIcon = wrapper.find(StepIcon);
+      assert.strictEqual(stepIcon.length, 0);
+
+      const customizedIcon = wrapper.find(CustomizedIcon);
+      assert.strictEqual(customizedIcon.length, 1);
+      const props = customizedIcon.props();
+      assert.strictEqual(props.prop1, 'value1');
+      assert.strictEqual(props.prop2, 'value2');
+      assert.strictEqual(props.completed, true);
+      assert.strictEqual(props.active, true);
+    });
+
+    it('should not render', () => {
+      const wrapper = shallow(
+        <StepLabel active completed>
+          Step One
+        </StepLabel>,
+      );
+      const stepIcon = wrapper.find(StepIcon);
+      assert.strictEqual(stepIcon.length, 0);
+    });
+  });
+
   describe('prop: active', () => {
     it('renders <Typography> with the className active', () => {
       const wrapper = shallow(<StepLabel active>Step One</StepLabel>);