Implemented Timeline

pull/7319/merge
Cagatay Civici 2025-03-01 22:45:40 +03:00
parent 11bd7f029c
commit ee0e002e26
11 changed files with 466 additions and 7 deletions

View File

@ -38,6 +38,10 @@
"name": "Textarea",
"to": "/textarea"
},
{
"name": "Timeline",
"to": "/timeline"
},
{
"name": "ToggleSwitch",
"to": "/toggleswitch"

View File

@ -0,0 +1,73 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Content location relative the line is defined with the <i>align</i> property.</p>
</DocSectionText>
<div class="card flex flex-wrap gap-12">
<Timeline :value="events" class="w-full md:w-80">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
<Timeline :value="events" align="right" class="w-full md:w-80">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
<Timeline :value="events" align="alternate" class="w-full md:w-80">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
</div>
<DocSectionCode :code="code" />
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
const code = ref(`
<template>
<div class="card flex flex-wrap gap-12">
<Timeline :value="events" class="w-full md:w-80">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
<Timeline :value="events" align="right" class="w-full md:w-80">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
<Timeline :value="events" align="alternate" class="w-full md:w-80">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
</div>
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
<\/script>
`);
</script>

View File

@ -0,0 +1,49 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Timeline requires a <i>value</i> for the collection of events and <i>content</i> slot that receives an object as a parameter to return content.</p>
</DocSectionText>
<div class="card">
<Timeline :value="events">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
</div>
<DocSectionCode :code="code" />
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
const code = ref(`
<template>
<div class="card">
<Timeline :value="events">
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
</div>
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
<\/script>
`);
</script>

View File

@ -0,0 +1,65 @@
<template>
<DocSectionText v-bind="$attrs">
<p>TimeLine orientation is controlled with the <i>layout</i> property, default is <i>vertical</i> having <i>horizontal</i> as the alternative.</p>
</DocSectionText>
<div class="card flex flex-col gap-4">
<Timeline :value="events" layout="horizontal" align="top">
<template #content="slotProps">
{{ slotProps.item }}
</template>
</Timeline>
<Timeline :value="events" layout="horizontal" align="bottom">
<template #content="slotProps">
{{ slotProps.item }}
</template>
</Timeline>
<Timeline :value="events" layout="horizontal" align="alternate">
<template #opposite> &nbsp; </template>
<template #content="slotProps">
{{ slotProps.item }}
</template>
</Timeline>
</div>
<DocSectionCode :code="code" />
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref(['2020', '2021', '2022', '2023']);
const code = ref(`
<template>
<div class="card flex flex-col gap-4">
<Timeline :value="events" layout="horizontal" align="top">
<template #content="slotProps">
{{ slotProps.item }}
</template>
</Timeline>
<Timeline :value="events" layout="horizontal" align="bottom">
<template #content="slotProps">
{{ slotProps.item }}
</template>
</Timeline>
<Timeline :value="events" layout="horizontal" align="alternate">
<template #opposite> &nbsp; </template>
<template #content="slotProps">
{{ slotProps.item }}
</template>
</Timeline>
</div>
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref(['2020', '2021', '2022', '2023']);
<\/script>
`);
</script>

View File

@ -0,0 +1,16 @@
<template>
<DocSectionText v-bind="$attrs" />
<DocSectionCode :code="code" lang="script" />
</template>
<script>
export default {
data() {
return {
code: `
import Timeline from '@/plex/timeline';
`
};
}
};
</script>

View File

@ -0,0 +1,55 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Additional content at the other side of the line can be provided with the <i>opposite</i> property.</p>
</DocSectionText>
<div class="card">
<Timeline :value="events">
<template #opposite="slotProps">
<small class="text-surface-500 dark:text-surface-400">{{ slotProps.item.date }}</small>
</template>
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
</div>
<DocSectionCode :code="code" />
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
const code = ref(`
<template>
<div class="card">
<Timeline :value="events">
<template #opposite="slotProps">
<small class="text-surface-500 dark:text-surface-400">{{ slotProps.item.date }}</small>
</template>
<template #content="slotProps">
{{ slotProps.item.status }}
</template>
</Timeline>
</div>
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
<\/script>
`);
</script>

View File

@ -0,0 +1,89 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Sample implementation with custom content and styled markers.</p>
</DocSectionText>
<div class="card">
<Timeline :value="events" align="alternate">
<template #marker="slotProps">
<span class="flex w-8 h-8 items-center justify-center text-white rounded-full z-10 shadow-sm" :style="{ backgroundColor: slotProps.item.color }">
<i :class="slotProps.item.icon"></i>
</span>
</template>
<template #content="slotProps">
<Card class="mt-4">
<template #title>
{{ slotProps.item.status }}
</template>
<template #subtitle>
{{ slotProps.item.date }}
</template>
<template #content>
<img v-if="slotProps.item.image" :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.item.image}`" :alt="slotProps.item.name" width="200" class="shadow-sm" />
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore sed consequuntur error repudiandae numquam deserunt quisquam repellat libero asperiores earum nam nobis, culpa ratione quam perferendis esse, cupiditate
neque quas!
</p>
<Button label="Read more" text></Button>
</template>
</Card>
</template>
</Timeline>
</div>
<DocSectionCode :code="code" />
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0', image: 'game-controller.jpg' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
const code = ref(`
<template>
<div class="card">
<Timeline :value="events" align="alternate">
<template #marker="slotProps">
<span class="flex w-8 h-8 items-center justify-center text-white rounded-full z-10 shadow-sm" :style="{ backgroundColor: slotProps.item.color }">
<i :class="slotProps.item.icon"></i>
</span>
</template>
<template #content="slotProps">
<Card class="mt-4">
<template #title>
{{ slotProps.item.status }}
</template>
<template #subtitle>
{{ slotProps.item.date }}
</template>
<template #content>
<img v-if="slotProps.item.image" :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.item.image}\`" :alt="slotProps.item.name" width="200" class="shadow-sm" />
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore sed consequuntur error repudiandae numquam deserunt quisquam repellat libero asperiores earum nam nobis, culpa ratione quam perferendis esse, cupiditate
neque quas!
</p>
<Button label="Read more" text></Button>
</template>
</Card>
</template>
</Timeline>
</div>
</template>
<script setup>
import Timeline from '@/plex/timeline';
import { ref } from 'vue';
const events = ref([
{ status: 'Ordered', date: '15/10/2020 10:30', icon: 'pi pi-shopping-cart', color: '#9C27B0', image: 'game-controller.jpg' },
{ status: 'Processing', date: '15/10/2020 14:00', icon: 'pi pi-cog', color: '#673AB7' },
{ status: 'Shipped', date: '15/10/2020 16:15', icon: 'pi pi-shopping-cart', color: '#FF9800' },
{ status: 'Delivered', date: '16/10/2020 10:00', icon: 'pi pi-check', color: '#607D8B' }
]);
<\/script>
`);
</script>

View File

@ -0,0 +1,51 @@
<template>
<DocComponent title="Vue Timeline Component" header="Timeline" description="Timeline visualizes a series of chained events." :componentDocs="docs" />
</template>
<script>
import AlignmentDoc from '@/doc/timeline/AlignmentDoc.vue';
import BasicDoc from '@/doc/timeline/BasicDoc.vue';
import HorizontalDoc from '@/doc/timeline/HorizontalDoc.vue';
import ImportDoc from '@/doc/timeline/ImportDoc.vue';
import OppositeDoc from '@/doc/timeline/OppositeDoc.vue';
import TemplateDoc from '@/doc/timeline/TemplateDoc.vue';
export default {
data() {
return {
docs: [
{
id: 'import',
label: 'Import',
component: ImportDoc
},
{
id: 'basic',
label: 'Basic',
component: BasicDoc
},
{
id: 'alignment',
label: 'Alignment',
component: AlignmentDoc
},
{
id: 'opposite',
label: 'Opposite',
component: OppositeDoc
},
{
id: 'template',
label: 'Template',
component: TemplateDoc
},
{
id: 'horizontal',
label: 'Horizontal',
component: HorizontalDoc
}
]
};
}
};
</script>

View File

@ -0,0 +1,43 @@
<template>
<Timeline unstyled :pt="theme">
<template v-for="(_, slotName) in $slots" v-slot:[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps ?? {}" />
</template>
</Timeline>
</template>
<script setup>
import Timeline from 'primevue/timeline';
import { ref } from 'vue';
const theme = ref({
root: `flex flex-col flex-grow
p-horizontal:flex-row`,
event: `group flex relative min-h-20 last:min-h-0
p-right:flex-row-reverse
p-horizontal:flex-col p-horizontal:flex-1 p-horizontal:last:flex-none
p-bottom:flex-col-reverse
p-vertical:p-alternate:even:flex-row-reverse
p-horizontal:p-alternate:even:flex-col-reverse`,
eventOpposite: `flex-1
p-left:text-end p-right:text-start
p-vertical:py-0 p-vertical:px-4 p-vertical:leading-none
p-vertical:p-alternate:group-odd:text-end p-vertical:p-alternate:group-even:text-start
p-horizontal:py-4 p-horizontal:px-0`,
eventSeparator: `flex-none flex flex-col items-center p-horizontal:flex-row`,
eventMarker: `inline-flex items-center justify-center relative self-baseline
border-2 rounded-full border-surface-200 dark:border-surface-700 w-[1.125rem] h-[1.125rem]
bg-surface-0 dark:bg-surface-900
before:rounded-full before:w-[0.375rem] before:h-[0.375rem] before:bg-primary
after:absolute after:w-full after:h-full after:rounded-full after:shadow-[0px_0.5px_0px_0px_rgba(0,0,0,0.06),0px_1px_1px_0px_rgba(0,0,0,0.12)]
p-horizontal:flex-row`,
eventConnector: `flex-grow bg-surface-200 dark:bg-surface-700
p-vertical:w-[2px]
p-horizontal:w-full p-horizontal:h-[2px]`,
eventContent: `flex-1
p-left:text-start p-right:text-end
p-vertical:py-0 p-vertical:px-4 p-vertical:leading-none
p-vertical:p-alternate:group-odd:text-start p-vertical:p-alternate:group-even:text-end
p-horizontal:py-4 p-horizontal:px-0`
});
</script>

View File

@ -18,6 +18,11 @@ export default {
addVariant('p-checked', '&[data-p~="checked"]');
addVariant('p-disabled', '&[data-p~="disabled"]');
addVariant('p-enabled', '&:not([data-p~="disabled"])');
addVariant('p-left', '&[data-p~="left"]');
addVariant('p-right', '&[data-p~="right"]');
addVariant('p-top', '&[data-p~="top"]');
addVariant('p-bottom', '&[data-p~="bottom"]');
addVariant('p-alternate', '&[data-p~="alternate"]');
})
],
theme: {

View File

@ -1,18 +1,18 @@
<template>
<div :class="cx('root')" v-bind="ptmi('root')">
<div v-for="(item, index) of value" :key="getKey(item, index)" :class="cx('event')" v-bind="getPTOptions('event', index)">
<div :class="cx('eventOpposite', { index })" v-bind="getPTOptions('eventOpposite', index)">
<div :class="cx('root')" v-bind="ptmi('root')" :data-p="dataP">
<div v-for="(item, index) of value" :key="getKey(item, index)" :class="cx('event')" v-bind="getPTOptions('event', index)" :data-p="dataP">
<div :class="cx('eventOpposite', { index })" v-bind="getPTOptions('eventOpposite', index)" :data-p="dataP">
<slot name="opposite" :item="item" :index="index"></slot>
</div>
<div :class="cx('eventSeparator')" v-bind="getPTOptions('eventSeparator', index)">
<div :class="cx('eventSeparator')" v-bind="getPTOptions('eventSeparator', index)" :data-p="dataP">
<slot name="marker" :item="item" :index="index">
<div :class="cx('eventMarker')" v-bind="getPTOptions('eventMarker', index)"></div>
<div :class="cx('eventMarker')" v-bind="getPTOptions('eventMarker', index)" :data-p="dataP"></div>
</slot>
<slot v-if="index !== value.length - 1" name="connector" :item="item" :index="index">
<div :class="cx('eventConnector')" v-bind="getPTOptions('eventConnector', index)"></div>
<div :class="cx('eventConnector')" v-bind="getPTOptions('eventConnector', index)" :data-p="dataP"></div>
</slot>
</div>
<div :class="cx('eventContent')" v-bind="getPTOptions('eventContent', index)">
<div :class="cx('eventContent')" v-bind="getPTOptions('eventContent', index)" :data-p="dataP">
<slot name="content" :item="item" :index="index"></slot>
</div>
</div>
@ -20,6 +20,7 @@
</template>
<script>
import { cn } from '@primeuix/utils';
import { resolveFieldData } from '@primeuix/utils/object';
import BaseTimeline from './BaseTimeline.vue';
@ -39,6 +40,14 @@ export default {
}
});
}
},
computed: {
dataP() {
return cn({
[this.layout]: this.layout,
[this.align]: this.align
});
}
}
};
</script>