diff --git a/packages/web/docs/src/app/page.tsx b/packages/web/docs/src/app/page.tsx index 7e3d697c2e5..88403e3359c 100644 --- a/packages/web/docs/src/app/page.tsx +++ b/packages/web/docs/src/app/page.tsx @@ -92,7 +92,7 @@ export default function IndexPage(): ReactElement { - + diff --git a/packages/web/docs/src/components/ecosystem-management/dashed-line.tsx b/packages/web/docs/src/components/ecosystem-management/dashed-line.tsx new file mode 100644 index 00000000000..0eebef928f4 --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/dashed-line.tsx @@ -0,0 +1,47 @@ +import { cn } from '../../lib/utils'; +import css from './ecosystem-management.module.css'; + +export interface DashedLineProps extends React.SVGProps { + short?: boolean; +} + +export function DashedLine(props: DashedLineProps) { + if (props.short) { + return ( + + + + ); + } + + return ( + + + + ); +} diff --git a/packages/web/docs/src/components/ecosystem-management/ecosystem-illustration.tsx b/packages/web/docs/src/components/ecosystem-management/ecosystem-illustration.tsx index 4ba2dfa5c0e..6bf5aaacd03 100644 --- a/packages/web/docs/src/components/ecosystem-management/ecosystem-illustration.tsx +++ b/packages/web/docs/src/components/ecosystem-management/ecosystem-illustration.tsx @@ -1,355 +1,230 @@ 'use client'; -import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { cn, CodegenIcon, HiveGatewayIcon, HiveIcon, YogaIcon } from '@theguild/components'; +import { ReactNode } from 'react'; +import { cn, CodegenIcon, HiveGatewayIcon, HiveIcon } from '@theguild/components'; +import { DashedLine } from './dashed-line'; import { GraphQLLogo } from './graphql-logo'; import { IconGradientDefs } from './icon-gradient-defs'; +import { AndroidLogo } from './logos/android'; +import { AppleLogo } from './logos/apple'; +import { GrpcLogo } from './logos/grpc'; +import { McpLogo } from './logos/mcp'; +import { OpenAPILogo } from './logos/openapi'; +import { ReactLogo } from './logos/react'; import styles from './ecosystem-management.module.css'; -const edgeTexts = [ - 'Apps send requests to Hive Gateway that acts as the api gateway to data from your federated graph.', - 'Developers that build the apps/api clients will use GraphQL Codegen for generating type-safe code that makes writing apps safer and faster.', - 'Codegen uses Hive to pull the GraphQL schema for generating the code.', - 'Hive Gateway pulls the supergraph from the Hive Schema Registry that gives it all the information about the subgraphs and available data to serve to the outside world.', - 'Hive Gateway delegates GraphQL requests to the corresponding Yoga subgraphs within your internal network.', - 'Check the subgraph schema against the Hive Schema Registry before deployment to ensure integrity. After deploying a new subgraph version, publish its schema to Hive, to generate the supergraph used by Gateway.', -]; -const longestEdgeText = edgeTexts.reduce((a, b) => (a.length > b.length ? a : b)); - -const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect; - -const EDGE_HOVER_INTERVAL_TIME = 5000; -const EDGE_HOVER_RESET_TIME = 10_000; - export function EcosystemIllustration(props: { className?: string }) { - const [highlightedEdge, setHighlightedEdge] = useState(4); - - const intervalRef = useRef | null>(null); - - useIsomorphicLayoutEffect(() => { - intervalRef.current = setInterval(() => { - setHighlightedEdge(prev => (prev! % 6) + 1); - }, EDGE_HOVER_INTERVAL_TIME); - - return () => clearInterval(intervalRef.current || undefined); - }, []); - - const highlightEdge = (edgeNumber: number) => { - clearInterval(intervalRef.current || undefined); - - setHighlightedEdge(edgeNumber); - - // after 10 seconds, we'll start stepping through edges again - intervalRef.current = setTimeout(() => { - intervalRef.current = setInterval(() => { - setHighlightedEdge(prev => (prev! % 6) + 1); - }, EDGE_HOVER_INTERVAL_TIME); - }, EDGE_HOVER_RESET_TIME); - }; - - const onPointerOverEdge = (event: React.PointerEvent) => { - const edgeNumber = parseInt(event.currentTarget.textContent!); - if (Number.isNaN(edgeNumber) || edgeNumber < 1 || edgeNumber > 6) { - return; - } - - highlightEdge(edgeNumber); - }; - - const highlightIterators = useRef<{ node: number[]; index: number }>({ node: [], index: -1 }); - const onHighlightNode = (edges: number[]) => { - clearInterval(intervalRef.current || undefined); - - let previousIndex: number; - if (highlightIterators.current.node.every((x, i) => edges[i] === x)) { - previousIndex = highlightIterators.current.index; - } else { - highlightIterators.current.node = edges; - previousIndex = -1; - } - - let index = (previousIndex + 1) % edges.length; - - // if edge under index is already highlighted, we move forward - if (highlightedEdge === edges[index]) { - index = (index + 1) % edges.length; - } - - highlightIterators.current.index = index; - highlightEdge(edges[index]); - }; + const boxHeight = 66; // p-4 (16*2) + size-8 (32) + border (2) + const halfBoxHeight = boxHeight / 2; return (
-
- -
- 5 -
- -
- - Hive Gateway - - } - description="GraphQL Router" - edges={[1, 4, 5]} - highlightedEdge={highlightedEdge} - onHighlight={onHighlightNode} - > - - - -
- 4 -
- + + {/* Col 1: Codegen (Desktop) */} +
+ + GraphQL Code +
+ Generation + + } + className="z-20" + > + +
+
+ + {/* Col 2: Left Connections */} +
+ {/* Top-Left Line: Connects Top-Center to Side-Center */} + + {/* Bottom-Left Line: Connects Bottom-Center to Side-Center */} + +
+ +
+
+
+ + + + +
+
+ +
+ +
- Hive Console + GraphQL Edge Security +
+ and Caching Layer } - description="Schema Registry and CDN" - edges={[3, 4, 6]} - highlightedEdge={highlightedEdge} - onHighlight={onHighlightNode} - > - -
- -
- 6 -
- - - +
-
- -
- 1 -
- -
- -
- 3 -
- -
-
- GraphQL Client of Choice - } - edges={[1, 2]} - highlightedEdge={highlightedEdge} - onHighlight={onHighlightNode} - > - - - -
- 2 -
- - GraphQL Code Generation - } - edges={[2, 3]} - highlightedEdge={highlightedEdge} - onHighlight={onHighlightNode} - > - + +
+ +
+ +
+ + Hive Gateway +
+
+
+ + + Hive Router +
+ (Rust) +
+
+ +
+ +
+
+ + + +
+
+
+ + {/* Col 4: Right Connections */} +
+ + + + +
+ + {/* Col 5: Console (Desktop) */} +
+ + Hive Console + + } + description={ + <> + Schema registry +
+ and monitoring + + } + className="z-20" + > + +
-

- {/* We use the longest text to ensure we have enough space. */} - {longestEdgeText} - {edgeTexts.map((text, i) => { - return ( - - {text} - - ); - })} -

); } interface EdgeProps extends React.HTMLAttributes { - highlighted: boolean; top?: boolean; left?: boolean; bottom?: boolean; } -function Edge({ highlighted, top, bottom, left, className, ...rest }: EdgeProps) { +function Edge({ top, bottom, left, className, ...rest }: EdgeProps) { return (
*]:transition-colors [&>*]:duration-500 [&>:nth-child(odd)]:border-green-700', + '[&>:nth-child(odd)]:border-green-700', top && (bottom - ? '[&>:nth-child(1)]:border-t-[length:var(--bw)] [&>:nth-child(3)]:border-b-[length:var(--bw)]' - : '[&>:nth-child(odd)]:border-t-[length:var(--bw)]'), - left && '[&>:nth-child(odd)]:border-l-[length:var(--bw)]', - highlighted && - '[&>*]:text-green-1000 [&>:nth-child(even)]:bg-green-300 [&>:nth-child(odd)]:border-green-300', + ? '[&>:nth-child(1)]:border-t [&>:nth-child(3)]:border-b' + : '[&>:nth-child(odd)]:border-t'), + left && '[&>:nth-child(odd)]:border-l', )} {...rest} /> ); } -interface EdgeLabelProps extends React.HTMLAttributes { - onPointerOver: React.PointerEventHandler; -} -function EdgeLabel(props: EdgeLabelProps) { - return ( -
- ); -} - interface NodeProps extends Omit, 'title'> { title: ReactNode; description?: ReactNode; - edges: number[]; - highlightedEdge: number | null; - onHighlight: (edges: number[]) => void; } -function Node({ - title, - description, - children, - edges, - highlightedEdge, - className, - onHighlight, - ...rest -}: NodeProps) { - const highlighted = edges.includes(highlightedEdge!); - - const hovered = useRef(false); +function Node({ title, description, children, className, ...rest }: NodeProps) { return (
{ - if (hovered.current || event.currentTarget !== event.target) { - return; - } - - hovered.current = true; - - if (edges.includes(highlightedEdge!)) return; - onHighlight([edges[0]]); - }} - onPointerOut={event => { - if ( - !event.currentTarget.contains(event.relatedTarget as Node) && - event.currentTarget === event.target - ) { - hovered.current = false; - } - }} - onClick={() => onHighlight(edges)} className={cn( styles.node, - 'relative z-10 flex h-[var(--node-h)] items-center gap-2 rounded-2xl p-4 xl:gap-4 xl:p-[22px]' + - ' bg-[linear-gradient(135deg,rgb(255_255_255/0.10),rgb(255_255_255/0.20))]' + - ' cursor-pointer transition-colors duration-500 [&>svg]:flex-shrink-0', - // todo: linear gradients don't transition, so we should add white/10 background layer' - highlighted && - 'bg-[linear-gradient(135deg,rgb(255_255_255_/0.2),rgb(255_255_255/0.3))] ring ring-green-300', + 'relative z-10 flex min-h-[96px] items-center gap-4 rounded-2xl bg-[linear-gradient(135deg,rgb(255_255_255/0.10),rgb(255_255_255/0.20))] p-4 backdrop-blur-md xl:p-[22px] [&>svg]:shrink-0', + description && 'flex-row', className, )} {...rest} > {children} -
-
{title}
- {description && ( -
- {description} -
- )} -
+ {(title || description) && ( +
+
{title}
+ {description && ( +
+ {description} +
+ )} +
+ )}
); } diff --git a/packages/web/docs/src/components/ecosystem-management/ecosystem-management.module.css b/packages/web/docs/src/components/ecosystem-management/ecosystem-management.module.css index 363bee68e63..501cddb3d54 100644 --- a/packages/web/docs/src/components/ecosystem-management/ecosystem-management.module.css +++ b/packages/web/docs/src/components/ecosystem-management/ecosystem-management.module.css @@ -2,52 +2,26 @@ container-type: inline-size; } -.vars { - --node-w: 96px; - --big-node-h: 112px; - --node-h: 112px; - --label-h: 32px; - --gap: 80px; - --big-logo-size: 48px; - --node-desc-display: none; -} - -@container (width >= 586px) { - .vars { - --node-w: 240px; - --big-node-h: 222px; - --node-h: 96px; - --big-logo-size: 112px; - --node-desc-display: block; +@container (width < 586px) { + .node { + flex-direction: column; + text-align: center; + } + .smHidden { + display: none; } } -.text { - margin-top: 1.5rem; -} - -@container (width >= 480px) { - .text { - margin-top: 0; - position: absolute; - bottom: 0; - right: 0; - height: 144px; - width: 250px; +@keyframes dash { + to { + stroke-dashoffset: -18px; /* 2 * 9px pattern */ } } -@container (width >= 720px) { - .text { - right: 48px; - } +.animate-dash path { + animation: dash 0.75s linear infinite; } -@container (width < 586px) { - .node { - flex-direction: column; - } - .smHidden { - display: none; - } +.animate-dash-reverse path { + animation: dash 0.75s linear infinite reverse; } diff --git a/packages/web/docs/src/components/ecosystem-management/index.tsx b/packages/web/docs/src/components/ecosystem-management/index.tsx index 4bd80a5c680..dc3498c68b5 100644 --- a/packages/web/docs/src/components/ecosystem-management/index.tsx +++ b/packages/web/docs/src/components/ecosystem-management/index.tsx @@ -15,12 +15,11 @@ export function EcosystemManagementSection({ className }: { className?: string } return (
-
+
360° GraphQL API Management @@ -104,7 +103,12 @@ export function EcosystemManagementSection({ className }: { className?: string }
- +
diff --git a/packages/web/docs/src/components/ecosystem-management/logos/android.tsx b/packages/web/docs/src/components/ecosystem-management/logos/android.tsx new file mode 100644 index 00000000000..66e20de5725 --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/logos/android.tsx @@ -0,0 +1,30 @@ +export function AndroidLogo(props: React.SVGProps) { + return ( + + + + + + + + + + ); +} diff --git a/packages/web/docs/src/components/ecosystem-management/logos/apple.tsx b/packages/web/docs/src/components/ecosystem-management/logos/apple.tsx new file mode 100644 index 00000000000..83a0a017ac5 --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/logos/apple.tsx @@ -0,0 +1,30 @@ +export function AppleLogo(props: React.SVGProps) { + return ( + + + + + + + + + + ); +} diff --git a/packages/web/docs/src/components/ecosystem-management/logos/grpc.tsx b/packages/web/docs/src/components/ecosystem-management/logos/grpc.tsx new file mode 100644 index 00000000000..cbb613777bc --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/logos/grpc.tsx @@ -0,0 +1,39 @@ +export function GrpcLogo(props: React.SVGProps) { + return ( + + + + + + + + + + + + + + + + ); +} diff --git a/packages/web/docs/src/components/ecosystem-management/logos/mcp.tsx b/packages/web/docs/src/components/ecosystem-management/logos/mcp.tsx new file mode 100644 index 00000000000..f38ae8f33d1 --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/logos/mcp.tsx @@ -0,0 +1,37 @@ +export function McpLogo(props: React.SVGProps) { + return ( + + + + + + + + + + + + + + + ); +} diff --git a/packages/web/docs/src/components/ecosystem-management/logos/openapi.tsx b/packages/web/docs/src/components/ecosystem-management/logos/openapi.tsx new file mode 100644 index 00000000000..7f6e126b3ab --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/logos/openapi.tsx @@ -0,0 +1,35 @@ +export function OpenAPILogo(props: React.SVGProps) { + return ( + + + + + + + + + + + + + + + ); +} diff --git a/packages/web/docs/src/components/ecosystem-management/logos/react.tsx b/packages/web/docs/src/components/ecosystem-management/logos/react.tsx new file mode 100644 index 00000000000..50db38b1969 --- /dev/null +++ b/packages/web/docs/src/components/ecosystem-management/logos/react.tsx @@ -0,0 +1,46 @@ +export function ReactLogo(props: React.SVGProps) { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}