From 883e32220cab4d3ce7c91516b790778fa45c8221 Mon Sep 17 00:00:00 2001 From: BFlat Date: Thu, 13 Mar 2025 00:26:10 +0700 Subject: [PATCH] tabs and app mode signals (#450) Usage example - Conditional Rendering Based on App Mode: ```typescript import { appMode } from "~/signals"; function ConditionalComponent() { return (
{/* Show/hide components based on current mode */} {appMode.value === 'image' && (

Image Mode Tools

)} {appMode.value === 'edit' && (

Edit Mode Tools

)} ); } ``` --- .../features/ToolbarTopLeft/index.tsx | 28 ++++++- .../src/components/ui/TabSelector.tsx | 82 +++++++++++++++++++ frontend/realtime/src/components/ui/index.ts | 1 + .../realtime/src/signals/uiEvents/appMode.ts | 38 +++++++++ .../realtime/src/signals/uiEvents/index.ts | 5 ++ 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 frontend/realtime/src/components/ui/TabSelector.tsx create mode 100644 frontend/realtime/src/signals/uiEvents/appMode.ts diff --git a/frontend/realtime/src/components/features/ToolbarTopLeft/index.tsx b/frontend/realtime/src/components/features/ToolbarTopLeft/index.tsx index 576acad090..05f00de41d 100644 --- a/frontend/realtime/src/components/features/ToolbarTopLeft/index.tsx +++ b/frontend/realtime/src/components/features/ToolbarTopLeft/index.tsx @@ -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 ( -
+
logo + + + {/* New Board diff --git a/frontend/realtime/src/components/ui/TabSelector.tsx b/frontend/realtime/src/components/ui/TabSelector.tsx new file mode 100644 index 0000000000..6bda0a8ed5 --- /dev/null +++ b/frontend/realtime/src/components/ui/TabSelector.tsx @@ -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 = ({ + 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 ( + + + {/* Animated indicator */} +
+ + {tabs.map((tab, index) => ( + (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} + + ))} + + + ); +}; diff --git a/frontend/realtime/src/components/ui/index.ts b/frontend/realtime/src/components/ui/index.ts index 25897f25c1..788a004ad8 100644 --- a/frontend/realtime/src/components/ui/index.ts +++ b/frontend/realtime/src/components/ui/index.ts @@ -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"; diff --git a/frontend/realtime/src/signals/uiEvents/appMode.ts b/frontend/realtime/src/signals/uiEvents/appMode.ts new file mode 100644 index 0000000000..e2b721e6e1 --- /dev/null +++ b/frontend/realtime/src/signals/uiEvents/appMode.ts @@ -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("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); + } + }; + }, +}; diff --git a/frontend/realtime/src/signals/uiEvents/index.ts b/frontend/realtime/src/signals/uiEvents/index.ts index a698c9c1f3..f54c5ff6f5 100644 --- a/frontend/realtime/src/signals/uiEvents/index.ts +++ b/frontend/realtime/src/signals/uiEvents/index.ts @@ -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 };