@egose/shadcn-theme-ng-tw 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +2 -2
  2. package/accordion/README.md +405 -2
  3. package/alert/README.md +372 -2
  4. package/alert/fesm2022/alert.mjs +1 -1
  5. package/alert-dialog/README.md +471 -5
  6. package/aspect-ratio/README.md +272 -5
  7. package/autocomplete/README.md +502 -2
  8. package/autocomplete/fesm2022/autocomplete.mjs +1 -1
  9. package/avatar/README.md +357 -5
  10. package/badge/README.md +318 -2
  11. package/basic-alert/README.md +353 -2
  12. package/breadcrumb/README.md +406 -5
  13. package/button/README.md +482 -2
  14. package/button/fesm2022/button.mjs +85 -107
  15. package/button/types/button.d.ts +5 -8
  16. package/button-group/README.md +318 -5
  17. package/button-group/fesm2022/button-group.mjs +1 -1
  18. package/calendar/README.md +357 -2
  19. package/card/README.md +331 -5
  20. package/carousel/README.md +333 -5
  21. package/carousel/fesm2022/carousel.mjs +4 -1
  22. package/checkbox/README.md +320 -2
  23. package/checkbox/fesm2022/checkbox.mjs +6 -7
  24. package/checkbox/types/checkbox.d.ts +1 -1
  25. package/collapsible/README.md +332 -5
  26. package/combobox/README.md +507 -5
  27. package/combobox/fesm2022/combobox.mjs +5 -2
  28. package/command/README.md +435 -5
  29. package/confirmation-dialog/README.md +301 -2
  30. package/context-menu/README.md +366 -5
  31. package/date-picker/README.md +469 -2
  32. package/date-picker/fesm2022/date-picker.mjs +150 -32
  33. package/date-picker/types/date-picker.d.ts +102 -9
  34. package/dialog/README.md +448 -2
  35. package/drawer/README.md +395 -5
  36. package/dropdown-menu/README.md +417 -5
  37. package/empty/README.md +329 -5
  38. package/field/README.md +385 -5
  39. package/form-autocomplete/README.md +177 -0
  40. package/form-autocomplete/fesm2022/form-autocomplete.mjs +211 -0
  41. package/form-autocomplete/package.json +24 -0
  42. package/form-autocomplete/types/form-autocomplete.d.ts +61 -0
  43. package/form-checkbox/README.md +322 -2
  44. package/form-checkbox/fesm2022/form-checkbox.mjs +22 -9
  45. package/form-checkbox/types/form-checkbox.d.ts +23 -4
  46. package/form-combobox/README.md +202 -0
  47. package/form-combobox/fesm2022/form-combobox.mjs +273 -0
  48. package/form-combobox/package.json +24 -0
  49. package/form-combobox/types/form-combobox.d.ts +73 -0
  50. package/form-date-picker/README.md +348 -2
  51. package/form-date-picker/fesm2022/form-date-picker.mjs +60 -16
  52. package/form-date-picker/types/form-date-picker.d.ts +17 -1
  53. package/form-date-picker-multi/README.md +191 -0
  54. package/form-date-picker-multi/fesm2022/form-date-picker-multi.mjs +239 -0
  55. package/form-date-picker-multi/package.json +24 -0
  56. package/form-date-picker-multi/types/form-date-picker-multi.d.ts +58 -0
  57. package/form-date-range-picker/README.md +253 -0
  58. package/form-date-range-picker/fesm2022/form-date-range-picker.mjs +236 -0
  59. package/form-date-range-picker/package.json +24 -0
  60. package/form-date-range-picker/types/form-date-range-picker.d.ts +55 -0
  61. package/form-field/README.md +356 -2
  62. package/form-field-simple/README.md +340 -2
  63. package/form-input-otp/README.md +194 -0
  64. package/form-input-otp/fesm2022/form-input-otp.mjs +194 -0
  65. package/form-input-otp/package.json +24 -0
  66. package/form-input-otp/types/form-input-otp.d.ts +58 -0
  67. package/form-month-year-picker/README.md +188 -0
  68. package/form-month-year-picker/fesm2022/form-month-year-picker.mjs +229 -0
  69. package/form-month-year-picker/package.json +24 -0
  70. package/form-month-year-picker/types/form-month-year-picker.d.ts +53 -0
  71. package/form-native-select/README.md +205 -0
  72. package/form-native-select/fesm2022/form-native-select.mjs +187 -0
  73. package/form-native-select/package.json +24 -0
  74. package/form-native-select/types/form-native-select.d.ts +57 -0
  75. package/form-phone-input/README.md +188 -0
  76. package/form-phone-input/fesm2022/form-phone-input.mjs +203 -0
  77. package/form-phone-input/package.json +24 -0
  78. package/form-phone-input/types/form-phone-input.d.ts +58 -0
  79. package/form-radio-group/README.md +198 -0
  80. package/form-radio-group/fesm2022/form-radio-group.mjs +211 -0
  81. package/form-radio-group/package.json +24 -0
  82. package/form-radio-group/types/form-radio-group.d.ts +63 -0
  83. package/form-searchable-multiselect/README.md +371 -2
  84. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +19 -10
  85. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +18 -1
  86. package/form-select/README.md +360 -2
  87. package/form-select/fesm2022/form-select.mjs +24 -15
  88. package/form-select/types/form-select.d.ts +18 -1
  89. package/form-slider/README.md +182 -0
  90. package/form-slider/fesm2022/form-slider.mjs +186 -0
  91. package/form-slider/package.json +24 -0
  92. package/form-slider/types/form-slider.d.ts +60 -0
  93. package/form-switch/README.md +173 -0
  94. package/form-switch/fesm2022/form-switch.mjs +178 -0
  95. package/form-switch/package.json +24 -0
  96. package/form-switch/types/form-switch.d.ts +50 -0
  97. package/form-text-input/README.md +381 -2
  98. package/form-text-input/fesm2022/form-text-input.mjs +19 -10
  99. package/form-text-input/types/form-text-input.d.ts +18 -1
  100. package/form-textarea/README.md +357 -2
  101. package/form-textarea/fesm2022/form-textarea.mjs +19 -10
  102. package/form-textarea/types/form-textarea.d.ts +18 -1
  103. package/form-toggle/README.md +186 -0
  104. package/form-toggle/fesm2022/form-toggle.mjs +241 -0
  105. package/form-toggle/package.json +24 -0
  106. package/form-toggle/types/form-toggle.d.ts +82 -0
  107. package/form-toggle-group/README.md +176 -0
  108. package/form-toggle-group/fesm2022/form-toggle-group.mjs +216 -0
  109. package/form-toggle-group/package.json +24 -0
  110. package/form-toggle-group/types/form-toggle-group.d.ts +65 -0
  111. package/hover-card/README.md +256 -5
  112. package/icon/README.md +239 -2
  113. package/input/README.md +269 -2
  114. package/input-group/README.md +335 -5
  115. package/input-group/fesm2022/input-group.mjs +23 -13
  116. package/input-group/types/input-group.d.ts +4 -1
  117. package/input-otp/README.md +375 -5
  118. package/item/README.md +385 -5
  119. package/item/fesm2022/item.mjs +3 -3
  120. package/kbd/README.md +291 -5
  121. package/label/README.md +272 -2
  122. package/layout-simple/README.md +193 -2
  123. package/layout-simple/fesm2022/layout-simple.mjs +877 -409
  124. package/layout-simple/types/layout-simple.d.ts +174 -137
  125. package/menu/README.md +417 -2
  126. package/menubar/README.md +343 -5
  127. package/native-select/README.md +323 -5
  128. package/native-select/fesm2022/native-select.mjs +18 -6
  129. package/native-select/types/native-select.d.ts +7 -2
  130. package/navigation-menu/README.md +369 -5
  131. package/package.json +57 -1
  132. package/pagination/README.md +388 -5
  133. package/phone-input/README.md +114 -0
  134. package/phone-input/fesm2022/phone-input.mjs +191 -0
  135. package/phone-input/package.json +24 -0
  136. package/phone-input/types/phone-input.d.ts +67 -0
  137. package/popover/README.md +331 -2
  138. package/progress/README.md +311 -5
  139. package/radio-group/README.md +364 -2
  140. package/radio-group/fesm2022/radio-group.mjs +5 -1
  141. package/resizable/README.md +269 -5
  142. package/scroll-area/README.md +233 -5
  143. package/searchable-multiselect/README.md +323 -2
  144. package/select/README.md +437 -2
  145. package/separator/README.md +222 -2
  146. package/sheet/README.md +311 -2
  147. package/sheet/fesm2022/sheet.mjs +1 -1
  148. package/sidebar/README.md +457 -5
  149. package/skeleton/README.md +217 -5
  150. package/slider/README.md +273 -5
  151. package/slider/fesm2022/slider.mjs +17 -13
  152. package/sonner/README.md +346 -2
  153. package/spinner/README.md +284 -2
  154. package/switch/README.md +310 -2
  155. package/switch/fesm2022/switch.mjs +7 -5
  156. package/switch/types/switch.d.ts +2 -1
  157. package/table/README.md +423 -5
  158. package/tabs/README.md +411 -2
  159. package/tabs/fesm2022/tabs.mjs +12 -2
  160. package/textarea/README.md +282 -5
  161. package/toggle/README.md +270 -5
  162. package/toggle-group/README.md +340 -5
  163. package/tooltip/README.md +269 -2
  164. package/typography/README.md +271 -5
  165. package/utils/README.md +303 -2
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, input, NgModule } from '@angular/core';
3
- import { classes } from '@egose/shadcn-theme-ng-tw/utils';
2
+ import { Directive, input, inject, effect, NgModule } from '@angular/core';
3
+ import { classes, hlm } from '@egose/shadcn-theme-ng-tw/utils';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import * as i1 from '@egose/shadcn-theme-ng-tw/button';
6
- import { provideBrnButtonConfig, HlmBtn } from '@egose/shadcn-theme-ng-tw/button';
6
+ import { HlmBtn, provideBrnButtonConfig } from '@egose/shadcn-theme-ng-tw/button';
7
7
  import * as i1$1 from '@egose/shadcn-theme-ng-tw/input';
8
8
  import { HlmInput } from '@egose/shadcn-theme-ng-tw/input';
9
9
  import * as i1$2 from '@egose/shadcn-theme-ng-tw/textarea';
@@ -11,7 +11,7 @@ import { HlmTextarea } from '@egose/shadcn-theme-ng-tw/textarea';
11
11
 
12
12
  class HlmInputGroup {
13
13
  constructor() {
14
- classes(() => 'tw:border-input tw:dark:bg-input/30 tw:has-[[data-slot=input-group-control]:focus-visible]:border-ring tw:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 tw:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-destructive/20 tw:has-[[data-slot][data-matches-spartan-invalid=true]]:border-destructive tw:dark:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-destructive/40 tw:h-9 tw:rounded-md tw:border tw:shadow-xs tw:transition-[color,box-shadow] tw:in-data-[slot=combobox-content]:focus-within:border-inherit tw:in-data-[slot=combobox-content]:focus-within:ring-0 tw:has-[[data-slot=input-group-control]:focus-visible]:ring-3 tw:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-3 tw:has-[>[data-align=block-end]]:h-auto tw:has-[>[data-align=block-end]]:flex-col tw:has-[>[data-align=block-start]]:h-auto tw:has-[>[data-align=block-start]]:flex-col tw:has-[>[data-align=block-end]]:[&>input]:pt-3 tw:has-[>[data-align=block-start]]:[&>input]:pb-3 tw:has-[>[data-align=inline-end]]:[&>input]:pe-1.5 tw:has-[>[data-align=inline-start]]:[&>input]:ps-1.5 tw:group/input-group tw:relative tw:flex tw:w-full tw:min-w-0 tw:items-center tw:outline-none tw:has-[>textarea]:h-auto');
14
+ classes(() => 'tw:border-input tw:dark:bg-input/30 tw:has-[[data-slot=input-group-control]:focus-visible]:border-ring tw:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 tw:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-destructive/20 tw:has-[[data-slot][data-matches-spartan-invalid=true]]:border-destructive tw:dark:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-destructive/40 tw:h-9 tw:rounded-md tw:border tw:shadow-xs tw:transition-[color,box-shadow] tw:in-data-[slot=combobox-content]:focus-within:border-inherit tw:in-data-[slot=combobox-content]:focus-within:ring-0 tw:has-[[data-slot=input-group-control]:focus-visible]:ring-3 tw:has-[[data-slot][data-matches-spartan-invalid=true]]:ring-3 tw:has-[>[data-align=block-end]]:h-auto tw:has-[>[data-align=block-end]]:flex-col tw:has-[>[data-align=block-start]]:h-auto tw:has-[>[data-align=block-start]]:flex-col tw:has-[>[data-align=block-end]]:[&>input]:pt-3 tw:has-[>[data-align=block-start]]:[&>input]:pb-3 tw:has-[>[data-align=inline-end]]:[&>input]:pe-1.5 tw:has-[>[data-align=inline-start]]:[&>input]:ps-1.5 tw:group/input-group tw:relative tw:flex tw:w-full tw:min-w-0 tw:max-w-full tw:items-center tw:outline-none tw:has-[>textarea]:h-auto');
15
15
  }
16
16
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmInputGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
17
17
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: HlmInputGroup, isStandalone: true, selector: "[hlmInputGroup],hlm-input-group", host: { attributes: { "data-slot": "input-group", "role": "group" } }, ngImport: i0 });
@@ -27,17 +27,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
27
27
  }]
28
28
  }], ctorParameters: () => [] });
29
29
 
30
- const inputGroupAddonVariants$1 = cva("tw:text-muted-foreground tw:h-auto tw:gap-2 tw:py-1.5 tw:text-sm tw:font-medium tw:group-data-[disabled=true]/input-group:opacity-50 tw:[&>kbd]:rounded-[calc(var(--radius)-5px)] tw:[&>ng-icon:not([class*='text-'])]:text-[length:--spacing(4)] tw:flex tw:cursor-text tw:items-center tw:justify-center tw:select-none", {
30
+ const inputGroupAddonVariants$1 = cva("tw:text-muted-foreground tw:h-auto tw:gap-2 tw:py-1.5 tw:text-sm tw:font-medium tw:group-data-[disabled=true]/input-group:opacity-50 tw:[&>kbd]:rounded-[calc(var(--radius)-5px)] tw:[&>ng-icon:not([class*='text-'])]:text-[length:--spacing(4)] tw:flex tw:shrink-0 tw:cursor-text tw:items-center tw:justify-center tw:select-none", {
31
31
  variants: {
32
32
  align: {
33
33
  'inline-start': 'tw:ps-2 tw:has-[>button]:-ms-1 tw:has-[>kbd]:ms-[-0.15rem] tw:order-first',
34
- 'inline-end': 'tw:pe-2 tw:has-[>button]:-me-1 tw:has-[>kbd]:me-[-0.15rem] tw:order-last',
34
+ 'inline-end': 'tw:pe-2 tw:has-[>button]:pe-1 tw:has-[>kbd]:me-[-0.15rem] tw:order-last',
35
35
  'block-start': 'tw:px-2.5 tw:pt-2 tw:group-has-[>input]/input-group:pt-2 tw:[.border-b]:pb-2 tw:order-first tw:w-full tw:justify-start',
36
36
  'block-end': 'tw:px-2.5 tw:pb-2 tw:group-has-[>input]/input-group:pb-2 tw:[.border-t]:pt-2 tw:order-last tw:w-full tw:justify-start',
37
37
  },
38
38
  },
39
39
  defaultVariants: {
40
- align: "tw:inline-start",
40
+ align: 'inline-start',
41
41
  },
42
42
  });
43
43
  class HlmInputGroupAddon {
@@ -64,10 +64,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
64
64
  const inputGroupAddonVariants = cva('tw:gap-2 tw:text-sm tw:flex tw:items-center tw:shadow-none', {
65
65
  variants: {
66
66
  size: {
67
- xs: "tw:h-6 tw:gap-1 tw:rounded-[calc(var(--radius)-5px)] tw:px-1.5 tw:[&>ng-icon:not([class*='text-'])]:text-[length:--spacing(3.5)]",
67
+ xs: "tw:h-6 tw:gap-1 tw:rounded-[calc(var(--radius)-5px)] tw:px-1.5 tw:[&>ng-icon:not([class*='text-'])]:text-[length:--spacing(3.5)] tw:[&_svg:not([class*='size-'])]:size-3.5",
68
68
  sm: '',
69
- 'icon-xs': 'tw:size-6 tw:rounded-[calc(var(--radius)-5px)] tw:p-0 tw:has-[>ng-icon]:p-0',
70
- 'icon-sm': 'tw:size-8 tw:p-0 tw:has-[>ng-icon]:p-0',
69
+ 'icon-xs': "tw:size-6 tw:rounded-[calc(var(--radius)-5px)] tw:p-0 tw:has-[>ng-icon]:p-0 tw:[&>ng-icon:not([class*='text-'])]:text-[length:--spacing(3)] tw:[&_svg:not([class*='size-'])]:size-3",
70
+ 'icon-sm': "tw:size-8 tw:p-0 tw:has-[>ng-icon]:p-0 tw:[&>ng-icon:not([class*='text-'])]:text-[length:--spacing(4)] tw:[&_svg:not([class*='size-'])]:size-4",
71
71
  },
72
72
  },
73
73
  defaultVariants: {
@@ -75,15 +75,25 @@ const inputGroupAddonVariants = cva('tw:gap-2 tw:text-sm tw:flex tw:items-center
75
75
  },
76
76
  });
77
77
  class HlmInputGroupButton {
78
+ _btn = inject(HlmBtn);
78
79
  size = input('xs', /* @ts-ignore */
79
80
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
80
81
  type = input('button', /* @ts-ignore */
81
82
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
83
+ userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
82
84
  constructor() {
83
- classes(() => inputGroupAddonVariants({ size: this.size() }));
85
+ // HlmBtn renders its classes through a `[class]` host binding, so layering
86
+ // another `classes()` call here would lose: the binding output is treated
87
+ // as base classes and merged last, letting HlmBtn's default `h-9 px-4 py-2`
88
+ // beat the input-group sizing and push the button onto the outer
89
+ // container's borders. Route the variant through `setClass()` instead so it
90
+ // wins over the button defaults while explicit `class` values still win.
91
+ effect(() => {
92
+ this._btn.setClass(hlm(inputGroupAddonVariants({ size: this.size() }), this.userClass()));
93
+ });
84
94
  }
85
95
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmInputGroupButton, deps: [], target: i0.ɵɵFactoryTarget.Directive });
86
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: HlmInputGroupButton, isStandalone: true, selector: "button[hlmInputGroupButton]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-size": "size()", "type": "type()" } }, providers: [
96
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: HlmInputGroupButton, isStandalone: true, selector: "button[hlmInputGroupButton]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-size": "size()", "type": "type()" } }, providers: [
87
97
  provideBrnButtonConfig({
88
98
  variant: 'ghost',
89
99
  }),
@@ -109,7 +119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
109
119
  '[type]': 'type()',
110
120
  },
111
121
  }]
112
- }], ctorParameters: () => [], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }] } });
122
+ }], ctorParameters: () => [], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
113
123
 
114
124
  class HlmInputGroupInput {
115
125
  constructor() {
@@ -1,4 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
+ import { ClassValue } from 'clsx';
2
3
  import * as i1 from '@egose/shadcn-theme-ng-tw/button';
3
4
  import * as i1$1 from '@egose/shadcn-theme-ng-tw/input';
4
5
  import * as i1$2 from '@egose/shadcn-theme-ng-tw/textarea';
@@ -17,11 +18,13 @@ declare class HlmInputGroupAddon {
17
18
  }
18
19
 
19
20
  declare class HlmInputGroupButton {
21
+ private readonly _btn;
20
22
  readonly size: i0.InputSignal<"xs" | "sm" | "icon-xs" | "icon-sm" | null | undefined>;
21
23
  readonly type: i0.InputSignal<"button" | "submit" | "reset">;
24
+ readonly userClass: i0.InputSignal<ClassValue>;
22
25
  constructor();
23
26
  static ɵfac: i0.ɵɵFactoryDeclaration<HlmInputGroupButton, never>;
24
- static ɵdir: i0.ɵɵDirectiveDeclaration<HlmInputGroupButton, "button[hlmInputGroupButton]", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.HlmBtn; inputs: { "variant": "variant"; }; outputs: {}; }]>;
27
+ static ɵdir: i0.ɵɵDirectiveDeclaration<HlmInputGroupButton, "button[hlmInputGroupButton]", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.HlmBtn; inputs: { "variant": "variant"; }; outputs: {}; }]>;
25
28
  }
26
29
 
27
30
  declare class HlmInputGroupInput {
@@ -1,11 +1,381 @@
1
- # InputOtp
1
+ # Input OTP (`@egose/shadcn-theme-ng/input-otp`)
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli).
3
+ One-time-password entry: a row of individual character slots (with a blinking fake caret) that behaves as a single field — the shadcn/ui _InputOTP_ equivalent (6-digit SMS/email verification codes, 2FA, invite codes). Keystroke handling, paste splitting, and the `ControlValueAccessor` come from spartan-ng's `BrnInputOtp` family; this subpath adds the shadcn slot styling, group wrapper, separator glyph, and fake caret.
4
4
 
5
- ## Building
5
+ > **Ships as:** `@egose/shadcn-theme-ng/input-otp` (plain Tailwind) and `@egose/shadcn-theme-ng-tw/input-otp` (`tw:`-prefixed variant). See the [package README](../../README.md) for installation, peer dependencies, and the Tailwind-variant contract. Do not publish this project directory independently.
6
6
 
7
- To build the library, run:
7
+ ## Installation
8
8
 
9
9
  ```bash
10
- ng build input-otp
10
+ # Plain Tailwind (no prefix)
11
+ npm install @egose/shadcn-theme-ng
12
+
13
+ # Or the tw:-prefixed variant
14
+ npm install @egose/shadcn-theme-ng-tw
15
+ ```
16
+
17
+ Peer dependencies (Angular, CDK, `@spartan-ng/brain`, `rxjs`) are documented in the [package README](../../README.md#peer-dependencies).
18
+
19
+ ```ts
20
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
21
+ // tw variant:
22
+ // import { HlmInputOtpImports } from '@egose/shadcn-theme-ng-tw/input-otp';
23
+ ```
24
+
25
+ ## Imports
26
+
27
+ The behavior host `BrnInputOtp` lives in `@spartan-ng/brain/input-otp` — import it alongside the styling imports:
28
+
29
+ ```ts
30
+ // Standalone component:
31
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
32
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
33
+
34
+ @Component({
35
+ standalone: true,
36
+ imports: [BrnInputOtp, HlmInputOtpImports],
37
+ template: `
38
+ <brn-input-otp hlmInputOtp [length]="6">
39
+ <div hlmInputOtpGroup>
40
+ <hlm-input-otp-slot index="0" />
41
+ <hlm-input-otp-slot index="1" />
42
+ <hlm-input-otp-slot index="2" />
43
+ </div>
44
+ <hlm-input-otp-separator />
45
+ <div hlmInputOtpGroup>
46
+ <hlm-input-otp-slot index="3" />
47
+ <hlm-input-otp-slot index="4" />
48
+ <hlm-input-otp-slot index="5" />
49
+ </div>
50
+ </brn-input-otp>
51
+ `,
52
+ })
53
+ export class MyComp {}
11
54
  ```
55
+
56
+ ```ts
57
+ // NgModule-based:
58
+ import { HlmInputOtpModule } from '@egose/shadcn-theme-ng/input-otp';
59
+
60
+ @NgModule({ imports: [BrnInputOtp, HlmInputOtpModule] })
61
+ export class MyModule {}
62
+ ```
63
+
64
+ Exported from `src/public-api.ts`: `HlmInputOtp`, `HlmInputOtpGroup`, `HlmInputOtpSeparator`, `HlmInputOtpSlot`, `HlmInputOtpFakeCaret`, plus `HlmInputOtpImports` and `HlmInputOtpModule`.
65
+
66
+ ## Anatomy / Structure
67
+
68
+ ```html
69
+ <label for="otp-code">Verification code</label>
70
+ <brn-input-otp hlmInputOtp [length]="6" inputId="otp-code">
71
+ <div hlmInputOtpGroup>
72
+ <hlm-input-otp-slot index="0" />
73
+ <hlm-input-otp-slot index="1" />
74
+ <hlm-input-otp-slot index="2" />
75
+ </div>
76
+ <hlm-input-otp-separator />
77
+ <div hlmInputOtpGroup>
78
+ <hlm-input-otp-slot index="3" />
79
+ <hlm-input-otp-slot index="4" />
80
+ <hlm-input-otp-slot index="5" />
81
+ </div>
82
+ </brn-input-otp>
83
+ ```
84
+
85
+ Real selectors: `brn-input-otp[hlmInputOtp]` / `brn-input-otp[hlm]` (flex row container, `data-slot="input-otp"`); `[hlmInputOtpGroup]` / `hlm-input-otp-group` (slot cluster, `data-slot="input-otp-group"`); `hlm-input-otp-slot` (component, requires `index`, renders `brn-input-otp-slot` + `hlm-input-otp-fake-caret`); `hlm-input-otp-separator` (component, `role="separator"`, `lucideMinus` glyph); `hlm-input-otp-fake-caret` (blinking caret block, usually not used directly — the slot renders it).
86
+
87
+ Brain inputs on `brn-input-otp` (from `@spartan-ng/brain/input-otp`): `length` (**required**), `value` / `valueChange`, `completed`, `disabled`, `inputId`, `inputAutocomplete`, `inputMode`, `inputClass`, `autofocus`, `transformPaste`, `hostStyles` / `inputStyles` / `containerStyles`.
88
+
89
+ ## API reference
90
+
91
+ ### `brn-input-otp[hlmInputOtp]` — `HlmInputOtp`
92
+
93
+ Pure styling directive (`data-slot="input-otp"`, flex row, dims on `has-disabled`). No inputs/outputs — bind `length`, `value`, `disabled`, `inputId`, … on the `brn-input-otp` element itself.
94
+
95
+ ### `[hlmInputOtpGroup]` — `HlmInputOtpGroup`
96
+
97
+ Slot cluster wrapper (`data-slot="input-otp-group"`). No inputs/outputs. Use one group per visual cluster (e.g. `3+3` with a separator, or a single group of 4/6).
98
+
99
+ ### `hlm-input-otp-slot` — `HlmInputOtpSlot`
100
+
101
+ | Input | Type | Description |
102
+ | ------- | -------------------------------------------------- | -------------------------------------------------------- |
103
+ | `index` | `number` (**required**, `numberAttribute` coerced) | Zero-based position of the character this slot displays. |
104
+
105
+ Renders `<brn-input-otp-slot [index]>` internally; active slot gets the `ring` highlight, invalid state the destructive border.
106
+
107
+ ### `hlm-input-otp-separator` — `HlmInputOtpSeparator`
108
+
109
+ Visual `-` divider between groups (`role="separator"`, `lucideMinus` via `ng-icon`). No inputs/outputs.
110
+
111
+ ### `hlm-input-otp-fake-caret` — `HlmInputOtpFakeCaret`
112
+
113
+ Blinking caret block rendered inside each slot. No inputs/outputs; not normally instantiated directly.
114
+
115
+ ## Examples
116
+
117
+ ### 1. Basic 6-digit code (3+3 with separator)
118
+
119
+ ```ts
120
+ import { Component } from '@angular/core';
121
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
122
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
123
+
124
+ @Component({
125
+ standalone: true,
126
+ imports: [BrnInputOtp, HlmInputOtpImports],
127
+ template: `
128
+ <label for="otp-code">Verification code</label>
129
+ <brn-input-otp hlmInputOtp [length]="6" inputId="otp-code">
130
+ <div hlmInputOtpGroup>
131
+ <hlm-input-otp-slot index="0" />
132
+ <hlm-input-otp-slot index="1" />
133
+ <hlm-input-otp-slot index="2" />
134
+ </div>
135
+ <hlm-input-otp-separator />
136
+ <div hlmInputOtpGroup>
137
+ <hlm-input-otp-slot index="3" />
138
+ <hlm-input-otp-slot index="4" />
139
+ <hlm-input-otp-slot index="5" />
140
+ </div>
141
+ </brn-input-otp>
142
+ `,
143
+ })
144
+ export class BasicExample {}
145
+ ```
146
+
147
+ ### 2. Reactive form + auto-submit on `completed`
148
+
149
+ ```ts
150
+ import { Component } from '@angular/core';
151
+ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
152
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
153
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
154
+
155
+ @Component({
156
+ standalone: true,
157
+ imports: [ReactiveFormsModule, BrnInputOtp, HlmInputOtpImports],
158
+ template: `
159
+ <form [formGroup]="form">
160
+ <label for="otp">Enter the 6-digit code</label>
161
+ <brn-input-otp
162
+ hlmInputOtp
163
+ formControlName="code"
164
+ [length]="6"
165
+ inputId="otp"
166
+ inputMode="numeric"
167
+ inputAutocomplete="one-time-code"
168
+ (completed)="verify()"
169
+ >
170
+ <div hlmInputOtpGroup>
171
+ <hlm-input-otp-slot index="0" />
172
+ <hlm-input-otp-slot index="1" />
173
+ <hlm-input-otp-slot index="2" />
174
+ <hlm-input-otp-slot index="3" />
175
+ <hlm-input-otp-slot index="4" />
176
+ <hlm-input-otp-slot index="5" />
177
+ </div>
178
+ </brn-input-otp>
179
+ <p class="tw:text-sm">Value: {{ form.value.code }}</p>
180
+ </form>
181
+ `,
182
+ })
183
+ export class ReactiveExample {
184
+ readonly form = new FormGroup({
185
+ code: new FormControl<string>('', { nonNullable: true, validators: [Validators.minLength(6)] }),
186
+ });
187
+
188
+ verify() {
189
+ console.log('verifying', this.form.value.code);
190
+ }
191
+ }
192
+ ```
193
+
194
+ ### 3. Two-way value binding + controlled value
195
+
196
+ ```ts
197
+ import { Component, signal } from '@angular/core';
198
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
199
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
200
+
201
+ @Component({
202
+ standalone: true,
203
+ imports: [BrnInputOtp, HlmInputOtpImports],
204
+ template: `
205
+ <brn-input-otp hlmInputOtp [length]="4" [value]="pin()" (valueChange)="pin.set($event)" inputId="pin">
206
+ <div hlmInputOtpGroup>
207
+ <hlm-input-otp-slot index="0" />
208
+ <hlm-input-otp-slot index="1" />
209
+ <hlm-input-otp-slot index="2" />
210
+ <hlm-input-otp-slot index="3" />
211
+ </div>
212
+ </brn-input-otp>
213
+ <div class="tw:flex tw:gap-2">
214
+ <button type="button" (click)="pin.set('')">Clear</button>
215
+ <button type="button" (click)="pin.set('1234')">Fill demo</button>
216
+ </div>
217
+ `,
218
+ })
219
+ export class ControlledExample {
220
+ readonly pin = signal('');
221
+ }
222
+ ```
223
+
224
+ ### 4. Different lengths: 4-digit PIN vs 8-char invite code
225
+
226
+ ```ts
227
+ import { Component } from '@angular/core';
228
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
229
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
230
+
231
+ @Component({
232
+ standalone: true,
233
+ imports: [BrnInputOtp, HlmInputOtpImports],
234
+ template: `
235
+ <label for="pin">Card PIN (4 digits)</label>
236
+ <brn-input-otp hlmInputOtp [length]="4" inputId="pin" inputMode="numeric">
237
+ <div hlmInputOtpGroup>
238
+ <hlm-input-otp-slot index="0" />
239
+ <hlm-input-otp-slot index="1" />
240
+ <hlm-input-otp-slot index="2" />
241
+ <hlm-input-otp-slot index="3" />
242
+ </div>
243
+ </brn-input-otp>
244
+
245
+ <label for="invite">Invite code (8 characters, two groups of 4)</label>
246
+ <brn-input-otp hlmInputOtp [length]="8" inputId="invite" inputMode="text">
247
+ <div hlmInputOtpGroup>
248
+ <hlm-input-otp-slot index="0" />
249
+ <hlm-input-otp-slot index="1" />
250
+ <hlm-input-otp-slot index="2" />
251
+ <hlm-input-otp-slot index="3" />
252
+ </div>
253
+ <hlm-input-otp-separator />
254
+ <div hlmInputOtpGroup>
255
+ <hlm-input-otp-slot index="4" />
256
+ <hlm-input-otp-slot index="5" />
257
+ <hlm-input-otp-slot index="6" />
258
+ <hlm-input-otp-slot index="7" />
259
+ </div>
260
+ </brn-input-otp>
261
+ `,
262
+ })
263
+ export class LengthsExample {}
264
+ ```
265
+
266
+ ### 5. Disabled + error display with resend flow
267
+
268
+ ```ts
269
+ import { Component, signal } from '@angular/core';
270
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
271
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
272
+ import { HlmError } from '@egose/shadcn-theme-ng/form-field';
273
+
274
+ @Component({
275
+ standalone: true,
276
+ imports: [BrnInputOtp, HlmInputOtpImports, HlmError],
277
+ template: `
278
+ <label for="otp-verify">Verification code</label>
279
+ <brn-input-otp
280
+ hlmInputOtp
281
+ [length]="6"
282
+ inputId="otp-verify"
283
+ inputMode="numeric"
284
+ inputAutocomplete="one-time-code"
285
+ [disabled]="verifying()"
286
+ [value]="code()"
287
+ (valueChange)="code.set($event); error.set(null)"
288
+ (completed)="verify()"
289
+ aria-describedby="otp-error"
290
+ >
291
+ <div hlmInputOtpGroup>
292
+ <hlm-input-otp-slot index="0" />
293
+ <hlm-input-otp-slot index="1" />
294
+ <hlm-input-otp-slot index="2" />
295
+ </div>
296
+ <hlm-input-otp-separator />
297
+ <div hlmInputOtpGroup>
298
+ <hlm-input-otp-slot index="3" />
299
+ <hlm-input-otp-slot index="4" />
300
+ <hlm-input-otp-slot index="5" />
301
+ </div>
302
+ </brn-input-otp>
303
+ @if (error()) {
304
+ <hlm-error id="otp-error">{{ error() }}</hlm-error>
305
+ }
306
+ <button type="button" (click)="resend()" [disabled]="verifying()">
307
+ {{ verifying() ? 'Verifying…' : 'Resend code' }}
308
+ </button>
309
+ `,
310
+ })
311
+ export class VerifyFlowExample {
312
+ readonly code = signal('');
313
+ readonly error = signal<string | null>(null);
314
+ readonly verifying = signal(false);
315
+
316
+ verify() {
317
+ this.verifying.set(true);
318
+ setTimeout(() => {
319
+ this.verifying.set(false);
320
+ if (this.code() !== '123456') {
321
+ this.error.set('That code is incorrect. Try again or resend.');
322
+ this.code.set('');
323
+ }
324
+ }, 900);
325
+ }
326
+
327
+ resend() {
328
+ this.error.set(null);
329
+ this.code.set('');
330
+ }
331
+ }
332
+ ```
333
+
334
+ ### 6. Paste normalization with `transformPaste`
335
+
336
+ ```ts
337
+ import { Component } from '@angular/core';
338
+ import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
339
+ import { HlmInputOtpImports } from '@egose/shadcn-theme-ng/input-otp';
340
+
341
+ @Component({
342
+ standalone: true,
343
+ imports: [BrnInputOtp, HlmInputOtpImports],
344
+ template: `
345
+ <label for="otp-paste">Code (pasting “12-34-56” works)</label>
346
+ <brn-input-otp hlmInputOtp [length]="6" inputId="otp-paste" [transformPaste]="stripSeparators">
347
+ <div hlmInputOtpGroup>
348
+ <hlm-input-otp-slot index="0" />
349
+ <hlm-input-otp-slot index="1" />
350
+ <hlm-input-otp-slot index="2" />
351
+ <hlm-input-otp-slot index="3" />
352
+ <hlm-input-otp-slot index="4" />
353
+ <hlm-input-otp-slot index="5" />
354
+ </div>
355
+ </brn-input-otp>
356
+ `,
357
+ })
358
+ export class PasteExample {
359
+ stripSeparators(pasted: string): string {
360
+ return pasted.replace(/[^0-9]/g, '').slice(0, 6);
361
+ }
362
+ }
363
+ ```
364
+
365
+ ## Accessibility notes
366
+
367
+ - Always render a visible `<label>` bound via `inputId` — the hidden native input is what AT focuses; without the label the purpose is unclear.
368
+ - `inputMode="numeric"` (digits) vs `text` (alphanumeric) selects the right mobile keyboard; `inputAutocomplete="one-time-code"` lets browsers/OS autofill SMS codes.
369
+ - Announce failures with a text error (`<hlm-error>` + `aria-describedby`), never by slot color alone.
370
+ - Keep `length` aligned with what the backend expects and validate `minLength(length)` on the form control so incomplete codes cannot submit.
371
+
372
+ ## Theming / CSS variables
373
+
374
+ No component-specific CSS variables; slots use the shared `--input` / `--ring` / `--destructive` tokens. Slot size is fixed (`size-9`); adjust grouping/spacing with wrapper classes rather than per-slot overrides.
375
+
376
+ ## Related subpaths
377
+
378
+ - `@egose/shadcn-theme-ng/input` — single-field `HlmInput` for non-OTP entry.
379
+ - `@egose/shadcn-theme-ng/form-field` — `HlmError`/`HlmHint` for code error text.
380
+ - `@egose/shadcn-theme-ng/button` — Verify/Resend actions.
381
+ - `@egose/shadcn-theme-ng/form-text-input` — labeled wrapper pattern to mirror for OTP forms.