Add comprehensive library expansion with new components and demos

- Add new libraries: ui-accessibility, ui-animations, ui-backgrounds, ui-code-display, ui-data-utils, ui-font-manager, hcl-studio
- Add extensive layout components: gallery-grid, infinite-scroll-container, kanban-board, masonry, split-view, sticky-layout
- Add comprehensive demo components for all new features
- Update project configuration and dependencies
- Expand component exports and routing structure
- Add UI landing pages planning document

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
skyai_dev
2025-09-05 05:37:37 +10:00
parent 876eb301a0
commit 5346d6d0c9
5476 changed files with 350855 additions and 10 deletions

View File

@@ -0,0 +1,66 @@
import { Directive, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import { UiAnimationsService } from './ui-animations.service';
@Directive({
selector: '[uiAnimate]',
standalone: true
})
export class AnimateDirective implements OnInit, OnDestroy {
@Input('uiAnimate') animationClass: string = '';
@Input() animationTrigger: 'immediate' | 'hover' | 'click' | 'manual' = 'immediate';
@Input() animationOnce: boolean = false;
private hoverHandler?: () => void;
private clickHandler?: () => void;
constructor(
private elementRef: ElementRef<HTMLElement>,
private animationService: UiAnimationsService
) {}
ngOnInit() {
if (this.animationTrigger === 'immediate') {
this.animate();
} else if (this.animationTrigger === 'hover') {
this.setupHoverTrigger();
} else if (this.animationTrigger === 'click') {
this.setupClickTrigger();
}
}
ngOnDestroy() {
if (this.hoverHandler) {
this.elementRef.nativeElement.removeEventListener('mouseenter', this.hoverHandler);
}
if (this.clickHandler) {
this.elementRef.nativeElement.removeEventListener('click', this.clickHandler);
}
}
private setupHoverTrigger() {
this.hoverHandler = () => this.animate();
this.elementRef.nativeElement.addEventListener('mouseenter', this.hoverHandler);
}
private setupClickTrigger() {
this.clickHandler = () => this.animate();
this.elementRef.nativeElement.addEventListener('click', this.clickHandler);
}
private animate() {
if (!this.animationClass) return;
if (this.animationOnce) {
this.animationService.animateOnce(this.elementRef.nativeElement, this.animationClass);
} else {
this.animationService.animate(this.elementRef.nativeElement, this.animationClass);
}
}
/**
* Manually trigger animation (for manual trigger mode)
*/
public trigger() {
this.animate();
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UiAnimationsService } from './ui-animations.service';
describe('UiAnimationsService', () => {
let service: UiAnimationsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UiAnimationsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UiAnimationsService {
/**
* Add animation class to element with optional completion callback
*/
animate(element: HTMLElement, animationClass: string, onComplete?: () => void): Promise<void> {
return new Promise((resolve) => {
const handleAnimationEnd = () => {
element.removeEventListener('animationend', handleAnimationEnd);
if (onComplete) onComplete();
resolve();
};
element.addEventListener('animationend', handleAnimationEnd);
element.classList.add(animationClass);
});
}
/**
* Remove animation class from element
*/
removeAnimation(element: HTMLElement, animationClass: string): void {
element.classList.remove(animationClass);
}
/**
* Add animation class and automatically remove it after completion
*/
animateOnce(element: HTMLElement, animationClass: string): Promise<void> {
return this.animate(element, animationClass, () => {
this.removeAnimation(element, animationClass);
});
}
/**
* Check if element has animation class
*/
hasAnimation(element: HTMLElement, animationClass: string): boolean {
return element.classList.contains(animationClass);
}
}

View File

@@ -0,0 +1,6 @@
/*
* Public API Surface of ui-animations
*/
export * from './lib/ui-animations.service';
export * from './lib/animate.directive';

View File

@@ -0,0 +1,142 @@
// Emphasis Animations
// Attention-grabbing animations for highlighting elements
// Bounce
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
transform: translateY(0);
}
40%, 43% {
transform: translateY(-24px);
}
70% {
transform: translateY(-8px);
}
90% {
transform: translateY(-4px);
}
}
.animate-bounce {
animation: bounce 0.6s ease-in-out;
}
// Shake
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
20%, 40%, 60%, 80% { transform: translateX(4px); }
}
.animate-shake {
animation: shake 0.6s ease-in-out;
}
// Pulse
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.animate-pulse {
animation: pulse 0.6s ease-in-out infinite;
}
// Flash
@keyframes flash {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: 0; }
}
.animate-flash {
animation: flash 0.6s ease-in-out;
}
// Wobble
@keyframes wobble {
0% { transform: translateX(0%); }
15% { transform: translateX(-25%) rotate(-5deg); }
30% { transform: translateX(20%) rotate(3deg); }
45% { transform: translateX(-15%) rotate(-3deg); }
60% { transform: translateX(10%) rotate(2deg); }
75% { transform: translateX(-5%) rotate(-1deg); }
100% { transform: translateX(0%); }
}
.animate-wobble {
animation: wobble 0.6s ease-in-out;
}
// Swing
@keyframes swing {
20% { transform: rotate(15deg); }
40% { transform: rotate(-10deg); }
60% { transform: rotate(5deg); }
80% { transform: rotate(-5deg); }
100% { transform: rotate(0deg); }
}
.animate-swing {
animation: swing 0.6s ease-in-out;
transform-origin: top center;
}
// Rubber Band
@keyframes rubberBand {
0% { transform: scaleX(1); }
30% { transform: scaleX(1.25) scaleY(0.75); }
40% { transform: scaleX(0.75) scaleY(1.25); }
50% { transform: scaleX(1.15) scaleY(0.85); }
65% { transform: scaleX(0.95) scaleY(1.05); }
75% { transform: scaleX(1.05) scaleY(0.95); }
100% { transform: scaleX(1) scaleY(1); }
}
.animate-rubber-band {
animation: rubberBand 0.6s ease-in-out;
}
// Tada
@keyframes tada {
0% { transform: scaleX(1) scaleY(1); }
10%, 20% { transform: scaleX(0.9) scaleY(0.9) rotate(-3deg); }
30%, 50%, 70%, 90% { transform: scaleX(1.1) scaleY(1.1) rotate(3deg); }
40%, 60%, 80% { transform: scaleX(1.1) scaleY(1.1) rotate(-3deg); }
100% { transform: scaleX(1) scaleY(1) rotate(0); }
}
.animate-tada {
animation: tada 0.6s ease-in-out;
}
// Heartbeat
@keyframes heartbeat {
0% { transform: scale(1); }
14% { transform: scale(1.3); }
28% { transform: scale(1); }
42% { transform: scale(1.3); }
70% { transform: scale(1); }
}
.animate-heartbeat {
animation: heartbeat 0.6s ease-in-out infinite;
}
// Jello
@keyframes jello {
11.1% { transform: skewX(-12.5deg) skewY(-12.5deg); }
22.2% { transform: skewX(6.25deg) skewY(6.25deg); }
33.3% { transform: skewX(-3.125deg) skewY(-3.125deg); }
44.4% { transform: skewX(1.5625deg) skewY(1.5625deg); }
55.5% { transform: skewX(-0.78125deg) skewY(-0.78125deg); }
66.6% { transform: skewX(0.390625deg) skewY(0.390625deg); }
77.7% { transform: skewX(-0.1953125deg) skewY(-0.1953125deg); }
88.8% { transform: skewX(0.09765625deg) skewY(0.09765625deg); }
100% { transform: skewX(0) skewY(0); }
}
.animate-jello {
animation: jello 0.6s ease-in-out;
transform-origin: center;
}

View File

@@ -0,0 +1,151 @@
// Entrance Animations
// Elements appearing/entering the view
// Fade In
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out;
}
// Fade In Up
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.3s ease-out;
}
// Fade In Down
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-down {
animation: fadeInDown 0.3s ease-out;
}
// Fade In Left
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(-24px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-fade-in-left {
animation: fadeInLeft 0.3s ease-out;
}
// Fade In Right
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(24px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-fade-in-right {
animation: fadeInRight 0.3s ease-out;
}
// Slide In Up
@keyframes slideInUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.animate-slide-in-up {
animation: slideInUp 0.3s ease-out;
}
// Slide In Down
@keyframes slideInDown {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
.animate-slide-in-down {
animation: slideInDown 0.3s ease-out;
}
// Zoom In
@keyframes zoomIn {
from {
opacity: 0;
transform: scale(0.3);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-zoom-in {
animation: zoomIn 0.3s ease-out;
}
// Scale Up
@keyframes scaleUp {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
.animate-scale-up {
animation: scaleUp 0.15s ease-out;
}
// Rotate In
@keyframes rotateIn {
from {
opacity: 0;
transform: rotate(-200deg);
}
to {
opacity: 1;
transform: rotate(0);
}
}
.animate-rotate-in {
animation: rotateIn 0.3s ease-out;
}

View File

@@ -0,0 +1,151 @@
// Exit Animations
// Elements disappearing/leaving the view
// Fade Out
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.animate-fade-out {
animation: fadeOut 0.3s ease-in;
}
// Fade Out Up
@keyframes fadeOutUp {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-24px);
}
}
.animate-fade-out-up {
animation: fadeOutUp 0.3s ease-in;
}
// Fade Out Down
@keyframes fadeOutDown {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(24px);
}
}
.animate-fade-out-down {
animation: fadeOutDown 0.3s ease-in;
}
// Fade Out Left
@keyframes fadeOutLeft {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-24px);
}
}
.animate-fade-out-left {
animation: fadeOutLeft 0.3s ease-in;
}
// Fade Out Right
@keyframes fadeOutRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(24px);
}
}
.animate-fade-out-right {
animation: fadeOutRight 0.3s ease-in;
}
// Slide Out Up
@keyframes slideOutUp {
from {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
.animate-slide-out-up {
animation: slideOutUp 0.3s ease-in;
}
// Slide Out Down
@keyframes slideOutDown {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
.animate-slide-out-down {
animation: slideOutDown 0.3s ease-in;
}
// Zoom Out
@keyframes zoomOut {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.3);
}
}
.animate-zoom-out {
animation: zoomOut 0.3s ease-in;
}
// Scale Down
@keyframes scaleDown {
from {
transform: scale(1);
}
to {
transform: scale(0);
}
}
.animate-scale-down {
animation: scaleDown 0.15s ease-in;
}
// Rotate Out
@keyframes rotateOut {
from {
opacity: 1;
transform: rotate(0);
}
to {
opacity: 0;
transform: rotate(200deg);
}
}
.animate-rotate-out {
animation: rotateOut 0.3s ease-in;
}

View File

@@ -0,0 +1,53 @@
// Base Transitions
// Smooth transitions for common properties
// Transition utilities
.transition-all {
transition: all 0.3s ease;
}
.transition-opacity {
transition: opacity 0.3s ease;
}
.transition-transform {
transition: transform 0.3s ease;
}
.transition-colors {
transition: color, background-color, border-color 0.3s ease;
}
// Transition durations
.transition-fast {
transition-duration: 0.15s;
}
.transition-normal {
transition-duration: 0.3s;
}
.transition-slow {
transition-duration: 0.6s;
}
// Transition timing functions
.transition-linear {
transition-timing-function: linear;
}
.transition-ease-in {
transition-timing-function: ease-in;
}
.transition-ease-out {
transition-timing-function: ease-out;
}
.transition-ease-in-out {
transition-timing-function: ease-in-out;
}
.transition-bounce {
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

View File

@@ -0,0 +1,21 @@
// UI Animations - Main Entry Point
// Import all animation styles
// Animations
@forward 'animations/transitions';
@forward 'animations/entrances';
@forward 'animations/exits';
@forward 'animations/emphasis';
// Utilities
@forward 'utilities/animation-utilities';
// Mixins (use @use to access mixins in your components)
@forward 'mixins/animation-mixins';
// Export all animations and utilities
@use 'animations/transitions';
@use 'animations/entrances';
@use 'animations/exits';
@use 'animations/emphasis';
@use 'utilities/animation-utilities';

View File

@@ -0,0 +1,130 @@
// Animation Mixins
// Reusable SCSS mixins for creating custom animations
// Generic animation mixin
@mixin animate(
$name,
$duration: 0.3s,
$timing-function: ease-out,
$delay: motion.$semantic-motion-transition-delay-0,
$iteration-count: 1,
$direction: normal,
$fill-mode: both
) {
animation-name: $name;
animation-duration: $duration;
animation-timing-function: $timing-function;
animation-delay: $delay;
animation-iteration-count: $iteration-count;
animation-direction: $direction;
animation-fill-mode: $fill-mode;
}
// Transition mixin with design system tokens
@mixin transition(
$property: motion.$semantic-motion-transition-property-all,
$duration: 0.3s,
$timing-function: ease,
$delay: motion.$semantic-motion-transition-delay-0
) {
transition-property: $property;
transition-duration: $duration;
transition-timing-function: $timing-function;
transition-delay: $delay;
}
// Fade animation mixin
@mixin fade-animation($direction: 'in', $distance: 24px) {
@if $direction == 'in' {
from {
opacity: 0;
transform: translateY($distance);
}
to {
opacity: 1;
transform: translateY(0);
}
} @else {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-#{$distance});
}
}
}
// Slide animation mixin
@mixin slide-animation($direction: 'up', $distance: 100%) {
@if $direction == 'up' {
from { transform: translateY($distance); }
to { transform: translateY(0); }
} @else if $direction == 'down' {
from { transform: translateY(-$distance); }
to { transform: translateY(0); }
} @else if $direction == 'left' {
from { transform: translateX($distance); }
to { transform: translateX(0); }
} @else if $direction == 'right' {
from { transform: translateX(-$distance); }
to { transform: translateX(0); }
}
}
// Scale animation mixin
@mixin scale-animation($from: 0, $to: 1) {
from {
transform: scale($from);
}
to {
transform: scale($to);
}
}
// Rotate animation mixin
@mixin rotate-animation($from: 0deg, $to: 360deg) {
from {
transform: rotate($from);
}
to {
transform: rotate($to);
}
}
// Hover animation mixin
@mixin hover-animation($property: transform, $value: scale(1.05), $duration: 0.15s) {
transition: $property $duration ease;
&:hover {
#{$property}: $value;
}
}
// Loading animation mixin
@mixin loading-animation($size: 40px, $color: currentColor) {
width: $size;
height: $size;
border: 3px solid rgba($color, 0.3);
border-radius: 50%;
border-top-color: $color;
animation: spin 0.6sest ease-in-out infinite;
}
// Keyframes for common animations
@keyframes spin {
to { transform: rotate(360deg); }
}
// Staggered animation mixin for lists
@mixin staggered-animation($base-delay: motion.$semantic-motion-transition-delay-100, $animation: fadeInUp 0.3s ease-out) {
@for $i from 1 through 10 {
&:nth-child(#{$i}) {
animation: $animation;
animation-delay: #{$base-delay * $i};
animation-fill-mode: both;
}
}
}

View File

@@ -0,0 +1,118 @@
// Animation Utilities
// Helper classes for animation control
// Animation delays
.animation-delay-100 {
animation-delay: 0.1s;
}
.animation-delay-200 {
animation-delay: 0.2s;
}
.animation-delay-300 {
animation-delay: 0.3s;
}
.animation-delay-500 {
animation-delay: 0.5s;
}
.animation-delay-1000 {
animation-delay: 1s;
}
// Animation durations
.animation-duration-fast {
animation-duration: 0.15s;
}
.animation-duration-normal {
animation-duration: 0.3s;
}
.animation-duration-slow {
animation-duration: 0.6s;
}
.animation-duration-slower {
animation-duration: 1s;
}
// Animation fill modes
.animation-fill-none {
animation-fill-mode: none;
}
.animation-fill-forwards {
animation-fill-mode: forwards;
}
.animation-fill-backwards {
animation-fill-mode: backwards;
}
.animation-fill-both {
animation-fill-mode: both;
}
// Animation iteration counts
.animation-infinite {
animation-iteration-count: infinite;
}
.animation-once {
animation-iteration-count: 1;
}
.animation-twice {
animation-iteration-count: 2;
}
// Animation play states
.animation-paused {
animation-play-state: paused;
}
.animation-running {
animation-play-state: running;
}
// Animation directions
.animation-normal {
animation-direction: normal;
}
.animation-reverse {
animation-direction: reverse;
}
.animation-alternate {
animation-direction: alternate;
}
.animation-alternate-reverse {
animation-direction: alternate-reverse;
}
// Animation timing functions
.animation-linear {
animation-timing-function: linear;
}
.animation-ease-in {
animation-timing-function: ease-in;
}
.animation-ease-out {
animation-timing-function: ease-out;
}
.animation-ease-in-out {
animation-timing-function: ease-in-out;
}
.animation-bounce-timing {
animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}