npm package: @monarkmarkets/question-types
Description: Question type components for Monark Markets
Latest version: 1.0.9

# @monarkmarkets/question-types

A package of reusable React components for building interactive question interfaces including single choice, multiple
choice, and scale-based questions.

## Installation

```bash
npm install @monarkmarkets/question-types
```

> **Peer dependencies:** Install `react` and `react-dom` `^18.2.0 || ^19.0.0` in the consuming app. Styling helpers use Emotion and MUI (`@emotion/react`, `@emotion/styled`, `@mui/material`), which ship as direct dependencies of this package.

## Features

- **Multiple question formats** - Single choice, multiple choice, and scale/slider questions
- **Highly customizable** - Extensive props for tailoring appearance and behavior
- **Type-safe** - Written in TypeScript with comprehensive type definitions

## Components

### SingleChoiceQuestion

A radio button-based question component that allows users to select exactly one option.

```tsx
import {SingleChoiceQuestion} from '@monarkmarkets/question-types';

function MyForm() {
    return (
        <SingleChoiceQuestion
            options={["Option 1", "Option 2", "Option 3"]}
            explanation="Please select one option"
            selectedOption="Option 1"
            onSelect={(selected) => {
                // Handle selection
            }}
        />
    );
}
```

### MultiChoiceQuestion

A checkbox-based question component that allows users to select multiple options.

```tsx
import {MultiChoiceQuestion} from '@monarkmarkets/question-types';

function MyForm() {
    return (
        <MultiChoiceQuestion
            options={["Option 1", "Option 2", "Option 3"]}
            explanation="Please select all that apply"
            selectedOptions={["Option 1"]}
            onSelect={(selected) => {
                // Handle selection
            }}
        />
    );
}
```

### ScaleQuestion

A slider-based question component for numeric or scale-based inputs.

```tsx
import {ScaleQuestion} from '@monarkmarkets/question-types';

function MyForm() {
    return (
        <ScaleQuestion
            options={["How much would you like to invest?"]}
            explanation="Drag the slider to select an amount"
            min={0}
            max={1000000}
            step={5000}
            selectedValues={["50000"]}
            defaultValue={0}
            minLabel="Minimum Investment"
            maxLabel="Maximum Investment"
            currency={{
                symbol: 'USD',
                position: 'prefix',
                locale: 'en-US'
            }}
            onSelect={(selected) => {
                // Handle selection
            }}
            showNumberLabels={true}
        />
    );
}
```

## Props

### Common Props (BaseQuestionProps)

| Prop           | Type                         | Default    | Description                                |
|----------------|------------------------------|------------|--------------------------------------------|
| id             | string                       | undefined  | Optional unique identifier                 |
| disabled       | boolean                      | false      | Whether the component is disabled          |
| error          | string                       | undefined  | Custom error message                       |
| options        | string[]                     | (required) | Array of options to display                |
| onSelect       | (selected: string[]) => void | (required) | Callback function when selection changes   |
| explanation    | string                       | undefined  | Optional explanation or help text          |
| showNoneOption | boolean                      | false      | Whether to show "None of the Above" option |

### SingleChoiceQuestion Props

| Prop           | Type                                                     | Default   | Description                        |
|----------------|----------------------------------------------------------|-----------|------------------------------------|
| selectedOption | string                                                   | undefined | The currently selected option      |
| allowDeselect  | boolean                                                  | false     | Whether options can be deselected  |
| renderOption   | (option: string, isSelected: boolean) => React.ReactNode | undefined | Custom render function for options |

### MultiChoiceQuestion Props

| Prop            | Type                                                     | Default   | Description                        |
|-----------------|----------------------------------------------------------|-----------|------------------------------------|
| selectedOptions | string[]                                                 | []        | The currently selected options     |
| allowDeselect   | boolean                                                  | true      | Whether options can be deselected  |
| renderOption    | (option: string, isSelected: boolean) => React.ReactNode | undefined | Custom render function for options |

### ScaleQuestion Props

| Prop             | Type                      | Default    | Description                                 |
|------------------|---------------------------|------------|---------------------------------------------|
| min              | number                    | (required) | Minimum value of the scale                  |
| max              | number                    | (required) | Maximum value of the scale                  |
| step             | number                    | (required) | Step value for the scale                    |
| defaultValue     | number                    | 0          | Default value for the scale                 |
| minLabel         | string                    | undefined  | Label for the minimum value                 |
| maxLabel         | string                    | undefined  | Label for the maximum value                 |
| marks            | ScaleMark[]               | undefined  | Custom scale mark points                    |
| showNumberLabels | boolean                   | false      | Whether to show number labels on the scale  |
| currency         | CurrencyConfig            | undefined  | Configuration for currency formatting       |
| formatDisplay    | (value: number) => string | undefined  | Custom function to format the display value |
| selectedValues   | string[]                  | []         | Previously selected values                  |

#### CurrencyConfig

| Property | Type                 | Description                           |
|----------|----------------------|---------------------------------------|
| symbol   | string               | Currency symbol (e.g., 'USD', 'EUR')  |
| position | 'prefix' \| 'suffix' | Position of the currency symbol       |
| locale   | string               | Optional locale for number formatting |

#### ScaleMark

| Property | Type   | Description                            |
|----------|--------|----------------------------------------|
| value    | number | The value where the mark should appear |
| label    | string | The label to display at the mark       |

## Example

Here's an example of how to use the question components with data from the API:

```tsx
import React, {useState} from 'react';
import {
    SingleChoiceQuestion,
    MultiChoiceQuestion,
    ScaleQuestion
} from '@monarkmarkets/question-types';
import {QuestionnaireQuestionFormat} from '@monarkmarkets/api-client';

const QuestionRenderer = ({question, onAnswer}) => {
    // Render the appropriate question component based on question format
    switch (question.format) {
        case QuestionnaireQuestionFormat.Scale:
            return (
                <ScaleQuestion
                    options={question.options}
                    explanation={question.explanation}
                    min={question.scaleMin || 0}
                    max={question.scaleMax || 1000000}
                    step={question.scaleStep || 5000}
                    selectedValues={[]}
                    defaultValue={0}
                    minLabel="Minimum Investment"
                    maxLabel="Maximum Investment"
                    currency={{
                        symbol: 'USD',
                        position: 'prefix',
                        locale: 'en-US'
                    }}
                    onSelect={onAnswer}
                    showNumberLabels={true}
                />
            );

        case QuestionnaireQuestionFormat.MultipleChoiceMultiple:
            return (
                <MultiChoiceQuestion
                    options={question.options}
                    explanation={question.explanation}
                    selectedOptions={[]}
                    onSelect={onAnswer}
                />
            );

        case QuestionnaireQuestionFormat.MultipleChoiceSingle:
            return (
                <SingleChoiceQuestion
                    options={question.options}
                    explanation={question.explanation}
                    selectedOption={undefined}
                    onSelect={onAnswer}
                />
            );

        default:
            return <div>Unsupported question format</div>;
    }
};

// Example usage in a parent component
const QuestionnaireForm = () => {
    const [answers, setAnswers] = useState({});

    const handleAnswer = (questionId) => (selected) => {
        setAnswers(prev => ({
            ...prev,
            [questionId]: selected
        }));
    };

    // Sample question data (normally from API)
    const sampleQuestions = [
        {
            id: "q1",
            format: QuestionnaireQuestionFormat.MultipleChoiceSingle,
            text: "What is your investment experience?",
            explanation: "Please select your experience level",
            options: [
                "No prior investment experience",
                "Some experience with public markets",
                "Experienced with private investments",
                "Professional investor"
            ]
        },
        {
            id: "q2",
            format: QuestionnaireQuestionFormat.Scale,
            text: "How much would you like to invest?",
            explanation: "Drag the slider to select an amount",
            options: ["Investment Amount"],
            scaleMin: 10000,
            scaleMax: 1000000,
            scaleStep: 10000
        }
    ];

    return (
        <div className="questionnaire-form">
            <h1>Investment Questionnaire</h1>

            {sampleQuestions.map(question => (
                <div key={question.id} className="question-container">
                    <h3>{question.text}</h3>
                    <QuestionRenderer
                        question={question}
                        onAnswer={handleAnswer(question.id)}
                    />
                </div>
            ))}

            <button
                onClick={() => console.log('Submitted answers:', answers)}
                className="submit-button"
            >
                Submit
            </button>
        </div>
    );
};
```

## Development & Verification

1. `npm install`
2. `npm run build` — generates `dist/index.js`, `dist/index.esm.js`, and type declarations via Rollup.
3. `npx tsc --noEmit` — fast regression check that catches type issues without touching `dist/`.

## Dependencies

- `react` / `react-dom` `^18.2.0 || ^19.0.0` (peer deps)
- `@emotion/react` `11.14.x`
- `@emotion/styled` `11.14.x`
- `@mui/material` `7.3.x`
- `lodash` `4.17.21`

## License

Copyright © 2025 Monark Markets. All rights reserved.