add base layout

This commit is contained in:
yzned
2025-07-19 12:55:03 +03:00
committed by yzned
parent d249f0d291
commit de28d80769
13 changed files with 238 additions and 17 deletions

View File

@@ -20,6 +20,8 @@
"@types/node": "^24.0.14", "@types/node": "^24.0.14",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"mobx": "^6.13.7",
"mobx-react-lite": "^4.1.0",
"radix-ui": "^1.4.2", "radix-ui": "^1.4.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",

32
pnpm-lock.yaml generated
View File

@@ -38,6 +38,12 @@ importers:
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 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: radix-ui:
specifier: ^1.4.2 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) 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'} engines: {node: '>=10'}
hasBin: true 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: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -4439,6 +4461,16 @@ snapshots:
mkdirp@3.0.1: {} 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: {} ms@2.1.3: {}
nanoid@3.3.11: {} nanoid@3.3.11: {}

View File

@@ -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 (
<div
data-open={isMainMenuOpen}
className="w-[30px] data-[open=true]:w-[124px] transition-all border-r p-2"
>
<Button onClick={() => setIsMainMenuOpen(!isMainMenuOpen)}>
{isMainMenuOpen ? "Close" : "Open"}
</Button>
</div>
);
});

View File

@@ -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 (
<div className="flex-1">
<Button
onClick={() => {
setIsSandboxOpen(!isSandboxOpen);
if (isMainMenuOpen && !isSandboxOpen) {
setIsMainMenuOpen(false);
}
}}
className="ml-10"
>
OPEN SANDBOX
</Button>
</div>
);
});

View File

@@ -0,0 +1,15 @@
import { useAccountStore } from "@/contexts/AccountContext";
import { observer } from "mobx-react-lite";
export const Sandbox = observer(() => {
const { isSandboxOpen } = useAccountStore();
return (
<div
data-open={isSandboxOpen}
className="w-0 data-[open=true]:w-[40svw] border-l-1 transition-all border-r p-0 bg-red-600"
>
SANDBOOOOX
</div>
);
});

View File

@@ -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 (
<AccountStoreProviderContext.Provider
{...props}
value={{
store: _store,
setStore: _setStore,
}}
>
{children}
</AccountStoreProviderContext.Provider>
);
}
export function useAccountStore() {
const context = useContext(AccountStoreProviderContext);
if (context === undefined) {
throw new Error("useStoreProvider must be used within a StoreProvider");
}
return _AccountStore;
}

3
src/lib/constants.ts Normal file
View File

@@ -0,0 +1,3 @@
export const LS_TOKENS = {
isMainMenuOpen: "isMainMenuOpen",
};

View File

@@ -9,6 +9,7 @@ import "./styles.css";
import reportWebVitals from "./reportWebVitals.ts"; import reportWebVitals from "./reportWebVitals.ts";
import { Toaster } from "./components/ui/toaster.tsx"; import { Toaster } from "./components/ui/toaster.tsx";
import { TooltipProvider } from "./components/ui/tooltip.tsx"; import { TooltipProvider } from "./components/ui/tooltip.tsx";
import { AccountStoreProvider } from "./contexts/AccountContext.tsx";
// Create a new router instance // Create a new router instance
const router = createRouter({ const router = createRouter({
@@ -33,10 +34,12 @@ if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement); const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
<StrictMode> <StrictMode>
<AccountStoreProvider>
<TooltipProvider delayDuration={50}> <TooltipProvider delayDuration={50}>
<RouterProvider router={router} /> <RouterProvider router={router} />
<Toaster /> <Toaster />
</TooltipProvider> </TooltipProvider>
</AccountStoreProvider>
</StrictMode>, </StrictMode>,
); );
} }

View File

@@ -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. // 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 rootRouteImport } from './routes/__root'
import { Route as InteractionPanelRouteImport } from './routes/interaction-panel'
import { Route as IndexRouteImport } from './routes/index' 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({ const IndexRoute = IndexRouteImport.update({
id: '/', id: '/',
path: '/', path: '/',
@@ -19,28 +25,39 @@ const IndexRoute = IndexRouteImport.update({
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/interaction-panel': typeof InteractionPanelRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/interaction-panel': typeof InteractionPanelRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
'/interaction-panel': typeof InteractionPanelRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' fullPaths: '/' | '/interaction-panel'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' to: '/' | '/interaction-panel'
id: '__root__' | '/' id: '__root__' | '/' | '/interaction-panel'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
InteractionPanelRoute: typeof InteractionPanelRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/interaction-panel': {
id: '/interaction-panel'
path: '/interaction-panel'
fullPath: '/interaction-panel'
preLoaderRoute: typeof InteractionPanelRouteImport
parentRoute: typeof rootRouteImport
}
'/': { '/': {
id: '/' id: '/'
path: '/' path: '/'
@@ -53,6 +70,7 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
InteractionPanelRoute: InteractionPanelRoute,
} }
export const routeTree = rootRouteImport export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)

View File

@@ -1,11 +1,24 @@
import { Outlet, createRootRoute } from '@tanstack/react-router' // src/routes/__root.tsx
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' 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({ export const Route = createRootRoute({
component: () => ( component: RootLayout,
<> });
function RootLayout() {
return (
<div className="flex h-screen">
<MainMenu />
<div className="flex flex-col flex-1">
<div className="flex-1 overflow-hidden">
<Outlet /> <Outlet />
</div>
</div>
<TanStackRouterDevtools /> <TanStackRouterDevtools />
</> </div>
), );
}) }

View File

@@ -1,3 +1,4 @@
import { TextLink } from "@/components/ui/text-link";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
@@ -5,5 +6,9 @@ export const Route = createFileRoute("/")({
}); });
function App() { function App() {
return <div className="p-4">hi</div>; return (
<div className="p-4">
<TextLink>Lable</TextLink>
</div>
);
} }

View File

@@ -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 (
<div className="flex h-full">
<Chat />
<Sandbox />
</div>
);
}

25
src/store/account.ts Normal file
View File

@@ -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;
}
}