@welshare/questionnaire 0.2.0 → 0.2.2
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 +305 -60
- package/dist/esm/components/bmi-form.d.ts.map +1 -1
- package/dist/esm/components/legal-consent-form.d.ts +123 -0
- package/dist/esm/components/legal-consent-form.d.ts.map +1 -0
- package/dist/esm/components/legal-consent-form.js +97 -0
- package/dist/esm/components/media-attachment.d.ts +21 -0
- package/dist/esm/components/media-attachment.d.ts.map +1 -0
- package/dist/esm/components/media-attachment.js +22 -0
- package/dist/esm/components/question-renderer.d.ts.map +1 -1
- package/dist/esm/components/question-renderer.js +9 -4
- package/dist/esm/components/questions/boolean-question.d.ts +4 -4
- package/dist/esm/components/questions/boolean-question.d.ts.map +1 -1
- package/dist/esm/components/questions/boolean-question.js +6 -6
- package/dist/esm/components/questions/choice-question.d.ts +4 -4
- package/dist/esm/components/questions/choice-question.d.ts.map +1 -1
- package/dist/esm/components/questions/choice-question.js +19 -11
- package/dist/esm/components/questions/multiple-choice-question.d.ts.map +1 -1
- package/dist/esm/components/questions/multiple-choice-question.js +19 -11
- package/dist/esm/contexts/questionnaire-context.d.ts.map +1 -1
- package/dist/esm/contexts/questionnaire-context.js +4 -22
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/lib/bmi-helpers.d.ts.map +1 -1
- package/dist/esm/lib/constants.d.ts +12 -0
- package/dist/esm/lib/constants.d.ts.map +1 -1
- package/dist/esm/lib/constants.js +12 -0
- package/dist/esm/lib/questionnaire-utils.d.ts +15 -1
- package/dist/esm/lib/questionnaire-utils.d.ts.map +1 -1
- package/dist/esm/lib/questionnaire-utils.js +26 -0
- package/dist/esm/types/fhir.d.ts +25 -5
- package/dist/esm/types/fhir.d.ts.map +1 -1
- package/dist/esm/types/fhir.js +3 -1
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/styles.css +153 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,16 +11,16 @@ npm install @welshare/questionnaire
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
import { useState, useEffect } from
|
|
14
|
+
import { useState, useEffect } from "react";
|
|
15
15
|
import {
|
|
16
16
|
QuestionnaireProvider,
|
|
17
17
|
useQuestionnaire,
|
|
18
18
|
QuestionRenderer,
|
|
19
19
|
getVisiblePages,
|
|
20
20
|
type Questionnaire,
|
|
21
|
-
} from
|
|
22
|
-
import
|
|
23
|
-
import
|
|
21
|
+
} from "@welshare/questionnaire";
|
|
22
|
+
import "@welshare/questionnaire/tokens.css";
|
|
23
|
+
import "@welshare/questionnaire/styles.css";
|
|
24
24
|
|
|
25
25
|
function QuestionnairePage() {
|
|
26
26
|
const { questionnaire, isPageValid } = useQuestionnaire();
|
|
@@ -45,11 +45,13 @@ function QuestionnairePage() {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function App() {
|
|
48
|
-
const [questionnaire, setQuestionnaire] = useState<Questionnaire | null>(
|
|
48
|
+
const [questionnaire, setQuestionnaire] = useState<Questionnaire | null>(
|
|
49
|
+
null
|
|
50
|
+
);
|
|
49
51
|
|
|
50
52
|
useEffect(() => {
|
|
51
|
-
fetch(
|
|
52
|
-
.then(res => res.json())
|
|
53
|
+
fetch("/api/questionnaire/your-id")
|
|
54
|
+
.then((res) => res.json())
|
|
53
55
|
.then(setQuestionnaire);
|
|
54
56
|
}, []);
|
|
55
57
|
|
|
@@ -68,6 +70,7 @@ function App() {
|
|
|
68
70
|
### QuestionnaireProvider
|
|
69
71
|
|
|
70
72
|
**Props:**
|
|
73
|
+
|
|
71
74
|
- `questionnaire: Questionnaire` - FHIR Questionnaire object
|
|
72
75
|
- `questionnaireId?: string` - Optional ID override (defaults to `questionnaire.id`)
|
|
73
76
|
- `useNestedStructure?: boolean` - Nested or flat response structure (default: `true`)
|
|
@@ -76,18 +79,27 @@ function App() {
|
|
|
76
79
|
|
|
77
80
|
```tsx
|
|
78
81
|
const {
|
|
79
|
-
questionnaire,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
questionnaire,
|
|
83
|
+
response,
|
|
84
|
+
updateAnswer,
|
|
85
|
+
updateMultipleAnswers,
|
|
86
|
+
getAnswer,
|
|
87
|
+
getAnswers,
|
|
88
|
+
isPageValid,
|
|
89
|
+
getRequiredQuestions,
|
|
90
|
+
getUnansweredRequiredQuestions,
|
|
91
|
+
markValidationErrors,
|
|
92
|
+
clearValidationErrors,
|
|
93
|
+
hasValidationError,
|
|
94
|
+
debugMode,
|
|
95
|
+
toggleDebugMode,
|
|
85
96
|
} = useQuestionnaire();
|
|
86
97
|
```
|
|
87
98
|
|
|
88
99
|
### QuestionRenderer
|
|
89
100
|
|
|
90
101
|
**Props:**
|
|
102
|
+
|
|
91
103
|
- `item: QuestionnaireItem` - Questionnaire item to render
|
|
92
104
|
- `className?: string` - Container CSS classes
|
|
93
105
|
- `inputClassName?: string` - Input CSS classes
|
|
@@ -102,6 +114,7 @@ const {
|
|
|
102
114
|
A controlled component for collecting height, weight, and calculating BMI. The component acts as an input helper that manages BMI calculation internally and reports changes to the parent via callbacks. Unit system (metric/imperial) is managed internally and automatically clears all fields when switched.
|
|
103
115
|
|
|
104
116
|
**Props:**
|
|
117
|
+
|
|
105
118
|
- `height: number` - Current height value (0 when empty)
|
|
106
119
|
- `weight: number` - Current weight value (0 when empty)
|
|
107
120
|
- `bmi: number` - Current BMI value (calculated and set by the component, 0 when not calculated)
|
|
@@ -111,6 +124,7 @@ A controlled component for collecting height, weight, and calculating BMI. The c
|
|
|
111
124
|
- `className?: string` - Optional CSS classes
|
|
112
125
|
|
|
113
126
|
**Features:**
|
|
127
|
+
|
|
114
128
|
- Controlled numeric values (height, weight, bmi) managed by parent
|
|
115
129
|
- BMI calculation handled internally by the component
|
|
116
130
|
- Unit system managed internally (defaults to metric)
|
|
@@ -121,10 +135,10 @@ A controlled component for collecting height, weight, and calculating BMI. The c
|
|
|
121
135
|
**Example:**
|
|
122
136
|
|
|
123
137
|
```tsx
|
|
124
|
-
import { useState } from
|
|
125
|
-
import { BmiForm, getBmiCategory } from
|
|
126
|
-
import
|
|
127
|
-
import
|
|
138
|
+
import { useState } from "react";
|
|
139
|
+
import { BmiForm, getBmiCategory } from "@welshare/questionnaire";
|
|
140
|
+
import "@welshare/questionnaire/tokens.css";
|
|
141
|
+
import "@welshare/questionnaire/styles.css";
|
|
128
142
|
|
|
129
143
|
function MyComponent() {
|
|
130
144
|
const [height, setHeight] = useState(0);
|
|
@@ -133,7 +147,7 @@ function MyComponent() {
|
|
|
133
147
|
|
|
134
148
|
const handleBmiChange = (value: number) => {
|
|
135
149
|
setBmi(value);
|
|
136
|
-
|
|
150
|
+
|
|
137
151
|
// Optional: Get BMI category
|
|
138
152
|
if (value) {
|
|
139
153
|
const category = getBmiCategory(value);
|
|
@@ -154,14 +168,134 @@ function MyComponent() {
|
|
|
154
168
|
}
|
|
155
169
|
```
|
|
156
170
|
|
|
171
|
+
### LegalConsentForm
|
|
172
|
+
|
|
173
|
+
A self-contained form component for collecting user consent before data submission. This component handles:
|
|
174
|
+
|
|
175
|
+
1. **Terms & Conditions and Privacy Policy** (required) - User must accept to proceed
|
|
176
|
+
2. **Study invitation notifications** (optional) - User can opt in to receive study invitations
|
|
177
|
+
|
|
178
|
+
**Usage Pattern:**
|
|
179
|
+
This component provides the content for a consent dialog. Applications should wrap this component in their own Dialog/Modal component (e.g., from shadcn/ui, Radix UI, MUI, etc.), similar to how BmiForm is used with input helpers.
|
|
180
|
+
|
|
181
|
+
**Props:**
|
|
182
|
+
|
|
183
|
+
- `initialValues?: Partial<LegalConsentResult>` - Initial consent state (for restoring previous preferences)
|
|
184
|
+
- `documentLinks?: LegalDocumentLinks` - Custom URLs for Terms & Privacy documents
|
|
185
|
+
- `onConfirm?: (result: LegalConsentResult) => void` - Called when user confirms (only if terms accepted)
|
|
186
|
+
- `onCancel?: () => void` - Called when user cancels/declines
|
|
187
|
+
- `onChange?: (result: LegalConsentResult) => void` - Called on any checkbox change (real-time updates)
|
|
188
|
+
- `renderCheckbox?: (props: LegalCheckboxProps) => ReactNode` - Custom checkbox component renderer
|
|
189
|
+
- `confirmButtonLabel?: string` - Custom confirm button text (default: "Confirm & Continue")
|
|
190
|
+
- `cancelButtonLabel?: string` - Custom cancel button text (default: "Cancel")
|
|
191
|
+
- `className?: string` - Additional CSS classes
|
|
192
|
+
|
|
193
|
+
**Result Object:**
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
interface LegalConsentResult {
|
|
197
|
+
termsAccepted: boolean; // Required - T&Cs and Privacy Policy
|
|
198
|
+
studyConsentAccepted: boolean; // Optional - Study invitations
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Example with Dialog:**
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { useState } from "react";
|
|
206
|
+
import {
|
|
207
|
+
LegalConsentForm,
|
|
208
|
+
type LegalConsentResult,
|
|
209
|
+
} from "@welshare/questionnaire";
|
|
210
|
+
import {
|
|
211
|
+
Dialog,
|
|
212
|
+
DialogContent,
|
|
213
|
+
DialogHeader,
|
|
214
|
+
DialogTitle,
|
|
215
|
+
} from "@/components/ui/dialog";
|
|
216
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
217
|
+
import "@welshare/questionnaire/tokens.css";
|
|
218
|
+
import "@welshare/questionnaire/styles.css";
|
|
219
|
+
|
|
220
|
+
function DataSubmissionFlow() {
|
|
221
|
+
const [showConsent, setShowConsent] = useState(false);
|
|
222
|
+
|
|
223
|
+
const handleSubmitData = () => {
|
|
224
|
+
// Show consent dialog before submitting data
|
|
225
|
+
setShowConsent(true);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const handleConsent = (result: LegalConsentResult) => {
|
|
229
|
+
console.log("Terms accepted:", result.termsAccepted);
|
|
230
|
+
console.log("Study consent:", result.studyConsentAccepted);
|
|
231
|
+
|
|
232
|
+
// Proceed with data submission
|
|
233
|
+
submitDataToServer(result);
|
|
234
|
+
setShowConsent(false);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<>
|
|
239
|
+
<button onClick={handleSubmitData}>Submit My Data</button>
|
|
240
|
+
|
|
241
|
+
<Dialog open={showConsent} onOpenChange={setShowConsent}>
|
|
242
|
+
<DialogContent>
|
|
243
|
+
<DialogHeader>
|
|
244
|
+
<DialogTitle>Health Data Storage Consent</DialogTitle>
|
|
245
|
+
</DialogHeader>
|
|
246
|
+
<LegalConsentForm
|
|
247
|
+
renderCheckbox={({ id, checked, onCheckedChange, className }) => (
|
|
248
|
+
<Checkbox
|
|
249
|
+
id={id}
|
|
250
|
+
checked={checked}
|
|
251
|
+
onCheckedChange={onCheckedChange}
|
|
252
|
+
className={className}
|
|
253
|
+
/>
|
|
254
|
+
)}
|
|
255
|
+
onConfirm={handleConsent}
|
|
256
|
+
onCancel={() => setShowConsent(false)}
|
|
257
|
+
/>
|
|
258
|
+
</DialogContent>
|
|
259
|
+
</Dialog>
|
|
260
|
+
</>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Custom Document Links:**
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
<LegalConsentForm
|
|
269
|
+
documentLinks={{
|
|
270
|
+
termsUrl: "https://myapp.com/terms",
|
|
271
|
+
privacyUrl: "https://myapp.com/privacy",
|
|
272
|
+
}}
|
|
273
|
+
onConfirm={handleConsent}
|
|
274
|
+
onCancel={handleCancel}
|
|
275
|
+
/>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**CSS Classes for Styling:**
|
|
279
|
+
|
|
280
|
+
- `.wq-legal-consent-form` - Main form container
|
|
281
|
+
- `.wq-legal-consent-section` - Individual consent section
|
|
282
|
+
- `.wq-legal-consent-heading` - Section headings
|
|
283
|
+
- `.wq-legal-consent-checkbox-row` - Checkbox + label row
|
|
284
|
+
- `.wq-legal-consent-link` - Links to legal documents
|
|
285
|
+
- `.wq-legal-consent-info-box` - Information box (study consent details)
|
|
286
|
+
- `.wq-legal-consent-actions` - Button container
|
|
287
|
+
- `.wq-button`, `.wq-button-primary`, `.wq-button-outline` - Button styles
|
|
288
|
+
|
|
157
289
|
### Utilities
|
|
158
290
|
|
|
159
291
|
**Questionnaire Utilities:**
|
|
292
|
+
|
|
160
293
|
- `getVisiblePages(questionnaire)` - Get visible page groups
|
|
161
294
|
- `calculateProgress(currentIndex, total)` - Calculate progress percentage
|
|
162
295
|
- `getAllQuestionsFromPage(pageItem)` - Get all questions from a page
|
|
163
296
|
|
|
164
297
|
**BMI Helper Functions:**
|
|
298
|
+
|
|
165
299
|
- `calculateBmi(height, weight, unitSystem)` - Calculate BMI from height and weight
|
|
166
300
|
- `getBmiCategory(bmi)` - Get WHO BMI category (Underweight, Normal weight, Overweight, Obese)
|
|
167
301
|
|
|
@@ -196,37 +330,139 @@ Override CSS custom properties:
|
|
|
196
330
|
## FHIR Extensions
|
|
197
331
|
|
|
198
332
|
### Hidden Questions
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"extension": [
|
|
337
|
+
{
|
|
338
|
+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
|
|
339
|
+
"valueBoolean": true
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Media Attachments (Images)
|
|
346
|
+
|
|
347
|
+
Display images or other media content with question items using the FHIR SDC (Structured Data Capture) extensions.
|
|
348
|
+
|
|
349
|
+
**Item Media (images on questions):**
|
|
350
|
+
|
|
199
351
|
```json
|
|
200
352
|
{
|
|
201
|
-
"
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
353
|
+
"linkId": "body-diagram",
|
|
354
|
+
"text": "Where is the pain located?",
|
|
355
|
+
"type": "choice",
|
|
356
|
+
"extension": [
|
|
357
|
+
{
|
|
358
|
+
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia",
|
|
359
|
+
"valueAttachment": {
|
|
360
|
+
"contentType": "image/png",
|
|
361
|
+
"url": "https://example.com/images/body-diagram.png",
|
|
362
|
+
"title": "Body diagram"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Answer Option Media (images on answer options):**
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"linkId": "skin-condition",
|
|
374
|
+
"text": "Which image best matches your condition?",
|
|
375
|
+
"type": "choice",
|
|
376
|
+
"answerOption": [
|
|
377
|
+
{
|
|
378
|
+
"valueCoding": {
|
|
379
|
+
"code": "mild",
|
|
380
|
+
"display": "Mild"
|
|
381
|
+
},
|
|
382
|
+
"extension": [
|
|
383
|
+
{
|
|
384
|
+
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia",
|
|
385
|
+
"valueAttachment": {
|
|
386
|
+
"contentType": "image/jpeg",
|
|
387
|
+
"url": "https://example.com/images/mild.jpg",
|
|
388
|
+
"title": "Mild condition"
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"valueCoding": {
|
|
395
|
+
"code": "severe",
|
|
396
|
+
"display": "Severe"
|
|
397
|
+
},
|
|
398
|
+
"extension": [
|
|
399
|
+
{
|
|
400
|
+
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia",
|
|
401
|
+
"valueAttachment": {
|
|
402
|
+
"contentType": "image/jpeg",
|
|
403
|
+
"url": "https://example.com/images/severe.jpg",
|
|
404
|
+
"title": "Severe condition"
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**CSS Classes for Styling:**
|
|
414
|
+
|
|
415
|
+
- `.wq-question-media` - Container for question-level images
|
|
416
|
+
- `.wq-question-image` - Individual question image
|
|
417
|
+
- `.wq-choice-option-wrapper` - Wrapper around answer option with image
|
|
418
|
+
- `.wq-choice-option-image` - Individual answer option image
|
|
419
|
+
|
|
420
|
+
**Example Styling:**
|
|
421
|
+
|
|
422
|
+
```css
|
|
423
|
+
.wq-question-image {
|
|
424
|
+
max-width: 100%;
|
|
425
|
+
height: auto;
|
|
426
|
+
margin-bottom: 1rem;
|
|
427
|
+
border-radius: 0.5rem;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.wq-choice-option-image {
|
|
431
|
+
max-width: 200px;
|
|
432
|
+
height: auto;
|
|
433
|
+
margin-bottom: 0.5rem;
|
|
434
|
+
border-radius: 0.25rem;
|
|
205
435
|
}
|
|
206
436
|
```
|
|
207
437
|
|
|
208
438
|
### Slider Controls
|
|
439
|
+
|
|
209
440
|
```json
|
|
210
441
|
{
|
|
211
|
-
"extension": [
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
442
|
+
"extension": [
|
|
443
|
+
{
|
|
444
|
+
"url": "http://codes.welshare.app/StructureDefinition/questionnaire-slider-control",
|
|
445
|
+
"extension": [
|
|
446
|
+
{ "url": "minValue", "valueInteger": 0 },
|
|
447
|
+
{ "url": "maxValue", "valueInteger": 100 },
|
|
448
|
+
{ "url": "step", "valueInteger": 1 },
|
|
449
|
+
{ "url": "unit", "valueString": "minutes" }
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
]
|
|
220
453
|
}
|
|
221
454
|
```
|
|
222
455
|
|
|
223
456
|
### Exclusive Options
|
|
457
|
+
|
|
224
458
|
```json
|
|
225
459
|
{
|
|
226
|
-
"extension": [
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
460
|
+
"extension": [
|
|
461
|
+
{
|
|
462
|
+
"url": "http://codes.welshare.app/StructureDefinition/questionnaire-exclusive-option",
|
|
463
|
+
"valueString": "none-of-the-above-code"
|
|
464
|
+
}
|
|
465
|
+
]
|
|
230
466
|
}
|
|
231
467
|
```
|
|
232
468
|
|
|
@@ -235,37 +471,51 @@ Override CSS custom properties:
|
|
|
235
471
|
Input helpers allow you to provide auxiliary UI (like calculators or lookup dialogs) to assist users in filling out form fields. The library detects the extension and calls your `renderHelperTrigger` function, but you control the dialog/modal implementation.
|
|
236
472
|
|
|
237
473
|
**FHIR Extension:**
|
|
474
|
+
|
|
238
475
|
```json
|
|
239
476
|
{
|
|
240
477
|
"linkId": "bmi",
|
|
241
478
|
"text": "Body Mass Index",
|
|
242
479
|
"type": "decimal",
|
|
243
|
-
"extension": [
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
480
|
+
"extension": [
|
|
481
|
+
{
|
|
482
|
+
"url": "http://codes.welshare.app/StructureDefinition/questionnaire-inputHelper",
|
|
483
|
+
"valueCodeableConcept": {
|
|
484
|
+
"coding": [
|
|
485
|
+
{
|
|
486
|
+
"system": "http://codes.welshare.app/input-helper-type",
|
|
487
|
+
"code": "bmi-calculator",
|
|
488
|
+
"display": "BMI Calculator"
|
|
489
|
+
}
|
|
490
|
+
],
|
|
491
|
+
"text": "Calculate BMI from height and weight"
|
|
492
|
+
}
|
|
252
493
|
}
|
|
253
|
-
|
|
494
|
+
]
|
|
254
495
|
}
|
|
255
496
|
```
|
|
256
497
|
|
|
257
498
|
**Implementation:**
|
|
258
499
|
|
|
259
500
|
```tsx
|
|
260
|
-
import { useState } from
|
|
261
|
-
import {
|
|
501
|
+
import { useState } from "react";
|
|
502
|
+
import {
|
|
503
|
+
QuestionRenderer,
|
|
504
|
+
BmiForm,
|
|
505
|
+
type HelperTriggerProps,
|
|
506
|
+
} from "@welshare/questionnaire";
|
|
262
507
|
|
|
263
508
|
function MyQuestionnaireRenderer({ item }) {
|
|
264
509
|
const [showBmiDialog, setShowBmiDialog] = useState(false);
|
|
265
|
-
const [helperCallback, setHelperCallback] = useState<
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
510
|
+
const [helperCallback, setHelperCallback] = useState<
|
|
511
|
+
((v: string | number) => void) | null
|
|
512
|
+
>(null);
|
|
513
|
+
|
|
514
|
+
const handleHelperTrigger = ({
|
|
515
|
+
helper,
|
|
516
|
+
onValueSelected,
|
|
517
|
+
}: HelperTriggerProps) => {
|
|
518
|
+
if (helper.type === "bmi-calculator") {
|
|
269
519
|
return (
|
|
270
520
|
<button
|
|
271
521
|
type="button"
|
|
@@ -274,7 +524,7 @@ function MyQuestionnaireRenderer({ item }) {
|
|
|
274
524
|
setShowBmiDialog(true);
|
|
275
525
|
}}
|
|
276
526
|
>
|
|
277
|
-
{helper.display ||
|
|
527
|
+
{helper.display || "Calculate"}
|
|
278
528
|
</button>
|
|
279
529
|
);
|
|
280
530
|
}
|
|
@@ -283,11 +533,8 @@ function MyQuestionnaireRenderer({ item }) {
|
|
|
283
533
|
|
|
284
534
|
return (
|
|
285
535
|
<>
|
|
286
|
-
<QuestionRenderer
|
|
287
|
-
|
|
288
|
-
renderHelperTrigger={handleHelperTrigger}
|
|
289
|
-
/>
|
|
290
|
-
|
|
536
|
+
<QuestionRenderer item={item} renderHelperTrigger={handleHelperTrigger} />
|
|
537
|
+
|
|
291
538
|
{showBmiDialog && (
|
|
292
539
|
<Dialog onClose={() => setShowBmiDialog(false)}>
|
|
293
540
|
<BmiForm
|
|
@@ -304,6 +551,7 @@ function MyQuestionnaireRenderer({ item }) {
|
|
|
304
551
|
```
|
|
305
552
|
|
|
306
553
|
**Helper Trigger Props:**
|
|
554
|
+
|
|
307
555
|
- `helper: InputHelperConfig` - Configuration from the extension
|
|
308
556
|
- `type: string` - Helper identifier (e.g., "bmi-calculator")
|
|
309
557
|
- `display?: string` - Display name from the extension
|
|
@@ -316,9 +564,6 @@ function MyQuestionnaireRenderer({ item }) {
|
|
|
316
564
|
|
|
317
565
|
**Note:** The library only renders the trigger element you provide. You are responsible for implementing the dialog/modal UI, as this allows you to use your preferred dialog system (e.g., shadcn/ui, Radix UI, MUI, etc.).
|
|
318
566
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
567
|
## License
|
|
323
568
|
|
|
324
569
|
MIT © Welshare UG (haftungsbeschränkt)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bmi-form.d.ts","sourceRoot":"","sources":["../../../src/components/bmi-form.tsx"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,gBAAgB,CAAC;IAEjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEjD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEjD;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,OAAO,GAAI,mEAMrB,YAAY,
|
|
1
|
+
{"version":3,"file":"bmi-form.d.ts","sourceRoot":"","sources":["../../../src/components/bmi-form.tsx"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,gBAAgB,CAAC;IAEjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAEvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEjD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEjD;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,OAAO,GAAI,mEAMrB,YAAY,4CA8Sd,CAAC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for legal document links
|
|
4
|
+
*/
|
|
5
|
+
export interface LegalDocumentLinks {
|
|
6
|
+
/** URL to Terms & Conditions document */
|
|
7
|
+
termsUrl?: string;
|
|
8
|
+
/** URL to Privacy Policy document */
|
|
9
|
+
privacyUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Result object returned on submission
|
|
13
|
+
*/
|
|
14
|
+
export interface LegalConsentResult {
|
|
15
|
+
/** Whether user accepted terms & conditions and privacy policy */
|
|
16
|
+
termsAccepted: boolean;
|
|
17
|
+
/** Whether user opted in to receive study invitations */
|
|
18
|
+
studyConsentAccepted: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Props for rendering a checkbox in the legal consent form
|
|
22
|
+
*/
|
|
23
|
+
export interface LegalCheckboxProps {
|
|
24
|
+
/** Unique identifier for the checkbox */
|
|
25
|
+
id: string;
|
|
26
|
+
/** Whether the checkbox is checked */
|
|
27
|
+
checked: boolean;
|
|
28
|
+
/** Callback when the checkbox value changes */
|
|
29
|
+
onCheckedChange: (checked: boolean) => void;
|
|
30
|
+
/** Additional CSS class name */
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface LegalConsentFormProps {
|
|
34
|
+
/**
|
|
35
|
+
* Initial consent values.
|
|
36
|
+
* Useful for restoring previous consent state.
|
|
37
|
+
*/
|
|
38
|
+
initialValues?: Partial<LegalConsentResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Custom URLs for legal documents.
|
|
41
|
+
* Defaults to Welshare's standard T&Cs and Privacy Policy.
|
|
42
|
+
*/
|
|
43
|
+
documentLinks?: LegalDocumentLinks;
|
|
44
|
+
/**
|
|
45
|
+
* Callback fired when user confirms the consent.
|
|
46
|
+
* Only fires if required consents are accepted.
|
|
47
|
+
*/
|
|
48
|
+
onConfirm?: (result: LegalConsentResult) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Callback fired when user cancels/declines.
|
|
51
|
+
*/
|
|
52
|
+
onCancel?: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Callback fired whenever any consent value changes.
|
|
55
|
+
* Use this for real-time updates without requiring explicit submission.
|
|
56
|
+
*/
|
|
57
|
+
onChange?: (result: LegalConsentResult) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Custom checkbox renderer.
|
|
60
|
+
* Applications *can* optionally provide their own checkbox component renderer
|
|
61
|
+
* If not provided, uses native HTML checkboxes.
|
|
62
|
+
*/
|
|
63
|
+
renderCheckbox?: (props: LegalCheckboxProps) => ReactNode;
|
|
64
|
+
/**
|
|
65
|
+
* Label for the confirm button.
|
|
66
|
+
* Defaults to "Confirm & Continue".
|
|
67
|
+
*/
|
|
68
|
+
confirmButtonLabel?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Label for the cancel button.
|
|
71
|
+
* Defaults to "Cancel".
|
|
72
|
+
*/
|
|
73
|
+
cancelButtonLabel?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Additional CSS class names
|
|
76
|
+
*/
|
|
77
|
+
className?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Legal Consent Form Component
|
|
81
|
+
*
|
|
82
|
+
* A self-contained form component for collecting user consent for:
|
|
83
|
+
* 1. Terms & Conditions and Privacy Policy (required)
|
|
84
|
+
* 2. Study invitation notifications (optional)
|
|
85
|
+
*
|
|
86
|
+
* **Usage Pattern:**
|
|
87
|
+
* This component provides the content for a consent dialog. Applications
|
|
88
|
+
* should wrap this component in their own Dialog/Modal component, similar
|
|
89
|
+
* to how BmiForm is used.
|
|
90
|
+
*
|
|
91
|
+
* **Customization:**
|
|
92
|
+
* - Provide custom checkbox renderer via `renderCheckbox` prop
|
|
93
|
+
* - Customize document URLs via `documentLinks` prop
|
|
94
|
+
* - Style with CSS classes using `className` prop
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* <Dialog open={showConsent} onOpenChange={setShowConsent}>
|
|
99
|
+
* <DialogContent>
|
|
100
|
+
* <DialogHeader>
|
|
101
|
+
* <DialogTitle>Health Data Storage Consent</DialogTitle>
|
|
102
|
+
* </DialogHeader>
|
|
103
|
+
* <LegalConsentForm
|
|
104
|
+
* renderCheckbox={({ id, checked, onCheckedChange, className }) => (
|
|
105
|
+
* <Checkbox
|
|
106
|
+
* id={id}
|
|
107
|
+
* checked={checked}
|
|
108
|
+
* onCheckedChange={onCheckedChange}
|
|
109
|
+
* className={className}
|
|
110
|
+
* />
|
|
111
|
+
* )}
|
|
112
|
+
* onConfirm={(result) => {
|
|
113
|
+
* handleConsent(result);
|
|
114
|
+
* setShowConsent(false);
|
|
115
|
+
* }}
|
|
116
|
+
* onCancel={() => setShowConsent(false)}
|
|
117
|
+
* />
|
|
118
|
+
* </DialogContent>
|
|
119
|
+
* </Dialog>
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare const LegalConsentForm: ({ initialValues, documentLinks, onConfirm, onCancel, onChange, renderCheckbox, confirmButtonLabel, cancelButtonLabel, className, }: LegalConsentFormProps) => import("react/jsx-runtime").JSX.Element;
|
|
123
|
+
//# sourceMappingURL=legal-consent-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legal-consent-form.d.ts","sourceRoot":"","sources":["../../../src/components/legal-consent-form.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,aAAa,EAAE,OAAO,CAAC;IACvB,yDAAyD;IACzD,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE5C;;;OAGG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IAEnC;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAEjD;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAEhD;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,SAAS,CAAC;IAE1D;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,gBAAgB,GAAI,oIAU9B,qBAAqB,4CAmLvB,CAAC"}
|