Files
ui-essentials/projects/demo-ui-essentials/src/app/demos/list-demo/list-demo.component.ts
skyai_dev 5983722793 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>
2025-09-03 05:38:09 +10:00

622 lines
20 KiB
TypeScript

import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
ListItemComponent,
ListContainerComponent,
ListItemData
} from '../../../../../ui-essentials/src/lib/components/data-display/list';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import {
faInbox,
faStar,
faPaperPlane,
faEdit,
faArchive,
faTrash,
faPhone,
faComment,
faPlay,
faDownload,
faCheck,
faChevronRight,
faPlus,
faMinus
} from '@fortawesome/free-solid-svg-icons';
@Component({
selector: 'ui-list-demo',
standalone: true,
imports: [
CommonModule,
ListItemComponent,
ListContainerComponent,
FontAwesomeModule
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div style="padding: 2rem;">
<h2>List Component Showcase</h2>
<!-- Basic Lists with Size Variants -->
<section style="margin-bottom: 3rem;">
<h3>Basic Lists - Size Variants</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; margin-bottom: 2rem;">
<div>
<h4>Small (sm)</h4>
<ui-list-container
elevation="sm"
spacing="xs"
[rounded]="true"
ariaLabel="Small navigation list">
@for (item of navigationItems; track item.primary) {
<ui-list-item
[data]="item"
size="sm"
lines="one"
variant="text"
[divider]="true"
>
<fa-icon [icon]="getIconForItem(item.primary)" slot="trailing"></fa-icon>
</ui-list-item>
}
</ui-list-container>
</div>
<div>
<h4>Medium (md)</h4>
<ui-list-container
elevation="sm"
spacing="xs"
[rounded]="true"
ariaLabel="Medium navigation list">
@for (item of navigationItems; track item.primary) {
<ui-list-item
[data]="item"
size="md"
lines="one"
variant="text"
[divider]="true"
>
<fa-icon [icon]="getIconForItem(item.primary)" slot="trailing"></fa-icon>
</ui-list-item>
}
</ui-list-container>
</div>
<div>
<h4>Large (lg)</h4>
<ui-list-container
elevation="sm"
spacing="xs"
[rounded]="true"
ariaLabel="Large navigation list">
@for (item of navigationItems; track item.primary) {
<ui-list-item
[data]="item"
size="lg"
lines="one"
variant="text"
[divider]="true"
>
<fa-icon [icon]="getIconForItem(item.primary)" slot="trailing"></fa-icon>
</ui-list-item>
}
</ui-list-container>
</div>
</div>
</section>
<!-- Multi-line Lists -->
<section style="margin-bottom: 3rem;">
<h3>Multi-line Lists</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 2rem;">
<div>
<h4>Two Lines</h4>
<ui-list-container
elevation="md"
spacing="sm"
[rounded]="true"
ariaLabel="Two line task list">
@for (item of twoLineItems; track item.primary) {
<ui-list-item
[data]="item"
size="md"
lines="two"
variant="text"
>
<button
slot="trailing"
style="background: none; border: none; padding: 8px; cursor: pointer; color: #6c757d;"
(click)="handleAction('complete', item.primary)"
[attr.aria-label]="'Complete ' + item.primary">
<fa-icon [icon]="faCheck"></fa-icon>
</button>
</ui-list-item>
}
</ui-list-container>
</div>
<div>
<h4>Three Lines</h4>
<ui-list-container
elevation="md"
spacing="sm"
[rounded]="true"
ariaLabel="Three line task list">
@for (item of threeLineItems; track item.primary) {
<ui-list-item
[data]="item"
size="lg"
lines="three"
variant="text"
>
<div slot="trailing" style="display: flex; gap: 0.5rem;">
<button
style="background: none; border: none; padding: 4px; cursor: pointer; color: #28a745;"
(click)="handleAction('approve', item.primary)"
[attr.aria-label]="'Approve ' + item.primary">
<fa-icon [icon]="faCheck"></fa-icon>
</button>
</div>
</ui-list-item>
}
</ui-list-container>
</div>
</div>
</section>
<!-- Avatar Lists -->
<section style="margin-bottom: 3rem;">
<h3>Lists with Avatars</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 2rem;">
<div>
<h4>Contacts - Avatar + One Line</h4>
<ui-list-container
elevation="sm"
spacing="xs"
[rounded]="true"
ariaLabel="Contact list">
@for (item of contactItems; track item.primary) {
<ui-list-item
[data]="item"
size="md"
lines="one"
variant="avatar"
>
<div slot="trailing" style="display: flex; gap: 0.75rem;">
<button
style="background: none; border: none; padding: 4px; cursor: pointer; color: #007bff;"
(click)="handleAction('call', item.primary)"
[attr.aria-label]="'Call ' + item.primary">
<fa-icon [icon]="faPhone"></fa-icon>
</button>
<button
style="background: none; border: none; padding: 4px; cursor: pointer; color: #28a745;"
(click)="handleAction('message', item.primary)"
[attr.aria-label]="'Message ' + item.primary">
<fa-icon [icon]="faComment"></fa-icon>
</button>
</div>
</ui-list-item>
}
</ui-list-container>
</div>
<div>
<h4>Messages - Avatar + Two Lines</h4>
<ui-list-container
elevation="sm"
spacing="xs"
[rounded]="true"
ariaLabel="Message list">
@for (item of messageItems; track item.primary) {
<ui-list-item
[data]="item"
size="lg"
lines="two"
variant="avatar"
>
<span
slot="trailing"
style="font-size: 0.75rem; color: #6c757d;">
2h
</span>
</ui-list-item>
}
</ui-list-container>
</div>
</div>
</section>
<!-- Media Lists -->
<section style="margin-bottom: 3rem;">
<h3>Lists with Media</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 2rem;">
<div>
<h4>Music - Media + Two Lines</h4>
<ui-list-container
elevation="md"
spacing="sm"
[rounded]="true"
ariaLabel="Music playlist">
@for (item of musicItems; track item.primary) {
<ui-list-item
[data]="item"
size="lg"
lines="two"
variant="media"
>
<div slot="trailing" style="display: flex; gap: 0.75rem; align-items: center;">
<span style="font-size: 0.875rem; color: #6c757d;">3:42</span>
<button
style="background: none; border: none; padding: 8px; cursor: pointer; color: #007bff;"
(click)="handleAction('play', item.primary)"
[attr.aria-label]="'Play ' + item.primary">
<fa-icon [icon]="faPlay"></fa-icon>
</button>
</div>
</ui-list-item>
}
</ui-list-container>
</div>
<div>
<h4>Videos - Media + Three Lines</h4>
<ui-list-container
elevation="md"
spacing="sm"
[rounded]="true"
ariaLabel="Video library">
@for (item of videoItems; track item.primary) {
<ui-list-item
[data]="item"
size="lg"
lines="three"
variant="media"
>
<div slot="trailing" style="display: flex; flex-direction: column; align-items: center; gap: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.25rem;">
<fa-icon [icon]="faStar" style="color: #ffc107; font-size: 0.75rem;"></fa-icon>
<span style="font-size: 0.75rem; color: #6c757d;">4.8</span>
</div>
<button
style="background: none; border: none; padding: 4px; cursor: pointer; color: #007bff;"
(click)="handleAction('download', item.primary)"
[attr.aria-label]="'Download ' + item.primary">
<fa-icon [icon]="faDownload"></fa-icon>
</button>
</div>
</ui-list-item>
}
</ui-list-container>
</div>
</div>
</section>
<!-- Interactive Demo -->
<section style="margin-bottom: 3rem;">
<h3>Interactive Demo</h3>
<p style="margin-bottom: 1rem; color: #6c757d;">
Click items to interact with them:
</p>
<div style="max-width: 600px;">
<ui-list-container
elevation="sm"
spacing="sm"
[rounded]="true"
ariaLabel="Interactive demo list">
@for (item of interactiveItems(); track item.primary) {
<ui-list-item
[data]="item"
size="md"
lines="two"
variant="avatar"
style="cursor: pointer;"
(click)="toggleInteractiveItem(item.primary)"
>
<div slot="trailing" style="display: flex; align-items: center; gap: 0.5rem;">
@if (item.selected) {
<span style="background: #28a745; color: white; padding: 4px 8px; border-radius: 12px; font-size: 0.75rem;">
Enabled
</span>
} @else if (item.disabled) {
<span style="background: #dc3545; color: white; padding: 4px 8px; border-radius: 12px; font-size: 0.75rem;">
Disabled
</span>
} @else {
<span style="background: #6c757d; color: white; padding: 4px 8px; border-radius: 12px; font-size: 0.75rem;">
Click to enable
</span>
}
</div>
</ui-list-item>
}
</ui-list-container>
</div>
</section>
<!-- Code Examples -->
<section style="margin-bottom: 3rem;">
<h3>Usage Examples</h3>
<div style="background: #f8f9fa; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #007bff;">
<h4>Basic List with Icons:</h4>
<pre style="background: #fff; padding: 1rem; border-radius: 4px; overflow-x: auto; font-size: 0.875rem;"><code>&lt;ui-list-container elevation="sm" spacing="xs" [rounded]="true"&gt;
&lt;ui-list-item
[data]="&#123;primary: 'Inbox'&#125;"
size="md"
lines="one"
variant="text"&gt;
&lt;fa-icon [icon]="faInbox" slot="trailing"&gt;&lt;/fa-icon&gt;
&lt;/ui-list-item&gt;
&lt;/ui-list-container&gt;</code></pre>
<h4>List with Avatar and Actions:</h4>
<pre style="background: #fff; padding: 1rem; border-radius: 4px; overflow-x: auto; font-size: 0.875rem;"><code>&lt;ui-list-container elevation="md" spacing="sm" [rounded]="true"&gt;
&lt;ui-list-item
[data]="&#123;
primary: 'John Doe',
secondary: 'john&#64;example.com',
avatarSrc: 'avatar.jpg'
&#125;"
size="lg"
lines="two"
variant="avatar"&gt;
&lt;button slot="trailing" (click)="call()"&gt;
&lt;fa-icon [icon]="faPhone"&gt;&lt;/fa-icon&gt;
&lt;/button&gt;
&lt;/ui-list-item&gt;
&lt;/ui-list-container&gt;</code></pre>
</div>
</section>
@if (lastAction()) {
<div style="position: fixed; bottom: 20px; right: 20px; background: #007bff; color: white; padding: 1rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-width: 300px;">
<strong>Action:</strong> {{ lastAction() }}
</div>
}
</div>
`,
styles: [`
h2 {
color: hsl(279, 14%, 11%);
font-size: 2rem;
margin-bottom: 2rem;
border-bottom: 2px solid hsl(258, 100%, 47%);
padding-bottom: 0.5rem;
}
h3 {
color: hsl(279, 14%, 25%);
font-size: 1.5rem;
margin-bottom: 1rem;
margin-top: 2rem;
}
h4 {
color: hsl(287, 12%, 35%);
font-size: 1.125rem;
margin-bottom: 0.75rem;
margin-top: 1rem;
}
section {
border: 1px solid hsl(289, 14%, 90%);
border-radius: 12px;
padding: 1.5rem;
background: hsl(286, 20%, 99%);
margin-bottom: 2rem;
}
pre {
font-size: 0.875rem;
line-height: 1.5;
margin: 0.5rem 0;
white-space: pre-wrap;
}
code {
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Courier New', monospace;
color: #d63384;
}
button {
transition: all 0.15s ease;
}
button:hover {
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
fa-icon {
transition: color 0.2s ease;
}
`]
})
export class ListDemoComponent {
// State signals
lastAction = signal('');
// Demo data
navigationItems: ListItemData[] = [
{ primary: 'Inbox' },
{ primary: 'Starred' },
{ primary: 'Sent' },
{ primary: 'Drafts' },
{ primary: 'Archive' },
{ primary: 'Trash' }
];
twoLineItems: ListItemData[] = [
{
primary: 'Review Design System Updates',
secondary: 'Check new component specifications and design tokens'
},
{
primary: 'Team Standup Meeting',
secondary: 'Daily sync with development and design teams'
},
{
primary: 'Code Review - Authentication',
secondary: 'Review pull request #247 for OAuth integration'
}
];
threeLineItems: ListItemData[] = [
{
primary: 'Angular 19 Migration',
secondary: 'Upgrade project to latest Angular version with control flow',
tertiary: 'Estimated completion: Next Sprint • Priority: High'
},
{
primary: 'Design System Documentation',
secondary: 'Create comprehensive documentation for all components',
tertiary: 'Status: In Progress • Assigned: Design Team'
}
];
contactItems: ListItemData[] = [
{
primary: 'Sarah Wilson',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=sarah',
avatarAlt: 'Sarah Wilson avatar'
},
{
primary: 'Alex Chen',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=alex',
avatarAlt: 'Alex Chen avatar'
},
{
primary: 'Mike Rodriguez',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=mike',
avatarAlt: 'Mike Rodriguez avatar'
}
];
messageItems: ListItemData[] = [
{
primary: 'John Doe',
secondary: 'Hey! How\'s the new component library coming along?',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=john',
avatarAlt: 'John Doe avatar'
},
{
primary: 'Jane Smith',
secondary: 'The design tokens look great. Ready for review when you are.',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=jane',
avatarAlt: 'Jane Smith avatar'
}
];
musicItems: ListItemData[] = [
{
primary: 'Bohemian Rhapsody',
secondary: 'Queen • A Night at the Opera',
mediaSrc: 'https://picsum.photos/80/80?random=1',
mediaAlt: 'Album cover'
},
{
primary: 'Hotel California',
secondary: 'Eagles • Hotel California',
mediaSrc: 'https://picsum.photos/80/80?random=2',
mediaAlt: 'Album cover'
}
];
videoItems: ListItemData[] = [
{
primary: 'Angular Fundamentals',
secondary: 'Complete guide to modern Angular development',
tertiary: 'Duration: 2h 30m • Updated: Today • 1.2M views',
mediaSrc: 'https://picsum.photos/80/80?random=5',
mediaAlt: 'Video thumbnail'
}
];
interactiveItems = signal<ListItemData[]>([
{
primary: 'Email Notifications',
secondary: 'Receive emails about important updates',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=email',
selected: false,
disabled: false
},
{
primary: 'SMS Alerts',
secondary: 'Get text messages for urgent notifications',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=sms',
selected: true,
disabled: false
},
{
primary: 'Marketing Communications',
secondary: 'Promotional offers and product updates',
avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=marketing',
selected: false,
disabled: true
}
]);
// Font Awesome icons
faInbox = faInbox;
faStar = faStar;
faPaperPlane = faPaperPlane;
faEdit = faEdit;
faArchive = faArchive;
faTrash = faTrash;
faPhone = faPhone;
faComment = faComment;
faPlay = faPlay;
faDownload = faDownload;
faCheck = faCheck;
faChevronRight = faChevronRight;
faPlus = faPlus;
faMinus = faMinus;
// Helper methods
getIconForItem(itemName: string): any {
const iconMap: { [key: string]: any } = {
'Inbox': faInbox,
'Starred': faStar,
'Sent': faPaperPlane,
'Drafts': faEdit,
'Archive': faArchive,
'Trash': faTrash
};
return iconMap[itemName] || faInbox;
}
// Event handlers
handleAction(action: string, item?: string): void {
const message = item
? `${action} action on "${item}" at ${new Date().toLocaleTimeString()}`
: `${action} action at ${new Date().toLocaleTimeString()}`;
this.lastAction.set(message);
console.log(`List action: ${action}`, item);
// Clear the message after 3 seconds
setTimeout(() => this.lastAction.set(''), 3000);
}
toggleInteractiveItem(itemName: string): void {
this.interactiveItems.update(items =>
items.map(item => {
if (item.primary === itemName && !item.disabled) {
return { ...item, selected: !item.selected };
}
return item;
})
);
this.handleAction('toggle', itemName);
}
}