组件
- 手风琴 (Accordion)
- 警告 (Alert)
- 警告对话框 (Alert Dialog)
- 宽高比 (Aspect Ratio)
- 头像 (Avatar)
- 徽章 (Badge)
- 面包屑 (Breadcrumb)
- 按钮 (Button)
- 按钮组 (Button Group)
- 日历 (Calendar)
- 卡片 (Card)
- 轮播图 (Carousel)
- 图表 (Chart)
- 复选框 (Checkbox)
- 折叠面板 (Collapsible)
- 组合框 (Combobox)
- 命令面板 (Command)
- 上下文菜单 (Context Menu)
- 数据表格 (Data Table)
- 日期选择器 (Date Picker)
- 对话框 (Dialog)
- 抽屉 (Drawer)
- 下拉菜单 (Dropdown Menu)
- 空状态 (Empty)
- 字段 (Field)
- 表单 (Form)
- 悬停卡片 (Hover Card)
- 输入框 (Input)
- 输入组 (Input Group)
- 验证码输入 (Input OTP)
- 项 (Item)
- 键盘按键 (Kbd)
- 标签 (Label)
- 菜单栏 (Menubar)
- 原生选择器 (Native Select)
- 导航菜单 (Navigation Menu)
- 数字输入框 (Number Field)
- 分页 (Pagination)
- 引脚输入 (Pin Input)
- 气泡卡片 (Popover)
- 进度条 (Progress)
- 单选框组 (Radio Group)
- 范围日历 (Range Calendar)
- 可调整大小 (Resizable)
- 滚动区域 (Scroll Area)
- 选择器 (Select)
- 分隔线 (Separator)
- 侧边栏抽屉 (Sheet)
- 侧边栏 (Sidebar)
- 骨架屏 (Skeleton)
- 滑块 (Slider)
- 轻量提示 (Sonner)
- 加载动画 (Spinner)
- 步骤条 (Stepper)
- 开关 (Switch)
- 表格 (Table)
- 标签页 (Tabs)
- 标签输入 (Tags Input)
- 文本域 (Textarea)
- 吐司提示 (Toast)
- 切换按钮 (Toggle)
- 切换按钮组 (Toggle Group)
- 工具提示 (Tooltip)
- 排版 (Typography)
开始使用
地址
添加您的地址
物流
设置您的首选物流
支付
添加任何支付方式
结账
确认您的订单
步骤 1 / 0
<script setup lang="ts">
import { BookUser, CreditCard, Truck } from 'lucide-vue-next'
import { Stepper, StepperDescription, StepperIndicator, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const steps = [
{
step: 1,
title: 'Address',
description: 'Add your address',
icon: BookUser,
},
{
step: 2,
title: 'Shipping',
description: 'Set your preferred',
icon: Truck,
},
{
step: 3,
title: 'Payment',
description: 'Add any payment',
icon: CreditCard,
},
{
step: 4,
title: 'Checkout',
description: 'Confirm your order',
},
]
</script>
<template>
<Stepper class="flex w-10/12 items-start gap-2">
<StepperItem
v-for="item in steps"
:key="item.step"
:step="item.step"
class="relative flex w-full flex-col items-center justify-center"
>
<StepperTrigger>
<StepperIndicator v-slot="{ step }" class="bg-muted">
<template v-if="item.icon">
<component :is="item.icon" class="w-4 h-4" />
</template>
<span v-else>{{ step }}</span>
</StepperIndicator>
</StepperTrigger>
<StepperSeparator
v-if="item.step !== steps[steps.length - 1]?.step"
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<div class="flex flex-col items-center">
<StepperTitle>
{{ item.title }}
</StepperTitle>
<StepperDescription>
{{ item.description }}
</StepperDescription>
</div>
</StepperItem>
</Stepper>
</template>安装
pnpm dlx shadcn-vue@latest add stepper
使用方法
<script setup lang="ts">
import {
Stepper,
StepperDescription,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from '@/components/ui/stepper'
</script>
<template>
<Stepper>
<StepperItem :step="1">
<StepperTrigger>
<StepperIndicator>1</StepperIndicator>
<StepperTitle>Step 1</StepperTitle>
<StepperDescription>This is the first step</StepperDescription>
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem :step="2">
<StepperTrigger>
<StepperIndicator>2</StepperIndicator>
<StepperTitle>Step 2</StepperTitle>
<StepperDescription>This is the second step</StepperDescription>
</StepperTrigger>
</StepperItem>
</Stepper>
</template>示例
水平布局
个人信息
提供您的姓名和邮箱
公司信息
关于您公司的一些细节
邀请团队
开始与您的团队协作
步骤 1 / 0
<script setup lang="ts">
import { Check, Circle, Dot } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const steps = [
{
step: 1,
title: 'Your details',
description: 'Provide your name and email',
},
{
step: 2,
title: 'Company details',
description: 'A few details about your company',
},
{
step: 3,
title: 'Invite your team',
description: 'Start collaborating with your team',
},
]
</script>
<template>
<Stepper class="flex w-full items-start gap-2">
<StepperItem
v-for="step in steps"
:key="step.step"
v-slot="{ state }"
class="relative flex w-full flex-col items-center justify-center"
:step="step.step"
>
<StepperSeparator
v-if="step.step !== steps[steps.length - 1]?.step"
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<StepperTrigger as-child>
<Button
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
size="icon"
class="z-10 rounded-full shrink-0"
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
>
<Check v-if="state === 'completed'" class="size-5" />
<Circle v-if="state === 'active'" />
<Dot v-if="state === 'inactive'" />
</Button>
</StepperTrigger>
<div class="mt-5 flex flex-col items-center text-center">
<StepperTitle
:class="[state === 'active' && 'text-primary']"
class="text-sm font-semibold transition lg:text-base"
>
{{ step.title }}
</StepperTitle>
<StepperDescription
:class="[state === 'active' && 'text-primary']"
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
>
{{ step.description }}
</StepperDescription>
</div>
</StepperItem>
</Stepper>
</template>垂直布局
个人信息
提供您的姓名和电子邮箱地址。我们将使用此信息来创建您的账户。
公司信息
提供一些关于您公司的详细信息,有助于我们为您提供个性化体验。
邀请团队
邀请团队成员加入您的账户,开始协作。您可以跳过此步骤,稍后再邀请他们。
步骤 1 / 0
<script setup lang="ts">
import { Check, Circle, Dot } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const steps = [
{
step: 1,
title: 'Your details',
description:
'Provide your name and email address. We will use this information to create your account',
},
{
step: 2,
title: 'Company details',
description: 'A few details about your company will help us personalize your experience',
},
{
step: 3,
title: 'Invite your team',
description:
'Start collaborating with your team by inviting them to join your account. You can skip this step and invite them later',
},
]
</script>
<template>
<Stepper orientation="vertical" class="mx-auto flex w-full max-w-md flex-col justify-start gap-10">
<StepperItem
v-for="step in steps"
:key="step.step"
v-slot="{ state }"
class="relative flex w-full items-start gap-6"
:step="step.step"
>
<StepperSeparator
v-if="step.step !== steps[steps.length - 1]?.step"
class="absolute left-[18px] top-[38px] block h-[105%] w-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<StepperTrigger as-child>
<Button
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
size="icon"
class="z-10 rounded-full shrink-0"
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
>
<Check v-if="state === 'completed'" class="size-5" />
<Circle v-if="state === 'active'" />
<Dot v-if="state === 'inactive'" />
</Button>
</StepperTrigger>
<div class="flex flex-col gap-1">
<StepperTitle
:class="[state === 'active' && 'text-primary']"
class="text-sm font-semibold transition lg:text-base"
>
{{ step.title }}
</StepperTitle>
<StepperDescription
:class="[state === 'active' && 'text-primary']"
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
>
{{ step.description }}
</StepperDescription>
</div>
</StepperItem>
</Stepper>
</template>表单
步骤 1 / 0
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import { Check, Circle, Dot } from 'lucide-vue-next'
import { h, ref } from 'vue'
import { toast } from 'vue-sonner'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Stepper, StepperDescription, StepperItem, StepperSeparator, StepperTitle, StepperTrigger } from '@/components/ui/stepper'
const formSchema = [
z.object({
fullName: z.string(),
email: z.string().email(),
}),
z.object({
password: z.string().min(2).max(50),
confirmPassword: z.string(),
}).refine(
(values) => {
return values.password === values.confirmPassword
},
{
message: 'Passwords must match!',
path: ['confirmPassword'],
},
),
z.object({
favoriteDrink: z.union([z.literal('coffee'), z.literal('tea'), z.literal('soda')]),
}),
]
const stepIndex = ref(1)
const steps = [
{
step: 1,
title: 'Your details',
description: 'Provide your name and email',
},
{
step: 2,
title: 'Your password',
description: 'Choose a password',
},
{
step: 3,
title: 'Your Favorite Drink',
description: 'Choose a drink',
},
]
function onSubmit(values: any) {
toast('You submitted the following values:', {
description: h('pre', { class: 'mt-2 w-[320px] rounded-md bg-neutral-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<Form
v-slot="{ meta, values, validate }"
as="" keep-values :validation-schema="toTypedSchema(formSchema[stepIndex - 1]!)"
>
<Stepper v-slot="{ isNextDisabled, isPrevDisabled, nextStep, prevStep, modelValue }" v-model="stepIndex" class="block w-full">
<form
@submit="(e) => {
e.preventDefault()
validate()
if (stepIndex === steps.length && meta.valid) {
onSubmit(values)
}
}"
>
<div class="flex w-full flex-start gap-2">
<StepperItem
v-for="(step, index) in steps"
:key="step.step"
v-slot="{ state }"
class="relative flex w-full flex-col items-center justify-center"
:step="step.step"
>
<StepperSeparator
v-if="step.step !== steps[steps.length - 1]!.step"
class="absolute left-[calc(50%+20px)] right-[calc(-50%+10px)] top-5 block h-0.5 shrink-0 rounded-full bg-muted group-data-[state=completed]:bg-primary"
/>
<StepperTrigger as-child>
<Button
:variant="state === 'completed' || state === 'active' ? 'default' : 'outline'"
size="icon"
class="z-10 rounded-full shrink-0"
:class="[state === 'active' && 'ring-2 ring-ring ring-offset-2 ring-offset-background']"
:disabled="state !== 'completed' && (index >= (modelValue || 0) && !meta.valid)"
>
<Check v-if="state === 'completed'" class="size-5" />
<Circle v-if="state === 'active'" />
<Dot v-if="state === 'inactive'" />
</Button>
</StepperTrigger>
<div class="mt-5 flex flex-col items-center text-center">
<StepperTitle
:class="[state === 'active' && 'text-primary']"
class="text-sm font-semibold transition lg:text-base"
>
{{ step.title }}
</StepperTitle>
<StepperDescription
:class="[state === 'active' && 'text-primary']"
class="sr-only text-xs text-muted-foreground transition md:not-sr-only lg:text-sm"
>
{{ step.description }}
</StepperDescription>
</div>
</StepperItem>
</div>
<div class="flex flex-col gap-4 mt-4">
<template v-if="stepIndex === 1">
<FormField v-slot="{ componentField }" name="fullName">
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input type="text" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email " v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</template>
<template v-if="stepIndex === 2">
<FormField v-slot="{ componentField }" name="password">
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="confirmPassword">
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<Input type="password" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</template>
<template v-if="stepIndex === 3">
<FormField v-slot="{ componentField }" name="favoriteDrink">
<FormItem>
<FormLabel>Drink</FormLabel>
<Select v-bind="componentField">
<FormControl>
<SelectTrigger class="w-full!">
<SelectValue placeholder="Select a drink" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectGroup>
<SelectItem value="coffee">
Coffee
</SelectItem>
<SelectItem value="tea">
Tea
</SelectItem>
<SelectItem value="soda">
Soda
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
</FormField>
</template>
</div>
<div class="flex items-center justify-between mt-4">
<Button :disabled="isPrevDisabled" variant="outline" size="sm" @click="prevStep()">
Back
</Button>
<div class="flex items-center gap-3">
<Button v-if="stepIndex !== 3" :type="meta.valid ? 'button' : 'submit'" :disabled="isNextDisabled" size="sm" @click="meta.valid && nextStep()">
Next
</Button>
<Button
v-if="stepIndex === 3" size="sm" type="submit"
>
Submit
</Button>
</div>
</div>
</form>
</Stepper>
</Form>
</template>