UNPKG

svarog-ui-core

Version:

Core components of Svarog UI without themes

266 lines (229 loc) 7.35 kB
// src/components/BlogCard/BlogCard.js import { createComponent, createElement, } from '../../utils/componentFactory.js'; import { withThemeAwareness } from '../../utils/composition.js'; import { validateRequiredProps } from '../../utils/validation.js'; import { createStyleInjector } from '../../utils/styleInjection.js'; import { blogCardStyles } from './BlogCard.styles.js'; import Card from '../Card/Card.js'; import Link from '../Link/Link.js'; // Create style injector for BlogCard component const injectBlogCardStyles = createStyleInjector('BlogCard'); /** * Creates a BlogCard component for displaying blog post previews * * @param {Object} props - BlogCard properties * @param {string} [props.title=''] - Blog post title * @param {string} [props.slug=''] - Blog post URL slug * @param {string} [props.excerpt=''] - Blog post excerpt * @param {string} [props.imageUrl=''] - URL of featured image * @param {string} [props.featuredImage=''] - DEPRECATED: Use imageUrl instead * @param {string} [props.publishedDate=''] - Published date string * @param {string} [props.author=''] - Author name * @param {Array} [props.categories=[]] - Post categories * @param {string} [props.className=''] - Additional CSS class names * @returns {Object} BlogCard component API */ const createBlogCard = (props) => { // Inject styles on first render injectBlogCardStyles(blogCardStyles); /** * Migrates legacy props to new standardized props * @param {Object} props - Original props * @returns {Object} Migrated props */ const migrateLegacyProps = (props) => { const migrated = { ...props }; // Handle featuredImage -> imageUrl migration if ('featuredImage' in props && !('imageUrl' in props)) { console.warn( '[BlogCard] featuredImage is deprecated, use imageUrl instead' ); migrated.imageUrl = props.featuredImage; delete migrated.featuredImage; } return migrated; }; // Migrate legacy props const migratedProps = migrateLegacyProps(props); // Define prop requirements const propRequirements = { title: { required: true, type: 'string' }, slug: { required: true, type: 'string' }, excerpt: { required: false, type: 'string' }, imageUrl: { required: false, type: 'string' }, publishedDate: { required: false, type: 'string' }, author: { required: false, type: 'string' }, categories: { required: false, type: 'array' }, className: { required: false, type: 'string' }, }; // Validate required props validateRequiredProps(migratedProps, propRequirements, 'BlogCard'); // Set default props and merge with provided props const { title, slug, excerpt = '', imageUrl = '', publishedDate = '', author = '', categories = [], className = '', } = migratedProps; /** * Helper function to format date * @param {string} dateString - ISO date string * @returns {string} Formatted date string */ const formatDate = (dateString) => { if (!dateString) { return ''; } const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); }; /** * Creates the card content elements * @returns {HTMLElement} Card content container */ const createCardContent = () => { const cardContent = createElement('div', { classes: 'blog-card__content', }); // Add featured image if available if (imageUrl) { const imageContainer = createElement('div', { classes: 'blog-card__image-container', }); const imageElement = createElement('img', { classes: 'blog-card__image', attributes: { src: imageUrl, alt: title || '', loading: 'lazy', }, }); imageContainer.appendChild(imageElement); cardContent.appendChild(imageContainer); } // Create title with link const titleElement = createElement('h3', { classes: 'blog-card__title', text: title, }); const titleLink = Link({ children: titleElement, href: `/blog/${slug}`, block: true, className: 'blog-card__title-link', }); cardContent.appendChild(titleLink.getElement()); // Meta information const metaContainer = createElement('div', { classes: 'blog-card__meta', }); if (author) { const authorElement = createElement('span', { classes: 'blog-card__author', text: `By ${author}`, }); metaContainer.appendChild(authorElement); } if (publishedDate) { const dateElement = createElement('span', { classes: 'blog-card__date', text: formatDate(publishedDate), }); metaContainer.appendChild(dateElement); } if (metaContainer.children.length > 0) { cardContent.appendChild(metaContainer); } // Add excerpt if (excerpt) { const excerptElement = createElement('p', { classes: 'blog-card__excerpt', text: excerpt, }); cardContent.appendChild(excerptElement); } // Add categories if (categories.length > 0) { const categoriesContainer = createElement('div', { classes: 'blog-card__categories', }); categories.forEach((category) => { const categoryElement = createElement('span', { classes: 'blog-card__category', text: category, }); categoriesContainer.appendChild(categoryElement); }); cardContent.appendChild(categoriesContainer); } // Add read more link const readMoreLink = Link({ children: 'Read more →', href: `/blog/${slug}`, underline: false, className: 'blog-card__read-more', }); cardContent.appendChild(readMoreLink.getElement()); return cardContent; }; // Create the card component with blog card content const card = Card({ children: createCardContent(), className: `blog-card ${className}`.trim(), }); // Return the public API return { /** * Gets the blog card element * @returns {HTMLElement} The blog card element */ getElement() { return card.getElement(); }, /** * Updates the blog card with new props * @param {Object} newProps - New properties to update * @returns {Object} Updated component */ update(newProps) { const updatedProps = { ...props, ...newProps }; const newCard = createBlogCard(updatedProps); // Replace the old element if it's in the DOM const element = card.getElement(); if (element.parentNode) { element.parentNode.replaceChild(newCard.getElement(), element); } return newCard; }, /** * Clean up resources */ destroy() { card.destroy(); }, /** * Handle theme changes * @param {string} theme - New theme * @param {string} previousTheme - Previous theme */ onThemeChange(theme, previousTheme) { // Theme changes are handled through CSS variables console.debug( `BlogCard: theme changed from ${previousTheme} to ${theme}` ); }, }; }; // Create the component factory with theme awareness export default createComponent('BlogCard', withThemeAwareness(createBlogCard));