Files
sure/app/views/settings/providers/_mercury_panel.html.erb
Guillem Arias Fauste 547dd21bf2 feat(design-system): migrate 3 provider panels to DS::Disclosure :card (#1715 §6) (#1856)
* feat(design-system): DS::Disclosure :card variant + migrate 14 provider items

Resolves part of #1715 §6. The provider-item view templates
(binance, brex, coinbase, coinstats, enable_banking, ibkr,
indexa_capital, kraken, lunchflow, mercury, plaid, simplefin,
snaptrade, sophtron — 14 in total) all hand-rolled the same
`<details open class="group bg-container p-4 shadow-border-xs
rounded-xl">` shell with a custom summary inside and content below.

Extend `DS::Disclosure` with a `:card` variant that bakes the card
chrome onto the `<details>` element itself; the summary becomes
slot-driven via the existing `summary_content` slot. Provider items
keep their custom summary content (logos, brand colors, status copy)
unchanged — they just hand it to the slot instead of writing it
between `<summary>` tags.

API:

  DS::Disclosure.new(variant: :card, open: true) do |d|
    d.with_summary_content do
      <div class="flex items-center gap-2">
        chevron + custom summary markup
      </div>
    end
    body content
  end

While here:

- Drop the no-op `group-open:transform` from the default chevron
  (Tailwind v4 applies `rotate-90` directly).
- Add `motion-safe:transition-transform motion-safe:duration-150`
  to chevron rotation for reduced-motion respect (matches the
  pattern landing in #1841).
- Extract `summary_classes` / `details_classes` helpers so the
  default and card surfaces stay readable side-by-side.

Note: this PR touches `DS::Disclosure` and will textually conflict
with #1841 (focus-ring + reduced-motion polish). Both changes are
compatible — when #1841 merges first, the resolution is just
preserving both edits (the focus-ring classes are already merged
into `summary_classes` here).

* feat(design-system): migrate 3 provider panels to DS::Disclosure :card variant

Resolves the panel slice of #1715 §6. Continuation of the
DS::Disclosure :card variant work — same migration pattern, applied
to the 3 provider-PANEL templates that share the card shape with the
provider-item templates landing on the parent branch.

Migrated `<details class="group bg-container p-4 shadow-border-xs
rounded-xl">` → `DS::Disclosure.new(variant: :card)` in:

- `app/views/settings/providers/_kraken_panel.html.erb` — 1 details
  in the items-each loop.
- `app/views/settings/providers/_mercury_panel.html.erb` — 1 details
  in the items-each loop.
- `app/views/settings/providers/_brex_panel.html.erb` — 2 details:
  one in the items-each loop, one standalone "add connection" panel
  that opened by default when no active items existed. The
  conditional `<%= "open" unless active_items.any? %>` becomes
  `open: active_items.none?` on the `:card` disclosure.

Panels do NOT show a chevron in their summary (different UX from
the per-item rows in #1855), so the migration preserves that — no
chevron inserted.

NOT migrated (intentionally — different shapes):

- `_ibkr_panel.html.erb` — `<details class="group bg-surface-inset
  rounded-xl p-4">`. Uses bg-surface-inset, not bg-container — needs
  a `:card-inset` variant we haven't built. Deferred.
- `_indexa_capital_panel.html.erb` — `<details class="group">` with
  no card chrome. Inline expander; doesn't fit either disclosure
  variant.
- `_snaptrade_panel.html.erb` — same inline pattern as indexa_capital.

* fix(review): use ring-alpha-black-300 focus token in DS::Disclosure

CodeRabbit P2: switch the focus-visible outline from raw
gray-900/white palette values to the alpha-black-300 ring token,
matching the established focus pattern on settings/provider_card.html.erb.
This keeps theme behavior centralized in the design system tokens
instead of branching on theme-dark: in the component.

Applies to both :default and :card summary variants.

* fix(review): stretch DS::Disclosure summary_content to full width

Codex P2 follow-up on the disclosure-migration stack: \`<summary>\` is
\`display: list-item\`, so a flex inner div inside the slot
shrink-wraps to content width — any \`justify-between\` the caller
adds has nothing to distribute, and the right-side admin actions
collapse toward the title across every provider-item partial migrated
to \`DS::Disclosure variant: :card\` in #1855 (and the panels in
#1856 / #1857 / #1858 that inherit this component).

Wrap the slot in \`<div class=\"w-full\">\` so caller-supplied flex
rows stretch across the card. \`:default\` variant is unchanged
(it never uses \`summary_content\`).

* fix(review): stretch :card summary flex row to full width

Codex P2 follow-up on #1856: the migrated kraken / mercury / brex
panel summary rows wrap their content in
\`<div class=\"flex items-center justify-between gap-X\">\`, but a
flex container inside \`<summary>\` (\`display: list-item\`)
shrink-wraps to content size, so \`justify-between\` had nothing to
distribute and the right-side admin actions collapsed toward the
title.

Add \`w-full\` so the flex row stretches across the card. The deeper
component-level fix lands in #1855 (wraps \`summary_content\` in a
\`w-full\` block); this commit makes #1856 self-contained against the
merge order.
2026-05-21 12:57:26 +02:00

126 lines
6.2 KiB
Plaintext

<div id="mercury-providers-panel" class="space-y-4">
<% active_items = local_assigns[:mercury_items] || @mercury_items || Current.family.mercury_items.active.ordered %>
<% credentialed_items = active_items.select(&:credentials_configured?) %>
<%= render "settings/providers/setup_steps",
steps: [
t("mercury_items.provider_panel.instructions.sign_in_html", link: link_to("Mercury", "https://mercury.com", target: "_blank", rel: "noopener noreferrer", class: "text-primary font-medium underline")).html_safe,
t("mercury_items.provider_panel.instructions.open_tokens"),
t("mercury_items.provider_panel.instructions.create_token"),
t("mercury_items.provider_panel.instructions.whitelist_ip_html").html_safe,
t("mercury_items.provider_panel.instructions.copy_token_html").html_safe
] %>
<% error_msg = local_assigns[:error_message] || @error_message %>
<% if error_msg.present? %>
<%= render DS::Alert.new(message: error_msg, variant: :error) %>
<% end %>
<% if active_items.any? %>
<div class="space-y-3">
<% active_items.each do |item| %>
<%= render DS::Disclosure.new(variant: :card) do |disclosure| %>
<% disclosure.with_summary_content do %>
<div class="flex items-center justify-between gap-2 w-full">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center h-8 w-8 bg-blue-600/10 rounded-full">
<p class="text-blue-600 text-xs font-medium"><%= item.name.to_s.first.to_s.upcase %></p>
</div>
<div>
<p class="font-medium text-primary"><%= item.name %></p>
<p class="text-xs text-secondary"><%= item.sync_status_summary %></p>
</div>
</div>
</div>
<% end %>
<div class="mt-4 space-y-4">
<div class="flex items-center gap-2">
<%= button_to sync_mercury_item_path(item),
method: :post,
class: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-secondary hover:text-primary border border-secondary rounded-lg hover:border-primary",
disabled: item.syncing? do %>
<%= icon "refresh-cw", size: "sm" %>
<%= t("mercury_items.provider_panel.sync") %>
<% end %>
<%= button_to mercury_item_path(item),
method: :delete,
class: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-destructive hover:bg-destructive/10 rounded-lg",
data: { turbo_confirm: t("mercury_items.provider_panel.disconnect_confirm", name: item.name) } do %>
<%= icon "trash-2", size: "sm" %>
<% end %>
</div>
<%= styled_form_with model: item,
url: mercury_item_path(item),
scope: :mercury_item,
method: :patch,
data: { turbo: true },
class: "space-y-3" do |form| %>
<%= form.text_field :name,
label: t("mercury_items.provider_panel.connection_name_label"),
placeholder: t("mercury_items.provider_panel.connection_name_placeholder") %>
<%= form.text_field :token,
label: t("mercury_items.provider_panel.token_label"),
placeholder: t("mercury_items.provider_panel.keep_token_placeholder"),
type: :password,
value: nil %>
<%= form.text_field :base_url,
label: t("mercury_items.provider_panel.base_url_label"),
placeholder: t("mercury_items.provider_panel.base_url_placeholder"),
value: item.base_url %>
<div class="flex flex-wrap justify-end gap-2">
<%= render DS::Link.new(
text: t("mercury_items.provider_panel.setup_accounts"),
icon: "settings",
variant: "secondary",
href: setup_accounts_mercury_item_path(item),
frame: :modal
) %>
<%= form.submit t("mercury_items.provider_panel.update_connection") %>
</div>
<% end %>
</div>
<% end %>
<% end %>
</div>
<% end %>
<% mercury_item = Current.family.mercury_items.build(name: t("mercury_items.provider_panel.default_connection_name")) %>
<% if active_items.any? %>
<h3 class="flex items-center gap-2 text-sm font-medium text-primary mt-4">
<%= icon "plus", size: "sm" %>
<%= t("mercury_items.provider_panel.add_connection") %>
</h3>
<% end %>
<%= styled_form_with model: mercury_item,
url: mercury_items_path,
scope: :mercury_item,
method: :post,
data: { turbo: true },
class: "space-y-3" do |form| %>
<%= form.text_field :name,
label: t("mercury_items.provider_panel.connection_name_label"),
placeholder: t("mercury_items.provider_panel.connection_name_placeholder") %>
<%= form.text_field :token,
label: t("mercury_items.provider_panel.token_label"),
placeholder: t("mercury_items.provider_panel.token_placeholder"),
type: :password,
value: nil %>
<%= form.text_field :base_url,
label: t("mercury_items.provider_panel.base_url_label"),
placeholder: t("mercury_items.provider_panel.base_url_placeholder") %>
<p class="text-xs text-secondary px-1 -mt-1"><%= t("mercury_items.provider_panel.sandbox_note_html").html_safe %></p>
<div class="flex justify-end">
<%= form.submit t("mercury_items.provider_panel.add_connection") %>
</div>
<% end %>
</div>