mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
User flagged two regressions: account colors didn't match between the
goal preview-card avatar stack on the index and the funding-widget
rows on the show page, and the color-picker pen toggle on the new-goal
modal still felt too big.
Color matching:
- `AccountStackComponent` (index card) used
`Goals::AvatarComponent.color_for(account.name)` — MD5-of-name into
the 10-color palette.
- `FundingAccountsBreakdownComponent` (show page) recently switched to
`color_for(account.id.to_s)` — MD5-of-id.
- Same account, two surfaces, two different palette picks. Plus
either hashing scheme can collide within a multi-account goal
(palette has 10 colors).
Move ownership to the Goal model: `Goal#account_color_map` returns
`{ account_id => palette_hex }` for the goal's linked accounts. Sort
by `id` for a stable order across reloads, then assign
`palette[i % palette.size]`. Stable + collision-free up to 10
accounts in a single goal (a realistic upper bound — most goals
link 1-3).
Both consumers now read off the same source:
- `AccountStackComponent.new(accounts:, color_map:)` accepts a hash
and falls back to the name-hash if no map provided (kept for
callers that don't have a goal in scope yet).
- `FundingAccountsBreakdownComponent#color_for` reads
`goal.account_color_map[account.id]`.
- Goal card on index passes `goal.account_color_map` to the stack.
Pen toggle:
The new-goal color-picker pen sat in a `w-5 h-5` circle with a
`border` ring + `text-secondary` icon. The border + secondary text
weight kept it loud against the avatar even at 20px. Drop the
border, drop the size another step (`w-4 h-4`), recolor the icon
`text-subdued` + `hover:text-secondary` so the affordance recedes
when not interacted with. Position shifts from `-bottom-1 -right-1`
(8px overhang) to `-bottom-0.5 -right-0.5` (2px overhang) since the
smaller circle doesn't need the larger float. Icon swaps "pen" for
"pencil" (the more conventional edit indicator across Sure).
64 lines
3.3 KiB
Plaintext
64 lines
3.3 KiB
Plaintext
<div class="group relative bg-container rounded-xl shadow-border-xs hover:bg-surface-hover transition-colors p-6 <%= "opacity-75" if goal.paused? || goal.archived? %>"
|
|
data-goals-filter-target="card"
|
|
data-goal-name="<%= goal.name %>"
|
|
data-goal-status="<%= goal.display_status %>">
|
|
<div class="flex items-start gap-3">
|
|
<%= render Goals::AvatarComponent.new(goal: goal, size: "lg") %>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex items-center gap-2 mb-0.5">
|
|
<p class="text-base font-medium text-primary truncate">
|
|
<a href="<%= goal_path(goal) %>"
|
|
aria-label="<%= aria_label %>"
|
|
class="before:absolute before:inset-0 before:rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-100">
|
|
<%= goal.name %>
|
|
</a>
|
|
</p>
|
|
<%= render Goals::StatusPillComponent.new(goal: goal) %>
|
|
</div>
|
|
<p class="text-xs text-subdued truncate"><%= secondary_line %></p>
|
|
</div>
|
|
|
|
<div class="shrink-0 relative" style="width: <%= Goals::CardComponent::RING_SIZE %>px; height: <%= Goals::CardComponent::RING_SIZE %>px;">
|
|
<svg width="<%= Goals::CardComponent::RING_SIZE %>" height="<%= Goals::CardComponent::RING_SIZE %>" viewBox="0 0 <%= Goals::CardComponent::RING_SIZE %> <%= Goals::CardComponent::RING_SIZE %>" aria-hidden="true">
|
|
<circle cx="<%= Goals::CardComponent::RING_SIZE / 2.0 %>"
|
|
cy="<%= Goals::CardComponent::RING_SIZE / 2.0 %>"
|
|
r="<%= ring_radius %>"
|
|
fill="none"
|
|
stroke="var(--budget-unallocated-fill)"
|
|
stroke-width="<%= Goals::CardComponent::RING_STROKE %>" />
|
|
<circle cx="<%= Goals::CardComponent::RING_SIZE / 2.0 %>"
|
|
cy="<%= Goals::CardComponent::RING_SIZE / 2.0 %>"
|
|
r="<%= ring_radius %>"
|
|
fill="none"
|
|
stroke="<%= ring_color %>"
|
|
stroke-width="<%= Goals::CardComponent::RING_STROKE %>"
|
|
stroke-linecap="round"
|
|
stroke-dasharray="<%= ring_circumference %>"
|
|
stroke-dashoffset="<%= ring_offset %>"
|
|
transform="rotate(-90 <%= Goals::CardComponent::RING_SIZE / 2.0 %> <%= Goals::CardComponent::RING_SIZE / 2.0 %>)" />
|
|
</svg>
|
|
<div class="absolute inset-0 flex items-center justify-center text-[11px] font-medium text-primary tabular-nums" aria-hidden="true">
|
|
<%= progress_percent %>%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-5">
|
|
<div class="flex items-baseline gap-1.5">
|
|
<span class="text-xl font-medium text-primary tabular-nums privacy-sensitive"><%= goal.current_balance_money.format(precision: 0) %></span>
|
|
<span class="text-xs text-subdued tabular-nums">/ <%= goal.target_amount_money.format(precision: 0) %></span>
|
|
</div>
|
|
<% if pace_line %>
|
|
<p class="text-xs text-subdued tabular-nums mt-1"><%= pace_line %></p>
|
|
<% end %>
|
|
</div>
|
|
|
|
<div class="mt-4 flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<%= render Goals::AccountStackComponent.new(accounts: linked_accounts, color_map: goal.account_color_map) %>
|
|
<span class="text-xs text-subdued"><%= linked_accounts_count_label %></span>
|
|
</div>
|
|
<span class="text-xs text-subdued tabular-nums"><%= footer_line %></span>
|
|
</div>
|
|
</div>
|