From 5170cfd7eb44a25c64673cf12979f9ca1049695f Mon Sep 17 00:00:00 2001 From: J-Michalek <71264422+J-Michalek@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:06:51 +0200 Subject: [PATCH] feat(Timeline): add `reverse` prop (#4316) Co-authored-by: Benjamin Canac --- docs/content/3.components/timeline.md | 40 +++++ playground/app/pages/components/timeline.vue | 3 + src/runtime/components/Timeline.vue | 29 +++- src/theme/timeline.ts | 37 ++++- test/components/Timeline.spec.ts | 9 +- .../__snapshots__/Timeline-vue.spec.ts.snap | 143 +++++++++++++++++- .../__snapshots__/Timeline.spec.ts.snap | 143 +++++++++++++++++- 7 files changed, 383 insertions(+), 21 deletions(-) diff --git a/docs/content/3.components/timeline.md b/docs/content/3.components/timeline.md index ee02d795..091c022f 100644 --- a/docs/content/3.components/timeline.md +++ b/docs/content/3.components/timeline.md @@ -173,6 +173,46 @@ class: 'overflow-x-auto' --- :: +### Reverse + +Use the reverse prop to reverse the direction of the Timeline. + +::component-code +--- +ignore: + - items + - class + - defaultValue +external: + - items +externalTypes: + - TimelineItem[] +props: + reverse: true + modelValue: 2 + orientation: 'vertical' + items: + - date: 'Mar 15, 2025' + title: 'Project Kickoff' + description: 'Kicked off the project with team alignment.' + icon: 'i-lucide-rocket' + - date: 'Mar 22 2025' + title: 'Design Phase' + description: 'User research and design workshops.' + icon: 'i-lucide-palette' + - date: 'Mar 29 2025' + title: 'Development Sprint' + description: 'Frontend and backend development.' + icon: 'i-lucide-code' + - date: 'Apr 5 2025' + title: 'Testing & Deployment' + description: 'QA testing and performance optimization.' + icon: 'i-lucide-check-circle' + class: 'w-full' +class: 'overflow-x-auto' +--- +:: + ## Examples ### Control active item diff --git a/playground/app/pages/components/timeline.vue b/playground/app/pages/components/timeline.vue index 837aced1..0f571bee 100644 --- a/playground/app/pages/components/timeline.vue +++ b/playground/app/pages/components/timeline.vue @@ -9,6 +9,7 @@ const orientations = Object.keys(theme.variants.orientation) const orientation = ref('vertical' as const) const color = ref('primary' as const) const size = ref('md' as const) +const reverse = ref(false) const items = [{ date: 'Mar 15, 2025', @@ -46,6 +47,7 @@ const value = ref('kickoff') + diff --git a/src/runtime/components/Timeline.vue b/src/runtime/components/Timeline.vue index 4676c2d1..e04ca289 100644 --- a/src/runtime/components/Timeline.vue +++ b/src/runtime/components/Timeline.vue @@ -41,6 +41,7 @@ export interface TimelineProps { */ orientation?: Timeline['variants']['orientation'] defaultValue?: string | number + reverse?: boolean class?: any ui?: Timeline['slots'] } @@ -75,16 +76,34 @@ const appConfig = useAppConfig() as Timeline['AppConfig'] const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.timeline || {}) })({ orientation: props.orientation, size: props.size, - color: props.color + color: props.color, + reverse: props.reverse })) const currentStepIndex = computed(() => { const value = modelValue.value ?? props.defaultValue - return ((typeof value === 'string') - ? props.items.findIndex(item => item.value === value) - : value) ?? -1 + if (typeof value === 'string') { + return props.items.findIndex(item => item.value === value) ?? -1 + } + + if (props.reverse) { + return value != null ? props.items.length - 1 - value : -1 + } else { + return value ?? -1 + } }) + +function getItemState(index: number): 'active' | 'completed' | undefined { + if (currentStepIndex.value === -1) return undefined + if (index === currentStepIndex.value) return 'active' + + if (props.reverse) { + return index > currentStepIndex.value ? 'completed' : undefined + } else { + return index < currentStepIndex.value ? 'completed' : undefined + } +}