From 9bdde9f7ad81a54a2fe4c102d89b2c2e38e27ff4 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:27:12 +0100 Subject: [PATCH 1/2] wip --- website/src/components/layout/site/header.tsx | 54 ++- website/src/components/mdx/code-block.tsx | 76 ++++- website/src/components/mdx/copy.tsx | 17 +- website/src/components/mdx/play.tsx | 47 +++ .../components/widgets/community-section.tsx | 6 +- website/src/images/icons/debug-start.svg | 3 + website/src/pages/example-graphql-api.tsx | 322 ++++++++++++++++++ 7 files changed, 472 insertions(+), 53 deletions(-) create mode 100644 website/src/components/mdx/play.tsx create mode 100644 website/src/images/icons/debug-start.svg create mode 100644 website/src/pages/example-graphql-api.tsx diff --git a/website/src/components/layout/site/header.tsx b/website/src/components/layout/site/header.tsx index 8534fd7b6e0..f3a1d96c87a 100644 --- a/website/src/components/layout/site/header.tsx +++ b/website/src/components/layout/site/header.tsx @@ -17,7 +17,12 @@ import { WorkshopNdcOsloImage, WorkshopOnlineImage, } from "@/components/images"; -import { IconContainer, Link, SearchModal } from "@/components/misc"; +import { + IconContainer, + Link, + LinkButton, + SearchModal, +} from "@/components/misc"; import { Icon, Logo } from "@/components/sprites"; import { GitHubStarButton } from "@/components/widgets"; import { @@ -502,7 +507,10 @@ const PlatformNavItem: FC = ({ -
Nitro (fka Banana Cake Pop)
+
+ Nitro (fka Banana Cake + Pop) +
GraphQL IDE / API Cockpit
@@ -689,10 +697,11 @@ const DeveloperNavItem: FC = ({ {products.map((product, index) => ( @@ -927,7 +936,7 @@ const DemoAndLaunch: FC = ({ tools }) => { > Request a Demo - Launch + Launch ); }; @@ -1179,9 +1188,9 @@ const SubNav = styled.div.attrs({ overflow-y: initial; ${ApplyBackdropBlur( - 48, - `background-color: ${THEME_COLORS.backgroundMenu};` -)} + 48, + `background-color: ${THEME_COLORS.backgroundMenu};` + )} } `; @@ -1528,31 +1537,6 @@ const Tools = styled.div` } `; -const LaunchLink = styled(Link)` - display: flex; - flex: 0 0 auto; - align-items: center; - box-sizing: border-box; - border-radius: var(--button-border-radius); - height: 38px; - padding: 0 30px; - border: 2px solid ${THEME_COLORS.primaryButtonBorder}; - color: ${THEME_COLORS.primaryButtonText}; - background-color: ${THEME_COLORS.primaryButton}; - font-family: ${FONT_FAMILY_HEADING}; - font-size: 0.875rem; - text-decoration: none; - font-weight: 500; - transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out, - color 0.2s ease-in-out; - - :hover { - border-color: ${THEME_COLORS.primaryButtonBorder}; - color: ${THEME_COLORS.primaryButtonHoverText}; - background-color: ${THEME_COLORS.primaryButtonHover}; - } -`; - const RequestDemoLink = styled(Link)` display: flex; flex: 0 0 auto; diff --git a/website/src/components/mdx/code-block.tsx b/website/src/components/mdx/code-block.tsx index 3582d0213c8..1eaee959ef8 100644 --- a/website/src/components/mdx/code-block.tsx +++ b/website/src/components/mdx/code-block.tsx @@ -3,17 +3,22 @@ import Prism from "prismjs"; import React, { FC } from "react"; import styled, { css } from "styled-components"; +import { Play } from "@/components/mdx/play"; import { FONT_FAMILY_CODE, THEME_COLORS } from "@/style"; import { Copy } from "./copy"; export interface CodeBlockProps { readonly children: any; readonly language?: Language; + readonly hideLanguageIndicator?: boolean; + readonly playUrl?: string; } export const CodeBlock: FC = ({ children, language: fallbackLanguage, + hideLanguageIndicator, + playUrl, }) => { const language = (children.props?.className?.replace(/language-/, "") as Language) || @@ -24,10 +29,16 @@ export const CodeBlock: FC = ({ return ( - - - - + {!hideLanguageIndicator && language ? ( + + ) : null} + + + {playUrl ? : null} + + + + pre[class*="language-"] { margin: 0; - padding: 30px 20px; + padding: 40px 30px; + width: 100%; + overflow: visible; + } + + @media only screen and (min-width: 700px) { + > pre[class*="language-"] { + padding: 40px 40px; + } } `; -const CopyPosition = styled.div` +const ActionsContainer = styled.div` position: absolute; - z-index: 1; - top: 0; - right: 0; + z-index: 2; + top: 12px; + right: 12px; + display: flex; + align-items: center; + gap: 6px; +`; + +const sharedButtonStyles = css` + border-radius: var(--button-border-radius) !important; + border: 1px solid ${THEME_COLORS.boxBorder} !important; + padding: 10px !important; + background-color: ${THEME_COLORS.background} !important; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2) !important; + transition: all 0.2s ease-in-out !important; + + svg { + width: 20px !important; + height: 20px !important; + } + + > div { + width: 20px !important; + height: 20px !important; + } + + &:hover { + background-color: ${THEME_COLORS.backgroundAlt} !important; + transform: translateY(-2px); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25) !important; + } +`; + +const StyledCopy = styled(Copy)` + ${sharedButtonStyles} +`; + +const StyledPlay = styled(Play)` + ${sharedButtonStyles} `; diff --git a/website/src/components/mdx/copy.tsx b/website/src/components/mdx/copy.tsx index eaf9559e645..c4db1b61cf8 100644 --- a/website/src/components/mdx/copy.tsx +++ b/website/src/components/mdx/copy.tsx @@ -22,27 +22,34 @@ function copyToClipboard(content: string): void { export interface CopyProps { readonly content: string; + readonly hideToast?: boolean; + readonly className?: string; } -export const Copy: FC = ({ content }) => { +export const Copy: FC = ({ content, hideToast, className }) => { const [showToast, setShowToast] = useState(false); return ( <> { copyToClipboard(content); - setShowToast(true); - setTimeout(() => { - setShowToast(false); - }, 3000); + if (!hideToast) { + setShowToast(true); + setTimeout(() => { + setShowToast(false); + }, 3000); + } }} > + {showToast && } ); diff --git a/website/src/components/mdx/play.tsx b/website/src/components/mdx/play.tsx new file mode 100644 index 00000000000..cbb83315b60 --- /dev/null +++ b/website/src/components/mdx/play.tsx @@ -0,0 +1,47 @@ +import { IconContainer } from "@/components/misc"; +import { Icon } from "@/components/sprites"; +import { THEME_COLORS } from "@/style"; +import React, { FC } from "react"; +import styled from "styled-components"; + +// Icons +import PlayIconSvg from "@/images/icons/debug-start.svg"; + +export interface PlayProps { + readonly content: string; + readonly url: string; + readonly className?: string; +} + +export const Play: FC = ({ content, url, className }) => { + return ( + { + // TODO: Open Nitro with url and content + + alert(`Open '${content}' in Nitro with URL '${url}'`); + }} + > + + + + + ); +}; + +const PlayIconButton = styled.button` + display: flex; + align-items: center; + justify-content: center; + border-radius: 0 var(--box-border-radius) 0 var(--button-border-radius); + border: 1px solid ${THEME_COLORS.boxBorder}; + border-top: 0 none; + border-right: 0 none; + padding: 6px; + + svg { + fill: #eb64b9; + } +`; diff --git a/website/src/components/widgets/community-section.tsx b/website/src/components/widgets/community-section.tsx index 55c634bc300..5437742ffd3 100644 --- a/website/src/components/widgets/community-section.tsx +++ b/website/src/components/widgets/community-section.tsx @@ -32,7 +32,7 @@ export const CommunityVisualization: FC = () => { - 6k + 7.1k Slack Users @@ -40,11 +40,11 @@ export const CommunityVisualization: FC = () => { Package Downloads - 5k + 5.6k GitHub Stars - 3.2k + 4.6k Pull Requests diff --git a/website/src/images/icons/debug-start.svg b/website/src/images/icons/debug-start.svg new file mode 100644 index 00000000000..2350ed475dd --- /dev/null +++ b/website/src/images/icons/debug-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/src/pages/example-graphql-api.tsx b/website/src/pages/example-graphql-api.tsx new file mode 100644 index 00000000000..3a6f2b6c245 --- /dev/null +++ b/website/src/pages/example-graphql-api.tsx @@ -0,0 +1,322 @@ +import { SiteLayout } from "@/components/layout"; +import { CodeBlock } from "@/components/mdx/code-block"; +import { Copy } from "@/components/mdx/copy"; +import { Video } from "@/components/mdx/video"; +import { IconContainer, LinkButton, SEO } from "@/components/misc"; +import { Icon } from "@/components/sprites"; +import ArrowRightIconSvg from "@/images/icons/arrow-right.svg"; +import CheckIconSvg from "@/images/icons/check.svg"; +import { FONT_FAMILY_CODE, MAX_CONTENT_WIDTH, THEME_COLORS } from "@/style"; +import React, { FC, useEffect } from "react"; +import styled from "styled-components"; + +const exampleQuery = `query GetProducts { + products { + edges { + node { + id + name + reviews { + edges { + node { + stars + body + author { + name + } + } + } + } + } + } + } +}`; + +const serverUrl = "https://demo.chillicream.cloud/graphql"; +const features = [ + "Relay compatible: Includes Connections for paging and the node field", + "Modern GraphQL: Contains @defer, @stream, and @oneOf", + "No CORS: Use it directly in your frontend", + "Subcriptions", +]; + +const GraphQLExamplePage: FC = () => { + return ( + + + + + Example GraphQL API + + Explore and experiment with a fully functional GraphQL API featuring + advanced capabilities like Relay compatibility, subscriptions, and + streaming. + + + + + Endpoint URL: + {serverUrl} + + + + + + Open in Nitro + + + + + + + + + Example Query + + + + + + + Features + + {features.map((feature) => ( + + + + + + + {feature} + + ))} + + + + + Nitro overview + + + + ); +}; + +export default GraphQLExamplePage; + +const HeroContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding-top: 80px; + padding-bottom: 60px; + width: 100%; + + @media only screen and (min-width: 992px) { + padding-top: 100px; + padding-bottom: 80px; + } +`; + +const PageTitle = styled.h1` + font-size: 2.5rem; + font-weight: 600; + text-align: center; + margin: 0 0 16px 0; + padding: 0 16px; + + @media only screen and (min-width: 992px) { + font-size: 3rem; + } +`; + +const PageTeaser = styled.p` + font-size: 1.125rem; + text-align: center; + margin: 0 0 32px 0; + padding: 0 16px; + max-width: 700px; + line-height: 1.6; + color: ${THEME_COLORS.textAlt}; + + @media only screen and (min-width: 992px) { + font-size: 1.25rem; + margin-bottom: 40px; + } +`; + +const EndpointSection = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + margin-top: 0; + width: 100%; + max-width: 800px; + padding: 0 16px; + + @media only screen and (min-width: 768px) { + flex-direction: row; + justify-content: center; + gap: 16px; + padding: 0; + } +`; + +const EndpointUrlContainer = styled.div` + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background-color: ${THEME_COLORS.backgroundAlt}; + border: 1px solid ${THEME_COLORS.boxBorder}; + border-radius: var(--box-border-radius); + flex: 1; + min-width: 0; + + @media only screen and (min-width: 768px) { + flex: 1 1 auto; + max-width: 500px; + } +`; + +const EndpointLabel = styled.span` + font-size: 0.875rem; + color: ${THEME_COLORS.textAlt}; + flex-shrink: 0; + + @media only screen and (max-width: 480px) { + display: none; + } +`; + +const EndpointUrl = styled.code` + font-family: ${FONT_FAMILY_CODE}; + font-size: 0.875rem; + color: ${THEME_COLORS.link}; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const CopyButtonWrapper = styled.div` + flex-shrink: 0; + display: flex; + align-items: center; +`; + +const OpenButton = styled(LinkButton)` + flex-shrink: 0; + + > ${IconContainer} { + margin-left: 8px; + margin-right: 0; + + svg { + fill: currentColor; + transition: transform 0.2s ease-in-out; + } + } + + &:hover > ${IconContainer} svg { + transform: translateX(2px); + } +`; + +const SimpleSection = styled.section` + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: 32px 16px; + max-width: ${MAX_CONTENT_WIDTH}px; + margin: 0 auto; + + @media only screen and (min-width: 992px) { + padding: 40px 24px; + } + + @media only screen and (min-width: 1246px) { + padding: 48px 0; + } +`; + +const SectionTitle = styled.h2` + font-size: 1.5rem; + font-weight: 600; + text-align: center; + margin: 0 0 20px 0; + color: ${THEME_COLORS.heading}; + + @media only screen and (min-width: 992px) { + font-size: 1.75rem; + margin-bottom: 24px; + } +`; + +const SectionText = styled.p` + font-size: 1rem; + line-height: 1.6; + text-align: center; + color: ${THEME_COLORS.text}; + margin: 0; + max-width: 700px; + + @media only screen and (min-width: 992px) { + font-size: 1.125rem; + } +`; + +const CodeBlockContainer = styled.div` + width: 100%; + max-width: 1000px; + margin: 0 auto; + + @media only screen and (min-width: 992px) { + max-width: 1200px; + } +`; + +const FeaturesList = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 0; + width: 100%; + max-width: 600px; +`; + +const FeatureItem = styled.div` + display: flex; + align-items: center; + gap: 12px; + padding: 8px 0; +`; + +const FeatureIcon = styled.div` + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + + ${IconContainer} > svg { + fill: ${THEME_COLORS.primary}; + } +`; + +const FeatureText = styled.span` + font-size: 0.9375rem; + line-height: 1.5; + color: ${THEME_COLORS.text}; + flex: 1; +`; From 60f21dd2be0f0c5d65b15d431bdb544a54d6e6ec Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:39:32 +0100 Subject: [PATCH 2/2] responsive design --- website/src/pages/example-graphql-api.tsx | 345 +++++++++------------- 1 file changed, 137 insertions(+), 208 deletions(-) diff --git a/website/src/pages/example-graphql-api.tsx b/website/src/pages/example-graphql-api.tsx index 3a6f2b6c245..d0c30a234c5 100644 --- a/website/src/pages/example-graphql-api.tsx +++ b/website/src/pages/example-graphql-api.tsx @@ -2,12 +2,12 @@ import { SiteLayout } from "@/components/layout"; import { CodeBlock } from "@/components/mdx/code-block"; import { Copy } from "@/components/mdx/copy"; import { Video } from "@/components/mdx/video"; -import { IconContainer, LinkButton, SEO } from "@/components/misc"; +import { IconContainer, Link, LinkButton, SEO } from "@/components/misc"; import { Icon } from "@/components/sprites"; import ArrowRightIconSvg from "@/images/icons/arrow-right.svg"; import CheckIconSvg from "@/images/icons/check.svg"; -import { FONT_FAMILY_CODE, MAX_CONTENT_WIDTH, THEME_COLORS } from "@/style"; -import React, { FC, useEffect } from "react"; +import { IsMobile, IsPhablet, IsTablet, THEME_COLORS } from "@/style"; +import React, { FC } from "react"; import styled from "styled-components"; const exampleQuery = `query GetProducts { @@ -37,186 +37,165 @@ const features = [ "Relay compatible: Includes Connections for paging and the node field", "Modern GraphQL: Contains @defer, @stream, and @oneOf", "No CORS: Use it directly in your frontend", - "Subcriptions", + "Subscriptions", ]; const GraphQLExamplePage: FC = () => { return ( - + - - Example GraphQL API - - Explore and experiment with a fully functional GraphQL API featuring - advanced capabilities like Relay compatibility, subscriptions, and - streaming. - - - - - Endpoint URL: - {serverUrl} - - - - - - Open in Nitro - - - - - - - - - Example Query - - - - - - - Features - - {features.map((feature) => ( - - - + + + Example GraphQL API + + + {serverUrl} + + + + + + + + Open in Nitro + + + + + + + + + Example Query + + + + + Features + + {features.map((feature) => ( + + - - {feature} - - ))} - - - - - Nitro overview - - + {feature} + + ))} + + + + Get to know Nitro + + + ); }; export default GraphQLExamplePage; -const HeroContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; - padding-top: 80px; - padding-bottom: 60px; - width: 100%; +const Layout = styled.div` + display: grid; + grid-template-columns: 1fr 720px 440px 1fr; + grid-template-rows: 1fr; + gap: 20px; - @media only screen and (min-width: 992px) { - padding-top: 100px; - padding-bottom: 80px; - } + ${IsTablet(` + grid-template-columns: 1fr; + `)} + + width: 100%; + height: 100%; + padding-top: 26px; + overflow: visible; `; -const PageTitle = styled.h1` - font-size: 2.5rem; - font-weight: 600; - text-align: center; - margin: 0 0 16px 0; - padding: 0 16px; +const HeaderLeft = styled.div` + grid-row: 1; + grid-column: 2; - @media only screen and (min-width: 992px) { - font-size: 3rem; - } + ${IsTablet(` + grid-column: 1; + `)} `; -const PageTeaser = styled.p` - font-size: 1.125rem; - text-align: center; - margin: 0 0 32px 0; - padding: 0 16px; - max-width: 700px; - line-height: 1.6; - color: ${THEME_COLORS.textAlt}; +const HeaderRight = styled.div` + grid-row: 1; + grid-column: 3; - @media only screen and (min-width: 992px) { - font-size: 1.25rem; - margin-bottom: 40px; - } + ${IsTablet(` + grid-row: 2; + grid-column: 1; + `)} `; -const EndpointSection = styled.div` +const OpenButtonContainer = styled.div` display: flex; - flex-direction: column; + justify-content: center; align-items: center; - gap: 16px; - margin-top: 0; - width: 100%; - max-width: 800px; - padding: 0 16px; - - @media only screen and (min-width: 768px) { - flex-direction: row; - justify-content: center; - gap: 16px; - padding: 0; - } -`; + height: 100%; -const EndpointUrlContainer = styled.div` - display: flex; - align-items: center; - gap: 12px; - padding: 16px 20px; - background-color: ${THEME_COLORS.backgroundAlt}; - border: 1px solid ${THEME_COLORS.boxBorder}; - border-radius: var(--box-border-radius); - flex: 1; - min-width: 0; - - @media only screen and (min-width: 768px) { - flex: 1 1 auto; - max-width: 500px; - } + ${IsTablet(` + justify-content: flex-start; + width: 100%; + `)} `; -const EndpointLabel = styled.span` - font-size: 0.875rem; - color: ${THEME_COLORS.textAlt}; - flex-shrink: 0; +const LeftContent = styled.div` + grid-row: 2; + grid-column: 2; - @media only screen and (max-width: 480px) { - display: none; - } + ${IsTablet(` + grid-row: 3; + grid-column: 1; + `)} `; -const EndpointUrl = styled.code` - font-family: ${FONT_FAMILY_CODE}; - font-size: 0.875rem; - color: ${THEME_COLORS.link}; - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +const RightContent = styled.div` + grid-row: 2; + grid-column: 3; + + ${IsTablet(` + grid-row: 4; + grid-column: 1; + `)} `; -const CopyButtonWrapper = styled.div` - flex-shrink: 0; +const UrlContainer = styled.div` display: flex; - align-items: center; +`; + +const PageTitle = styled.h1` + font-size: 2.5rem; + font-weight: normal; +`; + +const Title = styled.h2` + margin-right: 16px; + margin-bottom: 16px; + margin-left: 16px; + font-size: 2rem; + font-weight: normal; + + @media only screen and (min-width: 860px) { + margin-right: 0; + margin-left: 0; + } `; const OpenButton = styled(LinkButton)` flex-shrink: 0; + justify-content: center; > ${IconContainer} { margin-left: 8px; @@ -231,50 +210,10 @@ const OpenButton = styled(LinkButton)` &:hover > ${IconContainer} svg { transform: translateX(2px); } -`; - -const SimpleSection = styled.section` - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - padding: 32px 16px; - max-width: ${MAX_CONTENT_WIDTH}px; - margin: 0 auto; - - @media only screen and (min-width: 992px) { - padding: 40px 24px; - } - - @media only screen and (min-width: 1246px) { - padding: 48px 0; - } -`; - -const SectionTitle = styled.h2` - font-size: 1.5rem; - font-weight: 600; - text-align: center; - margin: 0 0 20px 0; - color: ${THEME_COLORS.heading}; - - @media only screen and (min-width: 992px) { - font-size: 1.75rem; - margin-bottom: 24px; - } -`; - -const SectionText = styled.p` - font-size: 1rem; - line-height: 1.6; - text-align: center; - color: ${THEME_COLORS.text}; - margin: 0; - max-width: 700px; - @media only screen and (min-width: 992px) { - font-size: 1.125rem; - } + ${IsTablet(` + width: 100%; + `)} `; const CodeBlockContainer = styled.div` @@ -287,36 +226,26 @@ const CodeBlockContainer = styled.div` } `; -const FeaturesList = styled.div` +const Features = styled.ul.attrs({ + className: "text-2", +})` display: flex; flex-direction: column; - gap: 12px; - margin-top: 0; - width: 100%; - max-width: 600px; -`; - -const FeatureItem = styled.div` - display: flex; - align-items: center; - gap: 12px; - padding: 8px 0; + align-items: flex-start; + justify-content: flex-start; + grid-row: 3; + box-sizing: border-box; + margin: 0; + list-style-type: none; `; -const FeatureIcon = styled.div` - flex-shrink: 0; +const Feature = styled.li` display: flex; + flex-direction: row; align-items: center; - justify-content: center; + gap: 8px; ${IconContainer} > svg { - fill: ${THEME_COLORS.primary}; + fill: ${THEME_COLORS.text}; } `; - -const FeatureText = styled.span` - font-size: 0.9375rem; - line-height: 1.5; - color: ${THEME_COLORS.text}; - flex: 1; -`;