Module: Www::NavbarHelper

Included in:
DesktopMenuComponent, MobileMenuComponent, NavbarMenuComponent
Defined in:
app/helpers/www/navbar_helper.rb

Overview

View helper: navbar.

Constant Summary collapse

{
  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/smartplan', icon: 'paper-plane' },
              { label: 'Heat Loss Calculator', path: '/floor-heating/heatloss-calculator', icon: 'temperature-arrow-down' }
            ]
          },
          {
            header: 'Resources',
            items: [
              { label: 'Floor Heating Videos', path: '/floor-heating/videos', icon: 'circle-play' },
              { label: 'Floor Heating Plan Finder', path: '/floor-heating-floor-plans', icon: 'map',
                description: 'Select the plan that matches your current or desired remodel to instantly ' \
                             'explore tailored product quotes, costs, and installation plans.' },
              { 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/smartplan', icon: 'paper-plane' }
            ]
          },
          {
            header: 'Resources',
            items: [
              { label: 'Snow Melting Videos', path: '/snow-melting/videos', icon: 'circle-play' },
              { label: 'Snow Melt Plan Finder', path: '/snow-melting-plans', icon: 'map',
                description: 'Select the plan that matches your outdoor layout or upcoming project to ' \
                             'instantly explore tailored product quotes, costs, and installation plans.' },
              { 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: 'Videos', path: '/videos', icon: 'circle-play' },
        { 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: 'Towel Warmer Videos', path: '/towel-warmer/videos', icon: 'circle-play' },
        { 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/wifi-front-platinum-wf4snx.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' }
        ]
      },
      {
        # TODO: revisit visual treatment once we have product image crops that
        #       actually represent size (current product photos didn't read as
        #       small/medium/large).
        header: 'Shop by Size',
        items: [
          { label: 'Compact', path: '/towel-warmer/compact', icon: 'compress',
            subtitle: 'Under 30″ · 4–6 bars' },
          { label: 'Standard', path: '/towel-warmer/standard', icon: 'bars',
            subtitle: '30–40″ · 8–9 bars' },
          { label: 'Large', path: '/towel-warmer/large', icon: 'expand',
            subtitle: 'Over 40″ · 10+ bars' }
        ]
      },
      {
        # TODO: swap icons for bar-profile crops once source images are picked.
        header: 'Shop by Bar Shape',
        items: [
          { label: 'Round', path: '/towel-warmer/round-bars', icon: 'circle',
            subtitle: 'Classic curved bars' },
          { label: 'Square', path: '/towel-warmer/square-bars', icon: 'square',
            subtitle: 'Modern geometric' },
          { label: 'Flat', path: '/towel-warmer/flat-bars', icon: 'grip-lines',
            subtitle: 'Wide drying surface' }
        ]
      }
    ]
  },

  comfort_products: {
    label: 'Comfort Products',
    quick_links: {
      header: 'Quick Links',
      items: [
        { label: 'On Sale', path: '/sales', icon: 'tags', highlight: true },
        { label: 'Videos', path: '/videos', icon: 'circle-play' },
        { 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: 'Infrared Heating Panels',
        header_link: '/infrared-heating-panels',
        card_layout: true,
        items: [
          { label: 'Ember Glass', path: '/infrared-heating-panels/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: '/infrared-heating-panels/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: '/infrared-heating-panels/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: '/infrared-heating-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: '/videos', 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

Instance Method Details

#desktop_items(items) ⇒ Object

Filters items for desktop display (excludes mobile_only items)



531
532
533
# File 'app/helpers/www/navbar_helper.rb', line 531

def desktop_items(items)
  items.reject { |item| item[:mobile_only] }
end

Renders a desktop dropdown menu item with icon, badge, and image support



714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
# File 'app/helpers/www/navbar_helper.rb', line 714

def desktop_menu_link(item)
  link_class = item[:highlight] ? 'dropdown-item dropdown-item-highlight' : 'dropdown-item'
  link_options = { class: link_class }
  link_options[:target] = '_blank' if item[:publication_skus]

  if item[:offcanvas]
    content = safe_join([
      menu_item_icon(item),
      item[:label],
      menu_item_badge(item)
    ].compact)

    link_to content, item[:path],
            class: link_class,
            data: { 'bs-toggle': 'offcanvas' },
            role: 'button'
  elsif item[:image]
    link_options[:class] += ' d-flex align-items-center'
    link_to menu_item_url(item), **link_options do
      safe_join([
        image_tag(item[:image],
                  class: 'me-2',
                  style: 'width: 20px; height: 20px; object-fit: cover;',
                  alt: item[:image_alt]),
        item[:label],
        menu_item_badge(item)
      ].compact)
    end
  else
    # `:subtitle` renders inline secondary text (used by Size / Bar Shape entries).
    # `:description` is the legacy key — it still drives the `?` tooltip via
    # `menu_item_help`. The two are mutually exclusive in practice.
    label_content = if item[:subtitle]
                      tag.span(class: 'dropdown-item-label-wrap') do
                        safe_join([
                          tag.span(item[:label], class: 'dropdown-item-label'),
                          tag.span(item[:subtitle], class: 'dropdown-item-desc')
                        ])
                      end
                    else
                      item[:label]
                    end

    content = safe_join([
      menu_item_icon(item),
      label_content,
      menu_item_badge(item),
      menu_item_help(item)
    ].compact)

    link_to content, menu_item_url(item), **link_options
  end
end

#has_quick_links?(key) ⇒ Boolean

Checks if a menu has Quick Links

Returns:

  • (Boolean)


506
507
508
# File 'app/helpers/www/navbar_helper.rb', line 506

def has_quick_links?(key)
  NAVBAR_MENUS[key][:quick_links].present?
end

Renders a badge for menu items



536
537
538
539
540
541
# File 'app/helpers/www/navbar_helper.rb', line 536

def menu_item_badge(item)
  return unless item[:badge]

  badge_class = item[:badge_class] || 'bg-success'
  tag.span(item[:badge], class: "badge #{badge_class} ms-1")
end

Renders a "?" help icon whose hover/focus tooltip carries the item's
description. Lets menu items (e.g. the plan finders) surface a short
explanation without cluttering the label. Desktop only — the mobile
menu renders the description inline (see #mobile_menu_link). Requires
the tooltip Stimulus controller on an ancestor element.



555
556
557
558
559
560
561
562
563
564
565
566
# File 'app/helpers/www/navbar_helper.rb', line 555

def menu_item_help(item)
  return if item[:description].blank?

  tag.span(
    fa_icon('circle-question', class: 'fa-fw'),
    class: 'menu-item-help text-muted ms-1',
    tabindex: 0,
    role: 'note',
    data: { 'bs-toggle': 'tooltip', 'bs-placement': 'right', 'bs-container': 'body',
            'bs-title': item[:description] }
  )
end

Renders an icon for menu items



544
545
546
547
548
# File 'app/helpers/www/navbar_helper.rb', line 544

def menu_item_icon(item, classes: 'me-2')
  return unless item[:icon]

  fa_icon(item[:icon], class: "fa-fw #{classes}")
end

Returns the URL for a menu item, handling both regular paths and publication downloads
Publication items should have publication_skus: ['USA-SKU', 'CANADA-SKU']



653
654
655
656
657
658
659
# File 'app/helpers/www/navbar_helper.rb', line 653

def menu_item_url(item)
  if item[:publication_skus]
    publication_url_for_menu(*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



611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'app/helpers/www/navbar_helper.rb', line 611

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)



526
527
528
# File 'app/helpers/www/navbar_helper.rb', line 526

def mobile_items(items)
  items.reject { |item| item[:desktop_only] }
end

Renders a mobile menu item link with icon, image and badge support



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'app/helpers/www/navbar_helper.rb', line 569

def mobile_menu_link(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 inline secondary text. Prefers `:subtitle`
  # (new explicit inline key) and falls back to `:description` for legacy
  # items where the desktop pattern uses a `?` tooltip but mobile renders it inline.
  secondary = item[:subtitle] || item[:description]
  label_content = if secondary
                    tag.span(class: 'mobile-menu-label-wrap') do
                      safe_join([
                                  tag.span(item[:label], class: 'mobile-menu-label'),
                                  tag.span(secondary, class: 'mobile-menu-desc')
                                ])
                    end
                  else
                    tag.span(item[:label], class: 'mobile-menu-label')
                  end

  content = safe_join([visual, label_content, menu_item_badge(item)].compact)

  link_class = item[:image] ? 'mobile-menu-link mobile-menu-link--has-image' : 'mobile-menu-link'

  link_options = { class: link_class }
  link_options[:data] = { 'bs-toggle': 'offcanvas' } if item[:offcanvas]
  link_options[:role] = 'button' if item[:offcanvas]
  link_options[:target] = '_blank' if item[:publication_skus]

  link_to content, (item[:offcanvas] ? item[:path] : menu_item_url(item)), link_options
end

Renders a mobile quick link with visible icon



634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# File 'app/helpers/www/navbar_helper.rb', line 634

def mobile_quick_link(item)
  icon = (fa_icon(item[:icon], class: 'mobile-quick-link-icon') if item[:icon])

  content = safe_join([
    icon,
    tag.span(item[:label], class: 'mobile-quick-link-label'),
    menu_item_badge(item)
  ].compact)

  link_options = { class: 'mobile-quick-link' }
  link_options[:data] = { 'bs-toggle': 'offcanvas' } if item[:offcanvas]
  link_options[:role] = 'button' if item[:offcanvas]
  link_options[:target] = '_blank' if item[:publication_skus]

  link_to content, (item[:offcanvas] ? item[:path] : menu_item_url(item)), link_options
end

Auto-calculates column class based on number of categories



511
512
513
# File 'app/helpers/www/navbar_helper.rb', line 511

def navbar_column_class(_key)
  'col'
end

Renders a category/section header — as a link if header_link is present, otherwise a plain span



702
703
704
705
706
707
708
709
710
711
# File 'app/helpers/www/navbar_helper.rb', line 702

def navbar_header_tag(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

Returns the menu structure for a given menu key



496
497
498
# File 'app/helpers/www/navbar_helper.rb', line 496

def navbar_menu(key)
  NAVBAR_MENUS[key]
end

Returns all menu keys in display order



516
517
518
# File 'app/helpers/www/navbar_helper.rb', line 516

def navbar_menu_keys
  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.



665
666
667
668
669
670
671
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
# File 'app/helpers/www/navbar_helper.rb', line 665

def publication_url_for_menu(*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.message}"
  cms_link('/contact/request-literature')
end

Returns the Quick Links column for a specific menu (if it has one)



501
502
503
# File 'app/helpers/www/navbar_helper.rb', line 501

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)

Returns:

  • (Boolean)


521
522
523
# File 'app/helpers/www/navbar_helper.rb', line 521

def single_category_menu?(key)
  NAVBAR_MENUS[key][:categories].size == 1
end