valtech-components 4.0.7 → 4.0.10
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/README.md +262 -31
- package/esm2022/lib/components/molecules/date-picker/date-picker.component.mjs +5 -4
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +5 -4
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<img style="margin-bottom: 20px;" src="https://myvaltech.s3.us-east-2.amazonaws.com/logo-terminal-rounded.png" width="60px" />
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# valtech-components
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/valtech-components)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
Component library for Angular + Ionic
|
|
8
|
+
Component library for Angular 18 + Ionic 8 apps. 145+ components and 23+ services covering auth, Firebase, i18n, theming, and more.
|
|
9
9
|
|
|
10
|
-
**[Full
|
|
10
|
+
**[Full docs → ui.myvaltech.com](https://ui.myvaltech.com)**
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -23,11 +23,11 @@ npm install valtech-components
|
|
|
23
23
|
npm install @angular/common @angular/core @ionic/angular ionicons rxjs
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
**Optional — only if you use these features:**
|
|
26
|
+
**Optional — only install if you use these features:**
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
npm install @angular/fire firebase # Firebase services
|
|
30
|
-
npm install @capacitor/app @capacitor/clipboard # Native features
|
|
30
|
+
npm install @capacitor/app @capacitor/clipboard # Native (Capacitor) features
|
|
31
31
|
npm install qr-code-styling # QR code component
|
|
32
32
|
npm install prismjs # Code highlighting
|
|
33
33
|
npm install swiper # Carousel component
|
|
@@ -35,11 +35,11 @@ npm install swiper # Carousel component
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Quick start
|
|
39
39
|
|
|
40
|
-
### UI only (no backend
|
|
40
|
+
### UI only (no backend required)
|
|
41
41
|
|
|
42
|
-
144 of 145 components work standalone
|
|
42
|
+
144 of 145 components work standalone:
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
import { ButtonComponent, CardComponent, TextComponent } from 'valtech-components';
|
|
@@ -50,7 +50,7 @@ import { ButtonComponent, CardComponent, TextComponent } from 'valtech-component
|
|
|
50
50
|
template: `
|
|
51
51
|
<val-card [props]="{ title: 'My Card' }">
|
|
52
52
|
<val-text [props]="{ content: 'Hello World' }" />
|
|
53
|
-
<val-button [props]="{ text: 'Click me'
|
|
53
|
+
<val-button preset="primary-round" [props]="{ text: 'Click me' }" />
|
|
54
54
|
</val-card>
|
|
55
55
|
`
|
|
56
56
|
})
|
|
@@ -59,49 +59,156 @@ export class MyComponent {}
|
|
|
59
59
|
|
|
60
60
|
### Full integration (auth + Firebase + i18n)
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Wire providers in `main.ts`:
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
import {
|
|
66
66
|
provideValtechFirebase,
|
|
67
67
|
provideValtechAuth,
|
|
68
68
|
provideValtechI18n,
|
|
69
|
-
|
|
69
|
+
provideValtechErrorHandling,
|
|
70
|
+
provideValtechSkeleton,
|
|
71
|
+
provideValtechPresets,
|
|
72
|
+
HandoffService,
|
|
70
73
|
} from 'valtech-components';
|
|
71
|
-
import
|
|
74
|
+
import { APP_INITIALIZER } from '@angular/core';
|
|
72
75
|
|
|
73
76
|
bootstrapApplication(AppComponent, {
|
|
74
77
|
providers: [
|
|
75
|
-
provideValtechFirebase(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
provideValtechFirebase(environment.valtechFirebase),
|
|
79
|
+
|
|
80
|
+
// Auth must come before provideValtechErrorHandling
|
|
81
|
+
provideValtechAuth({
|
|
82
|
+
apiUrl: environment.apiUrl,
|
|
83
|
+
appId: 'my-app',
|
|
84
|
+
loginRoute: '/login',
|
|
85
|
+
homeRoute: '/app',
|
|
86
|
+
enableFirebaseIntegration: true,
|
|
87
|
+
enableTabSync: true,
|
|
88
|
+
}),
|
|
89
|
+
|
|
90
|
+
// HTTP error observability — always after provideValtechAuth
|
|
91
|
+
provideValtechErrorHandling(),
|
|
92
|
+
|
|
93
|
+
// Cross-app session handoff — always after provideValtechAuth
|
|
94
|
+
{
|
|
95
|
+
provide: APP_INITIALIZER,
|
|
96
|
+
multi: true,
|
|
97
|
+
deps: [HandoffService],
|
|
98
|
+
useFactory: (h: HandoffService) => () => h.detectAndExchangeHandoff(),
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
provideValtechI18n({
|
|
102
|
+
defaultLanguage: 'es',
|
|
103
|
+
supportedLanguages: ['es', 'en'],
|
|
104
|
+
content: APP_I18N_CONTENT,
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
provideValtechPresets(APP_PRESETS),
|
|
108
|
+
provideValtechSkeleton({ animated: true }),
|
|
78
109
|
],
|
|
79
110
|
});
|
|
80
111
|
```
|
|
81
112
|
|
|
113
|
+
**Provider order matters:**
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
provideHttpClient
|
|
117
|
+
↓
|
|
118
|
+
provideValtechFirebase (initializes Firebase app)
|
|
119
|
+
↓
|
|
120
|
+
provideValtechAuth (registers auth HTTP interceptor)
|
|
121
|
+
↓
|
|
122
|
+
provideValtechErrorHandling (registers error interceptor — composes with auth)
|
|
123
|
+
↓
|
|
124
|
+
APP_INITIALIZER handoff (depends on AuthService being configured)
|
|
125
|
+
```
|
|
126
|
+
|
|
82
127
|
---
|
|
83
128
|
|
|
84
129
|
## Components
|
|
85
130
|
|
|
86
131
|
| Category | Count | Includes |
|
|
87
132
|
|----------|------:|---------|
|
|
88
|
-
| Atoms | 24 |
|
|
89
|
-
| Molecules | 81 |
|
|
90
|
-
| Organisms | 31 |
|
|
91
|
-
| Templates | 9 | page-wrapper
|
|
92
|
-
|
|
133
|
+
| Atoms | ~24 | `val-button`, `val-text`, `val-icon`, `val-avatar`, `val-image`, `val-badge`, `val-skeleton`, `val-progress-bar`, `val-qr-code`, `val-page-waves` |
|
|
134
|
+
| Molecules | ~81 | `val-card`, `val-input`, `val-select`, `val-date-picker`, `val-tabs`, `val-accordion`, `val-searchbar`, `val-empty-state`, `val-ticket-card`, `val-field-list`, `val-stats-bar`, `val-article-card` |
|
|
135
|
+
| Organisms | ~31 | `val-form`, `val-data-table`, `val-bottom-nav`, `val-toolbar`, `val-menu`, `val-login`, `val-comment-section`, `val-qr-scanner`, `val-search-header`, `val-share-profile-modal`, `val-fun-modal` |
|
|
136
|
+
| Templates | ~9 | `val-page-wrapper`, `val-page-content`, `val-auth-background`, `val-maintenance-page`, `val-docs-shell` |
|
|
137
|
+
|
|
138
|
+
Components requiring a backend API: `val-login`, `val-content-reaction`.
|
|
139
|
+
|
|
140
|
+
### Buttons — presets, not factories
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<!-- Declarative (preferred) -->
|
|
144
|
+
<val-button preset="primary-round" [props]="{ text: 'Save' }" />
|
|
145
|
+
<val-button preset="secondary" [props]="{ text: 'Cancel' }" />
|
|
146
|
+
<val-button preset="danger-round" [props]="{ text: 'Delete' }" />
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Built-in presets: `primary`, `primary-round`, `primary-block`, `primary-full`, `secondary`, `secondary-round`, `solid`, `solid-round`, `solid-block`, `outline`, `outline-round`, `clear`, `clear-round`, `danger`, `danger-round`.
|
|
150
|
+
|
|
151
|
+
### Date picker
|
|
152
|
+
|
|
153
|
+
Use `val-date-picker` instead of `val-date-input` or `ion-datetime` (ion-datetime has a known bug with empty day grids in modals):
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<val-date-picker
|
|
157
|
+
[props]="{
|
|
158
|
+
control: myControl,
|
|
159
|
+
label: 'Date',
|
|
160
|
+
placeholder: 'Select date',
|
|
161
|
+
locale: 'es-CL',
|
|
162
|
+
firstWeekday: 1
|
|
163
|
+
}"
|
|
164
|
+
(valueChange)="onDate($event)"
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Value format: `YYYY-MM-DD` (local date string, no timezone shift). If the backend returns ISO timestamps, slice to 10 chars: `value.slice(0, 10)`.
|
|
169
|
+
|
|
170
|
+
### Field list (editable repeating items)
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<val-field-list
|
|
174
|
+
[props]="{
|
|
175
|
+
fields: [
|
|
176
|
+
{ name: 'name', label: 'Prize name', maxlength: 200 },
|
|
177
|
+
{ name: 'description', label: 'Description', maxlength: 300 }
|
|
178
|
+
],
|
|
179
|
+
items: seedSignal(),
|
|
180
|
+
itemLabel: 'Prize {n}',
|
|
181
|
+
addLabel: 'Add prize'
|
|
182
|
+
}"
|
|
183
|
+
(itemsChange)="onItemsChange($event)"
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Pass a **separate seed signal** for `items` — do not bind the editing signal directly (it re-seeds on every change).
|
|
188
|
+
|
|
189
|
+
### Empty / error state
|
|
93
190
|
|
|
94
|
-
|
|
191
|
+
```html
|
|
192
|
+
@if (page.loading()) {
|
|
193
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 4 }" />
|
|
194
|
+
} @else if (page.errorState(); as e) {
|
|
195
|
+
<val-empty-state [props]="e" />
|
|
196
|
+
} @else if (items().length === 0) {
|
|
197
|
+
<val-empty-state [props]="{ variant: 'empty', title: t('emptyTitle') }" />
|
|
198
|
+
} @else {
|
|
199
|
+
<!-- content -->
|
|
200
|
+
}
|
|
201
|
+
```
|
|
95
202
|
|
|
96
203
|
---
|
|
97
204
|
|
|
98
205
|
## Services
|
|
99
206
|
|
|
100
207
|
**No setup required:**
|
|
101
|
-
`ThemeService` · `ToastService` · `NavigationService` · `DownloadService` · `
|
|
208
|
+
`ThemeService` · `ToastService` · `NavigationService` · `DownloadService` · `IconService` · `LocaleService` · `LocalStorageService` · `ModalService` · `ConfirmationDialogService` · `QrGeneratorService` · `PaginationService` · `SkeletonService`
|
|
102
209
|
|
|
103
210
|
**Requires `provideValtechAuth`:**
|
|
104
|
-
`AuthService` · `OAuthService` · `SessionService` · `TokenService` · `AuthStateService` · `DeviceService`
|
|
211
|
+
`AuthService` · `OAuthService` · `SessionService` · `TokenService` · `AuthStateService` · `DeviceService` · `HandoffService` · `OrgSwitchService`
|
|
105
212
|
|
|
106
213
|
**Requires `provideValtechFirebase`:**
|
|
107
214
|
`FirebaseService` · `AnalyticsService` · `FirestoreService` · `MessagingService` · `NotificationsService` · `FirebaseStorageService`
|
|
@@ -109,23 +216,147 @@ Components requiring a backend API: `LoginComponent`, `ContentReactionComponent`
|
|
|
109
216
|
**Requires `provideValtechI18n`:**
|
|
110
217
|
`I18nService`
|
|
111
218
|
|
|
219
|
+
**Requires `provideValtechErrorHandling`:**
|
|
220
|
+
`ValtechErrorService`
|
|
221
|
+
|
|
112
222
|
---
|
|
113
223
|
|
|
114
|
-
##
|
|
224
|
+
## Common patterns
|
|
115
225
|
|
|
116
|
-
|
|
226
|
+
### Page loading state — `createPageState`
|
|
227
|
+
|
|
228
|
+
Replaces ~15 lines of boilerplate (loading signal + error signal + try/catch + computed error state):
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { createPageState, connectPageRefresh } from 'valtech-components';
|
|
232
|
+
|
|
233
|
+
export class MyPage {
|
|
234
|
+
private svc = inject(MyService);
|
|
235
|
+
private i18n = inject(I18nService);
|
|
236
|
+
readonly items = signal<Item[]>([]);
|
|
237
|
+
|
|
238
|
+
readonly page = createPageState(this.i18n, async () => {
|
|
239
|
+
const res = await firstValueFrom(this.svc.getItems());
|
|
240
|
+
this.items.set(res.items);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
constructor() {
|
|
244
|
+
connectPageRefresh(() => this.page.reload());
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`page.loading()` · `page.errorState()` · `page.reload()` — all signals/promises, ready to bind.
|
|
250
|
+
|
|
251
|
+
### Pull-to-refresh
|
|
252
|
+
|
|
253
|
+
Every page that displays remote data must register a refresh handler. Use `connectPageRefresh` in the constructor — it registers and auto-cleans via `DestroyRef`:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { connectPageRefresh, createRefreshableStream } from 'valtech-components';
|
|
257
|
+
|
|
258
|
+
export class MyPage {
|
|
259
|
+
private data = createRefreshableStream(() => this.svc.getItems());
|
|
260
|
+
readonly items = this.data.data;
|
|
261
|
+
readonly loading = this.data.loading;
|
|
262
|
+
|
|
263
|
+
constructor() {
|
|
264
|
+
connectPageRefresh(() => this.data.reload());
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Error handling in catch blocks
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { ValtechErrorService } from 'valtech-components';
|
|
273
|
+
|
|
274
|
+
export class MyPage {
|
|
275
|
+
private errors = inject(ValtechErrorService);
|
|
276
|
+
|
|
277
|
+
async save() {
|
|
278
|
+
try {
|
|
279
|
+
await firstValueFrom(this.api.updateX(...));
|
|
280
|
+
} catch (err) {
|
|
281
|
+
this.errors.handle(err, {
|
|
282
|
+
context: 'mypage.save',
|
|
283
|
+
i18nMap: { BACKEND_CODE: 'i18nKey' },
|
|
284
|
+
fallbackKey: 'saveError',
|
|
285
|
+
i18nNamespace: 'MyNamespace',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Cross-app session handoff
|
|
293
|
+
|
|
294
|
+
Seamlessly transfer a logged-in session from one factory app to another:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// App A — create handoff and redirect
|
|
298
|
+
const { url } = await handoff.createHandoff({ app: 'sigify', route: '/docs/123' });
|
|
299
|
+
window.location.href = url;
|
|
300
|
+
|
|
301
|
+
// App B — auto-handled in bootstrap via APP_INITIALIZER (see Full integration above)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### OAuth callback route
|
|
305
|
+
|
|
306
|
+
Add this route to `app.routes.ts` if your app uses social login:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { OAuthCallbackComponent } from 'valtech-components';
|
|
310
|
+
|
|
311
|
+
{ path: 'auth/oauth/callback', component: OAuthCallbackComponent }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Without this route, post-login redirects to a 404.
|
|
315
|
+
|
|
316
|
+
### Toasts — always `color: 'dark'`
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// All toasts use color: 'dark' regardless of message type
|
|
320
|
+
this.toast.show({ message: 'Saved', color: 'dark', duration: 2000 });
|
|
321
|
+
this.toast.show({ message: 'Error: ...', color: 'dark', duration: 3000 });
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Content reactions (feedback widget)
|
|
325
|
+
|
|
326
|
+
```html
|
|
327
|
+
<val-content-reaction [props]="{
|
|
328
|
+
entityRef: { entityType: 'blog', entityId: post.slug },
|
|
329
|
+
question: 'Was this article helpful?',
|
|
330
|
+
emojis: ['👎', '👍'],
|
|
331
|
+
commentOnValues: ['negative'],
|
|
332
|
+
allowAnonymous: true
|
|
333
|
+
}" />
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Requires `provideValtechFeedback({ apiUrl, appId })` in `main.ts`.
|
|
117
337
|
|
|
118
338
|
---
|
|
119
339
|
|
|
120
|
-
##
|
|
340
|
+
## Styles
|
|
121
341
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
342
|
+
Import fonts and base styles in your app's `variables.scss`:
|
|
343
|
+
|
|
344
|
+
```scss
|
|
345
|
+
@import "valtech-components/styles/fonts.scss"; // Nunito Sans (default) + Khula (Bingo brand)
|
|
346
|
+
|
|
347
|
+
:root {
|
|
348
|
+
--ion-font-family: "Nunito Sans", Arial, sans-serif;
|
|
349
|
+
}
|
|
127
350
|
```
|
|
128
351
|
|
|
352
|
+
All `font-size` values in the lib use `rem` to respect the user's font size preference (`FontSizeService` scales the root `html` element).
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Technologies
|
|
357
|
+
|
|
358
|
+
Angular 18+ · Ionic 8+ · Capacitor 6+ · Firebase 10+ · RxJS 7.8+
|
|
359
|
+
|
|
129
360
|
---
|
|
130
361
|
|
|
131
362
|
## License
|