react-bootstrap-plugins 2.5.1 → 2.7.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.
- package/README.md +8 -2
- package/dist/ConfirmDialog.cjs +2 -0
- package/dist/ConfirmDialog.d.cts +43 -0
- package/dist/ConfirmDialog.d.ts +43 -0
- package/dist/ConfirmDialog.js +2 -0
- package/dist/FormRow.cjs +2 -0
- package/dist/FormRow.d.cts +17 -0
- package/dist/FormRow.d.ts +17 -0
- package/dist/FormRow.js +2 -0
- package/dist/NavPills.cjs +1 -1
- package/dist/NavPills.d.cts +1 -0
- package/dist/NavPills.d.ts +1 -0
- package/dist/NavPills.js +1 -1
- package/dist/chunk-6MGNNSRS.js +2 -0
- package/dist/chunk-DMKBSV5Q.cjs +2 -0
- package/dist/chunk-FRNRH2L3.js +2 -0
- package/dist/chunk-HGRCY2U6.cjs +2 -0
- package/dist/chunk-XCPF5NPT.cjs +2 -0
- package/dist/chunk-XL4EQW5D.js +2 -0
- package/dist/css/datepicker.css +1 -1
- package/dist/css/plugins.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/docs/CONFIRMDIALOG.md +60 -0
- package/docs/FORMROW.md +114 -0
- package/docs/superpowers/plans/2026-08-19-confirmdialog-plugin.md +549 -0
- package/docs/superpowers/specs/2026-08-19-confirmdialog-plugin-design.md +128 -0
- package/package.json +35 -11
- package/dist/chunk-G26VJJWL.cjs +0 -2
- package/dist/chunk-KVUHNUBG.js +0 -2
package/docs/FORMROW.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# FormRow
|
|
2
|
+
|
|
3
|
+
> A Bootstrap-styled form row wrapper — label, control, helper text, and required indicator in one block.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// Default import (recommended for single-component tree-shaking)
|
|
11
|
+
import FormRow from 'react-bootstrap-plugins/FormRow'
|
|
12
|
+
|
|
13
|
+
// Named import from individual entry point
|
|
14
|
+
import { FormRow } from 'react-bootstrap-plugins/FormRow'
|
|
15
|
+
|
|
16
|
+
// Barrel import
|
|
17
|
+
import { FormRow } from 'react-bootstrap-plugins'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Basic Usage
|
|
23
|
+
|
|
24
|
+
```jsx
|
|
25
|
+
import { FormRow } from 'react-bootstrap-plugins'
|
|
26
|
+
|
|
27
|
+
<FormRow label="Student Name" required hint="Full name as it appears on the birth certificate">
|
|
28
|
+
<input type="text" className="form-control" />
|
|
29
|
+
</FormRow>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Renders:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<div>
|
|
36
|
+
<label class="form-label small fw-medium">Student Name <b class="text-danger ms-1">*</b></label>
|
|
37
|
+
<input type="text" class="form-control" />
|
|
38
|
+
<div class="form-text">Full name as it appears on the birth certificate</div>
|
|
39
|
+
</div>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Props
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Default | Description |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `label` | `string` | — | Label text rendered above the field |
|
|
49
|
+
| `hint` | `string` | — | Helper text rendered below the field (`form-text`) |
|
|
50
|
+
| `required` | `boolean` | `false` | Show red asterisk after the label |
|
|
51
|
+
| `className` | `string` | — | Additional CSS classes on the wrapper `<div>` |
|
|
52
|
+
| `children` | `ReactNode` | — | Form control(s) rendered inside the row |
|
|
53
|
+
|
|
54
|
+
`FormRow` also forwards its `ref` to the wrapper `<div>`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
### Required field with hint
|
|
61
|
+
|
|
62
|
+
```jsx
|
|
63
|
+
<FormRow label="Email Address" required hint="We'll never share your email">
|
|
64
|
+
<input type="email" className="form-control" placeholder="you@school.ac.ug" />
|
|
65
|
+
</FormRow>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Optional field (no asterisk)
|
|
69
|
+
|
|
70
|
+
```jsx
|
|
71
|
+
<FormRow label="Middle Name">
|
|
72
|
+
<input type="text" className="form-control" />
|
|
73
|
+
</FormRow>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### With a Select control
|
|
77
|
+
|
|
78
|
+
```jsx
|
|
79
|
+
<FormRow label="Class" required>
|
|
80
|
+
<select className="form-select">
|
|
81
|
+
<option value="">Choose a class…</option>
|
|
82
|
+
<option value="s1">Senior One</option>
|
|
83
|
+
<option value="s2">Senior Two</option>
|
|
84
|
+
</select>
|
|
85
|
+
</FormRow>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### With extra wrapper classes
|
|
89
|
+
|
|
90
|
+
```jsx
|
|
91
|
+
<FormRow label="Phone" className="col-md-6 mb-3">
|
|
92
|
+
<input type="tel" className="form-control" />
|
|
93
|
+
</FormRow>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Dark Mode
|
|
99
|
+
|
|
100
|
+
The FormRow respects Bootstrap 5's dark mode. Set `data-bs-theme="dark"` on any parent element.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Bundle Size
|
|
105
|
+
|
|
106
|
+
~0.3 KB (min+gzip).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## See Also
|
|
111
|
+
|
|
112
|
+
- [Label](./LABEL.md) — Standalone form label with required indicator
|
|
113
|
+
- [InputCurrency](./INPUTCURRENCY.md) — Controlled currency input
|
|
114
|
+
- [Main README](../README.md) — Package overview, installation, and general info
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# ConfirmDialog Plugin Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a premium zero-dependency `ConfirmDialog` component to react-bootstrap-plugins and migrate the portal's local ConfirmDialog in CBCAlevelReviewResults.jsx to it.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Bootstrap 5 modal markup rendered via `createPortal` to `document.body` — backdrop div + `.modal > .modal-dialog.modal-dialog-centered.modal-sm > .modal-content`. No react-bootstrap, no animation, fully controlled via `show`. "Refined Minimal" premium styling appended to `src/css/plugins.css`. Async `onConfirm` drives a pending spinner state.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript + React 18 (forwardRef, hooks), Bootstrap 5 CSS (classes + CSS variables), tsup build, pnpm.
|
|
10
|
+
|
|
11
|
+
**Testing note (documented deviation, spec-approved):** the package has no test runner (`"test": "echo 'Tests passed'"`). Verification = `pnpm run build` (tsup + dts + CSS) after each task, plus a live smoke checklist in the portal at the end. No vitest is added.
|
|
12
|
+
|
|
13
|
+
**Spec:** `docs/superpowers/specs/2026-08-19-confirmdialog-plugin-design.md`
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
### Task 1: ConfirmDialog component
|
|
18
|
+
|
|
19
|
+
**Files:**
|
|
20
|
+
- Create: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/src/components/ConfirmDialog.tsx`
|
|
21
|
+
|
|
22
|
+
- [ ] **Step 1: Write the component**
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import * as React from 'react'
|
|
26
|
+
import { createPortal } from 'react-dom'
|
|
27
|
+
import { cn } from '../lib/cn.js'
|
|
28
|
+
|
|
29
|
+
/* ------------------------------------------------------------------ */
|
|
30
|
+
/* Types */
|
|
31
|
+
/* ------------------------------------------------------------------ */
|
|
32
|
+
|
|
33
|
+
export type ConfirmDialogVariant = 'primary' | 'danger' | 'success' | 'warning' | 'info'
|
|
34
|
+
|
|
35
|
+
export interface ConfirmDialogProps {
|
|
36
|
+
/** Visibility — fully controlled by the caller */
|
|
37
|
+
show: boolean
|
|
38
|
+
/** Dialog title */
|
|
39
|
+
title: React.ReactNode
|
|
40
|
+
/** Body message (string or JSX) */
|
|
41
|
+
message: React.ReactNode
|
|
42
|
+
/**
|
|
43
|
+
* Fired when the confirm button is clicked. May return a promise —
|
|
44
|
+
* the dialog shows a pending spinner until it resolves.
|
|
45
|
+
* On rejection the dialog stays open and the error propagates;
|
|
46
|
+
* callers handle their own error display inside `onConfirm`.
|
|
47
|
+
*/
|
|
48
|
+
onConfirm: () => void | Promise<void>
|
|
49
|
+
/** Fired on the Cancel button or the Escape key */
|
|
50
|
+
onCancel: () => void
|
|
51
|
+
/** Confirm button label */
|
|
52
|
+
label?: React.ReactNode
|
|
53
|
+
/** Bootstrap color variant for the confirm button, header dot, and message rule */
|
|
54
|
+
variant?: ConfirmDialogVariant
|
|
55
|
+
/** Extra CSS classes on the dialog element */
|
|
56
|
+
className?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ------------------------------------------------------------------ */
|
|
60
|
+
/* ConfirmDialog */
|
|
61
|
+
/* ------------------------------------------------------------------ */
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Zero-dependency Bootstrap 5 confirmation dialog.
|
|
65
|
+
*
|
|
66
|
+
* Renders standard `.modal` markup portaled to `document.body` with its own
|
|
67
|
+
* backdrop — no react-bootstrap required. Backdrop clicks do nothing;
|
|
68
|
+
* Escape fires `onCancel`; focus is trapped inside the dialog and lands on
|
|
69
|
+
* the confirm button on open. Body scroll is locked while shown.
|
|
70
|
+
*
|
|
71
|
+
* **Important:** the accompanying CSS **must** be imported for the premium
|
|
72
|
+
* styling (backdrop, radius, header dot, message rule):
|
|
73
|
+
* ```js
|
|
74
|
+
* import 'react-bootstrap-plugins/css/plugins.css'
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
const ConfirmDialog = React.forwardRef<HTMLDivElement, ConfirmDialogProps>(({
|
|
78
|
+
show = false,
|
|
79
|
+
title,
|
|
80
|
+
message,
|
|
81
|
+
onConfirm,
|
|
82
|
+
onCancel,
|
|
83
|
+
label = 'Confirm',
|
|
84
|
+
variant = 'primary',
|
|
85
|
+
className,
|
|
86
|
+
}, ref) => {
|
|
87
|
+
const [isPending, setIsPending] = React.useState(false)
|
|
88
|
+
const titleId = React.useId()
|
|
89
|
+
const messageId = React.useId()
|
|
90
|
+
const dialogRef = React.useRef<HTMLDivElement>(null)
|
|
91
|
+
const confirmRef = React.useRef<HTMLButtonElement>(null)
|
|
92
|
+
|
|
93
|
+
/* Focus the primary action when the dialog opens */
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
if (show) confirmRef.current?.focus()
|
|
96
|
+
}, [show])
|
|
97
|
+
|
|
98
|
+
/* Body scroll lock while shown (compensate scrollbar disappearance) */
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
if (!show) return
|
|
101
|
+
const { body } = document
|
|
102
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
|
|
103
|
+
const prevOverflow = body.style.overflow
|
|
104
|
+
const prevPaddingRight = body.style.paddingRight
|
|
105
|
+
body.style.overflow = 'hidden'
|
|
106
|
+
if (scrollbarWidth > 0) body.style.paddingRight = `${scrollbarWidth}px`
|
|
107
|
+
return () => {
|
|
108
|
+
body.style.overflow = prevOverflow
|
|
109
|
+
body.style.paddingRight = prevPaddingRight
|
|
110
|
+
}
|
|
111
|
+
}, [show])
|
|
112
|
+
|
|
113
|
+
/* Escape closes (unless a confirm is pending) */
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (!show) return
|
|
116
|
+
const handleKey = (e: KeyboardEvent) => {
|
|
117
|
+
if (e.key === 'Escape' && !isPending) onCancel()
|
|
118
|
+
}
|
|
119
|
+
document.addEventListener('keydown', handleKey)
|
|
120
|
+
return () => document.removeEventListener('keydown', handleKey)
|
|
121
|
+
}, [show, isPending, onCancel])
|
|
122
|
+
|
|
123
|
+
/* Tab / Shift+Tab cycle within the dialog */
|
|
124
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
125
|
+
if (e.key !== 'Tab') return
|
|
126
|
+
const root = dialogRef.current
|
|
127
|
+
if (!root) return
|
|
128
|
+
const focusables = Array.from(
|
|
129
|
+
root.querySelectorAll<HTMLElement>('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
|
|
130
|
+
).filter((el) => !el.hasAttribute('disabled'))
|
|
131
|
+
if (!focusables.length) return
|
|
132
|
+
const first = focusables[0]
|
|
133
|
+
const last = focusables[focusables.length - 1]
|
|
134
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
135
|
+
e.preventDefault()
|
|
136
|
+
last.focus()
|
|
137
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
138
|
+
e.preventDefault()
|
|
139
|
+
first.focus()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const handleConfirmClick = async () => {
|
|
144
|
+
if (isPending) return
|
|
145
|
+
setIsPending(true)
|
|
146
|
+
try {
|
|
147
|
+
await onConfirm()
|
|
148
|
+
} finally {
|
|
149
|
+
setIsPending(false)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!show || typeof document === 'undefined') return null
|
|
154
|
+
|
|
155
|
+
return createPortal(
|
|
156
|
+
<>
|
|
157
|
+
<div className="confirmdialog-backdrop" aria-hidden="true" />
|
|
158
|
+
<div
|
|
159
|
+
ref={ref}
|
|
160
|
+
tabIndex={-1}
|
|
161
|
+
role="dialog"
|
|
162
|
+
aria-modal="true"
|
|
163
|
+
aria-labelledby={titleId}
|
|
164
|
+
aria-describedby={messageId}
|
|
165
|
+
className={cn('modal confirmdialog d-block', className)}
|
|
166
|
+
onKeyDown={handleKeyDown}
|
|
167
|
+
>
|
|
168
|
+
<div ref={dialogRef} className="modal-dialog modal-dialog-centered modal-sm">
|
|
169
|
+
<div className="modal-content">
|
|
170
|
+
<div className="confirmdialog-header">
|
|
171
|
+
<span className={cn('confirmdialog-dot', `bg-${variant}`)} aria-hidden="true" />
|
|
172
|
+
<h5 className="modal-title" id={titleId}>{title}</h5>
|
|
173
|
+
</div>
|
|
174
|
+
<div
|
|
175
|
+
id={messageId}
|
|
176
|
+
className={cn('confirmdialog-body border-start border-3', `border-${variant}`)}
|
|
177
|
+
>
|
|
178
|
+
{message}
|
|
179
|
+
</div>
|
|
180
|
+
<div className="confirmdialog-footer">
|
|
181
|
+
<button type="button" className="btn btn-light" onClick={onCancel} disabled={isPending}>
|
|
182
|
+
Cancel
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
ref={confirmRef}
|
|
186
|
+
type="button"
|
|
187
|
+
className={cn('btn', `btn-${variant}`, 'fw-semibold')}
|
|
188
|
+
onClick={handleConfirmClick}
|
|
189
|
+
disabled={isPending}
|
|
190
|
+
>
|
|
191
|
+
{isPending && <span className="spinner-border spinner-border-sm me-2" aria-hidden="true" />}
|
|
192
|
+
{label}
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</>,
|
|
199
|
+
document.body
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
ConfirmDialog.displayName = 'ConfirmDialog'
|
|
204
|
+
|
|
205
|
+
export { ConfirmDialog }
|
|
206
|
+
export default ConfirmDialog
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
- [ ] **Step 2: Verify types compile**
|
|
210
|
+
|
|
211
|
+
Run: `cd "/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins" && pnpm run build:types`
|
|
212
|
+
Expected: exit 0, `DTS Build success`
|
|
213
|
+
|
|
214
|
+
- [ ] **Step 3: Commit**
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
git add src/components/ConfirmDialog.tsx
|
|
218
|
+
git commit -m "feat: add ConfirmDialog component"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### Task 2: ConfirmDialog styles
|
|
224
|
+
|
|
225
|
+
**Files:**
|
|
226
|
+
- Modify: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/src/css/plugins.css` (append at end of file)
|
|
227
|
+
|
|
228
|
+
- [ ] **Step 1: Append the ConfirmDialog CSS section**
|
|
229
|
+
|
|
230
|
+
Append exactly this to the end of `src/css/plugins.css`:
|
|
231
|
+
|
|
232
|
+
```css
|
|
233
|
+
/* ==========================================================================
|
|
234
|
+
ConfirmDialog — "Refined Minimal" premium confirmation dialog
|
|
235
|
+
Bootstrap 5 modal markup portaled to document.body. Theme-aware via
|
|
236
|
+
--bs-* variables (dark mode works automatically).
|
|
237
|
+
========================================================================== */
|
|
238
|
+
|
|
239
|
+
.confirmdialog-backdrop {
|
|
240
|
+
position: fixed;
|
|
241
|
+
inset: 0;
|
|
242
|
+
z-index: 1050;
|
|
243
|
+
background: rgba(0, 0, 0, 0.5);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.confirmdialog .modal-content {
|
|
247
|
+
border: 1px solid var(--bs-border-color, #dee2e6);
|
|
248
|
+
border-radius: 12px;
|
|
249
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.confirmdialog-header {
|
|
253
|
+
display: flex;
|
|
254
|
+
align-items: center;
|
|
255
|
+
gap: 8px;
|
|
256
|
+
padding: 16px 18px 12px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.confirmdialog-dot {
|
|
260
|
+
width: 8px;
|
|
261
|
+
height: 8px;
|
|
262
|
+
border-radius: 50%;
|
|
263
|
+
flex-shrink: 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.confirmdialog-header .modal-title {
|
|
267
|
+
font-weight: 600;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.confirmdialog-body {
|
|
271
|
+
padding: 4px 18px 16px 34px;
|
|
272
|
+
color: var(--bs-secondary-color, #6c757d);
|
|
273
|
+
font-size: 0.875rem;
|
|
274
|
+
line-height: 1.55;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.confirmdialog-footer {
|
|
278
|
+
display: flex;
|
|
279
|
+
justify-content: flex-end;
|
|
280
|
+
gap: 8px;
|
|
281
|
+
padding: 12px 18px 16px;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
- [ ] **Step 2: Verify CSS builds**
|
|
286
|
+
|
|
287
|
+
Run: `cd "/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins" && pnpm run build`
|
|
288
|
+
Expected: exit 0, output includes `✓ plugins.css (minified)` and `CSS built successfully.`
|
|
289
|
+
|
|
290
|
+
- [ ] **Step 3: Commit**
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
git add src/css/plugins.css
|
|
294
|
+
git commit -m "style: add ConfirmDialog premium styles"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### Task 3: Package wiring
|
|
300
|
+
|
|
301
|
+
**Files:**
|
|
302
|
+
- Modify: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/tsup.config.ts`
|
|
303
|
+
- Modify: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/package.json`
|
|
304
|
+
- Modify: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/src/index.ts`
|
|
305
|
+
|
|
306
|
+
- [ ] **Step 1: Add tsup entry**
|
|
307
|
+
|
|
308
|
+
In `tsup.config.ts`, add one line to the `entry` object after the `FormRow` line:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
ConfirmDialog: 'src/components/ConfirmDialog.tsx',
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
- [ ] **Step 2: Add exports map entry**
|
|
315
|
+
|
|
316
|
+
In `package.json`, after the `"./FormRow"` block (it ends with `},`), add:
|
|
317
|
+
|
|
318
|
+
```json
|
|
319
|
+
"./ConfirmDialog": {
|
|
320
|
+
"import": {
|
|
321
|
+
"types": "./dist/ConfirmDialog.d.ts",
|
|
322
|
+
"default": "./dist/ConfirmDialog.js"
|
|
323
|
+
},
|
|
324
|
+
"require": {
|
|
325
|
+
"types": "./dist/ConfirmDialog.d.ts",
|
|
326
|
+
"default": "./dist/ConfirmDialog.cjs"
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- [ ] **Step 3: Bump version**
|
|
332
|
+
|
|
333
|
+
In `package.json`, change `"version": "2.6.0"` to `"version": "2.7.0"`.
|
|
334
|
+
|
|
335
|
+
- [ ] **Step 4: Add keywords**
|
|
336
|
+
|
|
337
|
+
In `package.json`, in the `keywords` array, add before `"ui-components"`:
|
|
338
|
+
|
|
339
|
+
```json
|
|
340
|
+
"confirm-dialog",
|
|
341
|
+
"confirmation",
|
|
342
|
+
"modal",
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
- [ ] **Step 5: Barrel export**
|
|
346
|
+
|
|
347
|
+
In `src/index.ts`, after the `FormRow` export lines, add:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
export { ConfirmDialog } from './components/ConfirmDialog.js'
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
and after the `FormRowProps` type export line:
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
export type { ConfirmDialogProps, ConfirmDialogVariant } from './components/ConfirmDialog.js'
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- [ ] **Step 6: Verify full build and dist output**
|
|
360
|
+
|
|
361
|
+
Run: `cd "/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins" && pnpm run build && ls dist/ConfirmDialog.*`
|
|
362
|
+
Expected: exit 0, and output lists exactly `ConfirmDialog.cjs`, `ConfirmDialog.d.cts`, `ConfirmDialog.d.mts`, `ConfirmDialog.d.ts`, `ConfirmDialog.js`
|
|
363
|
+
|
|
364
|
+
- [ ] **Step 7: Commit**
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
git add tsup.config.ts package.json src/index.ts
|
|
368
|
+
git commit -m "feat: wire ConfirmDialog into build, exports, and barrel"
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### Task 4: Documentation
|
|
374
|
+
|
|
375
|
+
**Files:**
|
|
376
|
+
- Create: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/docs/CONFIRMDIALOG.md`
|
|
377
|
+
- Modify: `/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins/README.md`
|
|
378
|
+
|
|
379
|
+
- [ ] **Step 1: Write the component guide**
|
|
380
|
+
|
|
381
|
+
Create `docs/CONFIRMDIALOG.md`:
|
|
382
|
+
|
|
383
|
+
```markdown
|
|
384
|
+
# ConfirmDialog
|
|
385
|
+
|
|
386
|
+
Zero-dependency Bootstrap 5 confirmation dialog with async loading state. Premium "Refined Minimal" styling — soft radius, variant-colored header dot, variant rule on the message.
|
|
387
|
+
|
|
388
|
+
## Import
|
|
389
|
+
|
|
390
|
+
```js
|
|
391
|
+
import ConfirmDialog from 'react-bootstrap-plugins/ConfirmDialog'
|
|
392
|
+
// or
|
|
393
|
+
import { ConfirmDialog } from 'react-bootstrap-plugins'
|
|
394
|
+
// Required CSS
|
|
395
|
+
import 'react-bootstrap-plugins/css/plugins.css'
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Requires Bootstrap 5 CSS loaded in your app.
|
|
399
|
+
|
|
400
|
+
## Usage
|
|
401
|
+
|
|
402
|
+
```jsx
|
|
403
|
+
const [deleteTarget, setDeleteTarget] = useState(null)
|
|
404
|
+
|
|
405
|
+
<ConfirmDialog
|
|
406
|
+
show={!!deleteTarget}
|
|
407
|
+
title="Delete Report Card"
|
|
408
|
+
message="Are you sure you want to delete this report card? This action cannot be undone."
|
|
409
|
+
label="Confirm Delete"
|
|
410
|
+
variant="danger"
|
|
411
|
+
onConfirm={async () => {
|
|
412
|
+
await deleteReportCard(deleteTarget.id) // spinner shows until this settles
|
|
413
|
+
setDeleteTarget(null) // caller controls closing
|
|
414
|
+
}}
|
|
415
|
+
onCancel={() => setDeleteTarget(null)}
|
|
416
|
+
/>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The dialog is fully controlled: it never closes itself. Close by setting `show={false}`.
|
|
420
|
+
|
|
421
|
+
## Props
|
|
422
|
+
|
|
423
|
+
| Prop | Type | Default | Description |
|
|
424
|
+
|------|------|---------|-------------|
|
|
425
|
+
| `show` | `boolean` | required | Visibility — controlled by the caller |
|
|
426
|
+
| `title` | `ReactNode` | required | Dialog title |
|
|
427
|
+
| `message` | `ReactNode` | required | Body message (string or JSX) |
|
|
428
|
+
| `onConfirm` | `() => void \| Promise<void>` | required | Fired on confirm. A returned promise drives the pending spinner; on rejection the dialog stays open and the error propagates — handle error display (toasts) inside `onConfirm` |
|
|
429
|
+
| `onCancel` | `() => void` | required | Fired on the Cancel button or Escape key |
|
|
430
|
+
| `label` | `ReactNode` | `"Confirm"` | Confirm button label |
|
|
431
|
+
| `variant` | `"primary" \| "danger" \| "success" \| "warning" \| "info"` | `"primary"` | Color variant for the confirm button, header dot, and message rule |
|
|
432
|
+
| `className` | `string` | — | Extra classes on the dialog element |
|
|
433
|
+
|
|
434
|
+
## Behavior
|
|
435
|
+
|
|
436
|
+
- **Backdrop click does nothing** — safe for destructive confirms.
|
|
437
|
+
- **Escape** fires `onCancel` (ignored while a confirm is pending).
|
|
438
|
+
- **Async loading**: while `onConfirm`'s promise is unresolved, the confirm button shows a `spinner-border spinner-border-sm` and both buttons are disabled.
|
|
439
|
+
- **Accessibility**: `role="dialog"` + `aria-modal`, labelled by the title and described by the message; Tab / Shift+Tab cycle within the dialog; focus lands on the confirm button on open; body scroll is locked while shown.
|
|
440
|
+
|
|
441
|
+
## Dark Mode
|
|
442
|
+
|
|
443
|
+
All colors use Bootstrap CSS variables — the dialog adapts automatically via `[data-bs-theme="dark"]`.
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- [ ] **Step 2: Add README table row**
|
|
447
|
+
|
|
448
|
+
In `README.md`, add after the `**FormRow**` row in the Components table:
|
|
449
|
+
|
|
450
|
+
```markdown
|
|
451
|
+
| **ConfirmDialog** | [CONFIRMDIALOG.md](./docs/CONFIRMDIALOG.md) | Bootstrap 5 confirmation dialog with async loading state |
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
- [ ] **Step 3: Commit**
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
git add docs/CONFIRMDIALOG.md README.md
|
|
458
|
+
git commit -m "docs: add ConfirmDialog guide and README entry"
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
### Task 5: Publish to npm (user action)
|
|
464
|
+
|
|
465
|
+
- [ ] **Step 1: Dry run**
|
|
466
|
+
|
|
467
|
+
Run: `cd "/media/robert/Development/Allios Apps/allios-global/react-bootstrap-plugins" && pnpm publish --dry-run`
|
|
468
|
+
Expected: package contents include `dist/ConfirmDialog.*`, `dist/css/plugins.css`, `dist/index.*`
|
|
469
|
+
|
|
470
|
+
- [ ] **Step 2: Publish**
|
|
471
|
+
|
|
472
|
+
Run: `pnpm publish --no-git-checks`
|
|
473
|
+
Expected: publishes `react-bootstrap-plugins@2.7.0` to npm
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
### Task 6: Portal migration (allios-swiftplus-dashboard)
|
|
478
|
+
|
|
479
|
+
**Files:**
|
|
480
|
+
- Modify: `/media/robert/Development/Allios Apps/allios-global/allios-swiftplus-dashboard/src/views/academics/CBCAlevelReviewResults.jsx`
|
|
481
|
+
|
|
482
|
+
- [ ] **Step 1: Remove the local ConfirmDialog component**
|
|
483
|
+
|
|
484
|
+
Locate by content (currently lines 150–166, but the file has uncommitted edits so line numbers may have shifted): delete the entire local component starting at `const ConfirmDialog = ({ show, title, message, onConfirm, onCancel, confirmLabel = 'Confirm', variant = 'primary' }) => {` through the component's closing `}` (the one after its `<Modal>...</Modal>` return, before the next top-level declaration). Do not remove anything else.
|
|
485
|
+
|
|
486
|
+
- [ ] **Step 2: Add plugin imports**
|
|
487
|
+
|
|
488
|
+
In the import block, after the existing `import AutoDisplay from 'react-bootstrap-plugins/AutoDisplay'` line, add:
|
|
489
|
+
|
|
490
|
+
```jsx
|
|
491
|
+
import ConfirmDialog from 'react-bootstrap-plugins/ConfirmDialog'
|
|
492
|
+
import 'react-bootstrap-plugins/css/plugins.css'
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
- [ ] **Step 3: Update the call site (around line 3664)**
|
|
496
|
+
|
|
497
|
+
Change the prop name `confirmLabel` to `label`:
|
|
498
|
+
|
|
499
|
+
```jsx
|
|
500
|
+
<ConfirmDialog
|
|
501
|
+
show={!!deleteConfirm}
|
|
502
|
+
title="Delete Report Card"
|
|
503
|
+
message="Are you sure you want to delete this report card? This action cannot be undone."
|
|
504
|
+
label="Confirm Delete"
|
|
505
|
+
variant="danger"
|
|
506
|
+
onConfirm={() => handleDelete(deleteConfirm)}
|
|
507
|
+
onCancel={() => setDeleteConfirm(null)}
|
|
508
|
+
/>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Everything else stays — `handleDelete` is async, so the pending spinner will show while the delete API call runs.
|
|
512
|
+
|
|
513
|
+
- [ ] **Step 4: Verify no leftover references**
|
|
514
|
+
|
|
515
|
+
Run: `grep -n "confirmLabel" "/media/robert/Development/Allios Apps/allios-global/allios-swiftplus-dashboard/src/views/academics/CBCAlevelReviewResults.jsx"`
|
|
516
|
+
Expected: no output
|
|
517
|
+
|
|
518
|
+
- [ ] **Step 5: Commit**
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
git add src/views/academics/CBCAlevelReviewResults.jsx
|
|
522
|
+
git commit -m "refactor: use react-bootstrap-plugins ConfirmDialog for report card deletion"
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
### Task 7: Portal verification (user action)
|
|
528
|
+
|
|
529
|
+
- [ ] **Step 1: Bump the installed package**
|
|
530
|
+
|
|
531
|
+
Run: `cd "/media/robert/Development/Allios Apps/allios-global/allios-swiftplus-dashboard" && pnpm update react-bootstrap-plugins && grep '"react-bootstrap-plugins"' package.json`
|
|
532
|
+
Expected: package.json shows `^2.7.0`
|
|
533
|
+
|
|
534
|
+
- [ ] **Step 2: Smoke check in the running app**
|
|
535
|
+
|
|
536
|
+
Start the dev server (`pnpm dev` — the user runs this) and on the CBC A-Level results page open the Delete Report Card confirm dialog:
|
|
537
|
+
|
|
538
|
+
1. Dialog appears instantly (no animation), centered, ~300px wide, 12px radius, thin border, soft shadow.
|
|
539
|
+
2. Header shows a small red dot + "Delete Report Card" (semibold); body message has a 3px red left rule, muted text; footer has `Cancel` (btn-light) + `Confirm Delete` (btn-danger).
|
|
540
|
+
3. Click Confirm Delete → button shows a spinner and both buttons disable while the API call runs; dialog closes on success; success toast appears.
|
|
541
|
+
4. Press Escape → dialog closes (onCancel fires), no deletion.
|
|
542
|
+
5. Click the dimmed backdrop → nothing happens.
|
|
543
|
+
6. Tab / Shift+Tab cycle stays within the dialog.
|
|
544
|
+
7. Toggle dark mode → dialog adapts (dot, rule, and buttons use theme colors).
|
|
545
|
+
8. The page behind the dialog does not scroll.
|
|
546
|
+
|
|
547
|
+
- [ ] **Step 3: Done**
|
|
548
|
+
|
|
549
|
+
After the smoke check passes, the feature is complete. Tag the plugin repo: `git tag v2.7.0 && git push origin v2.7.0`.
|