Skip to content

Commit cc771fb

Browse files
committed
feat(insights): add search dialog
1 parent bb94a6e commit cc771fb

File tree

14 files changed

+774
-97
lines changed

14 files changed

+774
-97
lines changed

apps/insights/src/components/PublisherTag/index.module.scss

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
.publisherTag {
44
display: flex;
55
flex-flow: row nowrap;
6-
gap: theme.spacing(4);
6+
gap: theme.spacing(3);
77
align-items: center;
8+
width: 100%;
9+
10+
.icon,
11+
.undisclosedIconWrapper {
12+
width: theme.spacing(10);
13+
height: theme.spacing(10);
14+
}
815

916
.icon {
10-
width: theme.spacing(9);
11-
height: theme.spacing(9);
17+
flex: none;
1218
display: grid;
1319
place-content: center;
1420

@@ -20,16 +26,22 @@
2026
}
2127
}
2228

29+
.name {
30+
color: theme.color("heading");
31+
font-weight: theme.font-weight("medium");
32+
}
33+
34+
.publisherKey,
35+
.icon {
36+
color: theme.color("foreground");
37+
}
38+
2339
.nameAndKey {
2440
display: flex;
2541
flex-flow: column nowrap;
2642
gap: theme.spacing(1);
2743
align-items: flex-start;
2844

29-
.name {
30-
color: theme.color("heading");
31-
}
32-
3345
.key {
3446
margin-bottom: -#{theme.spacing(2)};
3547
}
@@ -55,4 +67,12 @@
5567
border-radius: theme.border-radius("full");
5668
}
5769
}
70+
71+
&[data-compact] {
72+
.icon,
73+
.undisclosedIconWrapper {
74+
width: theme.spacing(6);
75+
height: theme.spacing(6);
76+
}
77+
}
5878
}
Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,35 @@
11
import { Broadcast } from "@phosphor-icons/react/dist/ssr/Broadcast";
22
import { Skeleton } from "@pythnetwork/component-library/Skeleton";
33
import clsx from "clsx";
4-
import { type ComponentProps, type ReactNode } from "react";
4+
import type { ComponentProps, ReactNode } from "react";
55

66
import styles from "./index.module.scss";
77
import { PublisherKey } from "../PublisherKey";
88

9-
type Props =
10-
| { isLoading: true }
11-
| ({
12-
isLoading?: false;
13-
publisherKey: string;
14-
} & (
15-
| { name: string; icon: ReactNode }
16-
| { name?: undefined; icon?: undefined }
17-
));
9+
type Props = ComponentProps<"div"> & { compact?: boolean | undefined } & (
10+
| { isLoading: true }
11+
| ({
12+
isLoading?: false;
13+
publisherKey: string;
14+
} & (
15+
| { name: string; icon: ReactNode }
16+
| { name?: undefined; icon?: undefined }
17+
))
18+
);
1819

19-
export const PublisherTag = (props: Props) => (
20+
export const PublisherTag = ({ className, ...props }: Props) => (
2021
<div
2122
data-loading={props.isLoading ? "" : undefined}
22-
className={styles.publisherTag}
23+
data-compact={props.compact ? "" : undefined}
24+
className={clsx(styles.publisherTag, className)}
25+
{...props}
2326
>
2427
{props.isLoading ? (
2528
<Skeleton fill className={styles.icon} />
2629
) : (
2730
<div className={styles.icon}>{props.icon ?? <UndisclosedIcon />}</div>
2831
)}
29-
{props.isLoading ? (
30-
<Skeleton width={30} />
31-
) : (
32-
<>
33-
{props.name ? (
34-
<div className={styles.nameAndKey}>
35-
<div className={styles.name}>{props.name}</div>
36-
<PublisherKey
37-
className={styles.key ?? ""}
38-
publisherKey={props.publisherKey}
39-
size="xs"
40-
/>
41-
</div>
42-
) : (
43-
<PublisherKey publisherKey={props.publisherKey} size="sm" />
44-
)}
45-
</>
46-
)}
32+
<Contents {...props} />
4733
</div>
4834
);
4935

@@ -52,3 +38,28 @@ const UndisclosedIcon = ({ className, ...props }: ComponentProps<"div">) => (
5238
<Broadcast className={styles.undisclosedIcon} />
5339
</div>
5440
);
41+
42+
const Contents = (props: Props) => {
43+
if (props.isLoading) {
44+
return <Skeleton width={30} />;
45+
} else if (props.compact) {
46+
return props.name ? (
47+
<div className={styles.name}>{props.name}</div>
48+
) : (
49+
<PublisherKey publisherKey={props.publisherKey} size="xs" />
50+
);
51+
} else if (props.name) {
52+
return (
53+
<div className={styles.nameAndKey}>
54+
<div className={styles.name}>{props.name}</div>
55+
<PublisherKey
56+
className={styles.key ?? ""}
57+
publisherKey={props.publisherKey}
58+
size="xs"
59+
/>
60+
</div>
61+
);
62+
} else {
63+
return <PublisherKey publisherKey={props.publisherKey} size="sm" />;
64+
}
65+
};
Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,69 @@
1+
import { lookup as lookupPublisher } from "@pythnetwork/known-publishers";
12
import { Root as BaseRoot } from "@pythnetwork/next-root";
23
import { NuqsAdapter } from "nuqs/adapters/next/app";
34
import type { ReactNode } from "react";
5+
import { createElement } from "react";
46

57
import { Footer } from "./footer";
68
import { Header } from "./header";
79
// import { MobileMenu } from "./mobile-menu";
810
import styles from "./index.module.scss";
11+
import { SearchDialogProvider } from "./search-dialog";
912
import { TabRoot, TabPanel } from "./tabs";
1013
import {
1114
IS_PRODUCTION_SERVER,
1215
GOOGLE_ANALYTICS_ID,
1316
AMPLITUDE_API_KEY,
1417
} from "../../config/server";
18+
import { toHex } from "../../hex";
19+
import { getPublishers } from "../../services/clickhouse";
20+
import { getData } from "../../services/pyth";
1521
import { LivePricesProvider } from "../LivePrices";
22+
import { PriceFeedIcon } from "../PriceFeedIcon";
1623

1724
type Props = {
1825
children: ReactNode;
1926
};
2027

21-
export const Root = ({ children }: Props) => (
22-
<BaseRoot
23-
amplitudeApiKey={AMPLITUDE_API_KEY}
24-
googleAnalyticsId={GOOGLE_ANALYTICS_ID}
25-
enableAccessibilityReporting={!IS_PRODUCTION_SERVER}
26-
providers={[NuqsAdapter, LivePricesProvider]}
27-
className={styles.root}
28-
>
29-
<TabRoot className={styles.tabRoot ?? ""}>
30-
<Header className={styles.header} />
31-
<main className={styles.main}>
32-
<TabPanel>{children}</TabPanel>
33-
</main>
34-
<Footer />
35-
</TabRoot>
36-
</BaseRoot>
37-
);
28+
export const Root = async ({ children }: Props) => {
29+
const [data, publishers] = await Promise.all([getData(), getPublishers()]);
30+
31+
return (
32+
<BaseRoot
33+
amplitudeApiKey={AMPLITUDE_API_KEY}
34+
googleAnalyticsId={GOOGLE_ANALYTICS_ID}
35+
enableAccessibilityReporting={!IS_PRODUCTION_SERVER}
36+
providers={[NuqsAdapter, LivePricesProvider]}
37+
className={styles.root}
38+
>
39+
<SearchDialogProvider
40+
feeds={data.map((feed) => ({
41+
id: feed.symbol,
42+
key: toHex(feed.product.price_account),
43+
displaySymbol: feed.product.display_symbol,
44+
icon: <PriceFeedIcon symbol={feed.symbol} />,
45+
assetClass: feed.product.asset_type,
46+
}))}
47+
publishers={publishers.map((publisher) => {
48+
const knownPublisher = lookupPublisher(publisher.key);
49+
return {
50+
id: publisher.key,
51+
medianScore: publisher.medianScore,
52+
...(knownPublisher && {
53+
name: knownPublisher.name,
54+
icon: createElement(knownPublisher.icon.color),
55+
}),
56+
};
57+
})}
58+
>
59+
<TabRoot className={styles.tabRoot ?? ""}>
60+
<Header className={styles.header} />
61+
<main className={styles.main}>
62+
<TabPanel>{children}</TabPanel>
63+
</main>
64+
<Footer />
65+
</TabRoot>
66+
</SearchDialogProvider>
67+
</BaseRoot>
68+
);
69+
};

apps/insights/src/components/Root/search-button.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,22 @@ import { Skeleton } from "@pythnetwork/component-library/Skeleton";
66
import { useMemo } from "react";
77
import { useIsSSR } from "react-aria";
88

9-
export const SearchButton = () => (
10-
<Button beforeIcon={MagnifyingGlass} variant="outline" size="sm" rounded>
11-
<SearchText />
12-
</Button>
13-
);
9+
import { useToggleSearchDialog } from "./search-dialog";
10+
11+
export const SearchButton = () => {
12+
const toggleSearchDialog = useToggleSearchDialog();
13+
return (
14+
<Button
15+
onPress={toggleSearchDialog}
16+
beforeIcon={MagnifyingGlass}
17+
variant="outline"
18+
size="sm"
19+
rounded
20+
>
21+
<SearchText />
22+
</Button>
23+
);
24+
};
1425

1526
const SearchText = () => {
1627
const isSSR = useIsSSR();
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.modalOverlay {
4+
position: fixed;
5+
inset: 0;
6+
background: rgba(from black r g b / 30%);
7+
z-index: 1;
8+
9+
.searchMenu {
10+
position: relative;
11+
top: theme.spacing(32);
12+
margin: 0 auto;
13+
outline: none;
14+
background: theme.color("background", "secondary");
15+
border-radius: theme.border-radius("2xl");
16+
padding: theme.spacing(1);
17+
max-height: theme.spacing(120);
18+
display: flex;
19+
flex-flow: column nowrap;
20+
flex-grow: 1;
21+
gap: theme.spacing(1);
22+
width: fit-content;
23+
24+
.searchBar {
25+
flex: none;
26+
display: flex;
27+
flex-flow: row nowrap;
28+
gap: theme.spacing(2);
29+
align-items: center;
30+
padding: theme.spacing(1);
31+
32+
.closeButton {
33+
margin-left: theme.spacing(8);
34+
}
35+
}
36+
37+
.body {
38+
background: theme.color("background", "primary");
39+
border-radius: theme.border-radius("xl");
40+
flex-grow: 1;
41+
overflow: hidden;
42+
display: flex;
43+
44+
.listbox {
45+
outline: none;
46+
overflow: auto;
47+
flex-grow: 1;
48+
49+
.item {
50+
padding: theme.spacing(3) theme.spacing(4);
51+
display: flex;
52+
flex-flow: row nowrap;
53+
align-items: center;
54+
width: 100%;
55+
cursor: pointer;
56+
transition: background-color 100ms linear;
57+
outline: none;
58+
text-decoration: none;
59+
border-top: 1px solid theme.color("background", "secondary");
60+
61+
&[data-is-first] {
62+
border-top: none;
63+
}
64+
65+
& > *:last-child {
66+
flex-shrink: 0;
67+
}
68+
69+
&[data-focused] {
70+
background-color: theme.color(
71+
"button",
72+
"outline",
73+
"background",
74+
"hover"
75+
);
76+
}
77+
78+
&[data-pressed] {
79+
background-color: theme.color(
80+
"button",
81+
"outline",
82+
"background",
83+
"active"
84+
);
85+
}
86+
87+
.itemType {
88+
width: theme.spacing(21);
89+
flex-shrink: 0;
90+
margin-right: theme.spacing(6);
91+
}
92+
93+
.itemTag {
94+
flex-grow: 1;
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)