Add comprehensive library expansion with new components and demos
- Add new libraries: ui-accessibility, ui-animations, ui-backgrounds, ui-code-display, ui-data-utils, ui-font-manager, hcl-studio - Add extensive layout components: gallery-grid, infinite-scroll-container, kanban-board, masonry, split-view, sticky-layout - Add comprehensive demo components for all new features - Update project configuration and dependencies - Expand component exports and routing structure - Add UI landing pages planning document 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,448 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
// Mock font combinations since ui-font-manager is not yet available
|
||||
const FONT_COMBINATIONS = {
|
||||
'Modern Clean': {
|
||||
sans: 'Inter',
|
||||
serif: 'Playfair Display',
|
||||
mono: 'JetBrains Mono',
|
||||
display: 'Inter'
|
||||
},
|
||||
'Classic Editorial': {
|
||||
sans: 'Source Sans Pro',
|
||||
serif: 'Lora',
|
||||
mono: 'Source Code Pro',
|
||||
display: 'Source Sans Pro'
|
||||
},
|
||||
'Friendly Modern': {
|
||||
sans: 'Poppins',
|
||||
serif: 'Merriweather',
|
||||
mono: 'Fira Code',
|
||||
display: 'Poppins'
|
||||
},
|
||||
'Professional': {
|
||||
sans: 'Roboto',
|
||||
serif: 'EB Garamond',
|
||||
mono: 'Roboto Mono',
|
||||
display: 'Roboto'
|
||||
}
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-font-manager-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<h1>UI Font Manager Demo</h1>
|
||||
<p class="demo-description">
|
||||
Dynamically load Google Fonts and switch between font themes with smooth transitions.
|
||||
Select a font preset to apply it globally to the entire application.
|
||||
</p>
|
||||
|
||||
<!-- Font Preset Selection -->
|
||||
<section class="demo-section">
|
||||
<h2>Font Preset Selection</h2>
|
||||
<p class="preset-description">
|
||||
Choose from curated font combinations. The selected preset will be applied to the entire application.
|
||||
</p>
|
||||
|
||||
<div class="preset-controls">
|
||||
<div class="preset-selector">
|
||||
<label for="preset-select">Choose Font Preset:</label>
|
||||
<select
|
||||
id="preset-select"
|
||||
[value]="selectedPreset"
|
||||
(change)="onPresetChange($event)">
|
||||
<option value="">Select a preset...</option>
|
||||
<option *ngFor="let preset of fontPresets" [value]="preset.key">
|
||||
{{preset.name}} - {{preset.description}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="transition-selector" *ngIf="selectedPreset">
|
||||
<label for="transition-select">Transition Effect:</label>
|
||||
<select id="transition-select" [(ngModel)]="selectedTransition">
|
||||
<option value="fade">Fade</option>
|
||||
<option value="scale">Scale</option>
|
||||
<option value="typewriter">Typewriter</option>
|
||||
</select>
|
||||
<button
|
||||
class="apply-btn"
|
||||
(click)="applyPresetWithTransition()">
|
||||
Apply with {{selectedTransition | titlecase}} Effect
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Preset Display -->
|
||||
<div class="current-preset" *ngIf="currentPreset">
|
||||
<h4>Currently Applied: {{currentPreset.name}}</h4>
|
||||
<div class="preset-fonts">
|
||||
<div class="font-assignment">
|
||||
<span class="font-type">Sans-serif:</span>
|
||||
<span class="font-name">{{currentPreset.fonts.sans}}</span>
|
||||
</div>
|
||||
<div class="font-assignment">
|
||||
<span class="font-type">Serif:</span>
|
||||
<span class="font-name">{{currentPreset.fonts.serif}}</span>
|
||||
</div>
|
||||
<div class="font-assignment">
|
||||
<span class="font-type">Monospace:</span>
|
||||
<span class="font-name">{{currentPreset.fonts.mono}}</span>
|
||||
</div>
|
||||
<div class="font-assignment">
|
||||
<span class="font-type">Display:</span>
|
||||
<span class="font-name">{{currentPreset.fonts.display}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Typography Preview -->
|
||||
<section class="demo-section">
|
||||
<h2>Typography Preview</h2>
|
||||
<p class="preview-description">See how the selected fonts look in a real application context.</p>
|
||||
|
||||
<div class="typography-showcase">
|
||||
<div class="type-sample">
|
||||
<h1 style="font-family: var(--font-family-display)">Display Heading (H1)</h1>
|
||||
<h2 style="font-family: var(--font-family-sans)">Sans-serif Heading (H2)</h2>
|
||||
<h3 style="font-family: var(--font-family-serif)">Serif Heading (H3)</h3>
|
||||
|
||||
<p style="font-family: var(--font-family-sans)">
|
||||
This is a paragraph using the sans-serif font. It demonstrates how readable body text
|
||||
appears with the currently selected font combination. Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</p>
|
||||
|
||||
<blockquote style="font-family: var(--font-family-serif)">
|
||||
"This is a quote using the serif font, which often provides better readability
|
||||
for longer text passages and adds elegance to quoted content."
|
||||
</blockquote>
|
||||
|
||||
<div class="font-info">
|
||||
Current fonts: Sans (var(--font-family-sans)) | Serif (var(--font-family-serif)) |
|
||||
Mono (var(--font-family-mono)) | Display (var(--font-family-display))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Available Font Presets -->
|
||||
<section class="demo-section">
|
||||
<h2>Available Font Presets</h2>
|
||||
<div class="themes-grid">
|
||||
<div
|
||||
*ngFor="let preset of fontPresets"
|
||||
class="theme-card"
|
||||
[class.active]="preset.key === selectedPreset">
|
||||
|
||||
<h4>{{preset.name}}</h4>
|
||||
<p class="theme-description">{{preset.description}}</p>
|
||||
|
||||
<div class="theme-fonts">
|
||||
<div class="theme-font">
|
||||
<strong>Sans:</strong>
|
||||
<span>{{preset.fonts.sans}}</span>
|
||||
</div>
|
||||
<div class="theme-font">
|
||||
<strong>Serif:</strong>
|
||||
<span>{{preset.fonts.serif}}</span>
|
||||
</div>
|
||||
<div class="theme-font">
|
||||
<strong>Mono:</strong>
|
||||
<span>{{preset.fonts.mono}}</span>
|
||||
</div>
|
||||
<div class="theme-font">
|
||||
<strong>Display:</strong>
|
||||
<span>{{preset.fonts.display}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="apply-theme-btn"
|
||||
(click)="applyPreset(preset.key)">
|
||||
Apply {{preset.name}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Popular Individual Fonts -->
|
||||
<section class="demo-section">
|
||||
<h3>Popular Individual Fonts</h3>
|
||||
<p class="section-description">Load individual fonts to test and preview them.</p>
|
||||
|
||||
<div class="font-controls">
|
||||
<div class="font-grid">
|
||||
<button
|
||||
*ngFor="let font of popularFonts"
|
||||
class="font-button"
|
||||
[class.loaded]="isLoaded(font)"
|
||||
(click)="loadSingleFont(font)">
|
||||
|
||||
{{font}}
|
||||
<span class="load-status" *ngIf="isLoaded(font)">✓</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event Log -->
|
||||
<section class="demo-section" *ngIf="eventLog.length > 0">
|
||||
<h3>Event Log</h3>
|
||||
<div class="event-log">
|
||||
<div
|
||||
*ngFor="let event of eventLog.slice(-10)"
|
||||
class="event-item"
|
||||
[class]="event.type">
|
||||
<span class="event-time">{{event.time}}</span>
|
||||
<span class="event-message">{{event.message}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './font-manager-demo.component.scss'
|
||||
})
|
||||
export class FontManagerDemoComponent implements OnInit, OnDestroy {
|
||||
popularFonts = ['Inter', 'Roboto', 'Playfair Display', 'JetBrains Mono', 'Montserrat', 'Lora'];
|
||||
loadedFonts = new Set<string>();
|
||||
|
||||
// Font preset management
|
||||
fontPresets = [
|
||||
{
|
||||
key: 'modern-clean',
|
||||
name: 'Modern Clean',
|
||||
description: 'Contemporary and minimal design with Inter and Playfair Display',
|
||||
fonts: FONT_COMBINATIONS['Modern Clean']
|
||||
},
|
||||
{
|
||||
key: 'classic-editorial',
|
||||
name: 'Classic Editorial',
|
||||
description: 'Traditional publishing style with Source Sans Pro and Lora',
|
||||
fonts: FONT_COMBINATIONS['Classic Editorial']
|
||||
},
|
||||
{
|
||||
key: 'friendly-modern',
|
||||
name: 'Friendly Modern',
|
||||
description: 'Approachable and warm with Poppins and Merriweather',
|
||||
fonts: FONT_COMBINATIONS['Friendly Modern']
|
||||
},
|
||||
{
|
||||
key: 'professional',
|
||||
name: 'Professional',
|
||||
description: 'Corporate and reliable with Roboto and EB Garamond',
|
||||
fonts: FONT_COMBINATIONS['Professional']
|
||||
},
|
||||
{
|
||||
key: 'system',
|
||||
name: 'System Default',
|
||||
description: 'Use system fonts for optimal performance',
|
||||
fonts: {
|
||||
sans: 'system-ui',
|
||||
serif: 'ui-serif',
|
||||
mono: 'ui-monospace',
|
||||
display: 'system-ui'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
selectedPreset: string = '';
|
||||
selectedTransition: 'fade' | 'scale' | 'typewriter' = 'fade';
|
||||
currentPreset: any = null;
|
||||
|
||||
eventLog: Array<{time: string, message: string, type: 'success' | 'error' | 'info'}> = [];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.logEvent('Component initialized', 'info');
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
onThemeChanged(themeName: string): void {
|
||||
this.logEvent(`Theme changed to: ${themeName}`, 'success');
|
||||
}
|
||||
|
||||
onFontLoaded(fontName: string): void {
|
||||
this.logEvent(`Font loaded: ${fontName}`, 'success');
|
||||
this.loadedFonts.add(fontName);
|
||||
}
|
||||
|
||||
onFontError(event: {fontName: string, error: string}): void {
|
||||
this.logEvent(`Font error - ${event.fontName}: ${event.error}`, 'error');
|
||||
}
|
||||
|
||||
loadSingleFont(fontName: string): void {
|
||||
this.logEvent(`Loading font: ${fontName}`, 'info');
|
||||
|
||||
// Load font via Google Fonts API
|
||||
this.loadGoogleFont(fontName);
|
||||
this.loadedFonts.add(fontName);
|
||||
this.logEvent(`Successfully loaded: ${fontName}`, 'success');
|
||||
}
|
||||
|
||||
applyTheme(themeName: string): void {
|
||||
this.logEvent(`Applying theme: ${themeName}`, 'info');
|
||||
this.logEvent(`Successfully applied theme: ${themeName}`, 'success');
|
||||
}
|
||||
|
||||
isLoaded(fontName: string): boolean {
|
||||
return this.loadedFonts.has(fontName);
|
||||
}
|
||||
|
||||
private loadGoogleFont(fontName: string): void {
|
||||
const link = document.createElement('link');
|
||||
link.href = `https://fonts.googleapis.com/css2?family=${fontName.replace(' ', '+')}&display=swap`;
|
||||
link.rel = 'stylesheet';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
onPresetChange(event: Event): void {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
this.selectedPreset = target.value;
|
||||
|
||||
if (this.selectedPreset) {
|
||||
const preset = this.fontPresets.find(p => p.key === this.selectedPreset);
|
||||
if (preset) {
|
||||
this.logEvent(`Selected preset: ${preset.name}`, 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyPreset(presetKey: string): void {
|
||||
const preset = this.fontPresets.find(p => p.key === presetKey);
|
||||
if (!preset) {
|
||||
this.logEvent(`Preset not found: ${presetKey}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedPreset = presetKey;
|
||||
this.logEvent(`Applying preset: ${preset.name}`, 'info');
|
||||
|
||||
// Apply fonts to document root for global effect
|
||||
this.applyFontsGlobally(preset.fonts);
|
||||
this.currentPreset = preset;
|
||||
}
|
||||
|
||||
applyPresetWithTransition(): void {
|
||||
const preset = this.fontPresets.find(p => p.key === this.selectedPreset);
|
||||
if (!preset) return;
|
||||
|
||||
this.logEvent(`Applying preset "${preset.name}" with ${this.selectedTransition} transition`, 'info');
|
||||
|
||||
// Apply fonts with transition effect
|
||||
const fontVariables = {
|
||||
'--font-family-sans': this.buildGlobalFontStack(preset.fonts.sans),
|
||||
'--font-family-serif': this.buildGlobalFontStack(preset.fonts.serif),
|
||||
'--font-family-mono': this.buildGlobalFontStack(preset.fonts.mono),
|
||||
'--font-family-display': this.buildGlobalFontStack(preset.fonts.display)
|
||||
};
|
||||
|
||||
this.applyFontsWithTransition(fontVariables, this.selectedTransition);
|
||||
this.currentPreset = preset;
|
||||
}
|
||||
|
||||
private applyFontsGlobally(fonts: any): void {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Set CSS custom properties on the document root
|
||||
root.style.setProperty('--font-family-sans', this.buildGlobalFontStack(fonts.sans));
|
||||
root.style.setProperty('--font-family-serif', this.buildGlobalFontStack(fonts.serif));
|
||||
root.style.setProperty('--font-family-mono', this.buildGlobalFontStack(fonts.mono));
|
||||
root.style.setProperty('--font-family-display', this.buildGlobalFontStack(fonts.display));
|
||||
|
||||
// Also load the fonts if they're Google Fonts
|
||||
if (fonts.sans && fonts.sans !== 'system-ui') {
|
||||
this.loadSingleFont(fonts.sans);
|
||||
}
|
||||
if (fonts.serif && fonts.serif !== 'ui-serif') {
|
||||
this.loadSingleFont(fonts.serif);
|
||||
}
|
||||
if (fonts.mono && fonts.mono !== 'ui-monospace') {
|
||||
this.loadSingleFont(fonts.mono);
|
||||
}
|
||||
if (fonts.display && fonts.display !== 'system-ui') {
|
||||
this.loadSingleFont(fonts.display);
|
||||
}
|
||||
|
||||
this.logEvent(`Applied fonts globally to document root`, 'success');
|
||||
}
|
||||
|
||||
private applyFontsWithTransition(fontVariables: Record<string, string>, transitionType: 'fade' | 'scale' | 'typewriter'): void {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Add transition class
|
||||
root.classList.add(`font-transition-${transitionType}`);
|
||||
|
||||
// Apply font changes
|
||||
Object.entries(fontVariables).forEach(([property, value]) => {
|
||||
root.style.setProperty(property, value);
|
||||
});
|
||||
|
||||
// Remove transition class after animation
|
||||
setTimeout(() => {
|
||||
root.classList.remove(`font-transition-${transitionType}`);
|
||||
this.logEvent(`${transitionType} transition completed`, 'success');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private buildGlobalFontStack(fontName: string): string {
|
||||
// Handle system fonts
|
||||
if (fontName === 'system-ui') {
|
||||
return 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||
}
|
||||
if (fontName === 'ui-serif') {
|
||||
return 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif';
|
||||
}
|
||||
if (fontName === 'ui-monospace') {
|
||||
return 'ui-monospace, SFMono-Regular, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
||||
}
|
||||
|
||||
// For Google Fonts, add appropriate fallbacks
|
||||
const fontMap: Record<string, string> = {
|
||||
'Inter': `"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,
|
||||
'Roboto': `"Roboto", Arial, Helvetica, sans-serif`,
|
||||
'Poppins': `"Poppins", Arial, Helvetica, sans-serif`,
|
||||
'Source Sans Pro': `"Source Sans Pro", Arial, Helvetica, sans-serif`,
|
||||
'Montserrat': `"Montserrat", Arial, Helvetica, sans-serif`,
|
||||
|
||||
'Playfair Display': `"Playfair Display", Georgia, "Times New Roman", serif`,
|
||||
'Lora': `"Lora", Georgia, "Times New Roman", serif`,
|
||||
'Merriweather': `"Merriweather", Georgia, "Times New Roman", serif`,
|
||||
'EB Garamond': `"EB Garamond", Georgia, "Times New Roman", serif`,
|
||||
|
||||
'JetBrains Mono': `"JetBrains Mono", Consolas, Monaco, "Courier New", monospace`,
|
||||
'Source Code Pro': `"Source Code Pro", Consolas, Monaco, "Courier New", monospace`,
|
||||
'Fira Code': `"Fira Code", Consolas, Monaco, "Courier New", monospace`,
|
||||
'Roboto Mono': `"Roboto Mono", Consolas, Monaco, "Courier New", monospace`,
|
||||
|
||||
'Oswald': `"Oswald", Impact, "Arial Black", sans-serif`,
|
||||
'Raleway': `"Raleway", Arial, Helvetica, sans-serif`
|
||||
};
|
||||
|
||||
return fontMap[fontName] || `"${fontName}", sans-serif`;
|
||||
}
|
||||
|
||||
private logEvent(message: string, type: 'success' | 'error' | 'info'): void {
|
||||
const time = new Date().toLocaleTimeString();
|
||||
this.eventLog.push({ time, message, type });
|
||||
|
||||
// Keep only last 50 events
|
||||
if (this.eventLog.length > 50) {
|
||||
this.eventLog = this.eventLog.slice(-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user