mirror of
https://github.com/AykutSarac/jsoncrack.com.git
synced 2025-01-27 15:22:56 +08:00
feat: UI improvements
This commit is contained in:
parent
b83917c6bb
commit
e6cb91bb74
32
src/components/Loading/index.tsx
Normal file
32
src/components/Loading/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { LoadingOverlay } from "@mantine/core";
|
||||
|
||||
export const Loading = () => {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleStart = (url: string) => {
|
||||
return url !== router.asPath && url === "/editor" && setLoading(true);
|
||||
};
|
||||
|
||||
const handleComplete = (url: string) => url === router.asPath && setLoading(false);
|
||||
|
||||
router.events.on("routeChangeStart", handleStart);
|
||||
router.events.on("routeChangeComplete", handleComplete);
|
||||
router.events.on("routeChangeError", handleComplete);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", handleStart);
|
||||
router.events.off("routeChangeComplete", handleComplete);
|
||||
router.events.off("routeChangeError", handleComplete);
|
||||
};
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <LoadingOverlay visible loaderProps={{ color: "orange", type: "oval" }} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { LoadingOverlay } from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import Editor, { loader, useMonaco } from "@monaco-editor/react";
|
||||
import { Loading } from "src/layout/Loading";
|
||||
import useConfig from "src/store/useConfig";
|
||||
import useFile from "src/store/useFile";
|
||||
|
||||
@ -81,7 +81,7 @@ export const MonacoEditor = () => {
|
||||
options={editorOptions}
|
||||
onValidate={errors => setError(errors[0]?.message)}
|
||||
onChange={contents => setContents({ contents, skipUpdate: true })}
|
||||
loading={<Loading message="Loading Monaco Editor..." loading />}
|
||||
loading={<LoadingOverlay visible />}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import { Menu, Text } from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import { firaMono } from "src/constants/fonts";
|
||||
import { Graph } from "src/containers/Views/GraphView";
|
||||
@ -32,44 +31,8 @@ const View = () => {
|
||||
};
|
||||
|
||||
const LiveEditor: React.FC = () => {
|
||||
const [contextOpened, setContextOpened] = React.useState(false);
|
||||
const [contextPosition, setContextPosition] = React.useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledLiveEditor
|
||||
onContextMenuCapture={e => {
|
||||
e.preventDefault();
|
||||
setContextOpened(true);
|
||||
setContextPosition({ x: e.pageX, y: e.pageY });
|
||||
}}
|
||||
onClick={() => setContextOpened(false)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: contextPosition.y,
|
||||
left: contextPosition.x,
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<Menu opened={false} shadow="sm">
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item>
|
||||
<Text size="xs">Download as Image</Text>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Text size="xs">Zoom to Fit</Text>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Text size="xs">Rotate</Text>
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
<StyledLiveEditor onContextMenuCapture={e => e.preventDefault()}>
|
||||
<View />
|
||||
</StyledLiveEditor>
|
||||
);
|
||||
|
@ -55,41 +55,34 @@ export const TypeModal: React.FC<ModalProps> = ({ opened, onClose }) => {
|
||||
return typeOptions[typeOptions.findIndex(o => o.value === selectedType)]?.lang;
|
||||
}, [selectedType]);
|
||||
|
||||
const transformer = React.useCallback(
|
||||
async ({ value }) => {
|
||||
const { run } = await import("json_typegen_wasm");
|
||||
return run(
|
||||
"Root",
|
||||
value,
|
||||
JSON.stringify({
|
||||
output_mode: selectedType,
|
||||
})
|
||||
);
|
||||
},
|
||||
[selectedType]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (opened) {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (selectedType === Language.Go) {
|
||||
import("src/lib/utils/json2go").then(jtg => {
|
||||
import("gofmt.js").then(gofmt => {
|
||||
const types = jtg.default(getJson());
|
||||
setType(gofmt.default(types.go));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
transformer({ value: getJson() }).then(setType);
|
||||
(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const json = getJson();
|
||||
|
||||
if (selectedType === Language.Go) {
|
||||
const jtg = await import("src/lib/utils/json2go");
|
||||
const gofmt = await import("gofmt.js");
|
||||
|
||||
const types = jtg.default(json);
|
||||
setType(gofmt.default(types.go));
|
||||
} else {
|
||||
const { run } = await import("json_typegen_wasm");
|
||||
const output_mode = selectedType;
|
||||
const types = run("Root", json, JSON.stringify({ output_mode }));
|
||||
|
||||
setType(types);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [getJson, opened, selectedType, transformer]);
|
||||
}, [getJson, opened, selectedType]);
|
||||
|
||||
return (
|
||||
<Modal title="Generate Types" size="md" opened={opened} onClose={onClose} centered>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Badge, Flex, Group, Select, Text } from "@mantine/core";
|
||||
import { Badge, Flex, Group, Indicator, Select, Text } from "@mantine/core";
|
||||
import { useSessionStorage } from "@mantine/hooks";
|
||||
import toast from "react-hot-toast";
|
||||
import { AiOutlineFullscreen } from "react-icons/ai";
|
||||
import { AiFillGift } from "react-icons/ai";
|
||||
@ -35,6 +36,10 @@ export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false })
|
||||
const setFormat = useFile(state => state.setFormat);
|
||||
const format = useFile(state => state.format);
|
||||
const premium = useUser(state => state.premium);
|
||||
const [seenPremium, setSeenPremium] = useSessionStorage({
|
||||
key: "seenPremium",
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<Styles.StyledTools>
|
||||
@ -74,11 +79,30 @@ export const Toolbar: React.FC<{ isWidget?: boolean }> = ({ isWidget = false })
|
||||
)}
|
||||
<Group gap="xs" justify="right" w="100%" style={{ flexWrap: "nowrap" }}>
|
||||
{!premium && !isWidget && (
|
||||
<Styles.StyledToolElement onClick={() => setVisible("premium")(true)}>
|
||||
<Text display="flex" c="teal" fz="xs" fw={600} style={{ textAlign: "center", gap: 4 }}>
|
||||
<AiFillGift size="18" />
|
||||
Get Premium
|
||||
</Text>
|
||||
<Styles.StyledToolElement
|
||||
onClick={() => {
|
||||
setSeenPremium(true);
|
||||
setVisible("premium")(true);
|
||||
}}
|
||||
>
|
||||
<Indicator
|
||||
size={5}
|
||||
color="green"
|
||||
position="top-start"
|
||||
processing
|
||||
disabled={seenPremium}
|
||||
>
|
||||
<Text
|
||||
display="flex"
|
||||
c="teal"
|
||||
fz="xs"
|
||||
fw={600}
|
||||
style={{ textAlign: "center", gap: 4 }}
|
||||
>
|
||||
<AiFillGift size="18" />
|
||||
Get Premium
|
||||
</Text>
|
||||
</Indicator>
|
||||
</Styles.StyledToolElement>
|
||||
)}
|
||||
|
||||
|
@ -1,10 +1,5 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { EdgeProps } from "reaflow/dist/symbols/Edge";
|
||||
|
||||
const Edge = dynamic(() => import("reaflow").then(r => r.Edge), {
|
||||
ssr: false,
|
||||
});
|
||||
import { Edge, EdgeProps } from "reaflow";
|
||||
|
||||
const CustomEdgeWrapper = (props: EdgeProps) => {
|
||||
return <Edge containerClassName={`edge-${props.id}`} {...props} />;
|
||||
|
@ -1,16 +1,11 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { NodeProps } from "reaflow";
|
||||
import { Node, NodeProps } from "reaflow";
|
||||
import useGraph from "src/store/useGraph";
|
||||
import useModal from "src/store/useModal";
|
||||
import { NodeData } from "src/types/graph";
|
||||
import { ObjectNode } from "./ObjectNode";
|
||||
import { TextNode } from "./TextNode";
|
||||
|
||||
const Node = dynamic(() => import("reaflow").then(r => r.Node), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export interface CustomNodeProps {
|
||||
node: NodeData;
|
||||
x: number;
|
||||
|
@ -1,22 +1,18 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { LoadingOverlay } from "@mantine/core";
|
||||
import styled from "styled-components";
|
||||
import debounce from "lodash.debounce";
|
||||
import { Space } from "react-zoomable-ui";
|
||||
import { Canvas } from "reaflow";
|
||||
import { ElkRoot } from "reaflow/dist/layout/useLayout";
|
||||
import { useLongPress } from "use-long-press";
|
||||
import { CustomNode } from "src/containers/Views/GraphView/CustomNode";
|
||||
import useToggleHide from "src/hooks/useToggleHide";
|
||||
import { Loading } from "src/layout/Loading";
|
||||
import useConfig from "src/store/useConfig";
|
||||
import useGraph from "src/store/useGraph";
|
||||
import { CustomEdge } from "./CustomEdge";
|
||||
import { PremiumView } from "./PremiumView";
|
||||
|
||||
const Canvas = dynamic(() => import("reaflow").then(r => r.Canvas), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
interface GraphProps {
|
||||
isWidget?: boolean;
|
||||
}
|
||||
@ -175,7 +171,7 @@ export const Graph = ({ isWidget = false }: GraphProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Loading loading={loading} message="Painting graph..." />
|
||||
<LoadingOverlay visible={loading} />
|
||||
<StyledEditorWrapper
|
||||
$widget={isWidget}
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
|
@ -1,57 +0,0 @@
|
||||
import React from "react";
|
||||
import { Center, Stack, Text } from "@mantine/core";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import { JSONCrackLogo } from "../JsonCrackLogo";
|
||||
|
||||
interface LoadingProps {
|
||||
loading?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const fadeIn = keyframes`
|
||||
99% {
|
||||
visibility: hidden;
|
||||
}
|
||||
100% {
|
||||
visibility: visible;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLoading = styled.div<{ $visible: boolean }>`
|
||||
display: ${({ $visible }) => ($visible ? "grid" : "none")};
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
place-content: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
z-index: 100;
|
||||
pointer-events: visiblePainted;
|
||||
animation: 200ms ${fadeIn};
|
||||
animation-fill-mode: forwards;
|
||||
visibility: hidden;
|
||||
background: ${({ theme }) => theme.BACKGROUND_NODE};
|
||||
opacity: 0.8;
|
||||
color: ${({ theme }) => theme.INTERACTIVE_HOVER};
|
||||
cursor: wait;
|
||||
|
||||
img {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
`;
|
||||
|
||||
export const Loading = ({ loading = false, message }: LoadingProps) => {
|
||||
return (
|
||||
<Center mx="auto">
|
||||
<StyledLoading $visible={loading}>
|
||||
<Stack>
|
||||
<JSONCrackLogo fontSize="3rem" />
|
||||
<Text fz="lg" fw="bold">
|
||||
{message ?? "Preparing the environment for you..."}
|
||||
</Text>
|
||||
</Stack>
|
||||
</StyledLoading>
|
||||
</Center>
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { Box, Burger, Button, Flex, Overlay } from "@mantine/core";
|
||||
import { Button } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import styled from "styled-components";
|
||||
import useUser from "src/store/useUser";
|
||||
@ -26,18 +26,6 @@ const StyledNavbar = styled.nav`
|
||||
@media only screen and (max-width: 768px) {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.hide-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Left = styled.div``;
|
||||
@ -98,74 +86,28 @@ export const Navbar = () => {
|
||||
</Button>
|
||||
</Left>
|
||||
<Right>
|
||||
{!hasSession && (
|
||||
<>
|
||||
<Button
|
||||
component="a"
|
||||
href="https://app.jsoncrack.com/sign-in"
|
||||
variant="outline"
|
||||
color="gray"
|
||||
className="hide-mobile"
|
||||
radius="md"
|
||||
visibleFrom="sm"
|
||||
size="md"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href={premium ? "https://app.jsoncrack.com/editor" : "/editor"}
|
||||
color="dark"
|
||||
className="hide-mobile"
|
||||
visibleFrom="sm"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
Editor
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{hasSession && (
|
||||
<Button
|
||||
color="dark"
|
||||
size="md"
|
||||
radius="md"
|
||||
component={Link}
|
||||
href="/editor"
|
||||
prefetch={false}
|
||||
visibleFrom="sm"
|
||||
>
|
||||
Editor
|
||||
</Button>
|
||||
)}
|
||||
<Burger opened={opened} onClick={toggle} aria-label="Toggle navigation" hiddenFrom="sm" />
|
||||
{opened && (
|
||||
<Overlay top={56} h="100dvh">
|
||||
<Box
|
||||
bg="white"
|
||||
top={56}
|
||||
left={0}
|
||||
pos="fixed"
|
||||
w="100%"
|
||||
pb="lg"
|
||||
style={{ zIndex: 3, borderBottom: "1px solid black" }}
|
||||
>
|
||||
<Flex pt="lg" direction="column" align="center" justify="center" gap="lg">
|
||||
<Button
|
||||
component={Link}
|
||||
href="/pricing"
|
||||
variant="transparent"
|
||||
color="dark"
|
||||
radius="md"
|
||||
onClick={toggle}
|
||||
>
|
||||
Pricing
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Overlay>
|
||||
)}
|
||||
<Button
|
||||
component="a"
|
||||
href="https://app.jsoncrack.com/sign-in"
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
radius="xl"
|
||||
visibleFrom="sm"
|
||||
size="md"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href={premium ? "https://app.jsoncrack.com/editor" : "/editor"}
|
||||
color="dark"
|
||||
visibleFrom="sm"
|
||||
radius="xl"
|
||||
size="md"
|
||||
>
|
||||
Editor
|
||||
</Button>
|
||||
</Right>
|
||||
</StyledNavbar>
|
||||
</StyledNavbarWrapper>
|
||||
|
@ -8,12 +8,14 @@ import "@mantine/core/styles.css";
|
||||
import "@mantine/code-highlight/styles.css";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import ReactGA from "react-ga4";
|
||||
import { Loading } from "src/components/Loading";
|
||||
import GlobalStyle from "src/constants/globalStyle";
|
||||
import { lightTheme } from "src/constants/theme";
|
||||
import { supabase } from "src/lib/api/supabase";
|
||||
import useUser from "src/store/useUser";
|
||||
|
||||
const Toaster = dynamic(() => import("react-hot-toast").then(c => c.Toaster));
|
||||
const ExternalMode = dynamic(() => import("src/layout/ExternalMode"));
|
||||
|
||||
const mantineTheme = createTheme({
|
||||
primaryShade: 8,
|
||||
@ -24,8 +26,6 @@ const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;
|
||||
|
||||
ReactGA.initialize(GA_TRACKING_ID, { testMode: isDevelopment });
|
||||
|
||||
const ExternalMode = dynamic(() => import("src/layout/ExternalMode"));
|
||||
|
||||
function JsonCrack({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
const setSession = useUser(state => state.setSession);
|
||||
@ -72,6 +72,7 @@ function JsonCrack({ Component, pageProps }: AppProps) {
|
||||
}}
|
||||
/>
|
||||
<GlobalStyle />
|
||||
<Loading />
|
||||
<Component {...pageProps} />
|
||||
<ExternalMode />
|
||||
</ThemeProvider>
|
||||
|
@ -1,16 +1,12 @@
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import styled from "styled-components";
|
||||
import { BottomBar } from "src/containers/Editor/BottomBar";
|
||||
import Panes from "src/containers/Editor/Panes";
|
||||
import { Toolbar } from "src/containers/Toolbar";
|
||||
import { EditorWrapper } from "src/layout/EditorWrapper";
|
||||
import { Loading } from "src/layout/Loading";
|
||||
import useFile from "src/store/useFile";
|
||||
import useJson from "src/store/useJson";
|
||||
|
||||
const Panes = dynamic(() => import("src/containers/Editor/Panes"));
|
||||
|
||||
export const StyledPageWrapper = styled.div`
|
||||
height: calc(100vh - 27px);
|
||||
@ -29,7 +25,6 @@ export const StyledEditorWrapper = styled.div`
|
||||
|
||||
const EditorPage: React.FC = () => {
|
||||
const { query, isReady } = useRouter();
|
||||
const loading = useJson(state => state.loading);
|
||||
const hasQuery = React.useMemo(() => Object.keys(query).length > 0, [query]);
|
||||
const checkEditorSession = useFile(state => state.checkEditorSession);
|
||||
|
||||
@ -37,24 +32,13 @@ const EditorPage: React.FC = () => {
|
||||
if (isReady) checkEditorSession(query?.json);
|
||||
}, [checkEditorSession, isReady, query]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<StyledEditorWrapper>
|
||||
<Head>
|
||||
<title>Editor | JSON Crack</title>
|
||||
{hasQuery && <meta name="robots" content="noindex,nofollow" />}
|
||||
</Head>
|
||||
<Loading message="Preparing the editor for you..." loading />
|
||||
</StyledEditorWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EditorWrapper>
|
||||
<StyledEditorWrapper>
|
||||
<Head>
|
||||
<title>Editor | JSON Crack</title>
|
||||
<link rel="canonical" href="https://jsoncrack.com/editor" />
|
||||
{hasQuery && <meta name="robots" content="noindex,nofollow" />}
|
||||
</Head>
|
||||
<StyledPageWrapper>
|
||||
<Toolbar />
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
Center,
|
||||
Flex,
|
||||
Grid,
|
||||
Group,
|
||||
Image,
|
||||
Paper,
|
||||
Stack,
|
||||
@ -21,7 +20,6 @@ import { Carousel } from "@mantine/carousel";
|
||||
import "@mantine/carousel/styles.css";
|
||||
import styled from "styled-components";
|
||||
import { BiChevronDown } from "react-icons/bi";
|
||||
import { FaRocket } from "react-icons/fa";
|
||||
import {
|
||||
MdChevronRight,
|
||||
MdCompare,
|
||||
@ -78,19 +76,19 @@ const StyledHeroSectionBody = styled.div`
|
||||
|
||||
const StyledHeroText = styled.p`
|
||||
font-size: 0.8rem;
|
||||
color: #414141;
|
||||
color: #5b5b5b;
|
||||
font-weight: 400;
|
||||
max-width: 100%;
|
||||
min-width: 400px;
|
||||
text-align: center;
|
||||
|
||||
@media only screen and (min-width: 576px) {
|
||||
font-size: 1.3rem;
|
||||
font-size: 1.2rem;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1400px) {
|
||||
font-size: 1.4rem;
|
||||
font-size: 1.3rem;
|
||||
max-width: 60%;
|
||||
}
|
||||
`;
|
||||
@ -212,77 +210,51 @@ export const HomePage = () => {
|
||||
Experience the ultimate online editor designed to empower you in visualizing,
|
||||
refining, and formatting data effortlessly.
|
||||
</StyledHeroText>
|
||||
<Group justify="center">
|
||||
<Button
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/editor"
|
||||
size="xl"
|
||||
fw="bold"
|
||||
color="indigo"
|
||||
rightSection={<MdChevronRight size={30} />}
|
||||
visibleFrom="sm"
|
||||
radius="md"
|
||||
>
|
||||
Go To Editor
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/editor"
|
||||
fw="bold"
|
||||
size="md"
|
||||
color="indigo"
|
||||
rightSection={<MdChevronRight size={24} />}
|
||||
hiddenFrom="sm"
|
||||
radius="md"
|
||||
>
|
||||
Go To Editor
|
||||
</Button>
|
||||
<Button
|
||||
component="a"
|
||||
href="/#features"
|
||||
size="xl"
|
||||
fw="bold"
|
||||
variant="outline"
|
||||
color="gray.7"
|
||||
leftSection={<FaRocket />}
|
||||
visibleFrom="sm"
|
||||
radius="md"
|
||||
>
|
||||
Explore Premium
|
||||
</Button>
|
||||
<Button
|
||||
component="a"
|
||||
href="/#features"
|
||||
fw="bold"
|
||||
size="md"
|
||||
variant="outline"
|
||||
color="gray.7"
|
||||
leftSection={<FaRocket />}
|
||||
hiddenFrom="sm"
|
||||
radius="md"
|
||||
>
|
||||
Explore Premium
|
||||
</Button>
|
||||
</Group>
|
||||
<Flex gap="xs">
|
||||
<Badge color="dark" radius="sm" variant="light">
|
||||
<Badge size="xs" color="dark" radius="sm" variant="light">
|
||||
JSON
|
||||
</Badge>
|
||||
<Badge color="dark" radius="sm" variant="light">
|
||||
<Badge size="xs" color="dark" radius="sm" variant="light">
|
||||
YAML
|
||||
</Badge>
|
||||
<Badge color="dark" radius="sm" variant="light">
|
||||
<Badge size="xs" color="dark" radius="sm" variant="light">
|
||||
CSV
|
||||
</Badge>
|
||||
<Badge color="dark" radius="sm" variant="light">
|
||||
<Badge size="xs" color="dark" radius="sm" variant="light">
|
||||
XML
|
||||
</Badge>
|
||||
<Badge color="dark" radius="sm" variant="light">
|
||||
<Badge size="xs" color="dark" radius="sm" variant="light">
|
||||
TOML
|
||||
</Badge>
|
||||
</Flex>
|
||||
<Button
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/editor"
|
||||
size="xl"
|
||||
fw="bold"
|
||||
color="orange"
|
||||
rightSection={<MdChevronRight size={30} />}
|
||||
visibleFrom="sm"
|
||||
radius="xl"
|
||||
mt="lg"
|
||||
>
|
||||
Go to Editor
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
prefetch={false}
|
||||
href="/editor"
|
||||
fw="bold"
|
||||
size="md"
|
||||
color="indigo"
|
||||
rightSection={<MdChevronRight size={24} />}
|
||||
hiddenFrom="sm"
|
||||
radius="xl"
|
||||
mt="lg"
|
||||
>
|
||||
Go to Editor
|
||||
</Button>
|
||||
</Stack>
|
||||
</StyledHeroSectionBody>
|
||||
</StyledHeroSection>
|
||||
|
Loading…
x
Reference in New Issue
Block a user