@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.
Files changed (36) hide show
  1. package/README.md +305 -60
  2. package/dist/esm/components/bmi-form.d.ts.map +1 -1
  3. package/dist/esm/components/legal-consent-form.d.ts +123 -0
  4. package/dist/esm/components/legal-consent-form.d.ts.map +1 -0
  5. package/dist/esm/components/legal-consent-form.js +97 -0
  6. package/dist/esm/components/media-attachment.d.ts +21 -0
  7. package/dist/esm/components/media-attachment.d.ts.map +1 -0
  8. package/dist/esm/components/media-attachment.js +22 -0
  9. package/dist/esm/components/question-renderer.d.ts.map +1 -1
  10. package/dist/esm/components/question-renderer.js +9 -4
  11. package/dist/esm/components/questions/boolean-question.d.ts +4 -4
  12. package/dist/esm/components/questions/boolean-question.d.ts.map +1 -1
  13. package/dist/esm/components/questions/boolean-question.js +6 -6
  14. package/dist/esm/components/questions/choice-question.d.ts +4 -4
  15. package/dist/esm/components/questions/choice-question.d.ts.map +1 -1
  16. package/dist/esm/components/questions/choice-question.js +19 -11
  17. package/dist/esm/components/questions/multiple-choice-question.d.ts.map +1 -1
  18. package/dist/esm/components/questions/multiple-choice-question.js +19 -11
  19. package/dist/esm/contexts/questionnaire-context.d.ts.map +1 -1
  20. package/dist/esm/contexts/questionnaire-context.js +4 -22
  21. package/dist/esm/index.d.ts +4 -2
  22. package/dist/esm/index.d.ts.map +1 -1
  23. package/dist/esm/index.js +2 -1
  24. package/dist/esm/lib/bmi-helpers.d.ts.map +1 -1
  25. package/dist/esm/lib/constants.d.ts +12 -0
  26. package/dist/esm/lib/constants.d.ts.map +1 -1
  27. package/dist/esm/lib/constants.js +12 -0
  28. package/dist/esm/lib/questionnaire-utils.d.ts +15 -1
  29. package/dist/esm/lib/questionnaire-utils.d.ts.map +1 -1
  30. package/dist/esm/lib/questionnaire-utils.js +26 -0
  31. package/dist/esm/types/fhir.d.ts +25 -5
  32. package/dist/esm/types/fhir.d.ts.map +1 -1
  33. package/dist/esm/types/fhir.js +3 -1
  34. package/dist/esm/types/index.d.ts.map +1 -1
  35. package/dist/styles.css +153 -0
  36. 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 'react';
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 '@welshare/questionnaire';
22
- import '@welshare/questionnaire/tokens.css';
23
- import '@welshare/questionnaire/styles.css';
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>(null);
48
+ const [questionnaire, setQuestionnaire] = useState<Questionnaire | null>(
49
+ null
50
+ );
49
51
 
50
52
  useEffect(() => {
51
- fetch('/api/questionnaire/your-id')
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, response,
80
- updateAnswer, updateMultipleAnswers,
81
- getAnswer, getAnswers,
82
- isPageValid, getRequiredQuestions, getUnansweredRequiredQuestions,
83
- markValidationErrors, clearValidationErrors, hasValidationError,
84
- debugMode, toggleDebugMode,
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 'react';
125
- import { BmiForm, getBmiCategory } from '@welshare/questionnaire';
126
- import '@welshare/questionnaire/tokens.css';
127
- import '@welshare/questionnaire/styles.css';
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
- "extension": [{
202
- "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
203
- "valueBoolean": true
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
- "url": "http://codes.welshare.app/StructureDefinition/questionnaire-slider-control",
213
- "extension": [
214
- { "url": "minValue", "valueInteger": 0 },
215
- { "url": "maxValue", "valueInteger": 100 },
216
- { "url": "step", "valueInteger": 1 },
217
- { "url": "unit", "valueString": "minutes" }
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
- "url": "http://codes.welshare.app/StructureDefinition/questionnaire-exclusive-option",
228
- "valueString": "none-of-the-above-code"
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
- "url": "http://codes.welshare.app/StructureDefinition/questionnaire-inputHelper",
245
- "valueCodeableConcept": {
246
- "coding": [{
247
- "system": "http://codes.welshare.app/input-helper-type",
248
- "code": "bmi-calculator",
249
- "display": "BMI Calculator"
250
- }],
251
- "text": "Calculate BMI from height and weight"
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 'react';
261
- import { QuestionRenderer, BmiForm, type HelperTriggerProps } from '@welshare/questionnaire';
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<((v: string | number) => void) | null>(null);
266
-
267
- const handleHelperTrigger = ({ helper, onValueSelected }: HelperTriggerProps) => {
268
- if (helper.type === 'bmi-calculator') {
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 || 'Calculate'}
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
- item={item}
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,4CAmSd,CAAC"}
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"}