diff --git a/app/assets/stylesheets/_global.css b/app/assets/stylesheets/_global.css index f737200eb..37af77acf 100644 --- a/app/assets/stylesheets/_global.css +++ b/app/assets/stylesheets/_global.css @@ -33,6 +33,14 @@ /* Components */ --btn-size: 2.65em; + /* Z-index */ + --z-events-column-header: 1; + --z-events-day-header: 2; + --z-popup: 10; + --z-terminal: 20; + --z-tray: 21; + + /* OKLCH colors: Fixed */ --lch-black: 0% 0 0; --lch-white: 100% 0 0; diff --git a/app/assets/stylesheets/color-picker.css b/app/assets/stylesheets/color-picker.css index a0dd8e2af..69bcbe86d 100644 --- a/app/assets/stylesheets/color-picker.css +++ b/app/assets/stylesheets/color-picker.css @@ -10,7 +10,7 @@ max-inline-size: var(--panel-size) !important; position: absolute; translate: 0 -50%; - z-index: 2; + z-index: var(--z-popup); &[open] { display: flex; diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 7770a1bd8..68c61d5a7 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -1,43 +1,113 @@ @layer components { + /* Event column layout + /* ------------------------------------------------------------------------ */ + .events { - background-color: var(--color-ink-lighter); - border-block-start: var(--grid-lines) solid var(--color-ink-lighter); - border-inline-start: var(--grid-lines) solid var(--color-ink-lighter); - display: grid; - gap: 0 var(--grid-lines); - grid-template-columns: repeat(3, 1fr); - position: relative; - z-index: 0; + --events-gap: 1ch; + --events-border: 1px solid var(--color-ink-lighter); } - .events__container { - --column-gap: clamp(var(--inline-space), 3cqmin, calc(var(--inline-space) * 3)); - --grid-lines: 1px; + .events__day { + position: relative; - margin: auto; - max-inline-size: min(105ch, 100%); + @media (max-width: 639px) { + margin-block-end: calc(var(--events-gap) * 2); + } + } + + .events__day-header, + .events__column-header { + font-size: var(--text-small); + text-align: center; + text-transform: uppercase; } .events__day-header { - background-color: var(--color-canvas); - block-size: var(--grid-lines); - display: block; - position: relative; - z-index: 1; - } - - .events__day-header-content { background-color: var(--color-ink-lighter); border-radius: 0.2em; - display: inline-flex; - font-size: 0.8em; - inset: calc(var(--grid-lines) - 1em) auto -1em; + display: block; + inline-size: fit-content; line-height: 1.5; - padding: 0.2em 1.3em; + margin-inline: auto; + padding: 0.5ch 1.3em; position: absolute; - text-transform: uppercase; - transform: translateX(calc(-50% - var(--grid-lines) / 2)); - z-index: 2; + inset: 0 auto auto 50%; + translate: -50% -50%; + z-index: var(--z-events-day-header); + } + + .events__columns { + border-block-start: var(--events-border); + position: relative; + + @media (min-width: 640px) { + align-items: end; + border-inline: var(--events-border); + display: grid; + grid-template-columns: repeat(3, 1fr); + + /* Pseudo column borders since .events__column is display: contents */ + &:before, + &:after { + border-inline-start: var(--events-border); + content: ""; + inset-block: 0; + position: absolute; + z-index: var(--z-events-day-header); + } + + &:before { inset-inline-start: calc(100% / 3); } + &:after { inset-inline-end: calc(100% / 3); } + } + } + + .events__column { + @media (max-width: 639px) { + &:not(:has(.event)) { + display: none; + } + } + + @media (min-width: 640px) { + display: contents; + } + } + + .events__column-header { + background-color: var(--color-canvas); + grid-row-start: 2; + inset-block-start: calc(var(--events-gap) * -2); + margin-block-end: var(--events-gap); + padding-block: calc(var(--events-gap) * 3) var(--events-gap); + position: sticky; + z-index: var(--z-events-column-header); + } + + .events__time-block { + align-content: end; + display: grid; + gap: var(--events-gap); + justify-items: center; + margin: 0; + padding: 0; + + @media (min-width: 640px) { + padding-block-end: calc(var(--events-gap) * 3); + padding-inline: calc(var(--events-gap) * 2); + } + + .event { + grid-column-start: unset !important; + grid-row-start: unset !important; + } + } + + .events__none { + background-color: var(--color-canvas); + border-block-start: var(--events-border); + grid-column: 1 / -1; + padding-block: 3em; + text-align: center; } .events__filter-btn { @@ -46,27 +116,13 @@ margin: auto; } - .events__index { - --row-gap: var(--block-space); - } - - .events__index-header { - h2 { - font-weight: 900; - } - } - - .events--none { - background-color: var(--color-canvas); - border-block-start: var(--grid-lines) solid var(--color-ink-lightest); - padding-block: 3em; - margin: auto; - } - .events__popup { margin-block-start: var(--block-space-double); } + /* Event + /* ------------------------------------------------------------------------ */ + .event { --column-gap: 0.7ch; --panel-border-color: transparent; @@ -76,7 +132,11 @@ margin: var(--inline-space); position: relative; - z-index: 1; + /* z-index: 1; */ + + @media (max-width: 639px) { + --panel-size: 100%; + } } .event--related { @@ -84,7 +144,7 @@ --hover-size: 0.15rem; box-shadow: 0 0 0 var(--hover-size) var(--hover-color); - z-index: 2; + /* z-index: 2; */ } .event__collection { @@ -119,25 +179,11 @@ .event__title { --lines: 4; - + font-size: 1.1em; line-height: 1.2; } - .event__wrapper { - align-content: end; - display: grid; - gap: var(--block-space-half); - justify-items: center; - margin: var(--grid-lines) var(--grid-lines) calc(var(--grid-lines) * 6) var(--grid-lines); - padding: var(--block-space) var(--inline-space-double); - - .event { - grid-column-start: unset !important; - grid-row-start: unset !important; - } - } - .event__icon { aspect-ratio: 1; background-color: var(--card-color); diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index 6f7794b5b..3b376c072 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -10,14 +10,14 @@ max-inline-size: min(50ch, 90vw); position: absolute; transform: translateX(-50%); - z-index: 2; + z-index: var(--z-popup); &[open] { display: flex; } #header:has(&) { - max-inline-size: 100%; + max-inline-size: 100vw; position: relative; z-index: 5; @@ -41,7 +41,7 @@ .btn { --btn-background: transparent; } - + @media (any-hover: hover) { &:hover { background: var(--color-selected); @@ -110,7 +110,7 @@ .icon { margin-inline-end: 0.5em; } - + padding-inline-start: var(--inline-space-half); } diff --git a/app/assets/stylesheets/terminals.css b/app/assets/stylesheets/terminals.css index 3a20664a0..890f1fd11 100644 --- a/app/assets/stylesheets/terminals.css +++ b/app/assets/stylesheets/terminals.css @@ -9,7 +9,7 @@ inset: auto 0 0 0; padding: var(--block-space) calc(var(--tray-size) + var(--inline-space) * 3); position: fixed; - z-index: 1; + z-index: var(--z-terminal); @media (prefers-color-scheme: dark) { background-color: var(--color-black); diff --git a/app/assets/stylesheets/trays.css b/app/assets/stylesheets/trays.css index 6c21ab0b5..c87f95f1d 100644 --- a/app/assets/stylesheets/trays.css +++ b/app/assets/stylesheets/trays.css @@ -15,7 +15,7 @@ justify-content: end; padding: var(--block-space-half) var(--inline-space) calc(var(--block-space-half) / 2); position: fixed; - z-index: 2; + z-index: var(--z-tray); .tray__item { --offset: calc((var(--position) - 1) * (var(--block-space) * -1)); diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index a07d98422..1e87ca91c 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -26,6 +26,8 @@ .txt-capitalize { text-transform: capitalize; } .txt-link { color: var(--color-link); text-decoration: underline; } + .font-black { font-weight: 900; } + /* Flexbox and Grid */ .justify-end { justify-content: end; } .justify-start { justify-content: start; } diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index b00408cc3..f19209a07 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -1,4 +1,29 @@ module EventsHelper + def event_columns(event_type, day_timeline) + case event_type + when "added" + events = day_timeline.events.where(action: "card_published") + { + title: events.count > 0 ? "Added (#{events.count})" : "Added", + index: 1, + events: events + } + when "closed" + events = day_timeline.events.where(action: "card_closed") + { + title: events.count > 0 ? "Closed (#{events.count})" : "Closed", + index: 3, + events: events + } + else + { + title: "Updated", + index: 2, + events: day_timeline.events.where.not(action: [ "card_published", "card_closed" ]) + } + end + end + def event_column(event) case event.action when "card_closed" @@ -12,7 +37,7 @@ module EventsHelper def event_cluster_tag(hour, col, &) row = 25 - hour - tag.div class: "event__wrapper", style: "grid-area: #{row}/#{col}", & + tag.ul class: "events__time-block", style: "grid-area: #{row}/#{col}", & end def event_next_page_link(next_day) @@ -22,27 +47,6 @@ module EventsHelper end end - def render_event_grid_cells(columns: 4, rows: 24) - safe_join((2..rows + 1).map do |row| - (1..columns).map do |col| - tag.div class: class_names("event__grid-item"), style: "grid-area: #{row}/#{col};" - end - end.flatten) - end - - def render_column_headers(day_timeline) - headers = { - "Added" => day_timeline.events.where(action: "card_published").count, - "Updated" => nil, - "Closed" => day_timeline.events.where(action: "card_closed").count - } - - headers.map do |header, count| - title = count&.positive? ? "#{header} (#{count})" : header - content_tag(:h3, title, class: "event__grid-column-title position-sticky") - end.join.html_safe - end - def event_action_sentence(event) if event.action.comment_created? comment_event_action_sentence(event) diff --git a/app/views/events/_day.html.erb b/app/views/events/_day.html.erb index 91058f79f..b4d7c520f 100644 --- a/app/views/events/_day.html.erb +++ b/app/views/events/_day.html.erb @@ -1,20 +1,18 @@ -
<% if day_timeline.has_activity? %> -

- - <%= local_datetime_tag day_timeline.day, style: :agoorweekday, class: "txt-ink" %> - +

+ <%= local_datetime_tag day_timeline.day, style: :agoorweekday, class: "txt-ink" %>

-
- <%= render_column_headers(day_timeline) %> - <%= render_event_grid_cells %> - <%= render "events/day_timeline", day_timeline: day_timeline %> +
+ <%= render "events/day_timeline_column", event_type: "added", day_timeline: day_timeline %> + <%= render "events/day_timeline_column", event_type: "updated", day_timeline: day_timeline %> + <%= render "events/day_timeline_column", event_type: "closed", day_timeline: day_timeline %>
<%= render "events/empty_days", day_timeline: day_timeline %> <% else %> <%= render "events/empty_days", day_timeline: day_timeline %> <% end %> -
+ diff --git a/app/views/events/_day_timeline.html.erb b/app/views/events/_day_timeline.html.erb index 91933a709..244414d2a 100644 --- a/app/views/events/_day_timeline.html.erb +++ b/app/views/events/_day_timeline.html.erb @@ -2,7 +2,6 @@ <% events_grouped_by_hour.group_by { event_column(it) }.each do |column, events| %> <%= event_cluster_tag(hour, column) do %> <%= render partial: "events/event", collection: events %> - <%= local_datetime_tag events.first.created_at, class: "event__timestamp txt-small translucent" %> <% end %> <% end %> diff --git a/app/views/events/_day_timeline_column.html.erb b/app/views/events/_day_timeline_column.html.erb new file mode 100644 index 000000000..e93699848 --- /dev/null +++ b/app/views/events/_day_timeline_column.html.erb @@ -0,0 +1,11 @@ +<% column = event_columns(event_type, day_timeline) %> + +
+

<%= column[:title] %>

+ <% column[:events].group_by { it.created_at.hour }.each do |hour, events| %> + <%= event_cluster_tag(hour, column[:index]) do %> + <%= render partial: "events/event", collection: events %> + <%= local_datetime_tag events.first.created_at, class: "event__timestamp txt-small translucent" %> + <% end %> + <% end %> +
diff --git a/app/views/events/_day_timeline_columns.html.erb b/app/views/events/_day_timeline_columns.html.erb new file mode 100644 index 000000000..7295b1749 --- /dev/null +++ b/app/views/events/_day_timeline_columns.html.erb @@ -0,0 +1,11 @@ +<% day_timeline.events.group_by { event_column(it) }.each do |column, events_grouped_by_column| %> +
+

<%= column %>

+ <% events_grouped_by_column.group_by { it.created_at.hour }.each do |hour, events| %> + <%= event_cluster_tag(hour, column) do %> + <%= render partial: "events/event", collection: events %> + <%= local_datetime_tag events.first.created_at, class: "event__timestamp txt-small translucent" %> + <% end %> + <% end %> +
+<% end %> diff --git a/app/views/events/_empty_days.html.erb b/app/views/events/_empty_days.html.erb index 93be21058..98b8bd8a4 100644 --- a/app/views/events/_empty_days.html.erb +++ b/app/views/events/_empty_days.html.erb @@ -2,18 +2,18 @@ <% if earliest.present? %> <% if earliest == latest %> -
+

<%= local_datetime_tag earliest, style: :agoorweekday, class: "txt-ink txt-capitalize" %>

No activity

<% elsif earliest < latest %> -
+

<%= local_datetime_tag earliest, style: :agoorweekday, class: "txt-ink txt-capitalize" %> – <%= local_datetime_tag latest, style: :agoorweekday, class: "txt-ink txt-capitalize" %>

No activity for <%= (latest - earliest).seconds.in_days.round + 1 %> days

<% end %> <% else %> -
+

No more activity.

<% end %> diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 2c4678fde..254c9d33f 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -4,8 +4,8 @@ <% end %> -
-
-
- <%= render "events/day", day_timeline: @day_timeline %> -
- <%= event_next_page_link(@day_timeline.next_day) %> -
+
+ <%= render "events/day", day_timeline: @day_timeline %> + <%= event_next_page_link(@day_timeline.next_day) %>
diff --git a/test/controllers/events_controller_test.rb b/test/controllers/events_controller_test.rb index c5dcd3d0a..d89d953f4 100644 --- a/test/controllers/events_controller_test.rb +++ b/test/controllers/events_controller_test.rb @@ -11,7 +11,7 @@ class EventsControllerTest < ActionDispatch::IntegrationTest test "index" do get events_path - assert_select "div.event__wrapper[style='grid-area: 17/2']" do + assert_select "ul.events__time-block[style='grid-area: 17/2']" do assert_select "strong", text: "David assigned JZ to Layout is broken" end end @@ -21,7 +21,7 @@ class EventsControllerTest < ActionDispatch::IntegrationTest get events_path - assert_select "div.event__wrapper[style='grid-area: 22/2']" do + assert_select "ul.events__time-block[style='grid-area: 22/2']" do assert_select "strong", text: "David assigned JZ to Layout is broken" end end