# @opengovsg/confetti

Embeddable React survey widget for collecting user feedback.

[![npm version](https://img.shields.io/npm/v/@opengovsg/confetti.svg)](https://www.npmjs.com/package/@opengovsg/confetti)

## Installation

```bash
# npm
npm install @opengovsg/confetti

# yarn
yarn add @opengovsg/confetti

# pnpm
pnpm add @opengovsg/confetti
```

### Peer Dependencies

This package requires React 18.2.0+ or React 19:

```bash
npm install react@^18.2.0
```

## Quick Start

1. Add the widget to your page:

```tsx
import { PopoverConfetti } from '@opengovsg/confetti'

function App() {
  return (
    <div style={{ position: 'fixed', bottom: '1rem', right: '1rem' }}>
      <PopoverConfetti
        surveyId="your-survey-id"
        publishableKey="your-publishable-key"
        metadata={{}}
      />
    </div>
  )
}
```

The template components load their script bundle and stylesheet from the
Confetti CDN at runtime, so there's **no CSS to import** — including the
host-rendered trigger button, which `TriggerPopoverConfetti` styles itself. (The
lower-level [`@opengovsg/confetti/components`](#building-blocks) primitives and
the `@opengovsg/confetti/static` opt-out surface render in your own bundle and
still require `import '@opengovsg/confetti/confetti.css'`.)

> **Note:** You'll need the survey ID and publishable key from your Confetti dashboard. Make sure to whitelist your domain in your team settings.

2. Add Confetti to your CSP headers. The widget loads its script and stylesheet from the CDN and calls the API, so allowlist all three:

```
script-src https://confetti.gov.sg;
style-src https://confetti.gov.sg;
connect-src https://confetti.gov.sg;
```

### Proxying requests

Every template component accepts an optional `proxyUrl`. When set, the widget
sends its API requests to `{proxyUrl}/api/v1/cfti/...` instead of
`https://confetti.gov.sg`, and the CDN-backed shells also load their script
and stylesheet from `{proxyUrl}/widget/v1/...`. Use this to route widget
traffic through your own origin, for example when your CSP disallows third
party hosts.

## Template Components

Choose the component that best fits your use case:

| Component                | Use Case                          | Behavior                                                                              |
| ------------------------ | --------------------------------- | ------------------------------------------------------------------------------------- |
| `EmbeddedConfetti`       | Inline surveys, feedback sections | Shows questions in an inline manner with a submit button                              |
| `ModalConfetti`          | Interruptive, centered surveys    | Centered modal overlay, step-by-step questions, auto-submits                          |
| `PopoverConfetti`        | Floating feedback widgets         | Dismissible popover, step-by-step questions, auto-submits                             |
| `TriggerPopoverConfetti` | Feedback launcher button          | Renders a trigger button that opens a dismissible popover survey on click; reopenable |
| `StepperConfetti`        | Guided surveys                    | One question at a time, auto-submits after last question                              |

## Static Components

The `@opengovsg/confetti/static` subpath ships self-contained builds of the
same template components (`EmbeddedConfetti`, `ModalConfetti`,
`PopoverConfetti`, `StepperConfetti`) plus the `ConfettiTrigger` composition
helper. They render entirely from your own bundle with no runtime CDN
dependency, so survey rendering updates only reach your site when you upgrade
the package.

```tsx
import '@opengovsg/confetti/confetti.css'

import { PopoverConfetti } from '@opengovsg/confetti/static'
```

Unlike the CDN-backed shells, the static components require the stylesheet
import shown above.

## Visibility Hooks

Control when your survey appears with these hooks:

### useVisibleAfterDelay

Show the survey after a delay:

```tsx
import { useVisibleAfterDelay } from '@opengovsg/confetti'

const { isVisible } = useVisibleAfterDelay({ delay: 5000 }) // 5 seconds
```

### useVisibleAfterScroll

Show the survey after scrolling a certain distance:

```tsx
import { useVisibleAfterScroll } from '@opengovsg/confetti'

const { isVisible } = useVisibleAfterScroll({ threshold: 500 }) // 500px
```

### useVisibleAfterPageVisits

Show the survey once the visitor has loaded the current page a given number of times (persisted across sessions via `localStorage`):

```tsx
import { useVisibleAfterPageVisits } from '@opengovsg/confetti'

const { isVisible, reset } = useVisibleAfterPageVisits({
  visits: 3,
  respondent: 'optional-respondent-identifier',
})
```

### useVisibleAfterSessionPageVisits

Same as above, but the visit count is scoped to the current browser session via `sessionStorage`:

```tsx
import { useVisibleAfterSessionPageVisits } from '@opengovsg/confetti'

const { isVisible, reset } = useVisibleAfterSessionPageVisits({ visits: 3 })
```

## Building Blocks

The lower-level primitives that power the [template components](#template-components) are published from the `@opengovsg/confetti/components` subpath. Reach for these to compose your own survey UI.

```tsx
import {
  ConfettiController,
  ConfettiProvider,
  SurveyContext,
  useSurvey,
} from '@opengovsg/confetti/components'
```

Unlike the template components, these primitives render in your own bundle, so
import the stylesheet once in your app entry point:

```tsx
import '@opengovsg/confetti/confetti.css'
```
