vuetify icon indicating copy to clipboard operation
vuetify copied to clipboard

[Feature Request] Add a slot to be able to change the icon template in v-stepper-step

Open Tofandel opened this issue 5 years ago â€ĸ 17 comments

Problem to solve

This would allow for more costumization of the stepper icon, currently the icon can only be passed as editableIcon, errorIcon, completedIcon

This would allow to change the logic of the displayed icons and to give more flexibility for their display (For example to add an image, or to change the default number or text to an icon, and to change the size and color of the stepper icon)

Related #3963

Proposed solution

To add a scoped slot named "icon" that will pass the props of the parent (hasError, completed, editable, isActive, isInactive and the icons props) so that we can declare our components like so

<v-stepper-step>
    <template #icon="step">
        <v-icon v-if="step.isActive">ActiveIcon</v-icon>
        <v-icon v-else-if="step.editable">{{step.editableIcon}}</v-icon>
        //Etc ...
    </template>
    Step Label
</v-stepper-step>

Tofandel avatar Apr 23 '19 10:04 Tofandel

I also was looking to change the icons to Font Awesome icons in the steppers, and it just won't work ☚ī¸ Maybe there's some way to "hack" it?

megaroeny avatar May 28 '19 18:05 megaroeny

There is a way to hack it but it's really really bad and I don't recommend it

You can use the :step property to change the default text and it accepts html so you can enter something like <i class="fa fa-smt" > inside, the problem you'll find is it's also the identifier of the step so you can't have the same twice and you also can't use components

Tofandel avatar May 28 '19 18:05 Tofandel

@jaminroe https://vuetifyjs.com/en/framework/icons#install-font-awesome-5-icons v-stepper-step also has complete-icon, edit-icon, and error-icon props.

KaelWD avatar May 29 '19 11:05 KaelWD

@KaelWD @johnleider Any news?

It should be as simple as doing this

    genStepContent () {
      //Simply return the content of the "icon" slot if it's defined with the entire VStepperStep node passed as a prop
      if (typeof this.$scopedSlots.icon === 'function') {
        return this.$scopedSlots.icon(this);
      }
      
      const children = []
      
      if (this.hasError) {
        children.push(this.genIcon(this.errorIcon))
      } else if (this.complete) {
        if (this.editable) {
          children.push(this.genIcon(this.editIcon))
        } else {
          children.push(this.genIcon(this.completeIcon))
        }
      } else {
        children.push(String(this.step))
      }
      
      return children
    },

Tofandel avatar Jun 06 '19 14:06 Tofandel

Seems this was forgotten but is in good demand, so a little bump

Tofandel avatar Jul 05 '20 20:07 Tofandel

This would be really nice, also the ability to change the color of the individual steps because there isn't a horizontal timeline, and the stepper would work perfectly as a horizontal timeline if it was a bit more customizable.

nmalchy avatar Jul 15 '20 22:07 nmalchy

Checking on this... need it :)

res63661 avatar Sep 02 '20 20:09 res63661

In dire need of this too

anjayluh avatar Sep 17 '20 13:09 anjayluh

Same here!

FBenkhalifa avatar Sep 22 '20 19:09 FBenkhalifa

A workaround for this is putting the icon unicode value in the :before of the step. This becomes a hassle if you have a lot of steps though.

deRaaf avatar Sep 30 '20 11:09 deRaaf

@Tofandel how used this solution ? i try add like you but not working 🤔🤔

  • VStepperStep
genStepContent () {
      if (typeof this.$scopedSlots.icon === 'function') {
        return this.$scopedSlots.icon(this);
      }
      const children = []

      if (this.hasError) {
        children.push(this.genIcon(this.errorIcon))
      } else if (this.complete) {
        if (this.editable) {
          children.push(this.genIcon(this.editIcon))
        } else {
          children.push(this.genIcon(this.completeIcon))
        }
      } else {
        children.push(String(this.step))
      }

      return children
    },
  • My code
<v-stepper-step
  :color="steps > 1 ? 'success' : 'primary'"
  :complete="steps > 1"
  step="1"
>
  <template #icon="step">
    <v-icon v-if="step.isActive">headset</v-icon>
    <v-icon>headset</v-icon>
  </template>
  <span
    class="headline"
    :class="steps > 1 ? 'success--text' : 'primary--text'"
    >Call Center</span
  >
</v-stepper-step>

ibraahim6 avatar Feb 01 '21 11:02 ibraahim6

@ibraahim6 This is not a solution, only a proposal to be added to vuetify, you'd need to modify the vuetify source and recompile it as a local dependency of your project for it to work (which is quite a pain if you use vuetify-loader), my code is also possibly not up to date anymore

Tofandel avatar Feb 09 '21 11:02 Tofandel

@ibraahim6 @Tofandel I think the cleanest solution right now is to extend the VStepperStep with custom code:

VeStepperStep (Vuetify Extended Stepper Step):

import { VStepperStep } from 'vuetify/lib/components/VStepper'
export default {
  // eslint-disable-next-line
  name: 've-stepper-step',
  extends: VStepperStep,
  methods: {
    genStepContent() {
      const children = []
      if (this.hasError) {
        children.push(this.genIcon(this.errorIcon))
      } else if (this.complete) {
        if (this.editable) {
          children.push(this.genIcon(this.editIcon))
        } else {
          children.push(this.genIcon(this.completeIcon))
        }
      } else if (this.$attrs.icon) {
        // this "else if" branch is added,
        // so the step can display custom
        // Vuetify icons
        children.push(this.genIcon(this.$attrs.icon))
      } else {
        children.push(String(this.step))
      }

      return children
    },
  },
}

To make it work smoothly with the stepper, the VStepper should also be extended:

import { VStepper } from 'vuetify/lib'
export default {
  // eslint-disable-next-line
  name: 've-stepper',
  extends: VStepper,
  methods: {
    register(item) {
      if (
        item.$options.name === 've-stepper-step' ||
        item.$options.name === 'v-stepper-step'
      ) {
        this.steps.push(item)
      } else if (item.$options.name === 'v-stepper-content') {
        item.isVertical = this.vertical
        this.content.push(item)
      }
    },

    unregister(item) {
      if (
        item.$options.name === 've-stepper-step' ||
        item.$options.name === 'v-stepper-step'
      ) {
        this.steps = this.steps.filter(i => i !== item)
      } else if (item.$options.name === 'v-stepper-content') {
        item.isVertical = this.vertical
        this.content = this.content.filter(i => i !== item)
      }
    },
  },
}

This way, when you include & use ve-stepper you can add steps of ve-stepper-step that accept an icon prop - everything else stays the same as it is with the original VStepper & VStepperStep.

Working piece: @codesandbox

https://user-images.githubusercontent.com/5388876/121066542-4df2a180-c7ca-11eb-9f4d-47c006788caa.mov

gegeke avatar Jun 07 '21 17:06 gegeke

@gegeke If you name it v-stepper-step instead of ve-stepper-step you don't need to extend the stepper as well, vue allows for components to have the same name

Tofandel avatar Jun 07 '21 20:06 Tofandel

@gegeke If you name it v-stepper-step instead of ve-stepper-step you don't need to extend the stepper as well, vue allows for components to have the same name

@Tofandel thanks for the comment - I agree, fewer changes are needed that way, less to maintain.

But to me, it's a cleaner solution if names are changed - easier to keep track of modifications & I like to use the project-naming convention if there is one for components (be they written or just customized).

gegeke avatar Jun 08 '21 06:06 gegeke

bump

fadedhero avatar Dec 08 '21 16:12 fadedhero

Am I not seeing something.. or?

genStepContent () {
  const children = []
  if (this.hasError) {
    children.push(this.genIcon(this.errorIcon))
  } else if (this.complete) {
    if (this.editable) {
      children.push(this.genIcon(this.editIcon))
    } else {
      children.push(this.genIcon(this.completeIcon))
    }
  } else {
    children.push(String(this.step))
  }
  return children
},

Now just take a look at getIcon

genIcon (icon: string) {
  return this.$createElement(VIcon, icon)
},

Ok, let's take a look at VIcon.ts getIcon function

getIcon (): VuetifyIcon {
  let iconName = ''
  if (this.$slots.default) iconName = this.$slots.default[0].text!.trim()
  return remapInternalIcon(this, iconName)
},

Inside remapInternalIcon there is a lines

// Look for overrides
if (iconName.startsWith('$')) {
  // Get the target icon name
  const iconPath = `$vuetify.icons.values.${iconName.split('$').pop()!.split('.').pop()}`
  // Now look up icon indirection name,
  // e.g. '$vuetify.icons.values.cancel'
  const override = getObjectValueByPath(vm, iconPath, iconName)
  if (typeof override === 'string') iconName = override
  else return override
}

Take a look at const iconPath..

So theoretically it could be possible to add custom item and used it?

Vuetify configuration

icons: {
    iconFont: 'mdi',
    values: {
        myCustomIcon: 'example'
    }
},

Looks like vuetify adds that custom icon

image

<v-stepper-step
    complete-icon="$myCustomIcon"
    edit-icon="$myCustomIcon"
    editable
    step="1"
>

But still this is not working :/

LG0012 avatar Aug 23 '22 12:08 LG0012