0
votes

I am working on a wrapped component for the v-stepper in Vuetify. In this case, i want the user to define a slot when using the component and then i will use that slot name to build out the steps.

I need to have this slot exist for both a desktop view and a mobile view. I tried using v-if to hide if mobile or not, but that caused other issues, so I went with v-show, but that gives me an error in developer console:

Duplicate presence of slot "page6" found in the same render tree - this will likely cause render errors.

Here is how the component is structured Note that these are in the same .vue file

Desktop snippet

<v-stepper-items>
    <v-stepper-content v-for="(item,index) in steps" :key="index" :step="index+1">
      <slot :name="item.slot"></slot>
    </v-stepper-content>
  </v-stepper-items>

Mobile snippet

<v-stepper-content :key="`${index}-stepContent-mobile`" :step="index+1">
    <slot :name="item.slot"></slot>
  </v-stepper-content>

I tried doing a slot scope, but that doesn't seem to apply here

<v-stepper-items>
    <v-stepper-content v-for="(item,index) in steps" :key="index" :step="index+1">
      <template :slot-scope="item.slot"><slot :name="item.slot"></slot></template>
    </v-stepper-content>
  </v-stepper-items>

This is how the user would setup the slot when using the component which can be seen here

 <div slot="page5">
    <h4>Step 3</h4>
  </div>

So the key here is that the user sets up one slot when using the component, but the component puts that slot in 2 places in the .vue file, one in the desktop section and one in the mobile section. It needs to have the same name, however, as it's effectively the same slot...

Here is my github project with the code, you can pull it down and run npm install and then npm run dev to see it in action: Vuetify-Simple-Wizard

2

2 Answers

1
votes

You could use Portal-Vue to multi-cast the step slots to the two locations (desk and mobile).

Since portal's target name can be dynamic, it works when resizing the window after initial render, i.e whenever isMobile changes value the portal re-evaluates and moves it's content to the appropriate section.

<v-stepper v-model="stepStage" :alt-labels="!isMobile" :vertical="isMobile" >
  <div v-show="!isMobile">
    ...
    <v-stepper-items>
      <v-stepper-content v-for="(item,index) in steps" :key="index" :step="index+1">
        <portal-target :name="`portal-desk-${index}`" :key="index" slim></portal-target>
      </v-stepper-content>
    </v-stepper-items>
    ...
  </div>
  <div v-show="isMobile">
    <template v-for="(item,index) in steps">
      ...
      <v-stepper-content :key="`${index}-stepContent-mobile`" :step="index+1">
        <portal-target :name="`portal-mobile-${index}`" :key="index" slim></portal-target>
      </v-stepper-content>
      ...
    </template>
  </div>  

  <template v-for="(item, index) in steps">
    <portal :to="portalName(index)" :key="index">
      <template :slot-scope="item.slot"><slot :name="item.slot"></slot></template>
    </portal>
  </template>

</v-stepper>
computed: {
  portalName() {
    return (index) => {
      const section = this.isMobile ? 'mobile' : 'desk';
      return `portal-${section}-${index}` 
    }
  }
},

v-if

I'm not sure what problems you found with v-if, but it seems to work with v-if on v-stepper-content

<div v-show="!isMobile">
  ...
  <v-stepper-content v-if="!isMobile" v-for="(item,index) in steps" :key="index" :step="index+1">
  ...
<div v-show="isMobile">
  ...
  <v-stepper-content v-if="isMobile" :key="`${index}-stepContent-mobile`" :step="index+1">
1
votes

You could break the component up into SimpleWizardMobile and SimpleWizardDesk, then use a dynamic component template in SimpleWizard,

template

<template>
  <component :is="deviceType" 
    :completeStep="completeStep" 
    :previousStepLabel="previousStepLabel"
    :nextStepLabel="nextStepLabel"
    :steps="steps"
    :onNext="onNext"
    :onBack="onBack"
    :mobileBreakpoint="mobileBreakpoint"
    :theme="theme"
    ></component>
</template>

component

<script>
import SimpleWizardDesk from './SimpleWizardDesk'
import SimpleWizardMobile from './SimpleWizardMobile'
...
computed: {
  deviceType() {
    return this.isMobile ? SimpleWizardMobile : SimpleWizardDesk
  }
},

I confirmed that your dev harness is still working with this structure, but have not worked out all the js refactoring (just used the same component for each and refactored the templates).