feat(stage-ui): steppers component

This commit is contained in:
Neko Ayaka
2025-04-22 23:25:21 +08:00
parent 0b78c1440f
commit 8485a49d5f
6 changed files with 738 additions and 0 deletions
@@ -0,0 +1,53 @@
<script setup lang="ts">
import { ref } from 'vue'
import Steppers from './Steppers.vue' // Import the new component
interface Step {
id: number
title: string
description?: string
}
const setupWorkflowSteps: Step[] = [
{ id: 1, title: 'Welcome to StageWeb!', description: 'Let\'s get you set up. First, we need to configure at least one AI provider. Click "Next" to go to Provider Settings.' },
{ id: 2, title: 'Configure Provider', description: 'You are now in Provider Settings. Click on a provider (e.g., OpenAI, Ollama) to add your credentials (like API Key or Base URL).' },
{ id: 3, title: 'Set Up Consciousness', description: 'Great! Now head over to the "Consciousness" module in the settings.' },
{ id: 4, title: 'Select Consciousness Provider', description: 'In the Consciousness module, select the provider you just configured from the list.' },
{ id: 5, title: 'Select Consciousness Model', description: 'Now, choose a specific model from the list that this provider offers.' },
{ id: 6, title: 'Configure Text-to-Speech (TTS)?', description: 'Do you want to enable speech output? If yes, we need to configure a Speech Provider next. If not, you can skip to the end.' },
{ id: 7, title: 'Configure Speech Provider', description: '(Optional) Go back to Provider Settings and configure a provider that supports Speech (like ElevenLabs or Microsoft Speech).' },
{ id: 8, title: 'Set Up Speech Module', description: '(Optional) Navigate to the "Speech" module in settings.' },
{ id: 9, title: 'Select Speech Provider & Voice', description: '(Optional) Select your configured Speech provider and choose a voice you like.' },
{ id: 10, title: 'Setup Complete!', description: 'Excellent! Your core setup is complete. You can now return to the main page and start interacting.' },
]
const currentStepIndex = ref(0)
function handleFinish() {
currentStepIndex.value = 0
}
</script>
<template>
<Story title="Steppers" group="misc" :layout="{ type: 'grid', width: '100%' }">
<template #controls>
<ThemeColorsHueControl />
</template>
<Variant title="Default Stepper">
<Steppers
v-model="currentStepIndex"
:steps="setupWorkflowSteps"
step-key="id"
@finish="handleFinish"
>
<template #header>
<p class="text-xs text-neutral-500/50 dark:text-neutral-400">
First time? Follow this guide ({{ currentStepIndex + 1 }} / {{ setupWorkflowSteps.length }})
</p>
</template>
</Steppers>
</Variant>
</Story>
</template>
@@ -0,0 +1,146 @@
<script setup lang="ts" generic="T extends Record<string, any> | any">
import { computed, useSlots } from 'vue'
const props = withDefaults(defineProps<{
steps: T[]
stepKey?: keyof T | string
}>(), {
stepKey: 'id',
})
const emit = defineEmits<{
(e: 'finish'): void
}>()
const value = defineModel<number>({ required: false, default: 0 })
const slots = useSlots()
const isFirstStep = computed(() => value.value === 0)
const isLastStep = computed(() => value.value === props.steps.length - 1)
function getKey(step: T, index: number): string | number {
if (typeof step === 'object' && step !== null && props.stepKey in step) {
return (step as any)[props.stepKey]
}
return index // Fallback to index
}
function back() {
if (!isFirstStep.value) {
value.value--
}
}
function next() {
if (isLastStep.value) {
emit('finish')
}
else {
value.value++
}
}
function goToStep(index: number) {
value.value = index
}
</script>
<template>
<div class="w-full font-sans">
<div
class="flex flex-col gap-1 overflow-hidden rounded-xl px-4 py-3"
bg="white/70 dark:neutral-900/70"
border="neutral-100 dark:neutral-800/25 hover:primary-500/30 dark:hover:primary-400/30 solid 2"
drop-shadow="none hover:[0px_4px_4px_rgba(220,220,220,0.4)] active:[0px_0px_0px_rgba(220,220,220,0.25)] dark:hover:none"
transition="all ease-in-out duration-400"
>
<!-- Header Slot -->
<div v-if="slots.header">
<slot name="header" />
</div>
<div v-else>
<p class="text-xs text-neutral-500/50 dark:text-neutral-400">
Step {{ value + 1 }} of {{ steps.length }}
</p>
</div>
<!-- Current Step Content -->
<!-- Need relative parent for absolute positioning of steps -->
<template v-for="(step, index) in steps" :key="getKey(step, index)">
<div
v-if="index === value"
v-motion
:initial="{ opacity: 0, filter: 'blur(1px)', x: 10, transition: { type: 'keyframes', duration: 500, ease: 'easeInOut' } }"
:enter="{ opacity: 1, filter: 'blur(0px)', x: 0, transition: { type: 'keyframes', duration: 500, ease: 'easeInOut' } }"
:leave="{ opacity: 0, filter: 'blur(1px)', x: -10, transition: { type: 'keyframes', duration: 500, ease: 'easeInOut' } }"
>
<slot name="step" :step="step" :index="index" :is-active="index === value">
<!-- Default step rendering -->
<div class="flex flex-col gap-1">
<p v-if="typeof step === 'object' && step !== null && 'title' in step" class="text-primary-600 dark:text-primary-300 mb-4 pb-0 pt-0 text-xl font-bold">
{{ step.title }}
</p>
<p v-if="typeof step === 'object' && step !== null && 'description' in step" class="text-sm text-neutral-600 dark:text-neutral-300">
{{ step.description }}
</p>
<!-- Fallback if step is not an object with title/description -->
<p v-else class="text-sm text-neutral-600 dark:text-neutral-300">
{{ step }}
</p>
</div>
</slot>
</div>
</template>
<!-- Navigation Footer -->
<div class="flex items-center justify-between">
<!-- Progress Dots -->
<div class="flex flex-wrap items-center gap-1.5">
<template v-for="(step, index) in steps" :key="`dot-${getKey(step, index)}`">
<slot
name="step-dot"
:step="step"
:index="index"
:is-active="index === value"
:go-to-step="() => goToStep(index)"
>
<!-- Default dot rendering -->
<div
class="size-2 cursor-pointer rounded-full transition-all duration-200"
:class="[
index === value ? 'bg-primary-500 scale-125' : 'bg-neutral-300 dark:bg-neutral-600 hover:bg-neutral-400',
index < value ? 'bg-primary-300 dark:bg-primary-700 opacity-70' : '',
]"
:title="`Step ${index + 1}${typeof step === 'object' && step !== null && 'title' in step ? `: ${step.title}` : ''}`"
@click="goToStep(index)"
/>
</slot>
</template>
</div>
<!-- Back/Next Buttons -->
<div class="flex gap-2">
<slot name="back-button" :back="back" :is-disabled="isFirstStep">
<button
:disabled="isFirstStep"
class="rounded bg-neutral-200 px-3 py-1 text-xs text-neutral-700 disabled:cursor-not-allowed dark:bg-neutral-700 hover:bg-neutral-300 dark:text-neutral-200 disabled:opacity-50 dark:hover:bg-neutral-600"
@click="back"
>
Back
</button>
</slot>
<slot name="next-button" :next="next" :is-disabled="false" :is-last="isLastStep">
<!-- Default next/finish button -->
<button
class="bg-primary-500 hover:bg-primary-600 rounded px-3 py-1 text-xs text-white disabled:cursor-not-allowed disabled:opacity-50"
@click="next"
>
{{ isLastStep ? 'Finish' : 'Next' }}
</button>
</slot>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1 @@
export { default as Steppers } from './Steppers.vue'
@@ -0,0 +1,536 @@
<script setup lang="ts">
import type { Ref } from 'vue'
// import Steps from './Steps.vue'
import { ref } from 'vue'
interface Step {
id: number
title: string
description?: string
}
const stepsData: Step[] = [
{ id: 1, title: 'アカウント作成', description: 'ユーザー情報を入力します。' }, // Account Creation - Enter user information.
{ id: 2, title: 'プロフィール設定', description: '詳細なプロフィール情報を設定します。これは非常に長い説明文になる可能性があります。' }, // Profile Settings - Set detailed profile information. This could be a very long description.
{ id: 3, title: '確認', description: '入力内容を確認します。' }, // Confirmation - Confirm your input.
{ id: 4, title: '完了', description: 'プロセスが完了しました。' }, // Completion - The process is complete.
]
function createStepperState(initialStep: number = 1): { currentStep: Ref<number>, totalSteps: number, steps: Step[] } {
return {
currentStep: ref(initialStep),
totalSteps: stepsData.length,
steps: stepsData,
}
}
const stepper3 = createStepperState()
const stepper8 = createStepperState()
const stepper16 = createStepperState()
function updateStep(state: { currentStep: Ref<number> }, stepId: number) {
state.currentStep.value = stepId
}
function isStepActive(state: { currentStep: Ref<number> }, stepId: number): boolean {
return state.currentStep.value === stepId
}
function isStepCompleted(state: { currentStep: Ref<number> }, stepId: number): boolean {
return state.currentStep.value > stepId
}
const setupWorkflowSteps: Step[] = [
{ id: 1, title: 'Welcome to StageWeb!', description: 'Let\'s get you set up. First, we need to configure at least one AI provider. Click "Next" to go to Provider Settings.' },
{ id: 2, title: 'Configure Provider', description: 'You are now in Provider Settings. Click on a provider (e.g., OpenAI, Ollama) to add your credentials (like API Key or Base URL).' },
{ id: 3, title: 'Set Up Consciousness', description: 'Great! Now head over to the "Consciousness" module in the settings.' },
{ id: 4, title: 'Select Consciousness Provider', description: 'In the Consciousness module, select the provider you just configured from the list.' },
{ id: 5, title: 'Select Consciousness Model', description: 'Now, choose a specific model from the list that this provider offers.' },
{ id: 6, title: 'Configure Text-to-Speech (TTS)?', description: 'Do you want to enable speech output? If yes, we need to configure a Speech Provider next. If not, you can skip to the end.' },
{ id: 7, title: 'Configure Speech Provider', description: '(Optional) Go back to Provider Settings and configure a provider that supports Speech (like ElevenLabs or Microsoft Speech).' },
{ id: 8, title: 'Set Up Speech Module', description: '(Optional) Navigate to the "Speech" module in settings.' },
{ id: 9, title: 'Select Speech Provider & Voice', description: '(Optional) Select your configured Speech provider and choose a voice you like.' },
{ id: 10, title: 'Setup Complete!', description: 'Excellent! Your core setup is complete. You can now return to the main page and start interacting.' },
]
// Separate state for this specific tutorial variant
const setupGuide = {
currentStep: ref(1),
totalSteps: setupWorkflowSteps.length,
steps: setupWorkflowSteps,
skippedTTS: ref(false), // Track if TTS setup was skipped
}
function updateSetupGuideStep(stepId: number) {
setupGuide.currentStep.value = stepId
// Logic to skip TTS steps if user chooses
if (stepId === 6 && setupGuide.skippedTTS.value) {
setupGuide.currentStep.value = 10 // Skip directly to the end
}
}
function nextSetupGuideStep() {
const current = setupGuide.currentStep.value
if (current === 6 && setupGuide.skippedTTS.value) {
updateSetupGuideStep(10) // Skip TTS
}
else if (current < setupGuide.totalSteps) {
updateSetupGuideStep(current + 1)
}
}
function prevSetupGuideStep() {
const current = setupGuide.currentStep.value
if (current === 10 && setupGuide.skippedTTS.value) {
updateSetupGuideStep(6) // Go back to the skip question
}
else if (current > 1) {
updateSetupGuideStep(current - 1)
}
}
function skipTTSSetup() {
setupGuide.skippedTTS.value = true
updateSetupGuideStep(10) // Go directly to the final step
}
</script>
<template>
<Story title="Steps" group="misc" :layout="{ type: 'grid', width: '100%' }">
<template #controls>
<ThemeColorsHueControl />
</template>
<!-- Variant 3: Vertical Timeline Icons -->
<Variant title="3. Vertical Timeline Icons">
<div class="w-full flex p-4">
<div class="relative flex flex-col gap-8">
<!-- Connecting line -->
<div class="absolute bottom-4 left-[14px] top-4 w-0.5 bg-neutral-300 -z-1 dark:bg-neutral-700" />
<template v-for="step in stepper3.steps" :key="step.id">
<div class="flex cursor-pointer items-start gap-4" @click="updateStep(stepper3, step.id)">
<div
class="size-8 flex items-center justify-center border-2 rounded-full transition-all duration-300 ease-in-out"
:class="[
isStepActive(stepper3, step.id) ? 'bg-primary-100 dark:bg-primary-900 border-primary-500 scale-110' : 'border-transparent',
isStepCompleted(stepper3, step.id) ? 'bg-primary-500 dark:bg-primary-600 border-primary-500' : '',
!isStepActive(stepper3, step.id) && !isStepCompleted(stepper3, step.id) ? 'bg-neutral-200 dark:bg-neutral-800 border-neutral-300 dark:border-neutral-700' : '',
]"
>
<!-- Placeholder for Icon -->
<div
v-if="isStepCompleted(stepper3, step.id)" i-solar:check-read-bold-duotone
class="text-white dark:text-black"
/>
<span
v-else class="text-sm font-semibold"
:class="[isStepActive(stepper3, step.id) ? 'text-primary-700 dark:text-primary-200' : 'text-neutral-600 dark:text-neutral-300']"
>
{{ step.id }}
</span>
</div>
<div class="mt-1">
<p
class="font-medium"
:class="[isStepActive(stepper3, step.id) ? 'text-primary-700 dark:text-primary-200' : 'text-neutral-800 dark:text-neutral-200']"
>
{{ step.title }}
</p>
<p v-if="isStepActive(stepper3, step.id)" class="mt-0.5 text-xs text-neutral-500 dark:text-neutral-400">
{{ step.description }}
</p>
</div>
</div>
</template>
</div>
</div>
</Variant>
<!-- Variant 8: Accordion Steps -->
<Variant title="8. Accordion Steps">
<div class="w-full flex flex-col gap-2 p-4">
<template v-for="step in stepper8.steps" :key="step.id">
<div
class="overflow-hidden border rounded-lg transition-all duration-300 ease-in-out" :class="[
isStepActive(stepper8, step.id) ? 'border-primary-300 dark:border-primary-700 shadow-md' : 'border-neutral-200 dark:border-neutral-800',
isStepCompleted(stepper8, step.id) ? 'bg-neutral-50 dark:bg-neutral-900/30' : 'bg-white dark:bg-neutral-900/20',
]"
>
<div
class="flex cursor-pointer items-center justify-between p-3"
:class="[isStepCompleted(stepper8, step.id) ? 'opacity-70' : '']"
@click="updateStep(stepper8, step.id)"
>
<div class="flex items-center gap-3">
<div
class="size-5 flex items-center justify-center border rounded-full text-xs font-medium transition-all duration-300 ease-in-out"
:class="[
isStepActive(stepper8, step.id) ? 'bg-primary-100 dark:bg-primary-900 border-primary-500 text-primary-700 dark:text-primary-200' : '',
isStepCompleted(stepper8, step.id) ? 'bg-primary-500 dark:bg-primary-600 border-primary-500 text-white dark:text-black' : '',
!isStepActive(stepper8, step.id) && !isStepCompleted(stepper8, step.id) ? 'bg-neutral-200 dark:bg-neutral-800 border-neutral-300 dark:border-neutral-700 text-neutral-500 dark:text-neutral-400' : '',
]"
>
<div v-if="isStepCompleted(stepper8, step.id)" i-solar:check-read-bold-duotone text-xs />
<span v-else>{{ step.id }}</span>
</div>
<span
class="text-sm font-medium"
:class="[isStepActive(stepper8, step.id) ? 'text-primary-700 dark:text-primary-200' : 'text-neutral-700 dark:text-neutral-300']"
>
{{ step.title }}
</span>
</div>
<div
v-if="!isStepActive(stepper8, step.id)"
class="i-solar:alt-arrow-down-bold-duotone text-sm text-neutral-400 transition-transform duration-300 dark:text-neutral-600"
:class="{ 'rotate-180': isStepActive(stepper8, step.id) }"
/>
<button
v-else-if="stepper8.currentStep.value < stepper8.totalSteps"
class="bg-primary-500 hover:bg-primary-600 rounded px-2 py-0.5 text-xs text-white"
@click.stop="updateStep(stepper8, step.id + 1)"
>
Next
</button>
</div>
<!-- Expanded Content -->
<Transition name="expand">
<div
v-if="isStepActive(stepper8, step.id)"
class="border-t border-neutral-200 px-3 pb-3 pt-1 dark:border-neutral-800"
>
<p class="text-sm text-neutral-600 dark:text-neutral-400">
{{ step.description || 'No description available for this step.' }}
</p>
<!-- Placeholder for step content -->
<div class="mt-4 rounded bg-neutral-100 p-4 text-center text-sm text-neutral-500 dark:bg-neutral-800">
Step {{ step.id }} Content Area
</div>
</div>
</Transition>
</div>
</template>
</div>
</Variant>
<!-- Variant 16: Stacked Cards -->
<Variant title="16. Stacked Cards">
<div class="h-48 w-full flex perspective-1000 items-center justify-center p-6">
<div class="relative h-28 w-48">
<!-- Calculate relative position and apply dynamic styles for stacking -->
<template v-for="step in stepper16.steps" :key="step.id">
<div
:style="(() => {
const totalSteps = stepper16.totalSteps;
const currentStepValue = stepper16.currentStep.value;
// Calculate how many steps 'behind' the active card this one is (0 = active)
const relativePosition = (step.id - currentStepValue + totalSteps) % totalSteps;
const isActive = relativePosition === 0;
// Define style properties based on relative position
const zIndex = totalSteps - relativePosition;
const scale = 1 - relativePosition * 0.04; // Decrease scale slightly for cards further back
const translateX = relativePosition * 12; // Offset X based on position
const translateY = relativePosition * 6; // Offset Y less than X
const rotateZ = relativePosition * 2; // Rotate slightly based on position
const opacity = 1 - relativePosition * 0.1; // Decrease opacity for cards further back
const filter = isActive ? 'none' : `blur(${relativePosition * 0.5}px)`; // Blur background cards
return {
zIndex,
transform: `translateX(${translateX}px) translateY(${translateY}px) rotateZ(${rotateZ}deg) scale(${scale})`,
opacity,
filter,
};
})()"
class="absolute h-full w-full flex flex-col preserve-3d cursor-pointer justify-between border border-2 rounded-lg p-3 shadow-lg transition-all duration-500 ease-out"
:class="[
// Simplified classes: Active vs Inactive styling
(stepper16.currentStep.value === step.id)
? 'bg-primary-50 dark:bg-primary-900/50 border-primary-300 dark:border-primary-700'
: 'bg-neutral-50 dark:bg-neutral-900 border-neutral-200 dark:border-neutral-800',
]"
@click="() => {
if (stepper16.currentStep.value === step.id) {
// Clicked the active card: cycle it to the back by advancing the step
const nextStepId = (stepper16.currentStep.value % stepper16.totalSteps) + 1;
updateStep(stepper16, nextStepId);
}
else {
// Clicked an inactive card: bring it to the front
updateStep(stepper16, step.id);
}
}"
>
<div class="flex items-center justify-between">
<span
class="text-sm font-medium"
:class="(stepper16.currentStep.value === step.id) ? 'text-primary-700 dark:text-primary-200' : 'text-neutral-600 dark:text-neutral-300'"
>{{
step.title }}</span>
<span
class="rounded px-1.5 py-0.5 text-xs"
:class="(stepper16.currentStep.value === step.id) ? 'bg-primary-100 dark:bg-primary-800 text-primary-700 dark:text-primary-200' : 'bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-300'"
>
{{ step.id }} / {{ stepper16.totalSteps }}
</span>
</div>
<!-- Show description only for the active card -->
<p
v-if="stepper16.currentStep.value === step.id"
class="line-clamp-2 text-xs text-neutral-500 dark:text-neutral-400"
>
{{ step.description }}
</p>
<!-- Placeholder for inactive cards to maintain height -->
<div v-else class="h-6" />
</div>
</template>
</div>
</div>
</Variant>
<!-- Tutorial Variant E: Multi-Page Workflow Guide -->
<Variant title="E. Tutorial: Setup Workflow">
<div class="w-full p-4">
<div class="dark:from-neutral-850 flex flex-col overflow-hidden rounded-xl from-white to-neutral-100 bg-gradient-to-b shadow-md dark:to-neutral-900">
<!-- Header -->
<div class="bg-white/70 p-4 backdrop-blur-sm dark:bg-neutral-950/60">
<h4 class="text-base text-neutral-800 font-semibold dark:text-neutral-100">
StageWeb Setup Guide
</h4>
</div>
<!-- Current Step Content -->
<div class="p-4 space-y-3">
<p class="text-primary-600 dark:text-primary-300 text-sm font-medium">
Step {{ setupGuide.currentStep.value }}: {{ setupGuide.steps[setupGuide.currentStep.value - 1]?.title }}
</p>
<p class="text-sm text-neutral-600 dark:text-neutral-300">
{{ setupGuide.steps[setupGuide.currentStep.value - 1]?.description }}
</p>
<!-- Special actions for step 6 (TTS decision) -->
<div v-if="setupGuide.currentStep.value === 6" class="flex gap-2 pt-2">
<button
class="bg-primary-500 hover:bg-primary-600 rounded px-3 py-1 text-xs text-white"
@click="nextSetupGuideStep"
>
Yes, setup TTS
</button>
<button
class="rounded bg-neutral-200 px-3 py-1 text-xs text-neutral-700 dark:bg-neutral-700 hover:bg-neutral-300 dark:text-neutral-200 dark:hover:bg-neutral-600"
@click="skipTTSSetup"
>
Skip TTS
</button>
</div>
</div>
<!-- Navigation Footer -->
<div class="flex items-center justify-between bg-white/70 px-4 py-3 backdrop-blur-sm dark:bg-neutral-950/60">
<!-- Progress Dots -->
<div class="flex gap-1.5">
<template v-for="step in setupGuide.steps" :key="`dot-${step.id}`">
<div
class="size-2 cursor-pointer rounded-full transition-colors duration-200"
:class="[
step.id === setupGuide.currentStep.value ? 'bg-primary-500 scale-110' : 'bg-neutral-300 dark:bg-neutral-600 hover:bg-neutral-400',
step.id < setupGuide.currentStep.value ? 'bg-primary-300 dark:bg-primary-700' : '',
(step.id >= 7 && step.id <= 9 && setupGuide.skippedTTS.value) ? 'bg-neutral-200 dark:bg-neutral-700 opacity-50' : '', // Visually indicate skipped steps
]"
:title="`Step ${step.id}: ${step.title}`"
@click="updateSetupGuideStep(step.id)"
/>
</template>
</div>
<!-- Back/Next Buttons (Hide on step 6) -->
<div v-if="setupGuide.currentStep.value !== 6" class="flex gap-2">
<button
:disabled="setupGuide.currentStep.value <= 1"
class="rounded bg-neutral-200 px-3 py-1 text-xs text-neutral-700 disabled:cursor-not-allowed dark:bg-neutral-700 hover:bg-neutral-300 dark:text-neutral-200 disabled:opacity-50 dark:hover:bg-neutral-600"
@click="prevSetupGuideStep"
>
Back
</button>
<button
:disabled="setupGuide.currentStep.value >= setupGuide.totalSteps"
class="bg-primary-500 hover:bg-primary-600 rounded px-3 py-1 text-xs text-white disabled:cursor-not-allowed disabled:opacity-50"
@click="nextSetupGuideStep"
>
{{ setupGuide.currentStep.value >= setupGuide.totalSteps ? 'Finish' : 'Next' }}
</button>
</div>
</div>
</div>
</div>
</Variant>
</Story>
</template>
<style scoped>
/* For Variant 8 Accordion */
.expand-enter-active,
.expand-leave-active {
transition: all 0.3s ease-in-out;
max-height: 200px;
/* Adjust as needed */
overflow: hidden;
}
.expand-enter-from,
.expand-leave-to {
opacity: 0;
max-height: 0;
}
/* For Variant 9 Character Card */
.perspective-1000 {
perspective: 1000px;
}
.preserve-3d {
transform-style: preserve-3d;
}
.rotate-y-0 {
transform: rotateY(0deg);
}
.rotate-y-10 {
transform: rotateY(10deg);
}
.-rotate-y-10 {
transform: rotateY(-10deg);
}
/* For Variant 10 Interlocking Shapes */
/* Use CSS for clip-path as UnoCSS might not directly support complex polygons */
.clip-path-chevron-right {
clip-path: polygon(0% 0%, calc(100% - 10px) 0%, 100% 50%, calc(100% - 10px) 100%, 0% 100%);
}
.clip-path-chevron-left {
clip-path: polygon(10px 0%, 100% 0%, 100% 100%, 10px 100%, 0% 50%);
}
/* For Variant 12 Chevron Arrow Bar */
.clip-path-arrow {
/* Creates an arrow shape pointing right */
clip-path: polygon(0% 0%, calc(100% - 10px) 0%, 100% 50%, calc(100% - 10px) 100%, 0% 100%, 10px 50%);
}
/* For Variant 17 SVG path marker */
/* Keyframe animation for the marker (alternative to smil) */
@keyframes moveMarker {
from {
motion-offset: 0%;
}
to {
motion-offset: 100%;
}
}
.animated-marker {
motion-path: path('M 20 32 C 60 10, 100 10, 128 32 S 196 54, 236 32');
animation: moveMarker 2s linear infinite;
/* Example infinite animation */
/* For actual step positioning, inline style or JS is better */
offset-path: path('M 20 32 C 60 10, 100 10, 128 32 S 196 54, 236 32');
/* offset-distance needs to be set dynamically based on current step */
transition: offset-distance 0.5s ease-out;
}
/* Ensure sufficient contrast in forced-colors mode */
@media (forced-colors: active) {
.bg-primary-50,
.bg-primary-100,
.bg-neutral-50,
.bg-neutral-100,
.bg-neutral-200,
.bg-neutral-300,
.bg-white {
forced-color-adjust: none;
background-color: ButtonFace;
color: ButtonText;
}
.dark\:bg-primary-900\/20,
.dark\:bg-primary-900,
.dark\:bg-neutral-900\/20,
.dark\:bg-neutral-900,
.dark\:bg-neutral-950,
.dark\:bg-neutral-800,
.dark\:bg-neutral-700 {
forced-color-adjust: none;
background-color: ButtonFace;
color: ButtonText;
}
.border-primary-100,
.border-primary-300,
.border-primary-500,
.border-neutral-100,
.border-neutral-200,
.border-neutral-300 {
forced-color-adjust: none;
border-color: ButtonText;
}
.dark\:border-primary-900,
.dark\:border-primary-700,
.dark\:border-primary-400,
.dark\:border-neutral-900,
.dark\:border-neutral-800,
.dark\:border-neutral-700 {
forced-color-adjust: none;
border-color: ButtonText;
}
.text-primary-500,
.text-primary-600,
.text-primary-700,
.text-neutral-500,
.text-neutral-600,
.text-neutral-700,
.text-neutral-800,
.text-white {
forced-color-adjust: none;
color: ButtonText;
}
.dark\:text-primary-400,
.dark\:text-primary-300,
.dark\:text-primary-200,
.dark\:text-neutral-400,
.dark\:text-neutral-300,
.dark\:text-neutral-200,
.dark\:text-black {
forced-color-adjust: none;
color: ButtonText;
}
.bg-primary-500,
.bg-primary-400,
.bg-primary-300,
.bg-green-400,
.bg-teal-500 {
forced-color-adjust: none;
background-color: Highlight;
color: HighlightText;
}
.dark\:bg-primary-900,
.dark\:bg-primary-700,
.dark\:bg-primary-600,
.dark\:bg-green-600,
.dark\:bg-teal-700 {
forced-color-adjust: none;
background-color: Highlight;
color: HighlightText;
}
}
</style>
@@ -0,0 +1 @@
export { default as Steps } from './Steps.vue'
@@ -1 +1,2 @@
export { default as Skeleton } from './Skeleton.vue'
export * from './Steppers'