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`};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="11.1228" y1="11.1849" x2="19.5844" y2="19.6464" stroke="black"/>
|
||||
<circle cx="6.53846" cy="6.53846" r="6.03846" stroke="black"/>
|
||||
<path d="M1 7.52174C1 11.1251 3.91842 14.0435 7.52174 14.0435C11.1251 14.0435 14.0435 11.1251 14.0435 7.52174C14.0435 3.9795 11.221 1.09597 7.7006 1.00436C7.63516 1.00436 7.57409 1 7.50865 1C3.91406 1.00436 1 3.92279 1 7.52174Z" stroke="#B1B1B1" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round"/>
|
||||
<path d="M12 12L19 19" stroke="#B1B1B1" stroke-width="2"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 474 B |
@@ -14,7 +14,10 @@ import { BY_NAME_ORDERINGS, EDITORIAL, FILTERS, ORDERINGS, PROJECTS } from 'ts/p
|
||||
import { ExploreSettingsDropdown } from 'ts/pages/explore/explore_dropdown';
|
||||
import { ExploreGrid } from 'ts/pages/explore/explore_grid';
|
||||
import { EXPLORE_STATE_DIALOGS, ExploreGridDialogTile } from 'ts/pages/explore/explore_grid_state_tile';
|
||||
import { Button as ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
|
||||
import { ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
|
||||
import { analytics } from 'ts/utils/analytics';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
|
||||
import {
|
||||
ExploreAnalyticAction,
|
||||
ExploreFilterMetadata,
|
||||
@@ -43,34 +46,36 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
isTilesLoading: false,
|
||||
isContactModalOpen: false,
|
||||
tiles: [] as ExploreTile[],
|
||||
tilesOrdering: ExploreTilesOrdering.None,
|
||||
tilesOrdering: ExploreTilesOrdering.Popular,
|
||||
isEditorialShown: true,
|
||||
filters: FILTERS,
|
||||
query: '',
|
||||
};
|
||||
|
||||
private readonly _debouncedChangeSearchResults: (query: string) => void;
|
||||
|
||||
constructor(props: ExploreProps) {
|
||||
super(props);
|
||||
this._debouncedChangeSearchResults = _.debounce(this._changeSearchResults, 300);
|
||||
}
|
||||
|
||||
public componentWillMount(): void {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._loadEntriesAsync().then(() => {
|
||||
this._setFilter('all');
|
||||
});
|
||||
// tslint:disable-next-line:async-suffix
|
||||
public async componentDidMount(): Promise<void> {
|
||||
await this._loadEntriesAsync();
|
||||
await this._setFilter('all');
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<SiteWrap theme="light">
|
||||
<DocumentTitle {...documentConstants.EXPLORE} />
|
||||
<ExploreHero onSearch={this._changeSearchResults} />
|
||||
<Section padding={'0 0 120px 0'} maxWidth={'1150px'}>
|
||||
<ExploreHero query={this.state.query} onSearch={this._setNewQuery} />
|
||||
<Section padding={'0 0 60px 0'} maxWidth={'1150px'}>
|
||||
<ExploreToolBar
|
||||
onFilterClick={this._setFilter}
|
||||
filters={this.state.filters}
|
||||
editorial={this.state.isEditorialShown}
|
||||
onEditorial={this._onEditorial}
|
||||
// editorial={this.state.isEditorialShown}
|
||||
// onEditorial={this._onEditorial}
|
||||
orderings={ORDERINGS}
|
||||
activeOrdering={this.state.tilesOrdering}
|
||||
onOrdering={this._onOrdering}
|
||||
@@ -81,7 +86,7 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
heading="Working on a 0x project?"
|
||||
subline="Lorem Ipsum something then that and say something more."
|
||||
mainCta={{ text: 'Apply Now', onClick: this._onOpenContactModal }}
|
||||
secondaryCta={{ text: 'Join Discord', href: 'https://discordapp.com/invite/d3FTX3M' }}
|
||||
secondaryCta={{ text: 'Join Discord', href: constants.URL_ZEROEX_CHAT }}
|
||||
/>
|
||||
<ModalContact
|
||||
isOpen={this.state.isContactModalOpen}
|
||||
@@ -100,23 +105,21 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
this.setState({ isContactModalOpen: false });
|
||||
};
|
||||
|
||||
private readonly _onEditorial = (newValue: boolean): void => {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Editorial, {
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
private readonly _onEditorial = async (newValue: boolean): Promise<void> => {
|
||||
const newTiles = await this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Editorial, {
|
||||
isEditorialShown: newValue,
|
||||
}).then(newTiles => {
|
||||
this.setState({ isEditorialShown: newValue, tiles: newTiles });
|
||||
});
|
||||
this.setState({ isEditorialShown: newValue, tiles: newTiles });
|
||||
};
|
||||
|
||||
private readonly _onOrdering = (newValue: string) => {
|
||||
private readonly _onOrdering = async (newValue: string): Promise<void> => {
|
||||
this.setState({ tilesOrdering: newValue });
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Ordering, {
|
||||
const newTiles = await this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Ordering, {
|
||||
tilesOrdering: newValue as ExploreTilesOrdering,
|
||||
}).then(newTiles => {
|
||||
this.setState({ tilesOrdering: newValue, tiles: newTiles });
|
||||
});
|
||||
this.setState({ tilesOrdering: newValue, tiles: newTiles });
|
||||
};
|
||||
|
||||
private readonly _launchInstantAsync = (params: ExploreProjectInstantMetadata): void => {
|
||||
@@ -125,6 +128,16 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
|
||||
private readonly _onAnalytics = (project: ExploreProject, action: ExploreAnalyticAction): void => {
|
||||
// Do Something
|
||||
switch (action) {
|
||||
case ExploreAnalyticAction.InstantClick:
|
||||
analytics.track('Explore - Instant - Clicked', { name: project.name });
|
||||
break;
|
||||
case ExploreAnalyticAction.LinkClick:
|
||||
analytics.track('Explore - Link - Clicked', { name: project.name });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
private _generateEditorialContent(): void {
|
||||
@@ -155,24 +168,24 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
return this.state.tiles;
|
||||
};
|
||||
|
||||
private readonly _changeSearchResults = (query: string): void => {
|
||||
const trimmedQuery = query.trim().toLowerCase();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Search, {
|
||||
query: trimmedQuery,
|
||||
filter: this.state.filters.find(f => f.active),
|
||||
})
|
||||
.then(async newTiles =>
|
||||
this._generateTilesWithModifier(newTiles, ExploreTilesModifiers.Editorial, {
|
||||
isEditorialShown: _.isEmpty(trimmedQuery) ? this.state.isEditorialShown : false,
|
||||
}),
|
||||
)
|
||||
.then(newTiles => {
|
||||
this.setState({ query: trimmedQuery, tiles: newTiles });
|
||||
});
|
||||
private readonly _setNewQuery = (query: string): void => {
|
||||
this.setState({ query });
|
||||
this._debouncedChangeSearchResults(query);
|
||||
};
|
||||
|
||||
private readonly _setFilter = (filterName: string, active: boolean = true): void => {
|
||||
private readonly _changeSearchResults = async (query: string): Promise<void> => {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
const searchedTiles = await this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Search, {
|
||||
query,
|
||||
filter: this.state.filters.find(f => f.active),
|
||||
});
|
||||
// const newTiles = this._generateTilesWithModifier(searchedTiles, ExploreTilesModifiers.Editorial, {
|
||||
// isEditorialShown: _.isEmpty(query) ? this.state.isEditorialShown : false,
|
||||
// })
|
||||
this.setState({ tiles: searchedTiles });
|
||||
};
|
||||
|
||||
private readonly _setFilter = async (filterName: string, active: boolean = true): Promise<void> => {
|
||||
let updatedFilters: ExploreFilterMetadata[];
|
||||
updatedFilters = this.state.filters.map(f => {
|
||||
const newFilter = _.assign({}, f);
|
||||
@@ -181,14 +194,18 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
});
|
||||
// If no filters are enabled, default to all
|
||||
if (_.filter(updatedFilters, f => f.active).length === 0) {
|
||||
this._setFilter('all');
|
||||
await this._setFilter('all');
|
||||
} else {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._generateTilesWithModifier(this.state.tiles, ExploreTilesModifiers.Filter, {
|
||||
const newTiles = await this._generateTilesWithModifier(
|
||||
this.state.tiles,
|
||||
_.isEmpty(this.state.query) ? ExploreTilesModifiers.Filter : ExploreTilesModifiers.Search,
|
||||
{
|
||||
filter: updatedFilters.find(f => f.active),
|
||||
}).then(newTiles => {
|
||||
query: this.state.query,
|
||||
},
|
||||
);
|
||||
this.setState({ filters: updatedFilters, tiles: newTiles });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,6 +233,7 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
modifier: ExploreTilesModifiers,
|
||||
options: ExploreModifierOptions,
|
||||
): Promise<ExploreTile[]> => {
|
||||
const trimmedQuery = modifier === ExploreTilesModifiers.Search ? options.query.trim().toLowerCase() : '';
|
||||
if (!this._verifyExploreTilesModifierOptions(modifier, options)) {
|
||||
return tiles;
|
||||
}
|
||||
@@ -244,7 +262,7 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
newTile.visibility === ExploreTileVisibility.Visible
|
||||
) {
|
||||
newTile.visibility =
|
||||
(_.startsWith(newTile.exploreProject.label.toLowerCase(), options.query) &&
|
||||
(_.startsWith(newTile.exploreProject.label.toLowerCase(), trimmedQuery) &&
|
||||
ExploreTileVisibility.Visible) ||
|
||||
ExploreTileVisibility.Hidden;
|
||||
}
|
||||
@@ -280,7 +298,7 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
tilesOrdering: this.state.tilesOrdering,
|
||||
});
|
||||
this.setState({ tiles: orderedTiles, isEntriesLoading: false }, () => {
|
||||
this._generateEditorialContent();
|
||||
// this._generateEditorialContent();
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -292,22 +310,21 @@ const ExploreHeroContentWrapper = styled.div`
|
||||
`;
|
||||
|
||||
interface ExploreHeroProps {
|
||||
query: string;
|
||||
onSearch(query: string): void;
|
||||
}
|
||||
|
||||
const ExploreHero = (props: ExploreHeroProps) => {
|
||||
// tslint:disable-next-line:no-unbound-method
|
||||
const onSearchDebounce = _.debounce(props.onSearch, 300);
|
||||
const onChange = (e: any) => {
|
||||
onSearchDebounce(e.target.value);
|
||||
props.onSearch(e.target.value);
|
||||
};
|
||||
return (
|
||||
<Section maxWidth={'1150px'}>
|
||||
<Section maxWidth={'1150px'} padding={'50px 0 50px 0'}>
|
||||
<ExploreHeroContentWrapper>
|
||||
<Heading isNoMargin={true} size="large">
|
||||
Explore 0x
|
||||
</Heading>
|
||||
<SearchInput onChange={onChange} width={'28rem'} placeholder="Search tokens, relayers, and dApps..." />
|
||||
<SearchInput value={props.query} onChange={onChange} width={'22rem'} placeholder="Search..." />
|
||||
</ExploreHeroContentWrapper>
|
||||
</Section>
|
||||
);
|
||||
@@ -320,7 +337,7 @@ const ExploreToolBarWrapper = styled.div`
|
||||
|
||||
const ExploreToolBarContentWrapper = styled.div`
|
||||
display: flex;
|
||||
padding: 2rem 0;
|
||||
padding-bottom: 2rem;
|
||||
& > * {
|
||||
margin: 0 0.3rem;
|
||||
}
|
||||
@@ -336,9 +353,9 @@ interface ExploreToolBarProps {
|
||||
filters: ExploreFilterMetadata[];
|
||||
activeOrdering: ExploreTilesOrdering;
|
||||
orderings: ExploreTilesOrderingMetadata[];
|
||||
editorial: boolean;
|
||||
editorial?: boolean;
|
||||
onOrdering: (newValue: string) => void;
|
||||
onEditorial: (newValue: boolean) => void;
|
||||
onEditorial?: (newValue: boolean) => void;
|
||||
onFilterClick(filterName: string, active: boolean): void;
|
||||
}
|
||||
|
||||
@@ -358,9 +375,7 @@ const ExploreToolBar = (props: ExploreToolBarProps) => {
|
||||
);
|
||||
})}
|
||||
</ExploreToolBarContentWrapper>
|
||||
<ExploreToolBarContentWrapper>
|
||||
<ExploreSettingsDropdown {...props} />
|
||||
</ExploreToolBarContentWrapper>
|
||||
</ExploreToolBarWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -99,23 +99,30 @@ export const FILTERS: ExploreFilterMetadata[] = [
|
||||
label: 'Relayer',
|
||||
name: 'relayer',
|
||||
filterType: ExploreFilterType.Keyword,
|
||||
keyword: 'relayer',
|
||||
},
|
||||
{
|
||||
label: 'Collectibles',
|
||||
name: 'collectibles',
|
||||
filterType: ExploreFilterType.Keyword,
|
||||
keyword: 'ERC-721',
|
||||
},
|
||||
];
|
||||
|
||||
export const ORDERINGS: ExploreTilesOrderingMetadata[] = [
|
||||
{ label: 'None', ordering: ExploreTilesOrdering.None, type: ExploreTilesOrderingType.HardCodedByName },
|
||||
{ label: 'Latest', ordering: ExploreTilesOrdering.Latest, type: ExploreTilesOrderingType.HardCodedByName },
|
||||
{ label: 'Popular', ordering: ExploreTilesOrdering.Popular, type: ExploreTilesOrderingType.HardCodedByName },
|
||||
{
|
||||
label: 'Recently Added',
|
||||
ordering: ExploreTilesOrdering.RecentlyAdded,
|
||||
type: ExploreTilesOrderingType.HardCodedByName,
|
||||
},
|
||||
{
|
||||
label: 'Alphabetical',
|
||||
ordering: ExploreTilesOrdering.Alphabetical,
|
||||
type: ExploreTilesOrderingType.HardCodedByName,
|
||||
},
|
||||
];
|
||||
|
||||
export const BY_NAME_ORDERINGS: { [s: string]: string[] } = {
|
||||
[ExploreTilesOrdering.Popular]: ['boxswap', 'veil', 'paradex', 'emoon', 'radar_relay', 'openrelay'],
|
||||
[ExploreTilesOrdering.Latest]: ['veil', 'boxswap', 'emoon', 'paradex', 'radar_relay', 'openrelay'],
|
||||
[ExploreTilesOrdering.RecentlyAdded]: ['veil', 'boxswap', 'emoon', 'paradex', 'radar_relay', 'openrelay'],
|
||||
[ExploreTilesOrdering.Alphabetical]: ['veil', 'boxswap', 'emoon', 'paradex', 'radar_relay', 'openrelay'],
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import styled from 'styled-components';
|
||||
import { Icon } from 'ts/components/icon';
|
||||
import { Heading } from 'ts/components/text';
|
||||
import { Switch } from 'ts/components/ui/switch';
|
||||
import { Button as ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
|
||||
import { ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
|
||||
import { colors } from 'ts/style/colors';
|
||||
import { ExploreTilesOrdering, ExploreTilesOrderingMetadata } from 'ts/types';
|
||||
|
||||
@@ -32,7 +32,7 @@ const ExploreSettingsDropdownButton = ({}) => {
|
||||
<SettingsIconWrapper>
|
||||
<Icon color={colors.grey} name="settings" size={16} />
|
||||
</SettingsIconWrapper>
|
||||
Settings
|
||||
Sort
|
||||
</ExploreTagButton>
|
||||
);
|
||||
};
|
||||
@@ -65,7 +65,7 @@ interface DropdownWrapInterface {
|
||||
|
||||
const DropdownWrap = styled.div<DropdownWrapInterface>`
|
||||
width: ${props => props.width || 280}px;
|
||||
margin-top: 16px;
|
||||
margin-top: calc(16px - 2rem);
|
||||
padding: 16px 24px;
|
||||
border: 1px solid transparent;
|
||||
border-color: ${props => props.theme.dropdownBorderColor};
|
||||
@@ -107,27 +107,29 @@ const DropdownWrap = styled.div<DropdownWrapInterface>`
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
// display: block;
|
||||
// visibility: visible;
|
||||
// opacity: 1;
|
||||
// transform: translate3d(0, 0, 0);
|
||||
// transition: opacity 0.35s, transform 0.35s, visibility 0s;
|
||||
`;
|
||||
|
||||
export interface ExploreSettingsDropdownProps {
|
||||
activeOrdering: ExploreTilesOrdering;
|
||||
orderings: ExploreTilesOrderingMetadata[];
|
||||
onOrdering: (newValue: string) => void;
|
||||
onEditorial: (newValue: boolean) => void;
|
||||
editorial: boolean;
|
||||
onEditorial?: (newValue: boolean) => void;
|
||||
editorial?: boolean;
|
||||
}
|
||||
|
||||
export class ExploreSettingsDropdown extends React.Component<ExploreSettingsDropdownProps> {
|
||||
constructor(props: ExploreSettingsDropdownProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<SettingsWrapper>
|
||||
<ExploreSettingsDropdownButton />
|
||||
<DropdownWrap>
|
||||
<DropdownContentWrapper>
|
||||
{!!this.props.onEditorial &&
|
||||
<div>
|
||||
<Switch
|
||||
isChecked={this.props.editorial}
|
||||
@@ -137,11 +139,11 @@ export class ExploreSettingsDropdown extends React.Component<ExploreSettingsDrop
|
||||
<Heading asElement="h4" size={14} color="inherit" marginBottom="0" isMuted={0.35}>
|
||||
Editorial content reflects the views of the 0x core team.
|
||||
</Heading>
|
||||
</div>
|
||||
</div>}
|
||||
<OrderingWrapper>
|
||||
<Heading asElement="h4" size={14} color="inherit" marginBottom="16px" isMuted={0.35}>
|
||||
{/* <Heading asElement="h4" size={14} color="inherit" marginBottom="16px" isMuted={0.35}>
|
||||
Ordering
|
||||
</Heading>
|
||||
</Heading> */}
|
||||
<OrderingListWrapper>
|
||||
{this.props.orderings.map(o => {
|
||||
const onClick = () => this.props.onOrdering(o.ordering);
|
||||
@@ -167,19 +169,18 @@ export class ExploreSettingsDropdown extends React.Component<ExploreSettingsDrop
|
||||
const DropdownContentWrapper = styled.div``;
|
||||
|
||||
const OrderingWrapper = styled.div`
|
||||
padding-top: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${props => props.theme.dropdownColor};
|
||||
opacity: 0.15;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
// padding-top: 20px;
|
||||
// margin-bottom: 20px;
|
||||
// &:before {
|
||||
// content: '';
|
||||
// width: 100%;
|
||||
// height: 1px;
|
||||
// background-color: ${props => props.theme.dropdownColor};
|
||||
// opacity: 0.15;
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// }
|
||||
`;
|
||||
|
||||
@@ -15,10 +15,6 @@ interface RicherExploreGridListTile extends ExploreTile {
|
||||
}
|
||||
|
||||
export class ExploreGrid extends React.Component<ExptoreGridProps> {
|
||||
constructor(props: ExptoreGridProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<ExploreGridList>
|
||||
@@ -79,6 +75,9 @@ const ExploreGridList = styled.div<ExploreGridListProps>`
|
||||
grid-template-columns: repeat(${ExploreTileWidth.FullWidth}, 1fr);
|
||||
grid-column-gap: 1.5rem;
|
||||
grid-row-gap: 1.5rem;
|
||||
& > * {
|
||||
align-self: stretch;
|
||||
}
|
||||
@media (max-width: 56rem) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -6,11 +7,9 @@ import { Heading, Paragraph } from 'ts/components/text';
|
||||
import { Image } from 'ts/components/ui/image';
|
||||
import { ExploreAnalyticAction, ExploreProject } from 'ts/types';
|
||||
|
||||
const EMPTY_FUNCTION = () => true;
|
||||
|
||||
export const ExploreGridTile = (props: ExploreProject) => {
|
||||
// tslint:disable-next-line:no-unbound-method
|
||||
const onAnalytics = props.onAnalytics || EMPTY_FUNCTION;
|
||||
const onAnalytics = props.onAnalytics || _.noop;
|
||||
const onInstantClick = !!props.instant
|
||||
? () => {
|
||||
props.onInstantClick();
|
||||
@@ -21,9 +20,9 @@ export const ExploreGridTile = (props: ExploreProject) => {
|
||||
onAnalytics(ExploreAnalyticAction.LinkClick);
|
||||
};
|
||||
return (
|
||||
<ExploreGridTileWrapper>
|
||||
<ExploreGridTileWrapper style={{ border: 'none' }}>
|
||||
{!!onInstantClick && (
|
||||
<ExploreGridButtonWrapper>
|
||||
<ExploreGridButtonWrapper className="explore-grid-instant-button-wrapper">
|
||||
<Button onClick={onInstantClick} padding={'12px 18px'} bgColor={'white'}>
|
||||
Instant Trade
|
||||
</Button>
|
||||
@@ -59,21 +58,32 @@ const ExploreGridHeroWell = styled.div<ExploreGridHeroWellProps>`
|
||||
|
||||
const ExploreGridContentWell = styled.div`
|
||||
padding: 1.5rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-top: 0;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const ExploreGridTileLink = styled.a`
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const ExploreGridTileWrapper = styled.div`
|
||||
display: block;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
height: 100%;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
// transition: box-shadow 200ms ease-in-out;
|
||||
// &:hover {
|
||||
// box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1);
|
||||
// }
|
||||
& .explore-grid-instant-button-wrapper {
|
||||
transition: opacity 0.2s, visibility 0.2s 0s;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover .explore-grid-instant-button-wrapper {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
`;
|
||||
|
||||
const ExploreGridButtonWrapper = styled.div`
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface ButtonInterface {
|
||||
theme?: ThemeInterface;
|
||||
}
|
||||
|
||||
export const Button: React.StatelessComponent<ButtonInterface> = (props: ButtonInterface) => {
|
||||
export const ExploreTagButton: React.StatelessComponent<ButtonInterface> = (props: ButtonInterface) => {
|
||||
const { children, isDisabled, className } = props;
|
||||
|
||||
const buttonProps = { disabled: isDisabled };
|
||||
@@ -29,7 +29,7 @@ export const Button: React.StatelessComponent<ButtonInterface> = (props: ButtonI
|
||||
);
|
||||
};
|
||||
|
||||
Button.defaultProps = {};
|
||||
ExploreTagButton.defaultProps = {};
|
||||
|
||||
const ButtonBase = styled.button<ButtonInterface>`
|
||||
appearance: none;
|
||||
|
||||
@@ -265,8 +265,8 @@ export enum ExploreAnalyticAction {
|
||||
}
|
||||
|
||||
export enum ExploreTilesOrdering {
|
||||
None = 'NONE',
|
||||
Latest = 'LATEST',
|
||||
Alphabetical = 'ALPHABETICAL',
|
||||
RecentlyAdded = 'RECENTLY_ADDED',
|
||||
Popular = 'POPULAR',
|
||||
}
|
||||
|
||||
@@ -297,7 +297,6 @@ export interface ExploreFilterMetadata {
|
||||
label: string;
|
||||
filterType: ExploreFilterType;
|
||||
name: string;
|
||||
keyword?: string;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user