React-Paket
Bau Opini direkt in deine React-App ein. Vier Stufen beim Styling, vom fertigen Widget bis ganz ohne Oberfläche. Normale Inputs, keine Feld-Komponenten: Dein Layout bleibt deins.
Installieren
# pnpm
pnpm add @opini-dev/react
# npm
npm install @opini-dev/reactReact 18 oder 19 als Peer-Dependency. Nur ESM. Sicher für Server-Rendering: Portale im schwebenden Modus werden erst nach dem ersten Effect im Client eingehängt.
Stufe 1: fertig zum Einbauen
Eine Komponente. Die Standard-Styles sind aktiv, es sieht also sofort wie ein Feedback-Widget aus.
import { OpiniWidget } from "@opini-dev/react"
export default function App() {
return <OpiniWidget projectKey="pk_live_xxx" />
}Gib unstyled an, wenn du bei null anfangen willst.
Stufe 2: Klassen pro Bereich
Gib den strukturellen Bereichen Klassen aus Tailwind, CSS Modules oder normalem CSS. Das Styling der Felder bleibt im HTML deines Formulars (siehe Stufe 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",
}}
/>Für reine CSS-Übergänge hat jeder Bereich data-state="open|closed" und data-loading="true|false". Mit Tailwind steuerst du Ein- und Ausblenden über data-[state=open]:scale-100.
Stufe 3: kombinierbare Teile
Bring deinen eigenen Button, deine Texte und dein Feld-Layout mit. Normale <input> und <textarea> mit name="message", "email" oder "name" sind der Vertrag für die Felder.
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>Stufe 4: useOpiniSubmit (ohne Oberfläche)
Wenn du den API-Client und die Zustandslogik ganz ohne Oberfläche willst.
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
Alle Props sind typisiert, dein Editor zeigt sie also beim Tippen an. Eine Prop customCss gibt es nicht: Dieses Feld im Dashboard gilt nur für das Widget per Script-Tag. In React stylst du mit dem, was du ohnehin nutzt.
Bereichsnamen
Die vollständige Liste: trigger, popover, arrow, close, form, actions, submit, success, msg. Jeder Bereich hat außerdem ein Attribut data-opini-part="…" für CSS-in-JS.
Feldnamen
<OpiniForm> liest das Formular beim Absenden über FormData. Erlaubt sind genau drei Felder:
name="message": Pflicht, der Feedback-Text.name="email": optional, wird geprüft, wenn vorhanden.name="name": optional, freier Text.
Alles andere wird vor dem POST still verworfen, du kannst also eigene Zustandsfelder hinzufügen, ohne die Daten zu verschmutzen.
SSR
Inhalte von Portalen im schwebenden Modus rendern erst nach dem ersten Effect im Client; im ersten HTML gibt es keine Portal-Ausgabe. Die Hydration tut nichts. Der Inline-Modus rendert direkt in deinen Baum, auf Server und Client.