logo removal, tooltip/menu fixes, added center icon (#1071)

### UI Refinements: Topbar, Menu, and Tooltips
- **Top bar organization:** Removed the extra logo from the top bar and
added the Artcraft icon to the center text for a cleaner layout.
- **Menu interaction:** Improved the behavior of the menu icon selector
so that confusing tooltips no longer appear when the menu is temporarily
disabled (e.g., during product switching).
- **Tooltip positioning:** Fixed an issue where tooltips were
occasionally extending beyond the edges of the screen, ensuring they now
stay fully visible.
This commit is contained in:
BFlat
2026-03-01 12:21:37 +07:00
committed by GitHub
parent 28beaab2c5
commit 08ceb5fb08
7 changed files with 326 additions and 117 deletions
@@ -11,6 +11,7 @@ import {
} from "~/pages/PageEnigma/signals";
import { Input } from "@storyteller/ui-input";
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
import { getCreatorIcon, ModelCreator } from "@storyteller/model-list";
interface Props {
pageName: string;
@@ -98,10 +99,15 @@ export const SceneTitleInput = ({ pageName }: Props) => {
"flex w-full items-center justify-center gap-1.5",
isSaving && "ml-3",
)}
data-tauri-drag-region
>
{!showInput && (
<div className="flex items-center">
<span className="mr-2 text-nowrap opacity-60">{pageName}</span>
<div className="flex items-center gap-2.5" data-tauri-drag-region>
{getCreatorIcon(
ModelCreator.ArtCraft,
"h-5 w-5 icon-auto-contrast opacity-60",
)}
<span className="text-nowrap opacity-60">{pageName}</span>
<span className="opacity-60">/</span>
{isSceneOwner ? (
@@ -16,6 +16,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { signal } from "@preact/signals-react";
import { useSignals } from "@preact/signals-react/runtime";
import { FilterMediaClasses } from "@storyteller/api";
import { getCreatorIcon, ModelCreator } from "@storyteller/model-list";
import { useCreditsState } from "@storyteller/credits";
import { gtagEvent } from "@storyteller/google-analytics";
import { ProviderBillingModal } from "@storyteller/provider-billing-modal";
@@ -278,7 +279,7 @@ export const TopBar = ({ pageName }: Props) => {
case "IMAGE":
return "Text to Image";
case "VIDEO":
return "Image to Video";
return "Generate Video";
case "EDIT":
return "Edit Image";
case "VIDEO_FRAME_EXTRACTOR":
@@ -304,7 +305,7 @@ export const TopBar = ({ pageName }: Props) => {
const { toggleModal: toggleCreditsModal } = useCreditsModalStore();
// Pick logo based on current theme (light uses black logo; others use white)
const [logoSrc, setLogoSrc] = useState<string>(
const [_logoSrc, setLogoSrc] = useState<string>(
"/resources/logo/artcraft-logo-color-white.svg",
);
useEffect(() => {
@@ -344,8 +345,11 @@ export const TopBar = ({ pageName }: Props) => {
aria-label="navigation"
data-tauri-drag-region
>
<div className="flex items-center gap-3" data-tauri-drag-region>
<div className="mr-2" data-tauri-drag-region>
<div
className={`flex items-center gap-3 ${platform === "macos" ? "ml-14" : ""}`}
data-tauri-drag-region
>
{/* <div className="mr-2" data-tauri-drag-region>
<span className="sr-only" data-tauri-drag-region>
ArtCraft
</span>
@@ -355,7 +359,7 @@ export const TopBar = ({ pageName }: Props) => {
alt="ArtCraft Logo"
data-tauri-drag-region
/>
</div>
</div> */}
<MenuIconSelector
menuItems={appMenuTabs}
activeMenu={tabStore.activeTabId}
@@ -405,7 +409,14 @@ export const TopBar = ({ pageName }: Props) => {
{tabStore.activeTabId === "3D" ? (
<SceneTitleInput pageName={pageName} />
) : (
<h1 className="text-base-fg" data-tauri-drag-region>
<h1
className="flex items-center gap-2.5 text-base-fg"
data-tauri-drag-region
>
{getCreatorIcon(
ModelCreator.ArtCraft,
"h-5 w-5 icon-auto-contrast opacity-50",
)}
{pageTitle}
</h1>
)}
@@ -43,7 +43,7 @@ export const APP_DESCRIPTORS: AppDescriptor[] = [
},
{
id: "VIDEO",
label: "Image to Video",
label: "Generate Video",
icon: faFilm,
},
{
@@ -87,7 +87,7 @@ export const ALL_APPS: FullAppItem[] = [
},
{
id: "image-to-video",
label: "Image to Video",
label: "Generate Video",
description: "Create video from images",
icon: faFilm,
category: "generate",
@@ -82,7 +82,7 @@ export const MenuIconSelector: React.FC<MenuIconSelectorProps> = ({
className={twMerge(
"w-full flex items-center justify-center relative",
className,
disabled && "cursor-not-allowed opacity-60"
disabled && "cursor-not-allowed opacity-60",
)}
>
<div
@@ -131,9 +131,10 @@ export const MenuIconSelector: React.FC<MenuIconSelectorProps> = ({
delay={100}
closeOnClick={true}
interactive={interactive}
disabled={disabled}
className={twMerge(
"text-sm font-semibold",
item.large && "text-md"
item.large && "text-md",
)}
imageSrc={imageSrc || undefined}
description={description || undefined}
@@ -147,7 +148,7 @@ export const MenuIconSelector: React.FC<MenuIconSelectorProps> = ({
onMouseEnter={() => setHoveredIndex(idx)}
className={twMerge(
"relative z-30 flex flex-col items-center justify-center px-3 py-2 rounded-lg transition-all duration-150 text-base-fg",
disabled ? "cursor-not-allowed opacity-60" : ""
disabled ? "cursor-not-allowed opacity-60" : "",
)}
tabIndex={0}
type="button"
@@ -163,15 +164,17 @@ export const MenuIconSelector: React.FC<MenuIconSelectorProps> = ({
return (
<>
{disabled && (
{disabled && disabledMessage ? (
<Tooltip
content={disabledMessage ?? "Cannot change menu. Action in progress."}
content={disabledMessage}
position="bottom"
className="bg-transparent border-none"
>
{MenuGroupElement}
</Tooltip>
) : (
MenuGroupElement
)}
{!disabled && MenuGroupElement}
</>
);
};
@@ -207,7 +207,7 @@ export function CostBreakdownModal({ activeTabId }: CostBreakdownModalProps) {
case ModelPage.TextToImage:
return "Text to Image";
case ModelPage.ImageToVideo:
return "Image to Video";
return "Generate Video";
case ModelPage.Canvas2D:
return "Canvas 2D";
case ModelPage.Stage3D:
@@ -308,18 +308,20 @@ export function CostBreakdownModal({ activeTabId }: CostBreakdownModalProps) {
Credits
</div>
<div className="text-lg font-bold text-base-fg flex items-center gap-1.5">
{isVideoEstimateLoading && activePage === ModelPage.ImageToVideo ? (
{isVideoEstimateLoading &&
activePage === ModelPage.ImageToVideo ? (
<>
<FontAwesomeIcon
icon={faSpinner}
className="animate-spin text-base"
/>
<span className="text-base-fg/50 text-sm">Calculating…</span>
<span className="text-base-fg/50 text-sm">
Calculating…
</span>
</>
) : totalCredits != null ? (
<>
{totalCredits}{" "}
{totalCredits === 1 ? "Credit" : "Credits"}
{totalCredits} {totalCredits === 1 ? "Credit" : "Credits"}
</>
) : (
<span className="text-base-fg/50">—</span>
@@ -77,39 +77,92 @@ export const Tooltip = ({
};
}, []);
const [measuredWidth, setMeasuredWidth] = useState(0);
const setTooltipRef = React.useCallback((node: HTMLDivElement | null) => {
tooltipRef.current = node;
if (node) {
const w = node.getBoundingClientRect().width;
if (w > 0) {
setMeasuredWidth((prev) => (prev !== w ? w : prev));
}
}
}, []);
const getStyleForPosition = () => {
let baseStyle: React.CSSProperties = {};
if (triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
const vw = typeof window !== "undefined" ? window.innerWidth : 1000;
//const vh = typeof window !== "undefined" ? window.innerHeight : 1000;
const padding = 10;
// Use the known measured width over fallback, or aggressively measure via ref
let estWidth = measuredWidth > 0 ? measuredWidth : 100;
if (tooltipRef.current && measuredWidth === 0) {
const currentWidth = tooltipRef.current.getBoundingClientRect().width;
if (currentWidth > 0) estWidth = currentWidth;
}
switch (position) {
case "top":
return {
case "top": {
baseStyle = {
bottom: rect.height + 10,
left: "50%",
transform: "translateX(-50%)",
};
case "bottom":
return {
top: rect.height + 10,
left: "50%",
transform: "translateX(-50%)",
};
case "left":
return {
break;
}
case "bottom": {
const center = rect.left + rect.width / 2;
const halfWidth = estWidth / 2;
if (center - halfWidth < padding) {
// Fixes flush-left behavior
const diff = padding - (center - halfWidth);
baseStyle = {
top: rect.height + 10,
left: "50%",
transform: `translateX(calc(-50% + ${diff}px))`,
};
} else if (center + halfWidth > vw - padding) {
// Fixes flush-right behavior
const diff = center + halfWidth - (vw - padding);
baseStyle = {
top: rect.height + 10,
left: "50%",
transform: `translateX(calc(-50% - ${diff}px))`,
};
} else {
// Uses normal centering
baseStyle = {
top: rect.height + 10,
left: "50%",
transform: "translateX(-50%)",
};
}
break;
}
case "left": {
baseStyle = {
right: rect.width + 10,
top: "50%",
transform: "translateY(-50%)",
};
case "right":
return {
break;
}
case "right": {
baseStyle = {
left: rect.width + 10,
top: "50%",
transform: "translateY(-50%)",
};
default:
return {};
break;
}
}
}
return {};
return baseStyle;
};
const handleClick = (e: React.MouseEvent) => {
@@ -168,7 +221,7 @@ export const Tooltip = ({
leaveTo="opacity-0"
>
<div
ref={tooltipRef}
ref={setTooltipRef}
onMouseEnter={() => interactive && setIsHoveringTooltip(true)}
onMouseLeave={() => interactive && setIsHoveringTooltip(false)}
onClick={() => {
+214 -80
View File
File diff suppressed because it is too large Load Diff