@kubex/zinc 1.0.25 → 1.0.99

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.
Files changed (199) hide show
  1. package/.claude/project.md +556 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +9 -10
  3. package/dist/custom-elements.json +13575 -8856
  4. package/dist/vscode.html-custom-data.json +583 -48
  5. package/dist/web-types.json +1441 -181
  6. package/dist/zn.d.ts +1645 -660
  7. package/dist/zn.min.css +1 -1
  8. package/dist/zn.min.js +1412 -893
  9. package/docs/_utilities/code-previews.cjs +10 -1
  10. package/docs/assets/scripts/docs.js +42 -18
  11. package/docs/data/data-table.json +229 -66
  12. package/docs/data/empty.json +6 -0
  13. package/docs/data/products-table.json +41 -193
  14. package/docs/pages/components/action-bar.md +121 -6
  15. package/docs/pages/components/alert.md +118 -15
  16. package/docs/pages/components/animated-button.md +299 -0
  17. package/docs/pages/components/audio-select.md +534 -0
  18. package/docs/pages/components/bulk-actions.md +79 -5
  19. package/docs/pages/components/button-group.md +235 -27
  20. package/docs/pages/components/button.md +139 -1
  21. package/docs/pages/components/chart.md +365 -9
  22. package/docs/pages/components/checkbox.md +387 -58
  23. package/docs/pages/components/chip.md +253 -33
  24. package/docs/pages/components/collapsible.md +393 -46
  25. package/docs/pages/components/cols.md +152 -30
  26. package/docs/pages/components/confirm.md +182 -16
  27. package/docs/pages/components/copy-button.md +169 -6
  28. package/docs/pages/components/data-select.md +194 -15
  29. package/docs/pages/components/data-table-filter.md +96 -5
  30. package/docs/pages/components/data-table-search.md +451 -0
  31. package/docs/pages/components/data-table-sort.md +66 -5
  32. package/docs/pages/components/data-table.md +727 -66
  33. package/docs/pages/components/datepicker.md +402 -7
  34. package/docs/pages/components/defined-label.md +120 -5
  35. package/docs/pages/components/dialog.md +339 -59
  36. package/docs/pages/components/dropdown.md +366 -11
  37. package/docs/pages/components/editor.md +604 -7
  38. package/docs/pages/components/empty-state.md +315 -16
  39. package/docs/pages/components/expanding-action.md +212 -28
  40. package/docs/pages/components/file.md +421 -12
  41. package/docs/pages/components/filter-container.md +158 -5
  42. package/docs/pages/components/filter-wrapper.md +167 -6
  43. package/docs/pages/components/form-group.md +383 -6
  44. package/docs/pages/components/hover-container.md +237 -6
  45. package/docs/pages/components/icon-picker.md +121 -0
  46. package/docs/pages/components/icon.md +454 -48
  47. package/docs/pages/components/inline-edit.md +787 -19
  48. package/docs/pages/components/input-group.md +207 -13
  49. package/docs/pages/components/input.md +194 -0
  50. package/docs/pages/components/item.md +18 -0
  51. package/docs/pages/components/key-container.md +115 -0
  52. package/docs/pages/components/key.md +101 -0
  53. package/docs/pages/components/menu.md +512 -6
  54. package/docs/pages/components/note.md +239 -36
  55. package/docs/pages/components/opt-group.md +107 -0
  56. package/docs/pages/components/pagination.md +332 -6
  57. package/docs/pages/components/pane.md +222 -6
  58. package/docs/pages/components/panel.md +213 -26
  59. package/docs/pages/components/popup.md +1354 -6
  60. package/docs/pages/components/priority-list.md +233 -0
  61. package/docs/pages/components/progress-bar.md +387 -6
  62. package/docs/pages/components/radio-group.md +242 -5
  63. package/docs/pages/components/radio.md +474 -6
  64. package/docs/pages/components/rating.md +331 -7
  65. package/docs/pages/components/reveal.md +524 -6
  66. package/docs/pages/components/scroll-container.md +552 -6
  67. package/docs/pages/components/select.md +762 -160
  68. package/docs/pages/components/skeleton.md +473 -30
  69. package/docs/pages/components/slideout.md +261 -5
  70. package/docs/pages/components/sp.md +99 -41
  71. package/docs/pages/components/split-button.md +347 -9
  72. package/docs/pages/components/split-pane.md +445 -6
  73. package/docs/pages/components/stat.md +549 -9
  74. package/docs/pages/components/status-indicator.md +230 -1
  75. package/docs/pages/components/stepper.md +563 -6
  76. package/docs/pages/components/tabs.md +439 -13
  77. package/docs/pages/components/textarea.md +415 -17
  78. package/docs/pages/components/tile.md +385 -26
  79. package/docs/pages/components/toggle.md +424 -6
  80. package/docs/pages/components/tooltip.md +367 -0
  81. package/docs/pages/components/translation-group.md +210 -0
  82. package/docs/pages/components/translations.md +319 -19
  83. package/docs/pages/components/vertical-stepper.md +401 -8
  84. package/docs/pages/components/well.md +300 -3
  85. package/docs/pages/getting-started/form-controls.md +162 -0
  86. package/docs/pages/index.md +116 -15
  87. package/package.json +5 -6
  88. package/scss/_global-spacing.scss +4 -0
  89. package/scss/_root.scss +2 -2
  90. package/scss/boot.scss +19 -0
  91. package/scss/shared/layout.scss +5 -0
  92. package/scss/themes/_light.scss +3 -3
  93. package/src/components/alert/alert.scss +1 -0
  94. package/src/components/animated-button/README.md +306 -0
  95. package/src/components/animated-button/animated-button.component.ts +229 -0
  96. package/src/components/animated-button/animated-button.scss +228 -0
  97. package/src/components/animated-button/animated-button.test.ts +143 -0
  98. package/src/components/animated-button/index.ts +12 -0
  99. package/src/components/button/button.component.ts +5 -0
  100. package/src/components/button-group/button-group.component.ts +25 -13
  101. package/src/components/button-group/button-group.scss +6 -0
  102. package/src/components/checkbox/checkbox.component.ts +4 -0
  103. package/src/components/checkbox/checkbox.scss +7 -4
  104. package/src/components/checkbox-group/checkbox-group.scss +7 -0
  105. package/src/components/chip/chip.component.ts +7 -2
  106. package/src/components/chip/chip.scss +16 -2
  107. package/src/components/collapsible/collapsible.component.ts +27 -20
  108. package/src/components/cols/cols.component.ts +14 -2
  109. package/src/components/cols/cols.scss +6 -12
  110. package/src/components/confirm/confirm.component.ts +7 -0
  111. package/src/components/copy-button/copy-button.component.ts +12 -11
  112. package/src/components/data-select/data-select.component.ts +215 -44
  113. package/src/components/data-select/data-select.scss +66 -6
  114. package/src/components/data-select/providers/currency-data-provider.ts +20 -6
  115. package/src/components/data-select/providers/provider.ts +1 -0
  116. package/src/components/data-select/providers/us-state-data-provider.ts +68 -0
  117. package/src/components/data-table/data-table.component.ts +81 -22
  118. package/src/components/data-table-search/data-table-search.component.ts +184 -0
  119. package/src/components/data-table-search/data-table-search.scss +17 -0
  120. package/src/components/data-table-search/data-table-search.test.ts +11 -0
  121. package/src/components/data-table-search/index.ts +12 -0
  122. package/src/components/datepicker/datepicker.component.ts +347 -13
  123. package/src/components/dialog/dialog.component.ts +1 -1
  124. package/src/components/empty-state/empty-state.scss +6 -1
  125. package/src/components/header/header.scss +3 -3
  126. package/src/components/icon/icon.component.ts +23 -1
  127. package/src/components/icon/icon.scss +23 -0
  128. package/src/components/icon-picker/brand-icons.ts +24 -0
  129. package/src/components/icon-picker/icon-picker.component.ts +407 -0
  130. package/src/components/icon-picker/icon-picker.scss +205 -0
  131. package/src/components/icon-picker/index.ts +12 -0
  132. package/src/components/icon-picker/line-icons.ts +612 -0
  133. package/src/components/icon-picker/material-icons.ts +14543 -0
  134. package/src/components/inline-edit/inline-edit.component.ts +190 -46
  135. package/src/components/inline-edit/inline-edit.scss +29 -0
  136. package/src/components/inline-edit/inline-edit.test.ts +314 -1
  137. package/src/components/input/input.component.ts +324 -5
  138. package/src/components/input/input.scss +87 -0
  139. package/src/components/input-group/input-group.component.ts +28 -7
  140. package/src/components/input-group/input-group.scss +48 -10
  141. package/src/components/item/item.component.ts +24 -3
  142. package/src/components/item/item.scss +18 -0
  143. package/src/components/key/index.ts +12 -0
  144. package/src/components/key/key.component.ts +47 -0
  145. package/src/components/key/key.scss +38 -0
  146. package/src/components/key-container/index.ts +12 -0
  147. package/src/components/key-container/key-container.component.ts +142 -0
  148. package/src/components/key-container/key-container.scss +33 -0
  149. package/src/components/menu-item/menu-item.component.ts +20 -0
  150. package/src/components/menu-item/menu-item.scss +69 -2
  151. package/src/components/navbar/navbar.component.ts +103 -11
  152. package/src/components/navbar/navbar.scss +6 -1
  153. package/src/components/opt-group/index.ts +12 -0
  154. package/src/components/opt-group/opt-group.component.ts +74 -0
  155. package/src/components/opt-group/opt-group.scss +37 -0
  156. package/src/components/opt-group/opt-group.test.ts +27 -0
  157. package/src/components/option/option.component.ts +1 -16
  158. package/src/components/option/option.scss +4 -0
  159. package/src/components/panel/panel.component.ts +10 -8
  160. package/src/components/panel/panel.scss +20 -0
  161. package/src/components/priority-list/index.ts +12 -0
  162. package/src/components/priority-list/priority-list.component.ts +611 -0
  163. package/src/components/priority-list/priority-list.scss +185 -0
  164. package/src/components/priority-list/priority-list.test.ts +139 -0
  165. package/src/components/progress-bar/progress-bar.component.ts +22 -16
  166. package/src/components/radio/radio.scss +10 -3
  167. package/src/components/select/select.component.ts +812 -72
  168. package/src/components/select/select.scss +108 -2
  169. package/src/components/select/select.test.ts +147 -2
  170. package/src/components/settings-container/settings-container.component.ts +44 -19
  171. package/src/components/settings-container/settings-container.scss +1 -1
  172. package/src/components/skeleton/skeleton.component.ts +6 -1
  173. package/src/components/slideout/slideout.scss +8 -3
  174. package/src/components/sp/sp.component.ts +3 -1
  175. package/src/components/sp/sp.scss +19 -0
  176. package/src/components/stepper/stepper.component.ts +16 -10
  177. package/src/components/textarea/textarea.scss +0 -4
  178. package/src/components/tile/tile.component.ts +12 -3
  179. package/src/components/tile/tile.scss +1 -1
  180. package/src/components/timer/timer.component.ts +15 -2
  181. package/src/components/toggle/toggle.component.ts +29 -26
  182. package/src/components/toggle/toggle.scss +13 -1
  183. package/src/components/translation-group/index.ts +12 -0
  184. package/src/components/translation-group/translation-group.component.ts +207 -0
  185. package/src/components/translation-group/translation-group.scss +8 -0
  186. package/src/components/translations/translations.component.ts +90 -44
  187. package/src/components/translations/translations.scss +4 -0
  188. package/src/components/vertical-stepper/vertical-stepper.component.ts +14 -9
  189. package/src/events/events.ts +12 -7
  190. package/src/events/zn-language-change.ts +7 -0
  191. package/src/events/zn-purchase.ts +11 -0
  192. package/src/events/zn-redirect.ts +7 -0
  193. package/src/events/zn-reorder.ts +7 -0
  194. package/src/events/zn-search-change.ts +11 -0
  195. package/src/events/zn-submit.ts +1 -1
  196. package/src/form-control.scss +4 -0
  197. package/src/internal/form-navigation.ts +510 -0
  198. package/src/internal/form.ts +6 -0
  199. package/src/zinc.ts +91 -83
package/scss/_root.scss CHANGED
@@ -83,11 +83,11 @@
83
83
  --zn-shadow: 10, 10, 10;
84
84
  --zn-panel-secondary: 27, 27, 30;
85
85
  --zn-panel-opacity: 1;
86
- --zn-text: 149, 163, 184;
86
+ --zn-text: 159, 173, 194;
87
87
  --zn-text-heading: 241, 245, 249;
88
88
  --zn-text-page-title: 225, 225, 225;
89
89
  --zn-text-panel-title: 181, 158, 255;
90
- --zn-panel-highlight: 255, 255, 255;
90
+ --zn-panel-highlight: 25, 27, 32;
91
91
  --zn-panel-highlight-opacity: 0.1;
92
92
  --zn-border-color: 50, 50, 60;
93
93
  --zn-input-bg: 28, 28, 32;
package/scss/boot.scss CHANGED
@@ -40,6 +40,25 @@
40
40
  background-color: var(--zn-input-background-color);
41
41
  }
42
42
 
43
+ .air-datepicker-global-container {
44
+ z-index: 1000000;
45
+ }
46
+
47
+ .air-datepicker-button {
48
+ border: none;
49
+ padding: var(--zn-spacing-2x-small) var(--zn-spacing-x-small);
50
+ border-radius: var(--adp-border-radius);
51
+ font-family: var(--adp-font-family), sans-serif;
52
+ font-size: var(--zn-font-size-medium);
53
+ font-weight: var(--zn-font-weight-normal);
54
+ cursor: pointer;
55
+ color: rgba(var(--zn-text), var(--zn-text-opacity));
56
+
57
+ &:hover {
58
+ background-color: var(--zn-input-background-color-hover) !important;
59
+ }
60
+ }
61
+
43
62
  // Utility to minimize flashing of undefined web components
44
63
  .zn-cloak:has(:not(:defined)) {
45
64
  animation: 2s step-end zn-cloak;
@@ -48,6 +48,11 @@
48
48
  justify-content: space-between;
49
49
  }
50
50
 
51
+ .flex-end {
52
+ display: flex;
53
+ justify-content: flex-end;
54
+ }
55
+
51
56
  .flex-bottom {
52
57
  display: flex;
53
58
  align-items: flex-end;
@@ -390,8 +390,8 @@
390
390
  --zn-font-size-x-small: 0.75rem; // 12px // 10.5px
391
391
  --zn-font-size-small: 0.929rem; // 14px // 13px
392
392
  --zn-font-size-medium: 1rem; // 16px // 14px
393
- --zn-font-size-large: 1.25rem; // 20px // 16px
394
- --zn-font-size-x-large: 1.5rem; // 24px // 21px
393
+ --zn-font-size-large: 1.15rem; // 20px // 16px
394
+ --zn-font-size-x-large: 1.25rem; // 24px // 21px
395
395
  --zn-font-size-2x-large: 2.25rem; // 36px // 31.5px
396
396
  --zn-font-size-3x-large: 3rem; // 48px // 42px
397
397
  --zn-font-size-4x-large: 4.5rem; // 72px // 63px
@@ -512,7 +512,7 @@
512
512
  --zn-toggle-size-large: 1.375rem; // 22px
513
513
 
514
514
  /* Overlays */
515
- --zn-overlay-background-color: hsl(240 3.8% 46.1% / 33%);
515
+ --zn-overlay-background-color: hsl(none 0% 0% / 0.7);
516
516
 
517
517
  /* Panels */
518
518
  --zn-panel-background-color: var(--zn-color-neutral-0);
@@ -45,6 +45,7 @@ p {
45
45
  display: flex;
46
46
  flex-direction: column;
47
47
  flex-grow: 1;
48
+ align-self: start;
48
49
  text-align: left;
49
50
  padding: 8px;
50
51
 
@@ -0,0 +1,306 @@
1
+ # Animated Button Component
2
+
3
+ A stateful button component designed for purchase/payment flows with built-in loading, success, and error states, complete with smooth animations and automatic state management.
4
+
5
+ ## Features
6
+
7
+ - **4 States**: Idle (blue), Processing (blue with shimmer), Success (green with animated fill and drawing checkmark), Failure (red with error)
8
+ - **Smooth Animations**:
9
+ - Success state: Background fills from bottom with green color
10
+ - Checkmark draws itself with a satisfying "swoosh" effect
11
+ - Shimmer animation during processing
12
+ - Shake animation on failure
13
+ - **Auto State Management**: Automatically resets from failure state after a configurable delay
14
+ - **Auto Redirect**: Redirects after successful purchase with configurable delay
15
+ - **Event-Driven**: Emit custom events for purchase flow integration
16
+ - **Accessible**: Supports `aria-live` regions and respects `prefers-reduced-motion`
17
+ - **Consistent Height**: Button maintains the same height across all states
18
+
19
+ ## Installation
20
+
21
+ ```typescript
22
+ import '@kubex/zinc/components/animated-button';
23
+ ```
24
+
25
+ Or if auto-loading is enabled, just use the tag:
26
+
27
+ ```html
28
+ <zn-animated-button></zn-animated-button>
29
+ ```
30
+
31
+ ## Basic Usage
32
+
33
+ ```html
34
+ <zn-animated-button id="purchase-btn"></zn-animated-button>
35
+
36
+ <script>
37
+ const button = document.getElementById('purchase-btn');
38
+
39
+ button.addEventListener('zn-purchase', async (event) => {
40
+ const { setSuccess, setFailure } = event.detail;
41
+
42
+ try {
43
+ const response = await fetch('/api/purchase', { method: 'POST' });
44
+
45
+ if (response.ok) {
46
+ setSuccess();
47
+ } else {
48
+ setFailure('Payment failed');
49
+ }
50
+ } catch (error) {
51
+ setFailure(error.message);
52
+ }
53
+ });
54
+ </script>
55
+ ```
56
+
57
+ ## Properties
58
+
59
+ | Property | Type | Default | Description |
60
+ |----------|------|---------|-------------|
61
+ | `idle-text` | `string` | `'Purchase'` | Text displayed in idle state |
62
+ | `processing-text` | `string` | `'Purchasing'` | Text displayed in processing state |
63
+ | `success-text` | `string` | `'Success'` | Text displayed in success state |
64
+ | `failure-text` | `string` | `'Failed'` | Text displayed in failure state |
65
+ | `redirect-url` | `string` | `''` | URL to redirect to after success |
66
+ | `redirect-delay` | `number` | `1500` | Milliseconds to wait before redirecting (after success) |
67
+ | `failure-reset-delay` | `number` | `2000` | Milliseconds to wait before auto-resetting from failure |
68
+ | `disabled` | `boolean` | `false` | Whether the button is disabled |
69
+ | `name` | `string` | `''` | Form control name |
70
+ | `value` | `string` | `''` | Form control value |
71
+
72
+ ## Methods
73
+
74
+ ### `purchase(): Promise<void>`
75
+
76
+ Programmatically trigger the purchase flow.
77
+
78
+ ```javascript
79
+ const button = document.getElementById('purchase-btn');
80
+ await button.purchase();
81
+ ```
82
+
83
+ ### `setSuccess(): void`
84
+
85
+ Manually set the button to success state. Triggers the redirect timer.
86
+
87
+ ```javascript
88
+ button.setSuccess();
89
+ ```
90
+
91
+ ### `setFailure(message?: string): void`
92
+
93
+ Manually set the button to failure state with an optional error message. Triggers the auto-reset timer.
94
+
95
+ ```javascript
96
+ button.setFailure('Payment declined');
97
+ ```
98
+
99
+ ### `reset(): void`
100
+
101
+ Reset the button to idle state, clearing any timers.
102
+
103
+ ```javascript
104
+ button.reset();
105
+ ```
106
+
107
+ ## Events
108
+
109
+ ### `zn-purchase`
110
+
111
+ Emitted when the button is clicked in idle state. The event detail contains:
112
+
113
+ - `value`: The form control value
114
+ - `setSuccess()`: Function to set success state
115
+ - `setFailure(message?: string)`: Function to set failure state
116
+
117
+ ```javascript
118
+ button.addEventListener('zn-purchase', (event) => {
119
+ const { value, setSuccess, setFailure } = event.detail;
120
+
121
+ // Implement purchase logic
122
+ if (purchaseSuccessful) {
123
+ setSuccess();
124
+ } else {
125
+ setFailure('Error message');
126
+ }
127
+ });
128
+ ```
129
+
130
+ **Note**: The event is cancelable. Call `event.preventDefault()` to prevent the state transition.
131
+
132
+ ### `zn-redirect`
133
+
134
+ Emitted when the redirect timer completes after success. The event detail contains:
135
+
136
+ - `url`: The redirect URL
137
+
138
+ ```javascript
139
+ button.addEventListener('zn-redirect', (event) => {
140
+ console.log('Redirecting to:', event.detail.url);
141
+ });
142
+ ```
143
+
144
+ ## State Flow
145
+
146
+ ```
147
+ ┌─────────┐
148
+ │ Idle │ (Blue)
149
+ │ Default │
150
+ └────┬────┘
151
+ │ Click
152
+
153
+ ┌─────────┐
154
+ │Processing│ (Blue + Shimmer)
155
+ │ Disabled │
156
+ └────┬────┘
157
+
158
+ ┌─────┴─────┐
159
+ │ │
160
+ ↓ ↓
161
+ ┌─────────┐ ┌─────────┐
162
+ │Success │ │ Failure │ (Red + Error Icon)
163
+ │(Green + │ │ Disabled│
164
+ │ Check) │ └────┬────┘
165
+ └────┬────┘ │
166
+ │ │ Auto-reset (2s)
167
+ │ ↓
168
+ │ ┌─────────┐
169
+ │ │ Idle │
170
+ │ └─────────┘
171
+
172
+ │ Redirect (1.5s)
173
+
174
+ [Redirect]
175
+ ```
176
+
177
+ ## Styling
178
+
179
+ The component uses CSS custom properties from the Zinc design system:
180
+
181
+ - `--zn-primary`: Primary color (blue, idle state)
182
+ - `--zn-color-success`: Success color (green)
183
+ - `--zn-color-error`: Error color (red)
184
+ - `--zn-transition-medium`: Transition duration for color changes
185
+ - `--zn-transition-fast`: Transition duration for interactions
186
+ - `--zn-border-radius-medium`: Border radius
187
+
188
+ ### CSS Parts
189
+
190
+ The component doesn't expose CSS parts but follows BEM naming:
191
+
192
+ - `.purchase-button`: Main button element
193
+ - `.purchase-button--idle`: Idle state modifier
194
+ - `.purchase-button--processing`: Processing state modifier
195
+ - `.purchase-button--success`: Success state modifier
196
+ - `.purchase-button--failure`: Failure state modifier
197
+ - `.purchase-button__content`: Content wrapper
198
+ - `.purchase-button__text`: Text element
199
+ - `.purchase-button__spinner`: Spinning loading icon
200
+ - `.purchase-button__shimmer`: Shimmer animation overlay
201
+ - `.purchase-button__icon`: Icon element
202
+
203
+ ## Accessibility
204
+
205
+ - The button includes `aria-live="polite"` for screen reader announcements of state changes
206
+ - The button includes `aria-busy` attribute during processing state
207
+ - Respects `prefers-reduced-motion` media query:
208
+ - Disables all animations including success fill, checkmark drawing, and shimmer
209
+ - Shows instant state changes for users who prefer reduced motion
210
+ - Proper focus states with visible outline
211
+ - Disabled state prevents interaction and is properly announced
212
+ - Consistent button height prevents layout shift that could confuse users
213
+
214
+ ## Examples
215
+
216
+ ### Simple Success Flow
217
+
218
+ ```html
219
+ <zn-animated-button id="btn"></zn-animated-button>
220
+
221
+ <script>
222
+ document.getElementById('btn').addEventListener('zn-purchase', async (e) => {
223
+ const { setSuccess } = e.detail;
224
+ await new Promise(r => setTimeout(r, 2000)); // Simulate API
225
+ setSuccess();
226
+ });
227
+ </script>
228
+ ```
229
+
230
+ ### With Error Handling
231
+
232
+ ```html
233
+ <zn-animated-button
234
+ id="btn"
235
+ failure-text="Payment Failed"
236
+ ></zn-animated-button>
237
+
238
+ <script>
239
+ document.getElementById('btn').addEventListener('zn-purchase', async (e) => {
240
+ const { setSuccess, setFailure } = e.detail;
241
+
242
+ try {
243
+ const response = await fetch('/api/purchase', { method: 'POST' });
244
+ const data = await response.json();
245
+
246
+ if (response.ok) {
247
+ setSuccess();
248
+ } else {
249
+ setFailure(data.error || 'Payment failed');
250
+ }
251
+ } catch (error) {
252
+ setFailure('Network error');
253
+ }
254
+ });
255
+ </script>
256
+ ```
257
+
258
+ ### Custom Text and Timing
259
+
260
+ ```html
261
+ <zn-animated-button
262
+ idle-text="Complete Purchase"
263
+ processing-text="Processing Payment"
264
+ success-text="Payment Complete"
265
+ failure-text="Transaction Failed"
266
+ redirect-url="/thank-you"
267
+ redirect-delay="3000"
268
+ failure-reset-delay="4000"
269
+ ></zn-animated-button>
270
+ ```
271
+
272
+ ### Manual Control
273
+
274
+ ```javascript
275
+ const button = document.getElementById('purchase-btn');
276
+
277
+ // Trigger purchase programmatically
278
+ await button.purchase();
279
+
280
+ // Or control state directly
281
+ button.setSuccess();
282
+ button.setFailure('Insufficient funds');
283
+ button.reset();
284
+ ```
285
+
286
+ ## Dependencies
287
+
288
+ - `zn-icon`: Icon component for spinner, success, and failure icons
289
+
290
+ ## Browser Support
291
+
292
+ Works in all modern browsers that support:
293
+ - Web Components (Custom Elements)
294
+ - CSS Custom Properties
295
+ - CSS Animations
296
+ - ES Modules
297
+
298
+ ## Testing
299
+
300
+ Run tests with:
301
+
302
+ ```bash
303
+ npm test
304
+ ```
305
+
306
+ See `purchase-button.test.ts` for test examples.
@@ -0,0 +1,229 @@
1
+ import {classMap} from 'lit/directives/class-map.js';
2
+ import {type CSSResultGroup, html, unsafeCSS} from 'lit';
3
+ import {property, state} from 'lit/decorators.js';
4
+ import ZincElement from '../../internal/zinc-element';
5
+ import ZnIcon from '../icon/icon.component';
6
+
7
+ import styles from './animated-button.scss';
8
+
9
+ type AnimatedButtonState = 'idle' | 'processing' | 'success' | 'failure';
10
+
11
+ export default class ZnAnimatedButton extends ZincElement {
12
+ static styles: CSSResultGroup = unsafeCSS(styles);
13
+
14
+ static dependencies = {
15
+ 'zn-icon': ZnIcon
16
+ };
17
+
18
+ @state() private currentState: AnimatedButtonState = 'idle';
19
+ @state() private errorMessage = '';
20
+
21
+ /** The text to display in the idle state */
22
+ @property({attribute: 'idle-text'}) idleText = 'Purchase';
23
+
24
+ /** The text to display in the processing state */
25
+ @property({attribute: 'processing-text'}) processingText = 'Purchasing';
26
+
27
+ /** The text to display in the success state */
28
+ @property({attribute: 'success-text'}) successText = 'Success';
29
+
30
+ /** The text to display in the failure state */
31
+ @property({attribute: 'failure-text'}) failureText = 'Failed';
32
+
33
+ /** The URL to redirect to after successful purchase */
34
+ @property({attribute: 'redirect-url'}) redirectUrl = '';
35
+
36
+ /** Delay in milliseconds before redirecting after success (default: 1500ms) */
37
+ @property({type: Number, attribute: 'redirect-delay'}) redirectDelay = 1500;
38
+
39
+ /** Delay in milliseconds before resetting from failure state (default: 2000ms) */
40
+ @property({type: Number, attribute: 'failure-reset-delay'}) failureResetDelay = 2000;
41
+
42
+ /** Disabled state */
43
+ @property({type: Boolean, reflect: true}) disabled = false;
44
+
45
+ /** The name of the form control (required for form submission) */
46
+ @property() name = '';
47
+
48
+ /** The value of the form control */
49
+ @property() value = '';
50
+
51
+ private resetTimeout?: number;
52
+ private redirectTimeout?: number;
53
+
54
+ disconnectedCallback(): void {
55
+ super.disconnectedCallback();
56
+ this.clearTimeouts();
57
+ }
58
+
59
+ private clearTimeouts() {
60
+ if (this.resetTimeout) {
61
+ clearTimeout(this.resetTimeout);
62
+ this.resetTimeout = undefined;
63
+ }
64
+ if (this.redirectTimeout) {
65
+ clearTimeout(this.redirectTimeout);
66
+ this.redirectTimeout = undefined;
67
+ }
68
+ }
69
+
70
+ /** Programmatically trigger the purchase flow */
71
+ public purchase(): void {
72
+ if (this.currentState !== 'idle' || this.disabled) {
73
+ return;
74
+ }
75
+
76
+ this.handlePurchase();
77
+ }
78
+
79
+ /** Set the button to success state manually */
80
+ public setSuccess(): void {
81
+ this.clearTimeouts();
82
+ this.currentState = 'success';
83
+ this.scheduleRedirect();
84
+ }
85
+
86
+ /** Set the button to failure state manually with optional error message */
87
+ public setFailure(message = ''): void {
88
+ this.clearTimeouts();
89
+ this.errorMessage = message;
90
+ this.currentState = 'failure';
91
+ this.scheduleReset();
92
+ }
93
+
94
+ /** Reset the button to idle state */
95
+ public reset(): void {
96
+ this.clearTimeouts();
97
+ this.currentState = 'idle';
98
+ this.errorMessage = '';
99
+ }
100
+
101
+ private handlePurchase() {
102
+ this.clearTimeouts();
103
+ this.currentState = 'processing';
104
+
105
+ const event = this.emit('zn-purchase', {
106
+ cancelable: true,
107
+ detail: {
108
+ value: this.value,
109
+ setSuccess: () => this.setSuccess(),
110
+ setFailure: (message?: string) => this.setFailure(message)
111
+ }
112
+ });
113
+
114
+ // If the event was prevented, reset to idle
115
+ if (event.defaultPrevented) {
116
+ this.currentState = 'idle';
117
+ }
118
+ }
119
+
120
+ private scheduleRedirect() {
121
+ this.redirectTimeout = window.setTimeout(() => {
122
+ if (this.redirectUrl) {
123
+ window.location.href = this.redirectUrl;
124
+ }
125
+ this.emit('zn-redirect', {
126
+ detail: {url: this.redirectUrl}
127
+ });
128
+ }, this.redirectDelay);
129
+ }
130
+
131
+ private scheduleReset() {
132
+ this.resetTimeout = window.setTimeout(() => {
133
+ this.reset();
134
+ }, this.failureResetDelay);
135
+ }
136
+
137
+ private handleClick() {
138
+ if (this.currentState === 'idle' && !this.disabled) {
139
+ this.handlePurchase();
140
+ }
141
+ }
142
+
143
+ protected render() {
144
+ const isDisabled = this.disabled || this.currentState !== 'idle';
145
+ const showSpinner = this.currentState === 'processing';
146
+ const showSuccess = this.currentState === 'success';
147
+ const showFailure = this.currentState === 'failure';
148
+
149
+ const classes = {
150
+ 'animated-button': true,
151
+ 'animated-button--idle': this.currentState === 'idle',
152
+ 'animated-button--processing': this.currentState === 'processing',
153
+ 'animated-button--success': this.currentState === 'success',
154
+ 'animated-button--failure': this.currentState === 'failure'
155
+ };
156
+
157
+ let buttonText = this.idleText;
158
+ if (this.currentState === 'processing') buttonText = this.processingText;
159
+ if (this.currentState === 'success') buttonText = this.successText;
160
+ if (this.currentState === 'failure') buttonText = this.failureText;
161
+
162
+ return html`
163
+ <button
164
+ class=${classMap(classes)}
165
+ ?disabled=${isDisabled}
166
+ @click=${this.handleClick}
167
+ type="button"
168
+ aria-live="polite"
169
+ aria-busy=${showSpinner}
170
+ >
171
+ ${showSpinner
172
+ ? html`
173
+ <span class="animated-button__content">
174
+ <zn-icon
175
+ class="animated-button__spinner"
176
+ src="progress_activity"
177
+ size="20"
178
+ color="white"
179
+ ></zn-icon>
180
+ <span class="animated-button__text">${buttonText}</span>
181
+ </span>
182
+ <span class="animated-button__shimmer"></span>
183
+ `
184
+ : showSuccess
185
+ ? html`
186
+ <span class="animated-button__content">
187
+ <svg
188
+ class="animated-button__icon animated-button__icon--success"
189
+ width="20"
190
+ height="20"
191
+ viewBox="0 0 24 24"
192
+ fill="none"
193
+ xmlns="http://www.w3.org/2000/svg"
194
+ >
195
+ <path
196
+ class="animated-button__tick"
197
+ d="M5 13l4 4L19 7"
198
+ stroke="white"
199
+ stroke-width="3"
200
+ stroke-linecap="round"
201
+ stroke-linejoin="round"
202
+ />
203
+ </svg>
204
+ <span class="animated-button__text">${buttonText}</span>
205
+ </span>
206
+ `
207
+ : showFailure
208
+ ? html`
209
+ <span class="animated-button__content">
210
+ <zn-icon
211
+ class="animated-button__icon animated-button__icon--failure"
212
+ src="cancel"
213
+ size="20"
214
+ color="white"
215
+ ></zn-icon>
216
+ <span class="animated-button__text">
217
+ ${buttonText}${this.errorMessage ? `: ${this.errorMessage}` : ''}
218
+ </span>
219
+ </span>
220
+ `
221
+ : html`
222
+ <span class="animated-button__content">
223
+ <span class="animated-button__text">${buttonText}</span>
224
+ </span>
225
+ `}
226
+ </button>
227
+ `;
228
+ }
229
+ }