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]);