Compare commits

...
Author SHA1 Message Date
bflatastic ca2c88494a fixed width and shadow 2025-03-12 23:57:28 +07:00
bflatastic c0efe1f031 tabs and app mode signals 2025-03-12 23:46:53 +07:00
5 changed files with 153 additions and 1 deletions
@@ -1,17 +1,43 @@
import { twMerge } from "tailwind-merge";
import { TabSelector, TabItem } from "~/components/ui/TabSelector";
import { useComputed } from "@preact/signals-react";
import { appMode, dispatchUiEvents } from "~/signals";
// import { paperWrapperStyles } from "~/components/styles";
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
// import { ToolbarButton } from "~/components/features/ToolbarButton";
export const ToolbarTopLeft = () => {
// Use the appMode signal directly with useComputed for reactivity - active tab sets the app mode
const activeTab = useComputed(() => appMode.value);
const tabs: TabItem[] = [
{ id: "image", label: "Image" },
{ id: "edit", label: "Edit" },
// { id: "video", label: "Video" },
];
const handleTabChange = (tabId: string) => {
// Update the global signal
dispatchUiEvents.changeAppMode(tabId as "image" | "edit");
};
console.log(appMode.value);
return (
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-1")}>
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-8 px-4")}>
<img
src="/brand/mira-logo.png"
alt="logo"
className="h-[30px] select-none pb-1"
/>
<TabSelector
tabs={tabs}
activeTab={activeTab.value}
onTabChange={handleTabChange}
/>
{/* <ToolbarButton icon={faPlus}>
<span className="font-semibold">New Board</span>
</ToolbarButton>
@@ -0,0 +1,82 @@
import React, { useRef, useEffect, useState } from "react";
import { Tab, TabGroup, TabList } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
export interface TabItem {
id: string;
label: string;
}
interface TabSelectorProps {
tabs: TabItem[];
activeTab: string;
onTabChange: (tabId: string) => void;
className?: string;
}
export const TabSelector: React.FC<TabSelectorProps> = ({
tabs,
activeTab,
onTabChange,
className,
}) => {
// Find the index of the active tab
const selectedIndex = tabs.findIndex((tab) => tab.id === activeTab);
const tabsRef = useRef<(HTMLElement | null)[]>([]);
const [indicatorStyle, setIndicatorStyle] = useState({
left: 0,
width: 0,
});
// Handle tab change
const handleTabChange = (index: number) => {
onTabChange(tabs[index].id);
};
// Update the indicator position when the selected tab changes
useEffect(() => {
if (selectedIndex >= 0 && tabsRef.current[selectedIndex]) {
const currentTab = tabsRef.current[selectedIndex];
if (currentTab) {
setIndicatorStyle({
left: currentTab.offsetLeft,
width: currentTab.offsetWidth,
});
}
}
}, [selectedIndex, tabs]);
return (
<TabGroup
selectedIndex={selectedIndex}
onChange={handleTabChange}
className={twMerge("w-full", className)}
>
<TabList className="glass relative inline-flex min-w-fit overflow-x-auto rounded-lg p-1 shadow-lg">
{/* Animated indicator */}
<div
className="absolute top-1 z-10 h-[calc(100%-8px)] rounded-md border-2 border-primary bg-primary/30 transition-all duration-300 ease-in-out"
style={{
left: indicatorStyle.left,
width: indicatorStyle.width,
}}
/>
{tabs.map((tab, index) => (
<Tab
key={tab.id}
ref={(el) => (tabsRef.current[index] = el)}
className={({ selected }) =>
twMerge(
"relative z-20 mx-0.5 min-w-max rounded-md border-2 border-transparent px-4 py-1 text-center font-semibold transition-all duration-300 ease-in-out",
selected ? "text-white" : "text-gray-300 hover:text-white",
)
}
>
{tab.label}
</Tab>
))}
</TabList>
</TabGroup>
);
};
@@ -8,6 +8,7 @@ export * from "./PanelRight";
export * from "./Slider";
export * from "./Spinner";
export * from "./Switches";
export * from "./TabSelector";
export * from "./TextArea";
export * from "./TextEditor";
export * from "./Tooltip";
@@ -0,0 +1,38 @@
import { signal, effect } from "@preact/signals-core";
// this is to control the app mode - BFlat
// Define the app mode types
export type AppModeType = "image" | "edit";
// Create a signal for the current app mode
export const appMode = signal<AppModeType>("image");
// Create a function to change the app mode
export const changeAppMode = (mode: AppModeType) => {
appMode.value = mode;
};
// Store callbacks for mode changes
const callbacks: ((mode: AppModeType) => void)[] = [];
// Set up an effect to notify callbacks when the mode changes
effect(() => {
const currentMode = appMode.value;
callbacks.forEach((callback) => callback(currentMode));
});
// Export an event handler for components to use
export const appModeEvents = {
onChange: (callback: (mode: AppModeType) => void) => {
callbacks.push(callback);
// Return a function to unsubscribe
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
};
},
};
@@ -36,6 +36,7 @@ import {
dispatchers as modelSelectionDispatchers,
events as modelSelectionEvents,
} from "./modelSelection";
import { appMode, changeAppMode, appModeEvents } from "./appMode";
export const uiEvents = {
...addMediaEvents,
@@ -49,6 +50,7 @@ export const uiEvents = {
promptEvents: promptEvents,
loadingIndicator: loadingIndicatorEvents,
modelSelection: modelSelectionEvents,
appMode: appModeEvents,
};
export const dispatchUiEvents = {
...addMediaDispatchers,
@@ -62,4 +64,7 @@ export const dispatchUiEvents = {
promptSettings: promptDispatchers,
loadingIndicator: loadingIndicatorDispatchers,
modelSelection: modelSelectionDispatchers,
changeAppMode,
};
export { appMode };