+ Each Price Feed Component that a Publisher{" "}
+ provides has an associated Score, which is determined
+ by that component{"'"}s Uptime,{" "}
+ Price Deviation, and Staleness. The publisher
+ {"'"}s Median Score measures the 50th percentile of
+ the Score across all of that publisher{"'"}s{" "}
+ Price Feed Components. The{" "}
+ Average Median Score is the average of the{" "}
+ Median Scores of all publishers who contribute to the
+ Pyth Network.
+
+
+ Learn more
+
+
+
+ }
stat={(
publishers.reduce(
(sum, publisher) => sum + publisher.medianScore,
diff --git a/packages/app-logger/src/index.tsx b/packages/app-logger/src/index.tsx
index 105df6c18f..804b3509cf 100644
--- a/packages/app-logger/src/index.tsx
+++ b/packages/app-logger/src/index.tsx
@@ -7,11 +7,13 @@ export const useLogger = () => {
if (logger) {
return logger;
} else {
- throw new NotInitializedError();
+ throw new LoggerNotInitializedError();
}
};
-class NotInitializedError extends Error {
- override message =
- "This component must be contained within a `LoggerProvider`!";
+class LoggerNotInitializedError extends Error {
+ constructor() {
+ super("This component must be contained within a ");
+ this.name = "LoggerNotInitializedError";
+ }
}
diff --git a/packages/component-library/.storybook/preview.tsx b/packages/component-library/.storybook/preview.tsx
index 5e4e2298f3..3e17d83e12 100644
--- a/packages/component-library/.storybook/preview.tsx
+++ b/packages/component-library/.storybook/preview.tsx
@@ -5,6 +5,7 @@ import clsx from "clsx";
import "../src/Html/base.scss";
import styles from "./storybook.module.scss";
+import { OverlayVisibleContextProvider } from "../src/overlay-visible-context.js";
const preview = {
parameters: {
@@ -28,6 +29,11 @@ const preview = {
export default preview;
export const decorators: Decorator[] = [
+ (Story) => (
+
+
+
+ ),
withThemeByClassName({
themes: {
Light: clsx(sans.className, styles.light),
diff --git a/packages/component-library/src/Alert/index.module.scss b/packages/component-library/src/Alert/index.module.scss
new file mode 100644
index 0000000000..7ec4520637
--- /dev/null
+++ b/packages/component-library/src/Alert/index.module.scss
@@ -0,0 +1,63 @@
+@use "../theme";
+
+.modalOverlay {
+ position: fixed;
+ inset: 0;
+ z-index: 1;
+
+ .modal {
+ position: fixed;
+ bottom: theme.spacing(8);
+ right: theme.spacing(8);
+ outline: none;
+
+ .dialog {
+ background: theme.color("states", "info", "background-opaque");
+ border-radius: theme.border-radius("3xl");
+ backdrop-filter: blur(32px);
+ width: theme.spacing(156);
+ outline: none;
+ position: relative;
+ padding: theme.spacing(6);
+ padding-right: theme.spacing(16);
+ display: flex;
+ flex-flow: column nowrap;
+ gap: theme.spacing(4);
+
+ .closeButton {
+ position: absolute;
+ right: theme.spacing(2);
+ top: theme.spacing(2);
+ }
+
+ .title {
+ @include theme.h4;
+
+ display: flex;
+ flex-flow: row nowrap;
+ gap: theme.spacing(3);
+ align-items: center;
+ color: theme.color("heading");
+ line-height: 1;
+
+ .icon {
+ color: theme.color("states", "info", "normal");
+ flex: none;
+ display: grid;
+ place-content: center;
+ font-size: theme.spacing(6);
+ }
+ }
+
+ .body {
+ color: theme.color("paragraph");
+ font-size: theme.font-size("sm");
+ line-height: 140%;
+ display: flex;
+ flex-flow: column nowrap;
+ gap: theme.spacing(4);
+ align-items: flex-start;
+ }
+ }
+ }
+}
diff --git a/packages/component-library/src/Alert/index.stories.tsx b/packages/component-library/src/Alert/index.stories.tsx
new file mode 100644
index 0000000000..5f82370ecc
--- /dev/null
+++ b/packages/component-library/src/Alert/index.stories.tsx
@@ -0,0 +1,50 @@
+import * as Icon from "@phosphor-icons/react/dist/ssr";
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { Alert as AlertComponent, AlertTrigger } from "./index.js";
+import { Button } from "../Button/index.js";
+
+const meta = {
+ component: AlertComponent,
+ decorators: [
+ (Story) => (
+
+
+
+
+ ),
+ ],
+ argTypes: {
+ icon: {
+ control: "select",
+ options: Object.keys(Icon),
+ mapping: Object.fromEntries(
+ Object.entries(Icon).map(([key, Icon]) => [key, ]),
+ ),
+ table: {
+ category: "Contents",
+ },
+ },
+ title: {
+ control: "text",
+ table: {
+ category: "Contents",
+ },
+ },
+ children: {
+ control: "text",
+ table: {
+ category: "Contents",
+ },
+ },
+ },
+} satisfies Meta;
+export default meta;
+
+export const Alert = {
+ args: {
+ title: "An Alert",
+ children:
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ },
+} satisfies StoryObj;
diff --git a/packages/component-library/src/Alert/index.tsx b/packages/component-library/src/Alert/index.tsx
new file mode 100644
index 0000000000..546eb6ee47
--- /dev/null
+++ b/packages/component-library/src/Alert/index.tsx
@@ -0,0 +1,69 @@
+"use client";
+
+import { XCircle } from "@phosphor-icons/react/dist/ssr/XCircle";
+import clsx from "clsx";
+import type { ComponentProps, ReactNode } from "react";
+import { Dialog, Heading } from "react-aria-components";
+
+import styles from "./index.module.scss";
+import { Button } from "../Button/index.js";
+import { Modal } from "../Modal/index.js";
+
+export { DialogTrigger as AlertTrigger } from "react-aria-components";
+
+const CLOSE_DURATION_IN_S = 0.1;
+export const CLOSE_DURATION_IN_MS = CLOSE_DURATION_IN_S * 1000;
+
+type OwnProps = Pick, "children"> & {
+ icon?: ReactNode | undefined;
+ title: ReactNode;
+};
+
+type Props = Omit, keyof OwnProps> & OwnProps;
+
+export const Alert = ({
+ icon,
+ title,
+ children,
+ className,
+ ...props
+}: Props) => (
+
+ {(state) => (
+
+ )}
+
+);
diff --git a/packages/component-library/src/Drawer/index.module.scss b/packages/component-library/src/Drawer/index.module.scss
index ae70fc5dd4..6ba1686bdc 100644
--- a/packages/component-library/src/Drawer/index.module.scss
+++ b/packages/component-library/src/Drawer/index.module.scss
@@ -13,13 +13,13 @@
right: theme.spacing(4);
width: 40%;
max-width: theme.spacing(160);
- background: theme.color("background", "primary");
- border: 1px solid theme.color("border");
- border-radius: theme.border-radius("3xl");
outline: none;
- overflow: hidden;
.dialog {
+ background: theme.color("background", "primary");
+ border: 1px solid theme.color("border");
+ border-radius: theme.border-radius("3xl");
+ overflow: hidden;
outline: none;
display: flex;
flex-flow: column nowrap;
diff --git a/packages/component-library/src/Drawer/index.tsx b/packages/component-library/src/Drawer/index.tsx
index 9afce5ebf7..c7f6149153 100644
--- a/packages/component-library/src/Drawer/index.tsx
+++ b/packages/component-library/src/Drawer/index.tsx
@@ -2,116 +2,64 @@
import { XCircle } from "@phosphor-icons/react/dist/ssr/XCircle";
import clsx from "clsx";
-import { motion, AnimatePresence } from "motion/react";
-import {
- type ComponentProps,
- type ReactNode,
- type ContextType,
- use,
- useCallback,
- useEffect,
-} from "react";
-import {
- Dialog,
- Heading,
- Modal as ModalComponent,
- ModalOverlay as ModalOverlayComponent,
- OverlayTriggerStateContext,
-} from "react-aria-components";
+import type { ComponentProps, ReactNode } from "react";
+import { Dialog, Heading } from "react-aria-components";
import styles from "./index.module.scss";
import { Button } from "../Button/index.js";
-import { useSetOverlayVisible } from "../overlay-visible-context.js";
+import { Modal } from "../Modal/index.js";
export { DialogTrigger as DrawerTrigger } from "react-aria-components";
const CLOSE_DURATION_IN_S = 0.15;
export const CLOSE_DURATION_IN_MS = CLOSE_DURATION_IN_S * 1000;
-// @ts-expect-error Looks like there's a typing mismatch currently between
-// motion and react, probably due to us being on react 19. I'm expecting this
-// will go away when react 19 is officially stabilized...
-const ModalOverlay = motion.create(ModalOverlayComponent);
-// @ts-expect-error Looks like there's a typing mismatch currently between
-// motion and react, probably due to us being on react 19. I'm expecting this
-// will go away when react 19 is officially stabilized...
-const Modal = motion.create(ModalComponent);
-
-type OwnProps = {
+type OwnProps = Pick, "children"> & {
title: ReactNode;
- children:
- | ReactNode
- | ((
- state: NonNullable>,
- ) => ReactNode);
};
type Props = Omit, keyof OwnProps> & OwnProps;
-export const Drawer = ({ title, children, className, ...props }: Props) => {
- const state = use(OverlayTriggerStateContext);
- const { hideOverlay, showOverlay } = useSetOverlayVisible();
-
- useEffect(() => {
- if (state?.isOpen) {
- showOverlay();
- }
- }, [state, showOverlay]);
-
- const onOpenChange = useCallback(
- (newValue: boolean) => {
- state?.setOpen(newValue);
- },
- [state],
- );
-
- return (
-
- {state?.isOpen && (
-
- (
+
+ {(state) => (
+
+ )}
+
+);
diff --git a/packages/component-library/src/Modal/index.tsx b/packages/component-library/src/Modal/index.tsx
new file mode 100644
index 0000000000..267a04d20d
--- /dev/null
+++ b/packages/component-library/src/Modal/index.tsx
@@ -0,0 +1,78 @@
+"use client";
+
+import { motion, AnimatePresence } from "motion/react";
+import {
+ type ComponentProps,
+ type ContextType,
+ type ReactNode,
+ use,
+ useCallback,
+ useEffect,
+} from "react";
+import {
+ Modal as ModalComponent,
+ ModalOverlay,
+ OverlayTriggerStateContext,
+} from "react-aria-components";
+
+import { useSetOverlayVisible } from "../overlay-visible-context.js";
+
+// @ts-expect-error Looks like there's a typing mismatch currently between
+// motion and react, probably due to us being on react 19. I'm expecting this
+// will go away when react 19 is officially stabilized...
+const MotionModal = motion.create(ModalComponent);
+
+// @ts-expect-error Looks like there's a typing mismatch currently between
+// motion and react, probably due to us being on react 19. I'm expecting this
+// will go away when react 19 is officially stabilized...
+const MotionModalOverlay = motion.create(ModalOverlay);
+
+type OwnProps = {
+ overlayProps?: Omit<
+ ComponentProps,
+ "isOpen" | "isDismissable" | "onOpenChange"
+ >;
+ children:
+ | ReactNode
+ | ((
+ state: NonNullable>,
+ ) => ReactNode);
+};
+
+type Props = Omit, keyof OwnProps> &
+ OwnProps;
+
+export const Modal = ({ overlayProps, children, ...props }: Props) => {
+ const state = use(OverlayTriggerStateContext);
+ const { hideOverlay, showOverlay } = useSetOverlayVisible();
+
+ useEffect(() => {
+ if (state?.isOpen) {
+ showOverlay();
+ }
+ }, [state, showOverlay]);
+
+ const onOpenChange = useCallback(
+ (newValue: boolean) => {
+ state?.setOpen(newValue);
+ },
+ [state],
+ );
+
+ return (
+
+ {state?.isOpen && (
+
+
+ {typeof children === "function" ? children(state) : children}
+
+
+ )}
+
+ );
+};
diff --git a/packages/component-library/src/StatCard/index.module.scss b/packages/component-library/src/StatCard/index.module.scss
index e7b2c6735c..6dc24a4996 100644
--- a/packages/component-library/src/StatCard/index.module.scss
+++ b/packages/component-library/src/StatCard/index.module.scss
@@ -11,6 +11,13 @@
padding: theme.spacing(3);
padding-bottom: theme.spacing(2);
+ .corner {
+ position: absolute;
+ right: theme.spacing(3);
+ top: theme.spacing(3);
+ display: flex;
+ }
+
.header {
color: theme.color("muted");
text-align: left;
diff --git a/packages/component-library/src/StatCard/index.stories.tsx b/packages/component-library/src/StatCard/index.stories.tsx
index 967566e5a4..13871a5e91 100644
--- a/packages/component-library/src/StatCard/index.stories.tsx
+++ b/packages/component-library/src/StatCard/index.stories.tsx
@@ -31,6 +31,12 @@ const meta = {
category: "Contents",
},
},
+ corner: {
+ control: "text",
+ table: {
+ category: "Contents",
+ },
+ },
},
} satisfies Meta;
export default meta;
@@ -47,5 +53,6 @@ export const StatCard = {
header: "Active Feeds",
stat: "552",
miniStat: "+5",
+ corner: ":)",
},
} satisfies StoryObj;
diff --git a/packages/component-library/src/StatCard/index.tsx b/packages/component-library/src/StatCard/index.tsx
index d97f9d604c..4f3639b7b8 100644
--- a/packages/component-library/src/StatCard/index.tsx
+++ b/packages/component-library/src/StatCard/index.tsx
@@ -11,6 +11,7 @@ type Props = Omit<
header: ReactNode;
stat: ReactNode;
miniStat?: ReactNode | undefined;
+ corner?: ReactNode | undefined;
};
export const StatCard = ({
@@ -18,10 +19,12 @@ export const StatCard = ({
stat,
miniStat,
className,
+ corner,
...props
}: Props) => (
+ {corner &&
{corner}
}
{header}
{stat}
diff --git a/packages/component-library/src/theme.scss b/packages/component-library/src/theme.scss
index a5df474bb7..51ea440155 100644
--- a/packages/component-library/src/theme.scss
+++ b/packages/component-library/src/theme.scss
@@ -469,6 +469,11 @@ $color: (
light-dark(pallette-color("steel", 900), pallette-color("steel", 50)),
),
"info": (
+ "background-opaque":
+ light-dark(
+ rgb(from pallette-color("indigo", 200) r g b / 80%),
+ rgb(from pallette-color("indigo", 950) r g b / 80%)
+ ),
"normal":
light-dark(pallette-color("indigo", 600), pallette-color("indigo", 400)),
),