) => {
+ event.preventDefault();
+ // TODO: handle submit
+ };
+
+ const renderEndpointUrl = () => {
return (
- <>
-
-
-
- Endpoints
-
-
-
- TODO: add text
-
-
-
-
- >
+
+ GET
}
+ />
+ {/* TODO: disable if can't submit */}
+
+ Submit
+
+ {/* TODO: add text to copy */}
+
+ }>
+
+
);
- }
+ };
- return renderPage(pathname);
+ const renderFields = () => {
+ return (
+
+
+ {/* TODO: render fields for path */}
+ {`Explore Endpoints: ${pathname}`}
+
+
+
+
+
+
+ );
+ };
+
+ return (
+ <>
+
+
+ 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;
+ }
+ }
+}