tailjng 0.0.13 → 0.0.15
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/cli/component-manager.js +45 -0
- package/cli/dependency-manager.js +52 -0
- package/cli/file-operations.js +88 -0
- package/cli/index.js +51 -0
- package/cli/settings/colors.js +17 -0
- package/cli/settings/components-list.js +87 -0
- package/cli/settings/header-generator.js +42 -0
- package/cli/settings/path-utils.js +50 -0
- package/cli/settings/prompt-utils.js +37 -0
- package/cli/settings/tailwind-check.js +21 -0
- package/fesm2022/tailjng.mjs +903 -25
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/config/tailjng-config.token.d.ts +3 -0
- package/lib/interfaces/alert/dialog-alert.interface.d.ts +52 -0
- package/lib/interfaces/alert/toast-alert.interface.d.ts +52 -0
- package/lib/interfaces/config.interface.d.ts +5 -0
- package/lib/interfaces/crud/api-response.d.ts +29 -0
- package/lib/interfaces/crud/crud.interface.d.ts +103 -0
- package/lib/services/alert/dialog-alert.service.d.ts +24 -0
- package/lib/services/alert/toast-alert.service.d.ts +26 -0
- package/lib/services/crud/converter-crud.service.d.ts +41 -0
- package/lib/services/crud/generic-crud.service.d.ts +81 -0
- package/lib/services/http/error-handler-http.service.d.ts +26 -0
- package/lib/services/http/params-http.service.d.ts +13 -0
- package/lib/services/static/icons.service.d.ts +31 -0
- package/lib/services/transformer/calendar.service.d.ts +71 -0
- package/package.json +5 -3
- package/public-api.d.ts +10 -3
- package/src/lib/components/alert/dialog-alert/dialog-alert.component.css +0 -0
- package/src/lib/components/alert/dialog-alert/dialog-alert.component.html +72 -0
- package/src/lib/components/alert/dialog-alert/dialog-alert.component.ts +66 -0
- package/src/lib/components/alert/toast-alert/toast-alert.component.css +5 -0
- package/src/lib/components/alert/toast-alert/toast-alert.component.html +76 -0
- package/src/lib/components/alert/toast-alert/toast-alert.component.ts +87 -0
- package/src/lib/components/button/button.component.css +0 -0
- package/src/lib/components/button/button.component.html +36 -0
- package/src/lib/components/button/button.component.ts +95 -0
- package/src/lib/components/checkbox/input-checkbox/input-checkbox.component.css +0 -0
- package/src/lib/components/checkbox/input-checkbox/input-checkbox.component.html +23 -0
- package/src/lib/components/checkbox/input-checkbox/input-checkbox.component.ts +44 -0
- package/src/lib/components/checkbox/switch-checkbox/switch-checkbox.component.css +0 -0
- package/src/lib/components/checkbox/switch-checkbox/switch-checkbox.component.html +26 -0
- package/src/lib/components/checkbox/switch-checkbox/switch-checkbox.component.ts +29 -0
- package/src/lib/components/color/colors.service.ts +109 -0
- package/src/lib/components/dialog/dialog.component.css +8 -0
- package/src/lib/components/dialog/dialog.component.html +57 -0
- package/src/lib/components/dialog/dialog.component.ts +179 -0
- package/src/lib/components/image/viewer-image/viewer-image.component.css +4 -0
- package/src/lib/components/image/viewer-image/viewer-image.component.html +75 -0
- package/src/lib/components/image/viewer-image/viewer-image.component.ts +131 -0
- package/src/lib/components/input/file-input/file-input.component.css +0 -0
- package/src/lib/components/input/file-input/file-input.component.html +49 -0
- package/src/lib/components/input/file-input/file-input.component.ts +218 -0
- package/src/lib/components/input/input/input.component.css +0 -0
- package/src/lib/components/input/input/input.component.html +24 -0
- package/src/lib/components/input/input/input.component.ts +78 -0
- package/src/lib/components/input/range-input/range-input.component.css +0 -0
- package/src/lib/components/input/range-input/range-input.component.html +64 -0
- package/src/lib/components/input/range-input/range-input.component.ts +78 -0
- package/src/lib/components/input/textarea-input/textarea-input.component.css +0 -0
- package/src/lib/components/input/textarea-input/textarea-input.component.html +21 -0
- package/src/lib/components/input/textarea-input/textarea-input.component.ts +75 -0
- package/src/lib/components/label/label.component.html +1 -1
- package/src/lib/components/label/label.component.ts +1 -1
- package/src/lib/components/mode-toggle/mode-toggle.component.css +0 -0
- package/src/lib/components/mode-toggle/mode-toggle.component.html +8 -0
- package/src/lib/components/mode-toggle/mode-toggle.component.ts +61 -0
- package/src/lib/components/progress-bar/progress-bar.component.css +0 -0
- package/src/lib/components/progress-bar/progress-bar.component.html +22 -0
- package/src/lib/components/progress-bar/progress-bar.component.ts +20 -0
- package/src/lib/components/select/dropdown/dropdown.component.css +0 -0
- package/src/lib/components/select/dropdown/dropdown.component.html +95 -0
- package/src/lib/components/select/dropdown/dropdown.component.ts +562 -0
- package/src/lib/components/select/multi-dropdown/multi-dropdown.component.css +0 -0
- package/src/lib/components/select/multi-dropdown/multi-dropdown.component.html +87 -0
- package/src/lib/components/select/multi-dropdown/multi-dropdown.component.ts +315 -0
- package/src/lib/components/select/multi-table/multi-table.component.css +0 -0
- package/src/lib/components/select/multi-table/multi-table.component.html +83 -0
- package/src/lib/components/select/multi-table/multi-table.component.ts +230 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.css +0 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.html +51 -0
- package/src/lib/components/toggle-radio/toggle-radio.component.ts +203 -0
- package/src/styles.css +126 -0
- package/cli/tailjng.js +0 -105
- package/lib/services/icons.service.d.ts +0 -9
- package/lib/tailjng.component.d.ts +0 -5
- package/lib/tailjng.service.d.ts +0 -6
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Component, Input, forwardRef, ViewChild, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
|
|
2
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { NgClass, CommonModule } from '@angular/common';
|
|
4
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
5
|
+
import { JIconsService } from 'tailjng';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'JFileInput',
|
|
9
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule],
|
|
10
|
+
templateUrl: './file-input.component.html',
|
|
11
|
+
styleUrl: './file-input.component.css',
|
|
12
|
+
providers: [
|
|
13
|
+
{
|
|
14
|
+
provide: NG_VALUE_ACCESSOR,
|
|
15
|
+
useExisting: forwardRef(() => JFileInputComponent),
|
|
16
|
+
multi: true
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
})
|
|
20
|
+
export class JFileInputComponent implements ControlValueAccessor, OnChanges {
|
|
21
|
+
|
|
22
|
+
@Input() id?: string;
|
|
23
|
+
@Input() name?: string;
|
|
24
|
+
|
|
25
|
+
@Input() accept: string = '';
|
|
26
|
+
@Input() multiple: boolean = false;
|
|
27
|
+
|
|
28
|
+
@Input() disabled: boolean = false;
|
|
29
|
+
@Input() required: boolean = false;
|
|
30
|
+
@Input() clearButton: boolean = false;
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@Input() showImage: boolean = false;
|
|
34
|
+
@Input() widthImgFile: number = 0;
|
|
35
|
+
@Input() heightImgFile: number = 0;
|
|
36
|
+
|
|
37
|
+
@ViewChild('fileInput') fileInputRef!: ElementRef<HTMLInputElement>;
|
|
38
|
+
|
|
39
|
+
innerValue: any = '';
|
|
40
|
+
previewUrl: string | null = null;
|
|
41
|
+
|
|
42
|
+
// This variable is used to track if the file input has been cleared
|
|
43
|
+
public fileInputCleared = false
|
|
44
|
+
|
|
45
|
+
get value(): any {
|
|
46
|
+
return this.innerValue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ControlValueAccessor
|
|
50
|
+
onChange: any = () => { };
|
|
51
|
+
onTouched: any = () => { };
|
|
52
|
+
|
|
53
|
+
constructor(public readonly iconsService: JIconsService) { }
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Writes a value to the component.
|
|
57
|
+
* @param val
|
|
58
|
+
*/
|
|
59
|
+
writeValue(val: any): void {
|
|
60
|
+
this.innerValue = val;
|
|
61
|
+
|
|
62
|
+
// If the value is null or empty, clear the preview
|
|
63
|
+
if (!val) {
|
|
64
|
+
this.previewUrl = null;
|
|
65
|
+
|
|
66
|
+
// If the value is set to null from outside (e.g., from the form)
|
|
67
|
+
// we need to reset the file input
|
|
68
|
+
if (this.fileInputRef?.nativeElement) {
|
|
69
|
+
this.fileInputRef.nativeElement.value = ""
|
|
70
|
+
this.fileInputCleared = true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Registers a callback function that is called when the value changes.
|
|
79
|
+
* @param fn The callback function.
|
|
80
|
+
*/
|
|
81
|
+
registerOnChange(fn: any): void {
|
|
82
|
+
this.onChange = fn;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Registers a callback function that is called when the input is touched.
|
|
89
|
+
* @param fn The callback function.
|
|
90
|
+
*/
|
|
91
|
+
registerOnTouched(fn: any): void {
|
|
92
|
+
this.onTouched = fn;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Sets the disabled state of the component.
|
|
99
|
+
* If disabled, it clears the inner value and preview URL.
|
|
100
|
+
* @param isDisabled
|
|
101
|
+
*/
|
|
102
|
+
setDisabledState(isDisabled: boolean): void {
|
|
103
|
+
if (isDisabled) {
|
|
104
|
+
this.innerValue = null;
|
|
105
|
+
this.previewUrl = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Handles changes to the component's inputs.
|
|
113
|
+
* If the value changes to null or empty, it clears the preview URL.
|
|
114
|
+
* @param changes
|
|
115
|
+
*/
|
|
116
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
117
|
+
// If the value is null or empty, clear the preview
|
|
118
|
+
if (changes['value'] && !this.value) {
|
|
119
|
+
this.previewUrl = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Handles the file selection event.
|
|
127
|
+
* It reads the selected file(s) and updates the inner value and preview URL if applicable
|
|
128
|
+
* @param event
|
|
129
|
+
*/
|
|
130
|
+
onFileSelected(event: Event): void {
|
|
131
|
+
const input = event.target as HTMLInputElement
|
|
132
|
+
const files = input.files
|
|
133
|
+
|
|
134
|
+
if (files && files.length > 0) {
|
|
135
|
+
const value = this.multiple ? Array.from(files) : files[0]
|
|
136
|
+
this.innerValue = value
|
|
137
|
+
this.onChange(value)
|
|
138
|
+
this.onTouched()
|
|
139
|
+
this.fileInputCleared = false
|
|
140
|
+
|
|
141
|
+
if (!this.multiple && value instanceof File && value.type?.startsWith("image")) {
|
|
142
|
+
const reader = new FileReader()
|
|
143
|
+
reader.onload = () => {
|
|
144
|
+
this.previewUrl = reader.result as string
|
|
145
|
+
}
|
|
146
|
+
reader.readAsDataURL(value)
|
|
147
|
+
} else {
|
|
148
|
+
this.previewUrl = null
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clears the file input and resets the preview URL.
|
|
157
|
+
* This method also resets the file input field so that it can be re-triggered.
|
|
158
|
+
*/
|
|
159
|
+
clearFile(): void {
|
|
160
|
+
this.innerValue = null
|
|
161
|
+
this.previewUrl = null
|
|
162
|
+
this.onChange(this.multiple ? [] : null)
|
|
163
|
+
this.onTouched()
|
|
164
|
+
|
|
165
|
+
// Reset the file input field so that it can be re-triggered
|
|
166
|
+
if (this.fileInputRef?.nativeElement) {
|
|
167
|
+
this.fileInputRef.nativeElement.value = ""
|
|
168
|
+
this.fileInputCleared = true
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resets the file input to its initial state.
|
|
176
|
+
*/
|
|
177
|
+
resetFileInput(): void {
|
|
178
|
+
this.clearFile()
|
|
179
|
+
|
|
180
|
+
// Create a new input element to replace the current one
|
|
181
|
+
// This forces the browser to recognize any selected file as "new"
|
|
182
|
+
if (this.fileInputRef?.nativeElement) {
|
|
183
|
+
const parent = this.fileInputRef.nativeElement.parentNode
|
|
184
|
+
if (parent) {
|
|
185
|
+
|
|
186
|
+
// We need to create a new input element
|
|
187
|
+
const oldInput = this.fileInputRef.nativeElement
|
|
188
|
+
const newInput = document.createElement("input")
|
|
189
|
+
|
|
190
|
+
// Copy all important attributes
|
|
191
|
+
newInput.type = "file"
|
|
192
|
+
if (this.accept) newInput.accept = this.accept
|
|
193
|
+
if (this.multiple) newInput.multiple = this.multiple
|
|
194
|
+
if (this.name) newInput.name = this.name
|
|
195
|
+
if (this.id) newInput.id = this.id
|
|
196
|
+
if (this.disabled) newInput.disabled = this.disabled
|
|
197
|
+
if (this.required) newInput.required = this.required
|
|
198
|
+
|
|
199
|
+
// Copy all event listeners
|
|
200
|
+
newInput.addEventListener("change", (e) => this.onFileSelected(e))
|
|
201
|
+
|
|
202
|
+
// Replace the input
|
|
203
|
+
parent.replaceChild(newInput, oldInput)
|
|
204
|
+
|
|
205
|
+
// Update the reference
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
if (this.fileInputRef) {
|
|
208
|
+
this.fileInputRef.nativeElement = newInput as HTMLInputElement
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="relative w-full">
|
|
2
|
+
<input
|
|
3
|
+
[type]="type"
|
|
4
|
+
[id]="id"
|
|
5
|
+
[name]="name ?? ''"
|
|
6
|
+
[placeholder]="placeholder"
|
|
7
|
+
[value]="value"
|
|
8
|
+
(input)="onInput($event)"
|
|
9
|
+
[required]="required"
|
|
10
|
+
[disabled]="disabled"
|
|
11
|
+
(blur)="onTouched()"
|
|
12
|
+
[ngClass]="combinedNgClass"
|
|
13
|
+
[class]="classes"
|
|
14
|
+
class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
@if (value && clearButton) {
|
|
18
|
+
<button type="button"
|
|
19
|
+
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
|
|
20
|
+
(click)="clearInput()">
|
|
21
|
+
<lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
|
|
22
|
+
</button>
|
|
23
|
+
}
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { NgClass, CommonModule } from '@angular/common';
|
|
4
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
5
|
+
import { JIconsService } from 'tailjng';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'JInput',
|
|
9
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule],
|
|
10
|
+
templateUrl: './input.component.html',
|
|
11
|
+
styleUrl: './input.component.css'
|
|
12
|
+
})
|
|
13
|
+
export class JInputComponent {
|
|
14
|
+
|
|
15
|
+
@Input() type: 'text' | 'password' | 'number' | 'date' | 'datetime-local' | 'email' = 'text';
|
|
16
|
+
@Input() id?: string;
|
|
17
|
+
@Input() name?: string;
|
|
18
|
+
@Input() placeholder: string = '';
|
|
19
|
+
|
|
20
|
+
@Input() disabled: boolean = false;
|
|
21
|
+
@Input() required: boolean = false;
|
|
22
|
+
@Input() classes: string = '';
|
|
23
|
+
@Input() ngClass: { [key: string]: boolean } = {};
|
|
24
|
+
|
|
25
|
+
@Input() clearButton: boolean = false;
|
|
26
|
+
|
|
27
|
+
innerValue: any = '';
|
|
28
|
+
|
|
29
|
+
get value(): any {
|
|
30
|
+
return this.innerValue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set value(val: any) {
|
|
34
|
+
if (val !== this.innerValue) {
|
|
35
|
+
this.innerValue = val;
|
|
36
|
+
this.onChange(val);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get combinedNgClass() {
|
|
41
|
+
return {
|
|
42
|
+
...(this.ngClass || {}),
|
|
43
|
+
'opacity-50': this.disabled
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ControlValueAccessor
|
|
48
|
+
onChange: any = () => { };
|
|
49
|
+
onTouched: any = () => { };
|
|
50
|
+
|
|
51
|
+
constructor(public readonly iconsService: JIconsService) { }
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Writes a value to the component.
|
|
56
|
+
* @param event
|
|
57
|
+
*/
|
|
58
|
+
onInput(event: Event): void {
|
|
59
|
+
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
|
60
|
+
this.value = target.value;
|
|
61
|
+
this.onChange(this.value);
|
|
62
|
+
this.onTouched();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Clears the input value and resets the component state.
|
|
69
|
+
* This method is typically used when a clear button is clicked.
|
|
70
|
+
*/
|
|
71
|
+
clearInput(): void {
|
|
72
|
+
this.value = '';
|
|
73
|
+
this.onChange('');
|
|
74
|
+
this.onTouched();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<div class="w-full">
|
|
2
|
+
@if (placeholder) {
|
|
3
|
+
@if (isLabel) {
|
|
4
|
+
|
|
5
|
+
<label [for]="id" class="flex text-[15px] font-bold text-black dark:text-white justify-between mb-1 opacity-80">
|
|
6
|
+
<span>{{ placeholder }}</span>
|
|
7
|
+
<span>{{ value | number:'1.0-2' }}{{simbol}}</span>
|
|
8
|
+
</label>
|
|
9
|
+
|
|
10
|
+
} @else {
|
|
11
|
+
<label [for]="id" class="block text-[15px] font-bold text-black dark:text-white text-center mb-1 opacity-80">{{ placeholder }}</label>
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
<div class="relative flex gap-2 w-full items-center justify-center align-center items-center justify-center align-center">
|
|
16
|
+
@if (min !== null) {
|
|
17
|
+
<span class="text-[15px] font-bold text-gray-500 mt-1 text-center">{{min}}{{simbol}}</span>
|
|
18
|
+
}
|
|
19
|
+
<input
|
|
20
|
+
type="range"
|
|
21
|
+
[id]="id"
|
|
22
|
+
[name]="name ?? ''"
|
|
23
|
+
[min]="min"
|
|
24
|
+
[max]="max"
|
|
25
|
+
[step]="step"
|
|
26
|
+
[required]="required"
|
|
27
|
+
[disabled]="disabled"
|
|
28
|
+
[(ngModel)]="value"
|
|
29
|
+
(blur)="onTouched()"
|
|
30
|
+
[ngClass]="combinedNgClass"
|
|
31
|
+
[class]="classes"
|
|
32
|
+
class="
|
|
33
|
+
w-full h-5 bg-background dark:bg-dark-background border border-border dark:border-dark-border
|
|
34
|
+
rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary transition duration-200
|
|
35
|
+
|
|
36
|
+
[&::-webkit-slider-thumb]:appearance-none
|
|
37
|
+
[&::-webkit-slider-thumb]:h-8
|
|
38
|
+
[&::-webkit-slider-thumb]:w-8
|
|
39
|
+
[&::-webkit-slider-thumb]:rounded-full
|
|
40
|
+
[&::-webkit-slider-thumb]:cursor-pointer
|
|
41
|
+
[&::-webkit-slider-thumb]:bg-dark-primary
|
|
42
|
+
dark:[&::-webkit-slider-thumb]:bg-primary
|
|
43
|
+
[&::-webkit-slider-thumb:hover]:bg-primary
|
|
44
|
+
dark:[&::-webkit-slider-thumb:hover]:bg-dark-primary
|
|
45
|
+
|
|
46
|
+
[&::-moz-range-thumb]:h-4
|
|
47
|
+
[&::-moz-range-thumb]:w-4
|
|
48
|
+
[&::-moz-range-thumb]:rounded-full
|
|
49
|
+
[&::-moz-range-thumb]:bg-primary
|
|
50
|
+
[&::-moz-range-thumb]:cursor-pointer
|
|
51
|
+
[&::-moz-range-thumb:hover]:bg-primary
|
|
52
|
+
dark:[&::-moz-range-thumb:hover]:bg-dark-primary
|
|
53
|
+
"
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
@if (max) {
|
|
57
|
+
<span class="text-[15px] font-bold text-gray-500 mt-1 text-center">{{max}}{{simbol}}</span>
|
|
58
|
+
}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
@if (!isLabel) {
|
|
62
|
+
<div class="text-xl font-bold text-gray-500 mt-1 text-center">{{ value }}</div>
|
|
63
|
+
}
|
|
64
|
+
</div>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { NgClass, CommonModule } from '@angular/common';
|
|
4
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'JRangeInput',
|
|
8
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule],
|
|
9
|
+
templateUrl: './range-input.component.html',
|
|
10
|
+
styleUrl: './range-input.component.css'
|
|
11
|
+
})
|
|
12
|
+
export class JRangeInputComponent {
|
|
13
|
+
|
|
14
|
+
@Input() id?: string;
|
|
15
|
+
@Input() name?: string;
|
|
16
|
+
@Input() placeholder: string = '';
|
|
17
|
+
|
|
18
|
+
@Input() disabled: boolean = false;
|
|
19
|
+
@Input() required: boolean = false;
|
|
20
|
+
@Input() classes: string = '';
|
|
21
|
+
@Input() ngClass: { [key: string]: boolean } = {};
|
|
22
|
+
|
|
23
|
+
@Input() min: number = 0;
|
|
24
|
+
@Input() max: number = 100;
|
|
25
|
+
@Input() step: number = 1;
|
|
26
|
+
|
|
27
|
+
@Input() isLabel: boolean = false;
|
|
28
|
+
@Input() simbol: string = '';
|
|
29
|
+
|
|
30
|
+
innerValue: any = '';
|
|
31
|
+
|
|
32
|
+
get value(): any {
|
|
33
|
+
return this.innerValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set value(val: any) {
|
|
37
|
+
if (val !== this.innerValue) {
|
|
38
|
+
this.innerValue = val;
|
|
39
|
+
this.onChange(val);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get combinedNgClass() {
|
|
44
|
+
return {
|
|
45
|
+
...(this.ngClass || {}),
|
|
46
|
+
'opacity-50': this.disabled
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ControlValueAccessor
|
|
51
|
+
onChange: any = () => { };
|
|
52
|
+
onTouched: any = () => { };
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Writes a value to the component.
|
|
56
|
+
* @param event
|
|
57
|
+
*/
|
|
58
|
+
onInput(event: Event): void {
|
|
59
|
+
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
|
60
|
+
this.value = target.value;
|
|
61
|
+
this.onChange(this.value);
|
|
62
|
+
this.onTouched();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Clears the input value and resets the component state.
|
|
69
|
+
* This method is typically used when a clear button is clicked.
|
|
70
|
+
*/
|
|
71
|
+
clearInput(): void {
|
|
72
|
+
this.value = '';
|
|
73
|
+
this.onChange('');
|
|
74
|
+
this.onTouched();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div class="relative w-full">
|
|
2
|
+
<textarea
|
|
3
|
+
[id]="id"
|
|
4
|
+
[name]="name ?? ''"
|
|
5
|
+
[placeholder]="placeholder"
|
|
6
|
+
[value]="value"
|
|
7
|
+
(input)="onInput($event)"
|
|
8
|
+
[required]="required"
|
|
9
|
+
[disabled]="disabled"
|
|
10
|
+
(blur)="onTouched()"
|
|
11
|
+
[ngClass]="combinedNgClass"
|
|
12
|
+
[class]="classes"
|
|
13
|
+
class="input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
|
|
14
|
+
></textarea>
|
|
15
|
+
|
|
16
|
+
@if (value && clearButton) {
|
|
17
|
+
<button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" (click)="clearInput()">
|
|
18
|
+
<lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
|
|
19
|
+
</button>
|
|
20
|
+
}
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { NgClass, CommonModule } from '@angular/common';
|
|
4
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
5
|
+
import { JIconsService } from 'tailjng';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'JTextareaInput',
|
|
9
|
+
imports: [FormsModule, ReactiveFormsModule, NgClass, LucideAngularModule, CommonModule],
|
|
10
|
+
templateUrl: './textarea-input.component.html',
|
|
11
|
+
styleUrl: './textarea-input.component.css'
|
|
12
|
+
})
|
|
13
|
+
export class JTextareaInputComponent {
|
|
14
|
+
|
|
15
|
+
@Input() id?: string;
|
|
16
|
+
@Input() name?: string;
|
|
17
|
+
@Input() placeholder: string = '';
|
|
18
|
+
|
|
19
|
+
@Input() disabled: boolean = false;
|
|
20
|
+
@Input() required: boolean = false;
|
|
21
|
+
@Input() classes: string = '';
|
|
22
|
+
@Input() ngClass: { [key: string]: boolean } = {};
|
|
23
|
+
@Input() clearButton: boolean = false;
|
|
24
|
+
|
|
25
|
+
innerValue: any = '';
|
|
26
|
+
|
|
27
|
+
get value(): any {
|
|
28
|
+
return this.innerValue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set value(val: any) {
|
|
32
|
+
if (val !== this.innerValue) {
|
|
33
|
+
this.innerValue = val;
|
|
34
|
+
this.onChange(val);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get combinedNgClass() {
|
|
39
|
+
return {
|
|
40
|
+
...(this.ngClass || {}),
|
|
41
|
+
'opacity-50': this.disabled
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ControlValueAccessor
|
|
46
|
+
onChange: any = () => { };
|
|
47
|
+
onTouched: any = () => { };
|
|
48
|
+
|
|
49
|
+
constructor(public readonly iconsService: JIconsService) { }
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Writes a value to the component.
|
|
53
|
+
* @param event
|
|
54
|
+
*/
|
|
55
|
+
onInput(event: Event): void {
|
|
56
|
+
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
|
57
|
+
this.value = target.value;
|
|
58
|
+
this.onChange(this.value);
|
|
59
|
+
this.onTouched();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clears the input value and resets the component state.
|
|
66
|
+
* This method is typically used when a clear button is clicked.
|
|
67
|
+
*/
|
|
68
|
+
clearInput(): void {
|
|
69
|
+
this.value = '';
|
|
70
|
+
this.onChange('');
|
|
71
|
+
this.onTouched();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NgClass } from '@angular/common';
|
|
2
2
|
import { Component, Input } from '@angular/core';
|
|
3
3
|
import { JTooltipDirective } from '../tooltip/tooltip.directive';
|
|
4
|
-
import { JIconsService } from '
|
|
4
|
+
import { JIconsService } from 'tailjng';
|
|
5
5
|
import { LucideAngularModule } from 'lucide-angular';
|
|
6
6
|
|
|
7
7
|
@Component({
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<button (click)="toggleDarkMode()"
|
|
2
|
+
class="p-2 bg-gray-300 dark:bg-gray-700 text-black dark:text-white rounded-full cursor-pointer">
|
|
3
|
+
@if (theme === 'dark') {
|
|
4
|
+
<lucide-icon [name]="iconsService.icons.moon"></lucide-icon>
|
|
5
|
+
} @else {
|
|
6
|
+
<lucide-icon [name]="iconsService.icons.sun"></lucide-icon>
|
|
7
|
+
}
|
|
8
|
+
</button>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Component, Inject, PLATFORM_ID } from '@angular/core';
|
|
2
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
3
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
4
|
+
import { JIconsService } from 'tailjng';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'JModeToggle',
|
|
8
|
+
imports: [LucideAngularModule],
|
|
9
|
+
templateUrl: './mode-toggle.component.html',
|
|
10
|
+
styleUrl: './mode-toggle.component.css'
|
|
11
|
+
})
|
|
12
|
+
export class JModeToggleComponent {
|
|
13
|
+
|
|
14
|
+
title = 'frontend';
|
|
15
|
+
theme: string = 'light';
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
@Inject(PLATFORM_ID) private readonly platformId: object,
|
|
19
|
+
public readonly iconsService: JIconsService
|
|
20
|
+
) {
|
|
21
|
+
this.loadTheme();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Set the theme for the application.
|
|
27
|
+
* @param theme - The theme to set, either 'light' or 'dark'.
|
|
28
|
+
*/
|
|
29
|
+
setTheme(theme: 'light' | 'dark') {
|
|
30
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
31
|
+
localStorage.setItem('theme', theme);
|
|
32
|
+
}
|
|
33
|
+
this.theme = theme; // Actualizar la variable theme
|
|
34
|
+
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load the theme from localStorage and apply it to the document.
|
|
41
|
+
*/
|
|
42
|
+
loadTheme() {
|
|
43
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
44
|
+
const storedTheme = localStorage.getItem('theme');
|
|
45
|
+
this.theme = storedTheme ?? 'light';
|
|
46
|
+
document.documentElement.classList.toggle('dark', this.theme === 'dark');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Toggle between light and dark mode.
|
|
54
|
+
*/
|
|
55
|
+
toggleDarkMode() {
|
|
56
|
+
const newTheme = this.theme === 'dark' ? 'light' : 'dark';
|
|
57
|
+
this.setTheme(newTheme);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div class="relative w-full flex items-center mb-2">
|
|
2
|
+
<progress
|
|
3
|
+
class="relative w-full overflow-hidden bg-slate-200 border border-border dark:border-dark-border
|
|
4
|
+
[&::-webkit-progress-bar]:bg-background
|
|
5
|
+
dark:[&::-webkit-progress-bar]:bg-dark-background
|
|
6
|
+
[&::-webkit-progress-value]:bg-primary
|
|
7
|
+
[&::-moz-progress-bar]:bg-primary
|
|
8
|
+
dark:[&::-webkit-progress-value]:bg-dark-primary
|
|
9
|
+
dark:[&::-moz-progress-bar]:bg-dark-primary
|
|
10
|
+
[&:disabled]:opacity-50 transition-all duration-300 ease-in-out"
|
|
11
|
+
[value]="value"
|
|
12
|
+
[max]="max"
|
|
13
|
+
[ngClass]="ngClasses"
|
|
14
|
+
[style.height.px]="height"
|
|
15
|
+
[style.border-radius.px]="borderRadius"
|
|
16
|
+
>
|
|
17
|
+
</progress>
|
|
18
|
+
|
|
19
|
+
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm font-bold text-gray-500 dark:text-gray-400">
|
|
20
|
+
{{ value }}{{ simbol }}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|