@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<div class="events--day" data-controller="related-element"
|
||||
<section class="events__day" data-controller="related-element"
|
||||
data-related-element-highlight-class="event--related">
|
||||
<% if day_timeline.has_activity? %>
|
||||
<h2 class="txt-medium events__day-header center">
|
||||
<span class="events__day-header-content">
|
||||
<%= local_datetime_tag day_timeline.day, style: :agoorweekday, class: "txt-ink" %>
|
||||
</span>
|
||||
<h2 class="events__day-header">
|
||||
<%= local_datetime_tag day_timeline.day, style: :agoorweekday, class: "txt-ink" %>
|
||||
</h2>
|
||||
|
||||
<div class="center events">
|
||||
<%= render_column_headers(day_timeline) %>
|
||||
<%= render_event_grid_cells %>
|
||||
<%= render "events/day_timeline", day_timeline: day_timeline %>
|
||||
<div class="events__columns">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
|
||||
<%= render "events/empty_days", day_timeline: day_timeline %>
|
||||
<% else %>
|
||||
<%= render "events/empty_days", day_timeline: day_timeline %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<% column = event_columns(event_type, day_timeline) %>
|
||||
|
||||
<div class="events__column">
|
||||
<h3 class="events__column-header"><%= column[:title] %></h3>
|
||||
<% 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 %>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<% day_timeline.events.group_by { event_column(it) }.each do |column, events_grouped_by_column| %>
|
||||
<div class="events__column">
|
||||
<h2 class="events__column-header"><%= column %></h2>
|
||||
<% 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
<% if earliest.present? %>
|
||||
<% if earliest == latest %>
|
||||
<div class="events--none txt-align-center">
|
||||
<div class="events__none">
|
||||
<h2 class="txt-medium margin-none"><%= local_datetime_tag earliest, style: :agoorweekday, class: "txt-ink txt-capitalize" %></h2>
|
||||
<p class="margin-none">No activity</p>
|
||||
</div>
|
||||
<% elsif earliest < latest %>
|
||||
<div class="events--none txt-align-center">
|
||||
<div class="events__none">
|
||||
<h2 class="txt-medium margin-none"><%= local_datetime_tag earliest, style: :agoorweekday, class: "txt-ink txt-capitalize" %> – <%= local_datetime_tag latest, style: :agoorweekday, class: "txt-ink txt-capitalize" %></h2>
|
||||
<p class="margin-none">No activity for <%= (latest - earliest).seconds.in_days.round + 1 %> days</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="events--none txt-align-center">
|
||||
<div class="events__none">
|
||||
<p class="margin-none">No more activity.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<nav class="align-start">
|
||||
<div class="btn btn--circle btn--placeholder flex-item-justify-start"></div>
|
||||
|
||||
<div class="events__index-header flex-inline flex-column center gap-half margin-block-end-double txt-align-center">
|
||||
<h2 class="txt-large txt-nowrap center"><%= Account.sole.name %> / Latest activity</h2>
|
||||
<div class="flex-inline flex-column center gap-half margin-block-end-double txt-align-center min-width">
|
||||
<h2 class="font-black txt-large center overflow-ellipsis max-width"><%= Account.sole.name %> / Latest activity</h2>
|
||||
<%= render "events/filter", filter: @filter %>
|
||||
</div>
|
||||
|
||||
@@ -16,11 +16,7 @@
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<div class="events__container align-start gap">
|
||||
<div class="flex flex-column">
|
||||
<div id="activity" class="events__index flex flex-column">
|
||||
<%= render "events/day", day_timeline: @day_timeline %>
|
||||
</div>
|
||||
<%= event_next_page_link(@day_timeline.next_day) %>
|
||||
</div>
|
||||
<div class="events" id="activity">
|
||||
<%= render "events/day", day_timeline: @day_timeline %>
|
||||
<%= event_next_page_link(@day_timeline.next_day) %>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user