From 768e7a390fe205bc5c54265a76b7ac8bd1650f6f Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 12 Mar 2026 10:56:56 +0100 Subject: [PATCH] Improve empty encounter UX with interactive add button Replace the static "No combatants yet" text with a centered, breathing "+" icon that focuses the action bar input on click, guiding users to add their first combatant. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/App.tsx | 18 ++++++++++++++---- apps/web/src/components/action-bar.tsx | 11 ++++++++++- apps/web/src/index.css | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index c6860fe..c92ad1b 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -3,6 +3,7 @@ import { rollInitiativeUseCase, } from "@initiative/application"; import type { CombatantId, Creature, CreatureId } from "@initiative/domain"; +import { Plus } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { ActionBar } from "./components/action-bar"; import { CombatantRow } from "./components/combatant-row"; @@ -160,6 +161,8 @@ export function App() { setPinnedCreatureId(null); }, []); + const actionBarInputRef = useRef(null); + // Auto-scroll to the active combatant when the turn changes const activeRowRef = useRef(null); useEffect(() => { @@ -205,11 +208,17 @@ export function App() { {/* Scrollable area — combatant list */}
-
+
{encounter.combatants.length === 0 ? ( -

- No combatants yet — add one to get started -

+ ) : ( encounter.combatants.map((c, i) => (
diff --git a/apps/web/src/components/action-bar.tsx b/apps/web/src/components/action-bar.tsx index 4cc0451..2806f83 100644 --- a/apps/web/src/components/action-bar.tsx +++ b/apps/web/src/components/action-bar.tsx @@ -1,5 +1,11 @@ import { Check, Eye, Import, Minus, Plus } from "lucide-react"; -import { type FormEvent, useEffect, useRef, useState } from "react"; +import { + type FormEvent, + type RefObject, + useEffect, + useRef, + useState, +} from "react"; import type { SearchResult } from "../hooks/use-bestiary.js"; import { Button } from "./ui/button.js"; import { Input } from "./ui/input.js"; @@ -20,6 +26,7 @@ interface ActionBarProps { onViewStatBlock?: (result: SearchResult) => void; onBulkImport?: () => void; bulkImportDisabled?: boolean; + inputRef?: RefObject; } function creatureKey(r: SearchResult): string { @@ -34,6 +41,7 @@ export function ActionBar({ onViewStatBlock, onBulkImport, bulkImportDisabled, + inputRef, }: ActionBarProps) { const [nameInput, setNameInput] = useState(""); const [suggestions, setSuggestions] = useState([]); @@ -222,6 +230,7 @@ export function ActionBar({ >
handleNameChange(e.target.value)} diff --git a/apps/web/src/index.css b/apps/web/src/index.css index e2a5439..1f61fea 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -80,6 +80,22 @@ } } +@keyframes breathe { + 0%, + 100% { + opacity: 0.4; + scale: 0.9; + } + 50% { + opacity: 1; + scale: 1.1; + } +} + +@utility animate-breathe { + animation: breathe 3s ease-in-out infinite; +} + @custom-variant pointer-coarse (@media (pointer: coarse)); @utility animate-confirm-pulse {