@welshare/questionnaire 0.1.2 → 0.2.1
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 +308 -30
- package/dist/esm/components/bmi-form.d.ts +68 -0
- package/dist/esm/components/bmi-form.d.ts.map +1 -0
- package/dist/esm/components/bmi-form.js +138 -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 +6 -1
- package/dist/esm/components/question-renderer.d.ts.map +1 -1
- package/dist/esm/components/question-renderer.js +30 -14
- 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/decimal-question.d.ts +8 -1
- package/dist/esm/components/questions/decimal-question.d.ts.map +1 -1
- package/dist/esm/components/questions/decimal-question.js +19 -1
- package/dist/esm/components/questions/multiple-choice-question.d.ts.map +1 -1
- package/dist/esm/components/questions/multiple-choice-question.js +21 -13
- package/dist/esm/contexts/questionnaire-context.d.ts.map +1 -1
- package/dist/esm/contexts/questionnaire-context.js +7 -24
- package/dist/esm/index.d.ts +7 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/lib/bmi-helpers.d.ts +50 -0
- package/dist/esm/lib/bmi-helpers.d.ts.map +1 -0
- package/dist/esm/lib/bmi-helpers.js +69 -0
- package/dist/esm/lib/constants.d.ts +106 -0
- package/dist/esm/lib/constants.d.ts.map +1 -0
- package/dist/esm/lib/constants.js +105 -0
- package/dist/esm/lib/questionnaire-utils.d.ts +35 -1
- package/dist/esm/lib/questionnaire-utils.d.ts.map +1 -1
- package/dist/esm/lib/questionnaire-utils.js +111 -4
- package/dist/esm/types/fhir.d.ts +26 -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 +25 -0
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/styles.css +108 -0
- package/package.json +17 -6
- package/dist/node_modules/@welshare/questionnaire/.tshy/build.json +0 -8
- package/dist/node_modules/@welshare/questionnaire/.tshy/esm.json +0 -16
- package/dist/node_modules/@welshare/questionnaire/LICENSE +0 -7
- package/dist/node_modules/@welshare/questionnaire/README.md +0 -173
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.d.ts +0 -44
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/debug-section.js +0 -28
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.d.ts +0 -80
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/question-renderer.js +0 -183
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.d.ts +0 -15
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/boolean-question.js +0 -19
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.d.ts +0 -19
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/choice-question.js +0 -23
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.d.ts +0 -12
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/decimal-question.js +0 -7
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.d.ts +0 -18
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/integer-question.js +0 -24
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.d.ts +0 -20
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/multiple-choice-question.js +0 -39
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.d.ts +0 -12
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/components/questions/string-question.js +0 -7
- package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.d.ts +0 -41
- package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/contexts/questionnaire-context.js +0 -350
- package/dist/node_modules/@welshare/questionnaire/dist/esm/index.d.ts +0 -7
- package/dist/node_modules/@welshare/questionnaire/dist/esm/index.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/index.js +0 -6
- package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.d.ts +0 -33
- package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/lib/questionnaire-utils.js +0 -99
- package/dist/node_modules/@welshare/questionnaire/dist/esm/package.json +0 -3
- package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.d.ts +0 -117
- package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/types/fhir.js +0 -3
- package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.d.ts +0 -51
- package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.d.ts.map +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/esm/types/index.js +0 -1
- package/dist/node_modules/@welshare/questionnaire/dist/styles.css +0 -467
- package/dist/node_modules/@welshare/questionnaire/dist/tokens.css +0 -130
- package/dist/node_modules/@welshare/questionnaire/package.json +0 -85
- package/dist/node_modules/@welshare/questionnaire/src/components/debug-section.tsx +0 -116
- package/dist/node_modules/@welshare/questionnaire/src/components/question-renderer.tsx +0 -391
- package/dist/node_modules/@welshare/questionnaire/src/components/questionnaire-styles.css +0 -467
- package/dist/node_modules/@welshare/questionnaire/src/components/questionnaire-tokens.css +0 -130
- package/dist/node_modules/@welshare/questionnaire/src/components/questions/boolean-question.tsx +0 -72
- package/dist/node_modules/@welshare/questionnaire/src/components/questions/choice-question.tsx +0 -68
- package/dist/node_modules/@welshare/questionnaire/src/components/questions/decimal-question.tsx +0 -32
- package/dist/node_modules/@welshare/questionnaire/src/components/questions/integer-question.tsx +0 -87
- package/dist/node_modules/@welshare/questionnaire/src/components/questions/multiple-choice-question.tsx +0 -119
- package/dist/node_modules/@welshare/questionnaire/src/components/questions/string-question.tsx +0 -31
- package/dist/node_modules/@welshare/questionnaire/src/contexts/questionnaire-context.tsx +0 -499
- package/dist/node_modules/@welshare/questionnaire/src/index.ts +0 -41
- package/dist/node_modules/@welshare/questionnaire/src/lib/__tests__/questionnaire-utils.test.ts +0 -578
- package/dist/node_modules/@welshare/questionnaire/src/lib/questionnaire-utils.ts +0 -122
- package/dist/node_modules/@welshare/questionnaire/src/types/fhir.ts +0 -126
- package/dist/node_modules/@welshare/questionnaire/src/types/index.ts +0 -44
- package/dist/node_modules/@welshare/questionnaire/tsconfig.json +0 -16
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
|
|
@@ -97,12 +109,78 @@ const {
|
|
|
97
109
|
|
|
98
110
|
**Supported Types:** `choice`, `boolean`, `integer`, `decimal`, `string`, `text`
|
|
99
111
|
|
|
112
|
+
### BmiForm
|
|
113
|
+
|
|
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.
|
|
115
|
+
|
|
116
|
+
**Props:**
|
|
117
|
+
|
|
118
|
+
- `height: number` - Current height value (0 when empty)
|
|
119
|
+
- `weight: number` - Current weight value (0 when empty)
|
|
120
|
+
- `bmi: number` - Current BMI value (calculated and set by the component, 0 when not calculated)
|
|
121
|
+
- `onHeightChange: (value: number, unit: "cm" | "in") => void` - Called when height changes, includes current unit
|
|
122
|
+
- `onWeightChange: (value: number, unit: "kg" | "lb") => void` - Called when weight changes, includes current unit
|
|
123
|
+
- `onBmiChange: (value: number) => void` - Called when BMI is calculated or cleared
|
|
124
|
+
- `className?: string` - Optional CSS classes
|
|
125
|
+
|
|
126
|
+
**Features:**
|
|
127
|
+
|
|
128
|
+
- Controlled numeric values (height, weight, bmi) managed by parent
|
|
129
|
+
- BMI calculation handled internally by the component
|
|
130
|
+
- Unit system managed internally (defaults to metric)
|
|
131
|
+
- Automatically clears all fields (sets to 0) when switching unit systems
|
|
132
|
+
- No unit conversion - values are cleared on unit system change
|
|
133
|
+
- Uses consistent styling with questionnaire components
|
|
134
|
+
|
|
135
|
+
**Example:**
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useState } from "react";
|
|
139
|
+
import { BmiForm, getBmiCategory } from "@welshare/questionnaire";
|
|
140
|
+
import "@welshare/questionnaire/tokens.css";
|
|
141
|
+
import "@welshare/questionnaire/styles.css";
|
|
142
|
+
|
|
143
|
+
function MyComponent() {
|
|
144
|
+
const [height, setHeight] = useState(0);
|
|
145
|
+
const [weight, setWeight] = useState(0);
|
|
146
|
+
const [bmi, setBmi] = useState(0);
|
|
147
|
+
|
|
148
|
+
const handleBmiChange = (value: number) => {
|
|
149
|
+
setBmi(value);
|
|
150
|
+
|
|
151
|
+
// Optional: Get BMI category
|
|
152
|
+
if (value) {
|
|
153
|
+
const category = getBmiCategory(value);
|
|
154
|
+
console.log(`BMI Category: ${category}`);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<BmiForm
|
|
160
|
+
height={height}
|
|
161
|
+
weight={weight}
|
|
162
|
+
bmi={bmi}
|
|
163
|
+
onHeightChange={(value, unit) => setHeight(value)}
|
|
164
|
+
onWeightChange={(value, unit) => setWeight(value)}
|
|
165
|
+
onBmiChange={handleBmiChange}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
100
171
|
### Utilities
|
|
101
172
|
|
|
173
|
+
**Questionnaire Utilities:**
|
|
174
|
+
|
|
102
175
|
- `getVisiblePages(questionnaire)` - Get visible page groups
|
|
103
176
|
- `calculateProgress(currentIndex, total)` - Calculate progress percentage
|
|
104
177
|
- `getAllQuestionsFromPage(pageItem)` - Get all questions from a page
|
|
105
178
|
|
|
179
|
+
**BMI Helper Functions:**
|
|
180
|
+
|
|
181
|
+
- `calculateBmi(height, weight, unitSystem)` - Calculate BMI from height and weight
|
|
182
|
+
- `getBmiCategory(bmi)` - Get WHO BMI category (Underweight, Normal weight, Overweight, Obese)
|
|
183
|
+
|
|
106
184
|
## Theming
|
|
107
185
|
|
|
108
186
|
Override CSS custom properties:
|
|
@@ -134,40 +212,240 @@ Override CSS custom properties:
|
|
|
134
212
|
## FHIR Extensions
|
|
135
213
|
|
|
136
214
|
### Hidden Questions
|
|
215
|
+
|
|
137
216
|
```json
|
|
138
217
|
{
|
|
139
|
-
"extension": [
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
218
|
+
"extension": [
|
|
219
|
+
{
|
|
220
|
+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
|
|
221
|
+
"valueBoolean": true
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Media Attachments (Images)
|
|
228
|
+
|
|
229
|
+
Display images or other media content with question items using the FHIR SDC (Structured Data Capture) extensions.
|
|
230
|
+
|
|
231
|
+
**Item Media (images on questions):**
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"linkId": "body-diagram",
|
|
236
|
+
"text": "Where is the pain located?",
|
|
237
|
+
"type": "choice",
|
|
238
|
+
"extension": [
|
|
239
|
+
{
|
|
240
|
+
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemMedia",
|
|
241
|
+
"valueAttachment": {
|
|
242
|
+
"contentType": "image/png",
|
|
243
|
+
"url": "https://example.com/images/body-diagram.png",
|
|
244
|
+
"title": "Body diagram"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Answer Option Media (images on answer options):**
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"linkId": "skin-condition",
|
|
256
|
+
"text": "Which image best matches your condition?",
|
|
257
|
+
"type": "choice",
|
|
258
|
+
"answerOption": [
|
|
259
|
+
{
|
|
260
|
+
"valueCoding": {
|
|
261
|
+
"code": "mild",
|
|
262
|
+
"display": "Mild"
|
|
263
|
+
},
|
|
264
|
+
"extension": [
|
|
265
|
+
{
|
|
266
|
+
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia",
|
|
267
|
+
"valueAttachment": {
|
|
268
|
+
"contentType": "image/jpeg",
|
|
269
|
+
"url": "https://example.com/images/mild.jpg",
|
|
270
|
+
"title": "Mild condition"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"valueCoding": {
|
|
277
|
+
"code": "severe",
|
|
278
|
+
"display": "Severe"
|
|
279
|
+
},
|
|
280
|
+
"extension": [
|
|
281
|
+
{
|
|
282
|
+
"url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemAnswerMedia",
|
|
283
|
+
"valueAttachment": {
|
|
284
|
+
"contentType": "image/jpeg",
|
|
285
|
+
"url": "https://example.com/images/severe.jpg",
|
|
286
|
+
"title": "Severe condition"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**CSS Classes for Styling:**
|
|
296
|
+
|
|
297
|
+
- `.wq-question-media` - Container for question-level images
|
|
298
|
+
- `.wq-question-image` - Individual question image
|
|
299
|
+
- `.wq-choice-option-wrapper` - Wrapper around answer option with image
|
|
300
|
+
- `.wq-choice-option-image` - Individual answer option image
|
|
301
|
+
|
|
302
|
+
**Example Styling:**
|
|
303
|
+
|
|
304
|
+
```css
|
|
305
|
+
.wq-question-image {
|
|
306
|
+
max-width: 100%;
|
|
307
|
+
height: auto;
|
|
308
|
+
margin-bottom: 1rem;
|
|
309
|
+
border-radius: 0.5rem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.wq-choice-option-image {
|
|
313
|
+
max-width: 200px;
|
|
314
|
+
height: auto;
|
|
315
|
+
margin-bottom: 0.5rem;
|
|
316
|
+
border-radius: 0.25rem;
|
|
143
317
|
}
|
|
144
318
|
```
|
|
145
319
|
|
|
146
320
|
### Slider Controls
|
|
321
|
+
|
|
147
322
|
```json
|
|
148
323
|
{
|
|
149
|
-
"extension": [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
324
|
+
"extension": [
|
|
325
|
+
{
|
|
326
|
+
"url": "http://codes.welshare.app/StructureDefinition/questionnaire-slider-control",
|
|
327
|
+
"extension": [
|
|
328
|
+
{ "url": "minValue", "valueInteger": 0 },
|
|
329
|
+
{ "url": "maxValue", "valueInteger": 100 },
|
|
330
|
+
{ "url": "step", "valueInteger": 1 },
|
|
331
|
+
{ "url": "unit", "valueString": "minutes" }
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
]
|
|
158
335
|
}
|
|
159
336
|
```
|
|
160
337
|
|
|
161
338
|
### Exclusive Options
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"extension": [
|
|
343
|
+
{
|
|
344
|
+
"url": "http://codes.welshare.app/StructureDefinition/questionnaire-exclusive-option",
|
|
345
|
+
"valueString": "none-of-the-above-code"
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Input Helpers
|
|
352
|
+
|
|
353
|
+
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.
|
|
354
|
+
|
|
355
|
+
**FHIR Extension:**
|
|
356
|
+
|
|
162
357
|
```json
|
|
163
358
|
{
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
359
|
+
"linkId": "bmi",
|
|
360
|
+
"text": "Body Mass Index",
|
|
361
|
+
"type": "decimal",
|
|
362
|
+
"extension": [
|
|
363
|
+
{
|
|
364
|
+
"url": "http://codes.welshare.app/StructureDefinition/questionnaire-inputHelper",
|
|
365
|
+
"valueCodeableConcept": {
|
|
366
|
+
"coding": [
|
|
367
|
+
{
|
|
368
|
+
"system": "http://codes.welshare.app/input-helper-type",
|
|
369
|
+
"code": "bmi-calculator",
|
|
370
|
+
"display": "BMI Calculator"
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
"text": "Calculate BMI from height and weight"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
]
|
|
168
377
|
}
|
|
169
378
|
```
|
|
170
379
|
|
|
380
|
+
**Implementation:**
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
import { useState } from "react";
|
|
384
|
+
import {
|
|
385
|
+
QuestionRenderer,
|
|
386
|
+
BmiForm,
|
|
387
|
+
type HelperTriggerProps,
|
|
388
|
+
} from "@welshare/questionnaire";
|
|
389
|
+
|
|
390
|
+
function MyQuestionnaireRenderer({ item }) {
|
|
391
|
+
const [showBmiDialog, setShowBmiDialog] = useState(false);
|
|
392
|
+
const [helperCallback, setHelperCallback] = useState<
|
|
393
|
+
((v: string | number) => void) | null
|
|
394
|
+
>(null);
|
|
395
|
+
|
|
396
|
+
const handleHelperTrigger = ({
|
|
397
|
+
helper,
|
|
398
|
+
onValueSelected,
|
|
399
|
+
}: HelperTriggerProps) => {
|
|
400
|
+
if (helper.type === "bmi-calculator") {
|
|
401
|
+
return (
|
|
402
|
+
<button
|
|
403
|
+
type="button"
|
|
404
|
+
onClick={() => {
|
|
405
|
+
setHelperCallback(() => onValueSelected);
|
|
406
|
+
setShowBmiDialog(true);
|
|
407
|
+
}}
|
|
408
|
+
>
|
|
409
|
+
{helper.display || "Calculate"}
|
|
410
|
+
</button>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<>
|
|
418
|
+
<QuestionRenderer item={item} renderHelperTrigger={handleHelperTrigger} />
|
|
419
|
+
|
|
420
|
+
{showBmiDialog && (
|
|
421
|
+
<Dialog onClose={() => setShowBmiDialog(false)}>
|
|
422
|
+
<BmiForm
|
|
423
|
+
onSubmit={({ bmi }) => {
|
|
424
|
+
helperCallback?.(bmi);
|
|
425
|
+
setShowBmiDialog(false);
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
</Dialog>
|
|
429
|
+
)}
|
|
430
|
+
</>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Helper Trigger Props:**
|
|
436
|
+
|
|
437
|
+
- `helper: InputHelperConfig` - Configuration from the extension
|
|
438
|
+
- `type: string` - Helper identifier (e.g., "bmi-calculator")
|
|
439
|
+
- `display?: string` - Display name from the extension
|
|
440
|
+
- `description?: string` - Description/tooltip text
|
|
441
|
+
- `linkId: string` - The question's linkId
|
|
442
|
+
- `currentValue?: T` - Current field value (if any), where T defaults to `string | number`
|
|
443
|
+
- `onValueSelected: (value: T) => void` - Callback to update the field
|
|
444
|
+
|
|
445
|
+
**Note:** `HelperTriggerProps<T>` is a generic interface. For decimal questions, it's specialized as `HelperTriggerProps<number>`, but you can use different types for other question types (e.g., `HelperTriggerProps<string>` for text inputs).
|
|
446
|
+
|
|
447
|
+
**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.).
|
|
448
|
+
|
|
171
449
|
## License
|
|
172
450
|
|
|
173
451
|
MIT © Welshare UG (haftungsbeschränkt)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result object returned on submission - all values in metric units
|
|
3
|
+
*/
|
|
4
|
+
export interface BmiSubmissionResult {
|
|
5
|
+
/** Height in centimeters */
|
|
6
|
+
heightCm: number;
|
|
7
|
+
/** Weight in kilograms */
|
|
8
|
+
weightKg: number;
|
|
9
|
+
/** Calculated BMI (kg/m²) */
|
|
10
|
+
bmi: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Initial values for the BMI form - always in metric units
|
|
14
|
+
*/
|
|
15
|
+
export interface BmiInitialValues {
|
|
16
|
+
/** Height in centimeters */
|
|
17
|
+
heightCm?: number;
|
|
18
|
+
/** Weight in kilograms */
|
|
19
|
+
weightKg?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface BmiFormProps {
|
|
22
|
+
/**
|
|
23
|
+
* Initial values in metric units (cm, kg).
|
|
24
|
+
* These are used to populate the form on mount.
|
|
25
|
+
* If unitPreference is "imperial", they will be converted for display.
|
|
26
|
+
*/
|
|
27
|
+
initialValues?: BmiInitialValues;
|
|
28
|
+
/**
|
|
29
|
+
* Preferred unit system for initial display.
|
|
30
|
+
* When "imperial", initial metric values are converted for display.
|
|
31
|
+
* Defaults to "metric".
|
|
32
|
+
*/
|
|
33
|
+
unitPreference?: "metric" | "imperial";
|
|
34
|
+
/**
|
|
35
|
+
* Callback fired when user submits/confirms the BMI values.
|
|
36
|
+
* Returns all values in metric units (cm, kg).
|
|
37
|
+
*/
|
|
38
|
+
onSubmit?: (result: BmiSubmissionResult) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Callback fired whenever any value changes.
|
|
41
|
+
* Returns all values in metric units (cm, kg).
|
|
42
|
+
* Use this for real-time updates without requiring explicit submission.
|
|
43
|
+
*/
|
|
44
|
+
onChange?: (result: BmiSubmissionResult) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Additional CSS class names
|
|
47
|
+
*/
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* BMI Form Component
|
|
52
|
+
*
|
|
53
|
+
* A self-contained form component for entering height and weight measurements.
|
|
54
|
+
* Manages all state internally and supports both metric (cm/kg) and imperial (ft+in/lb) input.
|
|
55
|
+
*
|
|
56
|
+
* **Clinical Standard Compliance:**
|
|
57
|
+
* - All external communication uses metric units (cm, kg)
|
|
58
|
+
* - Imperial inputs are UI conveniences that convert to metric internally
|
|
59
|
+
* - Initial values must always be provided in metric
|
|
60
|
+
* - BMI is always calculated using the metric formula: kg / m²
|
|
61
|
+
*
|
|
62
|
+
* **Unit System Behavior:**
|
|
63
|
+
* - Switching unit systems clears all input fields (no automatic conversion)
|
|
64
|
+
* - Initial values provided in metric are converted to imperial for display
|
|
65
|
+
* only when unitPreference is "imperial"
|
|
66
|
+
*/
|
|
67
|
+
export declare const BmiForm: ({ initialValues, unitPreference, onSubmit, onChange, className, }: BmiFormProps) => import("react/jsx-runtime").JSX.Element;
|
|
68
|
+
//# sourceMappingURL=bmi-form.d.ts.map
|
|
@@ -0,0 +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,4CA8Sd,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
|
+
import { calculateBmi, cmToFeetAndInches, feetAndInchesToCm, kgToLbs, lbsToKg, } from "../lib/bmi-helpers.js";
|
|
4
|
+
/**
|
|
5
|
+
* BMI Form Component
|
|
6
|
+
*
|
|
7
|
+
* A self-contained form component for entering height and weight measurements.
|
|
8
|
+
* Manages all state internally and supports both metric (cm/kg) and imperial (ft+in/lb) input.
|
|
9
|
+
*
|
|
10
|
+
* **Clinical Standard Compliance:**
|
|
11
|
+
* - All external communication uses metric units (cm, kg)
|
|
12
|
+
* - Imperial inputs are UI conveniences that convert to metric internally
|
|
13
|
+
* - Initial values must always be provided in metric
|
|
14
|
+
* - BMI is always calculated using the metric formula: kg / m²
|
|
15
|
+
*
|
|
16
|
+
* **Unit System Behavior:**
|
|
17
|
+
* - Switching unit systems clears all input fields (no automatic conversion)
|
|
18
|
+
* - Initial values provided in metric are converted to imperial for display
|
|
19
|
+
* only when unitPreference is "imperial"
|
|
20
|
+
*/
|
|
21
|
+
export const BmiForm = ({ initialValues, unitPreference = "metric", onSubmit, onChange, className = "", }) => {
|
|
22
|
+
// Track if this is the initial render to handle initial value conversion
|
|
23
|
+
const isInitialRender = useRef(true);
|
|
24
|
+
// Unit system state - starts with preference
|
|
25
|
+
const [unitSystem, setUnitSystem] = useState(unitPreference);
|
|
26
|
+
// Internal state for metric display (cm)
|
|
27
|
+
const [heightCmInput, setHeightCmInput] = useState("");
|
|
28
|
+
// Internal state for imperial height display (ft + in)
|
|
29
|
+
const [feetInput, setFeetInput] = useState("");
|
|
30
|
+
const [inchesInput, setInchesInput] = useState("");
|
|
31
|
+
// Internal state for metric weight display (kg)
|
|
32
|
+
const [weightKgInput, setWeightKgInput] = useState("");
|
|
33
|
+
// Internal state for imperial weight display (lb)
|
|
34
|
+
const [weightLbInput, setWeightLbInput] = useState("");
|
|
35
|
+
// The canonical metric values (source of truth for calculations)
|
|
36
|
+
const [heightCm, setHeightCm] = useState(0);
|
|
37
|
+
const [weightKg, setWeightKg] = useState(0);
|
|
38
|
+
// Calculated BMI (always from metric values)
|
|
39
|
+
const [bmi, setBmi] = useState(0);
|
|
40
|
+
// Initialize form with provided values (converted to display units if needed)
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!isInitialRender.current)
|
|
43
|
+
return;
|
|
44
|
+
isInitialRender.current = false;
|
|
45
|
+
const initialHeightCm = initialValues?.heightCm ?? 0;
|
|
46
|
+
const initialWeightKg = initialValues?.weightKg ?? 0;
|
|
47
|
+
// Set canonical metric values
|
|
48
|
+
setHeightCm(initialHeightCm);
|
|
49
|
+
setWeightKg(initialWeightKg);
|
|
50
|
+
if (unitPreference === "metric") {
|
|
51
|
+
// Display as metric
|
|
52
|
+
if (initialHeightCm > 0) {
|
|
53
|
+
setHeightCmInput(String(initialHeightCm));
|
|
54
|
+
}
|
|
55
|
+
if (initialWeightKg > 0) {
|
|
56
|
+
setWeightKgInput(String(initialWeightKg));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Convert metric to imperial for display
|
|
61
|
+
if (initialHeightCm > 0) {
|
|
62
|
+
const { feet, inches } = cmToFeetAndInches(initialHeightCm);
|
|
63
|
+
setFeetInput(String(feet));
|
|
64
|
+
setInchesInput(String(inches));
|
|
65
|
+
}
|
|
66
|
+
if (initialWeightKg > 0) {
|
|
67
|
+
const lbs = Math.round(kgToLbs(initialWeightKg) * 10) / 10;
|
|
68
|
+
setWeightLbInput(String(lbs));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, [initialValues, unitPreference]);
|
|
72
|
+
// Calculate BMI whenever canonical metric values change
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const calculatedBmi = calculateBmi(heightCm, weightKg);
|
|
75
|
+
setBmi(calculatedBmi);
|
|
76
|
+
}, [heightCm, weightKg]);
|
|
77
|
+
// Notify parent of changes
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (onChange && !isInitialRender.current) {
|
|
80
|
+
onChange({ heightCm, weightKg, bmi });
|
|
81
|
+
}
|
|
82
|
+
}, [heightCm, weightKg, bmi, onChange]);
|
|
83
|
+
// Handle unit system change - clear all fields
|
|
84
|
+
const handleUnitSystemChange = useCallback((newSystem) => {
|
|
85
|
+
if (newSystem === unitSystem)
|
|
86
|
+
return;
|
|
87
|
+
// Clear all inputs
|
|
88
|
+
setHeightCmInput("");
|
|
89
|
+
setFeetInput("");
|
|
90
|
+
setInchesInput("");
|
|
91
|
+
setWeightKgInput("");
|
|
92
|
+
setWeightLbInput("");
|
|
93
|
+
// Clear canonical values
|
|
94
|
+
setHeightCm(0);
|
|
95
|
+
setWeightKg(0);
|
|
96
|
+
setBmi(0);
|
|
97
|
+
setUnitSystem(newSystem);
|
|
98
|
+
}, [unitSystem]);
|
|
99
|
+
// Metric height change (cm)
|
|
100
|
+
const handleHeightCmChange = useCallback((value) => {
|
|
101
|
+
setHeightCmInput(value);
|
|
102
|
+
const numValue = parseFloat(value);
|
|
103
|
+
setHeightCm(isNaN(numValue) ? 0 : numValue);
|
|
104
|
+
}, []);
|
|
105
|
+
// Imperial height change (feet)
|
|
106
|
+
const handleFeetChange = useCallback((value) => {
|
|
107
|
+
setFeetInput(value);
|
|
108
|
+
const feet = parseFloat(value) || 0;
|
|
109
|
+
const inches = parseFloat(inchesInput) || 0;
|
|
110
|
+
setHeightCm(feetAndInchesToCm(feet, inches));
|
|
111
|
+
}, [inchesInput]);
|
|
112
|
+
// Imperial height change (inches)
|
|
113
|
+
const handleInchesChange = useCallback((value) => {
|
|
114
|
+
setInchesInput(value);
|
|
115
|
+
const feet = parseFloat(feetInput) || 0;
|
|
116
|
+
const inches = parseFloat(value) || 0;
|
|
117
|
+
setHeightCm(feetAndInchesToCm(feet, inches));
|
|
118
|
+
}, [feetInput]);
|
|
119
|
+
// Metric weight change (kg)
|
|
120
|
+
const handleWeightKgChange = useCallback((value) => {
|
|
121
|
+
setWeightKgInput(value);
|
|
122
|
+
const numValue = parseFloat(value);
|
|
123
|
+
setWeightKg(isNaN(numValue) ? 0 : numValue);
|
|
124
|
+
}, []);
|
|
125
|
+
// Imperial weight change (lb)
|
|
126
|
+
const handleWeightLbChange = useCallback((value) => {
|
|
127
|
+
setWeightLbInput(value);
|
|
128
|
+
const numValue = parseFloat(value);
|
|
129
|
+
setWeightKg(isNaN(numValue) ? 0 : lbsToKg(numValue));
|
|
130
|
+
}, []);
|
|
131
|
+
// Handle form submission
|
|
132
|
+
const handleSubmit = useCallback(() => {
|
|
133
|
+
if (onSubmit) {
|
|
134
|
+
onSubmit({ heightCm, weightKg, bmi });
|
|
135
|
+
}
|
|
136
|
+
}, [onSubmit, heightCm, weightKg, bmi]);
|
|
137
|
+
return (_jsxs("div", { className: `wq-bmi-form ${className}`, children: [_jsxs("div", { className: "wq-bmi-unit-toggle", children: [_jsx("button", { type: "button", className: `wq-bmi-unit-option ${unitSystem === "metric" ? "wq-selected" : ""}`, onClick: () => handleUnitSystemChange("metric"), "aria-pressed": unitSystem === "metric", children: "Metric" }), _jsx("button", { type: "button", className: `wq-bmi-unit-option ${unitSystem === "imperial" ? "wq-selected" : ""}`, onClick: () => handleUnitSystemChange("imperial"), "aria-pressed": unitSystem === "imperial", children: "Imperial" })] }), _jsxs("div", { className: "wq-bmi-field", children: [_jsx("label", { htmlFor: "bmi-height", className: "wq-bmi-label", children: unitSystem === "metric" ? "Height (cm)" : "Height (ft + in)" }), unitSystem === "metric" ? (_jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-height", type: "number", className: "wq-question-input", value: heightCmInput, onChange: (e) => handleHeightCmChange(e.target.value), placeholder: "Enter height in cm", step: "0.1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "cm" })] })) : (_jsxs("div", { className: "wq-bmi-imperial-height", children: [_jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-height-feet", type: "number", className: "wq-question-input", value: feetInput, onChange: (e) => handleFeetChange(e.target.value), placeholder: "Feet", step: "1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "ft" })] }), _jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-height-inches", type: "number", className: "wq-question-input", value: inchesInput, onChange: (e) => handleInchesChange(e.target.value), placeholder: "Inches", step: "0.1", min: "0", max: "11.9" }), _jsx("span", { className: "wq-bmi-unit", children: "in" })] })] }))] }), _jsxs("div", { className: "wq-bmi-field", children: [_jsx("label", { htmlFor: "bmi-weight", className: "wq-bmi-label", children: unitSystem === "metric" ? "Weight (kg)" : "Weight (lbs)" }), _jsx("div", { className: "wq-bmi-input-group", children: unitSystem === "metric" ? (_jsxs(_Fragment, { children: [_jsx("input", { id: "bmi-weight", type: "number", className: "wq-question-input", value: weightKgInput, onChange: (e) => handleWeightKgChange(e.target.value), placeholder: "Enter weight in kg", step: "0.1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "kg" })] })) : (_jsxs(_Fragment, { children: [_jsx("input", { id: "bmi-weight", type: "number", className: "wq-question-input", value: weightLbInput, onChange: (e) => handleWeightLbChange(e.target.value), placeholder: "Enter weight in lbs", step: "0.1", min: "0" }), _jsx("span", { className: "wq-bmi-unit", children: "lbs" })] })) })] }), _jsxs("div", { className: "wq-bmi-field", children: [_jsx("label", { htmlFor: "bmi-result", className: "wq-bmi-label", children: "BMI" }), _jsxs("div", { className: "wq-bmi-input-group", children: [_jsx("input", { id: "bmi-result", type: "text", className: "wq-question-input wq-bmi-result", value: bmi ? bmi.toFixed(1) : "", placeholder: "Calculated automatically", readOnly: true, disabled: true }), _jsx("span", { className: "wq-bmi-unit", children: "kg/m\u00B2" })] })] }), bmi > 0 && (_jsx("div", { className: "wq-bmi-info", children: _jsx("p", { className: "wq-text-secondary", children: "BMI is calculated automatically from height and weight measurements." }) })), onSubmit && (_jsx("div", { className: "wq-bmi-actions", children: _jsx("button", { type: "button", className: "wq-button wq-button-primary", onClick: handleSubmit, disabled: !bmi, children: "Confirm Measurements" }) }))] }));
|
|
138
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Attachment } from "../types/fhir.js";
|
|
2
|
+
export interface MediaAttachmentProps {
|
|
3
|
+
attachment: Attachment;
|
|
4
|
+
alt: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Renders a single media attachment (image) if it has a URL and is an image type
|
|
9
|
+
*/
|
|
10
|
+
export declare const MediaAttachment: ({ attachment, alt, className, }: MediaAttachmentProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
+
export interface MediaAttachmentsProps {
|
|
12
|
+
attachments: Attachment[];
|
|
13
|
+
baseAlt?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
containerClassName?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Renders multiple media attachments in a container
|
|
19
|
+
*/
|
|
20
|
+
export declare const MediaAttachments: ({ attachments, baseAlt, className, containerClassName, }: MediaAttachmentsProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
//# sourceMappingURL=media-attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-attachment.d.ts","sourceRoot":"","sources":["../../../src/components/media-attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,iCAI7B,oBAAoB,mDAkBtB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,0DAK9B,qBAAqB,mDAevB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Renders a single media attachment (image) if it has a URL and is an image type
|
|
4
|
+
*/
|
|
5
|
+
export const MediaAttachment = ({ attachment, alt, className = "", }) => {
|
|
6
|
+
// Only render if URL exists
|
|
7
|
+
if (!attachment.url)
|
|
8
|
+
return null;
|
|
9
|
+
// Check if it's an image (or assume image if no contentType)
|
|
10
|
+
const isImage = !attachment.contentType || attachment.contentType.startsWith("image/");
|
|
11
|
+
if (!isImage)
|
|
12
|
+
return null;
|
|
13
|
+
return (_jsx("img", { src: attachment.url, alt: alt, className: className, title: attachment.title }));
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Renders multiple media attachments in a container
|
|
17
|
+
*/
|
|
18
|
+
export const MediaAttachments = ({ attachments, baseAlt = "Image", className = "", containerClassName = "", }) => {
|
|
19
|
+
if (attachments.length === 0)
|
|
20
|
+
return null;
|
|
21
|
+
return (_jsx("div", { className: containerClassName, children: attachments.map((attachment, index) => (_jsx(MediaAttachment, { attachment: attachment, alt: attachment.title || `${baseAlt} ${index + 1}`, className: className }, index))) }));
|
|
22
|
+
};
|