@explorer02/cfm-survey-sdk 0.1.6 → 0.1.8

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.
@@ -1,11 +1,14 @@
1
- # Survey Page Orchestrator
1
+ # Survey Page Orchestrator Architectural Blueprint
2
2
 
3
- > **Source**: `apps/client/src/components/SurveyPage.tsx`
4
- > **Role**: The main entry point that wires the UI to the `useSurveySDK` hook.
3
+ > **Target Component**: `SurveyPage`
4
+ > **Role**: The main entry point that wires the UI to the `useSurveySDK` hook, manages loading/error states, orchestrates pagination, and controls submission.
5
5
 
6
- ## Setup & Wiring
6
+ ## Core Responsibility
7
+ This component MUST act as the state machine for the entire survey flow. It should never render questions directly—it delegates that to `Question.tsx`.
7
8
 
8
- The component must pass the `options` to `useSurveySDK`, including the **instanceId** provided by the client prompt.
9
+ ## Setup & Wiring (CRITICAL)
10
+
11
+ The component MUST invoke `useSurveySDK`, passing the **instanceId** from the client prompt.
9
12
 
10
13
  ```tsx
11
14
  const SURVEY_PLACEHOLDERS = { FIRST_NAME: 'Customer' }; // Must be outside component!
@@ -24,52 +27,40 @@ export default function SurveyPage() {
24
27
 
25
28
  const survey = surveyQueryResults.data;
26
29
 
27
- // ... render states
30
+ // ... state machine rendering
28
31
  }
29
32
  ```
30
33
 
31
- ## The 5 Render States
34
+ ## The 5 State Machine Renders (MANDATORY)
32
35
 
33
- A complete `SurveyPage` must handle 5 distinct rendering states in this exact order:
36
+ You MUST structure your render logic to strictly handle these 5 states in order to avoid UI crashes when data is not yet available.
34
37
 
35
38
  ### 1. Loading State
36
- ```tsx
37
- if (surveyQueryResults.isLoading) {
38
- return <LoadingView /> // Header, Footer, and a "Loading..." message
39
- }
40
- ```
39
+ If `surveyQueryResults.isLoading` is true, render a Skeleton or Spinner.
40
+ *Include your CSS-only Header and Footer around the spinner.*
41
41
 
42
42
  ### 2. Error State
43
- ```tsx
44
- if (surveyQueryResults.error) {
45
- return <ErrorView error={surveyQueryResults.error.message} />
46
- }
47
- ```
43
+ If `surveyQueryResults.error` is true, render a red Error banner containing `surveyQueryResults.error.message`.
48
44
 
49
45
  ### 3. Empty State
50
- ```tsx
51
- if (!survey) {
52
- return <EmptyView /> // Header, Footer, and a "No survey data" message
53
- }
54
- ```
46
+ If `!survey`, render a generic "Survey not found" message.
55
47
 
56
48
  ### 4. Submitted State
57
- ```tsx
58
- if (submitSurveyResults.data) {
59
- return <SubmitSummaryView /> // Header, Footer, and a "Thank you" message
60
- }
61
- ```
49
+ If `submitSurveyResults.data` exists, the survey is complete.
50
+ *Render the Thank You Screen (e.g., "Thank you for your feedback!"). Do not render the question list.*
62
51
 
63
52
  ### 5. Active Survey State
64
- The main render path. Must include:
65
- 1. `Header`
66
- 2. `ProgressBar` (passing `state.progressPercentage`)
67
- 3. `LanguageSelector` (passing `survey.languages`, `survey.language`, `setSelectedLanguage`)
68
- 4. The question list mapping over `state.currentQuestions`
69
- 5. Back / Next buttons
53
+ If the above 4 conditions are met, render the active survey page:
54
+ 1. `Header` (Logo)
55
+ 2. `ProgressBar` (Pass `state.progressPercentage`)
56
+ 3. `LanguageSelector` (Pass `survey.languages`, `survey.language`, `setSelectedLanguage`)
57
+ 4. **The Question Map** (See below)
58
+ 5. **Navigation Buttons** (See below)
70
59
  6. `Footer`
71
60
 
72
- ## Question Mapping Pattern
61
+ ## The Question Map
62
+
63
+ You MUST iterate over `state.currentQuestions` and dispatch to `Question.tsx`.
73
64
 
74
65
  ```tsx
75
66
  <div className="space-y-12">
@@ -88,26 +79,13 @@ The main render path. Must include:
88
79
  </div>
89
80
  ```
90
81
 
91
- ## Navigation Buttons Pattern
82
+ ## Navigation Buttons Logic
92
83
 
93
- ```tsx
94
- {/* Back Button (hide on first page) */}
95
- {state.currentPageIndex > 0 && (
96
- <button onClick={() => onAction({ type: 'PREVIOUS' })}>
97
- {survey.language?.startsWith('de') ? 'Zurück' : 'Back'}
98
- </button>
99
- )}
100
-
101
- {/* Next/Submit Button */}
102
- <button
103
- onClick={() => onAction({ type: 'NEXT' })}
104
- disabled={submitSurveyResults.isLoading}
105
- >
106
- {submitSurveyResults.isLoading
107
- ? (survey.language?.startsWith('de') ? 'Wird gesendet...' : 'Submitting...')
108
- : state.currentPageIndex < survey.pages.length - 1
109
- ? (survey.language?.startsWith('de') ? 'Weiter' : 'Next')
110
- : (survey.language?.startsWith('de') ? 'Absenden' : 'Submit')
111
- }
112
- </button>
113
- ```
84
+ You MUST conditionally render "Back" and "Next/Submit" buttons.
85
+
86
+ - **Back Button**: Hide if `state.currentPageIndex === 0`.
87
+ - `onClick={() => onAction({ type: 'PREVIOUS' })}`
88
+ - **Next/Submit Button**:
89
+ - `disabled={submitSurveyResults.isLoading}`
90
+ - If `state.currentPageIndex < survey.pages.length - 1`, label is "Next", action is `type: 'NEXT'`.
91
+ - If on the last page, label is "Submit", action is `type: 'NEXT'` (the SDK intercepts NEXT on the last page to trigger submission automatically!).
@@ -1,50 +1,98 @@
1
- # Question Dispatcher
1
+ # Question Dispatcher Architectural Blueprint
2
2
 
3
- > **Source**: `apps/client/src/components/Question.tsx`
4
- > **Role**: Wrapper card component that routes to the correct scale component based on question type.
3
+ > **Target Component**: `Question`
4
+ > **Role**: Wrapper card component that routes to the correct scale component based on `question.type`.
5
5
 
6
- ## Props Interface
6
+ ## Core Responsibility
7
+ Render a visually separated container (card) for each question. Handle rich text parsing, required asterisks, description text, and error validation messages. Most importantly, it MUST act as an exhaustive switch statement dispatching to ALL supported question types.
7
8
 
8
- ```typescript
9
- type QuestionProps = {
10
- question: SurveyQuestion;
11
- selectedValue?: AnswerValue;
12
- validationError?: string;
13
- onSelect: (value: AnswerValue) => void;
14
- };
15
- ```
9
+ ## Exhaustive Dispatcher Logic (CRITICAL)
10
+
11
+ The agent MUST handle EVERY SINGLE ONE of these `question.type` values. Failure to handle them will result in blank UI or broken surveys.
12
+
13
+ 1. **`rating`**: Renders `RatingScale`. This covers NPS, Stars, Emoji, and generic numeric scales.
14
+ 2. **`csat`**: Renders `CsatScale`. This covers CSAT specific faces/icons.
15
+ 3. **`radio`**: Renders inline MCQ radio buttons. (Can be implemented inside this file or separately).
16
+ 4. **`text`**: Renders an inline `<textarea>` or `<input>`.
17
+ 5. **`matrix`**:
18
+ - If `subType === 'CFM_MATRIX'`, render `LikertMatrixScale`
19
+ - If `subType === 'CSAT_MATRIX'` or `'RATING_MATRIX'`, render `CsatMatrixScale`
20
+ 6. **`slider_matrix`**: Renders `SliderMatrixScale`
21
+ 7. **`file_upload`**: Renders `FileUploadScale`
22
+ 8. **`text_and_media`**: Renders inline `<video>` or `<img>` based on `question.mediaUrl`.
23
+ - *Note: `text_and_media` questions may NOT have user input. Do not render a scale for them.*
24
+
25
+ ## Container UI Structure
26
+
27
+ Every question MUST be wrapped in a standard HTML `<section>` to visually separate it.
28
+
29
+ 1. **Wrapper styling**:
30
+ - Border: `border border-gray-200`
31
+ - Radius: `rounded-xl`
32
+ - Background: `bg-white`
33
+ - Padding: `p-6 md:p-8`
34
+ - Shadow & Hover: `shadow-sm transition-all hover:shadow-md`
35
+ - **Crucial**: ID must be set to `question.id` (`id={question.id}`) so scroll-to-error works!
36
+
37
+ 2. **Heading (`question.text`)**:
38
+ - MUST use `dangerouslySetInnerHTML={{ __html: question.text }}` because text from Sprinklr contains rich HTML (bold, links, italics).
39
+ - If `question.required === true`, append a red asterisk `*`.
40
+
41
+ 3. **Description (`question.description`)**:
42
+ - Render conditionally if `question.description` exists.
43
+ - MUST use `dangerouslySetInnerHTML`.
44
+ - Style with slightly smaller text, greyish color (e.g., `text-sm text-gray-600 mt-2`).
45
+
46
+ 4. **Validation Error Banner**:
47
+ - If `validationError` prop is passed, render a highly visible banner below the scale.
48
+ - Example: Red border-left, red background, bold text.
16
49
 
17
- ## Structure & Styling Rules (CRITICAL)
50
+ ## Hover Tooltips & Micro-interactions
51
+ - Focus states on text inputs MUST use the brand primary color (e.g., magenta `#e20074` for Telekom).
52
+ - Radio button wrappers should have a subtle background hover effect (e.g., `hover:bg-pink-50`).
18
53
 
19
- Every question is wrapped in a standard HTML section. It **MUST** have a border around the entire question block to separate them visually, and it **MUST** handle rich text (HTML) provided by the SDK safely.
54
+ ## Example Architectural Skeleton
20
55
 
21
56
  ```tsx
22
- <section
23
- id={question.id}
24
- className="space-y-6 rounded-xl border border-gray-200 bg-white p-6 md:p-8 shadow-sm transition-all hover:shadow-md"
25
- >
26
- {/* 1. Question Text (Rich Text with required asterisk) */}
57
+ <section id={question.id} className="space-y-6 rounded-xl border border-gray-200 bg-white p-6 md:p-8 shadow-sm transition-all hover:shadow-md">
58
+
59
+ {/* HEADING */}
27
60
  <h2 className="text-lg md:text-xl font-semibold leading-relaxed text-gray-900 flex items-start gap-1">
28
- {/* dangerouslySetInnerHTML handles strong, em, and link tags from the SDK */}
29
61
  <span dangerouslySetInnerHTML={{ __html: question.text }} className="prose prose-sm max-w-none" />
30
62
  {question.required && <span className="text-[#e20074] shrink-0">*</span>}
31
63
  </h2>
32
64
 
33
- {/* 2. Question Description (Rich Text) */}
65
+ {/* DESCRIPTION */}
34
66
  {question.description && (
35
- <div
36
- className="text-sm text-gray-600 mt-2 leading-relaxed prose prose-sm max-w-none"
37
- dangerouslySetInnerHTML={{ __html: question.description }}
38
- />
67
+ <div className="text-sm text-gray-600 mt-2 leading-relaxed prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: question.description }} />
39
68
  )}
40
69
 
41
- {/* 3. The Scale Component (Dispatcher switch statement) */}
70
+ {/* DISPATCHER */}
42
71
  <div className="pt-4">
43
72
  {question.type === 'rating' && <RatingScale ... />}
44
- {/* ... other types ... */}
73
+ {question.type === 'csat' && <CsatScale ... />}
74
+ {question.type === 'slider_matrix' && <SliderMatrixScale ... />}
75
+ {question.type === 'file_upload' && <FileUploadScale ... />}
76
+ {question.type === 'matrix' && question.subType === 'CFM_MATRIX' && <LikertMatrixScale ... />}
77
+ {question.type === 'matrix' && (question.subType === 'CSAT_MATRIX' || question.subType === 'RATING_MATRIX') && <CsatMatrixScale ... />}
78
+
79
+ {question.type === 'radio' && (
80
+ // Inline Radio implementation
81
+ // Make sure to match brand hover colors!
82
+ )}
83
+
84
+ {question.type === 'text' && (
85
+ // Inline Textarea implementation
86
+ // Handle character limit `question.maxCharacterCount`
87
+ )}
88
+
89
+ {question.type === 'text_and_media' && (
90
+ // Display media (image/video) based on question properties
91
+ // This handles CONTENT blocks.
92
+ )}
45
93
  </div>
46
-
47
- {/* 4. Validation Error Banner */}
94
+
95
+ {/* ERROR BANNER */}
48
96
  {validationError && (
49
97
  <div className="mt-6 rounded-lg border-l-4 border-l-[#e20074] bg-red-50 p-4">
50
98
  <p className="text-sm font-bold text-gray-900 leading-tight flex items-center gap-2">
@@ -52,23 +100,6 @@ Every question is wrapped in a standard HTML section. It **MUST** have a border
52
100
  </p>
53
101
  </div>
54
102
  )}
103
+
55
104
  </section>
56
105
  ```
57
-
58
- **⚠️ CRITICAL RULES**:
59
- 1. **DOM ID**: The wrapper element **MUST** have `id={question.id}` for the scroll-to-error validation to work!
60
- 2. **Borders**: The wrapper **MUST** have `border border-gray-200 rounded-xl`.
61
- 3. **Rich Text**: `question.text` and `question.description` MUST be rendered using `dangerouslySetInnerHTML` because the SDK sends formatted HTML. Do not render them as raw strings.
62
- 4. **Spacing**: Use `space-y-6` and `p-6 md:p-8` for generous internal padding.
63
-
64
- ## Inline vs Dedicated Components
65
-
66
- While complex scales (like Matrix, Slider, FileUpload) get their own dedicated files (e.g. `CsatMatrixScale.tsx`), some simpler types are handled directly inline within `Question.tsx` to reduce boilerplate:
67
-
68
- 1. **`radio` (MCQ)**:
69
- - Each option is a card: `border rounded-lg p-4 flex items-center gap-3 cursor-pointer transition-colors`.
70
- - Hover state: `hover:border-[#e20074] hover:bg-pink-50`.
71
- - Selected state: `border-[#e20074] bg-pink-50`.
72
- - Custom radio dot: `<div className={`h-5 w-5 rounded-full border-2 ${isSelected ? 'border-[#e20074]' : 'border-gray-300'} flex items-center justify-center`}><div className={`h-2.5 w-2.5 rounded-full bg-[#e20074] ${isSelected ? 'block' : 'hidden'}`} /></div>`
73
- 2. **`text`**: Inline `<textarea>` or `<input type="text">` with `border-gray-300 focus:border-[#e20074] focus:ring-[#e20074] rounded-lg w-full`.
74
- 3. **`text_and_media`**: Inline `<video>` or `<img>` tag with `rounded-lg`.
@@ -1,91 +1,28 @@
1
- # Rating Scale Component
1
+ # Rating Scale Architectural Blueprint
2
2
 
3
- > **Component**: `RatingScale`
4
- > **Handles**: `type: 'rating'` | `type: 'rating_scale'`
3
+ > **Target Component**: `RatingScale`
4
+ > **Handles**: `type: 'rating'`, `type: 'rating_scale'`
5
5
 
6
- ## Props Interface
7
- ```typescript
8
- type RatingScaleProps = {
9
- questionId: string;
10
- options: SurveyOption[];
11
- selectedValue?: string | number | null | any;
12
- onSelect: (value: number) => void;
13
- // Extracted from question object:
14
- minLabel?: string;
15
- midLabel?: string;
16
- maxLabel?: string;
17
- midLabelIndex?: number;
18
- };
19
- ```
6
+ ## Core Responsibility
7
+ Render a horizontal sequence of numbered badges.
20
8
 
21
- ## UI Styling & Interaction Rules (CRITICAL)
9
+ ## Configuration Matrix
10
+ You MUST handle the following configurations dynamically:
22
11
 
23
- The UI must exactly match these specifications:
12
+ ### 1. Color Grading (NPS)
13
+ - Check if `option.color` exists. If so, apply it to the badge background. This is crucial for NPS scales (Red 0-6, Yellow 7-8, Green 9-10).
24
14
 
25
- ### 1. Square Rounded Buttons
26
- - Do **NOT** use circles for `rating` badges. Use squares with rounded corners.
27
- - Base classes: `flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center rounded-lg text-sm sm:text-base font-medium transition-all duration-200 cursor-pointer`.
15
+ ### 2. Label Positioning (`minLabel`, `midLabel`, `maxLabel`)
16
+ - **Architectural Requirement**: Wrap the badge sequence and the labels in a relative container.
17
+ - Place labels underneath the badges using absolute positioning.
18
+ - **Mid Label Calculation**: You must calculate the exact center position: `style={{ left: \`\${(midLabelIndex / (options.length - 1)) * 100}%\` }}`.
28
19
 
29
- ### 2. Colors & Hover States
30
- - `options` may contain a `color` property (NPS scale). Use it for the background.
31
- - If no color is provided, use a default gray background.
32
- - **Unselected State**: `opacity-30` (if any option is selected, dim the others), or just standard opacity with a subtle border.
33
- - **Hover State**: `hover:scale-105 hover:shadow-md hover:border-[#e20074] hover:border-2`.
34
- - **Selected State**:
35
- - Apply `opacity-100`.
36
- - Apply a strong prominent border: `border-2 border-[#e20074] shadow-md`.
20
+ ## UI/UX Rules (CRITICAL)
37
21
 
38
- ### 3. Tooltips
39
- - Every button **MUST** have `title={option.label}` so users can see the exact semantic meaning (e.g., "Extremely Likely") when hovering over the number '10'.
22
+ The UI must exactly match enterprise standards:
40
23
 
41
- ### 4. Label Positioning
42
- - Place `minLabel`, `midLabel`, and `maxLabel` underneath the button track.
43
- - Container for labels: `relative mt-3 h-8 w-full text-xs text-gray-500`.
44
- - Left label (`minLabel`): `absolute left-0`.
45
- - Right label (`maxLabel`): `absolute right-0`.
46
- - Middle label (`midLabel`): `absolute transform -translate-x-1/2` with `style={{ left: \`\${(midLabelIndex / (options.length - 1)) * 100}%\` }}`.
47
-
48
- ## Implementation Snippet
49
-
50
- ```tsx
51
- <div className="w-full">
52
- <div className="flex w-full justify-between gap-1 sm:gap-2">
53
- {options.map((option, i) => {
54
- const isSelected = selectedValue === option.value;
55
- const isAnySelected = selectedValue !== undefined && selectedValue !== null;
56
-
57
- return (
58
- <button
59
- key={option.id}
60
- type="button"
61
- onClick={() => onSelect(option.value as number)}
62
- title={option.label} // Tooltip required
63
- className={`
64
- flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center rounded-lg font-semibold transition-all
65
- ${isSelected ? 'border-2 border-[#e20074] scale-105 shadow-md opacity-100 z-10' : 'border border-transparent'}
66
- ${!isSelected && isAnySelected ? 'opacity-40' : 'opacity-100'}
67
- hover:border-[#e20074] hover:scale-105 hover:opacity-100
68
- `}
69
- style={{ backgroundColor: option.color || '#f3f4f6', color: '#000000' }}
70
- >
71
- {option.value}
72
- </button>
73
- );
74
- })}
75
- </div>
76
-
77
- {/* Label Positioning */}
78
- <div className="relative mt-3 h-6 w-full text-xs font-medium text-gray-500">
79
- {minLabel && <span className="absolute left-0">{minLabel}</span>}
80
- {midLabel && (
81
- <span
82
- className="absolute transform -translate-x-1/2 text-center"
83
- style={{ left: `${(midLabelIndex! / (options.length - 1)) * 100}%` }}
84
- >
85
- {midLabel}
86
- </span>
87
- )}
88
- {maxLabel && <span className="absolute right-0 text-right">{maxLabel}</span>}
89
- </div>
90
- </div>
91
- ```
24
+ 1. **Shape**: Badges MUST be square with rounded corners (`rounded-lg w-10 h-10 sm:w-12 sm:h-12`). Do NOT use full circles (`rounded-full`).
25
+ 2. **Unselected State**: If any item is selected, unselected items must dim (`opacity-30`).
26
+ 3. **Selected State**: The selected badge MUST pop out prominently: `border-2 border-[#e20074] shadow-md scale-105 opacity-100 font-bold`.
27
+ 4. **Hover State**: `hover:scale-105 hover:border-[#e20074] transition-all cursor-pointer`.
28
+ 5. **Tooltips**: Every badge MUST have `title={option.label}` applied for accessibility and semantic context (e.g. hovering '10' shows 'Extremely Likely').
@@ -1,79 +1,38 @@
1
- # CSAT Scale Component
1
+ # CSAT & Rating Architectural Blueprints
2
2
 
3
- > **Component**: `CsatScale`
4
- > **Handles**: `type: 'csat'`
3
+ > **Target Components**: `CsatScale`, `RatingScale`, `CsatMatrixScale`
4
+ > **Handles**: `type: 'csat'`, `'rating'`, `'rating_scale'`, CSAT matrices.
5
5
 
6
- ## Props Interface
7
- ```typescript
8
- type CsatScaleProps = {
9
- question: SurveyQuestion & { type: 'csat' };
10
- selectedValue?: string | number | null;
11
- onSelect: (value: number | null) => void;
12
- };
13
- ```
6
+ ## Core Responsibility
7
+ Render sentiment arrays (Faces, Stars, Numbers) ensuring that active selections are unmistakably highlighted using bounding boxes or strong border accents, NEVER just by changing the SVG fill color.
14
8
 
15
- ## UI Styling & Interaction Rules (CRITICAL)
9
+ ## State Management & Mapping
10
+ - **Emojis**: `buttonType === 'emoji'`
11
+ - Map values using `getEmojiForIndex(index, totalLength)` from your icons utility.
12
+ - **Stars**: `buttonType === 'star'`
13
+ - Active stars (index <= selectedValue) get `CsatStarIcons.filled`.
14
+ - **Numbers**: Default for rating scales.
15
+ - Apply `option.color` to the background if provided by the SDK (NPS tracking).
16
16
 
17
- The visual rendering heavily depends on `buttonType`. All variants MUST implement strict hover states, tooltips, and distinct active state bounding boxes.
17
+ ## UX Bounding Box Rules (CRITICAL)
18
18
 
19
- ### 1. `buttonType === 'emoji'`
20
- - **Icons**: Use `getEmojiForIndex(index, total)` or map `CsatEmojiMapping`.
21
- - **Hover State**: `hover:scale-110 transition-transform cursor-pointer`.
22
- - **Active State Bounding Box**:
23
- - DO NOT just change the emoji color.
24
- - The selected emoji MUST be wrapped in a prominent pink square box with rounded corners.
25
- - Wrapper CSS: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 shadow-sm scale-110`.
26
- - Unselected CSS: `border-2 border-transparent p-2 opacity-50 hover:opacity-100`.
27
- - **Tooltips**: `title={option.label}` on every button.
19
+ The UI must exactly match the "Active Box" standard.
28
20
 
29
- ### 2. `buttonType === 'star'`
30
- - **Icons**: Use `CsatStarIcons.filled` and `CsatStarIcons.empty`.
31
- - **Active State Bounding Box**:
32
- - The *last* selected star (the one matching the `selectedValue`) receives the pink bounding box: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 scale-110`.
33
- - Previous filled stars get `border-transparent p-2 text-[#e20074]`.
34
- - Empty stars get `opacity-30`.
21
+ ### For Emojis & Stars:
22
+ - **Unselected**: `inline-flex p-2 border-2 border-transparent opacity-40 hover:opacity-100 hover:scale-110 transition-all cursor-pointer`.
23
+ - **Selected**: The specific selected icon MUST be wrapped in a prominent square box with rounded corners.
24
+ - **CSS**: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 shadow-sm scale-110 opacity-100`.
35
25
 
36
- ### 3. `buttonType === 'numbered'`
37
- - Fallback to `RatingScale` style (Square badges).
26
+ ### For Numbered Badges (Rating):
27
+ - **Shape**: MUST be square with rounded corners (`rounded-lg w-12 h-12`). Do not use full circles.
28
+ - **Selected**: `border-2 border-[#e20074] opacity-100 font-bold scale-105 shadow-md`.
29
+ - **Unselected**: `opacity-30 border-transparent`.
38
30
 
39
- ### 4. `buttonType === 'graphics'`
40
- - Render a single `<CustomSliderTrack>`.
31
+ ## Bounding Boxes inside Matrices
32
+ When emojis or stars are rendered inside `CsatMatrixScale`, the exact same bounding box logic applies. The table cell `<td>` should center the icon, and the icon wrapper `<div>` or `<button>` should toggle between `border-transparent` and `border-[#e20074]` based on the active state.
41
33
 
42
- ## N/A Option Handling
43
- If `question.hasNotApplicable` is true:
44
- - Render a distinct button or checkbox for N/A.
45
- - Selecting it passes `null` to `onSelect`.
46
- - Style it distinctly from the main scale (e.g., standard text button or checkbox).
47
-
48
- ## Label Positioning
49
- - Place `minLabel` and `maxLabel` underneath the emoji/star track, just like RatingScale.
50
- - `relative mt-4 flex w-full justify-between text-xs text-gray-500 font-medium`.
51
-
52
- ## Implementation Snippet (Emoji Variant)
53
-
54
- ```tsx
55
- <div className="flex w-full justify-between items-center gap-2">
56
- {options.map((option, i) => {
57
- const isSelected = selectedValue === option.value;
58
-
59
- return (
60
- <button
61
- key={option.id}
62
- onClick={() => onSelect(option.value as number)}
63
- title={option.label} // Required Tooltip
64
- className={`
65
- flex items-center justify-center transition-all duration-200
66
- ${isSelected
67
- ? 'border-2 border-[#e20074] rounded-lg p-1 sm:p-2 bg-pink-50 scale-110 shadow-sm z-10'
68
- : 'border-2 border-transparent p-1 sm:p-2 opacity-50 hover:opacity-100 hover:scale-105'
69
- }
70
- `}
71
- >
72
- <div className="w-8 h-8 sm:w-10 sm:h-10">
73
- {getEmojiForIndex(i, options.length)}
74
- </div>
75
- </button>
76
- );
77
- })}
78
- </div>
79
- ```
34
+ ## Tooltips & Label Positions
35
+ - **Tooltips**: Every single emoji, star, and badge MUST have `title={label}` applied so the user sees the semantic text string on hover.
36
+ - **Labels (`minLabel`, `midLabel`, `maxLabel`)**:
37
+ - Must be rendered absolutely below the track.
38
+ - `midLabel` must use inline styles to calculate its exact centered position: `style={{ left: \`\${(midLabelIndex / (options.length - 1)) * 100}%\` }}`.
@@ -1,77 +1,33 @@
1
- # CSAT Matrix Scale Component
1
+ # CSAT Matrix Architectural Blueprint
2
2
 
3
- > **Component**: `CsatMatrixScale`
4
- > **Handles**: `type: 'matrix'` (CSAT_MATRIX subType) | `type: 'rating_matrix'`
3
+ > **Target Component**: `CsatMatrixScale`
4
+ > **Handles**: `type: 'matrix'` (CSAT_MATRIX and RATING_MATRIX subTypes)
5
5
 
6
- ## Props Interface
7
- ```typescript
8
- type CsatMatrixScaleProps = {
9
- question: SurveyQuestion & { type: 'matrix' | 'rating_matrix' };
10
- selectedValue: MatrixAnswerMap;
11
- onSelect: (value: MatrixAnswerMap) => void;
12
- };
13
- ```
6
+ ## Core Responsibility
7
+ Render a grid where columns represent satisfaction scales (Emojis, Stars, or Rating Badges).
14
8
 
15
- ## UI Structure & Hover Rules (CRITICAL)
9
+ ## Data Mapping & State Management
10
+ - `selectedValue`: Expects `MatrixAnswerMap` shape.
11
+ - Iterate over `question.rows`. Within each row, iterate over `question.columns`.
12
+ - Render the correct icon/badge in the `<td>` based on `question.buttonType`.
16
13
 
17
- The matrix must be rendered as an HTML `<table>` with strict hover tracking and active selection bounding boxes.
14
+ ## Configuration Matrix
15
+ You MUST handle:
16
+ - **`hasNotApplicable`**: If true, the last column might be `null`. Render a distinct UI (e.g., standard radio or gray badge) for the N/A column.
17
+ - **`reverseScaleOrder`**: Ensure your column mapping respects this logic if implemented in the UI.
18
18
 
19
- ### 1. Table Layout
20
- - `w-full text-left border-collapse`.
21
- - **Headers (`<thead>`)**:
22
- - `th` elements must be `p-4 text-sm font-semibold text-gray-600 text-center`.
23
- - Leftmost header is empty.
19
+ ## UI/UX Bounding Box Rules (CRITICAL)
24
20
 
25
- ### 2. Row Hover Tracking
26
- - Matrix grids are hard to read. You **MUST** add `hover:bg-gray-50 transition-colors` to every `<tr>` in the `<tbody>`.
27
- - Cells: `td` elements must be `p-4 border-b border-gray-100 text-center`.
28
- - Leftmost cell (row text): `text-sm font-medium text-gray-800 text-left w-1/3`.
21
+ Matrices are difficult to read and click accurately. You MUST implement these strict UI rules:
29
22
 
30
- ### 3. Active State Bounding Boxes (Emojis & Stars)
31
- Just like `CsatScale`, when `buttonType === 'emoji'` or `'star'`, you cannot just swap the icon. The selected cell must highlight the selection prominently.
23
+ 1. **Row Tracking**:
24
+ - Every data `<tr>` MUST have `hover:bg-gray-50 transition-colors duration-150`.
32
25
 
33
- - **Unselected Icon Wrapper**: `inline-flex p-1 border-2 border-transparent opacity-40 hover:opacity-100 hover:scale-110 transition-all cursor-pointer`.
34
- - **Selected Icon Wrapper**: `inline-flex p-1 border-2 border-[#e20074] rounded-md bg-pink-50 scale-110 shadow-sm opacity-100 cursor-pointer`.
26
+ 2. **The "Active Bounding Box"**:
27
+ - For Emojis and Stars inside the matrix, do NOT just change the SVG fill.
28
+ - The interactive wrapper inside the `<td>` MUST toggle a pink bounding box.
29
+ - **Selected Cell**: `border-2 border-[#e20074] rounded-lg p-2 bg-pink-50 shadow-sm scale-110 opacity-100 cursor-pointer`.
30
+ - **Unselected Cell**: `border-2 border-transparent p-2 opacity-40 hover:opacity-100 hover:scale-110 cursor-pointer`.
35
31
 
36
- ### 4. Tooltips
37
- - Every icon in every cell MUST have `title={col.label}` so the user knows what column they are clicking.
38
-
39
- ## Implementation Snippet (Row Rendering)
40
-
41
- ```tsx
42
- <tbody>
43
- {question.rows.map((row) => (
44
- <tr key={row.id} className="hover:bg-gray-50 transition-colors group">
45
- <td className="p-4 border-b border-gray-100 text-sm font-medium text-gray-800 text-left w-1/3">
46
- {row.text}
47
- </td>
48
- {question.columns.map((col, colIdx) => {
49
- const isSelected = selectedValue[row.id] === col.value;
50
-
51
- return (
52
- <td key={col.id} className="p-4 border-b border-gray-100 text-center">
53
- <button
54
- type="button"
55
- onClick={() => onSelect({ ...selectedValue, [row.id]: col.value })}
56
- title={col.label} // Tooltip required
57
- className={`
58
- inline-flex items-center justify-center transition-all duration-200
59
- ${isSelected
60
- ? 'border-2 border-[#e20074] rounded-md p-1.5 bg-pink-50 scale-110 shadow-sm'
61
- : 'border-2 border-transparent p-1.5 opacity-40 group-hover:opacity-70 hover:opacity-100 hover:scale-110'
62
- }
63
- `}
64
- >
65
- <div className="w-6 h-6 sm:w-8 sm:h-8">
66
- {/* Render icon based on buttonType */}
67
- {question.buttonType === 'emoji' && getEmojiForIndex(colIdx, question.columns.length)}
68
- {question.buttonType === 'star' && (isSelected ? CsatStarIcons.filled : CsatStarIcons.empty)}
69
- </div>
70
- </button>
71
- </td>
72
- );
73
- })}
74
- </tr>
75
- ))}
76
- </tbody>
77
- ```
32
+ 3. **Tooltips**:
33
+ - Every single interactive cell MUST have `title={col.label}` so the user sees exactly what column they are in without scrolling up to the table header.