Add comprehensive component library and demo application
Added extensive component library with feedback components (empty state, loading spinner, skeleton loader), enhanced form components (autocomplete, date picker, file upload, form field, time picker), navigation components (pagination), and overlay components (backdrop, drawer, modal, overlay container). Updated demo application with comprehensive showcase components and enhanced styling throughout the project. Excluded font files from repository to reduce size. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ImageContainerComponent, ImageContainerSize, ImageContainerAspectRatio, ImageContainerObjectFit, ImageContainerShape } from '../../../../../ui-essentials/src/lib/components/data-display/image-container';
|
||||
import { BadgeComponent } from '../../../../../ui-essentials/src/lib/components/data-display/badge';
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||
import { faRefresh, faHeart, faPlay } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-image-container-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ImageContainerComponent, BadgeComponent, FontAwesomeModule],
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<h2>Image Container Demo</h2>
|
||||
<p>A flexible image container with lazy loading, aspect ratios, and various display options.</p>
|
||||
|
||||
<!-- Size Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Sizes</h3>
|
||||
<div class="demo-row">
|
||||
@for (size of sizes; track size) {
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
[size]="size"
|
||||
[src]="sampleImages.landscape"
|
||||
[alt]="'Sample image - ' + size + ' size'">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">{{ size }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Aspect Ratios -->
|
||||
<section class="demo-section">
|
||||
<h3>Aspect Ratios</h3>
|
||||
<div class="demo-grid">
|
||||
@for (ratio of aspectRatios; track ratio) {
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="md"
|
||||
[aspectRatio]="ratio"
|
||||
[src]="sampleImages.landscape"
|
||||
[alt]="'Sample image - ' + ratio + ' aspect ratio'">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">{{ ratio }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Object Fit Options -->
|
||||
<section class="demo-section">
|
||||
<h3>Object Fit</h3>
|
||||
<div class="demo-row">
|
||||
@for (fit of objectFits; track fit) {
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="md"
|
||||
aspectRatio="1/1"
|
||||
[objectFit]="fit"
|
||||
[src]="sampleImages.landscape"
|
||||
[alt]="'Sample image with ' + fit + ' object fit'">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">{{ fit }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Shape Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Shapes</h3>
|
||||
<div class="demo-row">
|
||||
@for (shape of shapes; track shape) {
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="md"
|
||||
aspectRatio="1/1"
|
||||
[shape]="shape"
|
||||
[src]="sampleImages.portrait"
|
||||
[alt]="'Sample image with ' + shape + ' shape'">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">{{ shape }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Loading States -->
|
||||
<section class="demo-section">
|
||||
<h3>Loading States</h3>
|
||||
<div class="demo-row">
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="md"
|
||||
[loading]="true"
|
||||
src="placeholder"
|
||||
alt="Loading image">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">Loading</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="md"
|
||||
src="invalid-url.jpg"
|
||||
alt="Failed to load image">
|
||||
<div slot="error">
|
||||
<fa-icon [icon]="faRefresh" size="lg"></fa-icon>
|
||||
<span>Custom Error</span>
|
||||
<button class="retry-btn" (click)="retryImage($event)">Retry</button>
|
||||
</div>
|
||||
</ui-image-container>
|
||||
<span class="demo-label">Error State</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="md"
|
||||
[src]="sampleImages.landscape"
|
||||
[lazy]="false"
|
||||
alt="Eager loaded image">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">Eager Load</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Features -->
|
||||
<section class="demo-section">
|
||||
<h3>Interactive Features</h3>
|
||||
<div class="demo-row">
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="lg"
|
||||
aspectRatio="16/9"
|
||||
[src]="sampleImages.landscape"
|
||||
[overlay]="true"
|
||||
alt="Image with overlay"
|
||||
(imageClick)="handleImageClick('overlay')">
|
||||
<div slot="overlay">
|
||||
<fa-icon [icon]="faPlay" size="2x"></fa-icon>
|
||||
<span>Play Video</span>
|
||||
</div>
|
||||
</ui-image-container>
|
||||
<span class="demo-label">With Overlay</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="lg"
|
||||
aspectRatio="4/3"
|
||||
[src]="sampleImages.landscape"
|
||||
caption="Beautiful landscape scene"
|
||||
alt="Image with caption"
|
||||
(imageClick)="handleImageClick('caption')">
|
||||
</ui-image-container>
|
||||
<span class="demo-label">With Caption</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Advanced Examples -->
|
||||
<section class="demo-section">
|
||||
<h3>Advanced Examples</h3>
|
||||
<div class="demo-row">
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="lg"
|
||||
aspectRatio="1/1"
|
||||
shape="circle"
|
||||
[src]="sampleImages.portrait"
|
||||
[overlay]="true"
|
||||
alt="Profile picture with status"
|
||||
(imageClick)="handleImageClick('profile')">
|
||||
<div slot="overlay">
|
||||
<fa-icon [icon]="faHeart" style="color: red;"></fa-icon>
|
||||
</div>
|
||||
</ui-image-container>
|
||||
<span class="demo-label">Profile Avatar</span>
|
||||
</div>
|
||||
|
||||
<div class="demo-item">
|
||||
<ui-image-container
|
||||
size="xl"
|
||||
aspectRatio="16/9"
|
||||
[src]="sampleImages.landscape"
|
||||
caption="Hero image with custom styling"
|
||||
alt="Hero banner image"
|
||||
class="hero-image">
|
||||
<div slot="caption">
|
||||
<h4>Custom Caption Content</h4>
|
||||
<p>With additional styled elements</p>
|
||||
<ui-badge variant="primary" size="xs">Featured</ui-badge>
|
||||
</div>
|
||||
</ui-image-container>
|
||||
<span class="demo-label">Hero Banner</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Responsive Gallery -->
|
||||
<section class="demo-section">
|
||||
<h3>Responsive Gallery</h3>
|
||||
<div class="demo-gallery">
|
||||
@for (image of galleryImages; track image.id; let i = $index) {
|
||||
<ui-image-container
|
||||
size="md"
|
||||
[aspectRatio]="i % 3 === 0 ? '16/9' : '1/1'"
|
||||
[src]="image.url"
|
||||
[alt]="image.alt"
|
||||
[overlay]="true"
|
||||
(imageClick)="handleGalleryClick(image, i)"
|
||||
(imageLoaded)="handleImageLoad(image.id)"
|
||||
(imageError)="handleImageError(image.id)">
|
||||
<div slot="overlay">
|
||||
<span>{{ image.title }}</span>
|
||||
</div>
|
||||
</ui-image-container>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event Log -->
|
||||
@if (eventLog.length > 0) {
|
||||
<section class="demo-section">
|
||||
<h3>Event Log</h3>
|
||||
<div class="event-log">
|
||||
@for (event of eventLog.slice(-5); track event.id) {
|
||||
<div class="event-item">
|
||||
<span class="event-time">{{ event.time | date:'HH:mm:ss' }}</span>
|
||||
<span class="event-type">{{ event.type }}</span>
|
||||
<span class="event-message">{{ event.message }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './image-container-demo.component.scss'
|
||||
})
|
||||
export class ImageContainerDemoComponent {
|
||||
// Icons
|
||||
faRefresh = faRefresh;
|
||||
faHeart = faHeart;
|
||||
faPlay = faPlay;
|
||||
|
||||
// Demo data
|
||||
sizes: ImageContainerSize[] = ['sm', 'md', 'lg', 'xl'];
|
||||
aspectRatios: ImageContainerAspectRatio[] = ['1/1', '4/3', '16/9', '3/2', '2/1', '3/4', '9/16'];
|
||||
objectFits: ImageContainerObjectFit[] = ['contain', 'cover', 'fill', 'scale-down'];
|
||||
shapes: ImageContainerShape[] = ['square', 'rounded', 'circle'];
|
||||
|
||||
// Sample images (using placeholder service)
|
||||
sampleImages = {
|
||||
landscape: 'https://picsum.photos/800/600?random=1',
|
||||
portrait: 'https://picsum.photos/600/800?random=2',
|
||||
square: 'https://picsum.photos/600/600?random=3'
|
||||
};
|
||||
|
||||
galleryImages = [
|
||||
{ id: 1, url: 'https://picsum.photos/400/400?random=10', title: 'Nature', alt: 'Beautiful nature scene' },
|
||||
{ id: 2, url: 'https://picsum.photos/400/300?random=11', title: 'Architecture', alt: 'Modern building' },
|
||||
{ id: 3, url: 'https://picsum.photos/400/500?random=12', title: 'Portrait', alt: 'Person portrait' },
|
||||
{ id: 4, url: 'https://picsum.photos/400/400?random=13', title: 'Urban', alt: 'City landscape' },
|
||||
{ id: 5, url: 'https://picsum.photos/400/300?random=14', title: 'Technology', alt: 'Tech equipment' },
|
||||
{ id: 6, url: 'https://picsum.photos/400/400?random=15', title: 'Abstract', alt: 'Abstract art' }
|
||||
];
|
||||
|
||||
// Event tracking
|
||||
eventLog: Array<{id: number, type: string, message: string, time: Date}> = [];
|
||||
private eventId = 1;
|
||||
|
||||
handleImageClick(context: string): void {
|
||||
this.logEvent('click', `Image clicked in ${context} context`);
|
||||
}
|
||||
|
||||
handleGalleryClick(image: any, index: number): void {
|
||||
this.logEvent('gallery-click', `Gallery image "${image.title}" clicked (index ${index})`);
|
||||
}
|
||||
|
||||
handleImageLoad(imageId: number): void {
|
||||
this.logEvent('load', `Image ${imageId} loaded successfully`);
|
||||
}
|
||||
|
||||
handleImageError(imageId: number): void {
|
||||
this.logEvent('error', `Image ${imageId} failed to load`);
|
||||
}
|
||||
|
||||
retryImage(event: Event): void {
|
||||
event.stopPropagation();
|
||||
this.logEvent('retry', 'Retry button clicked for failed image');
|
||||
// In a real implementation, this would retry loading the image
|
||||
}
|
||||
|
||||
private logEvent(type: string, message: string): void {
|
||||
this.eventLog.push({
|
||||
id: this.eventId++,
|
||||
type,
|
||||
message,
|
||||
time: new Date()
|
||||
});
|
||||
|
||||
// Keep only last 20 events
|
||||
if (this.eventLog.length > 20) {
|
||||
this.eventLog.splice(0, this.eventLog.length - 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user