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:
skyai_dev
2025-09-05 05:37:37 +10:00
parent 876eb301a0
commit 5346d6d0c9
5476 changed files with 350855 additions and 10 deletions

View File

@@ -0,0 +1,178 @@
<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>
<pre><code style="font-family: var(--font-family-mono)">// Code example with monospace font
function applyFontTheme(theme) {
document.documentElement.style.setProperty('--font-family-sans', theme.sans);
document.documentElement.style.setProperty('--font-family-serif', theme.serif);
document.documentElement.style.setProperty('--font-family-mono', theme.mono);
}</code></pre>
<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>

View File

@@ -0,0 +1,580 @@
.demo-container {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.demo-description {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
line-height: 1.6;
}
.demo-section {
margin: 3rem 0;
padding: 2rem;
border: 1px solid #e0e0e0;
border-radius: 12px;
background: #fafafa;
h2, h3 {
margin-top: 0;
color: #333;
}
h2 {
font-size: 1.5rem;
margin-bottom: 1.5rem;
}
h3 {
font-size: 1.25rem;
margin-bottom: 1rem;
}
}
// Loading State Display
.state-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.state-item {
padding: 1rem;
background: white;
border-radius: 8px;
border-left: 4px solid #2196F3;
strong {
color: #333;
display: block;
margin-bottom: 0.5rem;
}
.loading {
color: #FF9800;
font-weight: 500;
}
.idle {
color: #4CAF50;
font-weight: 500;
}
}
// Manual Font Controls
.font-controls {
margin-top: 1rem;
}
.font-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
}
.font-button {
position: relative;
padding: 1rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
background: white;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
&:hover {
border-color: #2196F3;
background: #f5f5f5;
}
&.loaded {
border-color: #4CAF50;
background: #e8f5e8;
color: #2e7d32;
font-weight: 500;
}
}
.load-status {
position: absolute;
top: 0.5rem;
right: 0.5rem;
color: #4CAF50;
font-weight: bold;
}
// Transition Controls
.transition-controls {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.transition-btn {
padding: 1rem;
border: 2px solid #2196F3;
border-radius: 8px;
background: white;
color: #2196F3;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
&:hover {
background: #2196F3;
color: white;
}
&.scale {
border-color: #FF9800;
color: #FF9800;
&:hover {
background: #FF9800;
color: white;
}
}
&.typewriter {
border-color: #9C27B0;
color: #9C27B0;
&:hover {
background: #9C27B0;
color: white;
}
}
}
// Typography Showcase
.typography-showcase {
display: grid;
gap: 2rem;
margin-top: 1.5rem;
}
.type-sample {
padding: 2rem;
background: white;
border-radius: 12px;
border: 1px solid #e0e0e0;
transition: all 0.3s ease;
h1, h2, h3, h4 {
margin: 0 0 1rem 0;
line-height: 1.3;
}
h1 {
font-size: 2.5rem;
font-weight: 700;
}
h2 {
font-size: 2rem;
font-weight: 600;
}
h3 {
font-size: 1.75rem;
font-weight: 500;
}
h4 {
font-size: 1.5rem;
font-weight: 500;
}
p {
line-height: 1.6;
margin-bottom: 1rem;
}
pre {
background: #f5f5f5;
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
margin: 1rem 0;
code {
font-size: 0.9rem;
line-height: 1.5;
}
}
}
.font-info {
font-size: 0.8rem;
color: #666;
font-style: italic;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e0e0e0;
}
// Available Themes
.themes-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.theme-card {
padding: 1.5rem;
background: white;
border: 1px solid #e0e0e0;
border-radius: 12px;
transition: all 0.3s ease;
&:hover {
border-color: #2196F3;
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.1);
}
h4 {
margin: 0 0 0.5rem 0;
color: #333;
font-size: 1.1rem;
}
}
.theme-description {
color: #666;
font-size: 0.9rem;
margin-bottom: 1rem;
line-height: 1.4;
}
.theme-fonts {
margin-bottom: 1.5rem;
}
.theme-font {
display: flex;
justify-content: space-between;
padding: 0.25rem 0;
font-size: 0.85rem;
strong {
color: #333;
min-width: 60px;
}
& + .theme-font {
border-top: 1px solid #f0f0f0;
}
}
.apply-theme-btn {
width: 100%;
padding: 0.75rem;
border: 1px solid #2196F3;
border-radius: 6px;
background: #2196F3;
color: white;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
&:hover {
background: #1976D2;
border-color: #1976D2;
}
}
// Event Log
.event-log {
max-height: 300px;
overflow-y: auto;
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1rem;
}
.event-item {
display: flex;
gap: 1rem;
padding: 0.5rem 0;
font-size: 0.9rem;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&.success {
color: #4CAF50;
}
&.error {
color: #f44336;
}
&.info {
color: #2196F3;
}
}
.event-time {
color: #666;
font-size: 0.8rem;
min-width: 80px;
font-family: var(--font-family-mono, 'Courier New', monospace);
}
.event-message {
flex: 1;
}
// Font Preset Controls
.preset-controls {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-top: 1rem;
}
.preset-selector {
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
select {
width: 100%;
padding: 0.75rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
background: white;
transition: border-color 0.3s ease;
&:focus {
outline: none;
border-color: #2196F3;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
}
.transition-selector {
display: flex;
gap: 1rem;
align-items: end;
flex-wrap: wrap;
label {
font-weight: 600;
color: #333;
margin-bottom: 0.5rem;
display: block;
}
select {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 0.9rem;
min-width: 120px;
}
}
.apply-btn {
padding: 0.75rem 1.5rem;
background: #2196F3;
color: white;
border: none;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
&:hover:not(:disabled) {
background: #1976D2;
transform: translateY(-2px);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
}
// Current Preset Display
.current-preset {
margin-top: 2rem;
padding: 1.5rem;
background: #e8f5e8;
border: 1px solid #4CAF50;
border-radius: 12px;
h4 {
margin: 0 0 1rem 0;
color: #2e7d32;
font-size: 1.1rem;
}
}
.preset-fonts {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.5rem;
}
.font-assignment {
display: flex;
justify-content: space-between;
padding: 0.5rem;
background: white;
border-radius: 6px;
font-size: 0.9rem;
.font-type {
font-weight: 600;
color: #333;
}
.font-name {
color: #666;
font-style: italic;
}
}
// Theme card active state
.theme-card.active {
border-color: #4CAF50;
background: #f1f8e9;
h4 {
color: #2e7d32;
}
}
// Section descriptions
.preset-description,
.preview-description,
.section-description {
color: #666;
font-size: 1rem;
margin-bottom: 1rem;
line-height: 1.5;
}
// Font transition animations
:global(html.font-transition-fade) {
* {
transition: font-family 0.8s ease-in-out;
}
}
:global(html.font-transition-scale) {
* {
transition: font-family 0.6s ease-in-out, transform 0.6s ease-in-out;
}
body {
transform: scale(1.02);
transition: transform 0.3s ease-in-out;
}
&.font-transition-scale body {
transform: scale(1);
}
}
:global(html.font-transition-typewriter) {
* {
transition: font-family 0.5s steps(10, end);
}
}
// Enhanced blockquote styling
blockquote {
margin: 1.5rem 0;
padding: 1rem 1.5rem;
border-left: 4px solid #2196F3;
background: #f8f9fa;
font-style: italic;
position: relative;
&::before {
content: '"';
font-size: 3rem;
color: #2196F3;
position: absolute;
top: -0.5rem;
left: 1rem;
opacity: 0.3;
}
}
// Responsive Design
@media (max-width: 768px) {
.demo-container {
padding: 1rem;
}
.demo-section {
padding: 1.5rem;
}
.font-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.transition-controls {
grid-template-columns: 1fr;
}
.themes-grid {
grid-template-columns: 1fr;
}
.state-info {
grid-template-columns: 1fr;
}
.type-sample {
padding: 1.5rem;
h1 { font-size: 2rem; }
h2 { font-size: 1.75rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
}
.preset-controls {
gap: 1rem;
}
.transition-selector {
flex-direction: column;
align-items: stretch;
gap: 1rem;
select,
.apply-btn {
width: 100%;
}
}
.preset-fonts {
grid-template-columns: 1fr;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1 @@
export * from './font-manager-demo.component';