timeline

Timeline with pipeline steps.

Timeline with pipeline steps.

Загрузка превью…

src/timeline/c-timeline-6.vue

<!--
  Расхождение с апстримом:
  - `IconPlaceholder` (иконки статуса "check"/"circle", шеврон коллапса
    "chevron-right") заменена инлайновым `<svg>` (lucide-react v0.545.0) —
    см. docs/PORTING.md §5. `Spinner` — то же расхождение, что и в
    `ui-spinner` (он сам рендерит `IconPlaceholder` внутри), здесь
    используется примитив `@revueui/ui`, который уже адаптирован.
  - Сетевые аватары (unsplash) заменены статичным 1x1 data-URI (PIXEL) —
    сетевые запросы в headless-стенде недетерминированы.
-->
<script setup lang="ts">
import { Badge } from "@/components/reui/badge"
import { Frame, FrameHeader, FramePanel } from "@/components/reui/frame"
import { Timeline, TimelineContent, TimelineHeader, TimelineIndicator, TimelineItem, TimelineSeparator, TimelineTitle } from "@/components/reui/timeline"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { Spinner } from "@/components/ui/spinner"
import { cn } from "@/lib/utils"

const PIXEL =
  "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="

const pipelineSteps = [
  {
    id: 1,
    title: "Source Code Checkout",
    duration: "12s",
    status: "completed",
    description: "Successfully fetched latest changes from the main branch.",
    user: { name: "Alex Johnson", avatar: PIXEL },
  },
  {
    id: 2,
    title: "Dependency Installation",
    duration: "1m 45s",
    status: "completed",
    description: "All npm packages installed and cached for future builds.",
    user: { name: "Sarah Chen", avatar: PIXEL },
  },
  {
    id: 3,
    title: "Unit & Integration Tests",
    duration: "Running",
    status: "active",
    description: "Running 142 test suites across the entire codebase...",
    user: { name: "Michael Rodriguez", avatar: PIXEL },
  },
  {
    id: 4,
    title: "Production Build",
    duration: "Pending",
    status: "pending",
    description: "Optimizing assets and generating static site pages.",
    user: { name: "Emma Wilson", avatar: PIXEL },
  },
]

function statusVariant(status: string) {
  return status === "completed"
    ? "success-light"
    : status === "active"
      ? "info-light"
      : "warning-light"
}
</script>

<template>
  <div class="w-full max-w-lg">
    <Timeline :value="3">
      <TimelineItem
        v-for="step in pipelineSteps"
        :key="step.id"
        :step="step.id"
        class="ms-10 pb-10"
      >
        <TimelineHeader>
          <TimelineSeparator class="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-7" />
          <div class="flex items-center gap-2">
            <TimelineTitle class="text-sm font-semibold">{{ step.title }}</TimelineTitle>
            <Badge :variant="statusVariant(step.status)" size="sm">{{ step.duration }}</Badge>
          </div>
          <TimelineIndicator
            :class="
              cn(
                'bg-muted text-muted-foreground group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 items-center justify-center border-none group-data-[orientation=vertical]/timeline:-left-7',
                step.status === 'active' && 'ring-primary/20 ring-2'
              )
            "
          >
            <svg
              v-if="step.status === 'completed'"
              xmlns="http://www.w3.org/2000/svg"
              width="24"
              height="24"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
              aria-hidden="true"
              class="lucide lucide-check size-3.5"
            >
              <path d="M20 6 9 17l-5-5" />
            </svg>
            <Spinner v-else-if="step.status === 'active'" class="size-3.5" />
            <svg
              v-else
              xmlns="http://www.w3.org/2000/svg"
              width="24"
              height="24"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
              aria-hidden="true"
              class="lucide lucide-circle size-3.5"
            >
              <circle cx="12" cy="12" r="10" />
            </svg>
          </TimelineIndicator>
        </TimelineHeader>
        <TimelineContent class="mt-2">
          <Frame stacked dense spacing="sm">
            <Collapsible default-open class="group/collapsible">
              <CollapsibleTrigger class="flex w-full">
                <FrameHeader class="flex grow flex-row items-center justify-between gap-2">
                  <div class="flex items-center gap-2">
                    <Avatar class="size-5">
                      <AvatarImage :src="step.user.avatar" :alt="step.user.name" />
                      <AvatarFallback>{{ step.user.name.charAt(0) }}</AvatarFallback>
                    </Avatar>
                    <span class="text-muted-foreground text-xs font-medium">{{ step.user.name }}</span>
                  </div>
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    width="24"
                    height="24"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    aria-hidden="true"
                    class="lucide lucide-chevron-right text-muted-foreground size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
                  >
                    <path d="m9 18 6-6-6-6" />
                  </svg>
                </FrameHeader>
              </CollapsibleTrigger>
              <CollapsibleContent>
                <FramePanel>
                  <p class="text-muted-foreground text-sm leading-relaxed">{{ step.description }}</p>
                </FramePanel>
              </CollapsibleContent>
            </Collapsible>
          </Frame>
        </TimelineContent>
      </TimelineItem>
    </Timeline>
  </div>
</template>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-timeline-6.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)