@explorer02/cfm-survey-sdk 0.1.9 → 0.2.0
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/package.json +1 -1
- package/templates/docs/01-components/01-survey-page.md +2 -0
- package/templates/docs/01-components/02-question.md +2 -0
- package/templates/docs/01-components/03-rating-scale.md +2 -0
- package/templates/docs/01-components/04-csat-scale.md +3 -1
- package/templates/docs/01-components/05-csat-matrix-scale.md +2 -0
- package/templates/docs/01-components/06-likert-matrix-scale.md +2 -0
- package/templates/docs/01-components/07-slider-matrix-scale.md +2 -0
- package/templates/docs/01-components/08-file-upload-scale.md +2 -0
- package/templates/docs/01-components/09-custom-slider-track.md +2 -0
- package/templates/docs/01-components/10-header-footer.md +3 -1
- package/templates/docs/01-components/11-progress-bar.md +2 -0
- package/templates/docs/01-components/12-language-selector.md +2 -0
- package/templates/docs/01-components/13-matrix-dropdown.md +2 -0
- package/templates/docs/01-components/README.md +2 -2
- package/templates/docs/02-reference/config-field-index.md +158 -0
- package/templates/docs/02-reference/routing-table.md +16 -16
- package/templates/docs/03-ui-specs/00-question-shell.md +89 -0
- package/templates/docs/03-ui-specs/01-rating.md +80 -0
- package/templates/docs/03-ui-specs/02-radio.md +57 -0
- package/templates/docs/03-ui-specs/03-text.md +51 -0
- package/templates/docs/03-ui-specs/04-csat.md +57 -0
- package/templates/docs/03-ui-specs/05-rating-scale.md +49 -0
- package/templates/docs/03-ui-specs/06-slider.md +42 -0
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +66 -0
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +65 -0
- package/templates/docs/03-ui-specs/09-slider-matrix.md +61 -0
- package/templates/docs/03-ui-specs/10-file-upload.md +54 -0
- package/templates/docs/03-ui-specs/11-text-and-media.md +49 -0
- package/templates/docs/03-ui-specs/12-survey-chrome.md +60 -0
- package/templates/docs/03-ui-specs/README.md +79 -0
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +52 -0
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +38 -0
- package/templates/docs/MANIFEST.json +22 -1
- package/templates/docs/index.md +14 -3
- package/templates/docs/templates/implementation_plan.md +13 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# UI Spec: CSAT (`type: 'csat'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/CsatScale.tsx`
|
|
4
|
+
> **SDK type:** `CsatQuestion` — `fetchSurvey/types/csat.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/04-csat-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `options[]` | ✅ | Scale buttons or dropdown options |
|
|
12
|
+
| `buttonType` | ✅ partial | Branches below |
|
|
13
|
+
| `labelPosition` | ✅ | `'top'` → flex-col-reverse; `'hidden'` → no label |
|
|
14
|
+
| `minLabel` / `maxLabel` | ✅ | Rendered in Question.tsx above scale |
|
|
15
|
+
| `hasNotApplicable` | ❌ | N/A option in options array still renders |
|
|
16
|
+
| `reverseScaleOrder` | ❌ | Document: reverse options before map |
|
|
17
|
+
|
|
18
|
+
## Config Decision Tree
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
csat
|
|
22
|
+
├── buttonType === 'dropdown'
|
|
23
|
+
│ └── Native <select>, placeholder "Select an option"
|
|
24
|
+
│ └── Parse value 'null' → null
|
|
25
|
+
└── else (emoji | star | numbered | number | default)
|
|
26
|
+
└── flex flex-wrap gap-3 buttons
|
|
27
|
+
├── emoji → getEmojiForIndex(index, scaleLength)
|
|
28
|
+
├── star → CsatStarIcons filled/empty; labels hidden
|
|
29
|
+
├── numbered/number → mono font value
|
|
30
|
+
└── default → bold value or 'N/A' if null
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Interactive States
|
|
34
|
+
|
|
35
|
+
| State | Border | Background | Transform |
|
|
36
|
+
|-------|--------|------------|-----------|
|
|
37
|
+
| Default | `border-gray-200` | `white` | scale-100 |
|
|
38
|
+
| Hover | `border-gray-300` | `hover:bg-gray-50` | — |
|
|
39
|
+
| Selected | `border-[#e20074]` | `#fdf2f8` | `scale-110` |
|
|
40
|
+
| Star mode | transparent | transparent | no border/bg on button |
|
|
41
|
+
|
|
42
|
+
## Tooltips
|
|
43
|
+
|
|
44
|
+
Recommended: `title={option.label}` on each button. Not in reference client.
|
|
45
|
+
|
|
46
|
+
## Answer Mutation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
onSelect(string | number | null)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Agent Checklist
|
|
53
|
+
|
|
54
|
+
- [ ] Dropdown branch with magenta focus ring
|
|
55
|
+
- [ ] Emoji/star/numbered branches per decision tree
|
|
56
|
+
- [ ] `labelPosition` controls label above/below/hidden
|
|
57
|
+
- [ ] Implement `reverseScaleOrder` and standalone `hasNotApplicable` (SDK gaps)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# UI Spec: Rating Scale (`type: 'rating_scale'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** ❌ Not routed in `Question.tsx` — reuse `CsatScale.tsx` patterns
|
|
4
|
+
> **SDK type:** `RatingScaleQuestion` — `fetchSurvey/types/ratingScale.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/03-rating-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `options[]` | ❌ gap | Star/emoji buttons (no NPS colors) |
|
|
12
|
+
| `scaleStyle` | ❌ gap | `'star'` or `'emoji'` |
|
|
13
|
+
| `minLabel` / `midLabel` / `maxLabel` | ❌ gap | Same formulas as NPS in Question shell |
|
|
14
|
+
| `midLabelIndex` | ❌ gap | `(midLabelIndex / (options.length - 1)) * 100`% |
|
|
15
|
+
| `reverseScaleOrder` | ❌ gap | Reverse options before render |
|
|
16
|
+
|
|
17
|
+
## Expected UI (from CsatScale + rating labels)
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[minLabel] [midLabel @ %] [maxLabel]
|
|
21
|
+
┌────┬────┬────┬────┬────┐
|
|
22
|
+
│ ★ │ ★ │ ★ │ ★ │ ★ │ scaleStyle: 'star'
|
|
23
|
+
└────┴────┴────┴────┴────┘
|
|
24
|
+
OR emoji row from getEmojiForIndex when scaleStyle: 'emoji'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Reuse CsatScale selected styling: `border-[#e20074] bg-[#fdf2f8] scale-110`.
|
|
28
|
+
|
|
29
|
+
## Config Decision Tree
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
rating_scale
|
|
33
|
+
├── scaleStyle === 'star' → CsatStarIcons per option
|
|
34
|
+
├── scaleStyle === 'emoji' → getEmojiForIndex
|
|
35
|
+
└── else → numbered badges (no option.color)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Answer Mutation
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
onSelect(number)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Agent Checklist
|
|
45
|
+
|
|
46
|
+
- [ ] Add dispatcher branch in Question for `rating_scale`
|
|
47
|
+
- [ ] No traffic-light NPS colors on options
|
|
48
|
+
- [ ] min/mid/max labels with midLabelIndex formula
|
|
49
|
+
- [ ] Match CsatScale interactive states
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# UI Spec: Standalone Slider (`type: 'slider'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** ❌ Not routed in `Question.tsx` — reuse `CustomSliderTrack.tsx`
|
|
4
|
+
> **SDK type:** `SliderQuestion` — `fetchSurvey/types/slider.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/09-custom-slider-track.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `min` / `max` / `step` | ❌ gap | Track range |
|
|
12
|
+
| `defaultValue` | ❌ gap | Initial thumb position |
|
|
13
|
+
| `minLabel` / `maxLabel` | ❌ gap | End labels above track |
|
|
14
|
+
| `enableInputBox` | ❌ gap | See SliderMatrixScale input pattern |
|
|
15
|
+
| `displayValues` | ❌ gap | Pass to CustomSliderTrack |
|
|
16
|
+
|
|
17
|
+
## Layout Anatomy
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[minLabel] [maxLabel]
|
|
21
|
+
┌──────────────────────────────────────────────────┐
|
|
22
|
+
│ ───●──────────────── (CustomSliderTrack) │
|
|
23
|
+
└──────────────────────────────────────────────────┘
|
|
24
|
+
[optional number input + steppers if enableInputBox]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Reference Patterns
|
|
28
|
+
|
|
29
|
+
Copy from [`09-slider-matrix.md`](09-slider-matrix.md) single-row layout and [`shared/custom-slider-track.md`](shared/custom-slider-track.md).
|
|
30
|
+
|
|
31
|
+
## Answer Mutation
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
onSelect(number)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Agent Checklist
|
|
38
|
+
|
|
39
|
+
- [ ] Add Question dispatcher for `slider`
|
|
40
|
+
- [ ] CustomSliderTrack with min/max/step
|
|
41
|
+
- [ ] Optional input box with clamp min/max
|
|
42
|
+
- [ ] End labels flex justify-between
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# UI Spec: CFM Matrix (`type: 'matrix'`, `subType: 'CFM_MATRIX'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/LikertMatrixScale.tsx`
|
|
4
|
+
> **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/06-likert-matrix-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `rows[]`, `columns[]` | ✅ | Grid / carousel / dropdown data |
|
|
12
|
+
| `matrixFormat` | ✅ | Orchestrator routing |
|
|
13
|
+
| `answerType` | ✅ | `'multiple'` → checkboxes |
|
|
14
|
+
| `matrixType: 'bipolar'` | ✅ | 25% left + 25% right columns |
|
|
15
|
+
| `labels[]` | ✅ | Anchor labels above scale |
|
|
16
|
+
| `transposeTable` | ✅ | Swap row/col iteration |
|
|
17
|
+
| `repeatScale` | ✅ | Re-render header per row |
|
|
18
|
+
| `enableScale` | ✅ | Show/hide column header row |
|
|
19
|
+
| `hasNotApplicable` | ✅ | N/A column with mutex |
|
|
20
|
+
|
|
21
|
+
## Config Decision Tree
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
CFM_MATRIX
|
|
25
|
+
├── matrixFormat === 'dropdown' → LikertMatrixDropdownLayout (MatrixDropdown per row)
|
|
26
|
+
├── matrixFormat === 'carousel' → LikertMatrixCarouselLayout (one row, prev/next, blue dots)
|
|
27
|
+
└── else → LikertMatrixGridLayout
|
|
28
|
+
├── transposeTable ? columns×rows : rows×columns
|
|
29
|
+
├── repeatScale ? header per row : single header
|
|
30
|
+
├── enableScale ? column headers : hidden
|
|
31
|
+
└── matrixType === 'bipolar' ? 25% | scale | 25% layout
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Interactive Behaviors
|
|
35
|
+
|
|
36
|
+
| Behavior | Condition | Action |
|
|
37
|
+
|----------|-----------|--------|
|
|
38
|
+
| Click selected radio again | single-select grid/carousel | Clear row (`undefined`) |
|
|
39
|
+
| N/A selected | any | Other cols opacity 0.4, disabled |
|
|
40
|
+
| Multi N/A | answerType multiple | Sets `[null]` exclusively |
|
|
41
|
+
| Multi toggle | answerType multiple | Add/remove from array |
|
|
42
|
+
|
|
43
|
+
## Grid Layout
|
|
44
|
+
|
|
45
|
+
- Row label column: `180px` (likert) or `25%` (bipolar)
|
|
46
|
+
- Zebra rows: even rows `rgba(249,250,251,0.8)`
|
|
47
|
+
- Native inputs: `accentColor: #e20074`, 18×18px
|
|
48
|
+
|
|
49
|
+
## Carousel
|
|
50
|
+
|
|
51
|
+
- Blue active dot: `#2563eb` (Likert only)
|
|
52
|
+
- Card: white, border, shadow, centered row text
|
|
53
|
+
|
|
54
|
+
## Answer Mutation
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
onSelect(MatrixAnswerMap) // Record<rowId, value | value[] | null>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Agent Checklist
|
|
61
|
+
|
|
62
|
+
- [ ] All three matrixFormat branches
|
|
63
|
+
- [ ] transposeTable + repeatScale + enableScale
|
|
64
|
+
- [ ] Bipolar 25/50/25 layout with rightText
|
|
65
|
+
- [ ] Single-select deselect on re-click
|
|
66
|
+
- [ ] N/A mutex with opacity 0.4
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# UI Spec: CSAT / Rating Matrix (`subType: 'CSAT_MATRIX' | 'RATING_MATRIX'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/CsatMatrixScale.tsx`
|
|
4
|
+
> **SDK type:** `MatrixQuestion` — `fetchSurvey/types/matrix.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/05-csat-matrix-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `buttonType` | ✅ | Grid vs vertical list routing |
|
|
12
|
+
| `matrixFormat` | ✅ | carousel vs standard |
|
|
13
|
+
| `hasNotApplicable` | ✅ | Checkbox below row (vertical) or N/A column (grid) |
|
|
14
|
+
| `reverseScaleOrder` | ✅ | Flip emoji/star/number index |
|
|
15
|
+
| `labels[]` | ✅ | Anchor row above columns |
|
|
16
|
+
| `columns[].color` | ✅ | RATING_MATRIX badge colors |
|
|
17
|
+
|
|
18
|
+
## Config Decision Tree
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
CSAT_MATRIX | RATING_MATRIX
|
|
22
|
+
├── matrixFormat === 'carousel' → CsatMatrixCarousel
|
|
23
|
+
├── buttonType in (dropdown, selectbox, radio) → CsatMatrixVerticalList
|
|
24
|
+
│ ├── dropdown → MatrixDropdown placeholder "Select..."
|
|
25
|
+
│ ├── selectbox → full-width cards #fdf2f8 selected
|
|
26
|
+
│ └── else → card radio list
|
|
27
|
+
└── else → CsatMatrixGrid
|
|
28
|
+
├── emoji → getEmojiForIndex + hover scale + ring when selected
|
|
29
|
+
├── star → opacity 0.5 unselected; filled/empty icons
|
|
30
|
+
├── numbered → 40×40 badge; selected magenta fill
|
|
31
|
+
└── graphics → CustomSliderTrack per row (not in grid — carousel only)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Grid Structure
|
|
35
|
+
|
|
36
|
+
- Row label: `180px` fixed column
|
|
37
|
+
- N/A column: `56px` when `hasNotApplicable`
|
|
38
|
+
- Alternating row backgrounds
|
|
39
|
+
- Column headers OR `labels[]` anchor row with percentage positioning
|
|
40
|
+
|
|
41
|
+
## Carousel
|
|
42
|
+
|
|
43
|
+
- Pink active dot expands: `20px × 8px` vs `8px` inactive
|
|
44
|
+
- Column labels below cells
|
|
45
|
+
- Emoji tooltip via hover state (`hoveredCell`)
|
|
46
|
+
- Graphics mode: slider maps column index
|
|
47
|
+
|
|
48
|
+
## Tooltips
|
|
49
|
+
|
|
50
|
+
`title={col.label}` recommended on grid cells (emoji hover uses scale transform).
|
|
51
|
+
|
|
52
|
+
## Answer Mutation
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
onSelect(MatrixAnswerMap)
|
|
56
|
+
// hasNotApplicable: null for N/A; undefined clears
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Agent Checklist
|
|
60
|
+
|
|
61
|
+
- [ ] scaleColumns = columns.filter(c => c.value !== null)
|
|
62
|
+
- [ ] All buttonType × matrixFormat branches
|
|
63
|
+
- [ ] reverseScaleOrder on emoji/star/numbered
|
|
64
|
+
- [ ] Carousel pink dot indicators
|
|
65
|
+
- [ ] Vertical selectbox cards match standalone radio styling
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# UI Spec: Slider Matrix (`type: 'slider_matrix'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/SliderMatrixScale.tsx`
|
|
4
|
+
> **SDK type:** `SliderMatrixQuestion` — `fetchSurvey/types/sliderMatrix.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/07-slider-matrix-scale.md`
|
|
6
|
+
> **Shared:** [`shared/custom-slider-track.md`](shared/custom-slider-track.md)
|
|
7
|
+
|
|
8
|
+
## Config Inventory
|
|
9
|
+
|
|
10
|
+
| Field | Implemented | UI impact |
|
|
11
|
+
|-------|-------------|-----------|
|
|
12
|
+
| `rows[]` (min/max/step) | ✅ | Per-row slider range |
|
|
13
|
+
| `labels[]` | ✅ | Header anchor labels |
|
|
14
|
+
| `tickValues[]` | ✅ | Colored badges above track |
|
|
15
|
+
| `ticks` | ✅ | Discrete marks + htmlStep |
|
|
16
|
+
| `sliderType: 'graphics'` | ✅ | Plain text tick labels; emoji thumb |
|
|
17
|
+
| `enableInputBox` | ✅ | 80px input + stepper buttons |
|
|
18
|
+
| `displayValues` | ✅ | Passed to CustomSliderTrack |
|
|
19
|
+
| `enableNotApplicable` | ✅ | Checkbox; value `'N/A'` string |
|
|
20
|
+
|
|
21
|
+
## Layout Anatomy
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
[header: labels[] + tickValues badges OR computed marks]
|
|
25
|
+
┌─ row band (#f3f4f6, rounded-xl) ─────────────────────────────┐
|
|
26
|
+
│ 25% statement │ CustomSliderTrack │ 25% rightText? │ input? │ N/A? │
|
|
27
|
+
└───────────────────────────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Header Tick Positioning
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
percentage = ((tv.value - min) / (max - min)) * 100
|
|
34
|
+
sliceWidth = tickValues.length > 1 ? 100 / (tickValues.length - 1) : 100
|
|
35
|
+
// Colored badge: tv.color bg, white text, rounded 4px
|
|
36
|
+
// graphics: plain text label instead of colored badge
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## N/A Behavior
|
|
40
|
+
|
|
41
|
+
- Checked → `rowVal === 'N/A'`; slider disabled, input grayed
|
|
42
|
+
- Unchecked → reset to `defaultValue ?? min`
|
|
43
|
+
|
|
44
|
+
## Input Box
|
|
45
|
+
|
|
46
|
+
- Clamp to row.min / row.max on change
|
|
47
|
+
- Steppers use `htmlStep` increment/decrement
|
|
48
|
+
|
|
49
|
+
## Answer Mutation
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
onSelect(Record<string, number | null | 'N/A'>)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Agent Checklist
|
|
56
|
+
|
|
57
|
+
- [ ] Shared header from first row min/max/step
|
|
58
|
+
- [ ] Gray row bands with 25% statement columns
|
|
59
|
+
- [ ] CustomSliderTrack per row with correct htmlStep
|
|
60
|
+
- [ ] tickValues colored badges (non-graphics)
|
|
61
|
+
- [ ] N/A mutex disables slider + input
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# UI Spec: File Upload (`type: 'file_upload'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/FileUploadScale.tsx`
|
|
4
|
+
> **SDK type:** `FileUploadQuestion` — `fetchSurvey/types/fileUpload.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/08-file-upload-scale.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `uploadMessage` | ✅ | HTML in dropzone (bold 16px) |
|
|
12
|
+
| `supportedFileFormats` | ✅ | Validation + accept attribute |
|
|
13
|
+
| `maxFileCount` | ✅ | Total file limit |
|
|
14
|
+
| `fileSizeLimit` | ✅ | MB limit |
|
|
15
|
+
| `fileSizeLimitType` | ✅ | `PER_FILE` or `IN_TOTAL` |
|
|
16
|
+
|
|
17
|
+
## Layout Anatomy
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[uploadMessage HTML in dashed dropzone]
|
|
21
|
+
File limit: N; File size limit: XMB/file|in total; Supports: ...
|
|
22
|
+
[local error banner — red if validation fails]
|
|
23
|
+
[file list cards: icon, name, size, remove ×]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Interactive States
|
|
27
|
+
|
|
28
|
+
| State | Border | Background |
|
|
29
|
+
|-------|--------|------------|
|
|
30
|
+
| Default | `#d1d5db` dashed | `#f9fafb` |
|
|
31
|
+
| Hover | `#e20074` | `#fdf2f8` |
|
|
32
|
+
| Drag active | `#e20074` | `#fdf2f8` |
|
|
33
|
+
|
|
34
|
+
## Local Validation (before SDK)
|
|
35
|
+
|
|
36
|
+
1. Max file count exceeded
|
|
37
|
+
2. Unsupported extension
|
|
38
|
+
3. PER_FILE size exceeded
|
|
39
|
+
4. IN_TOTAL cumulative size exceeded
|
|
40
|
+
|
|
41
|
+
Error banner: `#fee2e2` bg, `#b91c1c` text.
|
|
42
|
+
|
|
43
|
+
## Answer Mutation
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
onSelect(File[] | undefined) // undefined when all removed
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Agent Checklist
|
|
50
|
+
|
|
51
|
+
- [ ] Hidden `<input type="file">` triggered by dropzone click
|
|
52
|
+
- [ ] Drag enter/over/leave/drop handlers
|
|
53
|
+
- [ ] Dynamic constraints line in dropzone
|
|
54
|
+
- [ ] File list with remove button (hover red)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# UI Spec: Text and Media (`type: 'text_and_media'`)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/Question.tsx` (inline)
|
|
4
|
+
> **SDK type:** `TextAndMediaQuestion` — `fetchSurvey/types/textAndMedia.ts`
|
|
5
|
+
> **Component blueprint:** `01-components/02-question.md`
|
|
6
|
+
|
|
7
|
+
## Config Inventory
|
|
8
|
+
|
|
9
|
+
| Field | Implemented | UI impact |
|
|
10
|
+
|-------|-------------|-----------|
|
|
11
|
+
| `mediaUrl` | ✅ | Required to render media block |
|
|
12
|
+
| `mediaMimeType` | ✅ | `video/` or `VIDEO` → `<video>`; else `<img>` |
|
|
13
|
+
| `mediaTitle` | ✅ | Caption below media, centered gray text |
|
|
14
|
+
| `mediaAlignment` | ✅ | Flex justify: CENTER / RIGHT / default left |
|
|
15
|
+
| `mediaSize` | ✅ | Width `%` on media container; default 100% |
|
|
16
|
+
|
|
17
|
+
No answer input — display-only question.
|
|
18
|
+
|
|
19
|
+
## Layout Anatomy
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
[question title + description — standard shell]
|
|
23
|
+
|
|
24
|
+
┌─ flex container (justify per mediaAlignment) ──────────────┐
|
|
25
|
+
│ ┌─ media block (width: mediaSize%) ──────────────────┐ │
|
|
26
|
+
│ │ <video controls> or <img> │ │
|
|
27
|
+
│ │ [optional mediaTitle caption] │ │
|
|
28
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
29
|
+
└────────────────────────────────────────────────────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Alignment Mapping
|
|
33
|
+
|
|
34
|
+
| `mediaAlignment` | CSS |
|
|
35
|
+
|------------------|-----|
|
|
36
|
+
| `TOP_CENTER`, `BOTTOM_CENTER` | `justify-center` |
|
|
37
|
+
| `TOP_RIGHT`, `BOTTOM_RIGHT` | `justify-end` |
|
|
38
|
+
| default | `justify-start` |
|
|
39
|
+
|
|
40
|
+
## Answer Mutation
|
|
41
|
+
|
|
42
|
+
None — no `onSelect` payload for this type.
|
|
43
|
+
|
|
44
|
+
## Agent Checklist
|
|
45
|
+
|
|
46
|
+
- [ ] Render only when `mediaUrl` is present
|
|
47
|
+
- [ ] Video vs image from mime type
|
|
48
|
+
- [ ] `max-w-full rounded-md shadow-sm` on media element
|
|
49
|
+
- [ ] Optional centered caption for `mediaTitle`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# UI Spec: Survey Chrome
|
|
2
|
+
|
|
3
|
+
> **Reference:** `SurveyPage.tsx`, `Header.tsx`, `Footer.tsx`, `ProgressBar.tsx`, `LanguageSelector.tsx`
|
|
4
|
+
> **Component blueprints:** `01-components/01-survey-page.md`, `10-header-footer.md`, `11-progress-bar.md`, `12-language-selector.md`
|
|
5
|
+
|
|
6
|
+
## SurveyPage — 5-State Machine
|
|
7
|
+
|
|
8
|
+
| State | Condition | UI |
|
|
9
|
+
|-------|-----------|-----|
|
|
10
|
+
| Loading | `surveyQueryResults.isLoading` | Centered "Loading..." |
|
|
11
|
+
| Error | `surveyQueryResults.error` | Red error message |
|
|
12
|
+
| Empty | `!survey` | "No survey data" |
|
|
13
|
+
| Submitted | `submitSurveyResults.data` | Thank you message |
|
|
14
|
+
| Active | else | Questions + nav |
|
|
15
|
+
|
|
16
|
+
## Active Layout
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Header
|
|
20
|
+
main (max-w-4xl)
|
|
21
|
+
ProgressBar(progressPercentage)
|
|
22
|
+
LanguageSelector (hidden if ≤1 language)
|
|
23
|
+
Questions (space-y-12)
|
|
24
|
+
Submit error (if any)
|
|
25
|
+
Nav: Back (if page > 0) + Next/Submit
|
|
26
|
+
Footer
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Navigation Labels
|
|
30
|
+
|
|
31
|
+
German when `survey.language.startsWith('de')`:
|
|
32
|
+
|
|
33
|
+
| Action | EN | DE |
|
|
34
|
+
|--------|----|----|
|
|
35
|
+
| Back | Zurück | Back |
|
|
36
|
+
| Next | Weiter | Next |
|
|
37
|
+
| Submit | Absenden | Submit |
|
|
38
|
+
| Submitting | Wird gesendet... | Submitting... |
|
|
39
|
+
|
|
40
|
+
## Button Tokens
|
|
41
|
+
|
|
42
|
+
- Back: `border-[#e20074] text-[#e20074] hover:bg-[#fdf2f8]`
|
|
43
|
+
- Primary: `bg-[#e20074] text-white hover:opacity-90`
|
|
44
|
+
|
|
45
|
+
## ProgressBar
|
|
46
|
+
|
|
47
|
+
Pink fill bar, `300ms` width transition on `progressPercentage`.
|
|
48
|
+
|
|
49
|
+
## LanguageSelector
|
|
50
|
+
|
|
51
|
+
- Hidden when `languages.length <= 1`
|
|
52
|
+
- Focus ring: `#2563eb`
|
|
53
|
+
|
|
54
|
+
## Agent Checklist
|
|
55
|
+
|
|
56
|
+
- [ ] All 5 states implemented
|
|
57
|
+
- [ ] `onAction({ type: 'CHANGE' })` wired per question
|
|
58
|
+
- [ ] PREVIOUS / NEXT actions on nav buttons
|
|
59
|
+
- [ ] ProgressBar receives `state.progressPercentage`
|
|
60
|
+
- [ ] Header/Footer on every state branch
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# UI Specifications — Reference Implementation Guide
|
|
2
|
+
|
|
3
|
+
> **Authoritative source:** [`apps/client/src/components/`](../../../../../../apps/client/src/components/) in the monorepo.
|
|
4
|
+
> Agents must reproduce this UI when building survey apps. Read the spec for each question type present in the fetched survey.
|
|
5
|
+
|
|
6
|
+
## How to Read a UI Spec
|
|
7
|
+
|
|
8
|
+
Each file in this folder follows the same structure:
|
|
9
|
+
|
|
10
|
+
1. **Config inventory** — every SDK field, whether the reference client implements it
|
|
11
|
+
2. **Layout anatomy** — structured ASCII diagram
|
|
12
|
+
3. **Config decision tree** — matches `if/else` branches in reference code
|
|
13
|
+
4. **Interactive states** — default, hover, selected, disabled, focus
|
|
14
|
+
5. **Tooltips & labels** — positioning formulas
|
|
15
|
+
6. **Color tokens** — exact hex values from reference client
|
|
16
|
+
7. **Answer mutation** — `onSelect` / `onAction CHANGE` shape
|
|
17
|
+
8. **Agent checklist**
|
|
18
|
+
|
|
19
|
+
## Design Tokens (Reference Client)
|
|
20
|
+
|
|
21
|
+
| Token | Value | Usage |
|
|
22
|
+
|-------|-------|-------|
|
|
23
|
+
| Primary | `#e20074` | Borders, rings, dots, slider fill, selected text |
|
|
24
|
+
| Selected bg | `#fdf2f8` | Card options, CSAT buttons, matrix cells |
|
|
25
|
+
| NPS track highlight | `#fbe8f3` | Selected radio cell in NPS track row |
|
|
26
|
+
| NPS track bar | `#f2f2f2` | Gray bar behind NPS radios |
|
|
27
|
+
| Validation border | `#333333` | Error banner |
|
|
28
|
+
| Validation bg | `#d9d9d9` | Error banner |
|
|
29
|
+
| Row band | `#f3f4f6` | Slider matrix row background |
|
|
30
|
+
| Zebra row | `rgba(249,250,251,0.8)` | Matrix alternating rows |
|
|
31
|
+
| Tooltip bg | `#1f2937` | Hover tooltips on emoji/slider |
|
|
32
|
+
| Likert carousel dot (active) | `#2563eb` | LikertMatrix carousel only |
|
|
33
|
+
| CSAT carousel dot (active) | `#e20074` | CsatMatrix carousel |
|
|
34
|
+
|
|
35
|
+
## Reference Component Index
|
|
36
|
+
|
|
37
|
+
| Spec file | Reference component | `question.type` |
|
|
38
|
+
|-----------|---------------------|-----------------|
|
|
39
|
+
| [00-question-shell.md](00-question-shell.md) | `Question.tsx` | All (wrapper) |
|
|
40
|
+
| [01-rating.md](01-rating.md) | `RatingScale.tsx` | `rating` |
|
|
41
|
+
| [02-radio.md](02-radio.md) | `Question.tsx` inline | `radio` |
|
|
42
|
+
| [03-text.md](03-text.md) | `Question.tsx` inline | `text` |
|
|
43
|
+
| [04-csat.md](04-csat.md) | `CsatScale.tsx` | `csat` |
|
|
44
|
+
| [05-rating-scale.md](05-rating-scale.md) | *(gap — reuse CsatScale patterns)* | `rating_scale` |
|
|
45
|
+
| [06-slider.md](06-slider.md) | *(gap — reuse CustomSliderTrack)* | `slider` |
|
|
46
|
+
| [07-matrix-cfm.md](07-matrix-cfm.md) | `LikertMatrixScale.tsx` | `matrix` + CFM |
|
|
47
|
+
| [08-matrix-csat-rating.md](08-matrix-csat-rating.md) | `CsatMatrixScale.tsx` | CSAT/RATING matrix |
|
|
48
|
+
| [09-slider-matrix.md](09-slider-matrix.md) | `SliderMatrixScale.tsx` | `slider_matrix` |
|
|
49
|
+
| [10-file-upload.md](10-file-upload.md) | `FileUploadScale.tsx` | `file_upload` |
|
|
50
|
+
| [11-text-and-media.md](11-text-and-media.md) | `Question.tsx` inline | `text_and_media` |
|
|
51
|
+
| [12-survey-chrome.md](12-survey-chrome.md) | `SurveyPage.tsx` + chrome | Shell |
|
|
52
|
+
| [shared/custom-slider-track.md](shared/custom-slider-track.md) | `CustomSliderTrack.tsx` | Primitive |
|
|
53
|
+
| [shared/matrix-dropdown.md](shared/matrix-dropdown.md) | `MatrixDropdown.tsx` | Primitive |
|
|
54
|
+
|
|
55
|
+
## NPS Traffic-Light Colors
|
|
56
|
+
|
|
57
|
+
Use `option.color` from SDK — never invent colors:
|
|
58
|
+
|
|
59
|
+
| Value | Hex |
|
|
60
|
+
|-------|-----|
|
|
61
|
+
| 0 | `#e2001a` |
|
|
62
|
+
| 1 | `#e4251b` |
|
|
63
|
+
| 2 | `#ec610a` |
|
|
64
|
+
| 3 | `#f18b00` |
|
|
65
|
+
| 4 | `#f7b200` |
|
|
66
|
+
| 5 | `#fcd900` |
|
|
67
|
+
| 6 | `#fcd900` |
|
|
68
|
+
| 7 | `#9fca00` |
|
|
69
|
+
| 8 | `#6bb300` |
|
|
70
|
+
| 9 | `#339a00` |
|
|
71
|
+
| 10 | `#008000` |
|
|
72
|
+
|
|
73
|
+
## When to Read
|
|
74
|
+
|
|
75
|
+
After Phase 3 in [`index.md`](../index.md), for each question in `state.currentQuestions`:
|
|
76
|
+
|
|
77
|
+
1. Read [`02-reference/question-types/`](../02-reference/question-types/) for data shape
|
|
78
|
+
2. Read matching spec from this folder (see [`MANIFEST.json`](../MANIFEST.json) `uiSpecs`)
|
|
79
|
+
3. Cross-check [`config-field-index.md`](../02-reference/config-field-index.md)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# UI Spec: Custom Slider Track (shared primitive)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/CustomSliderTrack.tsx`
|
|
4
|
+
> **Used by:** `SliderMatrixScale.tsx`, `CsatMatrixScale` (graphics), standalone `slider` (gap)
|
|
5
|
+
|
|
6
|
+
## Layer Architecture
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
1. Gray inactive track (#e5e7eb, 4px height)
|
|
10
|
+
2. Magenta fill bar (width = percentage%, #e20074 or #9ca3af if disabled)
|
|
11
|
+
3. Tick marks (1px × 8px at ticks positions)
|
|
12
|
+
4. Invisible <input type="range"> overlay (opacity 0, full hit area)
|
|
13
|
+
5. Visual thumb: circle OR emoji (sliderType === 'graphics')
|
|
14
|
+
6. Tooltip bubble on hover/drag (#1f2937 bg, rounded value)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
| Prop | Role |
|
|
20
|
+
|------|------|
|
|
21
|
+
| `min`, `max`, `htmlStep` | Range input bounds |
|
|
22
|
+
| `value` | Current position |
|
|
23
|
+
| `disabled` | Gray theme, opacity 0.5, no interaction |
|
|
24
|
+
| `displayValues` | Reserved — tooltip always shows on hover/drag in client |
|
|
25
|
+
| `hasSelectedValue` | Reserved |
|
|
26
|
+
| `sliderType: 'graphics'` | Emoji thumb via `getEmojiForIndex` |
|
|
27
|
+
| `ticks` | Discrete tick count + emoji index scale |
|
|
28
|
+
| `reverseScaleOrder` | Flip emoji index calculation |
|
|
29
|
+
|
|
30
|
+
## Interactive States
|
|
31
|
+
|
|
32
|
+
| State | Thumb | Tooltip |
|
|
33
|
+
|-------|-------|---------|
|
|
34
|
+
| Default | White circle, 2.5px magenta border | Hidden |
|
|
35
|
+
| Hover/drag | Pink glow ring `rgba(226,0,116,0.2)` | Dark bubble with `Math.round(value)` |
|
|
36
|
+
| Disabled | Gray border, opacity 0.5 | Hidden |
|
|
37
|
+
| Graphics | Emoji at percentage position | Same tooltip |
|
|
38
|
+
|
|
39
|
+
## Emoji Index Formula
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const idx = Math.round(((value - min) / (max - min)) * ((ticks || 10) - 1));
|
|
43
|
+
const displayIdx = reverseScaleOrder ? (ticks || 10) - 1 - idx : idx;
|
|
44
|
+
getEmojiForIndex(displayIdx, ticks || 10)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Agent Checklist
|
|
48
|
+
|
|
49
|
+
- [ ] 4-layer overlay with invisible range input
|
|
50
|
+
- [ ] Tooltip on hover AND drag
|
|
51
|
+
- [ ] Graphics mode emoji thumb
|
|
52
|
+
- [ ] Transition disabled while dragging
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# UI Spec: Matrix Dropdown (shared primitive)
|
|
2
|
+
|
|
3
|
+
> **Reference:** `apps/client/src/components/MatrixDropdown.tsx`
|
|
4
|
+
> **Used by:** `LikertMatrixScale`, `CsatMatrixScale` vertical list
|
|
5
|
+
|
|
6
|
+
## Modes
|
|
7
|
+
|
|
8
|
+
| `multiple` | Trigger | Selection |
|
|
9
|
+
|------------|---------|-----------|
|
|
10
|
+
| `false` | Single line + chevron | One value; checkmark on selected option |
|
|
11
|
+
| `true` | Tag chips with × | Toggle array; N/A mutex |
|
|
12
|
+
|
|
13
|
+
## Interactive States
|
|
14
|
+
|
|
15
|
+
| State | Trigger border | Shadow |
|
|
16
|
+
|-------|----------------|--------|
|
|
17
|
+
| Closed | `#d1d5db` | subtle |
|
|
18
|
+
| Open | `#e20074` | `0 0 0 3px rgba(226,0,116,0.1)` |
|
|
19
|
+
| Option hover | bg `#fdf2f8` | — |
|
|
20
|
+
| Option selected | bg `#fdf2f8`, text `#e20074` | — |
|
|
21
|
+
| N/A mutex (multi) | Other options opacity 0.5, disabled | — |
|
|
22
|
+
|
|
23
|
+
## N/A Mutex (multi-select)
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
if (val === null) {
|
|
27
|
+
if (multiValues.includes(null)) onChange([]);
|
|
28
|
+
else onChange([null]);
|
|
29
|
+
}
|
|
30
|
+
// When null selected, non-null options disabled
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Agent Checklist
|
|
34
|
+
|
|
35
|
+
- [ ] Click-outside closes panel
|
|
36
|
+
- [ ] HTML labels via dangerouslySetInnerHTML
|
|
37
|
+
- [ ] Single: close on select
|
|
38
|
+
- [ ] Multi: tag chips removable with ×
|