@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
@@ -7,6 +7,59 @@ import { NgTemplateOutlet } from '@angular/common';
7
7
  import { HlmSpinner } from '@egose/shadcn-theme-ng-tw/spinner';
8
8
  import { cva } from 'class-variance-authority';
9
9
 
10
+ // Keep complete utility names here so consumer Tailwind builds can discover every tone.
11
+ const toneTextClasses = {
12
+ default: 'tw:text-primary',
13
+ primary: 'tw:text-primary',
14
+ secondary: 'tw:text-secondary',
15
+ success: 'tw:text-success',
16
+ warning: 'tw:text-warning',
17
+ danger: 'tw:text-danger',
18
+ info: 'tw:text-info',
19
+ light: 'tw:text-light-foreground',
20
+ dark: 'tw:text-dark',
21
+ accent: 'tw:text-accent',
22
+ destructive: 'tw:text-destructive',
23
+ muted: 'tw:text-muted-foreground',
24
+ outline: 'tw:text-primary',
25
+ link: 'tw:text-primary',
26
+ ghost: 'tw:text-light-foreground',
27
+ };
28
+ const outlineClasses = {
29
+ default: 'tw:border-primary tw:hover:bg-primary/10',
30
+ primary: 'tw:border-primary tw:hover:bg-primary/10',
31
+ secondary: 'tw:border-secondary tw:hover:bg-secondary/10',
32
+ success: 'tw:border-success tw:hover:bg-success/10',
33
+ warning: 'tw:border-warning tw:hover:bg-warning/10',
34
+ danger: 'tw:border-danger tw:hover:bg-danger/10',
35
+ info: 'tw:border-info tw:hover:bg-info/10',
36
+ light: 'tw:border-light tw:hover:bg-light/10',
37
+ dark: 'tw:border-dark tw:hover:bg-dark/10',
38
+ accent: 'tw:border-accent tw:hover:bg-accent/10',
39
+ destructive: 'tw:border-destructive tw:hover:bg-destructive/10',
40
+ muted: 'tw:border-muted tw:hover:bg-muted/10',
41
+ outline: 'tw:border-primary tw:hover:bg-primary/10',
42
+ link: '',
43
+ ghost: '',
44
+ };
45
+ const outlineFilledClasses = {
46
+ default: 'tw:hover:bg-primary tw:hover:text-primary-foreground',
47
+ primary: 'tw:hover:bg-primary tw:hover:text-primary-foreground',
48
+ secondary: 'tw:hover:bg-secondary tw:hover:text-secondary-foreground',
49
+ success: 'tw:hover:bg-success tw:hover:text-success-foreground',
50
+ warning: 'tw:hover:bg-warning tw:hover:text-warning-foreground',
51
+ danger: 'tw:hover:bg-danger tw:hover:text-danger-foreground',
52
+ info: 'tw:hover:bg-info tw:hover:text-info-foreground',
53
+ light: 'tw:hover:bg-light tw:hover:text-light-foreground',
54
+ dark: 'tw:hover:bg-dark tw:hover:text-dark-foreground',
55
+ accent: 'tw:hover:bg-accent tw:hover:text-accent-foreground',
56
+ destructive: 'tw:hover:bg-destructive tw:hover:text-destructive-foreground',
57
+ muted: 'tw:hover:bg-muted tw:hover:text-muted-foreground',
58
+ outline: 'tw:hover:bg-primary tw:hover:text-primary-foreground',
59
+ link: 'tw:hover:underline',
60
+ ghost: 'tw:hover:bg-light',
61
+ };
62
+ /** Semantic color utilities and appearances. Merge the result with hlm() for custom hosts. */
10
63
  const buttonVariants = cva([
11
64
  'tw:cursor-pointer',
12
65
  'tw:active:translate-y-[0.5px]',
@@ -45,7 +98,7 @@ const buttonVariants = cva([
45
98
  accent: 'tw:border tw:border-accent tw:bg-accent tw:text-accent-foreground tw:shadow-sm tw:hover:bg-accent/90',
46
99
  destructive: 'tw:border tw:border-destructive tw:bg-destructive tw:text-destructive-foreground tw:shadow-sm tw:hover:bg-destructive/90',
47
100
  muted: 'tw:border tw:border-muted tw:bg-muted tw:text-muted-foreground tw:shadow-sm tw:hover:bg-muted/90',
48
- outline: 'tw:border tw:border-primary tw:bg-white tw:text-primary tw:shadow-sm tw:hover:bg-primary/10',
101
+ outline: 'tw:border tw:border-primary tw:bg-background tw:text-primary tw:shadow-sm tw:hover:bg-primary/10',
49
102
  link: 'tw:text-primary tw:underline-offset-4 tw:hover:underline',
50
103
  ghost: 'tw:hover:bg-light tw:hover:text-light-foreground',
51
104
  },
@@ -66,14 +119,33 @@ const buttonVariants = cva([
66
119
  },
67
120
  appearance: {
68
121
  solid: '',
69
- outline: 'tw:bg-white tw:border',
70
- 'outline-filled': 'tw:bg-white tw:border',
122
+ outline: 'tw:bg-background tw:border tw:shadow-sm',
123
+ 'outline-filled': 'tw:bg-background tw:border tw:shadow-sm',
124
+ ghost: 'tw:bg-transparent tw:border-0 tw:shadow-none',
125
+ link: 'tw:bg-transparent tw:border-0 tw:shadow-none tw:hover:bg-transparent tw:underline-offset-4 tw:hover:underline',
71
126
  },
72
127
  loading: {
73
128
  true: 'tw:pointer-events-none',
74
129
  false: null,
75
130
  },
76
131
  },
132
+ compoundVariants: Object.keys(toneTextClasses).flatMap((variant) => [
133
+ {
134
+ variant,
135
+ appearance: ['outline', 'outline-filled', 'ghost', 'link'],
136
+ class: toneTextClasses[variant],
137
+ },
138
+ {
139
+ variant,
140
+ appearance: ['outline', 'outline-filled', 'ghost'],
141
+ class: outlineClasses[variant],
142
+ },
143
+ {
144
+ variant,
145
+ appearance: 'outline-filled',
146
+ class: outlineFilledClasses[variant],
147
+ },
148
+ ]),
77
149
  defaultVariants: {
78
150
  variant: 'primary',
79
151
  size: 'default',
@@ -82,11 +154,12 @@ const buttonVariants = cva([
82
154
  },
83
155
  });
84
156
  class HlmButton {
85
- /** Props as signal-based inputs */
157
+ /** Semantic color token. Legacy default, outline, link, and ghost variants remain supported. */
86
158
  variant = input('primary', /* @ts-ignore */
87
159
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
88
160
  size = input('default', /* @ts-ignore */
89
161
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
162
+ /** Visual treatment independent of color: solid, outline, outline-filled, ghost, or link. */
90
163
  appearance = input('solid', /* @ts-ignore */
91
164
  ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
92
165
  loading = input(false, /* @ts-ignore */
@@ -105,114 +178,19 @@ class HlmButton {
105
178
  _additionalClasses = signal('', /* @ts-ignore */
106
179
  ...(ngDevMode ? [{ debugName: "_additionalClasses" }] : /* istanbul ignore next */ []));
107
180
  /** Computed button class merging */
108
- _computedClass = computed(() => {
109
- const outlineClasses = this.appearance() === 'outline' || this.appearance() === 'outline-filled'
110
- ? [this.getOutlineClasses(this.variant())]
111
- : [];
112
- if (this.appearance() === 'outline-filled') {
113
- outlineClasses.push(this.getOutlineFilledClasses(this.variant()));
114
- }
115
- return hlm(buttonVariants({
116
- variant: this.variant(),
117
- size: this.size(),
118
- appearance: this.appearance(),
119
- loading: this.loading(),
120
- className: this.userClass(),
121
- }), outlineClasses, this.loading() ? 'tw:pointer-events-none' : '', this._additionalClasses());
122
- }, /* @ts-ignore */
181
+ _computedClass = computed(() => hlm(buttonVariants({
182
+ variant: this.variant(),
183
+ size: this.size(),
184
+ appearance: this.appearance(),
185
+ loading: this.loading(),
186
+ }), this.userClass(), this.loading() ? 'tw:pointer-events-none' : '', this._additionalClasses()), /* @ts-ignore */
123
187
  ...(ngDevMode ? [{ debugName: "_computedClass" }] : /* istanbul ignore next */ []));
124
- /** Computed spinner classes */
125
- spinnerClass = computed(() => {
126
- const base = this.appearance() === 'outline' || this.appearance() === 'outline-filled'
127
- ? this.getOutlineSpinnerClasses(this.variant())
128
- : this.getSpinnerClasses(this.variant());
129
- return hlm(base, this.spinnerUserClass());
130
- }, /* @ts-ignore */
188
+ /** Inherit the button's resolved text color, including theme and consumer overrides. */
189
+ spinnerClass = computed(() => hlm('tw:[&>svg]:text-current', this.spinnerUserClass()), /* @ts-ignore */
131
190
  ...(ngDevMode ? [{ debugName: "spinnerClass" }] : /* istanbul ignore next */ []));
132
191
  setClass(classes) {
133
192
  this._additionalClasses.set(classes);
134
193
  }
135
- /** Helper functions */
136
- getOutlineClasses(variant) {
137
- const colors = {
138
- default: 'tw:border-primary tw:text-primary tw:shadow-sm tw:hover:bg-primary/10',
139
- primary: 'tw:border-primary tw:text-primary tw:shadow-sm tw:hover:bg-primary/10',
140
- secondary: 'tw:border-secondary tw:text-secondary tw:shadow-sm tw:hover:bg-secondary/10',
141
- success: 'tw:border-success tw:text-success tw:shadow-sm tw:hover:bg-success/10',
142
- warning: 'tw:border-warning tw:text-warning tw:shadow-sm tw:hover:bg-warning/10',
143
- danger: 'tw:border-danger tw:text-danger tw:shadow-sm tw:hover:bg-danger/10',
144
- info: 'tw:border-info tw:text-info tw:shadow-sm tw:hover:bg-info/10',
145
- light: 'tw:border-light tw:text-light-foreground tw:shadow-sm tw:hover:bg-light/10',
146
- dark: 'tw:border-dark tw:text-dark tw:shadow-sm tw:hover:bg-dark/10',
147
- accent: 'tw:border-accent tw:text-accent tw:shadow-sm tw:hover:bg-accent/10',
148
- destructive: 'tw:border-destructive tw:text-destructive tw:shadow-sm tw:hover:bg-destructive/10',
149
- muted: 'tw:border-muted tw:text-muted-foreground tw:shadow-sm tw:hover:bg-muted/10',
150
- outline: 'tw:border-primary tw:text-primary tw:shadow-sm tw:hover:bg-primary/10',
151
- link: 'tw:text-primary',
152
- ghost: 'tw:text-light-foreground',
153
- };
154
- return colors[variant];
155
- }
156
- getOutlineFilledClasses(variant) {
157
- const colors = {
158
- default: 'tw:hover:bg-primary tw:hover:text-primary-foreground',
159
- primary: 'tw:hover:bg-primary tw:hover:text-primary-foreground',
160
- secondary: 'tw:hover:bg-secondary tw:hover:text-secondary-foreground',
161
- success: 'tw:hover:bg-success tw:hover:text-success-foreground',
162
- warning: 'tw:hover:bg-warning tw:hover:text-warning-foreground',
163
- danger: 'tw:hover:bg-danger tw:hover:text-danger-foreground',
164
- info: 'tw:hover:bg-info tw:hover:text-info-foreground',
165
- light: 'tw:hover:bg-light tw:hover:text-light-foreground',
166
- dark: 'tw:hover:bg-dark tw:hover:text-dark-foreground',
167
- accent: 'tw:hover:bg-accent tw:hover:text-accent-foreground',
168
- destructive: 'tw:hover:bg-destructive tw:hover:text-destructive-foreground',
169
- muted: 'tw:hover:bg-muted tw:hover:text-muted-foreground',
170
- outline: 'tw:hover:bg-primary tw:hover:text-primary-foreground',
171
- link: 'tw:hover:underline',
172
- ghost: 'tw:hover:bg-light',
173
- };
174
- return colors[variant];
175
- }
176
- getSpinnerClasses(variant) {
177
- const colors = {
178
- default: 'tw:[&>svg]:text-primary-foreground',
179
- primary: 'tw:[&>svg]:text-primary-foreground',
180
- secondary: 'tw:[&>svg]:text-secondary-foreground',
181
- success: 'tw:[&>svg]:text-success-foreground',
182
- warning: 'tw:[&>svg]:text-warning-foreground',
183
- danger: 'tw:[&>svg]:text-danger-foreground',
184
- info: 'tw:[&>svg]:text-info-foreground',
185
- light: 'tw:[&>svg]:text-light-foreground',
186
- dark: 'tw:[&>svg]:text-dark-foreground',
187
- accent: 'tw:[&>svg]:text-accent-foreground',
188
- destructive: 'tw:[&>svg]:text-destructive-foreground',
189
- muted: 'tw:[&>svg]:text-muted-foreground',
190
- outline: 'tw:[&>svg]:text-primary',
191
- link: 'tw:[&>svg]:text-primary',
192
- ghost: 'tw:[&>svg]:text-light-foreground',
193
- };
194
- return colors[variant];
195
- }
196
- getOutlineSpinnerClasses(variant) {
197
- const colors = {
198
- default: 'tw:[&>svg]:text-primary',
199
- primary: 'tw:[&>svg]:text-primary',
200
- secondary: 'tw:[&>svg]:text-secondary',
201
- success: 'tw:[&>svg]:text-success',
202
- warning: 'tw:[&>svg]:text-warning',
203
- danger: 'tw:[&>svg]:text-danger',
204
- info: 'tw:[&>svg]:text-info',
205
- light: 'tw:[&>svg]:text-light',
206
- dark: 'tw:[&>svg]:text-dark',
207
- accent: 'tw:[&>svg]:text-accent',
208
- destructive: 'tw:[&>svg]:text-destructive',
209
- muted: 'tw:[&>svg]:text-muted-foreground',
210
- outline: 'tw:[&>svg]:text-primary',
211
- link: 'tw:[&>svg]:text-primary',
212
- ghost: 'tw:[&>svg]:text-light-foreground',
213
- };
214
- return colors[variant];
215
- }
216
194
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
217
195
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmButton, isStandalone: true, selector: "button[hlmButton], a[hlmButton]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, spinnerUserClass: { classPropertyName: "spinnerUserClass", publicName: "spinnerUserClass", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "_computedClass()", "attr.aria-busy": "loading() || null", "attr.disabled": "(loading() || disabled()) ? true : null", "attr.type": "type()" } }, hostDirectives: [{ directive: i1.BrnButton, inputs: ["disabled", "disabled"] }], ngImport: i0, template: `
218
196
  <ng-template #projected>
@@ -18,10 +18,11 @@ declare class HlmBtn {
18
18
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HlmBtn, "button[hlmBtn], a[hlmBtn]", ["hlmBtn"], { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.BrnButton; inputs: { "disabled": "disabled"; }; outputs: {}; }]>;
19
19
  }
20
20
 
21
+ /** Semantic color utilities and appearances. Merge the result with hlm() for custom hosts. */
21
22
  declare const buttonVariants: (props?: ({
22
23
  variant?: "default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "light" | "dark" | "accent" | "destructive" | "muted" | "outline" | "link" | "ghost" | null | undefined;
23
24
  size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "compact-xs" | "compact-sm" | "compact-default" | "compact-lg" | "compact-icon" | null | undefined;
24
- appearance?: "outline" | "solid" | "outline-filled" | null | undefined;
25
+ appearance?: "outline" | "link" | "ghost" | "solid" | "outline-filled" | null | undefined;
25
26
  loading?: boolean | null | undefined;
26
27
  } & class_variance_authority_types.ClassProp) | undefined) => string;
27
28
  type ButtonVariants = VariantProps<typeof buttonVariants>;
@@ -29,9 +30,10 @@ type VariantType = NonNullable<ButtonVariants['variant']>;
29
30
  type SizeType = NonNullable<ButtonVariants['size']>;
30
31
  type AppearanceType = NonNullable<ButtonVariants['appearance']>;
31
32
  declare class HlmButton {
32
- /** Props as signal-based inputs */
33
+ /** Semantic color token. Legacy default, outline, link, and ghost variants remain supported. */
33
34
  readonly variant: _angular_core.InputSignal<VariantType>;
34
35
  readonly size: _angular_core.InputSignal<SizeType>;
36
+ /** Visual treatment independent of color: solid, outline, outline-filled, ghost, or link. */
35
37
  readonly appearance: _angular_core.InputSignal<AppearanceType>;
36
38
  readonly loading: _angular_core.InputSignal<boolean>;
37
39
  readonly icon: _angular_core.InputSignal<TemplateRef<unknown> | undefined>;
@@ -43,14 +45,9 @@ declare class HlmButton {
43
45
  private readonly _additionalClasses;
44
46
  /** Computed button class merging */
45
47
  protected readonly _computedClass: _angular_core.Signal<string>;
46
- /** Computed spinner classes */
48
+ /** Inherit the button's resolved text color, including theme and consumer overrides. */
47
49
  protected readonly spinnerClass: _angular_core.Signal<string>;
48
50
  setClass(classes: string): void;
49
- /** Helper functions */
50
- private getOutlineClasses;
51
- private getOutlineFilledClasses;
52
- private getSpinnerClasses;
53
- private getOutlineSpinnerClasses;
54
51
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<HlmButton, never>;
55
52
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<HlmButton, "button[hlmButton], a[hlmButton]", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "iconPosition": { "alias": "iconPosition"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "spinnerUserClass": { "alias": "spinnerUserClass"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, [{ directive: typeof i1.BrnButton; inputs: { "disabled": "disabled"; }; outputs: {}; }]>;
56
53
  }
@@ -1,11 +1,324 @@
1
- # ButtonGroup
1
+ # Button Group (`@egose/shadcn-theme-ng/button-group`)
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli).
3
+ A joined, toolbar-style group layout that fuses buttons, inputs, selects, and static text into one visually continuous control — the Angular port of shadcn/ui `Button Group`. There is no interactive behavior of its own: `HlmButtonGroup` is a thin `cva`-styled layout directive (`orientation: horizontal | vertical`) that strips inner border-radii and collapses adjacent borders, while `HlmButtonGroupText` and `HlmButtonGroupSeparator` provide the static-text cell and the divider cell. The only headless primitive involved is `BrnSeparator` from `@spartan-ng/brain/separator` (used inside the separator directive).
4
4
 
5
- ## Building
5
+ Ships as `@egose/shadcn-theme-ng/button-group` and `@egose/shadcn-theme-ng-tw/button-group` (the `tw:`-prefixed variant). See the [package README](../../README.md) for install, peer dependencies, and Tailwind setup. Do not publish this project directory independently — it is consumed via the published subpath only.
6
6
 
7
- To build the library, run:
7
+ ## Installation
8
8
 
9
9
  ```bash
10
- ng build button-group
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, `@spartan-ng/brain`, etc.) are documented in the [package README](../../README.md). This subpath itself declares `@angular/common`, `@angular/core`, and `@spartan-ng/brain` as peers (see `projects/button-group/package.json`).
18
+
19
+ ## Imports
20
+
21
+ Named imports from the subpath public API (`projects/button-group/src/public-api.ts`):
22
+
23
+ ```ts
24
+ import {
25
+ HlmButtonGroup,
26
+ HlmButtonGroupText,
27
+ HlmButtonGroupSeparator,
28
+ HlmButtonGroupImports,
29
+ HlmButtonGroupModule,
30
+ } from '@egose/shadcn-theme-ng/button-group';
31
+ // tw variant:
32
+ // import { ... } from '@egose/shadcn-theme-ng-tw/button-group';
33
+ ```
34
+
35
+ Standalone-component usage (preferred):
36
+
37
+ ```ts
38
+ import { Component } from '@angular/core';
39
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
40
+
41
+ @Component({
42
+ selector: 'app-demo',
43
+ standalone: true,
44
+ imports: [...HlmButtonGroupImports],
45
+ template: `...`,
46
+ })
47
+ export class DemoComponent {}
48
+ ```
49
+
50
+ NgModule usage:
51
+
52
+ ```ts
53
+ import { NgModule } from '@angular/core';
54
+ import { HlmButtonGroupModule } from '@egose/shadcn-theme-ng/button-group';
55
+
56
+ @NgModule({ imports: [HlmButtonGroupModule] })
57
+ export class DemoModule {}
58
+ ```
59
+
60
+ You can also import the three directives individually (`HlmButtonGroup`, `HlmButtonGroupText`, `HlmButtonGroupSeparator`) instead of the whole `HlmButtonGroupImports` array.
61
+
62
+ ## Anatomy / Structure
63
+
64
+ ```html
65
+ <!-- Element OR attribute form for the group itself -->
66
+ <hlm-button-group orientation="horizontal">
67
+ <button hlmBtn variant="outline">Left</button>
68
+ <button hlmBtn variant="outline">Middle</button>
69
+ <button hlmBtn variant="outline">Right</button>
70
+ </hlm-button-group>
71
+
72
+ <!-- With static text + separator (attribute forms also work) -->
73
+ <div hlmButtonGroup orientation="horizontal">
74
+ <span hlmButtonGroupText>https://</span>
75
+ <input hlmInput placeholder="example.com" />
76
+ <hlm-button-group-separator></hlm-button-group-separator>
77
+ <button hlmBtn>Go</button>
78
+ </div>
79
+ ```
80
+
81
+ Real selectors (from source):
82
+
83
+ | Class | Selectors |
84
+ | ------------------------- | --------------------------------------------------------- |
85
+ | `HlmButtonGroup` | `[hlmButtonGroup]`, `hlm-button-group` |
86
+ | `HlmButtonGroupText` | `[hlmButtonGroupText]`, `hlm-button-group-text` |
87
+ | `HlmButtonGroupSeparator` | `[hlmButtonGroupSeparator]`, `hlm-button-group-separator` |
88
+
89
+ All three carry a `data-slot` host attribute (`button-group`, `button-group-text`, `button-group-separator`). The group host also sets `role="group"` and `[attr.data-orientation]`.
90
+
91
+ ## API reference
92
+
93
+ ### `HlmButtonGroup` (`[hlmButtonGroup]`, `hlm-button-group`)
94
+
95
+ | Input | Type | Default | Description |
96
+ | ------------- | ---------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
97
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout axis. `horizontal` collapses left/right radii and left borders; `vertical` stacks, collapses top/bottom radii and top borders. Reflected as `data-orientation`. |
98
+
99
+ No outputs, no methods. Styling is computed by `buttonGroupVariants({ orientation })` (cva) plus helpers for nested `select-trigger`, `input`, and nested `button-group` spacing.
100
+
101
+ ### `HlmButtonGroupText` (`[hlmButtonGroupText]`, `hlm-button-group-text`)
102
+
103
+ Thin directive wrapper — no inputs, outputs, or methods. Renders a muted, bordered, static-text cell (`bg-muted`, `rounded-lg`, `px-2.5`, icon-aware). Use for prefixes/suffixes such as `https://`, `.com`, currency symbols, or counts.
104
+
105
+ ### `HlmButtonGroupSeparator` (`[hlmButtonGroupSeparator]`, `hlm-button-group-separator`)
106
+
107
+ | Brain input (via `BrnSeparator` hostDirective) | Type | Default | Description |
108
+ | ---------------------------------------------- | ---------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
109
+ | `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` (via `provideBrnSeparatorConfig`) | Separator axis. The provider defaults it to `vertical` so it divides horizontal groups out of the box; override for vertical groups. |
110
+ | `decorative` | `boolean` | — | Passed straight to `BrnSeparator` (aria-hidden handling). |
111
+
112
+ No outputs of its own. Host classes make it `self-stretch`, `bg-input`, 1px thick on the active axis.
113
+
114
+ ## Examples
115
+
116
+ ### 1. Basic horizontal button cluster
117
+
118
+ Three outline buttons fused into one segmented control.
119
+
120
+ ```ts
121
+ import { Component } from '@angular/core';
122
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
123
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
124
+
125
+ @Component({
126
+ selector: 'app-basic-group',
127
+ standalone: true,
128
+ imports: [...HlmButtonGroupImports, ...HlmButtonImports],
129
+ template: `
130
+ <hlm-button-group>
131
+ <button hlmBtn variant="outline">Left</button>
132
+ <button hlmBtn variant="outline">Center</button>
133
+ <button hlmBtn variant="outline">Right</button>
134
+ </hlm-button-group>
135
+ `,
136
+ })
137
+ export class BasicGroupComponent {}
138
+ ```
139
+
140
+ ### 2. All orientations
141
+
142
+ `horizontal` (default) vs `vertical`. Vertical stacks children and collapses vertical borders instead.
143
+
144
+ ```ts
145
+ import { Component } from '@angular/core';
146
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
147
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
148
+
149
+ @Component({
150
+ selector: 'app-orientations',
151
+ standalone: true,
152
+ imports: [...HlmButtonGroupImports, ...HlmButtonImports],
153
+ template: `
154
+ <div class="flex gap-8">
155
+ <hlm-button-group orientation="horizontal">
156
+ <button hlmBtn variant="outline">A</button>
157
+ <button hlmBtn variant="outline">B</button>
158
+ <button hlmBtn variant="outline">C</button>
159
+ </hlm-button-group>
160
+
161
+ <hlm-button-group orientation="vertical">
162
+ <button hlmBtn variant="outline">Top</button>
163
+ <button hlmBtn variant="outline">Middle</button>
164
+ <button hlmBtn variant="outline">Bottom</button>
165
+ </hlm-button-group>
166
+ </div>
167
+ `,
168
+ })
169
+ export class OrientationsComponent {}
170
+ ```
171
+
172
+ ### 3. Input group with static prefix/suffix text
173
+
174
+ Classic URL / price field: muted text cells fused with a native-styled input and an action button.
175
+
176
+ ```ts
177
+ import { Component } from '@angular/core';
178
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
179
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
180
+ import { HlmInput } from '@egose/shadcn-theme-ng/input';
181
+
182
+ @Component({
183
+ selector: 'app-addon-group',
184
+ standalone: true,
185
+ imports: [...HlmButtonGroupImports, ...HlmButtonImports, HlmInput],
186
+ template: `
187
+ <div hlmButtonGroup>
188
+ <span hlmButtonGroupText>https://</span>
189
+ <input hlmInput placeholder="example.com" class="w-48" />
190
+ <span hlmButtonGroupText>.com</span>
191
+ <button hlmBtn>Visit</button>
192
+ </div>
193
+ `,
194
+ })
195
+ export class AddonGroupComponent {}
196
+ ```
197
+
198
+ ### 4. Separated actions (divider cells)
199
+
200
+ Use `hlm-button-group-separator` between clusters instead of spacing utilities so the divider stretches to the group height.
201
+
202
+ ```ts
203
+ import { Component } from '@angular/core';
204
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
205
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
206
+
207
+ @Component({
208
+ selector: 'app-separated-group',
209
+ standalone: true,
210
+ imports: [...HlmButtonGroupImports, ...HlmButtonImports],
211
+ template: `
212
+ <hlm-button-group>
213
+ <button hlmBtn variant="outline">Cut</button>
214
+ <button hlmBtn variant="outline">Copy</button>
215
+ <button hlmBtn variant="outline">Paste</button>
216
+ <hlm-button-group-separator></hlm-button-group-separator>
217
+ <button hlmBtn variant="outline">Undo</button>
218
+ <button hlmBtn variant="outline">Redo</button>
219
+ </hlm-button-group>
220
+ `,
221
+ })
222
+ export class SeparatedGroupComponent {}
223
+ ```
224
+
225
+ Explanation: the separator defaults to `orientation="vertical"` via its provider. Inside a `orientation="vertical"` group, bind it explicitly:
226
+
227
+ ```html
228
+ <hlm-button-group orientation="vertical">
229
+ <button hlmBtn variant="outline">One</button>
230
+ <hlm-button-group-separator orientation="horizontal"></hlm-button-group-separator>
231
+ <button hlmBtn variant="outline">Two</button>
232
+ </hlm-button-group>
233
+ ```
234
+
235
+ ### 5. Mixed controls: select + input + button
236
+
237
+ The group stylesheet special-cases `select-trigger` and `input` children (`w-fit` triggers, `flex-1` inputs), so mixed toolbars fuse cleanly.
238
+
239
+ ```ts
240
+ import { Component, signal } from '@angular/core';
241
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
242
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
243
+ import { HlmInput } from '@egose/shadcn-theme-ng/input';
244
+ import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
245
+
246
+ @Component({
247
+ selector: 'app-mixed-group',
248
+ standalone: true,
249
+ imports: [...HlmButtonGroupImports, ...HlmButtonImports, HlmInput, ...HlmSelectImports],
250
+ template: `
251
+ <div hlmButtonGroup>
252
+ <hlm-select [value]="unit()">
253
+ <hlm-select-trigger class="w-28">
254
+ <hlm-select-value />
255
+ </hlm-select-trigger>
256
+ <hlm-select-content *hlmSelectPortal>
257
+ <hlm-select-item value="px">px</hlm-select-item>
258
+ <hlm-select-item value="rem">rem</hlm-select-item>
259
+ <hlm-select-item value="em">em</hlm-select-item>
260
+ </hlm-select-content>
261
+ </hlm-select>
262
+ <input hlmInput type="number" value="16" class="w-24" />
263
+ <button hlmBtn (click)="apply()">Apply</button>
264
+ </div>
265
+ `,
266
+ })
267
+ export class MixedGroupComponent {
268
+ readonly unit = signal('px');
269
+ apply() {
270
+ console.log('apply', this.unit());
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### 6. Icon toolbar with labels for assistive tech
276
+
277
+ Buttons keep their own `aria-label`s; the group only adds `role="group"` — give it an `aria-label` for context.
278
+
279
+ ```ts
280
+ import { Component } from '@angular/core';
281
+ import { HlmButtonGroupImports } from '@egose/shadcn-theme-ng/button-group';
282
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
283
+ import { NgIcon, provideIcons } from '@ng-icons/core';
284
+ import { lucideBold, lucideItalic, lucideUnderline } from '@ng-icons/lucide';
285
+
286
+ @Component({
287
+ selector: 'app-toolbar-group',
288
+ standalone: true,
289
+ imports: [...HlmButtonGroupImports, ...HlmButtonImports, NgIcon],
290
+ providers: [provideIcons({ lucideBold, lucideItalic, lucideUnderline })],
291
+ template: `
292
+ <hlm-button-group aria-label="Text formatting">
293
+ <button hlmBtn variant="outline" size="icon" aria-label="Bold">
294
+ <ng-icon name="lucideBold" />
295
+ </button>
296
+ <button hlmBtn variant="outline" size="icon" aria-label="Italic">
297
+ <ng-icon name="lucideItalic" />
298
+ </button>
299
+ <button hlmBtn variant="outline" size="icon" aria-label="Underline">
300
+ <ng-icon name="lucideUnderline" />
301
+ </button>
302
+ </hlm-button-group>
303
+ `,
304
+ })
305
+ export class ToolbarGroupComponent {}
11
306
  ```
307
+
308
+ ## Accessibility notes
309
+
310
+ - The group host renders `role="group"` — always pair it with `aria-label` or `aria-labelledby` when the purpose is not obvious from the buttons alone.
311
+ - `HlmButtonGroupSeparator` delegates to `BrnSeparator`: mark purely visual dividers `decorative`, otherwise keep them exposed so screen readers announce the grouping break.
312
+ - `HlmButtonGroupText` is a plain `<span>`-style cell with no semantics; never put interactive content inside it.
313
+ - Keyboard behavior is unchanged — Tab moves through each fused button/input normally; roving tabindex is not applied.
314
+
315
+ ## Theming / CSS variables
316
+
317
+ No component-specific CSS variables. Styling is Tailwind-driven (`bg-muted` text cells, `bg-input` separators, ring/border tokens). Dark mode follows the shared `tw:dark:` theme tokens. Override via `class` on any cell — the `classes()` helper merges your classes with the defaults.
318
+
319
+ ## Related subpaths
320
+
321
+ - `@egose/shadcn-theme-ng/button` — `hlmBtn` / `HlmBtn` used for every fused button.
322
+ - `@egose/shadcn-theme-ng/input` — `hlmInput` fused text/number fields.
323
+ - `@egose/shadcn-theme-ng/select` — triggers auto-size (`w-fit`) inside groups.
324
+ - `@egose/shadcn-theme-ng/separator` — standalone separator primitive (the group ships its own opinionated divider).
@@ -5,7 +5,7 @@ import { cva } from 'class-variance-authority';
5
5
  import * as i1 from '@spartan-ng/brain/separator';
6
6
  import { provideBrnSeparatorConfig, BrnSeparator } from '@spartan-ng/brain/separator';
7
7
 
8
- const buttonGroupVariants = cva('tw:has-[>[data-slot=button-group]]:gap-2 tw:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg tw:flex tw:w-fit tw:items-stretch tw:*:focus-visible:relative tw:*:focus-visible:z-10 tw:[&>[data-slot=select-trigger]:not([class*=\'w-\'])]:w-fit tw:[&>input]:flex-1', {
8
+ const buttonGroupVariants = cva("tw:has-[>[data-slot=button-group]]:gap-2 tw:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg tw:flex tw:w-fit tw:items-stretch tw:*:focus-visible:relative tw:*:focus-visible:z-10 tw:[&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit tw:[&>input]:flex-1", {
9
9
  variants: {
10
10
  orientation: {
11
11
  horizontal: 'tw:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! tw:[&>*:not(:first-child)]:rounded-l-none tw:[&>*:not(:first-child)]:border-l-0 tw:[&>*:not(:last-child)]:rounded-r-none',