@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.
- package/LICENSE +21 -0
- package/bin/vekron-ui.js +362 -0
- package/package.json +45 -3
- package/src/accordion/accordion.component.ts +64 -0
- package/src/accordion/public-api.ts +1 -0
- package/src/autocomplete/autocomplete.component.ts +144 -0
- package/src/autocomplete/public-api.ts +1 -0
- package/src/avatar/avatar.component.ts +23 -0
- package/src/avatar/public-api.ts +1 -0
- package/src/badge/badge.component.ts +27 -0
- package/src/badge/public-api.ts +2 -0
- package/src/breadcrumb/breadcrumb.component.ts +32 -0
- package/src/breadcrumb/public-api.ts +2 -0
- package/src/button/button.component.ts +50 -0
- package/src/button/public-api.ts +2 -0
- package/src/carousel/carousel.component.ts +76 -0
- package/src/carousel/public-api.ts +2 -0
- package/src/checkbox/checkbox.component.ts +92 -0
- package/src/checkbox/public-api.ts +1 -0
- package/src/chip/chip.component.ts +20 -0
- package/src/chip/public-api.ts +1 -0
- package/src/confirm-dialog/confirm-dialog.component.ts +38 -0
- package/src/confirm-dialog/public-api.ts +1 -0
- package/src/confirm-popup/confirm-popup.component.ts +42 -0
- package/src/confirm-popup/public-api.ts +1 -0
- package/src/context-menu/context-menu.component.ts +43 -0
- package/src/context-menu/public-api.ts +1 -0
- package/src/datepicker/datepicker.component.ts +205 -0
- package/src/datepicker/public-api.ts +1 -0
- package/src/dialog/dialog.component.ts +77 -0
- package/src/dialog/public-api.ts +1 -0
- package/src/divider/divider.component.ts +17 -0
- package/src/divider/public-api.ts +1 -0
- package/src/file-upload/file-upload.component.ts +110 -0
- package/src/file-upload/public-api.ts +1 -0
- package/src/input-number/input-number.component.ts +123 -0
- package/src/input-number/public-api.ts +1 -0
- package/src/input-otp/input-otp.component.ts +129 -0
- package/src/input-otp/public-api.ts +1 -0
- package/src/input-password/input-password.component.ts +127 -0
- package/src/input-password/public-api.ts +1 -0
- package/src/input-text/input-text.component.ts +106 -0
- package/src/input-text/public-api.ts +1 -0
- package/src/menu/menu.component.ts +35 -0
- package/src/menu/public-api.ts +1 -0
- package/src/message/message.component.ts +17 -0
- package/src/message/public-api.ts +1 -0
- package/src/paginator/paginator.component.ts +62 -0
- package/src/paginator/public-api.ts +1 -0
- package/src/pdf-viewer/pdf-viewer.component.ts +16 -0
- package/src/pdf-viewer/public-api.ts +1 -0
- package/src/pick-list/pick-list.component.ts +68 -0
- package/src/pick-list/public-api.ts +1 -0
- package/src/popover/popover.component.ts +22 -0
- package/src/popover/public-api.ts +1 -0
- package/src/progress-bar/progress-bar.component.ts +27 -0
- package/src/progress-bar/public-api.ts +1 -0
- package/src/progress-spinner/progress-spinner.component.ts +27 -0
- package/src/progress-spinner/public-api.ts +1 -0
- package/src/public-api.ts +46 -0
- package/src/radio-button/public-api.ts +1 -0
- package/src/radio-button/radio-button.component.ts +98 -0
- package/src/registry.json +184 -0
- package/src/scroll-top/public-api.ts +1 -0
- package/src/scroll-top/scroll-top.component.ts +36 -0
- package/src/select/public-api.ts +1 -0
- package/src/select/select.component.ts +116 -0
- package/src/select-button/public-api.ts +1 -0
- package/src/select-button/select-button.component.ts +70 -0
- package/src/skeleton/public-api.ts +1 -0
- package/src/skeleton/skeleton.component.ts +11 -0
- package/src/split-button/public-api.ts +1 -0
- package/src/split-button/split-button.component.ts +39 -0
- package/src/stepper/public-api.ts +2 -0
- package/src/stepper/stepper.component.ts +45 -0
- package/src/styles.css +63 -0
- package/src/table/public-api.ts +2 -0
- package/src/table/table.component.ts +47 -0
- package/src/tabs/public-api.ts +1 -0
- package/src/tabs/tabs.component.ts +76 -0
- package/src/tag/public-api.ts +1 -0
- package/src/tag/tag.component.ts +22 -0
- package/src/textarea/public-api.ts +1 -0
- package/src/textarea/textarea.component.ts +106 -0
- package/src/toast/public-api.ts +2 -0
- package/src/toast/toast.component.ts +60 -0
- package/src/toggle-button/public-api.ts +1 -0
- package/src/toggle-button/toggle-button.component.ts +80 -0
- package/src/toggle-switch/public-api.ts +1 -0
- package/src/toggle-switch/toggle-switch.component.ts +96 -0
- package/src/toolbar/public-api.ts +1 -0
- package/src/toolbar/toolbar.component.ts +17 -0
- package/src/tooltip/public-api.ts +1 -0
- package/src/tooltip/tooltip.component.ts +30 -0
- package/src/tree/public-api.ts +2 -0
- package/src/tree/tree.component.ts +64 -0
- package/src/utils/classes.ts +19 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/models.ts +13 -0
- package/src/utils/public-api.ts +3 -0
- package/README.md +0 -3
|
@@ -0,0 +1,106 @@
|
|
|
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 { uiFieldClass, uiLabelClass } from '../utils/classes';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'ui-input-text',
|
|
15
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
16
|
+
template: `
|
|
17
|
+
<div class="flex w-full flex-col gap-1.5">
|
|
18
|
+
@if (label().length > 0) {
|
|
19
|
+
<label [class]="labelClass" [attr.for]="resolvedId()">{{ label() }}</label>
|
|
20
|
+
}
|
|
21
|
+
<input
|
|
22
|
+
[id]="resolvedId()"
|
|
23
|
+
[attr.name]="resolvedName()"
|
|
24
|
+
[type]="type()"
|
|
25
|
+
[placeholder]="placeholder()"
|
|
26
|
+
[disabled]="isDisabled()"
|
|
27
|
+
[readOnly]="readOnly()"
|
|
28
|
+
[value]="value()"
|
|
29
|
+
[class]="fieldClass"
|
|
30
|
+
(input)="onInput($event)"
|
|
31
|
+
(blur)="markTouched()"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
`,
|
|
35
|
+
providers: [
|
|
36
|
+
{
|
|
37
|
+
provide: NG_VALUE_ACCESSOR,
|
|
38
|
+
useExisting: forwardRef(() => UiInputTextComponent),
|
|
39
|
+
multi: true,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
export class UiInputTextComponent implements ControlValueAccessor {
|
|
44
|
+
readonly value = model('');
|
|
45
|
+
readonly label = input('');
|
|
46
|
+
readonly placeholder = input('');
|
|
47
|
+
readonly name = input('');
|
|
48
|
+
readonly inputId = input('');
|
|
49
|
+
readonly type = input<'text' | 'email' | 'search' | 'tel' | 'url'>('text');
|
|
50
|
+
readonly readOnly = input(false);
|
|
51
|
+
readonly disabled = input(false);
|
|
52
|
+
private readonly formDisabled = signal(false);
|
|
53
|
+
private readonly generatedId = crypto.randomUUID();
|
|
54
|
+
private onChange: (value: string) => void = () => undefined;
|
|
55
|
+
private onTouched: () => void = () => undefined;
|
|
56
|
+
readonly fieldClass = uiFieldClass;
|
|
57
|
+
readonly labelClass = uiLabelClass;
|
|
58
|
+
readonly isDisabled = computed(() => this.disabled() || this.formDisabled());
|
|
59
|
+
readonly resolvedId = computed(() => {
|
|
60
|
+
const provided = this.inputId();
|
|
61
|
+
if (provided.length > 0) {
|
|
62
|
+
return provided;
|
|
63
|
+
}
|
|
64
|
+
return this.generatedId;
|
|
65
|
+
});
|
|
66
|
+
readonly resolvedName = computed(() => {
|
|
67
|
+
const name = this.name();
|
|
68
|
+
if (name.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return name;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
onInput(event: Event): void {
|
|
75
|
+
const target = event.target;
|
|
76
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
77
|
+
throw new Error('ui-input-text esperaba un HTMLInputElement.');
|
|
78
|
+
}
|
|
79
|
+
this.value.set(target.value);
|
|
80
|
+
this.onChange(target.value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
markTouched(): void {
|
|
84
|
+
this.onTouched();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
writeValue(value: string | null): void {
|
|
88
|
+
if (value === null) {
|
|
89
|
+
this.value.set('');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.value.set(value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
registerOnChange(fn: (value: string) => void): void {
|
|
96
|
+
this.onChange = fn;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
registerOnTouched(fn: () => void): void {
|
|
100
|
+
this.onTouched = fn;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setDisabledState(isDisabled: boolean): void {
|
|
104
|
+
this.formDisabled.set(isDisabled);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiInputTextComponent } from './input-text.component';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
|
2
|
+
import { uiPanelClass } from '../utils/classes';
|
|
3
|
+
import { type UiMenuItem } from '../utils/models';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'ui-menu',
|
|
7
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8
|
+
template: `
|
|
9
|
+
<div [class]="panelClass" role="menu">
|
|
10
|
+
@for (item of items(); track item.id) {
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
class="flex w-full rounded-ui px-3 py-2 text-left text-sm hover:bg-accent disabled:opacity-50"
|
|
14
|
+
role="menuitem"
|
|
15
|
+
[disabled]="item.disabled === true"
|
|
16
|
+
(click)="choose(item)"
|
|
17
|
+
>
|
|
18
|
+
{{ item.label }}
|
|
19
|
+
</button>
|
|
20
|
+
}
|
|
21
|
+
</div>
|
|
22
|
+
`,
|
|
23
|
+
})
|
|
24
|
+
export class UiMenuComponent {
|
|
25
|
+
readonly items = input.required<readonly UiMenuItem[]>();
|
|
26
|
+
readonly selected = output<UiMenuItem>();
|
|
27
|
+
readonly panelClass = uiPanelClass;
|
|
28
|
+
|
|
29
|
+
choose(item: UiMenuItem): void {
|
|
30
|
+
if (item.disabled === true) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.selected.emit(item);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiMenuComponent } from './menu.component';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
2
|
+
import { uiSeverityClass } from '../utils/classes';
|
|
3
|
+
import { type UiSeverity } from '../utils/models';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'ui-message',
|
|
7
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8
|
+
template: `<div role="status" class="rounded-ui border px-4 py-3 text-sm" [class]="severityClass()">{{ text() }}</div>`,
|
|
9
|
+
})
|
|
10
|
+
export class UiMessageComponent {
|
|
11
|
+
readonly text = input.required<string>();
|
|
12
|
+
readonly severity = input<UiSeverity>('info');
|
|
13
|
+
|
|
14
|
+
severityClass(): string {
|
|
15
|
+
return uiSeverityClass[this.severity()];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiMessageComponent } from './message.component';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input, model } from '@angular/core';
|
|
2
|
+
import { type UiButtonVariant, UiButtonComponent } from '../button/button.component';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'ui-paginator',
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
imports: [UiButtonComponent],
|
|
8
|
+
template: `
|
|
9
|
+
<nav class="flex items-center gap-2" aria-label="Paginación">
|
|
10
|
+
<ui-button variant="outline" size="sm" [disabled]="page() <= 1" (pressed)="go(page() - 1)">Anterior</ui-button>
|
|
11
|
+
@for (pageNumber of pages(); track pageNumber) {
|
|
12
|
+
<ui-button size="sm" [variant]="variantFor(pageNumber)" (pressed)="go(pageNumber)">
|
|
13
|
+
{{ pageNumber }}
|
|
14
|
+
</ui-button>
|
|
15
|
+
}
|
|
16
|
+
<ui-button variant="outline" size="sm" [disabled]="page() >= pageCount()" (pressed)="go(page() + 1)">
|
|
17
|
+
Siguiente
|
|
18
|
+
</ui-button>
|
|
19
|
+
</nav>
|
|
20
|
+
`,
|
|
21
|
+
})
|
|
22
|
+
export class UiPaginatorComponent {
|
|
23
|
+
readonly page = model(1);
|
|
24
|
+
readonly pageSize = input(10);
|
|
25
|
+
readonly total = input.required<number>();
|
|
26
|
+
readonly pageCount = computed(() => {
|
|
27
|
+
const pageSize = this.pageSize();
|
|
28
|
+
if (pageSize < 1) {
|
|
29
|
+
throw new Error('ui-paginator pageSize debe ser mayor a 0.');
|
|
30
|
+
}
|
|
31
|
+
const count = Math.ceil(this.total() / pageSize);
|
|
32
|
+
if (count < 1) {
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
return count;
|
|
36
|
+
});
|
|
37
|
+
readonly pages = computed(() => {
|
|
38
|
+
const count = this.pageCount();
|
|
39
|
+
const current = this.page();
|
|
40
|
+
const start = Math.max(1, current - 2);
|
|
41
|
+
const end = Math.min(count, current + 2);
|
|
42
|
+
const pageNumbers: number[] = [];
|
|
43
|
+
for (let pageNumber = start; pageNumber <= end; pageNumber += 1) {
|
|
44
|
+
pageNumbers.push(pageNumber);
|
|
45
|
+
}
|
|
46
|
+
return pageNumbers;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
variantFor(pageNumber: number): UiButtonVariant {
|
|
50
|
+
if (pageNumber === this.page()) {
|
|
51
|
+
return 'primary';
|
|
52
|
+
}
|
|
53
|
+
return 'outline';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
go(nextPage: number): void {
|
|
57
|
+
if (nextPage < 1 || nextPage > this.pageCount()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.page.set(nextPage);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiPaginatorComponent } from './paginator.component';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
|
|
2
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'ui-pdf-viewer',
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
template: `
|
|
8
|
+
<iframe class="h-[70vh] w-full rounded-ui border border-border" [title]="title()" [src]="resourceUrl()"></iframe>
|
|
9
|
+
`,
|
|
10
|
+
})
|
|
11
|
+
export class UiPdfViewerComponent {
|
|
12
|
+
readonly src = input.required<string>();
|
|
13
|
+
readonly title = input('Documento PDF');
|
|
14
|
+
private readonly sanitizer = inject(DomSanitizer);
|
|
15
|
+
readonly resourceUrl = computed(() => this.sanitizer.bypassSecurityTrustResourceUrl(this.src()));
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiPdfViewerComponent } from './pdf-viewer.component';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, model } from '@angular/core';
|
|
2
|
+
import { UiButtonComponent } from '../button/button.component';
|
|
3
|
+
import { type UiOption } from '../utils/models';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'ui-pick-list',
|
|
7
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8
|
+
imports: [UiButtonComponent],
|
|
9
|
+
template: `
|
|
10
|
+
<div class="grid items-stretch gap-3 md:grid-cols-[1fr_auto_1fr]">
|
|
11
|
+
<ul class="min-h-40 rounded-ui border border-border p-2">
|
|
12
|
+
@for (option of source(); track option.value) {
|
|
13
|
+
<li>
|
|
14
|
+
<button type="button" class="w-full rounded-ui px-2 py-1 text-left text-sm hover:bg-accent" (click)="moveToTarget(option)">
|
|
15
|
+
{{ option.label }}
|
|
16
|
+
</button>
|
|
17
|
+
</li>
|
|
18
|
+
} @empty {
|
|
19
|
+
<li class="px-2 py-1 text-sm text-muted-foreground">Sin elementos</li>
|
|
20
|
+
}
|
|
21
|
+
</ul>
|
|
22
|
+
<div class="flex items-center justify-center gap-2 md:flex-col">
|
|
23
|
+
<ui-button size="sm" variant="outline" (pressed)="moveAllToTarget()">≫</ui-button>
|
|
24
|
+
<ui-button size="sm" variant="outline" (pressed)="moveAllToSource()">≪</ui-button>
|
|
25
|
+
</div>
|
|
26
|
+
<ul class="min-h-40 rounded-ui border border-border p-2">
|
|
27
|
+
@for (option of target(); track option.value) {
|
|
28
|
+
<li>
|
|
29
|
+
<button type="button" class="w-full rounded-ui px-2 py-1 text-left text-sm hover:bg-accent" (click)="moveToSource(option)">
|
|
30
|
+
{{ option.label }}
|
|
31
|
+
</button>
|
|
32
|
+
</li>
|
|
33
|
+
} @empty {
|
|
34
|
+
<li class="px-2 py-1 text-sm text-muted-foreground">Sin elementos</li>
|
|
35
|
+
}
|
|
36
|
+
</ul>
|
|
37
|
+
</div>
|
|
38
|
+
`,
|
|
39
|
+
})
|
|
40
|
+
export class UiPickListComponent {
|
|
41
|
+
readonly source = model.required<readonly UiOption[]>();
|
|
42
|
+
readonly target = model<readonly UiOption[]>([]);
|
|
43
|
+
|
|
44
|
+
moveToTarget(option: UiOption): void {
|
|
45
|
+
if (option.disabled === true) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.source.set(this.source().filter((item) => item.value !== option.value));
|
|
49
|
+
this.target.set([...this.target(), option]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
moveToSource(option: UiOption): void {
|
|
53
|
+
this.target.set(this.target().filter((item) => item.value !== option.value));
|
|
54
|
+
this.source.set([...this.source(), option]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
moveAllToTarget(): void {
|
|
58
|
+
const movable = this.source().filter((option) => option.disabled !== true);
|
|
59
|
+
const blocked = this.source().filter((option) => option.disabled === true);
|
|
60
|
+
this.target.set([...this.target(), ...movable]);
|
|
61
|
+
this.source.set(blocked);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
moveAllToSource(): void {
|
|
65
|
+
this.source.set([...this.source(), ...this.target()]);
|
|
66
|
+
this.target.set([]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiPickListComponent } from './pick-list.component';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, model } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ui-popover',
|
|
5
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6
|
+
template: `
|
|
7
|
+
<div class="relative inline-flex">
|
|
8
|
+
<div (click)="open.set(!open())">
|
|
9
|
+
<ng-content select="[uiPopoverTrigger]" />
|
|
10
|
+
</div>
|
|
11
|
+
@if (open()) {
|
|
12
|
+
<div class="absolute top-full left-0 z-50 mt-2 min-w-48 rounded-ui border border-border bg-card p-3 text-sm text-card-foreground shadow-md">
|
|
13
|
+
<ng-content select="[uiPopoverContent]" />
|
|
14
|
+
</div>
|
|
15
|
+
}
|
|
16
|
+
</div>
|
|
17
|
+
`,
|
|
18
|
+
host: { class: 'inline-flex' },
|
|
19
|
+
})
|
|
20
|
+
export class UiPopoverComponent {
|
|
21
|
+
readonly open = model(false);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiPopoverComponent } from './popover.component';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ui-progress-bar',
|
|
5
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6
|
+
template: `
|
|
7
|
+
<div
|
|
8
|
+
class="h-2 w-full overflow-hidden rounded-full bg-muted"
|
|
9
|
+
role="progressbar"
|
|
10
|
+
aria-valuemin="0"
|
|
11
|
+
aria-valuemax="100"
|
|
12
|
+
[attr.aria-valuenow]="safeValue()"
|
|
13
|
+
>
|
|
14
|
+
<div class="h-full bg-primary" [style.width.%]="safeValue()"></div>
|
|
15
|
+
</div>
|
|
16
|
+
`,
|
|
17
|
+
})
|
|
18
|
+
export class UiProgressBarComponent {
|
|
19
|
+
readonly value = input.required<number>();
|
|
20
|
+
readonly safeValue = computed(() => {
|
|
21
|
+
const value = this.value();
|
|
22
|
+
if (value < 0 || value > 100) {
|
|
23
|
+
throw new Error('ui-progress-bar value debe estar entre 0 y 100.');
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiProgressBarComponent } from './progress-bar.component';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'ui-progress-spinner',
|
|
5
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6
|
+
template: `
|
|
7
|
+
<div class="inline-flex items-center gap-2" role="status">
|
|
8
|
+
<span [class]="spinnerClass()"></span>
|
|
9
|
+
<span class="sr-only">{{ label() }}</span>
|
|
10
|
+
</div>
|
|
11
|
+
`,
|
|
12
|
+
})
|
|
13
|
+
export class UiProgressSpinnerComponent {
|
|
14
|
+
readonly label = input('Cargando');
|
|
15
|
+
readonly size = input<'sm' | 'md' | 'lg'>('md');
|
|
16
|
+
|
|
17
|
+
spinnerClass(): string {
|
|
18
|
+
const base = 'animate-spin rounded-full border-2 border-muted border-t-primary';
|
|
19
|
+
if (this.size() === 'sm') {
|
|
20
|
+
return `${base} size-4`;
|
|
21
|
+
}
|
|
22
|
+
if (this.size() === 'lg') {
|
|
23
|
+
return `${base} size-12`;
|
|
24
|
+
}
|
|
25
|
+
return `${base} size-8`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiProgressSpinnerComponent } from './progress-spinner.component';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export * from './accordion/public-api';
|
|
2
|
+
export * from './autocomplete/public-api';
|
|
3
|
+
export * from './avatar/public-api';
|
|
4
|
+
export * from './badge/public-api';
|
|
5
|
+
export * from './breadcrumb/public-api';
|
|
6
|
+
export * from './button/public-api';
|
|
7
|
+
export * from './carousel/public-api';
|
|
8
|
+
export * from './checkbox/public-api';
|
|
9
|
+
export * from './chip/public-api';
|
|
10
|
+
export * from './confirm-dialog/public-api';
|
|
11
|
+
export * from './confirm-popup/public-api';
|
|
12
|
+
export * from './context-menu/public-api';
|
|
13
|
+
export * from './datepicker/public-api';
|
|
14
|
+
export * from './dialog/public-api';
|
|
15
|
+
export * from './divider/public-api';
|
|
16
|
+
export * from './file-upload/public-api';
|
|
17
|
+
export * from './input-number/public-api';
|
|
18
|
+
export * from './input-otp/public-api';
|
|
19
|
+
export * from './input-password/public-api';
|
|
20
|
+
export * from './input-text/public-api';
|
|
21
|
+
export * from './menu/public-api';
|
|
22
|
+
export * from './message/public-api';
|
|
23
|
+
export * from './paginator/public-api';
|
|
24
|
+
export * from './pdf-viewer/public-api';
|
|
25
|
+
export * from './pick-list/public-api';
|
|
26
|
+
export * from './popover/public-api';
|
|
27
|
+
export * from './progress-bar/public-api';
|
|
28
|
+
export * from './progress-spinner/public-api';
|
|
29
|
+
export * from './radio-button/public-api';
|
|
30
|
+
export * from './scroll-top/public-api';
|
|
31
|
+
export * from './select/public-api';
|
|
32
|
+
export * from './select-button/public-api';
|
|
33
|
+
export * from './skeleton/public-api';
|
|
34
|
+
export * from './split-button/public-api';
|
|
35
|
+
export * from './stepper/public-api';
|
|
36
|
+
export * from './table/public-api';
|
|
37
|
+
export * from './tabs/public-api';
|
|
38
|
+
export * from './tag/public-api';
|
|
39
|
+
export * from './textarea/public-api';
|
|
40
|
+
export * from './toast/public-api';
|
|
41
|
+
export * from './toggle-button/public-api';
|
|
42
|
+
export * from './toggle-switch/public-api';
|
|
43
|
+
export * from './toolbar/public-api';
|
|
44
|
+
export * from './tooltip/public-api';
|
|
45
|
+
export * from './tree/public-api';
|
|
46
|
+
export * from './utils/public-api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiRadioButtonComponent, UiRadioGroupComponent } from './radio-button.component';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
computed,
|
|
5
|
+
forwardRef,
|
|
6
|
+
inject,
|
|
7
|
+
input,
|
|
8
|
+
model,
|
|
9
|
+
signal,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'ui-radio-group',
|
|
15
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
16
|
+
template: `
|
|
17
|
+
<div class="flex flex-col gap-2" role="radiogroup" [attr.aria-label]="label()">
|
|
18
|
+
@if (label().length > 0) {
|
|
19
|
+
<span class="text-sm font-medium text-foreground">{{ label() }}</span>
|
|
20
|
+
}
|
|
21
|
+
<ng-content />
|
|
22
|
+
</div>
|
|
23
|
+
`,
|
|
24
|
+
providers: [
|
|
25
|
+
{
|
|
26
|
+
provide: NG_VALUE_ACCESSOR,
|
|
27
|
+
useExisting: forwardRef(() => UiRadioGroupComponent),
|
|
28
|
+
multi: true,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
})
|
|
32
|
+
export class UiRadioGroupComponent implements ControlValueAccessor {
|
|
33
|
+
readonly value = model('');
|
|
34
|
+
readonly label = input('');
|
|
35
|
+
readonly name = input('');
|
|
36
|
+
readonly disabled = input(false);
|
|
37
|
+
private readonly formDisabled = signal(false);
|
|
38
|
+
private readonly generatedName = crypto.randomUUID();
|
|
39
|
+
private onChange: (value: string) => void = () => undefined;
|
|
40
|
+
private onTouched: () => void = () => undefined;
|
|
41
|
+
readonly isDisabled = computed(() => this.disabled() || this.formDisabled());
|
|
42
|
+
readonly groupName = computed(() => {
|
|
43
|
+
const provided = this.name();
|
|
44
|
+
if (provided.length > 0) {
|
|
45
|
+
return provided;
|
|
46
|
+
}
|
|
47
|
+
return this.generatedName;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
select(value: string): void {
|
|
51
|
+
this.value.set(value);
|
|
52
|
+
this.onChange(value);
|
|
53
|
+
this.onTouched();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
writeValue(value: string | null): void {
|
|
57
|
+
if (value === null) {
|
|
58
|
+
this.value.set('');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.value.set(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
registerOnChange(fn: (value: string) => void): void {
|
|
65
|
+
this.onChange = fn;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
registerOnTouched(fn: () => void): void {
|
|
69
|
+
this.onTouched = fn;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setDisabledState(isDisabled: boolean): void {
|
|
73
|
+
this.formDisabled.set(isDisabled);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Component({
|
|
78
|
+
selector: 'ui-radio-button',
|
|
79
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
80
|
+
template: `
|
|
81
|
+
<label class="inline-flex items-center gap-2 text-sm text-foreground">
|
|
82
|
+
<input
|
|
83
|
+
type="radio"
|
|
84
|
+
[name]="group.groupName()"
|
|
85
|
+
[value]="option()"
|
|
86
|
+
[checked]="group.value() === option()"
|
|
87
|
+
[disabled]="group.isDisabled() || disabled()"
|
|
88
|
+
(change)="group.select(option())"
|
|
89
|
+
/>
|
|
90
|
+
<ng-content />
|
|
91
|
+
</label>
|
|
92
|
+
`,
|
|
93
|
+
})
|
|
94
|
+
export class UiRadioButtonComponent {
|
|
95
|
+
readonly option = input.required<string>();
|
|
96
|
+
readonly disabled = input(false);
|
|
97
|
+
readonly group = inject(UiRadioGroupComponent);
|
|
98
|
+
}
|