-
Notifications
You must be signed in to change notification settings - Fork 240
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
base: main
Are you sure you want to change the base?
Changes from all commits
b952f18
b27e3e4
e938fae
8b37653
9ded02d
e61dae4
f4ca5a1
f4aa420
e5f22ef
c307350
9bf27b6
c6a6c6f
60b85d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DISABLE_ACCESSIBILITY_REPORTING=true | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
.env*.local | ||
.env*.development |
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"); | ||
} | ||
} |
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> | ||
) | ||
} |
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"); | ||
} |
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> | ||
); | ||
}; |
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> | ||
); |
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; | ||
} | ||
} | ||
} |
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> | ||
) | ||
} |
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"); | ||
} | ||
} |
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> | ||
); | ||
}; |
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); | ||
} | ||
} | ||
|
||
|
Large diffs are not rendered by default.
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 /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
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)))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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; | ||
} | ||
} | ||
} |
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; | ||
} | ||
} | ||
} | ||
} | ||
} |
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> | ||
); | ||
}; |
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"); | ||
} | ||
} | ||
} |
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> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
@use "../theme"; | ||
|
||
.button { | ||
display: inline flow-root; | ||
display: flex; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's very intentional not to use 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; | ||
|
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; | ||
|
||
|
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; | ||
} | ||
} | ||
partyparrotgreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$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); | ||
} | ||
partyparrotgreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
@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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 .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); | ||
|
There was a problem hiding this comment.
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.