fix(surveys): one-row rating scale, rating auto-advance, mobile spacing
Design review of the live survey popover surfaced three issues: 1. NPS 0–10 wrapped to two rows (flex-wrap + flex-1 made the 8/9/10 row stretch absurdly wide) — now a single equal-width grid row (grid-cols-N by scale; static map so Tailwind keeps the classes). 2. Single-question ratings forced a redundant "Devam" tap — tapping a score now auto-advances (450ms debounce to allow changing the pick) and the submit button is hidden on rating questions. 3. On mobile the card crowded the Chatwoot launcher — bumped to bottom-28 (sm:bottom-24 keeps desktop unchanged). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
import { Button } from "@sase/ui";
|
||||
import { X } from "lucide-react";
|
||||
import type { Survey } from "posthog-js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// Self-rendered PostHog survey popover (API-mode surveys). Question copy comes
|
||||
// from the survey definition in PostHog; this component only knows how to draw
|
||||
@@ -16,6 +16,16 @@ import { useEffect, useState } from "react";
|
||||
|
||||
const OTHER_CHOICE = "__other__";
|
||||
|
||||
// Rating scales render as one equal-width row. Static map so Tailwind's JIT
|
||||
// keeps these classes — a template-literal `grid-cols-${n}` would get purged.
|
||||
// (NPS = 0–10 → 11 buttons; CSAT/CES → 3/5/7.)
|
||||
const RATING_COLS: Record<number, string> = {
|
||||
3: "grid-cols-3",
|
||||
5: "grid-cols-5",
|
||||
7: "grid-cols-7",
|
||||
11: "grid-cols-11",
|
||||
};
|
||||
|
||||
export function SurveyPopover() {
|
||||
const [survey, setSurvey] = useState<Survey | null>(null);
|
||||
useEffect(() => subscribeActiveSurvey(setSurvey), []);
|
||||
@@ -31,6 +41,7 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
const [openText, setOpenText] = useState("");
|
||||
const [rating, setRating] = useState<number | null>(null);
|
||||
const [done, setDone] = useState(false);
|
||||
const advanceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const questions = survey.questions;
|
||||
const q = questions[step];
|
||||
@@ -41,6 +52,9 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
return () => clearTimeout(t);
|
||||
}, [done]);
|
||||
|
||||
// Cancel a pending rating auto-advance if the card unmounts mid-debounce.
|
||||
useEffect(() => () => clearTimeout(advanceTimer.current ?? undefined), []);
|
||||
|
||||
const qid = q?.id ?? `q${step}`;
|
||||
const choices = q && "choices" in q && Array.isArray(q.choices) ? q.choices : [];
|
||||
const hasOpenChoice = Boolean(q && "hasOpenChoice" in q && q.hasOpenChoice);
|
||||
@@ -69,8 +83,7 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
: { ...answers, [qid]: value };
|
||||
}
|
||||
|
||||
function advance() {
|
||||
const next = collect();
|
||||
function proceed(next: SurveyResponses) {
|
||||
setAnswers(next);
|
||||
if (step + 1 < questions.length) {
|
||||
setStep(step + 1);
|
||||
@@ -84,6 +97,19 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
setDone(true);
|
||||
}
|
||||
|
||||
function advance() {
|
||||
proceed(collect());
|
||||
}
|
||||
|
||||
// Rating questions auto-advance — tapping a score IS the answer, so there's no
|
||||
// separate submit button. A short debounce lets the user change their pick
|
||||
// (and re-tapping clears the prior timer) before the card moves on.
|
||||
function selectRating(v: number) {
|
||||
setRating(v);
|
||||
clearTimeout(advanceTimer.current ?? undefined);
|
||||
advanceTimer.current = setTimeout(() => proceed({ ...answers, [qid]: v }), 450);
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
if (done) {
|
||||
closeActiveSurvey();
|
||||
@@ -101,7 +127,7 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
return (
|
||||
<section
|
||||
aria-label={survey.name}
|
||||
className="fixed bottom-24 right-4 z-[70] w-[min(22rem,calc(100vw-2rem))] rounded-xl border border-border bg-card p-5 text-card-foreground shadow-2xl"
|
||||
className="fixed bottom-28 right-4 z-[70] w-[min(22rem,calc(100vw-2rem))] rounded-xl border border-border bg-card p-5 text-card-foreground shadow-2xl sm:bottom-24"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -177,13 +203,13 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
|
||||
{q.type === "rating" ? (
|
||||
<div className="mt-3">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<div className={`grid gap-1 ${RATING_COLS[ratingValues.length] ?? "grid-cols-5"}`}>
|
||||
{ratingValues.map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
onClick={() => setRating(v)}
|
||||
className={`h-8 min-w-8 flex-1 rounded-md border text-sm transition-colors ${
|
||||
onClick={() => selectRating(v)}
|
||||
className={`flex h-9 items-center justify-center rounded-md border text-xs transition-colors ${
|
||||
rating === v
|
||||
? "border-brand bg-brand text-brand-foreground"
|
||||
: "border-border text-muted-foreground hover:border-foreground/30 hover:text-foreground"
|
||||
@@ -212,9 +238,16 @@ function SurveyCard({ survey }: { survey: Survey }) {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Button variant="brand" className="mt-4 w-full" disabled={!canContinue} onClick={advance}>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
{q.type !== "rating" ? (
|
||||
<Button
|
||||
variant="brand"
|
||||
className="mt-4 w-full"
|
||||
disabled={!canContinue}
|
||||
onClick={advance}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user