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 } }.
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).
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 =
'box'
Instance Attribute Summary collapse
-
#carousel ⇒ Object
readonly
Returns the value of attribute carousel.
-
#cta_label ⇒ Object
readonly
Returns the value of attribute cta_label.
-
#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 ⇒ Object
- #card_component_for(option, index:, motion_override: motion) ⇒ Object
- #carousel_config ⇒ Object
- #catalog_card_image_asset_slug(presenter) ⇒ Object
-
#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', 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, hide_cta: false, open_sections: false, include_feature_collapse: nil) ⇒ ProductCardsComponent
constructor
A new instance of ProductCardsComponent.
- #resolve_option(option) ⇒ Object
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', 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, hide_cta: false, open_sections: false, include_feature_collapse: nil) ⇒ ProductCardsComponent
Returns a new instance of ProductCardsComponent.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 |
# File 'app/components/www/product_cards_component.rb', line 66 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', 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, 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 @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 = true @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.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def carousel @carousel end |
#cta_label ⇒ Object (readonly)
Returns the value of attribute cta_label.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def cta_label @cta_label end |
#group_link ⇒ Object (readonly)
Returns the value of attribute group_link.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def group_link @group_link end |
#group_link_title ⇒ Object (readonly)
Returns the value of attribute group_link_title.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def group_link_title @group_link_title end |
#hide_cta ⇒ Object (readonly)
Returns the value of attribute hide_cta.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def hide_cta @hide_cta end |
#image_fit ⇒ Object (readonly)
Returns the value of attribute image_fit.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def image_fit @image_fit end |
#include_feature_collapse ⇒ Object (readonly)
Returns the value of attribute include_feature_collapse.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def include_feature_collapse @include_feature_collapse end |
#include_sku ⇒ Object (readonly)
Returns the value of attribute include_sku.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def include_sku @include_sku end |
#include_whats_included ⇒ Object (readonly)
Returns the value of attribute include_whats_included.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def include_whats_included @include_whats_included end |
#layout ⇒ Object (readonly)
Returns the value of attribute layout.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def layout @layout end |
#motion ⇒ Object (readonly)
Returns the value of attribute motion.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def motion @motion end |
#open_sections ⇒ Object (readonly)
Returns the value of attribute open_sections.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def open_sections @open_sections end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def @options end |
#render_only_cards ⇒ Object (readonly)
Returns the value of attribute render_only_cards.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def render_only_cards @render_only_cards end |
#render_schema ⇒ Object (readonly)
Returns the value of attribute render_schema.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def render_schema @render_schema end |
#row_cols_classes ⇒ Object (readonly)
Returns the value of attribute row_cols_classes.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def row_cols_classes @row_cols_classes end |
#row_dom_id ⇒ Object (readonly)
Returns the value of attribute row_dom_id.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def row_dom_id @row_dom_id end |
#section_description ⇒ Object (readonly)
Returns the value of attribute section_description.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def section_description @section_description end |
#section_icon ⇒ Object (readonly)
Returns the value of attribute section_icon.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def section_icon @section_icon end |
#section_svg_icon ⇒ Object (readonly)
Returns the value of attribute section_svg_icon.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def section_svg_icon @section_svg_icon end |
#section_title ⇒ Object (readonly)
Returns the value of attribute section_title.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def section_title @section_title end |
#show_page_description ⇒ Object (readonly)
Returns the value of attribute show_page_description.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def show_page_description @show_page_description end |
#show_price ⇒ Object (readonly)
Returns the value of attribute show_price.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def show_price @show_price end |
#simple_card ⇒ Object (readonly)
Returns the value of attribute simple_card.
58 59 60 |
# File 'app/components/www/product_cards_component.rb', line 58 def simple_card @simple_card end |
Instance Method Details
#before_render ⇒ Object
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'app/components/www/product_cards_component.rb', line 182 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) ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'app/components/www/product_cards_component.rb', line 156 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, open_sections: option[:open_sections] || open_sections, render_schema: render_schema, layout: layout, motion: motion_override, image_fit: image_fit ) end |
#carousel_config ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'app/components/www/product_cards_component.rb', line 198 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. defaults = { 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 } } } defaults end |
#catalog_card_image_asset_slug(presenter) ⇒ Object
176 177 178 179 180 |
# File 'app/components/www/product_cards_component.rb', line 176 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) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'app/components/www/product_cards_component.rb', line 136 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 |