diff --git a/apps/insights/.env.development b/apps/insights/.env.development
new file mode 100644
index 0000000000..addc9dd5a0
--- /dev/null
+++ b/apps/insights/.env.development
@@ -0,0 +1 @@
+DISABLE_ACCESSIBILITY_REPORTING=true
\ No newline at end of file
diff --git a/apps/insights/.gitignore b/apps/insights/.gitignore
index 9d2ee2a739..e6fc78b8ba 100644
--- a/apps/insights/.gitignore
+++ b/apps/insights/.gitignore
@@ -1 +1,2 @@
.env*.local
+.env*.development
\ No newline at end of file
diff --git a/apps/insights/src/components/CardTitle/index.module.scss b/apps/insights/src/components/CardTitle/index.module.scss
new file mode 100644
index 0000000000..ccb19f4b02
--- /dev/null
+++ b/apps/insights/src/components/CardTitle/index.module.scss
@@ -0,0 +1,25 @@
+@use "@pythnetwork/component-library/theme";
+
+.cardTitle {
+ display: flex;
+ flex-flow: row nowrap;
+ gap: theme.spacing(3);
+ align-items: center;
+ justify-content: flex-start;
+ .title {
+ color: theme.color("heading");
+ display: flex;
+ flex-flow: row nowrap;
+ gap: theme.spacing(3);
+ align-items: center;
+ @include theme.text("base", "semibold");
+ @include theme.breakpoint("md") {
+ @include theme.text("lg", "semibold");
+ }
+ }
+ .icon {
+ font-size: theme.spacing(6);
+ height: theme.spacing(6);
+ color: theme.color("button", "primary", "background", "normal");
+ }
+}
diff --git a/apps/insights/src/components/CardTitle/index.tsx b/apps/insights/src/components/CardTitle/index.tsx
new file mode 100644
index 0000000000..902235689e
--- /dev/null
+++ b/apps/insights/src/components/CardTitle/index.tsx
@@ -0,0 +1,20 @@
+import clsx from "clsx";
+import type { ComponentProps, ReactNode } from "react";
+
+import styles from "./index.module.scss";
+
+type CardTitleProps = {
+ children: ReactNode;
+ icon?: ReactNode | undefined;
+ badge?: ReactNode;
+} & ComponentProps<"div">;
+
+export const CardTitle = ({ children, icon, badge, ...props }: CardTitleProps) => {
+ return (
+
+ {icon &&
{icon}
}
+
{children}
+ {badge}
+
+ )
+}
\ No newline at end of file
diff --git a/apps/insights/src/components/CopyButton/index.tsx b/apps/insights/src/components/CopyButton/index.tsx
index f002ccbbbc..53de57de10 100644
--- a/apps/insights/src/components/CopyButton/index.tsx
+++ b/apps/insights/src/components/CopyButton/index.tsx
@@ -27,7 +27,6 @@ export const CopyButton = ({ text, children, className, ...props }: Props) => {
const [isCopied, setIsCopied] = useState(false);
const logger = useLogger();
const copy = useCallback(() => {
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
navigator.clipboard
.writeText(text)
.then(() => {
diff --git a/apps/insights/src/components/MobileMenu/mobile-menu.module.scss b/apps/insights/src/components/MobileMenu/mobile-menu.module.scss
new file mode 100644
index 0000000000..ae47132655
--- /dev/null
+++ b/apps/insights/src/components/MobileMenu/mobile-menu.module.scss
@@ -0,0 +1,54 @@
+@use "@pythnetwork/component-library/theme";
+
+.mobileMenuTrigger {
+ display: block;
+
+ @include theme.breakpoint("md") {
+ display: none;
+ }
+}
+
+.mobileMenuOverlay {
+ background: rgb(0 0 0 / 40%);
+ position: fixed;
+ inset: 0;
+ z-index: 999;
+}
+
+.mobileMenuContainer {
+ border-top-left-radius: theme.border-radius("2xl");
+ border-top-right-radius: theme.border-radius("2xl");
+ background: theme.color("background", "modal");
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 1rem;
+ display: flex;
+ flex-flow: column nowrap;
+ gap: theme.spacing(4);
+}
+
+.mobileMenuHandle {
+ background: theme.color("background", "secondary");
+ width: 33%;
+ height: 6px;
+ border-radius: theme.border-radius("full");
+ align-self: center;
+}
+
+.mobileThemeSwitcher {
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.mobileThemeSwitcherFeedback {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ gap: theme.spacing(3);
+ text-transform: capitalize;
+ font-weight: theme.font-weight("medium");
+}
diff --git a/apps/insights/src/components/MobileMenu/mobile-menu.tsx b/apps/insights/src/components/MobileMenu/mobile-menu.tsx
new file mode 100644
index 0000000000..4e5932b425
--- /dev/null
+++ b/apps/insights/src/components/MobileMenu/mobile-menu.tsx
@@ -0,0 +1,29 @@
+"use client";
+import { List } from "@phosphor-icons/react/dist/ssr/List";
+import { Button } from "@pythnetwork/component-library/Button";
+import clsx from "clsx";
+import { useState, type ComponentProps } from "react";
+
+import styles from "./mobile-menu.module.scss";
+
+export const MobileMenu = ({ className, ...props }: ComponentProps<"div">) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const toggleMenu = () => {
+ setIsOpen(!isOpen);
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/insights/src/components/MobileNavigation/mobile-navigation.module.scss b/apps/insights/src/components/MobileNavigation/mobile-navigation.module.scss
new file mode 100644
index 0000000000..00e1c7ceec
--- /dev/null
+++ b/apps/insights/src/components/MobileNavigation/mobile-navigation.module.scss
@@ -0,0 +1,12 @@
+@use "@pythnetwork/component-library/theme";
+
+.mobileNavigation {
+ display: block;
+ padding: theme.spacing(2);
+ background: theme.color("background", "primary");
+ border-top: 1px solid theme.color("background", "secondary");
+
+ @include theme.breakpoint("md") {
+ display: none;
+ }
+}
diff --git a/apps/insights/src/components/MobileNavigation/mobile-navigation.tsx b/apps/insights/src/components/MobileNavigation/mobile-navigation.tsx
new file mode 100644
index 0000000000..0ee7773a4b
--- /dev/null
+++ b/apps/insights/src/components/MobileNavigation/mobile-navigation.tsx
@@ -0,0 +1,9 @@
+import styles from "./mobile-navigation.module.scss";
+import { MainNavTabs } from "../Root/tabs";
+export const MobileNavigation = () => {
+ return (
+
+
+
+ );
+};
diff --git a/apps/insights/src/components/Overview/index.tsx b/apps/insights/src/components/Overview/index.tsx
index 3185cbd16d..1d9bf083d2 100644
--- a/apps/insights/src/components/Overview/index.tsx
+++ b/apps/insights/src/components/Overview/index.tsx
@@ -1,7 +1,9 @@
-import styles from "./index.module.scss";
+import { Card } from "@pythnetwork/component-library/Card";
+
+import { PageLayout } from "../PageLayout/page-layout";
export const Overview = () => (
-
-
Overview
-
+
+
+
);
diff --git a/apps/insights/src/components/PageLayout/page-layout.module.scss b/apps/insights/src/components/PageLayout/page-layout.module.scss
new file mode 100644
index 0000000000..442bb61b8e
--- /dev/null
+++ b/apps/insights/src/components/PageLayout/page-layout.module.scss
@@ -0,0 +1,24 @@
+@use "@pythnetwork/component-library/theme";
+
+.pageLayout {
+ @include theme.max-width;
+ display: flex;
+ gap: theme.spacing(6);
+ flex-direction: column;
+
+ .pageTitleContainer {
+ display: flex;
+ flex-flow: row nowrap;
+ gap: theme.spacing(3);
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+
+ .pageTitle {
+ @include theme.h3;
+ color: theme.color("heading");
+ font-weight: theme.font-weight("semibold");
+ flex-grow: 1;
+ }
+ }
+}
diff --git a/apps/insights/src/components/PageLayout/page-layout.tsx b/apps/insights/src/components/PageLayout/page-layout.tsx
new file mode 100644
index 0000000000..b70e04c255
--- /dev/null
+++ b/apps/insights/src/components/PageLayout/page-layout.tsx
@@ -0,0 +1,15 @@
+import type { ReactNode } from "react";
+
+import styles from "./page-layout.module.scss";
+
+export const PageLayout = ({ children, title, actions }: { children: ReactNode; title: ReactNode, actions?: ReactNode }) => {
+ return (
+
+
+
{title}
+ {actions &&
{actions}
}
+
+ {children}
+
+ )
+}
\ No newline at end of file
diff --git a/apps/insights/src/components/PriceFeedTag/index.module.scss b/apps/insights/src/components/PriceFeedTag/index.module.scss
index 0ca6b99f12..fc92baf48e 100644
--- a/apps/insights/src/components/PriceFeedTag/index.module.scss
+++ b/apps/insights/src/components/PriceFeedTag/index.module.scss
@@ -1,8 +1,8 @@
@use "@pythnetwork/component-library/theme";
.priceFeedTag {
- display: flex;
- flex-flow: row nowrap;
+ display: grid;
+ grid-template-columns: theme.spacing(10) 1fr;
gap: theme.spacing(3);
align-items: center;
@@ -13,6 +13,8 @@
}
.nameAndDescription {
+ width: 100%;
+ position: relative;
display: flex;
flex-flow: column nowrap;
gap: theme.spacing(1.5);
@@ -52,6 +54,8 @@
color: theme.color("muted");
overflow: hidden;
text-overflow: ellipsis;
+ white-space: nowrap; // Add this line
+ width: 100%;
@include theme.text("xs", "medium");
}
diff --git a/apps/insights/src/components/PriceFeeds/index.module.scss b/apps/insights/src/components/PriceFeeds/index.module.scss
index d5927a894b..dd081b90e7 100644
--- a/apps/insights/src/components/PriceFeeds/index.module.scss
+++ b/apps/insights/src/components/PriceFeeds/index.module.scss
@@ -1,66 +1,53 @@
@use "@pythnetwork/component-library/theme";
-.priceFeeds {
- @include theme.max-width;
+.toolbarContainer {
+ display: flex;
+ flex-flow: row nowrap;
+ gap: theme.spacing(2);
+}
+
+.feedKey {
+ margin: 0 -#{theme.button-padding("xs", true)};
+}
- .header {
- @include theme.h3;
+.featuredFeeds {
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: stretch;
- color: theme.color("heading");
- font-weight: theme.font-weight("semibold");
+ @include theme.breakpoint("md") {
+ flex-flow: row nowrap;
+
+ & > * {
+ flex: 1 1 0px;
+ width: 0;
+ }
}
+}
+
+.featuredFeeds {
+ gap: theme.spacing(1);
- .body {
+ .feedCardContents {
display: flex;
flex-flow: column nowrap;
+ justify-content: space-between;
+ align-items: stretch;
+ padding: theme.spacing(3);
gap: theme.spacing(6);
- margin-top: theme.spacing(6);
-
- .feedKey {
- margin: 0 -#{theme.button-padding("xs", true)};
- }
- .featuredFeeds,
- .stats {
+ .prices {
display: flex;
flex-flow: row nowrap;
+ justify-content: space-between;
align-items: center;
+ color: theme.color("heading");
+ font-weight: theme.font-weight("medium");
+ line-height: 1;
+ font-size: theme.font-size("base");
- & > * {
- flex: 1 1 0px;
- width: 0;
- }
- }
-
- .stats {
- gap: theme.spacing(6);
- }
-
- .featuredFeeds {
- gap: theme.spacing(1);
-
- .feedCardContents {
- display: flex;
- flex-flow: column nowrap;
- justify-content: space-between;
- align-items: stretch;
- padding: theme.spacing(3);
- gap: theme.spacing(6);
-
- .prices {
- display: flex;
- flex-flow: row nowrap;
- justify-content: space-between;
- align-items: center;
- color: theme.color("heading");
- font-weight: theme.font-weight("medium");
- line-height: 1;
- font-size: theme.font-size("base");
-
- .changePercent {
- font-size: theme.font-size("sm");
- }
- }
+ .changePercent {
+ font-size: theme.font-size("sm");
}
}
}
diff --git a/apps/insights/src/components/PriceFeeds/index.tsx b/apps/insights/src/components/PriceFeeds/index.tsx
index 16691c0844..05f5d1f35a 100644
--- a/apps/insights/src/components/PriceFeeds/index.tsx
+++ b/apps/insights/src/components/PriceFeeds/index.tsx
@@ -19,10 +19,13 @@ import styles from "./index.module.scss";
import { PriceFeedsCard } from "./price-feeds-card";
import { Cluster, getData } from "../../services/pyth";
import { priceFeeds as priceFeedsStaticConfig } from "../../static-data/price-feeds";
+import { CardTitle } from "../CardTitle";
import { YesterdaysPricesProvider, ChangePercent } from "../ChangePercent";
import { LivePrice } from "../LivePrices";
+import { PageLayout } from "../PageLayout/page-layout";
import { PriceFeedIcon } from "../PriceFeedIcon";
import { PriceFeedTag } from "../PriceFeedTag";
+import { Stats } from "../Stats";
const PRICE_FEEDS_ANCHOR = "priceFeeds";
@@ -45,99 +48,94 @@ export const PriceFeeds = async () => {
);
return (
-
-
Price Feeds
-
-
- }
- />
-
+
+
+ }
+ />
+
+ }
+ />
+
}
+ header="Asset Classes"
+ stat={Object.keys(numFeedsByAssetClass).length}
+ corner={}
/>
-
- }
- />
-
-
-
[
- symbol,
- product.price_account,
- ]),
- )}
- >
- }
- feeds={featuredRecentlyAdded}
- showPrices
- linkFeeds
- />
-
+
+
+
[
+ symbol,
+ product.price_account,
+ ]),
+ )}
+ >
}
- feeds={featuredComingSoon}
- toolbar={
-
-
-
- Coming Soon
- {priceFeeds.comingSoon.length}
- >
- }
- >
- ({
- id: feed.product.price_account,
- displaySymbol: feed.product.display_symbol,
- assetClass: feed.product.asset_type,
- icon: (
-
- ),
- }))}
- />
-
-
- }
- />
- ({
- symbol: feed.symbol,
- icon: ,
- id: feed.product.price_account,
- displaySymbol: feed.product.display_symbol,
- assetClass: feed.product.asset_type,
- exponent: feed.price.exponent,
- numQuoters: feed.price.numQuoters,
- }))}
+ title={}>Recently added}
+ feeds={featuredRecentlyAdded}
+ showPrices
+ linkFeeds
/>
-
-
+
+ }>Coming soon}
+ feeds={featuredComingSoon}
+ action={
+
+
+
+ Coming Soon
+ {priceFeeds.comingSoon.length}
+ >
+ }
+ >
+ ({
+ id: feed.product.price_account,
+ displaySymbol: feed.product.display_symbol,
+ assetClass: feed.product.asset_type,
+ icon: (
+
+ ),
+ }))}
+ />
+
+
+ }
+ />
+ ({
+ symbol: feed.symbol,
+ icon: ,
+ id: feed.product.price_account,
+ displaySymbol: feed.product.display_symbol,
+ assetClass: feed.product.asset_type,
+ exponent: feed.price.exponent,
+ numQuoters: feed.price.numQuoters,
+ }))}
+ />
+
);
};
diff --git a/apps/insights/src/components/PriceFeeds/price-feed-items.module.scss b/apps/insights/src/components/PriceFeeds/price-feed-items.module.scss
new file mode 100644
index 0000000000..9825c9e6e4
--- /dev/null
+++ b/apps/insights/src/components/PriceFeeds/price-feed-items.module.scss
@@ -0,0 +1,22 @@
+@use "@pythnetwork/component-library/theme";
+
+.priceFeedItemsWrapper {
+ display: flex;
+ flex-flow: column nowrap;
+ gap: 0;
+ border-radius: theme.border-radius("xl");
+ background: theme.color("background", "card-secondary");
+
+ @include theme.breakpoint("md") {
+ display: none;
+ }
+}
+
+.priceFeedItem {
+ padding: theme.spacing(4);
+ position: relative;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid theme.color("background", "secondary");
+ }
+}
diff --git a/apps/insights/src/components/PriceFeeds/price-feed-items.tsx b/apps/insights/src/components/PriceFeeds/price-feed-items.tsx
new file mode 100644
index 0000000000..3759311ca7
--- /dev/null
+++ b/apps/insights/src/components/PriceFeeds/price-feed-items.tsx
@@ -0,0 +1,32 @@
+import styles from "./price-feed-items.module.scss";
+import { PriceFeedTag } from "../PriceFeedTag";
+import { StructuredList } from "../StructuredList";
+
+export const PriceFeedItems = () => {
+ return (
+
+ {Array.from({ length: 20 }).map((_, index) => {
+ return (
+
+ ,
+ value: "$32,323.22",
+ },
+ {
+ label: "Last Price",
+ value: "$10,000.00",
+ },
+ {
+ label: "Last Updated",
+ value: "2022-01-01",
+ },
+ ]}
+ />
+
+ );
+ })}
+
+ );
+};
diff --git a/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx b/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
index 0fe94882fd..3d632740ed 100644
--- a/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
+++ b/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
@@ -16,7 +16,9 @@ import { useQueryState, parseAsString } from "nuqs";
import { type ReactNode, Suspense, useCallback, useMemo } from "react";
import { useFilter, useCollator } from "react-aria";
+import { PriceFeedItems } from "./price-feed-items";
import { useQueryParamFilterPagination } from "../../use-query-param-filter-pagination";
+import { CardTitle } from "../CardTitle";
import { FeedKey } from "../FeedKey";
import {
SKELETON_WIDTH,
@@ -195,49 +197,56 @@ type PriceFeedsCardContents = Pick &
(
| { isLoading: true }
| {
- isLoading?: false;
- numResults: number;
- search: string;
- sortDescriptor: SortDescriptor;
- onSortChange: (newSort: SortDescriptor) => void;
- assetClass: string;
- assetClasses: string[];
- numPages: number;
- page: number;
- pageSize: number;
- onSearchChange: (newSearch: string) => void;
- onAssetClassChange: (newAssetClass: string) => void;
- onPageSizeChange: (newPageSize: number) => void;
- onPageChange: (newPage: number) => void;
- mkPageLink: (page: number) => string;
- rows: RowConfig<
- | "priceFeedName"
- | "assetClass"
- | "priceFeedId"
- | "price"
- | "confidenceInterval"
- | "exponent"
- | "numPublishers"
- >[];
- }
+ isLoading?: false;
+ numResults: number;
+ search: string;
+ sortDescriptor: SortDescriptor;
+ onSortChange: (newSort: SortDescriptor) => void;
+ assetClass: string;
+ assetClasses: string[];
+ numPages: number;
+ page: number;
+ pageSize: number;
+ onSearchChange: (newSearch: string) => void;
+ onAssetClassChange: (newAssetClass: string) => void;
+ onPageSizeChange: (newPageSize: number) => void;
+ onPageChange: (newPage: number) => void;
+ mkPageLink: (page: number) => string;
+ rows: RowConfig<
+ | "priceFeedName"
+ | "assetClass"
+ | "priceFeedId"
+ | "price"
+ | "confidenceInterval"
+ | "exponent"
+ | "numPublishers"
+ >[];
+ }
);
const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
}
title={
- <>
- Price Feeds
- {!props.isLoading && (
-
- {props.numResults}
-
- )}
- >
+ } badge={!props.isLoading && (
+
+ {props.numResults}
+
+ )}>Price Feeds
}
toolbar={
<>
+