@keepui/ui 0.3.0 → 0.5.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 (32) hide show
  1. package/README.md +741 -276
  2. package/fesm2022/keepui-ui.mjs +1509 -2
  3. package/fesm2022/keepui-ui.mjs.map +1 -1
  4. package/lib/components/signal-dropdown/signal-dropdown.component.d.ts +91 -0
  5. package/lib/components/signal-dropdown/signal-dropdown.component.d.ts.map +1 -0
  6. package/lib/components/signal-dropdown/signal-dropdown.types.d.ts +12 -0
  7. package/lib/components/signal-dropdown/signal-dropdown.types.d.ts.map +1 -0
  8. package/lib/components/signal-text-input/signal-text-input.component.d.ts +101 -0
  9. package/lib/components/signal-text-input/signal-text-input.component.d.ts.map +1 -0
  10. package/lib/components/signal-text-input/signal-text-input.types.d.ts +8 -0
  11. package/lib/components/signal-text-input/signal-text-input.types.d.ts.map +1 -0
  12. package/lib/components/signal-textarea/signal-textarea.component.d.ts +70 -0
  13. package/lib/components/signal-textarea/signal-textarea.component.d.ts.map +1 -0
  14. package/lib/components/signal-textarea/signal-textarea.types.d.ts +11 -0
  15. package/lib/components/signal-textarea/signal-textarea.types.d.ts.map +1 -0
  16. package/lib/components/stepper/stepper.component.d.ts +67 -0
  17. package/lib/components/stepper/stepper.component.d.ts.map +1 -0
  18. package/lib/components/stepper/stepper.types.d.ts +16 -0
  19. package/lib/components/stepper/stepper.types.d.ts.map +1 -0
  20. package/lib/components/tab-group/tab-group.component.d.ts +52 -0
  21. package/lib/components/tab-group/tab-group.component.d.ts.map +1 -0
  22. package/lib/components/tab-group/tab-group.types.d.ts +14 -0
  23. package/lib/components/tab-group/tab-group.types.d.ts.map +1 -0
  24. package/lib/i18n/keep-ui-translations.d.ts +4 -0
  25. package/lib/i18n/keep-ui-translations.d.ts.map +1 -1
  26. package/lib/i18n/translation-keys.d.ts +4 -0
  27. package/lib/i18n/translation-keys.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/public-api.d.ts +10 -0
  30. package/public-api.d.ts.map +1 -1
  31. package/styles/index.css +1 -1
  32. package/styles/prebuilt.css +1 -1
package/README.md CHANGED
@@ -1,153 +1,167 @@
1
1
  # KeepUI
2
2
 
3
- > Angular cross-platform UI component library with optional Capacitor support.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@keepui/ui.svg)](https://www.npmjs.com/package/@keepui/ui)
6
- [![license](https://img.shields.io/npm/l/@keepui/ui.svg)](LICENSE)
7
-
8
- ---
9
-
10
- ## Table of Contents
11
-
12
- - [Purpose](#purpose)
13
- - [Package Structure](#package-structure)
14
- - [Installation](#installation)
15
- - [Setup](#setup)
16
- - [1. Import styles](#1-import-styles)
17
- - [2. Register providers](#2-register-providers)
18
- - [Available Components](#available-components)
19
- - [`<keepui-button>`](#keepui-button)
20
- - [`<keepui-card>`](#keepui-card)
21
- - [`<keepui-icon>`](#keepui-icon)
22
- - [`<keepui-icon-action-button>`](#keepui-icon-action-button)
23
- - [`<keepui-image-preview>`](#keepui-image-preview)
24
- - [i18n](#i18n)
25
- - [Theming](#theming)
26
- - [Architecture Notes](#architecture-notes)
27
- - [Testing](#testing)
28
- - [Building](#building)
29
- - [Publishing to npm](#publishing-to-npm)
30
-
31
- ---
32
-
33
- ## Purpose
34
-
35
- KeepUI is a reusable Angular component library designed to work seamlessly across:
36
-
37
- - **Standard Angular web applications** (browser only)
38
- - **Angular + Capacitor applications** (iOS, Android, PWA)
39
-
40
- The library uses a **Port/Adapter pattern** to keep UI components fully decoupled from platform-specific APIs. Components never import `@capacitor/*` directly; they depend on injected interface implementations registered via functional providers.
3
+ > Component library for Angular with multi-platform support (web and Angular + Capacitor).
4
+ > Built on **Tailwind CSS v4** with full **light/dark** theming and **i18n** via `@jsverse/transloco`.
5
+ >
6
+ > **Auto-generated** from JSDoc in the source files.
7
+ > Do **not** edit manually — run `npm run docs:generate` to update.
41
8
 
42
9
  ---
10
+ ## 1. Package identity
43
11
 
44
- ## Package Structure
45
-
46
- | Package | Description |
12
+ | Field | Value |
47
13
  |---|---|
48
- | `@keepui/ui` | Core components, ports, tokens, services, web adapter |
49
- | `@keepui/ui/capacitor` | Capacitor adapter — use *instead of* the web provider |
14
+ | Package name | `@keepui/ui` |
15
+ | Angular compat | `>= 19` |
16
+ | Style engine | Tailwind CSS v4 |
17
+ | i18n engine | `@jsverse/transloco` |
50
18
 
51
19
  ---
20
+ ## 2. Installation
52
21
 
53
- ## Installation
22
+ ### Option A — local build (development)
54
23
 
55
24
  ```bash
56
- # Using ng add (recommended):
57
- ng add @keepui/ui
25
+ npm run build # outputs to dist/keep-ui
26
+ npm install /absolute/path/to/dist/keep-ui
27
+ ```
58
28
 
59
- # Or manually:
29
+ ### Option B — npm registry
30
+
31
+ ```bash
60
32
  npm install @keepui/ui
61
33
  ```
62
34
 
63
- For Capacitor support (optional):
35
+ ### Required peer dependencies
64
36
 
65
37
  ```bash
66
- npm install @capacitor/core @capacitor/camera
67
- npx cap sync
38
+ npm install @angular/common @angular/core @jsverse/transloco
68
39
  ```
69
40
 
70
41
  ---
42
+ ## 3. Global styles (required)
71
43
 
72
- ## Setup
73
-
74
- ### 1. Import styles
75
-
76
- KeepUI components use CSS custom properties for theming. Import the library styles in your global stylesheet **before** any component-level styles.
77
-
78
- **If your project uses Tailwind CSS v4:**
44
+ Add once to the host application's global stylesheet (e.g. `src/styles.css`):
79
45
 
80
46
  ```css
81
- /* src/styles.css */
82
- @import "tailwindcss";
83
47
  @import "@keepui/ui/styles";
84
48
  ```
85
49
 
86
- **If your project does NOT use Tailwind:**
50
+ Provides:
51
+ - Light theme CSS custom properties (default)
52
+ - Dark theme via `[data-theme="dark"]`
53
+ - Auto dark via `@media (prefers-color-scheme: dark)`
54
+ - Tailwind v4 `@theme inline` mappings for all `bg-keepui-*`, `text-keepui-*`, `border-keepui-*`, `shadow-keepui-*` utilities
87
55
 
88
- ```css
89
- /* src/styles.css */
90
- @import "@keepui/ui/styles/prebuilt.css";
91
- ```
56
+ #### Theme switching at runtime
92
57
 
93
- ### 2. Register providers
58
+ ```ts
59
+ document.documentElement.setAttribute('data-theme', 'dark'); // force dark
60
+ document.documentElement.setAttribute('data-theme', 'light'); // force light
61
+ document.documentElement.removeAttribute('data-theme'); // follow OS
62
+ ```
94
63
 
95
- Register one platform provider plus the i18n provider in `app.config.ts`.
64
+ ---
65
+ ## 4. Provider setup (`app.config.ts`)
96
66
 
97
- **Web:**
67
+ ### Web application
98
68
 
99
69
  ```ts
100
- // src/app/app.config.ts
101
- import { ApplicationConfig } from '@angular/core';
102
- import { provideRouter } from '@angular/router';
103
70
  import { provideKeepUi, provideKeepUiI18n } from '@keepui/ui';
104
71
 
105
72
  export const appConfig: ApplicationConfig = {
106
73
  providers: [
107
- provideRouter(routes),
108
- provideKeepUi(),
109
- provideKeepUiI18n({ defaultLang: 'en' }),
74
+ provideKeepUi(), // registers WebFileService for FILE_PORT
75
+ provideKeepUiI18n(), // default language: 'en'
76
+ // provideKeepUiI18n({ defaultLang: 'es' }),
110
77
  ],
111
78
  };
112
79
  ```
113
80
 
114
- **Angular + Capacitor:**
81
+ ### Angular + Capacitor application
115
82
 
116
83
  ```ts
117
- // src/app/app.config.ts
118
- import { ApplicationConfig } from '@angular/core';
119
- import { provideRouter } from '@angular/router';
120
- import { provideKeepUiI18n } from '@keepui/ui';
121
84
  import { provideKeepUiCapacitor } from '@keepui/ui/capacitor';
85
+ import { provideKeepUiI18n } from '@keepui/ui';
86
+
87
+ export const appConfig: ApplicationConfig = {
88
+ providers: [provideKeepUiCapacitor(), provideKeepUiI18n()],
89
+ };
90
+ ```
91
+
92
+ > **Rule:** Never register both `provideKeepUi()` and `provideKeepUiCapacitor()` at the same time.
93
+
94
+ ### Available provider functions
122
95
 
96
+ #### `provideKeepUiI18n(options?: KeepUiI18nOptions = {}): EnvironmentProviders`
97
+
98
+ Registers all providers required for KeepUI internationalisation.
99
+
100
+ - Configures a self-contained Transloco instance scoped to `'keepui'`.
101
+ - Bundles all translations **inline** — no HTTP assets required.
102
+ - Provides {@link KeepUiLanguageService} so the host app can switch locale.
103
+
104
+ ### Usage in `app.config.ts`
105
+ ```ts
123
106
  export const appConfig: ApplicationConfig = {
124
107
  providers: [
125
- provideRouter(routes),
126
- provideKeepUiCapacitor(), // instead of provideKeepUi()
127
- provideKeepUiI18n({ defaultLang: 'en' }),
108
+ provideKeepUi(),
109
+ provideKeepUiI18n(), // default language: 'en'
110
+ provideKeepUiI18n({ defaultLang: 'es' }), // or start in Spanish
128
111
  ],
129
112
  };
130
113
  ```
131
114
 
132
- > **Note:** Use `provideKeepUiCapacitor()` **instead of** `provideKeepUi()` — never both.
115
+ ### Changing language at runtime
116
+ ```ts
117
+ const lang = inject(KeepUiLanguageService);
118
+ lang.setLanguage('de');
119
+ ```
120
+
121
+ > **Note:** If your application already calls `provideTransloco()`, do NOT
122
+ > call `provideKeepUiI18n()` — instead configure your Transloco loader to
123
+ > handle the `'keepui/{lang}'` scope paths, and provide `KeepUiLanguageService`
124
+ > manually.
125
+
126
+ #### `provideKeepUi(): EnvironmentProviders`
127
+
128
+ Registers KeepUI core providers for a **web** Angular application.
129
+
130
+ Registers `WebFileService` as the implementation of `FILE_PORT`, enabling
131
+ all KeepUI components to use the browser's native file picker.
132
+
133
+ Add to `app.config.ts`:
134
+ ```ts
135
+ export const appConfig: ApplicationConfig = {
136
+ providers: [provideKeepUi()],
137
+ };
138
+ ```
133
139
 
134
140
  ---
135
141
 
136
- ## Available Components
142
+ ## 5. Component API reference
137
143
 
138
- ### `<keepui-button>`
144
+ All components are **standalone**. Import directly from `@keepui/ui`.
139
145
 
140
- Accessible, themed action button with variants, shapes, sizes, loading state, full-width layout, and named icon slots.
146
+ ### 5.1 `ButtonComponent`
147
+
148
+ **Selector:** `keepui-button`
149
+ **Import:** `import { ButtonComponent } from '@keepui/ui';`
150
+
151
+ Accessible, themed action button with support for variants, shapes, sizes,
152
+ loading state, full-width layout, and named icon slots.
153
+
154
+ Works identically on **web** and **Angular + Capacitor** (no native API usage).
141
155
 
142
156
  ```html
143
- <!-- Basic -->
157
+ <!-- Basic usage -->
144
158
  <keepui-button (clicked)="save()">Save</keepui-button>
145
159
 
146
- <!-- Variant + shape -->
147
- <keepui-button variant="danger" shape="rounded" size="auto">Delete</keepui-button>
160
+ <!-- Primary, pill-shaped, fixed width -->
161
+ <keepui-button variant="primary" shape="pill">Confirm</keepui-button>
148
162
 
149
- <!-- With leading icon -->
150
- <keepui-button variant="primary" size="auto">
163
+ <!-- With leading icon (any inline element) -->
164
+ <keepui-button variant="outline" size="auto">
151
165
  <svg slot="leading" width="16" height="16" aria-hidden="true">…</svg>
152
166
  Upload
153
167
  </keepui-button>
@@ -155,338 +169,789 @@ Accessible, themed action button with variants, shapes, sizes, loading state, fu
155
169
  <!-- Loading state -->
156
170
  <keepui-button [loading]="isSaving()">Saving…</keepui-button>
157
171
 
158
- <!-- Full-width -->
159
- <keepui-button [fullWidth]="true">Submit</keepui-button>
172
+ <!-- Full-width, danger -->
173
+ <keepui-button variant="danger" [fullWidth]="true">Delete account</keepui-button>
160
174
 
161
- <!-- Icon-only (ariaLabel required) -->
175
+ <!-- Icon-only (requires ariaLabel for accessibility) -->
162
176
  <keepui-button variant="ghost" size="auto" ariaLabel="Close dialog">
163
177
  <svg slot="leading" …>…</svg>
164
178
  </keepui-button>
165
179
  ```
166
180
 
167
- **Inputs:**
181
+ #### Inputs
168
182
 
169
183
  | Input | Type | Default | Description |
170
184
  |---|---|---|---|
171
- | `variant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost' \| 'danger'` | `'primary'` | Visual style. |
172
- | `size` | `'md' \| 'auto'` | `'md'` | `md`: 160 px wide, 40 px tall. `auto`: padding-driven width. |
173
- | `shape` | `'pill' \| 'rounded'` | `'pill'` | Border-radius style. |
174
- | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML `type` attribute. |
175
- | `disabled` | `boolean` | `false` | Disables the button. |
176
- | `loading` | `boolean` | `false` | Shows spinner, disables button, sets `aria-busy="true"`. |
177
- | `fullWidth` | `boolean` | `false` | Expands the button to 100% container width. |
178
- | `ariaLabel` | `string` | `''` | Accessible label. Required for icon-only buttons. |
179
-
180
- **Outputs:**
181
-
182
- | Output | Type | Description |
185
+ | `variant` | `ButtonVariant` | `'primary'` | Visual style of the button. |
186
+ | `size` | `ButtonSize` | `'md'` | Size mode.
187
+ - `md` fixed 160 px wide, 40 px tall.
188
+ - `auto` padding-driven width, 40 px tall. |
189
+ | `shape` | `ButtonShape` | `'pill'` | Border-radius style. |
190
+ | `type` | `ButtonType` | `'button'` | HTML `type` attribute of the inner `<button>`. |
191
+ | `disabled` | `boolean` | `false` | Disables the button when `true`. |
192
+ | `loading` | `boolean` | `false` | Replaces the content with an animated spinner and sets `aria-busy`.
193
+ Also disables the button until the operation completes. |
194
+ | `fullWidth` | `boolean` | `false` | Expands the button to fill its container width. |
195
+ | `ariaLabel` | `string` | `''` | Accessible label for icon-only buttons.
196
+ When provided, sets the `aria-label` attribute on the inner `<button>`. |
197
+
198
+ #### Outputs
199
+
200
+ | Output | Emitter type | Description |
183
201
  |---|---|---|
184
- | `clicked` | `void` | Emitted on click (only when enabled and not loading). |
202
+ | `clicked` | `OutputEmitterRef<void>` | Emitted when the button is clicked and is not disabled or loading. |
185
203
 
186
- **Slots:**
204
+ #### Content slots (`ng-content`)
187
205
 
188
206
  | Slot | Description |
189
207
  |---|---|
190
- | `slot="leading"` | Element placed before the label (e.g. icon). Hidden during loading. |
191
- | `slot="trailing"` | Element placed after the label (e.g. icon). Hidden during loading. |
192
- | *(default)* | Button label / content. |
208
+ | *(default)* | Main projected content |
209
+ | `[slot='leading']` | Leading icon/element. Hidden while `loading`. |
210
+ | `[slot='trailing']` | Trailing icon/element. Hidden while `loading`. |
193
211
 
194
212
  ---
213
+ ### 5.2 `CardComponent`
195
214
 
196
- ### `<keepui-card>`
215
+ **Selector:** `keepui-card`
216
+ **Import:** `import { CardComponent } from '@keepui/ui';`
197
217
 
198
- Versatile container with variant, padding, color, clickable, selected, and scrollable states.
218
+ Contenedor versátil con soporte de variante, padding, color, estado clickable,
219
+ seleccionado y scrollable.
199
220
 
200
- ```html
201
- <!-- Basic -->
202
- <keepui-card>Content</keepui-card>
221
+ `padding="screen"` aplica padding lateral y superior pero omite el inferior
222
+ intencionadamente para que el contenido parezca continuar más allá del área visible,
223
+ invitando al usuario a hacer scroll. Un `div` espaciador se inserta automáticamente
224
+ al final del contenido proyectado: cuando el usuario llega al fondo, el espaciador
225
+ reproduce el padding inferior correcto sin que el consumidor deba declararlo.
203
226
 
204
- <!-- Outlined with large padding -->
205
- <keepui-card variant="outlined" padding="lg">…</keepui-card>
227
+ ```html
228
+ <keepui-card>Contenido</keepui-card>
206
229
 
207
- <!-- Clickable + selectable -->
208
- <keepui-card [clickable]="true" [selected]="isSelected()" (clicked)="select()">
209
- Option A
230
+ <keepui-card variant="flat" padding="lg" [clickable]="true" (clicked)="onSelect()">
231
+ Card clicable
210
232
  </keepui-card>
211
233
 
212
- <!-- Full-height scrollable panel -->
213
234
  <div class="h-screen overflow-hidden">
214
235
  <keepui-card padding="screen" [scrollable]="true" [fullHeight]="true">
215
- Long list content…
236
+ Contenido largo — el padding inferior se gestiona automáticamente
216
237
  </keepui-card>
217
238
  </div>
218
239
  ```
219
240
 
220
- > `padding="screen"` applies lateral and top padding but omits the bottom intentionally so content appears to continue beyond the visible area. A spacer `<div>` is inserted automatically at the end of the projected content — when the user scrolls to the bottom, the correct bottom gap appears without extra markup from the consumer.
221
-
222
- **Inputs:**
241
+ #### Inputs
223
242
 
224
243
  | Input | Type | Default | Description |
225
244
  |---|---|---|---|
226
- | `variant` | `'flat' \| 'outlined'` | `'outlined'` | With or without border. |
227
- | `padding` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'screen'` | `'md'` | Internal padding. |
228
- | `colors` | `'primary' \| 'secondary'` | `'primary'` | Background surface token. |
229
- | `clickable` | `boolean` | `false` | Enables hover, focus ring, and button role. |
230
- | `selected` | `boolean` | `false` | Applies brand border when active. |
231
- | `scrollable` | `boolean` | `false` | Activates `overflow-y-auto`. Combine with `fullHeight`. |
232
- | `fullHeight` | `boolean` | `false` | Applies `h-full` to host and inner container. |
245
+ | `variant` | `CardVariant` | `'outlined'` | |
246
+ | `padding` | `CardPadding` | `'md'` | |
247
+ | `colors` | `CardColors` | `'primary'` | |
248
+ | `clickable` | `boolean` | `false` | |
249
+ | `selected` | `boolean` | `false` | |
250
+ | `scrollable` | `boolean` | `false` | |
251
+ | `fullHeight` | `boolean` | `false` | |
233
252
 
234
- **Outputs:**
253
+ #### Outputs
235
254
 
236
- | Output | Type | Description |
255
+ | Output | Emitter type | Description |
237
256
  |---|---|---|
238
- | `clicked` | `void` | Emitted on click or Enter / Space (requires `clickable`). |
257
+ | `clicked` | `OutputEmitterRef<void>` | |
239
258
 
240
259
  ---
260
+ ### 5.3 `IconActionButtonComponent`
241
261
 
242
- ### `<keepui-icon>`
262
+ **Selector:** `keepui-icon-action-button`
263
+ **Import:** `import { IconActionButtonComponent } from '@keepui/ui';`
243
264
 
244
- Renders an SVG symbol via `<use href="#name">`. The consuming application is responsible for registering SVG symbols in the DOM (e.g. with an `IconRegistryService`).
265
+ Circular icon-only action button with `default` and `danger` variants,
266
+ loading state, and full accessibility support.
245
267
 
246
- The icon color is inherited from `currentColor` apply any Tailwind text-color class directly to `keepui-icon`.
268
+ Because this button renders no visible text, `ariaLabel` is **required** to
269
+ satisfy WCAG 2.1 SC 4.1.2 (Name, Role, Value).
270
+
271
+ The icon color is inherited automatically via `currentColor` from the button's
272
+ text color, so SVG symbols defined with `stroke="currentColor"` will adapt to
273
+ both variants and themes without extra classes.
247
274
 
248
275
  ```html
249
- <!-- Decorative — aria-hidden="true" applied automatically -->
250
- <keepui-icon name="check-icon" [size]="20" />
276
+ <keepui-icon-action-button icon="edit-icon" ariaLabel="Editar" />
251
277
 
252
- <!-- Semantic standalone icon — role="img" + aria-label applied -->
253
- <keepui-icon name="close-icon" ariaLabel="Close dialog" />
278
+ <keepui-icon-action-button
279
+ icon="trash-icon"
280
+ variant="danger"
281
+ ariaLabel="Eliminar elemento"
282
+ [loading]="isDeleting()"
283
+ />
284
+ ```
254
285
 
255
- <!-- Inside a button slot -->
256
- <keepui-button variant="primary" size="auto">
257
- <keepui-icon slot="leading" name="add-icon" [size]="16" />
258
- New item
259
- </keepui-button>
286
+ #### Inputs
260
287
 
261
- <!-- Custom color -->
262
- <keepui-icon name="star-icon" [size]="24" class="text-ku-action-primary" />
288
+ | Input | Type | Default | Description |
289
+ |---|---|---|---|
290
+ | `icon` | `unknown` | — | ID of the SVG symbol to render (without the `#` prefix). |
291
+ | `ariaLabel` | `unknown` | — | Accessible label for the button. Always required — icon-only buttons must
292
+ have a programmatic name for screen readers (WCAG 2.1 SC 4.1.2). |
293
+ | `variant` | `IconActionButtonVariant` | `'default'` | Visual style variant. |
294
+ | `iconSize` | `number` | `20` | Size of the inner icon in pixels. |
295
+ | `type` | `IconActionButtonType` | `'button'` | HTML `type` attribute of the inner `<button>`. |
296
+ | `disabled` | `boolean` | `false` | Disables the button when `true`. |
297
+ | `loading` | `boolean` | `false` | Shows an animated spinner and disables the button.
298
+ Also sets `aria-busy="true"` on the element. |
299
+
300
+ ---
301
+ ### 5.4 `IconComponent`
302
+
303
+ **Selector:** `keepui-icon`
304
+ **Import:** `import { IconComponent } from '@keepui/ui';`
305
+
306
+ Generic SVG sprite icon renderer.
307
+
308
+ Renders a `<use href="#name">` reference pointing to an SVG symbol pre-registered
309
+ in the DOM by the consuming application (e.g. via `IconRegistryService`).
310
+
311
+ Accessibility:
312
+ - When used decoratively (default), the SVG carries `aria-hidden="true"` automatically.
313
+ - When used as a standalone meaningful icon, supply an `ariaLabel` — the SVG will
314
+ receive `role="img"` and `aria-label` accordingly.
315
+
316
+ ```html
317
+ <!-- Decorative — hidden from screen readers -->
318
+ <keepui-icon name="check-icon" [size]="20" aria-hidden="true" />
319
+
320
+ <!-- Meaningful standalone icon -->
321
+ <keepui-icon name="close-icon" ariaLabel="Cerrar" />
263
322
  ```
264
323
 
265
- **Inputs:**
324
+ #### Inputs
266
325
 
267
326
  | Input | Type | Default | Description |
268
327
  |---|---|---|---|
269
- | `name` | `string` | — | ID of the SVG symbol (without `#`). Required. |
328
+ | `name` | `unknown` | — | ID of the SVG symbol to render (without the `#` prefix). |
270
329
  | `size` | `number` | `24` | Width and height of the icon in pixels. |
271
330
  | `viewBox` | `string` | `'0 0 24 24'` | `viewBox` attribute forwarded to the `<svg>` element. |
272
- | `ariaLabel` | `string` | `''` | When provided: sets `role="img"` and `aria-label`. When omitted: `aria-hidden="true"` is applied automatically. |
331
+ | `ariaLabel` | `string` | `''` | Accessible label for standalone icons.
332
+ When provided, sets `role="img"` and `aria-label` on the SVG.
333
+ When omitted, the SVG is marked `aria-hidden="true"` (decorative). |
334
+
335
+ ---
336
+ ### 5.5 `ImagePreviewComponent`
273
337
 
274
- > ★ Required input.
338
+ **Selector:** `keepui-image-preview`
339
+ **Import:** `import { ImagePreviewComponent } from '@keepui/ui';`
340
+
341
+ Standalone component that allows the user to pick and preview an image.
342
+
343
+ This component is fully platform-agnostic. It delegates file picking to
344
+ whatever `FilePort` implementation is provided via `FILE_PORT`.
345
+
346
+ UI strings are fully internationalised via Transloco (scope `'keepui'`).
347
+ Call `provideKeepUiI18n()` in your `app.config.ts` and use
348
+ `KeepUiLanguageService.setLanguage(lang)` to change locale at runtime.
349
+
350
+ Usage:
351
+ ```html
352
+ <keepui-image-preview />
353
+ ```
354
+
355
+ Prerequisites — register providers in `app.config.ts`:
356
+ - Web: `provideKeepUi()` + `provideKeepUiI18n()`
357
+ - Capacitor: `provideKeepUiCapacitor()` + `provideKeepUiI18n()`
358
+
359
+ #### Public signals (readable from outside)
360
+
361
+ | Signal | Type | Description |
362
+ |---|---|---|
363
+ | `imageUrl` | `Signal<string | null>` | URL of the selected image, ready to bind to `[src]`. |
364
+ | `error` | `Signal<string | null>` | Error message if the last pick operation failed. |
365
+ | `loading` | `Signal<boolean>` | True while the pick operation is in progress. |
275
366
 
276
367
  ---
368
+ ### 5.6 `SignalDropdownComponent`
277
369
 
278
- ### `<keepui-icon-action-button>`
370
+ **Selector:** `keepui-signal-dropdown`
371
+ **Import:** `import { SignalDropdownComponent } from '@keepui/ui';`
279
372
 
280
- Circular icon-only action button with `default` and `danger` variants, loading state, and full accessibility support.
373
+ Signal-based accessible dropdown / select component.
281
374
 
282
- `ariaLabel` is **required** because the button contains no visible text (WCAG 2.1 SC 4.1.2). The icon color is inherited automatically via `currentColor` from the button's text color.
375
+ Fully platform-agnostic no native API usage. The panel opens in `fixed`
376
+ position so it is never clipped by overflow-hidden ancestors. It repositions
377
+ itself automatically on scroll and resize.
378
+
379
+ The `value` and `touched` properties are `model()` signals so the component
380
+ integrates seamlessly with Angular signal-based forms.
283
381
 
284
382
  ```html
285
- <!-- Default variant -->
286
- <keepui-icon-action-button icon="edit-icon" ariaLabel="Edit" />
383
+ <keepui-signal-dropdown
384
+ label="País"
385
+ placeholder="Selecciona un país"
386
+ [options]="countries"
387
+ [(value)]="selectedCountry"
388
+ />
389
+ ```
287
390
 
288
- <!-- Danger variant -->
289
- <keepui-icon-action-button
290
- icon="trash-icon"
291
- variant="danger"
292
- ariaLabel="Delete item"
391
+ #### Inputs
392
+
393
+ | Input | Type | Default | Description |
394
+ |---|---|---|---|
395
+ | `label` | `string` | `''` | Optional label text rendered above the dropdown. |
396
+ | `placeholder` | `string` | `''` | Placeholder shown when no value is selected. |
397
+ | `options` | `unknown` | — | Array of options to display in the panel. |
398
+ | `width` | `SignalDropdownWidth` | `'full'` | Layout width of the wrapper. |
399
+ | `required` | `boolean` | `false` | Marks the field as required. Adds `aria-required` and a visual asterisk. |
400
+ | `errorMessage` | `string` | `''` | Human-readable error message shown below the dropdown. Takes precedence over `errors[0]`. |
401
+ | `errors` | `readonly string[]` | `[]` | Array of error strings. The first item is displayed when `errorMessage` is empty.
402
+ Set together with `invalid=true` to trigger the error state. |
403
+ | `selectId` | `string` | ``ku-dropdown-${Math.random(` | Stable `id` used to link the `<label>` with the trigger `<button>`.
404
+ A random suffix is generated by default. |
405
+ | `disabled` | `boolean` | `false` | Disables the dropdown. |
406
+ | `invalid` | `boolean` | `false` | Forces the error visual state regardless of the `touched` model.
407
+ Useful for external form validation. |
408
+
409
+ #### Outputs
410
+
411
+ | Output | Emitter type | Description |
412
+ |---|---|---|
413
+ | `valueChange` | `OutputEmitterRef<T | null>` | Emitted when the selected value changes. |
414
+
415
+ ---
416
+ ### 5.7 `SignalTextareaComponent`
417
+
418
+ **Selector:** `keepui-signal-textarea`
419
+ **Import:** `import { SignalTextareaComponent } from '@keepui/ui';`
420
+
421
+ Signal-based accessible textarea component with character counter,
422
+ resize control, and integrated error display.
423
+
424
+ The `value` and `touched` properties are `model()` signals so the component
425
+ integrates seamlessly with Angular signal-based forms.
426
+
427
+ ```html
428
+ <keepui-signal-textarea
429
+ label="Descripción"
430
+ placeholder="Escribe aquí…"
431
+ [rows]="5"
432
+ [maxLength]="500"
433
+ [(value)]="description"
293
434
  />
435
+ ```
294
436
 
295
- <!-- Loading state -->
296
- <keepui-icon-action-button
297
- icon="upload-icon"
298
- ariaLabel="Upload file"
299
- [loading]="isUploading()"
437
+ #### Inputs
438
+
439
+ | Input | Type | Default | Description |
440
+ |---|---|---|---|
441
+ | `label` | `string` | `''` | Optional label text rendered above the textarea. |
442
+ | `placeholder` | `string` | `''` | Placeholder passed to the underlying `<textarea>`. |
443
+ | `rows` | `number` | `4` | Number of visible text rows. |
444
+ | `width` | `SignalTextareaWidth` | `'full'` | Layout width of the wrapper. |
445
+ | `resize` | `SignalTextareaResize` | `'none'` | CSS `resize` behaviour. |
446
+ | `required` | `boolean` | `false` | Marks the field as required. |
447
+ | `errorMessage` | `string` | `''` | Human-readable error message. Takes precedence over `errors[0]`. |
448
+ | `errors` | `readonly string[]` | `[]` | Array of error strings. The first item is displayed when `errorMessage` is empty.
449
+ Set together with `invalid=true` to trigger the error state. |
450
+ | `textareaId` | `string` | ``ku-textarea-${Math.random(` | Stable `id` used to link the `<label>` with the `<textarea>`.
451
+ A random suffix is generated by default. |
452
+ | `disabled` | `boolean` | `false` | Disables the textarea. |
453
+ | `maxLength` | `number | undefined` | `undefined` | Maximum number of characters allowed. Shows a character counter when set. |
454
+ | `invalid` | `boolean` | `false` | Forces the error visual state regardless of the `touched` model.
455
+ Useful for external form validation. |
456
+
457
+ ---
458
+ ### 5.8 `SignalTextInputComponent`
459
+
460
+ **Selector:** `keepui-signal-text-input`
461
+ **Import:** `import { SignalTextInputComponent } from '@keepui/ui';`
462
+
463
+ Signal-based accessible text input supporting all common HTML input types,
464
+ leading/trailing icons, a trailing content slot, and a built-in
465
+ password-visibility toggle (when `type="password"`).
466
+
467
+ The `value` and `touched` properties are `model()` signals so the component
468
+ integrates seamlessly with Angular signal-based forms.
469
+
470
+ Password toggle labels are translated via Transloco (scope `'keepui'`).
471
+ Call `provideKeepUiI18n()` in your `app.config.ts` to activate i18n.
472
+
473
+ ```html
474
+ <keepui-signal-text-input
475
+ label="Email"
476
+ type="email"
477
+ placeholder="usuario@ejemplo.com"
478
+ leadingIcon="mail-icon"
479
+ [(value)]="email"
300
480
  />
301
481
 
302
- <!-- Disabled -->
303
- <keepui-icon-action-button icon="share-icon" ariaLabel="Share" [disabled]="true" />
482
+ <!-- Password with toggle -->
483
+ <keepui-signal-text-input
484
+ label="Contraseña"
485
+ type="password"
486
+ [(value)]="password"
487
+ />
304
488
  ```
305
489
 
306
- **Inputs:**
490
+ #### Inputs
307
491
 
308
492
  | Input | Type | Default | Description |
309
493
  |---|---|---|---|
310
- | `icon` | `string` | | ID of the SVG symbol (without `#`). Required. |
311
- | `ariaLabel` | `string` | | Accessible label. Required (no visible text). |
312
- | `variant` | `'default' \| 'danger'` | `'default'` | Visual style. |
313
- | `iconSize` | `number` | `20` | Size of the inner icon in pixels. |
314
- | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML `type` attribute. |
315
- | `disabled` | `boolean` | `false` | Disables the button. |
316
- | `loading` | `boolean` | `false` | Shows spinner, disables button, sets `aria-busy="true"`. |
317
-
318
- > Required input.
494
+ | `label` | `string` | `''` | Optional label text rendered above the input. |
495
+ | `placeholder` | `string` | `''` | Placeholder passed to the underlying `<input>`. |
496
+ | `type` | `SignalTextInputType` | `'text'` | HTML `type` attribute. Use `'password'` to enable the visibility toggle. |
497
+ | `width` | `SignalTextInputWidth` | `'full'` | Layout width of the wrapper. |
498
+ | `leadingIcon` | `string` | `''` | Name of the leading icon (SVG symbol ID). Leave empty for no icon. |
499
+ | `trailingIcon` | `string` | `''` | Name of the trailing icon (SVG symbol ID). Ignored when `type="password"`. |
500
+ | `hasTrailingSlot` | `boolean` | `false` | When `true`, a slot for custom trailing content is enabled
501
+ (projects `[trailingSlot]` content). Overrides `trailingIcon`. |
502
+ | `required` | `boolean` | `false` | Marks the field as required. Adds `aria-required` and a visual asterisk. |
503
+ | `showRequiredIndicator` | `boolean` | `true` | When `false`, hides the visual asterisk even if `required=true`. |
504
+ | `errorMessage` | `string` | `''` | Human-readable error message. Takes precedence over `errors[0]`. |
505
+ | `errors` | `readonly string[]` | `[]` | Array of error strings. The first item is displayed when `errorMessage` is empty.
506
+ Set together with `invalid=true` to trigger the error state. |
507
+ | `inputId` | `string` | ``ku-text-input-${Math.random(` | Stable `id` used to link the `<label>` with the `<input>`.
508
+ A random suffix is generated by default. |
509
+ | `disabled` | `boolean` | `false` | Disables the input. |
510
+ | `invalid` | `boolean` | `false` | Forces the error visual state regardless of the `touched` model.
511
+ Useful for external form validation. |
319
512
 
320
513
  ---
321
514
 
322
- ### `<keepui-image-preview>`
515
+ ## 6. Type definitions
323
516
 
324
- Standalone image picker and preview. Delegates file selection to the registered `FilePort` implementation — works identically on web and native (Capacitor) without any code change. UI strings are fully internationalised via Transloco.
517
+ > Visual style variant of the button.
325
518
 
326
- ```html
327
- <keepui-image-preview />
519
+ ```ts
520
+ type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
328
521
  ```
329
522
 
330
- No inputs or outputs. Exposes three readable signals for advanced use cases:
523
+ > Size mode of the button.
524
+ - `md`: fixed width (160 px) and height (40 px).
525
+ - `auto`: height fixed (40 px), width grows with padding to fit content.
331
526
 
332
- | Signal | Type | Description |
333
- |---|---|---|
334
- | `imageUrl` | `Signal<string \| null>` | Data URL of the selected image, ready to bind to `[src]`. |
335
- | `error` | `Signal<string \| null>` | Error message if the last pick operation failed. |
336
- | `loading` | `Signal<boolean>` | `true` while the pick operation is in progress. |
527
+ ```ts
528
+ type ButtonSize = 'md' | 'auto';
529
+ ```
530
+
531
+ > Border-radius style of the button.
532
+ - `pill`: fully rounded (`rounded-full`).
533
+ - `rounded`: moderately rounded (`rounded-2xl`).
534
+
535
+ ```ts
536
+ type ButtonShape = 'pill' | 'rounded';
537
+ ```
337
538
 
338
- **Prerequisites** register in `app.config.ts`:
539
+ > HTML `type` attribute for the underlying `<button>` element.
339
540
 
340
541
  ```ts
341
- // Web
342
- provideKeepUi()
343
- provideKeepUiI18n({ defaultLang: 'en' })
542
+ type ButtonType = 'button' | 'submit' | 'reset';
543
+ ```
344
544
 
345
- // Capacitor
346
- provideKeepUiCapacitor()
347
- provideKeepUiI18n({ defaultLang: 'en' })
545
+ ```ts
546
+ type CardVariant = 'flat' | 'outlined';
348
547
  ```
349
548
 
350
- ---
549
+ ```ts
550
+ type CardPadding = 'none' | 'sm' | 'md' | 'lg' | 'screen';
551
+ ```
552
+
553
+ ```ts
554
+ type CardColors = 'primary' | 'secondary';
555
+ ```
556
+
557
+ ```ts
558
+ type IconActionButtonVariant = 'default' | 'danger';
559
+ ```
560
+
561
+ ```ts
562
+ type IconActionButtonType = 'button' | 'submit' | 'reset';
563
+ ```
564
+
565
+ > Layout width of the dropdown wrapper.
566
+
567
+ ```ts
568
+ type SignalDropdownWidth = 'full' | 'half' | 'auto';
569
+ ```
570
+
571
+ > Layout width of the text input wrapper.
572
+
573
+ ```ts
574
+ type SignalTextInputWidth = 'full' | 'half' | 'auto';
575
+ ```
351
576
 
352
- ## i18n
577
+ > HTML `type` attribute for the underlying `<input>` element.
578
+ Use `'password'` to enable the built-in show/hide toggle.
353
579
 
354
- KeepUI uses [`@jsverse/transloco`](https://jsverse.github.io/transloco/) with the scope `'keepui'`.
580
+ ```ts
581
+ type SignalTextInputType = | 'text'
582
+ | 'email'
583
+ | 'tel'
584
+ | 'number'
585
+ | 'password'
586
+ | 'search'
587
+ | 'url'
588
+ | 'date';
589
+ ```
355
590
 
356
- **Supported languages:** `en` · `es` · `de`
591
+ > Layout width of the textarea wrapper.
592
+
593
+ ```ts
594
+ type SignalTextareaWidth = 'full' | 'half' | 'auto';
595
+ ```
357
596
 
358
- **Change locale at runtime:**
597
+ > CSS `resize` behaviour of the `<textarea>` element.
598
+ - `none` → not resizable (default).
599
+ - `vertical` → user can drag to resize vertically.
600
+ - `horizontal` → user can drag to resize horizontally.
601
+ - `both` → user can drag freely.
359
602
 
360
603
  ```ts
361
- import { KeepUiLanguageService, KeepUiLanguage } from '@keepui/ui';
604
+ type SignalTextareaResize = 'none' | 'vertical' | 'horizontal' | 'both';
605
+ ```
606
+
607
+ ---
362
608
 
363
- @Component({ })
364
- export class MyComponent {
365
- private readonly langService = inject(KeepUiLanguageService);
609
+ ## 7. Interfaces & models
366
610
 
367
- switch(lang: KeepUiLanguage) {
368
- this.langService.setLanguage(lang);
369
- }
611
+ ### `FileResult`
612
+
613
+ Represents the result of a file/image selection operation.
614
+ This model is platform-agnostic and used across all adapters.
615
+
616
+ ```ts
617
+ interface FileResult {
618
+ /** A data URL (e.g. `data:image/jpeg;base64,...`) or an HTTP/file URL ready to display. */
619
+ dataUrl: string;
620
+ /** MIME type of the selected file, e.g. `image/jpeg`. */
621
+ mimeType: string;
622
+ }
623
+ ```
624
+
625
+ ### `FilePort`
626
+
627
+ Platform-agnostic port (interface) for file/image selection.
628
+
629
+ Provide a concrete implementation via the `FILE_PORT` injection token.
630
+ - Web: `WebFileService`
631
+ - Capacitor: `CapacitorFileService` (from `@keepui/ui/capacitor`)
632
+
633
+ ```ts
634
+ interface FilePort {
635
+ /** Opens a platform-native image picker and returns the selected image.
636
+ Resolves with a `FileResult` containing a displayable URL and MIME type.
637
+ Rejects if the user cancels or an error occurs. */
638
+ pickImage(): Promise<FileResult>;
370
639
  }
371
640
  ```
372
641
 
373
642
  ---
374
643
 
375
- ## Theming
644
+ ## 8. Services
376
645
 
377
- KeepUI uses CSS custom properties for all design tokens. The `data-theme` attribute on `<html>` switches between `light` (default) and `dark`.
646
+ ### `KeepUiLanguageService`
378
647
 
379
- **Switch theme at runtime:**
648
+ Service that exposes a public API for changing the active language of all
649
+ KeepUI components at runtime.
380
650
 
651
+ ### Usage
381
652
  ```ts
382
- document.documentElement.setAttribute('data-theme', 'dark');
653
+ // Inject anywhere in the host application
654
+ const lang = inject(KeepUiLanguageService);
655
+
656
+ // Switch to Spanish
657
+ lang.setLanguage('es');
658
+
659
+ // Read the active language
660
+ console.log(lang.activeLanguage()); // 'es'
383
661
  ```
384
662
 
385
- **Override tokens in your own CSS:**
663
+ Register via `provideKeepUiI18n()` in `app.config.ts`.
664
+
665
+ **Public properties:**
666
+
667
+ | Property | Type | Description |
668
+ |---|---|---|
669
+ | `activeLanguage` | — | Signal that reflects the currently active KeepUI locale. |
670
+ | `availableLanguages` | `readonly KeepUiLanguage[]` | Ordered list of all supported locales. |
671
+
672
+ **Public methods:**
673
+
674
+ - `setLanguage(lang: KeepUiLanguage): void` — Changes the active language for all KeepUI components.
675
+
676
+ ### `WebFileService`
677
+
678
+ Web implementation of `FilePort`.
679
+
680
+ Uses a hidden `<input type="file">` and the `FileReader` API to let the user
681
+ pick an image from the file system in a browser environment.
682
+
683
+ This implementation has no dependency on Capacitor or any native plugin.
684
+
685
+ **Public methods:**
686
+
687
+ - `pickImage(): Promise<FileResult>` —
688
+
689
+ ---
690
+
691
+ ## 9. Internationalisation (i18n)
692
+
693
+ Use typed constants instead of raw strings:
694
+
695
+ ```ts
696
+ import { KEEPUI_TRANSLATION_KEYS as T } from '@keepui/ui';
697
+
698
+ // Example
699
+ translocoService.translate(T.IMAGE_PREVIEW.SELECT_IMAGE);
700
+ ```
701
+
702
+ **Supported languages:** `en` (English), `es` (Spanish), `de` (German)
703
+
704
+ **Translation keys:**
705
+
706
+ | Constant path | Translation key |
707
+ |---|---|
708
+ | `IMAGE_PREVIEW.SELECT_IMAGE` | `imagePreview.selectImage` |
709
+ | `IMAGE_PREVIEW.LOADING` | `imagePreview.loading` |
710
+ | `IMAGE_PREVIEW.PREVIEW_ALT` | `imagePreview.previewAlt` |
711
+ | `IMAGE_PREVIEW.ERROR_UNEXPECTED` | `imagePreview.errorUnexpected` |
712
+ | `SIGNAL_TEXT_INPUT.SHOW_PASSWORD` | `signalTextInput.showPassword` |
713
+ | `SIGNAL_TEXT_INPUT.HIDE_PASSWORD` | `signalTextInput.hidePassword` |
714
+
715
+ **Changing language at runtime:**
716
+
717
+ ```ts
718
+ import { KeepUiLanguageService } from '@keepui/ui';
719
+
720
+ const lang = inject(KeepUiLanguageService);
721
+ lang.setLanguage('es'); // 'en' | 'es' | 'de'
722
+ ```
723
+
724
+ ---
725
+
726
+ ## 10. CSS design tokens
727
+
728
+ Override any variable in your CSS to customise the theme:
386
729
 
387
730
  ```css
388
731
  :root {
389
- --ku-action-primary: #6366f1;
732
+ --keepui-primary: #3b82f6;
733
+ --keepui-primary-hover: #2563eb;
734
+ --keepui-primary-active: #1d4ed8;
735
+ --keepui-primary-foreground: #ffffff;
736
+ --keepui-background: #f5f5f5;
737
+ --keepui-surface: #ffffff;
738
+ --keepui-surface-hover: #f0f0f0;
739
+ --keepui-border: #e0e0e0;
740
+ --keepui-border-strong: #cccccc;
741
+ --keepui-text: #1f2937;
742
+ --keepui-text-muted: #6b7280;
743
+ --keepui-text-disabled: #9ca3af;
744
+ --keepui-error: #dc2626;
745
+ --keepui-error-foreground: #ffffff;
746
+ --keepui-success: #16a34a;
747
+ --keepui-warning: #f59e0b;
748
+ --keepui-shadow-sm: 0 1px 3px rgba(0,0,0,.12);
749
+ --keepui-shadow-md: 0 3px 6px rgba(0,0,0,.15);
750
+ --keepui-shadow-lg: 0 6px 12px rgba(0,0,0,.18);
390
751
  }
391
752
 
392
753
  [data-theme="dark"] {
393
- --ku-action-primary: #818cf8;
754
+ --keepui-primary: #60a5fa;
755
+ --keepui-primary-foreground: #0f172a;
756
+ --keepui-background: #0f172a;
757
+ --keepui-surface: #1e293b;
758
+ --keepui-surface-hover: #334155;
759
+ --keepui-border: #334155;
760
+ --keepui-border-strong: #475569;
761
+ --keepui-text: #f1f5f9;
762
+ --keepui-text-muted: #94a3b8;
763
+ --keepui-text-disabled: #64748b;
764
+ --keepui-error: #f87171;
765
+ --keepui-error-foreground: #0f172a;
766
+ --keepui-success: #4ade80;
767
+ --keepui-warning: #fbbf24;
394
768
  }
395
769
  ```
396
770
 
397
- Refer to the bundled `themes.css` file for the full list of available tokens.
398
-
399
771
  ---
400
772
 
401
- ## Architecture Notes
773
+ ## 11. Tailwind utility classes
402
774
 
403
- ### Port/Adapter Pattern
775
+ Generated automatically after `@import "@keepui/ui/styles"` with Tailwind v4:
404
776
 
405
777
  ```
406
- ImagePreviewComponent
407
-
408
- │ inject(FILE_PORT)
409
-
410
- FilePort (interface)
411
-
412
- ┌───┴──────────────────┐
413
- │ │
414
- WebFileService CapacitorFileService
415
- (browser <input>) (@capacitor/camera)
778
+ bg-keepui-background bg-keepui-surface bg-keepui-surface-hover
779
+ bg-keepui-primary bg-keepui-primary-hover bg-keepui-primary-active
780
+ bg-keepui-error bg-keepui-success bg-keepui-warning
781
+
782
+ text-keepui-text text-keepui-text-muted text-keepui-text-disabled
783
+ text-keepui-primary text-keepui-primary-fg text-keepui-error
784
+
785
+ border-keepui-border border-keepui-border-strong border-keepui-primary
786
+
787
+ shadow-keepui-sm shadow-keepui-md shadow-keepui-lg
788
+
789
+ focus-visible:ring-keepui-primary
416
790
  ```
417
791
 
418
- - Components depend only on the `FilePort` interface via the `FILE_PORT` injection token.
419
- - `provideKeepUi()` registers `WebFileService`.
420
- - `provideKeepUiCapacitor()` registers `CapacitorFileService`.
421
- - Capacitor dependencies are **not** pulled into web builds.
792
+ ---
793
+ ### 5.10 `TabGroupComponent`
422
794
 
423
- ### Secondary Entrypoint
795
+ **Selector:** `keepui-tab-group`
796
+ **Import:** `import { TabGroupComponent, Tab, TabGroupVariant } from '@keepui/ui';`
424
797
 
425
- `@keepui/ui/capacitor` is a secondary ng-packagr entrypoint compiled alongside the main package. Keeping it separate means Capacitor dependencies are never included in web-only builds.
798
+ Accessible pill-style tab group implementing the WAI-ARIA `tablist` pattern.
799
+ Supports icons, disabled tabs, keyboard navigation (arrow keys, `Home`, `End`)
800
+ and two visual variants. No internal i18n strings — tab labels are provided
801
+ by the consumer.
426
802
 
427
- ### Types
803
+ ```html
804
+ <!-- Default variant -->
805
+ <keepui-tab-group
806
+ [tabs]="tabs"
807
+ [selectedTabId]="activeTab"
808
+ (tabChange)="activeTab = $event"
809
+ ariaLabel="Main sections"
810
+ />
428
811
 
429
- Every component that exposes custom TypeScript types places those types in a sibling `<component>.types.ts` file and re-exports them from the main entrypoint. Import them directly from `@keepui/ui`:
812
+ <!-- Filled variant -->
813
+ <keepui-tab-group
814
+ variant="filled"
815
+ [tabs]="tabs"
816
+ [selectedTabId]="activeTab"
817
+ (tabChange)="activeTab = $event"
818
+ />
819
+ ```
430
820
 
431
821
  ```ts
432
- import {
433
- ButtonVariant, ButtonSize, ButtonShape, ButtonType,
434
- CardVariant, CardPadding, CardColors,
435
- IconActionButtonVariant, IconActionButtonType,
436
- } from '@keepui/ui';
822
+ readonly tabs: Tab[] = [
823
+ { id: 'home', label: 'Home' },
824
+ { id: 'profile', label: 'Profile', icon: 'user-icon' },
825
+ { id: 'settings', label: 'Settings', disabled: true },
826
+ ];
437
827
  ```
438
828
 
829
+ #### Inputs
830
+
831
+ | Input | Type | Default | Description |
832
+ |---|---|---|---|
833
+ | `tabs` | `Tab[]` | *(required)* | List of tabs to render. |
834
+ | `selectedTabId` | `string` | *(required)* | ID of the currently active tab. |
835
+ | `variant` | `TabGroupVariant` | `'default'` | Visual style: `'default'` or `'filled'`. |
836
+ | `ariaLabel` | `string` | `''` | Accessible label for the `tablist` element. |
837
+
838
+ #### Outputs
839
+
840
+ | Output | Emitter type | Description |
841
+ |---|---|---|
842
+ | `tabChange` | `OutputEmitterRef<string>` | Emits the `id` of the selected tab. |
843
+
844
+ #### `Tab` interface
845
+
846
+ | Property | Type | Required | Description |
847
+ |---|---|---|---|
848
+ | `id` | `string` | ✅ | Unique identifier. |
849
+ | `label` | `string` | ✅ | Visible tab label. |
850
+ | `icon` | `string` | — | SVG symbol ID (without `#`) for an optional icon. |
851
+ | `disabled` | `boolean` | — | When `true`, the tab is not selectable. Default `false`. |
852
+
853
+ #### Accessibility
854
+
855
+ - Container: `role="tablist"` with optional `aria-label`.
856
+ - Each button: `role="tab"`, `aria-selected`, `aria-disabled`, managed `tabindex`.
857
+ - Keyboard: `ArrowRight`/`ArrowLeft` (and `Down`/`Up`) navigate between tabs; `Home`/`End` jump to extremes.
858
+ - Focus ring: `focus-visible:ring-2 focus-visible:ring-ku-action-primary`.
859
+ - Touch target: `min-h-[2.75rem]` on every tab button.
860
+
439
861
  ---
862
+ ### 5.11 `StepperComponent`
440
863
 
441
- ## Testing
864
+ **Selector:** `keepui-stepper`
865
+ **Import:** `import { StepperComponent, StepperStep, StepperSize, StepperOrientation } from '@keepui/ui';`
442
866
 
443
- Use `MockFileService` to test components that depend on `FILE_PORT`:
867
+ Visual step-progress indicator with optional interactive navigation.
868
+ Renders numbered circles connected by animated progress bars. Completed steps
869
+ show a check-mark; the active step is highlighted with a ring; future steps are muted.
870
+ Steps can carry an icon that replaces the number.
444
871
 
445
- ```ts
446
- import { MockFileService, FILE_PORT } from '@keepui/ui';
872
+ ```html
873
+ <!-- Read-only progress indicator -->
874
+ <keepui-stepper [steps]="steps" [activeIndex]="1" />
875
+
876
+ <!-- Interactive (allows going back to completed steps) -->
877
+ <keepui-stepper
878
+ [steps]="steps"
879
+ [activeIndex]="currentStep"
880
+ (stepChange)="currentStep = $event"
881
+ ariaLabel="Registration process"
882
+ />
447
883
 
448
- TestBed.configureTestingModule({
449
- imports: [ImagePreviewComponent],
450
- providers: [{ provide: FILE_PORT, useClass: MockFileService }],
451
- });
884
+ <!-- Small, vertical -->
885
+ <keepui-stepper
886
+ [steps]="steps"
887
+ [activeIndex]="currentStep"
888
+ size="sm"
889
+ orientation="vertical"
890
+ />
452
891
  ```
453
892
 
454
- `MockFileService` resolves successfully by default. Set `nextError` to test error paths:
455
-
456
893
  ```ts
457
- const mock = TestBed.inject(FILE_PORT) as MockFileService;
458
- mock.nextError = new Error('Camera cancelled');
894
+ readonly steps: StepperStep[] = [
895
+ { id: '1', label: 'Account' },
896
+ { id: '2', label: 'Profile', icon: 'user-icon' },
897
+ { id: '3', label: 'Plan' },
898
+ { id: '4', label: 'Confirm', disabled: true },
899
+ ];
459
900
  ```
460
901
 
461
- ---
902
+ #### Inputs
462
903
 
463
- ## Building
904
+ | Input | Type | Default | Description |
905
+ |---|---|---|---|
906
+ | `steps` | `StepperStep[]` | *(required)* | List of steps to render. |
907
+ | `activeIndex` | `number` | `0` | Zero-based index of the currently active step. |
908
+ | `size` | `StepperSize` | `'md'` | Size of step circles and connectors: `'md'` or `'sm'`. |
909
+ | `orientation` | `StepperOrientation` | `'horizontal'` | Layout direction: `'horizontal'` or `'vertical'`. |
910
+ | `ariaLabel` | `string` | `''` | Accessible label for the `<nav>` element. |
464
911
 
465
- ```bash
466
- # Production build (APF, partial compilation):
467
- npm run build
912
+ #### Outputs
468
913
 
469
- # Development build (without schematics):
470
- npm run build:dev
914
+ | Output | Emitter type | Description |
915
+ |---|---|---|
916
+ | `stepChange` | `OutputEmitterRef<number>` | Emits the zero-based index of the clicked step (completed or active steps only). |
471
917
 
472
- # Schematics only:
473
- npm run build:schematics
918
+ #### `StepperStep` interface
474
919
 
475
- # Run unit tests:
476
- npm test
477
- ```
920
+ | Property | Type | Required | Description |
921
+ |---|---|---|---|
922
+ | `id` | `string` | ✅ | Unique identifier. |
923
+ | `label` | `string` | ✅ | Label shown below the step circle. |
924
+ | `icon` | `string` | — | SVG symbol ID (without `#`) rendered inside the circle instead of the step number. |
925
+ | `disabled` | `boolean` | — | When `true`, the step is not interactive. Default `false`. |
478
926
 
479
- Output is placed in `dist/keep-ui/` following Angular Package Format.
927
+ #### Accessibility
928
+
929
+ - Container: `<nav>` with optional `aria-label`.
930
+ - List: `role="list"` / `role="listitem"` with `aria-current="step"` on the active item.
931
+ - Completed and active steps render as `<button>` elements with `aria-label` equal to the step label.
932
+ - Future and disabled steps render as inert `<span>` elements.
933
+ - Focus ring: `focus-visible:ring-2 focus-visible:ring-ku-action-primary`.
934
+ - Touch target: `min-h-[2.75rem] min-w-[2.75rem]` on every interactive step bubble.
480
935
 
481
936
  ---
482
937
 
483
- ## Publishing to npm
938
+ ## 12. Integration checklist
484
939
 
485
- ```bash
486
- npm run build
487
- cd dist/keep-ui
488
- npm publish --access public
489
- ```
940
+ - [ ] `@keepui/ui` installed in `package.json`
941
+ - [ ] `@import "@keepui/ui/styles"` in global stylesheet
942
+ - [ ] Tailwind v4 configured (via `@tailwindcss/postcss` or `@tailwindcss/vite`)
943
+ - [ ] `provideKeepUi()` **or** `provideKeepUiCapacitor()` registered in `app.config.ts`
944
+ - [ ] `provideKeepUiI18n()` registered in `app.config.ts`
945
+ - [ ] Components imported individually in each standalone component or NgModule
946
+ - [ ] `[data-theme="dark"]` toggle wired if dark mode switching is needed
947
+ - [ ] `KeepUiLanguageService.setLanguage()` called if runtime i18n is needed
948
+
949
+ ---
490
950
 
491
- Ensure `package.json` has the correct `name`, `version`, and `peerDependencies` before publishing.
951
+ ## 13. What NOT to do
492
952
 
953
+ - Do **not** import from `@keepui/ui/src/lib/...` — only from `@keepui/ui` or `@keepui/ui/capacitor`.
954
+ - Do **not** call both `provideKeepUi()` and `provideKeepUiCapacitor()`.
955
+ - Do **not** hardcode `FILE_PORT` implementations inside components — always use the token.
956
+ - Do **not** skip `@import "@keepui/ui/styles"` — without it, all theme utility classes are missing.
957
+ - Do **not** import `@capacitor/*` in code that also imports `@keepui/ui` core.