Update steps demos

pull/4789/head
Cagatay Civici 2023-11-09 16:32:32 +03:00
parent 4fdbe365ba
commit a4a1f8dacb
19 changed files with 418 additions and 1235 deletions

View File

@ -3,25 +3,7 @@
<p>Steps requires a collection of menuitems as its <i>model</i>.</p> <p>Steps requires a collection of menuitems as its <i>model</i>.</p>
</DocSectionText> </DocSectionText>
<div class="card"> <div class="card">
<Steps <Steps :model="items" />
:model="items"
aria-label="Form Steps"
:readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}"
>
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
</template>
</Steps>
</div> </div>
<DocSectionCode :code="code" /> <DocSectionCode :code="code" />
</template> </template>
@ -32,68 +14,23 @@ export default {
return { return {
items: [ items: [
{ {
label: 'Personal', label: 'Personal Info'
route: '/steps/'
}, },
{ {
label: 'Seat', label: 'Reservation'
route: '/steps/seat'
}, },
{ {
label: 'Payment', label: 'Review'
route: '/steps/payment'
},
{
label: 'Confirmation',
route: '/steps/confirmation'
} }
], ],
code: { code: {
basic: ` basic: `
<Steps :model="items" aria-label="Form Steps" :readonly="false" <Steps :model="items" />
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}">
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<span v-else v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</span>
</template>
</Steps>
`, `,
options: ` options: `
<template> <template>
<div> <div class="card">
<div class="card"> <Steps :model="items" />
<Steps :model="items" aria-label="Form Steps" :readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}">
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<span v-else v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</span>
</template>
</Steps>
</div>
</div> </div>
</template> </template>
@ -103,97 +40,46 @@ export default {
return { return {
items: [ items: [
{ {
label: 'Personal', label: 'Personal Info'
route: '/'
}, },
{ {
label: 'Seat', label: 'Reservation'
route: '/seat'
}, },
{ {
label: 'Payment', label: 'Review'
route: '/payment'
},
{
label: 'Confirmation',
route: '/confirmation'
} }
] ]
} }
},
methods: {
isActive(item) {
return item.route ? this.$router.resolve(item.route).path === this.$route.path : false;
}
} }
} }
<\/script> <\/script>
`, `,
composition: ` composition: `
<template> <template>
<div> <div class="card">
<div class="card"> <Steps :model="items" />
<Steps :model="items" aria-label="Form Steps" :readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}">
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<span v-else v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</span>
</template>
</Steps>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const items = ref([ const items = ref([
{ {
label: 'Personal', label: 'Personal Info'
route: "/"
}, },
{ {
label: 'Seat', label: 'Reservation'
route: "/seat",
}, },
{ {
label: 'Payment', label: 'Review'
route: "/payment",
},
{
label: 'Confirmation',
route: "/confirmation",
} }
]); ]);
const isActive = (item) => {
return item.route ? router.resolve(item.route).path === route.path : false;
}
<\/script> <\/script>
` `
} }
}; };
},
methods: {
isActive(item) {
return item.route ? this.$router.resolve(item.route).path === this.$route.path : false;
}
} }
}; };
</script> </script>

108
doc/steps/ControlledDoc.vue Normal file
View File

@ -0,0 +1,108 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Steps can be controlled programmatically using <i>activeStep</i> property.</p>
</DocSectionText>
<div class="card">
<div class="flex mb-5 gap-2 justify-content-end">
<Button @click="active = 0" rounded label="1" class="w-2rem h-2rem p-0" :outlined="active !== 0" />
<Button @click="active = 1" rounded label="2" class="w-2rem h-2rem p-0" :outlined="active !== 1" />
<Button @click="active = 2" rounded label="3" class="w-2rem h-2rem p-0" :outlined="active !== 2" />
</div>
<Steps v-model:activeStep="active" :model="items" />
</div>
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
active: 0,
items: [
{
label: 'Personal Info'
},
{
label: 'Reservation'
},
{
label: 'Review'
}
],
code: {
basic: `
<div class="flex mb-2 gap-2 justify-content-end">
<Button @click="active = 0" rounded label="1" class="w-2rem h-2rem p-0" :outlined="active !== 0" />
<Button @click="active = 1" rounded label="2" class="w-2rem h-2rem p-0" :outlined="active !== 1" />
<Button @click="active = 2" rounded label="3" class="w-2rem h-2rem p-0" :outlined="active !== 2" />
</div>
<Steps v-model:activeStep="active" :model="items" />
`,
options: `
<template>
<div class="card">
<div class="flex mb-2 gap-2 justify-content-end">
<Button @click="active = 0" rounded label="1" class="w-2rem h-2rem p-0" :outlined="active !== 0" />
<Button @click="active = 1" rounded label="2" class="w-2rem h-2rem p-0" :outlined="active !== 1" />
<Button @click="active = 2" rounded label="3" class="w-2rem h-2rem p-0" :outlined="active !== 2" />
</div>
<Steps v-model:activeStep="active" :model="items" />
</div>
</template>
<script>
export default {
data() {
return {
active: 0,
items: [
{
label: 'Personal Info'
},
{
label: 'Reservation'
},
{
label: 'Review'
}
]
}
}
}
<\/script>
`,
composition: `
<template>
<div class="card">
<div class="flex mb-2 gap-2 justify-content-end">
<Button @click="active = 0" rounded label="1" class="w-2rem h-2rem p-0" :outlined="active !== 0" />
<Button @click="active = 1" rounded label="2" class="w-2rem h-2rem p-0" :outlined="active !== 1" />
<Button @click="active = 2" rounded label="3" class="w-2rem h-2rem p-0" :outlined="active !== 2" />
</div>
<Steps v-model:activeStep="active" :model="items" />
</div>
</template>
<script setup>
import { ref } from "vue";
const active = ref(0);
const items = ref([
{
label: 'Personal Info'
},
{
label: 'Reservation'
},
{
label: 'Review'
}
]);
<\/script>
`
}
};
}
};
</script>

View File

@ -1,571 +0,0 @@
<template>
<DocSectionText v-bind="$attrs">
<p>In order to add interactivity to the component, disable <i>readonly</i> to control the Steps.</p>
</DocSectionText>
<div class="card">
<Steps
:model="items"
aria-label="Form Steps"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}"
>
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
</template>
</Steps>
</div>
<NuxtPage v-slot="{ Component }" :formData="formObject" @prev-page="prevPage($event)" @next-page="nextPage($event)" @complete="complete">
<keep-alive>
<component :is="Component" />
</keep-alive>
</NuxtPage>
<br class="mt-4" />
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
items: [
{
label: 'Personal',
route: '/steps/'
},
{
label: 'Seat',
route: '/steps/seat'
},
{
label: 'Payment',
route: '/steps/payment'
},
{
label: 'Confirmation',
route: '/steps/confirmation'
}
],
formObject: {},
code: {
basic: `
<div class="card">
<Steps
:model="items"
aria-label="Form Steps"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}"
>
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
</template>
</Steps>
</div>
<router-view v-slot="{ Component }" :formData="formObject" @prev-page="prevPage($event)" @next-page="nextPage($event)" @complete="complete">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
`,
options: `
<template>
<div>
<div class="card">
<Steps
:model="items"
aria-label="Form Steps"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}"
>
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
</template>
</Steps>
</div>
<Toast />
<router-view v-slot="{Component}" :formData="formObject" @prevPage="prevPage($event)" @nextPage="nextPage($event)" @complete="complete">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
label: 'Personal',
route: '/'
},
{
label: 'Seat',
route: '/seat'
},
{
label: 'Payment',
route: '/payment'
},
{
label: 'Confirmation',
route: '/confirmation'
}
],
formObject: {}
}
},
methods: {
nextPage(event) {
for (let field in event.formData) {
this.formObject[field] = event.formData[field];
}
this.$router.push(this.items[event.pageIndex + 1].route);
},
prevPage(event) {
this.$router.push(this.items[event.pageIndex - 1].route);
},
complete() {
this.$toast.add({severity:'success', summary:'Order submitted', detail: 'Dear, ' + this.formObject.firstname + ' ' + this.formObject.lastname + ' your order completed.'});
},
isActive(item) {
return item.route ? this.$router.resolve(item.route).path === this.$route.path : false;
}
}
}
<\/script>
`,
composition: `
<template>
<div>
<div class="card">
<Steps
:model="items"
aria-label="Form Steps"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}"
>
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
</template>
</Steps>
</div>
<Toast />
<router-view v-slot="{Component}" :formData="formObject" @prevPage="prevPage($event)" @nextPage="nextPage($event)" @complete="complete">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
const router = useRouter();
const toast = useToast();
const items = ref([
{
label: 'Personal',
route: "/"
},
{
label: 'Seat',
route: "/seat",
},
{
label: 'Payment',
route: "/payment",
},
{
label: 'Confirmation',
route: "/confirmation",
}
]);
const formObject = ref({});
const nextPage = (event) => {
for (let field in event.formData) {
formObject.value[field] = event.formData[field];
}
router.push(items.value[event.pageIndex + 1].route);
};
const prevPage = (event) => {
router.push(items.value[event.pageIndex - 1].route);
};
const complete = () => {
toast.add({severity:'success', summary:'Order submitted', detail: 'Dear, ' + formObject.value.firstname + ' ' + formObject.value.lastname + ' your order completed.'});
};
const isActive = (item) => {
return item.route ? router.resolve(item.route).path === route.path : false;
};
<\/script>
`,
pages: [
{
tabName: 'PersonalDemo',
content: `
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title>
Personal Information
</template>
<template v-slot:subtitle>
Enter your personal information
</template>
<template v-slot:content>
<div class="p-fluid">
<div class="field">
<label for="firstname">Firstname</label>
<InputText id="firstname" v-model="firstname" :class="{'p-invalid': validationErrors.firstname && submitted}" />
<small v-show="validationErrors.firstname && submitted" class="p-error">Firstname is required.</small>
</div>
<div class="field">
<label for="lastname">Lastname</label>
<InputText id="lastname" v-model="lastname" :class="{'p-invalid': validationErrors.lastname && submitted}" />
<small v-show="validationErrors.lastname && submitted" class="p-error">Lastname is required.</small>
</div>
<div class="field">
<label for="age">Age</label>
<InputNumber id="age" v-model="age" />
</div>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<i></i>
<Button label="Next" @click="nextPage()" icon="pi pi-angle-right" iconPos="right" />
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
data () {
return {
firstname: '',
lastname: '',
age: null,
submitted: false,
validationErrors: {}
}
},
methods: {
nextPage() {
this.submitted = true;
if (this.validateForm() ) {
this.$emit('next-page', {formData: {firstname: this.firstname, lastname: this.lastname, age: this.age}, pageIndex: 0});
}
},
validateForm() {
if (!this.firstname.trim())
this.validationErrors['firstname'] = true;
else
delete this.validationErrors['firstname'];
if (!this.lastname.trim())
this.validationErrors['lastname'] = true;
else
delete this.validationErrors['lastname'];
return !Object.keys(this.validationErrors).length;
}
}
}
<\/script>
`
},
{
tabName: 'SeatDemo',
content: `
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title>
Seat Information
</template>
<template v-slot:subtitle>
Choose your seat
</template>
<template v-slot:content>
<div class="p-fluid formgrid grid">
<div class="field col-12 md:col-6">
<label for="class">Class</label>
<Dropdown inputId="class" v-model="selectedClass" :options="classes" @change="setVagons($event)" optionLabel="name" placeholder="Select a Class" />
</div>
<div class="field col-12 md:col-6">
<label for="lastname">Wagon</label>
<Dropdown inputId="wagon" v-model="selectedVagon" :options="vagons" @change="setSeats($event)" optionLabel="vagon" placeholder="Select a Vagon" />
</div>
<div class="field col-12">
<label for="seat">Seat</label>
<Dropdown inputId="seat" v-model="selectedSeat" :options="seats" optionLabel="seat" placeholder="Select a Seat" />
</div>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<Button label="Back" @click="prevPage()" icon="pi pi-angle-left" />
<Button label="Next" @click="nextPage()" icon="pi pi-angle-right" iconPos="right" />
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
data () {
return {
selectedClass: '',
classes: [
{name: 'First Class', code: 'A', factor: 1},
{name: 'Second Class', code: 'B', factor: 2},
{name: 'Third Class', code: 'C', factor: 3}
],
vagons: [],
selectedVagon: '',
seats: [],
selectedSeat: ''
}
},
methods: {
setVagons(event) {
if (this.selectedClass && event.value) {
this.vagons = [];
this.seats = [];
for (let i = 1; i < 3 * event.value.factor; i++) {
this.vagons.push({vagon: i + event.value.code, type: event.value.name, factor: event.value.factor});
}
}
},
setSeats(event) {
if (this.selectedVagon && event.value) {
this.seats = [];
for (let i = 1; i < 10 * event.value.factor; i++) {
this.seats.push({seat: i, type: event.value.type});
}
}
},
nextPage() {
this.$emit('next-page', {formData: {class: this.selectedClass.name, vagon: this.selectedVagon.vagon, seat: this.selectedSeat.seat}, pageIndex: 1});
},
prevPage() {
this.$emit('prev-page', {pageIndex: 1});
}
}
}
<\/script>
`
},
{
tabName: 'PaymentDemo',
content: `
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title>
Payment Information
</template>
<template v-slot:subtitle>
Enter your card details
</template>
<template v-slot:content>
<div class="p-fluid formgrid grid">
<div class="field col-12">
<label for="class">Card Holder Name</label>
<InputText type="text" v-model="cardholderName" />
</div>
<div class="field col-8">
<label id="number" for="lastname">Number</label>
<InputMask id="number" mask="9999-9999-9999-9999" v-model="cardholderNumber" />
</div>
<div class="field col-2">
<label id="date" for="date">Date</label>
<InputMask id="date" mask="99/99" v-model="date" />
</div>
<div class="field col-2">
<label for="cvv">CVV</label>
<InputMask id="cvv" mask="999" v-model="cvv" />
</div>
<div class="field-checkbox col-12">
<Checkbox id="remember" v-model="remember" :binary="true" />
<label for="remember" class="p-checkbox-label">Save credit card information for future</label>
</div>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<Button label="Back" @click="prevPage()" icon="pi pi-angle-left" />
<Button label="Next" @click="nextPage()" icon="pi pi-angle-right" iconPos="right" />
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
data () {
return {
cardholderName:'',
cardholderNumber:'',
date:'',
cvv:'',
remember:false
}
},
methods: {
nextPage() {
this.$emit('next-page', {formData: {cardholderName: this.cardholderName, cardholderNumber: this.cardholderNumber, date: this.date, cvv: this.cvv}, pageIndex: 2});
},
prevPage() {
this.$emit('prev-page', {pageIndex: 2});
}
}
}
<\/script>
`
},
{
tabName: 'ConfirmationDemo',
content: `
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title>
Confirmation
</template>
<template v-slot:content>
<div class="field col-12">
<label for="class">Name</label>
<b>{{formData.firstname ? formData.firstname : '-'}} {{formData.lastname ? formData.lastname : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Age</label>
<b>{{formData.age ? formData.age : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Seat Class</label>
<b>{{formData.class ? formData.class : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Wagon Number</label>
<b>{{formData.vagon ? formData.vagon : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Seat</label>
<b>{{formData.seat ? formData.seat : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Cardholder Name</label>
<b>{{formData.cardholderName ? formData.cardholderName : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Card Number</label>
<b>{{formData.cardholderNumber ? formData.cardholderNumber : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">Date</label>
<b>{{formData.date ? formData.date : '-'}}</b>
</div>
<div class="field col-12">
<label for="Age">CVV</label>
<b>{{formData.cvv && formData.cvv.length === 3 ? '**' + formData.cvv[2] : '-'}}</b>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<Button label="Back" @click="prevPage()" icon="pi pi-angle-left" />
<Button label="Complete" @click="complete()" icon="pi pi-check" iconPos="right" class="p-button-success"/>
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
props: {
formData: Object
},
methods: {
prevPage() {
this.$emit('prev-page', {pageIndex: 3});
},
complete() {
this.$emit('complete');
}
}
}
<\/script>
`
}
]
}
};
},
methods: {
nextPage(event) {
for (let field in event.formData) {
this.formObject[field] = event.formData[field];
}
this.$router.push(this.items[event.pageIndex + 1].route);
},
prevPage(event) {
this.$router.push(this.items[event.pageIndex - 1].route);
},
complete() {
this.$toast.add({ severity: 'success', summary: 'Order submitted', detail: 'Dear, ' + this.formObject.firstname + ' ' + this.formObject.lastname + ' your order completed.' });
},
isActive(item) {
return item.route ? this.$router.resolve(item.route).path === this.$route.path : false;
}
}
};
</script>

85
doc/steps/LinearDoc.vue Normal file
View File

@ -0,0 +1,85 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Steps is linear by default to enforce completion of a previus step to proceed, set <i>readonly</i> as false for non-linear mode.</p>
</DocSectionText>
<div class="card">
<Steps :model="items" :readonly="false" />
</div>
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
items: [
{
label: 'Personal Info'
},
{
label: 'Reservation'
},
{
label: 'Review'
}
],
code: {
basic: `
<Steps :model="items" :readonly="false" />
`,
options: `
<template>
<div class="card">
<Steps :model="items" />
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
label: 'Personal Info'
},
{
label: 'Reservation'
},
{
label: 'Review'
}
]
}
}
}
<\/script>
`,
composition: `
<template>
<div class="card">
<Steps :model="items" :readonly="false" />
</div>
</template>
<script setup>
import { ref } from "vue";
const items = ref([
{
label: 'Personal Info'
},
{
label: 'Reservation'
},
{
label: 'Review'
}
]);
<\/script>
`
}
};
}
};
</script>

View File

@ -1,218 +0,0 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Since v3.33.0 the vue-router dependency of menu components is deprecated and templating should be used to define router links instead. This approach provides flexibility to be able to use any kind of router link component such as
<i>NuxtLink</i> or <i>router-link</i>. Here is an example with vue-router.
</p>
</DocSectionText>
<!-- <div class="card">
<Steps
:model="items"
aria-label="Form Steps"
:readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}"
>
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</template>
</Steps>
</div> -->
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
// items: [
// {
// label: 'Personal',
// route: '/steps/'
// },
// {
// label: 'Seat',
// route: '/steps/seat'
// },
// {
// label: 'Payment',
// route: '/steps/payment'
// },
// {
// label: 'Confirmation',
// route: '/steps/confirmation'
// },
// {
// label: 'File Upload',
// url: '/fileupload'
// }
// ],
code: {
basic: `
<Steps :model="items" aria-label="Form Steps" :readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}">
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</template>
</Steps>
`,
options: `
<template>
<div>
<div class="card">
<Steps :model="items" aria-label="Form Steps" :readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}">
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</template>
</Steps>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
label: 'Personal',
route: '/'
},
{
label: 'Seat',
route: '/seat'
},
{
label: 'Payment',
route: '/payment'
},
{
label: 'Confirmation',
route: '/confirmation'
},
{
label: 'File Upload',
url: '/fileupload'
}
]
}
},
methods: {
isActive(item) {
return item.route ? this.$router.resolve(item.route).path === this.$route.path : false;
}
}
}
<\/script>
`,
composition: `
<template>
<div>
<div class="card">
<Steps :model="items" aria-label="Form Steps" :readonly="false"
:pt="{
menuitem: ({ context }) => ({
class: isActive(context.item) && 'p-highlight p-steps-current'
})
}">
<template #item="{ label, item, index, props }">
<router-link v-if="item.route" v-slot="routerProps" :to="item.route" custom>
<a :href="routerProps.href" v-bind="props.action" @click="($event) => routerProps.navigate($event)" @keydown.enter="($event) => routerProps.navigate($event)">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</router-link>
<a v-else :href="item.url" :target="item.target" v-bind="props.action">
<span v-bind="props.step">{{ index + 1 }}</span>
<span v-bind="props.label">{{ label }}</span>
</a>
</template>
</Steps>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const items = ref([
{
label: 'Personal',
route: "/"
},
{
label: 'Seat',
route: "/seat",
},
{
label: 'Payment',
route: "/payment",
},
{
label: 'Confirmation',
route: "/confirmation",
},
{
label: 'File Upload',
url: '/fileupload'
}
]);
const isActive = (item) => {
return item.route ? router.resolve(item.route).path === route.path : false;
}
<\/script>
`
}
};
},
methods: {
isActive(item) {
return item.route ? this.$router.resolve(item.route).path === this.$route.path : false;
}
}
};
</script>

118
doc/steps/TemplateDoc.vue Normal file
View File

@ -0,0 +1,118 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Steps offers item customization with the <i>item</i> template that receives the item instance from the model as a parameter.</p>
</DocSectionText>
<div class="card">
<Steps :model="items" class="custom-steps" :readonly="false">
<template #item="{ item, active }">
<span :class="['inline-flex align-items-center justify-content-center align-items-center border-circle border-primary border-1 h-3rem w-3rem z-1 cursor-pointer', { 'bg-primary': active, 'surface-overlay text-primary': !active }]">
<i :class="[item.icon, 'text-xl']" />
</span>
</template>
</Steps>
</div>
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
items: [
{
icon: 'pi pi-user'
},
{
icon: 'pi pi-calendar'
},
{
icon: 'pi pi-check'
}
],
code: {
basic: `
<Steps :model="items" class="custom-steps" :readonly="false">
<template #item="{ item, active }">
<span :class="['inline-flex align-items-center justify-content-center align-items-center border-circle border-primary border-1 h-3rem w-3rem z-1 cursor-pointer', { 'bg-primary': active, 'surface-overlay text-primary': !active }]">
<i :class="[item.icon, 'text-xl']" />
</span>
</template>
</Steps>
`,
options: `
<template>
<div class="card">
<Steps :model="items" class="custom-steps" :readonly="false">
<template #item="{ item, active }">
<span :class="['inline-flex align-items-center justify-content-center align-items-center border-circle border-primary border-1 h-3rem w-3rem z-1 cursor-pointer', { 'bg-primary': active, 'surface-overlay text-primary': !active }]">
<i :class="[item.icon, 'text-xl']" />
</span>
</template>
</Steps>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
icon: 'pi pi-user'
},
{
icon: 'pi pi-calendar'
},
{
icon: 'pi pi-check'
}
]
}
}
}
<\/script>
`,
composition: `
<template>
<div class="card">
<Steps :model="items" class="custom-steps" :readonly="false">
<template #item="{ item, active }">
<span :class="['inline-flex align-items-center justify-content-center align-items-center border-circle border-primary border-1 h-3rem w-3rem z-1 cursor-pointer', { 'bg-primary': active, 'surface-overlay text-primary': !active }]">
<i :class="[item.icon, 'text-xl']" />
</span>
</template>
</Steps>
</div>
</template>
<script setup>
import { ref } from "vue";
const items = ref([
{
icon: 'pi pi-user'
},
{
icon: 'pi pi-calendar'
},
{
icon: 'pi pi-check'
}
]);
<\/script>
`
}
};
}
};
</script>
<style lang="scss" scoped>
::v-deep(.custom-steps) {
.p-steps-item:before {
margin-top: 0;
border-color: var(--primary-color);
}
}
</style>

View File

@ -1,57 +0,0 @@
<template>
<DocComponent
title="Vue Stepper Component"
header="Steps"
description="Steps also known as Stepper, is an indicator for the steps in a workflow. Layout of steps component is optimized for responsive design."
:componentDocs="docs"
:apiDocs="['Steps', 'MenuItem']"
:ptTabComponent="ptComponent"
:themingDocs="themingDoc"
/>
</template>
<script>
import AccessibilityDoc from '@/doc/steps/AccessibilityDoc.vue';
import BasicDoc from '@/doc/steps/BasicDoc.vue';
import ImportDoc from '@/doc/steps/ImportDoc.vue';
import InteractiveDoc from '@/doc/steps/InteractiveDoc.vue';
import RouterDoc from '@/doc/steps/RouterDoc.vue';
import PTComponent from '@/doc/steps/pt/index.vue';
import ThemingDoc from '@/doc/steps/theming/index.vue';
export default {
data() {
return {
docs: [
{
id: 'import',
label: 'Import',
component: ImportDoc
},
{
id: 'basic',
label: 'Basic',
component: BasicDoc
},
{
id: 'interactive',
label: 'Interactive',
component: InteractiveDoc
},
{
id: 'router',
label: 'Router',
component: RouterDoc
},
{
id: 'accessibility',
label: 'Accessibility',
component: AccessibilityDoc
}
],
ptComponent: PTComponent,
themingDoc: ThemingDoc
};
}
};
</script>

View File

@ -1,68 +0,0 @@
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title> Confirmation </template>
<template v-slot:content>
<div class="field col-12">
<label for="class">Name</label>
<b>{{ formData.firstname ? formData.firstname : '-' }} {{ formData.lastname ? formData.lastname : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Age</label>
<b>{{ formData.age ? formData.age : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Seat Class</label>
<b>{{ formData.class ? formData.class : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Wagon Number</label>
<b>{{ formData.vagon ? formData.vagon : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Seat</label>
<b>{{ formData.seat ? formData.seat : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Cardholder Name</label>
<b>{{ formData.cardholderName ? formData.cardholderName : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Card Number</label>
<b>{{ formData.cardholderNumber ? formData.cardholderNumber : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">Date</label>
<b>{{ formData.date ? formData.date : '-' }}</b>
</div>
<div class="field col-12">
<label for="Age">CVV</label>
<b>{{ formData.cvv && formData.cvv.length === 3 ? '**' + formData.cvv[2] : '-' }}</b>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<Button label="Back" @click="prevPage()" icon="pi pi-angle-left" />
<Button label="Complete" @click="complete()" icon="pi pi-check" iconPos="right" severity="success" />
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
emits: ['prev-page', 'complete'],
props: {
formData: Object
},
methods: {
prevPage() {
this.$emit('prev-page', { pageIndex: 3 });
},
complete() {
this.$emit('complete');
}
}
};
</script>

View File

@ -1,61 +0,0 @@
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title> Payment Information </template>
<template v-slot:subtitle> Enter your card details </template>
<template v-slot:content>
<div class="p-fluid formgrid grid">
<div class="field col-12">
<label for="class">Card Holder Name</label>
<InputText v-model="cardholderName" type="text" />
</div>
<div class="field col-8">
<label id="number" for="lastname">Number</label>
<InputMask id="number" v-model="cardholderNumber" mask="9999-9999-9999-9999" />
</div>
<div class="field col-2">
<label id="date" for="date">Date</label>
<InputMask id="date" v-model="date" mask="99/99" />
</div>
<div class="field col-2">
<label for="cvv">CVV</label>
<InputMask id="cvv" v-model="cvv" mask="999" />
</div>
<div class="field-checkbox col-12">
<Checkbox id="remember" v-model="remember" :binary="true" />
<label for="remember" class="p-checkbox-label">Save credit card information for future</label>
</div>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<Button label="Back" @click="prevPage()" icon="pi pi-angle-left" />
<Button label="Next" @click="nextPage()" icon="pi pi-angle-right" iconPos="right" />
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
emits: ['next-page', 'prev-page'],
data() {
return {
cardholderName: '',
cardholderNumber: '',
date: '',
cvv: '',
remember: false
};
},
methods: {
nextPage() {
this.$emit('next-page', { formData: { cardholderName: this.cardholderName, cardholderNumber: this.cardholderNumber, date: this.date, cvv: this.cvv }, pageIndex: 2 });
},
prevPage() {
this.$emit('prev-page', { pageIndex: 2 });
}
}
};
</script>

View File

@ -1,77 +0,0 @@
<template>
<div class="stepsdemo-content">
<Card>
<template v-slot:title> Seat Information </template>
<template v-slot:subtitle> Choose your seat </template>
<template v-slot:content>
<div class="p-fluid formgrid grid">
<div class="field col-12 md:col-6">
<label for="class">Class</label>
<Dropdown v-model="selectedClass" inputId="class" :options="classes" @change="setWagons($event)" optionLabel="name" placeholder="Select a Class" />
</div>
<div class="field col-12 md:col-6">
<label for="lastname">Wagon</label>
<Dropdown v-model="selectedWagon" inputId="wagon" :options="wagons" @change="setSeats($event)" optionLabel="wagon" placeholder="Select a Wagon" />
</div>
<div class="field col-12">
<label for="seat">Seat</label>
<Dropdown v-model="selectedSeat" inputId="seat" :options="seats" optionLabel="seat" placeholder="Select a Seat" />
</div>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<Button label="Back" @click="prevPage()" icon="pi pi-angle-left" />
<Button label="Next" @click="nextPage()" icon="pi pi-angle-right" iconPos="right" />
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
emits: ['next-page', 'prev-page'],
data() {
return {
selectedClass: '',
classes: [
{ name: 'First Class', code: 'A', factor: 1 },
{ name: 'Second Class', code: 'B', factor: 2 },
{ name: 'Third Class', code: 'C', factor: 3 }
],
wagons: [],
selectedWagon: '',
seats: [],
selectedSeat: ''
};
},
methods: {
setWagons(event) {
if (this.selectedClass && event.value) {
this.wagons = [];
this.seats = [];
for (let i = 1; i < 3 * event.value.factor; i++) {
this.wagons.push({ wagon: i + event.value.code, type: event.value.name, factor: event.value.factor });
}
}
},
setSeats(event) {
if (this.selectedWagon && event.value) {
this.seats = [];
for (let i = 1; i < 10 * event.value.factor; i++) {
this.seats.push({ seat: i, type: event.value.type });
}
}
},
nextPage() {
this.$emit('next-page', { formData: { class: this.selectedClass.name, wagon: this.selectedWagon.wagon, seat: this.selectedSeat.seat }, pageIndex: 1 });
},
prevPage() {
this.$emit('prev-page', { pageIndex: 1 });
}
}
};
</script>

106
pages/steps/index.vue Executable file → Normal file
View File

@ -1,65 +1,63 @@
<template> <template>
<div class="stepsdemo-content"> <DocComponent
<Card> title="Vue Stepper Component"
<template v-slot:title> Personal Information </template> header="Steps"
<template v-slot:subtitle> Enter your personal information </template> description="Steps also known as Stepper, is an indicator for the steps in a workflow."
<template v-slot:content> :componentDocs="docs"
<div class="p-fluid"> :apiDocs="['Steps', 'MenuItem']"
<div class="field"> :ptTabComponent="ptComponent"
<label for="firstname">Firstname</label> :themingDocs="themingDoc"
<InputText id="firstname" v-model="firstname" :class="{ 'p-invalid': validationErrors.firstname && submitted }" /> />
<small v-show="validationErrors.firstname && submitted" class="p-error">Firstname is required.</small>
</div>
<div class="field">
<label for="lastname">Lastname</label>
<InputText id="lastname" v-model="lastname" :class="{ 'p-invalid': validationErrors.lastname && submitted }" />
<small v-show="validationErrors.lastname && submitted" class="p-error">Lastname is required.</small>
</div>
<div class="field">
<label for="age">Age</label>
<InputNumber id="age" v-model="age" />
</div>
</div>
</template>
<template v-slot:footer>
<div class="grid grid-nogutter justify-content-between">
<i></i>
<Button label="Next" @click="nextPage()" icon="pi pi-angle-right" iconPos="right" />
</div>
</template>
</Card>
</div>
</template> </template>
<script> <script>
import AccessibilityDoc from '@/doc/steps/AccessibilityDoc.vue';
import BasicDoc from '@/doc/steps/BasicDoc.vue';
import ControlledDoc from '@/doc/steps/ControlledDoc.vue';
import ImportDoc from '@/doc/steps/ImportDoc.vue';
import LinearDoc from '@/doc/steps/LinearDoc.vue';
import TemplateDoc from '@/doc/steps/TemplateDoc.vue';
import PTComponent from '@/doc/steps/pt/index.vue';
import ThemingDoc from '@/doc/steps/theming/index.vue';
export default { export default {
emits: ['next-page'],
data() { data() {
return { return {
firstname: '', docs: [
lastname: '', {
age: null, id: 'import',
submitted: false, label: 'Import',
validationErrors: {} component: ImportDoc
},
{
id: 'basic',
label: 'Basic',
component: BasicDoc
},
{
id: 'controlled',
label: 'Controlled',
component: ControlledDoc
},
{
id: 'linear',
label: 'Linear',
component: LinearDoc
},
{
id: 'template',
label: 'Template',
component: TemplateDoc
},
{
id: 'accessibility',
label: 'Accessibility',
component: AccessibilityDoc
}
],
ptComponent: PTComponent,
themingDoc: ThemingDoc
}; };
},
methods: {
nextPage() {
this.submitted = true;
if (this.validateForm()) {
this.$emit('next-page', { formData: { firstname: this.firstname, lastname: this.lastname, age: this.age }, pageIndex: 0 });
}
},
validateForm() {
if (!this.firstname.trim()) this.validationErrors['firstname'] = true;
else delete this.validationErrors['firstname'];
if (!this.lastname.trim()) this.validationErrors['lastname'] = true;
else delete this.validationErrors['lastname'];
return !Object.keys(this.validationErrors).length;
}
} }
}; };
</script> </script>

View File

@ -5729,4 +5729,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #60a5fa;
color: #030712;
}
} }

View File

@ -5729,4 +5729,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #818cf8;
color: #030712;
}
} }

View File

@ -5729,4 +5729,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #a78bfa;
color: #030712;
}
} }

View File

@ -5729,4 +5729,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #34d399;
color: #030712;
}
} }

View File

@ -5721,4 +5721,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #3B82F6;
color: #ffffff;
}
} }

View File

@ -5721,4 +5721,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #6366F1;
color: #ffffff;
}
} }

View File

@ -5721,4 +5721,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #8B5CF6;
color: #ffffff;
}
} }

View File

@ -5721,4 +5721,9 @@
.p-orderlist-controls .p-button { .p-orderlist-controls .p-button {
transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; transition: opacity 0.2s, background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
} }
.p-steps .p-steps-item.p-highlight .p-steps-number {
background: #10b981;
color: #ffffff;
}
} }