npm package: @oliv-e/vitals-widget
Description: Componentes React do iFrame do Vitals Widget
Latest version: 0.1.9
Deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

# @oliv-e/vitals-widget

Biblioteca de componentes React e widget embutível (Custom Element) para iniciar a medição de sinais vitais via iFrame. Este projeto entrega:

- Lib npm para React/TypeScript (ESM + CJS + tipos .d.ts)
- Bundle standalone para integração via SCRIPT TAG (<vitals-widget />)


## Sumário

- Visão Geral
- Estrutura do projeto
- Uso como biblioteca React (npm)
- Uso via SCRIPT TAG (widget standalone)
- Atributos (obrigatórios e opcionais)
- Build, testes e publicação
- Tipos exportados
- Eventos e comunicação
- Configuração de estilos (Tailwind/CSS)
- Dicas de troubleshooting


## Visão Geral

- A lib React expõe os componentes que constroem um iFrame apontando para `https://vitals.oliv-e.health/widget/` e fazem o handshake com `postMessage + MessageChannel` para receber resultados.
- O bundle standalone disponibiliza o Custom Element `<vitals-widget>` para integrar via SCRIPT TAG sem React.


## Estrutura do projeto

```
.
├── dist/                      # Artefatos de build (lib + widget standalone)
├── src/
│   ├── index.ts               # Entry da lib (reexporta componentes e tipos)
│   └── widget/
│       ├── components/
│       │   ├── widget.tsx             # Componente que renderiza o iFrame e o handshake
│       │   └── widget-container.tsx   # Provider + composição do widget
│       │   └── widget-sdk.tsx         # Provider + composição do widget para aplicações React
│       ├── lib/
│       │   ├── context.ts             # Contexto do widget
│       │   ├── types.ts               # Tipos públicos exportados
│       │   └── use-widget-context.ts  # Hook utilitário
│       ├── styles/
│       │   └── style.css              # Estilos do widget (Tailwind @apply)
│       ├── custom-element.tsx         # Definição do <vitals-widget /> (Shadow DOM)
│       └── index.tsx                  # Entry para o bundle standalone
├── test/
│   └── index.html              # Página de teste local do widget standalone
├── rollup.config.mjs           # Build do widget standalone (IIFE)
├── vite.config.lib.ts          # Build da lib (ESM/CJS + d.ts)
├── tailwind.config.cjs         # Configuração Tailwind para build do CSS
├── postcss.config.cjs          # PostCSS + autoprefixer
├── .env.development            # Variáveis para build:widget ambiente dev
├── .env.production             # Variáveis para build:widget ambiente prod
├── package.json
└── README.md
```


## Uso como biblioteca React (npm)

Instalação (após publicação):

```bash
pnpm add @oliv-e/vitals-widget
```

Exemplo recomendado (default export):

```tsx
import VitalsWidget from '@oliv-e/vitals-widget';

export default function Page() {
  return (
    <VitalsWidget
      clientKey=""
      birthyear={1999}
      height={170}
      weight={65}
      sex={1}
      bp_group="primary"
      bp_mode="normal"
      facing_mode="user"
    />
  );
}
```

Notas:
- Obrigatório: clientKey (identificador público do parceiro).
- Opcionais: birthyear, height, weight, sex, bp_group, bp_mode, facing_mode, initScan, scanOnly.
- sex: 0 = female, 1 = male.
- facing_mode: 'user' | 'environment' (padrão: 'user').
- scanOnly: quando true, o iFrame renderiza somente vídeo+canvas (sem botões/overlays) e delega o início (InitScan) e o redirecionamento de sucesso ao parent. O parent deve enviar o comando InitScan via MessageChannel e tratar o evento OnSuccess.

Exemplo mínimo (somente clientKey):

```tsx
import VitalsWidget from '@oliv-e/vitals-widget';

export default function Page() {
  return <VitalsWidget clientKey="your_client_key" />;
}
```

CSS
- O pacote exporta um CSS compilado (dist/vitals-widget.css). Na maioria dos bundlers, importar a lib já injeta o CSS. Caso seu bundler não carregue CSS de node_modules automaticamente, importe explicitamente:

```ts
import '@oliv-e/vitals-widget/dist/vitals-widget.css';
```

Uso avançado (opcional) com WidgetContainer
- Caso você já possua o trio (client, name, theme), é possível montar o widget diretamente:

```tsx
import { WidgetContainer } from '@oliv-e/vitals-widget';

export default function Page() {
  return (
    <WidgetContainer
      client="..."
      name="..."
      theme="standard"
      birthyear={1999}
      height={170}
      weight={65}
      sex={1}
      bp_group="primary"
      bp_mode="normal"
      facing_mode="user"
    />
  );
}
```

Tipos exportados úteis:

```ts
import type {
  VitalsConfig,
  Sex,
  FacingMode,
  BPMode,
  BPGroup,
  WidgetEventMap,
  WidgetSDK as WidgetSDKProps
} from '@oliv-e/vitals-widget';
```

Observação: o componente React padrão exportado chama-se `WidgetSDK`; no exemplo acima utilizamos o nome `VitalsWidget` para evitar colisão com o nome do tipo `WidgetSDK`.


## Uso via SCRIPT TAG (widget standalone)

Build do widget (dev/prod):

```bash
pnpm build:widget
pnpm build:widget:production
```

Servir e testar:

```bash
pnpm serve:widget   # serve ./dist na porta 3334
pnpm serve          # serve ./test na porta 3333
```

Abra `http://localhost:3333/test/index.html` — o HTML de teste carrega `http://localhost:3334/widget.js` e instância o Custom Element:

```html
<script async src="http://localhost:3334/widget.js"></script>
<vitals-widget
  client-key=""
  birthyear="1999"
  height="175"
  weight="55"
  sex="1"
  bp-group="primary"
  bp-mode="normal"
  facing-mode="user"
/>
```

Nota: obrigatório: client-key. Opcionais: birthyear, height, weight, sex, bp-group, bp-mode, facing-mode (padrão: 'user').

Exemplo mínimo (somente client-key):

```html
<script async src="http://localhost:3334/widget.js"></script>
<vitals-widget client-key="your_client_key"></vitals-widget>
```

Publicação em CDN (ex.: jsDelivr):

```html
<script async src="https://cdn.jsdelivr.net/npm/@oliv-e/vitals-widget@<versao>/dist/widget.js"></script>
```

Nota: o CSS do widget é carregado automaticamente dentro do Shadow DOM com base no nome do script (`widget.js` → `widget.css`). Se precisar forçar um caminho absoluto, defina a variável de ambiente `WIDGET_CSS_URL` durante o build do bundle standalone.

### Variáveis de ambiente do widget standalone
- `WIDGET_NAME`: nome do arquivo JS de saída (ex.: `vitals-widget.js`)
- `WIDGET_CSS_URL` (opcional): URL absoluta do CSS; se não definido, o CSS é resolvido dinamicamente com base na URL do script atual.


## Build, testes e publicação

Scripts principais:

- `pnpm build`            → build da lib (ESM, CJS, d.ts) via `vite.config.lib.ts`
- `pnpm build:lib`        → build somente da lib
- `pnpm build:widget`     → build do bundle standalone (IIFE)
- `pnpm build:widget:production` → build do bundle standalone em modo prod (.env.production)
- `pnpm serve`            → serve página de teste `./test` (porta 3333)
- `pnpm serve:widget`     → serve `./dist` (porta 3334)
- `pnpm typecheck`        → checagem de tipos
- `pnpm lint` / `pnpm format`

Publicação no npm (escopo @oliv-e):

1) Crie/tenha acesso à organização `oliv-e` no npm (reserva o escopo `@oliv-e`).
2) Garanta login e 2FA conforme sua política.
3) O package já possui:
   - `"files": ["dist"]`
   - `"types": "./dist/index.d.ts"`
   - `"exports"` para ESM/CJS/Types
   - `"prepublishOnly": "pnpm run build && pnpm run build:widget"`
4) Publique:

```bash
npm publish --access public
# ou, se 2FA write:
# npm publish --access public --otp <codigo>
```


## Atributos (obrigatórios e opcionais)

React (componente) vs Custom Element (HTML)
- clientKey (React) / client-key (Custom Element) – obrigatório. Identificador público do parceiro. Não inclua segredos.
- birthyear – opcional. Ano de nascimento (number no React; string numérica no HTML).
- height – opcional. Altura em cm (number no React; string no HTML).
- weight – opcional. Peso em kg (number no React; string no HTML).
- sex – opcional. 0 = female, 1 = male.
- bp_group – opcional. 'primary' | 'normal' | 'prehypertension' | 'hypertension'.
- bp_mode – opcional. 'normal' | 'binary' | 'ternary'.
- facing_mode (React) / facing-mode (Custom Element) – opcional. Padrão: 'user'.
- initScan – opcional. Padrão: false. Quando true, dispara InitScan ao receber OnReady do iFrame.

Notas
- Parâmetros são enviados ao iFrame apenas quando definidos; valores ausentes não são incluídos na query string.

## Tipos exportados

```ts path=/Users/venusmiloterra/Desktop/Projects/Oliv-e/vitals-widget/vitals-sdk/src/widget/lib/types.ts start=1
// Tipos base exportados pela biblioteca
export type Sex = 0 | 1; // 0=female, 1=male
export type FacingMode = 'user' | 'environment';

// Mantemos valores mais amplos para compatibilidade com integrações existentes
export type BPMode = 'normal' | 'binary' | 'ternary';
export type BPGroup = 'primary' | 'normal' | 'prehypertension' | 'hypertension';

export interface VitalsConfig {
  client: string;
  name: string;
  theme: string;
  height: number;
  weight: number;
  birthyear: number;
  sex: Sex;
  bp_mode: BPMode;
  bp_group: BPGroup;
  facing_mode: FacingMode;
}

export type WidgetEventName = 'vh:handshake' | 'vh:result' | 'vh:dispose';
export interface HandshakePayload { ok: boolean }
export type ResultPayload = unknown;

export interface WidgetEventMap {
  'vh:handshake': CustomEvent<HandshakePayload>;
  'vh:result': CustomEvent<ResultPayload>;
  'vh:dispose': CustomEvent<void>;
}

export interface WidgetContainerProps extends VitalsConfig {}
export interface WidgetProps {}
```


## Eventos e comunicação

Comandos imperativos (via eventos de janela):
- vh:startScan → envia InitScan ao iFrame (use quando scanOnly=true para iniciar sob demanda)
- vh:stopScan → envia StopScan ao iFrame

Exemplo:
```ts
window.dispatchEvent(new Event('vh:startScan'));
```

O componente React `<Widget />` cria um `MessageChannel` com o iFrame. O iFrame envia mensagens que são propagadas para `window` como eventos customizados:

- `vh:handshake` – canal pronto
- `vh:result` – resultados de medição (payload depende do backend)
- `vh:dispose` – canal encerrado

Exemplo de listener no app host:

```ts path=null start=null
useEffect(() => {
  function onResult(e: CustomEvent) {
    console.log('Resultado:', e.detail);
  }
  window.addEventListener('vh:result' as any, onResult as any);
  return () => window.removeEventListener('vh:result' as any, onResult as any);
}, []);
```


## Configuração de estilos (Tailwind/CSS)

- Os estilos do widget são gerados em build a partir de `src/widget/styles/style.css` (usa `@apply`).
- `tailwind.config.cjs` define `corePlugins.preflight = false` para evitar reset global.
- A lib exporta `dist/vitals-widget.css`. Se o host não importar CSS de node_modules automaticamente, importe manualmente.


## Dicas de troubleshooting

- `Scope not found` ao publicar: crie a org `oliv-e` no npm ou use seu escopo de usuário.
- CSS em `dist/dist/...`: já corrigido; o build agora gera `dist/vitals-widget.css`.
- Tipos não aparecem no pacote: rode `pnpm build` antes do publish (gera `dist/index.d.ts`).
- SSR: os acessos a `window` ocorrem dentro de efeitos; evite renderizar o iFrame no server.


## Licença

MIT. Veja [LICENSE](./LICENSE).