diff --git a/app/components/npq_separation/timeline_component.rb b/app/components/npq_separation/timeline_component.rb new file mode 100644 index 0000000000..378e73efe7 --- /dev/null +++ b/app/components/npq_separation/timeline_component.rb @@ -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 diff --git a/app/views/npq_separation/admin/applications/show.html.erb b/app/views/npq_separation/admin/applications/show.html.erb index 2fe33cf3a2..f738a524bc 100644 --- a/app/views/npq_separation/admin/applications/show.html.erb +++ b/app/views/npq_separation/admin/applications/show.html.erb @@ -83,3 +83,7 @@ end end %> + +

Timeline

+ +<%= render NpqSeparation::TimelineComponent.new(@application.events) %> diff --git a/app/webpacker/styles/application.scss b/app/webpacker/styles/application.scss index ee5ecc0d89..30ce72f405 100644 --- a/app/webpacker/styles/application.scss +++ b/app/webpacker/styles/application.scss @@ -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; diff --git a/app/webpacker/styles/timeline.scss b/app/webpacker/styles/timeline.scss new file mode 100644 index 0000000000..8ea0ac0622 --- /dev/null +++ b/app/webpacker/styles/timeline.scss @@ -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. + } +} diff --git a/spec/components/npq_separation/timeline_component_spec.rb b/spec/components/npq_separation/timeline_component_spec.rb new file mode 100644 index 0000000000..c3f3181f31 --- /dev/null +++ b/spec/components/npq_separation/timeline_component_spec.rb @@ -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 diff --git a/spec/factories/event.rb b/spec/factories/event.rb new file mode 100644 index 0000000000..0b38e210fc --- /dev/null +++ b/spec/factories/event.rb @@ -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