Module: Www::NavbarHelper

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

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/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

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

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 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
    content = safe_join([
      menu_item_icon(item),
      item[:label],
      menu_item_badge(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)


485
486
487
# File 'app/helpers/www/navbar_helper.rb', line 485

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

Renders a badge for menu items



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

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 an icon for menu items



523
524
525
526
527
# File 'app/helpers/www/navbar_helper.rb', line 523

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']



611
612
613
614
615
616
617
# File 'app/helpers/www/navbar_helper.rb', line 611

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



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

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 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 description
  label_content = if item[:description]
                    (:span, class: 'mobile-menu-label-wrap') do
                      safe_join([
                                  (:span, item[:label], class: 'mobile-menu-label'),
                                  (:span, item[:description], class: 'mobile-menu-desc')
                                ])
                    end
                  else
                    (: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



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,
    (: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



490
491
492
# File 'app/helpers/www/navbar_helper.rb', line 490

def navbar_column_class(_key)
  'col'
end

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 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



475
476
477
# File 'app/helpers/www/navbar_helper.rb', line 475

def navbar_menu(key)
  NAVBAR_MENUS[key]
end

Returns all menu keys in display order



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

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.



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 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)



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)

Returns:

  • (Boolean)


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

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