Module: Www::NavbarHelper
- Included in:
- DesktopMenuComponent, MobileMenuComponent, NavbarMenuComponent
- Defined in:
- app/helpers/www/navbar_helper.rb
Constant Summary collapse
- NAVBAR_MENUS =
Centralized navigation menu structure
Used by both desktop mega-menus and mobile accordion menu
Update this single source to keep both menus in syncItem options:
label: (required) Display text
path: (required) URL path
icon: (optional) Font Awesome icon name (e.g., 'phone', 'envelope')
badge: (optional) Badge text (e.g., 'New', 'Sale')
badge_class: (optional) Badge CSS class (default: 'bg-success')
image: (optional) Image URL for visual items (like towel warmer finishes)
image_alt: (optional) Alt text for image
offcanvas: (optional) If true, opens an offcanvas instead of navigating
desktop_only: (optional) If true, only shows on desktop
mobile_only: (optional) If true, only shows on mobileCategory options:
header_link: (optional) Makes the category header a clickable link (e.g., '/trade')Menu-specific quick_links: (optional) Each product menu can have its own Quick Links column
quick_links: { header: 'Quick Links', items: [...] } { floor_heating: { label: 'Floor Heating', quick_links: { header: 'Quick Links', items: [ { label: 'On Sale', path: '/sales', icon: 'tags', highlight: true }, { label: 'Refurbished', path: '/sales/refurbished', icon: 'recycle' }, { label: 'Find Your Quote', path: '/quote-lookup', icon: 'file-invoice' }, { label: 'Get Samples', path: '/contact/product-sample', icon: 'swatchbook' }, { label: 'Support', path: '/support', icon: 'headset' }, { label: 'Training', path: '/product-training#floor-heating-training', icon: 'graduation-cap' }, { label: 'Warranty', path: '/contact/warranty-floor-heating', icon: 'shield-check' }, { label: 'Download Catalog', icon: 'download', publication_skus: %w[WARMLYYOURS-ELECTRIC-FLOOR-HEATING-CATALOG-USA WARMLYYOURS-ELECTRIC-FLOOR-HEATING-CATALOG-CANADA] } ] }, categories: [ { header: 'Floor Heating Systems', header_link: '/floor-heating', card_layout: true, items: [ { label: 'TempZone™ Heating Mat', path: '/floor-heating/heated-floor-mat', image: 'https://ik.warmlyyours.com/img/tempzone-flex-with-backup-sensor-hifjrk?tr=w-240,h-240,fo-auto', description: 'Easiest install for tile & stone' }, { label: 'TempZone™ Heating Cable', path: '/floor-heating/heating-cable', image: 'https://ik.warmlyyours.com/img/tz-cable-with-sensor-2muand?tr=w-240,h-240,fo-auto', description: 'Flexible & cost-effective for any room' }, { label: 'TempZone™ Custom Mats', path: '/floor-heating/custom-mats', image: 'https://ik.warmlyyours.com/img/tempzone-custom-mat-with-sensor-erzuv5?tr=w-240,h-240,fo-auto', description: 'Made-to-order for any room shape' }, { label: 'Environ™ Heating Mat', path: '/floor-heating/environ-mats', image: 'https://ik.warmlyyours.com/img/environ-flex-with-backup-sensor-y3z2rh?tr=w-240,h-240,fo-auto', description: 'Designed for floating floors' }, { label: 'Slab Heating Systems', path: '/floor-heating/slab-heating', image: 'https://ik.warmlyyours.com/img/slab-roll-with-sensor-muwu4e?tr=w-240,h-240,fo-auto', description: 'Embedded power for concrete slabs' }, { label: 'Thermostats', path: '/floor-heating/thermostats', image: 'https://ik.warmlyyours.com/img/nspire-touch-thermostat-angle-with-82-image-b3t9oe?tr=w-240,h-240,fo-auto', description: 'Smart controls for your heat' }, { label: 'Underlayments', path: '/floor-heating/underlayment', image: 'https://ik.warmlyyours.com/img/prodeso-roll-b7d050.png?tr=w-240,h-240,fo-auto', description: 'Sound & thermal insulation' }, { label: 'Accessories', path: '/floor-heating/accessories', image: 'https://ik.warmlyyours.com/img/replacement-or-backup-floor-sensor-sensor2-1f8443.png?tr=w-240,h-240,fo-auto', description: 'Sensors, cables & add-ons' } ] }, { # Stacked sections: Tools header + items, then Resources header + items sections: [ { header: 'Tools', items: [ { label: 'Design Your Room', path: '/tools/online-design-tool', icon: 'pen-ruler' }, { label: 'Get an Instant Quote', path: '/floor-heating/quote-builder', icon: 'calculator' }, { label: 'Estimate Running Costs', path: '/floor-heating/cost-calculator', icon: 'chart-line' }, { label: 'Send us your Plan', path: '/floor-heating/electric-floor-heating-smartplan', icon: 'paper-plane' }, { label: 'Heat Loss Calculator', path: '/floor-heating/heatloss-calculator', icon: 'temperature-arrow-down' } ] }, { header: 'Resources', items: [ { label: 'Floor Plan Finder', path: '/floor-heating-floor-plans', icon: 'map' }, { label: 'Project Showcase', path: '/floor-heating/showcases', icon: 'images' }, { label: 'Pets/Animals Guide', path: '/floor-heating/pets', icon: 'paw' }, { label: 'Radiant Heating Guide', path: '/radiant-heating', icon: 'book' } ] } ] }, { header: 'Explore by Room', items: [ { label: 'Bathroom', path: '/floor-heating/bathroom' }, { label: 'Shower', path: '/floor-heating/shower' }, { label: 'Kitchen', path: '/floor-heating/kitchen' }, { label: 'Living Room', path: '/floor-heating/living-room' }, { label: 'Bedroom', path: '/floor-heating/bedroom' }, { label: 'Basement', path: '/floor-heating/basement' }, { label: 'Laundry & Mudroom', path: '/floor-heating/laundry-mudroom' }, { label: 'Sunroom', path: '/floor-heating/sunroom' }, { label: 'Tiny Houses', path: '/floor-heating/tiny-house' } ] }, { header: 'Explore by Flooring', items: [ { label: 'Tile, Marble, Stone', path: '/floor-heating/tile-marble-or-stone' }, { label: 'Luxury Vinyl', path: '/floor-heating/luxury-vinyl-tiles' }, { label: 'Laminate', path: '/floor-heating/laminate' }, { label: 'Engineered Wood', path: '/floor-heating/engineered-wood' }, { label: 'Nailed Hardwood', path: '/floor-heating/nailed-hardwood' }, { label: 'Bamboo', path: '/floor-heating/bamboo' }, { label: 'Carpet', path: '/floor-heating/carpet' }, { label: 'Concrete', path: '/floor-heating/concrete' } ] } ] }, snow_melting: { label: 'Snow Melting', quick_links: { header: 'Quick Links', items: [ { label: 'On Sale', path: '/sales', icon: 'tags', highlight: true }, { label: 'Find Your Quote', path: '/quote-lookup', icon: 'file-invoice' }, { label: 'Get Samples', path: '/contact/product-sample', icon: 'swatchbook' }, { label: 'Support', path: '/support', icon: 'headset' }, { label: 'Training', path: '/product-training#snow-melting-training', icon: 'graduation-cap' }, { label: 'Warranty', path: '/contact/warranty-snow-melting', icon: 'shield-check' }, { label: 'Download Catalog', icon: 'download', publication_skus: %w[WARMLYYOURS-OUTDOOR-HEATING-CATALOG-USA WARMLYYOURS-OUTDOOR-HEATING-CATALOG-CANADA] } ] }, categories: [ { header: 'Snow Melting Systems', header_link: '/snow-melting', card_layout: true, items: [ { label: 'PowerMat', path: '/snow-melting/mat/powermat', image: 'https://ik.warmlyyours.com/img/iq-snow-melting-mat-thumbnail-a0e07c.png?tr=w-240,h-240,fo-auto', description: 'Maximum heat for heavy snowfall' }, { label: 'OmniMat', path: '/snow-melting/mat/omnimat', image: 'https://ik.warmlyyours.com/img/iq-snow-melting-mat-thumbnail-a0e07c.png?tr=w-240,h-240,fo-auto', description: 'Balanced performance & efficiency' }, { label: 'Cable', path: '/snow-melting/cable', image: 'https://ik.warmlyyours.com/img/iq-snow-melt-cable-thumbnail-2ff059.png?tr=w-240,h-240,cm-pad_resize,bg-FFFFFF', description: 'Flexible for any area' }, { label: 'Controls & Relays', path: '/snow-melting/controls', image: 'https://ik.warmlyyours.com/img/sce-120-slab-ss-dmglsy.jpeg?tr=w-240,h-240,cm-pad_resize,bg-FFFFFF', description: 'Sensors & Power Controllers' }, { label: 'Accessories', path: '/snow-melting/accessories', image: 'https://ik.warmlyyours.com/img/snow-melt-plaque-nec-requirement-smp-bf0b13.png?tr=w-240,h-240,cm-pad_resize,bg-FFFFFF', description: 'Installation aides & Splices' } ] }, { sections: [ { header: 'Tools', items: [ { label: 'Get an Instant Quote', path: '/snow-melting/quote-builder', icon: 'calculator' }, { label: 'Running Cost Estimator', path: '/snow-melting/cost-calculator', icon: 'chart-line' }, { label: 'Request Installation Plan', path: '/snow-melting/electric-snow-melting-smartplan', icon: 'paper-plane' } ] }, { header: 'Resources', items: [ { label: 'Outdoor Plans Finder', path: '/snow-melting-plans', icon: 'map' }, { label: 'Project Showcase', path: '/snow-melting/showcases', icon: 'images' } ] } ] }, { header: 'Explore by Application', items: [ { label: 'Driveways', path: '/snow-melting/heated-driveway' }, { label: 'Walkways & Paths', path: '/snow-melting/heated-walkway-path' }, { label: 'Outdoor Stairs', path: '/snow-melting/outdoor-stairs' }, { label: 'Ramps', path: '/snow-melting/accessibility-ramp-deicing' }, { label: 'Patios', path: '/snow-melting/heated-concrete-patio' } ] }, { header: 'Explore by Surface', items: [ { label: 'Asphalt', path: '/snow-melting/asphalt' }, { label: 'Concrete', path: '/snow-melting/concrete' }, { label: 'Pavers/Stone', path: '/snow-melting/heated-pavers-and-stone' } ] } ] }, deicing: { label: 'Deicing', quick_links: { header: 'Quick Links', items: [ { label: 'On Sale', path: '/sales', icon: 'tags', highlight: true }, { label: 'Find Your Quote', path: '/quote-lookup', icon: 'file-invoice' }, { label: 'Support', path: '/support', icon: 'headset' }, { label: 'Training', path: '/product-training#roof-gutter-deicing-training', icon: 'graduation-cap' }, { label: 'Warranty', path: '/warranty-registration', icon: 'shield-check' }, { label: 'Download Catalog', icon: 'download', publication_skus: %w[WARMLYYOURS-OUTDOOR-HEATING-CATALOG-USA WARMLYYOURS-OUTDOOR-HEATING-CATALOG-CANADA] } ] }, categories: [ { header: 'Pipe Freeze & Heat Trace', header_link: '/pipe-freeze-protection', card_layout: true, items: [ { label: 'Heat Tape Systems', path: '/pipe-freeze-protection', image: 'https://ik.warmlyyours.com/img/pipe-pro-tect-trace-heating-plug-in-cable-4115a8.png?tr=w-240,h-240,fo-auto', description: 'Pipe freeze protection' }, { label: 'Controls', path: '/pipe-freeze-protection/controls', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/premium-snow-melting-control-577303.png', description: 'Sensors & thermostats' } ] }, { header: 'Roof & Gutter Deicing', header_link: '/roof-and-gutter-deicing', card_layout: true, items: [ { label: 'Roof & Gutter Systems', path: '/roof-and-gutter-deicing', image: 'https://ik.warmlyyours.com/img/roof-and-gutter-kit-with-clips-xcgdu9?tr=w-240,h-240,fo-auto', description: 'Ice dam prevention' }, { label: 'Controls', path: '/roof-and-gutter-deicing/controls', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/premium-snow-melting-control-577303.png', description: 'Sensors & thermostats' }, { label: 'Accessories', path: '/roof-and-gutter-deicing/accessories', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/roof-and-gutter-de-icing-cable-19f875.png', description: 'Clips, kits & parts' } ] }, { header: 'By Roof Type', grouped_with_previous: true, items: [ { label: 'Metal Roofs', path: '/roof-and-gutter-deicing/metal-roof' }, { label: 'Asphalt Shingle Roofs', path: '/roof-and-gutter-deicing/asphalt-shingle-roof' }, { label: 'Slate Shingle Roofs', path: '/roof-and-gutter-deicing/slate-shingle-roof' } ] } ] }, towel_warmers: { label: 'Towel Warmers', quick_links: { header: 'Quick Links', items: [ { label: 'On Sale', path: '/sales', icon: 'tags', highlight: true }, { label: 'Find Your Quote', path: '/quote-lookup', icon: 'file-invoice' }, { label: 'Support', path: '/support', icon: 'headset' }, { label: 'Training', path: '/product-training#other-products-training', icon: 'graduation-cap' }, { label: 'Warranty', path: '/contact/warranty-towel-warmer', icon: 'shield-check' }, { label: 'Download Catalog', icon: 'download', publication_skus: %w[WARMLYYOURS-TOWEL-WARMER-CATALOG-USA WARMLYYOURS-TOWEL-WARMER-CATALOG-CANADA] } ] }, categories: [ { header: 'Towel Warmers', header_link: '/towel-warmer', card_layout: true, items: [ { label: 'Wall-Mounted', path: '/towel-warmer/wall-mounted', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/sierra-gold-t8k4hg', description: 'Hardwired & plug-in' }, { label: 'Freestanding', path: '/towel-warmer/freestanding', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/aaa-barcelona-niqcla', description: 'Portable & flexible' }, { label: 'Controls', path: '/towel-warmer/controls', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/timer-wifi-black-1mmzvp.jpeg', description: 'Timers & WiFi switches' }, { label: 'Crystal Accents', path: '/towel-warmer/crystal', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/hardwired-wifi-switch-with-austrian-crystals-orc5gc.png', description: 'Faceplates & collar accents' } ] }, { header: 'Shop by Finish', card_layout: true, items: [ { label: 'Brushed Stainless', path: '/towel-warmer/brushed-stainless-steel', image: 'https://ik.warmlyyours.com/tr:cm-extract,x-517,y-88,w-300,h-300,tr:w-240,h-240/img/riviera-brushed-towel-warmer-detail-0159da.jpeg', description: 'Fingerprint-resistant classic' }, { label: 'Polished Stainless', path: '/towel-warmer/polished-stainless-steel', image: 'https://ik.warmlyyours.com/tr:cm-extract,x-531,y-82,w-260,h-260,tr:w-240,h-240/img/riviera-towel-warmer-polished-detail-4d40bc.jpeg', description: 'Chrome-like mirror finish' }, { label: 'Matte Black', path: '/towel-warmer/matte-black', image: 'https://ik.warmlyyours.com/tr:cm-extract,x-2460,y-1280,w-382,h-382,tr:w-240,h-240/img/tws2-tah07kh-tahoe-7-black-3ebda2.png', description: 'Bold modern statement' }, { label: 'Gold', path: '/towel-warmer/gold', image: 'https://ik.warmlyyours.com/tr:w-240,h-240/img/tws2-tah07gh-tahoe-7-brushed-gold-swatch-87lc7b.png', description: 'Brushed & polished gold' } ] }, { header: 'Shop by Size', card_layout: true, items: [ { label: 'Compact', path: '/towel-warmer/compact', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/palma-side-a87492.jpeg', description: 'Under 30″ · 4–6 bars' }, { label: 'Standard', path: '/towel-warmer/standard', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/riviera-brushed-slate-2cu3s1.png', description: '30–40″ · 8–9 bars' }, { label: 'Large', path: '/towel-warmer/large', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/malta-polished-towel-warmer-c5b56a.jpeg', description: 'Over 40″ · 10+ bars' } ] } ] }, comfort_products: { label: 'Comfort Products', quick_links: { header: 'Quick Links', items: [ { label: 'On Sale', path: '/sales', icon: 'tags', highlight: true }, { label: 'Find Your Quote', path: '/quote-lookup', icon: 'file-invoice' }, { label: 'Support', path: '/support', icon: 'headset' }, { label: 'Training', path: '/product-training#other-products-training', icon: 'graduation-cap' }, { label: 'Warranty', path: '/warranty-registration', icon: 'shield-check' }, { label: 'Download Catalogs', path: '/contact/request-literature', icon: 'download' } ] }, categories: [ { header: 'Radiant Heating Panels', header_link: '/radiant-heat-panels', card_layout: true, items: [ { label: 'Ember Glass', path: '/radiant-panel/ember/glass', image: 'https://ik.warmlyyours.com/img/ember-glass-black-600-angle-8qtqsr.jpeg?tr=w-240,h-240,fo-auto', description: 'Sleek glass panels' }, { label: 'Ember Mirror', path: '/radiant-panel/ember/mirror', image: 'https://ik.warmlyyours.com/img/ipe-emg-mw-6090-600-angle-xjvmqb.jpeg?tr=w-240,h-240,fo-auto', description: 'Reflective panels' }, { label: 'Ember Flex', path: '/radiant-panel/ember/flex', image: 'https://ik.warmlyyours.com/img/ember-flex-white-400-angle-mqbsdp.jpeg?tr=w-240,h-240,fo-auto', description: 'Flexible panels' }, { label: 'Thermostats', path: '/radiant-heat-panels/controls', image: 'https://ik.warmlyyours.com/tr:w-240,h-240,fo-auto/img/nspire-touch-thermostat-angle-with-82-image-b3t9oe', description: 'Smart controls' } ] }, { header: 'Bathroom & Kitchen Comfort', card_layout: true, items: [ { label: 'LED Mirrors', path: '/led-mirror', image: 'https://ik.warmlyyours.com/img/01-marilyn-hero-67hazb?tr=w-240,h-240,fo-auto', description: 'Backlit elegance' }, { label: 'Mirror Defoggers', path: '/mirror-defogger', image: 'https://ik.warmlyyours.com/img/mirror-defogger-circle-6e15ac.jpeg?tr=w-240,h-240,fo-auto', description: 'Clear reflections' }, { label: 'Countertop Heaters', path: '/countertop-heater', image: 'https://ik.warmlyyours.com/img/countertop-heating-izycjr?tr=w-240,h-240,fo-auto', description: 'Warm surfaces' } ] } ] }, inspiration: { label: 'Inspiration', categories: [ { header: 'Learn & Discover', items: [ { label: 'Our Radiant Journal Blog', path: '/posts', icon: 'newspaper' }, { label: 'Watch Videos', path: '/video-media', icon: 'circle-play' }, { label: 'Join Webinars & Live Events', path: '/webinar', icon: 'calendar' }, { label: 'Customer Reviews', path: '/reviews', icon: 'star' }, { label: 'Press Room', path: '/company/press', icon: 'bullhorn' } ] }, { header: 'Project Galleries', header_link: '/showcases', items: [ { label: 'Floor Heating Showcase', path: '/floor-heating/showcases' }, { label: 'Snow Melting Showcase', path: '/snow-melting/showcases' }, { label: 'All Showcases', path: '/showcases', icon: 'grid-2' } ] }, { header: 'Our Company', header_link: '/company', items: [ { label: 'Our Story', path: '/company' }, { label: 'Customer Testimonials', path: '/company/testimonials' }, { label: 'Brand Assets', path: '/brand-assets' } ] }, { header: 'Featured Blog', featured_card: true, image: 'https://ik.warmlyyours.com/img/installing-snow-melt-mats-on-asphalt-driveway-363c9c.jpeg?tr=w-400,h-260,fo-auto', title: 'Heated Driveway Cost Breakdown: What to Expect in 2025', path: '/posts/How-to-Calculate-the-Cost-of-a-Heated-Driveway-1181', cta: 'See Featured Blog' }, { header: 'Featured Gallery', featured_card: true, image: 'https://ik.warmlyyours.com/img/sls-warmly-yours-images-29-uafcat?tr=w-400,h-260,fo-auto', title: 'Radiant Luxury for a Farmhouse Master Bath', path: '/showcases/master-bathroom', cta: 'See Featured Gallery' } ] }, help_and_pros: { label: 'Help & Pros', categories: [ { header: 'Get Help', header_link: '/support', items: [ { label: 'My Account', path: '#offcanvas-account', offcanvas: true }, { label: 'Product Support', path: '/support' }, { label: 'Training & Tutorials', path: '/product-training' }, { label: 'Courses', path: '/courses' }, { label: 'Register Your Warranty', path: '/warranty-registration' }, { label: 'Services', path: '/services' } ] }, { header: 'Find & Connect', header_link: '/contact', items: [ { label: 'Find a Dealer or Installer', path: '/company/where-to-buy' }, { label: 'Contact Us', path: '/contact' }, { label: 'Hours of Operation', path: '/company/hours-of-operation' } ] }, { header: 'For Pros', header_link: '/trade', pro_column: true, items: [ { label: 'Join the Pro Program', path: '/trade', icon: 'badge-check' }, { label: 'Download Catalogs', path: '/contact/request-literature', icon: 'book' }, { label: 'See Upcoming Tradeshows', path: '/trade/trade-shows', icon: 'calendar' } ] } ] } }.freeze
Instance Method Summary collapse
-
#desktop_items(items) ⇒ Object
Filters items for desktop display (excludes mobile_only items).
-
#desktop_menu_link(item) ⇒ Object
Renders a desktop dropdown menu item with icon, badge, and image support.
-
#has_quick_links?(key) ⇒ Boolean
Checks if a menu has Quick Links.
-
#menu_item_badge(item) ⇒ Object
Renders a badge for menu items.
-
#menu_item_icon(item, classes: 'me-2') ⇒ Object
Renders an icon for menu items.
-
#menu_item_url(item) ⇒ Object
Returns the URL for a menu item, handling both regular paths and publication downloads Publication items should have publication_skus: ['USA-SKU', 'CANADA-SKU'].
-
#mobile_image_url(url, compact: false) ⇒ Object
Returns a resized image URL for mobile menu (smaller for performance) Preserves cm-extract crops while replacing size transforms.
-
#mobile_items(items) ⇒ Object
Filters items for mobile display (excludes desktop_only items).
-
#mobile_menu_link(item, compact_image: false) ⇒ Object
Renders a mobile menu item link with icon, image and badge support.
-
#mobile_quick_link(item) ⇒ Object
Renders a mobile quick link with visible icon.
-
#navbar_column_class(_key) ⇒ Object
Auto-calculates column class based on number of categories.
-
#navbar_header_tag(category_or_section, css_class:) ⇒ Object
Renders a category/section header — as a link if header_link is present, otherwise a plain span.
-
#navbar_menu(key) ⇒ Object
Returns the menu structure for a given menu key.
-
#navbar_menu_keys ⇒ Object
Returns all menu keys in display order.
-
#publication_url_for_menu(*skus) ⇒ Object
Helper to get publication URL that works in both view and ViewComponent contexts Falls back to the catalogs page if publication is not found OPTIMIZATION: Uses Rails.cache to avoid repeated publication lookups across requests.
-
#quick_links_for(key) ⇒ Object
Returns the Quick Links column for a specific menu (if it has one).
-
#single_category_menu?(key) ⇒ Boolean
Checks if a menu has only one category (used to skip header on mobile).
Instance Method Details
#desktop_items(items) ⇒ Object
Filters items for desktop display (excludes mobile_only items)
510 511 512 |
# File 'app/helpers/www/navbar_helper.rb', line 510 def desktop_items(items) items.reject { |item| item[:mobile_only] } end |
#desktop_menu_link(item) ⇒ Object
Renders a desktop dropdown menu item with icon, badge, and image support
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 |
# File 'app/helpers/www/navbar_helper.rb', line 672 def (item) link_class = item[:highlight] ? 'dropdown-item dropdown-item-highlight' : 'dropdown-item' = { class: link_class } [:target] = '_blank' if item[:publication_skus] if item[:offcanvas] content = safe_join([ (item), item[:label], (item) ].compact) link_to content, item[:path], class: link_class, data: { 'bs-toggle': 'offcanvas' }, role: 'button' elsif item[:image] [:class] += ' d-flex align-items-center' link_to (item), ** do safe_join([ image_tag(item[:image], class: 'me-2', style: 'width: 20px; height: 20px; object-fit: cover;', alt: item[:image_alt]), item[:label], (item) ].compact) end else content = safe_join([ (item), item[:label], (item) ].compact) link_to content, (item), ** end end |
#has_quick_links?(key) ⇒ Boolean
Checks if a menu has Quick Links
485 486 487 |
# File 'app/helpers/www/navbar_helper.rb', line 485 def has_quick_links?(key) NAVBAR_MENUS[key][:quick_links].present? end |
#menu_item_badge(item) ⇒ Object
Renders a badge for menu items
515 516 517 518 519 520 |
# File 'app/helpers/www/navbar_helper.rb', line 515 def (item) return unless item[:badge] badge_class = item[:badge_class] || 'bg-success' tag.span(item[:badge], class: "badge #{badge_class} ms-1") end |
#menu_item_icon(item, classes: 'me-2') ⇒ Object
Renders an icon for menu items
523 524 525 526 527 |
# File 'app/helpers/www/navbar_helper.rb', line 523 def (item, classes: 'me-2') return unless item[:icon] fa_icon(item[:icon], class: "fa-fw #{classes}") end |
#menu_item_url(item) ⇒ Object
Returns the URL for a menu item, handling both regular paths and publication downloads
Publication items should have publication_skus: ['USA-SKU', 'CANADA-SKU']
611 612 613 614 615 616 617 |
# File 'app/helpers/www/navbar_helper.rb', line 611 def (item) if item[:publication_skus] (*item[:publication_skus]) else cms_link(item[:path]) end end |
#mobile_image_url(url, compact: false) ⇒ Object
Returns a resized image URL for mobile menu (smaller for performance)
Preserves cm-extract crops while replacing size transforms
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'app/helpers/www/navbar_helper.rb', line 569 def mobile_image_url(url, compact: false) size = compact ? '60' : '80' size_transform = "w-#{size},h-#{size},fo-auto" # Handle path-style transforms (tr:...) if url.include?('/tr:') # Check if URL has cm-extract crop that should be preserved if url =~ %r{/tr:(cm-extract[^,]*(?:,[xywh]-\d+)*)} extract_params = Regexp.last_match(1) url.gsub(%r{/tr:[^/]+/}, "/tr:#{extract_params},#{size_transform}/") else url.gsub(%r{/tr:[^/]+/}, "/tr:#{size_transform}/") end # Handle query-style transforms (?tr=...) elsif url.include?('?tr=') url.gsub(/\?tr=[^&]+/, "?tr=#{size_transform}") # No existing transform, add query param else "#{url}?tr=#{size_transform}" end end |
#mobile_items(items) ⇒ Object
Filters items for mobile display (excludes desktop_only items)
505 506 507 |
# File 'app/helpers/www/navbar_helper.rb', line 505 def mobile_items(items) items.reject { |item| item[:desktop_only] } end |
#mobile_menu_link(item, compact_image: false) ⇒ Object
Renders a mobile menu item link with icon, image and badge support
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 |
# File 'app/helpers/www/navbar_helper.rb', line 530 def (item, compact_image: false) # Build the visual element (image or icon) visual = if item[:image] image_tag( mobile_image_url(item[:image], compact: compact_image), class: compact_image ? 'mobile-menu-image mobile-menu-image--compact' : 'mobile-menu-image', alt: item[:label], loading: 'lazy' ) elsif item[:icon] fa_icon(item[:icon], class: 'mobile-menu-icon') end # Build label with optional description label_content = if item[:description] content_tag(:span, class: 'mobile-menu-label-wrap') do safe_join([ content_tag(:span, item[:label], class: 'mobile-menu-label'), content_tag(:span, item[:description], class: 'mobile-menu-desc') ]) end else content_tag(:span, item[:label], class: 'mobile-menu-label') end content = safe_join([visual, label_content, (item)].compact) link_class = item[:image] ? 'mobile-menu-link mobile-menu-link--has-image' : 'mobile-menu-link' = { class: link_class } [:data] = { 'bs-toggle': 'offcanvas' } if item[:offcanvas] [:role] = 'button' if item[:offcanvas] [:target] = '_blank' if item[:publication_skus] link_to content, (item[:offcanvas] ? item[:path] : (item)), end |
#mobile_quick_link(item) ⇒ Object
Renders a mobile quick link with visible icon
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
# File 'app/helpers/www/navbar_helper.rb', line 592 def mobile_quick_link(item) icon = (fa_icon(item[:icon], class: 'mobile-quick-link-icon') if item[:icon]) content = safe_join([ icon, content_tag(:span, item[:label], class: 'mobile-quick-link-label'), (item) ].compact) = { class: 'mobile-quick-link' } [:data] = { 'bs-toggle': 'offcanvas' } if item[:offcanvas] [:role] = 'button' if item[:offcanvas] [:target] = '_blank' if item[:publication_skus] link_to content, (item[:offcanvas] ? item[:path] : (item)), end |
#navbar_column_class(_key) ⇒ Object
Auto-calculates column class based on number of categories
490 491 492 |
# File 'app/helpers/www/navbar_helper.rb', line 490 def (_key) 'col' end |
#navbar_header_tag(category_or_section, css_class:) ⇒ Object
Renders a category/section header — as a link if header_link is present, otherwise a plain span
660 661 662 663 664 665 666 667 668 669 |
# File 'app/helpers/www/navbar_helper.rb', line 660 def (category_or_section, css_class:) header = category_or_section[:header] link = category_or_section[:header_link] if link link_to header, cms_link(link), class: css_class else tag.span header, class: css_class end end |
#navbar_menu(key) ⇒ Object
Returns the menu structure for a given menu key
475 476 477 |
# File 'app/helpers/www/navbar_helper.rb', line 475 def (key) NAVBAR_MENUS[key] end |
#navbar_menu_keys ⇒ Object
Returns all menu keys in display order
495 496 497 |
# File 'app/helpers/www/navbar_helper.rb', line 495 def NAVBAR_MENUS.keys end |
#publication_url_for_menu(*skus) ⇒ Object
Helper to get publication URL that works in both view and ViewComponent contexts
Falls back to the catalogs page if publication is not found
OPTIMIZATION: Uses Rails.cache to avoid repeated publication lookups across requests.
Cache key includes store_id for locale-specific publications.
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 |
# File 'app/helpers/www/navbar_helper.rb', line 623 def (*skus) # In ViewComponent, we need to access helpers through the helpers proxy store = respond_to?(:helpers) ? helpers.locale_store : locale_store store_id = store&.id || 1 # Create a cache key based on the SKUs and store cache_key = "navbar_publication_url/#{store_id}/#{skus.join('-')}" # Use Rails cache with 1-hour expiration (publications rarely change) cached_sku = Rails.cache.fetch(cache_key, expires_in: 1.hour) do # Find the first matching publication found_pub = nil skus.each do |sku| pub = Item.find_publication(sku, store) if pub found_pub = pub.sku break end end found_pub # Cache the SKU (or nil if not found) end if cached_sku return helpers.www_publication_path(cached_sku, format: :pdf) if respond_to?(:helpers) return www_publication_path(cached_sku, format: :pdf) end # Fallback to catalogs page if publication not found cms_link('/contact/request-literature') rescue StandardError => e Rails.logger.warn "Error resolving publication URL for #{skus}: #{e.}" cms_link('/contact/request-literature') end |
#quick_links_for(key) ⇒ Object
Returns the Quick Links column for a specific menu (if it has one)
480 481 482 |
# File 'app/helpers/www/navbar_helper.rb', line 480 def quick_links_for(key) NAVBAR_MENUS[key][:quick_links] end |
#single_category_menu?(key) ⇒ Boolean
Checks if a menu has only one category (used to skip header on mobile)
500 501 502 |
# File 'app/helpers/www/navbar_helper.rb', line 500 def (key) NAVBAR_MENUS[key][:categories].size == 1 end |