@devjuliovilla/jv-ui 1.10.0 → 1.11.1

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 CHANGED
@@ -46,6 +46,9 @@ export class App {}
46
46
  | Data Grid | `JvGrid` — full-featured data table (see below) |
47
47
  | Containers | `JvCard`, `JvSection`, `JvDivider` |
48
48
  | List | `JvList`, `JvListItem` — list with title, description, icons, badges, and interactive items |
49
+ | Tabs | `JvTabs`, `JvTabPanelDirective` — horizontal tabs with keyboard navigation and animated transitions |
50
+ | Slider | `JvImageSlider` — responsive image slider with thumbnails and keyboard navigation |
51
+ | Gallery | `JvImageGallery` — horizontal image gallery with wheel scrolling and full-screen viewer |
49
52
  | Feedback | `JvAlert`, `JvBadge`, `JvLoader`, `JvToast` |
50
53
  | Dialogs | `JvDialog`, `JvConfirmDialog` |
51
54
  | Layout | `JvDashboardShell`, `JvSidebar`, `JvTopbar`, `JvBreadcrumb`, `JvPage` |
@@ -293,6 +296,265 @@ import { JvPaginationComponent } from '@devjuliovilla/jv-ui';
293
296
  |---|---|
294
297
  | `pageChange` | `{ pageIndex: number; pageSize: number }` |
295
298
 
299
+ ## Tabs API
300
+
301
+ ```typescript
302
+ import { JvTabsComponent, JvTabPanelDirective, JvTabItem } from '@devjuliovilla/jv-ui';
303
+ ```
304
+
305
+ **Inputs:**
306
+
307
+ | Input | Type | Default | Description |
308
+ |---|---|---|---|
309
+ | `tabs` | `JvTabItem[]` | `[]` | Tab definitions (`{ id, label, icon?, disabled? }`) |
310
+ | `activeTabId` | `string \| null` | `null` | Controlled active tab ID |
311
+ | `keepMounted` | `boolean` | `true` | Keep all panels in DOM; `false` renders only active panel |
312
+ | `transition` | `'none' \| 'fade' \| 'slide' \| null` | `null` | Panel transition animation (falls back to global config) |
313
+
314
+ **Outputs:**
315
+
316
+ | Output | Type |
317
+ |---|---|
318
+ | `activeTabChange` | `string` |
319
+
320
+ **Content projection:** use `jvTabPanel` directive on `ng-template` to define panel content:
321
+
322
+ ```html
323
+ <jv-tabs [tabs]="tabs" [activeTabId]="activeTabId()" (activeTabChange)="activeTabId.set($event)">
324
+ <ng-template jvTabPanel="overview">
325
+ <p>Overview content</p>
326
+ </ng-template>
327
+ <ng-template jvTabPanel="analytics">
328
+ <p>Analytics content</p>
329
+ </ng-template>
330
+ </jv-tabs>
331
+ ```
332
+
333
+ ### Tab Transitions
334
+
335
+ Tabs support animated transitions between panels. Configure globally via `provideJvUi()` or per-instance via the `transition` input:
336
+
337
+ ```typescript
338
+ // Global default
339
+ provideJvUi({ tabsTransition: 'fade' })
340
+ ```
341
+
342
+ ```typescript
343
+ // Per-instance override
344
+ <jv-tabs transition="slide" [keepMounted]="false">...</jv-tabs>
345
+ ```
346
+
347
+ | Transition | `keepMounted=true` | `keepMounted=false` | Description |
348
+ |---|---|---|---|
349
+ | `'none'` | Instant switch | Instant switch | No animation |
350
+ | `'fade'` | Opacity cross-fade | Opacity cross-fade | Smooth fade between panels |
351
+ | `'slide'` | Falls back to `'fade'` | Directional slide | Slides left/right based on tab index |
352
+
353
+ **Note:** `slide` requires `keepMounted=false` because it needs to animate both entering and exiting panels simultaneously. When used with `keepMounted=true`, it automatically falls back to `fade`.
354
+
355
+ ### Tabs Example
356
+
357
+ ```typescript
358
+ import { Component, signal } from '@angular/core';
359
+ import { JvTabItem, JvTabPanelDirective, JvTabsComponent } from '@devjuliovilla/jv-ui';
360
+
361
+ @Component({
362
+ standalone: true,
363
+ imports: [JvTabsComponent, JvTabPanelDirective],
364
+ template: `
365
+ <jv-tabs
366
+ [tabs]="tabs"
367
+ [activeTabId]="activeTabId()"
368
+ transition="fade"
369
+ (activeTabChange)="activeTabId.set($event)"
370
+ >
371
+ <ng-template jvTabPanel="overview">
372
+ <p>Overview content</p>
373
+ </ng-template>
374
+ <ng-template jvTabPanel="analytics">
375
+ <p>Analytics content</p>
376
+ </ng-template>
377
+ </jv-tabs>
378
+ `,
379
+ })
380
+ export class TabsExampleComponent {
381
+ readonly activeTabId = signal('overview');
382
+ readonly tabs: JvTabItem[] = [
383
+ { id: 'overview', label: 'Overview' },
384
+ { id: 'analytics', label: 'Analytics', icon: 'chart-column' },
385
+ ];
386
+ }
387
+ ```
388
+
389
+ ## Image Slider API
390
+
391
+ ```typescript
392
+ import { JvImageSliderComponent, JvSliderImage } from '@devjuliovilla/jv-ui';
393
+ ```
394
+
395
+ **JvSliderImage:**
396
+
397
+ | Property | Type | Required | Description |
398
+ |---|---|---|---|
399
+ | `src` | `string` | Yes | Image URL |
400
+ | `alt` | `string` | Yes | Alternative text for accessibility |
401
+ | `description` | `string` | No | Caption displayed below the image |
402
+ | `thumbnail` | `string` | No | Custom thumbnail URL (defaults to `src`) |
403
+
404
+ **Inputs:**
405
+
406
+ | Input | Type | Default | Description |
407
+ |---|---|---|---|
408
+ | `images` | `JvSliderImage[]` | `[]` | Array of images to display |
409
+ | `showThumbnails` | `boolean` | `true` | Show thumbnail strip below slider |
410
+ | `showDescription` | `boolean` | `true` | Show image description |
411
+
412
+ **Outputs:**
413
+
414
+ | Output | Type | Description |
415
+ |---|---|---|
416
+ | `imageChange` | `number` | Emits index when active image changes |
417
+
418
+ **i18n support:**
419
+
420
+ The component automatically translates aria labels using `JvTranslationService`:
421
+
422
+ | Key | English | Spanish |
423
+ |---|---|---|
424
+ | `slider.prevImage` | Previous image | Imagen anterior |
425
+ | `slider.nextImage` | Next image | Imagen siguiente |
426
+ | `slider.thumbnailList` | Image thumbnails | Miniaturas de imagenes |
427
+
428
+ ### Features
429
+
430
+ - **Keyboard navigation:** ArrowLeft/ArrowRight to navigate, Home/End for first/last image
431
+ - **Responsive:** Images scale to fit container with `object-fit: contain`
432
+ - **Thumbnails:** Scrollable thumbnail strip with active state indicator
433
+ - **Single image mode:** Navigation controls hidden when only one image is provided
434
+ - **Lazy loading:** Images use native `loading="lazy"` attribute
435
+ - **i18n:** Aria labels automatically translated based on configured locale
436
+
437
+ ### Example
438
+
439
+ ```typescript
440
+ import { Component } from '@angular/core';
441
+ import { JvImageSliderComponent, JvSliderImage } from '@devjuliovilla/jv-ui';
442
+
443
+ @Component({
444
+ standalone: true,
445
+ imports: [JvImageSliderComponent],
446
+ template: `
447
+ <jv-image-slider
448
+ [images]="images"
449
+ [showThumbnails]="true"
450
+ [showDescription]="true"
451
+ (imageChange)="onImageChange($event)"
452
+ />
453
+ `,
454
+ })
455
+ export class SliderComponent {
456
+ readonly images: JvSliderImage[] = [
457
+ { src: 'photo1.jpg', alt: 'Sunset', description: 'Beautiful sunset over the ocean' },
458
+ { src: 'photo2.jpg', alt: 'Mountain', description: 'Snow-capped mountain peak' },
459
+ { src: 'photo3.jpg', alt: 'Forest', description: 'Dense forest trail' },
460
+ ];
461
+
462
+ onImageChange(index: number) {
463
+ console.log('Viewing image:', index);
464
+ }
465
+ }
466
+ ```
467
+
468
+ ## Image Gallery API
469
+
470
+ ```typescript
471
+ import { JvImageGalleryComponent, JvGalleryImage, JvGalleryAction } from '@devjuliovilla/jv-ui';
472
+ ```
473
+
474
+ **JvGalleryImage:**
475
+
476
+ | Property | Type | Required | Description |
477
+ |---|---|---|---|
478
+ | `src` | `string` | Yes | Image URL |
479
+ | `alt` | `string` | Yes | Alternative text for accessibility |
480
+ | `thumbnail` | `string` | No | Custom thumbnail URL (defaults to `src`) |
481
+
482
+ **JvGalleryAction:**
483
+
484
+ | Property | Type | Required | Description |
485
+ |---|---|---|---|
486
+ | `id` | `string` | Yes | Unique action identifier |
487
+ | `label` | `string` | Yes | Button label text |
488
+ | `icon` | `string` | No | Lucide icon name |
489
+ | `tone` | `JvIntent` | No | Button variant |
490
+
491
+ **Inputs:**
492
+
493
+ | Input | Type | Default | Description |
494
+ |---|---|---|---|
495
+ | `images` | `JvGalleryImage[]` | `[]` | Array of images to display |
496
+ | `stripHeightVh` | `number` | `25` | Height of the thumbnail strip in viewport-height units, clamped between `0` and `100` |
497
+ | `actions` | `JvGalleryAction[]` | `[]` | Action buttons shown in dialog footer |
498
+
499
+ **Outputs:**
500
+
501
+ | Output | Type | Description |
502
+ |---|---|---|
503
+ | `imageClick` | `JvGalleryImage` | Emits when an image is clicked |
504
+ | `actionClick` | `{ actionId: string; image: JvGalleryImage }` | Emits when an action button is clicked |
505
+
506
+ ### Features
507
+
508
+ - **Horizontal strip:** Scrollable thumbnail strip with configurable height in `vh`
509
+ - **Full-screen viewer:** Click any image to open in a dialog with dark background
510
+ - **Navigation:** Prev/next buttons and keyboard arrows (← →) in the viewer
511
+ - **Configurable actions:** Action buttons in dialog footer (favorite, edit, delete, etc.)
512
+ - **Responsive:** Shows multiple columns on desktop, single column with scroll on mobile
513
+ - **Lazy loading:** All images use native `loading="lazy"` attribute
514
+ - **i18n:** Close and navigation labels automatically translated
515
+
516
+ ### Example
517
+
518
+ ```typescript
519
+ import { Component } from '@angular/core';
520
+ import { JvImageGalleryComponent, JvGalleryImage, JvGalleryAction } from '@devjuliovilla/jv-ui';
521
+
522
+ @Component({
523
+ standalone: true,
524
+ imports: [JvImageGalleryComponent],
525
+ template: `
526
+ <jv-image-gallery
527
+ [images]="images"
528
+ [stripHeightVh]="30"
529
+ [actions]="actions"
530
+ (imageClick)="onImageClick($event)"
531
+ (actionClick)="onActionClick($event)"
532
+ />
533
+ `,
534
+ })
535
+ export class GalleryComponent {
536
+ readonly images: JvGalleryImage[] = [
537
+ { src: 'photo1.jpg', alt: 'Sunset' },
538
+ { src: 'photo2.jpg', alt: 'Mountain' },
539
+ { src: 'photo3.jpg', alt: 'Forest' },
540
+ ];
541
+
542
+ readonly actions: JvGalleryAction[] = [
543
+ { id: 'favorite', label: 'Favorite', icon: 'heart', tone: 'danger' },
544
+ { id: 'edit', label: 'Edit', icon: 'pencil' },
545
+ { id: 'delete', label: 'Delete', icon: 'trash-2', tone: 'danger' },
546
+ ];
547
+
548
+ onImageClick(image: JvGalleryImage) {
549
+ console.log('Viewing:', image.alt);
550
+ }
551
+
552
+ onActionClick(event: { actionId: string; image: JvGalleryImage }) {
553
+ console.log(event.actionId, 'on', event.image.alt);
554
+ }
555
+ }
556
+ ```
557
+
296
558
  ## Textarea API
297
559
 
298
560
  ```typescript