Compare commits

...
Author SHA1 Message Date
bflatastic f1678c97b4 expand tooltip on hover 2026-04-16 10:59:53 +07:00
bflatastic 7c6cc86399 mention text area expand fixes 2026-04-16 10:57:19 +07:00
bflatastic b96300f0a5 website: expandable and resizable promptbox 2026-04-16 10:52:49 +07:00
8 changed files with 96 additions and 40 deletions
@@ -6,7 +6,7 @@ import {
type IconDefinition,
} from "@fortawesome/pro-solid-svg-icons";
import { Button } from "@storyteller/ui-button";
import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
import { type PopoverItem } from "@storyteller/ui-popover";
import Seo from "../../components/seo";
import Footer from "../../components/footer";
@@ -49,8 +49,6 @@ export function CreateMediaPageShell({
emptyStateTitle,
emptyStateSubtitle,
bottomOffset,
modelItems,
onModelChange,
glowOrbs,
gridContent,
promptBox,
@@ -146,28 +144,6 @@ export function CreateMediaPageShell({
)}
{promptBox}
<div
className="animate-fade-in-up fixed bottom-3 left-6 z-20 hidden items-center gap-5 lg:flex"
style={{ animationDelay: "300ms" }}
>
<div
className="rounded-xl border border-white/10 px-3 py-2 shadow-lg backdrop-blur-xl"
style={{ backgroundColor: "rgba(30, 30, 33, 0.85)" }}
>
<PopoverMenu
items={modelItems}
onSelect={onModelChange}
mode="hoverSelect"
panelTitle="Select Model"
panelClassName="min-w-[300px]"
buttonClassName="bg-transparent border-0 shadow-none p-0 text-lg hover:bg-transparent text-white/80 hover:text-white"
showIconsInList
triggerLabel="Model"
maxListHeight={400}
/>
</div>
</div>
</div>
</div>
@@ -633,7 +633,7 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
onBlur={onBlur}
className={twMerge(
className,
"outline-none whitespace-pre-wrap break-words overflow-y-auto",
"outline-none whitespace-pre-wrap break-words overflow-y-auto resize-y",
)}
/>
@@ -10,6 +10,8 @@ import {
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronDown,
faChevronUp,
faMusic,
faUserGroup,
faVideo,
@@ -143,8 +145,25 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
const highlightRef = useRef<HTMLDivElement>(null);
const mentionEditorRef = useRef<HTMLDivElement>(null);
const [isFocused, setIsFocused] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const [showImagePrompts, setShowImagePrompts] = useState(false);
const EXPANDED_HEIGHT = "clamp(120px, calc(100vh - 700px), 500px)";
const toggleExpand = () => {
setIsExpanded((prev) => {
const next = !prev;
const height = next ? EXPANDED_HEIGHT : "auto";
if (textareaRef.current) {
textareaRef.current.style.height = height;
}
if (mentionEditorRef.current) {
mentionEditorRef.current.style.height = height;
}
return next;
});
};
// @-mention state
const [mentionOpen, setMentionOpen] = useState(false);
const [mentionFilter, setMentionFilter] = useState("");
@@ -167,13 +186,14 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
);
}, [mentionItems, mentionFilter]);
// Auto-resize textarea
// Auto-resize textarea (skip when expanded)
useEffect(() => {
if (isExpanded) return;
if (textareaRef.current) {
textareaRef.current.style.height = "auto";
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
}
}, [prompt]);
}, [prompt, isExpanded]);
// Move caret to end on mount so autoFocus doesn't leave it at position 0
useEffect(() => {
@@ -402,7 +422,10 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
onChange={onPromptChange}
mentionItems={mentionItems}
placeholder={placeholder}
className="max-h-[5.5em] w-full text-white"
className={twMerge(
"promptbox-scrollbar min-h-[2.5em] w-full text-white",
isExpanded ? "max-h-[500px]" : "max-h-[5.5em]",
)}
colorMap={mentionColorMap}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey && !e.metaKey) {
@@ -419,7 +442,10 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
<div
ref={highlightRef}
aria-hidden
className="pointer-events-none absolute inset-0 max-h-[5.5em] overflow-y-auto whitespace-pre-wrap break-words text-sm text-white"
className={twMerge(
"pointer-events-none absolute inset-0 overflow-y-auto whitespace-pre-wrap break-words text-sm text-white",
isExpanded ? "max-h-[500px]" : "max-h-[5.5em]",
)}
>
{renderHighlightedPrompt()}
</div>
@@ -431,7 +457,8 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
autoFocus
placeholder={placeholder}
className={twMerge(
"max-h-[5.5em] w-full flex-1 resize-none overflow-y-auto bg-transparent text-md text-white placeholder-white/50 focus:outline-none",
"promptbox-scrollbar min-h-[2.5em] w-full flex-1 resize-y overflow-y-auto bg-transparent text-md text-white placeholder-white/50 focus:outline-none",
isExpanded ? "max-h-[500px]" : "max-h-[5.5em]",
hasMentionItems && "text-transparent caret-white",
)}
value={prompt}
@@ -515,9 +542,7 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
{/* Toolbar */}
<div className="mt-2 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2">
{modelSelector && (
<div className="lg:hidden">{modelSelector}</div>
)}
{modelSelector}
{leftToolbar}
</div>
<div className="flex items-center gap-2 sm:shrink-0">
@@ -533,6 +558,25 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
</GenerateButton>
</div>
</div>
{/* Expand / Collapse toggle — hidden on small screens */}
<div className="absolute -bottom-1 left-1/2 hidden -translate-x-1/2 sm:block">
<Tooltip
content={isExpanded ? "Collapse" : "Expand"}
position="top"
>
<button
type="button"
onClick={toggleExpand}
className="px-3 py-0.5 text-white/30 transition-colors hover:text-white/90"
>
<FontAwesomeIcon
icon={isExpanded ? faChevronUp : faChevronDown}
className="text-xs"
/>
</button>
</Tooltip>
</div>
</div>
</div>
</div>
@@ -71,6 +71,30 @@
background-color: rgba(46, 46, 49, 0.85) !important;
}
/* Minimal thin scrollbar for prompt textareas */
.promptbox-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}
.promptbox-scrollbar::-webkit-scrollbar {
width: 4px;
}
.promptbox-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.promptbox-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 9999px;
}
.promptbox-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 255, 255, 0.35);
}
@keyframes fade-in-up {
from {
opacity: 0;
@@ -40,6 +40,9 @@
{
"path": "../../libs/components/gravatar/tsconfig.lib.json"
},
{
"path": "../../libs/components/slider-v2/tsconfig.lib.json"
},
{
"path": "../../libs/components/tab-selector/tsconfig.lib.json"
},
@@ -5,6 +5,9 @@
{
"path": "../../libs/components/gravatar"
},
{
"path": "../../libs/components/slider-v2"
},
{
"path": "../../libs/components/tab-selector"
},
@@ -9,7 +9,7 @@
"path": "../../common"
},
{
"path": "../loading-spinner"
"path": "../../api"
},
{
"path": "../close-button"
@@ -24,10 +24,10 @@
"path": "../modal"
},
{
"path": "../../tauri-api"
"path": "../toaster"
},
{
"path": "../../api"
"path": "../../tauri-api"
},
{
"path": "../action-reminder-modal"
@@ -38,6 +38,9 @@
{
"path": "../tooltip"
},
{
"path": "../loading-spinner"
},
{
"path": "./tsconfig.lib.json"
},
@@ -50,7 +50,7 @@
"path": "../../common/tsconfig.lib.json"
},
{
"path": "../loading-spinner/tsconfig.lib.json"
"path": "../../api/tsconfig.lib.json"
},
{
"path": "../close-button/tsconfig.lib.json"
@@ -65,10 +65,10 @@
"path": "../modal/tsconfig.lib.json"
},
{
"path": "../../tauri-api/tsconfig.lib.json"
"path": "../toaster/tsconfig.lib.json"
},
{
"path": "../../api/tsconfig.lib.json"
"path": "../../tauri-api/tsconfig.lib.json"
},
{
"path": "../action-reminder-modal/tsconfig.lib.json"
@@ -78,6 +78,9 @@
},
{
"path": "../tooltip/tsconfig.lib.json"
},
{
"path": "../loading-spinner/tsconfig.lib.json"
}
]
}