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,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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './font-manager-demo.component';
|
||||
Reference in New Issue
Block a user