A simple timeline in VueJs
A Pen by David Huanca on CodePen.
A simple timeline in VueJs
A Pen by David Huanca on CodePen.
| <div id="app"> | |
| <div class="container"> | |
| <div class="page-header"> | |
| <span id="timeline-header">Timeline</span> | |
| </div> | |
| <timeline :items="timeline"></timeline> | |
| </div> | |
| <span class="copy"> | |
| Based on an example from <a href="https://codepen.io/betdream">betdream</a> | |
| </span> | |
| <template id="timeline-template"> | |
| <ul class="timeline"> | |
| <li | |
| v-for="item in items" | |
| is="timeline-item" | |
| :item="item"> | |
| </li> | |
| </ul> | |
| </template> | |
| <template id="timeline-item-template"> | |
| <li class="timeline-item {{ item.action_needed }}"> | |
| <div class="timeline-badge {{ item.icon_status }}"><i class="{{ item.icon_class }}"></i></div> | |
| <div class="timeline-panel {{ item.element_status }} {{ item.element_day_marker }}"> | |
| <div class="timeline-heading"> | |
| <h4 class="timeline-title {{ item.text_status }}">{{ item.title }}</h4> | |
| <div class="timeline-panel-controls"> | |
| <div class="controls"> | |
| <a | |
| v-for="control in item.controls" | |
| is="timeline-control" | |
| :control="control"> | |
| </a> | |
| </div> | |
| <div class="timestamp"> | |
| <small class="">{{ item.created }}</small> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="timeline-body">{{{ item.body }}}</div> | |
| </div> | |
| </div> | |
| </li> | |
| </template> | |
| <template id="timeline-control-template"> | |
| <a href="#" @click="handleClick"> | |
| <i class="{{ control.icon_class }}"></i> | |
| </a> | |
| </template> | |
| </div> |
| Vue.component('timeline-control', { | |
| template: '#timeline-control-template', | |
| props: ['control'], | |
| methods: { | |
| handleClick: function() { | |
| if(this.control.method == 'delete') { | |
| this.$dispatch('timeline-delete'); | |
| } else if(this.control.method == 'edit') { | |
| this.$dispatch('timeline-edit'); | |
| } else { | |
| console.log("Unknown method "+this.control.method) | |
| } | |
| } | |
| }, | |
| }); | |
| Vue.component('timeline', { | |
| template: '#timeline-template', | |
| props: ['items'], | |
| events: { | |
| 'delete-item': function() { | |
| return true; // forward to parent | |
| } | |
| } | |
| }); | |
| Vue.component('timeline-item', { | |
| template: '#timeline-item-template', | |
| props: ['item'], | |
| methods: { | |
| delete: function() { | |
| this.$dispatch('timeline-delete-item', this.item.id) | |
| }, | |
| edit: function() { | |
| } | |
| }, | |
| events: { | |
| 'timeline-delete': function() { | |
| this.delete(); | |
| }, | |
| 'timeline-edit': function() { | |
| this.edit(); | |
| } | |
| } | |
| }); | |
| new Vue({ | |
| el: '#app', | |
| data: { | |
| timeline: [ | |
| { | |
| id: 7, | |
| element_status: '', | |
| icon_class: 'glyphicon glyphicon-log-out', | |
| icon_status: 'success', | |
| title: 'DELIVERY', | |
| controls: [], | |
| created: '', | |
| body: 'To plan, created by Insurer', | |
| action_needed: 'pulse_wrap' | |
| }, | |
| { | |
| id: 7, | |
| element_status: 'main_element', | |
| icon_class: 'glyphicon glyphicon-log-out', | |
| icon_status: 'success', | |
| title: 'DELIVERY', | |
| controls: [], | |
| created: '', | |
| body: 'To plan, created by Insurer', | |
| action_needed: 'pulse_wrap' | |
| }, | |
| { | |
| id: 7, | |
| element_status: 'main_element', | |
| icon_class: 'glyphicon glyphicon-log-out', | |
| icon_status: 'success', | |
| title: 'DELIVERY', | |
| controls: [], | |
| created: 'Monday Oct. 4 - 10:00', | |
| body: 'In 16 days, created by you' | |
| }, | |
| { | |
| id: 6, | |
| icon_class: 'glyphicon glyphicon-wrench', | |
| icon_status: 'planning', | |
| title: 'REPAIR END', | |
| controls: [], | |
| created: 'Monday Oct. 3 - 8:00', | |
| body: 'In 15 days, expected in Planning' | |
| }, | |
| { | |
| id: 5, | |
| icon_class: 'glyphicon glyphicon-wrench', | |
| icon_status: 'planning', | |
| title: 'REPAIR START', | |
| controls: [], | |
| created: 'Monday Sept. 30 - 10:00', | |
| body: 'In 12 days, expected in Planning' | |
| }, | |
| { | |
| id: 4, | |
| icon_class: 'glyphicon glyphicon-log-in', | |
| icon_status: 'success', | |
| title: 'DROP-OFF', | |
| controls: [], | |
| created: 'Friday Sept. 24 - 14:30', | |
| body: 'In 3 days, created by you' | |
| }, | |
| { | |
| id: 3, | |
| element_day_marker: 'today', | |
| icon_class: 'glyphicon glyphicon-arrow-up', | |
| icon_status: 'warning', | |
| title: '', | |
| controls: [], | |
| created: '', | |
| body: '' | |
| }, | |
| { | |
| id: 2, | |
| element_day_marker: 'past', | |
| icon_class: 'glyphicon glyphicon-edit', | |
| icon_status: '', | |
| title: 'ESTIMATION', | |
| controls: [], | |
| created: 'Tuesday Sept. 20 - 15:00', | |
| body: 'Follow-up required, self-booking', | |
| action_needed: 'pulse_wrap' | |
| }, | |
| { | |
| id: 2, | |
| element_status: 'selected_past', | |
| element_day_marker: 'past', | |
| icon_class: 'glyphicon glyphicon-edit', | |
| icon_status: '', | |
| title: 'ESTIMATION', | |
| controls: [], | |
| created: 'Tuesday Sept. 20 - 15:00', | |
| body: 'Follow-up required, self-booking', | |
| action_needed: 'pulse_wrap' | |
| }, | |
| { | |
| id: 2, | |
| element_day_marker: 'past', | |
| icon_class: 'glyphicon glyphicon-edit', | |
| icon_status: '', | |
| title: 'ESTIMATION', | |
| controls: [], | |
| created: 'Tuesday Sept. 20 - 15:00', | |
| body: '4 days ago, self-booking' | |
| }, | |
| { | |
| id: 1, | |
| element_day_marker: 'past', | |
| icon_class: 'glyphicon glyphicon-edit', | |
| icon_status: '', | |
| text_status: 'cancelled', | |
| title: 'ESTIMATION', | |
| controls: [], | |
| created: 'Monday Sept. 16 - 15:00', | |
| body: 'Cancelled, created by Desjardins' | |
| }, | |
| { | |
| id: 1, | |
| element_day_marker: 'past', | |
| icon_class: 'glyphicon glyphicon-edit', | |
| icon_status: '', | |
| text_status: 'cancelled', | |
| title: 'ESTIMATION', | |
| controls: [], | |
| created: '', | |
| body: 'Cancelled, created by Desjardins' | |
| }, | |
| { | |
| id: 7, | |
| element_status: 'selected_past', | |
| icon_class: 'glyphicon glyphicon-log-out', | |
| icon_status: 'past', | |
| title: 'ESTIMATION', | |
| controls: [], | |
| created: 'Monday Oct. 4 - 10:00', | |
| body: '16 days ago, created by you' | |
| }, | |
| ] | |
| }, | |
| events: { | |
| 'timeline-delete-item': function(id) { | |
| this.timeline = _.remove(this.timeline, function(item) { | |
| return item.id != id | |
| }); | |
| } | |
| } | |
| }) |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.js"></script> |
| @import 'https://fonts.googleapis.com/css?family=Libre+Franklin'; | |
| body { | |
| font-family: 'Libre Franklin', sans-serif; | |
| } | |
| #timeline-header { | |
| font-size: 26px; | |
| } | |
| .cancelled { | |
| text-decoration: line-through; | |
| } | |
| .timeline-panel.today { | |
| height: 5px !important; | |
| padding-top: 0px !important; | |
| padding-bottom: 0px !important; | |
| margin-top: 0px; | |
| margin-bottom: 10px; | |
| background: #000; | |
| &:before { | |
| visibility: hidden !important; | |
| display: none !important; | |
| } | |
| &:after { | |
| visibility: hidden !important; | |
| display: none !important; | |
| } | |
| } | |
| .timeline-badge.warning { | |
| top: -20px !important; | |
| } | |
| .timeline-panel.past { | |
| background: #eee; | |
| &:after { | |
| border-right: 14px solid #eee !important; | |
| } | |
| } | |
| .timeline-panel.main_element { | |
| font-weight: bolder; | |
| color: #FFFFFF !important; | |
| background: #0196a3; | |
| border-color: #0196a3 !important; | |
| &:after { | |
| border-right: 14px solid #0196a3 !important; | |
| } | |
| } | |
| .timeline-panel.selected_past { | |
| font-weight: bolder; | |
| color: #FFFFFF !important; | |
| background: #333; | |
| border-color: #333 !important; | |
| &:after { | |
| border-right: 14px solid #333 !important; | |
| } | |
| } | |
| .timeline { | |
| list-style: none; | |
| padding: 10px 0 10px; | |
| position: relative; | |
| width: 420px; | |
| &:before { | |
| background-color: #eee; | |
| bottom: 0; | |
| content: " "; | |
| left: 50px; | |
| margin-left: -1.5px; | |
| position: absolute; | |
| top: 0; | |
| width: 3px; | |
| } | |
| > li { | |
| margin-bottom: 10px; | |
| position: relative; | |
| &:before, | |
| &:after { | |
| content: " "; | |
| display: table; | |
| } | |
| &:after { | |
| clear: both; | |
| } | |
| > .timeline-panel { | |
| border-radius: 2px; | |
| border: 1px solid #d4d4d4; | |
| box-shadow: 0 1px 2px rgba(100, 100, 100, 0.2); | |
| margin-left: 100px; | |
| padding: 20px; | |
| position: relative; | |
| .timeline-heading { | |
| .timeline-panel-controls { | |
| position: absolute; | |
| right: 8px; | |
| top: 5px; | |
| .timestamp { | |
| display: inline-block; | |
| } | |
| .controls { | |
| display: inline-block; | |
| padding-right: 5px; | |
| border-right: 1px solid #aaa; | |
| a { | |
| color: #999; | |
| font-size: 11px; | |
| padding: 0 5px; | |
| &:hover { | |
| color: #333; | |
| text-decoration: none; | |
| cursor: pointer; | |
| } | |
| } | |
| } | |
| .controls + .timestamp { | |
| padding-left: 5px; | |
| } | |
| } | |
| } | |
| } | |
| .timeline-badge { | |
| background-color: #999; | |
| border-radius: 50%; | |
| color: #fff; | |
| font-size: 1.4em; | |
| height: 50px; | |
| left: 50px; | |
| line-height: 52px; | |
| margin-left: -25px; | |
| position: absolute; | |
| text-align: center; | |
| top: 16px; | |
| width: 50px; | |
| z-index: 100; | |
| } | |
| .timeline-badge + .timeline-panel { | |
| &:before { | |
| border-bottom: 15px solid transparent; | |
| border-left: 0 solid #ccc; | |
| border-right: 15px solid #ccc; | |
| border-top: 15px solid transparent; | |
| content: " "; | |
| display: inline-block; | |
| position: absolute; | |
| left: -15px; | |
| right: auto; | |
| top: 26px; | |
| } | |
| &:after { | |
| border-bottom: 14px solid transparent; | |
| border-left: 0 solid #fff; | |
| border-right: 14px solid #fff; | |
| border-top: 14px solid transparent; | |
| content: " "; | |
| display: inline-block; | |
| position: absolute; | |
| left: -14px; | |
| right: auto; | |
| top: 27px; | |
| } | |
| } | |
| } | |
| } | |
| .timeline-badge { | |
| &.primary { | |
| background-color: #2e6da4 !important; | |
| } | |
| &.success { | |
| background-color: #0196a3 !important; | |
| } | |
| &.warning { | |
| background-color: #000000 !important; | |
| } | |
| &.danger { | |
| background-color: #d9534f !important; | |
| } | |
| &.info { | |
| background-color: #5bc0de !important; | |
| } | |
| &.planning { | |
| background-color: #00629c !important; | |
| } | |
| } | |
| .timeline-title { | |
| margin-top: 0; | |
| color: inherit; | |
| } | |
| .timeline-body { | |
| > p, | |
| > ul { | |
| margin-bottom: 0; | |
| } | |
| > p + p { | |
| margin-top: 5px; | |
| } | |
| } | |
| .copy { | |
| position: absolute; | |
| top: 5px; | |
| right: 5px; | |
| color: #aaa; | |
| font-size: 11px; | |
| > * { color: #444; } | |
| } | |
| /*============================ | |
| ANIMATIONS | |
| =============================*/ | |
| .pulse_wrap { | |
| animation: pulse 1.5s ease-in-out alternate infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { | |
| opacity: 0.8; | |
| transform: scale(0.95); | |
| //margin-left: -20px; | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| } | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" /> |