Add comprehensive library expansion with new components and demos
- Add new libraries: ui-accessibility, ui-animations, ui-backgrounds, ui-code-display, ui-data-utils, ui-font-manager, hcl-studio - Add extensive layout components: gallery-grid, infinite-scroll-container, kanban-board, masonry, split-view, sticky-layout - Add comprehensive demo components for all new features - Update project configuration and dependencies - Expand component exports and routing structure - Add UI landing pages planning document 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
@use '../../../../../ui-design-system/src/styles/semantic' as *;
|
||||
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: $semantic-spacing-layout-section-xl;
|
||||
|
||||
h3 {
|
||||
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-component-md;
|
||||
border-bottom: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
padding-bottom: $semantic-spacing-component-sm;
|
||||
}
|
||||
|
||||
h4 {
|
||||
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-secondary;
|
||||
margin-bottom: $semantic-spacing-component-sm;
|
||||
}
|
||||
|
||||
p {
|
||||
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;
|
||||
margin-bottom: $semantic-spacing-component-md;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: $semantic-spacing-grid-gap-lg;
|
||||
margin-bottom: $semantic-spacing-layout-section-md;
|
||||
}
|
||||
|
||||
.demo-item {
|
||||
padding: $semantic-spacing-component-md;
|
||||
background: $semantic-color-surface-secondary;
|
||||
border-radius: $semantic-border-radius-md;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $semantic-spacing-component-sm;
|
||||
margin-bottom: $semantic-spacing-component-lg;
|
||||
padding: $semantic-spacing-component-md;
|
||||
background: $semantic-color-surface-elevated;
|
||||
border-radius: $semantic-border-radius-md;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
|
||||
button {
|
||||
padding: $semantic-spacing-interactive-button-padding-y $semantic-spacing-interactive-button-padding-x;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-primary;
|
||||
border-radius: $semantic-border-radius-sm;
|
||||
background: $semantic-color-surface-primary;
|
||||
color: $semantic-color-text-primary;
|
||||
cursor: pointer;
|
||||
transition: all $semantic-motion-duration-fast $semantic-motion-easing-ease;
|
||||
|
||||
font-family: map-get($semantic-typography-button-medium, font-family);
|
||||
font-size: map-get($semantic-typography-button-medium, font-size);
|
||||
font-weight: map-get($semantic-typography-button-medium, font-weight);
|
||||
line-height: map-get($semantic-typography-button-medium, line-height);
|
||||
|
||||
&:hover {
|
||||
background: $semantic-color-surface-elevated;
|
||||
box-shadow: $semantic-shadow-button-hover;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $semantic-color-focus;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $semantic-color-primary;
|
||||
color: $semantic-color-on-primary;
|
||||
border-color: $semantic-color-primary;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: $semantic-opacity-disabled;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selection-info {
|
||||
margin-top: $semantic-spacing-component-lg;
|
||||
padding: $semantic-spacing-component-md;
|
||||
background: $semantic-color-surface-elevated;
|
||||
border-radius: $semantic-border-radius-md;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
|
||||
p {
|
||||
font-weight: $semantic-typography-font-weight-semibold;
|
||||
margin-bottom: $semantic-spacing-component-sm;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
padding: $semantic-spacing-content-line-tight 0;
|
||||
border-bottom: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
font-family: map-get($semantic-typography-body-small, font-family);
|
||||
font-size: map-get($semantic-typography-body-small, font-size);
|
||||
font-weight: map-get($semantic-typography-body-small, font-weight);
|
||||
line-height: map-get($semantic-typography-body-small, line-height);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.event-log {
|
||||
background: $semantic-color-surface-elevated;
|
||||
border-radius: $semantic-border-radius-md;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
overflow: hidden;
|
||||
|
||||
.event-controls {
|
||||
padding: $semantic-spacing-component-md;
|
||||
border-bottom: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
background: $semantic-color-surface-secondary;
|
||||
|
||||
button {
|
||||
padding: $semantic-spacing-interactive-button-padding-y $semantic-spacing-interactive-button-padding-x;
|
||||
border: $semantic-border-width-1 solid $semantic-color-border-primary;
|
||||
border-radius: $semantic-border-radius-sm;
|
||||
background: $semantic-color-surface-primary;
|
||||
color: $semantic-color-text-primary;
|
||||
cursor: pointer;
|
||||
transition: all $semantic-motion-duration-fast $semantic-motion-easing-ease;
|
||||
|
||||
font-family: map-get($semantic-typography-button-small, font-family);
|
||||
font-size: map-get($semantic-typography-button-small, font-size);
|
||||
font-weight: map-get($semantic-typography-button-small, font-weight);
|
||||
line-height: map-get($semantic-typography-button-small, line-height);
|
||||
|
||||
&:hover {
|
||||
background: $semantic-color-surface-elevated;
|
||||
box-shadow: $semantic-shadow-button-hover;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $semantic-color-focus;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.event-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
.no-events {
|
||||
padding: $semantic-spacing-component-lg;
|
||||
text-align: center;
|
||||
color: $semantic-color-text-tertiary;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.event-item {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr auto;
|
||||
gap: $semantic-spacing-component-sm;
|
||||
padding: $semantic-spacing-component-sm $semantic-spacing-component-md;
|
||||
border-bottom: $semantic-border-width-1 solid $semantic-color-border-subtle;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
font-weight: $semantic-typography-font-weight-semibold;
|
||||
color: $semantic-color-primary;
|
||||
font-family: map-get($semantic-typography-body-small, font-family);
|
||||
font-size: map-get($semantic-typography-body-small, font-size);
|
||||
line-height: map-get($semantic-typography-body-small, line-height);
|
||||
}
|
||||
|
||||
.event-details {
|
||||
color: $semantic-color-text-primary;
|
||||
font-family: map-get($semantic-typography-body-small, font-family);
|
||||
font-size: map-get($semantic-typography-body-small, font-size);
|
||||
font-weight: map-get($semantic-typography-body-small, font-weight);
|
||||
line-height: map-get($semantic-typography-body-small, line-height);
|
||||
}
|
||||
|
||||
.event-time {
|
||||
color: $semantic-color-text-tertiary;
|
||||
font-family: map-get($semantic-typography-body-small, font-family);
|
||||
font-size: map-get($semantic-typography-body-small, font-size);
|
||||
font-weight: map-get($semantic-typography-body-small, font-weight);
|
||||
line-height: map-get($semantic-typography-body-small, line-height);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive Design
|
||||
@media (max-width: $semantic-breakpoint-md - 1) {
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-md;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $semantic-spacing-grid-gap-md;
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.event-item {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $semantic-spacing-content-line-tight;
|
||||
|
||||
.event-time {
|
||||
justify-self: end;
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.event-details {
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $semantic-breakpoint-sm - 1) {
|
||||
.demo-container {
|
||||
padding: $semantic-spacing-component-sm;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: $semantic-spacing-layout-section-md;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { GalleryGridComponent, GalleryGridItem } from 'ui-essentials';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-gallery-grid-demo',
|
||||
standalone: true,
|
||||
imports: [CommonModule, GalleryGridComponent],
|
||||
template: `
|
||||
<div class="demo-container">
|
||||
<h2>Gallery Grid Demo</h2>
|
||||
<p>A responsive media grid with lightbox functionality for organizing and displaying images.</p>
|
||||
|
||||
<!-- Column Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Column Variants</h3>
|
||||
<div class="demo-grid">
|
||||
@for (columnCount of columnVariants; track columnCount) {
|
||||
<div class="demo-item">
|
||||
<h4>{{ columnCount }} Columns</h4>
|
||||
<ui-gallery-grid
|
||||
[items]="sampleItems.slice(0, 6)"
|
||||
[columns]="columnCount">
|
||||
</ui-gallery-grid>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Gap Size Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Gap Sizes</h3>
|
||||
<div class="demo-grid">
|
||||
@for (gap of gapSizes; track gap) {
|
||||
<div class="demo-item">
|
||||
<h4>{{ gap | titlecase }} Gap</h4>
|
||||
<ui-gallery-grid
|
||||
[items]="sampleItems.slice(0, 6)"
|
||||
[columns]="3"
|
||||
[gap]="gap">
|
||||
</ui-gallery-grid>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Object Fit Variants -->
|
||||
<section class="demo-section">
|
||||
<h3>Object Fit Options</h3>
|
||||
<div class="demo-grid">
|
||||
@for (fit of objectFitOptions; track fit) {
|
||||
<div class="demo-item">
|
||||
<h4>{{ fit | titlecase }} Fit</h4>
|
||||
<ui-gallery-grid
|
||||
[items]="landscapeItems"
|
||||
[columns]="3"
|
||||
[objectFit]="fit">
|
||||
</ui-gallery-grid>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Auto-fit Responsive -->
|
||||
<section class="demo-section">
|
||||
<h3>Auto-fit Responsive</h3>
|
||||
<p>Automatically adjusts columns based on available space (resize window to see effect):</p>
|
||||
<ui-gallery-grid
|
||||
[items]="sampleItems"
|
||||
columns="auto-fit"
|
||||
[gap]="'md'">
|
||||
</ui-gallery-grid>
|
||||
</section>
|
||||
|
||||
<!-- Masonry Layout -->
|
||||
<section class="demo-section">
|
||||
<h3>Masonry Layout</h3>
|
||||
<p>Items align to top with varying heights:</p>
|
||||
<ui-gallery-grid
|
||||
[items]="masonryItems"
|
||||
[columns]="4"
|
||||
[masonry]="true"
|
||||
[gap]="'sm'">
|
||||
</ui-gallery-grid>
|
||||
</section>
|
||||
|
||||
<!-- Interactive Features -->
|
||||
<section class="demo-section">
|
||||
<h3>Interactive Features</h3>
|
||||
|
||||
<div class="demo-controls">
|
||||
<button (click)="toggleOverlay()" [class.active]="showOverlay">
|
||||
{{ showOverlay ? 'Hide' : 'Show' }} Overlay
|
||||
</button>
|
||||
<button (click)="toggleZoomIndicator()" [class.active]="showZoomIndicator">
|
||||
{{ showZoomIndicator ? 'Hide' : 'Show' }} Zoom Indicator
|
||||
</button>
|
||||
<button (click)="toggleSelection()" [class.active]="selectionEnabled">
|
||||
{{ selectionEnabled ? 'Disable' : 'Enable' }} Selection
|
||||
</button>
|
||||
<button (click)="selectAll()" [disabled]="!selectionEnabled">
|
||||
Select All
|
||||
</button>
|
||||
<button (click)="deselectAll()" [disabled]="!selectionEnabled">
|
||||
Deselect All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ui-gallery-grid
|
||||
[items]="interactiveItems"
|
||||
[columns]="4"
|
||||
[showOverlay]="showOverlay"
|
||||
[showZoomIndicator]="showZoomIndicator"
|
||||
(itemClick)="handleItemClick($event)"
|
||||
(itemSelect)="handleItemSelect($event)"
|
||||
(imageLoad)="handleImageLoad($event)"
|
||||
(imageError)="handleImageError($event)">
|
||||
</ui-gallery-grid>
|
||||
|
||||
@if (selectedItems.length > 0) {
|
||||
<div class="selection-info">
|
||||
<p>Selected Items: {{ selectedItems.length }}</p>
|
||||
<ul>
|
||||
@for (item of selectedItems; track item.id) {
|
||||
<li>{{ item.title || item.alt || 'Item ' + item.id }}</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<!-- Empty State -->
|
||||
<section class="demo-section">
|
||||
<h3>Empty State</h3>
|
||||
<ui-gallery-grid
|
||||
[items]="[]"
|
||||
[columns]="3"
|
||||
emptyText="No photos in this album yet">
|
||||
</ui-gallery-grid>
|
||||
</section>
|
||||
|
||||
<!-- Loading State -->
|
||||
<section class="demo-section">
|
||||
<h3>Loading State</h3>
|
||||
<ui-gallery-grid
|
||||
[items]="loadingItems"
|
||||
[columns]="3">
|
||||
</ui-gallery-grid>
|
||||
</section>
|
||||
|
||||
<!-- Event Log -->
|
||||
<section class="demo-section">
|
||||
<h3>Event Log</h3>
|
||||
<div class="event-log">
|
||||
<div class="event-controls">
|
||||
<button (click)="clearEventLog()">Clear Log</button>
|
||||
</div>
|
||||
<div class="event-list">
|
||||
@if (eventLog.length === 0) {
|
||||
<p class="no-events">No events yet. Interact with the gallery above to see events.</p>
|
||||
} @else {
|
||||
@for (event of eventLog; track event.id) {
|
||||
<div class="event-item">
|
||||
<span class="event-type">{{ event.type }}</span>
|
||||
<span class="event-details">{{ event.details }}</span>
|
||||
<span class="event-time">{{ event.timestamp | date:'HH:mm:ss' }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './gallery-grid-demo.component.scss'
|
||||
})
|
||||
export class GalleryGridDemoComponent {
|
||||
columnVariants = [2, 3, 4] as const;
|
||||
gapSizes = ['sm', 'md', 'lg'] as const;
|
||||
objectFitOptions = ['cover', 'contain', 'fill'] as const;
|
||||
|
||||
showOverlay = true;
|
||||
showZoomIndicator = true;
|
||||
selectionEnabled = false;
|
||||
selectedItems: GalleryGridItem[] = [];
|
||||
eventLog: Array<{id: number; type: string; details: string; timestamp: Date}> = [];
|
||||
|
||||
sampleItems: GalleryGridItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
src: 'https://picsum.photos/400/300?random=1',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=1',
|
||||
alt: 'Sample image 1',
|
||||
title: 'Mountain Landscape',
|
||||
caption: 'Beautiful mountain vista at sunset'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
src: 'https://picsum.photos/400/300?random=2',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=2',
|
||||
alt: 'Sample image 2',
|
||||
title: 'Ocean Waves',
|
||||
caption: 'Crashing waves on a sandy beach'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
src: 'https://picsum.photos/400/300?random=3',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=3',
|
||||
alt: 'Sample image 3',
|
||||
title: 'Forest Path',
|
||||
caption: 'Winding trail through dense woods'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
src: 'https://picsum.photos/400/300?random=4',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=4',
|
||||
alt: 'Sample image 4',
|
||||
title: 'City Skyline',
|
||||
caption: 'Urban architecture at night'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
src: 'https://picsum.photos/400/300?random=5',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=5',
|
||||
alt: 'Sample image 5',
|
||||
title: 'Desert Dunes',
|
||||
caption: 'Rolling sand dunes in morning light'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
src: 'https://picsum.photos/400/300?random=6',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=6',
|
||||
alt: 'Sample image 6',
|
||||
title: 'Lake Reflection',
|
||||
caption: 'Mirror-like water surface'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
src: 'https://picsum.photos/400/300?random=7',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=7',
|
||||
alt: 'Sample image 7',
|
||||
title: 'Flower Field',
|
||||
caption: 'Colorful wildflowers in bloom'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
src: 'https://picsum.photos/400/300?random=8',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=8',
|
||||
alt: 'Sample image 8',
|
||||
title: 'Snow Peaks',
|
||||
caption: 'Snow-capped mountain range'
|
||||
}
|
||||
];
|
||||
|
||||
landscapeItems: GalleryGridItem[] = [
|
||||
{
|
||||
id: 'l1',
|
||||
src: 'https://picsum.photos/800/400?random=11',
|
||||
thumbnail: 'https://picsum.photos/200/100?random=11',
|
||||
alt: 'Landscape image 1',
|
||||
title: 'Wide Landscape',
|
||||
caption: 'Panoramic view'
|
||||
},
|
||||
{
|
||||
id: 'l2',
|
||||
src: 'https://picsum.photos/800/400?random=12',
|
||||
thumbnail: 'https://picsum.photos/200/100?random=12',
|
||||
alt: 'Landscape image 2',
|
||||
title: 'River Valley',
|
||||
caption: 'Flowing water through hills'
|
||||
},
|
||||
{
|
||||
id: 'l3',
|
||||
src: 'https://picsum.photos/800/400?random=13',
|
||||
thumbnail: 'https://picsum.photos/200/100?random=13',
|
||||
alt: 'Landscape image 3',
|
||||
title: 'Coastal View',
|
||||
caption: 'Rocky coastline'
|
||||
}
|
||||
];
|
||||
|
||||
masonryItems: GalleryGridItem[] = [
|
||||
{
|
||||
id: 'm1',
|
||||
src: 'https://picsum.photos/300/400?random=21',
|
||||
thumbnail: 'https://picsum.photos/150/200?random=21',
|
||||
alt: 'Tall image 1',
|
||||
title: 'Portrait 1'
|
||||
},
|
||||
{
|
||||
id: 'm2',
|
||||
src: 'https://picsum.photos/300/200?random=22',
|
||||
thumbnail: 'https://picsum.photos/150/100?random=22',
|
||||
alt: 'Wide image 1',
|
||||
title: 'Landscape 1'
|
||||
},
|
||||
{
|
||||
id: 'm3',
|
||||
src: 'https://picsum.photos/300/500?random=23',
|
||||
thumbnail: 'https://picsum.photos/150/250?random=23',
|
||||
alt: 'Very tall image',
|
||||
title: 'Portrait 2'
|
||||
},
|
||||
{
|
||||
id: 'm4',
|
||||
src: 'https://picsum.photos/300/300?random=24',
|
||||
thumbnail: 'https://picsum.photos/150/150?random=24',
|
||||
alt: 'Square image',
|
||||
title: 'Square 1'
|
||||
},
|
||||
{
|
||||
id: 'm5',
|
||||
src: 'https://picsum.photos/300/350?random=25',
|
||||
thumbnail: 'https://picsum.photos/150/175?random=25',
|
||||
alt: 'Tall image 2',
|
||||
title: 'Portrait 3'
|
||||
},
|
||||
{
|
||||
id: 'm6',
|
||||
src: 'https://picsum.photos/300/250?random=26',
|
||||
thumbnail: 'https://picsum.photos/150/125?random=26',
|
||||
alt: 'Medium image',
|
||||
title: 'Landscape 2'
|
||||
}
|
||||
];
|
||||
|
||||
interactiveItems: GalleryGridItem[] = this.sampleItems.slice(0, 8).map(item => ({
|
||||
...item,
|
||||
selected: false
|
||||
}));
|
||||
|
||||
loadingItems: GalleryGridItem[] = [
|
||||
{
|
||||
id: 'loading1',
|
||||
src: 'https://picsum.photos/400/300?random=31',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=31',
|
||||
alt: 'Loading image 1',
|
||||
title: 'Loading Item 1',
|
||||
loading: true
|
||||
},
|
||||
{
|
||||
id: 'loading2',
|
||||
src: 'https://picsum.photos/400/300?random=32',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=32',
|
||||
alt: 'Loading image 2',
|
||||
title: 'Loading Item 2',
|
||||
loading: true
|
||||
},
|
||||
{
|
||||
id: 'loading3',
|
||||
src: 'https://picsum.photos/400/300?random=33',
|
||||
thumbnail: 'https://picsum.photos/200/150?random=33',
|
||||
alt: 'Loading image 3',
|
||||
title: 'Loaded Item',
|
||||
loading: false
|
||||
}
|
||||
];
|
||||
|
||||
private eventCounter = 0;
|
||||
|
||||
toggleOverlay(): void {
|
||||
this.showOverlay = !this.showOverlay;
|
||||
}
|
||||
|
||||
toggleZoomIndicator(): void {
|
||||
this.showZoomIndicator = !this.showZoomIndicator;
|
||||
}
|
||||
|
||||
toggleSelection(): void {
|
||||
this.selectionEnabled = !this.selectionEnabled;
|
||||
if (!this.selectionEnabled) {
|
||||
this.deselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
selectAll(): void {
|
||||
this.interactiveItems.forEach(item => item.selected = true);
|
||||
this.updateSelectedItems();
|
||||
}
|
||||
|
||||
deselectAll(): void {
|
||||
this.interactiveItems.forEach(item => item.selected = false);
|
||||
this.updateSelectedItems();
|
||||
}
|
||||
|
||||
handleItemClick(event: { item: GalleryGridItem; event: MouseEvent }): void {
|
||||
this.addEvent('Item Click', `Clicked item: ${event.item.title || event.item.id}`);
|
||||
|
||||
if (this.selectionEnabled) {
|
||||
event.item.selected = !event.item.selected;
|
||||
this.updateSelectedItems();
|
||||
this.addEvent('Item Selection', `${event.item.selected ? 'Selected' : 'Deselected'} item: ${event.item.title || event.item.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleItemSelect(item: GalleryGridItem): void {
|
||||
this.updateSelectedItems();
|
||||
this.addEvent('Item Select', `Selected item: ${item.title || item.id}`);
|
||||
}
|
||||
|
||||
handleImageLoad(event: { item: GalleryGridItem; event: Event }): void {
|
||||
this.addEvent('Image Load', `Loaded image: ${event.item.title || event.item.id}`);
|
||||
}
|
||||
|
||||
handleImageError(event: { item: GalleryGridItem; event: Event }): void {
|
||||
this.addEvent('Image Error', `Failed to load image: ${event.item.title || event.item.id}`);
|
||||
}
|
||||
|
||||
clearEventLog(): void {
|
||||
this.eventLog = [];
|
||||
}
|
||||
|
||||
private updateSelectedItems(): void {
|
||||
this.selectedItems = this.interactiveItems.filter(item => item.selected);
|
||||
}
|
||||
|
||||
private addEvent(type: string, details: string): void {
|
||||
this.eventLog.unshift({
|
||||
id: ++this.eventCounter,
|
||||
type,
|
||||
details,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// Keep only the last 20 events
|
||||
if (this.eventLog.length > 20) {
|
||||
this.eventLog = this.eventLog.slice(0, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user