Class: Www::ProductCardsComponent
- Inherits:
-
ApplicationComponent
- Object
- ViewComponent::Base
- ApplicationComponent
- Www::ProductCardsComponent
- Includes:
- ActionView::Helpers::TagHelper, ActionView::Helpers::UrlHelper
- Defined in:
- app/components/www/product_cards_component.rb
Overview
Www::ProductCardsComponent
Renders a responsive grid of product cards. Feed it product-line slugs,
SKUs, or pre-built option hashes. The component resolves product data
(images, links, ratings, pricing) and outputs accessible, SEO-safe markup.
Data inputs (mutually exclusive, checked in this order)
options: Array of option hashes or SKU strings.
skus: Array of SKU strings, or Hash { 'SKU' => { overrides } }.
product_line_slugs: Array of slugs, or Hash { slug => { overrides } }.
Overrides: link, primary_image_id, accessories,
additional_features (extra bullets for this page only).
Section chrome
section_title, section_description, section_icon, section_svg_icon,
group_link, group_link_title
Layout
layout: :vertical (default) or :horizontal_full
row_cols_classes: Override the default Bootstrap grid classes.
row_dom_id: DOM id for the grid container (Turbo targeting).
carousel: Render cards in a Fancyapps carousel instead of a grid.
render_only_cards: Omit wrapper markup (for Turbo Stream appends).
Card display
simple_card: Minimal card (hides description, features, whats-included).
render_schema: Add CollectionPage schema entries (default true).
cta_label: CTA button label (default "View Details").
hide_cta: Hide the CTA button entirely (default false).
open_sections: Expand accordion sections by default (default false).
include_feature_collapse: Show features as collapsible (default true, false in simple_card).
show_page_description: Show short description (default true, false in simple_card).
show_price: Show price in footer (default true).
include_sku: Show SKU under title (default true).
include_whats_included: Show "What's Included?" (default true, false in simple_card).
image_fit: :contain (default, pad + bg-light) or nil (natural sizing).
motion: Per-card fadeInUp entrance animation (default true). Pass
false where the cards are swapped in a Turbo Frame and a
per-card slide-up would read as jumpy (e.g. towel-warmer).
Examples
Simple:
render Www::ProductCardsComponent.new(
simple_card: true,
product_line_slugs: [ProductLineUrls::FLOOR_HEATING_TEMPZONE_FLEX_ROLL]
)
Full featured:
render Www::ProductCardsComponent.new(
product_line_slugs: [ProductLineUrls::FLOOR_HEATING_CONTROL],
include_feature_collapse: true,
open_sections: true,
show_price: true,
include_sku: false
)
Constant Summary collapse
- DEFAULT_ICON =
Default icon.
'box'.freeze
- RESERVED_OVERRIDE_KEYS =
Per-slug override keys with dedicated handling in #build_options_from_slugs.
Anything else at the slug level is a back-compat { 'SKU' => 'Label' } accessory
map, so a new key has to be listed here or it gets read as accessories. %w[ link primary_image_id primary_image_slug accessories compatible_accessories additional_features ].freeze
Instance Attribute Summary collapse
-
#carousel ⇒ Object
readonly
Returns the value of attribute carousel.
-
#cta_label ⇒ Object
readonly
Returns the value of attribute cta_label.
-
#cta_modal_target ⇒ Object
readonly
Returns the value of attribute cta_modal_target.
-
#group_link ⇒ Object
readonly
Returns the value of attribute group_link.
-
#group_link_title ⇒ Object
readonly
Returns the value of attribute group_link_title.
-
#hide_cta ⇒ Object
readonly
Returns the value of attribute hide_cta.
-
#image_fit ⇒ Object
readonly
Returns the value of attribute image_fit.
-
#include_feature_collapse ⇒ Object
readonly
Returns the value of attribute include_feature_collapse.
-
#include_sku ⇒ Object
readonly
Returns the value of attribute include_sku.
-
#include_whats_included ⇒ Object
readonly
Returns the value of attribute include_whats_included.
-
#layout ⇒ Object
readonly
Returns the value of attribute layout.
-
#motion ⇒ Object
readonly
Returns the value of attribute motion.
-
#open_sections ⇒ Object
readonly
Returns the value of attribute open_sections.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#render_only_cards ⇒ Object
readonly
Returns the value of attribute render_only_cards.
-
#render_schema ⇒ Object
readonly
Returns the value of attribute render_schema.
-
#row_cols_classes ⇒ Object
readonly
Returns the value of attribute row_cols_classes.
-
#row_dom_id ⇒ Object
readonly
Returns the value of attribute row_dom_id.
-
#section_description ⇒ Object
readonly
Returns the value of attribute section_description.
-
#section_icon ⇒ Object
readonly
Returns the value of attribute section_icon.
-
#section_svg_icon ⇒ Object
readonly
Returns the value of attribute section_svg_icon.
-
#section_title ⇒ Object
readonly
Returns the value of attribute section_title.
-
#show_page_description ⇒ Object
readonly
Returns the value of attribute show_page_description.
-
#show_price ⇒ Object
readonly
Returns the value of attribute show_price.
-
#simple_card ⇒ Object
readonly
Returns the value of attribute simple_card.
Instance Method Summary collapse
-
#before_render ⇒ void
Resolves the deferred inputs (options, SKUs, or slugs) into card option hashes.
-
#card_component_for(option, index:, motion_override: motion) ⇒ Www::ProductCardComponent
The configured per-card component.
-
#carousel_config ⇒ Hash
Fancyapps carousel configuration sized to the resolved card count.
-
#catalog_card_image_asset_slug(presenter) ⇒ String?
Looks up the card image asset slug for a catalog presenter.
-
#initialize(options: nil, product_line_slugs: nil, skus: nil, section_title: nil, section_icon: nil, section_svg_icon: nil, section_description: nil, group_link: nil, group_link_title: nil, simple_card: false, render_schema: true, cta_label: 'View Details', cta_modal_target: nil, row_cols_classes: nil, render_only_cards: false, layout: :vertical, carousel: false, row_dom_id: nil, show_page_description: nil, show_price: true, include_sku: true, include_whats_included: nil, image_fit: :contain, motion: true, hide_cta: false, open_sections: false, include_feature_collapse: nil) ⇒ void
constructor
Sets up the component's display flags and defers option resolution to #before_render.
-
#resolve_option(option) ⇒ Hash?
The option merged with resolved catalog data, or nil when the SKU is not found.
Methods inherited from ApplicationComponent
#cms_link, #fetch_or_fallback, #image_asset_tag, #image_tag, #number_to_currency, #number_with_delimiter, #post_path, #post_url, #strip_tags
Constructor Details
#initialize(options: nil, product_line_slugs: nil, skus: nil, section_title: nil, section_icon: nil, section_svg_icon: nil, section_description: nil, group_link: nil, group_link_title: nil, simple_card: false, render_schema: true, cta_label: 'View Details', cta_modal_target: nil, row_cols_classes: nil, render_only_cards: false, layout: :vertical, carousel: false, row_dom_id: nil, show_page_description: nil, show_price: true, include_sku: true, include_whats_included: nil, image_fit: :contain, motion: true, hide_cta: false, open_sections: false, include_feature_collapse: nil) ⇒ void
Sets up the component's display flags and defers option resolution to #before_render.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'app/components/www/product_cards_component.rb', line 110 def initialize(options: nil, product_line_slugs: nil, skus: nil, section_title: nil, section_icon: nil, section_svg_icon: nil, section_description: nil, group_link: nil, group_link_title: nil, simple_card: false, render_schema: true, cta_label: 'View Details', cta_modal_target: nil, row_cols_classes: nil, render_only_cards: false, layout: :vertical, carousel: false, row_dom_id: nil, show_page_description: nil, show_price: true, include_sku: true, include_whats_included: nil, image_fit: :contain, motion: true, hide_cta: false, open_sections: false, include_feature_collapse: nil) @section_title = section_title @section_icon = section_svg_icon.present? ? nil : (section_icon || DEFAULT_ICON) @section_svg_icon = section_svg_icon @section_description = section_description @group_link = group_link @group_link_title = group_link_title @simple_card = simple_card @render_schema = render_schema @cta_label = cta_label @cta_modal_target = cta_modal_target @layout = layout.to_sym @hide_cta = hide_cta @open_sections = open_sections @include_feature_collapse = include_feature_collapse.nil? ? !simple_card : include_feature_collapse @render_only_cards = render_only_cards @row_cols_classes = row_cols_classes || default_row_cols_classes @show_page_description = show_page_description.nil? ? !simple_card : show_page_description @include_whats_included = include_whats_included.nil? ? !simple_card : include_whats_included @include_sku = include_sku @show_price = show_price @motion = motion @image_fit = image_fit if @layout == :horizontal_full @include_feature_collapse = false @open_sections = true end # Defer resolving options until we have a view context (helpers) available @init_options = Array().compact.presence @init_skus = skus # Accept either an Array of slugs or a Hash of { slug => accessory_map } @slug_overrides = {} if product_line_slugs.is_a?(Hash) @init_product_line_slugs = product_line_slugs.keys.map(&:to_s) @slug_overrides = product_line_slugs.transform_keys(&:to_s) else @init_product_line_slugs = Array(product_line_slugs).compact.map(&:to_s) end @locale = I18n.locale @options = [] @carousel = carousel @row_dom_id = row_dom_id super() end |
Instance Attribute Details
#carousel ⇒ Object (readonly)
Returns the value of attribute carousel.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def carousel @carousel end |
#cta_label ⇒ Object (readonly)
Returns the value of attribute cta_label.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def cta_label @cta_label end |
#cta_modal_target ⇒ Object (readonly)
Returns the value of attribute cta_modal_target.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def cta_modal_target @cta_modal_target end |
#group_link ⇒ Object (readonly)
Returns the value of attribute group_link.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def group_link @group_link end |
#group_link_title ⇒ Object (readonly)
Returns the value of attribute group_link_title.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def group_link_title @group_link_title end |
#hide_cta ⇒ Object (readonly)
Returns the value of attribute hide_cta.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def hide_cta @hide_cta end |
#image_fit ⇒ Object (readonly)
Returns the value of attribute image_fit.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def image_fit @image_fit end |
#include_feature_collapse ⇒ Object (readonly)
Returns the value of attribute include_feature_collapse.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def include_feature_collapse @include_feature_collapse end |
#include_sku ⇒ Object (readonly)
Returns the value of attribute include_sku.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def include_sku @include_sku end |
#include_whats_included ⇒ Object (readonly)
Returns the value of attribute include_whats_included.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def include_whats_included @include_whats_included end |
#layout ⇒ Object (readonly)
Returns the value of attribute layout.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def layout @layout end |
#motion ⇒ Object (readonly)
Returns the value of attribute motion.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def motion @motion end |
#open_sections ⇒ Object (readonly)
Returns the value of attribute open_sections.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def open_sections @open_sections end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def @options end |
#render_only_cards ⇒ Object (readonly)
Returns the value of attribute render_only_cards.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def render_only_cards @render_only_cards end |
#render_schema ⇒ Object (readonly)
Returns the value of attribute render_schema.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def render_schema @render_schema end |
#row_cols_classes ⇒ Object (readonly)
Returns the value of attribute row_cols_classes.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def row_cols_classes @row_cols_classes end |
#row_dom_id ⇒ Object (readonly)
Returns the value of attribute row_dom_id.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def row_dom_id @row_dom_id end |
#section_description ⇒ Object (readonly)
Returns the value of attribute section_description.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def section_description @section_description end |
#section_icon ⇒ Object (readonly)
Returns the value of attribute section_icon.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def section_icon @section_icon end |
#section_svg_icon ⇒ Object (readonly)
Returns the value of attribute section_svg_icon.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def section_svg_icon @section_svg_icon end |
#section_title ⇒ Object (readonly)
Returns the value of attribute section_title.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def section_title @section_title end |
#show_page_description ⇒ Object (readonly)
Returns the value of attribute show_page_description.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def show_page_description @show_page_description end |
#show_price ⇒ Object (readonly)
Returns the value of attribute show_price.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def show_price @show_price end |
#simple_card ⇒ Object (readonly)
Returns the value of attribute simple_card.
64 65 66 |
# File 'app/components/www/product_cards_component.rb', line 64 def simple_card @simple_card end |
Instance Method Details
#before_render ⇒ void
This method returns an undefined value.
Resolves the deferred inputs (options, SKUs, or slugs) into card option hashes.
Runs before render so view helpers are available for link/image resolution.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'app/components/www/product_cards_component.rb', line 247 def before_render = if @init_options.present? (@init_options, @locale) elsif @init_skus.is_a?(Hash) && @init_skus.any? normalize_sku_hash(@init_skus, @locale) elsif Array(@init_skus).compact.any? (Array(@init_skus).compact, @locale) else (@init_product_line_slugs, @locale) end @options = @options = Array(@options).map { |opt| opt.merge(open_sections: true) } if @open_sections end |
#card_component_for(option, index:, motion_override: motion) ⇒ Www::ProductCardComponent
Returns the configured per-card component.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/components/www/product_cards_component.rb', line 212 def card_component_for(option, index:, motion_override: motion) Www::ProductCardComponent.new( option: option, index: index, simple_card: simple_card, include_sku: include_sku, include_feature_collapse: include_feature_collapse, include_whats_included: include_whats_included, show_page_description: show_page_description, show_price: show_price, hide_cta: hide_cta, cta_label: cta_label, cta_modal_target: cta_modal_target, open_sections: option[:open_sections] || open_sections, render_schema: render_schema, layout: layout, motion: motion_override, image_fit: image_fit ) end |
#carousel_config ⇒ Hash
Fancyapps carousel configuration sized to the resolved card count.
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'app/components/www/product_cards_component.rb', line 266 def carousel_config count = @options.size # Breakpoints mirror CardGridComponent (posts/videos) so product cards # maintain the same visual rhythm across all content carousels on a page. { type: 'slide', perPage: [4, count].min, perMove: 1, gap: '1rem', pagination: true, arrows: true, drag: true, padding: count > 4 ? { right: '4%' } : { right: '0' }, breakpoints: { 1399 => { perPage: [4, count].min, padding: count > 4 ? { right: '4%' } : { right: '0' } }, 1199 => { perPage: [3, count].min, padding: count > 3 ? { right: '5%' } : { right: '0' } }, 991 => { perPage: [3, count].min, padding: count > 3 ? { right: '5%' } : { right: '0' } }, 767 => { perPage: [2, count].min, padding: count > 2 ? { right: '8%' } : { right: '0' } }, 575 => { perPage: 1, padding: count > 1 ? { right: '18%' } : { right: '0' }, arrows: false } } } end |
#catalog_card_image_asset_slug(presenter) ⇒ String?
Looks up the card image asset slug for a catalog presenter.
237 238 239 240 241 |
# File 'app/components/www/product_cards_component.rb', line 237 def catalog_card_image_asset_slug(presenter) return nil unless presenter.is_a?(Www::ProductCatalogPresenter) && presenter.item presenter.image_asset_slug_for_card end |
#resolve_option(option) ⇒ Hash?
Returns the option merged with resolved catalog data, or nil when the SKU is not found.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'app/components/www/product_cards_component.rb', line 187 def resolve_option(option) return option unless option[:sku].present? && option[:presenter].blank? record = ViewProductCatalog.find_by(item_sku: option[:sku], catalog_id: Catalog.locale_to_catalog_id(I18n.locale)) unless record Rails.logger.warn "[ProductCardsComponent] SKU not found in catalog: #{option[:sku]}" return nil end presenter = Www::ProductCatalogPresenter.new(record) { title: presenter.title, link: presenter.url, image_asset_id: catalog_card_image_asset_slug(presenter), review_data: presenter.product_review_info, presenter: presenter, page_description: presenter.page_description }.merge(option) end |