Module: Www::NavbarHelper
- Included in:
- DesktopMenuComponent, MobileMenuComponent, NavbarMenuComponent
- Defined in:
- app/helpers/www/navbar_helper.rb
Overview
View helper: navbar.
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')
view_all: (optional) With header_link set, adds a "View all →" link to the
column header (same treatment as the card-layout product columns)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' } ] }, { 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' } ] }, { # Stacked sections: Tools header + items, then Resources header + items. # Positioned last (just before Quick Links) per marketing request. 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' } ] } ] } ] }, 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: 'Mats', path: '/snow-melting/mat', image: 'https://ik.warmlyyours.com/img/iq-snow-melting-mat-thumbnail-a0e07c.png?tr=w-240,h-240,fo-auto', description: 'Pre-spaced heat for any climate' }, { label: 'Cables', 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' } ] }, { 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' } ] }, { # Stacked sections: Tools header + items, then Resources header + items. # Positioned last (just before Quick Links) per marketing request. 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' } ] } ] } ] }, 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: { label: 'Help', categories: [ { header: 'Support', header_link: '/support', items: [ { label: 'My Account', path: '#offcanvas-account', offcanvas: true }, { label: 'Product Support', path: '/support' }, { label: 'Register Your Warranty', path: '/warranty-registration' }, { label: 'Services', path: '/services' } ] }, { header: 'Resources', header_link: '/product-training', items: [ { label: 'Training & Tutorials', path: '/product-training' }, { label: 'Courses', path: '/courses' } ] }, { 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' } ] } ] }, pros: { label: 'Pros', categories: [ { # Professional segments ("PRO types"), shown alongside the For Pros # column. Mirrors the cards on /trade (see pages/trade/_your_trade.html.erb). header: 'By Trade', header_link: '/trade', view_all: true, items: [ { label: 'Flooring Contractors', path: '/trade/flooring-contractors', icon: 'grid-2', subtitle: 'Floor heating for every tile job' }, { label: 'Electricians', path: '/trade/electricians', icon: 'bolt', subtitle: 'All radiant heating products' }, { label: 'Builders & GCs', path: '/trade/builders', icon: 'helmet-safety', subtitle: 'New construction & remodels' }, { label: 'Designers & Architects', path: '/trade/designers', icon: 'pen-ruler', subtitle: 'Luxury comfort products' }, { label: 'Roofers', path: '/trade/roofers', icon: 'house-chimney', subtitle: 'Roof & gutter deicing' }, { label: 'Landscapers', path: '/trade/landscapers', icon: 'tree', subtitle: 'Snow melting systems' } ] }, { header: 'Programs & Events', header_link: '/trade', 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' }, { label: 'Pro Radiant Newsletter', path: '/trade/professional-newsletter', icon: 'envelope' }, { label: 'Join Webinar & Live Events', path: '/webinar', icon: 'circle-play' } ] } ] } }.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_help(item) ⇒ Object
Renders a "?" help icon whose hover/focus tooltip carries the item's description.
-
#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)
560 561 562 |
# File 'app/helpers/www/navbar_helper.rb', line 560 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
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 |
# File 'app/helpers/www/navbar_helper.rb', line 746 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 # `: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([ (item), label_content, (item), (item) ].compact) link_to content, (item), ** end end |
#has_quick_links?(key) ⇒ Boolean
Checks if a menu has Quick Links
535 536 537 |
# File 'app/helpers/www/navbar_helper.rb', line 535 def has_quick_links?(key) NAVBAR_MENUS[key][:quick_links].present? end |
#menu_item_badge(item) ⇒ Object
Renders a badge for menu items
565 566 567 568 569 570 |
# File 'app/helpers/www/navbar_helper.rb', line 565 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_help(item) ⇒ Object
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.
584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'app/helpers/www/navbar_helper.rb', line 584 def (item) return if item[:description].blank? tag.span( fa_icon('circle-question', class: 'fa-fw'), class: 'menu-item-help text-body-secondary ms-1', tabindex: 0, role: 'note', data: { 'bs-toggle': 'tooltip', 'bs-placement': 'right', 'bs-container': 'body', 'bs-title': item[:description] } ) end |
#menu_item_icon(item, classes: 'me-2') ⇒ Object
Renders an icon for menu items
573 574 575 576 577 |
# File 'app/helpers/www/navbar_helper.rb', line 573 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']
682 683 684 685 686 687 688 |
# File 'app/helpers/www/navbar_helper.rb', line 682 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
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 |
# File 'app/helpers/www/navbar_helper.rb', line 640 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)
555 556 557 |
# File 'app/helpers/www/navbar_helper.rb', line 555 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
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 |
# File 'app/helpers/www/navbar_helper.rb', line 598 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 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, (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
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'app/helpers/www/navbar_helper.rb', line 663 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'), (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
540 541 542 |
# File 'app/helpers/www/navbar_helper.rb', line 540 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
731 732 733 734 735 736 737 738 739 740 741 742 743 |
# File 'app/helpers/www/navbar_helper.rb', line 731 def (category_or_section, css_class:) header = category_or_section[:header] link = category_or_section[:header_link] icon = category_or_section[:header_icon] label = icon ? safe_join([fa_icon(icon, class: 'fa-fw me-1'), header]) : header if link link_to label, cms_link(link), class: css_class else tag.span label, class: css_class end end |
#navbar_menu(key) ⇒ Object
Returns the menu structure for a given menu key
525 526 527 |
# File 'app/helpers/www/navbar_helper.rb', line 525 def (key) NAVBAR_MENUS[key] end |
#navbar_menu_keys ⇒ Object
Returns all menu keys in display order
545 546 547 |
# File 'app/helpers/www/navbar_helper.rb', line 545 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.
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 |
# File 'app/helpers/www/navbar_helper.rb', line 694 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)
530 531 532 |
# File 'app/helpers/www/navbar_helper.rb', line 530 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)
550 551 552 |
# File 'app/helpers/www/navbar_helper.rb', line 550 def (key) NAVBAR_MENUS[key][:categories].size == 1 end |