@explorer02/cfm-survey-sdk 0.2.2 → 0.2.4
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/dist/cli/index.js +140 -33
- package/dist/cli/index.mjs +140 -33
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -1
- package/postinstall.js +8 -5
- package/templates/AGENT.md +62 -17
- package/templates/deploy-wizard-to-s3.sh +52 -0
- package/templates/docs/00-integration/agent-execution-flow.md +281 -0
- package/templates/docs/00-integration/agent-operating-contract.md +260 -0
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/apply-ui-config.md +365 -0
- package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
- package/templates/docs/00-integration/client-integration-guide.md +10 -8
- package/templates/docs/00-integration/client-lib-folder.md +115 -0
- package/templates/docs/00-integration/component-checklist.md +90 -54
- package/templates/docs/00-integration/constraints.md +13 -5
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +16 -1
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +24 -0
- package/templates/docs/00-integration/progress.md +4 -2
- package/templates/docs/00-integration/question-type-sdk-matrix.md +11 -9
- package/templates/docs/00-integration/setup.md +15 -3
- package/templates/docs/00-integration/skip-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +1 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +144 -0
- package/templates/docs/00-integration/useSurveySDK.md +35 -1
- package/templates/docs/00-integration/wizard-api.md +134 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +246 -0
- package/templates/docs/00-integration/wizard-preview-build-guide.md +400 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +195 -0
- package/templates/docs/01-components/01-survey-page.md +18 -8
- package/templates/docs/01-components/02-question.md +2 -2
- package/templates/docs/01-components/03-rating-scale.md +12 -18
- package/templates/docs/01-components/05-csat-matrix-scale.md +18 -26
- package/templates/docs/01-components/06-likert-matrix-scale.md +19 -38
- package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
- package/templates/docs/01-components/08-file-upload-scale.md +15 -52
- package/templates/docs/01-components/09-custom-slider-track.md +15 -25
- package/templates/docs/01-components/10-header-footer.md +23 -4
- package/templates/docs/01-components/11-progress-bar.md +14 -6
- package/templates/docs/01-components/13-matrix-dropdown.md +28 -33
- package/templates/docs/01-components/17-heatmap-scale.md +4 -4
- package/templates/docs/01-components/18-rank-order-scale.md +24 -31
- package/templates/docs/01-components/19-survey-sticky-chrome.md +79 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +38 -26
- package/templates/docs/02-reference/question-types/11-file-upload.md +36 -21
- package/templates/docs/02-reference/question-types/README.md +11 -1
- package/templates/docs/02-reference/routing-table.md +1 -0
- package/templates/docs/02-reference/value-derivation.md +1 -1
- package/templates/docs/03-ui-specs/00-question-shell.md +10 -0
- package/templates/docs/03-ui-specs/01-rating.md +28 -2
- package/templates/docs/03-ui-specs/02-radio.md +94 -24
- package/templates/docs/03-ui-specs/03-text.md +12 -1
- package/templates/docs/03-ui-specs/04-csat.md +90 -26
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +74 -35
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +50 -16
- package/templates/docs/03-ui-specs/09-slider-matrix.md +70 -26
- package/templates/docs/03-ui-specs/10-file-upload.md +52 -20
- package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +92 -5
- package/templates/docs/03-ui-specs/13-heatmap.md +21 -3
- package/templates/docs/03-ui-specs/14-rank-order.md +108 -28
- package/templates/docs/03-ui-specs/README.md +11 -7
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
- package/templates/docs/MANIFEST.json +187 -6
- package/templates/docs/index.md +116 -116
- package/templates/docs/templates/CsatMatrixScale.tsx +637 -0
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/FileUploadScale.tsx +262 -0
- package/templates/docs/templates/HeatmapScale.tsx +114 -0
- package/templates/docs/templates/LikertMatrixScale.tsx +414 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +3 -0
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/RatingScale.tsx +129 -0
- package/templates/docs/templates/SliderMatrixScale.tsx +248 -0
- package/templates/docs/templates/SurveyStickyChrome.tsx +24 -0
- package/templates/docs/templates/customFieldValues.ts +36 -0
- package/templates/docs/templates/implementation_plan.md +75 -8
- package/templates/docs/templates/labelStyles.ts +33 -0
- package/templates/docs/templates/selectionStyles.ts +100 -0
- package/templates/docs/templates/survey-inventory.schema.json +28 -23
- package/templates/docs/templates/surveyUiIcons.tsx +63 -0
- package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
- package/templates/docs/templates/verify-agent-build.sh +151 -0
- package/templates/preview-harness/preview-bridge.inline.js +201 -0
- package/templates/preview-harness/previewPages.js +108 -0
- package/templates/preview-harness/previewPages.ts +298 -0
- package/templates/preview-harness/vite-app/src/PreviewConfigContext.tsx +67 -0
- package/templates/preview-harness/vite-app/src/QuestionPreview.tsx +122 -0
- package/templates/preview-harness/vite-app/src/SurveyPagePreview.tsx +75 -0
- package/templates/preview-harness/vite-app/src/fixtures/questions.ts +311 -0
- package/templates/preview-harness/vite-app/src/globals.css +16 -0
- package/templates/preview-harness/vite-app/src/mount.tsx +9 -0
- package/templates/preview-harness/vite-app/src/preview-live-overrides.css +101 -0
- package/templates/preview-harness/vite-app/stubs/next-dynamic.tsx +25 -0
- package/templates/preview-harness/vite-app/stubs/next-image.tsx +29 -0
- package/templates/previewBridge.ts +153 -0
- package/templates/survey-theme.css +295 -0
- package/templates/survey-ui-config.schema.json +213 -0
- package/templates/wizard-dist/assets/PreviewMock-DgHfrVeb.js +1 -0
- package/templates/wizard-dist/assets/TypePanel-CFVC3Ptn.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-DYK3X1e5.js +34 -0
- package/templates/wizard-dist/assets/vendor-BwkXDkd3.js +17 -0
- package/templates/wizard-dist/index.html +20 -0
- package/templates/wizard.html +13 -1129
|
@@ -1,40 +1,34 @@
|
|
|
1
1
|
# Rating Scale Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/01-rating.md` (NPS) · `03-ui-specs/05-rating-scale.md` (star/emoji gap)
|
|
4
|
-
> **
|
|
4
|
+
> **Portable template:** copy [`templates/RatingScale.tsx`](../templates/RatingScale.tsx) + [`surveyUiScaleUtils.ts`](../templates/surveyUiScaleUtils.ts) + [`surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx)
|
|
5
5
|
> **Target Component**: `RatingScale`
|
|
6
6
|
> **Handles**: `type: 'NPS_SCALE'` (via `QUESTION_TYPE.NPS_SCALE`)
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
Render a horizontal sequence of numbered badges with traffic-light colors (NPS 0–10).
|
|
10
11
|
|
|
11
12
|
## Configuration Matrix
|
|
12
|
-
You MUST handle the following configurations dynamically:
|
|
13
13
|
|
|
14
14
|
### 1. Color Grading (NPS)
|
|
15
|
-
-
|
|
15
|
+
- Use `getAccentColor(value)` from `surveyUiScaleUtils.ts` for badge backgrounds
|
|
16
16
|
|
|
17
17
|
### 2. Label Positioning (`minLabel`, `midLabel`, `maxLabel`)
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- **Mid Label Calculation**: You must calculate the exact center position: `style={{ left: \`\${(midLabelIndex / (options.length - 1)) * 100}%\` }}`.
|
|
18
|
+
- Render in `Question.tsx` above `RatingScale` with absolute positioning
|
|
19
|
+
- Mid label: `left: (midLabelIndex / (options.length - 1)) * 100%`
|
|
21
20
|
|
|
22
21
|
## UI/UX Rules (CRITICAL)
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
3. **Selected State**: The selected badge MUST pop out prominently: `border-2 border-[#e20074] shadow-md scale-105 opacity-100 font-bold`.
|
|
29
|
-
4. **Hover State**: `hover:scale-105 hover:border-[#e20074] transition-all cursor-pointer`.
|
|
30
|
-
5. **Tooltips**: Every badge MUST have `title={option.label}` applied for accessibility and semantic context (e.g. hovering '10' shows 'Extremely Likely').
|
|
23
|
+
1. **Shape**: Square badges with rounded corners — not full circles
|
|
24
|
+
2. **Unselected State**: Dim unselected when any item selected (`opacity-30`)
|
|
25
|
+
3. **Selected State**: `border-2 border-[#e20074] shadow-md scale-105`
|
|
26
|
+
4. **Tooltips**: `title={option.label}` on every badge
|
|
31
27
|
|
|
32
28
|
## SDK Integration Checklist (agent)
|
|
33
29
|
|
|
30
|
+
- [ ] Copied portable `RatingScale.tsx`
|
|
34
31
|
- [ ] Type literal: `QUESTION_TYPE.NPS_SCALE`
|
|
35
32
|
- [ ] Answer stored as: `number`
|
|
36
33
|
- [ ] onAction CHANGE: `{ questionId, answerValue: number }`
|
|
37
|
-
- [ ]
|
|
38
|
-
- [ ] Custom fields: 4th arg on scale visibility helper when needed
|
|
39
|
-
- [ ] Logic features: skip ✓ display ✓ answer ✓ variants ✓ numbering ✓
|
|
40
|
-
- [ ] Matrix row: [`question-type-sdk-matrix.md`](../00-integration/question-type-sdk-matrix.md#nps_scale)
|
|
34
|
+
- [ ] minLabel/maxLabel rendered in Question.tsx when present
|
|
@@ -1,44 +1,36 @@
|
|
|
1
1
|
# CSAT Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
|
-
> **UI spec (authoritative):** `03-ui-specs/04-csat.md` (
|
|
4
|
-
> **
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/04-csat.md` (CSAT) · `03-ui-specs/08-matrix-csat-rating.md` (RATING)
|
|
4
|
+
> **Portable template:** copy [`templates/CsatMatrixScale.tsx`](../templates/CsatMatrixScale.tsx) + shared deps (see ui-spec)
|
|
5
5
|
> **Target Component**: `CsatMatrixScale`
|
|
6
6
|
> **Handles**: `type: 'CSAT_MATRIX'` and `type: 'RATING_MATRIX'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
9
|
|
|
10
|
-
Render a grid where columns
|
|
10
|
+
Render a matrix grid where rows are `statementRows` and columns are `scaleColumns`. Route on `displayStyle` and `gridLayout`.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Statement & labels (CRITICAL)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- Iterate over `question.statementRows`. Within each row, iterate over `question.scaleColumns`.
|
|
16
|
-
- Render the correct icon/badge in the `<td>` based on `question.displayStyle`.
|
|
14
|
+
Every grid row MUST show `statementRows[].statementText` in a **180px** left column.
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **`hasNotApplicableOption`**: If true, the last column might be `null`. Render a distinct UI for the N/A column.
|
|
23
|
-
- **`reverseScaleOrder`**: Reverse column order before render when true.
|
|
24
|
-
- **`gridLayout`**: `'standard'`, `'carousel'`, or `'dropdown'` — see ui-spec decision tree.
|
|
16
|
+
Every grid MUST show either:
|
|
17
|
+
- A **column header row** from `scaleColumns[].label`, OR
|
|
18
|
+
- An **anchor label row** from `scaleAnchorLabels[]`
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
Never render a bare grid of inputs with no row/column labels.
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
1. **Row Tracking**:
|
|
31
|
-
- Every data `<tr>` MUST have `hover:bg-gray-50 transition-colors duration-150`.
|
|
22
|
+
## Configuration Matrix
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
- **`displayStyle`**: emoji | star | numbered | graphics | standard — each has distinct UI (see ui-spec)
|
|
25
|
+
- **`hasNotApplicableOption`**: 56px N/A column
|
|
26
|
+
- **`reverseScaleOrder`**: flip column index before icon/badge lookup
|
|
27
|
+
- **`gridLayout`**: standard | carousel | dropdown
|
|
36
28
|
|
|
37
29
|
## SDK Integration Checklist (agent)
|
|
38
30
|
|
|
31
|
+
- [ ] Copied portable template + `surveyUiIcons.tsx` + `surveyUiScaleUtils.ts`
|
|
39
32
|
- [ ] Type literals: `QUESTION_TYPE.CSAT_MATRIX` and `QUESTION_TYPE.RATING_MATRIX`
|
|
40
|
-
- [ ] Answer
|
|
41
|
-
- [ ]
|
|
42
|
-
- [ ]
|
|
43
|
-
- [ ] Emoji/star UI: client-side `react-icons` — not SDK exports
|
|
33
|
+
- [ ] Answer: `MatrixRowAnswers` via `matrixColumnStoredValue(col)`
|
|
34
|
+
- [ ] Visibility: `getVisibleMatrixItemsForAnswers(..., customFieldValues?)`
|
|
35
|
+
- [ ] Row labels + column/anchor labels visible
|
|
44
36
|
- [ ] Matrix row: [`question-type-sdk-matrix.md`](../00-integration/question-type-sdk-matrix.md)
|
|
@@ -1,55 +1,36 @@
|
|
|
1
1
|
# Likert Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/07-matrix-cfm.md`
|
|
4
|
-
> **
|
|
4
|
+
> **Portable template:** copy [`templates/LikertMatrixScale.tsx`](../templates/LikertMatrixScale.tsx) + [`MatrixDropdown.tsx`](../templates/MatrixDropdown.tsx)
|
|
5
5
|
> **Target Component**: `LikertMatrixScale`
|
|
6
6
|
> **Handles**: `type: 'CFM_MATRIX'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
|
-
Render a grid with text-label columns. However, this component must be highly dynamic to support complex layouts, transpositions, and long-form matrix readability.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
- `selectedValue`: `MatrixRowAnswers` — store column **`id`** via `matrixColumnStoredValue(col)`
|
|
13
|
-
- **Mutation**: When a cell is selected for `row.id`, dispatch `onSelect({ ...selectedValue, [row.id]: columnId })`
|
|
10
|
+
Render CFM matrix grids with support for bipolar layout, transposed tables, multi-select checkboxes, and anchor labels.
|
|
14
11
|
|
|
15
|
-
##
|
|
12
|
+
## Statement & labels (CRITICAL)
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
| Mode | Left label | Right label | Column headers |
|
|
15
|
+
|------|------------|-------------|----------------|
|
|
16
|
+
| Likert | `statementText` (180px) | — | `scaleColumns[].label` when `showColumnHeaders` |
|
|
17
|
+
| Bipolar | `statementText` (25%) | `oppositeStatementText` (25%) | anchor labels OR column headers |
|
|
18
|
+
| Transposed | `scaleColumns[].label` as row | — | `statementRows[].statementText` as columns |
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
If `true`, the standard Row-by-Column matrix is inverted:
|
|
21
|
-
- `question.columns` become the row labels on the left.
|
|
22
|
-
- `question.rows` become the top column headers.
|
|
23
|
-
- **Architectural Requirement**: Your `<tbody>` map must iterate over `question.columns` first, and render `<td>` cells mapped from `question.rows`.
|
|
24
|
-
- Ensure `onSelect` still correctly pairs the original `row.id` with the original `col.value`.
|
|
20
|
+
Use `dangerouslySetInnerHTML` for all statement/label HTML.
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
If `true`, the `<thead>` containing column labels must be re-injected periodically to prevent users from losing track of columns on long matrices.
|
|
28
|
-
- **Implementation Strategy**: In your row `map()`, if `index > 0 && index % 5 === 0`, return a React Fragment containing a cloned `<tr>` header row followed by the actual data `<tr>`.
|
|
22
|
+
## Configuration Matrix
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
## UI/UX & Styling Constraints
|
|
35
|
-
|
|
36
|
-
To match enterprise standards, implement the following UI strictly:
|
|
37
|
-
|
|
38
|
-
1. **Row Tracking (Hover Effects)**:
|
|
39
|
-
- Every data `<tr>` MUST have `hover:bg-gray-50 transition-colors duration-150 group`. This is mandatory for UX on wide matrices.
|
|
40
|
-
2. **Radio Buttons**:
|
|
41
|
-
- Use `accent-[#e20074] w-5 h-5 cursor-pointer` or build custom CSS circles that fill with the brand color.
|
|
42
|
-
- Do NOT use standard unstyled blue browser radios.
|
|
43
|
-
3. **Tooltips (Accessibility)**:
|
|
44
|
-
- Every input MUST have `title={col.label}` so the user sees the semantic meaning when hovering the radio button.
|
|
45
|
-
4. **Header Typography**:
|
|
46
|
-
- `th` tags should be `text-xs font-semibold text-gray-500 uppercase tracking-wider p-4 border-b`.
|
|
24
|
+
- **`transposeTable`**: swaps which array becomes grid rows vs columns
|
|
25
|
+
- **`selectionMode: 'multiple'`**: checkboxes; answers are `string[]` per row
|
|
26
|
+
- **`statementLayout: 'bipolar'`**: 25% | scale | 25% layout
|
|
27
|
+
- **`scaleAnchorLabels`**: positioned above scale when bipolar or likert
|
|
47
28
|
|
|
48
29
|
## SDK Integration Checklist (agent)
|
|
49
30
|
|
|
31
|
+
- [ ] Copied portable template
|
|
50
32
|
- [ ] Type literal: `QUESTION_TYPE.CFM_MATRIX`
|
|
51
|
-
- [ ] Answer
|
|
52
|
-
- [ ]
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ] Matrix row: [`question-type-sdk-matrix.md#cfm_matrix`](../00-integration/question-type-sdk-matrix.md#cfm_matrix)
|
|
33
|
+
- [ ] Answer: `MatrixRowAnswers` via `matrixColumnStoredValue(col)`
|
|
34
|
+
- [ ] Visibility: `getVisibleMatrixItemsForAnswers(..., customFieldValues?)`
|
|
35
|
+
- [ ] All row labels and header labels visible per ui-spec
|
|
36
|
+
- [ ] Matrix row: [`question-type-sdk-matrix.md`](../00-integration/question-type-sdk-matrix.md)
|
|
@@ -1,56 +1,71 @@
|
|
|
1
1
|
# Slider Matrix Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/09-slider-matrix.md`
|
|
4
|
-
> **
|
|
4
|
+
> **Portable template:** copy [`templates/SliderMatrixScale.tsx`](../templates/SliderMatrixScale.tsx) + [`CustomSliderTrack.tsx`](../templates/CustomSliderTrack.tsx) + [`surveyUiScaleUtils.ts`](../templates/surveyUiScaleUtils.ts)
|
|
5
5
|
> **Target Component**: `SliderMatrixScale`
|
|
6
6
|
> **Handles**: `type: 'SLIDER_MATRIX'`
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
|
-
Render an independent range slider for every row in the matrix. Sliders must be highly visible, utilizing a "thick track" UI, dynamic fill based on the current value, and exact value badges.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
- `selectedValue`: Expects `MatrixRowAnswers` shape.
|
|
13
|
-
- `row.min`, `row.max`, and `row.step` must dictate the boundaries of the slider logic.
|
|
14
|
-
- **Mutation**: `onChange` of the input must parse the value as a Number before dispatching `onSelect`.
|
|
10
|
+
Render one `CustomSliderTrack` per `statementRows[]` entry, with a shared header row of colored tick badges and optional anchor labels.
|
|
15
11
|
|
|
16
|
-
##
|
|
12
|
+
## CRITICAL — Do NOT improvise
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
- **Do NOT** build custom “thick slider” / invisible overlay range inputs
|
|
15
|
+
- **Do NOT** use legacy field names (`rows`, `labels`, `tickValues`, `enableNotApplicable`, `sliderType`)
|
|
16
|
+
- **Copy the portable template first**, then customize branding colors only
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
For every slider row, build a relative container:
|
|
22
|
-
1. **Base Track**: `absolute w-full h-2 bg-gray-200 rounded-full`.
|
|
23
|
-
2. **Dynamic Fill Track**: `absolute h-2 bg-[#e20074] rounded-l-full`.
|
|
24
|
-
- Calculate width dynamically: `((value - min) / (max - min)) * 100 + '%'`.
|
|
25
|
-
3. **The Thumb**: `absolute w-5 h-5 bg-[#e20074] border-2 border-white rounded-full shadow-md pointer-events-none transition-transform group-hover:scale-125`.
|
|
26
|
-
- Position it dynamically: `left: calc(${percentage}% - 10px)`.
|
|
27
|
-
4. **The Input Control**: Layer a native input on top, completely invisible but clickable:
|
|
28
|
-
- `<input type="range" className="absolute w-full h-full opacity-0 cursor-pointer z-10" />`
|
|
18
|
+
## SDK Fields (authoritative)
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
From `fetchSurvey/types/sliderMatrix.ts`:
|
|
31
21
|
|
|
32
|
-
|
|
22
|
+
| Field | UI role |
|
|
23
|
+
|-------|---------|
|
|
24
|
+
| `statementRows[]` | Row labels + per-row min/max/step |
|
|
25
|
+
| `ticks.count` + `ticks.marks[]` | Header colored badges (`tickPosition`, `tickLabel`) |
|
|
26
|
+
| `scaleAnchorLabels?` | Optional text labels above tick row |
|
|
27
|
+
| `enableInputBox` | 80px numeric input + steppers |
|
|
28
|
+
| `displayValues` | Pass to `CustomSliderTrack` |
|
|
29
|
+
| `hasNotApplicableOption` | N/A checkbox column; answer value **`'N/A'`** string |
|
|
30
|
+
| `sliderStyle?: 'graphics'` | Plain text ticks + emoji thumb via `CustomSliderTrack` |
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
- Users must see the exact number they are selecting.
|
|
36
|
-
- Render a badge at the far right of the slider track: `shrink-0 w-8 text-center font-bold text-[#e20074]`.
|
|
32
|
+
## Layout (matches reference)
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
```
|
|
35
|
+
[25% spacer] [scaleAnchorLabels?] [colored tick badges 0–10] [input spacer?] [N/A header?]
|
|
36
|
+
┌─ gray row band #f3f4f6 ─────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ 25% statementText │ CustomSliderTrack │ 80px input? │ N/A checkbox? │
|
|
38
|
+
└─────────────────────────────────────────────────────────────────────────────────────┘
|
|
39
|
+
```
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
- `row.minLabel` and `row.maxLabel` must be displayed directly underneath the far-left and far-right of the slider track using flexbox `justify-between text-xs text-gray-500`.
|
|
41
|
+
## Statement text visibility
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
43
|
+
- Render `row.statementText` with `dangerouslySetInnerHTML` in the **25% left column**
|
|
44
|
+
- Skip rendering when text is empty or literal `"Question"` (placeholder from API)
|
|
45
|
+
- **Never** omit the statement column — agents that put statement text in the question title only will fail multi-row surveys
|
|
46
|
+
|
|
47
|
+
## Header tick badges
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const tickValues = ticks.marks.map((mark, index) => ({
|
|
51
|
+
value: mark.tickPosition,
|
|
52
|
+
label: mark.tickLabel,
|
|
53
|
+
color: tickColorFromIndex(index, ticks.count),
|
|
54
|
+
}));
|
|
55
|
+
const htmlStep = ticks.count > 1 ? (max - min) / (ticks.count - 1) : row.step;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## N/A behavior
|
|
59
|
+
|
|
60
|
+
- Checked → store `'N/A'` string (not `null`)
|
|
61
|
+
- Unchecked → reset to `row.defaultValue ?? row.min`
|
|
48
62
|
|
|
49
63
|
## SDK Integration Checklist (agent)
|
|
50
64
|
|
|
65
|
+
- [ ] Copied `SliderMatrixScale.tsx`, `CustomSliderTrack.tsx`, `surveyUiScaleUtils.ts`
|
|
51
66
|
- [ ] Type literal: `QUESTION_TYPE.SLIDER_MATRIX`
|
|
52
|
-
- [ ] Answer
|
|
67
|
+
- [ ] Answer: `MatrixRowAnswers` — `{ [rowId]: number | 'N/A' }`
|
|
53
68
|
- [ ] onAction CHANGE: `{ questionId, answerValue: MatrixRowAnswers }`
|
|
54
|
-
- [ ] Visibility
|
|
55
|
-
- [ ]
|
|
69
|
+
- [ ] Visibility: `getVisibleStatementRowsForAnswers(..., customFieldValues?)`
|
|
70
|
+
- [ ] Header row renders even for single-row questions
|
|
56
71
|
- [ ] Matrix row: [`question-type-sdk-matrix.md#slider_matrix`](../00-integration/question-type-sdk-matrix.md#slider_matrix)
|
|
@@ -1,67 +1,30 @@
|
|
|
1
1
|
# File Upload Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/10-file-upload.md`
|
|
4
|
-
> **
|
|
5
|
-
> **
|
|
6
|
-
> **
|
|
4
|
+
> **Upload API:** [`00-integration/file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
5
|
+
> **Portable template:** implement per ui-spec (upload wiring is project-specific)
|
|
6
|
+
> **Target Component:** `FileUploadScale`
|
|
7
|
+
> **Handles:** `type: 'FILE_UPLOAD'`
|
|
7
8
|
|
|
8
9
|
## Core Responsibility
|
|
9
|
-
Render a robust drag-and-drop zone and enforce complex file size, type limits, and maximum file count constraints BEFORE updating the state.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- `selectedValue`: Expects an array of native `File` objects (or serialized equivalents).
|
|
13
|
-
- **Mutation Pattern**:
|
|
14
|
-
1. The user selects files.
|
|
15
|
-
2. The component intercepts the files.
|
|
16
|
-
3. The component validates them against ALL limits.
|
|
17
|
-
4. If valid, append the new files to `selectedValue` (handling it as an array).
|
|
18
|
-
5. Call `onSelect(updatedFilesArray)`.
|
|
19
|
-
|
|
20
|
-
## Configuration Matrix (CRITICAL FOR PLANNING)
|
|
21
|
-
|
|
22
|
-
Before calling `onSelect`, your component MUST validate against these properties:
|
|
23
|
-
|
|
24
|
-
### 1. `supportedFileFormats`
|
|
25
|
-
- Extracted from `question.supportedFileFormats` (e.g. `['PDF', 'PNG', 'JPG']`).
|
|
26
|
-
- Use this to build the `accept` attribute string for the hidden input: `question.supportedFileFormats?.map(f => \`.\${f.toLowerCase()}\`).join(',')`.
|
|
27
|
-
- You MUST reject files with extensions that are not in this list.
|
|
11
|
+
Render drag-and-drop zone, validate files locally, **upload via presigned URL**, then bubble `UploadedFileAnswer[]` via `onSelect`.
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
- Prevent uploads if `currentFiles.length + newFiles.length > maxFileCount`.
|
|
31
|
-
- Provide a clear local error message indicating the max limit.
|
|
32
|
-
|
|
33
|
-
### 3. `fileSizeLimit` & `fileSizeLimitType`
|
|
34
|
-
- `fileSizeLimit` is a number representing megabytes (MB).
|
|
35
|
-
- If `fileSizeLimitType === 'PER_FILE'`, validate that EVERY incoming file's `size` (in bytes) is `<= fileSizeLimit * 1024 * 1024`.
|
|
36
|
-
- If `fileSizeLimitType === 'IN_TOTAL'`, validate that the combined sum of all existing `selectedValue` sizes plus the incoming files' sizes is `<= fileSizeLimit * 1024 * 1024`.
|
|
37
|
-
|
|
38
|
-
## UI/UX Specifications
|
|
39
|
-
|
|
40
|
-
1. **Dropzone**:
|
|
41
|
-
- Wrap the main area with `onDragEnter`, `onDragOver`, `onDragLeave`, `onDrop`.
|
|
42
|
-
- **Inactive State**: `border-2 border-dashed border-gray-300 rounded-xl bg-gray-50 p-8 text-center cursor-pointer transition-all`.
|
|
43
|
-
- **Drag/Hover State**: `border-[#e20074] bg-[#fdf2f8]` (Using the brand primary color).
|
|
44
|
-
- Display a Cloud SVG icon (use gray, switch to magenta on hover).
|
|
45
|
-
- Render `question.uploadMessage` (which can contain HTML, so use `dangerouslySetInnerHTML`).
|
|
46
|
-
- Dynamically render the limits text (e.g., "Max 3 files, 10MB per file").
|
|
13
|
+
## State Management
|
|
47
14
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
15
|
+
- `selectedValue`: `UploadedFileAnswer[] | undefined` — **not** raw `File[]` after upload completes
|
|
16
|
+
- **Mutation pattern:**
|
|
17
|
+
1. User selects files → local validation
|
|
18
|
+
2. For each file: request presigned URL → PUT to S3 → append `{ id, mediaUrl, name, mimeType, size }`
|
|
19
|
+
3. `onSelect(uploadedAnswers)` → `onAction(CHANGE)`
|
|
53
20
|
|
|
54
|
-
|
|
55
|
-
- Local validation errors (e.g. "File too large") must be displayed in a highly visible red banner (`bg-red-50 text-red-700 p-3 rounded-md`) right below the dropzone.
|
|
21
|
+
## Local validation (before upload)
|
|
56
22
|
|
|
57
|
-
|
|
58
|
-
- Include a hidden `<input type="file" multiple={question.maxFileCount > 1} accept={...} />` that is triggered via a `useRef` when the dropzone is clicked.
|
|
23
|
+
Same as ui-spec: `supportedFileFormats`, `maxFileCount`, `fileSizeLimit` + `fileSizeLimitType`.
|
|
59
24
|
|
|
60
25
|
## SDK Integration Checklist (agent)
|
|
61
26
|
|
|
62
27
|
- [ ] Type literal: `QUESTION_TYPE.FILE_UPLOAD`
|
|
63
|
-
- [ ]
|
|
64
|
-
- [ ]
|
|
65
|
-
- [ ] Visibility helper: N/A
|
|
66
|
-
- [ ] Enforce `maxFileCount` and `fileSizeLimit` client-side
|
|
28
|
+
- [ ] Final answer: `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
29
|
+
- [ ] Wire upload API per [`file-upload-aws.md`](../00-integration/file-upload-aws.md)
|
|
67
30
|
- [ ] Matrix row: [`question-type-sdk-matrix.md#file_upload`](../00-integration/question-type-sdk-matrix.md#file_upload)
|
|
@@ -1,38 +1,28 @@
|
|
|
1
1
|
# Custom Slider Track Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/shared/custom-slider-track.md`
|
|
4
|
-
> **
|
|
4
|
+
> **Portable template:** copy [`templates/CustomSliderTrack.tsx`](../templates/CustomSliderTrack.tsx) + [`surveyUiIcons.tsx`](../templates/surveyUiIcons.tsx)
|
|
5
5
|
> **Target Component**: `CustomSliderTrack`
|
|
6
|
-
> **Used By**: `SliderMatrixScale`, `CsatMatrixScale
|
|
6
|
+
> **Used By**: `SliderMatrixScale`, `CsatMatrixScale` (`displayStyle: graphics`)
|
|
7
7
|
|
|
8
8
|
## Core Responsibility
|
|
9
|
-
Native browser `<input type="range">` elements are impossible to style consistently across browsers, especially when trying to render dynamic emoji handles. This component MUST implement an invisible overlay architecture to provide total CSS control over the slider's appearance.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Provide a styled range slider with optional emoji thumb (`sliderType: 'graphics'`). Used **inside** `SliderMatrixScale` and matrix graphics rows — not as a standalone question type.
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
## CRITICAL — Copy template
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
A static `<div>` representing the entire length of the slider.
|
|
17
|
-
- CSS: `absolute w-full h-2 bg-gray-200 rounded-full`.
|
|
14
|
+
Do not reimplement from this blueprint alone. Copy `templates/CustomSliderTrack.tsx` which implements the layered track + invisible range input pattern.
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
A dynamic `<div>` representing the portion of the slider up to the current value.
|
|
21
|
-
- CSS: `absolute h-2 bg-[#e20074] rounded-l-full`.
|
|
22
|
-
- Inline Style: `style={{ width: \`\${percentage}%\` }}`.
|
|
16
|
+
## Pass tickLabels for matrix graphics
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **For Graphics Sliders (`sliderType === 'graphics'`)**: A larger white circle containing an emoji mapped to the current value using client-side react-icons. `w-10 h-10 bg-white shadow-md border flex items-center justify-center text-2xl`.
|
|
28
|
-
- Inline Style: `style={{ left: \`\${percentage}%\` }}`.
|
|
18
|
+
```typescript
|
|
19
|
+
tickLabels={scaleColumns.map(col => col.label)}
|
|
20
|
+
```
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
An invisible native input overlaid exactly on top to capture all mouse/touch events automatically without needing manual math for drag interactions.
|
|
32
|
-
- CSS: `<input type="range" className="absolute w-full h-full opacity-0 cursor-pointer z-10" />`.
|
|
33
|
-
- This ensures perfect accessibility, keyboard navigation, and mobile touch support.
|
|
22
|
+
Required for correct tooltip text on graphics sliders (RATING_MATRIX Page 11 fixture).
|
|
34
23
|
|
|
35
|
-
##
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
24
|
+
## SDK Integration Checklist
|
|
25
|
+
|
|
26
|
+
- [ ] Copied portable `CustomSliderTrack.tsx`
|
|
27
|
+
- [ ] Used by `SliderMatrixScale` or `CsatMatrixScale` — not wired directly in `Question.tsx` for SLIDER_MATRIX
|
|
28
|
+
- [ ] `hasSelectedValue`, `disabled`, `htmlStep` props wired correctly
|
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
# Theme & Logo Engineering Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
-
> **
|
|
4
|
+
> **Wizard contract:** [`wizard-preview-build-guide.md`](../00-integration/wizard-preview-build-guide.md) — `data-cfm-*` + `var(--cfm-*)`
|
|
5
|
+
> **Monorepo snapshot (do not copy):** `apps/client/src/components/Header.tsx`, `Footer.tsx`
|
|
5
6
|
> **Target Components**: `Header`, `Footer`
|
|
6
7
|
> **Handles**: Branding, Mockup Replication
|
|
7
8
|
|
|
8
9
|
## Core Responsibility
|
|
9
10
|
When building the survey layout, the client will provide a text prompt or an image mockup. You MUST dynamically build the Theme and Logo using purely CSS and Tailwind to match the requested brand.
|
|
10
11
|
|
|
12
|
+
## Sticky content chrome
|
|
13
|
+
|
|
14
|
+
On the **content** phase, render `Header` inside `SurveyStickyChrome` with `embedded` so the wrapper owns the shadow:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<Header embedded />
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use standalone `Header` (default, with shadow) on intro, end, paused, loading, and terminal pages. See `19-survey-sticky-chrome.md`.
|
|
21
|
+
|
|
11
22
|
## Logo Engineering Rules (CRITICAL)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
|
|
24
|
+
Agents must implement **one** of these wizard-compatible patterns in `Header.tsx`:
|
|
25
|
+
|
|
26
|
+
| Mode | When | Required markup |
|
|
27
|
+
|------|------|-----------------|
|
|
28
|
+
| **Image** | Brand asset in `public/` | `<img data-cfm-logo src="/exact-file.svg" />` plus hidden `<span data-cfm-logo-text>` fallback |
|
|
29
|
+
| **Text** | CSS-only logo from prompt (no image file) | `<span data-cfm-logo-text>Company</span>` in logo slot — no `data-cfm-logo` img |
|
|
30
|
+
|
|
31
|
+
- **NO EXTERNAL URLS** for logos (`https://…`).
|
|
32
|
+
- **CSS-only logos** use `data-cfm-logo-text` (and optional CSS shapes) — wizard shows text until client uploads an image.
|
|
33
|
+
- After wizard upload, saved `global.logo.fileName` is used on subsequent sessions — see `wizard-preview-build-guide.md`.
|
|
15
34
|
|
|
16
35
|
## Theme Engineering
|
|
17
36
|
- Map the primary brand color to the accent and border colors across the entire survey.
|
|
@@ -20,20 +20,28 @@ export default function ProgressBar({ progressPercentage }: ProgressBarProps) {
|
|
|
20
20
|
const width = Math.min(100, Math.max(0, progressPercentage));
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<div className="
|
|
24
|
-
<div
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
<div className="w-full">
|
|
24
|
+
<div className="h-3 w-full rounded-full bg-gray-100">
|
|
25
|
+
<div
|
|
26
|
+
className="h-3 rounded-full bg-[#e20074] transition-all duration-300 ease-out"
|
|
27
|
+
style={{ width: `${width}%` }}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
28
30
|
</div>
|
|
29
31
|
);
|
|
30
32
|
}
|
|
31
33
|
```
|
|
32
34
|
|
|
35
|
+
Vertical spacing (`pt-4` above, `pb-5` below) lives on the **`SurveyStickyChrome`** wrapper — not on `ProgressBar` itself.
|
|
36
|
+
|
|
33
37
|
## Wiring
|
|
34
38
|
|
|
39
|
+
Place inside **`SurveyStickyChrome`** on the content phase (see `19-survey-sticky-chrome.md`):
|
|
40
|
+
|
|
35
41
|
```tsx
|
|
36
|
-
<
|
|
42
|
+
<SurveyStickyChrome progressPercentage={state.progressPercentage} />
|
|
37
43
|
```
|
|
38
44
|
|
|
45
|
+
Do not duplicate `ProgressBar` inside scrollable `<main>`.
|
|
46
|
+
|
|
39
47
|
The SDK computes percentage from **questions on the active skip path**, not total survey pages. Skip logic and answer changes update the bar automatically.
|
|
@@ -1,46 +1,41 @@
|
|
|
1
1
|
# Matrix Dropdown
|
|
2
2
|
|
|
3
|
-
> **UI spec (authoritative):** `03-ui-specs/shared/matrix-dropdown.md`
|
|
4
|
-
> **
|
|
5
|
-
> **Component
|
|
6
|
-
> **Used by
|
|
3
|
+
> **UI spec (authoritative):** [`03-ui-specs/shared/matrix-dropdown.md`](../03-ui-specs/shared/matrix-dropdown.md) — implement the **custom panel** (not a native `<select>`).
|
|
4
|
+
> **Portable template:** copy [`templates/MatrixDropdown.tsx`](../templates/MatrixDropdown.tsx) when available.
|
|
5
|
+
> **Component:** `MatrixDropdown`
|
|
6
|
+
> **Used by:** `CsatMatrixScale`, `LikertMatrixScale`
|
|
7
7
|
|
|
8
8
|
## Role
|
|
9
|
-
Renders a dropdown selector for a single row in a matrix question. Used when `gridLayout === 'dropdown'` or `displayStyle === 'dropdown'`.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Renders a custom dropdown selector for a single matrix row. Used when `gridLayout === 'dropdown'` or `displayStyle` is `'dropdown'` / `'selectbox'`.
|
|
11
|
+
|
|
12
|
+
Emits stored column **`id`** values via `matrixColumnStoredValue(col)` — see [`02-reference/value-derivation.md`](../02-reference/value-derivation.md).
|
|
13
|
+
|
|
14
|
+
## Props (wiring only)
|
|
12
15
|
|
|
13
|
-
## Props Interface
|
|
14
16
|
```typescript
|
|
15
17
|
type MatrixDropdownProps = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
options: ScaleColumn[]; // visible columns for this row
|
|
19
|
+
value: string | number | null | undefined;
|
|
18
20
|
onChange: (value: string | number | null) => void;
|
|
21
|
+
multiple?: boolean;
|
|
19
22
|
placeholder?: string;
|
|
20
23
|
};
|
|
21
24
|
```
|
|
22
25
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<option value="" disabled>Select an option...</option>
|
|
40
|
-
{columns.map(col => (
|
|
41
|
-
<option key={String(col.value)} value={String(col.value)}>
|
|
42
|
-
{col.label}
|
|
43
|
-
</option>
|
|
44
|
-
))}
|
|
45
|
-
</select>
|
|
46
|
-
```
|
|
26
|
+
## Implementation
|
|
27
|
+
|
|
28
|
+
Do **not** use a native `<select>`. Follow the shared ui-spec for:
|
|
29
|
+
|
|
30
|
+
- Click-to-open panel with click-outside dismiss
|
|
31
|
+
- Single-select: one choice; multi-select: tag chips + toggle
|
|
32
|
+
- N/A mutual exclusion when `hasNotApplicableOption` applies
|
|
33
|
+
- Placeholder text: `"Select..."` (dropdown) or full-width cards (selectbox)
|
|
34
|
+
|
|
35
|
+
See [`03-ui-specs/shared/matrix-dropdown.md`](../03-ui-specs/shared/matrix-dropdown.md) for layout, states, and agent checklist.
|
|
36
|
+
|
|
37
|
+
## SDK Integration Checklist
|
|
38
|
+
|
|
39
|
+
- [ ] Copy `templates/MatrixDropdown.tsx` or implement per shared ui-spec
|
|
40
|
+
- [ ] `onChange` stores column **`id`** via `matrixColumnStoredValue(col)`
|
|
41
|
+
- [ ] Filter columns with `getVisibleMatrixItemsForAnswers` before passing to dropdown
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/13-heatmap.md`
|
|
4
4
|
> **Reference implementation:** `apps/client/src/components/HeatmapScale.tsx`
|
|
5
|
-
> **
|
|
5
|
+
> **Portable coord helper:** copy [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts)
|
|
6
6
|
> **Target Component**: `HeatmapScale`
|
|
7
7
|
> **Handles**: `type: 'HEATMAP'`
|
|
8
8
|
|
|
@@ -15,8 +15,8 @@ Render the heatmap image, capture click spots in normalized 0–1 coordinates, t
|
|
|
15
15
|
```typescript
|
|
16
16
|
type HeatmapScaleProps = {
|
|
17
17
|
question: HeatmapQuestion;
|
|
18
|
-
selectedValue?:
|
|
19
|
-
onSelect: (answer:
|
|
18
|
+
selectedValue?: HeatmapClickPoint[];
|
|
19
|
+
onSelect: (answer: HeatmapClickPoint[] | undefined) => void;
|
|
20
20
|
};
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -34,7 +34,7 @@ type HeatmapScaleProps = {
|
|
|
34
34
|
|
|
35
35
|
## Coordinate Helpers
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Copy [`templates/heatmapCoords.ts`](../templates/heatmapCoords.ts):
|
|
38
38
|
|
|
39
39
|
- `getNormalizedClickFromImage(event, imageElement)`
|
|
40
40
|
- `findSpotNearClick(clickX, clickY, spots, imageRect)`
|