Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ coverage

storybook-static

.claude/*
.claude/*

tsconfig.app.tsbuildinfo
55 changes: 55 additions & 0 deletions src/plugins/prime/stories/Timeline/Timeline.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Meta, Canvas, Title, Description, Controls } from '@storybook/addon-docs/blocks';
import * as TimelineStories from './Timeline.stories';

<Meta of={TimelineStories} />

<Title />
<Description />

## Импорт

```js
import Timeline from 'primevue/timeline';
```

## Варианты использования

### Horizontal

Горизонтальная ориентация с событиями в ряд.

<Canvas of={TimelineStories.Horizontal} />

#### Список аргументов компонента.
<Controls of={TimelineStories.Horizontal} />

### Vertical

Вертикальная ориентация — события сверху вниз.

<Canvas of={TimelineStories.Vertical} />

### Vertical Alternate

Чередующееся расположение контента (слева-справа).

<Canvas of={TimelineStories.VerticalAlternate} />

### Markers

Кастомные маркеры с цветами и иконками.

<Canvas of={TimelineStories.Markers} />

<details>
<summary>Подробнее о кастомных маркерах</summary>

Доступны два варианта:
- **Без иконки** — цветная обводка через CSS-класс `timeline-marker-dot` и inline-стиль `borderColor`
- **С иконкой** — иконка Tabler через CSS-класс `timeline-marker-icon` и inline-стиль `color`

Классы `timeline-marker-dot` и `timeline-marker-icon` — кастомные классы из дизайн-системы CDEK.
Они стилизуют маркер на основе токенов `timeline.eventMarker.*` (размер, фон, скругление, толщина обводки).
Применяются внутри слота `#marker` компонента Timeline.

</details>
156 changes: 156 additions & 0 deletions src/plugins/prime/stories/Timeline/Timeline.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import Timeline from 'primevue/timeline';
import {
HorizontalTemplate,
VerticalTemplate,
AlternateTemplate,
MarkersTemplate,
} from './Timeline.template';

const meta = {
title: 'Prime/Timeline',
component: Timeline,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Компонент для визуализации последовательности событий в хронологическом порядке. Поддерживает горизонтальную и вертикальную ориентацию, кастомные маркеры.',
},
},
designToken: { disable: false },
designTokens: { prefix: '--p-timeline' },
},
argTypes: {
value: {
control: 'object',
description: 'Массив событий для отображения',
table: {
category: 'Props',
type: { summary: 'array' },
},
},
layout: {
control: 'select',
options: ['vertical', 'horizontal'],
description: 'Ориентация таймлайна',
table: {
category: 'Props',
defaultValue: { summary: "'vertical'" },
type: { summary: "'vertical' | 'horizontal'" },
},
},
align: {
control: 'select',
options: ['left', 'right', 'top', 'bottom', 'alternate'],
description: 'Положение контента относительно маркера',
table: {
category: 'Props',
defaultValue: { summary: "'left'" },
type: { summary: "'left' | 'right' | 'top' | 'bottom' | 'alternate'" },
},
},
},
};

export default meta;

// ── Horizontal ──────────────────────────────────────────────────────────────

export const Horizontal = {
render: HorizontalTemplate,
args: {},
parameters: {
docs: {
description: {
story: 'Горизонтальная ориентация Timeline с событиями в ряд.',
},
source: {
code: `<Timeline :value="events" layout="horizontal">
<template #content="slotProps">
<div class="body-bold-base">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</Timeline>`,
},
},
},
};

// ── Vertical ────────────────────────────────────────────────────────────────

export const Vertical = {
render: VerticalTemplate,
args: {},
parameters: {
docs: {
description: {
story: 'Вертикальная ориентация Timeline — события сверху вниз.',
},
source: {
code: `<Timeline :value="events" layout="vertical">
<template #content="slotProps">
<div class="body-bold-lg">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</Timeline>`,
},
},
},
};

// ── Vertical Alternate ──────────────────────────────────────────────────────

export const VerticalAlternate = {
render: AlternateTemplate,
args: {},
parameters: {
docs: {
description: {
story: 'Вертикальная ориентация с чередующимся расположением контента.',
},
source: {
code: `<Timeline :value="events" layout="vertical" align="alternate">
<template #content="slotProps">
<div class="body-bold-lg">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</Timeline>`,
},
},
},
};

// ── Markers ─────────────────────────────────────────────────────────────────

export const Markers = {
render: MarkersTemplate,
args: {},
parameters: {
docs: {
description: {
story:
'Кастомные маркеры с цветами и иконками. Доступны варианты: с иконкой (через слот #marker) и без (цветная обводка).',
},
source: {
code: `<PBlockTimeline :value="events" layout="vertical">
<template #marker="slotProps">
<i
v-if="slotProps.item.icon"
:class="[slotProps.item.icon, 'timeline-marker-icon']"
:style="{ color: slotProps.item.color }"
></i>
<div
v-else
class="timeline-marker-dot"
:style="{ borderColor: slotProps.item.color }"
></div>
</template>
<template #content="slotProps">
<div class="body-bold-lg">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</PBlockTimeline>`,
},
},
},
};
116 changes: 116 additions & 0 deletions src/plugins/prime/stories/Timeline/Timeline.template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import Timeline from 'primevue/timeline';
import PBlockTimeline from '@/primeBlocks/PBlockTimeline/PBlockTimeline.vue';

const defaultEvents = [
{ status: 'Заказ создан', date: '15 апр 2026, 10:00' },
{ status: 'Принят на склад', date: '16 апр 2026, 14:30' },
{ status: 'В пути', date: '17 апр 2026, 09:15' },
{ status: 'Доставлен', date: '18 апр 2026, 11:45' },
];

const markerEvents = [
{
status: 'Успех',
date: 'caption',
icon: 'ti ti-circle-check',
color: 'var(--p-green-500)',
},
{ status: 'Успех (точка)', date: 'caption', color: 'var(--p-green-500)' },
{
status: 'Предупреждение',
date: 'caption',
icon: 'ti ti-alert-circle',
color: 'var(--p-orange-500)',
},
{
status: 'Предупреждение (точка)',
date: 'caption',
color: 'var(--p-orange-500)',
},
{
status: 'Ошибка',
date: 'caption',
icon: 'ti ti-circle-x',
color: 'var(--p-red-500)',
},
{ status: 'Ошибка (точка)', date: 'caption', color: 'var(--p-red-500)' },
{
status: 'Информация',
date: 'caption',
icon: 'ti ti-info-circle',
color: 'var(--p-blue-500)',
},
{ status: 'Информация (точка)', date: 'caption', color: 'var(--p-blue-500)' },
];

export const HorizontalTemplate = (args) => ({
components: { Timeline },
setup() {
return { args, events: defaultEvents };
},
template: `
<Timeline :value="events" layout="horizontal" v-bind="args">
<template #content="slotProps">
<div class="body-bold-base">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</Timeline>
`,
});

export const VerticalTemplate = (args) => ({
components: { Timeline },
setup() {
return { args, events: defaultEvents };
},
template: `
<Timeline :value="events" layout="vertical" v-bind="args">
<template #content="slotProps">
<div class="body-bold-lg">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</Timeline>
`,
});

export const AlternateTemplate = (args) => ({
components: { Timeline },
setup() {
return { args, events: defaultEvents };
},
template: `
<Timeline :value="events" layout="vertical" align="alternate" v-bind="args">
<template #content="slotProps">
<div class="body-bold-lg">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</Timeline>
`,
});

export const MarkersTemplate = (args) => ({
components: { PBlockTimeline },
setup() {
return { args, events: markerEvents };
},
template: `
<PBlockTimeline :value="events" layout="vertical" v-bind="args">
<template #marker="slotProps">
<i
v-if="slotProps.item.icon"
:class="[slotProps.item.icon, 'timeline-marker-icon']"
:style="{ color: slotProps.item.color }"
></i>
<div
v-else
class="timeline-marker-dot"
:style="{ borderColor: slotProps.item.color }"
></div>
</template>
<template #content="slotProps">
<div class="body-bold-lg">{{ slotProps.item.status }}</div>
<div class="caption-secondary">{{ slotProps.item.date }}</div>
</template>
</PBlockTimeline>
`,
});
43 changes: 43 additions & 0 deletions src/plugins/prime/theme3.0/components/css/timeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const css = ({ dt }: { dt: (token: string) => string }) => `

/* --- Base --- */

.p-timeline .p-timeline-event-content.p-timeline-event-content {
display: flex;
flex-direction: column;
gap: ${dt('timeline.extend.extEvent.gap')};
}

/* --- Orientation paddings --- */

.p-timeline-horizontal .p-timeline-event-opposite,
.p-timeline-horizontal .p-timeline-event-content {
padding: ${dt('feedback.padding.100')} 0;
}

.p-timeline-vertical .p-timeline-event-opposite,
.p-timeline-vertical .p-timeline-event-content {
padding: 0 ${dt('feedback.padding.100')};
}

/* --- Horizontal --- */

.p-timeline.p-timeline-horizontal {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
align-items: start;
}

/* Connector для последнего элемента в горизонтальной ориентации */
.p-timeline.p-timeline-horizontal .p-timeline-event:last-child .p-timeline-event-separator::after {
content: '';
display: block;
flex: 1;
background-color: ${dt('timeline.eventConnector.color')};
height: ${dt('timeline.eventConnector.size')};
}

`;

export default css;
Loading
Loading