@tolle_/tolle-ui 0.0.19-beta → 0.0.21-beta

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.
@@ -1,7 +1,7 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import * as i0 from '@angular/core';
4
- import { Component, Input, forwardRef, Injectable, Optional, HostListener, ViewChild, ContentChildren, EventEmitter, Output, Directive, inject, PLATFORM_ID, Inject, InjectionToken, APP_INITIALIZER, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector, HostBinding } from '@angular/core';
4
+ import { Component, Input, forwardRef, ViewChild, Injectable, Optional, HostListener, ContentChildren, EventEmitter, Output, Directive, inject, PLATFORM_ID, Inject, InjectionToken, APP_INITIALIZER, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector, HostBinding } from '@angular/core';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, isPlatformBrowser, DOCUMENT, NgIf, NgTemplateOutlet } from '@angular/common';
7
7
  import { cva } from 'class-variance-authority';
@@ -18,15 +18,15 @@ function cn(...inputs) {
18
18
  return twMerge(clsx(inputs));
19
19
  }
20
20
 
21
- const buttonVariants = cva("inline-flex items-center justify-center rounded-md text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background active:scale-[0.98]", {
21
+ const buttonVariants = cva("tolle-button-base inline-flex items-center justify-center rounded-md text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background active:scale-[0.98]", {
22
22
  variants: {
23
23
  variant: {
24
24
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
25
25
  destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
26
- outline: "border border-input hover:bg-accent hover:text-accent-foreground",
26
+ outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
27
27
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
28
28
  ghost: "hover:bg-accent hover:text-accent-foreground",
29
- link: "underline-offset-4 hover:underline text-primary",
29
+ link: "text-primary underline-offset-4 hover:underline",
30
30
  },
31
31
  size: {
32
32
  default: "h-10 px-4 py-2",
@@ -38,7 +38,7 @@ const buttonVariants = cva("inline-flex items-center justify-center rounded-md t
38
38
  icon: "h-10 w-10",
39
39
  "icon-lg": "h-11 w-11",
40
40
  },
41
- busy: { true: "relative !cursor-wait !pointer-events-none" }
41
+ busy: { true: "tolle-button--busy relative !cursor-wait !pointer-events-none" }
42
42
  },
43
43
  defaultVariants: {
44
44
  variant: "default",
@@ -51,16 +51,15 @@ class ButtonComponent {
51
51
  size = 'default';
52
52
  disabled = false;
53
53
  busy = false;
54
- readonly = false;
55
54
  get computedClass() {
56
55
  return cn(buttonVariants({
57
56
  variant: this.variant,
58
57
  size: this.size,
59
58
  busy: this.busy
60
- }), 'size-' + this.size, this.class);
59
+ }), this.class);
61
60
  }
62
61
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
63
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "tolle-button", inputs: { class: "class", variant: "variant", size: "size", disabled: "disabled", busy: "busy", readonly: "readonly" }, ngImport: i0, template: `
62
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "tolle-button", inputs: { class: "class", variant: "variant", size: "size", disabled: "disabled", busy: "busy" }, host: { classAttribute: "tolle-button-wrapper inline-block align-middle" }, ngImport: i0, template: `
64
63
  <button
65
64
  [class]="computedClass"
66
65
  [disabled]="disabled || busy"
@@ -77,11 +76,15 @@ class ButtonComponent {
77
76
  <ng-content></ng-content>
78
77
  </span>
79
78
  </button>
80
- `, isInline: true, styles: [":host{display:inline-block;vertical-align:middle}:host ::ng-deep i{display:inline-flex;align-items:center;justify-content:center;line-height:1}:host-context(.size-xs) ::ng-deep i,:host-context(.size-icon-xs) ::ng-deep i,:host-context(.size-sm) ::ng-deep i,:host-context(.size-icon-sm) ::ng-deep i{font-size:1rem}:host-context(.size-default) ::ng-deep i,:host-context(.size-icon) ::ng-deep i,:host-context(.size-lg) ::ng-deep i,:host-context(.size-icon-lg) ::ng-deep i{font-size:1.2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
79
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
81
80
  }
82
81
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, decorators: [{
83
82
  type: Component,
84
- args: [{ selector: 'tolle-button', standalone: true, imports: [CommonModule], template: `
83
+ args: [{
84
+ selector: 'tolle-button',
85
+ standalone: true,
86
+ imports: [CommonModule],
87
+ template: `
85
88
  <button
86
89
  [class]="computedClass"
87
90
  [disabled]="disabled || busy"
@@ -98,7 +101,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
98
101
  <ng-content></ng-content>
99
102
  </span>
100
103
  </button>
101
- `, styles: [":host{display:inline-block;vertical-align:middle}:host ::ng-deep i{display:inline-flex;align-items:center;justify-content:center;line-height:1}:host-context(.size-xs) ::ng-deep i,:host-context(.size-icon-xs) ::ng-deep i,:host-context(.size-sm) ::ng-deep i,:host-context(.size-icon-sm) ::ng-deep i{font-size:1rem}:host-context(.size-default) ::ng-deep i,:host-context(.size-icon) ::ng-deep i,:host-context(.size-lg) ::ng-deep i,:host-context(.size-icon-lg) ::ng-deep i{font-size:1.2rem}\n"] }]
104
+ `,
105
+ host: {
106
+ 'class': 'tolle-button-wrapper inline-block align-middle'
107
+ }
108
+ }]
102
109
  }], propDecorators: { class: [{
103
110
  type: Input
104
111
  }], variant: [{
@@ -109,12 +116,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
109
116
  type: Input
110
117
  }], busy: [{
111
118
  type: Input
112
- }], readonly: [{
113
- type: Input
114
119
  }] } });
115
120
 
116
121
  class InputComponent {
117
122
  cdr;
123
+ inputElement;
118
124
  id = `input-${Math.random().toString(36).substr(2, 9)}`;
119
125
  label = '';
120
126
  hint = '';
@@ -128,12 +134,20 @@ class InputComponent {
128
134
  disabled = false;
129
135
  readonly = false;
130
136
  error = false;
137
+ // Focus behavior
138
+ hideHintOnFocus = true;
131
139
  value = '';
132
140
  onChange = () => { };
133
141
  onTouched = () => { };
142
+ isFocused = false;
134
143
  constructor(cdr) {
135
144
  this.cdr = cdr;
136
145
  }
146
+ ngAfterViewInit() {
147
+ if (this.inputElement?.nativeElement.hasAttribute('autofocus')) {
148
+ setTimeout(() => this.inputElement.nativeElement.focus());
149
+ }
150
+ }
137
151
  writeValue(value) {
138
152
  this.value = value;
139
153
  this.cdr.markForCheck();
@@ -151,66 +165,132 @@ class InputComponent {
151
165
  this.value = val;
152
166
  this.onChange(val);
153
167
  }
168
+ onFocus() {
169
+ this.isFocused = true;
170
+ }
171
+ onBlur() {
172
+ this.isFocused = false;
173
+ this.onTouched();
174
+ }
175
+ focusInput() {
176
+ if (!this.disabled && this.inputElement) {
177
+ this.inputElement.nativeElement.focus();
178
+ }
179
+ }
154
180
  cn = cn;
181
+ get computedLabelClass() {
182
+ return cn("text-sm font-medium text-foreground leading-none transition-opacity duration-200", this.disabled && "opacity-50");
183
+ }
155
184
  get computedContainerClass() {
156
- return cn("group relative flex items-center w-full rounded-md border transition-all shadow-sm", "bg-background ring-offset-background",
185
+ return cn(
186
+ // Base styles
187
+ "group relative flex items-center w-full rounded-md border transition-all duration-200", "bg-background",
188
+ // Border and shadow
189
+ "border-input shadow-sm",
157
190
  // Sizing
158
- this.size === 'xs' && "h-8 px-2 gap-1.5", this.size === 'sm' && "h-9 px-3 gap-2", this.size === 'default' && "h-10 px-3 gap-2", this.size === 'lg' && "h-11 px-4 gap-3",
159
- // Interaction States (Focus ring disabled for Readonly/Disabled)
160
- !(this.readonly || this.disabled) && "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1",
161
- // Colors & Borders
162
- this.error ? "border-destructive focus-within:ring-destructive" : "border-input",
163
- // Disabled vs Readonly styling
164
- this.disabled && "cursor-not-allowed opacity-50", this.readonly && "cursor-default border-dashed focus-within:ring-0", this.containerClass);
191
+ this.size === 'xs' && "h-8 px-2 gap-1.5 text-xs", this.size === 'sm' && "h-9 px-3 gap-2 text-sm", this.size === 'default' && "h-10 px-3 gap-2 text-sm", this.size === 'lg' && "h-11 px-4 gap-3 text-base",
192
+ // Focus state - SIMPLE AND ELEGANT LIKE ZARDUI
193
+ // The magic happens in CSS: border darkens automatically on focus
194
+ !(this.readonly || this.disabled) && [
195
+ "focus-within:border-primary/80",
196
+ "focus-within:ring-4",
197
+ "focus-within:ring-ring/30",
198
+ "focus-within:ring-offset-0",
199
+ "focus-within:shadow-none",
200
+ ],
201
+ // Error state
202
+ this.error && [
203
+ "border-destructive",
204
+ !(this.readonly || this.disabled) && [
205
+ "focus-within:border-destructive/80",
206
+ "focus-within:ring-destructive/30"
207
+ ]
208
+ ],
209
+ // Disabled state
210
+ this.disabled && [
211
+ "cursor-not-allowed opacity-50",
212
+ "border-opacity-50"
213
+ ],
214
+ // Readonly state
215
+ this.readonly && [
216
+ "cursor-default",
217
+ "border-dashed",
218
+ !this.disabled && "focus-within:ring-0 focus-within:border-opacity-100"
219
+ ], this.containerClass);
165
220
  }
166
221
  get computedInputClass() {
167
- return cn("flex-1 bg-transparent border-none p-0 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-0", this.size === 'xs' && "text-xs", this.size === 'lg' && "text-base", this.disabled && "cursor-not-allowed", this.readonly && "cursor-default", this.class);
222
+ return cn(
223
+ // Base styles
224
+ "flex-1 bg-transparent border-none p-0", "placeholder:text-muted-foreground",
225
+ // Remove all default focus styles
226
+ "focus:outline-none focus:ring-0 focus:shadow-none",
227
+ // Text sizing
228
+ this.size === 'xs' && "text-xs", this.size === 'sm' && "text-sm", this.size === 'default' && "text-sm", this.size === 'lg' && "text-base",
229
+ // Cursor states
230
+ this.disabled && "cursor-not-allowed", this.readonly && "cursor-default",
231
+ // Text color
232
+ "text-foreground",
233
+ // Selection color
234
+ "selection:bg-primary/20 selection:text-foreground", this.class);
168
235
  }
169
236
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: InputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
170
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: InputComponent, isStandalone: true, selector: "tolle-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", type: "type", placeholder: "placeholder", size: "size", containerClass: "containerClass", class: "class", disabled: "disabled", readonly: "readonly", error: "error" }, providers: [
237
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: InputComponent, isStandalone: true, selector: "tolle-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", type: "type", placeholder: "placeholder", size: "size", containerClass: "containerClass", class: "class", disabled: "disabled", readonly: "readonly", error: "error", hideHintOnFocus: "hideHintOnFocus" }, providers: [
171
238
  {
172
239
  provide: NG_VALUE_ACCESSOR,
173
240
  useExisting: forwardRef(() => InputComponent),
174
241
  multi: true
175
242
  }
176
- ], ngImport: i0, template: `
243
+ ], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["inputElement"], descendants: true }], ngImport: i0, template: `
177
244
  <div class="flex flex-col gap-1.5 w-full">
178
245
  <label
179
246
  *ngIf="label"
180
247
  [for]="id"
181
- [class.opacity-50]="disabled"
182
- class="text-sm font-medium text-foreground leading-none transition-opacity"
183
- >
248
+ [class]="computedLabelClass">
184
249
  {{ label }}
185
250
  </label>
186
251
 
187
- <div [class]="computedContainerClass">
188
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
252
+ <div
253
+ [class]="computedContainerClass"
254
+ (click)="focusInput()"
255
+ >
256
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
189
257
  <ng-content select="[prefix]"></ng-content>
190
258
  </div>
191
259
 
192
260
  <input
261
+ #inputElement
193
262
  [id]="id"
194
263
  [type]="type"
195
264
  [placeholder]="placeholder"
196
265
  [disabled]="disabled"
197
266
  [readOnly]="readonly"
198
267
  [(ngModel)]="value"
199
- (blur)="onTouched()"
268
+ (blur)="onBlur()"
269
+ (focus)="onFocus()"
200
270
  (input)="onInputChange($event)"
201
271
  [class]="computedInputClass"
272
+ [attr.aria-invalid]="error"
273
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
202
274
  />
203
275
 
204
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
276
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
205
277
  <ng-content select="[suffix]"></ng-content>
206
278
  </div>
207
279
  </div>
208
280
 
209
281
  <ng-container *ngIf="!disabled">
210
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
282
+ <p
283
+ *ngIf="hint && !error"
284
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
285
+ [class.opacity-0]="isFocused && hideHintOnFocus"
286
+ >
211
287
  {{ hint }}
212
288
  </p>
213
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
289
+ <p
290
+ *ngIf="error && errorMessage"
291
+ [id]="id + '-error'"
292
+ class="text-xs text-destructive px-1"
293
+ >
214
294
  {{ errorMessage }}
215
295
  </p>
216
296
  </ng-container>
@@ -235,46 +315,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
235
315
  <label
236
316
  *ngIf="label"
237
317
  [for]="id"
238
- [class.opacity-50]="disabled"
239
- class="text-sm font-medium text-foreground leading-none transition-opacity"
240
- >
318
+ [class]="computedLabelClass">
241
319
  {{ label }}
242
320
  </label>
243
321
 
244
- <div [class]="computedContainerClass">
245
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
322
+ <div
323
+ [class]="computedContainerClass"
324
+ (click)="focusInput()"
325
+ >
326
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
246
327
  <ng-content select="[prefix]"></ng-content>
247
328
  </div>
248
329
 
249
330
  <input
331
+ #inputElement
250
332
  [id]="id"
251
333
  [type]="type"
252
334
  [placeholder]="placeholder"
253
335
  [disabled]="disabled"
254
336
  [readOnly]="readonly"
255
337
  [(ngModel)]="value"
256
- (blur)="onTouched()"
338
+ (blur)="onBlur()"
339
+ (focus)="onFocus()"
257
340
  (input)="onInputChange($event)"
258
341
  [class]="computedInputClass"
342
+ [attr.aria-invalid]="error"
343
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
259
344
  />
260
345
 
261
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
346
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
262
347
  <ng-content select="[suffix]"></ng-content>
263
348
  </div>
264
349
  </div>
265
350
 
266
351
  <ng-container *ngIf="!disabled">
267
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
352
+ <p
353
+ *ngIf="hint && !error"
354
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
355
+ [class.opacity-0]="isFocused && hideHintOnFocus"
356
+ >
268
357
  {{ hint }}
269
358
  </p>
270
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
359
+ <p
360
+ *ngIf="error && errorMessage"
361
+ [id]="id + '-error'"
362
+ class="text-xs text-destructive px-1"
363
+ >
271
364
  {{ errorMessage }}
272
365
  </p>
273
366
  </ng-container>
274
367
  </div>
275
368
  `,
276
369
  }]
277
- }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { id: [{
370
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { inputElement: [{
371
+ type: ViewChild,
372
+ args: ['inputElement']
373
+ }], id: [{
278
374
  type: Input
279
375
  }], label: [{
280
376
  type: Input
@@ -298,6 +394,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
298
394
  type: Input
299
395
  }], error: [{
300
396
  type: Input
397
+ }], hideHintOnFocus: [{
398
+ type: Input
301
399
  }] } });
302
400
 
303
401
  class CardComponent {
@@ -305,7 +403,7 @@ class CardComponent {
305
403
  cn = cn;
306
404
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
307
405
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardComponent, isStandalone: true, selector: "tolle-card", inputs: { class: "class" }, ngImport: i0, template: `
308
- <div [class]="cn('rounded-xl border border-border bg-card text-card-foreground shadow', class)">
406
+ <div [class]="cn('rounded-md border border-border bg-card text-card-foreground shadow', class)">
309
407
  <ng-content></ng-content>
310
408
  </div>
311
409
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
@@ -317,7 +415,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
317
415
  standalone: true,
318
416
  imports: [CommonModule],
319
417
  template: `
320
- <div [class]="cn('rounded-xl border border-border bg-card text-card-foreground shadow', class)">
418
+ <div [class]="cn('rounded-md border border-border bg-card text-card-foreground shadow', class)">
321
419
  <ng-content></ng-content>
322
420
  </div>
323
421
  `,
@@ -496,6 +594,7 @@ class SelectComponent {
496
594
  readonly = false;
497
595
  trigger;
498
596
  popover;
597
+ container;
499
598
  items;
500
599
  sub = new Subscription();
501
600
  searchQuery = '';
@@ -519,15 +618,38 @@ class SelectComponent {
519
618
  this.close();
520
619
  }));
521
620
  }
522
- // UPDATED: Centralized sizing logic for the trigger
621
+ // SIMPLIFIED: Zardui-inspired trigger styling
523
622
  get computedTriggerClass() {
524
- return cn('flex w-full items-center justify-between rounded-md border transition-all shadow-sm', 'bg-background ring-offset-background placeholder:text-muted-foreground',
623
+ return cn(
624
+ // Base styles
625
+ 'flex w-full items-center justify-between rounded-md border transition-all duration-200', 'bg-background text-foreground',
626
+ // Border and shadow
627
+ 'border-input shadow-sm',
525
628
  // Sizing
526
629
  this.size === 'xs' && 'h-8 px-2 text-xs', this.size === 'sm' && 'h-9 px-3 text-sm', this.size === 'default' && 'h-10 px-3 text-sm', this.size === 'lg' && 'h-11 px-4 text-base',
527
- // Interaction States (Focus ring disabled for Readonly/Disabled)
528
- !(this.readonly || this.disabled) && 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',
529
- // Colors & Borders (Matching your Hex-based theme logic)
530
- this.disabled && 'cursor-not-allowed opacity-50 bg-muted/30 border-input', this.readonly && 'cursor-default bg-muted/10 border-dashed border-input focus:ring-0', !this.disabled && !this.readonly && 'border-input hover:border-accent cursor-pointer', this.class);
630
+ // Focus state - SIMPLE LIKE ZARDUI
631
+ !(this.readonly || this.disabled) && [
632
+ 'focus:outline-none',
633
+ 'focus:ring-4',
634
+ 'focus:ring-ring/30',
635
+ 'focus:ring-offset-0',
636
+ 'focus:shadow-none',
637
+ // Border darkens on focus automatically
638
+ 'focus:border-primary/80'
639
+ ],
640
+ // Hover state
641
+ !(this.readonly || this.disabled) && 'hover:border-accent',
642
+ // Disabled state
643
+ this.disabled && [
644
+ 'cursor-not-allowed opacity-50',
645
+ 'border-opacity-50'
646
+ ],
647
+ // Readonly state
648
+ this.readonly && [
649
+ 'cursor-default',
650
+ 'border-dashed',
651
+ !this.disabled && 'focus:ring-0 focus:border-opacity-100'
652
+ ], this.class);
531
653
  }
532
654
  // UPDATED: Dynamic icon sizing relative to the trigger size
533
655
  get iconClass() {
@@ -553,6 +675,8 @@ class SelectComponent {
553
675
  }
554
676
  open() {
555
677
  this.isOpen = true;
678
+ // Trigger focus on the button for focus styling
679
+ this.trigger.nativeElement.focus();
556
680
  // Tick to ensure DOM is rendered before positioning
557
681
  setTimeout(() => this.updatePosition());
558
682
  }
@@ -621,7 +745,7 @@ class SelectComponent {
621
745
  useExisting: forwardRef(() => SelectComponent),
622
746
  multi: true
623
747
  }
624
- ], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
748
+ ], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }, { propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
625
749
  <div [class]="cn('w-full', 'size-' + size)" #container>
626
750
  <button
627
751
  type="button"
@@ -661,7 +785,7 @@ class SelectComponent {
661
785
  </div>
662
786
  </div>
663
787
  </div>
664
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
788
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
665
789
  }
666
790
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectComponent, decorators: [{
667
791
  type: Component,
@@ -737,6 +861,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
737
861
  }], popover: [{
738
862
  type: ViewChild,
739
863
  args: ['popover']
864
+ }], container: [{
865
+ type: ViewChild,
866
+ args: ['container']
740
867
  }], items: [{
741
868
  type: ContentChildren,
742
869
  args: [SelectItemComponent, { descendants: true }]
@@ -929,7 +1056,7 @@ class BadgeComponent {
929
1056
  get computedClass() {
930
1057
  return cn(
931
1058
  // Base styles - Pills are always rounded-full
932
- 'inline-flex items-center justify-center rounded-full border px-2 py-0.5 font-medium transition-colors gap-1',
1059
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 font-medium transition-colors gap-1',
933
1060
  // Variants (Google Dark Mode theme)
934
1061
  this.variant === 'default' && 'border-transparent bg-primary text-primary-foreground', this.variant === 'secondary' && 'border-transparent bg-secondary text-secondary-foreground', this.variant === 'outline' && 'text-foreground border-border bg-transparent', this.variant === 'destructive' && 'border-transparent bg-destructive text-destructive-foreground',
935
1062
  // Sizing
@@ -947,7 +1074,7 @@ class BadgeComponent {
947
1074
  <button
948
1075
  *ngIf="removable"
949
1076
  (click)="onRemove.emit($event)"
950
- class="ml-1 -mr-1 rounded-full p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
1077
+ class="ml-1 -mr-1 rounded-md p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
951
1078
  >
952
1079
  <i class="ri-close-line text-[1.1em]"></i>
953
1080
  </button>
@@ -971,7 +1098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
971
1098
  <button
972
1099
  *ngIf="removable"
973
1100
  (click)="onRemove.emit($event)"
974
- class="ml-1 -mr-1 rounded-full p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
1101
+ class="ml-1 -mr-1 rounded-md p-0.5 hover:bg-foreground/20 transition-colors outline-none focus:ring-1 focus:ring-ring"
975
1102
  >
976
1103
  <i class="ri-close-line text-[1.1em]"></i>
977
1104
  </button>
@@ -1363,96 +1490,205 @@ class ThemeService {
1363
1490
  else if (this.config?.primaryColor) {
1364
1491
  this.setPrimaryColor(this.config.primaryColor, false);
1365
1492
  }
1493
+ // Set radius from config
1366
1494
  if (this.config?.radius) {
1367
- this.renderer.setStyle(this.document.documentElement, '--radius', this.config.radius);
1368
- }
1369
- // 2. Apply Brand Config - This will generate full palette
1370
- if (this.config) {
1371
- this.applyBrandConfig(this.config);
1495
+ this.setRadius(this.config.radius, false);
1372
1496
  }
1373
1497
  }
1374
1498
  /**
1375
- * Applies the brand identity variables with full shade palette
1499
+ * Sets the border radius for all components
1376
1500
  */
1377
- applyBrandConfig(config) {
1501
+ setRadius(radius, persist = true) {
1378
1502
  if (!isPlatformBrowser(this.platformId))
1379
1503
  return;
1380
1504
  const root = this.document.documentElement;
1381
- // Set primary color if provided
1382
- if (config.primaryColor) {
1383
- this.renderer.setStyle(root, '--primary', config.primaryColor);
1384
- this.generatePrimaryShades(config.primaryColor);
1505
+ // Set the CSS variable
1506
+ this.renderer.setStyle(root, '--radius', radius);
1507
+ // Also update the dynamic styles to include radius calculations
1508
+ this.updateRadiusInDynamicStyles(radius);
1509
+ // Persist if needed
1510
+ if (persist) {
1511
+ localStorage.setItem('tolle-radius', radius);
1385
1512
  }
1386
- // Set border radius if provided
1387
- if (config.radius) {
1388
- this.renderer.setStyle(root, '--radius', config.radius);
1513
+ }
1514
+ /**
1515
+ * Updates the radius calculations in dynamic styles
1516
+ */
1517
+ updateRadiusInDynamicStyles(radius) {
1518
+ const existingStyle = this.document.getElementById(this.styleId);
1519
+ if (existingStyle) {
1520
+ let css = existingStyle.textContent || '';
1521
+ // Update or add radius calculations
1522
+ if (css.includes('--radius:')) {
1523
+ // Replace existing radius declarations
1524
+ css = css.replace(/--radius:[^;]+;/g, `--radius: ${radius};`);
1525
+ }
1526
+ else {
1527
+ // Add radius to the beginning of :root
1528
+ css = css.replace(/:root\s*{/, `:root {\n --radius: ${radius};`);
1529
+ }
1530
+ // Update the calculated radius values in the CSS
1531
+ const radiusCalcRegex = /calc\(var\(--radius[^)]+\)/g;
1532
+ css = css.replace(radiusCalcRegex, (match) => {
1533
+ if (match.includes('- 2px')) {
1534
+ return `calc(${radius} - 2px)`;
1535
+ }
1536
+ else if (match.includes('- 4px')) {
1537
+ return `calc(${radius} - 4px)`;
1538
+ }
1539
+ return match;
1540
+ });
1541
+ existingStyle.textContent = css;
1389
1542
  }
1390
1543
  }
1391
1544
  /**
1392
1545
  * Generates full primary color palette (50-900) based on base color
1393
- * Uses color-mix() for consistency with your existing approach
1394
1546
  */
1395
1547
  generatePrimaryShades(baseColor) {
1548
+ // Convert hex to RGB
1549
+ const rgb = this.hexToRgb(baseColor);
1550
+ const rgbString = rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '37 99 235';
1551
+ // Create lighter ring colors in RGB
1552
+ const ringLight = this.lightenColor(baseColor, 40);
1553
+ const ringLightRgb = this.hexToRgb(ringLight);
1554
+ const ringLightRgbString = ringLightRgb ? `${ringLightRgb.r} ${ringLightRgb.g} ${ringLightRgb.b}` : '96 165 250';
1555
+ const ringDark = this.lightenColor(baseColor, 20);
1556
+ const ringDarkRgb = this.hexToRgb(ringDark);
1557
+ const ringDarkRgbString = ringDarkRgb ? `${ringDarkRgb.r} ${ringDarkRgb.g} ${ringDarkRgb.b}` : '147 197 253';
1558
+ // Get current radius or use default
1559
+ const root = this.document.documentElement;
1560
+ const currentRadius = getComputedStyle(root).getPropertyValue('--radius').trim() || '0.5rem';
1396
1561
  const css = `
1562
+ /* Override primary colors - this needs to come AFTER your main CSS */
1397
1563
  :root {
1398
- /* Primary Shades */
1399
- --primary-50: color-mix(in srgb, ${baseColor}, white 90%);
1400
- --primary-100: color-mix(in srgb, ${baseColor}, white 80%);
1401
- --primary-200: color-mix(in srgb, ${baseColor}, white 60%);
1402
- --primary-300: color-mix(in srgb, ${baseColor}, white 40%);
1403
- --primary-400: color-mix(in srgb, ${baseColor}, white 20%);
1404
- --primary-500: ${baseColor};
1405
- --primary-600: color-mix(in srgb, ${baseColor}, black 20%);
1406
- --primary-700: color-mix(in srgb, ${baseColor}, black 40%);
1407
- --primary-800: color-mix(in srgb, ${baseColor}, black 60%);
1408
- --primary-900: color-mix(in srgb, ${baseColor}, black 80%);
1409
-
1410
- /* Your existing derived colors - updated to use new shades */
1411
- --primary-foreground: white;
1412
- --secondary: color-mix(in srgb, var(--primary-200), transparent 40%);
1413
- --secondary-foreground: var(--primary-900);
1414
- --muted: color-mix(in srgb, var(--primary-50), #f3f4f6 50%);
1415
- --muted-foreground: color-mix(in srgb, var(--primary-400), #4b5563 60%);
1416
- --accent: color-mix(in srgb, var(--primary-100), transparent 70%);
1417
- --accent-foreground: var(--primary-700);
1418
- --ring: color-mix(in srgb, var(--primary-300), transparent 50%);
1564
+ /* Primary in RGB format for Tailwind opacity support */
1565
+ --primary: ${rgbString};
1566
+ --primary-foreground: ${this.getContrastColorRgb(baseColor)};
1567
+
1568
+ /* Radius */
1569
+ --radius: ${currentRadius};
1570
+
1571
+ /* Primary shades for light mode */
1572
+ --primary-50: ${this.hexToRgbString(this.lightenColor(baseColor, 90))};
1573
+ --primary-100: ${this.hexToRgbString(this.lightenColor(baseColor, 80))};
1574
+ --primary-200: ${this.hexToRgbString(this.lightenColor(baseColor, 60))};
1575
+ --primary-300: ${this.hexToRgbString(this.lightenColor(baseColor, 40))};
1576
+ --primary-400: ${this.hexToRgbString(this.lightenColor(baseColor, 20))};
1577
+ --primary-500: ${rgbString};
1578
+ --primary-600: ${this.hexToRgbString(this.darkenColor(baseColor, 20))};
1579
+ --primary-700: ${this.hexToRgbString(this.darkenColor(baseColor, 40))};
1580
+ --primary-800: ${this.hexToRgbString(this.darkenColor(baseColor, 60))};
1581
+ --primary-900: ${this.hexToRgbString(this.darkenColor(baseColor, 80))};
1582
+
1583
+ /* Update ring color to be lighter */
1584
+ --ring: ${ringLightRgbString};
1419
1585
  }
1420
1586
 
1421
1587
  .dark {
1422
- /* Dark mode variants */
1423
- --primary-50: color-mix(in srgb, ${baseColor}, black 85%);
1424
- --primary-100: color-mix(in srgb, ${baseColor}, black 75%);
1425
- --primary-200: color-mix(in srgb, ${baseColor}, black 65%);
1426
- --primary-300: color-mix(in srgb, ${baseColor}, black 55%);
1427
- --primary-400: color-mix(in srgb, ${baseColor}, black 45%);
1428
- --primary-500: ${baseColor};
1429
- --primary-600: color-mix(in srgb, ${baseColor}, white 20%);
1430
- --primary-700: color-mix(in srgb, ${baseColor}, white 35%);
1431
- --primary-800: color-mix(in srgb, ${baseColor}, white 50%);
1432
- --primary-900: color-mix(in srgb, ${baseColor}, white 65%);
1433
-
1434
- /* Dark mode derived colors */
1435
- --primary-foreground: color-mix(in srgb, ${baseColor}, white 90%);
1436
- --secondary: color-mix(in srgb, var(--primary-900), transparent 70%);
1437
- --secondary-foreground: var(--primary-100);
1438
- --muted: color-mix(in srgb, var(--primary-950), #1f2937 50%);
1439
- --muted-foreground: color-mix(in srgb, var(--primary-300), #9ca3af 40%);
1440
- --accent: color-mix(in srgb, var(--primary-800), transparent 80%);
1441
- --accent-foreground: var(--primary-200);
1442
- --ring: color-mix(in srgb, var(--primary-600), transparent 60%);
1588
+ /* For dark mode, we keep the primary color but adjust shades */
1589
+ --primary: ${rgbString};
1590
+ --primary-foreground: ${this.getContrastColorRgb(baseColor)};
1591
+
1592
+ /* Dark mode shades */
1593
+ --primary-50: ${this.hexToRgbString(this.darkenColor(baseColor, 85))};
1594
+ --primary-100: ${this.hexToRgbString(this.darkenColor(baseColor, 75))};
1595
+ --primary-200: ${this.hexToRgbString(this.darkenColor(baseColor, 65))};
1596
+ --primary-300: ${this.hexToRgbString(this.darkenColor(baseColor, 55))};
1597
+ --primary-400: ${this.hexToRgbString(this.darkenColor(baseColor, 45))};
1598
+ --primary-500: ${rgbString};
1599
+ --primary-600: ${this.hexToRgbString(this.lightenColor(baseColor, 20))};
1600
+ --primary-700: ${this.hexToRgbString(this.lightenColor(baseColor, 35))};
1601
+ --primary-800: ${this.hexToRgbString(this.lightenColor(baseColor, 50))};
1602
+ --primary-900: ${this.hexToRgbString(this.lightenColor(baseColor, 65))};
1603
+
1604
+ /* Update ring color for dark mode - lighter variant */
1605
+ --ring: ${ringDarkRgbString};
1443
1606
  }
1444
1607
  `;
1445
1608
  this.injectDynamicStyles(css);
1446
1609
  }
1610
+ /**
1611
+ * Convert hex color to RGB object
1612
+ */
1613
+ hexToRgb(hex) {
1614
+ // Remove # if present
1615
+ const cleanedHex = hex.replace('#', '');
1616
+ let r = 0, g = 0, b = 0;
1617
+ if (cleanedHex.length === 3) {
1618
+ r = parseInt(cleanedHex[0] + cleanedHex[0], 16);
1619
+ g = parseInt(cleanedHex[1] + cleanedHex[1], 16);
1620
+ b = parseInt(cleanedHex[2] + cleanedHex[2], 16);
1621
+ return { r, g, b };
1622
+ }
1623
+ if (cleanedHex.length === 6) {
1624
+ r = parseInt(cleanedHex.substring(0, 2), 16);
1625
+ g = parseInt(cleanedHex.substring(2, 4), 16);
1626
+ b = parseInt(cleanedHex.substring(4, 6), 16);
1627
+ return { r, g, b };
1628
+ }
1629
+ return null;
1630
+ }
1631
+ /**
1632
+ * Convert hex to RGB string format for CSS (space-separated)
1633
+ */
1634
+ hexToRgbString(hexColor) {
1635
+ const rgb = this.hexToRgb(hexColor);
1636
+ return rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '37 99 235';
1637
+ }
1638
+ /**
1639
+ * Get contrast color in RGB format
1640
+ */
1641
+ getContrastColorRgb(hexColor) {
1642
+ const contrast = this.getContrastColor(hexColor);
1643
+ const rgb = this.hexToRgb(contrast);
1644
+ return rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '255 255 255';
1645
+ }
1646
+ /**
1647
+ * Lighten a hex color by a percentage
1648
+ */
1649
+ lightenColor(color, percent) {
1650
+ const rgb = this.hexToRgb(color);
1651
+ if (!rgb)
1652
+ return color;
1653
+ // Lighten by percentage
1654
+ const r = Math.min(255, Math.floor(rgb.r + (255 - rgb.r) * (percent / 100)));
1655
+ const g = Math.min(255, Math.floor(rgb.g + (255 - rgb.g) * (percent / 100)));
1656
+ const b = Math.min(255, Math.floor(rgb.b + (255 - rgb.b) * (percent / 100)));
1657
+ // Convert back to hex
1658
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
1659
+ }
1660
+ /**
1661
+ * Darken a hex color by a percentage
1662
+ */
1663
+ darkenColor(color, percent) {
1664
+ const rgb = this.hexToRgb(color);
1665
+ if (!rgb)
1666
+ return color;
1667
+ const factor = 1 - (percent / 100);
1668
+ const r = Math.max(0, Math.floor(rgb.r * factor));
1669
+ const g = Math.max(0, Math.floor(rgb.g * factor));
1670
+ const b = Math.max(0, Math.floor(rgb.b * factor));
1671
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
1672
+ }
1673
+ /**
1674
+ * Calculate contrast color (black or white) based on background color
1675
+ */
1676
+ getContrastColor(hexColor) {
1677
+ const rgb = this.hexToRgb(hexColor);
1678
+ if (!rgb)
1679
+ return '#ffffff';
1680
+ // Calculate relative luminance
1681
+ const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
1682
+ // Return black for light colors, white for dark colors
1683
+ return luminance > 0.5 ? '#000000' : '#ffffff';
1684
+ }
1447
1685
  injectDynamicStyles(css) {
1448
1686
  if (!isPlatformBrowser(this.platformId))
1449
1687
  return;
1450
- // Remove existing dynamic styles
1451
1688
  const existingStyle = this.document.getElementById(this.styleId);
1452
1689
  if (existingStyle) {
1453
1690
  existingStyle.remove();
1454
1691
  }
1455
- // Create and inject new styles
1456
1692
  const styleElement = this.document.createElement('style');
1457
1693
  styleElement.id = this.styleId;
1458
1694
  styleElement.textContent = css;
@@ -1475,10 +1711,12 @@ class ThemeService {
1475
1711
  setPrimaryColor(color, persist = true) {
1476
1712
  if (!isPlatformBrowser(this.platformId))
1477
1713
  return;
1478
- // Update CSS variables + palette
1479
- this.renderer.setStyle(this.document.documentElement, '--primary', color);
1480
1714
  this.generatePrimaryShades(color);
1481
- // Persist user preference
1715
+ // Also set inline for immediate update
1716
+ const rgb = this.hexToRgb(color);
1717
+ if (rgb) {
1718
+ this.renderer.setStyle(this.document.documentElement, '--primary', `${rgb.r} ${rgb.g} ${rgb.b}`);
1719
+ }
1482
1720
  if (persist) {
1483
1721
  localStorage.setItem('tolle-primary-color', color);
1484
1722
  }
@@ -1487,8 +1725,44 @@ class ThemeService {
1487
1725
  return this.isDarkSubject.value ? 'dark' : 'light';
1488
1726
  }
1489
1727
  get primaryColor() {
1728
+ if (!isPlatformBrowser(this.platformId))
1729
+ return null;
1730
+ const root = this.document.documentElement;
1731
+ const cssValue = getComputedStyle(root).getPropertyValue('--primary').trim();
1732
+ if (cssValue && cssValue !== '') {
1733
+ // Convert RGB string back to hex for external use
1734
+ const rgbParts = cssValue.split(' ').map(Number);
1735
+ if (rgbParts.length === 3) {
1736
+ return `#${rgbParts[0].toString(16).padStart(2, '0')}${rgbParts[1].toString(16).padStart(2, '0')}${rgbParts[2].toString(16).padStart(2, '0')}`;
1737
+ }
1738
+ }
1490
1739
  return localStorage.getItem('tolle-primary-color');
1491
1740
  }
1741
+ get radius() {
1742
+ if (!isPlatformBrowser(this.platformId))
1743
+ return null;
1744
+ const root = this.document.documentElement;
1745
+ const cssValue = getComputedStyle(root).getPropertyValue('--radius').trim();
1746
+ if (cssValue && cssValue !== '') {
1747
+ return cssValue;
1748
+ }
1749
+ return localStorage.getItem('tolle-radius') || '0.5rem';
1750
+ }
1751
+ /**
1752
+ * Applies the brand identity variables with full shade palette
1753
+ */
1754
+ applyBrandConfig(config) {
1755
+ if (!isPlatformBrowser(this.platformId))
1756
+ return;
1757
+ // Set primary color if provided
1758
+ if (config.primaryColor) {
1759
+ this.setPrimaryColor(config.primaryColor, false);
1760
+ }
1761
+ // Set border radius if provided
1762
+ if (config.radius) {
1763
+ this.setRadius(config.radius, false);
1764
+ }
1765
+ }
1492
1766
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }, { token: TOLLE_CONFIG, optional: true }, { token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable });
1493
1767
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, providedIn: 'root' });
1494
1768
  }
@@ -1670,7 +1944,7 @@ class MultiSelectComponent {
1670
1944
  </div>
1671
1945
  </div>
1672
1946
  </div>
1673
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BadgeComponent, selector: "tolle-badge", inputs: ["variant", "size", "removable", "class"], outputs: ["onRemove"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
1947
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BadgeComponent, selector: "tolle-badge", inputs: ["variant", "size", "removable", "class"], outputs: ["onRemove"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
1674
1948
  }
1675
1949
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiSelectComponent, decorators: [{
1676
1950
  type: Component,
@@ -2067,10 +2341,12 @@ class MaskedInputComponent {
2067
2341
  error = false;
2068
2342
  size = 'default';
2069
2343
  returnRaw = false;
2344
+ hideHintOnFocus = true;
2070
2345
  inputEl;
2071
2346
  hasPrefix = false;
2072
2347
  hasSuffix = false;
2073
2348
  displayValue = '';
2349
+ isFocused = false;
2074
2350
  tokens = {
2075
2351
  '0': /\d/, '9': /\d/, 'a': /[a-z]/i, 'A': /[a-z]/i, '*': /[a-z0-9]/i
2076
2352
  };
@@ -2089,19 +2365,71 @@ class MaskedInputComponent {
2089
2365
  this.cdr.detectChanges();
2090
2366
  }
2091
2367
  }
2368
+ get computedLabelClass() {
2369
+ return cn("text-sm font-medium text-foreground leading-none transition-opacity duration-200", this.disabled && "opacity-50");
2370
+ }
2092
2371
  get computedContainerClass() {
2093
- return cn("group relative flex items-center w-full rounded-md border transition-all shadow-sm", "bg-background ring-offset-background",
2372
+ return cn(
2373
+ // Base styles
2374
+ "group relative flex items-center w-full rounded-md border transition-all duration-200", "bg-background",
2375
+ // Border and shadow
2376
+ "border-input shadow-sm",
2094
2377
  // Sizing
2095
- this.size === 'xs' && "h-8 px-2 gap-1.5", this.size === 'sm' && "h-9 px-3 gap-2", this.size === 'default' && "h-10 px-3 gap-2", this.size === 'lg' && "h-11 px-4 gap-3",
2096
- // Interaction States (Inherited from Input focus style)
2097
- !(this.readonly || this.disabled) && "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1",
2098
- // Colors & Borders
2099
- this.error ? "border-destructive focus-within:ring-destructive" : "border-input",
2100
- // Disabled vs Readonly styling
2101
- this.disabled && "cursor-not-allowed opacity-50", this.readonly && "cursor-default border-dashed focus-within:ring-0", this.containerClass);
2378
+ this.size === 'xs' && "h-8 px-2 gap-1.5 text-xs", this.size === 'sm' && "h-9 px-3 gap-2 text-sm", this.size === 'default' && "h-10 px-3 gap-2 text-sm", this.size === 'lg' && "h-11 px-4 gap-3 text-base",
2379
+ // Focus state - SIMPLE LIKE ZARDUI
2380
+ !(this.readonly || this.disabled) && [
2381
+ "focus-within:border-primary/80",
2382
+ "focus-within:ring-4",
2383
+ "focus-within:ring-ring/30",
2384
+ "focus-within:ring-offset-0",
2385
+ "focus-within:shadow-none",
2386
+ ],
2387
+ // Error state
2388
+ this.error && [
2389
+ "border-destructive",
2390
+ !(this.readonly || this.disabled) && [
2391
+ "focus-within:border-destructive/80",
2392
+ "focus-within:ring-destructive/30"
2393
+ ]
2394
+ ],
2395
+ // Disabled state
2396
+ this.disabled && [
2397
+ "cursor-not-allowed opacity-50",
2398
+ "border-opacity-50"
2399
+ ],
2400
+ // Readonly state
2401
+ this.readonly && [
2402
+ "cursor-default",
2403
+ "border-dashed",
2404
+ !this.disabled && "focus-within:ring-0 focus-within:border-opacity-100"
2405
+ ], this.containerClass);
2102
2406
  }
2103
2407
  get computedInputClass() {
2104
- return cn("flex-1 bg-transparent border-none p-0 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-0", this.size === 'xs' && "text-xs", this.size === 'lg' && "text-base", this.disabled && "cursor-not-allowed", this.readonly && "cursor-default", this.class);
2408
+ return cn(
2409
+ // Base styles
2410
+ "flex-1 bg-transparent border-none p-0", "placeholder:text-muted-foreground",
2411
+ // Remove all default focus styles
2412
+ "focus:outline-none focus:ring-0 focus:shadow-none",
2413
+ // Text sizing
2414
+ this.size === 'xs' && "text-xs", this.size === 'sm' && "text-sm", this.size === 'default' && "text-sm", this.size === 'lg' && "text-base",
2415
+ // Cursor states
2416
+ this.disabled && "cursor-not-allowed", this.readonly && "cursor-default",
2417
+ // Text color
2418
+ "text-foreground",
2419
+ // Selection color
2420
+ "selection:bg-primary/20 selection:text-foreground", this.class);
2421
+ }
2422
+ focusInput() {
2423
+ if (!this.disabled && this.inputEl) {
2424
+ this.inputEl.nativeElement.focus();
2425
+ }
2426
+ }
2427
+ onFocus() {
2428
+ this.isFocused = true;
2429
+ }
2430
+ onBlur() {
2431
+ this.isFocused = false;
2432
+ this.onTouched();
2105
2433
  }
2106
2434
  // --- Masking Logic ---
2107
2435
  onInput(event) {
@@ -2140,20 +2468,26 @@ class MaskedInputComponent {
2140
2468
  }
2141
2469
  return formatted;
2142
2470
  }
2143
- unmask(val) { return val.replace(/[^a-zA-Z0-9]/g, ''); }
2471
+ unmask(val) {
2472
+ return val.replace(/[^a-zA-Z0-9]/g, '');
2473
+ }
2144
2474
  writeValue(value) {
2145
2475
  this.displayValue = value ? this.applyMask(this.unmask(value.toString())) : '';
2146
2476
  this.cdr.markForCheck();
2147
2477
  }
2148
- registerOnChange(fn) { this.onChange = fn; }
2149
- registerOnTouched(fn) { this.onTouched = fn; }
2478
+ registerOnChange(fn) {
2479
+ this.onChange = fn;
2480
+ }
2481
+ registerOnTouched(fn) {
2482
+ this.onTouched = fn;
2483
+ }
2150
2484
  setDisabledState(isDisabled) {
2151
2485
  this.disabled = isDisabled;
2152
2486
  this.cdr.markForCheck();
2153
2487
  }
2154
2488
  cn = cn;
2155
2489
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MaskedInputComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2156
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MaskedInputComponent, isStandalone: true, selector: "tolle-masked-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", mask: "mask", placeholder: "placeholder", type: "type", disabled: "disabled", readonly: "readonly", class: "class", containerClass: "containerClass", error: "error", size: "size", returnRaw: "returnRaw" }, providers: [
2490
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MaskedInputComponent, isStandalone: true, selector: "tolle-masked-input", inputs: { id: "id", label: "label", hint: "hint", errorMessage: "errorMessage", mask: "mask", placeholder: "placeholder", type: "type", disabled: "disabled", readonly: "readonly", class: "class", containerClass: "containerClass", error: "error", size: "size", returnRaw: "returnRaw", hideHintOnFocus: "hideHintOnFocus" }, providers: [
2157
2491
  {
2158
2492
  provide: NG_VALUE_ACCESSOR,
2159
2493
  useExisting: forwardRef(() => MaskedInputComponent),
@@ -2164,15 +2498,17 @@ class MaskedInputComponent {
2164
2498
  <label
2165
2499
  *ngIf="label"
2166
2500
  [for]="id"
2167
- [class.opacity-50]="disabled"
2168
- class="text-sm font-medium text-foreground leading-none transition-opacity"
2501
+ [class]="computedLabelClass"
2169
2502
  >
2170
2503
  {{ label }}
2171
2504
  </label>
2172
2505
 
2173
- <div [class]="computedContainerClass">
2506
+ <div
2507
+ [class]="computedContainerClass"
2508
+ (click)="focusInput()"
2509
+ >
2174
2510
  <!-- Prefix Icon -->
2175
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2511
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2176
2512
  <ng-content select="[prefix]"></ng-content>
2177
2513
  </div>
2178
2514
 
@@ -2185,21 +2521,32 @@ class MaskedInputComponent {
2185
2521
  [readOnly]="readonly"
2186
2522
  [value]="displayValue"
2187
2523
  (input)="onInput($event)"
2188
- (blur)="onTouched()"
2524
+ (blur)="onBlur()"
2525
+ (focus)="onFocus()"
2189
2526
  [class]="computedInputClass"
2527
+ [attr.aria-invalid]="error"
2528
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
2190
2529
  />
2191
2530
 
2192
2531
  <!-- Suffix Icon -->
2193
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2532
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2194
2533
  <ng-content select="[suffix]"></ng-content>
2195
2534
  </div>
2196
2535
  </div>
2197
2536
 
2198
2537
  <ng-container *ngIf="!disabled">
2199
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
2538
+ <p
2539
+ *ngIf="hint && !error"
2540
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
2541
+ [class.opacity-0]="isFocused && hideHintOnFocus"
2542
+ >
2200
2543
  {{ hint }}
2201
2544
  </p>
2202
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
2545
+ <p
2546
+ *ngIf="error && errorMessage"
2547
+ [id]="id + '-error'"
2548
+ class="text-xs text-destructive px-1"
2549
+ >
2203
2550
  {{ errorMessage }}
2204
2551
  </p>
2205
2552
  </ng-container>
@@ -2224,15 +2571,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2224
2571
  <label
2225
2572
  *ngIf="label"
2226
2573
  [for]="id"
2227
- [class.opacity-50]="disabled"
2228
- class="text-sm font-medium text-foreground leading-none transition-opacity"
2574
+ [class]="computedLabelClass"
2229
2575
  >
2230
2576
  {{ label }}
2231
2577
  </label>
2232
2578
 
2233
- <div [class]="computedContainerClass">
2579
+ <div
2580
+ [class]="computedContainerClass"
2581
+ (click)="focusInput()"
2582
+ >
2234
2583
  <!-- Prefix Icon -->
2235
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2584
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2236
2585
  <ng-content select="[prefix]"></ng-content>
2237
2586
  </div>
2238
2587
 
@@ -2245,21 +2594,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2245
2594
  [readOnly]="readonly"
2246
2595
  [value]="displayValue"
2247
2596
  (input)="onInput($event)"
2248
- (blur)="onTouched()"
2597
+ (blur)="onBlur()"
2598
+ (focus)="onFocus()"
2249
2599
  [class]="computedInputClass"
2600
+ [attr.aria-invalid]="error"
2601
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
2250
2602
  />
2251
2603
 
2252
2604
  <!-- Suffix Icon -->
2253
- <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors">
2605
+ <div class="flex items-center text-muted-foreground group-focus-within:text-primary transition-colors duration-200">
2254
2606
  <ng-content select="[suffix]"></ng-content>
2255
2607
  </div>
2256
2608
  </div>
2257
2609
 
2258
2610
  <ng-container *ngIf="!disabled">
2259
- <p *ngIf="hint && !error" class="text-xs text-muted-foreground px-1">
2611
+ <p
2612
+ *ngIf="hint && !error"
2613
+ class="text-xs text-muted-foreground px-1 transition-opacity duration-200"
2614
+ [class.opacity-0]="isFocused && hideHintOnFocus"
2615
+ >
2260
2616
  {{ hint }}
2261
2617
  </p>
2262
- <p *ngIf="error && errorMessage" class="text-xs text-destructive px-1">
2618
+ <p
2619
+ *ngIf="error && errorMessage"
2620
+ [id]="id + '-error'"
2621
+ class="text-xs text-destructive px-1"
2622
+ >
2263
2623
  {{ errorMessage }}
2264
2624
  </p>
2265
2625
  </ng-container>
@@ -2294,6 +2654,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2294
2654
  type: Input
2295
2655
  }], returnRaw: [{
2296
2656
  type: Input
2657
+ }], hideHintOnFocus: [{
2658
+ type: Input
2297
2659
  }], inputEl: [{
2298
2660
  type: ViewChild,
2299
2661
  args: ['inputEl', { static: true }]
@@ -2446,7 +2808,7 @@ class DatePickerComponent {
2446
2808
  ></tolle-calendar>
2447
2809
  </div>
2448
2810
  </div>
2449
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MaskedInputComponent, selector: "tolle-masked-input", inputs: ["id", "label", "hint", "errorMessage", "mask", "placeholder", "type", "disabled", "readonly", "class", "containerClass", "error", "size", "returnRaw"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "disablePastDates"] }] });
2811
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MaskedInputComponent, selector: "tolle-masked-input", inputs: ["id", "label", "hint", "errorMessage", "mask", "placeholder", "type", "disabled", "readonly", "class", "containerClass", "error", "size", "returnRaw", "hideHintOnFocus"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "disablePastDates"] }] });
2450
2812
  }
2451
2813
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
2452
2814
  type: Component,
@@ -3038,7 +3400,7 @@ class DataTableComponent {
3038
3400
  (onPageSizeChange)="updatePage()"
3039
3401
  ></tolle-pagination>
3040
3402
  </div>
3041
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PaginationComponent, selector: "tolle-pagination", inputs: ["class", "showPageLinks", "showPageOptions", "showCurrentPageInfo", "currentPageInfoTemplate", "totalRecords", "currentPageSize", "currentPage", "pageSizeOptions"], outputs: ["onPageNumberChange", "onPageSizeChange"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
3403
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PaginationComponent, selector: "tolle-pagination", inputs: ["class", "showPageLinks", "showPageOptions", "showCurrentPageInfo", "currentPageInfoTemplate", "totalRecords", "currentPageSize", "currentPage", "pageSizeOptions"], outputs: ["onPageNumberChange", "onPageSizeChange"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
3042
3404
  }
3043
3405
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DataTableComponent, decorators: [{
3044
3406
  type: Component,
@@ -3948,7 +4310,7 @@ class DateRangePickerComponent {
3948
4310
  ></tolle-range-calendar>
3949
4311
  </div>
3950
4312
  </div>
3951
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: RangeCalendarComponent, selector: "tolle-range-calendar", inputs: ["class", "disablePastDates"], outputs: ["rangeSelect"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error"] }] });
4313
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: RangeCalendarComponent, selector: "tolle-range-calendar", inputs: ["class", "disablePastDates"], outputs: ["rangeSelect"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
3952
4314
  }
3953
4315
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DateRangePickerComponent, decorators: [{
3954
4316
  type: Component,
@@ -4166,30 +4528,69 @@ class TextareaComponent {
4166
4528
  label = '';
4167
4529
  placeholder = '';
4168
4530
  hint = '';
4531
+ errorMessage = '';
4169
4532
  rows = 3;
4170
4533
  maxLength;
4171
4534
  showCharacterCount = false;
4172
4535
  autoGrow = false;
4173
4536
  error = false;
4174
4537
  className = '';
4538
+ // Focus behavior
4539
+ hideHintOnFocus = true;
4540
+ hideCharacterCountOnFocus = false;
4175
4541
  // New States
4176
4542
  disabled = false;
4177
4543
  readonly = false;
4178
4544
  value = '';
4545
+ isFocused = false;
4179
4546
  onChange = () => { };
4180
4547
  onTouched = () => { };
4181
4548
  ngAfterViewInit() {
4182
4549
  if (this.autoGrow)
4183
4550
  this.resize();
4184
4551
  }
4552
+ get computedLabelClass() {
4553
+ return cn("text-sm font-medium text-foreground leading-none transition-opacity duration-200", this.disabled && "opacity-50");
4554
+ }
4185
4555
  get textareaClasses() {
4186
- return cn('flex w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background transition-all duration-200 shadow-sm', 'placeholder:text-muted-foreground focus-visible:outline-none',
4187
- // Focus States (Disabled only when readonly or disabled is true)
4188
- !(this.readonly || this.disabled) && 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
4189
- // Border colors
4190
- this.error ? 'border-destructive' : 'border-input',
4191
- // Disabled vs Readonly styles
4192
- this.disabled && 'cursor-not-allowed opacity-50', this.readonly && 'cursor-default border-dashed focus-visible:ring-0', 'scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent', 'min-h-[80px]', this.className);
4556
+ return cn(
4557
+ // Base styles
4558
+ 'flex w-full rounded-md border bg-background px-3 py-2 text-sm', 'ring-offset-background transition-all duration-200', 'placeholder:text-muted-foreground',
4559
+ // Border and shadow
4560
+ 'border-input shadow-sm',
4561
+ // Minimum height
4562
+ 'min-h-[80px]',
4563
+ // Focus state - SIMPLE LIKE ZARDUI
4564
+ !(this.readonly || this.disabled) && [
4565
+ 'focus:outline-none',
4566
+ 'focus:ring-4',
4567
+ 'focus:ring-ring/30',
4568
+ 'focus:ring-offset-0',
4569
+ 'focus:shadow-none',
4570
+ // Border darkens on focus automatically
4571
+ 'focus:border-primary/80'
4572
+ ],
4573
+ // Error state
4574
+ this.error && [
4575
+ 'border-destructive',
4576
+ !(this.readonly || this.disabled) && [
4577
+ 'focus:border-destructive/80',
4578
+ 'focus:ring-destructive/30'
4579
+ ]
4580
+ ],
4581
+ // Disabled state
4582
+ this.disabled && [
4583
+ 'cursor-not-allowed opacity-50',
4584
+ 'border-opacity-50'
4585
+ ],
4586
+ // Readonly state
4587
+ this.readonly && [
4588
+ 'cursor-default',
4589
+ 'border-dashed',
4590
+ !this.disabled && 'focus:ring-0 focus:border-opacity-100'
4591
+ ],
4592
+ // Scrollbar styling
4593
+ 'scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent', this.className);
4193
4594
  }
4194
4595
  handleInput(event) {
4195
4596
  if (this.readonly || this.disabled)
@@ -4200,6 +4601,13 @@ class TextareaComponent {
4200
4601
  if (this.autoGrow)
4201
4602
  this.resize();
4202
4603
  }
4604
+ onFocus() {
4605
+ this.isFocused = true;
4606
+ }
4607
+ onBlur() {
4608
+ this.isFocused = false;
4609
+ this.onTouched();
4610
+ }
4203
4611
  resize() {
4204
4612
  const textarea = this.textareaElement.nativeElement;
4205
4613
  textarea.style.height = 'auto';
@@ -4210,11 +4618,17 @@ class TextareaComponent {
4210
4618
  if (this.autoGrow)
4211
4619
  setTimeout(() => this.resize());
4212
4620
  }
4213
- registerOnChange(fn) { this.onChange = fn; }
4214
- registerOnTouched(fn) { this.onTouched = fn; }
4215
- setDisabledState(isDisabled) { this.disabled = isDisabled; }
4621
+ registerOnChange(fn) {
4622
+ this.onChange = fn;
4623
+ }
4624
+ registerOnTouched(fn) {
4625
+ this.onTouched = fn;
4626
+ }
4627
+ setDisabledState(isDisabled) {
4628
+ this.disabled = isDisabled;
4629
+ }
4216
4630
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4217
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TextareaComponent, isStandalone: true, selector: "tolle-textarea", inputs: { id: "id", label: "label", placeholder: "placeholder", hint: "hint", rows: "rows", maxLength: "maxLength", showCharacterCount: "showCharacterCount", autoGrow: "autoGrow", error: "error", className: "className", disabled: "disabled", readonly: "readonly" }, providers: [
4631
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: TextareaComponent, isStandalone: true, selector: "tolle-textarea", inputs: { id: "id", label: "label", placeholder: "placeholder", hint: "hint", errorMessage: "errorMessage", rows: "rows", maxLength: "maxLength", showCharacterCount: "showCharacterCount", autoGrow: "autoGrow", error: "error", className: "className", hideHintOnFocus: "hideHintOnFocus", hideCharacterCountOnFocus: "hideCharacterCountOnFocus", disabled: "disabled", readonly: "readonly" }, providers: [
4218
4632
  {
4219
4633
  provide: NG_VALUE_ACCESSOR,
4220
4634
  useExisting: forwardRef(() => TextareaComponent),
@@ -4222,9 +4636,11 @@ class TextareaComponent {
4222
4636
  }
4223
4637
  ], viewQueries: [{ propertyName: "textareaElement", first: true, predicate: ["textareaElement"], descendants: true }], ngImport: i0, template: `
4224
4638
  <div class="flex flex-col gap-1.5 w-full">
4225
- <label *ngIf="label" [for]="id"
4226
- [class.opacity-50]="disabled"
4227
- class="text-sm font-medium text-foreground leading-none transition-opacity">
4639
+ <label
4640
+ *ngIf="label"
4641
+ [for]="id"
4642
+ [class]="computedLabelClass"
4643
+ >
4228
4644
  {{ label }}
4229
4645
  </label>
4230
4646
 
@@ -4238,16 +4654,37 @@ class TextareaComponent {
4238
4654
  [rows]="rows"
4239
4655
  [(ngModel)]="value"
4240
4656
  (input)="handleInput($event)"
4241
- (blur)="onTouched()"
4657
+ (blur)="onBlur()"
4658
+ (focus)="onFocus()"
4242
4659
  [class]="textareaClasses"
4243
4660
  [style.resize]="(autoGrow || readonly || disabled) ? 'none' : 'vertical'"
4244
4661
  [style.overflow]="autoGrow ? 'hidden' : 'auto'"
4662
+ [attr.aria-invalid]="error"
4663
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
4664
+ [attr.maxlength]="maxLength"
4245
4665
  ></textarea>
4246
4666
  </div>
4247
4667
 
4248
- <div *ngIf="(showCharacterCount || hint) && !disabled" class="flex justify-between items-center px-1">
4249
- <p *ngIf="hint" class="text-xs text-muted-foreground">{{ hint }}</p>
4250
- <p *ngIf="showCharacterCount" class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium">
4668
+ <div *ngIf="(showCharacterCount || hint || errorMessage) && !disabled" class="flex justify-between items-center px-1">
4669
+ <p
4670
+ *ngIf="hint && !error"
4671
+ class="text-xs text-muted-foreground transition-opacity duration-200"
4672
+ [class.opacity-0]="isFocused && hideHintOnFocus"
4673
+ >
4674
+ {{ hint }}
4675
+ </p>
4676
+ <p
4677
+ *ngIf="error && errorMessage"
4678
+ [id]="id + '-error'"
4679
+ class="text-xs text-destructive"
4680
+ >
4681
+ {{ errorMessage }}
4682
+ </p>
4683
+ <p
4684
+ *ngIf="showCharacterCount"
4685
+ class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium transition-opacity duration-200"
4686
+ [class.opacity-0]="isFocused && hideCharacterCountOnFocus"
4687
+ >
4251
4688
  {{ value.length || 0 }}{{ maxLength ? ' / ' + maxLength : '' }}
4252
4689
  </p>
4253
4690
  </div>
@@ -4269,9 +4706,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4269
4706
  ],
4270
4707
  template: `
4271
4708
  <div class="flex flex-col gap-1.5 w-full">
4272
- <label *ngIf="label" [for]="id"
4273
- [class.opacity-50]="disabled"
4274
- class="text-sm font-medium text-foreground leading-none transition-opacity">
4709
+ <label
4710
+ *ngIf="label"
4711
+ [for]="id"
4712
+ [class]="computedLabelClass"
4713
+ >
4275
4714
  {{ label }}
4276
4715
  </label>
4277
4716
 
@@ -4285,16 +4724,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4285
4724
  [rows]="rows"
4286
4725
  [(ngModel)]="value"
4287
4726
  (input)="handleInput($event)"
4288
- (blur)="onTouched()"
4727
+ (blur)="onBlur()"
4728
+ (focus)="onFocus()"
4289
4729
  [class]="textareaClasses"
4290
4730
  [style.resize]="(autoGrow || readonly || disabled) ? 'none' : 'vertical'"
4291
4731
  [style.overflow]="autoGrow ? 'hidden' : 'auto'"
4732
+ [attr.aria-invalid]="error"
4733
+ [attr.aria-describedby]="error && errorMessage ? id + '-error' : null"
4734
+ [attr.maxlength]="maxLength"
4292
4735
  ></textarea>
4293
4736
  </div>
4294
4737
 
4295
- <div *ngIf="(showCharacterCount || hint) && !disabled" class="flex justify-between items-center px-1">
4296
- <p *ngIf="hint" class="text-xs text-muted-foreground">{{ hint }}</p>
4297
- <p *ngIf="showCharacterCount" class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium">
4738
+ <div *ngIf="(showCharacterCount || hint || errorMessage) && !disabled" class="flex justify-between items-center px-1">
4739
+ <p
4740
+ *ngIf="hint && !error"
4741
+ class="text-xs text-muted-foreground transition-opacity duration-200"
4742
+ [class.opacity-0]="isFocused && hideHintOnFocus"
4743
+ >
4744
+ {{ hint }}
4745
+ </p>
4746
+ <p
4747
+ *ngIf="error && errorMessage"
4748
+ [id]="id + '-error'"
4749
+ class="text-xs text-destructive"
4750
+ >
4751
+ {{ errorMessage }}
4752
+ </p>
4753
+ <p
4754
+ *ngIf="showCharacterCount"
4755
+ class="text-[10px] uppercase tracking-wider text-muted-foreground ml-auto font-medium transition-opacity duration-200"
4756
+ [class.opacity-0]="isFocused && hideCharacterCountOnFocus"
4757
+ >
4298
4758
  {{ value.length || 0 }}{{ maxLength ? ' / ' + maxLength : '' }}
4299
4759
  </p>
4300
4760
  </div>
@@ -4312,6 +4772,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4312
4772
  type: Input
4313
4773
  }], hint: [{
4314
4774
  type: Input
4775
+ }], errorMessage: [{
4776
+ type: Input
4315
4777
  }], rows: [{
4316
4778
  type: Input
4317
4779
  }], maxLength: [{
@@ -4324,6 +4786,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4324
4786
  type: Input
4325
4787
  }], className: [{
4326
4788
  type: Input
4789
+ }], hideHintOnFocus: [{
4790
+ type: Input
4791
+ }], hideCharacterCountOnFocus: [{
4792
+ type: Input
4327
4793
  }], disabled: [{
4328
4794
  type: Input
4329
4795
  }], readonly: [{
@@ -4887,23 +5353,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4887
5353
  class OtpSlotComponent {
4888
5354
  char = '';
4889
5355
  isActive = false;
4890
- isFirst = false; // New Input
4891
- isLast = false; // New Input
5356
+ isFirst = false;
5357
+ isLast = false;
4892
5358
  class = '';
4893
5359
  cn = cn;
5360
+ get computedClass() {
5361
+ return cn(
5362
+ // Base styles (matching input container)
5363
+ 'relative flex h-10 w-10 items-center justify-center', 'transition-all duration-200', 'text-center font-medium select-none',
5364
+ // Border styling - exactly like input
5365
+ 'border border-input shadow-sm bg-background',
5366
+ // FOCUS STYLING - EXACTLY LIKE INPUT COMPONENT
5367
+ this.isActive && [
5368
+ // Darker border on focus
5369
+ 'border-primary/80',
5370
+ // Remove shadow on focus
5371
+ 'shadow-none',
5372
+ // Focus ring with same styling as input
5373
+ 'ring-4',
5374
+ 'ring-ring/30',
5375
+ 'ring-offset-0',
5376
+ // Ensure it's above other slots
5377
+ 'z-10'
5378
+ ],
5379
+ // Filled state
5380
+ this.char && !this.isActive && 'border-primary/60',
5381
+ // Border radius
5382
+ this.isFirst && 'rounded-l-md', this.isLast && 'rounded-r-md',
5383
+ // Remove left border for all but first slot
5384
+ !this.isFirst && '-ml-px', this.class);
5385
+ }
4894
5386
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OtpSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4895
5387
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: OtpSlotComponent, isStandalone: true, selector: "tolle-otp-slot", inputs: { char: "char", isActive: "isActive", isFirst: "isFirst", isLast: "isLast", class: "class" }, ngImport: i0, template: `
4896
- <div [class]="cn(
4897
- 'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all',
4898
- 'bg-background',
4899
- isFirst ? 'rounded-l-md border-l' : '',
4900
- isLast ? 'rounded-r-md' : '',
4901
- isActive ? 'z-10 ring-2 ring-ring ring-offset-background' : '',
4902
- class
4903
- )">
4904
- {{ char || '' }}
5388
+ <div [class]="computedClass">
5389
+ <span class="text-lg font-medium">{{ char || '' }}</span>
4905
5390
  <div *ngIf="isActive && !char" class="pointer-events-none absolute inset-0 flex items-center justify-center">
4906
- <div class="h-4 w-px animate-caret-blink bg-foreground duration-1000"></div>
5391
+ <div class="h-6 w-0.5 animate-caret-blink bg-foreground"></div>
4907
5392
  </div>
4908
5393
  </div>
4909
5394
  `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
@@ -4915,17 +5400,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4915
5400
  standalone: true,
4916
5401
  imports: [NgIf],
4917
5402
  template: `
4918
- <div [class]="cn(
4919
- 'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all',
4920
- 'bg-background',
4921
- isFirst ? 'rounded-l-md border-l' : '',
4922
- isLast ? 'rounded-r-md' : '',
4923
- isActive ? 'z-10 ring-2 ring-ring ring-offset-background' : '',
4924
- class
4925
- )">
4926
- {{ char || '' }}
5403
+ <div [class]="computedClass">
5404
+ <span class="text-lg font-medium">{{ char || '' }}</span>
4927
5405
  <div *ngIf="isActive && !char" class="pointer-events-none absolute inset-0 flex items-center justify-center">
4928
- <div class="h-4 w-px animate-caret-blink bg-foreground duration-1000"></div>
5406
+ <div class="h-6 w-0.5 animate-caret-blink bg-foreground"></div>
4929
5407
  </div>
4930
5408
  </div>
4931
5409
  `