npm package: @reviewhub/widget
Description: Drop-in reviews widget for any website — list, grid, carousel & badge layouts with dark mode, voice reviews and zero dependencies
Latest version: 0.1.0

# @reviewhub/widget

Drop-in reviews widget for any website.  
Works with **React, Vue, Angular, Svelte, Next.js, plain HTML** — no framework dependency.

**Layouts:** List &middot; Grid &middot; Carousel &middot; Badge  
**Features:** Dark mode &middot; Voice reviews &middot; Verified badges &middot; Rating distribution &middot; Responsive &middot; Auto-injected CSS

---

## Install

### npm / yarn / pnpm

```bash
npm install @reviewhub/widget
```

### CDN (script tag)

```html
<script src="https://unpkg.com/@reviewhub/widget/dist/index.global.js"></script>
```

---

## Usage

### With a bundler (React, Vue, Angular, Next.js, etc.)

```javascript
import { mountList, mountCarousel, mountGrid, mountBadge } from "@reviewhub/widget";

// List layout
mountList({
  container: "#reviews",
  sellerKey: "pk_your_seller_key",
  productId: "your-product-id",
});

// Carousel layout
mountCarousel({
  container: "#reviews-carousel",
  sellerKey: "pk_your_seller_key",
  productId: "your-product-id",
  primaryColor: "#7c3aed",
  showVoice: true,
});

// Grid layout
mountGrid({
  container: "#reviews-grid",
  sellerKey: "pk_your_seller_key",
  productId: "your-product-id",
});

// Compact badge (rating + count)
mountBadge({
  container: "#rating-badge",
  sellerKey: "pk_your_seller_key",
  productId: "your-product-id",
});
```

### With a script tag (vanilla HTML)

```html
<div id="reviews"></div>

<script src="https://unpkg.com/@reviewhub/widget/dist/index.global.js"></script>
<script>
  ReviewsSDK.mountCarousel({
    container: "#reviews",
    sellerKey: "pk_your_seller_key",
    productId: "your-product-id",
    theme: "light",
    primaryColor: "#4F46E5",
    showVoice: true,
    limit: 10,
  });
</script>
```

---

## React example

```jsx
import { useEffect, useRef } from "react";
import { mountCarousel } from "@reviewhub/widget";

export function Reviews({ productId }) {
  const ref = useRef(null);
  const widgetRef = useRef(null);

  useEffect(() => {
    if (!ref.current) return;
    widgetRef.current = mountCarousel({
      container: ref.current,
      sellerKey: "pk_your_seller_key",
      productId,
      primaryColor: "#7c3aed",
      showVoice: true,
    });
    return () => widgetRef.current?.destroy();
  }, [productId]);

  return <div ref={ref} />;
}
```

## Vue example

```vue
<template>
  <div ref="reviewsEl" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { mountList } from "@reviewhub/widget";

const reviewsEl = ref(null);
let widget = null;

onMounted(() => {
  widget = mountList({
    container: reviewsEl.value,
    sellerKey: "pk_your_seller_key",
    productId: "your-product-id",
  });
});

onUnmounted(() => widget?.destroy());
</script>
```

## Next.js example

```jsx
"use client";

import { useEffect, useRef } from "react";

export function ReviewsSection({ productId }) {
  const ref = useRef(null);
  const widgetRef = useRef(null);

  useEffect(() => {
    // Dynamic import keeps the widget out of the server bundle
    import("@reviewhub/widget").then(({ mountCarousel }) => {
      if (!ref.current || widgetRef.current) return;
      widgetRef.current = mountCarousel({
        container: ref.current,
        sellerKey: "pk_your_seller_key",
        productId,
        primaryColor: "#7c3aed",
        showVoice: true,
      });
    });
    return () => {
      widgetRef.current?.destroy();
      widgetRef.current = null;
    };
  }, [productId]);

  return <div ref={ref} className="w-full max-w-4xl mx-auto" />;
}
```

---

## API

### Mount functions

| Function | Layout | Description |
|----------|--------|-------------|
| `mountWidget(options)` | any | Mount with explicit `layout` option |
| `mountList(options)` | list | Vertical list of review cards |
| `mountGrid(options)` | grid | Responsive grid (auto-fill, min 280px) |
| `mountCarousel(options)` | carousel | Swipeable carousel with arrows, dots & autoplay |
| `mountBadge(options)` | badge | Compact inline badge (stars + rating + count) |

All mount functions return a `ReviewWidget` instance.

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `container` | `string \| HTMLElement` | — | CSS selector or DOM element to mount into |
| `sellerKey` | `string` | — | Your public seller API key (`pk_...`) |
| `productId` | `string` | — | Product ID to show reviews for |
| `theme` | `"light" \| "dark"` | `"light"` | Color theme |
| `primaryColor` | `string` | `"#4F46E5"` | Accent color for stars, bars and highlights |
| `limit` | `number` | `10` | Max reviews to fetch |
| `showVoice` | `boolean` | `true` | Show audio player for voice reviews |
| `layout` | `"list" \| "grid" \| "carousel" \| "badge"` | `"list"` | Layout style (only for `mountWidget`) |
| `apiBaseUrl` | `string` | `"https://api.reviewhub.dev/api"` | API base URL |

### `ReviewWidget` instance

```javascript
const widget = mountCarousel({ /* ... */ });

// Remove widget and clean up
widget.destroy();
```

---

## Advanced: use the SDK directly

The widget re-exports the full SDK client for custom integrations:

```typescript
import { ReviewHubClient } from "@reviewhub/widget";

const client = new ReviewHubClient({
  sellerKey: "pk_your_seller_key",
});

const { data: reviews } = await client.getProductReviews({
  productId: "abc123",
  limit: 5,
});

const summary = await client.getProductSummary("abc123");
```

Or install the standalone SDK: `npm install @reviewhub/sdk`

---

## Styling

CSS is **auto-injected** — no stylesheet import needed. All classes are prefixed with `rh-` to avoid conflicts.

To customize beyond `primaryColor` and `theme`, override any `.rh-*` class in your own CSS:

```css
.rh-review {
  border-radius: 12px;
  border-color: #e2e8f0;
}

.rh-avg-rating {
  font-size: 56px;
}
```

---

## Browser support

Works in all modern browsers (Chrome, Firefox, Safari, Edge).  
The IIFE build (`index.global.js`) targets ES2022.

## License

MIT