wally-ui 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/playground/showcase/public/sitemap.xml +8 -0
- package/playground/showcase/src/app/app.routes.server.ts +4 -0
- package/playground/showcase/src/app/components/carousel/carousel.html +32 -0
- package/playground/showcase/src/app/components/carousel/carousel.ts +257 -0
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.css +0 -0
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.examples.ts +48 -0
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.html +156 -0
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.spec.ts +23 -0
- package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.ts +46 -0
- package/playground/showcase/src/app/pages/documentation/components/components.html +13 -0
- package/playground/showcase/src/app/pages/documentation/components/components.routes.ts +4 -0
package/package.json
CHANGED
|
@@ -44,4 +44,12 @@
|
|
|
44
44
|
<priority>0.8</priority>
|
|
45
45
|
</url>
|
|
46
46
|
|
|
47
|
+
<!-- Carousel Documentation -->
|
|
48
|
+
<url>
|
|
49
|
+
<loc>https://wally-ui.com/documentation/components/carousel</loc>
|
|
50
|
+
<lastmod>2025-09-27</lastmod>
|
|
51
|
+
<changefreq>monthly</changefreq>
|
|
52
|
+
<priority>0.8</priority>
|
|
53
|
+
</url>
|
|
54
|
+
|
|
47
55
|
</urlset>
|
|
@@ -21,6 +21,10 @@ export const serverRoutes: ServerRoute[] = [
|
|
|
21
21
|
path: 'documentation/components/breadcrumb',
|
|
22
22
|
renderMode: RenderMode.Prerender,
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
path: 'documentation/components/carousel',
|
|
26
|
+
renderMode: RenderMode.Prerender,
|
|
27
|
+
},
|
|
24
28
|
{
|
|
25
29
|
path: 'components',
|
|
26
30
|
renderMode: RenderMode.Prerender,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div class="w-full flex flex-col items-center gap-4">
|
|
2
|
+
<div class="relative w-96 h-96 overflow-hidden shadow rounded-lg" #carouselContainer>
|
|
3
|
+
<ng-content></ng-content>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="flex items-center gap-3">
|
|
7
|
+
@for (navigationDot of navigationDotsArray; track $index; let dotIndex = $index) {
|
|
8
|
+
<button
|
|
9
|
+
class="size-2 rounded-full transition-all duration-500 ease-in-out cursor-pointer"
|
|
10
|
+
[ngClass]="{
|
|
11
|
+
'bg-blue-500 border scale-150': dotIndex === currentVisibleItemIndex(),
|
|
12
|
+
'bg-white hover:bg-gray-400 hover:scale-125': dotIndex !== currentVisibleItemIndex()
|
|
13
|
+
}"
|
|
14
|
+
(click)="navigateToSpecificItem(dotIndex)"
|
|
15
|
+
[attr.aria-label]="'Navigate to slide ' + (dotIndex + 1)">
|
|
16
|
+
</button>
|
|
17
|
+
}
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="flex gap-4">
|
|
21
|
+
<button class="px-4 py-2 bg-gray-700 text-white rounded hover:bg-gray-600 transition-colors duration-200"
|
|
22
|
+
(click)="navigateToPreviousItem()"
|
|
23
|
+
aria-label="Go to previous slide">
|
|
24
|
+
Previous
|
|
25
|
+
</button>
|
|
26
|
+
<button class="px-4 py-2 bg-gray-700 text-white rounded hover:bg-gray-600 transition-colors duration-200"
|
|
27
|
+
(click)="navigateToNextItem()"
|
|
28
|
+
aria-label="Go to next slide">
|
|
29
|
+
Next
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { Component, input, OnInit, signal, ViewChild, ElementRef, AfterViewInit, Renderer2, OnDestroy, HostListener } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'wally-carousel',
|
|
6
|
+
imports: [CommonModule],
|
|
7
|
+
templateUrl: './carousel.html',
|
|
8
|
+
})
|
|
9
|
+
export class Carousel implements OnInit, AfterViewInit, OnDestroy {
|
|
10
|
+
@ViewChild('carouselContainer', { static: false }) carouselContainer!: ElementRef;
|
|
11
|
+
|
|
12
|
+
totalItemsCount = signal<number>(0);
|
|
13
|
+
currentVisibleItemIndex = signal<number>(0);
|
|
14
|
+
carouselItemElements: HTMLElement[] = [];
|
|
15
|
+
|
|
16
|
+
// Touch and swipe gesture state
|
|
17
|
+
private gestureState = {
|
|
18
|
+
startPositionX: 0,
|
|
19
|
+
startPositionY: 0,
|
|
20
|
+
currentPositionX: 0,
|
|
21
|
+
currentPositionY: 0,
|
|
22
|
+
isCurrentlyDragging: false,
|
|
23
|
+
gestureStartTime: 0
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Configuration constants for touch and swipe behavior
|
|
27
|
+
private readonly MINIMUM_SWIPE_DISTANCE = 50; // minimum pixels to register as swipe
|
|
28
|
+
private readonly MINIMUM_SWIPE_VELOCITY = 0.3; // minimum velocity to register as swipe
|
|
29
|
+
private readonly MAXIMUM_SWIPE_DURATION = 300; // maximum milliseconds for swipe gesture
|
|
30
|
+
|
|
31
|
+
// Event listener cleanup functions
|
|
32
|
+
private eventListenerCleanupFunctions: (() => void)[] = [];
|
|
33
|
+
|
|
34
|
+
get navigationDotsArray() {
|
|
35
|
+
return Array(this.totalItemsCount()).fill(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
constructor(private renderer: Renderer2) { }
|
|
39
|
+
|
|
40
|
+
ngOnInit(): void {
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ngAfterViewInit(): void {
|
|
44
|
+
if (this.carouselContainer) {
|
|
45
|
+
this.initializeCarouselItems();
|
|
46
|
+
this.setupTouchAndMouseEvents();
|
|
47
|
+
this.setupAccessibilityAttributes();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ngOnDestroy(): void {
|
|
52
|
+
// Clean up all event listeners to prevent memory leaks
|
|
53
|
+
this.eventListenerCleanupFunctions.forEach(cleanupFunction => cleanupFunction());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private initializeCarouselItems(): void {
|
|
57
|
+
// Get all direct children elements of the carousel container
|
|
58
|
+
this.carouselItemElements = Array.from(this.carouselContainer.nativeElement.children);
|
|
59
|
+
this.totalItemsCount.set(this.carouselItemElements.length);
|
|
60
|
+
|
|
61
|
+
// Apply positioning and styling to each carousel item using Renderer2
|
|
62
|
+
this.carouselItemElements.forEach((itemElement, itemIndex) => {
|
|
63
|
+
this.renderer.setStyle(itemElement, 'position', 'absolute');
|
|
64
|
+
this.renderer.setStyle(itemElement, 'inset', '0');
|
|
65
|
+
this.renderer.setStyle(itemElement, 'transition', 'transform 700ms ease-out');
|
|
66
|
+
this.renderer.setStyle(itemElement, 'display', 'flex');
|
|
67
|
+
this.renderer.setStyle(itemElement, 'align-items', 'center');
|
|
68
|
+
this.renderer.setStyle(itemElement, 'justify-content', 'center');
|
|
69
|
+
this.updateItemElementPosition(itemElement, itemIndex);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private setupTouchAndMouseEvents(): void {
|
|
74
|
+
const carouselContainerElement = this.carouselContainer.nativeElement;
|
|
75
|
+
|
|
76
|
+
// Touch events for mobile devices
|
|
77
|
+
const touchStartListener = this.renderer.listen(carouselContainerElement, 'touchstart', (event) => this.handleTouchStart(event));
|
|
78
|
+
const touchMoveListener = this.renderer.listen(carouselContainerElement, 'touchmove', (event) => this.handleTouchMove(event));
|
|
79
|
+
const touchEndListener = this.renderer.listen(carouselContainerElement, 'touchend', () => this.handleTouchEnd());
|
|
80
|
+
|
|
81
|
+
// Mouse events for desktop devices
|
|
82
|
+
const mouseDownListener = this.renderer.listen(carouselContainerElement, 'mousedown', (event) => this.handleMouseDown(event));
|
|
83
|
+
const mouseMoveListener = this.renderer.listen(carouselContainerElement, 'mousemove', (event) => this.handleMouseMove(event));
|
|
84
|
+
const mouseUpListener = this.renderer.listen(carouselContainerElement, 'mouseup', () => this.handleMouseUp());
|
|
85
|
+
const mouseLeaveListener = this.renderer.listen(carouselContainerElement, 'mouseleave', () => this.handleMouseUp());
|
|
86
|
+
|
|
87
|
+
// Store all cleanup functions for proper memory management
|
|
88
|
+
this.eventListenerCleanupFunctions.push(
|
|
89
|
+
touchStartListener,
|
|
90
|
+
touchMoveListener,
|
|
91
|
+
touchEndListener,
|
|
92
|
+
mouseDownListener,
|
|
93
|
+
mouseMoveListener,
|
|
94
|
+
mouseUpListener,
|
|
95
|
+
mouseLeaveListener
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Optimize touch performance by restricting to horizontal panning only
|
|
99
|
+
this.renderer.setStyle(carouselContainerElement, 'touch-action', 'pan-x');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private setupAccessibilityAttributes(): void {
|
|
103
|
+
const carouselContainerElement = this.carouselContainer.nativeElement;
|
|
104
|
+
|
|
105
|
+
// Set ARIA attributes for screen readers and accessibility
|
|
106
|
+
this.renderer.setAttribute(carouselContainerElement, 'role', 'region');
|
|
107
|
+
this.renderer.setAttribute(carouselContainerElement, 'aria-label', 'Carousel');
|
|
108
|
+
this.renderer.setAttribute(carouselContainerElement, 'tabindex', '0');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
updateItemElementPosition(carouselItemElement: HTMLElement, itemIndex: number): void {
|
|
112
|
+
const currentVisibleIndex = this.currentVisibleItemIndex();
|
|
113
|
+
|
|
114
|
+
if (itemIndex === currentVisibleIndex) {
|
|
115
|
+
// Position the currently visible item at center (0% translation)
|
|
116
|
+
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(0)');
|
|
117
|
+
} else if (itemIndex > currentVisibleIndex) {
|
|
118
|
+
// Position items to the right of current item (110% translation to the right)
|
|
119
|
+
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(110%)');
|
|
120
|
+
} else {
|
|
121
|
+
// Position items to the left of current item (110% translation to the left)
|
|
122
|
+
this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(-110%)');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
updateAllItemElementPositions(): void {
|
|
127
|
+
this.carouselItemElements.forEach((itemElement, itemIndex) => {
|
|
128
|
+
this.updateItemElementPosition(itemElement, itemIndex);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Touch event handlers for mobile devices
|
|
133
|
+
private handleTouchStart(touchEvent: TouchEvent): void {
|
|
134
|
+
this.initializeGesture(touchEvent.touches[0].clientX, touchEvent.touches[0].clientY);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private handleTouchMove(touchEvent: TouchEvent): void {
|
|
138
|
+
if (this.gestureState.isCurrentlyDragging) {
|
|
139
|
+
touchEvent.preventDefault(); // Prevent page scrolling during swipe
|
|
140
|
+
this.updateGesturePosition(touchEvent.touches[0].clientX, touchEvent.touches[0].clientY);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private handleTouchEnd(): void {
|
|
145
|
+
this.finalizeGestureAndNavigate();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Mouse event handlers for desktop devices
|
|
149
|
+
private handleMouseDown(mouseEvent: MouseEvent): void {
|
|
150
|
+
this.initializeGesture(mouseEvent.clientX, mouseEvent.clientY);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private handleMouseMove(mouseEvent: MouseEvent): void {
|
|
154
|
+
if (this.gestureState.isCurrentlyDragging) {
|
|
155
|
+
mouseEvent.preventDefault(); // Prevent text selection during drag
|
|
156
|
+
this.updateGesturePosition(mouseEvent.clientX, mouseEvent.clientY);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private handleMouseUp(): void {
|
|
161
|
+
this.finalizeGestureAndNavigate();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Unified gesture handling methods
|
|
165
|
+
private initializeGesture(xPosition: number, yPosition: number): void {
|
|
166
|
+
this.gestureState = {
|
|
167
|
+
startPositionX: xPosition,
|
|
168
|
+
startPositionY: yPosition,
|
|
169
|
+
currentPositionX: xPosition,
|
|
170
|
+
currentPositionY: yPosition,
|
|
171
|
+
isCurrentlyDragging: true,
|
|
172
|
+
gestureStartTime: Date.now()
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private updateGesturePosition(xPosition: number, yPosition: number): void {
|
|
177
|
+
if (!this.gestureState.isCurrentlyDragging) return;
|
|
178
|
+
|
|
179
|
+
this.gestureState.currentPositionX = xPosition;
|
|
180
|
+
this.gestureState.currentPositionY = yPosition;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private finalizeGestureAndNavigate(): void {
|
|
184
|
+
if (!this.gestureState.isCurrentlyDragging) return;
|
|
185
|
+
|
|
186
|
+
const horizontalDistanceMoved = this.gestureState.currentPositionX - this.gestureState.startPositionX;
|
|
187
|
+
const verticalDistanceMoved = this.gestureState.currentPositionY - this.gestureState.startPositionY;
|
|
188
|
+
const gestureDuration = Date.now() - this.gestureState.gestureStartTime;
|
|
189
|
+
const gestureVelocity = Math.abs(horizontalDistanceMoved) / gestureDuration;
|
|
190
|
+
|
|
191
|
+
// Only process horizontal swipes (ignore vertical movements)
|
|
192
|
+
if (Math.abs(horizontalDistanceMoved) > Math.abs(verticalDistanceMoved)) {
|
|
193
|
+
// Check if gesture meets minimum requirements for navigation
|
|
194
|
+
const isValidSwipeGesture = Math.abs(horizontalDistanceMoved) > this.MINIMUM_SWIPE_DISTANCE ||
|
|
195
|
+
(gestureVelocity > this.MINIMUM_SWIPE_VELOCITY && gestureDuration < this.MAXIMUM_SWIPE_DURATION);
|
|
196
|
+
|
|
197
|
+
if (isValidSwipeGesture) {
|
|
198
|
+
if (horizontalDistanceMoved > 0) {
|
|
199
|
+
this.navigateToPreviousItem(); // Swipe right = go to previous item
|
|
200
|
+
} else {
|
|
201
|
+
this.navigateToNextItem(); // Swipe left = go to next item
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Reset gesture state after processing
|
|
207
|
+
this.gestureState.isCurrentlyDragging = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Keyboard navigation handler
|
|
211
|
+
@HostListener('keydown', ['$event'])
|
|
212
|
+
handleKeyboardNavigation(keyboardEvent: KeyboardEvent): void {
|
|
213
|
+
switch (keyboardEvent.key) {
|
|
214
|
+
case 'ArrowLeft':
|
|
215
|
+
keyboardEvent.preventDefault();
|
|
216
|
+
this.navigateToPreviousItem();
|
|
217
|
+
break;
|
|
218
|
+
case 'ArrowRight':
|
|
219
|
+
keyboardEvent.preventDefault();
|
|
220
|
+
this.navigateToNextItem();
|
|
221
|
+
break;
|
|
222
|
+
case 'Home':
|
|
223
|
+
keyboardEvent.preventDefault();
|
|
224
|
+
this.navigateToSpecificItem(0);
|
|
225
|
+
break;
|
|
226
|
+
case 'End':
|
|
227
|
+
keyboardEvent.preventDefault();
|
|
228
|
+
this.navigateToSpecificItem(this.totalItemsCount() - 1);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Circular buffer algorithm implementation for infinite carousel navigation
|
|
234
|
+
calculateNextItemIndex(currentItemIndex: number): number {
|
|
235
|
+
return (currentItemIndex + 1) % this.totalItemsCount();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
calculatePreviousItemIndex(currentItemIndex: number): number {
|
|
239
|
+
return (currentItemIndex - 1 + this.totalItemsCount()) % this.totalItemsCount();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
navigateToNextItem(): void {
|
|
243
|
+
this.currentVisibleItemIndex.set(this.calculateNextItemIndex(this.currentVisibleItemIndex()));
|
|
244
|
+
this.updateAllItemElementPositions();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
navigateToPreviousItem(): void {
|
|
248
|
+
this.currentVisibleItemIndex.set(this.calculatePreviousItemIndex(this.currentVisibleItemIndex()));
|
|
249
|
+
this.updateAllItemElementPositions();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
navigateToSpecificItem(targetItemIndex: number): void {
|
|
253
|
+
this.currentVisibleItemIndex.set(targetItemIndex);
|
|
254
|
+
this.updateAllItemElementPositions();
|
|
255
|
+
}
|
|
256
|
+
// end test Circular Buffer ---
|
|
257
|
+
}
|
package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.css
ADDED
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Carousel documentation code examples
|
|
2
|
+
export const CarouselCodeExamples = {
|
|
3
|
+
// Installation
|
|
4
|
+
installation: `npx wally-ui add carousel`,
|
|
5
|
+
|
|
6
|
+
// Import examples
|
|
7
|
+
import: `import { Carousel } from './components/wally-ui/carousel/carousel';`,
|
|
8
|
+
componentImport: `@Component({
|
|
9
|
+
selector: 'app-example',
|
|
10
|
+
imports: [Carousel],
|
|
11
|
+
templateUrl: './example.html',
|
|
12
|
+
styleUrl: './example.css'
|
|
13
|
+
})`,
|
|
14
|
+
|
|
15
|
+
// Basic usage
|
|
16
|
+
basicUsage: `<wally-carousel>
|
|
17
|
+
<div>Item 1</div>
|
|
18
|
+
<div>Item 2</div>
|
|
19
|
+
</wally-carousel>`,
|
|
20
|
+
|
|
21
|
+
// Custom content example
|
|
22
|
+
customContent: `<wally-carousel>
|
|
23
|
+
<div class="w-full h-full bg-gradient-to-r from-blue-500 to-purple-600 text-white flex flex-col items-center justify-center p-6">
|
|
24
|
+
<h3 class="text-xl font-bold mb-2">Feature 1</h3>
|
|
25
|
+
<p class="text-center text-sm">Custom slide with rich content</p>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="w-full h-full bg-gradient-to-r from-green-500 to-blue-500 text-white flex flex-col items-center justify-center p-6">
|
|
28
|
+
<h3 class="text-xl font-bold mb-2">Feature 2</h3>
|
|
29
|
+
<p class="text-center text-sm">Another slide with different styling</p>
|
|
30
|
+
</div>
|
|
31
|
+
</wally-carousel>`,
|
|
32
|
+
|
|
33
|
+
// Properties
|
|
34
|
+
properties: `// Component properties
|
|
35
|
+
// The carousel automatically detects child elements and creates navigation
|
|
36
|
+
// Currently no input properties - content projection only
|
|
37
|
+
|
|
38
|
+
// Available interactions:
|
|
39
|
+
// • Click navigation buttons (Previous/Next)
|
|
40
|
+
// • Click dot indicators for direct navigation
|
|
41
|
+
// • Touch/swipe gestures on mobile and desktop
|
|
42
|
+
// • Keyboard navigation (Arrow keys, Home, End)
|
|
43
|
+
|
|
44
|
+
// Auto-detected features:
|
|
45
|
+
totalItemsCount: number; // Automatically set based on child elements
|
|
46
|
+
currentVisibleItemIndex: number; // Currently visible slide index (0-based)
|
|
47
|
+
navigationDotsArray: any[]; // Auto-generated dot indicators`
|
|
48
|
+
};
|
package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.html
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<div class="font-mono">
|
|
2
|
+
<div class="max-w-4xl mx-auto p-6">
|
|
3
|
+
<div class="mb-4">
|
|
4
|
+
<wally-breadcrumb [items]="breadcrumbItems"></wally-breadcrumb>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<h1 class="text-2xl font-bold mb-4 text-[#0a0a0a] dark:text-white">
|
|
8
|
+
Carousel
|
|
9
|
+
</h1>
|
|
10
|
+
<p class="text-gray-700 dark:text-gray-400 mb-4">
|
|
11
|
+
A carousel component with navigation controls, touch gestures, and smooth transitions.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<!-- Under Construction Badge -->
|
|
15
|
+
<div class="mb-6">
|
|
16
|
+
<span class="text-xs bg-yellow-500 text-black px-3 py-1 rounded-full font-medium">Under Construction</span>
|
|
17
|
+
<p class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
|
18
|
+
This component is actively being developed. Touch gestures, accessibility, and customization options are coming soon.
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- AI Prompts -->
|
|
23
|
+
<div class="flex flex-wrap gap-2 mb-6">
|
|
24
|
+
<a [href]="claudeUrl" target="_blank"
|
|
25
|
+
class="inline-flex items-center gap-2 px-3 py-1.5 text-xs bg-gray-200 dark:bg-[#121212] text-gray-600 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-[#1a1a1a] hover:text-[#0a0a0a] dark:hover:text-white transition-colors">
|
|
26
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="size-6" viewBox="0 0 24 24">
|
|
27
|
+
<path fill="currentColor"
|
|
28
|
+
d="m4.714 15.956l4.718-2.648l.079-.23l-.08-.128h-.23l-.79-.048l-2.695-.073l-2.337-.097l-2.265-.122l-.57-.121l-.535-.704l.055-.353l.48-.321l.685.06l1.518.104l2.277.157l1.651.098l2.447.255h.389l.054-.158l-.133-.097l-.103-.098l-2.356-1.596l-2.55-1.688l-1.336-.972l-.722-.491L2 6.223l-.158-1.008l.656-.722l.88.06l.224.061l.893.686l1.906 1.476l2.49 1.833l.364.304l.146-.104l.018-.072l-.164-.274l-1.354-2.446l-1.445-2.49l-.644-1.032l-.17-.619a3 3 0 0 1-.103-.729L6.287.133L6.7 0l.995.134l.42.364l.619 1.415L9.735 4.14l1.555 3.03l.455.898l.243.832l.09.255h.159V9.01l.127-1.706l.237-2.095l.23-2.695l.08-.76l.376-.91l.747-.492l.583.28l.48.685l-.067.444l-.286 1.851l-.558 2.903l-.365 1.942h.213l.243-.242l.983-1.306l1.652-2.064l.728-.82l.85-.904l.547-.431h1.032l.759 1.129l-.34 1.166l-1.063 1.347l-.88 1.142l-1.263 1.7l-.79 1.36l.074.11l.188-.02l2.853-.606l1.542-.28l1.84-.315l.832.388l.09.395l-.327.807l-1.967.486l-2.307.462l-3.436.813l-.043.03l.049.061l1.548.146l.662.036h1.62l3.018.225l.79.522l.473.638l-.08.485l-1.213.62l-1.64-.389l-3.825-.91l-1.31-.329h-.183v.11l1.093 1.068l2.003 1.81l2.508 2.33l.127.578l-.321.455l-.34-.049l-2.204-1.657l-.85-.747l-1.925-1.62h-.127v.17l.443.649l2.343 3.521l.122 1.08l-.17.353l-.607.213l-.668-.122l-1.372-1.924l-1.415-2.168l-1.141-1.943l-.14.08l-.674 7.254l-.316.37l-.728.28l-.607-.461l-.322-.747l.322-1.476l.388-1.924l.316-1.53l.285-1.9l.17-.632l-.012-.042l-.14.018l-1.432 1.967l-2.18 2.945l-1.724 1.845l-.413.164l-.716-.37l.066-.662l.401-.589l2.386-3.036l1.439-1.882l.929-1.086l-.006-.158h-.055L4.138 18.56l-1.13.146l-.485-.456l.06-.746l.231-.243l1.907-1.312Z" />
|
|
29
|
+
</svg>
|
|
30
|
+
Open in Claude
|
|
31
|
+
</a>
|
|
32
|
+
|
|
33
|
+
<a [href]="chatGptUrl" target="_blank"
|
|
34
|
+
class="inline-flex items-center gap-2 px-3 py-1.5 text-xs bg-gray-200 dark:bg-[#121212] text-gray-600 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-[#1a1a1a] hover:text-[#0a0a0a] dark:hover:text-white transition-colors">
|
|
35
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="size-6" viewBox="0 0 48 48">
|
|
36
|
+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
|
|
37
|
+
d="M18.38 27.94v-14.4l11.19-6.46c6.2-3.58 17.3 5.25 12.64 13.33" />
|
|
38
|
+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
|
|
39
|
+
d="m18.38 20.94l12.47-7.2l11.19 6.46c6.2 3.58 4.1 17.61-5.23 17.61" />
|
|
40
|
+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
|
|
41
|
+
d="m24.44 17.44l12.47 7.2v12.93c0 7.16-13.2 12.36-17.86 4.28" />
|
|
42
|
+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
|
|
43
|
+
d="M30.5 21.2v14.14L19.31 41.8c-6.2 3.58-17.3-5.25-12.64-13.33" />
|
|
44
|
+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
|
|
45
|
+
d="m30.5 27.94l-12.47 7.2l-11.19-6.46c-6.21-3.59-4.11-17.61 5.22-17.61" />
|
|
46
|
+
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"
|
|
47
|
+
d="m24.44 31.44l-12.47-7.2V11.31c0-7.16 13.2-12.36 17.86-4.28" />
|
|
48
|
+
</svg>
|
|
49
|
+
Open in ChatGPT
|
|
50
|
+
</a>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Installation -->
|
|
54
|
+
<section class="mb-8">
|
|
55
|
+
<h2 class="text-lg font-semibold mb-4 text-[#0a0a0a] dark:text-white">Installation</h2>
|
|
56
|
+
<div class="bg-gray-200 dark:bg-[#121212] p-4 rounded-lg">
|
|
57
|
+
<pre><code [innerHTML]="installationCode" class="text-sm text-[#0a0a0a] dark:text-white"></code></pre>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<!-- Preview -->
|
|
62
|
+
<section class="mb-8">
|
|
63
|
+
<h2 class="text-lg font-semibold mb-4 text-[#0a0a0a] dark:text-white">Preview</h2>
|
|
64
|
+
<div class="p-6 border rounded-lg bg-white dark:bg-[#121212]">
|
|
65
|
+
<div class="flex justify-center">
|
|
66
|
+
<wally-carousel>
|
|
67
|
+
<div class="w-full h-full bg-blue-500 text-white flex items-center justify-center text-lg font-semibold">
|
|
68
|
+
Slide 1
|
|
69
|
+
</div>
|
|
70
|
+
<div class="w-full h-full bg-green-500 text-white flex items-center justify-center text-lg font-semibold">
|
|
71
|
+
Slide 2
|
|
72
|
+
</div>
|
|
73
|
+
<div class="w-full h-full bg-purple-500 text-white flex items-center justify-center text-lg font-semibold">
|
|
74
|
+
Slide 3
|
|
75
|
+
</div>
|
|
76
|
+
</wally-carousel>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<!-- Import -->
|
|
82
|
+
<section class="mb-8">
|
|
83
|
+
<h2 class="text-lg font-semibold mb-4 text-[#0a0a0a] dark:text-white">Import</h2>
|
|
84
|
+
<div class="space-y-4">
|
|
85
|
+
<div class="bg-gray-200 dark:bg-[#121212] p-4 rounded-lg">
|
|
86
|
+
<pre><code [innerHTML]="importCode" class="text-sm text-[#0a0a0a] dark:text-white"></code></pre>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="bg-gray-200 dark:bg-[#121212] p-4 rounded-lg">
|
|
90
|
+
<pre><code [innerHTML]="componentImportCode" class="text-sm text-[#0a0a0a] dark:text-white"></code></pre>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<!-- Basic Usage -->
|
|
96
|
+
<section class="mb-8">
|
|
97
|
+
<h2 class="text-lg font-semibold mb-4 text-[#0a0a0a] dark:text-white">Basic Usage</h2>
|
|
98
|
+
<div class="bg-gray-200 dark:bg-[#121212] p-4 rounded-lg mb-4">
|
|
99
|
+
<pre><code [innerHTML]="basicUsageCode" class="text-sm text-[#0a0a0a] dark:text-white"></code></pre>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="p-6 border rounded-lg bg-white dark:bg-[#121212]">
|
|
102
|
+
<div class="flex justify-center">
|
|
103
|
+
<wally-carousel>
|
|
104
|
+
<div class="w-full h-full bg-blue-400 text-white flex items-center justify-center">
|
|
105
|
+
Item 1
|
|
106
|
+
</div>
|
|
107
|
+
<div class="w-full h-full bg-green-400 text-white flex items-center justify-center">
|
|
108
|
+
Item 2
|
|
109
|
+
</div>
|
|
110
|
+
</wally-carousel>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</section>
|
|
114
|
+
|
|
115
|
+
<!-- Advanced Examples -->
|
|
116
|
+
<section class="mb-8">
|
|
117
|
+
<h2 class="text-lg font-semibold mb-4 text-[#0a0a0a] dark:text-white">Advanced Examples</h2>
|
|
118
|
+
|
|
119
|
+
<!-- Custom Content Example -->
|
|
120
|
+
<div class="mb-6">
|
|
121
|
+
<h3 class="text-md font-medium mb-3 text-[#0a0a0a] dark:text-white">Custom Content</h3>
|
|
122
|
+
<div class="bg-gray-200 dark:bg-[#121212] p-4 rounded-lg mb-4">
|
|
123
|
+
<pre><code [innerHTML]="customContentCode" class="text-sm text-[#0a0a0a] dark:text-white"></code></pre>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="p-6 border rounded-lg bg-white dark:bg-[#121212]">
|
|
126
|
+
<div class="flex justify-center">
|
|
127
|
+
<wally-carousel>
|
|
128
|
+
<div class="w-full h-full bg-gradient-to-r from-blue-500 to-purple-600 text-white flex flex-col items-center justify-center p-6">
|
|
129
|
+
<h3 class="text-xl font-bold mb-2">Feature 1</h3>
|
|
130
|
+
<p class="text-center text-sm">This is a custom slide with rich content</p>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="w-full h-full bg-gradient-to-r from-green-500 to-blue-500 text-white flex flex-col items-center justify-center p-6">
|
|
133
|
+
<h3 class="text-xl font-bold mb-2">Feature 2</h3>
|
|
134
|
+
<p class="text-center text-sm">Another slide with different styling</p>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 text-white flex flex-col items-center justify-center p-6">
|
|
137
|
+
<h3 class="text-xl font-bold mb-2">Feature 3</h3>
|
|
138
|
+
<p class="text-center text-sm">Beautiful gradients and typography</p>
|
|
139
|
+
</div>
|
|
140
|
+
</wally-carousel>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
|
|
146
|
+
<!-- Properties -->
|
|
147
|
+
<section class="mb-8">
|
|
148
|
+
<h2 class="text-lg font-semibold mb-4 text-[#0a0a0a] dark:text-white">Properties</h2>
|
|
149
|
+
<div class="bg-gray-200 dark:bg-[#121212] p-4 rounded-lg">
|
|
150
|
+
<pre><code [innerHTML]="propertiesCode" class="text-sm text-[#0a0a0a] dark:text-white"></code></pre>
|
|
151
|
+
</div>
|
|
152
|
+
</section>
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { CarouselDocs } from './carousel-docs';
|
|
4
|
+
|
|
5
|
+
describe('CarouselDocs', () => {
|
|
6
|
+
let component: CarouselDocs;
|
|
7
|
+
let fixture: ComponentFixture<CarouselDocs>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [CarouselDocs]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(CarouselDocs);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
package/playground/showcase/src/app/pages/documentation/components/carousel-docs/carousel-docs.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { Breadcrumb, BreadcrumbItem } from '../../../../components/breadcrumb/breadcrumb';
|
|
4
|
+
import { Carousel } from '../../../../components/carousel/carousel';
|
|
5
|
+
|
|
6
|
+
import { AiPromptService } from '../../../../core/services/ai-prompt.service';
|
|
7
|
+
import { CarouselCodeExamples } from './carousel-docs.examples';
|
|
8
|
+
import { getFormattedCode } from '../../../../core/utils/prism';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'wally-carousel-docs',
|
|
12
|
+
imports: [
|
|
13
|
+
Carousel,
|
|
14
|
+
Breadcrumb
|
|
15
|
+
],
|
|
16
|
+
templateUrl: './carousel-docs.html',
|
|
17
|
+
styleUrl: './carousel-docs.css'
|
|
18
|
+
})
|
|
19
|
+
export class CarouselDocs {
|
|
20
|
+
breadcrumbItems: BreadcrumbItem[] = [
|
|
21
|
+
{ label: 'Home', url: '/' },
|
|
22
|
+
{ label: 'Documentation', url: '/documentation' },
|
|
23
|
+
{ label: 'Components', url: '/documentation/components' },
|
|
24
|
+
{ label: 'Carousel' }
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Code examples with highlighting
|
|
28
|
+
installationCode = getFormattedCode(CarouselCodeExamples.installation, 'bash');
|
|
29
|
+
importCode = getFormattedCode(CarouselCodeExamples.import, 'typescript');
|
|
30
|
+
componentImportCode = getFormattedCode(CarouselCodeExamples.componentImport, 'typescript');
|
|
31
|
+
basicUsageCode = getFormattedCode(CarouselCodeExamples.basicUsage, 'html');
|
|
32
|
+
customContentCode = getFormattedCode(CarouselCodeExamples.customContent, 'html');
|
|
33
|
+
propertiesCode = getFormattedCode(CarouselCodeExamples.properties, 'typescript');
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private aiPromptService: AiPromptService
|
|
37
|
+
) { }
|
|
38
|
+
|
|
39
|
+
get claudeUrl(): string {
|
|
40
|
+
return this.aiPromptService.generateClaudeUrl();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get chatGptUrl(): string {
|
|
44
|
+
return this.aiPromptService.generateChatGptUrl();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -60,6 +60,19 @@
|
|
|
60
60
|
</a>
|
|
61
61
|
</div>
|
|
62
62
|
|
|
63
|
+
<div class="bg-gray-200 dark:bg-[#121212] rounded-lg p-4">
|
|
64
|
+
<div class="flex items-center gap-2 mb-2">
|
|
65
|
+
<h4 class="text-md font-semibold text-[#0a0a0a] dark:text-white">Carousel</h4>
|
|
66
|
+
<span class="text-xs bg-yellow-500 text-black px-2 py-1 rounded">Under Construction</span>
|
|
67
|
+
</div>
|
|
68
|
+
<p class="text-sm text-gray-700 dark:text-gray-400 mb-3">
|
|
69
|
+
A smooth carousel component with navigation controls, dot indicators, and elegant transitions using circular buffer algorithm.
|
|
70
|
+
</p>
|
|
71
|
+
<a href="/documentation/components/carousel" class="text-blue-500 underline hover:text-blue-700 text-sm">
|
|
72
|
+
View Documentation
|
|
73
|
+
</a>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
63
76
|
<div class="w-auto h-auto">
|
|
64
77
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4944285767597175"
|
|
65
78
|
crossorigin="anonymous"></script>
|
|
@@ -16,5 +16,9 @@ export const componentsRoutes: Routes = [
|
|
|
16
16
|
{
|
|
17
17
|
path: 'input',
|
|
18
18
|
loadComponent: () => import('./input-docs/input-docs').then(m => m.InputDocs)
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: 'carousel',
|
|
22
|
+
loadComponent: () => import('./carousel-docs/carousel-docs').then(m => m.CarouselDocs)
|
|
19
23
|
}
|
|
20
24
|
];
|