yukit-web 0.0.1

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 (196) hide show
  1. package/.npm/_logs/2026-08-20T08_40_55_706Z-debug-0.log +38 -0
  2. package/.npm/_logs/2026-08-20T08_40_56_280Z-debug-0.log +14 -0
  3. package/README.md +1465 -0
  4. package/fesm2022/yukit-web-helm-accordion.mjs +141 -0
  5. package/fesm2022/yukit-web-helm-accordion.mjs.map +1 -0
  6. package/fesm2022/yukit-web-helm-alert-dialog.mjs +250 -0
  7. package/fesm2022/yukit-web-helm-alert-dialog.mjs.map +1 -0
  8. package/fesm2022/yukit-web-helm-alert.mjs +94 -0
  9. package/fesm2022/yukit-web-helm-alert.mjs.map +1 -0
  10. package/fesm2022/yukit-web-helm-aspect-ratio.mjs +47 -0
  11. package/fesm2022/yukit-web-helm-aspect-ratio.mjs.map +1 -0
  12. package/fesm2022/yukit-web-helm-attachment.mjs +211 -0
  13. package/fesm2022/yukit-web-helm-attachment.mjs.map +1 -0
  14. package/fesm2022/yukit-web-helm-autocomplete.mjs +361 -0
  15. package/fesm2022/yukit-web-helm-autocomplete.mjs.map +1 -0
  16. package/fesm2022/yukit-web-helm-avatar.mjs +152 -0
  17. package/fesm2022/yukit-web-helm-avatar.mjs.map +1 -0
  18. package/fesm2022/yukit-web-helm-badge.mjs +47 -0
  19. package/fesm2022/yukit-web-helm-badge.mjs.map +1 -0
  20. package/fesm2022/yukit-web-helm-breadcrumb.mjs +196 -0
  21. package/fesm2022/yukit-web-helm-breadcrumb.mjs.map +1 -0
  22. package/fesm2022/yukit-web-helm-bubble.mjs +121 -0
  23. package/fesm2022/yukit-web-helm-bubble.mjs.map +1 -0
  24. package/fesm2022/yukit-web-helm-button-group.mjs +86 -0
  25. package/fesm2022/yukit-web-helm-button-group.mjs.map +1 -0
  26. package/fesm2022/yukit-web-helm-button.mjs +77 -0
  27. package/fesm2022/yukit-web-helm-button.mjs.map +1 -0
  28. package/fesm2022/yukit-web-helm-calendar.mjs +937 -0
  29. package/fesm2022/yukit-web-helm-calendar.mjs.map +1 -0
  30. package/fesm2022/yukit-web-helm-card.mjs +141 -0
  31. package/fesm2022/yukit-web-helm-card.mjs.map +1 -0
  32. package/fesm2022/yukit-web-helm-carousel.mjs +270 -0
  33. package/fesm2022/yukit-web-helm-carousel.mjs.map +1 -0
  34. package/fesm2022/yukit-web-helm-checkbox.mjs +147 -0
  35. package/fesm2022/yukit-web-helm-checkbox.mjs.map +1 -0
  36. package/fesm2022/yukit-web-helm-collapsible.mjs +62 -0
  37. package/fesm2022/yukit-web-helm-collapsible.mjs.map +1 -0
  38. package/fesm2022/yukit-web-helm-combobox.mjs +624 -0
  39. package/fesm2022/yukit-web-helm-combobox.mjs.map +1 -0
  40. package/fesm2022/yukit-web-helm-command.mjs +315 -0
  41. package/fesm2022/yukit-web-helm-command.mjs.map +1 -0
  42. package/fesm2022/yukit-web-helm-context-menu.mjs +71 -0
  43. package/fesm2022/yukit-web-helm-context-menu.mjs.map +1 -0
  44. package/fesm2022/yukit-web-helm-date-picker.mjs +1517 -0
  45. package/fesm2022/yukit-web-helm-date-picker.mjs.map +1 -0
  46. package/fesm2022/yukit-web-helm-dialog.mjs +271 -0
  47. package/fesm2022/yukit-web-helm-dialog.mjs.map +1 -0
  48. package/fesm2022/yukit-web-helm-drawer.mjs +233 -0
  49. package/fesm2022/yukit-web-helm-drawer.mjs.map +1 -0
  50. package/fesm2022/yukit-web-helm-dropdown-menu.mjs +508 -0
  51. package/fesm2022/yukit-web-helm-dropdown-menu.mjs.map +1 -0
  52. package/fesm2022/yukit-web-helm-empty.mjs +125 -0
  53. package/fesm2022/yukit-web-helm-empty.mjs.map +1 -0
  54. package/fesm2022/yukit-web-helm-field.mjs +325 -0
  55. package/fesm2022/yukit-web-helm-field.mjs.map +1 -0
  56. package/fesm2022/yukit-web-helm-hover-card.mjs +102 -0
  57. package/fesm2022/yukit-web-helm-hover-card.mjs.map +1 -0
  58. package/fesm2022/yukit-web-helm-input-group.mjs +171 -0
  59. package/fesm2022/yukit-web-helm-input-group.mjs.map +1 -0
  60. package/fesm2022/yukit-web-helm-input-otp.mjs +127 -0
  61. package/fesm2022/yukit-web-helm-input-otp.mjs.map +1 -0
  62. package/fesm2022/yukit-web-helm-input.mjs +32 -0
  63. package/fesm2022/yukit-web-helm-input.mjs.map +1 -0
  64. package/fesm2022/yukit-web-helm-item.mjs +242 -0
  65. package/fesm2022/yukit-web-helm-item.mjs.map +1 -0
  66. package/fesm2022/yukit-web-helm-kbd.mjs +46 -0
  67. package/fesm2022/yukit-web-helm-kbd.mjs.map +1 -0
  68. package/fesm2022/yukit-web-helm-label.mjs +30 -0
  69. package/fesm2022/yukit-web-helm-label.mjs.map +1 -0
  70. package/fesm2022/yukit-web-helm-marker.mjs +77 -0
  71. package/fesm2022/yukit-web-helm-marker.mjs.map +1 -0
  72. package/fesm2022/yukit-web-helm-menubar.mjs +90 -0
  73. package/fesm2022/yukit-web-helm-menubar.mjs.map +1 -0
  74. package/fesm2022/yukit-web-helm-message.mjs +115 -0
  75. package/fesm2022/yukit-web-helm-message.mjs.map +1 -0
  76. package/fesm2022/yukit-web-helm-native-select.mjs +180 -0
  77. package/fesm2022/yukit-web-helm-native-select.mjs.map +1 -0
  78. package/fesm2022/yukit-web-helm-navigation-menu.mjs +173 -0
  79. package/fesm2022/yukit-web-helm-navigation-menu.mjs.map +1 -0
  80. package/fesm2022/yukit-web-helm-pagination.mjs +745 -0
  81. package/fesm2022/yukit-web-helm-pagination.mjs.map +1 -0
  82. package/fesm2022/yukit-web-helm-popover.mjs +136 -0
  83. package/fesm2022/yukit-web-helm-popover.mjs.map +1 -0
  84. package/fesm2022/yukit-web-helm-progress.mjs +60 -0
  85. package/fesm2022/yukit-web-helm-progress.mjs.map +1 -0
  86. package/fesm2022/yukit-web-helm-radio-group.mjs +185 -0
  87. package/fesm2022/yukit-web-helm-radio-group.mjs.map +1 -0
  88. package/fesm2022/yukit-web-helm-resizable.mjs +98 -0
  89. package/fesm2022/yukit-web-helm-resizable.mjs.map +1 -0
  90. package/fesm2022/yukit-web-helm-scroll-area.mjs +36 -0
  91. package/fesm2022/yukit-web-helm-scroll-area.mjs.map +1 -0
  92. package/fesm2022/yukit-web-helm-select.mjs +412 -0
  93. package/fesm2022/yukit-web-helm-select.mjs.map +1 -0
  94. package/fesm2022/yukit-web-helm-separator.mjs +33 -0
  95. package/fesm2022/yukit-web-helm-separator.mjs.map +1 -0
  96. package/fesm2022/yukit-web-helm-sheet.mjs +254 -0
  97. package/fesm2022/yukit-web-helm-sheet.mjs.map +1 -0
  98. package/fesm2022/yukit-web-helm-sidebar.mjs +857 -0
  99. package/fesm2022/yukit-web-helm-sidebar.mjs.map +1 -0
  100. package/fesm2022/yukit-web-helm-skeleton.mjs +29 -0
  101. package/fesm2022/yukit-web-helm-skeleton.mjs.map +1 -0
  102. package/fesm2022/yukit-web-helm-slider.mjs +123 -0
  103. package/fesm2022/yukit-web-helm-slider.mjs.map +1 -0
  104. package/fesm2022/yukit-web-helm-sonner.mjs +126 -0
  105. package/fesm2022/yukit-web-helm-sonner.mjs.map +1 -0
  106. package/fesm2022/yukit-web-helm-spinner.mjs +48 -0
  107. package/fesm2022/yukit-web-helm-spinner.mjs.map +1 -0
  108. package/fesm2022/yukit-web-helm-switch.mjs +126 -0
  109. package/fesm2022/yukit-web-helm-switch.mjs.map +1 -0
  110. package/fesm2022/yukit-web-helm-table.mjs +180 -0
  111. package/fesm2022/yukit-web-helm-table.mjs.map +1 -0
  112. package/fesm2022/yukit-web-helm-tabs.mjs +251 -0
  113. package/fesm2022/yukit-web-helm-tabs.mjs.map +1 -0
  114. package/fesm2022/yukit-web-helm-textarea.mjs +32 -0
  115. package/fesm2022/yukit-web-helm-textarea.mjs.map +1 -0
  116. package/fesm2022/yukit-web-helm-toggle-group.mjs +95 -0
  117. package/fesm2022/yukit-web-helm-toggle-group.mjs.map +1 -0
  118. package/fesm2022/yukit-web-helm-toggle.mjs +61 -0
  119. package/fesm2022/yukit-web-helm-toggle.mjs.map +1 -0
  120. package/fesm2022/yukit-web-helm-tooltip.mjs +57 -0
  121. package/fesm2022/yukit-web-helm-tooltip.mjs.map +1 -0
  122. package/fesm2022/yukit-web-helm-typography.mjs +205 -0
  123. package/fesm2022/yukit-web-helm-typography.mjs.map +1 -0
  124. package/fesm2022/yukit-web-helm-utils.mjs +272 -0
  125. package/fesm2022/yukit-web-helm-utils.mjs.map +1 -0
  126. package/fesm2022/yukit-web.mjs +3592 -0
  127. package/fesm2022/yukit-web.mjs.map +1 -0
  128. package/package.json +291 -0
  129. package/src/lib/theme/spartan-overwrites/accordion.css +4 -0
  130. package/src/lib/theme/theme.css +161 -0
  131. package/src/lib/theme/tokens/color.css +356 -0
  132. package/src/lib/theme/tokens/component-colors.css +167 -0
  133. package/src/lib/theme/tokens/radius.css +18 -0
  134. package/src/lib/theme/tokens/typography.css +201 -0
  135. package/types/yukit-web-helm-accordion.d.ts +32 -0
  136. package/types/yukit-web-helm-alert-dialog.d.ts +85 -0
  137. package/types/yukit-web-helm-alert.d.ts +37 -0
  138. package/types/yukit-web-helm-aspect-ratio.d.ts +16 -0
  139. package/types/yukit-web-helm-attachment.d.ts +80 -0
  140. package/types/yukit-web-helm-autocomplete.d.ts +91 -0
  141. package/types/yukit-web-helm-avatar.d.ts +45 -0
  142. package/types/yukit-web-helm-badge.d.ts +19 -0
  143. package/types/yukit-web-helm-breadcrumb.d.ts +52 -0
  144. package/types/yukit-web-helm-bubble.d.ts +46 -0
  145. package/types/yukit-web-helm-button-group.d.ts +25 -0
  146. package/types/yukit-web-helm-button.d.ts +33 -0
  147. package/types/yukit-web-helm-calendar.d.ts +89 -0
  148. package/types/yukit-web-helm-card.d.ts +49 -0
  149. package/types/yukit-web-helm-carousel.d.ts +79 -0
  150. package/types/yukit-web-helm-checkbox.d.ts +60 -0
  151. package/types/yukit-web-helm-collapsible.d.ts +22 -0
  152. package/types/yukit-web-helm-combobox.d.ts +150 -0
  153. package/types/yukit-web-helm-command.d.ts +84 -0
  154. package/types/yukit-web-helm-context-menu.d.ts +29 -0
  155. package/types/yukit-web-helm-date-picker.d.ts +574 -0
  156. package/types/yukit-web-helm-dialog.d.ts +90 -0
  157. package/types/yukit-web-helm-drawer.d.ts +71 -0
  158. package/types/yukit-web-helm-dropdown-menu.d.ts +171 -0
  159. package/types/yukit-web-helm-empty.d.ts +49 -0
  160. package/types/yukit-web-helm-field.d.ts +102 -0
  161. package/types/yukit-web-helm-hover-card.d.ts +31 -0
  162. package/types/yukit-web-helm-input-group.d.ts +47 -0
  163. package/types/yukit-web-helm-input-otp.d.ts +40 -0
  164. package/types/yukit-web-helm-input.d.ts +13 -0
  165. package/types/yukit-web-helm-item.d.ts +96 -0
  166. package/types/yukit-web-helm-kbd.d.ts +17 -0
  167. package/types/yukit-web-helm-label.d.ts +12 -0
  168. package/types/yukit-web-helm-marker.d.ts +31 -0
  169. package/types/yukit-web-helm-menubar.d.ts +35 -0
  170. package/types/yukit-web-helm-message.d.ts +44 -0
  171. package/types/yukit-web-helm-native-select.d.ts +65 -0
  172. package/types/yukit-web-helm-navigation-menu.d.ts +49 -0
  173. package/types/yukit-web-helm-pagination.d.ts +187 -0
  174. package/types/yukit-web-helm-popover.d.ts +49 -0
  175. package/types/yukit-web-helm-progress.d.ts +22 -0
  176. package/types/yukit-web-helm-radio-group.d.ts +66 -0
  177. package/types/yukit-web-helm-resizable.d.ts +27 -0
  178. package/types/yukit-web-helm-scroll-area.d.ts +11 -0
  179. package/types/yukit-web-helm-select.d.ts +114 -0
  180. package/types/yukit-web-helm-separator.d.ts +13 -0
  181. package/types/yukit-web-helm-sheet.d.ts +75 -0
  182. package/types/yukit-web-helm-sidebar.d.ts +232 -0
  183. package/types/yukit-web-helm-skeleton.d.ts +11 -0
  184. package/types/yukit-web-helm-slider.d.ts +14 -0
  185. package/types/yukit-web-helm-sonner.d.ts +52 -0
  186. package/types/yukit-web-helm-spinner.d.ts +18 -0
  187. package/types/yukit-web-helm-switch.d.ts +54 -0
  188. package/types/yukit-web-helm-table.d.ts +82 -0
  189. package/types/yukit-web-helm-tabs.d.ts +66 -0
  190. package/types/yukit-web-helm-textarea.d.ts +13 -0
  191. package/types/yukit-web-helm-toggle-group.d.ts +34 -0
  192. package/types/yukit-web-helm-toggle.d.ts +22 -0
  193. package/types/yukit-web-helm-tooltip.d.ts +17 -0
  194. package/types/yukit-web-helm-typography.d.ts +89 -0
  195. package/types/yukit-web-helm-utils.d.ts +44 -0
  196. package/types/yukit-web.d.ts +2426 -0
package/README.md ADDED
@@ -0,0 +1,1465 @@
1
+ # yukit-web
2
+
3
+ Angular UI component library for Yurest web applications. Built on [Spartan](https://spartan.ng)
4
+ (Tailwind CSS v4 + `@spartan-ng/brain`) and themed with the Yurest design tokens.
5
+
6
+ ## Table of contents
7
+
8
+ - [Requirements](#requirements)
9
+ - [Installation](#installation)
10
+ - [Setup](#setup)
11
+ - [Quick start](#quick-start)
12
+ - [Theming](#theming)
13
+ - [Conventions](#conventions)
14
+ - [Components](#components)
15
+ - [Utilities and services](#utilities-and-services)
16
+ - [Development](#development)
17
+
18
+ ## Requirements
19
+
20
+ | Dependency | Version | Notes |
21
+ |:------------------------------------------------------------------|:----------|:-----------------------------------|
22
+ | [Angular](https://angular.dev/) | 21+ | Standalone APIs, signal inputs |
23
+ | [Tailwind CSS](https://tailwindcss.com/) | v4 | Required — see [Setup](#setup) |
24
+ | [`@spartan-ng/brain`](https://spartan.ng) | ^1.3.0 | Headless primitives |
25
+ | [`@angular/cdk`](https://material.angular.dev/cdk/categories) | ^21.0.0 | Overlays, a11y |
26
+ | [`@ng-icons/core`](https://ng-icons.github.io/ng-icons/) + lucide | ^32.2.0 | Icons |
27
+ | [`@tanstack/angular-table`](https://tanstack.com/table) | ^9.0.0 | `<yuw-datatable>` |
28
+ | `embla-carousel-angular` | ^21.0.0 | Carousel primitives |
29
+ | `ngx-scrollbar` | ^19.1.5 | Scroll areas |
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install yukit-web \
35
+ @spartan-ng/brain @angular/cdk @tanstack/angular-table \
36
+ @ng-icons/core @ng-icons/lucide embla-carousel-angular ngx-scrollbar
37
+ ```
38
+
39
+ Plus the Tailwind toolchain, as dev dependencies:
40
+
41
+ ```bash
42
+ npm install -D tailwindcss @tailwindcss/postcss tw-animate-css
43
+ ```
44
+
45
+ `tw-animate-css` is required because `@spartan-ng/brain/hlm-tailwind-preset.css` imports it.
46
+
47
+ ## Setup
48
+
49
+ `yukit-web` does **not** ship compiled component CSS. Its templates carry Tailwind utility classes,
50
+ and your Tailwind build is what turns them into real styles. Two things have to be true in your
51
+ app, or the components render unstyled.
52
+
53
+ ### 1. Set up `styles.css`
54
+
55
+ ```css
56
+ @layer theme, base, components, utilities;
57
+
58
+ @import "tailwindcss";
59
+ @import "@spartan-ng/brain/hlm-tailwind-preset.css";
60
+ @import "yukit-web/theme.css";
61
+
62
+ @source "../node_modules/yukit-web";
63
+ ```
64
+
65
+ Order matters:
66
+
67
+ | Line | Why |
68
+ |:------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------|
69
+ | `@layer …` | Fixes layer precedence before anything registers into a layer. |
70
+ | `@import "tailwindcss"` | Must come first — everything below extends it. |
71
+ | Spartan preset | Maps token *names* (`--primary`, `--radius`, …) onto Tailwind utilities and registers Spartan's variants (`data-open`, `data-checked`, …). |
72
+ | `yukit-web/theme.css` | Supplies the Yurest token *values*. Without the preset above it, the custom properties exist but no utilities are generated. |
73
+ | `@source` | **Required.** Tailwind only generates the classes it can see. `yukit-web`'s classes live inside the published package, which Tailwind does not scan by default. |
74
+
75
+ Adjust the `@source` path so it resolves from the file it is written in — the example assumes
76
+ `src/styles.css` with `node_modules` one level up.
77
+
78
+ **If you skip `@source`, the components render with no styling at all.** That is the expected
79
+ failure mode, not a bug: nothing is missing from the package, Tailwind simply never saw it.
80
+ `node_modules` being gitignored does not prevent the scan — an explicit `@source` overrides that.
81
+
82
+ ### 2. Enable the Tailwind PostCSS plugin
83
+
84
+ `.postcssrc.json` at the project root:
85
+
86
+ ```json
87
+ {
88
+ "plugins": {
89
+ "@tailwindcss/postcss": {}
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### 3. Icons
95
+
96
+ Components register the icons they need themselves — nothing to configure for `<yuw-badge>`,
97
+ `<yuw-dialog>`, `<yuw-pagination>` and friends to render their own glyphs.
98
+
99
+ You only need `provideIcons` for icons **you** project into a component (`[yuwAlertIcon]`,
100
+ `[yuwItemMedia]`, a button's content, `<yuw-spinner icon="…">`):
101
+
102
+ ```ts
103
+ import { NgIcon, provideIcons } from '@ng-icons/core';
104
+ import { lucideMail, lucidePlus } from '@ng-icons/lucide';
105
+
106
+ @Component({
107
+ imports: [NgIcon, YuwButtonComponent],
108
+ providers: [provideIcons({ lucideMail, lucidePlus })],
109
+ template: `<yuw-button><ng-icon name="lucidePlus" /> New</yuw-button>`,
110
+ })
111
+ export class ExampleComponent {}
112
+ ```
113
+
114
+ ## Quick start
115
+
116
+ Every export is standalone — import the component classes you use directly.
117
+
118
+ ```ts
119
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
120
+ import { YuwButtonComponent, YuwInputComponent, yuwToast, YuwToasterComponent } from 'yukit-web';
121
+
122
+ @Component({
123
+ selector: 'app-login',
124
+ imports: [YuwInputComponent, YuwButtonComponent, YuwToasterComponent],
125
+ template: `
126
+ <yuw-input [(value)]="email" type="email" placeholder="you@yurest.com" />
127
+ <yuw-button color="primary" (clicked)="submit()">Sign in</yuw-button>
128
+
129
+ <yuw-toaster />
130
+ `,
131
+ changeDetection: ChangeDetectionStrategy.OnPush,
132
+ })
133
+ export class LoginComponent {
134
+ protected readonly email = signal('');
135
+
136
+ protected submit(): void {
137
+ yuwToast.success('Welcome back');
138
+ }
139
+ }
140
+ ```
141
+
142
+ ## Theming
143
+
144
+ `yukit-web/theme.css` is the only stylesheet the package exposes. It defines the Yurest palette in
145
+ `oklch` under the token names Spartan expects, so `bg-primary`, `text-muted-foreground`,
146
+ `border-border` and `rounded-lg` resolve to Yurest's design system instead of Spartan's default
147
+ neutral gray.
148
+
149
+ ### Extra semantic tokens
150
+
151
+ It also adds Yurest-only semantic colors that Spartan has no equivalent for, registered so they
152
+ behave like any other Tailwind color:
153
+
154
+ | Token | Utilities |
155
+ |:-------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------|
156
+ | `--success` / `--success-foreground` | `bg-success`, `text-success-foreground`, … |
157
+ | `--warning` / `--warning-foreground` | `bg-warning`, `text-warning-foreground`, … |
158
+ | `--info` / `--info-foreground` | `bg-info`, `text-info-foreground`, … |
159
+ | `--destructive-foreground` | `text-destructive-foreground` — Spartan defines only `--destructive` and hardcodes `text-white`; Yurest names a contrast color, so the pair is completed. |
160
+
161
+ To override a token, redefine it in your own `:root` *after* importing the theme.
162
+
163
+ ### The `color` input
164
+
165
+ Components that carry brand color (`<yuw-button>`, `<yuw-badge>`, `<yuw-pagination>`) accept a
166
+ `color` input typed as `YuwColorDto`:
167
+
168
+ ```ts
169
+ type YuwColorDto =
170
+ | 'primary' | 'secondary' | 'destructive' | 'success'
171
+ | 'warning' | 'info' | 'muted' | 'violet';
172
+ ```
173
+
174
+ It sets `data-yuw-color` on the host, and the theme remaps the Spartan semantic variables
175
+ (`--primary`, `--secondary`, `--border`, …) for that subtree — so the whole component recolors
176
+ consistently instead of one class being patched.
177
+
178
+ ```html
179
+ <yuw-button color="success" variant="outline">Approve</yuw-button>
180
+ <yuw-badge color="warning" dot />
181
+ ```
182
+
183
+ ### Dark mode
184
+
185
+ Dark mode is supported via a `.dark` class on a root element, matching Tailwind v4's default dark
186
+ variant. The consuming app toggles it:
187
+
188
+ ```ts
189
+ document.documentElement.classList.toggle('dark');
190
+ ```
191
+
192
+ Note that `<yuw-toaster>` defaults to `theme="light"` — pass `theme="dark"` or `theme="system"`
193
+ explicitly in apps that render dark.
194
+
195
+ ## Conventions
196
+
197
+ A few rules hold across the whole library. Knowing them removes most of the per-component surprises.
198
+
199
+ **Text is always yours.** No component hardcodes a translatable string. Anything a user reads —
200
+ labels, empty-state messages, accessible names for icon-only controls — is an input, often a
201
+ *required* one (`ariaLabel`, `previousLabel`, `emptyText`, `label`). Pass it already translated.
202
+
203
+ **Presence implies the feature.** Instead of a `removable` flag plus a `removeLabel` string, there
204
+ is one input: set `removeLabel` and the remove control appears, omit it and there is nothing to
205
+ remove. The same shape applies to `clearAriaLabel` (search input, combobox), `closeLabel` (dialog,
206
+ sheet) and `revealAriaLabel` (password input).
207
+
208
+ **Two-way state uses `model()`.** `[(value)]`, `[(open)]`, `[(checked)]`, `[(page)]`,
209
+ `[(activeTab)]` are all two-way bindable, and each also emits a `…Change` output you can listen to
210
+ one-way.
211
+
212
+ **Class passthrough.** Components whose host is `display: contents` accept your classes on a
213
+ dedicated input so they actually reach the rendered element — `class` on most components,
214
+ `panelClass` on overlays, `tableClass` on the datatable. They are merged **last** through
215
+ `tailwind-merge`, so your class always beats the component default without `!important`.
216
+
217
+ **Forms.** `<yuw-input>`, `<yuw-textarea>`, `<yuw-search-input>`, `<yuw-password-input>`,
218
+ `<yuw-input-otp>`, `<yuw-checkbox>`, `<yuw-switch>`, `<yuw-radio-group>`, `<yuw-combobox>` and
219
+ `<yuw-datepicker>` implement `ControlValueAccessor`:
220
+
221
+ ```html
222
+ <form [formGroup]="form">
223
+ <label yuwLabel for="email">Email</label>
224
+ <yuw-input inputId="email" formControlName="email" type="email" [invalid]="emailInvalid()" />
225
+
226
+ <yuw-checkbox formControlName="terms">Accept terms</yuw-checkbox>
227
+ </form>
228
+ ```
229
+
230
+ `disabled` from the forms API (`control.disable()`) and the `disabled` input are OR'd together, so
231
+ neither source silently overrides the other.
232
+
233
+ ## Components
234
+
235
+ | Component | Selector | Import |
236
+ |:--------------------------------------|:----------------------------------------|:----------------------------------------------|
237
+ | **Actions** | | |
238
+ | [Button](#button) | `yuw-button` | `YuwButtonComponent` |
239
+ | **Form controls** | | |
240
+ | [Input](#input) | `yuw-input` | `YuwInputComponent` |
241
+ | [Textarea](#textarea) | `yuw-textarea` | `YuwTextareaComponent` |
242
+ | [Search input](#search-input) | `yuw-search-input` | `YuwSearchInputComponent` |
243
+ | [Password input](#password-input) | `yuw-password-input` | `YuwPasswordInputComponent` |
244
+ | [Input OTP](#input-otp) | `yuw-input-otp` | `YuwInputOtpComponent` |
245
+ | [Label](#label) | `label[yuwLabel]` | `YuwLabel` |
246
+ | [Checkbox](#checkbox) | `yuw-checkbox` | `YuwCheckboxComponent` |
247
+ | [Switch](#switch) | `yuw-switch` | `YuwSwitchComponent` |
248
+ | [Radio group](#radio-group) | `yuw-radio-group` | `YuwRadioGroupComponent` |
249
+ | [Combobox](#combobox) | `yuw-combobox` | `YuwComboboxComponent` |
250
+ | [Datepicker](#datepicker) | `yuw-datepicker` | `YuwDatepickerComponent` |
251
+ | **Data display** | | |
252
+ | [Avatar](#avatar) | `yuw-avatar` | `YuwAvatarComponent` |
253
+ | [Badge](#badge) | `yuw-badge` | `YuwBadgeComponent` |
254
+ | [Card](#card) | `[yuwCard]` | `YuwCardImports` |
255
+ | [Item](#item) | `[yuwItem]` | `YuwItemImports` |
256
+ | [Attachment](#attachment) | `[yuwAttachment]` | `YuwAttachmentDirective`, … |
257
+ | [Table](#table) | `[yuwTable]` | `YuwTableImports` |
258
+ | [Datatable](#datatable) | `yuw-datatable` | `YuwDatatableComponent` |
259
+ | [Empty](#empty) | `[yuwEmpty]` | `YuwEmptyImports` |
260
+ | [Skeleton](#skeleton) | `[yuwSkeleton]` | `YuwSkeletonDirective` |
261
+ | [Spinner](#spinner) | `yuw-spinner` | `YuwSpinnerComponent` |
262
+ | **Navigation** | | |
263
+ | [Accordion](#accordion) | `yuw-accordion` | `YuwAccordionImports` |
264
+ | [Breadcrumb](#breadcrumb) | `yuw-breadcrumb` | `YuwBreadcrumbComponent` |
265
+ | [Pagination](#pagination) | `yuw-pagination` | `YuwPaginationComponent` |
266
+ | [Tabs](#tabs) | `yuw-tabs` | `YuwTabsComponent`, `YuwTabPanelDirective` |
267
+ | [Sidebar](#sidebar) | `yuw-sidebar` | `YuwSidebarComponent`, … |
268
+ | **Overlays** | | |
269
+ | [Dialog](#dialog) | `yuw-dialog` | `YuwDialogImports` |
270
+ | [Alert dialog](#alert-dialog) | `yuw-alert-dialog` | `YuwAlertDialogComponent` |
271
+ | [Sheet](#sheet) | `yuw-sheet` | `YuwSheetImports` |
272
+ | [Popover](#popover) | `yuw-popover` | `YuwPopoverComponent` |
273
+ | [Tooltip](#tooltip) | `[yuwTooltip]` | `YuwTooltipDirective` |
274
+ | [Context menu](#context-menu) | `[yuwContextMenuTrigger]` | `YuwContextMenuImports` |
275
+ | **Feedback** | | |
276
+ | [Alert](#alert) | `yuw-alert` | `YuwAlertImports` |
277
+ | [Toaster](#toaster) | `yuw-toaster` | `YuwToasterComponent`, `yuwToast` |
278
+
279
+ ---
280
+
281
+ ### Button
282
+
283
+ Single action button, link, or a welded group of buttons.
284
+
285
+ ```html
286
+ <yuw-button variant="outline" color="primary" (clicked)="save()">Save</yuw-button>
287
+
288
+ <yuw-button href="/dashboard" target="_blank">Dashboard</yuw-button>
289
+
290
+ <yuw-button size="icon" ariaLabel="Add item">
291
+ <ng-icon name="lucidePlus" />
292
+ </yuw-button>
293
+
294
+ <!-- Button group -->
295
+ <yuw-button [buttons]="actions" groupAriaLabel="Alignment" (buttonClicked)="onAction($event)" />
296
+ ```
297
+
298
+ #### Inputs
299
+
300
+ | Input | Type | Default |
301
+ |:-----------------|:---------------------------------------------------------------------------------|:---------------|
302
+ | `variant` | `'default'|'secondary'|'outline'|'destructive'|'ghost'|'link'` | `'default'` |
303
+ | `size` | `'default'|'xs'|'sm'|'lg'|'icon'|'icon-xs'|'icon-sm'|'icon-lg'` | `'default'` |
304
+ | `color` | `YuwColorDto` | `'secondary'` |
305
+ | `disabled` | `boolean` | `false` |
306
+ | `type` | `'button'|'submit'|'reset'` | `'button'` |
307
+ | `href` | `string|undefined` | `undefined` |
308
+ | `target` | `string|undefined` | `undefined` |
309
+ | `transparent` | `boolean` — removes background and border | `false` |
310
+ | `ariaLabel` | `string|undefined` — required for icon-only buttons | `undefined` |
311
+ | `class` | `string` — merged onto the inner button/anchor, wins over defaults | `''` |
312
+ | `buttons` | `YuwButtonGroupItemDto[]|undefined` — switches to group mode | `undefined` |
313
+ | `orientation` | `'horizontal'|'vertical'` — group layout | `'horizontal'` |
314
+ | `groupAriaLabel` | `string|undefined` — accessible name of the group | `undefined` |
315
+ | `yuwDropdownMenuTrigger` | `TemplateRef<unknown>|undefined` | `undefined` |
316
+
317
+ #### Outputs
318
+
319
+ | Output | Payload | Description |
320
+ |:----------------|:------------------------|:----------------------------------------|
321
+ | `clicked` | `MouseEvent` | Single-button mode. |
322
+ | `buttonClicked` | `YuwButtonGroupClickDto`| Group mode — `{ id, event }`. |
323
+
324
+ #### Content slots
325
+
326
+ | Slot | Description |
327
+ |:-----------------|:-------------------------------------|
328
+ | `[yuwButtonIcon]`| Leading icon. |
329
+ | *(default)* | Button label. |
330
+
331
+ `YuwButtonGroupItemDto` entries are `{ id, label }` plus optional `variant`, `size`, `color`,
332
+ `disabled`, `href`, `target`, `type`, `ariaLabel`, `transparent` — each falling back to the
333
+ component-level input of the same name.
334
+
335
+ ---
336
+
337
+ ### Input
338
+
339
+ Single-line text field with addon slots and form integration.
340
+
341
+ ```html
342
+ <yuw-input [(value)]="username" placeholder="Enter username…" />
343
+
344
+ <yuw-input formControlName="email" type="email">
345
+ <yuw-left-addon><ng-icon name="lucideMail" /></yuw-left-addon>
346
+ </yuw-input>
347
+ ```
348
+
349
+ #### Inputs
350
+
351
+ | Input | Type | Default |
352
+ |:---------------|:-------------------------------------------------|:------------|
353
+ | `value` | `string` — two-way | `''` |
354
+ | `inputId` | `string|undefined` — for external `<label for>` | `undefined` |
355
+ | `type` | `'text'|'email'|'tel'|'url'|'number'` | `'text'` |
356
+ | `inputmode` | `YuwInputModeDto|undefined` | `undefined` |
357
+ | `placeholder` | `string|undefined` | `undefined` |
358
+ | `maxlength` | `number|undefined` | `undefined` |
359
+ | `name` | `string|undefined` | `undefined` |
360
+ | `autocomplete` | `string|undefined` | `undefined` |
361
+ | `disabled` | `boolean` | `false` |
362
+ | `readonly` | `boolean` | `false` |
363
+ | `required` | `boolean` | `false` |
364
+ | `invalid` | `boolean` | `false` |
365
+ | `class` | `string` | `''` |
366
+
367
+ #### Outputs
368
+
369
+ | Output | Payload | Description |
370
+ |:------------|:---------|:------------------------------------------------|
371
+ | `committed` | `string` | Current value on the native `change` (blur/enter). |
372
+
373
+ #### Content slots
374
+
375
+ `yuw-left-addon`, `yuw-right-addon` — import `YuwInputAddonImports`.
376
+
377
+ ---
378
+
379
+ ### Textarea
380
+
381
+ Multi-line text field. Auto-grows where `field-sizing` is supported.
382
+
383
+ ```html
384
+ <yuw-textarea [(value)]="bio" placeholder="Write your biography…" [rows]="4" [maxlength]="500" />
385
+ <yuw-textarea formControlName="comments" invalid />
386
+ ```
387
+
388
+ #### Inputs
389
+
390
+ Same contract as [Input](#input), minus `type` / `inputmode`, plus:
391
+
392
+ | Input | Type | Default |
393
+ |:-------|:-------------------|:------------|
394
+ | `rows` | `number|undefined` | `undefined` |
395
+
396
+ #### Outputs
397
+
398
+ | Output | Payload | Description |
399
+ |:------------|:---------|:-------------------------------------------|
400
+ | `committed` | `string` | Current value on the native `change` event. |
401
+
402
+ ---
403
+
404
+ ### Search input
405
+
406
+ Text field with a leading search icon and an optional clear button.
407
+
408
+ ```html
409
+ <yuw-search-input
410
+ [(value)]="query"
411
+ placeholder="Search items…"
412
+ clearAriaLabel="Clear search"
413
+ (searched)="onSearch($event)"
414
+ />
415
+ ```
416
+
417
+ #### Inputs
418
+
419
+ Same contract as [Input](#input), minus `type` / `inputmode` / `maxlength`, plus:
420
+
421
+ | Input | Type | Default |
422
+ |:-----------------|:-------------------|:------------|
423
+ | `clearAriaLabel` | `string|undefined` — set it to render the clear button | `undefined` |
424
+
425
+ #### Outputs
426
+
427
+ | Output | Payload | Description |
428
+ |:------------|:---------|:--------------------------------------------------|
429
+ | `searched` | `string` | Emitted on every keystroke. |
430
+ | `committed` | `string` | Emitted on Enter — for search-on-submit flows. |
431
+
432
+ ---
433
+
434
+ ### Password input
435
+
436
+ Password field with an optional reveal toggle.
437
+
438
+ ```html
439
+ <yuw-password-input [(value)]="password" revealAriaLabel="Toggle password visibility" />
440
+ <yuw-password-input formControlName="password" autocomplete="current-password" />
441
+ ```
442
+
443
+ #### Inputs
444
+
445
+ Same contract as [Input](#input), minus `type` / `inputmode` / `maxlength`, plus:
446
+
447
+ | Input | Type | Default |
448
+ |:------------------|:----------------------------------------------------|:------------|
449
+ | `revealAriaLabel` | `string|undefined` — set it to render the toggle | `undefined` |
450
+ | `masked` | `boolean` — two-way; a parent can re-mask the value | `true` |
451
+
452
+ #### Outputs
453
+
454
+ | Output | Payload | Description |
455
+ |:------------|:---------|:-------------------------------------------|
456
+ | `committed` | `string` | Current value on the native `change` event. |
457
+
458
+ ---
459
+
460
+ ### Input OTP
461
+
462
+ One-time password / verification code field, rendered as grouped slots.
463
+
464
+ ```html
465
+ <yuw-input-otp [(value)]="code" [groupSizes]="[3, 3]" (completed)="verify($event)" />
466
+ <yuw-input-otp formControlName="otpCode" mode="numeric" />
467
+ ```
468
+
469
+ #### Inputs
470
+
471
+ | Input | Type | Default |
472
+ |:-----------------|:----------------------------------------------|:------------------------|
473
+ | `value` | `string` — two-way | `''` |
474
+ | `groupSizes` | `readonly number[]` — `[3, 3]` renders `000-000` | `[6]` |
475
+ | `mode` | `'numeric'|'text'` | `'numeric'` |
476
+ | `inputId` | `string|undefined` | `undefined` |
477
+ | `disabled` | `boolean` | `false` |
478
+ | `invalid` | `boolean` | `false` |
479
+ | `autofocus` | `boolean` | `false` |
480
+ | `transformPaste` | `(pastedText: string, length: number) => string` | identity |
481
+ | `class` | `string` | `''` |
482
+
483
+ #### Outputs
484
+
485
+ | Output | Payload | Description |
486
+ |:------------|:---------|:----------------------------------------------------|
487
+ | `completed` | `string` | The full code, once the final slot is filled. |
488
+
489
+ ---
490
+
491
+ ### Label
492
+
493
+ Styled form label, applied to a native `<label>`.
494
+
495
+ ```html
496
+ <label yuwLabel for="username">Username</label>
497
+ <yuw-input inputId="username" [(value)]="username" />
498
+ ```
499
+
500
+ Accepts the native `for` / `id` attributes.
501
+
502
+ ---
503
+
504
+ ### Checkbox
505
+
506
+ Projected content becomes the clickable label — the control is wrapped in a `<label>`, so clicking
507
+ the text toggles it and the text is announced as the accessible name.
508
+
509
+ ```html
510
+ <yuw-checkbox [(checked)]="acceptedTerms">Accept terms</yuw-checkbox>
511
+ <yuw-checkbox [(indeterminate)]="isMixed" [(checked)]="allSelected">Select all</yuw-checkbox>
512
+ <yuw-checkbox formControlName="rememberMe" />
513
+ ```
514
+
515
+ #### Inputs
516
+
517
+ | Input | Type | Default |
518
+ |:------------------|:---------------------------|:------------|
519
+ | `checked` | `boolean` — two-way (alias of `value`) | `false` |
520
+ | `indeterminate` | `boolean` — two-way | `false` |
521
+ | `secondary` | `boolean` — secondary fill | `false` |
522
+ | `disabled` | `boolean` | `false` |
523
+ | `required` | `boolean` | `false` |
524
+ | `invalid` | `boolean` | `false` |
525
+ | `name` | `string|undefined` | `undefined` |
526
+ | `inputId` | `string|undefined` | `undefined` |
527
+ | `ariaLabel` | `string|undefined` | `undefined` |
528
+ | `ariaLabelledby` | `string|undefined` | `undefined` |
529
+ | `ariaDescribedby` | `string|undefined` | `undefined` |
530
+
531
+ ---
532
+
533
+ ### Switch
534
+
535
+ Toggle for binary settings. Projected content becomes the clickable label.
536
+
537
+ ```html
538
+ <yuw-switch [(checked)]="notificationsEnabled">Enable notifications</yuw-switch>
539
+ <yuw-switch formControlName="darkMode" size="sm" />
540
+ ```
541
+
542
+ #### Inputs
543
+
544
+ | Input | Type | Default |
545
+ |:------------------|:-------------------|:------------|
546
+ | `checked` | `boolean` — two-way (alias of `value`) | `false` |
547
+ | `size` | `'default'|'sm'` | `'default'` |
548
+ | `disabled` | `boolean` | `false` |
549
+ | `inputId` | `string|undefined` | `undefined` |
550
+ | `ariaLabel` | `string|undefined` | `undefined` |
551
+ | `ariaLabelledby` | `string|undefined` | `undefined` |
552
+ | `ariaDescribedby` | `string|undefined` | `undefined` |
553
+
554
+ ---
555
+
556
+ ### Radio group
557
+
558
+ ```html
559
+ <yuw-radio-group [options]="planOptions" [(value)]="selectedPlan" ariaLabel="Plan" />
560
+ <yuw-radio-group [options]="genderOptions" formControlName="gender" ariaLabelledby="gender-heading" />
561
+ ```
562
+
563
+ ```ts
564
+ interface YuwRadioOptionDto {
565
+ value: string;
566
+ label: string;
567
+ disabled?: boolean;
568
+ }
569
+ ```
570
+
571
+ #### Inputs
572
+
573
+ | Input | Type | Default |
574
+ |:-----------------|:----------------------------------------------|:------------|
575
+ | `options` | `readonly YuwRadioOptionDto[]` — **required** | — |
576
+ | `value` | `string|undefined` — two-way | `undefined` |
577
+ | `disabled` | `boolean` — disables the whole group | `false` |
578
+ | `required` | `boolean` | `false` |
579
+ | `name` | `string|undefined` — generated when omitted | `undefined` |
580
+ | `ariaLabel` | `string|undefined` | `undefined` |
581
+ | `ariaLabelledby` | `string|undefined` | `undefined` |
582
+ | `class` | `string` — layout tweaks (`grid-cols-2`, …) | `''` |
583
+
584
+ ---
585
+
586
+ ### Combobox
587
+
588
+ Searchable select. Single or multiple selection, in-place search or a read-only trigger, with
589
+ optional custom option templates.
590
+
591
+ ```html
592
+ <!-- Type-to-filter field -->
593
+ <yuw-combobox
594
+ [options]="userOptions"
595
+ [(value)]="selectedUser"
596
+ placeholder="Select user…"
597
+ emptyText="No user found."
598
+ triggerAriaLabel="Toggle options"
599
+ clearAriaLabel="Clear selection"
600
+ />
601
+
602
+ <!-- Classic select shape -->
603
+ <yuw-combobox readonlyTrigger [options]="colorOptions" [(value)]="color"
604
+ searchPlaceholder="Search colors…" triggerAriaLabel="Toggle options" />
605
+
606
+ <!-- Multiple, with chips -->
607
+ <yuw-combobox multiple [options]="categoryOptions" [(value)]="categories"
608
+ triggerAriaLabel="Toggle options" />
609
+
610
+ <!-- Custom option template -->
611
+ <yuw-combobox [options]="users" [(value)]="userId" triggerAriaLabel="Toggle options">
612
+ <ng-template yuwComboboxItem let-option let-selected="selected">
613
+ <img [src]="option.data.avatar" /> {{ option.label }}
614
+ </ng-template>
615
+ </yuw-combobox>
616
+ ```
617
+
618
+ ```ts
619
+ interface YuwComboboxOptionDto<TData = unknown> {
620
+ value: string;
621
+ label: string;
622
+ disabled?: boolean;
623
+ data?: TData; // available as `option.data` in a custom item template
624
+ }
625
+ ```
626
+
627
+ #### Inputs
628
+
629
+ | Input | Type | Default |
630
+ |:--------------------|:--------------------------------------------------|:------------|
631
+ | `options` | `YuwComboboxOptionDto<TData>[]` — **required** | — |
632
+ | `value` | `string|string[]|undefined` — two-way | `undefined` |
633
+ | `multiple` | `boolean` — chips shape; takes precedence over `readonlyTrigger` | `false` |
634
+ | `readonlyTrigger` | `boolean` — button trigger, search inside popover | `false` |
635
+ | `search` | `string` — two-way filter query | `''` |
636
+ | `placeholder` | `string|undefined` | `undefined` |
637
+ | `searchPlaceholder` | `string|undefined` | `undefined` |
638
+ | `emptyText` | `string|undefined` | `undefined` |
639
+ | `triggerAriaLabel` | `string` — **required** | — |
640
+ | `clearAriaLabel` | `string|undefined` — set it to render a clear button | `undefined` |
641
+ | `inputId` | `string|undefined` | `undefined` |
642
+ | `disabled` | `boolean` | `false` |
643
+ | `invalid` | `boolean` | `false` |
644
+
645
+ ---
646
+
647
+ ### Datepicker
648
+
649
+ ```html
650
+ <yuw-datepicker [(value)]="selectedDate" placeholder="Select date…" />
651
+ <yuw-datepicker mode="range" [(value)]="dateRange" captionLayout="dropdown" />
652
+ <yuw-datepicker mode="monthYear" [(value)]="selectedMonth" />
653
+ ```
654
+
655
+ #### Inputs
656
+
657
+ | Input | Type | Default |
658
+ |:----------------|:-----------------------------------------------------------------|:------------|
659
+ | `mode` | `'single'|'range'|'multiple'|'monthYear'` | `'single'` |
660
+ | `value` | `Date|[Date, Date]|Date[]|null` — two-way, shape follows `mode` | `null` |
661
+ | `minDate` | `Date|undefined` | `undefined` |
662
+ | `maxDate` | `Date|undefined` | `undefined` |
663
+ | `captionLayout` | `'label'|'dropdown'|'dropdown-months'|'dropdown-years'` | `'label'` |
664
+ | `align` | `'start'|'center'|'end'` | `'start'` |
665
+ | `placeholder` | `string|undefined` | `undefined` |
666
+ | `buttonId` | `string|undefined` — generated when omitted | `undefined` |
667
+ | `formatDate` | `((date: Date) => string)|undefined` | `undefined` |
668
+ | `disabled` | `boolean` | `false` |
669
+ | `invalid` | `boolean` | `false` |
670
+ | `class` | `string` — merged onto the trigger button | `''` |
671
+
672
+ `captionLayout` is ignored in `monthYear` mode, which renders its own month/year grid.
673
+
674
+ ---
675
+
676
+ ### Avatar
677
+
678
+ ```html
679
+ <yuw-avatar src="/user.png" alt="Jane Doe" fallback="JD" />
680
+ <yuw-avatar alt="Jane Doe" fallback="JD" badge />
681
+ <yuw-avatar [group]="teamMembers" alt="Team" groupCount="+5" />
682
+ ```
683
+
684
+ ```ts
685
+ interface YuwAvatarGroupItemDto {
686
+ alt: string;
687
+ src?: string;
688
+ fallback: string;
689
+ }
690
+ ```
691
+
692
+ #### Inputs
693
+
694
+ | Input | Type | Default |
695
+ |:-------------|:----------------------------------------|:------------|
696
+ | `alt` | `string` — **required** | — |
697
+ | `src` | `string|undefined` | `undefined` |
698
+ | `fallback` | `string|undefined` — initials | `undefined` |
699
+ | `size` | `'default'|'sm'|'lg'` | `'default'` |
700
+ | `badge` | `boolean` — status marker, bottom-right | `false` |
701
+ | `group` | `YuwAvatarGroupItemDto[]|undefined` | `undefined` |
702
+ | `groupCount` | `string|undefined` — overflow label | `undefined` |
703
+ | `class` | `string` | `''` |
704
+
705
+ #### Content slots
706
+
707
+ `[yuwAvatarBadgeIcon]` — projecting an icon turns the status dot into a role/verification marker.
708
+
709
+ ---
710
+
711
+ ### Badge
712
+
713
+ ```html
714
+ <yuw-badge color="primary">New</yuw-badge>
715
+ <yuw-badge variant="outline" removeLabel="Remove tag" (removed)="onRemove()">Category</yuw-badge>
716
+ <yuw-badge dot color="success" />
717
+ ```
718
+
719
+ #### Inputs
720
+
721
+ | Input | Type | Default |
722
+ |:--------------|:-------------------------------------------------------------------|:------------|
723
+ | `color` | `YuwColorDto` | `'primary'` |
724
+ | `variant` | `'default'|'secondary'|'destructive'|'outline'|'ghost'|'link'` | `'default'` |
725
+ | `dot` | `boolean` — compact dot, renders no content | `false` |
726
+ | `removeLabel` | `string|undefined` — set it to render the dismiss button | `undefined` |
727
+
728
+ #### Outputs
729
+
730
+ | Output | Payload | Description |
731
+ |:----------|:--------|:-----------------------------------|
732
+ | `removed` | `void` | Dismiss button activated. |
733
+
734
+ #### Content slots
735
+
736
+ `[yuwBadgeIcon]` — leading icon.
737
+
738
+ ---
739
+
740
+ ### Card
741
+
742
+ Directives applied to your own elements — no wrapper element is inserted.
743
+
744
+ ```html
745
+ <div yuwCard size="sm">
746
+ <div yuwCardHeader>
747
+ <h3 yuwCardTitle>Monthly report</h3>
748
+ <p yuwCardDescription>Summary of the last 30 days.</p>
749
+ <div yuwCardAction><yuw-button size="sm" variant="ghost">Export</yuw-button></div>
750
+ </div>
751
+ <div yuwCardContent>…</div>
752
+ <div yuwCardFooter>…</div>
753
+ </div>
754
+ ```
755
+
756
+ | Directive | Notes |
757
+ |:--------------------|:-----------------------------|
758
+ | `[yuwCard]` | `size`: `'default'|'sm'` |
759
+ | `[yuwCardHeader]` | |
760
+ | `[yuwCardTitle]` | |
761
+ | `[yuwCardDescription]` | |
762
+ | `[yuwCardAction]` | Top-right action area. |
763
+ | `[yuwCardContent]` | |
764
+ | `[yuwCardFooter]` | |
765
+
766
+ Import them all at once with `YuwCardImports`.
767
+
768
+ ---
769
+
770
+ ### Item
771
+
772
+ Row/list layout primitive.
773
+
774
+ ```html
775
+ <div yuwItem variant="outline" size="sm">
776
+ <div yuwItemMedia variant="icon"><ng-icon name="lucideMail" /></div>
777
+ <div yuwItemContent>
778
+ <div yuwItemTitle>New message</div>
779
+ <div yuwItemDescription>You have a message from Ada Lovelace.</div>
780
+ </div>
781
+ <div yuwItemActions><yuw-button size="sm">Open</yuw-button></div>
782
+ </div>
783
+ ```
784
+
785
+ | Directive | Notes |
786
+ |:-------------------------|:--------------------------------------------------|
787
+ | `[yuwItem]` | `variant`: `'default'|'outline'|'muted'`, `size`: `'default'|'sm'|'xs'` |
788
+ | `[yuwItemHeader]` | |
789
+ | `[yuwItemMedia]` | `variant`: `'default'|'icon'|'image'` |
790
+ | `[yuwItemContent]` | |
791
+ | `[yuwItemTitle]` | |
792
+ | `[yuwItemDescription]` | |
793
+ | `[yuwItemActions]` | |
794
+ | `[yuwItemFooter]` | |
795
+ | `[yuwItemGroup]` | |
796
+ | `[yuwItemSeparator]` | |
797
+
798
+ Import them all at once with `YuwItemImports`. Each directive also accepts the `yuw-item-*` element
799
+ form (`<yuw-item-title>`).
800
+
801
+ ---
802
+
803
+ ### Attachment
804
+
805
+ File attachment card, with upload states.
806
+
807
+ ```html
808
+ <div yuwAttachment state="uploading" size="sm">
809
+ <div yuwAttachmentMedia variant="icon"><ng-icon name="lucideFileText" /></div>
810
+ <div yuwAttachmentContent>
811
+ <span yuwAttachmentTitle>report.pdf</span>
812
+ <span yuwAttachmentDescription>2.4 MB</span>
813
+ </div>
814
+ <div yuwAttachmentActions>
815
+ <button yuwAttachmentAction><ng-icon name="lucideX" /></button>
816
+ </div>
817
+ </div>
818
+ ```
819
+
820
+ | Directive | Notes |
821
+ |:------------------------------|:----------------------------------------------------------------------|
822
+ | `[yuwAttachment]` | `state`: `'idle'|'uploading'|'processing'|'error'|'done'`, `size`: `'default'|'sm'|'xs'`, `orientation`: `'horizontal'|'vertical'` |
823
+ | `[yuwAttachmentGroup]` | |
824
+ | `[yuwAttachmentMedia]` | `variant`: `'icon'|'image'` |
825
+ | `[yuwAttachmentContent]` | |
826
+ | `[yuwAttachmentTitle]` | |
827
+ | `[yuwAttachmentDescription]` | |
828
+ | `[yuwAttachmentActions]` | |
829
+ | `button[yuwAttachmentAction]` | Action button inside the card. |
830
+ | `[yuwAttachmentTrigger]` | Makes a `<button>` / `<a>` the card's primary trigger. |
831
+
832
+ ---
833
+
834
+ ### Table
835
+
836
+ Styling directives for a table you write yourself.
837
+
838
+ ```html
839
+ <div yuwTableContainer>
840
+ <table yuwTable>
841
+ <caption yuwCaption>Team members and their roles.</caption>
842
+ <thead yuwTHead>
843
+ <tr yuwTr><th yuwTh>Name</th><th yuwTh>Role</th></tr>
844
+ </thead>
845
+ <tbody yuwTBody>
846
+ <tr yuwTr><td yuwTd>Ada Lovelace</td><td yuwTd>Engineer</td></tr>
847
+ </tbody>
848
+ <tfoot yuwTFoot>…</tfoot>
849
+ </table>
850
+ </div>
851
+ ```
852
+
853
+ Import them all at once with `YuwTableImports`.
854
+
855
+ ---
856
+
857
+ ### Datatable
858
+
859
+ Sorting, pagination, row selection, column visibility and a loading skeleton, over
860
+ [TanStack Table](https://tanstack.com/table).
861
+
862
+ ```html
863
+ <yuw-datatable
864
+ [columns]="columns"
865
+ [data]="users"
866
+ sortable
867
+ paginated
868
+ selectable
869
+ [loading]="loading()"
870
+ [(columnVisibility)]="columnVisibility"
871
+ emptyText="No users found."
872
+ previousPageLabel="Previous page"
873
+ nextPageLabel="Next page"
874
+ (selectedRowsChange)="onSelect($event)"
875
+ />
876
+ ```
877
+
878
+ #### Inputs
879
+
880
+ | Input | Type | Default |
881
+ |:--------------------|:----------------------------------------------|:-------------------------------|
882
+ | `columns` | `YuwDatatableColumnDef<TData>[]` | `[]` |
883
+ | `data` | `TData[]` | `[]` |
884
+ | `sortable` | `boolean` | `false` |
885
+ | `paginated` | `boolean` | `false` |
886
+ | `selectable` | `boolean` | `false` |
887
+ | `loading` | `boolean` — renders skeleton rows | `false` |
888
+ | `loadingRows` | `number` | `5` |
889
+ | `emptyText` | `string|undefined` | `undefined` |
890
+ | `previousPageLabel` | `string` — **required** | — |
891
+ | `nextPageLabel` | `string` — **required** | — |
892
+ | `caption` | `string|undefined` | `undefined` |
893
+ | `tableClass` | `string|undefined` — merged onto `<table>` | `undefined` |
894
+ | `sorting` | `SortingState` — two-way | `[]` |
895
+ | `columnFilters` | `ColumnFiltersState` — two-way | `[]` |
896
+ | `columnVisibility` | `ColumnVisibilityState` — two-way | `{}` |
897
+ | `rowSelection` | `RowSelectionState` — two-way | `{}` |
898
+ | `pagination` | `PaginationState` — two-way | `{ pageIndex: 0, pageSize: 10 }` |
899
+
900
+ #### Outputs
901
+
902
+ | Output | Payload | Description |
903
+ |:---------------------|:----------|:----------------------------------------------------------------|
904
+ | `selectedRowsChange` | `TData[]` | Selection changes. The initial state does not emit. |
905
+
906
+ > `selectable` enables the selection *state*; the checkbox column itself is a display column you
907
+ > provide (`id: 'select'`, with `flexRenderComponent(...)` header/cell calling TanStack's toggle
908
+ > handlers).
909
+
910
+ ---
911
+
912
+ ### Empty
913
+
914
+ Empty-state panel ("no results", "inbox empty").
915
+
916
+ ```html
917
+ <div yuwEmpty>
918
+ <div yuwEmptyHeader>
919
+ <div yuwEmptyMedia variant="icon"><ng-icon name="lucideFolderOpen" /></div>
920
+ <h3 yuwEmptyTitle>No files found</h3>
921
+ <p yuwEmptyDescription>Try uploading a file or changing your query.</p>
922
+ </div>
923
+ <div yuwEmptyContent>
924
+ <yuw-button>Upload file</yuw-button>
925
+ </div>
926
+ </div>
927
+ ```
928
+
929
+ `[yuwEmptyMedia]` accepts `variant`: `'default'|'icon'`. Import them all at once with
930
+ `YuwEmptyImports`.
931
+
932
+ ---
933
+
934
+ ### Skeleton
935
+
936
+ ```html
937
+ <div yuwSkeleton class="h-4 w-32 rounded"></div>
938
+ <div yuwSkeleton class="size-10 rounded-full"></div>
939
+ ```
940
+
941
+ Sized with standard CSS height/width classes on the host element.
942
+
943
+ ---
944
+
945
+ ### Spinner
946
+
947
+ ```html
948
+ <yuw-spinner label="Loading" />
949
+ <yuw-spinner label="Refreshing" icon="lucideRefreshCw" />
950
+ ```
951
+
952
+ #### Inputs
953
+
954
+ | Input | Type | Default |
955
+ |:--------|:---------|:-------------------|
956
+ | `label` | `string` — **required**, the accessible name | — |
957
+ | `icon` | `string` — any registered icon name | `'lucideLoader2'` |
958
+
959
+ The spinner inherits its size from the container's font size — wrap it in `text-2xl` to scale it.
960
+
961
+ ---
962
+
963
+ ### Accordion
964
+
965
+ ```html
966
+ <yuw-accordion type="multiple">
967
+ <yuw-accordion-item [disabled]="false" (openedChange)="onToggle($event)">
968
+ <yuw-accordion-trigger>Shipping</yuw-accordion-trigger>
969
+ <yuw-accordion-content>Delivered in 24–48 h.</yuw-accordion-content>
970
+ </yuw-accordion-item>
971
+ </yuw-accordion>
972
+ ```
973
+
974
+ | Element | Inputs / Outputs |
975
+ |:--------------------------|:----------------------------------------------------------------------|
976
+ | `yuw-accordion` | `type`: `'single'|'multiple'` (default `'single'`), `orientation`: `'vertical'|'horizontal'` (default `'vertical'`) |
977
+ | `yuw-accordion-item` | `isOpened`: `boolean`, `disabled`: `boolean`; output `openedChange` |
978
+ | `yuw-accordion-trigger` | `triggerClass`: `string` — merged after defaults, so it wins |
979
+ | `yuw-accordion-content` | — |
980
+
981
+ Import them all at once with `YuwAccordionImports`.
982
+
983
+ ---
984
+
985
+ ### Breadcrumb
986
+
987
+ ```html
988
+ <yuw-breadcrumb
989
+ [items]="[{ label: 'Home', href: '/' }, { label: 'Settings' }]"
990
+ ariaLabel="Breadcrumb"
991
+ />
992
+ ```
993
+
994
+ ```ts
995
+ interface YuwBreadcrumbItemDto {
996
+ label: string;
997
+ href?: string;
998
+ }
999
+ ```
1000
+
1001
+ #### Inputs
1002
+
1003
+ | Input | Type | Default |
1004
+ |:------------|:------------------------------------|:--------|
1005
+ | `items` | `YuwBreadcrumbItemDto[]` — **required** | — |
1006
+ | `ariaLabel` | `string` — **required** | — |
1007
+ | `separator` | `string` | `'/'` |
1008
+
1009
+ The last item is announced as the current page (`aria-current="page"`); intermediate items render
1010
+ as links when they have an `href`.
1011
+
1012
+ ---
1013
+
1014
+ ### Pagination
1015
+
1016
+ ```html
1017
+ <yuw-pagination
1018
+ [(page)]="currentPage"
1019
+ [totalPages]="20"
1020
+ previousLabel="Previous page"
1021
+ nextLabel="Next page"
1022
+ ellipsisSrText="More pages"
1023
+ ariaLabel="Pagination"
1024
+ />
1025
+ ```
1026
+
1027
+ #### Inputs
1028
+
1029
+ | Input | Type | Default |
1030
+ |:-----------------|:-----------------------------------------------------------------------|:------------|
1031
+ | `page` | `number` — two-way, **required** | — |
1032
+ | `totalPages` | `number` — **required** | — |
1033
+ | `previousLabel` | `string` — **required** | — |
1034
+ | `nextLabel` | `string` — **required** | — |
1035
+ | `ellipsisSrText` | `string` — **required** | — |
1036
+ | `ariaLabel` | `string` — **required** | — |
1037
+ | `size` | `'default'|'xs'|'sm'|'lg'|'icon'|'icon-xs'|'icon-sm'|'icon-lg'` | `'icon'` |
1038
+ | `color` | `YuwColorDto` | `'primary'` |
1039
+
1040
+ `getPaginationRange(page, totalPages)` is exported if you need the same page/ellipsis sequence
1041
+ elsewhere.
1042
+
1043
+ ---
1044
+
1045
+ ### Tabs
1046
+
1047
+ Triggers come from the `tabs` input; each panel is an `<ng-template yuwTabPanel="id">` whose id
1048
+ matches a tab. Panels are lazy — instantiated the first time their tab becomes active.
1049
+
1050
+ ```html
1051
+ <yuw-tabs [tabs]="settingsTabs" [(activeTab)]="selected" variant="line">
1052
+ <ng-template yuwTabPanel="general">Workspace name, timezone and locale.</ng-template>
1053
+ <ng-template yuwTabPanel="billing">Plan, invoices and payment method.</ng-template>
1054
+ </yuw-tabs>
1055
+ ```
1056
+
1057
+ ```ts
1058
+ interface YuwTabItemDto {
1059
+ id: string;
1060
+ label: string;
1061
+ disabled?: boolean;
1062
+ }
1063
+ ```
1064
+
1065
+ #### Inputs
1066
+
1067
+ | Input | Type | Default |
1068
+ |:-----------------|:--------------------------------|:---------------|
1069
+ | `tabs` | `YuwTabItemDto[]` — **required**| — |
1070
+ | `activeTab` | `string` — two-way, **required**| — |
1071
+ | `variant` | `'default'|'line'` | `'default'` |
1072
+ | `orientation` | `'horizontal'|'vertical'` | `'horizontal'` |
1073
+ | `activationMode` | `'automatic'|'manual'` | `'automatic'` |
1074
+
1075
+ ---
1076
+
1077
+ ### Sidebar
1078
+
1079
+ Collapsible navigation sidebar — a fixed panel on desktop, a slide-in sheet on mobile. Open state
1080
+ lives in `YuwSidebarService`, so `<yuw-sidebar-trigger>` works from anywhere on the page.
1081
+
1082
+ ```html
1083
+ <div yuwSidebarWrapper>
1084
+ <yuw-sidebar collapsible="icon">
1085
+ <div yuwSidebarHeader>Yurest</div>
1086
+
1087
+ <div yuwSidebarContent>
1088
+ <div yuwSidebarGroup>
1089
+ <div yuwSidebarGroupLabel>Management</div>
1090
+ <div yuwSidebarGroupContent>
1091
+ <ul yuwSidebarMenu>
1092
+ <li yuwSidebarMenuItem>
1093
+ <a yuwSidebarMenuButton [isActive]="true" tooltip="Orders">
1094
+ <ng-icon name="lucideShoppingCart" />
1095
+ <span>Orders</span>
1096
+ </a>
1097
+ <span yuwSidebarMenuBadge>12</span>
1098
+ </li>
1099
+ </ul>
1100
+ </div>
1101
+ </div>
1102
+ </div>
1103
+
1104
+ <div yuwSidebarFooter>…</div>
1105
+ </yuw-sidebar>
1106
+
1107
+ <main yuwSidebarInset>
1108
+ <yuw-sidebar-trigger label="Toggle sidebar" />
1109
+ </main>
1110
+ </div>
1111
+ ```
1112
+
1113
+ #### `<yuw-sidebar>` inputs
1114
+
1115
+ | Input | Type | Default |
1116
+ |:------------------------|:------------------------------------------|:---------------|
1117
+ | `side` | `'left'|'right'` | `'left'` |
1118
+ | `variant` | `'sidebar'|'floating'|'inset'` | `'sidebar'` |
1119
+ | `collapsible` | `'offcanvas'|'icon'|'none'` | `'offcanvas'` |
1120
+ | `sidebarWidthMobile` | `string` — any CSS length | `'18rem'` |
1121
+ | `sidebarContainerClass` | `string` — merged onto the desktop container | `''` |
1122
+
1123
+ #### Structure directives
1124
+
1125
+ | Directive | Notes |
1126
+ |:-------------------------------------|:------------------------------------------------------------------|
1127
+ | `[yuwSidebarWrapper]` | `sidebarWidth`, `sidebarWidthIcon` |
1128
+ | `[yuwSidebarHeader]` / `[yuwSidebarContent]` / `[yuwSidebarFooter]` | |
1129
+ | `[yuwSidebarGroup]` / `[yuwSidebarGroupLabel]` / `[yuwSidebarGroupContent]` | |
1130
+ | `ul[yuwSidebarMenu]` / `li[yuwSidebarMenuItem]` | |
1131
+ | `a[yuwSidebarMenuButton]`, `button[…]` | `variant`: `'default'|'outline'`, `size`: `'default'|'sm'|'lg'`, `isActive`, `closeMobileSidebarOnClick`; add Spartan's `tooltip` to label items while collapsed to icons |
1132
+ | `[yuwSidebarMenuBadge]` | Trailing count/status. |
1133
+ | `ul[yuwSidebarMenuSub]` / `li[yuwSidebarMenuSubItem]` / `a[yuwSidebarMenuSubButton]` | Nested level. |
1134
+ | `[yuwSidebarSeparator]` | |
1135
+ | `button[yuwSidebarRail]` | `label` — **required**, thin edge toggle. |
1136
+ | `main[yuwSidebarInset]` | Main content area. |
1137
+ | `<yuw-sidebar-trigger>` | `label` — **required**, screen-reader-only. |
1138
+
1139
+ Inject `YuwSidebarService` to read or drive the state (`state()`, `isMobile()`, `openMobile()`,
1140
+ `toggle()`); configure defaults app-wide with `provideYuwSidebarConfig({ … })`.
1141
+
1142
+ ---
1143
+
1144
+ ### Dialog
1145
+
1146
+ Modal overlay for focused tasks and forms. The body scrolls internally when taller than the
1147
+ viewport.
1148
+
1149
+ > Provide either `title` or `ariaLabel` — a modal without an accessible name is announced as an
1150
+ > anonymous dialog by screen readers.
1151
+
1152
+ ```html
1153
+ <yuw-dialog [(open)]="isOpen" title="Create project"
1154
+ description="Give it a name to get started." closeLabel="Close">
1155
+ <ng-container yuwDialogTrigger>Create project</ng-container>
1156
+
1157
+ <yuw-input [(value)]="name" placeholder="Project name" />
1158
+
1159
+ <div yuwDialogFooter>
1160
+ <yuw-button (clicked)="save()">Save</yuw-button>
1161
+ </div>
1162
+ </yuw-dialog>
1163
+ ```
1164
+
1165
+ #### Inputs
1166
+
1167
+ | Input | Type | Default |
1168
+ |:-----------------|:---------------------------------|:------------|
1169
+ | `open` | `boolean` — two-way | `false` |
1170
+ | `showTrigger` | `boolean` — set `false` when driving via `[(open)]` only | `true` |
1171
+ | `title` | `string|undefined` | `undefined` |
1172
+ | `description` | `string|undefined` | `undefined` |
1173
+ | `ariaLabel` | `string|undefined` | `undefined` |
1174
+ | `closeLabel` | `string|undefined` — set it to render the corner ✕ | `undefined` |
1175
+ | `panelClass` | `string|undefined` — e.g. `sm:max-w-lg` | `undefined` |
1176
+ | `triggerVariant` | button variant | `'outline'` |
1177
+ | `triggerSize` | button size | `'default'` |
1178
+
1179
+ #### Content slots
1180
+
1181
+ `[yuwDialogTrigger]`, `[yuwDialogFooter]` (the footer renders only when filled), and the default
1182
+ slot for the body. Import with `YuwDialogImports`.
1183
+
1184
+ ---
1185
+
1186
+ ### Alert dialog
1187
+
1188
+ Blocking confirmation for destructive or irreversible actions. Unlike `<yuw-dialog>` it cannot be
1189
+ dismissed by clicking the backdrop.
1190
+
1191
+ ```html
1192
+ <yuw-alert-dialog
1193
+ [(open)]="isOpen"
1194
+ title="Delete invoice?"
1195
+ description="This action cannot be undone."
1196
+ confirmLabel="Delete"
1197
+ cancelLabel="Cancel"
1198
+ confirmVariant="destructive"
1199
+ (confirmed)="delete()"
1200
+ />
1201
+ ```
1202
+
1203
+ #### Inputs
1204
+
1205
+ | Input | Type | Default |
1206
+ |:-----------------|:--------------------|:------------|
1207
+ | `open` | `boolean` — two-way | `false` |
1208
+ | `showTrigger` | `boolean` | `true` |
1209
+ | `title` | `string` — **required** | — |
1210
+ | `confirmLabel` | `string` — **required** | — |
1211
+ | `cancelLabel` | `string` — **required** | — |
1212
+ | `description` | `string|undefined` | `undefined` |
1213
+ | `confirmVariant` | button variant | `'default'` |
1214
+ | `size` | `'default'|'sm'` | `'default'` |
1215
+ | `triggerVariant` | button variant | `'outline'` |
1216
+ | `triggerSize` | button size | `'default'` |
1217
+
1218
+ #### Outputs
1219
+
1220
+ | Output | Payload | Description |
1221
+ |:------------|:--------|:--------------------------------------------------------------------------|
1222
+ | `confirmed` | `void` | Confirm action activated. |
1223
+ | `cancelled` | `void` | Cancel **button** activated — programmatic closes do not emit it. |
1224
+
1225
+ #### Content slots
1226
+
1227
+ `[yuwAlertDialogTrigger]`, `[yuwAlertDialogMedia]`.
1228
+
1229
+ ---
1230
+
1231
+ ### Sheet
1232
+
1233
+ Slide-in panel for side drawers and detail views. Same accessible-name rule as
1234
+ [Dialog](#dialog).
1235
+
1236
+ ```html
1237
+ <yuw-sheet [(open)]="isOpen" side="right" title="Edit profile"
1238
+ description="Update your account details." closeLabel="Close">
1239
+ <ng-container yuwSheetTrigger>Edit profile</ng-container>
1240
+
1241
+ <p>Sheet form content goes here.</p>
1242
+
1243
+ <div yuwSheetFooter>
1244
+ <yuw-button (clicked)="save()">Save changes</yuw-button>
1245
+ </div>
1246
+ </yuw-sheet>
1247
+ ```
1248
+
1249
+ #### Inputs
1250
+
1251
+ Same as [Dialog](#dialog), plus:
1252
+
1253
+ | Input | Type | Default |
1254
+ |:-------|:----------------------------------|:----------|
1255
+ | `side` | `'top'|'bottom'|'left'|'right'` | `'right'` |
1256
+
1257
+ #### Content slots
1258
+
1259
+ `[yuwSheetTrigger]`, `[yuwSheetFooter]`. Import with `YuwSheetImports`.
1260
+
1261
+ ---
1262
+
1263
+ ### Popover
1264
+
1265
+ ```html
1266
+ <yuw-popover [(open)]="isOpen" align="start" title="Notifications"
1267
+ description="You're all caught up.">
1268
+ <button yuwPopoverTrigger>View notifications</button>
1269
+
1270
+ <p>No new notifications since your last visit.</p>
1271
+ </yuw-popover>
1272
+ ```
1273
+
1274
+ #### Inputs
1275
+
1276
+ | Input | Type | Default |
1277
+ |:-----------------|:---------------------------|:-----------|
1278
+ | `open` | `boolean` — two-way | `false` |
1279
+ | `showTrigger` | `boolean` | `true` |
1280
+ | `align` | `'start'|'center'|'end'` | `'center'` |
1281
+ | `sideOffset` | `number` — px | `4` |
1282
+ | `title` | `string|undefined` | `undefined`|
1283
+ | `description` | `string|undefined` | `undefined`|
1284
+ | `panelClass` | `string|undefined` — e.g. `w-80` | `undefined` |
1285
+ | `triggerVariant` | button variant | `'outline'`|
1286
+ | `triggerSize` | button size | `'default'`|
1287
+
1288
+ #### Content slots
1289
+
1290
+ `[yuwPopoverTrigger]`, plus the default slot for the panel body.
1291
+
1292
+ ---
1293
+
1294
+ ### Tooltip
1295
+
1296
+ Applied directly to the trigger element — no wrapper markup.
1297
+
1298
+ ```html
1299
+ <yuw-button yuwTooltip="Save your changes">Save</yuw-button>
1300
+ <yuw-button [yuwTooltip]="helpText" position="right" [showDelay]="500">Help</yuw-button>
1301
+ ```
1302
+
1303
+ #### Inputs
1304
+
1305
+ | Input | Type | Default |
1306
+ |:------------------|:----------------------------------------|:--------|
1307
+ | `yuwTooltip` | `string|TemplateRef` — the content | — |
1308
+ | `position` | `'top'|'bottom'|'left'|'right'` | `'top'` |
1309
+ | `showDelay` | `number` — ms | `150` |
1310
+ | `hideDelay` | `number` — ms | `100` |
1311
+ | `tooltipDisabled` | `boolean` | `false` |
1312
+
1313
+ ---
1314
+
1315
+ ### Context menu
1316
+
1317
+ Right-click menu, declared as a template and attached to any element.
1318
+
1319
+ ```html
1320
+ <div [yuwContextMenuTrigger]="menu">Right-click me</div>
1321
+
1322
+ <ng-template #menu>
1323
+ <div yuwContextMenu>
1324
+ <div yuwContextMenuLabel>Actions</div>
1325
+
1326
+ <button yuwContextMenuItem (triggered)="edit()">
1327
+ Edit <span yuwContextMenuShortcut>⌘E</span>
1328
+ </button>
1329
+
1330
+ <div yuwContextMenuSeparator></div>
1331
+
1332
+ <div yuwContextMenuGroup>
1333
+ <button yuwContextMenuRadio [checked]="order() === 'asc'" (triggered)="setOrder('asc')">
1334
+ Ascending
1335
+ <yuw-context-menu-radio-indicator />
1336
+ </button>
1337
+ </div>
1338
+
1339
+ <button yuwContextMenuCheckbox [checked]="showArchived()" (triggered)="toggleArchived()">
1340
+ Show archived
1341
+ <yuw-context-menu-checkbox-indicator />
1342
+ </button>
1343
+
1344
+ <button [yuwContextMenuSubTrigger]="subMenu">
1345
+ More tools
1346
+ <yuw-context-menu-sub-indicator />
1347
+ </button>
1348
+ </div>
1349
+ </ng-template>
1350
+ ```
1351
+
1352
+ | Directive | Inputs / Outputs |
1353
+ |:----------------------------|:----------------------------------------------------------------|
1354
+ | `[yuwContextMenuTrigger]` | Menu `TemplateRef`; `yuwContextMenuTriggerData`; outputs `opened`, `closed` |
1355
+ | `[yuwContextMenu]` | Menu panel. |
1356
+ | `[yuwContextMenuItem]` | Output `triggered`. |
1357
+ | `[yuwContextMenuCheckbox]` | `checked`, `disabled`; output `triggered`. |
1358
+ | `[yuwContextMenuRadio]` | `checked`, `disabled`; output `triggered`. |
1359
+ | `[yuwContextMenuGroup]` / `[yuwContextMenuLabel]` / `[yuwContextMenuSeparator]` / `[yuwContextMenuShortcut]` | Structure. |
1360
+ | `[yuwContextMenuSub]` / `[yuwContextMenuSubTrigger]` | Nested menus. |
1361
+ | `<yuw-context-menu-checkbox-indicator>` / `…-radio-indicator>` / `…-sub-indicator>` | Indicators. |
1362
+
1363
+ Checkbox and radio items keep the menu open; drive their `checked` state from `triggered`. Import
1364
+ them all at once with `YuwContextMenuImports`.
1365
+
1366
+ ---
1367
+
1368
+ ### Alert
1369
+
1370
+ Inline, non-blocking message panel.
1371
+
1372
+ ```html
1373
+ <yuw-alert variant="destructive" title="Payment declined"
1374
+ description="Check the card details and try again.">
1375
+ <ng-icon yuwAlertIcon name="lucideTriangleAlert" />
1376
+ <yuw-button yuwAlertAction size="sm" variant="ghost">Retry</yuw-button>
1377
+ </yuw-alert>
1378
+
1379
+ <!-- Rich content replaces the plain-text inputs -->
1380
+ <yuw-alert>
1381
+ <span yuwAlertTitle>Terms <strong>updated</strong></span>
1382
+ <span yuwAlertDescription>Review the <a href="/changes">changes</a> before continuing.</span>
1383
+ </yuw-alert>
1384
+ ```
1385
+
1386
+ #### Inputs
1387
+
1388
+ | Input | Type | Default |
1389
+ |:--------------|:----------------------------|:------------|
1390
+ | `variant` | `'default'|'destructive'` | `'default'` |
1391
+ | `title` | `string|undefined` | `undefined` |
1392
+ | `description` | `string|undefined` | `undefined` |
1393
+ | `class` | `string` | `''` |
1394
+
1395
+ #### Content slots
1396
+
1397
+ `[yuwAlertIcon]`, `[yuwAlertTitle]`, `[yuwAlertDescription]`, `[yuwAlertAction]`. A projected title
1398
+ or description replaces the corresponding input. Import with `YuwAlertImports`.
1399
+
1400
+ ---
1401
+
1402
+ ### Toaster
1403
+
1404
+ Mount `<yuw-toaster />` **once** near the app root, then dispatch toasts from anywhere with
1405
+ `yuwToast`.
1406
+
1407
+ ```html
1408
+ <yuw-toaster position="top-right" theme="system" closeButton />
1409
+ ```
1410
+
1411
+ ```ts
1412
+ import { yuwToast } from 'yukit-web';
1413
+
1414
+ yuwToast('Settings saved');
1415
+ yuwToast.success('Invoice sent', { description: 'A copy was emailed to the customer.' });
1416
+ yuwToast.error('Payment declined');
1417
+ yuwToast.warning('Storage almost full');
1418
+ yuwToast.info('Maintenance on Sunday');
1419
+
1420
+ yuwToast.promise(saveSettings(), {
1421
+ loading: 'Saving…',
1422
+ success: 'Saved',
1423
+ error: 'Could not save',
1424
+ });
1425
+
1426
+ yuwToast('Item deleted', {
1427
+ action: { label: 'Undo', onClick: () => restore() },
1428
+ });
1429
+ ```
1430
+
1431
+ #### Inputs
1432
+
1433
+ | Input | Type | Default |
1434
+ |:----------------|:-----------------------------------------------------------------------------------|:-----------------|
1435
+ | `position` | `'top-left'|'top-center'|'top-right'|'bottom-left'|'bottom-center'|'bottom-right'` | `'bottom-right'` |
1436
+ | `richColors` | `boolean` — per-type coloring (deviates from the Spartan default) | `true` |
1437
+ | `theme` | `'light'|'dark'|'system'` | `'light'` |
1438
+ | `duration` | `number` — ms | `4000` |
1439
+ | `visibleToasts` | `number` | `3` |
1440
+ | `closeButton` | `boolean` | `false` |
1441
+ | `expand` | `boolean` — keeps the stack expanded | `false` |
1442
+ | `toastOptions` | `ToastOptions` — defaults applied to every toast | `{}` |
1443
+
1444
+ ## Utilities and services
1445
+
1446
+ | Export | Description |
1447
+ |:--------------------------|:-----------------------------------------------------------------------------------------------|
1448
+ | `cn(...classes)` | `clsx` + `tailwind-merge`. Merges class lists so a later class overrides an earlier one of the same Tailwind group. |
1449
+ | `yuwToast` | Toast API (`success`, `error`, `warning`, `info`, `promise`, …). |
1450
+ | `YuwSidebarService` | Sidebar open/collapsed state, shared across the page. |
1451
+ | `provideYuwSidebarConfig` | App-wide sidebar defaults (widths, variant). |
1452
+ | `provideValueAccessor` | Registers a component as a form control — used by the library's own controls. |
1453
+ | `getPaginationRange` | The page/ellipsis sequence `<yuw-pagination>` renders. |
1454
+ | `YuwColorDto`, `YuwInputTypeDto`, `YuwInputModeDto`, `YuwTextFieldContract` | Shared type vocabulary. |
1455
+
1456
+ ### Spartan helm layer
1457
+
1458
+ The Spartan **helm** layer this library builds on is compiled into the package itself as secondary
1459
+ entry points (`yukit-web/helm/button`, `yukit-web/helm/input`, …). Consumers don't need it — the
1460
+ `yuw-*` components are the supported API — but it is public and safe to import when you need a
1461
+ primitive no wrapper covers yet:
1462
+
1463
+ ```ts
1464
+ import { HlmButton } from 'yukit-web/helm/button';
1465
+ ```