- Move layout components from layouts/ directory to components/layout/ - Reorganize divider component from data-display to layout category - Add comprehensive layout component collection including aspect-ratio, bento-grid, box, breakpoint-container, center, column, dashboard-shell, feed-layout, flex, grid-container, hstack, list-detail-layout, scroll-container, section, sidebar-layout, stack, supporting-pane-layout, tabs-container, and vstack - Update all demo components to match new layout structure - Refactor routing and index exports to reflect reorganized component architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
359 lines
12 KiB
TypeScript
359 lines
12 KiB
TypeScript
import { Component, signal } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { FeedLayoutComponent, FeedItem } from 'ui-essentials';
|
|
|
|
interface DemoFeedItem extends FeedItem {
|
|
title: string;
|
|
content: string;
|
|
timestamp: Date;
|
|
author: string;
|
|
likes: number;
|
|
type: 'text' | 'image' | 'video';
|
|
}
|
|
|
|
@Component({
|
|
selector: 'ui-feed-layout-demo',
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule, FeedLayoutComponent],
|
|
template: `
|
|
<div class="demo-container">
|
|
<h2>Feed Layout Demo</h2>
|
|
|
|
<!-- Size Variants -->
|
|
<section class="demo-section">
|
|
<h3>Sizes</h3>
|
|
<div class="demo-row">
|
|
@for (size of sizes; track size) {
|
|
<div class="demo-feed-wrapper">
|
|
<h4>{{ size | titlecase }}</h4>
|
|
<ui-feed-layout
|
|
[size]="size"
|
|
[loading]="false"
|
|
[enableInfiniteScroll]="false"
|
|
[enableRefresh]="false"
|
|
class="demo-feed demo-feed--preview">
|
|
@for (item of sampleItems.slice(0, 2); track item.id) {
|
|
<div class="demo-feed-item">
|
|
<div class="demo-feed-item__header">
|
|
<strong>{{ item.author }}</strong>
|
|
<span class="demo-feed-item__timestamp">{{ item.timestamp | date:'short' }}</span>
|
|
</div>
|
|
<h5 class="demo-feed-item__title">{{ item.title }}</h5>
|
|
<p class="demo-feed-item__content">{{ item.content }}</p>
|
|
<div class="demo-feed-item__actions">
|
|
<button class="demo-feed-item__action">♡ {{ item.likes }}</button>
|
|
<button class="demo-feed-item__action">💬 Comment</button>
|
|
<button class="demo-feed-item__action">↗ Share</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</ui-feed-layout>
|
|
</div>
|
|
}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Interactive Feed -->
|
|
<section class="demo-section">
|
|
<h3>Interactive Feed with Infinite Scroll</h3>
|
|
<div class="demo-controls">
|
|
<button (click)="resetFeed()" [disabled]="isLoading()">Reset Feed</button>
|
|
<button (click)="toggleError()">
|
|
{{ hasError() ? 'Clear Error' : 'Simulate Error' }}
|
|
</button>
|
|
<label>
|
|
<input type="checkbox" [(ngModel)]="enableRefreshControl" />
|
|
Enable Pull to Refresh
|
|
</label>
|
|
</div>
|
|
|
|
<ui-feed-layout
|
|
size="md"
|
|
[loading]="isLoading()"
|
|
[hasError]="hasError()"
|
|
[errorMessage]="errorMessage"
|
|
[isEmpty]="feedItems().length === 0 && !isLoading()"
|
|
[enableInfiniteScroll]="true"
|
|
[enableRefresh]="enableRefreshControl"
|
|
(loadMore)="loadMoreItems()"
|
|
(refresh)="refreshFeed()"
|
|
(retry)="retryLoad()"
|
|
class="demo-feed demo-feed--interactive"
|
|
#interactiveFeed>
|
|
|
|
@for (item of feedItems(); track item.id) {
|
|
<div class="demo-feed-item">
|
|
<div class="demo-feed-item__header">
|
|
<strong>{{ item.author }}</strong>
|
|
<span class="demo-feed-item__badge demo-feed-item__badge--{{item.type}}">
|
|
{{ item.type }}
|
|
</span>
|
|
<span class="demo-feed-item__timestamp">{{ item.timestamp | date:'short' }}</span>
|
|
</div>
|
|
<h5 class="demo-feed-item__title">{{ item.title }}</h5>
|
|
<p class="demo-feed-item__content">{{ item.content }}</p>
|
|
<div class="demo-feed-item__actions">
|
|
<button
|
|
class="demo-feed-item__action"
|
|
(click)="toggleLike(item)">
|
|
{{ item.likes > 0 ? '❤️' : '♡' }} {{ item.likes }}
|
|
</button>
|
|
<button class="demo-feed-item__action">💬 Comment</button>
|
|
<button class="demo-feed-item__action">↗ Share</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Empty state slot -->
|
|
<div slot="empty" class="demo-empty-state">
|
|
<div class="demo-empty-state__icon">📝</div>
|
|
<h4>No posts yet</h4>
|
|
<p>Be the first to share something!</p>
|
|
<button (click)="addSampleContent()" class="demo-empty-state__button">
|
|
Add Sample Content
|
|
</button>
|
|
</div>
|
|
</ui-feed-layout>
|
|
</section>
|
|
|
|
<!-- States Demo -->
|
|
<section class="demo-section">
|
|
<h3>States</h3>
|
|
<div class="demo-row">
|
|
<div class="demo-feed-wrapper">
|
|
<h4>Loading State</h4>
|
|
<ui-feed-layout
|
|
size="sm"
|
|
[loading]="true"
|
|
[enableInfiniteScroll]="false"
|
|
[enableRefresh]="false"
|
|
class="demo-feed demo-feed--preview">
|
|
@for (item of sampleItems.slice(0, 1); track item.id) {
|
|
<div class="demo-feed-item demo-feed-item--loading">
|
|
<div class="demo-feed-item__header">
|
|
<strong>{{ item.author }}</strong>
|
|
</div>
|
|
<h5 class="demo-feed-item__title">{{ item.title }}</h5>
|
|
<p class="demo-feed-item__content">{{ item.content }}</p>
|
|
</div>
|
|
}
|
|
</ui-feed-layout>
|
|
</div>
|
|
|
|
<div class="demo-feed-wrapper">
|
|
<h4>Error State</h4>
|
|
<ui-feed-layout
|
|
size="sm"
|
|
[loading]="false"
|
|
[hasError]="true"
|
|
errorMessage="Network connection failed"
|
|
[enableInfiniteScroll]="false"
|
|
[enableRefresh]="false"
|
|
class="demo-feed demo-feed--preview">
|
|
</ui-feed-layout>
|
|
</div>
|
|
|
|
<div class="demo-feed-wrapper">
|
|
<h4>Empty State</h4>
|
|
<ui-feed-layout
|
|
size="sm"
|
|
[loading]="false"
|
|
[isEmpty]="true"
|
|
[enableInfiniteScroll]="false"
|
|
[enableRefresh]="false"
|
|
class="demo-feed demo-feed--preview">
|
|
<div slot="empty" class="demo-empty-state demo-empty-state--small">
|
|
<div class="demo-empty-state__icon">📭</div>
|
|
<p>No content available</p>
|
|
</div>
|
|
</ui-feed-layout>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Usage Statistics -->
|
|
<section class="demo-section">
|
|
<h3>Usage Statistics</h3>
|
|
<div class="demo-stats">
|
|
<div class="demo-stat">
|
|
<span class="demo-stat__label">Total Items:</span>
|
|
<span class="demo-stat__value">{{ feedItems().length }}</span>
|
|
</div>
|
|
<div class="demo-stat">
|
|
<span class="demo-stat__label">Load More Calls:</span>
|
|
<span class="demo-stat__value">{{ loadMoreCount }}</span>
|
|
</div>
|
|
<div class="demo-stat">
|
|
<span class="demo-stat__label">Refresh Calls:</span>
|
|
<span class="demo-stat__value">{{ refreshCount }}</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
`,
|
|
styleUrl: './feed-layout-demo.component.scss'
|
|
})
|
|
export class FeedLayoutDemoComponent {
|
|
sizes = ['sm', 'md', 'lg'] as const;
|
|
|
|
protected readonly feedItems = signal<DemoFeedItem[]>([]);
|
|
protected readonly isLoading = signal(false);
|
|
protected readonly hasError = signal(false);
|
|
|
|
protected errorMessage = '';
|
|
protected enableRefreshControl = true;
|
|
protected loadMoreCount = 0;
|
|
protected refreshCount = 0;
|
|
|
|
protected sampleItems: DemoFeedItem[] = [
|
|
{
|
|
id: '1',
|
|
title: 'Getting Started with Angular 19',
|
|
content: 'Angular 19 brings exciting new features including improved SSR, better performance, and enhanced developer experience.',
|
|
timestamp: new Date(Date.now() - 3600000),
|
|
author: 'Sarah Chen',
|
|
likes: 42,
|
|
type: 'text'
|
|
},
|
|
{
|
|
id: '2',
|
|
title: 'Building Responsive Layouts',
|
|
content: 'Learn how to create flexible, mobile-first designs that work beautifully across all devices.',
|
|
timestamp: new Date(Date.now() - 7200000),
|
|
author: 'Mike Rodriguez',
|
|
likes: 28,
|
|
type: 'image'
|
|
},
|
|
{
|
|
id: '3',
|
|
title: 'TypeScript Best Practices',
|
|
content: 'Discover advanced TypeScript patterns and techniques to write more maintainable code.',
|
|
timestamp: new Date(Date.now() - 10800000),
|
|
author: 'Alex Kim',
|
|
likes: 35,
|
|
type: 'video'
|
|
}
|
|
];
|
|
|
|
constructor() {
|
|
this.addSampleContent();
|
|
}
|
|
|
|
loadMoreItems(): void {
|
|
if (this.isLoading() || this.hasError()) return;
|
|
|
|
this.loadMoreCount++;
|
|
this.isLoading.set(true);
|
|
|
|
// Simulate API call delay
|
|
setTimeout(() => {
|
|
const currentItems = this.feedItems();
|
|
const nextBatch = this.generateItems(3, currentItems.length);
|
|
this.feedItems.set([...currentItems, ...nextBatch]);
|
|
this.isLoading.set(false);
|
|
}, 1500);
|
|
}
|
|
|
|
refreshFeed(): void {
|
|
this.refreshCount++;
|
|
this.isLoading.set(true);
|
|
this.hasError.set(false);
|
|
|
|
// Simulate refresh delay
|
|
setTimeout(() => {
|
|
const newItems = this.generateItems(5, 0);
|
|
this.feedItems.set(newItems);
|
|
this.isLoading.set(false);
|
|
}, 1000);
|
|
}
|
|
|
|
retryLoad(): void {
|
|
this.hasError.set(false);
|
|
this.loadMoreItems();
|
|
}
|
|
|
|
resetFeed(): void {
|
|
this.feedItems.set([]);
|
|
this.hasError.set(false);
|
|
this.isLoading.set(false);
|
|
this.loadMoreCount = 0;
|
|
this.refreshCount = 0;
|
|
this.addSampleContent();
|
|
}
|
|
|
|
toggleError(): void {
|
|
if (this.hasError()) {
|
|
this.hasError.set(false);
|
|
this.errorMessage = '';
|
|
} else {
|
|
this.hasError.set(true);
|
|
this.errorMessage = 'Failed to load more content. Please check your connection.';
|
|
}
|
|
}
|
|
|
|
toggleLike(item: DemoFeedItem): void {
|
|
const items = this.feedItems();
|
|
const index = items.findIndex(i => i.id === item.id);
|
|
if (index !== -1) {
|
|
const updatedItems = [...items];
|
|
updatedItems[index] = { ...item, likes: item.likes > 0 ? 0 : 1 };
|
|
this.feedItems.set(updatedItems);
|
|
}
|
|
}
|
|
|
|
addSampleContent(): void {
|
|
const items = this.generateItems(5, 0);
|
|
this.feedItems.set(items);
|
|
}
|
|
|
|
private generateItems(count: number, startId: number): DemoFeedItem[] {
|
|
const contentTemplates = [
|
|
{
|
|
title: 'Web Development Trends',
|
|
content: 'Exploring the latest trends in modern web development and what they mean for developers.',
|
|
author: 'Jane Doe',
|
|
type: 'text' as const
|
|
},
|
|
{
|
|
title: 'UI/UX Design Principles',
|
|
content: 'Understanding the fundamental principles that make great user interfaces and experiences.',
|
|
author: 'John Smith',
|
|
type: 'image' as const
|
|
},
|
|
{
|
|
title: 'Performance Optimization',
|
|
content: 'Tips and techniques for optimizing web application performance and user experience.',
|
|
author: 'Emma Wilson',
|
|
type: 'video' as const
|
|
},
|
|
{
|
|
title: 'Accessibility Matters',
|
|
content: 'Why web accessibility is crucial and how to implement it in your projects.',
|
|
author: 'David Brown',
|
|
type: 'text' as const
|
|
},
|
|
{
|
|
title: 'Mobile-First Design',
|
|
content: 'Best practices for designing mobile-first responsive web applications.',
|
|
author: 'Lisa Garcia',
|
|
type: 'image' as const
|
|
}
|
|
];
|
|
|
|
return Array.from({ length: count }, (_, index) => {
|
|
const template = contentTemplates[index % contentTemplates.length];
|
|
const id = startId + index + 1;
|
|
|
|
return {
|
|
id: id.toString(),
|
|
title: `${template.title} #${id}`,
|
|
content: template.content,
|
|
timestamp: new Date(Date.now() - (index + 1) * 1800000),
|
|
author: template.author,
|
|
likes: Math.floor(Math.random() * 50),
|
|
type: template.type
|
|
};
|
|
});
|
|
}
|
|
} |