From 8de14ed2a54b9940b626621d9b5ade0cb89b59c2 Mon Sep 17 00:00:00 2001 From: Guillem Arias Fauste Date: Fri, 22 May 2026 02:14:44 +0200 Subject: [PATCH] =?UTF-8?q?feat(design-system):=20DS::Disclosure=20:inline?= =?UTF-8?q?=20variant=20+=20migrate=20indexa=5Fcapital=20+=20snaptrade=20p?= =?UTF-8?q?anels=20(#1715=20=C2=A76)=20(#1858)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(design-system): add :inline variant + migrate indexa_capital + snaptrade panels Adds an `:inline` variant to `DS::Disclosure` for plain text-link-style toggles that have no surface, no padding, no shadow — the disclosure reads as a clickable summary text + revealed content, nothing more. Use case: "Alternative auth" form section toggle in the Indexa Capital provider panel; "Manage connections" lazy-loaded toggle in the Snaptrade provider panel. Both were the last raw-`
` callsites in `app/views/settings/providers/`. Migrations: - `_indexa_capital_panel.html.erb` — single inline `
` revealing username / document / password form fields under an "Alternative auth" summary text. - `_snaptrade_panel.html.erb` — lazy-load `
` with `data-controller="lazy-load"` etc. The new `tag.details ... **opts` forwarding from #1857 lets the Stimulus controller attrs flow through cleanly via DS::Disclosure's `data:` keyword. Chevron rotation on snaptrade gets the standard `motion-safe:transition-transform motion-safe:duration-150` treatment (was `transition-transform` without the motion-safe gate). Variant summary now: | Variant | Details surface | Use case | |---|---|---| | `:default` | none / bg-surface summary | inline expander inside parent card | | `:card` | `bg-container shadow-border-xs rounded-xl p-4` | provider rows, settings sections | | `:card_inset` | `bg-surface-inset rounded-xl p-4` | inset sub-panels | | `:inline` | no surface | text-link-style toggles | * fix(review): guard variant.to_sym against nil in DS::Disclosure CodeRabbit on #1858 flagged that `variant: nil` crashed with `NoMethodError` at `variant.to_sym` before the explicit `VARIANTS` check could run. Use safe navigation (`variant&.to_sym`) so nil falls through to the validation, and inspect `@variant` in the error message so nil / non-symbol inputs render readably. Verified manually via runner: `DS::Disclosure.new(variant: nil)` now raises `ArgumentError: Invalid variant: nil. Must be one of [:default, :card, :card_inset, :inline]`. --- app/components/DS/disclosure.rb | 23 +++++++--- .../providers/_indexa_capital_panel.html.erb | 12 ++--- .../providers/_snaptrade_panel.html.erb | 44 +++++++++++-------- 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/app/components/DS/disclosure.rb b/app/components/DS/disclosure.rb index ad4bdb37e..5f993d6c1 100644 --- a/app/components/DS/disclosure.rb +++ b/app/components/DS/disclosure.rb @@ -1,12 +1,13 @@ class DS::Disclosure < DesignSystemComponent renders_one :summary_content - VARIANTS = %i[default card card_inset].freeze + VARIANTS = %i[default card card_inset inline].freeze attr_reader :title, :align, :open, :variant, :opts # `:default` — bg-surface summary, no chrome on the `
`. Use - # for inline expanders inside a parent card. + # for inline expanders that sit inside a parent card (the summary + # itself reads as the surface). # # `:card` — `
` itself becomes a `bg-container shadow-border-xs # rounded-xl` card; the summary inherits the container (no own bg). @@ -18,17 +19,23 @@ class DS::Disclosure < DesignSystemComponent # (e.g. the IBKR flex-query "report details" panel embedded inside # the IBKR settings flow). Same summary contract as `:card`. # - # In both card variants, callers should pass their own + # `:inline` — no surface, no padding, no shadow. The disclosure reads + # as a plain text-link-style toggle (e.g. "Alternative auth" inside + # a form, or a "Manage connections" lazy-load opener). Caller provides + # the summary text (and optional chevron) via the `summary_content` + # slot. + # + # In card / inline variants, callers should pass their own # `summary_content` slot; the built-in title rendering assumes the # `:default` shape. def initialize(title: nil, align: "right", open: false, variant: :default, **opts) @title = title @align = align.to_sym @open = open - @variant = variant.to_sym + @variant = variant&.to_sym @opts = opts - raise ArgumentError, "Invalid variant: #{@variant}. Must be one of #{VARIANTS.inspect}" unless VARIANTS.include?(@variant) + raise ArgumentError, "Invalid variant: #{@variant.inspect}. Must be one of #{VARIANTS.inspect}" unless VARIANTS.include?(@variant) end def details_classes @@ -59,6 +66,12 @@ class DS::Disclosure < DesignSystemComponent # Ring token matches `settings/provider_card.html.erb` (the # established focus pattern on container cards). "list-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-300 rounded-xl" + when :inline + # Inline variant: no surface, no padding — the summary reads as + # plain text-link copy. Caller markup (text + optional chevron) + # provides the visual. Keep cursor + focus-visible ring + matching + # alpha-black-300 token used by the card variants for consistency. + "list-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-300 rounded-sm" else "px-3 py-2 rounded-xl cursor-pointer flex items-center justify-between bg-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-300" end diff --git a/app/views/settings/providers/_indexa_capital_panel.html.erb b/app/views/settings/providers/_indexa_capital_panel.html.erb index c751e0feb..255126e42 100644 --- a/app/views/settings/providers/_indexa_capital_panel.html.erb +++ b/app/views/settings/providers/_indexa_capital_panel.html.erb @@ -29,10 +29,12 @@ type: :password %>

<%= t("indexa_capital_items.panel.fields.api_token.description") %>

-
- - <%= t("indexa_capital_items.panel.alternative_auth") %> - + <%= render DS::Disclosure.new(variant: :inline) do |disclosure| %> + <% disclosure.with_summary_content do %> + + <%= t("indexa_capital_items.panel.alternative_auth") %> + + <% end %>
<%= form.text_field :username, label: t("indexa_capital_items.panel.fields.username.label"), @@ -49,7 +51,7 @@ placeholder: is_new_record ? t("indexa_capital_items.panel.fields.password.placeholder_new") : t("indexa_capital_items.panel.fields.password.placeholder_update"), type: :password %>
-
+ <% end %>
<%= form.submit is_new_record ? t("indexa_capital_items.panel.save_button") : t("indexa_capital_items.panel.update_button"), diff --git a/app/views/settings/providers/_snaptrade_panel.html.erb b/app/views/settings/providers/_snaptrade_panel.html.erb index ec877bdcd..1c814b234 100644 --- a/app/views/settings/providers/_snaptrade_panel.html.erb +++ b/app/views/settings/providers/_snaptrade_panel.html.erb @@ -57,25 +57,31 @@ <% if items&.any? && items.first.user_registered? %> <% item = items.first %>
-
- -
-

- <%= t("providers.snaptrade.status_connected", count: item.snaptrade_accounts.count) %> - <% if item.unlinked_accounts_count > 0 %> - (<%= t("providers.snaptrade.needs_setup", count: item.unlinked_accounts_count) %>) - <% end %> -

+ <%= render DS::Disclosure.new( + variant: :inline, + data: { + controller: "lazy-load", + action: "toggle->lazy-load#toggled", + lazy_load_url_value: connections_snaptrade_item_path(item), + lazy_load_auto_open_param_value: "manage" + } + ) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+

+ <%= t("providers.snaptrade.status_connected", count: item.snaptrade_accounts.count) %> + <% if item.unlinked_accounts_count > 0 %> + (<%= t("providers.snaptrade.needs_setup", count: item.unlinked_accounts_count) %>) + <% end %> +

+
+ + <%= t("providers.snaptrade.manage_connections") %> + <%= icon "chevron-right", class: "w-3 h-3 group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> +
- - <%= t("providers.snaptrade.manage_connections") %> - <%= icon "chevron-right", class: "w-3 h-3 transition-transform group-open:rotate-90" %> - -
+ <% end %>
@@ -86,7 +92,7 @@
-
+ <% end %>
<% end %>