Skip to content

Commit

Permalink
Add timeline component
Browse files Browse the repository at this point in the history
The timeline component accepts an array of events which are
ViewComponent slots (meaning it can be constructed manually too via
`with_item(event)`) although I don't see us using that.

It's intended to display a chronological list of events that happened to
a thing.
  • Loading branch information
peteryates committed May 14, 2024
1 parent 679cf5a commit 253f591
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 0 deletions.
61 changes: 61 additions & 0 deletions app/components/npq_separation/timeline_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module NpqSeparation
class TimelineComponent < ViewComponent::Base
renders_many :items, "ItemComponent"

attr_reader :events

def initialize(events)
@events = events.sort_by(&:created_at)

@events.each { |event| with_item(event) }
end

def call
tag.div(class: "app-timeline") do
safe_join(items)
end
end

class ItemComponent < ViewComponent::Base
attr_reader :event

def initialize(event)
@event = event
end

def call
tag.div(class: "app-timeline__item") do
safe_join([header, timestamp, description])
end
end

private

def header
tag.div(class: "app-timeline__header") do
safe_join([title, byline], " ")
end
end

def title
tag.h2(event.title, class: "app-timeline__title")
end

def timestamp
tag.p(class: "app-timeline__date") do
tag.time(event.created_at.to_fs(:govuk_short), datetime: event.created_at.to_fs(:iso8601))
end
end

def description
tag.div(class: "app-timeline__description") { event.description }
end

def byline
return if event.byline.blank?

tag.p("by #{event.byline}", class: "app-timeline__byline")
end
end
end
end
4 changes: 4 additions & 0 deletions app/views/npq_separation/admin/applications/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@
end
end
%>

<h2 class="govuk-heading-m govuk-!-margin-top-9">Timeline</h2>

<%= render NpqSeparation::TimelineComponent.new(@application.events) %>
1 change: 1 addition & 0 deletions app/webpacker/styles/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ $govuk-image-url-function: frontend-image-url;
@import "big-number";
@import "admin";
@import "api_guidance";
@import "timeline";

h1.govuk-fieldset__heading {
margin-bottom: 10px;
Expand Down
107 changes: 107 additions & 0 deletions app/webpacker/styles/timeline.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// timeline
//
// borrowed from the MoJ frontend library, moj- replaced with app- as
// we will likely customise this to suit our needs
//
// https://design-patterns.service.justice.gov.uk/components/timeline/
.app-timeline {
margin-bottom: govuk-spacing(4);
overflow: hidden;
position: relative;

&:before {
background-color: $govuk-brand-colour;
content: "";
height: 100%;
left: 0;
position: absolute;
top: govuk-spacing(2);
width: 5px;
}

}

.app-timeline--full {
margin-bottom: 0;
&:before {
height: calc(100% - 75px);
}
}

.app-timeline__item {
padding-bottom: govuk-spacing(6);
padding-left: govuk-spacing(4);
position: relative;

&:before {
background-color: $govuk-brand-colour;
content: "";
height: 5px;
left: 0;
position: absolute;
top: govuk-spacing(2);
width: 15px;
}

}

.app-timeline__title {
@include govuk-font($size: 19, $weight: bold);
display: inline;
}

.app-timeline__byline {
@include govuk-font($size: 19);
color: $govuk-secondary-text-colour;
display: inline;
margin: 0;
}

.app-timeline__date {
@include govuk-font($size: 16);
margin-top: govuk-spacing(1);
margin-bottom: 0;
}

.app-timeline__description {
@include govuk-font($size: 19);
margin-top: govuk-spacing(4);
}

.app-timeline__documents {
list-style: none;
margin-bottom: 0;
padding-left: 0;
}

.app-timeline__document-item {
margin-bottom: govuk-spacing(1);

&:last-child {
margin-bottom: 0;
}

}

.app-timeline__document-icon {
float: left;
margin-top: 4px;
margin-right: 4px;
fill: currentColor;

@media screen and (forced-colors: active) {
fill: linkText;
}
}

.app-timeline__document-link {
// background-image: url(#{$app-images-path}icon-document.svg);
background-repeat: no-repeat;
background-size: 20px 16px;
background-position: 0 50%;
padding-left: govuk-spacing(5);

&:focus {
color: govuk-colour("black"); // Focus colour on yellow should really be black.
}
}
36 changes: 36 additions & 0 deletions spec/components/npq_separation/timeline_component_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require "rails_helper"

RSpec.describe NpqSeparation::TimelineComponent, type: :component do
let(:one_day_ago) { FactoryBot.build(:event, :with_byline, created_at: 1.day.ago) }
let(:two_days_ago) { FactoryBot.build(:event, :with_byline, created_at: 2.days.ago) }
let(:three_days_ago) { FactoryBot.build(:event, :with_byline, created_at: 3.days.ago) }
let(:events) { [two_days_ago, one_day_ago, three_days_ago] }

subject { NpqSeparation::TimelineComponent.new(events) }

before { render_inline(subject) }

context "when the events aren't in choronological order" do
it "orders the events by created_at on initialization" do
expect(subject.events).to eql(events.sort_by(&:created_at))
end
end

it "displays all of the events in a timeline" do
expect(rendered_content).to have_css(".app-timeline__item", count: events.size)
end

it "shows a timestamp for each event" do
events.each do |event|
expect(rendered_content).to have_css("time", text: event.created_at.to_fs(:govuk_short))
expect(rendered_content).to have_css("time[datetime='#{event.created_at.to_fs(:iso8601)}']")
end
end

it "shows the title and byline in the header" do
events.each do |event|
expect(rendered_content).to have_css(".app-timeline__header > .app-timeline__title", text: event.title)
expect(rendered_content).to have_css(".app-timeline__header > .app-timeline__byline", text: event.byline)
end
end
end
10 changes: 10 additions & 0 deletions spec/factories/event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FactoryBot.define do
factory :event do
sequence(:title) { |n| "Event #{n}" }
sequence(:description) { |n| "Event #{n} description goes here" }
created_at { Time.zone.now }

trait(:with_random_description) { description { Faker::Lorem.paragraph } }
trait(:with_byline) { byline { Faker::Name.name } }
end
end

0 comments on commit 253f591

Please sign in to comment.