added announcement banner
This commit is contained in:
195
packages/website/ts/components/annoucement_banner.tsx
Normal file
195
packages/website/ts/components/annoucement_banner.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { colors } from 'ts/style/colors';
|
||||
|
||||
import { Button } from 'ts/components/button';
|
||||
import { ThemeInterface } from 'ts/components/siteWrap';
|
||||
import { Paragraph } from 'ts/components/text';
|
||||
|
||||
import { Column, Section, SectionProps } from 'ts/components/newLayout';
|
||||
|
||||
interface Props {
|
||||
heading?: string;
|
||||
subline?: string;
|
||||
onDismiss?: () => void;
|
||||
mainCta?: CTAButton;
|
||||
secondaryCta?: CTAButton;
|
||||
theme?: ThemeInterface;
|
||||
dismissed?: boolean;
|
||||
}
|
||||
|
||||
interface CTAButton {
|
||||
text: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
shouldOpenInNewTab?: boolean;
|
||||
}
|
||||
|
||||
interface BorderProps {
|
||||
isBottom?: boolean;
|
||||
}
|
||||
|
||||
export const ANNOUNCEMENT_BANNER_HEIGHT = '12rem';
|
||||
|
||||
export const AnnouncementBanner: React.StatelessComponent<Props> = (props: Props) => {
|
||||
const { heading, subline, mainCta, secondaryCta } = props;
|
||||
return (
|
||||
<CustomSection bgColor={colors.brandDark} paddingMobile="120px 0" dismissed={props.dismissed}>
|
||||
<Border />
|
||||
<Border isBottom={true} />
|
||||
<BannerContentWrapper>
|
||||
<Column maxWidth="755px">
|
||||
<CustomHeading>{heading}</CustomHeading>
|
||||
|
||||
{subline && (
|
||||
<Paragraph color={colors.white} isMuted={0.5} isNoMargin={true}>
|
||||
{subline}
|
||||
</Paragraph>
|
||||
)}
|
||||
</Column>
|
||||
<ColumnCta>
|
||||
<ButtonWrap>
|
||||
<Button
|
||||
color={'rgba(255,255,255,0.6)'}
|
||||
isTransparent={true}
|
||||
isNoBorder={true}
|
||||
borderColor={'rgba(0,0,0,0)'}
|
||||
onClick={props.onDismiss}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
{mainCta && (
|
||||
<Button
|
||||
color={colors.white}
|
||||
isTransparent={false}
|
||||
href={mainCta.href}
|
||||
onClick={mainCta.onClick}
|
||||
target={mainCta.shouldOpenInNewTab ? '_blank' : ''}
|
||||
>
|
||||
{mainCta.text}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{secondaryCta && (
|
||||
<Button
|
||||
color={colors.white}
|
||||
href={secondaryCta.href}
|
||||
onClick={secondaryCta.onClick}
|
||||
target={secondaryCta.shouldOpenInNewTab ? '_blank' : ''}
|
||||
isTransparent={true}
|
||||
>
|
||||
{secondaryCta.text}
|
||||
</Button>
|
||||
)}
|
||||
</ButtonWrap>
|
||||
</ColumnCta>
|
||||
</BannerContentWrapper>
|
||||
</CustomSection>
|
||||
);
|
||||
};
|
||||
|
||||
interface CustomSectionProps extends SectionProps {
|
||||
dismissed: boolean;
|
||||
}
|
||||
|
||||
const BannerContentWrapper = styled.div`
|
||||
max-width: 1200px;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
padding: 0 20px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const CustomSection = styled(Section)<CustomSectionProps>`
|
||||
color: ${colors.white};
|
||||
position: fixed;
|
||||
height: ${ANNOUNCEMENT_BANNER_HEIGHT};
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1201;
|
||||
padding: 0 1px;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
width: inherit;
|
||||
transition: 300ms transform ease-in-out;
|
||||
transform: translateY(-${props => props.dismissed ? '100%' : '0'});
|
||||
font-family: Formular, sans-serif;
|
||||
@media (max-width: 900px) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ColumnCta = styled(Column)`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const CustomHeading = styled.h2`
|
||||
font-size: 28px;
|
||||
line-height: normal;
|
||||
font-weight: 400;
|
||||
margin-bottom: 10px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonWrap = styled.div`
|
||||
display: inline-block;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
* + * {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
a,
|
||||
button {
|
||||
display: block;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
* + * {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Note let's refactor this
|
||||
// is it absolutely necessary to have a stateless component
|
||||
// to pass props down into the styled icon?
|
||||
const Border = styled.div<BorderProps>`
|
||||
position: absolute;
|
||||
background-image: ${props =>
|
||||
props.isBottom ? 'url(/images/banner/bottomofcta.png);' : 'url(/images/banner/topofcta.png);'};
|
||||
background-position: ${props => (props.isBottom ? 'left top' : 'left bottom')};
|
||||
left: 0;
|
||||
width: calc(100% + 214px);
|
||||
height: 40px;
|
||||
top: ${props => !props.isBottom && 0};
|
||||
bottom: ${props => props.isBottom && 0};
|
||||
transform: translate(-112px);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: calc(100% + 82px);
|
||||
height: 40px;
|
||||
transform: translate(-41px);
|
||||
background-size: auto 80px;
|
||||
}
|
||||
`;
|
@@ -72,8 +72,8 @@ const ButtonBase = styled.button<ButtonInterface>`
|
||||
background-color: ${props => (props.isTransparent || props.isWithArrow) && 'transparent'};
|
||||
border-color: ${props => props.isTransparent && !props.isWithArrow && props.borderColor};
|
||||
color: ${props => (props.isAccentColor ? props.theme.linkColor : props.color || props.theme.textColor)};
|
||||
padding: ${props => !props.isNoPadding && !props.isWithArrow && '18px 30px'};
|
||||
padding: ${props => !!props.padding && props.padding};
|
||||
padding: ${props =>
|
||||
!props.isNoPadding && !props.isWithArrow && ((!!props.padding && props.padding) || '18px 30px')};
|
||||
white-space: ${props => props.isWithArrow && 'nowrap'};
|
||||
text-align: center;
|
||||
font-size: ${props => (props.isWithArrow ? '20px' : '18px')};
|
||||
|
@@ -79,7 +79,12 @@ class HeaderBase extends React.Component<HeaderProps> {
|
||||
const { isNavToggled, toggleMobileNav, theme } = this.props;
|
||||
|
||||
return (
|
||||
<Headroom onUnpin={this.onUnpin} downTolerance={4} upTolerance={10}>
|
||||
<Headroom
|
||||
onUnpin={this.onUnpin}
|
||||
downTolerance={4}
|
||||
upTolerance={10}
|
||||
wrapperStyle={{ position: 'relative', zIndex: 2 }}
|
||||
>
|
||||
<StyledHeader isNavToggled={isNavToggled}>
|
||||
<HeaderWrap>
|
||||
<Link to={WebsitePaths.Home}>
|
||||
@@ -94,7 +99,7 @@ class HeaderBase extends React.Component<HeaderProps> {
|
||||
|
||||
<MediaQuery minWidth={990}>
|
||||
<TradeButton bgColor={theme.headerButtonBg} color="#ffffff" href="/explore">
|
||||
Explore 0x
|
||||
Trade on 0x
|
||||
</TradeButton>
|
||||
</MediaQuery>
|
||||
|
||||
|
@@ -93,8 +93,7 @@ const SectionBase = styled.section<SectionProps>`
|
||||
width: ${props => !props.isFullWidth && 'calc(100% - 60px)'};
|
||||
max-width: 1500px;
|
||||
margin: 0 auto;
|
||||
padding: ${props => props.isPadded && '120px 0'};
|
||||
padding: ${props => !!props.padding && props.padding};
|
||||
padding: ${props => (!!props.padding && props.padding) || (props.isPadded && '120px 0')};
|
||||
background-color: ${props => props.theme[`${props.bgColor}BgColor`] || props.bgColor};
|
||||
position: relative;
|
||||
overflow: ${props => !props.isFullWidth && 'hidden'};
|
||||
|
@@ -63,7 +63,7 @@ const DEFAULT_MENU_THEME: MenuTheme = {
|
||||
|
||||
export const Menu: React.StatelessComponent<MenuProps> = (props: MenuProps) => {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ paddingTop: 25 }}>
|
||||
{_.map(props.menuItemEntries, entry => {
|
||||
const isSelected = entry.to === props.selectedPath;
|
||||
return (
|
||||
|
@@ -2,9 +2,10 @@ import { colors, Link } from '@0x/react-shared';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { ANNOUNCEMENT_BANNER_HEIGHT, AnnouncementBanner } from 'ts/components/annoucement_banner';
|
||||
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
|
||||
import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
|
||||
import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
|
||||
@@ -12,7 +13,6 @@ import { EthWrappers } from 'ts/components/eth_wrappers';
|
||||
import { FillOrder } from 'ts/components/fill_order';
|
||||
import { AssetPicker } from 'ts/components/generate_order/asset_picker';
|
||||
import { MetaTags } from 'ts/components/meta_tags';
|
||||
import { BackButton } from 'ts/components/portal/back_button';
|
||||
import { Loading } from 'ts/components/portal/loading';
|
||||
import { Menu, MenuTheme } from 'ts/components/portal/menu';
|
||||
import { Section } from 'ts/components/portal/section';
|
||||
@@ -88,6 +88,7 @@ interface PortalState {
|
||||
isLedgerDialogOpen: boolean;
|
||||
tokenManagementState: TokenManagementState;
|
||||
trackedTokenStateByAddress: TokenStateByAddress;
|
||||
dismissBanner: boolean;
|
||||
}
|
||||
|
||||
interface AccountManagementItem {
|
||||
@@ -133,6 +134,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
|
||||
tokenManagementState: TokenManagementState.None,
|
||||
isLedgerDialogOpen: false,
|
||||
dismissBanner: false,
|
||||
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
|
||||
};
|
||||
}
|
||||
@@ -233,6 +235,18 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
return (
|
||||
<Container>
|
||||
<MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} />
|
||||
<AnnouncementBanner
|
||||
dismissed={this.state.dismissBanner}
|
||||
onDismiss={this._dismissBanner.bind(this)}
|
||||
heading="Check out the new 0x Explore page"
|
||||
subline="Need more advanced functionality? Try our code sandbox."
|
||||
mainCta={{ text: 'Explore 0x', href: '/explore', shouldOpenInNewTab: true }}
|
||||
secondaryCta={{
|
||||
text: 'Code Sandbox',
|
||||
href: 'https://codesandbox.io/s/1qmjyp7p5j',
|
||||
shouldOpenInNewTab: true,
|
||||
}}
|
||||
/>
|
||||
<TopBar
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
@@ -248,18 +262,22 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
style={{
|
||||
backgroundColor: colors.lightestGrey,
|
||||
position: 'fixed',
|
||||
transition: '300ms top ease-in-out',
|
||||
top: this.state.dismissBanner ? '0' : ANNOUNCEMENT_BANNER_HEIGHT,
|
||||
zIndex: zIndex.topBar,
|
||||
}}
|
||||
maxWidth={LARGE_LAYOUT_MAX_WIDTH}
|
||||
/>
|
||||
<Container marginTop={TOP_BAR_HEIGHT} minHeight="100vh" backgroundColor={colors.lightestGrey}>
|
||||
<Container
|
||||
marginTop={`calc(${TOP_BAR_HEIGHT}px + ${
|
||||
this.state.dismissBanner ? '0px' : ANNOUNCEMENT_BANNER_HEIGHT
|
||||
})`}
|
||||
minHeight="100vh"
|
||||
backgroundColor={colors.lightestGrey}
|
||||
>
|
||||
<Switch>
|
||||
<Route path={`${WebsitePaths.Portal}/:route`} render={this._renderOtherRoutes.bind(this)} />
|
||||
<Route
|
||||
exact={true}
|
||||
path={`${WebsitePaths.Portal}/`}
|
||||
render={this._renderMainRoute.bind(this)}
|
||||
/>
|
||||
<Redirect from={WebsitePaths.Portal} to={`/portal/account`} />
|
||||
</Switch>
|
||||
<BlockchainErrDialog
|
||||
blockchain={this._blockchain}
|
||||
@@ -295,6 +313,11 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
private _dismissBanner(): void {
|
||||
this.setState({dismissBanner: true});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
private _renderMainRoute(): React.ReactNode {
|
||||
if (this._isSmallScreen()) {
|
||||
return <SmallLayout content={this._renderRelayerIndexSection()} />;
|
||||
@@ -317,12 +340,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
selectedIconColor: colors.yellow800,
|
||||
selectedBackgroundColor: 'transparent',
|
||||
};
|
||||
return (
|
||||
<Section
|
||||
header={<BackButton to={WebsitePaths.Portal} labelText="Back to Relayers" />}
|
||||
body={<Menu selectedPath={routeComponentProps.location.pathname} theme={menuTheme} />}
|
||||
/>
|
||||
);
|
||||
return <Section body={<Menu selectedPath={routeComponentProps.location.pathname} theme={menuTheme} />} />;
|
||||
}
|
||||
private _renderWallet(): React.ReactNode {
|
||||
const isMobile = utils.isMobileWidth(this.props.screenWidth);
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface SectionProps {
|
||||
header: React.ReactNode;
|
||||
header?: React.ReactNode;
|
||||
body: React.ReactNode;
|
||||
}
|
||||
export const Section = (props: SectionProps) => {
|
||||
return (
|
||||
<div className="flex flex-column">
|
||||
{props.header}
|
||||
{!!props.header && props.header}
|
||||
{props.body}
|
||||
</div>
|
||||
);
|
||||
|
@@ -7,22 +7,24 @@ interface InputProps {
|
||||
name?: string;
|
||||
width?: string;
|
||||
type?: string;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
onChange?: (e: React.ChangeEvent) => void;
|
||||
}
|
||||
|
||||
export const Input = React.forwardRef((props: InputProps, ref?: React.Ref<HTMLInputElement>) => {
|
||||
const { name, type, placeholder, defaultValue, onChange, width, className } = props;
|
||||
const { name, type, placeholder, defaultValue, onChange, width, className, value } = props;
|
||||
const componentType = type === 'textarea' ? 'textarea' : 'input';
|
||||
const inputProps = { name, type };
|
||||
|
||||
return (
|
||||
<InputWrapper className={className} width={width}>
|
||||
<Icon size={20} name="search" />
|
||||
<Icon size={18} name="search" />
|
||||
<StyledInput
|
||||
as={componentType}
|
||||
ref={ref}
|
||||
value={value}
|
||||
id={`input-${name}`}
|
||||
placeholder={placeholder}
|
||||
defaultValue={defaultValue}
|
||||
@@ -37,8 +39,8 @@ const StyledInput = styled.input`
|
||||
appearance: none;
|
||||
border: none;
|
||||
color: #000;
|
||||
font-size: 1.294117647rem;
|
||||
padding: 16px 15px 14px;
|
||||
font-size: 22px;
|
||||
padding: 10px 12px 9px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
min-height: ${props => props.type === 'textarea' && `120px`};
|
||||
|
Reference in New Issue
Block a user