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:
skyai_dev
2025-09-03 05:38:09 +10:00
parent c803831f60
commit 5983722793
246 changed files with 52845 additions and 25 deletions

View File

@@ -0,0 +1,399 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { FileUploadComponent, UploadedFile } from '../../../../../../projects/ui-essentials/src/lib/components/forms/file-upload';
@Component({
selector: 'ui-file-upload-demo',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, FileUploadComponent],
template: `
<div class="demo-container">
<h2>File Upload Demo</h2>
<!-- Size Variants -->
<section class="demo-section">
<h3>Sizes</h3>
<div class="demo-grid">
@for (size of sizes; track size) {
<div class="demo-item">
<h4>{{ size.toUpperCase() }} Size</h4>
<p>File upload with {{ size }} sizing.</p>
<ui-file-upload
[size]="size"
label="Upload Files ({{ size }})"
[multiple]="true"
helperText="Select one or more files to upload"
(filesSelected)="onFilesSelected('size-' + size, $event)"
>
</ui-file-upload>
</div>
}
</div>
</section>
<!-- Variant Styles -->
<section class="demo-section">
<h3>Variants</h3>
<div class="demo-grid">
@for (variant of variants; track variant) {
<div class="demo-item">
<h4>{{ variant | titlecase }} Variant</h4>
<p>File upload with {{ variant }} styling.</p>
<ui-file-upload
[variant]="variant"
label="Upload Files ({{ variant }})"
[multiple]="true"
helperText="Drag & drop or browse to select files"
(filesSelected)="onFilesSelected('variant-' + variant, $event)"
>
</ui-file-upload>
</div>
}
</div>
</section>
<!-- File Type Restrictions -->
<section class="demo-section">
<h3>File Type Restrictions</h3>
<div class="demo-grid">
<div class="demo-item">
<h4>Images Only</h4>
<p>Accepts only image files (JPEG, PNG, GIF, WebP).</p>
<ui-file-upload
label="Upload Images"
[acceptedTypes]="['image/jpeg', 'image/png', 'image/gif', 'image/webp']"
accept="image/*"
[multiple]="true"
helperText="Only image files are accepted"
(filesSelected)="onFilesSelected('images', $event)"
>
</ui-file-upload>
</div>
<div class="demo-item">
<h4>Documents Only</h4>
<p>Accepts PDF, Word, and Excel documents.</p>
<ui-file-upload
label="Upload Documents"
[acceptedTypes]="['.pdf', '.doc', '.docx', '.xls', '.xlsx']"
accept=".pdf,.doc,.docx,.xls,.xlsx"
[multiple]="true"
helperText="PDF, Word, and Excel files only"
(filesSelected)="onFilesSelected('documents', $event)"
>
</ui-file-upload>
</div>
</div>
</section>
<!-- Size Limits -->
<section class="demo-section">
<h3>Size & File Limits</h3>
<div class="demo-grid">
<div class="demo-item">
<h4>Size Limit (5MB)</h4>
<p>Maximum file size of 5MB per file.</p>
<ui-file-upload
label="Upload Files (5MB limit)"
[maxFileSize]="5242880"
[multiple]="true"
helperText="Max file size: 5MB"
(filesSelected)="onFilesSelected('size-limit', $event)"
>
</ui-file-upload>
</div>
<div class="demo-item">
<h4>File Count Limit</h4>
<p>Maximum of 3 files can be selected.</p>
<ui-file-upload
label="Upload Files (3 max)"
[maxFiles]="3"
[multiple]="true"
helperText="Maximum 3 files allowed"
(filesSelected)="onFilesSelected('file-limit', $event)"
>
</ui-file-upload>
</div>
<div class="demo-item">
<h4>Single File</h4>
<p>Only one file can be selected.</p>
<ui-file-upload
label="Upload Single File"
[multiple]="false"
helperText="Select a single file to upload"
(filesSelected)="onFilesSelected('single-file', $event)"
>
</ui-file-upload>
</div>
</div>
</section>
<!-- States -->
<section class="demo-section">
<h3>States</h3>
<div class="demo-grid">
<div class="demo-item">
<h4>Disabled State</h4>
<p>File upload in disabled state.</p>
<ui-file-upload
label="Disabled Upload"
[disabled]="true"
helperText="This upload is disabled"
(filesSelected)="onFilesSelected('disabled', $event)"
>
</ui-file-upload>
</div>
<div class="demo-item">
<h4>Error State</h4>
<p>File upload showing error state.</p>
<ui-file-upload
label="Upload with Error"
state="error"
errorMessage="Please select valid files"
[multiple]="true"
(filesSelected)="onFilesSelected('error', $event)"
>
</ui-file-upload>
</div>
<div class="demo-item">
<h4>Success State</h4>
<p>File upload showing success state.</p>
<ui-file-upload
label="Successful Upload"
state="success"
helperText="Files uploaded successfully!"
[multiple]="true"
(filesSelected)="onFilesSelected('success', $event)"
>
</ui-file-upload>
</div>
</div>
</section>
<!-- Reactive Form Integration -->
<section class="demo-section">
<h3>Reactive Form Integration</h3>
<div class="demo-item">
<h4>Form with Validation</h4>
<p>File upload integrated with Angular reactive forms and validation.</p>
<form [formGroup]="uploadForm" (ngSubmit)="onSubmit()">
<ui-file-upload
label="Required File Upload"
formControlName="files"
[multiple]="true"
[required]="true"
[acceptedTypes]="['.pdf', '.doc', '.docx', 'image/*']"
[maxFileSize]="10485760"
[maxFiles]="5"
helperText="Required field - select 1-5 files (PDF, Word, or images, max 10MB each)"
[state]="getFormFieldState('files')"
[errorMessage]="getFormFieldError('files')"
(filesSelected)="onFormFilesSelected($event)"
>
</ui-file-upload>
<div class="demo-actions">
<button
type="submit"
class="demo-button"
[disabled]="uploadForm.invalid"
>
Submit Form
</button>
<button
type="button"
class="demo-button demo-button--secondary"
(click)="resetForm()"
>
Reset
</button>
<div class="demo-info">
Form Status: {{ uploadForm.status }} |
Form Valid: {{ uploadForm.valid }} |
Files Count: {{ uploadForm.get('files')?.value?.length || 0 }}
</div>
</div>
</form>
@if (submittedFiles.length > 0) {
<div class="file-list-demo">
<h5>Submitted Files:</h5>
@for (file of submittedFiles; track file.id) {
<div class="file-item">
<div>
<div class="file-info">{{ file.name }}</div>
<div class="file-size">{{ formatFileSize(file.size) }}</div>
</div>
</div>
}
</div>
}
</div>
</section>
<!-- Event Handling -->
<section class="demo-section">
<h3>Event Handling</h3>
<div class="demo-item">
<h4>Event Monitoring</h4>
<p>Monitor file upload events in real-time.</p>
<ui-file-upload
label="Event Demo Upload"
[multiple]="true"
[maxFiles]="3"
helperText="Add/remove files to see events below"
(filesSelected)="onEventFilesSelected($event)"
(fileAdded)="onFileAdded($event)"
(fileRemoved)="onFileRemoved($event)"
>
</ui-file-upload>
<div class="file-list-demo">
<h5>Recent Events:</h5>
@if (recentEvents.length === 0) {
<p class="demo-info">No events yet. Add or remove files to see events.</p>
} @else {
@for (event of recentEvents.slice(-5).reverse(); track $index) {
<div class="file-item">
<div>
<div class="file-info">{{ event.type }}: {{ event.fileName }}</div>
<div class="file-size">{{ event.timestamp | date:'medium' }}</div>
</div>
</div>
}
}
</div>
</div>
</section>
<!-- Code Example -->
<section class="demo-section">
<h3>Usage Example</h3>
<div class="code-demo">
<pre><code>{{ codeExample }}</code></pre>
</div>
</section>
</div>
`,
styleUrl: './file-upload-demo.component.scss'
})
export class FileUploadDemoComponent {
sizes = ['sm', 'md', 'lg'] as const;
variants = ['outlined', 'filled', 'underlined'] as const;
uploadForm = new FormGroup({
files: new FormControl<UploadedFile[]>([], [Validators.required])
});
submittedFiles: UploadedFile[] = [];
recentEvents: Array<{type: string, fileName: string, timestamp: Date}> = [];
readonly codeExample = `import { FileUploadComponent, UploadedFile } from 'ui-essentials';
// Basic usage
<ui-file-upload
label="Upload Files"
[multiple]="true"
[acceptedTypes]="['image/*', '.pdf']"
[maxFileSize]="5242880"
[maxFiles]="5"
helperText="Select up to 5 files (images or PDF, max 5MB each)"
(filesSelected)="onFilesSelected($event)"
>
</ui-file-upload>
// With reactive forms
<ui-file-upload
formControlName="files"
[required]="true"
[state]="getFieldState('files')"
[errorMessage]="getFieldError('files')"
>
</ui-file-upload>
// Event handling
onFilesSelected(files: UploadedFile[]): void {
console.log('Selected files:', files);
files.forEach(file => {
console.log(file.name, file.size, file.type);
});
}`;
onFilesSelected(context: string, files: UploadedFile[]): void {
console.log(`Files selected in ${context}:`, files);
}
onFormFilesSelected(files: UploadedFile[]): void {
this.uploadForm.patchValue({ files });
this.uploadForm.get('files')?.markAsTouched();
}
onEventFilesSelected(files: UploadedFile[]): void {
this.addEvent('filesSelected', `${files.length} files total`);
}
onFileAdded(file: UploadedFile): void {
this.addEvent('fileAdded', file.name);
}
onFileRemoved(file: UploadedFile): void {
this.addEvent('fileRemoved', file.name);
}
private addEvent(type: string, fileName: string): void {
this.recentEvents.push({
type,
fileName,
timestamp: new Date()
});
}
onSubmit(): void {
if (this.uploadForm.valid) {
this.submittedFiles = this.uploadForm.get('files')?.value || [];
console.log('Form submitted with files:', this.submittedFiles);
}
}
resetForm(): void {
this.uploadForm.reset();
this.uploadForm.get('files')?.setValue([]);
this.submittedFiles = [];
}
getFormFieldState(fieldName: string): 'default' | 'error' | 'success' {
const control = this.uploadForm.get(fieldName);
if (control?.invalid && control?.touched) {
return 'error';
}
if (control?.valid && control?.value?.length > 0) {
return 'success';
}
return 'default';
}
getFormFieldError(fieldName: string): string {
const control = this.uploadForm.get(fieldName);
if (control?.invalid && control?.touched) {
if (control.errors?.['required']) {
return 'Please select at least one file';
}
}
return '';
}
formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}