@vekron/vekron-ui 0.0.0-stage → 0.1.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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/bin/vekron-ui.js +362 -0
  3. package/package.json +45 -3
  4. package/src/accordion/accordion.component.ts +64 -0
  5. package/src/accordion/public-api.ts +1 -0
  6. package/src/autocomplete/autocomplete.component.ts +144 -0
  7. package/src/autocomplete/public-api.ts +1 -0
  8. package/src/avatar/avatar.component.ts +23 -0
  9. package/src/avatar/public-api.ts +1 -0
  10. package/src/badge/badge.component.ts +27 -0
  11. package/src/badge/public-api.ts +2 -0
  12. package/src/breadcrumb/breadcrumb.component.ts +32 -0
  13. package/src/breadcrumb/public-api.ts +2 -0
  14. package/src/button/button.component.ts +50 -0
  15. package/src/button/public-api.ts +2 -0
  16. package/src/carousel/carousel.component.ts +76 -0
  17. package/src/carousel/public-api.ts +2 -0
  18. package/src/checkbox/checkbox.component.ts +92 -0
  19. package/src/checkbox/public-api.ts +1 -0
  20. package/src/chip/chip.component.ts +20 -0
  21. package/src/chip/public-api.ts +1 -0
  22. package/src/confirm-dialog/confirm-dialog.component.ts +38 -0
  23. package/src/confirm-dialog/public-api.ts +1 -0
  24. package/src/confirm-popup/confirm-popup.component.ts +42 -0
  25. package/src/confirm-popup/public-api.ts +1 -0
  26. package/src/context-menu/context-menu.component.ts +43 -0
  27. package/src/context-menu/public-api.ts +1 -0
  28. package/src/datepicker/datepicker.component.ts +205 -0
  29. package/src/datepicker/public-api.ts +1 -0
  30. package/src/dialog/dialog.component.ts +77 -0
  31. package/src/dialog/public-api.ts +1 -0
  32. package/src/divider/divider.component.ts +17 -0
  33. package/src/divider/public-api.ts +1 -0
  34. package/src/file-upload/file-upload.component.ts +110 -0
  35. package/src/file-upload/public-api.ts +1 -0
  36. package/src/input-number/input-number.component.ts +123 -0
  37. package/src/input-number/public-api.ts +1 -0
  38. package/src/input-otp/input-otp.component.ts +129 -0
  39. package/src/input-otp/public-api.ts +1 -0
  40. package/src/input-password/input-password.component.ts +127 -0
  41. package/src/input-password/public-api.ts +1 -0
  42. package/src/input-text/input-text.component.ts +106 -0
  43. package/src/input-text/public-api.ts +1 -0
  44. package/src/menu/menu.component.ts +35 -0
  45. package/src/menu/public-api.ts +1 -0
  46. package/src/message/message.component.ts +17 -0
  47. package/src/message/public-api.ts +1 -0
  48. package/src/paginator/paginator.component.ts +62 -0
  49. package/src/paginator/public-api.ts +1 -0
  50. package/src/pdf-viewer/pdf-viewer.component.ts +16 -0
  51. package/src/pdf-viewer/public-api.ts +1 -0
  52. package/src/pick-list/pick-list.component.ts +68 -0
  53. package/src/pick-list/public-api.ts +1 -0
  54. package/src/popover/popover.component.ts +22 -0
  55. package/src/popover/public-api.ts +1 -0
  56. package/src/progress-bar/progress-bar.component.ts +27 -0
  57. package/src/progress-bar/public-api.ts +1 -0
  58. package/src/progress-spinner/progress-spinner.component.ts +27 -0
  59. package/src/progress-spinner/public-api.ts +1 -0
  60. package/src/public-api.ts +46 -0
  61. package/src/radio-button/public-api.ts +1 -0
  62. package/src/radio-button/radio-button.component.ts +98 -0
  63. package/src/registry.json +184 -0
  64. package/src/scroll-top/public-api.ts +1 -0
  65. package/src/scroll-top/scroll-top.component.ts +36 -0
  66. package/src/select/public-api.ts +1 -0
  67. package/src/select/select.component.ts +116 -0
  68. package/src/select-button/public-api.ts +1 -0
  69. package/src/select-button/select-button.component.ts +70 -0
  70. package/src/skeleton/public-api.ts +1 -0
  71. package/src/skeleton/skeleton.component.ts +11 -0
  72. package/src/split-button/public-api.ts +1 -0
  73. package/src/split-button/split-button.component.ts +39 -0
  74. package/src/stepper/public-api.ts +2 -0
  75. package/src/stepper/stepper.component.ts +45 -0
  76. package/src/styles.css +63 -0
  77. package/src/table/public-api.ts +2 -0
  78. package/src/table/table.component.ts +47 -0
  79. package/src/tabs/public-api.ts +1 -0
  80. package/src/tabs/tabs.component.ts +76 -0
  81. package/src/tag/public-api.ts +1 -0
  82. package/src/tag/tag.component.ts +22 -0
  83. package/src/textarea/public-api.ts +1 -0
  84. package/src/textarea/textarea.component.ts +106 -0
  85. package/src/toast/public-api.ts +2 -0
  86. package/src/toast/toast.component.ts +60 -0
  87. package/src/toggle-button/public-api.ts +1 -0
  88. package/src/toggle-button/toggle-button.component.ts +80 -0
  89. package/src/toggle-switch/public-api.ts +1 -0
  90. package/src/toggle-switch/toggle-switch.component.ts +96 -0
  91. package/src/toolbar/public-api.ts +1 -0
  92. package/src/toolbar/toolbar.component.ts +17 -0
  93. package/src/tooltip/public-api.ts +1 -0
  94. package/src/tooltip/tooltip.component.ts +30 -0
  95. package/src/tree/public-api.ts +2 -0
  96. package/src/tree/tree.component.ts +64 -0
  97. package/src/utils/classes.ts +19 -0
  98. package/src/utils/cn.ts +6 -0
  99. package/src/utils/models.ts +13 -0
  100. package/src/utils/public-api.ts +3 -0
  101. package/README.md +0 -3
@@ -0,0 +1,184 @@
1
+ {
2
+ "components": {
3
+ "accordion": {
4
+ "description": "Paneles que se abren de a uno.",
5
+ "dependencies": []
6
+ },
7
+ "autocomplete": {
8
+ "description": "Campo con sugerencias.",
9
+ "dependencies": []
10
+ },
11
+ "avatar": {
12
+ "description": "Imagen de perfil o iniciales.",
13
+ "dependencies": []
14
+ },
15
+ "badge": {
16
+ "description": "Indicador compacto.",
17
+ "dependencies": []
18
+ },
19
+ "breadcrumb": {
20
+ "description": "Ruta de navegación.",
21
+ "dependencies": []
22
+ },
23
+ "button": {
24
+ "description": "Botón con variantes y tamaños.",
25
+ "dependencies": []
26
+ },
27
+ "carousel": {
28
+ "description": "Carrusel de imágenes.",
29
+ "dependencies": ["button"]
30
+ },
31
+ "checkbox": {
32
+ "description": "Casilla de verificación.",
33
+ "dependencies": []
34
+ },
35
+ "chip": {
36
+ "description": "Etiqueta, con opción de quitarla.",
37
+ "dependencies": []
38
+ },
39
+ "confirm-dialog": {
40
+ "description": "Diálogo para confirmar una acción.",
41
+ "dependencies": ["dialog", "button"]
42
+ },
43
+ "confirm-popup": {
44
+ "description": "Confirmación anclada al contenido.",
45
+ "dependencies": ["button"]
46
+ },
47
+ "context-menu": {
48
+ "description": "Menú al hacer clic derecho.",
49
+ "dependencies": ["menu"]
50
+ },
51
+ "datepicker": {
52
+ "description": "Selector de fecha.",
53
+ "dependencies": []
54
+ },
55
+ "dialog": {
56
+ "description": "Diálogo modal.",
57
+ "dependencies": ["button"]
58
+ },
59
+ "divider": {
60
+ "description": "Separador horizontal o vertical.",
61
+ "dependencies": []
62
+ },
63
+ "file-upload": {
64
+ "description": "Carga de archivos.",
65
+ "dependencies": ["button"]
66
+ },
67
+ "input-number": {
68
+ "description": "Campo numérico.",
69
+ "dependencies": []
70
+ },
71
+ "input-otp": {
72
+ "description": "Código de un solo uso.",
73
+ "dependencies": []
74
+ },
75
+ "input-password": {
76
+ "description": "Campo de contraseña.",
77
+ "dependencies": []
78
+ },
79
+ "input-text": {
80
+ "description": "Campo de texto.",
81
+ "dependencies": []
82
+ },
83
+ "menu": {
84
+ "description": "Lista de acciones.",
85
+ "dependencies": []
86
+ },
87
+ "message": {
88
+ "description": "Mensaje de estado.",
89
+ "dependencies": []
90
+ },
91
+ "paginator": {
92
+ "description": "Paginación.",
93
+ "dependencies": ["button"]
94
+ },
95
+ "pdf-viewer": {
96
+ "description": "Visor de PDF.",
97
+ "dependencies": []
98
+ },
99
+ "pick-list": {
100
+ "description": "Mover opciones entre dos listas.",
101
+ "dependencies": ["button"]
102
+ },
103
+ "popover": {
104
+ "description": "Contenido flotante.",
105
+ "dependencies": []
106
+ },
107
+ "progress-bar": {
108
+ "description": "Barra de progreso.",
109
+ "dependencies": []
110
+ },
111
+ "progress-spinner": {
112
+ "description": "Indicador de carga.",
113
+ "dependencies": []
114
+ },
115
+ "radio-button": {
116
+ "description": "Grupo de opciones exclusivas.",
117
+ "dependencies": []
118
+ },
119
+ "scroll-top": {
120
+ "description": "Volver al inicio de la página.",
121
+ "dependencies": []
122
+ },
123
+ "select": {
124
+ "description": "Selector de una opción.",
125
+ "dependencies": []
126
+ },
127
+ "select-button": {
128
+ "description": "Grupo de botones de selección única.",
129
+ "dependencies": ["button"]
130
+ },
131
+ "skeleton": {
132
+ "description": "Marcador de carga.",
133
+ "dependencies": []
134
+ },
135
+ "split-button": {
136
+ "description": "Acción principal y menú.",
137
+ "dependencies": ["button", "menu"]
138
+ },
139
+ "stepper": {
140
+ "description": "Pasos de un proceso.",
141
+ "dependencies": []
142
+ },
143
+ "table": {
144
+ "description": "Tabla de filas y columnas.",
145
+ "dependencies": []
146
+ },
147
+ "tabs": {
148
+ "description": "Pestañas.",
149
+ "dependencies": []
150
+ },
151
+ "tag": {
152
+ "description": "Etiqueta de estado.",
153
+ "dependencies": []
154
+ },
155
+ "textarea": {
156
+ "description": "Texto de varias líneas.",
157
+ "dependencies": []
158
+ },
159
+ "toast": {
160
+ "description": "Notificaciones temporales.",
161
+ "dependencies": []
162
+ },
163
+ "toggle-button": {
164
+ "description": "Botón con estado activo.",
165
+ "dependencies": []
166
+ },
167
+ "toggle-switch": {
168
+ "description": "Interruptor.",
169
+ "dependencies": []
170
+ },
171
+ "toolbar": {
172
+ "description": "Barra de acciones.",
173
+ "dependencies": []
174
+ },
175
+ "tooltip": {
176
+ "description": "Ayuda al pasar el mouse.",
177
+ "dependencies": []
178
+ },
179
+ "tree": {
180
+ "description": "Árbol de nodos.",
181
+ "dependencies": []
182
+ }
183
+ }
184
+ }
@@ -0,0 +1 @@
1
+ export { UiScrollTopComponent } from './scroll-top.component';
@@ -0,0 +1,36 @@
1
+ import { ChangeDetectionStrategy, Component, HostListener, afterNextRender, input, signal } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'ui-scroll-top',
5
+ changeDetection: ChangeDetectionStrategy.OnPush,
6
+ template: `
7
+ @if (visible()) {
8
+ <button
9
+ type="button"
10
+ class="fixed right-4 bottom-4 z-40 rounded-ui bg-primary px-4 py-2 text-sm text-primary-foreground shadow-md"
11
+ (click)="scrollToTop()"
12
+ >
13
+ Arriba
14
+ </button>
15
+ }
16
+ `,
17
+ })
18
+ export class UiScrollTopComponent {
19
+ readonly threshold = input(200);
20
+ readonly visible = signal(false);
21
+
22
+ constructor() {
23
+ afterNextRender(() => {
24
+ this.visible.set(window.scrollY > this.threshold());
25
+ });
26
+ }
27
+
28
+ @HostListener('window:scroll')
29
+ onWindowScroll(): void {
30
+ this.visible.set(window.scrollY > this.threshold());
31
+ }
32
+
33
+ scrollToTop(): void {
34
+ window.scrollTo({ top: 0, behavior: 'smooth' });
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ export { UiSelectComponent } from './select.component';
@@ -0,0 +1,116 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ computed,
5
+ forwardRef,
6
+ input,
7
+ model,
8
+ signal,
9
+ } from '@angular/core';
10
+ import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
11
+ import { uiLabelClass, uiPanelClass } from '../utils/classes';
12
+ import { type UiOption } from '../utils/models';
13
+
14
+ @Component({
15
+ selector: 'ui-select',
16
+ changeDetection: ChangeDetectionStrategy.OnPush,
17
+ template: `
18
+ <div class="relative flex w-full flex-col gap-1.5">
19
+ @if (label().length > 0) {
20
+ <span [class]="labelClass">{{ label() }}</span>
21
+ }
22
+ <button
23
+ type="button"
24
+ class="flex h-10 w-full items-center justify-between rounded-ui border border-input bg-background px-3 text-sm"
25
+ [disabled]="isDisabled()"
26
+ (click)="open.set(!open())"
27
+ (blur)="markTouched()"
28
+ >
29
+ <span>{{ selectedLabel() }}</span>
30
+ <span aria-hidden="true">▾</span>
31
+ </button>
32
+ @if (open()) {
33
+ <div [class]="panelClass + ' absolute top-full right-0 left-0 mt-1'">
34
+ @for (option of options(); track option.value) {
35
+ <button
36
+ type="button"
37
+ class="flex w-full rounded-ui px-3 py-2 text-left text-sm hover:bg-accent disabled:opacity-50"
38
+ [disabled]="option.disabled === true"
39
+ (mousedown)="choose($event, option)"
40
+ >
41
+ {{ option.label }}
42
+ </button>
43
+ }
44
+ </div>
45
+ }
46
+ </div>
47
+ `,
48
+ providers: [
49
+ {
50
+ provide: NG_VALUE_ACCESSOR,
51
+ useExisting: forwardRef(() => UiSelectComponent),
52
+ multi: true,
53
+ },
54
+ ],
55
+ })
56
+ export class UiSelectComponent implements ControlValueAccessor {
57
+ readonly value = model('');
58
+ readonly options = input.required<readonly UiOption[]>();
59
+ readonly label = input('');
60
+ readonly placeholder = input('Seleccionar');
61
+ readonly disabled = input(false);
62
+ readonly open = signal(false);
63
+ private readonly formDisabled = signal(false);
64
+ private onChange: (value: string) => void = () => undefined;
65
+ private onTouched: () => void = () => undefined;
66
+ readonly labelClass = uiLabelClass;
67
+ readonly panelClass = uiPanelClass;
68
+ readonly isDisabled = computed(() => this.disabled() || this.formDisabled());
69
+ readonly selectedLabel = computed(() => {
70
+ const current = this.value();
71
+ if (current.length === 0) {
72
+ return this.placeholder();
73
+ }
74
+ for (const option of this.options()) {
75
+ if (option.value === current) {
76
+ return option.label;
77
+ }
78
+ }
79
+ throw new Error(`ui-select no encontró la opción "${current}".`);
80
+ });
81
+
82
+ choose(event: MouseEvent, option: UiOption): void {
83
+ event.preventDefault();
84
+ if (option.disabled === true) {
85
+ return;
86
+ }
87
+ this.value.set(option.value);
88
+ this.onChange(option.value);
89
+ this.open.set(false);
90
+ }
91
+
92
+ markTouched(): void {
93
+ this.onTouched();
94
+ this.open.set(false);
95
+ }
96
+
97
+ writeValue(value: string | null): void {
98
+ if (value === null) {
99
+ this.value.set('');
100
+ return;
101
+ }
102
+ this.value.set(value);
103
+ }
104
+
105
+ registerOnChange(fn: (value: string) => void): void {
106
+ this.onChange = fn;
107
+ }
108
+
109
+ registerOnTouched(fn: () => void): void {
110
+ this.onTouched = fn;
111
+ }
112
+
113
+ setDisabledState(isDisabled: boolean): void {
114
+ this.formDisabled.set(isDisabled);
115
+ }
116
+ }
@@ -0,0 +1 @@
1
+ export { UiSelectButtonComponent } from './select-button.component';
@@ -0,0 +1,70 @@
1
+ import { ChangeDetectionStrategy, Component, forwardRef, input, model } from '@angular/core';
2
+ import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3
+ import { type UiButtonVariant, UiButtonComponent } from '../button/button.component';
4
+ import { type UiOption } from '../utils/models';
5
+
6
+ @Component({
7
+ selector: 'ui-select-button',
8
+ changeDetection: ChangeDetectionStrategy.OnPush,
9
+ imports: [UiButtonComponent],
10
+ template: `
11
+ <div class="inline-flex overflow-hidden rounded-ui border border-border" role="group">
12
+ @for (option of options(); track option.value) {
13
+ <ui-button
14
+ [variant]="variantFor(option.value)"
15
+ buttonClass="rounded-none"
16
+ [disabled]="option.disabled === true"
17
+ (pressed)="select(option)"
18
+ >
19
+ {{ option.label }}
20
+ </ui-button>
21
+ }
22
+ </div>
23
+ `,
24
+ providers: [
25
+ {
26
+ provide: NG_VALUE_ACCESSOR,
27
+ useExisting: forwardRef(() => UiSelectButtonComponent),
28
+ multi: true,
29
+ },
30
+ ],
31
+ host: { class: 'inline-flex' },
32
+ })
33
+ export class UiSelectButtonComponent implements ControlValueAccessor {
34
+ readonly value = model('');
35
+ readonly options = input.required<readonly UiOption[]>();
36
+ private onChange: (value: string) => void = () => undefined;
37
+ private onTouched: () => void = () => undefined;
38
+
39
+ variantFor(optionValue: string): UiButtonVariant {
40
+ if (optionValue === this.value()) {
41
+ return 'primary';
42
+ }
43
+ return 'ghost';
44
+ }
45
+
46
+ select(option: UiOption): void {
47
+ if (option.disabled === true) {
48
+ return;
49
+ }
50
+ this.value.set(option.value);
51
+ this.onChange(option.value);
52
+ this.onTouched();
53
+ }
54
+
55
+ writeValue(value: string | null): void {
56
+ if (value === null) {
57
+ this.value.set('');
58
+ return;
59
+ }
60
+ this.value.set(value);
61
+ }
62
+
63
+ registerOnChange(fn: (value: string) => void): void {
64
+ this.onChange = fn;
65
+ }
66
+
67
+ registerOnTouched(fn: () => void): void {
68
+ this.onTouched = fn;
69
+ }
70
+ }
@@ -0,0 +1 @@
1
+ export { UiSkeletonComponent } from './skeleton.component';
@@ -0,0 +1,11 @@
1
+ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'ui-skeleton',
5
+ changeDetection: ChangeDetectionStrategy.OnPush,
6
+ template: `<div class="animate-pulse rounded-ui bg-muted" [style.width]="width()" [style.height]="height()"></div>`,
7
+ })
8
+ export class UiSkeletonComponent {
9
+ readonly width = input('100%');
10
+ readonly height = input('1rem');
11
+ }
@@ -0,0 +1 @@
1
+ export { UiSplitButtonComponent } from './split-button.component';
@@ -0,0 +1,39 @@
1
+ import { ChangeDetectionStrategy, Component, input, output, signal } from '@angular/core';
2
+ import { UiButtonComponent } from '../button/button.component';
3
+ import { UiMenuComponent } from '../menu/menu.component';
4
+ import { type UiMenuItem } from '../utils/models';
5
+
6
+ @Component({
7
+ selector: 'ui-split-button',
8
+ changeDetection: ChangeDetectionStrategy.OnPush,
9
+ imports: [UiButtonComponent, UiMenuComponent],
10
+ template: `
11
+ <div class="relative inline-flex">
12
+ <div class="inline-flex overflow-hidden rounded-ui">
13
+ <ui-button [buttonClass]="mainClass" (pressed)="activated.emit()">{{ label() }}</ui-button>
14
+ <ui-button size="icon" buttonClass="rounded-l-none" aria-label="Más acciones" (pressed)="open.set(!open())">
15
+ ▾
16
+ </ui-button>
17
+ </div>
18
+ @if (open()) {
19
+ <div class="absolute top-full right-0 mt-1">
20
+ <ui-menu [items]="items()" (selected)="choose($event)" />
21
+ </div>
22
+ }
23
+ </div>
24
+ `,
25
+ host: { class: 'inline-flex' },
26
+ })
27
+ export class UiSplitButtonComponent {
28
+ readonly label = input.required<string>();
29
+ readonly items = input.required<readonly UiMenuItem[]>();
30
+ readonly activated = output<void>();
31
+ readonly selected = output<UiMenuItem>();
32
+ readonly open = signal(false);
33
+ readonly mainClass = 'rounded-r-none';
34
+
35
+ choose(item: UiMenuItem): void {
36
+ this.selected.emit(item);
37
+ this.open.set(false);
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ export { UiStepperComponent } from './stepper.component';
2
+ export type { UiStep } from './stepper.component';
@@ -0,0 +1,45 @@
1
+ import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
2
+
3
+ export interface UiStep {
4
+ label: string;
5
+ description: string;
6
+ }
7
+
8
+ @Component({
9
+ selector: 'ui-stepper',
10
+ changeDetection: ChangeDetectionStrategy.OnPush,
11
+ template: `
12
+ <ol class="flex flex-col gap-4">
13
+ @for (step of steps(); track step.label; let index = $index) {
14
+ <li class="flex gap-3">
15
+ <button
16
+ type="button"
17
+ class="flex size-8 shrink-0 items-center justify-center rounded-full text-sm"
18
+ [class]="markerClass(index)"
19
+ (click)="activeIndex.set(index)"
20
+ >
21
+ {{ index + 1 }}
22
+ </button>
23
+ <div>
24
+ <p class="text-sm font-medium">{{ step.label }}</p>
25
+ <p class="text-sm text-muted-foreground">{{ step.description }}</p>
26
+ </div>
27
+ </li>
28
+ }
29
+ </ol>
30
+ `,
31
+ })
32
+ export class UiStepperComponent {
33
+ readonly steps = input.required<readonly UiStep[]>();
34
+ readonly activeIndex = model(0);
35
+
36
+ markerClass(index: number): string {
37
+ if (index === this.activeIndex()) {
38
+ return 'bg-primary text-primary-foreground';
39
+ }
40
+ if (index < this.activeIndex()) {
41
+ return 'bg-secondary text-secondary-foreground';
42
+ }
43
+ return 'bg-muted text-muted-foreground';
44
+ }
45
+ }
package/src/styles.css ADDED
@@ -0,0 +1,63 @@
1
+ /* Importar después de tailwindcss. Las clases de los componentes usan estos tokens. */
2
+
3
+ @theme inline {
4
+ --color-background: var(--ui-background);
5
+ --color-foreground: var(--ui-foreground);
6
+ --color-card: var(--ui-card);
7
+ --color-card-foreground: var(--ui-card-foreground);
8
+ --color-primary: var(--ui-primary);
9
+ --color-primary-foreground: var(--ui-primary-foreground);
10
+ --color-secondary: var(--ui-secondary);
11
+ --color-secondary-foreground: var(--ui-secondary-foreground);
12
+ --color-muted: var(--ui-muted);
13
+ --color-muted-foreground: var(--ui-muted-foreground);
14
+ --color-accent: var(--ui-accent);
15
+ --color-accent-foreground: var(--ui-accent-foreground);
16
+ --color-destructive: var(--ui-destructive);
17
+ --color-destructive-foreground: var(--ui-destructive-foreground);
18
+ --color-border: var(--ui-border);
19
+ --color-input: var(--ui-input);
20
+ --color-ring: var(--ui-ring);
21
+ --radius-ui: var(--ui-radius);
22
+ }
23
+
24
+ :root {
25
+ --ui-background: oklch(1 0 0);
26
+ --ui-foreground: oklch(0.145 0 0);
27
+ --ui-card: oklch(1 0 0);
28
+ --ui-card-foreground: oklch(0.145 0 0);
29
+ --ui-primary: oklch(0.205 0 0);
30
+ --ui-primary-foreground: oklch(0.985 0 0);
31
+ --ui-secondary: oklch(0.97 0 0);
32
+ --ui-secondary-foreground: oklch(0.205 0 0);
33
+ --ui-muted: oklch(0.97 0 0);
34
+ --ui-muted-foreground: oklch(0.556 0 0);
35
+ --ui-accent: oklch(0.97 0 0);
36
+ --ui-accent-foreground: oklch(0.205 0 0);
37
+ --ui-destructive: oklch(0.577 0.245 27.325);
38
+ --ui-destructive-foreground: oklch(0.985 0 0);
39
+ --ui-border: oklch(0.922 0 0);
40
+ --ui-input: oklch(0.922 0 0);
41
+ --ui-ring: oklch(0.708 0 0);
42
+ --ui-radius: 0.5rem;
43
+ }
44
+
45
+ .dark {
46
+ --ui-background: oklch(0.145 0 0);
47
+ --ui-foreground: oklch(0.985 0 0);
48
+ --ui-card: oklch(0.205 0 0);
49
+ --ui-card-foreground: oklch(0.985 0 0);
50
+ --ui-primary: oklch(0.922 0 0);
51
+ --ui-primary-foreground: oklch(0.205 0 0);
52
+ --ui-secondary: oklch(0.269 0 0);
53
+ --ui-secondary-foreground: oklch(0.985 0 0);
54
+ --ui-muted: oklch(0.269 0 0);
55
+ --ui-muted-foreground: oklch(0.708 0 0);
56
+ --ui-accent: oklch(0.269 0 0);
57
+ --ui-accent-foreground: oklch(0.985 0 0);
58
+ --ui-destructive: oklch(0.704 0.191 22.216);
59
+ --ui-destructive-foreground: oklch(0.985 0 0);
60
+ --ui-border: oklch(1 0 0 / 10%);
61
+ --ui-input: oklch(1 0 0 / 15%);
62
+ --ui-ring: oklch(0.556 0 0);
63
+ }
@@ -0,0 +1,2 @@
1
+ export { UiTableComponent } from './table.component';
2
+ export type { UiTableColumn } from './table.component';
@@ -0,0 +1,47 @@
1
+ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
2
+
3
+ export interface UiTableColumn {
4
+ key: string;
5
+ header: string;
6
+ }
7
+
8
+ @Component({
9
+ selector: 'ui-table',
10
+ changeDetection: ChangeDetectionStrategy.OnPush,
11
+ template: `
12
+ <div class="overflow-auto rounded-ui border border-border">
13
+ <table class="w-full border-collapse text-sm">
14
+ <thead class="bg-muted text-muted-foreground">
15
+ <tr>
16
+ @for (column of columns(); track column.key) {
17
+ <th class="px-3 py-2 text-left font-medium">{{ column.header }}</th>
18
+ }
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ @for (row of rows(); track $index) {
23
+ <tr class="border-t border-border">
24
+ @for (column of columns(); track column.key) {
25
+ <td class="px-3 py-2">{{ valueOf(row, column.key) }}</td>
26
+ }
27
+ </tr>
28
+ }
29
+ </tbody>
30
+ </table>
31
+ @if (rows().length === 0) {
32
+ <p class="p-4 text-sm text-muted-foreground">Sin resultados.</p>
33
+ }
34
+ </div>
35
+ `,
36
+ })
37
+ export class UiTableComponent {
38
+ readonly columns = input.required<readonly UiTableColumn[]>();
39
+ readonly rows = input.required<readonly Record<string, string | number>[]>();
40
+
41
+ valueOf(row: Record<string, string | number>, key: string): string | number {
42
+ if (!(key in row)) {
43
+ throw new Error(`ui-table no encontró la columna "${key}".`);
44
+ }
45
+ return row[key];
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export { UiTabComponent, UiTabsComponent } from './tabs.component';