UNPKG

b0nes

Version:

Zero-dependency component library and SSR/SSG framework

164 lines (155 loc) 5.42 kB
import { processSlotTrusted } from '../../utils/processSlot.js'; import { normalizeClasses } from '../../utils/normalizeClasses.js'; import { validateProps, validatePropTypes } from '../../utils/componentError.js'; import { escapeAttr } from '../../utils/escapeAttr.js'; import { box } from '../../atoms/index.js'; /** * Hero component - A prominent banner/hero section for page intros * * Renders a hero section using the box atom with role="hero". Hero sections are * large, prominent areas typically at the top of a page used to grab attention * and communicate the main message. Often includes headlines, subheadings, images, * and call-to-action buttons. * * Common use cases: * - Landing page heroes with headline and CTA * - Product showcase heroes * - Campaign/promotional banners * - Page introductions with key messaging * - Image backgrounds with overlaid text * * @param {Object} props - Component properties * @param {string | Array<string>} props.slot - Hero content (headline, subheading, image, CTA, etc.) * @param {string} [props.attrs=''] - Additional HTML attributes (e.g., 'data-background="dark"') * @param {string} [props.className=''] - Additional CSS classes to apply * * @returns {string} Rendered HTML hero section (div with role="hero") * * @throws {createComponentError} If required prop (slot) is missing * * @example * // Basic hero with headline and CTA * hero({ * slot: [ * '<h1>Welcome to Our Site</h1>', * '<p>Build amazing things with zero dependencies</p>', * '<a href="/get-started" class="btn">Get Started</a>' * ] * }) * // Returns: '<div class="box hero" role="hero"><h1>Welcome to Our Site</h1>...</div>' * * @example * // Hero with custom styling * hero({ * className: 'full-height centered', * slot: [ * '<h1 class="display-1">Big Headline</h1>', * '<p class="lead">Supporting text that explains the value proposition</p>', * '<div class="cta-buttons">', * ' <button class="btn primary">Primary Action</button>', * ' <button class="btn secondary">Learn More</button>', * '</div>' * ] * }) * // Returns: '<div class="box hero full-height centered" role="hero">...</div>' * * @example * // Hero with background image * hero({ * attrs: `style="background-image: url(\'/images/hero-bg.jpg\')"`, * className: 'text-white', * slot: [ * '<h1>Adventure Awaits</h1>', * '<p>Discover amazing experiences</p>' * ] * }) * // Returns: '<div class="box hero text-white" role="hero" style="background-image: url(\'/images/hero-bg.jpg\')">...</div>' * * @example * // Product hero with image * hero({ * className: 'product-hero', * slot: [ * '<div class="hero-content">', * ' <h1>Introducing Product X</h1>', * ' <p>Revolutionary features that change everything</p>', * ' <button class="btn">Pre-order Now</button>', * '</div>', * '<div class="hero-image">', * ' <img src="/product.png" alt="Product X"/>', * '</div>' * ] * }) * * @example * // Accessible hero with ID and aria-label * hero({ * attrs: 'id="main-hero" aria-label="Main page introduction and call to action"' * slot: [ * '<h1>Your Journey Starts Here</h1>', * '<p>Join thousands of users already benefiting</p>', * '<a href="/signup">Sign Up Free</a>' * ] * }) * // Returns: '<div class="box hero" role="hero" id="main-hero" aria-label="Main page introduction and call to action">...</div>' * * @example * // Minimal hero with just heading * hero({ * className: 'simple', * slot: '<h1>Page Title</h1>' * }) * // Returns: '<div class="box hero simple" role="hero"><h1>Page Title</h1></div>' */ export const hero = ({ slot, attrs = '', className = '' }) => { // Validate required props validateProps( { slot }, ['slot'], { componentName: 'hero', componentType: 'organism' } ); // Validate prop types validatePropTypes( { attrs, className }, { attrs: 'string', className: 'string' }, { componentName: 'hero', componentType: 'organism' } ); // Validate slot is not empty if ((typeof slot === 'string' && slot.trim().length === 0) || (Array.isArray(slot) && slot.length === 0)) { console.warn( `[b0nes Warning] Hero has empty content. ` + `Hero sections should contain headlines, descriptions, or calls-to-action.` ); } // Suggest including h1 for SEO and accessibility const slotString = Array.isArray(slot) ? slot.join('') : slot; if (!slotString.includes('<h1')) { console.info( `[b0nes Info] Hero section should typically include an <h1> heading ` + `for SEO and accessibility best practices.` ); } // Process attributes attrs = attrs ? ` ${attrs}` : ''; // Normalize classes - hero class plus any custom classes const classes = normalizeClasses(['hero', className]); // Process slot content const slotContent = processSlotTrusted(slot); // Combine all attributes for box const boxAttrs = `role="hero"${attrs}`.trim(); // Use box component as the base return box({ is: 'div', className: classes, attrs: boxAttrs, slot: slotContent }); };