diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 6ec5f197bd..597ff96dbf 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,11 +2,8 @@ apps/api-reference @pyth-network/web-team
apps/entropy-debugger @pyth-network/web-team
apps/insights @pyth-network/web-team
apps/staking @pyth-network/web-team
-packages/app-logger @pyth-network/web-team
packages/component-library @pyth-network/web-team
-packages/fonts @pyth-network/web-team
packages/known-publishers @pyth-network/web-team
-packages/next-root @pyth-network/web-team
Dockerfile.node @pyth-network/web-team
package.json @pyth-network/web-team
pnpm-workspace.yaml @pyth-network/web-team
diff --git a/apps/insights/package.json b/apps/insights/package.json
index e078a8b0d9..70f222f285 100644
--- a/apps/insights/package.json
+++ b/apps/insights/package.json
@@ -22,13 +22,10 @@
"dependencies": {
"@clickhouse/client": "catalog:",
"@phosphor-icons/react": "catalog:",
- "@pythnetwork/app-logger": "workspace:*",
"@pythnetwork/client": "catalog:",
"@pythnetwork/component-library": "workspace:*",
- "@pythnetwork/fonts": "workspace:*",
"@pythnetwork/hermes-client": "workspace:*",
"@pythnetwork/known-publishers": "workspace:*",
- "@pythnetwork/next-root": "workspace:*",
"@react-hookz/web": "catalog:",
"@solana/web3.js": "catalog:",
"bs58": "catalog:",
diff --git a/apps/insights/src/app/global-error.tsx b/apps/insights/src/app/global-error.tsx
index 480e056fa6..535e194876 100644
--- a/apps/insights/src/app/global-error.tsx
+++ b/apps/insights/src/app/global-error.tsx
@@ -1,6 +1,6 @@
"use client";
-import { LoggerProvider } from "@pythnetwork/app-logger/provider";
+import { LoggerProvider } from "@pythnetwork/component-library/useLogger";
import type { ComponentProps } from "react";
import { Error } from "../components/Error";
diff --git a/apps/insights/src/components/CopyButton/index.tsx b/apps/insights/src/components/CopyButton/index.tsx
index 2943823327..53a104649c 100644
--- a/apps/insights/src/components/CopyButton/index.tsx
+++ b/apps/insights/src/components/CopyButton/index.tsx
@@ -2,8 +2,8 @@
import { Check } from "@phosphor-icons/react/dist/ssr/Check";
import { Copy } from "@phosphor-icons/react/dist/ssr/Copy";
-import { useLogger } from "@pythnetwork/app-logger";
import { Button } from "@pythnetwork/component-library/unstyled/Button";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import clsx from "clsx";
import type { ComponentProps } from "react";
import { useCallback, useEffect, useState } from "react";
diff --git a/apps/insights/src/components/Error/index.tsx b/apps/insights/src/components/Error/index.tsx
index 1b891fe6ce..651bd71d2e 100644
--- a/apps/insights/src/components/Error/index.tsx
+++ b/apps/insights/src/components/Error/index.tsx
@@ -1,6 +1,6 @@
import { Warning } from "@phosphor-icons/react/dist/ssr/Warning";
-import { useLogger } from "@pythnetwork/app-logger";
import { Button } from "@pythnetwork/component-library/Button";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useEffect } from "react";
import styles from "./index.module.scss";
diff --git a/apps/insights/src/components/PriceComponentDrawer/index.tsx b/apps/insights/src/components/PriceComponentDrawer/index.tsx
index 296ef7bcc2..990b1aa622 100644
--- a/apps/insights/src/components/PriceComponentDrawer/index.tsx
+++ b/apps/insights/src/components/PriceComponentDrawer/index.tsx
@@ -1,6 +1,5 @@
import { ArrowSquareOut } from "@phosphor-icons/react/dist/ssr/ArrowSquareOut";
import { Flask } from "@phosphor-icons/react/dist/ssr/Flask";
-import { useLogger } from "@pythnetwork/app-logger";
import type { Props as ButtonProps } from "@pythnetwork/component-library/Button";
import { Button } from "@pythnetwork/component-library/Button";
import { Card } from "@pythnetwork/component-library/Card";
@@ -11,6 +10,7 @@ import { StatCard } from "@pythnetwork/component-library/StatCard";
import { Table } from "@pythnetwork/component-library/Table";
import type { Button as UnstyledButton } from "@pythnetwork/component-library/unstyled/Button";
import { useDrawer } from "@pythnetwork/component-library/useDrawer";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useMountEffect } from "@react-hookz/web";
import dynamic from "next/dynamic";
import { useRouter } from "next/navigation";
diff --git a/apps/insights/src/components/PriceComponentsCard/index.module.scss b/apps/insights/src/components/PriceComponentsCard/index.module.scss
index 80a04946c8..dc78ed5a3d 100644
--- a/apps/insights/src/components/PriceComponentsCard/index.module.scss
+++ b/apps/insights/src/components/PriceComponentsCard/index.module.scss
@@ -87,3 +87,8 @@
}
}
}
+
+:export {
+ // stylelint-disable-next-line property-no-unknown
+ headerHeight: theme.$header-height;
+}
diff --git a/apps/insights/src/components/PriceComponentsCard/index.tsx b/apps/insights/src/components/PriceComponentsCard/index.tsx
index ce5276ae72..a549ca1dc8 100644
--- a/apps/insights/src/components/PriceComponentsCard/index.tsx
+++ b/apps/insights/src/components/PriceComponentsCard/index.tsx
@@ -1,6 +1,5 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import { Button } from "@pythnetwork/component-library/Button";
import { Card } from "@pythnetwork/component-library/Card";
@@ -14,6 +13,7 @@ import type {
SortDescriptor,
} from "@pythnetwork/component-library/Table";
import { Table } from "@pythnetwork/component-library/Table";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import clsx from "clsx";
import { useQueryState, parseAsStringEnum, parseAsBoolean } from "nuqs";
import type { ReactNode } from "react";
@@ -37,7 +37,6 @@ import { LivePrice, LiveConfidence, LiveComponentValue } from "../LivePrices";
import { NoResults } from "../NoResults";
import { usePriceComponentDrawer } from "../PriceComponentDrawer";
import { PriceName } from "../PriceName";
-import rootStyles from "../Root/index.module.scss";
import { Score } from "../Score";
import { Status as StatusComponent } from "../Status";
@@ -490,7 +489,7 @@ export const PriceComponentsCardContents = <
label={label}
fill
rounded
- stickyHeader={rootStyles.headerHeight}
+ stickyHeader={styles.headerHeight}
className={styles.table ?? ""}
columns={[
{
diff --git a/apps/insights/src/components/PriceFeed/chart.tsx b/apps/insights/src/components/PriceFeed/chart.tsx
index ee58a935f4..ee874f7097 100644
--- a/apps/insights/src/components/PriceFeed/chart.tsx
+++ b/apps/insights/src/components/PriceFeed/chart.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useResizeObserver } from "@react-hookz/web";
import type { IChartApi, ISeriesApi, UTCTimestamp } from "lightweight-charts";
import { LineSeries, LineStyle, createChart } from "lightweight-charts";
diff --git a/apps/insights/src/components/PriceFeed/header.module.scss b/apps/insights/src/components/PriceFeed/header.module.scss
index 90ebba35b9..8103fe4842 100644
--- a/apps/insights/src/components/PriceFeed/header.module.scss
+++ b/apps/insights/src/components/PriceFeed/header.module.scss
@@ -19,6 +19,10 @@
gap: theme.spacing(2);
justify-content: space-between;
+ .assetClassBadge {
+ align-self: start;
+ }
+
@include theme.breakpoint("sm") {
flex-flow: row nowrap;
align-items: center;
diff --git a/apps/insights/src/components/PriceFeed/header.tsx b/apps/insights/src/components/PriceFeed/header.tsx
index 5af002186b..81fffd2b9a 100644
--- a/apps/insights/src/components/PriceFeed/header.tsx
+++ b/apps/insights/src/components/PriceFeed/header.tsx
@@ -65,7 +65,9 @@ const PriceFeedHeaderImpl = (props: PriceFeedHeaderImplProps) => (
{props.isLoading ? (
) : (
- {props.feed.product.asset_type}
+
+ {props.feed.product.asset_type}
+
)}
diff --git a/apps/insights/src/components/PriceFeed/publishers-card.tsx b/apps/insights/src/components/PriceFeed/publishers-card.tsx
index e359e3acce..53b60ebc91 100644
--- a/apps/insights/src/components/PriceFeed/publishers-card.tsx
+++ b/apps/insights/src/components/PriceFeed/publishers-card.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
import { Switch } from "@pythnetwork/component-library/Switch";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useQueryState, parseAsBoolean } from "nuqs";
import { Suspense, useCallback, useMemo } from "react";
diff --git a/apps/insights/src/components/PriceFeeds/asset-class-table.tsx b/apps/insights/src/components/PriceFeeds/asset-class-table.tsx
index 8f56a7a09e..f7af7e02f0 100644
--- a/apps/insights/src/components/PriceFeeds/asset-class-table.tsx
+++ b/apps/insights/src/components/PriceFeeds/asset-class-table.tsx
@@ -1,9 +1,9 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import { Table } from "@pythnetwork/component-library/Table";
import { useDrawer } from "@pythnetwork/component-library/useDrawer";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { usePathname } from "next/navigation";
import {
parseAsString,
diff --git a/apps/insights/src/components/PriceFeeds/price-feeds-card.module.scss b/apps/insights/src/components/PriceFeeds/price-feeds-card.module.scss
index 7288aad7e6..b92a2bd1c8 100644
--- a/apps/insights/src/components/PriceFeeds/price-feeds-card.module.scss
+++ b/apps/insights/src/components/PriceFeeds/price-feeds-card.module.scss
@@ -21,3 +21,8 @@
}
}
}
+
+:export {
+ // stylelint-disable-next-line property-no-unknown
+ headerHeight: theme.$header-height;
+}
diff --git a/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx b/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
index 91ccbacc5f..53aa45bd20 100644
--- a/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
+++ b/apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
@@ -1,7 +1,6 @@
"use client";
import { ChartLine } from "@phosphor-icons/react/dist/ssr/ChartLine";
-import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import { Card } from "@pythnetwork/component-library/Card";
import { Paginator } from "@pythnetwork/component-library/Paginator";
@@ -12,6 +11,7 @@ import type {
SortDescriptor,
} from "@pythnetwork/component-library/Table";
import { Table } from "@pythnetwork/component-library/Table";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useQueryState, parseAsString } from "nuqs";
import type { ReactNode } from "react";
import { Suspense, useCallback, useMemo } from "react";
@@ -32,7 +32,6 @@ import {
import { NoResults } from "../NoResults";
import { PriceFeedTag } from "../PriceFeedTag";
import { PriceName } from "../PriceName";
-import rootStyles from "../Root/index.module.scss";
type Props = {
id: string;
@@ -317,7 +316,7 @@ const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
rounded
fill
label="Price Feeds"
- stickyHeader={rootStyles.headerHeight}
+ stickyHeader={styles.headerHeight}
className={styles.table ?? ""}
columns={[
{
diff --git a/apps/insights/src/components/Publishers/index.module.scss b/apps/insights/src/components/Publishers/index.module.scss
index 181b2d3eb6..4d4f5068ca 100644
--- a/apps/insights/src/components/Publishers/index.module.scss
+++ b/apps/insights/src/components/Publishers/index.module.scss
@@ -1,5 +1,4 @@
@use "@pythnetwork/component-library/theme";
-@use "../Root/index.module.scss" as root;
$gap: theme.spacing(4);
@@ -53,7 +52,7 @@ $gap: theme.spacing(4);
.statCard {
@include theme.breakpoint("2xl") {
position: sticky;
- top: root.$header-height;
+ top: theme.$header-height;
}
}
@@ -83,7 +82,7 @@ $gap: theme.spacing(4);
$card-wrapper-p: (2 * theme.spacing(1));
$card-height: $card-content + $card-pt + $card-pb + $card-wrapper-p;
- top: calc(root.$header-height + $gap + $card-height);
+ top: calc(theme.$header-height + $gap + $card-height);
}
.oisPool {
diff --git a/apps/insights/src/components/Publishers/publishers-card.module.scss b/apps/insights/src/components/Publishers/publishers-card.module.scss
index 9c8b07c527..4bfd1b048b 100644
--- a/apps/insights/src/components/Publishers/publishers-card.module.scss
+++ b/apps/insights/src/components/Publishers/publishers-card.module.scss
@@ -39,3 +39,8 @@
}
}
}
+
+:export {
+ // stylelint-disable-next-line property-no-unknown
+ headerHeight: theme.$header-height;
+}
diff --git a/apps/insights/src/components/Publishers/publishers-card.tsx b/apps/insights/src/components/Publishers/publishers-card.tsx
index 102f67ce4e..633811628d 100644
--- a/apps/insights/src/components/Publishers/publishers-card.tsx
+++ b/apps/insights/src/components/Publishers/publishers-card.tsx
@@ -2,7 +2,6 @@
import { Broadcast } from "@phosphor-icons/react/dist/ssr/Broadcast";
import { Database } from "@phosphor-icons/react/dist/ssr/Database";
-import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import { Card } from "@pythnetwork/component-library/Card";
import { Link } from "@pythnetwork/component-library/Link";
@@ -14,6 +13,7 @@ import type {
SortDescriptor,
} from "@pythnetwork/component-library/Table";
import { Table } from "@pythnetwork/component-library/Table";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import clsx from "clsx";
import { useQueryState, parseAsStringEnum } from "nuqs";
import type { ReactNode } from "react";
@@ -32,7 +32,6 @@ import {
import { NoResults } from "../NoResults";
import { PublisherTag } from "../PublisherTag";
import { Ranking } from "../Ranking";
-import rootStyles from "../Root/index.module.scss";
import { Score } from "../Score";
const PUBLISHER_SCORE_WIDTH = 38;
@@ -324,7 +323,7 @@ const PublishersCardContents = ({
rounded
fill
label="Publishers"
- stickyHeader={rootStyles.headerHeight}
+ stickyHeader={styles.headerHeight}
className={styles.table ?? ""}
columns={[
{
diff --git a/apps/insights/src/components/Root/header.tsx b/apps/insights/src/components/Root/header.tsx
deleted file mode 100644
index 1c21eac69b..0000000000
--- a/apps/insights/src/components/Root/header.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Lifebuoy } from "@phosphor-icons/react/dist/ssr/Lifebuoy";
-import { Button } from "@pythnetwork/component-library/Button";
-import { Link } from "@pythnetwork/component-library/Link";
-import clsx from "clsx";
-import type { ComponentProps } from "react";
-
-import styles from "./header.module.scss";
-import Logo from "./logo.svg";
-import { MobileMenu } from "./mobile-menu";
-import { SearchButton, SearchShortcutText } from "./search-button";
-import { SupportDrawer } from "./support-drawer";
-import { MainNavTabs } from "./tabs";
-import { ThemeSwitch } from "./theme-switch";
-
-type Props = ComponentProps<"header"> & {
- tabs: ComponentProps
["items"];
-};
-
-export const Header = ({ className, tabs, ...props }: Props) => (
-
-);
diff --git a/apps/insights/src/components/Root/index.module.scss b/apps/insights/src/components/Root/index.module.scss
deleted file mode 100644
index 311e7f37c3..0000000000
--- a/apps/insights/src/components/Root/index.module.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-@use "@pythnetwork/component-library/theme";
-
-$header-height: var(--header-height);
-
-:export {
- // stylelint-disable-next-line property-no-unknown
- headerHeight: $header-height;
-}
-
-.root {
- scroll-padding-top: $header-height;
-
- --header-height: #{theme.spacing(18)};
-
- @include theme.breakpoint("md") {
- --header-height: #{theme.spacing(20)};
- }
-
- .tabRoot {
- display: grid;
- min-height: 100dvh;
- grid-template-rows: auto 1fr auto;
- grid-template-columns: 100%;
-
- .main {
- isolation: isolate;
- padding-top: theme.spacing(4);
- min-height: calc(100svh - $header-height);
-
- @include theme.breakpoint("sm") {
- min-height: unset;
- padding-top: theme.spacing(6);
- }
- }
-
- .header {
- z-index: 1;
- height: $header-height;
- }
- }
-
- .mobileNavTabs {
- @include theme.breakpoint("sm") {
- display: none;
- }
- }
-}
diff --git a/apps/insights/src/components/Root/index.tsx b/apps/insights/src/components/Root/index.tsx
index 8ea454c309..27537b4bea 100644
--- a/apps/insights/src/components/Root/index.tsx
+++ b/apps/insights/src/components/Root/index.tsx
@@ -1,13 +1,9 @@
+import { AppShell } from "@pythnetwork/component-library/AppShell";
import { lookup as lookupPublisher } from "@pythnetwork/known-publishers";
-import { Root as BaseRoot } from "@pythnetwork/next-root";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import type { ReactNode } from "react";
+import { Suspense } from "react";
-import { Footer } from "./footer";
-import { Header } from "./header";
-import styles from "./index.module.scss";
-import { MobileNavTabs } from "./mobile-nav-tabs";
-import { TabRoot, TabPanel } from "./tabs";
import {
ENABLE_ACCESSIBILITY_REPORTING,
GOOGLE_ANALYTICS_ID,
@@ -18,46 +14,37 @@ import { getPublishers } from "../../services/clickhouse";
import { Cluster, getFeeds } from "../../services/pyth";
import { PriceFeedIcon } from "../PriceFeedIcon";
import { PublisherIcon } from "../PublisherIcon";
-import { SearchButtonProvider as SearchButtonProviderImpl } from "./search-button";
+import { SearchButton as SearchButtonImpl } from "./search-button";
export const TABS = [
- { href: "/", id: "", children: "Overview" },
- { href: "/publishers", id: "publishers", children: "Publishers" },
- {
- href: "/price-feeds",
- id: "price-feeds",
- children: "Price Feeds",
- },
+ { segment: "", children: "Overview" },
+ { segment: "publishers", children: "Publishers" },
+ { segment: "price-feeds", children: "Price Feeds" },
];
type Props = {
children: ReactNode;
};
-export const Root = ({ children }: Props) => {
- return (
-
-
-
-
-
- {children}
-
-
-
-
-
-
- );
-};
+export const Root = ({ children }: Props) => (
+ }>
+
+
+ }
+ >
+ {children}
+
+);
-const SearchButtonProvider = async ({ children }: { children: ReactNode }) => {
+const SearchButton = async () => {
const [publishers, feeds] = await Promise.all([
Promise.all([
getPublishersForSearchDialog(Cluster.Pythnet),
@@ -66,11 +53,7 @@ const SearchButtonProvider = async ({ children }: { children: ReactNode }) => {
getFeedsForSearchDialog(Cluster.Pythnet),
]);
- return (
-
- {children}
-
- );
+ return ;
};
const getPublishersForSearchDialog = async (cluster: Cluster) => {
diff --git a/apps/insights/src/components/Root/logo.svg b/apps/insights/src/components/Root/logo.svg
deleted file mode 100644
index ab3e827a7b..0000000000
--- a/apps/insights/src/components/Root/logo.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/apps/insights/src/components/Root/mobile-menu.module.scss b/apps/insights/src/components/Root/mobile-menu.module.scss
deleted file mode 100644
index 05cf4932c2..0000000000
--- a/apps/insights/src/components/Root/mobile-menu.module.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-@use "@pythnetwork/component-library/theme";
-
-.mobileMenu {
- display: flex;
- flex-flow: column nowrap;
- align-items: stretch;
- gap: theme.spacing(6);
- justify-content: space-between;
-
- .buttons {
- display: flex;
- flex-flow: column nowrap;
- align-items: stretch;
- gap: theme.spacing(6);
- }
-
- .theme {
- display: flex;
- flex-flow: row nowrap;
- justify-content: flex-end;
- align-items: center;
- gap: theme.spacing(2);
-
- .themeLabel {
- @include theme.text("sm", "normal");
-
- color: theme.color("muted");
- }
- }
-}
diff --git a/apps/insights/src/components/Root/mobile-menu.tsx b/apps/insights/src/components/Root/mobile-menu.tsx
deleted file mode 100644
index 0a12e4d543..0000000000
--- a/apps/insights/src/components/Root/mobile-menu.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Lifebuoy } from "@phosphor-icons/react/dist/ssr/Lifebuoy";
-import { List } from "@phosphor-icons/react/dist/ssr/List";
-import { Button } from "@pythnetwork/component-library/Button";
-
-import styles from "./mobile-menu.module.scss";
-import { SupportDrawer } from "./support-drawer";
-import { ThemeSwitch } from "./theme-switch";
-
-type Props = {
- className?: string | undefined;
-};
-
-export const MobileMenu = ({ className }: Props) => (
- ,
- }}
- >
- Menu
-
-);
-
-const MobileMenuContents = () => (
-
-
-
-
-
-
- Theme
-
-
-
-);
diff --git a/apps/insights/src/components/Root/search-button.module.scss b/apps/insights/src/components/Root/search-button.module.scss
index af54faa385..f2d6b9458f 100644
--- a/apps/insights/src/components/Root/search-button.module.scss
+++ b/apps/insights/src/components/Root/search-button.module.scss
@@ -1,11 +1,27 @@
@use "@pythnetwork/component-library/theme";
+.searchButton {
+ .largeScreenSearchButton {
+ display: none;
+
+ @include theme.breakpoint("md") {
+ display: unset;
+ }
+ }
+
+ .smallScreenSearchButton {
+ @include theme.breakpoint("md") {
+ display: none;
+ }
+ }
+}
+
.searchDialogContents {
gap: theme.spacing(1);
display: flex;
flex-flow: column nowrap;
overflow: hidden;
- max-height: 100%;
+ max-height: theme.spacing(120);
min-height: 0;
.searchBar,
@@ -178,9 +194,3 @@
}
}
}
-
-// stylelint-disable property-no-unknown
-:export {
- breakpoint-sm: theme.map-get-strict(theme.$breakpoints, "sm");
-}
-// stylelint-enable property-no-unknown
diff --git a/apps/insights/src/components/Root/search-button.tsx b/apps/insights/src/components/Root/search-button.tsx
index c93784996e..c6962de052 100644
--- a/apps/insights/src/components/Root/search-button.tsx
+++ b/apps/insights/src/components/Root/search-button.tsx
@@ -2,7 +2,6 @@
import { MagnifyingGlass } from "@phosphor-icons/react/dist/ssr/MagnifyingGlass";
import { XCircle } from "@phosphor-icons/react/dist/ssr/XCircle";
-import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import type { Props as ButtonProps } from "@pythnetwork/component-library/Button";
import { Button } from "@pythnetwork/component-library/Button";
@@ -19,15 +18,9 @@ import {
ListBoxItem,
} from "@pythnetwork/component-library/unstyled/ListBox";
import { useDrawer } from "@pythnetwork/component-library/useDrawer";
-import type { ReactNode, ComponentProps } from "react";
-import {
- useMemo,
- useCallback,
- useEffect,
- useState,
- createContext,
- use,
-} from "react";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
+import type { ReactNode } from "react";
+import { useMemo, useCallback, useEffect, useState } from "react";
import { useIsSSR, useCollator, useFilter } from "react-aria";
import styles from "./search-button.module.scss";
@@ -40,9 +33,18 @@ import { Score } from "../Score";
const INPUTS = new Set(["input", "select", "button", "textarea"]);
-const SearchButtonContext = createContext void)>(undefined);
+type Props =
+ | { isLoading: true }
+ | (ResolvedSearchButtonProps & { isLoading?: false | undefined });
+
+export const SearchButton = (props: Props) =>
+ props.isLoading ? (
+
+ ) : (
+
+ );
-type Props = Omit, "value"> & {
+type ResolvedSearchButtonProps = {
feeds: {
symbol: string;
displaySymbol: string;
@@ -60,11 +62,43 @@ type Props = Omit, "value"> & {
))[];
};
-export const SearchButtonProvider = ({
- feeds,
- publishers,
- ...props
-}: Props) => {
+const ResolvedSearchButton = (props: ResolvedSearchButtonProps) => {
+ const openSearchDrawer = useSearchDrawer(props);
+
+ useSearchHotkey(openSearchDrawer);
+
+ return ;
+};
+
+const SearchButtonImpl = (
+ props: Omit, "children">,
+) => (
+
+
+
+
+);
+
+const useSearchDrawer = ({ feeds, publishers }: ResolvedSearchButtonProps) => {
const drawer = useDrawer();
const searchDrawer = useMemo(
@@ -82,6 +116,10 @@ export const SearchButtonProvider = ({
drawer.open(searchDrawer);
}, [drawer, searchDrawer]);
+ return openSearchDrawer;
+};
+
+const useSearchHotkey = (openSearchDrawer: () => void) => {
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
const activeElement = document.activeElement;
@@ -110,34 +148,9 @@ export const SearchButtonProvider = ({
globalThis.removeEventListener("keydown", handleKeyDown);
};
}, [handleKeyDown]);
-
- return ;
-};
-
-export const SearchButton = (
- props: Omit<
- ButtonProps,
- "beforeIcon" | "size" | "rounded" | "onPress"
- >,
-) => {
- const openSearchDrawer = use(SearchButtonContext);
- if (openSearchDrawer) {
- return (
-
- );
- } else {
- throw new Error("Search drawer context not initialized!");
- }
};
-export const SearchShortcutText = () => {
+const SearchShortcutText = () => {
const isSSR = useIsSSR();
return isSSR ? : ;
};
@@ -147,10 +160,7 @@ const SearchTextImpl = () => {
return isMac ? "⌘ K" : "Ctrl K";
};
-type SearchDialogContentsProps = {
- feeds: Props["feeds"];
- publishers: Props["publishers"];
-};
+type SearchDialogContentsProps = ResolvedSearchButtonProps;
const SearchDialogContents = ({
feeds,
diff --git a/apps/insights/src/components/Root/support-drawer.module.scss b/apps/insights/src/components/Root/support-drawer.module.scss
deleted file mode 100644
index ab366bca0f..0000000000
--- a/apps/insights/src/components/Root/support-drawer.module.scss
+++ /dev/null
@@ -1,73 +0,0 @@
-@use "@pythnetwork/component-library/theme";
-
-.supportDrawer {
- display: flex;
- flex-flow: column nowrap;
- gap: theme.spacing(8);
-
- & > * {
- flex: none;
- }
-
- .linkList {
- display: flex;
- flex-flow: column nowrap;
- gap: theme.spacing(4);
-
- .title {
- @include theme.text("lg", "medium");
-
- color: theme.color("heading");
- }
-
- .items {
- list-style-type: none;
- padding: 0;
- margin: 0;
- display: flex;
- flex-flow: column nowrap;
- gap: theme.spacing(2);
-
- .link {
- padding: theme.spacing(3);
- display: grid;
- grid-template-columns: max-content 1fr max-content;
- grid-template-rows: max-content max-content;
- text-align: left;
- gap: theme.spacing(2) theme.spacing(4);
- align-items: center;
- width: 100%;
-
- .icon {
- font-size: theme.spacing(8);
- color: theme.color("states", "data", "normal");
- grid-row: span 2 / span 2;
- display: grid;
- place-content: center;
- }
-
- .header {
- @include theme.text("sm", "medium");
-
- color: theme.color("heading");
- }
-
- .description {
- @include theme.text("xs", "normal");
-
- color: theme.color("muted");
- grid-column: 2;
- grid-row: 2;
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- .caret {
- color: theme.color("states", "data", "normal");
- font-size: theme.spacing(4);
- grid-row: span 2 / span 2;
- }
- }
- }
- }
-}
diff --git a/apps/insights/src/components/Root/tabs.tsx b/apps/insights/src/components/Root/tabs.tsx
deleted file mode 100644
index 6159503c2a..0000000000
--- a/apps/insights/src/components/Root/tabs.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client";
-
-import { MainNavTabs as MainNavTabsComponent } from "@pythnetwork/component-library/MainNavTabs";
-import {
- TabPanel as UnstyledTabPanel,
- Tabs,
-} from "@pythnetwork/component-library/unstyled/Tabs";
-import { useSelectedLayoutSegment, usePathname } from "next/navigation";
-import type { ComponentProps } from "react";
-
-export const TabRoot = (
- props: Omit, "selectedKey">,
-) => {
- const tabId = useSelectedLayoutSegment() ?? "";
-
- return ;
-};
-
-export const MainNavTabs = (
- props: Omit, "pathname">,
-) => {
- const pathname = usePathname();
-
- return ;
-};
-
-export const TabPanel = (
- props: Omit, "id">,
-) => {
- const tabId = useSelectedLayoutSegment() ?? "";
-
- return ;
-};
diff --git a/apps/insights/src/components/TokenIcon/index.tsx b/apps/insights/src/components/TokenIcon/index.tsx
index 6b66e11a58..979e9c00c8 100644
--- a/apps/insights/src/components/TokenIcon/index.tsx
+++ b/apps/insights/src/components/TokenIcon/index.tsx
@@ -4,7 +4,7 @@ import clsx from "clsx";
import type { ComponentProps } from "react";
import styles from "./index.module.scss";
-import Logo from "../Root/logo.svg";
+import Logo from "./logo.svg";
type Props = Omit, "children">;
diff --git a/apps/insights/src/components/TokenIcon/logo.svg b/apps/insights/src/components/TokenIcon/logo.svg
new file mode 100644
index 0000000000..1ca7aeae24
--- /dev/null
+++ b/apps/insights/src/components/TokenIcon/logo.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/insights/src/hooks/use-data.ts b/apps/insights/src/hooks/use-data.ts
index c5eb6d9a7c..d72adbb3e0 100644
--- a/apps/insights/src/hooks/use-data.ts
+++ b/apps/insights/src/hooks/use-data.ts
@@ -1,4 +1,4 @@
-import { useLogger } from "@pythnetwork/app-logger";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useCallback } from "react";
import type { KeyedMutator } from "swr";
import useSWR from "swr";
diff --git a/apps/insights/src/hooks/use-live-price-data.tsx b/apps/insights/src/hooks/use-live-price-data.tsx
index 53bf948b0c..ffcf574ef1 100644
--- a/apps/insights/src/hooks/use-live-price-data.tsx
+++ b/apps/insights/src/hooks/use-live-price-data.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
import type { PriceData } from "@pythnetwork/client";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { useMap } from "@react-hookz/web";
import { PublicKey } from "@solana/web3.js";
import type { ComponentProps } from "react";
diff --git a/apps/insights/src/hooks/use-query-param-filter-pagination.ts b/apps/insights/src/hooks/use-query-param-filter-pagination.ts
index 6a80de88d1..deb0486943 100644
--- a/apps/insights/src/hooks/use-query-param-filter-pagination.ts
+++ b/apps/insights/src/hooks/use-query-param-filter-pagination.ts
@@ -1,7 +1,7 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
import type { SortDescriptor } from "@pythnetwork/component-library/unstyled/Table";
+import { useLogger } from "@pythnetwork/component-library/useLogger";
import { usePathname } from "next/navigation";
import {
parseAsString,
diff --git a/packages/app-logger/.prettierignore b/packages/app-logger/.prettierignore
deleted file mode 100644
index b509c88b36..0000000000
--- a/packages/app-logger/.prettierignore
+++ /dev/null
@@ -1,2 +0,0 @@
-coverage/
-node_modules/
diff --git a/packages/app-logger/README.md b/packages/app-logger/README.md
deleted file mode 100644
index f92292a54a..0000000000
--- a/packages/app-logger/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# @pythnetwork/app-logger
diff --git a/packages/app-logger/eslint.config.js b/packages/app-logger/eslint.config.js
deleted file mode 100644
index 6e702316f0..0000000000
--- a/packages/app-logger/eslint.config.js
+++ /dev/null
@@ -1 +0,0 @@
-export { react as default } from "@cprussin/eslint-config";
diff --git a/packages/app-logger/jest.config.js b/packages/app-logger/jest.config.js
deleted file mode 100644
index b34e11aeae..0000000000
--- a/packages/app-logger/jest.config.js
+++ /dev/null
@@ -1 +0,0 @@
-export { base as default } from "@cprussin/jest-config";
diff --git a/packages/app-logger/package.json b/packages/app-logger/package.json
deleted file mode 100644
index b450e4a11f..0000000000
--- a/packages/app-logger/package.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "name": "@pythnetwork/app-logger",
- "version": "0.0.0",
- "private": true,
- "type": "module",
- "exports": {
- ".": "./src/index.tsx",
- "./provider": "./src/provider.tsx"
- },
- "scripts": {
- "fix:format": "prettier --write .",
- "fix:lint": "eslint --fix . --max-warnings 0",
- "test:format": "prettier --check .",
- "test:lint": "eslint . --max-warnings 0",
- "test:types": "tsc"
- },
- "peerDependencies": {
- "react": "catalog:"
- },
- "dependencies": {
- "pino": "catalog:"
- },
- "devDependencies": {
- "@cprussin/eslint-config": "catalog:",
- "@cprussin/jest-config": "catalog:",
- "@cprussin/prettier-config": "catalog:",
- "@cprussin/tsconfig": "catalog:",
- "@types/jest": "catalog:",
- "@types/react": "catalog:",
- "eslint": "catalog:",
- "jest": "catalog:",
- "prettier": "catalog:",
- "react": "catalog:",
- "typescript": "catalog:"
- }
-}
diff --git a/packages/app-logger/prettier.config.js b/packages/app-logger/prettier.config.js
deleted file mode 100644
index 1e43aeeddb..0000000000
--- a/packages/app-logger/prettier.config.js
+++ /dev/null
@@ -1 +0,0 @@
-export { base as default } from "@cprussin/prettier-config";
diff --git a/packages/app-logger/src/context.ts b/packages/app-logger/src/context.ts
deleted file mode 100644
index a7830ed6fa..0000000000
--- a/packages/app-logger/src/context.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-"use client";
-
-import type { Logger } from "pino";
-import { createContext } from "react";
-
-export const LoggerContext = createContext>(
- undefined,
-);
diff --git a/packages/app-logger/src/index.tsx b/packages/app-logger/src/index.tsx
deleted file mode 100644
index 804b3509cf..0000000000
--- a/packages/app-logger/src/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useContext } from "react";
-
-import { LoggerContext } from "./context.js";
-
-export const useLogger = () => {
- const logger = useContext(LoggerContext);
- if (logger) {
- return logger;
- } else {
- throw new LoggerNotInitializedError();
- }
-};
-
-class LoggerNotInitializedError extends Error {
- constructor() {
- super("This component must be contained within a ");
- this.name = "LoggerNotInitializedError";
- }
-}
diff --git a/packages/app-logger/src/provider.tsx b/packages/app-logger/src/provider.tsx
deleted file mode 100644
index a17be757a5..0000000000
--- a/packages/app-logger/src/provider.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-"use client";
-
-import { pino } from "pino";
-import type { ComponentProps } from "react";
-import { useMemo } from "react";
-
-import { LoggerContext } from "./context.js";
-
-type LoggerProviderProps = Omit<
- ComponentProps,
- "config" | "value"
-> & {
- config?: Parameters[0] | undefined;
-};
-
-export const LoggerProvider = ({ config, ...props }: LoggerProviderProps) => {
- const logger = useMemo(
- () =>
- pino({
- ...config,
- browser: { ...config?.browser },
- }),
- [config],
- );
- return ;
-};
diff --git a/packages/app-logger/tsconfig.json b/packages/app-logger/tsconfig.json
deleted file mode 100644
index 54cd46c719..0000000000
--- a/packages/app-logger/tsconfig.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@cprussin/tsconfig/react.json"
-}
diff --git a/packages/component-library/.storybook/main.ts b/packages/component-library/.storybook/main.ts
index efe6868cd9..b84c3fa856 100644
--- a/packages/component-library/.storybook/main.ts
+++ b/packages/component-library/.storybook/main.ts
@@ -18,7 +18,13 @@ const config = {
},
addons: [
- "@storybook/addon-essentials",
+ {
+ name: "@storybook/addon-essentials",
+ options: {
+ backgrounds: false,
+ measure: false,
+ },
+ },
"@storybook/addon-themes",
{
name: "@storybook/addon-styling-webpack",
@@ -51,14 +57,38 @@ const config = {
},
],
- webpackFinal: (config) => ({
- ...config,
- resolve: {
+ webpackFinal: (config) => {
+ config.resolve = {
...config.resolve,
extensionAlias: {
+ ...config.resolve?.extensionAlias,
".js": [".js", ".ts", ".tsx"],
},
- },
- }),
+ };
+
+ for (const rule of config.module?.rules ?? []) {
+ if (
+ typeof rule === "object" &&
+ rule !== null &&
+ rule.test instanceof RegExp &&
+ rule.test.test(".svg")
+ ) {
+ rule.exclude = /\.svg$/i;
+ }
+ }
+
+ config.module = {
+ ...config.module,
+ rules: [
+ ...(config.module?.rules ?? []),
+ {
+ test: /\.svg$/i,
+ use: ["@svgr/webpack"],
+ },
+ ],
+ };
+
+ return config;
+ },
} satisfies StorybookConfig;
export default config;
diff --git a/packages/component-library/.storybook/preview.tsx b/packages/component-library/.storybook/preview.tsx
index b7b5ad3024..0879bd8b1e 100644
--- a/packages/component-library/.storybook/preview.tsx
+++ b/packages/component-library/.storybook/preview.tsx
@@ -1,37 +1,88 @@
-import { sans } from "@pythnetwork/fonts";
-import { withThemeByClassName } from "@storybook/addon-themes";
import type { Preview, Decorator } from "@storybook/react";
-import clsx from "clsx";
+import { useEffect } from "react";
-import "../src/Html/base.scss";
import styles from "./storybook.module.scss";
-import { MainContent } from "../src/MainContent";
+import { BodyProviders } from "../src/AppShell/body-providers.js";
+import { sans } from "../src/AppShell/fonts";
+import { RootProviders } from "../src/AppShell/index.js";
+import shellStyles from "../src/AppShell/index.module.scss";
const preview = {
- parameters: {
- layout: "fullscreen",
- backgrounds: {
- disable: true,
+ globalTypes: {
+ theme: {
+ description: "Theme",
+ toolbar: {
+ title: "Theme",
+ icon: "sun",
+ items: [
+ { value: "light", title: "Light", icon: "sun" },
+ { value: "dark", title: "Dark", icon: "moon" },
+ ],
+ dynamicTitle: true,
+ },
+ },
+ background: {
+ description: "Background",
+ toolbar: {
+ title: "Background",
+ icon: "switchalt",
+ items: [
+ { value: "primary", title: "Primary", icon: "switchalt" },
+ { value: "secondary", title: "Secondary", icon: "contrast" },
+ ],
+ dynamicTitle: true,
+ },
},
+ },
+ initialGlobals: {
+ background: "primary",
+ theme: "light",
+ },
+ parameters: {
+ layout: "centered",
actions: { argTypesRegex: "^on[A-Z].*" },
+ nextjs: {
+ appDirectory: true,
+ navigation: {
+ segments: [],
+ },
+ },
},
} satisfies Preview;
export default preview;
export const decorators: Decorator[] = [
- (Story) => (
-
-
-
- ),
- withThemeByClassName({
- themes: {
- Light: styles.light ?? "",
- "Light (Secondary Background)": clsx(styles.light, styles.secondary),
- Dark: styles.dark ?? "",
- "Dark (Secondary Background)": clsx(styles.dark, styles.secondary),
- },
- defaultTheme: "Light",
- }),
+ (Story, { globals, parameters }) => {
+ useEffect(() => {
+ document.documentElement.classList.add(
+ sans.className,
+ shellStyles.html ?? "",
+ );
+ document.body.classList.add(shellStyles.body ?? "");
+ }, []);
+ return (
+
+ {globals.bare ? (
+
+ ) : (
+
+
+
+ )}
+
+ );
+ },
];
+
+const isValidTheme = (theme: unknown): theme is "light" | "dark" =>
+ theme === "light" || theme === "dark";
diff --git a/packages/component-library/.storybook/storybook.module.scss b/packages/component-library/.storybook/storybook.module.scss
index d2104bcdef..4ee4766cc3 100644
--- a/packages/component-library/.storybook/storybook.module.scss
+++ b/packages/component-library/.storybook/storybook.module.scss
@@ -1,23 +1,31 @@
@use "../src/theme.scss";
-html,
-body {
- height: 100%;
- width: 100%;
+body,
+:global(#storybook-root) {
+ padding: 0 !important;
}
-.light {
- color-scheme: light;
-}
+.contents {
+ height: 100vh;
+ width: 100vw;
+ color: theme.color("foreground");
+ background: theme.color("background", "primary");
+ display: grid;
+ place-content: center;
+ isolation: isolate;
-.dark {
- color-scheme: dark;
-}
+ &[data-background="secondary"] {
+ background: theme.color("background", "secondary");
+ }
-.secondary .mainContent {
- background: theme.color("background", "secondary");
-}
+ &[data-layout="padded"] {
+ padding: theme.spacing(10);
+ display: block;
+ place-content: unset;
+ }
-.mainContent {
- padding: theme.spacing(10);
+ &[data-layout="fullscreen"] {
+ display: block;
+ place-content: unset;
+ }
}
diff --git a/packages/component-library/package.json b/packages/component-library/package.json
index 8eca36e9f4..8d54e8cf67 100644
--- a/packages/component-library/package.json
+++ b/packages/component-library/package.json
@@ -26,13 +26,20 @@
"react": "catalog:"
},
"dependencies": {
- "@pythnetwork/fonts": "workspace:*",
+ "@amplitude/analytics-browser": "catalog:",
+ "@amplitude/plugin-autocapture-browser": "catalog:",
+ "@axe-core/react": "catalog:",
+ "@next/third-parties": "catalog:",
"@react-hookz/web": "catalog:",
+ "bcp-47": "catalog:",
"clsx": "catalog:",
"modern-normalize": "catalog:",
"motion": "catalog:",
+ "next-themes": "catalog:",
+ "pino": "catalog:",
"react-aria": "catalog:",
- "react-aria-components": "catalog:"
+ "react-aria-components": "catalog:",
+ "react-dom": "catalog:"
},
"devDependencies": {
"@cprussin/eslint-config": "catalog:",
@@ -46,8 +53,10 @@
"@storybook/blocks": "catalog:",
"@storybook/nextjs": "catalog:",
"@storybook/react": "catalog:",
+ "@svgr/webpack": "catalog:",
"@types/jest": "catalog:",
"@types/react": "catalog:",
+ "@types/react-dom": "catalog:",
"autoprefixer": "catalog:",
"css-loader": "catalog:",
"eslint": "catalog:",
diff --git a/packages/next-root/src/amplitude.tsx b/packages/component-library/src/AppShell/amplitude.tsx
similarity index 100%
rename from packages/next-root/src/amplitude.tsx
rename to packages/component-library/src/AppShell/amplitude.tsx
diff --git a/packages/component-library/src/AppShell/base.scss b/packages/component-library/src/AppShell/base.scss
new file mode 100644
index 0000000000..fccafb2813
--- /dev/null
+++ b/packages/component-library/src/AppShell/base.scss
@@ -0,0 +1 @@
+@use "modern-normalize";
diff --git a/packages/component-library/src/AppShell/body-providers.tsx b/packages/component-library/src/AppShell/body-providers.tsx
new file mode 100644
index 0000000000..aaaf00021b
--- /dev/null
+++ b/packages/component-library/src/AppShell/body-providers.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { ThemeProvider } from "next-themes";
+import type { ComponentProps, CSSProperties } from "react";
+import { useState } from "react";
+
+import { OverlayVisibleContext } from "../overlay-visible-context.js";
+import { AlertProvider } from "../useAlert/index.js";
+import { DrawerProvider } from "../useDrawer/index.js";
+
+type TabRootProps = ComponentProps<"div"> & {
+ theme?: "dark" | "light" | undefined;
+};
+
+export const BodyProviders = ({ theme, ...props }: TabRootProps) => {
+ const overlayVisibleState = useState(false);
+ const [offset, setOffset] = useState(0);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/fonts/src/index.ts b/packages/component-library/src/AppShell/fonts.tsx
similarity index 100%
rename from packages/fonts/src/index.ts
rename to packages/component-library/src/AppShell/fonts.tsx
diff --git a/packages/next-root/src/html-with-lang.tsx b/packages/component-library/src/AppShell/html-with-lang.tsx
similarity index 68%
rename from packages/next-root/src/html-with-lang.tsx
rename to packages/component-library/src/AppShell/html-with-lang.tsx
index db200d7da5..e32056d176 100644
--- a/packages/next-root/src/html-with-lang.tsx
+++ b/packages/component-library/src/AppShell/html-with-lang.tsx
@@ -1,6 +1,5 @@
"use client";
-import { Html } from "@pythnetwork/component-library/Html";
import type { ComponentProps } from "react";
import { useLocale } from "react-aria";
@@ -8,5 +7,5 @@ type HtmlWithLangProps = Omit, "lang">;
export const HtmlWithLang = (props: HtmlWithLangProps) => {
const locale = useLocale();
- return ;
+ return ;
};
diff --git a/packages/next-root/src/i18n-provider.tsx b/packages/component-library/src/AppShell/i18n-provider.tsx
similarity index 100%
rename from packages/next-root/src/i18n-provider.tsx
rename to packages/component-library/src/AppShell/i18n-provider.tsx
diff --git a/packages/component-library/src/AppShell/index.module.scss b/packages/component-library/src/AppShell/index.module.scss
new file mode 100644
index 0000000000..3fd2b643c6
--- /dev/null
+++ b/packages/component-library/src/AppShell/index.module.scss
@@ -0,0 +1,66 @@
+@use "../theme";
+
+.html {
+ padding-right: 0 !important;
+
+ --header-height: #{theme.spacing(18)};
+
+ @include theme.breakpoint("md") {
+ --header-height: #{theme.spacing(20)};
+ }
+
+ .body {
+ background: black;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ scroll-behavior: smooth;
+ line-height: 1;
+
+ *::selection {
+ color: theme.color("selection", "foreground");
+ background: theme.color("selection", "background");
+ }
+
+ .appShell {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ grid-template-columns: 100%;
+ color: theme.color("foreground");
+ background: theme.color("background", "primary");
+ border-top-left-radius: calc(var(--offset) * theme.border-radius("xl"));
+ border-top-right-radius: calc(var(--offset) * theme.border-radius("xl"));
+ overflow: hidden auto;
+ transform: scale(calc(100% - (var(--offset) * 5%)));
+ height: 100dvh;
+ scrollbar-gutter: stable;
+
+ .header {
+ z-index: 1;
+ }
+
+ .main {
+ isolation: isolate;
+ padding-top: theme.spacing(4);
+
+ @include theme.breakpoint("sm") {
+ min-height: unset;
+ padding-top: theme.spacing(6);
+ }
+ }
+
+ .mainNavTabs {
+ display: none;
+
+ @include theme.breakpoint("sm") {
+ display: flex;
+ }
+ }
+
+ .mobileNavTabs {
+ @include theme.breakpoint("sm") {
+ display: none;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/component-library/src/AppShell/index.stories.tsx b/packages/component-library/src/AppShell/index.stories.tsx
new file mode 100644
index 0000000000..9a6f345be4
--- /dev/null
+++ b/packages/component-library/src/AppShell/index.stories.tsx
@@ -0,0 +1,51 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { AppBody as AppShellComponent } from "./index.js";
+
+const meta = {
+ component: AppShellComponent,
+ globals: {
+ bare: true,
+ theme: {
+ disable: true,
+ },
+ },
+ parameters: {
+ layout: "fullscreen",
+ themes: {
+ disable: true,
+ },
+ },
+ argTypes: {
+ tabs: {
+ table: {
+ disable: true,
+ },
+ },
+ appName: {
+ control: "text",
+ table: {
+ category: "Contents",
+ },
+ },
+ children: {
+ control: "text",
+ table: {
+ category: "Contents",
+ },
+ },
+ },
+} satisfies Meta;
+export default meta;
+
+export const AppShell = {
+ args: {
+ appName: "Component Library",
+ children: "Hello world!",
+ tabs: [
+ { children: "Home", segment: "" },
+ { children: "Foo", segment: "foo" },
+ { children: "Bar", segment: "bar" },
+ ],
+ },
+} satisfies StoryObj;
diff --git a/packages/component-library/src/AppShell/index.tsx b/packages/component-library/src/AppShell/index.tsx
new file mode 100644
index 0000000000..4d6f4ec809
--- /dev/null
+++ b/packages/component-library/src/AppShell/index.tsx
@@ -0,0 +1,107 @@
+import { GoogleAnalytics } from "@next/third-parties/google";
+import clsx from "clsx";
+import dynamic from "next/dynamic";
+import type { ComponentProps, ReactNode } from "react";
+
+import { Amplitude } from "./amplitude.js";
+import { BodyProviders } from "./body-providers.js";
+import { sans } from "./fonts.js";
+import { HtmlWithLang } from "./html-with-lang.js";
+import { I18nProvider } from "./i18n-provider.js";
+import styles from "./index.module.scss";
+import { TabRoot, TabPanel } from "./tabs";
+import { Footer } from "../Footer/index.js";
+import { Header } from "../Header/index.js";
+import { MainNavTabs } from "../MainNavTabs/index.js";
+import { MobileNavTabs } from "../MobileNavTabs/index.js";
+import { ComposeProviders } from "../compose-providers.js";
+import { RouterProvider } from "./router-provider.js";
+import { LoggerProvider } from "../useLogger/index.js";
+
+import "./base.scss";
+
+const ReportAccessibility = dynamic(() =>
+ import("./report-accessibility.js").then((mod) => mod.ReportAccessibility),
+);
+
+type Tab = ComponentProps["tabs"][number] & {
+ children: ReactNode;
+};
+
+type Props = AppBodyProps & {
+ enableAccessibilityReporting: boolean;
+ amplitudeApiKey?: string | undefined;
+ googleAnalyticsId?: string | undefined;
+ providers?: ComponentProps["providers"] | undefined;
+};
+
+export const AppShell = ({
+ enableAccessibilityReporting,
+ amplitudeApiKey,
+ googleAnalyticsId,
+ providers,
+ ...props
+}: Props) => (
+
+
+
+
+
+ {googleAnalyticsId && }
+ {amplitudeApiKey && }
+ {enableAccessibilityReporting && }
+
+
+);
+
+type AppBodyProps = Pick<
+ ComponentProps,
+ "appName" | "mainCta" | "extraCta"
+> & {
+ tabs?: Tab[] | undefined;
+ children: ReactNode;
+};
+
+export const AppBody = ({ tabs, children, ...props }: AppBodyProps) => (
+
+
+
+ )
+ }
+ {...props}
+ />
+
+ {children}
+
+
+ {tabs && }
+
+
+);
+
+type RootProvidersProps = Omit<
+ ComponentProps,
+ "providers"
+> & {
+ providers?: ComponentProps["providers"] | undefined;
+};
+
+export const RootProviders = ({ providers, ...props }: RootProvidersProps) => (
+
+);
diff --git a/packages/next-root/src/report-accessibility.tsx b/packages/component-library/src/AppShell/report-accessibility.ts
similarity index 92%
rename from packages/next-root/src/report-accessibility.tsx
rename to packages/component-library/src/AppShell/report-accessibility.ts
index 6d8fc09d10..b68c9581ea 100644
--- a/packages/next-root/src/report-accessibility.tsx
+++ b/packages/component-library/src/AppShell/report-accessibility.ts
@@ -1,10 +1,11 @@
"use client";
-import { useLogger } from "@pythnetwork/app-logger";
import { useEffect } from "react";
import * as React from "react";
import * as ReactDOM from "react-dom";
+import { useLogger } from "../useLogger/index.js";
+
const AXE_TIMEOUT = 1000;
export const ReportAccessibility = () => {
diff --git a/packages/next-root/src/router-provider.tsx b/packages/component-library/src/AppShell/router-provider.tsx
similarity index 100%
rename from packages/next-root/src/router-provider.tsx
rename to packages/component-library/src/AppShell/router-provider.tsx
diff --git a/packages/component-library/src/AppShell/tabs.tsx b/packages/component-library/src/AppShell/tabs.tsx
new file mode 100644
index 0000000000..2e11d5fc7c
--- /dev/null
+++ b/packages/component-library/src/AppShell/tabs.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { useSelectedLayoutSegment } from "next/navigation";
+import type { ComponentProps } from "react";
+
+import { TabPanel as UnstyledTabPanel, Tabs } from "../unstyled/Tabs/index.js";
+
+export const TabRoot = (
+ props: Omit, "selectedKey">,
+) => {
+ const tabId = useSelectedLayoutSegment() ?? "";
+
+ return ;
+};
+
+export const TabPanel = (
+ props: Omit, "id">,
+) => {
+ const tabId = useSelectedLayoutSegment() ?? "";
+
+ return ;
+};
diff --git a/packages/component-library/src/Card/index.stories.tsx b/packages/component-library/src/Card/index.stories.tsx
index 1247ba8cb9..ddc12c63e0 100644
--- a/packages/component-library/src/Card/index.stories.tsx
+++ b/packages/component-library/src/Card/index.stories.tsx
@@ -5,6 +5,12 @@ import { Card as CardComponent, VARIANTS } from "./index.js";
const meta = {
component: CardComponent,
+ globals: {
+ background: "primary",
+ },
+ parameters: {
+ layout: "padded",
+ },
argTypes: {
href: {
control: "text",
diff --git a/packages/component-library/src/CrossfadeTabPanels/index.tsx b/packages/component-library/src/CrossfadeTabPanels/index.tsx
index 50709c5b19..8e859a8cde 100644
--- a/packages/component-library/src/CrossfadeTabPanels/index.tsx
+++ b/packages/component-library/src/CrossfadeTabPanels/index.tsx
@@ -7,7 +7,7 @@ import { TabListStateContext } from "react-aria-components";
import { TabPanel as UnstyledTabPanel } from "../unstyled/Tabs/index.js";
-const AnimatedPanel = motion(UnstyledTabPanel);
+const AnimatedPanel = motion.create(UnstyledTabPanel);
type Props = {
items: {
diff --git a/apps/insights/src/components/Root/footer.module.scss b/packages/component-library/src/Footer/index.module.scss
similarity index 96%
rename from apps/insights/src/components/Root/footer.module.scss
rename to packages/component-library/src/Footer/index.module.scss
index 17c4389870..728ff1dcce 100644
--- a/apps/insights/src/components/Root/footer.module.scss
+++ b/packages/component-library/src/Footer/index.module.scss
@@ -1,7 +1,6 @@
-@use "@pythnetwork/component-library/theme";
+@use "../theme";
.footer {
- background: theme.color("background", "primary");
padding: theme.spacing(8) 0;
.topContent {
diff --git a/packages/component-library/src/Footer/index.stories.tsx b/packages/component-library/src/Footer/index.stories.tsx
new file mode 100644
index 0000000000..372d7d751d
--- /dev/null
+++ b/packages/component-library/src/Footer/index.stories.tsx
@@ -0,0 +1,16 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { Footer as FooterComponent } from "./index.js";
+
+const meta = {
+ component: FooterComponent,
+ parameters: {
+ layout: "fullscreen",
+ },
+ argTypes: {},
+} satisfies Meta;
+export default meta;
+
+export const Footer = {
+ args: {},
+} satisfies StoryObj;
diff --git a/apps/insights/src/components/Root/footer.tsx b/packages/component-library/src/Footer/index.tsx
similarity index 91%
rename from apps/insights/src/components/Root/footer.tsx
rename to packages/component-library/src/Footer/index.tsx
index c98cf2e162..ab432389db 100644
--- a/apps/insights/src/components/Root/footer.tsx
+++ b/packages/component-library/src/Footer/index.tsx
@@ -1,12 +1,12 @@
-import type { Props as ButtonProps } from "@pythnetwork/component-library/Button";
-import { Button } from "@pythnetwork/component-library/Button";
-import { Link } from "@pythnetwork/component-library/Link";
import type { ComponentProps, ElementType } from "react";
-import styles from "./footer.module.scss";
-import { socialLinks } from "./social-links";
-import { SupportDrawer } from "./support-drawer";
+import styles from "./index.module.scss";
import Wordmark from "./wordmark.svg";
+import type { Props as ButtonProps } from "../Button/index.js";
+import { Button } from "../Button/index.js";
+import { SupportDrawer } from "../Header/index.js";
+import { Link } from "../Link/index.js";
+import { socialLinks } from "../social-links.js";
export const Footer = () => (