mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
init
This commit is contained in:
13
src/layout/Content.tsx
Normal file
13
src/layout/Content.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
import { useIsMobile } from "../components/contexts/MobileContext";
|
||||
function Content({ children }) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<Flex flexDirection="column" w="100%" h="100vh" p={!isMobile ? 4 : 1}>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default Content;
|
48
src/layout/Hero.tsx
Normal file
48
src/layout/Hero.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { Box, Heading, Text } from "@chakra-ui/react";
|
||||
import { usePageContext } from "../renderer/usePageContext";
|
||||
import Routes from "../renderer/routes";
|
||||
import { useIsMobile } from "../components/contexts/MobileContext";
|
||||
|
||||
export default function Hero() {
|
||||
const pageContext = usePageContext();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Box p={2}>
|
||||
<Box>
|
||||
<Heading
|
||||
textAlign={isMobile ? "left" : "right"}
|
||||
minWidth="90px"
|
||||
maxWidth={"220px"}
|
||||
color="text.accent"
|
||||
as="h3"
|
||||
letterSpacing={"tight"}
|
||||
size="lg"
|
||||
>
|
||||
{Routes[normalizePath(pageContext.urlPathname)]?.heroLabel}
|
||||
</Heading>
|
||||
</Box>
|
||||
|
||||
<Text
|
||||
isTruncated
|
||||
maxWidth="100%"
|
||||
whiteSpace="nowrap"
|
||||
letterSpacing={"tight"}
|
||||
color="text.accent"
|
||||
textAlign={isMobile ? "left" : "right"}
|
||||
overflow="hidden"
|
||||
>
|
||||
{new Date().toLocaleDateString()}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const normalizePath = (path) => {
|
||||
if (!path) return "/";
|
||||
if (path.length > 1 && path.endsWith("/")) {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
return path.toLowerCase();
|
||||
};
|
21
src/layout/Layout.css
Normal file
21
src/layout/Layout.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/* CSS for people who'd rather be coding */
|
||||
|
||||
* {
|
||||
box-sizing: border-box; /* Because guessing sizes is for amateurs */
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #fffff0; /* White, like the light at the end of a dark terminal */
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #c0c0c0; /* Light gray, like the reflections of your code */
|
||||
}
|
||||
|
||||
/* Media query, because even I have standards */
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
font-size: 14px; /* For ants */
|
||||
}
|
||||
}
|
52
src/layout/Layout.tsx
Normal file
52
src/layout/Layout.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { PageContextProvider } from "../renderer/usePageContext";
|
||||
import { MobileProvider } from "../components/contexts/MobileContext";
|
||||
import LayoutComponent from "./LayoutComponent";
|
||||
import userOptionsStore from "../stores/UserOptionsStore";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Chakra } from "../components/contexts/ChakraContext";
|
||||
import { getTheme } from "./theme/color-themes";
|
||||
|
||||
export { Layout };
|
||||
|
||||
const Layout = observer(({ pageContext, children }) => {
|
||||
const [activeTheme, setActiveTheme] = useState<string>("darknight");
|
||||
|
||||
useEffect(() => {
|
||||
if (userOptionsStore.theme !== activeTheme) {
|
||||
setActiveTheme(userOptionsStore.theme);
|
||||
}
|
||||
}, [userOptionsStore.theme]);
|
||||
|
||||
try {
|
||||
if (pageContext?.headersOriginal) {
|
||||
const headers = new Headers(pageContext.headersOriginal);
|
||||
|
||||
const cookies = headers.get("cookie");
|
||||
|
||||
const userPreferencesCookie = cookies
|
||||
?.split("; ")
|
||||
.find((row) => row.startsWith("user_preferences="))
|
||||
?.split("=")[1];
|
||||
|
||||
try {
|
||||
const { theme: receivedTheme } = JSON.parse(
|
||||
atob(userPreferencesCookie ?? "{}"),
|
||||
);
|
||||
setActiveTheme(receivedTheme);
|
||||
} catch (e) {}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<PageContextProvider pageContext={pageContext}>
|
||||
<MobileProvider>
|
||||
<Chakra theme={getTheme(activeTheme)}>
|
||||
<LayoutComponent>{children}</LayoutComponent>
|
||||
</Chakra>
|
||||
</MobileProvider>
|
||||
</PageContextProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
});
|
35
src/layout/LayoutComponent.tsx
Normal file
35
src/layout/LayoutComponent.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { Grid, GridItem } from "@chakra-ui/react";
|
||||
import Navigation from "./Navigation";
|
||||
import Routes from "../renderer/routes";
|
||||
import Hero from "./Hero";
|
||||
import Content from "./Content";
|
||||
import { useIsMobile } from "../components/contexts/MobileContext";
|
||||
|
||||
export default function LayoutComponent({ children }) {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Grid
|
||||
templateAreas={
|
||||
isMobile
|
||||
? `"nav"
|
||||
"main"`
|
||||
: `"nav main"`
|
||||
}
|
||||
gridTemplateRows={isMobile ? "auto 1fr" : "1fr"}
|
||||
gridTemplateColumns={isMobile ? "1fr" : "auto 1fr"}
|
||||
minHeight="100vh"
|
||||
gap="1"
|
||||
>
|
||||
<GridItem area={"nav"} hidden={false}>
|
||||
<Navigation routeRegistry={Routes}>
|
||||
<Hero />
|
||||
</Navigation>
|
||||
</GridItem>
|
||||
<GridItem area={"main"}>
|
||||
<Content>{children}</Content>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
);
|
||||
}
|
43
src/layout/NavItem.tsx
Normal file
43
src/layout/NavItem.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Box } from "@chakra-ui/react";
|
||||
import React from "react";
|
||||
|
||||
function NavItem({ path, children, color, onClick, as, cursor }) {
|
||||
return (
|
||||
<Box
|
||||
as={as ?? "a"}
|
||||
href={path}
|
||||
mb={2}
|
||||
cursor={cursor}
|
||||
// ml={5}
|
||||
mr={2}
|
||||
color={color ?? "text.accent"}
|
||||
letterSpacing="normal"
|
||||
display="block"
|
||||
position="relative"
|
||||
textAlign="right"
|
||||
onClick={onClick}
|
||||
_after={{
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "2px",
|
||||
bottom: "0",
|
||||
left: "0",
|
||||
bg: "accent.secondary",
|
||||
transform: "scaleX(0)",
|
||||
transformOrigin: "right",
|
||||
transition: "transform 0.3s ease-in-out",
|
||||
}}
|
||||
_hover={{
|
||||
color: "tertiary.tertiary",
|
||||
_after: {
|
||||
transform: "scaleX(1)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavItem;
|
132
src/layout/Navigation.tsx
Normal file
132
src/layout/Navigation.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
Grid,
|
||||
GridItem,
|
||||
useBreakpointValue,
|
||||
} from "@chakra-ui/react";
|
||||
import { MenuIcon } from "lucide-react";
|
||||
import Sidebar from "./Sidebar";
|
||||
import NavItem from "./NavItem";
|
||||
import menuState from "../stores/AppMenuStore";
|
||||
import { usePageContext } from "../renderer/usePageContext";
|
||||
import { useIsMobile } from "../components/contexts/MobileContext";
|
||||
import { getTheme } from "./theme/color-themes";
|
||||
import userOptionsStore from "../stores/UserOptionsStore";
|
||||
|
||||
const Navigation = observer(({ children, routeRegistry }) => {
|
||||
const isMobile = useIsMobile();
|
||||
const pageContext = usePageContext();
|
||||
|
||||
const currentPath = pageContext.urlPathname || "/";
|
||||
|
||||
const getTopValue = () => {
|
||||
if (!isMobile) return undefined;
|
||||
if (currentPath === "/") return 12;
|
||||
return 0;
|
||||
};
|
||||
|
||||
const variant = useBreakpointValue(
|
||||
{
|
||||
base: "outline",
|
||||
md: "solid",
|
||||
},
|
||||
{
|
||||
fallback: "md",
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
menuState.closeMenu();
|
||||
}, [variant]);
|
||||
|
||||
return (
|
||||
<Grid templateColumns="1fr" templateRows="auto 1fr">
|
||||
<GridItem
|
||||
p={4}
|
||||
position="fixed"
|
||||
top={0}
|
||||
left={0}
|
||||
zIndex={1100}
|
||||
width={isMobile ? "20%" : "100%"}
|
||||
hidden={!isMobile}
|
||||
>
|
||||
<Grid templateColumns="auto 1fr" alignItems="center">
|
||||
<GridItem>
|
||||
<MenuIcon
|
||||
cursor="pointer"
|
||||
w={6}
|
||||
h={6}
|
||||
stroke={getTheme(userOptionsStore.theme).colors.text.accent}
|
||||
onClick={() => {
|
||||
switch (menuState.isOpen) {
|
||||
case true:
|
||||
menuState.closeMenu();
|
||||
break;
|
||||
case false:
|
||||
menuState.openMenu();
|
||||
break;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem>{children}</GridItem>
|
||||
</Grid>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Collapse
|
||||
hidden={isMobile && !menuState.isOpen}
|
||||
in={(isMobile && menuState.isOpen) || !isMobile}
|
||||
animateOpacity
|
||||
>
|
||||
<Grid
|
||||
as="nav"
|
||||
templateColumns="1fr"
|
||||
width="100%"
|
||||
h={isMobile ? "100vh" : "100vh"}
|
||||
top={getTopValue()}
|
||||
position={"relative"}
|
||||
bg={"transparent"}
|
||||
zIndex={1000}
|
||||
gap={4}
|
||||
p={isMobile ? 4 : 0}
|
||||
>
|
||||
<GridItem>{!isMobile && children}</GridItem>
|
||||
<GridItem>
|
||||
<Sidebar>
|
||||
{Object.keys(routeRegistry)
|
||||
.filter((p) => !routeRegistry[p].hideNav)
|
||||
.map((path) => (
|
||||
<NavItem key={path} path={path}>
|
||||
{routeRegistry[path].sidebarLabel}
|
||||
</NavItem>
|
||||
))}
|
||||
</Sidebar>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Collapse>
|
||||
</GridItem>
|
||||
{isMobile && (
|
||||
<Box
|
||||
position="fixed"
|
||||
top="0"
|
||||
left="0"
|
||||
right="0"
|
||||
height={menuState.isOpen ? "100vh" : "auto"}
|
||||
pointerEvents="none"
|
||||
zIndex={900}
|
||||
>
|
||||
<Box
|
||||
height="100%"
|
||||
transition="all 0.3s"
|
||||
opacity={menuState.isOpen ? 1 : 0}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
});
|
||||
|
||||
export default Navigation;
|
141
src/layout/Sidebar.tsx
Normal file
141
src/layout/Sidebar.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Flex, VStack } from "@chakra-ui/react";
|
||||
import NavItem from "./NavItem";
|
||||
import ToolBar from "../components/toolbar/Toolbar";
|
||||
import { useIsMobile } from "../components/contexts/MobileContext";
|
||||
import FeedbackModal from "../components/feedback/FeedbackModal";
|
||||
import { ThemeSelectionOptions } from "../components/ThemeSelection";
|
||||
|
||||
function LowerSidebarContainer({ children, isMobile, ...props }) {
|
||||
const bottom = isMobile ? undefined : "6rem";
|
||||
const position = isMobile ? "relative" : "absolute";
|
||||
return (
|
||||
<Box width="100%" m={0.99} position={position} bottom={bottom} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Sidebar({ children: navLinks }) {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<SidebarContainer isMobile={isMobile}>
|
||||
<VStack
|
||||
spacing={6}
|
||||
alignItems={isMobile ? "flex-start" : "flex-end"}
|
||||
letterSpacing="tighter"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
{navLinks}
|
||||
|
||||
<Box
|
||||
alignItems={isMobile ? "flex-start" : "flex-end"}
|
||||
bg="background.primary"
|
||||
zIndex={1000}
|
||||
width="100%"
|
||||
fontSize={"x-small"}
|
||||
>
|
||||
<LowerSidebarContainer isMobile={isMobile}>
|
||||
<ToolBar isMobile={isMobile} />
|
||||
<RegulatoryItems isMobile={isMobile} />
|
||||
<ThemeSelectionOptions />
|
||||
</LowerSidebarContainer>
|
||||
</Box>
|
||||
</VStack>
|
||||
|
||||
{!isMobile && <BreathingVerticalDivider />}
|
||||
</SidebarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function RegulatoryItems({ isMobile }) {
|
||||
const [isFeedbackModalOpen, setFeedbackModalOpen] = useState(false);
|
||||
|
||||
const openFeedbackModal = () => setFeedbackModalOpen(true);
|
||||
const closeFeedbackModal = () => setFeedbackModalOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack alignItems={isMobile ? "flex-start" : "flex-end"} spacing={1}>
|
||||
<NavItem
|
||||
color="text.tertiary"
|
||||
as={"span"}
|
||||
path=""
|
||||
cursor={"pointer"}
|
||||
onClick={() => {
|
||||
window.open("https://seemueller.ai");
|
||||
}}
|
||||
>
|
||||
seemueller.ai
|
||||
</NavItem>
|
||||
<NavItem
|
||||
color="text.tertiary"
|
||||
as={"span"}
|
||||
path=""
|
||||
cursor={"pointer"}
|
||||
onClick={openFeedbackModal}
|
||||
>
|
||||
Feedback
|
||||
</NavItem>
|
||||
<NavItem color="text.tertiary" path="/privacy-policy">
|
||||
Privacy Policy
|
||||
</NavItem>
|
||||
<NavItem color="text.tertiary" path="/terms-of-service">
|
||||
Terms of Service
|
||||
</NavItem>
|
||||
</VStack>
|
||||
|
||||
{/* Feedback Modal */}
|
||||
<FeedbackModal
|
||||
isOpen={isFeedbackModalOpen}
|
||||
onClose={closeFeedbackModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarContainer({ children, isMobile }) {
|
||||
return (
|
||||
<Flex
|
||||
mt={isMobile ? 28 : undefined}
|
||||
position="relative"
|
||||
height="100vh"
|
||||
width="100%"
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function BreathingVerticalDivider() {
|
||||
return (
|
||||
<Box
|
||||
position="absolute"
|
||||
h="150%"
|
||||
right={0}
|
||||
bottom={0}
|
||||
width="2px"
|
||||
background="text.secondary"
|
||||
animation="breathing 3s ease-in-out infinite"
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
@keyframes breathing {
|
||||
0%, 100% {
|
||||
opacity: 0.7;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scaleY(1.2);
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
19
src/layout/_IsMobileHook.ts
Normal file
19
src/layout/_IsMobileHook.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "@chakra-ui/react";
|
||||
|
||||
// Only use this when it is necessary to style responsively outside a MobileProvider.
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [isFallbackMobile] = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
useEffect(() => {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
const mobile =
|
||||
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
|
||||
userAgent.toLowerCase(),
|
||||
);
|
||||
setIsMobile(mobile);
|
||||
}, []);
|
||||
|
||||
return isMobile || isFallbackMobile;
|
||||
}
|
239
src/layout/theme/base_theme.ts
Normal file
239
src/layout/theme/base_theme.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import { extendTheme } from "@chakra-ui/react";
|
||||
|
||||
const fonts = {
|
||||
body: "monospace",
|
||||
heading: "monospace",
|
||||
};
|
||||
|
||||
const styles = {
|
||||
global: {
|
||||
body: {
|
||||
fontFamily: fonts.body,
|
||||
bg: "background.primary",
|
||||
color: "text.primary",
|
||||
margin: 0,
|
||||
overflow: "hidden",
|
||||
},
|
||||
html: {
|
||||
overflow: "hidden",
|
||||
},
|
||||
"::selection": {
|
||||
backgroundColor: "accent.secondary",
|
||||
color: "background.primary",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const components = {
|
||||
Button: {
|
||||
baseStyle: {
|
||||
fontWeight: "bold",
|
||||
borderRadius: "md", // Slightly rounded corners
|
||||
},
|
||||
variants: {
|
||||
solid: {
|
||||
bg: "accent.primary",
|
||||
color: "background.primary",
|
||||
_hover: {
|
||||
bg: "accent.primary",
|
||||
color: "background.primary",
|
||||
},
|
||||
},
|
||||
outline: {
|
||||
borderColor: "accent.primary",
|
||||
color: "text.primary",
|
||||
_hover: {
|
||||
bg: "accent.primary",
|
||||
color: "background.primary",
|
||||
},
|
||||
},
|
||||
ghost: {
|
||||
color: "text.primary",
|
||||
_hover: {
|
||||
bg: "background.secondary",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Link: {
|
||||
baseStyle: {
|
||||
color: "accent.secondary",
|
||||
_hover: {
|
||||
color: "accent.primary",
|
||||
textDecoration: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
Heading: {
|
||||
baseStyle: {
|
||||
color: "text.primary",
|
||||
letterSpacing: "tight",
|
||||
},
|
||||
sizes: {
|
||||
"4xl": { fontSize: ["6xl", null, "7xl"], lineHeight: 1 },
|
||||
"3xl": { fontSize: ["5xl", null, "6xl"], lineHeight: 1.2 },
|
||||
"2xl": { fontSize: ["4xl", null, "5xl"] },
|
||||
xl: { fontSize: ["3xl", null, "4xl"] },
|
||||
lg: { fontSize: ["2xl", null, "3xl"] },
|
||||
md: { fontSize: "xl" },
|
||||
sm: { fontSize: "md" },
|
||||
xs: { fontSize: "sm" },
|
||||
},
|
||||
},
|
||||
Text: {
|
||||
baseStyle: {
|
||||
color: "text.primary",
|
||||
},
|
||||
variants: {
|
||||
secondary: {
|
||||
color: "text.secondary",
|
||||
},
|
||||
accent: {
|
||||
color: "text.accent",
|
||||
},
|
||||
},
|
||||
},
|
||||
Input: {
|
||||
variants: {
|
||||
filled: {
|
||||
field: {
|
||||
bg: "background.secondary",
|
||||
_hover: {
|
||||
bg: "background.tertiary",
|
||||
},
|
||||
_focus: {
|
||||
bg: "background.tertiary",
|
||||
borderColor: "accent.primary",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MDXEditor: {
|
||||
baseStyle: (props) => ({
|
||||
border: "1px solid",
|
||||
borderColor: "text.primary",
|
||||
borderRadius: "lg",
|
||||
height: "sm",
|
||||
bg: "background.primary",
|
||||
color: "text.primary",
|
||||
boxShadow: "md",
|
||||
// p: 4,
|
||||
|
||||
".mdxeditor-toolbar": {
|
||||
border: "1px solid",
|
||||
borderColor: "text.primary",
|
||||
borderRadius: "xl",
|
||||
bg: "background.primary",
|
||||
m: 2,
|
||||
p: 2,
|
||||
// mb: 3,
|
||||
// p: 3,
|
||||
|
||||
"& button": {
|
||||
border: "none",
|
||||
borderRadius: "md",
|
||||
cursor: "pointer",
|
||||
px: 3,
|
||||
py: 1,
|
||||
mr: 2,
|
||||
transition: "all 0.3s ease",
|
||||
|
||||
_hover: {
|
||||
bg: "text.secondary",
|
||||
},
|
||||
_active: {
|
||||
bg: "background.primary",
|
||||
color: "text.primary",
|
||||
},
|
||||
'&[data-state="on"]': {
|
||||
bg: "transparent",
|
||||
fill: "text.primary",
|
||||
stroke: "text.primary",
|
||||
boxShadow: "0 0 0 2px var(--text-primary)",
|
||||
transform: "translateY(-1px)",
|
||||
transition: "all 0.2s ease",
|
||||
// border: '2px solid transparent', // No border needed for SVG
|
||||
},
|
||||
'&[data-state="off"]': {
|
||||
bg: "transparent",
|
||||
fill: "text.secondary",
|
||||
stroke: "text.secondary",
|
||||
opacity: 0.8,
|
||||
transition: "all 0.2s ease",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"[aria-label='editable markdown']": {
|
||||
color: "text.primary",
|
||||
},
|
||||
}),
|
||||
},
|
||||
CodeBlocks: {
|
||||
baseStyle: (props) => ({
|
||||
bg: "background.primary",
|
||||
// color: 'text.primary',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const Base_theme = extendTheme({
|
||||
config: {
|
||||
cssVarPrefix: "wgs",
|
||||
initialColorMode: "dark",
|
||||
useSystemColorMode: false,
|
||||
},
|
||||
fonts,
|
||||
styles,
|
||||
components,
|
||||
letterSpacings: {
|
||||
tighter: "-0.05em",
|
||||
tight: "-0.025em",
|
||||
normal: "0",
|
||||
wide: "0.025em",
|
||||
wider: "0.05em",
|
||||
widest: "0.1em",
|
||||
},
|
||||
space: {
|
||||
px: "1px",
|
||||
0.5: "0.125rem",
|
||||
1: "0.25rem",
|
||||
1.5: "0.375rem",
|
||||
2: "0.5rem",
|
||||
2.5: "0.625rem",
|
||||
3: "0.75rem",
|
||||
3.5: "0.875rem",
|
||||
4: "1rem",
|
||||
5: "1.25rem",
|
||||
6: "1.5rem",
|
||||
7: "1.75rem",
|
||||
8: "2rem",
|
||||
9: "2.25rem",
|
||||
10: "2.5rem",
|
||||
12: "3rem",
|
||||
14: "3.5rem",
|
||||
16: "4rem",
|
||||
18: "4.5rem",
|
||||
20: "5rem",
|
||||
22: "5.5rem",
|
||||
24: "6rem",
|
||||
28: "7rem",
|
||||
32: "8rem",
|
||||
34: "8.5rem",
|
||||
36: "9rem",
|
||||
38: "9.5rem",
|
||||
40: "10rem",
|
||||
44: "11rem",
|
||||
48: "12rem",
|
||||
52: "13rem",
|
||||
56: "14rem",
|
||||
60: "15rem",
|
||||
64: "16rem",
|
||||
72: "18rem",
|
||||
80: "20rem",
|
||||
96: "24rem",
|
||||
},
|
||||
});
|
||||
|
||||
export default Base_theme;
|
32
src/layout/theme/color-themes/AtomOne.ts
Normal file
32
src/layout/theme/color-themes/AtomOne.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
brand: {
|
||||
900: "#21252b",
|
||||
800: "#343a40",
|
||||
750: "#495057",
|
||||
700: "#525c65",
|
||||
600: "#90ee90",
|
||||
500: "#ffa07a",
|
||||
400: "#e0e0e0",
|
||||
300: "#ff69b4",
|
||||
200: "#da70d6",
|
||||
100: "#ffffff",
|
||||
},
|
||||
background: {
|
||||
primary: "#21252b",
|
||||
secondary: "#343a40",
|
||||
tertiary: "#495057",
|
||||
},
|
||||
text: {
|
||||
primary: "#e0e0e0",
|
||||
secondary: "#c0c0c0",
|
||||
tertiary: "#a9a9a9",
|
||||
accent: "#87cefa",
|
||||
link: "#87cefa",
|
||||
},
|
||||
accent: {
|
||||
primary: "#90ee90",
|
||||
secondary: "#ffa07a",
|
||||
danger: "#ff69b4",
|
||||
confirm: "#90ee90",
|
||||
},
|
||||
};
|
32
src/layout/theme/color-themes/Capuchin.ts
Normal file
32
src/layout/theme/color-themes/Capuchin.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
brand: {
|
||||
900: "#1E1E2E",
|
||||
800: "#302D41",
|
||||
750: "#332E41",
|
||||
700: "#575268",
|
||||
600: "#6E6C7E",
|
||||
500: "#988BA2",
|
||||
400: "#C3BAC6",
|
||||
300: "#D9E0EE",
|
||||
200: "#F5E0DC",
|
||||
100: "#FAE3B0",
|
||||
},
|
||||
background: {
|
||||
primary: "#1E1E2E",
|
||||
secondary: "#302D41",
|
||||
tertiary: "#575268",
|
||||
},
|
||||
text: {
|
||||
primary: "#D9E0EE",
|
||||
secondary: "#C3BAC6",
|
||||
tertiary: "#988BA2",
|
||||
accent: "#F5E0DC",
|
||||
link: "#96CDFB",
|
||||
},
|
||||
accent: {
|
||||
primary: "#F5C2E7",
|
||||
secondary: "#DDB6F2",
|
||||
danger: "#F28FAD",
|
||||
confirm: "#ABE9B3",
|
||||
},
|
||||
};
|
32
src/layout/theme/color-themes/Darknight.ts
Normal file
32
src/layout/theme/color-themes/Darknight.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
brand: {
|
||||
900: "#000000",
|
||||
800: "#333333",
|
||||
750: "#2B2B2B",
|
||||
700: "#666666",
|
||||
600: "#999999",
|
||||
500: "#CCCCCC",
|
||||
400: "#FFFFFF",
|
||||
300: "#F0F0F0",
|
||||
200: "#F8F9FA",
|
||||
100: "#FFFFFF",
|
||||
},
|
||||
background: {
|
||||
primary: "#000000",
|
||||
secondary: "#222222",
|
||||
tertiary: "#333333",
|
||||
},
|
||||
text: {
|
||||
primary: "#F0F0F0",
|
||||
secondary: "#CCCCCC",
|
||||
tertiary: "#999999",
|
||||
accent: "#FFFFFF",
|
||||
link: "#0d9488",
|
||||
},
|
||||
accent: {
|
||||
primary: "#FFFFFF",
|
||||
secondary: "#c0c0c0",
|
||||
danger: "#E53E3E",
|
||||
confirm: "#00D26A",
|
||||
},
|
||||
};
|
40
src/layout/theme/color-themes/OneDark.ts
Normal file
40
src/layout/theme/color-themes/OneDark.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export default {
|
||||
brand: {
|
||||
colors: {
|
||||
900: "#2C2E43",
|
||||
800: "#3D4162",
|
||||
750: "#4F5285",
|
||||
700: "#6076AC",
|
||||
600: "#7693D6",
|
||||
500: "#8DAFF0",
|
||||
400: "#A3C7FF",
|
||||
300: "#B9E0FF",
|
||||
200: "#CDF4FE",
|
||||
100: "#E1FEFF",
|
||||
},
|
||||
},
|
||||
|
||||
background: {
|
||||
primary: "linear-gradient(360deg, #15171C 100%, #353A47 100%)",
|
||||
|
||||
secondary: "#1B1F26",
|
||||
tertiary: "#1E1E2E",
|
||||
},
|
||||
|
||||
text: {
|
||||
primary: "#f8f8f8",
|
||||
secondary: "#3D4162",
|
||||
tertiary: "#e5ebff",
|
||||
accent: "#e6e6e6",
|
||||
link: "aquamarine",
|
||||
},
|
||||
|
||||
accent: {
|
||||
primary: "#127c91",
|
||||
|
||||
secondary: "#39b4bf",
|
||||
|
||||
danger: "#E74C3C",
|
||||
confirm: "#27AE60",
|
||||
},
|
||||
};
|
35
src/layout/theme/color-themes/VsCode.ts
Normal file
35
src/layout/theme/color-themes/VsCode.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export default {
|
||||
brand: {
|
||||
900: "#15171C",
|
||||
800: "#1B1F26",
|
||||
750: "#222731",
|
||||
700: "#353A47",
|
||||
600: "#535966",
|
||||
500: "#747C88",
|
||||
400: "#A0A4AC",
|
||||
300: "#C6CBDC",
|
||||
200: "#E6E9F0",
|
||||
100: "#F3F4F8",
|
||||
},
|
||||
|
||||
background: {
|
||||
primary: "#15171C",
|
||||
secondary: "#1B1F26",
|
||||
tertiary: "#353A47",
|
||||
},
|
||||
|
||||
text: {
|
||||
primary: "#ffffff",
|
||||
secondary: "#A0A4AC",
|
||||
tertiary: "#747C88",
|
||||
accent: "#E6E9F0",
|
||||
link: "#96CDFB",
|
||||
},
|
||||
|
||||
accent: {
|
||||
primary: "#0095ff",
|
||||
secondary: "#00acff",
|
||||
danger: "#EA4D4D",
|
||||
confirm: "#10CE8D",
|
||||
},
|
||||
};
|
50
src/layout/theme/color-themes/index.ts
Normal file
50
src/layout/theme/color-themes/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { extendTheme } from "@chakra-ui/react";
|
||||
import BaseTheme from "../base_theme";
|
||||
import DarknightColors from "./Darknight";
|
||||
import CapuchinColors from "./Capuchin";
|
||||
import VsCodeColors from "./VsCode";
|
||||
import OneDark from "./OneDark";
|
||||
|
||||
export function getColorThemes() {
|
||||
return [
|
||||
{ name: "darknight", colors: DarknightColors },
|
||||
{ name: "onedark", colors: OneDark },
|
||||
{ name: "capuchin", colors: CapuchinColors },
|
||||
{ name: "vscode", colors: VsCodeColors },
|
||||
];
|
||||
}
|
||||
|
||||
const darknight = extendTheme({
|
||||
...BaseTheme,
|
||||
colors: DarknightColors,
|
||||
});
|
||||
|
||||
const capuchin = extendTheme({
|
||||
...BaseTheme,
|
||||
colors: CapuchinColors,
|
||||
});
|
||||
|
||||
const vsCode = extendTheme({
|
||||
...BaseTheme,
|
||||
colors: VsCodeColors,
|
||||
});
|
||||
|
||||
const onedark = extendTheme({
|
||||
...BaseTheme,
|
||||
colors: OneDark,
|
||||
});
|
||||
|
||||
export function getTheme(theme: string) {
|
||||
switch (theme) {
|
||||
case "onedark":
|
||||
return onedark;
|
||||
case "darknight":
|
||||
return darknight;
|
||||
case "capuchin":
|
||||
return capuchin;
|
||||
case "vscode":
|
||||
return vsCode;
|
||||
default:
|
||||
return darknight;
|
||||
}
|
||||
}
|
33
src/layout/useMaxWidth.ts
Normal file
33
src/layout/useMaxWidth.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useIsMobile } from "../components/contexts/MobileContext";
|
||||
|
||||
export const useMaxWidth = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const [maxWidth, setMaxWidth] = useState("600px");
|
||||
|
||||
const calculateMaxWidth = () => {
|
||||
if (isMobile) {
|
||||
setMaxWidth("800px");
|
||||
} else if (window.innerWidth < 1024) {
|
||||
setMaxWidth("500px");
|
||||
} else {
|
||||
setMaxWidth("800px");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
calculateMaxWidth();
|
||||
|
||||
const handleResize = () => {
|
||||
calculateMaxWidth();
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [isMobile]);
|
||||
|
||||
return maxWidth;
|
||||
};
|
26
src/layout/usePageLoaded.ts
Normal file
26
src/layout/usePageLoaded.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const usePageLoaded = (callback: () => void) => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePageLoad = () => {
|
||||
setIsLoaded(true);
|
||||
callback();
|
||||
};
|
||||
|
||||
if (document.readyState === "complete") {
|
||||
// Page is already fully loaded
|
||||
handlePageLoad();
|
||||
} else {
|
||||
// Wait for the page to load
|
||||
window.addEventListener("load", handlePageLoad);
|
||||
}
|
||||
|
||||
return () => window.removeEventListener("load", handlePageLoad);
|
||||
}, [callback]);
|
||||
|
||||
return isLoaded;
|
||||
};
|
||||
|
||||
export default usePageLoaded;
|
Reference in New Issue
Block a user