@open-rlb/ng-bootstrap 3.3.24 → 3.3.26
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.
- package/README.md +3 -1
- package/package.json +2 -2
- package/schematics/ng-add/claude-skills/date-tz/SKILL.md +375 -0
- package/schematics/ng-add/claude-skills/rlb-calendar/SKILL.md +450 -0
- package/schematics/ng-add/claude-skills/rlb-components/SKILL.md +364 -0
- package/schematics/ng-add/claude-skills/rlb-datatable/SKILL.md +295 -0
- package/schematics/ng-add/claude-skills/rlb-design/SKILL.md +120 -0
- package/schematics/ng-add/claude-skills/rlb-inputs/SKILL.md +326 -0
- package/schematics/ng-add/claude-skills/rlb-modals/SKILL.md +236 -0
- package/schematics/ng-add/index.js +19 -5
- package/schematics/ng-add/schema.json +5 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rlb-design
|
|
3
|
+
description: Bootstrap 5.3 + @open-rlb/ng-bootstrap design guidance for layout, spacing, typography, color palette, and responsive design. Use when making visual or design decisions for UIs in this project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RLB Bootstrap 5.3 Design Skill
|
|
7
|
+
|
|
8
|
+
You are an expert in designing Angular UIs using **Bootstrap 5.3** with the **@open-rlb/ng-bootstrap** component library. This skill guides layout, spacing, typography, color, and responsive design decisions.
|
|
9
|
+
|
|
10
|
+
## Bootstrap 5.3 Color Palette
|
|
11
|
+
|
|
12
|
+
The library uses a typed `Color` union:
|
|
13
|
+
```typescript
|
|
14
|
+
type Color = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
All components that accept a `color` input use this type.
|
|
18
|
+
|
|
19
|
+
## Layout & Grid
|
|
20
|
+
|
|
21
|
+
Use Bootstrap's 12-column grid system:
|
|
22
|
+
```html
|
|
23
|
+
<div class="container">
|
|
24
|
+
<div class="row">
|
|
25
|
+
<div class="col-12 col-md-6 col-lg-4">...</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Breakpoints: `xs` (default), `sm` (576px), `md` (768px), `lg` (992px), `xl` (1200px), `xxl` (1400px).
|
|
31
|
+
|
|
32
|
+
## Spacing
|
|
33
|
+
|
|
34
|
+
Use Bootstrap spacing utilities `m-{size}`, `p-{size}`, `gap-{size}` (0–5, auto).
|
|
35
|
+
Directional: `mt`, `mb`, `ms`, `me`, `mx`, `my`, `pt`, `pb`, `ps`, `pe`, `px`, `py`.
|
|
36
|
+
|
|
37
|
+
## Typography
|
|
38
|
+
|
|
39
|
+
- Headings: `h1`–`h6`, `.display-1`–`.display-6`
|
|
40
|
+
- Text utilities: `fw-bold`, `fw-semibold`, `fst-italic`, `text-{color}`, `text-{align}`
|
|
41
|
+
- Size: `fs-1`–`fs-6`
|
|
42
|
+
- Truncation: `text-truncate`, `text-break`
|
|
43
|
+
|
|
44
|
+
## Flexbox & Grid Utilities
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<div class="d-flex align-items-center justify-content-between gap-2">...</div>
|
|
48
|
+
<div class="d-grid gap-2">...</div>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Background & Border
|
|
52
|
+
|
|
53
|
+
- Background: `bg-{color}`, `bg-opacity-{10|25|50|75|100}`
|
|
54
|
+
- Border: `border`, `border-{color}`, `border-{size}`, `rounded`, `rounded-{size}`, `rounded-pill`
|
|
55
|
+
- Shadow: `shadow-sm`, `shadow`, `shadow-lg`
|
|
56
|
+
|
|
57
|
+
## Sizing Utilities
|
|
58
|
+
|
|
59
|
+
- Width: `w-25`, `w-50`, `w-75`, `w-100`, `w-auto`, `mw-100`, `vw-100`
|
|
60
|
+
- Height: `h-25`, `h-50`, `h-75`, `h-100`, `h-auto`, `vh-100`, `min-vh-100`
|
|
61
|
+
|
|
62
|
+
## Overflow & Position
|
|
63
|
+
|
|
64
|
+
- `overflow-auto`, `overflow-hidden`, `overflow-scroll`
|
|
65
|
+
- `position-relative`, `position-absolute`, `position-fixed`, `position-sticky`
|
|
66
|
+
- `top-0`, `start-0`, `bottom-0`, `end-0`, `translate-middle`
|
|
67
|
+
|
|
68
|
+
## Visibility & Display
|
|
69
|
+
|
|
70
|
+
- `d-none`, `d-{bp}-block`, `d-{bp}-flex`, `d-{bp}-none`
|
|
71
|
+
- `visible`, `invisible`
|
|
72
|
+
|
|
73
|
+
## RLB Theme Conventions
|
|
74
|
+
|
|
75
|
+
- Use `color="primary"` for main actions, `color="secondary"` for secondary.
|
|
76
|
+
- Use `color="danger"` for destructive actions (delete, remove).
|
|
77
|
+
- Use `color="success"` / `color="warning"` / `color="info"` for status feedback.
|
|
78
|
+
- Prefer `outline` buttons for secondary actions inside cards or toolbars.
|
|
79
|
+
- Use `size="sm"` for table rows and dense UIs; `size="lg"` for hero sections.
|
|
80
|
+
|
|
81
|
+
## Page Layout Pattern
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<div class="container-fluid p-0 d-flex flex-column min-vh-100">
|
|
85
|
+
<rlb-navbar>...</rlb-navbar>
|
|
86
|
+
<div class="d-flex flex-grow-1 overflow-hidden">
|
|
87
|
+
<rlb-sidebar>...</rlb-sidebar>
|
|
88
|
+
<main class="flex-grow-1 overflow-auto p-3">
|
|
89
|
+
<!-- page content -->
|
|
90
|
+
</main>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Card-based Sections
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<div class="row g-3">
|
|
99
|
+
<div class="col-12 col-md-6 col-xl-4">
|
|
100
|
+
<rlb-card>
|
|
101
|
+
<rlb-card-header>Title</rlb-card-header>
|
|
102
|
+
<rlb-card-body>Content</rlb-card-body>
|
|
103
|
+
</rlb-card>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Responsive Helpers
|
|
109
|
+
|
|
110
|
+
- Hide on mobile: `d-none d-md-block`
|
|
111
|
+
- Show only on mobile: `d-block d-md-none`
|
|
112
|
+
- Stack to row on md+: `flex-column flex-md-row`
|
|
113
|
+
|
|
114
|
+
## Design Principles
|
|
115
|
+
|
|
116
|
+
1. Mobile-first: design for small screens, enhance for larger ones.
|
|
117
|
+
2. Prefer utility classes over custom CSS.
|
|
118
|
+
3. Use semantic HTML (`main`, `section`, `nav`, `aside`).
|
|
119
|
+
4. Keep component `color` consistent with the app's design tokens.
|
|
120
|
+
5. Use `gap-*` on flex/grid containers instead of margin on children.
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rlb-inputs
|
|
3
|
+
description: Expert guidance for @open-rlb/ng-bootstrap form input components (ControlValueAccessor-based, integrate with reactive and template-driven forms). Use when building forms or using input components.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RLB ng-Bootstrap Form Inputs Skill
|
|
7
|
+
|
|
8
|
+
You are an expert in the **@open-rlb/ng-bootstrap** form input components. All inputs implement `ControlValueAccessor` and integrate seamlessly with Angular Reactive Forms and Template-driven forms. They use Angular 18+ signals and `ChangeDetectionStrategy.OnPush`.
|
|
9
|
+
|
|
10
|
+
## Common Pattern
|
|
11
|
+
|
|
12
|
+
All inputs share these base inputs:
|
|
13
|
+
- `disabled: boolean` — disables the control
|
|
14
|
+
- `readonly: boolean` — read-only display
|
|
15
|
+
- `size: 'small' | 'large' | undefined` — field size variant
|
|
16
|
+
- `id: string` — HTML id (auto-generated if omitted)
|
|
17
|
+
- `enable-validation: boolean` — show Bootstrap validation styles (invalid/valid)
|
|
18
|
+
|
|
19
|
+
All inputs bind via `formControlName` or `[(ngModel)]`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## rlb-input — Text / Number / Date
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<!-- Text -->
|
|
27
|
+
<rlb-input formControlName="username" placeholder="Enter username" [enable-validation]="true"></rlb-input>
|
|
28
|
+
|
|
29
|
+
<!-- Number with bounds -->
|
|
30
|
+
<rlb-input formControlName="age" type="number" [min]="0" [max]="120" [step]="1"></rlb-input>
|
|
31
|
+
|
|
32
|
+
<!-- Date (stores as DateTz, timezone-aware) -->
|
|
33
|
+
<rlb-input formControlName="birthDate" type="datetime-local" date-type="date-tz" timezone="Europe/Rome"></rlb-input>
|
|
34
|
+
|
|
35
|
+
<!-- Date stored as ISO string -->
|
|
36
|
+
<rlb-input formControlName="startDate" type="datetime-local" date-type="string" timezone="UTC"></rlb-input>
|
|
37
|
+
|
|
38
|
+
<!-- Password -->
|
|
39
|
+
<rlb-input formControlName="password" type="password" [enable-validation]="true"></rlb-input>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Inputs:**
|
|
43
|
+
| Input | Type | Default | Description |
|
|
44
|
+
|-------|------|---------|-------------|
|
|
45
|
+
| `type` | `string` | `'text'` | HTML input type |
|
|
46
|
+
| `placeholder` | `string` | — | Placeholder text |
|
|
47
|
+
| `size` | `'small'\|'large'` | — | Field size |
|
|
48
|
+
| `min` / `max` / `step` | `number` | — | Number constraints |
|
|
49
|
+
| `date-type` | `'date'\|'string'\|'number'\|'date-tz'` | `'date-tz'` | Date value format |
|
|
50
|
+
| `timezone` | `string` | `'UTC'` | Timezone for date handling |
|
|
51
|
+
| `readonly` | `boolean` | `false` | — |
|
|
52
|
+
| `disabled` | `boolean` | `false` | — |
|
|
53
|
+
| `enable-validation` | `boolean` | `false` | Show validation state |
|
|
54
|
+
| `ext-validation` | `boolean` | `false` | External validation display |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## rlb-select — Dropdown Select
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<!-- Single select -->
|
|
62
|
+
<rlb-select formControlName="country" placeholder="Choose country" [enable-validation]="true">
|
|
63
|
+
<rlb-option value="it">Italy</rlb-option>
|
|
64
|
+
<rlb-option value="de">Germany</rlb-option>
|
|
65
|
+
<rlb-option value="fr">France</rlb-option>
|
|
66
|
+
</rlb-select>
|
|
67
|
+
|
|
68
|
+
<!-- Multiple select -->
|
|
69
|
+
<rlb-select formControlName="roles" [multiple]="true" [display]="5">
|
|
70
|
+
<rlb-option value="admin">Admin</rlb-option>
|
|
71
|
+
<rlb-option value="editor">Editor</rlb-option>
|
|
72
|
+
<rlb-option value="viewer">Viewer</rlb-option>
|
|
73
|
+
</rlb-select>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Inputs:** `placeholder`, `size`, `disabled`, `readonly`, `multiple`, `display` (visible rows for multiple), `inputId`, `enable-validation`
|
|
77
|
+
**Returns:** `string` (single) or `string[]` (multiple)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## rlb-checkbox — Checkbox
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<rlb-checkbox formControlName="accepted" id="terms">
|
|
85
|
+
I accept the terms and conditions
|
|
86
|
+
</rlb-checkbox>
|
|
87
|
+
|
|
88
|
+
<!-- Indeterminate state -->
|
|
89
|
+
<rlb-checkbox formControlName="selectAll" [indeterminate]="someSelected" id="select-all">
|
|
90
|
+
Select all
|
|
91
|
+
</rlb-checkbox>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Inputs:** `disabled`, `readonly`, `indeterminate`, `id`
|
|
95
|
+
**Returns:** `boolean | undefined`
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## rlb-switch — Toggle Switch
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<rlb-switch formControlName="notifications" id="notif-toggle">
|
|
103
|
+
Enable notifications
|
|
104
|
+
</rlb-switch>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Inputs:** `disabled`, `readonly`, `size`, `id`
|
|
108
|
+
**Returns:** `boolean`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## rlb-radio — Radio Group
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<rlb-radio formControlName="gender" id="gender">
|
|
116
|
+
<rlb-option value="m">Male</rlb-option>
|
|
117
|
+
<rlb-option value="f">Female</rlb-option>
|
|
118
|
+
<rlb-option value="o">Other</rlb-option>
|
|
119
|
+
</rlb-radio>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Inputs:** `disabled`, `readonly`, `id`
|
|
123
|
+
**Returns:** `string`
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## rlb-textarea — Multi-line Text
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<rlb-textarea formControlName="notes" placeholder="Add notes..." [rows]="5" [enable-validation]="true"></rlb-textarea>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Inputs:** `disabled`, `readonly`, `placeholder`, `size`, `rows` (default: 3), `id`
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## rlb-autocomplete — Searchable Input
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<rlb-autocomplete
|
|
141
|
+
formControlName="city"
|
|
142
|
+
placeholder="Search city..."
|
|
143
|
+
[autocomplete]="searchCities"
|
|
144
|
+
[chars-to-search]="2"
|
|
145
|
+
[loading]="isSearching"
|
|
146
|
+
[enable-validation]="true"
|
|
147
|
+
(selected)="onCitySelected($event)"
|
|
148
|
+
></rlb-autocomplete>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Search function — supports sync array, Promise, or Observable
|
|
153
|
+
searchCities: AutocompleteFn = (query: string) => {
|
|
154
|
+
return this.cityService.search(query); // returns Observable<AutocompleteItem[]>
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// AutocompleteItem interface
|
|
158
|
+
interface AutocompleteItem {
|
|
159
|
+
label: string;
|
|
160
|
+
value: any;
|
|
161
|
+
icon?: string;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Inputs:**
|
|
166
|
+
| Input | Type | Default | Description |
|
|
167
|
+
|-------|------|---------|-------------|
|
|
168
|
+
| `autocomplete` | `AutocompleteFn` | — | Search function (required) |
|
|
169
|
+
| `placeholder` | `string` | — | — |
|
|
170
|
+
| `chars-to-search` | `number` | `3` | Min chars before search |
|
|
171
|
+
| `loading` | `boolean` | `false` | Show loading indicator |
|
|
172
|
+
| `max-height` | `number` | `200` | Dropdown max height (px) |
|
|
173
|
+
| `type` | `string` | `'text'` | Input type |
|
|
174
|
+
| `size` | `'small'\|'large'` | — | — |
|
|
175
|
+
| `menu-max-width` | `number\|null` | — | Dropdown max width |
|
|
176
|
+
| `input-autocomplete` | `string` | `'off'` | HTML autocomplete attr |
|
|
177
|
+
| `enable-validation` | `boolean` | `false` | — |
|
|
178
|
+
|
|
179
|
+
**Outputs:** `selected: AutocompleteItem`
|
|
180
|
+
**Note:** Debounces search by 300ms automatically.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## rlb-file — File Upload
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<!-- Single file -->
|
|
188
|
+
<rlb-file formControlName="document" accept=".pdf,.doc" [enable-validation]="true"></rlb-file>
|
|
189
|
+
|
|
190
|
+
<!-- Multiple files -->
|
|
191
|
+
<rlb-file formControlName="images" [multiple]="true" accept="image/*"></rlb-file>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Inputs:** `disabled`, `readonly`, `multiple`, `size`, `accept`, `id`
|
|
195
|
+
**Returns:** `File | File[] | null`
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## rlb-dnd-file — Drag & Drop File
|
|
200
|
+
|
|
201
|
+
```html
|
|
202
|
+
<rlb-dnd-file formControlName="upload" [multiple]="true" accept="image/*"></rlb-dnd-file>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## rlb-color — Color Picker
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<rlb-color formControlName="themeColor"></rlb-color>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Returns:** `string` (hex color, e.g. `#ff0000`)
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## rlb-range — Range Slider
|
|
218
|
+
|
|
219
|
+
```html
|
|
220
|
+
<rlb-range formControlName="volume" [min]="0" [max]="100" [step]="5"></rlb-range>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## rlb-input-group — Input with Addons
|
|
226
|
+
|
|
227
|
+
```html
|
|
228
|
+
<rlb-input-group>
|
|
229
|
+
<span class="input-group-text">@</span>
|
|
230
|
+
<rlb-input formControlName="username"></rlb-input>
|
|
231
|
+
</rlb-input-group>
|
|
232
|
+
|
|
233
|
+
<rlb-input-group>
|
|
234
|
+
<rlb-input formControlName="amount" type="number"></rlb-input>
|
|
235
|
+
<span class="input-group-text">EUR</span>
|
|
236
|
+
</rlb-input-group>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## rlb-input-validation — Validation Messages
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<rlb-input-validation [control]="form.get('email')">
|
|
245
|
+
<ng-container *rlbError="'required'">Email is required</ng-container>
|
|
246
|
+
<ng-container *rlbError="'email'">Invalid email format</ng-container>
|
|
247
|
+
</rlb-input-validation>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Form Field Wrapper (rlb-form-fields)
|
|
253
|
+
|
|
254
|
+
```html
|
|
255
|
+
<rlb-form-field label="Email" [required]="true">
|
|
256
|
+
<rlb-input formControlName="email" type="email" [enable-validation]="true"></rlb-input>
|
|
257
|
+
</rlb-form-field>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Reactive Form Integration Pattern
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { FormBuilder, Validators } from '@angular/forms';
|
|
266
|
+
|
|
267
|
+
@Component({
|
|
268
|
+
template: `
|
|
269
|
+
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
270
|
+
<div class="mb-3">
|
|
271
|
+
<label class="form-label">Name *</label>
|
|
272
|
+
<rlb-input formControlName="name" [enable-validation]="true"></rlb-input>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="mb-3">
|
|
275
|
+
<label class="form-label">Role</label>
|
|
276
|
+
<rlb-select formControlName="role" [enable-validation]="true">
|
|
277
|
+
<rlb-option value="admin">Admin</rlb-option>
|
|
278
|
+
<rlb-option value="user">User</rlb-option>
|
|
279
|
+
</rlb-select>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="mb-3">
|
|
282
|
+
<rlb-checkbox formControlName="active" id="active-check">Active</rlb-checkbox>
|
|
283
|
+
</div>
|
|
284
|
+
<button rlb-button type="submit" color="primary" [disabled]="form.invalid">Save</button>
|
|
285
|
+
</form>
|
|
286
|
+
`
|
|
287
|
+
})
|
|
288
|
+
export class MyFormComponent {
|
|
289
|
+
form = this.fb.group({
|
|
290
|
+
name: ['', Validators.required],
|
|
291
|
+
role: ['user', Validators.required],
|
|
292
|
+
active: [true]
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
constructor(private fb: FormBuilder) {}
|
|
296
|
+
|
|
297
|
+
onSubmit() {
|
|
298
|
+
if (this.form.valid) {
|
|
299
|
+
console.log(this.form.value);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Validators
|
|
308
|
+
|
|
309
|
+
The library provides custom validators:
|
|
310
|
+
```typescript
|
|
311
|
+
import { RequiredAutocompleteValidator } from '@open-rlb/ng-bootstrap';
|
|
312
|
+
|
|
313
|
+
form = this.fb.group({
|
|
314
|
+
city: ['', [Validators.required, RequiredAutocompleteValidator]]
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Best Practices
|
|
321
|
+
|
|
322
|
+
1. Always set `[enable-validation]="true"` on inputs inside forms with validation.
|
|
323
|
+
2. Use `rlb-autocomplete` for any remote search — pass an `Observable`-returning function.
|
|
324
|
+
3. For date inputs, always specify `timezone` explicitly; default is `'UTC'`.
|
|
325
|
+
4. Use `date-type="date-tz"` when the model uses `@open-rlb/date-tz` DateTz objects.
|
|
326
|
+
5. Wrap inputs in a `<div class="mb-3">` with a `<label class="form-label">` for correct Bootstrap spacing.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rlb-modals
|
|
3
|
+
description: Expert guidance for the @open-rlb/ng-bootstrap modal system: programmatic modals via ModalService and the declarative [rlb-modal] directive. Use when opening, building, or configuring modals.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RLB ng-Bootstrap Modals Skill
|
|
7
|
+
|
|
8
|
+
You are an expert in the **@open-rlb/ng-bootstrap** modal system. Modals support programmatic opening via `ModalService` and declarative usage via the `[rlb-modal]` directive.
|
|
9
|
+
|
|
10
|
+
## Core Types
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
type ModalCloseReason = 'ok' | 'cancel' | 'close';
|
|
14
|
+
type ModalType = 'success' | 'info' | 'warning' | 'error';
|
|
15
|
+
|
|
16
|
+
interface ModalOptions {
|
|
17
|
+
backdrop?: boolean | 'static'; // 'static' = click outside doesn't close
|
|
18
|
+
scrollable?: boolean;
|
|
19
|
+
verticalcentered?: boolean;
|
|
20
|
+
animation?: boolean;
|
|
21
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
22
|
+
fullscreen?: boolean | 'sm-down' | 'md-down' | 'lg-down' | 'xl-down' | 'xxl-down';
|
|
23
|
+
focus?: boolean;
|
|
24
|
+
keyboard?: boolean; // Esc key closes modal
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ModalData<T = any> {
|
|
28
|
+
data?: T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ModalResult<T = any> {
|
|
32
|
+
reason: ModalCloseReason;
|
|
33
|
+
data?: T;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## ModalService — Programmatic API
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { ModalService } from '@open-rlb/ng-bootstrap';
|
|
43
|
+
|
|
44
|
+
@Component({ ... })
|
|
45
|
+
export class MyComponent {
|
|
46
|
+
constructor(private modal: ModalService) {}
|
|
47
|
+
|
|
48
|
+
// Open a named modal (registered component)
|
|
49
|
+
openCustomModal() {
|
|
50
|
+
this.modal.openModal<InputData, OutputData>(
|
|
51
|
+
'my-modal-name',
|
|
52
|
+
{ data: { userId: 42 } },
|
|
53
|
+
{ size: 'lg', verticalcentered: true }
|
|
54
|
+
).subscribe(result => {
|
|
55
|
+
if (result.reason === 'ok') {
|
|
56
|
+
console.log(result.data); // OutputData
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Simple info/alert modal
|
|
62
|
+
showAlert() {
|
|
63
|
+
this.modal.openSimpleModal(
|
|
64
|
+
'Title',
|
|
65
|
+
'Message body text.',
|
|
66
|
+
'Optional header',
|
|
67
|
+
'Close', // ok button label
|
|
68
|
+
'info' // ModalType
|
|
69
|
+
).subscribe();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Confirmation modal (ok + cancel)
|
|
73
|
+
confirmDelete() {
|
|
74
|
+
this.modal.openConfirmModal(
|
|
75
|
+
'Delete item?',
|
|
76
|
+
'This action cannot be undone.',
|
|
77
|
+
'Confirm', // header
|
|
78
|
+
'Delete', // ok label
|
|
79
|
+
'Cancel', // cancel label
|
|
80
|
+
'warning' // ModalType
|
|
81
|
+
).subscribe(result => {
|
|
82
|
+
if (result.reason === 'ok') {
|
|
83
|
+
this.deleteItem();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## [rlb-modal] Directive — Declarative Usage
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<!-- Trigger button -->
|
|
96
|
+
<button rlb-button color="primary" (click)="myModal.show()">Open Modal</button>
|
|
97
|
+
|
|
98
|
+
<!-- Modal template -->
|
|
99
|
+
<div rlb-modal id="my-modal" [data-instance]="myModal" [data-options]="modalOptions">
|
|
100
|
+
<div class="modal-header">
|
|
101
|
+
<h5 class="modal-title">Modal Title</h5>
|
|
102
|
+
<button type="button" class="btn-close" data-modal-reason="close"></button>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="modal-body">
|
|
105
|
+
Modal body content here.
|
|
106
|
+
</div>
|
|
107
|
+
<div class="modal-footer">
|
|
108
|
+
<button rlb-button color="secondary" data-modal-reason="cancel">Cancel</button>
|
|
109
|
+
<button rlb-button color="primary" data-modal-reason="ok">Confirm</button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
@Component({ ... })
|
|
116
|
+
export class MyComponent {
|
|
117
|
+
myModal!: IModal; // reference set by the directive
|
|
118
|
+
modalOptions: ModalOptions = {
|
|
119
|
+
size: 'md',
|
|
120
|
+
backdrop: 'static',
|
|
121
|
+
verticalcentered: true
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Close reason attributes:** add `data-modal-reason="ok"`, `data-modal-reason="cancel"`, or `data-modal-reason="close"` to any button inside the modal to auto-close with that reason.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Modal Events
|
|
131
|
+
|
|
132
|
+
Listen to Bootstrap modal lifecycle events on the modal element:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// 'show.bs.modal' | 'shown.bs.modal' | 'hide.bs.modal' | 'hidden.bs.modal' | 'hidePrevented.bs.modal'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Modal with Form
|
|
141
|
+
|
|
142
|
+
Pattern for a modal that collects data and returns it on confirm:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
@Component({
|
|
146
|
+
template: `
|
|
147
|
+
<div rlb-modal id="edit-modal" [data-instance]="modal">
|
|
148
|
+
<div class="modal-header">
|
|
149
|
+
<h5 class="modal-title">Edit User</h5>
|
|
150
|
+
<button class="btn-close" data-modal-reason="close"></button>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="modal-body">
|
|
153
|
+
<form [formGroup]="form">
|
|
154
|
+
<div class="mb-3">
|
|
155
|
+
<label class="form-label">Name</label>
|
|
156
|
+
<rlb-input formControlName="name" [enable-validation]="true"></rlb-input>
|
|
157
|
+
</div>
|
|
158
|
+
</form>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="modal-footer">
|
|
161
|
+
<button rlb-button color="secondary" data-modal-reason="cancel">Cancel</button>
|
|
162
|
+
<button rlb-button color="primary" (click)="save()">Save</button>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
`
|
|
166
|
+
})
|
|
167
|
+
export class EditModalComponent {
|
|
168
|
+
modal!: IModal;
|
|
169
|
+
form = new FormGroup({ name: new FormControl('') });
|
|
170
|
+
|
|
171
|
+
save() {
|
|
172
|
+
if (this.form.valid) {
|
|
173
|
+
this.modal.hide('ok');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Multi-step (wizard) modal
|
|
182
|
+
|
|
183
|
+
For a multi-step form inside a modal, put a **carousel driven as a controlled stepper** in the
|
|
184
|
+
`modal-body` and Back / Next / Finish buttons in the `modal-footer`. The carousel runs in manual mode
|
|
185
|
+
(`autoplay="none" no-touch hide-controls hide-indicators`); bind `[current-slide]` to a signal and
|
|
186
|
+
update it from the buttons, gate `Next` on the current step's validity, and call `modal.hide('ok')`
|
|
187
|
+
on finish. See the **Wizard pattern** in the `rlb-components` skill for the full carousel details.
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<div rlb-modal id="wizard-modal" [data-instance]="modal">
|
|
191
|
+
<div class="modal-header"><h5 class="modal-title">Create</h5>
|
|
192
|
+
<button class="btn-close" data-modal-reason="close"></button>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="modal-body" [formGroup]="form">
|
|
195
|
+
<rlb-carousel autoplay="none" no-touch hide-controls hide-indicators
|
|
196
|
+
[current-slide]="page()" (current-slideChange)="page.set($event)"
|
|
197
|
+
(slide-count)="count.set($event)" id="wizard">
|
|
198
|
+
<rlb-carousel-slide active><div formGroupName="step1"><!-- … --></div></rlb-carousel-slide>
|
|
199
|
+
<rlb-carousel-slide><div formGroupName="step2"><!-- … --></div></rlb-carousel-slide>
|
|
200
|
+
</rlb-carousel>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="modal-footer">
|
|
203
|
+
<button rlb-button [disabled]="page() === 0" (click)="prev()">Back</button>
|
|
204
|
+
@if (page() < count() - 1) {
|
|
205
|
+
<button rlb-button color="primary" [disabled]="currentStepInvalid()" (click)="next()">Next</button>
|
|
206
|
+
} @else {
|
|
207
|
+
<button rlb-button color="primary" [disabled]="form.invalid" (click)="onFinish()">Finish</button>
|
|
208
|
+
}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## ModalOptions Reference
|
|
216
|
+
|
|
217
|
+
| Option | Type | Default | Description |
|
|
218
|
+
|--------|------|---------|-------------|
|
|
219
|
+
| `backdrop` | `boolean \| 'static'` | `true` | `'static'` prevents closing on backdrop click |
|
|
220
|
+
| `scrollable` | `boolean` | `false` | Modal body scrolls independently |
|
|
221
|
+
| `verticalcentered` | `boolean` | `false` | Vertically center modal |
|
|
222
|
+
| `animation` | `boolean` | `true` | Fade animation |
|
|
223
|
+
| `size` | `'sm'\|'md'\|'lg'\|'xl'` | `'md'` | Modal width |
|
|
224
|
+
| `fullscreen` | `boolean\|string` | `false` | Full-screen mode |
|
|
225
|
+
| `focus` | `boolean` | `true` | Auto-focus when opened |
|
|
226
|
+
| `keyboard` | `boolean` | `true` | Esc closes modal |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Best Practices
|
|
231
|
+
|
|
232
|
+
1. Use `openConfirmModal()` for destructive actions — never skip a confirmation step.
|
|
233
|
+
2. Use `backdrop: 'static'` for forms where accidental close would lose data.
|
|
234
|
+
3. Subscribe to the modal result observable and handle all three reasons: `'ok'`, `'cancel'`, `'close'`.
|
|
235
|
+
4. For complex modals with forms, use the declarative `[rlb-modal]` approach and control close programmatically after validation.
|
|
236
|
+
5. Use `size: 'lg'` or `size: 'xl'` for forms with many fields; `size: 'sm'` for simple confirmations.
|