add base layout
This commit is contained in:
@@ -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
32
pnpm-lock.yaml
generated
@@ -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: {}
|
||||||
|
|||||||
17
src/components/common/main-menu.tsx
Normal file
17
src/components/common/main-menu.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
||||||
24
src/components/interaction-panel/chat.tsx
Normal file
24
src/components/interaction-panel/chat.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
||||||
15
src/components/interaction-panel/sandbox.tsx
Normal file
15
src/components/interaction-panel/sandbox.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
});
|
||||||
47
src/contexts/AccountContext.tsx
Normal file
47
src/contexts/AccountContext.tsx
Normal 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
3
src/lib/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const LS_TOKENS = {
|
||||||
|
isMainMenuOpen: "isMainMenuOpen",
|
||||||
|
};
|
||||||
@@ -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>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
),
|
);
|
||||||
})
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/routes/interaction-panel.tsx
Normal file
17
src/routes/interaction-panel.tsx
Normal 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
25
src/store/account.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user