feat(Timeline): add reverse prop (#4316)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
J-Michalek
2025-06-12 17:06:51 +02:00
committed by GitHub
parent 9bcf1ad92f
commit 5170cfd7eb
7 changed files with 383 additions and 21 deletions

View File

@@ -41,6 +41,7 @@ export interface TimelineProps<T extends TimelineItem = TimelineItem> {
*/
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
}
}
</script>
<template>
@@ -93,7 +112,7 @@ const currentStepIndex = computed(() => {
v-for="(item, index) in items"
:key="item.value ?? index"
:class="ui.item({ class: [props.ui?.item, item.ui?.item, item.class] })"
:data-state="index < currentStepIndex ? 'completed' : index === currentStepIndex ? 'active' : undefined"
:data-state="getItemState(index)"
>
<div :class="ui.container({ class: [props.ui?.container, item.ui?.container] })">
<UAvatar :size="size" :icon="item.icon" v-bind="typeof item.avatar === 'object' ? item.avatar : {}" :class="ui.indicator({ class: [props.ui?.indicator, item.ui?.indicator] })" :ui="{ icon: 'text-inherit', fallback: 'text-inherit' }">

View File

@@ -29,12 +29,11 @@ export default (options: Required<ModuleOptions>) => ({
color: {
...Object.fromEntries((options.theme.colors || []).map((color: string) => [color, {
indicator: `group-data-[state=completed]:bg-${color} group-data-[state=active]:bg-${color}`,
separator: `group-data-[state=completed]:bg-${color}`
indicator: `group-data-[state=completed]:bg-${color} group-data-[state=active]:bg-${color}`
}])),
neutral: {
indicator: 'group-data-[state=completed]:bg-inverted group-data-[state=active]:bg-inverted',
separator: 'group-data-[state=completed]:bg-inverted'
indicator: 'group-data-[state=completed]:bg-inverted group-data-[state=active]:bg-inverted'
}
},
@@ -48,10 +47,38 @@ export default (options: Required<ModuleOptions>) => ({
'xl': '',
'2xl': '',
'3xl': ''
},
reverse: {
true: ''
}
},
compoundVariants: [{
compoundVariants: [...(options.theme.colors || []).map((color: string) => ({
color,
reverse: false,
class: {
separator: `group-data-[state=completed]:bg-${color}`
}
})), ...(options.theme.colors || []).map((color: string) => ({
color,
reverse: true,
class: {
separator: `group-data-[state=active]:bg-${color} group-data-[state=completed]:bg-${color}`
}
})), {
color: 'neutral',
reverse: false,
class: {
separator: 'group-data-[state=completed]:bg-inverted'
}
}, {
color: 'neutral',
reverse: true,
class: {
separator: 'group-data-[state=active]:bg-inverted group-data-[state=completed]:bg-inverted'
}
}, {
orientation: 'horizontal',
size: '3xs',
class: {