@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,76 @@
1
+ import { ChangeDetectionStrategy, Component, computed, effect, input, model, contentChildren, signal } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'ui-tab',
5
+ changeDetection: ChangeDetectionStrategy.OnPush,
6
+ template: `
7
+ @if (active()) {
8
+ <div class="pt-4">
9
+ <ng-content />
10
+ </div>
11
+ }
12
+ `,
13
+ })
14
+ export class UiTabComponent {
15
+ readonly title = input.required<string>();
16
+ private readonly activeState = signal(false);
17
+
18
+ active(): boolean {
19
+ return this.activeState();
20
+ }
21
+
22
+ setActive(active: boolean): void {
23
+ this.activeState.set(active);
24
+ }
25
+ }
26
+
27
+ @Component({
28
+ selector: 'ui-tabs',
29
+ changeDetection: ChangeDetectionStrategy.OnPush,
30
+ template: `
31
+ <div class="w-full">
32
+ <div class="flex gap-1 border-b border-border" role="tablist">
33
+ @for (tab of tabList(); track tab.title(); let index = $index) {
34
+ <button
35
+ type="button"
36
+ role="tab"
37
+ class="border-b-2 px-3 py-2 text-sm"
38
+ [class]="tabClass(index)"
39
+ [attr.aria-selected]="index === selected()"
40
+ (click)="selected.set(index)"
41
+ >
42
+ {{ tab.title() }}
43
+ </button>
44
+ }
45
+ </div>
46
+ <ng-content />
47
+ </div>
48
+ `,
49
+ })
50
+ export class UiTabsComponent {
51
+ readonly selected = model(0);
52
+ readonly tabList = contentChildren(UiTabComponent);
53
+
54
+ constructor() {
55
+ effect(() => {
56
+ const tabs = this.tabList();
57
+ const selected = this.selected();
58
+ if (tabs.length === 0) {
59
+ return;
60
+ }
61
+ if (selected < 0 || selected >= tabs.length) {
62
+ throw new Error('ui-tabs selected está fuera de rango.');
63
+ }
64
+ tabs.forEach((tab, index) => {
65
+ tab.setActive(index === selected);
66
+ });
67
+ });
68
+ }
69
+
70
+ tabClass(index: number): string {
71
+ if (index === this.selected()) {
72
+ return 'border-primary text-foreground';
73
+ }
74
+ return 'border-transparent text-muted-foreground';
75
+ }
76
+ }
@@ -0,0 +1 @@
1
+ export { UiTagComponent } from './tag.component';
@@ -0,0 +1,22 @@
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-tag',
7
+ changeDetection: ChangeDetectionStrategy.OnPush,
8
+ template: `
9
+ <span class="inline-flex rounded-ui border px-2 py-0.5 text-xs font-medium" [class]="severityClass()">
10
+ {{ label() }}
11
+ </span>
12
+ `,
13
+ host: { class: 'inline-flex' },
14
+ })
15
+ export class UiTagComponent {
16
+ readonly label = input.required<string>();
17
+ readonly severity = input<UiSeverity>('info');
18
+
19
+ severityClass(): string {
20
+ return uiSeverityClass[this.severity()];
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export { UiTextareaComponent } from './textarea.component';
@@ -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 { uiLabelClass, uiTextareaClass } from '../utils/classes';
12
+
13
+ @Component({
14
+ selector: 'ui-textarea',
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
+ <textarea
22
+ [id]="resolvedId()"
23
+ [attr.name]="resolvedName()"
24
+ [placeholder]="placeholder()"
25
+ [disabled]="isDisabled()"
26
+ [readOnly]="readOnly()"
27
+ [rows]="rows()"
28
+ [value]="value()"
29
+ [class]="fieldClass"
30
+ (input)="onInput($event)"
31
+ (blur)="markTouched()"
32
+ ></textarea>
33
+ </div>
34
+ `,
35
+ providers: [
36
+ {
37
+ provide: NG_VALUE_ACCESSOR,
38
+ useExisting: forwardRef(() => UiTextareaComponent),
39
+ multi: true,
40
+ },
41
+ ],
42
+ })
43
+ export class UiTextareaComponent implements ControlValueAccessor {
44
+ readonly value = model('');
45
+ readonly label = input('');
46
+ readonly placeholder = input('');
47
+ readonly name = input('');
48
+ readonly inputId = input('');
49
+ readonly rows = input(4);
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 = uiTextareaClass;
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 HTMLTextAreaElement)) {
77
+ throw new Error('ui-textarea esperaba un HTMLTextAreaElement.');
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,2 @@
1
+ export { UiToastComponent, UiToastService } from './toast.component';
2
+ export type { UiToastRequest } from './toast.component';
@@ -0,0 +1,60 @@
1
+ import { ChangeDetectionStrategy, Component, Injectable, inject, signal } from '@angular/core';
2
+ import { uiSeverityClass } from '../utils/classes';
3
+ import { type UiSeverity } from '../utils/models';
4
+
5
+ export interface UiToastRequest {
6
+ title: string;
7
+ description: string;
8
+ severity: UiSeverity;
9
+ }
10
+
11
+ interface UiToastItem extends UiToastRequest {
12
+ id: string;
13
+ }
14
+
15
+ @Injectable({ providedIn: 'root' })
16
+ export class UiToastService {
17
+ private readonly items = signal<readonly UiToastItem[]>([]);
18
+ readonly toasts = this.items.asReadonly();
19
+
20
+ show(request: UiToastRequest): void {
21
+ const item: UiToastItem = {
22
+ id: crypto.randomUUID(),
23
+ title: request.title,
24
+ description: request.description,
25
+ severity: request.severity,
26
+ };
27
+ this.items.update((current) => [...current, item]);
28
+ }
29
+
30
+ dismiss(id: string): void {
31
+ this.items.update((current) => current.filter((item) => item.id !== id));
32
+ }
33
+ }
34
+
35
+ @Component({
36
+ selector: 'ui-toast',
37
+ changeDetection: ChangeDetectionStrategy.OnPush,
38
+ template: `
39
+ <div class="fixed right-4 bottom-4 z-50 flex w-80 flex-col gap-2">
40
+ @for (toast of toastService.toasts(); track toast.id) {
41
+ <article class="rounded-ui border p-4 shadow-md" [class]="severityClass(toast.severity)">
42
+ <div class="flex items-start justify-between gap-3">
43
+ <div>
44
+ <h3 class="text-sm font-semibold">{{ toast.title }}</h3>
45
+ <p class="text-sm">{{ toast.description }}</p>
46
+ </div>
47
+ <button type="button" class="text-sm" (click)="toastService.dismiss(toast.id)">Cerrar</button>
48
+ </div>
49
+ </article>
50
+ }
51
+ </div>
52
+ `,
53
+ })
54
+ export class UiToastComponent {
55
+ readonly toastService = inject(UiToastService);
56
+
57
+ severityClass(severity: UiSeverity): string {
58
+ return uiSeverityClass[severity];
59
+ }
60
+ }
@@ -0,0 +1 @@
1
+ export { UiToggleButtonComponent } from './toggle-button.component';
@@ -0,0 +1,80 @@
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 { cn } from '../utils/cn';
12
+
13
+ @Component({
14
+ selector: 'ui-toggle-button',
15
+ changeDetection: ChangeDetectionStrategy.OnPush,
16
+ template: `
17
+ <button
18
+ type="button"
19
+ [attr.aria-pressed]="value()"
20
+ [disabled]="isDisabled()"
21
+ [class]="classes()"
22
+ (click)="toggle()"
23
+ (blur)="markTouched()"
24
+ >
25
+ <ng-content />
26
+ </button>
27
+ `,
28
+ providers: [
29
+ {
30
+ provide: NG_VALUE_ACCESSOR,
31
+ useExisting: forwardRef(() => UiToggleButtonComponent),
32
+ multi: true,
33
+ },
34
+ ],
35
+ host: { class: 'inline-flex' },
36
+ })
37
+ export class UiToggleButtonComponent implements ControlValueAccessor {
38
+ readonly value = model(false);
39
+ readonly disabled = input(false);
40
+ private readonly formDisabled = signal(false);
41
+ private onChange: (value: boolean) => void = () => undefined;
42
+ private onTouched: () => void = () => undefined;
43
+ readonly isDisabled = computed(() => this.disabled() || this.formDisabled());
44
+ readonly classes = computed(() =>
45
+ cn('inline-flex h-10 items-center rounded-ui border px-4 text-sm font-medium', {
46
+ 'border-primary bg-primary text-primary-foreground': this.value(),
47
+ 'border-border bg-background text-foreground': !this.value(),
48
+ }),
49
+ );
50
+
51
+ toggle(): void {
52
+ const next = !this.value();
53
+ this.value.set(next);
54
+ this.onChange(next);
55
+ }
56
+
57
+ markTouched(): void {
58
+ this.onTouched();
59
+ }
60
+
61
+ writeValue(value: boolean | null): void {
62
+ if (value === null) {
63
+ this.value.set(false);
64
+ return;
65
+ }
66
+ this.value.set(value);
67
+ }
68
+
69
+ registerOnChange(fn: (value: boolean) => void): void {
70
+ this.onChange = fn;
71
+ }
72
+
73
+ registerOnTouched(fn: () => void): void {
74
+ this.onTouched = fn;
75
+ }
76
+
77
+ setDisabledState(isDisabled: boolean): void {
78
+ this.formDisabled.set(isDisabled);
79
+ }
80
+ }
@@ -0,0 +1 @@
1
+ export { UiToggleSwitchComponent } from './toggle-switch.component';
@@ -0,0 +1,96 @@
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 { cn } from '../utils/cn';
12
+
13
+ @Component({
14
+ selector: 'ui-toggle-switch',
15
+ changeDetection: ChangeDetectionStrategy.OnPush,
16
+ template: `
17
+ <button
18
+ type="button"
19
+ role="switch"
20
+ class="inline-flex items-center gap-3 text-sm text-foreground"
21
+ [attr.aria-checked]="value()"
22
+ [disabled]="isDisabled()"
23
+ (click)="toggle()"
24
+ (blur)="markTouched()"
25
+ >
26
+ <span
27
+ [class]="
28
+ trackClass()
29
+ "
30
+ >
31
+ <span [class]="thumbClass()"></span>
32
+ </span>
33
+ @if (label().length > 0) {
34
+ <span>{{ label() }}</span>
35
+ }
36
+ </button>
37
+ `,
38
+ providers: [
39
+ {
40
+ provide: NG_VALUE_ACCESSOR,
41
+ useExisting: forwardRef(() => UiToggleSwitchComponent),
42
+ multi: true,
43
+ },
44
+ ],
45
+ })
46
+ export class UiToggleSwitchComponent implements ControlValueAccessor {
47
+ readonly value = model(false);
48
+ readonly label = input('');
49
+ readonly disabled = input(false);
50
+ private readonly formDisabled = signal(false);
51
+ private onChange: (value: boolean) => void = () => undefined;
52
+ private onTouched: () => void = () => undefined;
53
+ readonly isDisabled = computed(() => this.disabled() || this.formDisabled());
54
+ readonly trackClass = computed(() =>
55
+ cn('relative inline-flex h-6 w-11 items-center rounded-full transition-colors', {
56
+ 'bg-primary': this.value(),
57
+ 'bg-input': !this.value(),
58
+ }),
59
+ );
60
+ readonly thumbClass = computed(() =>
61
+ cn('inline-block size-5 rounded-full bg-background transition-transform', {
62
+ 'translate-x-5': this.value(),
63
+ 'translate-x-0.5': !this.value(),
64
+ }),
65
+ );
66
+
67
+ toggle(): void {
68
+ const next = !this.value();
69
+ this.value.set(next);
70
+ this.onChange(next);
71
+ }
72
+
73
+ markTouched(): void {
74
+ this.onTouched();
75
+ }
76
+
77
+ writeValue(value: boolean | null): void {
78
+ if (value === null) {
79
+ this.value.set(false);
80
+ return;
81
+ }
82
+ this.value.set(value);
83
+ }
84
+
85
+ registerOnChange(fn: (value: boolean) => void): void {
86
+ this.onChange = fn;
87
+ }
88
+
89
+ registerOnTouched(fn: () => void): void {
90
+ this.onTouched = fn;
91
+ }
92
+
93
+ setDisabledState(isDisabled: boolean): void {
94
+ this.formDisabled.set(isDisabled);
95
+ }
96
+ }
@@ -0,0 +1 @@
1
+ export { UiToolbarComponent } from './toolbar.component';
@@ -0,0 +1,17 @@
1
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'ui-toolbar',
5
+ changeDetection: ChangeDetectionStrategy.OnPush,
6
+ template: `
7
+ <div class="flex items-center gap-2 border-b border-border bg-background px-4 py-2">
8
+ <div class="flex flex-1 items-center gap-2">
9
+ <ng-content select="[uiToolbarStart]" />
10
+ </div>
11
+ <div class="flex items-center gap-2">
12
+ <ng-content select="[uiToolbarEnd]" />
13
+ </div>
14
+ </div>
15
+ `,
16
+ })
17
+ export class UiToolbarComponent {}
@@ -0,0 +1 @@
1
+ export { UiTooltipComponent } from './tooltip.component';
@@ -0,0 +1,30 @@
1
+ import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'ui-tooltip',
5
+ changeDetection: ChangeDetectionStrategy.OnPush,
6
+ template: `
7
+ <span
8
+ class="relative inline-flex"
9
+ (mouseenter)="open.set(true)"
10
+ (mouseleave)="open.set(false)"
11
+ (focusin)="open.set(true)"
12
+ (focusout)="open.set(false)"
13
+ >
14
+ <ng-content />
15
+ @if (open()) {
16
+ <span
17
+ role="tooltip"
18
+ class="absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 rounded-ui bg-foreground px-2 py-1 text-xs whitespace-nowrap text-background"
19
+ >
20
+ {{ text() }}
21
+ </span>
22
+ }
23
+ </span>
24
+ `,
25
+ host: { class: 'inline-flex' },
26
+ })
27
+ export class UiTooltipComponent {
28
+ readonly text = input.required<string>();
29
+ readonly open = signal(false);
30
+ }
@@ -0,0 +1,2 @@
1
+ export { UiTreeComponent, UiTreeNodeComponent } from './tree.component';
2
+ export type { UiTreeNode } from './tree.component';
@@ -0,0 +1,64 @@
1
+ import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core';
2
+
3
+ export interface UiTreeNode {
4
+ id: string;
5
+ label: string;
6
+ children: readonly UiTreeNode[];
7
+ }
8
+
9
+ @Component({
10
+ selector: 'ui-tree-node',
11
+ changeDetection: ChangeDetectionStrategy.OnPush,
12
+ imports: [UiTreeNodeComponent],
13
+ template: `
14
+ <li>
15
+ <button type="button" class="rounded-ui px-2 py-1 text-left text-sm hover:bg-accent" (click)="toggle()">
16
+ {{ marker() }} {{ node().label }}
17
+ </button>
18
+ @if (expanded() && node().children.length > 0) {
19
+ <ul class="ml-4 flex flex-col gap-1">
20
+ @for (child of node().children; track child.id) {
21
+ <ui-tree-node [node]="child" />
22
+ }
23
+ </ul>
24
+ }
25
+ </li>
26
+ `,
27
+ })
28
+ export class UiTreeNodeComponent {
29
+ readonly node = input.required<UiTreeNode>();
30
+ readonly expanded = signal(false);
31
+
32
+ toggle(): void {
33
+ if (this.node().children.length === 0) {
34
+ return;
35
+ }
36
+ this.expanded.set(!this.expanded());
37
+ }
38
+
39
+ marker(): string {
40
+ if (this.node().children.length === 0) {
41
+ return '•';
42
+ }
43
+ if (this.expanded()) {
44
+ return '▾';
45
+ }
46
+ return '▸';
47
+ }
48
+ }
49
+
50
+ @Component({
51
+ selector: 'ui-tree',
52
+ changeDetection: ChangeDetectionStrategy.OnPush,
53
+ imports: [UiTreeNodeComponent],
54
+ template: `
55
+ <ul class="flex flex-col gap-1">
56
+ @for (node of nodes(); track node.id) {
57
+ <ui-tree-node [node]="node" />
58
+ }
59
+ </ul>
60
+ `,
61
+ })
62
+ export class UiTreeComponent {
63
+ readonly nodes = input.required<readonly UiTreeNode[]>();
64
+ }
@@ -0,0 +1,19 @@
1
+ import { type UiSeverity } from './models';
2
+
3
+ export const uiFieldClass =
4
+ 'flex h-10 w-full rounded-ui border border-input bg-background px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50';
5
+
6
+ export const uiTextareaClass =
7
+ 'flex min-h-24 w-full rounded-ui border border-input bg-background px-3 py-2 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50';
8
+
9
+ export const uiLabelClass = 'text-sm font-medium text-foreground';
10
+
11
+ export const uiPanelClass =
12
+ 'z-50 min-w-48 rounded-ui border border-border bg-card p-1 text-card-foreground shadow-md';
13
+
14
+ export const uiSeverityClass: Record<UiSeverity, string> = {
15
+ info: 'border-sky-200 bg-sky-50 text-sky-950',
16
+ success: 'border-emerald-200 bg-emerald-50 text-emerald-950',
17
+ warning: 'border-amber-200 bg-amber-50 text-amber-950',
18
+ danger: 'border-red-200 bg-red-50 text-red-950',
19
+ };
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]): string {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,13 @@
1
+ export interface UiOption {
2
+ value: string;
3
+ label: string;
4
+ disabled?: boolean;
5
+ }
6
+
7
+ export interface UiMenuItem {
8
+ id: string;
9
+ label: string;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ export type UiSeverity = 'info' | 'success' | 'warning' | 'danger';
@@ -0,0 +1,3 @@
1
+ export { cn } from './cn';
2
+ export { uiFieldClass, uiLabelClass, uiPanelClass, uiSeverityClass, uiTextareaClass } from './classes';
3
+ export { type UiMenuItem, type UiOption, type UiSeverity } from './models';
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.