@egose/shadcn-theme-ng-tw 0.1.0 → 0.2.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 (86) hide show
  1. package/README.md +1 -1
  2. package/accordion/README.md +405 -2
  3. package/alert/README.md +372 -2
  4. package/alert-dialog/README.md +471 -5
  5. package/aspect-ratio/README.md +272 -5
  6. package/autocomplete/README.md +502 -2
  7. package/avatar/README.md +357 -5
  8. package/badge/README.md +318 -2
  9. package/basic-alert/README.md +353 -2
  10. package/breadcrumb/README.md +406 -5
  11. package/button/README.md +482 -2
  12. package/button/fesm2022/button.mjs +85 -107
  13. package/button/types/button.d.ts +5 -8
  14. package/button-group/README.md +318 -5
  15. package/button-group/fesm2022/button-group.mjs +1 -1
  16. package/calendar/README.md +357 -2
  17. package/card/README.md +331 -5
  18. package/carousel/README.md +333 -5
  19. package/carousel/fesm2022/carousel.mjs +4 -1
  20. package/checkbox/README.md +320 -2
  21. package/collapsible/README.md +332 -5
  22. package/combobox/README.md +507 -5
  23. package/combobox/fesm2022/combobox.mjs +4 -1
  24. package/command/README.md +435 -5
  25. package/confirmation-dialog/README.md +301 -2
  26. package/context-menu/README.md +366 -5
  27. package/date-picker/README.md +465 -2
  28. package/date-picker/fesm2022/date-picker.mjs +2 -2
  29. package/dialog/README.md +448 -2
  30. package/drawer/README.md +395 -5
  31. package/dropdown-menu/README.md +417 -5
  32. package/empty/README.md +329 -5
  33. package/field/README.md +385 -5
  34. package/form-checkbox/README.md +312 -2
  35. package/form-date-picker/README.md +322 -2
  36. package/form-field/README.md +356 -2
  37. package/form-field-simple/README.md +340 -2
  38. package/form-searchable-multiselect/README.md +361 -2
  39. package/form-select/README.md +350 -2
  40. package/form-text-input/README.md +371 -2
  41. package/form-textarea/README.md +347 -2
  42. package/hover-card/README.md +256 -5
  43. package/icon/README.md +239 -2
  44. package/input/README.md +269 -2
  45. package/input-group/README.md +335 -5
  46. package/input-group/fesm2022/input-group.mjs +3 -3
  47. package/input-otp/README.md +375 -5
  48. package/item/README.md +385 -5
  49. package/item/fesm2022/item.mjs +3 -3
  50. package/kbd/README.md +291 -5
  51. package/label/README.md +272 -2
  52. package/layout-simple/README.md +193 -2
  53. package/layout-simple/fesm2022/layout-simple.mjs +877 -409
  54. package/layout-simple/types/layout-simple.d.ts +174 -137
  55. package/menu/README.md +417 -2
  56. package/menubar/README.md +343 -5
  57. package/native-select/README.md +323 -5
  58. package/navigation-menu/README.md +369 -5
  59. package/package.json +1 -1
  60. package/pagination/README.md +388 -5
  61. package/popover/README.md +331 -2
  62. package/progress/README.md +311 -5
  63. package/radio-group/README.md +364 -2
  64. package/radio-group/fesm2022/radio-group.mjs +5 -1
  65. package/resizable/README.md +269 -5
  66. package/scroll-area/README.md +233 -5
  67. package/searchable-multiselect/README.md +323 -2
  68. package/select/README.md +437 -2
  69. package/separator/README.md +222 -2
  70. package/sheet/README.md +311 -2
  71. package/sidebar/README.md +457 -5
  72. package/skeleton/README.md +217 -5
  73. package/slider/README.md +273 -5
  74. package/slider/fesm2022/slider.mjs +17 -13
  75. package/sonner/README.md +346 -2
  76. package/spinner/README.md +284 -2
  77. package/switch/README.md +310 -2
  78. package/table/README.md +423 -5
  79. package/tabs/README.md +411 -2
  80. package/tabs/fesm2022/tabs.mjs +12 -2
  81. package/textarea/README.md +282 -5
  82. package/toggle/README.md +270 -5
  83. package/toggle-group/README.md +340 -5
  84. package/tooltip/README.md +269 -2
  85. package/typography/README.md +271 -5
  86. package/utils/README.md +303 -2
@@ -1,11 +1,277 @@
1
- # Typography
1
+ # Typography (`@egose/shadcn-theme-ng/typography`)
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli).
3
+ Document type styles, equivalent to [shadcn/ui Typography](https://ui.shadcn.com/docs/components/typography). This subpath ships twelve **thin styling directives** — no behavior, no primitives, no inputs/outputs. Attach them to native elements (`h1`–`h4`, `p`, `blockquote`, `code`, `ul`, …) for shadcn type scale, plus matching exported class constants (`hlmH1`, `hlmP`, …) for reuse in custom components.
4
4
 
5
- ## Building
5
+ > **Ships as:** `@egose/shadcn-theme-ng/typography` and `@egose/shadcn-theme-ng-tw/typography` (the `tw:`-prefixed Tailwind variant). See the [package README](../../README.md) for install steps, peer dependencies, Tailwind setup, and testing/release guidance. 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 typography
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
+ No extra runtime dependencies. See the [package README](../../README.md) for the peer-dependency table.
18
+
19
+ ## Imports
20
+
21
+ All public symbols are re-exported from `projects/typography/src/public-api.ts`:
22
+
23
+ ```ts
24
+ import {
25
+ HlmBlockquote,
26
+ HlmCode,
27
+ HlmH1,
28
+ HlmH2,
29
+ HlmH3,
30
+ HlmH4,
31
+ HlmLarge,
32
+ HlmLead,
33
+ HlmMuted,
34
+ HlmP,
35
+ HlmSmall,
36
+ HlmTypographyImports,
37
+ HlmTypographyModule,
38
+ HlmUl,
39
+ } from '@egose/shadcn-theme-ng/typography';
40
+ // tw variant: swap to '@egose/shadcn-theme-ng-tw/typography'
41
+ ```
42
+
43
+ Class constants are exported alongside (`hlmH1`, `hlmH2`, `hlmH3`, `hlmH4`, `hlmP`, `hlmBlockquote`, `hlmCode`, `hlmLarge`, `hlmLead`, `hlmMuted`, `hlmSmall`, `hlmUl`).
44
+
45
+ Standalone-component usage (preferred):
46
+
47
+ ```ts
48
+ import { Component } from '@angular/core';
49
+ import { HlmTypographyImports } from '@egose/shadcn-theme-ng/typography';
50
+
51
+ @Component({
52
+ selector: 'app-demo',
53
+ standalone: true,
54
+ imports: [...HlmTypographyImports],
55
+ template: `<h1 hlmH1>Title</h1>`,
56
+ })
57
+ export class DemoComponent {}
58
+ ```
59
+
60
+ NgModule usage:
61
+
62
+ ```ts
63
+ import { NgModule } from '@angular/core';
64
+ import { HlmTypographyModule } from '@egose/shadcn-theme-ng/typography';
65
+
66
+ @NgModule({ imports: [HlmTypographyModule] })
67
+ export class FeatureModule {}
68
+ ```
69
+
70
+ | Symbol | Kind | Host selector (real) |
71
+ | ---------------------- | ------------- | -------------------------------- |
72
+ | `HlmH1` | Directive | `[hlmH1]` |
73
+ | `HlmH2` | Directive | `[hlmH2]` |
74
+ | `HlmH3` | Directive | `[hlmH3]` |
75
+ | `HlmH4` | Directive | `[hlmH4]` |
76
+ | `HlmP` | Directive | `[hlmP]` |
77
+ | `HlmBlockquote` | Directive | `[hlmBlockquote]` |
78
+ | `HlmCode` | Directive | `[hlmCode]` |
79
+ | `HlmLarge` | Directive | `[hlmLarge]` |
80
+ | `HlmLead` | Directive | `[hlmLead]` |
81
+ | `HlmMuted` | Directive | `[hlmMuted]` |
82
+ | `HlmSmall` | Directive | `[hlmSmall]` |
83
+ | `HlmUl` | Directive | `[hlmUl]` |
84
+ | `HlmTypographyImports` | `const` array | All twelve directives. |
85
+ | `HlmTypographyModule` | NgModule | Imports + re-exports all twelve. |
86
+
87
+ ## Anatomy / Structure
88
+
89
+ ```html
90
+ <h1 hlmH1>The Joke Tax Chronicles</h1>
91
+ <p hlmLead>Once upon a time, in a far-off land, there was a very lazy king.</p>
92
+ <h2 hlmH2>The King's Plan</h2>
93
+ <p hlmP>The king thought…</p>
94
+ <blockquote hlmBlockquote>"After all," he said, "everyone enjoys a good joke."</blockquote>
95
+ <h3 hlmH3>The Joke Tax</h3>
96
+ <ul hlmUl>
97
+ <li>1st level of puns: 5 gold coins</li>
98
+ <li>2nd level of jokes: 10 gold coins</li>
99
+ </ul>
100
+ <p hlmP>Learn more with <code hlmCode>ng add</code>.</p>
101
+ <p hlmLarge>Large callout line.</p>
102
+ <p hlmMuted>Muted fine print.</p>
103
+ <p hlmSmall>Small label text.</p>
104
+ <h4 hlmH4>Appendix</h4>
105
+ ```
106
+
107
+ Attach each directive to its matching semantic element (`HlmH1`→`<h1>`, `HlmUl`→`<ul>`, `HlmCode`→`<code>`, …) — the directive adds classes only and does not change semantics.
108
+
109
+ ## API reference
110
+
111
+ None of the twelve directives declare inputs, outputs, or methods. Each merges a fixed class string (also exported as a constant) via `classes()`:
112
+
113
+ | Directive / constant | Element | Fixed classes (abridged) |
114
+ | --------------------------------- | -------------- | ----------------------------------------------------------------------------------- |
115
+ | `HlmH1` / `hlmH1` | `<h1>` | `scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl` |
116
+ | `HlmH2` / `hlmH2` | `<h2>` | `scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0` |
117
+ | `HlmH3` / `hlmH3` | `<h3>` | `scroll-m-20 text-2xl font-semibold tracking-tight` |
118
+ | `HlmH4` / `hlmH4` | `<h4>` | `scroll-m-20 text-xl font-semibold tracking-tight` |
119
+ | `HlmP` / `hlmP` | `<p>` | `leading-7 [&:not(:first-child)]:mt-6` |
120
+ | `HlmBlockquote` / `hlmBlockquote` | `<blockquote>` | `mt-6 border-l-2 pl-6 italic` |
121
+ | `HlmCode` / `hlmCode` | `<code>` | `relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold` |
122
+ | `HlmUl` / `hlmUl` | `<ul>` | `my-6 ml-6 list-disc [&>li]:mt-2` |
123
+ | `HlmLarge` / `hlmLarge` | any | `text-lg font-semibold` |
124
+ | `HlmLead` / `hlmLead` | `<p>` | `text-xl text-muted-foreground` |
125
+ | `HlmMuted` / `hlmMuted` | `<p>` | `text-sm text-muted-foreground` |
126
+ | `HlmSmall` / `hlmSmall` | any | `text-sm font-medium leading-none` |
127
+
128
+ User `class` attributes merge (not clobbered) through the shared `classes()` helper.
129
+
130
+ ## Examples
131
+
132
+ ### 1. Full article page
133
+
134
+ ```ts
135
+ // demo-article.component.ts
136
+ import { Component } from '@angular/core';
137
+ import { HlmTypographyImports } from '@egose/shadcn-theme-ng/typography';
138
+
139
+ @Component({
140
+ selector: 'demo-article',
141
+ standalone: true,
142
+ imports: [...HlmTypographyImports],
143
+ template: `
144
+ <article class="max-w-2xl">
145
+ <h1 hlmH1>Taxing Laughter: The Joke Tax</h1>
146
+ <p hlmLead>Once upon a time, a king decided to tax every joke told in his kingdom.</p>
147
+ <h2 hlmH2>The King's Plan</h2>
148
+ <p hlmP>The king thought long and hard, and finally came up with a brilliant plan.</p>
149
+ <blockquote hlmBlockquote>"After all," he said, "everyone enjoys a good joke, so it's only fair."</blockquote>
150
+ <h3 hlmH3>The Joke Tax</h3>
151
+ <p hlmP>The king's subjects were not amused. They grumbled and complained.</p>
152
+ <ul hlmUl>
153
+ <li>1st level of puns: 5 gold coins</li>
154
+ <li>2nd level of jokes: 10 gold coins</li>
155
+ <li>3rd level of one-liners: 20 gold coins</li>
156
+ </ul>
157
+ </article>
158
+ `,
159
+ })
160
+ export class DemoArticle {}
161
+ ```
162
+
163
+ ### 2. Headings hierarchy (h1–h4)
164
+
165
+ ```ts
166
+ // demo-headings.component.ts
167
+ import { Component } from '@angular/core';
168
+ import { HlmTypographyImports } from '@egose/shadcn-theme-ng/typography';
169
+
170
+ @Component({
171
+ selector: 'demo-headings',
172
+ standalone: true,
173
+ imports: [...HlmTypographyImports],
174
+ template: `
175
+ <h1 hlmH1>Heading 1 — page title</h1>
176
+ <h2 hlmH2>Heading 2 — section (with bottom border)</h2>
177
+ <h3 hlmH3>Heading 3 — subsection</h3>
178
+ <h4 hlmH4>Heading 4 — detail header</h4>
179
+ `,
180
+ })
181
+ export class DemoHeadings {}
182
+ ```
183
+
184
+ Keep one `<h1>` per page and nest levels without skipping (h1→h2→h3).
185
+
186
+ ### 3. Lead, large, muted, small
187
+
188
+ ```ts
189
+ // demo-scale.component.ts
190
+ import { Component } from '@angular/core';
191
+ import { HlmTypographyImports } from '@egose/shadcn-theme-ng/typography';
192
+
193
+ @Component({
194
+ selector: 'demo-scale',
195
+ standalone: true,
196
+ imports: [...HlmTypographyImports],
197
+ template: `
198
+ <p hlmLead>A lead paragraph introduces the page — larger, muted.</p>
199
+ <p hlmP>Body copy with comfortable <code hlmCode>leading-7</code> rhythm.</p>
200
+ <div hlmLarge>Large: callouts, card titles, key figures.</div>
201
+ <p hlmMuted>Muted: timestamps, helper text, captions.</p>
202
+ <span hlmSmall>Small: labels, badges, table meta.</span>
203
+ `,
204
+ })
205
+ export class DemoScale {}
206
+ ```
207
+
208
+ ### 4. Inline code + blockquote
209
+
210
+ ```html
211
+ <p hlmP>Run <code hlmCode>ng build my-lib</code> to build, then ship the output.</p>
212
+ <blockquote hlmBlockquote>
213
+ "Design is the silent ambassador of your brand." — pair with a <cite>cite</cite> for attribution.
214
+ </blockquote>
215
+ ```
216
+
217
+ ### 5. Lists (bulleted + mixed content)
218
+
219
+ ```html
220
+ <ul hlmUl>
221
+ <li>Ship one library per component.</li>
222
+ <li>
223
+ Consume via subpath imports:
224
+ <code hlmCode>@egose/shadcn-theme-ng/button</code>
225
+ </li>
226
+ <li>Theme with CSS variables — no forks.</li>
227
+ </ul>
228
+ ```
229
+
230
+ ### 6. Advanced: reusing class constants in custom components
231
+
232
+ ```ts
233
+ // prose.component.ts
234
+ import { Component, input } from '@angular/core';
235
+ import { hlm } from '@egose/shadcn-theme-ng/utils';
236
+ import { hlmH2, hlmP } from '@egose/shadcn-theme-ng/typography';
237
+
238
+ @Component({
239
+ selector: 'app-prose-title',
240
+ standalone: true,
241
+ template: `<h2 [class]="classes()"><ng-content /></h2>`,
242
+ })
243
+ export class ProseTitle {
244
+ readonly tone = input<'default' | 'muted'>('default');
245
+
246
+ protected classes() {
247
+ return hlm(hlmH2, this.tone() === 'muted' && 'text-muted-foreground');
248
+ }
249
+ }
250
+
251
+ @Component({
252
+ selector: 'app-prose-body',
253
+ standalone: true,
254
+ template: `<p [class]="hlmP"><ng-content /></p>`,
255
+ })
256
+ export class ProseBody {
257
+ protected readonly hlmP = hlmP;
258
+ }
11
259
  ```
260
+
261
+ ## Accessibility notes
262
+
263
+ - Use real heading levels in order (`h1`→`h2`→`h3`); the directives style but never repair skipped levels — screen-reader navigation depends on the elements you choose.
264
+ - `HlmMuted`/`HlmSmall` reduce size/contrast — verify contrast ratios for body text (muted grays can fail on light backgrounds at small sizes).
265
+ - `<blockquote>` should contain the quote; add `<cite>`/attribution outside or within per your style guide.
266
+ - `<code hlmCode>` is inline; for multi-line samples use `<pre><code>` with your own overflow handling and a plaintext alternative when meaning depends on formatting.
267
+
268
+ ## Theming / CSS variables
269
+
270
+ Type styles key off `border-border`, `bg-muted`, and `text-muted-foreground` tokens, so they track your shadcn theme automatically. No per-component CSS variables.
271
+
272
+ ## Related subpaths
273
+
274
+ - `@egose/shadcn-theme-ng/card` — `CardTitle`/`CardDescription` surfaces that pair with type scale.
275
+ - `@egose/shadcn-theme-ng/table` — tabular content styled alongside prose.
276
+ - `@egose/shadcn-theme-ng/badge` / `@egose/shadcn-theme-ng/kbd` — inline semantics to mix into paragraphs.
277
+ - `@egose/shadcn-theme-ng/utils` — `hlm()` for composing the exported class constants.
package/utils/README.md CHANGED
@@ -1,3 +1,304 @@
1
- # Utils Subpath
1
+ # Utils (`@egose/shadcn-theme-ng/utils`)
2
2
 
3
- This project ships only as `@egose/shadcn-theme-ng/utils` or `@egose/shadcn-theme-ng-tw/utils`. See the [package README](../../README.md) for installation, compatibility, Tailwind variant, test, and release guidance. Do not publish this project directory independently.
3
+ Shared styling and setup helpers used by every component in this library — equivalent to the `cn()`/`lib/utils` module in [shadcn/ui](https://ui.shadcn.com/docs/installation). This subpath exports **`hlm()`** (the `clsx` + `tailwind-merge` class merger), **`classes()`** (the reactive host-class manager behind every `hlm*` directive), and **`provideSpartanHlm()`** (the app-level overlay provider). It has no components and no `*Imports`/`*Module` — import the functions directly. Notably, `classes()` is SSR-safe (no `MutationObserver` on the server), transition-flash-safe (suppresses CSS transitions on first apply), and teardown-safe (disconnects observers / cancels animation frames via `DestroyRef`).
4
+
5
+ > **Ships as:** `@egose/shadcn-theme-ng/utils` and `@egose/shadcn-theme-ng-tw/utils` (the `tw:`-prefixed Tailwind variant). See the [package README](../../README.md) for install steps, peer dependencies, Tailwind setup, and testing/release guidance. Do not publish this project directory independently.
6
+
7
+ ## Installation
8
+
9
+ ```bash
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
+ `clsx` and `tailwind-merge` arrive transitively. See the [package README](../../README.md) for the full peer-dependency table.
18
+
19
+ Register the environment provider once at bootstrap (required for overlay-based components):
20
+
21
+ ```ts
22
+ // app.config.ts
23
+ import { ApplicationConfig } from '@angular/core';
24
+ import { provideSpartanHlm } from '@egose/shadcn-theme-ng/utils';
25
+
26
+ export const appConfig: ApplicationConfig = {
27
+ providers: [provideSpartanHlm()],
28
+ };
29
+ ```
30
+
31
+ ## Imports
32
+
33
+ All public symbols are re-exported from `projects/utils/src/public-api.ts` (`./lib/utils` + `./lib/provide-spartan-hlm`):
34
+
35
+ ```ts
36
+ import { classes, hlm, provideSpartanHlm } from '@egose/shadcn-theme-ng/utils';
37
+ // tw variant: swap to '@egose/shadcn-theme-ng-tw/utils'
38
+ ```
39
+
40
+ | Symbol | Kind | Signature (real) | Description |
41
+ | ------------------- | -------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ |
42
+ | `hlm` | Function | `hlm(...inputs: ClassValue[]): string` | Merges class lists (`twMerge(clsx(inputs))`). |
43
+ | `classes` | Function | `classes(computed: () => ClassValue[] \| string, options?: ClassesOptions): void` | Reactively applies computed classes to the host element. |
44
+ | `provideSpartanHlm` | Function | `provideSpartanHlm(): EnvironmentProviders` | Provides `OVERLAY_DEFAULT_CONFIG` (`{ usePopover: false }`). |
45
+
46
+ `ClassesOptions` (interface, not exported from the barrel — structural shape):
47
+
48
+ ```ts
49
+ interface ClassesOptions {
50
+ elementRef?: ElementRef<HTMLElement>;
51
+ injector?: Injector;
52
+ }
53
+ ```
54
+
55
+ There is no `UtilsImports` / `UtilsModule` — nothing to add to `imports: [...]`.
56
+
57
+ ## Anatomy / Structure
58
+
59
+ ```ts
60
+ // 1. One-off merge (templates, custom components)
61
+ import { hlm } from '@egose/shadcn-theme-ng/utils';
62
+ const cls = hlm('px-4 py-2', condition && 'bg-primary', userClass);
63
+
64
+ // 2. Reactive host styling (directives/components)
65
+ import { Directive } from '@angular/core';
66
+ import { classes } from '@egose/shadcn-theme-ng/utils';
67
+
68
+ @Directive({ selector: '[myStyled]' })
69
+ export class MyStyled {
70
+ constructor() {
71
+ classes(() => 'rounded-md border bg-background');
72
+ }
73
+ }
74
+
75
+ // 3. App bootstrap (once)
76
+ import { provideSpartanHlm } from '@egose/shadcn-theme-ng/utils';
77
+ export const appConfig: ApplicationConfig = { providers: [provideSpartanHlm()] };
78
+ ```
79
+
80
+ ## API reference
81
+
82
+ ### `hlm(...inputs: ClassValue[]): string`
83
+
84
+ Merges any `clsx`-compatible inputs (`string | string[] | Record<string, boolean> | false | null | undefined`, nested) and resolves Tailwind conflicts with `twMerge`. Later arguments win on conflict.
85
+
86
+ | Call | Result |
87
+ | ------------------------------------------------------- | ---------------------------- |
88
+ | `hlm('px-2 px-4')` | `'px-4'` (conflict resolved) |
89
+ | `hlm('text-sm', isActive && 'text-primary', userClass)` | conditional merge |
90
+ | `hlm(['rounded', { 'bg-muted': selected }])` | array/object forms |
91
+
92
+ Backed by `clsx` + `tailwind-merge`; string inputs are cached (up to 1000 entries) internally.
93
+
94
+ ### `classes(computed, options?): void`
95
+
96
+ Runs in an injection context (`runInInjectionContext`) and wires the host element to a reactive class computation. Must be called in a constructor (or any injection context) of a directive/component.
97
+
98
+ - `computed: () => ClassValue[] | string` — re-evaluated in an `effect()`; any signals read inside are tracked, so class updates are automatic.
99
+ - `options.elementRef?: ElementRef<HTMLElement>` — defaults to the injected host `ElementRef`. Pass explicitly for out-of-band elements (tests, portals, iframes).
100
+ - `options.injector?: Injector` — defaults to the current injector. Pass explicitly when calling outside the construction context (e.g. per-document setup in tests).
101
+
102
+ Behavioral guarantees (verified by `utils.spec.ts`):
103
+
104
+ - Base-class preservation: pre-existing `class` attribute values and externally added classes are kept and re-merged (not wiped) via a per-document `MutationObserver`.
105
+ - Scoped observation: one shared observer per `Document` (isolated per iframe document); unrelated elements never trigger callbacks.
106
+ - Transition suppression: CSS `transition` is forced to `none !important` on first apply, then restored on the next animation frame — prevents initial style flashes.
107
+ - Teardown: `DestroyRef.onDestroy` removes the source, cancels pending RAFs, restores transitions, disconnects the observer when the last managed element is destroyed, and cleans per-document state.
108
+ - SSR-safe: no `MutationObserver` is created when `PLATFORM_ID` is `'server'`.
109
+
110
+ ### `provideSpartanHlm(): EnvironmentProviders`
111
+
112
+ ```ts
113
+ export function provideSpartanHlm(): EnvironmentProviders {
114
+ return makeEnvironmentProviders([{ provide: OVERLAY_DEFAULT_CONFIG, useValue: { usePopover: false } }]);
115
+ }
116
+ ```
117
+
118
+ Call once in `appConfig.providers`. Forces CDK overlays (tooltip, popover, dropdown, dialog, …) to use the classic overlay strategy instead of the native `popover` API.
119
+
120
+ ## Examples
121
+
122
+ ### 1. `hlm()` — conditional class merging
123
+
124
+ ```ts
125
+ // demo-merge.component.ts
126
+ import { Component, input } from '@angular/core';
127
+ import { hlm } from '@egose/shadcn-theme-ng/utils';
128
+
129
+ @Component({
130
+ selector: 'demo-merge',
131
+ standalone: true,
132
+ template: `<div [class]="cls()">Hello</div>`,
133
+ })
134
+ export class DemoMerge {
135
+ readonly active = input(false);
136
+ readonly userClass = input<string>('', { alias: 'class' });
137
+
138
+ protected cls() {
139
+ return hlm(
140
+ 'rounded-md border px-4 py-2 text-sm',
141
+ this.active() && 'border-primary bg-primary/10',
142
+ this.userClass(),
143
+ );
144
+ }
145
+ }
146
+ ```
147
+
148
+ ```html
149
+ <demo-merge [active]="true" class="shadow-lg" />
150
+ <!-- later utilities win: hlm('px-2 px-4') === 'px-4' -->
151
+ ```
152
+
153
+ ### 2. `hlm()` — object / array forms
154
+
155
+ ```ts
156
+ import { hlm } from '@egose/shadcn-theme-ng/utils';
157
+
158
+ const cls = hlm(
159
+ ['inline-flex items-center gap-2', 'rounded-md'],
160
+ { 'bg-primary text-primary-foreground': true, 'opacity-50': false },
161
+ null,
162
+ undefined,
163
+ );
164
+ ```
165
+
166
+ ### 3. `classes()` — custom styled directive (signal-reactive)
167
+
168
+ ```ts
169
+ // status-dot.directive.ts
170
+ import { Directive, input } from '@angular/core';
171
+ import { classes } from '@egose/shadcn-theme-ng/utils';
172
+
173
+ @Directive({ selector: '[statusDot]' })
174
+ export class StatusDot {
175
+ readonly tone = input<'ok' | 'warn' | 'bad'>('ok');
176
+
177
+ constructor() {
178
+ classes(() => [
179
+ 'inline-block size-2 rounded-full',
180
+ this.tone() === 'ok' && 'bg-emerald-500',
181
+ this.tone() === 'warn' && 'bg-amber-500',
182
+ this.tone() === 'bad' && 'bg-destructive',
183
+ ]);
184
+ }
185
+ }
186
+ ```
187
+
188
+ ```ts
189
+ // usage
190
+ import { Component } from '@angular/core';
191
+
192
+ @Component({
193
+ selector: 'demo-dot',
194
+ standalone: true,
195
+ imports: [StatusDot],
196
+ template: `
197
+ <span statusDot tone="ok"></span>
198
+ <span statusDot tone="bad" class="ml-2"></span>
199
+ `,
200
+ })
201
+ export class DemoDot {}
202
+ ```
203
+
204
+ The trailing `class="ml-2"` is preserved — `classes()` merges base classes instead of overwriting them.
205
+
206
+ ### 4. `classes()` — variant-driven component (cva + signals)
207
+
208
+ ```ts
209
+ // pill.component.ts
210
+ import { Component, input } from '@angular/core';
211
+ import { cva, type VariantProps } from 'class-variance-authority';
212
+ import { classes } from '@egose/shadcn-theme-ng/utils';
213
+
214
+ export const pillVariants = cva('inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium', {
215
+ variants: { tone: { default: 'bg-muted', brand: 'bg-primary text-primary-foreground' } },
216
+ defaultVariants: { tone: 'default' },
217
+ });
218
+ export type PillTones = VariantProps<typeof pillVariants>;
219
+
220
+ @Component({
221
+ selector: 'demo-pill',
222
+ standalone: true,
223
+ template: `<ng-content />`,
224
+ })
225
+ export class DemoPill {
226
+ readonly tone = input<PillTones['tone']>('default');
227
+
228
+ constructor() {
229
+ classes(() => pillVariants({ tone: this.tone() }));
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### 5. `provideSpartanHlm()` — app bootstrap + overlay consumers
235
+
236
+ ```ts
237
+ // app.config.ts
238
+ import { ApplicationConfig } from '@angular/core';
239
+ import { provideAnimations } from '@angular/platform-browser/animations';
240
+ import { provideSpartanHlm } from '@egose/shadcn-theme-ng/utils';
241
+
242
+ export const appConfig: ApplicationConfig = {
243
+ providers: [provideAnimations(), provideSpartanHlm()],
244
+ };
245
+ ```
246
+
247
+ Any overlay component (tooltip, popover, dropdown-menu, dialog, hover-card, …) then uses the supported overlay strategy without per-component configuration:
248
+
249
+ ```ts
250
+ // demo-overlay.component.ts
251
+ import { Component } from '@angular/core';
252
+ import { HlmTooltipImports } from '@egose/shadcn-theme-ng/tooltip';
253
+ import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
254
+
255
+ @Component({
256
+ selector: 'demo-overlay',
257
+ standalone: true,
258
+ imports: [...HlmTooltipImports, ...HlmButtonImports],
259
+ template: `<button hlmBtn [hlmTooltip]="'Overlay-styled hint'">Hover</button>`,
260
+ })
261
+ export class DemoOverlay {}
262
+ ```
263
+
264
+ ### 6. Advanced: explicit `elementRef` / multi-document usage
265
+
266
+ ```ts
267
+ // managed-element.service.ts (e.g. portals, iframes, tests)
268
+ import { DOCUMENT, Injectable, inject, Injector, ElementRef } from '@angular/core';
269
+ import { classes } from '@egose/shadcn-theme-ng/utils';
270
+
271
+ @Injectable({ providedIn: 'root' })
272
+ export class ManagedElementService {
273
+ private readonly injector = inject(Injector);
274
+ private readonly document = inject(DOCUMENT);
275
+
276
+ styleDetached(element: HTMLElement, extra: string) {
277
+ // Drives classes on an element outside the current host,
278
+ // tracked against this injector's DestroyRef lifecycle.
279
+ classes(() => ['rounded-md border', extra], {
280
+ elementRef: new ElementRef(element),
281
+ injector: this.injector,
282
+ });
283
+ }
284
+ }
285
+ ```
286
+
287
+ Per-document observer isolation means elements in different `Document`s (iframes) each get their own observer lifecycle — destroying the injector disconnects only its own document's observer.
288
+
289
+ ## Accessibility notes
290
+
291
+ - `hlm()`/`classes()` are styling-only — they confer no semantics. Pair visual treatments with real roles, labels, and focus management from the component subpaths.
292
+ - When toggling state classes (e.g. `bg-destructive` for errors), always also surface a text cue (`hlm-error`, `aria-invalid`, `aria-describedby`) — color alone is not perceivable.
293
+ - Transition suppression in `classes()` avoids first-paint flashes but does not disable motion for users with `prefers-reduced-motion` — respect that media query in your own animation utilities.
294
+
295
+ ## Theming / CSS variables
296
+
297
+ No theme of its own — `hlm()`/`classes()` are token-agnostic mergers. They resolve whatever utilities you pass (including shadcn theme tokens like `bg-background`, `text-muted-foreground`, `border-input`), so output follows your configured theme automatically.
298
+
299
+ ## Related subpaths
300
+
301
+ - Every component subpath (`button`, `dialog`, `table`, …) — all consume `hlm()`/`classes()` internally; import this subpath when building custom shadcn-styled directives.
302
+ - `@egose/shadcn-theme-ng/tooltip` — example consumer of `hlm()` for arrow/content classes (`tooltipPositionVariants`).
303
+ - `@egose/shadcn-theme-ng/toggle` / `@egose/shadcn-theme-ng/toggle-group` — example consumers of the cva + `classes()` pattern.
304
+ - `@egose/shadcn-theme-ng/typography` — example consumer re-exporting class constants composed with `hlm()`.