Files
sure/app/views/holdings/_cost_basis_cell.html.erb
Guillem Arias Fauste 99844c1b90 chore(design-system): swap raw gray classes for semantic tokens in holdings/ (#1654)
* chore(design-system): swap raw gray classes for semantic tokens in holdings/

Continues the raw-color sweep on the holdings/ domain plus the related
account activity feed component. 11 occurrences across 5 files.

Token additions:

- button-bg-secondary-strong (gray-200 / gray-700) and -hover (gray-300 /
  gray-600). Holdings CTAs (Add Trade, Add Holding, Edit Cost Basis,
  Sync Prices, etc.) used a hand-rolled "secondary-strong" pattern that
  doesn't match the existing button-bg-secondary token (which is gray-50
  / gray-700, much subtler). Adding the strong variant preserves the
  intentional visual weight of these CTAs and gives future PRs a name
  to reuse.
- $version bump 1.0.0 -> 1.1.0 (additive).

Mappings:

- 8x text-primary bg-gray-200 hover:bg-gray-300 theme-dark:bg-gray-700
  theme-dark:hover:bg-gray-600 (holdings/show + sync_prices +
  cost_basis_cell)
  -> text-primary button-bg-secondary-strong hover:button-bg-secondary-strong-hover
- 1x bg-gray-50 theme-dark:bg-gray-700 hover:bg-gray-100
  theme-dark:hover:bg-gray-600 (holdings/index search button)
  -> button-bg-secondary hover:button-bg-secondary-hover
- 1x hover:bg-gray-100 theme-dark:hover:bg-gray-700 (cost_basis_cell
  hover row)
  -> hover:bg-container-inset-hover
- 1x focus-within:border-gray-900 (activity_feed search wrapper)
  -> focus-within:border-primary

Left intentionally:

- bg-gray-300 status indicator dot in show.html.erb (same pattern as
  the settings pilot; no semantic equivalent for "neutral inactive
  indicator" yet).
- bg-gray-700 in _missing_price_tooltip.html.erb (already fixed in
  PR #1626; would conflict on rebase).
- focus-within:ring-gray-100 (subtle effect that works in both modes;
  ring-color tokens are a separate concern).

* chore(design-system): bump $version to 2.1.0 for additive token additions

Per the design tokens semver contract: PR #1626 already bumped to 2.0.0
(major / breaking when fg-* utilities were removed). This PR adds
button-bg-secondary-strong + hover without removing or changing existing
tokens, so the correct bump is minor (2.0.0 → 2.1.0).

Spotted by CodeRabbit on the rebased branch.

* fix(design-system): drop dead focus-within:ring-gray-100 on activity feed search

The focus-within:ring-gray-100 class only sets --tw-ring-color, but the
parent has no ring-width utility, so it produces no visible ring — dead
code from before the focus-within:border-primary swap landed.

Same issue spotted on app/views/accounts/show/_activity.html.erb in the
finalize sweep PR; applying the equivalent fix here for the holdings
activity feed component.

---------

Signed-off-by: Guillem Arias Fauste <gariasf@proton.me>
2026-05-04 21:44:47 +02:00

110 lines
5.4 KiB
Plaintext

<%# locals: (holding:, editable: true) %>
<%
# Pre-calculate values for the form
# Note: cost_basis field stores per-share cost, so calculate total for display
current_per_share = holding.cost_basis.present? && holding.cost_basis.positive? ? holding.cost_basis : nil
current_total = current_per_share && holding.qty.positive? ? (current_per_share * holding.qty).round(2) : nil
currency = Money::Currency.new(holding.currency)
%>
<%= turbo_frame_tag dom_id(holding, :cost_basis) do %>
<% if holding.cost_basis_locked? && !editable %>
<%# Locked and not editable (from holdings list) - just show value, right-aligned %>
<div class="flex items-center justify-end gap-1">
<%= tag.span format_money(holding.avg_cost) %>
<%= icon "lock", size: "xs", class: "text-secondary" %>
</div>
<% else %>
<%# Unlocked OR editable context (drawer) - show clickable menu %>
<%= render DS::Menu.new(variant: :button, placement: "bottom-end") do |menu| %>
<% menu.with_button(class: "hover:text-primary cursor-pointer group") do %>
<% if holding.avg_cost %>
<div class="flex items-center gap-1">
<%= tag.span format_money(holding.avg_cost) %>
<% if holding.cost_basis_locked? %>
<%= icon "lock", size: "xs", class: "text-secondary" %>
<% end %>
<%= icon "pencil", size: "xs", class: "text-secondary opacity-0 group-hover:opacity-100 transition-opacity" %>
</div>
<% else %>
<div class="flex items-center gap-1 px-2 py-0.5 rounded text-secondary hover:text-primary hover:bg-container-inset-hover transition-colors">
<%= icon "pencil", size: "xs" %>
<span class="text-xs">Set</span>
</div>
<% end %>
<% end %>
<% menu.with_custom_content do %>
<div class="p-4 min-w-[280px]"
data-controller="cost-basis-form"
data-cost-basis-form-qty-value="<%= holding.qty %>">
<h4 class="font-medium text-sm mb-3">
<%= t(".set_cost_basis_header", ticker: holding.ticker, qty: format_quantity(holding.qty)) %>
</h4>
<%
form_data = { turbo: false }
if holding.avg_cost
form_data[:turbo_confirm] = {
title: t(".overwrite_confirm_title"),
body: t(".overwrite_confirm_body", current: format_money(holding.avg_cost))
}
end
%>
<%= styled_form_with model: holding,
url: holding_path(holding),
method: :patch,
class: "space-y-3",
data: form_data do |f| %>
<!-- Primary: Total cost basis (custom input, no spinners) -->
<div class="form-field">
<div class="form-field__body">
<label class="form-field__label"><%= t(".total_cost_basis_label") %></label>
<div class="flex items-center gap-1">
<span class="text-secondary text-sm font-medium"><%= currency.symbol %></span>
<input type="number" step="any"
name="holding[cost_basis]"
class="form-field__input grow"
placeholder="0.00"
autocomplete="off"
value="<%= sprintf("%.2f", current_total) if current_total %>"
data-action="input->cost-basis-form#updatePerShare"
data-cost-basis-form-target="total">
<span class="text-secondary text-sm"><%= currency.iso_code %></span>
</div>
</div>
</div>
<p class="text-xs text-secondary -mt-2" data-cost-basis-form-target="perShareDisplay">
= <%= currency.symbol %><span data-cost-basis-form-target="perShareValue"><%= number_with_precision(current_per_share, precision: 2) || "0.00" %></span> <%= t(".per_share") %>
</p>
<!-- Alternative: Per-share input -->
<div class="pt-2 border-t border-tertiary">
<label class="text-xs text-secondary block mb-1"><%= t(".or_per_share_label") %></label>
<div class="flex items-center gap-1">
<span class="text-secondary text-sm font-medium"><%= currency.symbol %></span>
<input type="number" step="any"
class="form-field__input grow"
placeholder="0.00"
autocomplete="off"
value="<%= sprintf("%.2f", current_per_share) if current_per_share %>"
data-action="input->cost-basis-form#updateTotal"
data-cost-basis-form-target="perShare">
<span class="text-secondary text-sm"><%= currency.iso_code %></span>
</div>
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button"
class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium text-primary button-bg-secondary-strong hover:button-bg-secondary-strong-hover"
data-action="click->DS--menu#close">
<%= t(".cancel") %>
</button>
<%= f.submit t(".save"), class: "inline-flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium text-inverse bg-inverse hover:bg-inverse-hover" %>
</div>
<% end %>
</div>
<% end %>
<% end %>
<% end %>
<% end %>