diff --git a/src/app/(sidebar)/explore-endpoints/[[...pages]]/page.tsx b/src/app/(sidebar)/explore-endpoints/[[...pages]]/page.tsx index f9fed4b7..33b49d27 100644 --- a/src/app/(sidebar)/explore-endpoints/[[...pages]]/page.tsx +++ b/src/app/(sidebar)/explore-endpoints/[[...pages]]/page.tsx @@ -1,61 +1,155 @@ "use client"; +import { useState } from "react"; import { usePathname } from "next/navigation"; -import { Card, Icon, Text } from "@stellar/design-system"; +import { + Alert, + Button, + Card, + Checkbox, + CopyText, + Icon, + Input, + Text, +} from "@stellar/design-system"; import { InfoCards } from "@/components/InfoCards"; +import { TabView } from "@/components/TabView"; +import { SdsLink } from "@/components/SdsLink"; import { Routes } from "@/constants/routes"; - -const infoCards = [ - { - id: "soroban-rpc", - title: "RPC Endpoints", - description: "TODO: add text", - buttonLabel: "See docs", - buttonIcon: , - buttonAction: () => - window.open("https://soroban.stellar.org/api/methods", "_blank"), - }, - { - id: "horizon", - title: "Horizon Endpoints", - description: "TODO: add text", - buttonLabel: "See docs", - buttonIcon: , - buttonAction: () => - window.open( - "https://developers.stellar.org/api/horizon/resources/", - "_blank", - ), - }, -]; +import { WithInfoText } from "@/components/WithInfoText"; export default function ExploreEndpoints() { const pathname = usePathname(); + const [activeTab, setActiveTab] = useState("endpoints-tab-params"); + if (pathname === Routes.EXPLORE_ENDPOINTS) { + return ; + } + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + // TODO: handle submit + }; + + const renderEndpointUrl = () => { return ( - <> - -
- - Endpoints - - - - TODO: add text - -
-
- - +
+ GET
} + /> + {/* TODO: disable if can't submit */} + + {/* TODO: add text to copy */} + + + + ); - } + }; - return renderPage(pathname); + const renderFields = () => { + return ( +
+
+ {/* TODO: render fields for path */} + {`Explore Endpoints: ${pathname}`} +
+ + + + +
+ ); + }; + + return ( + <> +
+ TODO: render JSON, + }} + onTabChange={(id) => { + setActiveTab(id); + }} + activeTabId={activeTab} + /> + + + This tool can be used to run queries against the{" "} + + REST API endpoints + {" "} + on the Horizon server. Horizon is the client facing library for the + Stellar ecosystem. + + + ); } -const renderPage = (pathname: string) => { - // TODO: add switch to render path component - return
{`Explore Endpoints: ${pathname}`}
; +const ExploreEndpointsLandingPage = () => { + const infoCards = [ + { + id: "soroban-rpc", + title: "RPC Endpoints", + description: "TODO: add text", + buttonLabel: "See docs", + buttonIcon: , + buttonAction: () => + window.open("https://soroban.stellar.org/api/methods", "_blank"), + }, + { + id: "horizon", + title: "Horizon Endpoints", + description: "TODO: add text", + buttonLabel: "See docs", + buttonIcon: , + buttonAction: () => + window.open( + "https://developers.stellar.org/api/horizon/resources/", + "_blank", + ), + }, + ]; + + return ( + <> + +
+ + Endpoints + + + + TODO: add text + +
+
+ + + ); }; diff --git a/src/components/TabView/index.tsx b/src/components/TabView/index.tsx new file mode 100644 index 00000000..5bf39448 --- /dev/null +++ b/src/components/TabView/index.tsx @@ -0,0 +1,117 @@ +import { Card, Text } from "@stellar/design-system"; +import { WithInfoText } from "@/components/WithInfoText"; +import { Tabs } from "@/components/Tabs"; +import "./styles.scss"; + +type Tab = { + id: string; + label: string; + content: React.ReactNode; +}; + +type TabViewProps = { + heading: TabViewHeadingProps; + tab1: Tab; + tab2: Tab; + tab3?: Tab; + tab4?: Tab; + tab5?: Tab; + tab6?: Tab; + onTabChange: (id: string) => void; + activeTabId: string; + staticTop?: React.ReactNode; +}; + +export const TabView = ({ + heading, + onTabChange, + activeTabId, + staticTop, + ...tabs +}: TabViewProps) => { + const tabItems = Object.values(tabs).map((t) => ({ + id: t.id, + label: t.label, + })); + + const tabContent = Object.values(tabs).map((t) => ({ + id: t.id, + content: t.content, + })); + + return ( +
+
+ + +
+ +
+
+ + +
+ {staticTop ?? null} + +
+ {tabContent.map((tc) => ( +
+ {tc.content} +
+ ))} +
+
+
+
+ ); +}; + +type TabViewHeadingProps = ( + | { + infoText: React.ReactNode | string; + href?: undefined; + } + | { + infoText?: undefined; + href: string; + } + | { infoText?: undefined; href?: undefined } +) & { + title: string; + infoHoverText?: string; +}; + +const TabViewHeading = ({ + title, + infoHoverText, + infoText, + href, +}: TabViewHeadingProps) => { + const renderTitle = () => ( + + {title} + + ); + + if (href || infoText) { + if (href) { + return ( + + {renderTitle()} + + ); + } + + return ( + + {renderTitle()} + + ); + } + + return renderTitle(); +}; diff --git a/src/components/TabView/styles.scss b/src/components/TabView/styles.scss new file mode 100644 index 00000000..10316d91 --- /dev/null +++ b/src/components/TabView/styles.scss @@ -0,0 +1,30 @@ +@use "../../styles/utils.scss" as *; + +.TabView { + display: flex; + flex-direction: column; + gap: pxToRem(12px); + + &__heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: pxToRem(24px); + } + + &__body { + display: flex; + flex-direction: column; + gap: pxToRem(12px); + } + + &__content { + & > [data-is-active="false"] { + display: none; + } + + & > [data-is-active="true"] { + display: block; + } + } +} diff --git a/src/components/Tabs/index.tsx b/src/components/Tabs/index.tsx new file mode 100644 index 00000000..2c3f31e9 --- /dev/null +++ b/src/components/Tabs/index.tsx @@ -0,0 +1,31 @@ +import "./styles.scss"; + +type Tab = { + id: string; + label: string; +}; + +export const Tabs = ({ + tabs, + activeTabId, + onChange, +}: { + tabs: Tab[]; + activeTabId: string; + onChange: (id: string) => void; +}) => { + return ( +
+ {tabs.map((t) => ( +
onChange(t.id)} + > + {t.label} +
+ ))} +
+ ); +}; diff --git a/src/components/Tabs/styles.scss b/src/components/Tabs/styles.scss new file mode 100644 index 00000000..86cf646f --- /dev/null +++ b/src/components/Tabs/styles.scss @@ -0,0 +1,37 @@ +@use "../../styles/utils.scss" as *; + +.Tabs { + --Tabs-default-text: var(--sds-clr-gray-11); + --Tabs-default-background: transparent; + + display: flex; + align-items: center; + gap: pxToRem(8px); + + .Tab { + font-size: pxToRem(14px); + line-height: pxToRem(20px); + font-weight: var(--sds-fw-medium); + color: var(--Tabs-default-text); + background-color: var(--Tabs-default-background); + border-radius: pxToRem(6px); + padding: pxToRem(6px) pxToRem(10px); + cursor: pointer; + transition: + color var(--sds-anim-transition-default), + background-color var(--sds-anim-transition-default); + + @media (hover: hover) { + &:hover { + --Tabs-default-text: var(--sds-clr-lilac-11); + --Tabs-default-background: var(--sds-clr-lilac-04); + } + } + + &[data-is-active="true"] { + --Tabs-default-text: var(--sds-clr-lilac-11); + --Tabs-default-background: var(--sds-clr-lilac-04); + cursor: default; + } + } +} diff --git a/src/components/WithInfoText/index.tsx b/src/components/WithInfoText/index.tsx new file mode 100644 index 00000000..60f8ac0d --- /dev/null +++ b/src/components/WithInfoText/index.tsx @@ -0,0 +1,57 @@ +import { Icon, Tooltip } from "@stellar/design-system"; +import { NextLink } from "@/components/NextLink"; +import "./styles.scss"; + +type WithInfoTextProps = ( + | { + infoText: React.ReactNode | string; + href?: undefined; + } + | { + infoText?: undefined; + href: string; + } +) & { + children: React.ReactNode; + infoHoverText?: string; +}; + +export const WithInfoText = ({ + children, + infoText, + infoHoverText, + href, +}: WithInfoTextProps) => { + const buttonBaseProps = { + className: "WithInfoText__button", + title: infoHoverText || "Learn more", + }; + + if (href) { + return ( +
+ {children} + + + + +
+ ); + } + + return ( +
+ {children} + + + + + } + > + {infoText} + +
+ ); +}; diff --git a/src/components/WithInfoText/styles.scss b/src/components/WithInfoText/styles.scss new file mode 100644 index 00000000..46049279 --- /dev/null +++ b/src/components/WithInfoText/styles.scss @@ -0,0 +1,34 @@ +@use "../../styles/utils.scss" as *; + +.WithInfoText { + --WithInfoText-icon-color: var(--sds-clr-gray-08); + + display: inline-flex; + gap: pxToRem(2px); + align-items: center; + + &__button { + display: block; + + text-decoration: none; + cursor: pointer; + border: none; + background: none; + padding: pxToRem(2px); + margin: 0; + + svg { + display: block; + width: pxToRem(12px); + height: pxToRem(12px); + transition: stroke var(--sds-anim-transition-default); + stroke: var(--WithInfoText-icon-color); + } + + @media (hover: hover) { + &:hover { + --WithInfoText-icon-color: var(--sds-clr-gray-11); + } + } + } +} diff --git a/src/styles/globals.scss b/src/styles/globals.scss index fcb80fe4..6c159fcf 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -309,3 +309,53 @@ font-size: pxToRem(14px); line-height: pxToRem(20px); } + +// Endpoint Explorer +.Endpoints { + &__urlBar { + display: flex; + align-items: center; + justify-content: space-between; + gap: pxToRem(8px); + } + + &__input__requestType { + font-size: pxToRem(14px); + line-height: pxToRem(20px); + font-weight: var(--sds-fw-semi-bold); + color: var(--sds-clr-gray-12); + background-color: var(--sds-clr-gray-01); + height: 100%; + display: flex; + align-items: center; + flex-shrink: 0; + flex-grow: 0; + padding: pxToRem(6px) pxToRem(10px); + + border-right: 1px solid var(--Input-color-border); + margin-left: calc(var(--Input-padding-horizontal) * -1); + border-top-left-radius: var(--Input-border-radius); + border-bottom-left-radius: var(--Input-border-radius); + } + + &__content { + display: flex; + flex-direction: column; + gap: pxToRem(12px); + + &__inputs { + display: flex; + flex-direction: column; + gap: pxToRem(16px); + padding: pxToRem(16px); + background-color: var(--sds-clr-gray-03); + border-radius: pxToRem(8px); + } + } + + .Input--disabled { + input:read-only { + cursor: default; + } + } +}