Skip to content

Commit 6c04a2d

Browse files
authored
fix: cover photo fix and spacing tweaks (#4350)
1 parent 602f2ac commit 6c04a2d

25 files changed

Lines changed: 439 additions & 49 deletions
Lines changed: 1 addition & 1 deletion
Loading

app/assets/stylesheets/application.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,15 @@
223223
@apply ms-0 col-start-2;
224224
}
225225
}
226+
227+
.panel-spacer {
228+
@apply relative flex flex-col gap-y-12;
229+
230+
&>[data-component="avo/ui/panel_header_component"],
231+
&>[data-component="avo/cover_component"] {
232+
@apply -mb-8; /* Offset from the regular panel spacing */
233+
}
234+
}
226235
}
227236

228237
.loading-spinner {

app/assets/stylesheets/css/components/grid.css

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,34 @@
22
/* BEM Methodology: .grid-card (block), .grid-card__element (elements) */
33

44
.grid-wrapper {
5-
@apply w-full grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 gap-4 mt-8;
5+
/*
6+
* Fluid column count from the *container* (cqw), not the viewport.
7+
* Nearest `container-type` ancestor: e.g. `@container` on the grid turbo-frame.
8+
* `--grid-max-cols`: cap columns as if the row were evenly split (incl. gaps).
9+
* `--grid-min-col`: floor track width so cards never shrink below `min-w-52`.
10+
*/
11+
--grid-max-cols: 12;
12+
--grid-min-col: 13rem; /* 52 * 0.25rem, matches .grid-card min-w-52 */
13+
--grid-gap: 1rem; /* gap-4 */
14+
15+
@apply w-full gap-4;
16+
display: grid;
17+
grid-template-columns: repeat(
18+
auto-fill,
19+
minmax(
20+
min(
21+
100%,
22+
max(
23+
var(--grid-min-col),
24+
calc(
25+
(100cqw - (var(--grid-max-cols) - 1) * var(--grid-gap)) /
26+
var(--grid-max-cols)
27+
)
28+
)
29+
),
30+
1fr
31+
)
32+
);
633
}
734

835
.grid-card {
@@ -59,3 +86,35 @@
5986
.grid-card__description {
6087
@apply text-content-secondary text-xs font-normal self-stretch leading-4;
6188
}
89+
90+
@keyframes icon-float {
91+
0%, 100% { translate: 0 0; }
92+
50% { translate: 0 -5px; }
93+
}
94+
95+
.grid-card__icon {
96+
@apply absolute size-4 pointer-events-none;
97+
transform: translate(var(--tx, 0px), var(--ty, 0px));
98+
transition: transform 0.5s ease-out;
99+
}
100+
101+
.grid-card__icon--repelling {
102+
transition: transform 0.08s ease-out;
103+
}
104+
105+
.grid-card__avocado {
106+
@apply block;
107+
transform: translate(var(--tx, 0px), var(--ty, 0px));
108+
transition: transform 0.5s ease-out;
109+
110+
&.grid-card__icon--repelling {
111+
transition: transform 0.08s ease-out;
112+
}
113+
}
114+
115+
.grid-card__icon--float {
116+
animation-name: icon-float;
117+
animation-timing-function: ease-in-out;
118+
animation-iteration-count: infinite;
119+
animation-fill-mode: none;
120+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
<%= tag.div class: class_names("related w-full flex overflow-hidden rounded-2xl", size_class) do %>
2-
<%= image_tag helpers.main_app.url_for(@cover.value), class: class_names("w-full object-cover self-center"), data: {component: self.class.to_s.underscore} %>
1+
<%= tag.div class: class_names("related w-full flex overflow-hidden rounded-2xl", size_class), data: {component: component_name} do %>
2+
<%= image_tag helpers.main_app.url_for(@cover.value), class: class_names("w-full object-cover self-center") %>
33
<% end %>
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1-
<div class="absolute bg-gray-50 size-full content-center">
2-
<%= image_tag Avo.configuration.branding.placeholder, class: "relative h-20 text-gray-400 mx-auto", loading: :lazy %>
1+
<div
2+
class="absolute bg-secondary dark:bg-tertiary size-full content-center"
3+
data-controller="grid-cover-empty-state"
4+
data-action="mousemove->grid-cover-empty-state#mousemove mouseleave->grid-cover-empty-state#mouseleave"
5+
>
6+
<%# Decorative icons — purely visual, hidden from screen readers %>
7+
<% icon_items.each do |icon| %>
8+
<span
9+
aria-hidden="true"
10+
class="grid-card__icon grid-card__icon--float"
11+
data-grid-cover-empty-state-target="icon"
12+
style="<%= icon[:v_edge] %>: <%= icon[:v_px] %>px; <%= icon[:h_edge] %>: <%= icon[:h_px] %>px; rotate: <%= icon[:rotation] %>deg; animation-duration: <%= icon[:duration] %>s; animation-delay: <%= icon[:delay] %>s; color: color-mix(in srgb, var(--color-content) <%= icon[:intensity] %>%, transparent);"
13+
><%= helpers.svg icon[:path], class: "size-4" %></span>
14+
<% end %>
15+
16+
<span data-grid-cover-empty-state-target="icon" data-repulsion-scale="0.125" class="grid-card__avocado"><%= svg Avo.configuration.branding.placeholder, class: "mx-auto h-20 text-content-secondary" %></span>
317
</div>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,94 @@
11
# frozen_string_literal: true
22

33
class Avo::Index::GridCoverEmptyStateComponent < Avo::BaseComponent
4+
ICONS = %w[
5+
tabler/outline/star
6+
tabler/outline/bolt
7+
tabler/outline/clock
8+
tabler/outline/heart
9+
tabler/outline/leaf
10+
tabler/outline/sparkles
11+
tabler/outline/rocket
12+
tabler/outline/diamond
13+
].freeze
14+
15+
# Per-icon animation timing and color intensity (stable across all layouts)
16+
ICON_META = [
17+
{duration: 3.8, delay: 0.0, intensity: 15},
18+
{duration: 4.2, delay: 0.4, intensity: 18},
19+
{duration: 3.5, delay: 0.8, intensity: 13},
20+
{duration: 4.6, delay: 1.2, intensity: 20},
21+
{duration: 3.2, delay: 1.6, intensity: 16},
22+
{duration: 4.8, delay: 2.0, intensity: 14},
23+
{duration: 3.6, delay: 2.4, intensity: 19},
24+
{duration: 4.4, delay: 2.8, intensity: 17}
25+
].freeze
26+
27+
# 5 preset spatial layouts — each is an array of 8 position+rotation hashes.
28+
# v_edge: "top"|"bottom", h_edge: CSS logical "inset-inline-start"|"inset-inline-end"
29+
# v_px / h_px: distance in px from that edge. rotation: degrees.
30+
LAYOUTS = [
31+
# Layout 1 — scattered, original feel
32+
[
33+
{v_edge: "top", v_px: 28, h_edge: "inset-inline-start", h_px: 28, rotation: 12},
34+
{v_edge: "top", v_px: 32, h_edge: "inset-inline-end", h_px: 32, rotation: -12},
35+
{v_edge: "top", v_px: 72, h_edge: "inset-inline-start", h_px: 24, rotation: 0},
36+
{v_edge: "bottom", v_px: 28, h_edge: "inset-inline-start", h_px: 44, rotation: 6},
37+
{v_edge: "top", v_px: 40, h_edge: "inset-inline-start", h_px: 72, rotation: -6},
38+
{v_edge: "bottom", v_px: 32, h_edge: "inset-inline-end", h_px: 32, rotation: 12},
39+
{v_edge: "top", v_px: 28, h_edge: "inset-inline-end", h_px: 52, rotation: -6},
40+
{v_edge: "bottom", v_px: 28, h_edge: "inset-inline-end", h_px: 60, rotation: 6}
41+
],
42+
# Layout 2 — corners and mid-edges
43+
[
44+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-start", h_px: 20, rotation: -8},
45+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-end", h_px: 20, rotation: 8},
46+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-start", h_px: 20, rotation: 8},
47+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-end", h_px: 20, rotation: -8},
48+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-start", h_px: 68, rotation: 0},
49+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-start", h_px: 68, rotation: 0},
50+
{v_edge: "top", v_px: 60, h_edge: "inset-inline-start", h_px: 20, rotation: 12},
51+
{v_edge: "top", v_px: 60, h_edge: "inset-inline-end", h_px: 20, rotation: -12}
52+
],
53+
# Layout 3 — diagonal scatter
54+
[
55+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-start", h_px: 36, rotation: 15},
56+
{v_edge: "top", v_px: 48, h_edge: "inset-inline-end", h_px: 24, rotation: -10},
57+
{v_edge: "top", v_px: 66, h_edge: "inset-inline-start", h_px: 28, rotation: 5},
58+
{v_edge: "bottom", v_px: 66, h_edge: "inset-inline-end", h_px: 28, rotation: -5},
59+
{v_edge: "bottom", v_px: 48, h_edge: "inset-inline-start", h_px: 24, rotation: 10},
60+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-end", h_px: 36, rotation: -15},
61+
{v_edge: "top", v_px: 36, h_edge: "inset-inline-start", h_px: 72, rotation: 8},
62+
{v_edge: "bottom", v_px: 36, h_edge: "inset-inline-end", h_px: 60, rotation: -8}
63+
],
64+
# Layout 4 — even frame
65+
[
66+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-start", h_px: 44, rotation: 12},
67+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-end", h_px: 44, rotation: -12},
68+
{v_edge: "top", v_px: 60, h_edge: "inset-inline-start", h_px: 20, rotation: 0},
69+
{v_edge: "top", v_px: 60, h_edge: "inset-inline-end", h_px: 20, rotation: 0},
70+
{v_edge: "bottom", v_px: 60, h_edge: "inset-inline-start", h_px: 20, rotation: 6},
71+
{v_edge: "bottom", v_px: 60, h_edge: "inset-inline-end", h_px: 20, rotation: -6},
72+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-start", h_px: 44, rotation: -12},
73+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-end", h_px: 44, rotation: 12}
74+
],
75+
# Layout 5 — asymmetric cluster
76+
[
77+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-start", h_px: 20, rotation: 8},
78+
{v_edge: "top", v_px: 40, h_edge: "inset-inline-start", h_px: 52, rotation: -8},
79+
{v_edge: "top", v_px: 24, h_edge: "inset-inline-end", h_px: 36, rotation: 12},
80+
{v_edge: "top", v_px: 66, h_edge: "inset-inline-end", h_px: 20, rotation: -5},
81+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-start", h_px: 36, rotation: -12},
82+
{v_edge: "bottom", v_px: 52, h_edge: "inset-inline-start", h_px: 20, rotation: 8},
83+
{v_edge: "bottom", v_px: 24, h_edge: "inset-inline-end", h_px: 20, rotation: 10},
84+
{v_edge: "bottom", v_px: 40, h_edge: "inset-inline-end", h_px: 52, rotation: -10}
85+
]
86+
].freeze
87+
88+
def icon_items
89+
layout = LAYOUTS.sample
90+
ICONS.each_with_index.map do |path, i|
91+
ICON_META[i].merge(layout[i]).merge(path: path)
92+
end
93+
end
494
end

app/components/avo/items/switcher_component.html.erb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@
3030
<%= render Avo::Collaborate::TimelineComponent.new(resource: @resource) %>
3131
<% elsif item.is_header? %>
3232
<% if @resource.cover.present? && @resource.cover.visible_in_current_view? %>
33-
<div class="mb-6">
34-
<%= render Avo::CoverComponent.new cover: @resource.cover %>
35-
</div>
33+
<%= render Avo::CoverComponent.new cover: @resource.cover %>
3634
<% end %>
3735

38-
<%= render ui.panel_header(title:, description: @resource.description, index:, class: "!-mb-8") do |header| %>
36+
<%= render ui.panel_header(title:, description: @resource.description, index:) do |header| %>
3937
<% header.with_controls do %>
4038
<% controls.each do |control| %>
4139
<%= render_control control %>

app/components/avo/u_i/panel_header_component.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"header",
44
"header--size-medium": size_medium?,
55
"header--size-small": size_small?
6-
), data: { "item-index": @index }.compact_blank do %>
6+
), data: { "item-index": @index, component: component_name }.compact_blank do %>
77
<div class="header__main">
88
<div class="header__content">
99
<% if avatar? %>

app/components/avo/view_types/grid_component.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<turbo-frame id="<%= @resource.model_key %>_list" target="_top" class="relative flex w-full flex-col">
1+
<turbo-frame id="<%= @resource.model_key %>_list" target="_top" class="relative flex w-full flex-col @container">
22
<%= content_tag :div,
3-
class:"grid-wrapper",
3+
class: "grid-wrapper",
44
data: {
55
component_name: self.class.to_s.underscore,
66
selected_resources_name: @resource.model_key,

app/components/avo/views/resource_edit_component.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<%= build_form do |form| %>
1414
<%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
1515

16-
<%= content_tag :div, class: "flex flex-col gap-y-12" do %>
16+
<%= content_tag :div, class: "panel-spacer" do %>
1717
<% @resource.get_items.each_with_index do |item, index| %>
1818
<%= render Avo::Items::SwitcherComponent.new(
1919
resource: @resource,

0 commit comments

Comments
 (0)