Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(insights): responsive layouts and components #2285

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/insights/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DISABLE_ACCESSIBILITY_REPORTING=true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't check this config in; this is a local setting and isn't intended to be checked in. We can gitignore this file instead.

1 change: 1 addition & 0 deletions apps/insights/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env*.local
.env*.development
25 changes: 25 additions & 0 deletions apps/insights/src/components/CardTitle/index.module.scss
Original file line number Diff line number Diff line change
@@ -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");
}
}
20 changes: 20 additions & 0 deletions apps/insights/src/components/CardTitle/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={clsx(styles.cardTitle, props.className)} {...props}>
{icon && <div className={styles.icon}>{icon}</div>}
<h2 className={styles.title}>{children}</h2>
{badge}
</div>
)
}
1 change: 0 additions & 1 deletion apps/insights/src/components/CopyButton/index.tsx
Original file line number Diff line number Diff line change
@@ -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(() => {
54 changes: 54 additions & 0 deletions apps/insights/src/components/MobileMenu/mobile-menu.module.scss
Original file line number Diff line number Diff line change
@@ -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");
}
29 changes: 29 additions & 0 deletions apps/insights/src/components/MobileMenu/mobile-menu.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={clsx(styles.mobileMenuTrigger, className)} {...props}>
<Button
variant="ghost"
size="sm"
afterIcon={List}
rounded
onPress={toggleMenu}
>
Menu
</Button>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styles from "./mobile-navigation.module.scss";
import { MainNavTabs } from "../Root/tabs";
export const MobileNavigation = () => {
return (
<div className={styles.mobileNavigation}>
<MainNavTabs />
</div>
);
};
10 changes: 6 additions & 4 deletions apps/insights/src/components/Overview/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<div className={styles.overview}>
<h1 className={styles.header}>Overview</h1>
</div>
<PageLayout title={"Overview"}>
<Card title="Overview"></Card>
</PageLayout>
);
24 changes: 24 additions & 0 deletions apps/insights/src/components/PageLayout/page-layout.module.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
15 changes: 15 additions & 0 deletions apps/insights/src/components/PageLayout/page-layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={styles.pageLayout}>
<div className={styles.pageTitleContainer}>
<h1 className={styles.pageTitle}>{title}</h1>
{actions && <div className={styles.actions}>{actions}</div>}
</div>
{children}
</div>
)
}
8 changes: 6 additions & 2 deletions apps/insights/src/components/PriceFeedTag/index.module.scss
Original file line number Diff line number Diff line change
@@ -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");
}
85 changes: 36 additions & 49 deletions apps/insights/src/components/PriceFeeds/index.module.scss
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
176 changes: 87 additions & 89 deletions apps/insights/src/components/PriceFeeds/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={styles.priceFeeds}>
<h1 className={styles.header}>Price Feeds</h1>
<div className={styles.body}>
<section className={styles.stats}>
<StatCard
variant="primary"
header="Active Feeds"
stat={priceFeeds.activeFeeds.length}
href={`#${PRICE_FEEDS_ANCHOR}`}
corner={<ArrowLineDown />}
/>
<StatCard
header="Frequency"
stat={priceFeedsStaticConfig.updateFrequency}
/>
<PageLayout title={"Price Feeds"}>
<Stats>
<StatCard
variant="primary"
header="Active Feeds"
stat={priceFeeds.activeFeeds.length}
href={`#${PRICE_FEEDS_ANCHOR}`}
corner={<ArrowLineDown />}
/>
<StatCard
header="Frequency"
stat={priceFeedsStaticConfig.updateFrequency}
/>
<StatCard
header="Active Chains"
stat={priceFeedsStaticConfig.activeChains}
href="https://docs.pyth.network/price-feeds/contract-addresses"
target="_blank"
corner={<ArrowSquareOut weight="fill" />}
/>
<AssetClassesDrawer numFeedsByAssetClass={numFeedsByAssetClass}>
<StatCard
header="Active Chains"
stat={priceFeedsStaticConfig.activeChains}
href="https://docs.pyth.network/price-feeds/contract-addresses"
target="_blank"
corner={<ArrowSquareOut weight="fill" />}
header="Asset Classes"
stat={Object.keys(numFeedsByAssetClass).length}
corner={<Info weight="fill" />}
/>
<AssetClassesDrawer numFeedsByAssetClass={numFeedsByAssetClass}>
<StatCard
header="Asset Classes"
stat={Object.keys(numFeedsByAssetClass).length}
corner={<Info weight="fill" />}
/>
</AssetClassesDrawer>
</section>
<YesterdaysPricesProvider
feeds={Object.fromEntries(
featuredRecentlyAdded.map(({ symbol, product }) => [
symbol,
product.price_account,
]),
)}
>
<FeaturedFeedsCard
title="Recently Added"
icon={<StackPlus />}
feeds={featuredRecentlyAdded}
showPrices
linkFeeds
/>
</YesterdaysPricesProvider>
</AssetClassesDrawer>
</Stats>
<YesterdaysPricesProvider
feeds={Object.fromEntries(
featuredRecentlyAdded.map(({ symbol, product }) => [
symbol,
product.price_account,
]),
)}
>
<FeaturedFeedsCard
title="Coming Soon"
icon={<ClockCountdown />}
feeds={featuredComingSoon}
toolbar={
<DrawerTrigger>
<Button size="xs" variant="outline">
Show all
</Button>
<Drawer
fill
className={styles.comingSoonCard ?? ""}
title={
<>
<span>Coming Soon</span>
<Badge>{priceFeeds.comingSoon.length}</Badge>
</>
}
>
<ComingSoonList
comingSoonFeeds={priceFeeds.comingSoon.map((feed) => ({
id: feed.product.price_account,
displaySymbol: feed.product.display_symbol,
assetClass: feed.product.asset_type,
icon: (
<PriceFeedIcon symbol={feed.product.display_symbol} />
),
}))}
/>
</Drawer>
</DrawerTrigger>
}
/>
<PriceFeedsCard
id={PRICE_FEEDS_ANCHOR}
priceFeeds={priceFeeds.activeFeeds.map((feed) => ({
symbol: feed.symbol,
icon: <PriceFeedIcon symbol={feed.product.display_symbol} />,
id: feed.product.price_account,
displaySymbol: feed.product.display_symbol,
assetClass: feed.product.asset_type,
exponent: feed.price.exponent,
numQuoters: feed.price.numQuoters,
}))}
title={<CardTitle icon={<StackPlus />}>Recently added</CardTitle>}
feeds={featuredRecentlyAdded}
showPrices
linkFeeds
/>
</div>
</div>
</YesterdaysPricesProvider>
<FeaturedFeedsCard
title={<CardTitle icon={<ClockCountdown />}>Coming soon</CardTitle>}
feeds={featuredComingSoon}
action={
<DrawerTrigger>
<Button size="sm" variant="outline">
Show all
</Button>
<Drawer
fill
className={styles.comingSoonCard ?? ""}
title={
<>
<span>Coming Soon</span>
<Badge>{priceFeeds.comingSoon.length}</Badge>
</>
}
>
<ComingSoonList
comingSoonFeeds={priceFeeds.comingSoon.map((feed) => ({
id: feed.product.price_account,
displaySymbol: feed.product.display_symbol,
assetClass: feed.product.asset_type,
icon: (
<PriceFeedIcon symbol={feed.product.display_symbol} />
),
}))}
/>
</Drawer>
</DrawerTrigger>
}
/>
<PriceFeedsCard
id={PRICE_FEEDS_ANCHOR}
priceFeeds={priceFeeds.activeFeeds.map((feed) => ({
symbol: feed.symbol,
icon: <PriceFeedIcon symbol={feed.product.display_symbol} />,
id: feed.product.price_account,
displaySymbol: feed.product.display_symbol,
assetClass: feed.product.asset_type,
exponent: feed.price.exponent,
numQuoters: feed.price.numQuoters,
}))}
/>
</PageLayout>
);
};

Original file line number Diff line number Diff line change
@@ -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");
}
}
32 changes: 32 additions & 0 deletions apps/insights/src/components/PriceFeeds/price-feed-items.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import styles from "./price-feed-items.module.scss";
import { PriceFeedTag } from "../PriceFeedTag";
import { StructuredList } from "../StructuredList";

export const PriceFeedItems = () => {
return (
<div className={styles.priceFeedItemsWrapper}>
{Array.from({ length: 20 }).map((_, index) => {
return (
<div className={styles.priceFeedItem} key={index}>
<StructuredList
items={[
{
label: <PriceFeedTag compact isLoading />,
value: "$32,323.22",
},
{
label: "Last Price",
value: "$10,000.00",
},
{
label: "Last Updated",
value: "2022-01-01",
},
]}
/>
</div>
);
})}
</div>
);
};
142 changes: 71 additions & 71 deletions apps/insights/src/components/PriceFeeds/price-feeds-card.tsx
Original file line number Diff line number Diff line change
@@ -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<Props, "id"> &
(
| { 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) => (
<Card
id={id}
icon={<ChartLine />}
title={
<>
<span>Price Feeds</span>
{!props.isLoading && (
<Badge style="filled" variant="neutral" size="md">
{props.numResults}
</Badge>
)}
</>
<CardTitle icon={<ChartLine />} badge={!props.isLoading && (
<Badge style="filled" variant="neutral" size="md">
{props.numResults}
</Badge>
)}>Price Feeds</CardTitle>
}
toolbar={
<>
<SearchInput
size="sm"
width={50}
placeholder="Feed symbol"
{...(props.isLoading
? { isPending: true, isDisabled: true }
: {
value: props.search,
onChange: props.onSearchChange,
})}
/>
<Select<string>
label="Asset Class"
size="sm"
@@ -246,29 +255,18 @@ const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
{...(props.isLoading
? { isPending: true, options: [], buttonLabel: "Asset Class" }
: {
optionGroups: [
{ name: "All", options: [""] },
{ name: "Asset classes", options: props.assetClasses },
],
hideGroupLabel: true,
show: (value) => (value === "" ? "All" : value),
placement: "bottom end",
buttonLabel:
props.assetClass === "" ? "Asset Class" : props.assetClass,
selectedKey: props.assetClass,
onSelectionChange: props.onAssetClassChange,
})}
/>
<SearchInput
size="sm"
width={50}
placeholder="Feed symbol"
{...(props.isLoading
? { isPending: true, isDisabled: true }
: {
value: props.search,
onChange: props.onSearchChange,
})}
optionGroups: [
{ name: "All", options: [""] },
{ name: "Asset classes", options: props.assetClasses },
],
hideGroupLabel: true,
show: (value) => (value === "" ? "All" : value),
placement: "bottom end",
buttonLabel:
props.assetClass === "" ? "Asset Class" : props.assetClass,
selectedKey: props.assetClass,
onSelectionChange: props.onAssetClassChange,
})}
/>
</>
}
@@ -286,6 +284,8 @@ const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
),
})}
>
<PriceFeedItems />

<Table
rounded
fill
@@ -344,21 +344,21 @@ const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
]}
{...(props.isLoading
? {
isLoading: true,
}
isLoading: true,
}
: {
rows: props.rows,
sortDescriptor: props.sortDescriptor,
onSortChange: props.onSortChange,
emptyState: (
<NoResults
query={props.search}
onClearSearch={() => {
props.onSearchChange("");
}}
/>
),
})}
rows: props.rows,
sortDescriptor: props.sortDescriptor,
onSortChange: props.onSortChange,
emptyState: (
<NoResults
query={props.search}
onClearSearch={() => {
props.onSearchChange("");
}}
/>
),
})}
/>
</Card>
);
3 changes: 1 addition & 2 deletions apps/insights/src/components/Publisher/layout.module.scss
Original file line number Diff line number Diff line change
@@ -84,8 +84,7 @@

.body {
@include theme.max-width;

padding-top: theme.spacing(6);
margin-top: theme.spacing(6);
}
}

447 changes: 225 additions & 222 deletions apps/insights/src/components/Publisher/layout.tsx

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions apps/insights/src/components/Publisher/performance.module.scss
Original file line number Diff line number Diff line change
@@ -2,11 +2,14 @@

.performance {
display: grid;
grid-template-columns: 1fr 1fr;
gap: theme.spacing(12) theme.spacing(6);
grid-template-columns: 1fr;
gap: theme.spacing(6);
align-items: flex-start;

> *:first-child {
grid-column: span 2 / span 2;
@include theme.breakpoint("md") {
grid-template-columns: 1fr 1fr;
> *:first-child {
grid-column: span 2 / span 2;
}
}
}
7 changes: 4 additions & 3 deletions apps/insights/src/components/Publisher/performance.tsx
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import { TopFeedsTable } from "./top-feeds-table";
import { getPublishers } from "../../services/clickhouse";
import { Cluster, getTotalFeedCount } from "../../services/pyth";
import { Status } from "../../status";
import { CardTitle } from "../CardTitle";
import { NoResults } from "../NoResults";
import { PriceFeedIcon } from "../PriceFeedIcon";
import { PriceFeedTag } from "../PriceFeedTag";
@@ -47,7 +48,7 @@ export const Performance = async ({ params }: Props) => {
notFound()
) : (
<div className={styles.performance}>
<Card icon={<Broadcast />} title="Publishers Ranking">
<Card icon={<Broadcast />} title={<CardTitle>Publisher Ranking</CardTitle>}>
<Table
rounded
fill
@@ -118,7 +119,7 @@ export const Performance = async ({ params }: Props) => {
})}
/>
</Card>
<Card icon={<Network />} title="High-Performing Feeds">
<Card title={<CardTitle icon={<Network />}>High-performing Feeds</CardTitle>}>
<TopFeedsTable
label="High-Performing Feeds"
publisherScoreWidth={PUBLISHER_SCORE_WIDTH}
@@ -138,7 +139,7 @@ export const Performance = async ({ params }: Props) => {
)}
/>
</Card>
<Card icon={<Network />} title="Low-Performing Feeds">
<Card title={<CardTitle icon={<Network />}>Low-performing Feeds</CardTitle>}>
<TopFeedsTable
label="Low-Performing Feeds"
publisherScoreWidth={PUBLISHER_SCORE_WIDTH}
96 changes: 48 additions & 48 deletions apps/insights/src/components/Publishers/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
@use "@pythnetwork/component-library/theme";
@use "../Root/index.module.scss" as root;

.publishers {
@include theme.max-width;
.body {
display: flex;
flex-flow: column nowrap;
width: 100%;

.header {
@include theme.h3;

color: theme.color("heading");
font-weight: theme.font-weight("semibold");
@include theme.breakpoint("lg") {
flex-flow: row nowrap;
align-items: flex-start;
gap: theme.spacing(6);
}

.body {
.stats {
display: flex;
flex-flow: row nowrap;
gap: theme.spacing(12);
align-items: flex-start;
margin-top: theme.spacing(6);
flex-flow: column;
gap: theme.spacing(6);
align-items: stretch;
flex-grow: 1;

.stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: theme.spacing(4);
align-items: center;
width: 40%;
@include theme.breakpoint("lg") {
position: sticky;
top: root.$header-height;
}

.averageMedianScoreExplainButton {
margin-top: -#{theme.button-padding("xs", false)};
margin-right: -#{theme.button-padding("xs", false)};
}

.oisCard {
grid-column: span 2 / span 2;
.averageMedianScoreExplainButton {
margin-top: -#{theme.button-padding("xs", false)};
margin-right: -#{theme.button-padding("xs", false)};
}

.oisPool {
.title {
font-size: theme.font-size("sm");
font-weight: theme.font-weight("normal");
color: theme.color("heading");
margin: 0;
}
.oisCard {
.oisPool {
.title {
font-size: theme.font-size("sm");
font-weight: theme.font-weight("normal");
color: theme.color("heading");
margin: 0;
}

.poolUsed {
margin: 0;
color: theme.color("heading");
.poolUsed {
margin: 0;
color: theme.color("heading");

@include theme.h3;
}
@include theme.h3;
}

.poolTotal {
margin: 0;
color: theme.color("muted");
font-size: theme.font-size("sm");
font-weight: theme.font-weight("normal");
}
.poolTotal {
margin: 0;
color: theme.color("muted");
font-size: theme.font-size("sm");
font-weight: theme.font-weight("normal");
}
}

.oisStats {
display: flex;
flex-flow: column;
gap: theme.spacing(1);

.oisStats {
@include theme.breakpoint("lg") {
column-span: 2;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: theme.spacing(1);
}
}
}
}

.publishersCard {
width: 60%;
}
.publishersCard {
width: 60%;
}
}

106 changes: 55 additions & 51 deletions apps/insights/src/components/Publishers/index.tsx
Original file line number Diff line number Diff line change
@@ -17,10 +17,13 @@ import {
getClaimableRewards,
getDistributedRewards,
} from "../../services/staking";
import { CardTitle } from "../CardTitle";
import { FormattedTokens } from "../FormattedTokens";
import { PageLayout } from "../PageLayout/page-layout";
import { PublisherIcon } from "../PublisherIcon";
import { PublisherTag } from "../PublisherTag";
import { SemicircleMeter, Label } from "../SemicircleMeter";
import { Stats } from "../Stats";
import { TokenIcon } from "../TokenIcon";

const INITIAL_REWARD_POOL_SIZE = 60_000_000_000_000n;
@@ -33,64 +36,65 @@ export const Publishers = async () => {
]);

return (
<div className={styles.publishers}>
<h1 className={styles.header}>Publishers</h1>
<PageLayout title={"Publishers"}>
<div className={styles.body}>
<section className={styles.stats}>
<StatCard
variant="primary"
header="Active Publishers"
stat={publishers.length}
/>
<StatCard
header="Avg. Median Score"
corner={
<AlertTrigger>
<Button
variant="ghost"
size="xs"
beforeIcon={(props) => <Info weight="fill" {...props} />}
rounded
hideText
className={styles.averageMedianScoreExplainButton ?? ""}
>
Explain Average Median Score
</Button>
<Alert title="Average Median Score" icon={<Lightbulb />}>
<p className={styles.averageMedianScoreDescription}>
Each <b>Price Feed Component</b> that a <b>Publisher</b>{" "}
provides has an associated <b>Score</b>, which is determined
by that component{"'"}s <b>Uptime</b>,{" "}
<b>Price Deviation</b>, and <b>Staleness</b>. The publisher
{"'"}s <b>Median Score</b> measures the 50th percentile of
the <b>Score</b> across all of that publisher{"'"}s{" "}
<b>Price Feed Components</b>. The{" "}
<b>Average Median Score</b> is the average of the{" "}
<b>Median Scores</b> of all publishers who contribute to the
Pyth Network.
</p>
<Stats>
<StatCard
variant="primary"
header="Active Publishers"
stat={publishers.length}
/>
<StatCard
header="Avg. Median Score"
corner={
<AlertTrigger>
<Button
variant="ghost"
size="xs"
variant="solid"
href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking"
target="_blank"
beforeIcon={(props) => <Info weight="fill" {...props} />}
rounded
hideText
className={styles.averageMedianScoreExplainButton ?? ""}
>
Learn more
Explain Average Median Score
</Button>
</Alert>
</AlertTrigger>
}
stat={(
publishers.reduce(
(sum, publisher) => sum + publisher.medianScore,
0,
) / publishers.length
).toFixed(2)}
/>
<Alert title="Average Median Score" icon={<Lightbulb />}>
<p className={styles.averageMedianScoreDescription}>
Each <b>Price Feed Component</b> that a <b>Publisher</b>{" "}
provides has an associated <b>Score</b>, which is determined
by that component{"'"}s <b>Uptime</b>,{" "}
<b>Price Deviation</b>, and <b>Staleness</b>. The publisher
{"'"}s <b>Median Score</b> measures the 50th percentile of
the <b>Score</b> across all of that publisher{"'"}s{" "}
<b>Price Feed Components</b>. The{" "}
<b>Average Median Score</b> is the average of the{" "}
<b>Median Scores</b> of all publishers who contribute to the
Pyth Network.
</p>
<Button
size="xs"
variant="solid"
href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking"
target="_blank"
>
Learn more
</Button>
</Alert>
</AlertTrigger>
}
stat={(
publishers.reduce(
(sum, publisher) => sum + publisher.medianScore,
0,
) / publishers.length
).toFixed(2)}
/>
</Stats>
<Card
title="Oracle Integrity Staking (OIS)"
title={<CardTitle>OIS</CardTitle>}
className={styles.oisCard}
toolbar={
action={
<Button
href="https://staking.pyth.network"
target="_blank"
@@ -169,7 +173,7 @@ export const Publishers = async () => {
)}
/>
</div>
</div>
</PageLayout >
);
};

87 changes: 42 additions & 45 deletions apps/insights/src/components/Publishers/publishers-card.tsx
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import { type ReactNode, Suspense, useMemo } from "react";
import { useFilter, useCollator } from "react-aria";

import { useQueryParamFilterPagination } from "../../use-query-param-filter-pagination";
import { CardTitle } from "../CardTitle";
import { NoResults } from "../NoResults";
import { PublisherTag } from "../PublisherTag";
import { Ranking } from "../Ranking";
@@ -35,9 +36,9 @@ type Publisher = {
inactiveFeeds: number;
medianScore: number;
} & (
| { name: string; icon: ReactNode }
| { name?: undefined; icon?: undefined }
);
| { name: string; icon: ReactNode }
| { name?: undefined; icon?: undefined }
);

export const PublishersCard = ({ publishers, ...props }: Props) => (
<Suspense fallback={<PublishersCardContents isLoading {...props} />}>
@@ -155,22 +156,22 @@ type PublishersCardContentsProps = Pick<
(
| { isLoading: true }
| {
isLoading?: false;
numResults: number;
search: string;
sortDescriptor: SortDescriptor;
numPages: number;
page: number;
pageSize: number;
onSearchChange: (newSearch: string) => void;
onSortChange: (newSort: SortDescriptor) => void;
onPageSizeChange: (newPageSize: number) => void;
onPageChange: (newPage: number) => void;
mkPageLink: (page: number) => string;
rows: RowConfig<
"ranking" | "name" | "activeFeeds" | "inactiveFeeds" | "medianScore"
>[];
}
isLoading?: false;
numResults: number;
search: string;
sortDescriptor: SortDescriptor;
numPages: number;
page: number;
pageSize: number;
onSearchChange: (newSearch: string) => void;
onSortChange: (newSort: SortDescriptor) => void;
onPageSizeChange: (newPageSize: number) => void;
onPageChange: (newPage: number) => void;
mkPageLink: (page: number) => string;
rows: RowConfig<
"ranking" | "name" | "activeFeeds" | "inactiveFeeds" | "medianScore"
>[];
}
);

const PublishersCardContents = ({
@@ -180,16 +181,12 @@ const PublishersCardContents = ({
}: PublishersCardContentsProps) => (
<Card
className={className}
icon={<Broadcast />}
title={
<>
<span>Publishers</span>
{!props.isLoading && (
<Badge style="filled" variant="neutral" size="md">
{props.numResults}
</Badge>
)}
</>
<CardTitle badge={!props.isLoading && (
<Badge style="filled" variant="neutral" size="md">
{props.numResults}
</Badge>
)} icon={<Broadcast />}>Publishers</CardTitle>
}
toolbar={
<SearchInput
@@ -199,9 +196,9 @@ const PublishersCardContents = ({
{...(props.isLoading
? { isPending: true, isDisabled: true }
: {
value: props.search,
onChange: props.onSearchChange,
})}
value: props.search,
onChange: props.onSearchChange,
})}
/>
}
{...(!props.isLoading && {
@@ -264,21 +261,21 @@ const PublishersCardContents = ({
]}
{...(props.isLoading
? {
isLoading: true,
}
isLoading: true,
}
: {
rows: props.rows,
sortDescriptor: props.sortDescriptor,
onSortChange: props.onSortChange,
emptyState: (
<NoResults
query={props.search}
onClearSearch={() => {
props.onSearchChange("");
}}
/>
),
})}
rows: props.rows,
sortDescriptor: props.sortDescriptor,
onSortChange: props.onSortChange,
emptyState: (
<NoResults
query={props.search}
onClearSearch={() => {
props.onSearchChange("");
}}
/>
),
})}
/>
</Card>
);
35 changes: 14 additions & 21 deletions apps/insights/src/components/Root/footer.module.scss
Original file line number Diff line number Diff line change
@@ -2,41 +2,32 @@

.footer {
// SM
background: theme.color("background", "primary");

// XL
margin-top: theme.spacing(8);
padding: theme.spacing(8) 0;

// bg-beige-100 sm:border-t sm:border-stone-300
background: theme.color("background", "primary");

.topContent {
display: flex;
gap: theme.spacing(6);

// SM
flex-flow: row nowrap;
align-items: center;
flex-flow: column nowrap;
align-items: flex-start;
justify-content: space-between;

@include theme.max-width;

// XL
margin-bottom: theme.spacing(12);

// py-6
margin-bottom: theme.spacing(6);

// flex-col
@include theme.breakpoint("sm") {
flex-flow: row nowrap;
align-items: center;
}

.left {
display: flex;
align-items: stretch;
justify-content: space-between;

// SM
gap: theme.spacing(6);

// gap-8

.logoLink {
height: theme.spacing(5);
box-sizing: content-box;
@@ -86,10 +77,12 @@
.bottomContent {
display: flex;
gap: theme.spacing(6);
flex-flow: column;

// SM
flex-flow: row nowrap;
justify-content: space-between;
@include theme.breakpoint("sm") {
flex-flow: row nowrap;
justify-content: space-between;
}

// "flex-col

5 changes: 3 additions & 2 deletions apps/insights/src/components/Root/footer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ArrowSquareOut } from "@phosphor-icons/react/dist/ssr/ArrowSquareOut";
import {
type Props as ButtonProps,
Button,
@@ -21,10 +22,10 @@ export const Footer = () => (
<div className={styles.divider} />
<div className={styles.help}>
<SupportDrawer>
<Link>Help</Link>
<Link>Support</Link>
</SupportDrawer>
<Link href="https://docs.pyth.network" target="_blank">
Documentation
Documentation <ArrowSquareOut />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice little touch, I like this. Thoughts about doing something like making the <Link> component automatically add this icon if it detects that the href property is an external url and not a relative url?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could make this smarter by recognising if it's an external link and have a default styling with icon for this. This will happen quite often

</Link>
</div>
</div>
54 changes: 31 additions & 23 deletions apps/insights/src/components/Root/header.module.scss
Original file line number Diff line number Diff line change
@@ -4,8 +4,9 @@
position: sticky;
top: 0;
width: 100%;
background-color: theme.color("background", "nav-blur");
backdrop-filter: blur(32px);
background-color: theme.color("background", "primary");
// TODO: This causes that navigation is not fixed
// backdrop-filter: blur(32px);

.content {
height: 100%;
@@ -16,18 +17,24 @@

.leftMenu {
flex: none;
gap: theme.spacing(6);
gap: theme.spacing(4);
position: relative;

@include theme.row;

.logoLink {
padding: theme.spacing(3);
margin: -#{theme.spacing(3)};
color: theme.color("foreground");
position: relative;

@include theme.breakpoint("3xl") {
position: absolute;
left: -#{theme.spacing(16)};
}

.logoWrapper {
width: theme.spacing(9);
height: theme.spacing(9);
width: theme.spacing(8);
height: theme.spacing(8);
position: relative;

.logo {
@@ -52,33 +59,34 @@

.rightMenu {
flex: none;
position: relative;
gap: theme.spacing(2);

@include theme.row;

margin-right: -#{theme.button-padding("sm", false)};

.themeSwitch {
margin-left: theme.spacing(1);
}
}

@media screen and (min-width: theme.$max-width + (2 * (theme.spacing(9) + theme.spacing(8) + theme.spacing(7)))) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably leave this in place -- this calculation basically keeps the header to max-width unless the screen is wide enough to push the logo & theme switcher to the gutter. As soon as the screen is large enough, the logo & theme switcher push.

Doing it this way means you don't need to fiddle with setting breakpoints to accommodate this functionality specifically and we can set breakpoints to make sense for the rest of the application -- as long as the max-width is set correctly, the header will properly snap whenever the gutter size is large enough.

.leftMenu {
margin-left: -#{theme.spacing(9) + theme.spacing(7)};
display: none;

.logoLink {
margin-right: -#{theme.spacing(2)};
@include theme.breakpoint("md") {
position: relative;
display: block;
}
}

.rightMenu {
margin-right: -#{theme.spacing(9) + theme.spacing(7)};

.themeSwitch {
margin-left: theme.spacing(5);
@include theme.breakpoint("3xl") {
display: block;
position: absolute;
right: -#{theme.spacing(16)};
}
}
}
}

.desktopNavigation,
.desktopSupport,
.desktopDocs {
display: none;
@include theme.breakpoint("md") {
display: block;
}
}
}
38 changes: 24 additions & 14 deletions apps/insights/src/components/Root/header.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { Lifebuoy } from "@phosphor-icons/react/dist/ssr/Lifebuoy";
import { Button } from "@pythnetwork/component-library/Button";
import { Link } from "@pythnetwork/component-library/Link";
@@ -10,6 +12,7 @@ import { SearchButton } from "./search-button";
import { SupportDrawer } from "./support-drawer";
import { MainNavTabs } from "./tabs";
import { ThemeSwitch } from "./theme-switch";
import { MobileMenu } from "../MobileMenu/mobile-menu";

export const Header = ({ className, ...props }: ComponentProps<"header">) => (
<header className={clsx(styles.header, className)} {...props}>
@@ -22,24 +25,31 @@ export const Header = ({ className, ...props }: ComponentProps<"header">) => (
<div className={styles.logoLabel}>Pyth Homepage</div>
</Link>
<div className={styles.appName}>Insights</div>
<MainNavTabs />
<div className={styles.desktopNavigation}>
<MainNavTabs />
</div>
</div>
<div className={styles.rightMenu}>
<SupportDrawer>
<Button beforeIcon={Lifebuoy} variant="ghost" size="sm" rounded>
Support
</Button>
</SupportDrawer>
<div className={styles.desktopSupport}>
<SupportDrawer>
<Button beforeIcon={Lifebuoy} variant="ghost" size="sm" rounded>
Support
</Button>
</SupportDrawer>
</div>
<SearchButton />
<Button
href="https://docs.pyth.network"
size="sm"
rounded
target="_blank"
>
Dev Docs
</Button>
<div className={styles.desktopDocs}>
<Button
href="https://docs.pyth.network"
size="sm"
rounded
target="_blank"
>
Dev Docs
</Button>
</div>
<ThemeSwitch className={styles.themeSwitch ?? ""} />
<MobileMenu />
</div>
</div>
</header>
2 changes: 2 additions & 0 deletions apps/insights/src/components/Root/index.tsx
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import { toHex } from "../../hex";
import { getPublishers } from "../../services/clickhouse";
import { Cluster, getData } from "../../services/pyth";
import { LivePricesProvider } from "../LivePrices";
import { MobileNavigation } from "../MobileNavigation/mobile-navigation";
import { PriceFeedIcon } from "../PriceFeedIcon";
import { PublisherIcon } from "../PublisherIcon";

@@ -65,6 +66,7 @@ export const Root = async ({ children }: Props) => {
<TabPanel>{children}</TabPanel>
</main>
<Footer />
<MobileNavigation />
</TabRoot>
</SearchDialogProvider>
</BaseRoot>
2 changes: 1 addition & 1 deletion apps/insights/src/components/Root/search-button.tsx
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ const SearchText = () => {
const SearchTextImpl = () => {
// This component can only ever render in the client so we can safely ignore
// this eslint rule.
// eslint-disable-next-line n/no-unsupported-features/node-builtins

const isMac = useMemo(() => navigator.userAgent.includes("Mac"), []);
return isMac ? "⌘ K" : "Ctrl K";
};
15 changes: 10 additions & 5 deletions apps/insights/src/components/Root/support-drawer.module.scss
Original file line number Diff line number Diff line change
@@ -34,30 +34,35 @@
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;
gap: theme.spacing(1) theme.spacing(4);
align-items: start;
width: 100%;

.icon {
font-size: theme.spacing(8);
font-size: theme.spacing(6);
color: theme.color("states", "data", "normal");
grid-row: span 2 / span 2;
display: grid;
place-content: center;
}

.header {
@include theme.text("sm", "medium");
@include theme.h6;

line-height: 1.5;
color: theme.color("heading");
}

.description {
@include theme.text("xs", "normal");
@include theme.text("sm", "normal");

line-height: 1.5;
color: theme.color("muted");
grid-column: 2;
grid-row: 2;
word-wrap: break-word;
overflow: hidden;
max-width: 100%;
}

.caret {
60 changes: 60 additions & 0 deletions apps/insights/src/components/Stats/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@use "@pythnetwork/component-library/theme";

.statsContainer {
.statScrollWrapper {
width: 100dvw; // Extend the width beyond the root padding
margin-left: -#{theme.spacing(4)};
margin-right: -#{theme.spacing(4)};
white-space: nowrap;
overflow: auto hidden;
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
scroll-padding: theme.spacing(4);
left: 0;
right: 0;

// Optional: Hide scrollbars
scrollbar-width: none;

&::-webkit-scrollbar {
display: none;
}

@include theme.breakpoint("md") {
width: 100%;
position: relative;
display: flex;
flex-flow: row nowrap;
overflow: visible;
margin: 0;
}

.statsItemsContainer {
display: flex;
flex-flow: row nowrap;
width: max-content;
gap: theme.spacing(3);
padding: 0 theme.spacing(4);

> * {
width: 280px;
scroll-snap-align: start;
}

@include theme.breakpoint("md") {
gap: theme.spacing(6);
width: 100%;
padding: 0;

> * {
display: flex;
width: 100%;
flex: 1 0 0;
min-width: 0;
max-width: 100%;
min-height: 0;
}
}
}
}
}
18 changes: 18 additions & 0 deletions apps/insights/src/components/Stats/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import clsx from "clsx";
import type { ComponentProps } from "react";

import styles from "./index.module.scss";

type StatsProps = {
children: React.ReactNode;
} & ComponentProps<"div">;

export const Stats = ({ children, ...props }: StatsProps) => {
return (
<div className={clsx(styles.statsContainer, props.className)} {...props}>
<div className={styles.statScrollWrapper}>
<div className={styles.statsItemsContainer}>{children}</div>
</div>
</div>
);
};
27 changes: 27 additions & 0 deletions apps/insights/src/components/StructuredList/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use "@pythnetwork/component-library/theme";

.structuredList {
display: flex;
flex-flow: column nowrap;
gap: theme.spacing(4);

.structuredListItem {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: theme.spacing(2);
align-items: center;
justify-content: space-between;

// font-size: theme.font-size("sm");
.structuredListItemLabel {
color: theme.color("muted");
}

.structuredListItemValue {
text-align: right;
place-items: end;
font-weight: theme.font-weight("medium");
color: theme.color("heading");
}
}
}
41 changes: 41 additions & 0 deletions apps/insights/src/components/StructuredList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import clsx from "clsx";
import type { ComponentProps } from "react";

import styles from "./index.module.scss";

type StructuredListProps = {
items: StructureListItemProps[];
} & ComponentProps<"div">;

type StructureListItemProps = {
label: React.ReactNode;
value: React.ReactNode;
} & ComponentProps<"div">;

export const StructuredList = ({ items, ...props }: StructuredListProps) => {
return (
items.length > 0 && (
<div className={clsx(styles.structuredList, props.className)} {...props}>
{items.map((item, index) => (
<StructuredListItem key={index} {...item} />
))}
</div>
)
);
};

export const StructuredListItem = ({
label,
value,
...props
}: StructureListItemProps) => {
return (
<div
className={clsx(styles.structuredListItem, props.className)}
{...props}
>
<div className={styles.structuredListItemLabel}>{label}</div>
<div className={styles.structuredListItemValue}>{value}</div>
</div>
);
};
5 changes: 4 additions & 1 deletion packages/component-library/src/Badge/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
@use "../theme";

.badge {
display: inline flow-root;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: theme.border-radius("3xl");
transition-property: color, background-color, border-color;
transition-duration: 100ms;
transition-timing-function: linear;
white-space: nowrap;
border-width: 1px;
border-style: solid;
flex-shrink: 1;

&[data-size="xs"] {
line-height: theme.spacing(4);
5 changes: 4 additions & 1 deletion packages/component-library/src/Button/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use "../theme";

.button {
display: inline flow-root;
display: flex;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very intentional not to use flex here -- the reason is that if a button is used in an inline context, using flex will mean that the baseline of the button text does not get aligned properly against the baseline of the text outside the button in the rest of the inline context. Using inline flow-root (which is the modern equivalent of inline-block) is a bit more challenging to structure the button properly but it makes it behave identically to how a default-styled button would in an inline context.

This may be overthinking things as we don't have any buttons in an inline context in the insights hub yet, but it's a very painful thing to undo down the line if the use case comes up.

cursor: pointer;
white-space: nowrap;
font-weight: theme.font-weight("medium");
@@ -12,6 +12,9 @@
text-decoration: none;
outline-offset: 0;
outline: theme.spacing(1) solid transparent;
text-align: center;
justify-content: center;
align-items: center;

.iconWrapper {
display: inline-grid;
50 changes: 29 additions & 21 deletions packages/component-library/src/Card/index.module.scss
Original file line number Diff line number Diff line change
@@ -33,35 +33,43 @@
}

.header {
display: flex;
padding: theme.spacing(3) theme.spacing(4);
position: relative;

.title {
color: theme.color("heading");
display: inline-flex;
display: flex;
padding: theme.spacing(3);
gap: theme.spacing(3);
flex-direction: column;
justify-content: flex-start;

@include theme.breakpoint("lg") {
padding: 0 theme.spacing(2);
height: theme.spacing(12);
justify-content: space-between;
flex-flow: row nowrap;
gap: theme.spacing(3);
align-items: center;
}

@include theme.text("lg", "medium");

.icon {
font-size: theme.spacing(6);
height: theme.spacing(6);
color: theme.color("button", "primary", "background", "normal");
.toolbar {
&:empty {
display: none;
}
gap: theme.spacing(2);
display: flex;
}

.toolbar {
.action {
align-self: center;
grid-area: action;
// display: grid;
// place-items: center;
// height: 100%;
// padding: 0 theme.spacing(2);
position: absolute;
right: theme.spacing(3);
top: 0;
bottom: theme.spacing(0);
display: flex;
flex-flow: row nowrap;
gap: theme.spacing(2);
align-items: center;
top: theme.spacing(1.5);
right: theme.spacing(1.5);
@include theme.breakpoint("md") {
position: static;
padding: 0;
}
}
}

10 changes: 5 additions & 5 deletions packages/component-library/src/Card/index.tsx
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ type OwnProps = {
icon?: ReactNode | undefined;
title?: ReactNode | undefined;
toolbar?: ReactNode | ReactNode[] | undefined;
action?: ReactNode | undefined;
footer?: ReactNode | undefined;
nonInteractive?: boolean | undefined;
};
@@ -59,6 +60,7 @@ const cardProps = <T extends ElementType>({
title,
toolbar,
footer,
action,
...props
}: Props<T>) => ({
...props,
@@ -69,11 +71,9 @@ const cardProps = <T extends ElementType>({
<div className={styles.cardHoverBackground} />
{(Boolean(icon) || Boolean(title) || Boolean(toolbar)) && (
<div className={styles.header}>
<h2 className={styles.title}>
{icon && <div className={styles.icon}>{icon}</div>}
{title}
</h2>
<div className={styles.toolbar}>{toolbar}</div>
<div className={styles.title}>{title}</div>
{toolbar && <div className={styles.toolbar}>{toolbar}</div>}
{action && <div className={styles.action}>{action}</div>}
</div>
)}
{children}
55 changes: 37 additions & 18 deletions packages/component-library/src/Drawer/index.module.scss
Original file line number Diff line number Diff line change
@@ -7,31 +7,46 @@
z-index: 1;

.drawer {
position: fixed;
top: theme.spacing(4);
bottom: theme.spacing(4);
right: theme.spacing(4);
width: 60%;
max-width: theme.spacing(160);
outline: none;
width: 100%;
border-radius: 0;
border: none;
padding-bottom: 0;
background: theme.color("background", "primary");
border: 1px solid theme.color("border");
border-radius: theme.border-radius("3xl");
display: flex;
flex-flow: column nowrap;
overflow-y: hidden;
padding-bottom: theme.border-radius("3xl");

@include theme.breakpoint("sm") {
position: fixed;
top: theme.spacing(4);
bottom: theme.spacing(4);
right: theme.spacing(4);
width: 80%;
max-width: theme.spacing(160);
outline: none;
border: 1px solid theme.color("background", "secondary");
border-radius: theme.border-radius("3xl");
display: flex;
flex-flow: column nowrap;
overflow-y: hidden;
}

@include theme.breakpoint("lg") {
width: 60%;
}

.heading {
padding: theme.spacing(4);
padding-left: theme.spacing(6);
padding: theme.spacing(3);
padding-left: theme.spacing(4);
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
color: theme.color("heading");
flex: none;
border-bottom: 1px solid theme.color("border");
border-bottom: 1px solid theme.color("background", "secondary");

@include theme.breakpoint("sm") {
padding: theme.spacing(4);
padding-left: theme.spacing(6);
}

.title {
@include theme.h4;
@@ -51,8 +66,12 @@

.body {
flex: 1;
overflow-y: auto;
padding: theme.spacing(6);
overflow: hidden auto;
padding: theme.spacing(4);

@include theme.breakpoint("sm") {
padding: theme.spacing(6);
}
}

&[data-fill] {
20 changes: 8 additions & 12 deletions packages/component-library/src/MainNavTabs/index.module.scss
Original file line number Diff line number Diff line change
@@ -2,12 +2,18 @@

.mainNavTabs {
gap: theme.spacing(2);
z-index: 1;

@include theme.row;

.tab {
width: 100%;
position: relative;
outline: none;
z-index: 1;
@include theme.breakpoint("md") {
width: auto;
}

.bubble {
position: absolute;
@@ -39,21 +45,11 @@
pointer-events: auto;

&[data-hovered] .bubble {
background-color: theme.color(
"button",
"solid",
"background",
"hover"
);
background-color: theme.color("button", "solid", "background", "hover");
}

&[data-pressed] .bubble {
background-color: theme.color(
"button",
"solid",
"background",
"active"
);
background-color: theme.color("button", "solid", "background", "active");
}
}
}
12 changes: 10 additions & 2 deletions packages/component-library/src/Paginator/index.module.scss
Original file line number Diff line number Diff line change
@@ -3,14 +3,22 @@
.paginator {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
justify-content: center;

@include theme.breakpoint("lg") {
justify-content: space-between;
}

.pageSizeSelect {
display: flex;
display: none;
flex-flow: row nowrap;
align-items: center;
gap: theme.spacing(1);

@include theme.breakpoint("lg") {
display: flex;
}

.loadingIndicator {
width: theme.spacing(4);
height: theme.spacing(4);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@use "../theme";

.singleToggleGroup {
gap: theme.spacing(2);
gap: theme.spacing(1);

@include theme.row;

21 changes: 8 additions & 13 deletions packages/component-library/src/Table/index.module.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
@use "../theme";

.tableContainer {
display: none;
background-color: theme.color("background", "primary");
position: relative;

@include theme.breakpoint("md") {
display: block;
}

.loaderWrapper {
position: absolute;
top: theme.spacing(10);
@@ -83,7 +88,7 @@

.cell {
position: relative;
border-bottom: 1px solid theme.color("border");
border-bottom: 1px solid theme.color("background", "secondary");
font-weight: theme.font-weight("medium");

.divider {
@@ -164,21 +169,11 @@
}

&[data-hovered] .cell {
background-color: theme.color(
"button",
"outline",
"background",
"hover"
);
background-color: theme.color("button", "outline", "background", "hover");
}

&[data-pressed] .cell {
background-color: theme.color(
"button",
"outline",
"background",
"active"
);
background-color: theme.color("button", "outline", "background", "active");
}
}
}
70 changes: 68 additions & 2 deletions packages/component-library/src/theme.scss
Original file line number Diff line number Diff line change
@@ -80,6 +80,50 @@ $border-radius: (
@return map-get-strict($border-radius, $radius);
}

$breakpoints: (
"xs": 480px,
"sm": 720px,
"md": 960px,
"lg": 1024px,
"xl": 1280px,
"2xl": 1536px,
"3xl": 1720px,
);

@function breakpoint-old($breakpoint) {
@return map-get-strict($breakpoints, $breakpoint);
}

@mixin breakpoint($point) {
@media (min-width: map-get-strict($breakpoints, $point)) {
@content;
}
}

@mixin mobile() {
@media screen and (max-width: breakpoint-old("md")) {
@content;

background: cyan;
}
}

@mixin tablet() {
@media screen and (min-width: breakpoint-old("md")) {
@content;

background: orange;
}
}

@mixin desktop() {
@media screen and (min-width: breakpoint-old("3xl")) {
@content;

background: pink;
}
}

$color-pallette: (
"black": #000,
"white": #fff,
@@ -433,7 +477,7 @@ $color: (
"highlight":
light-dark(pallette-color("violet", 600), pallette-color("violet", 500)),
"muted":
light-dark(pallette-color("stone", 700), pallette-color("steel", 300)),
light-dark(pallette-color("stone", 500), pallette-color("steel", 400)),
"border":
light-dark(pallette-color("stone", 300), pallette-color("steel", 600)),
"selection": (
@@ -724,8 +768,12 @@ $max-width: 96rem;
@mixin max-width {
margin: 0 auto;
max-width: $max-width;
padding: 0 spacing(6);
padding: 0 spacing(4);
box-sizing: content-box;

@include breakpoint("xl") {
padding: 0 spacing(6);
}
}

@mixin row {
@@ -784,6 +832,24 @@ $elevations: (
margin: 0;
}

@mixin h5 {
font-size: font-size("lg");
font-style: normal;
font-weight: font-weight("medium");
line-height: 125%;
letter-spacing: letter-spacing("tight");
margin: 0;
}

@mixin h6 {
font-size: font-size("base");
font-style: normal;
font-weight: font-weight("medium");
line-height: 125%;
letter-spacing: letter-spacing("tight");
margin: 0;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that I really didn't like these mixins much and I haven't been using them much; instead I started using the text mixin a few lines below, e.g. this would be:

.someClass {
  @include theme.text("base", "medium");
  line-height: 125%;
  letter-spacing: theme.letter-spacing("tight");
}

I suppose the line heights & letter spacing could get redundant, but I found there to be so much variations in the different combinations of text properties that it didn't seem to make a lot of sense to try to abstract them all.

I'm open to other ideas here though, this is one place I don't feel great about the theming system right now...


@mixin text($size: "base", $weight: "normal") {
font-size: font-size($size);
font-weight: font-weight($weight);