UNPKG

story-az-card-kit

Version:

A flexible and customizable story card component with multiple layouts

372 lines (315 loc) 9.86 kB
/** * StoryCardManager - A utility class for managing multiple story cards */ export class StoryCardManager { constructor(container, options = {}) { this.container = typeof container === 'string' ? document.querySelector(container) : container; this.stories = options.stories || []; this.currentStories = [...this.stories]; this.options = { layout: 'grid', theme: 'default', columns: 3, gap: 'medium', itemsPerPage: 12, currentPage: 1, sortBy: 'date', sortOrder: 'desc', filterTags: [], searchQuery: '', ...options }; this.render(); } /** * Add a new story to the collection */ addStory(story) { this.stories.push(story); this.currentStories = [...this.stories]; this.render(); } /** * Remove a story from the collection */ removeStory(index) { this.stories.splice(index, 1); this.currentStories = [...this.stories]; this.render(); } /** * Update a story in the collection */ updateStory(index, updatedStory) { this.stories[index] = { ...this.stories[index], ...updatedStory }; this.currentStories = [...this.stories]; this.render(); } /** * Filter stories by tags */ filterByTags(tags) { this.options.filterTags = Array.isArray(tags) ? tags : [tags]; this.applyFilters(); } /** * Search stories by title and description */ search(query) { this.options.searchQuery = query.toLowerCase(); this.applyFilters(); } /** * Sort stories by a specific field */ sortBy(field, order = 'desc') { this.options.sortBy = field; this.options.sortOrder = order; this.applyFilters(); } /** * Go to a specific page */ goToPage(page) { this.options.currentPage = Math.max(1, Math.min(page, this.getTotalPages())); this.render(); } /** * Get the next page */ nextPage() { this.goToPage(this.options.currentPage + 1); } /** * Get the previous page */ previousPage() { this.goToPage(this.options.currentPage - 1); } /** * Get total number of pages */ getTotalPages() { return Math.ceil(this.currentStories.length / this.options.itemsPerPage); } /** * Get current page stories */ getCurrentPageStories() { const start = (this.options.currentPage - 1) * this.options.itemsPerPage; const end = start + this.options.itemsPerPage; return this.currentStories.slice(start, end); } /** * Apply all filters and sorting */ applyFilters() { let filtered = [...this.stories]; // Apply tag filtering if (this.options.filterTags.length > 0) { filtered = filtered.filter(story => story.tags && story.tags.some(tag => this.options.filterTags.includes(tag) ) ); } // Apply search filtering if (this.options.searchQuery) { filtered = filtered.filter(story => (story.title && story.title.toLowerCase().includes(this.options.searchQuery)) || (story.description && story.description.toLowerCase().includes(this.options.searchQuery)) || (story.author && story.author.toLowerCase().includes(this.options.searchQuery)) ); } // Apply sorting filtered.sort((a, b) => { let aValue = a[this.options.sortBy]; let bValue = b[this.options.sortBy]; if (this.options.sortBy === 'date') { aValue = new Date(aValue || 0); bValue = new Date(bValue || 0); } else { aValue = String(aValue || '').toLowerCase(); bValue = String(bValue || '').toLowerCase(); } if (this.options.sortOrder === 'asc') { return aValue > bValue ? 1 : -1; } else { return aValue < bValue ? 1 : -1; } }); this.currentStories = filtered; this.options.currentPage = 1; // Reset to first page when filtering this.render(); } /** * Render the current state */ render() { if (!this.container) return; // Clear container this.container.innerHTML = ''; // Create grid container const grid = document.createElement('div'); const gridClass = `story-card-grid story-card-grid--${this.options.columns}-cols story-card-grid--gap-${this.options.gap}`; grid.className = gridClass; // Add story cards const currentStories = this.getCurrentPageStories(); currentStories.forEach(story => { const card = this.createStoryCard(story); grid.appendChild(card); }); this.container.appendChild(grid); // Add pagination if needed if (this.getTotalPages() > 1) { this.renderPagination(); } // Add info about current state this.renderInfo(); } /** * Create a single story card */ createStoryCard(story) { const card = document.createElement('div'); card.className = `story-card story-card--${this.options.layout} story-card--${this.options.theme}`; card.setAttribute('role', 'button'); card.setAttribute('tabindex', '0'); // Add image if (story.image) { const imageContainer = document.createElement('div'); imageContainer.className = 'story-card__image'; const img = document.createElement('img'); img.src = story.image; img.alt = story.title || ''; img.loading = 'lazy'; imageContainer.appendChild(img); card.appendChild(imageContainer); } // Add content const content = document.createElement('div'); content.className = 'story-card__content'; if (story.title) { const title = document.createElement('h3'); title.className = 'story-card__title'; title.textContent = story.title; content.appendChild(title); } if (story.description) { const description = document.createElement('p'); description.className = 'story-card__description'; description.textContent = story.description; content.appendChild(description); } if (story.author || story.date) { const meta = document.createElement('div'); meta.className = 'story-card__meta'; if (story.author) { const author = document.createElement('span'); author.className = 'story-card__author'; author.textContent = story.author; meta.appendChild(author); } if (story.date) { const date = document.createElement('time'); date.className = 'story-card__date'; date.setAttribute('datetime', story.date); date.textContent = new Date(story.date).toLocaleDateString(); meta.appendChild(date); } content.appendChild(meta); } if (story.tags && story.tags.length > 0) { const tags = document.createElement('div'); tags.className = 'story-card__tags'; story.tags.forEach(tag => { const tagElement = document.createElement('span'); tagElement.className = 'story-card__tag'; tagElement.textContent = tag; tags.appendChild(tagElement); }); content.appendChild(tags); } card.appendChild(content); // Add click handler card.addEventListener('click', () => { if (this.options.onCardClick) { this.options.onCardClick(story); } }); return card; } /** * Render pagination controls */ renderPagination() { const pagination = document.createElement('div'); pagination.className = 'story-card-pagination'; const totalPages = this.getTotalPages(); const currentPage = this.options.currentPage; // Previous button if (currentPage > 1) { const prevBtn = document.createElement('button'); prevBtn.textContent = '‹ Previous'; prevBtn.className = 'story-card-pagination__btn'; prevBtn.addEventListener('click', () => this.previousPage()); pagination.appendChild(prevBtn); } // Page numbers for (let i = 1; i <= totalPages; i++) { const pageBtn = document.createElement('button'); pageBtn.textContent = i; pageBtn.className = `story-card-pagination__btn ${i === currentPage ? 'story-card-pagination__btn--active' : ''}`; pageBtn.addEventListener('click', () => this.goToPage(i)); pagination.appendChild(pageBtn); } // Next button if (currentPage < totalPages) { const nextBtn = document.createElement('button'); nextBtn.textContent = 'Next ›'; nextBtn.className = 'story-card-pagination__btn'; nextBtn.addEventListener('click', () => this.nextPage()); pagination.appendChild(nextBtn); } this.container.appendChild(pagination); } /** * Render info about current state */ renderInfo() { const info = document.createElement('div'); info.className = 'story-card-info'; const totalStories = this.currentStories.length; const currentPage = this.options.currentPage; const itemsPerPage = this.options.itemsPerPage; const start = (currentPage - 1) * itemsPerPage + 1; const end = Math.min(currentPage * itemsPerPage, totalStories); info.textContent = `Showing ${start}-${end} of ${totalStories} stories`; if (this.options.searchQuery) { info.textContent += ` matching "${this.options.searchQuery}"`; } if (this.options.filterTags.length > 0) { info.textContent += ` filtered by tags: ${this.options.filterTags.join(', ')}`; } this.container.appendChild(info); } /** * Get current state */ getState() { return { stories: this.stories, currentStories: this.currentStories, options: { ...this.options }, totalPages: this.getTotalPages(), currentPageStories: this.getCurrentPageStories() }; } /** * Update options and re-render */ updateOptions(newOptions) { this.options = { ...this.options, ...newOptions }; this.render(); } }