Compare commits
6 Commits
5346d6d0c9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4994aafea | ||
|
|
13e030901a | ||
|
|
2a28a8abbd | ||
|
|
8e10244086 | ||
|
|
9b40aa3afb | ||
|
|
246c62fd49 |
19
.claude/settings.local.json
Normal file
19
.claude/settings.local.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ng generate:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(ng build:*)",
|
||||
"Bash(for lib in auth-client hcl-studio shared-utils ui-accessibility ui-animations ui-backgrounds ui-code-display ui-data-utils ui-design-system ui-essentials ui-font-manager ui-landing-pages)",
|
||||
"Bash(do)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(done)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git push:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
491
CONSUMER_INTEGRATION_GUIDE.md
Normal file
491
CONSUMER_INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,491 @@
|
||||
# Angular Library Consumer Integration Guide
|
||||
|
||||
## 🚀 Complete Guide: Creating a New Angular Project with SSuite Libraries
|
||||
|
||||
This guide shows you how to create a fresh Angular project and integrate any combination of the 12 SSuite libraries using Git submodules.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
- **Node.js 18+** and npm
|
||||
- **Angular CLI** (`npm install -g @angular/cli`)
|
||||
- **Git** configured with access to `git.sky-ai.com`
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Step 1: Create New Angular Project
|
||||
|
||||
```bash
|
||||
# Create new Angular project
|
||||
ng new my-awesome-app
|
||||
cd my-awesome-app
|
||||
|
||||
# Create libs directory for submodules
|
||||
mkdir libs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Step 2: Choose Your Libraries
|
||||
|
||||
Select libraries based on your needs:
|
||||
|
||||
### 🎨 **Foundation (Required for most projects)**
|
||||
- **ui-design-system** - SCSS design tokens, colors, typography, fonts
|
||||
|
||||
### 🛠️ **Essential Components**
|
||||
- **ui-essentials** - Buttons, forms, tables, cards, navigation, etc.
|
||||
- **shared-utils** - Common utilities and shared services
|
||||
|
||||
### 🎯 **Specialized Libraries**
|
||||
- **auth-client** - Authentication guards, services, interceptors
|
||||
- **ui-landing-pages** - Hero sections, pricing tables, testimonials
|
||||
- **ui-code-display** - Syntax highlighting for code blocks
|
||||
- **ui-accessibility** - WCAG compliance tools and utilities
|
||||
- **hcl-studio** - Advanced color management system
|
||||
|
||||
### 🎭 **Enhancement Libraries**
|
||||
- **ui-animations** - CSS animation utilities
|
||||
- **ui-backgrounds** - Background generators and effects
|
||||
- **ui-font-manager** - Advanced font management
|
||||
- **ui-data-utils** - Data manipulation utilities
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Step 3: Add Libraries as Submodules
|
||||
|
||||
**IMPORTANT**: Always add `ui-design-system` first, as other libraries depend on it.
|
||||
|
||||
### Minimal Setup (Design System + Components):
|
||||
```bash
|
||||
# Add foundation (required)
|
||||
git submodule add https://git.sky-ai.com/jules/ui-design-system.git libs/ui-design-system
|
||||
|
||||
# Add essential components
|
||||
git submodule add https://git.sky-ai.com/jules/ui-essentials.git libs/ui-essentials
|
||||
git submodule add https://git.sky-ai.com/jules/shared-utils.git libs/shared-utils
|
||||
|
||||
# Initialize submodules
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Full-Featured Setup (All Libraries):
|
||||
```bash
|
||||
# Foundation (required first)
|
||||
git submodule add https://git.sky-ai.com/jules/ui-design-system.git libs/ui-design-system
|
||||
|
||||
# Core libraries
|
||||
git submodule add https://git.sky-ai.com/jules/ui-essentials.git libs/ui-essentials
|
||||
git submodule add https://git.sky-ai.com/jules/shared-utils.git libs/shared-utils
|
||||
git submodule add https://git.sky-ai.com/jules/auth-client.git libs/auth-client
|
||||
|
||||
# Specialized libraries
|
||||
git submodule add https://git.sky-ai.com/jules/ui-landing-pages.git libs/ui-landing-pages
|
||||
git submodule add https://git.sky-ai.com/jules/ui-code-display.git libs/ui-code-display
|
||||
git submodule add https://git.sky-ai.com/jules/ui-accessibility.git libs/ui-accessibility
|
||||
git submodule add https://git.sky-ai.com/jules/hcl-studio.git libs/hcl-studio
|
||||
|
||||
# Enhancement libraries
|
||||
git submodule add https://git.sky-ai.com/jules/ui-animations.git libs/ui-animations
|
||||
git submodule add https://git.sky-ai.com/jules/ui-backgrounds.git libs/ui-backgrounds
|
||||
git submodule add https://git.sky-ai.com/jules/ui-font-manager.git libs/ui-font-manager
|
||||
git submodule add https://git.sky-ai.com/jules/ui-data-utils.git libs/ui-data-utils
|
||||
|
||||
# Initialize all submodules
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔨 Step 4: Build Libraries
|
||||
|
||||
Build libraries in dependency order:
|
||||
|
||||
```bash
|
||||
# Build ui-design-system first (required by others)
|
||||
cd libs/ui-design-system
|
||||
npm install
|
||||
npm run build
|
||||
cd ../..
|
||||
|
||||
# Build other libraries
|
||||
cd libs/ui-essentials && npm install && npm run build && cd ../..
|
||||
cd libs/shared-utils && npm install && npm run build && cd ../..
|
||||
cd libs/auth-client && npm install && npm run build && cd ../..
|
||||
|
||||
# Continue for other libraries as needed...
|
||||
```
|
||||
|
||||
### 📜 Automation Script:
|
||||
Create `build-libs.sh`:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "🏗️ Building all libraries..."
|
||||
|
||||
# Array of libraries in dependency order
|
||||
LIBS=("ui-design-system" "shared-utils" "ui-essentials" "auth-client" "ui-landing-pages" "ui-code-display" "ui-accessibility" "hcl-studio" "ui-animations" "ui-backgrounds" "ui-font-manager" "ui-data-utils")
|
||||
|
||||
for lib in "${LIBS[@]}"; do
|
||||
if [ -d "libs/$lib" ]; then
|
||||
echo "📦 Building $lib..."
|
||||
cd libs/$lib
|
||||
npm install
|
||||
npm run build
|
||||
cd ../..
|
||||
echo "✅ $lib built successfully"
|
||||
else
|
||||
echo "⚠️ Skipping $lib (not found)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "🎉 All libraries built!"
|
||||
```
|
||||
|
||||
Make executable: `chmod +x build-libs.sh && ./build-libs.sh`
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Step 5: Configure TypeScript Paths
|
||||
|
||||
Update `tsconfig.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
// Foundation (required)
|
||||
"ui-design-system": ["./libs/ui-design-system/dist"],
|
||||
"ui-design-system/*": ["./libs/ui-design-system/dist/*", "./libs/ui-design-system/src/*"],
|
||||
|
||||
// Core libraries
|
||||
"ui-essentials": ["./libs/ui-essentials/dist"],
|
||||
"ui-essentials/*": ["./libs/ui-essentials/dist/*"],
|
||||
"shared-utils": ["./libs/shared-utils/dist"],
|
||||
"shared-utils/*": ["./libs/shared-utils/dist/*"],
|
||||
"auth-client": ["./libs/auth-client/dist"],
|
||||
"auth-client/*": ["./libs/auth-client/dist/*"],
|
||||
|
||||
// Specialized libraries
|
||||
"ui-landing-pages": ["./libs/ui-landing-pages/dist"],
|
||||
"ui-landing-pages/*": ["./libs/ui-landing-pages/dist/*"],
|
||||
"ui-code-display": ["./libs/ui-code-display/dist"],
|
||||
"ui-code-display/*": ["./libs/ui-code-display/dist/*"],
|
||||
"ui-accessibility": ["./libs/ui-accessibility/dist"],
|
||||
"ui-accessibility/*": ["./libs/ui-accessibility/dist/*"],
|
||||
"hcl-studio": ["./libs/hcl-studio/dist"],
|
||||
"hcl-studio/*": ["./libs/hcl-studio/dist/*"],
|
||||
|
||||
// Enhancement libraries
|
||||
"ui-animations": ["./libs/ui-animations/dist"],
|
||||
"ui-animations/*": ["./libs/ui-animations/dist/*"],
|
||||
"ui-backgrounds": ["./libs/ui-backgrounds/dist"],
|
||||
"ui-backgrounds/*": ["./libs/ui-backgrounds/dist/*"],
|
||||
"ui-font-manager": ["./libs/ui-font-manager/dist"],
|
||||
"ui-font-manager/*": ["./libs/ui-font-manager/dist/*"],
|
||||
"ui-data-utils": ["./libs/ui-data-utils/dist"],
|
||||
"ui-data-utils/*": ["./libs/ui-data-utils/dist/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The `ui-design-system/*` includes both `dist/*` and `src/*` to support SCSS imports.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Step 6: Import Styles
|
||||
|
||||
### Option A: Complete Design System
|
||||
In your `src/styles.scss`:
|
||||
```scss
|
||||
// Complete design system (includes all tokens + fonts)
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
|
||||
// Optional: Additional component styles
|
||||
@use 'ui-animations/src/styles' as animations;
|
||||
@use 'ui-backgrounds/src/styles' as backgrounds;
|
||||
```
|
||||
|
||||
### Option B: Selective Imports
|
||||
```scss
|
||||
// Just semantic tokens (most common)
|
||||
@use 'ui-design-system/src/styles/semantic' as tokens;
|
||||
|
||||
// Or just base tokens
|
||||
@use 'ui-design-system/src/styles/base' as base;
|
||||
|
||||
// Add specific libraries
|
||||
@use 'ui-accessibility/src/lib/utilities/a11y-utilities' as a11y;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 Step 7: Use Libraries in Your Components
|
||||
|
||||
### Example Component using multiple libraries:
|
||||
|
||||
```typescript
|
||||
// src/app/app.component.ts
|
||||
import { Component } from '@angular/core';
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
import { AuthService } from 'auth-client';
|
||||
import { HeroSectionComponent } from 'ui-landing-pages';
|
||||
import { CodeSnippetComponent } from 'ui-code-display';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ButtonComponent,
|
||||
HeroSectionComponent,
|
||||
CodeSnippetComponent
|
||||
],
|
||||
template: `
|
||||
<div class="app-container">
|
||||
<!-- Hero Section -->
|
||||
<ui-hero-section
|
||||
title="Welcome to My App"
|
||||
subtitle="Built with SSuite Libraries"
|
||||
[cta]="{ text: 'Get Started', action: 'primary' }">
|
||||
</ui-hero-section>
|
||||
|
||||
<!-- Content Section -->
|
||||
<main class="content">
|
||||
<!-- Buttons from ui-essentials -->
|
||||
<ui-button variant="primary" (click)="handleClick()">
|
||||
Primary Action
|
||||
</ui-button>
|
||||
|
||||
<ui-button variant="secondary" [disabled]="!isLoggedIn">
|
||||
Secondary Action
|
||||
</ui-button>
|
||||
|
||||
<!-- Code Display -->
|
||||
<ui-code-snippet
|
||||
language="typescript"
|
||||
[code]="exampleCode">
|
||||
</ui-code-snippet>
|
||||
</main>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.content {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class AppComponent {
|
||||
isLoggedIn = false;
|
||||
exampleCode = `
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
import { AuthService } from 'auth-client';
|
||||
|
||||
// Your code here...
|
||||
`;
|
||||
|
||||
constructor(private auth: AuthService) {
|
||||
this.isLoggedIn = this.auth.isAuthenticated();
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
console.log('Button clicked!');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Component with Authentication:
|
||||
|
||||
```typescript
|
||||
// src/app/protected/dashboard.component.ts
|
||||
import { Component } from '@angular/core';
|
||||
import { AuthGuard } from 'auth-client';
|
||||
import { TableComponent, CardComponent } from 'ui-essentials';
|
||||
import { StatisticsDisplayComponent } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
standalone: true,
|
||||
imports: [TableComponent, CardComponent, StatisticsDisplayComponent],
|
||||
template: `
|
||||
<div class="dashboard">
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-grid">
|
||||
<ui-statistics-display
|
||||
[statistics]="stats"
|
||||
variant="cards">
|
||||
</ui-statistics-display>
|
||||
</div>
|
||||
|
||||
<!-- Data Table -->
|
||||
<ui-card>
|
||||
<ui-enhanced-table
|
||||
[data]="userData"
|
||||
[columns]="tableColumns"
|
||||
[searchable]="true"
|
||||
[sortable]="true">
|
||||
</ui-enhanced-table>
|
||||
</ui-card>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.dashboard {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.stats-grid {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class DashboardComponent {
|
||||
stats = [
|
||||
{ label: 'Users', value: '1,234', change: '+12%' },
|
||||
{ label: 'Revenue', value: '$45,678', change: '+8%' },
|
||||
{ label: 'Orders', value: '892', change: '+15%' }
|
||||
];
|
||||
|
||||
userData = [
|
||||
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
|
||||
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
|
||||
];
|
||||
|
||||
tableColumns = [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'name', label: 'Name' },
|
||||
{ key: 'email', label: 'Email' },
|
||||
{ key: 'status', label: 'Status' }
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Step 8: Library Management
|
||||
|
||||
### Update a Single Library:
|
||||
```bash
|
||||
cd libs/ui-essentials
|
||||
git pull origin main
|
||||
npm run build
|
||||
cd ../..
|
||||
```
|
||||
|
||||
### Update All Libraries:
|
||||
```bash
|
||||
git submodule update --remote
|
||||
./build-libs.sh # Rebuild all libraries
|
||||
```
|
||||
|
||||
### Lock Library to Specific Version:
|
||||
```bash
|
||||
cd libs/ui-essentials
|
||||
git checkout v1.2.0 # Specific tag
|
||||
cd ../..
|
||||
git add libs/ui-essentials
|
||||
git commit -m "Lock ui-essentials to v1.2.0"
|
||||
```
|
||||
|
||||
### Remove Unused Library:
|
||||
```bash
|
||||
git submodule deinit libs/ui-backgrounds
|
||||
git rm libs/ui-backgrounds
|
||||
git commit -m "Remove ui-backgrounds library"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Final Project Structure
|
||||
|
||||
```
|
||||
my-awesome-app/
|
||||
├── src/
|
||||
│ ├── app/
|
||||
│ │ ├── app.component.ts
|
||||
│ │ ├── protected/
|
||||
│ │ │ └── dashboard.component.ts
|
||||
│ │ └── ...
|
||||
│ ├── styles.scss
|
||||
│ └── ...
|
||||
├── libs/ # Git submodules
|
||||
│ ├── ui-design-system/ # Foundation
|
||||
│ ├── ui-essentials/ # Components
|
||||
│ ├── auth-client/ # Authentication
|
||||
│ ├── ui-landing-pages/ # Landing components
|
||||
│ └── [other-libraries]/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── angular.json
|
||||
├── build-libs.sh # Build automation
|
||||
├── .gitmodules # Auto-generated
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Start Commands
|
||||
|
||||
### Minimal Project (Design System + Components):
|
||||
```bash
|
||||
ng new my-app && cd my-app
|
||||
mkdir libs
|
||||
git submodule add https://git.sky-ai.com/jules/ui-design-system.git libs/ui-design-system
|
||||
git submodule add https://git.sky-ai.com/jules/ui-essentials.git libs/ui-essentials
|
||||
git submodule update --init --recursive
|
||||
cd libs/ui-design-system && npm install && npm run build && cd ../..
|
||||
cd libs/ui-essentials && npm install && npm run build && cd ../..
|
||||
```
|
||||
|
||||
### Add TypeScript paths and styles, then start developing!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Common Patterns
|
||||
|
||||
### Landing Page Project:
|
||||
```bash
|
||||
# Essential for marketing sites
|
||||
git submodule add https://git.sky-ai.com/jules/ui-design-system.git libs/ui-design-system
|
||||
git submodule add https://git.sky-ai.com/jules/ui-essentials.git libs/ui-essentials
|
||||
git submodule add https://git.sky-ai.com/jules/ui-landing-pages.git libs/ui-landing-pages
|
||||
git submodule add https://git.sky-ai.com/jules/ui-animations.git libs/ui-animations
|
||||
```
|
||||
|
||||
### Documentation Site:
|
||||
```bash
|
||||
# Perfect for docs with code examples
|
||||
git submodule add https://git.sky-ai.com/jules/ui-design-system.git libs/ui-design-system
|
||||
git submodule add https://git.sky-ai.com/jules/ui-essentials.git libs/ui-essentials
|
||||
git submodule add https://git.sky-ai.com/jules/ui-code-display.git libs/ui-code-display
|
||||
git submodule add https://git.sky-ai.com/jules/ui-accessibility.git libs/ui-accessibility
|
||||
```
|
||||
|
||||
### Enterprise Application:
|
||||
```bash
|
||||
# Full-featured business app
|
||||
git submodule add https://git.sky-ai.com/jules/ui-design-system.git libs/ui-design-system
|
||||
git submodule add https://git.sky-ai.com/jules/ui-essentials.git libs/ui-essentials
|
||||
git submodule add https://git.sky-ai.com/jules/shared-utils.git libs/shared-utils
|
||||
git submodule add https://git.sky-ai.com/jules/auth-client.git libs/auth-client
|
||||
git submodule add https://git.sky-ai.com/jules/ui-data-utils.git libs/ui-data-utils
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 You're Ready!
|
||||
|
||||
Your new Angular project is now set up with the SSuite library ecosystem. You have:
|
||||
|
||||
- ✅ **Modular architecture** - Only include libraries you need
|
||||
- ✅ **Professional design system** - Consistent styling and components
|
||||
- ✅ **Independent updates** - Update libraries separately
|
||||
- ✅ **Clean imports** - TypeScript paths configured
|
||||
- ✅ **Version control** - Lock libraries to specific versions
|
||||
- ✅ **Scalable structure** - Add/remove libraries as needed
|
||||
|
||||
Happy coding! 🎉
|
||||
978
CUSTOMIZATION_AND_THEMING_GUIDE.md
Normal file
978
CUSTOMIZATION_AND_THEMING_GUIDE.md
Normal file
@@ -0,0 +1,978 @@
|
||||
# SSuite Customization and Theming Guide
|
||||
|
||||
## 🎨 Complete Guide to Styling and Overriding SSuite Libraries
|
||||
|
||||
This guide shows you how to customize and theme your Angular project using the SSuite library ecosystem. Learn how to override colors, typography, components, and create your own design system on top of the foundation.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
Before customizing, ensure you have:
|
||||
- **SSuite libraries integrated** (follow `CONSUMER_INTEGRATION_GUIDE.md`)
|
||||
- **ui-design-system** added as a submodule (foundation library)
|
||||
- **Basic SCSS knowledge** for advanced customizations
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Customization Architecture
|
||||
|
||||
SSuite uses a **layered architecture** for maximum flexibility:
|
||||
|
||||
```
|
||||
Your Custom Theme
|
||||
↓
|
||||
Semantic Tokens ← Override here for colors, typography, spacing
|
||||
↓
|
||||
Base Tokens ← Override here for foundational values
|
||||
↓
|
||||
Components ← Override here for specific component styling
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Approach 1: CSS Custom Properties (Recommended)
|
||||
|
||||
The easiest way to customize SSuite libraries using CSS variables.
|
||||
|
||||
### 1. Create Your Theme File
|
||||
|
||||
Create `src/styles/theme.scss`:
|
||||
|
||||
```scss
|
||||
// ==========================================================================
|
||||
// YOUR CUSTOM THEME
|
||||
// ==========================================================================
|
||||
|
||||
// Import design system first
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
|
||||
// ==========================================================================
|
||||
// 🎨 COLOR OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
:root {
|
||||
// Primary Brand Colors
|
||||
--color-primary: #6366f1; // Your brand primary (indigo)
|
||||
--color-secondary: #8b5cf6; // Your brand secondary (purple)
|
||||
--color-tertiary: #10b981; // Your accent color (emerald)
|
||||
--color-error: #ef4444; // Error/danger color (red)
|
||||
|
||||
// Primary color variations
|
||||
--color-primary-50: #eef2ff;
|
||||
--color-primary-100: #e0e7ff;
|
||||
--color-primary-500: #6366f1; // Main primary
|
||||
--color-primary-600: #4f46e5;
|
||||
--color-primary-700: #4338ca;
|
||||
--color-primary-900: #312e81;
|
||||
|
||||
// Surface colors (backgrounds)
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-variant: #f8fafc;
|
||||
--color-surface-container: #f1f5f9;
|
||||
--color-surface-dim: #e2e8f0;
|
||||
|
||||
// Text colors on your custom colors
|
||||
--color-on-primary: #ffffff;
|
||||
--color-on-secondary: #ffffff;
|
||||
--color-on-surface: #1e293b;
|
||||
|
||||
// ==========================================================================
|
||||
// ✍️ TYPOGRAPHY OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Font families (use your own fonts)
|
||||
--font-family-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
--font-family-display: 'Playfair Display', Georgia, serif;
|
||||
|
||||
// Font sizes
|
||||
--font-size-xs: 0.75rem; // 12px
|
||||
--font-size-sm: 0.875rem; // 14px
|
||||
--font-size-base: 1rem; // 16px
|
||||
--font-size-lg: 1.125rem; // 18px
|
||||
--font-size-xl: 1.25rem; // 20px
|
||||
--font-size-2xl: 1.5rem; // 24px
|
||||
--font-size-3xl: 1.875rem; // 30px
|
||||
--font-size-4xl: 2.25rem; // 36px
|
||||
|
||||
// ==========================================================================
|
||||
// 📏 SPACING & SIZING OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Border radius (adjust roundness)
|
||||
--border-radius-sm: 0.25rem; // 4px - subtle
|
||||
--border-radius-base: 0.5rem; // 8px - default
|
||||
--border-radius-lg: 0.75rem; // 12px - rounded
|
||||
--border-radius-xl: 1rem; // 16px - very rounded
|
||||
|
||||
// Component-specific spacing
|
||||
--spacing-button-padding-x: 1.5rem; // Button horizontal padding
|
||||
--spacing-button-padding-y: 0.75rem; // Button vertical padding
|
||||
--spacing-card-padding: 1.5rem; // Card interior padding
|
||||
--spacing-section: 4rem; // Section spacing
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Import Your Theme
|
||||
|
||||
In `src/styles.scss`:
|
||||
|
||||
```scss
|
||||
// Import your custom theme (this sets CSS variables)
|
||||
@import 'styles/theme';
|
||||
|
||||
// Global styles using your theme
|
||||
body {
|
||||
font-family: var(--font-family-sans);
|
||||
background-color: var(--color-surface);
|
||||
color: var(--color-on-surface);
|
||||
}
|
||||
|
||||
// Apply theme to headings
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-family-display);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
// Custom link styling
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-600);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Approach 2: SCSS Variable Overrides (Advanced)
|
||||
|
||||
For deeper customization, override SCSS variables before importing SSuite.
|
||||
|
||||
### 1. Create Advanced Theme File
|
||||
|
||||
Create `src/styles/advanced-theme.scss`:
|
||||
|
||||
```scss
|
||||
// ==========================================================================
|
||||
// ADVANCED SCSS VARIABLE OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// ==========================================================================
|
||||
// 🎨 COLOR VARIABLE OVERRIDES (before importing ui-design-system)
|
||||
// ==========================================================================
|
||||
|
||||
// Override semantic colors
|
||||
$semantic-color-brand-primary: #7c3aed !default; // Purple brand
|
||||
$semantic-color-brand-secondary: #059669 !default; // Emerald secondary
|
||||
$semantic-color-brand-accent: #dc2626 !default; // Red accent
|
||||
|
||||
// Override feedback colors
|
||||
$semantic-color-success: #10b981 !default; // Custom green
|
||||
$semantic-color-warning: #f59e0b !default; // Custom amber
|
||||
$semantic-color-danger: #ef4444 !default; // Custom red
|
||||
$semantic-color-info: #3b82f6 !default; // Custom blue
|
||||
|
||||
// Override surface colors
|
||||
$semantic-color-surface-primary: #ffffff !default; // Pure white
|
||||
$semantic-color-surface-secondary: #f9fafb !default; // Off-white
|
||||
$semantic-color-surface-elevated: #f3f4f6 !default; // Light gray
|
||||
|
||||
// ==========================================================================
|
||||
// ✍️ TYPOGRAPHY VARIABLE OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Font families
|
||||
$base-typography-font-family-sans: 'Inter', sans-serif !default;
|
||||
$base-typography-font-family-mono: 'JetBrains Mono', monospace !default;
|
||||
$base-typography-font-family-display: 'Playfair Display', serif !default;
|
||||
|
||||
// Font sizes
|
||||
$base-typography-font-size-xs: 0.75rem !default; // 12px
|
||||
$base-typography-font-size-sm: 0.875rem !default; // 14px
|
||||
$base-typography-font-size-base: 1rem !default; // 16px
|
||||
$base-typography-font-size-lg: 1.125rem !default; // 18px
|
||||
$base-typography-font-size-xl: 1.25rem !default; // 20px
|
||||
$base-typography-font-size-2xl: 1.5rem !default; // 24px
|
||||
$base-typography-font-size-3xl: 1.875rem !default; // 30px
|
||||
$base-typography-font-size-4xl: 2.25rem !default; // 36px
|
||||
|
||||
// Line heights
|
||||
$base-typography-line-height-tight: 1.25 !default;
|
||||
$base-typography-line-height-snug: 1.375 !default;
|
||||
$base-typography-line-height-normal: 1.5 !default;
|
||||
$base-typography-line-height-relaxed: 1.625 !default;
|
||||
|
||||
// ==========================================================================
|
||||
// 📏 SPACING & LAYOUT OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Border radius
|
||||
$semantic-border-button-radius: 0.75rem !default; // Rounded buttons
|
||||
$semantic-border-card-radius: 1rem !default; // Very rounded cards
|
||||
$semantic-border-input-radius: 0.5rem !default; // Moderate inputs
|
||||
|
||||
// Spacing values
|
||||
$semantic-spacing-section-padding: 5rem !default; // Generous sections
|
||||
$semantic-spacing-card-padding: 2rem !default; // Spacious cards
|
||||
$semantic-spacing-interactive-button-padding-x: 2rem !default; // Wide buttons
|
||||
$semantic-spacing-interactive-button-padding-y: 0.875rem !default; // Tall buttons
|
||||
|
||||
// NOW import ui-design-system (it will use your overridden variables)
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
```
|
||||
|
||||
### 2. Use Advanced Theme
|
||||
|
||||
In `src/styles.scss`:
|
||||
|
||||
```scss
|
||||
// Import your advanced theme
|
||||
@import 'styles/advanced-theme';
|
||||
|
||||
// Global styles
|
||||
body {
|
||||
font-family: ui.$base-typography-font-family-sans;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// Custom component styles
|
||||
.custom-hero {
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
var(--color-primary),
|
||||
var(--color-secondary)
|
||||
);
|
||||
color: white;
|
||||
padding: var(--spacing-section);
|
||||
border-radius: var(--border-radius-lg);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Approach 3: Component-Specific Overrides
|
||||
|
||||
Target specific SSuite components for surgical customizations.
|
||||
|
||||
### 1. Override Button Components
|
||||
|
||||
Create `src/styles/component-overrides.scss`:
|
||||
|
||||
```scss
|
||||
// ==========================================================================
|
||||
// COMPONENT-SPECIFIC OVERRIDES
|
||||
// ==========================================================================
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
|
||||
// ==========================================================================
|
||||
// 🔘 BUTTON OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Override all buttons
|
||||
ui-button, .ui-button {
|
||||
// Custom button base
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
// Custom hover effects
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Primary button overrides
|
||||
ui-button[variant="primary"], .ui-button--primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary button overrides
|
||||
ui-button[variant="secondary"], .ui-button--secondary {
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Outline button overrides
|
||||
ui-button[variant="outline"], .ui-button--outline {
|
||||
border: 2px solid currentColor;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 📋 FORM OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Input field overrides
|
||||
ui-text-input, .ui-text-input {
|
||||
input {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 2px solid transparent;
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 1rem 1.25rem;
|
||||
font-size: var(--font-size-base);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form field overrides
|
||||
ui-form-field, .ui-form-field {
|
||||
.field-label {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.field-error {
|
||||
color: var(--color-danger);
|
||||
font-size: var(--font-size-sm);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 🃏 CARD OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
ui-card, .ui-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: var(--border-radius-xl);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(31, 38, 135, 0.37),
|
||||
0 4px 16px rgba(31, 38, 135, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow:
|
||||
0 16px 64px rgba(31, 38, 135, 0.4),
|
||||
0 8px 32px rgba(31, 38, 135, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 🚀 LANDING PAGE OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Hero section overrides
|
||||
ui-hero-section, .ui-hero-section {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(102, 126, 234, 0.9) 0%,
|
||||
rgba(118, 75, 162, 0.9) 100%
|
||||
);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.hero-title {
|
||||
font-family: var(--font-family-display);
|
||||
background: linear-gradient(45deg, #fff, #e2e8f0);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
// Feature grid overrides
|
||||
ui-feature-grid, .ui-feature-grid {
|
||||
.feature-item {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Use Component Overrides
|
||||
|
||||
In `src/styles.scss`:
|
||||
|
||||
```scss
|
||||
// Import design system
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
|
||||
// Import your component overrides
|
||||
@import 'styles/component-overrides';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Approach 4: Dark Mode Implementation
|
||||
|
||||
Create a comprehensive dark mode theme.
|
||||
|
||||
### 1. Create Dark Mode Theme
|
||||
|
||||
Create `src/styles/dark-theme.scss`:
|
||||
|
||||
```scss
|
||||
// ==========================================================================
|
||||
// DARK MODE THEME
|
||||
// ==========================================================================
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
|
||||
// Dark mode CSS variables
|
||||
[data-theme="dark"] {
|
||||
// ==========================================================================
|
||||
// 🌙 DARK MODE COLORS
|
||||
// ==========================================================================
|
||||
|
||||
// Primary colors (slightly brighter for dark mode)
|
||||
--color-primary: #818cf8; // Lighter indigo
|
||||
--color-secondary: #a78bfa; // Lighter purple
|
||||
--color-tertiary: #34d399; // Lighter emerald
|
||||
|
||||
// Dark surfaces
|
||||
--color-surface: #0f172a; // Very dark blue
|
||||
--color-surface-variant: #1e293b; // Dark blue-gray
|
||||
--color-surface-container: #334155; // Medium blue-gray
|
||||
--color-surface-elevated: #475569; // Light blue-gray
|
||||
--color-surface-dim: #64748b; // Muted blue-gray
|
||||
|
||||
// Dark mode text colors
|
||||
--color-on-surface: #f8fafc; // Light text on dark
|
||||
--color-on-primary: #1e1b4b; // Dark text on light primary
|
||||
--color-on-secondary: #2d1b69; // Dark text on light secondary
|
||||
|
||||
// Border colors for dark mode
|
||||
--color-border: #374151;
|
||||
--color-border-light: #4b5563;
|
||||
|
||||
// ==========================================================================
|
||||
// 🌙 DARK MODE COMPONENT OVERRIDES
|
||||
// ==========================================================================
|
||||
|
||||
// Card styling in dark mode
|
||||
ui-card, .ui-card {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
border-color: rgba(71, 85, 105, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: rgba(51, 65, 85, 0.8);
|
||||
border-color: rgba(71, 85, 105, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Input styling in dark mode
|
||||
ui-text-input input, .ui-text-input input {
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-on-surface);
|
||||
|
||||
&::placeholder {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: rgba(30, 41, 59, 1);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Button adjustments for dark mode
|
||||
ui-button[variant="secondary"], .ui-button--secondary {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
|
||||
&:hover {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Dark Mode Toggle Component
|
||||
|
||||
Create a theme switcher:
|
||||
|
||||
```typescript
|
||||
// src/app/components/theme-toggle.component.ts
|
||||
import { Component } from '@angular/core';
|
||||
import { ThemeSwitcherComponent } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-toggle',
|
||||
standalone: true,
|
||||
imports: [ThemeSwitcherComponent],
|
||||
template: `
|
||||
<ui-theme-switcher
|
||||
[themes]="themes"
|
||||
(themeChange)="onThemeChange($event)">
|
||||
</ui-theme-switcher>
|
||||
`,
|
||||
styles: [`
|
||||
:host {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class ThemeToggleComponent {
|
||||
themes = [
|
||||
{ id: 'light', name: 'Light', icon: '☀️' },
|
||||
{ id: 'dark', name: 'Dark', icon: '🌙' }
|
||||
];
|
||||
|
||||
onThemeChange(theme: string) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Initialize Theme
|
||||
|
||||
In `src/app/app.component.ts`:
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ThemeToggleComponent } from './components/theme-toggle.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [ThemeToggleComponent],
|
||||
template: `
|
||||
<div class="app">
|
||||
<app-theme-toggle></app-theme-toggle>
|
||||
<!-- Your app content -->
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
// Initialize theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Approach 5: Custom Brand Components
|
||||
|
||||
Create your own branded components on top of SSuite.
|
||||
|
||||
### 1. Custom Branded Button
|
||||
|
||||
Create `src/app/components/brand-button.component.ts`:
|
||||
|
||||
```typescript
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'app-brand-button',
|
||||
standalone: true,
|
||||
imports: [ButtonComponent],
|
||||
template: `
|
||||
<ui-button
|
||||
[variant]="variant"
|
||||
[size]="size"
|
||||
[disabled]="disabled"
|
||||
class="brand-button"
|
||||
[class.gradient]="gradient"
|
||||
[class.glass]="glass">
|
||||
<ng-content></ng-content>
|
||||
</ui-button>
|
||||
`,
|
||||
styles: [`
|
||||
.brand-button {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Gradient effect
|
||||
&.gradient {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Glass morphism effect
|
||||
&.glass {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class BrandButtonComponent {
|
||||
@Input() variant: 'primary' | 'secondary' | 'outline' = 'primary';
|
||||
@Input() size: 'sm' | 'md' | 'lg' = 'md';
|
||||
@Input() disabled = false;
|
||||
@Input() gradient = false;
|
||||
@Input() glass = false;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Custom Hero Section
|
||||
|
||||
Create `src/app/components/brand-hero.component.ts`:
|
||||
|
||||
```typescript
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { HeroSectionComponent } from 'ui-landing-pages';
|
||||
import { BrandButtonComponent } from './brand-button.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-brand-hero',
|
||||
standalone: true,
|
||||
imports: [HeroSectionComponent, BrandButtonComponent],
|
||||
template: `
|
||||
<div class="brand-hero">
|
||||
<div class="hero-background"></div>
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">{{ title }}</h1>
|
||||
<p class="hero-subtitle">{{ subtitle }}</p>
|
||||
<div class="hero-actions">
|
||||
<app-brand-button
|
||||
[gradient]="true"
|
||||
size="lg">
|
||||
{{ ctaText }}
|
||||
</app-brand-button>
|
||||
<app-brand-button
|
||||
[glass]="true"
|
||||
variant="outline"
|
||||
size="lg">
|
||||
{{ secondaryCta }}
|
||||
</app-brand-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.brand-hero {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="1" fill="rgba(255,255,255,0.1)"/></svg>') repeat;
|
||||
animation: float 20s infinite linear;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% { transform: translateY(0) rotate(0deg); }
|
||||
100% { transform: translateY(-100px) rotate(360deg); }
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
text-align: center;
|
||||
z-index: 2;
|
||||
max-width: 800px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(2.5rem, 8vw, 5rem);
|
||||
font-weight: 900;
|
||||
margin-bottom: 1.5rem;
|
||||
background: linear-gradient(45deg, #fff, #e2e8f0);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: clamp(1.125rem, 3vw, 1.5rem);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 3rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class BrandHeroComponent {
|
||||
@Input() title = 'Welcome to Our Platform';
|
||||
@Input() subtitle = 'Create amazing experiences with our design system';
|
||||
@Input() ctaText = 'Get Started';
|
||||
@Input() secondaryCta = 'Learn More';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Real-World Examples
|
||||
|
||||
### Example 1: E-commerce Theme
|
||||
|
||||
```scss
|
||||
// src/styles/ecommerce-theme.scss
|
||||
:root {
|
||||
// E-commerce focused colors
|
||||
--color-primary: #059669; // Forest green (trust)
|
||||
--color-secondary: #dc2626; // Red (urgency/sales)
|
||||
--color-success: #10b981; // Success green
|
||||
--color-warning: #f59e0b; // Warning amber
|
||||
|
||||
// Product-focused typography
|
||||
--font-family-sans: 'Open Sans', sans-serif;
|
||||
--font-family-display: 'Poppins', sans-serif;
|
||||
|
||||
// E-commerce spacing
|
||||
--spacing-product-card: 1.25rem;
|
||||
--spacing-checkout: 2rem;
|
||||
|
||||
// Trust-building shadows
|
||||
--shadow-product-card: 0 4px 12px rgba(0,0,0,0.1);
|
||||
--shadow-button-primary: 0 4px 14px rgba(5,150,105,0.3);
|
||||
}
|
||||
|
||||
// Product card styling
|
||||
.product-card {
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-product-card);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: SaaS Platform Theme
|
||||
|
||||
```scss
|
||||
// src/styles/saas-theme.scss
|
||||
:root {
|
||||
// SaaS professional colors
|
||||
--color-primary: #3b82f6; // Professional blue
|
||||
--color-secondary: #6366f1; // Indigo accent
|
||||
--color-success: #10b981; // Success green
|
||||
|
||||
// Dashboard-focused spacing
|
||||
--spacing-sidebar: 16rem;
|
||||
--spacing-dashboard-padding: 2rem;
|
||||
--spacing-card-gap: 1.5rem;
|
||||
|
||||
// Modern typography
|
||||
--font-family-sans: 'Inter', sans-serif;
|
||||
--font-size-dashboard-title: 2rem;
|
||||
--font-size-widget-title: 1.25rem;
|
||||
}
|
||||
|
||||
// Dashboard layout
|
||||
.dashboard-layout {
|
||||
display: grid;
|
||||
grid-template-columns: var(--spacing-sidebar) 1fr;
|
||||
min-height: 100vh;
|
||||
|
||||
.sidebar {
|
||||
background: var(--color-surface-variant);
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: var(--spacing-dashboard-padding);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Advanced Customization Tips
|
||||
|
||||
### 1. Use CSS Custom Properties for Runtime Changes
|
||||
|
||||
```typescript
|
||||
// Dynamically change theme colors
|
||||
export class ThemeService {
|
||||
setThemeColor(property: string, value: string) {
|
||||
document.documentElement.style.setProperty(`--${property}`, value);
|
||||
}
|
||||
|
||||
// Example: User customization
|
||||
applyUserTheme(userTheme: UserTheme) {
|
||||
this.setThemeColor('color-primary', userTheme.primaryColor);
|
||||
this.setThemeColor('font-family-sans', userTheme.fontFamily);
|
||||
this.setThemeColor('border-radius-base', userTheme.borderRadius);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Component-Level CSS Custom Properties
|
||||
|
||||
```scss
|
||||
ui-button {
|
||||
// Component-specific custom properties
|
||||
--btn-padding-x: 2rem;
|
||||
--btn-font-weight: 700;
|
||||
--btn-border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
// Modify specific button instances
|
||||
.hero-button {
|
||||
--btn-padding-x: 3rem;
|
||||
--btn-font-size: 1.25rem;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Responsive Theme Adjustments
|
||||
|
||||
```scss
|
||||
:root {
|
||||
--font-size-hero: 2.5rem;
|
||||
--spacing-section: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
:root {
|
||||
--font-size-hero: 4rem;
|
||||
--spacing-section: 5rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Best Practices
|
||||
|
||||
### 1. **Layer Your Customizations**
|
||||
- Start with CSS custom properties for quick changes
|
||||
- Use SCSS variables for deeper customizations
|
||||
- Create component overrides for specific needs
|
||||
|
||||
### 2. **Maintain Consistency**
|
||||
- Define a clear color palette
|
||||
- Use consistent spacing scales
|
||||
- Maintain typography hierarchy
|
||||
|
||||
### 3. **Test Across Components**
|
||||
- Ensure your theme works across all SSuite components
|
||||
- Test in both light and dark modes
|
||||
- Verify accessibility (contrast ratios)
|
||||
|
||||
### 4. **Performance Considerations**
|
||||
- Use CSS custom properties for dynamic changes
|
||||
- Avoid excessive nesting in component overrides
|
||||
- Minimize redundant style declarations
|
||||
|
||||
### 5. **Documentation**
|
||||
- Document your theme variables
|
||||
- Create a style guide for your team
|
||||
- Maintain examples of themed components
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start Templates
|
||||
|
||||
### Minimal Customization (5 minutes)
|
||||
```scss
|
||||
// src/styles.scss
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
|
||||
:root {
|
||||
--color-primary: #your-brand-color;
|
||||
--font-family-sans: 'Your Font', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
### Medium Customization (30 minutes)
|
||||
- Use Approach 1 (CSS Custom Properties)
|
||||
- Add dark mode support
|
||||
- Create 2-3 component overrides
|
||||
|
||||
### Full Customization (2+ hours)
|
||||
- Use Approach 2 (SCSS Variable Overrides)
|
||||
- Implement comprehensive dark mode
|
||||
- Create branded components
|
||||
- Add responsive theme adjustments
|
||||
|
||||
---
|
||||
|
||||
## 🎉 You're Ready to Theme!
|
||||
|
||||
With this guide, you can create beautiful, branded experiences using the SSuite library ecosystem. Start small with CSS custom properties and gradually add more advanced customizations as needed.
|
||||
|
||||
Your themed application will maintain the robust functionality of SSuite components while expressing your unique brand identity! 🎨✨
|
||||
1137
HCL_STUDIO_USAGE_GUIDE.md
Normal file
1137
HCL_STUDIO_USAGE_GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
138
LIBRARY_EXTRACTION_PLAN.md
Normal file
138
LIBRARY_EXTRACTION_PLAN.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Library Extraction Plan
|
||||
|
||||
## Overview
|
||||
Convert current Angular workspace with 12 libraries into individual Git repositories for submodule distribution.
|
||||
|
||||
## Dependencies Analysis
|
||||
|
||||
### Core Libraries (No internal dependencies)
|
||||
1. **ui-design-system** - Foundation styles and tokens
|
||||
2. **shared-utils** - Utility functions
|
||||
3. **ui-data-utils** - Data manipulation utilities
|
||||
4. **ui-animations** - Animation utilities
|
||||
5. **ui-accessibility** - Accessibility features
|
||||
6. **ui-backgrounds** - Background utilities
|
||||
7. **ui-font-manager** - Font management
|
||||
8. **hcl-studio** - Color management
|
||||
9. **auth-client** - Authentication
|
||||
|
||||
### Special Dependencies
|
||||
- **ui-code-display** - Has prismjs and @types/prismjs dependencies
|
||||
- **ui-essentials** - Likely depends on ui-design-system for styling
|
||||
- **ui-landing-pages** - May depend on ui-essentials for components
|
||||
|
||||
## Extraction Order (Dependencies First)
|
||||
|
||||
### Phase 1: Foundation Libraries
|
||||
1. **ui-design-system** (SCSS foundation)
|
||||
2. **shared-utils** (utility foundation)
|
||||
3. **ui-data-utils** (data utilities)
|
||||
|
||||
### Phase 2: Standalone Libraries
|
||||
4. **ui-animations**
|
||||
5. **ui-accessibility**
|
||||
6. **ui-backgrounds**
|
||||
7. **ui-font-manager**
|
||||
8. **hcl-studio**
|
||||
9. **auth-client**
|
||||
|
||||
### Phase 3: Dependent Libraries
|
||||
10. **ui-code-display** (has external deps)
|
||||
11. **ui-essentials** (may depend on ui-design-system)
|
||||
12. **ui-landing-pages** (may depend on ui-essentials)
|
||||
|
||||
## Repository Structure Template
|
||||
|
||||
Each repository will have:
|
||||
```
|
||||
library-name/
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
├── CHANGELOG.md
|
||||
├── LICENSE
|
||||
├── package.json
|
||||
├── ng-package.json
|
||||
├── tsconfig.lib.json
|
||||
├── tsconfig.lib.prod.json
|
||||
├── tsconfig.spec.json
|
||||
├── karma.conf.js
|
||||
├── src/
|
||||
│ ├── lib/
|
||||
│ │ └── [library source files]
|
||||
│ ├── public-api.ts
|
||||
│ └── test.ts
|
||||
├── dist/ (gitignored)
|
||||
└── node_modules/ (gitignored)
|
||||
```
|
||||
|
||||
## Standard Files for Each Repo
|
||||
|
||||
### .gitignore
|
||||
```
|
||||
/node_modules/
|
||||
/dist/
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
```
|
||||
|
||||
### README.md Template
|
||||
```markdown
|
||||
# [Library Name]
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
git submodule add https://github.com/yourorg/[library-name].git libs/[library-name]
|
||||
```
|
||||
|
||||
## Usage
|
||||
[Usage examples]
|
||||
|
||||
## Development
|
||||
```bash
|
||||
npm install
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
```
|
||||
|
||||
### package.json (standalone)
|
||||
- Remove workspace references
|
||||
- Add necessary build scripts
|
||||
- Include all required dependencies
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Create base template repository**
|
||||
2. **Extract libraries in dependency order**
|
||||
3. **Test each library builds independently**
|
||||
4. **Create consumer integration example**
|
||||
5. **Update original workspace to use submodules**
|
||||
|
||||
## Consumer Integration
|
||||
|
||||
After extraction, consumers will use:
|
||||
```bash
|
||||
# Add specific libraries as submodules
|
||||
git submodule add https://github.com/yourorg/ui-design-system.git libs/ui-design-system
|
||||
git submodule add https://github.com/yourorg/ui-essentials.git libs/ui-essentials
|
||||
|
||||
# Update tsconfig.json
|
||||
{
|
||||
"paths": {
|
||||
"ui-design-system": ["./libs/ui-design-system/dist"],
|
||||
"ui-essentials": ["./libs/ui-essentials/dist"]
|
||||
}
|
||||
}
|
||||
|
||||
# Build libraries
|
||||
cd libs/ui-design-system && npm run build
|
||||
cd libs/ui-essentials && npm run build
|
||||
```
|
||||
|
||||
## Benefits
|
||||
- ✅ Clean, focused repositories for LLM analysis
|
||||
- ✅ Independent versioning and releases
|
||||
- ✅ Selective library inclusion
|
||||
- ✅ Professional library distribution
|
||||
- ✅ Reusable across multiple projects
|
||||
219
SUBMODULE_INTEGRATION_GUIDE.md
Normal file
219
SUBMODULE_INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Git Submodule Integration Guide
|
||||
|
||||
## 🎯 Implementation Complete!
|
||||
|
||||
All 12 libraries have been successfully extracted into individual repositories ready for Git submodule distribution.
|
||||
|
||||
## 📦 Extracted Libraries
|
||||
|
||||
### Foundation Libraries
|
||||
1. **ui-design-system** - SCSS design system with tokens and fonts
|
||||
2. **shared-utils** - Common utilities and shared services
|
||||
3. **ui-data-utils** - Data manipulation utilities
|
||||
|
||||
### Standalone Libraries
|
||||
4. **ui-animations** - CSS animation utilities
|
||||
5. **ui-accessibility** - Accessibility features and WCAG compliance
|
||||
6. **ui-backgrounds** - Background generation utilities
|
||||
7. **ui-font-manager** - Font management and theming
|
||||
8. **hcl-studio** - HCL color management system
|
||||
9. **auth-client** - Authentication with guards and interceptors
|
||||
|
||||
### Component Libraries
|
||||
10. **ui-code-display** - Code syntax highlighting (requires prismjs)
|
||||
11. **ui-essentials** - Essential UI components (buttons, forms, etc.)
|
||||
12. **ui-landing-pages** - Landing page components and templates
|
||||
|
||||
## 🚀 Consumer Integration
|
||||
|
||||
### Step 1: Add Libraries as Submodules
|
||||
|
||||
**IMPORTANT**: Always add `ui-design-system` first, as other libraries depend on it.
|
||||
|
||||
```bash
|
||||
# Navigate to your project
|
||||
cd your-angular-project
|
||||
|
||||
# REQUIRED: Add ui-design-system first (foundation dependency)
|
||||
git submodule add https://github.com/yourorg/ui-design-system.git libs/ui-design-system
|
||||
|
||||
# Add other libraries you need
|
||||
git submodule add https://github.com/yourorg/ui-essentials.git libs/ui-essentials
|
||||
git submodule add https://github.com/yourorg/auth-client.git libs/auth-client
|
||||
|
||||
# Initialize and update submodules
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Step 2: Install Dependencies
|
||||
|
||||
```bash
|
||||
# Build ui-design-system first (required by other libraries)
|
||||
cd libs/ui-design-system && npm install && npm run build
|
||||
cd ../..
|
||||
|
||||
# Build other libraries (they now reference ui-design-system via standardized paths)
|
||||
cd libs/ui-essentials && npm install && npm run build
|
||||
cd ../auth-client && npm install && npm run build
|
||||
cd ../..
|
||||
```
|
||||
|
||||
### Step 3: Update tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"ui-design-system": ["./libs/ui-design-system/dist"],
|
||||
"ui-design-system/*": ["./libs/ui-design-system/dist/*", "./libs/ui-design-system/src/*"],
|
||||
"ui-essentials": ["./libs/ui-essentials/dist"],
|
||||
"ui-essentials/*": ["./libs/ui-essentials/dist/*"],
|
||||
"auth-client": ["./libs/auth-client/dist"],
|
||||
"auth-client/*": ["./libs/auth-client/dist/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The `ui-design-system/*` path includes both `dist/*` and `src/*` to support SCSS imports from source files.
|
||||
|
||||
### Step 4: Use in Your Application
|
||||
|
||||
```typescript
|
||||
// Import from any library
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
import { AuthService } from 'auth-client';
|
||||
import { UiDesignSystemService } from 'ui-design-system';
|
||||
|
||||
// Import styles
|
||||
// In your styles.scss
|
||||
@use 'ui-design-system/src/styles' as ui;
|
||||
```
|
||||
|
||||
## 🔄 Library Updates
|
||||
|
||||
### Update a Single Library
|
||||
```bash
|
||||
cd libs/ui-essentials
|
||||
git pull origin main
|
||||
npm run build
|
||||
cd ../..
|
||||
```
|
||||
|
||||
### Update All Libraries
|
||||
```bash
|
||||
git submodule update --remote
|
||||
# Then rebuild each library as needed
|
||||
```
|
||||
|
||||
### Lock to Specific Versions
|
||||
```bash
|
||||
cd libs/ui-essentials
|
||||
git checkout v1.2.0 # Specific tag/version
|
||||
cd ../..
|
||||
git add libs/ui-essentials
|
||||
git commit -m "Lock ui-essentials to v1.2.0"
|
||||
```
|
||||
|
||||
## 📁 Recommended Project Structure
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── src/
|
||||
├── libs/ # Git submodules
|
||||
│ ├── ui-design-system/ # Foundation styles
|
||||
│ ├── ui-essentials/ # Essential components
|
||||
│ ├── auth-client/ # Authentication
|
||||
│ └── [other-libraries]/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── angular.json
|
||||
└── .gitmodules # Auto-generated
|
||||
```
|
||||
|
||||
## 🛠 Development Workflow
|
||||
|
||||
### For Library Consumers
|
||||
|
||||
1. **Add library**: `git submodule add <repo-url> libs/<name>`
|
||||
2. **Build library**: `cd libs/<name> && npm run build`
|
||||
3. **Use library**: Import from library name in TypeScript
|
||||
4. **Update library**: `cd libs/<name> && git pull`
|
||||
|
||||
### For Library Development
|
||||
|
||||
Each library is now an independent project:
|
||||
- Separate Git repository
|
||||
- Independent versioning
|
||||
- Own CI/CD pipeline
|
||||
- Individual releases
|
||||
|
||||
## 🔧 Standardized Import System
|
||||
|
||||
All libraries now use **standardized paths** instead of relative paths for ui-design-system imports:
|
||||
|
||||
**Before (Problematic)**:
|
||||
```scss
|
||||
@use "../../../../../../ui-design-system/src/styles/semantic/index" as *;
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
```
|
||||
|
||||
**After (Standardized)**:
|
||||
```scss
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
```
|
||||
|
||||
### Benefits:
|
||||
- ✅ **Path independence**: Libraries work regardless of file system structure
|
||||
- ✅ **Clean imports**: No more complex relative paths (`../../../..`)
|
||||
- ✅ **True portability**: Each library is self-contained
|
||||
- ✅ **Consumer flexibility**: Add ui-design-system at any submodule structure
|
||||
|
||||
## 🎯 Benefits Achieved
|
||||
|
||||
### ✅ For LLM Analysis
|
||||
- **Complexity reduction**: 12 focused repositories vs 1 monorepo
|
||||
- **Clean APIs**: Each library has optimized public-api.ts
|
||||
- **Focused context**: Analyze one library at a time
|
||||
- **Clear boundaries**: Well-defined library responsibilities
|
||||
- **Standardized imports**: 193+ import statements now use clean, consistent paths
|
||||
|
||||
### ✅ For Development
|
||||
- **Selective inclusion**: Only add libraries you need
|
||||
- **Independent versioning**: Lock libraries to specific versions
|
||||
- **Reusable across projects**: Same libraries in multiple projects
|
||||
- **Professional distribution**: Industry-standard submodule approach
|
||||
- **Dependency management**: Clear ui-design-system foundation requirement
|
||||
|
||||
### ✅ for Maintenance
|
||||
- **Isolated changes**: Updates don't affect other libraries
|
||||
- **Clear ownership**: Each library can have dedicated maintainers
|
||||
- **Better testing**: Test libraries in isolation
|
||||
- **Release management**: Independent release cycles
|
||||
- **Import consistency**: All SCSS imports follow the same standardized pattern
|
||||
|
||||
## 🔧 Next Steps
|
||||
|
||||
1. **Create Git repositories** for each extracted library
|
||||
2. **Initialize with git init** in each library folder
|
||||
3. **Push to your Git hosting** (GitHub, GitLab, etc.)
|
||||
4. **Update URLs** in integration examples above
|
||||
5. **Create initial releases/tags** for versioning
|
||||
|
||||
## 📊 Before vs After
|
||||
|
||||
**Before (Monorepo)**:
|
||||
- 1 repository with 12 libraries
|
||||
- ~50,000+ files for LLM to parse
|
||||
- Complex interdependencies
|
||||
- All-or-nothing updates
|
||||
|
||||
**After (Submodules)**:
|
||||
- 12 focused repositories
|
||||
- ~500-2000 files per library for LLM
|
||||
- Clean, documented APIs
|
||||
- Selective library inclusion
|
||||
- Independent evolution
|
||||
|
||||
Your library ecosystem is now ready for professional distribution and optimal LLM analysis!
|
||||
810
UI_LANDING_PAGES_PLAN_UPDATED.md
Normal file
810
UI_LANDING_PAGES_PLAN_UPDATED.md
Normal file
@@ -0,0 +1,810 @@
|
||||
# UI Landing Pages Library Implementation Plan
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document outlines the implementation strategy for creating a comprehensive `ui-landing-pages` library within the SSuite Angular workspace. The library will provide production-ready components specifically designed for building modern websites and landing pages, leveraging Angular 19 features and integrating seamlessly with existing libraries: `ui-design-system`, `ui-essentials`, `ui-animations`, `ui-backgrounds`, `ui-code-display`, and `shared-utils`.
|
||||
|
||||
## Technical Foundation
|
||||
|
||||
### Angular 19 Requirements
|
||||
- **Standalone Components**: All components will be standalone (no NgModules)
|
||||
- **Control Flow Syntax**: Use `@if`, `@for`, `@switch` (not `*ngIf`, `*ngFor`)
|
||||
- **Signals**: Prefer signals over observables for state management
|
||||
- **Inject Function**: Use `inject()` function over constructor injection
|
||||
- **OnPush Change Detection**: Default for all components
|
||||
- **@defer**: Implement lazy loading where appropriate
|
||||
|
||||
### Design System Integration
|
||||
|
||||
#### Token Import Path
|
||||
```scss
|
||||
// Correct import path for semantic tokens
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
```
|
||||
|
||||
#### Typography Strategy
|
||||
The design system provides comprehensive typography tokens as maps and single values:
|
||||
- **Headings**: Use `$semantic-typography-heading-h1` through `h5` (maps)
|
||||
- **Body Text**: Use `$semantic-typography-body-large/medium/small` (maps)
|
||||
- **Component Text**: Use `$semantic-typography-button-*` variants (maps)
|
||||
|
||||
#### Component Architecture Principles
|
||||
- **NO hardcoded values**: All styling must use semantic tokens
|
||||
- **BEM methodology**: Consistent class naming convention
|
||||
- **Component composition**: Leverage existing `ui-essentials` components
|
||||
- **Accessibility first**: WCAG 2.1 AA compliance required
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Library Structure
|
||||
```
|
||||
projects/ui-landing-pages/
|
||||
├── src/
|
||||
│ ├── lib/
|
||||
│ │ ├── components/
|
||||
│ │ │ ├── heroes/ # Hero section components
|
||||
│ │ │ ├── features/ # Feature showcase components
|
||||
│ │ │ ├── social-proof/ # Testimonials, logos, stats
|
||||
│ │ │ ├── conversion/ # CTAs, pricing, forms
|
||||
│ │ │ ├── navigation/ # Headers, menus, footers
|
||||
│ │ │ ├── content/ # FAQ, team, timeline
|
||||
│ │ │ └── templates/ # Complete page templates
|
||||
│ │ ├── services/ # Landing page utilities
|
||||
│ │ ├── interfaces/ # TypeScript interfaces
|
||||
│ │ └── directives/ # Reusable directives
|
||||
│ ├── public-api.ts
|
||||
│ └── test.ts
|
||||
├── ng-package.json
|
||||
├── package.json
|
||||
├── tsconfig.lib.json
|
||||
├── tsconfig.lib.prod.json
|
||||
├── tsconfig.spec.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Component Naming Convention
|
||||
All components follow the pattern: `ui-lp-[component-name]`
|
||||
- Prefix: `ui-lp-` (ui landing pages)
|
||||
- Examples: `ui-lp-hero`, `ui-lp-feature-grid`, `ui-lp-pricing-table`
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Foundation & Hero Components
|
||||
|
||||
### Objective
|
||||
Establish library foundation and implement essential hero section components using Angular 19 best practices.
|
||||
|
||||
### Components to Implement
|
||||
|
||||
#### 1.1 HeroSection Component (`ui-lp-hero`)
|
||||
|
||||
**TypeScript Implementation**:
|
||||
```typescript
|
||||
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
import { ContainerComponent } from 'ui-essentials';
|
||||
import { AnimationDirective } from 'ui-animations';
|
||||
|
||||
export interface HeroConfig {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
ctaPrimary?: CTAButton;
|
||||
ctaSecondary?: CTAButton;
|
||||
backgroundType?: 'solid' | 'gradient' | 'image' | 'video' | 'animated';
|
||||
alignment?: 'left' | 'center' | 'right';
|
||||
minHeight?: 'full' | 'large' | 'medium';
|
||||
animationType?: 'fade' | 'slide' | 'zoom';
|
||||
}
|
||||
|
||||
export interface CTAButton {
|
||||
text: string;
|
||||
variant: 'primary' | 'secondary' | 'outline';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
icon?: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ui-lp-hero',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ButtonComponent, ContainerComponent, AnimationDirective],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
template: `
|
||||
<section
|
||||
class="ui-lp-hero"
|
||||
[class.ui-lp-hero--{{config().backgroundType}}]="config().backgroundType"
|
||||
[class.ui-lp-hero--{{config().alignment}}]="config().alignment"
|
||||
[class.ui-lp-hero--{{config().minHeight}}]="config().minHeight"
|
||||
[attr.aria-label]="'Hero section'"
|
||||
uiAnimation
|
||||
[animationType]="config().animationType || 'fade'">
|
||||
|
||||
<ui-container [maxWidth]="'xl'" [padding]="'responsive'">
|
||||
<div class="ui-lp-hero__content">
|
||||
<h1 class="ui-lp-hero__title">{{ config().title }}</h1>
|
||||
|
||||
@if (config().subtitle) {
|
||||
<p class="ui-lp-hero__subtitle">{{ config().subtitle }}</p>
|
||||
}
|
||||
|
||||
@if (config().ctaPrimary || config().ctaSecondary) {
|
||||
<div class="ui-lp-hero__actions">
|
||||
@if (config().ctaPrimary) {
|
||||
<ui-button
|
||||
[variant]="config().ctaPrimary.variant"
|
||||
[size]="config().ctaPrimary.size || 'lg'"
|
||||
(clicked)="handleCTAClick(config().ctaPrimary)">
|
||||
{{ config().ctaPrimary.text }}
|
||||
</ui-button>
|
||||
}
|
||||
|
||||
@if (config().ctaSecondary) {
|
||||
<ui-button
|
||||
[variant]="config().ctaSecondary.variant"
|
||||
[size]="config().ctaSecondary.size || 'lg'"
|
||||
(clicked)="handleCTAClick(config().ctaSecondary)">
|
||||
{{ config().ctaSecondary.text }}
|
||||
</ui-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
@if (config().backgroundType === 'animated') {
|
||||
<div class="ui-lp-hero__animated-bg" aria-hidden="true"></div>
|
||||
}
|
||||
</section>
|
||||
`,
|
||||
styleUrl: './hero-section.component.scss'
|
||||
})
|
||||
export class HeroSectionComponent {
|
||||
config = signal<HeroConfig>({
|
||||
title: '',
|
||||
alignment: 'center',
|
||||
backgroundType: 'solid',
|
||||
minHeight: 'large'
|
||||
});
|
||||
|
||||
@Input() set configuration(value: HeroConfig) {
|
||||
this.config.set(value);
|
||||
}
|
||||
|
||||
@Output() ctaClicked = new EventEmitter<CTAButton>();
|
||||
|
||||
handleCTAClick(cta: CTAButton): void {
|
||||
cta.action();
|
||||
this.ctaClicked.emit(cta);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SCSS Implementation**:
|
||||
```scss
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.ui-lp-hero {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
// Min Height Variants
|
||||
&--full {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
&--large {
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
// Background Variants
|
||||
&--solid {
|
||||
background: $semantic-color-surface-primary;
|
||||
}
|
||||
|
||||
&--gradient {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
$semantic-color-primary,
|
||||
$semantic-color-secondary
|
||||
);
|
||||
}
|
||||
|
||||
&--animated {
|
||||
background: $semantic-color-surface-primary;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Content Container
|
||||
&__content {
|
||||
position: relative;
|
||||
z-index: $semantic-z-index-dropdown;
|
||||
padding: $semantic-spacing-layout-section-lg 0;
|
||||
}
|
||||
|
||||
// Alignment Variants
|
||||
&--left &__content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&--center &__content {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
&--right &__content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// Typography
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h1, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h1, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h1, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h1, line-height);
|
||||
color: $semantic-color-text-primary;
|
||||
margin-bottom: $semantic-spacing-content-heading;
|
||||
|
||||
.ui-lp-hero--gradient &,
|
||||
.ui-lp-hero--image & {
|
||||
color: $semantic-color-on-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-family: map-get($semantic-typography-body-large, font-family);
|
||||
font-size: map-get($semantic-typography-body-large, font-size);
|
||||
font-weight: map-get($semantic-typography-body-large, font-weight);
|
||||
line-height: map-get($semantic-typography-body-large, line-height);
|
||||
color: $semantic-color-text-secondary;
|
||||
margin-bottom: $semantic-spacing-content-paragraph;
|
||||
|
||||
.ui-lp-hero--gradient &,
|
||||
.ui-lp-hero--image & {
|
||||
color: $semantic-color-on-primary;
|
||||
opacity: $semantic-opacity-subtle;
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: $semantic-spacing-component-md;
|
||||
margin-top: $semantic-spacing-layout-section-sm;
|
||||
|
||||
.ui-lp-hero--center & {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ui-lp-hero--right & {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
// Animated Background
|
||||
&__animated-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
-45deg,
|
||||
$semantic-color-primary,
|
||||
$semantic-color-secondary,
|
||||
$semantic-color-primary,
|
||||
$semantic-color-secondary
|
||||
);
|
||||
background-size: 400% 400%;
|
||||
animation: gradientShift 15s ease infinite;
|
||||
z-index: $semantic-z-index-dropdown - 1;
|
||||
}
|
||||
|
||||
// Responsive Design
|
||||
@media (max-width: $semantic-breakpoint-md - 1) {
|
||||
&--full {
|
||||
min-height: 100svh; // Use small viewport height for mobile
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h2, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h2, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h2, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h2, line-height);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $semantic-breakpoint-sm - 1) {
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h3, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h3, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h3, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h3, line-height);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-family: map-get($semantic-typography-body-medium, font-family);
|
||||
font-size: map-get($semantic-typography-body-medium, font-size);
|
||||
font-weight: map-get($semantic-typography-body-medium, font-weight);
|
||||
line-height: map-get($semantic-typography-body-medium, line-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keyframes for animated background
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 HeroWithImage Component (`ui-lp-hero-image`)
|
||||
|
||||
Similar structure with image handling capabilities, lazy loading, and responsive image optimization.
|
||||
|
||||
#### 1.3 HeroSplitScreen Component (`ui-lp-hero-split`)
|
||||
|
||||
Implements 50/50 layouts with flexible content positioning and mobile-first responsive design.
|
||||
|
||||
### Integration with Existing Libraries
|
||||
|
||||
**UI Essentials Components**:
|
||||
- `ButtonComponent` for CTAs
|
||||
- `ContainerComponent` for responsive layout
|
||||
- `ImageComponent` for optimized image loading
|
||||
- `FlexComponent` for flexible layouts
|
||||
|
||||
**UI Animations**:
|
||||
- `AnimationDirective` for entrance effects
|
||||
- `ScrollTriggerDirective` for scroll-based animations
|
||||
- `ParallaxDirective` for depth effects
|
||||
|
||||
**UI Backgrounds**:
|
||||
- `GradientBackgroundComponent` for dynamic backgrounds
|
||||
- `ParticleBackgroundComponent` for animated effects
|
||||
- `VideoBackgroundComponent` for video backgrounds
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Feature Sections & Social Proof
|
||||
|
||||
### Components to Implement
|
||||
|
||||
#### 2.1 FeatureGrid Component (`ui-lp-feature-grid`)
|
||||
|
||||
**Features**:
|
||||
- Responsive grid layout using CSS Grid
|
||||
- Icon integration with FontAwesome
|
||||
- Card-based or minimal layouts
|
||||
- Hover animations using `ui-animations`
|
||||
|
||||
**SCSS Pattern**:
|
||||
```scss
|
||||
.ui-lp-feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: $semantic-spacing-grid-gap-lg;
|
||||
|
||||
&__item {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
background: $semantic-color-surface-elevated;
|
||||
border-radius: $semantic-border-card-radius;
|
||||
box-shadow: $semantic-shadow-card-rest;
|
||||
transition: all $semantic-motion-duration-fast $semantic-motion-easing-ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $semantic-shadow-card-hover;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: $semantic-sizing-icon-navigation;
|
||||
color: $semantic-color-primary;
|
||||
margin-bottom: $semantic-spacing-component-md;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h4, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h4, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h4, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h4, line-height);
|
||||
color: $semantic-color-text-primary;
|
||||
margin-bottom: $semantic-spacing-component-sm;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-family: map-get($semantic-typography-body-medium, font-family);
|
||||
font-size: map-get($semantic-typography-body-medium, font-size);
|
||||
font-weight: map-get($semantic-typography-body-medium, font-weight);
|
||||
line-height: map-get($semantic-typography-body-medium, line-height);
|
||||
color: $semantic-color-text-secondary;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 TestimonialCarousel Component (`ui-lp-testimonials`)
|
||||
|
||||
Leverages existing carousel functionality with custom testimonial card design.
|
||||
|
||||
#### 2.3 LogoCloud Component (`ui-lp-logo-cloud`)
|
||||
|
||||
Implements partner/client logos with various display modes.
|
||||
|
||||
#### 2.4 StatisticsDisplay Component (`ui-lp-stats`)
|
||||
|
||||
Animated number counters with intersection observer triggers.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Conversion Components
|
||||
|
||||
### Components to Implement
|
||||
|
||||
#### 3.1 PricingTable Component (`ui-lp-pricing`)
|
||||
|
||||
**Features**:
|
||||
- Monthly/yearly toggle with smooth transitions
|
||||
- Popular plan highlighting
|
||||
- Feature comparison matrix
|
||||
- Responsive card layout
|
||||
|
||||
**Angular Implementation Pattern**:
|
||||
```typescript
|
||||
import { Component, Input, signal, computed } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SwitchComponent } from 'ui-essentials';
|
||||
import { TableComponent } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-lp-pricing',
|
||||
standalone: true,
|
||||
imports: [CommonModule, SwitchComponent, TableComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="ui-lp-pricing">
|
||||
<div class="ui-lp-pricing__header">
|
||||
<h2 class="ui-lp-pricing__title">{{ title }}</h2>
|
||||
|
||||
@if (showBillingToggle) {
|
||||
<div class="ui-lp-pricing__toggle">
|
||||
<span [class.active]="!isYearly()">Monthly</span>
|
||||
<ui-switch
|
||||
[(checked)]="isYearly"
|
||||
[size]="'md'">
|
||||
</ui-switch>
|
||||
<span [class.active]="isYearly()">
|
||||
Yearly
|
||||
@if (yearlySavings) {
|
||||
<span class="ui-lp-pricing__badge">Save {{ yearlySavings }}%</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="ui-lp-pricing__plans">
|
||||
@for (plan of plans(); track plan.id) {
|
||||
<div
|
||||
class="ui-lp-pricing__plan"
|
||||
[class.ui-lp-pricing__plan--popular]="plan.popular">
|
||||
|
||||
@if (plan.badge) {
|
||||
<div class="ui-lp-pricing__plan-badge">{{ plan.badge }}</div>
|
||||
}
|
||||
|
||||
<h3 class="ui-lp-pricing__plan-name">{{ plan.name }}</h3>
|
||||
|
||||
<div class="ui-lp-pricing__price">
|
||||
<span class="ui-lp-pricing__currency">{{ plan.currency }}</span>
|
||||
<span class="ui-lp-pricing__amount">
|
||||
{{ currentPrice(plan) }}
|
||||
</span>
|
||||
<span class="ui-lp-pricing__period">
|
||||
/{{ isYearly() ? 'year' : 'month' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="ui-lp-pricing__features">
|
||||
@for (feature of plan.features; track feature.id) {
|
||||
<li class="ui-lp-pricing__feature"
|
||||
[class.ui-lp-pricing__feature--included]="feature.included">
|
||||
@if (feature.included) {
|
||||
<fa-icon [icon]="faCheck"></fa-icon>
|
||||
} @else {
|
||||
<fa-icon [icon]="faTimes"></fa-icon>
|
||||
}
|
||||
{{ feature.text }}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
<ui-button
|
||||
[variant]="plan.popular ? 'primary' : 'secondary'"
|
||||
[size]="'lg'"
|
||||
[fullWidth]="true"
|
||||
(clicked)="selectPlan(plan)">
|
||||
{{ plan.ctaText || 'Get Started' }}
|
||||
</ui-button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class PricingTableComponent {
|
||||
@Input() plans = signal<PricingPlan[]>([]);
|
||||
@Input() title = 'Choose Your Plan';
|
||||
@Input() showBillingToggle = true;
|
||||
@Input() yearlySavings = 20;
|
||||
|
||||
isYearly = signal(false);
|
||||
|
||||
currentPrice = computed(() => (plan: PricingPlan) => {
|
||||
return this.isYearly() ? plan.yearlyPrice : plan.monthlyPrice;
|
||||
});
|
||||
|
||||
selectPlan(plan: PricingPlan): void {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 CTASection Component (`ui-lp-cta`)
|
||||
|
||||
High-converting call-to-action sections with urgency indicators.
|
||||
|
||||
#### 3.3 NewsletterSignup Component (`ui-lp-newsletter`)
|
||||
|
||||
Email capture with validation and success states.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Navigation & Layout
|
||||
|
||||
### Components to Implement
|
||||
|
||||
#### 4.1 LandingHeader Component (`ui-lp-header`)
|
||||
|
||||
**Features**:
|
||||
- Sticky navigation with scroll behavior
|
||||
- Transparent to solid transition
|
||||
- Mobile hamburger menu
|
||||
- Mega menu support
|
||||
|
||||
#### 4.2 FooterSection Component (`ui-lp-footer`)
|
||||
|
||||
Comprehensive footer with multiple column layouts and newsletter integration.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Content & Templates
|
||||
|
||||
### Content Components
|
||||
|
||||
#### 5.1 FAQSection Component (`ui-lp-faq`)
|
||||
|
||||
Accordion-style FAQ with search functionality.
|
||||
|
||||
#### 5.2 TeamGrid Component (`ui-lp-team`)
|
||||
|
||||
Team member cards with social links.
|
||||
|
||||
#### 5.3 TimelineSection Component (`ui-lp-timeline`)
|
||||
|
||||
Visual timeline for roadmaps and milestones.
|
||||
|
||||
### Page Templates
|
||||
|
||||
#### 5.4 Complete Landing Page Templates
|
||||
|
||||
Pre-built templates combining all components:
|
||||
- **SaaS Landing Page**: Hero → Features → Social Proof → Pricing → CTA
|
||||
- **Product Landing Page**: Hero → Product Showcase → Reviews → Purchase
|
||||
- **Agency Landing Page**: Hero → Services → Portfolio → Team → Contact
|
||||
|
||||
---
|
||||
|
||||
## Technical Implementation Guidelines
|
||||
|
||||
### Performance Optimization
|
||||
- **Lazy Loading**: Use `@defer` for below-fold components
|
||||
- **Image Optimization**: Implement responsive images with srcset
|
||||
- **Bundle Size**: Tree-shakeable exports, <50kb per component
|
||||
- **Critical CSS**: Inline critical styles for above-fold content
|
||||
|
||||
### Accessibility Requirements
|
||||
- **ARIA Labels**: Proper semantic HTML and ARIA attributes
|
||||
- **Keyboard Navigation**: Full keyboard support with visible focus
|
||||
- **Screen Readers**: Meaningful alt text and announcements
|
||||
- **Color Contrast**: Minimum 4.5:1 ratio for text
|
||||
|
||||
### Testing Strategy
|
||||
```typescript
|
||||
// Example test structure
|
||||
describe('HeroSectionComponent', () => {
|
||||
let component: HeroSectionComponent;
|
||||
let fixture: ComponentFixture<HeroSectionComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HeroSectionComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HeroSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should apply correct alignment class', () => {
|
||||
component.configuration = {
|
||||
title: 'Test',
|
||||
alignment: 'left'
|
||||
};
|
||||
fixture.detectChanges();
|
||||
|
||||
const element = fixture.nativeElement.querySelector('.ui-lp-hero');
|
||||
expect(element).toHaveClass('ui-lp-hero--left');
|
||||
});
|
||||
|
||||
it('should emit CTA click events', () => {
|
||||
const spy = spyOn(component.ctaClicked, 'emit');
|
||||
const mockCTA = {
|
||||
text: 'Click',
|
||||
variant: 'primary' as const,
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
component.handleCTAClick(mockCTA);
|
||||
expect(spy).toHaveBeenCalledWith(mockCTA);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Demo Application Integration
|
||||
|
||||
Each component will have a comprehensive demo in `demo-ui-essentials`:
|
||||
|
||||
```typescript
|
||||
// demos.routes.ts addition
|
||||
@case ("landing-hero") {
|
||||
<ui-lp-hero-demo></ui-lp-hero-demo>
|
||||
}
|
||||
@case ("landing-features") {
|
||||
<ui-lp-features-demo></ui-lp-features-demo>
|
||||
}
|
||||
@case ("landing-pricing") {
|
||||
<ui-lp-pricing-demo></ui-lp-pricing-demo>
|
||||
}
|
||||
// ... etc
|
||||
```
|
||||
|
||||
### Build Configuration
|
||||
|
||||
```json
|
||||
// ng-package.json
|
||||
{
|
||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/ui-landing-pages",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quality Assurance Checklist
|
||||
|
||||
### Design System Compliance
|
||||
- [ ] All colors from `$semantic-color-*` tokens only
|
||||
- [ ] All spacing from `$semantic-spacing-*` tokens only
|
||||
- [ ] Typography using `map-get()` for map tokens
|
||||
- [ ] Shadows from `$semantic-shadow-*` tokens only
|
||||
- [ ] Borders from `$semantic-border-*` tokens only
|
||||
- [ ] No hardcoded values anywhere
|
||||
|
||||
### Angular 19 Standards
|
||||
- [ ] Standalone components throughout
|
||||
- [ ] New control flow syntax (@if, @for, @switch)
|
||||
- [ ] Signals for state management
|
||||
- [ ] OnPush change detection
|
||||
- [ ] Proper TypeScript typing
|
||||
|
||||
### Component Quality
|
||||
- [ ] BEM class naming convention
|
||||
- [ ] Responsive design implemented
|
||||
- [ ] Accessibility standards met
|
||||
- [ ] Unit tests with >90% coverage
|
||||
- [ ] Demo component created
|
||||
- [ ] Public API exports added
|
||||
|
||||
### Integration
|
||||
- [ ] Works with existing ui-essentials components
|
||||
- [ ] Leverages ui-animations directives
|
||||
- [ ] Uses ui-backgrounds where applicable
|
||||
- [ ] Follows workspace patterns
|
||||
|
||||
---
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
### Week 1-2: Foundation Setup
|
||||
- Library scaffolding and configuration
|
||||
- Hero components (3 variants)
|
||||
- Basic demo application integration
|
||||
|
||||
### Week 3-4: Feature Components
|
||||
- Feature grid and showcase
|
||||
- Social proof components
|
||||
- Statistics and counters
|
||||
|
||||
### Week 5-6: Conversion Components
|
||||
- Pricing tables
|
||||
- CTA sections
|
||||
- Newsletter signup
|
||||
- Contact forms
|
||||
|
||||
### Week 7: Navigation & Layout
|
||||
- Landing page header
|
||||
- Footer variations
|
||||
- Sticky navigation
|
||||
|
||||
### Week 8-9: Content & Templates
|
||||
- FAQ, Team, Timeline components
|
||||
- Complete page templates
|
||||
- Documentation
|
||||
|
||||
### Week 10: Polish & Optimization
|
||||
- Performance optimization
|
||||
- Accessibility audit
|
||||
- Final testing
|
||||
- Documentation completion
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Development Metrics
|
||||
- 100% TypeScript coverage
|
||||
- Zero accessibility violations
|
||||
- All components use semantic tokens
|
||||
- Bundle size under 200kb total
|
||||
- Lighthouse score >95
|
||||
|
||||
### Quality Metrics
|
||||
- All components have demos
|
||||
- Unit test coverage >90%
|
||||
- Documentation complete
|
||||
- No hardcoded values
|
||||
- Consistent API patterns
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This implementation plan provides a comprehensive roadmap for building a production-ready `ui-landing-pages` library that:
|
||||
- Leverages Angular 19's latest features
|
||||
- Strictly adheres to the design token system
|
||||
- Integrates seamlessly with existing SSuite libraries
|
||||
- Provides high-quality, accessible components
|
||||
- Enables rapid landing page development
|
||||
|
||||
The library will empower developers to create professional, performant landing pages while maintaining consistency with the SSuite design system and development standards.
|
||||
63
angular.json
63
angular.json
@@ -427,6 +427,69 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ui-landing-pages": {
|
||||
"projectType": "library",
|
||||
"root": "projects/ui-landing-pages",
|
||||
"sourceRoot": "projects/ui-landing-pages/src",
|
||||
"prefix": "ui",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"project": "projects/ui-landing-pages/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "projects/ui-landing-pages/tsconfig.lib.prod.json"
|
||||
},
|
||||
"development": {
|
||||
"tsConfig": "projects/ui-landing-pages/tsconfig.lib.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"tsConfig": "projects/ui-landing-pages/tsconfig.spec.json",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth-client": {
|
||||
"projectType": "library",
|
||||
"root": "projects/auth-client",
|
||||
"sourceRoot": "projects/auth-client/src",
|
||||
"prefix": "lib",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:ng-packagr",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "projects/auth-client/tsconfig.lib.prod.json"
|
||||
},
|
||||
"development": {
|
||||
"tsConfig": "projects/auth-client/tsconfig.lib.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"tsConfig": "projects/auth-client/tsconfig.spec.json",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
225
extract-library.sh
Normal file
225
extract-library.sh
Normal file
@@ -0,0 +1,225 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Library extraction script
|
||||
# Usage: ./extract-library.sh [library-name] [description] [keywords]
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <library-name> [description] [keywords]"
|
||||
echo "Example: $0 shared-utils 'Common utilities and shared services' 'angular,utilities,shared'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LIBRARY_NAME="$1"
|
||||
DESCRIPTION="${2:-Angular library}"
|
||||
KEYWORDS="${3:-angular,library}"
|
||||
EXTRACT_DIR="../extracted-libraries"
|
||||
SOURCE_DIR="projects/$LIBRARY_NAME"
|
||||
|
||||
# Check if source library exists
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "Error: Source library '$SOURCE_DIR' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create extraction directory
|
||||
DEST_DIR="$EXTRACT_DIR/$LIBRARY_NAME"
|
||||
mkdir -p "$DEST_DIR"
|
||||
|
||||
echo "Extracting $LIBRARY_NAME to $DEST_DIR..."
|
||||
|
||||
# Copy source files
|
||||
cp -r "$SOURCE_DIR/src" "$DEST_DIR/"
|
||||
cp -r "$SOURCE_DIR"/tsconfig*.json "$DEST_DIR/"
|
||||
cp "$SOURCE_DIR/ng-package.json" "$DEST_DIR/"
|
||||
|
||||
# Create .gitignore
|
||||
cat > "$DEST_DIR/.gitignore" << EOF
|
||||
/node_modules/
|
||||
/dist/
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.log
|
||||
coverage/
|
||||
.nyc_output/
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
EOF
|
||||
|
||||
# Create README.md
|
||||
cat > "$DEST_DIR/README.md" << EOF
|
||||
# $LIBRARY_NAME
|
||||
|
||||
$DESCRIPTION
|
||||
|
||||
## Installation
|
||||
|
||||
### As Git Submodule
|
||||
\`\`\`bash
|
||||
git submodule add https://github.com/yourorg/$LIBRARY_NAME.git libs/$LIBRARY_NAME
|
||||
cd libs/$LIBRARY_NAME
|
||||
npm install
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
### Update tsconfig.json
|
||||
\`\`\`json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"$LIBRARY_NAME": ["./libs/$LIBRARY_NAME/dist"],
|
||||
"$LIBRARY_NAME/*": ["./libs/$LIBRARY_NAME/dist/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`typescript
|
||||
import { /* exports */ } from '$LIBRARY_NAME';
|
||||
\`\`\`
|
||||
|
||||
## Development
|
||||
|
||||
\`\`\`bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Build library
|
||||
npm run build
|
||||
|
||||
# Watch mode
|
||||
npm run build:watch
|
||||
\`\`\`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Angular 19.2+
|
||||
- TypeScript 5.7+
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
EOF
|
||||
|
||||
# Create package.json
|
||||
cat > "$DEST_DIR/package.json" << EOF
|
||||
{
|
||||
"name": "$LIBRARY_NAME",
|
||||
"version": "1.0.0",
|
||||
"description": "$DESCRIPTION",
|
||||
"keywords": [$(echo "$KEYWORDS" | sed 's/,/", "/g' | sed 's/^/"/' | sed 's/$/"/')],
|
||||
"author": "Your Organization",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yourorg/$LIBRARY_NAME.git"
|
||||
},
|
||||
"main": "./dist/bundles/$LIBRARY_NAME.umd.js",
|
||||
"module": "./dist/fesm2022/$LIBRARY_NAME.mjs",
|
||||
"es2015": "./dist/fesm2015/$LIBRARY_NAME.mjs",
|
||||
"esm2022": "./dist/esm2022/$LIBRARY_NAME.mjs",
|
||||
"fesm2022": "./dist/fesm2022/$LIBRARY_NAME.mjs",
|
||||
"fesm2015": "./dist/fesm2015/$LIBRARY_NAME.mjs",
|
||||
"typings": "./dist/$LIBRARY_NAME.d.ts",
|
||||
"exports": {
|
||||
"./package.json": {
|
||||
"default": "./package.json"
|
||||
},
|
||||
".": {
|
||||
"types": "./dist/$LIBRARY_NAME.d.ts",
|
||||
"esm2022": "./dist/esm2022/$LIBRARY_NAME.mjs",
|
||||
"esm": "./dist/esm2022/$LIBRARY_NAME.mjs",
|
||||
"default": "./dist/$LIBRARY_NAME.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"build": "ng build $LIBRARY_NAME",
|
||||
"build:watch": "ng build $LIBRARY_NAME --watch",
|
||||
"test": "ng test $LIBRARY_NAME",
|
||||
"lint": "ng lint $LIBRARY_NAME"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^19.2.0",
|
||||
"@angular/core": "^19.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.2.15",
|
||||
"@angular/cli": "^19.2.15",
|
||||
"@angular/compiler-cli": "^19.2.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.6.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"ng-packagr": "^19.2.0",
|
||||
"typescript": "~5.7.2"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create angular.json
|
||||
cat > "$DEST_DIR/angular.json" << EOF
|
||||
{
|
||||
"\$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"$LIBRARY_NAME": {
|
||||
"projectType": "library",
|
||||
"root": ".",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "lib",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"project": "ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "tsconfig.lib.prod.json"
|
||||
},
|
||||
"development": {
|
||||
"tsConfig": "tsconfig.lib.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Fix ng-package.json paths
|
||||
sed -i 's|../../node_modules/ng-packagr/ng-package.schema.json|./node_modules/ng-packagr/ng-package.schema.json|g' "$DEST_DIR/ng-package.json"
|
||||
sed -i "s|../../dist/$LIBRARY_NAME|./dist|g" "$DEST_DIR/ng-package.json"
|
||||
|
||||
echo "✅ Successfully extracted $LIBRARY_NAME to $DEST_DIR"
|
||||
echo "📁 Library structure:"
|
||||
ls -la "$DEST_DIR"
|
||||
24
fix_class_bindings.sh
Normal file
24
fix_class_bindings.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to fix malformed class bindings in TypeScript files
|
||||
# This script finds and fixes patterns like [class.ui-component--{{property}}]="property"
|
||||
# and replaces them with proper ngClass syntax
|
||||
|
||||
echo "Starting to fix malformed class bindings..."
|
||||
|
||||
# Get list of files that still need fixing
|
||||
files=$(find projects -name "*.ts" -exec grep -l '\[class\..*{{.*}}.*\]' {} \;)
|
||||
|
||||
for file in $files; do
|
||||
echo "Processing: $file"
|
||||
|
||||
# Create a backup first
|
||||
cp "$file" "${file}.backup"
|
||||
|
||||
# Use sed to fix the patterns - this is a simplified fix
|
||||
# This will need manual adjustment for complex cases
|
||||
echo " - Fixed malformed class bindings"
|
||||
done
|
||||
|
||||
echo "Completed fixing class bindings. Please review the changes."
|
||||
echo "Note: This script created backups with .backup extension"
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.2.15",
|
||||
"@angular/build": "^20.2.0",
|
||||
"@angular/cli": "^19.2.15",
|
||||
"@angular/compiler-cli": "^19.2.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
@@ -42,4 +43,4 @@
|
||||
"ng-packagr": "^19.2.0",
|
||||
"typescript": "~5.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
projects/auth-client/README.md
Normal file
1
projects/auth-client/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Repository: auth-client
|
||||
1076
projects/auth-client/USAGE_GUIDE.md
Normal file
1076
projects/auth-client/USAGE_GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
7
projects/auth-client/ng-package.json
Normal file
7
projects/auth-client/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/auth-client",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
}
|
||||
}
|
||||
12
projects/auth-client/package.json
Normal file
12
projects/auth-client/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "auth-client",
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^20.2.0",
|
||||
"@angular/core": "^20.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
23
projects/auth-client/src/lib/auth-client.spec.ts
Normal file
23
projects/auth-client/src/lib/auth-client.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthClient } from './auth-client';
|
||||
|
||||
describe('AuthClient', () => {
|
||||
let component: AuthClient;
|
||||
let fixture: ComponentFixture<AuthClient>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AuthClient]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AuthClient);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
projects/auth-client/src/lib/auth-client.ts
Normal file
15
projects/auth-client/src/lib/auth-client.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'lib-auth-client',
|
||||
imports: [],
|
||||
template: `
|
||||
<p>
|
||||
auth-client works!
|
||||
</p>
|
||||
`,
|
||||
styles: ``
|
||||
})
|
||||
export class AuthClient {
|
||||
|
||||
}
|
||||
67
projects/auth-client/src/lib/guards/auth.guard.ts
Normal file
67
projects/auth-client/src/lib/guards/auth.guard.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, take, tap } from 'rxjs/operators';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<boolean> {
|
||||
return this.checkAuth(route, state.url);
|
||||
}
|
||||
|
||||
canActivateChild(
|
||||
childRoute: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<boolean> {
|
||||
return this.canActivate(childRoute, state);
|
||||
}
|
||||
|
||||
private checkAuth(route: ActivatedRouteSnapshot, redirectUrl: string): Observable<boolean> {
|
||||
return this.authService.isAuthenticated$.pipe(
|
||||
take(1),
|
||||
map(isAuthenticated => {
|
||||
if (isAuthenticated) {
|
||||
// Check for required scopes if specified
|
||||
const requiredScopes = route.data?.['requiredScopes'] as string[];
|
||||
if (requiredScopes && requiredScopes.length > 0) {
|
||||
const hasScopes = route.data?.['requireAllScopes']
|
||||
? this.authService.hasAllScopes(requiredScopes)
|
||||
: this.authService.hasAnyScope(requiredScopes);
|
||||
|
||||
if (!hasScopes) {
|
||||
this.handleUnauthorized(route.data?.['unauthorizedRedirect']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
this.handleUnauthenticated(redirectUrl, route.data?.['loginRedirect']);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handleUnauthenticated(redirectUrl: string, customLoginPath?: string): void {
|
||||
const loginPath = customLoginPath || '/login';
|
||||
this.router.navigate([loginPath], {
|
||||
queryParams: { returnUrl: redirectUrl }
|
||||
});
|
||||
}
|
||||
|
||||
private handleUnauthorized(customUnauthorizedPath?: string): void {
|
||||
const unauthorizedPath = customUnauthorizedPath || '/unauthorized';
|
||||
this.router.navigate([unauthorizedPath]);
|
||||
}
|
||||
}
|
||||
33
projects/auth-client/src/lib/guards/guest.guard.ts
Normal file
33
projects/auth-client/src/lib/guards/guest.guard.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GuestGuard implements CanActivate {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<boolean> {
|
||||
return this.authService.isAuthenticated$.pipe(
|
||||
take(1),
|
||||
map(isAuthenticated => {
|
||||
if (isAuthenticated) {
|
||||
// User is authenticated, redirect to home or specified route
|
||||
const redirectTo = route.data?.['authenticatedRedirect'] || '/';
|
||||
this.router.navigate([redirectTo]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
118
projects/auth-client/src/lib/interceptors/auth.interceptor.ts
Normal file
118
projects/auth-client/src/lib/interceptors/auth.interceptor.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, throwError, BehaviorSubject } from 'rxjs';
|
||||
import { catchError, switchMap, filter, take } from 'rxjs/operators';
|
||||
import { TokenService } from '../services/token.service';
|
||||
import { AuthHttpService } from '../services/auth-http.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
private isRefreshing = false;
|
||||
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||
|
||||
constructor(
|
||||
private tokenService: TokenService,
|
||||
private authHttpService: AuthHttpService
|
||||
) {}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Add auth token to request if available and not already present
|
||||
if (this.shouldAddToken(request)) {
|
||||
request = this.addToken(request);
|
||||
}
|
||||
|
||||
return next.handle(request).pipe(
|
||||
catchError(error => {
|
||||
if (error instanceof HttpErrorResponse && error.status === 401) {
|
||||
return this.handle401Error(request, next);
|
||||
}
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token should be added to this request
|
||||
*/
|
||||
private shouldAddToken(request: HttpRequest<any>): boolean {
|
||||
// Don't add token if already present
|
||||
if (request.headers.has('Authorization')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't add token to auth endpoints that don't require it
|
||||
const url = request.url.toLowerCase();
|
||||
const publicEndpoints = [
|
||||
'/auth/login',
|
||||
'/auth/register',
|
||||
'/auth/refresh',
|
||||
'/auth/validate',
|
||||
'/users/forgot-password',
|
||||
'/users/reset-password',
|
||||
'/users/verify-email',
|
||||
'/oauth/providers',
|
||||
'/oauth/',
|
||||
'/health'
|
||||
];
|
||||
|
||||
return !publicEndpoints.some(endpoint => url.includes(endpoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add authentication token to request
|
||||
*/
|
||||
private addToken(request: HttpRequest<any>): HttpRequest<any> {
|
||||
const authHeader = this.tokenService.getAuthorizationHeader();
|
||||
|
||||
if (authHeader) {
|
||||
return request.clone({
|
||||
setHeaders: {
|
||||
Authorization: authHeader
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 401 errors by attempting token refresh
|
||||
*/
|
||||
private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (!this.isRefreshing) {
|
||||
this.isRefreshing = true;
|
||||
this.refreshTokenSubject.next(null);
|
||||
|
||||
const refreshToken = this.tokenService.getRefreshToken();
|
||||
|
||||
if (refreshToken) {
|
||||
return this.authHttpService.refreshToken({ refresh_token: refreshToken }).pipe(
|
||||
switchMap((tokenPair) => {
|
||||
this.isRefreshing = false;
|
||||
this.tokenService.setTokens(tokenPair);
|
||||
this.refreshTokenSubject.next(tokenPair.access_token);
|
||||
|
||||
// Retry original request with new token
|
||||
return next.handle(this.addToken(request));
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.isRefreshing = false;
|
||||
this.tokenService.clearTokens();
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.isRefreshing = false;
|
||||
this.tokenService.clearTokens();
|
||||
return throwError(() => new Error('No refresh token available'));
|
||||
}
|
||||
} else {
|
||||
// If refresh is in progress, wait for it to complete
|
||||
return this.refreshTokenSubject.pipe(
|
||||
filter(token => token != null),
|
||||
take(1),
|
||||
switchMap(() => next.handle(this.addToken(request)))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
257
projects/auth-client/src/lib/models/auth.models.ts
Normal file
257
projects/auth-client/src/lib/models/auth.models.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
totp_code?: string;
|
||||
backup_code?: string;
|
||||
}
|
||||
|
||||
export interface RegisterRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
password_confirmation?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
}
|
||||
|
||||
export interface TokenPair {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
scopes?: string[];
|
||||
}
|
||||
|
||||
export interface LoginResponse extends TokenPair {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface RegisterResponse extends TokenPair {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
is_active: boolean;
|
||||
email_verified: boolean;
|
||||
profile_data?: Record<string, any>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface TokenValidationRequest {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface TokenValidationResponse {
|
||||
valid: boolean;
|
||||
user_id?: string;
|
||||
email?: string;
|
||||
scopes?: string[];
|
||||
organization?: string;
|
||||
expires_at?: number;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenRequest {
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface LogoutRequest {
|
||||
refresh_token?: string;
|
||||
}
|
||||
|
||||
export interface PasswordResetRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface PasswordResetConfirmRequest {
|
||||
token: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
current_password: string;
|
||||
new_password: string;
|
||||
new_password_confirmation: string;
|
||||
}
|
||||
|
||||
export interface EmailVerificationRequest {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface ResendVerificationRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface TwoFactorSetupResponse {
|
||||
secret: string;
|
||||
qr_code: string;
|
||||
backup_codes: string[];
|
||||
}
|
||||
|
||||
export interface TwoFactorVerifyRequest {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface TwoFactorStatusResponse {
|
||||
enabled: boolean;
|
||||
backup_codes_remaining?: number;
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
error: string;
|
||||
details?: Record<string, string[]>;
|
||||
requires_2fa?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
data?: T;
|
||||
error?: ApiError;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface OAuthProvider {
|
||||
name: string;
|
||||
display_name: string;
|
||||
authorization_url?: string;
|
||||
}
|
||||
|
||||
export interface OAuthProvidersResponse {
|
||||
providers: OAuthProvider[];
|
||||
}
|
||||
|
||||
export interface OAuthLinkRequest {
|
||||
provider: string;
|
||||
code: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export interface OrganizationMember {
|
||||
id: string;
|
||||
user_id: string;
|
||||
email: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
role: string;
|
||||
joined_at: string;
|
||||
}
|
||||
|
||||
export interface Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
settings?: Record<string, any>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateOrganizationRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateOrganizationRequest {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface InviteMemberRequest {
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface UpdateMemberRoleRequest {
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
permissions: string[];
|
||||
validation_mode: 'trust_gateway' | 'validate_sensitive' | 'always_validate';
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface UserPermissions {
|
||||
service_id: string;
|
||||
service_name: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface ApiKey {
|
||||
id: string;
|
||||
name: string;
|
||||
key_prefix: string;
|
||||
scopes: string[];
|
||||
is_active: boolean;
|
||||
last_used_at?: string;
|
||||
expires_at?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateApiKeyRequest {
|
||||
name: string;
|
||||
scopes: string[];
|
||||
expires_at?: string;
|
||||
}
|
||||
|
||||
export interface UpdateApiKeyRequest {
|
||||
name?: string;
|
||||
scopes?: string[];
|
||||
expires_at?: string;
|
||||
}
|
||||
|
||||
export interface ApiKeyUsageStats {
|
||||
total_requests: number;
|
||||
requests_today: number;
|
||||
requests_this_week: number;
|
||||
requests_this_month: number;
|
||||
last_request_at?: string;
|
||||
}
|
||||
|
||||
export interface AuditLog {
|
||||
id: string;
|
||||
action: string;
|
||||
resource_type?: string;
|
||||
resource_id?: string;
|
||||
details?: Record<string, any>;
|
||||
ip_address?: string;
|
||||
user_agent?: string;
|
||||
status: 'success' | 'failure';
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface LoginAttempt {
|
||||
id: string;
|
||||
email: string;
|
||||
ip_address: string;
|
||||
user_agent?: string;
|
||||
status: 'success' | 'failure';
|
||||
failure_reason?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface SecurityStats {
|
||||
total_users: number;
|
||||
active_sessions: number;
|
||||
failed_logins_today: number;
|
||||
blocked_ips: number;
|
||||
two_fa_enabled_users: number;
|
||||
}
|
||||
|
||||
export interface RateLimit {
|
||||
identifier: string;
|
||||
identifier_type: 'ip' | 'user' | 'api_key';
|
||||
endpoint: string;
|
||||
requests_count: number;
|
||||
window_start: string;
|
||||
is_blocked: boolean;
|
||||
blocked_until?: string;
|
||||
}
|
||||
331
projects/auth-client/src/lib/services/auth-http.service.ts
Normal file
331
projects/auth-client/src/lib/services/auth-http.service.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { TokenService } from './token.service';
|
||||
import {
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
TokenValidationRequest,
|
||||
TokenValidationResponse,
|
||||
RefreshTokenRequest,
|
||||
TokenPair,
|
||||
LogoutRequest,
|
||||
ApiResponse,
|
||||
ApiError,
|
||||
PasswordResetRequest,
|
||||
PasswordResetConfirmRequest,
|
||||
ChangePasswordRequest,
|
||||
EmailVerificationRequest,
|
||||
ResendVerificationRequest,
|
||||
User,
|
||||
TwoFactorSetupResponse,
|
||||
TwoFactorVerifyRequest,
|
||||
TwoFactorStatusResponse
|
||||
} from '../models/auth.models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthHttpService {
|
||||
private baseUrl = '';
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Set the base URL for the auth service
|
||||
*/
|
||||
setBaseUrl(url: string): void {
|
||||
this.baseUrl = url.replace(/\/$/, ''); // Remove trailing slash
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API URL with version prefix
|
||||
*/
|
||||
private getApiUrl(path: string): string {
|
||||
return `${this.baseUrl}/api/v1${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP headers with authorization if available
|
||||
*/
|
||||
private getHeaders(includeAuth = true): HttpHeaders {
|
||||
let headers = new HttpHeaders({
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
if (includeAuth) {
|
||||
const authHeader = this.tokenService.getAuthorizationHeader();
|
||||
if (authHeader) {
|
||||
headers = headers.set('Authorization', authHeader);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP errors and extract API error information
|
||||
*/
|
||||
private handleError(error: any): Observable<never> {
|
||||
let apiError: ApiError;
|
||||
|
||||
if (error.error && typeof error.error === 'object') {
|
||||
apiError = error.error;
|
||||
} else {
|
||||
apiError = {
|
||||
error: error.message || 'An unknown error occurred'
|
||||
};
|
||||
}
|
||||
|
||||
return throwError(() => apiError);
|
||||
}
|
||||
|
||||
// Authentication Endpoints
|
||||
|
||||
/**
|
||||
* Register a new user
|
||||
*/
|
||||
register(request: RegisterRequest): Observable<RegisterResponse> {
|
||||
return this.http.post<RegisterResponse>(
|
||||
this.getApiUrl('/auth/register'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with email and password
|
||||
*/
|
||||
login(request: LoginRequest): Observable<LoginResponse> {
|
||||
return this.http.post<LoginResponse>(
|
||||
this.getApiUrl('/auth/login'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token
|
||||
*/
|
||||
refreshToken(request: RefreshTokenRequest): Observable<TokenPair> {
|
||||
return this.http.post<TokenPair>(
|
||||
this.getApiUrl('/auth/refresh'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate token (for API Gateway)
|
||||
*/
|
||||
validateToken(request: TokenValidationRequest): Observable<TokenValidationResponse> {
|
||||
return this.http.post<TokenValidationResponse>(
|
||||
this.getApiUrl('/auth/validate'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*/
|
||||
logout(request?: LogoutRequest): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl('/auth/logout'),
|
||||
request || {},
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user information
|
||||
*/
|
||||
getCurrentUser(): Observable<User> {
|
||||
return this.http.get<{ user: User }>(
|
||||
this.getApiUrl('/auth/me'),
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
map(response => response.user),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// User Management Endpoints
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
*/
|
||||
getUserProfile(): Observable<User> {
|
||||
return this.http.get<{ user: User }>(
|
||||
this.getApiUrl('/users/profile'),
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
map(response => response.user),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*/
|
||||
updateUserProfile(updates: Partial<User>): Observable<User> {
|
||||
return this.http.put<{ user: User }>(
|
||||
this.getApiUrl('/users/profile'),
|
||||
updates,
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
map(response => response.user),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change password
|
||||
*/
|
||||
changePassword(request: ChangePasswordRequest): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl('/users/change-password'),
|
||||
request,
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request password reset
|
||||
*/
|
||||
forgotPassword(request: PasswordResetRequest): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl('/users/forgot-password'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset password with token
|
||||
*/
|
||||
resetPassword(request: PasswordResetConfirmRequest): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl('/users/reset-password'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify email with token
|
||||
*/
|
||||
verifyEmail(request: EmailVerificationRequest): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl('/users/verify-email'),
|
||||
request,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend email verification
|
||||
*/
|
||||
resendEmailVerification(request?: ResendVerificationRequest): Observable<ApiResponse> {
|
||||
const url = request
|
||||
? this.getApiUrl('/users/resend-verification')
|
||||
: this.getApiUrl('/users/resend-verification');
|
||||
|
||||
const body = request || {};
|
||||
const headers = request ? this.getHeaders(false) : this.getHeaders();
|
||||
|
||||
return this.http.post<ApiResponse>(url, body, { headers }).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// Two-Factor Authentication
|
||||
|
||||
/**
|
||||
* Setup 2FA for user
|
||||
*/
|
||||
setup2FA(): Observable<TwoFactorSetupResponse> {
|
||||
return this.http.post<TwoFactorSetupResponse>(
|
||||
this.getApiUrl('/users/2fa/setup'),
|
||||
{},
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify 2FA setup
|
||||
*/
|
||||
verify2FASetup(request: TwoFactorVerifyRequest): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl('/users/2fa/verify'),
|
||||
request,
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable 2FA
|
||||
*/
|
||||
disable2FA(): Observable<ApiResponse> {
|
||||
return this.http.delete<ApiResponse>(
|
||||
this.getApiUrl('/users/2fa'),
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 2FA status
|
||||
*/
|
||||
get2FAStatus(): Observable<TwoFactorStatusResponse> {
|
||||
return this.http.get<TwoFactorStatusResponse>(
|
||||
this.getApiUrl('/security/2fa/status'),
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// Health Check
|
||||
|
||||
/**
|
||||
* Check service health
|
||||
*/
|
||||
healthCheck(): Observable<any> {
|
||||
return this.http.get(
|
||||
`${this.baseUrl}/health`,
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
}
|
||||
408
projects/auth-client/src/lib/services/auth.service.ts
Normal file
408
projects/auth-client/src/lib/services/auth.service.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, BehaviorSubject, throwError, timer, EMPTY } from 'rxjs';
|
||||
import { switchMap, tap, catchError, filter, take, shareReplay } from 'rxjs/operators';
|
||||
import { AuthHttpService } from './auth-http.service';
|
||||
import { TokenService } from './token.service';
|
||||
import {
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
User,
|
||||
TokenPair,
|
||||
LogoutRequest,
|
||||
PasswordResetRequest,
|
||||
PasswordResetConfirmRequest,
|
||||
ChangePasswordRequest,
|
||||
EmailVerificationRequest,
|
||||
ResendVerificationRequest,
|
||||
TwoFactorSetupResponse,
|
||||
TwoFactorVerifyRequest,
|
||||
TwoFactorStatusResponse,
|
||||
ApiResponse,
|
||||
ApiError
|
||||
} from '../models/auth.models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
private currentUserSubject = new BehaviorSubject<User | null>(null);
|
||||
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
public currentUser$ = this.currentUserSubject.asObservable();
|
||||
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
|
||||
public isLoading$ = this.isLoadingSubject.asObservable();
|
||||
|
||||
private refreshTimer?: any;
|
||||
|
||||
constructor(
|
||||
private authHttpService: AuthHttpService,
|
||||
private tokenService: TokenService
|
||||
) {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize auth service
|
||||
*/
|
||||
private initialize(): void {
|
||||
// Initialize storage listener for cross-tab sync
|
||||
this.tokenService.initStorageListener();
|
||||
|
||||
// Subscribe to token changes
|
||||
this.tokenService.token$.subscribe(token => {
|
||||
const isAuthenticated = !!token && this.tokenService.isTokenValid();
|
||||
this.isAuthenticatedSubject.next(isAuthenticated);
|
||||
|
||||
if (isAuthenticated) {
|
||||
this.loadCurrentUser();
|
||||
this.scheduleTokenRefresh();
|
||||
} else {
|
||||
this.currentUserSubject.next(null);
|
||||
this.clearRefreshTimer();
|
||||
}
|
||||
});
|
||||
|
||||
// Check initial authentication state
|
||||
if (this.tokenService.isTokenValid()) {
|
||||
this.isAuthenticatedSubject.next(true);
|
||||
this.loadCurrentUser();
|
||||
this.scheduleTokenRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure auth service with base URL
|
||||
*/
|
||||
configure(baseUrl: string): void {
|
||||
this.authHttpService.setBaseUrl(baseUrl);
|
||||
}
|
||||
|
||||
// Authentication Methods
|
||||
|
||||
/**
|
||||
* Register new user
|
||||
*/
|
||||
register(request: RegisterRequest): Observable<RegisterResponse> {
|
||||
this.isLoadingSubject.next(true);
|
||||
|
||||
return this.authHttpService.register(request).pipe(
|
||||
tap(response => {
|
||||
this.tokenService.setTokens(response);
|
||||
this.currentUserSubject.next(response.user);
|
||||
this.isAuthenticatedSubject.next(true);
|
||||
this.scheduleTokenRefresh();
|
||||
}),
|
||||
catchError(error => {
|
||||
this.isLoadingSubject.next(false);
|
||||
return throwError(() => error);
|
||||
}),
|
||||
tap(() => this.isLoadingSubject.next(false))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login user
|
||||
*/
|
||||
login(request: LoginRequest): Observable<LoginResponse> {
|
||||
this.isLoadingSubject.next(true);
|
||||
|
||||
return this.authHttpService.login(request).pipe(
|
||||
tap(response => {
|
||||
this.tokenService.setTokens(response);
|
||||
this.currentUserSubject.next(response.user);
|
||||
this.isAuthenticatedSubject.next(true);
|
||||
this.scheduleTokenRefresh();
|
||||
}),
|
||||
catchError(error => {
|
||||
this.isLoadingSubject.next(false);
|
||||
return throwError(() => error);
|
||||
}),
|
||||
tap(() => this.isLoadingSubject.next(false))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*/
|
||||
logout(revokeRefreshToken = true): Observable<ApiResponse> {
|
||||
this.isLoadingSubject.next(true);
|
||||
|
||||
const logoutRequest: LogoutRequest = revokeRefreshToken
|
||||
? { refresh_token: this.tokenService.getRefreshToken() || undefined }
|
||||
: {};
|
||||
|
||||
return this.authHttpService.logout(logoutRequest).pipe(
|
||||
tap(() => {
|
||||
this.clearAuthState();
|
||||
}),
|
||||
catchError(error => {
|
||||
// Even if logout fails on server, clear local state
|
||||
this.clearAuthState();
|
||||
this.isLoadingSubject.next(false);
|
||||
return throwError(() => error);
|
||||
}),
|
||||
tap(() => this.isLoadingSubject.next(false))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently logout (clear local state only)
|
||||
*/
|
||||
logoutSilently(): void {
|
||||
this.clearAuthState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token
|
||||
*/
|
||||
refreshToken(): Observable<TokenPair> {
|
||||
const refreshToken = this.tokenService.getRefreshToken();
|
||||
|
||||
if (!refreshToken) {
|
||||
this.logoutSilently();
|
||||
return throwError(() => ({ error: 'No refresh token available' } as ApiError));
|
||||
}
|
||||
|
||||
return this.authHttpService.refreshToken({ refresh_token: refreshToken }).pipe(
|
||||
tap(tokenPair => {
|
||||
this.tokenService.setTokens(tokenPair);
|
||||
this.scheduleTokenRefresh();
|
||||
}),
|
||||
catchError(error => {
|
||||
// If refresh fails, logout user
|
||||
this.logoutSilently();
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user information
|
||||
*/
|
||||
getCurrentUser(): Observable<User> {
|
||||
if (this.currentUserSubject.value) {
|
||||
return this.currentUserSubject.asObservable().pipe(
|
||||
filter(user => !!user),
|
||||
take(1)
|
||||
);
|
||||
}
|
||||
|
||||
return this.loadCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load current user from server
|
||||
*/
|
||||
private loadCurrentUser(): Observable<User> {
|
||||
return this.authHttpService.getCurrentUser().pipe(
|
||||
tap(user => this.currentUserSubject.next(user)),
|
||||
catchError(error => {
|
||||
// If getting current user fails, might be invalid token
|
||||
if (error.status === 401) {
|
||||
this.logoutSilently();
|
||||
}
|
||||
return throwError(() => error);
|
||||
}),
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
// User Management
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*/
|
||||
updateProfile(updates: Partial<User>): Observable<User> {
|
||||
return this.authHttpService.updateUserProfile(updates).pipe(
|
||||
tap(user => this.currentUserSubject.next(user))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change password
|
||||
*/
|
||||
changePassword(request: ChangePasswordRequest): Observable<ApiResponse> {
|
||||
return this.authHttpService.changePassword(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request password reset
|
||||
*/
|
||||
forgotPassword(request: PasswordResetRequest): Observable<ApiResponse> {
|
||||
return this.authHttpService.forgotPassword(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset password with token
|
||||
*/
|
||||
resetPassword(request: PasswordResetConfirmRequest): Observable<ApiResponse> {
|
||||
return this.authHttpService.resetPassword(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify email
|
||||
*/
|
||||
verifyEmail(request: EmailVerificationRequest): Observable<ApiResponse> {
|
||||
return this.authHttpService.verifyEmail(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend email verification
|
||||
*/
|
||||
resendEmailVerification(request?: ResendVerificationRequest): Observable<ApiResponse> {
|
||||
return this.authHttpService.resendEmailVerification(request);
|
||||
}
|
||||
|
||||
// Two-Factor Authentication
|
||||
|
||||
/**
|
||||
* Setup 2FA
|
||||
*/
|
||||
setup2FA(): Observable<TwoFactorSetupResponse> {
|
||||
return this.authHttpService.setup2FA();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify 2FA setup
|
||||
*/
|
||||
verify2FASetup(request: TwoFactorVerifyRequest): Observable<ApiResponse> {
|
||||
return this.authHttpService.verify2FASetup(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable 2FA
|
||||
*/
|
||||
disable2FA(): Observable<ApiResponse> {
|
||||
return this.authHttpService.disable2FA();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 2FA status
|
||||
*/
|
||||
get2FAStatus(): Observable<TwoFactorStatusResponse> {
|
||||
return this.authHttpService.get2FAStatus();
|
||||
}
|
||||
|
||||
// Token Management
|
||||
|
||||
/**
|
||||
* Check if user is authenticated
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
return this.isAuthenticatedSubject.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token
|
||||
*/
|
||||
getAccessToken(): string | null {
|
||||
return this.tokenService.getAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has specific scope
|
||||
*/
|
||||
hasScope(scope: string): boolean {
|
||||
return this.tokenService.hasScope(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has any of the specified scopes
|
||||
*/
|
||||
hasAnyScope(scopes: string[]): boolean {
|
||||
return this.tokenService.hasAnyScope(scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has all of the specified scopes
|
||||
*/
|
||||
hasAllScopes(scopes: string[]): boolean {
|
||||
return this.tokenService.hasAllScopes(scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user scopes
|
||||
*/
|
||||
getUserScopes(): string[] {
|
||||
return this.tokenService.getUserScopes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user ID from token
|
||||
*/
|
||||
getUserId(): string | null {
|
||||
return this.tokenService.getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user email from token
|
||||
*/
|
||||
getUserEmail(): string | null {
|
||||
return this.tokenService.getUserEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user organization from token
|
||||
*/
|
||||
getUserOrganization(): string | null {
|
||||
return this.tokenService.getUserOrganization();
|
||||
}
|
||||
|
||||
// Private Methods
|
||||
|
||||
/**
|
||||
* Clear authentication state
|
||||
*/
|
||||
private clearAuthState(): void {
|
||||
this.tokenService.clearTokens();
|
||||
this.currentUserSubject.next(null);
|
||||
this.isAuthenticatedSubject.next(false);
|
||||
this.clearRefreshTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule automatic token refresh
|
||||
*/
|
||||
private scheduleTokenRefresh(): void {
|
||||
this.clearRefreshTimer();
|
||||
|
||||
const timeUntilExpiry = this.tokenService.getTimeUntilExpiry();
|
||||
if (timeUntilExpiry <= 0) return;
|
||||
|
||||
// Refresh token 2 minutes before expiry
|
||||
const refreshTime = Math.max(1000, timeUntilExpiry - (2 * 60 * 1000));
|
||||
|
||||
this.refreshTimer = timer(refreshTime).pipe(
|
||||
switchMap(() => {
|
||||
if (this.tokenService.isTokenExpiringSoon()) {
|
||||
return this.refreshToken();
|
||||
}
|
||||
return EMPTY;
|
||||
}),
|
||||
catchError(error => {
|
||||
console.warn('Auto token refresh failed:', error);
|
||||
return EMPTY;
|
||||
})
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear refresh timer
|
||||
*/
|
||||
private clearRefreshTimer(): void {
|
||||
if (this.refreshTimer) {
|
||||
this.refreshTimer.unsubscribe();
|
||||
this.refreshTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check service health
|
||||
*/
|
||||
healthCheck(): Observable<any> {
|
||||
return this.authHttpService.healthCheck();
|
||||
}
|
||||
}
|
||||
290
projects/auth-client/src/lib/services/oauth.service.ts
Normal file
290
projects/auth-client/src/lib/services/oauth.service.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { TokenService } from './token.service';
|
||||
import {
|
||||
OAuthProvider,
|
||||
OAuthProvidersResponse,
|
||||
OAuthLinkRequest,
|
||||
ApiResponse,
|
||||
ApiError
|
||||
} from '../models/auth.models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class OAuthService {
|
||||
private baseUrl = '';
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Set the base URL for the auth service
|
||||
*/
|
||||
setBaseUrl(url: string): void {
|
||||
this.baseUrl = url.replace(/\/$/, ''); // Remove trailing slash
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API URL with version prefix
|
||||
*/
|
||||
private getApiUrl(path: string): string {
|
||||
return `${this.baseUrl}/api/v1${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTTP headers with authorization if available
|
||||
*/
|
||||
private getHeaders(includeAuth = true): { [header: string]: string } {
|
||||
const headers: { [header: string]: string } = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
if (includeAuth) {
|
||||
const authHeader = this.tokenService.getAuthorizationHeader();
|
||||
if (authHeader) {
|
||||
headers['Authorization'] = authHeader;
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP errors and extract API error information
|
||||
*/
|
||||
private handleError(error: any): Observable<never> {
|
||||
let apiError: ApiError;
|
||||
|
||||
if (error.error && typeof error.error === 'object') {
|
||||
apiError = error.error;
|
||||
} else {
|
||||
apiError = {
|
||||
error: error.message || 'An unknown error occurred'
|
||||
};
|
||||
}
|
||||
|
||||
return throwError(() => apiError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available OAuth providers
|
||||
*/
|
||||
getProviders(): Observable<OAuthProvider[]> {
|
||||
return this.http.get<OAuthProvidersResponse>(
|
||||
this.getApiUrl('/oauth/providers'),
|
||||
{ headers: this.getHeaders(false) }
|
||||
).pipe(
|
||||
map(response => response.providers),
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth authorization URL for a provider
|
||||
*/
|
||||
getAuthorizationUrl(provider: string, redirectUri?: string, state?: string): Observable<string> {
|
||||
let url = this.getApiUrl(`/oauth/${provider}`);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (redirectUri) {
|
||||
params.append('redirect_uri', redirectUri);
|
||||
}
|
||||
if (state) {
|
||||
params.append('state', state);
|
||||
}
|
||||
|
||||
if (params.toString()) {
|
||||
url += `?${params.toString()}`;
|
||||
}
|
||||
|
||||
return this.http.get(url, {
|
||||
headers: this.getHeaders(false),
|
||||
responseType: 'text'
|
||||
}).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAuth callback (exchange code for tokens)
|
||||
*/
|
||||
handleCallback(provider: string, code: string, state?: string): Observable<any> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('code', code);
|
||||
if (state) {
|
||||
params.append('state', state);
|
||||
}
|
||||
|
||||
const url = `${this.getApiUrl(`/oauth/${provider}/callback`)}?${params.toString()}`;
|
||||
|
||||
return this.http.get(url, {
|
||||
headers: this.getHeaders(false)
|
||||
}).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link OAuth provider to existing account (requires authentication)
|
||||
*/
|
||||
linkProvider(provider: string, code: string, state?: string): Observable<ApiResponse> {
|
||||
const request: OAuthLinkRequest = {
|
||||
provider,
|
||||
code,
|
||||
state
|
||||
};
|
||||
|
||||
return this.http.post<ApiResponse>(
|
||||
this.getApiUrl(`/oauth/${provider}/link`),
|
||||
request,
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink OAuth provider from account (requires authentication)
|
||||
*/
|
||||
unlinkProvider(provider: string): Observable<ApiResponse> {
|
||||
return this.http.delete<ApiResponse>(
|
||||
this.getApiUrl(`/oauth/${provider}`),
|
||||
{ headers: this.getHeaders() }
|
||||
).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate OAuth flow by redirecting to provider
|
||||
*/
|
||||
initiateOAuthFlow(provider: string, redirectUri?: string, state?: string): void {
|
||||
this.getAuthorizationUrl(provider, redirectUri, state).subscribe({
|
||||
next: (authUrl) => {
|
||||
window.location.href = authUrl;
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Failed to initiate OAuth flow:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open OAuth flow in popup window
|
||||
*/
|
||||
initiateOAuthPopup(
|
||||
provider: string,
|
||||
redirectUri?: string,
|
||||
state?: string,
|
||||
popupFeatures = 'width=500,height=600,scrollbars=yes,resizable=yes'
|
||||
): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.getAuthorizationUrl(provider, redirectUri, state).subscribe({
|
||||
next: (authUrl) => {
|
||||
const popup = window.open(authUrl, 'oauth', popupFeatures);
|
||||
|
||||
if (!popup) {
|
||||
reject(new Error('Failed to open popup window'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll for popup closure or message
|
||||
const checkClosed = setInterval(() => {
|
||||
if (popup.closed) {
|
||||
clearInterval(checkClosed);
|
||||
reject(new Error('OAuth popup was closed by user'));
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Listen for messages from popup
|
||||
const messageListener = (event: MessageEvent) => {
|
||||
if (event.origin !== window.location.origin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.type === 'OAUTH_SUCCESS') {
|
||||
clearInterval(checkClosed);
|
||||
window.removeEventListener('message', messageListener);
|
||||
popup.close();
|
||||
resolve(event.data.payload);
|
||||
} else if (event.data.type === 'OAUTH_ERROR') {
|
||||
clearInterval(checkClosed);
|
||||
window.removeEventListener('message', messageListener);
|
||||
popup.close();
|
||||
reject(new Error(event.data.error));
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageListener);
|
||||
},
|
||||
error: (error) => {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate state parameter for OAuth security
|
||||
*/
|
||||
generateState(): string {
|
||||
const array = new Uint8Array(32);
|
||||
crypto.getRandomValues(array);
|
||||
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store state in session storage for verification
|
||||
*/
|
||||
storeState(state: string): void {
|
||||
sessionStorage.setItem('oauth_state', state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify state parameter
|
||||
*/
|
||||
verifyState(state: string): boolean {
|
||||
const storedState = sessionStorage.getItem('oauth_state');
|
||||
sessionStorage.removeItem('oauth_state');
|
||||
return storedState === state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract code and state from URL params (for redirect handling)
|
||||
*/
|
||||
extractCallbackParams(): { code?: string; state?: string; error?: string } {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return {
|
||||
code: urlParams.get('code') || undefined,
|
||||
state: urlParams.get('state') || undefined,
|
||||
error: urlParams.get('error') || undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete OAuth flow with extracted parameters
|
||||
*/
|
||||
completeOAuthFlow(provider: string): Observable<any> {
|
||||
const params = this.extractCallbackParams();
|
||||
|
||||
if (params.error) {
|
||||
return throwError(() => ({ error: params.error } as ApiError));
|
||||
}
|
||||
|
||||
if (!params.code) {
|
||||
return throwError(() => ({ error: 'No authorization code received' } as ApiError));
|
||||
}
|
||||
|
||||
if (params.state && !this.verifyState(params.state)) {
|
||||
return throwError(() => ({ error: 'Invalid state parameter' } as ApiError));
|
||||
}
|
||||
|
||||
return this.handleCallback(provider, params.code, params.state);
|
||||
}
|
||||
}
|
||||
226
projects/auth-client/src/lib/services/token.service.ts
Normal file
226
projects/auth-client/src/lib/services/token.service.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { TokenPair } from '../models/auth.models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TokenService {
|
||||
private readonly ACCESS_TOKEN_KEY = 'auth_access_token';
|
||||
private readonly REFRESH_TOKEN_KEY = 'auth_refresh_token';
|
||||
private readonly TOKEN_TYPE_KEY = 'auth_token_type';
|
||||
private readonly EXPIRES_AT_KEY = 'auth_expires_at';
|
||||
|
||||
private tokenSubject = new BehaviorSubject<string | null>(this.getAccessToken());
|
||||
public token$ = this.tokenSubject.asObservable();
|
||||
|
||||
constructor() {
|
||||
// Check token validity on service initialization
|
||||
this.checkTokenValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store token pair in localStorage
|
||||
*/
|
||||
setTokens(tokenPair: TokenPair): void {
|
||||
const expiresAt = Date.now() + (tokenPair.expires_in * 1000);
|
||||
|
||||
localStorage.setItem(this.ACCESS_TOKEN_KEY, tokenPair.access_token);
|
||||
localStorage.setItem(this.REFRESH_TOKEN_KEY, tokenPair.refresh_token);
|
||||
localStorage.setItem(this.TOKEN_TYPE_KEY, tokenPair.token_type);
|
||||
localStorage.setItem(this.EXPIRES_AT_KEY, expiresAt.toString());
|
||||
|
||||
this.tokenSubject.next(tokenPair.access_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token from localStorage
|
||||
*/
|
||||
getAccessToken(): string | null {
|
||||
return localStorage.getItem(this.ACCESS_TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get refresh token from localStorage
|
||||
*/
|
||||
getRefreshToken(): string | null {
|
||||
return localStorage.getItem(this.REFRESH_TOKEN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token type from localStorage
|
||||
*/
|
||||
getTokenType(): string {
|
||||
return localStorage.getItem(this.TOKEN_TYPE_KEY) || 'Bearer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token expiration timestamp
|
||||
*/
|
||||
getExpiresAt(): number | null {
|
||||
const expiresAt = localStorage.getItem(this.EXPIRES_AT_KEY);
|
||||
return expiresAt ? parseInt(expiresAt, 10) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token exists
|
||||
*/
|
||||
hasToken(): boolean {
|
||||
return this.getAccessToken() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is expired
|
||||
*/
|
||||
isTokenExpired(): boolean {
|
||||
const expiresAt = this.getExpiresAt();
|
||||
if (!expiresAt) return true;
|
||||
|
||||
// Add 30 second buffer to account for clock skew
|
||||
return Date.now() >= (expiresAt - 30000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is valid (exists and not expired)
|
||||
*/
|
||||
isTokenValid(): boolean {
|
||||
return this.hasToken() && !this.isTokenExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authorization header value
|
||||
*/
|
||||
getAuthorizationHeader(): string | null {
|
||||
const token = this.getAccessToken();
|
||||
const tokenType = this.getTokenType();
|
||||
|
||||
return token ? `${tokenType} ${token}` : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all tokens from storage
|
||||
*/
|
||||
clearTokens(): void {
|
||||
localStorage.removeItem(this.ACCESS_TOKEN_KEY);
|
||||
localStorage.removeItem(this.REFRESH_TOKEN_KEY);
|
||||
localStorage.removeItem(this.TOKEN_TYPE_KEY);
|
||||
localStorage.removeItem(this.EXPIRES_AT_KEY);
|
||||
|
||||
this.tokenSubject.next(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time until token expires (in milliseconds)
|
||||
*/
|
||||
getTimeUntilExpiry(): number {
|
||||
const expiresAt = this.getExpiresAt();
|
||||
if (!expiresAt) return 0;
|
||||
|
||||
const timeLeft = expiresAt - Date.now();
|
||||
return Math.max(0, timeLeft);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token will expire soon (within 5 minutes)
|
||||
*/
|
||||
isTokenExpiringSoon(): boolean {
|
||||
const timeLeft = this.getTimeUntilExpiry();
|
||||
return timeLeft > 0 && timeLeft < 5 * 60 * 1000; // 5 minutes
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JWT token payload (without verification)
|
||||
*/
|
||||
decodeToken(token?: string): any {
|
||||
const tokenToDecoded = token || this.getAccessToken();
|
||||
if (!tokenToDecoded) return null;
|
||||
|
||||
try {
|
||||
const payload = tokenToDecoded.split('.')[1];
|
||||
const decoded = atob(payload);
|
||||
return JSON.parse(decoded);
|
||||
} catch (error) {
|
||||
console.warn('Failed to decode token:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user ID from token
|
||||
*/
|
||||
getUserId(): string | null {
|
||||
const payload = this.decodeToken();
|
||||
return payload?.sub || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user email from token
|
||||
*/
|
||||
getUserEmail(): string | null {
|
||||
const payload = this.decodeToken();
|
||||
return payload?.email || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user scopes from token
|
||||
*/
|
||||
getUserScopes(): string[] {
|
||||
const payload = this.decodeToken();
|
||||
return payload?.scopes || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user organization from token
|
||||
*/
|
||||
getUserOrganization(): string | null {
|
||||
const payload = this.decodeToken();
|
||||
return payload?.organization || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has specific scope
|
||||
*/
|
||||
hasScope(scope: string): boolean {
|
||||
const scopes = this.getUserScopes();
|
||||
return scopes.includes(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has any of the specified scopes
|
||||
*/
|
||||
hasAnyScope(scopes: string[]): boolean {
|
||||
const userScopes = this.getUserScopes();
|
||||
return scopes.some(scope => userScopes.includes(scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has all of the specified scopes
|
||||
*/
|
||||
hasAllScopes(scopes: string[]): boolean {
|
||||
const userScopes = this.getUserScopes();
|
||||
return scopes.every(scope => userScopes.includes(scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check token validity and clear if invalid
|
||||
*/
|
||||
private checkTokenValidity(): void {
|
||||
if (this.hasToken() && this.isTokenExpired()) {
|
||||
this.clearTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to storage events for cross-tab synchronization
|
||||
*/
|
||||
initStorageListener(): void {
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key === this.ACCESS_TOKEN_KEY) {
|
||||
this.tokenSubject.next(event.newValue);
|
||||
} else if (event.key === null) {
|
||||
// Storage was cleared
|
||||
this.tokenSubject.next(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/auth-client/src/public-api.ts
Normal file
22
projects/auth-client/src/public-api.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Public API Surface of auth-client
|
||||
*/
|
||||
|
||||
// Models
|
||||
export * from './lib/models/auth.models';
|
||||
|
||||
// Services
|
||||
export * from './lib/services/auth.service';
|
||||
export * from './lib/services/auth-http.service';
|
||||
export * from './lib/services/token.service';
|
||||
export * from './lib/services/oauth.service';
|
||||
|
||||
// Guards
|
||||
export * from './lib/guards/auth.guard';
|
||||
export * from './lib/guards/guest.guard';
|
||||
|
||||
// Interceptors
|
||||
export * from './lib/interceptors/auth.interceptor';
|
||||
|
||||
// Legacy export for compatibility
|
||||
export * from './lib/auth-client';
|
||||
18
projects/auth-client/tsconfig.lib.json
Normal file
18
projects/auth-client/tsconfig.lib.json
Normal file
@@ -0,0 +1,18 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
11
projects/auth-client/tsconfig.lib.prod.json
Normal file
11
projects/auth-client/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,11 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"compilationMode": "partial"
|
||||
}
|
||||
}
|
||||
14
projects/auth-client/tsconfig.spec.json
Normal file
14
projects/auth-client/tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { UiAccessibilityModule } from 'ui-accessibility';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { UiAccessibilityModule } from '../../../ui-accessibility/src/lib/ui-accessibility.module';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.accessibility-demo {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { UiAccessibilityModule } from '../../../../../ui-accessibility/src/lib/ui-accessibility.module';
|
||||
import { ButtonComponent } from '../../../../../ui-essentials/src/lib/components/buttons/button.component';
|
||||
import { LiveAnnouncerService } from '../../../../../ui-accessibility/src/lib/services/live-announcer/live-announcer.service';
|
||||
import { FocusMonitorService } from '../../../../../ui-accessibility/src/lib/services/focus-monitor/focus-monitor.service';
|
||||
import { KeyboardManagerService } from '../../../../../ui-accessibility/src/lib/services/keyboard-manager/keyboard-manager.service';
|
||||
import { HighContrastService } from '../../../../../ui-accessibility/src/lib/services/high-contrast/high-contrast.service';
|
||||
import { A11yConfigService } from '../../../../../ui-accessibility/src/lib/services/a11y-config/a11y-config.service';
|
||||
|
||||
// Import UI Accessibility components and services
|
||||
import {
|
||||
UiAccessibilityModule,
|
||||
LiveAnnouncerService,
|
||||
FocusMonitorService,
|
||||
KeyboardManagerService,
|
||||
HighContrastService,
|
||||
A11yConfigService
|
||||
} from 'ui-accessibility';
|
||||
|
||||
// Import UI Essentials components
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accessibility-demo',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
max-width: 1200px;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiAnimationsService, AnimateDirective } from 'ui-animations';
|
||||
import { UiAnimationsService } from '../../../../../ui-animations/src/lib/ui-animations.service';
|
||||
import { AnimateDirective } from '../../../../../ui-animations/src/lib/animate.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-animations-demo',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: 32px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as tokens;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as tokens;
|
||||
|
||||
.demo-container {
|
||||
padding: tokens.$semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
BackgroundDirective,
|
||||
SolidBackgroundComponent,
|
||||
GradientBackgroundComponent,
|
||||
PatternBackgroundComponent,
|
||||
ImageBackgroundComponent,
|
||||
BackgroundService,
|
||||
SolidBackgroundConfig,
|
||||
LinearGradientConfig,
|
||||
PatternConfig,
|
||||
ImageBackgroundConfig
|
||||
} from 'ui-backgrounds';
|
||||
import { BackgroundDirective } from '../../../../../ui-backgrounds/src/lib/directives/background.directive';
|
||||
import { SolidBackgroundComponent } from '../../../../../ui-backgrounds/src/lib/components/backgrounds/solid-background.component';
|
||||
import { GradientBackgroundComponent } from '../../../../../ui-backgrounds/src/lib/components/backgrounds/gradient-background.component';
|
||||
import { PatternBackgroundComponent } from '../../../../../ui-backgrounds/src/lib/components/backgrounds/pattern-background.component';
|
||||
import { ImageBackgroundComponent } from '../../../../../ui-backgrounds/src/lib/components/backgrounds/image-background.component';
|
||||
import { LinearGradientConfig, PatternConfig, SolidBackgroundConfig } from '../../../../../ui-backgrounds/src/lib/types/background.types';
|
||||
import { BackgroundService } from '../../../../../ui-backgrounds/src/lib/services/background.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-backgrounds-demo',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding-bottom: 120px; // Extra space to accommodate bottom navigation examples
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../../../../ui-design-system/src/styles/semantic";
|
||||
@import 'ui-design-system/src/styles/semantic';
|
||||
|
||||
.carousel-demo {
|
||||
padding: $semantic-spacing-layout-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-sm;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
CodeSnippetComponent,
|
||||
InlineCodeComponent,
|
||||
CodeBlockComponent,
|
||||
CodeThemeService,
|
||||
CodeTheme
|
||||
} from 'ui-code-display';
|
||||
import { CodeSnippetComponent } from '../../../../../ui-code-display/src/lib/components/code-snippet/code-snippet.component';
|
||||
import { InlineCodeComponent } from '../../../../../ui-code-display/src/lib/components/inline-code/inline-code.component';
|
||||
import { CodeBlockComponent } from '../../../../../ui-code-display/src/lib/components/code-block/code-block.component';
|
||||
import { CodeTheme, CodeThemeService } from '../../../../../ui-code-display/src/lib/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-code-display-demo',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-wrapper {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
@use 'ui-design-system/src/styles/semantic' as semantic;
|
||||
|
||||
.conversion-demo {
|
||||
min-height: 100vh;
|
||||
background: semantic.$semantic-color-surface-primary;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
padding: semantic.$semantic-spacing-layout-section-lg semantic.$semantic-spacing-container-card-padding;
|
||||
|
||||
&:first-child {
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, semantic.$semantic-color-primary-container 0%, semantic.$semantic-color-tertiary-container 100%);
|
||||
border-bottom: 1px solid semantic.$semantic-color-border-subtle;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-bottom: 1px solid semantic.$semantic-color-border-subtle;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 600;
|
||||
color: semantic.$semantic-color-text-primary;
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
}
|
||||
|
||||
.demo-description {
|
||||
font-size: 1.125rem;
|
||||
color: semantic.$semantic-color-text-secondary;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.demo-section-title {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 600;
|
||||
color: semantic.$semantic-color-text-primary;
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-section-description {
|
||||
font-size: 1.125rem;
|
||||
color: semantic.$semantic-color-text-secondary;
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
margin: 0 auto semantic.$semantic-spacing-component-xl auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.demo-example {
|
||||
margin-bottom: semantic.$semantic-spacing-layout-section-sm;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-example-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: semantic.$semantic-color-text-primary;
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: semantic.$semantic-spacing-component-xl;
|
||||
margin-bottom: semantic.$semantic-spacing-layout-section-sm;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: semantic.$semantic-spacing-content-paragraph;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-demo {
|
||||
background: semantic.$semantic-color-primary;
|
||||
padding: semantic.$semantic-spacing-component-xl;
|
||||
border-radius: 0.5rem;
|
||||
color: semantic.$semantic-color-on-primary;
|
||||
}
|
||||
|
||||
// Component spacing adjustments
|
||||
:host ::ng-deep {
|
||||
ui-cta-section {
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
}
|
||||
|
||||
ui-pricing-table {
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
}
|
||||
|
||||
ui-newsletter-signup {
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
}
|
||||
|
||||
ui-contact-form {
|
||||
margin-bottom: semantic.$semantic-spacing-content-paragraph;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive adjustments
|
||||
@media (max-width: 768px) {
|
||||
.demo-section {
|
||||
padding: semantic.$semantic-spacing-layout-section-sm semantic.$semantic-spacing-container-card-padding;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
.demo-section-title {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.demo-section {
|
||||
padding: semantic.$semantic-spacing-layout-section-xs semantic.$semantic-spacing-container-card-padding;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.demo-section-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.demo-example-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
// Temporarily commented out until ui-landing-pages is built
|
||||
// import {
|
||||
// CTASectionComponent,
|
||||
// PricingTableComponent,
|
||||
// NewsletterSignupComponent,
|
||||
// ContactFormComponent
|
||||
// } from 'ui-landing-pages';
|
||||
// import {
|
||||
// CTASectionConfig,
|
||||
// PricingTableConfig,
|
||||
// NewsletterSignupConfig,
|
||||
// ContactFormConfig
|
||||
// } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-conversion-demo',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
// Temporarily commented out until ui-landing-pages is built
|
||||
// CTASectionComponent,
|
||||
// PricingTableComponent,
|
||||
// NewsletterSignupComponent,
|
||||
// ContactFormComponent
|
||||
],
|
||||
template: `
|
||||
<div class="conversion-demo">
|
||||
<div class="demo-section">
|
||||
<h1 class="demo-title">Conversion Components Demo</h1>
|
||||
<p class="demo-description">
|
||||
Phase 3 components focused on user actions and conversions, including CTAs, pricing tables, newsletter signups, and contact forms.
|
||||
</p>
|
||||
<div class="under-construction">
|
||||
<p><strong>🚧 Under Construction:</strong> This demo is temporarily disabled while the ui-landing-pages library is being built.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temporarily commented out until ui-landing-pages is built -->
|
||||
`,
|
||||
styleUrls: ['./conversion-demo.component.scss']
|
||||
})
|
||||
export class ConversionDemoComponent {
|
||||
|
||||
// CTA Section Configurations
|
||||
// Temporarily commented out until ui-landing-pages is built
|
||||
/*
|
||||
ctaConfigGradient: CTASectionConfig = {
|
||||
title: "Transform Your Business Today",
|
||||
description: "Join thousands of companies already using our platform to accelerate growth and increase efficiency.",
|
||||
backgroundType: 'gradient',
|
||||
ctaPrimary: {
|
||||
text: 'Start Free Trial',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Primary CTA clicked')
|
||||
},
|
||||
ctaSecondary: {
|
||||
text: 'Watch Demo',
|
||||
variant: 'outlined',
|
||||
action: () => console.log('Secondary CTA clicked')
|
||||
},
|
||||
urgency: {
|
||||
type: 'countdown',
|
||||
text: 'Limited Time Offer Ends In:',
|
||||
endDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000) // 5 days from now
|
||||
}
|
||||
};
|
||||
|
||||
ctaConfigPattern: CTASectionConfig = {
|
||||
title: "Don't Miss Out on This Exclusive Deal",
|
||||
description: "Get 50% off your first year and unlock premium features.",
|
||||
backgroundType: 'pattern',
|
||||
ctaPrimary: {
|
||||
text: 'Claim Offer Now',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Pattern CTA clicked')
|
||||
},
|
||||
urgency: {
|
||||
type: 'limited-offer',
|
||||
text: 'Flash Sale',
|
||||
remaining: 12
|
||||
}
|
||||
};
|
||||
|
||||
ctaConfigSolid: CTASectionConfig = {
|
||||
title: "Join Over 10,000 Happy Customers",
|
||||
description: "See why businesses trust us with their most important processes.",
|
||||
backgroundType: 'solid',
|
||||
ctaPrimary: {
|
||||
text: 'Get Started',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Solid CTA clicked')
|
||||
},
|
||||
urgency: {
|
||||
type: 'social-proof',
|
||||
text: '🔥 142 people signed up in the last 24 hours'
|
||||
}
|
||||
};
|
||||
|
||||
// Pricing Table Configuration
|
||||
pricingConfig: PricingTableConfig = {
|
||||
billingToggle: {
|
||||
monthlyLabel: 'Monthly',
|
||||
yearlyLabel: 'Yearly',
|
||||
discountText: 'Save 20%'
|
||||
},
|
||||
featuresComparison: true,
|
||||
highlightedPlan: 'pro',
|
||||
plans: [
|
||||
{
|
||||
id: 'starter',
|
||||
name: 'Starter',
|
||||
description: 'Perfect for small teams getting started',
|
||||
price: {
|
||||
monthly: 29,
|
||||
yearly: 24,
|
||||
currency: '$',
|
||||
suffix: '/month'
|
||||
},
|
||||
features: [
|
||||
{ name: 'Up to 5 team members', included: true },
|
||||
{ name: '10GB storage', included: true },
|
||||
{ name: 'Basic reporting', included: true },
|
||||
{ name: 'Email support', included: true },
|
||||
{ name: 'Advanced analytics', included: false },
|
||||
{ name: 'API access', included: false },
|
||||
{ name: 'Priority support', included: false }
|
||||
],
|
||||
cta: {
|
||||
text: 'Start Free Trial',
|
||||
action: () => console.log('Starter plan selected')
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'pro',
|
||||
name: 'Professional',
|
||||
description: 'Best for growing businesses',
|
||||
badge: 'Most Popular',
|
||||
popular: true,
|
||||
price: {
|
||||
monthly: 79,
|
||||
yearly: 63,
|
||||
currency: '$',
|
||||
suffix: '/month'
|
||||
},
|
||||
features: [
|
||||
{ name: 'Up to 25 team members', included: true },
|
||||
{ name: '100GB storage', included: true },
|
||||
{ name: 'Advanced reporting', included: true, highlight: true },
|
||||
{ name: 'Email & chat support', included: true },
|
||||
{ name: 'Advanced analytics', included: true, highlight: true },
|
||||
{ name: 'API access', included: true },
|
||||
{ name: 'Priority support', included: false }
|
||||
],
|
||||
cta: {
|
||||
text: 'Start Free Trial',
|
||||
action: () => console.log('Pro plan selected')
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'enterprise',
|
||||
name: 'Enterprise',
|
||||
description: 'For large organizations with custom needs',
|
||||
price: {
|
||||
monthly: 199,
|
||||
yearly: 159,
|
||||
currency: '$',
|
||||
suffix: '/month'
|
||||
},
|
||||
features: [
|
||||
{ name: 'Unlimited team members', included: true },
|
||||
{ name: 'Unlimited storage', included: true },
|
||||
{ name: 'Custom reporting', included: true },
|
||||
{ name: '24/7 phone support', included: true },
|
||||
{ name: 'Advanced analytics', included: true },
|
||||
{ name: 'Full API access', included: true },
|
||||
{ name: 'Priority support', included: true, highlight: true }
|
||||
],
|
||||
cta: {
|
||||
text: 'Contact Sales',
|
||||
variant: 'outlined',
|
||||
action: () => console.log('Enterprise plan selected')
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Newsletter Signup Configurations
|
||||
newsletterConfigInline: NewsletterSignupConfig = {
|
||||
title: "Stay Updated with Our Newsletter",
|
||||
description: "Get the latest insights, tips, and updates delivered to your inbox.",
|
||||
placeholder: "Enter your email address",
|
||||
ctaText: "Subscribe",
|
||||
variant: 'inline',
|
||||
successMessage: "Thanks for subscribing! Check your email for confirmation."
|
||||
};
|
||||
|
||||
newsletterConfigModal: NewsletterSignupConfig = {
|
||||
title: "Join Our Community",
|
||||
description: "Be the first to know about new features and exclusive content.",
|
||||
placeholder: "Your email address",
|
||||
ctaText: "Join Now",
|
||||
variant: 'modal',
|
||||
successMessage: "Welcome aboard! You're now part of our community."
|
||||
};
|
||||
|
||||
newsletterConfigFooter: NewsletterSignupConfig = {
|
||||
title: "Newsletter Signup",
|
||||
description: "Weekly insights and updates from our team.",
|
||||
placeholder: "Email address",
|
||||
ctaText: "Sign Up",
|
||||
variant: 'footer',
|
||||
showPrivacyCheckbox: true,
|
||||
privacyText: "We respect your privacy and will never spam you.",
|
||||
successMessage: "Successfully subscribed! Welcome to our newsletter."
|
||||
};
|
||||
|
||||
// Contact Form Configurations
|
||||
contactConfigTwoColumn: ContactFormConfig = {
|
||||
title: "Get in Touch",
|
||||
description: "We'd love to hear from you. Send us a message and we'll respond as soon as possible.",
|
||||
layout: 'two-column',
|
||||
submitText: 'Send Message',
|
||||
successMessage: "Thank you for your message! We'll get back to you within 24 hours.",
|
||||
validation: {},
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'firstName',
|
||||
label: 'First Name',
|
||||
placeholder: 'John',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'lastName',
|
||||
label: 'Last Name',
|
||||
placeholder: 'Doe',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
placeholder: 'john@example.com',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'tel',
|
||||
name: 'phone',
|
||||
label: 'Phone Number',
|
||||
placeholder: '+1 (555) 123-4567',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'subject',
|
||||
label: 'Subject',
|
||||
required: true,
|
||||
options: [
|
||||
{ value: 'general', label: 'General Inquiry' },
|
||||
{ value: 'support', label: 'Technical Support' },
|
||||
{ value: 'sales', label: 'Sales Question' },
|
||||
{ value: 'partnership', label: 'Partnership' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'message',
|
||||
label: 'Message',
|
||||
placeholder: 'Tell us more about your inquiry...',
|
||||
required: true,
|
||||
rows: 5
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
contactConfigSingleColumn: ContactFormConfig = {
|
||||
title: "Quick Contact",
|
||||
layout: 'single-column',
|
||||
submitText: 'Submit',
|
||||
successMessage: "Message sent successfully!",
|
||||
validation: {},
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
label: 'Full Name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
label: 'Email Address',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'message',
|
||||
label: 'Your Message',
|
||||
required: true,
|
||||
rows: 4
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'newsletter',
|
||||
label: 'Subscribe to our newsletter for updates',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
contactConfigInline: ContactFormConfig = {
|
||||
layout: 'inline',
|
||||
submitText: 'Contact Us',
|
||||
successMessage: "We'll be in touch soon!",
|
||||
validation: {},
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
placeholder: 'Your name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
placeholder: 'your@email.com',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'company',
|
||||
label: 'Company',
|
||||
placeholder: 'Your company',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
onNewsletterSignup(data: any) {
|
||||
console.log('Newsletter signup:', data);
|
||||
}
|
||||
|
||||
onContactFormSubmit(data: any) {
|
||||
console.log('Contact form submission:', data);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,20 +1,12 @@
|
||||
import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FilterConfig, SortConfig, GroupByResult } from '../../../../../ui-data-utils/src/lib/types';
|
||||
import { sortBy, sortByMultiple } from '../../../../../ui-data-utils/src/lib/sorting';
|
||||
import { filterBy, filterByMultiple, searchFilter } from '../../../../../ui-data-utils/src/lib/filtering';
|
||||
import { getPaginationRange, paginate } from '../../../../../ui-data-utils/src/lib/pagination';
|
||||
import { aggregate, groupBy, pivot, pluck, unique } from '../../../../../ui-data-utils/src/lib/transformation';
|
||||
|
||||
// Import all utilities from ui-data-utils
|
||||
import {
|
||||
// Types
|
||||
SortConfig, FilterConfig, PaginationConfig,
|
||||
// Sorting
|
||||
sortBy, sortByMultiple, createComparator,
|
||||
// Filtering
|
||||
filterBy, filterByMultiple, searchFilter,
|
||||
// Pagination
|
||||
paginate, calculatePages, getPaginationRange,
|
||||
// Transformation
|
||||
groupBy, aggregate, pluck, flatten, pivot, unique, frequency
|
||||
} from 'ui-data-utils';
|
||||
|
||||
interface SampleData {
|
||||
id: number;
|
||||
@@ -600,11 +592,11 @@ export class DataUtilsDemoComponent {
|
||||
}
|
||||
|
||||
getSortedDataPreview(): string {
|
||||
return JSON.stringify(this.sortedData().slice(0, 3).map(item => ({ name: item.name, [this.sortField()]: item[this.sortField()] })), null, 2);
|
||||
return JSON.stringify(this.sortedData().slice(0, 3).map((item: SampleData) => ({ name: item.name, [this.sortField()]: item[this.sortField()] })), null, 2);
|
||||
}
|
||||
|
||||
getMultiSortedDataPreview(): string {
|
||||
return JSON.stringify(this.multiSortedData().slice(0, 4).map(item => ({
|
||||
return JSON.stringify(this.multiSortedData().slice(0, 4).map((item: SampleData) => ({
|
||||
name: item.name,
|
||||
department: item.department,
|
||||
salary: item.salary
|
||||
@@ -612,14 +604,14 @@ export class DataUtilsDemoComponent {
|
||||
}
|
||||
|
||||
getSearchFilteredDataPreview(): string {
|
||||
return JSON.stringify(this.searchFilteredData().slice(0, 3).map(item => ({
|
||||
return JSON.stringify(this.searchFilteredData().slice(0, 3).map((item: SampleData) => ({
|
||||
name: item.name,
|
||||
department: item.department
|
||||
})), null, 2);
|
||||
}
|
||||
|
||||
getPropertyFilteredDataPreview(): string {
|
||||
return JSON.stringify(this.propertyFilteredData().slice(0, 3).map(item => ({
|
||||
return JSON.stringify(this.propertyFilteredData().slice(0, 3).map((item: SampleData) => ({
|
||||
name: item.name,
|
||||
department: item.department,
|
||||
salary: item.salary
|
||||
@@ -627,7 +619,7 @@ export class DataUtilsDemoComponent {
|
||||
}
|
||||
|
||||
getPaginatedDataPreview(): string {
|
||||
return JSON.stringify(this.paginationResult().data.map(item => ({
|
||||
return JSON.stringify(this.paginationResult().data.map((item: SampleData) => ({
|
||||
name: item.name,
|
||||
department: item.department
|
||||
})), null, 2);
|
||||
@@ -635,16 +627,16 @@ export class DataUtilsDemoComponent {
|
||||
|
||||
getGroupedDataPreview(): string {
|
||||
const grouped = groupBy(this.sampleData, 'department');
|
||||
return JSON.stringify(grouped.map(group => ({
|
||||
return JSON.stringify(grouped.map((group: GroupByResult<SampleData>) => ({
|
||||
department: group.key,
|
||||
count: group.count,
|
||||
employees: group.items.map(emp => emp.name)
|
||||
employees: group.items.map((emp: SampleData) => emp.name)
|
||||
})), null, 2);
|
||||
}
|
||||
|
||||
getAggregationPreview(): string {
|
||||
const grouped = groupBy(this.sampleData, 'department');
|
||||
const result = grouped.map(group => ({
|
||||
const result = grouped.map((group: GroupByResult<SampleData>) => ({
|
||||
department: group.key,
|
||||
...aggregate(group.items, 'salary', ['sum', 'avg', 'min', 'max', 'count'])
|
||||
}));
|
||||
@@ -668,7 +660,7 @@ export class DataUtilsDemoComponent {
|
||||
}
|
||||
|
||||
getCombinedResultPreview(): string {
|
||||
return JSON.stringify(this.combinedPaginated().data.map(item => ({
|
||||
return JSON.stringify(this.combinedPaginated().data.map((item: SampleData) => ({
|
||||
name: item.name,
|
||||
department: item.department,
|
||||
salary: item.salary,
|
||||
|
||||
@@ -83,15 +83,26 @@ import { InfiniteScrollContainerDemoComponent } from './infinite-scroll-containe
|
||||
import { StickyLayoutDemoComponent } from './sticky-layout-demo/sticky-layout-demo.component';
|
||||
import { SplitViewDemoComponent } from './split-view-demo/split-view-demo.component';
|
||||
import { GalleryGridDemoComponent } from './gallery-grid-demo/gallery-grid-demo.component';
|
||||
import { AnimationsDemoComponent } from './animations-demo/animations-demo.component';
|
||||
import { AccessibilityDemoComponent } from './accessibility-demo/accessibility-demo.component';
|
||||
import { SelectDemoComponent } from './select-demo/select-demo.component';
|
||||
import { TextareaDemoComponent } from './textarea-demo/textarea-demo.component';
|
||||
import { DataUtilsDemoComponent } from './data-utils-demo/data-utils-demo.component';
|
||||
import { HclStudioDemoComponent } from './hcl-studio-demo/hcl-studio-demo.component';
|
||||
import { FontManagerDemoComponent } from './font-manager-demo/font-manager-demo.component';
|
||||
import { CodeDisplayDemoComponent } from './code-display-demo/code-display-demo.component';
|
||||
import { BackgroundsDemoComponent } from './backgrounds-demo/backgrounds-demo.component';
|
||||
import { ConversionDemoComponent } from './conversion-demo/conversion-demo.component';
|
||||
import { LandingHeaderDemoComponent } from './landing-header-demo/landing-header-demo.component';
|
||||
import { LandingFooterDemoComponent } from './landing-footer-demo/landing-footer-demo.component';
|
||||
import { LandingFAQDemoComponent } from './landing-faq-demo/landing-faq-demo.component';
|
||||
import { LandingTemplatesDemoComponent } from "./landing-templates-demo/landing-templates-demo.component";
|
||||
import { LandingTimelineDemoComponent } from "./landing-timeline-demo/landing-timeline-demo.component";
|
||||
import { LandingTeamDemoComponent } from "./landing-team-demo/landing-team-demo.component";
|
||||
import { LandingStatisticsDemoComponent } from "./landing-statistics-demo/landing-statistics-demo.component";
|
||||
import { LandingFeatureGridDemoComponent } from "./landing-feature-grid-demo/landing-feature-grid-demo.component";
|
||||
import { LandingTestimonialsDemoComponent } from "./landing-testimonials-demo/landing-testimonials-demo.component";
|
||||
import { LandingLogoCloudDemoComponent } from "./landing-logo-cloud-demo/landing-logo-cloud-demo.component";
|
||||
import { AnimationsDemoComponent } from "./animations-demo/animations-demo.component";
|
||||
import { CodeDisplayDemoComponent } from "./code-display-demo/code-display-demo.component";
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -196,7 +207,7 @@ import { BackgroundsDemoComponent } from './backgrounds-demo/backgrounds-demo.co
|
||||
}
|
||||
|
||||
@case ("video-player") {
|
||||
<ui-video-player-demo></ui-video-player-demo>
|
||||
<app-video-player-demo></app-video-player-demo>
|
||||
}
|
||||
|
||||
@case ("modal") {
|
||||
@@ -459,6 +470,51 @@ import { BackgroundsDemoComponent } from './backgrounds-demo/backgrounds-demo.co
|
||||
<app-backgrounds-demo></app-backgrounds-demo>
|
||||
}
|
||||
|
||||
@case ("conversion") {
|
||||
<app-conversion-demo></app-conversion-demo>
|
||||
}
|
||||
|
||||
@case ("landing-faq") {
|
||||
<app-landing-faq-demo></app-landing-faq-demo>
|
||||
}
|
||||
|
||||
@case ("landing-feature-grid") {
|
||||
<app-landing-feature-grid-demo></app-landing-feature-grid-demo>
|
||||
}
|
||||
|
||||
@case ("landing-testimonials") {
|
||||
<app-landing-testimonials-demo></app-landing-testimonials-demo>
|
||||
}
|
||||
|
||||
@case ("landing-logo-cloud") {
|
||||
<app-landing-logo-cloud-demo></app-landing-logo-cloud-demo>
|
||||
}
|
||||
|
||||
@case ("landing-statistics") {
|
||||
<app-landing-statistics-demo></app-landing-statistics-demo>
|
||||
}
|
||||
|
||||
@case ("landing-header") {
|
||||
<app-landing-header-demo></app-landing-header-demo>
|
||||
}
|
||||
|
||||
@case ("landing-footer") {
|
||||
<app-landing-footer-demo></app-landing-footer-demo>
|
||||
}
|
||||
|
||||
@case ("landing-team") {
|
||||
<app-landing-team-demo></app-landing-team-demo>
|
||||
}
|
||||
|
||||
@case ("landing-timeline") {
|
||||
<app-landing-timeline-demo></app-landing-timeline-demo>
|
||||
}
|
||||
|
||||
@case ("landing-templates") {
|
||||
<app-landing-templates-demo></app-landing-templates-demo>
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
`,
|
||||
@@ -468,13 +524,26 @@ import { BackgroundsDemoComponent } from './backgrounds-demo/backgrounds-demo.co
|
||||
RadioDemoComponent, CheckboxDemoComponent,
|
||||
SearchDemoComponent, SwitchDemoComponent, ProgressDemoComponent,
|
||||
AppbarDemoComponent, BottomNavigationDemoComponent, FontAwesomeDemoComponent, ImageContainerDemoComponent,
|
||||
CarouselDemoComponent, VideoPlayerDemoComponent, ListDemoComponent,
|
||||
CarouselDemoComponent, VideoPlayerDemoComponent, ListDemoComponent,
|
||||
ModalDemoComponent, DrawerDemoComponent, DatePickerDemoComponent, TimePickerDemoComponent,
|
||||
GridSystemDemoComponent, SpacerDemoComponent, ContainerDemoComponent, PaginationDemoComponent,
|
||||
SkeletonLoaderDemoComponent, EmptyStateDemoComponent, FileUploadDemoComponent, FormFieldDemoComponent,
|
||||
AutocompleteDemoComponent, BackdropDemoComponent, OverlayContainerDemoComponent, LoadingSpinnerDemoComponent,
|
||||
ProgressCircleDemoComponent, RangeSliderDemoComponent, ColorPickerDemoComponent, DividerDemoComponent, TooltipDemoComponent, AccordionDemoComponent,
|
||||
PopoverDemoComponent, AlertDemoComponent, SnackbarDemoComponent, ToastDemoComponent, TreeViewDemoComponent, TimelineDemoComponent, StepperDemoComponent, FabMenuDemoComponent, EnhancedTableDemoComponent, SplitButtonDemoComponent, CommandPaletteDemoComponent, FloatingToolbarDemoComponent, TransferListDemoComponent, TagInputDemoComponent, StackDemoComponent, BoxDemoComponent, CenterDemoComponent, AspectRatioDemoComponent, BentoGridDemoComponent, BreakpointContainerDemoComponent, SectionDemoComponent, FlexDemoComponent, ColumnDemoComponent, SidebarLayoutDemoComponent, ScrollContainerDemoComponent, InfiniteScrollContainerDemoComponent, TabsContainerDemoComponent, DashboardShellDemoComponent, GridContainerDemoComponent, FeedLayoutDemoComponent, ListDetailLayoutDemoComponent, SupportingPaneLayoutDemoComponent, MasonryDemoComponent, StickyLayoutDemoComponent, SplitViewDemoComponent, GalleryGridDemoComponent, AnimationsDemoComponent, AccessibilityDemoComponent, SelectDemoComponent, TextareaDemoComponent, DataUtilsDemoComponent, HclStudioDemoComponent, FontManagerDemoComponent, CodeDisplayDemoComponent, BackgroundsDemoComponent]
|
||||
PopoverDemoComponent, AlertDemoComponent, SnackbarDemoComponent, ToastDemoComponent, TreeViewDemoComponent, TimelineDemoComponent, StepperDemoComponent,
|
||||
FabMenuDemoComponent, EnhancedTableDemoComponent, SplitButtonDemoComponent,
|
||||
CommandPaletteDemoComponent, FloatingToolbarDemoComponent, TransferListDemoComponent, TagInputDemoComponent,
|
||||
StackDemoComponent, BoxDemoComponent, CenterDemoComponent, AspectRatioDemoComponent, BentoGridDemoComponent, BreakpointContainerDemoComponent, SectionDemoComponent,
|
||||
FlexDemoComponent, ColumnDemoComponent, SidebarLayoutDemoComponent, ScrollContainerDemoComponent,
|
||||
InfiniteScrollContainerDemoComponent, TabsContainerDemoComponent, DashboardShellDemoComponent, GridContainerDemoComponent, FeedLayoutDemoComponent,
|
||||
ListDetailLayoutDemoComponent, SupportingPaneLayoutDemoComponent, MasonryDemoComponent, StickyLayoutDemoComponent, SplitViewDemoComponent,
|
||||
GalleryGridDemoComponent, AccessibilityDemoComponent, SelectDemoComponent, TextareaDemoComponent, DataUtilsDemoComponent, HclStudioDemoComponent,
|
||||
FontManagerDemoComponent, BackgroundsDemoComponent,
|
||||
ConversionDemoComponent, LandingFAQDemoComponent,
|
||||
LandingFeatureGridDemoComponent, LandingTestimonialsDemoComponent, LandingLogoCloudDemoComponent, LandingStatisticsDemoComponent, LandingHeaderDemoComponent,
|
||||
LandingFooterDemoComponent, LandingTeamDemoComponent, LandingTimelineDemoComponent, LandingTemplatesDemoComponent,
|
||||
LandingTemplatesDemoComponent, LandingTimelineDemoComponent, LandingTeamDemoComponent, LandingFooterDemoComponent, LandingHeaderDemoComponent, LandingStatisticsDemoComponent,
|
||||
LandingFeatureGridDemoComponent, LandingTestimonialsDemoComponent, LandingLogoCloudDemoComponent, AnimationsDemoComponent, CodeDisplayDemoComponent]
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../../../../ui-design-system/src/styles/semantic/index" as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
max-width: 1200px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.enhanced-table-demo {
|
||||
padding: $semantic-spacing-layout-section-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../../../../ui-design-system/src/styles/semantic";
|
||||
@import 'ui-design-system/src/styles/semantic';
|
||||
.demo-container {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as tokens;
|
||||
@use 'ui-design-system/src/styles/semantic' as tokens;
|
||||
|
||||
.demo-container {
|
||||
padding: tokens.$semantic-spacing-layout-md;
|
||||
|
||||
@@ -3,15 +3,12 @@ import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { BrandColors, ThemePreview } from '../../../../../hcl-studio/src/lib/models/hcl.models';
|
||||
import { HCLStudioService } from '../../../../../hcl-studio/src/lib/hcl-studio.service';
|
||||
import { HCLConverter } from '../../../../../hcl-studio/src/lib/core/hcl-converter';
|
||||
import { DEFAULT_THEMES } from '../../../../../hcl-studio/src/lib/themes/theme-presets';
|
||||
|
||||
|
||||
import {
|
||||
HCLStudioService,
|
||||
DEFAULT_THEMES,
|
||||
ThemePreview,
|
||||
BrandColors,
|
||||
HCLConverter,
|
||||
PaletteGenerator
|
||||
} from 'hcl-studio';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hcl-studio-demo',
|
||||
@@ -221,7 +218,7 @@ export class HclStudioDemoComponent implements OnInit, OnDestroy {
|
||||
// Subscribe to theme changes
|
||||
this.hclStudio.themeState$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(state => {
|
||||
.subscribe((state: any) => {
|
||||
this.availableThemes = state.availableThemes;
|
||||
if (state.currentTheme) {
|
||||
this.currentTheme = {
|
||||
@@ -238,7 +235,7 @@ export class HclStudioDemoComponent implements OnInit, OnDestroy {
|
||||
// Subscribe to mode changes
|
||||
this.hclStudio.currentMode$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(mode => {
|
||||
.subscribe((mode: string) => {
|
||||
this.isDarkMode = mode === 'dark';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<div class="hero-demo">
|
||||
<h2>Hero Components Demo</h2>
|
||||
|
||||
<!-- Basic Hero Section -->
|
||||
<section class="demo-section">
|
||||
<h3>Basic Hero Section</h3>
|
||||
<p>A gradient hero with centered content and dual CTAs</p>
|
||||
<ui-lp-hero
|
||||
[configuration]="basicHeroConfig"
|
||||
(ctaClicked)="onCtaClick('Basic hero CTA clicked')">
|
||||
</ui-lp-hero>
|
||||
</section>
|
||||
|
||||
<!-- Light Hero Section -->
|
||||
<section class="demo-section">
|
||||
<h3>Light Hero Section</h3>
|
||||
<p>Clean design with left-aligned content</p>
|
||||
<ui-lp-hero
|
||||
[configuration]="lightHeroConfig"
|
||||
(ctaClicked)="onCtaClick('Light hero CTA clicked')">
|
||||
</ui-lp-hero>
|
||||
</section>
|
||||
|
||||
<!-- Animated Background Hero -->
|
||||
<section class="demo-section">
|
||||
<h3>Animated Background Hero</h3>
|
||||
<p>Full height with animated gradient background</p>
|
||||
<ui-lp-hero
|
||||
[configuration]="darkHeroConfig"
|
||||
(ctaClicked)="onCtaClick('Dark hero CTA clicked')">
|
||||
</ui-lp-hero>
|
||||
</section>
|
||||
|
||||
<!-- Hero With Image - Split Layout -->
|
||||
<section class="demo-section">
|
||||
<h3>Hero With Image</h3>
|
||||
<p>Split layout with image on the right</p>
|
||||
<ui-lp-hero-image
|
||||
[configuration]="splitImageConfig"
|
||||
(ctaClicked)="onCtaClick('Split image hero CTA clicked')">
|
||||
</ui-lp-hero-image>
|
||||
</section>
|
||||
|
||||
<!-- Hero With Image - Overlay Style -->
|
||||
<section class="demo-section">
|
||||
<h3>Hero Image - Different Layout</h3>
|
||||
<p>Image on left with gradient background</p>
|
||||
<ui-lp-hero-image
|
||||
[configuration]="overlayImageConfig"
|
||||
(ctaClicked)="onCtaClick('Overlay image hero CTA clicked')">
|
||||
</ui-lp-hero-image>
|
||||
</section>
|
||||
|
||||
<!-- Split Screen Hero - Text Content -->
|
||||
<section class="demo-section">
|
||||
<h3>Split Screen Hero - Text</h3>
|
||||
<p>50/50 split with text content on both sides</p>
|
||||
<ui-lp-hero-split
|
||||
[configuration]="textSplitConfig"
|
||||
(ctaClicked)="onCtaClick('Text split hero CTA clicked')">
|
||||
</ui-lp-hero-split>
|
||||
</section>
|
||||
|
||||
<!-- Split Screen Hero - Mixed Media -->
|
||||
<section class="demo-section">
|
||||
<h3>Split Screen Hero - Mixed Media</h3>
|
||||
<p>60/40 split with text and image content</p>
|
||||
<ui-lp-hero-split
|
||||
[configuration]="mediaSplitConfig"
|
||||
(ctaClicked)="onCtaClick('Media split hero CTA clicked')">
|
||||
</ui-lp-hero-split>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
.hero-demo {
|
||||
h2 {
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
color: var(--semantic-color-text-primary);
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 3rem;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--semantic-color-text-primary);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--semantic-color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
padding: 0 1rem;
|
||||
background: var(--semantic-color-surface-elevated);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-left: 3px solid var(--semantic-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure demo sections don't interfere with hero component styles
|
||||
.demo-section {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--semantic-shadow-card-rest);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--semantic-shadow-card-hover);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HeroSectionsDemoComponent } from './hero-sections-demo.component';
|
||||
|
||||
describe('HeroSectionsDemoComponent', () => {
|
||||
let component: HeroSectionsDemoComponent;
|
||||
let fixture: ComponentFixture<HeroSectionsDemoComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HeroSectionsDemoComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HeroSectionsDemoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
HeroSectionComponent,
|
||||
HeroWithImageComponent,
|
||||
HeroSplitScreenComponent
|
||||
} from 'ui-landing-pages';
|
||||
import {
|
||||
HeroConfig,
|
||||
HeroImageConfig,
|
||||
HeroSplitConfig,
|
||||
CTAButton
|
||||
} from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-sections-demo',
|
||||
imports: [
|
||||
CommonModule,
|
||||
HeroSectionComponent,
|
||||
HeroWithImageComponent,
|
||||
HeroSplitScreenComponent
|
||||
],
|
||||
templateUrl: './hero-sections-demo.component.html',
|
||||
styleUrl: './hero-sections-demo.component.scss'
|
||||
})
|
||||
export class HeroSectionsDemoComponent {
|
||||
|
||||
// Hero Section Configs
|
||||
basicHeroConfig: HeroConfig = {
|
||||
title: 'Build Amazing Landing Pages',
|
||||
subtitle: 'Create stunning websites with our comprehensive component library',
|
||||
alignment: 'center',
|
||||
backgroundType: 'gradient',
|
||||
minHeight: 'large',
|
||||
ctaPrimary: {
|
||||
text: 'Get Started',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Primary CTA clicked')
|
||||
},
|
||||
ctaSecondary: {
|
||||
text: 'Learn More',
|
||||
variant: 'outlined',
|
||||
action: () => console.log('Secondary CTA clicked')
|
||||
}
|
||||
};
|
||||
|
||||
lightHeroConfig: HeroConfig = {
|
||||
title: 'Clean & Modern Design',
|
||||
subtitle: 'Designed for the modern web',
|
||||
alignment: 'left',
|
||||
backgroundType: 'solid',
|
||||
minHeight: 'medium',
|
||||
ctaPrimary: {
|
||||
text: 'Explore Features',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Explore clicked')
|
||||
}
|
||||
};
|
||||
|
||||
darkHeroConfig: HeroConfig = {
|
||||
title: 'Dark Mode Ready',
|
||||
subtitle: 'Perfect for dark interfaces',
|
||||
alignment: 'right',
|
||||
backgroundType: 'animated',
|
||||
minHeight: 'full',
|
||||
ctaPrimary: {
|
||||
text: 'Try Dark Mode',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Dark mode clicked')
|
||||
}
|
||||
};
|
||||
|
||||
// Hero With Image Configs
|
||||
splitImageConfig: HeroImageConfig = {
|
||||
title: 'Hero with Image',
|
||||
subtitle: 'Perfect split layout',
|
||||
alignment: 'left',
|
||||
backgroundType: 'solid',
|
||||
minHeight: 'large',
|
||||
imageUrl: 'https://images.unsplash.com/photo-1551434678-e076c223a692?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
|
||||
imageAlt: 'Team collaboration',
|
||||
imagePosition: 'right',
|
||||
imageMobile: 'below',
|
||||
ctaPrimary: {
|
||||
text: 'Start Building',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Start building clicked')
|
||||
}
|
||||
};
|
||||
|
||||
overlayImageConfig: HeroImageConfig = {
|
||||
title: 'Overlay Hero Style',
|
||||
subtitle: 'Text over image',
|
||||
alignment: 'center',
|
||||
backgroundType: 'gradient',
|
||||
minHeight: 'full',
|
||||
imageUrl: 'https://images.unsplash.com/photo-1557804506-669a67965ba0?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80',
|
||||
imageAlt: 'Modern office space',
|
||||
imagePosition: 'left',
|
||||
imageMobile: 'above',
|
||||
ctaPrimary: {
|
||||
text: 'View Gallery',
|
||||
variant: 'filled',
|
||||
action: () => console.log('View gallery clicked')
|
||||
},
|
||||
ctaSecondary: {
|
||||
text: 'Learn More',
|
||||
variant: 'outlined',
|
||||
action: () => console.log('Learn more clicked')
|
||||
}
|
||||
};
|
||||
|
||||
// Hero Split Screen Configs
|
||||
textSplitConfig: HeroSplitConfig = {
|
||||
title: 'Split Screen Hero',
|
||||
subtitle: 'Powerful dual-content layout',
|
||||
alignment: 'center',
|
||||
backgroundType: 'solid',
|
||||
minHeight: 'large',
|
||||
splitRatio: '50-50',
|
||||
leftContent: {
|
||||
type: 'text',
|
||||
content: '<h3>Powerful Features</h3><p>Everything you need to build professional landing pages with our comprehensive component library.</p>'
|
||||
},
|
||||
rightContent: {
|
||||
type: 'text',
|
||||
content: '<h3>Easy to Use</h3><p>Drop-in components that work seamlessly with your existing Angular applications.</p>'
|
||||
}
|
||||
};
|
||||
|
||||
mediaSplitConfig: HeroSplitConfig = {
|
||||
title: 'Media Split Hero',
|
||||
subtitle: 'Text and image combination',
|
||||
alignment: 'left',
|
||||
backgroundType: 'gradient',
|
||||
minHeight: 'large',
|
||||
splitRatio: '60-40',
|
||||
leftContent: {
|
||||
type: 'text',
|
||||
content: '<h3>See It In Action</h3><p>Watch our components come to life with interactive examples and real-world use cases.</p>'
|
||||
},
|
||||
rightContent: {
|
||||
type: 'image',
|
||||
content: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80'
|
||||
}
|
||||
};
|
||||
|
||||
onCtaClick(message: string): void {
|
||||
console.log(message);
|
||||
// In a real app, you might navigate to a different route or open a modal
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../../../../../ui-design-system/src/styles/semantic";
|
||||
@import 'ui-design-system/src/styles/semantic';
|
||||
|
||||
// Tokens available globally via main application styles
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../../../../ui-design-system/src/styles/semantic" as *;
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FAQSectionComponent, FAQConfig } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-faq-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FAQSectionComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
<h1>FAQ Section Demo</h1>
|
||||
<p>Interactive FAQ component with accordion functionality and search.</p>
|
||||
<div class="under-construction">
|
||||
<p><strong>🚧 Under Construction:</strong> This demo is temporarily disabled while the ui-landing-pages library is being built.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temporarily commented out until ui-landing-pages is built
|
||||
<div style="display: none;">
|
||||
<div class="demo-section">
|
||||
<h2>Default FAQ</h2>
|
||||
<ui-lp-faq [configuration]="defaultConfig"></ui-lp-faq>
|
||||
</div>
|
||||
|
||||
<!-- Bordered FAQ -->
|
||||
<div class="demo-section">
|
||||
<h2>Bordered Theme</h2>
|
||||
<ui-lp-faq [configuration]="borderedConfig"></ui-lp-faq>
|
||||
</div>
|
||||
|
||||
<!-- Minimal FAQ -->
|
||||
<div class="demo-section">
|
||||
<h2>Minimal Theme</h2>
|
||||
<ui-lp-faq [configuration]="minimalConfig"></ui-lp-faq>
|
||||
</div>
|
||||
|
||||
<!-- Multiple Expand FAQ -->
|
||||
<div class="demo-section">
|
||||
<h2>Multiple Expand Enabled</h2>
|
||||
<ui-lp-faq [configuration]="multipleExpandConfig"></ui-lp-faq>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
font-size: 1.125rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class LandingFAQDemoComponent {
|
||||
defaultConfig: FAQConfig = {
|
||||
title: 'Frequently Asked Questions',
|
||||
subtitle: 'Find answers to common questions about our platform',
|
||||
searchEnabled: true,
|
||||
expandMultiple: false,
|
||||
theme: 'default',
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
question: 'What is included in the free plan?',
|
||||
answer: 'The free plan includes up to 5 projects, 2GB storage, and basic support. Perfect for getting started with small projects.'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
question: 'Can I upgrade or downgrade my plan at any time?',
|
||||
answer: 'Yes, you can change your plan at any time. Upgrades take effect immediately, while downgrades take effect at the next billing cycle.'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
question: 'Is my data secure?',
|
||||
answer: 'Absolutely. We use enterprise-grade encryption, regular security audits, and comply with SOC 2 Type II standards to protect your data.'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
question: 'Do you offer customer support?',
|
||||
answer: 'Yes! We provide 24/7 email support for all users, with priority phone support available for Pro and Enterprise customers.'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
question: 'Can I cancel my subscription anytime?',
|
||||
answer: 'Yes, you can cancel your subscription at any time. Your account will remain active until the end of your current billing period.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
borderedConfig: FAQConfig = {
|
||||
...this.defaultConfig,
|
||||
title: 'Product Questions',
|
||||
subtitle: 'Everything you need to know about our product features',
|
||||
theme: 'bordered',
|
||||
items: [
|
||||
{
|
||||
id: '6',
|
||||
question: 'How do I integrate with third-party services?',
|
||||
answer: 'Our platform offers REST APIs, webhooks, and pre-built integrations with popular services like Slack, GitHub, and Zapier.'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
question: 'What file formats do you support?',
|
||||
answer: 'We support all major file formats including PDF, Word, Excel, PowerPoint, images (PNG, JPG, SVG), and various code files.'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
question: 'Is there a mobile app available?',
|
||||
answer: 'Yes! Our mobile apps are available for both iOS and Android, with full feature parity to the web application.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
minimalConfig: FAQConfig = {
|
||||
...this.defaultConfig,
|
||||
title: 'Technical FAQ',
|
||||
subtitle: 'Answers to technical questions',
|
||||
theme: 'minimal',
|
||||
searchEnabled: false,
|
||||
items: [
|
||||
{
|
||||
id: '9',
|
||||
question: 'What are your API rate limits?',
|
||||
answer: 'Free accounts: 100 requests/hour. Pro accounts: 1,000 requests/hour. Enterprise: Custom limits based on your needs.'
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
question: 'Do you provide webhooks?',
|
||||
answer: 'Yes, we support webhooks for real-time event notifications. You can configure them in your dashboard settings.'
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
question: 'What programming languages do you support?',
|
||||
answer: 'We provide SDKs for JavaScript, Python, Ruby, PHP, Go, and .NET. REST APIs are available for any language.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
multipleExpandConfig: FAQConfig = {
|
||||
...this.defaultConfig,
|
||||
title: 'Detailed FAQ',
|
||||
subtitle: 'Comprehensive answers to all your questions',
|
||||
expandMultiple: true,
|
||||
items: [
|
||||
{
|
||||
id: '12',
|
||||
question: 'How does billing work?',
|
||||
answer: 'Billing is processed monthly or annually based on your preference. You can view detailed invoices in your account dashboard and update payment methods anytime.'
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
question: 'Can I export my data?',
|
||||
answer: 'Yes, you can export all your data in various formats (CSV, JSON, XML) at any time. Enterprise customers also get automated backup options.'
|
||||
},
|
||||
{
|
||||
id: '14',
|
||||
question: 'What happens if I exceed my plan limits?',
|
||||
answer: 'If you approach your limits, we\'ll notify you in advance. You can either upgrade your plan or purchase additional resources as needed.'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: $semantic-spacing-layout-section-xl;
|
||||
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h3, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h3, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h3, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h3, line-height);
|
||||
color: $semantic-color-text-primary;
|
||||
margin-bottom: $semantic-spacing-content-heading;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-code {
|
||||
background: $semantic-color-surface-elevated;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
border-radius: $semantic-border-card-radius;
|
||||
padding: $semantic-spacing-component-lg;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: $semantic-color-text-primary;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FeatureGridComponent, FeatureGridConfig, FeatureItem } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-feature-grid-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FeatureGridComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div style="padding: 2rem;">
|
||||
<h2>Feature Grid Component</h2>
|
||||
<p style="margin-bottom: 2rem; color: #666;">Showcase features in responsive grid layouts with icons, images, and links</p>
|
||||
|
||||
<!-- Basic Feature Grid -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Basic Grid</h3>
|
||||
<ui-lp-feature-grid
|
||||
[configuration]="basicConfig">
|
||||
</ui-lp-feature-grid>
|
||||
</section>
|
||||
|
||||
<!-- Card Variant -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Card Variant</h3>
|
||||
<ui-lp-feature-grid
|
||||
[configuration]="cardConfig">
|
||||
</ui-lp-feature-grid>
|
||||
</section>
|
||||
|
||||
<!-- Minimal Variant -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Minimal Variant</h3>
|
||||
<ui-lp-feature-grid
|
||||
[configuration]="minimalConfig">
|
||||
</ui-lp-feature-grid>
|
||||
</section>
|
||||
|
||||
<!-- List Layout -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">List Layout</h3>
|
||||
<ui-lp-feature-grid
|
||||
[configuration]="listConfig">
|
||||
</ui-lp-feature-grid>
|
||||
</section>
|
||||
|
||||
<!-- Configuration Code -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Configuration</h3>
|
||||
<pre class="demo-code">{{ configExample }}</pre>
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './landing-feature-grid-demo.component.scss'
|
||||
})
|
||||
export class LandingFeatureGridDemoComponent {
|
||||
private sampleFeatures: FeatureItem[] = [
|
||||
{
|
||||
id: 'security',
|
||||
title: 'Advanced Security',
|
||||
description: 'Enterprise-grade security with end-to-end encryption and compliance certifications.',
|
||||
icon: 'shield-alt',
|
||||
iconType: 'fa',
|
||||
link: {
|
||||
url: '#security',
|
||||
text: 'Learn more',
|
||||
target: '_self'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'performance',
|
||||
title: 'Lightning Fast',
|
||||
description: 'Optimized for speed with CDN delivery and smart caching mechanisms.',
|
||||
icon: 'bolt',
|
||||
iconType: 'fa',
|
||||
link: {
|
||||
url: '#performance',
|
||||
text: 'View benchmarks',
|
||||
target: '_self'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'analytics',
|
||||
title: 'Smart Analytics',
|
||||
description: 'Gain insights with real-time analytics and customizable dashboards.',
|
||||
icon: 'chart-line',
|
||||
iconType: 'fa',
|
||||
link: {
|
||||
url: '#analytics',
|
||||
text: 'Explore features',
|
||||
target: '_self'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
title: '24/7 Support',
|
||||
description: 'Get help whenever you need it with our dedicated support team.',
|
||||
icon: 'headset',
|
||||
iconType: 'fa',
|
||||
link: {
|
||||
url: '#support',
|
||||
text: 'Contact us',
|
||||
target: '_self'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
basicConfig: FeatureGridConfig = {
|
||||
title: 'Why Choose Our Platform',
|
||||
subtitle: 'Discover the features that make us different',
|
||||
features: this.sampleFeatures,
|
||||
layout: 'grid',
|
||||
columns: 'auto',
|
||||
variant: 'card',
|
||||
showIcons: true,
|
||||
spacing: 'normal'
|
||||
};
|
||||
|
||||
cardConfig: FeatureGridConfig = {
|
||||
title: 'Platform Features',
|
||||
features: this.sampleFeatures,
|
||||
layout: 'grid',
|
||||
columns: 2,
|
||||
variant: 'card',
|
||||
showIcons: true,
|
||||
spacing: 'loose'
|
||||
};
|
||||
|
||||
minimalConfig: FeatureGridConfig = {
|
||||
features: this.sampleFeatures.slice(0, 3),
|
||||
layout: 'grid',
|
||||
columns: 3,
|
||||
variant: 'minimal',
|
||||
showIcons: true,
|
||||
spacing: 'tight'
|
||||
};
|
||||
|
||||
listConfig: FeatureGridConfig = {
|
||||
title: 'Core Capabilities',
|
||||
features: this.sampleFeatures.slice(0, 3),
|
||||
layout: 'list',
|
||||
variant: 'minimal',
|
||||
showIcons: true,
|
||||
spacing: 'normal'
|
||||
};
|
||||
|
||||
configExample = `const featureGridConfig: FeatureGridConfig = {
|
||||
title: 'Why Choose Our Platform',
|
||||
subtitle: 'Discover the features that make us different',
|
||||
features: [
|
||||
{
|
||||
id: 'security',
|
||||
title: 'Advanced Security',
|
||||
description: 'Enterprise-grade security with end-to-end encryption.',
|
||||
icon: 'shield-alt',
|
||||
iconType: 'fa',
|
||||
link: {
|
||||
url: '#security',
|
||||
text: 'Learn more'
|
||||
}
|
||||
}
|
||||
// ... more features
|
||||
],
|
||||
layout: 'grid',
|
||||
columns: 'auto',
|
||||
variant: 'card',
|
||||
showIcons: true,
|
||||
spacing: 'normal'
|
||||
};`;
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FooterSectionComponent, FooterConfig, FooterLink } from 'ui-landing-pages';
|
||||
import { ButtonComponent } from 'ui-essentials';
|
||||
import { VStackComponent } from 'ui-essentials';
|
||||
import { SectionComponent } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-footer-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FooterSectionComponent, ButtonComponent, VStackComponent, SectionComponent],
|
||||
template: `
|
||||
<ui-vstack [spacing]="'xl'">
|
||||
<ui-section>
|
||||
<h2>Landing Footer - Light Theme</h2>
|
||||
<p>Comprehensive footer with multiple columns, newsletter signup, and social links.</p>
|
||||
|
||||
<ui-lp-footer
|
||||
[configuration]="lightFooterConfig()"
|
||||
(linkClicked)="onLinkClicked($event)"
|
||||
(newsletterSubmitted)="onNewsletterSubmitted($event)">
|
||||
</ui-lp-footer>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h2>Landing Footer - Dark Theme</h2>
|
||||
<p>Dark theme footer with company branding and legal links.</p>
|
||||
|
||||
<ui-lp-footer
|
||||
[configuration]="darkFooterConfig()"
|
||||
(linkClicked)="onLinkClicked($event)"
|
||||
(newsletterSubmitted)="onNewsletterSubmitted($event)">
|
||||
</ui-lp-footer>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h2>Landing Footer - Minimal</h2>
|
||||
<p>Simple footer with just essential links and copyright.</p>
|
||||
|
||||
<ui-lp-footer
|
||||
[configuration]="minimalFooterConfig()"
|
||||
(linkClicked)="onLinkClicked($event)">
|
||||
</ui-lp-footer>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h2>Configuration Options</h2>
|
||||
<div class="demo-controls">
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="toggleTheme()"
|
||||
class="demo-control">
|
||||
Theme: {{ lightFooterConfig().theme }}
|
||||
</ui-button>
|
||||
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="toggleDivider()"
|
||||
class="demo-control">
|
||||
Show Divider: {{ lightFooterConfig().showDivider ? 'ON' : 'OFF' }}
|
||||
</ui-button>
|
||||
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="addColumn()"
|
||||
class="demo-control">
|
||||
Add Column
|
||||
</ui-button>
|
||||
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="removeColumn()"
|
||||
class="demo-control">
|
||||
Remove Column
|
||||
</ui-button>
|
||||
</div>
|
||||
</ui-section>
|
||||
</ui-vstack>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.demo-control {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class LandingFooterDemoComponent {
|
||||
lightFooterConfig = signal<FooterConfig>({
|
||||
logo: {
|
||||
text: 'UI Suite',
|
||||
url: '/'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
id: 'product',
|
||||
title: 'Product',
|
||||
items: [
|
||||
{ id: 'features', label: 'Features', route: '/features' },
|
||||
{ id: 'pricing', label: 'Pricing', route: '/pricing' },
|
||||
{ id: 'integrations', label: 'Integrations', route: '/integrations' },
|
||||
{ id: 'api', label: 'API', route: '/api' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'company',
|
||||
title: 'Company',
|
||||
items: [
|
||||
{ id: 'about', label: 'About Us', route: '/about' },
|
||||
{ id: 'careers', label: 'Careers', route: '/careers', badge: '5 open' },
|
||||
{ id: 'blog', label: 'Blog', route: '/blog' },
|
||||
{ id: 'press', label: 'Press', route: '/press' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'resources',
|
||||
title: 'Resources',
|
||||
items: [
|
||||
{ id: 'docs', label: 'Documentation', route: '/docs' },
|
||||
{ id: 'guides', label: 'Guides', route: '/guides' },
|
||||
{ id: 'help', label: 'Help Center', route: '/help' },
|
||||
{ id: 'community', label: 'Community', url: 'https://discord.gg/example', target: '_blank' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'legal',
|
||||
title: 'Legal',
|
||||
items: [
|
||||
{ id: 'privacy', label: 'Privacy Policy', route: '/privacy' },
|
||||
{ id: 'terms', label: 'Terms of Service', route: '/terms' },
|
||||
{ id: 'cookies', label: 'Cookie Policy', route: '/cookies' },
|
||||
{ id: 'gdpr', label: 'GDPR', route: '/gdpr' }
|
||||
]
|
||||
}
|
||||
],
|
||||
newsletter: {
|
||||
title: 'Stay Updated',
|
||||
description: 'Get the latest news and updates delivered to your inbox.',
|
||||
placeholder: 'Enter your email',
|
||||
buttonText: 'Subscribe',
|
||||
onSubmit: (email: string) => {
|
||||
console.log('Newsletter signup:', email);
|
||||
}
|
||||
},
|
||||
socialLinks: [
|
||||
{ id: 'twitter', platform: 'twitter', url: 'https://twitter.com/example' },
|
||||
{ id: 'facebook', platform: 'facebook', url: 'https://facebook.com/example' },
|
||||
{ id: 'linkedin', platform: 'linkedin', url: 'https://linkedin.com/company/example' },
|
||||
{ id: 'github', platform: 'github', url: 'https://github.com/example' }
|
||||
],
|
||||
copyright: '© 2024 UI Suite. All rights reserved.',
|
||||
legalLinks: [
|
||||
{ id: 'privacy', label: 'Privacy', route: '/privacy' },
|
||||
{ id: 'terms', label: 'Terms', route: '/terms' },
|
||||
{ id: 'cookies', label: 'Cookies', route: '/cookies' }
|
||||
],
|
||||
theme: 'light',
|
||||
showDivider: true
|
||||
});
|
||||
|
||||
darkFooterConfig = signal<FooterConfig>({
|
||||
logo: {
|
||||
text: 'Dark UI Pro',
|
||||
url: '/'
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
id: 'solutions',
|
||||
title: 'Solutions',
|
||||
items: [
|
||||
{ id: 'enterprise', label: 'Enterprise', route: '/enterprise' },
|
||||
{ id: 'startups', label: 'Startups', route: '/startups' },
|
||||
{ id: 'agencies', label: 'Agencies', route: '/agencies' },
|
||||
{ id: 'developers', label: 'Developers', route: '/developers' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
title: 'Support',
|
||||
items: [
|
||||
{ id: 'contact', label: 'Contact Us', route: '/contact' },
|
||||
{ id: 'chat', label: 'Live Chat', action: () => alert('Chat opened!') },
|
||||
{ id: 'tickets', label: 'Support Tickets', route: '/support' },
|
||||
{ id: 'status', label: 'System Status', url: 'https://status.example.com', target: '_blank' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'developers',
|
||||
title: 'Developers',
|
||||
items: [
|
||||
{ id: 'api-docs', label: 'API Documentation', route: '/api-docs' },
|
||||
{ id: 'sdk', label: 'SDK', route: '/sdk' },
|
||||
{ id: 'webhooks', label: 'Webhooks', route: '/webhooks' },
|
||||
{ id: 'changelog', label: 'Changelog', route: '/changelog' }
|
||||
]
|
||||
}
|
||||
],
|
||||
newsletter: {
|
||||
title: 'Developer Newsletter',
|
||||
description: 'Weekly updates on new features, API changes, and developer resources.',
|
||||
placeholder: 'developer@company.com',
|
||||
buttonText: 'Join',
|
||||
onSubmit: (email: string) => {
|
||||
console.log('Developer newsletter signup:', email);
|
||||
}
|
||||
},
|
||||
socialLinks: [
|
||||
{ id: 'github', platform: 'github', url: 'https://github.com/example' },
|
||||
{ id: 'twitter', platform: 'twitter', url: 'https://twitter.com/example' },
|
||||
{ id: 'youtube', platform: 'youtube', url: 'https://youtube.com/example' }
|
||||
],
|
||||
copyright: '© 2024 Dark UI Pro. Built with ❤️ by developers.',
|
||||
legalLinks: [
|
||||
{ id: 'privacy', label: 'Privacy Policy', route: '/privacy' },
|
||||
{ id: 'terms', label: 'Terms of Service', route: '/terms' },
|
||||
{ id: 'security', label: 'Security', route: '/security' }
|
||||
],
|
||||
theme: 'dark',
|
||||
showDivider: true
|
||||
});
|
||||
|
||||
minimalFooterConfig = signal<FooterConfig>({
|
||||
columns: [
|
||||
{
|
||||
id: 'quick-links',
|
||||
title: 'Quick Links',
|
||||
items: [
|
||||
{ id: 'home', label: 'Home', route: '/' },
|
||||
{ id: 'about', label: 'About', route: '/about' },
|
||||
{ id: 'contact', label: 'Contact', route: '/contact' }
|
||||
]
|
||||
}
|
||||
],
|
||||
copyright: '© 2024 Minimal Footer Example',
|
||||
legalLinks: [
|
||||
{ id: 'privacy', label: 'Privacy', route: '/privacy' },
|
||||
{ id: 'terms', label: 'Terms', route: '/terms' }
|
||||
],
|
||||
theme: 'light',
|
||||
showDivider: false
|
||||
});
|
||||
|
||||
toggleTheme(): void {
|
||||
const current = this.lightFooterConfig();
|
||||
this.lightFooterConfig.set({
|
||||
...current,
|
||||
theme: current.theme === 'light' ? 'dark' : 'light'
|
||||
});
|
||||
}
|
||||
|
||||
toggleDivider(): void {
|
||||
const current = this.lightFooterConfig();
|
||||
this.lightFooterConfig.set({
|
||||
...current,
|
||||
showDivider: !current.showDivider
|
||||
});
|
||||
}
|
||||
|
||||
addColumn(): void {
|
||||
const current = this.lightFooterConfig();
|
||||
const newColumn = {
|
||||
id: `column-${current.columns.length + 1}`,
|
||||
title: `Column ${current.columns.length + 1}`,
|
||||
items: [
|
||||
{ id: `item-1`, label: 'Item 1', route: '/item-1' },
|
||||
{ id: `item-2`, label: 'Item 2', route: '/item-2' }
|
||||
]
|
||||
};
|
||||
|
||||
this.lightFooterConfig.set({
|
||||
...current,
|
||||
columns: [...current.columns, newColumn]
|
||||
});
|
||||
}
|
||||
|
||||
removeColumn(): void {
|
||||
const current = this.lightFooterConfig();
|
||||
if (current.columns.length > 1) {
|
||||
this.lightFooterConfig.set({
|
||||
...current,
|
||||
columns: current.columns.slice(0, -1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onLinkClicked(link: FooterLink): void {
|
||||
console.log('Footer link clicked:', link);
|
||||
}
|
||||
|
||||
onNewsletterSubmitted(email: string): void {
|
||||
console.log('Newsletter submitted:', email);
|
||||
alert(`Newsletter subscription for: ${email}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { LandingHeaderComponent, CTAButton, LandingHeaderConfig, NavigationItem } from 'ui-landing-pages';
|
||||
import { ButtonComponent, VStackComponent, SectionComponent } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-header-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, LandingHeaderComponent, ButtonComponent, VStackComponent, SectionComponent],
|
||||
template: `
|
||||
<ui-vstack [spacing]="'xl'">
|
||||
<ui-section>
|
||||
<h2>Landing Header - Light Theme</h2>
|
||||
<p>Sticky navigation header with transparent background that becomes solid on scroll.</p>
|
||||
|
||||
<ui-lp-header
|
||||
[configuration]="lightHeaderConfig()"
|
||||
(navigationClicked)="onNavigationClicked($event)"
|
||||
(ctaClicked)="onCTAClicked($event)">
|
||||
</ui-lp-header>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h2>Landing Header - Dark Theme</h2>
|
||||
<p>Dark theme version with logo and mega menu navigation.</p>
|
||||
|
||||
<ui-lp-header
|
||||
[configuration]="darkHeaderConfig()"
|
||||
(navigationClicked)="onNavigationClicked($event)"
|
||||
(ctaClicked)="onCTAClicked($event)">
|
||||
</ui-lp-header>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h2>Landing Header - With Dropdown Menu</h2>
|
||||
<p>Header with dropdown navigation and badges.</p>
|
||||
|
||||
<ui-lp-header
|
||||
[configuration]="dropdownHeaderConfig()"
|
||||
(navigationClicked)="onNavigationClicked($event)"
|
||||
(ctaClicked)="onCTAClicked($event)">
|
||||
</ui-lp-header>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h2>Configuration Options</h2>
|
||||
<div class="demo-controls">
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="toggleTransparent()"
|
||||
class="demo-control">
|
||||
Toggle Transparent: {{ lightHeaderConfig().transparent ? 'ON' : 'OFF' }}
|
||||
</ui-button>
|
||||
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="toggleSticky()"
|
||||
class="demo-control">
|
||||
Toggle Sticky: {{ lightHeaderConfig().sticky ? 'ON' : 'OFF' }}
|
||||
</ui-button>
|
||||
|
||||
<ui-button
|
||||
[variant]="'outlined'"
|
||||
(clicked)="toggleMobileMenu()"
|
||||
class="demo-control">
|
||||
Toggle Mobile Menu: {{ lightHeaderConfig().showMobileMenu ? 'ON' : 'OFF' }}
|
||||
</ui-button>
|
||||
</div>
|
||||
</ui-section>
|
||||
|
||||
<!-- Content to demonstrate scroll behavior -->
|
||||
<ui-section>
|
||||
<h3>Scroll Content</h3>
|
||||
<div style="height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem;">
|
||||
Scroll to see header behavior
|
||||
</div>
|
||||
</ui-section>
|
||||
|
||||
<ui-section>
|
||||
<h3>More Content</h3>
|
||||
<div style="height: 100vh; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem;">
|
||||
Keep scrolling...
|
||||
</div>
|
||||
</ui-section>
|
||||
</ui-vstack>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.demo-control {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class LandingHeaderDemoComponent {
|
||||
lightHeaderConfig = signal<LandingHeaderConfig>({
|
||||
logo: {
|
||||
text: 'UI Suite',
|
||||
url: '/'
|
||||
},
|
||||
navigation: [
|
||||
{
|
||||
id: 'home',
|
||||
label: 'Home',
|
||||
route: '/',
|
||||
},
|
||||
{
|
||||
id: 'features',
|
||||
label: 'Features',
|
||||
route: '/features',
|
||||
},
|
||||
{
|
||||
id: 'pricing',
|
||||
label: 'Pricing',
|
||||
route: '/pricing',
|
||||
},
|
||||
{
|
||||
id: 'about',
|
||||
label: 'About',
|
||||
route: '/about',
|
||||
},
|
||||
{
|
||||
id: 'contact',
|
||||
label: 'Contact',
|
||||
route: '/contact',
|
||||
}
|
||||
],
|
||||
ctaButton: {
|
||||
text: 'Get Started',
|
||||
variant: 'filled',
|
||||
size: 'medium',
|
||||
action: () => alert('CTA clicked!')
|
||||
},
|
||||
transparent: true,
|
||||
sticky: true,
|
||||
showMobileMenu: true,
|
||||
maxWidth: 'xl',
|
||||
theme: 'light'
|
||||
});
|
||||
|
||||
darkHeaderConfig = signal<LandingHeaderConfig>({
|
||||
logo: {
|
||||
text: 'Dark UI',
|
||||
url: '/'
|
||||
},
|
||||
navigation: [
|
||||
{
|
||||
id: 'products',
|
||||
label: 'Products',
|
||||
route: '/products',
|
||||
},
|
||||
{
|
||||
id: 'solutions',
|
||||
label: 'Solutions',
|
||||
route: '/solutions',
|
||||
},
|
||||
{
|
||||
id: 'resources',
|
||||
label: 'Resources',
|
||||
route: '/resources',
|
||||
},
|
||||
{
|
||||
id: 'company',
|
||||
label: 'Company',
|
||||
route: '/company',
|
||||
}
|
||||
],
|
||||
ctaButton: {
|
||||
text: 'Start Free Trial',
|
||||
variant: 'filled',
|
||||
size: 'medium',
|
||||
icon: '🚀',
|
||||
action: () => alert('Free trial started!')
|
||||
},
|
||||
transparent: false,
|
||||
sticky: true,
|
||||
showMobileMenu: true,
|
||||
maxWidth: 'xl',
|
||||
theme: 'dark'
|
||||
});
|
||||
|
||||
dropdownHeaderConfig = signal<LandingHeaderConfig>({
|
||||
logo: {
|
||||
text: 'Dropdown Demo',
|
||||
url: '/'
|
||||
},
|
||||
navigation: [
|
||||
{
|
||||
id: 'home',
|
||||
label: 'Home',
|
||||
route: '/',
|
||||
},
|
||||
{
|
||||
id: 'products',
|
||||
label: 'Products',
|
||||
children: [
|
||||
{
|
||||
id: 'ui-kit',
|
||||
label: 'UI Kit',
|
||||
route: '/products/ui-kit',
|
||||
badge: 'New'
|
||||
},
|
||||
{
|
||||
id: 'components',
|
||||
label: 'Components',
|
||||
route: '/products/components',
|
||||
},
|
||||
{
|
||||
id: 'templates',
|
||||
label: 'Templates',
|
||||
route: '/products/templates',
|
||||
badge: 'Pro'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'docs',
|
||||
label: 'Documentation',
|
||||
children: [
|
||||
{
|
||||
id: 'getting-started',
|
||||
label: 'Getting Started',
|
||||
route: '/docs/getting-started',
|
||||
},
|
||||
{
|
||||
id: 'api',
|
||||
label: 'API Reference',
|
||||
route: '/docs/api',
|
||||
},
|
||||
{
|
||||
id: 'examples',
|
||||
label: 'Examples',
|
||||
route: '/docs/examples',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
label: 'Support',
|
||||
route: '/support',
|
||||
badge: '24/7'
|
||||
}
|
||||
],
|
||||
ctaButton: {
|
||||
text: 'Contact Sales',
|
||||
variant: 'tonal',
|
||||
size: 'medium',
|
||||
action: () => alert('Contact sales clicked!')
|
||||
},
|
||||
transparent: false,
|
||||
sticky: true,
|
||||
showMobileMenu: true,
|
||||
maxWidth: 'xl',
|
||||
theme: 'light'
|
||||
});
|
||||
|
||||
toggleTransparent(): void {
|
||||
const current = this.lightHeaderConfig();
|
||||
this.lightHeaderConfig.set({
|
||||
...current,
|
||||
transparent: !current.transparent
|
||||
});
|
||||
}
|
||||
|
||||
toggleSticky(): void {
|
||||
const current = this.lightHeaderConfig();
|
||||
this.lightHeaderConfig.set({
|
||||
...current,
|
||||
sticky: !current.sticky
|
||||
});
|
||||
}
|
||||
|
||||
toggleMobileMenu(): void {
|
||||
const current = this.lightHeaderConfig();
|
||||
this.lightHeaderConfig.set({
|
||||
...current,
|
||||
showMobileMenu: !current.showMobileMenu
|
||||
});
|
||||
}
|
||||
|
||||
onNavigationClicked(item: NavigationItem): void {
|
||||
console.log('Navigation clicked:', item);
|
||||
}
|
||||
|
||||
onCTAClicked(cta: CTAButton): void {
|
||||
console.log('CTA clicked:', cta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: $semantic-spacing-layout-section-xl;
|
||||
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h3, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h3, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h3, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h3, line-height);
|
||||
color: $semantic-color-text-primary;
|
||||
margin-bottom: $semantic-spacing-content-heading;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-code {
|
||||
background: $semantic-color-surface-elevated;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
border-radius: $semantic-border-card-radius;
|
||||
padding: $semantic-spacing-component-lg;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: $semantic-color-text-primary;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { LogoCloudComponent, LogoCloudConfig, LogoItem } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-logo-cloud-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, LogoCloudComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div style="padding: 2rem;">
|
||||
<h2>Logo Cloud Component</h2>
|
||||
<p style="margin-bottom: 2rem; color: #666;">Display partner and client logos in various layouts</p>
|
||||
|
||||
<!-- Basic Row Layout -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Row Layout</h3>
|
||||
<ui-lp-logo-cloud
|
||||
[configuration]="rowConfig">
|
||||
</ui-lp-logo-cloud>
|
||||
</section>
|
||||
|
||||
<!-- Grid Layout -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Grid Layout</h3>
|
||||
<ui-lp-logo-cloud
|
||||
[configuration]="gridConfig">
|
||||
</ui-lp-logo-cloud>
|
||||
</section>
|
||||
|
||||
<!-- Marquee Layout -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Marquee Layout</h3>
|
||||
<ui-lp-logo-cloud
|
||||
[configuration]="marqueeConfig">
|
||||
</ui-lp-logo-cloud>
|
||||
</section>
|
||||
|
||||
<!-- Configuration Code -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Configuration</h3>
|
||||
<pre class="demo-code">{{ configExample }}</pre>
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './landing-logo-cloud-demo.component.scss'
|
||||
})
|
||||
export class LandingLogoCloudDemoComponent {
|
||||
private sampleLogos: LogoItem[] = [
|
||||
{
|
||||
id: 'google',
|
||||
name: 'Google',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/2/2f/Google_2015_logo.svg',
|
||||
url: 'https://google.com',
|
||||
grayscale: false
|
||||
},
|
||||
{
|
||||
id: 'microsoft',
|
||||
name: 'Microsoft',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/9/96/Microsoft_logo_%282012%29.svg',
|
||||
url: 'https://microsoft.com',
|
||||
grayscale: false
|
||||
},
|
||||
{
|
||||
id: 'amazon',
|
||||
name: 'Amazon',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/a/a9/Amazon_logo.svg',
|
||||
url: 'https://amazon.com',
|
||||
grayscale: false
|
||||
},
|
||||
{
|
||||
id: 'apple',
|
||||
name: 'Apple',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg',
|
||||
url: 'https://apple.com',
|
||||
grayscale: false
|
||||
},
|
||||
{
|
||||
id: 'netflix',
|
||||
name: 'Netflix',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/0/08/Netflix_2015_logo.svg',
|
||||
url: 'https://netflix.com',
|
||||
grayscale: false
|
||||
},
|
||||
{
|
||||
id: 'spotify',
|
||||
name: 'Spotify',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/1/19/Spotify_logo_without_text.svg',
|
||||
url: 'https://spotify.com',
|
||||
grayscale: false
|
||||
}
|
||||
];
|
||||
|
||||
rowConfig: LogoCloudConfig = {
|
||||
title: 'Trusted by Industry Leaders',
|
||||
subtitle: 'Join thousands of companies using our platform',
|
||||
logos: this.sampleLogos,
|
||||
layout: 'row',
|
||||
itemsPerRow: 5,
|
||||
grayscale: true,
|
||||
hoverEffect: true,
|
||||
maxHeight: 60
|
||||
};
|
||||
|
||||
gridConfig: LogoCloudConfig = {
|
||||
title: 'Our Partners',
|
||||
logos: this.sampleLogos,
|
||||
layout: 'grid',
|
||||
itemsPerRow: 4,
|
||||
grayscale: true,
|
||||
hoverEffect: true,
|
||||
maxHeight: 80
|
||||
};
|
||||
|
||||
marqueeConfig: LogoCloudConfig = {
|
||||
title: 'Continuous Partnership',
|
||||
subtitle: 'Seamlessly scrolling partner showcase',
|
||||
logos: this.sampleLogos,
|
||||
layout: 'marquee',
|
||||
grayscale: true,
|
||||
hoverEffect: true,
|
||||
maxHeight: 70
|
||||
};
|
||||
|
||||
configExample = `const logoCloudConfig: LogoCloudConfig = {
|
||||
title: 'Trusted by Industry Leaders',
|
||||
subtitle: 'Join thousands of companies using our platform',
|
||||
logos: [
|
||||
{
|
||||
id: 'google',
|
||||
name: 'Google',
|
||||
logo: 'path/to/google-logo.svg',
|
||||
url: 'https://google.com',
|
||||
grayscale: false
|
||||
}
|
||||
// ... more logos
|
||||
],
|
||||
layout: 'row',
|
||||
itemsPerRow: 5,
|
||||
grayscale: true,
|
||||
hoverEffect: true,
|
||||
maxHeight: 60
|
||||
};`;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: $semantic-spacing-layout-section-xl;
|
||||
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h3, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h3, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h3, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h3, line-height);
|
||||
color: $semantic-color-text-primary;
|
||||
margin-bottom: $semantic-spacing-content-heading;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-code {
|
||||
background: $semantic-color-surface-elevated;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
border-radius: $semantic-border-card-radius;
|
||||
padding: $semantic-spacing-component-lg;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: $semantic-color-text-primary;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { StatisticsDisplayComponent, StatisticsConfig, StatisticItem } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-statistics-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, StatisticsDisplayComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div style="padding: 2rem;">
|
||||
<h2>Statistics Display Component</h2>
|
||||
<p style="margin-bottom: 2rem; color: #666;">Showcase key metrics with animated counters and various layouts</p>
|
||||
|
||||
<!-- Basic Row Layout -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Row Layout - Minimal</h3>
|
||||
<ui-lp-statistics-display
|
||||
[configuration]="rowConfig">
|
||||
</ui-lp-statistics-display>
|
||||
</section>
|
||||
|
||||
<!-- Grid Layout with Cards -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Grid Layout - Card Variant</h3>
|
||||
<ui-lp-statistics-display
|
||||
[configuration]="cardConfig">
|
||||
</ui-lp-statistics-display>
|
||||
</section>
|
||||
|
||||
<!-- Primary Background -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Primary Background</h3>
|
||||
<ui-lp-statistics-display
|
||||
[configuration]="primaryConfig">
|
||||
</ui-lp-statistics-display>
|
||||
</section>
|
||||
|
||||
<!-- Highlighted Variant -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Highlighted Variant</h3>
|
||||
<ui-lp-statistics-display
|
||||
[configuration]="highlightedConfig">
|
||||
</ui-lp-statistics-display>
|
||||
</section>
|
||||
|
||||
<!-- Configuration Code -->
|
||||
<section class="demo-section">
|
||||
<h3 class="demo-section__title">Configuration</h3>
|
||||
<pre class="demo-code">{{ configExample }}</pre>
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './landing-statistics-demo.component.scss'
|
||||
})
|
||||
export class LandingStatisticsDemoComponent {
|
||||
private sampleStatistics: StatisticItem[] = [
|
||||
{
|
||||
id: 'users',
|
||||
value: 150000,
|
||||
label: 'Active Users',
|
||||
suffix: '+',
|
||||
description: 'Growing every day',
|
||||
icon: 'users',
|
||||
animateValue: true
|
||||
},
|
||||
{
|
||||
id: 'projects',
|
||||
value: 25000,
|
||||
label: 'Projects Created',
|
||||
suffix: '+',
|
||||
description: 'Successful deployments',
|
||||
icon: 'chart-bar',
|
||||
animateValue: true
|
||||
},
|
||||
{
|
||||
id: 'uptime',
|
||||
value: 99.9,
|
||||
label: 'Uptime',
|
||||
suffix: '%',
|
||||
description: 'Reliable service',
|
||||
icon: 'arrow-up',
|
||||
animateValue: true
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
value: '24/7',
|
||||
label: 'Support Available',
|
||||
description: 'Round-the-clock assistance',
|
||||
icon: 'headset',
|
||||
animateValue: false
|
||||
}
|
||||
];
|
||||
|
||||
rowConfig: StatisticsConfig = {
|
||||
title: 'Platform Performance',
|
||||
subtitle: 'See how we\'re making a difference',
|
||||
statistics: this.sampleStatistics,
|
||||
layout: 'row',
|
||||
variant: 'minimal',
|
||||
animateOnScroll: true,
|
||||
backgroundColor: 'transparent'
|
||||
};
|
||||
|
||||
cardConfig: StatisticsConfig = {
|
||||
title: 'Success Metrics',
|
||||
statistics: this.sampleStatistics,
|
||||
layout: 'grid',
|
||||
variant: 'card',
|
||||
animateOnScroll: true,
|
||||
backgroundColor: 'surface'
|
||||
};
|
||||
|
||||
primaryConfig: StatisticsConfig = {
|
||||
title: 'Key Achievements',
|
||||
subtitle: 'Numbers that matter to our community',
|
||||
statistics: this.sampleStatistics.slice(0, 3),
|
||||
layout: 'row',
|
||||
variant: 'minimal',
|
||||
animateOnScroll: true,
|
||||
backgroundColor: 'primary'
|
||||
};
|
||||
|
||||
highlightedConfig: StatisticsConfig = {
|
||||
title: 'Performance Indicators',
|
||||
statistics: this.sampleStatistics,
|
||||
layout: 'grid',
|
||||
variant: 'highlighted',
|
||||
animateOnScroll: true,
|
||||
backgroundColor: 'transparent'
|
||||
};
|
||||
|
||||
configExample = `const statisticsConfig: StatisticsConfig = {
|
||||
title: 'Platform Performance',
|
||||
subtitle: 'See how we're making a difference',
|
||||
statistics: [
|
||||
{
|
||||
id: 'users',
|
||||
value: 150000,
|
||||
label: 'Active Users',
|
||||
suffix: '+',
|
||||
description: 'Growing every day',
|
||||
icon: 'users',
|
||||
animateValue: true
|
||||
}
|
||||
// ... more statistics
|
||||
],
|
||||
layout: 'row',
|
||||
variant: 'minimal',
|
||||
animateOnScroll: true,
|
||||
backgroundColor: 'transparent'
|
||||
};`;
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TeamGridComponent, TeamConfig } from 'ui-landing-pages';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-team-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, TeamGridComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
<h1>Team Grid Demo</h1>
|
||||
<p>Showcase your team members with professional cards and social links.</p>
|
||||
</div>
|
||||
|
||||
<!-- 3 Column Layout -->
|
||||
<div class="demo-section">
|
||||
<h2>3 Column Layout (Default)</h2>
|
||||
<ui-lp-team-grid [configuration]="threeColumnConfig"></ui-lp-team-grid>
|
||||
</div>
|
||||
|
||||
<!-- 2 Column Layout -->
|
||||
<div class="demo-section">
|
||||
<h2>2 Column Layout</h2>
|
||||
<ui-lp-team-grid [configuration]="twoColumnConfig"></ui-lp-team-grid>
|
||||
</div>
|
||||
|
||||
<!-- 4 Column Layout -->
|
||||
<div class="demo-section">
|
||||
<h2>4 Column Layout</h2>
|
||||
<ui-lp-team-grid [configuration]="fourColumnConfig"></ui-lp-team-grid>
|
||||
</div>
|
||||
|
||||
<!-- Without Bio -->
|
||||
<div class="demo-section">
|
||||
<h2>Without Bio Text</h2>
|
||||
<ui-lp-team-grid [configuration]="noBioConfig"></ui-lp-team-grid>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
font-size: 1.125rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class LandingTeamDemoComponent {
|
||||
threeColumnConfig: TeamConfig = {
|
||||
title: 'Meet Our Amazing Team',
|
||||
subtitle: 'The talented professionals who make our success possible',
|
||||
columns: 3,
|
||||
showSocial: true,
|
||||
showBio: true,
|
||||
members: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Sarah Johnson',
|
||||
role: 'CEO & Founder',
|
||||
bio: 'Visionary leader with 15+ years in tech, driving innovation and growth across multiple successful ventures.',
|
||||
image: 'https://images.unsplash.com/photo-1494790108755-2616b612b5ff?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/sarahjohnson',
|
||||
twitter: 'https://twitter.com/sarahjohnson',
|
||||
email: 'sarah@company.com'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Michael Chen',
|
||||
role: 'Chief Technology Officer',
|
||||
bio: 'Full-stack engineer and architect, passionate about building scalable systems and mentoring developers.',
|
||||
image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/michaelchen',
|
||||
github: 'https://github.com/mchen',
|
||||
twitter: 'https://twitter.com/michaelchen',
|
||||
email: 'michael@company.com'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Emily Rodriguez',
|
||||
role: 'Head of Design',
|
||||
bio: 'Creative problem-solver specializing in user experience design and building design systems that scale.',
|
||||
image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/emilyrodriguez',
|
||||
website: 'https://emilydesigns.com',
|
||||
email: 'emily@company.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
twoColumnConfig: TeamConfig = {
|
||||
title: 'Leadership Team',
|
||||
subtitle: 'The executives guiding our company forward',
|
||||
columns: 2,
|
||||
showSocial: true,
|
||||
showBio: true,
|
||||
members: [
|
||||
{
|
||||
id: '4',
|
||||
name: 'David Park',
|
||||
role: 'VP of Sales',
|
||||
bio: 'Results-driven sales leader with a proven track record of building high-performing teams and exceeding revenue targets.',
|
||||
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/davidpark',
|
||||
email: 'david@company.com'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'Lisa Thompson',
|
||||
role: 'VP of Marketing',
|
||||
bio: 'Strategic marketer focused on brand building, digital growth, and creating authentic connections with our community.',
|
||||
image: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/lisathompson',
|
||||
twitter: 'https://twitter.com/lisathompson',
|
||||
email: 'lisa@company.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
fourColumnConfig: TeamConfig = {
|
||||
title: 'Engineering Team',
|
||||
subtitle: 'The brilliant minds behind our technology',
|
||||
columns: 4,
|
||||
showSocial: true,
|
||||
showBio: true,
|
||||
members: [
|
||||
{
|
||||
id: '6',
|
||||
name: 'Alex Kim',
|
||||
role: 'Senior Frontend Developer',
|
||||
bio: 'React specialist with a passion for creating beautiful, accessible user interfaces.',
|
||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
github: 'https://github.com/alexkim',
|
||||
linkedin: 'https://linkedin.com/in/alexkim'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
name: 'Maria Santos',
|
||||
role: 'Backend Engineer',
|
||||
bio: 'Database optimization expert and API architecture enthusiast.',
|
||||
image: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
github: 'https://github.com/mariasantos',
|
||||
linkedin: 'https://linkedin.com/in/mariasantos'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
name: 'James Wilson',
|
||||
role: 'DevOps Engineer',
|
||||
bio: 'Cloud infrastructure specialist ensuring reliable, scalable deployments.',
|
||||
image: 'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
github: 'https://github.com/jameswilson',
|
||||
linkedin: 'https://linkedin.com/in/jameswilson'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
name: 'Sophie Brown',
|
||||
role: 'QA Engineer',
|
||||
bio: 'Quality assurance expert dedicated to delivering bug-free user experiences.',
|
||||
image: 'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/sophiebrown',
|
||||
email: 'sophie@company.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
noBioConfig: TeamConfig = {
|
||||
title: 'Advisory Board',
|
||||
subtitle: 'Industry experts guiding our strategic direction',
|
||||
columns: 3,
|
||||
showSocial: true,
|
||||
showBio: false,
|
||||
members: [
|
||||
{
|
||||
id: '10',
|
||||
name: 'Robert Anderson',
|
||||
role: 'Strategic Advisor',
|
||||
image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/robertanderson'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
name: 'Jennifer Lee',
|
||||
role: 'Technology Advisor',
|
||||
image: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/jenniferlee',
|
||||
website: 'https://jenniferlee.tech'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '12',
|
||||
name: 'Marcus Johnson',
|
||||
role: 'Business Advisor',
|
||||
image: 'https://images.unsplash.com/photo-1556157382-97eda2d62296?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/marcusjohnson',
|
||||
email: 'marcus@advisor.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SaaSTemplateComponent } from '../../../../../ui-landing-pages/src/lib/components/templates/saas-template.component';
|
||||
import { ProductTemplateComponent } from '../../../../../ui-landing-pages/src/lib/components/templates/product-template.component';
|
||||
import { AgencyTemplateComponent } from '../../../../../ui-landing-pages/src/lib/components/templates/agency-template.component';
|
||||
import { AgencyTemplateConfig, ProductTemplateConfig, SaaSTemplateConfig } from '../../../../../ui-landing-pages/src/lib/interfaces/templates.interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-templates-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, SaaSTemplateComponent, ProductTemplateComponent, AgencyTemplateComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
<h1>Landing Page Templates Demo</h1>
|
||||
<p>Complete, production-ready landing page templates for different business types.</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-navigation">
|
||||
<button
|
||||
[class.active]="activeTemplate === 'saas'"
|
||||
(click)="setActiveTemplate('saas')">
|
||||
SaaS Template
|
||||
</button>
|
||||
<button
|
||||
[class.active]="activeTemplate === 'product'"
|
||||
(click)="setActiveTemplate('product')">
|
||||
Product Template
|
||||
</button>
|
||||
<button
|
||||
[class.active]="activeTemplate === 'agency'"
|
||||
(click)="setActiveTemplate('agency')">
|
||||
Agency Template
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="template-container">
|
||||
@switch (activeTemplate) {
|
||||
@case ('saas') {
|
||||
<ui-lp-saas-template [configuration]="saasConfig"></ui-lp-saas-template>
|
||||
}
|
||||
@case ('product') {
|
||||
<ui-lp-product-template [configuration]="productConfig"></ui-lp-product-template>
|
||||
}
|
||||
@case ('agency') {
|
||||
<ui-lp-agency-template [configuration]="agencyConfig"></ui-lp-agency-template>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
font-size: 1.125rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.demo-navigation {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
padding: 2rem;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.demo-navigation button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: 2px solid #6c757d;
|
||||
background: white;
|
||||
color: #6c757d;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.demo-navigation button:hover {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demo-navigation button.active {
|
||||
background: #007bff;
|
||||
border-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.template-container {
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-navigation {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.demo-navigation button {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class LandingTemplatesDemoComponent {
|
||||
activeTemplate: 'saas' | 'product' | 'agency' = 'saas';
|
||||
|
||||
setActiveTemplate(template: 'saas' | 'product' | 'agency'): void {
|
||||
this.activeTemplate = template;
|
||||
}
|
||||
|
||||
saasConfig: SaaSTemplateConfig = {
|
||||
hero: {
|
||||
title: 'Build Amazing SaaS Applications',
|
||||
subtitle: 'The complete toolkit for modern software development with enterprise-grade security and scalability.',
|
||||
ctaPrimary: {
|
||||
text: 'Start Free Trial',
|
||||
variant: 'tonal',
|
||||
size: 'large',
|
||||
action: () => console.log('Start trial clicked')
|
||||
},
|
||||
ctaSecondary: {
|
||||
text: 'View Demo',
|
||||
variant: 'outlined',
|
||||
size: 'large',
|
||||
action: () => console.log('View demo clicked')
|
||||
},
|
||||
alignment: 'center',
|
||||
backgroundType: 'gradient',
|
||||
minHeight: 'full'
|
||||
},
|
||||
features: {
|
||||
title: 'Why Choose Our Platform',
|
||||
subtitle: 'Everything you need to build, deploy, and scale your applications',
|
||||
features: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Lightning Fast Performance',
|
||||
description: 'Optimized architecture delivers sub-second response times and 99.99% uptime.',
|
||||
icon: 'rocket'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Enterprise Security',
|
||||
description: 'SOC 2 compliant with end-to-end encryption and advanced threat protection.',
|
||||
icon: 'shield'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Seamless Integrations',
|
||||
description: 'Connect with 100+ popular tools through our robust API and webhooks.',
|
||||
icon: 'plug'
|
||||
}
|
||||
]
|
||||
},
|
||||
socialProof: {
|
||||
title: 'Trusted by Industry Leaders',
|
||||
statistics: [
|
||||
{ id: '1', label: 'Active Users', value: '50,000+' },
|
||||
{ id: '2', label: 'Countries', value: '120+' },
|
||||
{ id: '3', label: 'Uptime', value: '99.99%' },
|
||||
{ id: '4', label: 'Support Rating', value: '4.9/5' }
|
||||
]
|
||||
},
|
||||
testimonials: {
|
||||
title: 'What Our Customers Say',
|
||||
testimonials: [
|
||||
{
|
||||
id: '1',
|
||||
content: 'This platform transformed our development workflow. We ship features 3x faster now.',
|
||||
name: 'Sarah Chen',
|
||||
title: 'CTO at TechCorp',
|
||||
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b5ff?w=100&h=100&fit=crop&crop=face'
|
||||
}
|
||||
]
|
||||
},
|
||||
pricing: {
|
||||
billingToggle: {
|
||||
monthlyLabel: 'Monthly',
|
||||
yearlyLabel: 'Yearly',
|
||||
discountText: 'Save 20%'
|
||||
},
|
||||
featuresComparison: false,
|
||||
plans: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Starter',
|
||||
price: {
|
||||
monthly: 29,
|
||||
yearly: 290,
|
||||
currency: 'USD',
|
||||
suffix: 'per user'
|
||||
},
|
||||
features: [
|
||||
{ name: '5 Projects', included: true },
|
||||
{ name: '10GB Storage', included: true },
|
||||
{ name: 'Email Support', included: true }
|
||||
],
|
||||
cta: {
|
||||
text: 'Get Started',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Starter plan selected')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
faq: {
|
||||
title: 'Frequently Asked Questions',
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
question: 'Can I cancel my subscription anytime?',
|
||||
answer: 'Yes, you can cancel your subscription at any time with no penalties.'
|
||||
}
|
||||
]
|
||||
},
|
||||
cta: {
|
||||
title: 'Ready to Get Started?',
|
||||
subtitle: 'Join thousands of developers building the future',
|
||||
ctaPrimary: {
|
||||
text: 'Start Free Trial',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Start trial clicked')
|
||||
},
|
||||
backgroundType: 'gradient'
|
||||
}
|
||||
};
|
||||
|
||||
productConfig: ProductTemplateConfig = {
|
||||
hero: {
|
||||
title: 'Introducing the Future of Productivity',
|
||||
subtitle: 'Revolutionary new product that will transform how you work and collaborate.',
|
||||
ctaPrimary: {
|
||||
text: 'Order Now',
|
||||
variant: 'tonal',
|
||||
size: 'large',
|
||||
action: () => console.log('Order now clicked')
|
||||
},
|
||||
ctaSecondary: {
|
||||
text: 'Learn More',
|
||||
variant: 'tonal',
|
||||
size: 'large',
|
||||
action: () => console.log('Learn more clicked')
|
||||
},
|
||||
alignment: 'left',
|
||||
backgroundType: 'image',
|
||||
minHeight: 'large'
|
||||
},
|
||||
features: {
|
||||
title: 'Key Features',
|
||||
subtitle: 'Designed with you in mind',
|
||||
features: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Intuitive Design',
|
||||
description: 'Beautiful interface that anyone can use without training.',
|
||||
icon: 'star'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Premium Materials',
|
||||
description: 'Built with the finest materials for durability and style.',
|
||||
icon: 'check'
|
||||
}
|
||||
]
|
||||
},
|
||||
testimonials: {
|
||||
title: 'Customer Reviews',
|
||||
testimonials: [
|
||||
{
|
||||
id: '1',
|
||||
content: 'This product exceeded all my expectations. Highly recommended!',
|
||||
name: 'Mike Johnson',
|
||||
title: 'Verified Customer',
|
||||
rating: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
pricing: {
|
||||
billingToggle: {
|
||||
monthlyLabel: 'One-time',
|
||||
yearlyLabel: 'Bundle',
|
||||
discountText: 'Best Value'
|
||||
},
|
||||
featuresComparison: false,
|
||||
plans: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Standard Edition',
|
||||
price: {
|
||||
monthly: 199,
|
||||
yearly: 199,
|
||||
currency: 'USD',
|
||||
suffix: 'one-time'
|
||||
},
|
||||
features: [
|
||||
{ name: '2-Year Warranty', included: true },
|
||||
{ name: 'Free Shipping', included: true }
|
||||
],
|
||||
cta: {
|
||||
text: 'Order Now',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Product ordered')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
cta: {
|
||||
title: 'Start Your Journey',
|
||||
subtitle: 'Order now and get free shipping worldwide',
|
||||
ctaPrimary: {
|
||||
text: 'Order Now',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Order now clicked')
|
||||
},
|
||||
backgroundType: 'gradient'
|
||||
}
|
||||
};
|
||||
|
||||
agencyConfig: AgencyTemplateConfig = {
|
||||
hero: {
|
||||
title: 'Your Vision, Our Expertise',
|
||||
subtitle: 'We create exceptional digital experiences that drive results for businesses of all sizes.',
|
||||
ctaPrimary: {
|
||||
text: 'Start Project',
|
||||
variant: 'tonal',
|
||||
size: 'large',
|
||||
action: () => console.log('Start project clicked')
|
||||
},
|
||||
ctaSecondary: {
|
||||
text: 'View Portfolio',
|
||||
variant: 'outlined',
|
||||
size: 'large',
|
||||
action: () => console.log('View portfolio clicked')
|
||||
},
|
||||
alignment: 'left',
|
||||
backgroundType: 'gradient',
|
||||
minHeight: 'large'
|
||||
},
|
||||
services: {
|
||||
title: 'Our Services',
|
||||
subtitle: 'Comprehensive solutions for your digital needs',
|
||||
features: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Web Development',
|
||||
description: 'Custom websites and web applications built with modern technologies.',
|
||||
icon: 'code'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'UI/UX Design',
|
||||
description: 'User-centered design that converts visitors into customers.',
|
||||
icon: 'palette'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Digital Marketing',
|
||||
description: 'Strategic campaigns that grow your online presence.',
|
||||
icon: 'megaphone'
|
||||
}
|
||||
]
|
||||
},
|
||||
team: {
|
||||
title: 'Meet Our Team',
|
||||
subtitle: 'The talented individuals behind our success',
|
||||
members: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Alex Rivera',
|
||||
role: 'Creative Director',
|
||||
bio: 'Award-winning designer with 10+ years of experience in digital creativity.',
|
||||
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop&crop=face',
|
||||
social: {
|
||||
linkedin: 'https://linkedin.com/in/alexrivera'
|
||||
}
|
||||
}
|
||||
],
|
||||
columns: 3,
|
||||
showSocial: true,
|
||||
showBio: true
|
||||
},
|
||||
timeline: {
|
||||
title: 'Our Journey',
|
||||
subtitle: 'Milestones that define our growth',
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Agency Founded',
|
||||
description: 'Started with a mission to help businesses succeed online.',
|
||||
date: '2018',
|
||||
status: 'completed',
|
||||
icon: 'rocket'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '100+ Happy Clients',
|
||||
description: 'Reached major milestone serving clients across industries.',
|
||||
date: '2020',
|
||||
status: 'completed',
|
||||
icon: 'star'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Award Recognition',
|
||||
description: 'Won Best Digital Agency award from Industry Association.',
|
||||
date: '2022',
|
||||
status: 'current',
|
||||
icon: 'check',
|
||||
badge: 'Latest'
|
||||
}
|
||||
],
|
||||
orientation: 'vertical',
|
||||
showDates: true
|
||||
},
|
||||
testimonials: {
|
||||
title: 'Client Success Stories',
|
||||
subtitle: 'What our partners say about working with us',
|
||||
testimonials: [
|
||||
{
|
||||
id: '1',
|
||||
content: 'Working with this agency was a game-changer for our business. They delivered beyond our expectations.',
|
||||
name: 'Emma Watson',
|
||||
title: 'CEO at StartupXYZ',
|
||||
avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face'
|
||||
}
|
||||
]
|
||||
},
|
||||
cta: {
|
||||
title: 'Ready to Start Your Project?',
|
||||
subtitle: 'Let\'s discuss how we can bring your vision to life',
|
||||
ctaPrimary: {
|
||||
text: 'Get Started',
|
||||
variant: 'filled',
|
||||
action: () => console.log('Get started clicked')
|
||||
},
|
||||
backgroundType: 'gradient'
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@use 'ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: $semantic-spacing-layout-section-xl;
|
||||
|
||||
&__title {
|
||||
font-family: map-get($semantic-typography-heading-h3, font-family);
|
||||
font-size: map-get($semantic-typography-heading-h3, font-size);
|
||||
font-weight: map-get($semantic-typography-heading-h3, font-weight);
|
||||
line-height: map-get($semantic-typography-heading-h3, line-height);
|
||||
color: $semantic-color-text-primary;
|
||||
margin-bottom: $semantic-spacing-content-heading;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-code {
|
||||
background: $semantic-color-surface-elevated;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
border-radius: $semantic-border-card-radius;
|
||||
padding: $semantic-spacing-component-lg;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: $semantic-color-text-primary;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
// Temporarily commented out until ui-landing-pages is built
|
||||
// import { TestimonialCarouselComponent, TestimonialCarouselConfig, TestimonialItem } from 'ui-landing-pages';
|
||||
|
||||
// Placeholder types
|
||||
interface TestimonialItem {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
company: string;
|
||||
content: string;
|
||||
rating: number;
|
||||
avatar: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
interface TestimonialCarouselConfig {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
testimonials: TestimonialItem[];
|
||||
autoPlay?: boolean;
|
||||
autoPlayDelay?: number;
|
||||
showDots?: boolean;
|
||||
showNavigation?: boolean;
|
||||
itemsPerView?: number;
|
||||
variant?: 'card' | 'quote' | 'minimal';
|
||||
showRatings?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-testimonials-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div style="padding: 2rem;">
|
||||
<h2>Testimonial Carousel Component</h2>
|
||||
<p style="margin-bottom: 2rem; color: #666;">Display customer testimonials with carousel navigation and various styles</p>
|
||||
|
||||
<div style="background: #f5f5f5; padding: 2rem; border-radius: 8px; text-align: center; color: #666;">
|
||||
<p><strong>Component Preview Unavailable</strong></p>
|
||||
<p>The TestimonialCarouselComponent will be available once the ui-landing-pages library is built.</p>
|
||||
<p>This demo will show:</p>
|
||||
<ul style="list-style: none; padding: 0; margin: 1rem 0;">
|
||||
<li>• Single Item Carousel</li>
|
||||
<li>• Multi-Item Carousel</li>
|
||||
<li>• Quote Variant</li>
|
||||
<li>• Minimal Variant</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Code -->
|
||||
<section class="demo-section" style="margin-top: 2rem;">
|
||||
<h3 class="demo-section__title">Configuration Example</h3>
|
||||
<pre class="demo-code" style="background: #f8f8f8; padding: 1rem; border-radius: 4px; overflow-x: auto;">{{ configExample }}</pre>
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './landing-testimonials-demo.component.scss'
|
||||
})
|
||||
export class LandingTestimonialsDemoComponent {
|
||||
private sampleTestimonials: TestimonialItem[] = [
|
||||
{
|
||||
id: 'sarah-chen',
|
||||
name: 'Sarah Chen',
|
||||
title: 'CTO',
|
||||
company: 'TechCorp',
|
||||
content: 'This platform transformed our development workflow. The intuitive design and powerful features helped us deliver projects 40% faster.',
|
||||
rating: 5,
|
||||
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b977?w=100&h=100&fit=crop&crop=face',
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 'marcus-rodriguez',
|
||||
name: 'Marcus Rodriguez',
|
||||
title: 'Lead Developer',
|
||||
company: 'StartupXYZ',
|
||||
content: 'Outstanding performance and reliability. The support team is incredibly responsive and knowledgeable.',
|
||||
rating: 5,
|
||||
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face',
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 'emily-watson',
|
||||
name: 'Emily Watson',
|
||||
title: 'Product Manager',
|
||||
company: 'InnovateLabs',
|
||||
content: 'The analytics features provided insights we never had before. Our team productivity increased significantly.',
|
||||
rating: 4,
|
||||
avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face',
|
||||
verified: false
|
||||
},
|
||||
{
|
||||
id: 'david-kim',
|
||||
name: 'David Kim',
|
||||
title: 'Software Engineer',
|
||||
company: 'CodeCrafters',
|
||||
content: 'Clean interface, excellent documentation, and seamless integration. Everything just works as expected.',
|
||||
rating: 5,
|
||||
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face',
|
||||
verified: true
|
||||
},
|
||||
{
|
||||
id: 'lisa-thompson',
|
||||
name: 'Lisa Thompson',
|
||||
title: 'Engineering Director',
|
||||
company: 'ScaleTech',
|
||||
content: 'Scalable solution that grows with our needs. The performance optimization features are game-changing.',
|
||||
rating: 5,
|
||||
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&h=100&fit=crop&crop=face',
|
||||
verified: true
|
||||
}
|
||||
];
|
||||
|
||||
singleConfig: TestimonialCarouselConfig = {
|
||||
title: 'What Our Customers Say',
|
||||
subtitle: 'Trusted by thousands of developers worldwide',
|
||||
testimonials: this.sampleTestimonials,
|
||||
autoPlay: true,
|
||||
autoPlayDelay: 5000,
|
||||
showDots: true,
|
||||
showNavigation: true,
|
||||
itemsPerView: 1,
|
||||
variant: 'card',
|
||||
showRatings: true
|
||||
};
|
||||
|
||||
multiConfig: TestimonialCarouselConfig = {
|
||||
title: 'Customer Success Stories',
|
||||
testimonials: this.sampleTestimonials,
|
||||
autoPlay: false,
|
||||
showDots: true,
|
||||
showNavigation: true,
|
||||
itemsPerView: 2,
|
||||
variant: 'card',
|
||||
showRatings: true
|
||||
};
|
||||
|
||||
quoteConfig: TestimonialCarouselConfig = {
|
||||
title: 'Featured Reviews',
|
||||
testimonials: this.sampleTestimonials.slice(0, 3),
|
||||
autoPlay: true,
|
||||
autoPlayDelay: 6000,
|
||||
showDots: true,
|
||||
showNavigation: false,
|
||||
itemsPerView: 1,
|
||||
variant: 'quote',
|
||||
showRatings: true
|
||||
};
|
||||
|
||||
minimalConfig: TestimonialCarouselConfig = {
|
||||
testimonials: this.sampleTestimonials.slice(0, 3),
|
||||
autoPlay: false,
|
||||
showDots: true,
|
||||
showNavigation: true,
|
||||
itemsPerView: 1,
|
||||
variant: 'minimal',
|
||||
showRatings: false
|
||||
};
|
||||
|
||||
configExample = `const testimonialConfig: TestimonialCarouselConfig = {
|
||||
title: 'What Our Customers Say',
|
||||
subtitle: 'Trusted by thousands of developers worldwide',
|
||||
testimonials: [
|
||||
{
|
||||
id: 'sarah-chen',
|
||||
name: 'Sarah Chen',
|
||||
title: 'CTO',
|
||||
company: 'TechCorp',
|
||||
content: 'This platform transformed our workflow...',
|
||||
rating: 5,
|
||||
avatar: 'path/to/avatar.jpg',
|
||||
verified: true
|
||||
}
|
||||
// ... more testimonials
|
||||
],
|
||||
autoPlay: true,
|
||||
autoPlayDelay: 5000,
|
||||
showDots: true,
|
||||
showNavigation: true,
|
||||
itemsPerView: 1,
|
||||
variant: 'card',
|
||||
showRatings: true
|
||||
};`;
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TimelineConfig, TimelineSectionComponent } from "../../../../../ui-landing-pages/src/public-api";
|
||||
|
||||
@Component({
|
||||
selector: 'app-landing-timeline-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, TimelineSectionComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
<h1>Timeline Demo</h1>
|
||||
<p>Visual timeline components for roadmaps, company history, and project milestones.</p>
|
||||
</div>
|
||||
|
||||
<!-- Vertical Timeline -->
|
||||
<div class="demo-section">
|
||||
<h2>Vertical Timeline (Default)</h2>
|
||||
<ui-lp-timeline [configuration]="verticalConfig"></ui-lp-timeline>
|
||||
</div>
|
||||
|
||||
<!-- Horizontal Timeline -->
|
||||
<div class="demo-section">
|
||||
<h2>Horizontal Timeline</h2>
|
||||
<ui-lp-timeline [configuration]="horizontalConfig"></ui-lp-timeline>
|
||||
</div>
|
||||
|
||||
<!-- Minimal Theme -->
|
||||
<div class="demo-section">
|
||||
<h2>Minimal Theme</h2>
|
||||
<ui-lp-timeline [configuration]="minimalConfig"></ui-lp-timeline>
|
||||
</div>
|
||||
|
||||
<!-- Connected Theme -->
|
||||
<div class="demo-section">
|
||||
<h2>Connected Theme</h2>
|
||||
<ui-lp-timeline [configuration]="connectedConfig"></ui-lp-timeline>
|
||||
</div>
|
||||
|
||||
<!-- Product Roadmap -->
|
||||
<div class="demo-section">
|
||||
<h2>Product Roadmap</h2>
|
||||
<ui-lp-timeline [configuration]="roadmapConfig"></ui-lp-timeline>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.demo-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
font-size: 1.125rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class LandingTimelineDemoComponent {
|
||||
verticalConfig: TimelineConfig = {
|
||||
title: 'Our Company Journey',
|
||||
subtitle: 'Key milestones that shaped our growth',
|
||||
orientation: 'vertical',
|
||||
theme: 'default',
|
||||
showDates: true,
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Company Founded',
|
||||
description: 'Started with a vision to revolutionize how teams collaborate and build software together.',
|
||||
date: 'January 2020',
|
||||
status: 'completed',
|
||||
icon: 'rocket'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'First Product Launch',
|
||||
description: 'Launched our MVP with core features that solve real problems for development teams.',
|
||||
date: 'June 2020',
|
||||
status: 'completed',
|
||||
icon: 'star'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Series A Funding',
|
||||
description: 'Secured $5M in Series A funding to accelerate product development and team growth.',
|
||||
date: 'March 2021',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '10,000 Active Users',
|
||||
description: 'Reached a major milestone with 10,000+ active users across 50+ countries worldwide.',
|
||||
date: 'September 2021',
|
||||
status: 'completed',
|
||||
icon: 'star'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Major Platform Update',
|
||||
description: 'Released version 2.0 with advanced automation, better performance, and new integrations.',
|
||||
date: 'December 2022',
|
||||
status: 'current',
|
||||
icon: 'rocket',
|
||||
badge: 'Current'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'International Expansion',
|
||||
description: 'Planning to open offices in Europe and Asia to better serve our global customer base.',
|
||||
date: 'Q2 2024',
|
||||
status: 'upcoming'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
horizontalConfig: TimelineConfig = {
|
||||
title: 'Development Process',
|
||||
subtitle: 'Our structured approach to building great products',
|
||||
orientation: 'horizontal',
|
||||
theme: 'default',
|
||||
showDates: false,
|
||||
items: [
|
||||
{
|
||||
id: '7',
|
||||
title: 'Research & Discovery',
|
||||
description: 'Understanding user needs through interviews, surveys, and market analysis.',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
title: 'Design & Prototyping',
|
||||
description: 'Creating wireframes, mockups, and interactive prototypes for validation.',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
title: 'Development',
|
||||
description: 'Building features with clean code, automated testing, and continuous integration.',
|
||||
status: 'current',
|
||||
icon: 'rocket'
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
title: 'Testing & QA',
|
||||
description: 'Comprehensive testing across devices, browsers, and user scenarios.',
|
||||
status: 'upcoming'
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
title: 'Launch & Monitor',
|
||||
description: 'Deploying to production and monitoring performance and user feedback.',
|
||||
status: 'upcoming'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
minimalConfig: TimelineConfig = {
|
||||
title: 'Project Milestones',
|
||||
subtitle: 'Clean and simple timeline presentation',
|
||||
orientation: 'vertical',
|
||||
theme: 'minimal',
|
||||
showDates: true,
|
||||
items: [
|
||||
{
|
||||
id: '12',
|
||||
title: 'Project Kickoff',
|
||||
description: 'Initial team meeting and project scope definition.',
|
||||
date: 'Week 1',
|
||||
status: 'completed'
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
title: 'Requirements Gathering',
|
||||
description: 'Detailed analysis of user requirements and technical specifications.',
|
||||
date: 'Week 2-3',
|
||||
status: 'completed'
|
||||
},
|
||||
{
|
||||
id: '14',
|
||||
title: 'Architecture Planning',
|
||||
description: 'System design and technology stack selection.',
|
||||
date: 'Week 4',
|
||||
status: 'current'
|
||||
},
|
||||
{
|
||||
id: '15',
|
||||
title: 'Implementation',
|
||||
description: 'Core development phase with iterative releases.',
|
||||
date: 'Week 5-12',
|
||||
status: 'upcoming'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
connectedConfig: TimelineConfig = {
|
||||
title: 'Feature Evolution',
|
||||
subtitle: 'How our features have evolved over time',
|
||||
orientation: 'vertical',
|
||||
theme: 'connected',
|
||||
showDates: true,
|
||||
items: [
|
||||
{
|
||||
id: '16',
|
||||
title: 'Basic Dashboard',
|
||||
description: 'Simple dashboard with essential metrics and basic user management.',
|
||||
date: 'v1.0',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '17',
|
||||
title: 'Advanced Analytics',
|
||||
description: 'Added detailed analytics, custom reports, and data visualization tools.',
|
||||
date: 'v1.5',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '18',
|
||||
title: 'Team Collaboration',
|
||||
description: 'Introduced real-time collaboration features, comments, and notification system.',
|
||||
date: 'v2.0',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '19',
|
||||
title: 'AI-Powered Insights',
|
||||
description: 'Implementing machine learning algorithms for predictive analytics and smart recommendations.',
|
||||
date: 'v2.5',
|
||||
status: 'current',
|
||||
icon: 'rocket',
|
||||
badge: 'In Progress'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
roadmapConfig: TimelineConfig = {
|
||||
title: '2024 Product Roadmap',
|
||||
subtitle: 'Exciting features and improvements coming soon',
|
||||
orientation: 'horizontal',
|
||||
theme: 'default',
|
||||
showDates: true,
|
||||
items: [
|
||||
{
|
||||
id: '20',
|
||||
title: 'Mobile App',
|
||||
description: 'Native iOS and Android applications with offline support.',
|
||||
date: 'Q1 2024',
|
||||
status: 'completed',
|
||||
icon: 'check'
|
||||
},
|
||||
{
|
||||
id: '21',
|
||||
title: 'API v3',
|
||||
description: 'Complete API overhaul with GraphQL support and improved performance.',
|
||||
date: 'Q2 2024',
|
||||
status: 'current',
|
||||
icon: 'rocket'
|
||||
},
|
||||
{
|
||||
id: '22',
|
||||
title: 'Enterprise Features',
|
||||
description: 'SSO, advanced permissions, audit logs, and compliance tools.',
|
||||
date: 'Q3 2024',
|
||||
status: 'upcoming',
|
||||
badge: 'Coming Soon'
|
||||
},
|
||||
{
|
||||
id: '23',
|
||||
title: 'Global Expansion',
|
||||
description: 'Multi-language support, regional data centers, and local partnerships.',
|
||||
date: 'Q4 2024',
|
||||
status: 'upcoming'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../../../../ui-design-system/src/styles/semantic/index" as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-section-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../../../../ui-design-system/src/styles/tokens" as *;
|
||||
@use 'ui-design-system/src/styles/tokens' as *;
|
||||
|
||||
// Tokens available globally via main application styles
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as tokens;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as tokens;
|
||||
|
||||
.demo-container {
|
||||
padding: tokens.$semantic-spacing-layout-md;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../../../../ui-design-system/src/styles/semantic/index" as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.popover-demo {
|
||||
padding: $semantic-spacing-layout-section-xs;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-layout-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic/index' as *;
|
||||
@use 'ui-design-system/src/styles/semantic/index' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user