diff --git a/electron/package-lock.json b/electron/package-lock.json
index fac4966..5fbb43b 100644
--- a/electron/package-lock.json
+++ b/electron/package-lock.json
@@ -64,8 +64,10 @@
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
"globals": "^16.3.0",
"jsdom": "^26.1.0",
+ "next-themes": "^0.4.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
+ "sonner": "^2.0.7",
"tailwindcss": "^4.1.11",
"ts-node": "^10.9.2",
"typescript": "^5.9.2",
@@ -9690,6 +9692,17 @@
"node": ">= 0.6"
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -11679,6 +11692,17 @@
"seroval-plugins": "~1.3.0"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
+ "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
diff --git a/electron/package.json b/electron/package.json
index 308991d..7819f9f 100644
--- a/electron/package.json
+++ b/electron/package.json
@@ -55,8 +55,10 @@
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
"globals": "^16.3.0",
"jsdom": "^26.1.0",
+ "next-themes": "^0.4.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
+ "sonner": "^2.0.7",
"tailwindcss": "^4.1.11",
"ts-node": "^10.9.2",
"typescript": "^5.9.2",
diff --git a/electron/src/App.tsx b/electron/src/App.tsx
index cf46bcc..f240d55 100644
--- a/electron/src/App.tsx
+++ b/electron/src/App.tsx
@@ -6,6 +6,7 @@ import "./localization/i18n";
import { updateAppLanguage } from "./helpers/language_helpers";
import { router } from "./routes/router";
import { RouterProvider } from "@tanstack/react-router";
+import { Toaster } from "./components/ui/sonner";
export default function App() {
const { i18n } = useTranslation();
@@ -22,5 +23,6 @@ const root = createRoot(document.getElementById("app")!);
root.render(
+
,
);
diff --git a/electron/src/components/Icon.tsx b/electron/src/components/Icon.tsx
new file mode 100644
index 0000000..570aaff
--- /dev/null
+++ b/electron/src/components/Icon.tsx
@@ -0,0 +1,41 @@
+import {icons as lucideIcons} from "lucide-react";
+import React from "react";
+import { qitechIcons } from "./mxicon";
+
+type QitechIconName = `qi:${keyof typeof qitechIcons}`;
+
+type LucideIconName = `lu:${keyof typeof lucideIcons}`;
+
+// prefix keys with library to avoid conflicts with other icon libraries
+export type IconName = QitechIconName | LucideIconName;
+
+type Props = {
+ name?: IconName;
+ className?: string;
+};
+
+export const Icon = ({ name, className }: Props) => {
+ if (!name) {
+ return null;
+ }
+ const library = name.split(":")[0];
+ const rawIcon = name.split(":")[1];
+
+ if (library === "lu" && rawIcon in lucideIcons) {
+ const LucideIcon = lucideIcons[rawIcon as keyof typeof lucideIcons];
+ return ;
+ }
+
+ if (library === "qi" && rawIcon in qitechIcons) {
+ const QitechIcon = qitechIcons[rawIcon as keyof typeof qitechIcons];
+ return ;
+ }
+
+ console.error(`Icon ${name} not found`, library, rawIcon, lucideIcons);
+
+ return null;
+};
+
+export type IconNameMap = {
+ [key: string]: IconName;
+};
diff --git a/electron/src/components/OutsideCorner.tsx b/electron/src/components/OutsideCorner.tsx
new file mode 100644
index 0000000..dc47eb8
--- /dev/null
+++ b/electron/src/components/OutsideCorner.tsx
@@ -0,0 +1,40 @@
+import React from "react";
+
+export type OutsideCornerProps = {
+ rightTop?: boolean;
+ rightBottom?: boolean;
+ bottomLeft?: boolean;
+ bottomRight?: boolean;
+};
+
+export function OutsideCorner({
+ rightTop,
+ rightBottom,
+ bottomLeft,
+ bottomRight,
+}: OutsideCornerProps) {
+ return (
+ <>
+ {rightTop && (
+
+ )}
+ {rightBottom && (
+
+ )}
+ {bottomLeft && (
+
+ )}
+ {bottomRight && (
+
+ )}
+ >
+ );
+}
diff --git a/electron/src/components/SidebarLayout.tsx b/electron/src/components/SidebarLayout.tsx
new file mode 100644
index 0000000..75a6f2a
--- /dev/null
+++ b/electron/src/components/SidebarLayout.tsx
@@ -0,0 +1,44 @@
+import {Icon, IconName} from "./Icon";
+import { useOnSubpath } from "@/lib/useOnSubpath";
+import { OutsideCorner } from "./OutsideCorner";
+import {Link} from "@tanstack/react-router";
+import React from "react";
+
+type SideBarItemContent = {
+ link: string;
+ activeLink: string;
+ icon?: IconName;
+ title: string;
+}
+
+type SidebarItemProps = SideBarItemContent & {
+ isFirst: boolean;
+};
+
+export function SidebarItem({
+ link,
+ icon,
+ title,
+ isFirst,
+ activeLink
+}: SidebarItemProps) {
+ const isActive = useOnSubpath(activeLink);
+ return (
+
+
+ {icon && }
+ {title}
+
+ {isActive && }
+
+ );
+}
+
+export function SidebarLayout() {
+ return (
+ Hello World
+ )
+}
\ No newline at end of file
diff --git a/electron/src/components/Topbar.tsx b/electron/src/components/Topbar.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/electron/src/components/mxicon.tsx b/electron/src/components/mxicon.tsx
new file mode 100644
index 0000000..34b5cdb
--- /dev/null
+++ b/electron/src/components/mxicon.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+export const qitechIcons = {
+ Extruder: Extruder,
+};
+
+function Extruder({ className }: { className?: string }) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/electron/src/components/ui/sonner.tsx b/electron/src/components/ui/sonner.tsx
new file mode 100644
index 0000000..8100f92
--- /dev/null
+++ b/electron/src/components/ui/sonner.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { useTheme } from "next-themes";
+import {Toaster as Sonner, ToasterProps } from "sonner";
+
+const Toaster = ({...props}: ToasterProps) => {
+ const { theme = "system" } = useTheme();
+
+ return (
+
+ );
+};
+
+export { Toaster };
\ No newline at end of file
diff --git a/electron/src/lib/useOnSubpath.tsx b/electron/src/lib/useOnSubpath.tsx
new file mode 100644
index 0000000..62273ea
--- /dev/null
+++ b/electron/src/lib/useOnSubpath.tsx
@@ -0,0 +1,7 @@
+import { useRouterState } from "@tanstack/react-router";
+
+export function useOnSubpath(path: string) {
+ const {location } = useRouterState();
+ const onSubpath = location.pathname.startsWith(path);
+ return onSubpath;
+}
\ No newline at end of file
diff --git a/electron/src/routes/__root.tsx b/electron/src/routes/__root.tsx
index 3fa8f06..4027eaf 100644
--- a/electron/src/routes/__root.tsx
+++ b/electron/src/routes/__root.tsx
@@ -1,5 +1,4 @@
import React from "react";
-import BaseLayout from "@/layouts/BaseLayout";
import { Outlet, createRootRoute } from "@tanstack/react-router";
export const RootRoute = createRootRoute({
@@ -8,8 +7,10 @@ export const RootRoute = createRootRoute({
function Root() {
return (
-
+ <>
+
-
+
+ >
);
}
diff --git a/electron/src/routes/router.tsx b/electron/src/routes/router.tsx
index edbd2c2..6cd073e 100644
--- a/electron/src/routes/router.tsx
+++ b/electron/src/routes/router.tsx
@@ -8,6 +8,6 @@ declare module "@tanstack/react-router" {
}
const history = createMemoryHistory({
- initialEntries: ["/"],
+ initialEntries: ["/_sidebar/"],
});
export const router = createRouter({ routeTree: rootTree, history: history });
diff --git a/electron/src/routes/routes.tsx b/electron/src/routes/routes.tsx
index db5dbed..8cc6b4f 100644
--- a/electron/src/routes/routes.tsx
+++ b/electron/src/routes/routes.tsx
@@ -1,7 +1,8 @@
import { createRoute } from "@tanstack/react-router";
import { RootRoute } from "./__root";
-import HomePage from "../pages/HomePage";
-import SecondPage from "@/pages/SecondPage";
+import { SidebarLayout } from "@/components/SidebarLayout";
+import React from "react";
+
// TODO: Steps to add a new route:
// 1. Create a new page component in the '../pages/' directory (e.g., NewPage.tsx)
@@ -22,16 +23,10 @@ import SecondPage from "@/pages/SecondPage";
// 4. Add to routeTree: RootRoute.addChildren([HomeRoute, NewRoute, ...])
// 5. Add Link: New Page
-export const HomeRoute = createRoute({
+export const sidebarRoute = createRoute({
getParentRoute: () => RootRoute,
- path: "/",
- component: HomePage,
+ path: "_sidebar",
+ component:() => ,
});
-export const SecondPageRoute = createRoute({
- getParentRoute: () => RootRoute,
- path: "/second-page",
- component: SecondPage,
-});
-
-export const rootTree = RootRoute.addChildren([HomeRoute, SecondPageRoute]);
+export const rootTree = RootRoute.addChildren([sidebarRoute]);