diff --git a/package.json b/package.json index 53674b2..c7809ef 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "@types/node": "^24.0.14", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", "radix-ui": "^1.4.2", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50700e9..dcd6d8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,12 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + mobx: + specifier: ^6.13.7 + version: 6.13.7 + mobx-react-lite: + specifier: ^4.1.0 + version: 4.1.0(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) radix-ui: specifier: ^1.4.2 version: 1.4.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -2037,6 +2043,22 @@ packages: engines: {node: '>=10'} hasBin: true + mobx-react-lite@4.1.0: + resolution: {integrity: sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==} + peerDependencies: + mobx: ^6.9.0 + react: ^16.8.0 || ^17 || ^18 || ^19 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + mobx@6.13.7: + resolution: {integrity: sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4439,6 +4461,16 @@ snapshots: mkdirp@3.0.1: {} + mobx-react-lite@4.1.0(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + mobx: 6.13.7 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + + mobx@6.13.7: {} + ms@2.1.3: {} nanoid@3.3.11: {} diff --git a/src/components/common/main-menu.tsx b/src/components/common/main-menu.tsx new file mode 100644 index 0000000..12dbcb4 --- /dev/null +++ b/src/components/common/main-menu.tsx @@ -0,0 +1,17 @@ +import { Button } from "../ui/button"; +import { observer } from "mobx-react-lite"; +import { useAccountStore } from "@/contexts/AccountContext"; + +export const MainMenu = observer(() => { + const { isMainMenuOpen, setIsMainMenuOpen } = useAccountStore(); + return ( +
+ +
+ ); +}); diff --git a/src/components/interaction-panel/chat.tsx b/src/components/interaction-panel/chat.tsx new file mode 100644 index 0000000..727010e --- /dev/null +++ b/src/components/interaction-panel/chat.tsx @@ -0,0 +1,24 @@ +import { useAccountStore } from "@/contexts/AccountContext"; +import { Button } from "../ui/button"; +import { observer } from "mobx-react-lite"; + +export const Chat = observer(() => { + const { isSandboxOpen, setIsSandboxOpen, isMainMenuOpen, setIsMainMenuOpen } = + useAccountStore(); + + return ( +
+ +
+ ); +}); diff --git a/src/components/interaction-panel/sandbox.tsx b/src/components/interaction-panel/sandbox.tsx new file mode 100644 index 0000000..1126f0a --- /dev/null +++ b/src/components/interaction-panel/sandbox.tsx @@ -0,0 +1,15 @@ +import { useAccountStore } from "@/contexts/AccountContext"; +import { observer } from "mobx-react-lite"; + +export const Sandbox = observer(() => { + const { isSandboxOpen } = useAccountStore(); + + return ( +
+ SANDBOOOOX +
+ ); +}); diff --git a/src/contexts/AccountContext.tsx b/src/contexts/AccountContext.tsx new file mode 100644 index 0000000..e0a6ead --- /dev/null +++ b/src/contexts/AccountContext.tsx @@ -0,0 +1,47 @@ +import { createContext, useContext, useState } from "react"; +import { AccountStore } from "@/store/account"; + +interface StoreProviderState { + store?: AccountStore; + setStore: (store: AccountStore) => void; +} + +interface StoreProviderProps { + children: React.ReactNode; +} + +const initialState: StoreProviderState = { + store: undefined, + setStore: () => null, +}; + +const AccountStoreProviderContext = createContext(initialState); +const _AccountStore = new AccountStore(); + +export function AccountStoreProvider({ + children, + ...props +}: StoreProviderProps) { + const [_store, _setStore] = useState(_AccountStore); + + return ( + + {children} + + ); +} + +export function useAccountStore() { + const context = useContext(AccountStoreProviderContext); + if (context === undefined) { + throw new Error("useStoreProvider must be used within a StoreProvider"); + } + + return _AccountStore; +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..79e74f1 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,3 @@ +export const LS_TOKENS = { + isMainMenuOpen: "isMainMenuOpen", +}; diff --git a/src/main.tsx b/src/main.tsx index 10f7546..67ba82c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,6 +9,7 @@ import "./styles.css"; import reportWebVitals from "./reportWebVitals.ts"; import { Toaster } from "./components/ui/toaster.tsx"; import { TooltipProvider } from "./components/ui/tooltip.tsx"; +import { AccountStoreProvider } from "./contexts/AccountContext.tsx"; // Create a new router instance const router = createRouter({ @@ -33,10 +34,12 @@ if (rootElement && !rootElement.innerHTML) { const root = ReactDOM.createRoot(rootElement); root.render( - - - - + + + + + + , ); } diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index d204c26..93a4947 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -9,8 +9,14 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as InteractionPanelRouteImport } from './routes/interaction-panel' import { Route as IndexRouteImport } from './routes/index' +const InteractionPanelRoute = InteractionPanelRouteImport.update({ + id: '/interaction-panel', + path: '/interaction-panel', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -19,28 +25,39 @@ const IndexRoute = IndexRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/interaction-panel': typeof InteractionPanelRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/interaction-panel': typeof InteractionPanelRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/interaction-panel': typeof InteractionPanelRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' + fullPaths: '/' | '/interaction-panel' fileRoutesByTo: FileRoutesByTo - to: '/' - id: '__root__' | '/' + to: '/' | '/interaction-panel' + id: '__root__' | '/' | '/interaction-panel' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + InteractionPanelRoute: typeof InteractionPanelRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/interaction-panel': { + id: '/interaction-panel' + path: '/interaction-panel' + fullPath: '/interaction-panel' + preLoaderRoute: typeof InteractionPanelRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -53,6 +70,7 @@ declare module '@tanstack/react-router' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + InteractionPanelRoute: InteractionPanelRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index d495d48..f5d6902 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,11 +1,24 @@ -import { Outlet, createRootRoute } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' +// src/routes/__root.tsx +import { Outlet, createRootRoute } from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; +import { MainMenu } from "@/components/common/main-menu"; export const Route = createRootRoute({ - component: () => ( - <> - - - - ), -}) + component: RootLayout, +}); + +function RootLayout() { + return ( +
+ + +
+
+ +
+
+ + +
+ ); +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index b1b72fc..af885e7 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,3 +1,4 @@ +import { TextLink } from "@/components/ui/text-link"; import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ @@ -5,5 +6,9 @@ export const Route = createFileRoute("/")({ }); function App() { - return
hi
; + return ( +
+ Lable +
+ ); } diff --git a/src/routes/interaction-panel.tsx b/src/routes/interaction-panel.tsx new file mode 100644 index 0000000..6fa1021 --- /dev/null +++ b/src/routes/interaction-panel.tsx @@ -0,0 +1,17 @@ +// src/routes/chat.tsx +import { Chat } from "@/components/interaction-panel/chat"; +import { Sandbox } from "@/components/interaction-panel/sandbox"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/interaction-panel")({ + component: InteractionPanelRoute, +}); + +function InteractionPanelRoute() { + return ( +
+ + +
+ ); +} diff --git a/src/store/account.ts b/src/store/account.ts new file mode 100644 index 0000000..2f3c96c --- /dev/null +++ b/src/store/account.ts @@ -0,0 +1,25 @@ +import { LS_TOKENS } from "@/lib/constants"; +import { makeAutoObservable } from "mobx"; + +export class AccountStore { + isMainMenuOpen: boolean; + isSandboxOpen: boolean; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + + const savedState = localStorage.getItem(LS_TOKENS.isMainMenuOpen); + this.isMainMenuOpen = savedState ? savedState === "true" : false; + + this.isSandboxOpen = false; + } + + setIsMainMenuOpen(value: boolean) { + this.isMainMenuOpen = value; + localStorage.setItem(LS_TOKENS.isMainMenuOpen, String(this.isMainMenuOpen)); + } + + setIsSandboxOpen(value: boolean) { + this.isSandboxOpen = value; + } +}