mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
feat(coach): make the full Coach page a single full-bleed surface
Drop the inner bordered card on the dedicated /coach page so the chat reads as one continuous conversation rather than a window inside a window, and remove the top "back to the drawer" minimize control (it is pointless on the full page; the bottom-right FAB drawer remains the separate compact surface). Also remove the two starter-question suggestion chips below the new-chat composer, leaving a greeting + composer hero that matches the reference new-chat layout. History stays reachable through the existing conversation rail.
This commit is contained in:
1 parent
fa37941add
commit
01b73f6379
4 files changed
+56
-152
No files matched your search
@@ -12,13 +12,9 @@ function render(node: React.ReactNode, locale: "en" | "de" = "en") {
|
||||
}
|
||||
|
||||
describe("<CoachHero>", () => {
|
||||
it("renders the centred greeting, the composer slot, and the chips", () => {
|
||||
it("renders the centred greeting and the composer slot", () => {
|
||||
const html = render(
|
||||
<CoachHero
|
||||
composer={<div data-slot="test-composer">composer</div>}
|
||||
prompts={["What should I tell my doctor?", "Is my medication working?"]}
|
||||
onPickPrompt={() => {}}
|
||||
/>,
|
||||
<CoachHero composer={<div data-slot="test-composer">composer</div>} />,
|
||||
);
|
||||
expect(html).toContain('data-slot="coach-hero"');
|
||||
// Greeting copy from insights.coach.heroGreeting.
|
||||
@@ -26,26 +22,19 @@ describe("<CoachHero>", () => {
|
||||
// The composer is re-parented into the hero, not forked.
|
||||
expect(html).toContain('data-slot="coach-hero-composer"');
|
||||
expect(html).toContain('data-slot="test-composer"');
|
||||
// Both starter-question chips render.
|
||||
const chips = (html.match(/data-slot="coach-hero-chip"/g) ?? []).length;
|
||||
expect(chips).toBe(2);
|
||||
expect(html).toContain("What should I tell my doctor?");
|
||||
expect(html).toContain("Is my medication working?");
|
||||
});
|
||||
|
||||
it("renders the German greeting under the de locale", () => {
|
||||
const html = render(
|
||||
<CoachHero composer={null} prompts={[]} onPickPrompt={() => {}} />,
|
||||
"de",
|
||||
);
|
||||
const html = render(<CoachHero composer={null} />, "de");
|
||||
expect(html).toContain("Wie kann ich dir helfen?");
|
||||
});
|
||||
|
||||
it("omits the chip row when no prompts are supplied", () => {
|
||||
const html = render(
|
||||
<CoachHero composer={null} prompts={[]} onPickPrompt={() => {}} />,
|
||||
);
|
||||
it("does not render starter-question suggestion chips", () => {
|
||||
// v1.18.10 (W4) — the two starter chips below the composer were
|
||||
// removed; the hero is greeting + composer only.
|
||||
const html = render(<CoachHero composer={null} />);
|
||||
expect(html).not.toContain('data-slot="coach-hero-chips"');
|
||||
expect(html).not.toContain('data-slot="coach-hero-chip"');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -323,14 +323,6 @@ export function CoachConversation({
|
||||
pendingQuestions.length === 0 &&
|
||||
!pendingAdopt;
|
||||
|
||||
// The Coach's own starter questions seed the hero chips (NOT generic
|
||||
// "create an image" prompts). Same two openers the suggested-prompt strip
|
||||
// uses elsewhere, resolved here so the hero stays locale-agnostic.
|
||||
const heroPrompts = [
|
||||
t("insights.suggestedPrompts.tellMyDoctor"),
|
||||
t("insights.suggestedPrompts.medicationWorking"),
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="coach-conversation"
|
||||
@@ -407,11 +399,7 @@ export function CoachConversation({
|
||||
</header>
|
||||
|
||||
{heroActive ? (
|
||||
<CoachHero
|
||||
composer={composerNode}
|
||||
prompts={heroPrompts}
|
||||
onPickPrompt={(prompt) => handleSubmit(prompt)}
|
||||
/>
|
||||
<CoachHero composer={composerNode} />
|
||||
) : (
|
||||
<CoachDrawerBody
|
||||
historyOpen={historyRailOpen}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Quote, Sparkles } from "lucide-react";
|
||||
import { Sparkles } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
@@ -8,33 +8,32 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
/**
|
||||
* v1.18.9 — the Coach new-chat hero (page surface only).
|
||||
*
|
||||
* A centred greeting with the composer directly beneath it and a row of
|
||||
* HealthLog's own starter questions, floated in generous negative space
|
||||
* over a faint purple atmospheric backdrop. This replaces the cramped
|
||||
* bottom-pinned empty thread with a calm, ChatGPT/Claude-grade prompt
|
||||
* surface. On first send the conversation branch takes over and the
|
||||
* composer docks to the bottom (handled by `<CoachConversation>`).
|
||||
* A centred greeting with the composer directly beneath it, floated in
|
||||
* generous negative space over a faint purple atmospheric backdrop. This
|
||||
* replaces the cramped bottom-pinned empty thread with a calm,
|
||||
* ChatGPT/Claude-grade prompt surface. On first send the conversation
|
||||
* branch takes over and the composer docks to the bottom (handled by
|
||||
* `<CoachConversation>`).
|
||||
*
|
||||
* The hero does NOT fork composer logic: it RECEIVES the live
|
||||
* `<CoachInput>` as the `composer` slot, so dictation, auto-grow, the
|
||||
* send/stop control, and the guided-question placeholder all behave
|
||||
* exactly as in the docked composer. The suggestion chips seed the same
|
||||
* composer via `onPickPrompt`.
|
||||
* exactly as in the docked composer.
|
||||
*
|
||||
* Motion: one orchestrated reveal — sparkle → greeting → composer → chips
|
||||
* stagger in via `motion-safe` `animate-in` with capped delays;
|
||||
* reduced-motion users get the layout instantly.
|
||||
* v1.18.10 (W4) — the starter-question suggestion chips below the composer
|
||||
* are removed (maintainer feedback): the hero is greeting + composer only,
|
||||
* matching the reference ChatGPT/Claude new-chat surface.
|
||||
*
|
||||
* Motion: one orchestrated reveal — sparkle → greeting → composer stagger
|
||||
* in via `motion-safe` `animate-in` with capped delays; reduced-motion
|
||||
* users get the layout instantly.
|
||||
*/
|
||||
export interface CoachHeroProps {
|
||||
/** The live `<CoachInput>` composer, re-parented into the hero. */
|
||||
composer: React.ReactNode;
|
||||
/** Localised starter prompts shown as chips below the composer. */
|
||||
prompts: string[];
|
||||
/** Seeds the composer with a chip's text (the parent submits on pick). */
|
||||
onPickPrompt: (prompt: string) => void;
|
||||
}
|
||||
|
||||
export function CoachHero({ composer, prompts, onPickPrompt }: CoachHeroProps) {
|
||||
export function CoachHero({ composer }: CoachHeroProps) {
|
||||
const { t } = useTranslations();
|
||||
|
||||
return (
|
||||
@@ -93,41 +92,6 @@ export function CoachHero({ composer, prompts, onPickPrompt }: CoachHeroProps) {
|
||||
>
|
||||
{composer}
|
||||
</div>
|
||||
|
||||
{/* Starter-question chips, seeded with the Coach's own prompts. */}
|
||||
{prompts.length > 0 && (
|
||||
<div
|
||||
data-slot="coach-hero-chips"
|
||||
className={cn(
|
||||
"flex flex-wrap items-center justify-center gap-2",
|
||||
"motion-safe:animate-in motion-safe:fade-in-0 motion-safe:duration-500",
|
||||
)}
|
||||
style={{ animationDelay: "240ms", animationFillMode: "both" }}
|
||||
>
|
||||
{prompts.map((prompt) => (
|
||||
<button
|
||||
key={prompt}
|
||||
type="button"
|
||||
onClick={() => onPickPrompt(prompt)}
|
||||
data-slot="coach-hero-chip"
|
||||
title={prompt}
|
||||
className={cn(
|
||||
"border-dracula-purple/20 hover:border-dracula-purple/45 hover:text-foreground",
|
||||
"text-muted-foreground inline-flex min-h-11 max-w-full items-center gap-1.5 sm:min-h-9 sm:max-w-[18rem]",
|
||||
"rounded-full border bg-transparent px-3.5 py-1.5 text-xs sm:text-sm",
|
||||
"transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
||||
"focus-visible:ring-dracula-purple/50",
|
||||
)}
|
||||
>
|
||||
<Quote className="size-3 shrink-0" aria-hidden="true" />
|
||||
{/* Cap the chip to a tidy single line — long localized prompts
|
||||
(notably DE) otherwise wrap to ragged multi-line pills on a
|
||||
narrow viewport. The full prompt stays available via title. */}
|
||||
<span className="truncate">{prompt}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user