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,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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user