wally-ui 1.8.0 → 1.10.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wally-ui",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "About Where’s Wally? Right here — bringing you ready-to-use Angular components with Wally-UI. Stop searching, start building.",
5
5
  "bin": {
6
6
  "wally": "dist/cli.js"
@@ -1,28 +1,10 @@
1
- <button
2
- [type]="type()"
3
- [disabled]="disabled() || loading()"
4
- [attr.aria-label]="ariaLabel() || null"
5
- [attr.aria-describedby]="ariaDescribedBy() || null"
6
- [attr.aria-pressed]="ariaPressed()"
7
- [attr.aria-busy]="loading()"
8
- (click)="handleClick()"
9
- class="
10
- group
11
- relative
12
- w-full
13
- flex items-center justify-center gap-2
14
- text-white text-sm font-medium dark:text-[#0a0a0a]
15
- bg-[#0a0a0a] hover:bg-[#0a0a0a]/85
16
- disabled:bg-[#0a0a0a]/85
17
- dark:bg-white dark:hover:bg-white/85
18
- dark:disabled:bg-white/85
19
- disabled:pointer-events-none
20
- p-2.5
21
- rounded-md
22
- transition duration-300 ease-in-out
23
- antialiased
24
- cursor-pointer
25
- ">
1
+ <button [type]="type()" [disabled]="disabled() || loading()" [attr.aria-label]="ariaLabel() || null"
2
+ [attr.aria-describedby]="ariaDescribedBy() || null" [attr.aria-pressed]="ariaPressed()" [attr.aria-busy]="loading()"
3
+ (click)="handleClick()" class="group relative w-full flex items-center justify-center gap-2 text-sm font-medium disabled:pointer-events-none p-2.5 rounded-md transition duration-300 ease-in-out antialiased cursor-pointer
4
+ " [ngClass]="{
5
+ 'text-white dark:text-[#0a0a0a] bg-[#0a0a0a] hover:bg-[#0a0a0a]/85 disabled:bg-[#0a0a0a]/85 dark:bg-white dark:hover:bg-white/85 dark:disabled:bg-white/85 dark:disabled:text-[#0a0a0a]/60': variant() === 'primary',
6
+ 'text-[#0a0a0a] bg-neutral-200 hover:bg-neutral-200/60 disabled:bg-neutral-200/60 disabled:text-neutral-400 dark:text-white dark:bg-white/20 dark:hover:bg-white/10 dark:disabled:bg-white/5 dark:disabled:text-white/50': variant() === 'secondary',
7
+ }">
26
8
 
27
9
  @if (showNotification()) {
28
10
  <span class="absolute top-0 right-0 -mt-1 -mr-1 flex size-3">
@@ -34,8 +16,10 @@
34
16
  }
35
17
 
36
18
  @if (loading()) {
37
- <svg class="size-4 animate-spin text-white dark:text-[#0a0a0a]" xmlns="http://www.w3.org/2000/svg" fill="none"
38
- viewBox="0 0 24 24">
19
+ <svg class="size-4 animate-spin" [ngClass]="{
20
+ 'text-white dark:text-[#0a0a0a]': variant() === 'primary',
21
+ 'dark:text-white': variant() === 'secondary',
22
+ }" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
39
23
  <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
40
24
  <path class="opacity-75" fill="currentColor"
41
25
  d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
@@ -1,6 +1,8 @@
1
1
  import { Component, input, InputSignal, output, OutputEmitterRef } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
 
4
+ type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
5
+
4
6
  @Component({
5
7
  selector: 'wally-button',
6
8
  imports: [
@@ -14,6 +16,7 @@ export class Button {
14
16
  disabled: InputSignal<boolean> = input<boolean>(false);
15
17
  loading: InputSignal<boolean> = input<boolean>(false);
16
18
  showNotification: InputSignal<boolean> = input<boolean>(false);
19
+ variant: InputSignal<ButtonVariant> = input<ButtonVariant>('primary');
17
20
 
18
21
  // Accessibility properties
19
22
  ariaLabel: InputSignal<string> = input<string>('');
@@ -1,32 +1,46 @@
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>
1
+ <div class="w-full h-full flex flex-col items-center gap-4 p-2">
2
+ <div class="w-full h-full flex items-center gap-3">
3
+ <div>
4
+ <button type="button"
5
+ class="group hidden sm:block bg-neutral-100 hover:bg-neutral-200 border-2 border-neutral-200 hover:border-neutral-300 dark:bg-[#1b1b1b] dark:hover:bg-[#1d1d1d] dark:border-neutral-700 hover:dark:border-neutral-600 p-2 rounded-full cursor-pointer transition-all duration-500 ease-in-out"
6
+ (click)="navigateToPreviousItem()" aria-label="Go to previous slide">
7
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
8
+ stroke="currentColor"
9
+ class="size-5 text-neutral-500 dark:text-neutral-300 group-hover:dark:text-white transition-all duration-500 ease-in-out">
10
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
11
+ </svg>
12
+ </button>
13
+ </div>
14
+
15
+ <div class="relative w-full h-full overflow-hidden p-2 shadow rounded-lg" #carouselContainer>
16
+ <ng-content></ng-content>
17
+ </div>
5
18
 
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)">
19
+ <div>
20
+ <button type="button"
21
+ class="group hidden sm:block bg-neutral-100 hover:bg-neutral-200 border-2 border-neutral-200 hover:border-neutral-300 dark:bg-[#1b1b1b] dark:hover:bg-[#1d1d1d] dark:border-neutral-700 hover:dark:border-neutral-600 p-2 rounded-full cursor-pointer transition-all duration-500 ease-in-out"
22
+ (click)="navigateToNextItem()" aria-label="Go to next slide">
23
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
24
+ stroke="currentColor"
25
+ class="size-5 text-neutral-500 dark:text-neutral-300 group-hover:dark:text-white transition-all duration-500 ease-in-out">
26
+ <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
27
+ </svg>
16
28
  </button>
17
- }
29
+ </div>
18
30
  </div>
19
31
 
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
32
+ @if (isNavigationIndicator()) {
33
+ <div class="flex items-center gap-2">
34
+ @for (navigationIndicator of navigationIndicatorsArray; track $index; let indicatorIndex = $index) {
35
+ <button type="button"
36
+ class="w-3.5 h-1.5 rounded-sm transition-all duration-500 ease-in-out cursor-pointer focus:outline-none focus:ring-0 active:outline-none"
37
+ [ngClass]="{
38
+ 'bg-blue-500': indicatorIndex === currentVisibleItemIndex(),
39
+ 'bg-neutral-300 hover:bg-neutral-400 hover:scale-105 dark:bg-neutral-700': indicatorIndex !== currentVisibleItemIndex()
40
+ }" (click)="navigateToSpecificItem(indicatorIndex)"
41
+ [attr.aria-label]="'Navigate to slide ' + (indicatorIndex + 1)">
30
42
  </button>
43
+ }
31
44
  </div>
45
+ }
32
46
  </div>
@@ -1,4 +1,4 @@
1
- import { Component, input, OnInit, signal, ViewChild, ElementRef, AfterViewInit, Renderer2, OnDestroy, HostListener } from '@angular/core';
1
+ import { Component, signal, ViewChild, ElementRef, AfterViewInit, Renderer2, OnDestroy, HostListener, WritableSignal, input, InputSignal } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
 
4
4
  @Component({
@@ -6,14 +6,14 @@ import { CommonModule } from '@angular/common';
6
6
  imports: [CommonModule],
7
7
  templateUrl: './carousel.html',
8
8
  })
9
- export class Carousel implements OnInit, AfterViewInit, OnDestroy {
9
+ export class Carousel implements AfterViewInit, OnDestroy {
10
10
  @ViewChild('carouselContainer', { static: false }) carouselContainer!: ElementRef;
11
11
 
12
- totalItemsCount = signal<number>(0);
13
- currentVisibleItemIndex = signal<number>(0);
12
+ isNavigationIndicator: InputSignal<boolean> = input<boolean>(false);
13
+ totalItemsCount: WritableSignal<number> = signal<number>(0);
14
+ currentVisibleItemIndex: WritableSignal<number> = signal<number>(0);
14
15
  carouselItemElements: HTMLElement[] = [];
15
16
 
16
- // Touch and swipe gesture state
17
17
  private gestureState = {
18
18
  startPositionX: 0,
19
19
  startPositionY: 0,
@@ -23,22 +23,19 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
23
23
  gestureStartTime: 0
24
24
  };
25
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
26
+ private readonly MINIMUM_SWIPE_DISTANCE = 50;
27
+ private readonly MINIMUM_SWIPE_VELOCITY = 0.3;
28
+ private readonly MAXIMUM_SWIPE_DURATION = 300;
30
29
 
31
- // Event listener cleanup functions
32
30
  private eventListenerCleanupFunctions: (() => void)[] = [];
33
31
 
34
- get navigationDotsArray() {
32
+ get navigationIndicatorsArray() {
35
33
  return Array(this.totalItemsCount()).fill(0);
36
34
  }
37
35
 
38
- constructor(private renderer: Renderer2) { }
39
-
40
- ngOnInit(): void {
41
- }
36
+ constructor(
37
+ private renderer: Renderer2
38
+ ) { }
42
39
 
43
40
  ngAfterViewInit(): void {
44
41
  if (this.carouselContainer) {
@@ -49,16 +46,76 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
49
46
  }
50
47
 
51
48
  ngOnDestroy(): void {
52
- // Clean up all event listeners to prevent memory leaks
53
49
  this.eventListenerCleanupFunctions.forEach(cleanupFunction => cleanupFunction());
54
50
  }
55
51
 
52
+ calculateNextItemIndex(currentItemIndex: number): number {
53
+ return (currentItemIndex + 1) % this.totalItemsCount();
54
+ }
55
+
56
+ calculatePreviousItemIndex(currentItemIndex: number): number {
57
+ return (currentItemIndex - 1 + this.totalItemsCount()) % this.totalItemsCount();
58
+ }
59
+
60
+ navigateToNextItem(): void {
61
+ this.currentVisibleItemIndex.set(this.calculateNextItemIndex(this.currentVisibleItemIndex()));
62
+ this.updateAllItemElementPositions();
63
+ }
64
+
65
+ navigateToPreviousItem(): void {
66
+ this.currentVisibleItemIndex.set(this.calculatePreviousItemIndex(this.currentVisibleItemIndex()));
67
+ this.updateAllItemElementPositions();
68
+ }
69
+
70
+ navigateToSpecificItem(targetItemIndex: number): void {
71
+ this.currentVisibleItemIndex.set(targetItemIndex);
72
+ this.updateAllItemElementPositions();
73
+ }
74
+
75
+ updateItemElementPosition(carouselItemElement: HTMLElement, itemIndex: number): void {
76
+ const currentVisibleIndex = this.currentVisibleItemIndex();
77
+
78
+ if (itemIndex === currentVisibleIndex) {
79
+ this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(0)');
80
+ } else if (itemIndex > currentVisibleIndex) {
81
+ this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(110%)');
82
+ } else {
83
+ this.renderer.setStyle(carouselItemElement, 'transform', 'translateX(-110%)');
84
+ }
85
+ }
86
+
87
+ updateAllItemElementPositions(): void {
88
+ this.carouselItemElements.forEach((itemElement, itemIndex) => {
89
+ this.updateItemElementPosition(itemElement, itemIndex);
90
+ });
91
+ }
92
+
93
+ @HostListener('keydown', ['$event'])
94
+ handleKeyboardNavigation(keyboardEvent: KeyboardEvent): void {
95
+ switch (keyboardEvent.key) {
96
+ case 'ArrowLeft':
97
+ keyboardEvent.preventDefault();
98
+ this.navigateToPreviousItem();
99
+ break;
100
+ case 'ArrowRight':
101
+ keyboardEvent.preventDefault();
102
+ this.navigateToNextItem();
103
+ break;
104
+ case 'Home':
105
+ keyboardEvent.preventDefault();
106
+ this.navigateToSpecificItem(0);
107
+ break;
108
+ case 'End':
109
+ keyboardEvent.preventDefault();
110
+ this.navigateToSpecificItem(this.totalItemsCount() - 1);
111
+ break;
112
+ }
113
+ }
114
+
56
115
  private initializeCarouselItems(): void {
57
- // Get all direct children elements of the carousel container
58
116
  this.carouselItemElements = Array.from(this.carouselContainer.nativeElement.children);
59
117
  this.totalItemsCount.set(this.carouselItemElements.length);
60
118
 
61
- // Apply positioning and styling to each carousel item using Renderer2
62
119
  this.carouselItemElements.forEach((itemElement, itemIndex) => {
63
120
  this.renderer.setStyle(itemElement, 'position', 'absolute');
64
121
  this.renderer.setStyle(itemElement, 'inset', '0');
@@ -73,18 +130,15 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
73
130
  private setupTouchAndMouseEvents(): void {
74
131
  const carouselContainerElement = this.carouselContainer.nativeElement;
75
132
 
76
- // Touch events for mobile devices
77
133
  const touchStartListener = this.renderer.listen(carouselContainerElement, 'touchstart', (event) => this.handleTouchStart(event));
78
134
  const touchMoveListener = this.renderer.listen(carouselContainerElement, 'touchmove', (event) => this.handleTouchMove(event));
79
135
  const touchEndListener = this.renderer.listen(carouselContainerElement, 'touchend', () => this.handleTouchEnd());
80
136
 
81
- // Mouse events for desktop devices
82
137
  const mouseDownListener = this.renderer.listen(carouselContainerElement, 'mousedown', (event) => this.handleMouseDown(event));
83
138
  const mouseMoveListener = this.renderer.listen(carouselContainerElement, 'mousemove', (event) => this.handleMouseMove(event));
84
139
  const mouseUpListener = this.renderer.listen(carouselContainerElement, 'mouseup', () => this.handleMouseUp());
85
140
  const mouseLeaveListener = this.renderer.listen(carouselContainerElement, 'mouseleave', () => this.handleMouseUp());
86
141
 
87
- // Store all cleanup functions for proper memory management
88
142
  this.eventListenerCleanupFunctions.push(
89
143
  touchStartListener,
90
144
  touchMoveListener,
@@ -95,48 +149,24 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
95
149
  mouseLeaveListener
96
150
  );
97
151
 
98
- // Optimize touch performance by restricting to horizontal panning only
99
152
  this.renderer.setStyle(carouselContainerElement, 'touch-action', 'pan-x');
100
153
  }
101
154
 
102
155
  private setupAccessibilityAttributes(): void {
103
156
  const carouselContainerElement = this.carouselContainer.nativeElement;
104
157
 
105
- // Set ARIA attributes for screen readers and accessibility
106
158
  this.renderer.setAttribute(carouselContainerElement, 'role', 'region');
107
159
  this.renderer.setAttribute(carouselContainerElement, 'aria-label', 'Carousel');
108
160
  this.renderer.setAttribute(carouselContainerElement, 'tabindex', '0');
109
161
  }
110
162
 
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
163
  private handleTouchStart(touchEvent: TouchEvent): void {
134
164
  this.initializeGesture(touchEvent.touches[0].clientX, touchEvent.touches[0].clientY);
135
165
  }
136
166
 
137
167
  private handleTouchMove(touchEvent: TouchEvent): void {
138
168
  if (this.gestureState.isCurrentlyDragging) {
139
- touchEvent.preventDefault(); // Prevent page scrolling during swipe
169
+ touchEvent.preventDefault();
140
170
  this.updateGesturePosition(touchEvent.touches[0].clientX, touchEvent.touches[0].clientY);
141
171
  }
142
172
  }
@@ -145,14 +175,13 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
145
175
  this.finalizeGestureAndNavigate();
146
176
  }
147
177
 
148
- // Mouse event handlers for desktop devices
149
178
  private handleMouseDown(mouseEvent: MouseEvent): void {
150
179
  this.initializeGesture(mouseEvent.clientX, mouseEvent.clientY);
151
180
  }
152
181
 
153
182
  private handleMouseMove(mouseEvent: MouseEvent): void {
154
183
  if (this.gestureState.isCurrentlyDragging) {
155
- mouseEvent.preventDefault(); // Prevent text selection during drag
184
+ mouseEvent.preventDefault();
156
185
  this.updateGesturePosition(mouseEvent.clientX, mouseEvent.clientY);
157
186
  }
158
187
  }
@@ -161,7 +190,6 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
161
190
  this.finalizeGestureAndNavigate();
162
191
  }
163
192
 
164
- // Unified gesture handling methods
165
193
  private initializeGesture(xPosition: number, yPosition: number): void {
166
194
  this.gestureState = {
167
195
  startPositionX: xPosition,
@@ -188,70 +216,18 @@ export class Carousel implements OnInit, AfterViewInit, OnDestroy {
188
216
  const gestureDuration = Date.now() - this.gestureState.gestureStartTime;
189
217
  const gestureVelocity = Math.abs(horizontalDistanceMoved) / gestureDuration;
190
218
 
191
- // Only process horizontal swipes (ignore vertical movements)
192
219
  if (Math.abs(horizontalDistanceMoved) > Math.abs(verticalDistanceMoved)) {
193
- // Check if gesture meets minimum requirements for navigation
194
220
  const isValidSwipeGesture = Math.abs(horizontalDistanceMoved) > this.MINIMUM_SWIPE_DISTANCE ||
195
221
  (gestureVelocity > this.MINIMUM_SWIPE_VELOCITY && gestureDuration < this.MAXIMUM_SWIPE_DURATION);
196
222
 
197
223
  if (isValidSwipeGesture) {
198
224
  if (horizontalDistanceMoved > 0) {
199
- this.navigateToPreviousItem(); // Swipe right = go to previous item
225
+ this.navigateToPreviousItem();
200
226
  } else {
201
- this.navigateToNextItem(); // Swipe left = go to next item
227
+ this.navigateToNextItem();
202
228
  }
203
229
  }
204
230
  }
205
-
206
- // Reset gesture state after processing
207
231
  this.gestureState.isCurrentlyDragging = false;
208
232
  }
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
- }
233
+ }
@@ -15,6 +15,10 @@ export const ButtonCodeExamples = {
15
15
  // Basic usage
16
16
  basicUsage: `<wally-button>Wally Button</wally-button>`,
17
17
 
18
+ // Variants
19
+ primaryVariant: `<wally-button variant="primary">Primary Button</wally-button>`,
20
+ secondaryVariant: `<wally-button variant="secondary">Secondary Button</wally-button>`,
21
+
18
22
  // States
19
23
  disabled: `<wally-button [disabled]="true">Disabled</wally-button>`,
20
24
  loading: `<wally-button [loading]="true">Loading</wally-button>`,