@explorer02/cfm-survey-sdk 0.2.3 → 0.2.5
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 +141 -33
- package/dist/cli/index.mjs +141 -33
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -1
- package/postinstall.js +9 -5
- package/templates/AGENT.md +60 -19
- package/templates/deploy-wizard-to-s3.sh +52 -0
- package/templates/docs/00-integration/agent-execution-flow.md +293 -0
- package/templates/docs/00-integration/agent-operating-contract.md +268 -0
- package/templates/docs/00-integration/apply-ui-config.md +390 -0
- package/templates/docs/00-integration/aws-deploy.md +117 -3
- package/templates/docs/00-integration/canonical-survey-fixtures.md +94 -0
- package/templates/docs/00-integration/client-integration-guide.md +9 -7
- package/templates/docs/00-integration/client-lib-folder.md +115 -0
- package/templates/docs/00-integration/component-checklist.md +90 -62
- package/templates/docs/00-integration/constraints.md +14 -5
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +56 -0
- package/templates/docs/00-integration/mockup-ui-extraction.md +113 -0
- package/templates/docs/00-integration/placeholders-and-custom-fields-wiring.md +192 -0
- package/templates/docs/00-integration/placeholders-and-tokens.md +2 -1
- package/templates/docs/00-integration/progress.md +4 -2
- package/templates/docs/00-integration/setup.md +12 -0
- package/templates/docs/00-integration/ui-customization-wizard.md +123 -0
- package/templates/docs/00-integration/useSurveySDK.md +35 -1
- package/templates/docs/00-integration/wizard-api.md +126 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +292 -0
- package/templates/docs/00-integration/wizard-preview-build-guide.md +405 -0
- package/templates/docs/00-integration/wizard-question-type-styling.md +197 -0
- package/templates/docs/00-integration/wizard-troubleshooting.md +80 -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 -39
- package/templates/docs/01-components/07-slider-matrix-scale.md +48 -33
- 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/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/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 +18 -0
- package/templates/docs/03-ui-specs/02-radio.md +16 -1
- package/templates/docs/03-ui-specs/03-text.md +12 -1
- package/templates/docs/03-ui-specs/04-csat.md +84 -50
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +71 -48
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +48 -18
- package/templates/docs/03-ui-specs/09-slider-matrix.md +59 -32
- package/templates/docs/03-ui-specs/10-file-upload.md +11 -0
- package/templates/docs/03-ui-specs/11-text-and-media.md +4 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +54 -5
- package/templates/docs/03-ui-specs/13-heatmap.md +12 -1
- package/templates/docs/03-ui-specs/14-rank-order.md +11 -0
- package/templates/docs/03-ui-specs/README.md +4 -2
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +1 -1
- package/templates/docs/MANIFEST.json +180 -7
- package/templates/docs/index.md +114 -117
- package/templates/docs/templates/CsatMatrixScale.tsx +637 -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/Question.tsx +3 -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/deploy-to-aws.sh +74 -0
- package/templates/docs/templates/implementation_plan.md +64 -10
- package/templates/docs/templates/labelStyles.ts +33 -0
- package/templates/docs/templates/selectionStyles.ts +100 -0
- package/templates/docs/templates/surveyUiIcons.tsx +11 -0
- package/templates/docs/templates/surveyUiScaleUtils.ts +51 -0
- package/templates/docs/templates/verify-agent-build.sh +119 -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-AoZdAEPZ.js +1 -0
- package/templates/wizard-dist/assets/TypePanel-39FnbtvG.js +1 -0
- package/templates/wizard-dist/assets/index-BhWM50Yu.css +1 -0
- package/templates/wizard-dist/assets/index-CWKdsJOp.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,7 +1,8 @@
|
|
|
1
1
|
# Survey Page Orchestrator Architectural Blueprint
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
-
> **
|
|
4
|
+
> **Portable template:** implement from `docs/templates/` + [`agent-execution-flow.md`](../00-integration/agent-execution-flow.md) Phase 5
|
|
5
|
+
> **Monorepo snapshot (do not copy):** `apps/client/src/components/SurveyPage.tsx`
|
|
5
6
|
> **Target Component**: `SurveyPage`
|
|
6
7
|
> **Role**: Wires `useSurveySDK` to the UI — loading/error states, phase routing, pagination, submission.
|
|
7
8
|
|
|
@@ -13,9 +14,9 @@ Act as the **phase state machine** for the survey. Never render questions direct
|
|
|
13
14
|
|
|
14
15
|
```tsx
|
|
15
16
|
import type { CustomFieldValueMap } from '@explorer02/cfm-survey-sdk';
|
|
17
|
+
import { CUSTOM_FIELD_VALUES } from '@/lib/customFieldValues';
|
|
16
18
|
|
|
17
19
|
const SURVEY_PLACEHOLDERS = { FIRST_NAME: 'Customer' }; // Must be outside component!
|
|
18
|
-
const CUSTOM_FIELD_VALUES: CustomFieldValueMap = {}; // Populate when survey uses custom-field logic
|
|
19
20
|
|
|
20
21
|
export default function SurveyPage() {
|
|
21
22
|
const [selectedLanguage, setSelectedLanguage] = useState<string | undefined>();
|
|
@@ -46,6 +47,14 @@ export default function SurveyPage() {
|
|
|
46
47
|
|
|
47
48
|
See [`client-integration-guide.md`](../00-integration/client-integration-guide.md) for lifecycle effects (`SCROLL`, `RECORD_QUESTION_VIEW`).
|
|
48
49
|
|
|
50
|
+
When fetched survey contains any `_c_…` custom field id in logic rules:
|
|
51
|
+
|
|
52
|
+
- [ ] Copy [`templates/customFieldValues.ts`](../templates/customFieldValues.ts) → `src/lib/customFieldValues.ts`
|
|
53
|
+
- [ ] Populate map from CRM/transaction payload
|
|
54
|
+
- [ ] Import `CUSTOM_FIELD_VALUES` in `SurveyPage` (do not define inline empty `{}`)
|
|
55
|
+
|
|
56
|
+
See [`client-lib-folder.md`](../00-integration/client-lib-folder.md) and [`custom-field-logic-and-navigation.md`](../00-integration/custom-field-logic-and-navigation.md).
|
|
57
|
+
|
|
49
58
|
## Render Order (MANDATORY)
|
|
50
59
|
|
|
51
60
|
Handle in this order:
|
|
@@ -73,12 +82,13 @@ See `14-intro-page.md`, `15-end-page.md`, `16-paused-page.md`.
|
|
|
73
82
|
|
|
74
83
|
### 3. Active content (`phase.kind === 'content'`)
|
|
75
84
|
|
|
76
|
-
1. `Header`
|
|
77
|
-
2. `
|
|
78
|
-
3.
|
|
79
|
-
4.
|
|
80
|
-
5.
|
|
81
|
-
|
|
85
|
+
1. `SurveyStickyChrome` — sticky `Header` + `ProgressBar` (`state.progressPercentage`); see `19-survey-sticky-chrome.md` and `00-integration/progress.md`
|
|
86
|
+
2. `main`: `LanguageSelector`
|
|
87
|
+
3. Question map (below)
|
|
88
|
+
4. Navigation buttons (below)
|
|
89
|
+
5. `Footer`
|
|
90
|
+
|
|
91
|
+
Use standalone `Header` (no sticky chrome) on intro, end, paused, loading, and terminal phases.
|
|
82
92
|
|
|
83
93
|
## The Question Map
|
|
84
94
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **UI spec (authoritative):** `03-ui-specs/00-question-shell.md`
|
|
4
4
|
> **Canonical template:** [`templates/Question.tsx`](../templates/Question.tsx) — copy verbatim, then create imported components
|
|
5
|
-
> **Monorepo
|
|
5
|
+
> **Monorepo snapshot (illustrative only):** `apps/client/src/components/Question.tsx`
|
|
6
6
|
> **Target Component**: `Question`
|
|
7
7
|
|
|
8
8
|
## Start Here (CRITICAL)
|
|
@@ -77,7 +77,7 @@ Store matrix cell values as column **`id`** via `matrixColumnStoredValue(col)`
|
|
|
77
77
|
### MCQ (`QUESTION_TYPE.MCQ`)
|
|
78
78
|
|
|
79
79
|
- [ ] Filter options with `getVisibleMcqOptionsForAnswers` before render
|
|
80
|
-
- [ ] Store `option.id`;
|
|
80
|
+
- [ ] Store `option.id`; use `selectionMode` (`single` | `multiple`) — not `isMultiSelect`
|
|
81
81
|
- [ ] onAction CHANGE: `{ questionId, answerValue: optionId | optionId[] }`
|
|
82
82
|
|
|
83
83
|
### TEXTFIELD (`QUESTION_TYPE.TEXTFIELD`)
|
|
@@ -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,56 +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>`.
|
|
29
|
-
- **Note:** API/config field is `repeatColumnHeaders` (not `repeatScale`).
|
|
22
|
+
## Configuration Matrix
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
## UI/UX & Styling Constraints
|
|
36
|
-
|
|
37
|
-
To match enterprise standards, implement the following UI strictly:
|
|
38
|
-
|
|
39
|
-
1. **Row Tracking (Hover Effects)**:
|
|
40
|
-
- Every data `<tr>` MUST have `hover:bg-gray-50 transition-colors duration-150 group`. This is mandatory for UX on wide matrices.
|
|
41
|
-
2. **Radio Buttons**:
|
|
42
|
-
- Use `accent-[#e20074] w-5 h-5 cursor-pointer` or build custom CSS circles that fill with the brand color.
|
|
43
|
-
- Do NOT use standard unstyled blue browser radios.
|
|
44
|
-
3. **Tooltips (Accessibility)**:
|
|
45
|
-
- Every input MUST have `title={col.label}` so the user sees the semantic meaning when hovering the radio button.
|
|
46
|
-
4. **Header Typography**:
|
|
47
|
-
- `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
|
|
48
28
|
|
|
49
29
|
## SDK Integration Checklist (agent)
|
|
50
30
|
|
|
31
|
+
- [ ] Copied portable template
|
|
51
32
|
- [ ] Type literal: `QUESTION_TYPE.CFM_MATRIX`
|
|
52
|
-
- [ ] Answer
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ]
|
|
56
|
-
- [ ] 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,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" />` for AWS deploy (`assetPrefix: './'`). Use `/exact-file.svg` only for local dev. Include 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://…`, wizard `/assets/` URLs, or `data:image/…`).
|
|
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, set `src="./{global.logo.fileName}"` from saved config — see [`aws-deploy.md`](../00-integration/aws-deploy.md) § Logos & local assets.
|
|
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.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Survey Sticky Chrome
|
|
2
|
+
|
|
3
|
+
> **UI spec (authoritative):** `03-ui-specs/12-survey-chrome.md`
|
|
4
|
+
> **Reference implementation:** `apps/client/src/components/SurveyStickyChrome.tsx`
|
|
5
|
+
> **Template:** [`templates/SurveyStickyChrome.tsx`](../templates/SurveyStickyChrome.tsx)
|
|
6
|
+
> **Component**: `SurveyStickyChrome`
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Wrap **Header + ProgressBar** in a single `sticky top-0` container so the progress bar stays visible when the respondent scrolls to answer questions lower on the page (especially the last question on a multi-question page).
|
|
11
|
+
|
|
12
|
+
## Props
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
type SurveyStickyChromeProps = {
|
|
16
|
+
progressPercentage: number; // from state.progressPercentage
|
|
17
|
+
};
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Implementation
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
export default function SurveyStickyChrome({ progressPercentage }: SurveyStickyChromeProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="sticky top-0 z-30 bg-white shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
|
|
26
|
+
<Header embedded />
|
|
27
|
+
<div className="mx-auto w-full max-w-4xl px-4 pb-5 pt-4 sm:px-6 lg:px-8">
|
|
28
|
+
<ProgressBar progressPercentage={progressPercentage} />
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Spacing (logo → progress bar)
|
|
36
|
+
|
|
37
|
+
Keep a visible gap between the logo block and the progress track — do **not** flush them together.
|
|
38
|
+
|
|
39
|
+
| Token | Tailwind | Purpose |
|
|
40
|
+
|-------|----------|---------|
|
|
41
|
+
| Top inset | `pt-4` | 16px gap below logo / header row |
|
|
42
|
+
| Bottom inset | `pb-5` | 20px gap above scrollable question content |
|
|
43
|
+
| Horizontal | `px-4 sm:px-6 lg:px-8` | Align track with `main` content column (`max-w-4xl`) |
|
|
44
|
+
|
|
45
|
+
## Header `embedded` prop
|
|
46
|
+
|
|
47
|
+
When `Header` is inside sticky chrome, pass `embedded` to avoid a double shadow:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<header className={`relative h-[120px] w-full bg-white ${embedded ? '' : 'z-10 shadow-[0_2px_8px_rgba(0,0,0,0.06)]'}`}>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use the standalone `Header` (no `embedded`) on intro, end, loading, and terminal pages.
|
|
54
|
+
|
|
55
|
+
## SurveyPage wiring (content phase only)
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<div className="flex min-h-screen flex-col bg-white">
|
|
59
|
+
<SurveyStickyChrome progressPercentage={state.progressPercentage} />
|
|
60
|
+
|
|
61
|
+
<main className="mx-auto w-full max-w-4xl flex-1 px-4 py-10 sm:px-6 lg:px-8">
|
|
62
|
+
<LanguageSelector ... />
|
|
63
|
+
{/* questions + nav */}
|
|
64
|
+
</main>
|
|
65
|
+
|
|
66
|
+
<Footer />
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Do **not** render a separate `ProgressBar` inside `<main>` — it belongs in sticky chrome only.
|
|
71
|
+
|
|
72
|
+
## Agent checklist
|
|
73
|
+
|
|
74
|
+
- [ ] `SurveyStickyChrome` (or equivalent sticky wrapper) on `phase.kind === 'content'`
|
|
75
|
+
- [ ] `ProgressBar` bound to `state.progressPercentage`
|
|
76
|
+
- [ ] `sticky top-0 z-30 bg-white` on wrapper; single shadow on wrapper, not Header
|
|
77
|
+
- [ ] `pt-4` gap between logo and progress track; `pb-5` below track
|
|
78
|
+
- [ ] Progress track aligned to same `max-w-4xl` as question content
|
|
79
|
+
- [ ] Standalone `Header` on intro / end / loading / terminal branches
|
|
@@ -15,8 +15,7 @@ SurveyPage (phase router)
|
|
|
15
15
|
├── IntroPage / PausedPage / EndPage (special phases)
|
|
16
16
|
│
|
|
17
17
|
└── SurveyContent (phase.kind === 'content')
|
|
18
|
-
├── Header
|
|
19
|
-
├── ProgressBar
|
|
18
|
+
├── SurveyStickyChrome (sticky Header + ProgressBar)
|
|
20
19
|
├── LanguageSelector
|
|
21
20
|
├── Question × N (dispatcher)
|
|
22
21
|
├── Back / Next|Submit nav
|
|
@@ -62,6 +61,7 @@ Shared: `CustomSliderTrack`, `MatrixDropdown`
|
|
|
62
61
|
| `09-custom-slider-track.md` | `CustomSliderTrack` |
|
|
63
62
|
| `10-header-footer.md` | `Header` & `Footer` |
|
|
64
63
|
| `11-progress-bar.md` | `ProgressBar` |
|
|
64
|
+
| `19-survey-sticky-chrome.md` | `SurveyStickyChrome` (sticky Header + ProgressBar) |
|
|
65
65
|
| `12-language-selector.md` | `LanguageSelector` |
|
|
66
66
|
| `13-matrix-dropdown.md` | `MatrixDropdown` |
|
|
67
67
|
| `14-intro-page.md` | `IntroPage` |
|