Package React
Intégrez Opini directement dans votre app React. Quatre niveaux de style, du prêt à l'emploi au totalement sans interface. Des champs HTML simples, sans composants de champ : votre mise en page reste la vôtre.
Installer
# pnpm
pnpm add @opini-dev/react
# npm
npm install @opini-dev/reactReact 18 ou 19 en peer dependency. ESM uniquement. Compatible avec le rendu serveur : les portails du mode flottant ne se montent qu'après le premier effet côté client.
Niveau 1 : prêt à l'emploi
Un seul composant. Les styles par défaut sont actifs, il ressemble donc tout de suite à un widget de feedback.
import { OpiniWidget } from "@opini-dev/react"
export default function App() {
return <OpiniWidget projectKey="pk_live_xxx" />
}Passez unstyled pour partir d'une page blanche.
Niveau 2 : classes par partie
Appliquez des classes Tailwind, CSS Modules ou CSS classique aux parties structurelles. Le style des champs reste dans le HTML de votre formulaire (voir le niveau 3).
<OpiniWidget
projectKey="pk_live_xxx"
unstyled
className="fixed bottom-6 right-6"
classNames={{
trigger: "rounded-full bg-black px-5 py-2.5 text-sm text-white",
popover: "w-96 rounded-2xl border bg-white p-5 shadow-xl",
submit: "rounded-md bg-violet-600 px-3 py-2 text-white hover:bg-violet-700",
}}
/>Pour des transitions en CSS seul, chaque partie expose data-state="open|closed" et data-loading="true|false". Avec Tailwind, branchez l'entrée et la sortie avec data-[state=open]:scale-100.
Niveau 3 : composants combinables
Utilisez votre propre bouton, vos textes et votre mise en page. Des <input> et <textarea> simples avec name="message", "email" ou "name" forment le contrat des champs.
import {
OpiniWidgetRoot,
OpiniTrigger,
OpiniPopover,
OpiniForm,
OpiniSubmit,
OpiniSuccess,
} from "@opini-dev/react"
<OpiniWidgetRoot projectKey="pk_live_xxx">
<OpiniTrigger asChild>
<MyBrandedButton>Send feedback →</MyBrandedButton>
</OpiniTrigger>
<OpiniPopover className="my-popover">
<h2>Pitch a chapter.</h2>
<OpiniForm className="space-y-3">
<textarea
name="message"
placeholder="Albums that auto-curate themselves on a trip…"
/>
<input name="name" placeholder="name (optional)" />
<input name="email" type="email" placeholder="email" />
<OpiniSubmit asChild>
{({ status }) => (
<MyBrandedButton variant="primary" disabled={status === "submitting"}>
{status === "submitting" ? "Sending…" : "SEND IT →"}
</MyBrandedButton>
)}
</OpiniSubmit>
</OpiniForm>
<OpiniSuccess>You'll hear back. Promise.</OpiniSuccess>
</OpiniPopover>
</OpiniWidgetRoot>Niveau 4 : useOpiniSubmit (sans interface)
Quand vous voulez le client d'API et la machine à états sans aucune interface.
import { useOpiniSubmit } from "@opini-dev/react"
function MyOwnForm() {
const { submit, status, error } = useOpiniSubmit({ projectKey: "pk_live_xxx" })
return (
<form
onSubmit={(e) => {
e.preventDefault()
const fd = new FormData(e.currentTarget)
void submit({
text: String(fd.get("message") ?? ""),
email: fd.get("email") ? String(fd.get("email")) : undefined,
})
}}
>
<textarea name="message" />
<input name="email" />
<button disabled={status === "submitting"}>Send</button>
{error ? <p>{error.message}</p> : null}
</form>
)
}Props
Toutes les props sont typées : votre éditeur les liste pendant la saisie. Il n'y a pas de prop customCss : ce champ du tableau de bord ne concerne que le widget en balise script. En React, vous stylez avec vos outils habituels.
Noms des parties
La liste complète : trigger, popover, arrow, close, form, actions, submit, success, msg. Chaque partie porte aussi un attribut data-opini-part="…" pour le CSS-in-JS.
Contrat des champs
<OpiniForm> lit le formulaire via FormData à l'envoi. Exactement trois champs sont acceptés :
name="message": obligatoire, le texte du retour.name="email": facultatif, validé s'il est présent.name="name": facultatif, texte libre.
Tout le reste est ignoré avant le POST : vous pouvez ajouter vos propres champs d'état sans polluer l'envoi.
SSR
Le contenu des portails du mode flottant ne s'affiche qu'après le premier effet côté client ; le HTML initial ne contient aucun portail. L'hydratation ne fait rien. Le mode inline s'affiche directement dans votre arbre, côté serveur comme côté client.